Skip to content

infra 域开发 ​

适用端:后端 | 类型:参考

上游模块,但本项目一半以上的代码在这里

enlev-module-infra 来自上游 yudao,原本是「平台自己的运维面」:文件、定时任务、代码生成、 配置、API 日志、Redis 监控、Swagger。

但本项目的低代码引擎整套挂在 infra 上:表单设计(formdesign)、数据模型(datamodel)、 数据接口(datainterface)、取号(billtemplate)、门户(portal)。实测规模:

指标实测值
src/main/java 文件数467
src/main/java 行数61,109
其中 @author 走进新时代(本项目)199 文件 / 42.6%
其中 formdesign 相关(含 api / dal / framework / service / controller)134 文件 / 31,350 行

也就是说:formdesign 一个子域占了 infra 全部代码行数的一半以上,而上游原本的那些子域 (file / job / codegen / config / logger / redis)加起来还没它多。

读这一页的正确姿势:别把它当上游模块读。低代码那五个子域有各自的专页 (01 低代码平台开发),本页只讲 infra 作为一个模块的公共约定, 以及四个上游子域里被本项目改过或值得警惕的地方。

定位:infra 的模块边界、代码生成器为什么产出扁平结构、两张日志表的脱敏差异、文件存储的真实防线、 定时任务在本地为什么永不运行。

前置阅读:后端架构与模块依赖、 数据库约定与表总览、system 域开发(依赖方向的另一端)。

它解决什么问题 ​

infra 不依赖 system,但要用 system 的能力

enlev-module-infra/pom.xml 里没有 enlev-module-system;反过来 enlev-module-system/pom.xml:23 依赖 infra。

infra 需要 system 的行权限与字段权限(表单运行时要按人裁行、裁列),走的是接口反转: 接口 PermissionCommonApi 定义在 enlev-framework/enlev-common,system 实现, infra 侧用 ObjectProvider<PermissionCommonApi> 注入。 详见 system 域开发 · 跨模块通道。

实际影响:在 infra 里 @Resource private PermissionApi permissionApi; 会启动失败。 必须注入 common 里的那个接口,而且要 tolerantly(ObjectProvider), 因为 enlev-spring-boot-starter-biz-data-permission 也是这么取的 (EnlevDataPermissionAutoConfiguration:53-61 的注释专门解释了为什么用 ObjectProvider)。

代码在哪 ​

13 个子域(controller/admin/ 下的一级目录,逐个 ls 数出来的):

子域干什么归属专页
formdesign表单设计 + 运行时 + 扩展点 + 脱敏 + 导入导出自研表单设计后端 / 表单运行时 / 自研控件
datamodel物理表登记与 DDL 变更治理自研数据模型
datainterface只读 SQL 数据源(下拉/联动取数)自研数据接口
billtemplate单据编号规则与取号自研取号管理
portal门户配置与版本自研门户
codegen代码生成器(Velocity 模板)上游,引擎被改过本页「代码生成器」
file文件存储(本地 / FTP / S3 / DB)上游本页「文件存储」
job定时任务(Quartz)+ 执行日志上游本页「定时任务」
loggerAPI 访问日志 / API 错误日志上游,有一处脱敏缺口本页「两张日志表」
config参数配置上游—
db数据源配置(多数据源)上游—
redisRedis 监控上游—
demo代码生成器的示例表(enlev_demo01_contact 等 5 张)上游—

顶层包结构与其它模块一致(api / controller / convert / dal / enums / framework / job / mq / service / websocket), 其中 framework/ 有 139 个文件——绝大部分是 formdesign 的引擎代码 (framework/formdesign/core/ 下的 imports / mask / permission / formula / logic 等)。

核心机制 ​

代码生成器:为什么产出的是扁平模块 ​

这是全仓最容易被误解的一处,AGENTS.md 里那句「模板路径已按扁平模块写好」 读起来像是模板改过了,其实不是。实测:

事实实测值
src/main/resources/codegen/ 下的 .vm 模板总数106
其中提到 enlev 的模板只有 3 个:java/dal/mapper.xml.vm、java/enums/errorcode.vm、sql/h2.vm
扁平化逻辑在哪不在模板里,在 service/codegen/inner/CodegenEngine.java

判据在 CodegenEngine:336-339:

java
this.cloudEnable = ClassUtils.isPresent("cn.enlev.ainb.module.infra.framework.rpc.config.RpcConfiguration",
        ClassUtils.getDefaultClassLoader());

——用「classpath 上有没有那个 RPC 配置类」来判断是不是 Cloud 项目。本仓没有 ⟹ cloudEnable = false。

然后在 getTemplates(Integer frontType)(:644)里,把输出路径的两段字符串替换掉(:649-654):

java
// 如果是 Boot 项目,则不使用 api/server 模块
if (Boolean.FALSE.equals(cloudEnable)) {
    SERVER_TEMPLATES.forEach((templatePath, filePath) -> {
        filePath = StrUtil.replace(filePath, "/enlev-module-${table.moduleName}-api", "");
        filePath = StrUtil.replace(filePath, "/enlev-module-${table.moduleName}-server", "");
        templates.put(templatePath, filePath);
    });
}

所以 AGENTS.md 那句话应该读作:「引擎按扁平写好了,模板文案没跟上」

SERVER_TEMPLATES 这张表里存的目标路径仍然写着 -api / -server,只是在运行时被替换掉。 模板文件里的说明文字没人改,于是 sql/h2.vm:1 逐字是:

-- 将该建表 SQL 语句,添加到 enlev-module-${table.moduleName}-biz 模块的 test/resources/sql/create_tables.sql 文件里

-biz 这个模块在本仓根本不存在(上游 ruoyi-vue-pro 的 cloud 版才有)。 生成出来的 SQL 文件顶部就会带着这句指向空气的注释。

不影响功能,但会把新人带去找一个不存在的目录。 改的话就是改这一行文案, 风险为零 —— 已列入待授权修复清单。

两张日志表:一张脱敏,一张不脱敏 ​

infra 管两张 API 日志表,它们的写入路径完全不同,脱敏口径也完全不同:

infra_api_access_loginfra_api_error_log
谁写ApiAccessLogFilter(enlev-spring-boot-starter-web)GlobalExceptionHandler#buildExceptionLog(同 starter)
什么时候写每个请求(成功也写)只有抛异常时
有没有脱敏✅ 有🔴 没有
长度上限ApiAccessLogDO:32 REQUEST_PARAMS_MAX_LENGTH = 8000ApiErrorLogDO:31 REQUEST_PARAMS_MAX_LENGTH = 8000

访问日志侧的脱敏是硬编码的四个键(ApiAccessLogFilter:52):

java
private static final String[] SANITIZE_KEYS = new String[]{"password", "token", "accessToken", "refreshToken"};

在 sanitizeMap(:185-194)里无条件执行:

java
private static String sanitizeMap(Map<String, ?> map, String[] sanitizeKeys) {
    if (CollUtil.isEmpty(map)) { return null; }
    if (sanitizeKeys != null) { MapUtil.removeAny(map, sanitizeKeys); }
    MapUtil.removeAny(map, SANITIZE_KEYS);
    return JsonUtils.toJsonString(map);
}

错误日志侧,GlobalExceptionHandler#buildExceptionLog(:357-385)逐字是:

java
errorLog.setRequestUrl(request.getRequestURI());
Map<String, Object> requestParams = MapUtil.<String, Object>builder()
        .put("query", ServletUtils.getParamMap(request))
        .put("body", ServletUtils.getBody(request)).build();
errorLog.setRequestParams(JsonUtils.toJsonString(requestParams));

没有 SANITIZE_KEYS,没有 sanitizeMap,只有下游那个 8000 字符截断。

🔴 一次失败的登录会把明文密码写进 infra_api_error_log

链条是这样的:

  1. POST /admin-api/system/auth/login,body 里的字段名是 password (AuthLoginReqVO:34 private String password)。
  2. 密码错 → 抛 ServiceException → 落到 GlobalExceptionHandler。
  3. buildExceptionLog 把原始 body 塞进 requestParams,一字不改。
  4. 写进 infra_api_error_log.request_params。

于是这张表里会积累一批「用户名 + 明文密码」的组合。而这张表是有查询界面的 (菜单「API 错误日志」,权限 infra:api-error-log:query)—— 也就是说, 持有那个权限的人可以直接读到别人尝试过的密码。考虑到密码复用习惯,这不是理论风险。

失效方向:fail-open,且完全静默(写入成功、没有任何告警)。

这一条已列入待授权修复清单,手册只记录事实,不擅自改上游 starter。 修法有两种,取舍不同: ① 让 buildExceptionLog 复用 ApiAccessLogFilter.SANITIZE_KEYS(要把它从 filter 里提出来共享); ② 在 GlobalExceptionHandler 里独立维护一份键名清单。 ①更不容易漂移,但会把 web starter 里的两个类耦合起来。

文件存储:真实的防线只有路径校验 ​

service/file/FileServiceImpl.java 的三个开关,全是包级可见的 static boolean(:49 / :51 / :59):

java
static boolean PATH_PREFIX_DATE_ENABLE = true;      // 按 yyyyMMdd 分目录
static boolean PATH_SUFFIX_TIMESTAMP_ENABLE = false; // 时间戳 + 5 位随机数后缀
static boolean PATH_SUFFIX_AS_DIRECTORY = true;      // 后缀作为上级目录

拼装逻辑在 :110-140:先 FilePathUtils.validateFileName/validatePath/validateDirectory, 再按开关拼 suffix、拼 prefix。

🔴 PATH_SUFFIX_TIMESTAMP_ENABLE = false ⟹ 同日同名文件静默覆盖

那个字段的 javadoc 逐字写着:

上传文件的后缀,是否启用

算法:当前时间戳(毫秒)+ 5 位随机数;目的是保证文件的唯一性,避免覆盖 定制:可按需调整成 UUID、或者其他方式

而它的值是 false。加上 PATH_PREFIX_DATE_ENABLE = true,最终路径是 yyyyMMdd/原文件名.ext ⟹ 同一天上传两次同名文件,第二次直接覆盖第一次,不报错、不告警、infra_file 里会多一条指向同一路径的记录。

失效方向:fail-open + 静默,而且旧文件不可恢复。

没有扩展名白名单,也没有 MIME 白名单

上传路径上做的校验,逐个读下来只有三件事:

  1. FilePathUtils.validateFileName(:28-36)—— 挡斜杠与路径穿越:
    java
    if (!isPathValid(name) || StrUtil.contains(name, StrUtil.SLASH) || !StrUtil.equals(name, FileUtil.getName(name))) {
        throw exception(FILE_PATH_INVALID);
    }
  2. FilePathUtils.validatePath / validateDirectory —— 同类校验。
  3. FileTypeUtils.getMineType(...) —— 只是探测类型填进 type 字段,不做任何判断。

FileTypeUtils 用的是 Apache Tika(TIKA.detect(...)), getExtension(mineType) 反过来用 MIME 推后缀拼到文件名上(FileServiceImpl:87-89)。 所以 Tika 在这里的角色是命名,不是准入。

⟹ 上传 .html / .svg / .js 都不会被拒。如果文件存储配的是本地或可公开访问的 S3, 这就是一个存储型 XSS 的落点。防线得靠部署侧(Content-Type 强制、X-Content-Type-Options: nosniff、 独立域名)而不是靠这段代码。

顺带:FileTypeUtils 在仓库里有两份

  • enlev-module-infra/.../framework/file/core/utils/FileTypeUtils.java(完整版,含 writeAttachment)
  • enlev-module-ai/.../util/FileTypeUtils.java(精简版,只有 getMineType(String) 与 isImage)

包名不同、内容重叠。改其中一份不会影响另一份 —— 这是上游的重复,不是本项目的。 知道它存在就行,别指望「改一处两处都生效」。

那三个 static boolean 被单测直接翻

FileServiceImplTest 里有 9 处 FileServiceImpl.PATH_SUFFIX_TIMESTAMP_ENABLE = true/false 直接赋值 (:46, :280, :298, :316, :333, :349, :366, :382 等)。

这是把「配置」做成了「可变全局状态」,代价是:测试之间会互相污染, 而且它不是 final、没有同步 —— 并行跑测试时行为不确定。 目前能工作是因为 surefire 默认串行。改这三个开关之前先想清楚这一点。

定时任务:本地永不运行,而且是静默的 ​

enlev-server/src/main/resources/application-local.yaml:6-11:

yaml
spring:
  autoconfigure:
    # noinspection SpringBootApplicationYaml
    exclude:
      - org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration # 默认 local 环境,不开启 Quartz 的自动配置
      - org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStoreAutoConfiguration # 禁用 AI 模块的 Qdrant,手动创建
      - org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreAutoConfiguration # 禁用 AI 模块的 Milvus,手动创建

而 application-prod.yaml:9 那行是被注释掉的:

yaml
#      - org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration # 默认 local 环境,不开启 Quartz 的自动配置

⟹ 本地 profile 下 Quartz 完全不启动,所有 infra_job 一条都不会跑;prod 下会跑。

失效方向:fail-silent,而且本地与生产行为不一致

你在本地新建一个 job、点了「执行一次」也许能过(那是手动触发路径), 但cron 到点自动执行这件事在本地永远不会发生,而且没有任何日志说「我因为配置被跳过了」。

于是「本地测好了、上线才发现 job 逻辑有错」是这条配置的必然产物。 要真跑,临时把 application-local.yaml:9 那行注释掉,别忘了改回来。

顺带解释了另一件事:本项目里 job 类的 bug 特别容易漏到线上—— enlev-module-agent 的用量清理 job、enlev-module-bpm 的参与人归档 job 都是这个形状。

系统列白名单:一个只有两项的 Set ​

表单运行时的业务数据写在用户绑定的物理表上,而流程状态要写进同一张表的两个固定列。 这个「绕过字段映射直接写列」的口子被一个白名单卡住, FormDesignRuntimeServiceImpl:160-167 逐字:

java
/**
 * 允许通过 {@link #updateSystemColumns} 写入的系统列白名单
 *
 * 只放流程对接必需的两列。这个白名单是该方法的安全边界 —— 不要为了一时方便随意扩充,
 * 否则它会退化成绕过表绑定配置的通用写入口子。
 */
private static final Set<String> SYSTEM_WRITABLE_COLUMNS =
        Set.of("process_instance_id", "process_status");

调用方在 bpm:BpmOnlineFormDataHandler#updateStatus / #updateProcessInstanceId / #updateSystemColumns (见 bpm 域开发 · 在线表单不冻结快照)。

为什么列名固定、不接受映射

BpmOnlineFormDataHandler 的两个常量注释逐字:

流程实例编号列名。固定列名,不接受映射到别名

流程状态列名。仅主表单要求。 用 process_status 而非 status —— 与业务表自己的 status 业务字段解耦,避免同表撞名

⟹ 在线表单绑定的物理表必须有这两列(且列名一字不差),否则流程状态回写会静默失败。 建表时的这条约定在表单运行时与扩展点里有完整清单。

数据模型 ​

29 张表,前缀分两组(从 @TableName 逐个提出来数的):

前缀张数内容
infra_*24api_access_log、api_error_log、config、file、file_content、job、job_log,加上自研的 form_design、form_design_version、form_design_menu、form_design_data_log、data_model、data_model_log、data_interface_log、data_interface_version、bill_template、bill_template_log、portal、portal_version
enlev_demo*5enlev_demo01_contact、enlev_demo02_category、enlev_demo03_course、enlev_demo03_grade、enlev_demo03_student —— 代码生成器的示例表,别删,demo 子域的页面直接读它们

错误码 255 条(全仓最多),全在 enums/ErrorCodeConstants.java。

自研子域普遍是「主表 + _log / _version 影子表」的形状

form_design / form_design_version、data_model / data_model_log、 bill_template / bill_template_log、portal / portal_version、 data_interface_log / data_interface_version —— 五组全是这个套路。

原因在数据模型那一页讲过:DDL 与表结构变更不可回滚, 所以每一次变更都必须留下一条可追溯的记录。新增自研子域时沿用这个形状, 不要指望「以后再加日志」。

菜单侧的实测口径(直接查 system_menu 表): 465 条 infra:* 权限按钮,其中 462 条开启、3 条关闭。关闭的三条是:

id名称permission
18表单构建infra:build:list
19代码生成infra:codegen:query
20API 接口infra:swagger:list

🔴 「代码生成」菜单是关的,但代码生成器能用

infra:codegen:query 这条权限按钮 status = 1(关闭)⟹ 界面上看不到「代码生成」菜单。 但 CodegenController 的端点照样注册在 Spring 容器里,直接调 /admin-api/infra/codegen/** 依然通。

菜单禁用是 fail-open:它只影响导航树的渲染,不影响路由注册、不影响权限校验 (校验的是权限串,而权限串是否授予由角色-菜单关联决定,不由 status 决定)。 这个口径在 ai 域更极端,见 ai 域开发 · 数据模型(那里有一节讲「13 个 AI 菜单全部禁用、34 条 ai:* 按钮全开」)。

接口清单 ​

维度实测值
Controller23(controller/admin/ 22 + controller/app/ 1)
端点218
路径前缀/admin-api/infra/**、/app-api/infra/**

端点数别用不加锚定的 grep

grep -o '@(Get|Post|Put|Delete)Mapping' 会命中 javadoc 里的提及,数出 222; 加上行首锚定 ^\s*@ 之后是 218,与 tools/gen-api.mjs 的产物一致。 口径详见 system 域开发 · 接口清单。

资源实例级权限串在 infra 里有 4 处字面量

infra:form-design-run:%s:%s 这个格式串({表单编码}:{动作})在生产代码里出现在:

文件行用途
FormDesignRuntimeController:364、:405、:406运行时端点的 @PreAuthorize
FormDesignServiceImpl:648登记资源行时写入的权限串
FormDataPermissionChecker(在 agent 模块):52agent 工具执行前的实例级校验

外加 system 模块 DatasetResourceNaming:33 存着前缀常量。

这五处必须同步改。 漂移的失效方向是 fail-CLOSED: 格式对不上 ⟹ 查的是一个不存在的权限串 ⟹ 校验恒为 false ⟹ 拒绝。 好消息是不会越权;坏消息是功能静默失效,界面上表现为「明明配了权限却说没权限」,极难查。 完整分析见安全与提示注入防护 · 权限网关。

怎么扩展 ​

用代码生成器起一个新功能 ​

  1. 先在数据模型里把表登记好(或直接用客户端建表,再让生成器「导入」)。
  2. 「代码生成」菜单当前是关的(见上面那条 warning),要么打开菜单,要么直接调端点。
  3. 生成配置里注意 frontType:它决定前端产出 Vue3 还是别的,getTemplates(frontType) 按它取模板行。
  4. 产出的 zip 里,Java 部分是已经按扁平结构落好路径的(cloudEnable=false 的效果), 直接解压到 enlev-module-xxx/src/main/java/... 即可,不需要再手动去掉 -api / -server 段。
  5. SQL 文件顶部那句「添加到 -biz 模块」是模板文案遗留,照着做会找不到目录,忽略它。
  6. 完整流程见新增一个菜单功能端到端。

给表单运行时加一个读取出口 ​

🔴 脱敏必须三个出口一起加

FormDesignRuntimeServiceImpl 里的 FormDataMasker 注释逐字:

数据脱敏。三个读取出口(get / page / export)统一在返回前处理 —— 漏掉任何一个都等于开了一扇后门,而导出恰恰是泄露量最大的那个

同类还有一条,FormSubmitProcessor 的注释:

提交前后置处理:联动规则复校验 + 服务端扩展点。 收在一个组件里而不是在三个入口各写一遍 —— 漏掉一个入口就等于那条路径 没有服务端约束,而这种漏洞从功能上完全看不出来

⟹ 加第四个读取出口(比如给 agent 开一个检索接口)时, 必须在返回前调 dataMasker,否则那条路径上脱敏形同不存在。 ImportDataResolver:238 的注释也点了同一件事:「导出是带脱敏的(138****8000)—— 那是对的,导出是泄露量最大的出口」。

注意脱敏带来的回写陷阱:详情接口下发的是脱敏值,前端把整条记录回传时, 不做处理就会把 138****8000 真的写进库。FormDesignRuntimeServiceImpl:542-543 有专门的处理与注释。

坑与注意 ​

静默 / fail-open

  1. 🔴 GlobalExceptionHandler#buildExceptionLog 不脱敏 ⟹ 失败的登录把明文密码写进 infra_api_error_log, 且有查询界面。只有 8000 字符截断,没有键名过滤。
  2. 🔴 PATH_SUFFIX_TIMESTAMP_ENABLE = false ⟹ 同日同名文件静默覆盖,旧文件不可恢复。
  3. 🔴 上传没有扩展名 / MIME 白名单,Tika 只用来探测类型填字段与推后缀,不做准入判断。
  4. 🔴 本地 profile 排除了 QuartzAutoConfiguration ⟹ 所有 cron job 在本地永不执行,无日志、无告警; prod 里那行是注释掉的,会执行。本地与生产行为不一致。
  5. 菜单禁用是 fail-open:infra:codegen:query 等 3 条权限按钮关闭,但对应端点仍注册、仍可调。
  6. sql/h2.vm:1 的模板文案指向不存在的 -biz 模块,生成产物顶部会带这句错注释。
  7. 代码注释里点名的 10 个 sql/mysql/xpy-*.sql 增量脚本在仓库里不存在 (如 BillTemplateDO:20 引用的 xpy-2026-09-20-bill-template-tenant.sql)。 sql/mysql/ 下实际只有 6 个文件。
  8. FileTypeUtils 有两份(infra 与 ai 各一),改一份不影响另一份。
  9. FileServiceImpl 的三个 static boolean 被单测直接赋值 9 处 ⟹ 测试间会互相污染, 目前只靠 surefire 串行才没出问题。
  10. 脱敏漏一个读取出口 = 那条路径完全裸奔,而且从功能上看不出来。

fail-closed

  1. FilePathUtils.validateFileName 挡斜杠与路径穿越 → 抛 FILE_PATH_INVALID。
  2. SYSTEM_WRITABLE_COLUMNS 只有两项,白名单外的列写不进去。 源码注释明确警告「不要为了一时方便随意扩充,否则它会退化成绕过表绑定配置的通用写入口子」。
  3. datamodel 的五道闸(版本 / 存在 / 漂移 / 差异 / 有损)任一不过即拒,DDL 不进事务。 详见数据模型。
  4. infra:form-design-run:%s:%s 格式漂移 → 查不到权限串 → 拒绝(功能静默失效,但不越权)。

→ 本节是贴着上下文的提醒;按症状查的全域清单见坑位索引。

测试 ​

维度实测值
测试文件123(全仓最多)
@Test 方法1344(全仓最多)
@Disabled12
src/test/resources/application-unit-test.yaml✅ 存在

infra 是全仓测试网最密的模块:文件数与用例数都排第一,且 @Disabled 只有 12 个。 和它对照的是 ai 域 —— 43 文件 / 187 @Test 里 86 个 @Disabled,见 ai 域开发 · 测试。

实际影响:改 infra 的代码,mvn test -pl enlev-module-infra 大概率能撞出问题; 改 ai 的代码,跑完一片绿灯也说明不了什么。

未证实 ​

读源码读到、但没跑起来验证的

  • 「一次失败的登录会把明文密码写进 infra_api_error_log」—— 我是把 AuthLoginReqVO:34 + GlobalExceptionHandler:357-385 + ApiErrorLogDO:31 三处读通得出的, 没有真的登录失败一次然后去查那张表。中间可能有别的 filter 提前处理过 body (比如 ApiRequestFilter 包装 request 时做过什么),我没有逐个读那条链。
  • 「同日同名文件静默覆盖」—— 由 PATH_SUFFIX_TIMESTAMP_ENABLE = false + PATH_PREFIX_DATE_ENABLE = true
    • 拼装逻辑推出,没有真的上传两次同名文件比对结果。底层存储器(本地 / S3 / DB) 各自的覆盖行为可能不同,我只读了拼装路径那一段。
  • 「本地 job 永不运行」—— 由 application-local.yaml:9 的 exclude 推出, 没有真的在本地建一个 job 等它到点。手动「执行一次」是另一条路径,我没验它是否也受 exclude 影响。
  • 「代码生成器产出的 zip 路径已经是扁平的」—— 由 getTemplates():649-654 的替换逻辑推出, 没有真的生成一次并解压看。
  • 465 条 infra:* 权限按钮的开关状态查的是本地库,不是生产库。

推理得出、没有取证的

  • 「上传 .html / .svg 是存储型 XSS 的落点」—— 我确认了没有白名单, 但没有验证部署侧是否已有 Content-Type 强制或 nosniff。真实风险取决于部署,不取决于这段代码。
  • 「改 sql/h2.vm:1 的文案风险为零」—— 按「它只是一行 Velocity 注释」推的, 没有跑过生成流程确认它不参与任何逻辑。
  • 「自研子域都是主表 + 影子表的形状」—— 从 5 组表名归纳,源码里没有写下这条约定。 datainterface 只有 _log 与 _version 没有主表,严格说已经是个例外。
  • 「新增自研表沿用 sys_ 前缀」(在 system 域那页)与本页「沿用主表 + 影子表」 都是归纳,不是明文规定。

相关页面 ​