2025-04-02 14:29:44 +08:00

249 lines
7.2 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"
"log"
"strconv"
"time"
"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
"gitee.ltd/lxh/wechat-robot/internal/config"
"gitee.ltd/lxh/wechat-robot/internal/docker"
"gitee.ltd/lxh/wechat-robot/internal/model"
)
// ListRobots 列出所有机器人
func ListRobots(c *fiber.Ctx) error {
db := model.GetDB()
var robots []model.Robot
if err := db.Find(&robots).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "获取机器人列表失败")
}
return c.Render("robot/index", fiber.Map{
"Title": "机器人列表",
"Robots": robots,
})
}
// 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之间的有效数字")
}
}
// 加载配置
cfg, err := config.Load()
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "加载配置失败")
}
// 创建Docker容器
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
containerID, containerHost, err := docker.CreateRobotContainer(ctx, &cfg.Docker, robotName, port)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "创建容器失败: "+err.Error())
}
// 创建机器人数据库记录
robot := model.Robot{
ContainerID: containerID,
ContainerHost: containerHost, // 使用新的字段名
Nickname: robotName,
Status: model.RobotStatusOffline,
}
db := model.GetDB()
if err := db.Create(&robot).Error; err != nil {
// 如果数据库创建失败,尝试删除容器
docker.RemoveContainer(ctx, containerID, true)
return fiber.NewError(fiber.StatusInternalServerError, "保存机器人信息失败: "+err.Error())
}
// 添加到监控器
// 注意:这里假设有一个全局可访问的监控器实例,实际中可能需要通过依赖注入或其他方式获取
monitor := docker.NewContainerMonitor(db, time.Minute)
monitor.AddRobot(containerID)
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 {
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, "查询数据库失败")
}
// 首先尝试登出微信
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if robot.Status == model.RobotStatusOnline {
if err := docker.LogoutWechatBot(ctx, robot.ContainerID); err != nil {
log.Printf("登出机器人失败: %v", err)
// 继续删除流程,不因登出失败而中断
}
}
// 删除容器
if err := docker.RemoveContainer(ctx, robot.ContainerID, true); err != nil {
log.Printf("删除容器失败: %v", err)
// 继续删除流程,不因容器删除失败而中断
}
// 从监控器移除
monitor := docker.NewContainerMonitor(db, time.Minute)
monitor.RemoveRobot(robot.ContainerID)
// 删除数据库记录
if err := db.Delete(&robot).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "删除机器人失败: "+err.Error())
}
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 err == gorm.ErrRecordNotFound {
return fiber.NewError(fiber.StatusNotFound, "机器人不存在")
}
return fiber.NewError(fiber.StatusInternalServerError, "查询数据库失败")
}
// 获取登录二维码
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// 使用新的GetQRCode接口获取二维码并传递容器访问地址
qrcodeResp, err := docker.GetQRCode(ctx, robot.ContainerID, robot.ContainerHost)
if err != nil {
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": "微信登录",
"Robot": robot,
"QRCode": qrcodeResp.Data.QRCodeURL, // 完整的data URL格式
"UUID": qrcodeResp.Data.UUID,
"Expired": qrcodeResp.Data.ExpiredTime,
})
}
// 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 err == gorm.ErrRecordNotFound {
return fiber.NewError(fiber.StatusNotFound, "机器人不存在")
}
return fiber.NewError(fiber.StatusInternalServerError, "查询数据库失败")
}
// 登出微信
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// 使用新的LogOut API接口传递容器访问地址
if robot.WechatID != "" {
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 {
return fiber.NewError(fiber.StatusInternalServerError, "登出微信失败: "+err.Error())
}
}
// 更新机器人状态
robot.Status = model.RobotStatusOffline
robot.QRCodePath = ""
if err := db.Save(&robot).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "更新状态失败")
}
return c.Redirect("/admin/robots/" + c.Params("id"))
}