package com.zy.asrs.service.impl; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.mapper.EntityWrapper; import com.core.common.Cools; import com.zy.asrs.entity.LocDetl; import com.zy.asrs.service.InboundCameraCaptureService; import com.zy.asrs.service.LocDetlService; import com.zy.system.entity.Config; import com.zy.system.service.ConfigService; import lombok.extern.slf4j.Slf4j; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import okhttp3.ResponseBody; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; @Slf4j @Service public class InboundCameraCaptureServiceImpl implements InboundCameraCaptureService { private static final String CONFIG_CODE = "InboundCameraCapture"; private static final MediaType JSON_MEDIA_TYPE = MediaType.parse("application/json;charset=utf-8"); private static final OkHttpClient HTTP_CLIENT = new OkHttpClient.Builder() .connectTimeout(5, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .build(); @Autowired private ConfigService configService; @Autowired private LocDetlService locDetlService; @Override public String capture(Integer sourceStaNo, String locNo, String barcode) { CaptureConfig config = loadConfig(); if (config == null || !config.enabled) { return null; } if (sourceStaNo == null) { log.warn("入库抓拍配置跳过:sourceStaNo为空"); return null; } JSONArray cameras = config.stations.getJSONArray(String.valueOf(sourceStaNo)); if (Cools.isEmpty(cameras)) { log.warn("入库抓拍配置缺失:sourceStaNo={}", sourceStaNo); return null; } if (cameras.size() != 2) { log.warn("入库抓拍摄像头数量不是2:sourceStaNo={}, count={}", sourceStaNo, cameras.size()); } String baseName = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + "_" + sanitize(String.valueOf(sourceStaNo)) + "_" + sanitize(locNo) + "_" + sanitize(barcode); List successPics = new ArrayList<>(); for (int i = 0; i < cameras.size(); i++) { JSONObject camera = cameras.getJSONObject(i); if (camera == null) { continue; } String picName = baseName + "-" + (i + 1) + ".jpg"; if (captureOne(config.captureUrl, sourceStaNo, i + 1, camera, picName)) { successPics.add(picName); } } return successPics.isEmpty() ? null : JSON.toJSONString(successPics); } @Override public List resolvePicUrls(String pic) { CaptureConfig config = loadConfig(); String prefix = config == null ? "" : config.imageUrlPrefix; Set urls = new LinkedHashSet<>(); for (String picName : parsePicNames(pic)) { if (Cools.isEmpty(picName)) { continue; } if (picName.startsWith("http://") || picName.startsWith("https://")) { urls.add(picName); } else if (!Cools.isEmpty(prefix)) { urls.add(prefix + picName); } } return new ArrayList<>(urls); } @Override public List resolveLocPicUrls(String locNo) { if (Cools.isEmpty(locNo)) { return new ArrayList<>(); } List locDetls = locDetlService.selectList(new EntityWrapper().eq("loc_no", locNo)); Set urls = new LinkedHashSet<>(); for (LocDetl locDetl : locDetls) { urls.addAll(resolvePicUrls(locDetl.getPic())); } return new ArrayList<>(urls); } private boolean captureOne(String captureUrl, Integer sourceStaNo, int index, JSONObject camera, String picName) { String ip = camera.getString("ip"); if (Cools.isEmpty(ip)) { log.warn("入库抓拍摄像头IP为空:sourceStaNo={}, cameraIndex={}", sourceStaNo, index); return false; } JSONObject requestJson = new JSONObject(); requestJson.put("ip", ip); requestJson.put("port", defaultInt(camera.getInteger("port"), 554)); requestJson.put("user", defaultString(camera.getString("user"), "admin")); requestJson.put("password", defaultString(camera.getString("password"), "a327482030")); requestJson.put("picName", picName); try { RequestBody body = RequestBody.create(JSON_MEDIA_TYPE, requestJson.toJSONString()); Request request = new Request.Builder() .url(captureUrl) .post(body) .build(); try (Response response = HTTP_CLIENT.newCall(request).execute()) { ResponseBody responseBody = response.body(); String bodyText = responseBody == null ? "" : responseBody.string(); if (!response.isSuccessful()) { log.warn("入库抓拍HTTP失败:sourceStaNo={}, cameraIndex={}, ip={}, httpCode={}, picName={}", sourceStaNo, index, ip, response.code(), picName); return false; } JSONObject result = JSON.parseObject(bodyText); Integer code = result == null ? null : result.getInteger("code"); if (code != null && code == 200) { return true; } String msg = result == null ? bodyText : result.getString("msg"); log.warn("入库抓拍返回失败:sourceStaNo={}, cameraIndex={}, ip={}, code={}, msg={}, picName={}", sourceStaNo, index, ip, code, msg, picName); return false; } } catch (Exception e) { log.warn("入库抓拍异常:sourceStaNo={}, cameraIndex={}, ip={}, picName={}", sourceStaNo, index, ip, picName, e); return false; } } private CaptureConfig loadConfig() { Config config = configService.selectConfigByCode(CONFIG_CODE); if (config == null || Cools.isEmpty(config.getValue())) { return null; } if (config.getStatus() != null && config.getStatus() == 0) { return null; } try { JSONObject json = JSON.parseObject(config.getValue()); CaptureConfig captureConfig = new CaptureConfig(); Boolean enabled = json.getBoolean("enabled"); captureConfig.enabled = enabled == null || enabled; captureConfig.captureUrl = json.getString("captureUrl"); captureConfig.imageUrlPrefix = normalizePrefix(json.getString("imageUrlPrefix"), captureConfig.captureUrl); captureConfig.stations = json.getJSONObject("stations"); if (Cools.isEmpty(captureConfig.captureUrl) || captureConfig.stations == null) { log.warn("入库抓拍配置不完整:code={}", CONFIG_CODE); return null; } return captureConfig; } catch (Exception e) { log.warn("入库抓拍配置解析失败:code={}", CONFIG_CODE, e); return null; } } private List parsePicNames(String pic) { List names = new ArrayList<>(); if (Cools.isEmpty(pic)) { return names; } try { Object parsed = JSON.parse(pic); if (parsed instanceof JSONArray) { JSONArray array = (JSONArray) parsed; for (int i = 0; i < array.size(); i++) { names.add(array.getString(i)); } return names; } if (parsed instanceof String) { names.add((String) parsed); return names; } } catch (Exception ignored) { // 兼容旧数据中直接存单个文件名或逗号分隔文件名的情况。 } String[] split = pic.split(","); for (String item : split) { if (!Cools.isEmpty(item)) { names.add(item.trim()); } } return names; } private String normalizePrefix(String imageUrlPrefix, String captureUrl) { String prefix = imageUrlPrefix; if (Cools.isEmpty(prefix) && !Cools.isEmpty(captureUrl)) { int idx = captureUrl.lastIndexOf("/"); prefix = idx > "http://".length() ? captureUrl.substring(0, idx) + "/image/" : ""; } if (!Cools.isEmpty(prefix) && !prefix.endsWith("/")) { prefix = prefix + "/"; } return prefix; } private String sanitize(String value) { if (value == null) { return "null"; } return value.replaceAll("[\\\\/:*?\"<>|\\s]+", "_"); } private int defaultInt(Integer value, int defaultValue) { return value == null ? defaultValue : value; } private String defaultString(String value, String defaultValue) { return Cools.isEmpty(value) ? defaultValue : value; } private static class CaptureConfig { private boolean enabled; private String captureUrl; private String imageUrlPrefix; private JSONObject stations; } }