1
zhang
4 天以前 bc56530307e0e92b94a1abb5d38368f04b92e990
1
14个文件已修改
10个文件已添加
1292 ■■■■■ 已修改文件
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 | 历史
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-gateway/src/main/java/com/zy/acs/gateway/controller/UtilsController.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/dashboard.html 96 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/java/com/zy/acs/hex/config/WebMvcConfig.java 3 ●●●● 补丁 | 查看 | 原始文档 | 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 3 ●●●●● 补丁 | 查看 | 原始文档 | 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 补丁 | 查看 | 原始文档 | 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 598 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/webapp/views/login.html 92 ●●●● 补丁 | 查看 | 原始文档 | 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.");
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-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;
@@ -251,16 +257,86 @@
            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分钟前' }
                {
                    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分钟前'
                }
                ]);
                // 分页数据
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;
@@ -46,11 +45,11 @@
    }
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 配置静态资源处理器
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/static/")
                .addResourceLocations("/static/");
        // 配置视图文件处理器
        registry.addResourceHandler("/views/**")
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;
@@ -11,8 +10,6 @@
 */
@Controller
public class RouterController {
    @RequestMapping("/")
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
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,74 +10,248 @@
            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>
@@ -87,21 +261,65 @@
        <button id="logoutBtn">登出</button>
    </div>
    <div class="container">
        <button class="refresh-btn" id="refreshBtn">刷新数据</button>
    <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="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>
    </div>
    <script>
        // 检查登录状态
@@ -117,33 +335,155 @@
            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';
            
            fetch('/deviceLog/query')
        // 获取筛选条件
        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.length > 0) {
                // 检查接口返回格式
                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 => {
                        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>${item.time || '-'}</td>
                                    <td>${formattedTime}</td>
                                <td>${item.deviceId || '-'}</td>
                                <td>${item.messageType || '-'}</td>
                                <td>${item.messageContent || '-'}</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 {
                        document.getElementById('error').textContent = '暂无日志数据';
                                        alert('没有可解析的消息内容');
                                    }
                                });
                            });
                    } else {
                        document.getElementById('empty').style.display = 'block';
                    }
                } else {
                    document.getElementById('error').textContent = '加载数据失败: ' + (data.message || '未知错误');
                        document.getElementById('error').style.display = 'block';
                    }
                })
@@ -154,11 +494,165 @@
                });
        }
        
        // 刷新按钮点击事件
        document.getElementById('refreshBtn').addEventListener('click', loadLogData);
    // 从后台接口获取消息类型和标签
    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();
        }
        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>
</body>
zy-acs-hex/src/main/webapp/views/login.html
@@ -10,60 +10,98 @@
            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>
@@ -88,16 +126,36 @@
            e.preventDefault();
            const username = document.getElementById('username').value;
            const password = document.getElementById('password').value;
        const errorMessage = document.getElementById('errorMessage');
            
            // 简单的登录验证(实际项目中应该调用后端API)
            if (username === 'admin' && password === 'admin123') {
                // 存储登录状态
        // 显示加载状态
        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 {
                document.getElementById('errorMessage').textContent = '用户名或密码错误';
                    // 登录失败,显示错误信息
                    errorMessage.textContent = data.message || '用户名或密码错误';
            }
            })
            .catch(error => {
                // 网络错误,显示错误信息
                errorMessage.textContent = '登录失败,请稍后重试';
                console.error('登录失败:', error);
            });
        });
    </script>
</body>