流程分析
远端设备调用此接口传入设备最新运行状态记录,接口接收到数据首先判断命令类型如果为"3"心跳链接需要单独处理数据并将结果返回给设备,其他类型走异步调用,在XzcDataService中判断命令类型并在xzcService中将数据存到Redis中,通过EquipDataTask定时任务类将Redis中的数据使用SSE通信发给前端(目前为前端轮询查看)
实体类
TxXzcCmd
@Data
@Accessors(chain=true)
@TableName("tx_xzc_cmd")
@ApiModel(description="熏蒸床命令记录表")
public class TxXzcCmd {
@ApiModelProperty(value = "ID")
@TableId(type = IdType.AUTO)
private Long id;
@ApiModelProperty(value = "设备编码")
private String code;
@ApiModelProperty(value = "运行标识")
private String runid;
@ApiModelProperty(value = "命令状态(0:已作废 1:待执行 3:已执行)")
private Integer status;
@ApiModelProperty(value = "命令标识(1:开机 2:关机 3:开始 4:暂停 5:继续 6:设置 7:结束)")
private Integer cmd;
@ApiModelProperty(value = "设备状态(0:待机 1:预热 2:治疗 3:暂停 4:结束)")
private Integer sysState;
@ApiModelProperty(value = "颈部温度")
private Double neckTemp;
@ApiModelProperty(value = "腰部温度")
private Double waistTemp;
@ApiModelProperty(value = "额外数据")
private String extraData;
@ApiModelProperty(value = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@ApiModelProperty(value = "执行时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date executeTime;
}
接口逻辑
JSON处理
JSONObject使用详解
mapper爆红
@ApiOperation(value = "数据接口", notes = "数据接口")
@ApiImplicitParams({})
@PostMapping(value = "/data", consumes = "application/octet-stream")
@Transactional(rollbackFor = Exception.class)
public Result data(@RequestBody String string) throws Exception{
//合法性校验
if(string==null&&string.equals("")){
throw new BusinessException("熏蒸床数据为空");
}
log.info("接收到的熏蒸床数据:"+string);
//字符串转对象
JSONObject jsonObject = JSON.parseObject(string);
//获取命令类型的键值对
String type = jsonObject.getString("T").trim();
System.out.println("类型---------------"+type);
//类型为3的命令是心跳链接,远程控制设备的心跳返回数据只能当前接口返回,不做异步任务处理,其余的数据进入缓存,执行定时任务异步处理数据
if("3".equals(type)){
// 解析数据字段
//获取设备编码(唯一)
String code = jsonObject.getString("D");
//获取运行标识(唯一)
Integer runId = jsonObject.getInteger("R");
// 生成返回数据
JSONObject object = null;
// 查询回传命令(命令状态(0:已作废 1:待执行 3:已执行))
Integer status = 1;
//从redis中查询是否有该设备的缓存,键是设备编码,值是TxXzcCmd属性的键值对
Object cmdCache = redisUtils.get("XZC_CMD:"+code);
//如果缓存中该设备
if (ObjectUtil.isNotEmpty(cmdCache)){
//将缓存中的数据序列化到TxXzcCmd实体类中
TxXzcCmd cmd = JSON.parseObject(JSON.toJSONString(cmdCache), TxXzcCmd.class);
// 处理回传命令
// 设置基础数据
object = new JSONObject();
object.put("T", 3);
//设置设备编码
object.put("D", code);
//这里是解决远端设备运行标识不一致问题,需要补0
Integer size =5 -runId.toString().length();
String top = "";
for (int i=0;i<size;i++){
top = top+"0";
}
object.put("R", top+runId);
// 修改命令状态
// 不同命令处理
switch (cmd.getCmd()) {
case 1: // 开机
case 2: // 关机
case 3: // 开始
case 4: // 暂停
case 5: // 继续
case 7: // 结束
case 6: // 设置(所有的操作都用此命令来实现)
if (cmd.getSysState() != null) {
Integer sysState = cmd.getSysState();
//设置设备状态(0:待机 1:预热 2:治疗 3:暂停 4:结束)
object.put("S", sysState);
}
if (cmd.getNeckTemp() != null) {
Integer nSetTemper = cmd.getNeckTemp().intValue() * 10;
//设置颈部温度
object.put("O", nSetTemper);
}
if (cmd.getWaistTemp() != null) {
Integer wSetTemper = cmd.getWaistTemp().intValue() * 10;
//设置腰部温度
object.put("X", wSetTemper);
}
break;
}
//删除原来的缓存数据
redisUtils.del("XZC_CMD:"+code);
System.out.println("命令:"+object);
return Result.success(object);
}
}
//统一使用XzcDataService统一异步处理
xzcDataService.xzcData(jsonObject);
return Result.success();
}
Service层
XzcDataService
@Override
public void xzcData(JSONObject jsonObject) {
//命令类型
String type = jsonObject.getString("T");
if (type == null) {
throw new BusinessException("格式错误:缺少T字段");
}
log.info("运行数据XZC:"+jsonObject);
//根据命令类型做Redis缓存
switch (type) {
case "1":
xzcService.processOpen(jsonObject);
break;
case "2":
xzcService.processStart(jsonObject);
break;
case "3":
xzcService.processHeart(jsonObject);
break;
case "4":
xzcService.processData(jsonObject);
break;
case "5":
long xzcStart = xzcService.processClose(jsonObject);
//产生报告
saveBatchSmyReports(jsonObject.getString("D"),xzcStart);
break;
}
}
xzcService
@Override
public void processData(JSONObject jsonObject) {
// 解析数据字段
String code = jsonObject.getString("D");
String runId = jsonObject.getString("R");
Integer state = jsonObject.getInteger("S");
Integer minute = jsonObject.getInteger("M");
Integer second = jsonObject.getInteger("N");
Double neckSetTemp = jsonObject.getDouble("O");
Double neckWaterTemp = jsonObject.getDouble("P");
Double neckSteamTemp = jsonObject.getDouble("Q");
Double waistSetTemp = jsonObject.getDouble("X");
Double waistWaterTemp = jsonObject.getDouble("Y");
Double waistSteamTemp = jsonObject.getDouble("Z");
// 更新运行状态
TxXzcRun run = new TxXzcRun();
run.setCode(code);
run.setRunid(String.valueOf(Integer.parseInt(runId)));
run.setNeckTemp(neckSetTemp / 10);
run.setWaistTemp(waistSetTemp / 10);
xzcService.setRunOnly(run);
// 更新设备状态
xzcService.doUpdateState(code, state);
// 处理运行数据
TxXzcData data = new TxXzcData();
data.setCode(code);
data.setRunid(runId);
data.setTime(new Date());
data.setNeckTemp(neckSetTemp / 10);
data.setWaistTemp(waistSetTemp / 10);
data.setNeckLiquidTemp(neckWaterTemp/10);
data.setNeckSkinTemp(neckSteamTemp/10);
data.setWaistLiquidTemp(waistWaterTemp/10);
data.setWaistSkinTemp(waistSteamTemp/10);
String stateStr = "";
switch (state){
case 1:
stateStr = XzcState.HOT.getDescribe();
break;
case 2:
stateStr = XzcState.CURE.getDescribe();
break;
case 3:
stateStr = XzcState.STOP.getDescribe();
break;
case 4:
stateStr = XzcState.OVER.getDescribe();
break;
case 5:
stateStr = XzcState.READY.getDescribe();
break;
}
data.setState(stateStr);
//将运行数据丢入redis,5分钟过期,用户端从redis获取运行时数据,不在通过数据库
redisUtils.set("XZC_RUN_DATA:"+code,data,300);
redisUtils.sZSet("XZC_RUN_TEMP:"+code,data,System.currentTimeMillis(),300);
//将熏蒸床是否在线标记缓存进redis,当redis内查询不到的时候,任务调度修改设备状态,熏蒸床2分钟发送一次数据,设定130秒后失效
redisUtils.sZSet("XZC_RUN_EXIST",code,System.currentTimeMillis(),130);
xzcService.addData(data);
}
定时任务类
@Configuration
@DependsOn("redisTemplateInit")
@Slf4j
@EnableScheduling
public class EquipDataTask {
@Autowired
private RedisUtils redisUtils;
//每隔15秒将熏蒸床或拔罐器数据推送给前端---目前前端技术短时间能实现不了SSE,此方法展示不使用,改为前端轮询接口
// @Scheduled(cron = "0/15 * * * * ?")
public void sendBgqMessage() {
if (SseUtil.getIds().size()>0) {
SseUtil.getIds().forEach(a -> {
if (a.split("-")[0].equals("BGQ")) {
Object od = redisUtils.get("BGQ_RUN_DATA:" + a.split("-")[1] + ":" + a.split("-")[2]);
Object op = redisUtils.rangeZSet("BGQ_PRESSURE:" + a.split("-")[1] + ":" + a.split("-")[2],0,System.currentTimeMillis());
Object o1 = JSONObject.toJSONString(ObjectUtil.isNotEmpty(od)?od:"");
Object o2 = JSONObject.toJSONString(ObjectUtil.isNotEmpty(op)?op:"");
if (ObjectUtil.isNotEmpty(o1) && ObjectUtil.isNotEmpty(o2)) {
Map<String, Object> map = new HashMap<>();
map.put("data", o1);
map.put("pressure", o2);
SseUtil.sendMessage(a, JSONObject.toJSONString(map));
}
} else if (a.split("-")[0].equals("XZC")) {
Object od =redisUtils.get("XZC_RUN_DATA:" + a.split("-")[1] + ":" + a.split("-")[2]);
Object ot =redisUtils.rangeZSet("XZC_RUN_TEMP:" + a.split("-")[1] + ":" + a.split("-")[2],0,System.currentTimeMillis());
Object ow =redisUtils.get("XZC_RUN_WARM:" + a.split("-")[1] + ":" + a.split("-")[2]);
Object oq =redisUtils.get("XZC_RUN_QUANTITY:"+a.split("-")[1]);
Object o1 = JSONObject.toJSONString(ObjectUtil.isNotEmpty(od)?od:"");//运行时数据
Object o2 = JSONObject.toJSONString(ObjectUtil.isNotEmpty(ot)?ot:"");//运行时温度
Object o3 = JSONObject.toJSONString(ObjectUtil.isNotEmpty(ow)?ow:"");//报警数据
Object o4 = JSONObject.toJSONString(ObjectUtil.isNotEmpty(oq)?oq:"");//清洗热喷次数
if (ObjectUtil.isNotEmpty(o1) && ObjectUtil.isNotEmpty(o2)) {
Map<String, Object> map = new HashMap<>();
map.put("data", o1); //运行时数据
map.put("temp", o2); //运行时温度
if(ObjectUtil.isNotEmpty(o3)){
map.put("warm",o3);//报警数据
}else {
map.put("warm",null);
}
if (ObjectUtil.isNotEmpty(o4)){
map.put("quantity",o4);//清洗热喷次数
}else {
map.put("quantity",null);
}
System.out.println("数据:"+JSONObject.toJSONString(map));
SseUtil.sendMessage(a, JSONObject.toJSONString(map));
}
}
});
}
}
}
技术分析
为什么要从Redis里面查:
设备访问频次高在访问接口后,后端还要读取旧数据做校验访问量很大而且数据有时效性前端也需要15s做一次轮询频繁访问,MySQL扛不住