项目:nestjs-admin | 版本:v1.0 | 生成日期:2026-10-06 | 依据:源码分析
nestjs-admin 是一套前后端完全分离的企业级后台管理系统(若依/RuoYi 风格),由两个子项目组成,同仓维护于 nestjs-admin/ 目录下:
nestjs-microservice-app/:基于 NestJS 12 的微服务后端。它不是一个单体应用,而是一个 pnpm workspace 多包仓库(monorepo),内含 1 个 HTTP 网关(gateway)、2 个 gRPC 业务微服务(user、system)、1 个健康检查聚合服务(health)、1 个共享契约包(proto-pkg)和 1 个新服务脚手架(template)。服务注册与发现基于 Consul(本仓库 bin/consul 附带 v2.0.3 darwin-arm64 二进制),服务间通信走 gRPC(可选 TLS,自签证书随 proto-pkg/certs 分发)。vue-admin/:基于 vue-pure-admin 完整版深度改造的 Vue 3 管理端(package.json 中 name 仍为 vue-element-plus-admin,version 7.0.0)。登录、动态菜单、按钮权限码、用户/角色/菜单/部门/字典/日志/通知等管理页面全部对接真实网关接口,模板中的 mock 体系与假数据已被清除。按 nestjs-microservice-app/HANDOFF.md 的定位,这是一个"教学/演示级、但按生产思路实现"的项目:它完整落地了微服务拆分、注册中心容灾、双 token 鉴权、CASL 动态权限、操作/登录/系统三级日志、对象存储上传、Docker 化部署等工程实践,并且已实际部署到公网(HANDOFF §4.5:https://admin-vue.zhangzhengyang.com,NAS 多容器 + frp + VPS nginx 反代链路)。
目标用户与适用场景:
| 读者 | 用途 |
|---|---|
| 后端接手者 | 理解微服务拓扑、Consul 发现、gRPC 契约、权限模型,独立维护/扩展服务 |
| 前端接手者 | 理解 pure-admin 改造点、动态路由与权限码映射、API 封装约定 |
| 运维/部署者 | 本地一键起栈(dev-start.sh)、Docker 编排、生产反代链路 |
| 教学/二开者 | 以 template/ 为脚手架复制出新微服务,套用既有注册/发现/鉴权模式 |
演示账号(来自 HANDOFF 与登录页 vue-admin/src/views/login/index.vue 中写死的 TEST_ACCOUNT):p1user / secret123(主演示账号,已授权全部菜单;且后端 user/src/app.controller.ts 中对该账号写死"测试账号不可以修改密码"保护)、p0user / secret123、生产管理员 prodadmin(HANDOFF §4.5)。
从 nestjs-microservice-app/HANDOFF.md 记录的里程碑可以还原出这个项目的完整成长史,理解这条演进线索对接手者非常有价值——今天代码里的很多"为什么"都藏在这段历史里。项目最初是一个 NestJS 单体教学工程(user 服务的前身),随后经历了一次大规模依赖升级:NestJS 10 升 12、Prisma 5 升 7、Node 18 升 22/24,同时做了一批 P0 到 P4 级别的安全与质量修复——包括密码从明文改为 argon2 哈希、全局参数校验管道、JWT 认证闭环、统一异常过滤器、strict 类型检查全开、带 traceId 的日志体系,以及 TLS 加密与 CI 流水线。官方 consul npm 包在此期间被官方弃用,于是有了 proto-pkg 里那个基于 Node 内置 fetch 的 ConsulHttpClient 自研薄封装。
之后的功能迭代按里程碑推进:M1 引入 system 微服务与菜单管理全链路,并把动态权限码合并进 CASL 能力体系;M2 加入部门管理、数据字典与操作/登录日志(由网关中间件自动采集);M3 完成双端 gRPC TLS 实测、Swagger 与容器编排;M5/M6 补齐用户管理完整 CRUD 与个人中心(资料读写、头像上传、改密码、登出、在线用户强退、字典改删);M7 做了文件上传迁移到 MinIO 对象存储、菜单 iframe/外链支持、在线强退真实化(后又于 2026-09-10 把 Redis 图形验证码整体移除,Redis 依赖随之删除);M7.5 把"假 refresh"做成真实的双 token 体系;M8/M9 依据前端交接清单补齐了管理员重置密码、改他人头像、日志删除清空、通知消息待办、仪表盘统计、日志多维筛选、偏好设置与密保信息等一批缺口。2026-09-11 是一个密集的缺陷修复日,连续五排排查修复了"7 天免登录实为 24 小时"、"登录日志管理页因取值错误恒为空表"、"系统日志页无菜单入口也无数据源"、"全局菜单搜索弹窗图标渲染成 412×412 撑爆布局"等一批真实缺陷,并把登录/操作日志改造为服务端真分页、路由切换为 history 模式、接入 Umami 访问统计,最终完成公网生产发布。理解这些背景后你会发现:代码里大量防御性注释、try/catch 兜底与"特性化"的限流配置,都是这一路踩坑的沉淀。
值得强调的是,这个仓库实际上同时承载了三类读者的关注点,并且刻意让它们互不污染。第一类是业务功能:用户/角色/菜单/部门/字典五大管理域与日志/通知/仪表盘三个辅助域,全部走"前端页面 → 网关路由 → gRPC 微服务 → Prisma → PostgreSQL"的标准链路,任何新功能都建议沿用这条路径。第二类是平台能力:注册发现、健康检查、鉴权授权、限流、日志采集、异常映射、对象存储,这些能力集中在网关与 proto-pkg 中,业务服务基本不感知——比如 user 服务完全不知道自己会被 CASL 消费,它只负责按 proto 契约应答。第三类是工程示范:template/ 脚手架展示了"如何新增一个微服务",vue-admin 中保留的 pure-admin 演示页展示了"组件怎么用",二者与业务代码物理隔离(template 不参与生产构建,演示页不接业务接口)。接手者的改动几乎总落在第一类;改第二类时要跑全量单测与全链路冒烟;第三类可以随意删除而不影响业务。
以下功能均可在源码中定位到具体实现文件:
| 功能域 | 功能点 | 后端入口(网关路由) | 前端页面 |
|---|---|---|---|
| 认证会话 | 账号密码登录(双 token:access 2h + refresh 7d) | POST /api/auth/signin(gateway/src/auth/auth.controller.ts) | 登录页(含"一键测试登录") |
| 认证会话 | 注册(独立限流 5/min,argon2id 哈希落库) | POST /api/auth/signup | 登录页注册 Tab(views/login/components/LoginRegist.vue) |
| 认证会话 | 无感刷新 access token(会话内换发) | POST /api/auth/refresh(限流 30/min) | src/utils/http/index.ts 请求拦截器自动触发 |
| 认证会话 | 登出 / 在线会话强退(access+refresh 同时失效) | POST /api/auth/logout、DELETE /api/monitor/online-users/:sessionId | 导航栏下拉 / views/monitor/online |
| 个人中心 | 资料读写、改密、头像上传(MinIO)、偏好设置、密保信息 | `GET | PUT /api/auth/profile、PUT /api/auth/password、POST /api/auth/avatar、GET |
| 用户管理 | 分页列表(用户名/部门/手机号过滤)、增删改、分配角色、启停、管理员重置密码/改头像 | gateway/src/system/user.controller.ts(/api/system/users*) | views/system/user/ |
| 角色管理 | 角色增删改查、角色-菜单授权(权限码动态生效) | /api/system/roles*、/api/system/role-menus/:roleId(rolemenu.controller.ts) | views/system/role/ |
| 菜单管理 | 树形菜单 CRUD(目录/菜单/按钮/iframe/外链五类) | /api/system/menus*、GET /api/system/my-menus(system.controller.ts) | views/system/menu/ + 侧边栏动态渲染 |
| 部门管理 | 树形组织 CRUD(删前校验子部门/在编用户) | /api/system/depts*(dept.controller.ts → user 服务) | views/system/dept/ |
| 数据字典 | 字典类型/字典项 CRUD | /api/system/dicts*(dict.controller.ts) | views/system/dict/index.vue |
| 日志监控 | 操作日志(中间件自动采集,真分页+多维筛选+详情/批量删/清空) | GET /api/monitor/oper-logs*(monitor.controller.ts) | views/monitor/logs/operation/ |
| 日志监控 | 登录日志(signin/signup 自动落库,分页+筛选) | GET /api/monitor/login-logs* | views/monitor/logs/login/ |
| 日志监控 | 系统运行日志(网关 5xx 自动落库) | GET /api/monitor/system-logs | views/monitor/logs/system/ |
| 日志监控 | 在线用户(网关内存会话表)+ 强退 | GET /api/monitor/online-users、DELETE .../:sessionId | views/monitor/online/ |
| 通知消息 | 顶栏铃铛三 Tab(通知/消息/待办)、单条/按类全部已读 | `GET | POST /api/system/notices、PUT .../read |
| 仪表盘 | 7 项统计(用户/部门/角色/菜单/操作日志/登录日志/在线数,跨两服务聚合) | GET /api/system/dashboard-stats(dashboard.controller.ts) | views/welcome/index.vue |
| 基础设施 | 文件上传与回读(MinIO S3 兼容对象存储) | POST /api/auth/avatar、GET /uploads/:key(gateway/src/common/upload/) | 头像裁剪上传(ReCropper) |
| 运维可观测 | 服务健康聚合(terminus 主动 gRPC 探测)、Swagger 文档、限流 | GET :3030/api/v1/health?service=xxx、GET :3040/api/docs(非生产) | — |
模板遗留的演示页面(表格/组件/流图/水印等 views/table、views/able、views/components 等)仍保留,属于 vue-pure-admin 基座自带内容,与业务后端无接口耦合;部分演示页已在 M7 清 mock 时改为本地静态数据(HANDOFF 待办 5)。
逐行读这张功能表可以发现几个贯穿性的设计事实。第一,所有写操作都是"权限码先行"的:前端按权限码决定按钮显隐,网关按同一个权限码做服务端兜底,而权限码本身是 system 库菜单表里的一条 type=2 记录、经角色授权动态生效——这意味着"加一个按钮"是纯数据操作(插菜单记录加授权),不需要改网关代码。第二,日志是零侵入的:业务代码里没有任何一行"记日志"的调用,操作日志由网关中间件在响应结束后统一采集,登录日志按路径规则采集,系统日志由异常过滤器采集,三者都遵循"尽力而为、失败不影响业务"的旁路原则。第三,跨域聚合只发生在网关:仪表盘的七项统计来自 user、system 两个 gRPC 调用与网关自身的内存会话计数,dashboard.controller.ts 负责拼装;两业务服务之间从不互相调用,这让它们的依赖图始终是一棵以网关为根的树,排障时拓扑清晰。第四,功能演进高度依赖 proto 契约的向后兼容:从 M6 到 M9 的每一次加功能,动作序列都是"改 proto → 重生成产物 → 服务端补 RPC → 网关透传 → 前端接线 → seed 补权限码",这个固定节奏在 03 篇还有详细展开。
nestjs-microservice-app/)| 技术 | 版本依据 | 选型理由(源码/文档佐证) |
|---|---|---|
| NestJS | ^12.0.0(gateway/package.json 等) | 模块化 DI 框架,原生支持微服务传输层(@nestjs/microservices 的 Transport.GRPC)与统一管道/守卫/拦截器体系 |
| gRPC | @grpc/grpc-js ^1.14.4 + @grpc/proto-loader | 网关与业务服务间高性能强类型通信;proto 契约集中放在 proto-pkg/proto/(user.proto/system.proto/health.proto),杜绝双侧手写不一致 |
| Consul | 二进制 v2.0.3(bin/consul);客户端为自研 ConsulHttpClient | 服务注册发现 + HTTP 健康检查 + 60s 自动注销失效实例;HANDOFF 明确因官方 consul npm 包弃用而自研 fetch 薄封装(proto-pkg/src/utils/consul-http.ts 头注释) |
| PostgreSQL 15/16 | docker-compose 中 postgres:16-alpine;开发机为 Homebrew PG 15 | 关系型主存储;按领域拆 user_db 与 system_db 两个库,实现用户域与系统域数据隔离 |
| Prisma ORM | 7.x(新 prisma-client generator + PrismaPg 驱动适配器,见 user/prisma/schema.prisma 与 user/src/database/database.service.ts) | 类型安全的数据访问;迁移文件随仓库提交(user/prisma/migrations/、system/prisma/migrations/),保证新环境可重放 |
| JWT / Passport | @nestjs/jwt ^12、passport-jwt ^4 | 无状态双 token(access 2h / refresh 7d,均带 jwtid: randomUUID() 防同秒撞签,gateway/src/auth/auth.service.ts) |
| CASL | @casl/ability ^6.8.1(v6 因 v7 与 node10 moduleResolution 不兼容,HANDOFF 陷阱 8) | 把"角色→策略→权限码"编译为 Ability 规则集,网关统一 PoliciesGuard 校验 |
| argon2 | ^0.45.1 | 密码哈希(argon2id),登录侧 argon2.verify,写入侧 argon2.hash |
| MinIO | minio ^8.0.7 | S3 兼容对象存储存头像,网关 /uploads/:key 流式回读,前端零改动 |
| @nestjs/throttler | 6.5.0 | 全局 100/min,signin 10/min、signup 5/min、refresh 30/min 分口收紧(gateway/src/app.module.ts、auth.controller 各 @Throttle) |
| @nestjs/terminus | ^12.0.0 | GRPCHealthIndicator.checkService 实现"HTTP 端点 → gRPC 健康探测"的聚合健康检查 |
| helmet / Swagger | helmet ^8、@nestjs/swagger 12.0.1 | 安全响应头;/api/docs 文档仅非生产暴露(gateway/src/main.ts 的 swaggerEnabled 逻辑) |
| Jest + ts-jest | jest ^30(需 Node 24.9+,NODE_OPTIONS=--experimental-vm-modules,HANDOFF 陷阱 15/21) | 网关 26 + system 7 共 33 个单测,e2e 4 例(CI 中跑) |
vue-admin/)| 技术 | 版本依据 | 选型理由 |
|---|---|---|
| Vue | ^3.5.41 | Composition API + <script setup> |
| Vite | ^8.2.2(rolldown-vite 产物,vite.config.ts 使用 rolldownOptions 分包) | 极速冷启/HMR;构建产物按 vue-vendor/element-plus/echarts/vxe-table 四组 advancedChunks 拆分 |
| Element Plus | ^2.14.5 | 管理端组件基座;图标方案为 Element Plus Icons + Remixicon 双轨(src/components/ReIcon/RiIcon.ts) |
| Pinia | ^4.0.3 | 状态管理(user/permission/app/epTheme/multiTags/settings 六个模块,src/store/modules/) |
| vue-router | ^5.2.0 | history(h5)模式(三个 .env.* 均 VITE_ROUTER_HISTORY="h5"),动态路由由后端 my-menus 驱动 |
| TypeScript | ^6.0.3 + vue-tsc | 全量 TS,pnpm typecheck 作为质量门禁 |
| Tailwind CSS | ^4.3.3(@tailwindcss/vite) | 与 Element Plus 共存做原子化布局(src/style/tailwind.css、tokens.css 设计令牌) |
| axios | 1.18.1 | 统一封装 src/utils/http/index.ts(token 注入、无感刷新请求队列、401 全局登出) |
| pure-admin 生态 | @pureadmin/table、@pureadmin/utils、@pureadmin/descriptions | 表格栏(PureTableBar)、工具函数库等基座能力 |
| 其他 | echarts 6、vxe-table、dayjs、js-cookie、nprogress、mitt、localforage、cropperjs 等 | 图表/高级表格/本地缓存/进度条/事件总线/IndexedDB/头像裁剪 |
pnpm-workspace.yaml);后端 package.json 用 pnpm.onlyBuiltDependencies 显式放行 argon2/swc/esbuild/grpc-tools/prisma 等原生构建包;前端 preinstall 钩子强制 only-allow pnpm。nestjs-microservice-app/.github/workflows/ci.yml —— 起 Consul 1.21 + Postgres 16 容器,跑 pnpm -r build(strict 类型检查)、prisma migrate deploy、gateway 单测、health 启动后跑 e2e。Dockerfile(node:22-bookworm-slim,4 服务共用,compose command 决定跑谁)与参数化 Dockerfile.service(node:24-alpine,--build-arg SERVICE=);docker-compose.full.yml 一键编排 consul+postgres+4 服务;前端 vue-admin/Dockerfile 与生产反代配置 admin-vue-deploy/nginx-admin.conf。关于技术栈选型还有几点值得展开的"为什么"。微服务通信选 gRPC 而非 HTTP/REST 内部调用,除了性能与强类型契约之外,还有一个很实际的原因:NestJS 的 @nestjs/microservices 对 gRPC 提供了一等公民支持——@GrpcMethod 装饰器让业务服务的代码形态与 HTTP 控制器几乎一致,迁移成本极低;而 proto 文件天然就是接口文档,proto-pkg 把契约、加载工具、Consul 客户端、TLS 证书打包成一个 workspace 内共享包,两端永远引用同一份产物,从机制上杜绝了"接口文档与实现不一致"这一微服务最常见的老大难问题。选 Prisma 7 的新 prisma-client generator,是因为项目紧跟 Prisma 7 的驱动适配器架构(PrismaPg),这带来两个直接好处:连接串不再写死在 schema 里(由 prisma.config.ts 与运行时代码分别注入,便于 CLI 迁移与运行时指向不同环境),且生成物落在本地目录 src/generated/prisma-client,模型类型与客户端同仓可见、IDE 跳转友好。前端坚持完整版 pure-admin 而不是精简版或自研脚手架,看重的是它开箱即用的布局体系(三种侧边栏形态、多标签页、全局搜索、设置抽屉)、PureTableBar 加 hook 的页面组织模式与成熟的动态路由方案——本项目所有业务页都复用了这套骨架,业务代码只写"数据怎么来、操作怎么发",交互细节零成本。至于两端都用 pnpm,除了速度与磁盘节省,后端还依赖 workspace 协议("@toimc/proto-pkg": "workspace:*")实现包内联引用,前端则靠 preinstall 钩子把包管理器锁死,避免贡献者混用 npm/yaml 导致锁文件漂移。
nestjs-microservice-app/nestjs-microservice-app/
├── package.json # workspace 根:dev:user/dev:system/dev:gateway/dev:health 过滤脚本
├── pnpm-workspace.yaml # pnpm monorepo 声明
├── dev-start.sh # 本地一键启动(consul×2 → health → user → system → 等绿 → gateway)
├── dev-secrets.env # NAS MinIO/Redis 凭据(gitignore,dev-start.sh 自动 source)
├── .env.example # 全量环境变量模板(JWT/Consul/DB/TLS/健康端点)
├── docker-compose.full.yml # 全栈编排:consul+postgres+migrate 迁移任务+4 服务
├── Dockerfile # monorepo 单镜像(4 服务共用,生产采用)
├── Dockerfile.service # 参数化单服务镜像(--build-arg SERVICE=user|system|...)
├── smoke_fullchain.js # 全链路冒烟脚本(signin→my-menus→dashboard→ALL GREEN)
├── HANDOFF.md # 交接文档:里程碑/接口清单/32 条陷阱(重要的一手资料)
├── docs/PRD-admin.md # 产品需求文档
├── .github/workflows/ci.yml # GitHub Actions CI
├── bin/ # consul 2.0.3 二进制(darwin)
├── gateway/ # HTTP 网关 :3040(唯一对前端暴露的入口)
│ └── src/
│ ├── main.ts # trust proxy、全局前缀 /api、ValidationPipe、helmet、Swagger、版本控制
│ ├── app.module.ts # ConsulModule.forRoot×2(8500/8501 双注册中心)+ Throttler + 中间件
│ ├── auth/ # signin/signup/refresh/logout/me/profile/avatar + JwtStrategy + CASL
│ │ ├── auth.service.ts # 双 token 签发、refresh 换发、argon2 校验
│ │ ├── authorization.service.ts # 用户 Ability 构建(60s 缓存):user 库 Policy + system 库权限码
│ │ ├── policies/policies.guard.ts # @CheckPolicies 声明式鉴权守卫
│ │ └── strategies/jwt.strategy.ts # JWT 校验 + 会话存在性校验(被踢即 401)
│ ├── consul/ # 服务发现核心:ConsulService/ConsulCoreModule/ConsulController
│ ├── system/ # 业务透传层:user/system 两客户端服务 + 10 个控制器
│ ├── common/
│ │ ├── session/session.service.ts # 内存会话表(在线用户/强退/refresh 关联/30min 清扫)
│ │ ├── upload/ # MinIO 上传服务 + /uploads/:key 回读控制器
│ │ ├── middleware/request-logging.middleware.ts # traceId + 登录/操作日志采集
│ │ ├── filters/all-exceptions.filter.ts # gRPC→HTTP 状态映射 + 5xx 落系统日志
│ │ └── interceptors/ # GrpcExceptionInterceptor/BigInt/Serialize
│ └── ...
├── user/ # 用户域微服务 :40001(gRPC,user_db)
│ ├── prisma/ # schema.prisma + 9 个迁移 + seed.sql
│ └── src/
│ ├── main.ts # createMicroservice(GRPC,GRPC_TLS=on 时启服务端证书)
│ ├── app.controller.ts # UserService 全部 26 个 RPC(认证/用户/角色/部门/个人中心/统计)
│ ├── consul/consul-client.service.ts # 启动注册/关闭注销(10s HTTP 健康检查→health 服务)
│ └── database/ # PrismaService(PrismaPg 驱动适配器)
├── system/ # 系统域微服务 :40010(gRPC,system_db)
│ ├── prisma/ # schema.prisma + 4 个迁移 + seed.sql(33 条菜单/权限码种子)
│ └── src/ # SystemService 31 个 RPC(菜单/字典/三类日志/通知/角色-菜单/权限码)
├── health/ # 健康聚合服务 :3030(HTTP,供 Consul 对 gRPC 服务做健康检查代理)
│ └── src/consul/consul.controller.ts # GET /api/v1/health?service=xxx → terminus gRPC 探测
├── proto-pkg/ # 共享包 @toimc/proto-pkg
│ ├── proto/ # user.proto / system.proto / health.proto(契约唯一来源)
│ ├── src/utils/ # loadProto(keepCase) / promisifyServiceMethods / ConsulHttpClient / loadGrpcTls
│ └── certs/ # gRPC TLS 自签证书(生产环境以卷挂载覆盖补 SAN)
├── template/ # 新微服务脚手架(复制改名即得新服务)
└── consul/ # Consul 的 docker-compose(开发期用本机 dev agent 替代)vue-admin/vue-admin/
├── package.json # vue-element-plus-admin 7.0.0;dev/build/typecheck/lint 脚本
├── vite.config.ts # /api、/uploads 代理到 :3040(带超时与 502 兜底);rolldown 分包
├── .env / .env.development / .env.staging / .env.production # 端口/路由模式/CDN/压缩开关
├── Dockerfile # 前端生产镜像(nginx 托管,需带 SPA 回退 try_files)
├── public/
│ ├── platform-config.json # 运行时平台配置(标题"后台管理系统"/布局/主题/KeepAlive 等)
│ └── logo.svg / wasm/ / imgs/
├── types/ # 全局类型声明(router.d.ts 定义 RouteMeta 扩展)
├── build/ # vite 插件清单/优化项/工具(plugins.ts、utils.ts)
└── src/
├── main.ts # 装配顺序:样式→指令→Auth/Perms 全局组件→tippy→平台配置→store→router
├── App.vue # el-config-provider 根壳
├── config/index.ts # 读取 public/platform-config.json(getConfig)
├── router/
│ ├── index.ts # 静态路由装配 + 全局守卫(白名单/角色校验/刷新重建动态路由)
│ ├── utils.ts # initRouter 动态路由核心:addAsyncRoutes/拍平二级/404 兜底/hasAuth
│ └── modules/ # home.ts(首页)、remaining.ts(login/403/500/redirect/账户设置)、error.ts
├── store/modules/ # user(登录态/token)/permission(菜单树+keepAlive 缓存)/app/epTheme/multiTags/settings
├── api/
│ ├── user.ts # 登录/刷新/注册/个人中心/安全日志(含 pure-admin 信封适配)
│ ├── routes.ts # getAsyncRoutes:/system/my-menus → pure-admin 路由树(type0/1/3/4 映射)
│ └── system.ts # 用户/角色/菜单/部门/字典/日志/通知/仪表盘全部业务 API(544 行)
├── utils/
│ ├── http/index.ts # axios 封装:白名单、无感刷新队列、401 清理并跳 /login
│ └── auth.ts # token 双存储(cookie authorized-token + localStorage user-info)、hasPerms
├── layout/ # pure-admin 布局:侧边栏/导航栏/标签页/搜索/通知铃铛/设置抽屉
├── directives/ # v-auth(路由 meta.auths)/ v-perms(全局权限码)等 7 个指令
├── components/ # Re* 系列封装组件(Dialog/Drawer/Icon/TableBar/Cropper/PageContainer…)
└── views/
├── login/ # 登录/注册(一键测试登录、记住用户名)
├── welcome/ # 真实仪表盘(7 项统计 + 最新通知)
├── system/ # user/role/menu/dept/dict 五大管理页
├── monitor/ # 操作/登录/系统日志 + 在线用户四页
├── account-settings/ # 个人中心四卡片
└── (table|able|components|flow-chart|vue-flow|...) # 模板演示页(基座自带)engines.node >=22.22.1)、pnpm 10.x、PostgreSQL(15/16 均可,需建 user_db/system_db 两库)、Consul 二进制(默认找 $HOME/work/bin/consul,可用 CONSUL_BIN 覆盖)。user/、system/ 目录 npx prisma migrate deploy(Prisma 7 经 prisma.config.ts 读 DATABASE_URL/SYSTEM_DATABASE_URL),再 psql -f prisma/seed.sql 灌权限码/菜单种子(HANDOFF 陷阱 31:漏 seed 会导致菜单/权限 403)。dev-start.sh 已固化全部顺序)cd nestjs-microservice-app
bash dev-start.sh
# 脚本动作:导出 DATABASE_URL/SYSTEM_DATABASE_URL/JWT_SECRET/GRPC_TLS 等默认值
# → source dev-secrets.env(MinIO 凭据,可选)
# → [1/6] consul agent -dev ×2(8500/8501,第二个全套端口偏移)
# → [2/6] health :3030 → [3/6] user :40001 → [4/6] system :40010
# → [5/6] 轮询 Consul /v1/health/service?passing=true 等 user/system 转绿
# → [6/6] gateway :3040;日志在 /tmp/svc-*.log 与 /tmp/consul{1,2}.log首次运行前需先构建产物:pnpm install && pnpm -r build(脚本执行的是各服务 dist/main.js)。
cd vue-admin
pnpm install
pnpm dev # :5173,vite 把 /api 与 /uploads 反代到 http://localhost:3040浏览器访问 http://localhost:5173,登录页已预填 p1user/secret123,也可点"一键测试登录"。冒烟验证可跑仓库根 node smoke_fullchain.js(HANDOFF §3,注意 signin 限流 10/min,多轮需间隔 60s)。
curl http://127.0.0.1:3040/apicurl "http://127.0.0.1:3030/api/v1/health?service=user_grpc"http://localhost:3040/api/docs(NODE_ENV=production 时不挂载,可用 SWAGGER_ENABLED=true 显式打开)cd gateway && pnpm test(Node 24)、cd system && pnpm test;e2e:cd gateway && pnpm run test:e2epnpm typecheck && pnpm build新接手者建议按本文 → 01-系统架构.md(微服务拓扑与发现机制)→ 03-接口设计.md(网关路由与鉴权约定)→ 04-数据模型.md(两库实体)→ 05-核心流程.md(登录/CRUD/注册发现/健康检查端到端时序)→ 02-页面设计.md(前端页面与状态)→ 06-部署运行.md(环境变量与部署链路)的顺序阅读。阅读源码时始终以 proto-pkg/proto/*.proto 为接口契约锚点、以各服务 prisma/schema.prisma 为数据锚点;HANDOFF.md 的 32 条陷阱清单是排障时的第一参考。
补几条实践层面的阅读心法。读网关代码时建议从 app.module.ts 入手建立全局观——它集中声明了两个 Consul 实例、限流配额与请求日志中间件,是理解"每个请求会经历什么"的地图;然后按一个具体请求(比如打开用户列表页)沿"中间件 → 守卫 → 控制器 → 客户端服务 → gRPC"的路径走一遍,五分钟就能建立完整的数据流心智模型。读前端代码时则要从 router/index.ts 的全局守卫和 api/routes.ts 的菜单适配函数开始,因为 pure-admin 的动态路由机制是这套前端最"绕"的部分,理解了"后端菜单如何变成路由与权限码",其余页面代码都只是这个机制的消费者。阅读过程中遇到任何看似奇怪的设计(比如 refresh 换发后旧 access 立即失效、登录日志把每次写接口的删除也记一条),先查 05 篇的核心流程与本文功能清单里的注释说明——绝大多数"怪异"都是被真实事故教育出来的有意设计,HANDOFF 陷阱清单里几乎每一条都对应一次踩坑实录,贸然"修复"它们往往会把坑重新挖开。