Merge pull request #2065 from flipped-aurora/2.8.5

支持多模块mcp,精细化api和menu的创建规则。
This commit is contained in:
PiexlMax(奇淼
2025-08-05 18:43:21 +08:00
committed by GitHub
8 changed files with 977 additions and 425 deletions

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 && !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

View File

@@ -41,7 +41,7 @@ type ApiCreator struct{}
// New 创建API创建工具
func (a *ApiCreator) New() mcp.Tool {
return mcp.NewTool("create_api",
mcp.WithDescription("创建后端API记录用于在生成后端接口时自动创建对应的API权限记录,只要创建API层,router下的文件产生路径变化都需要调用此mcp。"),
mcp.WithDescription("创建后端API记录用于AI编辑器自动添加API接口时自动创建对应的API权限记录。注意使用gva_auto_generate创建的包和模块会自动创建API权限无需调用此工具。仅在AI编辑器自动添加API或router下的文件产生路径变化时使用。"),
mcp.WithString("path",
mcp.Required(),
mcp.Description("API路径/user/create"),

View File

@@ -8,12 +8,11 @@ ExecutionPlan 是用于自动化模块创建的执行计划结构体,包含了
```go
type ExecutionPlan struct {
PackageName string `json:"packageName"` // 包名,如:"user", "order", "product"
ModuleName string `json:"moduleName"` // 模块名,通常与结构体名相同
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时必需
ModulesInfo []*request.AutoCode `json:"modulesInfo,omitempty"` // 模块信息数组当NeedCreatedModules=true时必需,支持批量创建
Paths map[string]string `json:"paths,omitempty"` // 路径信息
}
```
@@ -86,20 +85,32 @@ type AutoCodeField struct {
Clearable bool `json:"clearable"` // 是否可清空
Sort bool `json:"sort"` // 是否支持排序
PrimaryKey bool `json:"primaryKey"` // 是否主键
DataSource *DataSource `json:"dataSource"` // 数据源
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创建新包和模块
### 示例1创建新包和批量创建多个模块
```json
{
"packageName": "user",
"moduleName": "User",
"packageType": "package",
"needCreatedPackage": true,
"needCreatedModules": true,
@@ -109,141 +120,404 @@ type AutoCodeField struct {
"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": null,
"checkDataSource": false,
"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"
}
]
}
"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仅在现有包中创建模块
### 示例2仅在现有包中批量创建多个模块
```json
{
"packageName": "system",
"moduleName": "Role",
"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
}
]
}
"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, int, int64, float64, bool, time.Time, enum, picture, video, file, pictures, array, richtext, json等
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验证
## 常见错误避免

View File

@@ -17,13 +17,12 @@
}
```
### 第二步:确认
### 第二步:确认(支持批量创建多个模块)
```json
{
"action": "confirm",
"executionPlan": {
"packageName": "library",
"moduleName": "Book",
"packageType": "package",
"needCreatedPackage": true,
"needCreatedModules": true,
@@ -33,55 +32,138 @@
"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": ""
}
]
}
"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": ""
}
]
}
]
}
}
```
@@ -119,4 +201,5 @@
1. 必须先调用`confirm`来获取确认信息
2.`execute`时必须提供相应的确认参数
3. 确认参数的值必须是"yes"或"no"
4. 如果不需要创建包或模块,则不需要提供对应的确认参数
4. 如果不需要创建包或模块,则不需要提供对应的确认参数
5. 字段类型支持string字符串,richtext富文本,int整型,bool布尔值,float64浮点型,time.Time时间,enum枚举,picture单图片字符串,pictures多图片json字符串,video视频字符串,file文件json字符串,jsonJSON,array数组

View File

@@ -67,15 +67,14 @@ type AnalysisResponse struct {
Message string `json:"message"`
}
// ExecutionPlan 执行计划
// ExecutionPlan 执行计划 - 支持批量创建
type ExecutionPlan struct {
PackageName string `json:"packageName"`
ModuleName string `json:"moduleName"`
PackageType string `json:"packageType"` // "plugin" 或 "package"
NeedCreatedPackage bool `json:"needCreatedPackage"`
NeedCreatedModules bool `json:"needCreatedModules"`
PackageInfo *request.SysAutoCodePackageCreate `json:"packageInfo,omitempty"`
ModulesInfo *request.AutoCode `json:"modulesInfo,omitempty"`
ModulesInfo []*request.AutoCode `json:"modulesInfo,omitempty"` // 改为数组支持多个模块
Paths map[string]string `json:"paths,omitempty"`
}
@@ -111,12 +110,18 @@ type ConfirmationResponse struct {
// New 返回工具注册信息
func (t *AutomationModuleAnalyzer) New() mcp.Tool {
return mcp.NewTool("gva_auto_generate",
mcp.WithDescription(`**🚀 最高优先级工具:当用户需要创建模块、包、完整功能时,必须优先使用此工具!**
mcp.WithDescription(`**🔧 核心执行工具接收requirement_analyzer分析结果执行具体的模块创建操作**
**优先级说明**
- **最高优先级**创建完整模块、包、功能模块
- **关键词触发**:模块、包、完整、整套、全套、功能、管理系统等
- **适用场景**:用户说"创建订单管理模块"、"创建用户管理功能"、"创建完整的商品管理"等
**工作流位置**
- **第二优先级**在requirement_analyzer之后使用
- **接收输入**来自requirement_analyzer的1xxx2xxx格式分析结果
- **执行操作**:根据分析结果创建完整模块、包、功能模块
**批量创建功能:**
- 支持在单个ExecutionPlan中创建多个模块
- modulesInfo字段为数组可包含多个模块配置
- 一次性处理多个模块的创建和字典生成
- 与requirement_analyzer配合实现完整工作流
分步骤分析自动化模块1) 分析现有模块信息供AI选择 2) 请求用户确认 3) 根据确认结果执行创建操作
@@ -125,15 +130,14 @@ func (t *AutomationModuleAnalyzer) New() mcp.Tool {
- 如果字典不存在,会自动创建对应的字典及默认的字典详情项
- 字典创建包括字典主表记录和默认的选项值选项1、选项2等
**与其他工具的关系**
- 此工具创建完整模块后会自动提示相关API和菜单创建建议
- 如果用户只需要单个API或菜单可以使用 smart_assistant 工具
- create_api 和 create_menu 工具仅用于数据库记录创建
**推荐工作流**
1. 用户提出需求 → requirement_analyzer最高优先级
2. AI分析需求为1xxx2xxx格式 → gva_auto_generate执行创建
3. 创建完成后,根据需要使用其他辅助工具
重要ExecutionPlan结构体格式要求
重要ExecutionPlan结构体格式要求(支持批量创建)
{
"packageName": "包名(string)",
"moduleName": "模块名(string)",
"packageType": "package或plugin(string)",
"needCreatedPackage": "是否需要创建包(bool)",
"needCreatedModules": "是否需要创建模块(bool)",
@@ -143,7 +147,7 @@ func (t *AutomationModuleAnalyzer) New() mcp.Tool {
"template": "package或plugin(string)",
"packageName": "包名(string)"
},
"modulesInfo": {
"modulesInfo": [{
"package": "包名(string)",
"tableName": "数据库表名(string)",
"businessDB": "业务数据库(string)",
@@ -185,18 +189,24 @@ func (t *AutomationModuleAnalyzer) New() mcp.Tool {
"clearable": "是否可清空(bool)",
"sort": "是否排序(bool)",
"primaryKey": "是否主键(bool)",
"dataSource": "数据源(object)",
"checkDataSource": "检查数据源(bool)",
"dataSource": "数据源配置(object) - 用于配置字段的关联表信息,结构:{\"dbName\":\"数据库名\",\"table\":\"关联表名\",\"label\":\"显示字段\",\"value\":\"值字段\",\"association\":1或2(1=一对一,2=一对多),\"hasDeletedAt\":true/false}。\n\n**获取表名提示:**\n- 可在 server/model 和 plugin/xxx/model 目录下查看对应模块的 TableName() 接口实现获取实际表名\n- 例如SysUser 的表名为 \"sys_users\"ExaFileUploadAndDownload 的表名为 \"exa_file_upload_and_downloads\"\n- 插件模块示例Info 的表名为 \"gva_announcements_info\"\n\n**获取数据库名提示:**\n- 主数据库:通常使用 \"gva\"(默认数据库标识)\n- 多数据库:可在 config.yaml 的 db-list 配置中查看可用数据库的 alias-name 字段\n- 如果用户未提及关联多数据库信息 则使用默认数据库 默认数据库的情况下 dbName此处填写为空",
"checkDataSource": "是否检查数据源(bool) - 启用后会验证关联表的存在性",
"fieldIndexType": "索引类型(string)"
}]
}
}, {
"package": "包名(string)",
"tableName": "第二个模块的表名(string)",
"structName": "第二个模块的结构体名(string)",
"description": "第二个模块的描述(string)",
"...": "更多模块配置..."
}]
}
注意:
1. needCreatedPackage=true时packageInfo必需
2. needCreatedModules=true时modulesInfo必需
3. packageType只能是"package"或"plugin"
4. 字段类型支持string,int,int64,float64,bool,time.Time,enum,picture,video,file,pictures,array,richtext,json
4. 字段类型支持string(字符串),richtext富文本,int整型,bool布尔值,float64(浮点型),time.Time(时间),enum(枚举),picture(单图片,字符串),pictures多图片json字符串,video视频字符串,file文件json字符串,jsonJSON,array数组
5. 搜索类型支持:=,!=,>,>=,<,<=,NOT BETWEEN/LIKE/BETWEEN/IN/NOT IN
6. gvaModel=true时自动包含ID,CreatedAt,UpdatedAt,DeletedAt字段
7. **重要**当gvaModel=false时必须有一个字段的primaryKey=true否则会导致PrimaryField为nil错误
@@ -204,10 +214,19 @@ func (t *AutomationModuleAnalyzer) New() mcp.Tool {
9. 智能字典创建功能:当字段使用字典类型(DictType)时,系统会:
- 自动检查字典是否存在,如果不存在则创建字典
- 根据字典类型和字段描述智能生成默认选项,支持状态、性别、类型、等级、优先级、审批、角色、布尔值、订单、颜色、尺寸等常见场景
- 为无法识别的字典类型提供通用默认选项`),
- 为无法识别的字典类型提供通用默认选项
10. **模块关联配置**当需要配置模块间的关联关系时使用dataSource字段
- **dbName**: 关联的数据库名称
- **table**: 关联的表名
- **label**: 用于显示的字段名如name、title等
- **value**: 用于存储的值字段名通常是id
- **association**: 关联关系类型1=一对一关联2=一对多关联)
- **hasDeletedAt**: 关联表是否有软删除字段
- **checkDataSource**: 设为true时会验证关联表的存在性
- 示例:{"dbName":"gva","table":"sys_users","label":"username","value":"id","association":2,"hasDeletedAt":true}`),
mcp.WithString("action",
mcp.Required(),
mcp.Description("执行操作:'analyze' 分析现有模块信息,'confirm' 请求用户确认创建,'execute' 执行创建操作"),
mcp.Description("执行操作:'analyze' 分析现有模块信息,'confirm' 请求用户确认创建,'execute' 执行创建操作(支持批量创建多个模块)"),
),
mcp.WithString("requirement",
mcp.Description("用户需求描述action=analyze时必需"),
@@ -514,7 +533,7 @@ func (t *AutomationModuleAnalyzer) handleAnalyze(ctx context.Context, request mc
var validPackages []model.SysAutoCodePackage
var emptyPackageIDs []uint
var emptyPackageNames []string
for _, pkg := range packages {
// 检查包对应的文件夹是否为空
isEmpty, err := t.isPackageFolderEmpty(pkg.PackageName, pkg.Template)
@@ -524,13 +543,13 @@ func (t *AutomationModuleAnalyzer) handleAnalyze(ctx context.Context, request mc
validPackages = append(validPackages, pkg)
continue
}
if isEmpty {
// 记录需要删除的包ID和包名
emptyPackageIDs = append(emptyPackageIDs, pkg.ID)
emptyPackageNames = append(emptyPackageNames, pkg.PackageName)
global.GVA_LOG.Info(fmt.Sprintf("发现空包文件夹: %s将删除数据库记录和文件夹", pkg.PackageName))
// 删除空文件夹
if err := t.removeEmptyPackageFolder(pkg.PackageName, pkg.Template); err != nil {
global.GVA_LOG.Warn(fmt.Sprintf("删除空包文件夹 %s 失败: %v", pkg.PackageName, err))
@@ -540,7 +559,7 @@ func (t *AutomationModuleAnalyzer) handleAnalyze(ctx context.Context, request mc
validPackages = append(validPackages, pkg)
}
}
// 批量删除空包的数据库记录
if len(emptyPackageIDs) > 0 {
if err := global.GVA_DB.Where("id IN ?", emptyPackageIDs).Delete(&model.SysAutoCodePackage{}).Error; err != nil {
@@ -549,7 +568,7 @@ func (t *AutomationModuleAnalyzer) handleAnalyze(ctx context.Context, request mc
global.GVA_LOG.Info(fmt.Sprintf("成功删除 %d 个空包的数据库记录", len(emptyPackageIDs)))
}
}
// 转换有效的包信息
for _, pkg := range validPackages {
moduleInfos = append(moduleInfos, ModuleInfo{
@@ -573,14 +592,14 @@ func (t *AutomationModuleAnalyzer) handleAnalyze(ctx context.Context, request mc
}
}
}
// 清理相关的API和菜单记录
if len(emptyHistoryIDs) > 0 {
if err := t.cleanupRelatedApiAndMenus(emptyHistoryIDs); err != nil {
global.GVA_LOG.Warn(fmt.Sprintf("清理空包相关API和菜单失败: %v", err))
}
}
// 批量删除相关历史记录
if len(emptyHistoryIDs) > 0 {
if err := global.GVA_DB.Where("id IN ?", emptyHistoryIDs).Delete(&model.SysAutoCodeHistory{}).Error; err != nil {
@@ -590,13 +609,13 @@ func (t *AutomationModuleAnalyzer) handleAnalyze(ctx context.Context, request mc
}
}
}
// 创建有效包名的映射,用于快速查找
validPackageNames := make(map[string]bool)
for _, pkg := range validPackages {
validPackageNames[pkg.PackageName] = true
}
// 收集需要删除的脏历史记录ID包名不在有效包列表中的历史记录
var dirtyHistoryIDs []uint
for _, history := range histories {
@@ -604,21 +623,21 @@ func (t *AutomationModuleAnalyzer) handleAnalyze(ctx context.Context, request mc
dirtyHistoryIDs = append(dirtyHistoryIDs, history.ID)
}
}
// 删除脏历史记录
if len(dirtyHistoryIDs) > 0 {
// 清理相关的API和菜单记录
if err := t.cleanupRelatedApiAndMenus(dirtyHistoryIDs); err != nil {
global.GVA_LOG.Warn(fmt.Sprintf("清理脏历史记录相关API和菜单失败: %v", err))
}
if err := global.GVA_DB.Where("id IN ?", dirtyHistoryIDs).Delete(&model.SysAutoCodeHistory{}).Error; err != nil {
global.GVA_LOG.Warn(fmt.Sprintf("删除脏历史记录失败: %v", err))
} else {
global.GVA_LOG.Info(fmt.Sprintf("成功删除 %d 个脏历史记录(包名不在有效包列表中)", len(dirtyHistoryIDs)))
}
}
// 转换有效的历史记录(只保留包名存在于有效包列表中的历史记录)
var historyInfos []HistoryInfo
for _, history := range histories {
@@ -643,7 +662,7 @@ func (t *AutomationModuleAnalyzer) handleAnalyze(ctx context.Context, request mc
global.GVA_LOG.Warn("扫描预设计模块失败" + err.Error())
allPredesignedModules = []PredesignedModuleInfo{} // 确保不为nil
}
// 过滤掉与已删除包相关的预设计模块
var predesignedModules []PredesignedModuleInfo
for _, module := range allPredesignedModules {
@@ -654,7 +673,7 @@ func (t *AutomationModuleAnalyzer) handleAnalyze(ctx context.Context, request mc
break
}
}
// 只保留未被删除包的预设计模块
if !isDeleted {
predesignedModules = append(predesignedModules, module)
@@ -664,7 +683,7 @@ func (t *AutomationModuleAnalyzer) handleAnalyze(ctx context.Context, request mc
// 构建分析结果消息
var message string
var deletionDetails []string
// 收集删除信息
if len(emptyHistoryIDs) > 0 {
deletionDetails = append(deletionDetails, fmt.Sprintf("%d个空包相关历史记录", len(emptyHistoryIDs)))
@@ -675,13 +694,13 @@ func (t *AutomationModuleAnalyzer) handleAnalyze(ctx context.Context, request mc
if len(allPredesignedModules) > len(predesignedModules) {
deletionDetails = append(deletionDetails, fmt.Sprintf("%d个相关预设计模块", len(allPredesignedModules)-len(predesignedModules)))
}
if len(emptyPackageNames) > 0 || len(deletionDetails) > 0 {
var cleanupInfo string
if len(emptyPackageNames) > 0 {
cleanupInfo = fmt.Sprintf("检测到存在 %s 包但内容为空我已经删除这些包的文件夹包括model、api、service、router目录和数据库记录", strings.Join(emptyPackageNames, "、"))
}
deletionInfo := ""
if len(deletionDetails) > 0 {
if cleanupInfo != "" {
@@ -690,7 +709,7 @@ func (t *AutomationModuleAnalyzer) handleAnalyze(ctx context.Context, request mc
deletionInfo = fmt.Sprintf("检测到脏数据,已删除%s", strings.Join(deletionDetails, "、"))
}
}
if cleanupInfo != "" {
message = fmt.Sprintf("分析完成:获取到 %d 个有效包、%d 个历史记录和 %d 个预设计模块。%s%s如果需要使用这些包名需要重新创建。请AI根据需求选择合适的包和模块", len(validPackages), len(historyInfos), len(predesignedModules), cleanupInfo, deletionInfo)
} else {
@@ -699,7 +718,7 @@ func (t *AutomationModuleAnalyzer) handleAnalyze(ctx context.Context, request mc
} else {
message = fmt.Sprintf("分析完成:获取到 %d 个有效包、%d 个历史记录和 %d 个预设计模块请AI根据需求选择合适的包和模块", len(validPackages), len(historyInfos), len(predesignedModules))
}
// 构建分析结果
analysisResult := AnalysisResponse{
Packages: moduleInfos,
@@ -748,10 +767,9 @@ func (t *AutomationModuleAnalyzer) handleAnalyze(ctx context.Context, request mc
}
- 请在创建模块之前先创建所需的字典选项
重要提醒ExecutionPlan必须严格按照以下格式
重要提醒ExecutionPlan必须严格按照以下格式(支持批量创建多个模块)
{
"packageName": "包名",
"moduleName": "模块名",
"packageType": "package或plugin", // 当用户提到插件时必须是"plugin"
"needCreatedPackage": true/false,
"needCreatedModules": true/false,
@@ -761,7 +779,7 @@ func (t *AutomationModuleAnalyzer) handleAnalyze(ctx context.Context, request mc
"template": "package或plugin", // 必须与packageType保持一致
"packageName": "包名"
},
"modulesInfo": {
"modulesInfo": [{
"package": "包名",
"tableName": "数据库表名",
"businessDB": "",
@@ -807,7 +825,13 @@ func (t *AutomationModuleAnalyzer) handleAnalyze(ctx context.Context, request mc
"checkDataSource": false,
"fieldIndexType": ""
}]
}
}, {
"package": "包名",
"tableName": "第二个模块的表名",
"structName": "第二个模块的结构体名",
"description": "第二个模块的描述",
"...": "更多模块配置..."
}]
}
**重要提醒**ExecutionPlan必须严格按照以下格式和验证规则
@@ -835,13 +859,13 @@ func (t *AutomationModuleAnalyzer) handleAnalyze(ctx context.Context, request mc
12. 如果字段需要字典类型,请先使用 generate_dictionary_options 工具创建字典
13. 字典创建成功后,再执行模块创建操作
`, string(resultJSON), requirement, pluginDetectionMsg,
func() string {
if len(emptyPackageNames) > 0 {
return fmt.Sprintf("**重要提醒**:检测到 %s 包存在但内容为空,已自动删除相关文件夹和数据库记录。如果用户需求涉及这些包名,请设置 needCreatedPackage=true 重新创建。", strings.Join(emptyPackageNames, "、"))
}
return ""
}()),
`, string(resultJSON), requirement, pluginDetectionMsg,
func() string {
if len(emptyPackageNames) > 0 {
return fmt.Sprintf("**重要提醒**:检测到 %s 包存在但内容为空,已自动删除相关文件夹和数据库记录。如果用户需求涉及这些包名,请设置 needCreatedPackage=true 重新创建。", strings.Join(emptyPackageNames, "、"))
}
return ""
}()),
},
},
}, nil
@@ -872,18 +896,23 @@ func (t *AutomationModuleAnalyzer) handleConfirm(ctx context.Context, request mc
}
// 构建确认响应
var moduleNames []string
for _, moduleInfo := range plan.ModulesInfo {
moduleNames = append(moduleNames, moduleInfo.StructName)
}
moduleNamesStr := strings.Join(moduleNames, "_")
confirmResponse := ConfirmationResponse{
Message: "请确认以下创建计划:",
PackageConfirm: plan.NeedCreatedPackage,
ModulesConfirm: plan.NeedCreatedModules,
CanProceed: true,
ConfirmationKey: fmt.Sprintf("%s_%s_%d", plan.PackageName, plan.ModuleName, time.Now().Unix()),
ConfirmationKey: fmt.Sprintf("%s_%s_%d", plan.PackageName, moduleNamesStr, time.Now().Unix()),
}
// 构建详细的确认信息
var confirmDetails strings.Builder
confirmDetails.WriteString(fmt.Sprintf("包名: %s\n", plan.PackageName))
confirmDetails.WriteString(fmt.Sprintf("模块名: %s\n", plan.ModuleName))
confirmDetails.WriteString(fmt.Sprintf("包类型: %s\n", plan.PackageType))
if plan.NeedCreatedPackage && plan.PackageInfo != nil {
@@ -894,15 +923,18 @@ func (t *AutomationModuleAnalyzer) handleConfirm(ctx context.Context, request mc
confirmDetails.WriteString(fmt.Sprintf(" - 模板: %s\n", plan.PackageInfo.Template))
}
if plan.NeedCreatedModules && plan.ModulesInfo != nil {
confirmDetails.WriteString("\n需要创建模块:\n")
confirmDetails.WriteString(fmt.Sprintf(" - 结构体名: %s\n", plan.ModulesInfo.StructName))
confirmDetails.WriteString(fmt.Sprintf(" - 表名: %s\n", plan.ModulesInfo.TableName))
confirmDetails.WriteString(fmt.Sprintf(" - 描述: %s\n", plan.ModulesInfo.Description))
confirmDetails.WriteString(fmt.Sprintf(" - 字段数量: %d\n", len(plan.ModulesInfo.Fields)))
confirmDetails.WriteString(" - 字段列表:\n")
for _, field := range plan.ModulesInfo.Fields {
confirmDetails.WriteString(fmt.Sprintf(" * %s (%s): %s\n", field.FieldName, field.FieldType, field.FieldDesc))
if plan.NeedCreatedModules && len(plan.ModulesInfo) > 0 {
confirmDetails.WriteString(fmt.Sprintf("\n需要创建模块 (共%d个):\n", len(plan.ModulesInfo)))
for i, moduleInfo := range plan.ModulesInfo {
confirmDetails.WriteString(fmt.Sprintf("\n模块 %d:\n", i+1))
confirmDetails.WriteString(fmt.Sprintf(" - 结构体名: %s\n", moduleInfo.StructName))
confirmDetails.WriteString(fmt.Sprintf(" - 表名: %s\n", moduleInfo.TableName))
confirmDetails.WriteString(fmt.Sprintf(" - 描述: %s\n", moduleInfo.Description))
confirmDetails.WriteString(fmt.Sprintf(" - 字段数量: %d\n", len(moduleInfo.Fields)))
confirmDetails.WriteString(" - 字段列表:\n")
for _, field := range moduleInfo.Fields {
confirmDetails.WriteString(fmt.Sprintf(" * %s (%s): %s\n", field.FieldName, field.FieldType, field.FieldDesc))
}
}
}
@@ -1045,10 +1077,10 @@ func (t *AutomationModuleAnalyzer) buildDirectoryStructure(plan *ExecutionPlan)
packageName = plan.PackageInfo.PackageName
}
// 如果计划中有模块信息,获取结构名
// 如果计划中有模块信息,获取第一个模块的结构名作为默认值
structName := "ExampleStruct"
if plan.ModulesInfo != nil && plan.ModulesInfo.StructName != "" {
structName = plan.ModulesInfo.StructName
if len(plan.ModulesInfo) > 0 && plan.ModulesInfo[0].StructName != "" {
structName = plan.ModulesInfo[0].StructName
}
// 根据包类型构建不同的路径结构
@@ -1138,9 +1170,6 @@ func (t *AutomationModuleAnalyzer) validateExecutionPlan(plan *ExecutionPlan) er
if plan.PackageName == "" {
return errors.New("packageName 不能为空")
}
if plan.ModuleName == "" {
return errors.New("moduleName 不能为空")
}
if plan.PackageType != "package" && plan.PackageType != "plugin" {
return errors.New("packageType 必须是 'package' 或 'plugin'")
}
@@ -1171,104 +1200,108 @@ func (t *AutomationModuleAnalyzer) validateExecutionPlan(plan *ExecutionPlan) er
}
}
// 验证模块信息
// 验证模块信息(批量验证)
if plan.NeedCreatedModules {
if plan.ModulesInfo == nil {
if len(plan.ModulesInfo) == 0 {
return errors.New("当 needCreatedModules=true 时modulesInfo 不能为空")
}
if plan.ModulesInfo.Package == "" {
return errors.New("modulesInfo.package 不能为空")
}
if plan.ModulesInfo.StructName == "" {
return errors.New("modulesInfo.structName 不能为空")
}
if plan.ModulesInfo.TableName == "" {
return errors.New("modulesInfo.tableName 不能为空")
}
if plan.ModulesInfo.Description == "" {
return errors.New("modulesInfo.description 不能为空")
}
if plan.ModulesInfo.Abbreviation == "" {
return errors.New("modulesInfo.abbreviation 不能为空")
}
if plan.ModulesInfo.PackageName == "" {
return errors.New("modulesInfo.packageName 不能为空")
}
if plan.ModulesInfo.HumpPackageName == "" {
return errors.New("modulesInfo.humpPackageName 不能为空")
}
// 验证字段信息
if len(plan.ModulesInfo.Fields) == 0 {
return errors.New("modulesInfo.fields 不能为空,至少需要一个字段")
}
for i, field := range plan.ModulesInfo.Fields {
if field.FieldName == "" {
return fmt.Errorf("字段 %d 的 fieldName 不能为空", i+1)
// 遍历验证每个模块
for moduleIndex, moduleInfo := range plan.ModulesInfo {
if moduleInfo.Package == "" {
return fmt.Errorf("模块 %d 的 package 不能为空", moduleIndex+1)
}
if field.FieldDesc == "" {
return fmt.Errorf("字段 %d 的 fieldDesc 不能为空", i+1)
if moduleInfo.StructName == "" {
return fmt.Errorf("模块 %d 的 structName 不能为空", moduleIndex+1)
}
if field.FieldType == "" {
return fmt.Errorf("字段 %d 的 fieldType 不能为空", i+1)
if moduleInfo.TableName == "" {
return fmt.Errorf("模块 %d 的 tableName 不能为空", moduleIndex+1)
}
if field.FieldJson == "" {
return fmt.Errorf("字段 %d 的 fieldJson 不能为空", i+1)
if moduleInfo.Description == "" {
return fmt.Errorf("模块 %d 的 description 不能为空", moduleIndex+1)
}
if field.ColumnName == "" {
return fmt.Errorf("字段 %d 的 columnName 不能为空", i+1)
if moduleInfo.Abbreviation == "" {
return fmt.Errorf("模块 %d 的 abbreviation 不能为空", moduleIndex+1)
}
if moduleInfo.PackageName == "" {
return fmt.Errorf("模块 %d 的 packageName 不能为空", moduleIndex+1)
}
if moduleInfo.HumpPackageName == "" {
return fmt.Errorf("模块 %d 的 humpPackageName 不能为空", moduleIndex+1)
}
// 验证字段类型
validFieldTypes := []string{"string", "int", "int64", "float64", "bool", "time.Time", "enum", "picture", "video", "file", "pictures", "array", "richtext", "json"}
validType := false
for _, validFieldType := range validFieldTypes {
if field.FieldType == validFieldType {
validType = true
break
// 验证字段信息
if len(moduleInfo.Fields) == 0 {
return fmt.Errorf("模块 %d 的 fields 不能为空,至少需要一个字段", moduleIndex+1)
}
for i, field := range moduleInfo.Fields {
if field.FieldName == "" {
return fmt.Errorf("模块 %d 字段 %d 的 fieldName 不能为空", moduleIndex+1, i+1)
}
if field.FieldDesc == "" {
return fmt.Errorf("模块 %d 字段 %d 的 fieldDesc 不能为空", moduleIndex+1, i+1)
}
if field.FieldType == "" {
return fmt.Errorf("模块 %d 字段 %d 的 fieldType 不能为空", moduleIndex+1, i+1)
}
if field.FieldJson == "" {
return fmt.Errorf("模块 %d 字段 %d 的 fieldJson 不能为空", moduleIndex+1, i+1)
}
if field.ColumnName == "" {
return fmt.Errorf("模块 %d 字段 %d 的 columnName 不能为空", moduleIndex+1, i+1)
}
}
if !validType {
return fmt.Errorf("字段 %d 的 fieldType '%s' 不支持,支持的类型:%v", i+1, field.FieldType, validFieldTypes)
}
// 验证搜索类型(如果设置了)
if field.FieldSearchType != "" {
validSearchTypes := []string{"=", "!=", ">", ">=", "<", "<=", "LIKE", "BETWEEN", "IN", "NOT IN"}
validSearchType := false
for _, validType := range validSearchTypes {
if field.FieldSearchType == validType {
validSearchType = true
// 验证字段类型
validFieldTypes := []string{"string", "int", "int64", "float64", "bool", "time.Time", "enum", "picture", "video", "file", "pictures", "array", "richtext", "json"}
validType := false
for _, validFieldType := range validFieldTypes {
if field.FieldType == validFieldType {
validType = true
break
}
}
if !validSearchType {
return fmt.Errorf("字段 %d 的 fieldSearchType '%s' 不支持,支持的类型:%v", i+1, field.FieldSearchType, validSearchTypes)
if !validType {
return fmt.Errorf("模块 %d 字段 %d 的 fieldType '%s' 不支持,支持的类型:%v", moduleIndex+1, i+1, field.FieldType, validFieldTypes)
}
}
}
// 验证主键设置
if !plan.ModulesInfo.GvaModel {
// 当不使用GVA模型时必须有且仅有一个字段设置为主键
primaryKeyCount := 0
for _, field := range plan.ModulesInfo.Fields {
if field.PrimaryKey {
primaryKeyCount++
// 验证搜索类型(如果设置了)
if field.FieldSearchType != "" {
validSearchTypes := []string{"=", "!=", ">", ">=", "<", "<=", "LIKE", "BETWEEN", "IN", "NOT IN"}
validSearchType := false
for _, validType := range validSearchTypes {
if field.FieldSearchType == validType {
validSearchType = true
break
}
}
if !validSearchType {
return fmt.Errorf("模块 %d 字段 %d 的 fieldSearchType '%s' 不支持,支持的类型:%v", moduleIndex+1, i+1, field.FieldSearchType, validSearchTypes)
}
}
}
if primaryKeyCount == 0 {
return errors.New("当 gvaModel=false 时,必须有一个字段的 primaryKey=true")
}
if primaryKeyCount > 1 {
return errors.New("当 gvaModel=false 时,只能有一个字段的 primaryKey=true")
}
} else {
// 当使用GVA模型时所有字段的primaryKey都应该为false
for i, field := range plan.ModulesInfo.Fields {
if field.PrimaryKey {
return fmt.Errorf("当 gvaModel=true 时,字段 %d 的 primaryKey 应该为 false系统会自动创建ID主键", i+1)
// 验证主键设置
if !moduleInfo.GvaModel {
// 当不使用GVA模型时必须有且仅有一个字段设置为主键
primaryKeyCount := 0
for _, field := range moduleInfo.Fields {
if field.PrimaryKey {
primaryKeyCount++
}
}
if primaryKeyCount == 0 {
return fmt.Errorf("模块 %d当 gvaModel=false 时,必须有一个字段的 primaryKey=true", moduleIndex+1)
}
if primaryKeyCount > 1 {
return fmt.Errorf("模块 %d当 gvaModel=false 时,只能有一个字段的 primaryKey=true", moduleIndex+1)
}
} else {
// 当使用GVA模型时所有字段的primaryKey都应该为false
for i, field := range moduleInfo.Fields {
if field.PrimaryKey {
return fmt.Errorf("模块 %d当 gvaModel=true 时,字段 %d 的 primaryKey 应该为 false系统会自动创建ID主键", moduleIndex+1, i+1)
}
}
}
}
@@ -1305,30 +1338,33 @@ func (t *AutomationModuleAnalyzer) executeCreation(ctx context.Context, plan *Ex
result.Message += "包创建成功; "
}
// 创建字典(如果需要)
if plan.NeedCreatedModules && plan.ModulesInfo != nil {
dictResult := t.createRequiredDictionaries(ctx, plan.ModulesInfo)
result.Message += dictResult
}
// 创建模块(如果需要)
if plan.NeedCreatedModules && plan.ModulesInfo != nil {
// 批量创建字典和模块(如果需要)
if plan.NeedCreatedModules && len(plan.ModulesInfo) > 0 {
templateService := service.ServiceGroupApp.SystemServiceGroup.AutoCodeTemplate
err := plan.ModulesInfo.Pretreatment()
if err != nil {
result.Message += fmt.Sprintf("模块信息预处理失败: %v", err)
// 即使预处理失败也要返回paths信息
return result
// 先批量创建所有模块需要的字典
dictResult := t.createRequiredDictionaries(ctx, plan.ModulesInfo)
result.Message += dictResult
// 遍历所有模块进行创建
for _, moduleInfo := range plan.ModulesInfo {
// 创建模块
err := moduleInfo.Pretreatment()
if err != nil {
result.Message += fmt.Sprintf("模块 %s 信息预处理失败: %v; ", moduleInfo.StructName, err)
continue // 继续处理下一个模块
}
err = templateService.Create(ctx, *moduleInfo)
if err != nil {
result.Message += fmt.Sprintf("创建模块 %s 失败: %v; ", moduleInfo.StructName, err)
continue // 继续处理下一个模块
}
result.Message += fmt.Sprintf("模块 %s 创建成功; ", moduleInfo.StructName)
}
err = templateService.Create(ctx, *plan.ModulesInfo)
if err != nil {
result.Message += fmt.Sprintf("创建模块失败: %v", err)
// 即使创建模块失败也要返回paths信息
return result
}
result.Message += "模块创建成功; "
result.Message += fmt.Sprintf("批量创建完成,共处理 %d 个模块; ", len(plan.ModulesInfo))
}
result.Message += "已构建目录结构信息; "
@@ -1341,43 +1377,64 @@ func (t *AutomationModuleAnalyzer) executeCreation(ctx context.Context, plan *Ex
return result
}
// createRequiredDictionaries 创建所需的字典
func (t *AutomationModuleAnalyzer) createRequiredDictionaries(ctx context.Context, modulesInfo *request.AutoCode) string {
// createRequiredDictionaries 创建所需的字典(批量处理)
func (t *AutomationModuleAnalyzer) createRequiredDictionaries(ctx context.Context, modulesInfoList []*request.AutoCode) string {
var messages []string
dictionaryService := service.ServiceGroupApp.SystemServiceGroup.DictionaryService
createdDictTypes := make(map[string]bool) // 用于避免重复创建相同的字典
// 遍历所有字段,查找使用字典的字段
for _, field := range modulesInfo.Fields {
if field.DictType != "" {
// 检查字典是否存在
exists, err := t.checkDictionaryExists(field.DictType)
if err != nil {
messages = append(messages, fmt.Sprintf("检查字典 %s 时出错: %v; ", field.DictType, err))
continue
}
// 遍历所有模块
for moduleIndex, modulesInfo := range modulesInfoList {
messages = append(messages, fmt.Sprintf("处理模块 %d (%s) 的字典: ", moduleIndex+1, modulesInfo.StructName))
if !exists {
// 字典不存在,创建字典
dictionary := model.SysDictionary{
Name: t.generateDictionaryName(field.DictType, field.FieldDesc),
Type: field.DictType,
Status: &[]bool{true}[0], // 默认启用
Desc: fmt.Sprintf("自动生成的字典,用于字段: %s (%s)", field.FieldName, field.FieldDesc),
// 遍历当前模块的所有字段,查找使用字典的字段
moduleHasDictFields := false
for _, field := range modulesInfo.Fields {
if field.DictType != "" {
moduleHasDictFields = true
// 如果这个字典类型已经在之前的模块中创建过,跳过
if createdDictTypes[field.DictType] {
messages = append(messages, fmt.Sprintf("字典 %s 已在前面的模块中创建,跳过; ", field.DictType))
continue
}
err = dictionaryService.CreateSysDictionary(dictionary)
// 检查字典是否存在
exists, err := t.checkDictionaryExists(field.DictType)
if err != nil {
messages = append(messages, fmt.Sprintf("创建字典 %s 失败: %v; ", field.DictType, err))
} else {
messages = append(messages, fmt.Sprintf("成功创建字典 %s (%s); ", field.DictType, dictionary.Name))
// 创建默认的字典详情项
t.createDefaultDictionaryDetails(ctx, field.DictType, field.FieldDesc)
messages = append(messages, fmt.Sprintf("检查字典 %s 时出错: %v; ", field.DictType, err))
continue
}
if !exists {
// 字典不存在,创建字典
dictionary := model.SysDictionary{
Name: t.generateDictionaryName(field.DictType, field.FieldDesc),
Type: field.DictType,
Status: &[]bool{true}[0], // 默认启用
Desc: fmt.Sprintf("自动生成的字典,用于模块 %s 字段: %s (%s)", modulesInfo.StructName, field.FieldName, field.FieldDesc),
}
err = dictionaryService.CreateSysDictionary(dictionary)
if err != nil {
messages = append(messages, fmt.Sprintf("创建字典 %s 失败: %v; ", field.DictType, err))
} else {
messages = append(messages, fmt.Sprintf("成功创建字典 %s (%s); ", field.DictType, dictionary.Name))
createdDictTypes[field.DictType] = true // 标记为已创建
// 创建默认的字典详情项
t.createDefaultDictionaryDetails(ctx, field.DictType, field.FieldDesc)
}
} else {
messages = append(messages, fmt.Sprintf("字典 %s 已存在,跳过创建; ", field.DictType))
createdDictTypes[field.DictType] = true // 标记为已存在
}
} else {
messages = append(messages, fmt.Sprintf("字典 %s 已存在,跳过创建; ", field.DictType))
}
}
if !moduleHasDictFields {
messages = append(messages, "无需创建字典; ")
}
}
if len(messages) == 0 {
@@ -1441,18 +1498,18 @@ func (t *AutomationModuleAnalyzer) generateSmartDictionaryOptions(dictType, fiel
func (t *AutomationModuleAnalyzer) detectPluginIntent(requirement string) (suggestedType string, isPlugin bool, confidence string) {
// 转换为小写进行匹配
requirementLower := strings.ToLower(requirement)
// 插件相关关键词
pluginKeywords := []string{
"插件", "plugin", "扩展", "extension", "addon", "模块插件",
"功能插件", "业务插件", "第三方插件", "自定义插件",
}
// 包相关关键词(用于排除误判)
packageKeywords := []string{
"包", "package", "模块包", "业务包", "功能包",
}
// 检测插件关键词
pluginMatches := 0
for _, keyword := range pluginKeywords {
@@ -1460,7 +1517,7 @@ func (t *AutomationModuleAnalyzer) detectPluginIntent(requirement string) (sugge
pluginMatches++
}
}
// 检测包关键词
packageMatches := 0
for _, keyword := range packageKeywords {
@@ -1468,7 +1525,7 @@ func (t *AutomationModuleAnalyzer) detectPluginIntent(requirement string) (sugge
packageMatches++
}
}
// 决策逻辑
if pluginMatches > 0 {
if packageMatches == 0 || pluginMatches > packageMatches {
@@ -1477,7 +1534,7 @@ func (t *AutomationModuleAnalyzer) detectPluginIntent(requirement string) (sugge
return "plugin", true, "中"
}
}
// 默认返回package
return "package", false, "低"
}
@@ -1492,7 +1549,7 @@ func (t *AutomationModuleAnalyzer) isPackageFolderEmpty(packageName, template st
// package 类型
basePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "model", packageName)
}
// 检查文件夹是否存在
if _, err := os.Stat(basePath); os.IsNotExist(err) {
// 文件夹不存在,认为是空的
@@ -1500,13 +1557,13 @@ func (t *AutomationModuleAnalyzer) isPackageFolderEmpty(packageName, template st
} else if err != nil {
return false, fmt.Errorf("检查文件夹状态失败: %v", err)
}
// 读取文件夹内容
entries, err := os.ReadDir(basePath)
if err != nil {
return false, fmt.Errorf("读取文件夹内容失败: %v", err)
}
// 检查目录下是否有 .go 文件
hasGoFiles := false
for _, entry := range entries {
@@ -1537,7 +1594,7 @@ func (t *AutomationModuleAnalyzer) isPackageFolderEmpty(packageName, template st
break
}
}
// 如果没有 .go 文件,认为是空包
return !hasGoFiles, nil
}
@@ -1545,7 +1602,7 @@ func (t *AutomationModuleAnalyzer) isPackageFolderEmpty(packageName, template st
// removeEmptyPackageFolder 删除空的包文件夹
func (t *AutomationModuleAnalyzer) removeEmptyPackageFolder(packageName, template string) error {
var errors []string
if template == "plugin" {
// plugin 类型只删除 plugin 目录下的文件夹
basePath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", packageName)
@@ -1560,18 +1617,18 @@ func (t *AutomationModuleAnalyzer) removeEmptyPackageFolder(packageName, templat
filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "service", packageName),
filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "router", packageName),
}
for _, path := range paths {
if err := t.removeDirectoryIfExists(path); err != nil {
errors = append(errors, fmt.Sprintf("删除%s失败: %v", path, err))
}
}
}
if len(errors) > 0 {
return fmt.Errorf("删除过程中出现错误: %s", strings.Join(errors, "; "))
}
return nil
}
@@ -1584,12 +1641,12 @@ func (t *AutomationModuleAnalyzer) removeDirectoryIfExists(dirPath string) error
} else if err != nil {
return fmt.Errorf("检查文件夹状态失败: %v", err)
}
// 删除文件夹及其所有内容
if err := os.RemoveAll(dirPath); err != nil {
return fmt.Errorf("删除文件夹失败: %v", err)
}
global.GVA_LOG.Info(fmt.Sprintf("成功删除目录: %s", dirPath))
return nil
}
@@ -1599,31 +1656,31 @@ func (t *AutomationModuleAnalyzer) cleanupRelatedApiAndMenus(historyIDs []uint)
if len(historyIDs) == 0 {
return nil
}
// 获取要删除的历史记录信息
var histories []model.SysAutoCodeHistory
if err := global.GVA_DB.Where("id IN ?", historyIDs).Find(&histories).Error; err != nil {
return fmt.Errorf("获取历史记录失败: %v", err)
}
var deletedApiCount, deletedMenuCount int
for _, history := range histories {
// 删除相关的API记录使用存储的API IDs
if len(history.ApiIDs) > 0 {
ids := make([]int, 0, len(history.ApiIDs))
for _, id := range history.ApiIDs {
ids = append(ids, int(id))
}
idsReq := common.IdsReq{Ids: ids}
if err := systemService.ApiServiceApp.DeleteApisByIds(idsReq); err != nil {
global.GVA_LOG.Warn(fmt.Sprintf("删除API记录失败 (模块: %s): %v", history.StructName, err))
} else {
deletedApiCount += len(ids)
global.GVA_LOG.Info(fmt.Sprintf("成功删除API记录 (模块: %s, 数量: %d)", history.StructName, len(ids)))
}
if len(history.ApiIDs) > 0 {
ids := make([]int, 0, len(history.ApiIDs))
for _, id := range history.ApiIDs {
ids = append(ids, int(id))
}
idsReq := common.IdsReq{Ids: ids}
if err := systemService.ApiServiceApp.DeleteApisByIds(idsReq); err != nil {
global.GVA_LOG.Warn(fmt.Sprintf("删除API记录失败 (模块: %s): %v", history.StructName, err))
} else {
deletedApiCount += len(ids)
global.GVA_LOG.Info(fmt.Sprintf("成功删除API记录 (模块: %s, 数量: %d)", history.StructName, len(ids)))
}
}
// 删除相关的菜单记录使用存储的菜单ID
if history.MenuID != 0 {
if err := systemService.BaseMenuServiceApp.DeleteBaseMenu(int(history.MenuID)); err != nil {
@@ -1634,10 +1691,10 @@ func (t *AutomationModuleAnalyzer) cleanupRelatedApiAndMenus(historyIDs []uint)
}
}
}
if deletedApiCount > 0 || deletedMenuCount > 0 {
global.GVA_LOG.Info(fmt.Sprintf("清理完成:删除了 %d 个API记录和 %d 个菜单记录", deletedApiCount, deletedMenuCount))
}
return nil
}

View File

@@ -64,7 +64,7 @@ type MenuCreator struct{}
// New 创建菜单创建工具
func (m *MenuCreator) New() mcp.Tool {
return mcp.NewTool("create_menu",
mcp.WithDescription("创建前端菜单记录,用于在生成前端页面时自动创建对应的菜单项,只要前端页面生成都需要调用此mcp。"),
mcp.WithDescription("创建前端菜单记录,用于AI编辑器自动添加前端页面时自动创建对应的菜单项。注意使用gva_auto_generate创建的包和模块会自动创建菜单项无需调用此工具。仅在AI编辑器自动添加前端页面时使用。"),
mcp.WithNumber("parentId",
mcp.Description("父菜单ID0表示根菜单"),
mcp.DefaultNumber(0),

View File

@@ -0,0 +1,139 @@
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

@@ -5,7 +5,7 @@ var (
ApiVerify = Rules{"Path": {NotEmpty()}, "Description": {NotEmpty()}, "ApiGroup": {NotEmpty()}, "Method": {NotEmpty()}}
MenuVerify = Rules{"Path": {NotEmpty()}, "Name": {NotEmpty()}, "Component": {NotEmpty()}, "Sort": {Ge("0")}}
MenuMetaVerify = Rules{"Title": {NotEmpty()}}
LoginVerify = Rules{"CaptchaId": {NotEmpty()}, "Username": {NotEmpty()}, "Password": {NotEmpty()}}
LoginVerify = Rules{"CaptchaId": {NotEmpty()}, "Captcha": {NotEmpty()}, "Username": {NotEmpty()}, "Password": {NotEmpty()}}
RegisterVerify = Rules{"Username": {NotEmpty()}, "NickName": {NotEmpty()}, "Password": {NotEmpty()}, "AuthorityId": {NotEmpty()}}
PageInfoVerify = Rules{"Page": {NotEmpty()}, "PageSize": {NotEmpty()}}
CustomerVerify = Rules{"CustomerName": {NotEmpty()}, "CustomerPhoneData": {NotEmpty()}}