🎨 更新项目版本

This commit is contained in:
2025-09-03 01:45:01 +08:00
parent f928348284
commit 5496bdaa94
130 changed files with 9397 additions and 1816 deletions

View File

@@ -32,7 +32,6 @@ func (b *FileUploadAndDownloadApi) UploadFile(c *gin.Context) {
response.FailWithMessage("接收文件失败", c)
return
}
file, err = fileUploadAndDownloadService.UploadFile(header, noSave, classId) // 文件上传后拿到文件路径
if err != nil {
global.GVA_LOG.Error("上传文件失败!", zap.Error(err))

View File

@@ -0,0 +1,144 @@
package system
import (
"fmt"
"git.echol.cn/loser/lckt/global"
"git.echol.cn/loser/lckt/mcp/client"
"git.echol.cn/loser/lckt/model/common/response"
"git.echol.cn/loser/lckt/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)
}

View File

@@ -22,6 +22,7 @@ type ApiGroup struct {
AutoCodeHistoryApi
AutoCodeTemplateApi
SysParamsApi
SysVersionApi
}
var (
@@ -44,4 +45,5 @@ var (
autoCodePackageService = service.ServiceGroupApp.SystemServiceGroup.AutoCodePackage
autoCodeHistoryService = service.ServiceGroupApp.SystemServiceGroup.AutoCodeHistory
autoCodeTemplateService = service.ServiceGroupApp.SystemServiceGroup.AutoCodeTemplate
sysVersionService = service.ServiceGroupApp.SystemServiceGroup.SysVersionService
)

View File

@@ -143,7 +143,7 @@ func (a *AuthorityMenuApi) AddBaseMenu(c *gin.Context) {
err = menuService.AddBaseMenu(menu)
if err != nil {
global.GVA_LOG.Error("添加失败!", zap.Error(err))
response.FailWithMessage("添加失败", c)
response.FailWithMessage("添加失败"+err.Error(), c)
return
}
response.OkWithMessage("添加成功", c)

View File

@@ -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("创建失败!", zap.Error(err))
response.FailWithMessage("创建失败", c)
return
}
response.OkWithMessage("创建成功", c)
}
// DeleteSysOperationRecord
// @Tags SysOperationRecord
// @Summary 删除SysOperationRecord

View File

@@ -55,19 +55,20 @@ 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("重系统失败!", zap.Error(err))
response.FailWithMessage("重系统失败", c)
global.GVA_LOG.Error("重系统失败!", zap.Error(err))
response.FailWithMessage("重系统失败:"+err.Error(), c)
return
}
response.OkWithMessage("重系统成功", c)
response.OkWithMessage("重系统成功", c)
}
// GetServerInfo

View File

@@ -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
@@ -93,7 +92,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("设置登录状态失败!", zap.Error(err))
response.FailWithMessage("设置登录状态失败", c)
return
@@ -114,7 +113,7 @@ func (b *BaseApi) TokenNext(c *gin.Context, user system.SysUser) {
response.FailWithMessage("jwt作废失败", c)
return
}
if err := jwtService.SetRedisJWT(token, user.GetUsername()); err != nil {
if err := utils.SetRedisJWT(token, user.GetUsername()); err != nil {
response.FailWithMessage("设置登录状态失败", c)
return
}
@@ -184,7 +183,7 @@ func (b *BaseApi) ChangePassword(c *gin.Context) {
}
uid := utils.GetUserID(c)
u := &system.SysUser{GVA_MODEL: global.GVA_MODEL{ID: uid}, Password: req.Password}
_, err = userService.ChangePassword(u, req.NewPassword)
err = userService.ChangePassword(u, req.NewPassword)
if err != nil {
global.GVA_LOG.Error("修改失败!", zap.Error(err))
response.FailWithMessage("修改失败,原密码与当前账户不符", c)
@@ -467,13 +466,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("重置失败!", zap.Error(err))
response.FailWithMessage("重置失败"+err.Error(), c)

View File

@@ -0,0 +1,486 @@
package system
import (
"encoding/json"
"fmt"
"net/http"
"sort"
"strconv"
"time"
"git.echol.cn/loser/lckt/global"
"git.echol.cn/loser/lckt/model/common/response"
"git.echol.cn/loser/lckt/model/system"
systemReq "git.echol.cn/loser/lckt/model/system/request"
systemRes "git.echol.cn/loser/lckt/model/system/response"
"git.echol.cn/loser/lckt/utils"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type SysVersionApi struct{}
// buildMenuTree 构建菜单树结构
func buildMenuTree(menus []system.SysBaseMenu) []system.SysBaseMenu {
// 创建菜单映射
menuMap := make(map[uint]*system.SysBaseMenu)
for i := range menus {
menuMap[menus[i].ID] = &menus[i]
}
// 构建树结构
var rootMenus []system.SysBaseMenu
for _, menu := range menus {
if menu.ParentId == 0 {
// 根菜单
menuData := convertMenuToStruct(menu, menuMap)
rootMenus = append(rootMenus, menuData)
}
}
// 按sort排序根菜单
sort.Slice(rootMenus, func(i, j int) bool {
return rootMenus[i].Sort < rootMenus[j].Sort
})
return rootMenus
}
// convertMenuToStruct 将菜单转换为结构体并递归处理子菜单
func convertMenuToStruct(menu system.SysBaseMenu, menuMap map[uint]*system.SysBaseMenu) system.SysBaseMenu {
result := system.SysBaseMenu{
Path: menu.Path,
Name: menu.Name,
Hidden: menu.Hidden,
Component: menu.Component,
Sort: menu.Sort,
Meta: menu.Meta,
}
// 清理并复制参数数据
if len(menu.Parameters) > 0 {
cleanParameters := make([]system.SysBaseMenuParameter, 0, len(menu.Parameters))
for _, param := range menu.Parameters {
cleanParam := system.SysBaseMenuParameter{
Type: param.Type,
Key: param.Key,
Value: param.Value,
// 不复制 ID, CreatedAt, UpdatedAt, SysBaseMenuID
}
cleanParameters = append(cleanParameters, cleanParam)
}
result.Parameters = cleanParameters
}
// 清理并复制菜单按钮数据
if len(menu.MenuBtn) > 0 {
cleanMenuBtns := make([]system.SysBaseMenuBtn, 0, len(menu.MenuBtn))
for _, btn := range menu.MenuBtn {
cleanBtn := system.SysBaseMenuBtn{
Name: btn.Name,
Desc: btn.Desc,
// 不复制 ID, CreatedAt, UpdatedAt, SysBaseMenuID
}
cleanMenuBtns = append(cleanMenuBtns, cleanBtn)
}
result.MenuBtn = cleanMenuBtns
}
// 查找并处理子菜单
var children []system.SysBaseMenu
for _, childMenu := range menuMap {
if childMenu.ParentId == menu.ID {
childData := convertMenuToStruct(*childMenu, menuMap)
children = append(children, childData)
}
}
// 按sort排序子菜单
if len(children) > 0 {
sort.Slice(children, func(i, j int) bool {
return children[i].Sort < children[j].Sort
})
result.Children = children
}
return result
}
// DeleteSysVersion 删除版本管理
// @Tags SysVersion
// @Summary 删除版本管理
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param data body system.SysVersion true "删除版本管理"
// @Success 200 {object} response.Response{msg=string} "删除成功"
// @Router /sysVersion/deleteSysVersion [delete]
func (sysVersionApi *SysVersionApi) DeleteSysVersion(c *gin.Context) {
// 创建业务用Context
ctx := c.Request.Context()
ID := c.Query("ID")
err := sysVersionService.DeleteSysVersion(ctx, ID)
if err != nil {
global.GVA_LOG.Error("删除失败!", zap.Error(err))
response.FailWithMessage("删除失败:"+err.Error(), c)
return
}
response.OkWithMessage("删除成功", c)
}
// DeleteSysVersionByIds 批量删除版本管理
// @Tags SysVersion
// @Summary 批量删除版本管理
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Success 200 {object} response.Response{msg=string} "批量删除成功"
// @Router /sysVersion/deleteSysVersionByIds [delete]
func (sysVersionApi *SysVersionApi) DeleteSysVersionByIds(c *gin.Context) {
// 创建业务用Context
ctx := c.Request.Context()
IDs := c.QueryArray("IDs[]")
err := sysVersionService.DeleteSysVersionByIds(ctx, IDs)
if err != nil {
global.GVA_LOG.Error("批量删除失败!", zap.Error(err))
response.FailWithMessage("批量删除失败:"+err.Error(), c)
return
}
response.OkWithMessage("批量删除成功", c)
}
// FindSysVersion 用id查询版本管理
// @Tags SysVersion
// @Summary 用id查询版本管理
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param ID query uint true "用id查询版本管理"
// @Success 200 {object} response.Response{data=system.SysVersion,msg=string} "查询成功"
// @Router /sysVersion/findSysVersion [get]
func (sysVersionApi *SysVersionApi) FindSysVersion(c *gin.Context) {
// 创建业务用Context
ctx := c.Request.Context()
ID := c.Query("ID")
resysVersion, err := sysVersionService.GetSysVersion(ctx, ID)
if err != nil {
global.GVA_LOG.Error("查询失败!", zap.Error(err))
response.FailWithMessage("查询失败:"+err.Error(), c)
return
}
response.OkWithData(resysVersion, c)
}
// GetSysVersionList 分页获取版本管理列表
// @Tags SysVersion
// @Summary 分页获取版本管理列表
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param data query systemReq.SysVersionSearch true "分页获取版本管理列表"
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "获取成功"
// @Router /sysVersion/getSysVersionList [get]
func (sysVersionApi *SysVersionApi) GetSysVersionList(c *gin.Context) {
// 创建业务用Context
ctx := c.Request.Context()
var pageInfo systemReq.SysVersionSearch
err := c.ShouldBindQuery(&pageInfo)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
list, total, err := sysVersionService.GetSysVersionInfoList(ctx, pageInfo)
if err != nil {
global.GVA_LOG.Error("获取失败!", zap.Error(err))
response.FailWithMessage("获取失败:"+err.Error(), c)
return
}
response.OkWithDetailed(response.PageResult{
List: list,
Total: total,
Page: pageInfo.Page,
PageSize: pageInfo.PageSize,
}, "获取成功", c)
}
// GetSysVersionPublic 不需要鉴权的版本管理接口
// @Tags SysVersion
// @Summary 不需要鉴权的版本管理接口
// @Accept application/json
// @Produce application/json
// @Success 200 {object} response.Response{data=object,msg=string} "获取成功"
// @Router /sysVersion/getSysVersionPublic [get]
func (sysVersionApi *SysVersionApi) GetSysVersionPublic(c *gin.Context) {
// 创建业务用Context
ctx := c.Request.Context()
// 此接口不需要鉴权
// 示例为返回了一个固定的消息接口一般本接口用于C端服务需要自己实现业务逻辑
sysVersionService.GetSysVersionPublic(ctx)
response.OkWithDetailed(gin.H{
"info": "不需要鉴权的版本管理接口信息",
}, "获取成功", c)
}
// ExportVersion 创建发版数据
// @Tags SysVersion
// @Summary 创建发版数据
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param data body systemReq.ExportVersionRequest true "创建发版数据"
// @Success 200 {object} response.Response{msg=string} "创建成功"
// @Router /sysVersion/exportVersion [post]
func (sysVersionApi *SysVersionApi) ExportVersion(c *gin.Context) {
ctx := c.Request.Context()
var req systemReq.ExportVersionRequest
err := c.ShouldBindJSON(&req)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
// 获取选中的菜单数据
var menuData []system.SysBaseMenu
if len(req.MenuIds) > 0 {
menuData, err = sysVersionService.GetMenusByIds(ctx, req.MenuIds)
if err != nil {
global.GVA_LOG.Error("获取菜单数据失败!", zap.Error(err))
response.FailWithMessage("获取菜单数据失败:"+err.Error(), c)
return
}
}
// 获取选中的API数据
var apiData []system.SysApi
if len(req.ApiIds) > 0 {
apiData, err = sysVersionService.GetApisByIds(ctx, req.ApiIds)
if err != nil {
global.GVA_LOG.Error("获取API数据失败!", zap.Error(err))
response.FailWithMessage("获取API数据失败:"+err.Error(), c)
return
}
}
// 获取选中的字典数据
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)
// 处理API数据清除ID和时间戳字段
processedApis := make([]system.SysApi, 0, len(apiData))
for _, api := range apiData {
cleanApi := system.SysApi{
Path: api.Path,
Description: api.Description,
ApiGroup: api.ApiGroup,
Method: api.Method,
}
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{
Name: req.VersionName,
Code: req.VersionCode,
Description: req.Description,
ExportTime: time.Now().Format("2006-01-02 15:04:05"),
},
Menus: processedMenus,
Apis: processedApis,
Dictionaries: processedDicts,
}
// 转换为JSON
jsonData, err := json.MarshalIndent(exportData, "", " ")
if err != nil {
global.GVA_LOG.Error("JSON序列化失败!", zap.Error(err))
response.FailWithMessage("JSON序列化失败:"+err.Error(), c)
return
}
// 保存版本记录
version := system.SysVersion{
VersionName: utils.Pointer(req.VersionName),
VersionCode: utils.Pointer(req.VersionCode),
Description: utils.Pointer(req.Description),
VersionData: utils.Pointer(string(jsonData)),
}
err = sysVersionService.CreateSysVersion(ctx, &version)
if err != nil {
global.GVA_LOG.Error("保存版本记录失败!", zap.Error(err))
response.FailWithMessage("保存版本记录失败:"+err.Error(), c)
return
}
response.OkWithMessage("创建发版成功", c)
}
// DownloadVersionJson 下载版本JSON数据
// @Tags SysVersion
// @Summary 下载版本JSON数据
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param ID query string true "版本ID"
// @Success 200 {object} response.Response{data=object,msg=string} "下载成功"
// @Router /sysVersion/downloadVersionJson [get]
func (sysVersionApi *SysVersionApi) DownloadVersionJson(c *gin.Context) {
ctx := c.Request.Context()
ID := c.Query("ID")
if ID == "" {
response.FailWithMessage("版本ID不能为空", c)
return
}
// 获取版本记录
version, err := sysVersionService.GetSysVersion(ctx, ID)
if err != nil {
global.GVA_LOG.Error("获取版本记录失败!", zap.Error(err))
response.FailWithMessage("获取版本记录失败:"+err.Error(), c)
return
}
// 构建JSON数据
var jsonData []byte
if version.VersionData != nil && *version.VersionData != "" {
jsonData = []byte(*version.VersionData)
} else {
// 如果没有存储的JSON数据构建一个基本的结构
basicData := systemRes.ExportVersionResponse{
Version: systemReq.VersionInfo{
Name: *version.VersionName,
Code: *version.VersionCode,
Description: *version.Description,
ExportTime: version.CreatedAt.Format("2006-01-02 15:04:05"),
},
Menus: []system.SysBaseMenu{},
Apis: []system.SysApi{},
}
jsonData, _ = json.MarshalIndent(basicData, "", " ")
}
// 设置下载响应头
filename := fmt.Sprintf("version_%s_%s.json", *version.VersionCode, time.Now().Format("20060102150405"))
c.Header("Content-Type", "application/json")
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
c.Header("Content-Length", strconv.Itoa(len(jsonData)))
c.Data(http.StatusOK, "application/json", jsonData)
}
// ImportVersion 导入版本数据
// @Tags SysVersion
// @Summary 导入版本数据
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param data body systemReq.ImportVersionRequest true "版本JSON数据"
// @Success 200 {object} response.Response{msg=string} "导入成功"
// @Router /sysVersion/importVersion [post]
func (sysVersionApi *SysVersionApi) ImportVersion(c *gin.Context) {
ctx := c.Request.Context()
// 获取JSON数据
var importData systemReq.ImportVersionRequest
err := c.ShouldBindJSON(&importData)
if err != nil {
response.FailWithMessage("解析JSON数据失败:"+err.Error(), c)
return
}
// 验证数据格式
if importData.VersionInfo.Name == "" || importData.VersionInfo.Code == "" {
response.FailWithMessage("版本信息格式错误", c)
return
}
// 导入菜单数据
if len(importData.ExportMenu) > 0 {
if err := sysVersionService.ImportMenus(ctx, importData.ExportMenu); err != nil {
global.GVA_LOG.Error("导入菜单失败!", zap.Error(err))
response.FailWithMessage("导入菜单失败: "+err.Error(), c)
return
}
}
// 导入API数据
if len(importData.ExportApi) > 0 {
if err := sysVersionService.ImportApis(importData.ExportApi); err != nil {
global.GVA_LOG.Error("导入API失败!", zap.Error(err))
response.FailWithMessage("导入API失败: "+err.Error(), c)
return
}
}
// 导入字典数据
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{
VersionName: utils.Pointer(importData.VersionInfo.Name),
VersionCode: utils.Pointer(fmt.Sprintf("%s_imported_%s", importData.VersionInfo.Code, time.Now().Format("20060102150405"))),
Description: utils.Pointer(fmt.Sprintf("导入版本: %s", importData.VersionInfo.Description)),
VersionData: utils.Pointer(string(jsonData)),
}
err = sysVersionService.CreateSysVersion(ctx, &version)
if err != nil {
global.GVA_LOG.Error("保存导入记录失败!", zap.Error(err))
// 这里不返回错误,因为数据已经导入成功
}
response.OkWithMessage("导入成功", c)
}

View File

@@ -1,132 +1,103 @@
aliyun-oss:
endpoint: oss-cn-hangzhou.aliyuncs.com
access-key-id: LTAI5tB3Mn5Y7mVo8h3zkf46
access-key-secret: FtuHdFy4NFdVItEiNBnTun3Ddi8BMK
bucket-name: lckt
bucket-url: https://lckt.oss-cn-hangzhou.aliyuncs.com
base-path: lckt
autocode:
web: web/src
root: C:\Users\Administrator\GolandProjects\zb
server: lckt-server
module: git.echol.cn/loser/lckt
ai-path: "https://ai.gin-vue-admin.com/{FUNC}/loser7659/c178e970-6a59-497d-96ed-86fee6b3285a"
aws-s3:
bucket: xxxxx-10005608
region: ap-shanghai
endpoint: ""
secret-id: your-secret-id
secret-key: your-secret-key
base-url: https://gin.vue.admin
path-prefix: git.echol.cn/loser/lckt
s3-force-path-style: false
disable-ssl: false
captcha:
key-long: 4
img-width: 240
img-height: 80
open-captcha: 0
open-captcha-timeout: 3600
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
cors:
mode: strict-whitelist
whitelist:
- allow-origin: example1.com
allow-methods: POST, GET
allow-headers: Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id
expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
allow-credentials: true
- allow-origin: example2.com
allow-methods: GET, POST
allow-headers: content-type
expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
allow-credentials: true
db-list:
- type: ""
alias-name: ""
prefix: ""
port: ""
config: ""
db-name: ""
username: ""
password: ""
path: ""
engine: ""
log-mode: ""
max-idle-conns: 10
max-open-conns: 100
singular: false
log-zap: false
disable: true
disk-list:
- mount-point: /
email:
to: xxx@qq.com
from: xxx@163.com
host: smtp.163.com
secret: xxx
nickname: test
port: 465
is-ssl: true
excel:
dir: ./resource/excel/
hua-wei-obs:
path: you-path
bucket: you-bucket
endpoint: you-endpoint
access-key: you-access-key
secret-key: you-secret-key
# git.echol.cn/loser/lckt Global Configuration
# jwt configuration
jwt:
signing-key: f5a4f443-779c-4734-a2f0-c033dc1f12a2
signing-key: qmPlus
expires-time: 7d
buffer-time: 1d
issuer: qmPlus
local:
path: uploads/file
store-path: uploads/file
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
mongo:
coll: ""
options: ""
database: ""
username: ""
# zap logger configuration
zap:
level: info
format: console
prefix: "[git.echol.cn/loser/lckt]"
director: log
show-line: true
encode-level: LowercaseColorLevelEncoder
stacktrace-key: stacktrace
log-in-console: true
retention-day: -1
# redis configuration
redis:
#是否使用redis集群模式
useCluster: false
#使用集群模式addr和db默认无效
addr: 127.0.0.1:6379
password: ""
auth-source: ""
db: 0
clusterAddrs:
- "172.21.0.3:7000"
- "172.21.0.4:7001"
- "172.21.0.2:7002"
# redis-list configuration
redis-list:
- name: cache # 数据库的名称,注意: name 需要在 redis-list 中唯一
useCluster: false # 是否使用redis集群模式
addr: 127.0.0.1:6379 # 使用集群模式addr和db默认无效
password: ""
db: 0
clusterAddrs:
- "172.21.0.3:7000"
- "172.21.0.4:7001"
- "172.21.0.2:7002"
# mongo configuration
mongo:
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: ""
mssql:
prefix: ""
port: ""
config: ""
db-name: ""
username: ""
password: ""
path: ""
engine: ""
log-mode: ""
max-idle-conns: 10
max-open-conns: 100
singular: false
log-zap: false
- 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
# system configuration
system:
env: local # 修改为public可以关闭路由日志输出
addr: 8888
db-type: mysql
oss-type: aliyun-oss # 控制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: 4
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:
prefix: ""
port: "3366"
@@ -141,87 +112,111 @@ mysql:
max-open-conns: 100
singular: false
log-zap: true
oracle:
prefix: ""
port: ""
config: ""
db-name: ""
username: ""
password: ""
path: ""
engine: ""
log-mode: ""
max-idle-conns: 10
max-open-conns: 100
singular: false
log-zap: false
# pgsql connect configuration
# 未初始化之前请勿手动修改数据库信息如果一定要手动初始化请看https://gin-vue-admin.com/docs/first_master
pgsql:
prefix: ""
path: ""
port: ""
config: ""
db-name: ""
username: ""
password: ""
path: ""
engine: ""
log-mode: ""
max-idle-conns: 10
max-open-conns: 100
singular: false
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
# autocode configuration
autocode:
web: web/src
root: "" # root 自动适配项目根目录, 请不要手动配置,他会在项目加载的时候识别出根路径
server: /lckt-server
module: 'git.echol.cn/loser/lckt'
ai-path: "https://ai.gin-vue-admin.com/{FUNC}/loser7659/c178e970-6a59-497d-96ed-86fee6b3285a" # AI服务路径
# qiniu configuration (请自行七牛申请对应的 公钥 私钥 bucket 和 域名地址)
qiniu:
zone: ZoneHuaDong
bucket: ""
img-path: ""
use-https: false
access-key: ""
secret-key: ""
use-https: false
use-cdn-domains: false
redis:
name: "hw"
addr: 120.46.165.63:6379
password: "loser765911"
db: 0
useCluster: false
clusterAddrs:
- 172.21.0.3:7000
- 172.21.0.4:7001
- 172.21.0.2:7002
redis-list:
- name: cache
addr: 120.46.165.63:6379
password: "loser765911"
db: 1
useCluster: false
clusterAddrs:
- 172.21.0.3:7000
- 172.21.0.4:7001
- 172.21.0.2:7002
sqlite:
prefix: ""
port: ""
config: ""
db-name: ""
username: ""
password: ""
path: ""
engine: ""
log-mode: ""
max-idle-conns: 10
max-open-conns: 100
singular: false
log-zap: false
system:
db-type: mysql
oss-type: aliyun-oss
router-prefix: ""
addr: 8888
iplimit-count: 15000
iplimit-time: 3600
use-multipoint: false
use-redis: true
use-mongo: false
use-strict-auth: 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: oss-cn-hangzhou.aliyuncs.com
access-key-id: LTAI5tB3Mn5Y7mVo8h3zkf46
access-key-secret: FtuHdFy4NFdVItEiNBnTun3Ddi8BMK
bucket-name: lckt
bucket-url: https://lckt.oss-cn-hangzhou.aliyuncs.com
base-path: lckt
# tencent cos configuration
tencent-cos:
bucket: xxxxx-10005608
region: ap-shanghai
@@ -229,46 +224,63 @@ tencent-cos:
secret-key: your-secret-key
base-url: https://gin.vue.admin
path-prefix: git.echol.cn/loser/lckt
zap:
level: info
prefix: '[git.echol.cn/loser/lckt]'
format: console
director: log
encode-level: LowercaseColorLevelEncoder
stacktrace-key: stacktrace
show-line: true
log-in-console: true
retention-day: -1
wechat:
app-id: wx3d21df18d7f8f9fc
app-secret: 3ab19e9b6a5e155c25ac6457be650047
token: kjeldcsdz2phfwfxnevsajnzsboho1ev
aes-key: PiqqlGdEblw5Gv1RJ5qcTnhKUjFw9YNkBMAX6CIw6Me
callback: https://api.gin-vue-admin.com/wechat/callback
pay-list:
- type: wxpay
alias-name: wxpay-1
app-id: wx3d21df18d7f8f9fc
mch-id: 1646874753
v3-key: 1a3sd8561d5179Df152D4789aD38wG9s
cert-path: /resource/cert/apiclient_cert.pem
key-path: /resource/cert/apiclient_key.pem
notify-url: http://lckt.hnlc5588.cn/app_order/notify
serial-no: 59A891FB403EC7A1CF2090DB9C8EC704BD43B101
- type: wxpay2
alias-name: wxpay-2
app-id: 2
mch-id: 2
v3-key: 2
cert-path: 2
key-path: 2
notify-url: 2
serial-no: 2
# 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: git.echol.cn/loser/lckt
sms:
access-key-id: your-access-key-id
access-key-secret: your-access-key-secret
sign-name: your-sign-name
template-code: your-template-code
expire-time: 5
# 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
# excel configuration
excel:
dir: ./resource/excel/
# disk usage configuration
disk-list:
- mount-point: "/"
# 跨域配置
# 需要配合 server/initialize/router.go -> `Router.Use(middleware.CorsByRules())` 使用
cors:
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: ''

View File

@@ -4,6 +4,6 @@ type Captcha struct {
KeyLong int `mapstructure:"key-long" json:"key-long" yaml:"key-long"` // 验证码长度
ImgWidth int `mapstructure:"img-width" json:"img-width" yaml:"img-width"` // 验证码宽度
ImgHeight int `mapstructure:"img-height" json:"img-height" yaml:"img-height"` // 验证码高度
OpenCaptcha int `mapstructure:"open-captcha" json:"open-captcha" yaml:"open-captcha"` // 防爆破验证码开启此数0代表每次登录都需要验证码其他数字代表错误密码如3代表错误三次后出现验证码
OpenCaptcha int `mapstructure:"open-captcha" json:"open-captcha" yaml:"open-captcha"` // 防爆破验证码开启此数0代表每次登录都需要验证码其他数字代表错误密码如3代表错误三次后出现验证码
OpenCaptchaTimeOut int `mapstructure:"open-captcha-timeout" json:"open-captcha-timeout" yaml:"open-captcha-timeout"` // 防爆破验证码超时时间单位s(秒)
}

View File

@@ -35,7 +35,9 @@ type Server struct {
// 跨域配置
Cors CORS `mapstructure:"cors" json:"cors" yaml:"cors"`
// Wechat
// MCP配置
MCP MCP `mapstructure:"mcp" json:"mcp" yaml:"mcp"`
Wechat Wechat `mapstructure:"wechat" json:"wechat" yaml:"wechat"`
Pays []Pays `mapstructure:"pay-list" json:"pay-list" yaml:"pay-list"`
SMS SMS `mapstructure:"sms" json:"sms" yaml:"sms"`

View File

@@ -1,8 +1,9 @@
package config
import (
"gorm.io/gorm/logger"
"strings"
"gorm.io/gorm/logger"
)
type DsnProvider interface {
@@ -31,13 +32,13 @@ type GeneralDB struct {
func (c GeneralDB) LogLevel() logger.LogLevel {
switch strings.ToLower(c.LogMode) {
case "silent", "Silent":
case "silent":
return logger.Silent
case "error", "Error":
case "error":
return logger.Error
case "warn", "Warn":
case "warn":
return logger.Warn
case "info", "Info":
case "info":
return logger.Info
default:
return logger.Info

View File

@@ -1,11 +1,12 @@
package config
type Email struct {
To string `mapstructure:"to" json:"to" yaml:"to"` // 收件人:多个以英文逗号分隔 例a@qq.com b@qq.com 正式开发中请把此项目作为参数使用
From string `mapstructure:"from" json:"from" yaml:"from"` // 发件人 你自己要发邮件的邮箱
Host string `mapstructure:"host" json:"host" yaml:"host"` // 服务器地址 例如 smtp.qq.com 请前往QQ或者你要发邮件的邮箱查看其smtp协议
Secret string `mapstructure:"secret" json:"secret" yaml:"secret"` // 密钥 用于登录的密钥 最好不要用邮箱密码 去邮箱smtp申请一个用于登录的密钥
Nickname string `mapstructure:"nickname" json:"nickname" yaml:"nickname"` // 昵称 发件人昵称 通常为自己的邮箱
Port int `mapstructure:"port" json:"port" yaml:"port"` // 端口 请前往QQ或者你要发邮件的邮箱查看其smtp协议 大多为 465
IsSSL bool `mapstructure:"is-ssl" json:"is-ssl" yaml:"is-ssl"` // 是否SSL 是否开启SSL
To string `mapstructure:"to" json:"to" yaml:"to"` // 收件人:多个以英文逗号分隔 例a@qq.com b@qq.com 正式开发中请把此项目作为参数使用
From string `mapstructure:"from" json:"from" yaml:"from"` // 发件人 你自己要发邮件的邮箱
Host string `mapstructure:"host" json:"host" yaml:"host"` // 服务器地址 例如 smtp.qq.com 请前往QQ或者你要发邮件的邮箱查看其smtp协议
Secret string `mapstructure:"secret" json:"secret" yaml:"secret"` // 密钥 用于登录的密钥 最好不要用邮箱密码 去邮箱smtp申请一个用于登录的密钥
Nickname string `mapstructure:"nickname" json:"nickname" yaml:"nickname"` // 昵称 发件人昵称 通常为自己的邮箱
Port int `mapstructure:"port" json:"port" yaml:"port"` // 端口 请前往QQ或者你要发邮件的邮箱查看其smtp协议 大多为 465
IsSSL bool `mapstructure:"is-ssl" json:"is-ssl" yaml:"is-ssl"` // 是否SSL 是否开启SSL
IsLoginAuth bool `mapstructure:"is-loginauth" json:"is-loginauth" yaml:"is-loginauth"` // 是否LoginAuth 是否使用LoginAuth认证方式适用于IBM、微软邮箱服务器等
}

View File

@@ -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
}

9
config/mcp.go Normal file
View File

@@ -0,0 +1,9 @@
package config
type MCP struct {
Name string `mapstructure:"name" json:"name" yaml:"name"` // MCP名称
Version string `mapstructure:"version" json:"version" yaml:"version"` // MCP版本
SSEPath string `mapstructure:"sse_path" json:"sse_path" yaml:"sse_path"` // SSE路径
MessagePath string `mapstructure:"message_path" json:"message_path" yaml:"message_path"` // 消息路径
UrlPrefix string `mapstructure:"url_prefix" json:"url_prefix" yaml:"url_prefix"` // URL前缀
}

View File

@@ -6,17 +6,16 @@ import (
"git.echol.cn/loser/lckt/initialize"
"git.echol.cn/loser/lckt/service/system"
"go.uber.org/zap"
"time"
)
type server interface {
ListenAndServe() error
}
func RunWindowsServer() {
if global.GVA_CONFIG.System.UseMultipoint || global.GVA_CONFIG.System.UseRedis {
func RunServer() {
if global.GVA_CONFIG.System.UseRedis {
// 初始化redis服务
initialize.Redis()
initialize.RedisList()
if global.GVA_CONFIG.System.UseMultipoint {
initialize.RedisList()
}
}
if global.GVA_CONFIG.System.UseMongo {
@@ -33,11 +32,6 @@ func RunWindowsServer() {
Router := initialize.Routers()
address := fmt.Sprintf(":%d", global.GVA_CONFIG.System.Addr)
s := initServer(address, Router)
global.GVA_LOG.Info("server run success on ", zap.String("address", address))
fmt.Println(global.GVA_CONFIG.Pays)
global.GVA_LOG.Error(s.ListenAndServe().Error())
initServer(address, Router, 10*time.Minute, 10*time.Minute)
}

60
core/server_run.go Normal file
View File

@@ -0,0 +1,60 @@
package core
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type server interface {
ListenAndServe() error
Shutdown(context.Context) error
}
// initServer 启动服务并实现优雅关闭
func initServer(address string, router *gin.Engine, readTimeout, writeTimeout time.Duration) {
// 创建服务
srv := &http.Server{
Addr: address,
Handler: router,
ReadTimeout: readTimeout,
WriteTimeout: writeTimeout,
MaxHeaderBytes: 1 << 20,
}
// 在goroutine中启动服务
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
fmt.Printf("listen: %s\n", err)
zap.L().Error("server启动失败", zap.Error(err))
os.Exit(1)
}
}()
// 等待中断信号以优雅地关闭服务器
quit := make(chan os.Signal, 1)
// kill (无参数) 默认发送 syscall.SIGTERM
// kill -2 发送 syscall.SIGINT
// kill -9 发送 syscall.SIGKILL但是无法被捕获所以不需要添加
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
zap.L().Info("关闭WEB服务...")
// 设置5秒的超时时间
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
zap.L().Fatal("WEB服务关闭异常", zap.Error(err))
}
zap.L().Info("WEB服务已关闭")
}

View File

@@ -1,21 +0,0 @@
//go:build windows
// +build windows
package core
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
)
func initServer(address string, router *gin.Engine) server {
return &http.Server{
Addr: address,
Handler: router,
ReadTimeout: 10 * time.Minute,
WriteTimeout: 10 * time.Minute,
MaxHeaderBytes: 1 << 20,
}
}

View File

@@ -3,55 +3,26 @@ package core
import (
"flag"
"fmt"
"git.echol.cn/loser/lckt/core/internal"
"github.com/gin-gonic/gin"
"os"
"path/filepath"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
"git.echol.cn/loser/lckt/core/internal"
"git.echol.cn/loser/lckt/global"
"github.com/fsnotify/fsnotify"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
)
// Viper //
// 优先级: 命令行 > 环境变量 > 默认值
// Author [SliverHorn](https://github.com/SliverHorn)
func Viper(path ...string) *viper.Viper {
var config string
if len(path) == 0 {
flag.StringVar(&config, "c", "", "choose config file.")
flag.Parse()
if config == "" { // 判断命令行参数是否为空
if configEnv := os.Getenv(internal.ConfigEnv); configEnv == "" { // 判断 internal.ConfigEnv 常量存储的环境变量是否为空
switch gin.Mode() {
case gin.DebugMode:
config = internal.ConfigDefaultFile
case gin.ReleaseMode:
config = internal.ConfigReleaseFile
case gin.TestMode:
config = internal.ConfigTestFile
}
fmt.Printf("您正在使用gin模式的%s环境名称,config的路径为%s\n", gin.Mode(), config)
} else { // internal.ConfigEnv 常量存储的环境变量不为空 将值赋值于config
config = configEnv
fmt.Printf("您正在使用%s环境变量,config的路径为%s\n", internal.ConfigEnv, config)
}
} else { // 命令行参数不为空 将值赋值于config
fmt.Printf("您正在使用命令行的-c参数传递的值,config的路径为%s\n", config)
}
} else { // 函数传递的可变参数的第一个值赋值于config
config = path[0]
fmt.Printf("您正在使用func Viper()传递的值,config的路径为%s\n", config)
}
// Viper 配置
func Viper() *viper.Viper {
config := getConfigPath()
v := viper.New()
v.SetConfigFile(config)
v.SetConfigType("yaml")
err := v.ReadInConfig()
if err != nil {
panic(fmt.Errorf("Fatal error config file: %s \n", err))
panic(fmt.Errorf("fatal error config file: %w", err))
}
v.WatchConfig()
@@ -62,10 +33,44 @@ func Viper(path ...string) *viper.Viper {
}
})
if err = v.Unmarshal(&global.GVA_CONFIG); err != nil {
panic(err)
panic(fmt.Errorf("fatal error unmarshal config: %w", err))
}
// root 适配性 根据root位置去找到对应迁移位置,保证root路径有效
global.GVA_CONFIG.AutoCode.Root, _ = filepath.Abs("..")
return v
}
// getConfigPath 获取配置文件路径, 优先级: 命令行 > 环境变量 > 默认值
func getConfigPath() (config string) {
// `-c` flag parse
flag.StringVar(&config, "c", "", "choose config file.")
flag.Parse()
if config != "" { // 命令行参数不为空 将值赋值于config
fmt.Printf("您正在使用命令行的 '-c' 参数传递的值, config 的路径为 %s\n", config)
return
}
if env := os.Getenv(internal.ConfigEnv); env != "" { // 判断环境变量 GVA_CONFIG
config = env
fmt.Printf("您正在使用 %s 环境变量, config 的路径为 %s\n", internal.ConfigEnv, config)
return
}
switch gin.Mode() { // 根据 gin 模式文件名
case gin.DebugMode:
config = internal.ConfigDebugFile
case gin.ReleaseMode:
config = internal.ConfigReleaseFile
case gin.TestMode:
config = internal.ConfigTestFile
}
fmt.Printf("您正在使用 gin 的 %s 模式运行, config 的路径为 %s\n", gin.Mode(), config)
_, err := os.Stat(config)
if err != nil || os.IsNotExist(err) {
config = internal.ConfigDefaultFile
fmt.Printf("配置文件路径不存在, 使用默认配置文件路径: %s\n", config)
}
return
}

View File

@@ -2,6 +2,7 @@ package global
import (
"fmt"
"github.com/mark3labs/mcp-go/server"
"sync"
"github.com/gin-gonic/gin"
@@ -35,6 +36,7 @@ var (
GVA_Concurrency_Control = &singleflight.Group{}
GVA_ROUTERS gin.RoutesInfo
GVA_ACTIVE_DBNAME *string
GVA_MCP_SERVER *server.MCPServer
BlackCache local_cache.Cache
lock sync.RWMutex
)

12
global/version.go Normal file
View File

@@ -0,0 +1,12 @@
package global
// Version 版本信息
// 目前只有Version正式使用 其余为预留
const (
// Version 当前版本号
Version = "v2.8.5"
// AppName 应用名称
AppName = "LCKT"
// Description 应用描述
Description = "使用gin+vue进行极速开发的全栈开发基础平台"
)

22
go.mod
View File

@@ -16,9 +16,9 @@ require (
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/fvbock/endless v0.0.0-20170109170031-447134032cb6
github.com/gin-contrib/cors v1.7.6
github.com/gin-gonic/gin v1.10.1
github.com/glebarez/sqlite v1.11.0
github.com/go-sql-driver/mysql v1.8.1
@@ -30,7 +30,8 @@ require (
github.com/gorilla/websocket v1.5.3
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/mholt/archiver/v4 v4.0.0-alpha.9
github.com/mark3labs/mcp-go v0.39.0
github.com/mholt/archives v0.1.3
github.com/minio/minio-go/v7 v7.0.84
github.com/mojocn/base64Captcha v1.3.8
github.com/otiai10/copy v1.14.1
@@ -67,18 +68,20 @@ require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/BurntSushi/toml v1.4.0 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/STARRY-S/zip v0.1.0 // indirect
github.com/STARRY-S/zip v0.2.1 // indirect
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 // indirect
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
github.com/alibabacloud-go/debug v1.0.1 // indirect
github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect
github.com/alibabacloud-go/openapi-util v0.1.1 // indirect
github.com/aliyun/credentials-go v1.4.5 // indirect
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/bmatcuk/doublestar/v4 v4.8.0 // indirect
github.com/bodgit/plumbing v1.3.0 // indirect
github.com/bodgit/sevenzip v1.6.0 // indirect
github.com/bodgit/windows v1.0.1 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/bytedance/sonic v1.13.3 // indirect
github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/casbin/govaluate v1.3.0 // indirect
@@ -90,6 +93,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/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/gammazero/toposort v0.1.1 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
@@ -113,6 +117,7 @@ require (
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/invopop/jsonschema v0.13.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.7.2 // indirect
@@ -131,7 +136,9 @@ require (
github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/microsoft/go-mssqldb v1.8.0 // indirect
github.com/mikelolasagasti/xz v1.0.1 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/minlz v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
@@ -139,7 +146,7 @@ require (
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/mozillazg/go-httpheader v0.4.0 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/nwaples/rardecode/v2 v2.0.1 // indirect
github.com/nwaples/rardecode/v2 v2.1.0 // indirect
github.com/otiai10/mint v1.6.3 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
@@ -153,25 +160,28 @@ 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/sorairolake/lzip-go v0.3.5 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.12.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
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/tjfoc/gmsm v1.4.1 // indirect
github.com/tklauser/go-sysconf v0.3.14 // indirect
github.com/tklauser/numcpus v0.9.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.0 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/xuri/efp v0.0.0-20241211021726-c4e992084aa6 // indirect
github.com/xuri/nfp v0.0.0-20250111060730-82a408b9aa71 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.opentelemetry.io/otel v1.29.0 // indirect

46
go.sum
View File

@@ -52,8 +52,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
github.com/STARRY-S/zip v0.1.0 h1:eUER3jKmHKXjv+iy3BekLa+QnNSo1Lqz4eTzYBcGDqo=
github.com/STARRY-S/zip v0.1.0/go.mod h1:qj/mTZkvb3AvfGQ2e775/3AODRvB4peSw8KNMvrM8/I=
github.com/STARRY-S/zip v0.2.1 h1:pWBd4tuSGm3wtpoqRZZ2EAwOmcHK6XFf7bU9qcJXyFg=
github.com/STARRY-S/zip v0.2.1/go.mod h1:xNvshLODWtC4EJ702g7cTYn13G53o1+X9BWnPFpcWV4=
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 h1:7dONQ3WNZ1zy960TmkxJPuwoolZwL7xKtpcM04MBnt4=
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82/go.mod h1:nLnM0KdK1CmygvjpDUO6m1TjSsiQtL61juhNsvV/JVI=
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 h1:eIf+iGJxdU4U9ypaUfbtOWCsZSbTb8AUHvyPrxu6mAA=
@@ -108,10 +108,12 @@ github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmP
github.com/aliyun/credentials-go v1.3.10/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
github.com/aliyun/credentials-go v1.4.5 h1:O76WYKgdy1oQYYiJkERjlA2dxGuvLRrzuO2ScrtGWSk=
github.com/aliyun/credentials-go v1.4.5/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 h1:8PmGpDEZl9yDpcdEr6Odf23feCxK3LNUNMxjXg41pZQ=
github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk=
github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bmatcuk/doublestar/v4 v4.8.0 h1:DSXtrypQddoug1459viM9X9D3dp1Z7993fw36I2kNcQ=
github.com/bmatcuk/doublestar/v4 v4.8.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
@@ -125,6 +127,8 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
@@ -167,6 +171,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.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -181,8 +189,6 @@ github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBv
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
github.com/gammazero/toposort v0.1.1 h1:OivGxsWxF3U3+U80VoLJ+f50HcPU1MIqE1JlKzoJ2Eg=
github.com/gammazero/toposort v0.1.1/go.mod h1:H2cozTnNpMw0hg2VHAYsAxmkHXBYroNangj2NTBQDvw=
github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY=
github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
@@ -325,6 +331,8 @@ github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible h1:XQVXdk+WAJ
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible/go.mod h1:l7VUhRbTKCzdOacdT4oWCwATKyvZqUOlOqr0Ous3k4s=
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
@@ -342,6 +350,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=
@@ -394,20 +403,26 @@ github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a
github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mark3labs/mcp-go v0.39.0 h1:dQwaOADzUJ1ROslEJB8QV+4u/8XQCqH9ylB//x8cCEQ=
github.com/mark3labs/mcp-go v0.39.0/go.mod h1:T7tUa2jO6MavG+3P25Oy/jR7iCeJPHImCZHRymCn39g=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mholt/archiver/v4 v4.0.0-alpha.9 h1:EZgAsW6DsuawxDgTtIdjCUBa2TQ6AOe9pnCidofSRtE=
github.com/mholt/archiver/v4 v4.0.0-alpha.9/go.mod h1:5D3uct315OMkMRXKwEuMB+wQi/2m5NQngKDmApqwVlo=
github.com/mholt/archives v0.1.3 h1:aEAaOtNra78G+TvV5ohmXrJOAzf++dIlYeDW3N9q458=
github.com/mholt/archives v0.1.3/go.mod h1:LUCGp++/IbV/I0Xq4SzcIR6uwgeh2yjnQWamjRQfLTU=
github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA=
github.com/microsoft/go-mssqldb v1.8.0 h1:7cyZ/AT7ycDsEoWPIXibd+aVKFtteUNhDGf3aobP+tw=
github.com/microsoft/go-mssqldb v1.8.0/go.mod h1:6znkekS3T2vp0waiMhen4GPU1BiAsrP+iXHcE7a7rFo=
github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0=
github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.84 h1:D1HVmAF8JF8Bpi6IU4V9vIEj+8pc+xU88EWMs2yed0E=
github.com/minio/minio-go/v7 v7.0.84/go.mod h1:57YXpvc5l3rjPdhqNrDsvVlY0qPI6UTk1bflAe+9doY=
github.com/minio/minlz v1.0.0 h1:Kj7aJZ1//LlTP1DM8Jm7lNKvvJS2m74gyyXXn3+uJWQ=
github.com/minio/minlz v1.0.0/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
@@ -432,8 +447,8 @@ github.com/mozillazg/go-httpheader v0.4.0/go.mod h1:PuT8h0pw6efvp8ZeUec1Rs7dwjK0
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nwaples/rardecode/v2 v2.0.1 h1:3MN6/R+Y4c7e+21U3yhWuUcf72sYmcmr6jtiuAVSH1A=
github.com/nwaples/rardecode/v2 v2.0.1/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY=
github.com/nwaples/rardecode/v2 v2.1.0 h1:JQl9ZoBPDy+nIZGb1mx8+anfHp/LV3NE2MjMiv0ct/U=
github.com/nwaples/rardecode/v2 v2.1.0/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8=
github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I=
github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs=
@@ -493,6 +508,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/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
@@ -539,8 +556,8 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.563/go.mod
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.563/go.mod h1:uom4Nvi9W+Qkom0exYiJ9VWJjXwyxtPYTkKkaLMlfE0=
github.com/tencentyun/cos-go-sdk-v5 v0.7.60 h1:/e/tmvRmfKexr/QQIBzWhOkZWsmY3EK72NrI6G/Tv0o=
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/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
@@ -557,6 +574,8 @@ github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/unrolled/secure v1.17.0 h1:Io7ifFgo99Bnh0J7+Q+qcMzWM6kaDPCA5FroFZEdbWU=
github.com/unrolled/secure v1.17.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
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=
@@ -573,6 +592,8 @@ github.com/xuri/nfp v0.0.0-20250111060730-82a408b9aa71 h1:hOh7aVDrvGJRxzXrQbDY8E
github.com/xuri/nfp v0.0.0-20250111060730-82a408b9aa71/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -915,6 +936,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=

View File

@@ -53,7 +53,7 @@ func (e *ensureTables) MigrateTable(ctx context.Context) (context.Context, error
sysModel.Condition{},
sysModel.JoinTemplate{},
sysModel.SysParams{},
sysModel.SysVersion{},
adapter.CasbinRule{},
example.ExaFile{},

View File

@@ -1,7 +1,6 @@
package initialize
import (
"git.echol.cn/loser/lckt/model/app"
"os"
"git.echol.cn/loser/lckt/global"
@@ -57,15 +56,13 @@ func RegisterTables() {
system.Condition{},
system.JoinTemplate{},
system.SysParams{},
system.SysVersion{},
example.ExaFile{},
example.ExaCustomer{},
example.ExaFileChunk{},
example.ExaFileUploadAndDownload{},
example.ExaAttachmentCategory{},
app.Banner{},
app.Order{},
)
if err != nil {
global.GVA_LOG.Error("register table failed", zap.Error(err))

View File

@@ -2,24 +2,11 @@ package initialize
import (
"git.echol.cn/loser/lckt/global"
"git.echol.cn/loser/lckt/model/article"
"git.echol.cn/loser/lckt/model/bot"
"git.echol.cn/loser/lckt/model/category"
"git.echol.cn/loser/lckt/model/notice"
"git.echol.cn/loser/lckt/model/user"
"git.echol.cn/loser/lckt/model/vip"
)
func bizModel() error {
db := global.GVA_DB
err := db.AutoMigrate(
category.Category{},
bot.Bot{},
article.Article{},
user.User{},
vip.Vip{},
notice.Notice{},
)
err := db.AutoMigrate()
if err != nil {
return err
}

View File

@@ -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")

View File

@@ -12,18 +12,32 @@ import (
// GormMysql 初始化Mysql数据库
// Author [piexlmax](https://github.com/piexlmax)
// Author [SliverHorn](https://github.com/SliverHorn)
// Author [ByteZhou-2018](https://github.com/ByteZhou-2018)
func GormMysql() *gorm.DB {
m := global.GVA_CONFIG.Mysql
return initMysqlDatabase(m)
}
// GormMysqlByConfig 通过传入配置初始化Mysql数据库
func GormMysqlByConfig(m config.Mysql) *gorm.DB {
return initMysqlDatabase(m)
}
// initMysqlDatabase 初始化Mysql数据库的辅助函数
func initMysqlDatabase(m config.Mysql) *gorm.DB {
if m.Dbname == "" {
return nil
}
mysqlConfig := mysql.Config{
DSN: m.Dsn(), // DSN data source name
DefaultStringSize: 191, // string 类型字段的默认长度
SkipInitializeWithVersion: false, // 根据版本自动配置
}
if db, err := gorm.Open(mysql.New(mysqlConfig), internal.Gorm.Config(m.Prefix, m.Singular)); err != nil {
return 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)
sqlDB, _ := db.DB()
@@ -32,24 +46,3 @@ func GormMysql() *gorm.DB {
return db
}
}
// GormMysqlByConfig 初始化Mysql数据库用过传入配置
func GormMysqlByConfig(m config.Mysql) *gorm.DB {
if m.Dbname == "" {
return nil
}
mysqlConfig := mysql.Config{
DSN: m.Dsn(), // DSN data source name
DefaultStringSize: 191, // string 类型字段的默认长度
SkipInitializeWithVersion: false, // 根据版本自动配置
}
if db, err := gorm.Open(mysql.New(mysqlConfig), internal.Gorm.Config(m.Prefix, m.Singular)); err != nil {
panic(err)
} else {
db.InstanceSet("gorm:table_options", "ENGINE=InnoDB")
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(m.MaxIdleConns)
sqlDB.SetMaxOpenConns(m.MaxOpenConns)
return db
}
}

View File

@@ -1,47 +1,32 @@
package initialize
import (
//"github.com/dzwvip/oracle"
"git.echol.cn/loser/lckt/config"
"git.echol.cn/loser/lckt/global"
"git.echol.cn/loser/lckt/initialize/internal"
//_ "github.com/godror/godror"
"gorm.io/driver/mysql"
oracle "github.com/dzwvip/gorm-oracle"
"gorm.io/gorm"
)
// GormOracle 初始化oracle数据库
// 如果需要Oracle库 放开import里的注释 把下方 mysql.Config 改为 oracle.Config ; mysql.New 改为 oracle.New
func GormOracle() *gorm.DB {
m := global.GVA_CONFIG.Oracle
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 {
panic(err)
} else {
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(m.MaxIdleConns)
sqlDB.SetMaxOpenConns(m.MaxOpenConns)
return db
}
return initOracleDatabase(m)
}
// GormOracleByConfig 初始化Oracle数据库用过传入配置
func GormOracleByConfig(m config.Oracle) *gorm.DB {
return initOracleDatabase(m)
}
// initOracleDatabase 初始化Oracle数据库的辅助函数
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()

View File

@@ -13,25 +13,16 @@ import (
// Author [SliverHorn](https://github.com/SliverHorn)
func GormPgSql() *gorm.DB {
p := global.GVA_CONFIG.Pgsql
if p.Dbname == "" {
return nil
}
pgsqlConfig := postgres.Config{
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 {
return nil
} else {
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(p.MaxIdleConns)
sqlDB.SetMaxOpenConns(p.MaxOpenConns)
return db
}
return initPgSqlDatabase(p)
}
// GormPgSqlByConfig 初始化 Postgresql 数据库 通过参数
// GormPgSqlByConfig 初始化 Postgresql 数据库 通过指定参数
func GormPgSqlByConfig(p config.Pgsql) *gorm.DB {
return initPgSqlDatabase(p)
}
// initPgSqlDatabase 初始化 Postgresql 数据库的辅助函数
func initPgSqlDatabase(p config.Pgsql) *gorm.DB {
if p.Dbname == "" {
return nil
}
@@ -39,7 +30,9 @@ func GormPgSqlByConfig(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()

View File

@@ -11,27 +11,23 @@ import (
// GormSqlite 初始化Sqlite数据库
func GormSqlite() *gorm.DB {
s := global.GVA_CONFIG.Sqlite
if s.Dbname == "" {
return nil
}
if db, err := gorm.Open(sqlite.Open(s.Dsn()), internal.Gorm.Config(s.Prefix, s.Singular)); err != nil {
panic(err)
} else {
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(s.MaxIdleConns)
sqlDB.SetMaxOpenConns(s.MaxOpenConns)
return db
}
return initSqliteDatabase(s)
}
// GormSqliteByConfig 初始化Sqlite数据库用过传入配置
func GormSqliteByConfig(s config.Sqlite) *gorm.DB {
return initSqliteDatabase(s)
}
// initSqliteDatabase 初始化Sqlite数据库辅助函数
func initSqliteDatabase(s config.Sqlite) *gorm.DB {
if s.Dbname == "" {
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()

15
initialize/init.go Normal file
View File

@@ -0,0 +1,15 @@
// 假设这是初始化逻辑的一部分
package initialize
import (
"git.echol.cn/loser/lckt/utils"
)
// 初始化全局函数
func SetupHandlers() {
// 注册系统重载处理函数
utils.GlobalSystemEvents.RegisterReloadHandler(func() error {
return Reload()
})
}

View File

@@ -1,12 +1,12 @@
package internal
import (
"time"
"git.echol.cn/loser/lckt/config"
"git.echol.cn/loser/lckt/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,
}

25
initialize/mcp.go Normal file
View File

@@ -0,0 +1,25 @@
package initialize
import (
"git.echol.cn/loser/lckt/global"
mcpTool "git.echol.cn/loser/lckt/mcp"
"github.com/mark3labs/mcp-go/server"
)
func McpRun() *server.SSEServer {
config := global.GVA_CONFIG.MCP
s := server.NewMCPServer(
config.Name,
config.Version,
)
global.GVA_MCP_SERVER = s
mcpTool.RegisterAllTools(s)
return server.NewSSEServer(s,
server.WithSSEEndpoint(config.SSEPath),
server.WithMessageEndpoint(config.MessagePath),
server.WithBaseURL(config.UrlPrefix))
}

View File

@@ -2,6 +2,7 @@ package initialize
import (
"fmt"
"git.echol.cn/loser/lckt/global"
"git.echol.cn/loser/lckt/plugin/email"
"git.echol.cn/loser/lckt/utils/plugin"
@@ -29,6 +30,7 @@ func bizPluginV1(group ...*gin.RouterGroup) {
global.GVA_CONFIG.Email.Nickname,
global.GVA_CONFIG.Email.Port,
global.GVA_CONFIG.Email.IsSSL,
global.GVA_CONFIG.Email.IsLoginAuth,
))
holder(public, private)
}

45
initialize/reload.go Normal file
View File

@@ -0,0 +1,45 @@
package initialize
import (
"git.echol.cn/loser/lckt/global"
"go.uber.org/zap"
)
// Reload 优雅地重新加载系统配置
func Reload() error {
global.GVA_LOG.Info("正在重新加载系统配置...")
// 重新加载配置文件
if err := global.GVA_VP.ReadInConfig(); err != nil {
global.GVA_LOG.Error("重新读取配置文件失败!", zap.Error(err))
return err
}
// 重新初始化数据库连接
if global.GVA_DB != nil {
db, _ := global.GVA_DB.DB()
err := db.Close()
if err != nil {
global.GVA_LOG.Error("关闭原数据库连接失败!", zap.Error(err))
return err
}
}
// 重新建立数据库连接
global.GVA_DB = Gorm()
// 重新初始化其他配置
OtherInit()
DBList()
if global.GVA_DB != nil {
// 确保数据库表结构是最新的
RegisterTables()
}
// 重新初始化定时任务
Timer()
global.GVA_LOG.Info("系统配置重新加载完成")
return nil
}

View File

@@ -1,19 +1,16 @@
package initialize
import (
"net/http"
"os"
"git.echol.cn/loser/lckt/docs"
"git.echol.cn/loser/lckt/global"
"git.echol.cn/loser/lckt/middleware"
"git.echol.cn/loser/lckt/plugin/customerservice"
"git.echol.cn/loser/lckt/plugin/picturelibrary"
"git.echol.cn/loser/lckt/router"
gc "github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
"net/http"
"os"
"time"
)
type justFilesFilesystem struct {
@@ -39,21 +36,21 @@ func (fs justFilesFilesystem) Open(name string) (http.File, error) {
func Routers() *gin.Engine {
Router := gin.New()
Router.Use(gin.Recovery())
Router.Use(middleware.AllCors())
Router.Use(gc.New(gc.Config{
AllowAllOrigins: true, // 允许所有来源
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"*"}, // 允许所有自定义header
ExposeHeaders: []string{"Content-Length", "Content-Type"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
}))
if gin.Mode() == gin.DebugMode {
Router.Use(gin.Logger())
}
sseServer := McpRun()
// 注册mcp服务
Router.GET(global.GVA_CONFIG.MCP.SSEPath, func(c *gin.Context) {
sseServer.SSEHandler().ServeHTTP(c.Writer, c.Request)
})
Router.POST(global.GVA_CONFIG.MCP.MessagePath, func(c *gin.Context) {
sseServer.MessageHandler().ServeHTTP(c.Writer, c.Request)
})
systemRouter := router.RouterGroupApp.System
exampleRouter := router.RouterGroupApp.Example
appRouter := router.RouterGroupApp.APP
@@ -61,13 +58,13 @@ func Routers() *gin.Engine {
// VUE_APP_BASE_API = /
// VUE_APP_BASE_PATH = http://localhost
// 然后执行打包命令 npm run build。在打开下面3行注释
// Router.Static("/favicon.ico", "./dist/favicon.ico")
// Router.StaticFile("/favicon.ico", "./dist/favicon.ico")
// Router.Static("/assets", "./dist/assets") // dist里面的静态资源
// Router.StaticFile("/", "./dist/index.html") // 前端网页入口页面
Router.StaticFS(global.GVA_CONFIG.Local.StorePath, justFilesFilesystem{http.Dir(global.GVA_CONFIG.Local.StorePath)}) // Router.Use(middleware.LoadTls()) // 如果需要使用https 请打开此中间件 然后前往 core/server.go 将启动模式 更变为 Router.RunTLS("端口","你的cre/pem文件","你的key文件")
// 跨域,如需跨域可以打开下面的注释
// Router.Use(middleware.AllCors()) // 直接放行全部跨域请求
// Router.Use(middleware.Cors()) // 直接放行全部跨域请求
// Router.Use(middleware.CorsByRules()) // 按照配置的规则放行跨域请求
// global.GVA_LOG.Info("use middleware cors")
docs.SwaggerInfo.BasePath = global.GVA_CONFIG.System.RouterPrefix
@@ -76,13 +73,13 @@ func Routers() *gin.Engine {
// 方便统一添加路由组前缀 多服务器上线使用
PublicGroup := Router.Group(global.GVA_CONFIG.System.RouterPrefix)
PublicGroup.Use(middleware.AllCors()) // 直接放行全部跨域请求
PublicGroup.Use(middleware.Cors()) // 直接放行全部跨域请求
PrivateGroup := Router.Group(global.GVA_CONFIG.System.RouterPrefix)
PrivateGroup.Use(middleware.JWTAuth()).Use(middleware.CasbinHandler()).Use(middleware.AllCors())
PrivateGroup.Use(middleware.JWTAuth()).Use(middleware.CasbinHandler()).Use(middleware.Cors())
AppAuthGroup := Router.Group(global.GVA_CONFIG.System.RouterPrefix)
//AppAuthGroup.Use(middleware.AllCors())
AppAuthGroup.Use(middleware.UserJWTAuth()).Use(middleware.AllCors())
AppAuthGroup.Use(middleware.UserJWTAuth()).Use(middleware.Cors())
{
// 健康监测
PublicGroup.GET("/health", func(c *gin.Context) {
@@ -100,6 +97,7 @@ func Routers() *gin.Engine {
systemRouter.InitUserRouter(PrivateGroup) // 注册用户路由
systemRouter.InitMenuRouter(PrivateGroup) // 注册menu路由
systemRouter.InitSystemRouter(PrivateGroup) // system相关路由
systemRouter.InitSysVersionRouter(PrivateGroup) // 发版相关路由
systemRouter.InitCasbinRouter(PrivateGroup) // 权限相关路由
systemRouter.InitAutoCodeRouter(PrivateGroup, PublicGroup) // 创建自动化代码
systemRouter.InitAuthorityRouter(PrivateGroup) // 注册角色路由
@@ -128,9 +126,6 @@ func Routers() *gin.Engine {
// 注册业务路由
initBizRouter(PrivateGroup, PublicGroup, AppAuthGroup)
PluginInit(PublicGroup, customerservice.CreateCustomerServicePlug())
PluginInit(PrivateGroup, picturelibrary.CreatePictureLibraryPlug())
global.GVA_ROUTERS = Router.Routes()
global.GVA_LOG.Info("router register success")

View File

@@ -5,10 +5,12 @@ import (
"github.com/gin-gonic/gin"
)
// 占位方法保证文件可以正确加载避免go空变量检测报错请勿删除。
func holder(routers ...*gin.RouterGroup) {
_ = routers
_ = router.RouterGroupApp
}
func initBizRouter(routers ...*gin.RouterGroup) {
privateGroup := routers[0]
publicGroup := routers[1]

20
main.go
View File

@@ -4,7 +4,6 @@ import (
"git.echol.cn/loser/lckt/core"
"git.echol.cn/loser/lckt/global"
"git.echol.cn/loser/lckt/initialize"
"git.echol.cn/loser/lckt/utils/wechat"
_ "go.uber.org/automaxprocs"
"go.uber.org/zap"
)
@@ -22,13 +21,22 @@ import (
// @Tag.Description 用户
// @title Gin-Vue-Admin Swagger API接口文档
// @version v2.8.0
// @version v2.8.5
// @description 使用gin+vue进行极速开发的全栈开发基础平台
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name x-token
// @BasePath /
func main() {
// 初始化系统
initializeSystem()
// 运行服务器
core.RunServer()
}
// initializeSystem 初始化系统所有组件
// 提取为单独函数以便于系统重载时调用
func initializeSystem() {
global.GVA_VP = core.Viper() // 初始化Viper
initialize.OtherInit()
global.GVA_LOG = core.Zap() // 初始化zap日志库
@@ -36,14 +44,8 @@ func main() {
global.GVA_DB = initialize.Gorm() // gorm连接数据库
initialize.Timer()
initialize.DBList()
initialize.SetupHandlers() // 注册全局函数
if global.GVA_DB != nil {
initialize.RegisterTables() // 初始化表
// 程序结束前关闭数据库链接
db, _ := global.GVA_DB.DB()
defer db.Close()
}
wechat.InitWeOfficial() // 初始化微信公众号SDK
wechat.InitWechatPay() // 初始化微信支付SDK
core.RunWindowsServer()
}

201
mcp/api_creator.go Normal file
View File

@@ -0,0 +1,201 @@
package mcpTool
import (
"context"
"encoding/json"
"errors"
"fmt"
"git.echol.cn/loser/lckt/global"
"git.echol.cn/loser/lckt/model/system"
"git.echol.cn/loser/lckt/service"
"github.com/mark3labs/mcp-go/mcp"
"go.uber.org/zap"
)
// 注册工具
func init() {
RegisterTool(&ApiCreator{})
}
// ApiCreateRequest API创建请求结构
type ApiCreateRequest struct {
Path string `json:"path"` // API路径
Description string `json:"description"` // API中文描述
ApiGroup string `json:"apiGroup"` // API组
Method string `json:"method"` // HTTP方法
}
// ApiCreateResponse API创建响应结构
type ApiCreateResponse struct {
Success bool `json:"success"`
Message string `json:"message"`
ApiID uint `json:"apiId"`
Path string `json:"path"`
Method string `json:"method"`
}
// ApiCreator API创建工具
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工具且needCreatedModules=true时模块创建会自动生成API权限不应调用此工具
- 仅在以下情况使用1) 单独创建API不涉及模块创建2) AI编辑器自动添加API3) router下的文件产生路径变化时`),
mcp.WithString("path",
mcp.Required(),
mcp.Description("API路径/user/create"),
),
mcp.WithString("description",
mcp.Required(),
mcp.Description("API中文描述创建用户"),
),
mcp.WithString("apiGroup",
mcp.Required(),
mcp.Description("API组名称用于分类管理用户管理"),
),
mcp.WithString("method",
mcp.Description("HTTP方法"),
mcp.DefaultString("POST"),
),
mcp.WithString("apis",
mcp.Description("批量创建API的JSON字符串格式[{\"path\":\"/user/create\",\"description\":\"创建用户\",\"apiGroup\":\"用户管理\",\"method\":\"POST\"}]"),
),
)
}
// Handle 处理API创建请求
func (a *ApiCreator) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
args := request.GetArguments()
var apis []ApiCreateRequest
// 检查是否是批量创建
if apisStr, ok := args["apis"].(string); ok && apisStr != "" {
if err := json.Unmarshal([]byte(apisStr), &apis); err != nil {
return nil, fmt.Errorf("apis 参数格式错误: %v", err)
}
} else {
// 单个API创建
path, ok := args["path"].(string)
if !ok || path == "" {
return nil, errors.New("path 参数是必需的")
}
description, ok := args["description"].(string)
if !ok || description == "" {
return nil, errors.New("description 参数是必需的")
}
apiGroup, ok := args["apiGroup"].(string)
if !ok || apiGroup == "" {
return nil, errors.New("apiGroup 参数是必需的")
}
method := "POST"
if val, ok := args["method"].(string); ok && val != "" {
method = val
}
apis = append(apis, ApiCreateRequest{
Path: path,
Description: description,
ApiGroup: apiGroup,
Method: method,
})
}
if len(apis) == 0 {
return nil, errors.New("没有要创建的API")
}
// 创建API记录
apiService := service.ServiceGroupApp.SystemServiceGroup.ApiService
var responses []ApiCreateResponse
successCount := 0
for _, apiReq := range apis {
api := system.SysApi{
Path: apiReq.Path,
Description: apiReq.Description,
ApiGroup: apiReq.ApiGroup,
Method: apiReq.Method,
}
err := apiService.CreateApi(api)
if err != nil {
global.GVA_LOG.Warn("创建API失败",
zap.String("path", apiReq.Path),
zap.String("method", apiReq.Method),
zap.Error(err))
responses = append(responses, ApiCreateResponse{
Success: false,
Message: fmt.Sprintf("创建API失败: %v", err),
Path: apiReq.Path,
Method: apiReq.Method,
})
} else {
// 获取创建的API ID
var createdApi system.SysApi
err = global.GVA_DB.Where("path = ? AND method = ?", apiReq.Path, apiReq.Method).First(&createdApi).Error
if err != nil {
global.GVA_LOG.Warn("获取创建的API ID失败", zap.Error(err))
}
responses = append(responses, ApiCreateResponse{
Success: true,
Message: fmt.Sprintf("成功创建API %s %s", apiReq.Method, apiReq.Path),
ApiID: createdApi.ID,
Path: apiReq.Path,
Method: apiReq.Method,
})
successCount++
}
}
// 构建总体响应
var resultMessage string
if len(apis) == 1 {
resultMessage = responses[0].Message
} else {
resultMessage = fmt.Sprintf("批量创建API完成成功 %d 个,失败 %d 个", successCount, len(apis)-successCount)
}
result := map[string]interface{}{
"success": successCount > 0,
"message": resultMessage,
"totalCount": len(apis),
"successCount": successCount,
"failedCount": len(apis) - successCount,
"details": responses,
}
resultJSON, err := json.MarshalIndent(result, "", " ")
if err != nil {
return nil, fmt.Errorf("序列化结果失败: %v", err)
}
// 添加权限分配提醒
permissionReminder := "\n\n⚠ 重要提醒:\n" +
"API创建完成后请前往【系统管理】->【角色管理】中为相关角色分配新创建的API权限" +
"以确保用户能够正常访问新接口。\n" +
"具体步骤:\n" +
"1. 进入角色管理页面\n" +
"2. 选择需要授权的角色\n" +
"3. 在API权限中勾选新创建的API接口\n" +
"4. 保存权限配置"
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: fmt.Sprintf("API创建结果\n\n%s%s", string(resultJSON), permissionReminder),
},
},
}, nil
}

165
mcp/api_lister.go Normal file
View File

@@ -0,0 +1,165 @@
package mcpTool
import (
"context"
"encoding/json"
"fmt"
"git.echol.cn/loser/lckt/global"
"git.echol.cn/loser/lckt/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
}

39
mcp/client/client.go Normal file
View File

@@ -0,0 +1,39 @@
package client
import (
"context"
"errors"
mcpClient "github.com/mark3labs/mcp-go/client"
"github.com/mark3labs/mcp-go/mcp"
)
func NewClient(baseUrl, name, version, serverName string) (*mcpClient.Client, error) {
client, err := mcpClient.NewSSEMCPClient(baseUrl)
if err != nil {
return nil, err
}
ctx := context.Background()
// 启动client
if err := client.Start(ctx); err != nil {
return nil, err
}
// 初始化
initRequest := mcp.InitializeRequest{}
initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
initRequest.Params.ClientInfo = mcp.Implementation{
Name: name,
Version: version,
}
result, err := client.Initialize(ctx, initRequest)
if err != nil {
return nil, err
}
if result.ServerInfo.Name != serverName {
return nil, errors.New("server name mismatch")
}
return client, nil
}

132
mcp/client/client_test.go Normal file
View File

@@ -0,0 +1,132 @@
package client
import (
"context"
"fmt"
"github.com/mark3labs/mcp-go/mcp"
"testing"
)
// 测试 MCP 客户端连接
func TestMcpClientConnection(t *testing.T) {
c, err := NewClient("http://localhost:8888/sse", "test-client", "1.0.0", "gin-vue-admin MCP服务")
defer c.Close()
if err != nil {
t.Fatalf(err.Error())
}
}
func TestTools(t *testing.T) {
t.Run("currentTime", func(t *testing.T) {
c, err := NewClient("http://localhost:8888/sse", "test-client", "1.0.0", "gin-vue-admin MCP服务")
defer c.Close()
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
request := mcp.CallToolRequest{}
request.Params.Name = "currentTime"
request.Params.Arguments = map[string]interface{}{
"timezone": "UTC+8",
}
result, err := c.CallTool(ctx, request)
if err != nil {
t.Fatalf("方法调用错误: %v", err)
}
if len(result.Content) != 1 {
t.Errorf("应该有且仅返回1条信息但是现在有 %d", len(result.Content))
}
if content, ok := result.Content[0].(mcp.TextContent); ok {
t.Logf("成功返回信息%s", content.Text)
} else {
t.Logf("返回为止类型信息%+v", content)
}
})
t.Run("getNickname", func(t *testing.T) {
c, err := NewClient("http://localhost:8888/sse", "test-client", "1.0.0", "gin-vue-admin MCP服务")
defer c.Close()
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
// Initialize
initRequest := mcp.InitializeRequest{}
initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
initRequest.Params.ClientInfo = mcp.Implementation{
Name: "test-client",
Version: "1.0.0",
}
_, err = c.Initialize(ctx, initRequest)
if err != nil {
t.Fatalf("初始化失败: %v", err)
}
request := mcp.CallToolRequest{}
request.Params.Name = "getNickname"
request.Params.Arguments = map[string]interface{}{
"username": "admin",
}
result, err := c.CallTool(ctx, request)
if err != nil {
t.Fatalf("方法调用错误: %v", err)
}
if len(result.Content) != 1 {
t.Errorf("应该有且仅返回1条信息但是现在有 %d", len(result.Content))
}
if content, ok := result.Content[0].(mcp.TextContent); ok {
t.Logf("成功返回信息%s", content.Text)
} else {
t.Logf("返回为止类型信息%+v", content)
}
})
}
func TestGetTools(t *testing.T) {
c, err := NewClient("http://localhost:8888/sse", "test-client", "1.0.0", "gin-vue-admin MCP服务")
defer c.Close()
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
toolsRequest := mcp.ListToolsRequest{}
toolListResult, err := c.ListTools(ctx, toolsRequest)
if err != nil {
t.Fatalf("获取工具列表失败: %v", err)
}
for i := range toolListResult.Tools {
tool := toolListResult.Tools[i]
fmt.Printf("工具名称: %s\n", tool.Name)
fmt.Printf("工具描述: %s\n", tool.Description)
// 打印参数信息
if tool.InputSchema.Properties != nil {
fmt.Println("参数列表:")
for paramName, prop := range tool.InputSchema.Properties {
required := "否"
// 检查参数是否在必填列表中
for _, reqField := range tool.InputSchema.Required {
if reqField == paramName {
required = "是"
break
}
}
fmt.Printf(" - %s (类型: %s, 描述: %s, 必填: %s)\n",
paramName, prop.(map[string]any)["type"], prop.(map[string]any)["description"], required)
}
} else {
fmt.Println("该工具没有参数")
}
fmt.Println("-------------------")
}
}

310
mcp/dictionary_generator.go Normal file
View File

@@ -0,0 +1,310 @@
package mcpTool
import (
"context"
"encoding/json"
"errors"
"fmt"
"git.echol.cn/loser/lckt/global"
"git.echol.cn/loser/lckt/model/system"
"git.echol.cn/loser/lckt/service"
"github.com/mark3labs/mcp-go/mcp"
"go.uber.org/zap"
"gorm.io/gorm"
)
func init() {
RegisterTool(&DictionaryOptionsGenerator{})
}
// DictionaryOptionsGenerator 字典选项生成器
type DictionaryOptionsGenerator struct{}
// DictionaryGenerateRequest 字典生成请求
type DictionaryGenerateRequest struct {
DictType string `json:"dictType"` // 字典类型
FieldDesc string `json:"fieldDesc"` // 字段描述
Options []DictionaryOption `json:"options"` // AI生成的字典选项
DictName string `json:"dictName"` // 字典名称(可选)
Description string `json:"description"` // 字典描述(可选)
}
// DictionaryGenerateResponse 字典生成响应
type DictionaryGenerateResponse struct {
Success bool `json:"success"`
Message string `json:"message"`
DictType string `json:"dictType"`
OptionsCount int `json:"optionsCount"`
}
// New 返回工具注册信息
func (d *DictionaryOptionsGenerator) New() mcp.Tool {
return mcp.NewTool("generate_dictionary_options",
mcp.WithDescription("智能生成字典选项并自动创建字典和字典详情"),
mcp.WithString("dictType",
mcp.Required(),
mcp.Description("字典类型,用于标识字典的唯一性"),
),
mcp.WithString("fieldDesc",
mcp.Required(),
mcp.Description("字段描述用于AI理解字段含义"),
),
mcp.WithString("options",
mcp.Required(),
mcp.Description("字典选项JSON字符串格式[{\"label\":\"显示名\",\"value\":\"值\",\"sort\":1}]"),
),
mcp.WithString("dictName",
mcp.Description("字典名称,如果不提供将自动生成"),
),
mcp.WithString("description",
mcp.Description("字典描述"),
),
)
}
// Name 返回工具名称
func (d *DictionaryOptionsGenerator) Name() string {
return "generate_dictionary_options"
}
// Description 返回工具描述
func (d *DictionaryOptionsGenerator) Description() string {
return `字典选项生成工具 - 让AI生成并创建字典选项
此工具允许AI根据字典类型和字段描述生成合适的字典选项并自动创建字典和字典详情。
参数说明:
- dictType: 字典类型(必填)
- fieldDesc: 字段描述(必填)
- options: AI生成的字典选项数组必填
- label: 选项标签
- value: 选项值
- sort: 排序号
- dictName: 字典名称可选默认根据fieldDesc生成
- description: 字典描述(可选)
使用场景:
1. 在创建模块时如果字段需要字典类型AI可以根据字段描述智能生成合适的选项
2. 支持各种业务场景的字典选项生成,如状态、类型、等级等
3. 自动创建字典和字典详情,无需手动配置
示例调用:
{
"dictType": "user_status",
"fieldDesc": "用户状态",
"options": [
{"label": "正常", "value": "1", "sort": 1},
{"label": "禁用", "value": "0", "sort": 2}
],
"dictName": "用户状态字典",
"description": "用于管理用户账户状态的字典"
}`
}
// InputSchema 返回输入参数的JSON Schema
func (d *DictionaryOptionsGenerator) InputSchema() map[string]interface{} {
return map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"dictType": map[string]interface{}{
"type": "string",
"description": "字典类型,用于标识字典的唯一性",
},
"fieldDesc": map[string]interface{}{
"type": "string",
"description": "字段描述,用于生成字典名称和理解字典用途",
},
"options": map[string]interface{}{
"type": "array",
"description": "AI生成的字典选项数组",
"items": map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"label": map[string]interface{}{
"type": "string",
"description": "选项标签,显示给用户的文本",
},
"value": map[string]interface{}{
"type": "string",
"description": "选项值,存储在数据库中的值",
},
"sort": map[string]interface{}{
"type": "integer",
"description": "排序号,用于控制选项显示顺序",
},
},
"required": []string{"label", "value", "sort"},
},
},
"dictName": map[string]interface{}{
"type": "string",
"description": "字典名称可选默认根据fieldDesc生成",
},
"description": map[string]interface{}{
"type": "string",
"description": "字典描述,可选",
},
},
"required": []string{"dictType", "fieldDesc", "options"},
}
}
// Handle 处理工具调用
func (d *DictionaryOptionsGenerator) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
// 解析请求参数
args := request.GetArguments()
dictType, ok := args["dictType"].(string)
if !ok || dictType == "" {
return nil, errors.New("dictType 参数是必需的")
}
fieldDesc, ok := args["fieldDesc"].(string)
if !ok || fieldDesc == "" {
return nil, errors.New("fieldDesc 参数是必需的")
}
optionsStr, ok := args["options"].(string)
if !ok || optionsStr == "" {
return nil, errors.New("options 参数是必需的")
}
// 解析options JSON字符串
var options []DictionaryOption
if err := json.Unmarshal([]byte(optionsStr), &options); err != nil {
return nil, fmt.Errorf("options 参数格式错误: %v", err)
}
if len(options) == 0 {
return nil, errors.New("options 不能为空")
}
// 可选参数
dictName, _ := args["dictName"].(string)
description, _ := args["description"].(string)
// 构建请求对象
req := &DictionaryGenerateRequest{
DictType: dictType,
FieldDesc: fieldDesc,
Options: options,
DictName: dictName,
Description: description,
}
// 创建字典
response, err := d.createDictionaryWithOptions(ctx, req)
if err != nil {
return nil, err
}
// 构建响应
resultJSON, err := json.MarshalIndent(response, "", " ")
if err != nil {
return nil, fmt.Errorf("序列化结果失败: %v", err)
}
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: fmt.Sprintf("字典选项生成结果:\n\n%s", string(resultJSON)),
},
},
}, nil
}
// createDictionaryWithOptions 创建字典和字典选项
func (d *DictionaryOptionsGenerator) createDictionaryWithOptions(ctx context.Context, req *DictionaryGenerateRequest) (*DictionaryGenerateResponse, error) {
// 检查字典是否已存在
exists, err := d.checkDictionaryExists(req.DictType)
if err != nil {
return nil, fmt.Errorf("检查字典是否存在失败: %v", err)
}
if exists {
return &DictionaryGenerateResponse{
Success: false,
Message: fmt.Sprintf("字典 %s 已存在,跳过创建", req.DictType),
DictType: req.DictType,
OptionsCount: 0,
}, nil
}
// 生成字典名称
dictName := req.DictName
if dictName == "" {
dictName = d.generateDictionaryName(req.DictType, req.FieldDesc)
}
// 创建字典
dictionaryService := service.ServiceGroupApp.SystemServiceGroup.DictionaryService
dictionary := system.SysDictionary{
Name: dictName,
Type: req.DictType,
Status: &[]bool{true}[0], // 默认启用
Desc: req.Description,
}
err = dictionaryService.CreateSysDictionary(dictionary)
if err != nil {
return nil, fmt.Errorf("创建字典失败: %v", err)
}
// 获取刚创建的字典ID
var createdDict system.SysDictionary
err = global.GVA_DB.Where("type = ?", req.DictType).First(&createdDict).Error
if err != nil {
return nil, fmt.Errorf("获取创建的字典失败: %v", err)
}
// 创建字典详情项
dictionaryDetailService := service.ServiceGroupApp.SystemServiceGroup.DictionaryDetailService
successCount := 0
for _, option := range req.Options {
dictionaryDetail := system.SysDictionaryDetail{
Label: option.Label,
Value: option.Value,
Status: &[]bool{true}[0], // 默认启用
Sort: option.Sort,
SysDictionaryID: int(createdDict.ID),
}
err = dictionaryDetailService.CreateSysDictionaryDetail(dictionaryDetail)
if err != nil {
global.GVA_LOG.Warn("创建字典详情项失败", zap.Error(err))
} else {
successCount++
}
}
return &DictionaryGenerateResponse{
Success: true,
Message: fmt.Sprintf("成功创建字典 %s包含 %d 个选项", req.DictType, successCount),
DictType: req.DictType,
OptionsCount: successCount,
}, nil
}
// checkDictionaryExists 检查字典是否存在
func (d *DictionaryOptionsGenerator) checkDictionaryExists(dictType string) (bool, error) {
var dictionary system.SysDictionary
err := global.GVA_DB.Where("type = ?", dictType).First(&dictionary).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return false, nil // 字典不存在
}
return false, err // 其他错误
}
return true, nil // 字典存在
}
// generateDictionaryName 生成字典名称
func (d *DictionaryOptionsGenerator) generateDictionaryName(dictType, fieldDesc string) string {
if fieldDesc != "" {
return fmt.Sprintf("%s字典", fieldDesc)
}
return fmt.Sprintf("%s字典", dictType)
}

234
mcp/dictionary_query.go Normal file
View File

@@ -0,0 +1,234 @@
package mcpTool
import (
"context"
"encoding/json"
"fmt"
"git.echol.cn/loser/lckt/global"
"git.echol.cn/loser/lckt/model/system"
"git.echol.cn/loser/lckt/service"
"github.com/mark3labs/mcp-go/mcp"
"go.uber.org/zap"
"gorm.io/gorm"
)
// 注册工具
func init() {
RegisterTool(&DictionaryQuery{})
}
// DictionaryInfo 字典信息结构
type DictionaryInfo struct {
ID uint `json:"id"`
Name string `json:"name"` // 字典名(中)
Type string `json:"type"` // 字典名(英)
Status *bool `json:"status"` // 状态
Desc string `json:"desc"` // 描述
Details []DictionaryDetailInfo `json:"details"` // 字典详情
}
// DictionaryDetailInfo 字典详情信息结构
type DictionaryDetailInfo struct {
ID uint `json:"id"`
Label string `json:"label"` // 展示值
Value string `json:"value"` // 字典值
Extend string `json:"extend"` // 扩展值
Status *bool `json:"status"` // 启用状态
Sort int `json:"sort"` // 排序标记
}
// DictionaryQueryResponse 字典查询响应结构
type DictionaryQueryResponse struct {
Success bool `json:"success"`
Message string `json:"message"`
Total int `json:"total"`
Dictionaries []DictionaryInfo `json:"dictionaries"`
}
// DictionaryQuery 字典查询工具
type DictionaryQuery struct{}
// New 创建字典查询工具
func (d *DictionaryQuery) New() mcp.Tool {
return mcp.NewTool("query_dictionaries",
mcp.WithDescription("查询系统中所有的字典和字典属性用于AI生成逻辑时了解可用的字典选项"),
mcp.WithString("dictType",
mcp.Description("可选:指定字典类型进行精确查询,如果不提供则返回所有字典"),
),
mcp.WithBoolean("includeDisabled",
mcp.Description("是否包含已禁用的字典和字典项默认为false只返回启用的"),
),
mcp.WithBoolean("detailsOnly",
mcp.Description("是否只返回字典详情信息不包含字典基本信息默认为false"),
),
)
}
// Handle 处理字典查询请求
func (d *DictionaryQuery) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
args := request.GetArguments()
// 获取参数
dictType := ""
if val, ok := args["dictType"].(string); ok {
dictType = val
}
includeDisabled := false
if val, ok := args["includeDisabled"].(bool); ok {
includeDisabled = val
}
detailsOnly := false
if val, ok := args["detailsOnly"].(bool); ok {
detailsOnly = val
}
// 获取字典服务
dictionaryService := service.ServiceGroupApp.SystemServiceGroup.DictionaryService
var dictionaries []DictionaryInfo
var err error
if dictType != "" {
// 查询指定类型的字典
var status *bool
if !includeDisabled {
status = &[]bool{true}[0]
}
sysDictionary, err := dictionaryService.GetSysDictionary(dictType, 0, status)
if err != nil {
global.GVA_LOG.Error("查询字典失败", zap.Error(err))
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.NewTextContent(fmt.Sprintf(`{"success": false, "message": "查询字典失败: %v", "total": 0, "dictionaries": []}`, err.Error())),
},
}, nil
}
// 转换为响应格式
dictInfo := DictionaryInfo{
ID: sysDictionary.ID,
Name: sysDictionary.Name,
Type: sysDictionary.Type,
Status: sysDictionary.Status,
Desc: sysDictionary.Desc,
}
// 获取字典详情
for _, detail := range sysDictionary.SysDictionaryDetails {
if includeDisabled || (detail.Status != nil && *detail.Status) {
dictInfo.Details = append(dictInfo.Details, DictionaryDetailInfo{
ID: detail.ID,
Label: detail.Label,
Value: detail.Value,
Extend: detail.Extend,
Status: detail.Status,
Sort: detail.Sort,
})
}
}
dictionaries = append(dictionaries, dictInfo)
} else {
// 查询所有字典
var sysDictionaries []system.SysDictionary
db := global.GVA_DB.Model(&system.SysDictionary{})
if !includeDisabled {
db = db.Where("status = ?", true)
}
err = db.Preload("SysDictionaryDetails", func(db *gorm.DB) *gorm.DB {
if includeDisabled {
return db.Order("sort")
} else {
return db.Where("status = ?", true).Order("sort")
}
}).Find(&sysDictionaries).Error
if err != nil {
global.GVA_LOG.Error("查询字典列表失败", zap.Error(err))
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.NewTextContent(fmt.Sprintf(`{"success": false, "message": "查询字典列表失败: %v", "total": 0, "dictionaries": []}`, err.Error())),
},
}, nil
}
// 转换为响应格式
for _, dict := range sysDictionaries {
dictInfo := DictionaryInfo{
ID: dict.ID,
Name: dict.Name,
Type: dict.Type,
Status: dict.Status,
Desc: dict.Desc,
}
// 获取字典详情
for _, detail := range dict.SysDictionaryDetails {
if includeDisabled || (detail.Status != nil && *detail.Status) {
dictInfo.Details = append(dictInfo.Details, DictionaryDetailInfo{
ID: detail.ID,
Label: detail.Label,
Value: detail.Value,
Extend: detail.Extend,
Status: detail.Status,
Sort: detail.Sort,
})
}
}
dictionaries = append(dictionaries, dictInfo)
}
}
// 如果只需要详情信息,则提取所有详情
if detailsOnly {
var allDetails []DictionaryDetailInfo
for _, dict := range dictionaries {
allDetails = append(allDetails, dict.Details...)
}
response := map[string]interface{}{
"success": true,
"message": "查询字典详情成功",
"total": len(allDetails),
"details": allDetails,
}
responseJSON, _ := json.Marshal(response)
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.NewTextContent(string(responseJSON)),
},
}, nil
}
// 构建响应
response := DictionaryQueryResponse{
Success: true,
Message: "查询字典成功",
Total: len(dictionaries),
Dictionaries: dictionaries,
}
responseJSON, err := json.Marshal(response)
if err != nil {
global.GVA_LOG.Error("序列化响应失败", zap.Error(err))
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.NewTextContent(fmt.Sprintf(`{"success": false, "message": "序列化响应失败: %v", "total": 0, "dictionaries": []}`, err.Error())),
},
}, nil
}
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.NewTextContent(string(responseJSON)),
},
}, nil
}

31
mcp/enter.go Normal file
View File

@@ -0,0 +1,31 @@
package mcpTool
import (
"context"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
// McpTool 定义了MCP工具必须实现的接口
type McpTool interface {
// Handle 返回工具调用信息
Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error)
// New 返回工具注册信息
New() mcp.Tool
}
// 工具注册表
var toolRegister = make(map[string]McpTool)
// RegisterTool 供工具在init时调用将自己注册到工具注册表中
func RegisterTool(tool McpTool) {
mcpTool := tool.New()
toolRegister[mcpTool.Name] = tool
}
// RegisterAllTools 将所有注册的工具注册到MCP服务中
func RegisterAllTools(mcpServer *server.MCPServer) {
for _, tool := range toolRegister {
mcpServer.AddTool(tool.New(), tool.Handle)
}
}

View File

@@ -0,0 +1,529 @@
# ExecutionPlan 结构体格式说明
## 概述
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"` // 路径信息
}
```
## 子结构体详细说明
### 1. SysAutoCodePackageCreate 结构体
```go
type SysAutoCodePackageCreate struct {
Desc string `json:"desc"` // 描述,如:"用户管理模块"
Label string `json:"label"` // 展示名,如:"用户管理"
Template string `json:"template"` // 模板类型:"plugin" 或 "package"
PackageName string `json:"packageName"` // 包名,如:"user"
Module string `json:"-"` // 模块名(自动填充,无需设置)
}
```
### 2. AutoCode 结构体(核心字段)
```go
type AutoCode struct {
Package string `json:"package"` // 包名
TableName string `json:"tableName"` // 数据库表名
BusinessDB string `json:"businessDB"` // 业务数据库名
StructName string `json:"structName"` // 结构体名称
PackageName string `json:"packageName"` // 文件名称
Description string `json:"description"` // 结构体中文名称
Abbreviation string `json:"abbreviation"` // 结构体简称
HumpPackageName string `json:"humpPackageName"` // 驼峰命名的包名
GvaModel bool `json:"gvaModel"` // 是否使用GVA默认Model
AutoMigrate bool `json:"autoMigrate"` // 是否自动迁移表结构
AutoCreateResource bool `json:"autoCreateResource"` // 是否自动创建资源标识
AutoCreateApiToSql bool `json:"autoCreateApiToSql"` // 是否自动创建API
AutoCreateMenuToSql bool `json:"autoCreateMenuToSql"` // 是否自动创建菜单
AutoCreateBtnAuth bool `json:"autoCreateBtnAuth"` // 是否自动创建按钮权限
OnlyTemplate bool `json:"onlyTemplate"` // 是否只生成模板
IsTree bool `json:"isTree"` // 是否树形结构
TreeJson string `json:"treeJson"` // 树形结构JSON字段
IsAdd bool `json:"isAdd"` // 是否新增
Fields []*AutoCodeField `json:"fields"` // 字段列表
GenerateWeb bool `json:"generateWeb"` // 是否生成前端代码
GenerateServer bool `json:"generateServer"` // 是否生成后端代码
Module string `json:"-"` // 模块(自动填充)
DictTypes []string `json:"-"` // 字典类型(自动填充)
}
```
### 3. AutoCodeField 结构体(字段定义)
```go
type AutoCodeField struct {
FieldName string `json:"fieldName"` // 字段名
FieldDesc string `json:"fieldDesc"` // 字段中文描述
FieldType string `json:"fieldType"` // 字段类型string, int, bool, time.Time等
FieldJson string `json:"fieldJson"` // JSON标签名
DataTypeLong string `json:"dataTypeLong"` // 数据库字段长度
Comment string `json:"comment"` // 数据库字段注释
ColumnName string `json:"columnName"` // 数据库列名
FieldSearchType string `json:"fieldSearchType"` // 搜索类型EQ, LIKE, BETWEEN等
FieldSearchHide bool `json:"fieldSearchHide"` // 是否隐藏查询条件
DictType string `json:"dictType"` // 字典类型
Form bool `json:"form"` // 是否在表单中显示
Table bool `json:"table"` // 是否在表格中显示
Desc bool `json:"desc"` // 是否在详情中显示
Excel bool `json:"excel"` // 是否支持导入导出
Require bool `json:"require"` // 是否必填
DefaultValue string `json:"defaultValue"` // 默认值
ErrorText string `json:"errorText"` // 校验失败提示
Clearable bool `json:"clearable"` // 是否可清空
Sort bool `json:"sort"` // 是否支持排序
PrimaryKey bool `json:"primaryKey"` // 是否主键
DataSource *DataSource `json:"dataSource"` // 数据源配置(用于关联其他表)
CheckDataSource bool `json:"checkDataSource"` // 是否检查数据源
FieldIndexType string `json:"fieldIndexType"` // 索引类型
}
```
### 4. DataSource 结构体(关联表配置)
```go
type DataSource struct {
DBName string `json:"dbName"` // 关联的数据库名称
Table string `json:"table"` // 关联的表名
Label string `json:"label"` // 用于显示的字段名如name、title等
Value string `json:"value"` // 用于存储的值字段名通常是id
Association int `json:"association"` // 关联关系1=一对一2=一对多
HasDeletedAt bool `json:"hasDeletedAt"` // 关联表是否有软删除字段
}
```
## 使用示例
### 示例1创建新包和批量创建多个模块
```json
{
"packageName": "user",
"packageType": "package",
"needCreatedPackage": true,
"needCreatedModules": true,
"packageInfo": {
"desc": "用户管理模块",
"label": "用户管理",
"template": "package",
"packageName": "user"
},
"modulesInfo": [
{
"package": "user",
"tableName": "sys_users",
"businessDB": "",
"structName": "User",
"packageName": "user",
"description": "用户",
"abbreviation": "user",
"humpPackageName": "user",
"gvaModel": true,
"autoMigrate": true,
"autoCreateResource": true,
"autoCreateApiToSql": true,
"autoCreateMenuToSql": true,
"autoCreateBtnAuth": true,
"onlyTemplate": false,
"isTree": false,
"treeJson": "",
"isAdd": true,
"generateWeb": true,
"generateServer": true,
"fields": [
{
"fieldName": "Username",
"fieldDesc": "用户名",
"fieldType": "string",
"fieldJson": "username",
"dataTypeLong": "50",
"comment": "用户名",
"columnName": "username",
"fieldSearchType": "LIKE",
"fieldSearchHide": false,
"dictType": "",
"form": true,
"table": true,
"desc": true,
"excel": true,
"require": true,
"defaultValue": "",
"errorText": "请输入用户名",
"clearable": true,
"sort": false,
"primaryKey": false,
"dataSource": {
"dbName": "gva",
"table": "sys_users",
"label": "username",
"value": "id",
"association": 2,
"hasDeletedAt": true
},
"checkDataSource": true,
"fieldIndexType": ""
},
{
"fieldName": "Email",
"fieldDesc": "邮箱",
"fieldType": "string",
"fieldJson": "email",
"dataTypeLong": "100",
"comment": "邮箱地址",
"columnName": "email",
"fieldSearchType": "EQ",
"fieldSearchHide": false,
"dictType": "",
"form": true,
"table": true,
"desc": true,
"excel": true,
"require": true,
"defaultValue": "",
"errorText": "请输入邮箱",
"clearable": true,
"sort": false,
"primaryKey": false,
"dataSource": null,
"checkDataSource": false,
"fieldIndexType": "index"
}
]
},
{
"package": "user",
"tableName": "user_profiles",
"businessDB": "",
"structName": "UserProfile",
"packageName": "user",
"description": "用户档案",
"abbreviation": "userProfile",
"humpPackageName": "user",
"gvaModel": true,
"autoMigrate": true,
"autoCreateResource": true,
"autoCreateApiToSql": true,
"autoCreateMenuToSql": true,
"autoCreateBtnAuth": true,
"onlyTemplate": false,
"isTree": false,
"treeJson": "",
"isAdd": true,
"generateWeb": true,
"generateServer": true,
"fields": [
{
"fieldName": "UserID",
"fieldDesc": "用户ID",
"fieldType": "int",
"fieldJson": "userId",
"dataTypeLong": "",
"comment": "关联用户ID",
"columnName": "user_id",
"fieldSearchType": "EQ",
"fieldSearchHide": false,
"dictType": "",
"form": true,
"table": true,
"desc": true,
"excel": true,
"require": true,
"defaultValue": "",
"errorText": "请选择用户",
"clearable": true,
"sort": false,
"primaryKey": false,
"dataSource": null,
"checkDataSource": false,
"fieldIndexType": "index"
},
{
"fieldName": "Avatar",
"fieldDesc": "头像",
"fieldType": "string",
"fieldJson": "avatar",
"dataTypeLong": "255",
"comment": "用户头像URL",
"columnName": "avatar",
"fieldSearchType": "",
"fieldSearchHide": true,
"dictType": "",
"form": true,
"table": true,
"desc": true,
"excel": false,
"require": false,
"defaultValue": "",
"errorText": "",
"clearable": true,
"sort": false,
"primaryKey": false,
"dataSource": null,
"checkDataSource": false,
"fieldIndexType": ""
}
]
}
]
}
```
### 示例2仅在现有包中批量创建多个模块
```json
{
"packageName": "system",
"packageType": "package",
"needCreatedPackage": false,
"needCreatedModules": true,
"packageInfo": null,
"modulesInfo": [
{
"package": "system",
"tableName": "sys_roles",
"businessDB": "",
"structName": "Role",
"packageName": "system",
"description": "角色",
"abbreviation": "role",
"humpPackageName": "system",
"gvaModel": true,
"autoMigrate": true,
"autoCreateResource": true,
"autoCreateApiToSql": true,
"autoCreateMenuToSql": true,
"autoCreateBtnAuth": true,
"onlyTemplate": false,
"isTree": false,
"generateWeb": true,
"generateServer": true,
"fields": [
{
"fieldName": "RoleName",
"fieldDesc": "角色名称",
"fieldType": "string",
"fieldJson": "roleName",
"dataTypeLong": "50",
"comment": "角色名称",
"columnName": "role_name",
"fieldSearchType": "LIKE",
"form": true,
"table": true,
"desc": true,
"require": true
}
]
},
{
"package": "system",
"tableName": "sys_permissions",
"businessDB": "",
"structName": "Permission",
"packageName": "system",
"description": "权限",
"abbreviation": "permission",
"humpPackageName": "system",
"gvaModel": true,
"autoMigrate": true,
"autoCreateResource": true,
"autoCreateApiToSql": true,
"autoCreateMenuToSql": true,
"autoCreateBtnAuth": true,
"onlyTemplate": false,
"isTree": false,
"generateWeb": true,
"generateServer": true,
"fields": [
{
"fieldName": "PermissionName",
"fieldDesc": "权限名称",
"fieldType": "string",
"fieldJson": "permissionName",
"dataTypeLong": "100",
"comment": "权限名称",
"columnName": "permission_name",
"fieldSearchType": "LIKE",
"form": true,
"table": true,
"desc": true,
"require": true
},
{
"fieldName": "PermissionCode",
"fieldDesc": "权限代码",
"fieldType": "string",
"fieldJson": "permissionCode",
"dataTypeLong": "50",
"comment": "权限代码",
"columnName": "permission_code",
"fieldSearchType": "=",
"form": true,
"table": true,
"desc": true,
"require": true
}
]
}
]
}
```
### 示例3模块关联关系配置详解
以下示例展示了如何配置不同类型的关联关系:
```json
{
"packageName": "order",
"packageType": "package",
"needCreatedPackage": true,
"needCreatedModules": true,
"packageInfo": {
"desc": "订单管理模块",
"label": "订单管理",
"template": "package",
"packageName": "order"
},
"modulesInfo": [
{
"package": "order",
"tableName": "orders",
"structName": "Order",
"packageName": "order",
"description": "订单",
"abbreviation": "order",
"humpPackageName": "order",
"gvaModel": true,
"autoMigrate": true,
"autoCreateResource": true,
"autoCreateApiToSql": true,
"autoCreateMenuToSql": true,
"autoCreateBtnAuth": true,
"generateWeb": true,
"generateServer": true,
"fields": [
{
"fieldName": "UserID",
"fieldDesc": "下单用户",
"fieldType": "uint",
"fieldJson": "userId",
"columnName": "user_id",
"fieldSearchType": "EQ",
"form": true,
"table": true,
"desc": true,
"require": true,
"dataSource": {
"dbName": "gva",
"table": "sys_users",
"label": "username",
"value": "id",
"association": 2,
"hasDeletedAt": true
},
"checkDataSource": true
},
{
"fieldName": "ProductID",
"fieldDesc": "商品",
"fieldType": "uint",
"fieldJson": "productId",
"columnName": "product_id",
"fieldSearchType": "EQ",
"form": true,
"table": true,
"desc": true,
"require": true,
"dataSource": {
"dbName": "gva",
"table": "products",
"label": "name",
"value": "id",
"association": 2,
"hasDeletedAt": false
},
"checkDataSource": true
},
{
"fieldName": "Status",
"fieldDesc": "订单状态",
"fieldType": "int",
"fieldJson": "status",
"columnName": "status",
"fieldSearchType": "EQ",
"form": true,
"table": true,
"desc": true,
"require": true,
"dictType": "order_status"
}
]
}
]
}
```
## DataSource 配置说明
### 关联关系类型
- **association: 1** - 一对一关联(如用户与用户档案)
- **association: 2** - 一对多关联(如用户与订单)
### 配置要点
1. **dbName**: 通常为 "gva"(默认数据库)
2. **table**: 关联表的实际表名
3. **label**: 用于前端显示的字段(如用户名、商品名称)
4. **value**: 用于存储关联ID的字段通常是 "id"
5. **hasDeletedAt**: 关联表是否支持软删除
6. **checkDataSource**: 建议设为true会验证关联表是否存在
### 常见关联场景
- 用户关联:`{"table": "sys_users", "label": "username", "value": "id"}`
- 角色关联:`{"table": "sys_authorities", "label": "authorityName", "value": "authorityId"}`
- 部门关联:`{"table": "sys_departments", "label": "name", "value": "id"}`
- 分类关联:`{"table": "categories", "label": "name", "value": "id"}`
## 重要注意事项
1. **PackageType**: 只能是 "plugin" 或 "package"
2. **NeedCreatedPackage**: 当为true时PackageInfo必须提供
3. **NeedCreatedModules**: 当为true时ModulesInfo必须提供
4. **字段类型**: FieldType支持的类型包括
- string字符串
- richtext富文本
- int整型
- bool布尔值
- float64浮点型
- time.Time时间
- enum枚举
- picture单图片字符串
- pictures多图片json字符串
- video视频字符串
- file文件json字符串
- jsonJSON
- array数组
5. **搜索类型**: FieldSearchType支持EQ, NE, GT, GE, LT, LE, LIKE, BETWEEN等
6. **索引类型**: FieldIndexType支持index, unique等
7. **GvaModel**: 设置为true时会自动包含ID、CreatedAt、UpdatedAt、DeletedAt字段
8. **关联配置**: 使用dataSource时确保关联表已存在建议开启checkDataSource验证
## 常见错误避免
1. 确保PackageName和ModuleName符合Go语言命名规范
2. 字段名使用大写开头的驼峰命名
3. JSON标签使用小写开头的驼峰命名
4. 数据库列名使用下划线分隔的小写命名
5. 必填字段不要遗漏
6. 字段类型要与实际需求匹配

205
mcp/gag_usage_example.md Normal file
View File

@@ -0,0 +1,205 @@
# GAG工具使用示例 - 带用户确认流程
## 新的工作流程
现在GAG工具支持三步工作流程
1. `analyze` - 分析现有模块信息
2. `confirm` - 请求用户确认创建计划
3. `execute` - 执行创建操作(需要用户确认)
## 使用示例
### 第一步:分析
```json
{
"action": "analyze",
"requirement": "创建一个图书管理功能"
}
```
### 第二步:确认(支持批量创建多个模块)
```json
{
"action": "confirm",
"executionPlan": {
"packageName": "library",
"packageType": "package",
"needCreatedPackage": true,
"needCreatedModules": true,
"packageInfo": {
"desc": "图书管理包",
"label": "图书管理",
"template": "package",
"packageName": "library"
},
"modulesInfo": [
{
"package": "library",
"tableName": "library_books",
"businessDB": "",
"structName": "Book",
"packageName": "library",
"description": "图书信息",
"abbreviation": "book",
"humpPackageName": "Library",
"gvaModel": true,
"autoMigrate": true,
"autoCreateResource": true,
"autoCreateApiToSql": true,
"autoCreateMenuToSql": true,
"autoCreateBtnAuth": true,
"onlyTemplate": false,
"isTree": false,
"treeJson": "",
"isAdd": false,
"generateWeb": true,
"generateServer": true,
"fields": [
{
"fieldName": "title",
"fieldDesc": "书名",
"fieldType": "string",
"fieldJson": "title",
"dataTypeLong": "255",
"comment": "书名",
"columnName": "title",
"fieldSearchType": "LIKE",
"fieldSearchHide": false,
"dictType": "",
"form": true,
"table": true,
"desc": true,
"excel": true,
"require": true,
"defaultValue": "",
"errorText": "请输入书名",
"clearable": true,
"sort": false,
"primaryKey": false,
"dataSource": {},
"checkDataSource": false,
"fieldIndexType": ""
},
{
"fieldName": "AuthorID",
"fieldDesc": "作者",
"fieldType": "uint",
"fieldJson": "authorId",
"dataTypeLong": "",
"comment": "作者ID",
"columnName": "author_id",
"fieldSearchType": "EQ",
"fieldSearchHide": false,
"dictType": "",
"form": true,
"table": true,
"desc": true,
"excel": true,
"require": true,
"defaultValue": "",
"errorText": "请选择作者",
"clearable": true,
"sort": false,
"primaryKey": false,
"dataSource": {
"dbName": "gva",
"table": "library_authors",
"label": "name",
"value": "id",
"association": 2,
"hasDeletedAt": true
},
"checkDataSource": true,
"fieldIndexType": ""
}
]
},
{
"package": "library",
"tableName": "library_authors",
"businessDB": "",
"structName": "Author",
"packageName": "library",
"description": "作者信息",
"abbreviation": "author",
"humpPackageName": "Library",
"gvaModel": true,
"autoMigrate": true,
"autoCreateResource": true,
"autoCreateApiToSql": true,
"autoCreateMenuToSql": true,
"autoCreateBtnAuth": true,
"onlyTemplate": false,
"isTree": false,
"treeJson": "",
"isAdd": false,
"generateWeb": true,
"generateServer": true,
"fields": [
{
"fieldName": "name",
"fieldDesc": "作者姓名",
"fieldType": "string",
"fieldJson": "name",
"dataTypeLong": "100",
"comment": "作者姓名",
"columnName": "name",
"fieldSearchType": "LIKE",
"fieldSearchHide": false,
"dictType": "",
"form": true,
"table": true,
"desc": true,
"excel": true,
"require": true,
"defaultValue": "",
"errorText": "请输入作者姓名",
"clearable": true,
"sort": false,
"primaryKey": false,
"dataSource": {},
"checkDataSource": false,
"fieldIndexType": ""
}
]
}
]
}
}
```
### 第三步:执行(需要确认参数)
```json
{
"action": "execute",
"executionPlan": {
// ... 同上面的executionPlan
},
"packageConfirm": "yes", // 确认创建包
"modulesConfirm": "yes" // 确认创建模块
}
```
## 确认参数说明
- `packageConfirm`: 当`needCreatedPackage`为true时必需
- "yes": 确认创建包
- "no": 取消创建包(停止后续处理)
- `modulesConfirm`: 当`needCreatedModules`为true时必需
- "yes": 确认创建模块
- "no": 取消创建模块(停止后续处理)
## 取消操作的行为
1. 如果用户在`packageConfirm`中选择"no",系统将停止所有后续处理
2. 如果用户在`modulesConfirm`中选择"no",系统将停止模块创建
3. 任何取消操作都会返回相应的取消消息,不会执行任何创建操作
## 注意事项
1. 必须先调用`confirm`来获取确认信息
2.`execute`时必须提供相应的确认参数
3. 确认参数的值必须是"yes"或"no"
4. 如果不需要创建包或模块,则不需要提供对应的确认参数
5. 字段类型支持string字符串,richtext富文本,int整型,bool布尔值,float64浮点型,time.Time时间,enum枚举,picture单图片字符串,pictures多图片json字符串,video视频字符串,file文件json字符串,jsonJSON,array数组

1755
mcp/gva_auto_generate.go Normal file

File diff suppressed because it is too large Load Diff

287
mcp/menu_creator.go Normal file
View File

@@ -0,0 +1,287 @@
package mcpTool
import (
"context"
"encoding/json"
"errors"
"fmt"
"git.echol.cn/loser/lckt/global"
"git.echol.cn/loser/lckt/model/system"
"git.echol.cn/loser/lckt/service"
"github.com/mark3labs/mcp-go/mcp"
"go.uber.org/zap"
)
// 注册工具
func init() {
RegisterTool(&MenuCreator{})
}
// MenuCreateRequest 菜单创建请求结构
type MenuCreateRequest struct {
ParentId uint `json:"parentId"` // 父菜单ID0表示根菜单
Path string `json:"path"` // 路由path
Name string `json:"name"` // 路由name
Hidden bool `json:"hidden"` // 是否在列表隐藏
Component string `json:"component"` // 对应前端文件路径
Sort int `json:"sort"` // 排序标记
Title string `json:"title"` // 菜单名
Icon string `json:"icon"` // 菜单图标
KeepAlive bool `json:"keepAlive"` // 是否缓存
DefaultMenu bool `json:"defaultMenu"` // 是否是基础路由
CloseTab bool `json:"closeTab"` // 自动关闭tab
ActiveName string `json:"activeName"` // 高亮菜单
Parameters []MenuParameterRequest `json:"parameters"` // 路由参数
MenuBtn []MenuButtonRequest `json:"menuBtn"` // 菜单按钮
}
// MenuParameterRequest 菜单参数请求结构
type MenuParameterRequest struct {
Type string `json:"type"` // 参数类型params或query
Key string `json:"key"` // 参数key
Value string `json:"value"` // 参数值
}
// MenuButtonRequest 菜单按钮请求结构
type MenuButtonRequest struct {
Name string `json:"name"` // 按钮名称
Desc string `json:"desc"` // 按钮描述
}
// MenuCreateResponse 菜单创建响应结构
type MenuCreateResponse struct {
Success bool `json:"success"`
Message string `json:"message"`
MenuID uint `json:"menuId"`
Name string `json:"name"`
Path string `json:"path"`
}
// MenuCreator 菜单创建工具
type MenuCreator struct{}
// New 创建菜单创建工具
func (m *MenuCreator) New() mcp.Tool {
return mcp.NewTool("create_menu",
mcp.WithDescription(`创建前端菜单记录用于AI编辑器自动添加前端页面时自动创建对应的菜单项。
**重要限制:**
- 当使用gva_auto_generate工具且needCreatedModules=true时模块创建会自动生成菜单项不应调用此工具
- 仅在以下情况使用1) 单独创建菜单不涉及模块创建2) AI编辑器自动添加前端页面时`),
mcp.WithNumber("parentId",
mcp.Description("父菜单ID0表示根菜单"),
mcp.DefaultNumber(0),
),
mcp.WithString("path",
mcp.Required(),
mcp.Description("路由pathuserList"),
),
mcp.WithString("name",
mcp.Required(),
mcp.Description("路由name用于Vue RouteruserList"),
),
mcp.WithBoolean("hidden",
mcp.Description("是否在菜单列表中隐藏"),
),
mcp.WithString("component",
mcp.Required(),
mcp.Description("对应的前端Vue组件路径view/user/list.vue"),
),
mcp.WithNumber("sort",
mcp.Description("菜单排序号,数字越小越靠前"),
mcp.DefaultNumber(1),
),
mcp.WithString("title",
mcp.Required(),
mcp.Description("菜单显示标题"),
),
mcp.WithString("icon",
mcp.Description("菜单图标名称"),
mcp.DefaultString("menu"),
),
mcp.WithBoolean("keepAlive",
mcp.Description("是否缓存页面"),
),
mcp.WithBoolean("defaultMenu",
mcp.Description("是否是基础路由"),
),
mcp.WithBoolean("closeTab",
mcp.Description("是否自动关闭tab"),
),
mcp.WithString("activeName",
mcp.Description("高亮菜单名称"),
),
mcp.WithString("parameters",
mcp.Description("路由参数JSON字符串格式[{\"type\":\"params\",\"key\":\"id\",\"value\":\"1\"}]"),
),
mcp.WithString("menuBtn",
mcp.Description("菜单按钮JSON字符串格式[{\"name\":\"add\",\"desc\":\"新增\"}]"),
),
)
}
// Handle 处理菜单创建请求
func (m *MenuCreator) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
// 解析请求参数
args := request.GetArguments()
// 必需参数
path, ok := args["path"].(string)
if !ok || path == "" {
return nil, errors.New("path 参数是必需的")
}
name, ok := args["name"].(string)
if !ok || name == "" {
return nil, errors.New("name 参数是必需的")
}
component, ok := args["component"].(string)
if !ok || component == "" {
return nil, errors.New("component 参数是必需的")
}
title, ok := args["title"].(string)
if !ok || title == "" {
return nil, errors.New("title 参数是必需的")
}
// 可选参数
parentId := uint(0)
if val, ok := args["parentId"].(float64); ok {
parentId = uint(val)
}
hidden := false
if val, ok := args["hidden"].(bool); ok {
hidden = val
}
sort := 1
if val, ok := args["sort"].(float64); ok {
sort = int(val)
}
icon := "menu"
if val, ok := args["icon"].(string); ok && val != "" {
icon = val
}
keepAlive := false
if val, ok := args["keepAlive"].(bool); ok {
keepAlive = val
}
defaultMenu := false
if val, ok := args["defaultMenu"].(bool); ok {
defaultMenu = val
}
closeTab := false
if val, ok := args["closeTab"].(bool); ok {
closeTab = val
}
activeName := ""
if val, ok := args["activeName"].(string); ok {
activeName = val
}
// 解析参数和按钮
var parameters []system.SysBaseMenuParameter
if parametersStr, ok := args["parameters"].(string); ok && parametersStr != "" {
var paramReqs []MenuParameterRequest
if err := json.Unmarshal([]byte(parametersStr), &paramReqs); err != nil {
return nil, fmt.Errorf("parameters 参数格式错误: %v", err)
}
for _, param := range paramReqs {
parameters = append(parameters, system.SysBaseMenuParameter{
Type: param.Type,
Key: param.Key,
Value: param.Value,
})
}
}
var menuBtn []system.SysBaseMenuBtn
if menuBtnStr, ok := args["menuBtn"].(string); ok && menuBtnStr != "" {
var btnReqs []MenuButtonRequest
if err := json.Unmarshal([]byte(menuBtnStr), &btnReqs); err != nil {
return nil, fmt.Errorf("menuBtn 参数格式错误: %v", err)
}
for _, btn := range btnReqs {
menuBtn = append(menuBtn, system.SysBaseMenuBtn{
Name: btn.Name,
Desc: btn.Desc,
})
}
}
// 构建菜单对象
menu := system.SysBaseMenu{
ParentId: parentId,
Path: path,
Name: name,
Hidden: hidden,
Component: component,
Sort: sort,
Meta: system.Meta{
Title: title,
Icon: icon,
KeepAlive: keepAlive,
DefaultMenu: defaultMenu,
CloseTab: closeTab,
ActiveName: activeName,
},
Parameters: parameters,
MenuBtn: menuBtn,
}
// 创建菜单
menuService := service.ServiceGroupApp.SystemServiceGroup.MenuService
err := menuService.AddBaseMenu(menu)
if err != nil {
return nil, fmt.Errorf("创建菜单失败: %v", err)
}
// 获取创建的菜单ID
var createdMenu system.SysBaseMenu
err = global.GVA_DB.Where("name = ? AND path = ?", name, path).First(&createdMenu).Error
if err != nil {
global.GVA_LOG.Warn("获取创建的菜单ID失败", zap.Error(err))
}
// 构建响应
response := &MenuCreateResponse{
Success: true,
Message: fmt.Sprintf("成功创建菜单 %s", title),
MenuID: createdMenu.ID,
Name: name,
Path: path,
}
resultJSON, err := json.MarshalIndent(response, "", " ")
if err != nil {
return nil, fmt.Errorf("序列化结果失败: %v", err)
}
// 添加权限分配提醒
permissionReminder := "\n\n⚠ 重要提醒:\n" +
"菜单创建完成后,请前往【系统管理】->【角色管理】中为相关角色分配新创建的菜单权限," +
"以确保用户能够正常访问新菜单。\n" +
"具体步骤:\n" +
"1. 进入角色管理页面\n" +
"2. 选择需要授权的角色\n" +
"3. 在菜单权限中勾选新创建的菜单项\n" +
"4. 保存权限配置"
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: fmt.Sprintf("菜单创建结果:\n\n%s%s", string(resultJSON), permissionReminder),
},
},
}, nil
}

111
mcp/menu_lister.go Normal file
View File

@@ -0,0 +1,111 @@
package mcpTool
import (
"context"
"encoding/json"
"fmt"
"git.echol.cn/loser/lckt/global"
"git.echol.cn/loser/lckt/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
}

137
mcp/requirement_analyzer.go Normal file
View File

@@ -0,0 +1,137 @@
package mcpTool
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/mark3labs/mcp-go/mcp"
)
func init() {
RegisterTool(&RequirementAnalyzer{})
}
type RequirementAnalyzer struct{}
// RequirementAnalysisRequest 需求分析请求
type RequirementAnalysisRequest struct {
UserRequirement string `json:"userRequirement"`
}
// RequirementAnalysisResponse 需求分析响应
type RequirementAnalysisResponse struct {
AIPrompt string `json:"aiPrompt"` // 给AI的提示词
}
// New 返回工具注册信息
func (t *RequirementAnalyzer) New() mcp.Tool {
return mcp.NewTool("requirement_analyzer",
mcp.WithDescription(`**🚀 需求分析工具 - 首选入口工具(最高优先级)**
**⭐ 重要提示这是所有MCP工具的首选入口请优先使用**
**🎯 核心职责:**
将用户的自然语言需求转换为AI可理解的结构化提示词
**📋 工作流程:**
1. 接收用户自然语言需求描述
2. 生成专业的AI提示词要求AI将需求梳理为清晰的逻辑步骤
- **1. 第一步功能描述**
- **2. 第二步功能描述**
- **3. 第三步功能描述**
- **...**
3. 指导后续使用 gva_auto_generate 工具进行代码生成
**✅ 适用场景:**
- 用户有新的业务需求需要开发
- 需要创建新的功能模块
- 想要快速搭建业务系统
- 需求描述比较模糊需要AI帮助梳理
**❌ 不负责的事情:**
- 不生成具体的包名和模块名(交给 gva_auto_generate
- 不进行代码生成(交给 gva_auto_generate
- 不创建数据库表结构(交给 gva_auto_generate
**🔄 推荐工作流:**
requirement_analyzer → gva_auto_generate → 其他辅助工具
`),
mcp.WithString("userRequirement",
mcp.Required(),
mcp.Description("用户的需求描述,支持自然语言,如:'我要做一个猫舍管理系统,用来录入猫的信息,并且记录每只猫每天的活动信息'"),
),
)
}
// Handle 处理工具调用
func (t *RequirementAnalyzer) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
userRequirement, ok := request.GetArguments()["userRequirement"].(string)
if !ok || userRequirement == "" {
return nil, errors.New("参数错误userRequirement 必须是非空字符串")
}
// 分析用户需求
analysisResponse, err := t.analyzeRequirement(userRequirement)
if err != nil {
return nil, fmt.Errorf("需求分析失败: %v", err)
}
// 序列化响应
responseData, err := json.Marshal(analysisResponse)
if err != nil {
return nil, fmt.Errorf("序列化响应失败: %v", err)
}
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.NewTextContent(string(responseData)),
},
}, nil
}
// analyzeRequirement 分析用户需求 - 专注于AI需求传递
func (t *RequirementAnalyzer) analyzeRequirement(userRequirement string) (*RequirementAnalysisResponse, error) {
// 生成AI提示词 - 这是唯一功能
aiPrompt := t.generateAIPrompt(userRequirement)
return &RequirementAnalysisResponse{
AIPrompt: aiPrompt,
}, nil
}
// generateAIPrompt 生成AI提示词 - 要求AI梳理逻辑为1xxx2xxx格式
func (t *RequirementAnalyzer) generateAIPrompt(userRequirement string) string {
prompt := fmt.Sprintf(`# 🤖 AI需求逻辑梳理任务
## 📝 用户原始需求
%s
## 🎯 AI任务要求
请将上述用户需求梳理成清晰的逻辑步骤,格式要求:
**1. 第一步功能描述**
**2. 第二步功能描述**
**3. 第三步功能描述**
**...**
## 📋 梳理要求
- 将需求拆解为具体的功能步骤
- 每个步骤用数字编号1、2、3...
- 步骤描述要清晰、具体、可执行
- 按照业务逻辑顺序排列
- 考虑数据流和用户操作流程
## 🔄 后续流程
梳理完成后,请使用 gva_auto_generate 工具进行代码生成:
- gva_auto_generate 会根据梳理的逻辑步骤自动生成包名、模块名
- gva_auto_generate 会设计数据表结构和API接口
- gva_auto_generate 会生成完整的前后端代码
现在请开始梳理用户需求:"%s"`, userRequirement, userRequirement)
return prompt
}

View File

@@ -6,13 +6,10 @@ import (
"git.echol.cn/loser/lckt/global"
"git.echol.cn/loser/lckt/model/common/response"
"git.echol.cn/loser/lckt/service"
"git.echol.cn/loser/lckt/utils"
"github.com/gin-gonic/gin"
)
var casbinService = service.ServiceGroupApp.SystemServiceGroup.CasbinService
// CasbinHandler 拦截器
func CasbinHandler() gin.HandlerFunc {
return func(c *gin.Context) {
@@ -24,7 +21,7 @@ func CasbinHandler() gin.HandlerFunc {
act := c.Request.Method
// 获取用户的角色
sub := strconv.Itoa(int(waitUse.AuthorityId))
e := casbinService.Casbin() // 判断策略中是否存在
e := utils.GetCasbin() // 判断策略中是否存在
success, _ := e.Enforce(sub, obj, act)
if !success {
response.FailWithDetailed(gin.H{}, "权限不足", c)

View File

@@ -71,19 +71,3 @@ func checkCors(currentOrigin string) *config.CORSWhitelist {
}
return nil
}
func AllCors() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
c.Writer.Header().Set("Access-Control-Allow-Headers", "*")
c.Writer.Header().Set("Access-Control-Expose-Headers", "Content-Length, Content-Type")
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
}
}

View File

@@ -11,13 +11,10 @@ import (
"git.echol.cn/loser/lckt/global"
"git.echol.cn/loser/lckt/model/system"
"git.echol.cn/loser/lckt/service"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
var userService = service.ServiceGroupApp.SystemServiceGroup.UserService
func ErrorToEmail() gin.HandlerFunc {
return func(c *gin.Context) {
var username string
@@ -26,11 +23,12 @@ func ErrorToEmail() gin.HandlerFunc {
username = claims.Username
} else {
id, _ := strconv.Atoi(c.Request.Header.Get("x-user-id"))
user, err := userService.FindUserById(id)
var u system.SysUser
err := global.GVA_DB.Where("id = ?", id).First(&u).Error
if err != nil {
username = "Unknown"
}
username = user.Username
username = u.Username
}
body, _ := io.ReadAll(c.Request.Body)
// 再重新写回请求体body中ioutil.ReadAll会清空c.Request.Body中的数据

View File

@@ -9,22 +9,19 @@ import (
"time"
"git.echol.cn/loser/lckt/model/common/response"
"git.echol.cn/loser/lckt/service"
"github.com/gin-gonic/gin"
)
var jwtService = service.ServiceGroupApp.SystemServiceGroup.JwtService
func JWTAuth() gin.HandlerFunc {
return func(c *gin.Context) {
// 我们这里jwt鉴权取头部信息 x-token 登录时回返回token信息 这里前端需要把token存储到cookie或者本地localStorage中 不过需要跟后端协商过期时间 可以约定刷新令牌或者重新登录
token := utils.GetToken(c)
if token == "" {
response.NoAuth("未登录或非法访问", c)
response.NoAuth("未登录或非法访问,请登录", c)
c.Abort()
return
}
if jwtService.IsBlacklist(token) {
if isBlacklist(token) {
response.NoAuth("您的帐户异地登陆或令牌失效", c)
utils.ClearToken(c)
c.Abort()
@@ -35,7 +32,7 @@ func JWTAuth() gin.HandlerFunc {
claims, err := j.ParseToken(token)
if err != nil {
if errors.Is(err, utils.TokenExpired) {
response.NoAuth("授权已过期", c)
response.NoAuth("登录已过期,请重新登录", c)
utils.ClearToken(c)
c.Abort()
return
@@ -65,7 +62,7 @@ func JWTAuth() gin.HandlerFunc {
utils.SetToken(c, newToken, int(dr.Seconds()))
if global.GVA_CONFIG.System.UseMultipoint {
// 记录新的活跃jwt
_ = jwtService.SetRedisJWT(newToken, newClaims.Username)
_ = utils.SetRedisJWT(newToken, newClaims.Username)
}
}
c.Next()
@@ -78,3 +75,14 @@ func JWTAuth() gin.HandlerFunc {
}
}
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: IsBlacklist
//@description: 判断JWT是否在黑名单内部
//@param: jwt string
//@return: bool
func isBlacklist(jwt string) bool {
_, ok := global.BlackCache.Get(jwt)
return ok
}

View File

@@ -15,13 +15,10 @@ import (
"git.echol.cn/loser/lckt/global"
"git.echol.cn/loser/lckt/model/system"
"git.echol.cn/loser/lckt/service"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
var operationRecordService = service.ServiceGroupApp.SystemServiceGroup.OperationRecordService
var respPool sync.Pool
var bufferSize = 1024
@@ -115,8 +112,7 @@ func OperationRecord() gin.HandlerFunc {
record.Body = "超出记录长度"
}
}
if err := operationRecordService.CreateSysOperationRecord(record); err != nil {
if err := global.GVA_DB.Create(&record).Error; err != nil {
global.GVA_LOG.Error("create operation record error:", zap.Error(err))
}
}

55
middleware/timeout.go Normal file
View File

@@ -0,0 +1,55 @@
package middleware
import (
"context"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
// TimeoutMiddleware 创建超时中间件
// 入参 timeout 设置超时时间例如time.Second * 5
// 使用示例 xxx.Get("path",middleware.TimeoutMiddleware(30*time.Second),HandleFunc)
func TimeoutMiddleware(timeout time.Duration) gin.HandlerFunc {
return func(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), timeout)
defer cancel()
c.Request = c.Request.WithContext(ctx)
// 使用 buffered channel 避免 goroutine 泄漏
done := make(chan struct{}, 1)
panicChan := make(chan interface{}, 1)
go func() {
defer func() {
if p := recover(); p != nil {
select {
case panicChan <- p:
default:
}
}
select {
case done <- struct{}{}:
default:
}
}()
c.Next()
}()
select {
case p := <-panicChan:
panic(p)
case <-done:
return
case <-ctx.Done():
// 确保服务器超时设置足够长
c.Header("Connection", "close")
c.AbortWithStatusJSON(http.StatusGatewayTimeout, gin.H{
"code": 504,
"msg": "请求超时",
})
return
}
}
}

View File

@@ -50,7 +50,7 @@ func UserJWTAuth() gin.HandlerFunc {
user_jwt.SetToken(c, newToken, int(dr.Seconds()))
if global.GVA_CONFIG.System.UseMultipoint {
// 记录新的活跃jwt
_ = jwtService.SetRedisJWT(newToken, newClaims.Username)
_ = utils.SetRedisJWT(newToken, newClaims.Username)
}
}
c.Next()

View File

@@ -158,7 +158,7 @@ func (r *AutoCode) Pretreatment() error {
r.NeedJSON = true
case "time.Time":
r.HasTimer = true
if r.Fields[i].FieldSearchType != "" {
if r.Fields[i].FieldSearchType != "" && r.Fields[i].FieldSearchType != "BETWEEN" && r.Fields[i].FieldSearchType != "NOT BETWEEN" {
r.HasSearchTimer = true
}
}

View File

@@ -0,0 +1,16 @@
package request
type AutoMcpTool struct {
Name string `json:"name" form:"name" binding:"required"`
Description string `json:"description" form:"description" binding:"required"`
Params []struct {
Name string `json:"name" form:"name" binding:"required"`
Description string `json:"description" form:"description" binding:"required"`
Type string `json:"type" form:"type" binding:"required"` // string, number, boolean, object, array
Required bool `json:"required" form:"required"`
Default string `json:"default" form:"default"`
} `json:"params" form:"params"`
Response []struct {
Type string `json:"type" form:"type" binding:"required"` // text, image
} `json:"response" form:"response"`
}

View File

@@ -33,6 +33,11 @@ type ChangePasswordReq struct {
NewPassword string `json:"newPassword"` // 新密码
}
type ResetPassword struct {
ID uint `json:"ID" form:"ID"`
Password string `json:"password" form:"password" gorm:"comment:用户登录密码"` // 用户登录密码
}
// SetUserAuth Modify user's auth structure
type SetUserAuth struct {
AuthorityId uint `json:"authorityId"` // 角色ID
@@ -51,7 +56,6 @@ type ChangeUserInfo struct {
AuthorityIds []uint `json:"authorityIds" gorm:"-"` // 角色ID
Email string `json:"email" gorm:"comment:用户邮箱"` // 用户邮箱
HeaderImg string `json:"headerImg" gorm:"default:https://qmplusimg.henrongyi.top/gva_header.jpg;comment:用户头像"` // 用户头像
SideMode string `json:"sideMode" gorm:"comment:用户侧边主题"` // 用户侧边主题
Enable int `json:"enable" gorm:"comment:冻结用户"` //冻结用户
Authorities []system.SysAuthority `json:"-" gorm:"many2many:sys_user_authority;"`
}

View File

@@ -0,0 +1,40 @@
package request
import (
"git.echol.cn/loser/lckt/model/common/request"
"git.echol.cn/loser/lckt/model/system"
"time"
)
type SysVersionSearch struct {
CreatedAtRange []time.Time `json:"createdAtRange" form:"createdAtRange[]"`
VersionName *string `json:"versionName" form:"versionName"`
VersionCode *string `json:"versionCode" form:"versionCode"`
request.PageInfo
}
// ExportVersionRequest 导出版本请求结构体
type ExportVersionRequest struct {
VersionName string `json:"versionName" binding:"required"` // 版本名称
VersionCode string `json:"versionCode" binding:"required"` // 版本号
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
ExportDictionary []system.SysDictionary `json:"dictionaries"` // 字典数据直接复用SysDictionary
}
// VersionInfo 版本信息结构体
type VersionInfo struct {
Name string `json:"name" binding:"required"` // 版本名称
Code string `json:"code" binding:"required"` // 版本号
Description string `json:"description"` // 版本描述
ExportTime string `json:"exportTime"` // 导出时间
}

View File

@@ -0,0 +1,14 @@
package response
import (
"git.echol.cn/loser/lckt/model/system"
"git.echol.cn/loser/lckt/model/system/request"
)
// ExportVersionResponse 导出版本响应结构体
type ExportVersionResponse struct {
Version request.VersionInfo `json:"version"` // 版本信息
Menus []system.SysBaseMenu `json:"menus"` // 菜单数据直接复用SysBaseMenu
Apis []system.SysApi `json:"apis"` // API数据直接复用SysApi
Dictionaries []system.SysDictionary `json:"dictionaries"` // 字典数据直接复用SysDictionary
}

View File

@@ -21,12 +21,13 @@ type SysBaseMenu struct {
}
type Meta struct {
ActiveName string `json:"activeName" gorm:"comment:高亮菜单"`
KeepAlive bool `json:"keepAlive" gorm:"comment:是否缓存"` // 是否缓存
DefaultMenu bool `json:"defaultMenu" gorm:"comment:是否是基础路由(开发中)"` // 是否是基础路由(开发中)
Title string `json:"title" gorm:"comment:菜单名"` // 菜单名
Icon string `json:"icon" gorm:"comment:菜单图标"` // 菜单图标
CloseTab bool `json:"closeTab" gorm:"comment:自动关闭tab"` // 自动关闭tab
ActiveName string `json:"activeName" gorm:"comment:高亮菜单"`
KeepAlive bool `json:"keepAlive" gorm:"comment:是否缓存"` // 是否缓存
DefaultMenu bool `json:"defaultMenu" gorm:"comment:是否是基础路由(开发中)"` // 是否是基础路由(开发中)
Title string `json:"title" gorm:"comment:菜单名"` // 菜单名
Icon string `json:"icon" gorm:"comment:菜单图标"` // 菜单图标
CloseTab bool `json:"closeTab" gorm:"comment:自动关闭tab"` // 自动关闭tab
TransitionType string `json:"transitionType" gorm:"comment:路由切换动画"` // 路由切换动画
}
type SysBaseMenuParameter struct {

View File

@@ -0,0 +1,20 @@
// 自动生成模板SysVersion
package system
import (
"git.echol.cn/loser/lckt/global"
)
// 版本管理 结构体 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:longtext;postgres:type:text;sqlite:type:text;"` //版本数据
}
// TableName 版本管理 SysVersion自定义表名 sys_versions
func (SysVersion) TableName() string {
return "sys_versions"
}

View File

@@ -14,6 +14,7 @@
global.GVA_CONFIG.Email.Nickname,
global.GVA_CONFIG.Email.Port,
global.GVA_CONFIG.Email.IsSSL,
global.GVA_CONFIG.Email.IsLoginAuth,
))
同样也可以再传入时写死
@@ -26,6 +27,7 @@
"登录密钥",
465,
true,
true,
))
### 2. 配置说明
@@ -34,13 +36,14 @@
//其中 Form 和 Secret 通常来说就是用户名和密码
type Email struct {
To string // 收件人:多个以英文逗号分隔 例a@qq.com b@qq.com 正式开发中请把此项目作为参数使用 此处配置主要用于发送错误监控邮件
From string // 发件人 你自己要发邮件的邮箱
Host string // 服务器地址 例如 smtp.qq.com 请前往QQ或者你要发邮件的邮箱查看其smtp协议
Secret string // 密钥 用于登录的密钥 最好不要用邮箱密码 去邮箱smtp申请一个用于登录的密钥
Nickname string // 昵称 发件人昵称 自定义即可 可以不填
Port int // 端口 请前往QQ或者你要发邮件的邮箱查看其smtp协议 大多为 465
IsSSL bool // 是否SSL 是否开启SSL
To string // 收件人:多个以英文逗号分隔 例a@qq.com b@qq.com 正式开发中请把此项目作为参数使用 此处配置主要用于发送错误监控邮件
From string // 发件人 你自己要发邮件的邮箱
Host string // 服务器地址 例如 smtp.qq.com 请前往QQ或者你要发邮件的邮箱查看其smtp协议
Secret string // 密钥 用于登录的密钥 最好不要用邮箱密码 去邮箱smtp申请一个用于登录的密钥
Nickname string // 昵称 发件人昵称 自定义即可 可以不填
Port int // 端口 请前往QQ或者你要发邮件的邮箱查看其smtp协议 大多为 465
IsSSL bool // 是否SSL 是否开启SSL
IsLoginAuth bool // 是否LoginAuth 是否使用LoginAuth认证方式适用于IBM、微软邮箱服务器等
}
#### 2-2 入参结构说明
//其中 Form 和 Secret 通常来说就是用户名和密码

View File

@@ -1,11 +1,12 @@
package config
type Email struct {
To string `mapstructure:"to" json:"to" yaml:"to"` // 收件人:多个以英文逗号分隔 例a@qq.com b@qq.com 正式开发中请把此项目作为参数使用
From string `mapstructure:"from" json:"from" yaml:"from"` // 发件人 你自己要发邮件的邮箱
Host string `mapstructure:"host" json:"host" yaml:"host"` // 服务器地址 例如 smtp.qq.com 请前往QQ或者你要发邮件的邮箱查看其smtp协议
Secret string `mapstructure:"secret" json:"secret" yaml:"secret"` // 密钥 用于登录的密钥 最好不要用邮箱密码 去邮箱smtp申请一个用于登录的密钥
Nickname string `mapstructure:"nickname" json:"nickname" yaml:"nickname"` // 昵称 发件人昵称 通常为自己的邮箱
Port int `mapstructure:"port" json:"port" yaml:"port"` // 端口 请前往QQ或者你要发邮件的邮箱查看其smtp协议 大多为 465
IsSSL bool `mapstructure:"is-ssl" json:"isSSL" yaml:"is-ssl"` // 是否SSL 是否开启SSL
To string `mapstructure:"to" json:"to" yaml:"to"` // 收件人:多个以英文逗号分隔 例a@qq.com b@qq.com 正式开发中请把此项目作为参数使用
From string `mapstructure:"from" json:"from" yaml:"from"` // 发件人 你自己要发邮件的邮箱
Host string `mapstructure:"host" json:"host" yaml:"host"` // 服务器地址 例如 smtp.qq.com 请前往QQ或者你要发邮件的邮箱查看其smtp协议
Secret string `mapstructure:"secret" json:"secret" yaml:"secret"` // 密钥 用于登录的密钥 最好不要用邮箱密码 去邮箱smtp申请一个用于登录的密钥
Nickname string `mapstructure:"nickname" json:"nickname" yaml:"nickname"` // 昵称 发件人昵称 通常为自己的邮箱
Port int `mapstructure:"port" json:"port" yaml:"port"` // 端口 请前往QQ或者你要发邮件的邮箱查看其smtp协议 大多为 465
IsSSL bool `mapstructure:"is-ssl" json:"isSSL" yaml:"is-ssl"` // 是否SSL 是否开启SSL
IsLoginAuth bool `mapstructure:"is-loginauth" json:"is-loginauth" yaml:"is-loginauth"` // 是否LoginAuth 是否使用LoginAuth认证
}

View File

@@ -8,7 +8,7 @@ import (
type emailPlugin struct{}
func CreateEmailPlug(To, From, Host, Secret, Nickname string, Port int, IsSSL bool) *emailPlugin {
func CreateEmailPlug(To, From, Host, Secret, Nickname string, Port int, IsSSL bool, IsLoginAuth bool) *emailPlugin {
global.GlobalConfig.To = To
global.GlobalConfig.From = From
global.GlobalConfig.Host = Host
@@ -16,6 +16,7 @@ func CreateEmailPlug(To, From, Host, Secret, Nickname string, Port int, IsSSL bo
global.GlobalConfig.Nickname = Nickname
global.GlobalConfig.Port = Port
global.GlobalConfig.IsSSL = IsSSL
global.GlobalConfig.IsLoginAuth = IsLoginAuth
return &emailPlugin{}
}

View File

@@ -60,8 +60,14 @@ func send(to []string, subject string, body string) error {
host := global.GlobalConfig.Host
port := global.GlobalConfig.Port
isSSL := global.GlobalConfig.IsSSL
isLoginAuth := global.GlobalConfig.IsLoginAuth
auth := smtp.PlainAuth("", from, secret, host)
var auth smtp.Auth
if isLoginAuth {
auth = LoginAuth(from, secret)
} else {
auth = smtp.PlainAuth("", from, secret, host)
}
e := email.NewEmail()
if nickname != "" {
e.From = fmt.Sprintf("%s <%s>", nickname, from)
@@ -80,3 +86,37 @@ func send(to []string, subject string, body string) error {
}
return err
}
// LoginAuth 用于IBM、微软邮箱服务器的LOGIN认证方式
type loginAuth struct {
username, password string
}
func LoginAuth(username, password string) smtp.Auth {
return &loginAuth{username, password}
}
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
return "LOGIN", []byte{}, nil
}
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if more {
switch string(fromServer) {
case "Username:":
return []byte(a.username), nil
case "Password:":
return []byte(a.password), nil
default:
// 邮箱服务器可能发送的其他提示信息
prompt := strings.ToLower(string(fromServer))
if strings.Contains(prompt, "username") || strings.Contains(prompt, "user") {
return []byte(a.username), nil
}
if strings.Contains(prompt, "password") || strings.Contains(prompt, "pass") {
return []byte(a.password), nil
}
}
}
return nil, nil
}

56
resource/mcp/tools.tpl Normal file
View File

@@ -0,0 +1,56 @@
package mcpTool
import (
"context"
"github.com/mark3labs/mcp-go/mcp"
)
func init() {
RegisterTool(&{{.Name | title}}{})
}
type {{.Name | title}} struct {
}
// {{.Description}}
func (t *{{.Name | title}}) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
// TODO:
// :
// {{- range .Params}}
// {{.Name}} := request.GetArguments()["{{.Name}}"]
// {{- end}}
return &mcp.CallToolResult{
Content: []mcp.Content{
{{- range .Response}}
mcp.{{.Type | title}}Content{
Type: "{{.Type}}",
// TODO: {{.Type}}
},
{{- end}}
},
}, nil
}
func (t *{{.Name | title}}) New() mcp.Tool {
return mcp.NewTool("{{.Name}}",
mcp.WithDescription("{{.Description}}"),
{{- range .Params}}
mcp.With{{.Type | title}}("{{.Name}}",
{{- if .Required}}mcp.Required(),{{end}}
mcp.Description("{{.Description}}"),
{{- if .Default}}
{{- if eq .Type "string"}}
mcp.DefaultString("{{.Default}}"),
{{- else if eq .Type "number"}}
mcp.DefaultNumber({{.Default}}),
{{- else if eq .Type "boolean"}}
mcp.DefaultBoolean({{if or (eq .Default "true") (eq .Default "True")}}true{{else}}false{{end}}),
{{- else if eq .Type "array"}}
//
// mcp.DefaultArray({{.Default}}),
{{- end}}
{{- end}}
),
{{- end}}
)
}

View File

@@ -1,25 +1,7 @@
{{- if .IsAdd}}
// 在结构体中新增如下字段
{{- range .Fields}}
{{- if eq .FieldType "enum" }}
{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};type:enum({{.DataTypeLong}});comment:{{.Comment}};" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if eq .FieldType "picture" }}
{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if eq .FieldType "video" }}
{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if eq .FieldType "file" }}
{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end }} swaggertype:"array,object"`
{{- else if eq .FieldType "pictures" }}
{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end }} swaggertype:"array,object"`
{{- else if eq .FieldType "richtext" }}
{{.FieldName}} *string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}type:text;" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if eq .FieldType "json" }}
{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}type:text;" {{- if .Require }} binding:"required"{{- end }} swaggertype:"object"`
{{- else if eq .FieldType "array" }}
{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}type:text;" {{- if .Require }} binding:"required"{{- end }} swaggertype:"array,object"`
{{- else }}
{{.FieldName}} *{{.FieldType}} `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end -}}`
{{- end }} {{ if .FieldDesc }}//{{.FieldDesc}}{{ end }}
{{ GenerateField . }}
{{- end }}
{{ else }}
@@ -47,25 +29,7 @@ type {{.StructName}} struct {
global.GVA_MODEL
{{- end }}
{{- range .Fields}}
{{- if eq .FieldType "enum" }}
{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};type:enum({{.DataTypeLong}});comment:{{.Comment}};" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if eq .FieldType "picture" }}
{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if eq .FieldType "video" }}
{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if eq .FieldType "file" }}
{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end }} swaggertype:"array,object"`
{{- else if eq .FieldType "pictures" }}
{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end }} swaggertype:"array,object"`
{{- else if eq .FieldType "richtext" }}
{{.FieldName}} *string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}type:text;" {{- if .Require }} binding:"required"{{- end -}}`
{{- else if eq .FieldType "json" }}
{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}type:text;" {{- if .Require }} binding:"required"{{- end }} swaggertype:"object"`
{{- else if eq .FieldType "array" }}
{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}type:text;" {{- if .Require }} binding:"required"{{- end }} swaggertype:"array,object"`
{{- else }}
{{.FieldName}} *{{.FieldType}} `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end -}}`
{{- end }} {{ if .FieldDesc }}//{{.FieldDesc}}{{ end }}
{{ GenerateField . }}
{{- end }}
{{- if .AutoCreateResource }}
CreatedBy uint `gorm:"column:created_by;comment:创建者"`

View File

@@ -2,16 +2,7 @@
// 在结构体中新增如下字段
{{- range .Fields}}
{{- if ne .FieldSearchType ""}}
{{- if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}}
Start{{.FieldName}} *{{.FieldType}} `json:"start{{.FieldName}}" form:"start{{.FieldName}}"`
End{{.FieldName}} *{{.FieldType}} `json:"end{{.FieldName}}" form:"end{{.FieldName}}"`
{{- else }}
{{- if or (eq .FieldType "enum") (eq .FieldType "picture") (eq .FieldType "pictures") (eq .FieldType "video") (eq .FieldType "json") }}
{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" `
{{- else }}
{{.FieldName}} *{{.FieldType}} `json:"{{.FieldJson}}" form:"{{.FieldJson}}" `
{{- end }}
{{- end }}
{{ GenerateSearchField . }}
{{- end}}
{{- end }}
{{- if .NeedSort}}
@@ -24,28 +15,18 @@ package request
import (
{{- if not .OnlyTemplate }}
"{{.Module}}/model/common/request"
{{ if or .HasSearchTimer .GvaModel}}"time"{{ end }}
{{ if or .HasSearchTimer .GvaModel }}"time"{{ end }}
{{- end }}
)
type {{.StructName}}Search struct{
{{- if not .OnlyTemplate}}
{{- if .GvaModel }}
StartCreatedAt *time.Time `json:"startCreatedAt" form:"startCreatedAt"`
EndCreatedAt *time.Time `json:"endCreatedAt" form:"endCreatedAt"`
CreatedAtRange []time.Time `json:"createdAtRange" form:"createdAtRange[]"`
{{- end }}
{{- range .Fields}}
{{- if ne .FieldSearchType ""}}
{{- if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}}
Start{{.FieldName}} *{{.FieldType}} `json:"start{{.FieldName}}" form:"start{{.FieldName}}"`
End{{.FieldName}} *{{.FieldType}} `json:"end{{.FieldName}}" form:"end{{.FieldName}}"`
{{- else }}
{{- if or (eq .FieldType "enum") (eq .FieldType "picture") (eq .FieldType "pictures") (eq .FieldType "video") (eq .FieldType "json") }}
{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" `
{{- else }}
{{.FieldName}} *{{.FieldType}} `json:"{{.FieldJson}}" form:"{{.FieldJson}}" `
{{- end }}
{{- end }}
{{ GenerateSearchField . }}
{{- end}}
{{- end }}
request.PageInfo

View File

@@ -8,29 +8,7 @@
{{- if .IsAdd}}
// Get{{.StructName}}InfoList 新增搜索语句
{{- range .Fields}}
{{- if .FieldSearchType}}
{{- if or (eq .FieldType "enum") (eq .FieldType "pictures") (eq .FieldType "picture") (eq .FieldType "video") (eq .FieldType "json") }}
if info.{{.FieldName}} != "" {
{{- if or (eq .FieldType "enum") }}
db = db.Where("{{.ColumnName}} {{.FieldSearchType}} ?",{{if eq .FieldSearchType "LIKE"}}"%"+ {{ end }}*info.{{.FieldName}}{{if eq .FieldSearchType "LIKE"}}+"%"{{ end }})
{{- else}}
//
{{- end}}
}
{{- else if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}}
if info.Start{{.FieldName}} != nil && info.End{{.FieldName}} != nil {
db = db.Where("{{.ColumnName}} {{.FieldSearchType}} ? AND ? ",info.Start{{.FieldName}},info.End{{.FieldName}})
}
{{- else}}
if info.{{.FieldName}} != nil{{- if eq .FieldType "string" }} && *info.{{.FieldName}} != ""{{- end }} {
db = db.Where("{{.ColumnName}} {{.FieldSearchType}} ?",{{if eq .FieldSearchType "LIKE"}}"%"+{{ end }}*info.{{.FieldName}}{{if eq .FieldSearchType "LIKE"}}+"%"{{ end }})
}
{{- end }}
{{- end }}
{{- end }}
{{ GenerateSearchConditions .Fields }}
// Get{{.StructName}}InfoList 新增排序语句 请自行在搜索语句中添加orderMap内容
{{- range .Fields}}
{{- if .Sort}}
@@ -170,31 +148,11 @@ func ({{.Abbreviation}}Service *{{.StructName}}Service)Get{{.StructName}}InfoLis
var {{.Abbreviation}}s []{{.Package}}.{{.StructName}}
//
{{- if .GvaModel }}
if info.StartCreatedAt !=nil && info.EndCreatedAt !=nil {
db = db.Where("created_at BETWEEN ? AND ?", info.StartCreatedAt, info.EndCreatedAt)
if len(info.CreatedAtRange) == 2 {
db = db.Where("created_at BETWEEN ? AND ?", info.CreatedAtRange[0], info.CreatedAtRange[1])
}
{{- end }}
{{- range .Fields}}
{{- if .FieldSearchType}}
{{- if or (eq .FieldType "enum") (eq .FieldType "pictures") (eq .FieldType "picture") (eq .FieldType "video") (eq .FieldType "json") }}
if info.{{.FieldName}} != "" {
{{- if or (eq .FieldType "enum")}}
db = db.Where("{{.ColumnName}} {{.FieldSearchType}} ?",{{if eq .FieldSearchType "LIKE"}}"%"+ {{ end }}*info.{{.FieldName}}{{if eq .FieldSearchType "LIKE"}}+"%"{{ end }})
{{- else}}
//
{{- end}}
}
{{- else if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}}
if info.Start{{.FieldName}} != nil && info.End{{.FieldName}} != nil {
db = db.Where("{{.ColumnName}} {{.FieldSearchType}} ? AND ? ",info.Start{{.FieldName}},info.End{{.FieldName}})
}
{{- else}}
if info.{{.FieldName}} != nil{{- if eq .FieldType "string" }} && *info.{{.FieldName}} != ""{{- end }} {
db = db.Where("{{.ColumnName}} {{.FieldSearchType}} ?",{{if eq .FieldSearchType "LIKE"}}"%"+{{ end }}*info.{{.FieldName}}{{if eq .FieldSearchType "LIKE"}}+"%"{{ end }})
}
{{- end }}
{{- end }}
{{- end }}
{{ GenerateSearchConditions .Fields }}
err = db.Count(&total).Error
if err!=nil {
return
@@ -202,6 +160,10 @@ func ({{.Abbreviation}}Service *{{.StructName}}Service)Get{{.StructName}}InfoLis
{{- if .NeedSort}}
var OrderStr string
orderMap := make(map[string]bool)
{{- if .GvaModel }}
orderMap["id"] = true
orderMap["created_at"] = true
{{- end }}
{{- range .Fields}}
{{- if .Sort}}
orderMap["{{.ColumnName}}"] = true
@@ -233,7 +195,7 @@ func ({{.Abbreviation}}Service *{{.StructName}}Service)Get{{.StructName}}DataSou
{{$key}} := make([]map[string]any, 0)
{{ $dataDB := "" }}
{{- if eq $value.DBName "" }}
{{ $dataDB = $db }}
{{ $dataDB = "global.GVA_DB" }}
{{- else}}
{{ $dataDB = printf "global.MustGetGlobalDBByDBName(\"%s\")" $value.DBName }}
{{- end}}

View File

@@ -1,75 +1,10 @@
{{- if .IsAdd }}
// 新增表单中增加如下代码
{{- range .Fields}}
{{- if .Form}}
<el-form-item label="{{.FieldDesc}}:" prop="{{.FieldJson}}" >
{{- if .CheckDataSource}}
<el-select {{if eq .DataSource.Association 2}} multiple {{ end }} v-model="formData.{{.FieldJson}}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="(item,key) in dataSource.{{.FieldJson}}" :key="key" :label="item.label" :value="item.value" />
</el-select>
{{- else }}
{{- if eq .FieldType "bool" }}
<el-switch v-model="formData.{{.FieldJson}}" active-color="#13ce66" inactive-color="#ff4949" active-text="是" inactive-text="否" clearable ></el-switch>
{{- end }}
{{- if eq .FieldType "string" }}
{{- if .DictType}}
<el-select {{if eq .FieldType "array"}}multiple {{end}}v-model="formData.{{ .FieldJson }}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="(item,key) in {{ .DictType }}Options" :key="key" :label="item.label" :value="item.value" />
</el-select>
{{- else }}
<el-input v-model="formData.{{.FieldJson}}" :clearable="{{.Clearable}}" placeholder="请输入{{.FieldDesc}}" />
{{- end }}
{{- end }}
{{- if eq .FieldType "richtext" }}
<RichEdit v-model="formData.{{.FieldJson}}"/>
{{- end }}
{{- if eq .FieldType "json" }}
// 此字段为json结构可以前端自行控制展示和数据绑定模式 需绑定json的key为 formData.{{.FieldJson}} 后端会按照json的类型进行存取
{{"{{"}} formData.{{.FieldJson}} {{"}}"}}
{{- end }}
{{- if eq .FieldType "array" }}
<ArrayCtrl v-model="formData.{{ .FieldJson }}" editable/>
{{- end }}
{{- if eq .FieldType "int" }}
<el-input v-model.number="formData.{{ .FieldJson }}" :clearable="{{.Clearable}}" placeholder="请输入{{.FieldDesc}}" />
{{- end }}
{{- if eq .FieldType "time.Time" }}
<el-date-picker v-model="formData.{{ .FieldJson }}" type="date" style="width:100%" placeholder="选择日期" :clearable="{{.Clearable}}" />
{{- end }}
{{- if eq .FieldType "float64" }}
<el-input-number v-model="formData.{{ .FieldJson }}" style="width:100%" :precision="2" :clearable="{{.Clearable}}" />
{{- end }}
{{- if eq .FieldType "enum" }}
<el-select v-model="formData.{{ .FieldJson }}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="item in [{{.DataTypeLong}}]" :key="item" :label="item" :value="item" />
</el-select>
{{- end }}
{{- if eq .FieldType "picture" }}
<SelectImage
v-model="formData.{{ .FieldJson }}"
file-type="image"
/>
{{- end }}
{{- if eq .FieldType "pictures" }}
<SelectImage
multiple
v-model="formData.{{ .FieldJson }}"
file-type="image"
/>
{{- end }}
{{- if eq .FieldType "video" }}
<SelectImage
v-model="formData.{{ .FieldJson }}"
file-type="video"
/>
{{- end }}
{{- if eq .FieldType "file" }}
<SelectFile v-model="formData.{{ .FieldJson }}" />
{{- end }}
{{- end }}
</el-form-item>
{{- end }}
{{- end }}
{{- if .Form}}
{{ GenerateFormItem . }}
{{- end }}
{{- end }}
// 字典增加如下代码
{{- range $index, $element := .DictTypes}}
@@ -85,42 +20,7 @@ const {{ $element }}Options = ref([])
// 基础formData结构增加如下字段
{{- range .Fields}}
{{- if .Form}}
{{- if eq .FieldType "bool" }}
{{.FieldJson}}: false,
{{- end }}
{{- if eq .FieldType "string" }}
{{.FieldJson}}: '',
{{- end }}
{{- if eq .FieldType "richtext" }}
{{.FieldJson}}: '',
{{- end }}
{{- if eq .FieldType "int" }}
{{.FieldJson}}: {{- if or .DataSource}} undefined{{ else }} 0{{- end }},
{{- end }}
{{- if eq .FieldType "time.Time" }}
{{.FieldJson}}: new Date(),
{{- end }}
{{- if eq .FieldType "float64" }}
{{.FieldJson}}: 0,
{{- end }}
{{- if eq .FieldType "picture" }}
{{.FieldJson}}: "",
{{- end }}
{{- if eq .FieldType "video" }}
{{.FieldJson}}: "",
{{- end }}
{{- if eq .FieldType "pictures" }}
{{.FieldJson}}: [],
{{- end }}
{{- if eq .FieldType "file" }}
{{.FieldJson}}: [],
{{- end }}
{{- if eq .FieldType "json" }}
{{.FieldJson}}: {},
{{- end }}
{{- if eq .FieldType "array" }}
{{.FieldJson}}: [],
{{- end }}
{{ GenerateDefaultFormValue . }}
{{- end }}
{{- end }}
// 验证规则中增加如下字段
@@ -181,62 +81,7 @@ getDataSourceFunc()
{{- end }}
{{- range .Fields}}
{{- if .Form }}
<el-form-item label="{{.FieldDesc}}:" prop="{{.FieldJson}}">
{{- if .CheckDataSource}}
<el-select {{if eq .DataSource.Association 2}} multiple {{ end }} v-model="formData.{{.FieldJson}}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="(item,key) in dataSource.{{.FieldJson}}" :key="key" :label="item.label" :value="item.value" />
</el-select>
{{- else }}
{{- if eq .FieldType "bool" }}
<el-switch v-model="formData.{{.FieldJson}}" active-color="#13ce66" inactive-color="#ff4949" active-text="是" inactive-text="否" clearable ></el-switch>
{{- end }}
{{- if eq .FieldType "string" }}
{{- if .DictType}}
<el-select {{if eq .FieldType "array"}}multiple {{end}}v-model="formData.{{ .FieldJson }}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="(item,key) in {{ .DictType }}Options" :key="key" :label="item.label" :value="item.value" />
</el-select>
{{- else }}
<el-input v-model="formData.{{.FieldJson}}" :clearable="{{.Clearable}}" placeholder="请输入{{.FieldDesc}}" />
{{- end }}
{{- end }}
{{- if eq .FieldType "richtext" }}
<RichEdit v-model="formData.{{.FieldJson}}"/>
{{- end }}
{{- if eq .FieldType "int" }}
<el-input v-model.number="formData.{{ .FieldJson }}" :clearable="{{.Clearable}}" placeholder="请输入" />
{{- end }}
{{- if eq .FieldType "time.Time" }}
<el-date-picker v-model="formData.{{ .FieldJson }}" type="date" placeholder="选择日期" :clearable="{{.Clearable}}"></el-date-picker>
{{- end }}
{{- if eq .FieldType "float64" }}
<el-input-number v-model="formData.{{ .FieldJson }}" :precision="2" :clearable="{{.Clearable}}"></el-input-number>
{{- end }}
{{- if eq .FieldType "enum" }}
<el-select v-model="formData.{{ .FieldJson }}" placeholder="请选择" style="width:100%" :clearable="{{.Clearable}}">
<el-option v-for="item in [{{ .DataTypeLong }}]" :key="item" :label="item" :value="item" />
</el-select>
{{- end }}
{{- if eq .FieldType "picture" }}
<SelectImage v-model="formData.{{ .FieldJson }}" file-type="image"/>
{{- end }}
{{- if eq .FieldType "video" }}
<SelectImage v-model="formData.{{ .FieldJson }}" file-type="video"/>
{{- end }}
{{- if eq .FieldType "pictures" }}
<SelectImage v-model="formData.{{ .FieldJson }}" multiple file-type="image"/>
{{- end }}
{{- if eq .FieldType "file" }}
<SelectFile v-model="formData.{{ .FieldJson }}" />
{{- end }}
{{- if eq .FieldType "json" }}
// 此字段为json结构可以前端自行控制展示和数据绑定模式 需绑定json的key为 formData.{{.FieldJson}} 后端会按照json的类型进行存取
{{"{{"}} formData.{{.FieldJson}} {{"}}"}}
{{- end }}
{{- if eq .FieldType "array" }}
<ArrayCtrl v-model="formData.{{ .FieldJson }}" editable/>
{{- end }}
{{- end }}
</el-form-item>
{{ GenerateFormItem . }}
{{- end }}
{{- end }}
<el-form-item>
@@ -333,42 +178,7 @@ const formData = ref({
{{- end }}
{{- range .Fields}}
{{- if .Form }}
{{- if eq .FieldType "bool" }}
{{.FieldJson}}: false,
{{- end }}
{{- if eq .FieldType "string" }}
{{.FieldJson}}: '',
{{- end }}
{{- if eq .FieldType "richtext" }}
{{.FieldJson}}: '',
{{- end }}
{{- if eq .FieldType "int" }}
{{.FieldJson}}: {{- if or .DataSource }} undefined{{ else }} 0{{- end }},
{{- end }}
{{- if eq .FieldType "time.Time" }}
{{.FieldJson}}: new Date(),
{{- end }}
{{- if eq .FieldType "float64" }}
{{.FieldJson}}: 0,
{{- end }}
{{- if eq .FieldType "picture" }}
{{.FieldJson}}: "",
{{- end }}
{{- if eq .FieldType "video" }}
{{.FieldJson}}: "",
{{- end }}
{{- if eq .FieldType "pictures" }}
{{.FieldJson}}: [],
{{- end }}
{{- if eq .FieldType "file" }}
{{.FieldJson}}: [],
{{- end }}
{{- if eq .FieldType "json" }}
{{.FieldJson}}: {},
{{- end }}
{{- if eq .FieldType "array" }}
{{.FieldJson}}: [],
{{- end }}
{{ GenerateDefaultFormValue . }}
{{- end }}
{{- end }}
})

View File

@@ -1,276 +1,35 @@
{{- $global := . }}
{{- $templateID := printf "%s_%s" .Package .StructName }}
{{- if .IsAdd }}
// 请在搜索条件中增加如下代码
{{- range .Fields}} {{- if .FieldSearchType}} {{- if eq .FieldType "bool" }}
<el-form-item label="{{.FieldDesc}}" prop="{{.FieldJson}}">
<el-select v-model="searchInfo.{{.FieldJson}}" clearable placeholder="请选择">
<el-option
key="true"
label="是"
value="true">
</el-option>
<el-option
key="false"
label="否"
value="false">
</el-option>
</el-select>
</el-form-item>
{{- else if .DictType}}
<el-form-item label="{{.FieldDesc}}" prop="{{.FieldJson}}">
<el-select {{if eq .FieldType "array"}}multiple {{end}}v-model="searchInfo.{{.FieldJson}}" clearable placeholder="请选择" @clear="()=>{searchInfo.{{.FieldJson}}=undefined}">
<el-option v-for="(item,key) in {{ .DictType }}Options" :key="key" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
{{- else if .CheckDataSource}}
<el-form-item label="{{.FieldDesc}}" prop="{{.FieldJson}}">
<el-select {{if eq .DataSource.Association 2}} multiple {{ end }} v-model="searchInfo.{{.FieldJson}}" placeholder="请选择{{.FieldDesc}}" :clearable="{{.Clearable}}" >
<el-option v-for="(item,key) in dataSource.{{.FieldJson}}" :key="key" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
{{- else}}
<el-form-item label="{{.FieldDesc}}" prop="{{.FieldJson}}">
{{- if eq .FieldType "float64" "int"}}
{{if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}}
<el-input v-model.number="searchInfo.start{{.FieldName}}" placeholder="最小值" />
<el-input v-model.number="searchInfo.end{{.FieldName}}" placeholder="最大值" />
{{- else}}
<el-input v-model.number="searchInfo.{{.FieldJson}}" placeholder="搜索条件" />
{{- end}}
{{- else if eq .FieldType "time.Time"}}
{{if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}}
<template #label>
<span>
{{.FieldDesc}}
<el-tooltip content="搜索范围是开始日期(包含)至结束日期(不包含)">
<el-icon><QuestionFilled /></el-icon>
</el-tooltip>
</span>
</template>
<el-date-picker v-model="searchInfo.start{{.FieldName}}" type="datetime" placeholder="开始日期" :disabled-date="time=> searchInfo.end{{.FieldName}} ? time.getTime() > searchInfo.end{{.FieldName}}.getTime() : false"></el-date-picker>
<el-date-picker v-model="searchInfo.end{{.FieldName}}" type="datetime" placeholder="结束日期" :disabled-date="time=> searchInfo.start{{.FieldName}} ? time.getTime() < searchInfo.start{{.FieldName}}.getTime() : false"></el-date-picker>
{{- else}}
<el-date-picker v-model="searchInfo.{{.FieldJson}}" type="datetime" placeholder="搜索条件"></el-date-picker>
{{- end}}
{{- else}}
<el-input v-model="searchInfo.{{.FieldJson}}" placeholder="搜索条件" />
{{- end}}
</el-form-item>{{ end }}{{ end }}{{ end }}
{{- range .Fields}}
{{- if .FieldSearchType}}
{{ GenerateSearchFormItem .}}
{{ end }}
{{ end }}
// 表格增加如下列代码
{{- range .Fields}}
{{- if .Table}}
{{- if .CheckDataSource }}
<el-table-column {{- if .Sort}} sortable{{- end}} align="left" label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="120">
<template #default="scope">
{{- if eq .DataSource.Association 2}}
<el-tag v-for="(item,key) in filterDataSource(dataSource.{{.FieldJson}},scope.row.{{.FieldJson}})" :key="key">
{{ "{{ item }}" }}
</el-tag>
{{- else }}
<span>{{"{{"}} filterDataSource(dataSource.{{.FieldJson}},scope.row.{{.FieldJson}}) {{"}}"}}</span>
{{- end }}
</template>
</el-table-column>
{{- else if .DictType}}
<el-table-column {{- if .Sort}} sortable{{- end}} align="left" label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="120">
<template #default="scope">
{{if eq .FieldType "array"}}
<el-tag class="mr-1" v-for="item in scope.row.{{.FieldJson}}" :key="item"> {{"{{"}} filterDict(item,{{.DictType}}Options) {{"}}"}}</el-tag>
{{- else }}
{{"{{"}} filterDict(scope.row.{{.FieldJson}},{{.DictType}}Options) {{"}}"}}
{{end}}
</template>
</el-table-column>
{{- else if eq .FieldType "bool" }}
<el-table-column {{- if .Sort}} sortable{{- end}} align="left" label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="120">
<template #default="scope">{{"{{"}} formatBoolean(scope.row.{{.FieldJson}}) {{"}}"}}</template>
</el-table-column>
{{- else if eq .FieldType "time.Time" }}
<el-table-column {{- if .Sort}} sortable{{- end}} align="left" label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="180">
<template #default="scope">{{"{{"}} formatDate(scope.row.{{.FieldJson}}) {{"}}"}}</template>
</el-table-column>
{{- else if eq .FieldType "picture" }}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="200">
<template #default="scope">
<el-image preview-teleported style="width: 100px; height: 100px" :src="getUrl(scope.row.{{.FieldJson}})" fit="cover"/>
</template>
</el-table-column>
{{- else if eq .FieldType "pictures" }}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="200">
<template #default="scope">
<div class="multiple-img-box">
<el-image preview-teleported v-for="(item,index) in scope.row.{{.FieldJson}}" :key="index" style="width: 80px; height: 80px" :src="getUrl(item)" fit="cover"/>
</div>
</template>
</el-table-column>
{{- else if eq .FieldType "video" }}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="200">
<template #default="scope">
<video
style="width: 100px; height: 100px"
muted
preload="metadata"
>
<source :src="getUrl(scope.row.{{.FieldJson}}) + '#t=1'">
</video>
</template>
</el-table-column>
{{- else if eq .FieldType "richtext" }}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="200">
<template #default="scope">
[富文本内容]
</template>
</el-table-column>
{{- else if eq .FieldType "file" }}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="200">
<template #default="scope">
<div class="file-list">
<el-tag v-for="file in scope.row.{{.FieldJson}}" :key="file.uid" @click="onDownloadFile(file.url)">{{"{{"}}file.name{{"}}"}}</el-tag>
</div>
</template>
</el-table-column>
{{- else if eq .FieldType "json" }}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="200">
<template #default="scope">
[JSON]
</template>
</el-table-column>
{{- else if eq .FieldType "array" }}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="200">
<template #default="scope">
<ArrayCtrl v-model="scope.row.{{ .FieldJson }}"/>
</template>
</el-table-column>
{{- else }}
<el-table-column {{- if .Sort}} sortable{{- end}} align="left" label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="120" />
{{- end }}
{{- end }}
{{- end }}
{{- if .Table}}
{{ GenerateTableColumn . }}
{{- end }}
{{- end }}
// 新增表单中增加如下代码
{{- range .Fields}}
{{- if .Form}}
<el-form-item label="{{.FieldDesc}}:" prop="{{.FieldJson}}" >
{{- if .CheckDataSource}}
<el-select {{if eq .DataSource.Association 2}} multiple {{ end }} v-model="formData.{{.FieldJson}}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="(item,key) in dataSource.{{.FieldJson}}" :key="key" :label="item.label" :value="item.value" />
</el-select>
{{- else }}
{{- if eq .FieldType "bool" }}
<el-switch v-model="formData.{{.FieldJson}}" active-color="#13ce66" inactive-color="#ff4949" active-text="是" inactive-text="否" clearable ></el-switch>
{{- end }}
{{- if eq .FieldType "string" }}
{{- if .DictType}}
<el-select v-model="formData.{{ .FieldJson }}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="(item,key) in {{ .DictType }}Options" :key="key" :label="item.label" :value="item.value" />
</el-select>
{{- else }}
<el-input v-model="formData.{{.FieldJson}}" :clearable="{{.Clearable}}" placeholder="请输入{{.FieldDesc}}" />
{{- end }}
{{- end }}
{{- if eq .FieldType "richtext" }}
<RichEdit v-model="formData.{{.FieldJson}}"/>
{{- end }}
{{- if eq .FieldType "json" }}
// 此字段为json结构可以前端自行控制展示和数据绑定模式 需绑定json的key为 formData.{{.FieldJson}} 后端会按照json的类型进行存取
{{"{{"}} formData.{{.FieldJson}} {{"}}"}}
{{- end }}
{{- if eq .FieldType "array" }}
{{- if .DictType}}
<el-select {{if eq .FieldType "array"}}multiple {{end}}v-model="formData.{{ .FieldJson }}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="(item,key) in {{ .DictType }}Options" :key="key" :label="item.label" :value="item.value" />
</el-select>
{{- else }}
<ArrayCtrl v-model="formData.{{ .FieldJson }}" editable/>
{{- end }}
{{- end }}
{{- if eq .FieldType "int" }}
<el-input v-model.number="formData.{{ .FieldJson }}" :clearable="{{.Clearable}}" placeholder="请输入{{.FieldDesc}}" />
{{- end }}
{{- if eq .FieldType "time.Time" }}
<el-date-picker v-model="formData.{{ .FieldJson }}" type="date" style="width:100%" placeholder="选择日期" :clearable="{{.Clearable}}" />
{{- end }}
{{- if eq .FieldType "float64" }}
<el-input-number v-model="formData.{{ .FieldJson }}" style="width:100%" :precision="2" :clearable="{{.Clearable}}" />
{{- end }}
{{- if eq .FieldType "enum" }}
<el-select v-model="formData.{{ .FieldJson }}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="item in [{{.DataTypeLong}}]" :key="item" :label="item" :value="item" />
</el-select>
{{- end }}
{{- if eq .FieldType "picture" }}
<SelectImage
v-model="formData.{{ .FieldJson }}"
file-type="image"
/>
{{- end }}
{{- if eq .FieldType "pictures" }}
<SelectImage
multiple
v-model="formData.{{ .FieldJson }}"
file-type="image"
/>
{{- end }}
{{- if eq .FieldType "video" }}
<SelectImage
v-model="formData.{{ .FieldJson }}"
file-type="video"
/>
{{- end }}
{{- if eq .FieldType "file" }}
<SelectFile v-model="formData.{{ .FieldJson }}" />
{{- end }}
{{- end }}
</el-form-item>
{{- end }}
{{- end }}
{{- if .Form}}
{{ GenerateFormItem . }}
{{- end }}
{{- end }}
// 查看抽屉中增加如下代码
{{- range .Fields}}
{{- if .Desc }}
<el-descriptions-item label="{{ .FieldDesc }}">
{{- if .CheckDataSource }}
<template #default="scope">
{{- if eq .DataSource.Association 2}}
<el-tag v-for="(item,key) in filterDataSource(dataSource.{{.FieldJson}},detailFrom.{{.FieldJson}})" :key="key">
{{ "{{ item }}" }}
</el-tag>
{{- else }}
<span>{{"{{"}} filterDataSource(dataSource.{{.FieldJson}},detailFrom.{{.FieldJson}}) {{"}}"}}</span>
{{- end }}
</template>
{{- else if and (ne .FieldType "picture" ) (ne .FieldType "pictures" ) (ne .FieldType "file" ) (ne .FieldType "array" ) }}
{{"{{"}} detailFrom.{{.FieldJson}} {{"}}"}}
{{- else }}
{{- if eq .FieldType "picture" }}
<el-image style="width: 50px; height: 50px" :preview-src-list="returnArrImg(detailFrom.{{ .FieldJson }})" :src="getUrl(detailFrom.{{ .FieldJson }})" fit="cover" />
{{- end }}
{{- if eq .FieldType "array" }}
<ArrayCtrl v-model="detailFrom.{{ .FieldJson }}"/>
{{- end }}
{{- if eq .FieldType "pictures" }}
<el-image style="width: 50px; height: 50px; margin-right: 10px" :preview-src-list="returnArrImg(detailFrom.{{ .FieldJson }})" :initial-index="index" v-for="(item,index) in detailFrom.{{ .FieldJson }}" :key="index" :src="getUrl(item)" fit="cover" />
{{- end }}
{{- if eq .FieldType "richtext" }}
<RichView v-model="detailFrom.{{.FieldJson}}" />
{{- end }}
{{- if eq .FieldType "file" }}
<div class="fileBtn" v-for="(item,index) in detailFrom.{{ .FieldJson }}" :key="index">
<el-button type="primary" text bg @click="onDownloadFile(item.url)">
<el-icon style="margin-right: 5px"><Download /></el-icon>
{{"{{"}}item.name{{"}}"}}
</el-button>
</div>
{{- end }}
{{- end }}
</el-descriptions-item>
{{ GenerateDescriptionItem . }}
{{- end }}
{{- end }}
@@ -288,42 +47,7 @@ const {{ $element }}Options = ref([])
// 基础formData结构变量处和关闭表单处增加如下字段
{{- range .Fields}}
{{- if .Form}}
{{- if eq .FieldType "bool" }}
{{.FieldJson}}: false,
{{- end }}
{{- if eq .FieldType "string" }}
{{.FieldJson}}: '',
{{- end }}
{{- if eq .FieldType "richtext" }}
{{.FieldJson}}: '',
{{- end }}
{{- if eq .FieldType "int" }}
{{.FieldJson}}: {{- if .DataSource}} undefined{{ else }} 0{{- end }},
{{- end }}
{{- if eq .FieldType "time.Time" }}
{{.FieldJson}}: new Date(),
{{- end }}
{{- if eq .FieldType "float64" }}
{{.FieldJson}}: 0,
{{- end }}
{{- if eq .FieldType "picture" }}
{{.FieldJson}}: "",
{{- end }}
{{- if eq .FieldType "video" }}
{{.FieldJson}}: "",
{{- end }}
{{- if eq .FieldType "pictures" }}
{{.FieldJson}}: [],
{{- end }}
{{- if eq .FieldType "file" }}
{{.FieldJson}}: [],
{{- end }}
{{- if eq .FieldType "json" }}
{{.FieldJson}}: {},
{{- end }}
{{- if eq .FieldType "array" }}
{{.FieldJson}}: [],
{{- end }}
{{ GenerateDefaultFormValue . }}
{{- end }}
{{- end }}
// 验证规则中增加如下字段
@@ -372,9 +96,9 @@ getDataSourceFunc()
<div>
{{- if not .IsTree }}
<div class="gva-search-box">
<el-form ref="elSearchFormRef" :inline="true" :model="searchInfo" class="demo-form-inline" :rules="searchRule" @keyup.enter="onSubmit">
<el-form ref="elSearchFormRef" :inline="true" :model="searchInfo" class="demo-form-inline" @keyup.enter="onSubmit">
{{- if .GvaModel }}
<el-form-item label="创建日期" prop="createdAt">
<el-form-item label="创建日期" prop="createdAtRange">
<template #label>
<span>
创建日期
@@ -383,124 +107,26 @@ getDataSourceFunc()
</el-tooltip>
</span>
</template>
<el-date-picker v-model="searchInfo.startCreatedAt" type="datetime" placeholder="开始日期" :disabled-date="time=> searchInfo.endCreatedAt ? time.getTime() > searchInfo.endCreatedAt.getTime() : false"></el-date-picker>
<el-date-picker v-model="searchInfo.endCreatedAt" type="datetime" placeholder="结束日期" :disabled-date="time=> searchInfo.startCreatedAt ? time.getTime() < searchInfo.startCreatedAt.getTime() : false"></el-date-picker>
</el-form-item>
<el-date-picker
v-model="searchInfo.createdAtRange"
class="w-[380px]"
type="datetimerange"
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
/>
</el-form-item>
{{ end -}}
{{- range .Fields}} {{- if .FieldSearchType}} {{- if not .FieldSearchHide }} {{- if eq .FieldType "bool" }}
<el-form-item label="{{.FieldDesc}}" prop="{{.FieldJson}}">
<el-select v-model="searchInfo.{{.FieldJson}}" clearable placeholder="请选择">
<el-option
key="true"
label="是"
value="true">
</el-option>
<el-option
key="false"
label="否"
value="false">
</el-option>
</el-select>
</el-form-item>
{{- else if .DictType}}
<el-form-item label="{{.FieldDesc}}" prop="{{.FieldJson}}">
<el-select {{if eq .FieldType "array"}} multiple {{end}}v-model="searchInfo.{{.FieldJson}}" clearable placeholder="请选择" @clear="()=>{searchInfo.{{.FieldJson}}=undefined}">
<el-option v-for="(item,key) in {{ .DictType }}Options" :key="key" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
{{- else if .CheckDataSource}}
<el-form-item label="{{.FieldDesc}}" prop="{{.FieldJson}}">
<el-select {{if eq .DataSource.Association 2}} multiple {{ end }} v-model="searchInfo.{{.FieldJson}}" placeholder="请选择{{.FieldDesc}}" :clearable="{{.Clearable}}" >
<el-option v-for="(item,key) in dataSource.{{.FieldJson}}" :key="key" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
{{- else}}
<el-form-item label="{{.FieldDesc}}" prop="{{.FieldJson}}">
{{- if eq .FieldType "float64" "int"}}
{{if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}}
<el-input v-model.number="searchInfo.start{{.FieldName}}" placeholder="最小值" />
<el-input v-model.number="searchInfo.end{{.FieldName}}" placeholder="最大值" />
{{- else}}
<el-input v-model.number="searchInfo.{{.FieldJson}}" placeholder="搜索条件" />
{{- end}}
{{- else if eq .FieldType "time.Time"}}
{{if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}}
<template #label>
<span>
{{.FieldDesc}}
<el-tooltip content="搜索范围是开始日期(包含)至结束日期(不包含)">
<el-icon><QuestionFilled /></el-icon>
</el-tooltip>
</span>
</template>
<el-date-picker v-model="searchInfo.start{{.FieldName}}" type="datetime" placeholder="开始日期" :disabled-date="time=> searchInfo.end{{.FieldName}} ? time.getTime() > searchInfo.end{{.FieldName}}.getTime() : false"></el-date-picker>
<el-date-picker v-model="searchInfo.end{{.FieldName}}" type="datetime" placeholder="结束日期" :disabled-date="time=> searchInfo.start{{.FieldName}} ? time.getTime() < searchInfo.start{{.FieldName}}.getTime() : false"></el-date-picker>
{{- else}}
<el-date-picker v-model="searchInfo.{{.FieldJson}}" type="datetime" placeholder="搜索条件"></el-date-picker>
{{- end}}
{{- else}}
<el-input v-model="searchInfo.{{.FieldJson}}" placeholder="搜索条件" />
{{- end}}
</el-form-item>{{ end }}{{ end }}{{ end }}{{ end }}
{{- range .Fields}} {{- if .FieldSearchType}} {{- if not .FieldSearchHide }}
{{ GenerateSearchFormItem .}}
{{ end }}{{ end }}{{ end }}
<template v-if="showAllQuery">
<!-- 将需要控制显示状态的查询条件添加到此范围内 -->
{{- range .Fields}} {{- if .FieldSearchType}} {{- if .FieldSearchHide }} {{- if eq .FieldType "bool" }}
<el-form-item label="{{.FieldDesc}}" prop="{{.FieldJson}}">
<el-select v-model="searchInfo.{{.FieldJson}}" clearable placeholder="请选择">
<el-option
key="true"
label="是"
value="true">
</el-option>
<el-option
key="false"
label="否"
value="false">
</el-option>
</el-select>
</el-form-item>
{{- else if .DictType}}
<el-form-item label="{{.FieldDesc}}" prop="{{.FieldJson}}">
<el-select {{if eq .FieldType "array"}}multiple {{end}}v-model="searchInfo.{{.FieldJson}}" clearable placeholder="请选择" @clear="()=>{searchInfo.{{.FieldJson}}=undefined}">
<el-option v-for="(item,key) in {{ .DictType }}Options" :key="key" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
{{- else}}
<el-form-item label="{{.FieldDesc}}" prop="{{.FieldJson}}">
{{- if eq .FieldType "float64" "int"}}
{{if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}}
<el-input v-model.number="searchInfo.start{{.FieldName}}" placeholder="最小值" />
<el-input v-model.number="searchInfo.end{{.FieldName}}" placeholder="最大值" />
{{- else}}
<el-input v-model.number="searchInfo.{{.FieldJson}}" placeholder="搜索条件" />
{{- end}}
{{- else if eq .FieldType "time.Time"}}
{{if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}}
<template #label>
<span>
{{.FieldDesc}}
<el-tooltip content="搜索范围是开始日期(包含)至结束日期(不包含)">
<el-icon><QuestionFilled /></el-icon>
</el-tooltip>
</span>
</template>
<el-date-picker v-model="searchInfo.start{{.FieldName}}" type="datetime" placeholder="开始日期" :disabled-date="time=> searchInfo.end{{.FieldName}} ? time.getTime() > searchInfo.end{{.FieldName}}.getTime() : false"></el-date-picker>
<el-date-picker v-model="searchInfo.end{{.FieldName}}" type="datetime" placeholder="结束日期" :disabled-date="time=> searchInfo.start{{.FieldName}} ? time.getTime() < searchInfo.start{{.FieldName}}.getTime() : false"></el-date-picker>
{{- else}}
<el-date-picker v-model="searchInfo.{{.FieldJson}}" type="datetime" placeholder="搜索条件"></el-date-picker>
{{- end}}
{{- else}}
<el-input v-model="searchInfo.{{.FieldJson}}" placeholder="搜索条件" />
{{- end}}
</el-form-item>
{{ end }}{{ end }}{{ end }}{{ end }}
{{- range .Fields}} {{- if .FieldSearchType}} {{- if .FieldSearchHide }}
{{ GenerateSearchFormItem .}}
{{ end }}{{ end }}{{ end }}
</template>
<el-form-item>
@@ -518,7 +144,7 @@ getDataSourceFunc()
<el-button {{ if $global.AutoCreateBtnAuth }}v-auth="btnAuth.batchDelete"{{ end }} icon="delete" style="margin-left: 10px;" :disabled="!multipleSelection.length" @click="onDelete">删除</el-button>
{{ if .HasExcel -}}
<ExportTemplate {{ if $global.AutoCreateBtnAuth }}v-auth="btnAuth.exportTemplate"{{ end }} template-id="{{$templateID}}" />
<ExportExcel {{ if $global.AutoCreateBtnAuth }}v-auth="btnAuth.exportExcel"{{ end }} template-id="{{$templateID}}" />
<ExportExcel {{ if $global.AutoCreateBtnAuth }}v-auth="btnAuth.exportExcel"{{ end }} template-id="{{$templateID}}" filterDeleted/>
<ImportExcel {{ if $global.AutoCreateBtnAuth }}v-auth="btnAuth.importExcel"{{ end }} template-id="{{$templateID}}" @on-success="getTableData" />
{{- end }}
</div>
@@ -535,97 +161,13 @@ getDataSourceFunc()
>
<el-table-column type="selection" width="55" />
{{ if .GvaModel }}
<el-table-column 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 }}
{{- range .Fields}}
{{- if .Table}}
{{- if .CheckDataSource }}
<el-table-column {{- if .Sort}} sortable{{- end}} align="left" label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="120">
<template #default="scope">
{{- if eq .DataSource.Association 2}}
<el-tag v-for="(item,key) in filterDataSource(dataSource.{{.FieldJson}},scope.row.{{.FieldJson}})" :key="key">
{{ "{{ item }}" }}
</el-tag>
{{- else }}
<span>{{"{{"}} filterDataSource(dataSource.{{.FieldJson}},scope.row.{{.FieldJson}}) {{"}}"}}</span>
{{- end }}
</template>
</el-table-column>
{{- else if .DictType}}
<el-table-column {{- if .Sort}} sortable{{- end}} align="left" label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="120">
<template #default="scope">
{{if eq .FieldType "array"}}
<el-tag class="mr-1" v-for="item in scope.row.{{.FieldJson}}" :key="item"> {{"{{"}} filterDict(item,{{.DictType}}Options) {{"}}"}}</el-tag>
{{- else }}
{{"{{"}} filterDict(scope.row.{{.FieldJson}},{{.DictType}}Options) {{"}}"}}
{{end}}
</template>
</el-table-column>
{{- else if eq .FieldType "bool" }}
<el-table-column {{- if .Sort}} sortable{{- end}} align="left" label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="120">
<template #default="scope">{{"{{"}} formatBoolean(scope.row.{{.FieldJson}}) {{"}}"}}</template>
</el-table-column>
{{- else if eq .FieldType "time.Time" }}
<el-table-column {{- if .Sort}} sortable{{- end}} align="left" label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="180">
<template #default="scope">{{"{{"}} formatDate(scope.row.{{.FieldJson}}) {{"}}"}}</template>
</el-table-column>
{{- else if eq .FieldType "picture" }}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="200">
<template #default="scope">
<el-image preview-teleported style="width: 100px; height: 100px" :src="getUrl(scope.row.{{.FieldJson}})" fit="cover"/>
</template>
</el-table-column>
{{- else if eq .FieldType "pictures" }}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="200">
<template #default="scope">
<div class="multiple-img-box">
<el-image preview-teleported v-for="(item,index) in scope.row.{{.FieldJson}}" :key="index" style="width: 80px; height: 80px" :src="getUrl(item)" fit="cover"/>
</div>
</template>
</el-table-column>
{{- else if eq .FieldType "video" }}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="200">
<template #default="scope">
<video
style="width: 100px; height: 100px"
muted
preload="metadata"
>
<source :src="getUrl(scope.row.{{.FieldJson}}) + '#t=1'">
</video>
</template>
</el-table-column>
{{- else if eq .FieldType "richtext" }}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="200">
<template #default="scope">
[富文本内容]
</template>
</el-table-column>
{{- else if eq .FieldType "file" }}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="200">
<template #default="scope">
<div class="file-list">
<el-tag v-for="file in scope.row.{{.FieldJson}}" :key="file.uid" @click="onDownloadFile(file.url)">{{"{{"}}file.name{{"}}"}}</el-tag>
</div>
</template>
</el-table-column>
{{- else if eq .FieldType "json" }}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="200">
<template #default="scope">
[JSON]
</template>
</el-table-column>
{{- else if eq .FieldType "array" }}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="200">
<template #default="scope">
<ArrayCtrl v-model="scope.row.{{ .FieldJson }}"/>
</template>
</el-table-column>
{{- else }}
<el-table-column {{- if .Sort}} sortable{{- end}} align="left" label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="120" />
{{- end }}
{{ GenerateTableColumn . }}
{{- end }}
{{- end }}
<el-table-column align="left" label="操作" fixed="right" :min-width="appStore.operateMinWith">
@@ -679,78 +221,7 @@ getDataSourceFunc()
{{- end }}
{{- range .Fields}}
{{- if .Form}}
<el-form-item label="{{.FieldDesc}}:" prop="{{.FieldJson}}" >
{{- if .CheckDataSource}}
<el-select {{if eq .DataSource.Association 2}} multiple {{ end }} v-model="formData.{{.FieldJson}}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="(item,key) in dataSource.{{.FieldJson}}" :key="key" :label="item.label" :value="item.value" />
</el-select>
{{- else }}
{{- if eq .FieldType "bool" }}
<el-switch v-model="formData.{{.FieldJson}}" active-color="#13ce66" inactive-color="#ff4949" active-text="是" inactive-text="否" clearable ></el-switch>
{{- end }}
{{- if eq .FieldType "string" }}
{{- if .DictType}}
<el-select v-model="formData.{{ .FieldJson }}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="(item,key) in {{ .DictType }}Options" :key="key" :label="item.label" :value="item.value" />
</el-select>
{{- else }}
<el-input v-model="formData.{{.FieldJson}}" :clearable="{{.Clearable}}" placeholder="请输入{{.FieldDesc}}" />
{{- end }}
{{- end }}
{{- if eq .FieldType "richtext" }}
<RichEdit v-model="formData.{{.FieldJson}}"/>
{{- end }}
{{- if eq .FieldType "json" }}
// 此字段为json结构可以前端自行控制展示和数据绑定模式 需绑定json的key为 formData.{{.FieldJson}} 后端会按照json的类型进行存取
{{"{{"}} formData.{{.FieldJson}} {{"}}"}}
{{- end }}
{{- if eq .FieldType "array" }}
{{- if .DictType}}
<el-select multiple v-model="formData.{{ .FieldJson }}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="(item,key) in {{ .DictType }}Options" :key="key" :label="item.label" :value="item.value" />
</el-select>
{{- else }}
<ArrayCtrl v-model="formData.{{ .FieldJson }}" editable/>
{{- end }}
{{- end }}
{{- if eq .FieldType "int" }}
<el-input v-model.number="formData.{{ .FieldJson }}" :clearable="{{.Clearable}}" placeholder="请输入{{.FieldDesc}}" />
{{- end }}
{{- if eq .FieldType "time.Time" }}
<el-date-picker v-model="formData.{{ .FieldJson }}" type="date" style="width:100%" placeholder="选择日期" :clearable="{{.Clearable}}" />
{{- end }}
{{- if eq .FieldType "float64" }}
<el-input-number v-model="formData.{{ .FieldJson }}" style="width:100%" :precision="2" :clearable="{{.Clearable}}" />
{{- end }}
{{- if eq .FieldType "enum" }}
<el-select v-model="formData.{{ .FieldJson }}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="item in [{{.DataTypeLong}}]" :key="item" :label="item" :value="item" />
</el-select>
{{- end }}
{{- if eq .FieldType "picture" }}
<SelectImage
v-model="formData.{{ .FieldJson }}"
file-type="image"
/>
{{- end }}
{{- if eq .FieldType "pictures" }}
<SelectImage
multiple
v-model="formData.{{ .FieldJson }}"
file-type="image"
/>
{{- end }}
{{- if eq .FieldType "video" }}
<SelectImage
v-model="formData.{{ .FieldJson }}"
file-type="video"
/>
{{- end }}
{{- if eq .FieldType "file" }}
<SelectFile v-model="formData.{{ .FieldJson }}" />
{{- end }}
{{- end }}
</el-form-item>
{{ GenerateFormItem . }}
{{- end }}
{{- end }}
</el-form>
@@ -761,7 +232,7 @@ getDataSourceFunc()
{{- if .IsTree }}
<el-descriptions-item label="父节点">
<el-tree-select
v-model="detailFrom.parentID"
v-model="detailForm.parentID"
:data="[rootNode,...tableData]"
check-strictly
disabled
@@ -775,46 +246,7 @@ getDataSourceFunc()
{{- end }}
{{- range .Fields}}
{{- if .Desc }}
<el-descriptions-item label="{{ .FieldDesc }}">
{{- if .CheckDataSource }}
{{- if eq .DataSource.Association 2}}
<el-tag v-for="(item,key) in filterDataSource(dataSource.{{.FieldJson}},detailFrom.{{.FieldJson}})" :key="key">
{{ "{{ item }}" }}
</el-tag>
{{- else }}
<span>{{"{{"}} filterDataSource(dataSource.{{.FieldJson}},detailFrom.{{.FieldJson}}) {{"}}"}}</span>
{{- end }}
{{- else if .DictType}}
{{if eq .FieldType "array"}}
<el-tag class="mr-1" v-for="item in detailFrom.{{.FieldJson}}" :key="item"> {{"{{"}} filterDict(item,{{.DictType}}Options) {{"}}"}}</el-tag>
{{- else }}
{{"{{"}} filterDict(detailFrom.{{.FieldJson}},{{.DictType}}Options) {{"}}"}}
{{end}}
{{- else if and (ne .FieldType "picture" ) (ne .FieldType "richtext" ) (ne .FieldType "pictures" ) (ne .FieldType "file" ) (ne .FieldType "array" ) }}
{{"{{"}} detailFrom.{{.FieldJson}} {{"}}"}}
{{- else }}
{{- if eq .FieldType "picture" }}
<el-image style="width: 50px; height: 50px" :preview-src-list="returnArrImg(detailFrom.{{ .FieldJson }})" :src="getUrl(detailFrom.{{ .FieldJson }})" fit="cover" />
{{- end }}
{{- if eq .FieldType "array" }}
<ArrayCtrl v-model="detailFrom.{{ .FieldJson }}"/>
{{- end }}
{{- if eq .FieldType "pictures" }}
<el-image style="width: 50px; height: 50px; margin-right: 10px" :preview-src-list="returnArrImg(detailFrom.{{ .FieldJson }})" :initial-index="index" v-for="(item,index) in detailFrom.{{ .FieldJson }}" :key="index" :src="getUrl(item)" fit="cover" />
{{- end }}
{{- if eq .FieldType "richtext" }}
<RichView v-model="detailFrom.{{.FieldJson}}" />
{{- end }}
{{- if eq .FieldType "file" }}
<div class="fileBtn" v-for="(item,index) in detailFrom.{{ .FieldJson }}" :key="index">
<el-button type="primary" text bg @click="onDownloadFile(item.url)">
<el-icon style="margin-right: 5px"><Download /></el-icon>
{{"{{"}}item.name{{"}}"}}
</el-button>
</div>
{{- end }}
{{- end }}
</el-descriptions-item>
{{ GenerateDescriptionItem . }}
{{- end }}
{{- end }}
</el-descriptions>
@@ -906,42 +338,7 @@ const formData = ref({
{{- end }}
{{- range .Fields}}
{{- if .Form}}
{{- if eq .FieldType "bool" }}
{{.FieldJson}}: false,
{{- end }}
{{- if eq .FieldType "string" }}
{{.FieldJson}}: '',
{{- end }}
{{- if eq .FieldType "richtext" }}
{{.FieldJson}}: '',
{{- end }}
{{- if eq .FieldType "int" }}
{{.FieldJson}}: {{- if .DataSource}} undefined{{ else }} 0{{- end }},
{{- end }}
{{- if eq .FieldType "time.Time" }}
{{.FieldJson}}: new Date(),
{{- end }}
{{- if eq .FieldType "float64" }}
{{.FieldJson}}: 0,
{{- end }}
{{- if eq .FieldType "picture" }}
{{.FieldJson}}: "",
{{- end }}
{{- if eq .FieldType "video" }}
{{.FieldJson}}: "",
{{- end }}
{{- if eq .FieldType "pictures" }}
{{.FieldJson}}: [],
{{- end }}
{{- if eq .FieldType "file" }}
{{.FieldJson}}: [],
{{- end }}
{{- if eq .FieldType "json" }}
{{.FieldJson}}: {},
{{- end }}
{{- if eq .FieldType "array" }}
{{.FieldJson}}: [],
{{- end }}
{{ GenerateDefaultFormValue . }}
{{- end }}
{{- end }}
})
@@ -982,39 +379,6 @@ const rule = reactive({
{{- end }}
})
const searchRule = reactive({
createdAt: [
{ validator: (rule, value, callback) => {
if (searchInfo.value.startCreatedAt && !searchInfo.value.endCreatedAt) {
callback(new Error('请填写结束日期'))
} else if (!searchInfo.value.startCreatedAt && searchInfo.value.endCreatedAt) {
callback(new Error('请填写开始日期'))
} else if (searchInfo.value.startCreatedAt && searchInfo.value.endCreatedAt && (searchInfo.value.startCreatedAt.getTime() === searchInfo.value.endCreatedAt.getTime() || searchInfo.value.startCreatedAt.getTime() > searchInfo.value.endCreatedAt.getTime())) {
callback(new Error('开始日期应当早于结束日期'))
} else {
callback()
}
}, trigger: 'change' }
],
{{- range .Fields }}
{{- if .FieldSearchType}}
{{- if eq .FieldType "time.Time" }}
{{.FieldJson }} : [{ validator: (rule, value, callback) => {
if (searchInfo.value.start{{.FieldName}} && !searchInfo.value.end{{.FieldName}}) {
callback(new Error('请填写结束日期'))
} else if (!searchInfo.value.start{{.FieldName}} && searchInfo.value.end{{.FieldName}}) {
callback(new Error('请填写开始日期'))
} else if (searchInfo.value.start{{.FieldName}} && searchInfo.value.end{{.FieldName}} && (searchInfo.value.start{{.FieldName}}.getTime() === searchInfo.value.end{{.FieldName}}.getTime() || searchInfo.value.start{{.FieldName}}.getTime() > searchInfo.value.end{{.FieldName}}.getTime())) {
callback(new Error('开始日期应当早于结束日期'))
} else {
callback()
}
}, trigger: 'change' }],
{{- end }}
{{- end }}
{{- end }}
})
const elFormRef = ref()
const elSearchFormRef = ref()
@@ -1029,6 +393,8 @@ const searchInfo = ref({})
// 排序
const sortChange = ({ prop, order }) => {
const sortMap = {
CreatedAt:"created_at",
ID:"id",
{{- range .Fields}}
{{- if .Table}}
{{- if and .Sort}}
@@ -1229,42 +595,7 @@ const closeDialog = () => {
formData.value = {
{{- range .Fields}}
{{- if .Form}}
{{- if eq .FieldType "bool" }}
{{.FieldJson}}: false,
{{- end }}
{{- if eq .FieldType "string" }}
{{.FieldJson}}: '',
{{- end }}
{{- if eq .FieldType "richtext" }}
{{.FieldJson}}: '',
{{- end }}
{{- if eq .FieldType "int" }}
{{.FieldJson}}: {{- if .DataSource }} undefined{{ else }} 0{{- end }},
{{- end }}
{{- if eq .FieldType "time.Time" }}
{{.FieldJson}}: new Date(),
{{- end }}
{{- if eq .FieldType "float64" }}
{{.FieldJson}}: 0,
{{- end }}
{{- if eq .FieldType "picture" }}
{{.FieldJson}}: "",
{{- end }}
{{- if eq .FieldType "video" }}
{{.FieldJson}}: "",
{{- end }}
{{- if eq .FieldType "pictures" }}
{{.FieldJson}}: [],
{{- end }}
{{- if eq .FieldType "file" }}
{{.FieldJson}}: [],
{{- end }}
{{- if eq .FieldType "json" }}
{{.FieldJson}}: {},
{{- end }}
{{- if eq .FieldType "array" }}
{{.FieldJson}}: [],
{{- end }}
{{ GenerateDefaultFormValue . }}
{{- end }}
{{- end }}
}
@@ -1298,7 +629,7 @@ const enterDialog = async () => {
})
}
const detailFrom = ref({})
const detailForm = ref({})
// 查看详情控制标记
const detailShow = ref(false)
@@ -1315,7 +646,7 @@ const getDetails = async (row) => {
//
const res = await find{{.StructName}}({ {{.PrimaryField.FieldJson}}: row.{{.PrimaryField.FieldJson}} })
if (res.code === 0) {
detailFrom.value = res.data
detailForm.value = res.data
openDetailShow()
}
}
@@ -1324,7 +655,7 @@ const getDetails = async (row) => {
// 关闭详情弹窗
const closeDetailShow = () => {
detailShow.value = false
detailFrom.value = {}
detailForm.value = {}
}

View File

@@ -0,0 +1,255 @@
package api
import (
{{if not .OnlyTemplate}}
"{{.Module}}/global"
"{{.Module}}/model/common/response"
"{{.Module}}/plugin/{{.Package}}/model"
{{- if not .IsTree}}
"{{.Module}}/plugin/{{.Package}}/model/request"
{{- end }}
"github.com/gin-gonic/gin"
"go.uber.org/zap"
{{- if .AutoCreateResource}}
"{{.Module}}/utils"
{{- end }}
{{- else }}
"{{.Module}}/model/common/response"
"github.com/gin-gonic/gin"
{{- end }}
)
var {{.StructName}} = new({{.Abbreviation}})
type {{.Abbreviation}} struct {}
{{if not .OnlyTemplate}}
// Create{{.StructName}} 创建{{.Description}}
// @Tags {{.StructName}}
// @Summary 创建{{.Description}}
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param data body model.{{.StructName}} true "创建{{.Description}}"
// @Success 200 {object} response.Response{msg=string} "创建成功"
// @Router /{{.Abbreviation}}/create{{.StructName}} [post]
func (a *{{.Abbreviation}}) Create{{.StructName}}(c *gin.Context) {
// Context
ctx := c.Request.Context()
var info model.{{.StructName}}
err := c.ShouldBindJSON(&info)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
{{- if .AutoCreateResource }}
info.CreatedBy = utils.GetUserID(c)
{{- end }}
err = service{{ .StructName }}.Create{{.StructName}}(ctx,&info)
if err != nil {
global.GVA_LOG.Error("创建失败!", zap.Error(err))
response.FailWithMessage("创建失败:" + err.Error(), c)
return
}
response.OkWithMessage("创建成功", c)
}
// Delete{{.StructName}} 删除{{.Description}}
// @Tags {{.StructName}}
// @Summary 删除{{.Description}}
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param data body model.{{.StructName}} true "删除{{.Description}}"
// @Success 200 {object} response.Response{msg=string} "删除成功"
// @Router /{{.Abbreviation}}/delete{{.StructName}} [delete]
func (a *{{.Abbreviation}}) Delete{{.StructName}}(c *gin.Context) {
// Context
ctx := c.Request.Context()
{{.PrimaryField.FieldJson}} := c.Query("{{.PrimaryField.FieldJson}}")
{{- if .AutoCreateResource }}
userID := utils.GetUserID(c)
{{- end }}
err := service{{ .StructName }}.Delete{{.StructName}}(ctx,{{.PrimaryField.FieldJson}} {{- if .AutoCreateResource -}},userID{{- end -}})
if err != nil {
global.GVA_LOG.Error("删除失败!", zap.Error(err))
response.FailWithMessage("删除失败:" + err.Error(), c)
return
}
response.OkWithMessage("删除成功", c)
}
// Delete{{.StructName}}ByIds 批量删除{{.Description}}
// @Tags {{.StructName}}
// @Summary 批量删除{{.Description}}
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Success 200 {object} response.Response{msg=string} "批量删除成功"
// @Router /{{.Abbreviation}}/delete{{.StructName}}ByIds [delete]
func (a *{{.Abbreviation}}) Delete{{.StructName}}ByIds(c *gin.Context) {
// Context
ctx := c.Request.Context()
{{.PrimaryField.FieldJson}}s := c.QueryArray("{{.PrimaryField.FieldJson}}s[]")
{{- if .AutoCreateResource }}
userID := utils.GetUserID(c)
{{- end }}
err := service{{ .StructName }}.Delete{{.StructName}}ByIds(ctx,{{.PrimaryField.FieldJson}}s{{- if .AutoCreateResource }},userID{{- end }})
if err != nil {
global.GVA_LOG.Error("批量删除失败!", zap.Error(err))
response.FailWithMessage("批量删除失败:" + err.Error(), c)
return
}
response.OkWithMessage("批量删除成功", c)
}
// Update{{.StructName}} 更新{{.Description}}
// @Tags {{.StructName}}
// @Summary 更新{{.Description}}
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param data body model.{{.StructName}} true "更新{{.Description}}"
// @Success 200 {object} response.Response{msg=string} "更新成功"
// @Router /{{.Abbreviation}}/update{{.StructName}} [put]
func (a *{{.Abbreviation}}) Update{{.StructName}}(c *gin.Context) {
// Context
ctx := c.Request.Context()
var info model.{{.StructName}}
err := c.ShouldBindJSON(&info)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
{{- if .AutoCreateResource }}
info.UpdatedBy = utils.GetUserID(c)
{{- end }}
err = service{{ .StructName }}.Update{{.StructName}}(ctx,info)
if err != nil {
global.GVA_LOG.Error("更新失败!", zap.Error(err))
response.FailWithMessage("更新失败:" + err.Error(), c)
return
}
response.OkWithMessage("更新成功", c)
}
// Find{{.StructName}} 用id查询{{.Description}}
// @Tags {{.StructName}}
// @Summary 用id查询{{.Description}}
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param {{.PrimaryField.FieldJson}} query {{.PrimaryField.FieldType}} true "用id查询{{.Description}}"
// @Success 200 {object} response.Response{data=model.{{.StructName}},msg=string} "查询成功"
// @Router /{{.Abbreviation}}/find{{.StructName}} [get]
func (a *{{.Abbreviation}}) Find{{.StructName}}(c *gin.Context) {
// Context
ctx := c.Request.Context()
{{.PrimaryField.FieldJson}} := c.Query("{{.PrimaryField.FieldJson}}")
re{{.Abbreviation}}, err := service{{ .StructName }}.Get{{.StructName}}(ctx,{{.PrimaryField.FieldJson}})
if err != nil {
global.GVA_LOG.Error("查询失败!", zap.Error(err))
response.FailWithMessage("查询失败:" + err.Error(), c)
return
}
response.OkWithData(re{{.Abbreviation}}, c)
}
{{- if .IsTree }}
// Get{{.StructName}}List 分页获取{{.Description}}列表
// @Tags {{.StructName}}
// @Summary 分页获取{{.Description}}列表
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "获取成功"
// @Router /{{.Abbreviation}}/get{{.StructName}}List [get]
func (a *{{.Abbreviation}}) Get{{.StructName}}List(c *gin.Context) {
// Context
ctx := c.Request.Context()
list, err := service{{ .StructName }}.Get{{.StructName}}InfoList(ctx)
if err != nil {
global.GVA_LOG.Error("获取失败!", zap.Error(err))
response.FailWithMessage("获取失败:" + err.Error(), c)
return
}
response.OkWithDetailed(list, "获取成功", c)
}
{{- else }}
// Get{{.StructName}}List 分页获取{{.Description}}列表
// @Tags {{.StructName}}
// @Summary 分页获取{{.Description}}列表
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param data query request.{{.StructName}}Search true "分页获取{{.Description}}列表"
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "获取成功"
// @Router /{{.Abbreviation}}/get{{.StructName}}List [get]
func (a *{{.Abbreviation}}) Get{{.StructName}}List(c *gin.Context) {
// Context
ctx := c.Request.Context()
var pageInfo request.{{.StructName}}Search
err := c.ShouldBindQuery(&pageInfo)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
list, total, err := service{{ .StructName }}.Get{{.StructName}}InfoList(ctx,pageInfo)
if err != nil {
global.GVA_LOG.Error("获取失败!", zap.Error(err))
response.FailWithMessage("获取失败:" + err.Error(), c)
return
}
response.OkWithDetailed(response.PageResult{
List: list,
Total: total,
Page: pageInfo.Page,
PageSize: pageInfo.PageSize,
}, "获取成功", c)
}
{{- end }}
{{- if .HasDataSource }}
// Get{{.StructName}}DataSource 获取{{.StructName}}的数据源
// @Tags {{.StructName}}
// @Summary 获取{{.StructName}}的数据源
// @Accept application/json
// @Produce application/json
// @Success 200 {object} response.Response{data=object,msg=string} "查询成功"
// @Router /{{.Abbreviation}}/get{{.StructName}}DataSource [get]
func (a *{{.Abbreviation}}) Get{{.StructName}}DataSource(c *gin.Context) {
// Context
ctx := c.Request.Context()
//
dataSource, err := service{{ .StructName }}.Get{{.StructName}}DataSource(ctx)
if err != nil {
global.GVA_LOG.Error("查询失败!", zap.Error(err))
response.FailWithMessage("查询失败:" + err.Error(), c)
return
}
response.OkWithData(dataSource, c)
}
{{- end }}
{{- end }}
// Get{{.StructName}}Public 不需要鉴权的{{.Description}}接口
// @Tags {{.StructName}}
// @Summary 不需要鉴权的{{.Description}}接口
// @Accept application/json
// @Produce application/json
// @Success 200 {object} response.Response{data=object,msg=string} "获取成功"
// @Router /{{.Abbreviation}}/get{{.StructName}}Public [get]
func (a *{{.Abbreviation}}) Get{{.StructName}}Public(c *gin.Context) {
// Context
ctx := c.Request.Context()
// C端服务
service{{ .StructName }}.Get{{.StructName}}Public(ctx)
response.OkWithDetailed(gin.H{"info": "不需要鉴权的{{.Description}}接口信息"}, "获取成功", c)
}

View File

@@ -0,0 +1,6 @@
package api
var Api = new(api)
type api struct {
}

View File

@@ -0,0 +1,4 @@
package config
type Config struct {
}

View File

@@ -0,0 +1,18 @@
package main
import (
"gorm.io/gen"
"path/filepath"
)
//go:generate go mod tidy
//go:generate go mod download
//go:generate go run gen.go
func main() {
g := gen.NewGenerator(gen.Config{
OutPath: filepath.Join("..", "..", "..", "{{ .Package }}", "blender", "model", "dao"),
Mode: gen.WithoutContext | gen.WithDefaultQuery | gen.WithQueryInterface,
})
g.ApplyBasic()
g.Execute()
}

View File

@@ -0,0 +1,12 @@
package initialize
import (
"context"
model "{{.Module}}/model/system"
"{{.Module}}/plugin/plugin-tool/utils"
)
func Api(ctx context.Context) {
entities := []model.SysApi{}
utils.RegisterApis(entities...)
}

View File

@@ -0,0 +1,17 @@
package initialize
import (
"context"
"fmt"
"{{.Module}}/global"
"github.com/pkg/errors"
"go.uber.org/zap"
)
func Gorm(ctx context.Context) {
err := global.GVA_DB.WithContext(ctx).AutoMigrate()
if err != nil {
err = errors.Wrap(err, "注册表失败!")
zap.L().Error(fmt.Sprintf("%+v", err))
}
}

View File

@@ -0,0 +1,12 @@
package initialize
import (
"context"
model "{{.Module}}/model/system"
"{{.Module}}/plugin/plugin-tool/utils"
)
func Menu(ctx context.Context) {
entities := []model.SysBaseMenu{}
utils.RegisterMenus(entities...)
}

View File

@@ -0,0 +1,14 @@
package initialize
import (
"{{.Module}}/global"
"{{.Module}}/middleware"
"github.com/gin-gonic/gin"
)
func Router(engine *gin.Engine) {
public := engine.Group(global.GVA_CONFIG.System.RouterPrefix).Group("")
public.Use()
private := engine.Group(global.GVA_CONFIG.System.RouterPrefix).Group("")
private.Use(middleware.JWTAuth()).Use(middleware.CasbinHandler())
}

View File

@@ -0,0 +1,17 @@
package initialize
import (
"fmt"
"{{.Module}}/global"
"{{.Module}}/plugin/{{ .Package }}/plugin"
"github.com/pkg/errors"
"go.uber.org/zap"
)
func Viper() {
err := global.GVA_VP.UnmarshalKey("{{ .Package }}", &plugin.Config)
if err != nil {
err = errors.Wrap(err, "初始化配置文件失败!")
zap.L().Error(fmt.Sprintf("%+v", err))
}
}

View File

@@ -0,0 +1,76 @@
{{- if .IsAdd}}
// 在结构体中新增如下字段
{{- range .Fields}}
{{ GenerateField . }}
{{- end }}
{{ else }}
package model
{{- if not .OnlyTemplate}}
import (
{{- if .GvaModel }}
"{{.Module}}/global"
{{- end }}
{{- if or .HasTimer }}
"time"
{{- end }}
{{- if .NeedJSON }}
"gorm.io/datatypes"
{{- end }}
)
{{- end }}
// {{.StructName}} {{.Description}} 结构体
type {{.StructName}} struct {
{{- if not .OnlyTemplate}}
{{- if .GvaModel }}
global.GVA_MODEL
{{- end }}
{{- range .Fields}}
{{ GenerateField . }}
{{- end }}
{{- if .AutoCreateResource }}
CreatedBy uint `gorm:"column:created_by;comment:创建者"`
UpdatedBy uint `gorm:"column:updated_by;comment:更新者"`
DeletedBy uint `gorm:"column:deleted_by;comment:删除者"`
{{- end }}
{{- if .IsTree }}
Children []*{{.StructName}} `json:"children" gorm:"-"` //
ParentID int `json:"parentID" gorm:"column:parent_id;comment:父节点"`
{{- end }}
{{- end }}
}
{{ if .TableName }}
// TableName {{.Description}} {{.StructName}}自定义表名 {{.TableName}}
func ({{.StructName}}) TableName() string {
return "{{.TableName}}"
}
{{ end }}
{{if .IsTree }}
// GetChildren 实现TreeNode接口
func (s *{{.StructName}}) GetChildren() []*{{.StructName}} {
return s.Children
}
// SetChildren 实现TreeNode接口
func (s *{{.StructName}}) SetChildren(children *{{.StructName}}) {
s.Children = append(s.Children, children)
}
// GetID 实现TreeNode接口
func (s *{{.StructName}}) GetID() int {
return int({{if not .GvaModel}}*{{- end }}s.{{.PrimaryField.FieldName}})
}
// GetParentID 实现TreeNode接口
func (s *{{.StructName}}) GetParentID() int {
return s.ParentID
}
{{ end }}
{{ end }}

View File

@@ -0,0 +1,38 @@
{{- if .IsAdd}}
// 在结构体中新增如下字段
{{- range .Fields}}
{{- if ne .FieldSearchType ""}}
{{ GenerateSearchField . }}
{{- end}}
{{- end }}
{{- if .NeedSort}}
Sort string `json:"sort" form:"sort"`
Order string `json:"order" form:"order"`
{{- end}}
{{- else }}
package request
{{- if not .OnlyTemplate}}
import (
"{{.Module}}/model/common/request"
{{ if or .HasSearchTimer .GvaModel }}"time"{{ end }}
)
{{- end}}
type {{.StructName}}Search struct{
{{- if not .OnlyTemplate}}
{{- if .GvaModel }}
CreatedAtRange []time.Time `json:"createdAtRange" form:"createdAtRange[]"`
{{- end }}
{{- range .Fields}}
{{- if ne .FieldSearchType ""}}
{{ GenerateSearchField . }}
{{- end}}
{{- end }}
request.PageInfo
{{- if .NeedSort}}
Sort string `json:"sort" form:"sort"`
Order string `json:"order" form:"order"`
{{- end}}
{{- end }}
}
{{- end }}

View File

@@ -0,0 +1,26 @@
package {{ .Package }}
import (
"context"
"{{.Module}}/plugin/{{ .Package }}/initialize"
interfaces "{{.Module}}/utils/plugin/v2"
"github.com/gin-gonic/gin"
)
var _ interfaces.Plugin = (*plugin)(nil)
var Plugin = new(plugin)
type plugin struct{}
// 如果需要配置文件请到config.Config中填充配置结构且到下方发放中填入其在config.yaml中的key并添加如下方法
// initialize.Viper()
// 安装插件时候自动注册的api数据请到下方法.Api方法中实现并添加如下方法
// initialize.Api(ctx)
// 安装插件时候自动注册的api数据请到下方法.Menu方法中实现并添加如下方法
// initialize.Menu(ctx)
func (p *plugin) Register(group *gin.Engine) {
ctx := context.Background()
initialize.Gorm(ctx)
initialize.Router(group)
}

View File

@@ -0,0 +1,5 @@
package plugin
import "{{.Module}}/plugin/{{ .Package }}/config"
var Config config.Config

View File

@@ -0,0 +1,6 @@
package router
var Router = new(router)
type router struct {
}

View File

@@ -0,0 +1,46 @@
package router
import (
{{if .OnlyTemplate }} // {{end}}"{{.Module}}/middleware"
"github.com/gin-gonic/gin"
)
var {{.StructName}} = new({{.Abbreviation}})
type {{.Abbreviation}} struct {}
// Init 初始化 {{.Description}} 路由信息
func (r *{{.Abbreviation}}) Init(public *gin.RouterGroup, private *gin.RouterGroup) {
{{- if not .OnlyTemplate }}
{
group := private.Group("{{.Abbreviation}}").Use(middleware.OperationRecord())
group.POST("create{{.StructName}}", api{{.StructName}}.Create{{.StructName}}) // {{.Description}}
group.DELETE("delete{{.StructName}}", api{{.StructName}}.Delete{{.StructName}}) // {{.Description}}
group.DELETE("delete{{.StructName}}ByIds", api{{.StructName}}.Delete{{.StructName}}ByIds) // {{.Description}}
group.PUT("update{{.StructName}}", api{{.StructName}}.Update{{.StructName}}) // {{.Description}}
}
{
group := private.Group("{{.Abbreviation}}")
group.GET("find{{.StructName}}", api{{.StructName}}.Find{{.StructName}}) // ID获取{{.Description}}
group.GET("get{{.StructName}}List", api{{.StructName}}.Get{{.StructName}}List) // {{.Description}}
}
{
group := public.Group("{{.Abbreviation}}")
{{- if .HasDataSource}}
group.GET("get{{.StructName}}DataSource", api{{.StructName}}.Get{{.StructName}}DataSource) // {{.Description}}
{{- end}}
group.GET("get{{.StructName}}Public", api{{.StructName}}.Get{{.StructName}}Public) // {{.Description}}
}
{{- else}}
// {
// group := private.Group("{{.Abbreviation}}").Use(middleware.OperationRecord())
// }
// {
// group := private.Group("{{.Abbreviation}}")
// }
{
group := public.Group("{{.Abbreviation}}")
group.GET("get{{.StructName}}Public", api{{.StructName}}.Get{{.StructName}}Public) // {{.Description}}
}
{{- end}}
}

View File

@@ -0,0 +1,7 @@
package service
var Service = new(service)
type service struct {
}

View File

@@ -0,0 +1,211 @@
{{- $db := "" }}
{{- if eq .BusinessDB "" }}
{{- $db = "global.GVA_DB" }}
{{- else}}
{{- $db = printf "global.MustGetGlobalDBByDBName(\"%s\")" .BusinessDB }}
{{- end}}
{{- if .IsAdd}}
// Get{{.StructName}}InfoList 新增搜索语句
{{ GenerateSearchConditions .Fields }}
// Get{{.StructName}}InfoList 新增排序语句 请自行在搜索语句中添加orderMap内容
{{- range .Fields}}
{{- if .Sort}}
orderMap["{{.ColumnName}}"] = true
{{- end}}
{{- end}}
{{- if .HasDataSource }}
// Get{{.StructName}}DataSource()方法新增关联语句
{{range $key, $value := .DataSourceMap}}
{{$key}} := make([]map[string]any, 0)
{{$db}}.Table("{{$value.Table}}"){{- if $value.HasDeletedAt}}.Where("deleted_at IS NULL"){{ end }}.Select("{{$value.Label}} as label,{{$value.Value}} as value").Scan(&{{$key}})
res["{{$key}}"] = {{$key}}
{{- end }}
{{- end }}
{{- else}}
package service
import (
{{- if not .OnlyTemplate }}
"context"
"{{.Module}}/global"
"{{.Module}}/plugin/{{.Package}}/model"
{{- if not .IsTree }}
"{{.Module}}/plugin/{{.Package}}/model/request"
{{- else }}
"errors"
{{- end }}
{{- if .AutoCreateResource }}
"gorm.io/gorm"
{{- end}}
{{- if .IsTree }}
"{{.Module}}/utils"
{{- end }}
{{- end }}
)
var {{.StructName}} = new({{.Abbreviation}})
type {{.Abbreviation}} struct {}
{{- $db := "" }}
{{- if eq .BusinessDB "" }}
{{- $db = "global.GVA_DB" }}
{{- else}}
{{- $db = printf "global.MustGetGlobalDBByDBName(\"%s\")" .BusinessDB }}
{{- end}}
{{- if not .OnlyTemplate }}
// Create{{.StructName}} 创建{{.Description}}记录
// Author [yourname](https://github.com/yourname)
func (s *{{.Abbreviation}}) Create{{.StructName}}(ctx context.Context, {{.Abbreviation}} *model.{{.StructName}}) (err error) {
err = {{$db}}.Create({{.Abbreviation}}).Error
return err
}
// Delete{{.StructName}} 删除{{.Description}}记录
// Author [yourname](https://github.com/yourname)
func (s *{{.Abbreviation}}) Delete{{.StructName}}(ctx context.Context, {{.PrimaryField.FieldJson}} string{{- if .AutoCreateResource -}},userID uint{{- end -}}) (err error) {
{{- if .IsTree }}
var count int64
err = {{$db}}.Find(&model.{{.StructName}}{},"parent_id = ?",{{.PrimaryField.FieldJson}}).Count(&count).Error
if count > 0 {
return errors.New("此节点存在子节点不允许删除")
}
if err != nil {
return err
}
{{- end }}
{{- if .AutoCreateResource }}
err = {{$db}}.Transaction(func(tx *gorm.DB) error {
if err := tx.Model(&model.{{.StructName}}{}).Where("{{.PrimaryField.ColumnName}} = ?", {{.PrimaryField.FieldJson}}).Update("deleted_by", userID).Error; err != nil {
return err
}
if err = tx.Delete(&model.{{.StructName}}{},"{{.PrimaryField.ColumnName}} = ?",{{.PrimaryField.FieldJson}}).Error; err != nil {
return err
}
return nil
})
{{- else }}
err = {{$db}}.Delete(&model.{{.StructName}}{},"{{.PrimaryField.ColumnName}} = ?",{{.PrimaryField.FieldJson}}).Error
{{- end }}
return err
}
// Delete{{.StructName}}ByIds 批量删除{{.Description}}记录
// Author [yourname](https://github.com/yourname)
func (s *{{.Abbreviation}}) Delete{{.StructName}}ByIds(ctx context.Context, {{.PrimaryField.FieldJson}}s []string {{- if .AutoCreateResource }},deleted_by uint{{- end}}) (err error) {
{{- if .AutoCreateResource }}
err = {{$db}}.Transaction(func(tx *gorm.DB) error {
if err := tx.Model(&model.{{.StructName}}{}).Where("{{.PrimaryField.ColumnName}} in ?", {{.PrimaryField.FieldJson}}s).Update("deleted_by", deleted_by).Error; err != nil {
return err
}
if err := tx.Where("{{.PrimaryField.ColumnName}} in ?", {{.PrimaryField.FieldJson}}s).Delete(&model.{{.StructName}}{}).Error; err != nil {
return err
}
return nil
})
{{- else}}
err = {{$db}}.Delete(&[]model.{{.StructName}}{},"{{.PrimaryField.ColumnName}} in ?",{{.PrimaryField.FieldJson}}s).Error
{{- end}}
return err
}
// Update{{.StructName}} 更新{{.Description}}记录
// Author [yourname](https://github.com/yourname)
func (s *{{.Abbreviation}}) Update{{.StructName}}(ctx context.Context, {{.Abbreviation}} model.{{.StructName}}) (err error) {
err = {{$db}}.Model(&model.{{.StructName}}{}).Where("{{.PrimaryField.ColumnName}} = ?",{{.Abbreviation}}.{{.PrimaryField.FieldName}}).Updates(&{{.Abbreviation}}).Error
return err
}
// Get{{.StructName}} 根据{{.PrimaryField.FieldJson}}获取{{.Description}}记录
// Author [yourname](https://github.com/yourname)
func (s *{{.Abbreviation}}) Get{{.StructName}}(ctx context.Context, {{.PrimaryField.FieldJson}} string) ({{.Abbreviation}} model.{{.StructName}}, err error) {
err = {{$db}}.Where("{{.PrimaryField.ColumnName}} = ?", {{.PrimaryField.FieldJson}}).First(&{{.Abbreviation}}).Error
return
}
{{- if .IsTree }}
// Get{{.StructName}}InfoList 分页获取{{.Description}}记录,Tree模式下不添加分页和搜索
// Author [yourname](https://github.com/yourname)
func (s *{{.Abbreviation}}) Get{{.StructName}}InfoList(ctx context.Context) (list []*model.{{.StructName}},err error) {
// db
db := {{$db}}.Model(&model.{{.StructName}}{})
var {{.Abbreviation}}s []*model.{{.StructName}}
err = db.Find(&{{.Abbreviation}}s).Error
return utils.BuildTree({{.Abbreviation}}s), err
}
{{- else }}
// Get{{.StructName}}InfoList 分页获取{{.Description}}记录
// Author [yourname](https://github.com/yourname)
func (s *{{.Abbreviation}}) Get{{.StructName}}InfoList(ctx context.Context, info request.{{.StructName}}Search) (list []model.{{.StructName}}, total int64, err error) {
limit := info.PageSize
offset := info.PageSize * (info.Page - 1)
// db
db := {{$db}}.Model(&model.{{.StructName}}{})
var {{.Abbreviation}}s []model.{{.StructName}}
//
{{- if .GvaModel }}
if len(info.CreatedAtRange) == 2 {
db = db.Where("created_at BETWEEN ? AND ?", info.CreatedAtRange[0], info.CreatedAtRange[1])
}
{{- end }}
{{ GenerateSearchConditions .Fields }}
err = db.Count(&total).Error
if err!=nil {
return
}
{{- if .NeedSort}}
var OrderStr string
orderMap := make(map[string]bool)
{{- if .GvaModel }}
orderMap["id"] = true
orderMap["created_at"] = true
{{- end }}
{{- range .Fields}}
{{- if .Sort}}
orderMap["{{.ColumnName}}"] = true
{{- end}}
{{- end}}
if orderMap[info.Sort] {
OrderStr = info.Sort
if info.Order == "descending" {
OrderStr = OrderStr + " desc"
}
db = db.Order(OrderStr)
}
{{- end}}
if limit != 0 {
db = db.Limit(limit).Offset(offset)
}
err = db.Find(&{{.Abbreviation}}s).Error
return {{.Abbreviation}}s, total, err
}
{{- end }}
{{- if .HasDataSource }}
func (s *{{.Abbreviation}})Get{{.StructName}}DataSource(ctx context.Context) (res map[string][]map[string]any, err error) {
res = make(map[string][]map[string]any)
{{range $key, $value := .DataSourceMap}}
{{$key}} := make([]map[string]any, 0)
{{$db}}.Table("{{$value.Table}}"){{- if $value.HasDeletedAt}}.Where("deleted_at IS NULL"){{ end }}.Select("{{$value.Label}} as label,{{$value.Value}} as value").Scan(&{{$key}})
res["{{$key}}"] = {{$key}}
{{- end }}
return
}
{{- end }}
{{- end }}
func (s *{{.Abbreviation}})Get{{.StructName}}Public(ctx context.Context) {
}
{{- end }}

View File

@@ -0,0 +1,127 @@
import service from '@/utils/request'
{{- if not .OnlyTemplate}}
// @Tags {{.StructName}}
// @Summary 创建{{.Description}}
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param data body model.{{.StructName}} true "创建{{.Description}}"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}"
// @Router /{{.Abbreviation}}/create{{.StructName}} [post]
export const create{{.StructName}} = (data) => {
return service({
url: '/{{.Abbreviation}}/create{{.StructName}}',
method: 'post',
data
})
}
// @Tags {{.StructName}}
// @Summary 删除{{.Description}}
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param data body model.{{.StructName}} true "删除{{.Description}}"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}"
// @Router /{{.Abbreviation}}/delete{{.StructName}} [delete]
export const delete{{.StructName}} = (params) => {
return service({
url: '/{{.Abbreviation}}/delete{{.StructName}}',
method: 'delete',
params
})
}
// @Tags {{.StructName}}
// @Summary 批量删除{{.Description}}
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param data body request.IdsReq true "批量删除{{.Description}}"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}"
// @Router /{{.Abbreviation}}/delete{{.StructName}} [delete]
export const delete{{.StructName}}ByIds = (params) => {
return service({
url: '/{{.Abbreviation}}/delete{{.StructName}}ByIds',
method: 'delete',
params
})
}
// @Tags {{.StructName}}
// @Summary 更新{{.Description}}
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param data body model.{{.StructName}} true "更新{{.Description}}"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"更新成功"}"
// @Router /{{.Abbreviation}}/update{{.StructName}} [put]
export const update{{.StructName}} = (data) => {
return service({
url: '/{{.Abbreviation}}/update{{.StructName}}',
method: 'put',
data
})
}
// @Tags {{.StructName}}
// @Summary 用id查询{{.Description}}
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param data query model.{{.StructName}} true "用id查询{{.Description}}"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"查询成功"}"
// @Router /{{.Abbreviation}}/find{{.StructName}} [get]
export const find{{.StructName}} = (params) => {
return service({
url: '/{{.Abbreviation}}/find{{.StructName}}',
method: 'get',
params
})
}
// @Tags {{.StructName}}
// @Summary 分页获取{{.Description}}列表
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param data query request.PageInfo true "分页获取{{.Description}}列表"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}"
// @Router /{{.Abbreviation}}/get{{.StructName}}List [get]
export const get{{.StructName}}List = (params) => {
return service({
url: '/{{.Abbreviation}}/get{{.StructName}}List',
method: 'get',
params
})
}
{{- if .HasDataSource}}
// @Tags {{.StructName}}
// @Summary 获取数据源
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Success 200 {string} string "{"success":true,"data":{},"msg":"查询成功"}"
// @Router /{{.Abbreviation}}/find{{.StructName}}DataSource [get]
export const get{{.StructName}}DataSource = () => {
return service({
url: '/{{.Abbreviation}}/get{{.StructName}}DataSource',
method: 'get',
})
}
{{- end}}
{{- end}}
// @Tags {{.StructName}}
// @Summary 不需要鉴权的{{.Description}}接口
// @Accept application/json
// @Produce application/json
// @Param data query request.{{.StructName}}Search true "分页获取{{.Description}}列表"
// @Success 200 {object} response.Response{data=object,msg=string} "获取成功"
// @Router /{{.Abbreviation}}/get{{.StructName}}Public [get]
export const get{{.StructName}}Public = () => {
return service({
url: '/{{.Abbreviation}}/get{{.StructName}}Public',
method: 'get',
})
}

View File

@@ -0,0 +1,464 @@
{{- if .IsAdd }}
// 新增表单中增加如下代码
{{- range .Fields}}
{{- if .Form}}
<el-form-item label="{{.FieldDesc}}:" prop="{{.FieldJson}}" >
{{- if .CheckDataSource}}
<el-select {{if eq .DataSource.Association 2}} multiple {{ end }} v-model="formData.{{.FieldJson}}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="(item,key) in dataSource.{{.FieldJson}}" :key="key" :label="item.label" :value="item.value" />
</el-select>
{{- else }}
{{- if eq .FieldType "bool" }}
<el-switch v-model="formData.{{.FieldJson}}" active-color="#13ce66" inactive-color="#ff4949" active-text="是" inactive-text="否" clearable ></el-switch>
{{- end }}
{{- if eq .FieldType "string" }}
{{- if .DictType}}
<el-select {{if eq .FieldType "array"}}multiple {{end}}v-model="formData.{{ .FieldJson }}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="(item,key) in {{ .DictType }}Options" :key="key" :label="item.label" :value="item.value" />
</el-select>
{{- else }}
<el-input v-model="formData.{{.FieldJson}}" :clearable="{{.Clearable}}" placeholder="请输入{{.FieldDesc}}" />
{{- end }}
{{- end }}
{{- if eq .FieldType "richtext" }}
<RichEdit v-model="formData.{{.FieldJson}}"/>
{{- end }}
{{- if eq .FieldType "json" }}
// 此字段为json结构可以前端自行控制展示和数据绑定模式 需绑定json的key为 formData.{{.FieldJson}} 后端会按照json的类型进行存取
{{"{{"}} formData.{{.FieldJson}} {{"}}"}}
{{- end }}
{{- if eq .FieldType "array" }}
<ArrayCtrl v-model="formData.{{ .FieldJson }}" editable/>
{{- end }}
{{- if eq .FieldType "int" }}
<el-input v-model.number="formData.{{ .FieldJson }}" :clearable="{{.Clearable}}" placeholder="请输入{{.FieldDesc}}" />
{{- end }}
{{- if eq .FieldType "time.Time" }}
<el-date-picker v-model="formData.{{ .FieldJson }}" type="date" style="width:100%" placeholder="选择日期" :clearable="{{.Clearable}}" />
{{- end }}
{{- if eq .FieldType "float64" }}
<el-input-number v-model="formData.{{ .FieldJson }}" style="width:100%" :precision="2" :clearable="{{.Clearable}}" />
{{- end }}
{{- if eq .FieldType "enum" }}
<el-select v-model="formData.{{ .FieldJson }}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="item in [{{.DataTypeLong}}]" :key="item" :label="item" :value="item" />
</el-select>
{{- end }}
{{- if eq .FieldType "picture" }}
<SelectImage
v-model="formData.{{ .FieldJson }}"
file-type="image"
/>
{{- end }}
{{- if eq .FieldType "pictures" }}
<SelectImage
multiple
v-model="formData.{{ .FieldJson }}"
file-type="image"
/>
{{- end }}
{{- if eq .FieldType "video" }}
<SelectImage
v-model="formData.{{ .FieldJson }}"
file-type="video"
/>
{{- end }}
{{- if eq .FieldType "file" }}
<SelectFile v-model="formData.{{ .FieldJson }}" />
{{- end }}
{{- end }}
</el-form-item>
{{- end }}
{{- end }}
// 字典增加如下代码
{{- range $index, $element := .DictTypes}}
const {{ $element }}Options = ref([])
{{- end }}
// init方法中增加如下调用
{{- range $index, $element := .DictTypes }}
{{ $element }}Options.value = await getDictFunc('{{$element}}')
{{- end }}
// 基础formData结构增加如下字段
{{- range .Fields}}
{{- if .Form}}
{{- if eq .FieldType "bool" }}
{{.FieldJson}}: false,
{{- end }}
{{- if eq .FieldType "string" }}
{{.FieldJson}}: '',
{{- end }}
{{- if eq .FieldType "richtext" }}
{{.FieldJson}}: '',
{{- end }}
{{- if eq .FieldType "int" }}
{{.FieldJson}}: {{- if or .DataSource}} undefined{{ else }} 0{{- end }},
{{- end }}
{{- if eq .FieldType "time.Time" }}
{{.FieldJson}}: new Date(),
{{- end }}
{{- if eq .FieldType "float64" }}
{{.FieldJson}}: 0,
{{- end }}
{{- if eq .FieldType "picture" }}
{{.FieldJson}}: "",
{{- end }}
{{- if eq .FieldType "video" }}
{{.FieldJson}}: "",
{{- end }}
{{- if eq .FieldType "pictures" }}
{{.FieldJson}}: [],
{{- end }}
{{- if eq .FieldType "file" }}
{{.FieldJson}}: [],
{{- end }}
{{- if eq .FieldType "json" }}
{{.FieldJson}}: {},
{{- end }}
{{- if eq .FieldType "array" }}
{{.FieldJson}}: [],
{{- end }}
{{- end }}
{{- end }}
// 验证规则中增加如下字段
{{- range .Fields }}
{{- if .Form }}
{{- if eq .Require true }}
{{.FieldJson }} : [{
required: true,
message: '{{ .ErrorText }}',
trigger: ['input','blur'],
},
{{- if eq .FieldType "string" }}
{
whitespace: true,
message: '不能只输入空格',
trigger: ['input', 'blur'],
}
{{- end }}
],
{{- end }}
{{- end }}
{{- end }}
{{- if .HasDataSource }}
// 请引用
get{{.StructName}}DataSource,
// 获取数据源
const dataSource = ref([])
const getDataSourceFunc = async()=>{
const res = await get{{.StructName}}DataSource()
if (res.code === 0) {
dataSource.value = res.data
}
}
getDataSourceFunc()
{{- end }}
{{- else }}
{{- if not .OnlyTemplate }}
<template>
<div>
<div class="gva-form-box">
<el-form :model="formData" ref="elFormRef" label-position="right" :rules="rule" label-width="80px">
{{- if .IsTree }}
<el-form-item label="父节点:" prop="parentID" >
<el-tree-select
v-model="formData.parentID"
:data="[rootNode,...tableData]"
check-strictly
:render-after-expand="false"
:props="defaultProps"
clearable
style="width: 240px"
placeholder="根节点"
/>
</el-form-item>
{{- end }}
{{- range .Fields}}
{{- if .Form }}
<el-form-item label="{{.FieldDesc}}:" prop="{{.FieldJson}}">
{{- if .CheckDataSource}}
<el-select {{if eq .DataSource.Association 2}} multiple {{ end }} v-model="formData.{{.FieldJson}}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="(item,key) in dataSource.{{.FieldJson}}" :key="key" :label="item.label" :value="item.value" />
</el-select>
{{- else }}
{{- if eq .FieldType "bool" }}
<el-switch v-model="formData.{{.FieldJson}}" active-color="#13ce66" inactive-color="#ff4949" active-text="是" inactive-text="否" clearable ></el-switch>
{{- end }}
{{- if eq .FieldType "string" }}
{{- if .DictType}}
<el-select {{if eq .FieldType "array"}}multiple {{end}}v-model="formData.{{ .FieldJson }}" placeholder="请选择{{.FieldDesc}}" style="width:100%" :clearable="{{.Clearable}}" >
<el-option v-for="(item,key) in {{ .DictType }}Options" :key="key" :label="item.label" :value="item.value" />
</el-select>
{{- else }}
<el-input v-model="formData.{{.FieldJson}}" :clearable="{{.Clearable}}" placeholder="请输入{{.FieldDesc}}" />
{{- end }}
{{- end }}
{{- if eq .FieldType "richtext" }}
<RichEdit v-model="formData.{{.FieldJson}}"/>
{{- end }}
{{- if eq .FieldType "int" }}
<el-input v-model.number="formData.{{ .FieldJson }}" :clearable="{{.Clearable}}" placeholder="请输入" />
{{- end }}
{{- if eq .FieldType "time.Time" }}
<el-date-picker v-model="formData.{{ .FieldJson }}" type="date" placeholder="选择日期" :clearable="{{.Clearable}}"></el-date-picker>
{{- end }}
{{- if eq .FieldType "float64" }}
<el-input-number v-model="formData.{{ .FieldJson }}" :precision="2" :clearable="{{.Clearable}}"></el-input-number>
{{- end }}
{{- if eq .FieldType "enum" }}
<el-select v-model="formData.{{ .FieldJson }}" placeholder="请选择" style="width:100%" :clearable="{{.Clearable}}">
<el-option v-for="item in [{{ .DataTypeLong }}]" :key="item" :label="item" :value="item" />
</el-select>
{{- end }}
{{- if eq .FieldType "picture" }}
<SelectImage v-model="formData.{{ .FieldJson }}" file-type="image"/>
{{- end }}
{{- if eq .FieldType "video" }}
<SelectImage v-model="formData.{{ .FieldJson }}" file-type="video"/>
{{- end }}
{{- if eq .FieldType "pictures" }}
<SelectImage v-model="formData.{{ .FieldJson }}" multiple file-type="image"/>
{{- end }}
{{- if eq .FieldType "file" }}
<SelectFile v-model="formData.{{ .FieldJson }}" />
{{- end }}
{{- if eq .FieldType "json" }}
// 此字段为json结构可以前端自行控制展示和数据绑定模式 需绑定json的key为 formData.{{.FieldJson}} 后端会按照json的类型进行存取
{{"{{"}} formData.{{.FieldJson}} {{"}}"}}
{{- end }}
{{- if eq .FieldType "array" }}
<ArrayCtrl v-model="formData.{{ .FieldJson }}" editable/>
{{- end }}
{{- end }}
</el-form-item>
{{- end }}
{{- end }}
<el-form-item>
<el-button :loading="btnLoading" type="primary" @click="save">保存</el-button>
<el-button type="primary" @click="back">返回</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script setup>
import {
{{- if .HasDataSource }}
get{{.StructName}}DataSource,
{{- end }}
{{- if .IsTree }}
get{{.StructName}}List,
{{- end }}
create{{.StructName}},
update{{.StructName}},
find{{.StructName}}
} from '@/plugin/{{.Package}}/api/{{.PackageName}}'
defineOptions({
name: '{{.StructName}}Form'
})
// 自动获取字典
import { getDictFunc } from '@/utils/format'
import { useRoute, useRouter } from "vue-router"
import { ElMessage } from 'element-plus'
import { ref, reactive } from 'vue'
{{- if .HasPic }}
// 图片选择组件
import SelectImage from '@/components/selectImage/selectImage.vue'
{{- end }}
{{- if .HasFile }}
// 文件选择组件
import SelectFile from '@/components/selectFile/selectFile.vue'
{{- end }}
{{- if .HasRichText }}
// 富文本组件
import RichEdit from '@/components/richtext/rich-edit.vue'
{{- end }}
{{- if .HasArray}}
// 数组控制组件
import ArrayCtrl from '@/components/arrayCtrl/arrayCtrl.vue'
{{- end }}
const route = useRoute()
const router = useRouter()
{{- if .IsTree }}
const tableData = ref([])
const defaultProps = {
children: "children",
label: "{{ .TreeJson }}",
value: "{{ .PrimaryField.FieldJson }}"
}
const rootNode = {
{{ .PrimaryField.FieldJson }}: 0,
{{ .TreeJson }}: '根节点',
children: []
}
const getTableData = async() => {
const table = await get{{.StructName}}List()
if (table.code === 0) {
tableData.value = table.data || []
}
}
getTableData()
{{- end }}
// 提交按钮loading
const btnLoading = ref(false)
const type = ref('')
{{- range $index, $element := .DictTypes}}
const {{ $element }}Options = ref([])
{{- end }}
const formData = ref({
{{- if .IsTree }}
parentID: undefined,
{{- end }}
{{- range .Fields}}
{{- if .Form }}
{{- if eq .FieldType "bool" }}
{{.FieldJson}}: false,
{{- end }}
{{- if eq .FieldType "string" }}
{{.FieldJson}}: '',
{{- end }}
{{- if eq .FieldType "richtext" }}
{{.FieldJson}}: '',
{{- end }}
{{- if eq .FieldType "int" }}
{{.FieldJson}}: {{- if or .DataSource }} undefined{{ else }} 0{{- end }},
{{- end }}
{{- if eq .FieldType "time.Time" }}
{{.FieldJson}}: new Date(),
{{- end }}
{{- if eq .FieldType "float64" }}
{{.FieldJson}}: 0,
{{- end }}
{{- if eq .FieldType "picture" }}
{{.FieldJson}}: "",
{{- end }}
{{- if eq .FieldType "video" }}
{{.FieldJson}}: "",
{{- end }}
{{- if eq .FieldType "pictures" }}
{{.FieldJson}}: [],
{{- end }}
{{- if eq .FieldType "file" }}
{{.FieldJson}}: [],
{{- end }}
{{- if eq .FieldType "json" }}
{{.FieldJson}}: {},
{{- end }}
{{- if eq .FieldType "array" }}
{{.FieldJson}}: [],
{{- end }}
{{- end }}
{{- end }}
})
// 验证规则
const rule = reactive({
{{- range .Fields }}
{{- if eq .Require true }}
{{.FieldJson }} : [{
required: true,
message: '{{ .ErrorText }}',
trigger: ['input','blur'],
}],
{{- end }}
{{- end }}
})
const elFormRef = ref()
{{- if .HasDataSource }}
const dataSource = ref([])
const getDataSourceFunc = async()=>{
const res = await get{{.StructName}}DataSource()
if (res.code === 0) {
dataSource.value = res.data
}
}
getDataSourceFunc()
{{- end }}
// 初始化方法
const init = async () => {
// url传参获取目标数据ID find方法进行查询数据操作 create还是update id作为url参数示例
if (route.query.id) {
const res = await find{{.StructName}}({ ID: route.query.id })
if (res.code === 0) {
formData.value = res.data
type.value = 'update'
}
} else {
type.value = 'create'
}
{{- range $index, $element := .DictTypes }}
{{ $element }}Options.value = await getDictFunc('{{$element}}')
{{- end }}
}
init()
// 保存按钮
const save = async() => {
btnLoading.value = true
elFormRef.value?.validate( async (valid) => {
if (!valid) return btnLoading.value = false
let res
switch (type.value) {
case 'create':
res = await create{{.StructName}}(formData.value)
break
case 'update':
res = await update{{.StructName}}(formData.value)
break
default:
res = await create{{.StructName}}(formData.value)
break
}
btnLoading.value = false
if (res.code === 0) {
ElMessage({
type: 'success',
message: '创建/更改成功'
})
}
})
}
// 返回按钮
const back = () => {
router.go(-1)
}
</script>
<style>
</style>
{{- else }}
<template>
<div>form</div>
</template>
<script setup>
</script>
<style>
</style>
{{- end }}
{{- end }}

View File

@@ -0,0 +1,689 @@
{{- $global := . }}
{{- $templateID := printf "%s_%s" .Package .StructName }}
{{- if .IsAdd }}
// 请在搜索条件中增加如下代码
{{- range .Fields}}
{{- if .FieldSearchType}}
{{ GenerateSearchFormItem .}}
{{ end }}
{{ end }}
// 表格增加如下列代码
{{- range .Fields}}
{{- if .Table}}
{{ GenerateTableColumn . }}
{{- end }}
{{- end }}
// 新增表单中增加如下代码
{{- range .Fields}}
{{- if .Form}}
{{ GenerateFormItem . }}
{{- end }}
{{- end }}
// 查看抽屉中增加如下代码
{{- range .Fields}}
{{- if .Desc }}
{{ GenerateDescriptionItem . }}
{{- end }}
{{- end }}
// 字典增加如下代码
{{- range $index, $element := .DictTypes}}
const {{ $element }}Options = ref([])
{{- end }}
// setOptions方法中增加如下调用
{{- range $index, $element := .DictTypes }}
{{ $element }}Options.value = await getDictFunc('{{$element}}')
{{- end }}
// 基础formData结构变量处和关闭表单处增加如下字段
{{- range .Fields}}
{{- if .Form}}
{{ GenerateDefaultFormValue . }}
{{- end }}
{{- end }}
// 验证规则中增加如下字段
{{- range .Fields }}
{{- if .Form }}
{{- if eq .Require true }}
{{.FieldJson }} : [{
required: true,
message: '{{ .ErrorText }}',
trigger: ['input','blur'],
},
{{- if eq .FieldType "string" }}
{
whitespace: true,
message: '不能只输入空格',
trigger: ['input', 'blur'],
}
{{- end }}
],
{{- end }}
{{- end }}
{{- end }}
{{- if .HasDataSource }}
// 请引用
get{{.StructName}}DataSource,
// 获取数据源
const dataSource = ref({})
const getDataSourceFunc = async()=>{
const res = await get{{.StructName}}DataSource()
if (res.code === 0) {
dataSource.value = res.data || []
}
}
getDataSourceFunc()
{{- end }}
{{- else }}
{{- if not .OnlyTemplate}}
<template>
<div>
{{- if not .IsTree }}
<div class="gva-search-box">
<el-form ref="elSearchFormRef" :inline="true" :model="searchInfo" class="demo-form-inline" @keyup.enter="onSubmit">
{{- if .GvaModel }}
<el-form-item label="创建日期" prop="createdAtRange">
<template #label>
<span>
创建日期
<el-tooltip content="搜索范围是开始日期(包含)至结束日期(不包含)">
<el-icon><QuestionFilled /></el-icon>
</el-tooltip>
</span>
</template>
<el-date-picker
v-model="searchInfo.createdAtRange"
class="w-[380px]"
type="datetimerange"
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
/>
</el-form-item>
{{ end -}}
{{- range .Fields}} {{- if .FieldSearchType}} {{- if not .FieldSearchHide }}
{{ GenerateSearchFormItem .}}
{{ end }}{{ end }}{{ end }}
<template v-if="showAllQuery">
<!-- 将需要控制显示状态的查询条件添加到此范围内 -->
{{- range .Fields}} {{- if .FieldSearchType}} {{- if .FieldSearchHide }}
{{ GenerateSearchFormItem .}}
{{ end }}{{ end }}{{ end }}
</template>
<el-form-item>
<el-button type="primary" icon="search" @click="onSubmit">查询</el-button>
<el-button icon="refresh" @click="onReset">重置</el-button>
<el-button link type="primary" icon="arrow-down" @click="showAllQuery=true" v-if="!showAllQuery">展开</el-button>
<el-button link type="primary" icon="arrow-up" @click="showAllQuery=false" v-else>收起</el-button>
</el-form-item>
</el-form>
</div>
{{- end }}
<div class="gva-table-box">
<div class="gva-btn-list">
<el-button {{ if $global.AutoCreateBtnAuth }}v-auth="btnAuth.add"{{ end }} type="primary" icon="plus" @click="openDialog()">新增</el-button>
<el-button {{ if $global.AutoCreateBtnAuth }}v-auth="btnAuth.batchDelete"{{ end }} icon="delete" style="margin-left: 10px;" :disabled="!multipleSelection.length" @click="onDelete">删除</el-button>
{{ if .HasExcel -}}
<ExportTemplate {{ if $global.AutoCreateBtnAuth }}v-auth="btnAuth.exportTemplate"{{ end }} template-id="{{$templateID}}" />
<ExportExcel {{ if $global.AutoCreateBtnAuth }}v-auth="btnAuth.exportExcel"{{ end }} template-id="{{$templateID}}" filterDeleted/>
<ImportExcel {{ if $global.AutoCreateBtnAuth }}v-auth="btnAuth.importExcel"{{ end }} template-id="{{$templateID}}" @on-success="getTableData" />
{{- end }}
</div>
<el-table
ref="multipleTable"
style="width: 100%"
tooltip-effect="dark"
:data="tableData"
row-key="{{.PrimaryField.FieldJson}}"
@selection-change="handleSelectionChange"
{{- if .NeedSort}}
@sort-change="sortChange"
{{- end}}
>
<el-table-column type="selection" width="55" />
{{ if .GvaModel }}
<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 }}
{{- range .Fields}}
{{- if .Table}}
{{ GenerateTableColumn . }}
{{- end }}
{{- end }}
<el-table-column align="left" label="操作" fixed="right" min-width="240">
<template #default="scope">
{{- if .IsTree }}
<el-button {{ if $global.AutoCreateBtnAuth }}v-auth="btnAuth.add"{{ end }} type="primary" link class="table-button" @click="openDialog(scope.row)"><el-icon style="margin-right: 5px"><InfoFilled /></el-icon>新增子节点</el-button>
{{- end }}
<el-button {{ if $global.AutoCreateBtnAuth }}v-auth="btnAuth.info"{{ end }} type="primary" link class="table-button" @click="getDetails(scope.row)"><el-icon style="margin-right: 5px"><InfoFilled /></el-icon>查看</el-button>
<el-button {{ if $global.AutoCreateBtnAuth }}v-auth="btnAuth.edit"{{ end }} type="primary" link icon="edit" class="table-button" @click="update{{.StructName}}Func(scope.row)">编辑</el-button>
<el-button {{ if .IsTree }}v-if="!scope.row.children?.length"{{ end }} {{ if $global.AutoCreateBtnAuth }}v-auth="btnAuth.delete"{{ end }} type="primary" link icon="delete" @click="deleteRow(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="gva-pagination">
<el-pagination
layout="total, sizes, prev, pager, next, jumper"
:current-page="page"
:page-size="pageSize"
:page-sizes="[10, 30, 50, 100]"
:total="total"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</div>
</div>
<el-drawer destroy-on-close size="800" v-model="dialogFormVisible" :show-close="false" :before-close="closeDialog">
<template #header>
<div class="flex justify-between items-center">
<span class="text-lg">{{"{{"}}type==='create'?'新增':'编辑'{{"}}"}}</span>
<div>
<el-button :loading="btnLoading" type="primary" @click="enterDialog">确 定</el-button>
<el-button @click="closeDialog">取 消</el-button>
</div>
</div>
</template>
<el-form :model="formData" label-position="top" ref="elFormRef" :rules="rule" label-width="80px">
{{- if .IsTree }}
<el-form-item label="父节点:" prop="parentID" >
<el-tree-select
v-model="formData.parentID"
:data="[rootNode,...tableData]"
check-strictly
:render-after-expand="false"
:props="defaultProps"
clearable
style="width: 240px"
placeholder="根节点"
/>
</el-form-item>
{{- end }}
{{- range .Fields}}
{{- if .Form}}
{{ GenerateFormItem . }}
{{- end }}
{{- end }}
</el-form>
</el-drawer>
<el-drawer destroy-on-close size="800" v-model="detailShow" :show-close="true" :before-close="closeDetailShow" title="查看">
<el-descriptions :column="1" border>
{{- if .IsTree }}
<el-descriptions-item label="父节点">
<el-tree-select
v-model="detailForm.parentID"
:data="[rootNode,...tableData]"
check-strictly
disabled
:render-after-expand="false"
:props="defaultProps"
clearable
style="width: 240px"
placeholder="根节点"
/>
</el-descriptions-item>
{{- end }}
{{- range .Fields}}
{{- if .Desc }}
{{ GenerateDescriptionItem . }}
{{- end }}
{{- end }}
</el-descriptions>
</el-drawer>
</div>
</template>
<script setup>
import {
{{- if .HasDataSource }}
get{{.StructName}}DataSource,
{{- end }}
create{{.StructName}},
delete{{.StructName}},
delete{{.StructName}}ByIds,
update{{.StructName}},
find{{.StructName}},
get{{.StructName}}List
} from '@/plugin/{{.Package}}/api/{{.PackageName}}'
{{- if or .HasPic .HasFile}}
import { getUrl } from '@/utils/image'
{{- end }}
{{- if .HasPic }}
// 图片选择组件
import SelectImage from '@/components/selectImage/selectImage.vue'
{{- end }}
{{- if .HasRichText }}
// 富文本组件
import RichEdit from '@/components/richtext/rich-edit.vue'
import RichView from '@/components/richtext/rich-view.vue'
{{- end }}
{{- if .HasFile }}
// 文件选择组件
import SelectFile from '@/components/selectFile/selectFile.vue'
{{- end }}
{{- if .HasArray}}
// 数组控制组件
import ArrayCtrl from '@/components/arrayCtrl/arrayCtrl.vue'
{{- end }}
// 全量引入格式化工具 请按需保留
import { getDictFunc, formatDate, formatBoolean, filterDict ,filterDataSource, returnArrImg, onDownloadFile } from '@/utils/format'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ref, reactive } from 'vue'
{{- if .AutoCreateBtnAuth }}
// 引入按钮权限标识
import { useBtnAuth } from '@/utils/btnAuth'
{{- end }}
{{if .HasExcel -}}
// 导出组件
import ExportExcel from '@/components/exportExcel/exportExcel.vue'
// 导入组件
import ImportExcel from '@/components/exportExcel/importExcel.vue'
// 导出模板组件
import ExportTemplate from '@/components/exportExcel/exportTemplate.vue'
{{- end}}
defineOptions({
name: '{{.StructName}}'
})
{{- if .AutoCreateBtnAuth }}
// 按钮权限实例化
const btnAuth = useBtnAuth()
{{- end }}
// 提交按钮loading
const btnLoading = ref(false)
// 控制更多查询条件显示/隐藏状态
const showAllQuery = ref(false)
// 自动化生成的字典(可能为空)以及字段
{{- range $index, $element := .DictTypes}}
const {{ $element }}Options = ref([])
{{- end }}
const formData = ref({
{{- if .IsTree }}
parentID:undefined,
{{- end }}
{{- range .Fields}}
{{- if .Form}}
{{ GenerateDefaultFormValue . }}
{{- end }}
{{- end }}
})
{{- if .HasDataSource }}
const dataSource = ref([])
const getDataSourceFunc = async()=>{
const res = await get{{.StructName}}DataSource()
if (res.code === 0) {
dataSource.value = res.data
}
}
getDataSourceFunc()
{{- end }}
// 验证规则
const rule = reactive({
{{- range .Fields }}
{{- if .Form }}
{{- if eq .Require true }}
{{.FieldJson }} : [{
required: true,
message: '{{ .ErrorText }}',
trigger: ['input','blur'],
},
{{- if eq .FieldType "string" }}
{
whitespace: true,
message: '不能只输入空格',
trigger: ['input', 'blur'],
}
{{- end }}
],
{{- end }}
{{- end }}
{{- end }}
})
const elFormRef = ref()
const elSearchFormRef = ref()
// =========== 表格控制部分 ===========
const page = ref(1)
const total = ref(0)
const pageSize = ref(10)
const tableData = ref([])
const searchInfo = ref({})
{{- if .NeedSort}}
// 排序
const sortChange = ({ prop, order }) => {
const sortMap = {
CreatedAt:"created_at",
ID:"id",
{{- range .Fields}}
{{- if .Table}}
{{- if and .Sort}}
{{- if not (eq .ColumnName "")}}
{{.FieldJson}}: '{{.ColumnName}}',
{{- end}}
{{- end}}
{{- end}}
{{- end}}
}
let sort = sortMap[prop]
if(!sort){
sort = prop.replace(/[A-Z]/g, match => `_${match.toLowerCase()}`)
}
searchInfo.value.sort = sort
searchInfo.value.order = order
getTableData()
}
{{- end}}
{{- if not .IsTree }}
// 重置
const onReset = () => {
searchInfo.value = {}
getTableData()
}
// 搜索
const onSubmit = () => {
elSearchFormRef.value?.validate(async(valid) => {
if (!valid) return
page.value = 1
{{- range .Fields}}{{- if eq .FieldType "bool" }}
if (searchInfo.value.{{.FieldJson}} === ""){
searchInfo.value.{{.FieldJson}}=null
}{{ end }}{{ end }}
getTableData()
})
}
// 分页
const handleSizeChange = (val) => {
pageSize.value = val
getTableData()
}
// 修改页面容量
const handleCurrentChange = (val) => {
page.value = val
getTableData()
}
// 查询
const getTableData = async() => {
const table = await get{{.StructName}}List({ page: page.value, pageSize: pageSize.value, ...searchInfo.value })
if (table.code === 0) {
tableData.value = table.data.list
total.value = table.data.total
page.value = table.data.page
pageSize.value = table.data.pageSize
}
}
{{- else }}
// 树选择器配置
const defaultProps = {
children: "children",
label: "{{ .TreeJson }}",
value: "{{ .PrimaryField.FieldJson }}"
}
const rootNode = {
{{ .PrimaryField.FieldJson }}: 0,
{{ .TreeJson }}: '根节点',
children: []
}
// 查询
const getTableData = async() => {
const table = await get{{.StructName}}List()
if (table.code === 0) {
tableData.value = table.data || []
}
}
{{- end }}
getTableData()
// ============== 表格控制部分结束 ===============
// 获取需要的字典 可能为空 按需保留
const setOptions = async () =>{
{{- range $index, $element := .DictTypes }}
{{ $element }}Options.value = await getDictFunc('{{$element}}')
{{- end }}
}
// 获取需要的字典 可能为空 按需保留
setOptions()
// 多选数据
const multipleSelection = ref([])
// 多选
const handleSelectionChange = (val) => {
multipleSelection.value = val
}
// 删除行
const deleteRow = (row) => {
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
delete{{.StructName}}Func(row)
})
}
// 多选删除
const onDelete = async() => {
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async() => {
const {{.PrimaryField.FieldJson}}s = []
if (multipleSelection.value.length === 0) {
ElMessage({
type: 'warning',
message: '请选择要删除的数据'
})
return
}
multipleSelection.value &&
multipleSelection.value.map(item => {
{{.PrimaryField.FieldJson}}s.push(item.{{.PrimaryField.FieldJson}})
})
const res = await delete{{.StructName}}ByIds({ {{.PrimaryField.FieldJson}}s })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功'
})
if (tableData.value.length === {{.PrimaryField.FieldJson}}s.length && page.value > 1) {
page.value--
}
getTableData()
}
})
}
// 行为控制标记(弹窗内部需要增还是改)
const type = ref('')
// 更新行
const update{{.StructName}}Func = async(row) => {
const res = await find{{.StructName}}({ {{.PrimaryField.FieldJson}}: row.{{.PrimaryField.FieldJson}} })
type.value = 'update'
if (res.code === 0) {
formData.value = res.data
dialogFormVisible.value = true
}
}
// 删除行
const delete{{.StructName}}Func = async (row) => {
const res = await delete{{.StructName}}({ {{.PrimaryField.FieldJson}}: row.{{.PrimaryField.FieldJson}} })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功'
})
if (tableData.value.length === 1 && page.value > 1) {
page.value--
}
getTableData()
}
}
// 弹窗控制标记
const dialogFormVisible = ref(false)
// 打开弹窗
const openDialog = ({{- if .IsTree -}}row{{- end -}}) => {
type.value = 'create'
{{- if .IsTree }}
formData.value.parentID = row ? row.{{.PrimaryField.FieldJson}} : undefined
{{- end }}
dialogFormVisible.value = true
}
// 关闭弹窗
const closeDialog = () => {
dialogFormVisible.value = false
formData.value = {
{{- range .Fields}}
{{- if .Form}}
{{ GenerateDefaultFormValue . }}
{{- end }}
{{- end }}
}
}
// 弹窗确定
const enterDialog = async () => {
btnLoading.value = true
elFormRef.value?.validate( async (valid) => {
if (!valid) return btnLoading.value = false
let res
switch (type.value) {
case 'create':
res = await create{{.StructName}}(formData.value)
break
case 'update':
res = await update{{.StructName}}(formData.value)
break
default:
res = await create{{.StructName}}(formData.value)
break
}
btnLoading.value = false
if (res.code === 0) {
ElMessage({
type: 'success',
message: '创建/更改成功'
})
closeDialog()
getTableData()
}
})
}
const detailForm = ref({})
// 查看详情控制标记
const detailShow = ref(false)
// 打开详情弹窗
const openDetailShow = () => {
detailShow.value = true
}
// 打开详情
const getDetails = async (row) => {
//
const res = await find{{.StructName}}({ {{.PrimaryField.FieldJson}}: row.{{.PrimaryField.FieldJson}} })
if (res.code === 0) {
detailForm.value = res.data
openDetailShow()
}
}
// 关闭详情弹窗
const closeDetailShow = () => {
detailShow.value = false
detailForm.value = {}
}
</script>
<style>
{{if .HasFile }}
.file-list{
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.fileBtn{
margin-bottom: 10px;
}
.fileBtn:last-child{
margin-bottom: 0;
}
{{end}}
</style>
{{- else}}
<template>
<div>form</div>
</template>
<script setup>
defineOptions({
name: '{{.StructName}}'
})
</script>
<style>
</style>
{{- end }}
{{- end }}

View File

@@ -19,6 +19,7 @@ type RouterGroup struct {
AuthorityBtnRouter
SysExportTemplateRouter
SysParamsRouter
SysVersionRouter
}
var (
@@ -41,4 +42,5 @@ var (
dictionaryDetailApi = api.ApiGroupApp.SystemApiGroup.DictionaryDetailApi
autoCodeTemplateApi = api.ApiGroupApp.SystemApiGroup.AutoCodeTemplateApi
exportTemplateApi = api.ApiGroupApp.SystemApiGroup.SysExportTemplateApi
sysVersionApi = api.ApiGroupApp.SystemApiGroup.SysVersionApi
)

View File

@@ -19,6 +19,11 @@ func (s *AutoCodeRouter) InitAutoCodeRouter(Router *gin.RouterGroup, RouterPubli
autoCodeRouter.POST("createTemp", autoCodeTemplateApi.Create) // 创建自动化代码
autoCodeRouter.POST("addFunc", autoCodeTemplateApi.AddFunc) // 为代码插入方法
}
{
autoCodeRouter.POST("mcp", autoCodeTemplateApi.MCP) // 自动创建Mcp Tool模板
autoCodeRouter.POST("mcpList", autoCodeTemplateApi.MCPList) // 获取MCP ToolList
autoCodeRouter.POST("mcpTest", autoCodeTemplateApi.MCPTest) // MCP 工具测试
}
{
autoCodeRouter.POST("getPackage", autoCodePackageApi.All) // 获取package包
autoCodeRouter.POST("delPackage", autoCodePackageApi.Delete) // 删除package包

View File

@@ -9,7 +9,6 @@ type OperationRecordRouter struct{}
func (s *OperationRecordRouter) InitSysOperationRecordRouter(Router *gin.RouterGroup) {
operationRecordRouter := Router.Group("sysOperationRecord")
{
operationRecordRouter.POST("createSysOperationRecord", operationRecordApi.CreateSysOperationRecord) // 新建SysOperationRecord
operationRecordRouter.DELETE("deleteSysOperationRecord", operationRecordApi.DeleteSysOperationRecord) // 删除SysOperationRecord
operationRecordRouter.DELETE("deleteSysOperationRecordByIds", operationRecordApi.DeleteSysOperationRecordByIds) // 批量删除SysOperationRecord
operationRecordRouter.GET("findSysOperationRecord", operationRecordApi.FindSysOperationRecord) // 根据ID获取SysOperationRecord

View File

@@ -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) // 用户界面配置
}
{

Some files were not shown because too many files have changed in this diff Show More