package com.ard.sample.media.service.impl; import com.ard.sample.common.error.CommonErrorEnum; import com.ard.sample.common.model.ResponseResult; import com.ard.sample.component.mqtt.model.*; import com.ard.sample.component.mqtt.service.IMessageSenderService; import com.ard.common.core.redis.RedisConst; import com.ard.common.core.redis.RedisOpsUtils; import com.ard.sample.component.websocket.model.BizCodeEnum; import com.ard.sample.component.websocket.model.CustomWebSocketMessage; import com.ard.sample.component.websocket.service.ISendMessageService; import com.ard.sample.component.websocket.service.IWebSocketManageService; import com.ard.sample.manage.model.dto.DeviceDTO; import com.ard.sample.manage.model.enums.UserTypeEnum; import com.ard.sample.manage.service.IDeviceService; import com.ard.sample.media.model.FileUploadCallback; import com.ard.sample.media.model.FileUploadDTO; import com.ard.sample.media.model.MediaFileCountDTO; import com.ard.sample.media.model.MediaFileDTO; import com.ard.sample.media.service.IFileService; import com.ard.sample.media.service.IMediaService; import com.ard.sample.wayline.mapper.IWaylineJobHistoryMapper; import com.ard.sample.wayline.model.dto.WaylineJobDTO; import com.ard.sample.wayline.service.IWaylineJobHistoryService; import com.ard.sample.wayline.service.IWaylineJobService; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.integration.annotation.ServiceActivator; import org.springframework.integration.mqtt.support.MqttHeaders; import org.springframework.messaging.MessageHeaders; import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; /** * @author sean * @version 0.2 * @date 2021/12/9 */ @Slf4j @Service public class MediaServiceImpl implements IMediaService { @Autowired private IFileService fileService; @Autowired private IWaylineJobService waylineJobService; @Autowired private ObjectMapper objectMapper; @Autowired private IMessageSenderService messageSenderService; @Autowired private IDeviceService deviceService; @Autowired private ISendMessageService sendMessageService; @Autowired private IWebSocketManageService webSocketManageService; @Override public Boolean fastUpload(String fingerprint) { return fileService.checkExist(fingerprint); } @Override public Integer saveMediaFile(FileUploadDTO file) { return fileService.saveFile(file); } @Override public List getAllTinyFingerprints() { return fileService.getAllFiles() .stream() .map(MediaFileDTO::getTinnyFingerprint) .collect(Collectors.toList()); } @Override public List getExistTinyFingerprints(List tinyFingerprints) { List tinyFingerprintList = this.getAllTinyFingerprints(); return tinyFingerprints .stream() .filter(tinyFingerprintList::contains) .collect(Collectors.toList()); } /** * 处理文件上传回调事件 * * 该方法接收文件上传回调消息,进行业务处理并返回响应。主要逻辑包括: * 1. 解析回调数据并构建响应对象 * 2. 检查回调结果,非成功状态直接返回 * 3. 验证Redis中存储的媒体文件计数信息,防止重复处理 * 4. 查询设备信息和作业信息,解析媒体文件 * 5. 发布处理结果到指定主题 * 6. 通知上传计数更新 * * @param receiver 包含文件上传回调数据的通用主题接收器对象 * 包含以下关键字段: * - data: 回调数据(JSON格式) * - gateway: 设备网关标识 * - tid: 事务ID * - bid: 业务ID */ @Override @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_FILE_UPLOAD_CALLBACK, outputChannel = ChannelName.OUTBOUND) public void handleFileUploadCallBack(CommonTopicReceiver receiver) { FileUploadCallback callback = objectMapper.convertValue(receiver.getData(), FileUploadCallback.class); log.info("媒体库回调-原始报文内容:{}",receiver); log.info("媒体库调试-收到文件上传回调:{}",callback); String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + receiver.getGateway() + TopicConst.EVENTS_SUF + TopicConst._REPLY_SUF; CommonTopicResponse data = CommonTopicResponse.builder() .timestamp(System.currentTimeMillis()) .method(EventsMethodEnum.FILE_UPLOAD_CALLBACK.getMethod()) .data(RequestsReply.success()) .tid(receiver.getTid()) .bid(receiver.getBid()) .build(); if (callback.getResult() != ResponseResult.CODE_SUCCESS) { messageSenderService.publish(topic, data); log.info("媒体库调试-回调失败:{}",callback); return; } String jobId = callback.getFile().getExt().getFlightId(); MediaFileCountDTO mediaFileCount = (MediaFileCountDTO) RedisOpsUtils.hashGet(RedisConst.MEDIA_FILE_PREFIX + receiver.getGateway(), jobId); // duplicate data if (receiver.getBid().equals(mediaFileCount.getBid()) && receiver.getTid().equals(mediaFileCount.getTid())) { messageSenderService.publish(topic, data); return; } DeviceDTO device = (DeviceDTO) RedisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + receiver.getGateway()); Optional jobOpt = waylineJobService.getJobByJobId(jobId); if (jobOpt.isPresent()) { boolean isSave = parseMediaFile(callback, jobOpt.get()); log.info("媒体库收到数据:{}",callback); if (!isSave) { data.setData(RequestsReply.error(CommonErrorEnum.ILLEGAL_ARGUMENT)); log.info("媒体库保存失败:{}",callback); } } messageSenderService.publish(topic, data); notifyUploadedCount(mediaFileCount, receiver, jobId); } /** * 更新上传的计数并通知网页端 * @param mediaFileCount * @param receiver * @param jobId */ private void notifyUploadedCount(MediaFileCountDTO mediaFileCount, CommonTopicReceiver receiver, String jobId) { mediaFileCount.setBid(receiver.getBid()); mediaFileCount.setTid(receiver.getTid()); mediaFileCount.setUploadedCount(mediaFileCount.getUploadedCount() + 1); String key = RedisConst.MEDIA_FILE_PREFIX + receiver.getGateway(); // After all the files of the job are uploaded, delete the media file key. if (mediaFileCount.getUploadedCount() >= mediaFileCount.getMediaCount()) { RedisOpsUtils.hashDel(key, new String[]{jobId}); // After uploading, delete the key with the highest priority. String highestKey = RedisConst.MEDIA_HIGHEST_PRIORITY_PREFIX + receiver.getGateway(); if (RedisOpsUtils.checkExist(highestKey) && jobId.equals(((MediaFileCountDTO) RedisOpsUtils.get(highestKey)).getJobId())) { RedisOpsUtils.del(highestKey); } if (RedisOpsUtils.hashLen(key) == 0) { RedisOpsUtils.del(key); } } else { RedisOpsUtils.hashSet(key, jobId, mediaFileCount); } DeviceDTO dock = (DeviceDTO) RedisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + receiver.getGateway()); sendMessageService.sendBatch(webSocketManageService.getValueAndUserType(UserTypeEnum.WEB.getVal()), CustomWebSocketMessage.builder() .bizCode(BizCodeEnum.FILE_UPLOAD_CALLBACK.getCode()) .timestamp(System.currentTimeMillis()) .data(mediaFileCount) .build()); } /** * 解析媒体文件并设置相关属性 * *

该函数主要完成两个操作: * 1. 根据任务中的机库序列号设置拍摄媒体的无人机序列号 * 2. 从文件对象键中提取路径并设置到文件对象中 * 最后尝试保存文件并返回是否保存成功 * * @param callback 文件上传回调对象,包含待处理的文件信息 * @param job 航线任务数据传输对象,包含机库序列号等信息 * @return 返回true表示文件保存成功,false表示保存失败 */ private Boolean parseMediaFile(FileUploadCallback callback, WaylineJobDTO job) { // 设置拍摄媒体的无人机 Optional dockDTO = deviceService.getDeviceBySn(job.getDockSn()); dockDTO.ifPresent(dock -> callback.getFile().getExt().setSn(dock.getChildDeviceSn())); // 设定路径 String objectKey = callback.getFile().getObjectKey()!= null ? callback.getFile().getObjectKey() : ""; callback.getFile().setPath(objectKey.substring(objectKey.indexOf("/") + 1, objectKey.lastIndexOf("/"))); log.info("保存媒体库数据:{}",callback); return fileService.saveFile(callback.getFile()) > 0; } /** * 处理最高优先级上传飞行任务媒体事件 * * 该方法处理来自MQTT通道的最高优先级上传飞行任务媒体请求,主要功能包括: * 1. 验证请求数据有效性 * 2. 发送成功响应 * 3. 更新Redis中媒体文件计数信息 * 4. 通过WebSocket通知前端 * * @param receiver 包含请求数据的接收器对象,包含方法、bid、tid等信息 * @param headers 消息头信息,包含接收的MQTT主题等元数据 */ @Override @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_HIGHEST_PRIORITY_UPLOAD_FLIGHT_TASK_MEDIA, outputChannel = ChannelName.OUTBOUND) public void handleHighestPriorityUploadFlightTaskMedia(CommonTopicReceiver receiver, MessageHeaders headers) { Map map = objectMapper.convertValue(receiver.getData(), Map.class); if (map.isEmpty() || !map.containsKey(MapKeyConst.FLIGHT_ID)) { return; } messageSenderService.publish(headers.get(MqttHeaders.RECEIVED_TOPIC) + TopicConst._REPLY_SUF, CommonTopicResponse.builder() .data(RequestsReply.success()) .method(receiver.getMethod()) .timestamp(System.currentTimeMillis()) .bid(receiver.getBid()) .tid(receiver.getTid()) .build()); String dockSn = receiver.getGateway(); String jobId = map.get(MapKeyConst.FLIGHT_ID).toString(); String key = RedisConst.MEDIA_HIGHEST_PRIORITY_PREFIX + dockSn; MediaFileCountDTO countDTO = new MediaFileCountDTO(); if (RedisOpsUtils.checkExist(key)) { countDTO = (MediaFileCountDTO) RedisOpsUtils.get(key); if (jobId.equals(countDTO.getJobId())) { RedisOpsUtils.setWithExpire(key, countDTO, RedisConst.DEVICE_ALIVE_SECOND * 5); return; } countDTO.setPreJobId(countDTO.getJobId()); } countDTO.setJobId(jobId); RedisOpsUtils.setWithExpire(key, countDTO, RedisConst.DEVICE_ALIVE_SECOND * 5); DeviceDTO dock = (DeviceDTO) RedisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + dockSn); sendMessageService.sendBatch(webSocketManageService.getValueAndUserType(UserTypeEnum.WEB.getVal()), CustomWebSocketMessage.builder() .timestamp(System.currentTimeMillis()) .bizCode(BizCodeEnum.HIGHEST_PRIORITY_UPLOAD_FLIGHT_TASK_MEDIA.getCode()) .data(countDTO) .build()); } }