package com.ard.sample.manage.service.impl; import com.ard.sample.manage.model.enums.DeviceDomainEnum; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ard.sample.common.model.Pagination; import com.ard.sample.common.model.PaginationData; import com.ard.sample.component.mqtt.model.ChannelName; import com.ard.sample.component.mqtt.model.CommonTopicReceiver; import com.ard.sample.component.mqtt.model.MapKeyConst; import com.ard.sample.component.mqtt.model.TopicConst; import com.ard.common.core.redis.RedisConst; import com.ard.common.core.redis.RedisOpsUtils; import com.ard.sample.component.websocket.service.impl.SendMessageServiceImpl; import com.ard.sample.component.websocket.service.impl.WebSocketManageServiceImpl; import com.ard.sample.manage.mapper.IDeviceHmsMapper; import com.ard.sample.manage.model.common.HmsJsonUtil; import com.ard.sample.manage.model.common.HmsMessage; import com.ard.sample.manage.model.dto.DeviceDTO; import com.ard.sample.manage.model.dto.DeviceHmsDTO; import com.ard.sample.manage.model.entity.DeviceHmsEntity; import com.ard.sample.manage.model.enums.HmsEnum; import com.ard.sample.manage.model.param.DeviceHmsQueryParam; import com.ard.sample.manage.model.receiver.DeviceHmsReceiver; import com.ard.sample.manage.model.receiver.HmsArgsReceiver; import com.ard.sample.manage.service.IDeviceHmsService; import com.fasterxml.jackson.core.type.TypeReference; 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 org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; /** * @author sean * @version 1.1 * @date 2022/7/6 */ @Slf4j @Service @Transactional public class DeviceHmsServiceImpl implements IDeviceHmsService { @Autowired private IDeviceHmsMapper mapper; @Autowired private ObjectMapper objectMapper; @Autowired private SendMessageServiceImpl sendMessageService; @Autowired private WebSocketManageServiceImpl webSocketManageService; private static final Pattern PATTERN_KEY = Pattern.compile( HmsEnum.FormatKeyEnum.KEY_START + "(" + Arrays.stream(HmsEnum.FormatKeyEnum.values()) .map(HmsEnum.FormatKeyEnum::getKey) .collect(Collectors.joining("|")) + ")"); @Override @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_HMS) public void handleHms(CommonTopicReceiver receiver, MessageHeaders headers) { String topic = headers.get(MqttHeaders.RECEIVED_TOPIC).toString(); String sn = topic.substring((TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT).length(), topic.indexOf(TopicConst.EVENTS_SUF)); DeviceHmsEntity entity = DeviceHmsEntity.builder() .bid(receiver.getBid()) .tid(receiver.getTid()) .createTime(receiver.getTimestamp()) .updateTime(0L) .sn(sn) .build(); String key = RedisConst.HMS_PREFIX + sn; // Query all unread hms messages of the device in redis. Set hmsMap = RedisOpsUtils.listGetAll(key).stream().map(String::valueOf).collect(Collectors.toSet()); DeviceDTO device = (DeviceDTO) RedisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + sn); List unReadList = new ArrayList<>(); objectMapper.convertValue(((Map) (receiver.getData())).get(MapKeyConst.LIST), new TypeReference>() {}) .forEach(hmsReceiver -> { final DeviceHmsEntity hms = entity.clone(); this.fillEntity(hms, hmsReceiver, device); this.fillMessage(hms, hmsReceiver.getArgs()); // The same unread hms are no longer incremented. if (hmsMap.contains(hms.getHmsKey())) { // 修改已存在数据的创建时间,先查询到已存在记录,再修改。 DeviceHmsEntity exisHms=getDeviceHmsEntity(hms); if (exisHms != null) { LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.set(DeviceHmsEntity::getCreateTime, hms.getCreateTime()); updateWrapper.eq(DeviceHmsEntity::getId, exisHms.getId()); mapper.update(exisHms, updateWrapper); } return; } unReadList.add(entity2Dto(hms)); mapper.insert(hms); }); if (unReadList.isEmpty()) { return; } // RedisOpsUtils.listRPush(key, (Object) unReadList.stream().map(DeviceHmsDTO::getKey).toArray(String[]::new)); // 修正后:直接传入String数组,每个元素单独存 List newHmsKeys = unReadList.stream() .map(DeviceHmsDTO::getKey) .collect(Collectors.toList()); RedisOpsUtils.listRPush(key, newHmsKeys.toArray(new String[0])); // push to the web // Collection sessions = webSocketManageService.getValueWithWorkspaceAndUserType( // device.getWorkspaceId(), UserTypeEnum.WEB.getVal()); // sendMessageService.sendBatch(sessions, CustomWebSocketMessage.builder() // .bizCode(BizCodeEnum.DEVICE_HMS.getCode()) // .data(TelemetryDTO.>builder().sn(sn).host(unReadList).build()) // .timestamp(System.currentTimeMillis()) // .build()); } @Override public PaginationData getDeviceHmsByParam(DeviceHmsQueryParam param) { // 1. 核心兜底:参数为null时初始化空对象(结合实体默认值,避免所有NPE) if (param == null) { param = new DeviceHmsQueryParam(); } LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper(); // 2. 设备SN条件:仅当集合非空时添加IN条件(实体已兜底空集合,无需判null) if (!param.getDeviceSn().isEmpty()) { queryWrapper.in(DeviceHmsEntity::getSn, param.getDeviceSn()); } // 3. 创建时间区间:① 两个时间都非null ② 开始时间≤结束时间(避免无效条件) if (param.getBeginTime() != null && param.getEndTime() != null) { //if (param.getBeginTime() <= param.getEndTime()) { queryWrapper.between(DeviceHmsEntity::getCreateTime, param.getBeginTime(), param.getEndTime()); // } else { // // 可选:抛异常/日志提醒/交换时间顺序 // // log.warn("创建时间区间不合法:beginTime({}) > endTime({})", param.getBeginTime(), param.getEndTime()); // } } // 4. 更新时间:参数非null时添加 queryWrapper.eq(param.getUpdateTime() != null, DeviceHmsEntity::getUpdateTime, param.getUpdateTime()); // 5. 级别:参数非null时添加 queryWrapper.eq(param.getLevel() != null, DeviceHmsEntity::getLevel, param.getLevel()); // 6. 消息模糊查询:按语言匹配(语言为null/空时自动忽略) String language = param.getLanguage(); String message = param.getMessage(); if (StringUtils.hasText(message)) { if (HmsEnum.MessageLanguage.ZH.getLanguage().equals(language)) { queryWrapper.like(DeviceHmsEntity::getMessageZh, message); } else if (HmsEnum.MessageLanguage.EN.getLanguage().equals(language)) { queryWrapper.like(DeviceHmsEntity::getMessageEn, message); } // 语言不匹配/为null时,不添加任何消息条件 } // 7. 固定排序:创建时间降序 queryWrapper.orderByDesc(DeviceHmsEntity::getCreateTime); // 8. 分页参数校验(避免非法值:page<1 / pageSize≤0) Long page = param.getPage() == null ? 1L : Math.max(param.getPage(), 1L); // 页码最小为1 Long pageSize = param.getPage() == null ? 1L : param.getPageSize() <= 0 ? 10L : param.getPageSize(); // 页大小默认10 // 9. 「传空返回所有」的分页处理:pageSize为null/0时,查询全部数据 if (param.getPageSize() == null || param.getPageSize() <= 0) { pageSize = (long) mapper.selectCount(queryWrapper); // 总条数作为页大小 } // 10. 执行分页查询 Page pagination = mapper.selectPage(new Page<>(page, pageSize), queryWrapper); // 11. 实体转DTO List deviceHmsList = pagination.getRecords().stream() .map(this::entity2Dto) .collect(Collectors.toList()); return new PaginationData(deviceHmsList, new Pagination(pagination)); } // region 原代码注释备份一份 // LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper() // .and(wrapper -> param.getDeviceSn().forEach(sn -> wrapper.eq(DeviceHmsEntity::getSn, sn).or())) // .between(param.getBeginTime() != null && param.getEndTime() != null, // DeviceHmsEntity::getCreateTime, param.getBeginTime(), param.getEndTime()) // .eq(param.getUpdateTime() != null, DeviceHmsEntity::getUpdateTime, param.getUpdateTime()) // .eq(param.getLevel() != null, DeviceHmsEntity::getLevel, param.getLevel()) // .like(StringUtils.hasText(param.getMessage()) && // HmsEnum.MessageLanguage.ZH.getLanguage().equals(param.getLanguage()), // DeviceHmsEntity::getMessageZh, param.getMessage()) // .like(StringUtils.hasText(param.getMessage()) && // HmsEnum.MessageLanguage.EN.getLanguage().equals(param.getLanguage()), // DeviceHmsEntity::getMessageEn, param.getMessage()) // .orderByDesc(DeviceHmsEntity::getCreateTime); // if (param.getPage() == null || param.getPageSize() == null) { // param.setPage(1L); // param.setPageSize(Long.valueOf(mapper.selectCount(queryWrapper))); // } // // Page pagination = mapper.selectPage(new Page<>(param.getPage(), param.getPageSize()), queryWrapper); // // List deviceHmsList = pagination.getRecords().stream().map(this::entity2Dto).collect(Collectors.toList()); // // return new PaginationData(deviceHmsList, new Pagination(pagination)); // endregion @Override public void updateUnreadHms(String deviceSn) { mapper.update(DeviceHmsEntity.builder().updateTime(System.currentTimeMillis()).build(), new LambdaUpdateWrapper() .eq(DeviceHmsEntity::getSn, deviceSn) .eq(DeviceHmsEntity::getUpdateTime, 0L)); // 删除Redis中缓存的未读消息。 RedisOpsUtils.del(RedisConst.HMS_PREFIX + deviceSn); } private DeviceHmsDTO entity2Dto(DeviceHmsEntity entity) { if (entity == null) { return null; } return DeviceHmsDTO.builder() .bid(entity.getBid()) .tid(entity.getTid()) .createTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getCreateTime()), ZoneId.systemDefault())) .updateTime(entity.getUpdateTime().intValue() == 0 ? null : LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getUpdateTime()), ZoneId.systemDefault())) .sn(entity.getSn()) .hmsId(entity.getHmsId()) .key(entity.getHmsKey()) .level(entity.getLevel()) .module(entity.getModule()) .messageEn(entity.getMessageEn()) .messageZh(entity.getMessageZh()) .inTheSky(entity.getInTheSky()) .deviceType(entity.getDeviceType()) .build(); } /** * 将收到的数据填充到实体中。请参阅文档中的拼接规则。 * @param dto * @param receiver */ private void fillEntity(DeviceHmsEntity dto, DeviceHmsReceiver receiver, DeviceDTO deviceDTO) { dto.setLevel(receiver.getLevel()); dto.setModule(receiver.getModule()); dto.setHmsId(UUID.randomUUID().toString()); dto.setInTheSky(receiver.getInTheSky()); dto.setDeviceType(receiver.getDeviceType()); // 不知道什么原因,在程序替换jar包重新启动时,报了个空指针异常,排查应该是因为 DeviceDTO对象为空,没有从redis中读取到设备信息。增加判断 if(deviceDTO !=null && deviceDTO.getDomain() == DeviceDomainEnum.DOCK.getVal()) { dto.setHmsKey(HmsEnum.HmsFaqIdEnum.DOCK_TIP.getText() + receiver.getCode()); // log.info("SN:{}的Hms匹配拼接规则值为:{}",deviceDTO.getDeviceSn(), HmsEnum.HmsFaqIdEnum.DOCK_TIP.getText() + receiver.getCode()); return; } // if (HmsEnum.DomainType.DRONE_NEST.getDomain().equals(receiver.getDomainType())) { // dto.setHmsKey(HmsEnum.HmsFaqIdEnum.DOCK_TIP.getText() + receiver.getCode()); // return; // } StringBuilder key = new StringBuilder(HmsEnum.HmsFaqIdEnum.FPV_TIP.getText()).append(receiver.getCode()); if (receiver != null && HmsEnum.IN_THE_SKY.getVal() == (receiver.getInTheSky() != null ? receiver.getInTheSky() : -1)) { key.append(HmsEnum.IN_THE_SKY.getText()); } dto.setHmsKey(key.toString()); // log.info("SN:{}的Hms匹配拼接规则值为:{}",deviceDTO.getDeviceSn(), key.toString()); } /** * Replace wildcards in messages according to the relevant rules. * Please refer to the documentation for splicing rules. * @param dto * @param args */ private void fillMessage(DeviceHmsEntity dto, HmsArgsReceiver args) { HmsMessage hmsMessage = HmsJsonUtil.get(dto.getHmsKey()); String zh = StringUtils.hasText(hmsMessage.getZh()) ? hmsMessage.getZh() : String.format("未知错误(%s)", dto.getHmsKey()); String en = StringUtils.hasText(hmsMessage.getEn()) ? hmsMessage.getEn() : String.format("Unknown(%s)", dto.getHmsKey());// dto.setMessageZh(format(Locale.CHINESE.getLanguage(), zh, args)); dto.setMessageEn(format(Locale.ENGLISH.getLanguage(), en, args)); } /** * Set the matching parameters for key. * @param l language: zh or en * @param hmsArgs * @return */ private List fillKeyArgs(String l, HmsArgsReceiver hmsArgs) { List args = new ArrayList<>(); args.add(Objects.nonNull(hmsArgs.getAlarmId()) ? Long.toHexString(hmsArgs.getAlarmId()) : null); args.add(Objects.nonNull(hmsArgs.getComponentIndex()) ? String.valueOf(hmsArgs.getComponentIndex() + 1) : null); if (Objects.nonNull(hmsArgs.getSensorIndex())) { args.add(String.valueOf(hmsArgs.getSensorIndex() + 1)); HmsEnum.HmsBatteryIndexEnum hmsBatteryIndexEnum = HmsEnum.HmsBatteryIndexEnum.find(hmsArgs.getSensorIndex()); HmsEnum.HmsDockCoverIndexEnum hmsDockCoverIndexEnum = HmsEnum.HmsDockCoverIndexEnum.find(hmsArgs.getSensorIndex()); HmsEnum.HmsChargingRodIndexEnum hmsChargingRodIndexEnum = HmsEnum.HmsChargingRodIndexEnum.find(hmsArgs.getSensorIndex()); switch (l) { case "zh": args.add(hmsBatteryIndexEnum.getZh()); args.add(hmsDockCoverIndexEnum.getZh()); args.add(hmsChargingRodIndexEnum.getZh()); break; case "en": args.add(hmsBatteryIndexEnum.getEn()); args.add(hmsDockCoverIndexEnum.getEn()); args.add(hmsChargingRodIndexEnum.getEn()); break; default: break; } } return args; } /** * Returns a formatted string using the specified locale, format string, and arguments. * @param l language: zh or en * @param format * @param hmsArgs * @return */ private String format(String l, String format, HmsArgsReceiver hmsArgs) { List args = fillKeyArgs(l, hmsArgs); List list = parse(format); StringBuilder sb = new StringBuilder(); for (String word : list) { if (!StringUtils.hasText(word)) { continue; } HmsEnum.FormatKeyEnum keyEnum = HmsEnum.FormatKeyEnum.find(word.substring(1)); sb.append(HmsEnum.FormatKeyEnum.KEY_START != word.charAt(0) || HmsEnum.FormatKeyEnum.UNKNOWN == keyEnum ? word : args.get(keyEnum.getIndex())); } return sb.toString(); } /** * Finds format specifiers in the format string. * @param s * @return */ private List parse(String s) { List list = new ArrayList<>(); Matcher matcher = PATTERN_KEY.matcher(s); for (int i = 0; i < s.length(); ) { if (matcher.find(i)) { if (matcher.start() != i) { list.add(s.substring(i, matcher.start())); } list.add(matcher.group()); i = matcher.end(); } else { list.add(s.substring(i)); break; } } return list; } /** * 按条件查询返回最新的一条数据 * */ public DeviceHmsEntity getDeviceHmsEntity(DeviceHmsEntity deviceHmsEntity) { List lstHms = mapper.selectList(new LambdaQueryWrapper() .eq(DeviceHmsEntity::getHmsKey, deviceHmsEntity.getHmsKey()) .eq(DeviceHmsEntity::getSn, deviceHmsEntity.getSn()) .eq(DeviceHmsEntity::getUpdateTime,0) .orderByDesc(DeviceHmsEntity::getCreateTime) ); return lstHms.isEmpty() ? null : lstHms.get(0); } }