# 客户数据变更追踪详细方案 ## 一、需求分析 ### 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> 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 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 originalData = originalDataCache.get(); if (originalData != null) { // 转换修改后的数据为Map Map modifiedData = convertUserToMap(modifiedUser); // 比较并记录变更 Map 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 originalData = originalDataCache.get(); if (originalData != null) { // 转换修改后的数据为Map(包含更新后的followup_at) Map modifiedData = convertUserToMap(modifiedUser); // 比较并记录变更 Map changedFields = compareData(originalData, modifiedData); if (!changedFields.isEmpty()) { // 记录变更到数据库 informationTraService.recordDetailedChange( originalData, modifiedData, changedFields, modifiedUser.getUserId() ); } } break; } } // 清除缓存 originalDataCache.remove(); } // 将Users对象转换为Map private Map convertUserToMap(Users user) { Map 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 compareData(Map original, Map modified) { Map 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 fieldChange = new HashMap<>(); fieldChange.put("original", null); fieldChange.put("modified", modifiedValue); changes.put(key, fieldChange); } else if (originalValue != null && modifiedValue == null) { // 从有值变为null Map fieldChange = new HashMap<>(); fieldChange.put("original", originalValue); fieldChange.put("modified", null); changes.put(key, fieldChange); } else if (originalValue != null && !originalValue.equals(modifiedValue)) { // 值发生了变化 Map 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 originalData, Map modifiedData, Map 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 originalData, Map 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 changedFields = new HashMap<>(); Map 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 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 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 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} ) ``` ### 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 = 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 originalData = convertUserToMap(originalUser); Map modifiedData = convertUserToMap(user); informationTraService.recordCustomerReturn(user.getUserId(), originalData, modifiedData); } } } } } } catch (Exception e) { e.printStackTrace(); } } // 将Users对象转换为Map(辅助方法) private Map convertUserToMap(Users user) { Map 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
修改时间 修改人 部门 操作事件 操作
{{ record.operationTime }} {{ record.trauserName }} {{ record.tradepartment }} {{ record.operationEvent }}
``` ### 4.2 变更详情展示 ```html

修改详情

修改时间:{{ currentRecord.operationTime }}

修改人:{{ currentRecord.trauserName }}

部门:{{ currentRecord.tradepartment }}

公司:{{ currentRecord.tracompany }}

变更字段详情

字段名称 原始值 修改后的值
{{ getFieldLabel(fieldName) }} {{ change.original || '空' }} {{ change.modified || '空' }}
``` ### 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. **可扩展性**:设计考虑了未来的扩展需求 该方案可以满足需求,实现客户数据的详细变更追踪和客户回流功能。