diff --git a/pom.xml b/pom.xml index 26ec708..6ee0dc8 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.cst Langchain4j-rj - 1.1.7-SNAPSHOT + 1.251130.7-SNAPSHOT Langchain4j-rj Langchain4j-rj20250803 diff --git a/src/main/java/com/rj/common/IntegerDeserializer.java b/src/main/java/com/rj/common/IntegerDeserializer.java new file mode 100644 index 0000000..e6b9702 --- /dev/null +++ b/src/main/java/com/rj/common/IntegerDeserializer.java @@ -0,0 +1,34 @@ +package com.rj.common; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; + +/** + * 自定义 Integer 反序列化器 + * 支持从字符串和整数两种格式反序列化为 Integer + * 如果无法解析,返回 null + */ +public class IntegerDeserializer extends JsonDeserializer { + + @Override + public Integer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + String value = p.getValueAsString(); + if (value == null || value.trim().isEmpty()) { + return null; + } + + try { + // 尝试直接解析为整数 + return Integer.parseInt(value); + } catch (NumberFormatException e) { + // 如果无法解析为整数,返回 null + // 这样可以避免反序列化错误,字段会被设置为 null + return null; + } + } +} + + diff --git a/src/main/java/com/rj/common/LocalDateTimeDeserializer.java b/src/main/java/com/rj/common/LocalDateTimeDeserializer.java new file mode 100644 index 0000000..fe21ae0 --- /dev/null +++ b/src/main/java/com/rj/common/LocalDateTimeDeserializer.java @@ -0,0 +1,65 @@ +package com.rj.common; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +/** + * 自定义 LocalDateTime 反序列化器 + * 支持多种时间格式,统一转换为应用配置时区的 LocalDateTime + * 支持格式: + * - 2025-11-29T18:13:02.1670603 + * - 2025-11-29T18:13:02.1670603+08:00 + * - 2025-11-29T18:13:02Z + * - 2025-11-29 18:13:02 + */ +public class LocalDateTimeDeserializer extends JsonDeserializer { + + @Override + public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + String timeStr = p.getValueAsString(); + if (timeStr == null || timeStr.trim().isEmpty()) { + return null; + } + + try { + // 检测是否包含时区信息:以Z结尾,或包含时区偏移格式(+HH:MM 或 -HH:MM) + boolean hasTimezone = timeStr.endsWith("Z") || + timeStr.matches(".*[+-]\\d{2}:\\d{2}(?:\\d{2})?$"); + + if (hasTimezone) { + // 处理ISO 8601格式,包含时区信息 + ZonedDateTime zonedDateTime; + if (timeStr.endsWith("Z")) { + // UTC时间,转换为应用配置的时区 + zonedDateTime = ZonedDateTime.parse(timeStr.replace("Z", "+00:00")).withZoneSameInstant(TimeZoneUtils.getZoneId()); + } else { + // 带时区偏移的时间,转换为应用配置的时区 + zonedDateTime = 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, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } + } catch (Exception e) { + throw new IOException("解析时间字符串失败: " + timeStr, e); + } + } +} + diff --git a/src/main/java/com/rj/common/TimeZoneUtils.java b/src/main/java/com/rj/common/TimeZoneUtils.java new file mode 100644 index 0000000..0f7bb2c --- /dev/null +++ b/src/main/java/com/rj/common/TimeZoneUtils.java @@ -0,0 +1,60 @@ +package com.rj.common; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import com.rj.config.AppConfig; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +/** + * 时区工具类 + * 提供统一的时区相关工具方法 + * + * @author Auto Generated + * @since 2025-01-01 + */ +@Component +public class TimeZoneUtils { + + private static AppConfig appConfig; + + @Autowired + public void setAppConfig(AppConfig appConfig) { + TimeZoneUtils.appConfig = appConfig; + } + + /** + * 获取应用配置的时区ID + * + * @return ZoneId + */ + public static ZoneId getZoneId() { + String timezone = appConfig != null ? appConfig.getTimezone() : "Asia/Shanghai"; + return ZoneId.of(timezone); + } + + /** + * 获取当前时区的当前时间 + * + * @return LocalDateTime + */ + public static LocalDateTime now() { + return ZonedDateTime.now(getZoneId()).toLocalDateTime(); + } + + /** + * 将指定时区的时间转换为应用时区的LocalDateTime + * + * @param zonedDateTime 带时区的时间 + * @return LocalDateTime + */ + public static LocalDateTime toLocalDateTime(ZonedDateTime zonedDateTime) { + if (zonedDateTime == null) { + return null; + } + return zonedDateTime.withZoneSameInstant(getZoneId()).toLocalDateTime(); + } +} + diff --git a/src/main/java/com/rj/config/AppConfig.java b/src/main/java/com/rj/config/AppConfig.java new file mode 100644 index 0000000..3e6a4d7 --- /dev/null +++ b/src/main/java/com/rj/config/AppConfig.java @@ -0,0 +1,23 @@ +package com.rj.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * 应用配置类 + * + * @author Auto Generated + * @since 2025-01-01 + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "app") +public class AppConfig { + + /** + * 应用时区,默认为 Asia/Shanghai + */ + private String timezone = "Asia/Shanghai"; +} + diff --git a/src/main/java/com/rj/config/GlobalExceptionHandler.java b/src/main/java/com/rj/config/GlobalExceptionHandler.java new file mode 100644 index 0000000..b972cc1 --- /dev/null +++ b/src/main/java/com/rj/config/GlobalExceptionHandler.java @@ -0,0 +1,226 @@ +package com.rj.config; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.HttpMediaTypeNotSupportedException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.multipart.MultipartException; + +import jakarta.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; + +/** + * 全局异常处理器 + * 用于捕获和处理各种异常,特别是 HttpMediaTypeNotSupportedException + * + * @author Auto Generated + * @since 2025-11-30 + */ +@Slf4j +@ControllerAdvice +public class GlobalExceptionHandler { + + @Autowired + private ObjectMapper objectMapper; + + /** + * 处理 HttpMediaTypeNotSupportedException 异常 + * 当 Content-Type 不支持时触发,特别是 multipart/form-data 请求使用 @RequestBody 时 + */ + @ExceptionHandler(HttpMediaTypeNotSupportedException.class) + public ResponseEntity> handleHttpMediaTypeNotSupportedException( + HttpMediaTypeNotSupportedException ex, + HttpServletRequest request, + WebRequest webRequest) { + + Map result = new HashMap<>(); + String contentType = ex.getContentType() != null ? ex.getContentType().toString() : "unknown"; + String dataType = null; + + // 尝试从请求参数中提取 dataType + try { + // 如果是 multipart/form-data,尝试从 data 参数中提取 + String dataParam = request.getParameter("data"); + if (dataParam != null && !dataParam.trim().isEmpty()) { + try { + Map dataMap = objectMapper.readValue(dataParam, new TypeReference>() {}); + dataType = (String) dataMap.get("dataType"); + if (dataType == null || dataType.trim().isEmpty()) { + dataType = (String) dataMap.get("pushDataType"); + } + } catch (Exception e) { + log.debug("无法从 data 参数解析 dataType: {}", e.getMessage()); + } + } + + // 如果 data 参数中没有,尝试直接从请求参数中获取 + if (dataType == null || dataType.trim().isEmpty()) { + dataType = request.getParameter("dataType"); + if (dataType == null || dataType.trim().isEmpty()) { + dataType = request.getParameter("pushDataType"); + } + } + } catch (Exception e) { + log.debug("提取 dataType 时发生异常: {}", e.getMessage()); + } + + // 输出错误信息和 dataType 到控制台 + String errorMessage = String.format( + "HttpMediaTypeNotSupportedException: Content-Type '%s' is not supported. dataType: %s, URI: %s", + contentType, + dataType != null ? dataType : "unknown", + request.getRequestURI() + ); + + log.error(errorMessage); + System.out.println("=========================================="); + System.out.println("ERROR: " + errorMessage); + System.out.println("Content-Type: " + contentType); + System.out.println("dataType: " + (dataType != null ? dataType : "unknown")); + System.out.println("Request URI: " + request.getRequestURI()); + System.out.println("Request Method: " + request.getMethod()); + System.out.println("=========================================="); + + result.put("success", false); + result.put("message", "不支持的 Content-Type: " + contentType); + result.put("error", "HttpMediaTypeNotSupportedException"); + result.put("contentType", contentType); + result.put("dataType", dataType); + result.put("uri", request.getRequestURI()); + + return ResponseEntity.status(HttpStatus.UNSUPPORTED_MEDIA_TYPE).body(result); + } + + /** + * 处理 MultipartException 异常 + * 当 multipart 请求解析失败时触发 + */ + @ExceptionHandler(MultipartException.class) + public ResponseEntity> handleMultipartException( + MultipartException ex, + HttpServletRequest request) { + + Map result = new HashMap<>(); + String dataType = null; + + // 尝试从请求参数中提取 dataType + try { + String dataParam = request.getParameter("data"); + if (dataParam != null && !dataParam.trim().isEmpty()) { + try { + Map dataMap = objectMapper.readValue(dataParam, new TypeReference>() {}); + dataType = (String) dataMap.get("dataType"); + if (dataType == null || dataType.trim().isEmpty()) { + dataType = (String) dataMap.get("pushDataType"); + } + } catch (Exception e) { + log.debug("无法从 data 参数解析 dataType: {}", e.getMessage()); + } + } + + if (dataType == null || dataType.trim().isEmpty()) { + dataType = request.getParameter("dataType"); + if (dataType == null || dataType.trim().isEmpty()) { + dataType = request.getParameter("pushDataType"); + } + } + } catch (Exception e) { + log.debug("提取 dataType 时发生异常: {}", e.getMessage()); + } + + String errorMessage = String.format( + "MultipartException: %s. dataType: %s, URI: %s", + ex.getMessage(), + dataType != null ? dataType : "unknown", + request.getRequestURI() + ); + + log.error(errorMessage, ex); + System.out.println("=========================================="); + System.out.println("ERROR: " + errorMessage); + System.out.println("dataType: " + (dataType != null ? dataType : "unknown")); + System.out.println("Request URI: " + request.getRequestURI()); + System.out.println("=========================================="); + + result.put("success", false); + result.put("message", "Multipart 请求解析失败: " + ex.getMessage()); + result.put("error", "MultipartException"); + result.put("dataType", dataType); + result.put("uri", request.getRequestURI()); + + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(result); + } + + /** + * 处理 HttpMessageNotReadableException 异常 + * 当请求体无法读取时触发 + */ + @ExceptionHandler(HttpMessageNotReadableException.class) + public ResponseEntity> handleHttpMessageNotReadableException( + HttpMessageNotReadableException ex, + HttpServletRequest request) { + + Map result = new HashMap<>(); + String contentType = request.getContentType(); + String dataType = null; + + // 尝试从请求参数中提取 dataType + try { + String dataParam = request.getParameter("data"); + if (dataParam != null && !dataParam.trim().isEmpty()) { + try { + Map dataMap = objectMapper.readValue(dataParam, new TypeReference>() {}); + dataType = (String) dataMap.get("dataType"); + if (dataType == null || dataType.trim().isEmpty()) { + dataType = (String) dataMap.get("pushDataType"); + } + } catch (Exception e) { + log.debug("无法从 data 参数解析 dataType: {}", e.getMessage()); + } + } + + if (dataType == null || dataType.trim().isEmpty()) { + dataType = request.getParameter("dataType"); + if (dataType == null || dataType.trim().isEmpty()) { + dataType = request.getParameter("pushDataType"); + } + } + } catch (Exception e) { + log.debug("提取 dataType 时发生异常: {}", e.getMessage()); + } + + String errorMessage = String.format( + "HttpMessageNotReadableException: %s. Content-Type: %s, dataType: %s, URI: %s", + ex.getMessage(), + contentType != null ? contentType : "unknown", + dataType != null ? dataType : "unknown", + request.getRequestURI() + ); + + log.error(errorMessage, ex); + System.out.println("=========================================="); + System.out.println("ERROR: " + errorMessage); + System.out.println("Content-Type: " + (contentType != null ? contentType : "unknown")); + System.out.println("dataType: " + (dataType != null ? dataType : "unknown")); + System.out.println("Request URI: " + request.getRequestURI()); + System.out.println("=========================================="); + + result.put("success", false); + result.put("message", "请求体无法读取: " + ex.getMessage()); + result.put("error", "HttpMessageNotReadableException"); + result.put("contentType", contentType); + result.put("dataType", dataType); + result.put("uri", request.getRequestURI()); + + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(result); + } +} + diff --git a/src/main/java/com/rj/controller/AudioFileController.java b/src/main/java/com/rj/controller/AudioFileController.java index 6ab8135..0f5f030 100644 --- a/src/main/java/com/rj/controller/AudioFileController.java +++ b/src/main/java/com/rj/controller/AudioFileController.java @@ -1,9 +1,19 @@ package com.rj.controller; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import com.rj.service.IFileUploadService; +import com.rj.service.MinIOService; +import com.rj.service.IYhyAudioUploadLogService; +import com.rj.service.IYhyHeartbeatLogService; +import com.rj.service.IYhyDatatypeLogService; +import com.rj.entity.YhyAudioUploadLog; +import com.rj.entity.YhyDatatypeLog; +import com.rj.common.TimeZoneUtils; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -21,6 +31,9 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.util.UUID; +import java.util.Enumeration; import java.util.HashMap; import java.util.Map; @@ -44,6 +57,368 @@ public class AudioFileController { @Value("${app.audio.upload.path:uploads/audio}") private String uploadPath; + + @Value("${app.audio.upload.yihangyi.path:/usr/local/tomcat/webapps/yihangyi}") + private String yihangyiUploadPath; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private MinIOService minIOService; + + @Autowired + private IYhyAudioUploadLogService yhyAudioUploadLogService; + + @Autowired + private IYhyHeartbeatLogService heartbeatLogService; + + @Autowired + private IYhyDatatypeLogService yhyDatatypeLogService; + + + /** + * v1 推送回调接口 + * 支持两种请求方式: + * 1. JSON格式:包含文件路径等信息,需要从指定路径读取文件 + * 2. multipart/form-data格式:包含实际文件和JSON数据 + * + * 参考C#实现:接收任意JSON对象,打印headers和body + * + * @param requestData JSON数据字符串(当Content-Type为multipart/form-data时,通过data参数传递) + * @param file 上传的文件(可选,当Content-Type为multipart/form-data时) + * @param request HttpServletRequest + * @return 响应结果 + */ + @PostMapping(value = "/v1/callback", consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.ALL_VALUE}) + @Operation(summary = "推送回调接口v1", description = "接收推送回调请求,支持JSON和multipart/form-data格式,自动处理文件上传") + public ResponseEntity> yhyV1Push( + @Parameter(description = "JSON数据字符串(multipart/form-data格式时使用)") + @RequestParam(value = "data", required = false) String requestData, + @Parameter(description = "上传的文件(multipart/form-data格式时使用)") + @RequestParam(value = "file", required = false) MultipartFile file, + HttpServletRequest request) { + log.info("接收推送回调请求,yuehangyi,20251129,requestData:{}",requestData); + Map result = new HashMap<>(); + + try { + // 获取并序列化请求头(参考C#代码:打印headers) + Map headers = new HashMap<>(); + Enumeration headerNames = request.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String headerName = headerNames.nextElement(); + headers.put(headerName, request.getHeader(headerName)); + } + String headersJson = objectMapper.writeValueAsString(headers); + log.info("headers: {}", headersJson); + System.out.println("headers: " + headersJson); // 参考C#的Console.WriteLine + + // 解析请求数据 + Map requestBody = null; + String contentType = request.getContentType(); + log.info("Content-Type: {}", contentType); + + if (contentType != null && contentType.contains(MediaType.MULTIPART_FORM_DATA_VALUE)) { + // multipart/form-data格式 + log.info("检测到multipart/form-data格式请求"); + + // 检查并获取文件参数 + log.info("检查文件参数,file是否为null: {}", file == null); + System.out.println("检查文件参数,file是否为null: " + (file == null)); + + // 无论file是否为空,都尝试从request中获取文件(确保能获取到) + if (request instanceof org.springframework.web.multipart.MultipartHttpServletRequest) { + System.out.println("request是MultipartHttpServletRequest类型"); + org.springframework.web.multipart.MultipartHttpServletRequest multipartRequest = + (org.springframework.web.multipart.MultipartHttpServletRequest) request; + + // 获取所有文件参数名 + java.util.Iterator fileNames = multipartRequest.getFileNames(); + java.util.List fileParamNames = new java.util.ArrayList<>(); + while (fileNames.hasNext()) { + fileParamNames.add(fileNames.next()); + } + log.info("multipart请求中的文件参数名列表: {}", fileParamNames); + System.out.println("multipart请求中的文件参数名列表: " + fileParamNames); + + // 如果file为空,尝试从request中获取第一个文件 + if (file == null || file.isEmpty()) { + System.out.println("file为空,尝试从request中获取文件"); + if (!fileParamNames.isEmpty()) { + String paramName = fileParamNames.get(0); + file = multipartRequest.getFile(paramName); + log.info("从multipart请求中获取到文件,参数名: {}, 文件名: {}, 大小: {} bytes", + paramName, file != null ? file.getOriginalFilename() : "null", + file != null ? file.getSize() : 0); + System.out.println("从multipart请求中获取到文件,参数名: " + paramName + + ", 文件名: " + (file != null ? file.getOriginalFilename() : "null") + + ", 大小: " + (file != null ? file.getSize() : 0) + " bytes"); + } else { + log.warn("multipart请求中未找到任何文件参数"); + System.out.println("multipart请求中未找到任何文件参数"); + } + } else { + log.info("通过@RequestParam获取到文件,文件名: {}, 大小: {} bytes", + file.getOriginalFilename(), file.getSize()); + System.out.println("通过@RequestParam获取到文件,文件名: " + file.getOriginalFilename() + + ", 大小: " + file.getSize() + " bytes"); + } + } else { + log.warn("request不是MultipartHttpServletRequest类型,实际类型: {}", + request.getClass().getName()); + System.out.println("request不是MultipartHttpServletRequest类型,实际类型: " + request.getClass().getName()); + if (file != null && !file.isEmpty()) { + log.info("通过@RequestParam获取到文件,文件名: {}, 大小: {} bytes", + file.getOriginalFilename(), file.getSize()); + } + } + + // 初始化 requestBody + requestBody = new HashMap<>(); + + // 首先获取所有 form-data 参数(包括 dataType 等单独参数) + // 使用 getParameterMap() 获取所有参数,包括 multipart 参数 + Map parameterMap = request.getParameterMap(); + Map formParams = new HashMap<>(); + + for (Map.Entry entry : parameterMap.entrySet()) { + String paramName = entry.getKey(); + String[] paramValues = entry.getValue(); + // 排除 file 参数(文件参数通过 MultipartFile 处理) + if (!"file".equals(paramName) && paramValues != null && paramValues.length > 0) { + // 取第一个值(对于单个值参数) + String paramValue = paramValues[0]; + formParams.put(paramName, paramValue); + log.debug("获取到form-data参数: {} = {}", paramName, paramValue); + } + } + + // 如果存在 data 参数,解析其 JSON 内容并合并到 requestBody + if (requestData != null && !requestData.trim().isEmpty()) { + try { + Map dataMap = objectMapper.readValue(requestData, new TypeReference>() {}); + if (dataMap != null) { + requestBody.putAll(dataMap); + log.info("multipart/form-data中的data参数解析成功,包含 {} 个字段", dataMap.size()); + } + } catch (Exception e) { + log.error("解析multipart/form-data中的data参数失败", e); + // 不直接返回错误,继续处理其他参数 + } + } + + // 将其他单独的参数合并到 requestBody 中(单独参数的优先级更高,会覆盖 data 参数中的同名字段) + for (Map.Entry entry : formParams.entrySet()) { + String paramName = entry.getKey(); + String paramValue = entry.getValue(); + // 如果参数名不是 "data",直接添加到 requestBody + if (!"data".equals(paramName)) { + requestBody.put(paramName, paramValue); + log.debug("将form-data参数 {} = {} 合并到requestBody", paramName, paramValue); + } + } + + log.info("multipart/form-data参数解析完成,requestBody包含 {} 个字段: {}", requestBody.size(), requestBody.keySet()); + + // 如果 requestBody 为空,记录警告但不返回错误(允许空请求体) + if (requestBody.isEmpty()) { + log.warn("multipart/form-data格式请求未找到任何有效参数"); + } + } else { + // application/json格式(参考C#代码:接收任意对象) + log.info("检测到application/json格式请求"); + // 从请求流中读取JSON数据(不使用@RequestBody避免与multipart冲突) + try { + java.io.BufferedReader reader = request.getReader(); + StringBuilder bodyBuilder = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + bodyBuilder.append(line); + } + String bodyString = bodyBuilder.toString(); + if (bodyString != null && !bodyString.trim().isEmpty()) { + log.info("从请求流读取到数据,长度: {}", bodyString.length()); + requestBody = objectMapper.readValue(bodyString, new TypeReference>() {}); + log.info("从请求流解析JSON成功"); + } else { + log.warn("请求体为空"); + requestBody = new HashMap<>(); // 空对象,不返回错误 + } + } catch (Exception e) { + log.warn("从请求流读取数据失败,可能请求体为空: {}", e.getMessage()); + requestBody = new HashMap<>(); // 空对象,不返回错误 + } + } + + // 打印请求体(参考C#代码:打印body) + String bodyJson = objectMapper.writeValueAsString(requestBody); + log.info("body: {}", bodyJson); + System.out.println("body: " + bodyJson); // 参考C#的Console.WriteLine + + // 保存原始数据到 yhy_datatype_log 表(初步解析后保存) + try { + saveDatatypeLog(headers, requestBody); + } catch (Exception e) { + log.error("保存数据到yhy_datatype_log表失败", e); + // 即使保存失败,也不影响后续业务处理,只记录日志 + } + + // 如果请求体为空,只记录日志,不处理业务逻辑 + if (requestBody == null || requestBody.isEmpty()) { + log.info("请求体为空,仅记录日志,不处理业务逻辑"); + result.put("success", true); + result.put("message", "请求接收成功(请求体为空)"); + return ResponseEntity.ok(result); + } + + // 提取dataType字段(新的数据结构) + String dataType = (String) requestBody.get("dataType"); + if (dataType == null || dataType.trim().isEmpty()) { + // 兼容旧的数据结构,尝试从pushDataType获取 + dataType = (String) requestBody.get("pushDataType"); + } + + log.info("检测到数据类型: {}", dataType); + + // 提取设备号 + String deviceNo = (String) requestBody.get("deviceNo"); + + // 根据不同的dataType进行不同的处理 + Map processResult = null; + String localFilePath = null; + + if (dataType == null || dataType.trim().isEmpty()) { + log.warn("未识别到数据类型,跳过业务处理"); + processResult = new HashMap<>(); + processResult.put("success", true); + processResult.put("message", "未识别到数据类型,仅记录日志"); + } else { + switch (dataType) { + + case "Audio": + // 录音推送:保存文件到本地,写入yhy_audio_upload_log表 + // 在处理Audio类型之前,再次检查文件(如果之前没有获取到) + MultipartFile audioFile = file; + if (audioFile == null || audioFile.isEmpty()) { + log.warn("处理Audio类型时文件为空,尝试重新获取"); + if (request instanceof org.springframework.web.multipart.MultipartHttpServletRequest) { + org.springframework.web.multipart.MultipartHttpServletRequest multipartRequest = + (org.springframework.web.multipart.MultipartHttpServletRequest) request; + java.util.Iterator fileNames = multipartRequest.getFileNames(); + if (fileNames.hasNext()) { + String paramName = fileNames.next(); + audioFile = multipartRequest.getFile(paramName); + log.info("重新获取到文件,参数名: {}, 文件名: {}, 大小: {} bytes", + paramName, audioFile != null ? audioFile.getOriginalFilename() : "null", + audioFile != null ? audioFile.getSize() : 0); + } + } + } + processResult = handleAudioDataType(requestBody, audioFile); + if (processResult != null && processResult.get("localFilePath") != null) { + localFilePath = (String) processResult.get("localFilePath"); + } + log.info("检测到数据类型Audio------------------------------: {}", dataType); + break; + + case "HeartbeatLog": + // 心跳日志:写入yhy_heartbeat_log表 + processResult = heartbeatLogService.processHeartbeatReport(requestBody); + break; + + case "Gps": + // GPS日志:待实现 + log.info("收到GPS日志数据,设备号: {}", deviceNo); + processResult = handleGpsDataType(requestBody); + break; + + case "AudioText": + // 语音转写:待实现 + log.info("收到语音转写数据,设备号: {}", deviceNo); + processResult = handleAudioTextDataType(requestBody); + break; + + case "LoginLog": + // 登录日志:待实现 + log.info("收到登录日志数据,设备号: {}", deviceNo); + processResult = handleLoginLogDataType(requestBody); + break; + + case "ControlLog": + // 操控日志:待实现 + log.info("收到操控日志数据,设备号: {}", deviceNo); + processResult = handleControlLogDataType(requestBody); + break; + + case "UploadLog": + // + log.info("收到上传日志数据,设备号: {}", deviceNo); + processResult = handleUploadLogDataType(requestBody, file); + if (processResult != null && processResult.get("localFilePath") != null) { + localFilePath = (String) processResult.get("localFilePath"); + } + break; + + case "DebugLog": + // 运行日志:待实现 + log.info("收到运行日志数据,设备号: {}", deviceNo); + processResult = handleDebugLogDataType(requestBody); + break; + + case "MergeAudio": + // 合并录音:待实现 + log.info("收到合并录音数据,设备号: {}", deviceNo); + processResult = handleMergeAudioDataType(requestBody); + break; + + default: + log.warn("未知的数据类型: {}, 设备号: {}", dataType, deviceNo); + processResult = new HashMap<>(); + processResult.put("success", true); + processResult.put("message", "未知的数据类型: " + dataType); + break; + } + } + + // 构建响应数据 + Map responseData = new HashMap<>(); + responseData.put("deviceNo", deviceNo); + responseData.put("dataType", dataType); + + if (localFilePath != null) { + responseData.put("localFilePath", localFilePath); + } + + if (processResult != null) { + Boolean success = (Boolean) processResult.get("success"); + if (success != null && success) { + result.put("success", true); + result.put("message", processResult.get("message") != null ? + processResult.get("message") : "回调处理成功"); + } else { + result.put("success", false); + result.put("message", processResult.get("message") != null ? + processResult.get("message") : "回调处理失败"); + } + } else { + result.put("success", true); + result.put("message", "回调处理成功"); + } + + result.put("data", responseData); + + log.info("推送回调处理完成,设备号: {}, 数据类型: {}, 本地路径: {}", + deviceNo, dataType, localFilePath); + log.info("响应数据: {}", result.toString()); + return ResponseEntity.ok(result); + + } catch (Exception e) { + log.error("处理推送回调异常", e); + result.put("success", false); + result.put("message", "处理异常: " + e.getMessage()); + return ResponseEntity.internalServerError().body(result); + } + } /** * 上传音频文件 @@ -187,18 +562,17 @@ public class AudioFileController { @GetMapping("/download/{filename:.+}") // 使用 {.+} 匹配包含点的完整文件名 public ResponseEntity serveAudio(@PathVariable String filename) throws IOException { -// 假设你的音频文件存储在这个目录 - Path audioLocation = Paths.get("path/to/your/audio/files"); // 1. 构建文件路径,防止路径遍历攻击 -// Path filePath = audioLocation.resolve(filename).normalize(); -// if (!filePath.startsWith(audioLocation)) { -// return ResponseEntity.badRequest().build(); // 安全检查 -// } + // Path audioLocation = Paths.get("path/to/your/audio/files"); + // Path filePath = audioLocation.resolve(filename).normalize(); + // if (!filePath.startsWith(audioLocation)) { + // return ResponseEntity.badRequest().build(); // 安全检查 + // } // 2. 检查文件是否存在 -// if (!Files.exists(filePath) || !Files.isRegularFile(filePath)) { -// return ResponseEntity.notFound().build(); -// } + // if (!Files.exists(filePath) || !Files.isRegularFile(filePath)) { + // return ResponseEntity.notFound().build(); + // } // 3. 创建 Resource 对象 Resource resource = new UrlResource(filename); @@ -285,6 +659,294 @@ public class AudioFileController { return false; } + /** + * 处理音频文件上传 + * 包括:保存文件到本地、保存信息到数据库 + * + * @param requestBody 请求体数据 + * @param processedData 处理后的数据 + * @param file 上传的文件 + * @param deviceNo 设备号 + * @param filePath 文件路径 + * @return 处理结果,包含success、message、localFilePath等 + */ + private Map handleAudioUpload(Map requestBody, + Map processedData, + MultipartFile file, + String deviceNo, + String filePath) { + Map result = new HashMap<>(); + + try { + // 1. 验证是否有文件上传 + if (file == null || file.isEmpty()) { + log.warn("文件参数为空或文件为空,file: {}, isEmpty: {}", + file == null ? "null" : "not null", + file != null ? file.isEmpty() : "N/A"); + result.put("success", false); + result.put("message", "音频文件上传失败:未检测到文件"); + return result; + } + + log.info("开始处理音频文件上传,文件名: {}, 大小: {} bytes, ContentType: {}", + file.getOriginalFilename(), file.getSize(), file.getContentType()); + + // 2. 确定文件名 + String fileName = file.getOriginalFilename(); + if (processedData != null && processedData.get("fileName") != null) { + fileName = (String) processedData.get("fileName"); + } else if (filePath != null && !filePath.isEmpty()) { + fileName = filePath.substring(filePath.lastIndexOf("/") + 1); + } + + // 3. 保存文件到本地 + String localFilePath; + try { + localFilePath = saveAudioFileToLocal(file, fileName, processedData, deviceNo); + log.info("音频文件保存到本地成功,路径: {}", localFilePath); + } catch (Exception e) { + log.error("音频文件保存到本地失败", e); + result.put("success", false); + result.put("message", "音频文件保存到本地失败: " + e.getMessage()); + return result; + } + + // 4. 保存信息到数据库 + try { + saveAudioUploadLogToDatabase(requestBody, processedData, localFilePath, file); + log.info("音频上传日志保存到数据库成功"); + } catch (Exception e) { + log.error("音频上传日志保存到数据库失败", e); + // 即使数据库保存失败,也不影响文件保存,只记录日志 + // 可以选择是否返回错误,这里选择继续处理 + } + + // 5. 返回成功结果 + result.put("success", true); + result.put("message", "音频文件上传处理成功"); + result.put("localFilePath", localFilePath); + result.put("fileName", fileName); + + return result; + + } catch (Exception e) { + log.error("处理音频文件上传异常", e); + result.put("success", false); + result.put("message", "处理音频文件上传异常: " + e.getMessage()); + return result; + } + } + + /** + * 保存音频文件到本地 + * + * @param file 上传的文件 + * @param fileName 文件名 + * @param processedData 处理后的数据 + * @param deviceNo 设备号 + * @return 保存后的文件路径(相对路径) + * @throws IOException 文件保存异常 + */ + private String saveAudioFileToLocal(MultipartFile file, String fileName, + Map processedData, String deviceNo) throws IOException { + // 验证文件不为空 + if (file == null || file.isEmpty()) { + throw new IllegalArgumentException("文件不能为空"); + } + + // 确定保存路径: + // 1. 优先使用processedData中的savePath + // 2. 如果是文件上传请求,使用yihangyi路径 + // 3. 否则使用配置的uploadPath + String baseSavePath = uploadPath; + + if (processedData != null && processedData.get("savePath") != null) { + String savePathFromData = (String) processedData.get("savePath"); + if (savePathFromData != null && !savePathFromData.trim().isEmpty()) { + baseSavePath = savePathFromData; + } + } else { + // 当接收到文件上传请求时,使用yihangyi目录 + baseSavePath = yihangyiUploadPath; + log.info("检测到文件上传请求,使用yihangyi保存路径: {}", baseSavePath); + } + + // 确保上传目录存在 + Path uploadDir = Paths.get(baseSavePath); + log.info("准备保存文件到目录: {} (绝对路径: {})", baseSavePath, uploadDir.toAbsolutePath()); + + // 检查目录是否存在 + if (!Files.exists(uploadDir)) { + try { + Files.createDirectories(uploadDir); + log.info("创建上传目录成功: {}", uploadDir.toAbsolutePath()); + } catch (Exception e) { + log.error("创建上传目录失败: {}", uploadDir.toAbsolutePath(), e); + throw new IOException("无法创建上传目录: " + uploadDir.toAbsolutePath(), e); + } + } else { + log.info("上传目录已存在: {}", uploadDir.toAbsolutePath()); + } + + // 验证目录是否可写 + if (!Files.isWritable(uploadDir)) { + log.error("上传目录不可写: {}", uploadDir.toAbsolutePath()); + throw new IOException("上传目录不可写: " + uploadDir.toAbsolutePath()); + } + + // 保存文件(使用原始文件名) + Path filePath = uploadDir.resolve(fileName); + + // 如果文件已存在,先删除(可选,根据业务需求决定) + if (Files.exists(filePath)) { + log.warn("文件已存在,将被覆盖: {}", filePath.toAbsolutePath()); + Files.delete(filePath); + } + + // 保存文件 + long bytesCopied = Files.copy(file.getInputStream(), filePath); + log.info("文件复制完成,复制字节数: {}, 文件路径: {}", bytesCopied, filePath.toAbsolutePath()); + + // 验证文件是否真的存在 + if (!Files.exists(filePath)) { + throw new IOException("文件保存失败:文件不存在于路径 " + filePath.toAbsolutePath()); + } + + // 验证文件大小 + long fileSize = Files.size(filePath); + if (fileSize != file.getSize()) { + log.warn("文件大小不匹配,期望: {} bytes, 实际: {} bytes", file.getSize(), fileSize); + } + + // 验证文件是否可读 + if (!Files.isReadable(filePath)) { + log.warn("文件不可读: {}", filePath.toAbsolutePath()); + } + + // 获取文件的完整信息 + try { + java.nio.file.attribute.BasicFileAttributes attrs = Files.readAttributes(filePath, + java.nio.file.attribute.BasicFileAttributes.class); + log.info("文件保存成功并验证通过,路径: {}, 大小: {} bytes, 创建时间: {}, 修改时间: {}", + filePath.toAbsolutePath(), fileSize, attrs.creationTime(), attrs.lastModifiedTime()); + } catch (Exception e) { + log.warn("无法读取文件属性: {}", e.getMessage()); + } + + // 延迟验证:等待一小段时间后再次检查文件是否存在(防止文件系统延迟) + try { + Thread.sleep(100); // 等待100毫秒 + if (!Files.exists(filePath)) { + log.error("延迟验证失败:文件在保存后消失了!路径: {}", filePath.toAbsolutePath()); + throw new IOException("文件保存后验证失败:文件不存在于路径 " + filePath.toAbsolutePath()); + } + log.info("延迟验证通过:文件仍然存在"); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.warn("延迟验证被中断"); + } + + // 返回绝对路径 + String absolutePath = filePath.toAbsolutePath().toString(); + log.info("最终返回的文件路径: {}", absolutePath); + return absolutePath; + } + + /** + * 保存音频上传日志到数据库 + * + * @param requestBody 请求体数据 + * @param processedData 处理后的数据 + * @param localFilePath 本地文件路径 + * @param file 上传的文件(可能为null) + */ + + private void saveAudioUploadLogToDatabase(Map requestBody, + Map processedData, + String localFilePath, + MultipartFile file) { + try { + + YhyAudioUploadLog audioUploadLog = new YhyAudioUploadLog(); + audioUploadLog.setId(UUID.randomUUID().toString()); + log.info("保存音频上传日志开始,requestBody: {}",requestBody.toString()); + // 从requestBody中提取数据 + audioUploadLog.setFilePath((String) requestBody.get("filePath")); + audioUploadLog.setContentType((String) requestBody.get("contentType")); + audioUploadLog.setDeviceNo((String) requestBody.get("deviceNo")); + audioUploadLog.setServerUrl((String) requestBody.get("serverUrl")); +// audioUploadLog.setPushDataType((String) requestBody.get("dataType")); + audioUploadLog.setPushDataType("Audio"); + // 从processedData中提取数据 + if (processedData != null) { + audioUploadLog.setFileName((String) processedData.get("fileName")); + audioUploadLog.setSavePath((String) processedData.get("savePath")); + audioUploadLog.setChunkIndex((String) processedData.get("chunkIndex")); + audioUploadLog.setStartTime((String) processedData.get("startTime")); + audioUploadLog.setEndTime((String) processedData.get("endTime")); + audioUploadLog.setUsrNo((String) processedData.get("usrNo")); + audioUploadLog.setDeviceType((String) processedData.get("deviceType")); + + // 处理hasBody字段 + Object hasBodyObj = processedData.get("hasBody"); + if (hasBodyObj != null) { + if (hasBodyObj instanceof Boolean) { + audioUploadLog.setHasBody((Boolean) hasBodyObj); + } else if (hasBodyObj instanceof String) { + audioUploadLog.setHasBody(Boolean.parseBoolean((String) hasBodyObj)); + } + } + + // 处理extended字段(JSON格式) + Object extendedObj = processedData.get("extended"); + if (extendedObj != null) { + try { + audioUploadLog.setExtended(objectMapper.writeValueAsString(extendedObj)); + } catch (Exception e) { + log.warn("序列化extended字段失败", e); + audioUploadLog.setExtended(extendedObj.toString()); + } + } + + audioUploadLog.setUserId((String) processedData.get("userId")); + audioUploadLog.setUserName((String) processedData.get("userName")); + audioUploadLog.setUserPhone((String) processedData.get("userPhone")); + audioUploadLog.setUserDept((String) processedData.get("userDept")); + } + + // 设置本地文件路径(如果文件已保存) + if (localFilePath != null && !localFilePath.isEmpty()) { + // 如果savePath为空,使用localFilePath的目录部分 + if (audioUploadLog.getSavePath() == null || audioUploadLog.getSavePath().isEmpty()) { + int lastSeparator = localFilePath.lastIndexOf(File.separator); + if (lastSeparator > 0) { + audioUploadLog.setSavePath(localFilePath.substring(0, lastSeparator)); + } + } + } + + // 设置同步阶段(初始状态) + audioUploadLog.setSyncStage("已上传"); + + // 设置创建和更新时间 + LocalDateTime now = LocalDateTime.now(); + audioUploadLog.setCreateTime(now); + audioUploadLog.setUpdateTime(now); + + // 保存到数据库 + yhyAudioUploadLogService.save(audioUploadLog); + log.info("音频上传日志保存成功,ID: {}, 设备号: {}, 文件名: {}", + audioUploadLog.getId(), audioUploadLog.getDeviceNo(), audioUploadLog.getFileName()); + + } catch (Exception e) { + log.error("保存音频上传日志到数据库失败", e); + throw new RuntimeException("保存音频上传日志失败: " + e.getMessage(), e); + } finally { + // 请求处理完成后清除标记(在方法返回前清除) + // 注意:这里不清除,让它在请求处理完成后由框架自动清理 + } + } + /** * 获取文件扩展名 */ @@ -326,4 +988,315 @@ public class AudioFileController { return "application/octet-stream"; } } + + /** + * 处理Audio数据类型(录音推送) + * 适配新的数据结构:dataType="Audio",数据在data字段中 + */ + private Map handleAudioDataType(Map requestBody, MultipartFile file) { + Map result = new HashMap<>(); + + try { + log.info("处理Audio数据类型(录音推送)"); + // 提取data字段 + Object dataObj = requestBody.get("data"); + Map dataMap = null; + if (dataObj != null) { + dataMap = objectMapper.convertValue(dataObj, new TypeReference>() {}); + } + + // 如果没有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); + + } catch (Exception e) { + log.error("处理Audio数据类型异常", e); + result.put("success", false); + result.put("message", "处理Audio数据类型异常: " + e.getMessage()); + return result; + } + } + + /** + * 处理Gps数据类型(GPS日志) + */ + private Map handleGpsDataType(Map requestBody) { + Map result = new HashMap<>(); + log.info("GPS日志处理功能待实现,数据: {}", requestBody); + result.put("success", true); + result.put("message", "GPS日志接收成功(处理功能待实现)"); + return result; + } + + /** + * 处理AudioText数据类型(语音转写) + */ + private Map handleAudioTextDataType(Map requestBody) { + Map result = new HashMap<>(); + log.info("语音转写处理功能待实现,数据: {}", requestBody); + result.put("success", true); + result.put("message", "语音转写接收成功(处理功能待实现)"); + return result; + } + + /** + * 处理LoginLog数据类型(登录日志) + */ + private Map handleLoginLogDataType(Map requestBody) { + Map result = new HashMap<>(); + log.info("登录日志处理功能待实现,数据: {}", requestBody); + result.put("success", true); + result.put("message", "登录日志接收成功(处理功能待实现)"); + return result; + } + + /** + * 处理ControlLog数据类型(操控日志) + */ + private Map handleControlLogDataType(Map requestBody) { + Map result = new HashMap<>(); + log.info("操控日志处理功能待实现,数据: {}", requestBody); + result.put("success", true); + result.put("message", "操控日志接收成功(处理功能待实现)"); + return result; + } + + /** + * 处理UploadLog数据类型(上传日志) + * 适配新的数据结构:dataType="UploadLog",数据在data字段中 + * 功能:保存文件到本地(如果有),写入yhy_audio_upload_log表 + */ + private Map handleUploadLogDataType(Map requestBody, MultipartFile file) { + Map result = new HashMap<>(); + + log.info("处理UploadLog数据类型(上传日志)"); + return result; + } + + /** + * 保存上传日志到数据库 + * + * @param requestBody 请求体数据 + * @param processedData 处理后的数据(从data字段解析) + * @param localFilePath 本地文件路径(如果文件已保存) + * @param file 上传的文件(可能为null) + * @param deviceNo 设备号 + */ + private void saveUploadLogToDatabase(Map requestBody, + Map processedData, + String localFilePath, + MultipartFile file, + String deviceNo) { + try { + YhyAudioUploadLog audioUploadLog = new YhyAudioUploadLog(); + audioUploadLog.setId(UUID.randomUUID().toString()); + + // 从requestBody中提取数据 + audioUploadLog.setFilePath((String) requestBody.get("filePath")); + audioUploadLog.setContentType((String) requestBody.get("contentType")); + audioUploadLog.setDeviceNo(deviceNo); + audioUploadLog.setServerUrl((String) requestBody.get("serverUrl")); + audioUploadLog.setPushDataType("UploadLog"); + + // 从processedData中提取数据(data字段中的内容) + if (processedData != null) { + audioUploadLog.setFileName((String) processedData.get("fileName")); + audioUploadLog.setSavePath((String) processedData.get("savePath")); + audioUploadLog.setChunkIndex((String) processedData.get("chunkIndex")); + audioUploadLog.setStartTime((String) processedData.get("startTime")); + audioUploadLog.setEndTime((String) processedData.get("endTime")); + audioUploadLog.setUsrNo((String) processedData.get("usrNo")); + audioUploadLog.setDeviceType((String) processedData.get("deviceType")); + + // 处理hasBody字段 + Object hasBodyObj = processedData.get("hasBody"); + if (hasBodyObj != null) { + if (hasBodyObj instanceof Boolean) { + audioUploadLog.setHasBody((Boolean) hasBodyObj); + } else if (hasBodyObj instanceof String) { + audioUploadLog.setHasBody(Boolean.parseBoolean((String) hasBodyObj)); + } + } + + // 处理extended字段(JSON格式),包含realStartTime、realEndTime、lengthBytes、createdTime等 + Map extendedMap = new HashMap<>(); + if (processedData.get("realStartTime") != null) { + extendedMap.put("realStartTime", processedData.get("realStartTime")); + } + if (processedData.get("realEndTime") != null) { + extendedMap.put("realEndTime", processedData.get("realEndTime")); + } + if (processedData.get("lengthBytes") != null) { + extendedMap.put("lengthBytes", processedData.get("lengthBytes")); + } + if (processedData.get("createdTime") != null) { + extendedMap.put("createdTime", processedData.get("createdTime")); + } + // 如果有其他扩展字段,也添加到extended中 + if (!extendedMap.isEmpty()) { + try { + audioUploadLog.setExtended(objectMapper.writeValueAsString(extendedMap)); + } catch (Exception e) { + log.warn("序列化extended字段失败", e); + } + } + + audioUploadLog.setUserId((String) processedData.get("userId")); + audioUploadLog.setUserName((String) processedData.get("userName")); + audioUploadLog.setUserPhone((String) processedData.get("userPhone")); + audioUploadLog.setUserDept((String) processedData.get("userDept")); + } + + // 设置本地文件路径(如果文件已保存) + if (localFilePath != null && !localFilePath.isEmpty()) { + // 如果savePath为空,使用localFilePath的目录部分 + if (audioUploadLog.getSavePath() == null || audioUploadLog.getSavePath().isEmpty()) { + int lastSeparator = localFilePath.lastIndexOf(File.separator); + if (lastSeparator > 0) { + audioUploadLog.setSavePath(localFilePath.substring(0, lastSeparator)); + } + } + } + + // 设置同步阶段(初始状态) + audioUploadLog.setSyncStage("已上传"); + + // 设置创建和更新时间 + LocalDateTime now = LocalDateTime.now(); + audioUploadLog.setCreateTime(now); + audioUploadLog.setUpdateTime(now); + + // 保存到数据库 + yhyAudioUploadLogService.save(audioUploadLog); + log.info("上传日志保存成功,ID: {}, 设备号: {}, 文件名: {}", + audioUploadLog.getId(), audioUploadLog.getDeviceNo(), audioUploadLog.getFileName()); + + } catch (Exception e) { + log.error("保存上传日志到数据库失败", e); + throw new RuntimeException("保存上传日志失败: " + e.getMessage(), e); + } + } + + /** + * 处理DebugLog数据类型(运行日志) + */ + private Map handleDebugLogDataType(Map requestBody) { + Map result = new HashMap<>(); + log.info("运行日志处理功能待实现,数据: {}", requestBody); + result.put("success", true); + result.put("message", "运行日志接收成功(处理功能待实现)"); + return result; + } + + /** + * 处理MergeAudio数据类型(合并录音) + */ + private Map handleMergeAudioDataType(Map requestBody) { + Map result = new HashMap<>(); + log.info("合并录音处理功能待实现,数据: {}", requestBody); + result.put("success", true); + result.put("message", "合并录音接收成功(处理功能待实现)"); + return result; + } + + /** + * 保存数据到 yhy_datatype_log 表 + * 将接收到的所有数据进行初步解析后保存 + * + * @param headers 请求头信息 + * @param requestBody 请求体数据 + */ + private void saveDatatypeLog(Map headers, Map requestBody) { + try { + YhyDatatypeLog datatypeLog = new YhyDatatypeLog(); + datatypeLog.setId(UUID.randomUUID().toString()); + + // 提取 dataType(优先使用 dataType,兼容 pushDataType) + String dataType = null; + if (requestBody != null) { + dataType = (String) requestBody.get("dataType"); + if (dataType == null || dataType.trim().isEmpty()) { + dataType = (String) requestBody.get("pushDataType"); + } + } + + // 如果 dataType 仍为 null,根据请求头或请求内容推断默认值 + if (dataType == null || dataType.trim().isEmpty()) { + // 根据 content-type 判断请求类型 + if (headers != null) { + String contentType = headers.get("content-type"); + if (contentType != null) { + if (contentType.toLowerCase().contains("multipart/form-data")) { + // 文件上传请求 + dataType = "UploadLog"; + } else if (contentType.toLowerCase().contains("application/json")) { + // JSON 请求 + dataType = "DataLog"; + } else { + // 其他类型 + dataType = "Unknown"; + } + } else { + // 没有 content-type,使用默认值 + dataType = "Unknown"; + } + } else { + // 没有 headers,使用默认值 + dataType = "Unknown"; + } + } + + datatypeLog.setDataType(dataType); + + // 提取 deviceNo + String deviceNo = null; + if (requestBody != null) { + deviceNo = (String) requestBody.get("deviceNo"); + } + datatypeLog.setDeviceNo(deviceNo); + + // 将 headers 和 requestBody 合并序列化为 JSON 字符串保存到 contents + Map contentsMap = new HashMap<>(); + if (headers != null && !headers.isEmpty()) { + contentsMap.put("headers", headers); + } + if (requestBody != null && !requestBody.isEmpty()) { + contentsMap.put("body", requestBody); + } + + String contentsJson = objectMapper.writeValueAsString(contentsMap); + datatypeLog.setContents(contentsJson); + + // 设置创建和更新时间(使用配置的时区) + LocalDateTime now = TimeZoneUtils.now(); + datatypeLog.setCreateTime(now); + datatypeLog.setUpdateTime(now); + + // 保存到数据库 + yhyDatatypeLogService.save(datatypeLog); + log.info("数据已保存到yhy_datatype_log表,ID: {}, 数据类型: {}, 设备号: {}", + datatypeLog.getId(), dataType, deviceNo); + + } catch (Exception e) { + log.error("保存数据到yhy_datatype_log表异常", e); + throw new RuntimeException("保存数据到yhy_datatype_log表失败: " + e.getMessage(), e); + } + } } diff --git a/src/main/java/com/rj/controller/AudioManagementController.java b/src/main/java/com/rj/controller/AudioManagementController.java index 4df6588..26e7466 100644 --- a/src/main/java/com/rj/controller/AudioManagementController.java +++ b/src/main/java/com/rj/controller/AudioManagementController.java @@ -15,6 +15,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; +import org.apache.tika.utils.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -301,24 +302,16 @@ public class AudioManagementController { @GetMapping("/list") @Operation(summary = "分页查询录音列表", description = "分页查询录音信息列表") public ResponseEntity> getAudioList( - @Parameter(description = "页码", example = "1") - @RequestParam(defaultValue = "1") Integer current, - @Parameter(description = "每页大小", example = "10") - @RequestParam(defaultValue = "10") Integer size, - @Parameter(description = "录音名称(模糊查询)") - @RequestParam(required = false) String recordingName, - @Parameter(description = "客户手机号(模糊查询)") - @RequestParam(required = false) String customerPhone, - @Parameter(description = "客户姓名(模糊查询)") - @RequestParam(required = false) String customerName, - @Parameter(description = "所属销售姓名(模糊查询)") - @RequestParam(required = false) String salesName, - @Parameter(description = "所属门店ID") - @RequestParam(required = false) String dealershipId, - @Parameter(description = "意向级别") - @RequestParam(required = false) String intentionLevel, - @Parameter(description = "销售人员电话(模糊查询)") - @RequestParam(required = false) String salesPhone) { + @Parameter(description = "页码", example = "1") @RequestParam(defaultValue = "1") Integer current, + @Parameter(description = "每页大小", example = "10") @RequestParam(defaultValue = "10") Integer size, + @Parameter(description = "录音名称(模糊查询)") @RequestParam(required = false) String recordingName, + @Parameter(description = "客户手机号(模糊查询)") @RequestParam(required = false) String customerPhone, + @Parameter(description = "客户姓名(模糊查询)") @RequestParam(required = false) String customerName, + @Parameter(description = "所属销售姓名(模糊查询)") @RequestParam(required = false) String salesName, + @Parameter(description = "所属门店ID") @RequestParam(required = false) String dealershipId, + @Parameter(description = "意向级别") @RequestParam(required = false) String intentionLevel, + @Parameter(description = "同步状态") @RequestParam(required = false) String syncStatus, + @Parameter(description = "销售人员电话(模糊查询)") @RequestParam(required = false) String salesPhone) { Map result = new HashMap<>(); try { Page page = new Page<>(current, size); @@ -346,6 +339,9 @@ public class AudioManagementController { if (salesPhone != null && !salesPhone.trim().isEmpty()) { queryWrapper.like(AudioManagement::getSalesPhone, salesPhone); } + if (syncStatus != null && !syncStatus.trim().isEmpty()) { + queryWrapper.eq(AudioManagement::getSyncStatus, syncStatus); + } // 按创建时间倒序排列 queryWrapper.orderByDesc(AudioManagement::getUpdateTime); @@ -367,7 +363,9 @@ public class AudioManagementController { audioPage.getRecords().forEach(audio -> { audio.setDealershipName(dealershipNameMap.getOrDefault(audio.getDealershipId(), "")); - audio.setSalesName(saleNameMap.getOrDefault(audio.getSalesId(), "")); + if (StringUtils.isEmpty( audio.getSalesName())) { + audio.setSalesName(saleNameMap.getOrDefault(audio.getSalesId(), "")); + } audio.setSalesPhone(salePhoneMap.getOrDefault(audio.getSalesId(), "")); audio.setProjectName(projectNameMap.getOrDefault(audio.getProjectId(), "")); // audio.setCustomerName(customerNameMap.getOrDefault(audio.getCustomerId(), "")); diff --git a/src/main/java/com/rj/controller/CustomerManagementController.java b/src/main/java/com/rj/controller/CustomerManagementController.java index 880e037..b8d9268 100644 --- a/src/main/java/com/rj/controller/CustomerManagementController.java +++ b/src/main/java/com/rj/controller/CustomerManagementController.java @@ -2,14 +2,18 @@ package com.rj.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.rj.common.AudioManagementConstants; import com.rj.common.DataEnrichmentUtil; +import com.rj.entity.AudioManagement; import com.rj.entity.CustomerManagement; +import com.rj.service.IAudioManagementService; import com.rj.service.ICustomerManagementService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import java.time.LocalDateTime; @@ -17,6 +21,7 @@ import java.time.format.DateTimeFormatter; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.UUID; /** *

@@ -34,27 +39,58 @@ public class CustomerManagementController { @Autowired private ICustomerManagementService customerManagementService; + @Autowired + private IAudioManagementService audioManagementService; + /** * 新增客户 */ @PostMapping("/add") @Operation(summary = "新增客户", description = "添加新的客户信息") + @Transactional(rollbackFor = Exception.class) public ResponseEntity> addCustomer( @Parameter(description = "客户信息", required = true) @RequestBody CustomerManagement customerManagement) { Map result = new HashMap<>(); try { - customerManagement.setCreateTime(LocalDateTime.now()); - customerManagement.setUpdateTime(LocalDateTime.now()); - boolean success = customerManagementService.save(customerManagement); + boolean isUpdate = customerManagement.getId() != null && !customerManagement.getId().trim().isEmpty(); + LocalDateTime now = LocalDateTime.now(); + String operationType = customerManagement.getOperationType(); + + if (isUpdate) { + CustomerManagement existing = customerManagementService.getById(customerManagement.getId()); + if (existing == null) { + result.put("success", false); + result.put("message", "客户不存在,无法更新"); + return ResponseEntity.badRequest().body(result); + } + customerManagement.setCreateTime(existing.getCreateTime()); + customerManagement.setUpdateTime(now); + } else { + customerManagement.setCreateTime(now); + customerManagement.setUpdateTime(now); + } + + boolean success = customerManagementService.saveOrUpdate(customerManagement); if (success) { + if ("start".equalsIgnoreCase(operationType)) { + if (customerManagement.getId() == null || customerManagement.getId().trim().isEmpty()) { + throw new IllegalStateException("客户ID为空,无法同步录音记录"); + } + AudioManagement audioRecord = buildAudioRecordFromCustomer(customerManagement, now); + boolean audioSaved = audioManagementService.save(audioRecord); + if (!audioSaved) { + throw new IllegalStateException("录音管理数据保存失败"); + } + result.put("audioRecordId", audioRecord.getId()); + } result.put("success", true); - result.put("message", "客户添加成功"); + result.put("message", isUpdate ? "客户信息更新成功" : "客户添加成功"); result.put("data", customerManagement); return ResponseEntity.ok(result); } else { result.put("success", false); - result.put("message", "客户添加失败"); + result.put("message", isUpdate ? "客户信息更新失败" : "客户添加失败"); return ResponseEntity.badRequest().body(result); } } catch (Exception e) { @@ -103,7 +139,9 @@ public class CustomerManagementController { Map result = new HashMap<>(); try { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - queryWrapper.eq(CustomerManagement::getContact, contact); + if (contact != null && !contact.trim().isEmpty()) { + queryWrapper.like(CustomerManagement::getContact, contact.trim()); + } List customers = customerManagementService.list(queryWrapper); result.put("success", true); result.put("message", "查询成功"); @@ -363,4 +401,31 @@ public class CustomerManagementController { return ResponseEntity.internalServerError().body(result); } } + + private AudioManagement buildAudioRecordFromCustomer(CustomerManagement customer, LocalDateTime now) { + AudioManagement audio = new AudioManagement(); + audio.setId(UUID.randomUUID().toString()); + String recordingName = customer.getCustomerName() != null && !customer.getCustomerName().trim().isEmpty() + ? customer.getCustomerName() + "接待记录" + : "客户接待记录"; + audio.setRecordingName(recordingName); + audio.setRecordingTime(now); + audio.setUploadTime(now); + audio.setCreateTime(now); + audio.setUpdateTime(now); + audio.setCustomerId(customer.getId()); + audio.setCustomerName(customer.getCustomerName()); + audio.setCustomerPhone(customer.getContact()); + audio.setSalesId(customer.getSalesId()); + audio.setSalesName(customer.getSalesName()); + audio.setSalesPhone(customer.getSalesPhone()); + audio.setDealershipId(customer.getDealershipId()); + audio.setDealershipName(customer.getDealershipName()); + audio.setIntentionLevel(customer.getIntendedModel()); + audio.setRemarks(customer.getRemark()); + audio.setInfoCarddescription(customer.getInfoCard()); + audio.setCompanyType(customer.getCustomerSource()); + audio.setSyncStatus(AudioManagementConstants.SYNC_STATUS_IN_SERVICE); + return audio; + } } diff --git a/src/main/java/com/rj/controller/YhyAudioUploadLogController.java b/src/main/java/com/rj/controller/YhyAudioUploadLogController.java new file mode 100644 index 0000000..9793d7c --- /dev/null +++ b/src/main/java/com/rj/controller/YhyAudioUploadLogController.java @@ -0,0 +1,161 @@ +package com.rj.controller; + +import com.rj.entity.YhyAudioUploadLog; +import com.rj.service.IYhyAudioUploadLogService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +/** + *

+ * 音频上传日志表 前端控制器 + *

+ * + * @author Auto Generated + * @since 2025-01-29 + */ +@RestController +@RequestMapping("/api/yhyAudioUploadLog") +@Tag(name = "音频上传日志", description = "音频上传日志相关接口") +@Slf4j +public class YhyAudioUploadLogController { + + @Autowired + private IYhyAudioUploadLogService yhyAudioUploadLogService; + + /** + * 分页查询音频上传日志列表 + */ + @GetMapping("/list") + @Operation(summary = "分页查询音频上传日志列表", description = "分页查询音频上传日志信息列表,支持按设备号和开始时间、结束时间查询") + public ResponseEntity> getYhyAudioUploadLogList( + @Parameter(description = "页码", example = "1") + @RequestParam(defaultValue = "1") Integer current, + @Parameter(description = "每页大小", example = "10") + @RequestParam(defaultValue = "10") Integer size, + @Parameter(description = "设备号(精确查询)") + @RequestParam(required = false) String deviceNo, + @Parameter(description = "开始时间(格式:251129185131)") + @RequestParam(required = false) String startTime, + @Parameter(description = "结束时间(格式:251129190131)") + @RequestParam(required = false) String endTime, + @Parameter(description = "创建开始时间", example = "2025-01-01 00:00:00") + @RequestParam(required = false) String createStartTime, + @Parameter(description = "创建结束时间", example = "2025-12-31 23:59:59") + @RequestParam(required = false) String createEndTime) { + Map result = yhyAudioUploadLogService.getYhyAudioUploadLogList( + current, size, deviceNo, startTime, endTime, createStartTime, createEndTime); + + Boolean success = (Boolean) result.get("success"); + if (success != null && success) { + return ResponseEntity.ok(result); + } else { + String message = (String) result.get("message"); + if (message != null && message.contains("格式错误")) { + return ResponseEntity.badRequest().body(result); + } + return ResponseEntity.internalServerError().body(result); + } + } + + /** + * 根据ID查询音频上传日志 + */ + @GetMapping("/get/{id}") + @Operation(summary = "根据ID查询音频上传日志", description = "根据音频上传日志ID获取详细信息") + public ResponseEntity> getYhyAudioUploadLogById( + @Parameter(description = "音频上传日志ID", required = true) + @PathVariable String id) { + Map result = yhyAudioUploadLogService.getYhyAudioUploadLogById(id); + + Boolean success = (Boolean) result.get("success"); + if (success != null && success) { + return ResponseEntity.ok(result); + } else { + return ResponseEntity.notFound().build(); + } + } + + /** + * 根据设备号查询音频上传日志列表 + */ + @GetMapping("/getByDeviceNo") + @Operation(summary = "根据设备号查询音频上传日志", description = "根据设备号查询音频上传日志列表") + public ResponseEntity> getYhyAudioUploadLogByDeviceNo( + @Parameter(description = "设备号", required = true) + @RequestParam String deviceNo) { + Map result = yhyAudioUploadLogService.getYhyAudioUploadLogByDeviceNo(deviceNo); + + Boolean success = (Boolean) result.get("success"); + if (success != null && success) { + return ResponseEntity.ok(result); + } else { + return ResponseEntity.internalServerError().body(result); + } + } + + /** + * 新增音频上传日志 + */ + @PostMapping("/add") + @Operation(summary = "新增音频上传日志", description = "添加新的音频上传日志信息") + public ResponseEntity> addYhyAudioUploadLog( + @Parameter(description = "音频上传日志信息", required = true) + @RequestBody YhyAudioUploadLog yhyAudioUploadLog) { + Map result = yhyAudioUploadLogService.addYhyAudioUploadLog(yhyAudioUploadLog); + + Boolean success = (Boolean) result.get("success"); + if (success != null && success) { + return ResponseEntity.ok(result); + } else { + return ResponseEntity.badRequest().body(result); + } + } + + /** + * 更新音频上传日志信息 + */ + @PutMapping("/update") + @Operation(summary = "更新音频上传日志信息", description = "更新音频上传日志详细信息") + public ResponseEntity> updateYhyAudioUploadLog( + @Parameter(description = "音频上传日志信息", required = true) + @RequestBody YhyAudioUploadLog yhyAudioUploadLog) { + Map result = yhyAudioUploadLogService.updateYhyAudioUploadLog(yhyAudioUploadLog); + + Boolean success = (Boolean) result.get("success"); + if (success != null && success) { + return ResponseEntity.ok(result); + } else { + String message = (String) result.get("message"); + if (message != null && message.contains("不能为空")) { + return ResponseEntity.badRequest().body(result); + } + return ResponseEntity.badRequest().body(result); + } + } + + /** + * 根据ID删除音频上传日志 + */ + @DeleteMapping("/delete/{id}") + @Operation(summary = "删除音频上传日志", description = "根据音频上传日志ID删除信息") + public ResponseEntity> deleteYhyAudioUploadLog( + @Parameter(description = "音频上传日志ID", required = true) + @PathVariable String id) { + Map result = yhyAudioUploadLogService.deleteYhyAudioUploadLog(id); + + Boolean success = (Boolean) result.get("success"); + if (success != null && success) { + return ResponseEntity.ok(result); + } else { + return ResponseEntity.badRequest().body(result); + } + } +} + diff --git a/src/main/java/com/rj/controller/yhy/readme/log.java b/src/main/java/com/rj/controller/yhy/readme/log.java new file mode 100644 index 0000000..cbe366f --- /dev/null +++ b/src/main/java/com/rj/controller/yhy/readme/log.java @@ -0,0 +1,13 @@ +package com.rj.controller.yhy.readme; + +/** + * Author: 李中华 wx: spllzh email(qq): 28668817@qq.com + * Date: 2025/11/29 15:28 + * 深圳悦航益科技有限公司 + * 地址:https://iot.yuehangyi.com/ + * 账户:BJYDW + * 密码:aBDd]@Gvy! + **/ +public class log { + +} diff --git a/src/main/java/com/rj/entity/CustomerManagement.java b/src/main/java/com/rj/entity/CustomerManagement.java index 4c14788..d187539 100644 --- a/src/main/java/com/rj/entity/CustomerManagement.java +++ b/src/main/java/com/rj/entity/CustomerManagement.java @@ -89,5 +89,13 @@ public class CustomerManagement implements Serializable { @TableField("contact_count") private Integer contactCount; + @Schema(description = "操作类型") + @TableField("operation_type") + private String operationType; + + @Schema(description = "客户来源") + @TableField("customer_source") + private String customerSource; + } diff --git a/src/main/java/com/rj/entity/YhyAudioUploadLog.java b/src/main/java/com/rj/entity/YhyAudioUploadLog.java new file mode 100644 index 0000000..85f5f67 --- /dev/null +++ b/src/main/java/com/rj/entity/YhyAudioUploadLog.java @@ -0,0 +1,117 @@ +package com.rj.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.baomidou.mybatisplus.annotation.TableField; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + *

+ * 音频上传日志表 + *

+ * + * @author Auto Generated + * @since 2025-01-29 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("yhy_audio_upload_log") +@Schema(description = "音频上传日志表") +public class YhyAudioUploadLog implements Serializable { + + private static final long serialVersionUID = 1L; + + @Schema(description = "主键,UUID") + @TableId("id") + private String id; + + @Schema(description = "文件路径") + @TableField("file_path") + private String filePath; + + @Schema(description = "内容类型(如:audio/mpeg)") + @TableField("content_type") + private String contentType; + + @Schema(description = "设备号") + @TableField("device_no") + private String deviceNo; + + @Schema(description = "服务器回调URL") + @TableField("server_url") + private String serverUrl; + + @Schema(description = "推送数据类型(如:Audio)") + @TableField("push_data_type") + private String pushDataType; + + @Schema(description = "文件名") + @TableField("file_name") + private String fileName; + + @Schema(description = "保存路径") + @TableField("save_path") + private String savePath; + + @Schema(description = "分块索引(如:A001)") + @TableField("chunk_index") + private String chunkIndex; + + @Schema(description = "开始时间(格式:251129185131)") + @TableField("start_time") + private String startTime; + + @Schema(description = "结束时间(格式:251129190131)") + @TableField("end_time") + private String endTime; + + @Schema(description = "用户号") + @TableField("usr_no") + private String usrNo; + + @Schema(description = "设备类型(如:Hanging4G)") + @TableField("device_type") + private String deviceType; + + @Schema(description = "是否有主体(0-否,1-是)") + @TableField("has_body") + private Boolean hasBody; + + @Schema(description = "扩展信息(JSON格式)") + @TableField("extended") + private String extended; + + @Schema(description = "用户ID") + @TableField("user_id") + private String userId; + + @Schema(description = "用户名") + @TableField("user_name") + private String userName; + + @Schema(description = "用户电话") + @TableField("user_phone") + private String userPhone; + + @Schema(description = "用户部门") + @TableField("user_dept") + private String userDept; + + @Schema(description = "同步阶段") + @TableField("sync_stage") + private String syncStage; + + @Schema(description = "系统创建时间") + @TableField("create_time") + private LocalDateTime createTime; + + @Schema(description = "系统更新时间") + @TableField("update_time") + private LocalDateTime updateTime; + +} + diff --git a/src/main/java/com/rj/entity/YhyDatatypeLog.java b/src/main/java/com/rj/entity/YhyDatatypeLog.java new file mode 100644 index 0000000..ff32718 --- /dev/null +++ b/src/main/java/com/rj/entity/YhyDatatypeLog.java @@ -0,0 +1,53 @@ +package com.rj.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.baomidou.mybatisplus.annotation.TableField; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + *

+ * 心跳日志表 + *

+ * + * @author Auto Generated + * @since 2025-01-01 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("yhy_datatype_log") +@Schema(description = "心跳日志表") +public class YhyDatatypeLog implements Serializable { + + private static final long serialVersionUID = 1L; + + @Schema(description = "主键,UUID") + @TableId("id") + private String id; + + @Schema(description = "数据类型,固定值:HeartbeatLog") + @TableField("data_type") + private String dataType; + + @Schema(description = "设备号") + @TableField("device_no") + private String deviceNo; + + @Schema(description = "内容") + @TableField("contents") + private String contents; + + @Schema(description = "系统创建时间") + @TableField("create_time") + private LocalDateTime createTime; + + @Schema(description = "系统更新时间") + @TableField("update_time") + private LocalDateTime updateTime; + +} + diff --git a/src/main/java/com/rj/entity/YhyHeartbeatLog.java b/src/main/java/com/rj/entity/YhyHeartbeatLog.java new file mode 100644 index 0000000..83f2b5c --- /dev/null +++ b/src/main/java/com/rj/entity/YhyHeartbeatLog.java @@ -0,0 +1,89 @@ +package com.rj.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.baomidou.mybatisplus.annotation.TableField; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + *

+ * 心跳日志表 + *

+ * + * @author Auto Generated + * @since 2025-01-01 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("yhy_heartbeat_log") +@Schema(description = "心跳日志表") +public class YhyHeartbeatLog implements Serializable { + + private static final long serialVersionUID = 1L; + + @Schema(description = "主键,UUID") + @TableId("id") + private String id; + + @Schema(description = "数据类型,固定值:HeartbeatLog") + @TableField("data_type") + private String dataType; + + @Schema(description = "时间戳") + @TableField("time_stamp") + private LocalDateTime timeStamp; + + @Schema(description = "设备号") + @TableField("device_no") + private String deviceNo; + + @Schema(description = "设备状态(如:Online)") + @TableField("device_status") + private String deviceStatus; + + @Schema(description = "电源状态(0-关闭,1-开启)") + @TableField("power_status") + private Boolean powerStatus; + + @Schema(description = "剩余电量(0-100)") + @TableField("remain_power") + private Integer remainPower; + + @Schema(description = "剩余存储空间(字节)") + @TableField("remain_storage_size") + private Long remainStorageSize; + + @Schema(description = "心跳时间") + @TableField("heartbeat_time") + private LocalDateTime heartbeatTime; + + @Schema(description = "设备版本(如:主:1.4.4 音频:1.4.2)") + @TableField("device_ver") + private String deviceVer; + + @Schema(description = "扩展信息") + @TableField("extend") + private String extend; + + @Schema(description = "主体信息") + @TableField("body") + private String body; + + @Schema(description = "数据创建时间(来自原始数据)") + @TableField("created_time") + private LocalDateTime createdTime; + + @Schema(description = "系统创建时间") + @TableField("create_time") + private LocalDateTime createTime; + + @Schema(description = "系统更新时间") + @TableField("update_time") + private LocalDateTime updateTime; + +} + diff --git a/src/main/java/com/rj/mapper/YhyAudioUploadLogMapper.java b/src/main/java/com/rj/mapper/YhyAudioUploadLogMapper.java new file mode 100644 index 0000000..cf525cf --- /dev/null +++ b/src/main/java/com/rj/mapper/YhyAudioUploadLogMapper.java @@ -0,0 +1,17 @@ +package com.rj.mapper; + +import com.rj.entity.YhyAudioUploadLog; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 音频上传日志表 Mapper 接口 + *

+ * + * @author Auto Generated + * @since 2025-01-29 + */ +public interface YhyAudioUploadLogMapper extends BaseMapper { + +} + diff --git a/src/main/java/com/rj/mapper/YhyDatatypeLogMapper.java b/src/main/java/com/rj/mapper/YhyDatatypeLogMapper.java new file mode 100644 index 0000000..d1aa568 --- /dev/null +++ b/src/main/java/com/rj/mapper/YhyDatatypeLogMapper.java @@ -0,0 +1,17 @@ +package com.rj.mapper; + +import com.rj.entity.YhyDatatypeLog; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 心跳日志表 Mapper 接口 + *

+ * + * @author Auto Generated + * @since 2025-01-01 + */ +public interface YhyDatatypeLogMapper extends BaseMapper { + +} + diff --git a/src/main/java/com/rj/mapper/YhyHeartbeatLogMapper.java b/src/main/java/com/rj/mapper/YhyHeartbeatLogMapper.java new file mode 100644 index 0000000..fd132d3 --- /dev/null +++ b/src/main/java/com/rj/mapper/YhyHeartbeatLogMapper.java @@ -0,0 +1,17 @@ +package com.rj.mapper; + +import com.rj.entity.YhyHeartbeatLog; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 心跳日志表 Mapper 接口 + *

+ * + * @author Auto Generated + * @since 2025-01-01 + */ +public interface YhyHeartbeatLogMapper extends BaseMapper { + +} + diff --git a/src/main/java/com/rj/service/IYhyAudioUploadLogService.java b/src/main/java/com/rj/service/IYhyAudioUploadLogService.java new file mode 100644 index 0000000..1c5a554 --- /dev/null +++ b/src/main/java/com/rj/service/IYhyAudioUploadLogService.java @@ -0,0 +1,73 @@ +package com.rj.service; + +import com.rj.entity.YhyAudioUploadLog; + +import java.util.Map; + +/** + *

+ * 音频上传日志表 服务类 + *

+ * + * @author Auto Generated + * @since 2025-01-29 + */ +public interface IYhyAudioUploadLogService extends com.baomidou.mybatisplus.extension.service.IService { + + /** + * 分页查询音频上传日志列表 + * + * @param current 页码 + * @param size 每页大小 + * @param deviceNo 设备号 + * @param startTime 开始时间(格式:251129185131) + * @param endTime 结束时间(格式:251129190131) + * @param createStartTime 创建开始时间(格式:yyyy-MM-dd HH:mm:ss) + * @param createEndTime 创建结束时间(格式:yyyy-MM-dd HH:mm:ss) + * @return 分页结果 + */ + Map getYhyAudioUploadLogList(Integer current, Integer size, String deviceNo, + String startTime, String endTime, + String createStartTime, String createEndTime); + + /** + * 根据ID查询音频上传日志 + * + * @param id 音频上传日志ID + * @return 查询结果 + */ + Map getYhyAudioUploadLogById(String id); + + /** + * 根据设备号查询音频上传日志列表 + * + * @param deviceNo 设备号 + * @return 查询结果 + */ + Map getYhyAudioUploadLogByDeviceNo(String deviceNo); + + /** + * 新增音频上传日志 + * + * @param yhyAudioUploadLog 音频上传日志信息 + * @return 操作结果 + */ + Map addYhyAudioUploadLog(YhyAudioUploadLog yhyAudioUploadLog); + + /** + * 更新音频上传日志信息 + * + * @param yhyAudioUploadLog 音频上传日志信息 + * @return 操作结果 + */ + Map updateYhyAudioUploadLog(YhyAudioUploadLog yhyAudioUploadLog); + + /** + * 根据ID删除音频上传日志 + * + * @param id 音频上传日志ID + * @return 操作结果 + */ + Map deleteYhyAudioUploadLog(String id); +} + diff --git a/src/main/java/com/rj/service/IYhyDatatypeLogService.java b/src/main/java/com/rj/service/IYhyDatatypeLogService.java new file mode 100644 index 0000000..425730a --- /dev/null +++ b/src/main/java/com/rj/service/IYhyDatatypeLogService.java @@ -0,0 +1,71 @@ +package com.rj.service; + +import com.rj.entity.YhyDatatypeLog; + +import java.util.Map; + +/** + *

+ * 心跳日志表 服务类 + *

+ * + * @author Auto Generated + * @since 2025-01-01 + */ +public interface IYhyDatatypeLogService extends com.baomidou.mybatisplus.extension.service.IService { + + /** + * 分页查询心跳日志列表 + * + * @param current 页码 + * @param size 每页大小 + * @param deviceNo 设备号 + * @param dataType 数据类型 + * @param createStartTime 创建开始时间 + * @param createEndTime 创建结束时间 + * @return 分页结果 + */ + Map getYhyDatatypeLogList(Integer current, Integer size, String deviceNo, + String dataType, String createStartTime, String createEndTime); + + /** + * 根据ID查询心跳日志 + * + * @param id 心跳日志ID + * @return 查询结果 + */ + Map getYhyDatatypeLogById(String id); + + /** + * 根据设备号查询心跳日志列表 + * + * @param deviceNo 设备号 + * @return 查询结果 + */ + Map getYhyDatatypeLogByDeviceNo(String deviceNo); + + /** + * 新增心跳日志 + * + * @param yhyDatatypeLog 心跳日志信息 + * @return 操作结果 + */ + Map addYhyDatatypeLog(YhyDatatypeLog yhyDatatypeLog); + + /** + * 更新心跳日志信息 + * + * @param yhyDatatypeLog 心跳日志信息 + * @return 操作结果 + */ + Map updateYhyDatatypeLog(YhyDatatypeLog yhyDatatypeLog); + + /** + * 根据ID删除心跳日志 + * + * @param id 心跳日志ID + * @return 操作结果 + */ + Map deleteYhyDatatypeLog(String id); +} + diff --git a/src/main/java/com/rj/service/IYhyHeartbeatLogService.java b/src/main/java/com/rj/service/IYhyHeartbeatLogService.java new file mode 100644 index 0000000..0018652 --- /dev/null +++ b/src/main/java/com/rj/service/IYhyHeartbeatLogService.java @@ -0,0 +1,85 @@ +package com.rj.service; + +import com.rj.entity.YhyHeartbeatLog; + +import java.util.Map; + +/** + *

+ * 心跳日志表 服务类 + *

+ * + * @author Auto Generated + * @since 2025-01-01 + */ +public interface IYhyHeartbeatLogService extends com.baomidou.mybatisplus.extension.service.IService { + + /** + * 分页查询心跳日志列表 + * + * @param current 页码 + * @param size 每页大小 + * @param deviceNo 设备号 + * @param deviceStatus 设备状态 + * @param powerStatus 电源状态 + * @param heartbeatStartTime 心跳开始时间 + * @param heartbeatEndTime 心跳结束时间 + * @param createStartTime 创建开始时间 + * @param createEndTime 创建结束时间 + * @return 分页结果 + */ + Map getHeartbeatLogList(Integer current, Integer size, String deviceNo, + String deviceStatus, Boolean powerStatus, + String heartbeatStartTime, String heartbeatEndTime, + String createStartTime, String createEndTime); + + /** + * 根据ID查询心跳日志 + * + * @param id 心跳日志ID + * @return 查询结果 + */ + Map getHeartbeatLogById(String id); + + /** + * 根据设备号查询心跳日志列表 + * + * @param deviceNo 设备号 + * @return 查询结果 + */ + Map getHeartbeatLogByDeviceNo(String deviceNo); + + /** + * 新增心跳日志 + * + * @param heartbeatLog 心跳日志信息 + * @return 操作结果 + */ + Map addHeartbeatLog(YhyHeartbeatLog heartbeatLog); + + /** + * 更新心跳日志信息 + * + * @param heartbeatLog 心跳日志信息 + * @return 操作结果 + */ + Map updateHeartbeatLog(YhyHeartbeatLog heartbeatLog); + + /** + * 根据ID删除心跳日志 + * + * @param id 心跳日志ID + * @return 操作结果 + */ + Map deleteHeartbeatLog(String id); + + /** + * 处理心跳上报数据 + * 解析JSON格式的心跳数据并保存到数据库 + * + * @param requestData 心跳上报的JSON数据 + * @return 操作结果 + */ + Map processHeartbeatReport(Map requestData); +} + diff --git a/src/main/java/com/rj/service/impl/YhyAudioUploadLogServiceImpl.java b/src/main/java/com/rj/service/impl/YhyAudioUploadLogServiceImpl.java new file mode 100644 index 0000000..047dc29 --- /dev/null +++ b/src/main/java/com/rj/service/impl/YhyAudioUploadLogServiceImpl.java @@ -0,0 +1,227 @@ +package com.rj.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.rj.entity.YhyAudioUploadLog; +import com.rj.mapper.YhyAudioUploadLogMapper; +import com.rj.service.IYhyAudioUploadLogService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + *

+ * 音频上传日志表 服务实现类 + *

+ * + * @author Auto Generated + * @since 2025-01-29 + */ +@Slf4j +@Service +public class YhyAudioUploadLogServiceImpl extends ServiceImpl implements IYhyAudioUploadLogService { + + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + @Override + public Map getYhyAudioUploadLogList(Integer current, Integer size, String deviceNo, + String startTime, String endTime, + String createStartTime, String createEndTime) { + Map result = new HashMap<>(); + try { + Page page = new Page<>(current, size); + LambdaQueryWrapper queryWrapper = buildQueryWrapper( + deviceNo, startTime, endTime, createStartTime, createEndTime); + + Page yhyAudioUploadLogPage = this.page(page, queryWrapper); + + result.put("success", true); + result.put("message", "查询成功"); + result.put("data", yhyAudioUploadLogPage.getRecords()); + result.put("total", yhyAudioUploadLogPage.getTotal()); + result.put("current", yhyAudioUploadLogPage.getCurrent()); + result.put("size", yhyAudioUploadLogPage.getSize()); + result.put("pages", yhyAudioUploadLogPage.getPages()); + } catch (Exception e) { + log.error("查询音频上传日志异常", e); + result.put("success", false); + result.put("message", "查询异常:" + e.getMessage()); + } + return result; + } + + @Override + public Map getYhyAudioUploadLogById(String id) { + Map result = new HashMap<>(); + try { + YhyAudioUploadLog yhyAudioUploadLog = this.getById(id); + if (yhyAudioUploadLog != null) { + result.put("success", true); + result.put("message", "查询成功"); + result.put("data", yhyAudioUploadLog); + } else { + result.put("success", false); + result.put("message", "音频上传日志不存在"); + } + } catch (Exception e) { + log.error("查询音频上传日志异常", e); + result.put("success", false); + result.put("message", "查询异常:" + e.getMessage()); + } + return result; + } + + @Override + public Map getYhyAudioUploadLogByDeviceNo(String deviceNo) { + Map result = new HashMap<>(); + try { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(YhyAudioUploadLog::getDeviceNo, deviceNo); + queryWrapper.orderByDesc(YhyAudioUploadLog::getCreateTime); + + List yhyAudioUploadLogs = this.list(queryWrapper); + + result.put("success", true); + result.put("message", "查询成功"); + result.put("data", yhyAudioUploadLogs); + result.put("count", yhyAudioUploadLogs.size()); + } catch (Exception e) { + log.error("查询音频上传日志异常", e); + result.put("success", false); + result.put("message", "查询异常:" + e.getMessage()); + } + return result; + } + + @Override + public Map addYhyAudioUploadLog(YhyAudioUploadLog yhyAudioUploadLog) { + Map result = new HashMap<>(); + try { + LocalDateTime now = LocalDateTime.now(); + if (yhyAudioUploadLog.getId() == null || yhyAudioUploadLog.getId().trim().isEmpty()) { + yhyAudioUploadLog.setId(UUID.randomUUID().toString()); + } + yhyAudioUploadLog.setCreateTime(now); + yhyAudioUploadLog.setUpdateTime(now); + + boolean success = this.save(yhyAudioUploadLog); + if (success) { + result.put("success", true); + result.put("message", "音频上传日志添加成功"); + result.put("data", yhyAudioUploadLog); + } else { + result.put("success", false); + result.put("message", "音频上传日志添加失败"); + } + } catch (Exception e) { + log.error("添加音频上传日志异常", e); + result.put("success", false); + result.put("message", "添加异常:" + e.getMessage()); + } + return result; + } + + @Override + public Map updateYhyAudioUploadLog(YhyAudioUploadLog yhyAudioUploadLog) { + Map result = new HashMap<>(); + try { + if (yhyAudioUploadLog.getId() == null || yhyAudioUploadLog.getId().trim().isEmpty()) { + result.put("success", false); + result.put("message", "音频上传日志ID不能为空"); + return result; + } + + yhyAudioUploadLog.setUpdateTime(LocalDateTime.now()); + boolean success = this.updateById(yhyAudioUploadLog); + + if (success) { + result.put("success", true); + result.put("message", "音频上传日志信息更新成功"); + result.put("data", yhyAudioUploadLog); + } else { + result.put("success", false); + result.put("message", "音频上传日志信息更新失败"); + } + } catch (Exception e) { + log.error("更新音频上传日志异常", e); + result.put("success", false); + result.put("message", "更新异常:" + e.getMessage()); + } + return result; + } + + @Override + public Map deleteYhyAudioUploadLog(String id) { + Map result = new HashMap<>(); + try { + boolean success = this.removeById(id); + if (success) { + result.put("success", true); + result.put("message", "音频上传日志删除成功"); + } else { + result.put("success", false); + result.put("message", "音频上传日志删除失败"); + } + } catch (Exception e) { + log.error("删除音频上传日志异常", e); + result.put("success", false); + result.put("message", "删除异常:" + e.getMessage()); + } + return result; + } + + /** + * 构建查询条件 + */ + private LambdaQueryWrapper buildQueryWrapper(String deviceNo, String startTime, + String endTime, String createStartTime, + String createEndTime) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + + // 添加设备号查询条件 + if (deviceNo != null && !deviceNo.trim().isEmpty()) { + queryWrapper.eq(YhyAudioUploadLog::getDeviceNo, deviceNo.trim()); + } + + // 添加开始时间范围查询条件(start_time字段是varchar类型,存储格式:251129185131) + if (startTime != null && !startTime.trim().isEmpty()) { + queryWrapper.ge(YhyAudioUploadLog::getStartTime, startTime.trim()); + } + if (endTime != null && !endTime.trim().isEmpty()) { + queryWrapper.le(YhyAudioUploadLog::getEndTime, endTime.trim()); + } + + // 添加创建时间范围查询条件 + if (createStartTime != null && !createStartTime.trim().isEmpty()) { + try { + LocalDateTime startDateTime = LocalDateTime.parse(createStartTime, DATE_TIME_FORMATTER); + queryWrapper.ge(YhyAudioUploadLog::getCreateTime, startDateTime); + } catch (Exception e) { + log.warn("创建开始时间格式错误: {}", createStartTime); + throw new IllegalArgumentException("创建开始时间格式错误,请使用格式:yyyy-MM-dd HH:mm:ss"); + } + } + if (createEndTime != null && !createEndTime.trim().isEmpty()) { + try { + LocalDateTime endDateTime = LocalDateTime.parse(createEndTime, DATE_TIME_FORMATTER); + queryWrapper.le(YhyAudioUploadLog::getCreateTime, endDateTime); + } catch (Exception e) { + log.warn("创建结束时间格式错误: {}", createEndTime); + throw new IllegalArgumentException("创建结束时间格式错误,请使用格式:yyyy-MM-dd HH:mm:ss"); + } + } + + // 按创建时间倒序排列 + queryWrapper.orderByDesc(YhyAudioUploadLog::getCreateTime); + + return queryWrapper; + } +} + diff --git a/src/main/java/com/rj/service/impl/YhyDatatypeLogServiceImpl.java b/src/main/java/com/rj/service/impl/YhyDatatypeLogServiceImpl.java new file mode 100644 index 0000000..8a19ace --- /dev/null +++ b/src/main/java/com/rj/service/impl/YhyDatatypeLogServiceImpl.java @@ -0,0 +1,224 @@ +package com.rj.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.rj.entity.YhyDatatypeLog; +import com.rj.mapper.YhyDatatypeLogMapper; +import com.rj.service.IYhyDatatypeLogService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.rj.common.TimeZoneUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + *

+ * 心跳日志表 服务实现类 + *

+ * + * @author Auto Generated + * @since 2025-01-01 + */ +@Slf4j +@Service +public class YhyDatatypeLogServiceImpl extends ServiceImpl implements IYhyDatatypeLogService { + + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + @Override + public Map getYhyDatatypeLogList(Integer current, Integer size, String deviceNo, + String dataType, String createStartTime, String createEndTime) { + Map result = new HashMap<>(); + try { + Page page = new Page<>(current, size); + LambdaQueryWrapper queryWrapper = buildQueryWrapper( + deviceNo, dataType, createStartTime, createEndTime); + + Page yhyDatatypeLogPage = this.page(page, queryWrapper); + + result.put("success", true); + result.put("message", "查询成功"); + result.put("data", yhyDatatypeLogPage.getRecords()); + result.put("total", yhyDatatypeLogPage.getTotal()); + result.put("current", yhyDatatypeLogPage.getCurrent()); + result.put("size", yhyDatatypeLogPage.getSize()); + result.put("pages", yhyDatatypeLogPage.getPages()); + } catch (Exception e) { + log.error("查询心跳日志异常", e); + result.put("success", false); + result.put("message", "查询异常:" + e.getMessage()); + } + return result; + } + + @Override + public Map getYhyDatatypeLogById(String id) { + Map result = new HashMap<>(); + try { + YhyDatatypeLog yhyDatatypeLog = this.getById(id); + if (yhyDatatypeLog != null) { + result.put("success", true); + result.put("message", "查询成功"); + result.put("data", yhyDatatypeLog); + } else { + result.put("success", false); + result.put("message", "心跳日志不存在"); + } + } catch (Exception e) { + log.error("查询心跳日志异常", e); + result.put("success", false); + result.put("message", "查询异常:" + e.getMessage()); + } + return result; + } + + @Override + public Map getYhyDatatypeLogByDeviceNo(String deviceNo) { + Map result = new HashMap<>(); + try { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(YhyDatatypeLog::getDeviceNo, deviceNo); + queryWrapper.orderByDesc(YhyDatatypeLog::getCreateTime); + + List yhyDatatypeLogs = this.list(queryWrapper); + + result.put("success", true); + result.put("message", "查询成功"); + result.put("data", yhyDatatypeLogs); + result.put("count", yhyDatatypeLogs.size()); + } catch (Exception e) { + log.error("查询心跳日志异常", e); + result.put("success", false); + result.put("message", "查询异常:" + e.getMessage()); + } + return result; + } + + @Override + public Map addYhyDatatypeLog(YhyDatatypeLog yhyDatatypeLog) { + Map result = new HashMap<>(); + try { + // 使用配置的时区获取当前时间 + LocalDateTime now = TimeZoneUtils.now(); + if (yhyDatatypeLog.getId() == null || yhyDatatypeLog.getId().trim().isEmpty()) { + yhyDatatypeLog.setId(UUID.randomUUID().toString()); + } + yhyDatatypeLog.setCreateTime(now); + yhyDatatypeLog.setUpdateTime(now); + + boolean success = this.save(yhyDatatypeLog); + if (success) { + result.put("success", true); + result.put("message", "心跳日志添加成功"); + result.put("data", yhyDatatypeLog); + } else { + result.put("success", false); + result.put("message", "心跳日志添加失败"); + } + } catch (Exception e) { + log.error("添加心跳日志异常", e); + result.put("success", false); + result.put("message", "添加异常:" + e.getMessage()); + } + return result; + } + + @Override + public Map updateYhyDatatypeLog(YhyDatatypeLog yhyDatatypeLog) { + Map result = new HashMap<>(); + try { + if (yhyDatatypeLog.getId() == null || yhyDatatypeLog.getId().trim().isEmpty()) { + result.put("success", false); + result.put("message", "心跳日志ID不能为空"); + return result; + } + + // 使用配置的时区获取当前时间 + yhyDatatypeLog.setUpdateTime(TimeZoneUtils.now()); + boolean success = this.updateById(yhyDatatypeLog); + + if (success) { + result.put("success", true); + result.put("message", "心跳日志信息更新成功"); + result.put("data", yhyDatatypeLog); + } else { + result.put("success", false); + result.put("message", "心跳日志信息更新失败"); + } + } catch (Exception e) { + log.error("更新心跳日志异常", e); + result.put("success", false); + result.put("message", "更新异常:" + e.getMessage()); + } + return result; + } + + @Override + public Map deleteYhyDatatypeLog(String id) { + Map result = new HashMap<>(); + try { + boolean success = this.removeById(id); + if (success) { + result.put("success", true); + result.put("message", "心跳日志删除成功"); + } else { + result.put("success", false); + result.put("message", "心跳日志删除失败"); + } + } catch (Exception e) { + log.error("删除心跳日志异常", e); + result.put("success", false); + result.put("message", "删除异常:" + e.getMessage()); + } + return result; + } + + /** + * 构建查询条件 + */ + private LambdaQueryWrapper buildQueryWrapper(String deviceNo, String dataType, + String createStartTime, String createEndTime) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + + // 添加查询条件 + if (deviceNo != null && !deviceNo.trim().isEmpty()) { + queryWrapper.eq(YhyDatatypeLog::getDeviceNo, deviceNo.trim()); + } + if (dataType != null && !dataType.trim().isEmpty()) { + queryWrapper.eq(YhyDatatypeLog::getDataType, dataType.trim()); + } + + // 添加创建时间范围查询条件 + if (createStartTime != null && !createStartTime.trim().isEmpty()) { + try { + LocalDateTime startTime = LocalDateTime.parse(createStartTime, DATE_TIME_FORMATTER); + queryWrapper.ge(YhyDatatypeLog::getCreateTime, startTime); + } catch (Exception e) { + log.warn("创建开始时间格式错误: {}", createStartTime); + throw new IllegalArgumentException("创建开始时间格式错误,请使用格式:yyyy-MM-dd HH:mm:ss"); + } + } + if (createEndTime != null && !createEndTime.trim().isEmpty()) { + try { + LocalDateTime endTime = LocalDateTime.parse(createEndTime, DATE_TIME_FORMATTER); + queryWrapper.le(YhyDatatypeLog::getCreateTime, endTime); + } catch (Exception e) { + log.warn("创建结束时间格式错误: {}", createEndTime); + throw new IllegalArgumentException("创建结束时间格式错误,请使用格式:yyyy-MM-dd HH:mm:ss"); + } + } + + // 按创建时间倒序排列 + queryWrapper.orderByDesc(YhyDatatypeLog::getCreateTime); + + return queryWrapper; + } + +} + diff --git a/src/main/java/com/rj/service/impl/YhyHeartbeatLogServiceImpl.java b/src/main/java/com/rj/service/impl/YhyHeartbeatLogServiceImpl.java new file mode 100644 index 0000000..9d69d32 --- /dev/null +++ b/src/main/java/com/rj/service/impl/YhyHeartbeatLogServiceImpl.java @@ -0,0 +1,499 @@ +package com.rj.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.rj.entity.YhyHeartbeatLog; +import com.rj.mapper.YhyHeartbeatLogMapper; +import com.rj.service.IYhyHeartbeatLogService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import com.rj.common.TimeZoneUtils; + +/** + *

+ * 心跳日志表 服务实现类 + *

+ * + * @author Auto Generated + * @since 2025-01-01 + */ +@Slf4j +@Service +public class YhyHeartbeatLogServiceImpl extends ServiceImpl implements IYhyHeartbeatLogService { + + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + @Autowired + private ObjectMapper objectMapper; + + @Override + public Map getHeartbeatLogList(Integer current, Integer size, String deviceNo, + String deviceStatus, Boolean powerStatus, + String heartbeatStartTime, String heartbeatEndTime, + String createStartTime, String createEndTime) { + Map result = new HashMap<>(); + try { + Page page = new Page<>(current, size); + LambdaQueryWrapper queryWrapper = buildQueryWrapper( + deviceNo, deviceStatus, powerStatus, heartbeatStartTime, heartbeatEndTime, + createStartTime, createEndTime); + + Page heartbeatLogPage = this.page(page, queryWrapper); + + result.put("success", true); + result.put("message", "查询成功"); + result.put("data", heartbeatLogPage.getRecords()); + result.put("total", heartbeatLogPage.getTotal()); + result.put("current", heartbeatLogPage.getCurrent()); + result.put("size", heartbeatLogPage.getSize()); + result.put("pages", heartbeatLogPage.getPages()); + } catch (Exception e) { + log.error("查询心跳日志异常", e); + result.put("success", false); + result.put("message", "查询异常:" + e.getMessage()); + } + return result; + } + + @Override + public Map getHeartbeatLogById(String id) { + Map result = new HashMap<>(); + try { + YhyHeartbeatLog heartbeatLog = this.getById(id); + if (heartbeatLog != null) { + result.put("success", true); + result.put("message", "查询成功"); + result.put("data", heartbeatLog); + } else { + result.put("success", false); + result.put("message", "心跳日志不存在"); + } + } catch (Exception e) { + log.error("查询心跳日志异常", e); + result.put("success", false); + result.put("message", "查询异常:" + e.getMessage()); + } + return result; + } + + @Override + public Map getHeartbeatLogByDeviceNo(String deviceNo) { + Map result = new HashMap<>(); + try { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(YhyHeartbeatLog::getDeviceNo, deviceNo); + queryWrapper.orderByDesc(YhyHeartbeatLog::getCreateTime); + + List heartbeatLogs = this.list(queryWrapper); + + result.put("success", true); + result.put("message", "查询成功"); + result.put("data", heartbeatLogs); + result.put("count", heartbeatLogs.size()); + } catch (Exception e) { + log.error("查询心跳日志异常", e); + result.put("success", false); + result.put("message", "查询异常:" + e.getMessage()); + } + return result; + } + + @Override + public Map addHeartbeatLog(YhyHeartbeatLog heartbeatLog) { + Map result = new HashMap<>(); + try { + LocalDateTime now = TimeZoneUtils.now(); + if (heartbeatLog.getId() == null || heartbeatLog.getId().trim().isEmpty()) { + heartbeatLog.setId(UUID.randomUUID().toString()); + } + heartbeatLog.setCreateTime(now); + heartbeatLog.setUpdateTime(now); + + boolean success = this.save(heartbeatLog); + if (success) { + result.put("success", true); + result.put("message", "心跳日志添加成功"); + result.put("data", heartbeatLog); + } else { + result.put("success", false); + result.put("message", "心跳日志添加失败"); + } + } catch (Exception e) { + log.error("添加心跳日志异常", e); + result.put("success", false); + result.put("message", "添加异常:" + e.getMessage()); + } + return result; + } + + @Override + public Map updateHeartbeatLog(YhyHeartbeatLog heartbeatLog) { + Map result = new HashMap<>(); + try { + if (heartbeatLog.getId() == null || heartbeatLog.getId().trim().isEmpty()) { + result.put("success", false); + result.put("message", "心跳日志ID不能为空"); + return result; + } + + heartbeatLog.setUpdateTime(TimeZoneUtils.now()); + boolean success = this.updateById(heartbeatLog); + + if (success) { + result.put("success", true); + result.put("message", "心跳日志信息更新成功"); + result.put("data", heartbeatLog); + } else { + result.put("success", false); + result.put("message", "心跳日志信息更新失败"); + } + } catch (Exception e) { + log.error("更新心跳日志异常", e); + result.put("success", false); + result.put("message", "更新异常:" + e.getMessage()); + } + return result; + } + + @Override + public Map deleteHeartbeatLog(String id) { + Map result = new HashMap<>(); + try { + boolean success = this.removeById(id); + if (success) { + result.put("success", true); + result.put("message", "心跳日志删除成功"); + } else { + result.put("success", false); + result.put("message", "心跳日志删除失败"); + } + } catch (Exception e) { + log.error("删除心跳日志异常", e); + result.put("success", false); + result.put("message", "删除异常:" + e.getMessage()); + } + return result; + } + + @Override + public Map processHeartbeatReport(Map requestData) { + Map result = new HashMap<>(); + try { + YhyHeartbeatLog heartbeatLog = new YhyHeartbeatLog(); + heartbeatLog.setId(UUID.randomUUID().toString()); + + // 设置数据类型(从顶层获取,如果没有则使用默认值) + String dataType = (String) requestData.get("dataType"); + if (dataType == null || dataType.trim().isEmpty()) { + dataType = "HeartbeatLog"; + } + heartbeatLog.setDataType(dataType); + + // 从顶层获取时间戳 + Object timeStampObj = requestData.get("timeStamp"); + if (timeStampObj != null) { + heartbeatLog.setTimeStamp(parseDateTime(timeStampObj)); + } + + // 提取 data 对象(大部分字段在 data 对象内) + @SuppressWarnings("unchecked") + Map dataMap = (Map) requestData.get("data"); + + // 如果存在 data 对象,优先从 data 中提取字段;否则从顶层提取(保持向后兼容) + Map sourceMap = dataMap != null ? dataMap : requestData; + + // 提取设备号(必需字段)- 优先从 data 中获取,如果没有则从顶层获取 + String deviceNo = null; + if (dataMap != null && dataMap.containsKey("deviceNo")) { + deviceNo = (String) dataMap.get("deviceNo"); + } + if (deviceNo == null || deviceNo.trim().isEmpty()) { + deviceNo = (String) requestData.get("deviceNo"); + } + if (deviceNo == null || deviceNo.trim().isEmpty()) { + result.put("success", false); + result.put("message", "设备号不能为空"); + return result; + } + heartbeatLog.setDeviceNo(deviceNo); + + // 提取设备状态 + heartbeatLog.setDeviceStatus((String) sourceMap.get("deviceStatus")); + + // 提取电源状态 + Object powerStatusObj = sourceMap.get("powerStatus"); + if (powerStatusObj != null) { + if (powerStatusObj instanceof Boolean) { + heartbeatLog.setPowerStatus((Boolean) powerStatusObj); + } else if (powerStatusObj instanceof Number) { + heartbeatLog.setPowerStatus(((Number) powerStatusObj).intValue() == 1); + } else if (powerStatusObj instanceof String) { + heartbeatLog.setPowerStatus("1".equals(powerStatusObj) || "true".equalsIgnoreCase((String) powerStatusObj)); + } + } + + // 提取剩余电量 + Object remainPowerObj = sourceMap.get("remainPower"); + if (remainPowerObj != null) { + if (remainPowerObj instanceof Number) { + heartbeatLog.setRemainPower(((Number) remainPowerObj).intValue()); + } else if (remainPowerObj instanceof String) { + try { + heartbeatLog.setRemainPower(Integer.parseInt((String) remainPowerObj)); + } catch (NumberFormatException e) { + log.warn("剩余电量格式错误: {}", remainPowerObj); + } + } + } + + // 提取剩余存储空间 + Object remainStorageSizeObj = sourceMap.get("remainStorageSize"); + if (remainStorageSizeObj != null) { + if (remainStorageSizeObj instanceof Number) { + heartbeatLog.setRemainStorageSize(((Number) remainStorageSizeObj).longValue()); + } else if (remainStorageSizeObj instanceof String) { + try { + heartbeatLog.setRemainStorageSize(Long.parseLong((String) remainStorageSizeObj)); + } catch (NumberFormatException e) { + log.warn("剩余存储空间格式错误: {}", remainStorageSizeObj); + } + } + } + + // 提取心跳时间 + Object heartbeatTimeObj = sourceMap.get("heartbeatTime"); + if (heartbeatTimeObj != null) { + heartbeatLog.setHeartbeatTime(parseDateTime(heartbeatTimeObj)); + } + + // 提取设备版本 + heartbeatLog.setDeviceVer((String) sourceMap.get("deviceVer")); + + // 提取扩展信息 + Object extendObj = sourceMap.get("extend"); + if (extendObj != null) { + try { + heartbeatLog.setExtend(objectMapper.writeValueAsString(extendObj)); + } catch (Exception e) { + log.warn("序列化extend字段失败", e); + heartbeatLog.setExtend(extendObj.toString()); + } + } + + // 提取主体信息 + Object bodyObj = sourceMap.get("body"); + if (bodyObj != null) { + try { + heartbeatLog.setBody(objectMapper.writeValueAsString(bodyObj)); + } catch (Exception e) { + log.warn("序列化body字段失败", e); + heartbeatLog.setBody(bodyObj.toString()); + } + } + + // 提取数据创建时间 + Object createdTimeObj = sourceMap.get("createdTime"); + if (createdTimeObj != null) { + heartbeatLog.setCreatedTime(parseDateTime(createdTimeObj)); + } + + // 设置系统创建和更新时间 + LocalDateTime now = TimeZoneUtils.now(); + heartbeatLog.setCreateTime(now); + heartbeatLog.setUpdateTime(now); + + // 保存到数据库 + boolean success = this.save(heartbeatLog); + if (success) { + result.put("success", true); + result.put("message", "心跳日志处理成功"); + result.put("data", heartbeatLog); + log.info("心跳日志保存成功,ID: {}, 设备号: {}, 心跳时间: {}", + heartbeatLog.getId(), heartbeatLog.getDeviceNo(), heartbeatLog.getHeartbeatTime()); + } else { + result.put("success", false); + result.put("message", "心跳日志保存失败"); + } + } catch (Exception e) { + log.error("处理心跳上报数据异常", e); + result.put("success", false); + result.put("message", "处理异常:" + e.getMessage()); + } + return result; + } + + /** + * 解析日期时间 + * 支持多种时间格式: + * - 2025-11-30T09:27:32.3963139 + * - 2025-11-30T09:27:32.3963443+08:00 + * - 2025-11-30T01:27:32Z + * - 2025-11-30 09:27:32 + */ + private LocalDateTime parseDateTime(Object dateTimeObj) { + if (dateTimeObj == null) { + return null; + } + if (dateTimeObj instanceof LocalDateTime) { + return (LocalDateTime) dateTimeObj; + } + if (dateTimeObj instanceof String) { + String dateTimeStr = (String) dateTimeObj; + if (dateTimeStr.trim().isEmpty()) { + return null; + } + + try { + // 检测是否包含时区信息:以Z结尾,或包含时区偏移格式(+HH:MM 或 -HH:MM) + boolean hasTimezone = dateTimeStr.endsWith("Z") || + dateTimeStr.matches(".*[+-]\\d{2}:\\d{2}(?:\\d{2})?$"); + + if (hasTimezone) { + // 处理ISO 8601格式,包含时区信息 + ZonedDateTime zonedDateTime; + if (dateTimeStr.endsWith("Z")) { + // UTC时间,转换为应用配置的时区 + zonedDateTime = ZonedDateTime.parse(dateTimeStr.replace("Z", "+00:00")) + .withZoneSameInstant(TimeZoneUtils.getZoneId()); + } else { + // 带时区偏移的时间,转换为应用配置的时区 + zonedDateTime = ZonedDateTime.parse(dateTimeStr) + .withZoneSameInstant(TimeZoneUtils.getZoneId()); + } + return zonedDateTime.toLocalDateTime(); + } else { + // 没有时区信息,尝试多种格式解析 + // 首先尝试标准格式:yyyy-MM-dd HH:mm:ss + try { + return LocalDateTime.parse(dateTimeStr, DATE_TIME_FORMATTER); + } catch (Exception e1) { + // 尝试ISO格式(不带时区):2025-11-30T09:27:32.3963139 + try { + // 如果有小数点,可能需要处理微秒 + if (dateTimeStr.contains(".")) { + String cleanTimeStr = dateTimeStr; + // 移除微秒部分(如果超过6位) + int dotIndex = cleanTimeStr.indexOf("."); + if (dotIndex > 0) { + String beforeDot = cleanTimeStr.substring(0, dotIndex); + String afterDot = cleanTimeStr.substring(dotIndex + 1); + // 如果小数点后有超过9位数字,截取前9位(纳秒精度) + if (afterDot.length() > 9) { + afterDot = afterDot.substring(0, 9); + } + // 补齐到9位(纳秒) + while (afterDot.length() < 9) { + afterDot += "0"; + } + cleanTimeStr = beforeDot + "." + afterDot; + } + return LocalDateTime.parse(cleanTimeStr); + } else { + return LocalDateTime.parse(dateTimeStr); + } + } catch (Exception e2) { + // 最后尝试:移除T并格式化 + try { + String cleanTimeStr = dateTimeStr; + 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, DATE_TIME_FORMATTER); + } catch (Exception e3) { + log.warn("日期时间格式错误,无法解析: {}", dateTimeStr); + return null; + } + } + } + } + } catch (Exception e) { + log.warn("解析日期时间失败: {}, 错误: {}", dateTimeStr, e.getMessage()); + return null; + } + } + return null; + } + + /** + * 构建查询条件 + */ + private LambdaQueryWrapper buildQueryWrapper(String deviceNo, String deviceStatus, + Boolean powerStatus, String heartbeatStartTime, + String heartbeatEndTime, String createStartTime, + String createEndTime) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + + // 添加设备号查询条件 + if (deviceNo != null && !deviceNo.trim().isEmpty()) { + queryWrapper.eq(YhyHeartbeatLog::getDeviceNo, deviceNo.trim()); + } + + // 添加设备状态查询条件 + if (deviceStatus != null && !deviceStatus.trim().isEmpty()) { + queryWrapper.eq(YhyHeartbeatLog::getDeviceStatus, deviceStatus.trim()); + } + + // 添加电源状态查询条件 + if (powerStatus != null) { + queryWrapper.eq(YhyHeartbeatLog::getPowerStatus, powerStatus); + } + + // 添加心跳时间范围查询条件 + if (heartbeatStartTime != null && !heartbeatStartTime.trim().isEmpty()) { + try { + LocalDateTime startTime = LocalDateTime.parse(heartbeatStartTime, DATE_TIME_FORMATTER); + queryWrapper.ge(YhyHeartbeatLog::getHeartbeatTime, startTime); + } catch (Exception e) { + log.warn("心跳开始时间格式错误: {}", heartbeatStartTime); + } + } + if (heartbeatEndTime != null && !heartbeatEndTime.trim().isEmpty()) { + try { + LocalDateTime endTime = LocalDateTime.parse(heartbeatEndTime, DATE_TIME_FORMATTER); + queryWrapper.le(YhyHeartbeatLog::getHeartbeatTime, endTime); + } catch (Exception e) { + log.warn("心跳结束时间格式错误: {}", heartbeatEndTime); + } + } + + // 添加创建时间范围查询条件 + if (createStartTime != null && !createStartTime.trim().isEmpty()) { + try { + LocalDateTime startTime = LocalDateTime.parse(createStartTime, DATE_TIME_FORMATTER); + queryWrapper.ge(YhyHeartbeatLog::getCreateTime, startTime); + } catch (Exception e) { + log.warn("创建开始时间格式错误: {}", createStartTime); + } + } + if (createEndTime != null && !createEndTime.trim().isEmpty()) { + try { + LocalDateTime endTime = LocalDateTime.parse(createEndTime, DATE_TIME_FORMATTER); + queryWrapper.le(YhyHeartbeatLog::getCreateTime, endTime); + } catch (Exception e) { + log.warn("创建结束时间格式错误: {}", createEndTime); + } + } + + // 按创建时间倒序排列 + queryWrapper.orderByDesc(YhyHeartbeatLog::getCreateTime); + + return queryWrapper; + } +} diff --git a/src/main/resources/application-audio.yml b/src/main/resources/application-audio.yml index 4360739..a127b3e 100644 --- a/src/main/resources/application-audio.yml +++ b/src/main/resources/application-audio.yml @@ -4,6 +4,9 @@ app: # 音频文件上传路径 upload: path: uploads/audio + # 文件上传保存路径(用于接收文件上传请求时) + yihangyi: + path: /usr/local/tomcat/webapps/yihangyi # 音频文件访问URL前缀 access: url: /api/audio/ diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 8311c63..3accad7 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,4 +1,8 @@ +# 应用时区配置 +app: + timezone: Asia/Shanghai + # DashScope API配置 dashscope: api: @@ -64,7 +68,7 @@ spring: database: 0 datasource: driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://124.221.59.58:3309/ai_smart_badge?useUnicode=true&characterEncoding=utf8 + url: jdbc:mysql://124.221.59.58:3309/ai_smart_badge?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai # url: jdbc:mysql://101.35.52.237:13307/ai_smart_badge?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&autoReconnect=true&failOverReadOnly=false&maxReconnects=3&initialTimeout=2&connectTimeout=30000&socketTimeout=60000 username: root # password: cstcom.123! diff --git a/src/main/resources/mapper/AudioUploadLogMapper.xml b/src/main/resources/mapper/AudioUploadLogMapper.xml new file mode 100644 index 0000000..4f0bad0 --- /dev/null +++ b/src/main/resources/mapper/AudioUploadLogMapper.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id, file_path, content_type, device_no, server_url, push_data_type, file_name, + save_path, chunk_index, start_time, end_time, usr_no, device_type, has_body, + extended, user_id, user_name, user_phone, user_dept, sync_stage, create_time, update_time + + + diff --git a/src/main/resources/mapper/HeartbeatLogMapper.xml b/src/main/resources/mapper/HeartbeatLogMapper.xml new file mode 100644 index 0000000..e74b410 --- /dev/null +++ b/src/main/resources/mapper/HeartbeatLogMapper.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + id, data_type, time_stamp, device_no, device_status, power_status, remain_power, + remain_storage_size, heartbeat_time, device_ver, extend, body, created_time, + create_time, update_time + + + diff --git a/src/main/resources/mapper/YhyAudioUploadLogMapper.xml b/src/main/resources/mapper/YhyAudioUploadLogMapper.xml new file mode 100644 index 0000000..0d7ac1a --- /dev/null +++ b/src/main/resources/mapper/YhyAudioUploadLogMapper.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id, file_path, content_type, device_no, server_url, push_data_type, file_name, + save_path, chunk_index, start_time, end_time, usr_no, device_type, has_body, + extended, user_id, user_name, user_phone, user_dept, sync_stage, create_time, update_time + + + diff --git a/src/main/resources/mapper/YhyDatatypeLogMapper.xml b/src/main/resources/mapper/YhyDatatypeLogMapper.xml new file mode 100644 index 0000000..5d64db4 --- /dev/null +++ b/src/main/resources/mapper/YhyDatatypeLogMapper.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + id, data_type, device_no, contents, create_time, update_time + + + + diff --git a/src/main/sql/audio_callback_data.sql b/src/main/sql/audio_callback_data.sql new file mode 100644 index 0000000..0cacbbd --- /dev/null +++ b/src/main/sql/audio_callback_data.sql @@ -0,0 +1,53 @@ +/* + Navicat Premium Data Transfer + + Source Server Type : MySQL + Source Server Version : 80042 + Target Server Type : MySQL + Target Server Version : 80042 + File Encoding : 65001 + + Date: 2025-01-29 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for audio_callback_data +-- ---------------------------- +DROP TABLE IF EXISTS `yhy_audio_upload_log`; + +CREATE TABLE `yhy_audio_upload_log` ( + `id` char(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '主键,UUID', + `file_path` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '文件路径', + `content_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '内容类型(如:audio/mpeg)', + `device_no` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '设备号', + `server_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '服务器回调URL', + `push_data_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '推送数据类型(如:Audio)', + `file_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '文件名', + `save_path` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '保存路径', + `chunk_index` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '分块索引(如:A001)', + `start_time` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '开始时间(格式:251129185131)', + `end_time` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '结束时间(格式:251129190131)', + `usr_no` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户号', + `device_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '设备类型(如:Hanging4G)', + `has_body` tinyint(1) NULL DEFAULT 0 COMMENT '是否有主体(0-否,1-是)', + `extended` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '扩展信息(JSON格式)', + `user_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户ID', + `user_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户名', + `user_phone` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户电话', + `user_dept` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户部门', + `sync_stage` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '同步阶段', + `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '系统创建时间', + `update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '系统更新时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_device_no`(`device_no`) USING BTREE COMMENT '设备号索引', + INDEX `idx_file_name`(`file_name`) USING BTREE COMMENT '文件名索引', + INDEX `idx_user_id`(`user_id`) USING BTREE COMMENT '用户ID索引', + INDEX `idx_start_time`(`start_time`) USING BTREE COMMENT '开始时间索引', + INDEX `idx_create_time`(`create_time`) USING BTREE COMMENT '创建时间索引' +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '音频上传日志数据表' ROW_FORMAT = Dynamic; + +SET FOREIGN_KEY_CHECKS = 1; + diff --git a/src/main/sql/heartbeat_log.sql b/src/main/sql/heartbeat_log.sql new file mode 100644 index 0000000..7d90897 --- /dev/null +++ b/src/main/sql/heartbeat_log.sql @@ -0,0 +1,44 @@ +/* + Navicat Premium Data Transfer + + Source Server Type : MySQL + Source Server Version : 80042 + Target Server Type : MySQL + Target Server Version : 80042 + File Encoding : 65001 + + Date: 2025-01-01 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for yhy_heartbeat_log +-- ---------------------------- +DROP TABLE IF EXISTS `yhy_heartbeat_log`; +CREATE TABLE `yhy_heartbeat_log` ( + `id` char(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '主键,UUID', + `data_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '数据类型,固定值:HeartbeatLog', + `time_stamp` datetime NULL DEFAULT NULL COMMENT '时间戳', + `device_no` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '设备号', + `device_status` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '设备状态(如:Online)', + `power_status` tinyint(1) NULL DEFAULT 0 COMMENT '电源状态(0-关闭,1-开启)', + `remain_power` int NULL DEFAULT NULL COMMENT '剩余电量(0-100)', + `remain_storage_size` bigint NULL DEFAULT NULL COMMENT '剩余存储空间(字节)', + `heartbeat_time` datetime NULL DEFAULT NULL COMMENT '心跳时间', + `device_ver` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '设备版本(如:主:1.4.4 音频:1.4.2)', + `extend` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '扩展信息', + `body` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '主体信息', + `created_time` datetime NULL DEFAULT NULL COMMENT '数据创建时间(来自原始数据)', + `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '系统创建时间', + `update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '系统更新时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_device_no`(`device_no`) USING BTREE COMMENT '设备号索引', + INDEX `idx_heartbeat_time`(`heartbeat_time`) USING BTREE COMMENT '心跳时间索引', + INDEX `idx_time_stamp`(`time_stamp`) USING BTREE COMMENT '时间戳索引', + INDEX `idx_create_time`(`create_time`) USING BTREE COMMENT '创建时间索引' +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '心跳日志表' ROW_FORMAT = Dynamic; + +SET FOREIGN_KEY_CHECKS = 1; + diff --git a/src/main/sql/yhy_datatype_log.sql b/src/main/sql/yhy_datatype_log.sql new file mode 100644 index 0000000..9fbe790 --- /dev/null +++ b/src/main/sql/yhy_datatype_log.sql @@ -0,0 +1,35 @@ +/* + Navicat Premium Data Transfer + + Source Server Type : MySQL + Source Server Version : 80042 + Target Server Type : MySQL + Target Server Version : 80042 + File Encoding : 65001 + + Date: 2025-01-01 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for yhy_datatype_log +-- ---------------------------- +DROP TABLE IF EXISTS `yhy_datatype_log`; +CREATE TABLE `yhy_datatype_log` ( + `id` char(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '主键,UUID', + `data_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '数据类型,固定值:HeartbeatLog', + `device_no` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '设备号', + `contents` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '内容', + `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '系统创建时间', + `update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '系统更新时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_device_no`(`device_no`) USING BTREE COMMENT '设备号索引', + INDEX `idx_create_time`(`create_time`) USING BTREE COMMENT '创建时间索引' +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '心跳日志表' ROW_FORMAT = Dynamic; + +SET FOREIGN_KEY_CHECKS = 1; + + +