package com.ycwl.basic.controller.viid; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DateUtil; import cn.hutool.core.thread.ThreadFactoryBuilder; import cn.hutool.core.util.ObjectUtil; import com.ycwl.basic.integration.common.manager.DeviceConfigManager; import com.ycwl.basic.utils.JacksonUtil; import com.ycwl.basic.annotation.IgnoreLogReq; import com.ycwl.basic.annotation.IgnoreToken; import com.ycwl.basic.facebody.adapter.IFaceBodyAdapter; import com.ycwl.basic.facebody.entity.AddFaceResp; import com.ycwl.basic.integration.device.service.DeviceIntegrationService; import com.ycwl.basic.integration.device.dto.device.CreateDeviceRequest; import com.ycwl.basic.integration.device.dto.device.UpdateDeviceRequest; import com.ycwl.basic.integration.device.dto.device.DeviceV2DTO; import com.ycwl.basic.mapper.FaceSampleMapper; import com.ycwl.basic.mapper.SourceMapper; import com.ycwl.basic.model.pc.device.entity.DeviceConfigEntity; import com.ycwl.basic.model.pc.device.entity.DeviceCropConfig; import com.ycwl.basic.model.pc.device.entity.DeviceEntity; import com.ycwl.basic.model.pc.faceSample.entity.FaceSampleEntity; import com.ycwl.basic.model.pc.source.entity.SourceEntity; import com.ycwl.basic.model.viid.entity.DeviceIdObject; import com.ycwl.basic.model.viid.entity.FaceListObject; import com.ycwl.basic.model.viid.entity.FaceObject; import com.ycwl.basic.model.viid.entity.FacePositionObject; import com.ycwl.basic.model.viid.entity.ResponseStatusObject; import com.ycwl.basic.model.viid.entity.SubImageInfoObject; import com.ycwl.basic.model.viid.entity.SubImageList; import com.ycwl.basic.model.viid.entity.SystemTimeObject; import com.ycwl.basic.model.viid.req.FaceUploadReq; import com.ycwl.basic.model.viid.req.ImageUploadReq; import com.ycwl.basic.model.viid.req.KeepaliveReq; import com.ycwl.basic.model.viid.req.RegisterReq; import com.ycwl.basic.model.viid.req.UnRegisterReq; import com.ycwl.basic.model.viid.resp.SystemTimeResp; import com.ycwl.basic.model.viid.resp.VIIDBaseResp; import com.ycwl.basic.repository.DeviceRepository; import com.ycwl.basic.service.pc.ScenicService; import com.ycwl.basic.service.task.TaskFaceService; import com.ycwl.basic.storage.StorageFactory; import com.ycwl.basic.storage.adapters.IStorageAdapter; import com.ycwl.basic.storage.enums.StorageAcl; import com.ycwl.basic.storage.utils.StorageUtil; import com.ycwl.basic.task.DynamicTaskGenerator; import com.ycwl.basic.utils.ImageUtils; import com.ycwl.basic.utils.IpUtils; import com.ycwl.basic.utils.SnowFlakeUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import jakarta.servlet.http.HttpServletRequest; import java.awt.image.RasterFormatException; import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.Map; import java.util.TimeZone; import java.util.UUID; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import static com.ycwl.basic.constant.StorageConstant.PHOTO_PATH; import static com.ycwl.basic.constant.StorageConstant.VIID_FACE; @IgnoreToken @RestController // 摄像头对接接口 @RequestMapping("/VIID") @Slf4j public class ViidController { @Autowired private DeviceIntegrationService deviceIntegrationService; private static final String serverId = "00000000000000000001"; @Autowired private SourceMapper sourceMapper; @Autowired private DeviceRepository deviceRepository; @Autowired private TaskFaceService taskFaceService; private final Map executors = new ConcurrentHashMap<>(); @Autowired private ScenicService scenicService; private ThreadPoolExecutor getExecutor(Long scenicId) { return executors.computeIfAbsent(scenicId, k -> { ThreadFactory threadFactory = new ThreadFactoryBuilder() .setNamePrefix("VIID-" + scenicId + "-t") .build(); return new ThreadPoolExecutor( 4, 1024, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1024), threadFactory); }); } // region 注册注销基础接口 /** * 注册接口 * * @param req 注册的信息 * @param request 请求 * @return 返回 */ @RequestMapping(value = "/System/Register", method = RequestMethod.POST) public VIIDBaseResp register(@RequestBody RegisterReq req, HttpServletRequest request) { DeviceIdObject deviceIdObject = req.getRegisterObject(); log.info("注册的设备信息:{}", deviceIdObject); // 保存设备注册时间 String deviceId = deviceIdObject.getDeviceId(); DeviceEntity device = deviceRepository.getDeviceByDeviceNo(deviceId); if (device == null) { device = new DeviceEntity(); device.setName("未配置设备"); device.setNo(deviceId); device.setOnline(1); } device.setKeepaliveAt(new Date()); device.setIpAddr(IpUtils.getIpAddr(request)); if (device.getId() == null) { // 通过zt-device服务创建新设备 CreateDeviceRequest createRequest = new CreateDeviceRequest(); createRequest.setName(device.getName()); createRequest.setNo(device.getNo()); createRequest.setType("IPC"); // 默认类型为IPC createRequest.setIsActive(0); createRequest.setScenicId(0L); createRequest.setSort(0); try { DeviceV2DTO createdDevice = deviceIntegrationService.createDevice(createRequest); device.setId(createdDevice.getId()); } catch (Exception e) { log.warn("创建设备失败,设备编号: {}, 错误: {}", deviceId, e.getMessage()); } } return new VIIDBaseResp( new ResponseStatusObject(serverId, "/VIID/System/Register", "0", "注册成功", sdfTime.format(new Date())) ); } /** * 保活接口 * * @param req 保活的设备信息 * @param request 请求 * @return 返回 */ @IgnoreLogReq @RequestMapping(value = "/System/Keepalive", method = RequestMethod.POST) public VIIDBaseResp keepalive(@RequestBody KeepaliveReq req, HttpServletRequest request) { DeviceIdObject keepaliveObject = req.getKeepaliveObject(); // log.info("对方发送的心跳的信息:{}", keepaliveObject); String deviceId = keepaliveObject.getDeviceId(); DeviceEntity device = deviceRepository.getDeviceByDeviceNo(deviceId); // 判断设备状态 if (device == null) { // 不存在设备就注册 device = new DeviceEntity(); device.setName("未配置设备"); device.setNo(deviceId); device.setOnline(1); device.setKeepaliveAt(new Date()); device.setIpAddr(IpUtils.getIpAddr(request)); // 通过zt-device服务创建新设备 CreateDeviceRequest createRequest = new CreateDeviceRequest(); createRequest.setName(device.getName()); createRequest.setNo(device.getNo()); createRequest.setType("IPC"); // 默认类型为IPC createRequest.setIsActive(0); createRequest.setScenicId(0L); createRequest.setSort(0); try { DeviceV2DTO createdDevice = deviceIntegrationService.createDevice(createRequest); device.setId(createdDevice.getId()); } catch (Exception e) { log.warn("创建设备失败,设备编号: {}, 错误: {}", deviceId, e.getMessage()); } } else { deviceRepository.updateOnlineStatus(device.getId(), IpUtils.getIpAddr(request), 1, new Date()); } // log.info("已经解析过的心跳信息:{}", keepaliveObject); return new VIIDBaseResp( new ResponseStatusObject(deviceId, "/VIID/System/Keepalive", "0", "保活", sdfTime.format(new Date())) ); } /** * 注销设备 * * @param req 参数 * @return 返回 */ @RequestMapping(value = "/System/UnRegister", method = RequestMethod.POST) public VIIDBaseResp unRegister(@RequestBody UnRegisterReq req, HttpServletRequest request) { // 获取设备id DeviceIdObject unRegisterObject = req.getUnRegisterObject(); String deviceId = unRegisterObject.getDeviceId(); log.info("获取的注销的请求参数:{}", unRegisterObject); // 首先查询该设备是否存在 DeviceEntity device = deviceRepository.getDeviceByDeviceNo(deviceId); // 判断 if (device != null) { deviceRepository.updateOnlineStatus(device.getId(), IpUtils.getIpAddr(request), 0, new Date()); } return new VIIDBaseResp( new ResponseStatusObject(deviceId, "/VIID/System/UnRegister", "0", "注销成功", sdfTime.format(new Date())) ); } /** * 校时接口 * * @return 返回 */ @RequestMapping(value = "/System/Time", method = RequestMethod.GET) public SystemTimeResp time() { SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); return new SystemTimeResp( new SystemTimeObject(serverId, "2", sdf.format(new Date()), TimeZone.getTimeZone("Asia/Shanghai").toString()) ); } // endregion @Autowired private FaceSampleMapper faceSampleMapper; private final SimpleDateFormat sdfTime = new SimpleDateFormat("yyyyMMddHHmmss"); /** * 批量新增人脸 */ @RequestMapping(value = "/Faces", method = RequestMethod.POST) @IgnoreLogReq public VIIDBaseResp faces(@RequestBody FaceUploadReq req) { FaceListObject faceListObject = req.getFaceListObject(); List faceObject = faceListObject.getFaceObject(); String faceId = null; // 遍历人脸列表 for (FaceObject face : faceObject) { // 设置FaceId faceId = face.getFaceID(); // 获取图片信息 SubImageList subImageList = face.getSubImageList(); // 判断人脸对象中的列表是否为空 String deviceID = face.getDeviceID(); DeviceEntity device = deviceRepository.getDeviceByDeviceNo(deviceID); if (device == null) { continue; } DeviceConfigManager deviceConfig = deviceRepository.getDeviceConfigManager(device.getId()); DeviceConfigEntity deviceConfigEntity = deviceRepository.getDeviceConfig(device.getId()); if (deviceConfig == null) { log.warn("设备配置不存在:" + deviceID); return new VIIDBaseResp( new ResponseStatusObject(faceId, "/VIID/Faces", "0", "OK", sdfTime.format(new Date())) ); } Integer viidMode = deviceConfig.getInteger("viid_mode", 0); Date shotTime = null; if (StringUtils.isNotBlank(face.getShotTime())) { try { shotTime = sdfTime.parse(face.getShotTime()); } catch (ParseException e) { log.warn("拍摄时间时间转换失败,使用当前时间。错误entity:{}", face); } } if (shotTime == null) { if (StringUtils.isNotBlank(face.getFaceAppearTime())) { try { shotTime = sdfTime.parse(face.getFaceAppearTime()); } catch (ParseException e) { log.warn("拍摄时间时间转换失败,使用当前时间。错误entity:{}", face); } } } if (shotTime == null) { shotTime = new Date(); } else if (!DateUtil.isSameDay(shotTime, new Date())) { log.warn("时间不是今天,使用当前时间。错误entity:{}", face); shotTime = new Date(); } if (Math.abs(shotTime.getTime() - System.currentTimeMillis()) > 3600 * 1000) { String jsonString = JacksonUtil.toJSONStringCompat(req); log.warn("时间差超过1小时。device:{},错误entity:{}", device, jsonString); } Long scenicId = device.getScenicId(); if (scenicId == null) { continue; } IStorageAdapter scenicStorageAdapter = scenicService.getScenicStorageAdapter(scenicId); IFaceBodyAdapter faceBodyAdapter = scenicService.getScenicFaceBodyAdapter(scenicId); FacePositionObject facePosition = new FacePositionObject(); facePosition.setLtY(face.getLeftTopY()); facePosition.setLtX(face.getLeftTopX()); facePosition.setRbY(face.getRightBtmY()); facePosition.setRbX(face.getRightBtmX()); if (ObjectUtil.isNotEmpty(subImageList) && CollUtil.isNotEmpty(subImageList.getSubImageInfoObject())) { if (viidMode == 0) { // 遍历每个图片对象 // 先找到type14的图片 List type14ImageList = subImageList.getSubImageInfoObject().stream().filter(subImage -> "14".equals(subImage.getType())).toList(); for (SubImageInfoObject subImage : subImageList.getSubImageInfoObject()) { // base64转换成MultipartFIle MultipartFile file = ImageUtils.base64ToMultipartFile(subImage.getData()); String ext; if (subImage.getFileFormat().equalsIgnoreCase("jpeg")) { ext = "jpg"; } else { ext = subImage.getFileFormat(); } IStorageAdapter adapter = StorageFactory.use("faces"); // Type=11 人脸 if (subImage.getType().equals("11")) { // 上传oss Long newFaceSampleId = SnowFlakeUtil.getLongId(); if (Integer.valueOf(1).equals(device.getStatus())) { FaceSampleEntity faceSample = new FaceSampleEntity(); faceSample.setId(newFaceSampleId); faceSample.setScenicId(scenicId); faceSample.setDeviceId(device.getId()); faceSample.setStatus(0); faceSample.setCreateAt(shotTime); String url = adapter.uploadFile(file, VIID_FACE, UUID.randomUUID() + "." + ext); faceSample.setFaceUrl(url); faceSampleMapper.add(faceSample); ThreadPoolExecutor executor = getExecutor(scenicId); executor.execute(() -> { if (faceBodyAdapter != null) { taskFaceService.assureFaceDb(faceBodyAdapter, scenicId.toString()); AddFaceResp addFaceResp = faceBodyAdapter.addFace(scenicId.toString(), faceSample.getId().toString(), url, newFaceSampleId.toString()); if (addFaceResp != null) { faceSample.setScore(addFaceResp.getScore()); faceSampleMapper.updateScore(faceSample.getId(), addFaceResp.getScore()); } } if (Integer.valueOf(1).equals(deviceConfig.getInteger("enable_pre_book"))) { DynamicTaskGenerator.addTask(faceSample.getId()); } }); } for (SubImageInfoObject _subImage : type14ImageList) { facePosition.setImgHeight(_subImage.getHeight()); facePosition.setImgWidth(_subImage.getWidth()); SourceEntity source = new SourceEntity(); source.setDeviceId(device.getId()); source.setScenicId(device.getScenicId()); source.setFaceSampleId(newFaceSampleId); source.setCreateTime(shotTime); source.setType(2); // 上传oss MultipartFile _file = ImageUtils.base64ToMultipartFile(_subImage.getData()); ThreadPoolExecutor executor = getExecutor(scenicId); executor.execute(() -> { List cropConfigs = deviceConfigEntity._getCropConfig(); for (DeviceCropConfig cropConfig : cropConfigs) { source.setId(SnowFlakeUtil.getLongId()); String filename = StorageUtil.joinPath(PHOTO_PATH, UUID.randomUUID() + "." + ext); MultipartFile _finalFile = _file; if (cropConfig.getCropType() == 1) { // 按固定位置截图 try { _finalFile = ImageUtils.cropImage(_file, cropConfig.getTargetX(), cropConfig.getTargetY(), cropConfig.getTargetWidth(), cropConfig.getTargetHeight()); } catch (IOException e) { log.error("裁切图片失败!", e); } catch (RasterFormatException e) { log.error("裁切图片出错!", e); } } else if (cropConfig.getCropType() == 2) { // 按人脸位置 try { int targetX = facePosition.getLtX() - (cropConfig.getTargetWidth() - facePosition.getWidth())/2; int targetY = facePosition.getLtY() - (cropConfig.getTargetHeight() - facePosition.getHeight())/2; _finalFile = ImageUtils.cropImage(_file, targetX, targetY, cropConfig.getTargetWidth(), cropConfig.getTargetHeight()); } catch (IOException e) { log.error("裁切图片失败!", e); } catch (RasterFormatException e) { log.error("裁切图片出错!", e); } facePosition.setImgHeight(cropConfig.getTargetHeight()); facePosition.setImgWidth(cropConfig.getTargetWidth()); } String _sourceUrl = scenicStorageAdapter.uploadFile(_finalFile, filename); scenicStorageAdapter.setAcl(StorageAcl.PUBLIC_READ, filename); source.setUrl(_sourceUrl); source.setPosJson(JacksonUtil.toJSONString(facePosition)); sourceMapper.add(source); } }); } log.info("人脸信息及原图{}张入库成功!设备ID:{}", type14ImageList.size(), deviceID); } } } else if (viidMode == 1) { for (SubImageInfoObject subImage : subImageList.getSubImageInfoObject()) { // base64转换成MultipartFIle MultipartFile file = ImageUtils.base64ToMultipartFile(subImage.getData()); String ext = subImage.getFileFormat(); if (ext.equalsIgnoreCase("jpeg")) { ext = "jpg"; } IStorageAdapter adapter = StorageFactory.use("faces"); // Type=14 人脸,传™的,有这么传的嘛 if (subImage.getType().equals("14")) { // 上传oss if (Integer.valueOf(1).equals(device.getStatus())) { FaceSampleEntity faceSample = new FaceSampleEntity(); Long newFaceSampleId = SnowFlakeUtil.getLongId(); faceSample.setId(newFaceSampleId); faceSample.setScenicId(scenicId); faceSample.setDeviceId(device.getId()); faceSample.setStatus(0); faceSample.setCreateAt(shotTime); String url = adapter.uploadFile(file, VIID_FACE, UUID.randomUUID() + "." + ext); faceSample.setFaceUrl(url); faceSampleMapper.add(faceSample); DynamicTaskGenerator.addTask(faceSample.getId()); ThreadPoolExecutor executor = getExecutor(scenicId); executor.execute(() -> { if (faceBodyAdapter != null) { taskFaceService.assureFaceDb(faceBodyAdapter, scenicId.toString()); AddFaceResp addFaceResp = faceBodyAdapter.addFace(scenicId.toString(), faceSample.getId().toString(), url, newFaceSampleId.toString()); if (addFaceResp != null) { faceSample.setScore(addFaceResp.getScore()); faceSampleMapper.updateScore(faceSample.getId(), addFaceResp.getScore()); } } if (Integer.valueOf(1).equals(deviceConfig.getInteger("enable_pre_book"))) { DynamicTaskGenerator.addTask(faceSample.getId()); } }); log.info("模式1人脸信息入库成功!设备ID:{}", deviceID); } } } } } } return new VIIDBaseResp( new ResponseStatusObject(faceId, "/VIID/Faces", "0", "OK", sdfTime.format(new Date())) ); } @RequestMapping(value = "/Images", method = RequestMethod.POST) @IgnoreLogReq public VIIDBaseResp images(HttpServletRequest request, @RequestBody ImageUploadReq req) throws IOException { return new VIIDBaseResp( new ResponseStatusObject("1", "/VIID/Images", "0", "OK", sdfTime.format(new Date())) ); } }