开发LED监控功能

dev_xd
lj7788 2026-01-04 17:40:15 +08:00
parent 4a2fc96768
commit c276e09c7f
9 changed files with 618 additions and 19 deletions

View File

@ -80,6 +80,14 @@ public class SysLedscreen extends BaseEntity
@Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()")
private Long state; private Long state;
/** 项目名称 */
@Excel(name = "项目名称")
private String projectName;
/** 工区名称 */
@Excel(name = "工区名称")
private String workareaName;
public void setId(Long id) public void setId(Long id)
{ {
this.id = id; this.id = id;
@ -198,6 +206,26 @@ public class SysLedscreen extends BaseEntity
return state; return state;
} }
public void setProjectName(String projectName)
{
this.projectName = projectName;
}
public String getProjectName()
{
return projectName;
}
public void setWorkareaName(String workareaName)
{
this.workareaName = workareaName;
}
public String getWorkareaName()
{
return workareaName;
}
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)

View File

@ -24,33 +24,38 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="createTime" column="create_time" /> <result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" /> <result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" /> <result property="updateTime" column="update_time" />
<result property="projectName" column="project_name" />
<result property="workareaName" column="workarea_name" />
</resultMap> </resultMap>
<sql id="selectSysLedscreenVo"> <sql id="selectSysLedscreenVo">
select id, project_id, workarea_id, device_name, device_sn, width, height, frequency, draw_type, title, enabled, is_del, state, is_online, remark, create_by, create_time, update_by, update_time from sys_ledscreen 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
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
</sql> </sql>
<select id="selectSysLedscreenList" parameterType="SysLedscreen" resultMap="SysLedscreenResult"> <select id="selectSysLedscreenList" parameterType="SysLedscreen" resultMap="SysLedscreenResult">
<include refid="selectSysLedscreenVo"/> <include refid="selectSysLedscreenVo"/>
<where> <where>
<if test="projectId != null "> and project_id = #{projectId}</if> <if test="projectId != null "> and sl.project_id = #{projectId}</if>
<if test="workareaId != null "> and workarea_id = #{workareaId}</if> <if test="workareaId != null "> and sl.workarea_id = #{workareaId}</if>
<if test="deviceName != null and deviceName != ''"> and device_name like concat('%', #{deviceName}, '%')</if> <if test="deviceName != null and deviceName != ''"> and sl.device_name like concat('%', #{deviceName}, '%')</if>
<if test="deviceSn != null and deviceSn != ''"> and device_sn = #{deviceSn}</if> <if test="deviceSn != null and deviceSn != ''"> and sl.device_sn = #{deviceSn}</if>
<if test="width != null "> and width = #{width}</if> <if test="width != null "> and sl.width = #{width}</if>
<if test="height != null "> and height = #{height}</if> <if test="height != null "> and sl.height = #{height}</if>
<if test="frequency != null "> and frequency = #{frequency}</if> <if test="frequency != null "> and sl.frequency = #{frequency}</if>
<if test="drawType != null "> and draw_type = #{drawType}</if> <if test="drawType != null "> and sl.draw_type = #{drawType}</if>
<if test="title != null and title != ''"> and title = #{title}</if> <if test="title != null and title != ''"> and sl.title = #{title}</if>
<if test="enabled != null "> and enabled = #{enabled}</if> <if test="enabled != null "> and sl.enabled = #{enabled}</if>
<if test="isDel != null "> and is_del = #{isDel}</if> <if test="isDel != null "> and sl.is_del = #{isDel}</if>
<if test="state != null "> and state = #{state}</if> <if test="state != null "> and sl.state = #{state}</if>
</where> </where>
</select> </select>
<select id="selectSysLedscreenById" parameterType="Long" resultMap="SysLedscreenResult"> <select id="selectSysLedscreenById" parameterType="Long" resultMap="SysLedscreenResult">
<include refid="selectSysLedscreenVo"/> <include refid="selectSysLedscreenVo"/>
where id = #{id} where sl.id = #{id}
</select> </select>
<insert id="insertSysLedscreen" parameterType="SysLedscreen"> <insert id="insertSysLedscreen" parameterType="SysLedscreen">

View File

@ -112,6 +112,17 @@ public class LedMainApplication {
return new String[]{"image1.jpg", "image2.jpg", "image3.jpg"}; // 实际使用时应从配置或数据库获取 return new String[]{"image1.jpg", "image2.jpg", "image3.jpg"}; // 实际使用时应从配置或数据库获取
} }
private boolean ledIsOnline(String deviceSn) {
try {
if(ledServerService.getServer()==null){
return false;
}
return ledServerService.getServer().getOnlineScreenByNetId(deviceSn) != null;
} catch (Exception e) {
logger.error(e.getMessage(), e);
return false;
}
}
/** /**
* LED * LED
*/ */
@ -121,6 +132,18 @@ public class LedMainApplication {
List<SysLedscreen> ledScreens = ledScreenService.getAllLedScreens(); List<SysLedscreen> ledScreens = ledScreenService.getAllLedScreens();
for (SysLedscreen ledScreen : ledScreens) { 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 (!refreshTasks.containsKey(ledScreen.getDeviceSn())) { if (!refreshTasks.containsKey(ledScreen.getDeviceSn())) {
// 如果没有对应的任务,则创建新任务 // 如果没有对应的任务,则创建新任务
@ -186,10 +209,27 @@ public class LedMainApplication {
return ledDrawService.getConnectedScreenCount(); return ledDrawService.getConnectedScreenCount();
} }
/**
* ID
* @param projectId IDnull
*/
public int getConnectedScreenCount(Long projectId) {
List<SysLedscreen> screens = ledScreenService.getAllLedScreensFromCacheByProjectId(projectId);
return (int) screens.stream().filter(SysLedscreen::isOnline).count();
}
/** /**
* LED * LED
*/ */
public List<SysLedscreen> getAllScreensFromCache() { public List<SysLedscreen> getAllScreensFromCache() {
return ledScreenService.getAllLedScreensFromCache(); return ledScreenService.getAllLedScreensFromCache();
} }
/**
* LEDID
* @param projectId IDnullLED
*/
public List<SysLedscreen> getAllScreensFromCache(Long projectId) {
return ledScreenService.getAllLedScreensFromCacheByProjectId(projectId);
}
} }

View File

@ -18,9 +18,9 @@ public class LedController {
* *
*/ */
@GetMapping("/status") @GetMapping("/status")
public String getServerStatus() { public String getServerStatus(@RequestParam(required = false) Long projectId) {
boolean isRunning = ledMainApplication.isServerRunning(); boolean isRunning = ledMainApplication.isServerRunning();
int connectedCount = ledMainApplication.getConnectedScreenCount(); int connectedCount = ledMainApplication.getConnectedScreenCount(projectId);
return String.format("服务器运行状态: %s, 已连接屏幕数量: %d", isRunning ? "运行中" : "已停止", connectedCount); return String.format("服务器运行状态: %s, 已连接屏幕数量: %d", isRunning ? "运行中" : "已停止", connectedCount);
} }
@ -28,8 +28,8 @@ public class LedController {
* LED * LED
*/ */
@GetMapping("/screens") @GetMapping("/screens")
public List<SysLedscreen> getAllScreens() { public List<SysLedscreen> getAllScreens(@RequestParam(required = false) Long projectId) {
return ledMainApplication.getAllScreensFromCache(); return ledMainApplication.getAllScreensFromCache(projectId);
} }
/** /**

View File

@ -35,5 +35,11 @@ public interface ILedScreenService {
*/ */
List<SysLedscreen> getAllLedScreensFromCache(); List<SysLedscreen> getAllLedScreensFromCache();
/**
* IDLED
* @param projectId IDnullLED
*/
List<SysLedscreen> getAllLedScreensFromCacheByProjectId(Long projectId);
String[] getImagePathsForScreen(SysLedscreen ledScreen); String[] getImagePathsForScreen(SysLedscreen ledScreen);
} }

View File

@ -150,4 +150,13 @@ public class LedDrawService {
public int getConnectedScreenCount() { public int getConnectedScreenCount() {
return connectedScreens.size(); return connectedScreens.size();
} }
/**
*
* @param deviceSn
* @return truefalse
*/
public boolean isConnected(String deviceSn) {
return connectedScreens.containsKey(deviceSn);
}
} }

View File

@ -44,8 +44,19 @@ public class LedScreenServiceImpl implements ILedScreenService {
List<SysLedscreen> ledScreens = new ArrayList<>(); List<SysLedscreen> ledScreens = new ArrayList<>();
for (SysLedscreen ledscreen : sysWorkAreaList) { for (SysLedscreen ledscreen : sysWorkAreaList) {
// 检查缓存中是否已存在该屏幕
SysLedscreen cachedScreen = ledScreenMap.get(ledscreen.getDeviceSn());
// 如果缓存中存在,保留缓存中的 isOnline 状态
if (cachedScreen != null) {
ledscreen.setOnline(cachedScreen.isOnline());
} else {
// 如果缓存中不存在,默认设置为离线
ledscreen.setOnline(false);
}
ledScreens.add(ledscreen); ledScreens.add(ledscreen);
// 同时更新内存中的缓存 // 更新内存中的缓存
ledScreenMap.put(ledscreen.getDeviceSn(), ledscreen); ledScreenMap.put(ledscreen.getDeviceSn(), ledscreen);
} }
@ -102,6 +113,20 @@ public class LedScreenServiceImpl implements ILedScreenService {
return new ArrayList<>(ledScreenMap.values()); return new ArrayList<>(ledScreenMap.values());
} }
/**
* IDLED
* @param projectId IDnullLED
*/
@Override
public List<SysLedscreen> getAllLedScreensFromCacheByProjectId(Long projectId) {
if (projectId == null) {
return getAllLedScreensFromCache();
}
return ledScreenMap.values().stream()
.filter(screen -> projectId.equals(screen.getProjectId()))
.collect(Collectors.toList());
}
@Override @Override
/** /**
* LED * LED

View File

@ -0,0 +1,44 @@
import request from '@/utils/request'
// 获取服务器运行状态
export function getServerStatus(projectId) {
return request({
url: '/manage/led/status',
method: 'get',
params: { projectId }
})
}
// 获取所有LED屏信息
export function getAllScreens(projectId) {
return request({
url: '/manage/led/screens',
method: 'get',
params: { projectId }
})
}
// 根据NetID获取特定LED屏信息
export function getScreenByNetId(netId) {
return request({
url: '/manage/led/screens/' + netId,
method: 'get'
})
}
// 手动刷新特定LED屏
export function refreshScreen(netId) {
return request({
url: '/manage/led/screens/' + netId + '/refresh',
method: 'post'
})
}
// 更新LED屏配置
export function updateScreen(data) {
return request({
url: '/manage/led/screens',
method: 'put',
data: data
})
}

View File

@ -0,0 +1,442 @@
<template>
<div class="app-container">
<el-row :gutter="20">
<!-- 服务器状态卡片 -->
<el-col :span="24">
<el-card class="box-card" shadow="never">
<template #header>
<div class="card-header">
<span>LED服务器状态</span>
<el-button class="button" type="primary" @click="getServerStatus(true)"></el-button>
</div>
</template>
<div class="status-content">
<el-descriptions :column="2" border>
<el-descriptions-item label="服务器运行状态">
<el-tag :type="serverStatus.isRunning ? 'success' : 'danger'">
{{ serverStatus.isRunning ? "运行中" : "已停止" }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="已连接屏幕数量">
<el-tag type="primary">{{ serverStatus.connectedCount }}</el-tag>
</el-descriptions-item>
</el-descriptions>
</div>
</el-card>
</el-col>
</el-row>
<!-- LED屏幕列表 -->
<el-row :gutter="20" style="margin-top: 20px;">
<el-col :span="24">
<el-card class="box-card" shadow="never">
<template #header>
<div class="card-header">
<span>LED屏幕列表</span>
<div>
<el-button type="primary" @click="getList"></el-button>
<el-button type="info" @click="handleBatchRefresh" v-if="1 == 2"></el-button>
</div>
</div>
</template>
<el-table v-loading="loading" :data="screenList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="项目" align="center" prop="projectName" width="200" />
<el-table-column label="分区" align="center" prop="workareaName" width="100" />
<el-table-column label="设备名称" align="center" prop="deviceName" width="150">
<template #default="{ row }">
<span :style="{ color: row.isOnline ? '#67C23A' : '#F56C6C' }">
{{ row.deviceName }}
</span>
</template>
</el-table-column>
<el-table-column label="设备序列号" align="center" prop="deviceSn" width="200" />
<el-table-column label="屏幕尺寸" align="center" width="150">
<template #default="{ row }">
{{ 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'">
{{ row.online ? "在线" : "离线" }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="启用状态" align="center" width="100">
<template #default="{ row }">
<el-tag :type="row.enabled == 1 ? 'success' : 'danger'">
{{ row.enabled == 1 ? "启用" : "停用" }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="标题" align="center" prop="title" />
<el-table-column label="操作" align="center" width="200" fixed="right" v-if="1 == 2">
<template #default="{ row }">
<el-button type="primary" link @click="handleRefresh(row)"></el-button>
<el-button type="primary" link @click="handleView(row)"></el-button>
<el-button type="primary" link @click="handleEdit(row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page="queryParams.pageNum"
:limit="queryParams.pageSize" @pagination="handlePagination" />
</el-card>
</el-col>
</el-row>
<!-- 查看/编辑弹窗 -->
<el-dialog :title="dialogTitle" v-model="dialogVisible" width="600px" :close-on-click-modal="false">
<el-form ref="dataFormRef" :model="form" :rules="rules" label-width="120px">
<el-row>
<el-col :span="12">
<el-form-item label="设备名称" prop="deviceName">
<el-input v-model="form.deviceName" placeholder="请输入设备名称"
:disabled="dialogType === 'view'" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="设备序列号" prop="deviceSn">
<el-input v-model="form.deviceSn" placeholder="请输入设备序列号"
:disabled="dialogType === 'view'" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="项目ID" prop="projectId">
<el-input v-model="form.projectId" type="number" placeholder="请输入项目ID"
:disabled="dialogType === 'view'" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="分区ID" prop="workareaId">
<el-input v-model="form.workareaId" type="number" placeholder="请输入分区ID"
:disabled="dialogType === 'view'" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="屏幕宽度" prop="width">
<el-input v-model="form.width" type="number" placeholder="请输入屏幕宽度"
:disabled="dialogType === 'view'" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="屏幕高度" prop="height">
<el-input v-model="form.height" type="number" placeholder="请输入屏幕高度"
:disabled="dialogType === 'view'" />
</el-form-item>
</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="请输入绘制模式"
:disabled="dialogType === 'view'" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="标题" prop="title">
<el-input v-model="form.title" placeholder="请输入标题" :disabled="dialogType === 'view'" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="启用状态" prop="enabled">
<el-select v-model="form.enabled" placeholder="请选择启用状态" :disabled="dialogType === 'view'">
<el-option label="启用" :value="1" />
<el-option label="停用" :value="0" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="状态" prop="state">
<el-input v-model="form.state" type="number" placeholder="请输入状态"
:disabled="dialogType === 'view'" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注"
:disabled="dialogType === 'view'" />
</el-form-item>
</el-form>
<template #footer v-if="dialogType !== 'view'">
<div class="dialog-footer">
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="submitForm"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { listLedscreen, getLedscreen, addLedscreen, updateLedscreen, delLedscreen } from "@/api/system/ledscreen";
import { getServerStatus as getLedServerStatus, getAllScreens, getScreenByNetId, refreshScreen, updateScreen } from "@/api/system/led";
import { VideoCamera, Close } from "@element-plus/icons-vue";
import useUserStore from "@/store/modules/user";
const userStore = useUserStore();
const { proxy } = getCurrentInstance();
//
const loading = ref(true);
const total = ref(0);
const screenList = ref([]);
const dialogVisible = ref(false);
const dialogType = ref("view"); // view, edit, add
const dialogTitle = ref("查看LED屏");
//
let timer = null;
//
const serverStatus = ref({
isRunning: false,
connectedCount: 0
});
//
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
//
const queryParams = ref({
pageNum: 1,
pageSize: 10,
deviceName: undefined,
deviceSn: undefined,
projectId: userStore.currentPrjId,
enabled: undefined
});
/** 分页处理 */
function handlePagination(data) {
queryParams.value.pageNum = data.page;
queryParams.value.pageSize = data.limit;
getList();
}
//
const form = ref({
id: undefined,
projectId: undefined,
workareaId: undefined,
deviceName: undefined,
deviceSn: undefined,
width: undefined,
height: undefined,
frequency: undefined,
drawType: undefined,
title: undefined,
enabled: 1,
state: undefined,
remark: undefined
});
//
const rules = ref({
deviceName: [
{ required: true, message: "设备名称不能为空", trigger: "blur" }
],
deviceSn: [
{ required: true, message: "设备序列号不能为空", trigger: "blur" }
],
width: [
{ required: true, message: "屏幕宽度不能为空", trigger: "blur" }
],
height: [
{ required: true, message: "屏幕高度不能为空", trigger: "blur" }
],
frequency: [
{ required: true, message: "更新频率不能为空", trigger: "blur" }
]
});
//
const dataFormRef = ref();
/** 查询LED屏配置列表 */
function getList() {
loading.value = true;
queryParams.value.projectId = userStore.currentPrjId;
listLedscreen(queryParams.value).then(response => {
screenList.value = response.rows;
total.value = response.total;
loading.value = false;
});
}
/** 获取服务器状态 */
function getServerStatus(showMsg) {
getLedServerStatus(userStore.currentPrjId).then(response => {
//
const statusText = response;
const isRunningMatch = statusText.match(/服务器运行状态: ([^,]+)/);
const connectedCountMatch = statusText.match(/已连接屏幕数量: (\d+)/);
serverStatus.value.isRunning = isRunningMatch ? isRunningMatch[1] === "运行中" : false;
serverStatus.value.connectedCount = connectedCountMatch ? parseInt(connectedCountMatch[1]) : 0;
if(showMsg){
proxy.$modal.msgSuccess("服务器状态已刷新");
}
}).catch(error => {
proxy.$modal.msgError("获取服务器状态失败: " + error.message);
});
}
/** 查询所有屏幕使用LED控制器接口 */
function getAllScreensFromController() {
loading.value = true;
getAllScreens(userStore.currentPrjId).then(response => {
// 线
const screens = response;
screenList.value = screens;
total.value = screens.length;
loading.value = false;
}).catch(error => {
proxy.$modal.msgError("获取LED屏幕列表失败: " + error.message);
loading.value = false;
});
}
/** 表格多选处理 */
function handleSelectionChange(selection) {
ids.value = selection.map(item => item.id);
single.value = selection.length !== 1;
multiple.value = !selection.length;
}
/** 刷新单个屏幕 */
function handleRefresh(row) {
refreshScreen(row.deviceSn).then(response => {
proxy.$modal.msgSuccess(response);
}).catch(error => {
proxy.$modal.msgError("刷新失败: " + error.message);
});
}
/** 批量刷新屏幕 */
function handleBatchRefresh() {
if (ids.value.length === 0) {
proxy.$modal.msgWarning("请选择要刷新的屏幕");
return;
}
proxy.$modal.confirm('是否确认刷新选中的LED屏幕').then(() => {
const promises = ids.value.map(id => {
const screen = screenList.value.find(s => s.id === id);
if (screen) {
return refreshScreen(screen.deviceSn);
}
}).filter(p => p !== undefined);
Promise.all(promises).then(results => {
proxy.$modal.msgSuccess("批量刷新请求已发送");
}).catch(error => {
proxy.$modal.msgError("批量刷新失败: " + error.message);
});
});
}
/** 查看屏幕详情 */
function handleView(row) {
dialogType.value = 'view';
dialogTitle.value = '查看LED屏';
form.value = { ...row };
dialogVisible.value = true;
}
/** 编辑屏幕 */
function handleEdit(row) {
dialogType.value = 'edit';
dialogTitle.value = '编辑LED屏';
form.value = { ...row };
dialogVisible.value = true;
}
/** 提交表单 */
function submitForm() {
dataFormRef.value.validate(valid => {
if (valid) {
updateScreen(form.value).then(response => {
proxy.$modal.msgSuccess(response);
dialogVisible.value = false;
getList();
}).catch(error => {
proxy.$modal.msgError("更新失败: " + error.message);
});
}
});
}
//
onMounted(() => {
getServerStatus();
getAllScreensFromController();
// 30
startTimer();
});
//
function startTimer() {
if (timer) {
clearInterval(timer);
}
timer = setInterval(() => {
getServerStatus();
getAllScreensFromController();
}, 30000); // 30
}
//
function stopTimer() {
if (timer) {
clearInterval(timer);
timer = null;
}
}
//
onBeforeUnmount(() => {
stopTimer();
});
</script>
<style scoped>
.status-content {
padding: 10px 0;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.dialog-footer {
text-align: right;
}
.app-container {
padding: 20px;
}
</style>