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