feat(spec58): 模块注册表后端与模块硬编码收敛(#60)
- 新增 gxmu/module 包:ModuleRegistry 实体/Mapper/Service,listEnabled 按 sort_number 升序,titleOf 未注册回退 module 原串 - 新增 GET /api/gxmu/module-registry/list(需登录),返回启用模块元数据 - ReviewFlowConfigServiceImpl / ReviewListServiceImpl 的模块名映射改读注册表,查不到回退原串 - DeclareGuardServiceImpl 的模块集合改为注册表驱动,含 enabled=0(停用模块存量仍可撤回/作废) - 启动期一致性校验:table_name≠module 或业务表缺失即启动失败,gxmu.module-registry.strict=false 可关闭 - 单测 13 例:注册表加载与排序、titleOf 回退、启动校验、module 名回退
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
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>允许 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行校验;不一致时抛 {@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");
|
||||
}
|
||||
log.info("模块注册表一致性校验通过:{} 个模块,{} 张业务表", modules.size(), tableNames.size());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user