293 lines
8.3 KiB
Go
293 lines
8.3 KiB
Go
package handler
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"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, "获取机器人列表失败")
|
||
}
|
||
|
||
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之间的有效数字")
|
||
}
|
||
}
|
||
|
||
// 加载配置
|
||
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 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()
|
||
|
||
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 errors.Is(err, gorm.ErrRecordNotFound) {
|
||
return fiber.NewError(fiber.StatusNotFound, "机器人不存在")
|
||
}
|
||
return fiber.NewError(fiber.StatusInternalServerError, "查询数据库失败")
|
||
}
|
||
// 处理一下设备信息
|
||
if robot.CheckDevice() {
|
||
db.Save(&robot)
|
||
}
|
||
|
||
// 获取登录二维码
|
||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||
defer cancel()
|
||
|
||
// 使用新的GetQRCode接口获取二维码,并传递容器访问地址
|
||
qrcodeResp, err := docker.GetQRCode(ctx, robot.ContainerHost)
|
||
if err != nil {
|
||
return fiber.NewError(fiber.StatusInternalServerError, "获取登录二维码失败: "+err.Error())
|
||
}
|
||
|
||
// 渲染登录页面,包含二维码和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 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()
|
||
|
||
// 使用新的LogOut API接口,传递容器访问地址
|
||
if robot.WechatID != "" {
|
||
if _, err = docker.LogOut(ctx, 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
|
||
if err = db.Save(&robot).Error; err != nil {
|
||
return fiber.NewError(fiber.StatusInternalServerError, "更新状态失败")
|
||
}
|
||
|
||
return c.Redirect("/admin/robots/" + c.Params("id"))
|
||
}
|