From 607b6c7bb3ed434ce2faeacb0ea40c716aabfa18 Mon Sep 17 00:00:00 2001
From: "Ray.Hao" <1490493387@qq.com>
Date: Sat, 26 Sep 2026 09:13:16 +0800
Subject: [PATCH] =?UTF-8?q?feat(codegen):=20=E4=BB=A3=E7=A0=81=E7=94=9F?=
=?UTF-8?q?=E6=88=90=E6=96=B0=E5=A2=9E=20AI=20=E9=85=8D=E7=BD=AE=E6=8E=A8?=
=?UTF-8?q?=E6=96=AD=E5=B9=B6=E8=BF=81=E7=A7=BB=E6=A8=A1=E6=9D=BF=E8=B5=84?=
=?UTF-8?q?=E6=BA=90?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 模板与外部文件移至 resources/codegen、excel、ip2region
- 新增 AI 配置推断接口、表单与返回对象
- 字典项与菜单 SQL、IP 工具、日志配置调整
- docker-compose 与英文 README 更新
- 版本 4.11.0
---
.gitignore | 2 -
README.en.md | 4 +-
docker-compose.yml | 4 +-
pom.xml | 6 +-
sql/extensions/dynamic-form.sql | 2 +-
.../codegen/controller/CodegenController.java | 13 ++
.../boot/codegen/model/entity/GenTable.java | 2 +-
.../codegen/model/form/AiFillConfigForm.java | 18 +++
.../codegen/model/form/GenConfigForm.java | 18 +--
.../boot/codegen/model/vo/GenConfigAiVO.java | 55 +++++++
.../codegen/property/CodegenProperties.java | 2 +-
.../boot/codegen/service/GenTableService.java | 9 ++
.../service/impl/CodegenServiceImpl.java | 14 +-
.../service/impl/GenTableServiceImpl.java | 145 ++++++++++++++++++
.../boot/framework/web/util/IPUtils.java | 2 +-
.../service/impl/RoleMenuServiceImpl.java | 2 +-
src/main/resources/codegen.yml | 26 ++--
.../codegen/backend/controller.java.vm | 0
.../codegen/backend/converter.java.vm | 0
.../codegen/backend/entity.java.vm | 0
.../codegen/backend/form.java.vm | 0
.../codegen/backend/mapper.java.vm | 0
.../codegen/backend/mapper.xml.vm | 0
.../codegen/backend/query.java.vm | 0
.../codegen/backend/service.java.vm | 0
.../codegen/backend/serviceImpl.java.vm | 0
.../codegen/backend/vo.java.vm | 0
.../codegen/frontend/js/api.js.vm | 0
.../frontend/js/index.crud.js.vue.vm} | 20 +--
.../codegen/frontend/js/index.js.vue.vm | 0
.../codegen/frontend/ts/api-types.ts.vm | 0
.../codegen/frontend/ts/api.ts.vm | 0
.../frontend/ts/index.crud.vue.vm} | 24 +--
.../codegen/frontend/ts/index.vue.vm | 0
.../{templates => }/excel/用户导入模板.xlsx | Bin
.../{data => ip2region}/ip2region.xdb | Bin
src/main/resources/logback-spring.xml | 13 +-
.../mapper/system/DictItemMapper.xml | 3 +
.../resources/mapper/system/DictMapper.xml | 13 +-
.../resources/mapper/system/MenuMapper.xml | 1 -
40 files changed, 313 insertions(+), 85 deletions(-)
create mode 100644 src/main/java/com/youlai/boot/codegen/model/form/AiFillConfigForm.java
create mode 100644 src/main/java/com/youlai/boot/codegen/model/vo/GenConfigAiVO.java
rename src/main/resources/{templates => }/codegen/backend/controller.java.vm (100%)
rename src/main/resources/{templates => }/codegen/backend/converter.java.vm (100%)
rename src/main/resources/{templates => }/codegen/backend/entity.java.vm (100%)
rename src/main/resources/{templates => }/codegen/backend/form.java.vm (100%)
rename src/main/resources/{templates => }/codegen/backend/mapper.java.vm (100%)
rename src/main/resources/{templates => }/codegen/backend/mapper.xml.vm (100%)
rename src/main/resources/{templates => }/codegen/backend/query.java.vm (100%)
rename src/main/resources/{templates => }/codegen/backend/service.java.vm (100%)
rename src/main/resources/{templates => }/codegen/backend/serviceImpl.java.vm (100%)
rename src/main/resources/{templates => }/codegen/backend/vo.java.vm (100%)
rename src/main/resources/{templates => }/codegen/frontend/js/api.js.vm (100%)
rename src/main/resources/{templates/codegen/frontend/js/index.curd.js.vue.vm => codegen/frontend/js/index.crud.js.vue.vm} (97%)
rename src/main/resources/{templates => }/codegen/frontend/js/index.js.vue.vm (100%)
rename src/main/resources/{templates => }/codegen/frontend/ts/api-types.ts.vm (100%)
rename src/main/resources/{templates => }/codegen/frontend/ts/api.ts.vm (100%)
rename src/main/resources/{templates/codegen/frontend/ts/index.curd.vue.vm => codegen/frontend/ts/index.crud.vue.vm} (95%)
rename src/main/resources/{templates => }/codegen/frontend/ts/index.vue.vm (100%)
rename src/main/resources/{templates => }/excel/用户导入模板.xlsx (100%)
rename src/main/resources/{data => ip2region}/ip2region.xdb (100%)
diff --git a/.gitignore b/.gitignore
index 4d1c7dcc..4b294208 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,4 @@
# Created by .ignore support plugin (hsz.mobi)
-### Example sysUserDetails template template
-### Example sysUserDetails template
# IntelliJ project files
.idea
diff --git a/README.en.md b/README.en.md
index bb45f787..8542c13e 100644
--- a/README.en.md
+++ b/README.en.md
@@ -178,10 +178,10 @@ youlai-boot/
| [youlai-gin](https://gitee.com/youlaiorg/youlai-gin) | Go + Gorm | Go | ✅️ |
| [youlai-django](https://gitee.com/youlaiorg/youlai-django) | Django + DRF | Python | ✅️ |
| [youlai-fastapi](https://gitee.com/youlaiorg/youlai-fastapi) | FastAPI + SQLAlchemy | Python | ✅️ |
-| [youlai-laravel](https://gitee.com/youlaiorg/youlai-laravel) | Laravel + Eloquent | PHP | ✅️ |
| [youlai-think](https://gitee.com/youlaiorg/youlai-think) | ThinkPHP + ThinkORM | PHP | ✅️ |
| [youlai-aspnet](https://gitee.com/youlaiorg/youlai-aspnet) | ASP.NET Core + EF Core | C# | ✅️ |
| [youlai-axum](https://gitee.com/youlaiorg/youlai-axum) | Axum + SeaORM | Rust | ✅️ |
+| [youlai-laravel](https://gitee.com/youlaiorg/youlai-laravel) | Laravel + Eloquent | PHP | ❌️ |
> The nine backends share the same **RESTful API specification** and **database schema**, so the frontend can switch seamlessly.
@@ -193,8 +193,8 @@ youlai-boot/
| [youlai-boot-flex](https://gitee.com/youlaiorg/youlai-boot-flex) | youlai-boot | Standalone repo | Built on MyBatis-Flex | ✅️ |
| [youlai-boot (db-pg)](https://gitee.com/youlaiorg/youlai-boot/tree/db-pg) | youlai-boot | Branch | PostgreSQL database branch | ✅️ |
| [youlai-boot (multi-module)](https://gitee.com/youlaiorg/youlai-boot/tree/multi-module) | youlai-boot | Branch | Multi-module project split | ✅️ |
-| [youlai-boot (spring-boot-3)](https://gitee.com/youlaiorg/youlai-boot/tree/spring-boot-3) | youlai-boot | Branch | Spring Boot 3 compatible branch | ✅️ |
| [youlai-nest (multi-tenant)](https://gitee.com/youlaiorg/youlai-nest/tree/multi-tenant) | youlai-nest | Branch | Multi-tenant SaaS with tenant isolation & config | ✅️ |
+| [youlai-boot (spring-boot-3)](https://gitee.com/youlaiorg/youlai-boot/tree/spring-boot-3) | youlai-boot | Branch | Spring Boot 3 compatible branch | ❌️ |
## Optional Extensions
diff --git a/docker-compose.yml b/docker-compose.yml
index 34df24fd..0f53b1b8 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -5,7 +5,6 @@
#
# 说明:
# - name 固定项目名,容器统一管理和卸载,避免与其他项目冲突
-# - MySQL 首次启动时自动执行 ./sql 下初始化脚本(仅首次,数据卷已存在时跳过)
# - 中间件配置全部内联于 command 参数,无需外部配置文件
# - 数据持久化使用命名卷(docker volume 统一管理),不污染项目目录
# - RustFS 兼容 MinIO 磁盘格式与 S3 协议(MinIO 社区版已于 2026 年停止维护)
@@ -25,7 +24,6 @@ services:
- MYSQL_ROOT_PASSWORD=123456
volumes:
- youlai-mysql-data:/var/lib/mysql
- - ./sql:/docker-entrypoint-initdb.d
ports:
- 3306:3306
@@ -58,7 +56,7 @@ services:
- 9001:9001
xxl-job-admin:
- image: xuxueli/xxl-job-admin:2.4.0
+ image: xuxueli/xxl-job-admin:3.4.2
container_name: xxl-job-admin
restart: unless-stopped
environment:
diff --git a/pom.xml b/pom.xml
index 2ff23159..e4952803 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
com.youlai
youlai-boot
- 4.10.0
+ 4.11.0
基于 Java 17 + SpringBoot 4 + Spring Security 构建的权限管理系统。
@@ -22,12 +22,10 @@
5.8.41
-
8.0.0
9.1.0
1.2.24
-
3.5.15
4.5.0
@@ -70,7 +68,6 @@
4.8.1.B
-
2.0.0
@@ -196,7 +193,6 @@
${druid.version}
-
com.baomidou
mybatis-plus-spring-boot4-starter
diff --git a/sql/extensions/dynamic-form.sql b/sql/extensions/dynamic-form.sql
index 75a9d31b..18d0fcde 100644
--- a/sql/extensions/dynamic-form.sql
+++ b/sql/extensions/dynamic-form.sql
@@ -76,7 +76,7 @@ CREATE TABLE `form_snapshot` (
-- ----------------------------
-- 菜单(扩展段顶级 ID=8,基础段 1-7 见 youlai-admin.sql)
--- 目录与视图目录/URL 全链路对齐:views/dynamic-form + /dynamic-form(route_path/component 字段)
+-- 顶级目录 route_path 为 /dynamic-form,子菜单 component 指向 views/dynamic-form 下的视图且 route_path 只填当前段
-- ----------------------------
-- params 携 {"badge":"NEW"}:挂顶级"动态表单"一级可见(二级折叠态角标不可见),宣传期过后清空 params 即消失
INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (8, 0, '0', '动态表单', 'C', '', '/dynamic-form', 'Layout', NULL, NULL, 1, 8, 'el-icon-Document', '/dynamic-form/definition', now(), now(), '{\"badge\":\"NEW\"}');
diff --git a/src/main/java/com/youlai/boot/codegen/controller/CodegenController.java b/src/main/java/com/youlai/boot/codegen/controller/CodegenController.java
index 77a14631..77d4433c 100644
--- a/src/main/java/com/youlai/boot/codegen/controller/CodegenController.java
+++ b/src/main/java/com/youlai/boot/codegen/controller/CodegenController.java
@@ -8,6 +8,7 @@ import com.youlai.boot.codegen.property.CodegenProperties;
import com.youlai.boot.common.enums.ActionTypeEnum;
import com.youlai.boot.common.enums.LogModuleEnum;
import com.youlai.boot.codegen.service.CodegenService;
+import com.youlai.boot.codegen.model.form.AiFillConfigForm;
import com.youlai.boot.codegen.model.form.GenConfigForm;
import com.youlai.boot.codegen.model.query.TableQuery;
import com.youlai.boot.codegen.model.vo.CodegenPreviewVO;
@@ -111,4 +112,16 @@ public class CodegenController {
throw new BusinessException("代码生成压缩包响应写入失败", e);
}
}
+
+ // AI 辅助:未开启 AI 时调用返回业务异常
+
+ @Operation(summary = "AI 填充代码生成配置")
+ @PostMapping("/{tableName}/ai-config")
+ public Result fillConfig(
+ @Parameter(description = "表名", example = "sys_user") @PathVariable String tableName,
+ @RequestBody(required = false) @Valid AiFillConfigForm form
+ ) {
+ String requirement = form == null ? null : form.getRequirement();
+ return Result.success(genTableService.aiFillConfig(tableName, requirement));
+ }
}
diff --git a/src/main/java/com/youlai/boot/codegen/model/entity/GenTable.java b/src/main/java/com/youlai/boot/codegen/model/entity/GenTable.java
index 51f5f289..fe9801eb 100644
--- a/src/main/java/com/youlai/boot/codegen/model/entity/GenTable.java
+++ b/src/main/java/com/youlai/boot/codegen/model/entity/GenTable.java
@@ -53,7 +53,7 @@ public class GenTable extends BaseEntity {
private String author;
/**
- * 页面类型 classic|curd
+ * 页面类型 classic|crud
*/
private String pageType;
diff --git a/src/main/java/com/youlai/boot/codegen/model/form/AiFillConfigForm.java b/src/main/java/com/youlai/boot/codegen/model/form/AiFillConfigForm.java
new file mode 100644
index 00000000..b444604b
--- /dev/null
+++ b/src/main/java/com/youlai/boot/codegen/model/form/AiFillConfigForm.java
@@ -0,0 +1,18 @@
+package com.youlai.boot.codegen.model.form;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * AI 填充生成配置入参
+ *
+ * @author Ray.Hao
+ * @since 0.1.0
+ */
+@Schema(description = "AI 填充生成配置入参")
+@Data
+public class AiFillConfigForm {
+
+ @Schema(description = "补充需求描述,可为空", example = "状态字段用下拉框,创建时间不出现在表单")
+ private String requirement;
+}
diff --git a/src/main/java/com/youlai/boot/codegen/model/form/GenConfigForm.java b/src/main/java/com/youlai/boot/codegen/model/form/GenConfigForm.java
index 60fb6076..db1ab797 100644
--- a/src/main/java/com/youlai/boot/codegen/model/form/GenConfigForm.java
+++ b/src/main/java/com/youlai/boot/codegen/model/form/GenConfigForm.java
@@ -19,36 +19,36 @@ import java.util.List;
@Data
public class GenConfigForm {
- @Schema(description = "主键",example = "1")
+ @Schema(description = "主键", example = "1")
private Long id;
- @Schema(description = "表名",example = "sys_user")
+ @Schema(description = "表名", example = "sys_user")
@NotBlank(message = "表名不能为空")
private String tableName;
- @Schema(description = "业务名",example = "用户")
+ @Schema(description = "业务名", example = "用户")
@NotBlank(message = "业务名不能为空")
private String businessName;
- @Schema(description = "模块名",example = "system")
+ @Schema(description = "模块名", example = "system")
@NotBlank(message = "模块名不能为空")
@Pattern(regexp = "[a-zA-Z][a-zA-Z0-9_]*", message = "模块名只能包含字母、数字和下划线,且以字母开头")
private String moduleName;
- @Schema(description = "包名",example = "com.youlai")
+ @Schema(description = "包名", example = "com.youlai")
@NotBlank(message = "包名不能为空")
@Pattern(regexp = "([a-zA-Z_][a-zA-Z0-9_]*\\.)*[a-zA-Z_][a-zA-Z0-9_]*", message = "包名格式不正确")
private String packageName;
- @Schema(description = "实体名",example = "User")
+ @Schema(description = "实体名", example = "User")
@NotBlank(message = "实体名不能为空")
@Pattern(regexp = "[a-zA-Z][a-zA-Z0-9]*", message = "实体名只能包含字母和数字,且以字母开头")
private String entityName;
- @Schema(description = "作者",example = "youlaitech")
+ @Schema(description = "作者", example = "youlaitech")
private String author;
- @Schema(description = "上级菜单ID",example = "1")
+ @Schema(description = "上级菜单ID", example = "1")
private Long parentMenuId;
@Schema(description = "字段配置列表")
@@ -60,7 +60,7 @@ public class GenConfigForm {
@Schema(description = "前端应用名")
private String frontendAppName;
- @Schema(description = "页面类型 classic|curd", example = "classic")
+ @Schema(description = "页面类型 classic|crud", example = "classic")
private String pageType;
@Schema(description = "要移除的表前缀,如: sys_", example = "sys_")
diff --git a/src/main/java/com/youlai/boot/codegen/model/vo/GenConfigAiVO.java b/src/main/java/com/youlai/boot/codegen/model/vo/GenConfigAiVO.java
new file mode 100644
index 00000000..86b04871
--- /dev/null
+++ b/src/main/java/com/youlai/boot/codegen/model/vo/GenConfigAiVO.java
@@ -0,0 +1,55 @@
+package com.youlai.boot.codegen.model.vo;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * AI 推断的代码生成配置
+ *
+ * @author Ray.Hao
+ * @since 0.1.0
+ */
+@Data
+public class GenConfigAiVO {
+
+ /** 业务名 */
+ private String businessName;
+
+ /** 字段配置列表 */
+ private List fieldConfigs;
+
+ /**
+ * AI 推断的字段配置
+ */
+ @Data
+ public static class FieldAiVO {
+
+ /** 列名,须与入参一致 */
+ private String columnName;
+
+ /** 字段描述 */
+ private String fieldComment;
+
+ /** 表单类型枚举名 */
+ private String formType;
+
+ /** 查询类型枚举名 */
+ private String queryType;
+
+ /** 是否必填 */
+ private Integer isRequired;
+
+ /** 是否在列表显示 */
+ private Integer isShowInList;
+
+ /** 是否在表单显示 */
+ private Integer isShowInForm;
+
+ /** 是否在查询条件显示 */
+ private Integer isShowInQuery;
+
+ /** 字典类型编码 */
+ private String dictType;
+ }
+}
diff --git a/src/main/java/com/youlai/boot/codegen/property/CodegenProperties.java b/src/main/java/com/youlai/boot/codegen/property/CodegenProperties.java
index ca8bd685..70e44d91 100644
--- a/src/main/java/com/youlai/boot/codegen/property/CodegenProperties.java
+++ b/src/main/java/com/youlai/boot/codegen/property/CodegenProperties.java
@@ -58,7 +58,7 @@ public class CodegenProperties {
public static class TemplateConfig {
/**
- * 模板路径 (e.g. /templates/codegen/controller.java.vm)
+ * 模板路径,相对模板根 classpath:codegen (e.g. backend/controller.java.vm)
*/
private String templatePath;
diff --git a/src/main/java/com/youlai/boot/codegen/service/GenTableService.java b/src/main/java/com/youlai/boot/codegen/service/GenTableService.java
index 44dbaa7c..934f705a 100644
--- a/src/main/java/com/youlai/boot/codegen/service/GenTableService.java
+++ b/src/main/java/com/youlai/boot/codegen/service/GenTableService.java
@@ -36,4 +36,13 @@ public interface GenTableService extends IService {
*/
void deleteGenConfig(String tableName);
+ /**
+ * 用 AI 推断代码生成配置,未推断出的字段沿用原有默认值
+ *
+ * @param tableName 表名
+ * @param requirement 补充需求描述,可为空
+ * @return 回填了 AI 推断结果的配置
+ */
+ GenConfigForm aiFillConfig(String tableName, String requirement);
+
}
diff --git a/src/main/java/com/youlai/boot/codegen/service/impl/CodegenServiceImpl.java b/src/main/java/com/youlai/boot/codegen/service/impl/CodegenServiceImpl.java
index dc09e96d..06d4502f 100644
--- a/src/main/java/com/youlai/boot/codegen/service/impl/CodegenServiceImpl.java
+++ b/src/main/java/com/youlai/boot/codegen/service/impl/CodegenServiceImpl.java
@@ -85,10 +85,10 @@ public class CodegenServiceImpl implements CodegenService {
return templateConfig.getTemplatePath();
}
if ("API".equals(templateName)) {
- return "codegen/frontend/js/api.js.vm";
+ return "frontend/js/api.js.vm";
}
if ("VIEW".equals(templateName)) {
- return "codegen/frontend/js/index.js.vue.vm";
+ return "frontend/js/index.js.vue.vm";
}
return templateConfig.getTemplatePath();
}
@@ -352,14 +352,14 @@ public class CodegenServiceImpl implements CodegenService {
bindMap.put("hasBigDecimal", hasBigDecimal);
bindMap.put("hasRequiredField", hasRequiredField);
- TemplateEngine templateEngine = TemplateUtil.createEngine(new TemplateConfig("templates", TemplateConfig.ResourceMode.CLASSPATH));
- // 根据 ui 选择不同的前端页面模板:默认 index.vue.vm;封装版使用 index.curd.vue.vm
+ TemplateEngine templateEngine = TemplateUtil.createEngine(new TemplateConfig("codegen", TemplateConfig.ResourceMode.CLASSPATH));
+ // 根据 ui 选择不同的前端页面模板:默认 index.vue.vm;封装版使用 index.crud.vue.vm
String path = templatePath;
- if ("curd".equalsIgnoreCase(pageType)) {
+ if ("crud".equalsIgnoreCase(pageType)) {
if (path.endsWith("index.js.vue.vm")) {
- path = path.replace("index.js.vue.vm", "index.curd.js.vue.vm");
+ path = path.replace("index.js.vue.vm", "index.crud.js.vue.vm");
} else if (path.endsWith("index.vue.vm")) {
- path = path.replace("index.vue.vm", "index.curd.vue.vm");
+ path = path.replace("index.vue.vm", "index.crud.vue.vm");
}
}
Template template = templateEngine.getTemplate(path);
diff --git a/src/main/java/com/youlai/boot/codegen/service/impl/GenTableServiceImpl.java b/src/main/java/com/youlai/boot/codegen/service/impl/GenTableServiceImpl.java
index 287218a4..aaf0261e 100644
--- a/src/main/java/com/youlai/boot/codegen/service/impl/GenTableServiceImpl.java
+++ b/src/main/java/com/youlai/boot/codegen/service/impl/GenTableServiceImpl.java
@@ -3,6 +3,7 @@ package com.youlai.boot.codegen.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.youlai.boot.YouLaiBootApplication;
@@ -20,8 +21,11 @@ import com.youlai.boot.codegen.model.vo.TableMetaVO;
import com.youlai.boot.codegen.model.entity.GenTable;
import com.youlai.boot.codegen.model.entity.GenTableColumn;
import com.youlai.boot.codegen.model.form.GenConfigForm;
+import com.youlai.boot.codegen.model.vo.GenConfigAiVO;
import com.youlai.boot.codegen.service.GenTableService;
import com.youlai.boot.codegen.service.GenTableColumnService;
+import com.youlai.boot.support.ai.AiChatService;
+import com.youlai.boot.support.ai.PromptLoader;
import com.youlai.boot.system.model.dto.MenuCreateDTO;
import com.youlai.boot.system.service.MenuService;
import lombok.RequiredArgsConstructor;
@@ -29,9 +33,14 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Comparator;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
/**
* 数据库服务实现类
@@ -43,10 +52,15 @@ import java.util.Objects;
@RequiredArgsConstructor
public class GenTableServiceImpl extends ServiceImpl implements GenTableService {
+ /** 系统提示词名称,对应 resources/prompts 下的相对路径 */
+ private static final String SYSTEM_PROMPT_PATH = "codegen/system.md";
+
private final DatabaseMapper databaseMapper;
private final CodegenProperties codegenProperties;
private final GenTableColumnService genTableColumnService;
private final CodegenConverter codegenConverter;
+ private final AiChatService aiChatService;
+ private final PromptLoader promptLoader;
@Value("${spring.profiles.active}")
private String springProfilesActive;
@@ -229,6 +243,137 @@ public class GenTableServiceImpl extends ServiceImpl i
}
}
+ //---------------------------------------------------
+ // AI 辅助:未开启 AI 时调用返回业务异常
+ //---------------------------------------------------
+ /**
+ * 用 AI 推断代码生成配置
+ *
+ * @param tableName 表名 eg: sys_user
+ * @param requirement 补充需求描述,可为空
+ * @return 回填了 AI 推断结果的配置
+ */
+ @Override
+ public GenConfigForm aiFillConfig(String tableName, String requirement) {
+ GenConfigForm config = getGenTableFormData(tableName);
+ String systemPrompt = promptLoader.render(SYSTEM_PROMPT_PATH, buildAiPromptVariables());
+ GenConfigAiVO aiConfig = aiChatService.chatEntity(systemPrompt, buildAiUserPrompt(config, requirement), GenConfigAiVO.class);
+
+ if (StrUtil.isNotBlank(aiConfig.getBusinessName())) {
+ config.setBusinessName(aiConfig.getBusinessName().trim());
+ }
+ applyAiFieldConfigs(config, aiConfig.getFieldConfigs());
+ return config;
+ }
+
+ /**
+ * 构造系统提示词的变量值,表单类型与查询类型清单取自枚举,新增枚举值无需改动提示词
+ *
+ * @return 变量值
+ */
+ private Map buildAiPromptVariables() {
+ String formTypes = Arrays.stream(FormTypeEnum.values())
+ .map(type -> type.name() + "(" + type.getLabel() + ")")
+ .collect(Collectors.joining("|"));
+ String queryTypes = Arrays.stream(QueryTypeEnum.values())
+ .map(Enum::name)
+ .collect(Collectors.joining("|"));
+ return Map.of("formTypes", formTypes, "queryTypes", queryTypes);
+ }
+
+ /**
+ * 按列名匹配回填字段配置
+ *
+ * @param config 原配置
+ * @param aiFields AI 返回的字段列表
+ */
+ private void applyAiFieldConfigs(GenConfigForm config, List aiFields) {
+ if (CollectionUtil.isEmpty(aiFields) || CollectionUtil.isEmpty(config.getFieldConfigs())) {
+ return;
+ }
+ Map aiFieldMap = aiFields.stream()
+ .filter(field -> StrUtil.isNotBlank(field.getColumnName()))
+ .collect(Collectors.toMap(GenConfigAiVO.FieldAiVO::getColumnName, Function.identity(),
+ (existing, replacement) -> existing, LinkedHashMap::new));
+
+ config.getFieldConfigs().forEach(field -> {
+ GenConfigAiVO.FieldAiVO aiField = aiFieldMap.get(field.getColumnName());
+ if (aiField == null) {
+ return;
+ }
+ if (StrUtil.isNotBlank(aiField.getFieldComment())) {
+ field.setFieldComment(aiField.getFieldComment().trim());
+ }
+ if (StrUtil.isNotBlank(aiField.getDictType())) {
+ field.setDictType(aiField.getDictType().trim());
+ }
+ field.setFormType(parseAiEnum(aiField.getFormType(), FormTypeEnum::valueOf, field.getFormType()));
+ field.setQueryType(parseAiEnum(aiField.getQueryType(), QueryTypeEnum::valueOf, field.getQueryType()));
+ field.setIsRequired(resolveAiValue(aiField.getIsRequired(), field.getIsRequired()));
+ field.setIsShowInList(resolveAiValue(aiField.getIsShowInList(), field.getIsShowInList()));
+ field.setIsShowInForm(resolveAiValue(aiField.getIsShowInForm(), field.getIsShowInForm()));
+ field.setIsShowInQuery(resolveAiValue(aiField.getIsShowInQuery(), field.getIsShowInQuery()));
+ });
+ }
+
+ /**
+ * 解析枚举名,非法或未给出时沿用原值
+ *
+ * @param name 枚举名
+ * @param parser 枚举解析器
+ * @param fallback 原值
+ * @return 枚举或原值
+ */
+ private T parseAiEnum(String name, Function parser, T fallback) {
+ if (StrUtil.isBlank(name)) {
+ return fallback;
+ }
+ try {
+ return parser.apply(name.trim());
+ } catch (IllegalArgumentException e) {
+ return fallback;
+ }
+ }
+
+ /**
+ * AI 值缺失时沿用原值
+ *
+ * @param aiValue AI 值
+ * @param fallback 原值
+ * @return 生效值
+ */
+ private T resolveAiValue(T aiValue, T fallback) {
+ return aiValue != null ? aiValue : fallback;
+ }
+
+ /**
+ * 构造用户提示词
+ *
+ * @param config 当前配置
+ * @param requirement 补充需求描述
+ * @return 用户提示词
+ */
+ private String buildAiUserPrompt(GenConfigForm config, String requirement) {
+ List