e5e76412f1a20e8aed95614cbd7bf2b638cda2cc..9671fa60b69a5b749bfbd989f0aa281aa284dde6
3 天以前 zhang
1
9671fa 对比 | 目录
3 天以前 zhang
1
bc5653 对比 | 目录
16个文件已修改
10个文件已添加
2138 ■■■■ 已修改文件
component/component-Influxdb/src/main/java/com/zy/component/influxdb/domain/BaseMessage.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
component/component-Influxdb/src/main/java/com/zy/component/influxdb/service/InfluxDBService.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
version/db/cv.sql 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-common/src/main/java/com/zy/acs/common/domain/mq/DeviceMessage.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-cv/src/main/resources/application.yml 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-cv/src/main/resources/mapper/JobMapper.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-gateway/src/main/java/com/zy/acs/gateway/controller/UtilsController.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/dashboard.html 728 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/java/com/zy/acs/hex/config/WebMvcConfig.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/java/com/zy/acs/hex/controller/DeviceLogController.java 59 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/java/com/zy/acs/hex/controller/LoginController.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/java/com/zy/acs/hex/controller/ProxyController.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/java/com/zy/acs/hex/controller/RouterController.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/java/com/zy/acs/hex/controller/SelectTypeController.java 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/java/com/zy/acs/hex/controller/TestController.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/java/com/zy/acs/hex/domain/DeviceLog.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/java/com/zy/acs/hex/domain/GenericQuery.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/java/com/zy/acs/hex/domain/SelectOption.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/java/com/zy/acs/hex/enums/DirectionType.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/java/com/zy/acs/hex/enums/ProtocolType.java 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/java/com/zy/acs/hex/influxdb/task/InfluxDbScheduler.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/java/com/zy/acs/hex/utils/HttpGo.java 60 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/java/com/zy/acs/hex/utils/StrUtils.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/resources/application.yml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/webapp/views/index.html 706 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/webapp/views/login.html 148 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
component/component-Influxdb/src/main/java/com/zy/component/influxdb/domain/BaseMessage.java
New file
@@ -0,0 +1,12 @@
package com.zy.component.influxdb.domain;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
public class BaseMessage implements Serializable {
    private Long timestamp;
}
component/component-Influxdb/src/main/java/com/zy/component/influxdb/service/InfluxDBService.java
@@ -6,6 +6,7 @@
import com.influxdb.v3.client.query.QueryOptions;
import com.influxdb.v3.client.query.QueryType;
import com.influxdb.v3.client.write.WritePrecision;
import com.zy.component.influxdb.domain.BaseMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -15,6 +16,7 @@
import java.lang.reflect.InvocationTargetException;
import java.time.Instant;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -68,17 +70,25 @@
        }
        return null;
    }
    /**
     * 查询数据
     *
     * @param sql sql语句
     * @return 查询结果列表
     */
    public <T> List<T> queryPoints(String sql, Class<T> clazz) {
    public <T  extends BaseMessage> List<T> queryPoints(String sql, Class<T> clazz) {
       return queryPoints(sql,new HashMap<>(),clazz);
    }
    /**
     * 查询数据
     *
     * @param sql sql语句
     * @return 查询结果列表
     */
    public <T  extends BaseMessage> List<T> queryPoints(String sql,Map<String,Object> queryParams, Class<T> clazz) {
        try {
            // 执行查询
            Stream<PointValues> queryPoints = influxDBClient.queryPoints(sql);
            Stream<PointValues> queryPoints = influxDBClient.queryPoints(sql, queryParams);
            Field[] declaredFields = clazz.getDeclaredFields();
                
            // 创建一个列表用于存储结果
@@ -124,10 +134,9 @@
                        }
                    }
                }
                newInstance.setTimestamp(point.getTimestamp().longValue());
                return newInstance;
            }).collect(Collectors.toList());
            logger.info("查询数据:{}", result);
            return result;
        } catch (Exception e) {
            logger.error("Failed to query data from the database.");
version/db/cv.sql
@@ -11,7 +11,7 @@
 Target Server Version : 50744
 File Encoding         : 65001
 Date: 09/03/2026 13:09:19
 Date: 12/03/2026 16:50:09
*/
SET NAMES utf8mb4;
@@ -43,20 +43,11 @@
  `update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
  `memo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '设备表' ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '设备表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of cv_devp
-- ----------------------------
INSERT INTO `cv_devp` VALUES (1, 1001, NULL, 'Y', 'N', 'Y', 'N', 'Y', 'Y', 0, NULL, 1, 1, 0, NULL, NULL, '2026-03-04 10:30:55', NULL, '2026-03-05 14:57:11', NULL);
INSERT INTO `cv_devp` VALUES (2, 1002, NULL, 'Y', 'N', 'Y', 'N', 'Y', 'Y', 0, NULL, 1, 1, 0, NULL, NULL, '2026-03-04 10:30:55', NULL, '2026-03-05 14:57:11', NULL);
INSERT INTO `cv_devp` VALUES (3, 1003, NULL, 'Y', 'N', 'Y', 'N', 'Y', 'Y', 0, NULL, 1, 1, 0, NULL, NULL, '2026-03-04 10:30:55', NULL, '2026-03-05 14:57:11', NULL);
INSERT INTO `cv_devp` VALUES (4, 1004, NULL, 'Y', 'N', 'Y', 'Y', 'Y', 'Y', 0, NULL, 1, 1, 0, NULL, NULL, '2026-03-04 10:30:55', NULL, '2026-03-05 14:57:11', NULL);
INSERT INTO `cv_devp` VALUES (5, 1005, NULL, 'N', 'Y', 'Y', 'Y', 'Y', 'Y', 14, NULL, 1, 1, 0, NULL, NULL, '2026-03-04 10:30:55', NULL, '2026-03-05 14:57:11', NULL);
INSERT INTO `cv_devp` VALUES (6, 1006, NULL, 'N', 'Y', 'Y', 'Y', 'Y', 'Y', 13, NULL, 1, 1, 0, NULL, NULL, '2026-03-04 10:30:55', NULL, '2026-03-05 14:57:11', NULL);
INSERT INTO `cv_devp` VALUES (7, 1007, NULL, 'N', 'Y', 'Y', 'Y', 'Y', 'Y', 9, NULL, 1, 1, 0, NULL, NULL, '2026-03-04 10:30:55', NULL, '2026-03-05 14:57:11', NULL);
INSERT INTO `cv_devp` VALUES (8, 2, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
INSERT INTO `cv_devp` VALUES (9, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
-- ----------------------------
-- Table structure for cv_job
@@ -78,23 +69,16 @@
  `deleted` int(11) NULL DEFAULT NULL COMMENT '是否删除 1:是 0:否',
  `tenant_id` bigint(20) NULL DEFAULT NULL COMMENT '租户',
  `create_by` bigint(20) NULL DEFAULT NULL COMMENT '添加人员',
  `create_time` datetime NULL DEFAULT NULL COMMENT '添加时间',
  `create_time` datetime NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '添加时间',
  `update_by` bigint(20) NULL DEFAULT NULL COMMENT '修改人员',
  `update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
  `memo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 14 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '任务表' ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '任务表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of cv_job
-- ----------------------------
INSERT INTO `cv_job` VALUES (7, 'TK2603054400', NULL, NULL, NULL, NULL, 8, 2, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
INSERT INTO `cv_job` VALUES (8, 'TK2603054408', '00000011', 'TK2603054408', '1007', 'A102900101', 9, 7, '2026-03-05 13:55:03', '2026-03-05 13:55:01', '2026-03-05 13:55:09', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
INSERT INTO `cv_job` VALUES (9, 'TK2603054404', NULL, NULL, NULL, NULL, 10, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '模拟按按钮');
INSERT INTO `cv_job` VALUES (10, 'TK2603054405', NULL, NULL, NULL, NULL, 11, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '模拟按按钮');
INSERT INTO `cv_job` VALUES (11, 'TK2603054406', NULL, NULL, NULL, NULL, 12, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '模拟按按钮');
INSERT INTO `cv_job` VALUES (12, 'TK2603054409', '00000006', 'TK2603054409', '1007', 'A102400201', 13, 5, '2026-03-05 14:07:16', '2026-03-05 14:07:15', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
INSERT INTO `cv_job` VALUES (13, 'TK2603054410', '00000005', 'TK2603054410', '1007', 'A102500201', 14, 5, '2026-03-05 14:14:19', '2026-03-05 14:09:20', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
-- ----------------------------
-- Table structure for cv_job_log
@@ -121,17 +105,11 @@
  `update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
  `memo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '任务日志表' ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '任务日志表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of cv_job_log
-- ----------------------------
INSERT INTO `cv_job_log` VALUES (1, 'TK2603054397', '00000006', 'TK2603054397', '1007', 'A103200101', 2, 8, '2026-03-05 13:21:57', '2026-03-05 13:21:55', '2026-03-05 13:22:03', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
INSERT INTO `cv_job_log` VALUES (2, 'TK2603054398', '00000011', 'TK2603054398', '1007', 'A102900101', 3, 8, '2026-03-05 13:36:45', '2026-03-05 13:36:43', '2026-03-05 13:36:50', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
INSERT INTO `cv_job_log` VALUES (3, 'TK2603054399', '00000006', 'TK2603054399', '1007', 'A102400201', 4, 8, '2026-03-05 13:37:25', '2026-03-05 13:37:23', '2026-03-05 13:37:31', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
INSERT INTO `cv_job_log` VALUES (4, 'TK2603054401', '00000009', 'TK2603054401', '1007', 'A102500201', 5, 8, '2026-03-05 13:43:49', '2026-03-05 13:43:47', '2026-03-05 13:43:55', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
INSERT INTO `cv_job_log` VALUES (5, 'TK2603054402', '00000005', 'TK2603054402', '1007', 'A102600201', 6, 8, '2026-03-05 13:43:55', '2026-03-05 13:43:53', '2026-03-05 13:44:13', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
INSERT INTO `cv_job_log` VALUES (6, 'TK2603054403', '00000012', 'TK2603054403', '1007', 'A102700201', 7, 8, '2026-03-05 13:44:14', '2026-03-05 13:44:01', '2026-03-05 13:46:10', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
-- ----------------------------
-- Table structure for cv_work_lastno
@@ -157,6 +135,6 @@
-- ----------------------------
-- Records of cv_work_lastno
-- ----------------------------
INSERT INTO `cv_work_lastno` VALUES (1, 1, 14, 1, 8999, 1, 0, NULL, NULL, NULL, NULL, NULL, NULL);
INSERT INTO `cv_work_lastno` VALUES (1, 1, 1, 1, 8999, 1, 0, NULL, NULL, NULL, NULL, NULL, NULL);
SET FOREIGN_KEY_CHECKS = 1;
zy-acs-common/src/main/java/com/zy/acs/common/domain/mq/DeviceMessage.java
@@ -9,12 +9,14 @@
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DeviceMessage implements Serializable {
    private String sourceHexStr;
    private Long timestamp = System.currentTimeMillis();
    public DeviceMessage(String sourceHexStr) {
        this.sourceHexStr = sourceHexStr;
    }
zy-acs-cv/src/main/resources/application.yml
@@ -35,10 +35,10 @@
        mark: 10
        max-retries: 3
        retry-delay: 800
      - type: FAKEUSER
        mark: 20
        max-retries: 2
        retry-delay: 800
#      - type: FAKEUSER
#        mark: 20
#        max-retries: 2
#        retry-delay: 800
      - type: APPLYLOC
        mark: 30
        max-retries: 2
zy-acs-cv/src/main/resources/mapper/JobMapper.xml
@@ -76,7 +76,7 @@
        select *
        from cv_job
        where job_sts = #{jobSts}
          and DATEDIFF(NOW(), #{day}) > 1
          and DATEDIFF(NOW(), create_time) > #{day}
    </select>
@@ -84,6 +84,6 @@
        select *
        from cv_job
        where job_sts = #{jobSts}
          and DATEDIFF(NOW(), #{day}) > 1
          and DATEDIFF(NOW(), create_time) > #{day}
    </select>
</mapper>
zy-acs-gateway/src/main/java/com/zy/acs/gateway/controller/UtilsController.java
New file
@@ -0,0 +1,25 @@
package com.zy.acs.gateway.controller;
import com.zy.acs.framework.common.R;
import com.zy.acs.gateway.utils.PacCoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
@RestController
@Slf4j
@RequestMapping(value = "/utils")
public class UtilsController {
    /**
     * 查询最新的十条数据
     *
     * @return
     */
    @RequestMapping(value = "/decode/{hex}")
    @ResponseBody
    public R query(@PathVariable String hex) {
        return R.ok(PacCoder.decode(hex));
    }
}
zy-acs-hex/dashboard.html
@@ -19,21 +19,27 @@
            font-family: 'Inter', sans-serif;
            background-color: #f5f7fa;
        }
        .layui-card {
            margin-bottom: 16px;
        }
        .layui-card-header {
            font-weight: 600;
        }
        .status-online {
            color: #10b981;
        }
        .status-offline {
            color: #ef4444;
        }
        .chart-container {
            height: 300px;
        }
        .realtime-container {
            height: 160px;
            overflow-y: auto;
@@ -41,352 +47,422 @@
    </style>
</head>
<body>
    <div id="app">
        <!-- 顶部导航栏 -->
        <lay-header height="60px" bg-color="#fff" shadow>
            <template #left>
                <div class="flex items-center space-x-2">
                    <i class="fa fa-android text-2xl" style="color: #3b82f6"></i>
                    <h1 class="text-xl font-bold" style="color: #1e293b">机器人数据监控</h1>
                </div>
            </template>
            <template #right>
                <div class="flex items-center space-x-4">
                    <lay-input placeholder="搜索机器人..." prefix-icon="search" style="width: 200px"></lay-input>
                    <lay-button type="primary" @click="refreshData">
                        <i class="fa fa-refresh mr-1"></i> 刷新
                    </lay-button>
                </div>
            </template>
        </lay-header>
<div id="app">
    <!-- 顶部导航栏 -->
    <lay-header height="60px" bg-color="#fff" shadow>
        <template #left>
            <div class="flex items-center space-x-2">
                <i class="fa fa-android text-2xl" style="color: #3b82f6"></i>
                <h1 class="text-xl font-bold" style="color: #1e293b">机器人数据监控</h1>
            </div>
        </template>
        <template #right>
            <div class="flex items-center space-x-4">
                <lay-input placeholder="搜索机器人..." prefix-icon="search" style="width: 200px"></lay-input>
                <lay-button type="primary" @click="refreshData">
                    <i class="fa fa-refresh mr-1"></i> 刷新
                </lay-button>
            </div>
        </template>
    </lay-header>
        <!-- 主内容区 -->
        <lay-container style="padding: 20px">
            <!-- 状态概览 -->
            <lay-row :gutter="16">
                <lay-col :span="6">
                    <lay-card shadow>
                        <div class="flex items-center justify-between">
                            <div>
                                <p style="color: #64748b; font-size: 14px">总机器人数</p>
                                <h3 style="font-size: 24px; font-weight: bold; color: #1e293b">24</h3>
                            </div>
                            <div style="width: 48px; height: 48px; border-radius: 50%; background-color: #dbeafe; display: flex; align-items: center; justify-content: center">
                                <i class="fa fa-microchip text-xl" style="color: #3b82f6"></i>
                            </div>
    <!-- 主内容区 -->
    <lay-container style="padding: 20px">
        <!-- 状态概览 -->
        <lay-row :gutter="16">
            <lay-col :span="6">
                <lay-card shadow>
                    <div class="flex items-center justify-between">
                        <div>
                            <p style="color: #64748b; font-size: 14px">总机器人数</p>
                            <h3 style="font-size: 24px; font-weight: bold; color: #1e293b">24</h3>
                        </div>
                    </lay-card>
                </lay-col>
                <lay-col :span="6">
                    <lay-card shadow>
                        <div class="flex items-center justify-between">
                            <div>
                                <p style="color: #64748b; font-size: 14px">在线机器人</p>
                                <h3 style="font-size: 24px; font-weight: bold; color: #10b981">18</h3>
                            </div>
                            <div style="width: 48px; height: 48px; border-radius: 50%; background-color: #d1fae5; display: flex; align-items: center; justify-content: center">
                                <i class="fa fa-check-circle text-xl" style="color: #10b981"></i>
                            </div>
                        </div>
                    </lay-card>
                </lay-col>
                <lay-col :span="6">
                    <lay-card shadow>
                        <div class="flex items-center justify-between">
                            <div>
                                <p style="color: #64748b; font-size: 14px">离线机器人</p>
                                <h3 style="font-size: 24px; font-weight: bold; color: #ef4444">6</h3>
                            </div>
                            <div style="width: 48px; height: 48px; border-radius: 50%; background-color: #fee2e2; display: flex; align-items: center; justify-content: center">
                                <i class="fa fa-exclamation-circle text-xl" style="color: #ef4444"></i>
                            </div>
                        </div>
                    </lay-card>
                </lay-col>
                <lay-col :span="6">
                    <lay-card shadow>
                        <div class="flex items-center justify-between">
                            <div>
                                <p style="color: #64748b; font-size: 14px">今日数据量</p>
                                <h3 style="font-size: 24px; font-weight: bold; color: #f59e0b">1.2k</h3>
                            </div>
                            <div style="width: 48px; height: 48px; border-radius: 50%; background-color: #fef3c7; display: flex; align-items: center; justify-content: center">
                                <i class="fa fa-database text-xl" style="color: #f59e0b"></i>
                            </div>
                        </div>
                    </lay-card>
                </lay-col>
            </lay-row>
            <!-- 数据图表 -->
            <lay-row :gutter="16" style="margin-top: 16px">
                <lay-col :span="12">
                    <lay-card shadow>
                        <template #header>
                            <div class="flex justify-between items-center">
                                <h2 style="font-size: 16px; font-weight: 600; color: #1e293b">上行数据趋势</h2>
                                <div class="flex space-x-2">
                                    <lay-button size="sm" type="primary">小时</lay-button>
                                    <lay-button size="sm">天</lay-button>
                                    <lay-button size="sm">周</lay-button>
                                </div>
                            </div>
                        </template>
                        <div class="chart-container">
                            <canvas ref="upDataChart"></canvas>
                        </div>
                    </lay-card>
                </lay-col>
                <lay-col :span="12">
                    <lay-card shadow>
                        <template #header>
                            <div class="flex justify-between items-center">
                                <h2 style="font-size: 16px; font-weight: 600; color: #1e293b">下行数据趋势</h2>
                                <div class="flex space-x-2">
                                    <lay-button size="sm" type="primary">小时</lay-button>
                                    <lay-button size="sm">天</lay-button>
                                    <lay-button size="sm">周</lay-button>
                                </div>
                            </div>
                        </template>
                        <div class="chart-container">
                            <canvas ref="downDataChart"></canvas>
                        </div>
                    </lay-card>
                </lay-col>
            </lay-row>
            <!-- 设备数据表格 -->
            <lay-card shadow style="margin-top: 16px">
                <template #header>
                    <div class="flex justify-between items-center">
                        <h2 style="font-size: 16px; font-weight: 600; color: #1e293b">机器人数据列表</h2>
                        <div class="flex space-x-2">
                            <lay-button size="sm">
                                <i class="fa fa-filter mr-1"></i> 筛选
                            </lay-button>
                            <lay-button size="sm">
                                <i class="fa fa-download mr-1"></i> 导出
                            </lay-button>
                        <div style="width: 48px; height: 48px; border-radius: 50%; background-color: #dbeafe; display: flex; align-items: center; justify-content: center">
                            <i class="fa fa-microchip text-xl" style="color: #3b82f6"></i>
                        </div>
                    </div>
                </template>
                <lay-table :data="devices" :height="400">
                    <lay-table-column prop="id" label="设备ID" width="120"></lay-table-column>
                    <lay-table-column prop="name" label="设备名称" width="150"></lay-table-column>
                    <lay-table-column prop="status" label="状态" width="100">
                        <template #default="{ row }">
                            <lay-badge v-if="row.status === 'online'" type="success">在线</lay-badge>
                            <lay-badge v-else type="danger">离线</lay-badge>
                        </template>
                    </lay-table-column>
                    <lay-table-column prop="upData" label="上行数据" width="120"></lay-table-column>
                    <lay-table-column prop="downData" label="下行数据" width="120"></lay-table-column>
                    <lay-table-column prop="lastComm" label="最后通信" width="150"></lay-table-column>
                    <lay-table-column label="操作" width="150">
                        <template #default="{ row }">
                            <lay-button size="sm" type="primary" style="margin-right: 8px">
                                <i class="fa fa-eye"></i>
                            </lay-button>
                            <lay-button size="sm" type="warning" style="margin-right: 8px">
                                <i class="fa fa-edit"></i>
                            </lay-button>
                            <lay-button size="sm" type="danger">
                                <i class="fa fa-trash"></i>
                            </lay-button>
                        </template>
                    </lay-table-column>
                </lay-table>
                <div class="flex justify-between items-center mt-4">
                    <p style="color: #64748b; font-size: 14px">显示 1-10 条,共 24 条</p>
                    <lay-pagination
                </lay-card>
            </lay-col>
            <lay-col :span="6">
                <lay-card shadow>
                    <div class="flex items-center justify-between">
                        <div>
                            <p style="color: #64748b; font-size: 14px">在线机器人</p>
                            <h3 style="font-size: 24px; font-weight: bold; color: #10b981">18</h3>
                        </div>
                        <div style="width: 48px; height: 48px; border-radius: 50%; background-color: #d1fae5; display: flex; align-items: center; justify-content: center">
                            <i class="fa fa-check-circle text-xl" style="color: #10b981"></i>
                        </div>
                    </div>
                </lay-card>
            </lay-col>
            <lay-col :span="6">
                <lay-card shadow>
                    <div class="flex items-center justify-between">
                        <div>
                            <p style="color: #64748b; font-size: 14px">离线机器人</p>
                            <h3 style="font-size: 24px; font-weight: bold; color: #ef4444">6</h3>
                        </div>
                        <div style="width: 48px; height: 48px; border-radius: 50%; background-color: #fee2e2; display: flex; align-items: center; justify-content: center">
                            <i class="fa fa-exclamation-circle text-xl" style="color: #ef4444"></i>
                        </div>
                    </div>
                </lay-card>
            </lay-col>
            <lay-col :span="6">
                <lay-card shadow>
                    <div class="flex items-center justify-between">
                        <div>
                            <p style="color: #64748b; font-size: 14px">今日数据量</p>
                            <h3 style="font-size: 24px; font-weight: bold; color: #f59e0b">1.2k</h3>
                        </div>
                        <div style="width: 48px; height: 48px; border-radius: 50%; background-color: #fef3c7; display: flex; align-items: center; justify-content: center">
                            <i class="fa fa-database text-xl" style="color: #f59e0b"></i>
                        </div>
                    </div>
                </lay-card>
            </lay-col>
        </lay-row>
        <!-- 数据图表 -->
        <lay-row :gutter="16" style="margin-top: 16px">
            <lay-col :span="12">
                <lay-card shadow>
                    <template #header>
                        <div class="flex justify-between items-center">
                            <h2 style="font-size: 16px; font-weight: 600; color: #1e293b">上行数据趋势</h2>
                            <div class="flex space-x-2">
                                <lay-button size="sm" type="primary">小时</lay-button>
                                <lay-button size="sm">天</lay-button>
                                <lay-button size="sm">周</lay-button>
                            </div>
                        </div>
                    </template>
                    <div class="chart-container">
                        <canvas ref="upDataChart"></canvas>
                    </div>
                </lay-card>
            </lay-col>
            <lay-col :span="12">
                <lay-card shadow>
                    <template #header>
                        <div class="flex justify-between items-center">
                            <h2 style="font-size: 16px; font-weight: 600; color: #1e293b">下行数据趋势</h2>
                            <div class="flex space-x-2">
                                <lay-button size="sm" type="primary">小时</lay-button>
                                <lay-button size="sm">天</lay-button>
                                <lay-button size="sm">周</lay-button>
                            </div>
                        </div>
                    </template>
                    <div class="chart-container">
                        <canvas ref="downDataChart"></canvas>
                    </div>
                </lay-card>
            </lay-col>
        </lay-row>
        <!-- 设备数据表格 -->
        <lay-card shadow style="margin-top: 16px">
            <template #header>
                <div class="flex justify-between items-center">
                    <h2 style="font-size: 16px; font-weight: 600; color: #1e293b">机器人数据列表</h2>
                    <div class="flex space-x-2">
                        <lay-button size="sm">
                            <i class="fa fa-filter mr-1"></i> 筛选
                        </lay-button>
                        <lay-button size="sm">
                            <i class="fa fa-download mr-1"></i> 导出
                        </lay-button>
                    </div>
                </div>
            </template>
            <lay-table :data="devices" :height="400">
                <lay-table-column prop="id" label="设备ID" width="120"></lay-table-column>
                <lay-table-column prop="name" label="设备名称" width="150"></lay-table-column>
                <lay-table-column prop="status" label="状态" width="100">
                    <template #default="{ row }">
                        <lay-badge v-if="row.status === 'online'" type="success">在线</lay-badge>
                        <lay-badge v-else type="danger">离线</lay-badge>
                    </template>
                </lay-table-column>
                <lay-table-column prop="upData" label="上行数据" width="120"></lay-table-column>
                <lay-table-column prop="downData" label="下行数据" width="120"></lay-table-column>
                <lay-table-column prop="lastComm" label="最后通信" width="150"></lay-table-column>
                <lay-table-column label="操作" width="150">
                    <template #default="{ row }">
                        <lay-button size="sm" type="primary" style="margin-right: 8px">
                            <i class="fa fa-eye"></i>
                        </lay-button>
                        <lay-button size="sm" type="warning" style="margin-right: 8px">
                            <i class="fa fa-edit"></i>
                        </lay-button>
                        <lay-button size="sm" type="danger">
                            <i class="fa fa-trash"></i>
                        </lay-button>
                    </template>
                </lay-table-column>
            </lay-table>
            <div class="flex justify-between items-center mt-4">
                <p style="color: #64748b; font-size: 14px">显示 1-10 条,共 24 条</p>
                <lay-pagination
                        v-model:current="currentPage"
                        v-model:limit="pageSize"
                        :total="total"
                        :limits="[10, 20, 50, 100]"
                        layout="prev, pager, next, jumper, sizes, total"
                    ></lay-pagination>
                </div>
            </lay-card>
            <!-- 实时数据更新 -->
            <lay-card shadow style="margin-top: 16px">
                <template #header>
                    <h2 style="font-size: 16px; font-weight: 600; color: #1e293b">实时数据更新</h2>
                </template>
                <div class="realtime-container p-2 border border-gray-200 rounded-lg">
                    <div v-for="(item, index) in realtimeData" :key="index" class="py-1 border-b border-gray-100">
                        <span style="color: #64748b; font-size: 12px">{{ item.timestamp }}</span>
                        <span style="color: #1e293b; margin-left: 10px">{{ item.message }}</span>
                    </div>
                </div>
            </lay-card>
        </lay-container>
        <!-- 页脚 -->
        <lay-footer height="60px" bg-color="#fff" shadow>
            <div class="text-center" style="color: #64748b; font-size: 14px">
                © 2026 机器人数据监控系统 | 版本 1.0.0
                ></lay-pagination>
            </div>
        </lay-footer>
    </div>
        </lay-card>
    <script>
        const { createApp, ref, onMounted } = Vue;
        const app = createApp({
            components: {
                LayHeader: layui.LayHeader,
                LayContainer: layui.LayContainer,
                LayRow: layui.LayRow,
                LayCol: layui.LayCol,
                LayCard: layui.LayCard,
                LayInput: layui.LayInput,
                LayButton: layui.LayButton,
                LayTable: layui.LayTable,
                LayTableColumn: layui.LayTableColumn,
                LayBadge: layui.LayBadge,
                LayPagination: layui.LayPagination,
                LayFooter: layui.LayFooter
            },
            setup() {
                // 模拟机器人数据
                const devices = ref([
                    { id: 'ROB-001', name: '配送机器人1号', status: 'online', upData: '2.4KB', downData: '0.8KB', lastComm: '2分钟前' },
                    { id: 'ROB-002', name: '配送机器人2号', status: 'online', upData: '1.8KB', downData: '0.5KB', lastComm: '5分钟前' },
                    { id: 'ROB-003', name: '巡检机器人1号', status: 'offline', upData: '0KB', downData: '0KB', lastComm: '2小时前' },
                    { id: 'ROB-004', name: '配送机器人3号', status: 'online', upData: '3.2KB', downData: '1.2KB', lastComm: '1分钟前' },
                    { id: 'ROB-005', name: '巡检机器人2号', status: 'online', upData: '1.5KB', downData: '0.6KB', lastComm: '3分钟前' },
                    { id: 'ROB-006', name: '配送机器人4号', status: 'offline', upData: '0KB', downData: '0KB', lastComm: '5小时前' },
                    { id: 'ROB-007', name: '巡检机器人3号', status: 'online', upData: '2.1KB', downData: '0.9KB', lastComm: '4分钟前' },
                    { id: 'ROB-008', name: '配送机器人5号', status: 'online', upData: '2.8KB', downData: '1.1KB', lastComm: '2分钟前' },
                    { id: 'ROB-009', name: '巡检机器人4号', status: 'offline', upData: '0KB', downData: '0KB', lastComm: '1天前' },
                    { id: 'ROB-010', name: '配送机器人6号', status: 'online', upData: '1.9KB', downData: '0.7KB', lastComm: '6分钟前' }
                ]);
        <!-- 实时数据更新 -->
        <lay-card shadow style="margin-top: 16px">
            <template #header>
                <h2 style="font-size: 16px; font-weight: 600; color: #1e293b">实时数据更新</h2>
            </template>
            <div class="realtime-container p-2 border border-gray-200 rounded-lg">
                <div v-for="(item, index) in realtimeData" :key="index" class="py-1 border-b border-gray-100">
                    <span style="color: #64748b; font-size: 12px">{{ item.timestamp }}</span>
                    <span style="color: #1e293b; margin-left: 10px">{{ item.message }}</span>
                </div>
            </div>
        </lay-card>
    </lay-container>
                // 分页数据
                const currentPage = ref(1);
                const pageSize = ref(10);
                const total = ref(24);
    <!-- 页脚 -->
    <lay-footer height="60px" bg-color="#fff" shadow>
        <div class="text-center" style="color: #64748b; font-size: 14px">
            © 2026 机器人数据监控系统 | 版本 1.0.0
        </div>
    </lay-footer>
</div>
                // 实时数据
                const realtimeData = ref([]);
<script>
    const {createApp, ref, onMounted} = Vue;
    const app = createApp({
        components: {
            LayHeader: layui.LayHeader,
            LayContainer: layui.LayContainer,
            LayRow: layui.LayRow,
            LayCol: layui.LayCol,
            LayCard: layui.LayCard,
            LayInput: layui.LayInput,
            LayButton: layui.LayButton,
            LayTable: layui.LayTable,
            LayTableColumn: layui.LayTableColumn,
            LayBadge: layui.LayBadge,
            LayPagination: layui.LayPagination,
            LayFooter: layui.LayFooter
        },
        setup() {
            // 模拟机器人数据
            const devices = ref([
                {
                    id: 'ROB-001',
                    name: '配送机器人1号',
                    status: 'online',
                    upData: '2.4KB',
                    downData: '0.8KB',
                    lastComm: '2分钟前'
                },
                {
                    id: 'ROB-002',
                    name: '配送机器人2号',
                    status: 'online',
                    upData: '1.8KB',
                    downData: '0.5KB',
                    lastComm: '5分钟前'
                },
                {
                    id: 'ROB-003',
                    name: '巡检机器人1号',
                    status: 'offline',
                    upData: '0KB',
                    downData: '0KB',
                    lastComm: '2小时前'
                },
                {
                    id: 'ROB-004',
                    name: '配送机器人3号',
                    status: 'online',
                    upData: '3.2KB',
                    downData: '1.2KB',
                    lastComm: '1分钟前'
                },
                {
                    id: 'ROB-005',
                    name: '巡检机器人2号',
                    status: 'online',
                    upData: '1.5KB',
                    downData: '0.6KB',
                    lastComm: '3分钟前'
                },
                {
                    id: 'ROB-006',
                    name: '配送机器人4号',
                    status: 'offline',
                    upData: '0KB',
                    downData: '0KB',
                    lastComm: '5小时前'
                },
                {
                    id: 'ROB-007',
                    name: '巡检机器人3号',
                    status: 'online',
                    upData: '2.1KB',
                    downData: '0.9KB',
                    lastComm: '4分钟前'
                },
                {
                    id: 'ROB-008',
                    name: '配送机器人5号',
                    status: 'online',
                    upData: '2.8KB',
                    downData: '1.1KB',
                    lastComm: '2分钟前'
                },
                {
                    id: 'ROB-009',
                    name: '巡检机器人4号',
                    status: 'offline',
                    upData: '0KB',
                    downData: '0KB',
                    lastComm: '1天前'
                },
                {
                    id: 'ROB-010',
                    name: '配送机器人6号',
                    status: 'online',
                    upData: '1.9KB',
                    downData: '0.7KB',
                    lastComm: '6分钟前'
                }
            ]);
                // 图表引用
                const upDataChart = ref(null);
                const downDataChart = ref(null);
            // 分页数据
            const currentPage = ref(1);
            const pageSize = ref(10);
            const total = ref(24);
                // 刷新数据
                const refreshData = () => {
                    console.log('刷新数据');
                    // 这里可以添加实际的刷新逻辑
                };
            // 实时数据
            const realtimeData = ref([]);
                // 初始化图表
                const initCharts = () => {
                    // 上行数据图表
                    const upCtx = upDataChart.value.getContext('2d');
                    new Chart(upCtx, {
                        type: 'line',
                        data: {
                            labels: ['00:00', '03:00', '06:00', '09:00', '12:00', '15:00', '18:00', '21:00'],
                            datasets: [{
                                label: '上行数据 (KB)',
                                data: [12, 19, 15, 25, 22, 30, 28, 35],
                                borderColor: '#3b82f6',
                                backgroundColor: 'rgba(59, 130, 246, 0.1)',
                                tension: 0.4,
                                fill: true
                            }]
            // 图表引用
            const upDataChart = ref(null);
            const downDataChart = ref(null);
            // 刷新数据
            const refreshData = () => {
                console.log('刷新数据');
                // 这里可以添加实际的刷新逻辑
            };
            // 初始化图表
            const initCharts = () => {
                // 上行数据图表
                const upCtx = upDataChart.value.getContext('2d');
                new Chart(upCtx, {
                    type: 'line',
                    data: {
                        labels: ['00:00', '03:00', '06:00', '09:00', '12:00', '15:00', '18:00', '21:00'],
                        datasets: [{
                            label: '上行数据 (KB)',
                            data: [12, 19, 15, 25, 22, 30, 28, 35],
                            borderColor: '#3b82f6',
                            backgroundColor: 'rgba(59, 130, 246, 0.1)',
                            tension: 0.4,
                            fill: true
                        }]
                    },
                    options: {
                        responsive: true,
                        maintainAspectRatio: false,
                        plugins: {
                            legend: {
                                display: false
                            }
                        },
                        options: {
                            responsive: true,
                            maintainAspectRatio: false,
                            plugins: {
                                legend: {
                                    display: false
                                }
                            },
                            scales: {
                                y: {
                                    beginAtZero: true
                                }
                        scales: {
                            y: {
                                beginAtZero: true
                            }
                        }
                    });
                    // 下行数据图表
                    const downCtx = downDataChart.value.getContext('2d');
                    new Chart(downCtx, {
                        type: 'line',
                        data: {
                            labels: ['00:00', '03:00', '06:00', '09:00', '12:00', '15:00', '18:00', '21:00'],
                            datasets: [{
                                label: '下行数据 (KB)',
                                data: [5, 8, 6, 12, 10, 15, 13, 18],
                                borderColor: '#10b981',
                                backgroundColor: 'rgba(16, 185, 129, 0.1)',
                                tension: 0.4,
                                fill: true
                            }]
                        },
                        options: {
                            responsive: true,
                            maintainAspectRatio: false,
                            plugins: {
                                legend: {
                                    display: false
                                }
                            },
                            scales: {
                                y: {
                                    beginAtZero: true
                                }
                            }
                        }
                    });
                };
                // 模拟实时数据更新
                const simulateRealtimeData = () => {
                    const messages = [
                        'ROB-001 配送机器人1号: 运行中,位置: A1区',
                        'ROB-002 配送机器人2号: 待机中,位置: B2区',
                        'ROB-004 配送机器人3号: 充电中,电量: 85%',
                        'ROB-005 巡检机器人2号: 巡检中,已完成3/5任务',
                        'ROB-007 巡检机器人3号: 待机中,位置: C3区',
                        'ROB-008 配送机器人5号: 运行中,位置: D4区'
                    ];
                    setInterval(() => {
                        const message = messages[Math.floor(Math.random() * messages.length)];
                        const timestamp = new Date().toLocaleTimeString();
                        realtimeData.value.unshift({ timestamp, message });
                        // 限制显示条数
                        if (realtimeData.value.length > 20) {
                            realtimeData.value.pop();
                        }
                    }, 2000);
                };
                // 页面加载完成后初始化
                onMounted(() => {
                    initCharts();
                    simulateRealtimeData();
                    }
                });
                return {
                    devices,
                    currentPage,
                    pageSize,
                    total,
                    realtimeData,
                    upDataChart,
                    downDataChart,
                    refreshData
                };
            }
        });
        app.mount('#app');
    </script>
                // 下行数据图表
                const downCtx = downDataChart.value.getContext('2d');
                new Chart(downCtx, {
                    type: 'line',
                    data: {
                        labels: ['00:00', '03:00', '06:00', '09:00', '12:00', '15:00', '18:00', '21:00'],
                        datasets: [{
                            label: '下行数据 (KB)',
                            data: [5, 8, 6, 12, 10, 15, 13, 18],
                            borderColor: '#10b981',
                            backgroundColor: 'rgba(16, 185, 129, 0.1)',
                            tension: 0.4,
                            fill: true
                        }]
                    },
                    options: {
                        responsive: true,
                        maintainAspectRatio: false,
                        plugins: {
                            legend: {
                                display: false
                            }
                        },
                        scales: {
                            y: {
                                beginAtZero: true
                            }
                        }
                    }
                });
            };
            // 模拟实时数据更新
            const simulateRealtimeData = () => {
                const messages = [
                    'ROB-001 配送机器人1号: 运行中,位置: A1区',
                    'ROB-002 配送机器人2号: 待机中,位置: B2区',
                    'ROB-004 配送机器人3号: 充电中,电量: 85%',
                    'ROB-005 巡检机器人2号: 巡检中,已完成3/5任务',
                    'ROB-007 巡检机器人3号: 待机中,位置: C3区',
                    'ROB-008 配送机器人5号: 运行中,位置: D4区'
                ];
                setInterval(() => {
                    const message = messages[Math.floor(Math.random() * messages.length)];
                    const timestamp = new Date().toLocaleTimeString();
                    realtimeData.value.unshift({timestamp, message});
                    // 限制显示条数
                    if (realtimeData.value.length > 20) {
                        realtimeData.value.pop();
                    }
                }, 2000);
            };
            // 页面加载完成后初始化
            onMounted(() => {
                initCharts();
                simulateRealtimeData();
            });
            return {
                devices,
                currentPage,
                pageSize,
                total,
                realtimeData,
                upDataChart,
                downDataChart,
                refreshData
            };
        }
    });
    app.mount('#app');
</script>
</body>
</html>
zy-acs-hex/src/main/java/com/zy/acs/hex/config/WebMvcConfig.java
@@ -3,7 +3,6 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@@ -36,28 +35,28 @@
    @Bean
    public AsyncHandlerInterceptor getAsyncHandlerInterceptor() {
        return new AsyncHandlerInterceptor(){
        return new AsyncHandlerInterceptor() {
            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
               cors(response);
                cors(response);
                return true;
            }
        };
    }
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 配置静态资源处理器
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/static/")
                .addResourceLocations("/static/");
        // 配置视图文件处理器
        registry.addResourceHandler("/views/**")
                .addResourceLocations("/views/");
    }
    public static void cors(HttpServletResponse response){
    public static void cors(HttpServletResponse response) {
        // 跨域设置
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Origin", "*");
zy-acs-hex/src/main/java/com/zy/acs/hex/controller/DeviceLogController.java
@@ -1,16 +1,20 @@
package com.zy.acs.hex.controller;
import com.zy.acs.common.domain.mq.DeviceMessage;
import com.influxdb.v3.client.PointValues;
import com.zy.acs.framework.common.R;
import com.zy.acs.hex.domain.DeviceLog;
import com.zy.component.influxdb.service.InfluxDBService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
@RestController
@Slf4j
@@ -28,10 +32,49 @@
     */
    @GetMapping(value = "/query")
    @ResponseBody
    public R query() {
        List<DeviceMessage> deviceMessages = influxDBService.queryPoints("select * from device order by time desc limit 10", DeviceMessage.class);
        return R.ok(deviceMessages);
    public R query(@RequestParam(required = false, defaultValue = "device") String measurement,
                   @RequestParam(required = false) Map<String, Object> conditions,
                   @RequestParam(required = false, defaultValue = "100") Integer limit,
                   @RequestParam(required = false, defaultValue = "time") String orderBy,
                   @RequestParam(required = false, defaultValue = "DESC") String orderDirection) {
        return R.ok(getData(measurement, conditions, limit, orderBy, orderDirection));
    }
    /**
     * 通用查询方法,支持动态条件
     */
    private List<DeviceLog> getData(String measurement, Map<String, Object> conditions,int limit, String orderBy, String orderDirection) {
        // 构建查询语句
        StringBuilder sqlBuilder = new StringBuilder("SELECT * FROM ").append(measurement).append(" WHERE 1=1");
        Map<String, Object> params = new HashMap<>();
        // 动态添加条件
        if (conditions != null && !conditions.isEmpty()) {
            if (conditions.get("startTime") != null) {
                if (conditions.get("startTime") != null) {
                    sqlBuilder.append(" AND ").append("time").append(" >= :").append("startTime");
                    params.put("startTime", conditions.get("startTime"));
                }
            }else if (conditions.get("endTime") != null) {
                if (conditions.get("endTime") != null) {
                    sqlBuilder.append(" AND ").append("time").append(" <= :").append("endTime");
                    params.put("endTime", conditions.get("endTime"));
                }
            }else {
                conditions.forEach((key, value) -> {
                    if (value != null) {
                        sqlBuilder.append(" AND ").append(key).append(" = :").append(key);
                        params.put(key, value);
                    }
                });
            }
        }
        // 添加排序和限制
        sqlBuilder.append(" ORDER BY ").append(orderBy).append(" ").append(orderDirection);
        sqlBuilder.append(" LIMIT :limit");
        params.put("limit", limit);
        return influxDBService.queryPoints(sqlBuilder.toString(),params, DeviceLog.class);
    }
}
zy-acs-hex/src/main/java/com/zy/acs/hex/controller/LoginController.java
New file
@@ -0,0 +1,38 @@
package com.zy.acs.hex.controller;
import com.zy.acs.framework.common.R;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
 * 登录控制器
 */
@RestController
@RequestMapping(value = "/login")
public class LoginController {
    @Value("${login.username}")
    private String username;
    @Value("${login.password}")
    private String password;
    /**
     * 登录接口
     *
     * @param user 用户名
     * @param pass 密码
     * @return 登录结果
     */
    @PostMapping(value = "/auth")
    public R login(@RequestParam String user, @RequestParam String pass) {
        if (username.equals(user) && password.equals(pass)) {
            return R.ok("登录成功");
        } else {
            return R.error("用户名或密码错误");
        }
    }
}
zy-acs-hex/src/main/java/com/zy/acs/hex/controller/ProxyController.java
New file
@@ -0,0 +1,33 @@
package com.zy.acs.hex.controller;
import com.zy.acs.framework.common.R;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping(value = "/proxy")
public class ProxyController {
    private final RestTemplate restTemplate = new RestTemplate();
    @GetMapping(value = "/decode")
    public R decode(@RequestParam String hexData) {
        try {
            String url = "http://127.0.0.1:9060/utils/decode/" + hexData;
            HttpHeaders headers = new HttpHeaders();
            headers.set("Content-Type", "application/json");
            HttpEntity<String> entity = new HttpEntity<>(headers);
            ResponseEntity<Object> response = restTemplate.exchange(url, HttpMethod.GET, entity, Object.class);
            return R.ok(response.getBody());
        } catch (Exception e) {
            return R.error("解析失败: " + e.getMessage());
        }
    }
}
zy-acs-hex/src/main/java/com/zy/acs/hex/controller/RouterController.java
@@ -1,6 +1,5 @@
package com.zy.acs.hex.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -13,12 +12,10 @@
public class RouterController {
    @RequestMapping("/")
    public void index(HttpServletResponse response) {
        try {
            response.sendRedirect(  "/views/index.html");
            response.sendRedirect("/views/index.html");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
@@ -27,7 +24,7 @@
    @RequestMapping("/login")
    public void login(HttpServletResponse response) {
        try {
            response.sendRedirect(  "/views/login.html");
            response.sendRedirect("/views/login.html");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
zy-acs-hex/src/main/java/com/zy/acs/hex/controller/SelectTypeController.java
New file
@@ -0,0 +1,58 @@
package com.zy.acs.hex.controller;
import com.zy.acs.framework.common.R;
import com.zy.acs.hex.domain.SelectOption;
import com.zy.acs.hex.enums.DirectionType;
import com.zy.acs.hex.enums.ProtocolType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
@RestController
@Slf4j
@RequestMapping(value = "/deviceLog")
public class SelectTypeController {
    /**
     * 查询消息类型
     *
     * @return
     */
    @GetMapping(value = "/queryType")
    @ResponseBody
    public R queryType() {
        DirectionType[] values = DirectionType.values();
        List<SelectOption> messageTypes = new ArrayList<>();
        for (DirectionType value : values) {
            messageTypes.add(new SelectOption(value.getText(), value.name().toLowerCase()));
        }
        return R.ok(messageTypes);
    }
    /**
     * 查询标签类型
     *
     * @return
     */
    @GetMapping(value = "/queryEvent")
    @ResponseBody
    public R queryEvent(@RequestParam(required = false) DirectionType directionType) {
        List<SelectOption> messageTypes = new ArrayList<>();
        if (directionType == null) {
            ProtocolType[] values = ProtocolType.values();
            for (ProtocolType value : values) {
                messageTypes.add(new SelectOption(value.name(), value.getDirection().getText() + "-" + value.getDes() + value.name()));
            }
            return R.ok(messageTypes);
        }
        List<ProtocolType> protocolTypes = ProtocolType.listByDirectionType(directionType);
        for (ProtocolType value : protocolTypes) {
            messageTypes.add(new SelectOption(value.name(), value.getDirection().getText() + "-" + value.getDes() + value.name()));
        }
        return R.ok(messageTypes);
    }
}
zy-acs-hex/src/main/java/com/zy/acs/hex/controller/TestController.java
@@ -2,6 +2,7 @@
import com.zy.acs.common.domain.mq.DeviceMessage;
import com.zy.acs.hex.constant.RabbitConstant;
import com.zy.acs.hex.domain.DeviceLog;
import com.zy.component.influxdb.service.InfluxDBService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
@@ -68,7 +69,7 @@
    @GetMapping(value = "/query2")
    @ResponseBody
    public Object queryTest2() {
        return influxDBService.queryPoints("select * from device order by time desc limit 10", DeviceMessage.class);
        return influxDBService.queryPoints("select * from device order by time desc limit 10", DeviceLog.class);
    }
}
zy-acs-hex/src/main/java/com/zy/acs/hex/domain/DeviceLog.java
New file
@@ -0,0 +1,18 @@
package com.zy.acs.hex.domain;
import com.zy.component.influxdb.domain.BaseMessage;
import lombok.Data;
import java.io.Serializable;
@Data
public class DeviceLog extends BaseMessage implements Serializable {
    private String deviceId;
    private String event;
    private String type;
    private String sourceHexStr;
}
zy-acs-hex/src/main/java/com/zy/acs/hex/domain/GenericQuery.java
New file
@@ -0,0 +1,16 @@
package com.zy.acs.hex.domain;
import lombok.Data;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
@Data
public class GenericQuery implements Serializable {
        private Map<String, Object> conditions = new HashMap<>(); // 动态查询条件
        private int limit = 100; // 默认返回100条
        private String orderBy = "time"; // 默认按时间排序
        private String orderDirection = "DESC"; // 默认降序
}
zy-acs-hex/src/main/java/com/zy/acs/hex/domain/SelectOption.java
New file
@@ -0,0 +1,19 @@
package com.zy.acs.hex.domain;
import lombok.Data;
import java.io.Serializable;
@Data
public class SelectOption implements Serializable {
    private String value;
    private String label;
    public SelectOption() {
    }
    public SelectOption(String value, String label) {
        this.value = value;
        this.label = label;
    }
}
zy-acs-hex/src/main/java/com/zy/acs/hex/enums/DirectionType.java
New file
@@ -0,0 +1,22 @@
package com.zy.acs.hex.enums;
public enum DirectionType {
    DOWN("下行"),
    UP("上行"),
    ;
    private String text;
    DirectionType(String text) {
        this.text = text;
    }
    public String getText() {
        return text;
    }
    public void setText(String text) {
        this.text = text;
    }
}
zy-acs-hex/src/main/java/com/zy/acs/hex/enums/ProtocolType.java
New file
@@ -0,0 +1,101 @@
package com.zy.acs.hex.enums;
import java.util.ArrayList;
import java.util.List;
/**
 * 标识枚举
 * Created by vincent on 2019-04-02
 */
public enum ProtocolType {
    // 下行 -------------------------------------------------------------------
    PATH_COMMAND(0x01, "路径数据包", DirectionType.DOWN),
    PICK_PLACE_ACK(0x06, "取放货应答包", DirectionType.DOWN),
    ACTION_COMMAND(0x02, "动作命令包", DirectionType.DOWN),
    HEARTBEAT_COMMAND(0x03, "心跳包", DirectionType.DOWN),
    FAULT_CLEAR_COMMAND(0x04, "故障命令包", DirectionType.DOWN),
    ACTIVATION_COMMAND(0x80, "激活包", DirectionType.DOWN),
    LOGIN_ACK(0xF0, "登录应答包", DirectionType.DOWN),
    ACTION_SUCCESS_ACK(0xA1, "动作完成成功应答", DirectionType.DOWN),
    ACTION_FAIL_ACK(0xA0, "动作完成失败应答", DirectionType.DOWN),
    PATH_ACK_RESPONSE(0xB1, "路径数据包回复ack", DirectionType.DOWN),
    // 上行 -------------------------------------------------------------------
    PATH_ACK(0x01, "路径应答包", DirectionType.UP),
    PICK_PLACE_REQUEST(0x06, "取放货请求包", DirectionType.UP),
    COMMAND_ACK(0x02, "命令应答包", DirectionType.UP),
    ACTION_COMPLETE(0x11, "动作完成包", DirectionType.UP),
    DATA_CODE_REPORT(0x12, "有码实时数据包", DirectionType.UP),
    DATA_WITHOUT_CODE_REPORT(0x13, "无码实时数据包", DirectionType.UP),
    HEARTBEAT_REPORT(0x03, "心跳包", DirectionType.UP),
    FAULT_REPORT(0x04, "故障数据包", DirectionType.UP),
    HANDLE_FALUT_ACK(0x14, "故障清除应答包", DirectionType.UP),
    SILO_REPORT(0x70, "料仓信息包", DirectionType.UP),
    LOGIN_REPORT(0xF0, "机器人登陆数据包", DirectionType.UP),
    ;
    private final int code;   // 编码
    private final String des; // 描述
    private final DirectionType direction;
    ProtocolType(int code, String des, DirectionType direction) {
        this.code = code;
        this.des = des;
        this.direction = direction;
    }
    public int getCode() {
        return code;
    }
    public String getDes() {
        return des;
    }
    public DirectionType getDirection() {
        return direction;
    }
    public static List<ProtocolType> listByDirectionType(DirectionType direction) {
        List<ProtocolType> protocolTypes = new ArrayList<ProtocolType>();
        for (ProtocolType type : ProtocolType.values()) {
            if (type.getDirection() == direction) {
                protocolTypes.add(type);
            }
        }
        return protocolTypes;
    }
    public static ProtocolType getByCode(int code, DirectionType direction) {
        for (ProtocolType type : ProtocolType.values()) {
            if (type.getCode() == code && type.getDirection() == direction) {
                return type;
            }
        }
        return null;
    }
}
zy-acs-hex/src/main/java/com/zy/acs/hex/influxdb/task/InfluxDbScheduler.java
@@ -61,7 +61,7 @@
                parames.put("retention-period", retentionPeriod);
                HttpGo.HttpResponse postResponse = this.http.postJson(createDatabaseUrl, headers, JSON.toJSONString(parames));
                log.info("是否创建数据库:{}", postResponse);
            }else {
            } else {
                log.info("数据库:{}", response.body());
            }
        } catch (IOException e) {
zy-acs-hex/src/main/java/com/zy/acs/hex/utils/HttpGo.java
@@ -18,7 +18,7 @@
/**
 * Minimal OkHttp wrapper: GET / POST only.
 *
 * <p>
 * - fluent API (get / postJson / postForm)
 * - default singleton instance (thread-safe)
 * - per-request headers + default headers
@@ -41,7 +41,9 @@
                : Collections.unmodifiableMap(new LinkedHashMap<>(defaultHeaders));
    }
    /** Shared default instance (safe SSL by default). */
    /**
     * Shared default instance (safe SSL by default).
     */
    public static HttpGo defaults() {
        return Holder.DEFAULT;
    }
@@ -78,12 +80,16 @@
    // ===================== POST =====================
    /** POST JSON string payload (null/blank -> "{}"). */
    /**
     * POST JSON string payload (null/blank -> "{}").
     */
    public HttpResponse postJson(String url, String json) throws IOException {
        return postJson(url, null, json);
    }
    /** POST JSON string payload (null/blank -> "{}"). */
    /**
     * POST JSON string payload (null/blank -> "{}").
     */
    public HttpResponse postJson(String url, Map<String, String> headers, String json) throws IOException {
        String payload = (json == null || json.trim().isEmpty()) ? "{}" : json;
        RequestBody body = RequestBody.create(payload, JSON);
@@ -99,12 +105,16 @@
        return execute(rb.build());
    }
    /** POST x-www-form-urlencoded fields. */
    /**
     * POST x-www-form-urlencoded fields.
     */
    public HttpResponse postForm(String url, Map<String, String> formFields) throws IOException {
        return postForm(url, null, formFields);
    }
    /** POST x-www-form-urlencoded fields. */
    /**
     * POST x-www-form-urlencoded fields.
     */
    public HttpResponse postForm(String url, Map<String, String> headers, Map<String, String> formFields) throws IOException {
        FormBody.Builder fb = new FormBody.Builder(DEFAULT_CHARSET);
        if (formFields != null) {
@@ -169,10 +179,21 @@
            this.tookMs = tookMs;
        }
        public int statusCode() { return statusCode; }
        public Headers headers() { return headers; }
        public String body() { return body; }
        public long tookMs() { return tookMs; }
        public int statusCode() {
            return statusCode;
        }
        public Headers headers() {
            return headers;
        }
        public String body() {
            return body;
        }
        public long tookMs() {
            return tookMs;
        }
        public boolean is2xx() {
            return statusCode >= 200 && statusCode < 300;
@@ -219,7 +240,9 @@
            return this;
        }
        /** Trust ALL certificates. ONLY for internal testing/self-signed endpoints. */
        /**
         * Trust ALL certificates. ONLY for internal testing/self-signed endpoints.
         */
        public Builder trustAllSsl(boolean enable) {
            this.trustAllSsl = enable;
            return this;
@@ -250,9 +273,18 @@
        TrustAll() {
            try {
                this.trustManager = new X509TrustManager() {
                    @Override public void checkClientTrusted(X509Certificate[] chain, String authType) { }
                    @Override public void checkServerTrusted(X509Certificate[] chain, String authType) { }
                    @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
                    @Override
                    public void checkClientTrusted(X509Certificate[] chain, String authType) {
                    }
                    @Override
                    public void checkServerTrusted(X509Certificate[] chain, String authType) {
                    }
                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[0];
                    }
                };
                SSLContext sslContext = SSLContext.getInstance("TLS");
zy-acs-hex/src/main/java/com/zy/acs/hex/utils/StrUtils.java
@@ -16,7 +16,7 @@
        String[] parts = routingKey.split("\\.");
        if (parts.length == 4) {
            Map<String, String> data = new HashMap<>();
            data.put(InfluxDBConstant.DEVICE_MEASUREMENT_TAG_TYPE, parts[1]);
            data.put(InfluxDBConstant.DEVICE_MEASUREMENT_TAG_TYPE, parts[1].toUpperCase());
            data.put(InfluxDBConstant.DEVICE_MEASUREMENT_TAG_DEVICEID, parts[2]);
            data.put(InfluxDBConstant.DEVICE_MEASUREMENT_TAG_EVENT, parts[3]);
            return data;
zy-acs-hex/src/main/resources/application.yml
@@ -33,3 +33,8 @@
  #token: apiv3_116RKycNhxbf62Nys4zthC05aRD-aidzhEpEpLtsFuedhJTaCtVklNrzHs9LHxBWMuzDclBHVgToGoQuWGiIIA
  retention-period: 30d
  createDatabaseUrl: ${influxdb3.url}/api/v3/configure/database
# 登录配置
login:
  username: admin
  password: admin123
zy-acs-hex/src/main/webapp/views/index.html
@@ -10,156 +10,650 @@
            padding: 0;
            box-sizing: border-box;
        }
        body {
            font-family: Arial, sans-serif;
            background-color: #f0f2f5;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background-color: #f5f7fa;
            color: #333;
        }
        .header {
            background-color: #1890ff;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 15px 20px;
            padding: 15px 30px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
        }
        .header h1 {
            font-size: 22px;
            font-weight: 600;
        }
        .header button {
            background-color: rgba(255, 255, 255, 0.2);
            color: white;
            border: 1px solid rgba(255, 255, 255, 0.3);
            padding: 8px 20px;
            border-radius: 20px;
            cursor: pointer;
            font-size: 14px;
            transition: all 0.3s ease;
        }
        .header button:hover {
            background-color: rgba(255, 255, 255, 0.3);
        }
        .container {
            padding: 30px;
        }
        .filter-section {
            background-color: white;
            padding: 20px;
            border-radius: 12px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
            margin-bottom: 20px;
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 15px;
            animation: fadeIn 0.5s ease-in-out;
        }
        @keyframes fadeIn {
            from {
                opacity: 0;
                transform: translateY(20px);
            }
            to {
                opacity: 1;
                transform: translateY(0);
            }
        }
        .filter-group {
            display: flex;
            flex-direction: column;
        }
        .filter-group label {
            margin-bottom: 8px;
            font-weight: 500;
            color: #666;
            font-size: 14px;
        }
        .filter-group select,
        .filter-group input {
            padding: 10px 12px;
            border: 2px solid #e0e0e0;
            border-radius: 8px;
            font-size: 14px;
            transition: border-color 0.3s ease;
        }
        .filter-group select:focus,
        .filter-group input:focus {
            outline: none;
            border-color: #667eea;
            box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
        }
        .filter-actions {
            grid-column: 1 / -1;
            display: flex;
            gap: 10px;
            margin-top: 10px;
        }
        .btn {
            padding: 10px 20px;
            border: none;
            border-radius: 8px;
            font-size: 14px;
            font-weight: 500;
            cursor: pointer;
            transition: all 0.3s ease;
        }
        .btn-primary {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
        }
        .btn-primary:hover {
            transform: translateY(-2px);
            box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
        }
        .btn-secondary {
            background-color: #f0f0f0;
            color: #333;
        }
        .btn-secondary:hover {
            background-color: #e0e0e0;
        }
        .log-section {
            background-color: white;
            border-radius: 12px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
            overflow: hidden;
        }
        .log-header {
            padding: 20px;
            border-bottom: 1px solid #f0f0f0;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        .header h1 {
            font-size: 20px;
        .log-header h2 {
            font-size: 18px;
            font-weight: 600;
            color: #333;
        }
        .header button {
            background-color: transparent;
            color: white;
            border: 1px solid white;
            padding: 5px 15px;
            border-radius: 4px;
            cursor: pointer;
        .log-content {
            padding: 0;
        }
        .header button:hover {
            background-color: rgba(255,255,255,0.1);
        }
        .container {
            padding: 20px;
        }
        .refresh-btn {
            background-color: #1890ff;
            color: white;
            border: none;
            padding: 8px 16px;
            border-radius: 4px;
            cursor: pointer;
            margin-bottom: 20px;
        }
        .refresh-btn:hover {
            background-color: #40a9ff;
        }
        table {
            width: 100%;
            border-collapse: collapse;
            background-color: white;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        th, td {
            padding: 12px;
            padding: 15px 20px;
            text-align: left;
            border-bottom: 1px solid #ddd;
            border-bottom: 1px solid #f0f0f0;
        }
        th {
            background-color: #f5f5f5;
            font-weight: bold;
            background-color: #f9f9f9;
            font-weight: 600;
            color: #666;
            font-size: 14px;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }
        tr:hover {
            background-color: #f5f5f5;
            background-color: #f9f9f9;
        }
        td {
            font-size: 14px;
            color: #555;
        }
        .loading {
            text-align: center;
            padding: 20px;
            color: #666;
            padding: 60px;
            color: #999;
            font-size: 16px;
        }
        .error {
            text-align: center;
            padding: 20px;
            color: red;
            padding: 60px;
            color: #ff4d4f;
            font-size: 16px;
        }
        .empty {
            text-align: center;
            padding: 60px;
            color: #999;
            font-size: 16px;
        }
        .status-badge {
            display: inline-block;
            padding: 4px 12px;
            border-radius: 12px;
            font-size: 12px;
            font-weight: 500;
        }
        .status-up {
            background-color: #e6f7ee;
            color: #52c41a;
        }
        .status-down {
            background-color: #fff2e8;
            color: #fa8c16;
        }
        @media (max-width: 768px) {
            .container {
                padding: 15px;
            }
            .filter-section {
                grid-template-columns: 1fr;
            }
            .filter-actions {
                flex-direction: column;
            }
            .btn {
                width: 100%;
            }
            table {
                display: block;
                overflow-x: auto;
            }
        }
    </style>
</head>
<body>
    <div class="header">
        <h1>机器人上下行日志</h1>
        <button id="logoutBtn">登出</button>
<div class="header">
    <h1>机器人上下行日志</h1>
    <button id="logoutBtn">登出</button>
</div>
<div class="container">
    <div class="filter-section">
        <div class="filter-group">
            <label for="deviceId">小车编号</label>
            <input type="text" id="deviceId" placeholder="请输入小车编号">
        </div>
        <div class="filter-group">
            <label for="messageType">消息类型</label>
            <select id="messageType">
                <option value="">全部</option>
                <!-- 从后台接口获取 -->
            </select>
        </div>
        <div class="filter-group">
            <label for="tag">标签</label>
            <select id="tag">
                <option value="">全部</option>
                <!-- 从后台接口获取 -->
            </select>
        </div>
        <div class="filter-group">
            <label for="startTime">开始时间</label>
            <input type="datetime-local" id="startTime" step="1">
        </div>
        <div class="filter-group">
            <label for="endTime">结束时间</label>
            <input type="datetime-local" id="endTime" step="1">
        </div>
        <div class="filter-actions">
            <button class="btn btn-primary" id="searchBtn">查询</button>
            <button class="btn btn-secondary" id="resetBtn">重置</button>
            <button class="btn btn-secondary" id="refreshBtn">刷新</button>
        </div>
    </div>
    <div class="container">
        <button class="refresh-btn" id="refreshBtn">刷新数据</button>
        <div id="loading" class="loading">加载中...</div>
        <div id="error" class="error" style="display: none;"></div>
        <table id="logTable" style="display: none;">
            <thead>
                <tr>
                    <th>时间</th>
                    <th>设备ID</th>
                    <th>消息类型</th>
                    <th>消息内容</th>
                </tr>
            </thead>
            <tbody id="logTableBody">
            </tbody>
        </table>
    <div class="log-section">
        <div class="log-header">
            <h2>日志记录</h2>
            <span id="recordCount">共 0 条记录</span>
        </div>
        <div class="log-content">
            <div id="loading" class="loading">加载中...</div>
            <div id="error" class="error" style="display: none;"></div>
            <div id="empty" class="empty" style="display: none;">暂无符合条件的日志记录</div>
            <table id="logTable" style="display: none;">
                <thead>
                        <tr>
                            <th>时间</th>
                            <th>设备ID</th>
                            <th>消息类型</th>
                            <th>标签</th>
                            <th>消息内容</th>
                            <th>操作</th>
                        </tr>
                    </thead>
                <tbody id="logTableBody">
                </tbody>
            </table>
        </div>
    </div>
    <script>
        // 检查登录状态
        function checkLogin() {
            if (!localStorage.getItem('loggedIn')) {
                window.location.href = '/login';
            }
        }
        // 登出功能
        document.getElementById('logoutBtn').addEventListener('click', function() {
            localStorage.removeItem('loggedIn');
</div>
<script>
    // 检查登录状态
    function checkLogin() {
        if (!localStorage.getItem('loggedIn')) {
            window.location.href = '/login';
        });
        // 加载日志数据
        function loadLogData() {
            document.getElementById('loading').style.display = 'block';
            document.getElementById('error').style.display = 'none';
            document.getElementById('logTable').style.display = 'none';
            fetch('/deviceLog/query')
                .then(response => response.json())
                .then(data => {
                    document.getElementById('loading').style.display = 'none';
                    if (data && data.length > 0) {
        }
    }
    // 登出功能
    document.getElementById('logoutBtn').addEventListener('click', function () {
        localStorage.removeItem('loggedIn');
        window.location.href = '/login';
    });
    // 重置筛选条件
    document.getElementById('resetBtn').addEventListener('click', function () {
        document.getElementById('deviceId').value = '';
        document.getElementById('messageType').value = '';
        document.getElementById('tag').value = '';
        document.getElementById('startTime').value = '';
        document.getElementById('endTime').value = '';
        loadLogData();
    });
    // 查询按钮点击事件
    document.getElementById('searchBtn').addEventListener('click', loadLogData);
    // 刷新按钮点击事件
    document.getElementById('refreshBtn').addEventListener('click', loadLogData);
    // 加载日志数据
    function loadLogData() {
        document.getElementById('loading').style.display = 'block';
        document.getElementById('error').style.display = 'none';
        document.getElementById('empty').style.display = 'none';
        document.getElementById('logTable').style.display = 'none';
        // 获取筛选条件
        const deviceId = document.getElementById('deviceId').value;
        const messageType = document.getElementById('messageType').value;
        const tag = document.getElementById('tag').value;
        const startTime = document.getElementById('startTime').value;
        const endTime = document.getElementById('endTime').value;
        // 构建查询参数
        const params = new URLSearchParams();
        if (deviceId) params.append('deviceId', deviceId);
        if (messageType) params.append('type', messageType);
        if (tag) params.append('event', tag);
        if (startTime) {
            // 使用原始的日期格式
            params.append('startTime', startTime+"Z");
        }
        if (endTime) {
            // 使用原始的日期格式
            params.append('endTime', endTime+"Z");
        }
        // 调用查询接口
        fetch(`/deviceLog/query?${params.toString()}`)
            .then(response => response.json())
            .then(data => {
                document.getElementById('loading').style.display = 'none';
                // 检查接口返回格式
                if (data && data.code === 200 && data.data) {
                    // 应用筛选条件(如果后台没有处理筛选)
                    let filteredData = data.data;
                    if (deviceId) {
                        // 模糊匹配设备ID
                        filteredData = filteredData.filter(item => {
                            return item.deviceId && item.deviceId.includes(deviceId);
                        });
                    }
                    if (messageType) {
                        // 直接使用messageType进行匹配
                        filteredData = filteredData.filter(item => {
                            return item.type && item.type === messageType;
                        });
                    }
                    if (tag) {
                        // 使用event字段作为标签进行筛选
                        filteredData = filteredData.filter(item => {
                            return item.event && item.event === tag;
                        });
                    }
                    if (startTime) {
                        const start = new Date(startTime).getTime();
                        filteredData = filteredData.filter(item => {
                            const itemTime = new Date(item.timestamp).getTime();
                            return itemTime >= start;
                        });
                    }
                    if (endTime) {
                        const end = new Date(endTime).getTime();
                        filteredData = filteredData.filter(item => {
                            const itemTime = new Date(item.timestamp).getTime();
                            return itemTime <= end;
                        });
                    }
                    // 更新记录数
                    document.getElementById('recordCount').textContent = `共 ${filteredData.length} 条记录`;
                    if (filteredData && filteredData.length > 0) {
                        document.getElementById('logTable').style.display = 'table';
                        const tbody = document.getElementById('logTableBody');
                        tbody.innerHTML = '';
                        data.forEach(item => {
                            const row = document.createElement('tr');
                            row.innerHTML = `
                                <td>${item.time || '-'}</td>
                                <td>${item.deviceId || '-'}</td>
                                <td>${item.messageType || '-'}</td>
                                <td>${item.messageContent || '-'}</td>
                            `;
                            tbody.appendChild(row);
                        });
                        filteredData.forEach(item => {
                                const row = document.createElement('tr');
                                const statusClass = item.type === 'up' ? 'status-up' : 'status-down';
                                const statusText = item.type === 'up' ? '上行' : '下行';
                                const sourceHexStr = item.sourceHexStr || '';
                                // 格式化时间戳
                                let formattedTime = '-';
                                if (item.timestamp) {
                                    try {
                                        const date = new Date(item.timestamp/1000/1000);
                                        formattedTime = date.toLocaleString('zh-CN', {
                                            year: 'numeric',
                                            month: '2-digit',
                                            day: '2-digit',
                                            hour: '2-digit',
                                            minute: '2-digit',
                                            second: '2-digit'
                                        });
                                    } catch (e) {
                                        formattedTime = item.timestamp;
                                    }
                                }
                                row.innerHTML = `
                                    <td>${formattedTime}</td>
                                    <td>${item.deviceId || '-'}</td>
                                    <td><span class="status-badge ${statusClass}">${statusText}</span></td>
                                    <td>${item.event || '-'}</td>
                                    <td>${sourceHexStr}</td>
                                    <td>
                                        <button class="btn btn-secondary parse-btn" data-hex="${sourceHexStr}">解析</button>
                                    </td>
                                `;
                                tbody.appendChild(row);
                            });
                            // 为解析按钮添加点击事件
                            document.querySelectorAll('.parse-btn').forEach(btn => {
                                btn.addEventListener('click', function() {
                                    const hexData = this.getAttribute('data-hex');
                                    console.log('解析按钮点击,hexData:', hexData);
                                    if (hexData) {
                                        parseHexData(hexData);
                                    } else {
                                        alert('没有可解析的消息内容');
                                    }
                                });
                            });
                    } else {
                        document.getElementById('error').textContent = '暂无日志数据';
                        document.getElementById('error').style.display = 'block';
                        document.getElementById('empty').style.display = 'block';
                    }
                })
                .catch(error => {
                    document.getElementById('loading').style.display = 'none';
                    document.getElementById('error').textContent = '加载数据失败: ' + error.message;
                } else {
                    document.getElementById('error').textContent = '加载数据失败: ' + (data.message || '未知错误');
                    document.getElementById('error').style.display = 'block';
                });
                }
            })
            .catch(error => {
                document.getElementById('loading').style.display = 'none';
                document.getElementById('error').textContent = '加载数据失败: ' + error.message;
                document.getElementById('error').style.display = 'block';
            });
    }
    // 从后台接口获取消息类型和标签
    function loadFilterOptions() {
        // 获取消息类型
        fetch('/deviceLog/queryType')
            .then(response => response.json())
            .then(data => {
                if (data && data.code === 200 && data.data) {
                    const messageTypeSelect = document.getElementById('messageType');
                    messageTypeSelect.innerHTML = '<option value="">全部</option>';
                    data.data.forEach(type => {
                        const option = document.createElement('option');
                        option.value = type.label;
                        option.textContent = type.value;
                        messageTypeSelect.appendChild(option);
                    });
                    // 消息类型变化时,重新加载标签
                    messageTypeSelect.addEventListener('change', function () {
                        loadTags(this.value);
                    });
                    // 初始加载标签
                    loadTags('');
                }
            })
            .catch(error => {
                console.error('加载消息类型失败:', error);
            });
    }
    // 加载标签
    function loadTags(directionType) {
        let url = '/deviceLog/queryEvent';
        if (directionType) {
            url += '?directionType=' + directionType;
        }
        fetch(url)
            .then(response => response.json())
            .then(data => {
                if (data && data.code === 200 && data.data) {
                    const tagSelect = document.getElementById('tag');
                    tagSelect.innerHTML = '<option value="">全部</option>';
                    data.data.forEach(tag => {
                        const option = document.createElement('option');
                        option.value = tag.value;
                        option.textContent = tag.label;
                        tagSelect.appendChild(option);
                    });
                }
            })
            .catch(error => {
                console.error('加载标签失败:', error);
            });
    }
    // 解析十六进制数据
    function parseHexData(hexData) {
        console.log('开始解析,hexData:', hexData);
        // 先关闭之前的加载状态和结果弹窗
        const oldLoading = document.getElementById('parseLoading');
        if (oldLoading) {
            oldLoading.remove();
        }
        
        // 刷新按钮点击事件
        document.getElementById('refreshBtn').addEventListener('click', loadLogData);
        const oldResult = document.querySelector('[id^="resultDiv"]');
        if (oldResult) {
            oldResult.remove();
        }
        // 显示加载状态
        const loadingDiv = document.createElement('div');
        loadingDiv.className = 'loading';
        loadingDiv.textContent = '解析中...';
        loadingDiv.style.position = 'fixed';
        loadingDiv.style.top = '50%';
        loadingDiv.style.left = '50%';
        loadingDiv.style.transform = 'translate(-50%, -50%)';
        loadingDiv.style.backgroundColor = 'rgba(255, 255, 255, 0.9)';
        loadingDiv.style.padding = '20px';
        loadingDiv.style.borderRadius = '8px';
        loadingDiv.style.boxShadow = '0 2px 10px rgba(0,0,0,0.1)';
        loadingDiv.id = 'parseLoading';
        document.body.appendChild(loadingDiv);
        // 构建请求URL
const url = `/proxy/decode?hexData=${encodeURIComponent(hexData)}`;
console.log('请求URL:', url);
        // 调用解析接口
        fetch(url, {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json',
                'Accept': 'application/json'
            },
            mode: 'cors' // 允许跨域请求
        })
        .then(response => {
            console.log('响应状态:', response.status);
            console.log('响应头:', response.headers);
            if (!response.ok) {
                throw new Error(`解析失败,状态码: ${response.status}`);
            }
            return response.json();
        })
        .then(data => {
            console.log('解析结果:', data);
            // 移除加载状态
            document.getElementById('parseLoading').remove();
            // 显示解析结果
            const resultDiv = document.createElement('div');
            resultDiv.id = 'resultDiv_' + Date.now(); // 添加唯一ID
            resultDiv.style.position = 'fixed';
            resultDiv.style.top = '50%';
            resultDiv.style.left = '50%';
            resultDiv.style.transform = 'translate(-50%, -50%)';
            resultDiv.style.backgroundColor = 'white';
            resultDiv.style.padding = '20px';
            resultDiv.style.borderRadius = '8px';
            resultDiv.style.boxShadow = '0 2px 20px rgba(0,0,0,0.2)';
            resultDiv.style.maxWidth = '80%';
            resultDiv.style.maxHeight = '80%';
            resultDiv.style.overflow = 'auto';
            resultDiv.style.zIndex = '1000';
            // 构建结果HTML
            let resultHTML = '<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">';
            resultHTML += '<h3 style="margin: 0;">解析结果</h3>';
            resultHTML += '<button id="closeResult" style="padding: 5px 10px; background-color: #f0f0f0; color: #333; border: none; border-radius: 4px; cursor: pointer;">×</button>';
            resultHTML += '</div>';
            resultHTML += '<pre style="background-color: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace; margin: 0;">';
            resultHTML += JSON.stringify(data, null, 2);
            resultHTML += '</pre>';
            resultDiv.innerHTML = resultHTML;
            document.body.appendChild(resultDiv);
            // 关闭按钮点击事件
            document.getElementById('closeResult').addEventListener('click', function() {
                resultDiv.remove();
            });
        })
        .catch(error => {
            console.error('解析错误:', error);
            // 移除加载状态
            document.getElementById('parseLoading').remove();
            // 显示详细的错误信息
            let errorMessage = '解析失败: ' + error.message;
            if (error.message.includes('Failed to fetch')) {
                errorMessage += '\n可能的原因:\n1. 解析服务未启动\n2. 跨域问题\n3. 网络连接问题';
            }
            alert(errorMessage);
        });
    }
        
        // 页面加载时检查登录状态并加载数据
        checkLogin();
        loadFilterOptions();
        loadLogData();
    </script>
</script>
</body>
</html>
zy-acs-hex/src/main/webapp/views/login.html
@@ -10,95 +10,153 @@
            padding: 0;
            box-sizing: border-box;
        }
        body {
            font-family: Arial, sans-serif;
            background-color: #f0f2f5;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
        }
        .login-container {
            background-color: white;
            padding: 40px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            border-radius: 12px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
            width: 100%;
            max-width: 400px;
            animation: fadeIn 0.5s ease-in-out;
        }
        @keyframes fadeIn {
            from {
                opacity: 0;
                transform: translateY(20px);
            }
            to {
                opacity: 1;
                transform: translateY(0);
            }
        }
        h2 {
            text-align: center;
            margin-bottom: 30px;
            color: #333;
            font-size: 24px;
            font-weight: 600;
        }
        .form-group {
            margin-bottom: 20px;
        }
        label {
            display: block;
            margin-bottom: 8px;
            color: #666;
            font-weight: 500;
        }
        input {
            width: 100%;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 4px;
            padding: 12px 16px;
            border: 2px solid #e0e0e0;
            border-radius: 8px;
            font-size: 16px;
            transition: border-color 0.3s ease;
        }
        input:focus {
            outline: none;
            border-color: #667eea;
            box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
        }
        button {
            width: 100%;
            padding: 12px;
            background-color: #1890ff;
            padding: 14px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            border: none;
            border-radius: 4px;
            border-radius: 8px;
            font-size: 16px;
            font-weight: 600;
            cursor: pointer;
            margin-top: 10px;
            transition: transform 0.2s ease;
        }
        button:hover {
            background-color: #40a9ff;
            transform: translateY(-2px);
        }
        button:active {
            transform: translateY(0);
        }
        .error-message {
            color: red;
            margin-top: 10px;
            color: #ff4d4f;
            margin-top: 12px;
            text-align: center;
            font-size: 14px;
        }
    </style>
</head>
<body>
    <div class="login-container">
        <h2>机器人日志系统</h2>
        <form id="loginForm">
            <div class="form-group">
                <label for="username">用户名</label>
                <input type="text" id="username" name="username" required>
            </div>
            <div class="form-group">
                <label for="password">密码</label>
                <input type="password" id="password" name="password" required>
            </div>
            <button type="submit">登录</button>
            <div id="errorMessage" class="error-message"></div>
        </form>
    </div>
    <script>
        document.getElementById('loginForm').addEventListener('submit', function(e) {
            e.preventDefault();
            const username = document.getElementById('username').value;
            const password = document.getElementById('password').value;
            // 简单的登录验证(实际项目中应该调用后端API)
            if (username === 'admin' && password === 'admin123') {
                // 存储登录状态
                localStorage.setItem('loggedIn', 'true');
                // 跳转到主页面
                window.location.href = '/';
            } else {
                document.getElementById('errorMessage').textContent = '用户名或密码错误';
            }
        });
    </script>
<div class="login-container">
    <h2>机器人日志系统</h2>
    <form id="loginForm">
        <div class="form-group">
            <label for="username">用户名</label>
            <input type="text" id="username" name="username" required>
        </div>
        <div class="form-group">
            <label for="password">密码</label>
            <input type="password" id="password" name="password" required>
        </div>
        <button type="submit">登录</button>
        <div id="errorMessage" class="error-message"></div>
    </form>
</div>
<script>
    document.getElementById('loginForm').addEventListener('submit', function (e) {
        e.preventDefault();
        const username = document.getElementById('username').value;
        const password = document.getElementById('password').value;
        const errorMessage = document.getElementById('errorMessage');
        // 显示加载状态
        errorMessage.textContent = '登录中...';
        // 调用后端登录接口
        fetch('/login/auth', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            },
            body: `user=${encodeURIComponent(username)}&pass=${encodeURIComponent(password)}`
        })
            .then(response => response.json())
            .then(data => {
                if (data && data.code === 200) {
                    // 登录成功,存储登录状态
                    localStorage.setItem('loggedIn', 'true');
                    // 跳转到主页面
                    window.location.href = '/';
                } else {
                    // 登录失败,显示错误信息
                    errorMessage.textContent = data.message || '用户名或密码错误';
                }
            })
            .catch(error => {
                // 网络错误,显示错误信息
                errorMessage.textContent = '登录失败,请稍后重试';
                console.error('登录失败:', error);
            });
    });
</script>
</body>
</html>