/** * package api * 作用:AI 机器人相关 HTTP 接口。 * * 权限模型: * - AI 配置读写、机器人增删改:仅管理员(用户 id=1); * - 机器人列表查询:所有登录用户(好友列表要展示机器人)。 */ package api import ( "context" "strconv" "strings" "time" "xk-websocket-v2/internal/service" "xk-websocket-v2/internal/utils" "github.com/gin-gonic/gin" ) /** * requireAdmin * 作用:校验当前登录用户是否为管理员(id=1)。 * 返回 false 时已写入 403 响应,调用方直接 return 即可 */ func requireAdmin(c *gin.Context) bool { userID, exists := c.Get("user_id") if !exists || userID.(string) != service.AdminUserID { utils.Error(c, utils.CodeForbidden, "仅管理员可操作") return false } return true } /** * maskAPIKey * 作用:API 密钥脱敏(保留前4后4位),配置回显时避免明文泄露 */ func maskAPIKey(key string) string { if key == "" { return "" } if len(key) <= 8 { return "********" } return key[:4] + strings.Repeat("*", 8) + key[len(key)-4:] } /** * GetAIConfigHandler * 功能:获取全局 AI 配置(密钥脱敏返回) * 路径:GET /api/ai/config (管理员) */ func GetAIConfigHandler(c *gin.Context) { if !requireAdmin(c) { return } cfg, err := service.AIBotSvc.GetAIConfig() if err != nil { utils.InternalError(c, "配置读取失败") return } utils.SuccessWithData(c, gin.H{ "provider": cfg.Provider, "base_url": cfg.BaseURL, "api_key": maskAPIKey(cfg.APIKey), "model": cfg.Model, "enabled": cfg.Enabled, // 前端据此判断"密钥是否已配置过"(脱敏值不能用于判断) "has_key": cfg.APIKey != "", }, "获取成功") } // saveAIConfigReq 保存 AI 配置请求体 type saveAIConfigReq struct { Provider string `json:"provider" binding:"required"` BaseURL string `json:"base_url"` // 为空表示不修改已保存的密钥(前端回显的是脱敏值) APIKey string `json:"api_key"` Model string `json:"model" binding:"required"` Enabled bool `json:"enabled"` } /** * SaveAIConfigHandler * 功能:保存全局 AI 配置 * 路径:POST /api/ai/config (管理员) */ func SaveAIConfigHandler(c *gin.Context) { if !requireAdmin(c) { return } var req saveAIConfigReq if err := c.ShouldBindJSON(&req); err != nil { utils.BadRequest(c, "参数错误: "+err.Error()) return } // 前端可能把脱敏值原样提交回来,含 **** 的一律视为"未修改" if strings.Contains(req.APIKey, "****") { req.APIKey = "" } cfg, err := service.AIBotSvc.SaveAIConfig(req.Provider, req.BaseURL, req.APIKey, req.Model, req.Enabled) if err != nil { utils.InternalError(c, "保存失败: "+err.Error()) return } utils.SuccessWithData(c, gin.H{ "provider": cfg.Provider, "base_url": cfg.BaseURL, "api_key": maskAPIKey(cfg.APIKey), "model": cfg.Model, "enabled": cfg.Enabled, "has_key": cfg.APIKey != "", }, "保存成功") } /** * TestAIConfigHandler * 功能:测试当前 AI 配置连通性(发一条固定问候语,成功返回模型回复) * 路径:POST /api/ai/config/test (管理员) */ func TestAIConfigHandler(c *gin.Context) { if !requireAdmin(c) { return } cfg, err := service.AIBotSvc.GetAIConfig() if err != nil { utils.InternalError(c, "配置读取失败") return } provider, err := service.NewAIProvider(cfg) if err != nil { utils.BadRequest(c, err.Error()) return } ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) defer cancel() reply, err := provider.Chat(ctx, []service.AIMessage{ {Role: "user", Content: "你好,请用一句话介绍你自己"}, }) if err != nil { utils.BadRequest(c, "测试失败: "+err.Error()) return } utils.SuccessWithData(c, gin.H{"reply": reply}, "测试成功") } /** * ListAIBotsHandler * 功能:机器人列表。管理员传 all=1 返回全部(含停用),普通用户只见启用中的 * 路径:GET /api/ai/bots (登录用户) */ func ListAIBotsHandler(c *gin.Context) { userID, _ := c.Get("user_id") onlyEnabled := true if c.Query("all") == "1" && userID != nil && userID.(string) == service.AdminUserID { onlyEnabled = false } bots, err := service.AIBotSvc.ListBots(onlyEnabled) if err != nil { utils.InternalError(c, "查询失败") return } utils.SuccessWithData(c, bots, "获取成功") } // botReq 创建/更新机器人请求体 type botReq struct { Name string `json:"name" binding:"required"` Avatar string `json:"avatar"` RolePrompt string `json:"role_prompt"` Enabled *bool `json:"enabled"` } /** * CreateAIBotHandler * 功能:创建机器人(同时生成 users 虚拟用户) * 路径:POST /api/ai/bots (管理员) */ func CreateAIBotHandler(c *gin.Context) { if !requireAdmin(c) { return } var req botReq if err := c.ShouldBindJSON(&req); err != nil { utils.BadRequest(c, "参数错误: "+err.Error()) return } bot, err := service.AIBotSvc.CreateBot(req.Name, req.Avatar, req.RolePrompt) if err != nil { utils.InternalError(c, "创建失败: "+err.Error()) return } utils.SuccessWithData(c, bot, "创建成功") } /** * UpdateAIBotHandler * 功能:更新机器人(名称/头像/角色设定/启停) * 路径:POST /api/ai/bots/update/:id (管理员) */ func UpdateAIBotHandler(c *gin.Context) { if !requireAdmin(c) { return } id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil || id == 0 { utils.BadRequest(c, "机器人ID不合法") return } var req botReq if err := c.ShouldBindJSON(&req); err != nil { utils.BadRequest(c, "参数错误: "+err.Error()) return } enabled := true if req.Enabled != nil { enabled = *req.Enabled } bot, err := service.AIBotSvc.UpdateBot(uint(id), req.Name, req.Avatar, req.RolePrompt, enabled) if err != nil { utils.InternalError(c, "更新失败: "+err.Error()) return } utils.SuccessWithData(c, bot, "更新成功") } /** * DeleteAIBotHandler * 功能:删除机器人(连带删除虚拟用户与群成员关系) * 路径:POST /api/ai/bots/delete/:id (管理员) */ func DeleteAIBotHandler(c *gin.Context) { if !requireAdmin(c) { return } id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil || id == 0 { utils.BadRequest(c, "机器人ID不合法") return } if err := service.AIBotSvc.DeleteBot(uint(id)); err != nil { utils.InternalError(c, "删除失败: "+err.Error()) return } utils.Success(c, "删除成功") }