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<String> getAllTinyFingerprints() {
|
return fileService.getAllFiles()
|
.stream()
|
.map(MediaFileDTO::getTinnyFingerprint)
|
.collect(Collectors.toList());
|
}
|
|
@Override
|
public List<String> getExistTinyFingerprints(List<String> tinyFingerprints) {
|
List<String> 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("媒体库调试-收到文件上传回调:{}",callback);
|
String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + receiver.getGateway()
|
+ TopicConst.EVENTS_SUF + TopicConst._REPLY_SUF;
|
CommonTopicResponse<RequestsReply> data = CommonTopicResponse.<RequestsReply>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<WaylineJobDTO> 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());
|
}
|
|
/**
|
* 解析媒体文件并设置相关属性
|
*
|
* <p>该函数主要完成两个操作:
|
* 1. 根据任务中的机库序列号设置拍摄媒体的无人机序列号
|
* 2. 从文件对象键中提取路径并设置到文件对象中
|
* 最后尝试保存文件并返回是否保存成功
|
*
|
* @param callback 文件上传回调对象,包含待处理的文件信息
|
* @param job 航线任务数据传输对象,包含机库序列号等信息
|
* @return 返回true表示文件保存成功,false表示保存失败
|
*/
|
private Boolean parseMediaFile(FileUploadCallback callback, WaylineJobDTO job) {
|
// 设置拍摄媒体的无人机
|
Optional<DeviceDTO> dockDTO = deviceService.getDeviceBySn(job.getDockSn());
|
dockDTO.ifPresent(dock -> callback.getFile().getExt().setSn(dock.getChildDeviceSn()));
|
|
// 设定路径
|
String objectKey = 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());
|
|
}
|
}
|