package com.ard.work.service.impl; import com.ard.common.utils.DateUtils; import com.ard.common.utils.file.MimeTypeUtils; import com.ard.common.utils.uuid.IdUtils; import com.ard.oss.service.impl.OssServiceContext; import com.ard.work.component.forest.TyClient; import com.ard.work.domain.dto.BoxHistoryDTO; import com.ard.work.domain.dto.BoxPhotosDTO; import com.ard.work.domain.entity.ArdFlightTask; import com.ard.work.domain.entity.ArdMedia; import com.ard.work.domain.receiver.BoxHistoryReceiver; import com.ard.work.domain.receiver.BoxPhotosReceiver; import com.ard.work.domain.receiver.TyHttpCommonReceiver; import com.ard.work.mapper.ArdFlightTaskMapper; import com.ard.work.mapper.ArdMediaMapper; import com.ard.work.service.IArdFlightTaskService; import com.ard.work.service.IArdServerInfoService; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Service; import javax.annotation.Resource; import javax.net.ssl.*; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @Slf4j @Service public class ArdFlightTaskServiceServiceImpl implements IArdFlightTaskService { @Resource private ArdFlightTaskMapper ardFlightTaskMapper; @Resource private ArdMediaMapper ardMediaMapper; @Resource private IArdServerInfoService ardServerInfoService; @Resource private TyClient tyClient; @Resource private OssServiceContext ossServiceContext; @Resource private ThreadPoolTaskExecutor threadPoolTaskExecutor; // 使用线程池执行异步任务 @Override public List selectArdFlightTask(ArdFlightTask ardFlightTask) { return ardFlightTaskMapper.selectArdFlightTask(ardFlightTask); } // 同步所有服务的飞行历史 @Scheduled(fixedDelay = 10000) public void syncAllFlightHistory() { List serverIds = ardServerInfoService.getServerIdsList(); for (String serverId : serverIds) { // 获取上次同步的时间(即数据库中最大 startTime) Date lastSyncTime = getLastSyncTimeForServer(serverId); // 根据上次同步时间同步飞行历史 syncFlightHistory(serverId, lastSyncTime); } } // 获取上次同步时间(获取最大 startTime) private Date getLastSyncTimeForServer(String serverId) { Date maxStartTime = ardFlightTaskMapper.findMaxStartTimeByServerId(serverId); return maxStartTime != null ? maxStartTime : new Date(0); // 如果没有数据,返回 Unix 纪元时间作为起点 } // 调用接口获取飞行历史数据 // 根据上次同步时间同步飞行历史 public void syncFlightHistory(String serverId, Date lastSyncTime) { // 创建查询条件 BoxHistoryDTO,并设置 startTime 作为过滤条件 BoxHistoryDTO boxHistoryDTO = new BoxHistoryDTO(); boxHistoryDTO.setStartTime(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, lastSyncTime)); // 传递 lastSyncTime 参数 // 获取飞行历史 List historyList = getBoxHistory(serverId, boxHistoryDTO); // 如果获取到数据,处理并同步到本地数据库 if (historyList != null && !historyList.isEmpty()) { for (BoxHistoryReceiver boxHistory : historyList) { syncToLocalDatabase(serverId, boxHistory); } } } // 调用接口获取飞行历史数据 private List getBoxHistory(String serverId, BoxHistoryDTO boxHistoryDTO) { TyHttpCommonReceiver> boxHistory = tyClient.getBoxHistory(serverId, boxHistoryDTO); return boxHistory.getContent(); } // 将数据同步到本地数据库 private void syncToLocalDatabase(String serverId, BoxHistoryReceiver boxHistoryReceiver) { // 创建FlightTask实体对象并填充数据 ArdFlightTask flightTask = new ArdFlightTask(); flightTask.setTaskId(boxHistoryReceiver.getTaskId()); flightTask.setBoxSn(boxHistoryReceiver.getBoxSn()); flightTask.setBoxName(boxHistoryReceiver.getBoxName()); flightTask.setTaskName(boxHistoryReceiver.getTaskName()); Integer photoCount = Integer.parseInt(boxHistoryReceiver.getPhotoCount()); flightTask.setPhotoCount(photoCount); Integer videoCount = (boxHistoryReceiver.getVideoPathList() != null) ? boxHistoryReceiver.getVideoPathList().size() : 0; flightTask.setVideoCount(videoCount); flightTask.setVideoPath(boxHistoryReceiver.getVideoPath()); flightTask.setServerId(serverId); Date startTime = DateUtils.dateTime(DateUtils.YYYY_MM_DD_HH_MM_SS, boxHistoryReceiver.getStartTime()); flightTask.setStartTime(startTime); Date endTime = DateUtils.dateTime(DateUtils.YYYY_MM_DD_HH_MM_SS, boxHistoryReceiver.getEndTime()); flightTask.setEndTime(endTime); flightTask.setCreateTime(DateUtils.getNowDate()); // 检查是否已经存在相同任务ID的记录 ArdFlightTask ardFlightTask = ardFlightTaskMapper.getArdFlightTaskById(flightTask.getTaskId()); if (ardFlightTask == null) { // 如果不存在,则插入新记录 log.info("Syncing task {}", boxHistoryReceiver.getTaskId()); // 插入新记录 int rows = ardFlightTaskMapper.insertArdFlightTask(flightTask); if (rows > 0) { // 删除旧的媒体记录,防止重复插入 ardMediaMapper.deleteArdMediaByTaskId(flightTask.getTaskId()); // 插入视频 syncVideoToDatabase(flightTask, boxHistoryReceiver.getVideoPathList()); // 插入图片 syncPhotoToDatabase(flightTask, serverId, boxHistoryReceiver.getBoxSn()); } } else { // 如果存在,则更新记录 log.info("Updating task {}", boxHistoryReceiver.getTaskId()); // 增量更新 updateVideoToDatabase(ardFlightTask, boxHistoryReceiver.getVideoPathList()); // 增量更新图片 updatePhotoToDatabase(ardFlightTask, serverId, boxHistoryReceiver.getBoxSn()); } } // 插入视频记录 private void syncVideoToDatabase(ArdFlightTask flightTask, List videoPathList) { if (videoPathList != null && !videoPathList.isEmpty()) { for (String videoPath : videoPathList) { ArdMedia ardMedia = new ArdMedia(); ardMedia.setId(IdUtils.simpleUUID()); ardMedia.setTaskId(flightTask.getTaskId()); ardMedia.setMediaType(1); // 1表示视频 ardMedia.setSourceUrl(videoPath); // 异步上传文件到OSS,并在上传完成后插入数据库 CompletableFuture.supplyAsync(() -> uploadFileToOss(flightTask.getBoxSn(), videoPath, "record", MimeTypeUtils.VIDEO_MP4), threadPoolTaskExecutor) .thenAccept(url -> { ardMedia.setMediaUrl(url); // 获取上传后的文件 URL ardMedia.setCreateTime(new Date()); ardMediaMapper.insertArdMedia(ardMedia); // 插入数据库 }); } } } private void updateVideoToDatabase(ArdFlightTask flightTask, List videoPathList) { if (videoPathList == null) { return; } Integer count = flightTask.getVideoCount();//获取本地库视频数量 int size = videoPathList.size();//获取接口返回的视频数量 if (count != null && size > count) { log.info("录像数量不一致,需要更新数据库"); // 需要插入新的视频 // 获取本地数据库中已存在的视频记录 List existingVideos = ardMediaMapper.selectArdMediaByTaskIdAndType(flightTask.getTaskId(), 1); List newVideos = getNewVideos(existingVideos, videoPathList); if (!newVideos.isEmpty()) { // 插入新的视频记录 for (String videoPath : newVideos) { ArdMedia ardMedia = new ArdMedia(); ardMedia.setId(IdUtils.simpleUUID()); ardMedia.setTaskId(flightTask.getTaskId()); ardMedia.setMediaType(1); // 1表示视频 ardMedia.setSourceUrl(videoPath); // 异步上传文件到OSS,并在上传完成后插入数据库 CompletableFuture.supplyAsync(() -> uploadFileToOss(flightTask.getBoxSn(), videoPath, "record", MimeTypeUtils.VIDEO_MP4), threadPoolTaskExecutor) .thenAccept(url -> { ardMedia.setMediaUrl(url); // 获取上传后的文件 URL ardMedia.setCreateTime(new Date()); ardMediaMapper.insertArdMedia(ardMedia); // 插入数据库 }); } // 更新任务的视频数量 flightTask.setVideoCount(flightTask.getVideoCount() + newVideos.size()); ardFlightTaskMapper.updateArdFlightTask(flightTask); } } } // 获取需要插入的新视频路径 private List getNewVideos(List existingVideos, List videoPathList) { // 提取现有的视频路径 List existingVideoPaths = existingVideos.stream() .map(ArdMedia::getSourceUrl) // 获取每个视频的 URL .collect(Collectors.toList()); // 从接口返回的视频路径中找出新的视频 List newVideos = videoPathList.stream() .filter(videoPath -> !existingVideoPaths.contains(videoPath)) // 过滤出新的视频 .collect(Collectors.toList()); return newVideos; } // 插入图片记录 private void syncPhotoToDatabase(ArdFlightTask flightTask, String serverId, String boxSn) { // 查询图片 BoxPhotosDTO boxPhotosDTO = new BoxPhotosDTO(); boxPhotosDTO.setTaskId(flightTask.getTaskId()); boxPhotosDTO.setBoxSn(boxSn); boxPhotosDTO.setPageIndex(1); boxPhotosDTO.setPageSize(500); TyHttpCommonReceiver> boxPhotos = tyClient.getBoxPhotos(serverId, boxPhotosDTO); List photoList = boxPhotos.getContent(); if (photoList != null && !photoList.isEmpty()) { for (BoxPhotosReceiver photo : photoList) { ArdMedia ardMedia = new ArdMedia(); ardMedia.setId(IdUtils.simpleUUID()); ardMedia.setTaskId(flightTask.getTaskId()); ardMedia.setMediaType(0); // 0表示图片 ardMedia.setFileSize(photo.getFileSize()); ardMedia.setFileMd5(photo.getFileMd5()); ardMedia.setSourceUrl(photo.getImgUrl()); // 异步上传文件到OSS,并在上传完成后插入数据库 CompletableFuture.supplyAsync(() -> uploadFileToOss(flightTask.getBoxSn(), photo.getImgUrl(), "pic", MimeTypeUtils.IMAGE_JPG), threadPoolTaskExecutor) .thenAccept(url -> { ardMedia.setMediaUrl(url); // 获取上传后的文件 URL ardMedia.setCreateTime(new Date()); ardMediaMapper.insertArdMedia(ardMedia); // 插入数据库 }); } } } private void updatePhotoToDatabase(ArdFlightTask flightTask, String serverId, String boxSn) { int count = flightTask.getPhotoCount(); //获取本地库图片数量 // 查询图片 BoxPhotosDTO boxPhotosDTO = new BoxPhotosDTO(); boxPhotosDTO.setTaskId(flightTask.getTaskId()); boxPhotosDTO.setBoxSn(boxSn); boxPhotosDTO.setPageIndex(1); boxPhotosDTO.setPageSize(500); TyHttpCommonReceiver> boxPhotos = tyClient.getBoxPhotos(serverId, boxPhotosDTO); List photoList = boxPhotos.getContent(); int size = photoList.size(); if (size > count) { log.info("图片数量不一致,需要更新数据库"); // 获取本地数据库中已存在的图片记录(类型为 0 表示图片) List existingPhotos = ardMediaMapper.selectArdMediaByTaskIdAndType(flightTask.getTaskId(), 0); // 需要插入新的视频 List newPhotos = getNewPhotos(existingPhotos, photoList); if (!newPhotos.isEmpty()) { // 插入新的视频记录 for (BoxPhotosReceiver photo : newPhotos) { ArdMedia ardMedia = new ArdMedia(); ardMedia.setId(IdUtils.simpleUUID()); ardMedia.setTaskId(flightTask.getTaskId()); ardMedia.setMediaType(0); // 0表示图片 ardMedia.setFileSize(photo.getFileSize()); ardMedia.setFileMd5(photo.getFileMd5()); ardMedia.setSourceUrl(photo.getImgUrl()); // 异步上传文件到OSS,并在上传完成后插入数据库 CompletableFuture.supplyAsync(() -> uploadFileToOss(flightTask.getBoxSn(), photo.getImgUrl(), "pic", MimeTypeUtils.IMAGE_JPG), threadPoolTaskExecutor) .thenAccept(url -> { ardMedia.setMediaUrl(url); // 获取上传后的文件 URL ardMedia.setCreateTime(new Date()); ardMediaMapper.insertArdMedia(ardMedia); // 插入数据库 }); } // 更新任务的视频数量 flightTask.setPhotoCount(flightTask.getPhotoCount() + newPhotos.size()); ardFlightTaskMapper.updateArdFlightTask(flightTask); } } } // 获取需要插入的新图片 private List getNewPhotos(List existingPhotos, List photoList) { // 提取现有的图片路径(根据 imgUrl 来判断是否重复) List existingPhotoUrls = existingPhotos.stream() .map(ArdMedia::getSourceUrl) // 提取 ArdMedia 中的图片 URL .collect(Collectors.toList()); // 筛选出 photoList 中没有出现在 existingPhotos 中的新图片 List newPhotos = photoList.stream() .filter(photo -> !existingPhotoUrls.contains(photo.getImgUrl())) // 过滤出新图片 .collect(Collectors.toList()); return newPhotos; // 返回新增的图片列表 } // 通用的上传方法,支持视频和图片等文件上传 public String uploadFileToOss(String boxSn, String fileUrl, String bucket, String contentType) { InputStream fileInputStream = null; String fileOssUrl = ""; try { // 创建信任所有证书的SSLContext TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager() { @Override public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; } @Override public void checkClientTrusted( java.security.cert.X509Certificate[] certs, String authType) { } @Override public void checkServerTrusted( java.security.cert.X509Certificate[] certs, String authType) { } } }; SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, trustAllCerts, new java.security.SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); // 创建所有主机名都信任的HostnameVerifier HostnameVerifier allHostsValid = new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { return true; } }; HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid); // 现在可以正常访问 // 从URL创建InputStream URL url = new URL(fileUrl); fileInputStream = url.openStream(); // 文件名 String fileName = boxSn + "/" + IdUtils.fastSimpleUUID() + "." + MimeTypeUtils.getExtension(contentType); // 调用OSS服务上下文进行文件上传 fileOssUrl = ossServiceContext.upload(bucket, fileName, fileInputStream, contentType); } catch (Exception e) { e.printStackTrace(); // 异常处理,可以选择记录日志或者抛出业务异常 } finally { // 关闭InputStream if (fileInputStream != null) { try { fileInputStream.close(); } catch (Exception e) { e.printStackTrace(); } } } return fileOssUrl; // 返回上传后的文件OSS URL } }