package com.dji.sample.manage.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.dji.sample.component.mqtt.model.EventsReceiver; import com.dji.sample.component.oss.model.OssConfiguration; import com.dji.sample.component.oss.service.impl.OssServiceContext; import com.dji.sample.component.redis.RedisConst; import com.dji.sample.component.redis.RedisOpsUtils; import com.dji.sample.component.websocket.model.BizCodeEnum; import com.dji.sample.component.websocket.service.IWebSocketMessageService; import com.dji.sample.component.websocketWmm.WebSocketServerPlayBack; import com.dji.sample.manage.dao.IDeviceFirmwareMapper; import com.dji.sample.manage.model.dto.*; import com.dji.sample.manage.model.entity.DeviceFirmwareEntity; import com.dji.sample.manage.model.enums.UserTypeEnum; import com.dji.sample.manage.model.param.DeviceFirmwareQueryParam; import com.dji.sample.manage.model.param.DeviceFirmwareUploadParam; import com.dji.sample.manage.service.IDeviceFirmwareService; import com.dji.sample.manage.service.IDeviceRedisService; import com.dji.sample.manage.service.IFirmwareModelService; import com.dji.sdk.cloudapi.firmware.FirmwareUpgradeTypeEnum; import com.dji.sdk.cloudapi.firmware.OtaCreateDevice; import com.dji.sdk.cloudapi.firmware.OtaProgress; import com.dji.sdk.cloudapi.firmware.OtaProgressStatusEnum; import com.dji.sdk.cloudapi.firmware.api.AbstractFirmwareService; import com.dji.sdk.common.Pagination; import com.dji.sdk.common.PaginationData; import com.dji.sdk.mqtt.MqttReply; import com.dji.sdk.mqtt.events.EventsDataRequest; import com.dji.sdk.mqtt.events.TopicEventsRequest; import com.dji.sdk.mqtt.events.TopicEventsResponse; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.MessageHeaders; import org.springframework.stereotype.Service; import org.springframework.util.DigestUtils; import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.time.Instant; import java.time.LocalDate; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; /** * @author sean * @version 1.2 * @date 2022/8/16 */ @Service @Slf4j public class DeviceFirmwareServiceImpl extends AbstractFirmwareService implements IDeviceFirmwareService { @Autowired private IDeviceFirmwareMapper mapper; @Autowired private ObjectMapper objectMapper; @Autowired private IWebSocketMessageService webSocketMessageService; @Autowired private OssServiceContext ossServiceContext; @Autowired private IFirmwareModelService firmwareModelService; @Autowired private IDeviceRedisService deviceRedisService; @Override public Optional getFirmware(String workspaceId, String deviceName, String version) { return Optional.ofNullable(entity2Dto(mapper.selectOne( new LambdaQueryWrapper() .eq(DeviceFirmwareEntity::getWorkspaceId, workspaceId) .eq(DeviceFirmwareEntity::getFirmwareVersion, version) .eq(DeviceFirmwareEntity::getStatus, true), deviceName))); } @Override public Optional getLatestFirmwareReleaseNote(String deviceName) { return Optional.ofNullable(entity2NoteDto(mapper.selectOne( Wrappers.lambdaQuery(DeviceFirmwareEntity.class) .eq(DeviceFirmwareEntity::getStatus, true) .orderByDesc(DeviceFirmwareEntity::getReleaseDate, DeviceFirmwareEntity::getFirmwareVersion), deviceName))); } @Override public List getDeviceOtaFirmware(String workspaceId, List upgradeDTOS) { List deviceOtaList = new ArrayList<>(); upgradeDTOS.forEach(upgradeDevice -> { boolean exist = deviceRedisService.checkDeviceOnline(upgradeDevice.getSn()); if (!exist) { throw new IllegalArgumentException("Device is offline."); } Optional firmwareOpt = this.getFirmware( workspaceId, upgradeDevice.getDeviceName(), upgradeDevice.getProductVersion()); if (firmwareOpt.isEmpty()) { throw new IllegalArgumentException("This firmware version does not exist or is not available."); } OtaCreateDevice ota = dto2OtaCreateDto(firmwareOpt.get()); ota.setSn(upgradeDevice.getSn()); ota.setFirmwareUpgradeType(FirmwareUpgradeTypeEnum.find(upgradeDevice.getFirmwareUpgradeType())); deviceOtaList.add(ota); }); return deviceOtaList; } @Override public TopicEventsResponse otaProgress(TopicEventsRequest> request, MessageHeaders headers) { String sn = request.getGateway(); EventsReceiver eventsReceiver = new EventsReceiver() .setBid(request.getBid()) .setOutput(request.getData().getOutput()) .setResult(request.getData().getResult()); log.info("SN: {}, {} ===> Upgrading progress: {}", sn, request.getMethod(), eventsReceiver.getOutput().getProgress()); if (!eventsReceiver.getResult().isSuccess()) { log.error("SN: {}, {} ===> Error: {}", sn, request.getMethod(), eventsReceiver.getResult()); } Optional deviceOpt = deviceRedisService.getDeviceOnline(sn); if (deviceOpt.isEmpty()) { return null; } OtaProgressStatusEnum statusEnum = eventsReceiver.getOutput().getStatus(); DeviceDTO device = deviceOpt.get(); handleProgress(device.getWorkspaceId(), sn, eventsReceiver, statusEnum.isEnd()); handleProgress(device.getWorkspaceId(), device.getChildDeviceSn(), eventsReceiver, statusEnum.isEnd()); WebSocketServerPlayBack.sendInfo(request.toString()); return new TopicEventsResponse().setData(MqttReply.success()); } private void handleProgress(String workspaceId, String sn, EventsReceiver events, boolean isEnd) { boolean upgrade = deviceRedisService.getFirmwareUpgradingProgress(sn).isPresent(); if (!upgrade) { return; } if (isEnd) { // Delete the cache after the update is complete. deviceRedisService.delFirmwareUpgrading(sn); } else { // Update the update progress of the dock in redis. deviceRedisService.setFirmwareUpgrading(sn, events); } events.setSn(sn); webSocketMessageService.sendBatch(workspaceId, UserTypeEnum.WEB.getVal(), BizCodeEnum.OTA_PROGRESS.getCode(), events); } @Override public Boolean checkFileExist(String workspaceId, String fileMd5) { return RedisOpsUtils.checkExist(RedisConst.FILE_UPLOADING_PREFIX + workspaceId + fileMd5) || mapper.selectCount(new LambdaQueryWrapper() .eq(DeviceFirmwareEntity::getWorkspaceId, workspaceId) .eq(DeviceFirmwareEntity::getFileMd5, fileMd5)) > 0; } @Override public PaginationData getAllFirmwarePagination(String workspaceId, DeviceFirmwareQueryParam param) { Page page = mapper.selectPage(new Page<>(param.getPage(), param.getPageSize()), new LambdaQueryWrapper() .eq(DeviceFirmwareEntity::getWorkspaceId, workspaceId) .eq(Objects.nonNull(param.getStatus()), DeviceFirmwareEntity::getStatus, param.getStatus()) .like(StringUtils.hasText(param.getProductVersion()), DeviceFirmwareEntity::getFirmwareVersion, param.getProductVersion()) .orderByDesc(DeviceFirmwareEntity::getReleaseDate), param.getDeviceName()); List data = page.getRecords().stream().map(this::entity2Dto).collect(Collectors.toList()); return new PaginationData(data, new Pagination(page.getCurrent(), page.getSize(), page.getTotal())); } @Override public void importFirmwareFile(String workspaceId, String creator, DeviceFirmwareUploadParam param, MultipartFile file) { String key = RedisConst.FILE_UPLOADING_PREFIX + workspaceId; String existKey = key + file.getOriginalFilename(); if (RedisOpsUtils.getExpire(existKey) > 0) { throw new RuntimeException("Please try again later."); } RedisOpsUtils.setWithExpire(existKey, true, RedisConst.DEVICE_ALIVE_SECOND); try (InputStream is = file.getInputStream()) { long size = is.available(); String md5 = DigestUtils.md5DigestAsHex(is); key += md5; boolean exist = checkFileExist(workspaceId, md5); if (exist) { throw new RuntimeException("The file already exists."); } RedisOpsUtils.set(key, System.currentTimeMillis()); Optional firmwareOpt = verifyFirmwareFile(file); if (firmwareOpt.isEmpty()) { throw new RuntimeException("The file format is incorrect."); } String firmwareId = UUID.randomUUID().toString(); String objectKey = OssConfiguration.objectDirPrefix + File.separator + firmwareId + FirmwareFileProperties.FIRMWARE_FILE_SUFFIX; ossServiceContext.putObject(OssConfiguration.bucket, objectKey, file.getInputStream()); log.info("upload success. {}", file.getOriginalFilename()); DeviceFirmwareDTO firmware = DeviceFirmwareDTO.builder() .releaseNote(param.getReleaseNote()) .firmwareStatus(param.getStatus()) .fileMd5(md5) .objectKey(objectKey) .fileName(file.getOriginalFilename()) .workspaceId(workspaceId) .username(creator) .fileSize(size) .productVersion(firmwareOpt.get().getProductVersion()) .releasedTime(firmwareOpt.get().getReleasedTime()) .firmwareId(firmwareId) .build(); saveFirmwareInfo(firmware, param.getDeviceName()); } catch (IOException e) { e.printStackTrace(); } finally { RedisOpsUtils.del(key); } } @Override public void saveFirmwareInfo(DeviceFirmwareDTO firmware, List deviceNames) { DeviceFirmwareEntity entity = dto2Entity(firmware); mapper.insert(entity); firmwareModelService.saveFirmwareDeviceName( FirmwareModelDTO.builder().firmwareId(entity.getFirmwareId()).deviceNames(deviceNames).build()); } @Override public void updateFirmwareInfo(DeviceFirmwareDTO firmware) { mapper.update(dto2Entity(firmware), new LambdaUpdateWrapper() .eq(DeviceFirmwareEntity::getFirmwareId, firmware.getFirmwareId())); } /** * Parse firmware file information. * @param file * @return */ private Optional verifyFirmwareFile(MultipartFile file) { try (ZipInputStream unzipFile = new ZipInputStream(file.getInputStream(), StandardCharsets.UTF_8)) { ZipEntry nextEntry = unzipFile.getNextEntry(); while (Objects.nonNull(nextEntry)) { String configName = nextEntry.getName(); if (!configName.contains(File.separator) && configName.endsWith(FirmwareFileProperties.FIRMWARE_CONFIG_FILE_SUFFIX + FirmwareFileProperties.FIRMWARE_SIG_FILE_SUFFIX)) { String[] filenameArr = configName.split(FirmwareFileProperties.FIRMWARE_FILE_DELIMITER); String date = filenameArr[FirmwareFileProperties.FILENAME_RELEASE_DATE_INDEX]; int index = date.indexOf("."); if (index != -1) { date = date.substring(0, index); } return Optional.of(DeviceFirmwareDTO.builder() .releasedTime(LocalDate.parse( date, DateTimeFormatter.ofPattern(FirmwareFileProperties.FILENAME_RELEASE_DATE_FORMAT))) // delete the string v. .productVersion(filenameArr[FirmwareFileProperties.FILENAME_VERSION_INDEX].substring(1)) .build()); } nextEntry = unzipFile.getNextEntry(); } } catch (IOException e) { e.printStackTrace(); } return Optional.empty(); } private DeviceFirmwareEntity dto2Entity(DeviceFirmwareDTO dto) { if (dto == null) { return null; } return DeviceFirmwareEntity.builder() .fileName(dto.getFileName()) .fileMd5(dto.getFileMd5()) .fileSize(dto.getFileSize()) .firmwareId(dto.getFirmwareId()) .firmwareVersion(dto.getProductVersion()) .objectKey(dto.getObjectKey()) .releaseDate(Objects.nonNull(dto.getReleasedTime()) ? dto.getReleasedTime().atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli() : null) .releaseNote(dto.getReleaseNote()) .status(dto.getFirmwareStatus()) .workspaceId(dto.getWorkspaceId()) .username(dto.getUsername()) .build(); } private DeviceFirmwareNoteDTO entity2NoteDto (DeviceFirmwareEntity entity) { if (entity == null) { return null; } return DeviceFirmwareNoteDTO.builder() .deviceName(entity.getDeviceName()) .productVersion(entity.getFirmwareVersion()) .releasedTime(LocalDate.ofInstant(Instant.ofEpochMilli(entity.getReleaseDate()), ZoneId.systemDefault())) .releaseNote(entity.getReleaseNote()) .build(); } private DeviceFirmwareDTO entity2Dto (DeviceFirmwareEntity entity) { if (entity == null) { return null; } return DeviceFirmwareDTO.builder() .deviceName(Arrays.asList(entity.getDeviceName().split(","))) .fileMd5(entity.getFileMd5()) .fileSize(entity.getFileSize()) .objectKey(entity.getObjectKey()) .firmwareId(entity.getFirmwareId()) .fileName(entity.getFileName()) .productVersion(entity.getFirmwareVersion()) .releasedTime(LocalDate.ofInstant(Instant.ofEpochMilli(entity.getReleaseDate()), ZoneId.systemDefault())) .releaseNote(entity.getReleaseNote()) .firmwareStatus(entity.getStatus()) .workspaceId(entity.getWorkspaceId()) .username(entity.getUsername()) .build(); } private OtaCreateDevice dto2OtaCreateDto(DeviceFirmwareDTO dto) { if (dto == null) { return null; } return new OtaCreateDevice() .setFileSize(dto.getFileSize()) .setFileUrl(ossServiceContext.getObjectUrl(OssConfiguration.bucket, dto.getObjectKey()).toString()) .setFileName(dto.getFileName()) .setMd5(dto.getFileMd5()) .setProductVersion(dto.getProductVersion()); } }