Files

250 lines
6.4 KiB
Go
Raw Permalink 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, "删除成功")
}