Files
backmanager-server/API.md
2026-06-25 21:53:24 +08:00

1153 lines
40 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 蜜雪冰城企业管理系统 —— 后端 API 文档
> 蜜雪冰城总部用于管理加盟商、合同、员工、产品、供应商及售后服务的企业信息管理系统后端服务。
## 目录
- [快速开始](#快速开始)
- [技术栈](#技术栈)
- [项目结构](#项目结构)
- [认证机制](#认证机制)
- [API 接口总览](#api-接口总览)
- [用户认证](#1-用户认证)
- [用户管理](#2-用户管理)
- [加盟商管理](#3-加盟商管理)
- [员工管理](#4-员工管理)
- [合同管理](#5-合同管理)
- [售后管理](#6-售后管理)
- [产品管理](#7-产品管理)
- [供应商管理](#8-供应商管理)
- [统一响应格式](#统一响应格式)
- [数据库设计](#数据库设计)
- [表结构详情](#表结构详情)
- [表关系 ER 图](#表关系-er-图)
- [RBAC 权限系统](#rbac-权限系统)
- [角色定义](#角色定义)
- [权限列表](#权限列表)
- [角色-权限映射](#角色-权限映射)
- [数据范围隔离](#数据范围隔离)
- [预置账号](#预置账号)
---
## 快速开始
### 环境要求
- Node.js >= 18
- MySQL 5.7+ / MariaDB 10.2+
### 安装与运行
```bash
# 1. 安装依赖
npm install
# 2. 配置环境变量
cp .env.example .env
# 编辑 .env 填入数据库连接信息和 JWT 密钥
# 3. 启动开发服务(自动重启)
npm run dev
# 4. 启动生产服务
npm start
# 5. 运行集成测试(需先启动服务)
node test-all.js
```
### 环境变量 (.env)
| 变量 | 说明 | 默认值 |
|------|------|--------|
| `PORT` | 服务端口 | `3000` |
| `DB_HOST` | MySQL 主机 | — |
| `DB_PORT` | MySQL 端口 | `3306` |
| `DB_USER` | MySQL 用户名 | — |
| `DB_PASSWORD` | MySQL 密码 | — |
| `DB_NAME` | 数据库名 | `backmanager` |
| `JWT_SECRET` | JWT 签名密钥 | — |
| `JWT_EXPIRES_IN` | Token 有效期 | `2h` |
> 首次启动时,系统会自动创建数据库、建表、初始化角色权限和预置用户。
---
## 技术栈
| 技术 | 用途 |
|------|------|
| Express 5 | Web 框架 |
| mysql2/promise | MySQL 驱动(连接池 + Promise |
| jsonwebtoken | JWT 认证 |
| bcryptjs | 密码哈希加密 |
| dotenv | 环境变量管理 |
---
## 项目结构
```
backmanager-server/
├── server.js # 入口Express 应用、鉴权中间件、路由注册
├── db.js # 数据库:连接池、建表、种子数据
├── middleware/
│ └── permissions.js # RBAC权限检查 + 数据范围过滤
├── routes/
│ ├── users.js # 用户登录/信息/CRUD
│ ├── customers.js # 加盟商 CRUD
│ ├── employees.js # 员工 CRUD
│ ├── contracts.js # 合同 CRUD
│ ├── afterSales.js # 售后 CRUD
│ ├── products.js # 产品 CRUD
│ └── suppliers.js # 供应商 CRUD
├── test-all.js # 集成测试(覆盖 6 个角色的权限与数据隔离)
├── .env.example # 环境变量模板
└── package.json
```
---
## 认证机制
所有 API除登录外需要在请求头中携带 JWT Token
```
Authorization: Bearer <token>
```
### Token 获取
通过 `POST /api/user/login` 登录获取 Token。
### Token 有效载荷 (Payload)
```json
{
"id": 1,
"username": "admin",
"name": "系统管理员",
"role_id": 1,
"roleName": "admin",
"department": "信息技术部",
"permissions": ["customer:read", "customer:create", "customer:update", "customer:delete", "contract:read", "..."]
}
```
### 认证流程
```
客户端请求 → auth 中间件验证 Token → checkPermission 检查权限 → 路由处理 → 数据范围过滤getDataScope→ 返回响应
```
### 错误响应
| HTTP 状态码 | 场景 |
|------------|------|
| 401 | 未携带 Token 或 Token 无效/过期 |
| 403 | 权限不足(无对应操作权限或数据不在可见范围内) |
---
## API 接口总览
### 通用说明
- **分页参数**:所有列表接口支持 `page`(页码,从 1 开始,默认 1`pageSize`(每页条数,默认 10最大 100
- **分页响应**:列表接口返回 `{ list, total, page, pageSize, totalPages }`
- **搜索参数**:通过 URL Query String 传递,如 `GET /api/customers?name=张&phone=138`
- **部分更新**PUT 接口只传需要修改的字段即可,未传字段保持不变
---
### 1. 用户认证
#### POST /api/user/login — 登录
无需 Token。
**请求体:**
```json
{
"username": "admin",
"password": "123456"
}
```
**成功响应:**
```json
{
"code": 200,
"message": "ok",
"data": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"userInfo": {
"id": 1,
"username": "admin",
"name": "系统管理员",
"role_id": 1,
"roleName": "admin",
"department": "信息技术部",
"permissions": ["customer:read", "customer:create", "..."]
}
}
}
```
> **注意**:登录成功返回 `code: 200`,其他接口统一返回 `code: 0`。前端通过 `res.code === 0 || res.code === 200` 兼容判断。
**错误响应:**
| code | message |
|------|---------|
| 400 | 用户名和密码必填 |
| 400 | 账号或密码错误 |
| 403 | 账号已被禁用,请联系管理员 |
#### GET /api/user/info — 获取当前用户信息
需要 Token。返回当前登录用户的完整信息含角色名、员工名
#### GET /api/user/list — 简易用户列表
需要 Token。返回所有启用用户的基本信息id、用户名、真实姓名用于前端下拉选择负责人。
**响应数据字段:**
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | number | 用户 ID |
| `username` | string | 用户名 |
| `real_name` | string | 真实姓名(来自员工表,可能为 null |
#### POST /api/user/logout — 登出
需要 Token。服务端无状态仅返回成功应答客户端自行清除 Token。
#### PUT /api/user/password — 修改密码
需要 Token。修改当前登录用户自己的密码。
**请求体:**
```json
{
"oldPassword": "123456",
"newPassword": "654321"
}
```
**校验规则:**
- 旧密码和新密码必填
- 新密码至少 6 位
- 新密码不能与旧密码相同
---
### 2. 用户管理
> 需要 `user:manage` 权限(仅系统管理员拥有)。
#### GET /api/users — 用户列表
**查询参数:**
| 参数 | 说明 | 示例 |
|------|------|------|
| `username` | 按用户名模糊搜索 | `?username=admin` |
| `real_name` | 按真实姓名模糊搜索 | `?real_name=张` |
| `id` | 按用户 ID 精确搜索 | `?id=1` |
| `role_id` | 按角色 ID 过滤 | `?role_id=2` |
| `is_active` | 按状态过滤0-禁用, 1-启用) | `?is_active=1` |
| `page` | 页码 | `?page=1` |
| `pageSize` | 每页条数 | `?pageSize=10` |
**响应数据字段:**
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | number | 用户 ID |
| `username` | string | 用户名 |
| `is_active` | number | 状态0-禁用, 1-启用 |
| `role_id` | number | 角色 ID |
| `role_name` | string | 角色标识(如 `admin` |
| `role_description` | string | 角色描述(如 系统管理员) |
| `employee_id` | number | 关联员工 ID |
| `real_name` | string | 真实姓名(来自员工表) |
| `department` | string | 所属部门 |
| `created_at` | datetime | 创建时间 |
| `updated_at` | datetime | 更新时间 |
#### GET /api/users/:id — 用户详情
返回单个用户完整信息。
#### POST /api/users — 创建用户
**请求体:**
| 字段 | 必填 | 说明 |
|------|------|------|
| `username` | ✅ | 用户名3-50 字符) |
| `password` | ✅ | 密码(至少 6 位) |
| `role_id` | ❌ | 角色 ID需为已存在的角色 |
| `employee_id` | ❌ | 关联员工 ID需为已存在的员工会自动同步部门信息 |
| `department` | ❌ | 所属部门(若填了 employee_id 且未传 department会自动从员工表同步 |
| `is_active` | ❌ | 状态,默认 1 |
#### PUT /api/users/:id — 更新用户
**可更新字段:** `username``role_id``employee_id``department``is_active``password`
**限制:**
- 不能修改自己的角色
- 不能禁用自己
#### DELETE /api/users/:id — 删除用户
**限制:**
- 不能删除自己
- 不能删除最后一个管理员账号(当系统中仅剩 1 个 admin 角色用户时,拒绝删除)
---
### 3. 加盟商管理
> 需要 `customer:read` / `customer:create` / `customer:update` / `customer:delete` 权限。
#### GET /api/customers — 加盟商列表
**查询参数:**
| 参数 | 说明 |
|------|------|
| `id` | 按 ID 精确搜索 |
| `name` | 按加盟商名称模糊搜索 |
| `phone` | 按电话模糊搜索 |
| `province` | 按省份精确匹配 |
| `city` | 按城市精确匹配 |
**响应数据字段(含 JOIN 字段):**
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | number | 加盟商 ID |
| `name` | string | 加盟商姓名 |
| `phone` | string | 联系电话 |
| `province` | string | 省 |
| `city` | string | 市 |
| `district` | string | 区 |
| `address` | string | 详细地址 |
| `email` | string | 电子邮箱 |
| `remark` | string | 备注信息 |
| `responsible_user_id` | number | 负责人用户 ID |
| `responsible_user_name` | string | 负责人姓名JOIN 自 users → employees |
| `created_at` | datetime | 创建时间 |
| `updated_at` | datetime | 更新时间 |
#### POST /api/customers — 新增加盟商
**请求体:**
| 字段 | 必填 | 说明 |
|------|------|------|
| `name` | ✅ | 加盟商姓名 |
| `phone` | ❌ | 联系电话 |
| `province` | ❌ | 省 |
| `city` | ❌ | 市 |
| `district` | ❌ | 区 |
| `address` | ❌ | 详细地址 |
| `email` | ❌ | 电子邮箱 |
| `remark` | ❌ | 备注 |
| `responsible_user_id` | ❌ | 负责人(默认为当前登录用户,仅管理员可在前端修改) |
#### PUT /api/customers/:id — 更新加盟商
所有字段均可选,只传需要修改的字段。
#### DELETE /api/customers/:id — 删除加盟商
---
### 4. 员工管理
> 需要 `employee:read` / `employee:create` / `employee:update` / `employee:delete` 权限。
#### GET /api/employees/simple — 简易员工列表
需要 Token。无数据范围限制用于前端下拉选择如售后分配业务员。支持按部门过滤。
**查询参数:**
| 参数 | 说明 |
|------|------|
| `department` | 按部门精确过滤(如 `?department=运营部` |
**响应数据字段:**
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | number | 员工 ID |
| `name` | string | 姓名 |
| `department` | string | 所属部门 |
| `position` | string | 职务/岗位 |
#### GET /api/employees — 员工列表
**查询参数:**
| 参数 | 说明 |
|------|------|
| `id` | 按 ID 精确搜索 |
| `name` | 按姓名模糊搜索 |
| `department` | 按部门模糊搜索 |
| `status` | 按在职状态过滤0-离职, 1-在职) |
**响应数据字段:**
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | number | 员工 ID |
| `name` | string | 姓名 |
| `gender` | string | 性别(男/女) |
| `age` | number | 年龄 |
| `education` | string | 学历 |
| `department` | string | 所属部门 |
| `entry_date` | date | 入职时间 |
| `position` | string | 职务/岗位 |
| `salary` | number | 工资金额 |
| `phone` | string | 联系电话 |
| `email` | string | 电子邮箱 |
| `status` | number | 在职状态0-离职, 1-在职 |
| `remark` | string | 备注 |
| `created_at` | datetime | 创建时间 |
| `updated_at` | datetime | 更新时间 |
#### POST /api/employees — 新增员工
**请求体:**
| 字段 | 必填 | 说明 |
|------|------|------|
| `name` | ✅ | 员工姓名 |
| `gender` | ❌ | 性别 |
| `age` | ❌ | 年龄 |
| `education` | ❌ | 学历 |
| `department` | ❌ | 部门 |
| `entry_date` | ❌ | 入职时间格式YYYY-MM-DD |
| `position` | ❌ | 职务 |
| `salary` | ❌ | 工资 |
| `phone` | ❌ | 电话 |
| `email` | ❌ | 邮箱 |
| `status` | ❌ | 在职状态,默认 1 |
| `remark` | ❌ | 备注 |
#### PUT /api/employees/:id — 更新员工
所有字段均可选,只传需要修改的字段。
**特殊行为:** 更新 `department` 字段时,会自动同步到关联的 users 表的 `department` 字段。
#### DELETE /api/employees/:id — 删除员工
**特殊行为:**
- 删除员工时会同步删除关联的用户账号users 表中 `employee_id` 匹配的记录)
- 不能删除当前登录用户自己关联的员工账号
---
### 5. 合同管理
> 需要 `contract:read` / `contract:create` / `contract:update` / `contract:delete` 权限。
#### GET /api/contracts — 合同列表
**查询参数:**
| 参数 | 说明 |
|------|------|
| `id` | 按 ID 精确搜索 |
| `status` | 按合同状态过滤(草稿/生效中/已完成/作废) |
| `customer_name` | 按客户名称模糊搜索 |
| `contract_no` | 按合同编号模糊搜索 |
| `contract_name` | 按合同名称模糊搜索 |
| `employee_name` | 按业务员姓名模糊搜索 |
| `effective_date_start` | 按生效日期起始过滤格式YYYY-MM-DD |
| `effective_date_end` | 按生效日期截止过滤格式YYYY-MM-DD |
**响应数据字段(含 JOIN 字段):**
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | number | 合同 ID |
| `customer_id` | number | 客户 ID |
| `customer_name` | string | 客户名称JOIN 自 customers |
| `supplier_id` | number | 供应商 ID采购合同必填 |
| `supplier_name` | string | 供应商名称JOIN 自 suppliers |
| `contract_name` | string | 合同名称 |
| `contract_no` | string | 合同编号 |
| `contract_content` | string | 合同内容/条款 |
| `amount` | number | 合同金额 |
| `effective_date` | date | 生效日期 |
| `expiry_date` | date | 到期日期 |
| `employee_id` | number | 业务员 ID |
| `employee_name` | string | 业务员姓名JOIN 自 employees |
| `status` | string | 状态:草稿/生效中/已完成/作废 |
| `remark` | string | 备注 |
| `type` | string | 合同类型:`franchise`(加盟)/ `supply`(采购) |
| `responsible_user_id` | number | 负责人用户 ID |
| `responsible_user_name` | string | 负责人姓名JOIN 自 users → employees |
| `created_at` | datetime | 创建时间 |
| `updated_at` | datetime | 更新时间 |
#### POST /api/contracts — 新增合同
**请求体:**
| 字段 | 必填 | 说明 |
|------|------|------|
| `customer_id` | 条件必填 | 关联客户 ID加盟合同必填 |
| `supplier_id` | 条件必填 | 关联供应商 ID采购合同必填 |
| `contract_name` | ✅ | 合同名称 |
| `contract_no` | ❌ | 合同编号 |
| `contract_content` | ❌ | 合同内容 |
| `amount` | ❌ | 合同金额 |
| `effective_date` | ❌ | 生效日期 |
| `expiry_date` | ❌ | 到期日期 |
| `employee_id` | ❌ | 业务员 ID |
| `status` | ❌ | 状态,默认 `生效中` |
| `remark` | ❌ | 备注 |
| `type` | ❌ | 合同类型(见下方说明) |
| `responsible_user_id` | ❌ | 负责人(默认为当前用户,仅管理员可在前端修改) |
**合同类型自动设置与角色限制规则:**
- 采购经理(`procurement_manager`)创建的合同**只能**为 `supply`(采购合同),且必须选择 `supplier_id`
- 招商经理(`franchise_manager`)创建的合同**只能**为 `franchise`(加盟合同),且必须选择 `customer_id`
- 其他角色默认 `franchise`,也可手动指定 `type`
**校验规则:**
- `customer_id` 必须为已存在的客户
- `supplier_id` 必须为已存在的供应商
- `employee_id`(如填写)必须为已存在的员工
#### PUT /api/contracts/:id — 更新合同
所有字段均可选。可更新字段包括 `type``supplier_id``responsible_user_id`
**校验规则:** 更新 `customer_id``supplier_id``employee_id` 时会校验关联记录是否存在。
#### DELETE /api/contracts/:id — 删除合同
---
### 6. 售后管理
> 需要 `after_sale:read` / `after_sale:create` / `after_sale:update` / `after_sale:delete` 权限。
#### GET /api/after-sales — 售后列表
**查询参数:**
| 参数 | 说明 |
|------|------|
| `id` | 按 ID 精确搜索 |
| `customer_id` | 按客户 ID 精确搜索 |
| `customer_name` | 按客户名称模糊搜索 |
| `handle_status` | 按处理状态过滤(待处理/处理中/已完成) |
| `feedback` | 按反馈内容模糊搜索 |
**响应数据字段(含 JOIN 字段):**
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | number | 售后记录 ID |
| `customer_id` | number | 客户 ID |
| `customer_name` | string | 客户名称JOIN 自 customers |
| `feedback` | string | 客户反馈意见 |
| `employee_id` | number | 处理业务员 ID |
| `employee_name` | string | 业务员姓名JOIN 自 employees |
| `handle_method` | string | 处理方式/解决方案 |
| `handle_status` | string | 处理状态:待处理/处理中/已完成 |
| `service_date` | date | 售后日期 |
| `remark` | string | 备注 |
| `responsible_user_id` | number | 负责人用户 ID |
| `created_at` | datetime | 创建时间 |
| `updated_at` | datetime | 更新时间 |
#### POST /api/after-sales — 新增售后
**请求体:**
| 字段 | 必填 | 说明 |
|------|------|------|
| `customer_id` | ✅ | 关联客户 ID |
| `feedback` | ✅ | 售后反馈内容 |
| `employee_id` | ❌ | 处理业务员 ID前端仅显示运营部员工 |
| `handle_method` | ❌ | 处理方式 |
| `handle_status` | ❌ | 处理状态,默认 `待处理` |
| `service_date` | ❌ | 售后日期 |
| `remark` | ❌ | 备注 |
| `responsible_user_id` | ❌ | 负责人(默认为当前用户,仅管理员可在前端修改) |
**校验规则:**
- `customer_id` 必须为已存在的客户
- `employee_id`(如填写)必须为已存在的员工
#### PUT /api/after-sales/:id — 更新售后
所有字段均可选。
**校验规则:** 更新 `customer_id``employee_id` 时会校验关联记录是否存在。
#### DELETE /api/after-sales/:id — 删除售后
---
### 7. 产品管理
> 需要 `product:read` / `product:create` / `product:update` / `product:delete` 权限。
> 无数据范围隔离,拥有权限即可查看所有产品。
#### GET /api/products — 产品列表
**查询参数:**
| 参数 | 说明 |
|------|------|
| `id` | 按 ID 精确搜索 |
| `name` | 按产品名称模糊搜索 |
| `type` | 按产品类型模糊搜索 |
| `supplier` | 按供应商名称模糊搜索 |
**响应数据字段:**
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | number | 产品 ID |
| `name` | string | 产品名称 |
| `type` | string | 产品类型/分类 |
| `quantity` | number | 库存数量 |
| `price` | number | 单价 |
| `unit` | string | 计量单位(默认"件" |
| `specification` | string | 规格/型号 |
| `supplier` | string | 供应商名称 |
| `remark` | string | 备注 |
| `created_at` | datetime | 创建时间 |
| `updated_at` | datetime | 更新时间 |
#### POST /api/products — 新增产品
**请求体:**
| 字段 | 必填 | 说明 |
|------|------|------|
| `name` | ✅ | 产品名称 |
| `type` | ❌ | 类型 |
| `quantity` | ❌ | 库存数量,默认 0 |
| `price` | ❌ | 单价,默认 0.00 |
| `unit` | ❌ | 单位,默认"件" |
| `specification` | ❌ | 规格 |
| `supplier` | ❌ | 供应商名称 |
| `remark` | ❌ | 备注 |
#### PUT /api/products/:id — 更新产品
所有字段均可选。
#### DELETE /api/products/:id — 删除产品
---
### 8. 供应商管理
> 需要 `supplier:read` / `supplier:create` / `supplier:update` / `supplier:delete` 权限。
> 无数据范围隔离,拥有权限即可查看所有供应商。
#### GET /api/suppliers — 供应商列表
**查询参数:**
| 参数 | 说明 |
|------|------|
| `id` | 按 ID 精确搜索 |
| `name` | 按供应商名称模糊搜索 |
| `type` | 按类型模糊搜索 |
| `status` | 按状态过滤0-停用, 1-正常) |
**响应数据字段:**
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | number | 供应商 ID |
| `name` | string | 供应商名称 |
| `contact` | string | 联系人 |
| `phone` | string | 联系电话 |
| `address` | string | 地址 |
| `type` | string | 类型(原材料/包装/设备) |
| `content` | string | 备注说明 |
| `status` | number | 状态0-停用, 1-正常 |
| `created_at` | datetime | 创建时间 |
| `updated_at` | datetime | 更新时间 |
#### POST /api/suppliers — 新增供应商
**请求体:**
| 字段 | 必填 | 说明 |
|------|------|------|
| `name` | ✅ | 供应商名称 |
| `contact` | ❌ | 联系人 |
| `phone` | ❌ | 联系电话 |
| `address` | ❌ | 地址 |
| `type` | ❌ | 类型 |
| `content` | ❌ | 备注说明 |
| `status` | ❌ | 状态,默认 1 |
#### PUT /api/suppliers/:id — 更新供应商
所有字段均可选。
#### DELETE /api/suppliers/:id — 删除供应商
---
## 统一响应格式
### 成功响应
```json
{
"code": 0,
"message": "ok",
"data": { ... }
}
```
### 列表响应
```json
{
"code": 0,
"message": "ok",
"data": {
"list": [ ... ],
"total": 100,
"page": 1,
"pageSize": 10,
"totalPages": 10
}
}
```
### 错误响应
```json
{
"code": 403,
"message": "权限不足"
}
```
| HTTP 状态码 | code | 说明 |
|------------|------|------|
| 400 | 400 | 参数错误 / 校验失败 / 关联记录不存在 |
| 401 | 401 | 未登录 / Token 无效 |
| 403 | 403 | 权限不足 / 账号被禁用 / 角色操作限制 |
| 404 | 404 | 资源不存在 |
| 409 | 409 | 数据冲突(如用户名重复) |
| 500 | 500 | 服务器内部错误 |
---
## 数据库设计
### 表结构详情
#### 1. users — 系统用户表
存储系统登录账号,每个用户关联一个角色和一个员工。
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | INT | PK, AUTO_INCREMENT | 用户 ID |
| `username` | VARCHAR(50) | NOT NULL, UNIQUE | 用户名(登录账号) |
| `password` | VARCHAR(255) | NOT NULL | 密码bcrypt 哈希) |
| `is_active` | TINYINT(1) | NOT NULL DEFAULT 1 | 账号状态0-禁用, 1-启用 |
| `role_id` | INT | NULLABLE | 角色 ID → roles.id |
| `employee_id` | INT | NULLABLE, UNIQUE | 员工 ID → employees.id |
| `department` | VARCHAR(100) | NULLABLE | 所属部门(冗余字段,从 employees 同步) |
| `created_at` | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
| `updated_at` | DATETIME | ON UPDATE CURRENT_TIMESTAMP | 更新时间 |
#### 2. roles — 角色表
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | INT | PK, AUTO_INCREMENT | 角色 ID |
| `name` | VARCHAR(50) | NOT NULL, UNIQUE | 角色标识(英文) |
| `description` | VARCHAR(200) | NULLABLE | 角色描述(中文) |
| `created_at` | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
#### 3. permissions — 权限表
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | INT | PK, AUTO_INCREMENT | 权限 ID |
| `name` | VARCHAR(100) | NOT NULL, UNIQUE | 权限标识(`resource:action` 格式) |
| `description` | VARCHAR(200) | NULLABLE | 权限描述(中文) |
| `resource` | VARCHAR(50) | NOT NULL | 资源名称 |
| `action` | VARCHAR(50) | NOT NULL | 操作类型 |
#### 4. role_permissions — 角色-权限关联表
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `role_id` | INT | PK (联合) | 角色 ID → roles.id |
| `permission_id` | INT | PK (联合) | 权限 ID → permissions.id |
#### 5. customers — 加盟商信息表
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | INT | PK, AUTO_INCREMENT | 加盟商 ID |
| `name` | VARCHAR(100) | NOT NULL, INDEX | 加盟商姓名 |
| `phone` | VARCHAR(20) | INDEX | 联系电话 |
| `province` | VARCHAR(50) | — | 省 |
| `city` | VARCHAR(50) | — | 市 |
| `district` | VARCHAR(50) | — | 区 |
| `address` | VARCHAR(200) | — | 详细地址 |
| `email` | VARCHAR(100) | — | 电子邮箱 |
| `remark` | TEXT | — | 备注信息 |
| `responsible_user_id` | INT | — | 负责人用户 ID → users.id |
| `created_at` | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
| `updated_at` | DATETIME | ON UPDATE CURRENT_TIMESTAMP | 更新时间 |
#### 6. employees — 员工信息表
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | INT | PK, AUTO_INCREMENT | 员工 ID |
| `name` | VARCHAR(100) | NOT NULL, INDEX | 姓名 |
| `gender` | VARCHAR(4) | — | 性别:男/女 |
| `age` | INT | — | 年龄 |
| `education` | VARCHAR(50) | — | 学历 |
| `department` | VARCHAR(100) | INDEX | 所属部门 |
| `entry_date` | DATE | — | 入职时间 |
| `position` | VARCHAR(100) | — | 职务/岗位 |
| `salary` | DECIMAL(10,2) | — | 工资金额 |
| `phone` | VARCHAR(20) | — | 联系电话 |
| `email` | VARCHAR(100) | — | 电子邮箱 |
| `status` | TINYINT(1) | NOT NULL DEFAULT 1 | 在职状态0-离职, 1-在职 |
| `remark` | TEXT | — | 备注 |
| `created_at` | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
| `updated_at` | DATETIME | ON UPDATE CURRENT_TIMESTAMP | 更新时间 |
#### 7. contracts — 合同信息表
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | INT | PK, AUTO_INCREMENT | 合同 ID |
| `customer_id` | INT | NULLABLE, INDEX | 客户 ID → customers.id加盟合同必填 |
| `supplier_id` | INT | NULLABLE, INDEX | 供应商 ID → suppliers.id采购合同必填 |
| `contract_name` | VARCHAR(200) | NOT NULL | 合同名称 |
| `contract_no` | VARCHAR(100) | — | 合同编号 |
| `contract_content` | TEXT | — | 合同内容/条款 |
| `amount` | DECIMAL(12,2) | — | 合同金额 |
| `effective_date` | DATE | INDEX | 生效日期 |
| `expiry_date` | DATE | — | 到期日期 |
| `employee_id` | INT | INDEX | 业务员 ID → employees.id |
| `status` | VARCHAR(20) | NOT NULL DEFAULT '生效中' | 状态:草稿/生效中/已完成/作废 |
| `remark` | TEXT | — | 备注 |
| `type` | VARCHAR(20) | NOT NULL DEFAULT 'franchise' | 类型:`franchise`(加盟) / `supply`(采购) |
| `responsible_user_id` | INT | — | 负责人用户 ID → users.id |
| `created_at` | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
| `updated_at` | DATETIME | ON UPDATE CURRENT_TIMESTAMP | 更新时间 |
#### 8. after_sales — 售后信息表
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | INT | PK, AUTO_INCREMENT | 售后记录 ID |
| `customer_id` | INT | NOT NULL, INDEX | 客户 ID → customers.id |
| `feedback` | TEXT | NOT NULL | 客户反馈意见 |
| `employee_id` | INT | INDEX | 处理业务员 ID → employees.id |
| `handle_method` | TEXT | — | 处理方式/解决方案 |
| `handle_status` | VARCHAR(20) | NOT NULL DEFAULT '待处理', INDEX | 状态:待处理/处理中/已完成 |
| `service_date` | DATE | — | 售后日期 |
| `remark` | TEXT | — | 备注 |
| `responsible_user_id` | INT | — | 负责人用户 ID → users.id |
| `created_at` | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
| `updated_at` | DATETIME | ON UPDATE CURRENT_TIMESTAMP | 更新时间 |
#### 9. products — 产品信息表
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | INT | PK, AUTO_INCREMENT | 产品 ID |
| `name` | VARCHAR(200) | NOT NULL, INDEX | 产品名称 |
| `type` | VARCHAR(100) | INDEX | 产品类型/分类 |
| `quantity` | INT | NOT NULL DEFAULT 0 | 库存数量 |
| `price` | DECIMAL(10,2) | NOT NULL DEFAULT 0.00 | 单价 |
| `unit` | VARCHAR(20) | DEFAULT '件' | 计量单位 |
| `specification` | VARCHAR(200) | — | 规格/型号 |
| `supplier` | VARCHAR(200) | — | 供应商名称 |
| `remark` | TEXT | — | 备注 |
| `created_at` | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
| `updated_at` | DATETIME | ON UPDATE CURRENT_TIMESTAMP | 更新时间 |
#### 10. suppliers — 供应商信息表
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | INT | PK, AUTO_INCREMENT | 供应商 ID |
| `name` | VARCHAR(200) | NOT NULL, INDEX | 供应商名称 |
| `contact` | VARCHAR(100) | — | 联系人 |
| `phone` | VARCHAR(50) | — | 联系电话 |
| `address` | VARCHAR(500) | — | 地址 |
| `type` | VARCHAR(50) | — | 类型(原材料/包装/设备) |
| `content` | TEXT | — | 备注说明 |
| `status` | TINYINT(1) | NOT NULL DEFAULT 1 | 状态0-停用, 1-正常 |
| `created_at` | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
| `updated_at` | DATETIME | ON UPDATE CURRENT_TIMESTAMP | 更新时间 |
---
### 表关系 ER 图
```
┌──────────────┐ ┌──────────────┐ ┌──────────────────┐
│ roles │ │ permissions │ │ role_permissions │
│──────────────│ │──────────────│ │──────────────────│
│ id (PK) │◄─┐ │ id (PK) │◄─┐ │ role_id (PK,FK) │
│ name │ │ │ name │ └──│ permission_id │
│ description │ │ │ description │ │ (PK,FK) │
└──────────────┘ │ │ resource │ └──────────────────┘
│ │ action │
│ └──────────────┘
┌──────────────┐ │ ┌──────────────┐
│ users │ │ │ employees │
│──────────────│ │ │──────────────│
│ id (PK) │ │ │ id (PK) │
│ username │ │ │ name │
│ password │ │ │ gender │
│ is_active │ │ │ age │
│ role_id (FK)─┼──┘ │ education │
│ employee_id │─ ─ ─│ department │◄──────── users.department (冗余)
│ (FK) ──────┼──→ │ entry_date │
│ department │ │ position │
│ (冗余) │ │ salary │
└──────┬───────┘ │ phone │
│ │ email │
│ │ status │
│ └──────┬───────┘
│ │
▼ ▼
┌──────────────────────────────────────────────┐
│ customers (加盟商) │
│──────────────────────────────────────────────│
│ id (PK) │
│ name, phone, province, city, district, ... │
│ responsible_user_id ──────────→ users.id │
└────────────────────┬─────────────────────────┘
┌──────────┴──────────┐
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ contracts │ │ after_sales │
│──────────────────│ │──────────────────│
│ id (PK) │ │ id (PK) │
│ customer_id (FK) │ │ customer_id (FK) │
│ → customers.id │ │ → customers.id │
│ supplier_id (FK) │ │ feedback │
│ → suppliers.id │ │ employee_id (FK) │
│ contract_name │ │ → employees.id │
│ contract_no │ │ handle_method │
│ amount │ │ handle_status │
│ employee_id (FK) │ │ service_date │
│ → employees.id │ │ responsible_user │
│ status │ │ _id → users.id │
│ type │ └──────────────────┘
│ franchise/ │
│ supply │
│ responsible_user │
│ _id → users.id │
└──────────────────┘
┌──────────────┐ ┌──────────────┐
│ products │ │ suppliers │
│──────────────│ │──────────────│
│ id (PK) │ │ id (PK) │
│ name │ │ name │
│ type │ │ contact │
│ quantity │ │ phone │
│ price │ │ address │
│ unit │ │ type │
│ specification│ │ content │
│ supplier │ │ status │
└──────────────┘ └──────────────┘
```
**关系说明:**
| 关系 | 类型 | 说明 |
|------|------|------|
| users → roles | 多对一 | 一个用户属于一个角色,一个角色可以有多个用户 |
| users → employees | 一对一 | 一个用户关联一个员工(通过 employee_id唯一约束 |
| roles ↔ permissions | 多对多 | 通过 role_permissions 中间表关联 |
| customers → users | 多对一 | 通过 responsible_user_id 指定负责人 |
| contracts → customers | 多对一 | 通过 customer_id 关联客户(加盟合同必填) |
| contracts → suppliers | 多对一 | 通过 supplier_id 关联供应商(采购合同必填) |
| contracts → employees | 多对一 | 通过 employee_id 关联业务员 |
| contracts → users | 多对一 | 通过 responsible_user_id 指定负责人 |
| after_sales → customers | 多对一 | 通过 customer_id 关联客户 |
| after_sales → employees | 多对一 | 通过 employee_id 关联业务员 |
| after_sales → users | 多对一 | 通过 responsible_user_id 指定负责人 |
> **注意**:数据库未设置外键约束,引用完整性由应用代码保证(插入/更新前检查关联记录是否存在)。
---
## RBAC 权限系统
### 角色定义
系统预置 6 个角色,对应蜜雪冰城总部的组织架构:
| 角色标识 | 中文名 | 所属部门 | 职责概述 |
|---------|--------|---------|---------|
| `admin` | 系统管理员 | 信息技术部 | 系统全权管理,拥有所有权限 |
| `general_manager` | 总经理 | 总经理办公室 | 全局只读,查看所有业务数据 |
| `franchise_manager` | 招商经理 | 招商部 | 管理加盟商和加盟合同 |
| `operations_manager` | 运营经理 | 运营部 | 维护加盟商信息、处理售后 |
| `procurement_manager` | 采购经理 | 采购部 | 管理产品、供应商和采购合同 |
| `finance` | 财务人员 | 财务部 | 只读查看业务数据(加盟商、合同、售后、员工) |
### 权限列表
共 25 个权限,按 `资源:操作` 格式命名:
| 权限标识 | 中文描述 |
|---------|---------|
| **加盟商 (customer)** | |
| `customer:read` | 查看加盟商 |
| `customer:create` | 新增加盟商 |
| `customer:update` | 修改加盟商 |
| `customer:delete` | 删除加盟商 |
| **合同 (contract)** | |
| `contract:read` | 查看合同 |
| `contract:create` | 新增合同 |
| `contract:update` | 修改合同 |
| `contract:delete` | 删除合同 |
| **售后 (after_sale)** | |
| `after_sale:read` | 查看售后 |
| `after_sale:create` | 新增售后 |
| `after_sale:update` | 修改售后 |
| `after_sale:delete` | 删除售后 |
| **产品 (product)** | |
| `product:read` | 查看产品原料 |
| `product:create` | 新增产品原料 |
| `product:update` | 修改产品原料 |
| `product:delete` | 删除产品原料 |
| **供应商 (supplier)** | |
| `supplier:read` | 查看供应商 |
| `supplier:create` | 新增供应商 |
| `supplier:update` | 修改供应商 |
| `supplier:delete` | 删除供应商 |
| **员工 (employee)** | |
| `employee:read` | 查看员工 |
| `employee:create` | 新增员工 |
| `employee:update` | 修改员工 |
| `employee:delete` | 删除员工 |
| **用户 (user)** | |
| `user:manage` | 管理用户账号 |
### 角色-权限映射
| 权限 | admin | general_manager | franchise_manager | operations_manager | procurement_manager | finance |
|------|:-----:|:---------------:|:-----------------:|:------------------:|:-------------------:|:-------:|
| customer:read | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
| customer:create | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
| customer:update | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ |
| customer:delete | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
| contract:read | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| contract:create | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ |
| contract:update | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ |
| contract:delete | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ |
| after_sale:read | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ |
| after_sale:create | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ |
| after_sale:update | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ |
| after_sale:delete | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ |
| product:read | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ |
| product:create | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ |
| product:update | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ |
| product:delete | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ |
| supplier:read | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ |
| supplier:create | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ |
| supplier:update | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ |
| supplier:delete | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ |
| employee:read | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| employee:create | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| employee:update | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| employee:delete | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| user:manage | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
### 数据范围隔离
拥有操作权限 ≠ 能看到所有数据。系统通过 `getDataScope()` 函数在 SQL 查询层面做数据隔离:
| 资源 | admin | general_manager | franchise_manager | operations_manager | procurement_manager | finance |
|------|-------|-----------------|-------------------|--------------------|--------------------|---------|
| customers | 全部 | 全部 | 仅自己负责 | 仅自己负责 | — | 全部 |
| contracts | 全部 | 全部 | 仅自己负责的加盟合同 | 仅自己负责的加盟合同 | 仅采购合同 | 全部 |
| after_sales | 全部 | 全部 | — | 仅自己负责 | — | 全部 |
| employees | 全部 | 全部 | 仅本部门 | 仅本部门 | 仅本部门 | 全部 |
| products | 全部 | 全部 | — | — | 全部 | — |
| suppliers | 全部 | 全部 | — | — | 全部 | — |
| users | 全部 | — | — | — | — | — |
> **"—"** 表示该角色无此资源的权限,请求会被权限中间件直接拦截(返回 403
**隔离机制说明:**
- **负责人类资源**customers、contracts、after_sales通过 `responsible_user_id` 字段过滤,只能看到自己负责的记录
- **合同类型过滤**contracts除了负责人类过滤还按合同 `type` 字段过滤franchise/supply
- **部门类资源**employees通过 `department` 字段过滤,只能看到自己部门的员工
- **全局类资源**products、suppliers无数据范围过滤只要有权限就能看到全部
**合同类型与角色限制:**
- 采购经理(`procurement_manager`)只能查看和操作采购合同(`type = 'supply'`
- 招商经理(`franchise_manager`)和运营经理(`operations_manager`)只能查看和操作加盟合同(`type = 'franchise'`)中自己负责的记录
- 创建合同时,采购经理只能创建采购合同,招商经理只能创建加盟合同
---
## 预置账号
系统首次启动时自动创建以下账号,密码均为 `123456`
| 用户名 | 姓名 | 角色 | 部门 | 职位 |
|--------|------|------|------|------|
| `admin` | 系统管理员 | admin | 信息技术部 | 系统管理员 |
| `zhangchao` | 张超 | general_manager | 总经理办公室 | 总经理 |
| `liming` | 李明 | franchise_manager | 招商部 | 招商经理 |
| `wangli` | 王丽 | operations_manager | 运营部 | 运营经理 |
| `zhaoqiang` | 赵强 | procurement_manager | 采购部 | 采购经理 |
| `chenfang` | 陈芳 | finance | 财务部 | 财务主管 |
### 快速测试
```bash
# 以管理员身份登录
curl -X POST http://127.0.0.1:3000/api/user/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"123456"}'
# 以招商经理身份登录(只能看到自己负责的加盟商)
curl -X POST http://127.0.0.1:3000/api/user/login \
-H "Content-Type: application/json" \
-d '{"username":"liming","password":"123456"}'
```