diff --git a/pom.xml b/pom.xml index a34edfd..5e75a17 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.cst - Langchain4j-rj + AIDriverEEBackend 1.251130.7-SNAPSHOT Langchain4j-rj Langchain4j-rj20250803 diff --git a/src/main/java/com/rj/controller/AudioFileController.java b/src/main/java/com/rj/controller/AudioFileController.java index b006c71..3a0951e 100644 --- a/src/main/java/com/rj/controller/AudioFileController.java +++ b/src/main/java/com/rj/controller/AudioFileController.java @@ -34,6 +34,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.LocalDateTime; +import java.math.BigDecimal; import java.util.*; /** @@ -80,6 +81,9 @@ public class AudioFileController { @Autowired private DeviceManagementMapper deviceManagementMapper; + + @Autowired + private com.rj.service.IAudioManagementSegmentsService audioManagementSegmentsService; public String deviceKey = "deviceManagementKey"; /** @@ -972,6 +976,14 @@ public class AudioFileController { audioUploadLog.getId(), audioUploadLog.getDeviceNo(), audioUploadLog.getFileName(), audioUploadLog.getAudioManagementId()); + // 保存到 audio_management_segments 表 + try { + saveAudioManagementSegment(requestBody, processedData, localFilePath, file, audioUploadLog); + } catch (Exception e) { + log.error("保存音频分段记录到audio_management_segments表失败", e); + // 即使保存失败,也不影响音频上传日志的保存,只记录日志 + } + } catch (Exception e) { log.error("保存音频上传日志到数据库失败", e); throw new RuntimeException("保存音频上传日志失败: " + e.getMessage(), e); @@ -1112,6 +1124,384 @@ public class AudioFileController { audioUploadLog.setCreateTime(now); audioUploadLog.setUpdateTime(now); } + + /** + * 保存音频分段记录到 audio_management_segments 表 + * 如果已存在相同的分段记录,则跳过保存(避免重复) + * + * @param requestBody 请求体数据 + * @param processedData 处理后的数据 + * @param localFilePath 本地文件路径 + * @param file 上传的文件(可能为null) + * @param audioUploadLog 音频上传日志对象(已保存) + */ + private void saveAudioManagementSegment(Map requestBody, + Map processedData, + String localFilePath, + MultipartFile file, + YhyAudioUploadLog audioUploadLog) { + try { + // 1. 检查是否已经保存过(使用requestBody中的标记) + String segmentSavedKey = "audioManagementSegmentSaved"; + if (requestBody.containsKey(segmentSavedKey) && Boolean.TRUE.equals(requestBody.get(segmentSavedKey))) { + log.info("音频分段记录已保存过,跳过重复保存"); + return; + } + + // 2. 创建音频分段对象 + AudioManagementSegments segment = new AudioManagementSegments(); + segment.setId(UUID.randomUUID().toString()); + + // 3. 设置父录音ID(从requestBody中获取audioManagementId) + Object audioManagementIdObj = requestBody.get("audioManagementId"); + if (audioManagementIdObj != null) { + segment.setParentId(audioManagementIdObj.toString()); + } + + // 4. 从DeviceManagement中获取设备信息 + DeviceManagement deviceManagement = (DeviceManagement) requestBody.get(deviceKey); + if (deviceManagement != null) { + segment.setTenantId(deviceManagement.getTenantId()); + segment.setSalesPhone(deviceManagement.getSalesPhone()); + segment.setSalesName(deviceManagement.getSalesName()); + segment.setDealershipId(deviceManagement.getDealershipId()); + segment.setDealershipName(deviceManagement.getDealershipName()); + } + + // 5. 从requestBody和processedData中提取字段 + populateSegmentFromRequestData(segment, requestBody, processedData); + + // 6. 设置文件相关字段 + setSegmentFileFields(segment, localFilePath, file, audioUploadLog); + + // 7. 解析时间字段并计算录音时长 + parseAndSetSegmentTimeFields(segment, processedData); + + // 8. 检查数据库中是否已存在相同的分段记录 + if (isSegmentExists(segment)) { + log.info("音频分段记录已存在,跳过保存。父录音ID: {}, chunkIndex: {}, deviceNo: {}, startTime: {}", + segment.getParentId(), segment.getChunkIndex(), segment.getDeviceNo(), segment.getStartTime()); + // 标记为已保存,避免后续重复检查 + requestBody.put(segmentSavedKey, true); + return; + } + + // 9. 从AudioManagement中获取录音名称(如果存在父录音) + if (segment.getParentId() != null) { + populateSegmentFromAudioManagement(segment, segment.getParentId()); + } + + // 10. 设置通用字段 + LocalDateTime now = TimeZoneUtils.now(); + segment.setCreateTime(now); + segment.setUploadTime(now); + segment.setRecordingTime(now); + + // 11. 设置默认状态 + if (segment.getUploadStatus() == null) { + segment.setUploadStatus("已上传"); + } + if (segment.getSyncStatus() == null) { + segment.setSyncStatus("未同步"); + } + if (segment.getIsMerged() == null) { + segment.setIsMerged(false); + } + + // 12. 保存到数据库 + audioManagementSegmentsService.save(segment); + log.info("音频分段记录保存成功,ID: {}, 父录音ID: {}, 设备号: {}, 文件名: {}", + segment.getId(), segment.getParentId(), segment.getDeviceNo(), segment.getAudioFileOriginalName()); + + // 13. 标记为已保存,避免重复保存 + requestBody.put(segmentSavedKey, true); + + } catch (Exception e) { + log.error("保存音频分段记录到audio_management_segments表失败", e); + throw new RuntimeException("保存音频分段记录失败: " + e.getMessage(), e); + } + } + + /** + * 检查音频分段记录是否已存在 + * 判断标准:parent_id + chunk_index + deviceNo + start_time(如果都存在) + * + * @param segment 音频分段对象 + * @return true表示已存在,false表示不存在 + */ + private boolean isSegmentExists(AudioManagementSegments segment) { + try { + // 如果缺少关键字段,无法判断,返回false(允许保存) + if (segment.getParentId() == null || segment.getParentId().isEmpty()) { + return false; + } + + // 构建查询条件 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(AudioManagementSegments::getParentId, segment.getParentId()); + + // chunkIndex 如果存在,则必须匹配 + if (segment.getChunkIndex() != null && !segment.getChunkIndex().isEmpty()) { + queryWrapper.eq(AudioManagementSegments::getChunkIndex, segment.getChunkIndex()); + } + + // deviceNo 如果存在,则必须匹配 + if (segment.getDeviceNo() != null && !segment.getDeviceNo().isEmpty()) { + queryWrapper.eq(AudioManagementSegments::getDeviceNo, segment.getDeviceNo()); + } + + // startTime 如果存在,则必须匹配(精确到秒) + if (segment.getStartTime() != null) { + // 使用时间范围查询(前后1秒内),避免时间精度问题 + LocalDateTime startTime = segment.getStartTime(); + queryWrapper.between(AudioManagementSegments::getStartTime, + startTime.minusSeconds(1), startTime.plusSeconds(1)); + } + + // 如果文件路径存在,也作为判断条件之一 + if (segment.getAudioFilePath() != null && !segment.getAudioFilePath().isEmpty()) { + queryWrapper.eq(AudioManagementSegments::getAudioFilePath, segment.getAudioFilePath()); + } + + // 查询是否存在 + long count = audioManagementSegmentsService.count(queryWrapper); + return count > 0; + + } catch (Exception e) { + log.warn("检查音频分段记录是否存在时发生异常,将允许保存: {}", e.getMessage()); + // 如果检查失败,允许保存(避免因为检查逻辑问题导致数据丢失) + return false; + } + } + + /** + * 从请求数据中填充音频分段字段 + * + * @param segment 音频分段对象 + * @param requestBody 请求体数据 + * @param processedData 处理后的数据 + */ + private void populateSegmentFromRequestData(AudioManagementSegments segment, + Map requestBody, + Map processedData) { + // 从requestBody中提取设备号 + String deviceNo = extractStringField(requestBody, "deviceNo"); + if (deviceNo != null) { + segment.setDeviceNo(deviceNo); + } + + // 从processedData中提取字段 + if (processedData != null) { + segment.setChunkIndex(extractStringField(processedData, "chunkIndex")); + } + } + + /** + * 设置音频分段的文件相关字段 + * + * @param segment 音频分段对象 + * @param localFilePath 本地文件路径 + * @param file 上传的文件(可能为null) + * @param audioUploadLog 音频上传日志对象 + */ + private void setSegmentFileFields(AudioManagementSegments segment, + String localFilePath, + MultipartFile file, + YhyAudioUploadLog audioUploadLog) { + // 设置文件路径 + if (localFilePath != null && !localFilePath.isEmpty()) { + segment.setAudioFilePath(localFilePath); + } else if (audioUploadLog.getSavePath() != null && audioUploadLog.getFileName() != null) { + segment.setAudioFilePath(audioUploadLog.getSavePath() + File.separator + audioUploadLog.getFileName()); + } + + // 设置文件原始名称 + if (file != null && file.getOriginalFilename() != null) { + segment.setAudioFileOriginalName(file.getOriginalFilename()); + } else if (audioUploadLog.getFileName() != null) { + segment.setAudioFileOriginalName(audioUploadLog.getFileName()); + } + + // 设置文件扩展名 + String fileName = segment.getAudioFileOriginalName(); + if (fileName != null) { + String extension = getFileExtension(fileName); + if (!extension.isEmpty()) { + segment.setAudioFileExtension(extension); + } + } + + // 设置文件大小 + if (file != null && file.getSize() > 0) { + segment.setAudioFileSize(file.getSize()); + } + } + + /** + * 解析时间字段并计算录音时长 + * + * @param segment 音频分段对象 + * @param processedData 处理后的数据 + */ + private void parseAndSetSegmentTimeFields(AudioManagementSegments segment, + Map processedData) { + if (processedData == null) { + return; + } + + // 解析开始时间 + LocalDateTime startTime = parseDateTimeField(processedData, "startTime"); + if (startTime != null) { + segment.setStartTime(startTime); + } + + // 解析结束时间 + LocalDateTime endTime = parseDateTimeField(processedData, "endTime"); + if (endTime != null) { + segment.setEndTime(endTime); + } + + // 计算录音时长(分钟) + if (startTime != null && endTime != null && endTime.isAfter(startTime)) { + long seconds = java.time.Duration.between(startTime, endTime).getSeconds(); + BigDecimal durationMinutes = BigDecimal.valueOf(seconds).divide(BigDecimal.valueOf(60), 2, + java.math.RoundingMode.HALF_UP); + segment.setDuration(durationMinutes); + } + } + + /** + * 解析时间字段 + * + * @param dataMap 数据Map + * @param fieldName 字段名 + * @return LocalDateTime对象,如果解析失败则返回null + */ + private LocalDateTime parseDateTimeField(Map dataMap, String fieldName) { + if (dataMap == null || fieldName == null) { + return null; + } + + Object timeObj = dataMap.get(fieldName); + if (timeObj == null) { + return null; + } + + // 如果已经是LocalDateTime类型,直接返回 + if (timeObj instanceof LocalDateTime) { + return (LocalDateTime) timeObj; + } + + // 如果是String类型,尝试解析 + if (timeObj instanceof String) { + String timeStr = (String) timeObj; + if (timeStr.trim().isEmpty()) { + return null; + } + + try { + // 使用TimeZoneUtils或类似的方法解析时间字符串 + // 这里简化处理,使用LocalDateTimeDeserializer的逻辑 + return parseTimeString(timeStr); + } catch (Exception e) { + log.warn("解析时间字段失败: {} = {}", fieldName, timeStr, e); + return null; + } + } + + return null; + } + + /** + * 解析时间字符串 + * 支持多种格式:ISO格式、标准格式等 + * + * @param timeStr 时间字符串 + * @return LocalDateTime对象 + */ + private LocalDateTime parseTimeString(String timeStr) { + if (timeStr == null || timeStr.trim().isEmpty()) { + return null; + } + + try { + // 检测是否包含时区信息 + boolean hasTimezone = timeStr.endsWith("Z") || + timeStr.matches(".*[+-]\\d{2}:\\d{2}(?:\\d{2})?$"); + + if (hasTimezone) { + // 处理带时区的时间 + java.time.ZonedDateTime zonedDateTime; + if (timeStr.endsWith("Z")) { + zonedDateTime = java.time.ZonedDateTime.parse(timeStr.replace("Z", "+00:00")) + .withZoneSameInstant(TimeZoneUtils.getZoneId()); + } else { + zonedDateTime = java.time.ZonedDateTime.parse(timeStr) + .withZoneSameInstant(TimeZoneUtils.getZoneId()); + } + return zonedDateTime.toLocalDateTime(); + } else { + // 处理不带时区的时间 + String cleanTimeStr = timeStr; + if (cleanTimeStr.contains("T")) { + cleanTimeStr = cleanTimeStr.replace("T", " "); + } + if (cleanTimeStr.contains(".")) { + cleanTimeStr = cleanTimeStr.substring(0, cleanTimeStr.indexOf(".")); + } + if (cleanTimeStr.length() > 19) { + cleanTimeStr = cleanTimeStr.substring(0, 19); + } + return LocalDateTime.parse(cleanTimeStr, + java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } + } catch (Exception e) { + log.warn("解析时间字符串失败: {}", timeStr, e); + return null; + } + } + + /** + * 从AudioManagement中获取录音名称等信息 + * + * @param segment 音频分段对象 + * @param parentId 父录音ID + */ + private void populateSegmentFromAudioManagement(AudioManagementSegments segment, String parentId) { + try { + AudioManagement audioManagement = serviceManager.getAudioManagementService().getById(parentId); + if (audioManagement != null) { + // 如果录音名称为空,使用父录音的名称 + if (segment.getRecordingName() == null || segment.getRecordingName().isEmpty()) { + segment.setRecordingName(audioManagement.getRecordingName()); + } + + // 如果销售信息为空,使用父录音的销售信息 + if (segment.getSalesPhone() == null || segment.getSalesPhone().isEmpty()) { + segment.setSalesPhone(audioManagement.getSalesPhone()); + } + if (segment.getSalesName() == null || segment.getSalesName().isEmpty()) { + segment.setSalesName(audioManagement.getSalesName()); + } + + // 如果经销商信息为空,使用父录音的经销商信息 + if (segment.getDealershipId() == null || segment.getDealershipId().isEmpty()) { + segment.setDealershipId(audioManagement.getDealershipId()); + } + if (segment.getDealershipName() == null || segment.getDealershipName().isEmpty()) { + segment.setDealershipName(audioManagement.getDealershipName()); + } + + // 如果租户ID为空,使用父录音的租户ID + if (segment.getTenantId() == null || segment.getTenantId().isEmpty()) { + segment.setTenantId(audioManagement.getTenantId()); + } + } + } catch (Exception e) { + log.warn("从AudioManagement获取信息失败,parentId: {}", parentId, e); + // 不影响主流程,只记录日志 + } + } /** * 提取请求头信息 @@ -1467,48 +1857,153 @@ public class AudioFileController { /** * 处理Audio数据类型(录音推送) * 适配新的数据结构:dataType="Audio",数据在data字段中 + * 同时兼容旧的数据结构(数据直接在requestBody中) + * + * @param requestBody 请求体数据 + * @param file 上传的音频文件 + * @return 处理结果,包含success、message、localFilePath等 */ private Map handleAudioDataType(Map requestBody, MultipartFile file) { - Map result = new HashMap<>(); + log.info("开始处理Audio数据类型(录音推送)"); try { - log.info("处理Audio数据类型(录音推送)"); - // 提取data字段 - Object dataObj = requestBody.get("data"); - Map dataMap = null; - if (dataObj != null) { - dataMap = objectMapper.convertValue(dataObj, new TypeReference>() {}); + // 1. 提取并解析data字段(新数据结构) + Map dataFromDataField = extractDataField(requestBody); + + // 2. 判断使用新数据结构还是旧数据结构 + if (isLegacyDataStructure(dataFromDataField)) { + log.info("检测到旧数据结构,使用requestBody作为processedData"); + return processLegacyAudioData(requestBody, file); } - // 如果没有data字段,尝试使用旧的数据结构(兼容处理) - if (dataMap == null || dataMap.isEmpty()) { - // 使用旧的数据结构,直接使用requestBody作为processedData - return handleAudioUpload(requestBody, requestBody, file, - (String) requestBody.get("deviceNo"), - (String) requestBody.get("filePath")); - } - - // 构建兼容旧接口的数据结构 - Map processedData = new HashMap<>(dataMap); - - // 从requestBody中提取其他字段 - String deviceNo = (String) requestBody.get("deviceNo"); - String filePath = (String) requestBody.get("filePath"); - if (filePath == null && dataMap.get("filePath") != null) { - filePath = (String) dataMap.get("filePath"); - } - - // 调用原有的音频上传处理方法 - return handleAudioUpload(requestBody, processedData, file, deviceNo, filePath); + // 3. 使用新数据结构处理 + log.info("检测到新数据结构,从data字段提取数据"); + return processNewAudioData(requestBody, dataFromDataField, file); } catch (Exception e) { log.error("处理Audio数据类型异常", e); - result.put("success", false); - result.put("message", "处理Audio数据类型异常: " + e.getMessage()); - return result; + return createErrorResult("处理Audio数据类型异常: " + e.getMessage()); } } + /** + * 从requestBody中提取data字段并转换为Map + * + * @param requestBody 请求体数据 + * @return data字段转换后的Map,如果不存在或转换失败则返回null + */ + private Map extractDataField(Map requestBody) { + if (requestBody == null) { + return null; + } + + Object dataObj = requestBody.get("data"); + if (dataObj == null) { + return null; + } + + try { + Map dataMap = objectMapper.convertValue( + dataObj, + new TypeReference>() {} + ); + return dataMap; + } catch (Exception e) { + log.warn("解析data字段失败,可能不是Map类型: {}", e.getMessage()); + return null; + } + } + + /** + * 判断是否为旧的数据结构 + * 旧数据结构:data字段不存在或为空 + * 新数据结构:data字段存在且不为空 + * + * @param dataFromDataField 从data字段提取的数据 + * @return true表示旧数据结构,false表示新数据结构 + */ + private boolean isLegacyDataStructure(Map dataFromDataField) { + return dataFromDataField == null || dataFromDataField.isEmpty(); + } + + /** + * 处理旧数据结构的Audio数据 + * 旧数据结构:数据直接在requestBody中,没有data字段 + * + * @param requestBody 请求体数据(同时作为processedData) + * @param file 上传的音频文件 + * @return 处理结果 + */ + private Map processLegacyAudioData(Map requestBody, MultipartFile file) { + String deviceNo = extractStringField(requestBody, "deviceNo"); + String filePath = extractStringField(requestBody, "filePath"); + + return handleAudioUpload(requestBody, requestBody, file, deviceNo, filePath); + } + + /** + * 处理新数据结构的Audio数据 + * 新数据结构:数据在data字段中,requestBody中可能还有其他字段(如deviceNo、filePath等) + * + * @param requestBody 请求体数据 + * @param dataFromDataField 从data字段提取的数据 + * @param file 上传的音频文件 + * @return 处理结果 + */ + private Map processNewAudioData(Map requestBody, + Map dataFromDataField, + MultipartFile file) { + // 构建processedData:以data字段中的数据为主 + Map processedData = new HashMap<>(dataFromDataField); + + // 提取关键字段:优先使用requestBody中的字段,如果不存在则使用data字段中的 + String deviceNo = extractStringField(requestBody, "deviceNo"); + String filePath = extractStringFieldWithFallback(requestBody, dataFromDataField, "filePath"); + + return handleAudioUpload(requestBody, processedData, file, deviceNo, filePath); + } + + /** + * 从Map中提取String类型字段 + * + * @param map 数据Map + * @param fieldName 字段名 + * @return 字段值,如果不存在或类型不匹配则返回null + */ + private String extractStringField(Map map, String fieldName) { + if (map == null || fieldName == null) { + return null; + } + + Object value = map.get(fieldName); + if (value == null) { + return null; + } + + return value.toString(); + } + + /** + * 从Map中提取String类型字段,如果不存在则从fallbackMap中获取 + * + * @param primaryMap 优先使用的Map + * @param fallbackMap 备用Map + * @param fieldName 字段名 + * @return 字段值,如果都不存在则返回null + */ + private String extractStringFieldWithFallback(Map primaryMap, + Map fallbackMap, + String fieldName) { + // 优先从primaryMap中获取 + String value = extractStringField(primaryMap, fieldName); + if (value != null) { + return value; + } + + // 如果primaryMap中没有,从fallbackMap中获取 + return extractStringField(fallbackMap, fieldName); + } + /** * 处理Gps数据类型(GPS日志) */ diff --git a/src/main/java/com/rj/controller/SalesManagementController.java b/src/main/java/com/rj/controller/SalesManagementController.java index 943f82d..78ddef8 100644 --- a/src/main/java/com/rj/controller/SalesManagementController.java +++ b/src/main/java/com/rj/controller/SalesManagementController.java @@ -100,22 +100,32 @@ public class SalesManagementController { } String phone = salesManagement.getLoginAccount(); - - // 生成密码:当前日期 + 联系方式后4位 - String password = generatePasswordForSales(phone); - + + User oldUser = userService.getUserByUserName(phone); // 创建用户对象 - User user = new User(); + User newUser = new User(); + ResponseEntity> response = ResponseEntity.ok(new HashMap<>()); + if (oldUser == null){ + // user.setUserId(salesManagement.getId().hashCode()); - user.setUserName(phone); // 用户名 = 销售人员电话 - user.setPhone(phone); // 电话号码 = 销售人员电话 - user.setOriginalPassword(salesManagement.getPassword()); + newUser.setUserName(phone); // 用户名 = 销售人员电话 + newUser.setPhone(phone); // 电话号码 = 销售人员电话 + newUser.setOriginalPassword(salesManagement.getPassword()); + newUser.setPassword(salesManagement.getPassword()); // 设置密码(UserController会自动加密) + newUser.setEmail(phone+"@YiDuoWang.com"); + newUser.setTenantId(salesManagement.getTenantId()); + // 调用UserController的addUser方法保存用户 + response = userController.addUser(newUser); + }else { + oldUser.setPhone(phone); // 电话号码 = 销售人员电话 + oldUser.setOriginalPassword(salesManagement.getPassword()); // encryptPassword(inputPassword,DEFAULT_SALT); - user.setPassword(salesManagement.getPassword()); // 设置密码(UserController会自动加密) - user.setEmail(phone+"@YiDuoWang.com"); - user.setTenantId(salesManagement.getTenantId()); - // 调用UserController的addUser方法保存用户 - ResponseEntity> response = userController.addUser(user); + oldUser.setPassword(salesManagement.getPassword()); // 设置密码(UserController会自动加密) + oldUser.setTenantId(salesManagement.getTenantId()); + userController.updateUser(oldUser); + } + + // 检查响应结果 if (response.getBody() != null) { @@ -299,7 +309,7 @@ public class SalesManagementController { salesManagement.setUpdateTime(LocalDateTime.now()); boolean success = salesManagementService.updateById(salesManagement); - + createUserForSales(salesManagement); if (success) { result.put("success", true); result.put("message", "销售信息更新成功");