You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
798 lines
32 KiB
798 lines
32 KiB
|
3 months ago
|
# 客户数据变更追踪详细方案
|
||
|
|
|
||
|
|
## 一、需求分析
|
||
|
|
|
||
|
|
### 1.1 核心需求
|
||
|
|
- 实现客户数据的详细变更追踪功能
|
||
|
|
- 记录业务员修改客户的哪些字段数据
|
||
|
|
- 详细展示原始数据和修改后的数据
|
||
|
|
- 基于现有的 `informationtra` 表进行扩展
|
||
|
|
- 实现客户回流功能,根据跟进情况自动调整客户等级
|
||
|
|
|
||
|
|
### 1.2 客户回流需求
|
||
|
|
- 在 `users` 表中添加 `followup_at`(跟进时间)字段
|
||
|
|
- `followup_at` 默认为 `updated_at` 的时间,只记录第一次之后便不更改
|
||
|
|
- 与数据库 `updated_at` 字段做对比,判断客户是否需要回流
|
||
|
|
- 回流条件:三天内 `updated_at` 发生变化但 `followup_at` 没有发生变化
|
||
|
|
- 回流操作:将客户等级变为 `organization-sea-pools`
|
||
|
|
- 提交跟进时需要更新 `followup_at`
|
||
|
|
- 客户回流操作需要被记录到信息跟踪表中
|
||
|
|
|
||
|
|
### 1.2 技术现状
|
||
|
|
- 已存在 `informationtra` 表用于记录操作跟踪
|
||
|
|
- 已存在 `Users` 实体类表示客户信息
|
||
|
|
- 已存在 `InformationTraService` 用于记录操作事件
|
||
|
|
- 需要实现字段级别的数据变更追踪
|
||
|
|
|
||
|
|
## 二、方案设计
|
||
|
|
|
||
|
|
### 2.1 架构设计
|
||
|
|
|
||
|
|
```
|
||
|
|
┌─────────────────┐ ┌───────────────────────┐ ┌─────────────────────┐
|
||
|
|
│ 客户修改请求 │────▶│ 数据变更追踪拦截器 │────▶│ InformationTraService │
|
||
|
|
└─────────────────┘ └───────────────────────┘ └─────────────────────┘
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
┌─────────────────────┐
|
||
|
|
│ informationtra表 │
|
||
|
|
└─────────────────────┘
|
||
|
|
|
||
|
|
┌─────────────────┐ ┌───────────────────────┐ ┌─────────────────────┐
|
||
|
|
│ 定时任务触发 │────▶│ 客户回流检查逻辑 │────▶│ CustomerService │
|
||
|
|
└─────────────────┘ └───────────────────────┘ └─────────────────────┘
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
┌─────────────────────┐
|
||
|
|
│ users表更新 │
|
||
|
|
└─────────────────────┘
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
┌─────────────────────┐
|
||
|
|
│ informationtra表 │
|
||
|
|
└─────────────────────┘
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2.2 数据结构设计
|
||
|
|
|
||
|
|
#### 2.2.1 现有 `informationtra` 表结构
|
||
|
|
```sql
|
||
|
|
CREATE TABLE `informationtra` (
|
||
|
|
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||
|
|
`tracompany` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '修改者公司',
|
||
|
|
`tradepartment` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '修改者部门',
|
||
|
|
`traorganization` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '修改者组织',
|
||
|
|
`trarole` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '修改者角色',
|
||
|
|
`trauserName` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '修改者名字',
|
||
|
|
`traassistant` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '修改协助人',
|
||
|
|
`userId` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '客户ID',
|
||
|
|
`operationEvent` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '操作事件',
|
||
|
|
`operationTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
|
||
|
|
`created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||
|
|
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||
|
|
PRIMARY KEY (`id`),
|
||
|
|
KEY `idx_userId` (`userId`),
|
||
|
|
KEY `idx_operationTime` (`operationTime`),
|
||
|
|
KEY `idx_trauserName` (`trauserName`)
|
||
|
|
) ENGINE=InnoDB AUTO_INCREMENT=258 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='信息跟踪表';
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 2.2.2 `users` 表结构修改(添加followup_at字段)
|
||
|
|
```sql
|
||
|
|
ALTER TABLE `users`
|
||
|
|
ADD COLUMN `followup_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '跟进时间(只记录第一次)';
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 2.2.2 扩展设计
|
||
|
|
|
||
|
|
**方案1:在现有表中扩展字段**
|
||
|
|
```sql
|
||
|
|
ALTER TABLE `informationtra`
|
||
|
|
ADD COLUMN `originalData` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '原始数据JSON',
|
||
|
|
ADD COLUMN `modifiedData` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '修改后数据JSON',
|
||
|
|
ADD COLUMN `changedFields` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '变更字段列表JSON';
|
||
|
|
```
|
||
|
|
|
||
|
|
**方案2:创建新的字段变更记录表**
|
||
|
|
```sql
|
||
|
|
CREATE TABLE `informationtra_fields` (
|
||
|
|
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||
|
|
`traId` int NOT NULL COMMENT '关联informationtra表ID',
|
||
|
|
`fieldName` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '字段名',
|
||
|
|
`fieldLabel` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '字段中文名称',
|
||
|
|
`originalValue` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '原始值',
|
||
|
|
`modifiedValue` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '修改后的值',
|
||
|
|
PRIMARY KEY (`id`),
|
||
|
|
KEY `idx_traId` (`traId`),
|
||
|
|
KEY `idx_fieldName` (`fieldName`)
|
||
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='信息跟踪字段变更表';
|
||
|
|
```
|
||
|
|
**推荐方案**:方案1,直接在现有表中扩展字段,实现简单,查询方便。
|
||
|
|
|
||
|
|
## 三、核心功能实现
|
||
|
|
|
||
|
|
### 3.1 数据变更追踪拦截器
|
||
|
|
|
||
|
|
```java
|
||
|
|
package com.example.web.aspect;
|
||
|
|
|
||
|
|
import com.example.web.entity.Users;
|
||
|
|
import com.example.web.service.InformationTraService;
|
||
|
|
import org.aspectj.lang.JoinPoint;
|
||
|
|
import org.aspectj.lang.annotation.AfterReturning;
|
||
|
|
import org.aspectj.lang.annotation.Aspect;
|
||
|
|
import org.aspectj.lang.annotation.Before;
|
||
|
|
import org.aspectj.lang.annotation.Pointcut;
|
||
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
||
|
|
import org.springframework.stereotype.Component;
|
||
|
|
|
||
|
|
import java.util.HashMap;
|
||
|
|
import java.util.Map;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 客户数据变更追踪拦截器
|
||
|
|
*/
|
||
|
|
@Aspect
|
||
|
|
@Component
|
||
|
|
public class CustomerDataChangeAspect {
|
||
|
|
|
||
|
|
@Autowired
|
||
|
|
private InformationTraService informationTraService;
|
||
|
|
|
||
|
|
@Autowired
|
||
|
|
private CustomerService customerService;
|
||
|
|
|
||
|
|
// 存储原始数据的临时缓存
|
||
|
|
private ThreadLocal<Map<String, Object>> originalDataCache = new ThreadLocal<>();
|
||
|
|
|
||
|
|
// 定义切入点:客户修改相关的方法
|
||
|
|
@Pointcut("execution(* com.example.web.service.CustomerService.update*(..))")
|
||
|
|
public void customerUpdatePointcut() {}
|
||
|
|
|
||
|
|
// 定义切入点:跟进信息更新方法
|
||
|
|
@Pointcut("execution(* com.example.web.service.CustomerService.submitFollowup*(..))")
|
||
|
|
public void followupSubmitPointcut() {}
|
||
|
|
|
||
|
|
// 修改前获取原始数据
|
||
|
|
@Before("customerUpdatePointcut() || followupSubmitPointcut()")
|
||
|
|
public void beforeUpdate(JoinPoint joinPoint) {
|
||
|
|
// 获取方法参数
|
||
|
|
Object[] args = joinPoint.getArgs();
|
||
|
|
for (Object arg : args) {
|
||
|
|
if (arg instanceof Users) {
|
||
|
|
Users user = (Users) arg;
|
||
|
|
// 这里需要根据userId查询数据库获取原始数据
|
||
|
|
Users originalUser = customerService.selectById(user.getUserId());
|
||
|
|
if (originalUser != null) {
|
||
|
|
// 将原始数据转换为Map存储
|
||
|
|
Map<String, Object> originalData = convertUserToMap(originalUser);
|
||
|
|
originalDataCache.set(originalData);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 修改后记录变更
|
||
|
|
@AfterReturning("customerUpdatePointcut()")
|
||
|
|
public void afterUpdate(JoinPoint joinPoint) {
|
||
|
|
// 获取修改后的数据
|
||
|
|
Object[] args = joinPoint.getArgs();
|
||
|
|
for (Object arg : args) {
|
||
|
|
if (arg instanceof Users) {
|
||
|
|
Users modifiedUser = (Users) arg;
|
||
|
|
// 获取原始数据
|
||
|
|
Map<String, Object> originalData = originalDataCache.get();
|
||
|
|
if (originalData != null) {
|
||
|
|
// 转换修改后的数据为Map
|
||
|
|
Map<String, Object> modifiedData = convertUserToMap(modifiedUser);
|
||
|
|
// 比较并记录变更
|
||
|
|
Map<String, Object> changedFields = compareData(originalData, modifiedData);
|
||
|
|
if (!changedFields.isEmpty()) {
|
||
|
|
// 记录变更到数据库
|
||
|
|
informationTraService.recordDetailedChange(
|
||
|
|
originalData,
|
||
|
|
modifiedData,
|
||
|
|
changedFields,
|
||
|
|
modifiedUser.getUserId()
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// 清除缓存
|
||
|
|
originalDataCache.remove();
|
||
|
|
}
|
||
|
|
|
||
|
|
// 跟进提交后更新followup_at并记录变更
|
||
|
|
@AfterReturning("followupSubmitPointcut()")
|
||
|
|
public void afterFollowupSubmit(JoinPoint joinPoint) {
|
||
|
|
// 获取方法参数
|
||
|
|
Object[] args = joinPoint.getArgs();
|
||
|
|
for (Object arg : args) {
|
||
|
|
if (arg instanceof Users) {
|
||
|
|
Users modifiedUser = (Users) arg;
|
||
|
|
// 获取原始数据
|
||
|
|
Map<String, Object> originalData = originalDataCache.get();
|
||
|
|
if (originalData != null) {
|
||
|
|
// 转换修改后的数据为Map(包含更新后的followup_at)
|
||
|
|
Map<String, Object> modifiedData = convertUserToMap(modifiedUser);
|
||
|
|
// 比较并记录变更
|
||
|
|
Map<String, Object> changedFields = compareData(originalData, modifiedData);
|
||
|
|
if (!changedFields.isEmpty()) {
|
||
|
|
// 记录变更到数据库
|
||
|
|
informationTraService.recordDetailedChange(
|
||
|
|
originalData,
|
||
|
|
modifiedData,
|
||
|
|
changedFields,
|
||
|
|
modifiedUser.getUserId()
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// 清除缓存
|
||
|
|
originalDataCache.remove();
|
||
|
|
}
|
||
|
|
|
||
|
|
// 将Users对象转换为Map
|
||
|
|
private Map<String, Object> convertUserToMap(Users user) {
|
||
|
|
Map<String, Object> map = new HashMap<>();
|
||
|
|
map.put("id", user.getId());
|
||
|
|
map.put("openid", user.getOpenid());
|
||
|
|
map.put("userId", user.getUserId());
|
||
|
|
map.put("nickName", user.getNickName());
|
||
|
|
map.put("avatarUrl", user.getAvatarUrl());
|
||
|
|
map.put("phoneNumber", user.getPhoneNumber());
|
||
|
|
map.put("type", user.getType());
|
||
|
|
map.put("gender", user.getGender());
|
||
|
|
map.put("country", user.getCountry());
|
||
|
|
map.put("province", user.getProvince());
|
||
|
|
map.put("city", user.getCity());
|
||
|
|
map.put("language", user.getLanguage());
|
||
|
|
map.put("company", user.getCompany());
|
||
|
|
map.put("region", user.getRegion());
|
||
|
|
map.put("level", user.getLevel());
|
||
|
|
map.put("demand", user.getDemand());
|
||
|
|
map.put("spec", user.getSpec());
|
||
|
|
map.put("followup", user.getFollowup());
|
||
|
|
map.put("notice", user.getNotice());
|
||
|
|
map.put("followup_at", user.getFollowupAt());
|
||
|
|
map.put("updated_at", user.getUpdated_at());
|
||
|
|
return map;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 比较原始数据和修改后的数据,返回变更的字段
|
||
|
|
private Map<String, Object> compareData(Map<String, Object> original, Map<String, Object> modified) {
|
||
|
|
Map<String, Object> changes = new HashMap<>();
|
||
|
|
for (String key : original.keySet()) {
|
||
|
|
Object originalValue = original.get(key);
|
||
|
|
Object modifiedValue = modified.get(key);
|
||
|
|
if (originalValue == null && modifiedValue != null) {
|
||
|
|
// 从null变为有值
|
||
|
|
Map<String, Object> fieldChange = new HashMap<>();
|
||
|
|
fieldChange.put("original", null);
|
||
|
|
fieldChange.put("modified", modifiedValue);
|
||
|
|
changes.put(key, fieldChange);
|
||
|
|
} else if (originalValue != null && modifiedValue == null) {
|
||
|
|
// 从有值变为null
|
||
|
|
Map<String, Object> fieldChange = new HashMap<>();
|
||
|
|
fieldChange.put("original", originalValue);
|
||
|
|
fieldChange.put("modified", null);
|
||
|
|
changes.put(key, fieldChange);
|
||
|
|
} else if (originalValue != null && !originalValue.equals(modifiedValue)) {
|
||
|
|
// 值发生了变化
|
||
|
|
Map<String, Object> fieldChange = new HashMap<>();
|
||
|
|
fieldChange.put("original", originalValue);
|
||
|
|
fieldChange.put("modified", modifiedValue);
|
||
|
|
changes.put(key, fieldChange);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return changes;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3.2 InformationTraService 扩展
|
||
|
|
|
||
|
|
```java
|
||
|
|
// 添加详细变更记录方法
|
||
|
|
public boolean recordDetailedChange(
|
||
|
|
Map<String, Object> originalData,
|
||
|
|
Map<String, Object> modifiedData,
|
||
|
|
Map<String, Object> changedFields,
|
||
|
|
String userId) {
|
||
|
|
try {
|
||
|
|
// 获取当前登录用户信息(需要从上下文获取)
|
||
|
|
String tracompany = "当前公司";
|
||
|
|
String tradepartment = "当前部门";
|
||
|
|
String traorganization = "当前组织";
|
||
|
|
String trarole = "当前角色";
|
||
|
|
String trauserName = "当前用户";
|
||
|
|
String traassistant = "当前协助人";
|
||
|
|
LocalDateTime now = LocalDateTime.now();
|
||
|
|
|
||
|
|
// 构造信息跟踪记录
|
||
|
|
InformationTra informationTra = new InformationTra();
|
||
|
|
informationTra.setTracompany(tracompany);
|
||
|
|
informationTra.setTradepartment(tradepartment);
|
||
|
|
informationTra.setTraorganization(traorganization);
|
||
|
|
informationTra.setTrarole(trarole);
|
||
|
|
informationTra.setTrauserName(trauserName);
|
||
|
|
informationTra.setTraassistant(traassistant);
|
||
|
|
informationTra.setUserId(userId);
|
||
|
|
|
||
|
|
// 生成操作事件描述
|
||
|
|
String operationEvent = generateOperationEvent(changedFields);
|
||
|
|
informationTra.setOperationEvent(operationEvent);
|
||
|
|
|
||
|
|
// 转换为JSON字符串存储
|
||
|
|
ObjectMapper objectMapper = new ObjectMapper();
|
||
|
|
String originalDataJson = objectMapper.writeValueAsString(originalData);
|
||
|
|
String modifiedDataJson = objectMapper.writeValueAsString(modifiedData);
|
||
|
|
String changedFieldsJson = objectMapper.writeValueAsString(changedFields);
|
||
|
|
|
||
|
|
informationTra.setOriginalData(originalDataJson);
|
||
|
|
informationTra.setModifiedData(modifiedDataJson);
|
||
|
|
informationTra.setChangedFields(changedFieldsJson);
|
||
|
|
|
||
|
|
informationTra.setOperationTime(now);
|
||
|
|
informationTra.setCreatedAt(now);
|
||
|
|
informationTra.setUpdatedAt(now);
|
||
|
|
|
||
|
|
// 写入数据库
|
||
|
|
DynamicDataSource.setDataSourceKey("wechat");
|
||
|
|
int result = informationTraMapper.insertInformationTra(informationTra);
|
||
|
|
return result > 0;
|
||
|
|
} catch (Exception e) {
|
||
|
|
e.printStackTrace();
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 记录客户回流操作
|
||
|
|
public boolean recordCustomerReturn(String userId, Map<String, Object> originalData, Map<String, Object> modifiedData) {
|
||
|
|
try {
|
||
|
|
// 获取系统用户信息
|
||
|
|
String tracompany = "系统自动";
|
||
|
|
String tradepartment = "系统自动";
|
||
|
|
String traorganization = "系统自动";
|
||
|
|
String trarole = "系统自动";
|
||
|
|
String trauserName = "系统自动";
|
||
|
|
String traassistant = "系统自动";
|
||
|
|
LocalDateTime now = LocalDateTime.now();
|
||
|
|
|
||
|
|
// 构造信息跟踪记录
|
||
|
|
InformationTra informationTra = new InformationTra();
|
||
|
|
informationTra.setTracompany(tracompany);
|
||
|
|
informationTra.setTradepartment(tradepartment);
|
||
|
|
informationTra.setTraorganization(traorganization);
|
||
|
|
informationTra.setTrarole(trarole);
|
||
|
|
informationTra.setTrauserName(trauserName);
|
||
|
|
informationTra.setTraassistant(traassistant);
|
||
|
|
informationTra.setUserId(userId);
|
||
|
|
|
||
|
|
// 生成操作事件描述
|
||
|
|
String operationEvent = "客户回流:由于三天内有数据更新但未跟进,客户等级变为organization-sea-pools";
|
||
|
|
informationTra.setOperationEvent(operationEvent);
|
||
|
|
|
||
|
|
// 转换为JSON字符串存储
|
||
|
|
ObjectMapper objectMapper = new ObjectMapper();
|
||
|
|
String originalDataJson = objectMapper.writeValueAsString(originalData);
|
||
|
|
String modifiedDataJson = objectMapper.writeValueAsString(modifiedData);
|
||
|
|
|
||
|
|
// 构造变更字段
|
||
|
|
Map<String, Object> changedFields = new HashMap<>();
|
||
|
|
Map<String, Object> levelChange = new HashMap<>();
|
||
|
|
levelChange.put("original", originalData.get("level"));
|
||
|
|
levelChange.put("modified", "organization-sea-pools");
|
||
|
|
changedFields.put("level", levelChange);
|
||
|
|
|
||
|
|
String changedFieldsJson = objectMapper.writeValueAsString(changedFields);
|
||
|
|
|
||
|
|
informationTra.setOriginalData(originalDataJson);
|
||
|
|
informationTra.setModifiedData(modifiedDataJson);
|
||
|
|
informationTra.setChangedFields(changedFieldsJson);
|
||
|
|
|
||
|
|
informationTra.setOperationTime(now);
|
||
|
|
informationTra.setCreatedAt(now);
|
||
|
|
informationTra.setUpdatedAt(now);
|
||
|
|
|
||
|
|
// 写入数据库
|
||
|
|
DynamicDataSource.setDataSourceKey("wechat");
|
||
|
|
int result = informationTraMapper.insertInformationTra(informationTra);
|
||
|
|
return result > 0;
|
||
|
|
} catch (Exception e) {
|
||
|
|
e.printStackTrace();
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 生成操作事件描述
|
||
|
|
private String generateOperationEvent(Map<String, Object> changedFields) {
|
||
|
|
StringBuilder sb = new StringBuilder("修改了客户信息:");
|
||
|
|
for (String fieldName : changedFields.keySet()) {
|
||
|
|
// 转换为中文字段名
|
||
|
|
String fieldLabel = getFieldLabel(fieldName);
|
||
|
|
sb.append(fieldLabel).append("、");
|
||
|
|
}
|
||
|
|
// 移除最后一个顿号
|
||
|
|
if (sb.length() > 5) {
|
||
|
|
sb.deleteCharAt(sb.length() - 1);
|
||
|
|
}
|
||
|
|
return sb.toString();
|
||
|
|
}
|
||
|
|
|
||
|
|
// 字段名映射
|
||
|
|
private String getFieldLabel(String fieldName) {
|
||
|
|
Map<String, String> fieldMap = new HashMap<>();
|
||
|
|
fieldMap.put("nickName", "客户昵称");
|
||
|
|
fieldMap.put("phoneNumber", "客户手机号");
|
||
|
|
fieldMap.put("company", "客户公司");
|
||
|
|
fieldMap.put("region", "客户地区");
|
||
|
|
fieldMap.put("level", "客户等级");
|
||
|
|
fieldMap.put("demand", "客户需求");
|
||
|
|
fieldMap.put("spec", "客户规格");
|
||
|
|
fieldMap.put("followup", "跟进信息");
|
||
|
|
fieldMap.put("notice", "通知状态");
|
||
|
|
fieldMap.put("followup_at", "跟进时间");
|
||
|
|
fieldMap.put("updated_at", "更新时间");
|
||
|
|
// 可以继续添加其他字段映射
|
||
|
|
|
||
|
|
return fieldMap.getOrDefault(fieldName, fieldName);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3.3 InformationTra 实体类扩展
|
||
|
|
|
||
|
|
```java
|
||
|
|
// 添加三个新字段
|
||
|
|
private String originalData; // 原始数据JSON
|
||
|
|
private String modifiedData; // 修改后数据JSON
|
||
|
|
private String changedFields; // 变更字段列表JSON
|
||
|
|
|
||
|
|
// 添加对应的getter和setter方法
|
||
|
|
public String getOriginalData() {
|
||
|
|
return originalData;
|
||
|
|
}
|
||
|
|
|
||
|
|
public void setOriginalData(String originalData) {
|
||
|
|
this.originalData = originalData;
|
||
|
|
}
|
||
|
|
|
||
|
|
public String getModifiedData() {
|
||
|
|
return modifiedData;
|
||
|
|
}
|
||
|
|
|
||
|
|
public void setModifiedData(String modifiedData) {
|
||
|
|
this.modifiedData = modifiedData;
|
||
|
|
}
|
||
|
|
|
||
|
|
public String getChangedFields() {
|
||
|
|
return changedFields;
|
||
|
|
}
|
||
|
|
|
||
|
|
public void setChangedFields(String changedFields) {
|
||
|
|
this.changedFields = changedFields;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3.4 InformationTraMapper 扩展
|
||
|
|
|
||
|
|
```xml
|
||
|
|
<!-- 在XML映射文件中添加三个新字段 -->
|
||
|
|
<insert id="insertInformationTra" parameterType="com.example.web.entity.InformationTra">
|
||
|
|
INSERT INTO informationtra (
|
||
|
|
tracompany, tradepartment, traorganization, trarole, trauserName,
|
||
|
|
traassistant, userId, operationEvent, operationTime, created_at,
|
||
|
|
updated_at, originalData, modifiedData, changedFields
|
||
|
|
) VALUES (
|
||
|
|
#{tracompany}, #{tradepartment}, #{traorganization}, #{trarole}, #{trauserName},
|
||
|
|
#{traassistant}, #{userId}, #{operationEvent}, #{operationTime}, #{createdAt},
|
||
|
|
#{updatedAt}, #{originalData}, #{modifiedData}, #{changedFields}
|
||
|
|
)
|
||
|
|
</insert>
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3.5 客户回流功能实现
|
||
|
|
|
||
|
|
#### 3.5.1 Users实体类扩展
|
||
|
|
|
||
|
|
```java
|
||
|
|
// 添加followup_at字段
|
||
|
|
private LocalDateTime followup_at; // 跟进时间(只记录第一次)
|
||
|
|
|
||
|
|
// 添加对应的getter和setter方法
|
||
|
|
public LocalDateTime getFollowup_at() {
|
||
|
|
return followup_at;
|
||
|
|
}
|
||
|
|
|
||
|
|
public void setFollowup_at(LocalDateTime followup_at) {
|
||
|
|
this.followup_at = followup_at;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 3.5.2 CustomerService 回流功能实现
|
||
|
|
|
||
|
|
```java
|
||
|
|
// 提交跟进信息并更新followup_at
|
||
|
|
public boolean submitFollowup(Users user) {
|
||
|
|
try {
|
||
|
|
// 查询当前用户信息
|
||
|
|
Users currentUser = this.selectById(user.getUserId());
|
||
|
|
if (currentUser == null) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 设置更新时间
|
||
|
|
LocalDateTime now = LocalDateTime.now();
|
||
|
|
user.setUpdated_at(now);
|
||
|
|
|
||
|
|
// 如果是第一次提交跟进或者followup_at为空,则设置followup_at
|
||
|
|
if (currentUser.getFollowup_at() == null) {
|
||
|
|
user.setFollowup_at(now);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 更新数据库
|
||
|
|
DynamicDataSource.setDataSourceKey("wechat");
|
||
|
|
int result = usersMapper.update(user);
|
||
|
|
return result > 0;
|
||
|
|
} catch (Exception e) {
|
||
|
|
e.printStackTrace();
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 检查并处理客户回流
|
||
|
|
@Scheduled(cron = "0 0 0 * * ?") // 每天凌晨执行
|
||
|
|
public void checkCustomerReturn() {
|
||
|
|
try {
|
||
|
|
DynamicDataSource.setDataSourceKey("wechat");
|
||
|
|
// 查询所有用户
|
||
|
|
List<Users> users = usersMapper.selectAll();
|
||
|
|
|
||
|
|
LocalDateTime now = LocalDateTime.now();
|
||
|
|
LocalDateTime threeDaysAgo = now.minusDays(3);
|
||
|
|
|
||
|
|
for (Users user : users) {
|
||
|
|
// 跳过空记录
|
||
|
|
if (user == null || user.getUserId() == null) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 获取用户的updated_at和followup_at
|
||
|
|
LocalDateTime updatedAt = user.getUpdated_at();
|
||
|
|
LocalDateTime followupAt = user.getFollowup_at();
|
||
|
|
|
||
|
|
// 如果followup_at为空,使用创建时间作为初始值
|
||
|
|
if (followupAt == null) {
|
||
|
|
followupAt = user.getCreated_at();
|
||
|
|
user.setFollowup_at(followupAt);
|
||
|
|
usersMapper.update(user);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 检查条件:三天内updated_at发生变化,但followup_at没有发生变化
|
||
|
|
if (updatedAt != null && followupAt != null) {
|
||
|
|
// 判断updated_at是否在三天内
|
||
|
|
if (updatedAt.isAfter(threeDaysAgo)) {
|
||
|
|
// 判断followup_at是否在三天内(如果在三天内说明有跟进)
|
||
|
|
if (!followupAt.isAfter(threeDaysAgo)) {
|
||
|
|
// 符合回流条件,将客户等级变为organization-sea-pools
|
||
|
|
Users originalUser = new Users();
|
||
|
|
BeanUtils.copyProperties(user, originalUser);
|
||
|
|
|
||
|
|
// 更新客户等级
|
||
|
|
user.setLevel("organization-sea-pools");
|
||
|
|
user.setUpdated_at(now);
|
||
|
|
|
||
|
|
// 保存更新
|
||
|
|
int result = usersMapper.update(user);
|
||
|
|
if (result > 0) {
|
||
|
|
// 记录回流操作
|
||
|
|
Map<String, Object> originalData = convertUserToMap(originalUser);
|
||
|
|
Map<String, Object> modifiedData = convertUserToMap(user);
|
||
|
|
informationTraService.recordCustomerReturn(user.getUserId(), originalData, modifiedData);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} catch (Exception e) {
|
||
|
|
e.printStackTrace();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 将Users对象转换为Map(辅助方法)
|
||
|
|
private Map<String, Object> convertUserToMap(Users user) {
|
||
|
|
Map<String, Object> map = new HashMap<>();
|
||
|
|
map.put("id", user.getId());
|
||
|
|
map.put("openid", user.getOpenid());
|
||
|
|
map.put("userId", user.getUserId());
|
||
|
|
map.put("nickName", user.getNickName());
|
||
|
|
map.put("phoneNumber", user.getPhoneNumber());
|
||
|
|
map.put("company", user.getCompany());
|
||
|
|
map.put("region", user.getRegion());
|
||
|
|
map.put("level", user.getLevel());
|
||
|
|
map.put("followup", user.getFollowup());
|
||
|
|
map.put("followup_at", user.getFollowup_at());
|
||
|
|
map.put("updated_at", user.getUpdated_at());
|
||
|
|
map.put("created_at", user.getCreated_at());
|
||
|
|
return map;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 3.5.3 定时任务配置
|
||
|
|
|
||
|
|
```java
|
||
|
|
package com.example.web.config;
|
||
|
|
|
||
|
|
import org.springframework.context.annotation.Configuration;
|
||
|
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 定时任务配置
|
||
|
|
*/
|
||
|
|
@Configuration
|
||
|
|
@EnableScheduling
|
||
|
|
public class SchedulingConfig {
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## 四、前端展示方案
|
||
|
|
|
||
|
|
### 4.1 变更记录列表展示
|
||
|
|
|
||
|
|
```html
|
||
|
|
<table class="change-record-table">
|
||
|
|
<thead>
|
||
|
|
<tr>
|
||
|
|
<th>修改时间</th>
|
||
|
|
<th>修改人</th>
|
||
|
|
<th>部门</th>
|
||
|
|
<th>操作事件</th>
|
||
|
|
<th>操作</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
<tr v-for="record in changeRecords" :key="record.id">
|
||
|
|
<td>{{ record.operationTime }}</td>
|
||
|
|
<td>{{ record.trauserName }}</td>
|
||
|
|
<td>{{ record.tradepartment }}</td>
|
||
|
|
<td>
|
||
|
|
<span :class="{'system-operation': record.trauserName === '系统自动'}">
|
||
|
|
{{ record.operationEvent }}
|
||
|
|
</span>
|
||
|
|
</td>
|
||
|
|
<td>
|
||
|
|
<button @click="showDetail(record)">查看详情</button>
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4.2 变更详情展示
|
||
|
|
|
||
|
|
```html
|
||
|
|
<div class="change-detail">
|
||
|
|
<h3>修改详情</h3>
|
||
|
|
<div class="detail-info">
|
||
|
|
<p><strong>修改时间:</strong>{{ currentRecord.operationTime }}</p>
|
||
|
|
<p><strong>修改人:</strong>{{ currentRecord.trauserName }}</p>
|
||
|
|
<p><strong>部门:</strong>{{ currentRecord.tradepartment }}</p>
|
||
|
|
<p><strong>公司:</strong>{{ currentRecord.tracompany }}</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<h4>变更字段详情</h4>
|
||
|
|
<table class="field-change-table">
|
||
|
|
<thead>
|
||
|
|
<tr>
|
||
|
|
<th>字段名称</th>
|
||
|
|
<th>原始值</th>
|
||
|
|
<th>修改后的值</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
<tr v-for="(change, fieldName) in changedFields" :key="fieldName">
|
||
|
|
<td>{{ getFieldLabel(fieldName) }}</td>
|
||
|
|
<td class="original-value">{{ change.original || '空' }}</td>
|
||
|
|
<td class="modified-value">{{ change.modified || '空' }}</td>
|
||
|
|
</tr>
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
</div>
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4.3 前端JavaScript处理
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
// 解析变更字段
|
||
|
|
parseChangedFields(record) {
|
||
|
|
if (record.changedFields) {
|
||
|
|
return JSON.parse(record.changedFields);
|
||
|
|
}
|
||
|
|
return {};
|
||
|
|
}
|
||
|
|
|
||
|
|
// 字段名映射
|
||
|
|
getFieldLabel(fieldName) {
|
||
|
|
const fieldMap = {
|
||
|
|
'nickName': '客户昵称',
|
||
|
|
'phoneNumber': '客户手机号',
|
||
|
|
'company': '客户公司',
|
||
|
|
'region': '客户地区',
|
||
|
|
'level': '客户等级',
|
||
|
|
'demand': '客户需求',
|
||
|
|
'spec': '客户规格',
|
||
|
|
'followup': '跟进信息',
|
||
|
|
'notice': '通知状态',
|
||
|
|
'followup_at': '跟进时间',
|
||
|
|
'updated_at': '更新时间'
|
||
|
|
};
|
||
|
|
return fieldMap[fieldName] || fieldName;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 格式化日期时间
|
||
|
|
formatDateTime(dateTime) {
|
||
|
|
if (!dateTime) return '';
|
||
|
|
const date = new Date(dateTime);
|
||
|
|
return date.toLocaleString('zh-CN', {
|
||
|
|
year: 'numeric',
|
||
|
|
month: '2-digit',
|
||
|
|
day: '2-digit',
|
||
|
|
hour: '2-digit',
|
||
|
|
minute: '2-digit',
|
||
|
|
second: '2-digit'
|
||
|
|
});
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## 五、实施步骤
|
||
|
|
|
||
|
|
### 5.1 数据库修改
|
||
|
|
1. 执行SQL语句扩展 `informationtra` 表
|
||
|
|
- 添加三个新字段:`originalData`、`modifiedData`、`changedFields`
|
||
|
|
2. 执行SQL语句修改 `users` 表
|
||
|
|
- 添加 `followup_at` 字段:`ALTER TABLE users ADD COLUMN followup_at datetime DEFAULT CURRENT_TIMESTAMP COMMENT '跟进时间(只记录第一次)';`
|
||
|
|
|
||
|
|
### 5.2 代码修改
|
||
|
|
1. 扩展 `InformationTra` 实体类,添加三个新字段
|
||
|
|
2. 扩展 `InformationTraMapper`,支持新字段的插入
|
||
|
|
3. 实现 `CustomerDataChangeAspect` 拦截器
|
||
|
|
4. 扩展 `InformationTraService`,添加详细变更记录和回流记录方法
|
||
|
|
5. 扩展 `Users` 实体类,添加 `followup_at` 字段
|
||
|
|
6. 在 `CustomerService` 中实现客户回流逻辑
|
||
|
|
7. 添加定时任务配置,启用客户回流检查
|
||
|
|
8. 修改前端代码,支持变更记录的展示和回流操作的标记
|
||
|
|
|
||
|
|
### 5.3 测试验证
|
||
|
|
1. 修改客户信息,验证变更记录是否正确生成
|
||
|
|
2. 查看变更详情,验证原始数据和修改后数据是否正确展示
|
||
|
|
3. 测试提交跟进信息,验证 `followup_at` 是否正确设置
|
||
|
|
4. 模拟三天内有数据更新但未跟进的情况,验证客户是否自动回流
|
||
|
|
5. 检查回流操作是否被正确记录到信息跟踪表
|
||
|
|
6. 测试不同字段的变更情况
|
||
|
|
|
||
|
|
## 六、注意事项
|
||
|
|
|
||
|
|
1. **性能考虑**:JSON存储会增加数据库存储量,但对于客户追踪场景,性能影响可接受
|
||
|
|
2. **数据安全**:确保敏感字段的变更记录符合数据保护要求
|
||
|
|
3. **兼容性**:新字段设置为NULL,确保不影响现有功能
|
||
|
|
4. **扩展性**:设计时考虑未来可能的字段扩展需求
|
||
|
|
5. **用户体验**:前端展示要清晰直观,便于业务员快速查看变更内容
|
||
|
|
6. **定时任务性能**:客户回流检查逻辑应考虑数据量,避免影响系统性能
|
||
|
|
7. **followup_at初始值**:对于现有数据,需要进行初始化,将followup_at设置为updated_at的值
|
||
|
|
|
||
|
|
## 七、总结
|
||
|
|
|
||
|
|
本方案通过扩展现有的 `informationtra` 表和 `users` 表,实现了客户数据的字段级变更追踪和客户回流功能。主要特点包括:
|
||
|
|
|
||
|
|
1. **完整性**:详细记录原始数据和修改后的数据,包括客户回流操作
|
||
|
|
2. **灵活性**:基于现有架构进行扩展,无需大量修改
|
||
|
|
3. **易用性**:前端展示直观清晰,便于业务员查看变更记录和回流操作
|
||
|
|
4. **智能性**:自动检测客户跟进情况,实现客户等级的自动调整
|
||
|
|
5. **可扩展性**:设计考虑了未来的扩展需求
|
||
|
|
|
||
|
|
该方案可以满足需求,实现客户数据的详细变更追踪和客户回流功能。
|