小米训练营-大作业
[!NOTE]
背景
BMS系统是智能化管理及维护各个电池单元,防止电池出现过充电和过放电、延长电池的使用寿命、监控电池状态的系统。在BMS系统中存在大量电池各种信号的规则管理以及监控,良好的是处理信号,并且根据规则,生成相关预警信息,能够极大提升用户体验。为此需要大家完成一套支持规则配置、信号预警的系统,来解决电池各种突发情况和提升用户体验。
1. 技术方案
1.1 系统设计
技术栈:Java17
+ SpringBoot2.6.13
+ SpringCloudAlibaba3.1.9
+ RocketMQ5.3.2
+ Nacos2.5.1
+ caffeine2.9.3
+ Redis2.6.9
+ MySQL5.7
1.2 数据库表设计
共由两个数据库:signal
库和warn
库
1.3 接口设计
核心功能共有四个接口:
signal
微服务的uploadSignal
上传信号接口:
signal
微服务的getSignal
获取信号接口:
- GET:
/api/upload/signal/{carId}
warn
微服务的warnSignal
预警信号接口:
warn
微服务的warnSignal
预警信号接口(供signal
微服务调用):
1.4 单元测试
使用Apifox
对接口进行测试:

2. 代码
2.1 代码地址
Gitee
:小米训练营-大作业:xiaomi-bms
2.2 核心代码
GetSignalServiceImpl
类:
@Slf4j @Service @RequiredArgsConstructor public class GetSignalServiceImpl implements GetSignalService {
private final RedisTemplate<String, Object> redisTemplate; private final WarnClient warnClient;
@Override public SignalVO getSignal(Long carId) { List<SignalDTO> signals = new ArrayList<>(); String key = "SignalOfCarId:" + carId + ":"; try { Set<Integer> warnIds = warnClient.getRules().stream().map(Rule::getWarnId).collect(Collectors.toSet()); for (Integer warnId : warnIds) { Object obj = redisTemplate.opsForValue().get(key + warnId); if (obj instanceof Map) { signals.add(new JacksonObjectMapper().convertValue(obj, SignalDTO.class)); } } SignalVO signalVO = new SignalVO(); signalVO.setSignals(signals); return signalVO; } catch (Exception e) { log.error("从Redis中获取对象失败"); return null; } } }
|
UploadSignalServiceImpl
类:
@Service @RequiredArgsConstructor public class UploadSignalServiceImpl implements UploadSignalService {
private final RedisTemplate<String, Object> redisTemplate; private final SignalProducer signalProducer; private final WarnClient warnClient;
@Override public void uploadAndSave(SignalDTO signalDTO) { signalDTO.setCreateTime(LocalDateTime.now()); String key = "SignalOfCarId:" + signalDTO.getCarId().toString() + ":"; if (signalDTO.getWarnId() == null) { Set<Integer> warnIds = warnClient.getRules().stream().map(Rule::getWarnId).collect(Collectors.toSet()); for (Integer warnId : warnIds) { signalDTO.setWarnId(warnId); redisTemplate.opsForValue().set(key + warnId, signalDTO); } signalDTO.setWarnId(null); } else { redisTemplate.opsForValue().set(key + signalDTO.getWarnId(), signalDTO); } signalProducer.sendSaveMessage(signalDTO); } }
|
WarnSignalServiceImpl
类:
@Service @RequiredArgsConstructor public class WarnSignalServiceImpl implements WarnSignalService { private final SignalClient signalClient; private final CarMapper carMapper; private final RuleService ruleService; private final StringRedisTemplate stringRedisTemplate; private final WarnProducer warnProducer;
@Override public WarnVO warnSignal(List<SignalDTO> SignalDTOList) { List<WarnDTO> warns = new ArrayList<>(); for (SignalDTO signalDTO : SignalDTOList) { signalClient.uploadSignal(signalDTO); List<SignalDTO> signals = signalClient.getSignal(signalDTO.getCarId()).getData().getSignals(); for (SignalDTO signal : signals) { WarnDTO warnDTO = checkSignal(signal); if (warnDTO != null) { warns.add(warnDTO); } } } return !warns.isEmpty() ? new WarnVO(warns) : null; }
public WarnDTO checkSignal(SignalDTO signal) { List<Rule> rules = ruleService.getAllRulesWithCache();
String key = "BatteryTypeOfCarId:" + signal.getCarId(); if (!stringRedisTemplate.hasKey(key)) { stringRedisTemplate.opsForValue().set(key, carMapper.selectById(signal.getCarId()).getBatteryType().toString()); } Integer batteryType = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get(key)));
Rule rule = null; for (Rule value : rules) if (Objects.equals(value.getWarnId(), signal.getWarnId()) && Objects.equals(value.getBatteryType(), batteryType)) { rule = value; break; }
if (rule != null) { BigDecimal subtract; if ("M".equalsIgnoreCase(rule.getDifference())) { if (signal.getMx() == null || signal.getMi() == null) subtract = BigDecimal.valueOf(0); else subtract = signal.getMx().subtract(signal.getMi()); } else if ("I".equalsIgnoreCase(rule.getDifference())) { if (signal.getIx() == null || signal.getIi() == null) subtract = BigDecimal.valueOf(0); else subtract = signal.getIx().subtract(signal.getIi()); } else return null; String[] limit = rule.getWarnRule().split(","); if (subtract.compareTo(new BigDecimal(limit[limit.length - 1])) < 0) return null; for (int i = 0; i < limit.length; i++) { if (subtract.compareTo(new BigDecimal(limit[i])) >= 0) { WarnDTO warnDTO = new WarnDTO(); warnDTO.setCarId(signal.getCarId()); warnDTO.setBatteryType(batteryType); warnDTO.setWarnName(rule.getWarnName()); warnDTO.setWarnLevel(i); warnDTO.setCreateTime(signal.getCreateTime()); warnProducer.sendSaveMessage(warnDTO); return warnDTO; } } } return null; }
}
|
SignalConsumer0
类:
@Service @Slf4j @RequiredArgsConstructor @RocketMQMessageListener( topic = "signalToSave0", consumerGroup = "signal-consumer" ) public class SignalConsumer0 implements RocketMQListener<SignalDTO> {
private final SignalMapper signalMapper;
private final List<Signal> signalList = new ArrayList<>();
@Override public void onMessage(SignalDTO signalDTO) { synchronized (signalList) { try { Signal signal = new Signal(); BeanUtils.copyProperties(signalDTO, signal); signalList.add(signal);
if (signalList.size() >= 100) { signalMapper.batchInsertSignals0(signalList); signalList.clear(); } } catch (Exception e) { log.error("Failed to process message: {}", e.getMessage(), e); } } } }
|
WarnAllSignalTask
类:
@Service @Slf4j @RequiredArgsConstructor public class WarnAllSignalTask { private final RedisTemplate<String, Object> redisTemplate; private final SignalProducer signalProducer;
@Scheduled(cron = "0 0/10 * * * ?") public void warnAllSignal() { String keyPattern = "SignalOfCarId:*"; Set<String> keys = scanKeys(keyPattern);
if (!keys.isEmpty()) { for (String key : keys) { Object obj = redisTemplate.opsForValue().get(key); if (obj instanceof Map) { signalProducer.sendCheckMessage(new JacksonObjectMapper().convertValue(obj, SignalDTO.class)); } } } else { log.error("SignalOfCarId为空"); } }
private Set<String> scanKeys(String pattern) { Set<String> keys = new HashSet<>(); redisTemplate.execute((RedisConnection connection) -> { try (Cursor<byte[]> cursor = connection.scan(ScanOptions.scanOptions().match(pattern).build())) { cursor.forEachRemaining(item -> keys.add(new String(item, StandardCharsets.UTF_8))); } return null; }); return keys; } }
|
3. 运行截图
3.1 网关鉴权
请求需要进行鉴权,防止非法上报信号:

3.2 上报信号
结果返回正常:

信号批处理:

信号存放入Redis
当中:

3.3 获取信号
获取carId
下的所有规则的信号数据:

3.4 预警信息
结果放回需要预警的carId
对应的预警信息:

同时更新Redis
当中的数据:

并将结果存入warn
数据库当中:

3.5 定时任务
使用非阻塞方法扫描Redis
,并发送消息给warn
微服务:

warn
微服务调用checkSignal
方法,检查信号是否需要预警:
