经销商管理系统
| .claude | ||
| backend | ||
| docs | ||
| frontend | ||
| sql | ||
| .gitignore | ||
| CLAUDE.md | ||
| docker-compose.yml | ||
| 部署说明.md | ||
泊云智销通 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) | 联系电话 |
| 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 * * ?
功能:
- 查询所有保护期已到的报备(
protect_end_date <= 今天且status = 1) - 批量更新报备状态为"已失效"(
status = 3) - 释放资源,允许相同"学校+产品+项目类型"组合重新报备
核心代码: ReportExpireTask.java
@Scheduled(cron = "0 0 1 * * ?")
public void handleExpiredReports() {
log.info("开始处理过期报备...");
try {
reportService.handleExpiredReports();
log.info("过期报备处理完成");
} catch (Exception e) {
log.error("处理过期报备失败", e);
}
}
防撞单机制详解
核心规则
唯一性维度: 学校 + 产品 + 项目类型
有效报备定义: 状态为"待审核(0)"或"已通过(1)"的报备
校验逻辑
// 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 表中,包含:
- 学校标识码(唯一)
- 学校名称
- 所在地
前端交互
-
自动完成搜索
- 用户输入学校名称
- 前端调用
/api/school/search?keyword=xxx接口 - 后端模糊匹配
school_name - 返回匹配列表供用户选择
-
手动输入
- 用户可以直接输入学校名称
- 如果不从下拉列表选择,
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)
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)
前端配置
// API 基础地址
const BASE_URL = 'http://localhost:8080/api'
// Token 存储
const TOKEN_KEY = 'token'
部署说明
开发环境启动
后端:
cd backend
mvn spring-boot:run
# 访问: http://localhost:8080
# Swagger: http://localhost:8080/swagger-ui.html
前端:
cd frontend
pnpm install
pnpm dev
# 访问: http://localhost:5173
生产环境部署
后端:
# 编译打包
mvn clean package
# 运行 JAR
java -jar target/bycrm-0.0.1-SNAPSHOT.jar
前端:
# 编译打包
pnpm build
# 部署 dist 目录到 Web 服务器
数据库初始化
# 创建数据库
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/目录
版权声明: 本系统为企业内部使用,未经授权不得复制或传播。