Compare commits
49
Commits
f35125cbce
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a3649107c | ||
|
|
b8447604c2 | ||
|
|
7455586bcf | ||
|
|
8991a00e17 | ||
|
|
73063af861 | ||
|
|
870f9fff86 | ||
|
|
27c97aa5c1 | ||
|
|
05fba0b6a3 | ||
|
|
a416353c29 | ||
|
|
c88252272c | ||
|
|
424c5b23b4 | ||
|
|
86b420ce67 | ||
|
|
ea5709e5b6 | ||
|
|
4e9e3570ed | ||
|
|
78cdc287c5 | ||
|
|
58b123d316 | ||
|
|
fb199aeedc | ||
|
|
364dddb70a | ||
|
|
6bf848d53f | ||
|
|
3e429f4091 | ||
|
|
daabca4768 | ||
|
|
9e688c65d3 | ||
|
|
2e8f5870d8 | ||
|
|
8b0a00b98a | ||
|
|
1ce3e11d52 | ||
|
|
11aa8c5cc2 | ||
|
|
d8e6f2f5f4 | ||
|
|
9d5c93b4c3 | ||
|
|
2f7f3e5ac6 | ||
|
|
cb97d3832d | ||
|
|
dd34d6e3be | ||
|
|
1d700ad5b6 | ||
|
|
3648df9522 | ||
|
|
eb2925829f | ||
|
|
1e3c968277 | ||
|
|
339db778c3 | ||
|
|
8b8ae7b692 | ||
|
|
52be26eb43 | ||
|
|
9f958addc8 | ||
|
|
7a69b88885 | ||
|
|
76fe71a8dd | ||
|
|
3f21d7f8dd | ||
|
|
1fce169616 | ||
|
|
0d900293eb | ||
|
|
b71ad7b6f4 | ||
|
|
2139e38147 | ||
|
|
a033ab9abe | ||
|
|
311e1ddb9f | ||
|
|
31e6316916 |
@@ -22,7 +22,8 @@
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td width="200">
|
||||
<td width="250">
|
||||
<p>⭐️ <a href="https://www.bilibili.com/video/BV1B3htzqEf1/?spm_id_from=333.1387.homepage.video_card.click" target="__blank"> 高度适配AI编辑器的MCP </a></p>
|
||||
<p>📄 创建基础模板</p>
|
||||
<p>🤖 AI生成结构</p>
|
||||
<p>⏰ 生成代码</p>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 103 KiB |
@@ -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 (
|
||||
|
||||
@@ -167,22 +167,3 @@ func (petAdoptionApplicationsApi *PetAdoptionApplicationsApi) GetPetAdoptionAppl
|
||||
PageSize: pageInfo.PageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetPetAdoptionApplicationsPublic 不需要鉴权的petAdoptionApplications表接口
|
||||
// @Tags PetAdoptionApplications
|
||||
// @Summary 不需要鉴权的petAdoptionApplications表接口
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{data=object,msg=string} "获取成功"
|
||||
// @Router /petAdoptionApplications/getPetAdoptionApplicationsPublic [get]
|
||||
func (petAdoptionApplicationsApi *PetAdoptionApplicationsApi) GetPetAdoptionApplicationsPublic(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
// 此接口不需要鉴权
|
||||
// 示例为返回了一个固定的消息接口,一般本接口用于C端服务,需要自己实现业务逻辑
|
||||
petAdoptionApplicationsService.GetPetAdoptionApplicationsPublic(ctx)
|
||||
response.OkWithDetailed(gin.H{
|
||||
"info": "不需要鉴权的petAdoptionApplications表接口信息",
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
@@ -167,22 +167,3 @@ func (petAdoptionPostsApi *PetAdoptionPostsApi) GetPetAdoptionPostsList(c *gin.C
|
||||
PageSize: pageInfo.PageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetPetAdoptionPostsPublic 不需要鉴权的petAdoptionPosts表接口
|
||||
// @Tags PetAdoptionPosts
|
||||
// @Summary 不需要鉴权的petAdoptionPosts表接口
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{data=object,msg=string} "获取成功"
|
||||
// @Router /petAdoptionPosts/getPetAdoptionPostsPublic [get]
|
||||
func (petAdoptionPostsApi *PetAdoptionPostsApi) GetPetAdoptionPostsPublic(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
// 此接口不需要鉴权
|
||||
// 示例为返回了一个固定的消息接口,一般本接口用于C端服务,需要自己实现业务逻辑
|
||||
petAdoptionPostsService.GetPetAdoptionPostsPublic(ctx)
|
||||
response.OkWithDetailed(gin.H{
|
||||
"info": "不需要鉴权的petAdoptionPosts表接口信息",
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
@@ -167,22 +167,3 @@ func (petAiAssistantConversationsApi *PetAiAssistantConversationsApi) GetPetAiAs
|
||||
PageSize: pageInfo.PageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetPetAiAssistantConversationsPublic 不需要鉴权的petAiAssistantConversations表接口
|
||||
// @Tags PetAiAssistantConversations
|
||||
// @Summary 不需要鉴权的petAiAssistantConversations表接口
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{data=object,msg=string} "获取成功"
|
||||
// @Router /petAiAssistantConversations/getPetAiAssistantConversationsPublic [get]
|
||||
func (petAiAssistantConversationsApi *PetAiAssistantConversationsApi) GetPetAiAssistantConversationsPublic(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
// 此接口不需要鉴权
|
||||
// 示例为返回了一个固定的消息接口,一般本接口用于C端服务,需要自己实现业务逻辑
|
||||
petAiAssistantConversationsService.GetPetAiAssistantConversationsPublic(ctx)
|
||||
response.OkWithDetailed(gin.H{
|
||||
"info": "不需要鉴权的petAiAssistantConversations表接口信息",
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
@@ -167,22 +167,3 @@ func (petAiConversationsApi *PetAiConversationsApi) GetPetAiConversationsList(c
|
||||
PageSize: pageInfo.PageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetPetAiConversationsPublic 不需要鉴权的petAiConversations表接口
|
||||
// @Tags PetAiConversations
|
||||
// @Summary 不需要鉴权的petAiConversations表接口
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{data=object,msg=string} "获取成功"
|
||||
// @Router /petAiConversations/getPetAiConversationsPublic [get]
|
||||
func (petAiConversationsApi *PetAiConversationsApi) GetPetAiConversationsPublic(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
// 此接口不需要鉴权
|
||||
// 示例为返回了一个固定的消息接口,一般本接口用于C端服务,需要自己实现业务逻辑
|
||||
petAiConversationsService.GetPetAiConversationsPublic(ctx)
|
||||
response.OkWithDetailed(gin.H{
|
||||
"info": "不需要鉴权的petAiConversations表接口信息",
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
@@ -167,22 +167,3 @@ func (petFamiliesApi *PetFamiliesApi) GetPetFamiliesList(c *gin.Context) {
|
||||
PageSize: pageInfo.PageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetPetFamiliesPublic 不需要鉴权的petFamilies表接口
|
||||
// @Tags PetFamilies
|
||||
// @Summary 不需要鉴权的petFamilies表接口
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{data=object,msg=string} "获取成功"
|
||||
// @Router /petFamilies/getPetFamiliesPublic [get]
|
||||
func (petFamiliesApi *PetFamiliesApi) GetPetFamiliesPublic(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
// 此接口不需要鉴权
|
||||
// 示例为返回了一个固定的消息接口,一般本接口用于C端服务,需要自己实现业务逻辑
|
||||
petFamiliesService.GetPetFamiliesPublic(ctx)
|
||||
response.OkWithDetailed(gin.H{
|
||||
"info": "不需要鉴权的petFamilies表接口信息",
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
@@ -167,22 +167,3 @@ func (petFamilyInvitationsApi *PetFamilyInvitationsApi) GetPetFamilyInvitationsL
|
||||
PageSize: pageInfo.PageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetPetFamilyInvitationsPublic 不需要鉴权的petFamilyInvitations表接口
|
||||
// @Tags PetFamilyInvitations
|
||||
// @Summary 不需要鉴权的petFamilyInvitations表接口
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{data=object,msg=string} "获取成功"
|
||||
// @Router /petFamilyInvitations/getPetFamilyInvitationsPublic [get]
|
||||
func (petFamilyInvitationsApi *PetFamilyInvitationsApi) GetPetFamilyInvitationsPublic(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
// 此接口不需要鉴权
|
||||
// 示例为返回了一个固定的消息接口,一般本接口用于C端服务,需要自己实现业务逻辑
|
||||
petFamilyInvitationsService.GetPetFamilyInvitationsPublic(ctx)
|
||||
response.OkWithDetailed(gin.H{
|
||||
"info": "不需要鉴权的petFamilyInvitations表接口信息",
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
@@ -167,22 +167,3 @@ func (petFamilyMembersApi *PetFamilyMembersApi) GetPetFamilyMembersList(c *gin.C
|
||||
PageSize: pageInfo.PageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetPetFamilyMembersPublic 不需要鉴权的petFamilyMembers表接口
|
||||
// @Tags PetFamilyMembers
|
||||
// @Summary 不需要鉴权的petFamilyMembers表接口
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{data=object,msg=string} "获取成功"
|
||||
// @Router /petFamilyMembers/getPetFamilyMembersPublic [get]
|
||||
func (petFamilyMembersApi *PetFamilyMembersApi) GetPetFamilyMembersPublic(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
// 此接口不需要鉴权
|
||||
// 示例为返回了一个固定的消息接口,一般本接口用于C端服务,需要自己实现业务逻辑
|
||||
petFamilyMembersService.GetPetFamilyMembersPublic(ctx)
|
||||
response.OkWithDetailed(gin.H{
|
||||
"info": "不需要鉴权的petFamilyMembers表接口信息",
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
@@ -167,22 +167,3 @@ func (petFamilyPetsApi *PetFamilyPetsApi) GetPetFamilyPetsList(c *gin.Context) {
|
||||
PageSize: pageInfo.PageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetPetFamilyPetsPublic 不需要鉴权的petFamilyPets表接口
|
||||
// @Tags PetFamilyPets
|
||||
// @Summary 不需要鉴权的petFamilyPets表接口
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{data=object,msg=string} "获取成功"
|
||||
// @Router /petFamilyPets/getPetFamilyPetsPublic [get]
|
||||
func (petFamilyPetsApi *PetFamilyPetsApi) GetPetFamilyPetsPublic(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
// 此接口不需要鉴权
|
||||
// 示例为返回了一个固定的消息接口,一般本接口用于C端服务,需要自己实现业务逻辑
|
||||
petFamilyPetsService.GetPetFamilyPetsPublic(ctx)
|
||||
response.OkWithDetailed(gin.H{
|
||||
"info": "不需要鉴权的petFamilyPets表接口信息",
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
@@ -167,22 +167,3 @@ func (petPetsApi *PetPetsApi) GetPetPetsList(c *gin.Context) {
|
||||
PageSize: pageInfo.PageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetPetPetsPublic 不需要鉴权的petPets表接口
|
||||
// @Tags PetPets
|
||||
// @Summary 不需要鉴权的petPets表接口
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{data=object,msg=string} "获取成功"
|
||||
// @Router /petPets/getPetPetsPublic [get]
|
||||
func (petPetsApi *PetPetsApi) GetPetPetsPublic(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
// 此接口不需要鉴权
|
||||
// 示例为返回了一个固定的消息接口,一般本接口用于C端服务,需要自己实现业务逻辑
|
||||
petPetsService.GetPetPetsPublic(ctx)
|
||||
response.OkWithDetailed(gin.H{
|
||||
"info": "不需要鉴权的petPets表接口信息",
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
@@ -167,22 +167,3 @@ func (petRecordsApi *PetRecordsApi) GetPetRecordsList(c *gin.Context) {
|
||||
PageSize: pageInfo.PageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetPetRecordsPublic 不需要鉴权的petRecords表接口
|
||||
// @Tags PetRecords
|
||||
// @Summary 不需要鉴权的petRecords表接口
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{data=object,msg=string} "获取成功"
|
||||
// @Router /petRecords/getPetRecordsPublic [get]
|
||||
func (petRecordsApi *PetRecordsApi) GetPetRecordsPublic(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
// 此接口不需要鉴权
|
||||
// 示例为返回了一个固定的消息接口,一般本接口用于C端服务,需要自己实现业务逻辑
|
||||
petRecordsService.GetPetRecordsPublic(ctx)
|
||||
response.OkWithDetailed(gin.H{
|
||||
"info": "不需要鉴权的petRecords表接口信息",
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package user
|
||||
import "github.com/flipped-aurora/gin-vue-admin/server/service"
|
||||
|
||||
type ApiGroup struct {
|
||||
PetAssistantApi
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
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
|
||||
|
||||
// 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=[]petResponse.ChatHistoryItem,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)
|
||||
}
|
||||
@@ -27,8 +27,6 @@ import (
|
||||
func (b *BaseApi) Login(c *gin.Context) {
|
||||
var l systemReq.Login
|
||||
err := c.ShouldBindJSON(&l)
|
||||
key := c.ClientIP()
|
||||
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
@@ -39,6 +37,7 @@ func (b *BaseApi) Login(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
key := c.ClientIP()
|
||||
// 判断验证码是否开启
|
||||
openCaptcha := global.GVA_CONFIG.Captcha.OpenCaptcha // 是否开启防爆次数
|
||||
openCaptchaTimeOut := global.GVA_CONFIG.Captcha.OpenCaptchaTimeOut // 缓存超时时间
|
||||
@@ -48,30 +47,30 @@ func (b *BaseApi) Login(c *gin.Context) {
|
||||
}
|
||||
|
||||
var oc bool = openCaptcha == 0 || openCaptcha < interfaceToInt(v)
|
||||
|
||||
if !oc || (l.CaptchaId != "" && l.Captcha != "" && store.Verify(l.CaptchaId, l.Captcha, true)) {
|
||||
u := &system.SysUser{Username: l.Username, Password: l.Password}
|
||||
user, err := userService.Login(u)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("登陆失败! 用户名不存在或者密码错误!", zap.Error(err))
|
||||
// 验证码次数+1
|
||||
global.BlackCache.Increment(key, 1)
|
||||
response.FailWithMessage("用户名不存在或者密码错误", c)
|
||||
return
|
||||
}
|
||||
if user.Enable != 1 {
|
||||
global.GVA_LOG.Error("登陆失败! 用户被禁止登录!")
|
||||
// 验证码次数+1
|
||||
global.BlackCache.Increment(key, 1)
|
||||
response.FailWithMessage("用户被禁止登录", c)
|
||||
return
|
||||
}
|
||||
b.TokenNext(c, *user)
|
||||
if oc && (l.Captcha == "" || l.CaptchaId == "" || !store.Verify(l.CaptchaId, l.Captcha, true)) {
|
||||
// 验证码次数+1
|
||||
global.BlackCache.Increment(key, 1)
|
||||
response.FailWithMessage("验证码错误", c)
|
||||
return
|
||||
}
|
||||
// 验证码次数+1
|
||||
global.BlackCache.Increment(key, 1)
|
||||
response.FailWithMessage("验证码错误", c)
|
||||
|
||||
u := &system.SysUser{Username: l.Username, Password: l.Password}
|
||||
user, err := userService.Login(u)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("登陆失败! 用户名不存在或者密码错误!", zap.Error(err))
|
||||
// 验证码次数+1
|
||||
global.BlackCache.Increment(key, 1)
|
||||
response.FailWithMessage("用户名不存在或者密码错误", c)
|
||||
return
|
||||
}
|
||||
if user.Enable != 1 {
|
||||
global.GVA_LOG.Error("登陆失败! 用户被禁止登录!")
|
||||
// 验证码次数+1
|
||||
global.BlackCache.Increment(key, 1)
|
||||
response.FailWithMessage("用户被禁止登录", c)
|
||||
return
|
||||
}
|
||||
b.TokenNext(c, *user)
|
||||
}
|
||||
|
||||
// TokenNext 登录以后签发jwt
|
||||
|
||||
@@ -267,6 +267,17 @@ func (sysVersionApi *SysVersionApi) ExportVersion(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取选中的字典数据
|
||||
var dictData []system.SysDictionary
|
||||
if len(req.DictIds) > 0 {
|
||||
dictData, err = sysVersionService.GetDictionariesByIds(ctx, req.DictIds)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取字典数据失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取字典数据失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 处理菜单数据,构建递归的children结构
|
||||
processedMenus := buildMenuTree(menuData)
|
||||
|
||||
@@ -282,6 +293,34 @@ func (sysVersionApi *SysVersionApi) ExportVersion(c *gin.Context) {
|
||||
processedApis = append(processedApis, cleanApi)
|
||||
}
|
||||
|
||||
// 处理字典数据,清除ID和时间戳字段,包含字典详情
|
||||
processedDicts := make([]system.SysDictionary, 0, len(dictData))
|
||||
for _, dict := range dictData {
|
||||
cleanDict := system.SysDictionary{
|
||||
Name: dict.Name,
|
||||
Type: dict.Type,
|
||||
Status: dict.Status,
|
||||
Desc: dict.Desc,
|
||||
}
|
||||
|
||||
// 处理字典详情数据,清除ID和时间戳字段
|
||||
cleanDetails := make([]system.SysDictionaryDetail, 0, len(dict.SysDictionaryDetails))
|
||||
for _, detail := range dict.SysDictionaryDetails {
|
||||
cleanDetail := system.SysDictionaryDetail{
|
||||
Label: detail.Label,
|
||||
Value: detail.Value,
|
||||
Extend: detail.Extend,
|
||||
Status: detail.Status,
|
||||
Sort: detail.Sort,
|
||||
// 不复制 ID, CreatedAt, UpdatedAt, SysDictionaryID
|
||||
}
|
||||
cleanDetails = append(cleanDetails, cleanDetail)
|
||||
}
|
||||
cleanDict.SysDictionaryDetails = cleanDetails
|
||||
|
||||
processedDicts = append(processedDicts, cleanDict)
|
||||
}
|
||||
|
||||
// 构建导出数据
|
||||
exportData := systemRes.ExportVersionResponse{
|
||||
Version: systemReq.VersionInfo{
|
||||
@@ -290,8 +329,9 @@ func (sysVersionApi *SysVersionApi) ExportVersion(c *gin.Context) {
|
||||
Description: req.Description,
|
||||
ExportTime: time.Now().Format("2006-01-02 15:04:05"),
|
||||
},
|
||||
Menus: processedMenus,
|
||||
Apis: processedApis,
|
||||
Menus: processedMenus,
|
||||
Apis: processedApis,
|
||||
Dictionaries: processedDicts,
|
||||
}
|
||||
|
||||
// 转换为JSON
|
||||
@@ -418,6 +458,15 @@ func (sysVersionApi *SysVersionApi) ImportVersion(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// 导入字典数据
|
||||
if len(importData.ExportDictionary) > 0 {
|
||||
if err := sysVersionService.ImportDictionaries(importData.ExportDictionary); err != nil {
|
||||
global.GVA_LOG.Error("导入字典失败!", zap.Error(err))
|
||||
response.FailWithMessage("导入字典失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 创建导入记录
|
||||
jsonData, _ := json.Marshal(importData)
|
||||
version := system.SysVersion{
|
||||
|
||||
+218
-155
@@ -2,107 +2,103 @@
|
||||
|
||||
# jwt configuration
|
||||
jwt:
|
||||
signing-key: qmPlus
|
||||
expires-time: 7d
|
||||
buffer-time: 1d
|
||||
issuer: qmPlus
|
||||
signing-key: qmPlus
|
||||
expires-time: 7d
|
||||
buffer-time: 1d
|
||||
issuer: qmPlus
|
||||
# zap logger configuration
|
||||
zap:
|
||||
level: info
|
||||
format: console
|
||||
prefix: "[github.com/flipped-aurora/gin-vue-admin/server]"
|
||||
director: log
|
||||
show-line: true
|
||||
encode-level: LowercaseColorLevelEncoder
|
||||
stacktrace-key: stacktrace
|
||||
log-in-console: true
|
||||
level: info
|
||||
format: console
|
||||
prefix: "[github.com/flipped-aurora/gin-vue-admin/server]"
|
||||
director: log
|
||||
show-line: true
|
||||
encode-level: LowercaseColorLevelEncoder
|
||||
stacktrace-key: stacktrace
|
||||
log-in-console: true
|
||||
retention-day: -1
|
||||
|
||||
# redis configuration
|
||||
redis:
|
||||
db: 0
|
||||
addr: 177.7.0.14:6379
|
||||
password: ""
|
||||
#是否使用redis集群模式
|
||||
useCluster: false
|
||||
#使用集群模式addr和db默认无效
|
||||
addr: 177.7.0.14:6379
|
||||
password: ""
|
||||
db: 0
|
||||
clusterAddrs:
|
||||
- "177.7.0.14:7000"
|
||||
- "177.7.0.15:7001"
|
||||
- "177.7.0.13:7002"
|
||||
|
||||
# redis-list configuration
|
||||
redis-list:
|
||||
- name: cache # 数据库的名称,注意: name 需要在 redis-list 中唯一
|
||||
useCluster: false # 是否使用redis集群模式
|
||||
addr: 177.7.0.14:6379 # 使用集群模式addr和db默认无效
|
||||
password: ""
|
||||
db: 0
|
||||
clusterAddrs:
|
||||
- "177.7.0.14:7000"
|
||||
- "177.7.0.15:7001"
|
||||
- "177.7.0.13:7002"
|
||||
|
||||
# mongo configuration
|
||||
mongo:
|
||||
coll: ''
|
||||
options: ''
|
||||
database: ''
|
||||
username: ''
|
||||
password: ''
|
||||
min-pool-size: 0
|
||||
max-pool-size: 100
|
||||
socket-timeout-ms: 0
|
||||
connect-timeout-ms: 0
|
||||
is-zap: false
|
||||
hosts:
|
||||
- host: ''
|
||||
port: ''
|
||||
coll: ''
|
||||
options: ''
|
||||
database: ''
|
||||
username: ''
|
||||
password: ''
|
||||
auth-source: ''
|
||||
min-pool-size: 0
|
||||
max-pool-size: 100
|
||||
socket-timeout-ms: 0
|
||||
connect-timeout-ms: 0
|
||||
is-zap: false
|
||||
hosts:
|
||||
- host: ''
|
||||
port: ''
|
||||
|
||||
# email configuration
|
||||
email:
|
||||
to: xxx@qq.com
|
||||
port: 465
|
||||
from: xxx@163.com
|
||||
host: smtp.163.com
|
||||
is-ssl: true
|
||||
secret: xxx
|
||||
nickname: test
|
||||
to: xxx@qq.com
|
||||
port: 465
|
||||
from: xxx@163.com
|
||||
host: smtp.163.com
|
||||
is-ssl: true
|
||||
secret: xxx
|
||||
nickname: test
|
||||
|
||||
# system configuration
|
||||
system:
|
||||
env: public # Change to "develop" to skip authentication for development mode
|
||||
addr: 8888
|
||||
db-type: mysql
|
||||
oss-type: local # 控制oss选择走本地还是 七牛等其他仓 自行增加其他oss仓可以在 server/utils/upload/upload.go 中 NewOss函数配置
|
||||
use-redis: false # 使用redis
|
||||
use-mongo: false # 使用mongo
|
||||
use-multipoint: false
|
||||
# IP限制次数 一个小时15000次
|
||||
iplimit-count: 15000
|
||||
# IP限制一个小时
|
||||
iplimit-time: 3600
|
||||
env: local # 修改为public可以关闭路由日志输出
|
||||
addr: 8888
|
||||
db-type: mysql
|
||||
oss-type: local # 控制oss选择走本地还是 七牛等其他仓 自行增加其他oss仓可以在 server/utils/upload/upload.go 中 NewOss函数配置
|
||||
use-redis: false # 使用redis
|
||||
use-mongo: false # 使用mongo
|
||||
use-multipoint: false
|
||||
# IP限制次数 一个小时15000次
|
||||
iplimit-count: 15000
|
||||
# IP限制一个小时
|
||||
iplimit-time: 3600
|
||||
# 路由全局前缀
|
||||
router-prefix: ""
|
||||
# 严格角色模式 打开后权限将会存在上下级关系
|
||||
use-strict-auth: false
|
||||
|
||||
# captcha configuration
|
||||
captcha:
|
||||
key-long: 6
|
||||
img-width: 240
|
||||
img-height: 80
|
||||
open-captcha: 0 # 0代表一直开启,大于0代表限制次数
|
||||
open-captcha-timeout: 3600 # open-captcha大于0时才生效
|
||||
key-long: 6
|
||||
img-width: 240
|
||||
img-height: 80
|
||||
open-captcha: 0 # 0代表一直开启,大于0代表限制次数
|
||||
open-captcha-timeout: 3600 # open-captcha大于0时才生效
|
||||
|
||||
# mysql connect configuration
|
||||
# 未初始化之前请勿手动修改数据库信息!!!如果一定要手动初始化请看(https://gin-vue-admin.com/docs/first_master)
|
||||
mysql:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
|
||||
# pgsql connect configuration
|
||||
# 未初始化之前请勿手动修改数据库信息!!!如果一定要手动初始化请看(https://gin-vue-admin.com/docs/first_master)
|
||||
pgsql:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
|
||||
db-list:
|
||||
- disable: true # 是否禁用
|
||||
type: "" # 数据库的类型,目前支持mysql、pgsql
|
||||
alias-name: "" # 数据库的名称,注意: alias-name 需要在db-list中唯一
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
@@ -114,107 +110,174 @@ db-list:
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
|
||||
# pgsql connect configuration
|
||||
# 未初始化之前请勿手动修改数据库信息!!!如果一定要手动初始化请看(https://gin-vue-admin.com/docs/first_master)
|
||||
pgsql:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
oracle:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
mssql:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
sqlite:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
db-list:
|
||||
- disable: true # 是否禁用
|
||||
type: "" # 数据库的类型,目前支持mysql、pgsql、mssql、oracle
|
||||
alias-name: "" # 数据库的名称,注意: alias-name 需要在db-list中唯一
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
|
||||
# local configuration
|
||||
local:
|
||||
path: uploads/file
|
||||
store-path: uploads/file
|
||||
path: uploads/file
|
||||
store-path: uploads/file
|
||||
|
||||
# autocode configuration
|
||||
autocode:
|
||||
transfer-restart: true
|
||||
# root 自动适配项目根目录
|
||||
# 请不要手动配置,他会在项目加载的时候识别出根路径
|
||||
root: ""
|
||||
server: /server
|
||||
server-plug: /plugin/%s
|
||||
server-api: /api/v1/%s
|
||||
server-initialize: /initialize
|
||||
server-model: /model/%s
|
||||
server-request: /model/%s/request/
|
||||
server-router: /router/%s
|
||||
server-service: /service/%s
|
||||
web: /web/src
|
||||
web-api: /api
|
||||
web-form: /view
|
||||
web-table: /view
|
||||
web: web/src
|
||||
root: "" # root 自动适配项目根目录, 请不要手动配置,他会在项目加载的时候识别出根路径
|
||||
server: server
|
||||
module: 'github.com/flipped-aurora/gin-vue-admin/server'
|
||||
ai-path: "" # AI服务路径
|
||||
|
||||
# qiniu configuration (请自行七牛申请对应的 公钥 私钥 bucket 和 域名地址)
|
||||
qiniu:
|
||||
zone: ZoneHuaDong
|
||||
bucket: ""
|
||||
img-path: ""
|
||||
use-https: false
|
||||
access-key: ""
|
||||
secret-key: ""
|
||||
use-cdn-domains: false
|
||||
zone: ZoneHuaDong
|
||||
bucket: ""
|
||||
img-path: ""
|
||||
use-https: false
|
||||
access-key: ""
|
||||
secret-key: ""
|
||||
use-cdn-domains: false
|
||||
|
||||
# minio oss configuration
|
||||
minio:
|
||||
endpoint: yourEndpoint
|
||||
access-key-id: yourAccessKeyId
|
||||
access-key-secret: yourAccessKeySecret
|
||||
bucket-name: yourBucketName
|
||||
use-ssl: false
|
||||
base-path: ""
|
||||
bucket-url: "http://host:9000/yourBucketName"
|
||||
|
||||
# aliyun oss configuration
|
||||
aliyun-oss:
|
||||
endpoint: yourEndpoint
|
||||
access-key-id: yourAccessKeyId
|
||||
access-key-secret: yourAccessKeySecret
|
||||
bucket-name: yourBucketName
|
||||
bucket-url: yourBucketUrl
|
||||
base-path: yourBasePath
|
||||
endpoint: yourEndpoint
|
||||
access-key-id: yourAccessKeyId
|
||||
access-key-secret: yourAccessKeySecret
|
||||
bucket-name: yourBucketName
|
||||
bucket-url: yourBucketUrl
|
||||
base-path: yourBasePath
|
||||
|
||||
# tencent cos configuration
|
||||
tencent-cos:
|
||||
bucket: xxxxx-10005608
|
||||
region: ap-shanghai
|
||||
secret-id: your-secret-id
|
||||
secret-key: your-secret-key
|
||||
base-url: https://gin.vue.admin
|
||||
path-prefix: github.com/flipped-aurora/gin-vue-admin/server
|
||||
bucket: xxxxx-10005608
|
||||
region: ap-shanghai
|
||||
secret-id: your-secret-id
|
||||
secret-key: your-secret-key
|
||||
base-url: https://gin.vue.admin
|
||||
path-prefix: github.com/flipped-aurora/gin-vue-admin/server
|
||||
|
||||
# aws s3 configuration (minio compatible)
|
||||
aws-s3:
|
||||
bucket: xxxxx-10005608
|
||||
region: ap-shanghai
|
||||
endpoint: ""
|
||||
s3-force-path-style: false
|
||||
disable-ssl: false
|
||||
secret-id: your-secret-id
|
||||
secret-key: your-secret-key
|
||||
base-url: https://gin.vue.admin
|
||||
path-prefix: github.com/flipped-aurora/gin-vue-admin/server
|
||||
bucket: xxxxx-10005608
|
||||
region: ap-shanghai
|
||||
endpoint: ""
|
||||
s3-force-path-style: false
|
||||
disable-ssl: false
|
||||
secret-id: your-secret-id
|
||||
secret-key: your-secret-key
|
||||
base-url: https://gin.vue.admin
|
||||
path-prefix: github.com/flipped-aurora/gin-vue-admin/server
|
||||
|
||||
# cloudflare r2 configuration
|
||||
cloudflare-r2:
|
||||
bucket: xxxx0bucket
|
||||
base-url: https://gin.vue.admin.com
|
||||
path: uploads
|
||||
account-id: xxx_account_id
|
||||
access-key-id: xxx_key_id
|
||||
secret-access-key: xxx_secret_key
|
||||
|
||||
# huawei obs configuration
|
||||
hua-wei-obs:
|
||||
path: you-path
|
||||
bucket: you-bucket
|
||||
endpoint: you-endpoint
|
||||
access-key: you-access-key
|
||||
secret-key: you-secret-key
|
||||
path: you-path
|
||||
bucket: you-bucket
|
||||
endpoint: you-endpoint
|
||||
access-key: you-access-key
|
||||
secret-key: you-secret-key
|
||||
|
||||
# excel configuration
|
||||
excel:
|
||||
dir: ./resource/excel/
|
||||
dir: ./resource/excel/
|
||||
|
||||
# timer task db clear table
|
||||
Timer:
|
||||
start: true
|
||||
spec: "@daily" # 定时任务详细配置参考 https://pkg.go.dev/github.com/robfig/cron/v3
|
||||
detail:
|
||||
- tableName: sys_operation_records
|
||||
compareField: created_at
|
||||
interval: 2160h
|
||||
- tableName: jwt_blacklists
|
||||
compareField: created_at
|
||||
interval: 168h
|
||||
# disk usage configuration
|
||||
disk-list:
|
||||
- mount-point: "/"
|
||||
|
||||
# 跨域配置
|
||||
# 需要配合 server/initialize/router.go -> `Router.Use(middleware.CorsByRules())` 使用
|
||||
cors:
|
||||
mode: whitelist # 放行模式: allow-all, 放行全部; whitelist, 白名单模式, 来自白名单内域名的请求添加 cors 头; strict-whitelist 严格白名单模式, 白名单外的请求一律拒绝
|
||||
whitelist:
|
||||
- allow-origin: example1.com
|
||||
allow-headers: content-type
|
||||
allow-methods: GET, POST
|
||||
expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
|
||||
allow-credentials: true # 布尔值
|
||||
- allow-origin: example2.com
|
||||
allow-headers: content-type
|
||||
allow-methods: GET, POST
|
||||
expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
|
||||
allow-credentials: true # 布尔值
|
||||
mode: strict-whitelist # 放行模式: allow-all, 放行全部; whitelist, 白名单模式, 来自白名单内域名的请求添加 cors 头; strict-whitelist 严格白名单模式, 白名单外的请求一律拒绝
|
||||
whitelist:
|
||||
- allow-origin: example1.com
|
||||
allow-headers: Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id
|
||||
allow-methods: POST, GET
|
||||
expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
|
||||
|
||||
allow-credentials: true # 布尔值
|
||||
- allow-origin: example2.com
|
||||
allow-headers: content-type
|
||||
allow-methods: GET, POST
|
||||
expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
|
||||
allow-credentials: true # 布尔值
|
||||
mcp:
|
||||
name: GVA_MCP
|
||||
version: v1.0.0
|
||||
sse_path: /sse
|
||||
message_path: /message
|
||||
url_prefix: ''
|
||||
+2
-5
@@ -237,11 +237,8 @@ tencent-cos:
|
||||
base-url: https://gin.vue.admin
|
||||
path-prefix: github.com/flipped-aurora/gin-vue-admin/server
|
||||
volcengine:
|
||||
access-key: your-access-key
|
||||
secret-key: your-secret-key
|
||||
region: cn-beijing
|
||||
endpoint: https://ark.cn-beijing.volces.com
|
||||
default-model: ep-xxx
|
||||
api-key: 7562d83f-fc5c-4229-9aed-e8d9242c8683
|
||||
model: ep-20250909151934-m777l
|
||||
wechat:
|
||||
mini-app-id: wx0f5dc17ba3f9fe31
|
||||
mini-app-secret: 5e700810a6f56717e28af76dbb63983d
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type Oracle struct {
|
||||
GeneralDB `yaml:",inline" mapstructure:",squash"`
|
||||
}
|
||||
|
||||
func (m *Oracle) Dsn() string {
|
||||
return "oracle://" + m.Username + ":" + m.Password + "@" + m.Path + ":" + m.Port + "/" + m.Dbname + "?" + m.Config
|
||||
dsn := fmt.Sprintf("oracle://%s:%s@%s/%s?%s", url.PathEscape(m.Username), url.PathEscape(m.Password),
|
||||
net.JoinHostPort(m.Path, m.Port), url.PathEscape(m.Dbname), m.Config)
|
||||
return dsn
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package config
|
||||
|
||||
type Volcengine struct {
|
||||
AccessKey string `mapstructure:"access-key" json:"access-key" yaml:"access-key"` // 火山引擎访问密钥ID
|
||||
SecretKey string `mapstructure:"secret-key" json:"secret-key" yaml:"secret-key"` // 火山引擎访问密钥Secret
|
||||
Region string `mapstructure:"region" json:"region" yaml:"region"` // 区域,如:cn-beijing
|
||||
Endpoint string `mapstructure:"endpoint" json:"endpoint" yaml:"endpoint"` // 服务端点,如:https://ark.cn-beijing.volces.com
|
||||
DefaultModel string `mapstructure:"default-model" json:"default-model" yaml:"default-model"` // 默认模型ID,如:ep-xxx
|
||||
ApiKey string `mapstructure:"api-key" json:"api-key" yaml:"api-key"` // 火山引擎API密钥
|
||||
Model string `mapstructure:"model" json:"model" yaml:"model"` // 模型ID,如:ep-xxx
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ func RunServer() {
|
||||
|
||||
fmt.Printf(`
|
||||
欢迎使用 gin-vue-admin
|
||||
当前版本:v2.8.4
|
||||
当前版本:%s
|
||||
加群方式:微信号:shouzi_1994 QQ群:470239250
|
||||
项目地址:https://github.com/flipped-aurora/gin-vue-admin
|
||||
插件市场:https://plugin.gin-vue-admin.com
|
||||
@@ -48,6 +48,7 @@ func RunServer() {
|
||||
** 版权所有方:flipped-aurora开源团队 **
|
||||
** 版权持有公司:北京翻转极光科技有限责任公司 **
|
||||
** 剔除授权标识需购买商用授权:https://gin-vue-admin.com/empower/index.html **
|
||||
`, address, address, global.GVA_CONFIG.MCP.SSEPath, address, global.GVA_CONFIG.MCP.MessagePath)
|
||||
** 感谢您对Gin-Vue-Admin的支持与关注 合法授权使用更有利于项目的长久发展**
|
||||
`, global.Version, address, address, global.GVA_CONFIG.MCP.SSEPath, address, global.GVA_CONFIG.MCP.MessagePath)
|
||||
initServer(address, Router, 10*time.Minute, 10*time.Minute)
|
||||
}
|
||||
|
||||
+5
-2
@@ -1,7 +1,10 @@
|
||||
// Package docs Code generated by swaggo/swag. DO NOT EDIT
|
||||
package docs
|
||||
|
||||
import "github.com/swaggo/swag"
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/swaggo/swag"
|
||||
)
|
||||
|
||||
const docTemplate = `{
|
||||
"schemes": {{ marshal .Schemes }},
|
||||
@@ -10411,7 +10414,7 @@ const docTemplate = `{
|
||||
|
||||
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||
var SwaggerInfo = &swag.Spec{
|
||||
Version: "v2.8.4",
|
||||
Version: global.Version,
|
||||
Host: "",
|
||||
BasePath: "",
|
||||
Schemes: []string{},
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package global
|
||||
|
||||
// Version 版本信息
|
||||
// 目前只有Version正式使用 其余为预留
|
||||
const (
|
||||
// Version 当前版本号
|
||||
Version = "v2.8.5"
|
||||
// AppName 应用名称
|
||||
AppName = "Gin-Vue-Admin"
|
||||
// Description 应用描述
|
||||
Description = "使用gin+vue进行极速开发的全栈开发基础平台"
|
||||
)
|
||||
+8
-5
@@ -1,14 +1,13 @@
|
||||
module github.com/flipped-aurora/gin-vue-admin/server
|
||||
|
||||
go 1.23
|
||||
|
||||
toolchain go1.23.9
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
||||
github.com/aws/aws-sdk-go v1.55.6
|
||||
github.com/casbin/casbin/v2 v2.103.0
|
||||
github.com/casbin/gorm-adapter/v3 v3.32.0
|
||||
github.com/dzwvip/gorm-oracle v0.1.2
|
||||
github.com/fsnotify/fsnotify v1.8.0
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
@@ -19,6 +18,7 @@ require (
|
||||
github.com/gookit/color v1.5.4
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
|
||||
github.com/kirklin/go-swd v0.0.3
|
||||
github.com/mark3labs/mcp-go v0.31.0
|
||||
github.com/mholt/archives v0.1.1
|
||||
github.com/minio/minio-go/v7 v7.0.84
|
||||
@@ -33,13 +33,13 @@ require (
|
||||
github.com/silenceper/wechat/v2 v2.1.9
|
||||
github.com/songzhibin97/gkit v1.2.13
|
||||
github.com/spf13/viper v1.19.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/swaggo/files v1.0.1
|
||||
github.com/swaggo/gin-swagger v1.6.0
|
||||
github.com/swaggo/swag v1.16.4
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.60
|
||||
github.com/unrolled/secure v1.17.0
|
||||
github.com/volcengine/volcengine-go-sdk v1.1.25
|
||||
github.com/volcengine/volcengine-go-sdk v1.1.31
|
||||
github.com/xuri/excelize/v2 v2.9.0
|
||||
go.mongodb.org/mongo-driver v1.17.2
|
||||
go.uber.org/automaxprocs v1.6.0
|
||||
@@ -77,6 +77,7 @@ require (
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/emirpasic/gods v1.12.0 // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/gammazero/toposort v0.1.1 // indirect
|
||||
@@ -142,6 +143,7 @@ require (
|
||||
github.com/sagikazarmark/locafero v0.7.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/sijms/go-ora/v2 v2.7.17 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/sorairolake/lzip-go v0.3.5 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
@@ -150,6 +152,7 @@ require (
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/therootcompany/xz v1.0.1 // indirect
|
||||
github.com/thoas/go-funk v0.7.0 // indirect
|
||||
github.com/tidwall/gjson v1.14.1 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
|
||||
+16
-3
@@ -115,6 +115,10 @@ github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj6
|
||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/dzwvip/gorm-oracle v0.1.2 h1:811aFDY7oDfKWHc0Z0lHdXzzr89EmKBSwc/jLJ8GU5g=
|
||||
github.com/dzwvip/gorm-oracle v0.1.2/go.mod h1:TbF7idnO9UgGpJ0qJpDZby1/wGquzP5GYof88ScBITE=
|
||||
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
|
||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
@@ -291,6 +295,7 @@ github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJk
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
@@ -305,6 +310,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/kirklin/go-swd v0.0.3 h1:NvtV4jV9mm/1gobGYp7UE6bgBhCdJfBckO+ePzFffbo=
|
||||
github.com/kirklin/go-swd v0.0.3/go.mod h1:bnU1Fz3Uil9T1mRyiwjyeV39567+iDFaxj7XRQUmK2s=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
@@ -455,6 +462,8 @@ github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFt
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/sijms/go-ora/v2 v2.7.17 h1:M/pYIqjaMUeBxyzOWp2oj4ntF6fHSBloJWGNH9vbmsU=
|
||||
github.com/sijms/go-ora/v2 v2.7.17/go.mod h1:EHxlY6x7y9HAsdfumurRfTd+v8NrEOTR3Xl4FWlH6xk=
|
||||
github.com/silenceper/wechat/v2 v2.1.9 h1:wc092gUkGbbBRTdzPxROhQhOH5iE98stnfzKA73mnTo=
|
||||
github.com/silenceper/wechat/v2 v2.1.9/go.mod h1:7Iu3EhQYVtDUJAj+ZVRy8yom75ga7aDWv8RurLkVm0s=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
@@ -489,8 +498,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
|
||||
@@ -505,6 +515,8 @@ github.com/tencentyun/cos-go-sdk-v5 v0.7.60 h1:/e/tmvRmfKexr/QQIBzWhOkZWsmY3EK72
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.60/go.mod h1:8+hG+mQMuRP/OIS9d83syAvXvrMj9HhkND6Q1fLghw0=
|
||||
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
|
||||
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
|
||||
github.com/thoas/go-funk v0.7.0 h1:GmirKrs6j6zJbhJIficOsz2aAI7700KsU/5YrdHRM1Y=
|
||||
github.com/thoas/go-funk v0.7.0/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q=
|
||||
github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo=
|
||||
github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
@@ -526,8 +538,8 @@ github.com/unrolled/secure v1.17.0 h1:Io7ifFgo99Bnh0J7+Q+qcMzWM6kaDPCA5FroFZEdbW
|
||||
github.com/unrolled/secure v1.17.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40=
|
||||
github.com/volcengine/volc-sdk-golang v1.0.23 h1:anOslb2Qp6ywnsbyq9jqR0ljuO63kg9PY+4OehIk5R8=
|
||||
github.com/volcengine/volc-sdk-golang v1.0.23/go.mod h1:AfG/PZRUkHJ9inETvbjNifTDgut25Wbkm2QoYBTbvyU=
|
||||
github.com/volcengine/volcengine-go-sdk v1.1.25 h1:wwR2DTJGw2sOZ1wTWaQLn03PGO0O+motGvsoVvAp5Zk=
|
||||
github.com/volcengine/volcengine-go-sdk v1.1.25/go.mod h1:EyKoi6t6eZxoPNGr2GdFCZti2Skd7MO3eUzx7TtSvNo=
|
||||
github.com/volcengine/volcengine-go-sdk v1.1.31 h1:qrNd/fu+ZWzH93EBSPBSntGwgwo7cHITxxv1IdvLxls=
|
||||
github.com/volcengine/volcengine-go-sdk v1.1.31/go.mod h1:EyKoi6t6eZxoPNGr2GdFCZti2Skd7MO3eUzx7TtSvNo=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||
@@ -889,6 +901,7 @@ gorm.io/driver/sqlserver v1.5.4 h1:xA+Y1KDNspv79q43bPyjDMUgHoYHLhXYmdFcYPobg8g=
|
||||
gorm.io/driver/sqlserver v1.5.4/go.mod h1:+frZ/qYmuna11zHPlh5oc2O6ZA/lS88Keb0XSH1Zh/g=
|
||||
gorm.io/gen v0.3.26 h1:sFf1j7vNStimPRRAtH4zz5NiHM+1dr6eA9aaRdplyhY=
|
||||
gorm.io/gen v0.3.26/go.mod h1:a5lq5y3w4g5LMxBcw0wnO6tYUCdNutWODq5LrIt75LE=
|
||||
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
||||
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
|
||||
@@ -28,7 +28,9 @@ func GormMssql() *gorm.DB {
|
||||
DSN: m.Dsn(), // DSN data source name
|
||||
DefaultStringSize: 191, // string 类型字段的默认长度
|
||||
}
|
||||
if db, err := gorm.Open(sqlserver.New(mssqlConfig), internal.Gorm.Config(m.Prefix, m.Singular)); err != nil {
|
||||
// 数据库配置
|
||||
general := m.GeneralDB
|
||||
if db, err := gorm.Open(sqlserver.New(mssqlConfig), internal.Gorm.Config(general)); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
db.InstanceSet("gorm:table_options", "ENGINE="+m.Engine)
|
||||
@@ -48,7 +50,9 @@ func GormMssqlByConfig(m config.Mssql) *gorm.DB {
|
||||
DSN: m.Dsn(), // DSN data source name
|
||||
DefaultStringSize: 191, // string 类型字段的默认长度
|
||||
}
|
||||
if db, err := gorm.Open(sqlserver.New(mssqlConfig), internal.Gorm.Config(m.Prefix, m.Singular)); err != nil {
|
||||
// 数据库配置
|
||||
general := m.GeneralDB
|
||||
if db, err := gorm.Open(sqlserver.New(mssqlConfig), internal.Gorm.Config(general)); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
db.InstanceSet("gorm:table_options", "ENGINE=InnoDB")
|
||||
|
||||
@@ -34,8 +34,9 @@ func initMysqlDatabase(m config.Mysql) *gorm.DB {
|
||||
DefaultStringSize: 191, // string 类型字段的默认长度
|
||||
SkipInitializeWithVersion: false, // 根据版本自动配置
|
||||
}
|
||||
|
||||
if db, err := gorm.Open(mysql.New(mysqlConfig), internal.Gorm.Config(m.Prefix, m.Singular)); err != nil {
|
||||
// 数据库配置
|
||||
general := m.GeneralDB
|
||||
if db, err := gorm.Open(mysql.New(mysqlConfig), internal.Gorm.Config(general)); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
db.InstanceSet("gorm:table_options", "ENGINE="+m.Engine)
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
//"github.com/dzwvip/oracle"
|
||||
oracle "github.com/dzwvip/gorm-oracle"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/config"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/initialize/internal"
|
||||
|
||||
//_ "github.com/godror/godror"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// GormOracle 初始化oracle数据库
|
||||
// 如果需要Oracle库 放开import里的注释 把下方 mysql.Config 改为 oracle.Config ; mysql.New 改为 oracle.New
|
||||
func GormOracle() *gorm.DB {
|
||||
m := global.GVA_CONFIG.Oracle
|
||||
return initOracleDatabase(m)
|
||||
@@ -28,13 +24,9 @@ func initOracleDatabase(m config.Oracle) *gorm.DB {
|
||||
if m.Dbname == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
oracleConfig := mysql.Config{
|
||||
DSN: m.Dsn(), // DSN data source name
|
||||
DefaultStringSize: 191, // string 类型字段的默认长度
|
||||
}
|
||||
|
||||
if db, err := gorm.Open(mysql.New(oracleConfig), internal.Gorm.Config(m.Prefix, m.Singular)); err != nil {
|
||||
// 数据库配置
|
||||
general := m.GeneralDB
|
||||
if db, err := gorm.Open(oracle.Open(m.Dsn()), internal.Gorm.Config(general)); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
sqlDB, _ := db.DB()
|
||||
|
||||
@@ -30,7 +30,9 @@ func initPgSqlDatabase(p config.Pgsql) *gorm.DB {
|
||||
DSN: p.Dsn(), // DSN data source name
|
||||
PreferSimpleProtocol: false,
|
||||
}
|
||||
if db, err := gorm.Open(postgres.New(pgsqlConfig), internal.Gorm.Config(p.Prefix, p.Singular)); err != nil {
|
||||
// 数据库配置
|
||||
general := p.GeneralDB
|
||||
if db, err := gorm.Open(postgres.New(pgsqlConfig), internal.Gorm.Config(general)); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
sqlDB, _ := db.DB()
|
||||
|
||||
@@ -25,7 +25,9 @@ func initSqliteDatabase(s config.Sqlite) *gorm.DB {
|
||||
return nil
|
||||
}
|
||||
|
||||
if db, err := gorm.Open(sqlite.Open(s.Dsn()), internal.Gorm.Config(s.Prefix, s.Singular)); err != nil {
|
||||
// 数据库配置
|
||||
general := s.GeneralDB
|
||||
if db, err := gorm.Open(sqlite.Open(s.Dsn()), internal.Gorm.Config(general)); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
sqlDB, _ := db.DB()
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/config"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
"gorm.io/gorm/schema"
|
||||
"time"
|
||||
)
|
||||
|
||||
var Gorm = new(_gorm)
|
||||
@@ -15,22 +15,7 @@ type _gorm struct{}
|
||||
|
||||
// Config gorm 自定义配置
|
||||
// Author [SliverHorn](https://github.com/SliverHorn)
|
||||
func (g *_gorm) Config(prefix string, singular bool) *gorm.Config {
|
||||
var general config.GeneralDB
|
||||
switch global.GVA_CONFIG.System.DbType {
|
||||
case "mysql":
|
||||
general = global.GVA_CONFIG.Mysql.GeneralDB
|
||||
case "pgsql":
|
||||
general = global.GVA_CONFIG.Pgsql.GeneralDB
|
||||
case "oracle":
|
||||
general = global.GVA_CONFIG.Oracle.GeneralDB
|
||||
case "sqlite":
|
||||
general = global.GVA_CONFIG.Sqlite.GeneralDB
|
||||
case "mssql":
|
||||
general = global.GVA_CONFIG.Mssql.GeneralDB
|
||||
default:
|
||||
general = global.GVA_CONFIG.Mysql.GeneralDB
|
||||
}
|
||||
func (g *_gorm) Config(general config.GeneralDB) *gorm.Config {
|
||||
return &gorm.Config{
|
||||
Logger: logger.New(NewWriter(general), logger.Config{
|
||||
SlowThreshold: 200 * time.Millisecond,
|
||||
@@ -38,8 +23,8 @@ func (g *_gorm) Config(prefix string, singular bool) *gorm.Config {
|
||||
Colorful: true,
|
||||
}),
|
||||
NamingStrategy: schema.NamingStrategy{
|
||||
TablePrefix: prefix,
|
||||
SingularTable: singular,
|
||||
TablePrefix: general.Prefix,
|
||||
SingularTable: general.Singular,
|
||||
},
|
||||
DisableForeignKeyConstraintWhenMigrating: true,
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/email"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/volcengine"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/utils/plugin"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -34,7 +33,6 @@ func bizPluginV1(group ...*gin.RouterGroup) {
|
||||
global.GVA_CONFIG.Email.IsSSL,
|
||||
global.GVA_CONFIG.Email.IsLoginAuth,
|
||||
),
|
||||
volcengine.CreateVolcenginePlug(),
|
||||
)
|
||||
holder(public, private)
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ func Routers() *gin.Engine {
|
||||
InstallPlugin(PrivateGroup, PublicGroup, Router)
|
||||
|
||||
// 注册业务路由
|
||||
initBizRouter(PrivateGroup, PublicGroup)
|
||||
initBizRouter(PrivateGroup, PublicGroup, UserGroup)
|
||||
|
||||
global.GVA_ROUTERS = Router.Routes()
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ func holder(routers ...*gin.RouterGroup) {
|
||||
func initBizRouter(routers ...*gin.RouterGroup) {
|
||||
privateGroup := routers[0]
|
||||
publicGroup := routers[1]
|
||||
userGroup := routers[2]
|
||||
holder(publicGroup, privateGroup)
|
||||
{
|
||||
petRouter := router.RouterGroupApp.Pet
|
||||
@@ -25,5 +26,10 @@ func initBizRouter(routers ...*gin.RouterGroup) {
|
||||
petRouter.InitPetFamilyPetsRouter(privateGroup, publicGroup)
|
||||
petRouter.InitPetPetsRouter(privateGroup, publicGroup) // 占位方法,保证文件可以正确加载,避免go空变量检测报错,请勿删除。
|
||||
petRouter.InitPetRecordsRouter(privateGroup, publicGroup)
|
||||
|
||||
// 用户相关路由(需要UserJWTAuth认证)
|
||||
if userGroup != nil {
|
||||
petRouter.InitPetAssistantRouter(userGroup, publicGroup)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ import (
|
||||
// @Tag.Description 用户
|
||||
|
||||
// @title Gin-Vue-Admin Swagger API接口文档
|
||||
// @version v2.8.4
|
||||
// @version v2.8.5
|
||||
// @description 使用gin+vue进行极速开发的全栈开发基础平台
|
||||
// @securityDefinitions.apikey ApiKeyAuth
|
||||
// @in header
|
||||
|
||||
@@ -41,7 +41,11 @@ type ApiCreator struct{}
|
||||
// New 创建API创建工具
|
||||
func (a *ApiCreator) New() mcp.Tool {
|
||||
return mcp.NewTool("create_api",
|
||||
mcp.WithDescription("创建后端API记录,用于AI编辑器自动添加API接口时自动创建对应的API权限记录。注意:使用gva_auto_generate创建的包和模块会自动创建API权限,无需调用此工具。仅在AI编辑器自动添加API或router下的文件产生路径变化时使用。"),
|
||||
mcp.WithDescription(`创建后端API记录,用于AI编辑器自动添加API接口时自动创建对应的API权限记录。
|
||||
|
||||
**重要限制:**
|
||||
- 当使用gva_auto_generate工具且needCreatedModules=true时,模块创建会自动生成API权限,不应调用此工具
|
||||
- 仅在以下情况使用:1) 单独创建API(不涉及模块创建);2) AI编辑器自动添加API;3) router下的文件产生路径变化时`),
|
||||
mcp.WithString("path",
|
||||
mcp.Required(),
|
||||
mcp.Description("API路径,如:/user/create"),
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
package mcpTool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// 注册工具
|
||||
func init() {
|
||||
// 注册工具将在enter.go中统一处理
|
||||
RegisterTool(&ApiLister{})
|
||||
}
|
||||
|
||||
// ApiInfo API信息结构
|
||||
type ApiInfo struct {
|
||||
ID uint `json:"id,omitempty"` // 数据库ID(仅数据库API有)
|
||||
Path string `json:"path"` // API路径
|
||||
Description string `json:"description,omitempty"` // API描述
|
||||
ApiGroup string `json:"apiGroup,omitempty"` // API组
|
||||
Method string `json:"method"` // HTTP方法
|
||||
Source string `json:"source"` // 来源:database 或 gin
|
||||
}
|
||||
|
||||
// ApiListResponse API列表响应结构
|
||||
type ApiListResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
DatabaseApis []ApiInfo `json:"databaseApis"` // 数据库中的API
|
||||
GinApis []ApiInfo `json:"ginApis"` // gin框架中的API
|
||||
TotalCount int `json:"totalCount"` // 总数量
|
||||
}
|
||||
|
||||
// ApiLister API列表工具
|
||||
type ApiLister struct{}
|
||||
|
||||
// New 创建API列表工具
|
||||
func (a *ApiLister) New() mcp.Tool {
|
||||
return mcp.NewTool("list_all_apis",
|
||||
mcp.WithDescription(`获取系统中所有的API接口,分为两组:
|
||||
|
||||
**功能说明:**
|
||||
- 返回数据库中已注册的API列表
|
||||
- 返回gin框架中实际注册的路由API列表
|
||||
- 帮助前端判断是使用现有API还是需要创建新的API,如果api在前端未使用且需要前端调用的时候,请到api文件夹下对应模块的js中添加方法并暴露给当前业务调用
|
||||
|
||||
**返回数据结构:**
|
||||
- databaseApis: 数据库中的API记录(包含ID、描述、分组等完整信息)
|
||||
- ginApis: gin路由中的API(仅包含路径和方法),需要AI根据路径自行揣摩路径的业务含义,例如:/api/user/:id 表示根据用户ID获取用户信息`),
|
||||
)
|
||||
}
|
||||
|
||||
// Handle 处理API列表请求
|
||||
func (a *ApiLister) Handle(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
|
||||
// 获取数据库中的API
|
||||
databaseApis, err := a.getDatabaseApis()
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取数据库API失败", zap.Error(err))
|
||||
errorResponse := ApiListResponse{
|
||||
Success: false,
|
||||
Message: "获取数据库API失败: " + err.Error(),
|
||||
}
|
||||
resultJSON, _ := json.Marshal(errorResponse)
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: string(resultJSON),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 获取gin路由中的API
|
||||
ginApis, err := a.getGinApis()
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取gin路由API失败", zap.Error(err))
|
||||
errorResponse := ApiListResponse{
|
||||
Success: false,
|
||||
Message: "获取gin路由API失败: " + err.Error(),
|
||||
}
|
||||
resultJSON, _ := json.Marshal(errorResponse)
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: string(resultJSON),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 构建响应
|
||||
response := ApiListResponse{
|
||||
Success: true,
|
||||
Message: "获取API列表成功",
|
||||
DatabaseApis: databaseApis,
|
||||
GinApis: ginApis,
|
||||
TotalCount: len(databaseApis) + len(ginApis),
|
||||
}
|
||||
|
||||
global.GVA_LOG.Info("API列表获取成功",
|
||||
zap.Int("数据库API数量", len(databaseApis)),
|
||||
zap.Int("gin路由API数量", len(ginApis)),
|
||||
zap.Int("总数量", response.TotalCount))
|
||||
|
||||
resultJSON, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("序列化结果失败: %v", err)
|
||||
}
|
||||
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: string(resultJSON),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// getDatabaseApis 获取数据库中的所有API
|
||||
func (a *ApiLister) getDatabaseApis() ([]ApiInfo, error) {
|
||||
var apis []system.SysApi
|
||||
err := global.GVA_DB.Model(&system.SysApi{}).Order("api_group ASC, path ASC").Find(&apis).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为ApiInfo格式
|
||||
var result []ApiInfo
|
||||
for _, api := range apis {
|
||||
result = append(result, ApiInfo{
|
||||
ID: api.ID,
|
||||
Path: api.Path,
|
||||
Description: api.Description,
|
||||
ApiGroup: api.ApiGroup,
|
||||
Method: api.Method,
|
||||
Source: "database",
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// getGinApis 获取gin路由中的所有API(包含被忽略的API)
|
||||
func (a *ApiLister) getGinApis() ([]ApiInfo, error) {
|
||||
// 从gin路由信息中获取所有API
|
||||
var result []ApiInfo
|
||||
for _, route := range global.GVA_ROUTERS {
|
||||
result = append(result, ApiInfo{
|
||||
Path: route.Path,
|
||||
Method: route.Method,
|
||||
Source: "gin",
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package mcpTool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterTool(&CurrentTime{})
|
||||
}
|
||||
|
||||
type CurrentTime struct {
|
||||
}
|
||||
|
||||
// 获取当前系统时间
|
||||
func (t *CurrentTime) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
// 获取当前系统时间
|
||||
|
||||
timezone, ok := request.GetArguments()["timezone"].(string)
|
||||
|
||||
if !ok {
|
||||
return nil, errors.New("参数错误:timezone 必须是字符串类型")
|
||||
}
|
||||
// 根据timezone参数加载对应时区
|
||||
loc, err := loadTimeZone(timezone)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取当前时间并转换为指定时区
|
||||
currentTime := time.Now().In(loc).Format("2006-01-02 15:04:05")
|
||||
//返回
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: fmt.Sprintf("%s 时区的当前时间是:%s", timezone, currentTime),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *CurrentTime) New() mcp.Tool {
|
||||
return mcp.NewTool("currentTime",
|
||||
mcp.WithDescription("获取当前系统时间"),
|
||||
mcp.WithString("timezone",
|
||||
mcp.Required(),
|
||||
mcp.Description("时区"),
|
||||
mcp.Enum("UTC", "CST", "PST", "EST", "GMT", "CET", "JST", "MST", "IST", "AST", "HST"),
|
||||
))
|
||||
}
|
||||
|
||||
// 将简写时区转换为IANA标准时区
|
||||
func loadTimeZone(timezone string) (*time.Location, error) {
|
||||
// 时区映射表
|
||||
timezoneMap := map[string]string{
|
||||
"UTC": "UTC",
|
||||
"CST": "Asia/Shanghai", // 中国标准时间
|
||||
"PST": "America/Los_Angeles",
|
||||
"EST": "America/New_York",
|
||||
"GMT": "GMT",
|
||||
"CET": "Europe/Paris",
|
||||
"JST": "Asia/Tokyo",
|
||||
"MST": "America/Denver",
|
||||
"IST": "Asia/Kolkata",
|
||||
"AST": "Asia/Riyadh", // 阿拉伯标准时间
|
||||
"HST": "Pacific/Honolulu",
|
||||
}
|
||||
|
||||
// 获取标准时区名称
|
||||
tzName, exists := timezoneMap[timezone]
|
||||
if !exists {
|
||||
return nil, errors.New("不支持的时区: " + timezone)
|
||||
}
|
||||
|
||||
// 加载时区
|
||||
return time.LoadLocation(tzName)
|
||||
}
|
||||
@@ -7,13 +7,13 @@ ExecutionPlan 是用于自动化模块创建的执行计划结构体,包含了
|
||||
|
||||
```go
|
||||
type ExecutionPlan struct {
|
||||
PackageName string `json:"packageName"` // 包名,如:"user", "order", "product"
|
||||
PackageType string `json:"packageType"` // "plugin" 或 "package"
|
||||
NeedCreatedPackage bool `json:"needCreatedPackage"` // 是否需要创建包
|
||||
NeedCreatedModules bool `json:"needCreatedModules"` // 是否需要创建模块
|
||||
PackageInfo *request.SysAutoCodePackageCreate `json:"packageInfo,omitempty"` // 包信息(当NeedCreatedPackage=true时必需)
|
||||
ModulesInfo []*request.AutoCode `json:"modulesInfo,omitempty"` // 模块信息数组(当NeedCreatedModules=true时必需,支持批量创建)
|
||||
Paths map[string]string `json:"paths,omitempty"` // 路径信息
|
||||
PackageName string `json:"packageName"` // 包名,如:"user", "order", "product"
|
||||
PackageType string `json:"packageType"` // "plugin" 或 "package"
|
||||
NeedCreatedPackage bool `json:"needCreatedPackage"` // 是否需要创建包
|
||||
NeedCreatedModules bool `json:"needCreatedModules"` // 是否需要创建模块
|
||||
PackageInfo *request.SysAutoCodePackageCreate `json:"packageInfo,omitempty"` // 包信息(当NeedCreatedPackage=true时必需)
|
||||
ModulesInfo []*request.AutoCode `json:"modulesInfo,omitempty"` // 模块信息数组(当NeedCreatedModules=true时必需,支持批量创建)
|
||||
Paths map[string]string `json:"paths,omitempty"` // 路径信息
|
||||
}
|
||||
```
|
||||
|
||||
@@ -501,19 +501,19 @@ type DataSource struct {
|
||||
2. **NeedCreatedPackage**: 当为true时,PackageInfo必须提供
|
||||
3. **NeedCreatedModules**: 当为true时,ModulesInfo必须提供
|
||||
4. **字段类型**: FieldType支持的类型包括:
|
||||
- string(字符串)
|
||||
- richtext(富文本)
|
||||
- int(整型)
|
||||
- bool(布尔值)
|
||||
- float64(浮点型)
|
||||
- time.Time(时间)
|
||||
- enum(枚举)
|
||||
- picture(单图片,字符串)
|
||||
- pictures(多图片,json字符串)
|
||||
- video(视频,字符串)
|
||||
- file(文件,json字符串)
|
||||
- json(JSON)
|
||||
- array(数组)
|
||||
- string(字符串)
|
||||
- richtext(富文本)
|
||||
- int(整型)
|
||||
- bool(布尔值)
|
||||
- float64(浮点型)
|
||||
- time.Time(时间)
|
||||
- enum(枚举)
|
||||
- picture(单图片,字符串)
|
||||
- pictures(多图片,json字符串)
|
||||
- video(视频,字符串)
|
||||
- file(文件,json字符串)
|
||||
- json(JSON)
|
||||
- array(数组)
|
||||
5. **搜索类型**: FieldSearchType支持:EQ, NE, GT, GE, LT, LE, LIKE, BETWEEN等
|
||||
6. **索引类型**: FieldIndexType支持:index, unique等
|
||||
7. **GvaModel**: 设置为true时会自动包含ID、CreatedAt、UpdatedAt、DeletedAt字段
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
package mcpTool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterTool(&GetNickname{})
|
||||
}
|
||||
|
||||
type GetNickname struct{}
|
||||
|
||||
// 根据用户username获取nickname
|
||||
func (t *GetNickname) New() mcp.Tool {
|
||||
return mcp.NewTool("getNickname",
|
||||
mcp.WithDescription("根据用户username获取nickname"),
|
||||
mcp.WithString("username",
|
||||
mcp.Required(),
|
||||
mcp.Description("用户的username"),
|
||||
))
|
||||
}
|
||||
|
||||
// Handle 处理获取昵称的请求
|
||||
func (t *GetNickname) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
// 1. 参数验证
|
||||
username, ok := request.GetArguments()["username"].(string)
|
||||
if !ok {
|
||||
return nil, errors.New("参数错误:username 必须是字符串类型")
|
||||
}
|
||||
|
||||
if username == "" {
|
||||
return nil, errors.New("参数错误:username 不能为空")
|
||||
}
|
||||
|
||||
// 2. 记录操作日志
|
||||
global.GVA_LOG.Info("getNickname 工具被调用")
|
||||
|
||||
// 3. 优化查询,只选择需要的字段
|
||||
var user struct {
|
||||
NickName string
|
||||
}
|
||||
|
||||
err := global.GVA_DB.Model(&system.SysUser{}).
|
||||
Select("nick_name").
|
||||
Where("username = ?", username).
|
||||
First(&user).Error
|
||||
|
||||
// 4. 优化错误处理
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: fmt.Sprintf("用户 %s 不存在", username),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
global.GVA_LOG.Error("数据库查询错误")
|
||||
return nil, errors.New("系统错误,请稍后再试")
|
||||
}
|
||||
|
||||
// 构造回复信息
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: fmt.Sprintf("用户 %s 的昵称是 %s", username, user.NickName),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
common "github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
|
||||
@@ -135,6 +136,10 @@ func (t *AutomationModuleAnalyzer) New() mcp.Tool {
|
||||
2. AI分析需求为1xxx2xxx格式 → gva_auto_generate(执行创建)
|
||||
3. 创建完成后,根据需要使用其他辅助工具
|
||||
|
||||
**重要限制:**
|
||||
- 当needCreatedModules=true时,模块创建会自动生成API和菜单,因此不应再调用api_creator和menu_creator工具
|
||||
- 只有在单独创建API或菜单(不涉及模块创建)时才使用api_creator和menu_creator工具
|
||||
|
||||
重要:ExecutionPlan结构体格式要求(支持批量创建):
|
||||
{
|
||||
"packageName": "包名(string)",
|
||||
@@ -169,10 +174,10 @@ func (t *AutomationModuleAnalyzer) New() mcp.Tool {
|
||||
"generateWeb": "是否生成前端(bool)",
|
||||
"generateServer": "是否生成后端(bool)",
|
||||
"fields": [{
|
||||
"fieldName": "字段名(string)",
|
||||
"fieldName": "字段名(string)必须大写开头",
|
||||
"fieldDesc": "字段描述(string)",
|
||||
"fieldType": "字段类型:string/int/bool/time.Time等(string)",
|
||||
"fieldJson": "JSON标签(string)",
|
||||
"fieldType": "字段类型支持:string(字符串),richtext(富文本),int(整型),bool(布尔值),float64(浮点型),time.Time(时间),enum(枚举),picture(单图片,字符串),pictures(多图片,json字符串),video(视频,字符串),file(文件,json字符串),json(JSON),array(数组)",
|
||||
"fieldJson": "JSON标签(string 必须是小驼峰命名,例:userName)",
|
||||
"dataTypeLong": "数据长度(string)",
|
||||
"comment": "注释(string)",
|
||||
"columnName": "数据库列名(string)",
|
||||
@@ -184,7 +189,7 @@ func (t *AutomationModuleAnalyzer) New() mcp.Tool {
|
||||
"desc": "详情显示(bool)",
|
||||
"excel": "导入导出(bool)",
|
||||
"require": "是否必填(bool)",
|
||||
"defaultValue": "默认值(string)",
|
||||
"defaultValue": "默认值(string),JSON类型(array,json,file,pictures)请保持为空他们不可以设置默认值",
|
||||
"errorText": "错误提示(string)",
|
||||
"clearable": "是否可清空(bool)",
|
||||
"sort": "是否排序(bool)",
|
||||
@@ -803,8 +808,8 @@ func (t *AutomationModuleAnalyzer) handleAnalyze(ctx context.Context, request mc
|
||||
"fields": [{
|
||||
"fieldName": "字段名(必须大写开头)",
|
||||
"fieldDesc": "字段描述",
|
||||
"fieldType": "GO 语言的数据类型",
|
||||
"fieldJson": "json标签",
|
||||
"fieldType": "字段类型支持:string(字符串),richtext(富文本),int(整型),bool(布尔值),float64(浮点型),time.Time(时间),enum(枚举),picture(单图片,字符串),pictures(多图片,json字符串),video(视频,字符串),file(文件,json字符串),json(JSON),array(数组)",
|
||||
"fieldJson": "json标签(string 必须是小驼峰命名,例:userName)",
|
||||
"dataTypeLong": "长度",
|
||||
"comment": "注释",
|
||||
"columnName": "数据库列名",
|
||||
@@ -1239,6 +1244,7 @@ func (t *AutomationModuleAnalyzer) validateExecutionPlan(plan *ExecutionPlan) er
|
||||
if field.FieldName == "" {
|
||||
return fmt.Errorf("模块 %d 字段 %d 的 fieldName 不能为空", moduleIndex+1, i+1)
|
||||
}
|
||||
|
||||
if field.FieldDesc == "" {
|
||||
return fmt.Errorf("模块 %d 字段 %d 的 fieldDesc 不能为空", moduleIndex+1, i+1)
|
||||
}
|
||||
@@ -1252,6 +1258,48 @@ func (t *AutomationModuleAnalyzer) validateExecutionPlan(plan *ExecutionPlan) er
|
||||
return fmt.Errorf("模块 %d 字段 %d 的 columnName 不能为空", moduleIndex+1, i+1)
|
||||
}
|
||||
|
||||
// 确保字段名首字母大写
|
||||
if len(field.FieldName) > 0 {
|
||||
firstChar := string(field.FieldName[0])
|
||||
if firstChar >= "a" && firstChar <= "z" {
|
||||
moduleInfo.Fields[i].FieldName = strings.ToUpper(firstChar) + field.FieldName[1:]
|
||||
}
|
||||
}
|
||||
|
||||
// 确保FieldJson使用小驼峰命名
|
||||
if len(field.FieldJson) > 0 {
|
||||
// 处理下划线命名转小驼峰
|
||||
if strings.Contains(field.FieldJson, "_") {
|
||||
parts := strings.Split(field.FieldJson, "_")
|
||||
camelCase := strings.ToLower(parts[0])
|
||||
for j := 1; j < len(parts); j++ {
|
||||
if len(parts[j]) > 0 {
|
||||
camelCase += strings.ToUpper(string(parts[j][0])) + strings.ToLower(parts[j][1:])
|
||||
}
|
||||
}
|
||||
moduleInfo.Fields[i].FieldJson = camelCase
|
||||
} else {
|
||||
// 处理首字母大写转小写
|
||||
firstChar := string(field.FieldJson[0])
|
||||
if firstChar >= "A" && firstChar <= "Z" {
|
||||
moduleInfo.Fields[i].FieldJson = strings.ToLower(firstChar) + field.FieldJson[1:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 确保ColumnName使用下划线命名
|
||||
if len(field.ColumnName) > 0 {
|
||||
// 将驼峰命名转换为下划线命名
|
||||
var result strings.Builder
|
||||
for i, r := range field.ColumnName {
|
||||
if i > 0 && r >= 'A' && r <= 'Z' {
|
||||
result.WriteRune('_')
|
||||
}
|
||||
result.WriteRune(unicode.ToLower(r))
|
||||
}
|
||||
moduleInfo.Fields[i].ColumnName = result.String()
|
||||
}
|
||||
|
||||
// 验证字段类型
|
||||
validFieldTypes := []string{"string", "int", "int64", "float64", "bool", "time.Time", "enum", "picture", "video", "file", "pictures", "array", "richtext", "json"}
|
||||
validType := false
|
||||
@@ -1365,6 +1413,13 @@ func (t *AutomationModuleAnalyzer) executeCreation(ctx context.Context, plan *Ex
|
||||
}
|
||||
|
||||
result.Message += fmt.Sprintf("批量创建完成,共处理 %d 个模块; ", len(plan.ModulesInfo))
|
||||
|
||||
// 添加重要提醒:不要使用其他MCP工具
|
||||
result.Message += "\n\n⚠️ 重要提醒:\n"
|
||||
result.Message += "模块创建已完成,API和菜单已自动生成。请不要再调用以下MCP工具:\n"
|
||||
result.Message += "- api_creator:API权限已在模块创建时自动生成\n"
|
||||
result.Message += "- menu_creator:前端菜单已在模块创建时自动生成\n"
|
||||
result.Message += "如需修改API或菜单,请直接在系统管理界面中进行配置。\n"
|
||||
}
|
||||
|
||||
result.Message += "已构建目录结构信息; "
|
||||
|
||||
@@ -64,7 +64,11 @@ type MenuCreator struct{}
|
||||
// New 创建菜单创建工具
|
||||
func (m *MenuCreator) New() mcp.Tool {
|
||||
return mcp.NewTool("create_menu",
|
||||
mcp.WithDescription("创建前端菜单记录,用于AI编辑器自动添加前端页面时自动创建对应的菜单项。注意:使用gva_auto_generate创建的包和模块会自动创建菜单项,无需调用此工具。仅在AI编辑器自动添加前端页面时使用。"),
|
||||
mcp.WithDescription(`创建前端菜单记录,用于AI编辑器自动添加前端页面时自动创建对应的菜单项。
|
||||
|
||||
**重要限制:**
|
||||
- 当使用gva_auto_generate工具且needCreatedModules=true时,模块创建会自动生成菜单项,不应调用此工具
|
||||
- 仅在以下情况使用:1) 单独创建菜单(不涉及模块创建);2) AI编辑器自动添加前端页面时`),
|
||||
mcp.WithNumber("parentId",
|
||||
mcp.Description("父菜单ID,0表示根菜单"),
|
||||
mcp.DefaultNumber(0),
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
package mcpTool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// 注册工具
|
||||
func init() {
|
||||
// 注册工具将在enter.go中统一处理
|
||||
RegisterTool(&MenuLister{})
|
||||
}
|
||||
|
||||
// MenuListResponse 菜单列表响应结构
|
||||
type MenuListResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Menus []system.SysBaseMenu `json:"menus"`
|
||||
TotalCount int `json:"totalCount"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// MenuLister 菜单列表工具
|
||||
type MenuLister struct{}
|
||||
|
||||
// New 创建菜单列表工具
|
||||
func (m *MenuLister) New() mcp.Tool {
|
||||
return mcp.NewTool("list_all_menus",
|
||||
mcp.WithDescription(`获取系统中所有菜单信息,包括菜单树结构、路由信息、组件路径等,用于前端编写vue-router时正确跳转
|
||||
|
||||
**功能说明:**
|
||||
- 返回完整的菜单树形结构
|
||||
- 包含路由配置信息(path、name、component)
|
||||
- 包含菜单元数据(title、icon、keepAlive等)
|
||||
- 包含菜单参数和按钮配置
|
||||
- 支持父子菜单关系展示
|
||||
|
||||
**使用场景:**
|
||||
- 前端路由配置:获取所有菜单信息用于配置vue-router
|
||||
- 菜单权限管理:了解系统中所有可用的菜单项
|
||||
- 导航组件开发:构建动态导航菜单
|
||||
- 系统架构分析:了解系统的菜单结构和页面组织`),
|
||||
)
|
||||
}
|
||||
|
||||
// Handle 处理菜单列表请求
|
||||
func (m *MenuLister) Handle(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
// 获取所有基础菜单
|
||||
allMenus, err := m.getAllMenus()
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取菜单列表失败", zap.Error(err))
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: fmt.Sprintf("获取菜单列表失败: %v", err),
|
||||
},
|
||||
},
|
||||
IsError: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 构建返回结果
|
||||
response := MenuListResponse{
|
||||
Success: true,
|
||||
Message: "获取菜单列表成功",
|
||||
Menus: allMenus,
|
||||
TotalCount: len(allMenus),
|
||||
Description: "系统中所有菜单信息的标准列表,包含路由配置和组件信息",
|
||||
}
|
||||
|
||||
// 序列化响应
|
||||
responseJSON, err := json.MarshalIndent(response, "", " ")
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("序列化菜单响应失败", zap.Error(err))
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: fmt.Sprintf("序列化响应失败: %v", err),
|
||||
},
|
||||
},
|
||||
IsError: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: string(responseJSON),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// getAllMenus 获取所有基础菜单
|
||||
func (m *MenuLister) getAllMenus() ([]system.SysBaseMenu, error) {
|
||||
var menus []system.SysBaseMenu
|
||||
err := global.GVA_DB.Order("sort").Preload("Parameters").Preload("MenuBtn").Find(&menus).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return menus, nil
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ type RequirementAnalysisRequest struct {
|
||||
UserRequirement string `json:"userRequirement"`
|
||||
}
|
||||
|
||||
|
||||
|
||||
// RequirementAnalysisResponse 需求分析响应
|
||||
type RequirementAnalysisResponse struct {
|
||||
AIPrompt string `json:"aiPrompt"` // 给AI的提示词
|
||||
|
||||
@@ -11,6 +11,10 @@ type PetAiAssistantConversations struct {
|
||||
UserId *int `json:"userId" form:"userId" gorm:"comment:用户ID;column:user_id;size:20;" binding:"required"` //用户ID
|
||||
MessageContent *string `json:"messageContent" form:"messageContent" gorm:"comment:消息内容;column:message_content;" binding:"required"` //消息内容
|
||||
Role *string `json:"role" form:"role" gorm:"comment:发送者角色:user-用户,assistant-AI助手;column:role;size:20;" binding:"required"` //发送者角色:user-用户,assistant-AI助手
|
||||
SessionId *string `json:"sessionId" form:"sessionId" gorm:"comment:会话ID;column:session_id;size:64;"` //会话ID
|
||||
IsSensitive *bool `json:"isSensitive" form:"isSensitive" gorm:"comment:是否包含敏感词;column:is_sensitive;default:false;"` //是否包含敏感词
|
||||
TokenCount *int `json:"tokenCount" form:"tokenCount" gorm:"comment:Token消耗数量;column:token_count;default:0;"` //Token消耗数量
|
||||
ResponseTime *int `json:"responseTime" form:"responseTime" gorm:"comment:响应时间(ms);column:response_time;default:0;"` //响应时间(ms)
|
||||
}
|
||||
|
||||
// TableName petAiAssistantConversations表 PetAiAssistantConversations自定义表名 pet_ai_assistant_conversations
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package request
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
|
||||
)
|
||||
|
||||
// ChatRequest 宠物助手聊天请求结构体
|
||||
type ChatRequest struct {
|
||||
Message string `json:"message" binding:"required" validate:"required,min=1,max=2000"` // 用户消息内容,必填,1-2000字符
|
||||
SessionId string `json:"sessionId" validate:"omitempty,uuid4"` // 会话ID,可选,UUID格式
|
||||
Stream bool `json:"stream"` // 是否流式响应,默认false
|
||||
Temperature float64 `json:"temperature" validate:"omitempty,min=0,max=2"` // 温度参数,控制随机性,范围0-2
|
||||
MaxTokens int `json:"maxTokens" validate:"omitempty,min=1,max=4000"` // 最大生成token数,范围1-4000
|
||||
Model string `json:"model" validate:"omitempty,min=1,max=100"` // 模型名称,可选
|
||||
TopP float64 `json:"topP" validate:"omitempty,min=0,max=1"` // 核采样参数,范围0-1
|
||||
}
|
||||
|
||||
// ChatHistoryRequest 获取聊天历史请求结构体
|
||||
type ChatHistoryRequest struct {
|
||||
SessionId string `json:"sessionId" form:"sessionId" validate:"omitempty,uuid4"` // 会话ID,可选,UUID格式
|
||||
request.PageInfo // 分页信息
|
||||
}
|
||||
|
||||
// ClearHistoryRequest 清空聊天历史请求结构体
|
||||
type ClearHistoryRequest struct {
|
||||
SessionId string `json:"sessionId" form:"sessionId" validate:"omitempty,uuid4"` // 会话ID,可选,不传则清空所有会话
|
||||
}
|
||||
|
||||
// SessionsRequest 获取会话列表请求结构体
|
||||
type SessionsRequest struct {
|
||||
request.PageInfo // 分页信息
|
||||
}
|
||||
|
||||
// StopGenerationRequest 停止生成请求结构体
|
||||
type StopGenerationRequest struct {
|
||||
RequestId string `json:"requestId" binding:"required" validate:"required,uuid4"` // 请求ID,必填,UUID格式
|
||||
}
|
||||
|
||||
// RegenerateRequest 重新生成回复请求结构体
|
||||
type RegenerateRequest struct {
|
||||
SessionId string `json:"sessionId" binding:"required" validate:"required,uuid4"` // 会话ID,必填
|
||||
MessageId uint `json:"messageId" binding:"required" validate:"required,min=1"` // 要重新生成的消息ID
|
||||
Temperature float64 `json:"temperature" validate:"omitempty,min=0,max=2"` // 温度参数
|
||||
MaxTokens int `json:"maxTokens" validate:"omitempty,min=1,max=4000"` // 最大token数
|
||||
}
|
||||
|
||||
// FeedbackRequest 用户反馈请求结构体
|
||||
type FeedbackRequest struct {
|
||||
MessageId uint `json:"messageId" binding:"required" validate:"required,min=1"` // 消息ID,必填
|
||||
FeedbackType string `json:"feedbackType" binding:"required" validate:"required,oneof=like dislike"` // 反馈类型:like/dislike
|
||||
Comment string `json:"comment" validate:"omitempty,max=500"` // 反馈评论,可选,最多500字符
|
||||
}
|
||||
|
||||
// ExportHistoryRequest 导出聊天历史请求结构体
|
||||
type ExportHistoryRequest struct {
|
||||
SessionId string `json:"sessionId" form:"sessionId" validate:"omitempty,uuid4"` // 会话ID,可选
|
||||
Format string `json:"format" form:"format" validate:"omitempty,oneof=json txt markdown"` // 导出格式:json/txt/markdown
|
||||
StartTime string `json:"startTime" form:"startTime" validate:"omitempty,datetime=2006-01-02"` // 开始时间,可选
|
||||
EndTime string `json:"endTime" form:"endTime" validate:"omitempty,datetime=2006-01-02"` // 结束时间,可选
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// ChatResponse 宠物助手聊天响应结构体
|
||||
type ChatResponse struct {
|
||||
ID uint `json:"id"` // 消息ID
|
||||
Message string `json:"message"` // AI回复消息内容
|
||||
SessionId string `json:"sessionId"` // 会话ID
|
||||
IsSensitive bool `json:"isSensitive"` // 是否包含敏感词
|
||||
TokenCount int `json:"tokenCount"` // Token消耗数量
|
||||
ResponseTime int64 `json:"responseTime"` // 响应时间(毫秒)
|
||||
RequestId string `json:"requestId,omitempty"` // 请求ID,用于流式响应管理
|
||||
Model string `json:"model,omitempty"` // 使用的模型名称
|
||||
CreatedAt time.Time `json:"createdAt"` // 创建时间
|
||||
FinishReason string `json:"finishReason,omitempty"` // 完成原因:stop/length/content_filter
|
||||
}
|
||||
|
||||
// StreamEvent 流式事件结构体
|
||||
type StreamEvent struct {
|
||||
Event string `json:"event"` // 事件类型: message, error, done, start
|
||||
Data interface{} `json:"data"` // 事件数据
|
||||
}
|
||||
|
||||
// StreamMessageData 流式消息数据结构体(简化版)
|
||||
type StreamMessageData struct {
|
||||
Delta string `json:"delta"` // 增量消息内容
|
||||
}
|
||||
|
||||
// StreamErrorData 流式错误数据结构体(简化版)
|
||||
type StreamErrorData struct {
|
||||
Error string `json:"error"` // 错误信息
|
||||
}
|
||||
|
||||
// StreamDoneData 流式完成数据结构体(简化版)
|
||||
type StreamDoneData struct {
|
||||
Message string `json:"message"` // 完整消息内容
|
||||
SessionId string `json:"sessionId"` // 会话ID
|
||||
}
|
||||
|
||||
// ChatHistoryItem 聊天历史项结构体(简化版)
|
||||
type ChatHistoryItem struct {
|
||||
ID uint `json:"id"` // 消息ID
|
||||
Role string `json:"role"` // 角色:user/assistant
|
||||
Message string `json:"message"` // 消息内容
|
||||
SessionId string `json:"sessionId"` // 会话ID
|
||||
CreatedAt time.Time `json:"createdAt"` // 创建时间
|
||||
}
|
||||
|
||||
// ChatHistoryResponse 聊天历史响应结构体
|
||||
type ChatHistoryResponse struct {
|
||||
List []ChatHistoryItem `json:"list"` // 对话记录列表
|
||||
Total int64 `json:"total"` // 总记录数
|
||||
Page int `json:"page"` // 当前页码
|
||||
PageSize int `json:"pageSize"` // 每页大小
|
||||
}
|
||||
|
||||
// SessionInfo 会话信息结构体
|
||||
type SessionInfo struct {
|
||||
SessionId string `json:"sessionId"` // 会话ID
|
||||
LastUpdated time.Time `json:"lastUpdated"` // 最后更新时间
|
||||
MessageCount int `json:"messageCount"` // 消息数量
|
||||
FirstMessage string `json:"firstMessage"` // 第一条消息内容(用作会话标题)
|
||||
CreatedAt time.Time `json:"createdAt"` // 创建时间
|
||||
}
|
||||
|
||||
// SessionsResponse 会话列表响应结构体
|
||||
type SessionsResponse struct {
|
||||
List []SessionInfo `json:"list"` // 会话列表
|
||||
Total int64 `json:"total"` // 总会话数
|
||||
Page int `json:"page"` // 当前页码
|
||||
PageSize int `json:"pageSize"` // 每页大小
|
||||
}
|
||||
|
||||
// TokenUsage Token使用情况结构体
|
||||
type TokenUsage struct {
|
||||
PromptTokens int `json:"promptTokens"` // 输入token数
|
||||
CompletionTokens int `json:"completionTokens"` // 输出token数
|
||||
TotalTokens int `json:"totalTokens"` // 总token数
|
||||
}
|
||||
|
||||
// ModelInfo 模型信息结构体
|
||||
type ModelInfo struct {
|
||||
Name string `json:"name"` // 模型名称
|
||||
Description string `json:"description"` // 模型描述
|
||||
MaxTokens int `json:"maxTokens"` // 最大token数
|
||||
Available bool `json:"available"` // 是否可用
|
||||
}
|
||||
|
||||
// ModelsResponse 可用模型列表响应结构体
|
||||
type ModelsResponse struct {
|
||||
Models []ModelInfo `json:"models"` // 模型列表
|
||||
}
|
||||
|
||||
// StatsResponse 统计信息响应结构体
|
||||
type StatsResponse struct {
|
||||
TotalSessions int64 `json:"totalSessions"` // 总会话数
|
||||
TotalMessages int64 `json:"totalMessages"` // 总消息数
|
||||
TotalTokens int64 `json:"totalTokens"` // 总token消耗
|
||||
LastChatTime *time.Time `json:"lastChatTime"` // 最后聊天时间
|
||||
AverageResponse float64 `json:"averageResponse"` // 平均响应时间(毫秒)
|
||||
SensitiveCount int64 `json:"sensitiveCount"` // 敏感词触发次数
|
||||
PopularQuestions []string `json:"popularQuestions"` // 热门问题
|
||||
}
|
||||
|
||||
// ExportResponse 导出响应结构体
|
||||
type ExportResponse struct {
|
||||
FileName string `json:"fileName"` // 文件名
|
||||
FileSize int64 `json:"fileSize"` // 文件大小(字节)
|
||||
DownloadUrl string `json:"downloadUrl"` // 下载链接
|
||||
ExpiresAt int64 `json:"expiresAt"` // 过期时间戳
|
||||
}
|
||||
|
||||
// HealthResponse 健康检查响应结构体
|
||||
type HealthResponse struct {
|
||||
Status string `json:"status"` // 服务状态:healthy/unhealthy
|
||||
LLMService string `json:"llmService"` // LLM服务状态
|
||||
SensitiveFilter string `json:"sensitiveFilter"` // 敏感词过滤状态
|
||||
Database string `json:"database"` // 数据库状态
|
||||
LastChecked time.Time `json:"lastChecked"` // 最后检查时间
|
||||
ResponseTime int64 `json:"responseTime"` // 响应时间(毫秒)
|
||||
ActiveSessions int `json:"activeSessions"` // 活跃会话数
|
||||
QueuedRequests int `json:"queuedRequests"` // 排队请求数
|
||||
}
|
||||
@@ -20,13 +20,15 @@ type ExportVersionRequest struct {
|
||||
Description string `json:"description"` // 版本描述
|
||||
MenuIds []uint `json:"menuIds"` // 选中的菜单ID列表
|
||||
ApiIds []uint `json:"apiIds"` // 选中的API ID列表
|
||||
DictIds []uint `json:"dictIds"` // 选中的字典ID列表
|
||||
}
|
||||
|
||||
// ImportVersionRequest 导入版本请求结构体
|
||||
type ImportVersionRequest struct {
|
||||
VersionInfo VersionInfo `json:"version" binding:"required"` // 版本信息
|
||||
ExportMenu []system.SysBaseMenu `json:"menus"` // 菜单数据,直接复用SysBaseMenu
|
||||
ExportApi []system.SysApi `json:"apis"` // API数据,直接复用SysApi
|
||||
VersionInfo VersionInfo `json:"version" binding:"required"` // 版本信息
|
||||
ExportMenu []system.SysBaseMenu `json:"menus"` // 菜单数据,直接复用SysBaseMenu
|
||||
ExportApi []system.SysApi `json:"apis"` // API数据,直接复用SysApi
|
||||
ExportDictionary []system.SysDictionary `json:"dictionaries"` // 字典数据,直接复用SysDictionary
|
||||
}
|
||||
|
||||
// VersionInfo 版本信息结构体
|
||||
|
||||
@@ -7,7 +7,8 @@ import (
|
||||
|
||||
// ExportVersionResponse 导出版本响应结构体
|
||||
type ExportVersionResponse struct {
|
||||
Version request.VersionInfo `json:"version"` // 版本信息
|
||||
Menus []system.SysBaseMenu `json:"menus"` // 菜单数据,直接复用SysBaseMenu
|
||||
Apis []system.SysApi `json:"apis"` // API数据,直接复用SysApi
|
||||
}
|
||||
Version request.VersionInfo `json:"version"` // 版本信息
|
||||
Menus []system.SysBaseMenu `json:"menus"` // 菜单数据,直接复用SysBaseMenu
|
||||
Apis []system.SysApi `json:"apis"` // API数据,直接复用SysApi
|
||||
Dictionaries []system.SysDictionary `json:"dictionaries"` // 字典数据,直接复用SysDictionary
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@ import (
|
||||
// 版本管理 结构体 SysVersion
|
||||
type SysVersion struct {
|
||||
global.GVA_MODEL
|
||||
VersionName *string `json:"versionName" form:"versionName" gorm:"comment:版本名称;column:version_name;size:255;" binding:"required"` //版本名称
|
||||
VersionCode *string `json:"versionCode" form:"versionCode" gorm:"comment:版本号;column:version_code;size:100;" binding:"required"` //版本号
|
||||
Description *string `json:"description" form:"description" gorm:"comment:版本描述;column:description;size:500;"` //版本描述
|
||||
VersionData *string `json:"versionData" form:"versionData" gorm:"comment:版本数据JSON;column:version_data;type:text;"` //版本数据
|
||||
VersionName *string `json:"versionName" form:"versionName" gorm:"comment:版本名称;column:version_name;size:255;" binding:"required"` //版本名称
|
||||
VersionCode *string `json:"versionCode" form:"versionCode" gorm:"comment:版本号;column:version_code;size:100;" binding:"required"` //版本号
|
||||
Description *string `json:"description" form:"description" gorm:"comment:版本描述;column:description;size:500;"` //版本描述
|
||||
VersionData *string `json:"versionData" form:"versionData" gorm:"comment:版本数据JSON;column:version_data;type:text;"` //版本数据
|
||||
}
|
||||
|
||||
// TableName 版本管理 SysVersion自定义表名 sys_versions
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
package api
|
||||
|
||||
type ApiGroup struct {
|
||||
LLMApi
|
||||
}
|
||||
|
||||
var ApiGroupApp = new(ApiGroup)
|
||||
@@ -1,177 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/volcengine/model/request"
|
||||
volcengineResponse "github.com/flipped-aurora/gin-vue-admin/server/plugin/volcengine/model/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/volcengine/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type LLMApi struct{}
|
||||
|
||||
// ChatCompletion
|
||||
// @Tags Volcengine
|
||||
// @Summary LLM聊天完成
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.ChatRequest true "聊天请求参数"
|
||||
// @Success 200 {object} response.Response{data=volcengineResponse.ChatResponse} "聊天响应"
|
||||
// @Router /volcengine/llm/chat [post]
|
||||
func (l *LLMApi) ChatCompletion(c *gin.Context) {
|
||||
var chatReq request.ChatRequest
|
||||
err := c.ShouldBindJSON(&chatReq)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
// 生成请求ID
|
||||
if chatReq.RequestID == "" {
|
||||
chatReq.RequestID = uuid.New().String()
|
||||
}
|
||||
|
||||
// 根据stream参数选择响应模式
|
||||
if chatReq.Stream {
|
||||
// 流式响应 (SSE)
|
||||
l.handleStreamResponse(c, chatReq)
|
||||
} else {
|
||||
// 普通JSON响应
|
||||
l.handleNormalResponse(c, chatReq)
|
||||
}
|
||||
}
|
||||
|
||||
// handleNormalResponse 处理普通JSON响应
|
||||
func (l *LLMApi) handleNormalResponse(c *gin.Context, chatReq request.ChatRequest) {
|
||||
resp, err := service.LLMServiceApp.ChatCompletion(chatReq)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("聊天完成失败!", zap.Error(err), zap.String("requestID", chatReq.RequestID))
|
||||
response.FailWithMessage("聊天完成失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
global.GVA_LOG.Info("聊天完成成功", zap.String("requestID", chatReq.RequestID))
|
||||
response.OkWithDetailed(resp, "聊天完成成功", c)
|
||||
}
|
||||
|
||||
// handleStreamResponse 处理流式响应 (SSE)
|
||||
func (l *LLMApi) handleStreamResponse(c *gin.Context, chatReq request.ChatRequest) {
|
||||
// 设置SSE响应头
|
||||
c.Header("Content-Type", "text/event-stream")
|
||||
c.Header("Cache-Control", "no-cache")
|
||||
c.Header("Connection", "keep-alive")
|
||||
c.Header("Access-Control-Allow-Origin", "*")
|
||||
c.Header("Access-Control-Allow-Headers", "Cache-Control")
|
||||
|
||||
// 创建事件通道
|
||||
eventChan := make(chan volcengineResponse.StreamEvent, 100)
|
||||
defer close(eventChan)
|
||||
|
||||
// 启动流式处理
|
||||
go func() {
|
||||
err := service.LLMServiceApp.StreamChatCompletion(chatReq, eventChan)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("流式聊天失败!", zap.Error(err), zap.String("requestID", chatReq.RequestID))
|
||||
eventChan <- volcengineResponse.StreamEvent{
|
||||
Event: "error",
|
||||
Data: volcengineResponse.ChatResponse{
|
||||
ID: chatReq.RequestID,
|
||||
Error: &volcengineResponse.APIError{
|
||||
Code: "stream_error",
|
||||
Message: err.Error(),
|
||||
Type: "internal_error",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// 发送流式数据
|
||||
c.Stream(func(w io.Writer) bool {
|
||||
select {
|
||||
case event, ok := <-eventChan:
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
switch event.Event {
|
||||
case "message":
|
||||
// 发送消息事件
|
||||
c.SSEvent("message", event.Data)
|
||||
case "error":
|
||||
// 发送错误事件
|
||||
c.SSEvent("error", event.Data)
|
||||
return false
|
||||
case "done":
|
||||
// 发送完成事件
|
||||
c.SSEvent("done", event.Data)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
case <-time.After(30 * time.Second):
|
||||
// 超时处理
|
||||
c.SSEvent("error", volcengineResponse.ChatResponse{
|
||||
ID: chatReq.RequestID,
|
||||
Error: &volcengineResponse.APIError{
|
||||
Code: "timeout",
|
||||
Message: "Stream timeout",
|
||||
Type: "timeout_error",
|
||||
},
|
||||
})
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// StopGeneration
|
||||
// @Tags Volcengine
|
||||
// @Summary 停止LLM生成
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.StopRequest true "停止请求参数"
|
||||
// @Success 200 {object} response.Response{data=volcengineResponse.StopResponse} "停止响应"
|
||||
// @Router /volcengine/llm/stop [post]
|
||||
func (l *LLMApi) StopGeneration(c *gin.Context) {
|
||||
var stopReq request.StopRequest
|
||||
err := c.ShouldBindJSON(&stopReq)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := service.LLMServiceApp.StopGeneration(stopReq)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("停止生成失败!", zap.Error(err), zap.String("requestID", stopReq.RequestID))
|
||||
response.FailWithMessage("停止生成失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
if resp.Success {
|
||||
global.GVA_LOG.Info("停止生成成功", zap.String("requestID", stopReq.RequestID))
|
||||
response.OkWithDetailed(resp, "停止生成成功", c)
|
||||
} else {
|
||||
global.GVA_LOG.Warn("停止生成失败", zap.String("requestID", stopReq.RequestID), zap.String("reason", resp.Message))
|
||||
response.FailWithDetailed(resp, resp.Message, c)
|
||||
}
|
||||
}
|
||||
|
||||
// GetActiveSessionsCount
|
||||
// @Tags Volcengine
|
||||
// @Summary 获取活跃会话数量
|
||||
// @Security ApiKeyAuth
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{data=int} "活跃会话数量"
|
||||
// @Router /volcengine/llm/sessions [get]
|
||||
func (l *LLMApi) GetActiveSessionsCount(c *gin.Context) {
|
||||
count := service.LLMServiceApp.GetActiveSessionsCount()
|
||||
response.OkWithDetailed(gin.H{"count": count}, fmt.Sprintf("当前活跃会话数量: %d", count), c)
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package volcengine
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/volcengine/router"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type volcenginePlugin struct{}
|
||||
|
||||
// CreateVolcenginePlug 创建volcengine插件实例
|
||||
func CreateVolcenginePlug() *volcenginePlugin {
|
||||
return &volcenginePlugin{}
|
||||
}
|
||||
|
||||
// Register 注册路由
|
||||
func (*volcenginePlugin) Register(group *gin.RouterGroup) {
|
||||
router.RouterGroupApp.InitLLMRouter(group)
|
||||
}
|
||||
|
||||
// RouterPath 返回注册路由路径
|
||||
func (*volcenginePlugin) RouterPath() string {
|
||||
return "volcengine"
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package request
|
||||
|
||||
// Message 聊天消息结构体
|
||||
type Message struct {
|
||||
Role string `json:"role" form:"role" binding:"required"` // 角色: system, user, assistant
|
||||
Content string `json:"content" form:"content" binding:"required"` // 消息内容
|
||||
}
|
||||
|
||||
// ChatRequest 聊天请求结构体
|
||||
type ChatRequest struct {
|
||||
Messages []Message `json:"messages" form:"messages" binding:"required"` // 对话消息列表
|
||||
Model string `json:"model" form:"model"` // 模型名称,如果为空则使用默认模型
|
||||
Stream bool `json:"stream" form:"stream"` // 是否流式响应,默认false
|
||||
Temperature float64 `json:"temperature" form:"temperature"` // 温度参数,控制随机性,范围0-1
|
||||
MaxTokens int `json:"max_tokens" form:"max_tokens"` // 最大生成token数
|
||||
TopP float64 `json:"top_p" form:"top_p"` // 核采样参数
|
||||
RequestID string `json:"request_id,omitempty" form:"request_id,omitempty"` // 请求ID,用于流式响应管理
|
||||
}
|
||||
|
||||
// StopRequest 停止生成请求结构体
|
||||
type StopRequest struct {
|
||||
RequestID string `json:"request_id" form:"request_id" binding:"required"` // 要停止的请求ID
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package response
|
||||
|
||||
import "time"
|
||||
|
||||
// Message 聊天消息结构体
|
||||
type Message struct {
|
||||
Role string `json:"role"` // 角色: system, user, assistant
|
||||
Content string `json:"content"` // 消息内容
|
||||
}
|
||||
|
||||
// Choice 响应选择结构体
|
||||
type Choice struct {
|
||||
Index int `json:"index"` // 选择索引
|
||||
Message Message `json:"message,omitempty"` // 完整响应消息(非流式)
|
||||
Delta Message `json:"delta,omitempty"` // 增量消息(流式)
|
||||
FinishReason string `json:"finish_reason,omitempty"` // 结束原因: stop, length, content_filter
|
||||
}
|
||||
|
||||
// Usage 使用量统计结构体
|
||||
type Usage struct {
|
||||
PromptTokens int `json:"prompt_tokens"` // 输入token数
|
||||
CompletionTokens int `json:"completion_tokens"` // 输出token数
|
||||
TotalTokens int `json:"total_tokens"` // 总token数
|
||||
}
|
||||
|
||||
// APIError API错误结构体
|
||||
type APIError struct {
|
||||
Code string `json:"code"` // 错误代码
|
||||
Message string `json:"message"` // 错误消息
|
||||
Type string `json:"type"` // 错误类型
|
||||
}
|
||||
|
||||
// ChatResponse 聊天响应结构体
|
||||
type ChatResponse struct {
|
||||
ID string `json:"id"` // 响应ID
|
||||
Object string `json:"object"` // 对象类型: chat.completion 或 chat.completion.chunk
|
||||
Created int64 `json:"created"` // 创建时间戳
|
||||
Model string `json:"model"` // 使用的模型
|
||||
Choices []Choice `json:"choices"` // 响应选择列表
|
||||
Usage *Usage `json:"usage,omitempty"` // 使用量统计(非流式响应)
|
||||
Error *APIError `json:"error,omitempty"` // 错误信息
|
||||
}
|
||||
|
||||
// StopResponse 停止生成响应结构体
|
||||
type StopResponse struct {
|
||||
Success bool `json:"success"` // 是否成功停止
|
||||
Message string `json:"message"` // 响应消息
|
||||
RequestID string `json:"request_id"` // 请求ID
|
||||
StoppedAt int64 `json:"stopped_at"` // 停止时间戳
|
||||
}
|
||||
|
||||
// StreamEvent 流式事件结构体
|
||||
type StreamEvent struct {
|
||||
Event string `json:"event"` // 事件类型: message, error, done
|
||||
Data ChatResponse `json:"data"` // 事件数据
|
||||
}
|
||||
|
||||
// ChatSession 聊天会话管理结构体(用于内部管理)
|
||||
type ChatSession struct {
|
||||
RequestID string `json:"request_id"` // 请求ID
|
||||
UserID uint `json:"user_id"` // 用户ID
|
||||
Model string `json:"model"` // 使用的模型
|
||||
StartTime time.Time `json:"start_time"` // 开始时间
|
||||
IsStreaming bool `json:"is_streaming"` // 是否流式响应
|
||||
Status string `json:"status"` // 状态: running, completed, stopped, error
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package router
|
||||
|
||||
type RouterGroup struct {
|
||||
LLMRouter
|
||||
}
|
||||
|
||||
var RouterGroupApp = new(RouterGroup)
|
||||
@@ -1,30 +0,0 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/middleware"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/volcengine/api"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type LLMRouter struct{}
|
||||
|
||||
// InitLLMRouter 初始化LLM路由
|
||||
func (l *LLMRouter) InitLLMRouter(Router *gin.RouterGroup) {
|
||||
// LLM相关路由组,使用操作记录中间件
|
||||
llmRouter := Router.Group("llm").Use(middleware.OperationRecord())
|
||||
// LLM查询路由组,不使用操作记录中间件(用于GET请求)
|
||||
llmRouterWithoutRecord := Router.Group("llm")
|
||||
|
||||
// 获取API实例
|
||||
llmApi := api.ApiGroupApp.LLMApi
|
||||
|
||||
{
|
||||
// 需要记录操作的路由(POST请求)
|
||||
llmRouter.POST("chat", llmApi.ChatCompletion) // LLM聊天完成
|
||||
llmRouter.POST("stop", llmApi.StopGeneration) // 停止LLM生成
|
||||
}
|
||||
{
|
||||
// 不需要记录操作的路由(GET请求)
|
||||
llmRouterWithoutRecord.GET("sessions", llmApi.GetActiveSessionsCount) // 获取活跃会话数量
|
||||
}
|
||||
}
|
||||
@@ -1,316 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/volcengine/model/request"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/volcengine/model/response"
|
||||
"github.com/google/uuid"
|
||||
"github.com/volcengine/volcengine-go-sdk/service/arkruntime"
|
||||
"github.com/volcengine/volcengine-go-sdk/service/arkruntime/model"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type LLMService struct {
|
||||
client *arkruntime.Client
|
||||
activeSessions sync.Map // requestID -> context.CancelFunc
|
||||
}
|
||||
|
||||
var LLMServiceApp = new(LLMService)
|
||||
|
||||
// InitVolcengineClient 初始化火山引擎客户端
|
||||
func (l *LLMService) InitVolcengineClient() error {
|
||||
config := global.GVA_CONFIG.Volcengine
|
||||
|
||||
// 验证配置
|
||||
if config.AccessKey == "" || config.SecretKey == "" {
|
||||
return fmt.Errorf("volcengine access key or secret key is empty")
|
||||
}
|
||||
|
||||
// 创建ARK Runtime客户端
|
||||
l.client = arkruntime.NewClientWithAkSk(config.AccessKey, config.SecretKey)
|
||||
|
||||
global.GVA_LOG.Info("Volcengine client initialized successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChatCompletion 非流式聊天完成
|
||||
func (l *LLMService) ChatCompletion(req request.ChatRequest) (*response.ChatResponse, error) {
|
||||
if l.client == nil {
|
||||
if err := l.InitVolcengineClient(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 生成请求ID
|
||||
if req.RequestID == "" {
|
||||
req.RequestID = uuid.New().String()
|
||||
}
|
||||
|
||||
// 转换消息格式
|
||||
messages := make([]*model.ChatCompletionMessage, len(req.Messages))
|
||||
for i, msg := range req.Messages {
|
||||
messages[i] = &model.ChatCompletionMessage{
|
||||
Role: msg.Role,
|
||||
Content: &model.ChatCompletionMessageContent{
|
||||
StringValue: &msg.Content,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 设置模型
|
||||
modelName := req.Model
|
||||
if modelName == "" {
|
||||
modelName = global.GVA_CONFIG.Volcengine.DefaultModel
|
||||
}
|
||||
|
||||
// 创建请求
|
||||
chatReq := &model.ChatCompletionRequest{
|
||||
Model: modelName,
|
||||
Messages: messages,
|
||||
}
|
||||
|
||||
// 设置可选参数
|
||||
if req.Temperature > 0 {
|
||||
chatReq.Temperature = float32(req.Temperature)
|
||||
}
|
||||
if req.MaxTokens > 0 {
|
||||
chatReq.MaxTokens = req.MaxTokens
|
||||
}
|
||||
if req.TopP > 0 {
|
||||
chatReq.TopP = float32(req.TopP)
|
||||
}
|
||||
|
||||
// 创建上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 存储取消函数
|
||||
l.activeSessions.Store(req.RequestID, cancel)
|
||||
defer l.activeSessions.Delete(req.RequestID)
|
||||
|
||||
// 调用API
|
||||
resp, err := l.client.CreateChatCompletion(ctx, chatReq)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("Chat completion failed", zap.Error(err), zap.String("requestID", req.RequestID))
|
||||
return nil, fmt.Errorf("chat completion failed: %w", err)
|
||||
}
|
||||
|
||||
// 转换响应格式
|
||||
chatResp := &response.ChatResponse{
|
||||
ID: req.RequestID,
|
||||
Object: "chat.completion",
|
||||
Created: time.Now().Unix(),
|
||||
Model: modelName,
|
||||
Choices: make([]response.Choice, len(resp.Choices)),
|
||||
}
|
||||
|
||||
for i, choice := range resp.Choices {
|
||||
messageContent := ""
|
||||
if choice.Message.Content != nil && choice.Message.Content.StringValue != nil {
|
||||
messageContent = *choice.Message.Content.StringValue
|
||||
}
|
||||
|
||||
chatResp.Choices[i] = response.Choice{
|
||||
Index: i,
|
||||
Message: response.Message{
|
||||
Role: choice.Message.Role,
|
||||
Content: messageContent,
|
||||
},
|
||||
FinishReason: string(choice.FinishReason),
|
||||
}
|
||||
}
|
||||
|
||||
// 设置使用量统计
|
||||
chatResp.Usage = &response.Usage{
|
||||
PromptTokens: resp.Usage.PromptTokens,
|
||||
CompletionTokens: resp.Usage.CompletionTokens,
|
||||
TotalTokens: resp.Usage.TotalTokens,
|
||||
}
|
||||
|
||||
global.GVA_LOG.Info("Chat completion successful", zap.String("requestID", req.RequestID))
|
||||
return chatResp, nil
|
||||
}
|
||||
|
||||
// StreamChatCompletion 流式聊天完成
|
||||
func (l *LLMService) StreamChatCompletion(req request.ChatRequest, eventChan chan<- response.StreamEvent) error {
|
||||
if l.client == nil {
|
||||
if err := l.InitVolcengineClient(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 生成请求ID
|
||||
if req.RequestID == "" {
|
||||
req.RequestID = uuid.New().String()
|
||||
}
|
||||
|
||||
// 转换消息格式
|
||||
messages := make([]*model.ChatCompletionMessage, len(req.Messages))
|
||||
for i, msg := range req.Messages {
|
||||
messages[i] = &model.ChatCompletionMessage{
|
||||
Role: msg.Role,
|
||||
Content: &model.ChatCompletionMessageContent{
|
||||
StringValue: &msg.Content,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 设置模型
|
||||
modelName := req.Model
|
||||
if modelName == "" {
|
||||
modelName = global.GVA_CONFIG.Volcengine.DefaultModel
|
||||
}
|
||||
|
||||
// 创建流式请求
|
||||
chatReq := &model.ChatCompletionRequest{
|
||||
Model: modelName,
|
||||
Messages: messages,
|
||||
Stream: true,
|
||||
}
|
||||
|
||||
// 设置可选参数
|
||||
if req.Temperature > 0 {
|
||||
chatReq.Temperature = float32(req.Temperature)
|
||||
}
|
||||
if req.MaxTokens > 0 {
|
||||
chatReq.MaxTokens = req.MaxTokens
|
||||
}
|
||||
if req.TopP > 0 {
|
||||
chatReq.TopP = float32(req.TopP)
|
||||
}
|
||||
|
||||
// 创建上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 300*time.Second) // 流式响应需要更长时间
|
||||
defer cancel()
|
||||
|
||||
// 存储取消函数
|
||||
l.activeSessions.Store(req.RequestID, cancel)
|
||||
defer l.activeSessions.Delete(req.RequestID)
|
||||
|
||||
// 调用流式API
|
||||
stream_resp, err := l.client.CreateChatCompletionStream(ctx, chatReq)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("Stream chat completion failed", zap.Error(err), zap.String("requestID", req.RequestID))
|
||||
return fmt.Errorf("stream chat completion failed: %w", err)
|
||||
}
|
||||
defer stream_resp.Close()
|
||||
|
||||
// 处理流式响应
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// 上下文取消
|
||||
eventChan <- response.StreamEvent{
|
||||
Event: "error",
|
||||
Data: response.ChatResponse{
|
||||
ID: req.RequestID,
|
||||
Error: &response.APIError{
|
||||
Code: "context_cancelled",
|
||||
Message: "Request was cancelled",
|
||||
Type: "request_cancelled",
|
||||
},
|
||||
},
|
||||
}
|
||||
return ctx.Err()
|
||||
default:
|
||||
// 接收流式数据
|
||||
recv, err := stream_resp.Recv()
|
||||
if err != nil {
|
||||
if err.Error() == "EOF" {
|
||||
// 流结束
|
||||
eventChan <- response.StreamEvent{
|
||||
Event: "done",
|
||||
Data: response.ChatResponse{
|
||||
ID: req.RequestID,
|
||||
Object: "chat.completion.chunk",
|
||||
},
|
||||
}
|
||||
global.GVA_LOG.Info("Stream chat completion finished", zap.String("requestID", req.RequestID))
|
||||
return nil
|
||||
}
|
||||
global.GVA_LOG.Error("Stream receive error", zap.Error(err), zap.String("requestID", req.RequestID))
|
||||
eventChan <- response.StreamEvent{
|
||||
Event: "error",
|
||||
Data: response.ChatResponse{
|
||||
ID: req.RequestID,
|
||||
Error: &response.APIError{
|
||||
Code: "stream_error",
|
||||
Message: err.Error(),
|
||||
Type: "stream_error",
|
||||
},
|
||||
},
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// 转换并发送响应
|
||||
chatResp := response.ChatResponse{
|
||||
ID: req.RequestID,
|
||||
Object: "chat.completion.chunk",
|
||||
Created: time.Now().Unix(),
|
||||
Model: modelName,
|
||||
Choices: make([]response.Choice, len(recv.Choices)),
|
||||
}
|
||||
|
||||
for i, choice := range recv.Choices {
|
||||
chatResp.Choices[i] = response.Choice{
|
||||
Index: i,
|
||||
Delta: response.Message{
|
||||
Role: choice.Delta.Role,
|
||||
Content: choice.Delta.Content,
|
||||
},
|
||||
}
|
||||
if choice.FinishReason != "" {
|
||||
chatResp.Choices[i].FinishReason = string(choice.FinishReason)
|
||||
}
|
||||
}
|
||||
|
||||
eventChan <- response.StreamEvent{
|
||||
Event: "message",
|
||||
Data: chatResp,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// StopGeneration 停止生成
|
||||
func (l *LLMService) StopGeneration(req request.StopRequest) (*response.StopResponse, error) {
|
||||
// 查找并取消对应的请求
|
||||
if cancelFunc, ok := l.activeSessions.Load(req.RequestID); ok {
|
||||
if cancel, ok := cancelFunc.(context.CancelFunc); ok {
|
||||
cancel()
|
||||
l.activeSessions.Delete(req.RequestID)
|
||||
|
||||
global.GVA_LOG.Info("Request stopped successfully", zap.String("requestID", req.RequestID))
|
||||
return &response.StopResponse{
|
||||
Success: true,
|
||||
Message: "Request stopped successfully",
|
||||
RequestID: req.RequestID,
|
||||
StoppedAt: time.Now().Unix(),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
global.GVA_LOG.Warn("Request not found or already completed", zap.String("requestID", req.RequestID))
|
||||
return &response.StopResponse{
|
||||
Success: false,
|
||||
Message: "Request not found or already completed",
|
||||
RequestID: req.RequestID,
|
||||
StoppedAt: time.Now().Unix(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetActiveSessionsCount 获取活跃会话数量
|
||||
func (l *LLMService) GetActiveSessionsCount() int {
|
||||
count := 0
|
||||
l.activeSessions.Range(func(key, value interface{}) bool {
|
||||
count++
|
||||
return true
|
||||
})
|
||||
return count
|
||||
}
|
||||
@@ -110,7 +110,7 @@ getDataSourceFunc()
|
||||
|
||||
<el-date-picker
|
||||
v-model="searchInfo.createdAtRange"
|
||||
class="w-[380px]"
|
||||
class="!w-380px"
|
||||
type="datetimerange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
@@ -161,7 +161,7 @@ getDataSourceFunc()
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
{{ if .GvaModel }}
|
||||
<el-table-column sortable align="left" label="日期" prop="CreatedAt" {{- if .IsTree }} min-{{- end -}}width="180">
|
||||
<el-table-column sortable align="left" label="日期" prop="CreatedAt" {{ if .IsTree -}} min-{{- end -}}width="180">
|
||||
<template #default="scope">{{ "{{ formatDate(scope.row.CreatedAt) }}" }}</template>
|
||||
</el-table-column>
|
||||
{{ end }}
|
||||
@@ -393,8 +393,8 @@ const searchInfo = ref({})
|
||||
// 排序
|
||||
const sortChange = ({ prop, order }) => {
|
||||
const sortMap = {
|
||||
CreatedAt:"CreatedAt",
|
||||
ID:"ID",
|
||||
CreatedAt:"created_at",
|
||||
ID:"id",
|
||||
{{- range .Fields}}
|
||||
{{- if .Table}}
|
||||
{{- if and .Sort}}
|
||||
|
||||
@@ -108,7 +108,7 @@ getDataSourceFunc()
|
||||
</template>
|
||||
<el-date-picker
|
||||
v-model="searchInfo.createdAtRange"
|
||||
class="w-[380px]"
|
||||
class="!w-380px"
|
||||
type="datetimerange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
@@ -158,7 +158,7 @@ getDataSourceFunc()
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
{{ if .GvaModel }}
|
||||
<el-table-column sortable align="left" label="日期" prop="CreatedAt" {{- if .IsTree }} min-{{- end -}}width="180">
|
||||
<el-table-column sortable align="left" label="日期" prop="CreatedAt" {{ if .IsTree -}} min-{{- end -}}width="180">
|
||||
<template #default="scope">{{ "{{ formatDate(scope.row.CreatedAt) }}" }}</template>
|
||||
</el-table-column>
|
||||
{{ end }}
|
||||
@@ -388,8 +388,8 @@ const searchInfo = ref({})
|
||||
// 排序
|
||||
const sortChange = ({ prop, order }) => {
|
||||
const sortMap = {
|
||||
CreatedAt:"CreatedAt",
|
||||
ID:"ID",
|
||||
CreatedAt:"created_at",
|
||||
ID:"id",
|
||||
{{- range .Fields}}
|
||||
{{- if .Table}}
|
||||
{{- if and .Sort}}
|
||||
|
||||
@@ -13,6 +13,7 @@ type RouterGroup struct {
|
||||
PetFamilyPetsRouter
|
||||
PetPetsRouter
|
||||
PetRecordsRouter
|
||||
PetAssistantRouter
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -26,4 +27,5 @@ var (
|
||||
petFamilyPetsApi = api.ApiGroupApp.PetApiGroup.PetFamilyPetsApi
|
||||
petPetsApi = api.ApiGroupApp.PetApiGroup.PetPetsApi
|
||||
petRecordsApi = api.ApiGroupApp.PetApiGroup.PetRecordsApi
|
||||
petUserApiGroup = api.ApiGroupApp.PetApiGroup.PetUserApiGroup
|
||||
)
|
||||
|
||||
@@ -22,7 +22,5 @@ func (s *PetAdoptionApplicationsRouter) InitPetAdoptionApplicationsRouter(Router
|
||||
petAdoptionApplicationsRouterWithoutRecord.GET("findPetAdoptionApplications", petAdoptionApplicationsApi.FindPetAdoptionApplications) // 根据ID获取petAdoptionApplications表
|
||||
petAdoptionApplicationsRouterWithoutRecord.GET("getPetAdoptionApplicationsList", petAdoptionApplicationsApi.GetPetAdoptionApplicationsList) // 获取petAdoptionApplications表列表
|
||||
}
|
||||
{
|
||||
petAdoptionApplicationsRouterWithoutAuth.GET("getPetAdoptionApplicationsPublic", petAdoptionApplicationsApi.GetPetAdoptionApplicationsPublic) // petAdoptionApplications表开放接口
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,7 +22,5 @@ func (s *PetAdoptionPostsRouter) InitPetAdoptionPostsRouter(Router *gin.RouterGr
|
||||
petAdoptionPostsRouterWithoutRecord.GET("findPetAdoptionPosts", petAdoptionPostsApi.FindPetAdoptionPosts) // 根据ID获取petAdoptionPosts表
|
||||
petAdoptionPostsRouterWithoutRecord.GET("getPetAdoptionPostsList", petAdoptionPostsApi.GetPetAdoptionPostsList) // 获取petAdoptionPosts表列表
|
||||
}
|
||||
{
|
||||
petAdoptionPostsRouterWithoutAuth.GET("getPetAdoptionPostsPublic", petAdoptionPostsApi.GetPetAdoptionPostsPublic) // petAdoptionPosts表开放接口
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,7 +22,5 @@ func (s *PetAiAssistantConversationsRouter) InitPetAiAssistantConversationsRoute
|
||||
petAiAssistantConversationsRouterWithoutRecord.GET("findPetAiAssistantConversations", petAiAssistantConversationsApi.FindPetAiAssistantConversations) // 根据ID获取petAiAssistantConversations表
|
||||
petAiAssistantConversationsRouterWithoutRecord.GET("getPetAiAssistantConversationsList", petAiAssistantConversationsApi.GetPetAiAssistantConversationsList) // 获取petAiAssistantConversations表列表
|
||||
}
|
||||
{
|
||||
petAiAssistantConversationsRouterWithoutAuth.GET("getPetAiAssistantConversationsPublic", petAiAssistantConversationsApi.GetPetAiAssistantConversationsPublic) // petAiAssistantConversations表开放接口
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,7 +22,5 @@ func (s *PetAiConversationsRouter) InitPetAiConversationsRouter(Router *gin.Rout
|
||||
petAiConversationsRouterWithoutRecord.GET("findPetAiConversations", petAiConversationsApi.FindPetAiConversations) // 根据ID获取petAiConversations表
|
||||
petAiConversationsRouterWithoutRecord.GET("getPetAiConversationsList", petAiConversationsApi.GetPetAiConversationsList) // 获取petAiConversations表列表
|
||||
}
|
||||
{
|
||||
petAiConversationsRouterWithoutAuth.GET("getPetAiConversationsPublic", petAiConversationsApi.GetPetAiConversationsPublic) // petAiConversations表开放接口
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package pet
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type PetAssistantRouter struct{}
|
||||
|
||||
// InitPetAssistantRouter 初始化宠物助手路由信息
|
||||
func (p *PetAssistantRouter) InitPetAssistantRouter(UserRouter *gin.RouterGroup, PublicRouter *gin.RouterGroup) {
|
||||
// 宠物助手路由组,UserRouter已经应用了UserJWTAuth中间件
|
||||
petAssistantRouter := UserRouter.Group("pet/user/assistant")
|
||||
|
||||
// 获取宠物助手API实例
|
||||
petAssistantApi := petUserApiGroup.PetAssistantApi
|
||||
|
||||
{
|
||||
// 宠物助手问答相关路由
|
||||
petAssistantRouter.POST("ask", petAssistantApi.AskPetAssistant) // 宠物助手提问(非流式)
|
||||
petAssistantRouter.POST("stream-ask", petAssistantApi.StreamAskPetAssistant) // 宠物助手流式提问
|
||||
petAssistantRouter.GET("history", petAssistantApi.GetAssistantHistory) // 获取宠物助手对话历史
|
||||
petAssistantRouter.DELETE("clear-history", petAssistantApi.ClearAssistantHistory) // 清空宠物助手对话历史
|
||||
petAssistantRouter.GET("sessions", petAssistantApi.GetAssistantSessions) // 获取宠物助手会话列表
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,5 @@ func (s *PetFamiliesRouter) InitPetFamiliesRouter(Router *gin.RouterGroup, Publi
|
||||
petFamiliesRouterWithoutRecord.GET("findPetFamilies", petFamiliesApi.FindPetFamilies) // 根据ID获取petFamilies表
|
||||
petFamiliesRouterWithoutRecord.GET("getPetFamiliesList", petFamiliesApi.GetPetFamiliesList) // 获取petFamilies表列表
|
||||
}
|
||||
{
|
||||
petFamiliesRouterWithoutAuth.GET("getPetFamiliesPublic", petFamiliesApi.GetPetFamiliesPublic) // petFamilies表开放接口
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,7 +22,5 @@ func (s *PetFamilyInvitationsRouter) InitPetFamilyInvitationsRouter(Router *gin.
|
||||
petFamilyInvitationsRouterWithoutRecord.GET("findPetFamilyInvitations", petFamilyInvitationsApi.FindPetFamilyInvitations) // 根据ID获取petFamilyInvitations表
|
||||
petFamilyInvitationsRouterWithoutRecord.GET("getPetFamilyInvitationsList", petFamilyInvitationsApi.GetPetFamilyInvitationsList) // 获取petFamilyInvitations表列表
|
||||
}
|
||||
{
|
||||
petFamilyInvitationsRouterWithoutAuth.GET("getPetFamilyInvitationsPublic", petFamilyInvitationsApi.GetPetFamilyInvitationsPublic) // petFamilyInvitations表开放接口
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,7 +22,5 @@ func (s *PetFamilyMembersRouter) InitPetFamilyMembersRouter(Router *gin.RouterGr
|
||||
petFamilyMembersRouterWithoutRecord.GET("findPetFamilyMembers", petFamilyMembersApi.FindPetFamilyMembers) // 根据ID获取petFamilyMembers表
|
||||
petFamilyMembersRouterWithoutRecord.GET("getPetFamilyMembersList", petFamilyMembersApi.GetPetFamilyMembersList) // 获取petFamilyMembers表列表
|
||||
}
|
||||
{
|
||||
petFamilyMembersRouterWithoutAuth.GET("getPetFamilyMembersPublic", petFamilyMembersApi.GetPetFamilyMembersPublic) // petFamilyMembers表开放接口
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,7 +22,5 @@ func (s *PetFamilyPetsRouter) InitPetFamilyPetsRouter(Router *gin.RouterGroup, P
|
||||
petFamilyPetsRouterWithoutRecord.GET("findPetFamilyPets", petFamilyPetsApi.FindPetFamilyPets) // 根据ID获取petFamilyPets表
|
||||
petFamilyPetsRouterWithoutRecord.GET("getPetFamilyPetsList", petFamilyPetsApi.GetPetFamilyPetsList) // 获取petFamilyPets表列表
|
||||
}
|
||||
{
|
||||
petFamilyPetsRouterWithoutAuth.GET("getPetFamilyPetsPublic", petFamilyPetsApi.GetPetFamilyPetsPublic) // petFamilyPets表开放接口
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,7 +22,5 @@ func (s *PetPetsRouter) InitPetPetsRouter(Router *gin.RouterGroup, PublicRouter
|
||||
petPetsRouterWithoutRecord.GET("findPetPets", petPetsApi.FindPetPets) // 根据ID获取petPets表
|
||||
petPetsRouterWithoutRecord.GET("getPetPetsList", petPetsApi.GetPetPetsList) // 获取petPets表列表
|
||||
}
|
||||
{
|
||||
petPetsRouterWithoutAuth.GET("getPetPetsPublic", petPetsApi.GetPetPetsPublic) // petPets表开放接口
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,7 +22,5 @@ func (s *PetRecordsRouter) InitPetRecordsRouter(Router *gin.RouterGroup, PublicR
|
||||
petRecordsRouterWithoutRecord.GET("findPetRecords", petRecordsApi.FindPetRecords) // 根据ID获取petRecords表
|
||||
petRecordsRouterWithoutRecord.GET("getPetRecordsList", petRecordsApi.GetPetRecordsList) // 获取petRecords表列表
|
||||
}
|
||||
{
|
||||
petRecordsRouterWithoutAuth.GET("getPetRecordsPublic", petRecordsApi.GetPetRecordsPublic) // petRecords表开放接口
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ func (s *UserRouter) InitUserRouter(Router *gin.RouterGroup) {
|
||||
userRouter.PUT("setUserInfo", baseApi.SetUserInfo) // 设置用户信息
|
||||
userRouter.PUT("setSelfInfo", baseApi.SetSelfInfo) // 设置自身信息
|
||||
userRouter.POST("setUserAuthorities", baseApi.SetUserAuthorities) // 设置用户权限组
|
||||
userRouter.POST("resetPassword", baseApi.ResetPassword) // 设置用户权限组
|
||||
userRouter.POST("resetPassword", baseApi.ResetPassword) // 重置用户密码
|
||||
userRouter.PUT("setSelfSetting", baseApi.SetSelfSetting) // 用户界面配置
|
||||
}
|
||||
{
|
||||
|
||||
@@ -11,4 +11,5 @@ type ServiceGroup struct {
|
||||
PetFamilyPetsService
|
||||
PetPetsService
|
||||
PetRecordsService
|
||||
PetChatService
|
||||
}
|
||||
|
||||
@@ -69,7 +69,3 @@ func (petAdoptionApplicationsService *PetAdoptionApplicationsService) GetPetAdop
|
||||
err = db.Find(&petAdoptionApplicationss).Error
|
||||
return petAdoptionApplicationss, total, err
|
||||
}
|
||||
func (petAdoptionApplicationsService *PetAdoptionApplicationsService) GetPetAdoptionApplicationsPublic(ctx context.Context) {
|
||||
// 此方法为获取数据源定义的数据
|
||||
// 请自行实现
|
||||
}
|
||||
|
||||
@@ -69,7 +69,3 @@ func (petAdoptionPostsService *PetAdoptionPostsService) GetPetAdoptionPostsInfoL
|
||||
err = db.Find(&petAdoptionPostss).Error
|
||||
return petAdoptionPostss, total, err
|
||||
}
|
||||
func (petAdoptionPostsService *PetAdoptionPostsService) GetPetAdoptionPostsPublic(ctx context.Context) {
|
||||
// 此方法为获取数据源定义的数据
|
||||
// 请自行实现
|
||||
}
|
||||
|
||||
@@ -69,7 +69,3 @@ func (petAiAssistantConversationsService *PetAiAssistantConversationsService) Ge
|
||||
err = db.Find(&petAiAssistantConversationss).Error
|
||||
return petAiAssistantConversationss, total, err
|
||||
}
|
||||
func (petAiAssistantConversationsService *PetAiAssistantConversationsService) GetPetAiAssistantConversationsPublic(ctx context.Context) {
|
||||
// 此方法为获取数据源定义的数据
|
||||
// 请自行实现
|
||||
}
|
||||
|
||||
@@ -69,7 +69,3 @@ func (petAiConversationsService *PetAiConversationsService) GetPetAiConversation
|
||||
err = db.Find(&petAiConversationss).Error
|
||||
return petAiConversationss, total, err
|
||||
}
|
||||
func (petAiConversationsService *PetAiConversationsService) GetPetAiConversationsPublic(ctx context.Context) {
|
||||
// 此方法为获取数据源定义的数据
|
||||
// 请自行实现
|
||||
}
|
||||
|
||||
@@ -0,0 +1,451 @@
|
||||
package pet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/pet"
|
||||
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/utils"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// 使用新定义的模型结构
|
||||
type ChatRequest = petRequest.ChatRequest
|
||||
type ChatResponse = petResponse.ChatResponse
|
||||
type StreamEvent = petResponse.StreamEvent
|
||||
|
||||
// PetChatService 宠物聊天服务
|
||||
type PetChatService struct{}
|
||||
|
||||
// SendMessage 发送消息(非流式)
|
||||
func (p *PetChatService) SendMessage(ctx context.Context, userId uint, req ChatRequest) (*ChatResponse, error) {
|
||||
startTime := time.Now()
|
||||
|
||||
// 1. 敏感词检测
|
||||
sensitiveUtil := utils.GetSensitiveWordUtil()
|
||||
filtered, hasSensitive, err := sensitiveUtil.FilterText(req.Message)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("敏感词检测失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("敏感词检测失败: %v", err)
|
||||
}
|
||||
|
||||
// 如果包含敏感词,记录并返回提示
|
||||
if hasSensitive {
|
||||
global.GVA_LOG.Warn("用户消息包含敏感词", zap.Uint("userId", userId), zap.String("original", req.Message), zap.String("filtered", filtered))
|
||||
|
||||
// 保存用户消息(包含敏感词标记)
|
||||
if err := p.saveUserMessage(ctx, userId, req.SessionId, req.Message, true); err != nil {
|
||||
global.GVA_LOG.Error("保存用户消息失败", zap.Error(err))
|
||||
}
|
||||
|
||||
return &ChatResponse{
|
||||
Message: "您的消息包含不当内容,请重新输入。",
|
||||
SessionId: req.SessionId,
|
||||
IsSensitive: true,
|
||||
TokenCount: 0,
|
||||
ResponseTime: time.Since(startTime).Milliseconds(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 2. 生成会话ID(如果没有提供)
|
||||
sessionId := req.SessionId
|
||||
if sessionId == "" {
|
||||
sessionId = uuid.New().String()
|
||||
}
|
||||
|
||||
// 3. 获取对话历史构建上下文
|
||||
history, err := p.GetChatHistory(ctx, userId, sessionId, 10) // 获取最近10条消息
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取对话历史失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取对话历史失败: %v", err)
|
||||
}
|
||||
|
||||
// 4. 构建LLM请求
|
||||
messages := p.buildMessages(history, req.Message)
|
||||
llmReq := utils.LLMRequest{
|
||||
Messages: messages,
|
||||
Model: req.Model,
|
||||
Stream: false,
|
||||
Temperature: req.Temperature,
|
||||
MaxTokens: req.MaxTokens,
|
||||
RequestID: uuid.New().String(),
|
||||
}
|
||||
|
||||
// 5. 调用LLM服务
|
||||
llmUtil := utils.GetVolcengineLLMUtil()
|
||||
llmResp, err := llmUtil.ChatCompletion(llmReq)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("LLM调用失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("LLM调用失败: %v", err)
|
||||
}
|
||||
|
||||
// 6. 提取AI回复
|
||||
var aiMessage string
|
||||
if len(llmResp.Choices) > 0 {
|
||||
aiMessage = llmResp.Choices[0].Message.Content
|
||||
}
|
||||
|
||||
// 7. 对AI回复进行敏感词检测
|
||||
aiFiltered, aiHasSensitive, err := sensitiveUtil.FilterText(aiMessage)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("AI回复敏感词检测失败", zap.Error(err))
|
||||
aiFiltered = aiMessage // 如果检测失败,使用原始消息
|
||||
}
|
||||
|
||||
if aiHasSensitive {
|
||||
global.GVA_LOG.Warn("AI回复包含敏感词", zap.String("original", aiMessage), zap.String("filtered", aiFiltered))
|
||||
aiMessage = aiFiltered
|
||||
}
|
||||
|
||||
// 8. 保存对话记录
|
||||
responseTime := time.Since(startTime).Milliseconds()
|
||||
tokenCount := 0
|
||||
if llmResp.Usage != nil {
|
||||
tokenCount = llmResp.Usage.TotalTokens
|
||||
}
|
||||
|
||||
// 保存用户消息
|
||||
if err := p.saveUserMessage(ctx, userId, sessionId, req.Message, false); err != nil {
|
||||
global.GVA_LOG.Error("保存用户消息失败", zap.Error(err))
|
||||
}
|
||||
|
||||
// 保存AI回复
|
||||
if err := p.saveAssistantMessage(ctx, userId, sessionId, aiMessage, aiHasSensitive, tokenCount, responseTime); err != nil {
|
||||
global.GVA_LOG.Error("保存AI回复失败", zap.Error(err))
|
||||
}
|
||||
|
||||
return &ChatResponse{
|
||||
Message: aiMessage,
|
||||
SessionId: sessionId,
|
||||
IsSensitive: aiHasSensitive,
|
||||
TokenCount: tokenCount,
|
||||
ResponseTime: responseTime,
|
||||
RequestId: llmReq.RequestID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// StreamChat 流式聊天
|
||||
func (p *PetChatService) StreamChat(ctx context.Context, userId uint, req ChatRequest, eventChan chan<- StreamEvent) error {
|
||||
startTime := time.Now()
|
||||
|
||||
// 1. 敏感词检测
|
||||
sensitiveUtil := utils.GetSensitiveWordUtil()
|
||||
filtered, hasSensitive, err := sensitiveUtil.FilterText(req.Message)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("敏感词检测失败", zap.Error(err))
|
||||
eventChan <- StreamEvent{
|
||||
Event: "error",
|
||||
Data: petResponse.StreamErrorData{
|
||||
Error: "敏感词检测失败",
|
||||
},
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// 如果包含敏感词,返回提示并结束
|
||||
if hasSensitive {
|
||||
global.GVA_LOG.Warn("用户消息包含敏感词", zap.Uint("userId", userId), zap.String("original", req.Message), zap.String("filtered", filtered))
|
||||
|
||||
// 保存用户消息(包含敏感词标记)
|
||||
if err := p.saveUserMessage(ctx, userId, req.SessionId, req.Message, true); err != nil {
|
||||
global.GVA_LOG.Error("保存用户消息失败", zap.Error(err))
|
||||
}
|
||||
|
||||
eventChan <- StreamEvent{
|
||||
Event: "message",
|
||||
Data: petResponse.StreamMessageData{
|
||||
Delta: "您的消息包含不当内容,请重新输入。",
|
||||
},
|
||||
}
|
||||
|
||||
eventChan <- StreamEvent{
|
||||
Event: "done",
|
||||
Data: petResponse.StreamDoneData{
|
||||
Message: "您的消息包含不当内容,请重新输入。",
|
||||
SessionId: req.SessionId,
|
||||
},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 2. 生成会话ID(如果没有提供)
|
||||
sessionId := req.SessionId
|
||||
if sessionId == "" {
|
||||
sessionId = uuid.New().String()
|
||||
}
|
||||
|
||||
// 3. 获取对话历史构建上下文
|
||||
history, err := p.GetChatHistory(ctx, userId, sessionId, 10)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取对话历史失败", zap.Error(err))
|
||||
eventChan <- StreamEvent{
|
||||
Event: "error",
|
||||
Data: petResponse.StreamErrorData{
|
||||
Error: "获取对话历史失败",
|
||||
},
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// 4. 构建LLM请求
|
||||
messages := p.buildMessages(history, req.Message)
|
||||
llmReq := utils.LLMRequest{
|
||||
Messages: messages,
|
||||
Model: req.Model,
|
||||
Stream: true,
|
||||
Temperature: req.Temperature,
|
||||
MaxTokens: req.MaxTokens,
|
||||
RequestID: uuid.New().String(),
|
||||
}
|
||||
|
||||
// 5. 创建LLM流式响应通道
|
||||
llmEventChan := make(chan utils.LLMStreamEvent, 100)
|
||||
defer close(llmEventChan)
|
||||
|
||||
// 6. 启动LLM流式调用
|
||||
llmUtil := utils.GetVolcengineLLMUtil()
|
||||
go func() {
|
||||
if err := llmUtil.StreamChatCompletion(llmReq, llmEventChan); err != nil {
|
||||
global.GVA_LOG.Error("LLM流式调用失败", zap.Error(err))
|
||||
eventChan <- StreamEvent{
|
||||
Event: "error",
|
||||
Data: petResponse.StreamErrorData{
|
||||
Error: "LLM调用失败",
|
||||
},
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// 7. 处理流式响应
|
||||
var fullMessage string
|
||||
var tokenCount int
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case llmEvent, ok := <-llmEventChan:
|
||||
if !ok {
|
||||
// 通道关闭,流式响应结束
|
||||
return nil
|
||||
}
|
||||
|
||||
switch llmEvent.Event {
|
||||
case "message":
|
||||
// 处理消息事件
|
||||
if len(llmEvent.Data.Choices) > 0 {
|
||||
delta := llmEvent.Data.Choices[0].Delta.Content
|
||||
fullMessage += delta
|
||||
|
||||
// 转发消息给客户端
|
||||
eventChan <- StreamEvent{
|
||||
Event: "message",
|
||||
Data: petResponse.StreamMessageData{
|
||||
Delta: delta,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
case "done":
|
||||
// 流式响应完成
|
||||
responseTime := time.Since(startTime).Milliseconds()
|
||||
if llmEvent.Data.Usage != nil {
|
||||
tokenCount = llmEvent.Data.Usage.TotalTokens
|
||||
}
|
||||
|
||||
// 对完整消息进行敏感词检测
|
||||
aiFiltered, aiHasSensitive, err := sensitiveUtil.FilterText(fullMessage)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("AI回复敏感词检测失败", zap.Error(err))
|
||||
aiFiltered = fullMessage
|
||||
}
|
||||
|
||||
if aiHasSensitive {
|
||||
global.GVA_LOG.Warn("AI回复包含敏感词", zap.String("original", fullMessage), zap.String("filtered", aiFiltered))
|
||||
fullMessage = aiFiltered
|
||||
}
|
||||
|
||||
// 保存对话记录
|
||||
if err := p.saveUserMessage(ctx, userId, sessionId, req.Message, false); err != nil {
|
||||
global.GVA_LOG.Error("保存用户消息失败", zap.Error(err))
|
||||
}
|
||||
|
||||
if err := p.saveAssistantMessage(ctx, userId, sessionId, fullMessage, aiHasSensitive, tokenCount, responseTime); err != nil {
|
||||
global.GVA_LOG.Error("保存AI回复失败", zap.Error(err))
|
||||
}
|
||||
|
||||
// 发送完成事件
|
||||
eventChan <- StreamEvent{
|
||||
Event: "done",
|
||||
Data: petResponse.StreamDoneData{
|
||||
Message: fullMessage,
|
||||
SessionId: sessionId,
|
||||
},
|
||||
}
|
||||
return nil
|
||||
|
||||
case "error":
|
||||
// 处理错误事件
|
||||
global.GVA_LOG.Error("LLM流式响应错误", zap.Any("error", llmEvent.Data.Error))
|
||||
eventChan <- StreamEvent{
|
||||
Event: "error",
|
||||
Data: petResponse.StreamErrorData{
|
||||
Error: "LLM响应错误",
|
||||
},
|
||||
}
|
||||
return fmt.Errorf("LLM响应错误")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetChatHistory 获取对话历史
|
||||
func (p *PetChatService) GetChatHistory(ctx context.Context, userId uint, sessionId string, limit int) ([]petResponse.ChatHistoryItem, error) {
|
||||
var conversations []pet.PetAiAssistantConversations
|
||||
|
||||
query := global.GVA_DB.WithContext(ctx).Where("user_id = ?", userId)
|
||||
|
||||
// 如果提供了会话ID,则按会话ID过滤
|
||||
if sessionId != "" {
|
||||
query = query.Where("session_id = ?", sessionId)
|
||||
}
|
||||
|
||||
// 按创建时间倒序,限制数量
|
||||
if err := query.Order("created_at DESC").Limit(limit).Find(&conversations).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 反转切片,使其按时间正序排列
|
||||
for i, j := 0, len(conversations)-1; i < j; i, j = i+1, j-1 {
|
||||
conversations[i], conversations[j] = conversations[j], conversations[i]
|
||||
}
|
||||
|
||||
// 转换为简化结构
|
||||
items := make([]petResponse.ChatHistoryItem, 0, len(conversations))
|
||||
for _, conv := range conversations {
|
||||
if conv.MessageContent != nil && conv.Role != nil {
|
||||
item := petResponse.ChatHistoryItem{
|
||||
ID: conv.ID,
|
||||
Role: *conv.Role,
|
||||
Message: *conv.MessageContent,
|
||||
SessionId: "",
|
||||
CreatedAt: conv.CreatedAt,
|
||||
}
|
||||
|
||||
// 设置会话ID(如果存在)
|
||||
if conv.SessionId != nil {
|
||||
item.SessionId = *conv.SessionId
|
||||
}
|
||||
|
||||
items = append(items, item)
|
||||
}
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// SaveConversation 保存对话记录
|
||||
func (p *PetChatService) SaveConversation(ctx context.Context, conversation *pet.PetAiAssistantConversations) error {
|
||||
return global.GVA_DB.WithContext(ctx).Create(conversation).Error
|
||||
}
|
||||
|
||||
// ClearChatHistory 清空对话历史
|
||||
func (p *PetChatService) ClearChatHistory(ctx context.Context, userId uint, sessionId string) error {
|
||||
query := global.GVA_DB.WithContext(ctx).Where("user_id = ?", userId)
|
||||
|
||||
// 如果提供了会话ID,则只清空指定会话
|
||||
if sessionId != "" {
|
||||
query = query.Where("session_id = ?", sessionId)
|
||||
}
|
||||
|
||||
return query.Delete(&pet.PetAiAssistantConversations{}).Error
|
||||
}
|
||||
|
||||
// GetChatSessions 获取用户的聊天会话列表
|
||||
func (p *PetChatService) GetChatSessions(ctx context.Context, userId uint) ([]map[string]interface{}, error) {
|
||||
var sessions []map[string]interface{}
|
||||
|
||||
// 查询用户的所有会话,按最后更新时间分组
|
||||
err := global.GVA_DB.WithContext(ctx).
|
||||
Model(&pet.PetAiAssistantConversations{}).
|
||||
Select("session_id, MAX(updated_at) as last_updated, COUNT(*) as message_count").
|
||||
Where("user_id = ? AND session_id IS NOT NULL AND session_id != ''", userId).
|
||||
Group("session_id").
|
||||
Order("last_updated DESC").
|
||||
Scan(&sessions).Error
|
||||
|
||||
return sessions, err
|
||||
}
|
||||
|
||||
// buildMessages 构建LLM请求消息
|
||||
func (p *PetChatService) buildMessages(history []petResponse.ChatHistoryItem, userMessage string) []utils.LLMMessage {
|
||||
messages := []utils.LLMMessage{
|
||||
{
|
||||
Role: "system",
|
||||
Content: "你是一个专业的宠物助手,专门为宠物主人提供关于宠物护理、健康、训练和日常生活的建议。请用友善、专业的语气回答问题。",
|
||||
},
|
||||
}
|
||||
|
||||
// 添加历史对话
|
||||
for _, conv := range history {
|
||||
messages = append(messages, utils.LLMMessage{
|
||||
Role: conv.Role,
|
||||
Content: conv.Message,
|
||||
})
|
||||
}
|
||||
|
||||
// 添加当前用户消息
|
||||
messages = append(messages, utils.LLMMessage{
|
||||
Role: "user",
|
||||
Content: userMessage,
|
||||
})
|
||||
|
||||
return messages
|
||||
}
|
||||
|
||||
// saveUserMessage 保存用户消息
|
||||
func (p *PetChatService) saveUserMessage(ctx context.Context, userId uint, sessionId, message string, isSensitive bool) error {
|
||||
userIdPtr := int(userId)
|
||||
rolePtr := "user"
|
||||
messagePtr := message
|
||||
sessionIdPtr := sessionId
|
||||
isSensitivePtr := isSensitive
|
||||
|
||||
conversation := &pet.PetAiAssistantConversations{
|
||||
UserId: &userIdPtr,
|
||||
MessageContent: &messagePtr,
|
||||
Role: &rolePtr,
|
||||
SessionId: &sessionIdPtr,
|
||||
IsSensitive: &isSensitivePtr,
|
||||
}
|
||||
|
||||
return p.SaveConversation(ctx, conversation)
|
||||
}
|
||||
|
||||
// saveAssistantMessage 保存AI助手消息
|
||||
func (p *PetChatService) saveAssistantMessage(ctx context.Context, userId uint, sessionId, message string, isSensitive bool, tokenCount int, responseTime int64) error {
|
||||
userIdPtr := int(userId)
|
||||
rolePtr := "assistant"
|
||||
messagePtr := message
|
||||
sessionIdPtr := sessionId
|
||||
isSensitivePtr := isSensitive
|
||||
tokenCountPtr := tokenCount
|
||||
responseTimePtr := int(responseTime)
|
||||
|
||||
conversation := &pet.PetAiAssistantConversations{
|
||||
UserId: &userIdPtr,
|
||||
MessageContent: &messagePtr,
|
||||
Role: &rolePtr,
|
||||
SessionId: &sessionIdPtr,
|
||||
IsSensitive: &isSensitivePtr,
|
||||
TokenCount: &tokenCountPtr,
|
||||
ResponseTime: &responseTimePtr,
|
||||
}
|
||||
|
||||
return p.SaveConversation(ctx, conversation)
|
||||
}
|
||||
@@ -69,7 +69,3 @@ func (petFamiliesService *PetFamiliesService) GetPetFamiliesInfoList(ctx context
|
||||
err = db.Find(&petFamiliess).Error
|
||||
return petFamiliess, total, err
|
||||
}
|
||||
func (petFamiliesService *PetFamiliesService) GetPetFamiliesPublic(ctx context.Context) {
|
||||
// 此方法为获取数据源定义的数据
|
||||
// 请自行实现
|
||||
}
|
||||
|
||||
@@ -69,7 +69,3 @@ func (petFamilyInvitationsService *PetFamilyInvitationsService) GetPetFamilyInvi
|
||||
err = db.Find(&petFamilyInvitationss).Error
|
||||
return petFamilyInvitationss, total, err
|
||||
}
|
||||
func (petFamilyInvitationsService *PetFamilyInvitationsService) GetPetFamilyInvitationsPublic(ctx context.Context) {
|
||||
// 此方法为获取数据源定义的数据
|
||||
// 请自行实现
|
||||
}
|
||||
|
||||
@@ -69,7 +69,3 @@ func (petFamilyMembersService *PetFamilyMembersService) GetPetFamilyMembersInfoL
|
||||
err = db.Find(&petFamilyMemberss).Error
|
||||
return petFamilyMemberss, total, err
|
||||
}
|
||||
func (petFamilyMembersService *PetFamilyMembersService) GetPetFamilyMembersPublic(ctx context.Context) {
|
||||
// 此方法为获取数据源定义的数据
|
||||
// 请自行实现
|
||||
}
|
||||
|
||||
@@ -69,7 +69,3 @@ func (petFamilyPetsService *PetFamilyPetsService) GetPetFamilyPetsInfoList(ctx c
|
||||
err = db.Find(&petFamilyPetss).Error
|
||||
return petFamilyPetss, total, err
|
||||
}
|
||||
func (petFamilyPetsService *PetFamilyPetsService) GetPetFamilyPetsPublic(ctx context.Context) {
|
||||
// 此方法为获取数据源定义的数据
|
||||
// 请自行实现
|
||||
}
|
||||
|
||||
@@ -69,7 +69,3 @@ func (petPetsService *PetPetsService) GetPetPetsInfoList(ctx context.Context, in
|
||||
err = db.Find(&petPetss).Error
|
||||
return petPetss, total, err
|
||||
}
|
||||
func (petPetsService *PetPetsService) GetPetPetsPublic(ctx context.Context) {
|
||||
// 此方法为获取数据源定义的数据
|
||||
// 请自行实现
|
||||
}
|
||||
|
||||
@@ -69,7 +69,3 @@ func (petRecordsService *PetRecordsService) GetPetRecordsInfoList(ctx context.Co
|
||||
err = db.Find(&petRecordss).Error
|
||||
return petRecordss, total, err
|
||||
}
|
||||
func (petRecordsService *PetRecordsService) GetPetRecordsPublic(ctx context.Context) {
|
||||
// 此方法为获取数据源定义的数据
|
||||
// 请自行实现
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ WHERE
|
||||
lower(a.table_name) = ?
|
||||
AND lower(a.OWNER) = ?
|
||||
ORDER BY
|
||||
a.COLUMN_ID;
|
||||
a.COLUMN_ID
|
||||
`
|
||||
|
||||
err = global.GVA_DBList[businessDB].Raw(sql, tableName, dbName).Scan(&entities).Error
|
||||
|
||||
@@ -86,6 +86,12 @@ func (sysVersionService *SysVersionService) GetApisByIds(ctx context.Context, id
|
||||
return
|
||||
}
|
||||
|
||||
// GetDictionariesByIds 根据ID列表获取字典数据
|
||||
func (sysVersionService *SysVersionService) GetDictionariesByIds(ctx context.Context, ids []uint) (dictionaries []system.SysDictionary, err error) {
|
||||
err = global.GVA_DB.Where("id in ?", ids).Preload("SysDictionaryDetails").Find(&dictionaries).Error
|
||||
return
|
||||
}
|
||||
|
||||
// ImportMenus 导入菜单数据
|
||||
func (sysVersionService *SysVersionService) ImportMenus(ctx context.Context, menus []system.SysBaseMenu) error {
|
||||
return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
|
||||
@@ -194,3 +200,31 @@ func (sysVersionService *SysVersionService) ImportApis(apis []system.SysApi) err
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// ImportDictionaries 导入字典数据
|
||||
func (sysVersionService *SysVersionService) ImportDictionaries(dictionaries []system.SysDictionary) error {
|
||||
return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
|
||||
for _, dict := range dictionaries {
|
||||
// 检查字典是否已存在
|
||||
var existingDict system.SysDictionary
|
||||
if err := tx.Where("type = ?", dict.Type).First(&existingDict).Error; err == nil {
|
||||
// 字典已存在,跳过
|
||||
continue
|
||||
}
|
||||
|
||||
// 创建新字典
|
||||
newDict := system.SysDictionary{
|
||||
Name: dict.Name,
|
||||
Type: dict.Type,
|
||||
Status: dict.Status,
|
||||
Desc: dict.Desc,
|
||||
SysDictionaryDetails: dict.SysDictionaryDetails,
|
||||
}
|
||||
|
||||
if err := tx.Create(&newDict).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ func (i *initDictDetail) InitializeData(ctx context.Context) (context.Context, e
|
||||
}
|
||||
|
||||
dicts[2].SysDictionaryDetails = []sysModel.SysDictionaryDetail{
|
||||
{Label: "date", Status: &True},
|
||||
{Label: "date", Value: "0", Status: &True, Extend: "mysql", Sort: 0},
|
||||
{Label: "time", Value: "1", Status: &True, Extend: "mysql", Sort: 1},
|
||||
{Label: "year", Value: "2", Status: &True, Extend: "mysql", Sort: 2},
|
||||
{Label: "datetime", Value: "3", Status: &True, Extend: "mysql", Sort: 3},
|
||||
@@ -74,7 +74,7 @@ func (i *initDictDetail) InitializeData(ctx context.Context) (context.Context, e
|
||||
{Label: "timestamptz", Value: "6", Status: &True, Extend: "pgsql", Sort: 5},
|
||||
}
|
||||
dicts[3].SysDictionaryDetails = []sysModel.SysDictionaryDetail{
|
||||
{Label: "float", Status: &True},
|
||||
{Label: "float", Value: "0", Status: &True, Extend: "mysql", Sort: 0},
|
||||
{Label: "double", Value: "1", Status: &True, Extend: "mysql", Sort: 1},
|
||||
{Label: "decimal", Value: "2", Status: &True, Extend: "mysql", Sort: 2},
|
||||
{Label: "numeric", Value: "3", Status: &True, Extend: "pgsql", Sort: 3},
|
||||
@@ -82,7 +82,7 @@ func (i *initDictDetail) InitializeData(ctx context.Context) (context.Context, e
|
||||
}
|
||||
|
||||
dicts[4].SysDictionaryDetails = []sysModel.SysDictionaryDetail{
|
||||
{Label: "char", Status: &True},
|
||||
{Label: "char", Value: "0", Status: &True, Extend: "mysql", Sort: 0},
|
||||
{Label: "varchar", Value: "1", Status: &True, Extend: "mysql", Sort: 1},
|
||||
{Label: "tinyblob", Value: "2", Status: &True, Extend: "mysql", Sort: 2},
|
||||
{Label: "tinytext", Value: "3", Status: &True, Extend: "mysql", Sort: 3},
|
||||
|
||||
@@ -70,7 +70,7 @@ func (i *initUser) InitializeData(ctx context.Context) (next context.Context, er
|
||||
Username: "a303176530",
|
||||
Password: password,
|
||||
NickName: "用户1",
|
||||
HeaderImg: "https:///qmplusimg.henrongyi.top/1572075907logo.png",
|
||||
HeaderImg: "https://qmplusimg.henrongyi.top/1572075907logo.png",
|
||||
AuthorityId: 9528,
|
||||
Phone: "17611111111",
|
||||
Email: "333333333@qq.com"},
|
||||
|
||||
@@ -46,7 +46,8 @@ func GenerateField(field systemReq.AutoCodeField) string {
|
||||
|
||||
gormTag += "column:" + field.ColumnName + ";"
|
||||
|
||||
if field.DataTypeLong != "" && field.FieldType != "enum" {
|
||||
// 对于int类型,根据DataTypeLong决定具体的Go类型,不使用size标签
|
||||
if field.DataTypeLong != "" && field.FieldType != "enum" && field.FieldType != "int" {
|
||||
gormTag += fmt.Sprintf("size:%s;", field.DataTypeLong)
|
||||
}
|
||||
|
||||
@@ -85,8 +86,27 @@ func GenerateField(field systemReq.AutoCodeField) string {
|
||||
tagContent := fmt.Sprintf(`json:"%s" form:"%s" gorm:"%s"`,
|
||||
field.FieldJson, field.FieldJson, gormTag)
|
||||
|
||||
// 对于int类型,根据DataTypeLong决定具体的Go类型
|
||||
var fieldType string
|
||||
if field.FieldType == "int" {
|
||||
switch field.DataTypeLong {
|
||||
case "1", "2", "3":
|
||||
fieldType = "int8"
|
||||
case "4", "5":
|
||||
fieldType = "int16"
|
||||
case "6", "7", "8", "9", "10":
|
||||
fieldType = "int32"
|
||||
case "11", "12", "13", "14", "15", "16", "17", "18", "19", "20":
|
||||
fieldType = "int64"
|
||||
default:
|
||||
fieldType = "int64"
|
||||
}
|
||||
} else {
|
||||
fieldType = field.FieldType
|
||||
}
|
||||
|
||||
result = fmt.Sprintf(`%s *%s `+"`"+`%s`+"`"+``,
|
||||
field.FieldName, field.FieldType, tagContent)
|
||||
field.FieldName, fieldType, tagContent)
|
||||
}
|
||||
|
||||
if field.Require {
|
||||
@@ -222,11 +242,11 @@ func GenerateSearchFormItem(field systemReq.AutoCodeField) string {
|
||||
`
|
||||
} else if field.FieldType == "float64" || field.FieldType == "int" {
|
||||
if field.FieldSearchType == "BETWEEN" || field.FieldSearchType == "NOT BETWEEN" {
|
||||
result += fmt.Sprintf(` <el-input class="w-40" v-model.number="searchInfo.start%s" placeholder="最小值" />
|
||||
result += fmt.Sprintf(` <el-input class="!w-40" v-model.number="searchInfo.start%s" placeholder="最小值" />
|
||||
`, field.FieldName)
|
||||
result += ` —
|
||||
`
|
||||
result += fmt.Sprintf(` <el-input class="w-40" v-model.number="searchInfo.end%s" placeholder="最大值" />
|
||||
result += fmt.Sprintf(` <el-input class="!w-40" v-model.number="searchInfo.end%s" placeholder="最大值" />
|
||||
`, field.FieldName)
|
||||
} else {
|
||||
result += fmt.Sprintf(` <el-input v-model.number="searchInfo.%s" placeholder="搜索条件" />
|
||||
@@ -250,7 +270,7 @@ func GenerateSearchFormItem(field systemReq.AutoCodeField) string {
|
||||
`
|
||||
result += ` </template>
|
||||
`
|
||||
result += fmt.Sprintf(`<el-date-picker class="w-[380px]" v-model="searchInfo.%sRange" type="datetimerange" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间"></el-date-picker>`, field.FieldJson)
|
||||
result += fmt.Sprintf(`<el-date-picker class="!w-380px" v-model="searchInfo.%sRange" type="datetimerange" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间"></el-date-picker>`, field.FieldJson)
|
||||
} else {
|
||||
result += fmt.Sprintf(`<el-date-picker v-model="searchInfo.%s" type="datetime" placeholder="搜索条件"></el-date-picker>`, field.FieldJson)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
swd "github.com/kirklin/go-swd"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type SensitiveWordUtil struct {
|
||||
detector *swd.SWD
|
||||
once sync.Once
|
||||
initErr error
|
||||
}
|
||||
|
||||
// 全局单例实例
|
||||
var (
|
||||
sensitiveWordInstance *SensitiveWordUtil
|
||||
sensitiveWordOnce sync.Once
|
||||
)
|
||||
|
||||
// GetSensitiveWordUtil 获取敏感词工具单例实例
|
||||
func GetSensitiveWordUtil() *SensitiveWordUtil {
|
||||
sensitiveWordOnce.Do(func() {
|
||||
sensitiveWordInstance = &SensitiveWordUtil{}
|
||||
})
|
||||
return sensitiveWordInstance
|
||||
}
|
||||
|
||||
// InitDetector 初始化敏感词检测器(单例模式)
|
||||
func (s *SensitiveWordUtil) InitDetector() error {
|
||||
s.once.Do(func() {
|
||||
detector, err := swd.New()
|
||||
if err != nil {
|
||||
s.initErr = err
|
||||
global.GVA_LOG.Error("Failed to initialize sensitive word detector", zap.Error(err))
|
||||
return
|
||||
}
|
||||
s.detector = detector
|
||||
global.GVA_LOG.Info("Sensitive word detector initialized successfully")
|
||||
})
|
||||
return s.initErr
|
||||
}
|
||||
|
||||
// DetectSensitive 检测文本中的敏感词
|
||||
func (s *SensitiveWordUtil) DetectSensitive(text string) ([]swd.SensitiveWord, error) {
|
||||
if s.detector == nil {
|
||||
if err := s.InitDetector(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
words := s.detector.MatchAll(text)
|
||||
return words, nil
|
||||
}
|
||||
|
||||
// HasSensitive 检查文本是否包含敏感词
|
||||
func (s *SensitiveWordUtil) HasSensitive(text string) (bool, error) {
|
||||
if s.detector == nil {
|
||||
if err := s.InitDetector(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
return s.detector.Detect(text), nil
|
||||
}
|
||||
|
||||
// FilterText 过滤文本中的敏感词,返回过滤后的文本和是否包含敏感词
|
||||
func (s *SensitiveWordUtil) FilterText(text string) (filtered string, hasSensitive bool, err error) {
|
||||
if s.detector == nil {
|
||||
if err := s.InitDetector(); err != nil {
|
||||
return text, false, err
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否包含敏感词
|
||||
hasSensitive = s.detector.Detect(text)
|
||||
|
||||
if !hasSensitive {
|
||||
return text, false, nil
|
||||
}
|
||||
|
||||
// 使用星号替换敏感词
|
||||
filtered = s.detector.ReplaceWithAsterisk(text)
|
||||
return filtered, true, nil
|
||||
}
|
||||
|
||||
// FilterTextWithCustomReplace 使用自定义替换策略过滤敏感词
|
||||
func (s *SensitiveWordUtil) FilterTextWithCustomReplace(text string, replaceFunc func(swd.SensitiveWord) string) (filtered string, hasSensitive bool, err error) {
|
||||
if s.detector == nil {
|
||||
if err := s.InitDetector(); err != nil {
|
||||
return text, false, err
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否包含敏感词
|
||||
hasSensitive = s.detector.Detect(text)
|
||||
|
||||
if !hasSensitive {
|
||||
return text, false, nil
|
||||
}
|
||||
|
||||
// 使用自定义替换策略
|
||||
filtered = s.detector.ReplaceWithStrategy(text, replaceFunc)
|
||||
return filtered, true, nil
|
||||
}
|
||||
|
||||
// GetFirstSensitiveWord 获取文本中第一个敏感词
|
||||
func (s *SensitiveWordUtil) GetFirstSensitiveWord(text string) (*swd.SensitiveWord, error) {
|
||||
if s.detector == nil {
|
||||
if err := s.InitDetector(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
word := s.detector.Match(text)
|
||||
return word, nil
|
||||
}
|
||||
|
||||
// AddCustomWords 添加自定义敏感词
|
||||
func (s *SensitiveWordUtil) AddCustomWords(words map[string]swd.Category) error {
|
||||
if s.detector == nil {
|
||||
if err := s.InitDetector(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return s.detector.AddWords(words)
|
||||
}
|
||||
|
||||
// RemoveWord 移除指定敏感词
|
||||
func (s *SensitiveWordUtil) RemoveWord(word string) error {
|
||||
if s.detector == nil {
|
||||
if err := s.InitDetector(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return s.detector.RemoveWord(word)
|
||||
}
|
||||
|
||||
// Clear 清空词库
|
||||
func (s *SensitiveWordUtil) Clear() error {
|
||||
if s.detector == nil {
|
||||
if err := s.InitDetector(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return s.detector.Clear()
|
||||
}
|
||||
@@ -5,7 +5,7 @@ var (
|
||||
ApiVerify = Rules{"Path": {NotEmpty()}, "Description": {NotEmpty()}, "ApiGroup": {NotEmpty()}, "Method": {NotEmpty()}}
|
||||
MenuVerify = Rules{"Path": {NotEmpty()}, "Name": {NotEmpty()}, "Component": {NotEmpty()}, "Sort": {Ge("0")}}
|
||||
MenuMetaVerify = Rules{"Title": {NotEmpty()}}
|
||||
LoginVerify = Rules{"CaptchaId": {NotEmpty()}, "Captcha": {NotEmpty()}, "Username": {NotEmpty()}, "Password": {NotEmpty()}}
|
||||
LoginVerify = Rules{"Username": {NotEmpty()}, "Password": {NotEmpty()}}
|
||||
RegisterVerify = Rules{"Username": {NotEmpty()}, "NickName": {NotEmpty()}, "Password": {NotEmpty()}, "AuthorityId": {NotEmpty()}}
|
||||
PageInfoVerify = Rules{"Page": {NotEmpty()}, "PageSize": {NotEmpty()}}
|
||||
CustomerVerify = Rules{"CustomerName": {NotEmpty()}, "CustomerPhoneData": {NotEmpty()}}
|
||||
|
||||
@@ -0,0 +1,361 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/google/uuid"
|
||||
"github.com/volcengine/volcengine-go-sdk/service/arkruntime"
|
||||
"github.com/volcengine/volcengine-go-sdk/service/arkruntime/model"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// LLMMessage 聊天消息结构体
|
||||
type LLMMessage struct {
|
||||
Role string `json:"role"` // 角色: system, user, assistant
|
||||
Content string `json:"content"` // 消息内容
|
||||
}
|
||||
|
||||
// LLMRequest 聊天请求结构体
|
||||
type LLMRequest struct {
|
||||
Messages []LLMMessage `json:"messages" binding:"required"` // 对话消息列表
|
||||
Model string `json:"model"` // 模型名称,如果为空则使用默认模型
|
||||
Stream bool `json:"stream"` // 是否流式响应,默认false
|
||||
Temperature float64 `json:"temperature"` // 温度参数,控制随机性,范围0-1
|
||||
MaxTokens int `json:"max_tokens"` // 最大生成token数
|
||||
TopP float64 `json:"top_p"` // 核采样参数
|
||||
RequestID string `json:"request_id,omitempty"` // 请求ID,用于流式响应管理
|
||||
}
|
||||
|
||||
// LLMChoice 响应选择结构体
|
||||
type LLMChoice struct {
|
||||
Index int `json:"index"` // 选择索引
|
||||
Message LLMMessage `json:"message,omitempty"` // 完整响应消息(非流式)
|
||||
Delta LLMMessage `json:"delta,omitempty"` // 增量消息(流式)
|
||||
FinishReason string `json:"finish_reason,omitempty"` // 结束原因: stop, length, content_filter
|
||||
}
|
||||
|
||||
// LLMUsage 使用量统计结构体
|
||||
type LLMUsage struct {
|
||||
PromptTokens int `json:"prompt_tokens"` // 输入token数
|
||||
CompletionTokens int `json:"completion_tokens"` // 输出token数
|
||||
TotalTokens int `json:"total_tokens"` // 总token数
|
||||
}
|
||||
|
||||
// LLMError API错误结构体
|
||||
type LLMError struct {
|
||||
Code string `json:"code"` // 错误代码
|
||||
Message string `json:"message"` // 错误消息
|
||||
Type string `json:"type"` // 错误类型
|
||||
}
|
||||
|
||||
// LLMResponse 聊天响应结构体
|
||||
type LLMResponse struct {
|
||||
ID string `json:"id"` // 响应ID
|
||||
Object string `json:"object"` // 对象类型: chat.completion 或 chat.completion.chunk
|
||||
Created int64 `json:"created"` // 创建时间戳
|
||||
Model string `json:"model"` // 使用的模型
|
||||
Choices []LLMChoice `json:"choices"` // 响应选择列表
|
||||
Usage *LLMUsage `json:"usage,omitempty"` // 使用量统计(非流式响应)
|
||||
Error *LLMError `json:"error,omitempty"` // 错误信息
|
||||
}
|
||||
|
||||
// LLMStreamEvent 流式事件结构体
|
||||
type LLMStreamEvent struct {
|
||||
Event string `json:"event"` // 事件类型: message, error, done
|
||||
Data LLMResponse `json:"data"` // 事件数据
|
||||
}
|
||||
|
||||
// VolcengineLLMUtil 火山引擎LLM工具类
|
||||
type VolcengineLLMUtil struct {
|
||||
client *arkruntime.Client
|
||||
activeSessions sync.Map // requestID -> context.CancelFunc
|
||||
once sync.Once
|
||||
initErr error
|
||||
}
|
||||
|
||||
// 全局单例实例
|
||||
var (
|
||||
volcengineLLMInstance *VolcengineLLMUtil
|
||||
volcengineLLMOnce sync.Once
|
||||
)
|
||||
|
||||
// GetVolcengineLLMUtil 获取火山引擎LLM工具单例实例
|
||||
func GetVolcengineLLMUtil() *VolcengineLLMUtil {
|
||||
volcengineLLMOnce.Do(func() {
|
||||
volcengineLLMInstance = &VolcengineLLMUtil{}
|
||||
})
|
||||
return volcengineLLMInstance
|
||||
}
|
||||
|
||||
// InitClient 初始化火山引擎客户端(单例模式)
|
||||
func (v *VolcengineLLMUtil) InitClient() error {
|
||||
v.once.Do(func() {
|
||||
config := global.GVA_CONFIG.Volcengine
|
||||
|
||||
// 验证配置
|
||||
if config.ApiKey == "" {
|
||||
v.initErr = fmt.Errorf("volcengine api key is empty")
|
||||
global.GVA_LOG.Error("Volcengine configuration error", zap.Error(v.initErr))
|
||||
return
|
||||
}
|
||||
|
||||
// 创建ARK Runtime客户端,使用API Key
|
||||
v.client = arkruntime.NewClientWithApiKey(config.ApiKey)
|
||||
|
||||
global.GVA_LOG.Info("Volcengine LLM client initialized successfully")
|
||||
})
|
||||
return v.initErr
|
||||
}
|
||||
|
||||
// ChatCompletion 非流式聊天完成
|
||||
func (v *VolcengineLLMUtil) ChatCompletion(req LLMRequest) (*LLMResponse, error) {
|
||||
if v.client == nil {
|
||||
if err := v.InitClient(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 生成请求ID
|
||||
if req.RequestID == "" {
|
||||
req.RequestID = uuid.New().String()
|
||||
}
|
||||
|
||||
// 转换消息格式
|
||||
messages := make([]*model.ChatCompletionMessage, len(req.Messages))
|
||||
for i, msg := range req.Messages {
|
||||
messages[i] = &model.ChatCompletionMessage{
|
||||
Role: msg.Role,
|
||||
Content: &model.ChatCompletionMessageContent{
|
||||
StringValue: &msg.Content,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 设置模型
|
||||
modelName := req.Model
|
||||
if modelName == "" {
|
||||
modelName = global.GVA_CONFIG.Volcengine.Model
|
||||
}
|
||||
|
||||
// 创建请求
|
||||
chatReq := &model.ChatCompletionRequest{
|
||||
Model: modelName,
|
||||
Messages: messages,
|
||||
Stream: false,
|
||||
}
|
||||
|
||||
// 设置可选参数
|
||||
if req.Temperature > 0 {
|
||||
chatReq.Temperature = float32(req.Temperature)
|
||||
}
|
||||
if req.MaxTokens > 0 {
|
||||
chatReq.MaxTokens = req.MaxTokens
|
||||
}
|
||||
if req.TopP > 0 {
|
||||
chatReq.TopP = float32(req.TopP)
|
||||
}
|
||||
|
||||
// 调用API
|
||||
resp, err := v.client.CreateChatCompletion(context.Background(), chatReq)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("Chat completion failed", zap.Error(err), zap.String("requestID", req.RequestID))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换响应格式
|
||||
choices := make([]LLMChoice, len(resp.Choices))
|
||||
for i, choice := range resp.Choices {
|
||||
choices[i] = LLMChoice{
|
||||
Index: choice.Index,
|
||||
Message: LLMMessage{
|
||||
Role: choice.Message.Role,
|
||||
Content: *choice.Message.Content.StringValue,
|
||||
},
|
||||
FinishReason: string(choice.FinishReason),
|
||||
}
|
||||
}
|
||||
|
||||
usage := &LLMUsage{
|
||||
PromptTokens: resp.Usage.PromptTokens,
|
||||
CompletionTokens: resp.Usage.CompletionTokens,
|
||||
TotalTokens: resp.Usage.TotalTokens,
|
||||
}
|
||||
|
||||
chatResp := &LLMResponse{
|
||||
ID: resp.ID,
|
||||
Object: resp.Object,
|
||||
Created: resp.Created,
|
||||
Model: resp.Model,
|
||||
Choices: choices,
|
||||
Usage: usage,
|
||||
}
|
||||
|
||||
global.GVA_LOG.Info("Chat completion successful", zap.String("requestID", req.RequestID))
|
||||
return chatResp, nil
|
||||
}
|
||||
|
||||
// StreamChatCompletion 流式聊天完成
|
||||
func (v *VolcengineLLMUtil) StreamChatCompletion(req LLMRequest, eventChan chan<- LLMStreamEvent) error {
|
||||
if v.client == nil {
|
||||
if err := v.InitClient(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 生成请求ID
|
||||
if req.RequestID == "" {
|
||||
req.RequestID = uuid.New().String()
|
||||
}
|
||||
|
||||
// 转换消息格式
|
||||
messages := make([]*model.ChatCompletionMessage, len(req.Messages))
|
||||
for i, msg := range req.Messages {
|
||||
messages[i] = &model.ChatCompletionMessage{
|
||||
Role: msg.Role,
|
||||
Content: &model.ChatCompletionMessageContent{
|
||||
StringValue: &msg.Content,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 设置模型
|
||||
modelName := req.Model
|
||||
if modelName == "" {
|
||||
modelName = global.GVA_CONFIG.Volcengine.Model
|
||||
}
|
||||
|
||||
// 创建流式请求
|
||||
chatReq := &model.ChatCompletionRequest{
|
||||
Model: modelName,
|
||||
Messages: messages,
|
||||
Stream: true,
|
||||
}
|
||||
|
||||
// 设置可选参数
|
||||
if req.Temperature > 0 {
|
||||
chatReq.Temperature = float32(req.Temperature)
|
||||
}
|
||||
if req.MaxTokens > 0 {
|
||||
chatReq.MaxTokens = req.MaxTokens
|
||||
}
|
||||
if req.TopP > 0 {
|
||||
chatReq.TopP = float32(req.TopP)
|
||||
}
|
||||
|
||||
// 创建上下文和取消函数
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
v.activeSessions.Store(req.RequestID, cancel)
|
||||
defer func() {
|
||||
v.activeSessions.Delete(req.RequestID)
|
||||
cancel()
|
||||
}()
|
||||
|
||||
// 调用流式API
|
||||
stream_resp, err := v.client.CreateChatCompletionStream(ctx, chatReq)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("Stream chat completion failed", zap.Error(err), zap.String("requestID", req.RequestID))
|
||||
return err
|
||||
}
|
||||
defer stream_resp.Close()
|
||||
|
||||
global.GVA_LOG.Info("Stream chat completion started", zap.String("requestID", req.RequestID))
|
||||
|
||||
// 处理流式响应
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
// 接收流式数据
|
||||
recv, err := stream_resp.Recv()
|
||||
if err != nil {
|
||||
if err.Error() == "EOF" {
|
||||
// 流结束
|
||||
eventChan <- LLMStreamEvent{
|
||||
Event: "done",
|
||||
Data: LLMResponse{
|
||||
ID: req.RequestID,
|
||||
Object: "chat.completion.chunk",
|
||||
},
|
||||
}
|
||||
global.GVA_LOG.Info("Stream chat completion finished", zap.String("requestID", req.RequestID))
|
||||
return nil
|
||||
}
|
||||
global.GVA_LOG.Error("Stream receive error", zap.Error(err), zap.String("requestID", req.RequestID))
|
||||
eventChan <- LLMStreamEvent{
|
||||
Event: "error",
|
||||
Data: LLMResponse{
|
||||
ID: req.RequestID,
|
||||
Error: &LLMError{
|
||||
Code: "stream_error",
|
||||
Message: err.Error(),
|
||||
Type: "stream_error",
|
||||
},
|
||||
},
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// 转换响应格式
|
||||
choices := make([]LLMChoice, len(recv.Choices))
|
||||
for i, choice := range recv.Choices {
|
||||
choices[i] = LLMChoice{
|
||||
Index: choice.Index,
|
||||
Delta: LLMMessage{
|
||||
Role: choice.Delta.Role,
|
||||
Content: choice.Delta.Content,
|
||||
},
|
||||
FinishReason: string(choice.FinishReason),
|
||||
}
|
||||
}
|
||||
|
||||
var usage *LLMUsage
|
||||
if recv.Usage != nil {
|
||||
usage = &LLMUsage{
|
||||
PromptTokens: recv.Usage.PromptTokens,
|
||||
CompletionTokens: recv.Usage.CompletionTokens,
|
||||
TotalTokens: recv.Usage.TotalTokens,
|
||||
}
|
||||
}
|
||||
|
||||
// 发送消息事件
|
||||
eventChan <- LLMStreamEvent{
|
||||
Event: "message",
|
||||
Data: LLMResponse{
|
||||
ID: recv.ID,
|
||||
Object: recv.Object,
|
||||
Created: recv.Created,
|
||||
Model: recv.Model,
|
||||
Choices: choices,
|
||||
Usage: usage,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// StopGeneration 停止生成
|
||||
func (v *VolcengineLLMUtil) StopGeneration(requestID string) error {
|
||||
if cancel, ok := v.activeSessions.Load(requestID); ok {
|
||||
if cancelFunc, ok := cancel.(context.CancelFunc); ok {
|
||||
cancelFunc()
|
||||
v.activeSessions.Delete(requestID)
|
||||
global.GVA_LOG.Info("Generation stopped", zap.String("requestID", requestID))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("session not found: %s", requestID)
|
||||
}
|
||||
|
||||
// GetActiveSessionsCount 获取活跃会话数量
|
||||
func (v *VolcengineLLMUtil) GetActiveSessionsCount() int {
|
||||
count := 0
|
||||
v.activeSessions.Range(func(key, value interface{}) bool {
|
||||
count++
|
||||
return true
|
||||
})
|
||||
return count
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
// 运行项目前通过node执行此脚本 (此脚本与 node_modules 目录同级)
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
const wfPath = path.resolve(__dirname, './node_modules/.bin')
|
||||
|
||||
fs.readdir(wfPath, (err, files) => {
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
未经授权的商用使用可能会被我们的资产搜索引擎爬取,并可能导致后续索赔。索赔金额将不低于高级授权费的十倍。请您遵守版权法律法规,尊重知识产权。
|
||||
*/
|
||||
|
||||
var child_process = require('child_process')
|
||||
import child_process from 'child_process'
|
||||
|
||||
var url = 'https://www.gin-vue-admin.com'
|
||||
var cmd = ''
|
||||
|
||||
+9
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gin-vue-admin",
|
||||
"version": "2.8.4",
|
||||
"version": "2.8.5",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "node openDocument.js && vite --host --mode development",
|
||||
@@ -9,10 +9,12 @@
|
||||
"preview": "vite preview",
|
||||
"fix-memory-limit": "cross-env LIMIT=4096 increase-memory-limit"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"@form-create/designer": "^3.2.6",
|
||||
"@form-create/element-ui": "^3.2.10",
|
||||
"@unocss/transformer-directives": "^66.4.2",
|
||||
"@vue-office/docx": "^1.6.2",
|
||||
"@vue-office/excel": "^1.7.11",
|
||||
"@vue-office/pdf": "^2.0.2",
|
||||
@@ -39,10 +41,9 @@
|
||||
"screenfull": "^6.0.2",
|
||||
"sortablejs": "^1.15.3",
|
||||
"spark-md5": "^3.0.2",
|
||||
"tailwindcss": "^3.4.10",
|
||||
"universal-cookie": "^7",
|
||||
"vform3-builds": "^3.0.10",
|
||||
"vite-auto-import-svg": "^1.6.0",
|
||||
"vite-auto-import-svg": "^1.9.0",
|
||||
"vue": "^3.5.7",
|
||||
"vue-cropper": "^1.1.4",
|
||||
"vue-echarts": "^7.0.3",
|
||||
@@ -55,6 +56,9 @@
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "^7.25.1",
|
||||
"@eslint/js": "^8.56.0",
|
||||
"@unocss/extractor-svelte": "^66.4.2",
|
||||
"@unocss/preset-wind3": "^66.4.2",
|
||||
"@unocss/vite": "^66.5.0",
|
||||
"@vitejs/plugin-legacy": "^6.0.0",
|
||||
"@vitejs/plugin-vue": "^5.0.3",
|
||||
"@vue/cli-plugin-babel": "~5.0.8",
|
||||
@@ -69,8 +73,10 @@
|
||||
"dotenv": "^16.4.5",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-vue": "^9.19.2",
|
||||
"globals": "^16.3.0",
|
||||
"sass": "^1.78.0",
|
||||
"terser": "^5.31.6",
|
||||
"unocss": "^66.4.2",
|
||||
"vite": "^6.2.3",
|
||||
"vite-plugin-banner": "^0.8.0",
|
||||
"vite-plugin-importer": "^0.2.5",
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
}
|
||||
}
|
||||
@@ -95,16 +95,4 @@ export const getPetAdoptionApplicationsList = (params) => {
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags PetAdoptionApplications
|
||||
// @Summary 不需要鉴权的petAdoptionApplications表接口
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data query petReq.PetAdoptionApplicationsSearch true "分页获取petAdoptionApplications表列表"
|
||||
// @Success 200 {object} response.Response{data=object,msg=string} "获取成功"
|
||||
// @Router /petAdoptionApplications/getPetAdoptionApplicationsPublic [get]
|
||||
export const getPetAdoptionApplicationsPublic = () => {
|
||||
return service({
|
||||
url: '/petAdoptionApplications/getPetAdoptionApplicationsPublic',
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -95,16 +95,4 @@ export const getPetAdoptionPostsList = (params) => {
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags PetAdoptionPosts
|
||||
// @Summary 不需要鉴权的petAdoptionPosts表接口
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data query petReq.PetAdoptionPostsSearch true "分页获取petAdoptionPosts表列表"
|
||||
// @Success 200 {object} response.Response{data=object,msg=string} "获取成功"
|
||||
// @Router /petAdoptionPosts/getPetAdoptionPostsPublic [get]
|
||||
export const getPetAdoptionPostsPublic = () => {
|
||||
return service({
|
||||
url: '/petAdoptionPosts/getPetAdoptionPostsPublic',
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user