mirror of
https://codeup.aliyun.com/67c68d4e484ca2f0a13ac3c1/ydc/jsowell-charger-web.git
synced 2026-04-20 02:55:04 +08:00
361 lines
14 KiB
Java
361 lines
14 KiB
Java
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("×tamp=").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;
|
||
// }
|
||
|
||
}
|