37cc8ee0d5
- gxmu_module_registry 增加 has_college(4 张在跑表置 1,含已有库补列与补数据语句) - 种子补 gxmu_tygl_form / gxmu_tygl(enabled=0,团员管理,sort 11/12), 同步修正末尾「执行后校验」:12 行未删除、5 行启用、has_college 4 行 - GET /api/gxmu/module-registry/list 支持 all=true(默认只返回启用), 改为返回 ModuleRegistryDTO,不再外泄 tenantId / userId / 时间戳 - ModuleRegistryService:删掉无生产调用的 titleOf(String)/allModules, 标题回退收敛成唯一的静态 titleOf(Map, module);getByModule 改由 hasCollege 使用 - listAll 只保留 SQL 排序(sort_number 升序,NULL 排最前,与 MySQL ASC 口径一致), 删掉内存二次排序 - ModuleRegistryIntegrityChecker 增加反向告警:review_flow_config 里有、注册表里没有的 module 打 warn,不阻断启动 refs #58
130 lines
5.5 KiB
Java
130 lines
5.5 KiB
Java
package com.gxwebsoft.gxmu.module;
|
|
|
|
import com.gxwebsoft.gxmu.module.entity.ModuleRegistry;
|
|
import com.gxwebsoft.gxmu.module.mapper.ModuleRegistryMapper;
|
|
import com.gxwebsoft.gxmu.module.service.ModuleRegistryService;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
import org.springframework.boot.ApplicationArguments;
|
|
import org.springframework.boot.ApplicationRunner;
|
|
import org.springframework.stereotype.Component;
|
|
|
|
import javax.annotation.Resource;
|
|
import java.util.ArrayList;
|
|
import java.util.HashSet;
|
|
import java.util.LinkedHashSet;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
|
|
/**
|
|
* 模块注册表启动期一致性校验(spec #58 用户故事 21)。
|
|
*
|
|
* <p>两件事不满足就在启动期直接失败,而不是等运行到详情页才发现空白:
|
|
* <ol>
|
|
* <li>注册表 table_name 与 module 一致(module 同时是业务表名);</li>
|
|
* <li>注册表引用的业务表在库里真实存在。</li>
|
|
* </ol>
|
|
*
|
|
* <p>反方向只告警不阻断:{@code gxmu_review_flow_config} 里有、注册表里没有的 module
|
|
* 打 warn(这种模块的标题会回退成裸 module 串、也不会出现在模块下拉里,但存量数据仍要能查)。
|
|
* 不阻断启动是刻意的——存量脏数据不该挡住上线,但必须留下可检索的线索。
|
|
*
|
|
* <p>允许 gxmu.module-registry.strict=false 关闭校验,便于无库本地启动;
|
|
* 生产环境必须保持默认的 true。
|
|
*
|
|
* @author Codex
|
|
* @since 2026-11-10
|
|
*/
|
|
@Component
|
|
public class ModuleRegistryIntegrityChecker implements ApplicationRunner {
|
|
|
|
private static final Logger log = LoggerFactory.getLogger(ModuleRegistryIntegrityChecker.class);
|
|
|
|
@Value("${gxmu.module-registry.strict:true}")
|
|
private boolean strict;
|
|
|
|
@Resource
|
|
private ModuleRegistryService moduleRegistryService;
|
|
|
|
@Resource
|
|
private ModuleRegistryMapper moduleRegistryMapper;
|
|
|
|
@Override
|
|
public void run(ApplicationArguments args) {
|
|
check();
|
|
}
|
|
|
|
/**
|
|
* 反向校验:审批链配置里有、注册表里没有的 module 打 warn,不阻断启动。
|
|
*
|
|
* <p>这类模块的标题会回退成裸 module 串、也不会出现在模块下拉里,
|
|
* 但存量申报记录仍要能查——所以只提示补注册表行,不抛异常。
|
|
*
|
|
* @param modules 注册表里的全部模块
|
|
*/
|
|
private void warnModulesMissingFromRegistry(List<ModuleRegistry> modules) {
|
|
Set<String> registered = new HashSet<>();
|
|
for (ModuleRegistry item : modules) {
|
|
if (item.getModule() != null) {
|
|
registered.add(item.getModule());
|
|
}
|
|
}
|
|
List<String> configured = moduleRegistryMapper.selectConfiguredModules();
|
|
if (configured == null || configured.isEmpty()) {
|
|
return;
|
|
}
|
|
for (String module : configured) {
|
|
if (module != null && !module.isEmpty() && !registered.contains(module)) {
|
|
log.warn("gxmu_review_flow_config 里的模块 {} 未在 gxmu_module_registry 登记:"
|
|
+ "该模块的标题会回退成 module 原串、也不会出现在模块下拉里;"
|
|
+ "请补一行注册表数据(enabled 按是否新开申报入口决定)", module);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 执行校验;不一致时抛 {@link IllegalStateException} 让启动失败。
|
|
*/
|
|
public void check() {
|
|
if (!strict) {
|
|
log.warn("gxmu.module-registry.strict=false,跳过模块注册表一致性校验(仅限本地无库启动)");
|
|
return;
|
|
}
|
|
List<ModuleRegistry> modules = moduleRegistryService.listAll();
|
|
if (modules == null || modules.isEmpty()) {
|
|
log.warn("模块注册表为空,跳过一致性校验;请确认 gxmu_module_registry 已初始化");
|
|
return;
|
|
}
|
|
List<String> mismatches = new ArrayList<>();
|
|
Set<String> tableNames = new LinkedHashSet<>();
|
|
for (ModuleRegistry item : modules) {
|
|
String module = item.getModule();
|
|
String tableName = item.getTableName();
|
|
if (tableName == null || !tableName.equals(module)) {
|
|
mismatches.add(module + " -> " + tableName);
|
|
} else {
|
|
tableNames.add(tableName);
|
|
}
|
|
}
|
|
if (!mismatches.isEmpty()) {
|
|
throw new IllegalStateException("模块注册表 table_name 与 module 不一致:" + String.join(", ", mismatches)
|
|
+ "。module 同时是业务表名,必须与 table_name 相同(见 gxmu_module_registry 表注释);"
|
|
+ "请修正注册表数据后重启,或本地无库启动时设置 gxmu.module-registry.strict=false");
|
|
}
|
|
Set<String> existing = new HashSet<>(moduleRegistryMapper.selectExistingTableNames(tableNames));
|
|
List<String> missing = new ArrayList<>();
|
|
for (String tableName : tableNames) {
|
|
if (!existing.contains(tableName)) {
|
|
missing.add(tableName);
|
|
}
|
|
}
|
|
if (!missing.isEmpty()) {
|
|
throw new IllegalStateException("模块注册表引用的业务表不存在:" + String.join(", ", missing)
|
|
+ "。请先执行建表脚本或修正注册表数据;本地无库启动时可设置 gxmu.module-registry.strict=false");
|
|
}
|
|
warnModulesMissingFromRegistry(modules);
|
|
log.info("模块注册表一致性校验通过:{} 个模块,{} 张业务表", modules.size(), tableNames.size());
|
|
}
|
|
}
|