by-crm/docs/README.md

805 lines
23 KiB
Markdown
Raw Permalink 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.

# 泊云智销通 CRM 系统
## 项目简介
泊云智销通是一款面向企业渠道管理的 CRM 系统,核心解决**报备冲突**与**保护期归属**问题。系统通过**唯一有效报备机制**与**自动到期释放机制**,确保同一学校的同一项目在同一时间只能被一个经销商跟进,并在保护期结束后自动释放资源,提升渠道效率与公平性。
### 核心价值
- **防撞单机制**:基于"学校+产品+项目类型"组合的唯一性校验,避免恶意抢占
- **保护期管理**:自动计算保护期,到期自动释放,无需人工干预
- **公平竞争**:防止资源长期占用,所有经销商公平竞争
- **全程可追溯**:报备记录永久保留,支持进展记录追踪
- **灵活配置**:支持手动输入学校名称,降低报备门槛
---
## 技术架构
### 后端技术栈
- **框架**: Spring Boot 2.7
- **ORM**: MyBatis
- **数据库**: MySQL 8
- **安全**: Spring Security + JWT + BCrypt
- **API 文档**: Swagger
- **构建工具**: Maven
- **定时任务**: Spring Task Scheduler
### 前端技术栈
- **框架**: Vue 3 + TypeScript
- **UI 组件**: Element Plus
- **路由**: Vue Router 4
- **状态管理**: Pinia
- **HTTP 客户端**: Axios
- **构建工具**: Vite
- **代码规范**: ESLint + Prettier
### 架构模式
- **前后端分离**: RESTful API 架构
- **MVC 分层**: Controller → Service → Mapper → Entity
- **数据库设计**: 遵循范式,合理使用索引
- **安全设计**: JWT 认证BCrypt 密码加密
---
## 用户角色与权限
### 1. 管理员
**角色标识**: `role = 0`
**权限范围**:
- ✅ 报备管理(全部报备的查看、审核、编辑、作废)
- ✅ 经销商管理(经销商的增删改查、重置密码)
- ✅ 学校管理(查询、批量导入学校数据)
- ✅ 系统配置(保护期天数等参数配置)
- ✅ 查看所有运营数据
**主要职责**:
- 审核报备申请
- 管理经销商账号
- 配置系统参数
- 监控渠道数据
### 2. 经销商用户
**角色标识**: `role = 1`
**权限范围**:
- ✅ 查看自己的报备记录
- ✅ 提交报备申请
- ✅ 撤回待审核的报备
- ✅ 为已通过的报备添加进展记录
- ✅ 修改个人密码
**主要职责**:
- 报备意向学校项目
- 跟进保护期内的项目
- 维护进展记录
### 权限矩阵
| 功能模块 | 管理员 | 经销商 |
|---------|--------|--------|
| 报备管理 | 全部 | 自己的报备 |
| 提交报备 | ✅ | ✅ |
| 报备审核 | ✅ | ❌ |
| 报备编辑/作废 | ✅ | ❌ |
| 进展记录 | 全部 | 自己报备的进展 |
| 经销商管理 | ✅ | ❌ |
| 学校管理 | ✅ | ❌ |
| 系统配置 | ✅ | ❌ |
---
## 功能清单
### 1. 认证与授权
#### 1.1 用户登录
- **路径**: `POST /api/auth/login`
- **描述**: 用户登录系统,获取 JWT Token
- **安全**: BCrypt 加密存储
- **返回**: Token + 用户基本信息
**默认账号**:
- 管理员: `admin` / `admin123`
- 经销商用户: 根据经销商数据
#### 1.2 修改密码
- **路径**: `POST /api/auth/change-password`
- **权限**: 所有用户
- **功能**: 用户修改自己的登录密码
- **验证**: 需要输入原密码验证
#### 1.3 重置密码
- **路径**: `POST /api/auth/reset-password`
- **权限**: 仅管理员
- **功能**: 管理员重置经销商用户的密码
---
### 2. 报备管理
#### 2.1 报备列表
- **路径**: `GET /api/report/page`
- **权限**: 管理员查看全部,经销商查看自己的
- **功能**: 分页查询报备记录
- **搜索字段**: 学校名称、状态
- **状态说明**:
- `0 - 待审核`: 等待管理员审核
- `1 - 已通过`: 审核通过,保护期内
- `2 - 已驳回`: 审核未通过
- `3 - 已失效`: 保护期已过
- `4 - 已作废`: 管理员主动作废
#### 2.2 提交报备
- **路径**: `POST /api/report`
- **权限**: 经销商
- **功能**: 经销商报备意向学校项目
- **必填字段**: 学校名称、所属产品、项目类型
- **可选字段**: 报备说明
- **防撞单规则**:
- 同一学校的同一产品+项目类型组合,只能有一个有效报备
- 有效报备定义:状态为"待审核(0)"或"已通过(1)"
- 已失效、已驳回、已作废的报备不影响新报备
#### 2.3 撤回报备
- **路径**: `DELETE /api/report/{id}`
- **权限**: 报备所属经销商
- **功能**: 撤回待审核的报备申请
- **限制**: 只能撤回状态为"待审核"的报备
#### 2.4 审核报备
- **路径**: `PUT /api/report/{id}/audit`
- **权限**: 仅管理员
- **功能**: 审核报备申请
- **操作**:
- 通过设置保护期默认90天状态变为"已通过"
- 驳回:填写驳回原因,状态变为"已驳回"
#### 2.5 编辑报备
- **路径**: `PUT /api/report/{id}`
- **权限**: 仅管理员
- **功能**: 编辑已通过的报备
- **可编辑字段**: 所属产品、项目类型、报备说明、状态
- **作废操作**: 状态改为"已作废"时必须填写作废原因
#### 2.6 报备详情
- **路径**: `GET /api/report/{id}`
- **权限**: 根据权限查看
- **功能**: 查看报备详细信息和进展记录
---
### 3. 进展记录管理
#### 3.1 添加进展记录
- **路径**: `POST /api/report-progress`
- **权限**: 经销商只能为自己的报备添加,管理员可查看所有
- **功能**: 为已通过的报备添加进展记录
- **限制**: 只能为状态为"已通过"的报备添加进展
#### 3.2 编辑进展记录
- **路径**: `PUT /api/report-progress/{id}`
- **权限**: 进展记录创建者
- **功能**: 修改进展记录内容
- **保留历史**: 不支持删除,确保数据完整性
---
### 4. 经销商管理
#### 4.1 经销商列表
- **路径**: `GET /api/dealer/list`
- **权限**: 仅管理员
- **功能**: 查询所有经销商信息
#### 4.2 新增经销商
- **路径**: `POST /api/dealer`
- **权限**: 仅管理员
- **必填字段**: 经销商名称、经销商账号、联系人、联系电话
- **自动创建**: 创建经销商记录的同时创建对应的登录用户
#### 4.3 编辑经销商
- **路径**: `PUT /api/dealer/{id}`
- **权限**: 仅管理员
- **功能**: 修改经销商信息
#### 4.4 删除经销商
- **路径**: `DELETE /api/dealer/{id}`
- **权限**: 仅管理员
- **功能**: 删除经销商及其关联用户
---
### 5. 学校管理
#### 5.1 学校列表
- **路径**: `GET /api/school/list`
- **权限**: 所有用户
- **功能**: 查询所有学校信息
#### 5.2 学校搜索
- **路径**: `GET /api/school/search?keyword=xxx`
- **权限**: 所有用户
- **功能**: 模糊搜索学校名称
- **应用场景**: 报备时学校名称的自动完成提示
#### 5.3 批量导入学校
- **路径**: `POST /api/school/import`
- **权限**: 仅管理员
- **功能**: 上传 Excel 文件批量导入学校数据
- **文件格式**: `.xls``.xlsx`
**学校数据结构**:
- 学校标识码(唯一)
- 学校名称
- 所在地(省份 + 城市)
---
### 6. 系统配置
#### 6.1 保护期配置
- **配置项**: `report.protect.days`
- **默认值**: `90`
- **说明**: 控制报备通过后的保护期时长
#### 6.2 报备冲突控制
- **配置项**: `report.allow.overlap`
- **默认值**: `false`
- **说明**: 是否允许重叠报备(仅用于测试)
- **生产环境**: 必须为 `false`
---
## 业务流程
### 报备完整生命周期
```
┌─────────────────────────────────────────────────────────────┐
│ 报备完整流程 │
└─────────────────────────────────────────────────────────────┘
1. 经销商提交报备
├─ 学校名称(可从学校库选择或手动输入)
├─ 所属产品(必填)
├─ 项目类型(必填)
├─ 报备说明(可选)
└─ 提交审核
状态:待审核(0)
2. 管理员审核
├─ 通过 → 设置保护期默认90天
│ ├─ 报备状态:已通过(1)
│ └─ 保护期倒计时开始
└─ 驳回 → 填写驳回原因
└─ 报备状态:已驳回(2)
3. 保护期内
├─ 该"学校+产品+项目类型"组合被锁定
├─ 其他经销商无法报备相同组合
├─ 经销商可添加进展记录
└─ 剩余保护天数倒计时
4. 管理员操作(可选)
├─ 编辑报备:修改产品、项目类型、说明
└─ 作废报备:状态改为已作废(4),填写原因
└─ 作废后该组合可重新报备
5. 保护期到期(定时任务)
└─ 每天凌晨1点自动执行
├─ 报备状态:已通过 → 已失效(3)
└─ 该"学校+产品+项目类型"组合可再次报备
```
### 报备状态转换规则
```
待审核(0)
↓ [管理员审核通过]
已通过(1) ←───────┐
↓ │
│ [保护期到期] │ [管理员编辑恢复]
↓ │
已失效(3) ─────────┘
待审核(0)
↓ [管理员驳回]
已驳回(2) ←─── 终态
待审核(0)
↓ [经销商撤回]
删除记录 ←─── 物理删除
已通过(1)
↓ [管理员作废]
已作废(4) ←─── 终态
```
---
## 数据库设计
### 核心表结构
#### 1. 报备表 (crm_report)
| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 报备ID主键 |
| dealer_id | BIGINT | 经销商ID |
| school_id | BIGINT | 学校ID可为NULL手动输入时为空 |
| school_name | VARCHAR(255) | 学校名称(冗余存储,方便查询) |
| product | VARCHAR(255) | 所属产品 |
| project_type | VARCHAR(255) | 项目类型 |
| description | VARCHAR(500) | 报备说明 |
| status | TINYINT | 状态0-待审核 1-已通过 2-已驳回 3-已失效 4-已作废 |
| reject_reason | VARCHAR(255) | 驳回原因 |
| cancel_reason | VARCHAR(500) | 作废原因 |
| protect_start_date | DATE | 保护期开始日期 |
| protect_end_date | DATE | 保护期结束日期 |
| created_at | DATETIME | 创建时间 |
| updated_at | DATETIME | 更新时间 |
**索引**:
- `idx_dealer_id` - 经销商ID索引
- `idx_status` - 状态索引
- `idx_protect_end_date` - 保护期结束日期索引(定时任务使用)
#### 2. 经销商表 (crm_dealer)
| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 经销商ID主键 |
| name | VARCHAR(200) | 经销商名称 |
| code | VARCHAR(50) | 经销商账号(唯一) |
| contact_person | VARCHAR(50) | 联系人 |
| contact_phone | VARCHAR(20) | 联系电话 |
| email | VARCHAR(100) | 邮箱 |
| status | TINYINT | 状态0-禁用 1-启用 |
| created_at | DATETIME | 创建时间 |
| updated_at | DATETIME | 更新时间 |
#### 3. 用户表 (crm_user)
| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 用户ID主键 |
| username | VARCHAR(50) | 用户名(登录账号) |
| password | VARCHAR(255) | 密码BCrypt加密 |
| dealer_id | BIGINT | 关联经销商ID管理员为NULL |
| role | TINYINT | 角色0-管理员 1-经销商用户 |
| status | TINYINT | 状态0-禁用 1-启用 |
| created_at | DATETIME | 创建时间 |
| updated_at | DATETIME | 更新时间 |
#### 4. 学校表 (crm_school)
| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 学校ID主键 |
| school_code | VARCHAR(50) | 学校标识码(唯一) |
| school_name | VARCHAR(200) | 学校名称 |
| location | VARCHAR(255) | 所在地 |
| created_at | DATETIME | 创建时间 |
| updated_at | DATETIME | 更新时间 |
**索引**:
- `uk_school_code` - 学校标识码唯一索引
- `idx_school_name` - 学校名称索引
#### 5. 报备进展记录表 (crm_report_progress)
| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 主键ID |
| report_id | BIGINT | 报备ID |
| progress_content | VARCHAR(500) | 进展内容 |
| created_by | BIGINT | 创建人ID |
| created_at | DATETIME | 创建时间 |
| updated_at | DATETIME | 更新时间 |
**索引**:
- `idx_report_id` - 报备ID索引
- `idx_created_by` - 创建人索引
#### 6. 系统配置表 (crm_system_config)
| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 主键ID |
| config_key | VARCHAR(100) | 配置键(唯一) |
| config_value | VARCHAR(500) | 配置值 |
| config_type | VARCHAR(20) | 配置类型string/integer/boolean |
| is_editable | TINYINT | 是否可编辑1-是 0-否) |
| created_at | DATETIME | 创建时间 |
| updated_at | DATETIME | 更新时间 |
---
## 定时任务
### 报备过期处理
**执行时间**: 每天凌晨 1 点
**Cron 表达式**: `0 0 1 * * ?`
**功能**:
1. 查询所有保护期已到的报备(`protect_end_date <= 今天` 且 `status = 1`
2. 批量更新报备状态为"已失效"`status = 3`
3. 释放资源,允许相同"学校+产品+项目类型"组合重新报备
**核心代码**: `ReportExpireTask.java`
```java
@Scheduled(cron = "0 0 1 * * ?")
public void handleExpiredReports() {
log.info("开始处理过期报备...");
try {
reportService.handleExpiredReports();
log.info("过期报备处理完成");
} catch (Exception e) {
log.error("处理过期报备失败", e);
}
}
```
---
## 防撞单机制详解
### 核心规则
**唯一性维度**: 学校 + 产品 + 项目类型
**有效报备定义**: 状态为"待审核(0)"或"已通过(1)"的报备
### 校验逻辑
```java
// 1. 如果选择了学校(有 school_id
// 按 school_id + product + project_type 精确匹配
WHERE school_id = #{schoolId}
AND product = #{product}
AND project_type = #{projectType}
AND status IN (0, 1)
// 2. 如果手动输入学校(无 school_id
// 按 school_name + product + project_type 匹配
WHERE school_name = #{schoolName}
AND product = #{product}
AND project_type = #{projectType}
AND status IN (0, 1)
```
### 业务意义
- **防止撞单**: 同一项目不能被多个经销商同时报备
- **公平竞争**: 保护期内锁定,到期后自动释放
- **灵活性**: 支持手动输入学校名称,降低报备门槛
---
## 学校选择机制
### 学校数据来源
学校数据存储在 `crm_school` 表中,包含:
- 学校标识码(唯一)
- 学校名称
- 所在地
### 前端交互
1. **自动完成搜索**
- 用户输入学校名称
- 前端调用 `/api/school/search?keyword=xxx` 接口
- 后端模糊匹配 `school_name`
- 返回匹配列表供用户选择
2. **手动输入**
- 用户可以直接输入学校名称
- 如果不从下拉列表选择,`school_id` 为 NULL
- `school_name` 保存用户输入的文本
- 防撞单校验时按 `school_name` 进行匹配
### 设计优势
- **降低门槛**: 学校不在库中也能报备,不影响业务
- **数据规范化**: 优先使用学校库数据,保证数据质量
- **灵活扩展**: 支持批量导入学校数据
---
## 安全机制
### 1. 认证与授权
#### JWT Token 认证
- 用户登录后获取 JWT Token
- Token 包含用户ID、用户名、角色、经销商ID
- Token 存储在客户端 `localStorage`
- 每次请求在 Header 中携带:`Authorization: Bearer {token}`
#### 权限拦截
- 前端路由守卫:未登录重定向到登录页
- 后端拦截器:验证 Token 有效性
- 数据权限:经销商只能查看自己的报备
### 2. 密码安全
#### 密码规则
- 长度6-20 位
- 加密BCrypt自动加盐
- 修改密码:需验证原密码
---
## 项目结构
```
by-crm/
├── backend/ # 后端工程
│ ├── src/main/java/com/bycrm/
│ │ ├── annotations/ # 自定义注解(权限等)
│ │ ├── common/ # 公共类Result、Constants等
│ │ ├── config/ # 配置类CORS、MyBatis、Swagger等
│ │ ├── controller/ # 控制器层
│ │ │ ├── AuthController.java
│ │ │ ├── ReportController.java
│ │ │ ├── DealerController.java
│ │ │ ├── SchoolController.java
│ │ │ └── ReportProgressController.java
│ │ ├── dto/ # 数据传输对象
│ │ ├── entity/ # 实体类
│ │ ├── exception/ # 异常处理
│ │ ├── mapper/ # MyBatis Mapper 接口
│ │ ├── service/ # 业务逻辑层
│ │ ├── task/ # 定时任务
│ │ ├── util/ # 工具类JWT、加密等
│ │ ├── vo/ # 视图对象
│ │ └── ByCrmApplication.java
│ ├── src/main/resources/
│ │ ├── mapper/*.xml # MyBatis SQL 映射
│ │ └── application.yml # 配置文件
│ └── pom.xml # Maven 依赖
├── frontend/ # 前端工程
│ ├── src/
│ │ ├── api/ # API 接口封装
│ │ ├── assets/ # 静态资源
│ │ ├── components/ # 通用组件
│ │ ├── router/ # Vue Router 配置
│ │ ├── stores/ # Pinia 状态管理
│ │ ├── types/ # TypeScript 类型定义
│ │ ├── utils/ # 工具函数(请求、加密等)
│ │ ├── views/ # 页面组件
│ │ │ ├── Login.vue # 登录页
│ │ │ ├── Layout.vue # 主布局
│ │ │ ├── Dashboard.vue # 首页
│ │ │ ├── Report.vue # 报备管理
│ │ │ └── Dealer.vue # 经销商管理
│ │ ├── App.vue # 根组件
│ │ └── main.ts # 入口文件
│ ├── vite.config.ts # Vite 配置
│ ├── package.json # 依赖配置
│ └── tsconfig.json # TS 配置
├── docs/ # 文档目录
│ └── README.md # 项目文档
└── sql/ # 数据库脚本
├── init.sql # 初始化脚本
└── *.sql # 其他 SQL 脚本
```
---
## 环境配置
### 后端配置 (application.yml)
```yaml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/by_crm
username: root
password: your_password
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.bycrm.entity
# JWT 配置
jwt:
secret: your-secret-key
expiration: 86400000 # 24小时毫秒
# 保护期配置
crm:
report:
protect-days: 90 # 保护期天数默认90天
allow-overlap: false # 是否允许报备重叠测试用生产必须false
```
### 前端配置
```typescript
// API 基础地址
const BASE_URL = 'http://localhost:8080/api'
// Token 存储
const TOKEN_KEY = 'token'
```
---
## 部署说明
### 开发环境启动
**后端**:
```bash
cd backend
mvn spring-boot:run
# 访问: http://localhost:8080
# Swagger: http://localhost:8080/swagger-ui.html
```
**前端**:
```bash
cd frontend
pnpm install
pnpm dev
# 访问: http://localhost:5173
```
### 生产环境部署
**后端**:
```bash
# 编译打包
mvn clean package
# 运行 JAR
java -jar target/bycrm-0.0.1-SNAPSHOT.jar
```
**前端**:
```bash
# 编译打包
pnpm build
# 部署 dist 目录到 Web 服务器
```
### 数据库初始化
```bash
# 创建数据库
mysql -u root -p -e "CREATE DATABASE by_crm CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"
# 导入初始化脚本
mysql -u root -p by_crm < sql/init.sql
```
---
## API 接口清单
### 认证接口
- `POST /api/auth/login` - 用户登录
- `GET /api/auth/user/info` - 获取当前用户信息
- `POST /api/auth/logout` - 用户退出
- `POST /api/auth/change-password` - 修改密码
- `POST /api/auth/reset-password` - 重置密码
### 报备接口
- `GET /api/report/page` - 分页查询报备
- `GET /api/report/{id}` - 获取报备详情
- `POST /api/report` - 创建报备
- `PUT /api/report/{id}` - 更新报备
- `DELETE /api/report/{id}` - 撤回报备
- `PUT /api/report/{id}/audit` - 审核报备
### 进展记录接口
- `GET /api/report-progress/list?reportId={id}` - 查询进展记录
- `POST /api/report-progress` - 添加进展记录
- `PUT /api/report-progress/{id}` - 编辑进展记录
### 经销商接口(仅管理员)
- `GET /api/dealer/list` - 查询所有经销商
- `POST /api/dealer` - 创建经销商
- `PUT /api/dealer/{id}` - 更新经销商
- `DELETE /api/dealer/{id}` - 删除经销商
### 学校接口
- `GET /api/school/list` - 查询所有学校
- `GET /api/school/search?keyword=xxx` - 搜索学校
- `POST /api/school` - 创建学校
- `POST /api/school/import` - 文件上传导入
---
## 版本历史
### v1.0.0 (当前版本)
**核心功能**:
- ✅ 报备管理(提交、审核、撤回、编辑、作废)
- ✅ 经销商管理(增删改查、重置密码)
- ✅ 学校管理(查询、搜索、导入)
- ✅ 用户认证(登录、密码管理)
- ✅ 进展记录(添加、编辑、查询)
- ✅ 自动保护期到期处理
- ✅ 防撞单机制(学校+产品+项目类型)
- ✅ 灵活的学校输入(库选择+手动输入)
**安全特性**:
- ✅ JWT Token 认证
- ✅ BCrypt 密码加密存储
- ✅ 权限注解拦截
- ✅ SQL 注入防护
- ✅ XSS 防护
---
## 常见问题
### 1. 默认账号是什么?
- 管理员账号:`admin` / `admin123`
- 经销商账号:根据经销商数据创建
### 2. 保护期可以修改吗?
- 可以,通过修改系统配置 `report.protect.days`
- 默认为 90 天,可根据业务需求调整
### 3. 同一学校项目可以同时被多个经销商报备吗?
- **不可以**,这是系统的核心规则
- 同一"学校+产品+项目类型"组合只能有一个"有效"报备
- 有效报备定义:状态为"待审核(0)"或"已通过(1)"
### 4. 保护期到期后会发生什么?
- 定时任务每天凌晨1点自动执行
- 将过期报备状态改为"已失效"
- 该"学校+产品+项目类型"组合可重新报备
### 5. 学校名称可以手动输入吗?
- 可以,支持从学校库选择或手动输入
- 从学校库选择时会保存 `school_id` 和标准学校名称
- 手动输入时 `school_id` 为 NULL保存用户输入的文本
### 6. 报备可以作废吗?
- 可以,只有管理员可以作废已通过的报备
- 作废时必须填写作废原因
- 作废后该"学校+产品+项目类型"组合可重新报备
---
## 技术支持
### 开发团队
- 后端开发Spring Boot + MyBatis + MySQL
- 前端开发Vue 3 + TypeScript + Element Plus
- 项目管理Maven + pnpm
### 联系方式
- 问题反馈:提交 Issue 到项目仓库
- 技术文档:详见 `docs/` 目录
---
**版权声明**: 本系统为企业内部使用,未经授权不得复制或传播。