仓库结构与改名约定
适用端:后端 + Web + 移动端 | 类型:参考
三个仓库的目录布局、构件命名规律,以及「从芋道改名过来」这件事在代码里留下了什么痕迹。看懂这一页,才能判断一段代码是上游自带的还是本项目定制的。
前置阅读:环境准备与启动。
本页只讲「三仓共同的规律」
移动端仓库自己的目录树、分包划分、easycom 规则不在这里重复一遍——那是工程结构与约定式路由的内容。本页下面两棵树是后端与 Web 的,移动端那棵去那一页看。
它解决什么问题
本项目是芋道的改名分支,不是从零写的。三仓的直接上游各不相同:
| 仓库 | 直接上游 | 上游的上游 |
|---|---|---|
ainb(后端) | ruoyi-vue-pro | — |
ainb-ui-admin-vue3(Web) | yudao-ui-admin-vue3 | — |
ainb-ui-admin-uniapp(移动端) | yudao-ui-admin-uniapp | unibest(工程骨架) |
开发者最常犯的三类错都源于没搞清这一点:
代码在哪
| 仓库 | 分支 | 提交数(2026-10-25 实测) | 本项目自己的提交 | 内容 |
|---|---|---|---|---|
C:\code\xpy\ainb | master | 26 | 26(全部) | 后端,Maven 多模块单体 |
C:\code\xpy\ainb-ui-admin-vue3 | master | 5821 | 大部分 | 后台前端,Vue 3 + Vite |
C:\code\xpy\ainb-ui-admin-uniapp | master | 2426 | 23(git log --author=zhechu) | 移动端,uni-app 3 + Vue 3.4 |
C:\code\xpy 本身不是 git 仓库,三个子目录各自独立。跨端改动要提交三次,执行任何 git 命令前先 cd 进对应子目录。
⚠️ 提交数那一列是活动值,每提交一次就变,别当常量抄——本页写下的当天就错过一次(Web 仓因为一个注释修复多了一个 commit,5820 当场过期)。要的是量级对比(后端二十几个 vs 另两仓数千个)与「哪一仓必须按作者过滤」。复核:
# 在 C:\code\xpy 下跑
for r in ainb ainb-ui-admin-vue3 ainb-ui-admin-uniapp; do echo "$r $(git -C $r rev-list --count HEAD)"; done
git -C ainb-ui-admin-uniapp log --author=zhechu --oneline | wc -l # 移动端里本项目自己的三仓的 git 考古可用度完全不同,别用同一套方法
- 后端(26 提交):
78027a03ce feat(系统):项目初始化一次性入库了 4136 个文件,这个提交里的东西查不出上游还是定制(git log -S只会指到它)。但它之后有 25 个真实提交,nav-node、菜单拆分、App菜单 这些近期定制都能查到。所以「后端历史被压平、考古不可用」这个旧结论只对初始化提交成立。4136 个文件里 3384 个至今没动过(blame 只能到那一刀)、752 个已有可查历史——量测命令见坑位索引 F5。 - Web(5821 提交):历史完整,
git log --diff-filter=A -- <path>能查某个组件是哪次提交新增的,本手册判断「定制组件」用的就是这个方法。 - 移动端(2426 提交):历史也完整,但绝大多数是上游的(
feige996/菲鸽是 unibest 作者,YunaiV是芋道作者)。--diff-filter=A查出来的「首次提交」多半是上游的,不能当定制依据。这里要用git log --author=zhechu——只有 23 个提交,一眼看完,本页与移动端章的定制结论都出自这份清单。
改名规律
| 维度 | 上游 | 本项目 |
|---|---|---|
| Java 包名 | cn.iocoder.yudao | cn.enlev.ainb |
| Maven groupId | cn.iocoder.cloud / cn.iocoder.boot | cn.enlev.ainb |
| 构件前缀 | yudao- | enlev- |
| 配置前缀 | yudao.* | enlev.* |
| Web 包名 | yudao-ui-admin-vue3 | ainb-ui-admin-vue3 |
| 移动端包名 | yudao-ui-admin-uniapp | ainb-ui-admin-uniapp |
| 产品名 | 芋道 | 启凌智搭 |
| 版本 | — | 后端 ${revision} = 2026.07-SNAPSHOT;Web 与移动端均 2026.07-snapshot |
🔴 移动端的改名是不彻底的,而且刻意如此:src/components/yudao-ui/ 这个目录名没改。它被 easycom 规则 ^yd-(.*) → @/components/yudao-ui/yd-$1/yd-$1.vue 与自动生成的组件类型声明双重引用,改名牵连 56 个文件且无收益。目录名不代表归属变更,别按它推断代码来源。详见工程结构与约定式路由。
包名靠一个配置项统一驱动,改名时不需要动代码:
# enlev-server/src/main/resources/application.yaml
enlev:
info:
version: 1.0.0
base-package: cn.enlev.ainb主类的 @SpringBootApplication(scanBasePackages = {"${enlev.info.base-package}.server", "${enlev.info.base-package}.module"})、MyBatis 的 @MapperScan("${enlev.info.base-package}", annotationClass = Mapper.class) 都引用它。新写自动配置类时不要硬编码 cn.enlev.ainb,跟着这个占位符走。
改名彻底程度(实测 grep,2026-10-25;复核命令在下面):
| 仓库 | cn.iocoder 在 *.java/*.xml/*.yaml | yudao 残留文件(代码类) | 残留文件(全仓,含 md/图片名) |
|---|---|---|---|
ainb | 0 | 12 | 17 |
ainb-ui-admin-vue3 | 0 | 20 | 26 |
ainb-ui-admin-uniapp | 0 | 62 | 67 |
# 在每个仓库根目录跑;三列分别对应上表
git grep -l 'cn\.iocoder' -- '*.java' '*.xml' '*.yaml' | wc -l
git grep -l yudao -- '*.java' '*.ts' '*.vue' '*.js' '*.xml' '*.yaml' '*.json' | wc -l
git grep -il yudao -- . | wc -l后端与 Web 的残留全是注释里的上游 issue 链接或归属声明,例如:
// enlev-spring-boot-starter-mybatis/.../core/util/MyBatisUtils.java:50
page.setOptimizeJoinOfCountSql(false); // 关联 issue:https://gitee.com/zhijiantianya/yudao-cloud/issues/ID2QLL这些不要改:它们是上游踩坑记录,链接失效不影响功能,删掉反而丢失了「为什么这么写」的依据。
移动端那 62 个是另一种性质:绝大多数是 src/components/yudao-ui/ 目录本身(56 个文件)与引用它的 easycom 规则、自动生成的类型声明。这不是「没改干净」,是上面那条 🔴 说的刻意保留。
改名是怎么做的
不是一堆 sed,是上游自带的一个一次性工具:ainb/enlev-server/src/test/java/cn/enlev/ainb/ProjectReactor.java(148 行,@author 芋道源码)。它把整棵目录树拷到 projectBaseDirNew,一路 replaceAll 掉 groupId / artifactId / 包名 / 产品标题,并按新包名重排目录路径;图片、字体、IP 库这类二进制走白名单直接拷贝;target/、node_modules/、.idea/、.git/、dist/、.iml、.html.gz 被过滤掉。
放在 src/test/java/ 下是有讲究的:不进 jar,也不被 surefire 收到(类名不以 Test 结尾)。这就是测试规范那张表的合计里没有它的原因——enlev-server/src/test/ 下唯一的 .java 就是它。
🔴 它的改名功能已经失效,别再按 javadoc 理解它。四个常量早被它自己改成了新值,与 main 里的目标值逐一相同:
| 常量 | 现值 | main 里的目标值 |
|---|---|---|
GROUP_ID | cn.enlev.ainb | cn.enlev.ainb |
ARTIFACT_ID | enlev | enlev |
PACKAGE_NAME | cn.enlev.ainb | cn.enlev.ainb |
TITLE | 启凌智搭 | 启凌智搭 |
原值 == 目标值 ⟹ replaceFileContent 是恒等变换。现在跑它只会把项目原样拷一份到硬编码的 C:\code\test\ainb(目录已存在则打日志退出)。
⚠️ 拷贝的根取的是 System.getProperty("user.dir"),也就是在哪个目录跑它就拷哪个目录。从 C:\code\xpy 跑会把三个仓库连 _logs/、_evidence/ 一起拷出去——那两个目录按产物纪律绝不外传(后端日志里有匿名 webhook 打进来的不可信注入载荷)。真要跑先 cd ainb。
改名早已完成,这个文件留着只是历史证据。不要「修好」它:把常量改回 cn.iocoder.yudao 会让它变成一个真能跑、但会把整个仓库改回上游命名的危险工具。
三仓的门面文件
三个仓库各有 README.md + LICENSE + THIRD-PARTY-NOTICES.md + licenses/,都已按本项目的真实上游重写过,不再是上游原文:
LICENSE都是 MIT 正文 +Copyright (c) 2026 启凌,后面跟一段中文归属,说明本仓派生自哪些上游;THIRD-PARTY-NOTICES.md是分组表格:派生关系 / 随产物分发的运行时依赖 / 随产物分发的资源 / 仅构建期依赖 / 传递依赖口径;licenses/存协议全文,按协议种类一份(MIT-LICENSE、Apache-2.0-LICENSE…)+ 按派生上游一份(ruoyi-vue-pro-LICENSE、unibest-LICENSE、yudao-ui-admin-uniapp-LICENSE…),不是一包一文件——移动端 31 个直接依赖解析出的传递闭包上千个包,一包一文件既不现实也无价值。具体包数与口径以各仓THIRD-PARTY-NOTICES.md的「传递依赖」节为准,本页不重述(重述就是第二份会漂移的拷贝)。
🔴 三仓的门面必须互相对得上:移动端仓曾经把 Web 仓的 LICENSE 逐字抄过来,声称自己「内嵌 src/lib/form-create、src/lib/form-designer 源码副本」——而移动端根本没有 src/lib/ 目录。改任何一仓的归属措辞前先读另两仓的同一节。
上游那批 《芋道 Spring Boot XXX 入门》.md 文档在本项目里已不存在。
后端目录布局
ainb/
├── pom.xml # 根 reactor,modules 列表在这里
├── enlev-dependencies/ # BOM:packaging=pom,纯 dependencyManagement(99 个依赖版本)
├── enlev-framework/ # 1 个公共包 + 14 个 starter
│ ├── enlev-common/ # 非 starter:pojo / exception / enums / util / validation
│ └── enlev-spring-boot-starter-*/
├── enlev-module-system/ # 用户/角色/菜单/租户 + ★ 定制权限体系
├── enlev-module-infra/ # 文件/代码生成/定时任务 + ★ 低代码(模型/表单/门户)
├── enlev-module-bpm/ # Flowable 工作流
├── enlev-module-ai/ # Spring AI 接入
├── enlev-module-agent/ # ★ 全模块自研:工具体系 / ReAct / 工作流 / HITL
├── enlev-server/ # 启动模块,端口 48080,打 fat jar
├── sql/ # mysql / oracle / postgresql / dm / kingbase / … 各方言脚本
└── script/ # docker / idea / jenkins / shellreactor 里只有 5 个业务模块,上游那些已经被整体删除
pom.xml 的 <modules> 是 enlev-dependencies、enlev-framework、enlev-server + enlev-module-{system,infra,bpm,ai,agent},一共 8 项、其中 5 个是业务模块。上游的支付 / 商城 / CRM / ERP / 会员 / IoT / 公众号 / IM 不是「被注释掉」,是目录连文件一起没了(12347efcd1 refactor(系统):清除未用上的模块;实测 ls -d enlev-module-* 只有 5 个)。
所以 -pl enlev-module-im 这类命令必然失败,别按上游 yudao 的模块清单猜这里有什么。移动端仓里那 11 个 pages-{crm,erp,im,iot,mall,…} 分包正是因为后端对应模块已删才点不到。
与上游最大的结构差异:模块是扁平 jar
上游把每个业务模块拆成 -api / -biz 两个构件,本项目没有拆——api 只是同一构件里的一个 package(实测各 enlev-module-*/pom.xml 均无 <modules> 节点)。代码生成器模板(enlev-module-infra/src/main/resources/codegen/)、模块依赖、启动类扫描路径都是按扁平结构写的。把上游的拆分方式「改回来」会直接破坏代码生成与编译。
单个业务模块内部的标准分层(以 enlev-module-system 为例,src/main/java/cn/enlev/ainb/module/system/ 下一级目录):
api/ 对外暴露给其它模块的接口与 DTO
controller/ 分 admin/ 与 app/ 两层(对应 /admin-api 与 /app-api 前缀)
convert/ MapStruct 转换器(新代码更多用 BeanUtils.toBean)
dal/ dataobject/(DO)+ mysql/(Mapper)
enums/ 枚举与 ErrorCodeConstants
framework/ 本模块的自动配置与 Web 配置
job/ Quartz 定时任务
mq/ 消息生产/消费
service/ 业务实现
util/ 模块内工具enlev-module-agent 的结构略有不同:controller / dal / enums / framework / job / listener / service / tool——多了 listener(事件)与 tool(Agent 工具实现)。
前端目录布局
ainb-ui-admin-vue3/
├── package.json vite.config.ts vitest.config.ts tsconfig.json
├── .env .env.local .env.dev .env.test .env.stage .env.prod
├── build/vite/ # index.ts(插件工厂)+ optimize.ts(预构建清单)
├── types/ # 全局 d.ts:global.d.ts(PageParam/PageResult)、router.d.ts(RouteMeta)
├── uno.config.ts # UnoCSS
└── src/
├── api/ # 后端接口封装,按模块分目录
├── components/ # 全局组件(62 项,unplugin 自动注册)
├── config/axios/ # 请求封装(唯一非业务的 config 目录)
├── directives/ # v-hasPermi / v-hasRole
├── hooks/web/ # useMessage / useTable / useCrudSchemas / …
├── layout/ # 布局与菜单渲染
├── lib/ # ★ vendored:form-create + form-designer 源码副本
├── plugins/ # echarts / elementPlus / formCreate / svgIcon / unocss / vueI18n
├── router/ # index.ts + modules/remaining.ts(静态路由 697 行)
├── store/modules/ # pinia:user / permission / dict / app / tagsView
├── utils/ # dict / formatTime / tree / auth / routerHelper / …
└── views/ # 页面,一级目录与后端模块对应src/views/ 一级目录:Error / Home / IFrame / Login / Profile / Redirect / agent / ai / bpm / crm / im / infra / system。其中 agent(assistant / config / def / promptTemplate / tooldef / usage / workflow)是本项目新增;infra 下的 billTemplate / dataInterface / dataModel / formDesign / portal 是低代码定制;crm、im 是上游遗留(后端对应模块未启用,路由还在,靠后端菜单过滤不显示)。
src/lib/ 是上游源码副本,要求零 diff
src/lib/form-create/ 与 src/lib/form-designer/ 不是 npm 依赖,是 vendored 的上游源码,已在 ESLint 中排除。不要修改、不要重排格式、不要「顺手优化」,详见 src/lib/form-create/VENDOR.md。
配套两处必须一起看:
vite.config.ts里那组@form-create/*alias 顺序敏感(长前缀必须排在短前缀之前,注释原文已说明 Rollup alias 的匹配规则);.gitignore里auto-*.d.ts会误伤 vendored 的类型声明,所以紧跟一行例外:
auto-*.d.ts
# 上一行本意是忽略 unplugin 生成的 src/types/auto-{imports,components}.d.ts,
# 但它会误伤 vendored 的 @form-create/element-ui/auto-import.d.ts(那是上游源码,必须入库)
!src/lib/**/auto-*.d.ts提交信息规范
格式是非标准的,用全角括号与全角冒号:
type(范围):中文描述type 取 feat / fix / refactor / chore / docs;范围用中文(前端、系统、infra、UI 等)。描述写清行为变化与动机。真实示例(前端 git log):
fix(前端):v-hasPermi/v-hasRole 改「打标记 + CSS 隐藏」,不再摘节点留游离锚点
fix(前端):授权抽屉显式关掉 Esc 关闭——穿透模式下误按 Esc 会毁掉填了一半的表单
feat(前端):资源树选择器在权限编码前展示带颜色的资源类型标记产物纪律
_logs/、_evidence/、token.txt、dist-prod*、target/ 一律不进 git。前端 .gitignore 用 /dist* 覆盖了 dist-prod(实测 git check-ignore 命中,git ls-files dist-prod 为空)。
永远不要用 git add -A / git add .
按文件名逐个 add。_logs/ 里的后端日志原样包含匿名 webhook 打进来的完整请求体,其中可能有用于注入实测的不可信载荷(零宽空格、RLO 控制符等)——误入库等于把提示注入样本永久写进 git 历史。
怎么判断「上游 or 定制」
按可靠度从高到低:
查 git 首次提交 / 作者——三仓方法不同,见上面那条 warning:
- Web:
git log --diff-filter=A --oneline -- src/components/Portal→ 若首见于0fe8f3a33 feat(infra):新增数据建模、业务数据、数据接口、门户设计、单据模板、表单设计等模块,即本项目定制; - 后端:只有初始化提交之后的 25 个提交可查,落在初始化提交里的一律查不出;
- 移动端:
git log --author=zhechu --oneline(23 个提交)就是本项目的全部改动,别用--diff-filter=A,那会指到 unibest 或芋道的提交上去。
- Web:
看目录归属:
enlev-module-agent整模块自研;enlev-module-system/.../framework/permission/(子包config / core / dataset / decision / rule / scan / subject / perf / runner)是定制权限体系;enlev-module-infra里的datamodel / formdesign / portal / datainterface / billtemplate是低代码定制;移动端src/pages-bpm/components/form-create/是自研渲染器。看表前缀:
sys_*(7 张,权限体系)与agent_*(16 张)是定制的,system_*/infra_*/bpm_*/ai_*是上游命名。🔴 有一个例外:system_nav_node(分端导航树)是本项目自研的,却挂在system_前缀下(NavNodeDO的@TableName)——按前缀判断会把它误判成上游表。同类例外还有infra_form_design.app_nav_node_id这一列。看配置项:
enlev.permission.grant-enabled/collapse-enabled、enlev.data-model(SqlSafeGuard)、agent.*、enlev.agent.doc.*都是本项目新增。看 Java 注释的
@author:「芋道源码」是上游,「走进新时代」是本项目。后端初始化提交把两类混在一起、git log -S查不出来,这是唯一能区分它们的办法,而且覆盖度比想象的高(实测 2026-10-25):bashcd ainb git grep -l '@author 芋道源码' -- '*.java' | wc -l # 636 git grep -l '@author 走进新时代' -- '*.java' | wc -l # 975🔴 也就是说后端 Java 文件里本项目自己写的比上游带来的还多(975 : 636)。别默认「后端基本是上游的、我只改了几个点」——那个印象是错的,改动的影响面比它暗示的大得多。
本手册的定制功能页首会标
::: tip 定制功能。
docs/ 目录:三仓的情况各不相同,而且它和知识库的关系已经变了
- 后端与 Web 都没有
docs/。代码注释里引用的docs/*.md已不存在,别去找。 - 移动端有
docs/,但那是 unibest 原版站点(讲模板工程本身、含上游打赏与案例页),既不是本平台文档、也没有guide/develop子目录。属遗留,不要顺手删(不可逆)。 - 本手册站点在独立工程
xpy-blog,与三仓无派生关系。
🔴 后端 AgentDocProperties(enlev.agent.doc.*)早已不做向上探测了。第一版靠标记 ainb-ui-admin-vue3/docs 逐级向上找、因目录不存在而永久空转;现在改成 root-path 必填:留空抛 AGENT_DOC_ROOT_PATH_MISSING,不是目录抛 AGENT_DOC_ROOT_PATH_INVALID。
🔴 而 c3dccc1a82 refactor(系统):区分专业版和开源版 曾把三个 profile 的 root-path 全配成 /(application-{local,dev,prod}.yaml——同一次提交还删掉了那段解释「为什么必填、空转长什么样」的注释,于是配置里再没有任何线索说明 / 是错的)。/ 能通过校验(Windows 下解析成盘根、确实是目录),只是默认的 directories = ["guide","develop"] 在它下面不存在 → planSync 每个子目录各记一条「子目录不存在,已跳过」warning、语料扫出 0 篇,接口照样返回 200。✅ 已修:现在三个 profile 都走环境变量 ${AGENT_DOC_ROOT_PATH:...},local 默认指到本机手册仓库,dev/prod 默认留空 ⟹ 没配就抛 AGENT_DOC_ROOT_PATH_MISSING,不再静默扫 /。
后果有两层:库里那 115 篇(root-path 还正确时灌进去的)曾冻结——本轮新增的页灌不进去(改完配置仍需重跑一次同步才真的更新)。第二层原本严重得多:REMOVED 的判据是「库里有、这次没扫到」,右项一旦为空,左项就整个变成待删清单,重跑一次 doc-sync 会把这 115 篇连段落带向量全部删掉。✅ 这条已由 4093e19c7d 在代码里堵死(扫到 0 篇即中止同步、连知识库都不建,并把「拦下了几篇的删除」写进 failures),所以配错 root-path 的最坏后果现在是「同步报错、什么都没做」。但只缺一部分子目录时刻意不拦删除——拦了就等于「真的删掉整个目录」永远传播不到库里,所以「先 doc-sync-plan 看 removedCount 与 warning,再 sync」这一步不能省。完整机制与四段演变见架构总览 · 坑与注意第 1 条。
相关页面
- 后端架构与模块依赖:14 个 starter 与模块依赖方向;
- 前端工程结构:Web 端
src/各目录与自动导入机制; - 工程结构与约定式路由:移动端
src/各目录、分包划分与 easycom 规则; - system 域开发:
system_nav_node这张自研表为什么挂在system_前缀下; - 坑位索引:本项目已知坑位汇总。
