🎨 优化登录逻辑,添加设备ID和名称生成,更新二维码状态处理
All checks were successful
BuildImage / build-image (push) Successful in 2m3s

This commit is contained in:
李寻欢 2025-04-07 13:51:21 +08:00
parent 37b766368f
commit 2f6b3fac01
12 changed files with 179 additions and 72 deletions

View File

@ -19,9 +19,9 @@ docker:
imageName: "lxh01/xybotv2:latest" imageName: "lxh01/xybotv2:latest"
network: "bridge" network: "bridge"
redis: redis:
host: "localhost" host: "10.0.0.31"
password: "password" password: "pGhQKwj7DE7FbFL1"
db: 0 db: 2
auth: auth:
secretKey: "dev-secret-key" secretKey: "dev-secret-key"

1
go.mod
View File

@ -14,6 +14,7 @@ require (
gorm.io/driver/postgres v1.5.11 gorm.io/driver/postgres v1.5.11
gorm.io/driver/sqlite v1.5.7 gorm.io/driver/sqlite v1.5.7
gorm.io/gorm v1.25.12 gorm.io/gorm v1.25.12
gorm.io/plugin/soft_delete v1.2.1
) )
require ( require (

8
go.sum
View File

@ -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/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 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 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 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 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-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 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 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 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 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= 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/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 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=
gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= 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 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= 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.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= 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/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 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=

View File

@ -44,7 +44,13 @@ type CheckUuidResponse struct {
Success bool `json:"success"` Success bool `json:"success"`
Message string `json:"message"` Message string `json:"message"`
Data struct { 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"` } `json:"data"`
} }
@ -149,7 +155,7 @@ func GetQRCode(ctx context.Context, containerID string, containerHost string) (*
} }
// CheckUuid 检查二维码状态 // 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() client := newHTTPClient()
url := fmt.Sprintf("http://%s/CheckUuid", containerHost) url := fmt.Sprintf("http://%s/CheckUuid", containerHost)

View File

@ -2,6 +2,10 @@ package handler
import ( import (
"context" "context"
"encoding/json"
"errors"
"gitee.ltd/lxh/wechat-robot/internal/config"
"github.com/gofiber/fiber/v2/log"
"strconv" "strconv"
"time" "time"
@ -33,8 +37,8 @@ func CheckQRCodeStatus(c *fiber.Ctx) error {
// 获取机器人实例 // 获取机器人实例
db := model.GetDB() db := model.GetDB()
var robot model.Robot var robot model.Robot
if err := db.First(&robot, id).Error; err != nil { if err = db.First(&robot, id).Error; err != nil {
if err == gorm.ErrRecordNotFound { if errors.Is(err, gorm.ErrRecordNotFound) {
return c.JSON(fiber.Map{ return c.JSON(fiber.Map{
"success": false, "success": false,
"message": "机器人不存在", "message": "机器人不存在",
@ -51,7 +55,7 @@ func CheckQRCodeStatus(c *fiber.Ctx) error {
defer cancel() defer cancel()
// 调用CheckUuid API检查二维码状态传递容器访问地址 // 调用CheckUuid API检查二维码状态传递容器访问地址
response, err := docker.CheckUuid(ctx, robot.ContainerID, uuid, robot.ContainerHost) response, err := docker.CheckUuid(ctx, uuid, robot.ContainerHost)
if err != nil { if err != nil {
return c.JSON(fiber.Map{ return c.JSON(fiber.Map{
"success": false, "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 != "" { if robot.WechatID != "" {
_, _ = docker.AutoHeartbeatStart(ctx, robot.ContainerID, robot.WechatID, robot.ContainerHost) _, _ = docker.AutoHeartbeatStart(ctx, robot.ContainerID, robot.WechatID, robot.ContainerHost)
@ -77,5 +96,6 @@ func CheckQRCodeStatus(c *fiber.Ctx) error {
"success": response.Success, "success": response.Success,
"status": response.Data.Status, "status": response.Data.Status,
"message": response.Message, "message": response.Message,
"userInfo": response.Data.AcctSectResp,
}) })
} }

View File

@ -2,6 +2,7 @@ package handler
import ( import (
"context" "context"
"errors"
"log" "log"
"strconv" "strconv"
"strings" "strings"
@ -79,9 +80,9 @@ func CreateRobot(c *fiber.Ctx) error {
} }
db := model.GetDB() 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()) return fiber.NewError(fiber.StatusInternalServerError, "保存机器人信息失败: "+err.Error())
} }
@ -133,7 +134,7 @@ func DeleteRobot(c *fiber.Ctx) error {
var robot model.Robot var robot model.Robot
db := model.GetDB() db := model.GetDB()
if err := db.First(&robot, id).Error; err != nil { if err = db.First(&robot, id).Error; err != nil {
// 针对API请求返回JSON // 针对API请求返回JSON
if strings.HasPrefix(c.Get("Accept"), "application/json") || c.Method() == "DELETE" { if strings.HasPrefix(c.Get("Accept"), "application/json") || c.Method() == "DELETE" {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{ return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
@ -141,7 +142,7 @@ func DeleteRobot(c *fiber.Ctx) error {
"message": "机器人不存在", "message": "机器人不存在",
}) })
} }
if err == gorm.ErrRecordNotFound { if errors.Is(err, gorm.ErrRecordNotFound) {
return fiber.NewError(fiber.StatusNotFound, "机器人不存在") return fiber.NewError(fiber.StatusNotFound, "机器人不存在")
} }
return fiber.NewError(fiber.StatusInternalServerError, "查询数据库失败") return fiber.NewError(fiber.StatusInternalServerError, "查询数据库失败")
@ -152,14 +153,14 @@ func DeleteRobot(c *fiber.Ctx) error {
defer cancel() defer cancel()
if robot.Status == model.RobotStatusOnline { 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) 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) log.Printf("删除容器失败: %v", err)
// 继续删除流程,不因容器删除失败而中断 // 继续删除流程,不因容器删除失败而中断
} }
@ -169,7 +170,7 @@ func DeleteRobot(c *fiber.Ctx) error {
monitor.RemoveRobot(robot.ContainerID) monitor.RemoveRobot(robot.ContainerID)
// 删除数据库记录 // 删除数据库记录
if err := db.Delete(&robot).Error; err != nil { if err = db.Delete(&robot).Error; err != nil {
// 针对API请求返回JSON // 针对API请求返回JSON
if strings.HasPrefix(c.Get("Accept"), "application/json") || c.Method() == "DELETE" { if strings.HasPrefix(c.Get("Accept"), "application/json") || c.Method() == "DELETE" {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
@ -201,13 +202,16 @@ func RobotLogin(c *fiber.Ctx) error {
var robot model.Robot var robot model.Robot
db := model.GetDB() db := model.GetDB()
if err = db.First(&robot, id).Error; err != nil {
if err := db.First(&robot, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) {
if err == gorm.ErrRecordNotFound {
return fiber.NewError(fiber.StatusNotFound, "机器人不存在") return fiber.NewError(fiber.StatusNotFound, "机器人不存在")
} }
return fiber.NewError(fiber.StatusInternalServerError, "查询数据库失败") return fiber.NewError(fiber.StatusInternalServerError, "查询数据库失败")
} }
// 处理一下设备信息
if robot.CheckDevice() {
db.Save(&robot)
}
// 获取登录二维码 // 获取登录二维码
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 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()) 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用于后续状态检查 // 渲染登录页面包含二维码和UUID用于后续状态检查
return c.Render("robot/login", fiber.Map{ return c.Render("robot/login", fiber.Map{
"Title": "微信登录", "Title": "微信登录",
@ -245,8 +243,8 @@ func RobotLogout(c *fiber.Ctx) error {
var robot model.Robot var robot model.Robot
db := model.GetDB() db := model.GetDB()
if err := db.First(&robot, id).Error; err != nil { if err = db.First(&robot, id).Error; err != nil {
if err == gorm.ErrRecordNotFound { if errors.Is(err, gorm.ErrRecordNotFound) {
return fiber.NewError(fiber.StatusNotFound, "机器人不存在") return fiber.NewError(fiber.StatusNotFound, "机器人不存在")
} }
return fiber.NewError(fiber.StatusInternalServerError, "查询数据库失败") return fiber.NewError(fiber.StatusInternalServerError, "查询数据库失败")
@ -258,20 +256,19 @@ func RobotLogout(c *fiber.Ctx) error {
// 使用新的LogOut API接口传递容器访问地址 // 使用新的LogOut API接口传递容器访问地址
if robot.WechatID != "" { 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()) return fiber.NewError(fiber.StatusInternalServerError, "登出微信失败: "+err.Error())
} }
} else { } else {
// 如果没有WechatID使用原来的方法 // 如果没有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()) return fiber.NewError(fiber.StatusInternalServerError, "登出微信失败: "+err.Error())
} }
} }
// 更新机器人状态 // 更新机器人状态
robot.Status = model.RobotStatusOffline 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, "更新状态失败") return fiber.NewError(fiber.StatusInternalServerError, "更新状态失败")
} }

View File

@ -3,7 +3,7 @@ package model
import ( import (
"time" "time"
"gorm.io/gorm" "gorm.io/plugin/soft_delete"
) )
// BaseModel 是所有模型的基础结构 // BaseModel 是所有模型的基础结构
@ -11,5 +11,6 @@ type BaseModel struct {
ID uint `gorm:"primarykey" json:"id"` ID uint `gorm:"primarykey" json:"id"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` 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)"`
} }

View File

@ -1,6 +1,7 @@
package model package model
import ( import (
"gitee.ltd/lxh/wechat-robot/internal/utils"
"time" "time"
"gorm.io/gorm" "gorm.io/gorm"
@ -18,15 +19,16 @@ const (
// Robot 表示微信机器人实例 // Robot 表示微信机器人实例
type Robot struct { type Robot struct {
BaseModel 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位 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 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"` Nickname string `gorm:"column:nickname" json:"nickname"`
Avatar string `gorm:"column:avatar" json:"avatar"` Avatar string `gorm:"column:avatar" json:"avatar"`
Status RobotStatus `gorm:"column:status;default:'offline'" json:"status"` Status RobotStatus `gorm:"column:status;default:'offline'" json:"status"`
LastLoginAt *time.Time `gorm:"column:last_login_at" json:"last_login_at"` 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"` ErrorMessage string `gorm:"column:error_message" json:"error_message"`
} }
@ -68,3 +70,22 @@ func (r *Robot) BeforeDelete(tx *gorm.DB) error {
// 在删除机器人之前,可以处理相关依赖 // 在删除机器人之前,可以处理相关依赖
return nil 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
}

66
internal/utils/device.go Normal file
View File

@ -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"
}

View File

@ -110,7 +110,7 @@
已扫描,等待确认 已扫描,等待确认
</span> </span>
`; `;
} else if (data.status === 2) { } else if (data.status === 99 && data.userInfo && Object.keys(data.userInfo).length > 0) {
qrcodeStatus.innerHTML = ` qrcodeStatus.innerHTML = `
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-100 text-green-800"> <span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-100 text-green-800">
<i class="fas fa-check-circle mr-1"></i> <i class="fas fa-check-circle mr-1"></i>

View File

@ -1,6 +0,0 @@
# 数据库迁移
此目录包含数据库迁移文件:
- 表结构创建
- 数据初始化
- 架构升级脚本

View File

@ -1,7 +0,0 @@
# 静态资源
此目录包含Web应用的静态资源
- CSS样式
- JavaScript文件
- 图片资源
- 字体文件