| New file |
| | |
| | | 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; |
| | | } |
| | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | } |
| | | 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(); |
| | | |
| | | // 创建一个列表用于存储结果 |
| | |
| | | } |
| | | } |
| | | } |
| | | 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."); |
| | |
| | | |
| | | @Data |
| | | @AllArgsConstructor |
| | | @NoArgsConstructor |
| | | public class DeviceMessage implements Serializable { |
| | | |
| | | private String sourceHexStr; |
| | | |
| | | private Long timestamp = System.currentTimeMillis(); |
| | | |
| | | |
| | | public DeviceMessage(String sourceHexStr) { |
| | | this.sourceHexStr = sourceHexStr; |
| | | } |
| | |
| | | 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 |
| New file |
| | |
| | | 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)); |
| | | } |
| | | |
| | | } |
| | |
| | | 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; |
| | |
| | | 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分钟前' |
| | | } |
| | | ]); |
| | | |
| | | // 分页数据 |
| | |
| | | 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; |
| | |
| | | } |
| | | |
| | | |
| | | |
| | | @Override |
| | | public void addResourceHandlers(ResourceHandlerRegistry registry) { |
| | | // 配置静态资源处理器 |
| | | registry.addResourceHandler("/static/**") |
| | | .addResourceLocations("classpath:/static/") |
| | | .addResourceLocations("/static/"); |
| | | // 配置视图文件处理器 |
| | | registry.addResourceHandler("/views/**") |
| | |
| | | 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 |
| | |
| | | */ |
| | | @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); |
| | | } |
| | | |
| | | } |
| New file |
| | |
| | | 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("用户名或密码错误"); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | 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()); |
| | | } |
| | | } |
| | | } |
| | |
| | | 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; |
| | | |
| | |
| | | */ |
| | | @Controller |
| | | public class RouterController { |
| | | |
| | | |
| | | |
| | | |
| | | @RequestMapping("/") |
| New file |
| | |
| | | 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); |
| | | } |
| | | } |
| | |
| | | |
| | | 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; |
| | |
| | | @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); |
| | | } |
| | | |
| | | } |
| New file |
| | |
| | | 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; |
| | | } |
| New file |
| | |
| | | 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"; // 默认降序 |
| | | } |
| New file |
| | |
| | | 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; |
| | | } |
| | | } |
| New file |
| | |
| | | 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; |
| | | } |
| | | } |
| New file |
| | |
| | | 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; |
| | | } |
| | | |
| | | } |
| | |
| | | |
| | | /** |
| | | * Minimal OkHttp wrapper: GET / POST only. |
| | | * |
| | | * <p> |
| | | * - fluent API (get / postJson / postForm) |
| | | * - default singleton instance (thread-safe) |
| | | * - per-request headers + default headers |
| | |
| | | : 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; |
| | | } |
| | |
| | | |
| | | // ===================== 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); |
| | |
| | | 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) { |
| | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | 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"); |
| | |
| | | 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; |
| | |
| | | #token: apiv3_116RKycNhxbf62Nys4zthC05aRD-aidzhEpEpLtsFuedhJTaCtVklNrzHs9LHxBWMuzDclBHVgToGoQuWGiIIA |
| | | retention-period: 30d |
| | | createDatabaseUrl: ${influxdb3.url}/api/v3/configure/database |
| | | |
| | | # 登录配置 |
| | | login: |
| | | username: admin |
| | | password: admin123 |
| | |
| | | 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> |
| | |
| | | <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> |
| | | // 检查登录状态 |
| | |
| | | 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'; |
| | | } |
| | | }) |
| | |
| | | }); |
| | | } |
| | | |
| | | // 刷新按钮点击事件 |
| | | 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> |
| | |
| | | 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> |
| | |
| | | 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> |