md-common-config
1. 模块定位
平台各服务的 Spring 装配层:Web MVC、上下文拦截、全局异常、消息(sms4j)、文件存储(x-file-storage)、MyBatis-Flex、WebSocket、Undertow、Actuator 安全、方法日志切面都在这里落地。
2. 源码解读
包根 top.mddata.common,按 configuration/(@Configuration 类)、configurer/(WebMvcConfigurer)、interceptor/(拦截器/过滤器)、aspect/(切面)、apiperm/(单体版接口权限入口)、controller/(公共服务接口)、datascope/(数据权限提供方)、file/、undertow/ 组织。
2.1 mode=cloud/boot 双拦截器互斥机制(核心)
WebConfiguration.java 用两个互斥的 @ConditionalOnProperty 装配上下文拦截器,条件都是 mdp.system.mode:
| mode | 装配 | 用户信息来源 |
|---|---|---|
cloud(matchIfMissing=true,缺省即此) | HeaderThreadLocalInterceptor | 网关已解析 token,把 userId/companyId/deptId 等写入请求头;拦截器只做 header → ContextUtil + MDC 的搬运 |
boot | TokenContextFilter extends SaInterceptor | 服务端直接读 sa-token 会话 StpUtil.getSession(),取 loginId 及 session 中缓存的组织字段 |
两者都在 preHandle 填充 ContextUtil/MDC,afterCompletion 中 ContextUtil.remove() + MDC.clear() 防止线程池串号;都只处理 HandlerMethod(静态资源直接放行),order 均为 -20,路径 /** 并排除 BasicConfigurer.getExcludeCommonPathPatterns() 的公共资源。(HeaderThreadLocalInterceptor 实现 AsyncHandlerInterceptor;TokenContextFilter 继承 sa-token 的 SaInterceptor。)
TokenContextFilter 还额外做了三件事(TokenContextFilter.java):
parseApplication:从 header/参数取 appId 写入上下文;parseToken:先用IgnoreProperties.isIgnoreUser(method, uri)判定免登录白名单,命中则跳过会话解析;- uri 级鉴权(已落地):
auth处理器回调ApiPermSupport.check(uri, method)——未登录跳过 →isIgnoreUriAuth白名单跳过 →ApiPermChecker.check(...)判定,不通过抛ArgumentException(判定链见 md-resource-api)。
2.2 其他关键类
| 类 | 说明 |
|---|---|
SystemAutoConfiguration | @ConditionalOnWebApplication + @EnableConfigurationProperties({MsgProperties, SystemProperties});无条件注册 MethodLogAspect(注释说明:不按 recordLog 条件注册,因 SystemProperties 是 @RefreshScope 代理,运行期开关可被 Nacos 热刷新);注册 AlwaysConfigurer Bean |
WebConfiguration | 继承 md-boot 的抽象类 BaseConfig(获得 4 个日期 Converter 注册);@EnableConfigurationProperties(IgnoreProperties.class);注入 ApiPermSupport 传给 TokenContextFilterConfigurer;addViewControllers 把 / 转发到 /index;覆写了 addResourceHandlers 但当前为空实现(静态资源走默认配置) |
MybatisFlexConfiguration | 独立 @Configuration:@EnableConfigurationProperties(DatabaseProperties.class) + @MapperScan;数据库 id 策略、审计、逻辑删除等能力由 md-db-mybatis-flex 的 MdMybatisFlexConfiguration extends DbConfiguration 自动装配 |
ExceptionConfiguration | 继承 md-boot 的抽象类 AbstractGlobalExceptionHandler。 |
ApiPermSupport | apiperm/ 包,单体版接口权限判定入口:未登录跳过 → isIgnoreUriAuth 白名单跳过 → ApiPermChecker.check(...),被 TokenContextFilter 的 auth 阶段调用 |
ServicePrefixController | controller/ 包,GET /findOnlineServicePrefix:返回 mdp.ignore.service-prefixes 映射,供前端与网关版同路径零分支 |
DataScopeProviderImpl | datascope/ 包,@Component implements DataScopeProvider(md-db-mybatis-flex DataScope 体系):isFilter() 恒 true;findEnabledMenuId(menuCode) 直查 mdc_resource_menu 并走 MenuDataScopeCacheKeyBuilder 缓存;getCurrentUser(menuId) 返回 userId/companyId/deptId(部门取 ContextUtil.getCurrentDeptOrCompanyId())+ 逐角色授权列表(RoleDataScopeCacheKeyBuilder 缓存);跨模块用 MyBatis-Flex Row Db 直查 mdc_ 表 |
MsgAutoConfiguration | @ConditionalOnBean(SmsReadConfig):应用提供了 sms4j 的动态配置读取 Bean 才装配;@EventListener(ContextRefreshedEvent) 时 SmsFactory.createSmsBlend 批量创建短信实例 |
FileStorageConfiguration | 取 x-file-storage 的 localPlus 第一个配置,复制后把 platform 改为 localPlusExt,注册 LocalPlusExtFileStorage(重写 generatePresignedUrl,返回 domain+fileKey 的完整访问地址) |
ActuatorSecurityConfig | 解决 /actuator 端点能直接访问导致敏感数据泄露的问题 |
WebSocketConfig | @EnableWebSocket + ServerEndpointExporter(使 @ServerEndpoint 生效) |
UndertowServerFactoryCustomizer | 为 WebSocket 预置 XnioWorker/ByteBufferPool,消除 Undertow 启动告警;@ConditionalOnClass(Undertow.class) 才注册 |
NotAllowWriteInterceptor | 演示环境保护:mdp.system.not-allow-write=true 时,按 not-allow-write-list 中的 Map<HTTP方法, URI列表> Ant 匹配,命中抛 BizException(-1, "演示环境禁止新增、修改、删除…");由 AlwaysConfigurer 以 order=Integer.MIN_VALUE 注册(最先执行) |
MethodLogAspect | 开发期全量方法日志,见 2.4 |
ServerApplication | 启动辅助工具:start(primarySource, args) 启动后打印 doc.html / druid 访问地址,各服务 main 方法调用它 |
2.4 MethodLogAspect 方法日志切面
与 md-log-starter 的 SysLogAspect(@RequestLog 注解驱动、异步审计入库)定位不同,本切面用于开发/测试期全量观察 Service 调用链(MethodLogAspect.java):
- 切点:
execution(public * com.mybatisflex.core.service.IService+.*(..))命中所有业务 Service(+表示子类型,覆盖 IService 的 default 方法与 SuperService/impl 声明的方法);within(top.mddata..service..*)兜底不走 IService 体系的 service 包 Bean。controllerLayer()切点已定义但未挂 @Around,需要时自行开启; - 开关:
mdp.system.record-log(默认 false)为总开关,record-args/record-result控制入参/返回值;每次调用实时读 @RefreshScope 代理,Nacos 改完即生效; - 安全性:单条日志截断 2000 字符;
sanitize()把 MultipartFile/ResponseEntity/流类型置换为占位描述,绝不读文件内容流;序列化失败降级 toString,不影响业务; - 防递归:
LogSuppressUtil.isSuppressed()跳过日志落库链路自身调用的 Service,避免"记录日志产生日志"; - 异常:只记类名+消息不打堆栈(堆栈由全局异常处理器统一记录),原样抛出。
3. 可配置参数
本模块消费的配置(定义在 md-common-pojo 与 mdp-base):
| 配置项 | 默认值 | 影响的类 |
|---|---|---|
mdp.system.mode | 缺省=cloud | WebConfiguration 拦截器装配 |
mdp.system.record-log/record-args/record-result | false/true/true | MethodLogAspect |
mdp.system.not-allow-write、not-allow-write-list | false/{} | NotAllowWriteInterceptor |
mdp.ignore.* | 见 md-common-pojo | TokenContextFilter 免登录判定 |
mdp.database.* | 见 md-db | MybatisFlexConfiguration(父类) |
mdp.async.* | 见 md-boot | 异步线程池(父类 BaseConfig 体系) |
| sms4j / x-file-storage 自身配置 | — | MsgAutoConfiguration / FileStorageConfiguration |
4. 扩展点
| 扩展点 | 方式 |
|---|---|
| 定制全局异常响应 | 在 ExceptionConfiguration 中覆写 AbstractGlobalExceptionHandler 的对应 @ExceptionHandler 方法 |
| 增删拦截器 | 新建 XxxConfigurer extends BasicConfigurer implements WebMvcConfigurer,参照 HeaderThreadLocalConfigurer 注册 |
| 数据权限用户来源 | 替换 DataScopeProviderImpl(@Component,可被同名/条件 Bean 覆盖),或扩展 DataScope 体系的用户/授权模型 |
| Web 基础配置 | WebConfiguration extends BaseConfig,可继续覆写 WebMvcConfigurer 各方法 |
| 文件存储平台 | 仿照 LocalPlusExtFileStorage 继承 x-file-storage 对应平台类,重写 URL 生成逻辑后在 FileStorageConfiguration 注册 |
| Actuator 用户体系 | 覆盖 UserDetailsService Bean,从内存用户换成数据库/LDAP |
5. 功能扩展建议
- 调整单体版 uri 鉴权行为:鉴权已内建于
TokenContextFilter→ApiPermSupport,免鉴权路径配mdp.ignore.*即可;要更换权限数据源则替换ApiPermProviderBean(见 md-resource-api),不必新建拦截器。 - 生产开启方法日志:
record-log默认关闭,压测/排障时经 Nacos 打开,配合record-args=false可只看调用链不落参数,注意日志量。 - 新增公共配置类:放
configuration/包并遵循现有条件装配风格(@ConditionalOnXxx),确保服务按需引入时不产生多余 Bean。
6. 二次开发注意事项
mode 配错 = 拿不到当前用户
mdp.system.mode 缺省按 cloud 装配(matchIfMissing=true)。单体部署若不显式配 boot,服务会等待网关注入请求头,ContextUtil.getUserId() 恒为 null,登录态表现为"时好时坏"。两套拦截器互斥,不要同时注册。
ActuatorSecurityConfig 存在硬编码凭证
源码中内存用户 actuator-admin 的密码为硬编码示例值。生产环境务必覆盖 UserDetailsService Bean 或改为外部化配置,不要把真实凭证提交到仓库;同时评估 /actuator/** 是否需要对公网暴露。
本模块对所有服务生效
它不是按依赖裁剪的 starter——只要服务依赖了 md-common-config 且启动类扫到 top.mddata 包,全部 @Configuration 都会参与装配。不需要的能力(如 sms4j)通过其自身条件(@ConditionalOnBean(SmsReadConfig))或排除依赖来关闭,改动本模块会影响所有服务,需全量回归。
抽象类继承点集中在此
mdp-base 故意把 BaseConfig、AbstractGlobalExceptionHandler 留成抽象类,md-common-config 是平台统一继承落地处。业务服务不要再各自继承一遍,否则会出现双份 Converter/异常处理器 Bean 冲突。(MyBatis-Flex 侧无需继承:MdMybatisFlexConfiguration 在 md-db-mybatis-flex 中自动装配,本模块只做 @MapperScan。)