diff --git a/src/main/java/com/example/web/WebApplication.java b/src/main/java/com/example/web/WebApplication.java index 67d0c8a..fbff558 100644 --- a/src/main/java/com/example/web/WebApplication.java +++ b/src/main/java/com/example/web/WebApplication.java @@ -2,6 +2,7 @@ package com.example.web; import me.paulschwarz.springdotenv.DotenvPropertySource; import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.PropertySource; @@ -9,6 +10,7 @@ import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @MapperScan("com.example.web.mapper") +@EnableAspectJAutoProxy//启用AOP自动代理 @EnableScheduling//定时任务 /*@PropertySource(value = "classpath:.env", factory = DotenvPropertySource.class)*/ public class WebApplication { diff --git a/src/main/java/com/example/web/aspect/CustomerDataChangeAspect.java b/src/main/java/com/example/web/aspect/CustomerDataChangeAspect.java new file mode 100644 index 0000000..1f83477 --- /dev/null +++ b/src/main/java/com/example/web/aspect/CustomerDataChangeAspect.java @@ -0,0 +1,383 @@ +package com.example.web.aspect; + +import com.example.web.entity.Users; +import com.example.web.dto.ManagerAuthInfo; +import com.example.web.dto.UnifiedCustomerDTO; +import com.example.web.service.InformationTraService; +import com.example.web.service.CustomerService; +import com.example.web.service.SupplyCustomerService; +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.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * 客户数据变更追踪拦截器 + */ +@Aspect +@Component +public class CustomerDataChangeAspect { + + @Autowired + private InformationTraService informationTraService; + + @Autowired + private CustomerService customerService; + + @Autowired + private SupplyCustomerService supplyCustomerService; + + // 存储原始数据的临时缓存 + private ThreadLocal> originalDataCache = new ThreadLocal<>(); + + // 定义切入点:客户修改相关的方法 + @Pointcut("execution(* com.example.web.service.CustomerService.update*(..)) || execution(* com.example.web.service.SupplyCustomerService.update*(..))") + public void customerUpdatePointcut() {} + + + + // 修改前获取原始数据 + @Before("customerUpdatePointcut()") + public void beforeUpdate(JoinPoint joinPoint) { + // 获取方法参数 + Object[] args = joinPoint.getArgs(); + String customerId = null; + UnifiedCustomerDTO dto = null; + ManagerAuthInfo authInfo = null; + + // 遍历参数,查找UnifiedCustomerDTO和ManagerAuthInfo + for (Object arg : args) { + if (arg instanceof UnifiedCustomerDTO) { + dto = (UnifiedCustomerDTO) arg; + customerId = dto.getId(); + } else if (arg instanceof ManagerAuthInfo) { + authInfo = (ManagerAuthInfo) arg; + } + } + + if (customerId != null && dto != null) { + try { + System.out.println("📝 前置通知 - 获取原始数据,客户ID: " + customerId); + + // 如果没有找到ManagerAuthInfo,创建一个系统级的 + if (authInfo == null) { + authInfo = new ManagerAuthInfo("system", "system", "system", "system", "system", "system", "system"); + } + + // 获取原始数据 - 先尝试CustomerService,再尝试SupplyCustomerService + UnifiedCustomerDTO originalDto = customerService.getCustomerById(customerId, authInfo); + + // 如果默认数据源没有,尝试wechat数据源 + if (originalDto == null) { + originalDto = customerService.getWechatCustomerById(customerId, authInfo); + } + + // 如果还是没有,尝试SupplyCustomerService + if (originalDto == null) { + originalDto = supplyCustomerService.getCustomerById(customerId, authInfo); + if (originalDto == null) { + originalDto = supplyCustomerService.getWechatCustomerById(customerId, authInfo); + } + } + + if (originalDto != null) { + // 将原始数据转换为Map存储 + Map originalData = convertUnifiedCustomerDTOToMap(originalDto); + originalDataCache.set(originalData); + System.out.println("✅ 原始数据缓存成功 - 客户ID: " + customerId); + } + } catch (Exception e) { + System.err.println("❌ 前置通知执行失败: " + e.getMessage()); + e.printStackTrace(); + } + } + } + + // 修改后记录变更 + @AfterReturning("customerUpdatePointcut()") + public void afterUpdate(JoinPoint joinPoint) { + // 获取方法参数 + Object[] args = joinPoint.getArgs(); + UnifiedCustomerDTO dto = null; + ManagerAuthInfo authInfo = null; + + // 遍历参数,查找UnifiedCustomerDTO和ManagerAuthInfo + for (Object arg : args) { + if (arg instanceof UnifiedCustomerDTO) { + dto = (UnifiedCustomerDTO) arg; + } else if (arg instanceof ManagerAuthInfo) { + authInfo = (ManagerAuthInfo) arg; + } + } + + if (dto != null && dto.getId() != null) { + try { + // 获取原始数据 + Map originalData = originalDataCache.get(); + if (originalData != null) { + // 如果没有找到ManagerAuthInfo,创建一个系统级的 + if (authInfo == null) { + authInfo = new ManagerAuthInfo("system", "system", "system", "system", "system", "system", "system"); + } + + // 从数据库中重新查询最新的数据 - 先尝试CustomerService,再尝试SupplyCustomerService + UnifiedCustomerDTO modifiedDto = customerService.getCustomerById(dto.getId(), authInfo); + + // 如果默认数据源没有,尝试wechat数据源 + if (modifiedDto == null) { + modifiedDto = customerService.getWechatCustomerById(dto.getId(), authInfo); + } + + // 如果还是没有,尝试SupplyCustomerService + if (modifiedDto == null) { + modifiedDto = supplyCustomerService.getCustomerById(dto.getId(), authInfo); + if (modifiedDto == null) { + modifiedDto = supplyCustomerService.getWechatCustomerById(dto.getId(), authInfo); + } + } + + if (modifiedDto != null) { + // 转换修改后的数据为Map + Map modifiedData = convertUnifiedCustomerDTOToMap(modifiedDto); + // 比较并记录变更 + Map changedFields = compareData(originalData, modifiedData); + if (!changedFields.isEmpty()) { + System.out.println("📊 检测到数据变更: " + changedFields.keySet()); + + // 获取操作人信息 + String operatorId = authInfo.getManagerId() != null ? authInfo.getManagerId() : "system"; + String operatorName = authInfo.getUserName() != null ? authInfo.getUserName() : "系统"; + String company = authInfo.getManagercompany() != null ? authInfo.getManagercompany() : "系统"; + String department = authInfo.getManagerdepartment() != null ? authInfo.getManagerdepartment() : "系统"; + String organization = authInfo != null ? authInfo.getOrganization() : "系统"; + String role = authInfo != null ? authInfo.getRole() : "admin"; + String user = authInfo != null ? authInfo.getUserName() : "系统"; + String assistant = authInfo != null ? authInfo.getAssistant() : ""; + + // 记录变更到数据库 - 添加详细调试日志 + System.out.println("🔄 开始调用recordDetailedChange方法"); + System.out.println("📋 原始数据类型: " + (originalData != null ? originalData.getClass().getName() : "null")); + System.out.println("📋 修改后数据类型: " + (modifiedData != null ? modifiedData.getClass().getName() : "null")); + + try { + informationTraService.recordDetailedChange( + dto.getPhoneNumber(), + dto.getNickName(), + generateOperationEvent(changedFields), + company, + department, + organization, + role, + user, + assistant, + originalData, + modifiedData, + new ArrayList<>(changedFields.keySet()) + ); + System.out.println("✅ 数据变更记录成功 - 客户ID: " + dto.getId()); + } catch (Exception e) { + System.err.println("❌ 调用recordDetailedChange方法失败: " + e.getMessage()); + e.printStackTrace(); + } + } else { + System.out.println("ℹ️ 未检测到数据变更 - 客户ID: " + dto.getId()); + // 即使没有变更,也记录原始数据和修改后的数据,以便调试 + String operatorId = authInfo.getManagerId() != null ? authInfo.getManagerId() : "system"; + String operatorName = authInfo.getUserName() != null ? authInfo.getUserName() : "系统"; + String company = authInfo.getManagercompany() != null ? authInfo.getManagercompany() : "系统"; + String department = authInfo.getManagerdepartment() != null ? authInfo.getManagerdepartment() : "系统"; + String organization = authInfo != null ? authInfo.getOrganization() : "系统"; + String role = authInfo != null ? authInfo.getRole() : "admin"; + String user = authInfo != null ? authInfo.getUserName() : "系统"; + String assistant = authInfo != null ? authInfo.getAssistant() : ""; + + // 记录无变更数据 - 添加详细调试日志 + System.out.println("🔄 开始调用recordDetailedChange方法(无变更)"); + Map convertedModifiedDto = convertUnifiedCustomerDTOToMap(modifiedDto); + System.out.println("📋 原始数据类型: " + (originalData != null ? originalData.getClass().getName() : "null")); + System.out.println("📋 修改后数据类型: " + (convertedModifiedDto != null ? convertedModifiedDto.getClass().getName() : "null")); + + try { + informationTraService.recordDetailedChange( + dto.getPhoneNumber(), + dto.getNickName(), + "未检测到数据变更", + company, + department, + organization, + role, + user, + assistant, + originalData, + convertedModifiedDto, + new ArrayList<>() + ); + System.out.println("✅ 无变更数据记录成功 - 客户ID: " + dto.getId()); + } catch (Exception e) { + System.err.println("❌ 调用recordDetailedChange方法(无变更)失败: " + e.getMessage()); + e.printStackTrace(); + } + } + } else { + System.out.println("❌ 无法获取修改后的数据 - 客户ID: " + dto.getId()); + } + } + } catch (Exception e) { + System.err.println("❌ 数据变更记录失败: " + e.getMessage()); + e.printStackTrace(); + } finally { + // 清除缓存 + originalDataCache.remove(); + } + } + } + + + + // 将UnifiedCustomerDTO对象转换为Map + private Map convertUnifiedCustomerDTOToMap(UnifiedCustomerDTO dto) { + Map map = new HashMap<>(); + map.put("id", dto.getId()); + map.put("company", dto.getCompany()); + map.put("region", dto.getRegion()); + map.put("level", dto.getLevel()); + map.put("type", dto.getType()); + map.put("demand", dto.getDemand()); + map.put("spec", dto.getSpec()); + map.put("nickName", dto.getNickName()); + map.put("phoneNumber", dto.getPhoneNumber()); + map.put("wechat", dto.getWechat()); + map.put("account", dto.getAccount()); + map.put("accountNumber", dto.getAccountNumber()); + map.put("bank", dto.getBank()); + map.put("address", dto.getAddress()); + map.put("managerId", dto.getManagerId()); + map.put("managercompany", dto.getManagercompany()); + map.put("managerdepartment", dto.getManagerdepartment()); + map.put("organization", dto.getOrganization()); + map.put("role", dto.getRole()); + map.put("userName", dto.getUserName()); + map.put("assistant", dto.getAssistant()); + map.put("created_at", dto.getCreated_at()); + map.put("updated_at", dto.getUpdated_at()); + map.put("productName", dto.getProductName()); + map.put("variety", dto.getVariety()); + map.put("specification", dto.getSpecification()); + map.put("quantity", dto.getQuantity()); + map.put("grossWeight", dto.getGrossWeight()); + map.put("yolk", dto.getYolk()); + map.put("dataSource", dto.getDataSource()); + return map; + } + + // 将Users对象转换为Map(备用方法) + private Map convertUserToMap(Users user) { + Map 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.getFollowup_at()); + map.put("updated_at", user.getUpdated_at()); + return map; + } + + // 比较原始数据和修改后的数据,返回变更的字段 + private Map compareData(Map original, Map modified) { + Map changes = new HashMap<>(); + + // 使用修改后的数据的key集合,确保覆盖所有可能变化的字段 + Set allKeys = new HashSet<>(); + allKeys.addAll(original.keySet()); + allKeys.addAll(modified.keySet()); + + for (String key : allKeys) { + Object originalValue = original.get(key); + Object modifiedValue = modified.get(key); + + // 跳过不需要比较的字段 + if ("created_at".equals(key) || "updated_at".equals(key) || "id".equals(key) || "userId".equals(key)) { + continue; + } + + if (originalValue == null && modifiedValue != null) { + // 从null变为有值 + Map fieldChange = new HashMap<>(); + fieldChange.put("original", null); + fieldChange.put("modified", modifiedValue); + changes.put(key, fieldChange); + } else if (originalValue != null && modifiedValue == null) { + // 从有值变为null + Map fieldChange = new HashMap<>(); + fieldChange.put("original", originalValue); + fieldChange.put("modified", null); + changes.put(key, fieldChange); + } else if (originalValue != null && !originalValue.equals(modifiedValue)) { + // 值发生了变化 + Map fieldChange = new HashMap<>(); + fieldChange.put("original", originalValue); + fieldChange.put("modified", modifiedValue); + changes.put(key, fieldChange); + } + } + return changes; + } + + // 生成操作事件描述 + private String generateOperationEvent(Map 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 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); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/web/config/MyBatisConfig.java b/src/main/java/com/example/web/config/MyBatisConfig.java index 150b89f..0dd5172 100644 --- a/src/main/java/com/example/web/config/MyBatisConfig.java +++ b/src/main/java/com/example/web/config/MyBatisConfig.java @@ -29,6 +29,12 @@ public class MyBatisConfig { // 设置Mapper XML文件路径 factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources("classpath:mapper/*.xml")); + + // 添加配置:开启下划线到驼峰命名的自动转换 + org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(); + configuration.setMapUnderscoreToCamelCase(true); + factoryBean.setConfiguration(configuration); + return factoryBean.getObject(); } diff --git a/src/main/java/com/example/web/controller/CustomerController.java b/src/main/java/com/example/web/controller/CustomerController.java index 3e6ab30..fb311f3 100644 --- a/src/main/java/com/example/web/controller/CustomerController.java +++ b/src/main/java/com/example/web/controller/CustomerController.java @@ -120,18 +120,7 @@ public class CustomerController { response.put("success", true); response.put("data", customer); - // 记录查看客户详情操作 - informationTraService.recordOperationEvent( - customer.getPhoneNumber(), - customer.getNickName() != null ? customer.getNickName() : "", - customer.getPhoneNumber() + "-查看客户", - authInfo.getManagercompany(), - authInfo.getManagerdepartment(), - authInfo.getOrganization(), - authInfo.getRole(), - authInfo.getUserName(), - authInfo.getAssistant() - ); + return ResponseEntity.ok(response); } @@ -171,28 +160,17 @@ public class CustomerController { } // 2. 如果wechat数据源没找到或无权限,再尝试查询默认数据源 - System.out.println("📊 查询 DEFAULT 数据源..."); - UnifiedCustomerDTO defaultCustomer = customerService.getCustomerById(id, authInfo); + System.out.println("📊 查询 DEFAULT 数据源..."); + UnifiedCustomerDTO defaultCustomer = customerService.getCustomerById(id, authInfo); - if (defaultCustomer != null) { - System.out.println("✅ 在默认数据源中找到客户"); - response.put("success", true); - response.put("data", defaultCustomer); - - // 记录查看客户详情操作 - informationTraService.recordOperationEvent( - defaultCustomer.getPhoneNumber(), - defaultCustomer.getNickName() != null ? defaultCustomer.getNickName() : "", - defaultCustomer.getPhoneNumber() + "-查看客户", - authInfo.getManagercompany(), - authInfo.getManagerdepartment(), - authInfo.getOrganization(), - authInfo.getRole(), - authInfo.getUserName(), - authInfo.getAssistant() - ); - - return ResponseEntity.ok(response); + if (defaultCustomer != null) { + System.out.println("✅ 在默认数据源中找到客户"); + response.put("success", true); + response.put("data", defaultCustomer); + + + + return ResponseEntity.ok(response); } // 3. 如果两个数据源都没找到 @@ -728,18 +706,7 @@ public class CustomerController { response.put("success", true); response.put("message", "客户信息更新成功"); - // 记录修改客户信息操作 - informationTraService.recordOperationEvent( - updatedDTO.getPhoneNumber(), - updatedDTO.getUserName() != null ? updatedDTO.getUserName() : "", - updatedDTO.getPhoneNumber() + "-更新客户", - authInfo.getManagercompany(), - authInfo.getManagerdepartment(), - authInfo.getOrganization(), - authInfo.getRole(), - authInfo.getUserName(), - authInfo.getAssistant() - ); + // 📝 注意:不再需要手动记录操作事件,因为CustomerDataChangeAspect切面会自动记录详细的变更信息 return ResponseEntity.ok(response); } else { @@ -936,18 +903,7 @@ public class CustomerController { return ResponseEntity.badRequest().body(response); } - // 记录更新客户信息操作 - informationTraService.recordOperationEvent( - updatedDTO.getPhoneNumber(), - updatedDTO.getNickName() != null ? updatedDTO.getNickName() : "", - updatedDTO.getPhoneNumber() + "-更新客户", - updatedDTO.getManagercompany() != null ? updatedDTO.getManagercompany() : "", - updatedDTO.getManagerdepartment() != null ? updatedDTO.getManagerdepartment() : "", - updatedDTO.getOrganization() != null ? updatedDTO.getOrganization() : "", - updatedDTO.getRole() != null ? updatedDTO.getRole() : "", - updatedDTO.getUserName() != null ? updatedDTO.getUserName() : "", - updatedDTO.getAssistant() != null ? updatedDTO.getAssistant() : "" - ); + // 📝 注意:不再需要手动记录操作事件,因为CustomerDataChangeAspect切面会自动记录详细的变更信息 response.put("success", true); response.put("message", "客户信息更新成功"); diff --git a/src/main/java/com/example/web/controller/SupplyCustomerController.java b/src/main/java/com/example/web/controller/SupplyCustomerController.java index f8e8501..13ce5b7 100644 --- a/src/main/java/com/example/web/controller/SupplyCustomerController.java +++ b/src/main/java/com/example/web/controller/SupplyCustomerController.java @@ -116,18 +116,7 @@ public class SupplyCustomerController { response.put("success", true); response.put("data", customer); - // 记录查看客户详情操作 - informationTraService.recordOperationEvent( - customer.getPhoneNumber(), - customer.getUserName() != null ? customer.getUserName() : "", - customer.getPhoneNumber() + "-查看客户", - authInfo.getManagercompany(), - authInfo.getManagerdepartment(), - authInfo.getOrganization(), - authInfo.getRole(), - authInfo.getUserName(), - authInfo.getAssistant() - ); + return ResponseEntity.ok(response); } @@ -171,24 +160,13 @@ public class SupplyCustomerController { UnifiedCustomerDTO defaultCustomer = supplyCustomerService.getCustomerById(id, authInfo); if (defaultCustomer != null) { - System.out.println("✅ 在默认数据源中找到客户"); - response.put("success", true); - response.put("data", defaultCustomer); - - // 记录查看客户详情操作 - informationTraService.recordOperationEvent( - defaultCustomer.getPhoneNumber(), - defaultCustomer.getUserName() != null ? defaultCustomer.getUserName() : "", - defaultCustomer.getPhoneNumber() + "-查看客户", - authInfo.getManagercompany(), - authInfo.getManagerdepartment(), - authInfo.getOrganization(), - authInfo.getRole(), - authInfo.getUserName(), - authInfo.getAssistant() - ); - - return ResponseEntity.ok(response); + System.out.println("✅ 在默认数据源中找到客户"); + response.put("success", true); + response.put("data", defaultCustomer); + + + + return ResponseEntity.ok(response); } // 3. 如果两个数据源都没找到 @@ -691,18 +669,7 @@ public class SupplyCustomerController { response.put("success", true); response.put("message", "客户信息更新成功"); - // 记录修改客户信息操作 - informationTraService.recordOperationEvent( - updatedDTO.getPhoneNumber(), - updatedDTO.getUserName() != null ? updatedDTO.getUserName() : "", - updatedDTO.getPhoneNumber() + "-更新客户", - authInfo.getManagercompany(), - authInfo.getManagerdepartment(), - authInfo.getOrganization(), - authInfo.getRole(), - authInfo.getUserName(), - authInfo.getAssistant() - ); + // 📝 注意:不再需要手动记录操作事件,因为CustomerDataChangeAspect切面会自动记录详细的变更信息 return ResponseEntity.ok(response); } else { @@ -908,18 +875,7 @@ public class SupplyCustomerController { return ResponseEntity.badRequest().body(response); } - // 记录更新客户信息操作 - informationTraService.recordOperationEvent( - updatedDTO.getPhoneNumber(), - updatedDTO.getNickName() != null ? updatedDTO.getNickName() : "", - updatedDTO.getPhoneNumber() + "-更新客户", - updatedDTO.getManagercompany() != null ? updatedDTO.getManagercompany() : "", - updatedDTO.getManagerdepartment() != null ? updatedDTO.getManagerdepartment() : "", - updatedDTO.getOrganization() != null ? updatedDTO.getOrganization() : "", - updatedDTO.getRole() != null ? updatedDTO.getRole() : "", - updatedDTO.getUserName() != null ? updatedDTO.getUserName() : "", - updatedDTO.getAssistant() != null ? updatedDTO.getAssistant() : "" - ); + // 📝 注意:不再需要手动记录操作事件,因为CustomerDataChangeAspect切面会自动记录详细的变更信息 // 🎯 关键修复:返回修正后的数据源信息给前端 Map result = new HashMap<>(); diff --git a/src/main/java/com/example/web/controller/SupplyCustomerRecycleController.java b/src/main/java/com/example/web/controller/SupplyCustomerRecycleController.java index f96eaf5..f55e152 100644 --- a/src/main/java/com/example/web/controller/SupplyCustomerRecycleController.java +++ b/src/main/java/com/example/web/controller/SupplyCustomerRecycleController.java @@ -16,7 +16,7 @@ import java.util.Map; public class SupplyCustomerRecycleController { @Autowired - private SupplyCustomerRecycleService supplycustomerRecycleService; + private SupplyCustomerRecycleService supplyCustomerRecycleService; /** * 手动触发客户回流 @@ -27,11 +27,11 @@ public class SupplyCustomerRecycleController { Map response = new HashMap<>(); try { - supplycustomerRecycleService.manualRecycle(); + supplyCustomerRecycleService.manualRecycle(); response.put("success", true); response.put("message", "客户回流任务执行成功"); - response.put("config", supplycustomerRecycleService.getRecycleConfigInfo()); + response.put("config", supplyCustomerRecycleService.getRecycleConfigInfo()); return ResponseEntity.ok(response); } catch (Exception e) { @@ -51,7 +51,7 @@ public class SupplyCustomerRecycleController { try { response.put("success", true); - response.put("config", supplycustomerRecycleService.getRecycleConfigInfo()); + response.put("config", supplyCustomerRecycleService.getRecycleConfigInfo()); return ResponseEntity.ok(response); } catch (Exception e) { diff --git a/src/main/java/com/example/web/entity/InformationTra.java b/src/main/java/com/example/web/entity/InformationTra.java index 39c6248..f095809 100644 --- a/src/main/java/com/example/web/entity/InformationTra.java +++ b/src/main/java/com/example/web/entity/InformationTra.java @@ -15,6 +15,9 @@ public class InformationTra { private LocalDateTime operationTime; private LocalDateTime createdAt; private LocalDateTime updatedAt; + private String originalData; // 原始数据JSON + private String modifiedData; // 修改后数据JSON + private String changedFields; // 变更字段列表JSON // Getters and Setters public Integer getId() { @@ -112,4 +115,28 @@ public class InformationTra { public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; } + + 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; + } } \ No newline at end of file diff --git a/src/main/java/com/example/web/entity/Users.java b/src/main/java/com/example/web/entity/Users.java index bfe9b9a..e14826c 100644 --- a/src/main/java/com/example/web/entity/Users.java +++ b/src/main/java/com/example/web/entity/Users.java @@ -34,8 +34,9 @@ public class Users { private String spec;//规格 private String followup;//跟进信息 private String notice;//通知状态 + private LocalDateTime followup_at;//最后跟进时间 - public Users(Integer id, String openid, String userId, String nickName, String avatarUrl, String phoneNumber, String type, Integer gender, String country, String province, String city, String language, String session_key, LocalDateTime created_at, LocalDateTime updated_at, String company, String region, String level, String demand, String spec, String followup, String notice) { + public Users(Integer id, String openid, String userId, String nickName, String avatarUrl, String phoneNumber, String type, Integer gender, String country, String province, String city, String language, String session_key, LocalDateTime created_at, LocalDateTime updated_at, String company, String region, String level, String demand, String spec, String followup, String notice, LocalDateTime followup_at) { this.id = id; this.openid = openid; this.userId = userId; @@ -58,6 +59,7 @@ public class Users { this.spec = spec; this.followup = followup; this.notice = notice; + this.followup_at = followup_at; } public Integer getId() { @@ -235,4 +237,12 @@ public class Users { public void setNotice(String notice) { this.notice = notice; } + + public LocalDateTime getFollowup_at() { + return followup_at; + } + + public void setFollowup_at(LocalDateTime followup_at) { + this.followup_at = followup_at; + } } diff --git a/src/main/java/com/example/web/mapper/ContactsMapper.java b/src/main/java/com/example/web/mapper/ContactsMapper.java index e560bc6..550dd6d 100644 --- a/src/main/java/com/example/web/mapper/ContactsMapper.java +++ b/src/main/java/com/example/web/mapper/ContactsMapper.java @@ -6,6 +6,7 @@ import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update; +import java.time.LocalDateTime; import java.util.List; @Mapper @@ -30,6 +31,6 @@ public interface ContactsMapper { String getFollowUpByPhone(@Param("phoneNumber") String phoneNumber); // 更新跟进信息 - @Update("UPDATE contacts SET followup = #{followup} WHERE phoneNumber = #{phoneNumber}") - int updateFollowUpByPhone(@Param("phoneNumber") String phoneNumber, @Param("followup") String followup); + @Update("UPDATE contacts SET followup = #{followup}, updated_at = #{updatedAt} WHERE phoneNumber = #{phoneNumber}") + int updateFollowUpByPhone(@Param("phoneNumber") String phoneNumber, @Param("followup") String followup, @Param("updatedAt") LocalDateTime updatedAt); } diff --git a/src/main/java/com/example/web/mapper/SupplyUsersMapper.java b/src/main/java/com/example/web/mapper/SupplyUsersMapper.java index c39ce85..70b36d3 100644 --- a/src/main/java/com/example/web/mapper/SupplyUsersMapper.java +++ b/src/main/java/com/example/web/mapper/SupplyUsersMapper.java @@ -223,7 +223,7 @@ public interface SupplyUsersMapper { /** * 更新客户等级 */ - @Update("UPDATE users SET level = #{level}, updated_at = #{updateTime} WHERE user_id = #{userId}") + @Update("UPDATE wechat_app.users SET level = #{level}, updated_at = #{updateTime} WHERE userId = #{userId}") boolean updateCustomerLevel(@Param("userId") String userId, @Param("level") String level, @Param("updateTime") LocalDateTime updateTime); @@ -261,8 +261,8 @@ public interface SupplyUsersMapper { String getFollowUpByPhone(@Param("phoneNumber") String phoneNumber); // 更新跟进信息 - @Update("UPDATE users SET followup = #{followup} WHERE phoneNumber = #{phoneNumber}") - int updateFollowUpByPhone(@Param("phoneNumber") String phoneNumber, @Param("followup") String followup); + @Update("UPDATE users SET followup = #{followup}, updated_at = #{updatedAt}, followup_at = COALESCE(followup_at, #{updatedAt}) WHERE phoneNumber = #{phoneNumber}") + int updateFollowUpByPhone(@Param("phoneNumber") String phoneNumber, @Param("followup") String followup, @Param("updatedAt") LocalDateTime updatedAt); // 🔥 新增:更新用户通知状态 @Update("UPDATE users SET notice = #{notice} WHERE userId = #{userId}") diff --git a/src/main/java/com/example/web/mapper/UsersMapper.java b/src/main/java/com/example/web/mapper/UsersMapper.java index d22c6a2..fc87803 100644 --- a/src/main/java/com/example/web/mapper/UsersMapper.java +++ b/src/main/java/com/example/web/mapper/UsersMapper.java @@ -3,6 +3,7 @@ package com.example.web.mapper; import com.example.web.annotation.DataSource; import com.example.web.dto.ManagerAuthInfo; import com.example.web.dto.UserProductCartDTO; +import com.example.web.entity.Users; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Update; @@ -273,4 +274,14 @@ public interface UsersMapper { // 🔥 新增:更新用户通知状态 @Update("UPDATE users SET notice = #{notice} WHERE userId = #{userId}") int updateNotice(@Param("userId") String userId, @Param("notice") String notice); + + /** + * 查询需要检查回流的客户列表 + */ + List selectCustomersForReturnFlowCheck(); + + /** + * 根据用户ID更新用户信息 + */ + int updateByUserId(Users user); } \ No newline at end of file diff --git a/src/main/java/com/example/web/service/CustomerReturnTaskService.java b/src/main/java/com/example/web/service/CustomerReturnTaskService.java new file mode 100644 index 0000000..c0d408f --- /dev/null +++ b/src/main/java/com/example/web/service/CustomerReturnTaskService.java @@ -0,0 +1,36 @@ +package com.example.web.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 客户回流定时任务服务 + * 定时检查客户回流情况 + */ +@Service +@RequiredArgsConstructor +public class CustomerReturnTaskService { + + private static final Logger log = LoggerFactory.getLogger(CustomerReturnTaskService.class); + + @Autowired + private CustomerService customerService; + + /** + * 客户回流检查定时任务 - 每天凌晨1点执行 + */ + @Scheduled(cron = "0 0 1 * * ?") + public void checkCustomerReturnFlowDaily() { + log.info("🎯 开始执行客户回流检查任务..."); + try { + customerService.checkCustomerReturnFlow(); + log.info("✅ 客户回流检查任务执行完成"); + } catch (Exception e) { + log.error("❌ 客户回流检查任务执行失败", e); + } + } +} diff --git a/src/main/java/com/example/web/service/CustomerService.java b/src/main/java/com/example/web/service/CustomerService.java index d550ed9..a834d5f 100644 --- a/src/main/java/com/example/web/service/CustomerService.java +++ b/src/main/java/com/example/web/service/CustomerService.java @@ -5,8 +5,10 @@ import com.example.web.dto.ManagerAuthInfo; import com.example.web.dto.UnifiedCustomerDTO; import com.example.web.dto.UserProductCartDTO; import com.example.web.entity.Managers; +import com.example.web.entity.Users; import com.example.web.entity.UsersManagements; import com.example.web.mapper.*; +import com.example.web.config.DynamicDataSource; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -41,6 +43,9 @@ public class CustomerService { @Autowired private PoolCustomerService poolCustomerService; + + @Autowired + private InformationTraService informationTraService; // ==================== 精确更新方法 ==================== @@ -1325,4 +1330,104 @@ public class CustomerService { return false; } } + + /** + * 检查客户回流 - 定时任务调用 + */ + public void checkCustomerReturnFlow() { + // 保存原始数据源,以便在finally块中恢复 + String originalDataSource = DynamicDataSource.getCurrentDataSourceKey(); + try { + // 设置为wechat数据源 + DynamicDataSource.setDataSourceKey("wechat"); + + System.out.println("🔄 开始执行客户回流检查任务"); + + // 获取需要检查回流的客户列表 + List customers = getCustomersForReturnFlowCheck(); + System.out.println("📊 找到需要检查回流的客户数量: " + (customers != null ? customers.size() : 0)); + + if (customers != null && !customers.isEmpty()) { + for (Users customer : customers) { + try { + // 执行回流操作 + updateCustomerToSeaPool(customer); + } catch (Exception e) { + System.err.println("❌ 处理客户回流失败,客户ID: " + customer.getUserId() + ", 错误: " + e.getMessage()); + e.printStackTrace(); + } + } + } + + System.out.println("✅ 客户回流检查任务执行完成"); + } catch (Exception e) { + System.err.println("❌ 执行客户回流检查任务失败: " + e.getMessage()); + e.printStackTrace(); + } finally { + // 恢复原始数据源,避免线程池复用问题 + if (originalDataSource != null) { + DynamicDataSource.setDataSourceKey(originalDataSource); + } else { + DynamicDataSource.clearDataSourceKey(); + } + System.out.println("🔄 恢复数据源为原始值: " + (originalDataSource != null ? originalDataSource : "默认数据源")); + } + } + + /** + * 获取需要检查回流的客户列表 + */ + private List getCustomersForReturnFlowCheck() { + try { + // 查询符合条件的客户: + // 1. updated_at在过去3天内 + // 2. followup_at为空或超过3天未更新 + // 3. 等级不是organization-sea-pools + List customers = usersMapper.selectCustomersForReturnFlowCheck(); + return customers; + } catch (Exception e) { + System.err.println("❌ 获取客户列表失败: " + e.getMessage()); + e.printStackTrace(); + return null; + } + } + + /** + * 将客户更新为公海池等级并记录回流信息 + */ + private void updateCustomerToSeaPool(Users customer) { + try { + System.out.println("🔄 开始处理客户回流,客户ID: " + customer.getUserId() + ", 手机号: " + customer.getPhoneNumber()); + + // 1. 记录原始等级 + String originalLevel = customer.getLevel(); + + // 2. 更新客户等级为organization-sea-pools + Users updateUser = new Users(); + updateUser.setUserId(customer.getUserId()); + updateUser.setLevel("organization-sea-pools"); + updateUser.setUpdated_at(LocalDateTime.now()); + + int rows = usersMapper.updateByUserId(updateUser); + if (rows > 0) { + System.out.println("✅ 客户等级更新成功,原始等级: " + originalLevel + ", 新等级: organization-sea-pools"); + + // 3. 记录客户回流信息 + informationTraService.recordCustomerReturn( + customer.getUserId(), + "系统自动操作", + "系统自动操作", + "系统自动操作", + "系统自动操作", + "系统自动操作", + "系统自动操作" + ); + } else { + System.err.println("❌ 客户等级更新失败,客户ID: " + customer.getUserId()); + } + } catch (Exception e) { + System.err.println("❌ 处理客户回流失败,客户ID: " + customer.getUserId() + ", 错误: " + e.getMessage()); + e.printStackTrace(); + } + } } \ No newline at end of file diff --git a/src/main/java/com/example/web/service/FollowUpService.java b/src/main/java/com/example/web/service/FollowUpService.java index 826b83f..504e669 100644 --- a/src/main/java/com/example/web/service/FollowUpService.java +++ b/src/main/java/com/example/web/service/FollowUpService.java @@ -9,6 +9,7 @@ import com.example.web.mapper.UsersMapper; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; @Service public class FollowUpService { @@ -52,12 +53,15 @@ public class FollowUpService { */ public boolean saveFollowUp(String phoneNumber, String followup) { try { + // 获取当前时间作为updated_at + LocalDateTime now = LocalDateTime.now(); + // 先尝试在contacts表中保存 - if (contactsMapper.updateFollowUpByPhone(phoneNumber, followup) > 0) { + if (contactsMapper.updateFollowUpByPhone(phoneNumber, followup, now) > 0) { return true; } // 如果contacts表中没有,尝试在users表中保存 - return supplyUsersMapper.updateFollowUpByPhone(phoneNumber, followup) > 0; + return supplyUsersMapper.updateFollowUpByPhone(phoneNumber, followup, now) > 0; } catch (Exception e) { throw e; } diff --git a/src/main/java/com/example/web/service/InformationTraService.java b/src/main/java/com/example/web/service/InformationTraService.java index 4c6f57d..b8c9cd4 100644 --- a/src/main/java/com/example/web/service/InformationTraService.java +++ b/src/main/java/com/example/web/service/InformationTraService.java @@ -3,12 +3,18 @@ package com.example.web.service; import com.example.web.dto.UserProductCartDTO; import com.example.web.entity.Contacts; import com.example.web.entity.InformationTra; +import com.example.web.entity.Users; import com.example.web.mapper.*; import com.example.web.config.DynamicDataSource; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.time.LocalDateTime; +import java.util.*; @Service public class InformationTraService { @@ -102,4 +108,236 @@ public class InformationTraService { System.out.println("🔄 恢复数据源为原始值: " + (originalDataSource != null ? originalDataSource : "默认数据源")); } } + + /** + * 记录详细变更信息 + */ + public boolean recordDetailedChange(String phoneNumber, String customerName, String operationEvent, + String managerCompany, String managerDepartment, String organization, + String role, String userName, String assistant, + Object originalData, Object modifiedData, List changedFields) { + // 保存原始数据源,以便在finally块中恢复 + String originalDataSource = DynamicDataSource.getCurrentDataSourceKey(); + try { + System.out.println("\n==========================================="); + System.out.println("🚀 开始记录详细变更信息"); + System.out.println("📱 手机号: " + phoneNumber); + System.out.println("👤 客户名: " + customerName); + System.out.println("📝 操作事件: " + operationEvent); + + // 记录参数类型信息 + System.out.println("📊 参数类型信息:"); + System.out.println(" - originalData类型: " + (originalData != null ? originalData.getClass().getName() : "null")); + System.out.println(" - modifiedData类型: " + (modifiedData != null ? modifiedData.getClass().getName() : "null")); + System.out.println(" - changedFields类型: " + (changedFields != null ? changedFields.getClass().getName() : "null")); + + // 1. 从两个数据源查询客户信息 + String userId = null; + + // 查询wechat数据源的users表 + DynamicDataSource.setDataSourceKey("wechat"); + System.out.println("🔄 查询wechat数据源..."); + UserProductCartDTO wechatUser = usersMapper.selectByPhone(phoneNumber); + + if (wechatUser != null) { + userId = wechatUser.getUserId(); + System.out.println("✅ 从wechat数据源获取到userId: " + userId); + } else { + System.out.println("⚠️ 在wechat数据源中未找到客户"); + // 查询primary数据源的contacts表 + DynamicDataSource.setDataSourceKey("primary"); + System.out.println("🔄 查询primary数据源..."); + Contacts contact = contactsMapper.selectByPhoneNumber(phoneNumber); + + if (contact != null) { + System.out.println("✅ 在primary数据源中找到客户: " + contact.getNickName()); + userId = contact.getId(); + System.out.println("ℹ️ 使用primary数据源的contact.id作为userId: " + userId); + } + } + + // 如果都没找到,返回失败 + if (userId == null) { + System.err.println("❌ 无法获取客户ID,操作记录失败"); + return false; + } + + // 2. 获取当前时间 + LocalDateTime now = LocalDateTime.now(); + + // 3. 构造操作记录 + InformationTra informationTra = new InformationTra(); + informationTra.setTracompany(managerCompany); + informationTra.setTradepartment(managerDepartment); + informationTra.setTraorganization(organization); + informationTra.setTrarole(role); + informationTra.setTrauserName(userName); + informationTra.setTraassistant(assistant); + informationTra.setUserId(userId); + informationTra.setOperationEvent(operationEvent); + informationTra.setOperationTime(now); + informationTra.setCreatedAt(now); + informationTra.setUpdatedAt(now); + + // 4. 处理变更数据 + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + objectMapper.registerModule(new JavaTimeModule()); + objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + + // 详细记录原始数据 + try { + if (originalData != null) { + System.out.println("\n📥 原始数据详细信息:"); + System.out.println(" - 类型: " + originalData.getClass().getName()); + System.out.println(" - 内容: " + originalData.toString()); + + // 尝试序列化 + String originalJson = objectMapper.writeValueAsString(originalData); + informationTra.setOriginalData(originalJson); + System.out.println("✅ originalData序列化成功,长度: " + originalJson.length() + " 字符"); + // 打印前100个字符预览 + System.out.println(" - 预览: " + (originalJson.length() > 100 ? originalJson.substring(0, 100) + "..." : originalJson)); + } else { + System.out.println("⚠️ originalData为null"); + } + } catch (Exception e) { + System.err.println("❌ originalData序列化失败: " + e.getMessage()); + e.printStackTrace(); + // 尝试使用toString方法作为备选 + if (originalData != null) { + System.out.println("🔄 尝试使用toString()作为备选: " + originalData.toString()); + informationTra.setOriginalData(originalData.toString()); + } + } + + // 详细记录修改后数据 + try { + if (modifiedData != null) { + System.out.println("\n📤 修改后数据详细信息:"); + System.out.println(" - 类型: " + modifiedData.getClass().getName()); + System.out.println(" - 内容: " + modifiedData.toString()); + + // 尝试序列化 + String modifiedJson = objectMapper.writeValueAsString(modifiedData); + informationTra.setModifiedData(modifiedJson); + System.out.println("✅ modifiedData序列化成功,长度: " + modifiedJson.length() + " 字符"); + // 打印前100个字符预览 + System.out.println(" - 预览: " + (modifiedJson.length() > 100 ? modifiedJson.substring(0, 100) + "..." : modifiedJson)); + } else { + System.out.println("⚠️ modifiedData为null"); + } + } catch (Exception e) { + System.err.println("❌ modifiedData序列化失败: " + e.getMessage()); + e.printStackTrace(); + // 尝试使用toString方法作为备选 + if (modifiedData != null) { + System.out.println("🔄 尝试使用toString()作为备选: " + modifiedData.toString()); + informationTra.setModifiedData(modifiedData.toString()); + } + } + + // 详细记录变更字段 + try { + if (changedFields != null && !changedFields.isEmpty()) { + System.out.println("\n🔄 变更字段详细信息:"); + System.out.println(" - 数量: " + changedFields.size()); + System.out.println(" - 内容: " + changedFields); + + // 尝试序列化 + String changedFieldsJson = objectMapper.writeValueAsString(changedFields); + informationTra.setChangedFields(changedFieldsJson); + System.out.println("✅ changedFields序列化成功"); + } else { + System.out.println("⚠️ changedFields为null或空"); + } + } catch (Exception e) { + System.err.println("❌ changedFields序列化失败: " + e.getMessage()); + e.printStackTrace(); + // 尝试使用toString方法作为备选 + if (changedFields != null) { + System.out.println("🔄 尝试使用toString()作为备选: " + changedFields.toString()); + informationTra.setChangedFields(changedFields.toString()); + } + } + + // 5. 始终写入wechat数据源的informationtra表 + DynamicDataSource.setDataSourceKey("wechat"); + System.out.println("\n💾 准备插入详细变更记录到wechat数据源"); + System.out.println(" - userId: " + userId); + + int result = informationTraMapper.insertInformationTra(informationTra); + if (result > 0) { + System.out.println("✅ 插入详细变更记录成功,影响行数: " + result); + System.out.println("==========================================="); + return true; + } else { + System.err.println("❌ 插入详细变更记录失败,影响行数: " + result); + return false; + } + } catch (Exception e) { + System.err.println("❌ 详细变更记录异常: " + e.getMessage()); + e.printStackTrace(); + return false; + } finally { + // 恢复原始数据源,避免线程池复用问题 + if (originalDataSource != null) { + DynamicDataSource.setDataSourceKey(originalDataSource); + } else { + DynamicDataSource.clearDataSourceKey(); + } + System.out.println("🔄 恢复数据源为原始值: " + (originalDataSource != null ? originalDataSource : "默认数据源")); + } + } + + /** + * 记录客户回流信息 + */ + public boolean recordCustomerReturn(String userId, String managerCompany, String managerDepartment, + String organization, String role, String userName, String assistant) { + // 保存原始数据源,以便在finally块中恢复 + String originalDataSource = DynamicDataSource.getCurrentDataSourceKey(); + try { + // 1. 获取当前时间 + LocalDateTime now = LocalDateTime.now(); + + // 2. 构造操作记录 + InformationTra informationTra = new InformationTra(); + informationTra.setTracompany(managerCompany); + informationTra.setTradepartment(managerDepartment); + informationTra.setTraorganization(organization); + informationTra.setTrarole(role); + informationTra.setTrauserName(userName); + informationTra.setTraassistant(assistant); + informationTra.setUserId(userId); + informationTra.setOperationEvent("客户回流:系统自动降级至organization-sea-pools等级"); + informationTra.setOperationTime(now); + informationTra.setCreatedAt(now); + informationTra.setUpdatedAt(now); + + // 3. 始终写入wechat数据源的informationtra表 + DynamicDataSource.setDataSourceKey("wechat"); + System.out.println("🔄 设置数据源为wechat,准备插入客户回流记录"); + int result = informationTraMapper.insertInformationTra(informationTra); + if (result > 0) { + System.out.println("✅ 插入客户回流记录成功,影响行数: " + result); + return true; + } else { + System.err.println("❌ 插入客户回流记录失败,影响行数: " + result); + return false; + } + } catch (Exception e) { + e.printStackTrace(); + System.err.println("❌ 客户回流记录异常: " + e.getMessage()); + return false; + } finally { + // 恢复原始数据源,避免线程池复用问题 + if (originalDataSource != null) { + DynamicDataSource.setDataSourceKey(originalDataSource); + } else { + DynamicDataSource.clearDataSourceKey(); + } + System.out.println("🔄 恢复数据源为原始值: " + (originalDataSource != null ? originalDataSource : "默认数据源")); + } + } } \ No newline at end of file diff --git a/src/main/java/com/example/web/service/SupplyCustomerRecycleService.java b/src/main/java/com/example/web/service/SupplyCustomerRecycleService.java index abc93cb..44815f3 100644 --- a/src/main/java/com/example/web/service/SupplyCustomerRecycleService.java +++ b/src/main/java/com/example/web/service/SupplyCustomerRecycleService.java @@ -34,9 +34,9 @@ public class SupplyCustomerRecycleService { private int organizationToDepartmentDays; /** - * 客户回流定时任务 - 每天凌晨2点执行 + * 客户回流定时任务 - 每15分钟执行一次 */ - @Scheduled(cron = "0 0 2 * * ?") + @Scheduled(cron = "0 */15 * * * ?") @Transactional(rollbackFor = Exception.class) public void autoRecycleCustomers() { log.info("🎯 开始执行客户回流任务..."); @@ -197,7 +197,6 @@ public class SupplyCustomerRecycleService { log.info("✅ 客户 {} 回流后负责人信息: {}", customer.getUserId(), manager.getUserName()); } } - log.info("📊 获取到 {} 个回流客户的完整信息", recycledCustomers.size()); return recycledCustomers; } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 7e4eaa7..018f027 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -17,12 +17,12 @@ spring: app: recycle: # 未分级客户回流到组织公海池的天数阈值 - unclassified-to-organization-days: 30 + unclassified-to-organization-days: 3 # 组织公海池客户回流到部门公海池的天数阈值 - organization-to-department-days: 30 + organization-to-department-days: 3 server: - port: 8081 + port: 8080 servlet: context-path: /DL # 在Tomcat中部署时,端口由Tomcat配置决定,这里不需要指定 @@ -39,7 +39,9 @@ mybatis: logging: level: - com.example.web.mapper: DEBUG - com.example.web.config: DEBUG - com.example.web.aspect: DEBUG + com.example.web.mapper: TRACE + com.example.web.config: TRACE + com.example.web.aspect: TRACE + org.apache.ibatis: TRACE + org.mybatis.spring: TRACE diff --git a/src/main/resources/mapper/InformationTraMapper.xml b/src/main/resources/mapper/InformationTraMapper.xml index adf8e24..11eb556 100644 --- a/src/main/resources/mapper/InformationTraMapper.xml +++ b/src/main/resources/mapper/InformationTraMapper.xml @@ -15,17 +15,20 @@ + + + INSERT INTO informationtra ( tracompany, tradepartment, traorganization, trarole, trauserName, traassistant, userId, operationEvent, - operationTime, created_at, updated_at + operationTime, created_at, updated_at, originalData, modifiedData, changedFields ) VALUES ( #{tracompany}, #{tradepartment}, #{traorganization}, #{trarole}, #{trauserName}, #{traassistant}, #{userId}, #{operationEvent}, - #{operationTime}, #{createdAt}, #{updatedAt} + #{operationTime}, #{createdAt}, #{updatedAt}, #{originalData}, #{modifiedData}, #{changedFields} ) diff --git a/src/main/resources/mapper/SupplyUsersManagementsMapper.xml b/src/main/resources/mapper/SupplyUsersManagementsMapper.xml index 5ee40b1..2c071bc 100644 --- a/src/main/resources/mapper/SupplyUsersManagementsMapper.xml +++ b/src/main/resources/mapper/SupplyUsersManagementsMapper.xml @@ -22,7 +22,7 @@ @@ -30,7 +30,7 @@ @@ -38,7 +38,7 @@ - INSERT INTO usermanagements ( + INSERT INTO wechat_app.usermanagements ( userId, managerId, managercompany, managerdepartment, organization, role, root, created_at, updated_at, userName, assistant @@ -69,7 +69,7 @@ - UPDATE usermanagements + UPDATE wechat_app.usermanagements SET managerId = #{managerId}, managercompany = #{managercompany}, managerdepartment = #{managerdepartment}, @@ -84,28 +84,28 @@ - UPDATE users_managements - SET updated_at = #{updatedAt} - WHERE user_id = #{userId} + UPDATE wechat_app.usermanagements + SET updated_at = #{updatedAt} + WHERE userId = #{userId} SELECT id, userId, managerId, managercompany, managerdepartment, organization, role, root, created_at, updated_at, userName, assistant - FROM usermanagements + FROM wechat_app.usermanagements WHERE userId IN #{userId} diff --git a/src/main/resources/mapper/SupplyUsersMapper.xml b/src/main/resources/mapper/SupplyUsersMapper.xml index bee7220..229b8aa 100644 --- a/src/main/resources/mapper/SupplyUsersMapper.xml +++ b/src/main/resources/mapper/SupplyUsersMapper.xml @@ -86,7 +86,7 @@ u.created_at, u.updated_at, u.notice - FROM users u + FROM wechat_app.users u LEFT JOIN usermanagements um ON u.userId = um.userId WHERE u.phoneNumber IS NOT NULL AND u.phoneNumber != '' @@ -122,7 +122,7 @@ @@ -275,7 +275,7 @@ notice, created_at, updated_at - FROM users + FROM wechat_app.users WHERE phoneNumber IS NOT NULL AND phoneNumber != '' AND (type = 'seller' OR type = 'both') @@ -297,7 +297,7 @@ created_at, updated_at, notice - FROM users + FROM wechat_app.users WHERE phoneNumber = #{phoneNumber} AND (type = 'seller' OR type = 'both') @@ -317,7 +317,7 @@ created_at, updated_at, notice - FROM users + FROM wechat_app.users WHERE userId = #{userId} AND (type = 'seller' OR type = 'both') @@ -341,7 +341,7 @@ - UPDATE users + UPDATE wechat_app.users SET nickName = #{nickName}, @@ -622,7 +622,7 @@ created_at, updated_at, notice - FROM users + FROM wechat_app.users WHERE userId IN #{userId} @@ -650,10 +650,10 @@ + + + + UPDATE users + + level = #{level}, + updated_at = #{updatedAt}, + followup_at = #{followupAt}, + + WHERE user_id = #{userId} + + \ No newline at end of file diff --git a/src/main/resources/static/mainapp-sells.html b/src/main/resources/static/mainapp-sells.html index 913271e..a6c6408 100644 --- a/src/main/resources/static/mainapp-sells.html +++ b/src/main/resources/static/mainapp-sells.html @@ -592,71 +592,406 @@ /* 通知铃铛按钮样式 */ .notification-btn { - background-color: #f8f9fa; - border: 1px solid #e0e0e0; + background-color: #ffffff; + border: 2px solid #e0e7ff; border-radius: 50%; - width: 40px; - height: 40px; + width: 48px; + height: 48px; display: flex; justify-content: center; align-items: center; cursor: pointer; transition: all 0.3s ease; margin-right: 10px; - color: #666; + color: #6366f1; position: relative; + box-shadow: 0 2px 8px rgba(99, 102, 241, 0.15); } .notification-btn:hover { - background-color: #e9ecef; - border-color: #adb5bd; - color: #495057; - transform: scale(1.05); + background-color: #f8fafc; + border-color: #818cf8; + color: #4f46e5; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(99, 102, 241, 0.2); } .notification-btn i { - font-size: 18px; + font-size: 20px; transition: all 0.3s ease; } /* 通知铃铛激活状态样式 */ .notification-btn.notification-active { - background-color: #fee2e2; - border-color: #fecaca; - color: #ef4444; + background-color: #eff6ff; + border-color: #6366f1; + color: #6366f1; + animation: ring 1s ease-in-out; + } + + /* 通知数量徽章样式 */ + .notification-count { + position: absolute; + top: 2px; + right: 2px; + background: linear-gradient(135deg, #6366f1, #8b5cf6); + color: white; + font-size: 11px; + font-weight: 700; + border-radius: 12px; + min-width: 22px; + height: 22px; + display: flex; + align-items: center; + justify-content: center; + padding: 0 7px; + box-shadow: 0 2px 8px rgba(99, 102, 241, 0.3); + animation: badge-pulse 2s infinite; + } + + /* 铃铛摇晃动画 */ + @keyframes ring { + 0% { transform: rotate(0deg); } + 10% { transform: rotate(15deg); } + 20% { transform: rotate(-15deg); } + 30% { transform: rotate(15deg); } + 40% { transform: rotate(-15deg); } + 50% { transform: rotate(0deg); } + 100% { transform: rotate(0deg); } + } + + /* 脉冲动画 */ + @keyframes pulse { + 0% { transform: scale(1); } + 50% { transform: scale(1.1); } + 100% { transform: scale(1); } + } + + /* 徽章脉冲动画 */ + @keyframes badge-pulse { + 0% { + transform: scale(1); + box-shadow: 0 2px 8px rgba(99, 102, 241, 0.3); + } + 50% { + transform: scale(1.05); + box-shadow: 0 4px 12px rgba(99, 102, 241, 0.4); + } + 100% { + transform: scale(1); + box-shadow: 0 2px 8px rgba(99, 102, 241, 0.3); + } + } + + /* 通知弹窗样式 */ + #notificationModal { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + z-index: 1000; + justify-content: flex-end; + align-items: flex-start; + padding: 20px; + overflow: auto; + } + + .modal-content { + background: white; + border-radius: 20px; + width: 90%; + max-width: 900px; + max-height: 90vh; + overflow-y: auto; + padding: 0; + position: relative; + animation: fadeIn 0.3s ease; + } + + .modal-header { + position: sticky; + top: 0; + z-index: 1001; + padding: 20px 30px; + display: flex; + justify-content: space-between; + align-items: center; + background: rgba(255, 255, 255, 0.7); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + border-bottom: 1px solid rgba(255, 255, 255, 0.5); + border-radius: 20px 20px 0 0; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05); + } + + .modal-header-left { + display: flex; + align-items: center; + gap: 10px; + } + + .modal-header-left i { + font-size: 20px; + } + + .modal-header-right { + display: flex; + align-items: center; + gap: 10px; + } + + .modal-title { + font-size: 24px; + margin: 0; + color: #333; + font-weight: 600; + } + + .mark-all-read-btn { + background: rgba(255, 255, 255, 0.2); + border: 1px solid rgba(255, 255, 255, 0.3); + color: white; + padding: 8px 16px; + border-radius: 6px; + cursor: pointer; + font-size: 14px; + transition: all 0.3s ease; + display: flex; + align-items: center; + gap: 6px; + } + + .mark-all-read-btn:hover { + background: rgba(255, 255, 255, 0.3); + border-color: rgba(255, 255, 255, 0.4); + } + + .close-modal { + background: rgba(255, 255, 255, 0.2); + border: 1px solid rgba(255, 255, 255, 0.3); + color: white; + width: 32px; + height: 32px; + border-radius: 50%; + cursor: pointer; + font-size: 18px; + display: flex; + justify-content: center; + align-items: center; + transition: all 0.3s ease; + } + + .close-modal:hover { + background: rgba(255, 255, 255, 0.3); + } + + .modal-body { + padding: 0; + max-height: 60vh; + overflow-y: auto; + } + + /* 通知列表样式 */ + .notification-list { + display: flex; + flex-direction: column; + gap: 8px; + padding: 12px; + } + + /* 通知项样式 */ + .notification-item { + background-color: #fff; + border: 1px solid #e0e0e0; + border-radius: 10px; + padding: 16px; + cursor: pointer; + transition: all 0.3s ease; + display: flex; + gap: 12px; + } + + .notification-item:hover { + border-color: #d32f2f; + box-shadow: 0 4px 12px rgba(211, 47, 47, 0.1); + transform: translateY(-2px); + } + + .notification-item.new { + border-left: 4px solid #d32f2f; + background-color: #fff8f8; + } + + /* 通知头像 */ + .notification-avatar { + width: 48px; + height: 48px; + border-radius: 50%; + background: linear-gradient(135deg, #f5f7fa, #c3cfe2); + display: flex; + justify-content: center; + align-items: center; + flex-shrink: 0; + } + + .notification-avatar i { + font-size: 24px; + color: #d32f2f; + } + + /* 通知主内容 */ + .notification-main { + flex: 1; + min-width: 0; + } + + .notification-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 6px; + } + + .notification-title { + margin: 0; + font-size: 16px; + font-weight: 600; + color: #333; + word-break: break-word; + } + + .notification-badge { + background-color: #ffe0b2; + color: #f57c00; + padding: 2px 8px; + border-radius: 10px; + font-size: 11px; + font-weight: 500; + text-transform: uppercase; + } + + .notification-description { + margin: 0 0 10px 0; + font-size: 14px; + color: #666; + line-height: 1.4; + } + + .notification-meta { + display: flex; + flex-wrap: wrap; + gap: 12px; + margin-bottom: 12px; + } + + .meta-item { + display: flex; + align-items: center; + gap: 4px; + font-size: 12px; + color: #999; + } + + .meta-item i { + font-size: 12px; + } + + /* 通知操作 */ + .notification-actions { + display: flex; + justify-content: flex-end; + gap: 8px; + padding-top: 12px; + border-top: 1px solid #f0f0f0; + } + + .action-btn { + padding: 6px 12px; + border: none; + border-radius: 6px; + font-size: 13px; + cursor: pointer; + transition: all 0.3s ease; + } + + .view-btn { + background-color: #d32f2f; + color: white; + } + + .view-btn:hover { + background-color: #b71c1c; + } + + .ignore-btn { + background-color: #f0f0f0; + color: #666; + } + + .ignore-btn:hover { + background-color: #e0e0e0; + } + + /* 空状态样式 */ + .notification-empty { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 60px 20px; + text-align: center; + } + + .empty-icon { + width: 80px; + height: 80px; + border-radius: 50%; + background-color: #f5f5f5; + display: flex; + justify-content: center; + align-items: center; + margin-bottom: 16px; + } + + .empty-icon i { + font-size: 40px; + color: #ccc; + } + + .empty-title { + margin: 0 0 8px 0; + font-size: 18px; + font-weight: 600; + color: #333; + } + + .empty-description { + margin: 0; + font-size: 14px; + color: #999; + max-width: 300px; + } + + /* 滚动条样式 */ + #notificationModal .modal-body::-webkit-scrollbar { + width: 6px; + } + + #notificationModal .modal-body::-webkit-scrollbar-track { + background-color: #f5f5f5; + border-radius: 3px; } - /* 通知数量徽章样式 */ - .notification-count { - position: absolute; - top: -5px; - right: -5px; - background: #ef4444; - color: white; - font-size: 12px; - font-weight: bold; - border-radius: 50%; - min-width: 18px; - height: 18px; - display: flex; - align-items: center; - justify-content: center; - padding: 0 6px; + #notificationModal .modal-body::-webkit-scrollbar-thumb { + background-color: #ccc; + border-radius: 3px; } - /* 铃铛摇晃动画 */ - @keyframes ring { - 0% { transform: rotate(0deg); } - 10% { transform: rotate(15deg); } - 20% { transform: rotate(-15deg); } - 30% { transform: rotate(10deg); } - 40% { transform: rotate(-10deg); } - 50% { transform: rotate(5deg); } - 60% { transform: rotate(-5deg); } - 70% { transform: rotate(2deg); } - 80% { transform: rotate(-2deg); } - 90% { transform: rotate(1deg); } - 100% { transform: rotate(0deg); } + #notificationModal .modal-body::-webkit-scrollbar-thumb:hover { + background-color: #999; } /* 头部操作容器样式 */ @@ -1186,6 +1521,11 @@ .modal.active { display: flex; } + + /* 修复通知弹窗优先级问题 */ + #notificationModal.active { + display: flex; + } .modal-content { background: white; @@ -2803,8 +3143,16 @@