项目:nestjs-admin | 版本:v1.2 | 生成日期:2026-10-06 | 依据:源码分析
本文逐个解析前后端的全部直接依赖:版本、用途、使用位置、选型理由、替代方案与升级风险。盘点口径说明:版本一律取各 package.json 的声明区间(pnpm-lock.yaml 锁定的精确版本以此为准),"使用位置"给到源码文件的相对路径。技术栈的全景与选型总述见《00-项目总览》§3,本文不复述架构结论,只做依赖级的显微镜工作。读法建议:先扫两张总表建立全局,再读重点依赖的逐段展开(@nestjs/microservices、CASL、Prisma、前端 vue 生态等),最后用 §5 的健康度评估指导升级决策。依赖清单本身就是架构的化石层——哪些依赖是核心命脉、哪些是演示页的包袱、哪些是历史事故的遗痕,都能在这张清单里读出来。
nestjs-microservice-app/package.json)根 package.json 本身几乎没有依赖,它的价值在两处:scripts 里的 dev:user1/dev:user2 多实例演示脚本(不同 PORT/SERVICE_ID/CHECK_KEY/CONSUL_PORT),以及 pnpm.onlyBuiltDependencies 白名单——显式放行 argon2、@swc/core、esbuild、@parcel/watcher、prisma、@prisma/engines、grpc-tools、protobufjs、unrs-resolver 九个带原生构建/安装脚本的包。这个白名单是 pnpm 10 的安全机制:不显式放行的包在安装时不会执行 postinstall 脚本,缺了 argon2 会直接导致登录 500。根 pnpm-workspace.yaml 按目录名声明六个包。
gateway/package.json,@toimc/gateway)| 依赖 | 版本 | 用途 | 使用位置 | 选型理由 / 替代 / 风险简注 |
|---|---|---|---|---|
| @nestjs/common、core、platform-express | ^12.0.0 | 框架核心 + Express 适配 | 全部 src | Nest 12 一代核心;替代无(框架绑定);升 13 关注 Express 5 与装饰器元数据变化 |
| @nestjs/microservices | ^12.0.0 | gRPC 传输层与 ClientProxyFactory | consul/consul.service.ts、各 client service | 核心命脉,见 §2.1 |
| @grpc/grpc-js + @grpc/proto-loader | ^1.14.4 / ^0.8.1 | gRPC 运行时与 proto 加载 | 同上 + proto-pkg/src/utils/proto-utils.ts | 官方 JS 实现(替代 C++ 版 grpc),见 §2.1 |
| @nestjs/jwt + passport + passport-jwt + @nestjs/passport | ^12 / ^0.7 / ^4.0.1 / ^12 | JWT 签发验签 + Passport 策略 | auth/auth.service.ts、auth/strategies/jwt.strategy.ts | passport-jwt 4 配合 Nest 策略模式;替代可考虑直接用 jsonwebtoken 去掉 passport 层 |
| @casl/ability | ^6.8.1 | CASL 能力引擎 | auth/authorization.service.ts、auth/policies/policies.guard.ts | 钉 v6:v7 与 node10 moduleResolution 不兼容(HANDOFF 陷阱 8),见 §2.2 |
| @nestjs/config | ^12.0.0 | 环境变量与 .env 装载 | common/config/config.module.ts | v12 起 Joi validationSchema 移除,校验写 validate 函数(陷阱 14) |
| @nestjs/throttler | 6.5.0 | 全局与分口限流 | app.module.ts、auth.controller 各 @Throttle | 精确钉版避免 6.x 小版本行为漂移;替代 @nestjs/evictor 类方案不成熟 |
| @nestjs/terminus | ^12.0.0 | 健康检查框架(GRPCHealthIndicator) | consul/consul.controller.ts | 只用了 gRPC 指标一个能力,属"大包用一角" |
| @nestjs/schedule | ^12.0.0 | 定时任务容器 | app.module.ts ScheduleModule.forRoot() | 当前零 @Cron 任务(《13-技术债务》§1.3),预留 |
| @nestjs/swagger | 12.0.1 | Swagger 文档 | main.ts swaggerEnabled 分支 | 精确钉版;仅非生产默认开启 |
| argon2 | ^0.45.1 | 密码哈希(argon2id) | auth/auth.service.ts verify | 原生模块,跨机器同步需 rebuild(陷阱 30) |
| helmet | ^8.3.0 | 安全响应头 | main.ts app.use(helmet()) | 生产 CSP 与 Swagger UI 兼容性 ⚠️ 待确认(08 篇隐患 9) |
| minio | ^8.0.7 | S3 兼容对象存储客户端 | common/upload/upload.service.ts | 见 §2.4 |
| multer | ^2.0.1 | multipart 解析(FileInterceptor 底层) | auth/auth.controller.ts、system/user.controller.ts | pnpm 严格链接要求直接 import 必须声明(陷阱 5 的教训包) |
| express | ^5.0.0 | HTTP 内核 | trust proxy 设置、中间件 | 显式声明以配合 platform-express 5.x |
| class-validator + class-transformer | ^0.15.1 / ^0.5.1 | DTO 校验与序列化 | 各 DTO、PublicUserDto @Exclude | 与全局 ValidationPipe 白名单模式配套 |
| rxjs | ^7.8.1 | 响应式流(竞速/重试/BehaviorSubject) | consul/consul.service.ts、promisify | 竞速与自愈机制的基石,见 §2.5 |
| reflect-metadata | ^0.2.0 | 装饰器元数据 | 全部入口 | Nest 必需 |
| @toimc/proto-pkg | workspace:* | 契约与工具共享包 | 全部 gRPC 相关代码 | workspace 内联引用,见 §2.6 |
| jest + ts-jest + supertest + cross-env | ^30 / ^29.4.12 / ^7.2.2 / ^10.1.0(dev) | 单测与 e2e | auth/*.spec.ts、consul/consul.service.spec.ts、test/ | jest 30 需 Node 24.9+ 与 --experimental-vm-modules(陷阱 15/21) |
| typescript / @swc/core | ^5.9.0 / 1.6.7(dev) | 类型检查与构建 | tsconfig、nest build | swc 不做类型检查(陷阱 29 的根源),CI 里另有 tsc --noEmit 门禁 |
user/package.json、system/package.json)两包结构几乎同构,差异用括号标注:
| 依赖 | 版本 | 用途 | 使用位置 | 备注 |
|---|---|---|---|---|
| @nestjs/common、core、microservices、platform-express、config | ^12.0.0 | 微服务框架 | main.ts、app.module.ts、app.controller.ts | platform-express 实际未监听 HTTP(user 服务残留 ConsulController 引用) |
| @grpc/grpc-js + @grpc/proto-loader | ^1.14.4 / ^0.8.1 | gRPC 服务端 | main.ts loadProto | 服务端同样经 proto-pkg 的 loadProto 取 packageDefinition |
| @prisma/client + @prisma/adapter-pg | ^7.10.0 | ORM 客户端与 PG 驱动适配器 | src/database/database.service.ts | Prisma 7 新架构:PrismaPg 适配器注入连接串,见 §2.3 |
| prisma(dev) | ^7.10.0 | CLI(migrate/generate) | prisma.config.ts、迁移目录 | schema 无 url 字段,连接串由 prisma.config.ts 提供 |
| argon2 | ^0.45.1 | 密码哈希(写入侧 hash) | user/src/app.controller.ts 多处 | system 包无此依赖 |
| @toimc/proto-pkg | workspace:* | HealthModule + loadProto | app.module.ts、main.ts | 两个服务都 import HealthModule 应答 Health.Check |
| dotenv(dev) | ^16.5.0 | prisma.config.ts 读 .env | user/prisma.config.ts、system/prisma.config.ts | 仅 CLI 链使用 |
| jest + ts-jest | ^30 / ^29.4.12(dev) | system 有 7 个单测 | system/src/app.controller.spec.ts | user 无测试文件(陷阱 21:jest 0 matches 属正常) |
| system 特有:cross-env | ^10.1.0(dev) | test 脚本跨平台注入 NODE_OPTIONS | system/package.json scripts | user 的 test 脚本未带 NODE_OPTIONS(无测试故未暴露) |
package.json)health(@toimc/health):@nestjs/terminus + @grpc/grpc-js + @toimc/proto-pkg 三件是特色依赖(terminus 的 GRPCHealthIndicator 是存在理由),其余为标准 Nest 12 套件。proto-pkg(@toimc/proto-pkg):运行时依赖 @grpc/grpc-js、@grpc/proto-loader、@nestjs/common、@nestjs/microservices、rxjs、google-protobuf ^3.21.2——其中 google-protobuf 是 grpc-tools 生成链的历史伴生依赖,运行时实际不再直接 import(⚠️ 待确认可否移除);devDependencies 里 grpc-tools 与 ts-proto(^1.180.0)是生成工具链,tsup ^8.1.0 负责打 CJS/ESM 双格式。template(@toimc/template):整个包停留在 NestJS 10 / jest 29 / TS 5.1 的旧版本线(@grpc/grpc-js ^1.10.10),是升级浪潮的化石孤岛——因为它不参与生产构建(根 Dockerfile 注释),升级优先级最低,但复制它起步的新服务要注意先对齐版本再开工。
这对组合承担了系统全部内部通信。选型的第一层理由是 NestJS 原生集成:Transport.GRPC 与 @GrpcMethod 装饰器让 gRPC 服务端的写法与 HTTP 控制器一致(user/src/app.controller.ts 全文就是证明),客户端侧 ClientProxyFactory.create 返回的 ClientGrpc 可通过 getService 取出类型化存根。第二层理由在 proto-loader 的加载参数:proto-pkg/src/utils/proto-utils.ts 的 loadSync 配置了 keepCase: true(运行时键保持 proto 原名)与 longs: String(64 位整数转字符串,从源头规避 JS number 精度与 JSON 序列化问题——这也是 gateway/src/common/interceptors/big-int.interceptor.ts 成为无人挂载的冗余备胎的原因,见《13-技术债务》§1.6)。替代方案上,官方 C++ 实现的 grpc 包性能更优但需要原生编译,grpc-js 纯 JS 实现牺牲少量性能换取零编译依赖,与本项目的 NAS/跨机部署场景更契合。升级风险集中在 grpc-js 1.x 的小版本:TLS 握手行为与 keepalive 参数偶有变化,好在三处消费点(网关客户端、服务端、health 探测端)共用同一个版本号,行为漂移会同时出现、容易归因。
CASL 被选中的理由在《08-安全与权限设计》§2 已详述:规则可序列化(action/subject/inverted/fields/conditions 正好对应 policy 表列)、Ability 构造器接受纯数据规则数组、ability.can(action, subject) 判定语义清晰。这里补充依赖层的三个事实。其一,版本被钉在 ^6.8.1 而非 v7,原因是 HANDOFF 陷阱 8 明确记录的兼容性问题——v7 的 package exports 与 tsconfig 的 node10 moduleResolution 不兼容,而 NestJS 生态的 tsconfig 惯例仍在用 node10 解析。其二,CASL 在本项目的使用面其实很窄:Ability 类、ability.can() 两三个 API,没有用到 @casl/angular/vue 等前端集成,因此若未来替换为手写判定器,改动收敛在 authorization.service.ts 与 policies.guard.ts 两个文件——依赖的可替换性与它的低侵入使用方式直接相关。其三,升级 v7 的前置条件是先把 tsconfig 的 moduleResolution 升到 bundler/node16+,这会牵动全部 Nest 包的解析行为,属于"牵一发动全身"的升级,建议与 Nest 13 升级合并为一次工具链现代化专项。
本项目是 Prisma 7 驱动适配器新架构的典型用户,与 Prisma 5 时代的用法有三点本质差异(HANDOFF 陷阱 7 全部踩过)。第一,schema 的 datasource 不再写 url,CLI 走 user/prisma.config.ts/system/prisma.config.ts 注入(读 DATABASE_URL / SYSTEM_DATABASE_URL),运行时走 database.service.ts 的 new PrismaPrismaPg({ connectionString })——两处必须指向同一库,陷阱 19 的连错库事故正是这个双通道配置的代价。第二,generator 用新的 prisma-client(output 指向 src/generated/prisma-client,moduleFormat cjs),生成物入仓可跳转,模型类型在 models/*.ts 而非 client.ts。第三,migrate dev 后不保证自动 generate,必须显式执行。替代方案对比:TypeORM 的装饰器实体与 Nest 集成更老练但类型推导弱;Drizzle 更轻但生态尚年轻;Kysely 适合纯查询场景。Prisma 的选型理由仍是类型安全 + 迁移体系成熟 + $transaction 交互式事务(app.controller.ts 的 createUser/deleteUser 依赖它保证多表原子性)。升级风险:7.x 线内小版本平稳,大版本升级需重审 adapter 协议;生成的客户端与 schema 强绑定,CI 里 prisma generate 必须先于 tsc。
minio ^8.0.7 只在 gateway/src/common/upload/upload.service.ts 一处消费,但它是头像功能的全部底座:bucketExists/makeBucket 兜底建桶、putObject 上传、getObject 流式回读、statObject 取元数据。选 minio SDK 而非裸 S3 SDK(@aws-sdk/client-s3)的理由:MinIO 是自建对象存储的既成事实,SDK 与其 API 语义完全对齐,且 Presigned URL 能力为《13-技术债务》路线图 P2 的"预签名直传"预留了升级路径。remove() 方法存在但无调用方(孤儿对象债务,§1.4 of 技术债篇)。multer ^2.0.1 的消费点在两个控制器的 FileInterceptor 配置(memoryStorage + 2MB limits + image/* fileFilter),multer 2.x 修复了 1.x 的多个 CVE,直接从 2 起步避免了历史包袱。风险点:minio SDK 8.x 的 metadata 返回结构(stat.metaData)曾有小版本变化,回读端点的 Content-Type 取值依赖它;上传功能整体设计为"缺凭据降级不崩"(onModuleInit 仅 warn),依赖层面不存在硬故障路径。
rxjs ^7.8.1 在网关里远不止是 Nest 的伴生依赖,它承担了三个架构级机制:retryGetServiceInfo 的 mergeMap+take(1) 并行竞速(gateway/src/consul/consul.service.ts)、BehaviorSubject 构成的客户端热更新槽位(grpcClientRegistry 每条记录的 client 字段)、retry 操作符的指数退避(retry({count:3, delay: retryCount => timer(2^retryCount*1000)}))。proto-pkg/src/utils/grpc-utils.ts 的 promisifyServiceMethods 则把 Observable 全面转译为 Promise——业务侧因此感知不到响应式的存在,这是"响应式藏于底层、Promise 面向业务"的分层设计。替代方案没有严肃选项:Nest 自身建立在 rxjs 上,版本跟随框架。风险点主要是认知门槛:take(1) 空流抛 EmptyError 的行为曾产生不可诊断的"no elements in sequence"报错,代码里专门把它翻译成带上下文的常规错误(同文件注释),新维护者改动竞速逻辑前应先理解这段历史。
以 "@toimc/proto-pkg": "workspace:*" 声明的内联引用是 monorepo 的关键粘合剂:五个服务不经过 npm registry 直接链接到本地包,pnpm --filter @toimc/proto-pkg build 重生成后所有消费方立即可见。包的 exports 双入口(. 与 ./dist/health)让 HealthModule 可以按需引入。proto-pkg 的 private: false 与 publishConfig 字段说明它曾考虑公开发布(版本 1.0.0),但目前仍以内联为主。风险在于"内联包的构建顺序"——消费方 build 前必须保证 proto-pkg 已构建(根 Dockerfile 按拓扑序显式构建 proto-pkg→health→user→system→gateway 就是在解决这个依赖序),直接 pnpm -r build 也按 workspace 拓扑执行,但单独 pnpm --filter @toimc/gateway build 会用到上次的旧产物,契约变更后忘记先重建 proto-pkg 是"改了 proto 没生效"类问题的第一嫌疑。
vue-admin/package.json,name 仍为 vue-element-plus-admin 7.0.0)| 依赖 | 版本 | 用途 | 使用位置 | 备注 |
|---|---|---|---|---|
| vue | ^3.5.41 | 视图框架 | 全部 | Composition API + script setup |
| vue-router | ^5.2.0 | 路由(history 模式) | src/router/ | 动态路由见 §4.2 |
| pinia | ^4.0.3 | 状态管理 | src/store/modules/ 六仓 | user/permission 两仓承载登录态与菜单 |
| element-plus + @element-plus/icons-vue | ^2.14.5 / ^2.3.2 | 组件库 | 全部页面 | 图标与 remixicon 双轨(src/components/ReIcon/RiIcon.ts) |
| axios + qs + js-cookie | 1.18.1 / ^6.15.3 / ^3.0.8 | HTTP、序列化、Cookie | src/utils/http/index.ts、src/utils/auth.ts | axios 精确钉版(pnpm audit 曾漏报 1.7.2 的 30 条公告,陷阱 13),见 §4.1 |
| @pureadmin/table、utils、descriptions | ^3.3.0 / ^2.6.4 / ^1.2.1 | pure-admin 生态 | 表格页、适配层 | isIncludeAllChildren 等工具被 hasPerms 直接消费 |
| @vueuse/core + @vueuse/motion | ^14.4.0 / ^3.0.3 | 组合式工具与动效 | hooks、登录页 | — |
| responsive-storage | ^2.2.0 | 响应式本地存储 | settings/app 等仓 | 与 pinia 联动持久化 |
| dayjs / mitt / nprogress / localforage | ^1.11.23 / ^3.0.1 / ^0.2.0 / ^1.10.0 | 时间、事件总线、进度条、IndexedDB | 全局 | localforage 服务于演示页缓存场景 |
| cropperjs | ^1.6.2 | 头像裁剪 | ReCropper 封装 | 1.x 经典版,2.x API 不兼容未升 |
| 依赖 | 版本 | 用途 | 备注 |
|---|---|---|---|
| vite | ^8.2.2 | 构建与 dev server | rolldown-vite 产物,vite.config.ts 用 rolldownOptions 四组分包 |
| typescript + vue-tsc | ^6.0.3 / ^3.3.11 | 类型检查 | pnpm typecheck 是门禁 |
| tailwindcss + @tailwindcss/vite | ^4.3.3 | 原子化 CSS | 与 Element Plus 共存(src/style/tailwind.css、tokens.css 设计令牌) |
| @vitejs/plugin-vue / vue-jsx | ^6.0.8 / ^5.1.6 | SFC/JSX 编译 | hook.tsx 页面依赖 vue-jsx |
| code-inspector-plugin | ^2.0.7 | Option+Shift 点击定位源码 | 开发提效,生产无痕 |
| vite-plugin-compression / cdn-import / remove-console / router-warn / svg-loader | 各 ^0.x–^2.x | 压缩/CDN/去 console/消路由警告/SVG 组件化 | 按 env 开关条件挂载(build/plugins.ts) |
| eslint + typescript-eslint + prettier 系 | ^10 / ^8.67 / ^3.9.6 | 静态检查 | lint --max-warnings 0 零容忍 |
| rollup-plugin-visualizer / svgo / rimraf / cssnano | — | 产物分析、SVG 优化、清理 | report 模式专用 |
| postcss-html | null(显式置空) | 锁死某个传递依赖版本 | 用 null 屏蔽是 pnpm overrides 语义的写法,属工具链防御 |
xlsx、swiper、vue3-danmaku、wavesurfer.js、xgplayer、@logicflow/core+extension、@vue-flow/core+background、print-js、jsbarcode、qrcode、vue-pdf-embed、intro.js、v-contextmenu、@howdyjs/mouse-menu、sortablejs、vuedraggable、el-table-infinite-scroll、china-area-data、pinyin-pro、plus-pro-components、mint-filter、@zxcvbn-ts/core、@amap/amap-jsapi-loader、version-rocket、animate.css、vue-json-pretty、vue-tippy、vue-types、vue-virtual-scroller、vue-waterfall-plugin-next、typeit、path-browserify、remixicon——这三十余个依赖服务于 views/table|able|components|flow-chart|vue-flow 等模板演示页,与业务后端零耦合(《00-项目总览》§2 末段)。它们的存在是"选完整版而非精简版"决策的代价:安装体积与潜在 CVE 面换来了现成的组件参考实现。按《13-技术债务》的分类,这属于设计取舍而非疏漏——若未来做产物瘦身,删除演示页并连带卸载这批依赖是收益最大的一刀(⚠️ 待确认演示页是否仍被用作教学参考)。
vue-admin/src/utils/http/index.ts 把 axios 包成 PureHttp 类,业务侧只接触 get/post 两个门面。依赖层面有三点值得记录。第一,版本精确钉在 1.18.1 而非区间——这是 HANDOFF 陷阱 13 的直接产物:pnpm audit 曾漏报 axios 1.7.2 的 30 条安全公告,团队此后改用 npm 官方 bulk advisory 接口交叉验证,并把 axios 钉在验证过的精确版本上,升级需主动评估而非被动跟随区间漂移。第二,qs 序列化以 paramsSerializer 注入(解决 axios issue 5142 的数组参数格式),数组 GET 参数(如批量删除的 ids)依赖这一行为。第三,超时 10 秒与后端 vite 代理的 15 秒超时构成两级水位线:axios 先超时则前端报网络错误,代理先超时则返回 502 兜底 JSON(vite.config.ts 的 configure 错误监听),两级设计的意图是让"后端挂起"与"后端不可达"呈现不同的错误形态。
值得单列一条反面发现:动态路由体系(src/router/utils.ts、src/api/routes.ts)几乎不依赖任何第三方库——import.meta.glob 是 Vite 原生能力,菜单树处理是 60 行手写 Map 算法,keep-alive 拍平是手写工具函数。这解释了 vue-router 从 4 升 5 时本项目迁移成本很低的原因:路由的复杂性都沉淀在业务代码而非框架配置里。同理 pinia 4 与 responsive-storage 的组合只覆盖 settings 类偏好持久化,登录态的持久化反而绕开它们、直接用 js-cookie + localStorage 手工管理(src/utils/auth.ts 的 setToken 注释逐条写明双存储分工),形成"框架推荐方案与项目实际方案并存"的双轨。接手者若要统一这两轨,改动面集中在 auth.ts 与 user store,不涉及路由。
两大样式体系的共存是前端依赖里最需要"守纪律"的部分。Element Plus ^2.14.5 提供管理端组件基座,其样式按需引入由 unplugin 系(vite 插件链)处理;Tailwind ^4.3.3 经 @tailwindcss/vite 原生插件接入,不再需要 postcss 配置文件(postcss-html: null 的显式置空正是清理旧 postcss 链的痕迹)。分工边界:布局、间距、响应式工具类用 Tailwind(tokens.css 定义设计令牌),交互组件用 Element Plus。风险点在于 Tailwind 4 的 CSS-first 配置与 Element Plus 的 CSS 变量体系存在命名空间重叠(如色彩变量),新增全局样式时应优先走 tokens.css 的令牌层而不是散写 utility,避免两套变量漂移;vxe-table 4.6.25(精确钉版)与 element-plus 的表格样式在演示页并存,业务页统一用 @pureadmin/table(它才是 element-plus 表格的官方薄封装),两套表格的边界在《02-页面设计》§3 已有说明。
绿区(健康,维持现状):Nest 12 全家桶、Prisma 7、grpc-js、minio、helmet、前端 vue/element-plus/pinia/vite 主体线——均为各自生态的当前主线版本,无弃用信号,CVE 面在已验证范围内。rxjs、class-validator 等跟随框架版本即可。
黄区(有已知约束,升级需专项):① @casl/ability v6——v7 升级被 tsconfig moduleResolution 卡住,需与工具链现代化合并处理(§2.2);② jest 30——运行时强依赖 Node 24.9+ 与实验性 ESM 标志,Node 版本策略必须先定(陷阱 15/21);③ axios 钉版策略——安全响应快但需要建立自己的公告跟踪流程(陷阱 13 的方法论);④ multer 2 与 memoryStorage——2MB 图片场景无碍,若放开大文件需换流式存储。
红区(建议尽快处置):① template 包的 NestJS 10 版本线——作为脚手架却带旧版本模板,复制起步即落后一个大版本,建议对齐到 12(改动小、收益直接);② proto-pkg 的 google-protobuf 运行时依赖——生成链历史伴生,运行时无直接 import(⚠️ 待确认),移除可减一个传递面;③ 演示页三十余个依赖——体积与 CVE 面的主要构成,按教学价值取舍后批量卸载;④ user/system 的 platform-express——user 服务纯 gRPC 无 HTTP 监听,platform-express 与残留 ConsulController(技术债 §1.5)一起清理可让依赖图更诚实。
升级路线建议:短期只做红区清理(零行为变更);中期观察 Nest 13 与 Prisma 8 的发布节奏,待两者稳定后做一次"框架版本对齐"专项(casl v7、moduleResolution、jest 配置一并解决);长期把"依赖健康度巡检"固化为例行动作——每月跑一次 npm bulk advisory 交叉验证 + pnpm outdated 分区评审,把陷阱 13 的手工方法论制度化。评估依赖健康度的判断顺序应当是:先看是否有安全公告(存量风险),再看是否被弃用(存续风险),最后才看版本新旧(现代性)——这个顺序能避免"为升级而升级"把黄区拖成红区。