puzhibing
10 小时以前 fe9bba84f5ded2ff0efec46cc706149b1a4073d8
添加日志记录
1个文件已添加
3个文件已修改
217 ■■■■■ 已修改文件
ruoyi-service/ruoyi-integration/src/main/java/com/ruoyi/integration/iotda/utils/listener/IotMessageListener.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-integration/src/main/java/com/ruoyi/integration/iotda/utils/produce/IotMessageProduce.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-integration/src/main/java/com/ruoyi/integration/iotda/utils/tools/IotCommandLog.java 157 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-integration/src/main/resources/logback.xml 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-integration/src/main/java/com/ruoyi/integration/iotda/utils/listener/IotMessageListener.java
@@ -17,6 +17,7 @@
import com.ruoyi.integration.iotda.model.*;
import com.ruoyi.integration.iotda.utils.produce.IotMessageProduce;
import com.ruoyi.integration.iotda.utils.tools.CP56Time2aConverter;
import com.ruoyi.integration.iotda.utils.tools.IotCommandLog;
import com.ruoyi.integration.iotda.utils.tools.MessageUtil;
import com.ruoyi.integration.iotda.utils.tools.StrategyUtil;
import com.ruoyi.integration.iotda.utils.tools.ChargingMessageUtil;
@@ -84,11 +85,12 @@
            content = body.getJSONObject("content");
        }catch (Exception e){
            String content1 = body.getString("content");
            // 按对象解析失败时无法识别 service_id(无法判断是否心跳),记录原始报文兜底便于排查
            IotCommandLog.uplinkUnparsed(deviceNoOf(jsonObject), content1);
            String substring = content1.substring(content1.length() - 1);
            if(!"}".equals(substring)){
                content1 = content1.substring(0, content1.length() - 1) + "}";
            }
//            log.info("content1:"+content1);
            content = JSON.parseObject(content1);
        }
        JSONObject header = jsonObject.getJSONObject("notify_data").getJSONObject("header");
@@ -96,10 +98,8 @@
        String nodeId = header.getString("node_id"); // 设备编号
        String productId = header.getString("product_id"); // 产品id
        String service_id = content.getString("service_id");
        if(!SendTagConstant.PING.equals(service_id)){
            log.info("收到IOT发送的消息:{}",jsonObject.toJSONString());
        }
//        log.info("服务id:{}",service_id);
        // 记录上行指令日志(独立日志文件、含指令中文含义;心跳 ping 不记录)
        IotCommandLog.uplink(deviceNoOf(jsonObject), service_id, content.toJSONString());
        ChargingMessage chargingMessage = new ChargingMessage();
        chargingMessage.setServiceId(service_id);
        // 设备消息下发
@@ -127,7 +127,6 @@
                log.warn("ONLINE消息处理:{} 毫秒", System.currentTimeMillis() - ONLINE);
                break;
            case SendTagConstant.PING:
                long PING = System.currentTimeMillis();
                PingMessage pingMessage = JSON.parseObject(content.toJSONString(),PingMessage.class);
                String fullName = pingMessage.getCharging_pile_code() + pingMessage.getCharging_gun_code();
                //主动丢弃一次心跳数据,用于降低调华为接口评率,防止被限流
@@ -174,8 +173,6 @@
                vo1.setPile_code(pingMessage.getCharging_pile_code());
                vo1.setStatus(pingMessage.getCharging_gun_status());
                chargingPileClient.updateChargingPileStatus(vo1);
                log.warn("PING消息处理:{} 毫秒", System.currentTimeMillis() - PING);
                break;
            case SendTagConstant.END_CHARGE:
                long END_CHARGE = System.currentTimeMillis();
@@ -531,6 +528,22 @@
        return AjaxResult.success();
    }
    /**
     * 从IoTDA推送报文中提取设备编号(node_id 优先,缺失时回退 device_id)
     */
    private String deviceNoOf(JSONObject msg) {
        try {
            JSONObject header = msg.getJSONObject("notify_data").getJSONObject("header");
            if (header == null) {
                return null;
            }
            String nodeId = header.getString("node_id");
            return (nodeId == null || nodeId.isEmpty()) ? header.getString("device_id") : nodeId;
        } catch (Exception e) {
            return null;
        }
    }
    @ApiOperation(value = "运营平台远程控制启机",tags = {"硬件接口"})
    @GetMapping(value = "/platformStartCharging")
ruoyi-service/ruoyi-integration/src/main/java/com/ruoyi/integration/iotda/utils/produce/IotMessageProduce.java
@@ -7,6 +7,7 @@
import com.huaweicloud.sdk.iotda.v5.model.DeviceMessageRequest;
import com.ruoyi.common.core.utils.uuid.UUID;
import com.ruoyi.integration.iotda.builder.IotBuilder;
import com.ruoyi.integration.iotda.utils.tools.IotCommandLog;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
@@ -38,11 +39,15 @@
        body.withName(name);
        body.withMessage(message);
        request.withBody(body);
        // 记录下行指令日志(独立日志文件、含指令中文含义;心跳应答 pong 不记录)
        String serviceId = message == null ? null : message.getString("service_id");
        IotCommandLog.downlink(code, name, serviceId, message);
        try {
            CreateMessageResponse response = iotBuilder.buildIot().createMessage(request);
            return JSON.toJSONString(response);
        } catch (Exception e) {
            e.printStackTrace();
            IotCommandLog.downlinkFail(code, name, serviceId, message, e.getMessage());
            return e.getMessage();
        }
    }
ruoyi-service/ruoyi-integration/src/main/java/com/ruoyi/integration/iotda/utils/tools/IotCommandLog.java
New file
@@ -0,0 +1,157 @@
package com.ruoyi.integration.iotda.utils.tools;
import com.ruoyi.integration.iotda.constant.SendTagConstant;
import com.ruoyi.integration.iotda.enums.ServiceIdMenu;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
/**
 * 充电桩指令收发日志
 * <p>
 * 1、使用独立 logger(pile-command)输出到独立日志文件,与业务日志分离(见 logback.xml);
 * 2、日志记录统一打印指令含义,格式:指令=中文含义(service_id);
 * 3、心跳指令(上行 ping、下行 pong)不记录,避免日志量过大。
 */
public class IotCommandLog {
    /** 独立日志 logger,输出到独立日志文件(logback.xml 中 pile-command 配置) */
    private static final Logger COMMAND_LOG = LoggerFactory.getLogger("pile-command");
    /** 上行指令 service_id -> 中文含义 */
    private static final Map<String, String> UPLINK_MEANING = new HashMap<>();
    static {
        UPLINK_MEANING.put(SendTagConstant.ONLINE, "充电桩登录认证");
        UPLINK_MEANING.put(SendTagConstant.PING, "充电桩心跳包");
        UPLINK_MEANING.put(SendTagConstant.END_CHARGE, "充电结束");
        UPLINK_MEANING.put(SendTagConstant.ERROR_MESSAGE, "错误报文");
        UPLINK_MEANING.put(SendTagConstant.BILLING_MODE_VERIFY, "计费模型验证请求");
        UPLINK_MEANING.put(SendTagConstant.ACQUISITION_BILLING_MODE, "充电桩计费模型请求");
        UPLINK_MEANING.put(SendTagConstant.UPLOAD_REAL_TIME_MONITORING_DATA, "上传实时监测数据");
        UPLINK_MEANING.put(SendTagConstant.CHARGING_HANDSHAKE, "充电握手");
        UPLINK_MEANING.put(SendTagConstant.PARAMETER_SETTING, "参数配置");
        UPLINK_MEANING.put(SendTagConstant.BMS_ABORT, "充电阶段BMS中止");
        UPLINK_MEANING.put(SendTagConstant.MOTOR_ABORT, "充电阶段充电机中止");
        UPLINK_MEANING.put(SendTagConstant.BMS_DEMAND_AND_CHARGER_EXPORTATION, "充电过程BMS需求、充电机输出");
        UPLINK_MEANING.put(SendTagConstant.BMS_INFORMATION, "充电过程BMS信息");
        UPLINK_MEANING.put(SendTagConstant.CHARGING_PILE_STARTS_CHARGING, "充电桩主动申请启动充电");
        UPLINK_MEANING.put(SendTagConstant.PLATFORM_START_CHARGING_REPLY, "远程启机命令回复");
        UPLINK_MEANING.put(SendTagConstant.PLATFORM_STOP_CHARGING_REPLY, "远程停机命令回复");
        UPLINK_MEANING.put(SendTagConstant.TRANSACTION_RECORD, "交易记录");
        UPLINK_MEANING.put(SendTagConstant.UPDATE_BALANCE_REPLY, "余额更新应答");
        UPLINK_MEANING.put(SendTagConstant.SYNCHRONIZE_OFFLINE_CARD_REPLY, "卡数据同步应答");
        UPLINK_MEANING.put(SendTagConstant.CLEAR_OFFLINE_CARD_REPLY, "离线卡数据清除应答");
        UPLINK_MEANING.put(SendTagConstant.QUERY_OFFLINE_CARD_REPLY, "离线卡数据查询应答");
        UPLINK_MEANING.put(SendTagConstant.WORKING_PARAMETER_SETTING_REPLY, "充电桩工作参数设置应答");
        UPLINK_MEANING.put(SendTagConstant.TIMING_SETTING, "对时设置");
        UPLINK_MEANING.put(SendTagConstant.SETUP_BILLING_MODEL_REPLY, "计费模型应答");
        UPLINK_MEANING.put(SendTagConstant.GROUND_LOCK_REAL_TIME_DATA, "地锁数据上送");
        UPLINK_MEANING.put(SendTagConstant.CHARGING_PILE_RETURNS_GROUND_LOCK_DATA, "充电桩返回地锁数据");
        UPLINK_MEANING.put(SendTagConstant.PLATFORM_RESTART_REPLY, "远程重启应答");
        UPLINK_MEANING.put(SendTagConstant.PLATFORM_REMOTE_UPDATE_REPLY, "远程更新应答");
        UPLINK_MEANING.put(SendTagConstant.QR_CODE_DELIVERY_REPLY, "二维码下发应答");
        UPLINK_MEANING.put(SendTagConstant.SECURITY_DETECTION, "安全监测");
        UPLINK_MEANING.put(SendTagConstant.GUN_STATUS, "推送充电设备接口状态信息");
        UPLINK_MEANING.put(SendTagConstant.ORDER_STATUS, "推送充电状态信息");
        UPLINK_MEANING.put(SendTagConstant.ORDER_INFO, "推送充电订单信息");
    }
    private IotCommandLog() {
    }
    /**
     * 记录上行指令(设备 -> 平台)
     * 心跳(ping)不记录
     *
     * @param deviceNo  设备编号
     * @param serviceId 指令 service_id
     * @param payload   报文内容
     */
    public static void uplink(String deviceNo, String serviceId, Object payload) {
        if (SendTagConstant.PING.equals(serviceId)) {
            // 心跳不记录日志
            return;
        }
        COMMAND_LOG.info("[上行] 设备={} 指令={} 报文={}", deviceNo, describeUplink(serviceId), payload);
    }
    /**
     * 记录下行指令(平台 -> 设备)
     * 心跳应答(pong)不记录
     *
     * @param deviceNo  设备编号
     * @param name      下发名称
     * @param serviceId 指令 service_id
     * @param payload   报文内容
     */
    public static void downlink(String deviceNo, String name, String serviceId, Object payload) {
        if (isPong(serviceId)) {
            // 心跳应答不记录日志
            return;
        }
        COMMAND_LOG.info("[下行] 设备={} 指令={} 报文={}", deviceNo, describeDownlink(serviceId, name), payload);
    }
    /**
     * 记录下行指令下发失败
     * 心跳应答(pong)不记录
     *
     * @param deviceNo  设备编号
     * @param name      下发名称
     * @param serviceId 指令 service_id
     * @param payload   报文内容
     * @param error     失败原因
     */
    public static void downlinkFail(String deviceNo, String name, String serviceId, Object payload, String error) {
        if (isPong(serviceId)) {
            // 心跳应答不记录日志
            return;
        }
        COMMAND_LOG.error("[下行失败] 设备={} 指令={} 报文={} 错误={}", deviceNo, describeDownlink(serviceId, name), payload, error);
    }
    /**
     * 记录无法解析的上行原始报文(兜底)
     * 报文解析失败时无法识别 service_id,不能判断是否为心跳,仍记录原始内容便于排查
     *
     * @param deviceNo   设备编号
     * @param rawPayload 原始报文内容
     */
    public static void uplinkUnparsed(String deviceNo, Object rawPayload) {
        COMMAND_LOG.info("[上行-原始报文] 设备={} 报文={}", deviceNo, rawPayload);
    }
    /** 是否心跳应答(pong) */
    private static boolean isPong(String serviceId) {
        return ServiceIdMenu.PONG.getValue().equals(serviceId);
    }
    /** 上行指令含义:中文含义(service_id) */
    private static String describeUplink(String serviceId) {
        String meaning = UPLINK_MEANING.get(serviceId);
        if (meaning == null || meaning.isEmpty()) {
            meaning = "未识别指令";
        }
        return meaning + "(" + serviceId + ")";
    }
    /** 下行指令含义:中文含义(service_id) */
    private static String describeDownlink(String serviceId, String name) {
        String meaning = null;
        if (serviceId != null) {
            for (ServiceIdMenu menu : ServiceIdMenu.values()) {
                if (menu.getValue().equals(serviceId)) {
                    meaning = menu.getKey();
                    break;
                }
            }
        }
        if (meaning == null || meaning.isEmpty()) {
            meaning = (name == null || name.isEmpty()) ? "未识别指令" : name;
        }
        return meaning + "(" + serviceId + ")";
    }
}
ruoyi-service/ruoyi-integration/src/main/resources/logback.xml
@@ -163,6 +163,32 @@
        </filter>
    </appender>
    
    <!-- 2.6 充电桩指令收发独立日志,独立文件输出(心跳不记录,见 IotCommandLog) -->
    <appender name="PILE_COMMAND_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文档的路径及文档名 -->
        <file>${log.path}/pile-command.log</file>
        <!--日志文档输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志归档 -->
            <fileNamePattern>${log.path}/%d{yyyy-MM-dd}/pile-command-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文档保留天数-->
            <maxHistory>30</maxHistory>
        </rollingPolicy>
    </appender>
    <!-- 充电桩指令收发日志:独立文件输出,不向 root 传递(additivity=false),避免污染业务日志 -->
    <logger name="pile-command" level="INFO" additivity="false">
        <appender-ref ref="PILE_COMMAND_FILE" />
    </logger>
    <!--
        <logger>用来设置某一个包或者具体的某一个类的日志打印级别、
        以及指定<appender>。<logger>仅有一个name属性,