Skip to content

仓库结构与改名约定 ​

适用端:后端 + Web + 移动端 | 类型:参考

三个仓库的目录布局、构件命名规律,以及「从芋道改名过来」这件事在代码里留下了什么痕迹。看懂这一页,才能判断一段代码是上游自带的还是本项目定制的。

前置阅读:环境准备与启动。

本页只讲「三仓共同的规律」

移动端仓库自己的目录树、分包划分、easycom 规则不在这里重复一遍——那是工程结构与约定式路由的内容。本页下面两棵树是后端与 Web 的,移动端那棵去那一页看。

它解决什么问题 ​

本项目是芋道的改名分支,不是从零写的。三仓的直接上游各不相同:

仓库直接上游上游的上游
ainb(后端)ruoyi-vue-pro—
ainb-ui-admin-vue3(Web)yudao-ui-admin-vue3—
ainb-ui-admin-uniapp(移动端)yudao-ui-admin-uniappunibest(工程骨架)

开发者最常犯的三类错都源于没搞清这一点:

代码在哪 ​

仓库分支提交数(2026-10-25 实测)本项目自己的提交内容
C:\code\xpy\ainbmaster2626(全部)后端,Maven 多模块单体
C:\code\xpy\ainb-ui-admin-vue3master5821大部分后台前端,Vue 3 + Vite
C:\code\xpy\ainb-ui-admin-uniappmaster242623(git log --author=zhechu)移动端,uni-app 3 + Vue 3.4

C:\code\xpy 本身不是 git 仓库,三个子目录各自独立。跨端改动要提交三次,执行任何 git 命令前先 cd 进对应子目录。

⚠️ 提交数那一列是活动值,每提交一次就变,别当常量抄——本页写下的当天就错过一次(Web 仓因为一个注释修复多了一个 commit,5820 当场过期)。要的是量级对比(后端二十几个 vs 另两仓数千个)与「哪一仓必须按作者过滤」。复核:

bash
# 在 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.yudaocn.enlev.ainb
Maven groupIdcn.iocoder.cloud / cn.iocoder.bootcn.enlev.ainb
构件前缀yudao-enlev-
配置前缀yudao.*enlev.*
Web 包名yudao-ui-admin-vue3ainb-ui-admin-vue3
移动端包名yudao-ui-admin-uniappainb-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 个文件且无收益。目录名不代表归属变更,别按它推断代码来源。详见工程结构与约定式路由。

包名靠一个配置项统一驱动,改名时不需要动代码:

yaml
# 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/*.yamlyudao 残留文件(代码类)残留文件(全仓,含 md/图片名)
ainb01217
ainb-ui-admin-vue302026
ainb-ui-admin-uniapp06267
bash
# 在每个仓库根目录跑;三列分别对应上表
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 链接或归属声明,例如:

java
// 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_IDcn.enlev.ainbcn.enlev.ainb
ARTIFACT_IDenlevenlev
PACKAGE_NAMEcn.enlev.ainbcn.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 / shell

reactor 里只有 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。

配套两处必须一起看:

  1. vite.config.ts 里那组 @form-create/* alias 顺序敏感(长前缀必须排在短前缀之前,注释原文已说明 Rollup alias 的匹配规则);
  2. .gitignore 里 auto-*.d.ts 会误伤 vendored 的类型声明,所以紧跟一行例外:
ini
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 定制」 ​

按可靠度从高到低:

  1. 查 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 或芋道的提交上去。
  2. 看目录归属: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/ 是自研渲染器。

  3. 看表前缀:sys_*(7 张,权限体系)与 agent_*(16 张)是定制的,system_* / infra_* / bpm_* / ai_* 是上游命名。🔴 有一个例外:system_nav_node(分端导航树)是本项目自研的,却挂在 system_ 前缀下(NavNodeDO 的 @TableName)——按前缀判断会把它误判成上游表。同类例外还有 infra_form_design.app_nav_node_id 这一列。

  4. 看配置项:enlev.permission.grant-enabled / collapse-enabled、enlev.data-model(SqlSafeGuard)、agent.*、enlev.agent.doc.* 都是本项目新增。

  5. 看 Java 注释的 @author:「芋道源码」是上游,「走进新时代」是本项目。后端初始化提交把两类混在一起、git log -S 查不出来,这是唯一能区分它们的办法,而且覆盖度比想象的高(实测 2026-10-25):

    bash
    cd ainb
    git grep -l '@author 芋道源码'   -- '*.java' | wc -l   # 636
    git grep -l '@author 走进新时代' -- '*.java' | wc -l   # 975

    🔴 也就是说后端 Java 文件里本项目自己写的比上游带来的还多(975 : 636)。别默认「后端基本是上游的、我只改了几个点」——那个印象是错的,改动的影响面比它暗示的大得多。

  6. 本手册的定制功能页首会标 ::: 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 条。

相关页面 ​