优化log机制

This commit is contained in:
weidong 2024-11-05 17:17:47 +08:00
parent 86776c2405
commit 7c9a5b0d26
24 changed files with 736 additions and 17 deletions

View File

@ -25,6 +25,9 @@ public class GnssDevice {
public static final short MODEL_G505 = 0; //F9P
public static final short MODEL_G510 = 1; //博通
public static final short LOGGING_MODE_SIMPLE = 0; // 精简模式 仅D3F0和D3F2
public static final short LOGGING_MODE_FULL = 1; // 完整模式
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private Integer tenantid;
@ -60,4 +63,7 @@ public class GnssDevice {
private String imei;
private Short model;
// 日志记录控制
private Short loggingmode;
}

View File

@ -0,0 +1,44 @@
package com.imdroid.secapi.dto;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.time.LocalDateTime;
import lombok.Data;
@Data
@TableName(value = "gnssdevicesinglerecords")
public class GnssSingleData {
public static final short MODEL_G505 = 0; //F9P
public static final short MODEL_G510 = 1; //博通
@TableId(value = "id", type = IdType.AUTO)
@ExcelIgnore
Long id;
@ExcelProperty("设备编号")
String deviceid;
@ExcelProperty("记录时间")
LocalDateTime createtime;
// 博通的 GGA 经纬度数据
// F9P 的私有格式 B562 ECEF 数据
@ExcelProperty("型号")
Short model;
@ExcelProperty("")
double x; // GGA时是 latitude, ECEF时是 x
@ExcelProperty("")
double y; // GGA时是 longitude, ECEF时是 y
@ExcelProperty("")
double z; // GGA时是 altitude, ECEF时是 z
// GGA GPS状态0初始化1单点定位2码差分3无效PPS4固定解5浮点解6正在估算7人工输入固定值8模拟模式9WAAS差分
// ECEF 0无B562,1 浮点解,2 固定解
@ExcelProperty("状态")
int status;
}

View File

@ -0,0 +1,13 @@
package com.imdroid.secapi.dto;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.yulichang.base.MPJBaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface GnssSingleDataMapper extends MPJBaseMapper<GnssSingleData> {
Page<GnssSingleData> queryByDeviceId(@Param("page") IPage page, @Param("deviceId") String deviceId,
@Param("beginTime") String beginTime, @Param("endTime") String endTime, @Param("count") Integer count);
}

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.imdroid.secapi.dto.GnssSingleDataMapper">
<select id="queryByDeviceId" resultType="com.imdroid.secapi.dto.GnssSingleData">
select c.* from (select @n:=@n+1 as n, a.* from (select * from gnssdevicesinglerecords
<where>
deviceid = #{deviceId}
<if test="beginTime != null and beginTime != ''">
and createtime &gt;= #{beginTime}
</if>
<if test="endTime != null and endTime != ''">
and createtime &lt;= #{endTime}
</if>
</where>
order by createtime desc)a,(select @n:=0)b)c where c.n%#{count}=1
</select>
</mapper>

View File

@ -3,16 +3,14 @@ package com.imdroid.sideslope.calc;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.imdroid.common.util.DataTypeUtil;
import com.imdroid.common.util.ThreadManager;
import com.imdroid.secapi.dto.GnssCalcData;
import com.imdroid.secapi.dto.GnssCalcDataMapper;
import com.imdroid.secapi.dto.GnssGroupCalc;
import com.imdroid.secapi.dto.GnssGroupCalcMapper;
import com.imdroid.secapi.dto.*;
import com.imdroid.sideslope.bd.*;
import com.imdroid.sideslope.message.D341LocationMessage;
import com.imdroid.sideslope.sal.Device;
import com.imdroid.sideslope.sal.DeviceService;
import com.imdroid.sideslope.server.DeviceChannel;
import com.imdroid.sideslope.server.OnlineChannels;
import com.imdroid.sideslope.service.GnssSingleBufferService;
import com.imdroid.sideslope.service.WarningService;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
@ -56,6 +54,9 @@ public class SingleLineGNSSCalcService implements GNSSDataCalcService {
@Autowired
GnssCalcDataMapper dataMapper;
@Autowired
private GnssSingleBufferService gnssSingleDataService;
// 非线程安全需加同步保护
List<GnssGroupCalc> groupCalcList;
@ -149,6 +150,32 @@ public class SingleLineGNSSCalcService implements GNSSDataCalcService {
hardResetDevice(deviceId);
}
}
// 若是该设备开启了日志记录则保存单次解析的数据
if(device.getLoggingmode() == GnssDevice.LOGGING_MODE_FULL){
// 保存单次解析的原始数据 loggingmode 字段控制
GnssSingleData gnssSingleData = new GnssSingleData();
gnssSingleData.setDeviceid(device.getDeviceId());
gnssSingleData.setCreatetime(LocalDateTime.now());
gnssSingleData.setModel(device.getModel());
if((device.getModel() == GnssDevice.MODEL_G505) && (doubles !=null)){
gnssSingleData.setX(doubles[0]);
gnssSingleData.setY(doubles[1]);
gnssSingleData.setZ(doubles[2]);
gnssSingleData.setStatus((int) doubles[3]);
gnssSingleDataService.addData(gnssSingleData);
}
else if((device.getModel() == GnssDevice.MODEL_G510) && (gga !=null)){
gnssSingleData.setX(gga.getLatitude());
gnssSingleData.setY(gga.getLongitude());
gnssSingleData.setZ(gga.getAltitude());
gnssSingleData.setStatus(gga.getQuality());
gnssSingleDataService.addData(gnssSingleData);
}
}
}
@Override

View File

@ -1,5 +1,6 @@
package com.imdroid.sideslope.executor;
import com.imdroid.common.util.DataTypeUtil;
import com.imdroid.common.util.ThreadManager;
import com.imdroid.secapi.client.BeidouClient;
import com.imdroid.secapi.dto.GnssDevice;
@ -41,7 +42,13 @@ public class D331RtcmMessageExecutor implements Executor<D331RtcmMessage, Void>
// 补齐tenantId
Device device1 = deviceService.findByDeviceId(id);
if(device1 == null || device1.getOpMode() == GnssDevice.OP_MODE_UNUSE) return null;
logger.debug("receive d331 rtcm message of device: "+message.getId()+", seq:"+message.getSeq()+", len:"+message.getLen());
// 原始码流输出到日志文件 -- INFO 级别
// 只有测站开了日志记录或者消息来自基站才将原码记录到日志文件
if(device1.getLoggingmode() == GnssDevice.LOGGING_MODE_FULL || device1.getDeviceType() == GnssDevice.TYPE_REFERENCE_STATION){
logger.info("receive {} d331 message: {}", message.getId(), DataTypeUtil.getHexString(message.getSrcData()));
logger.info("receive d331 rtcm message of device: {}, seq:{}, len:{}", message.getId(), message.getSeq(), message.getLen());
}
// 推送基站数据
if(device1.getOpMode() == GnssDevice.OP_MODE_USE) {

View File

@ -1,7 +1,9 @@
package com.imdroid.sideslope.executor;
import com.imdroid.common.util.DataTypeUtil;
import com.imdroid.common.util.ThreadManager;
import com.imdroid.secapi.client.BeidouClient;
import com.imdroid.secapi.dto.GnssDevice;
import com.imdroid.sideslope.bd.Gga;
import com.imdroid.sideslope.calc.GNSSDataCalcService;
import com.imdroid.sideslope.message.D341LocationMessage;
@ -35,14 +37,17 @@ public class D341LocationMessageExecutor implements Executor<D341LocationMessage
private DataPersistService dataPersistService;
@Override
public Void execute(D341LocationMessage message) {
if (logger.isDebugEnabled()) {
logger.debug("receive d341 message of device: "+message.getId()+", seq:"+message.getSeq()+", len:"+message.getLen());
}
// 补齐tenantId要求快速处理因此用缓存不要每次都查数据库
Device device = deviceService.findByDeviceId(message.getId());
if(device == null) return null;
message.setTenantId(device.getTenantId());
// 原始码流输出到日志文件 -- INFO 级别
if(device.getLoggingmode() == GnssDevice.LOGGING_MODE_FULL){
logger.info("receive "+message.getId()+" d341 message: "+ DataTypeUtil.getHexString(message.getSrcData()));
logger.info("receive d341 message of device: "+message.getId()+", seq:"+message.getSeq()+", len:"+message.getLen());
}
// trx cycle stat
LocalDateTime now = LocalDateTime.now();
if(device.getLastRxTime()!=null &&
@ -86,6 +91,13 @@ public class D341LocationMessageExecutor implements Executor<D341LocationMessage
device.setLatitude(gga.getLatitude());
device.setLongitude(gga.getLongitude());
device.setAltitude(gga.getAltitude());
}else{
// 若不是固定解则打印原始码流到日志里
if(device.getLoggingmode() == GnssDevice.LOGGING_MODE_SIMPLE){
logger.info("receive {} d341 message: {}", message.getId(), DataTypeUtil.getHexString(message.getSrcData()));
logger.info("receive d341 message of device: {}, seq:{}, len:{}", message.getId(), message.getSeq(), message.getLen());
}
}
if(!device.isB562AsCalc()){
device.updateCalcQuality(gga.getQuality());

View File

@ -1,5 +1,6 @@
package com.imdroid.sideslope.executor;
import com.imdroid.common.util.DataTypeUtil;
import com.imdroid.common.util.HexUtil;
import com.imdroid.common.util.ThreadManager;
import com.imdroid.secapi.client.BeidouClient;
@ -43,9 +44,7 @@ public class D3F0SelfCheckMessageExecutor implements Executor<D3F0SelfCheckMessa
@Override
public Void execute(D3F0SelfCheckMessage message) {
if (logger.isDebugEnabled()) {
logger.debug("receive d3f0 message of device: "+message.getId());
}
logger.info("receive d3f0 message of {} device: {}", message.getId(), DataTypeUtil.getHexString(message.getSrcData()));
Device device = deviceService.findByDeviceId(message.getId());
if(device == null) return null;

View File

@ -1,5 +1,6 @@
package com.imdroid.sideslope.executor;
import com.imdroid.common.util.DataTypeUtil;
import com.imdroid.common.util.ThreadManager;
import com.imdroid.secapi.client.BeidouClient;
import com.imdroid.secapi.dto.GnssTrxMsg;
@ -9,6 +10,7 @@ import com.imdroid.sideslope.message.D3F2StopIndicationMessage;
import com.imdroid.sideslope.sal.Device;
import com.imdroid.sideslope.sal.DeviceService;
import com.imdroid.sideslope.service.DataPersistService;
import com.imdroid.sideslope.service.GnssSingleBufferServiceImpl;
import com.imdroid.sideslope.service.WarningService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -41,11 +43,14 @@ public class D3F2StopIndicationMessageExecutor implements Executor<D3F2StopIndic
@Autowired
WarningService warningService;
@Autowired
GnssSingleBufferServiceImpl gnssSingleBufferService;
@Override
public Void execute(D3F2StopIndicationMessage message) {
if (logger.isDebugEnabled()) {
logger.debug("receive d3f2 message of device: "+message.getId());
}
logger.info("receive d3f2 message of {} device: {}", message.getId(), DataTypeUtil.getHexString(message.getSrcData()));
// 补齐tenantId
String deviceId = message.getId();
Device device = deviceService.findByDeviceId(deviceId);
@ -65,6 +70,10 @@ public class D3F2StopIndicationMessageExecutor implements Executor<D3F2StopIndic
LocalDateTime uploadTime = multiLineGNSSCalcService.checkUploadTime(
device.getDeviceId(), device.getTenantId());
dataPersistService.saveDeviceTrxStat(message, (uploadTime!=null), device);
// 保存缓存中的 GNSS 单次解析结果
gnssSingleBufferService.flush();
// 通知beidou服务设备休眠
try {
beidouClient.onDeviceStop(deviceId, device.getTenantId());

View File

@ -38,6 +38,8 @@ public class D341LocationMessage extends BaseMessage {
// b562
b562_loc = UBXUtil.getLocation(src);
gga = Gga.getFrom(src);
this.srcData = new byte[src.readableBytes()];
src.readBytes(this.srcData);
}
@Override

View File

@ -58,6 +58,11 @@ public class D3F0SelfCheckMessage extends BaseMessage {
" reboot:"+src.readUnsignedShort();
}
// read 会移动 bytebuf 的指针所以保存原始码流需要将此指针挑拨回开始处
src.readerIndex(0);
this.srcData = new byte[src.readableBytes()];
src.readBytes(this.srcData);
}
}

View File

@ -60,6 +60,11 @@ public class D3F2StopIndicationMessage extends BaseMessage {
}
}
// read 会移动 bytebuf 的指针所以保存原始码流需要将此指针挑拨回开始处
src.readerIndex(0);
this.srcData = new byte[src.readableBytes()];
src.readBytes(this.srcData);
}
}

View File

@ -30,6 +30,7 @@ public class DbDeviceServiceImpl implements DeviceService {
device.setTenantId(gnssDevice.getTenantid());
device.setDeviceId(gnssDevice.getDeviceid());
device.setDeviceType(gnssDevice.getDevicetype());
device.setModel(gnssDevice.getModel());
device.setParentId(gnssDevice.getParentid());
device.setName(gnssDevice.getName());
device.setProjectId(gnssDevice.getProject_id());
@ -42,6 +43,7 @@ public class DbDeviceServiceImpl implements DeviceService {
device.setEcefx(gnssDevice.getEcefx());
device.setEcefy(gnssDevice.getEcefy());
device.setEcefz(gnssDevice.getEcefz());
device.setLoggingmode(gnssDevice.getLoggingmode());
return device;
}

View File

@ -35,6 +35,7 @@ public class Device {
private String fwdId;
private Integer deviceType;
private Short model;
private Integer calcGroupId;
private Short opMode;
@ -65,6 +66,9 @@ public class Device {
LocalDateTime lastD3f0f2Time;
short noFixedAndFloatResult=0;
// 日志记录控制
Short loggingmode;
int lastRxHead = 0;
int fixedNum = 0;

View File

@ -1,6 +1,5 @@
package com.imdroid.sideslope.server.tcp;
import com.imdroid.common.util.DataTypeUtil;
import com.imdroid.sideslope.executor.BizExecutors;
import com.imdroid.sideslope.executor.MessageParser;
import com.imdroid.sideslope.message.BaseMessage;
@ -19,11 +18,11 @@ public class RtcmTcpHandler extends SimpleChannelInboundHandler<ByteBuf> {
private final Logger logger = LoggerFactory.getLogger(RtcmTcpServer.class);
@Override
public void channelRead0(ChannelHandlerContext ctx, ByteBuf src) throws Exception{
if (logger.isDebugEnabled()) {
/*if (logger.isDebugEnabled()) {
byte[] data = new byte[src.readableBytes()];
src.getBytes(0, data);
logger.debug("receive message:" + DataTypeUtil.getHexString(data));
}
}*/
try {
BaseMessage message = MessageParser.instance.parse(src);
OnlineChannels.INSTANCE.updateConfigChannel(message.getId(), ctx.channel(), null);

View File

@ -0,0 +1,10 @@
package com.imdroid.sideslope.service;
import com.imdroid.secapi.dto.GnssSingleData;
public interface GnssSingleBufferService {
void addData(GnssSingleData data);
void flush();
void clear();
int getBufferSize();
}

View File

@ -0,0 +1,81 @@
package com.imdroid.sideslope.service;
import com.imdroid.secapi.dto.GnssSingleData;
import com.imdroid.secapi.dto.GnssSingleDataMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class GnssSingleBufferServiceImpl implements GnssSingleBufferService {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final List<GnssSingleData> buffer = new ArrayList<>();
// BUFFER_SIZE 暂定此大小
private static final int BUFFER_SIZE = 1024;
// 资源锁
private final Object lock = new Object();
@Autowired
private GnssSingleDataMapper gnssSingleDataMapper;
@Override
public void addData(GnssSingleData data) {
if (data == null) {
return;
}
synchronized (lock) {
buffer.add(data);
if (buffer.size() >= BUFFER_SIZE) {
// 溢出时直接保存
// 此处暂定但很有必要引入一个线程来保存避免阻塞主线程
flush();
}
}
}
@Override
public void flush() {
synchronized (lock) {
if (buffer.isEmpty()) {
return;
}
try {
List<GnssSingleData> batchList = new ArrayList<>(buffer);
buffer.clear();
// 由于每秒写数据库操作频繁这里采用批量保存的方式
saveBatch(batchList);
logger.debug("批量插入{}条数据成功", batchList.size());
} catch (Exception e) {
logger.error("批量插入数据失败", e);
throw e;
}
}
}
private void saveBatch(List<GnssSingleData> dataList) {
for (GnssSingleData data : dataList) {
gnssSingleDataMapper.insert(data);
}
}
@Override
public void clear() {
synchronized (lock) {
buffer.clear();
}
}
@Override
public int getBufferSize() {
synchronized(lock) {
return buffer.size();
}
}
}

View File

@ -2,9 +2,11 @@ package com.imdroid.sideslope.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.imdroid.common.util.NumberUtils;
import com.imdroid.common.util.WarningLogExecutor;
import com.imdroid.secapi.dto.*;
import com.imdroid.sideslope.sal.Device;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
@ -22,6 +24,9 @@ public class WarningServiceImpl implements WarningService {
@Autowired
GnssStatusMapper gnssStatusMapper;
@Value("${warning.log.directory}")
private String logDirectory;
// warning type <-> level & value
Map<Integer, int[]> cfgMap = new ConcurrentHashMap<>();
int warningLevel2Code = 0;
@ -174,6 +179,9 @@ public class WarningServiceImpl implements WarningService {
warningMsgMapper.insert(warningMsg);
//告警级别
curStatus.setWarningcode(curStatus.getWarningcode() | warningType);
// 新告警出现后生成对应设备的 warning 日志文件
generate_warning_logs(curStatus.getDeviceid(),warningType,auxInfo);
}
isUpdated = true;
}
@ -219,4 +227,12 @@ public class WarningServiceImpl implements WarningService {
}
}
}
public void generate_warning_logs(String device_id,int warning_type,String warning_type_name){
if (warning_type == WarningCfg.TYPE_LOW_VOLTAGE) {
WarningLogExecutor warningLogExecutor = new WarningLogExecutor(logDirectory);
warningLogExecutor.generateWarningLogs(device_id, warning_type_name);
}
}
}

View File

@ -0,0 +1,93 @@
package com.imdroid.beidou.controller;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.imdroid.beidou.service.CommonExcelService;
import com.imdroid.secapi.dto.GnssSingleData;
import com.imdroid.secapi.dto.GnssSingleDataMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpSession;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Controller
public class GnssSingleDataController extends BasicController implements CommonExcelService<GnssSingleData, GnssSingleData> {
@Autowired
GnssSingleDataMapper gnssSingleDataMapper;
final DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@RequestMapping("/page/gnss_single_data")
public String gnssSingleData(Model m, HttpSession session){
initModel(m, session);
return "/page/gnss_single_data";
}
@RequestMapping("/gnss/data/list_single_data")
@ResponseBody
public JSONObject listData(HttpSession session, Integer page, Integer limit, String searchParams) {
// 检查查询条件
JSONObject search = null;
if (searchParams != null) {
search = (JSONObject) JSONObject.parse(searchParams);
System.out.println(search);
String deviceId = search.getString("deviceid");
Integer freqency = search.getInteger("freqency");
String begin = search.getString("dgt_.createtime");
String end = search.getString("dlt_.createtime");
if(deviceId != null && !deviceId.isEmpty() && freqency!=0){
Page pageable = new Page<>(page == null ? 1 : page, limit == null ? 10 : limit);
// 缺省按1小时采样如果时间跨度较大则按最多300条记录的采样率采样
int sample = 6;
if(freqency == 2){
LocalDateTime endTime = LocalDateTime.now();
LocalDateTime beginTime = endTime.minusDays(30);
if(begin!=null && !begin.isEmpty()){
beginTime = LocalDateTime.parse(begin,df);
}
if(end!=null && !end.isEmpty()){
endTime = LocalDateTime.parse(end,df);
}
Duration duration = Duration.between(beginTime, endTime);
int hours = (int)duration.toHours();
if(hours > 300) sample = hours*6/300;
}
Page<GnssSingleData> calcDataList = gnssSingleDataMapper.queryByDeviceId(pageable, deviceId,begin,end,sample);
JSONObject jsonObject = new JSONObject();
jsonObject.put("code", 0);
jsonObject.put("msg", "");
jsonObject.put("count", calcDataList.getTotal());
jsonObject.put("data", calcDataList.getRecords());
return jsonObject;
}
}
return this.pageList(session, page, limit, searchParams);
}
@Override
public Class<GnssSingleData> getEntityClass() {
return GnssSingleData.class;
}
@Override
public BaseMapper<GnssSingleData> getMapper() {
return gnssSingleDataMapper;
}
}

View File

@ -3,9 +3,11 @@ package com.imdroid.beidou.task;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.github.yulichang.query.MPJQueryWrapper;
import com.imdroid.beidou.service.NotificationService;
import com.imdroid.common.util.WarningLogExecutor;
import com.imdroid.secapi.dto.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
@ -44,6 +46,10 @@ public class DeviceStatusChecker {
GnssCalcDataMapper dataMapper;
@Autowired
GnssDeviceMapper deviceMapper;
@Value("${warning.log.directory}")
private String logDirectory;
static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Scheduled(cron = "0 18,48 * * * ?") // 每半时执行一次
@ -153,6 +159,12 @@ public class DeviceStatusChecker {
status.setWarningcode(status.getWarningcode() | WarningCfg.TYPE_NO_FIXED_RESULT);
status.setWarning(WarningCfg.LEVEL_2);
deviceIds.add(status.getDeviceid());
// 生成 Warning 日志
WarningLogExecutor warningLogExecutor = new WarningLogExecutor(logDirectory);
warningLogExecutor.generateWarningLogs(status.getDeviceid(),WarningCfg.TYPE_NAME_NO_FIXED_RESULT);
}
gnssStatusMapper.updateById(status);
}

View File

@ -53,6 +53,12 @@
"icon": "fa fa-calculator",
"target": "_self"
},
{
"title": "Log",
"href": "page/gnss_single_data",
"icon": "fa fa-clipboard",
"target": "_self"
},
{
"title": "配置管理",
"href": "",

View File

@ -0,0 +1,249 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>单次解算记录</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="../lib/layui-v2.6.3/css/layui.css" media="all">
<link rel="stylesheet" href="../css/public.css" media="all">
</head>
<body>
<div class="layuimini-container">
<div class="layuimini-main">
<fieldset class="table-search-fieldset">
<legend>搜索信息</legend>
<div style="margin: 10px 10px 10px 10px">
<form class="layui-form layui-form-pane" action="" id="searchFrm">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">设备号</label>
<div class="layui-input-inline">
<input type="text" name="deviceid" id="input_deviceid" class="layui-input" style="position:absolute;z-index:2;width:85%;" value="" autocomplete="off">
<select id="deviceid" lay-search="" lay-filter="device" >
<option value="">全部</option>
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">范围</label>
<div class="layui-input-inline">
<input type="text" name="dgt_createtime" autocomplete="off" id="ID-laydate-start-date" class="layui-input" placeholder="开始日期">
</div>
<div class="layui-input-inline">
<input type="text" name="dlt_createtime" autocomplete="off" id="ID-laydate-end-date" class="layui-input" placeholder="结束日期">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">粒度</label>
<div class="layui-input-inline">
<select name="freqency" id="freqency" lay-search="">
<option value="0"></option>
<option value="1">小时</option>
<option value="2">自动</option>
</select>
</div>
</div>
<div class="layui-inline">
<button type="submit" class="layui-btn layui-btn-primary" lay-submit lay-filter="data-search-btn"><i class="layui-icon">&#xe615;</i> 搜 索</button>
<button type="submit" class="layui-btn layui-btn-primary" lay-submit lay-filter="data-export-btn"><i class="layui-icon">&#xe67d;</i>导出</button>
</div>
</div>
</form>
</div>
</fieldset>
<div class="layui-tab layui-tab-card" lay-filter="data-tab">
<ul class="layui-tab-title">
<li class="layui-this">数据表格</li>
<li>单个设备曲线</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
<table class="layui-hide" id="currentTableId" lay-filter="currentTableFilter"></table>
</div>
<div class="layui-tab-item">
<div id="echarts-gnss" style="min-height:300px;padding: 10px"></div>
</div>
</div>
</div>
<script type="text/html" id="currentTableBar">
<a class="layui-btn layui-btn-normal layui-btn-xs data-count-edit" lay-event="cmd">命令行</a>
</script>
</div>
</div>
<script src="../js/lay-module/echarts/echartsTheme.js" charset="utf-8"></script>
<script src="../js/lay-module/echarts/echarts.js" charset="utf-8"></script>
<script src="../lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
<script th:inline="javascript">
layui.use(['form', 'table','laydate','element'], function () {
var $ = layui.$,
form = layui.form,
table = layui.table,
laydate = layui.laydate;
var searchDeviceId = false;
var echartsDevice = echarts.init(document.getElementById('echarts-gnss'), 'walden');
var data_cols = [
{field: 'deviceid', title: '设备号'},
{field: 'createtime', title: '产生时间', width:'18%', templet: "<div>{{layui.util.toDateString(d.createtime, 'yyyy-MM-dd HH:mm:ss')}}</div>"},
{field: 'x', title: '东'},
{field: 'y', title: '北'},
{field: 'z', title: '天'},
{field: 'status', title: '状态'}
];
/**
* 初始化表单,要加上,不然刷新部分组件可能会不加载
*/
form.render();
laydate.render({
elem: '#ID-laydate-start-date',
type: 'datetime'
});
laydate.render({
elem: '#ID-laydate-end-date',
type: 'datetime'
});
table.render({
elem: '#currentTableId',
url: '/gnss/data/list_single_data',
toolbar: '#toolbarDemo', //开启头部工具栏
defaultToolbar: ['filter'],
cols: [
data_cols
],
limits: [20, 50, 100, 200, 300],
limit: 20,
page: true,
skin: 'line',
done: function (result, curr, count) {
if(searchDeviceId){
console.log(result.data);
showChart(result.data)
}
}
});
// 监听搜索操作
form.on('submit(data-search-btn)', function (data) {
var result = JSON.stringify(data.field);
var deviceId = $('#input_deviceid').val();
searchDeviceId = !isNaN(deviceId);
//执行搜索重载
table.reload('currentTableId', {
page: {
curr: 1
}
, where: {
searchParams: result
}
}, 'data');
return false;
});
// 监听导出操作
form.on('submit(data-export-btn)', function (data) {
var result = $('#searchFrm').serialize();
var u = "/gnss/data/calc/export?" + result;
window.open(u, "_blank");
return false;
});
form.on('select(device)', function (data) {
//对select和input进行监控渲染
if (data.value != "") {
$("#input_deviceid").val(data.value);
}
});
//监听Tab切换重新resize图表否则显示不出来
layui.element.on('tab(data-tab)', function(data){
if (data.index == 1) {
echartsDevice.resize();
}
});
function showChart(chartData){
var sortedData = chartData.sort((a, b) => new Date(a.createtime) - new Date(b.createtime));
// 处理数据
var times = sortedData.map(item => {
let date = new Date(item.createtime);
return date.toLocaleTimeString();
});
var xValues = sortedData.map(item => item.x);
var yValues = sortedData.map(item => item.y);
var zValues = sortedData.map(item => item.z);
// 图表配置
var option = {
title: {
text: 'GNSS 单次解记录',
left: 'center'
},
tooltip: {
trigger: 'axis',
formatter: function(params) {
let result = params[0].name + '<br/>';
params.forEach(param => {
result += param.seriesName + ': ' +
param.value.toFixed(2) + '<br/>';
});
return result;
}
},
legend: {
data: ['X坐标', 'Y坐标', 'Z坐标'],
top: '30px'
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: times,
axisLabel: {
rotate: 45
}
},
yAxis: {
type: 'value',
axisLabel: {
formatter: '{value}'
}
},
series: [
{
name: 'X坐标',
type: 'line',
data: xValues,
smooth: true,
},
{
name: 'Y坐标',
type: 'line',
data: yValues,
smooth: true,
},
{
name: 'Z坐标',
type: 'line',
data: zValues,
smooth: true,
}
]
};
echartsDevice.setOption(option);
}
});
</script>
</body>
</html>

View File

@ -34,6 +34,16 @@
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label required">日志记录</label>
<div class="layui-input-inline">
<select name="loggingmode" id="loggingmode" lay-verify="required" lay-search="">
<option value="0">不记录(除了自检和停止消息)</option>
<option value="1">记录</option>
</select>
</div>
</div>
</div>
<div class="layui-form-item">
<div class="layui-inline">
@ -292,6 +302,7 @@
$('#fwd_group_id').val(data.fwd_group_id);
$('#fwd_group_id2').val(data.fwd_group_id2);
$('#opmode').val(data.opmode);
$('#loggingmode').val(data.loggingmode);
$('#fwddeviceid').val(data.fwddeviceid);
$('#ipose').val(data.ipose);
$('#iposn').val(data.iposn);

View File

@ -0,0 +1,89 @@
package com.imdroid.common.util;
import java.io.*;
import java.nio.file.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
public class WarningLogExecutor {
private final String logDirectory;
private final ExecutorService executor;
// 记录已初始化的文件
private final ConcurrentHashMap<String, Boolean> initializedFiles = new ConcurrentHashMap<>();
public WarningLogExecutor(String logDirectory) {
this.logDirectory = logDirectory;
this.executor = ThreadManager.getSingleThreadPool("warning_log_thread");
}
public void generateWarningLogs(String keyword, String warningTypeName) {
try {
String date = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmm"));
String warningFileName = "Warning-" + keyword + "-" + date + ".log";
Path warningFile = Paths.get(logDirectory, warningFileName);
// 如果是新文件先写入初始化信息
if (initializedFiles.putIfAbsent(warningFileName, true) == null) {
initializeWarningFile(warningFile, keyword, warningTypeName);
}
// 找到日志文件
List<Path> logFiles = Files.list(Paths.get(logDirectory))
.filter(Files::isRegularFile)
.filter(path -> path.getFileName().toString().matches("sideslopertcm(\\.\\d{4}-\\d{2}-\\d{2}\\.\\d+)?\\.log"))
.collect(Collectors.toList());
for (Path logFile : logFiles) {
executor.submit(() -> processFile(logFile, keyword, warningTypeName, warningFile));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
executor.shutdown();
}
}
private void initializeWarningFile(Path warningFile, String keyword, String warningTypeName) {
try (BufferedWriter writer = Files.newBufferedWriter(warningFile,
StandardOpenOption.CREATE, StandardOpenOption.APPEND)) {
writer.write("=== Warning Log File ===");
writer.newLine();
writer.write("Created Time: " + LocalDateTime.now());
writer.newLine();
writer.write("Warning Type Name: " + warningTypeName);
writer.newLine();
writer.write("Keyword: " + keyword);
writer.newLine();
writer.write("==============================");
writer.newLine();
writer.newLine();
} catch (IOException e) {
e.printStackTrace();
}
}
private void processFile(Path logFile, String keyword, String warningTypeName, Path warningFile) {
try (BufferedReader reader = Files.newBufferedReader(logFile);
BufferedWriter writer = Files.newBufferedWriter(warningFile,
StandardOpenOption.CREATE, StandardOpenOption.APPEND)) {
writer.write("Processing source file: " + logFile.getFileName());
writer.newLine();
String line;
while ((line = reader.readLine()) != null) {
if (line.contains(keyword)) {
writer.write(line);
writer.newLine();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}