7 Commits

Author SHA1 Message Date
湛兮 e9931a0923 feat: 补齐后端 agent skill 规范
- .agents: 新增中文提交与注释同步 skill 及项目索引

- AGENTS.md: 增加本地 skill 入口并保留 README 同步规则

- README.md: 同步 Agent skill 目录说明
2026-06-11 17:48:08 +08:00
湛兮 efa71ede6d feat: 完善环境标识配置 2026-06-05 15:35:51 +08:00
湛兮 c0b561b6cb chore: move Jenkins pipeline config out of repo 2026-06-05 15:08:11 +08:00
湛兮 1dbeaa7209 ci: harden production tag selection 2026-06-05 14:45:44 +08:00
湛兮 f1e3976c95 ci: add production tag pipeline 2026-06-05 14:38:23 +08:00
湛兮 998a44431d chore: approve backend pnpm build scripts 2026-06-05 12:40:59 +08:00
湛兮 488df0b1ee chore: split dev and production deployment env 2026-06-05 12:32:10 +08:00
15 changed files with 377 additions and 31 deletions
+23
View File
@@ -0,0 +1,23 @@
# access-manage Agent Skills
本目录存放 `access-manage` 的项目级 Agent skill。规则参考 SeaCloud 项目的 agent skill 组织方式,并按当前 Fastify、TypeScript、mysql2 和学习型后端项目场景保留最小必要规范。
## 使用入口
1. 进入仓库后先读 `AGENTS.md``RTK.md`
2. 创建或修改代码文件时,使用 `header-comment-sync` 保持中文文件头、导出声明和复杂逻辑注释同步。
3. 提交或推送代码时,使用 `chinese-commit-message` 生成英文 type 前缀加中文摘要的 commit message。
4. 目录、关键文件或脚本发生变化时,继续使用 `readme-structure-sync` 同步 README。
5. 所有 skill 的触达文件补齐规则只处理本次任务相关文件,不要求为了单次任务全仓扫描。
## 当前项目事实
- 应用类型:门店员工权限管理后端。
- 技术栈:Fastify、TypeScript、mysql2、zod、@fastify/jwt
- 关键边界:保持 controller、service、repository 分层,不引入 ORM。
## Skill 索引
- `header-comment-sync`:中文文件头、导出声明、复杂逻辑和风险边界注释。
- `chinese-commit-message`:中文提交信息格式。
- `readme-structure-sync`README 与目录结构同步规则。
@@ -0,0 +1,54 @@
---
name: chinese-commit-message
description: 在本仓库执行 git commit、整理提交说明或准备推送时使用,保持提交信息使用英文类型前缀加中文描述。
---
# 中文提交信息
## 何时使用
- 用户要求提交、推送或整理 commit message。
- 你准备执行 `git commit`
- 需要总结当前 diff 的提交说明。
## 核心要求
1. 提交信息必须使用英文 type 加中文摘要,例如 `feat: 补齐后端 agent skill 规范`
2. 常用 type 优先使用 `feat``fix``refactor``docs``chore`
3. 不强制 scope;只有模块边界非常清楚时才使用 `feat(auth): ...`
4. 标题部分使用简洁中文,直接说明改动,不写“修改一下”“更新代码”这类空泛描述。
5. 非 trivial 改动建议补中文正文,用扁平 bullet 按文件或内容块说明关键动作。
6. 正文与标题之间空一行;正文每条 bullet 对应一个独立文件或明确内容块。
7. 如果用户指定提交信息,优先尊重用户原意,只做必要格式整理。
## 触达文件补齐
- 不要求为了提交信息单独全仓扫描。
- 提交前如果 diff 中的触达文件明显违反对应 skill 的触达补齐要求,应先修正当前相关链路,再整理 commit message。
- commit 正文应概括本次触达补齐,例如“补齐触达文件注释”“同步 README 目录说明”。
## 推荐结构
```text
feat: 补齐后端 agent skill 规范
- .agents/README.md: 新增项目级 skill 索引和使用入口
- .agents/skills: 补充中文提交与注释同步规则
- AGENTS.md: 增加本地 skill 入口
```
## 不推荐写法
- `新增规则`
- `feat: add skills`
- `fix bug`
- `update`
- 复杂改动只有标题没有正文。
- 正文写成长段流水账或嵌套列表。
## 落地检查
- type 是否合理。
- 中文摘要是否覆盖主改动。
- 是否需要正文。
- 正文是否按文件或内容块归纳。
@@ -0,0 +1,66 @@
---
name: header-comment-sync
description: 在本仓库创建或修改 ts、tsx、js、jsx、mjs、cjs、vue、astro 文件时使用,保持中文文件头、导出声明、复杂逻辑和风险边界注释准确。
---
# 注释规范与同步
## 目标
让下一次进入文件的维护者能快速理解当前职责、关键约束和风险边界。注释解释“为什么”和“边界”,不复述代码表面行为。
## 适用场景
- 新增或修改 controller、service、repository、schema、types、config、db、middleware、utils 或脚本文件。
- 修改鉴权、权限、事务、SQL 条件、迁移、环境变量校验、后端协议转换或错误处理逻辑。
- 发现旧注释与当前代码行为不一致。
## 核心原则
- 注释使用中文,描述当前事实。
- 简单局部变量不强行注释。
- 文件头说明文件当前职责,1 到 2 行即可。
- 导出的函数、类型、常量、配置对象、repository method、service method 应有用途说明。
- 复杂兼容逻辑、SQL 约束、事务边界、权限映射、密码安全和审计边界需要短注释。
- TODO/FIXME 必须说明触发条件、剩余动作和可删除条件。
## 文件头规则
- 每个适用文件都要有准确文件头。
- 如果文件必须以 `"use client"``"use server"` 开头,文件头注释放在指令之后、导入之前。
- Vue 文件不为了补头注释重排模板结构;在 `<script>``<script setup>` 顶部补当前职责说明。
- 文件头不要写“本文件用于...”这类空话,直接说明业务角色。
```ts
/**
* 员工仓储层,集中封装员工查询、状态变更、角色绑定和凭据安全相关 SQL。
*/
```
## JSDoc 与局部注释
- 导出的函数、类、类型、常量和配置对象优先使用 JSDoc。
- 非导出但复杂的解析、格式化、请求构造、状态派生、事务回调也要补。
- 只在局部逻辑确实有约束时使用行内注释,例如事务顺序、兼容字段、后端协议和临时迁移。
- 不要在每一行、简单赋值、短生命周期变量上堆注释。
## 触达文件补齐
- 不要求为了注释规范单独全仓扫描。
- 只要本次任务修改了适用文件,就顺手补齐明显缺失或过时的文件头、导出声明、共享类型、复杂回调和协议注释。
- 大文件可按触达区域优先,但同文件内裸露的顶层导出和共享类型应一起补。
- 如果一次补齐整个大文件会明显超出需求范围,至少补齐当前需求链路和顶层声明,并在交付说明里说明剩余范围。
## 不推荐的写法
- 注释只写“处理数据”“点击事件”这种无信息内容。
- 注释描述旧方案,和当前代码矛盾。
- 为简单赋值、短生命周期变量写注释。
- 用英文口号、emoji 或情绪化标记替代项目内中文说明。
## 落地检查
- 修改后的文件头是否准确。
- 新增/修改的导出声明是否有必要说明。
- 复杂逻辑是否解释了约束而不是复述代码。
- 旧注释是否仍然可信。
+13
View File
@@ -1 +1,14 @@
@RTK.md
# access-manage Agent 入口
## 必用 Skill
- 创建或修改 `ts``tsx``js``jsx``mjs``cjs``vue``astro` 文件时,使用 `./.agents/skills/header-comment-sync/SKILL.md`
- 执行 `git commit`、整理提交说明或准备推送时,使用 `./.agents/skills/chinese-commit-message/SKILL.md`
- 新增、删除、移动目录或关键入口文件时,继续使用 `./.agents/skills/readme-structure-sync/SKILL.md`
## 本地规则
- 先读 `RTK.md`,再按任务读取 `.agents/README.md` 中匹配的 skill。
- skill 的触达文件补齐只处理本次修改相关文件,不为单次任务全仓扫描。
+67 -21
View File
@@ -36,8 +36,11 @@
```text
.
├── .agents/
│ ├── README.md # 项目级 Agent skill 索引和使用入口
│ └── skills/
── readme-structure-sync/ # README 和目录结构同步维护规则
── chinese-commit-message/ # 中文提交信息规范
│ ├── header-comment-sync/ # 中文文件头和注释同步规范
│ └── readme-structure-sync/ # README 和目录结构同步维护规则
├── .env.development # 默认本地开发环境变量文件,不提交到仓库
├── .env.local / .env.production # 可选环境变量文件,不提交到仓库
├── .gitignore # Git 忽略规则,排除本地配置、依赖和编译产物
@@ -46,9 +49,14 @@
├── docs/
│ └── API.md # 前端对接接口文档
├── deploy/
│ ├── env/
│ │ ├── local.env.example # 本地环境变量示例,不包含真实密码
│ │ ├── test.env.example # develop/测试环境变量示例,不包含真实密码
│ │ └── production.env.example # 生产环境变量示例,不包含真实密码
│ └── server/
│ ├── create-env.sh # 在服务器生成真实 .env 和 .env.production
── docker-compose.mysql.yml # 服务器 MySQL Compose 模板
│ ├── create-env.sh # 在服务器生成测试/生产真实环境变量
── docker-compose.mysql.test.yml # 测试 MySQL Compose 模板
│ └── docker-compose.mysql.production.yml # 生产 MySQL Compose 模板
├── migrations/ # 数据库迁移 SQL
│ ├── 001_initial_schema.sql # 创建基础表结构
│ ├── 002_seed_demo_data.sql # 初始化演示门店和角色
@@ -88,14 +96,18 @@
| 路径 | 作用 |
| --- | --- |
| `.agents/README.md` | 项目级 Agent skill 索引和使用入口。 |
| `.agents/skills/chinese-commit-message/` | 项目内 skill。约定提交信息使用英文 type 前缀加中文描述。 |
| `.agents/skills/header-comment-sync/` | 项目内 skill。约定触达代码文件时同步中文文件头、导出声明和复杂逻辑注释。 |
| `.agents/skills/readme-structure-sync/` | 项目内 skill。约定当目录、重要文件或 `package.json` 脚本变化时,同步更新 README。 |
| `.env.development` | 当前 `package.json` 脚本默认读取的本地开发环境变量文件;该文件只保留在本机,不提交到仓库。 |
| `.env.local` / `.env.production` | 本机已有的其他环境变量文件;代码已允许 `NODE_ENV=local``NODE_ENV=production`,切换脚本时可以复用。 |
| `.env.local` / `.env.production` | 本机已有的其他环境变量文件;代码已允许 `APP_ENV=local/develop/production` 区分本地、测试和生产,切换脚本时可以复用。 |
| `.gitignore` | 忽略本地环境变量、依赖目录、编译产物和系统文件,避免把无关文件推到仓库。 |
| `AGENTS.md` | Agent 工具读取的入口文件,当前通过 `@RTK.md` 引入项目规则。 |
| `RTK.md` | 本项目的协作规则,例如使用中文说明、保持分层、改目录时同步 README。 |
| `docs/API.md` | 面向前端对接的完整接口文档,包含认证、权限、字段约束、示例请求响应和错误码。 |
| `deploy/server/` | 服务器部署辅助文件。`create-env.sh` 在服务器本地生成真实环境变量,`docker-compose.mysql.yml` 用于启动服务器 MySQL。 |
| `deploy/env/` | 本地、develop/测试、生产环境变量示例,只给字段结构,不提交真实密码。 |
| `deploy/server/` | 服务器部署辅助文件。`create-env.sh` 在服务器本地生成测试/生产真实环境变量,Compose 模板分别启动测试和生产 MySQL。 |
| `migrations/` | 数据库迁移目录。所有建表、改表、初始化基础数据的 SQL 都放在这里。 |
| `src/app.ts` | 创建 Fastify 应用,注册路由,处理健康检查和全局错误。 |
| `src/server.ts` | 真正启动 HTTP 服务,监听端口,并处理优雅停机。 |
@@ -147,6 +159,8 @@ pnpm install
```env
NODE_ENV=development
APP_ENV=local
APP_ENV_LABEL=本地环境
PORT=3500
DB_HOST=127.0.0.1
@@ -160,7 +174,7 @@ JWT_SECRET=请使用至少 32 位的随机字符串
JWT_EXPIRES_IN=2h
```
代码允许的 `NODE_ENV` 值是:`local``development``test``production`。如果改用 `.env.local``.env.production` 启动,也需要包含同样的 `JWT_SECRET``JWT_EXPIRES_IN`
代码允许的 `APP_ENV` 值是:`local``develop``production`,其中历史 `test` 会兼容为 develop/测试环境。未显式配置 `APP_ENV_LABEL` 时,健康检查会按环境输出“本地环境”“测试环境”或“生产环境”。如果改用 `.env.local``.env.test``.env.production` 启动,也需要包含同样的 `JWT_SECRET``JWT_EXPIRES_IN`
## 启动步骤
@@ -227,60 +241,82 @@ curl http://localhost:3500/health
服务器部署时不要直接使用本地 `.env.development`,也不要把本机 `.env.*` 打进部署包。真实密码只应该在服务器本地生成和保存。
当前服务器按测试环境和生产环境拆分,默认路径如下:
| 环境 | 应用目录 | 默认端口 | 数据目录 |
| --- | --- | --- | --- |
| 测试环境 | `/srv/www/test/access-manage` | `3501` | `/srv/data/test/access-manage/mysql` |
| 生产环境 | `/srv/www/production/access-manage` | `3500` | `/srv/data/production/access-manage/mysql` |
1. 生成服务器真实环境变量:
```bash
cd /srv/www/access-manage
bash deploy/server/create-env.sh /srv/www/access-manage
cd /srv/www/test/access-manage
bash deploy/server/create-env.sh /srv/www/test/access-manage test
cd /srv/www/production/access-manage
bash deploy/server/create-env.sh /srv/www/production/access-manage production
```
这个脚本会生成两个不提交到仓库的文件:
这个脚本会生成不提交到仓库的文件:
```text
/srv/www/access-manage/.env
/srv/www/access-manage/.env.production
/srv/www/test/access-manage/.env.test.mysql
/srv/www/test/access-manage/.env.test
/srv/www/production/access-manage/.env.production.mysql
/srv/www/production/access-manage/.env.production
```
其中 `.env` 给 MySQL 容器使用,`.env.production` 给 Node 后端使用。如果 `.env` 已经存在,脚本会读取现有 `MYSQL_PASSWORD`,只补 `.env.production`;如果 `.env.production`存在,脚本会拒绝覆盖。
其中 `.env.*.mysql` 给 MySQL 容器使用,`.env.test` / `.env.production` 给 Node 后端使用。如果应用 env 已存在,脚本会拒绝覆盖。
2. 启动服务器 MySQL
```bash
docker-compose -f deploy/server/docker-compose.mysql.yml up -d mysql
cd /srv/www/test/access-manage
docker-compose --env-file .env.test.mysql -f deploy/server/docker-compose.mysql.test.yml up -d mysql
cd /srv/www/production/access-manage
docker-compose --env-file .env.production.mysql -f deploy/server/docker-compose.mysql.production.yml up -d mysql
```
服务器模板会把 MySQL 数据持久化到:
服务器模板会把 MySQL 数据分别持久化到:
```text
/srv/data/access-manage/mysql
/srv/data/test/access-manage/mysql
/srv/data/production/access-manage/mysql
```
当前模板默认后端通过服务器本机端口连接 MySQL:
```env
DB_HOST=127.0.0.1
DB_PORT=3307
DB_PORT=3308 # 测试
DB_PORT=3307 # 生产
```
3. 安装生产依赖、迁移、启动:
```bash
pnpm install --prod --frozen-lockfile
pnpm db:migrate:test
pnpm start:test
pnpm db:migrate:prod
pnpm start:prod
```
`pnpm build:dev``pnpm build:pro` 只生成编译后的 `dist/` 目录。服务器上不需要再执行 `pnpm build`
`pnpm build:dev``pnpm build:test``pnpm build:pro` 只生成编译后的 `dist/` 目录。服务器上不需要再执行 `pnpm build`
4. 健康检查:
```bash
curl http://127.0.0.1:3500/health
curl http://127.0.0.1:3501/health # 测试
curl http://127.0.0.1:3500/health # 生产
```
只有返回里出现 `database: "up"`,才代表后端服务和 MySQL 都连通。
如果手动压缩部署,至少需要包含 `dist/``migrations/``deploy/``package.json``pnpm-lock.yaml`。不要把本机 `.env.*``node_modules/``src/``tsconfig.json``.git/` 打进部署包。
如果手动压缩部署,至少需要包含 `dist/``migrations/``package.json``pnpm-lock.yaml`。不要把本机 `.env.*``node_modules/``src/``tsconfig.json``.git/` 打进部署包。
可以在本机执行:
@@ -288,7 +324,11 @@ curl http://127.0.0.1:3500/health
pnpm build:dev
```
命令执行完成后会得到最新的 `dist/`。如果需要压缩上传,由部署者手动选择要打包的文件;正式生产构建时可以执行 `pnpm build:pro`
命令执行完成后会得到最新的 `dist/`。如果需要压缩上传,由部署者手动选择要打包的文件;develop/测试构建可执行 `pnpm build:develop` 或兼容命令 `pnpm build:test`正式生产构建时执行 `pnpm build:pro`
## 环境构建约定
仓库只维护项目自身的构建脚本和环境变量示例,不维护流水线文件。测试、生产的触发规则和部署路径由外部 CI/CD 平台配置。
## package.json 脚本说明
@@ -299,11 +339,17 @@ pnpm build:dev
| `pnpm dev` | 使用现有 `.env.development` 启动开发服务,并通过 `tsx watch` 监听代码变化。 | 日常开发接口时使用。 |
| `pnpm build` | 使用 `tsc` 编译 TypeScript,输出到 `dist/`。 | 准备运行编译产物或发布前验证时使用。 |
| `pnpm build:dev` | 清空旧 `dist/`,再执行 `pnpm build` 生成新的 `dist/`。 | 准备开发/测试服务器部署产物时使用。 |
| `pnpm build:pro` | 清空旧 `dist/`,再执行 `pnpm build` 生成新的 `dist/`。 | 准备正式生产部署产物时使用。 |
| `pnpm build:develop` | 清空旧 `dist/`,再执行 `pnpm build` 生成新的 `dist/`。 | 准备 develop/测试环境部署产物时使用。 |
| `pnpm build:test` | `pnpm build:develop` 的兼容命令。 | 旧测试环境脚本继续可用。 |
| `pnpm build:pro` | 清空旧 `dist/`,再执行 `pnpm build` 生成新的 `dist/`。 | 准备生产环境部署产物时使用。 |
| `pnpm start` | 使用现有 `.env.development` 运行 `dist/server.js`。 | 已经执行过 `pnpm build` 后,用编译产物启动服务。 |
| `pnpm start:develop` | 使用 `.env.test` 运行 `dist/server.js`,其中 `APP_ENV=develop`。 | 服务器 develop/测试环境启动编译产物时使用。 |
| `pnpm start:test` | `pnpm start:develop` 的兼容命令。 | 旧测试环境服务命令继续可用。 |
| `pnpm start:prod` | 使用 `.env.production` 运行 `dist/server.js`。 | 服务器生产环境启动编译产物时使用。 |
| `pnpm typecheck` | 执行 `tsc --noEmit`,只检查类型,不生成文件。 | 改 TypeScript 代码后快速确认类型是否正确。 |
| `pnpm db:migrate` | 使用现有 `.env.development` 运行 `src/db/migrate.ts`,按顺序执行 `migrations/*.sql`。 | 第一次启动项目、拉到新迁移、改数据库结构后使用。 |
| `pnpm db:migrate:develop` | 使用 `.env.test` 运行 `dist/db/migrate.js`,按顺序执行 `migrations/*.sql`。 | 服务器 develop/测试环境建表、升级表结构或初始化基础数据时使用。 |
| `pnpm db:migrate:test` | `pnpm db:migrate:develop` 的兼容命令。 | 旧测试环境迁移命令继续可用。 |
| `pnpm db:migrate:prod` | 使用 `.env.production` 运行 `dist/db/migrate.js`,按顺序执行 `migrations/*.sql`。 | 服务器生产环境建表、升级表结构或初始化基础数据时使用。 |
| `pnpm db:shell` | 进入 Docker 容器里的 MySQL 命令行。 | 需要手动查看表结构或查询数据时使用。 |
| `pnpm mysql:up` | 启动本地 MySQL 容器。 | 开发前先启动数据库。 |
+14
View File
@@ -0,0 +1,14 @@
NODE_ENV=development
APP_ENV=local
APP_ENV_LABEL=本地环境
PORT=3500
DB_HOST=127.0.0.1
DB_PORT=3307
DB_USER=access_user
DB_PASSWORD=replace-with-local-password
DB_NAME=access_manage
DB_CONNECTION_LIMIT=10
JWT_SECRET=replace-with-at-least-32-characters-local-secret
JWT_EXPIRES_IN=2h
+14
View File
@@ -0,0 +1,14 @@
NODE_ENV=production
APP_ENV=production
APP_ENV_LABEL=生产环境
PORT=3500
DB_HOST=127.0.0.1
DB_PORT=3307
DB_USER=access_user
DB_PASSWORD=replace-with-production-password
DB_NAME=access_manage
DB_CONNECTION_LIMIT=10
JWT_SECRET=replace-with-at-least-32-characters-production-secret
JWT_EXPIRES_IN=2h
+14
View File
@@ -0,0 +1,14 @@
NODE_ENV=test
APP_ENV=develop
APP_ENV_LABEL=测试环境
PORT=3501
DB_HOST=127.0.0.1
DB_PORT=3308
DB_USER=access_user
DB_PASSWORD=replace-with-test-password
DB_NAME=access_manage_test
DB_CONNECTION_LIMIT=10
JWT_SECRET=replace-with-at-least-32-characters-test-secret
JWT_EXPIRES_IN=2h
+36 -8
View File
@@ -2,11 +2,37 @@
set -euo pipefail
target_dir="${1:-$(pwd)}"
mysql_env="${target_dir}/.env"
app_env="${target_dir}/.env.production"
deploy_env="${2:-production}"
case "${deploy_env}" in
test|develop)
mysql_env="${target_dir}/.env.test.mysql"
app_env="${target_dir}/.env.test"
node_env="test"
app_env_name="develop"
app_env_label="测试环境"
app_port="${ACCESS_MANAGE_TEST_PORT:-3501}"
mysql_port="${ACCESS_MANAGE_TEST_DB_PORT:-3308}"
mysql_database="${ACCESS_MANAGE_TEST_DB_NAME:-access_manage_test}"
;;
production)
mysql_env="${target_dir}/.env.production.mysql"
app_env="${target_dir}/.env.production"
node_env="production"
app_env_name="production"
app_env_label="生产环境"
app_port="${ACCESS_MANAGE_PRODUCTION_PORT:-3500}"
mysql_port="${ACCESS_MANAGE_PRODUCTION_DB_PORT:-3307}"
mysql_database="${ACCESS_MANAGE_PRODUCTION_DB_NAME:-access_manage}"
;;
*)
echo "Usage: $0 [target_dir] [test|develop|production]" >&2
exit 1
;;
esac
if [[ -e "${app_env}" ]]; then
echo "Refuse to overwrite existing .env.production in ${target_dir}" >&2
echo "Refuse to overwrite existing $(basename "${app_env}") in ${target_dir}" >&2
exit 1
fi
@@ -22,7 +48,7 @@ if [[ -e "${mysql_env}" ]]; then
# shellcheck disable=SC1090
source "${mysql_env}"
mysql_database="${MYSQL_DATABASE:-access_manage}"
mysql_database="${MYSQL_DATABASE:-${mysql_database}}"
mysql_user="${MYSQL_USER:-access_user}"
mysql_password="${MYSQL_PASSWORD:-}"
@@ -34,7 +60,6 @@ if [[ -e "${mysql_env}" ]]; then
echo "Found existing ${mysql_env}; only creating ${app_env}"
else
mysql_root_password="root_$(openssl rand -hex 24)"
mysql_database="access_manage"
mysql_user="access_user"
mysql_password="app_$(openssl rand -hex 24)"
@@ -43,6 +68,7 @@ MYSQL_ROOT_PASSWORD=${mysql_root_password}
MYSQL_DATABASE=${mysql_database}
MYSQL_USER=${mysql_user}
MYSQL_PASSWORD=${mysql_password}
MYSQL_HOST_PORT=${mysql_port}
EOF
echo "Created ${mysql_env}"
@@ -51,11 +77,13 @@ fi
jwt_secret="$(openssl rand -hex 48)"
cat > "${app_env}" <<EOF
NODE_ENV=production
PORT=3500
NODE_ENV=${node_env}
APP_ENV=${app_env_name}
APP_ENV_LABEL=${app_env_label}
PORT=${app_port}
DB_HOST=127.0.0.1
DB_PORT=3307
DB_PORT=${mysql_port}
DB_USER=${mysql_user}
DB_PASSWORD=${mysql_password}
DB_NAME=${mysql_database}
@@ -0,0 +1,22 @@
version: "3.8"
services:
mysql:
image: mysql:8.4
container_name: access-manage-mysql-production
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
TZ: Asia/Shanghai
ports:
- "${MYSQL_HOST_PORT:-3307}:3306"
volumes:
- /srv/data/production/access-manage/mysql:/var/lib/mysql
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -p$${MYSQL_ROOT_PASSWORD} --silent"]
interval: 5s
timeout: 3s
retries: 20
@@ -0,0 +1,22 @@
version: "3.8"
services:
mysql:
image: mysql:8.4
container_name: access-manage-mysql-test
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
TZ: Asia/Shanghai
ports:
- "${MYSQL_HOST_PORT:-3308}:3306"
volumes:
- /srv/data/test/access-manage/mysql:/var/lib/mysql
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -p$${MYSQL_ROOT_PASSWORD} --silent"]
interval: 5s
timeout: 3s
retries: 20
+6
View File
@@ -7,11 +7,17 @@
"dev": "DOTENV_CONFIG_PATH=.env.development tsx watch src/server.ts",
"build": "tsc",
"build:dev": "rm -rf dist && pnpm build",
"build:develop": "rm -rf dist && pnpm build",
"build:test": "pnpm build:develop",
"build:pro": "rm -rf dist && pnpm build",
"start": "DOTENV_CONFIG_PATH=.env.development node dist/server.js",
"start:develop": "DOTENV_CONFIG_PATH=.env.test node dist/server.js",
"start:test": "pnpm start:develop",
"start:prod": "DOTENV_CONFIG_PATH=.env.production node dist/server.js",
"typecheck": "tsc --noEmit",
"db:migrate": "DOTENV_CONFIG_PATH=.env.development tsx src/db/migrate.ts",
"db:migrate:develop": "DOTENV_CONFIG_PATH=.env.test node dist/db/migrate.js",
"db:migrate:test": "pnpm db:migrate:develop",
"db:migrate:prod": "DOTENV_CONFIG_PATH=.env.production node dist/db/migrate.js",
"db:shell": "docker compose exec mysql mysql -uaccess_user -paccess_pass access_manage",
"mysql:up": "docker compose up -d mysql",
+2
View File
@@ -0,0 +1,2 @@
onlyBuiltDependencies:
- esbuild
+2
View File
@@ -55,6 +55,8 @@ export function createApp() {
return ok({
status: "ok",
environment: env.APP_ENV,
environmentLabel: env.APP_ENV_LABEL,
database: "up",
now: new Date().toISOString(),
});
+22 -2
View File
@@ -5,8 +5,10 @@ import { z } from "zod";
// 这样数据库密码、端口等配置错误会在服务启动阶段暴露,而不是等到请求进来才失败。
const envSchema = z.object({
NODE_ENV: z
.enum(["local", "development", "test", "production"])
.enum(["local", "development", "test", "develop", "production"])
.default("development"),
APP_ENV: z.enum(["local", "development", "test", "develop", "production"]).optional(),
APP_ENV_LABEL: z.string().min(1).optional(),
PORT: z.coerce.number().int().positive().default(3000),
DB_HOST: z.string().min(1),
@@ -29,5 +31,23 @@ if (!result.success) {
process.exit(1);
}
const rawAppEnv = result.data.APP_ENV ?? result.data.NODE_ENV;
const resolvedAppEnv =
rawAppEnv === "production"
? "production"
: rawAppEnv === "test" || rawAppEnv === "develop"
? "develop"
: "local";
const defaultAppEnvLabel =
{
local: "本地环境",
develop: "测试环境",
production: "生产环境"
}[resolvedAppEnv] ?? resolvedAppEnv;
// 其他模块只从 env 读取已校验过的值,不再直接访问 process.env。
export const env = result.data;
export const env = {
...result.data,
APP_ENV: resolvedAppEnv,
APP_ENV_LABEL: result.data.APP_ENV_LABEL ?? defaultAppEnvLabel
};