feat: 限制用户单日提问次数

This commit is contained in:
Blizzard
2026-04-24 17:07:52 +08:00
parent e9c93d4029
commit 3aae619af5
6 changed files with 56 additions and 4 deletions
+42 -3
View File
@@ -1,6 +1,7 @@
package plant package plant
import ( import (
"fmt"
"sundynix-go/global" "sundynix-go/global"
"sundynix-go/model/commom/response" "sundynix-go/model/commom/response"
"sundynix-go/utils/auth" "sundynix-go/utils/auth"
@@ -24,7 +25,19 @@ func (a *AiChatApi) ChatStreamPlant(c *gin.Context) {
return return
} }
// SSE Headers(微信小程序通过 enableChunked: true 配合实现打字机效果) userId := auth.GetUserId(c)
// ── 每日用量检查 ──
cfg, cfgErr := sysAiConfigService.GetActiveAiConfig()
if cfgErr == nil && cfg.DailyQueryLimit > 0 {
todayCount, _ := chatHistoryService.GetTodayCount(userId)
if todayCount >= int64(cfg.DailyQueryLimit) {
response.FailWithMsg(fmt.Sprintf("今日问答次数已达上限(%d次),明天再来吧", cfg.DailyQueryLimit), c)
return
}
}
// SSE Headers
w := c.Writer w := c.Writer
header := w.Header() header := w.Header()
header.Set("Content-Type", "text/event-stream") header.Set("Content-Type", "text/event-stream")
@@ -46,14 +59,12 @@ func (a *AiChatApi) ChatStreamPlant(c *gin.Context) {
_, _ = w.WriteString("data: [ERROR]" + err.Error() + "\n\n") _, _ = w.WriteString("data: [ERROR]" + err.Error() + "\n\n")
w.Flush() w.Flush()
} else { } else {
// 流结束标志
_, _ = w.WriteString("data: [DONE]\n\n") _, _ = w.WriteString("data: [DONE]\n\n")
w.Flush() w.Flush()
} }
// 异步保存问答历史 // 异步保存问答历史
if fullAnswer != "" { if fullAnswer != "" {
userId := auth.GetUserId(c)
go func() { go func() {
if saveErr := chatHistoryService.SaveHistory(userId, query, fullAnswer); saveErr != nil { if saveErr := chatHistoryService.SaveHistory(userId, query, fullAnswer); saveErr != nil {
global.Logger.Error("Save chat history failed", zap.Error(saveErr)) global.Logger.Error("Save chat history failed", zap.Error(saveErr))
@@ -138,6 +149,34 @@ func (a *AiChatApi) ClearChatHistory(c *gin.Context) {
response.OkWithMsg("已清空", c) response.OkWithMsg("已清空", c)
} }
// GetChatQuota 获取当前用户今日剩余问答额度
// @Tags Plant-AiChat
// @Router /plant/chat/quota [get]
func (a *AiChatApi) GetChatQuota(c *gin.Context) {
userId := auth.GetUserId(c)
todayCount, _ := chatHistoryService.GetTodayCount(userId)
limit := 0 // 0 = unlimited
cfg, err := sysAiConfigService.GetActiveAiConfig()
if err == nil && cfg.DailyQueryLimit > 0 {
limit = cfg.DailyQueryLimit
}
remaining := -1 // -1 means unlimited
if limit > 0 {
remaining = limit - int(todayCount)
if remaining < 0 {
remaining = 0
}
}
response.OkWithData(map[string]interface{}{
"used": todayCount,
"limit": limit,
"remaining": remaining,
}, c)
}
func parseInt(s string) int { func parseInt(s string) int {
n := 0 n := 0
for _, c := range s { for _, c := range s {
+1
View File
@@ -31,4 +31,5 @@ var (
exchangeService = service.GroupApp.PlantServiceGroup.ExchangeService exchangeService = service.GroupApp.PlantServiceGroup.ExchangeService
aiRagService = service.GroupApp.PlantServiceGroup.AiRagService aiRagService = service.GroupApp.PlantServiceGroup.AiRagService
chatHistoryService = service.GroupApp.PlantServiceGroup.AiChatHistoryService chatHistoryService = service.GroupApp.PlantServiceGroup.AiChatHistoryService
sysAiConfigService = service.GroupApp.SystemServiceGroup.SysAiConfigService
) )
+2
View File
@@ -20,4 +20,6 @@ type SysAiConfig struct {
EmbeddingApiUrl string `gorm:"column:embedding_api_url;type:varchar(255);comment:Embedding模型接口地址" json:"embeddingApiUrl" form:"embeddingApiUrl"` EmbeddingApiUrl string `gorm:"column:embedding_api_url;type:varchar(255);comment:Embedding模型接口地址" json:"embeddingApiUrl" form:"embeddingApiUrl"`
EmbeddingApiKey string `gorm:"column:embedding_api_key;type:varchar(255);comment:Embedding模型ApiKey" json:"embeddingApiKey" form:"embeddingApiKey"` EmbeddingApiKey string `gorm:"column:embedding_api_key;type:varchar(255);comment:Embedding模型ApiKey" json:"embeddingApiKey" form:"embeddingApiKey"`
EmbeddingModelName string `gorm:"column:embedding_model_name;type:varchar(100);comment:Embedding模型名称" json:"embeddingModelName" form:"embeddingModelName"` EmbeddingModelName string `gorm:"column:embedding_model_name;type:varchar(100);comment:Embedding模型名称" json:"embeddingModelName" form:"embeddingModelName"`
// 用量限制
DailyQueryLimit int `gorm:"column:daily_query_limit;type:int;default:20;comment:每用户每日问答上限(0=不限)" json:"dailyQueryLimit" form:"dailyQueryLimit"`
} }
+1
View File
@@ -13,6 +13,7 @@ func (s *AiChatRouter) InitAiChatRouter(Router *gin.RouterGroup) (R gin.IRoutes)
aiChatRouter.GET("history", aiChatApi.GetChatHistory) // 问答历史列表 aiChatRouter.GET("history", aiChatApi.GetChatHistory) // 问答历史列表
aiChatRouter.POST("history/delete", aiChatApi.DeleteChatHistory) // 删除单条 aiChatRouter.POST("history/delete", aiChatApi.DeleteChatHistory) // 删除单条
aiChatRouter.POST("history/clear", aiChatApi.ClearChatHistory) // 清空历史 aiChatRouter.POST("history/clear", aiChatApi.ClearChatHistory) // 清空历史
aiChatRouter.GET("quota", aiChatApi.GetChatQuota) // 今日剩余额度
} }
return aiChatRouter return aiChatRouter
} }
+1 -1
View File
@@ -9,7 +9,7 @@ func (s *SysAiConfigRouter) InitSysAiConfigRouter(Router *gin.RouterGroup) (R gi
sysAiConfigApi := sysAiConfigApi sysAiConfigApi := sysAiConfigApi
{ {
sysAiConfigRouter.POST("create", sysAiConfigApi.CreateAiConfig) // 创建配置 sysAiConfigRouter.POST("create", sysAiConfigApi.CreateAiConfig) // 创建配置
sysAiConfigRouter.PUT("update", sysAiConfigApi.UpdateAiConfig) // 更新配置 sysAiConfigRouter.POST("update", sysAiConfigApi.UpdateAiConfig) // 更新配置
sysAiConfigRouter.POST("setActive", sysAiConfigApi.SetActive) // 设置激活状态 sysAiConfigRouter.POST("setActive", sysAiConfigApi.SetActive) // 设置激活状态
sysAiConfigRouter.GET("list", sysAiConfigApi.GetList) // 获取列表 sysAiConfigRouter.GET("list", sysAiConfigApi.GetList) // 获取列表
} }
+9
View File
@@ -46,3 +46,12 @@ func (s *AiChatHistoryService) DeleteHistory(userId, id string) error {
func (s *AiChatHistoryService) ClearHistory(userId string) error { func (s *AiChatHistoryService) ClearHistory(userId string) error {
return global.DB.Where("user_id = ?", userId).Delete(&plantModel.AiChatHistory{}).Error return global.DB.Where("user_id = ?", userId).Delete(&plantModel.AiChatHistory{}).Error
} }
// GetTodayCount 获取用户今日问答数量
func (s *AiChatHistoryService) GetTodayCount(userId string) (int64, error) {
var count int64
err := global.DB.Model(&plantModel.AiChatHistory{}).
Where("user_id = ? AND DATE(created_at) = CURDATE()", userId).
Count(&count).Error
return count, err
}