Files
jsowell-charger-web/jsowell-admin/src/main/java/com/jsowell/service/CameraService.java
2024-01-06 15:10:31 +08:00

361 lines
14 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.jsowell.service;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.ObjectMetadata;
import com.jsowell.common.config.AliyunOssConfig;
import com.jsowell.common.constant.CacheConstants;
import com.jsowell.common.core.redis.RedisCache;
import com.jsowell.common.util.DateUtils;
import com.jsowell.common.util.StringUtils;
import com.jsowell.common.util.file.AliyunOssUploadUtils;
import com.jsowell.common.util.file.ImageUtils;
import com.jsowell.common.util.sign.MD5Util;
import com.jsowell.netty.server.mqtt.BootNettyMqttChannelInboundHandler;
import com.jsowell.pile.domain.PileCameraInfo;
import com.jsowell.pile.dto.GenerateOccupyOrderDTO;
import com.jsowell.pile.dto.camera.Camera2GroundLockCommand;
import com.jsowell.pile.dto.camera.CameraHeartBeatDTO;
import com.jsowell.pile.dto.camera.CameraIdentifyResultsDTO;
import com.jsowell.pile.service.IMemberBasicInfoService;
import com.jsowell.pile.service.IMemberPlateNumberRelationService;
import com.jsowell.pile.service.IPileCameraInfoService;
import com.jsowell.pile.service.OrderPileOccupyService;
import com.jsowell.pile.vo.uniapp.MemberVO;
import io.netty.channel.Channel;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Base64;
import javax.imageio.ImageIO;
import java.util.List;
import java.util.Locale;
/**
* 相机管理系统 Service
*
* @author Lemon
* @Date 2023/12/5 11:11:32
*/
@Service
public class CameraService {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private RedisCache redisCache;
@Autowired
private IPileCameraInfoService pileCameraInfoService;
@Autowired
private OrderPileOccupyService orderPileOccupyService;
@Autowired
private BootNettyMqttChannelInboundHandler handler;
@Autowired
private IMemberBasicInfoService memberBasicInfoService;
public void receiveIdentifyResults(JSONObject jsonObject) throws InterruptedException {
// 区分入场和出场
Integer parking_state = jsonObject.getJSONObject("parking").getInteger("parking_state");
if (parking_state == 1) {
// 入场
String parkingState = "ENTRY";
vehicleEntry(jsonObject, parkingState);
}
if (parking_state == 2) {
// 在场
}
if (parking_state == 4) {
// 出场
}
// saveInfo2DataBase(jsonObject);
}
public void saveHeartBeat2Redis(CameraHeartBeatDTO dto) {
// 将基本信息存入缓存
String redisKey = CacheConstants.CAMERA_HEARTBEAT + dto.getIp();
redisCache.setCacheObject(redisKey, dto.getSn());
}
/**
* 车辆入场
*
* @param jsonObject
*/
private void vehicleEntry(JSONObject jsonObject, String parkingState) throws InterruptedException {
// 先将车牌图片信息存入缓存
// boolean result = saveCarPicture2Redis(jsonObject, parkingState);
// 解析 jsonObject
// 车牌信息
CameraIdentifyResultsDTO.ProductH.Plate plate = JSONObject.parseObject(jsonObject.getJSONObject("product_h").getJSONObject("plate").toJSONString(),
CameraIdentifyResultsDTO.ProductH.Plate.class);
if (plate == null) {
return;
}
// 设备信息
CameraIdentifyResultsDTO.DeviceInfo deviceInfo = JSONObject.parseObject(jsonObject.getJSONObject("device_info").toJSONString(),
CameraIdentifyResultsDTO.DeviceInfo.class);
if (deviceInfo == null) {
return;
}
// 停车位信息
CameraIdentifyResultsDTO.ProductH.Parking parking = JSONObject.parseObject(jsonObject.getJSONObject("product_h").getJSONObject("parking").toJSONString(),
CameraIdentifyResultsDTO.ProductH.Parking.class);
if (parking == null) {
return;
}
// 获取背景图片
JSONArray bgImgs = jsonObject.getJSONArray("bg_img");
List<CameraIdentifyResultsDTO.BgImg> bgImgList = bgImgs.toList(CameraIdentifyResultsDTO.BgImg.class);
if (CollectionUtils.isEmpty(bgImgList)) {
return;
}
// Base64 解密
String plateNumber = cn.hutool.core.codec.Base64.decodeStr(plate.getPlate());
String zoneName = cn.hutool.core.codec.Base64.decodeStr(parking.getZoneName());
// 将解密后的值重新 set 进对象中,便于存储数据库
plate.setPlate(plateNumber);
parking.setZoneName(zoneName);
// 将信息存数据库
boolean result = saveInfo2DataBase(bgImgList, plate, deviceInfo, parking);
if (!result) {
logger.error("车辆入场,将信息存入数据库 error, 源数据:{}", jsonObject);
return;
}
// 先判断该车牌是否有挂起未支付的占桩订单
// 如果有占桩订单,则先提醒“需支付占桩订单”
// 根据车牌号找出绑定小程序的用户
List<MemberVO> memberList = memberBasicInfoService.getMemberInfoByPlateNumber(plateNumber);
if (CollectionUtils.isNotEmpty(memberList)) {
// 如果是有小程序的用户,则先降地锁,然后生成一笔占桩订单
// 发送降锁指令
Camera2GroundLockCommand command = Camera2GroundLockCommand.builder()
.sn(deviceInfo.getSn())
.msgType("GroundlockOption")
.msgPrefix("GO")
.topic("/remoteCommand")
.zoneId(parking.getZoneId())
.option(3) // 3-降锁后不自动升锁
.force(0) // 强制操作 0否 1
.build();
String msgId = sendGroundLockMsg(command);
// 判断降锁是否成功
// 降锁成功,生成占桩订单
} else {
// 如果没有小程序账号,再根据此车牌是否有挂起的占桩订单
// 如果没有,则先降锁,再生成一笔占桩订单
// 如果有已挂起的占桩订单,则不予降锁,将“已存在有未支付的占桩订单”信息返回
}
// TODO 生成占桩订单
// GenerateOccupyOrderDTO dto = new GenerateOccupyOrderDTO();
// dto.setMemberId();
// dto.setPileSn();
// dto.setConnectorCode();
// orderPileOccupyService.generateOccupyPileOrder()
}
private boolean saveInfo2DataBase(List<CameraIdentifyResultsDTO.BgImg> bgImgList, CameraIdentifyResultsDTO.ProductH.Plate plate,
CameraIdentifyResultsDTO.DeviceInfo deviceInfo, CameraIdentifyResultsDTO.ProductH.Parking parking) {
String zoneName = parking.getZoneName();
for (CameraIdentifyResultsDTO.BgImg bgImg : bgImgList) {
// 上传到阿里云OSS获取图片上传成功后的地址
String fileName = zoneName + "-" + System.currentTimeMillis() / 1000 + ".jpg";
String url = saveImage(bgImg.getImage(), fileName);
if (StringUtils.isBlank(url)) {
logger.error("车位号:{} 图片上传失败", zoneName);
continue;
}
PileCameraInfo pileCameraInfo = PileCameraInfo.builder()
.deviceName(deviceInfo.getDevName())
.deviceIp(deviceInfo.getIp())
.deviceSn(deviceInfo.getSn())
.plateNumber(plate.getPlate())
.parkingState(String.valueOf(parking.getParkingState()))
.zoneId(parking.getZoneId())
.zoneName(zoneName)
.color(plate.getColor())
.plateType(plate.getType())
.image(url)
.build();
// 插入数据库
pileCameraInfoService.insertPileCameraInfo(pileCameraInfo);
}
return true;
}
/**
* 保存图像
* @param base64Image 图像的Base64编码
* @param fileName 文件名
* @return
*/
private String saveImage(String base64Image, String fileName) {
try {
// 将Base64编码的字符串解码为字节数组
byte[] imageBytes = Base64.getDecoder().decode(base64Image);
// 将字节数组转换为 BufferedImage
ByteArrayInputStream inputStream = new ByteArrayInputStream(imageBytes);
BufferedImage originalImage = ImageIO.read(inputStream);
// 对图像进行压缩
double quality = 0.5; // 压缩质量
BufferedImage compressedImage = ImageUtils.compressImage(originalImage, quality);
// 将压缩后的图片转换为字节数组
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ImageIO.write(compressedImage, "jpg", outputStream);
byte[] compressedImageBytes = outputStream.toByteArray();
// 上传图片到OSS
String url = AliyunOssUploadUtils.upload2OSS(compressedImageBytes, fileName);
if (StringUtils.isNotBlank(url)) {
return url;
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 发送地锁升降指令
*/
private String sendGroundLockMsg(Camera2GroundLockCommand command) throws InterruptedException {
JSONObject msgData = new JSONObject();
msgData.put("option", command.getOption());
msgData.put("zone_id", command.getZoneId());
msgData.put("force", command.getForce());
String msgId = sendMsg2Topic(command.getSn(), command.getMsgType(), command.getMsgPrefix(), command.getTopic(), msgData);
return msgId;
}
/**
* 发送具体指令到某主题
* @param sn 设备 sn
* @param msgType 消息类型
* @param msgPrefix 消息前缀
* @param topic 主题
* @param msgData 消息内容
*
* @throws InterruptedException
* @return msg_id 相当于此消息的唯一标识,用于辨识返回报文
*/
public String sendMsg2Topic(String sn, String msgType, String msgPrefix, String topic, JSONObject msgData) throws InterruptedException {
JSONObject jsonObject = spliceStr(sn, msgType, msgPrefix);
// 通过sn查找出对应的channelId
String mqttConnectRedisKey = CacheConstants.MQTT_CONNECT_SN + sn;
Object cacheObject = redisCache.getCacheObject(mqttConnectRedisKey);
if (cacheObject == null) {
return null;
}
String channelId = (String) cacheObject;
if (msgData != null) {
jsonObject.put("msg_data", msgData);
}
logger.info("给相机发送远程命令sn:{}, 消息类型:{}, 主题:{}, 最终发送数据:{}", sn, msgType, topic, jsonObject.toJSONString());
// 发送消息
handler.sendMsg(channelId, topic, jsonObject.toJSONString());
return jsonObject.getString("msg_id");
}
/**
* 根据规则拼装字符串
* @param sn 设备 sn
* @param msgType 消息类型
* @param msgPrefix 消息前缀
* @return 拼装好的json对象
*/
private JSONObject spliceStr(String sn, String msgType, String msgPrefix) {
StringBuilder sb = new StringBuilder();
String msgId = msgPrefix + DateUtils.dateTimeNow(DateUtils.YYYYMMDDHHMMSS) + "01";
String timeStamp = DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD_HH_MM_SS);
sb.append("sn=").append(sn)
.append("&timestamp=").append(timeStamp)
.append("&msg_id=").append(msgId)
.append("&msg_type=").append(msgType);
// 进行 32 位 MD5 计算
String sign = MD5Util.MD5Encode(sb.toString()).toUpperCase(Locale.ROOT);
JSONObject jsonObject = new JSONObject();
jsonObject.put("sign", sign);
jsonObject.put("sn", sn);
jsonObject.put("timestamp", timeStamp);
jsonObject.put("msg_id", msgId);
jsonObject.put("msg_type", msgType);
return jsonObject;
}
/**
* 将车辆图片信息存入缓存
* @param jsonObject
*/
// private boolean saveCarPicture2Redis(JSONObject jsonObject, String parkingState) {
// // 获取车牌号
// String plateNumber = jsonObject.getJSONObject("plate").getString("plate");
// if (StringUtils.isBlank(plateNumber)) {
// return false;
// }
// // 获取背景图片
// JSONArray bgImgs = jsonObject.getJSONArray("bg_img");
// List<CameraIdentifyResultsDTO.BgImg> bgImgList = bgImgs.toList(CameraIdentifyResultsDTO.BgImg.class);
// if (CollectionUtils.isEmpty(bgImgList)) {
// return false;
// }
// for (CameraIdentifyResultsDTO.BgImg bgImg : bgImgList) {
// String image = bgImg.getImage(); // 图片的 base64 编码
// // String key = bgImg.getKey(); // 索引id
// // key: 前缀 + 车牌号 + 日期 + 入场/出场状态
// String redisKey = CacheConstants.CAMERA_IMAGE_BY_PLATE_NUMBER + plateNumber + "_" + DateUtils.getDate() + "_" + parkingState;
// // 存入缓存
// // 暂时永久保存
// redisCache.setCacheObject(redisKey, image);
// }
// return true;
// }
}