项目:nestjs-admin | 版本:v1.1 | 生成日期:2026-10-06 | 依据:源码分析
本文是配置的"逐行走读手册":dev-start.sh 逐段解析、两份 compose 与两份 Dockerfile 的差异与取舍、Consul 的三种运行形态、前后端环境变量全清单、vue-admin 构建配置逐项说明。变量与故障的对应关系见《12-FAQ与故障排查》,部署链路全景见《06-部署运行》。
nestjs-microservice-app/dev-start.sh,78 行)第 1 段·头部与默认环境(L1-19):set -e 任一步失败即退出;cd "$(dirname "$0")" 保证任意 cwd 可执行。随后导出一组 ${VAR:-默认值} 形式的变量——这种写法的关键语义是已有环境变量优先(可从外部覆盖而不改脚本):DATABASE_URL/SYSTEM_DATABASE_URL 指向本机两库、JWT_SECRET 默认 dev-secret-change-me(仅限开发)、ROLE_ID=1(注册默认角色,需与 roles 表种子一致)、CONSUL_HOST/HOST 均为 127.0.0.1(注意两个变量语义不同:前者是"去哪注册",后者是"注册什么地址")、GRPC_TLS=on(本机默认开 TLS)、SERVICE_HEALTHCHECK_ENDPOINTS 双段值描述 user/system 两个探测目标(格式 key,grpc_url,package,proto,rpc名,竖线分隔)。
第 2 段·机密注入(L20-30):存在 dev-secrets.env 则 set -a; source; set +a(-a 使文件内变量自动导出为环境变量)注入 NAS MinIO/Redis 凭据;否则回退本机 MinIO 默认值并打印"上传降级"提示。第 3 段·进程清理(L32-35):pkill -f "node dist/main.js" 与 pkill -f "consul agent"——匹配的是相对路径进程名,pkill -f "gateway/dist/main.js" 匹配不到(陷阱 2);脚本刻意不清理 vite(避免误杀本机其他项目,陷阱 32)。第 4 段·双 Consul([1/6]):dev1 标准监听 8500;dev2 全套端口偏移(http 8501、server 8310、serf-lan 8311、serf-wan 8312、dns 8601、grpc 8513、grpc-tls 8514)——不偏移会撞 8300(陷阱 12)。第 5-7 段·三服务顺序起([2/6]-[4/6]):health 先行(它是全栈就绪信号),各 sleep 4 间隔后起 user(传 PORT 与 CHECK 指向 health 的 user_grpc 键)、system(额外传 SERVICE_NAME=nestjs-system SERVICE_KIND=system);全部 nohup node dist/main.js > /tmp/svc-*.log 后台化——注意 dev-start.sh 跑的是 dist 构建产物,改代码后需 pnpm -r build 再重跑。第 8 段·等绿([5/6]):轮询 :8500/v1/health/service/{nestjs-service,nestjs-system}?passing=true(python3 解析 JSON 计数),最多 15 轮 × 3s(首个 Consul 检查间隔 10s,总等待约 45s),输出 passing: user=N system=M。第 9 段·网关与验收([6/6]):起 gateway 后 sleep 6,curl :3040/api 与 health 端点输出状态并提示日志位置。整脚本的顺序设计(consul → health → 业务 → 等绿 → 网关)是《01-系统架构》§2 所述"health 充当全栈就绪信号"的落地。
nestjs-microservice-app/docker-compose.full.yml)九个服务的拓扑与依赖门控:consul(hashicorp/consul:1.21,agent -dev -client=0.0.0.0,自带 consul members 健康检查)→ postgres(16-alpine,POSTGRES_DB 直接建 user_db,pg_isready 探活)→ 两个一次性任务:migrate(复用 Dockerfile.service 的 user 构建,command 跑 pnpm --filter @toimc/user exec prisma migrate deploy,service_completed_successfully 门控 user 启动)与 system-db-init(postgres 镜像里 psql 对 user_db 实例执行 CREATE DATABASE system_db || true)→ migrate-system(同 migrate 但 SYSTEM_DATABASE_URL)→ health / user / system(GRPC_TLS=on;user/system 的 HOST 写 compose 服务名——Consul 注册地址必须容器间可解析;CHECK 写 http://health:3030/...)→ gateway(CONSUL1_HOST=CONSUL2_HOST=consul,单 Consul 双实例同指——生产 compose 注释确认这是单例部署的既定用法)。两处使用注意:文件头自述"尚未实测"(⚠️ 待确认,未纳入验证清单);compose 版未跑 seed——照 HANDOFF 陷阱 31 的教训,用它起栈后必须手工重放两份 seed.sql 才有菜单/权限。
生产实际使用的 admin-vue-deploy/docker-compose.yml 与之的差异值得一表(两者对照着读收获最大):
| 维度 | compose.full.yml(未实测) | admin-vue-deploy(生产实测) |
|---|---|---|
| 镜像 | Dockerfile.service 按 SERVICE 参数构建 6 次 | 统一 admin-backend:latest 单镜像,command 决定跑谁 |
| 数据库 | 自带 postgres 容器 | 复用 NAS dev-stack 的 dev-postgres(external 网络 dev-stack_devnet),库名 admin_user_db/admin_system_db |
| 机密 | 明文写在 environment(JWT_SECRET: change-me-in-production) | ${VAR} 从 600 权限的 .env 插值 |
| 证书 | 用镜像内自签(SAN 仅 localhost,容器互连会炸) | 卷挂 ./certs/*.pem 覆盖 /app/proto-pkg/certs/(重签含服务名 SAN) |
| 前端 | 无 | nginx:1.27-alpine 卷挂 dist,端口 127.0.0.1:5800 只绑回环 |
| restart 策略 | 无 | unless-stopped 全员 |
nestjs-microservice-app/)| 维度 | Dockerfile(生产主用) | Dockerfile.service(参数化备选) |
|---|---|---|
| 基础镜像 | node:22-bookworm-slim + openssl(Prisma 引擎需要) | node:24-alpine 三阶段(deps→build→runtime) |
| 构建产物 | monorepo 全量单镜像:按拓扑序显式构建 proto-pkg→health→user→system→gateway(template 不参与) | `--build-arg SERVICE=user |
| 运行时内容 | 全量代码在镜像内 | 仅拷 node_modules、proto-pkg 的 dist/proto、目标服务 dist(体积小) |
| CMD | 默认跑网关,具体角色由 compose command 覆盖(如 node /app/user/dist/main.js) | node dist/main.js(工作目录 /app/${SERVICE}) |
| 加速项 | corepack 钉 pnpm@10.23.0(与 lockfile 生成版本一致)、npmmirror + Prisma 引擎镜像 ARG、先拷包清单利用层缓存 | corepack(未钉版本)、同款清单先行策略 |
| 适用场景 | NAS 生产(一次构建五容器共用,legacy builder DOCKER_BUILDKIT=0,头注释警告 btrfs+BuildKit 死锁) | 本地/CI 单服务构建调试 |
另有 gateway/Dockerfile(子目录版)是 monorepo 化之前的遗留(COPY package*.json 与 npm run build 在 workspace 下跑不通),生产不使用,处置建议见《13-技术债务》§1.8。前端 vue-admin/Dockerfile 是标准两阶段:node:20-alpine(pnpm install --frozen-lockfile + pnpm build)→ nginx:stable-alpine 托管 dist——注意生产实际未用此镜像构建前端,而是本机 build 后由 admin-vue-deploy/sync.sh 传 dist 到 NAS 卷挂(前端零容器构建),此 Dockerfile 保留作独立交付路径。
三种运行形态各有配置入口:形态一·本机 dev agent ×2(dev-start.sh,零配置文件,agent -dev 内存模式,数据不持久,重启即空——所以每次起栈服务都会重新注册)。形态二·仓库 server 模式参考(consul/docker-compose.yml + consul/config/consul.hcl:-server -bootstrap-expect=1 -ui,挂载 data/config 卷——开发期未使用,留作带 UI 的持久化参考)。形态三·容器 dev agent(两份 compose 里 hashicorp/consul:1.21/1.20 + -client=0.0.0.0 允许容器外访问)。应用侧与 Consul 交互的全部配置收在 6 个变量里:注册侧 CONSUL_HOST/CONSUL_PORT(业务服务)、发现侧 CONSUL1_HOST/PORT + CONSUL2_HOST/PORT(网关,可同指一个实例)、鉴权 CONSUL_TOKEN(ConsulHttpClient 转为 X-Consul-Token 头;⚠️ 待确认生产是否启用 ACL,当前 dev agent 无 ACL)。注册体字段(user/src/consul/consul-client.service.ts):id(SERVICE_ID,实例唯一键)、name(SERVICE_NAME,发现键)、address/port(HOST/PORT)、check.http(CHECK,10s 间隔)、deregisterCriticalServiceAfter: '60s'(连续失败摘除阈值)。健康检查的完整链路是"Consul --HTTP--> health 服务 --gRPC Check--> 业务服务",因此 SERVICE_HEALTHCHECK_ENDPOINTS(health 侧)与 CHECK(注册侧)必须成对配置且 key 一致——这是新环境配置错误率最高的一对。
.env.example + 各服务消费点;生产覆盖走 admin-vue-deploy/.env)| 变量 | 默认值 | 消费方 | 说明 |
|---|---|---|---|
| JWT_SECRET | dev-secret-change-me | 网关 JwtModule/JwtStrategy | 缺失时构造期抛错拒绝启动 |
| DATABASE_URL | postgresql://nestjs:nestjs123@127.0.0.1:5432/user_db | user Prisma(运行时+CLI) | |
| SYSTEM_DATABASE_URL | (同实例 system_db) | system Prisma(运行时 ` | |
| PORT | 40001/40010/3040/3030 | 各服务监听 | |
| HOST | 127.0.0.1 | Consul 注册地址 | 容器内写服务名 |
| SERVICE_NAME | nestjs-service | 注册发现键 | system 为 nestjs-system |
| SERVICE_ID | nestjs-user-${PORT} | 注册实例 ID | 多实例必须唯一 |
| SERVICE_KIND | 未设(.env.example 未列) | system 服务缺省 SERVICE_ID 拼装(system/src/consul/consul-client.service.ts:12) | dev-start 为 system 传 SERVICE_KIND=system,缺省实例 ID 即 nestjs-system-40010;user 服务不读此变量(写死 nestjs-user-${PORT}) |
| CONSUL_HOST / CONSUL_PORT | 127.0.0.1 / 8500 | 注册侧 | |
| CONSUL1_HOST/PORT、CONSUL2_HOST/PORT | localhost / 8500、8501 | 网关发现侧 | 单例可同指 |
| CONSUL_TOKEN | 未设 | ConsulHttpClient | ACL 场景 |
| CHECK | http://127.0.0.1:3030/api/v1/health?service=user_grpc | 注册健康检查 | 容器内写 http://health:3030/... |
| CHECK_KEY | user_grpc | CHECK 缺省值的 key 部分 | 多实例区分 |
| SERVICE_HEALTHCHECK_ENDPOINTS | 双段值(见 §1) | health 与网关的 ConsulController | key,grpc_url,package,proto,rpc名 竖线分隔 |
| GRPC_TLS | off(.env.example)/on(dev-start) | 三处:user/system 服务端、网关客户端、health 探测端 | 必须三端一致 |
| ROLE_ID | 1 | 注册/建用户默认角色 | 与 roles 种子一致 |
| MINIO_ENDPOINT/PORT/USE_SSL/ACCESS_KEY/SECRET_KEY/BUCKET | 127.0.0.1/9000/false/''/''/nestjs-demo | 网关 UploadService | 缺凭据降级不崩 |
| VERSION / PREFIX | 空 / /api | 网关 URI 版本与前缀 | health 服务缺省 VERSION='1' → /api/v1 |
| SWAGGER_ENABLED | 未设 | 网关 Swagger | 未设时按 NODE_ENV≠production 开 |
| NODE_ENV | development | ConfigModule 校验 + Swagger 开关 | 只允许 development/production/test |
| REDIS_HOST/PORT/PASSWORD | (dev-secrets.env) | 无消费方 | 验证码移除后的残留,待清理 |
vue-admin/.env*)与运行时平台配置构建期变量四个文件分工:.env(基础,VITE_PORT=8848、VITE_HIDE_HOME=false)→ .env.development 覆盖端口 5173、PUBLIC_PATH=/、路由模式 h5 → .env.staging(VITE_CDN=true、COMPRESSION=none,注释说明"预发布也要生产行为")→ .env.production(VITE_CDN=false、COMPRESSION="both")。改 .env 必须重启 vite(构建期注入,HMR 不重读)。运行时配置在 public/platform-config.json(src/config/index.ts 的 getConfig 读取,改后硬刷新即可、无需重编译):Title"后台管理系统"、Layout=vertical、KeepAlive=true、TagsStyle=chrome、CachingAsyncRoutes=false(动态路由每次实拉、不落本地缓存——排障时"菜单不一致"类问题的关键开关)、MenuSearchHistory=6、MultiTagsCache=false 等。
vue-admin/vite.config.ts + build/)vite.config.ts 五大块:① server 代理——/api 与 /uploads 双双指向 http://localhost:3040,各带 timeout/proxyTimeout(15s/30s)与 configure 错误监听(后端不可用返回 502 JSON 而非打崩 dev server);warmup 预热 index.html 与 views/components。② 构建目标——target es2020、sourcemap hidden(生成不注入)、chunkSizeWarningLimit 1000 作回归告警、assetsInlineLimit 1024(防小图 base64 膨胀 CSS)。③ rolldownOptions 分包——advancedChunks 四组(vue-vendor/element-plus/echarts/vxe-table),产物命名 static/js|[ext]/[name]-[hash]。④ 插件清单(build/plugins.ts)——tailwindcss、vue、vueJsx、codeInspectorPlugin(Option+Shift 点击定位源码)、viteBuildInfo、svgLoader、removeNoMatch(消 router 警告)、按 VITE_CDN 条件挂 cdn、按 VITE_COMPRESSION 挂压缩插件、非 dev 模式 removeConsole、report 模式挂 rollup-plugin-visualizer 产 report.html。⑤ 依赖预构建(build/optimize.ts 的 include/exclude)。构建脚本链见 package.json:dev(4GB heap)、build(rimraf + 8GB heap + generate-version-file)、build:staging(--mode staging)、typecheck(vue-tsc --noEmit --skipLibCheck)、lint(eslint --max-warnings 0 + prettier)、preinstall(only-allow pnpm)。发布链路 admin-vue-deploy/sync.sh:本机 build → dist 传 NAS dist-new(旧 dist 保留作回滚)→ 后端源码 tar 同步 → NAS docker compose --no-build 重建;nginx 侧缓存策略与 XFF 透传细节见《06-部署运行》§4.3。
读图记住一条优先级链:真实环境变量 > .env 文件 > 代码默认值;dev-start.sh 与 compose 都是"环境变量制造者",机密永远走独立文件且不入库。任何配置问题先定位它属于哪一层(脚本默认值 / .env / compose 注入 / 运行时 json),再检查《12-FAQ》§4 的成对约束(CHECK 与 ENDPOINTS、CONSUL 注册侧与发现侧、GRPC_TLS 三端一致性)——配置类故障几乎都出在"成对变量只改了一半"。