Merge branch 'master' into 重构/1078

# Conflicts:
#	pom.xml
#	src/main/java/com/genersoft/iot/vmp/conf/SpringDocConfig.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/DeviceChannelServiceImpl.java
#	src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java
This commit is contained in:
648540858
2024-11-10 15:29:53 +08:00
170 changed files with 4812 additions and 3710 deletions

View File

@@ -1,118 +0,0 @@
package com.genersoft.iot.vmp.conf;
import com.genersoft.iot.vmp.common.ApiSaveConstant;
import com.genersoft.iot.vmp.conf.security.SecurityUtils;
import com.genersoft.iot.vmp.service.ILogService;
import com.genersoft.iot.vmp.storager.dao.dto.LogDto;
import com.genersoft.iot.vmp.utils.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author lin
*/
@Slf4j
@WebFilter(filterName = "ApiAccessFilter", urlPatterns = "/api/*", asyncSupported=true)
@Component
public class ApiAccessFilter extends OncePerRequestFilter {
@Autowired
private UserSetting userSetting;
@Autowired
private ILogService logService;
@Override
protected void doFilterInternal(HttpServletRequest servletRequest, HttpServletResponse servletResponse, FilterChain filterChain) throws ServletException, IOException {
String username = null;
if (SecurityUtils.getUserInfo() == null) {
username = servletRequest.getParameter("username");
}else {
username = SecurityUtils.getUserInfo().getUsername();
}
long start = System.currentTimeMillis(); // 请求进入时间
String uriName = ApiSaveConstant.getVal(servletRequest.getRequestURI());
filterChain.doFilter(servletRequest, servletResponse);
if (uriName != null && userSetting != null && userSetting.getLogInDatabase() != null && userSetting.getLogInDatabase()) {
LogDto logDto = new LogDto();
logDto.setName(uriName);
if (ObjectUtils.isEmpty(username)) {
username = "";
}
logDto.setUsername(username);
logDto.setAddress(servletRequest.getRemoteAddr());
logDto.setResult(HttpStatus.valueOf(servletResponse.getStatus()).toString());
logDto.setTiming(System.currentTimeMillis() - start);
logDto.setType(servletRequest.getMethod());
logDto.setUri(servletRequest.getRequestURI());
logDto.setCreateTime(DateUtil.getNow());
logService.add(logDto);
}
}
/**
* 获取IP地址
*
* @param request 请求
* @return request发起客户端的IP地址
*/
private String getIP(HttpServletRequest request) {
if (request == null) {
return "0.0.0.0";
}
String Xip = request.getHeader("X-Real-IP");
String XFor = request.getHeader("X-Forwarded-For");
String UNKNOWN_IP = "unknown";
if (StringUtils.isNotEmpty(XFor) && !UNKNOWN_IP.equalsIgnoreCase(XFor)) {
//多次反向代理后会有多个ip值第一个ip才是真实ip
int index = XFor.indexOf(",");
if (index != -1) {
return XFor.substring(0, index);
} else {
return XFor;
}
}
XFor = Xip;
if (StringUtils.isNotEmpty(XFor) && !UNKNOWN_IP.equalsIgnoreCase(XFor)) {
return XFor;
}
if (StringUtils.isBlank(XFor) || UNKNOWN_IP.equalsIgnoreCase(XFor)) {
XFor = request.getHeader("Proxy-Client-IP");
}
if (StringUtils.isBlank(XFor) || UNKNOWN_IP.equalsIgnoreCase(XFor)) {
XFor = request.getHeader("WL-Proxy-Client-IP");
}
if (StringUtils.isBlank(XFor) || UNKNOWN_IP.equalsIgnoreCase(XFor)) {
XFor = request.getHeader("HTTP_CLIENT_IP");
}
if (StringUtils.isBlank(XFor) || UNKNOWN_IP.equalsIgnoreCase(XFor)) {
XFor = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (StringUtils.isBlank(XFor) || UNKNOWN_IP.equalsIgnoreCase(XFor)) {
XFor = request.getRemoteAddr();
}
return XFor;
}
}

View File

@@ -8,7 +8,7 @@ import org.springframework.scheduling.annotation.Scheduled;
public class MediaStatusTimerTask {
@Scheduled(fixedRate = 2 * 1000) //每3秒执行一次
// @Scheduled(fixedRate = 2 * 1000) //每3秒执行一次
public void execute(){
}

View File

@@ -50,7 +50,7 @@ public class SpringDocConfig {
public GroupedOpenApi publicApi() {
return GroupedOpenApi.builder()
.group("1. 全部")
.packagesToScan("com.genersoft.iot.vmp.vmanager", "com.genersoft.iot.vmp.jt1078.controller")
.packagesToScan("com.genersoft.iot.vmp")
.build();
}
@@ -58,7 +58,7 @@ public class SpringDocConfig {
public GroupedOpenApi publicApi2() {
return GroupedOpenApi.builder()
.group("2. 国标28181")
.packagesToScan("com.genersoft.iot.vmp.vmanager.gb28181")
.packagesToScan("com.genersoft.iot.vmp.gb28181")
.build();
}
@@ -66,7 +66,7 @@ public class SpringDocConfig {
public GroupedOpenApi publicApi3() {
return GroupedOpenApi.builder()
.group("3. 拉流转发")
.packagesToScan("com.genersoft.iot.vmp.vmanager.streamProxy")
.packagesToScan("com.genersoft.iot.vmp.streamProxy")
.build();
}
@@ -74,7 +74,7 @@ public class SpringDocConfig {
public GroupedOpenApi publicApi4() {
return GroupedOpenApi.builder()
.group("4. 推流管理")
.packagesToScan("com.genersoft.iot.vmp.vmanager.streamPush")
.packagesToScan("com.genersoft.iot.vmp.streamPush")
.build();
}
@@ -82,7 +82,7 @@ public class SpringDocConfig {
public GroupedOpenApi publicApi5() {
return GroupedOpenApi.builder()
.group("4. 服务管理")
.packagesToScan("com.genersoft.iot.vmp.vmanager.server")
.packagesToScan("com.genersoft.iot.vmp.server")
.build();
}
@@ -90,7 +90,7 @@ public class SpringDocConfig {
public GroupedOpenApi publicApi6() {
return GroupedOpenApi.builder()
.group("5. 用户管理")
.packagesToScan("com.genersoft.iot.vmp.vmanager.user")
.packagesToScan("com.genersoft.iot.vmp.user")
.build();
}

View File

@@ -1,5 +1,6 @@
package com.genersoft.iot.vmp.conf;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@@ -13,326 +14,160 @@ import java.util.List;
@Component
@ConfigurationProperties(prefix = "user-settings", ignoreInvalidFields = true)
@Order(0)
@Data
public class UserSetting {
/**
* 是否保存位置的历史记录(轨迹)
*/
private Boolean savePositionHistory = Boolean.FALSE;
/**
* 是否开始自动点播: 请求流为未拉起的流时,自动开启点播, 需要rtp.enable=true
*/
private Boolean autoApplyPlay = Boolean.FALSE;
/**
* [可选] 部分设备需要扩展SDP需要打开此设置,一般设备无需打开
*/
private Boolean seniorSdp = Boolean.FALSE;
/**
* 点播/录像回放 等待超时时间,单位:毫秒
*/
private Integer playTimeout = 10000;
/**
* 上级点播等待超时时间,单位:毫秒
*/
private int platformPlayTimeout = 20000;
/**
* 是否开启接口鉴权
*/
private Boolean interfaceAuthentication = Boolean.TRUE;
private Boolean recordPushLive = Boolean.TRUE;
private Boolean recordSip = Boolean.TRUE;
private Boolean logInDatabase = Boolean.FALSE;
private Boolean usePushingAsStatus = Boolean.FALSE;
private Boolean useSourceIpAsStreamIp = Boolean.FALSE;
private Boolean sipUseSourceIpAsRemoteAddress = Boolean.FALSE;
private Boolean streamOnDemand = Boolean.TRUE;
private Boolean pushAuthority = Boolean.TRUE;
private Boolean syncChannelOnDeviceOnline = Boolean.FALSE;
private Boolean sipLog = Boolean.FALSE;
private Boolean sqlLog = Boolean.FALSE;
private Boolean sendToPlatformsWhenIdLost = Boolean.FALSE;
private Boolean refuseChannelStatusChannelFormNotify = Boolean.FALSE;
private Boolean deviceStatusNotify = Boolean.TRUE;
private Boolean useCustomSsrcForParentInvite = Boolean.TRUE;
private Boolean docEnable = Boolean.TRUE;
private String serverId = "000000";
private String thirdPartyGBIdReg = "[\\s\\S]*";
private String broadcastForPlatform = "UDP";
private String civilCodeFile = "classpath:civilCode.csv";
/**
* 接口鉴权例外的接口, 即不进行接口鉴权的接口,尽量详细书写,尽量不用/**,至少两级目录
*/
private List<String> interfaceAuthenticationExcludes = new ArrayList<>();
/**
* 推流直播是否录制
*/
private Boolean recordPushLive = Boolean.TRUE;
/**
* 国标是否录制
*/
private Boolean recordSip = Boolean.TRUE;
/**
* 使用推流状态作为推流通道状态
*/
private Boolean usePushingAsStatus = Boolean.FALSE;
/**
* 使用来源请求ip作为streamIp,当且仅当你只有zlm节点它与wvp在一起的情况下开启
*/
private Boolean useSourceIpAsStreamIp = Boolean.FALSE;
/**
* 是否使用设备来源Ip作为回复IP 不设置则为 false
*/
private Boolean sipUseSourceIpAsRemoteAddress = Boolean.FALSE;
/**
* 国标点播 按需拉流, true有人观看拉流无人观看释放 false拉起后不自动释放
*/
private Boolean streamOnDemand = Boolean.TRUE;
/**
* 推流鉴权, 默认开启
*/
private Boolean pushAuthority = Boolean.TRUE;
/**
* 设备上线时是否自动同步通道
*/
private Boolean syncChannelOnDeviceOnline = Boolean.FALSE;
/**
* 是否开启sip日志
*/
private Boolean sipLog = Boolean.FALSE;
/**
* 是否开启mybatis-sql日志
*/
private Boolean sqlLog = Boolean.FALSE;
/**
* 消息通道功能-缺少国标ID是否给所有上级发送消息
*/
private Boolean sendToPlatformsWhenIdLost = Boolean.FALSE;
/**
* 保持通道状态不接受notify通道状态变化 兼容海康平台发送错误消息
*/
private Boolean refuseChannelStatusChannelFormNotify = Boolean.FALSE;
/**
* 设备/通道状态变化时发送消息
*/
private Boolean deviceStatusNotify = Boolean.TRUE;
/**
* 上级平台点播时不使用上级平台指定的ssrc使用自定义的ssrc参考国标文档-点播外域设备媒体流SSRC处理方式
*/
private Boolean useCustomSsrcForParentInvite = Boolean.TRUE;
/**
* 开启接口文档页面。 默认开启生产环境建议关闭遇到swagger相关的漏洞时也可以关闭
*/
private Boolean docEnable = Boolean.TRUE;
/**
* 服务ID不写则为000000
*/
private String serverId = "000000";
/**
* 国标级联语音喊话发流模式 * UDP:udp传输 TCP-ACTIVEtcp主动模式 TCP-PASSIVEtcp被动模式
*/
private String broadcastForPlatform = "UDP";
/**
* 行政区划信息文件,系统启动时会加载到系统里
*/
private String civilCodeFile = "classpath:civilCode.csv";
/**
* 跨域配置,不配置此项则允许所有跨域请求,配置后则只允许配置的页面的地址请求, 可以配置多个
*/
private List<String> allowedOrigins = new ArrayList<>();
/**
* 设置notify缓存队列最大长度超过此长度的数据将返回486 BUSY_HERE消息丢弃, 默认100000
*/
private int maxNotifyCountQueue = 100000;
/**
* 国标级联离线后多久重试一次注册
*/
private int registerAgainAfterTime = 60;
/**
* 国标续订方式true为续订每次注册在同一个会话里false为重新注册每次使用新的会话
*/
private boolean registerKeepIntDialog = false;
/**
* # 国标设备离线后的上线策略,
* # 0 国标标准实现,设备离线后不回复心跳,直到设备重新注册上线,
* # 1默认 对于离线设备,收到心跳就把设备设置为上线,并更新注册时间为上次这次心跳的时间。防止过期时间判断异常
*/
private int gbDeviceOnline = 1;
public Boolean getSavePositionHistory() {
return savePositionHistory;
}
public Boolean isSavePositionHistory() {
return savePositionHistory;
}
public Boolean isAutoApplyPlay() {
return autoApplyPlay;
}
public Boolean isSeniorSdp() {
return seniorSdp;
}
public Integer getPlayTimeout() {
return playTimeout;
}
public Boolean isInterfaceAuthentication() {
return interfaceAuthentication;
}
public Boolean isRecordPushLive() {
return recordPushLive;
}
public List<String> getInterfaceAuthenticationExcludes() {
return interfaceAuthenticationExcludes;
}
public void setSavePositionHistory(Boolean savePositionHistory) {
this.savePositionHistory = savePositionHistory;
}
public void setAutoApplyPlay(Boolean autoApplyPlay) {
this.autoApplyPlay = autoApplyPlay;
}
public void setSeniorSdp(Boolean seniorSdp) {
this.seniorSdp = seniorSdp;
}
public void setPlayTimeout(Integer playTimeout) {
this.playTimeout = playTimeout;
}
public void setInterfaceAuthentication(boolean interfaceAuthentication) {
this.interfaceAuthentication = interfaceAuthentication;
}
public void setRecordPushLive(Boolean recordPushLive) {
this.recordPushLive = recordPushLive;
}
public void setInterfaceAuthenticationExcludes(List<String> interfaceAuthenticationExcludes) {
this.interfaceAuthenticationExcludes = interfaceAuthenticationExcludes;
}
public Boolean getLogInDatabase() {
return logInDatabase;
}
public void setLogInDatabase(Boolean logInDatabase) {
this.logInDatabase = logInDatabase;
}
public String getServerId() {
return serverId;
}
public void setServerId(String serverId) {
this.serverId = serverId;
}
public String getThirdPartyGBIdReg() {
return thirdPartyGBIdReg;
}
public void setThirdPartyGBIdReg(String thirdPartyGBIdReg) {
this.thirdPartyGBIdReg = thirdPartyGBIdReg;
}
public Boolean getRecordSip() {
return recordSip;
}
public void setRecordSip(Boolean recordSip) {
this.recordSip = recordSip;
}
public int getPlatformPlayTimeout() {
return platformPlayTimeout;
}
public void setPlatformPlayTimeout(int platformPlayTimeout) {
this.platformPlayTimeout = platformPlayTimeout;
}
public Boolean isUsePushingAsStatus() {
return usePushingAsStatus;
}
public void setUsePushingAsStatus(Boolean usePushingAsStatus) {
this.usePushingAsStatus = usePushingAsStatus;
}
public Boolean getStreamOnDemand() {
return streamOnDemand;
}
public void setStreamOnDemand(Boolean streamOnDemand) {
this.streamOnDemand = streamOnDemand;
}
public Boolean getUseSourceIpAsStreamIp() {
return useSourceIpAsStreamIp;
}
public void setUseSourceIpAsStreamIp(Boolean useSourceIpAsStreamIp) {
this.useSourceIpAsStreamIp = useSourceIpAsStreamIp;
}
public Boolean getPushAuthority() {
return pushAuthority;
}
public void setPushAuthority(Boolean pushAuthority) {
this.pushAuthority = pushAuthority;
}
public Boolean getSyncChannelOnDeviceOnline() {
return syncChannelOnDeviceOnline;
}
public void setSyncChannelOnDeviceOnline(Boolean syncChannelOnDeviceOnline) {
this.syncChannelOnDeviceOnline = syncChannelOnDeviceOnline;
}
public String getBroadcastForPlatform() {
return broadcastForPlatform;
}
public void setBroadcastForPlatform(String broadcastForPlatform) {
this.broadcastForPlatform = broadcastForPlatform;
}
public Boolean getSipUseSourceIpAsRemoteAddress() {
return sipUseSourceIpAsRemoteAddress;
}
public void setSipUseSourceIpAsRemoteAddress(Boolean sipUseSourceIpAsRemoteAddress) {
this.sipUseSourceIpAsRemoteAddress = sipUseSourceIpAsRemoteAddress;
}
public Boolean getSipLog() {
return sipLog;
}
public void setSipLog(Boolean sipLog) {
this.sipLog = sipLog;
}
public List<String> getAllowedOrigins() {
return allowedOrigins;
}
public void setAllowedOrigins(List<String> allowedOrigins) {
this.allowedOrigins = allowedOrigins;
}
public Boolean getSendToPlatformsWhenIdLost() {
return sendToPlatformsWhenIdLost;
}
public void setSendToPlatformsWhenIdLost(Boolean sendToPlatformsWhenIdLost) {
this.sendToPlatformsWhenIdLost = sendToPlatformsWhenIdLost;
}
public Boolean getRefuseChannelStatusChannelFormNotify() {
return refuseChannelStatusChannelFormNotify;
}
public void setRefuseChannelStatusChannelFormNotify(Boolean refuseChannelStatusChannelFormNotify) {
this.refuseChannelStatusChannelFormNotify = refuseChannelStatusChannelFormNotify;
}
public int getMaxNotifyCountQueue() {
return maxNotifyCountQueue;
}
public void setMaxNotifyCountQueue(int maxNotifyCountQueue) {
this.maxNotifyCountQueue = maxNotifyCountQueue;
}
public Boolean getDeviceStatusNotify() {
return deviceStatusNotify;
}
public void setDeviceStatusNotify(Boolean deviceStatusNotify) {
this.deviceStatusNotify = deviceStatusNotify;
}
public Boolean getUseCustomSsrcForParentInvite() {
return useCustomSsrcForParentInvite;
}
public void setUseCustomSsrcForParentInvite(Boolean useCustomSsrcForParentInvite) {
this.useCustomSsrcForParentInvite = useCustomSsrcForParentInvite;
}
public Boolean getSqlLog() {
return sqlLog;
}
public void setSqlLog(Boolean sqlLog) {
this.sqlLog = sqlLog;
}
public String getCivilCodeFile() {
return civilCodeFile;
}
public void setCivilCodeFile(String civilCodeFile) {
this.civilCodeFile = civilCodeFile;
}
public int getRegisterAgainAfterTime() {
return registerAgainAfterTime;
}
public void setRegisterAgainAfterTime(int registerAgainAfterTime) {
this.registerAgainAfterTime = registerAgainAfterTime;
}
public boolean isRegisterKeepIntDialog() {
return registerKeepIntDialog;
}
public void setRegisterKeepIntDialog(boolean registerKeepIntDialog) {
this.registerKeepIntDialog = registerKeepIntDialog;
}
public Boolean getDocEnable() {
return docEnable;
}
public void setDocEnable(Boolean docEnable) {
this.docEnable = docEnable;
}
public int getGbDeviceOnline() {
return gbDeviceOnline;
}
public void setGbDeviceOnline(int gbDeviceOnline) {
this.gbDeviceOnline = gbDeviceOnline;
}
}

View File

@@ -19,7 +19,7 @@ public class WVPTimerTask {
@Autowired
private SipConfig sipConfig;
@Scheduled(fixedRate = 2 * 1000) //每3秒执行一次
@Scheduled(fixedDelay = 2 * 1000) //每3秒执行一次
public void execute(){
JSONObject jsonObject = new JSONObject();
jsonObject.put("ip", sipConfig.getShowIp());

View File

@@ -25,6 +25,8 @@ import java.util.ArrayList;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final static String WSHeader = "sec-websocket-protocol";
@Autowired
private UserSetting userSetting;
@@ -44,17 +46,29 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
return;
}
if (!userSetting.isInterfaceAuthentication()) {
if (!userSetting.getInterfaceAuthentication()) {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(null, null, new ArrayList<>() );
SecurityContextHolder.getContext().setAuthentication(token);
chain.doFilter(request, response);
return;
}
String jwt = request.getHeader(JwtUtils.getHeader());
// 这里如果没有jwt继续往后走因为后面还有鉴权管理器等去判断是否拥有身份凭证所以是可以放行的
// 没有jwt相当于匿名访问若有一些接口是需要权限的则不能访问这些接口
// websocket 鉴权信息默认存储在这里
String secWebsocketProtocolHeader = request.getHeader(WSHeader);
if (StringUtils.isBlank(jwt)) {
jwt = request.getParameter(JwtUtils.getHeader());
if (secWebsocketProtocolHeader != null) {
jwt = secWebsocketProtocolHeader;
response.setHeader(WSHeader, secWebsocketProtocolHeader);
}else {
jwt = request.getParameter(JwtUtils.getHeader());
}
if (StringUtils.isBlank(jwt)) {
jwt = request.getHeader(JwtUtils.getApiKeyHeader());
if (StringUtils.isBlank(jwt)) {

View File

@@ -25,6 +25,7 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* 配置Spring Security
@@ -62,7 +63,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
**/
@Override
public void configure(WebSecurity web) {
if (userSetting.isInterfaceAuthentication()) {
if (userSetting.getInterfaceAuthentication()) {
ArrayList<String> matchers = new ArrayList<>();
matchers.add("/");
matchers.add("/#/**");
@@ -104,6 +105,16 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
List<String> defaultExcludes = userSetting.getInterfaceAuthenticationExcludes();
defaultExcludes.add("/api/user/login");
defaultExcludes.add("/index/hook/**");
defaultExcludes.add("/api/device/query/snap/**");
defaultExcludes.add("/index/hook/abl/**");
defaultExcludes.add("/swagger-ui/**");
defaultExcludes.add("/doc.html#/**");
// defaultExcludes.add("/channel/log");
http.headers().contentTypeOptions().disable()
.and().cors().configurationSource(configurationSource())
.and().csrf().disable()
@@ -114,8 +125,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
.and()
.authorizeRequests()
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
.antMatchers(userSetting.getInterfaceAuthenticationExcludes().toArray(new String[0])).permitAll()
.antMatchers("/api/user/login", "/index/hook/**","/index/hook/abl/**", "/swagger-ui/**", "/doc.html#/**").permitAll()
.antMatchers(defaultExcludes.toArray(new String[0])).permitAll()
.anyRequest().authenticated()
// 异常处理器
.and()

View File

@@ -0,0 +1,65 @@
package com.genersoft.iot.vmp.conf.webLog;
import lombok.extern.slf4j.Slf4j;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@ServerEndpoint(value = "/channel/log")
@Slf4j
public class LogChannel {
public static final ConcurrentMap<String, LogChannel> CHANNELS = new ConcurrentHashMap<>();
private Session session;
@OnMessage(maxMessageSize = 1) // MaxMessage 1 byte
public void onMessage(String message) {
try {
this.session.close(new CloseReason(CloseReason.CloseCodes.TOO_BIG, "此节点不接收任何客户端信息"));
} catch (IOException e) {
log.error("[Web-Log] 连接关闭失败: id={}, err={}", this.session.getId(), e.getMessage());
}
}
@OnOpen
public void onOpen(Session session, EndpointConfig endpointConfig) {
this.session = session;
this.session.setMaxIdleTimeout(0);
CHANNELS.put(this.session.getId(), this);
log.info("[Web-Log] 连接已建立: id={}", this.session.getId());
}
@OnClose
public void onClose(CloseReason closeReason) {
log.info("[Web-Log] 连接已断开: id={}, err={}", this.session.getId(), closeReason);
CHANNELS.remove(this.session.getId());
}
@OnError
public void onError(Throwable throwable) throws IOException {
log.info("[Web-Log] 连接错误: id={}, err= {}", this.session.getId(), throwable.getMessage());
if (this.session.isOpen()) {
this.session.close(new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, throwable.getMessage()));
}
}
/**
* Push messages to all clients
*
* @param message
*/
public static void push(String message) {
CHANNELS.values().stream().forEach(endpoint -> {
if (endpoint.session.isOpen()) {
endpoint.session.getAsyncRemote().sendText(message);
}
});
}
}

View File

@@ -0,0 +1,24 @@
package com.genersoft.iot.vmp.conf.webLog;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.nio.charset.StandardCharsets;
@Data
@EqualsAndHashCode(callSuper = true)
public class WebSocketAppender extends AppenderBase<ILoggingEvent> {
private PatternLayoutEncoder encoder;
@Override
protected void append(ILoggingEvent loggingEvent) {
byte[] data = this.encoder.encode(loggingEvent);
// Push to client.
// LogChannel.push(DateUtil.timestampMsTo_yyyy_MM_dd_HH_mm_ss(loggingEvent.getTimeStamp()) + " " + loggingEvent.getFormattedMessage());
LogChannel.push(new String(data, StandardCharsets.UTF_8));
}
}

View File

@@ -0,0 +1,19 @@
package com.genersoft.iot.vmp.conf.websocket;
import com.genersoft.iot.vmp.conf.webLog.LogChannel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
ServerEndpointExporter endpointExporter = new ServerEndpointExporter();
endpointExporter.setAnnotatedEndpointClasses(LogChannel.class);
return endpointExporter;
}
}