Files
backmanager-server/API.md
2026-06-24 23:12:43 +08:00

1071 lines
36 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 | message |
|------|---------|
| 400 | 用户名和密码必填 |
| 400 | 账号或密码错误 |
| 403 | 账号已被禁用,请联系管理员 |
#### GET /api/user/info — 获取当前用户信息
需要 Token。返回当前登录用户的完整信息含角色名、员工名
#### 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` |
| `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 会自动同步) |
| `is_active` | ❌ | 状态,默认 1 |
#### PUT /api/users/:id — 更新用户
**可更新字段:** `username``role_id``employee_id``department``is_active``password`
**限制:**
- 不能修改自己的角色
- 不能禁用自己
#### DELETE /api/users/:id — 删除用户
**限制:** 不能删除自己。
---
### 3. 加盟商管理
> 需要 `customer:read` / `customer:create` / `customer:update` / `customer:delete` 权限。
#### GET /api/customers — 加盟商列表
**查询参数:**
| 参数 | 说明 |
|------|------|
| `name` | 按加盟商名称模糊搜索 |
| `phone` | 按电话模糊搜索 |
| `province` | 按省份精确匹配 |
| `city` | 按城市精确匹配 |
**响应数据字段:**
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | number | 加盟商 ID |
| `name` | string | 加盟商姓名 |
| `phone` | string | 联系电话 |
| `province` | string | 省 |
| `city` | string | 市 |
| `district` | string | 区 |
| `address` | string | 详细地址 |
| `email` | string | 电子邮箱 |
| `remark` | string | 备注信息 |
| `responsible_user_id` | number | 负责人用户 ID |
| `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 — 员工列表
**查询参数:**
| 参数 | 说明 |
|------|------|
| `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 — 更新员工
所有字段均可选,只传需要修改的字段。
#### DELETE /api/employees/:id — 删除员工
---
### 5. 合同管理
> 需要 `contract:read` / `contract:create` / `contract:update` / `contract:delete` 权限。
#### GET /api/contracts — 合同列表
**查询参数:**
| 参数 | 说明 |
|------|------|
| `status` | 按合同状态过滤(草稿/生效/完成/作废) |
| `customer_name` | 按客户名称模糊搜索 |
| `contract_no` | 按合同编号模糊搜索 |
| `employee_name` | 按业务员姓名模糊搜索 |
**响应数据字段(含 JOIN 字段):**
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | number | 合同 ID |
| `customer_id` | number | 客户 ID |
| `customer_name` | string | 客户名称JOIN 自 customers |
| `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 |
| `created_at` | datetime | 创建时间 |
| `updated_at` | datetime | 更新时间 |
#### POST /api/contracts — 新增合同
**请求体:**
| 字段 | 必填 | 说明 |
|------|------|------|
| `customer_id` | ✅ | 关联客户 ID |
| `contract_name` | ✅ | 合同名称 |
| `contract_no` | ❌ | 合同编号 |
| `contract_content` | ❌ | 合同内容 |
| `amount` | ❌ | 合同金额 |
| `effective_date` | ❌ | 生效日期 |
| `expiry_date` | ❌ | 到期日期 |
| `employee_id` | ❌ | 业务员 ID |
| `status` | ❌ | 状态,默认 `生效` |
| `remark` | ❌ | 备注 |
| `type` | ❌ | 合同类型(见下方说明) |
| `responsible_user_id` | ❌ | 负责人(默认为当前用户) |
**合同类型自动设置规则:**
- 采购经理创建的合同自动设为 `supply`
- 其他角色默认 `franchise`,也可手动指定
#### PUT /api/contracts/:id — 更新合同
所有字段均可选。可更新字段包括 `type``responsible_user_id`
#### DELETE /api/contracts/:id — 删除合同
---
### 6. 售后管理
> 需要 `after_sale:read` / `after_sale:create` / `after_sale:update` / `after_sale:delete` 权限。
#### GET /api/after-sales — 售后列表
**查询参数:**
| 参数 | 说明 |
|------|------|
| `handle_status` | 按处理状态过滤(待处理/处理中/已完成) |
| `customer_name` | 按客户名称模糊搜索 |
**响应数据字段(含 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` | ❌ | 负责人(默认为当前用户) |
#### PUT /api/after-sales/:id — 更新售后
所有字段均可选。
#### DELETE /api/after-sales/:id — 删除售后
---
### 7. 产品管理
> 需要 `product:read` / `product:create` / `product:update` / `product:delete` 权限。
> 无数据范围隔离,拥有权限即可查看所有产品。
#### GET /api/products — 产品列表
**查询参数:**
| 参数 | 说明 |
|------|------|
| `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 — 供应商列表
**查询参数:**
| 参数 | 说明 |
|------|------|
| `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 | 员工 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 | NOT NULL, INDEX | 客户 ID → customers.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 │
│ contract_name │ │ feedback │
│ contract_no │ │ employee_id (FK) │
│ amount │ │ → employees.id │
│ employee_id (FK) │ │ handle_method │
│ → employees.id │ │ handle_status │
│ status │ │ service_date │
│ type │ │ responsible_user │
│ franchise/ │ │ _id → users.id │
│ 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 → 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无数据范围过滤只要有权限就能看到全部
---
## 预置账号
系统首次启动时自动创建以下账号,密码均为 `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"}'
```