Merge remote-tracking branch 'yvan/main'

# Conflicts:
#	server/go.mod
#	server/go.sum
This commit is contained in:
2025-09-10 19:49:22 +08:00
27 changed files with 1522 additions and 685 deletions
+5 -1
View File
@@ -1,6 +1,9 @@
package pet
import "github.com/flipped-aurora/gin-vue-admin/server/service"
import (
"github.com/flipped-aurora/gin-vue-admin/server/api/v1/pet/user"
"github.com/flipped-aurora/gin-vue-admin/server/service"
)
type ApiGroup struct {
PetAdoptionApplicationsApi
@@ -13,6 +16,7 @@ type ApiGroup struct {
PetFamilyPetsApi
PetPetsApi
PetRecordsApi
PetUserApiGroup user.ApiGroup
}
var (
+1
View File
@@ -3,6 +3,7 @@ package user
import "github.com/flipped-aurora/gin-vue-admin/server/service"
type ApiGroup struct {
PetAssistantApi
}
var (
+298
View File
@@ -0,0 +1,298 @@
package user
import (
"io"
"strconv"
"time"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
petRequest "github.com/flipped-aurora/gin-vue-admin/server/model/pet/request"
petResponse "github.com/flipped-aurora/gin-vue-admin/server/model/pet/response"
"github.com/flipped-aurora/gin-vue-admin/server/service"
"github.com/flipped-aurora/gin-vue-admin/server/utils"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type PetAssistantApi struct{}
var petChatService = service.ServiceGroupApp.PetServiceGroup.PetChatService
// AskPetAssistant 向宠物助手提问(非流式)
// @Tags PetAssistant
// @Summary 向宠物助手提问
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param data body petRequest.ChatRequest true "宠物助手提问请求"
// @Success 200 {object} response.Response{data=petResponse.ChatResponse,msg=string} "提问成功"
// @Router /api/v1/pet/user/assistant/ask [post]
func (p *PetAssistantApi) AskPetAssistant(ctx *gin.Context) {
// 创建业务用Context
businessCtx := ctx.Request.Context()
// 获取用户ID
userId := utils.GetAppUserID(ctx)
if userId == 0 {
global.GVA_LOG.Error("获取用户ID失败")
response.FailWithMessage("用户认证失败", ctx)
return
}
// 绑定请求参数
var req petRequest.ChatRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
global.GVA_LOG.Error("参数绑定失败", zap.Error(err))
response.FailWithMessage("参数错误: "+err.Error(), ctx)
return
}
// 参数验证
if req.Message == "" {
response.FailWithMessage("消息内容不能为空", ctx)
return
}
// 设置默认参数
if req.Temperature <= 0 {
req.Temperature = 0.7
}
if req.MaxTokens <= 0 {
req.MaxTokens = 1000
}
// 调用服务层
resp, err := petChatService.SendMessage(businessCtx, userId, req)
if err != nil {
global.GVA_LOG.Error("发送消息失败", zap.Error(err), zap.Uint("userId", userId))
response.FailWithMessage("发送消息失败: "+err.Error(), ctx)
return
}
response.OkWithDetailed(resp, "发送成功", ctx)
}
// StreamAskPetAssistant 向宠物助手流式提问
// @Tags PetAssistant
// @Summary 向宠物助手流式提问接口
// @Security ApiKeyAuth
// @Accept application/json
// @Produce text/event-stream
// @Param data body petRequest.ChatRequest true "宠物助手流式提问请求"
// @Success 200 {string} string "流式响应"
// @Router /api/v1/pet/user/assistant/stream-ask [post]
func (p *PetAssistantApi) StreamAskPetAssistant(ctx *gin.Context) {
// 创建业务用Context
businessCtx := ctx.Request.Context()
// 获取用户ID
userId := utils.GetAppUserID(ctx)
if userId == 0 {
global.GVA_LOG.Error("获取用户ID失败")
response.FailWithMessage("用户认证失败", ctx)
return
}
// 绑定请求参数
var req petRequest.ChatRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
global.GVA_LOG.Error("参数绑定失败", zap.Error(err))
response.FailWithMessage("参数错误: "+err.Error(), ctx)
return
}
// 参数验证
if req.Message == "" {
response.FailWithMessage("消息内容不能为空", ctx)
return
}
// 强制设置为流式响应
req.Stream = true
// 设置默认参数
if req.Temperature <= 0 {
req.Temperature = 0.7
}
if req.MaxTokens <= 0 {
req.MaxTokens = 1000
}
// 设置SSE响应头
ctx.Header("Content-Type", "text/event-stream")
ctx.Header("Cache-Control", "no-cache")
ctx.Header("Connection", "keep-alive")
ctx.Header("Access-Control-Allow-Origin", "*")
ctx.Header("Access-Control-Allow-Headers", "Cache-Control")
// 创建事件通道
eventChan := make(chan petResponse.StreamEvent, 100)
defer close(eventChan)
// 启动流式聊天
go func() {
if err := petChatService.StreamChat(businessCtx, userId, req, eventChan); err != nil {
global.GVA_LOG.Error("流式聊天失败", zap.Error(err), zap.Uint("userId", userId))
eventChan <- petResponse.StreamEvent{
Event: "error",
Data: map[string]interface{}{
"error": "流式聊天失败: " + err.Error(),
},
}
}
}()
// 发送流式数据
ctx.Stream(func(w io.Writer) bool {
select {
case event, ok := <-eventChan:
if !ok {
return false
}
switch event.Event {
case "message":
// 发送消息事件
ctx.SSEvent("message", event.Data)
case "error":
// 发送错误事件
ctx.SSEvent("error", event.Data)
return false
case "done":
// 发送完成事件
ctx.SSEvent("done", event.Data)
return false
}
return true
case <-time.After(30 * time.Second):
// 超时处理
ctx.SSEvent("error", map[string]interface{}{
"error": "流式响应超时",
})
return false
}
})
}
// GetAssistantHistory 获取宠物助手对话历史
// @Tags PetAssistant
// @Summary 获取宠物助手对话历史记录
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param sessionId query string false "会话ID"
// @Param page query int false "页码" default(1)
// @Param pageSize query int false "每页数量" default(20)
// @Success 200 {object} response.Response{data=[]pet.PetAiAssistantConversations,msg=string} "获取成功"
// @Router /api/v1/pet/user/assistant/history [get]
func (p *PetAssistantApi) GetAssistantHistory(ctx *gin.Context) {
// 创建业务用Context
businessCtx := ctx.Request.Context()
// 获取用户ID
userId := utils.GetAppUserID(ctx)
if userId == 0 {
global.GVA_LOG.Error("获取用户ID失败")
response.FailWithMessage("用户认证失败", ctx)
return
}
// 获取查询参数
sessionId := ctx.Query("sessionId")
pageStr := ctx.DefaultQuery("page", "1")
pageSizeStr := ctx.DefaultQuery("pageSize", "20")
page, err := strconv.Atoi(pageStr)
if err != nil || page < 1 {
page = 1
}
pageSize, err := strconv.Atoi(pageSizeStr)
if err != nil || pageSize < 1 || pageSize > 100 {
pageSize = 20
}
// 计算limit
limit := pageSize
// 调用服务层
conversations, err := petChatService.GetChatHistory(businessCtx, userId, sessionId, limit)
if err != nil {
global.GVA_LOG.Error("获取聊天历史失败", zap.Error(err), zap.Uint("userId", userId))
response.FailWithMessage("获取聊天历史失败: "+err.Error(), ctx)
return
}
response.OkWithDetailed(conversations, "获取成功", ctx)
}
// ClearAssistantHistory 清空宠物助手对话历史
// @Tags PetAssistant
// @Summary 清空宠物助手对话历史记录
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param sessionId query string false "会话ID,不传则清空所有会话"
// @Success 200 {object} response.Response{msg=string} "清空成功"
// @Router /api/v1/pet/user/assistant/clear-history [delete]
func (p *PetAssistantApi) ClearAssistantHistory(ctx *gin.Context) {
// 创建业务用Context
businessCtx := ctx.Request.Context()
// 获取用户ID
userId := utils.GetAppUserID(ctx)
if userId == 0 {
global.GVA_LOG.Error("获取用户ID失败")
response.FailWithMessage("用户认证失败", ctx)
return
}
// 获取会话ID(可选)
sessionId := ctx.Query("sessionId")
// 调用服务层
err := petChatService.ClearChatHistory(businessCtx, userId, sessionId)
if err != nil {
global.GVA_LOG.Error("清空聊天历史失败", zap.Error(err), zap.Uint("userId", userId))
response.FailWithMessage("清空聊天历史失败: "+err.Error(), ctx)
return
}
if sessionId != "" {
response.OkWithMessage("指定会话历史清空成功", ctx)
} else {
response.OkWithMessage("所有聊天历史清空成功", ctx)
}
}
// GetAssistantSessions 获取宠物助手会话列表
// @Tags PetAssistant
// @Summary 获取用户的宠物助手会话列表
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Success 200 {object} response.Response{data=[]map[string]interface{},msg=string} "获取成功"
// @Router /api/v1/pet/user/assistant/sessions [get]
func (p *PetAssistantApi) GetAssistantSessions(ctx *gin.Context) {
// 创建业务用Context
businessCtx := ctx.Request.Context()
// 获取用户ID
userId := utils.GetAppUserID(ctx)
if userId == 0 {
global.GVA_LOG.Error("获取用户ID失败")
response.FailWithMessage("用户认证失败", ctx)
return
}
// 调用服务层
sessions, err := petChatService.GetChatSessions(businessCtx, userId)
if err != nil {
global.GVA_LOG.Error("获取会话列表失败", zap.Error(err), zap.Uint("userId", userId))
response.FailWithMessage("获取会话列表失败: "+err.Error(), ctx)
return
}
response.OkWithDetailed(sessions, "获取成功", ctx)
}