20251220:授权码(TCP校验)& GB28181

This commit is contained in:
George 2025-12-20 23:32:55 +08:00
parent c26f430924
commit 2c44850c38
19 changed files with 313 additions and 151 deletions

View File

@ -18,11 +18,11 @@
<dependencies> <dependencies>
<!-- spring-boot-devtools --> <!-- spring-boot-devtools -->
<dependency> <!-- <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId> <artifactId>spring-boot-devtools</artifactId>
<optional>true</optional> <!-- 表示依赖不会传递 --> <optional>true</optional> &lt;!&ndash; 表示依赖不会传递 &ndash;&gt;
</dependency> </dependency>-->
<!-- swagger3--> <!-- swagger3-->
<dependency> <dependency>

View File

@ -127,10 +127,10 @@ emqx:
ApiSecret: emqx.api.123 ApiSecret: emqx.api.123
# sip 配置 # sip 配置
sip: sip:
enabled: false # 是否启用视频监控SIP,true为启用 enabled: true # 是否启用视频监控SIP,true为启用
## 本地调试时,绑定网卡局域网IP,设备在同一局域网,设备接入IP填写绑定IP ## 本地调试时,绑定网卡局域网IP,设备在同一局域网,设备接入IP填写绑定IP
## 部署服务端时,默认绑定容器IP,设备接入IP填写服务器公网IP ## 部署服务端时,默认绑定容器IP,设备接入IP填写服务器公网IP
ip: localhost ip: 192.168.110.78 # 本服务启动的 IP
port: 5061 # SIP端口(保持默认) port: 5061 # SIP端口(保持默认)
domain: 3402000000 # 由省级、市级、区级、基层编号组成 domain: 3402000000 # 由省级、市级、区级、基层编号组成
id: 34020000002000000001 # 同上,另外增加编号,(可保持默认) id: 34020000002000000001 # 同上,另外增加编号,(可保持默认)

View File

@ -129,7 +129,7 @@ sip:
enabled: true # 是否启用视频监控SIP,true为启用 enabled: true # 是否启用视频监控SIP,true为启用
## 本地调试时,绑定网卡局域网IP,设备在同一局域网,设备接入IP填写绑定IP ## 本地调试时,绑定网卡局域网IP,设备在同一局域网,设备接入IP填写绑定IP
## 部署服务端时,默认绑定容器IP,设备接入IP填写服务器公网IP ## 部署服务端时,默认绑定容器IP,设备接入IP填写服务器公网IP
ip: 177.7.0.13 ip: 177.7.0.13 # 容器部署容器IP:177.7.0.13;云服务器部署写公网IP;本地部署127.0.0.1;总原则:能被摄像机访问的地址
port: 5061 # SIP端口(保持默认) port: 5061 # SIP端口(保持默认)
domain: 3402000000 # 由省级、市级、区级、基层编号组成 domain: 3402000000 # 由省级、市级、区级、基层编号组成
id: 34020000002000000001 # 同上,另外增加编号,(可保持默认) id: 34020000002000000001 # 同上,另外增加编号,(可保持默认)

View File

@ -66,9 +66,9 @@ spring:
max-file-size: 10MB # 单个文件大小 max-file-size: 10MB # 单个文件大小
max-request-size: 20MB # 设置总上传的文件大小 max-request-size: 20MB # 设置总上传的文件大小
# 服务模块 # 服务模块
devtools: # devtools:
restart: # restart:
enabled: true # 热部署开关 # enabled: true # 热部署开关
task: task:
execution: execution:
pool: pool:

View File

@ -2,8 +2,8 @@
<configuration> <configuration>
<!-- 日志存放路径 --> <!-- 日志存放路径 -->
<property name="log.path" scope="context" value="./logs"/> <property name="log.path" scope="context" value="./logs"/>
<!-- 日志输出格式 --> <!-- 日志输出格式,添加TraceId -->
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %class - [%method,%line] - %msg%n"/> <property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] [%X{traceId}] %-5level %class - [%method,%line] - %msg%n"/>
<!-- 控制台输出 --> <!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <appender name="console" class="ch.qos.logback.core.ConsoleAppender">

View File

@ -0,0 +1,108 @@
package com.fastbee.common.utils;
import com.fastbee.common.utils.uuid.IdUtils;
import org.slf4j.MDC;
import java.util.concurrent.ThreadLocalRandom;
/**
* 日志追踪ID工具类
* 统一生成TraceId的逻辑,使用IdWorker和ThreadLocalRandom
*
* @author fastbee
*/
public class TraceIdUtils {
/**
* TraceId在MDC中的key
*/
public static final String TRACE_ID_KEY = "traceId";
/**
* IdWorker实例(单例模式)
*/
private static volatile IdUtils idWorker;
/**
* 获取IdWorker实例(懒加载单例)
*
* @return IdWorker实例
*/
private static IdUtils getIdWorker() {
if (idWorker == null) {
synchronized (TraceIdUtils.class) {
if (idWorker == null) {
// 使用机器ID,可以根据实际情况配置
// 这里使用1作为默认workerId,可以通过配置文件读取
// IdUtils构造函数:workerId, datacenterId,-1表示随机生成
idWorker = new IdUtils(-1L, -1L);
}
}
}
return idWorker;
}
/**
* 生成TraceId
* 格式:IdWorker.nextId() * 1000 + ThreadLocalRandom.nextInt(1000)
* 参考:generateClueId() 的实现方式
* 使用Math.abs确保TraceId为正数
*
* @return TraceId
*/
public static long generateTraceId() {
long id = getIdWorker().nextId();
// 使用绝对值确保TraceId为正数,避免负数显示
long traceId = Math.abs(id) * 1000 + ThreadLocalRandom.current().nextInt(1000);
MDC.put(TRACE_ID_KEY, String.valueOf(traceId));
return traceId;
}
/**
* 生成TraceId并放入MDC
* 返回字符串格式的TraceId
*
* @return TraceId字符串
*/
public static String generateTraceIdString() {
long traceId = generateTraceId();
return String.valueOf(traceId);
}
/**
* 设置TraceId到MDC
*
* @param traceId TraceId
*/
public static void setTraceId(String traceId) {
if (traceId != null && !traceId.isEmpty()) {
MDC.put(TRACE_ID_KEY, traceId);
}
}
/**
* 设置TraceId到MDC(long类型)
*
* @param traceId TraceId
*/
public static void setTraceId(long traceId) {
MDC.put(TRACE_ID_KEY, String.valueOf(traceId));
}
/**
* 从MDC获取TraceId
*
* @return TraceId
*/
public static String getTraceId() {
return MDC.get(TRACE_ID_KEY);
}
/**
* 清除MDC中的TraceId
*/
public static void clearTraceId() {
MDC.remove(TRACE_ID_KEY);
}
}

View File

@ -3,6 +3,7 @@ package com.fastbee.framework.config;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import com.fastbee.framework.interceptor.LanguageInterceptor; import com.fastbee.framework.interceptor.LanguageInterceptor;
import com.fastbee.framework.interceptor.TraceIdInterceptor;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -31,6 +32,8 @@ public class ResourcesConfig implements WebMvcConfigurer
private RepeatSubmitInterceptor repeatSubmitInterceptor; private RepeatSubmitInterceptor repeatSubmitInterceptor;
@Resource @Resource
private LanguageInterceptor languageInterceptor; private LanguageInterceptor languageInterceptor;
@Autowired
private TraceIdInterceptor traceIdInterceptor;
@Override @Override
public void addResourceHandlers(ResourceHandlerRegistry registry) public void addResourceHandlers(ResourceHandlerRegistry registry)
@ -51,6 +54,8 @@ public class ResourcesConfig implements WebMvcConfigurer
@Override @Override
public void addInterceptors(InterceptorRegistry registry) public void addInterceptors(InterceptorRegistry registry)
{ {
// TraceId拦截器放在最前面,确保所有请求都有TraceId
registry.addInterceptor(traceIdInterceptor).addPathPatterns("/**").order(0);
registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**"); registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
//这里配置国际化拦截器的白名单 //这里配置国际化拦截器的白名单
registry.addInterceptor(languageInterceptor).addPathPatterns("/**").excludePathPatterns("/v2/api-docs", registry.addInterceptor(languageInterceptor).addPathPatterns("/**").excludePathPatterns("/v2/api-docs",

View File

@ -0,0 +1,51 @@
package com.fastbee.framework.interceptor;
import com.fastbee.common.utils.TraceIdUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 日志追踪ID拦截器
* 用于生成和传递TraceId,方便同一个链路调用追查问题
*
* @author fastbee
*/
@Component
public class TraceIdInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取请求URI
String requestURI = request.getRequestURI();
// 尝试从请求头中获取TraceId(支持分布式追踪)
String traceId = request.getHeader("X-Trace-Id");
// 如果请求头中没有TraceId,则生成一个新的
if (traceId == null || traceId.isEmpty()) {
traceId = TraceIdUtils.generateTraceIdString();
} else {
// 如果请求头中有TraceId,设置到MDC
TraceIdUtils.setTraceId(traceId);
}
// 将TraceId放入响应头,方便前端获取
response.setHeader("X-Trace-Id", traceId);
// 打印请求路径日志
org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TraceIdInterceptor.class);
log.info("拦截请求路径: {}", requestURI);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 请求完成后清除MDC中的TraceId,避免内存泄漏
TraceIdUtils.clearTraceId();
}
}

View File

@ -16,6 +16,9 @@ import com.fastbee.iot.domain.Device;
import com.fastbee.iot.domain.DeviceLog; import com.fastbee.iot.domain.DeviceLog;
import com.fastbee.iot.service.IDeviceService; import com.fastbee.iot.service.IDeviceService;
import com.fastbee.iot.service.IProductService; import com.fastbee.iot.service.IProductService;
import com.fastbee.iot.service.IProductAuthorizeService;
import com.fastbee.iot.domain.ProductAuthorize;
import com.fastbee.iot.domain.Product;
import com.fastbee.iot.tdengine.service.ILogService; import com.fastbee.iot.tdengine.service.ILogService;
import com.fastbee.mqtt.manager.MqttRemoteManager; import com.fastbee.mqtt.manager.MqttRemoteManager;
import com.fastbee.mqtt.model.PushMessageBo; import com.fastbee.mqtt.model.PushMessageBo;
@ -33,6 +36,7 @@ import javax.annotation.Resource;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Objects;
/** /**
* 设备调试错误日志AOP切面 * 设备调试错误日志AOP切面
@ -63,6 +67,9 @@ public class DeviceDebugLogAspect {
@Resource @Resource
private IProductService productService; private IProductService productService;
@Resource
private IProductAuthorizeService productAuthorizeService;
@Resource @Resource
private MqttRemoteManager mqttRemoteManager; private MqttRemoteManager mqttRemoteManager;
@ -127,8 +134,7 @@ public class DeviceDebugLogAspect {
String protocolCode = bo.getProtocolCode(); String protocolCode = bo.getProtocolCode();
Long productId = bo.getProductId(); Long productId = bo.getProductId();
saveErrorLog(serialNumber, sourceData, errorMsg, protocolCode, bo.getTopicName(), saveErrorLog(serialNumber, sourceData, errorMsg, DeviceLogTypeEnum.DATA_PARSE_ERROR.getType(), "DEBUG_ERROR", productId);
DeviceLogTypeEnum.DATA_PARSE_ERROR.getType(), "DEBUG_ERROR", productId);
} }
} catch (Exception ex) { } catch (Exception ex) {
log.error("记录数据解析错误日志失败: {}", ex.getMessage(), ex); log.error("记录数据解析错误日志失败: {}", ex.getMessage(), ex);
@ -186,7 +192,7 @@ public class DeviceDebugLogAspect {
String protocolCode = getProtocolCodeBySerialNumber(serialNumber); String protocolCode = getProtocolCodeBySerialNumber(serialNumber);
Long productId = getProductIdBySerialNumber(serialNumber); Long productId = getProductIdBySerialNumber(serialNumber);
saveErrorLog(serialNumber, sourceData, errorMsg, protocolCode, deviceMessage.getTopicName(), saveErrorLog(serialNumber, sourceData, errorMsg,
DeviceLogTypeEnum.COMMAND_SEND_ERROR.getType(), "SEND_ERROR", productId); DeviceLogTypeEnum.COMMAND_SEND_ERROR.getType(), "SEND_ERROR", productId);
} }
} catch (Exception ex) { } catch (Exception ex) {
@ -242,7 +248,7 @@ public class DeviceDebugLogAspect {
} }
// 协议解析错误也归类为数据解析错误 // 协议解析错误也归类为数据解析错误
saveErrorLog(serialNumber, sourceData, errorMsg, protocolCode, deviceData.getTopicName(), saveErrorLog(serialNumber, sourceData, errorMsg,
DeviceLogTypeEnum.DATA_PARSE_ERROR.getType(), "PARSE_ERROR", productId); DeviceLogTypeEnum.DATA_PARSE_ERROR.getType(), "PARSE_ERROR", productId);
} }
} catch (Exception ex) { } catch (Exception ex) {
@ -299,7 +305,7 @@ public class DeviceDebugLogAspect {
* 保存错误日志到iot_device_log表,并推送到前端页面 * 保存错误日志到iot_device_log表,并推送到前端页面
*/ */
private void saveErrorLog(String serialNumber, String sourceData, String errorMsg, private void saveErrorLog(String serialNumber, String sourceData, String errorMsg,
String protocolCode, String topicName, Integer logType, String identity, Long productId) { Integer logType, String identity, Long productId) {
try { try {
if (StringUtils.isEmpty(serialNumber)) { if (StringUtils.isEmpty(serialNumber)) {
log.warn("设备编号为空,无法保存错误日志"); log.warn("设备编号为空,无法保存错误日志");
@ -327,6 +333,27 @@ public class DeviceDebugLogAspect {
productId = getProductIdBySerialNumber(serialNumber); productId = getProductIdBySerialNumber(serialNumber);
} }
// 授权码校验:如果产品开启了授权码,但设备未绑定授权码,不记录日志也不推送消息
if (productId != null) {
try {
Product product = productService.selectProductByProductId(productId);
if (product != null && product.getIsAuthorize() != null && product.getIsAuthorize() == 1) {
// 产品开启了授权码,检查设备是否绑定了授权码
ProductAuthorize queryParam = new ProductAuthorize();
queryParam.setProductId(productId);
queryParam.setSerialNumber(serialNumber);
java.util.List<ProductAuthorize> authorizeList = productAuthorizeService.selectProductAuthorizeList(queryParam);
if (authorizeList == null || authorizeList.isEmpty()) {
log.debug("设备[{}]未绑定授权码,跳过错误日志记录和消息推送", serialNumber);
return; // 不记录日志,也不推送消息
}
}
} catch (Exception ex) {
log.warn("授权码校验失败: {}, 设备编号: {}, 继续记录日志", ex.getMessage(), serialNumber);
// 校验失败时继续记录日志,避免影响正常功能
}
}
DeviceLog deviceLog = new DeviceLog(); DeviceLog deviceLog = new DeviceLog();
deviceLog.setSerialNumber(serialNumber); deviceLog.setSerialNumber(serialNumber);
deviceLog.setDeviceId(device.getDeviceId()); deviceLog.setDeviceId(device.getDeviceId());
@ -337,7 +364,7 @@ public class DeviceDebugLogAspect {
// 存储错误信息(限制500字符) // 存储错误信息(限制500字符)
deviceLog.setRemark(StringUtils.substring(errorMsg, 0, 500)); deviceLog.setRemark(StringUtils.substring(errorMsg, 0, 500));
deviceLog.setIdentity(identity); deviceLog.setIdentity(identity);
deviceLog.setModelName(logType == DeviceLogTypeEnum.DATA_PARSE_ERROR.getType() ? deviceLog.setModelName(Objects.equals(logType, DeviceLogTypeEnum.DATA_PARSE_ERROR.getType()) ?
"数据解析错误" : "指令发送错误"); "数据解析错误" : "指令发送错误");
deviceLog.setCreateTime(DateUtils.getNowDate()); deviceLog.setCreateTime(DateUtils.getNowDate());
deviceLog.setUserId(device.getTenantId()); deviceLog.setUserId(device.getTenantId());
@ -374,7 +401,7 @@ public class DeviceDebugLogAspect {
List<ThingsModelSimpleItem> errorItems = new ArrayList<>(); List<ThingsModelSimpleItem> errorItems = new ArrayList<>();
ThingsModelSimpleItem errorItem = new ThingsModelSimpleItem(); ThingsModelSimpleItem errorItem = new ThingsModelSimpleItem();
errorItem.setId("ERROR_" + identity); errorItem.setId("ERROR_" + identity);
errorItem.setName(logType == DeviceLogTypeEnum.DATA_PARSE_ERROR.getType() ? "数据解析错误" : "指令发送错误"); errorItem.setName(Objects.equals(logType, DeviceLogTypeEnum.DATA_PARSE_ERROR.getType()) ? "数据解析错误" : "指令发送错误");
errorItem.setValue(errorMsg); errorItem.setValue(errorMsg);
errorItem.setRemark(errorMsg); errorItem.setRemark(errorMsg);
errorItem.setTs(new Date()); errorItem.setTs(new Date());

View File

@ -9,6 +9,7 @@ import com.fastbee.common.utils.spring.SpringUtils;
import com.fastbee.iot.domain.DeviceJob; import com.fastbee.iot.domain.DeviceJob;
import com.fastbee.quartz.domain.SysJobLog; import com.fastbee.quartz.domain.SysJobLog;
import com.fastbee.quartz.service.ISysJobLogService; import com.fastbee.quartz.service.ISysJobLogService;
import com.fastbee.common.utils.TraceIdUtils;
import org.quartz.Job; import org.quartz.Job;
import org.quartz.JobExecutionContext; import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException; import org.quartz.JobExecutionException;
@ -61,6 +62,11 @@ public abstract class DeviceAbstractQuartzJob implements Job
protected void before(JobExecutionContext context, DeviceJob deviceJob) protected void before(JobExecutionContext context, DeviceJob deviceJob)
{ {
threadLocal.set(new Date()); threadLocal.set(new Date());
// 为设备定时任务生成TraceId,使用统一的生成逻辑
TraceIdUtils.generateTraceId();
log.info("设备定时任务开始执行: {} - {}", deviceJob.getJobName(), deviceJob.getJobGroup());
} }
/** /**
@ -110,6 +116,11 @@ public abstract class DeviceAbstractQuartzJob implements Job
// 写入数据库当中 // 写入数据库当中
SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog); SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog);
// 清除TraceId,避免内存泄漏
TraceIdUtils.clearTraceId();
log.info("设备定时任务执行完成: {} - {}, 耗时: {}ms", deviceJob.getJobName(), deviceJob.getJobGroup(), runMs);
} }
/** /**

View File

@ -27,6 +27,9 @@ import com.fastbee.iot.model.DeviceStatusVO;
import com.fastbee.iot.service.IDeviceService; import com.fastbee.iot.service.IDeviceService;
import com.fastbee.iot.service.IFunctionLogService; import com.fastbee.iot.service.IFunctionLogService;
import com.fastbee.iot.service.IProductService; import com.fastbee.iot.service.IProductService;
import com.fastbee.iot.service.IProductAuthorizeService;
import com.fastbee.iot.domain.ProductAuthorize;
import com.fastbee.iot.domain.Product;
import com.fastbee.mq.producer.MessageProducer; import com.fastbee.mq.producer.MessageProducer;
import com.fastbee.mqtt.manager.MqttRemoteManager; import com.fastbee.mqtt.manager.MqttRemoteManager;
import com.fastbee.mqtt.model.PushMessageBo; import com.fastbee.mqtt.model.PushMessageBo;
@ -41,10 +44,7 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.HashMap; import java.util.*;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
/** /**
* 处理类 处理设备主动上报和设备回调信息 * 处理类 处理设备主动上报和设备回调信息
@ -60,6 +60,8 @@ public class DeviceReportMessageServiceImpl implements IDeviceReportMessageServi
@Autowired @Autowired
private IProductService productService; private IProductService productService;
@Autowired @Autowired
private IProductAuthorizeService productAuthorizeService;
@Autowired
private IProtocolManagerService protocolManagerService; private IProtocolManagerService protocolManagerService;
@Autowired @Autowired
private IFirmwareTaskDetailService firmwareTaskDetailService; private IFirmwareTaskDetailService firmwareTaskDetailService;
@ -120,6 +122,23 @@ public class DeviceReportMessageServiceImpl implements IDeviceReportMessageServi
log.debug("*MQ收到TCP推送消息[{}]", JSON.toJSON(bo.getThingsModelSimpleItem())); log.debug("*MQ收到TCP推送消息[{}]", JSON.toJSON(bo.getThingsModelSimpleItem()));
DeviceStatusVO deviceStatusVO1 = deviceService.selectDeviceStatusAndTransportStatus(serialNumber); DeviceStatusVO deviceStatusVO1 = deviceService.selectDeviceStatusAndTransportStatus(serialNumber);
Optional.ofNullable(deviceStatusVO1).orElseThrow(() -> new ServiceException("设备不存在")); Optional.ofNullable(deviceStatusVO1).orElseThrow(() -> new ServiceException("设备不存在"));
// TCP协议授权码校验
productId = deviceStatusVO1.getProductId();
Product product = productService.selectProductByProductId(productId);
if (product != null && product.getIsAuthorize() != null && product.getIsAuthorize() == 1) {
// 产品开启了授权码,检查设备是否绑定了授权码
ProductAuthorize queryParam = new ProductAuthorize();
queryParam.setProductId(productId);
queryParam.setSerialNumber(serialNumber);
List<ProductAuthorize> authorizeList = productAuthorizeService.selectProductAuthorizeList(queryParam);
if (authorizeList == null || authorizeList.isEmpty()) {
log.warn("TCP数据上报-授权码校验失败: 设备[{}]未绑定授权码,拒绝数据上报", serialNumber);
return; // 拒绝数据上报
}
log.debug("TCP数据上报-授权码校验通过: 设备[{}]已绑定授权码", serialNumber);
}
//同步设备状态 //同步设备状态
this.synchDeviceStatus(serialNumber); this.synchDeviceStatus(serialNumber);
DeviceReport deviceReport = new DeviceReport(); DeviceReport deviceReport = new DeviceReport();

View File

@ -4,6 +4,7 @@ import com.fastbee.common.constant.Constants;
import com.fastbee.common.constant.ScheduleConstants; import com.fastbee.common.constant.ScheduleConstants;
import com.fastbee.common.utils.ExceptionUtil; import com.fastbee.common.utils.ExceptionUtil;
import com.fastbee.common.utils.StringUtils; import com.fastbee.common.utils.StringUtils;
import com.fastbee.common.utils.TraceIdUtils;
import com.fastbee.common.utils.bean.BeanUtils; import com.fastbee.common.utils.bean.BeanUtils;
import com.fastbee.common.utils.spring.SpringUtils; import com.fastbee.common.utils.spring.SpringUtils;
import com.fastbee.quartz.domain.SysJob; import com.fastbee.quartz.domain.SysJob;
@ -61,6 +62,11 @@ public abstract class AbstractQuartzJob implements Job
protected void before(JobExecutionContext context, SysJob sysJob) protected void before(JobExecutionContext context, SysJob sysJob)
{ {
threadLocal.set(new Date()); threadLocal.set(new Date());
// 为定时任务生成TraceId,使用统一的生成逻辑
TraceIdUtils.generateTraceId();
log.info("定时任务开始执行: {} - {}", sysJob.getJobName(), sysJob.getJobGroup());
} }
/** /**
@ -95,6 +101,11 @@ public abstract class AbstractQuartzJob implements Job
// 写入数据库当中 // 写入数据库当中
SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog); SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog);
// 清除TraceId,避免内存泄漏
TraceIdUtils.clearTraceId();
log.info("定时任务执行完成: {} - {}, 耗时: {}ms", sysJob.getJobName(), sysJob.getJobGroup(), runMs);
} }
/** /**

View File

@ -1,22 +1,29 @@
spring: spring:
# REDIS数据库配置 # REDIS数据库配置
redis: redis:
# [可选] 超时时间
timeout: 10000
# 以下为单机配置
# [必须修改] Redis服务器IP, REDIS安装在本机的,使用127.0.0.1 # [必须修改] Redis服务器IP, REDIS安装在本机的,使用127.0.0.1
host: 127.0.0.1 host: redis
# [必须修改] 端口号 # [必须修改] 端口号
port: 6379 port: 6379
# [可选] 数据库 DB # [可选] 数据库 DB
database: 8 database: 0
# [可选] 访问密码,若你的redis服务器没有设置密码,就不需要用密码去连接 # [可选] 访问密码,若你的redis服务器没有设置密码,就不需要用密码去连接
password: password: fastbee
# [可选] 超时时间 # 以下为集群配置
timeout: 10000 # cluster:
# nodes: 192.168.1.242:7001
# password: 4767cb971b40a1300fa09b7f87b09d1c
# [可选] 监听的HTTP端口, 网页和接口调用都是这个端口 # [可选] 监听的HTTP端口, 网页和接口调用都是这个端口
# 您需要使用实际的证书名称替换domain_name.jks # 您需要使用实际的证书名称替换domain_name.jks
# 证书获取参考文档:https://help.aliyun.com/zh/ssl-certificate/user-guide/enable-https-on-spring-boot # 证书获取参考文档:https://help.aliyun.com/zh/ssl-certificate/user-guide/enable-https-on-spring-boot
server: server:
port: 18081 port: 18081
servlet:
context-path: / # 应用的访问路径
# HTTPS配置, 默认不开启 # HTTPS配置, 默认不开启
ssl: ssl:
# 是否开启HTTPS访问 默认关闭 # 是否开启HTTPS访问 默认关闭
@ -31,7 +38,9 @@ server:
# [根据业务需求配置] # [根据业务需求配置]
userSettings: userSettings:
# [可选 ] zlm配置的录像路径, # [必选 ] 服务ID
id: 334533
# [必选 ] zlm配置的录像路径
record: /opt/media/bin/www/record record: /opt/media/bin/www/record
# [可选 ] 录像保存时长(单位: 天)每天晚12点自动对过期文件执行清理 # [可选 ] 录像保存时长(单位: 天)每天晚12点自动对过期文件执行清理
recordDay: 7 recordDay: 7
@ -44,6 +53,12 @@ userSettings:
# [可选 ] 限制 ffmpeg 合并文件使用的线程数,间接限制cpu使用率, 默认2 限制到50% # [可选 ] 限制 ffmpeg 合并文件使用的线程数,间接限制cpu使用率, 默认2 限制到50%
threads: 2 threads: 2
# MyBatis配置
mybatis:
typeAliasesPackage: com.fastbee.oss.domain # 搜索指定包别名
mapperLocations: classpath*:mapper/**/*Mapper.xml # 配置mapper的扫描,找到所有的mapper.xml映射文件
configLocation: classpath:mybatis/mybatis-config.xml # 加载全局的配置文件
swagger-ui: swagger-ui:
# [可选] 日志配置, 一般不需要改 # [可选] 日志配置, 一般不需要改

View File

@ -41,15 +41,15 @@ userSettings:
# [必选 ] 服务ID # [必选 ] 服务ID
id: 334533 id: 334533
# [必选 ] zlm配置的录像路径 # [必选 ] zlm配置的录像路径
record: D:\soft\ZLMediaKit\www\record record: D:\fastbee\ZLMediaKit\www\record
# [可选 ] 录像保存时长(单位: 天)每天晚12点自动对过期文件执行清理 # [可选 ] 录像保存时长(单位: 天)每天晚12点自动对过期文件执行清理
recordDay: 7 recordDay: 7
# [可选 ] 录像下载合成临时文件保存时长, 不配置默认取值recordDay(单位: 天)每天晚12点自动对过期文件执行清理 # [可选 ] 录像下载合成临时文件保存时长, 不配置默认取值recordDay(单位: 天)每天晚12点自动对过期文件执行清理
# recordTempDay: 7 # recordTempDay: 7
# [必选 ] ffmpeg路径 # [必选 ] ffmpeg路径
ffmpeg: D:\soft\ZLMediaKit\bin\ffmpeg ffmpeg: D:\fastbee\ZLMediaKit\ffmpeg-8.0.1-essentials_build\bin\ffmpeg
# [必选 ] ffprobe路径, 一般安装ffmpeg就会自带, 一般跟ffmpeg在同一目录,用于查询文件的信息 # [必选 ] ffprobe路径, 一般安装ffmpeg就会自带, 一般跟ffmpeg在同一目录,用于查询文件的信息
ffprobe: D:\soft\ZLMediaKit\bin\ffprobe ffprobe: D:\fastbee\ZLMediaKit\ffmpeg-8.0.1-essentials_build\bin\ffprobe
# [可选 ] 限制 ffmpeg 合并文件使用的线程数,间接限制cpu使用率, 默认2 限制到50% # [可选 ] 限制 ffmpeg 合并文件使用的线程数,间接限制cpu使用率, 默认2 限制到50%
threads: 2 threads: 2

View File

@ -23,8 +23,7 @@ import java.util.List;
* @author ruoyi * @author ruoyi
*/ */
@Service @Service
public class SysConfigServiceImpl implements ISysConfigService public class SysConfigServiceImpl implements ISysConfigService {
{
@Autowired @Autowired
private SysConfigMapper configMapper; private SysConfigMapper configMapper;
@ -35,8 +34,7 @@ public class SysConfigServiceImpl implements ISysConfigService
* 项目启动时,初始化参数到缓存 * 项目启动时,初始化参数到缓存
*/ */
@PostConstruct @PostConstruct
public void init() public void init() {
{
loadingConfigCache(); loadingConfigCache();
} }
@ -48,8 +46,7 @@ public class SysConfigServiceImpl implements ISysConfigService
*/ */
@Override @Override
@DS("master") @DS("master")
public SysConfig selectConfigById(Long configId) public SysConfig selectConfigById(Long configId) {
{
SysConfig config = new SysConfig(); SysConfig config = new SysConfig();
config.setConfigId(configId); config.setConfigId(configId);
return configMapper.selectConfig(config); return configMapper.selectConfig(config);
@ -62,18 +59,15 @@ public class SysConfigServiceImpl implements ISysConfigService
* @return 参数键值 * @return 参数键值
*/ */
@Override @Override
public String selectConfigByKey(String configKey) public String selectConfigByKey(String configKey) {
{
String configValue = Convert.toStr(redisCache.getCacheObject(getCacheKey(configKey))); String configValue = Convert.toStr(redisCache.getCacheObject(getCacheKey(configKey)));
if (StringUtils.isNotEmpty(configValue)) if (StringUtils.isNotEmpty(configValue)) {
{
return configValue; return configValue;
} }
SysConfig config = new SysConfig(); SysConfig config = new SysConfig();
config.setConfigKey(configKey); config.setConfigKey(configKey);
SysConfig retConfig = configMapper.selectConfig(config); SysConfig retConfig = configMapper.selectConfig(config);
if (StringUtils.isNotNull(retConfig)) if (StringUtils.isNotNull(retConfig)) {
{
redisCache.setCacheObject(getCacheKey(configKey), retConfig.getConfigValue()); redisCache.setCacheObject(getCacheKey(configKey), retConfig.getConfigValue());
return retConfig.getConfigValue(); return retConfig.getConfigValue();
} }
@ -86,11 +80,9 @@ public class SysConfigServiceImpl implements ISysConfigService
* @return true开启,false关闭 * @return true开启,false关闭
*/ */
@Override @Override
public boolean selectCaptchaEnabled() public boolean selectCaptchaEnabled() {
{
String captchaEnabled = selectConfigByKey("sys.account.captchaEnabled"); String captchaEnabled = selectConfigByKey("sys.account.captchaEnabled");
if (StringUtils.isEmpty(captchaEnabled)) if (StringUtils.isEmpty(captchaEnabled)) {
{
return true; return true;
} }
return Convert.toBool(captchaEnabled); return Convert.toBool(captchaEnabled);
@ -103,8 +95,7 @@ public class SysConfigServiceImpl implements ISysConfigService
* @return 参数配置集合 * @return 参数配置集合
*/ */
@Override @Override
public List<SysConfig> selectConfigList(SysConfig config) public List<SysConfig> selectConfigList(SysConfig config) {
{
return configMapper.selectConfigList(config); return configMapper.selectConfigList(config);
} }
@ -115,11 +106,9 @@ public class SysConfigServiceImpl implements ISysConfigService
* @return 结果 * @return 结果
*/ */
@Override @Override
public int insertConfig(SysConfig config) public int insertConfig(SysConfig config) {
{
int row = configMapper.insertConfig(config); int row = configMapper.insertConfig(config);
if (row > 0) if (row > 0) {
{
redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue()); redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue());
} }
return row; return row;
@ -132,17 +121,14 @@ public class SysConfigServiceImpl implements ISysConfigService
* @return 结果 * @return 结果
*/ */
@Override @Override
public int updateConfig(SysConfig config) public int updateConfig(SysConfig config) {
{
SysConfig temp = configMapper.selectConfigById(config.getConfigId()); SysConfig temp = configMapper.selectConfigById(config.getConfigId());
if (!StringUtils.equals(temp.getConfigKey(), config.getConfigKey())) if (!StringUtils.equals(temp.getConfigKey(), config.getConfigKey())) {
{
redisCache.deleteObject(getCacheKey(temp.getConfigKey())); redisCache.deleteObject(getCacheKey(temp.getConfigKey()));
} }
int row = configMapper.updateConfig(config); int row = configMapper.updateConfig(config);
if (row > 0) if (row > 0) {
{
redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue()); redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue());
} }
return row; return row;
@ -154,13 +140,10 @@ public class SysConfigServiceImpl implements ISysConfigService
* @param configIds 需要删除的参数ID * @param configIds 需要删除的参数ID
*/ */
@Override @Override
public void deleteConfigByIds(Long[] configIds) public void deleteConfigByIds(Long[] configIds) {
{ for (Long configId : configIds) {
for (Long configId : configIds)
{
SysConfig config = selectConfigById(configId); SysConfig config = selectConfigById(configId);
if (StringUtils.equals(UserConstants.YES, config.getConfigType())) if (StringUtils.equals(UserConstants.YES, config.getConfigType())) {
{
throw new ServiceException(String.format("内置参数【%1$s】不能删除 ", config.getConfigKey())); throw new ServiceException(String.format("内置参数【%1$s】不能删除 ", config.getConfigKey()));
} }
configMapper.deleteConfigById(configId); configMapper.deleteConfigById(configId);
@ -172,11 +155,9 @@ public class SysConfigServiceImpl implements ISysConfigService
* 加载参数缓存数据 * 加载参数缓存数据
*/ */
@Override @Override
public void loadingConfigCache() public void loadingConfigCache() {
{
List<SysConfig> configsList = configMapper.selectConfigList(new SysConfig()); List<SysConfig> configsList = configMapper.selectConfigList(new SysConfig());
for (SysConfig config : configsList) for (SysConfig config : configsList) {
{
redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue()); redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue());
} }
} }
@ -185,8 +166,7 @@ public class SysConfigServiceImpl implements ISysConfigService
* 清空参数缓存数据 * 清空参数缓存数据
*/ */
@Override @Override
public void clearConfigCache() public void clearConfigCache() {
{
Collection<String> keys = redisCache.keys(CacheConstants.SYS_CONFIG_KEY + "*"); Collection<String> keys = redisCache.keys(CacheConstants.SYS_CONFIG_KEY + "*");
redisCache.deleteObject(keys); redisCache.deleteObject(keys);
} }
@ -195,8 +175,7 @@ public class SysConfigServiceImpl implements ISysConfigService
* 重置参数缓存数据 * 重置参数缓存数据
*/ */
@Override @Override
public void resetConfigCache() public void resetConfigCache() {
{
clearConfigCache(); clearConfigCache();
loadingConfigCache(); loadingConfigCache();
} }
@ -208,12 +187,10 @@ public class SysConfigServiceImpl implements ISysConfigService
* @return 结果 * @return 结果
*/ */
@Override @Override
public String checkConfigKeyUnique(SysConfig config) public String checkConfigKeyUnique(SysConfig config) {
{
Long configId = StringUtils.isNull(config.getConfigId()) ? -1L : config.getConfigId(); Long configId = StringUtils.isNull(config.getConfigId()) ? -1L : config.getConfigId();
SysConfig info = configMapper.checkConfigKeyUnique(config.getConfigKey()); SysConfig info = configMapper.checkConfigKeyUnique(config.getConfigKey());
if (StringUtils.isNotNull(info) && info.getConfigId().longValue() != configId.longValue()) if (StringUtils.isNotNull(info) && info.getConfigId().longValue() != configId.longValue()) {
{
return UserConstants.NOT_UNIQUE; return UserConstants.NOT_UNIQUE;
} }
return UserConstants.UNIQUE; return UserConstants.UNIQUE;
@ -225,8 +202,7 @@ public class SysConfigServiceImpl implements ISysConfigService
* @param configKey 参数键 * @param configKey 参数键
* @return 缓存键key * @return 缓存键key
*/ */
private String getCacheKey(String configKey) private String getCacheKey(String configKey) {
{
return CacheConstants.SYS_CONFIG_KEY + configKey; return CacheConstants.SYS_CONFIG_KEY + configKey;
} }
} }

View File

@ -226,71 +226,6 @@
</el-row> </el-row>
</el-card> </el-card>
<el-card shadow="hover" style="margin: 10px 10px 20px 10px" class="phone-card">
<el-row :gutter="40">
<el-col :xs="24" :sm="24" :md="10" :lg="10" :xl="10" style="padding: 40px">
<div style="padding: 30px; margin: 20px 0; font-size: 14px">
<div style="font-size: 28px; font-weight: bold; margin-bottom: 20px">{{ $t('views.index.394840-0') }}</div>
<div style="display: table; font-size: 14px; margin-bottom: 10px">
<div style="display: table-cell; line-height: 22px">
<b style="color: #67c23a; margin-right: 10px">{{ $t('views.index.394840-1') }}</b>
</div>
</div>
<div style="display: table; margin-bottom: 10px">
<div style="width: 70px; font-weight: bold; display: table-cell">{{ $t('views.index.394840-2') }}</div>
<div style="display: table-cell; line-height: 22px">{{ $t('views.index.394840-3') }}</div>
</div>
<div style="display: table">
<div style="width: 70px; font-weight: bold; display: table-cell">{{ $t('views.index.394840-4') }}</div>
<div style="display: table-cell; line-height: 22px">
{{ $t('views.index.394840-5') }}
<br />
<el-link target="_blank" href="https://fastbee.cn/doc/pages/sponsor">{{ $t('views.index.394840-6') }}</el-link>
</div>
</div>
</div>
<div style="padding: 30px; font-size: 14px">
<div style="float: left; width: 230px">
<el-image style="width: 210px" :src="require('@/assets/images/code.jpg')"></el-image>
</div>
<div style="float: left">
<div style="font-size: 18px; font-weight: bold; margin: 16px 0">{{ $t('views.index.394840-7') }}</div>
<div style="font-size: 14px; font-weight: bold; margin: 16px 0; color: #f56c6c">{{ $t('views.index.394840-8') }}</div>
<div style="display: table; margin-bottom: 10px">
<div style="width: 70px; font-weight: bold; display: table-cell">{{ $t('views.index.394840-9') }}</div>
<div style="display: table-cell">
<el-link target="_blank" href="https://fastbee.cn/">www.fastbee.cn</el-link>
</div>
</div>
<div style="display: table; margin-bottom: 10px">
<div style="width: 70px; font-weight: bold; display: table-cell">{{ $t('views.index.394840-10') }}</div>
<div style="display: table-cell">
<el-link target="_blank" href="https://fastbee.cn/doc">www.fastbee.cn/doc</el-link>
</div>
</div>
<div style="display: table; margin-bottom: 10px">
<div style="width: 70px; font-weight: bold; display: table-cell">{{ $t('views.index.394840-11') }}</div>
<div style="display: table-cell">
<span>QQ 164770707</span>
</div>
</div>
<div style="display: table; margin-bottom: 15px">
<div style="width: 70px; font-weight: bold; display: table-cell">{{ $t('views.index.394840-12') }}</div>
<div style="display: table-cell">
<el-link target="_blank" href="https://gitee.com/kerwincui/wumei-smart" type="danger">{{ $t('views.index.394840-13') }}</el-link>
<el-link target="_blank" href="https://github.com/kerwincui/fastbee" style="margin-left: 20px">{{ $t('views.index.394840-14') }}</el-link>
</div>
</div>
</div>
</div>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12" style="padding: 40px">
<div class="phone">
<iframe src="https://fastbee.cn/h5" id="iframe" frameborder="0" scrolling="auto" height="100%" width="100%" class="phone-container"></iframe>
</div>
</el-col>
</el-row>
</el-card>
<!--通知公告详情 --> <!--通知公告详情 -->
<el-dialog :title="notice.noticeTitle" :visible.sync="open" width="800px" append-to-body> <el-dialog :title="notice.noticeTitle" :visible.sync="open" width="800px" append-to-body>
@ -787,6 +722,9 @@ export default {
this.$nextTick(() => { this.$nextTick(() => {
loadBMap().then(() => { loadBMap().then(() => {
this.getmap(); this.getmap();
}).catch((error) => {
console.error('百度地图加载失败:', error);
this.$message.error('百度地图加载失败,请检查API Key配置。错误信息:' + error.message);
}); });
}); });
}, },

View File

@ -47,7 +47,8 @@
<el-form-item :class="{ 'is-null': true }"> <el-form-item :class="{ 'is-null': true }">
<div style="display: flex; text-align: center; justify-content: center"> <div style="display: flex; text-align: center; justify-content: center">
<el-link style="float: left; color: #a0a5a8; font-weight: normal" href="https://fastbee.cn/" :underline="false" target="_blank">{{ $t('register.974236-7') }}</el-link> <el-link style="float: left; color: #a0a5a8; font-weight: normal" href="https://fastbee.cn/" :underline="false" target="_blank">{{ $t('register.974236-7') }}</el-link>
<el-link style="float: left; color: #a0a5a8; font-weight: normal; margin-left: 20px" href="https://fastbee.cn/doc" :underline="false" target="_blank">{{ $t('register.974236-8') }}</el-link> <!-- 隐藏查看文档入口 -->
<!-- <el-link style="float: left; color: #a0a5a8; font-weight: normal; margin-left: 20px" href="https://fastbee.cn/doc" :underline="false" target="_blank">{{ $t('register.974236-8') }}</el-link> -->
<router-link style="float: left; color: #a0a5a8; font-weight: normal; margin-left: 20px" :to="{ path: '/login', query: this.$route.query }">{{ $t('register.974236-9') }}</router-link> <router-link style="float: left; color: #a0a5a8; font-weight: normal; margin-left: 20px" :to="{ path: '/login', query: this.$route.query }">{{ $t('register.974236-9') }}</router-link>
</div> </div>
</el-form-item> </el-form-item>