package handler import ( "context" "log" "strconv" "strings" "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 { // 针对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 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 { // 针对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 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")) }