LED屏管理

dev_xd
lj7788 2026-01-06 14:11:24 +08:00
parent 35376801aa
commit d9b6afdc80
9 changed files with 5843 additions and 115 deletions

File diff suppressed because one or more lines are too long

View File

@ -45,9 +45,7 @@ public class SysLedscreen extends BaseEntity
@Excel(name = "高")
private Long height;
/** 频率(秒) */
@Excel(name = "频率(秒)")
private Long frequency;
/** LED绘图模式 */
@Excel(name = "LED绘图模式")
@ -151,15 +149,8 @@ public class SysLedscreen extends BaseEntity
{
return height;
}
public void setFrequency(Long frequency)
{
this.frequency = frequency;
}
public Long getFrequency()
{
return frequency;
}
public void setDrawType(Long drawType)
{
this.drawType = drawType;
@ -236,7 +227,6 @@ public class SysLedscreen extends BaseEntity
.append("deviceSn", getDeviceSn())
.append("width", getWidth())
.append("height", getHeight())
.append("frequency", getFrequency())
.append("drawType", getDrawType())
.append("title", getTitle())
.append("enabled", getEnabled())

View File

@ -12,7 +12,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="deviceSn" column="device_sn" />
<result property="width" column="width" />
<result property="height" column="height" />
<result property="frequency" column="frequency" />
<result property="drawType" column="draw_type" />
<result property="title" column="title" />
<result property="enabled" column="enabled" />
@ -29,7 +28,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap>
<sql id="selectSysLedscreenVo">
select sl.id, sl.project_id, sl.workarea_id, sl.device_name, sl.device_sn, sl.width, sl.height, sl.frequency, sl.draw_type, sl.title, sl.enabled, sl.is_del, sl.state, sl.is_online, sl.remark, sl.create_by, sl.create_time, sl.update_by, sl.update_time, pi.project_name, wa.title as workarea_name
select sl.id, sl.project_id, sl.workarea_id, sl.device_name, sl.device_sn, sl.width, sl.height, sl.draw_type, sl.title, sl.enabled, sl.is_del, sl.state, sl.is_online, sl.remark, sl.create_by, sl.create_time, sl.update_by, sl.update_time, pi.project_name, wa.title as workarea_name
from sys_ledscreen sl
left join pro_project_info pi on sl.project_id = pi.id
left join sys_work_area wa on sl.workarea_id = wa.id
@ -44,7 +43,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="deviceSn != null and deviceSn != ''"> and sl.device_sn = #{deviceSn}</if>
<if test="width != null "> and sl.width = #{width}</if>
<if test="height != null "> and sl.height = #{height}</if>
<if test="frequency != null "> and sl.frequency = #{frequency}</if>
<if test="drawType != null "> and sl.draw_type = #{drawType}</if>
<if test="title != null and title != ''"> and sl.title = #{title}</if>
<if test="enabled != null "> and sl.enabled = #{enabled}</if>
@ -68,7 +66,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="deviceSn != null">device_sn,</if>
<if test="width != null">width,</if>
<if test="height != null">height,</if>
<if test="frequency != null">frequency,</if>
<if test="drawType != null">draw_type,</if>
<if test="title != null">title,</if>
<if test="enabled != null">enabled,</if>
@ -89,7 +86,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="deviceSn != null">#{deviceSn},</if>
<if test="width != null">#{width},</if>
<if test="height != null">#{height},</if>
<if test="frequency != null">#{frequency},</if>
<if test="drawType != null">#{drawType},</if>
<if test="title != null">#{title},</if>
<if test="enabled != null">#{enabled},</if>
@ -113,7 +109,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="deviceSn != null">device_sn = #{deviceSn},</if>
<if test="width != null">width = #{width},</if>
<if test="height != null">height = #{height},</if>
<if test="frequency != null">frequency = #{frequency},</if>
<if test="drawType != null">draw_type = #{drawType},</if>
<if test="title != null">title = #{title},</if>
<if test="enabled != null">enabled = #{enabled},</if>
@ -148,7 +143,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="deviceName != null">device_name = #{deviceName},</if>
<if test="width != null">width = #{width},</if>
<if test="height != null">height = #{height},</if>
<if test="frequency != null">frequency = #{frequency},</if>
<if test="drawType != null">draw_type = #{drawType},</if>
<if test="title != null">title = #{title},</if>
<if test="enabled != null">enabled = #{enabled},</if>

View File

@ -1,24 +1,29 @@
package com.yanzhu.led;
import com.yanzhu.api.domain.SysDictData;
import com.yanzhu.led.service.ILedScreenService;
import com.yanzhu.led.service.LedDrawService;
import com.yanzhu.led.service.LedServerService;
import com.yanzhu.system.domain.SysLedscreen;
import com.yanzhu.system.domain.SysWorkArea;
import com.yanzhu.system.mapper.SysDictDataMapper;
import com.yanzhu.system.mapper.SysWorkAreaMapper;
import onbon.bx06.Bx6GScreen;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ScheduledFuture;
import java.util.stream.Collectors;
import com.yanzhu.led.config.LedProperties;
@ -42,45 +47,60 @@ public class LedMainApplication {
@Autowired
private LedDrawService ledDrawService;
// 用于管理各个LED屏的刷新任务
@Autowired
private SysDictDataMapper sysDictDataMapper;
private final ThreadPoolTaskScheduler cronScheduler = new ThreadPoolTaskScheduler();
private final ConcurrentHashMap<String, Map<String, ScheduledFuture<?>>> scheduledTasks = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, Runnable> refreshTasks = new ConcurrentHashMap<>();
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(10);
private List<SysDictData> cronExpressions = Collections.emptyList();
@PostConstruct
public void initialize() throws Exception {
// 启动LED服务器守护功能
loadLedRefreshTimer();
cronScheduler.initialize();
ledServerService.initializeServer();
// 加载所有LED屏配置
loadAllLedScreens();
// 启动定时刷新任务
startPeriodicRefresh();
}
/**
* LED
*/
private void loadLedRefreshTimer(){
SysDictData dictWhere=new SysDictData();
dictWhere.setDictType("led_refresh_timer");
dictWhere.setStatus("0");
cronExpressions=sysDictDataMapper.selectDictDataList(dictWhere);
}
private void loadAllLedScreens() {
List<SysLedscreen> ledScreens = ledScreenService.getAllLedScreens();
logger.info("加载了 {} 个LED屏配置", ledScreens.size());
for (SysLedscreen ledScreen : ledScreens) {
// 为每个LED屏创建刷新任务
createRefreshTask(ledScreen);
}
}
/**
* LED
*/
public List<String> getProjectCronExpressions(long projectId) {
String prjId=""+projectId;
List<SysDictData> prjList=cronExpressions.stream().filter(d-> StringUtils.equals(prjId,d.getCssClass())).collect(Collectors.toList());
if(prjList.isEmpty()){
prjList=cronExpressions.stream().filter(d->StringUtils.isBlank(d.getCssClass())).collect(Collectors.toList());
}
return prjList.stream().map(d->d.getDictValue()).collect(Collectors.toList());
}
private void createRefreshTask(SysLedscreen ledScreen) {
Runnable task = () -> {
try {
// 从服务中获取最新的LED屏信息确保获取最新的在线状态
SysLedscreen currentScreen = ledScreenService.getLedScreenByNetId(ledScreen.getDeviceSn());
if (currentScreen != null && currentScreen.isOnline()) {
boolean success = ledDrawService.drawLedScreenContent(currentScreen);
if (success) {
logger.info("成功刷新LED屏: {}", currentScreen.getDeviceSn());
@ -95,11 +115,33 @@ public class LedMainApplication {
}
};
// 存储任务引用
refreshTasks.put(ledScreen.getDeviceSn(), task);
scheduleWithCron(ledScreen.getDeviceSn(), task, getProjectCronExpressions(ledScreen.getProjectId()));
}
// 调度任务
scheduler.scheduleAtFixedRate(task, 0, ledScreen.getFrequency(), TimeUnit.SECONDS);
private void scheduleWithCron(String deviceSn, Runnable task, List<String> cronExpressions) {
Map<String, ScheduledFuture<?>> existingTasks = scheduledTasks.get(deviceSn);
if (existingTasks != null) {
for (ScheduledFuture<?> existingTask : existingTasks.values()) {
existingTask.cancel(false);
}
}
Map<String, ScheduledFuture<?>> newTasks = new HashMap<>();
for (String cronExpression : cronExpressions) {
if (StringUtils.isNotBlank(cronExpression)) {
try {
ScheduledFuture<?> scheduledTask = cronScheduler.schedule(task, new CronTrigger(cronExpression));
newTasks.put(cronExpression, scheduledTask);
logger.info("为LED屏 {} 创建调度任务Cron表达式: {}", deviceSn, cronExpression);
} catch (Exception e) {
logger.error("为LED屏 {} 创建调度任务失败Cron表达式: {}, 错误: {}", deviceSn, cronExpression, e.getMessage(), e);
}
}
}
scheduledTasks.put(deviceSn, newTasks);
}
/**
@ -112,64 +154,74 @@ public class LedMainApplication {
return new String[]{"image1.jpg", "image2.jpg", "image3.jpg"}; // 实际使用时应从配置或数据库获取
}
/**
* 线ping
*/
private boolean ledIsOnline(String deviceSn) {
try {
if(ledServerService.getServer()==null){
return false;
}
return ledServerService.getServer().getOnlineScreenByNetId(deviceSn) != null;
return ledDrawService.isConnected(deviceSn);
} catch (Exception e) {
logger.error(e.getMessage(), e);
return false;
}
}
/**
* LED
*/
@Scheduled(fixedRate = 60000) // 每分钟执行一次
@Scheduled(fixedRate = 60000)
public void startPeriodicRefresh() {
try {
List<SysLedscreen> ledScreens = ledScreenService.getAllLedScreens();
for (SysLedscreen ledScreen : ledScreens) {
// 检查设备的实际连接状态
boolean isActuallyOnline = ledIsOnline(ledScreen.getDeviceSn());
boolean wasOnline = ledScreen.isOnline();
// 如果在线状态改变,更新缓存和数据库
if (isActuallyOnline != wasOnline) {
ledScreen.setOnline(isActuallyOnline);
ledScreenService.updateLedScreen(ledScreen);
logger.info("LED屏 {} 在线状态已更新: {} -> {}",
ledScreen.getDeviceSn(), wasOnline, isActuallyOnline);
if (!isActuallyOnline) {
cancelScheduledTask(ledScreen.getDeviceSn());
} else {
createRefreshTask(ledScreen);
}
}
// 检查是否已有对应的任务
if (!refreshTasks.containsKey(ledScreen.getDeviceSn())) {
// 如果没有对应的任务,则创建新任务
createRefreshTask(ledScreen);
} else {
// 如果已有任务检查LED屏配置是否有变化如果有变化则更新任务
// 这样可以确保任务使用最新的配置,包括在线状态
SysLedscreen existingScreen = ledScreenService.getLedScreenByNetId(ledScreen.getDeviceSn());
if (existingScreen != null) {
// 检查频率是否变化,如果变化则重新创建任务
if (existingScreen.getFrequency() != ledScreen.getFrequency()) {
// 重新创建任务以使用新的频率设置
Runnable oldTask = refreshTasks.remove(ledScreen.getDeviceSn());
if (oldTask != null) {
logger.info("更新LED屏任务: {}", ledScreen.getDeviceSn());
}
createRefreshTask(ledScreen);
}
}
// 主动检测设备状态如果设备已连接但ping失败则认为设备离线
if (ledDrawService.isConnected(ledScreen.getDeviceSn()) && !isActuallyOnline) {
// 从连接管理器移除屏幕
ledDrawService.removeConnectedScreen(ledScreen.getDeviceSn());
logger.info("从连接管理器移除离线设备: {}", ledScreen.getDeviceSn());
}
}
} catch (Exception e) {
logger.error("执行定期刷新时出错: {}", e.getMessage(), e);
logger.error("定期刷新LED屏状态时出错", e);
}
}
@Scheduled(fixedRate = 600000)
public void refreshLedTimerData() {
try {
loadLedRefreshTimer();
logger.info("已刷新LED定时器数据");
} catch (Exception e) {
logger.error("刷新LED定时器数据时出错: {}", e.getMessage(), e);
}
}
private void cancelScheduledTask(String deviceSn) {
Map<String, ScheduledFuture<?>> cronTasks = scheduledTasks.remove(deviceSn);
if (cronTasks != null) {
for (ScheduledFuture<?> cronTask : cronTasks.values()) {
if (cronTask != null) {
cronTask.cancel(false);
}
}
logger.info("已取消LED屏 {} 的所有调度任务", deviceSn);
}
refreshTasks.remove(deviceSn);
}
/**
* NetIDLED
*/
@ -177,24 +229,16 @@ public class LedMainApplication {
return ledScreenService.getLedScreenByNetId(netId);
}
/**
* LED
*/
public void updateLedScreen(SysLedscreen ledScreen) {
// 先取消旧的刷新任务
Runnable oldTask = refreshTasks.remove(ledScreen.getDeviceSn());
if (oldTask != null) {
// 注意:这里无法直接取消已提交的任务,但会移除引用
logger.info("移除旧的刷新任务: {}", ledScreen.getDeviceSn());
}
cancelScheduledTask(ledScreen.getDeviceSn());
// 更新配置
ledScreenService.updateLedScreen(ledScreen);
// 创建新的刷新任务
createRefreshTask(ledScreen);
}
/**
*
*/

View File

@ -15,6 +15,7 @@ import onbon.bx06.area.page.TextBxPage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.awt.*;
@ -81,7 +82,6 @@ public class LedDrawService {
}
/**
* Bx6M
*/
@ -93,7 +93,8 @@ public class LedDrawService {
16, profile);
// 如果没有图片,添加文本页
String text ="研筑科技LED屏内容 - " + ledScreen.getTitle()+ DateUtil.now();
String text ="X研筑科技LED屏内容 - " + ledScreen.getTitle()+ DateUtil.now();
logger.info("LED-->{},sn={}",text,ledScreen.getDeviceSn());
TextBxPage textPage = new TextBxPage(text, new Font("宋体", Font.PLAIN, 12));//+当前时间
textPage.setForeground(Color.WHITE);
@ -127,7 +128,6 @@ public class LedDrawService {
}
/**
*
*/
@ -159,4 +159,47 @@ public class LedDrawService {
public boolean isConnected(String deviceSn) {
return connectedScreens.containsKey(deviceSn);
}
/**
*
* @param deviceSn
* @return Bx6GScreennull
*/
public Bx6GScreen getConnectedScreen(String deviceSn) {
return connectedScreens.get(deviceSn);
}
/**
* 线ping线
*/
@Scheduled(fixedRate = 30000) // 每30秒检测一次
public void checkConnectedDevicesStatus() {
for (Map.Entry<String, Bx6GScreen> entry : connectedScreens.entrySet()) {
String deviceSn = entry.getKey();
Bx6GScreen screen = entry.getValue();
try {
// 尝试ping设备检测其是否仍然在线
Bx6GScreen.Result<?> result = screen.ping();
if (result == null || !result.isOK()) {
logger.warn("设备 {} ping失败认为已离线将从连接管理器中移除", deviceSn);
// 设备离线,从连接管理器中移除
connectedScreens.remove(deviceSn);
logger.info("已从连接管理器移除离线设备: {}", deviceSn);
}
} catch (Exception e) {
logger.warn("检测设备 {} 在线状态时出错: {}, 将从连接管理器中移除", deviceSn, e.getMessage());
// 发生异常,从连接管理器中移除设备
connectedScreens.remove(deviceSn);
logger.info("已从连接管理器移除异常设备: {}", deviceSn);
}
}
}
/**
* SN
*/
public java.util.Set<String> getAllConnectedDeviceSns() {
return new java.util.HashSet<>(connectedScreens.keySet());
}
}

View File

@ -0,0 +1,62 @@
package com.yanzhu.led.service;
import com.yanzhu.system.domain.SysLedscreen;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
/**
* LED线
* 线
*/
@Service
public class LedOfflineDetectionService {
private static final Logger logger = LoggerFactory.getLogger(LedOfflineDetectionService.class);
@Autowired
private ILedScreenService ledScreenService;
@Autowired
private LedDrawService ledDrawService;
/**
* 线
* 线
*/
@Scheduled(fixedRate = 45000) // 每45秒检查一次
public void detectOfflineDevices() {
try {
// 获取数据库中所有标记为在线的设备
java.util.List<SysLedscreen> onlineScreens = ledScreenService.getAllLedScreens().stream()
.filter(SysLedscreen::isOnline)
.collect(Collectors.toList()) ;
// 获取连接管理器中的所有设备
Set<String> connectedDeviceSns = ledDrawService.getAllConnectedDeviceSns();
// 检查数据库中标记为在线但实际未连接的设备
for (SysLedscreen screen : onlineScreens) {
if (!connectedDeviceSns.contains(screen.getDeviceSn())) {
// 设备在数据库中标记为在线,但不在连接管理器中,说明已离线
logger.info("检测到设备 {} 已离线,更新数据库状态", screen.getDeviceSn());
// 更新数据库状态
screen.setOnline(false);
ledScreenService.updateLedScreen(screen);
logger.info("LED屏 {} 状态更新为离线", screen.getDeviceSn());
}
}
logger.debug("离线设备检测完成,当前在线设备数: {}", connectedDeviceSns.size());
} catch (Exception e) {
logger.error("检测离线设备时出错", e);
}
}
}

View File

@ -142,7 +142,6 @@ public class UniLedDrawer extends BaseDrawer{
SysLedscreen led=new SysLedscreen();
led.setWidth(230l);
led.setHeight(128l);
led.setFrequency(180l);
led.setDeviceSn("B06M3P2501160132");
led.setTitle("abc");
LedProperties ledProperties=new LedProperties();

View File

@ -49,14 +49,6 @@
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="频率(秒)" prop="frequency">
<el-input
v-model="queryParams.frequency"
placeholder="请输入频率(秒)"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="标题" prop="title">
<el-input
v-model="queryParams.title"
@ -146,7 +138,6 @@
<el-table-column label="设备序列号" align="center" prop="deviceSn" />
<el-table-column label="宽" align="center" prop="width" />
<el-table-column label="高" align="center" prop="height" />
<el-table-column label="频率(秒)" align="center" prop="frequency" />
<el-table-column label="LED绘图模式" align="center" prop="drawType" />
<el-table-column label="标题" align="center" prop="title" />
<el-table-column label="启停1-启用 0-停用" align="center" prop="enabled" />
@ -190,9 +181,6 @@
<el-form-item label="高" prop="height">
<el-input v-model="form.height" placeholder="请输入高" />
</el-form-item>
<el-form-item label="频率(秒)" prop="frequency">
<el-input v-model="form.frequency" placeholder="请输入频率(秒)" />
</el-form-item>
<el-form-item label="标题" prop="title">
<el-input v-model="form.title" placeholder="请输入标题" />
</el-form-item>
@ -245,7 +233,6 @@ const data = reactive({
deviceSn: null,
width: null,
height: null,
frequency: null,
drawType: null,
title: null,
enabled: null,
@ -284,7 +271,6 @@ function reset() {
deviceSn: null,
width: null,
height: null,
frequency: null,
drawType: null,
title: null,
enabled: null,

View File

@ -65,11 +65,6 @@
{{ row.width }} × {{ row.height }}
</template>
</el-table-column>
<el-table-column label="更新频率" align="center" prop="frequency" width="100">
<template #default="{ row }">
{{ row.frequency }}
</template>
</el-table-column>
<el-table-column label="状态" align="center" width="100">
<template #default="{ row }">
<el-tag :type="row.online ? 'success' : 'warning'">
@ -146,12 +141,6 @@
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="更新频率" prop="frequency">
<el-input v-model="form.frequency" type="number" placeholder="请输入更新频率(秒)"
:disabled="dialogType === 'view'" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="绘制模式" prop="drawType">
<el-input v-model="form.drawType" type="number" placeholder="请输入绘制模式"
@ -254,7 +243,6 @@ const form = ref({
deviceSn: undefined,
width: undefined,
height: undefined,
frequency: undefined,
drawType: undefined,
title: undefined,
enabled: 1,
@ -275,9 +263,6 @@ const rules = ref({
],
height: [
{ required: true, message: "屏幕高度不能为空", trigger: "blur" }
],
frequency: [
{ required: true, message: "更新频率不能为空", trigger: "blur" }
]
});