src/main/java/com/ruoyi/envaccess/controller/EnvInspectionController.java
@@ -1,16 +1,24 @@
package com.ruoyi.envaccess.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.handler.CustomTenantLineHandler;
import com.ruoyi.framework.aspectj.lang.annotation.Anonymous;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.envaccess.pojo.EnvInspection;
import com.ruoyi.envaccess.service.IEnvInspectionService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
 * 环保门禁--智能巡检台账(含路径规划/AI视频分析/报告/无人机机器人,AI与无人机字段预留)
@@ -67,4 +75,144 @@
    public void export(HttpServletResponse response, EnvInspection req) {
        envInspectionService.export(response, req);
    }
    @Value("${envaccess.callback.secret:hbmjxt-callback-2026}")
    private String callbackSecret;
    private static final ObjectMapper CALLBACK_MAPPER = new ObjectMapper();
    /**
     * 无人机 / 机器人 / AI 摄像头 巡检数据回传(对外开放接口,供第三方设备调用)
     *
     * 请求示例(POST JSON):
     * {
     *   "secret": "hbmjxt-callback-2026",
     *   "tenantId": 222,
     *   "inspectionId": 3,                    // 可选,不传则按 area 匹配最近一条未取消任务,再没有就新建
     *   "area": "东门区域",
     *   "deviceType": "UAV",                  // UAV 无人机 / ROBOT 机器人 / CAMERA 摄像头
     *   "deviceCode": "UAV-M350-001",
     *   "collectedAt": "2026-10-24 09:12:35",
     *   "aiSummary": "共识别 5 类目标,发现 1 处未佩戴安全帽",
     *   "aiObjects": [{"object":"人员未佩戴安全帽","conclusion":"未按要求佩戴","confidence":0.93,"frameTime":"2026-10-24 09:12:35"}],
     *   "longitude": 120.382145, "latitude": 31.298512, "altitude": 45.6, "battery": 78,
     *   "photos": ["/profile/upload/uav_east_01.jpg"],
     *   "track": [{"t":"09:12:10","lng":120.3821,"lat":31.2985}]
     * }
     */
    @Anonymous
    @PostMapping("/callback")
    public AjaxResult callback(@RequestBody Map<String, Object> payload) {
        if (payload == null || payload.isEmpty()) {
            return AjaxResult.error("回传数据不能为空");
        }
        if (!callbackSecret.equals(String.valueOf(payload.get("secret")))) {
            return AjaxResult.error("secret 校验失败,请在请求体中携带正确的 secret");
        }
        Long tenantId = toLong(payload.get("tenantId"));
        if (tenantId == null) {
            return AjaxResult.error("tenantId 不能为空");
        }
        String area = payload.get("area") == null ? "未指定区域" : String.valueOf(payload.get("area"));
        String deviceType = payload.get("deviceType") == null ? "UAV" : String.valueOf(payload.get("deviceType"));
        String deviceCode = payload.get("deviceCode") == null ? deviceType + "-UNKNOWN" : String.valueOf(payload.get("deviceCode"));
        Map<String, Object> ai = new LinkedHashMap<>();
        ai.put("summary", payload.get("aiSummary") == null ? "本次回传未附带 AI 识别结论" : payload.get("aiSummary"));
        ai.put("objects", payload.get("aiObjects") == null ? new ArrayList<>() : payload.get("aiObjects"));
        Map<String, Object> uav = new LinkedHashMap<>();
        uav.put("deviceType", deviceType);
        uav.put("deviceCode", deviceCode);
        uav.put("area", area);
        uav.put("collectedAt", payload.get("collectedAt") == null ? "" : payload.get("collectedAt"));
        uav.put("longitude", payload.get("longitude"));
        uav.put("latitude", payload.get("latitude"));
        uav.put("altitude", payload.get("altitude"));
        uav.put("battery", payload.get("battery"));
        uav.put("photos", payload.get("photos") == null ? new ArrayList<>() : payload.get("photos"));
        uav.put("track", payload.get("track") == null ? new ArrayList<>() : payload.get("track"));
        uav.put("receivedAt", new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        String aiJson;
        String uavJson;
        try {
            aiJson = CALLBACK_MAPPER.writeValueAsString(ai);
            uavJson = CALLBACK_MAPPER.writeValueAsString(uav);
        } catch (Exception e) {
            return AjaxResult.error("回传数据序列化失败:" + e.getMessage());
        }
        EnvInspection target = null;
        Long inspectionId = toLong(payload.get("inspectionId"));
        if (inspectionId != null) {
            target = envInspectionService.getById(inspectionId);
        }
        if (target == null) {
            com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<EnvInspection> w =
                    new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<>();
            w.eq(EnvInspection::getArea, area).ne(EnvInspection::getStatus, 3)
                    .orderByDesc(EnvInspection::getId).last("limit 1");
            target = envInspectionService.getOne(w, false);
        }
        boolean created = false;
        if (target == null) {
            target = new EnvInspection();
            target.setInspectionName(area + " 智能巡检(设备回传)");
            target.setInspectionType(2);
            target.setArea(area);
            target.setStatus(2);
            target.setPlanStartTime(new Date());
            target.setPlanEndTime(new Date());
            target.setActualStartTime(new Date());
            target.setActualEndTime(new Date());
            target.setInspector(deviceCode);
            target.setRoutePlan("由 " + deviceType + " 自动规划并执行");
            target.setInspectionResult("设备自动巡检已完成,识别结果详见回传数据。");
            target.setTenantId(tenantId);
            created = true;
        }
        target.setAiAnalysis(aiJson);
        target.setUavData(uavJson);
        if (target.getStatus() != null && target.getStatus() == 0) {
            target.setStatus(2);
        }
        if (target.getTenantId() == null) {
            target.setTenantId(tenantId);
        }
        // 设备回调没有登录上下文,临时跳过租户插件,tenant_id 已在上面显式写入
        CustomTenantLineHandler.TEMP_IGNORE.set(Boolean.TRUE);
        try {
            if (created) {
                envInspectionService.save(target);
            } else {
                envInspectionService.updateById(target);
            }
        } catch (Exception e) {
            return AjaxResult.error("回传数据落库失败:" + e.getMessage());
        } finally {
            CustomTenantLineHandler.TEMP_IGNORE.remove();
        }
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("inspectionId", target.getId());
        result.put("created", created);
        result.put("deviceCode", deviceCode);
        result.put("deviceType", deviceType);
        result.put("area", area);
        return AjaxResult.success("巡检数据回传成功", result);
    }
    private Long toLong(Object v) {
        if (v == null) {
            return null;
        }
        try {
            return Long.valueOf(String.valueOf(v).trim());
        } catch (NumberFormatException e) {
            return null;
        }
    }
}