# 客户信息跟踪系统解决方案 ## 1. 需求分析 ### 1.1 功能需求 - 前端记录业务员操作事件(查看详情、编辑、跟进) - 获取当前账号信息(公司、部门、组织、角色、姓名) - 根据电话号码在两个数据源中查询客户 - 将操作记录写入`informationtra`表 ### 1.2 数据源要求 - `wechat`数据源:`users`表和`informationtra`表 - `primary`数据源:`contacts`表 ### 1.3 数据流程 1. 前端传递操作事件、电话号码、事件类型 2. 后端获取当前用户认证信息 3. 根据电话号码查询两个数据源 4. 将查询结果与认证信息结合写入`informationtra`表 ## 2. 数据库设计 ### 2.1 现有表结构 ```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`), 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 ```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 ```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 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} ) ``` ### 3.3 Service设计 #### InformationTraService.java ```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 ```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> recordOperationEvent( @RequestBody Map requestBody, HttpServletRequest request) { Map 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 前端调用示例 ```javascript // 前端调用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. 添加操作记录审计功能,满足合规要求