diff --git a/src/main/java/com/rj/controller/SoundRecordingUploadController.java b/src/main/java/com/rj/controller/SoundRecordingUploadController.java new file mode 100644 index 0000000..b0786ab --- /dev/null +++ b/src/main/java/com/rj/controller/SoundRecordingUploadController.java @@ -0,0 +1,151 @@ +package com.rj.controller; + +import com.rj.service.ISoundRecordingUploadService; +import com.rj.service.impl.SoundRecordingUploadServiceImpl; +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.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 录音上传控制器 + * 除座席机外,本接口适用于本公司所有硬件产品 + * + * @author rj + * @date 2025-01-03 + */ +@RestController +@RequestMapping("/api/recording") +@Tag(name = "录音上传", description = "设备端录音文件上传接口") +@Slf4j +public class SoundRecordingUploadController { + + @Autowired + private ISoundRecordingUploadService soundRecordingUploadService; + + /** + * 录音上传接口 + * 设备端以http协议发送文件,Content-Type:multipart/form-data + * 认证方式:暂定无 + * + * @param deviceNo 设备序列号(必填) + * @param deviceType 设备类型(可选) + * @param soundRecording 文件数据流(必填) + * @param fileName 文件名称(必填) + * @param chunkIndex 分片索引(必填),A开头表示启动/运行段,Z开头表示停止段 + * @param startTime 录音开始时间,格式:yyMMddHHmmss(必填) + * @param endTime 录音结束时间,格式:yyMMddHHmmss(必填) + * @param usrNo 自定义编号(可选) + * @return 上传结果 + */ + @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @Operation(summary = "录音上传", description = "设备端录音文件上传接口,支持分片上传") + public ResponseEntity uploadRecording( + @Parameter(description = "设备序列号", required = true) + @RequestParam("deviceNo") String deviceNo, + @Parameter(description = "设备类型") + @RequestParam(value = "deviceType", required = false) String deviceType, + @Parameter(description = "录音文件数据流", required = true) + @RequestParam("soundRecording") MultipartFile soundRecording, + @Parameter(description = "文件名称", required = true) + @RequestParam("fileName") String fileName, + @Parameter(description = "分片索引,A开头表示启动/运行段,Z开头表示停止段", required = true) + @RequestParam("chunkIndex") String chunkIndex, + @Parameter(description = "录音开始时间,格式:yyMMddHHmmss", required = true) + @RequestParam("startTime") String startTime, + @Parameter(description = "录音结束时间,格式:yyMMddHHmmss", required = true) + @RequestParam("endTime") String endTime, + @Parameter(description = "自定义编号") + @RequestParam(value = "usrNo", required = false) String usrNo) { + + log.info("收到录音上传请求 - deviceNo: {}, fileName: {}, chunkIndex: {}, startTime: {}, endTime: {}", + deviceNo, fileName, chunkIndex, startTime, endTime); + + // 调用服务层处理业务逻辑 + SoundRecordingUploadServiceImpl.ResponseData serviceResponse = soundRecordingUploadService.uploadRecording( + deviceNo, deviceType, soundRecording, fileName, chunkIndex, startTime, endTime, usrNo); + + // 转换服务层返回的结果为响应对象 + SoundRecordingUploadResponse response = new SoundRecordingUploadResponse(); + response.setCode(serviceResponse.code); + response.setStatus(serviceResponse.status); + response.setSuccess(serviceResponse.success); + response.setMessage(serviceResponse.message); + response.setTimestamp(serviceResponse.timestamp); + return ResponseEntity.ok(response); + } + + /** + * 录音上传请求DTO(内部类) + */ + @Schema(description = "录音上传请求参数") + public static class SoundRecordingUploadRequest { + private String deviceNo; + private String deviceType; + private MultipartFile soundRecording; + private String fileName; + private String chunkIndex; + private String startTime; + private String endTime; + private String usrNo; + + // Getters and Setters + public String getDeviceNo() { return deviceNo; } + public void setDeviceNo(String deviceNo) { this.deviceNo = deviceNo; } + public String getDeviceType() { return deviceType; } + public void setDeviceType(String deviceType) { this.deviceType = deviceType; } + public MultipartFile getSoundRecording() { return soundRecording; } + public void setSoundRecording(MultipartFile soundRecording) { this.soundRecording = soundRecording; } + public String getFileName() { return fileName; } + public void setFileName(String fileName) { this.fileName = fileName; } + public String getChunkIndex() { return chunkIndex; } + public void setChunkIndex(String chunkIndex) { this.chunkIndex = chunkIndex; } + public String getStartTime() { return startTime; } + public void setStartTime(String startTime) { this.startTime = startTime; } + public String getEndTime() { return endTime; } + public void setEndTime(String endTime) { this.endTime = endTime; } + public String getUsrNo() { return usrNo; } + public void setUsrNo(String usrNo) { this.usrNo = usrNo; } + } + + /** + * 录音上传响应DTO(内部类) + */ + @Schema(description = "录音上传响应") + public static class SoundRecordingUploadResponse { + @Schema(description = "状态码,200或204表示成功", example = "200") + private Integer code; + + @Schema(description = "上传状态,0表示成功", example = "0") + private Integer status; + + @Schema(description = "是否成功", example = "true") + private Boolean success; + + @Schema(description = "消息", example = "请求成功") + private String message; + + @Schema(description = "时间戳(单位:秒)", example = "1704268800") + private Long timestamp; + + // Getters and Setters + public Integer getCode() { return code; } + public void setCode(Integer code) { this.code = code; } + public Integer getStatus() { return status; } + public void setStatus(Integer status) { this.status = status; } + public Boolean getSuccess() { return success; } + public void setSuccess(Boolean success) { this.success = success; } + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } + public Long getTimestamp() { return timestamp; } + public void setTimestamp(Long timestamp) { this.timestamp = timestamp; } + } +} + diff --git a/src/main/java/com/rj/service/ISoundRecordingUploadService.java b/src/main/java/com/rj/service/ISoundRecordingUploadService.java new file mode 100644 index 0000000..97a8526 --- /dev/null +++ b/src/main/java/com/rj/service/ISoundRecordingUploadService.java @@ -0,0 +1,32 @@ +package com.rj.service; + +import com.rj.service.impl.SoundRecordingUploadServiceImpl; +import org.springframework.web.multipart.MultipartFile; + +/** + * 录音上传服务接口 + * + * @author rj + * @date 2025-01-03 + */ +public interface ISoundRecordingUploadService { + + /** + * 上传录音文件 + * + * @param deviceNo 设备序列号 + * @param deviceType 设备类型 + * @param soundRecording 录音文件 + * @param fileName 文件名称 + * @param chunkIndex 分片索引 + * @param startTime 录音开始时间 + * @param endTime 录音结束时间 + * @param usrNo 自定义编号 + * @return 上传结果响应对象 + */ + SoundRecordingUploadServiceImpl.ResponseData uploadRecording(String deviceNo, String deviceType, + MultipartFile soundRecording, + String fileName, String chunkIndex, + String startTime, String endTime, String usrNo); +} + diff --git a/src/main/java/com/rj/service/impl/SoundRecordingUploadServiceImpl.java b/src/main/java/com/rj/service/impl/SoundRecordingUploadServiceImpl.java new file mode 100644 index 0000000..76c697a --- /dev/null +++ b/src/main/java/com/rj/service/impl/SoundRecordingUploadServiceImpl.java @@ -0,0 +1,183 @@ +package com.rj.service.impl; + +import com.rj.service.ISoundRecordingUploadService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.time.Instant; +import java.util.regex.Pattern; + +/** + * 录音上传服务实现类 + * + * @author rj + * @date 2025-01-03 + */ +@Slf4j +@Service +public class SoundRecordingUploadServiceImpl implements ISoundRecordingUploadService { + + /** + * 分片索引正则表达式 + * A开头:启动/运行段,如A001, A005 + * Z开头:停止段,Z后面数字表示总段数,如Z999表示总共999段,Z001表示只有一段 + */ + private static final Pattern CHUNK_INDEX_PATTERN = Pattern.compile("^[AZ]\\d{3}$"); + + /** + * 时间格式正则表达式(yyMMddHHmmss) + */ + private static final Pattern TIME_PATTERN = Pattern.compile("^\\d{12}$"); + + @Override + public ResponseData uploadRecording(String deviceNo, String deviceType, MultipartFile soundRecording, + String fileName, String chunkIndex, String startTime, String endTime, String usrNo) { + + log.info("开始处理录音上传 - deviceNo: {}, fileName: {}, chunkIndex: {}, startTime: {}, endTime: {}", + deviceNo, fileName, chunkIndex, startTime, endTime); + + try { + // 参数验证 + String validationError = validateParameters(deviceNo, soundRecording, fileName, chunkIndex, startTime, endTime); + if (validationError != null) { + log.warn("参数验证失败 - deviceNo: {}, error: {}", deviceNo, validationError); + return createErrorResponse(400, 1, validationError); + } + + // 解析分片信息 + ChunkInfo chunkInfo = parseChunkIndex(chunkIndex); + log.info("分片信息解析 - prefix: {}, chunkNumber: {}, isLastChunk: {}, totalChunks: {}", + chunkInfo.prefix, chunkInfo.chunkNumber, chunkInfo.isLastChunk, chunkInfo.totalChunks); + + // TODO: 这里可以添加文件保存逻辑 + // 1. 保存文件到指定目录 + // 2. 如果是分片上传,需要管理分片状态 + // 3. 如果是最后一片,需要合并分片或标记完成 + + log.info("录音文件上传成功 - deviceNo: {}, fileName: {}, fileSize: {} bytes", + deviceNo, fileName, soundRecording.getSize()); + + // 返回成功响应 + return createSuccessResponse(); + + } catch (Exception e) { + log.error("录音上传处理异常 - deviceNo: {}, fileName: {}, error: {}", + deviceNo, fileName, e.getMessage(), e); + return createErrorResponse(500, 1, "服务器内部错误:" + e.getMessage()); + } + } + + /** + * 验证参数 + */ + private String validateParameters(String deviceNo, MultipartFile soundRecording, String fileName, + String chunkIndex, String startTime, String endTime) { + + if (deviceNo == null || deviceNo.trim().isEmpty()) { + return "设备序列号不能为空"; + } + + if (soundRecording == null || soundRecording.isEmpty()) { + return "录音文件不能为空"; + } + + if (fileName == null || fileName.trim().isEmpty()) { + return "文件名称不能为空"; + } + + if (chunkIndex == null || chunkIndex.trim().isEmpty()) { + return "分片索引不能为空"; + } + + // 验证分片索引格式 + if (!CHUNK_INDEX_PATTERN.matcher(chunkIndex).matches()) { + return "分片索引格式错误,应为A开头或Z开头后跟3位数字,如A001或Z999"; + } + + if (startTime == null || startTime.trim().isEmpty()) { + return "录音开始时间不能为空"; + } + + if (!TIME_PATTERN.matcher(startTime).matches()) { + return "录音开始时间格式错误,应为yyMMddHHmmss,如240510130000"; + } + + if (endTime == null || endTime.trim().isEmpty()) { + return "录音结束时间不能为空"; + } + + if (!TIME_PATTERN.matcher(endTime).matches()) { + return "录音结束时间格式错误,应为yyMMddHHmmss,如240510135959"; + } + + return null; + } + + /** + * 解析分片索引 + */ + private ChunkInfo parseChunkIndex(String chunkIndex) { + char prefix = chunkIndex.charAt(0); + int chunkNumber = Integer.parseInt(chunkIndex.substring(1)); + boolean isLastChunk = prefix == 'Z'; + int totalChunks = isLastChunk ? chunkNumber : 0; + + return new ChunkInfo(prefix, chunkNumber, isLastChunk, totalChunks); + } + + /** + * 创建成功响应 + */ + private ResponseData createSuccessResponse() { + ResponseData response = new ResponseData(); + response.code = 200; + response.status = 0; + response.success = true; + response.message = "请求成功"; + response.timestamp = Instant.now().getEpochSecond(); + return response; + } + + /** + * 创建错误响应 + */ + private ResponseData createErrorResponse(int code, int status, String message) { + ResponseData response = new ResponseData(); + response.code = code; + response.status = status; + response.success = false; + response.message = message; + response.timestamp = Instant.now().getEpochSecond(); + return response; + } + + /** + * 分片信息内部类 + */ + private static class ChunkInfo { + char prefix; + int chunkNumber; + boolean isLastChunk; + int totalChunks; + + ChunkInfo(char prefix, int chunkNumber, boolean isLastChunk, int totalChunks) { + this.prefix = prefix; + this.chunkNumber = chunkNumber; + this.isLastChunk = isLastChunk; + this.totalChunks = totalChunks; + } + } + + /** + * 响应数据内部类 + */ + public static class ResponseData { + public Integer code; + public Integer status; + public Boolean success; + public String message; + public Long timestamp; + } +} + diff --git a/src/test/java/com/rj/audio/ATest.java b/src/test/java/com/rj/audio/ATest.java new file mode 100644 index 0000000..dc940d0 --- /dev/null +++ b/src/test/java/com/rj/audio/ATest.java @@ -0,0 +1,7 @@ +package com.rj.audio; + +public class ATest { + public static void main(String[] args) { + System.out.println("ddddddddddddddddddfffffffffffffffff"); + } +}