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
13 KiB
客户信息跟踪系统解决方案
1. 需求分析
1.1 功能需求
- 前端记录业务员操作事件(查看详情、编辑、跟进)
- 获取当前账号信息(公司、部门、组织、角色、姓名)
- 根据电话号码在两个数据源中查询客户
- 将操作记录写入
informationtra表
1.2 数据源要求
wechat数据源:users表和informationtra表primary数据源:contacts表
1.3 数据流程
- 前端传递操作事件、电话号码、事件类型
- 后端获取当前用户认证信息
- 根据电话号码查询两个数据源
- 将查询结果与认证信息结合写入
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. 实现步骤
- 创建InformationTra实体类
- 创建InformationTraMapper接口和XML文件
- 创建InformationTraService
- 创建InformationTraController
- 在前端页面添加API调用逻辑
- 测试功能完整性
5. 注意事项
- 确保数据源切换正确,避免数据查询错误
- 验证电话号码格式,确保查询准确性
- 处理并发情况,确保操作记录的完整性
- 添加适当的日志记录,便于调试和监控
- 考虑添加权限控制,确保只有授权用户可以调用API
6. 扩展建议
- 添加操作记录查询功能,便于查看历史操作
- 实现操作记录统计分析,提供数据可视化
- 添加操作记录导出功能,支持Excel或PDF格式
- 实现操作记录告警功能,对异常操作进行告警
- 添加操作记录审计功能,满足合规要求