Files
nl-im-service/internal/api/ai_handler.go

250 lines
6.4 KiB
Go
Raw Normal View History

2026-08-24 15:29:53 +08:00
/**
* 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, "删除成功")
}