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.
377 lines
13 KiB
377 lines
13 KiB
|
3 months ago
|
# 客户信息跟踪系统解决方案
|
||
|
|
|
||
|
|
## 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
|
||
|
|
<?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
|
||
|
|
```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<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 前端调用示例
|
||
|
|
|
||
|
|
```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. 添加操作记录审计功能,满足合规要求
|