Skip to content

后端架构与模块依赖 ​

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

后端是一个 Maven 多模块单体(不是微服务):enlev-server 是唯一启动模块,业务能力按域拆成 5 个 enlev-module-*,横切能力沉在 enlev-framework 的 14 个 starter 里。本页讲清依赖方向、请求生命周期与扩展位置。

前置阅读:仓库结构与改名约定。

它解决什么问题 ​

(依赖关系实测自各模块 pom.xml 的 cn.enlev.ainb 依赖声明。)

三句话结论:

  1. 依赖是单向的:agent → {ai, infra, bpm, system},没有任何模块反向依赖 agent。给 agent 加工具可以直接调别的模块的 service;反过来别的模块要用 agent 能力,只能通过接口或事件解耦。
  2. system 是地基:所有业务模块都依赖它(用户、租户、权限、字典)。
  3. infra 被 system 依赖,也依赖 system:system 的 pom 里有 enlev-module-infra(文件、日志),infra 不依赖任何业务模块——两者不构成环,是因为 system 只调 infra 的 api 包。

🔴 enlev-module-im 已被整体删除,不只是「注释出 reactor」

本手册曾有 10 处写着「它在根 pom.xml 第 19 行被注释掉、目录还在、不要顺手修好」。这个描述已过期,而且那条行号现在是误导——pom.xml:19 当前是 <module>enlev-module-ai</module>。

实测(2026-10-25,在 ainb/ 下):

检查结果
ls -d enlev-module-*只剩 agent ai bpm infra system 5 个,没有 im
grep -n "module-im" pom.xml0 命中,连注释掉的那行也没了
git show --numstat --format='' 12347efcd1 | grep -c '^enlev-module-im/'305(该提交共删 311 个文件)

删除发生在 12347efcd1 refactor(系统):清除未用上的模块。上游的支付 / 商城 / CRM / ERP / 会员 / IoT / 公众号等模块同批清掉。

reactor 现在是 24 个模块 = 根 pom + enlev-dependencies + enlev-framework 聚合 pom + 它的 15 个子模块 + enlev-server + 5 个业务模块。两条独立口径都对得上:mvn package 输出 [24/24],find . -name pom.xml -not -path '*/target/*' | wc -l → 24。

所以 -pl enlev-module-im 必然失败,不是因为被注释掉,是因为不存在。别按上游 yudao 的模块清单猜这里有什么。

移动端仓里那 11 个 pages-{crm,erp,im,iot,mall,…} 分包编译进包但用户点不到,正是因为后端这些模块已删。

代码在哪 ​

三层结构 ​

层目录职责能不能被业务模块改
启动层enlev-server主类、5 个 yaml、logback只改配置
业务层enlev-module-{system,infra,bpm,ai,agent}Controller / Service / Mapper / DO主战场
框架层enlev-framework/enlev-spring-boot-starter-*横切能力,全部走 Spring Boot 自动配置改这里等于改全项目,慎重

14 个 starter + 1 个公共包 ​

构件关键自动配置类负责什么
enlev-common—(非 starter)pojo(CommonResult / PageResult / PageParam)、exception(ErrorCode / ServiceException)、enums、util、validation
starter-webEnlevWebAutoConfiguration、EnlevJacksonAutoConfiguration、EnlevSwaggerAutoConfiguration、EnlevApiLogAutoConfiguration、EnlevXssAutoConfiguration、EnlevApiEncryptAutoConfiguration、EnlevBannerAutoConfigurationURL 前缀、CORS、全局异常、统一响应、JSON 序列化、knife4j 分组、XSS、接口加密
starter-securityEnlevSecurityAutoConfiguration、EnlevWebSecurityConfigurerAdapter、EnlevOperateLogConfigurationToken 认证过滤器、@ss bean、@PreAuthorize 支撑、操作日志
starter-mybatisEnlevMybatisAutoConfiguration、EnlevDataSourceAutoConfiguration、EnlevTranslateAutoConfigurationBaseMapperX / LambdaQueryWrapperX、BaseDO、分页与乐观锁拦截器、字段自动填充、Easy-Trans 翻译
starter-redisEnlevRedisAutoConfiguration、EnlevCacheAutoConfigurationRedisTemplate 序列化、Spring Cache
starter-biz-tenantEnlevTenantAutoConfiguration多租户:TenantBaseDO、TenantLineInnerInterceptor、@TenantIgnore、租户上下文过滤器
starter-biz-data-permissionEnlevDataPermissionAutoConfiguration、EnlevDeptDataPermissionAutoConfiguration上游的数据权限(@DataPermission、按部门过滤)。⚠️ 本项目定制的授权决策引擎不在这里,在 enlev-module-system/.../framework/permission/
starter-protectionEnlevIdempotentConfiguration、EnlevLock4jConfiguration、EnlevRateLimiterConfiguration、EnlevApiSignatureAutoConfiguration@Idempotent、@Lock4j、@RateLimiter、API 签名
starter-jobEnlevQuartzAutoConfiguration、EnlevAsyncAutoConfiguration定时任务用 Quartz(不是 XXL-Job)+ @Async 线程池
starter-mqEnlevRedisMQProducer/ConsumerAutoConfiguration、EnlevRabbitMQAutoConfigurationRedis Stream / RabbitMQ 消息
starter-websocketEnlevWebSocketAutoConfigurationWebSocket,sender-type: local 时不需要 MQ
starter-excelEnlevDictAutoConfigurationEasyExcel(cn.idev.excel)导入导出 + 字典转换
starter-monitorEnlevTracerAutoConfiguration、EnlevMetricsAutoConfigurationSkyWalking 链路 + Actuator 指标
starter-biz-ip—(纯工具)IP / 地区库(Area)
starter-test—(无 imports)测试基类:BaseDbUnitTest(H2)、BaseMockitoUnitTest、BaseRedisUnitTest

定制功能:自研代码不在 framework 层

enlev-framework 下没有 agent 或新权限体系的 starter。自研部分全在模块层:

  • enlev-module-system/.../framework/permission/——子包 config / core / dataset / decision / rule / scan / subject / perf / runner,核心类 GrantDecisionService,对应配置 enlev.permission.grant-enabled / collapse-enabled(yaml 注释原文标为「权限体系(S1~S5 重构)」);
  • enlev-module-agent——整模块自研;
  • enlev.data-model.*——低代码建模规范,硬约束在 SqlSafeGuard 里。

核心机制 ​

一个请求怎么从 URL 走到 SQL ​

过滤器顺序不是靠 @Order 随手写的,而是集中在 enlev-common 的 WebFilterOrderEnum(注释原文:「考虑到每个 starter 都需要用到该工具类,所以放到 common 模块下的 enums 包下」):

java
int CORS_FILTER = Integer.MIN_VALUE;
int TRACE_FILTER = CORS_FILTER + 1;
int REQUEST_BODY_CACHE_FILTER = Integer.MIN_VALUE + 500;
int API_ENCRYPT_FILTER = REQUEST_BODY_CACHE_FILTER + 1;
int TENANT_CONTEXT_FILTER = -104; // 需要保证在 ApiAccessLogFilter 前面
int API_ACCESS_LOG_FILTER = -103;
int XSS_FILTER = -102;
int TENANT_SECURITY_FILTER = -99; // 需要保证在 Spring Security 过滤器(-100)后面
int FLOWABLE_FILTER = -98;

新增过滤器时必须在这个枚举里占一个位,不要写魔法数字。

/admin-api 前缀是包名决定的,不是注解写的 ​

Controller 上只有 @RequestMapping("/system/post"),URL 里的 /admin-api 由 EnlevWebAutoConfiguration 通过 WebMvcRegistrations 在 HandlerMapping 实例化时统一加:

java
// WebProperties.java
private Api appApi = new Api("/app-api", "**.controller.app.**");
private Api adminApi = new Api("/admin-api", "**.controller.admin.**");

// EnlevWebAutoConfiguration#putPathPrefix
pathPrefixes.put(api.getPrefix(),
    clazz -> clazz.isAnnotationPresent(RestController.class)
            && matcher.match(api.getController(), clazz.getPackage().getName()));

匹配用的是 AntPathMatcher(".") 对包名做 **.controller.admin.** 匹配。推论:

  • 把 Controller 直接放在 controller/ 下(不进 admin / app 子包)→ 不加前缀 → 前端按 /admin-api/... 调必然 404;
  • 管理后台接口进 controller/admin/,用户端接口进 controller/app/,这是硬约定。

统一响应与异常 ​

GlobalResponseBodyHandler + GlobalExceptionHandler(都在 starter-web)保证任何返回都是 CommonResult{code, data, msg}。AccessDeniedException → 403 没有该操作权限,ServiceException → 原样带出业务码与消息,其余 Exception 兜底 500。详见接口约定与 CRUD 骨架。

配置与开关 ​

application.yaml 里 enlev.* 的主要开关(都能在不改代码的前提下改变行为,动之前先读它旁边的注释——本项目的 yaml 注释写得比文档细):

配置默认作用与后果
enlev.info.base-packagecn.enlev.ainb组件扫描与 @MapperScan 的根,改名时唯一需要动的地方
enlev.security.mock-enable三个 profile 均 false(476b4b2637 起)打开则 Bearer test{userId} 免登录,且 mock-secret 默认就是字面量 test。曾经 local/prod 都是 true,现已全关;代码路径未删,开关一开就复活。见环境准备与启动与坑位索引 A1
enlev.permission.grant-enabledtrue关掉则 @ss.hasPermission 回落到「按权限串在 system_menu 里找菜单」,数据集动作(infra:form-design-run:*)只存在于 sys_resource,非超管会寸步难行
enlev.permission.collapse-enabledtrue「资源身份 × 动作」塌缩读路径。🔴 必须与数据迁移同批切换:数据没迁就打开 = 静默判无权(全 200、无异常、按钮全消失)
enlev.permission.subject.dept-enabledtrue部门作为授权主体。关掉则流程设计器里配的「某部门可发起」静默失效
enlev.data-model.table-prefix-enforcedtrue建模规范(命名/前缀/注释/配额)只作用于新建路径;importTables 导入存量表一律放行。硬约束(保留字、标识符白名单、框架表黑名单)在 SqlSafeGuard 里,不可关
enlev.tenant.enabletrue多租户总开关
enlev.api-encrypt.enablefalse请求/响应 AES 加密,按请求头 opt-in(前端 headers.isEncrypt)
enlev.xss.enablefalseXSS 过滤
enlev.websocket.enabletruesender-type: local,不需要 MQ
enlev.agent.webhook.rate-limit-*开agent 模块唯一匿名面的限流:IP 维度 60/60s AND path 维度 300/60s(注释说明 IP 取自客户端可控头,真正承重的是 path 维度)
enlev.agent.sanitize.*-max-length五档不可信输入进 state 前按污点源分档截断(chat 4000 / webhook body 32768 / form-event 8192 / retrieval 4000 / tool-result 16384),max-depth: 16 防 StackOverflowError

接口清单 ​

按域统计(由 tools/gen-api.mjs 扫描 Controller 注解生成,勿手抄):

域Controller 数接口数
system45269
infra(含低代码)23219
bpm1582
ai13101
agent(自研)15101
合计111772

这张表会随后端提交漂移,别把它当常量引用

本页上一版记的是 110 / 761。三处差额都能点名到具体提交与端点,说明差异来自后端新增、 不是生成脚本口径变了:

域变化来源
system44 → 45 Controller、261 → 269 端点★ NavNodeController(/system/nav-node,8 个端点),bf82d570b2「新增分端导航树 system_nav_node」
infra218 → 219 端点POST /infra/form-design/bind-app-nav,cb41f251c5「表单设计支持发布到 App 端」
agent99 → 101 端点GET /agent/diagnostic/doc-sync-status、GET /agent/diagnostic/doc-sync-verify,1b1ac7b1e9「AI 助手内置知识库改成可溯源」

要引用具体数字时,以 docs/.generated/api-index.md 的当次生成结果为准(跑 node tools/gen-api.mjs 刷新),本表只作量级参考。

这张表曾经整列偏小,原因是脚本丢了多行注解

gen-api.mjs 原来在遇到不以 @ 开头的续行时会清空缓冲,把上面已经攒到的 @PostMapping 一起丢掉:

java
@Operation(summary = "…",
           description = "…")   // ← 这一行不以 @ 开头
@PostMapping("/xxx")

全站因此漏掉 4 个 Controller / 81 个端点,其中包括匿名可达的 POST /agent/webhook/{path} ——攻击面最大的那个端点恰好不在清单里。已改用「括号是否配平」判据修复。

⚠️ 由此得到一条通用口径:数 Controller 与端点不要用裸 grep。 grep -rl "@RestController" 会把只在注释里提到该注解的 PermissionAnnotationScanner 也算进去 (system 域因此多数 1 个:裸 grep 46,真实 45);grep -o '@GetMapping' 不加行首锚定会命中 注释掉的代码(infra 域因此多数 4 个端点:裸 grep 223,真实 219,多的是 DataModelDataController.java:72,81,93,104 四行被 // 注掉的 mapping)。 详见 system 域开发 · 接口清单。

完整清单在文档工程的 docs/.generated/ 下(api-index.md + api-{system,infra,bpm,ai,agent}.md)——本手册各域章节的「接口清单」小节直接引用它们,不手抄。

怎么扩展 ​

给现有模块加一个功能(最常见):照接口约定与 CRUD 骨架的五件套走,或者直接用代码生成器(infra/codegen 菜单)生成后改。

新增一个业务模块 enlev-module-xxx:

  1. 建目录,pom.xml 的 <parent> 指向 enlev,artifactId = enlev-module-xxx,依赖 enlev-module-system + 需要的 starter(照抄 enlev-module-ai/pom.xml 最省事);
  2. 根 pom.xml 的 <modules> 加一行;enlev-server/pom.xml 加依赖;
  3. 包结构按 cn.enlev.ainb.module.xxx.{api,controller,dal,enums,framework,service} 建,Controller 必须落在 controller/admin/ 或 controller/app/ 下(否则没有 URL 前缀);
  4. 若要独立 Swagger 分组,写一个 XxxWebConfiguration:
java
@Configuration(proxyBeanMethods = false)
public class AgentWebConfiguration {
    @Bean
    public GroupedOpenApi agentGroupedOpenApi() {
        return EnlevSwaggerAutoConfiguration.buildGroupedOpenApi("agent");
    }
}
  1. 错误码在 enums/ErrorCodeConstants.java 里按 ServiceErrorCodeRange 申请的区间写,别撞号。

新增一个 starter:在 enlev-framework/ 下建 enlev-spring-boot-starter-xxx,自动配置类用 @AutoConfiguration,并在 src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 里登记类全名——本项目全部 starter 都是这个套路(Spring Boot 3 已不用 spring.factories)。

坑与注意 ​

  • agent 模块没有 Swagger 分组:缺 AgentWebConfiguration(实测四个 *WebConfiguration 只有 ai / bpm / infra / system),101 个接口只能在 all 组看到(实测 /v3/api-docs/swagger-config 只返回 ai/all/bpm/infra/system)。
  • agent 模块在 pom 里本地钉住了 spring-ai-bom 版本,用来覆盖 spring-ai-alibaba-graph-core 的传递依赖——是刻意为之,不要「修正」。
  • agent 模块没有 src/test/resources/application-unit-test.yaml:它的 161 个测试文件 / 2225 个 @Test 绝大多数是纯 Mockito。要在那里加 DB 测试,得先补这份配置(BaseDbUnitTest 依赖 profile unit-test 与 /sql/clean.sql)。
  • ai 模块连 src/test/resources/ 目录都不存在:43 个测试文件 / 187 个 @Test 里有 86 个 @Disabled(全部不带原因,分布在 32 个文件:29 个 *Tests.java 占 81 个,另 5 个在 3 个厂商客户端 *Test.java),是全仓测试网最薄的模块,而它管着所有厂商模型调用。详见 ai 域开发 · 测试。🔴 数这个 86 要加行首锚定:grep -ro '@Disabled' 会多数 2 个——framework/ai/core/VendorApiKeys.java:9,23 的 javadoc 里用 {@code @Disabled} 提到了它。文件数同理会多数 1 个(33 vs 32),多的就是 VendorApiKeys.java 自己。
  • 测试 SQL 只在 3 个模块里有:enlev-module-{bpm,infra,system}/src/test/resources/sql/{clean.sql,create_tables.sql}(H2 用),ai / agent 没有。(旧版本这里写的是 4 个、含 enlev-module-im——那个模块已随 12347efcd1 整体删除,目录都不在了。)
  • @author 芋道源码 标签在很多文件里保留着,属于归属声明,不要批量替换成自己的名字。
  • 不要把业务逻辑写进 starter:framework 层的类被所有模块共享,改一处影响全站;业务定制请放模块内。
  • 依赖是单向的,别为了让 system 用 agent 的能力而加反向依赖——会立刻造成 Maven 循环依赖编译失败。用接口下沉到 api 包或走事件。

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

相关页面 ​