mirror of
https://github.com/flipped-aurora/gin-vue-admin.git
synced 2026-10-04 09:10:57 +00:00
+ Merge code from main branch (e58d40a).
+ Update translation. + Code clean up.
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/mcp/client"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
)
|
||||
|
||||
// Create
|
||||
// @Tags mcp
|
||||
// @Summary 自动McpTool
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.AutoMcpTool true "创建自动代码"
|
||||
// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}"
|
||||
// @Router /autoCode/mcp [post]
|
||||
func (a *AutoCodeTemplateApi) MCP(c *gin.Context) {
|
||||
var info request.AutoMcpTool
|
||||
err := c.ShouldBindJSON(&info)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
toolFilePath, err := autoCodeTemplateService.CreateMcp(c.Request.Context(), info)
|
||||
if err != nil {
|
||||
response.FailWithMessage("创建失败", c)
|
||||
global.GVA_LOG.Error(err.Error())
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("创建成功,MCP Tool路径:"+toolFilePath, c)
|
||||
}
|
||||
|
||||
// Create
|
||||
// @Tags mcp
|
||||
// @Summary 自动McpTool
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.AutoMcpTool true "创建自动代码"
|
||||
// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}"
|
||||
// @Router /autoCode/mcpList [post]
|
||||
func (a *AutoCodeTemplateApi) MCPList(c *gin.Context) {
|
||||
|
||||
baseUrl := fmt.Sprintf("http://127.0.0.1:%d%s", global.GVA_CONFIG.System.Addr, global.GVA_CONFIG.MCP.SSEPath)
|
||||
|
||||
testClient, err := client.NewClient(baseUrl, "testClient", "v1.0.0", global.GVA_CONFIG.MCP.Name)
|
||||
defer testClient.Close()
|
||||
toolsRequest := mcp.ListToolsRequest{}
|
||||
|
||||
list, err := testClient.ListTools(c.Request.Context(), toolsRequest)
|
||||
|
||||
if err != nil {
|
||||
response.FailWithMessage("创建失败", c)
|
||||
global.GVA_LOG.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
mcpServerConfig := map[string]interface{}{
|
||||
"mcpServers": map[string]interface{}{
|
||||
global.GVA_CONFIG.MCP.Name: map[string]string{
|
||||
"url": baseUrl,
|
||||
},
|
||||
},
|
||||
}
|
||||
response.OkWithData(gin.H{
|
||||
"mcpServerConfig": mcpServerConfig,
|
||||
"list": list,
|
||||
}, c)
|
||||
}
|
||||
|
||||
// Create
|
||||
// @Tags mcp
|
||||
// @Summary 测试McpTool
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body object true "调用MCP Tool的参数"
|
||||
// @Success 200 {object} response.Response "{"success":true,"data":{},"msg":"测试成功"}"
|
||||
// @Router /autoCode/mcpTest [post]
|
||||
func (a *AutoCodeTemplateApi) MCPTest(c *gin.Context) {
|
||||
// 定义接口请求结构
|
||||
var testRequest struct {
|
||||
Name string `json:"name" binding:"required"` // 工具名称
|
||||
Arguments map[string]interface{} `json:"arguments" binding:"required"` // 工具参数
|
||||
}
|
||||
|
||||
// 绑定JSON请求体
|
||||
if err := c.ShouldBindJSON(&testRequest); err != nil {
|
||||
response.FailWithMessage("参数解析失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
// 创建MCP客户端
|
||||
baseUrl := fmt.Sprintf("http://127.0.0.1:%d%s", global.GVA_CONFIG.System.Addr, global.GVA_CONFIG.MCP.SSEPath)
|
||||
testClient, err := client.NewClient(baseUrl, "testClient", "v1.0.0", global.GVA_CONFIG.MCP.Name)
|
||||
if err != nil {
|
||||
response.FailWithMessage("创建MCP客户端失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
defer testClient.Close()
|
||||
|
||||
ctx := c.Request.Context()
|
||||
|
||||
// 初始化MCP连接
|
||||
initRequest := mcp.InitializeRequest{}
|
||||
initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
|
||||
initRequest.Params.ClientInfo = mcp.Implementation{
|
||||
Name: "testClient",
|
||||
Version: "v1.0.0",
|
||||
}
|
||||
|
||||
_, err = testClient.Initialize(ctx, initRequest)
|
||||
if err != nil {
|
||||
response.FailWithMessage("初始化MCP连接失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
// 构建工具调用请求
|
||||
request := mcp.CallToolRequest{}
|
||||
request.Params.Name = testRequest.Name
|
||||
request.Params.Arguments = testRequest.Arguments
|
||||
|
||||
// 调用工具
|
||||
result, err := testClient.CallTool(ctx, request)
|
||||
if err != nil {
|
||||
response.FailWithMessage("工具调用失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
// 处理响应结果
|
||||
if len(result.Content) == 0 {
|
||||
response.FailWithMessage("工具未返回任何内容", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 返回结果
|
||||
response.OkWithData(result.Content, c)
|
||||
}
|
||||
@@ -3,6 +3,9 @@ package system
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
|
||||
@@ -15,6 +18,33 @@ import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// 用于token一次性存储
|
||||
var (
|
||||
exportTokenCache = make(map[string]interface{})
|
||||
exportTokenExpiration = make(map[string]time.Time)
|
||||
tokenMutex sync.RWMutex
|
||||
)
|
||||
|
||||
// 五分钟检测窗口过期
|
||||
func cleanupExpiredTokens() {
|
||||
for {
|
||||
time.Sleep(5 * time.Minute)
|
||||
tokenMutex.Lock()
|
||||
now := time.Now()
|
||||
for token, expiry := range exportTokenExpiration {
|
||||
if now.After(expiry) {
|
||||
delete(exportTokenCache, token)
|
||||
delete(exportTokenExpiration, token)
|
||||
}
|
||||
}
|
||||
tokenMutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
go cleanupExpiredTokens()
|
||||
}
|
||||
|
||||
type SysExportTemplateApi struct {
|
||||
}
|
||||
|
||||
@@ -183,7 +213,7 @@ func (sysExportTemplateApi *SysExportTemplateApi) GetSysExportTemplateList(c *gi
|
||||
}
|
||||
}
|
||||
|
||||
// ExportExcel 导出表格
|
||||
// ExportExcel 导出表格token
|
||||
// @Tags SysExportTemplate
|
||||
// @Summary 导出表格
|
||||
// @Security ApiKeyAuth
|
||||
@@ -192,16 +222,83 @@ func (sysExportTemplateApi *SysExportTemplateApi) GetSysExportTemplateList(c *gi
|
||||
// @Router /sysExportTemplate/exportExcel [get]
|
||||
func (sysExportTemplateApi *SysExportTemplateApi) ExportExcel(c *gin.Context) {
|
||||
templateID := c.Query("templateID")
|
||||
queryParams := c.Request.URL.Query()
|
||||
if templateID == "" {
|
||||
response.FailWithMessage(global.Translate("sys_export.templateIDEmpty"), c)
|
||||
return
|
||||
}
|
||||
|
||||
queryParams := c.Request.URL.Query()
|
||||
|
||||
//创造一次性token
|
||||
token := utils.RandomString(32) // 随机32位
|
||||
|
||||
// 记录本次请求参数
|
||||
exportParams := map[string]interface{}{
|
||||
"templateID": templateID,
|
||||
"queryParams": queryParams,
|
||||
}
|
||||
|
||||
// 参数保留记录完成鉴权
|
||||
tokenMutex.Lock()
|
||||
exportTokenCache[token] = exportParams
|
||||
exportTokenExpiration[token] = time.Now().Add(30 * time.Minute)
|
||||
tokenMutex.Unlock()
|
||||
|
||||
// 生成一次性链接
|
||||
exportUrl := fmt.Sprintf("/sysExportTemplate/exportExcelByToken?token=%s", token)
|
||||
response.OkWithData(exportUrl, c)
|
||||
}
|
||||
|
||||
// ExportExcelByToken 导出表格
|
||||
// @Tags ExportExcelByToken
|
||||
// @Summary 导出表格
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Router /sysExportTemplate/exportExcelByToken [get]
|
||||
func (sysExportTemplateApi *SysExportTemplateApi) ExportExcelByToken(c *gin.Context) {
|
||||
token := c.Query("token")
|
||||
if token == "" {
|
||||
response.FailWithMessage("导出token不能为空", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取token并且从缓存中剔除
|
||||
tokenMutex.RLock()
|
||||
exportParamsRaw, exists := exportTokenCache[token]
|
||||
expiry, _ := exportTokenExpiration[token]
|
||||
tokenMutex.RUnlock()
|
||||
|
||||
if !exists || time.Now().After(expiry) {
|
||||
global.GVA_LOG.Error("导出token无效或已过期!")
|
||||
response.FailWithMessage("导出token无效或已过期", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 从token获取参数
|
||||
exportParams, ok := exportParamsRaw.(map[string]interface{})
|
||||
if !ok {
|
||||
global.GVA_LOG.Error("解析导出参数失败!")
|
||||
response.FailWithMessage("解析导出参数失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取导出参数
|
||||
templateID := exportParams["templateID"].(string)
|
||||
queryParams := exportParams["queryParams"].(url.Values)
|
||||
|
||||
// 清理一次性token
|
||||
tokenMutex.Lock()
|
||||
delete(exportTokenCache, token)
|
||||
delete(exportTokenExpiration, token)
|
||||
tokenMutex.Unlock()
|
||||
|
||||
// 导出
|
||||
if file, name, err := sysExportTemplateService.ExportExcel(templateID, queryParams); err != nil {
|
||||
global.GVA_LOG.Error(global.Translate("general.getDataFail"), zap.Error(err))
|
||||
response.FailWithMessage(global.Translate("general.getDataFail"), c)
|
||||
} else {
|
||||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", name+utils.RandomString(6)+".xlsx")) // 对下载的文件重命名
|
||||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", name+utils.RandomString(6)+".xlsx"))
|
||||
c.Header("success", "true")
|
||||
c.Data(http.StatusOK, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", file.Bytes())
|
||||
}
|
||||
@@ -213,18 +310,91 @@ func (sysExportTemplateApi *SysExportTemplateApi) ExportExcel(c *gin.Context) {
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Router /sysExportTemplate/ExportTemplate [get]
|
||||
// @Router /sysExportTemplate/exportTemplate [get]
|
||||
func (sysExportTemplateApi *SysExportTemplateApi) ExportTemplate(c *gin.Context) {
|
||||
templateID := c.Query("templateID")
|
||||
if templateID == "" {
|
||||
response.FailWithMessage(global.Translate("sys_export.templateIDEmpty"), c)
|
||||
return
|
||||
}
|
||||
|
||||
// 创造一次性token
|
||||
token := utils.RandomString(32) // 随机32位
|
||||
|
||||
// 记录本次请求参数
|
||||
exportParams := map[string]interface{}{
|
||||
"templateID": templateID,
|
||||
"isTemplate": true,
|
||||
}
|
||||
|
||||
// 参数保留记录完成鉴权
|
||||
tokenMutex.Lock()
|
||||
exportTokenCache[token] = exportParams
|
||||
exportTokenExpiration[token] = time.Now().Add(30 * time.Minute)
|
||||
tokenMutex.Unlock()
|
||||
|
||||
// 生成一次性链接
|
||||
exportUrl := fmt.Sprintf("/sysExportTemplate/exportTemplateByToken?token=%s", token)
|
||||
response.OkWithData(exportUrl, c)
|
||||
}
|
||||
|
||||
// ExportTemplateByToken 通过token导出表格模板
|
||||
// @Tags ExportTemplateByToken
|
||||
// @Summary 通过token导出表格模板
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Router /sysExportTemplate/exportTemplateByToken [get]
|
||||
func (sysExportTemplateApi *SysExportTemplateApi) ExportTemplateByToken(c *gin.Context) {
|
||||
token := c.Query("token")
|
||||
if token == "" {
|
||||
response.FailWithMessage("导出token不能为空", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取token并且从缓存中剔除
|
||||
tokenMutex.RLock()
|
||||
exportParamsRaw, exists := exportTokenCache[token]
|
||||
expiry, _ := exportTokenExpiration[token]
|
||||
tokenMutex.RUnlock()
|
||||
|
||||
if !exists || time.Now().After(expiry) {
|
||||
global.GVA_LOG.Error("导出token无效或已过期!")
|
||||
response.FailWithMessage("导出token无效或已过期", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 从token获取参数
|
||||
exportParams, ok := exportParamsRaw.(map[string]interface{})
|
||||
if !ok {
|
||||
global.GVA_LOG.Error("解析导出参数失败!")
|
||||
response.FailWithMessage("解析导出参数失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否为模板导出
|
||||
isTemplate, _ := exportParams["isTemplate"].(bool)
|
||||
if !isTemplate {
|
||||
global.GVA_LOG.Error("token类型错误!")
|
||||
response.FailWithMessage("token类型错误", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取导出参数
|
||||
templateID := exportParams["templateID"].(string)
|
||||
|
||||
// 清理一次性token
|
||||
tokenMutex.Lock()
|
||||
delete(exportTokenCache, token)
|
||||
delete(exportTokenExpiration, token)
|
||||
tokenMutex.Unlock()
|
||||
|
||||
// 导出模板
|
||||
if file, name, err := sysExportTemplateService.ExportTemplate(templateID); err != nil {
|
||||
global.GVA_LOG.Error(global.Translate("general.getDataFail"), zap.Error(err))
|
||||
response.FailWithMessage(global.Translate("general.getDataFail"), c)
|
||||
} else {
|
||||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", name+"模板.xlsx")) // 对下载的文件重命名
|
||||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", name+"模板.xlsx"))
|
||||
c.Header("success", "true")
|
||||
c.Data(http.StatusOK, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", file.Bytes())
|
||||
}
|
||||
|
||||
@@ -13,31 +13,6 @@ import (
|
||||
|
||||
type OperationRecordApi struct{}
|
||||
|
||||
// CreateSysOperationRecord
|
||||
// @Tags SysOperationRecord
|
||||
// @Summary 创建SysOperationRecord
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body system.SysOperationRecord true "创建SysOperationRecord"
|
||||
// @Success 200 {object} response.Response{msg=string} "创建SysOperationRecord"
|
||||
// @Router /sysOperationRecord/createSysOperationRecord [post]
|
||||
func (s *OperationRecordApi) CreateSysOperationRecord(c *gin.Context) {
|
||||
var sysOperationRecord system.SysOperationRecord
|
||||
err := c.ShouldBindJSON(&sysOperationRecord)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = operationRecordService.CreateSysOperationRecord(sysOperationRecord)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error(global.Translate("general.creationFail"), zap.Error(err))
|
||||
response.FailWithMessage(global.Translate("general.creationFailErr"), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage(global.Translate("general.createSuccess"), c)
|
||||
}
|
||||
|
||||
// DeleteSysOperationRecord
|
||||
// @Tags SysOperationRecord
|
||||
// @Summary 删除SysOperationRecord
|
||||
|
||||
@@ -55,16 +55,17 @@ func (s *SystemApi) SetSystemConfig(c *gin.Context) {
|
||||
|
||||
// ReloadSystem
|
||||
// @Tags System
|
||||
// @Summary 重启系统
|
||||
// @Summary 重载系统
|
||||
// @Security ApiKeyAuth
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{msg=string} "重启系统"
|
||||
// @Success 200 {object} response.Response{msg=string} "重载系统"
|
||||
// @Router /system/reloadSystem [post]
|
||||
func (s *SystemApi) ReloadSystem(c *gin.Context) {
|
||||
err := utils.Reload()
|
||||
// 触发系统重载事件
|
||||
err := utils.GlobalSystemEvents.TriggerReload()
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error(global.Translate("sys_system.rebootFail"), zap.Error(err))
|
||||
response.FailWithMessage(global.Translate("sys_system.rebootFailErr"), c)
|
||||
response.FailWithMessage("重载系统失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage(global.Translate("sys_system.rebootSuccess"), c)
|
||||
|
||||
@@ -93,7 +93,7 @@ func (b *BaseApi) TokenNext(c *gin.Context, user system.SysUser) {
|
||||
}
|
||||
|
||||
if jwtStr, err := jwtService.GetRedisJWT(user.Username); err == redis.Nil {
|
||||
if err := jwtService.SetRedisJWT(token, user.Username); err != nil {
|
||||
if err := utils.SetRedisJWT(token, user.Username); err != nil {
|
||||
global.GVA_LOG.Error(global.Translate("sys_user.loginStatusFail"), zap.Error(err))
|
||||
response.FailWithMessage(global.Translate("sys_user.loginStatusFailErr"), c)
|
||||
return
|
||||
@@ -114,7 +114,7 @@ func (b *BaseApi) TokenNext(c *gin.Context, user system.SysUser) {
|
||||
response.FailWithMessage(global.Translate("sys_user.jwtInvalidationFailed"), c)
|
||||
return
|
||||
}
|
||||
if err := jwtService.SetRedisJWT(token, user.GetUsername()); err != nil {
|
||||
if err := utils.SetRedisJWT(token, user.GetUsername()); err != nil {
|
||||
response.FailWithMessage(global.Translate("sys_user.loginStatusFailErr"), c)
|
||||
return
|
||||
}
|
||||
@@ -467,13 +467,13 @@ func (b *BaseApi) GetUserInfo(c *gin.Context) {
|
||||
// @Success 200 {object} response.Response{msg=string} "重置用户密码"
|
||||
// @Router /user/resetPassword [post]
|
||||
func (b *BaseApi) ResetPassword(c *gin.Context) {
|
||||
var user system.SysUser
|
||||
err := c.ShouldBindJSON(&user)
|
||||
var rps systemReq.ResetPassword
|
||||
err := c.ShouldBindJSON(&rps)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = userService.ResetPassword(user.ID)
|
||||
err = userService.ResetPassword(rps.ID, rps.Password)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error(global.Translate("sys_user.resetPWFail"), zap.Error(err))
|
||||
response.FailWithMessage(global.Translate("sys_user.resetPWFailErr")+" "+err.Error(), c)
|
||||
|
||||
Reference in New Issue
Block a user