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.
 
 
 

13 KiB

客户信息跟踪系统解决方案

1. 需求分析

1.1 功能需求

  • 前端记录业务员操作事件(查看详情、编辑、跟进)
  • 获取当前账号信息(公司、部门、组织、角色、姓名)
  • 根据电话号码在两个数据源中查询客户
  • 将操作记录写入informationtra

1.2 数据源要求

  • wechat数据源:users表和informationtra
  • primary数据源:contacts

1.3 数据流程

  1. 前端传递操作事件、电话号码、事件类型
  2. 后端获取当前用户认证信息
  3. 根据电话号码查询两个数据源
  4. 将查询结果与认证信息结合写入informationtra

2. 数据库设计

2.1 现有表结构

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`),
  CONSTRAINT `fk_informationtra_userId` FOREIGN KEY (`userId`) REFERENCES `users` (`userId`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='信息跟踪表';

3. 代码设计

3.1 实体类设计

InformationTra.java

package com.example.web.entity;

import java.time.LocalDateTime;

public class InformationTra {
    private Integer id;
    private String tracompany;
    private String tradepartment;
    private String traorganization;
    private String trarole;
    private String trauserName;
    private String traassistant;
    private String userId;
    private String operationEvent;
    private LocalDateTime operationTime;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
    
    // Getters and Setters
    // ...
}

3.2 Mapper设计

InformationTraMapper.java

package com.example.web.mapper;

import com.example.web.entity.InformationTra;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface InformationTraMapper {
    /**
     * 插入操作记录
     */
    int insertInformationTra(InformationTra informationTra);
    
    /**
     * 根据userId查询操作记录
     */
    InformationTra selectByUserId(@Param("userId") String userId);
}

InformationTraMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.web.mapper.InformationTraMapper">
    
    <resultMap id="informationTraMap" type="com.example.web.entity.InformationTra">
        <id property="id" column="id"/>
        <result property="tracompany" column="tracompany"/>
        <result property="tradepartment" column="tradepartment"/>
        <result property="traorganization" column="traorganization"/>
        <result property="trarole" column="trarole"/>
        <result property="trauserName" column="trauserName"/>
        <result property="traassistant" column="traassistant"/>
        <result property="userId" column="userId"/>
        <result property="operationEvent" column="operationEvent"/>
        <result property="operationTime" column="operationTime"/>
        <result property="createdAt" column="created_at"/>
        <result property="updatedAt" column="updated_at"/>
    </resultMap>
    
    <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
        ) VALUES (
            #{tracompany}, #{tradepartment}, #{traorganization}, #{trarole}, 
            #{trauserName}, #{traassistant}, #{userId}, #{operationEvent}, 
            #{operationTime}, #{createdAt}, #{updatedAt}
        )
    </insert>
    
    <select id="selectByUserId" resultMap="informationTraMap">
        SELECT * FROM informationtra WHERE userId = #{userId}
    </select>
</mapper>

3.3 Service设计

InformationTraService.java

package com.example.web.service;

import com.example.web.dto.ManagerAuthInfo;
import com.example.web.entity.InformationTra;
import com.example.web.mapper.*;
import com.example.web.config.DynamicDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

@Service
public class InformationTraService {
    
    @Autowired
    private InformationTraMapper informationTraMapper;
    
    @Autowired
    private UsersMapper usersMapper;
    
    @Autowired
    private ContactsMapper contactsMapper;
    
    /**
     * 记录操作事件
     */
    public boolean recordOperationEvent(String phoneNumber, String operationEvent, ManagerAuthInfo authInfo) {
        try {
            // 1. 从两个数据源查询客户信息
            String userId = null;
            
            // 查询wechat数据源的users表
            DynamicDataSource.setDataSourceKey("wechat");
            com.example.web.entity.Users wechatUser = usersMapper.selectByPhoneNumber(phoneNumber);
            
            if (wechatUser != null) {
                userId = wechatUser.getUserId();
            } else {
                // 查询primary数据源的contacts表
                DynamicDataSource.setDataSourceKey("primary");
                com.example.web.entity.Contacts contact = contactsMapper.selectByPhoneNumber(phoneNumber);
                
                if (contact != null) {
                    userId = contact.getId();
                }
            }
            
            // 如果都没找到,返回失败
            if (userId == null) {
                return false;
            }
            
            // 2. 构造操作记录
            InformationTra informationTra = new InformationTra();
            informationTra.setTracompany(authInfo.getManagercompany());
            informationTra.setTradepartment(authInfo.getManagerdepartment());
            informationTra.setTraorganization(authInfo.getOrganization());
            informationTra.setTrarole(authInfo.getRole());
            informationTra.setTrauserName(authInfo.getUserName());
            informationTra.setTraassistant(authInfo.getAssistant());
            informationTra.setUserId(userId);
            informationTra.setOperationEvent(operationEvent);
            informationTra.setOperationTime(LocalDateTime.now());
            informationTra.setCreatedAt(LocalDateTime.now());
            informationTra.setUpdatedAt(LocalDateTime.now());
            
            // 3. 写入wechat数据源的informationtra表
            DynamicDataSource.setDataSourceKey("wechat");
            int result = informationTraMapper.insertInformationTra(informationTra);
            
            return result > 0;
        } finally {
            // 清除数据源标识
            DynamicDataSource.clearDataSourceKey();
        }
    }
}

3.4 Controller设计

InformationTraController.java

package com.example.web.controller;

import com.example.web.dto.ManagerAuthInfo;
import com.example.web.service.InformationTraService;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/api/information-tracking")
public class InformationTraController {
    
    @Autowired
    private InformationTraService informationTraService;
    
    /**
     * 记录操作事件
     */
    @PostMapping("/record")
    public ResponseEntity<Map<String, Object>> recordOperationEvent(
            @RequestBody Map<String, String> requestBody,
            HttpServletRequest request) {
        
        Map<String, Object> response = new HashMap<>();
        
        try {
            // 获取请求参数
            String phoneNumber = requestBody.get("phoneNumber");
            String operationEvent = requestBody.get("operationEvent");
            
            // 验证参数
            if (phoneNumber == null || phoneNumber.isEmpty() || operationEvent == null || operationEvent.isEmpty()) {
                response.put("success", false);
                response.put("message", "电话号码和操作事件不能为空");
                return ResponseEntity.badRequest().body(response);
            }
            
            // 获取当前用户认证信息
            String isSupplySideParam = request.getParameter("isSupplySide");
            boolean isSupplySide = !"false".equalsIgnoreCase(isSupplySideParam);
            
            ManagerAuthInfo authInfo = null;
            if (isSupplySide) {
                // 供应端获取认证信息的逻辑
                authInfo = getManagerAuthInfoFromRequest(request, true);
            } else {
                // 销售端获取认证信息的逻辑
                authInfo = getManagerAuthInfoFromRequest(request, false);
            }
            
            if (authInfo == null) {
                response.put("success", false);
                response.put("message", "用户未登录或认证信息缺失");
                return ResponseEntity.status(401).body(response);
            }
            
            // 记录操作事件
            boolean success = informationTraService.recordOperationEvent(phoneNumber, operationEvent, authInfo);
            
            if (success) {
                response.put("success", true);
                response.put("message", "操作记录成功");
                return ResponseEntity.ok(response);
            } else {
                response.put("success", false);
                response.put("message", "未找到对应的客户信息");
                return ResponseEntity.badRequest().body(response);
            }
            
        } catch (Exception e) {
            response.put("success", false);
            response.put("message", "记录操作事件失败: " + e.getMessage());
            return ResponseEntity.internalServerError().body(response);
        }
    }
    
    /**
     * 从请求中获取认证信息(复用现有逻辑)
     */
    private ManagerAuthInfo getManagerAuthInfoFromRequest(HttpServletRequest request, boolean isSupplySide) {
        // 复用SupplyCustomerController或CustomerController中的现有逻辑
        // 这里需要根据实际情况实现
        return null;
    }
}

3.5 前端调用示例

// 前端调用API示例
function recordOperation(phoneNumber, operationEvent) {
    fetch('/api/information-tracking/record?isSupplySide=true', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            phoneNumber: phoneNumber,
            operationEvent: operationEvent
        })
    })
    .then(response => response.json())
    .then(data => {
        if (data.success) {
            console.log('操作记录成功');
        } else {
            console.error('操作记录失败:', data.message);
        }
    })
    .catch(error => {
        console.error('API调用失败:', error);
    });
}

// 查看客户详情时调用
recordOperation('13800138000', '查看客户详情');

// 编辑客户信息后调用
recordOperation('13800138000', '修改客户信息');

// 跟进客户后调用
recordOperation('13800138000', '更新客户跟进');

4. 实现步骤

  1. 创建InformationTra实体类
  2. 创建InformationTraMapper接口和XML文件
  3. 创建InformationTraService
  4. 创建InformationTraController
  5. 在前端页面添加API调用逻辑
  6. 测试功能完整性

5. 注意事项

  1. 确保数据源切换正确,避免数据查询错误
  2. 验证电话号码格式,确保查询准确性
  3. 处理并发情况,确保操作记录的完整性
  4. 添加适当的日志记录,便于调试和监控
  5. 考虑添加权限控制,确保只有授权用户可以调用API

6. 扩展建议

  1. 添加操作记录查询功能,便于查看历史操作
  2. 实现操作记录统计分析,提供数据可视化
  3. 添加操作记录导出功能,支持Excel或PDF格式
  4. 实现操作记录告警功能,对异常操作进行告警
  5. 添加操作记录审计功能,满足合规要求