feat: 增加登录鉴权和固定角色权限

This commit is contained in:
湛兮
2026-05-26 12:14:33 +08:00
parent 643244abab
commit 55b99b5307
21 changed files with 957 additions and 250 deletions
+110 -45
View File
@@ -11,14 +11,19 @@
- MySQL 8.4
- mysql2
- zod
- @fastify/jwt
- Docker Compose
## 项目能力
- 门店管理:查询、新增、修改、软删除门店。
- 角色管理:查询、新增、修改、删除角色
- 角色管理:查询服务端固定角色,用于员工权限分配
- 员工管理:分页查询、新增、修改、启用/停用、软删除员工。
- 员工角色:一个员工可以绑定多个角色。
- 登录账号:超级管理员和员工都可以登录。
- 后台权限:超级管理员拥有所有权限;员工只有绑定 `admin` 角色时才能访问后台管理接口。
- 固定角色:店长、收银员、后厨、兼职、管理员是服务端固定角色,不提供角色新增、修改、删除接口。
- JWT 鉴权:登录后签发 token,除健康检查和登录外,接口都需要 Bearer token。
- 数据校验:使用 zod 校验路径参数、查询参数和请求体。
- 数据库迁移:使用 `migrations/*.sql` 管理建表和初始化数据。
- 事务处理:创建/更新员工和角色绑定时使用事务,避免部分成功。
@@ -30,13 +35,16 @@
├── .agents/
│ └── skills/
│ └── readme-structure-sync/ # README 和目录结构同步维护规则
├── .env.development # 本地开发环境变量文件,不提交到仓库
├── .env.development # 默认本地开发环境变量文件,不提交到仓库
├── .env.local / .env.production # 可选环境变量文件,不提交到仓库
├── .gitignore # Git 忽略规则,排除本地配置、依赖和编译产物
├── AGENTS.md # Codex/Agent 入口指令,当前指向 RTK.md
├── RTK.md # 项目协作规则和开发约定
├── migrations/ # 数据库迁移 SQL
│ ├── 001_initial_schema.sql # 创建基础表结构
── 002_seed_demo_data.sql # 初始化演示门店和角色
── 002_seed_demo_data.sql # 初始化演示门店和角色
│ ├── 003_create_super_admins.sql # 创建超级管理员表和默认账号
│ └── 004_add_employee_login_fields.sql # 给员工补充登录字段
├── src/
│ ├── app.ts # 创建 Fastify 应用、注册路由、统一错误处理
│ ├── server.ts # 启动 HTTP 服务和优雅停机
@@ -46,6 +54,7 @@
│ │ ├── migrate.ts # 执行 migrations 目录下的 SQL
│ │ └── pool.ts # MySQL 连接池
│ ├── modules/
│ │ ├── auth/ # 登录、当前用户和 JWT 鉴权模块
│ │ ├── catalog/ # 门店和角色模块
│ │ └── employees/ # 员工 CRUD 模块
│ └── shared/ # 通用响应结构和业务错误
@@ -61,7 +70,8 @@
| 路径 | 作用 |
| --- | --- |
| `.agents/skills/readme-structure-sync/` | 项目内 skill。约定当目录、重要文件或 `package.json` 脚本变化时,同步更新 README。 |
| `.env.development` | 当前项目本地开发使用的环境变量文件,`package.json` 脚本会显式读取它;该文件只保留在本机,不提交到仓库。 |
| `.env.development` | 当前 `package.json` 脚本默认读取的本地开发环境变量文件;该文件只保留在本机,不提交到仓库。 |
| `.env.local` / `.env.production` | 本机已有的其他环境变量文件;代码已允许 `NODE_ENV=local``NODE_ENV=production`,切换脚本时可以复用。 |
| `.gitignore` | 忽略本地环境变量、依赖目录、编译产物和系统文件,避免把无关文件推到仓库。 |
| `AGENTS.md` | Agent 工具读取的入口文件,当前通过 `@RTK.md` 引入项目规则。 |
| `RTK.md` | 本项目的协作规则,例如使用中文说明、保持分层、改目录时同步 README。 |
@@ -71,6 +81,7 @@
| `src/config/env.ts` | 使用 zod 校验 `.env.development` 中的环境变量,避免配置错误拖到请求阶段才暴露。 |
| `src/db/migrate.ts` | 执行 `migrations/*.sql`,并用 `schema_migrations` 记录已执行迁移。 |
| `src/db/pool.ts` | 创建 MySQL 连接池,提供数据库健康检查和关闭连接的方法。 |
| `src/modules/auth/` | 登录鉴权模块,负责超级管理员和员工登录、密码校验、JWT 签发、当前用户查询和后台权限 guard。 |
| `src/modules/catalog/` | 门店和角色模块,负责基础资料接口。 |
| `src/modules/employees/` | 员工模块,负责员工分页、详情、新增、修改、状态变更和软删除。 |
| `src/shared/` | 跨模块复用的响应结构和业务错误类型。 |
@@ -105,7 +116,7 @@
pnpm install
```
本地开发直接使用现有的 `.env.development`,当前配置如下
本地开发默认使用现有的 `.env.development`,当前需要这些变量
```env
NODE_ENV=development
@@ -117,8 +128,13 @@ DB_USER=access_user
DB_PASSWORD=access_pass
DB_NAME=access_manage
DB_CONNECTION_LIMIT=10
JWT_SECRET=请使用至少 32 位的随机字符串
JWT_EXPIRES_IN=2h
```
代码允许的 `NODE_ENV` 值是:`local``development``test``production`。如果改用 `.env.local``.env.production` 启动,也需要包含同样的 `JWT_SECRET``JWT_EXPIRES_IN`
## 启动步骤
1. 启动 MySQL
@@ -141,6 +157,7 @@ pnpm db:migrate
- `roles`:角色表
- `employees`:员工表
- `employee_roles`:员工角色关系表
- `super_admins`:超级管理员表
- `schema_migrations`:迁移记录表
3. 启动后端:
@@ -238,24 +255,83 @@ pnpm dev
}
```
## 登录和鉴权
本项目有两类可登录账号:
- 超级管理员:拥有所有后台管理权限。
- 员工:都可以登录;只有绑定 `admin` 角色的员工才能访问后台管理接口。
默认本地超级管理员账号由 [003_create_super_admins.sql](./migrations/003_create_super_admins.sql) 初始化:
```text
账号:admin
密码:Admin@123456
```
员工登录字段由 [004_add_employee_login_fields.sql](./migrations/004_add_employee_login_fields.sql) 初始化。已有员工和新建员工默认密码是:
```text
账号:员工手机号
密码:Employee@123456
```
登录获取 token
```bash
curl -X POST http://localhost:3500/api/auth/login \
-H 'Content-Type: application/json' \
-d '{
"username": "admin",
"password": "Admin@123456"
}'
```
响应里的 `data.token` 就是后续接口要使用的 JWT。
响应里的 `data.user.canManage` 表示当前账号是否能访问后台管理接口。
为了方便测试,可以先把 token 保存成 shell 变量:
```bash
TOKEN="把登录响应里的 data.token 粘贴到这里"
```
获取当前登录用户:
```bash
curl http://localhost:3500/api/auth/me \
-H "Authorization: Bearer $TOKEN"
```
`/health``/api/auth/login` 外,当前接口都需要带上:
```bash
-H "Authorization: Bearer $TOKEN"
```
如果员工账号没有 `admin` 角色,可以登录并访问 `/api/auth/me`,但访问门店、角色、员工等后台管理接口会返回 `403 FORBIDDEN`
## 门店接口示例
查询门店选项:
```bash
curl http://localhost:3500/api/stores
curl http://localhost:3500/api/stores \
-H "Authorization: Bearer $TOKEN"
```
查询包含停用门店的列表:
```bash
curl 'http://localhost:3500/api/stores?includeInactive=true'
curl 'http://localhost:3500/api/stores?includeInactive=true' \
-H "Authorization: Bearer $TOKEN"
```
新增门店:
```bash
curl -X POST http://localhost:3500/api/stores \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"name": "人民广场店",
@@ -269,6 +345,7 @@ curl -X POST http://localhost:3500/api/stores \
```bash
curl -X PATCH http://localhost:3500/api/stores/1 \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"phone": "021-87654321"
@@ -278,55 +355,30 @@ curl -X PATCH http://localhost:3500/api/stores/1 \
删除门店:
```bash
curl -X DELETE http://localhost:3500/api/stores/1
curl -X DELETE http://localhost:3500/api/stores/1 \
-H "Authorization: Bearer $TOKEN"
```
门店下还有员工时,不能停用或删除门店。
## 角色接口示例
角色是服务端固定权限集合,只允许查询,不允许通过接口新增、修改或删除。
查询角色:
```bash
curl http://localhost:3500/api/roles
curl http://localhost:3500/api/roles \
-H "Authorization: Bearer $TOKEN"
```
新增角色:
```bash
curl -X POST http://localhost:3500/api/roles \
-H 'Content-Type: application/json' \
-d '{
"code": "shift_leader",
"name": "班组长",
"description": "负责当班现场协调"
}'
```
修改角色:
```bash
curl -X PATCH http://localhost:3500/api/roles/1 \
-H 'Content-Type: application/json' \
-d '{
"description": "负责门店日常管理和权限审批"
}'
```
删除角色:
```bash
curl -X DELETE http://localhost:3500/api/roles/1
```
角色已绑定员工时,不能删除。
## 员工接口示例
新增员工:
```bash
curl -X POST http://localhost:3500/api/employees \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"storeId": 1,
@@ -340,31 +392,36 @@ curl -X POST http://localhost:3500/api/employees \
查询员工列表:
```bash
curl 'http://localhost:3500/api/employees?page=1&pageSize=20'
curl 'http://localhost:3500/api/employees?page=1&pageSize=20' \
-H "Authorization: Bearer $TOKEN"
```
按门店和状态筛选:
```bash
curl 'http://localhost:3500/api/employees?storeId=1&status=ACTIVE&page=1&pageSize=20'
curl 'http://localhost:3500/api/employees?storeId=1&status=ACTIVE&page=1&pageSize=20' \
-H "Authorization: Bearer $TOKEN"
```
按姓名或手机号搜索:
```bash
curl 'http://localhost:3500/api/employees?keyword=张三'
curl 'http://localhost:3500/api/employees?keyword=张三' \
-H "Authorization: Bearer $TOKEN"
```
查询员工详情:
```bash
curl http://localhost:3500/api/employees/1
curl http://localhost:3500/api/employees/1 \
-H "Authorization: Bearer $TOKEN"
```
修改员工:
```bash
curl -X PATCH http://localhost:3500/api/employees/1 \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"name": "张三丰",
@@ -376,6 +433,7 @@ curl -X PATCH http://localhost:3500/api/employees/1 \
```bash
curl -X PATCH http://localhost:3500/api/employees/1/status \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"status": "INACTIVE"}'
```
@@ -383,7 +441,8 @@ curl -X PATCH http://localhost:3500/api/employees/1/status \
软删除员工:
```bash
curl -X DELETE http://localhost:3500/api/employees/1
curl -X DELETE http://localhost:3500/api/employees/1 \
-H "Authorization: Bearer $TOKEN"
```
同一个门店下,未删除员工的手机号不能重复。员工软删除后,可以重新录入相同手机号。
@@ -396,6 +455,8 @@ curl -X DELETE http://localhost:3500/api/employees/1
- [001_initial_schema.sql](./migrations/001_initial_schema.sql):创建门店、角色、员工、员工角色关系表。
- [002_seed_demo_data.sql](./migrations/002_seed_demo_data.sql):写入一个示例门店和几个常见角色。
- [003_create_super_admins.sql](./migrations/003_create_super_admins.sql):创建超级管理员表,并初始化本地登录账号。
- [004_add_employee_login_fields.sql](./migrations/004_add_employee_login_fields.sql):给员工补充登录密码哈希和最后登录时间。
执行 `pnpm db:migrate` 时,脚本会:
@@ -416,10 +477,14 @@ migrations/003_add_employee_email.sql
- `stores.deleted_at``employees.deleted_at` 用于软删除。
- `employees.active_phone` 是生成列,用来实现“同一门店未删除员工手机号唯一”。
- `employees.password_hash` 让员工也能登录,默认本地密码是 `Employee@123456`
- `employee_roles` 是多对多关系表。
- `super_admins` 保存超级管理员账号,密码使用 PBKDF2 哈希,禁止存明文。
- 角色定义由服务端固定,`admin` 角色用于判断员工是否能访问后台管理接口。
- JWT 鉴权在 `src/modules/auth/` 中实现,`managementGuard` 统一保护后台管理接口。
- `repository` 使用参数化查询,避免 SQL 注入。
- `service` 使用事务保证员工信息和角色绑定同时成功或同时失败。
- `app.ts` 统一处理 zod 校验错误、业务错误和数据库唯一索引冲突。
- `app.ts` 统一注册 JWT、业务路由和错误处理,并处理 zod 校验错误、业务错误和数据库唯一索引冲突。
## README 维护规则