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.
 
 
 

32 KiB

客户数据变更追踪详细方案

一、需求分析

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 表结构

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字段)

ALTER TABLE `users` 
ADD COLUMN `followup_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '跟进时间(只记录第一次)';

2.2.2 扩展设计

方案1:在现有表中扩展字段

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:创建新的字段变更记录表

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 数据变更追踪拦截器

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 扩展

// 添加详细变更记录方法
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 实体类扩展

// 添加三个新字段
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 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实体类扩展

// 添加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 回流功能实现

// 提交跟进信息并更新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 定时任务配置

package com.example.web.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;

/**
 * 定时任务配置
 */
@Configuration
@EnableScheduling
public class SchedulingConfig {
}

四、前端展示方案

4.1 变更记录列表展示

<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 变更详情展示

<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处理

// 解析变更字段
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
    • 添加三个新字段:originalDatamodifiedDatachangedFields
  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. 可扩展性:设计考虑了未来的扩展需求

该方案可以满足需求,实现客户数据的详细变更追踪和客户回流功能。