328 lines
9.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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