48 KiB
蜜雪冰城企业管理系统 —— 后端 API 文档
蜜雪冰城总部用于管理加盟商、合同、员工、产品、供应商及售后服务的企业信息管理系统后端服务。
目录
快速开始
环境要求
- Node.js >= 18
- MySQL 5.7+ / MariaDB 10.2+
安装与运行
# 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 # 权限检查 + 数据范围 + 合同类型校验
│ ├── users.js # 用户自保护规则(禁止自改部门、自删等)
│ └── employees.js # 员工创建时同步创建用户账号
├── 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 个部门的权限与数据隔离,131 条用例)
├── API.md # 本文档
├── .env.example # 环境变量模板
└── package.json
架构分层
本项目采用三层分离架构,各层职责清晰:
请求 → 中间件层(认证/权限/数据范围/业务规则)→ 路由层(纯 CRUD,数据库操作)→ 响应
| 分层 | 文件 | 职责 |
|---|---|---|
| API 端点层 | server.js |
路由注册,串联中间件链 |
| 中间件层 | middleware/*.js |
JWT 认证、权限校验、数据范围注入、业务规则校验 |
| 数据库查询层 | routes/*.js |
纯 SQL CRUD 操作,不做权限判断和业务逻辑 |
路由层纯粹性要求: 所有 routes/*.js 只包含数据库的增删改查操作,不参与权限判断、数据范围计算等逻辑。权限和数据范围由中间件通过 req.user.permissions 和 req.scope 注入。
认证机制
所有 API(除登录外)需要在请求头中携带 JWT Token:
Authorization: Bearer <token>
Token 获取
通过 POST /api/user/login 登录获取 Token。
Token 有效载荷 (Payload)
{
"id": 1,
"username": "admin",
"name": "系统管理员",
"department_id": 1,
"departmentName": "admin",
"departmentDesc": "信息技术部",
"permissions": ["customer:read", "customer:create", "customer:update", "customer:delete", "contract:read", "..."]
}
中间件执行流程
请求 → auth(验证 Token)→ checkPermission(权限检查)→ dataScope(数据范围注入)→ [业务中间件] → 路由处理 → 响应
| 中间件 | 文件 | 说明 |
|---|---|---|
auth |
server.js |
解析并验证 JWT Token,将用户信息挂载到 req.user |
checkPermission(resource, action) |
middleware/permissions.js |
检查 req.user.permissions 是否包含 resource:action |
dataScope(resource) |
middleware/permissions.js |
根据用户部门计算数据范围,挂载 req.scope = { sql, params } |
validateContractType |
middleware/permissions.js |
限制招商部只能操作加盟合同,采购部只能操作采购合同 |
protectSelfUpdate |
middleware/users.js |
禁止用户修改自己的部门或禁用自己 |
protectUserDelete |
middleware/users.js |
禁止删除自己或最后一个管理员 |
allowUserCreation |
middleware/employees.js |
允许有 user:manage 权限的用户在创建员工时同步创建账号 |
错误响应
| 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 接口只传需要修改的字段即可,未传字段保持不变
- 权限要求:每个接口所需的权限标注在接口标题下方
接口权限速查表
| 接口 | 方法 | 权限 | 数据范围 |
|---|---|---|---|
/api/user/login |
POST | 无 | — |
/api/user/info |
GET | 登录即可 | 仅自己 |
/api/user/list |
GET | 登录即可 | 所有启用用户 |
/api/user/logout |
POST | 登录即可 | — |
/api/user/password |
PUT | 登录即可 | 仅自己 |
/api/users |
GET/POST | user:manage |
全部 |
/api/users/:id |
GET/PUT/DELETE | user:manage |
全部 |
/api/customers/simple |
GET | customer:read |
全部客户 |
/api/customers |
GET | customer:read |
过渡期全量 |
/api/customers |
POST | customer:create |
— |
/api/customers/:id |
GET/PUT/DELETE | 对应权限 | 过渡期全量 |
/api/employees/simple |
GET | 登录即可 | 全部在职员工 |
/api/employees |
GET | employee:read |
按部门隔离 |
/api/employees |
POST | employee:create |
— |
/api/employees/:id |
GET/PUT/DELETE | 对应权限 | 按部门隔离 |
/api/contracts |
GET | contract:read |
按合同类型隔离 |
/api/contracts |
POST | contract:create |
按部门限制类型 |
/api/contracts/:id |
GET/PUT/DELETE | 对应权限 | 按合同类型隔离 |
/api/after-sales |
GET | after_sale:read |
过渡期全量 |
/api/after-sales |
POST | after_sale:create |
— |
/api/after-sales/:id |
GET/PUT/DELETE | 对应权限 | 过渡期全量 |
/api/products |
GET | product:read |
全部 |
/api/products/:id |
GET/PUT/DELETE | 对应权限 | 全部 |
/api/suppliers/simple |
GET | supplier:read |
全部正常供应商 |
/api/suppliers |
GET | supplier:read |
全部 |
/api/suppliers/:id |
GET/PUT/DELETE | 对应权限 | 全部 |
1. 用户认证
POST /api/user/login — 登录
无需 Token。
请求体:
{
"username": "admin",
"password": "123456"
}
成功响应:
{
"code": 200,
"message": "ok",
"data": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"userInfo": {
"id": 1,
"username": "admin",
"name": "系统管理员",
"department_id": 1,
"departmentName": "admin",
"departmentDesc": "信息技术部",
"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。
返回当前登录用户的完整信息(含部门名称、员工姓名)。
响应数据字段:
| 字段 | 类型 | 说明 |
|---|---|---|
id |
number | 用户 ID |
username |
string | 用户名 |
is_active |
number | 状态:0-禁用, 1-启用 |
department_id |
number | 部门 ID |
employee_id |
number | 关联员工 ID |
dept_name |
string | 部门标识(如 admin) |
dept_desc |
string | 部门中文名(如 信息技术部) |
real_name |
string | 真实姓名(来自员工表) |
created_at |
datetime | 创建时间 |
updated_at |
datetime | 更新时间 |
GET /api/user/list — 简易用户列表
需要 Token(仅需登录,无需特定权限)。
返回所有启用用户的基本信息,用于前端下拉选择负责人。
响应数据字段:
| 字段 | 类型 | 说明 |
|---|---|---|
id |
number | 用户 ID |
username |
string | 用户名 |
real_name |
string | 真实姓名(来自员工表,可能为 null) |
POST /api/user/logout — 登出
需要 Token。服务端无状态,仅返回成功应答,客户端自行清除 Token。
PUT /api/user/password — 修改密码
需要 Token。修改当前登录用户自己的密码。
请求体:
{
"oldPassword": "123456",
"newPassword": "654321"
}
校验规则:
- 旧密码和新密码必填
- 新密码至少 6 位
- 新密码不能与旧密码相同
2. 用户管理
需要
user:manage权限(仅系统管理员 / 信息技术部拥有)。
GET /api/users — 用户列表
查询参数:
| 参数 | 说明 | 示例 |
|---|---|---|
username |
按用户名模糊搜索 | ?username=admin |
real_name |
按真实姓名模糊搜索 | ?real_name=张 |
id |
按用户 ID 精确搜索 | ?id=1 |
department_id |
按部门 ID 过滤 | ?department_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-启用 |
department_id |
number | 部门 ID |
employee_id |
number | 关联员工 ID |
dept_name |
string | 部门标识(如 admin) |
dept_desc |
string | 部门中文名(如 信息技术部) |
real_name |
string | 真实姓名(来自员工表) |
created_at |
datetime | 创建时间 |
updated_at |
datetime | 更新时间 |
GET /api/users/:id — 用户详情
返回单个用户完整信息。
POST /api/users — 创建用户
请求体:
| 字段 | 必填 | 说明 |
|---|---|---|
username |
✅ | 用户名(3-50 字符) |
password |
✅ | 密码(至少 6 位) |
department_id |
❌ | 部门 ID(需为已存在的部门) |
employee_id |
❌ | 关联员工 ID(需为已存在的员工) |
is_active |
❌ | 状态,默认 1 |
校验规则:
department_id必须存在于departments表employee_id必须存在于employees表username不可重复(返回 409)
PUT /api/users/:id — 更新用户
可更新字段: username、department_id、employee_id、is_active、password
自保护限制(protectSelfUpdate 中间件):
- 不能修改自己的
department_id - 不能禁用自己(
is_active不能设为 0)
DELETE /api/users/:id — 删除用户
自保护限制(protectUserDelete 中间件):
- 不能删除自己
- 不能删除信息技术部的最后一个管理员账号
3. 加盟商管理
需要
customer:read/customer:create/customer:update/customer:delete权限。
GET /api/customers/simple — 简易加盟商列表
需要 customer:read 权限。无数据范围限制,用于前端下拉选择。
响应数据字段:
| 字段 | 类型 | 说明 |
|---|---|---|
id |
number | 加盟商 ID |
name |
string | 加盟商姓名 |
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 | 备注信息 |
created_at |
datetime | 创建时间 |
updated_at |
datetime | 更新时间 |
POST /api/customers — 新增加盟商
请求体:
| 字段 | 必填 | 说明 |
|---|---|---|
name |
✅ | 加盟商姓名 |
phone |
❌ | 联系电话 |
province |
❌ | 省 |
city |
❌ | 市 |
district |
❌ | 区 |
address |
❌ | 详细地址 |
email |
❌ | 电子邮箱 |
remark |
❌ | 备注 |
PUT /api/customers/:id — 更新加盟商
所有字段均可选,只传需要修改的字段。
DELETE /api/customers/:id — 删除加盟商
限制: 被合同或售后记录引用的客户无法删除(返回 400)。
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 |
❌ | 备注 |
username |
❌ | 需同步创建用户时填写(需 user:manage 权限) |
password |
❌ | 需同步创建用户时填写(需 user:manage 权限) |
department_id |
❌ | 需同步创建用户时填写(指定用户所属部门 ID) |
同步创建用户账号(allowUserCreation 中间件):
当同时传入 username、password、department_id 三个字段时:
- 需要拥有
user:manage权限 - 系统会在创建员工后自动创建关联的系统用户账号
username长度 3-50 字符,password至少 6 位
PUT /api/employees/:id — 更新员工
所有字段均可选,只传需要修改的字段。
DELETE /api/employees/:id — 删除员工
特殊行为:
- 删除员工时会同步删除关联的用户账号(
users表中employee_id匹配的记录) - 不能删除当前登录用户自己关联的员工账号
- 被合同或售后记录引用的员工无法删除(返回 400)
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(加盟合同必填,采购合同为 null) |
customer_name |
string | 客户名称(JOIN 自 customers) |
supplier_id |
number | 供应商 ID(采购合同必填,加盟合同为 null) |
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 |
❌ | 负责人(默认为当前用户,仅管理员可在前端修改) |
合同类型限制(validateContractType 中间件):
| 部门 | 允许的合同类型 | 说明 |
|---|---|---|
招商部 (franchise_manager) |
仅 franchise |
只能创建加盟合同,且必须选择 customer_id |
采购部 (procurement_manager) |
仅 supply |
只能创建采购合同,且必须选择 supplier_id |
| 信息技术部/总经理/财务 | 不限制 | 默认 franchise,可手动指定 |
校验规则:
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 — 删除合同
限制: 被售后记录引用的合同无法删除(返回 400)。
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 |
responsible_user_name |
string | 负责人姓名(JOIN 自 users → employees) |
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/simple — 简易供应商列表
需要 supplier:read 权限。返回所有正常状态供应商的基本信息,用于前端下拉选择。
响应数据字段:
| 字段 | 类型 | 说明 |
|---|---|---|
id |
number | 供应商 ID |
name |
string | 供应商名称 |
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 — 删除供应商
统一响应格式
成功响应
{
"code": 0,
"message": "ok",
"data": { ... }
}
列表响应
{
"code": 0,
"message": "ok",
"data": {
"list": [ ... ],
"total": 100,
"page": 1,
"pageSize": 10,
"totalPages": 10
}
}
错误响应
{
"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-启用 |
department_id |
INT | NULLABLE | 部门 ID → departments.id |
employee_id |
INT | NULLABLE, UNIQUE | 员工 ID → employees.id |
created_at |
DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
updated_at |
DATETIME | ON UPDATE CURRENT_TIMESTAMP | 更新时间 |
2. departments — 部门表
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
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. department_permissions — 部门-权限关联表
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
department_id |
INT | PK (联合) | 部门 ID → departments.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 | — | 备注信息 |
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(加盟合同必填,采购合同为 null) |
supplier_id |
INT | NULLABLE, INDEX | 供应商 ID → suppliers.id(采购合同必填,加盟合同为 null) |
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 图
┌──────────────┐ ┌──────────────┐ ┌────────────────────────┐
│ departments │ │ permissions │ │ department_permissions │
│──────────────│ │──────────────│ │────────────────────────│
│ id (PK) │◄─┐ │ id (PK) │◄─┐ │ department_id (PK,FK) │
│ name │ │ │ name │ │ │ permission_id (PK,FK) │
│ description │ │ │ description │ └──│ → permissions.id │
└──────────────┘ │ │ resource │ └────────────────────────┘
│ │ action │
│ └──────────────┘
│
┌──────────────┐ │ ┌──────────────┐
│ users │ │ │ employees │
│──────────────│ │ │──────────────│
│ id (PK) │ │ │ id (PK) │
│ username │ │ │ name │
│ password │ │ │ gender │
│ is_active │ │ │ age │
│ department_id│──┘ │ education │
│ (FK) │ │ department │◄──── 数据范围隔离字段
│ employee_id │─ ─ ─│ entry_date │
│ (FK) ──────┼──→ │ position │
└──────┬───────┘ │ salary │
│ │ phone │
│ │ email │
│ │ status │
│ └──────┬───────┘
│ │
▼ ▼
┌──────────────────────────────────────────────┐
│ customers (加盟商) │
│──────────────────────────────────────────────│
│ id (PK) │
│ name, phone, province, city, district, ... │
└────────────────────┬─────────────────────────┘
│
┌──────────┴──────────┐
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ 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 → departments | 多对一 | 一个用户属于一个部门,一个部门可以有多个用户 |
| users → employees | 一对一 | 一个用户关联一个员工(通过 employee_id,唯一约束) |
| departments ↔ permissions | 多对多 | 通过 department_permissions 中间表关联 |
| contracts → customers | 多对一 | 通过 customer_id 关联客户(加盟合同必填,采购合同为 null) |
| contracts → suppliers | 多对一 | 通过 supplier_id 关联供应商(采购合同必填,加盟合同为 null) |
| 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 指定负责人 |
注意:数据库未设置外键约束,引用完整性由应用代码保证(插入/更新前检查关联记录是否存在)。
部门权限系统
部门定义
系统预置 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 | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
数据范围隔离
拥有操作权限 ≠ 能看到所有数据。系统通过 dataScope() 中间件在 SQL 查询层面做数据隔离:
| 资源 | admin | general_manager | franchise_manager | operations_manager | procurement_manager | finance |
|---|---|---|---|---|---|---|
| customers | 全部 | 全部 | 全部(过渡期) | 全部(过渡期) | 全部 | 全部 |
| contracts | 全部 | 全部 | 仅加盟合同 | — | 仅采购合同 | 全部 |
| after_sales | 全部 | 全部 | — | 全部(过渡期) | — | 全部 |
| employees | 全部 | 全部 | 仅本部门 | 仅本部门 | 仅本部门 | 全部 |
| products | 全部 | 全部 | — | — | 全部 | — |
| suppliers | 全部 | 全部 | — | — | 全部 | — |
| users | 全部 | — | — | — | — | — |
"—" 表示该部门无此资源的权限,请求会被
checkPermission中间件直接拦截(返回 403)。
隔离机制说明:
| 隔离类型 | 资源 | 实现方式 |
|---|---|---|
| 合同类型过滤 | contracts | 招商部只能看到 type = 'franchise',采购部只能看到 type = 'supply' |
| 部门过滤 | employees | 招商部/运营部/采购部只能看到本部门员工(department = 本部门名称) |
| 过渡期全量 | customers, after_sales | 当前不注入数据范围,所有部门看到全量数据 |
| 无隔离 | products, suppliers | 无数据范围过滤,有权限即可看到全部 |
过渡期说明:
customers已移除responsible_user_id字段,不再需要负责人隔离after_sales的dataScope()当前为占位状态(不注入任何 scope),所有有权限的部门都能看到全量数据- 后续可基于
responsible_user_id对 contracts/after_sales 激活负责人隔离
合同类型与部门限制(validateContractType 中间件):
- 招商部(
franchise_manager)只能创建/修改加盟合同(type = 'franchise') - 采购部(
procurement_manager)只能创建/修改采购合同(type = 'supply') - 信息技术部/总经理/财务不限制
中间件说明
| 中间件 | 文件 | 类型 | 说明 |
|---|---|---|---|
auth |
server.js |
认证 | 解析 JWT Token,将 { id, username, name, department_id, departmentName, departmentDesc, permissions } 挂到 req.user |
checkPermission(resource, action) |
middleware/permissions.js |
权限 | 检查 req.user.permissions 是否包含 "resource:action",否 → 403 |
dataScope(resource) |
middleware/permissions.js |
数据范围 | 根据用户部门计算 SQL 过滤条件,挂到 req.scope = { sql, params };admin/general_manager 不注入(全量) |
validateContractType |
middleware/permissions.js |
业务规则 | 限制招商部/采购部只能操作对应类型的合同 |
protectSelfUpdate |
middleware/users.js |
自保护 | 禁止用户修改自己的部门或禁用自己 |
protectUserDelete |
middleware/users.js |
自保护 | 禁止用户删除自己或删除信息技术部最后一个管理员 |
allowUserCreation |
middleware/employees.js |
业务规则 | 允许有 user:manage 权限的用户在创建员工时同步创建系统账号 |
req.scope 注入格式:
// 示例:招商部查看合同时注入
req.scope = {
sql: 'AND type = ?',
params: ['franchise']
}
// 路由层机械使用(contracts list/detail 需加 c. 别名前缀)
where += ' ' + req.scope.sql.replace(/\btype\b/g, 'c.type')
params.push(...req.scope.params)
预置账号
系统首次启动时自动创建以下账号,密码均为 123456:
| 用户名 | 姓名 | 部门标识 | 部门 | 职位 |
|---|---|---|---|---|
admin |
系统管理员 | admin |
信息技术部 | 系统管理员 |
zhangchao |
张超 | general_manager |
总经理办公室 | 总经理 |
liming |
李明 | franchise_manager |
招商部 | 招商经理 |
wangli |
王丽 | operations_manager |
运营部 | 运营经理 |
zhaoqiang |
赵强 | procurement_manager |
采购部 | 采购经理 |
chenfang |
陈芳 | finance |
财务部 | 财务主管 |
额外预置账号(业务员工,同样密码 123456):
| 用户名 | 姓名 | 部门 | 职位 |
|---|---|---|---|
liuyang |
刘洋 | 招商部 | 招商专员 |
sunting |
孙婷 | 招商部 | 招商专员 |
zhoujie |
周杰 | 运营部 | 运营专员 |
wumin |
吴敏 | 运营部 | 运营专员 |
zhengwei |
郑伟 | 运营部 | 售后工程师 |
huanglei |
黄磊 | 采购部 | 采购专员 |
mali |
马丽 | 财务部 | 会计 |
linfeng |
林峰 | 总经理办公室 | 副总经理 |
快速测试
# 以管理员身份登录
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"}'