328 lines
9.1 KiB
Go
328 lines
9.1 KiB
Go
package handler
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"gitee.ltd/lxh/xybot"
|
||
"github.com/gofiber/fiber/v2/log"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/gofiber/fiber/v2"
|
||
"gorm.io/gorm"
|
||
|
||
"gitee.ltd/lxh/wechat-robot/internal/docker"
|
||
"gitee.ltd/lxh/wechat-robot/internal/model"
|
||
)
|
||
|
||
// ListRobots 列出所有机器人
|
||
func ListRobots(c *fiber.Ctx) error {
|
||
log.Debugf("登录用户Id: %+v", c.Get("userId"))
|
||
|
||
db := model.GetDB()
|
||
var robots []model.Robot
|
||
|
||
if err := db.Find(&robots).Error; err != nil {
|
||
return fiber.NewError(fiber.StatusInternalServerError, "获取机器人列表失败")
|
||
}
|
||
|
||
total := len(robots)
|
||
online := 0
|
||
offline := 0
|
||
for _, robot := range robots {
|
||
if robot.Status == model.RobotStatusOnline {
|
||
online++
|
||
} else {
|
||
offline++
|
||
}
|
||
}
|
||
|
||
return c.Render("robot/index", fiber.Map{
|
||
"Title": "机器人列表",
|
||
"Robots": robots,
|
||
"Status": map[string]int{
|
||
"Total": total,
|
||
"Online": online,
|
||
"Offline": offline,
|
||
},
|
||
})
|
||
}
|
||
|
||
// NewRobotForm 显示创建机器人表单
|
||
func NewRobotForm(c *fiber.Ctx) error {
|
||
return c.Render("robot/new", fiber.Map{
|
||
"Title": "新建机器人",
|
||
})
|
||
}
|
||
|
||
// CreateRobot 创建新机器人
|
||
func CreateRobot(c *fiber.Ctx) error {
|
||
robotName := c.FormValue("name")
|
||
if robotName == "" {
|
||
return fiber.NewError(fiber.StatusBadRequest, "机器人名称不能为空")
|
||
}
|
||
|
||
// 获取可选的端口映射配置
|
||
port := 0
|
||
portStr := c.FormValue("port")
|
||
if portStr != "" {
|
||
var err error
|
||
port, err = strconv.Atoi(portStr)
|
||
if err != nil || port <= 0 || port > 65535 {
|
||
return fiber.NewError(fiber.StatusBadRequest, "端口必须是1-65535之间的有效数字")
|
||
}
|
||
}
|
||
|
||
// 创建Docker容器
|
||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||
defer cancel()
|
||
|
||
containerID, containerHost, err := docker.CreateRobotContainer(ctx, robotName, port)
|
||
if err != nil {
|
||
return fiber.NewError(fiber.StatusInternalServerError, "创建容器失败: "+err.Error())
|
||
}
|
||
|
||
// 创建机器人数据库记录
|
||
robot := model.Robot{
|
||
ContainerID: containerID,
|
||
ContainerHost: containerHost, // 使用新的字段名
|
||
Nickname: robotName,
|
||
Status: model.RobotStatusOffline,
|
||
}
|
||
// 处理一下设备 Id 和设备名称,防止后面出现空值
|
||
robot.CheckDevice()
|
||
|
||
db := model.GetDB()
|
||
if err = db.Create(&robot).Error; err != nil {
|
||
// 如果数据库创建失败,尝试删除容器
|
||
_ = docker.RemoveContainer(ctx, containerID, true)
|
||
return fiber.NewError(fiber.StatusInternalServerError, "保存机器人信息失败: "+err.Error())
|
||
}
|
||
|
||
return c.Redirect("/admin/robots/" + strconv.Itoa(int(robot.ID)))
|
||
}
|
||
|
||
// ShowRobot 显示机器人详情
|
||
func ShowRobot(c *fiber.Ctx) error {
|
||
id, err := strconv.Atoi(c.Params("id"))
|
||
if err != nil {
|
||
return fiber.NewError(fiber.StatusBadRequest, "无效的ID")
|
||
}
|
||
|
||
var robot model.Robot
|
||
db := model.GetDB()
|
||
|
||
if err := db.First(&robot, id).Error; err != nil {
|
||
if err == gorm.ErrRecordNotFound {
|
||
return fiber.NewError(fiber.StatusNotFound, "机器人不存在")
|
||
}
|
||
return fiber.NewError(fiber.StatusInternalServerError, "查询数据库失败")
|
||
}
|
||
|
||
return c.Render("robot/show", fiber.Map{
|
||
"Title": robot.Nickname,
|
||
"Robot": robot,
|
||
})
|
||
}
|
||
|
||
// DeleteRobot 删除机器人
|
||
func DeleteRobot(c *fiber.Ctx) error {
|
||
id, err := strconv.Atoi(c.Params("id"))
|
||
if err != nil {
|
||
// 针对API请求返回JSON
|
||
if strings.HasPrefix(c.Get("Accept"), "application/json") || c.Method() == "DELETE" {
|
||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||
"success": false,
|
||
"message": "无效的ID",
|
||
})
|
||
}
|
||
return fiber.NewError(fiber.StatusBadRequest, "无效的ID")
|
||
}
|
||
|
||
var robot model.Robot
|
||
db := model.GetDB()
|
||
|
||
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{
|
||
"success": false,
|
||
"message": "机器人不存在",
|
||
})
|
||
}
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
return fiber.NewError(fiber.StatusNotFound, "机器人不存在")
|
||
}
|
||
return fiber.NewError(fiber.StatusInternalServerError, "查询数据库失败")
|
||
}
|
||
|
||
// 首先尝试登出微信
|
||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||
defer cancel()
|
||
|
||
robotCli, err := xybot.NewClient(robot.WechatID, robot.ContainerHost, false)
|
||
if err != nil {
|
||
log.Errorf("创建微信客户端失败: %v", err)
|
||
}
|
||
|
||
if robot.Status == model.RobotStatusOnline {
|
||
if err = robotCli.Login.Logout(); err != nil {
|
||
log.Errorf("登出机器人失败: %v", err)
|
||
// 继续删除流程,不因登出失败而中断
|
||
}
|
||
}
|
||
|
||
// 删除容器
|
||
if err = docker.RemoveContainer(ctx, robot.ContainerID, true); err != nil {
|
||
log.Errorf("删除容器失败: %v", err)
|
||
// 继续删除流程,不因容器删除失败而中断
|
||
}
|
||
|
||
// 删除数据库记录
|
||
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{
|
||
"success": false,
|
||
"message": "删除机器人失败: " + err.Error(),
|
||
})
|
||
}
|
||
return fiber.NewError(fiber.StatusInternalServerError, "删除机器人失败: "+err.Error())
|
||
}
|
||
|
||
// 针对API请求返回JSON
|
||
if strings.HasPrefix(c.Get("Accept"), "application/json") || c.Method() == "DELETE" {
|
||
return c.JSON(fiber.Map{
|
||
"success": true,
|
||
"message": "机器人已成功删除",
|
||
})
|
||
}
|
||
|
||
// 普通请求重定向
|
||
return c.Redirect("/admin/robots")
|
||
}
|
||
|
||
// RobotLogin 显示微信登录二维码或唤醒登录
|
||
func RobotLogin(c *fiber.Ctx) error {
|
||
id, err := strconv.Atoi(c.Params("id"))
|
||
if err != nil {
|
||
return fiber.NewError(fiber.StatusBadRequest, "无效的ID")
|
||
}
|
||
|
||
var robot model.Robot
|
||
db := model.GetDB()
|
||
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.Status == model.RobotStatusOnline {
|
||
// 渲染"已在线"页面
|
||
return c.Render("robot/login", fiber.Map{
|
||
"Title": "微信登录",
|
||
"Robot": robot,
|
||
"IsOnline": true,
|
||
})
|
||
}
|
||
|
||
// 检查是否指定使用二维码登录
|
||
forceQrcode := c.Query("qrcode") == "1"
|
||
|
||
// 创建微信客户端
|
||
robotCli, err := xybot.NewClient(robot.WechatID, robot.ContainerHost, false)
|
||
if err != nil {
|
||
return c.Render("robot/login", fiber.Map{
|
||
"Title": "微信登录",
|
||
"Robot": robot,
|
||
"Message": "创建微信客户端失败: " + err.Error(),
|
||
"IsError": true,
|
||
})
|
||
}
|
||
|
||
// 如果存在WechatID且没有强制要求使用二维码,则使用唤醒登录
|
||
if robot.WechatID != "" && !forceQrcode {
|
||
awakenResp, err := robotCli.Login.AwakenLogin()
|
||
if err != nil {
|
||
return c.Render("robot/login", fiber.Map{
|
||
"Title": "微信登录",
|
||
"Robot": robot,
|
||
"Message": "唤醒登录失败: " + err.Error(),
|
||
"IsError": true,
|
||
"IsAwaken": true,
|
||
})
|
||
}
|
||
|
||
// 渲染唤醒登录页面
|
||
return c.Render("robot/login", fiber.Map{
|
||
"Title": "微信登录",
|
||
"Robot": robot,
|
||
"UUID": awakenResp.QrCodeResponse.Uuid,
|
||
"Expired": awakenResp.QrCodeResponse.ExpiredTime,
|
||
"IsAwaken": true,
|
||
})
|
||
}
|
||
|
||
// 使用新的GetQRCode接口获取二维码,并传递容器访问地址
|
||
qrcodeResp, err := robotCli.Login.GetQRCode(robot.DeviceId, robot.DeviceName)
|
||
if err != nil {
|
||
return c.Render("robot/login", fiber.Map{
|
||
"Title": "微信登录",
|
||
"Robot": robot,
|
||
"Message": "获取登录二维码失败: " + err.Error(),
|
||
"IsError": true,
|
||
})
|
||
}
|
||
|
||
// 渲染登录页面,包含二维码和UUID(用于后续状态检查)
|
||
return c.Render("robot/login", fiber.Map{
|
||
"Title": "微信登录",
|
||
"Robot": robot,
|
||
"QRCode": qrcodeResp.QRCodeURL,
|
||
"UUID": qrcodeResp.Uuid,
|
||
"Expired": qrcodeResp.ExpiredTime,
|
||
"IsAwaken": false,
|
||
})
|
||
}
|
||
|
||
// RobotLogout 机器人退出微信登录
|
||
func RobotLogout(c *fiber.Ctx) error {
|
||
id, err := strconv.Atoi(c.Params("id"))
|
||
if err != nil {
|
||
return fiber.NewError(fiber.StatusBadRequest, "无效的ID")
|
||
}
|
||
|
||
var robot model.Robot
|
||
db := model.GetDB()
|
||
|
||
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, "查询数据库失败")
|
||
}
|
||
|
||
// 创建微信客户端
|
||
robotCli, err := xybot.NewClient(robot.WechatID, robot.ContainerHost, false)
|
||
if err != nil {
|
||
return fiber.NewError(fiber.StatusInternalServerError, "创建微信客户端失败: "+err.Error())
|
||
}
|
||
// 使用新的Logout API接口,传递容器访问地址
|
||
if robot.WechatID != "" {
|
||
if err = robotCli.Login.Logout(); err != nil {
|
||
return fiber.NewError(fiber.StatusInternalServerError, "登出微信失败: "+err.Error())
|
||
}
|
||
}
|
||
|
||
// 更新机器人状态
|
||
robot.Status = model.RobotStatusOffline
|
||
if err = db.Save(&robot).Error; err != nil {
|
||
return fiber.NewError(fiber.StatusInternalServerError, "更新状态失败")
|
||
}
|
||
|
||
return c.Redirect("/admin/robots/" + c.Params("id"))
|
||
}
|