diff --git a/configs/config.dev.yaml b/configs/config.dev.yaml index 4778b01..e31d840 100644 --- a/configs/config.dev.yaml +++ b/configs/config.dev.yaml @@ -19,9 +19,9 @@ docker: imageName: "lxh01/xybotv2:latest" network: "bridge" redis: - host: "localhost" - password: "password" - db: 0 + host: "10.0.0.31" + password: "pGhQKwj7DE7FbFL1" + db: 2 auth: secretKey: "dev-secret-key" diff --git a/go.mod b/go.mod index fcd18a0..48f5179 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( gorm.io/driver/postgres v1.5.11 gorm.io/driver/sqlite v1.5.7 gorm.io/gorm v1.25.12 + gorm.io/plugin/soft_delete v1.2.1 ) require ( diff --git a/go.sum b/go.sum index 6716ac0..8340cba 100644 --- a/go.sum +++ b/go.sum @@ -64,6 +64,8 @@ github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -81,6 +83,7 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= @@ -243,10 +246,15 @@ gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314= gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= +gorm.io/driver/sqlite v1.1.3/go.mod h1:AKDgRWk8lcSQSw+9kxCJnX/yySj8G3rdwYlU57cB45c= gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I= gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= +gorm.io/gorm v1.20.1/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= +gorm.io/gorm v1.23.0/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= +gorm.io/plugin/soft_delete v1.2.1 h1:qx9D/c4Xu6w5KT8LviX8DgLcB9hkKl6JC9f44Tj7cGU= +gorm.io/plugin/soft_delete v1.2.1/go.mod h1:Zv7vQctOJTGOsJ/bWgrN1n3od0GBAZgnLjEx+cApLGk= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/internal/docker/robot.go b/internal/docker/robot.go index 29f19c6..5278e20 100644 --- a/internal/docker/robot.go +++ b/internal/docker/robot.go @@ -44,7 +44,13 @@ type CheckUuidResponse struct { Success bool `json:"success"` Message string `json:"message"` Data struct { - Status int `json:"Status"` + Uuid string `json:"uuid"` + Status int `json:"status"` // 状态 + PushLoginUrlExpiredTime int `json:"pushLoginUrlexpiredTime"` // 推送登录url过期时间 + ExpiredTime int `json:"expiredTime"` // 过期时间(秒) + HeadImgUrl string `json:"headImgUrl"` // 头像 + NickName string `json:"nickName"` // 昵称 + AcctSectResp map[string]any `json:"acctSectResp"` // 账号信息-登录成功之后才有 } `json:"data"` } @@ -149,7 +155,7 @@ func GetQRCode(ctx context.Context, containerID string, containerHost string) (* } // CheckUuid 检查二维码状态 -func CheckUuid(ctx context.Context, containerID string, uuid string, containerHost string) (*CheckUuidResponse, error) { +func CheckUuid(ctx context.Context, uuid string, containerHost string) (*CheckUuidResponse, error) { client := newHTTPClient() url := fmt.Sprintf("http://%s/CheckUuid", containerHost) diff --git a/internal/handler/api_login.go b/internal/handler/api_login.go index 0e92b93..6c58f10 100644 --- a/internal/handler/api_login.go +++ b/internal/handler/api_login.go @@ -2,6 +2,10 @@ package handler import ( "context" + "encoding/json" + "errors" + "gitee.ltd/lxh/wechat-robot/internal/config" + "github.com/gofiber/fiber/v2/log" "strconv" "time" @@ -33,8 +37,8 @@ func CheckQRCodeStatus(c *fiber.Ctx) error { // 获取机器人实例 db := model.GetDB() var robot model.Robot - if err := db.First(&robot, id).Error; err != nil { - if err == gorm.ErrRecordNotFound { + if err = db.First(&robot, id).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { return c.JSON(fiber.Map{ "success": false, "message": "机器人不存在", @@ -51,7 +55,7 @@ func CheckQRCodeStatus(c *fiber.Ctx) error { defer cancel() // 调用CheckUuid API检查二维码状态,传递容器访问地址 - response, err := docker.CheckUuid(ctx, robot.ContainerID, uuid, robot.ContainerHost) + response, err := docker.CheckUuid(ctx, uuid, robot.ContainerHost) if err != nil { return c.JSON(fiber.Map{ "success": false, @@ -59,8 +63,23 @@ func CheckQRCodeStatus(c *fiber.Ctx) error { }) } + if cfg, _ := config.Load(); cfg.Server.Env == "development" { + bs, _ := json.Marshal(response) + log.Debugf("扫码返回结果: %s", bs) + } + + // 如果返回status=1,表示已扫码,暂存一下昵称和头像 + if response.Data.Status == 1 { + robot.Nickname = response.Data.NickName + robot.Avatar = response.Data.HeadImgUrl + db.Save(&robot) + } + // 如果检测到已登录,更新机器人状态 - if response.Success && response.Data.Status == 2 { + if response.Success && response.Data.AcctSectResp != nil { + response.Data.Status = 99 + robot.WechatID = response.Data.AcctSectResp["userName"].(string) + // 开启自动心跳,传递容器访问地址 if robot.WechatID != "" { _, _ = docker.AutoHeartbeatStart(ctx, robot.ContainerID, robot.WechatID, robot.ContainerHost) @@ -74,8 +93,9 @@ func CheckQRCodeStatus(c *fiber.Ctx) error { } return c.JSON(fiber.Map{ - "success": response.Success, - "status": response.Data.Status, - "message": response.Message, + "success": response.Success, + "status": response.Data.Status, + "message": response.Message, + "userInfo": response.Data.AcctSectResp, }) } diff --git a/internal/handler/robot.go b/internal/handler/robot.go index ac58cac..86491f5 100644 --- a/internal/handler/robot.go +++ b/internal/handler/robot.go @@ -2,6 +2,7 @@ package handler import ( "context" + "errors" "log" "strconv" "strings" @@ -79,9 +80,9 @@ func CreateRobot(c *fiber.Ctx) error { } db := model.GetDB() - if err := db.Create(&robot).Error; err != nil { + if err = db.Create(&robot).Error; err != nil { // 如果数据库创建失败,尝试删除容器 - docker.RemoveContainer(ctx, containerID, true) + _ = docker.RemoveContainer(ctx, containerID, true) return fiber.NewError(fiber.StatusInternalServerError, "保存机器人信息失败: "+err.Error()) } @@ -133,7 +134,7 @@ func DeleteRobot(c *fiber.Ctx) error { var robot model.Robot db := model.GetDB() - if err := db.First(&robot, id).Error; err != nil { + if err = db.First(&robot, id).Error; err != nil { // 针对API请求返回JSON if strings.HasPrefix(c.Get("Accept"), "application/json") || c.Method() == "DELETE" { return c.Status(fiber.StatusNotFound).JSON(fiber.Map{ @@ -141,7 +142,7 @@ func DeleteRobot(c *fiber.Ctx) error { "message": "机器人不存在", }) } - if err == gorm.ErrRecordNotFound { + if errors.Is(err, gorm.ErrRecordNotFound) { return fiber.NewError(fiber.StatusNotFound, "机器人不存在") } return fiber.NewError(fiber.StatusInternalServerError, "查询数据库失败") @@ -152,14 +153,14 @@ func DeleteRobot(c *fiber.Ctx) error { defer cancel() if robot.Status == model.RobotStatusOnline { - if err := docker.LogoutWechatBot(ctx, robot.ContainerID); err != nil { + if err = docker.LogoutWechatBot(ctx, robot.ContainerID); err != nil { log.Printf("登出机器人失败: %v", err) // 继续删除流程,不因登出失败而中断 } } // 删除容器 - if err := docker.RemoveContainer(ctx, robot.ContainerID, true); err != nil { + if err = docker.RemoveContainer(ctx, robot.ContainerID, true); err != nil { log.Printf("删除容器失败: %v", err) // 继续删除流程,不因容器删除失败而中断 } @@ -169,7 +170,7 @@ func DeleteRobot(c *fiber.Ctx) error { monitor.RemoveRobot(robot.ContainerID) // 删除数据库记录 - if err := db.Delete(&robot).Error; err != nil { + if err = db.Delete(&robot).Error; err != nil { // 针对API请求返回JSON if strings.HasPrefix(c.Get("Accept"), "application/json") || c.Method() == "DELETE" { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ @@ -201,13 +202,16 @@ func RobotLogin(c *fiber.Ctx) error { var robot model.Robot db := model.GetDB() - - if err := db.First(&robot, id).Error; err != nil { - if err == gorm.ErrRecordNotFound { + if err = db.First(&robot, id).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { return fiber.NewError(fiber.StatusNotFound, "机器人不存在") } return fiber.NewError(fiber.StatusInternalServerError, "查询数据库失败") } + // 处理一下设备信息 + if robot.CheckDevice() { + db.Save(&robot) + } // 获取登录二维码 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) @@ -219,12 +223,6 @@ func RobotLogin(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusInternalServerError, "获取登录二维码失败: "+err.Error()) } - // 保存二维码相关信息到数据库 - robot.QRCodePath = qrcodeResp.Data.QRCodeBase64 - if err := db.Save(&robot).Error; err != nil { - return fiber.NewError(fiber.StatusInternalServerError, "保存二维码信息失败") - } - // 渲染登录页面,包含二维码和UUID(用于后续状态检查) return c.Render("robot/login", fiber.Map{ "Title": "微信登录", @@ -245,8 +243,8 @@ func RobotLogout(c *fiber.Ctx) error { var robot model.Robot db := model.GetDB() - if err := db.First(&robot, id).Error; err != nil { - if err == gorm.ErrRecordNotFound { + if err = db.First(&robot, id).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { return fiber.NewError(fiber.StatusNotFound, "机器人不存在") } return fiber.NewError(fiber.StatusInternalServerError, "查询数据库失败") @@ -258,20 +256,19 @@ func RobotLogout(c *fiber.Ctx) error { // 使用新的LogOut API接口,传递容器访问地址 if robot.WechatID != "" { - if _, err := docker.LogOut(ctx, robot.ContainerID, robot.WechatID, robot.ContainerHost); err != nil { + if _, err = docker.LogOut(ctx, robot.ContainerID, robot.WechatID, robot.ContainerHost); err != nil { return fiber.NewError(fiber.StatusInternalServerError, "登出微信失败: "+err.Error()) } } else { // 如果没有WechatID,使用原来的方法 - if err := docker.LogoutWechatBot(ctx, robot.ContainerID); err != nil { + if err = docker.LogoutWechatBot(ctx, robot.ContainerID); err != nil { return fiber.NewError(fiber.StatusInternalServerError, "登出微信失败: "+err.Error()) } } // 更新机器人状态 robot.Status = model.RobotStatusOffline - robot.QRCodePath = "" - if err := db.Save(&robot).Error; err != nil { + if err = db.Save(&robot).Error; err != nil { return fiber.NewError(fiber.StatusInternalServerError, "更新状态失败") } diff --git a/internal/model/base.go b/internal/model/base.go index 2dce08b..c86b803 100644 --- a/internal/model/base.go +++ b/internal/model/base.go @@ -3,13 +3,14 @@ package model import ( "time" - "gorm.io/gorm" + "gorm.io/plugin/soft_delete" ) // BaseModel 是所有模型的基础结构 type BaseModel struct { - ID uint `gorm:"primarykey" json:"id"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + ID uint `gorm:"primarykey" json:"id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt int64 `json:"-" gorm:"index:deleted; default:0"` + IsDel soft_delete.DeletedAt `json:"-" gorm:"softDelete:flag,DeletedAtField:DeletedAt; index:deleted; default:0; type:tinyint(1)"` } diff --git a/internal/model/robot.go b/internal/model/robot.go index e189049..a1d79c5 100644 --- a/internal/model/robot.go +++ b/internal/model/robot.go @@ -1,6 +1,7 @@ package model import ( + "gitee.ltd/lxh/wechat-robot/internal/utils" "time" "gorm.io/gorm" @@ -18,15 +19,16 @@ const ( // Robot 表示微信机器人实例 type Robot struct { BaseModel - ContainerID string `gorm:"column:container_id;uniqueIndex:idx_container_id,length:64" json:"container_id"` + ContainerID string `gorm:"column:container_id;index:deleted,unique,length:64" json:"container_id"` ShortContainerID string `gorm:"column:short_container_id;index" json:"short_container_id"` // 容器ID的前12位 ContainerHost string `gorm:"column:container_host" json:"container_host"` // 容器访问地址,格式为ip:port - WechatID string `gorm:"column:wechat_id;index:idx_wechat_id,length:64" json:"wechat_id"` + DeviceId string `gorm:"column:device_id;index:deleted,unique;" json:"deviceId"` // 设备Id + DeviceName string `gorm:"column:device_name" json:"deviceName"` // 设备名称 + WechatID string `gorm:"column:wechat_id;index:deleted,unique,length:64" json:"wechat_id"` Nickname string `gorm:"column:nickname" json:"nickname"` Avatar string `gorm:"column:avatar" json:"avatar"` Status RobotStatus `gorm:"column:status;default:'offline'" json:"status"` LastLoginAt *time.Time `gorm:"column:last_login_at" json:"last_login_at"` - QRCodePath string `gorm:"column:qrcode_path" json:"qrcode_path"` // 二维码路径,用于登录 ErrorMessage string `gorm:"column:error_message" json:"error_message"` } @@ -68,3 +70,22 @@ func (r *Robot) BeforeDelete(tx *gorm.DB) error { // 在删除机器人之前,可以处理相关依赖 return nil } + +// CheckDevice +// @description: 检查设备信息 +// @receiver r +// @return bool 是否重新处理过,如果是,就返回true +func (r *Robot) CheckDevice() (flag bool) { + // 如果是空的,就获取环境变量配置的值,如果还是空的,就生成一个新的 + if r.DeviceId == "" { + r.DeviceId = utils.GetDeviceId() + flag = true + } + + // 如果是空的,就获取环境变量配置的值,如果还是空的,就生成一个新的 + if r.DeviceName == "" { + r.DeviceName = utils.GetDeviceName() + flag = true + } + return +} diff --git a/internal/utils/device.go b/internal/utils/device.go new file mode 100644 index 0000000..9626008 --- /dev/null +++ b/internal/utils/device.go @@ -0,0 +1,66 @@ +package utils + +import ( + "crypto/md5" + "encoding/hex" + "math/rand" + "strings" + "time" +) + +// GetDeviceId +// @description: 生成设备ID +// @return deviceId 设备ID +func GetDeviceId() (deviceId string) { + // 创建一个有种子的随机数生成器 + r := rand.New(rand.NewSource(time.Now().UnixNano())) + // 定义可能的字母 + letters := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + // 初始化字符串构建器 + var sb strings.Builder + // 生成15个随机字母 + for i := 0; i < 15; i++ { + sb.WriteByte(letters[r.Intn(len(letters))]) + } + // 计算MD5哈希 + hash := md5.Sum([]byte(sb.String())) + hashString := hex.EncodeToString(hash[:]) + + // 返回49加上MD5哈希的第3个字符开始的部分 + return "49" + hashString[2:] +} + +// GetDeviceName +// @description: 生成设备名称 +// @return deviceName 设备名称 +func GetDeviceName() (deviceName string) { + firstNames := []string{ + "Oliver", "Emma", "Liam", "Ava", "Noah", "Sophia", "Elijah", "Isabella", + "James", "Mia", "William", "Amelia", "Benjamin", "Harper", "Lucas", "Evelyn", + "Henry", "Abigail", "Alexander", "Ella", "Jackson", "Scarlett", "Sebastian", + "Grace", "Aiden", "Chloe", "Matthew", "Zoey", "Samuel", "Lily", "David", + "Aria", "Joseph", "Riley", "Carter", "Nora", "Owen", "Luna", "Daniel", + "Sofia", "Gabriel", "Ellie", "Matthew", "Avery", "Isaac", "Mila", "Leo", + "Julian", "Layla", + } + + lastNames := []string{ + "Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", + "Rodriguez", "Martinez", "Hernandez", "Lopez", "Gonzalez", "Wilson", "Anderson", + "Thomas", "Taylor", "Moore", "Jackson", "Martin", "Lee", "Perez", "Thompson", + "White", "Harris", "Sanchez", "Clark", "Ramirez", "Lewis", "Robinson", "Walker", + "Young", "Allen", "King", "Wright", "Scott", "Torres", "Nguyen", "Hill", + "Flores", "Green", "Adams", "Nelson", "Baker", "Hall", "Rivera", "Campbell", + "Mitchell", "Carter", "Roberts", "Gomez", "Phillips", "Evans", + } + + // 使用随机数生成器 + r := rand.New(rand.NewSource(time.Now().UnixNano())) + + // 随机选择名字和姓氏 + firstName := firstNames[r.Intn(len(firstNames))] + lastName := lastNames[r.Intn(len(lastNames))] + + // 返回组合后的字符串 + return firstName + " " + lastName + "'s Pad" +} diff --git a/internal/view/robot/login.html b/internal/view/robot/login.html index 65bfda3..c537bba 100644 --- a/internal/view/robot/login.html +++ b/internal/view/robot/login.html @@ -13,12 +13,12 @@

{{.Robot.Nickname}}

请使用微信扫描二维码登录

- +
Login QR Code
- +
@@ -28,15 +28,15 @@ 等待扫描
- +

请打开微信,使用"扫一扫"功能扫描上方二维码登录

- +
二维码有效期: 120
- +
取消 @@ -47,7 +47,7 @@
- +

请确保手机与电脑在同一网络环境

@@ -59,30 +59,30 @@ const timer = document.getElementById('timer'); const countdown = document.getElementById('countdown'); const refreshQrcode = document.getElementById('refresh-qrcode'); - + let secondsLeft = 120; let statusCheckInterval; - + // 开始倒计时 const countdownTimer = setInterval(() => { secondsLeft--; countdown.textContent = secondsLeft; - + if (secondsLeft <= 0) { clearInterval(countdownTimer); clearInterval(statusCheckInterval); - + qrcodeStatus.innerHTML = ` 二维码已过期 `; - + refreshQrcode.classList.remove('hidden'); } }, 1000); - + // 检查扫码状态 function checkQRCodeStatus() { fetch(`/admin/robots/{{.Robot.ID}}/check-qrcode?uuid={{.UUID}}`) @@ -110,17 +110,17 @@ 已扫描,等待确认 `; - } else if (data.status === 2) { + } else if (data.status === 99 && data.userInfo && Object.keys(data.userInfo).length > 0) { qrcodeStatus.innerHTML = ` 登录成功 `; - + clearInterval(countdownTimer); clearInterval(statusCheckInterval); - + // 重定向到机器人详情页 setTimeout(() => { window.location.href = `/admin/robots/{{.Robot.ID}}`; @@ -134,10 +134,10 @@ console.error('Error:', error); }); } - + // 每3秒检查一次扫码状态 statusCheckInterval = setInterval(checkQRCodeStatus, 3000); - + // 立即执行一次检查 checkQRCodeStatus(); }); diff --git a/migrations/README.md b/migrations/README.md deleted file mode 100644 index 66a5300..0000000 --- a/migrations/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# 数据库迁移 - -此目录包含数据库迁移文件: -- 表结构创建 -- 数据初始化 -- 架构升级脚本 diff --git a/static/README.md b/static/README.md deleted file mode 100644 index 8e1c8f5..0000000 --- a/static/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# 静态资源 - -此目录包含Web应用的静态资源: -- CSS样式 -- JavaScript文件 -- 图片资源 -- 字体文件