UG Admin 架构

产品与后端边界

UG Admin 提供类似 RuoYi 的前端管理工作流:登录、导航、权限、用户、部门、角色和字典。React 与 Vue 是可独立选用的原生实现;UG UI 提供公共组件。模板不捆绑后端,不预建行业业务、多租户或插件平台。

Go、Java、Python、Rust、Node、PHP、Next.js 等后端通过公开 API 接入。区别在协议、字段和业务语义,不在语言:不按语言复制七套适配器,也不要求已有后端改成模板的默认 URL。

src/modules/<业务>/页面、查询与操作
                 ↓
模块 api / 系统管理 helper(业务模型与动作)
                 ↓
Auth / Data / Access 合同 ← src/backend 项目适配
                 ↓
后端公开 API(身份、最终授权、数据范围、事务与存储)

DataProvider 是列表和常见 CRUD 的复用工具,不是所有业务必须实现的框架。模块可以通过 custom 承载审批、导出等动作,也可以使用 HTTP 客户端封装自己的类型化 API;不要为每个行业动作给核心增加方法。具体 DTO、状态码、路径和凭据流程留在项目适配或模块 API 中。后端最终授权不能由前端菜单和按钮显隐替代。

维护仓库与新项目

维护仓库同时保存双端,以相同行为合同维护:

apps/react/src/              React 原生实现
apps/vue/src/                Vue 原生实现
apps/docs/                   独立开发者文档站,读取 docs Markdown
packages/admin-core/      框架无关合同及系统管理模型
packages/admin-react/     React 页面壳与组合模式
packages/admin-vue/       Vue 页面壳与组合模式
scripts/scaffold/           单端创建器、业务生成器、固定配置模板
tests/e2e/                  双端浏览器场景

创建器按明确目录边界复制所选端、系统模块及共享源码;不解析 import 图、不正则重写应用源码、不维护第二份内置页面 loader 映射。Vite 和项目初始化采用 scripts/scaffold/templates/ 的固定模板。输出是独立项目:

src/
  app/                     框架装配、路由、认证、查询缓存、主题
  backend/                 项目协议与认证适配
  modules/
    system/                用户、部门、角色、字典、首页、个人资料、设置
    orders/                新业务自己的 index.ts、api.ts、页面
  layout/                  壳层组合
  patterns/                原生页面组合模式
  pages/                   登录、加载、404 与错误页
  project.ts               品牌和提供者注入
shared/                    对应维护仓库的 admin-shared
public/
scripts/                   业务生成器和架构检查
vendor/                    固定版本的 UI 分发包
package.json               所选端依赖与 ugAdmin 模板版本

消费项目没有 apps/ 或另一框架的依赖,不带仓库测试和学习页面。当前兼容 UI 使用已安装构建包的分发快照,保留许可与锁文件;不依赖相邻仓库源码链接。升级采用源码差异合并,保留项目配置、适配和业务模块,不重新生成覆盖项目。

资源生成器默认只输出列表,--crud 可生成原生列表、详情和新增/编辑源码;所有输出位于所选模块,不新增核心注册表或运行时配置引擎。创建器携带接口、部署说明和配置示例;前端接口位置见 接入合同,部署见 部署指南。

开发者文档是独立的第一方站点,用 Astro 生成静态页面、UG UI 与 React 仅用于交互孤岛,展示两端共用的使用说明;不需要把同一文档站重复实现为 Vue。正文以 docs/*.md 为事实源,构建时用 marked 转换并改写站内链接;它不依赖后台认证、业务模块或真实服务,不参与 React/Vue 后台生产包,也不会被创建器复制到新项目。端口从 3400 起排:文档站 3400,React 3401,Vue 3402;新增框架应用依次往后追加,文档站始终是第一个入口。静态输出在 apps/docs/dist。

运行时状态

框架配置(品牌、模块开关、页面表、语言、表格形态)是进程内单实例状态,用就地更新的方式保存:

  • 不再导出可重新赋值的绑定,引用方拿到的对象始终同一实例;
  • resetAdminRuntime() 复位到默认值,测试与嵌入式场景用它隔离;
  • 同一时刻只能有一份配置,因此不支持同页面并发两个后台实例;需要并行实例时改成显式传入的运行时上下文。

开发期应用代码一律引用子路径(解析到源码);包根入口只给按版本安装的外部消费方。

业务模块内部职责

src/modules/<模块>/ 不再平铺,按职责分目录(system/management、system/dictionaries 已按此整理):

目录 放什么
pages/ 路由页组件,一个文件一个页面
editors/ 新增/编辑表单容器
fields/ 字段级组件与校验展示
actions/ 工具栏、行操作、状态开关、列设置
dialogs/ 详情抽屉、确认与批量弹窗
filters/ 查询摘要、搜索表单、范围筛选
hooks/ 数据与视图状态 Hook
lib/ 纯函数、契约映射、校验规则

模块对外只经 index.ts(装配)与 api.ts(稳定能力)暴露,跨模块不直接引用内部文件。

app 目录职责

apps/<框架>/src/app 按职责分目录,不再平铺:

目录 放什么
providers/ access、auth、data、theme 四类 Provider 装配
routing/ router、route-components、navigation、RouteBreadcrumbs、modules(模块注册与发现)、module-types
guards/ RequireAuth、PageActionGate、PermissionGate
layout/ 页签与主题配置等壳层组件
shared/ 跨模块共用的对话框与卡片列表
i18n/ 语言与文案 hook

routing/modules.ts 用 import.meta.glob("../../modules/*/index.ts") 发现模块;移动该文件时 glob 相对路径必须同步,否则模块不会加载。

模块注册

页面表只有一个来源:各端 src/modules/*/index.ts 的 AdminModule 由 app/routing/modules.ts 统一发现并组装。

  • configureAdminModules / configureAdminPages / configureAdminPermissions 只允许出现在 apps/*/src/app/routing/modules.ts,由 check:architecture 强制;测试可例外。
  • 新增页面 = 新增或修改模块入口,不改应用装配;删除页面 = 同时删路由、权限键、测试与文档(remove:module 有体检)。

模块入口与依赖方向

系统模块、示例模块、新业务使用同一 AdminModule 合同,没有内置页面枚举和单独的业务路由旁路:

  • id 是唯一模块名,enabled 是启动开关。
  • pages 登记一个或多个页面的元数据、原生懒加载函数、可选图标。
  • permissions 提供可分配权限目录;登记不授予当前账户权限。
  • decorateData 用于模块自己的写后行为;系统管理的身份复核由 system 提供。
  • createMockData 仅用于开发装配,生产入口不登记模拟数据。

src/app/routing/modules.ts 自动收集 modules/*/index.ts。路由、导航、搜索、面包屑和页签使用组合后的同一目录;ID、路径、别名冲突和父级循环在启动时报错。关闭模块同时退出菜单与路由,旧页签不能恢复。没有插件生命周期、事件总线或服务容器。

依赖以职责为准,不能靠移动目录名称绕过边界:

层 可以依赖 不可以依赖
共享核心 auth/config/data/routing 核心合同与平台无关公共依赖 任意系统或行业业务、演示数据、应用与框架运行时
应用基础层 app/layout/patterns/pages 共享核心、UG UI、框架适配;开发装配可用通用内存 Provider 具体模块页面、系统或行业模型、业务写后规则
modules/<名称> 基础层服务、共享合同、本模块实现、其他模块显式公开 API 其他模块内部页面、组件和查询实现
backend 与模块 API HTTP 客户端、Provider 合同、对应业务模型 将项目协议和服务端职责反写入框架核心或 UG UI

模块发现是装配入口:app/routing/modules.ts 通过 glob 收集模块入口,不手写具体业务 import。模块之间确有复用需求时,仅通过目标模块的 api.ts 显式导出稳定能力,内部可以重组;不要导入目标 index.ts 装配入口,也不为尚无消费者的模块预建出口。API 依赖不代表自动开启目标模块,调用方仍需处理权限与可用性。

check:architecture 检查静态 import/export、类型引用和字面量动态 import 的上述核心/基础层/模块边界,并检查跨端依赖、失效引用和循环依赖;模板与生成的单端项目使用同一检查器。测试可以直接覆盖内部实现;glob、动态拼接路径和 CSS 是否滥用公开 API 仍需按实际调用人工核对,不能用检查通过代替设计审查。

系统页面元数据在 management/pages.ts,双端 loader 和实现留在各自 modules/system/;系统管理的模型、权限、查询 helper、身份复核在 management/,字典合同在 dictionaries/。核心首页仅展示有权查看的少量统计,不增加复杂分析。个人中心从头像菜单进入。可选学习页面属于 modules/examples/,示例概览、账户、设置分别在 /examples/overview、/examples/profile、/examples/settings,不替换系统页面。

后端适配与状态

project.ts 在挂载前设置品牌并注入 backend/index.ts 的 auth/data/access。未指定部分使用默认实现;生产默认 HTTP,本地默认演示,生产禁止 demo/mock 模式。默认 REST 请求可用 mapRequest 转换方法、URL、查询、载荷,responseMappers 转换返回模型,mapError 转换包括 HTTP 200 在内的业务失败。HTTP Auth 支持端点、登录字段、会话和错误映射,复杂流程可替换 Provider。完整合同见 后端接入。

请求保留取消信号。401 清理会话与查询缓存,403 保持真实拒绝,422 保留字段错误,写操作不自动重试。切换账户清缓存;同一身份权限更新重置活跃查询。系统管理写入影响当前身份时由 management/session.ts 复核会话,失败清身份但不把已成功写入伪装成失败。外部撤权、定制业务动作和实时推送由实际项目接入。

React/Vue 各自使用 TanStack Query 原生适配,组件和 Hook/composable 不跨框架共享。品牌和本地存储使用 storageNamespace 隔离。真实后端仍需实现资源关系约束与授权;演示数据不等同于服务端实现。

UI 与验证

UGAdminShell 负责全局壳层,secondaryBar 承载页签;页面通过 Patterns 和 UG UI 组合。Admin CSS 只负责业务布局,不穿透公共组件内部结构。稳定身份、权限与字典规则分别见 身份体系、管理合同、字典合同。

按实际影响运行类型检查、相关现有测试、架构检查和构建。新增业务不要求新增测试文件或修改双端核心;公共合同变化才同步双端。构建、模拟 API 验证、真实服务联调和用户界面验收分别报告,不能互相替代。

新增业务先留在所选端模块;只有真实复用且语义稳定时才提取。纯模型和算法进入对应共享职责目录,框架组件与查询状态留在原生端,公共 UI 能力回 ug-ui。目录数量、代码行数和双端外形相似本身都不是增加抽象层的理由。

架构不变量

只保留“改了就一定出事”的硬约束,其余属于维护规范,见下一节。

不变量 含义 现状
依赖单向 ug-ui ← admin-core ← admin-react / admin-vue ← 应用与消费方;核心不依赖框架,两个渲染层互不引用 已由 check:architecture 守住
单一事实源 业务页面实现只在一处;消费方通过依赖引用,不复制源码 已由 check:copies(消费方)与模块契约守住
双端原生 React 与 Vue 各自原生实现,同名页面 + 同一份核心逻辑 已由 check:parity 守住文件层

维护规范(开发期与发布期分开)

开发期与发布期用不同的解析方式,二者不混用:

场景 依赖写法 产物解析
本地开发(模板仓库、消费方联调) file: 直连源码,改完刷新即生效 Vite/框架直接编译 TS 与 Vue 文件
发布(对外版本) 版本号依赖 发布流程先构建 dist,再把入口指向产物

因此“子路径当前指向源码”是开发期的正确状态,不是缺陷。发布时必须执行构建与入口切换,这条写进 docs/RELEASE.md。

架构债务处理状态

原表中的八项已全部收口,门禁固化在 bun run check 里,防止回流:

编号 问题 处理
A1 页面描述声明了动作却没人消费 动作改为 resource/action 合同并由双端工具栏消费,描述缺动作即不渲染;角色/部门/字典描述先移除未接线的动作
A2 包被按文件子路径随意引用 exports 改为按目录白名单,check:exports 拦截未声明子路径
A3 双端能力写死在脚本里 configs/frameworks.json 登记各端,检查、脚手架与构建统一读取
B1 核心用模块级可变单例 改为就地更新的单实例状态,新增 resetAdminRuntime();单实例限制写进文档
C1 模块注册双通道 页面表只由模块入口发现产生,configure* 仅允许出现在 app/routing/modules.ts(check:architecture 强制)
D1 样式无作用域、直连 UG token 类名限定 admin- 前缀,UG token 只在 app.css 映射为 --admin-* 语义变量(check:styles 强制)
E1 权限码是裸字符串 check:permissions 校验页面引用的权限码都在目录中,拼错即失败
F1 文档站与效果预览耦合 /demo/ 为独立页面并自带错误边界,UG_DOCS_PREVIEW=false 可整体关闭预览

新增触发条件:任一包新增导出目录、新增渲染层、改动权限目录或样式变量层时,先看本节对应门禁,不要绕过检查改实现。