mirror of
https://gitee.com/pan648540858/wvp-GB28181-pro.git
synced 2026-05-25 06:27:50 +08:00
支持心跳和注册统计
This commit is contained in:
parent
31549bce09
commit
a33e7949a4
@ -19,8 +19,8 @@ public class VideoManagerConstants {
|
|||||||
public static final String ONLINE_MEDIA_SERVERS_PREFIX = "VMP_ONLINE_MEDIA_SERVERS:";
|
public static final String ONLINE_MEDIA_SERVERS_PREFIX = "VMP_ONLINE_MEDIA_SERVERS:";
|
||||||
|
|
||||||
public static final String DEVICE_PREFIX = "VMP_DEVICE_INFO";
|
public static final String DEVICE_PREFIX = "VMP_DEVICE_INFO";
|
||||||
public static final String DEVICE_KEEPALIVE_PREFIX = "DEVICE_KEEPALIVE:";
|
public static final String DEVICE_KEEPALIVE_PREFIX = "VMP_DEVICE_KEEPALIVE:";
|
||||||
public static final String DEVICE_REGISTER_PREFIX = "DEVICE_REGISTER:";
|
public static final String DEVICE_REGISTER_PREFIX = "VMP_DEVICE_REGISTER:";
|
||||||
|
|
||||||
public static final String INVITE_PREFIX = "VMP_GB_INVITE_INFO";
|
public static final String INVITE_PREFIX = "VMP_GB_INVITE_INFO";
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import org.springframework.context.annotation.Bean;
|
|||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.data.redis.serializer.GenericToStringSerializer;
|
||||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ -43,4 +44,22 @@ public class RedisTemplateConfig {
|
|||||||
return redisTemplate;
|
return redisTemplate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RedisTemplate<String, Long> redisLongTemplate(RedisConnectionFactory connectionFactory) {
|
||||||
|
RedisTemplate<String, Long> template = new RedisTemplate<>();
|
||||||
|
template.setConnectionFactory(connectionFactory);
|
||||||
|
|
||||||
|
// Key 使用 String 序列化
|
||||||
|
template.setKeySerializer(new StringRedisSerializer());
|
||||||
|
template.setHashKeySerializer(new StringRedisSerializer());
|
||||||
|
|
||||||
|
// Value 使用 GenericToStringSerializer,它能将 Long 转换为纯文本字符串存入 Redis
|
||||||
|
// 这样在 Redis 命令行输入 'get key' 看到的是 "123" 而不是二进制乱码
|
||||||
|
template.setValueSerializer(new GenericToStringSerializer<>(Long.class));
|
||||||
|
template.setHashValueSerializer(new GenericToStringSerializer<>(Long.class));
|
||||||
|
|
||||||
|
template.afterPropertiesSet();
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -89,15 +89,15 @@ public class Device {
|
|||||||
/**
|
/**
|
||||||
* 注册时间
|
* 注册时间
|
||||||
*/
|
*/
|
||||||
@Schema(description = "注册时间")
|
@Schema(description = "注册时间戳")
|
||||||
private String registerTime;
|
private Long registerTimeStamp;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 心跳时间
|
* 心跳时间
|
||||||
*/
|
*/
|
||||||
@Schema(description = "心跳时间")
|
@Schema(description = "心跳时间")
|
||||||
private String keepaliveTime;
|
private Long keepaliveTimeStamp;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -0,0 +1,20 @@
|
|||||||
|
package com.genersoft.iot.vmp.gb28181.bean;
|
||||||
|
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Schema(description = "时间统计信息")
|
||||||
|
public class TimeStatistics {
|
||||||
|
|
||||||
|
@Schema(description = "时间")
|
||||||
|
private String time;
|
||||||
|
|
||||||
|
@Schema(description = "时间差")
|
||||||
|
private Long timeDiff;
|
||||||
|
}
|
||||||
@ -8,6 +8,7 @@ import com.genersoft.iot.vmp.conf.security.JwtUtils;
|
|||||||
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
|
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.SyncStatus;
|
import com.genersoft.iot.vmp.gb28181.bean.SyncStatus;
|
||||||
|
import com.genersoft.iot.vmp.gb28181.bean.TimeStatistics;
|
||||||
import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService;
|
import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService;
|
||||||
import com.genersoft.iot.vmp.gb28181.service.IDeviceService;
|
import com.genersoft.iot.vmp.gb28181.service.IDeviceService;
|
||||||
import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService;
|
import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService;
|
||||||
@ -21,8 +22,10 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.servlet.ServletOutputStream;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.compress.utils.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.ibatis.annotations.Options;
|
import org.apache.ibatis.annotations.Options;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
@ -31,12 +34,11 @@ import org.springframework.util.ObjectUtils;
|
|||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.context.request.async.DeferredResult;
|
import org.springframework.web.context.request.async.DeferredResult;
|
||||||
|
|
||||||
import jakarta.servlet.ServletOutputStream;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Tag(name = "国标设备查询", description = "国标设备查询")
|
@Tag(name = "国标设备查询", description = "国标设备查询")
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
@ -136,7 +138,7 @@ public class DeviceQuery {
|
|||||||
log.debug("设备通道信息同步API调用,deviceId:" + deviceId);
|
log.debug("设备通道信息同步API调用,deviceId:" + deviceId);
|
||||||
}
|
}
|
||||||
Device device = deviceService.getDeviceByDeviceId(deviceId);
|
Device device = deviceService.getDeviceByDeviceId(deviceId);
|
||||||
if (device.getRegisterTime() == null) {
|
if (device.getTransport() == null) {
|
||||||
WVPResult<SyncStatus> wvpResult = new WVPResult<>();
|
WVPResult<SyncStatus> wvpResult = new WVPResult<>();
|
||||||
wvpResult.setCode(ErrorCode.ERROR100.getCode());
|
wvpResult.setCode(ErrorCode.ERROR100.getCode());
|
||||||
wvpResult.setMsg("设备尚未注册过");
|
wvpResult.setMsg("设备尚未注册过");
|
||||||
@ -155,7 +157,7 @@ public class DeviceQuery {
|
|||||||
log.debug("设备信息删除API调用,deviceId:" + deviceId);
|
log.debug("设备信息删除API调用,deviceId:" + deviceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清除redis记录
|
// 清除 redis 记录
|
||||||
deviceService.delete(deviceId);
|
deviceService.delete(deviceId);
|
||||||
JSONObject json = new JSONObject();
|
JSONObject json = new JSONObject();
|
||||||
json.put("deviceId", deviceId);
|
json.put("deviceId", deviceId);
|
||||||
@ -181,8 +183,7 @@ public class DeviceQuery {
|
|||||||
|
|
||||||
DeviceChannel deviceChannel = deviceChannelService.getOne(deviceId,channelId);
|
DeviceChannel deviceChannel = deviceChannelService.getOne(deviceId,channelId);
|
||||||
if (deviceChannel == null) {
|
if (deviceChannel == null) {
|
||||||
PageInfo<DeviceChannel> deviceChannelPageResult = new PageInfo<>();
|
return new PageInfo<>();
|
||||||
return deviceChannelPageResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return deviceChannelService.getSubChannels(deviceChannel.getDataDeviceId(), channelId, query, channelType, online, page, count);
|
return deviceChannelService.getSubChannels(deviceChannel.getDataDeviceId(), channelId, query, channelType, online, page, count);
|
||||||
@ -430,4 +431,28 @@ public class DeviceQuery {
|
|||||||
public void subscribeMobilePosition(int id, int cycle, int interval) {
|
public void subscribeMobilePosition(int id, int cycle, int interval) {
|
||||||
deviceService.subscribeMobilePosition(id, cycle, interval);
|
deviceService.subscribeMobilePosition(id, cycle, interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/statistics/keepalive")
|
||||||
|
@Operation(summary = "请求心跳统计")
|
||||||
|
@Parameter(name = "deviceId", description = "设备国标编号", required = true)
|
||||||
|
@Parameter(name = "count", description = "返回的数量,按时间正向排序,返回的最新的", required = true)
|
||||||
|
public List<TimeStatistics> getKeepaliveTimeStatistics(String deviceId, Integer count) {
|
||||||
|
if (ObjectUtils.isEmpty(deviceId)) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
return deviceService.getKeepaliveTimeStatistics(deviceId, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/statistics/register")
|
||||||
|
@Operation(summary = "请求注册统计")
|
||||||
|
@Parameter(name = "deviceId", description = "设备国标编号", required = true)
|
||||||
|
@Parameter(name = "count", description = "返回的数量,按时间正向排序,返回的最新的", required = true)
|
||||||
|
public List<TimeStatistics> getRegisterTimeStatistics(String deviceId, Integer count) {
|
||||||
|
if (ObjectUtils.isEmpty(deviceId)) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
return deviceService.getRegisterTimeStatistics(deviceId, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -90,13 +90,6 @@ public interface IDeviceService {
|
|||||||
|
|
||||||
List<Device> getAllByStatus(Boolean status);
|
List<Device> getAllByStatus(Boolean status);
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断是否注册已经失效
|
|
||||||
* @param device 设备信息
|
|
||||||
* @return 布尔
|
|
||||||
*/
|
|
||||||
boolean expire(Device device);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查设备状态
|
* 检查设备状态
|
||||||
* @param device 设备信息
|
* @param device 设备信息
|
||||||
@ -201,4 +194,7 @@ public interface IDeviceService {
|
|||||||
|
|
||||||
void queryPreset(Device device, String channelId, ErrorCallback<List<Preset>> callback);
|
void queryPreset(Device device, String channelId, ErrorCallback<List<Preset>> callback);
|
||||||
|
|
||||||
|
List<TimeStatistics> getKeepaliveTimeStatistics(String deviceId, Integer count);
|
||||||
|
|
||||||
|
List<TimeStatistics> getRegisterTimeStatistics(String deviceId, Integer count);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,7 +54,6 @@ import javax.sip.InvalidArgumentException;
|
|||||||
import javax.sip.ResponseEvent;
|
import javax.sip.ResponseEvent;
|
||||||
import javax.sip.SipException;
|
import javax.sip.SipException;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.SynchronousQueue;
|
import java.util.concurrent.SynchronousQueue;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@ -677,13 +676,6 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
|
|||||||
return deviceMapper.getDevices(ChannelDataType.GB28181, status);
|
return deviceMapper.getDevices(ChannelDataType.GB28181, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean expire(Device device) {
|
|
||||||
Instant registerTimeDate = Instant.from(DateUtil.formatter.parse(device.getRegisterTime()));
|
|
||||||
Instant expireInstant = registerTimeDate.plusMillis(TimeUnit.SECONDS.toMillis(device.getExpires()));
|
|
||||||
return expireInstant.isBefore(Instant.now());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Boolean getDeviceStatus(@NotNull Device device) {
|
public Boolean getDeviceStatus(@NotNull Device device) {
|
||||||
SynchronousQueue<String> queue = new SynchronousQueue<>();
|
SynchronousQueue<String> queue = new SynchronousQueue<>();
|
||||||
@ -1258,5 +1250,36 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<TimeStatistics> getKeepaliveTimeStatistics(String deviceId, Integer count) {
|
||||||
|
List<Long> timeStampList = redisCatchStorage.getDeviceKeepaliveTimeStamp(deviceId, count);
|
||||||
|
return formateTimeStatistics(timeStampList, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<TimeStatistics> getRegisterTimeStatistics(String deviceId, Integer count) {
|
||||||
|
List<Long> timeStampList = redisCatchStorage.getDeviceRegisterTimeStamp(deviceId, count);
|
||||||
|
return formateTimeStatistics(timeStampList, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TimeStatistics> formateTimeStatistics(List<Long> timeStampList, Integer count) {
|
||||||
|
if (timeStampList.isEmpty()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
List<TimeStatistics> timeStatisticsList = new ArrayList<>();
|
||||||
|
for (int i = 0; i < timeStampList.size(); i++) {
|
||||||
|
Long timeStamp = timeStampList.get(i);
|
||||||
|
TimeStatistics timeStatistics = new TimeStatistics();
|
||||||
|
timeStatistics.setTime(DateUtil.timestampMsTo_yyyy_MM_dd_HH_mm_ss(timeStamp));
|
||||||
|
if (i > 0) {
|
||||||
|
Long lastTimeStamp = timeStampList.get(i - 1);
|
||||||
|
timeStatistics.setTimeDiff((timeStamp - lastTimeStamp) / 1000);
|
||||||
|
}
|
||||||
|
timeStatisticsList.add(timeStatistics);
|
||||||
|
}
|
||||||
|
if (timeStatisticsList.size() > count) {
|
||||||
|
timeStatisticsList = timeStatisticsList.subList(timeStatisticsList.size() - count, timeStatisticsList.size());
|
||||||
|
}
|
||||||
|
return timeStatisticsList;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
|
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
|
||||||
|
|
||||||
|
import com.genersoft.iot.vmp.common.RemoteAddressInfo;
|
||||||
import com.genersoft.iot.vmp.conf.SipConfig;
|
import com.genersoft.iot.vmp.conf.SipConfig;
|
||||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||||
import com.genersoft.iot.vmp.gb28181.auth.DigestServerAuthenticationHelper;
|
import com.genersoft.iot.vmp.gb28181.auth.DigestServerAuthenticationHelper;
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.GbSipDate;
|
import com.genersoft.iot.vmp.gb28181.bean.GbSipDate;
|
||||||
import com.genersoft.iot.vmp.common.RemoteAddressInfo;
|
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
|
import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
|
||||||
import com.genersoft.iot.vmp.gb28181.service.IDeviceService;
|
import com.genersoft.iot.vmp.gb28181.service.IDeviceService;
|
||||||
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
|
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
|
||||||
@ -13,7 +13,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
|
|||||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
|
import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
|
||||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
|
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
|
||||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||||
import com.genersoft.iot.vmp.utils.DateUtil;
|
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||||
import com.genersoft.iot.vmp.utils.IpPortUtil;
|
import com.genersoft.iot.vmp.utils.IpPortUtil;
|
||||||
import gov.nist.javax.sip.address.AddressImpl;
|
import gov.nist.javax.sip.address.AddressImpl;
|
||||||
import gov.nist.javax.sip.address.SipUri;
|
import gov.nist.javax.sip.address.SipUri;
|
||||||
@ -23,7 +23,6 @@ import gov.nist.javax.sip.message.SIPResponse;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.ObjectUtils;
|
import org.springframework.util.ObjectUtils;
|
||||||
|
|
||||||
@ -38,10 +37,8 @@ import javax.sip.message.Response;
|
|||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.concurrent.BlockingQueue;
|
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SIP命令类型: REGISTER请求
|
* SIP命令类型: REGISTER请求
|
||||||
@ -67,6 +64,9 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
|
|||||||
@Autowired
|
@Autowired
|
||||||
private UserSetting userSetting;
|
private UserSetting userSetting;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IRedisCatchStorage redisCatchStorage;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterPropertiesSet() throws Exception {
|
public void afterPropertiesSet() throws Exception {
|
||||||
@ -119,7 +119,7 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
|
|||||||
String transport = reqViaHeader.getTransport();
|
String transport = reqViaHeader.getTransport();
|
||||||
device.setTransport("TCP".equalsIgnoreCase(transport) ? "TCP" : "UDP");
|
device.setTransport("TCP".equalsIgnoreCase(transport) ? "TCP" : "UDP");
|
||||||
sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), registerOkResponse);
|
sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), registerOkResponse);
|
||||||
device.setRegisterTime(DateUtil.getNow());
|
device.setRegisterTimeStamp(System.currentTimeMillis());
|
||||||
deviceService.online(device);
|
deviceService.online(device);
|
||||||
} else {
|
} else {
|
||||||
deviceService.offline(device);
|
deviceService.offline(device);
|
||||||
@ -218,7 +218,7 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
|
|||||||
// 注册成功
|
// 注册成功
|
||||||
device.setExpires(request.getExpires().getExpires());
|
device.setExpires(request.getExpires().getExpires());
|
||||||
registerFlag = true;
|
registerFlag = true;
|
||||||
// 判断TCP还是UDP
|
// 判断 TCP/UDP
|
||||||
ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
|
ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
|
||||||
String transport = reqViaHeader.getTransport();
|
String transport = reqViaHeader.getTransport();
|
||||||
device.setTransport("TCP".equalsIgnoreCase(transport) ? "TCP" : "UDP");
|
device.setTransport("TCP".equalsIgnoreCase(transport) ? "TCP" : "UDP");
|
||||||
@ -226,10 +226,10 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
|
|||||||
|
|
||||||
sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
|
sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
|
||||||
// 注册成功
|
// 注册成功
|
||||||
// 保存到redis
|
device.setRegisterTimeStamp(System.currentTimeMillis());
|
||||||
|
// 保存到 redis
|
||||||
if (registerFlag) {
|
if (registerFlag) {
|
||||||
log.info("[注册成功] deviceId: {}->{}", deviceId, requestAddress);
|
log.info("[注册成功] deviceId: {}->{}", deviceId, requestAddress);
|
||||||
device.setRegisterTime(DateUtil.getNow());
|
|
||||||
SipTransactionInfo sipTransactionInfo = new SipTransactionInfo((SIPResponse) response);
|
SipTransactionInfo sipTransactionInfo = new SipTransactionInfo((SIPResponse) response);
|
||||||
device.setSipTransactionInfo(sipTransactionInfo);
|
device.setSipTransactionInfo(sipTransactionInfo);
|
||||||
deviceService.online(device);
|
deviceService.online(device);
|
||||||
@ -237,6 +237,7 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
|
|||||||
log.info("[注销成功] deviceId: {}->{}", deviceId, requestAddress);
|
log.info("[注销成功] deviceId: {}->{}", deviceId, requestAddress);
|
||||||
deviceService.offline(device);
|
deviceService.offline(device);
|
||||||
}
|
}
|
||||||
|
redisCatchStorage.updateDeviceRegisterTimeStamp(List.of(device));
|
||||||
} catch (SipException | NoSuchAlgorithmException | ParseException e) {
|
} catch (SipException | NoSuchAlgorithmException | ParseException e) {
|
||||||
log.error("未处理的异常 ", e);
|
log.error("未处理的异常 ", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,14 +10,13 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorP
|
|||||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
|
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
|
||||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.NotifyMessageHandler;
|
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.NotifyMessageHandler;
|
||||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||||
import com.genersoft.iot.vmp.utils.DateUtil;
|
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||||
import com.genersoft.iot.vmp.utils.IpPortUtil;
|
import com.genersoft.iot.vmp.utils.IpPortUtil;
|
||||||
import gov.nist.javax.sip.message.SIPRequest;
|
import gov.nist.javax.sip.message.SIPRequest;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.dom4j.Element;
|
import org.dom4j.Element;
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.scheduling.annotation.Async;
|
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@ -26,7 +25,6 @@ import javax.sip.RequestEvent;
|
|||||||
import javax.sip.SipException;
|
import javax.sip.SipException;
|
||||||
import javax.sip.message.Response;
|
import javax.sip.message.Response;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@ -55,6 +53,9 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp
|
|||||||
@Autowired
|
@Autowired
|
||||||
private UserSetting userSetting;
|
private UserSetting userSetting;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IRedisCatchStorage redisCatchStorage;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterPropertiesSet() throws Exception {
|
public void afterPropertiesSet() throws Exception {
|
||||||
notifyMessageHandler.addHandler(cmdType, this);
|
notifyMessageHandler.addHandler(cmdType, this);
|
||||||
@ -78,8 +79,7 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp
|
|||||||
device.setIp(remoteAddressInfo.getIp());
|
device.setIp(remoteAddressInfo.getIp());
|
||||||
device.setLocalIp(request.getLocalAddress().getHostAddress());
|
device.setLocalIp(request.getLocalAddress().getHostAddress());
|
||||||
}
|
}
|
||||||
|
device.setKeepaliveTimeStamp(System.currentTimeMillis());
|
||||||
|
|
||||||
if (device.isOnLine()) {
|
if (device.isOnLine()) {
|
||||||
taskQueue.add(device);
|
taskQueue.add(device);
|
||||||
long expiresTime = Math.min(device.getExpires(), device.getHeartBeatInterval() * device.getHeartBeatCount()) * 1000L;
|
long expiresTime = Math.min(device.getExpires(), device.getHeartBeatInterval() * device.getHeartBeatCount()) * 1000L;
|
||||||
@ -94,6 +94,7 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp
|
|||||||
@Scheduled(fixedDelay = 10, timeUnit = TimeUnit.SECONDS)
|
@Scheduled(fixedDelay = 10, timeUnit = TimeUnit.SECONDS)
|
||||||
public void executeUpdateDeviceList() {
|
public void executeUpdateDeviceList() {
|
||||||
if (!taskQueue.isEmpty()) {
|
if (!taskQueue.isEmpty()) {
|
||||||
|
redisCatchStorage.updateDeviceKeepaliveTimeStamp(taskQueue.stream().toList());
|
||||||
taskQueue.clear();
|
taskQueue.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -62,7 +62,7 @@ public class RedisRpcDeviceController extends RpcController {
|
|||||||
response.setBody("param error");
|
response.setBody("param error");
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
if (device.getRegisterTime() == null) {
|
if (device.getTransport() == null) {
|
||||||
response.setStatusCode(ErrorCode.ERROR400.getCode());
|
response.setStatusCode(ErrorCode.ERROR400.getCode());
|
||||||
response.setBody("设备尚未注册过");
|
response.setBody("设备尚未注册过");
|
||||||
return response;
|
return response;
|
||||||
|
|||||||
@ -184,8 +184,11 @@ public interface IRedisCatchStorage {
|
|||||||
|
|
||||||
String chooseOneServer(String serverId);
|
String chooseOneServer(String serverId);
|
||||||
|
|
||||||
void updateDeviceKeepaliveTime(List<Device> deviceList);
|
void updateDeviceKeepaliveTimeStamp(List<Device> deviceList);
|
||||||
|
|
||||||
void updateDeviceRegisterTime(List<Device> deviceList);
|
List<Long> getDeviceKeepaliveTimeStamp(String deviceId, Integer count);
|
||||||
|
|
||||||
|
void updateDeviceRegisterTimeStamp(List<Device> deviceList);
|
||||||
|
|
||||||
|
List<Long> getDeviceRegisterTimeStamp(String deviceId, Integer count);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,6 @@ package com.genersoft.iot.vmp.storager.impl;
|
|||||||
|
|
||||||
import com.alibaba.fastjson2.JSON;
|
import com.alibaba.fastjson2.JSON;
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
|
||||||
import com.genersoft.iot.vmp.common.ServerInfo;
|
import com.genersoft.iot.vmp.common.ServerInfo;
|
||||||
import com.genersoft.iot.vmp.common.SystemAllInfo;
|
import com.genersoft.iot.vmp.common.SystemAllInfo;
|
||||||
import com.genersoft.iot.vmp.common.VideoManagerConstants;
|
import com.genersoft.iot.vmp.common.VideoManagerConstants;
|
||||||
@ -21,7 +20,9 @@ import com.genersoft.iot.vmp.utils.JsonUtil;
|
|||||||
import com.genersoft.iot.vmp.utils.SystemInfoUtils;
|
import com.genersoft.iot.vmp.utils.SystemInfoUtils;
|
||||||
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
|
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jspecify.annotations.NonNull;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.redis.core.RedisOperations;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.data.redis.core.SessionCallback;
|
import org.springframework.data.redis.core.SessionCallback;
|
||||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
@ -48,6 +49,9 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private RedisTemplate<Object, Object> redisTemplate;
|
private RedisTemplate<Object, Object> redisTemplate;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedisTemplate<String, Long> longRedisTemplate;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private StringRedisTemplate stringRedisTemplate;
|
private StringRedisTemplate stringRedisTemplate;
|
||||||
|
|
||||||
@ -546,25 +550,71 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateDeviceKeepaliveTime(List<Device> deviceList) {
|
public void updateDeviceKeepaliveTimeStamp(List<Device> deviceList) {
|
||||||
// if (deviceList == null || deviceList.isEmpty()) {
|
if (deviceList == null || deviceList.isEmpty()) {
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
// // 使用 SessionCallback 保证批量操作在同一个连接中执行
|
// 使用 SessionCallback 保证批量操作在同一个连接中执行
|
||||||
// SessionCallback<Boolean> sessionCallback = session -> {
|
SessionCallback<Boolean> sessionCallback = new SessionCallback<>() {
|
||||||
// // 1. 批量添加心跳数据到列表尾部
|
@Override
|
||||||
// for (Device device : deviceList) {
|
// 注意:这里直接写死 String, String 覆盖接口的 K, V
|
||||||
// session.opsForList().rightPush(VideoManagerConstants.DEVICE_KEEPALIVE_PREFIX + device.getDeviceId(), device);
|
public Boolean execute(@NonNull RedisOperations operations) {
|
||||||
// }
|
// 1. 批量添加心跳数据到列表尾部
|
||||||
// // 2. 截取列表,只保留最新 100 条
|
for (Device device : deviceList) {
|
||||||
// session.opsForList().trim(VideoManagerConstants.DEVICE_KEEPALIVE_PREFIX, -1000, -1);
|
operations.opsForList().rightPush(VideoManagerConstants.DEVICE_KEEPALIVE_PREFIX + device.getDeviceId(), device.getKeepaliveTimeStamp());
|
||||||
// return true;
|
// 2. 截取列表,只保留最新 100 条
|
||||||
// };
|
operations.opsForList().trim((VideoManagerConstants.DEVICE_KEEPALIVE_PREFIX + device.getDeviceId()), -1000, -1);
|
||||||
// redisTemplate.execute(sessionCallback);
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
longRedisTemplate.execute(sessionCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateDeviceRegisterTime(List<Device> deviceList) {
|
public List<Long> getDeviceKeepaliveTimeStamp(String deviceId, Integer count) {
|
||||||
|
if (deviceId == null ) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
if (count == null) {
|
||||||
|
count = 20;
|
||||||
|
}
|
||||||
|
return longRedisTemplate.opsForList().range(VideoManagerConstants.DEVICE_KEEPALIVE_PREFIX + deviceId, 0, count + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateDeviceRegisterTimeStamp(List<Device> deviceList) {
|
||||||
|
if (deviceList == null || deviceList.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 使用 SessionCallback 保证批量操作在同一个连接中执行
|
||||||
|
SessionCallback<Boolean> sessionCallback = new SessionCallback<>() {
|
||||||
|
@Override
|
||||||
|
// 注意:这里直接写死 String, String 覆盖接口的 K, V
|
||||||
|
public Boolean execute(@NonNull RedisOperations operations) {
|
||||||
|
// 1. 批量添加心跳数据到列表尾部
|
||||||
|
for (Device device : deviceList) {
|
||||||
|
operations.opsForList().rightPush(VideoManagerConstants.DEVICE_REGISTER_PREFIX + device.getDeviceId(), device.getRegisterTimeStamp());
|
||||||
|
// 2. 截取列表,只保留最新 100 条
|
||||||
|
operations.opsForList().trim((VideoManagerConstants.DEVICE_REGISTER_PREFIX + device.getDeviceId()), -1000, -1);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
longRedisTemplate.execute(sessionCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Long> getDeviceRegisterTimeStamp(String deviceId, Integer count) {
|
||||||
|
if (deviceId == null ) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
if (count == null) {
|
||||||
|
count = 20;
|
||||||
|
}
|
||||||
|
return longRedisTemplate.opsForList().range(VideoManagerConstants.DEVICE_REGISTER_PREFIX + deviceId, 0, count + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -243,4 +243,24 @@ export function queryDeviceTree(params, deviceId) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
export function getKeepaliveTimeStatistics({ deviceId, count }) {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: '/api/device/query/statistics/keepalive',
|
||||||
|
params: {
|
||||||
|
deviceId: deviceId,
|
||||||
|
count: count
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export function getRegisterTimeStatistics({ deviceId, count }) {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: '/api/device/query/statistics/register',
|
||||||
|
params: {
|
||||||
|
deviceId: deviceId,
|
||||||
|
count: count
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import {
|
|||||||
add,
|
add,
|
||||||
changeChannelAudio,
|
changeChannelAudio,
|
||||||
deleteDevice,
|
deleteDevice,
|
||||||
deviceRecord,
|
deviceRecord, getKeepaliveTimeStatistics, getRegisterTimeStatistics,
|
||||||
queryBasicParam,
|
queryBasicParam,
|
||||||
queryChannelOne,
|
queryChannelOne,
|
||||||
queryChannels,
|
queryChannels,
|
||||||
@ -242,6 +242,26 @@ const actions = {
|
|||||||
reject(error)
|
reject(error)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
getKeepaliveTimeStatistics({ commit }, params) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
getKeepaliveTimeStatistics(params).then(response => {
|
||||||
|
const { data } = response
|
||||||
|
resolve(data)
|
||||||
|
}).catch(error => {
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getRegisterTimeStatistics({ commit }, params) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
getRegisterTimeStatistics(params).then(response => {
|
||||||
|
const { data } = response
|
||||||
|
resolve(data)
|
||||||
|
}).catch(error => {
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: "iconfont"; /* Project id 1291092 */
|
font-family: "iconfont"; /* Project id 1291092 */
|
||||||
src: url('iconfont.woff2?t=1758784486763') format('woff2')
|
src: url('iconfont.woff2?t=1769409737891') format('woff2')
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
@ -11,6 +11,18 @@
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-xintiao:before {
|
||||||
|
content: "\e7f4";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-register:before {
|
||||||
|
content: "\e7f5";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-tongji-Statistics:before {
|
||||||
|
content: "\e7f3";
|
||||||
|
}
|
||||||
|
|
||||||
.icon-mti-duobianxingxuan:before {
|
.icon-mti-duobianxingxuan:before {
|
||||||
content: "\e9e7";
|
content: "\e9e7";
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@ -64,3 +64,12 @@ div:focus {
|
|||||||
.app-container {
|
.app-container {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.iconfont-14 {
|
||||||
|
font-family: "iconfont" !important;
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|||||||
@ -106,6 +106,28 @@
|
|||||||
<!-- <el-checkbox label="报警" disabled :checked="scope.row.subscribeCycleForAlarm > 0"></el-checkbox>-->
|
<!-- <el-checkbox label="报警" disabled :checked="scope.row.subscribeCycleForAlarm > 0"></el-checkbox>-->
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column label="统计" min-width="160">
|
||||||
|
<template v-slot:default="scope">
|
||||||
|
<el-button
|
||||||
|
type="text"
|
||||||
|
size="mini"
|
||||||
|
:disabled="scope.row.online===0"
|
||||||
|
icon="iconfont-14 icon-xintiao"
|
||||||
|
title="心跳时间统计"
|
||||||
|
@click="getKeepaliveTimeStatistics(scope.row.deviceId)"
|
||||||
|
>心跳
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="text"
|
||||||
|
size="mini"
|
||||||
|
:disabled="scope.row.online===0"
|
||||||
|
icon="iconfont-14 icon-register"
|
||||||
|
title="注册时间统计"
|
||||||
|
@click="getRegisterTimeStatistics(scope.row.deviceId)"
|
||||||
|
>注册
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="操作" min-width="300" fixed="right">
|
<el-table-column label="操作" min-width="300" fixed="right">
|
||||||
<template v-slot:default="scope">
|
<template v-slot:default="scope">
|
||||||
<el-button
|
<el-button
|
||||||
@ -163,6 +185,7 @@
|
|||||||
<deviceEdit ref="deviceEdit" />
|
<deviceEdit ref="deviceEdit" />
|
||||||
<syncChannelProgress ref="syncChannelProgress" />
|
<syncChannelProgress ref="syncChannelProgress" />
|
||||||
<configInfo ref="configInfo" />
|
<configInfo ref="configInfo" />
|
||||||
|
<timeStatistics ref="timeStatistics" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -170,6 +193,7 @@
|
|||||||
import deviceEdit from './edit.vue'
|
import deviceEdit from './edit.vue'
|
||||||
import syncChannelProgress from '../dialog/SyncChannelProgress.vue'
|
import syncChannelProgress from '../dialog/SyncChannelProgress.vue'
|
||||||
import configInfo from '../dialog/configInfo.vue'
|
import configInfo from '../dialog/configInfo.vue'
|
||||||
|
import timeStatistics from './timeStatistics.vue'
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -177,7 +201,8 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
configInfo,
|
configInfo,
|
||||||
deviceEdit,
|
deviceEdit,
|
||||||
syncChannelProgress
|
syncChannelProgress,
|
||||||
|
timeStatistics
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -440,6 +465,12 @@ export default {
|
|||||||
message: error.message
|
message: error.message
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
getKeepaliveTimeStatistics: function(deviceId) {
|
||||||
|
this.$refs.timeStatistics.openDialog('心跳时间统计', 'device/getKeepaliveTimeStatistics', deviceId, 10)
|
||||||
|
},
|
||||||
|
getRegisterTimeStatistics: function(deviceId) {
|
||||||
|
this.$refs.timeStatistics.openDialog('注册时间统计', 'device/getRegisterTimeStatistics', deviceId, 10)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
158
web/src/views/device/timeStatistics.vue
Normal file
158
web/src/views/device/timeStatistics.vue
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
<template>
|
||||||
|
<div id="timeStatistics" v-loading="loading">
|
||||||
|
<el-dialog
|
||||||
|
v-el-drag-dialog
|
||||||
|
:title="title"
|
||||||
|
width="60%"
|
||||||
|
top="2rem"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:visible.sync="showDialog"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
@close="close"
|
||||||
|
>
|
||||||
|
<div style="margin-right: 20px;">
|
||||||
|
<el-row type="flex" justify="space-between" align="middle" style="margin-bottom: 12px;">
|
||||||
|
<div>
|
||||||
|
<el-button-group>
|
||||||
|
<el-button type="primary" :plain="viewMode !== 'table'" size="mini" @click="viewMode = 'table'">表格</el-button>
|
||||||
|
<el-button type="primary" :plain="viewMode !== 'chart'" size="mini" @click="viewMode = 'chart'">折线图</el-button>
|
||||||
|
</el-button-group>
|
||||||
|
<el-button icon="el-icon-refresh" size="mini" @click="fetchData" style="margin-left: 8px;">刷新</el-button>
|
||||||
|
</div>
|
||||||
|
<el-form :inline="true" size="mini">
|
||||||
|
<el-form-item label="数量">
|
||||||
|
<el-input-number v-model="count" :min="1" :max="500" @change="fetchData" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-table
|
||||||
|
v-if="viewMode === 'table'"
|
||||||
|
:data="list"
|
||||||
|
border
|
||||||
|
size="mini"
|
||||||
|
height="400px"
|
||||||
|
style="width: 100%;"
|
||||||
|
>
|
||||||
|
<el-table-column prop="time" label="时间" min-width="180" />
|
||||||
|
<el-table-column prop="timeDiff" label="间隔(秒)" min-width="120" />
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<ve-line
|
||||||
|
v-else
|
||||||
|
:data="chartData"
|
||||||
|
:extend="extend"
|
||||||
|
height="400px"
|
||||||
|
:legend-visible="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top: 12px; text-align: right;">
|
||||||
|
<span>最大波动:{{ timeDiffDelta }} 秒</span>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import moment from 'moment/moment'
|
||||||
|
import veLine from 'v-charts/lib/line'
|
||||||
|
import request from '@/utils/request'
|
||||||
|
import elDragDialog from '@/directive/el-drag-dialog'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'TimeStatistics',
|
||||||
|
components: { veLine },
|
||||||
|
directives: { elDragDialog },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
title: null,
|
||||||
|
url: null,
|
||||||
|
deviceId: null,
|
||||||
|
count: 50,
|
||||||
|
showDialog: false,
|
||||||
|
loading: false,
|
||||||
|
viewMode: 'table',
|
||||||
|
list: [],
|
||||||
|
extend: {
|
||||||
|
grid: { right: '30px', containLabel: true },
|
||||||
|
xAxis: {
|
||||||
|
boundaryGap: false,
|
||||||
|
axisLabel: {
|
||||||
|
formatter: (v) => moment(v).format('HH:mm:ss')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
min: 0,
|
||||||
|
splitNumber: 6,
|
||||||
|
axisLabel: { formatter: (v) => `${v} 秒` }
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
formatter: (data) => {
|
||||||
|
if (!data || !data.length) return ''
|
||||||
|
const [item] = data
|
||||||
|
return `${moment(item.data[0]).format('HH:mm:ss')}<br/>间隔:${item.data[1]} 秒`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: {
|
||||||
|
itemStyle: { color: '#409EFF' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
chartData() {
|
||||||
|
return {
|
||||||
|
columns: ['time', 'timeDiff'],
|
||||||
|
rows: this.list
|
||||||
|
}
|
||||||
|
},
|
||||||
|
timeDiffDelta() {
|
||||||
|
if (!this.list.length) return 0
|
||||||
|
const nums = this.list
|
||||||
|
.map(item => Number(item.timeDiff))
|
||||||
|
.filter(v => !Number.isNaN(v))
|
||||||
|
if (!nums.length) return 0
|
||||||
|
const max = Math.max(...nums)
|
||||||
|
const min = Math.min(...nums)
|
||||||
|
return (max - min).toFixed(2)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openDialog(title, url, deviceId, count = 50) {
|
||||||
|
this.title = title
|
||||||
|
this.url = url
|
||||||
|
this.deviceId = deviceId
|
||||||
|
this.count = count
|
||||||
|
this.showDialog = true
|
||||||
|
this.viewMode = 'table'
|
||||||
|
this.fetchData()
|
||||||
|
},
|
||||||
|
fetchData() {
|
||||||
|
console.log(this.url)
|
||||||
|
if (!this.url || !this.deviceId) return
|
||||||
|
this.loading = true
|
||||||
|
this.$store.dispatch(this.url, {
|
||||||
|
deviceId: this.deviceId,
|
||||||
|
count: this.count
|
||||||
|
}).then(data => {
|
||||||
|
this.list = data
|
||||||
|
}).catch((error) => {
|
||||||
|
this.$message.error({
|
||||||
|
showClose: true,
|
||||||
|
message: error.message
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.title = null
|
||||||
|
this.url = null
|
||||||
|
this.deviceId = null
|
||||||
|
this.list = []
|
||||||
|
this.showDialog = false
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
Loading…
Reference in New Issue
Block a user