diff --git a/config/application-external.yml b/config/application-external.yml index 5de7d2e..525b054 100644 --- a/config/application-external.yml +++ b/config/application-external.yml @@ -16,33 +16,6 @@ spring: username: dcms_dev password: your_password_here -# ==================== FTP同步配置 ==================== -ftp-sync: - # 临时文件存储路径 - temp-path: ./vaa-ftp-temp - - # 临时文件保留天数 - retain-days: 10 - - # txt文件编码 - txt-encoding: GBK - - # 字段分隔符 - field-separator: "※" - - # 地市配置(单地市部署,只配当前地市) - cities: - - city-code: "340100" - city-name: "huainan" - ftp-host: 10.126.129.7 - ftp-port: 9979 - ftp-username: yyfile - ftp-password: yyfile - ftp-source-dir: /voice_record/ - ftp-record-dir: /rec/ - ftp-archive-dir: /processed/ - phone-area-code: "0000" - # ==================== 服务端口 ==================== server: port: 8088 @@ -52,10 +25,11 @@ oss: base-url: http://53.1.211.7/apiOss upload-path: voice/record/ -# ==================== VAA同步配置 ==================== +# ==================== VAA语音盒同步配置 ==================== vaa-sync: download-path: ./vaa-recordings retain-days: 10 + sync-interval-cron: "0 0 0/8 * * ?" # ==================== 日志配置 ==================== logging: diff --git a/src/main/java/com/threecloud/dataserviceyy/config/DataSourceConfig.java b/src/main/java/com/threecloud/dataserviceyy/config/DataSourceConfig.java deleted file mode 100644 index e7560f0..0000000 --- a/src/main/java/com/threecloud/dataserviceyy/config/DataSourceConfig.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.threecloud.dataserviceyy.config; - -import com.zaxxer.hikari.HikariDataSource; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.util.StringUtils; - -import javax.sql.DataSource; - -@Configuration -public class DataSourceConfig { - - @Value("${spring.datasource.url}") - private String url; - - @Value("${spring.datasource.username}") - private String username; - - @Value("${spring.datasource.password}") - private String password; - - @Value("${spring.datasource.driver-class-name}") - private String driverClassName; - - @Bean - public DataSource dataSource() { - System.out.println("========================================"); - System.out.println("【DataSourceConfig】创建数据源"); - System.out.println("Driver: " + driverClassName); - System.out.println("URL: " + url); - System.out.println("Username: " + username); - System.out.println("Password length: " + (password != null ? password.length() : 0)); - System.out.println("========================================"); - - HikariDataSource datasource = new HikariDataSource(); - datasource.setJdbcUrl(url); - datasource.setDriverClassName(driverClassName); - datasource.setUsername(username); - datasource.setPassword(password); - datasource.setMinimumIdle(0); - datasource.setMaximumPoolSize(10); - datasource.setConnectionTimeout(30000); - datasource.setValidationTimeout(5000); - - // 根据数据库类型设置不同的验证SQL - if (url.contains("kingbase")) { - datasource.setConnectionTestQuery("SELECT 1"); - } else { - datasource.setConnectionTestQuery("SELECT 1 FROM DUAL"); - datasource.addDataSourceProperty("oracle.jdbc.timezoneAsRegion", "false"); - datasource.addDataSourceProperty("oracle.net.CONNECT_TIMEOUT", "10000"); - datasource.addDataSourceProperty("oracle.net.READ_TIMEOUT", "60000"); - } - - // 关键:初始化时不测试连接,即使数据库暂时连不上也能启动 - datasource.setInitializationFailTimeout(-1); - - System.out.println("【DataSourceConfig】数据源创建完成(允许延迟连接)"); - - return datasource; - } -} diff --git a/src/main/java/com/threecloud/dataserviceyy/config/DynamicDataSourceConfig.java b/src/main/java/com/threecloud/dataserviceyy/config/DynamicDataSourceConfig.java deleted file mode 100644 index c3bf7f7..0000000 --- a/src/main/java/com/threecloud/dataserviceyy/config/DynamicDataSourceConfig.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.threecloud.dataserviceyy.config; - -/** - * 多数据源配置 - 【方案A阶段: 已完全禁用】 - * - * 此类在方案B(多数据源模式)时会重新启用。 - * 当前阶段使用Spring Boot默认的DataSource自动配置,仿照老系统 yydc-tb-server。 - */ -// @Configuration // 【方案A: 已禁用】 -public class DynamicDataSourceConfig { - // 所有方法已禁用,不再干扰Spring Boot的自动配置 -} diff --git a/src/main/java/com/threecloud/dataserviceyy/config/SyncTargetConfig.java b/src/main/java/com/threecloud/dataserviceyy/config/SyncTargetConfig.java deleted file mode 100644 index 4c70de8..0000000 --- a/src/main/java/com/threecloud/dataserviceyy/config/SyncTargetConfig.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.threecloud.dataserviceyy.config; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -@Data -@Component -@ConfigurationProperties(prefix = "server.target") -public class SyncTargetConfig { - private String syncLogCode; - private String syncLogTable; -} diff --git a/src/main/java/com/threecloud/dataserviceyy/controller/VoiceBoxController.java b/src/main/java/com/threecloud/dataserviceyy/controller/VoiceBoxController.java deleted file mode 100644 index f2a8d78..0000000 --- a/src/main/java/com/threecloud/dataserviceyy/controller/VoiceBoxController.java +++ /dev/null @@ -1,265 +0,0 @@ -package com.threecloud.dataserviceyy.controller; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; - -import java.io.File; -import java.io.IOException; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.HashMap; -import java.util.Map; - -@RestController -@RequestMapping("/api") -public class VoiceBoxController { - - private static final Logger logger = LoggerFactory.getLogger(VoiceBoxController.class); - private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyyMMdd"); - - /** - * 事件上传接口 - GET方式 - * 语音盒来电/去电时主动推送事件信息 - * - * 参数说明(来自VAA录音仪开发文档): - * - event_type: 事件类型 (3=状态提机, 4=提机, 6=开始去电录音, 7=来电接听开始录音, - * 8=去电完成挂机, 9=新的去电录音产生, 10=新的来电录音产生, 11=未接来电, 14=发送来电号码) - * - line: 设备端口 - * - device_id: 设备编号 - * - duration: 录音时长(秒) - * - TimeLong: 通话时长(秒) - * - date: 来电日期(格式: 2022-04-21 15:48:40) - * - caller: 来电号码(9,10,14事件中有值) - * - FilePath: 录音文件名称(9,10事件中有值) - * - voltage: 电压 - * - RingCnt: 振铃次数 - * - TotalStore: 设备存储容量 - * - TotalFreeStore: 设备剩余存储容量 - * - TotalMem: 内存使用率 - * - CPU: CPU使用率 - * - calloutId: websocket回传的calloutId - * - * 返回值:成功返回 "0000",否则录音仪客户端日志会显示失败 - */ - @GetMapping("/event") - public String handleEvent( - @RequestParam(required = false) String event_type, - @RequestParam(required = false) String line, - @RequestParam(required = false) String device_id, - @RequestParam(required = false) String duration, - @RequestParam(required = false) String TimeLong, - @RequestParam(required = false) String date, - @RequestParam(required = false) String caller, - @RequestParam(required = false) String FilePath, - @RequestParam(required = false) String voltage, - @RequestParam(required = false) String RingCnt, - @RequestParam(required = false) String TotalStore, - @RequestParam(required = false) String TotalFreeStore, - @RequestParam(required = false) String TotalMem, - @RequestParam(required = false) String CPU, - @RequestParam(required = false) String calloutId) { - - try { - logger.info("========================================"); - logger.info("[事件上传] 收到语音盒事件推送"); - logger.info("[事件类型] event_type={} ({})", event_type, getEventTypeDesc(event_type)); - logger.info("[设备信息] device_id={}, line={}", device_id, line); - - if (date != null) { - logger.info("[通话时间] date={}", date); - } - if (caller != null && !caller.isEmpty()) { - logger.info("[来电号码] caller={}", caller); - } - if (FilePath != null && !FilePath.isEmpty()) { - logger.info("[文件路径] FilePath={}", FilePath); - } - if (duration != null) { - logger.info("[录音时长] duration={}秒", duration); - } - if (TimeLong != null) { - logger.info("[通话时长] TimeLong={}秒", TimeLong); - } - - Map eventData = new HashMap<>(); - eventData.put("event_type", event_type); - eventData.put("line", line); - eventData.put("device_id", device_id); - eventData.put("date", date); - eventData.put("caller", caller); - eventData.put("FilePath", FilePath); - - saveEventToDatabase(eventData); - - logger.info("[事件上传] 处理完成"); - logger.info("========================================"); - - return "0000"; - - } catch (Exception e) { - logger.error("[事件上传] 处理异常: {}", e.getMessage(), e); - return "0000"; - } - } - - /** - * 文件上传接口 - POST方式 - * 语音盒主动上传录音文件,先保存到本地临时目录,再由定时任务上传到OSS - * - * 参数说明(来自VAA录音仪开发文档): - * - files: 文件上传信息(文件名称与事件中的FilePath字段对应) - * 文件名称格式: 设备名称-年月日时分秒-(O|I)-L(端口号)-EN-电话号码.wav - * O=去电, I=来电 - * - event_type: 事件类型(9=去电录音, 10=来电录音) - * - device_id: 设备编号 - * - line: 端口编号 - * - date: 来电日期 - * - caller: 来电号码 - * - FilePath: 录音文件名称 - * - duration: 录音时长(秒) - * - TimeLong: 通话时长(秒) - * - * 返回值:成功返回 "0000",否则录音仪客户端日志会显示失败 - */ - @PostMapping("/file") - public String handleFileUpload( - @RequestParam("files") MultipartFile file, - @RequestParam(required = false) String event_type, - @RequestParam(required = false) String device_id, - @RequestParam(required = false) String line, - @RequestParam(required = false) String date, - @RequestParam(required = false) String caller, - @RequestParam(required = false) String FilePath, - @RequestParam(required = false) String duration, - @RequestParam(required = false) String TimeLong) { - - try { - logger.info("========================================"); - logger.info("[文件上传] 收到语音盒文件上传请求"); - logger.info("[事件类型] event_type={} ({})", event_type, getEventTypeDesc(event_type)); - logger.info("[设备信息] device_id={}, line={}", device_id, line); - - if (file != null && !file.isEmpty()) { - String originalFilename = file.getOriginalFilename(); - long fileSize = file.getSize(); - logger.info("[文件信息] fileName={}, size={} bytes", originalFilename, fileSize); - } - - if (FilePath != null && !FilePath.isEmpty()) { - logger.info("[文件路径] FilePath={}", FilePath); - } - if (caller != null && !caller.isEmpty()) { - logger.info("[来电号码] caller={}", caller); - } - - if (file != null && !file.isEmpty()) { - String fileName = file.getOriginalFilename(); - - String tempDir = "/tmp/voice_upload/"; - File tempDirFile = new File(tempDir); - if (!tempDirFile.exists()) { - tempDirFile.mkdirs(); - } - - String tempFilePath = tempDir + fileName; - file.transferTo(new File(tempFilePath)); - - logger.info("[保存本地] 文件已保存到临时目录: {}", tempFilePath); - - saveRecordToDatabase(device_id, line, date, caller, fileName, duration, TimeLong, tempFilePath, "0"); - } - - logger.info("[文件上传] 处理完成"); - logger.info("========================================"); - - return "0000"; - - } catch (IOException e) { - logger.error("[文件上传] 文件保存异常: {}", e.getMessage(), e); - return "0000"; - } catch (Exception e) { - logger.error("[文件上传] 处理异常: {}", e.getMessage(), e); - return "0000"; - } - } - - /** - * 获取事件类型描述 - */ - private String getEventTypeDesc(String eventType) { - if (eventType == null) return "未知"; - switch (eventType) { - case "3": return "状态提机(ON HOOK)"; - case "4": return "提机(OFF HOOK)"; - case "6": return "开始去电录音"; - case "7": return "来电接听开始录音"; - case "8": return "去电完成挂机(ON HOOK)"; - case "9": return "新的去电录音产生"; - case "10": return "新的来电录音产生"; - case "11": return "未接来电"; - case "14": return "发送来电号码"; - default: return "其他(" + eventType + ")"; - } - } - - /** - * 从文件名提取设备名称 - * 文件格式: 设备名称-年月日时分秒-(O|I)-L(端口号)-EN-电话号码.wav - */ - private String extractDeviceName(String fileName) { - if (fileName == null || !fileName.contains("-")) { - return "unknown"; - } - return fileName.substring(0, fileName.indexOf("-")); - } - - /** - * 从文件名提取日期 - * 文件格式: 设备名称-年月日时分秒-(O|I)-L(端口号)-EN-电话号码.wav - */ - private String extractDateFromFileName(String fileName) { - if (fileName == null) { - return LocalDateTime.now().format(DATE_FORMAT); - } - - String datePattern = "\\d{8}"; - java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(datePattern); - java.util.regex.Matcher matcher = pattern.matcher(fileName); - - if (matcher.find()) { - return matcher.group(); - } - - return LocalDateTime.now().format(DATE_FORMAT); - } - - /** - * 从文件名提取通话类型 - * O=去电, I=来电 - */ - private String extractCallType(String fileName) { - if (fileName == null) return "0"; - if (fileName.contains("-O-")) return "1"; - if (fileName.contains("-I-")) return "0"; - return "0"; - } - - /** - * 保存事件到数据库(示例方法) - */ - private void saveEventToDatabase(Map eventData) { - logger.debug("[事件存储] device_id={}, event_type={}", - eventData.get("device_id"), eventData.get("event_type")); - } - - /** - * 保存通话记录到数据库(示例方法) - */ - private void saveRecordToDatabase(String deviceId, String line, String date, String caller, - String fileName, String duration, String timeLong, - String ossUrl, String callType) { - logger.debug("[记录存储] device_id={}, fileName={}, ossUrl={}", deviceId, fileName, ossUrl); - } -} diff --git a/src/main/java/com/threecloud/dataserviceyy/entity/CallRecord.java b/src/main/java/com/threecloud/dataserviceyy/entity/CallRecord.java deleted file mode 100644 index f8d9353..0000000 --- a/src/main/java/com/threecloud/dataserviceyy/entity/CallRecord.java +++ /dev/null @@ -1,157 +0,0 @@ -package com.threecloud.dataserviceyy.entity; - -/** - * 通话记录实体类 - * - * 封装从FTP数据文件中解析出的单条通话记录信息。 - * 数据来源:FTP服务器上的.txt文件,字段间用"※"分隔 - * - * 字段说明: - * - kssj: 开始时间 (格式: yyyy-MM-dd HH:mm:ss) - * - jssj: 结束时间 (格式: yyyy-MM-dd HH:mm:ss) - * - hjls: 呼机流水号 - * - hjzls: 呼机总流水号 - * - zjhm: 主叫号码 - * - bjhm: 被叫号码 - * - thsc: 通话时长(秒) - * - thfx: 通话方向(1=主叫方向, 0=被叫方向) - * - dateDir: 录音文件所在日期目录(yyyyMMdd) - * - wavFileName: 对应的WAV录音文件名(格式: 呼机流水号_呼机总流水号.wav) - */ -public class CallRecord { - - /** 开始时间 */ - private String kssj; - - /** 结束时间 */ - private String jssj; - - /** 呼机流水号 */ - private String hjls; - - /** 呼机总流水号 */ - private String hjzls; - - /** 主叫号码(可能包含区号前缀如0553) */ - private String zjhm; - - /** 被叫号码(可能包含区号前缀如0553) */ - private String bjhm; - - /** 通话时长(秒) */ - private Long thsc; - - /** 通话方向(1=主叫, 0=被叫) */ - private String thfx; - - /** 日期目录(yyyyMMdd格式,从kssj中提取) */ - private String dateDir; - - /** WAV录音文件名(格式: 呼机流水号_呼机总流水号.wav) */ - private String wavFileName; - - public CallRecord() { - } - - /** - * 获取通话记录摘要信息 - * 用于日志输出,显示主被叫号码 - * @return 格式化的摘要字符串,如:主叫=13800138000, 被叫=13900139000 - */ - public String getSummary() { - return "主叫=" + zjhm + ", 被叫=" + bjhm; - } - - public String getKssj() { - return kssj; - } - - public void setKssj(String kssj) { - this.kssj = kssj; - } - - public String getJssj() { - return jssj; - } - - public void setJssj(String jssj) { - this.jssj = jssj; - } - - public String getHjls() { - return hjls; - } - - public void setHjls(String hjls) { - this.hjls = hjls; - } - - public String getHjzls() { - return hjzls; - } - - public void setHjzls(String hjzls) { - this.hjzls = hjzls; - } - - public String getZjhm() { - return zjhm; - } - - public void setZjhm(String zjhm) { - this.zjhm = zjhm; - } - - public String getBjhm() { - return bjhm; - } - - public void setBjhm(String bjhm) { - this.bjhm = bjhm; - } - - public Long getThsc() { - return thsc; - } - - public void setThsc(Long thsc) { - this.thsc = thsc; - } - - public String getThfx() { - return thfx; - } - - public void setThfx(String thfx) { - this.thfx = thfx; - } - - public String getDateDir() { - return dateDir; - } - - public void setDateDir(String dateDir) { - this.dateDir = dateDir; - } - - public String getWavFileName() { - return wavFileName; - } - - public void setWavFileName(String wavFileName) { - this.wavFileName = wavFileName; - } - - @Override - public String toString() { - return "CallRecord{" + - "kssj='" + kssj + '\'' + - ", jssj='" + jssj + '\'' + - ", zjhm='" + zjhm + '\'' + - ", bjhm='" + bjhm + '\'' + - ", thsc=" + thsc + - ", thfx='" + thfx + '\'' + - ", wavFileName='" + wavFileName + '\'' + - '}'; - } -} diff --git a/src/main/java/com/threecloud/dataserviceyy/entity/DeviceMatchResult.java b/src/main/java/com/threecloud/dataserviceyy/entity/DeviceMatchResult.java deleted file mode 100644 index 7adfa27..0000000 --- a/src/main/java/com/threecloud/dataserviceyy/entity/DeviceMatchResult.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.threecloud.dataserviceyy.entity; - -import java.util.Map; - -/** - * 设备匹配结果实体类 - * - * 封装通话记录与设备通道表匹配后的结果信息。 - * 匹配流程: - * 1. 根据电话号码在设备通道表(YYDC_SBTD)中查找对应设备 - * 2. 根据设备的UUID在语音盒表(YYDC_YYSB)中查找所属地市信息 - * 3. 返回完整的匹配结果 - */ -public class DeviceMatchResult { - - /** 是否匹配成功 */ - private boolean success; - - /** 设备通道信息(ID, UUID, PHONE, THFX等字段) */ - private Map deviceChannel; - - /** 语音盒信息(ID, ORGAN_NAME, ORGAN_ID等字段) */ - private Map voiceBox; - - /** 匹配时使用的电话号码(可能是主叫或被叫) */ - private String phone; - - /** 失败时的错误描述信息 */ - private String errorMessage; - - public DeviceMatchResult() { - } - - /** - * 创建匹配成功的结果对象 - * @param deviceChannel 设备通道表中的记录信息 - * @param voiceBox 语音盒表中的记录信息 - * @param phone 匹配到的有效电话号码 - * @return 成功状态的DeviceMatchResult实例 - */ - public static DeviceMatchResult success(Map deviceChannel, - Map voiceBox, - String phone) { - DeviceMatchResult result = new DeviceMatchResult(); - result.success = true; - result.deviceChannel = deviceChannel; - result.voiceBox = voiceBox; - result.phone = phone; - return result; - } - - /** - * 创建匹配失败的结果对象 - * @param errorMessage 失败原因描述 - * @return 失败状态的DeviceMatchResult实例 - */ - public static DeviceMatchResult fail(String errorMessage) { - DeviceMatchResult result = new DeviceMatchResult(); - result.success = false; - result.errorMessage = errorMessage; - return result; - } - - public boolean isSuccess() { - return success; - } - - public Map getDeviceChannel() { - return deviceChannel; - } - - public Map getVoiceBox() { - return voiceBox; - } - - public String getPhone() { - return phone; - } - - public String getErrorMessage() { - return errorMessage; - } - - @Override - public String toString() { - if (success) { - return "DeviceMatchResult{success=true, phone=" + phone + - ", organName=" + (voiceBox != null ? voiceBox.get("ORGAN_NAME") : "null") + "}"; - } else { - return "DeviceMatchResult{success=false, error='" + errorMessage + "'}"; - } - } -} diff --git a/src/main/java/com/threecloud/dataserviceyy/entity/FtpProcessedFile.java b/src/main/java/com/threecloud/dataserviceyy/entity/FtpProcessedFile.java deleted file mode 100644 index e78c2b8..0000000 --- a/src/main/java/com/threecloud/dataserviceyy/entity/FtpProcessedFile.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.threecloud.dataserviceyy.entity; - -import java.util.Date; - -/** - * FTP已处理文件记录表 - * 对应 mid_voice.mid_ftp_processed_file - * 用于记录已处理过的FTP txt文件,避免重复处理 - */ -public class FtpProcessedFile { - - /** 自增ID主键 */ - private Long id; - - /** 地市编码 */ - private String cityCode; - - /** 地市名称 */ - private String cityName; - - /** FTP文件名 */ - private String fileName; - - /** 文件大小(字节) */ - private Long fileSize; - - /** 处理状态:0失败,1成功 */ - private String processStatus; - - /** 记录总数 */ - private Integer recordCount; - - /** 成功入库数 */ - private Integer successCount; - - /** 失败数 */ - private Integer failCount; - - /** 处理耗时(毫秒) */ - private Long processTime; - - /** 错误信息 */ - private String errorMsg; - - /** 创建时间 */ - private Date createTime; - - // Getters and Setters - public Long getId() { return id; } - public void setId(Long id) { this.id = id; } - - public String getCityCode() { return cityCode; } - public void setCityCode(String cityCode) { this.cityCode = cityCode; } - - public String getCityName() { return cityName; } - public void setCityName(String cityName) { this.cityName = cityName; } - - public String getFileName() { return fileName; } - public void setFileName(String fileName) { this.fileName = fileName; } - - public Long getFileSize() { return fileSize; } - public void setFileSize(Long fileSize) { this.fileSize = fileSize; } - - public String getProcessStatus() { return processStatus; } - public void setProcessStatus(String processStatus) { this.processStatus = processStatus; } - - public Integer getRecordCount() { return recordCount; } - public void setRecordCount(Integer recordCount) { this.recordCount = recordCount; } - - public Integer getSuccessCount() { return successCount; } - public void setSuccessCount(Integer successCount) { this.successCount = successCount; } - - public Integer getFailCount() { return failCount; } - public void setFailCount(Integer failCount) { this.failCount = failCount; } - - public Long getProcessTime() { return processTime; } - public void setProcessTime(Long processTime) { this.processTime = processTime; } - - public String getErrorMsg() { return errorMsg; } - public void setErrorMsg(String errorMsg) { this.errorMsg = errorMsg; } - - public Date getCreateTime() { return createTime; } - public void setCreateTime(Date createTime) { this.createTime = createTime; } -} diff --git a/src/main/java/com/threecloud/dataserviceyy/entity/SyncLog.java b/src/main/java/com/threecloud/dataserviceyy/entity/SyncLog.java deleted file mode 100644 index f6fc89f..0000000 --- a/src/main/java/com/threecloud/dataserviceyy/entity/SyncLog.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.threecloud.dataserviceyy.entity; - -import java.util.Date; - -/** - * 同步日志表 - * 记录每次同步任务的执行情况 - */ -public class SyncLog { - - /** 主键ID */ - private Long id; - - /** 设备ID */ - private String deviceId; - - /** 设备名称 */ - private String deviceName; - - /** 同步开始时间 */ - private Date startTime; - - /** 同步结束时间 */ - private Date endTime; - - /** 查询到的记录数 */ - private Integer totalCount; - - /** 成功处理数 */ - private Integer successCount; - - /** 失败数 */ - private Integer failCount; - - /** 同步状态:0失败,1成功,2部分成功 */ - private Integer status; - - /** 错误信息 */ - private String errorMsg; - - /** 创建时间 */ - private Date createTime; - - // Getters and Setters - public Long getId() { return id; } - public void setId(Long id) { this.id = id; } - - public String getDeviceId() { return deviceId; } - public void setDeviceId(String deviceId) { this.deviceId = deviceId; } - - public String getDeviceName() { return deviceName; } - public void setDeviceName(String deviceName) { this.deviceName = deviceName; } - - public Date getStartTime() { return startTime; } - public void setStartTime(Date startTime) { this.startTime = startTime; } - - public Date getEndTime() { return endTime; } - public void setEndTime(Date endTime) { this.endTime = endTime; } - - public Integer getTotalCount() { return totalCount; } - public void setTotalCount(Integer totalCount) { this.totalCount = totalCount; } - - public Integer getSuccessCount() { return successCount; } - public void setSuccessCount(Integer successCount) { this.successCount = successCount; } - - public Integer getFailCount() { return failCount; } - public void setFailCount(Integer failCount) { this.failCount = failCount; } - - public Integer getStatus() { return status; } - public void setStatus(Integer status) { this.status = status; } - - public String getErrorMsg() { return errorMsg; } - public void setErrorMsg(String errorMsg) { this.errorMsg = errorMsg; } - - public Date getCreateTime() { return createTime; } - public void setCreateTime(Date createTime) { this.createTime = createTime; } -} diff --git a/src/main/java/com/threecloud/dataserviceyy/entity/VoiceRecord.java b/src/main/java/com/threecloud/dataserviceyy/entity/VoiceRecord.java deleted file mode 100644 index 7d32813..0000000 --- a/src/main/java/com/threecloud/dataserviceyy/entity/VoiceRecord.java +++ /dev/null @@ -1,125 +0,0 @@ -package com.threecloud.dataserviceyy.entity; - -import java.util.Date; - -/** - * 录音记录表 - * 保存每次从录音盒下载并上传到OSS的录音文件信息 - */ -public class VoiceRecord { - - /** 主键ID */ - private Long id; - - /** 设备ID */ - private String deviceId; - - /** 设备UUID */ - private String deviceUuid; - - /** 设备名称/机构名称 */ - private String deviceName; - - /** 录音盒原始记录ID */ - private String recordId; - - /** 录音文件名 */ - private String fileName; - - /** 录音文件在录音盒中的路径 */ - private String filePath; - - /** OSS访问地址 */ - private String ossUrl; - - /** 本地存储路径 */ - private String localPath; - - /** 通话开始时间 */ - private Date callStartTime; - - /** 通话结束时间 */ - private Date callEndTime; - - /** 通话时长(秒) */ - private Integer duration; - - /** 通道号 1-8 */ - private Integer channel; - - /** 对方电话号码 */ - private String phone; - - /** 通话方向:1呼出,2呼入 */ - private Integer direction; - - /** 上传状态:0失败,1成功 */ - private Integer status; - - /** 失败原因 */ - private String failReason; - - /** 创建时间 */ - private Date createTime; - - /** 更新时间 */ - private Date updateTime; - - // Getters and Setters - public Long getId() { return id; } - public void setId(Long id) { this.id = id; } - - public String getDeviceId() { return deviceId; } - public void setDeviceId(String deviceId) { this.deviceId = deviceId; } - - public String getDeviceUuid() { return deviceUuid; } - public void setDeviceUuid(String deviceUuid) { this.deviceUuid = deviceUuid; } - - public String getDeviceName() { return deviceName; } - public void setDeviceName(String deviceName) { this.deviceName = deviceName; } - - public String getRecordId() { return recordId; } - public void setRecordId(String recordId) { this.recordId = recordId; } - - public String getFileName() { return fileName; } - public void setFileName(String fileName) { this.fileName = fileName; } - - public String getFilePath() { return filePath; } - public void setFilePath(String filePath) { this.filePath = filePath; } - - public String getOssUrl() { return ossUrl; } - public void setOssUrl(String ossUrl) { this.ossUrl = ossUrl; } - - public String getLocalPath() { return localPath; } - public void setLocalPath(String localPath) { this.localPath = localPath; } - - public Date getCallStartTime() { return callStartTime; } - public void setCallStartTime(Date callStartTime) { this.callStartTime = callStartTime; } - - public Date getCallEndTime() { return callEndTime; } - public void setCallEndTime(Date callEndTime) { this.callEndTime = callEndTime; } - - public Integer getDuration() { return duration; } - public void setDuration(Integer duration) { this.duration = duration; } - - public Integer getChannel() { return channel; } - public void setChannel(Integer channel) { this.channel = channel; } - - public String getPhone() { return phone; } - public void setPhone(String phone) { this.phone = phone; } - - public Integer getDirection() { return direction; } - public void setDirection(Integer direction) { this.direction = direction; } - - public Integer getStatus() { return status; } - public void setStatus(Integer status) { this.status = status; } - - public String getFailReason() { return failReason; } - public void setFailReason(String failReason) { this.failReason = failReason; } - - public Date getCreateTime() { return createTime; } - public void setCreateTime(Date createTime) { this.createTime = createTime; } - - public Date getUpdateTime() { return updateTime; } - public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; } -} diff --git a/src/main/java/com/threecloud/dataserviceyy/mapper/FtpProcessedFileMapper.java b/src/main/java/com/threecloud/dataserviceyy/mapper/FtpProcessedFileMapper.java deleted file mode 100644 index 952afbd..0000000 --- a/src/main/java/com/threecloud/dataserviceyy/mapper/FtpProcessedFileMapper.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.threecloud.dataserviceyy.mapper; - -import com.threecloud.dataserviceyy.entity.FtpProcessedFile; -import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.annotations.Param; - -import java.util.List; - -/** - * FTP已处理文件记录Mapper - * 用于记录已处理过的FTP txt文件,避免重复处理 - */ -@Mapper -public interface FtpProcessedFileMapper { - - /** - * 插入处理记录 - */ - int insert(FtpProcessedFile record); - - /** - * 根据地市编码和文件名查询(判断是否已处理) - */ - FtpProcessedFile selectByCityAndFileName(@Param("cityCode") String cityCode, - @Param("fileName") String fileName); - - /** - * 查询指定地市下所有已处理的文件名列表 - */ - List selectProcessedFileNamesByCity(@Param("cityCode") String cityCode); - - /** - * 批量查询指定地市下多个文件的处理状态 - */ - List selectByCityAndFileNames(@Param("cityCode") String cityCode, - @Param("fileNames") List fileNames); - - /** - * 根据ID更新处理结果 - */ - int updateById(FtpProcessedFile record); - - /** - * 删除指定时间之前的记录(清理历史) - */ - int deleteBeforeTime(@Param("cityCode") String cityCode, - @Param("beforeTime") String beforeTime); -} diff --git a/src/main/java/com/threecloud/dataserviceyy/mapper/MidVoiceCallRecordMapper.java b/src/main/java/com/threecloud/dataserviceyy/mapper/MidVoiceCallRecordMapper.java index ac68277..46ffe5f 100644 --- a/src/main/java/com/threecloud/dataserviceyy/mapper/MidVoiceCallRecordMapper.java +++ b/src/main/java/com/threecloud/dataserviceyy/mapper/MidVoiceCallRecordMapper.java @@ -11,21 +11,11 @@ import java.util.List; */ @Mapper public interface MidVoiceCallRecordMapper { - + /** * 插入通话记录 */ int insert(MidVoiceCallRecord record); - - /** - * 根据ID查询 - */ - MidVoiceCallRecord selectById(Long id); - - /** - * 根据通话记录ID查询(防止重复插入) - */ - MidVoiceCallRecord selectByCallRecordId(@Param("callRecordId") String callRecordId); /** * 批量查询某设备在时间范围内的已存在call_record_id @@ -34,44 +24,4 @@ public interface MidVoiceCallRecordMapper { List selectExistingCallRecordIds(@Param("deviceNo") String deviceNo, @Param("startTime") String startTime, @Param("endTime") String endTime); - - /** - * 根据设备编码查询 - */ - List selectByDeviceNo(@Param("deviceNo") String deviceNo); - - /** - * 分页查询 - */ - List selectList(@Param("cityCode") String cityCode, - @Param("deviceNo") String deviceNo, - @Param("callTel") String callTel, - @Param("calledTel") String calledTel, - @Param("startTime") String startTime, - @Param("endTime") String endTime, - @Param("offset") Integer offset, - @Param("limit") Integer limit); - - /** - * 统计总数 - */ - int count(@Param("cityCode") String cityCode, - @Param("deviceNo") String deviceNo, - @Param("callTel") String callTel, - @Param("calledTel") String calledTel, - @Param("startTime") String startTime, - @Param("endTime") String endTime); - - /** - * 更新文件路径 - */ - int updateFilePath(@Param("id") Long id, - @Param("recordingFilePath") String recordingFilePath); - - /** - * 更新状态和失败原因 - */ - int updateStatus(@Param("id") Long id, - @Param("callStatus") String callStatus, - @Param("failReason") String failReason); -} +} \ No newline at end of file diff --git a/src/main/java/com/threecloud/dataserviceyy/mapper/MidVoiceDeviceLogMapper.java b/src/main/java/com/threecloud/dataserviceyy/mapper/MidVoiceDeviceLogMapper.java index d41fd32..3406adc 100644 --- a/src/main/java/com/threecloud/dataserviceyy/mapper/MidVoiceDeviceLogMapper.java +++ b/src/main/java/com/threecloud/dataserviceyy/mapper/MidVoiceDeviceLogMapper.java @@ -3,10 +3,6 @@ package com.threecloud.dataserviceyy.mapper; import com.threecloud.dataserviceyy.entity.MidVoiceDeviceLog; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.annotations.Param; -import org.apache.ibatis.annotations.Select; - -import java.util.List; /** * 设备连接日志 Mapper @@ -25,23 +21,4 @@ public interface MidVoiceDeviceLogMapper { "#{connectType}, #{connectStatus}, #{failReason}, #{createTime}" + ")") int insert(MidVoiceDeviceLog log); - - /** - * 查询设备的最近连接日志 - */ - @Select("SELECT * FROM mid_voice_device_log " + - "WHERE device_id = #{deviceId} " + - "ORDER BY create_time DESC " + - "LIMIT #{limit}") - List selectRecentByDeviceId(@Param("deviceId") String deviceId, - @Param("limit") int limit); - - /** - * 查询失败的连接记录 - */ - @Select("SELECT * FROM mid_voice_device_log " + - "WHERE connect_status = '0' " + - "AND create_time >= #{startTime} " + - "ORDER BY create_time DESC") - List selectFailedLogs(@Param("startTime") String startTime); -} +} \ No newline at end of file diff --git a/src/main/java/com/threecloud/dataserviceyy/mapper/SyncLogMapper.java b/src/main/java/com/threecloud/dataserviceyy/mapper/SyncLogMapper.java deleted file mode 100644 index 0d7108d..0000000 --- a/src/main/java/com/threecloud/dataserviceyy/mapper/SyncLogMapper.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.threecloud.dataserviceyy.mapper; - -import com.threecloud.dataserviceyy.entity.SyncLog; -import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.annotations.Param; - -import java.util.List; - -/** - * 同步日志Mapper - */ -@Mapper -public interface SyncLogMapper { - - /** - * 插入同步日志 - */ - int insert(SyncLog log); - - /** - * 根据ID查询 - */ - SyncLog selectById(Long id); - - /** - * 查询设备的同步日志 - */ - List selectByDeviceId(@Param("deviceId") String deviceId); - - /** - * 分页查询同步日志 - */ - List selectList(@Param("deviceId") String deviceId, - @Param("startTime") String startTime, - @Param("endTime") String endTime, - @Param("offset") Integer offset, - @Param("limit") Integer limit); - - /** - * 统计总数 - */ - int count(@Param("deviceId") String deviceId, - @Param("startTime") String startTime, - @Param("endTime") String endTime); - - /** - * 更新同步结果 - */ - int updateResult(@Param("id") Long id, - @Param("endTime") java.util.Date endTime, - @Param("successCount") Integer successCount, - @Param("failCount") Integer failCount, - @Param("status") Integer status, - @Param("errorMsg") String errorMsg); -} diff --git a/src/main/java/com/threecloud/dataserviceyy/mapper/VoiceRecordMapper.java b/src/main/java/com/threecloud/dataserviceyy/mapper/VoiceRecordMapper.java deleted file mode 100644 index 056825c..0000000 --- a/src/main/java/com/threecloud/dataserviceyy/mapper/VoiceRecordMapper.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.threecloud.dataserviceyy.mapper; - -import com.threecloud.dataserviceyy.entity.VoiceRecord; -import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.annotations.Param; - -import java.util.List; - -/** - * 录音记录Mapper - */ -@Mapper -public interface VoiceRecordMapper { - - /** - * 插入录音记录 - */ - int insert(VoiceRecord record); - - /** - * 根据ID查询 - */ - VoiceRecord selectById(Long id); - - /** - * 根据设备ID和录音ID查询(防止重复插入) - */ - VoiceRecord selectByDeviceAndRecordId(@Param("deviceId") String deviceId, - @Param("recordId") String recordId); - - /** - * 查询设备的所有录音记录 - */ - List selectByDeviceId(@Param("deviceId") String deviceId); - - /** - * 分页查询录音记录 - */ - List selectList(@Param("deviceId") String deviceId, - @Param("deviceName") String deviceName, - @Param("phone") String phone, - @Param("startTime") String startTime, - @Param("endTime") String endTime, - @Param("offset") Integer offset, - @Param("limit") Integer limit); - - /** - * 统计总数 - */ - int count(@Param("deviceId") String deviceId, - @Param("deviceName") String deviceName, - @Param("phone") String phone, - @Param("startTime") String startTime, - @Param("endTime") String endTime); - - /** - * 更新OSS地址(如果上传后需要更新) - */ - int updateOssUrl(@Param("id") Long id, @Param("ossUrl") String ossUrl); - - /** - * 更新状态 - */ - int updateStatus(@Param("id") Long id, - @Param("status") Integer status, - @Param("failReason") String failReason); -} diff --git a/src/main/java/com/threecloud/dataserviceyy/service/VaaSyncService.java b/src/main/java/com/threecloud/dataserviceyy/service/VaaSyncService.java index 59fb9b1..8de79eb 100644 --- a/src/main/java/com/threecloud/dataserviceyy/service/VaaSyncService.java +++ b/src/main/java/com/threecloud/dataserviceyy/service/VaaSyncService.java @@ -17,10 +17,12 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; +import java.io.File; import java.net.URLEncoder; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.text.SimpleDateFormat; import java.util.*; /** @@ -29,6 +31,7 @@ import java.util.*; * 【功能】从EBOX录音盒拉取录音 → 下载文件 → 上传OSS → 保存通话记录 * 【策略】增量同步、无重试、防重复、失败记录到日志表 * 【账户】每个设备的账号密码从 mid_voice_device_config 表读取 + * 【OSS存储路径】voice/{地市编码}/{yyyy-MM-dd}/{文件名} */ @Service public class VaaSyncService { @@ -108,7 +111,6 @@ public class VaaSyncService { String ip = getStr(device, "IP"); Integer port = getInt(device, "PORT", 80); String orgCode = getStr(device, "ORG_CODE"); - // 每个设备有自己的账号密码,从配置表读取 String username = getStr(device, "USERNAME"); String password = getStr(device, "PASSWORD"); @@ -131,12 +133,10 @@ public class VaaSyncService { String deviceHost = buildHost(ip, port); - // 步骤1:登录(无重试) + // 步骤1:登录 String authToken; try { - String loginUrl = String.format("http://%s/authorize?username=%s&password=%s", - deviceHost, URLEncoder.encode(username, "UTF-8"), URLEncoder.encode(password, "UTF-8")); - authToken = vaaHttpUtil.httpLogin(loginUrl); + authToken = loginDevice(deviceHost, username, password); saveDeviceLog(deviceId, deviceNo, cityCode, cityName, ip, port, "1", "1", null); } catch (Exception e) { saveDeviceLog(deviceId, deviceNo, cityCode, cityName, ip, port, "1", "0", "登录失败:" + e.getMessage()); @@ -144,10 +144,10 @@ public class VaaSyncService { return; } - // 步骤2:获取分机号码(通道→电话号码映射),用于解析主叫/被叫 + // 步骤2:获取分机号码 Map extNumbers = getExtensionNumbers(deviceHost, authToken); - // 步骤3:计算同步时间范围(增量同步) + // 步骤3:计算同步时间范围 Date lastSyncTime = SyncTimeUtil.readLastSyncTime(downloadPath, deviceId); Date now = new Date(); if (lastSyncTime == null || DateUtil.getDateDoubleDiff(now, lastSyncTime) > 1.0) { @@ -156,12 +156,12 @@ public class VaaSyncService { long startEpoch = lastSyncTime.getTime() / 1000; long endEpoch = now.getTime() / 1000; - // 步骤4:获取录音列表(无重试) + // 步骤4:获取录音列表(Token过期时自动重登录重试一次) JSONArray records; try { String recordUrl = String.format("http://%s/service/record/~/time[%d,%d]", deviceHost, startEpoch, endEpoch); - String recordData = vaaHttpUtil.httpVisit(recordUrl, authToken); + String recordData = httpVisitWithReauth(recordUrl, authToken, deviceHost, username, password); records = vaaHttpUtil.parseRecordData(recordData); saveDeviceLog(deviceId, deviceNo, cityCode, cityName, ip, port, "2", "1", null); } catch (Exception e) { @@ -176,7 +176,7 @@ public class VaaSyncService { } logger.info("【设备】获取到 {} 条录音记录", records.size()); - // 批量查询已存在的call_record_id(一次查询代替N次,防重复性能优化) + // 批量查询已存在的call_record_id String startTimeStr = DateUtil.formatDate(lastSyncTime, "yyyy-MM-dd HH:mm:ss"); String endTimeStr = DateUtil.formatDate(now, "yyyy-MM-dd HH:mm:ss"); Set existingIds = new HashSet<>(callRecordMapper.selectExistingCallRecordIds( @@ -186,18 +186,27 @@ public class VaaSyncService { // 步骤5:逐条处理录音 int successCount = 0; int failCount = 0; + int skipCount = 0; Date latestCallTime = null; for (int i = 0; i < records.size(); i++) { JSONObject rec = records.getJSONObject(i); try { + // 解析时间用于更新同步进度(即使跳过也要更新,防止重复拉取) + Long endTime = RecordParser.parseEndTime(rec); + if (endTime != null) { + Date recEndTime = new Date(endTime * 1000); + if (latestCallTime == null || recEndTime.after(latestCallTime)) { + latestCallTime = recEndTime; + } + } + Date callTime = processSingleRecord(rec, deviceNo, cityName, cityCode, orgCode, - deviceHost, authToken, extNumbers, existingIds); + deviceHost, authToken, extNumbers, existingIds, username, password); if (callTime != null) { successCount++; - if (latestCallTime == null || callTime.after(latestCallTime)) { - latestCallTime = callTime; - } + } else { + skipCount++; } } catch (Exception e) { failCount++; @@ -205,7 +214,7 @@ public class VaaSyncService { } } - // 步骤6:保存同步时间 + // 步骤6:保存同步时间(用API返回的最大时间,而非仅新处理的时间) if (latestCallTime != null) { SyncTimeUtil.writeLastSyncTime(downloadPath, deviceId, latestCallTime); } @@ -216,12 +225,15 @@ public class VaaSyncService { /** * 处理单条录音记录 * + * OSS存储路径: voice/{cityCode}/{yyyy-MM-dd}/{fileName} + * 上传失败不阻塞:记录仍保存到数据库,文件路径留空 + * * @return 通话开始时间(成功时),null表示跳过 */ private Date processSingleRecord(JSONObject rec, String deviceNo, String cityName, String cityCode, String orgCode, String deviceHost, String authToken, Map extNumbers, - Set existingIds) throws Exception { + Set existingIds, String username, String password) throws Exception { // 解析录音信息 String recordId = RecordParser.parseRecordId(rec); String filePath = RecordParser.parseFilePath(rec); @@ -232,7 +244,6 @@ public class VaaSyncService { Long begTime = RecordParser.parseBegTime(rec); Long endTime = RecordParser.parseEndTime(rec); - // 校验必要字段 if (filePath == null || filePath.isEmpty()) { return null; } @@ -240,7 +251,7 @@ public class VaaSyncService { return null; } - // 防重复:内存中比对已存在的call_record_id(一次查询代替N次) + // 防重复 String callRecordId = deviceNo + "_" + recordId; if (existingIds.contains(callRecordId)) { logger.debug("【录音】已存在,跳过: {}", callRecordId); @@ -253,7 +264,7 @@ public class VaaSyncService { String localPath = FilePathUtil.buildLocalPath(downloadPath, cityCode, callStartTime, deviceNo, fileName); Path localFile = Paths.get(localPath); - // 获取通道绑定的电话号码(从EBOX分机号码配置) + // 获取通道绑定的电话号码 String channelPhone = ""; if (channel != null && extNumbers != null) { channelPhone = extNumbers.getOrDefault(String.valueOf(channel), ""); @@ -275,12 +286,17 @@ public class VaaSyncService { } callRecord.setRecordingFileSize((int) Files.size(localFile)); - // 上传到OSS并获取预览URL - OssFileResponse ossResponse = ossFileService.uploadFile( - Files.readAllBytes(localFile), fileName, "voice/" + cityCode + "/"); - String previewUrl = ossResponse.getFileUrl(); - callRecord.setRecordingFilePath(previewUrl); - logger.info("【录音】OSS预览URL: {}", previewUrl); + // 上传到OSS(地市+日期目录),上传失败不阻塞,仍保存通话记录 + String ossPath = "voice/" + cityCode + "/" + new SimpleDateFormat("yyyy-MM-dd").format(callStartTime) + "/"; + try { + OssFileResponse ossResponse = ossFileService.uploadFile(localFile.toFile(), fileName, ossPath); + String previewUrl = ossResponse.getFileUrl(); + callRecord.setRecordingFilePath(previewUrl); + logger.info("【录音】OSS上传成功: {}", previewUrl); + } catch (Exception e) { + logger.error("【录音】OSS上传失败,通话记录仍将保存: fileName={}, 原因={}", fileName, e.getMessage()); + callRecord.setRecordingFilePath(null); + } // 保存到数据库 callRecordMapper.insert(callRecord); @@ -289,6 +305,32 @@ public class VaaSyncService { return callStartTime; } + /** + * 登录录音盒设备 + */ + private String loginDevice(String deviceHost, String username, String password) throws Exception { + String loginUrl = String.format("http://%s/authorize?username=%s&password=%s", + deviceHost, URLEncoder.encode(username, "UTF-8"), URLEncoder.encode(password, "UTF-8")); + return vaaHttpUtil.httpLogin(loginUrl); + } + + /** + * HTTP访问API,Token过期时自动重新登录并重试一次 + */ + private String httpVisitWithReauth(String apiUrl, String authToken, + String deviceHost, String username, String password) throws Exception { + try { + return vaaHttpUtil.httpVisit(apiUrl, authToken); + } catch (Exception e) { + if (e.getMessage() != null && e.getMessage().contains("认证失效")) { + logger.warn("【设备】Token过期,自动重新登录并重试"); + String newToken = loginDevice(deviceHost, username, password); + return vaaHttpUtil.httpVisit(apiUrl, newToken); + } + throw e; + } + } + /** * 构建通话记录实体 * @@ -330,8 +372,6 @@ public class VaaSyncService { /** * 获取分机号码配置 - * 调用 EBOX 接口 GET /service/ext/number - * 返回 Map<通道号, 电话号码>,如 {"1":"8001","2":"8002"} */ private Map getExtensionNumbers(String deviceHost, String authToken) { try { @@ -384,4 +424,4 @@ public class VaaSyncService { try { return Integer.parseInt(v.toString()); } catch (NumberFormatException e) { return defaultVal; } } -} +} \ No newline at end of file diff --git a/src/main/java/com/threecloud/dataserviceyy/util/DataUtil.java b/src/main/java/com/threecloud/dataserviceyy/util/DataUtil.java deleted file mode 100644 index b0f37e5..0000000 --- a/src/main/java/com/threecloud/dataserviceyy/util/DataUtil.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.threecloud.dataserviceyy.util; - -import java.util.Collection; -import java.util.Map; - -public class DataUtil { - - public static boolean isNotNull(Object obj) { - return obj != null; - } - - public static boolean isNotNull(Map map) { - return map != null && !map.isEmpty(); - } - - public static boolean isNotNull(Collection collection) { - return collection != null && !collection.isEmpty(); - } - - public static boolean isNotNull(String str) { - return str != null && !str.trim().isEmpty(); - } - - public static boolean isNumber(Object obj) { - if (obj == null) return false; - try { - Double.parseDouble(obj.toString()); - return true; - } catch (NumberFormatException e) { - return false; - } - } - - public static Long objToLong(Object obj) { - if (obj == null) return null; - if (obj instanceof Long) return (Long) obj; - if (obj instanceof Number) return ((Number) obj).longValue(); - if (isNumber(obj)) return Long.parseLong(obj.toString()); - return null; - } - - public static String objToValue(Object obj) { - if (obj == null) return null; - return obj.toString(); - } - - public static Integer objToInteger(Object obj) { - if (obj == null) return null; - if (obj instanceof Integer) return (Integer) obj; - if (obj instanceof Number) return ((Number) obj).intValue(); - if (isNumber(obj)) return Integer.parseInt(obj.toString()); - return null; - } - - public static Float objToFloat(Object obj) { - if (obj == null) return null; - if (obj instanceof Float) return (Float) obj; - if (obj instanceof Number) return ((Number) obj).floatValue(); - if (isNumber(obj)) return Float.parseFloat(obj.toString()); - return null; - } - - public static Double objToDouble(Object obj) { - if (obj == null) return null; - if (obj instanceof Double) return (Double) obj; - if (obj instanceof Number) return ((Number) obj).doubleValue(); - if (isNumber(obj)) return Double.parseDouble(obj.toString()); - return null; - } - - public static boolean objToBoolean(Object obj) { - if (obj == null) return false; - if (obj instanceof Boolean) return (Boolean) obj; - return Boolean.parseBoolean(obj.toString()); - } -} diff --git a/src/main/java/com/threecloud/dataserviceyy/util/OssFileService.java b/src/main/java/com/threecloud/dataserviceyy/util/OssFileService.java index a443e3d..ff69417 100644 --- a/src/main/java/com/threecloud/dataserviceyy/util/OssFileService.java +++ b/src/main/java/com/threecloud/dataserviceyy/util/OssFileService.java @@ -4,7 +4,7 @@ import com.threecloud.dataserviceyy.entity.OssFileResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.FileSystemResource; import org.springframework.http.*; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.stereotype.Component; @@ -13,18 +13,25 @@ import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; import javax.annotation.PostConstruct; +import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import java.io.File; import java.io.IOException; import java.net.HttpURLConnection; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; /** * OSS文件存储服务客户端 * * 基于新OSS文件存储服务API对接,提供文件上传、预览等功能。 - * API文档地址: file:sanduoyun/developspace/dataservice-yy/OSS文件存储服务-技术对接文档.md * * 配置项: - * oss.base-url: OSS服务基础地址(如 http://53.1.211.7) + * oss.base-url: OSS服务基础地址(如 http://53.1.211.7/apiOss) * oss.upload-path: 上传存储路径(如 voice/record/) */ @Component @@ -32,7 +39,7 @@ public class OssFileService { private static final Logger logger = LoggerFactory.getLogger(OssFileService.class); - @Value("${oss.base-url:http://53.1.211.7}") + @Value("${oss.base-url:http://53.1.211.7/apiOss}") private String ossBaseUrl; @Value("${oss.upload-path:voice/record/}") @@ -48,29 +55,28 @@ public class OssFileService { } /** - * 上传文件到OSS + * 上传文件到OSS(通过文件路径,避免大文件内存溢出) * * 调用 POST /oss/upload 接口。 * 存储路径格式: {uploadPath}/{fileName} - * 示例: voice/record/xxx.wav * - * @param fileData 文件字节数组 + * @param file 本地文件 * @param fileName 文件名(如 xxx.wav) * @return OssFileResponse 包含 fileUrl(预览地址)和 fileKey(文件标识) */ - public OssFileResponse uploadFile(byte[] fileData, String fileName) { - return uploadFile(fileData, fileName, defaultUploadPath); + public OssFileResponse uploadFile(File file, String fileName) { + return uploadFile(file, fileName, defaultUploadPath); } /** * 上传文件到OSS(指定存储路径) * - * @param fileData 文件字节数组 + * @param file 本地文件 * @param fileName 文件名 * @param uploadPath 存储路径(如 voice/record/) * @return OssFileResponse */ - public OssFileResponse uploadFile(byte[] fileData, String fileName, String uploadPath) { + public OssFileResponse uploadFile(File file, String fileName, String uploadPath) { String url = ossBaseUrl + "/oss/upload"; String path = uploadPath.endsWith("/") ? uploadPath : uploadPath + "/"; @@ -78,12 +84,7 @@ public class OssFileService { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.MULTIPART_FORM_DATA); - ByteArrayResource resource = new ByteArrayResource(fileData) { - @Override - public String getFilename() { - return fileName; - } - }; + FileSystemResource resource = new FileSystemResource(file); MultiValueMap body = new LinkedMultiValueMap<>(); body.add("file", resource); @@ -91,7 +92,8 @@ public class OssFileService { HttpEntity> requestEntity = new HttpEntity<>(body, headers); - logger.debug("上传文件到OSS: url={}, path={}, fileName={}, size={} bytes", url, path, fileName, fileData.length); + logger.debug("上传文件到OSS: url={}, path={}, fileName={}, size={} bytes", + url, path, fileName, file.length()); ResponseEntity response = restTemplate.exchange( url, HttpMethod.POST, @@ -131,26 +133,45 @@ public class OssFileService { } /** - * 创建支持自定义SSL配置的RestTemplate + * 创建支持自定义SSL配置和超时设置的RestTemplate */ private RestTemplate createRestTemplate() { SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory() { @Override protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException { + // 超时配置 + connection.setConnectTimeout(15000); // 连接超时15秒 + connection.setReadTimeout(300000); // 读取超时5分钟(大文件上传) + + // SSL信任配置 if (ossBaseUrl.startsWith("https") && connection instanceof HttpsURLConnection) { HttpsURLConnection httpsConn = (HttpsURLConnection) connection; try { - javax.net.ssl.TrustManager[] trustAllCerts = new javax.net.ssl.TrustManager[]{ - new javax.net.ssl.X509TrustManager() { - public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; } - public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {} - public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {} + TrustManager[] trustAllCerts = new TrustManager[]{ + new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + return null; + } + + @Override + public void checkClientTrusted(X509Certificate[] certs, String authType) { + } + + @Override + public void checkServerTrusted(X509Certificate[] certs, String authType) { + } } }; - javax.net.ssl.SSLContext sc = javax.net.ssl.SSLContext.getInstance("TLS"); - sc.init(null, trustAllCerts, new java.security.SecureRandom()); + SSLContext sc = SSLContext.getInstance("TLS"); + sc.init(null, trustAllCerts, new SecureRandom()); httpsConn.setSSLSocketFactory(sc.getSocketFactory()); - httpsConn.setHostnameVerifier((hostname, session) -> true); + httpsConn.setHostnameVerifier(new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + }); } catch (Exception e) { logger.warn("SSL配置失败: {}", e.getMessage()); } diff --git a/src/main/java/com/threecloud/dataserviceyy/util/VaaHttpUtil.java b/src/main/java/com/threecloud/dataserviceyy/util/VaaHttpUtil.java index a290b0f..bb88b0c 100644 --- a/src/main/java/com/threecloud/dataserviceyy/util/VaaHttpUtil.java +++ b/src/main/java/com/threecloud/dataserviceyy/util/VaaHttpUtil.java @@ -144,6 +144,9 @@ public class VaaHttpUtil { int responseCode = conn.getResponseCode(); if (responseCode == 200) { + // 获取服务器声明的文件大小,用于完整性校验 + long expectedSize = conn.getContentLengthLong(); + inputStream = conn.getInputStream(); outputStream = new FileOutputStream(savePath); @@ -159,6 +162,13 @@ public class VaaHttpUtil { outputStream.flush(); logger.info("录音文件下载完成: {}, 大小: {} bytes ({} MB)", savePath, totalBytes, totalBytes / 1024 / 1024); + + // 完整性校验:如果服务器返回了Content-Length,校验实际下载大小 + if (expectedSize > 0 && totalBytes != expectedSize) { + saveFile.delete(); + throw new RuntimeException(String.format( + "文件下载不完整: 期望 %d bytes, 实际 %d bytes", expectedSize, totalBytes)); + } } else { throw new RuntimeException("文件下载失败,HTTP状态码: " + responseCode); } diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..609c890 --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - %msg%n + + + + + + ${LOG_PATH}/${LOG_FILE}.log + + + ${LOG_PATH}/${LOG_FILE}-%d{yyyy-MM-dd}.log + + 30 + + 1GB + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/FtpProcessedFileMapper.xml b/src/main/resources/mapper/FtpProcessedFileMapper.xml deleted file mode 100644 index 0823425..0000000 --- a/src/main/resources/mapper/FtpProcessedFileMapper.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - INSERT INTO mid_ftp_processed_file ( - city_code, city_name, file_name, file_size, process_status, - record_count, success_count, fail_count, process_time, error_msg, create_time - ) VALUES ( - #{cityCode}, #{cityName}, #{fileName}, #{fileSize}, #{processStatus}, - #{recordCount}, #{successCount}, #{failCount}, #{processTime}, #{errorMsg}, NOW() - ) - - - - - - - - - - - - - - UPDATE mid_ftp_processed_file - SET process_status = #{processStatus}, - record_count = #{recordCount}, - success_count = #{successCount}, - fail_count = #{failCount}, - process_time = #{processTime}, - error_msg = #{errorMsg} - WHERE id = #{id} - - - - - DELETE FROM mid_ftp_processed_file - WHERE city_code = #{cityCode} - AND create_time < TO_TIMESTAMP(#{beforeTime}, 'YYYY-MM-DD HH24:MI:SS') - - - diff --git a/src/main/resources/mapper/MidVoiceCallRecordMapper.xml b/src/main/resources/mapper/MidVoiceCallRecordMapper.xml index 0032322..111e7a2 100644 --- a/src/main/resources/mapper/MidVoiceCallRecordMapper.xml +++ b/src/main/resources/mapper/MidVoiceCallRecordMapper.xml @@ -41,18 +41,6 @@ ) - - - - - - - - - - - - - - - - - - UPDATE mid_voice_call_record - SET recording_file_path = #{recordingFilePath}, sync_time = NOW() - WHERE id = #{id} - - - - - UPDATE mid_voice_call_record - SET call_status = #{callStatus}, fail_reason = #{failReason}, sync_time = NOW() - WHERE id = #{id} - - - + \ No newline at end of file diff --git a/src/main/resources/mapper/SyncLogMapper.xml b/src/main/resources/mapper/SyncLogMapper.xml deleted file mode 100644 index aa520e2..0000000 --- a/src/main/resources/mapper/SyncLogMapper.xml +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - INSERT INTO sync_log ( - device_id, device_name, start_time, end_time, total_count, - success_count, fail_count, status, error_msg, create_time - ) VALUES ( - #{deviceId}, #{deviceName}, #{startTime}, #{endTime}, #{totalCount}, - #{successCount}, #{failCount}, #{status}, #{errorMsg}, NOW() - ) - - - - - - - - - - - - - - - - - UPDATE sync_log - SET end_time = #{endTime}, - success_count = #{successCount}, - fail_count = #{failCount}, - status = #{status}, - error_msg = #{errorMsg} - WHERE id = #{id} - - - diff --git a/src/main/resources/mapper/VoiceRecordMapper.xml b/src/main/resources/mapper/VoiceRecordMapper.xml deleted file mode 100644 index ae4198c..0000000 --- a/src/main/resources/mapper/VoiceRecordMapper.xml +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - INSERT INTO voice_record ( - device_id, device_uuid, device_name, record_id, file_name, file_path, - oss_url, local_path, call_start_time, call_end_time, duration, - channel, phone, direction, status, fail_reason, create_time, update_time - ) VALUES ( - #{deviceId}, #{deviceUuid}, #{deviceName}, #{recordId}, #{fileName}, #{filePath}, - #{ossUrl}, #{localPath}, #{callStartTime}, #{callEndTime}, #{duration}, - #{channel}, #{phone}, #{direction}, #{status}, #{failReason}, NOW(), NOW() - ) - - - - - - - - - - - - - - - - - - - - UPDATE voice_record - SET oss_url = #{ossUrl}, update_time = NOW() - WHERE id = #{id} - - - - - UPDATE voice_record - SET status = #{status}, fail_reason = #{failReason}, update_time = NOW() - WHERE id = #{id} - - -