Compare commits

...

3 Commits

Author SHA1 Message Date
3ed5f81f6d 抢修和检修优化 2025-11-14 15:18:24 +08:00
8eac1edc90 备品备件优化 2025-11-14 14:58:58 +08:00
dffb65432c 对接逆变器并存库 2025-11-13 15:29:03 +08:00
20 changed files with 1560 additions and 322 deletions

View File

@ -98,6 +98,11 @@
<!-- <version>2.4.1</version>-->
<!-- </dependency>-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
</dependency>
</dependencies>

View File

@ -3,6 +3,7 @@ package org.dromara.inspection.service.impl;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.seata.spring.annotation.GlobalTransactional;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
@ -115,8 +116,11 @@ public class OpsInspectionRepairServiceImpl implements IOpsInspectionRepairServi
public Boolean insertByBo(OpsInspectionRepairBo bo) {
OpsInspectionRepair add = MapstructUtils.convert(bo, OpsInspectionRepair.class);
validEntityBeforeSave(add);
if (add == null){
throw new ServiceException("新增数据不能为空!!");
}
if (add.getFileId() != null){
if (add.getFileId() != null || !add.getFileId().isEmpty()){
String[] split = add.getFileId().split(",");
List<String> urls = new ArrayList<>();
for (String s : split) {

View File

@ -129,7 +129,7 @@ public class OpsInspectionReportServiceImpl implements IOpsInspectionReportServi
return false;
}
if (add.getFileId() != null){
if (add.getFileId() != null || !add.getFileId().isEmpty()){
String[] split = add.getFileId().split(",");
List<String> urls = new ArrayList<>();
for (String s : split) {

View File

@ -172,7 +172,7 @@ public class OpsBeipinBeijianServiceImpl extends ServiceImpl<OpsBeipinBeijianMap
for (OpsBeipinBeijianDto item : records) {
long kucunCount = item.getRuKuCount() - item.getChuKuCount();
zonCount += kucunCount;
if (kucunCount == 0 || (item.getRuKuCount() > 0 && item.getRuKuCount() / 2 > item.getKucunCount())){
if (kucunCount == 0 || (item.getRuKuCount() > 0 && item.getRuKuCount() / 2 > kucunCount)){
diCount++;
}
if (!map.containsKey(item.getShebeiType())) {

View File

@ -0,0 +1,106 @@
package org.dromara.shebei.controller;
import java.util.List;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.shebei.domain.vo.OpsSbDataVo;
import org.dromara.shebei.domain.bo.OpsSbDataBo;
import org.dromara.shebei.service.IOpsSbDataService;
import org.dromara.common.mybatis.core.page.TableDataInfo;
/**
* 运维-设备管理-设备数据
* 前端访问路由地址为:/shebei/sbData
*
* @author LionLi
* @date 2025-11-13
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/sbData")
public class OpsSbDataController extends BaseController {
private final IOpsSbDataService opsSbDataService;
/**
* 查询运维-设备管理-设备数据列表
*/
@SaCheckPermission("shebei:sbData:list")
@GetMapping("/list")
public TableDataInfo<OpsSbDataVo> list(OpsSbDataBo bo, PageQuery pageQuery) {
return opsSbDataService.queryPageList(bo, pageQuery);
}
/**
* 导出运维-设备管理-设备数据列表
*/
@SaCheckPermission("shebei:sbData:export")
@Log(title = "运维-设备管理-设备数据", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(OpsSbDataBo bo, HttpServletResponse response) {
List<OpsSbDataVo> list = opsSbDataService.queryList(bo);
ExcelUtil.exportExcel(list, "运维-设备管理-设备数据", OpsSbDataVo.class, response);
}
/**
* 获取运维-设备管理-设备数据详细信息
*
* @param id 主键
*/
@SaCheckPermission("shebei:sbData:query")
@GetMapping("/{id}")
public R<OpsSbDataVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable("id") Long id) {
return R.ok(opsSbDataService.queryById(id));
}
/**
* 新增运维-设备管理-设备数据
*/
@SaCheckPermission("shebei:sbData:add")
@Log(title = "运维-设备管理-设备数据", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody OpsSbDataBo bo) {
return toAjax(opsSbDataService.insertByBo(bo));
}
/**
* 修改运维-设备管理-设备数据
*/
@SaCheckPermission("shebei:sbData:edit")
@Log(title = "运维-设备管理-设备数据", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody OpsSbDataBo bo) {
return toAjax(opsSbDataService.updateByBo(bo));
}
/**
* 删除运维-设备管理-设备数据
*
* @param ids 主键串
*/
@SaCheckPermission("shebei:sbData:remove")
@Log(title = "运维-设备管理-设备数据", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable("ids") Long[] ids) {
return toAjax(opsSbDataService.deleteWithValidByIds(List.of(ids), true));
}
}

View File

@ -0,0 +1,62 @@
package org.dromara.shebei.domain;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.io.Serial;
/**
* 运维-设备管理-设备数据对象 ops_sb_data
*
* @author LionLi
* @date 2025-11-13
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("ops_sb_data")
public class OpsSbData extends BaseEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* id
*/
@TableId(value = "id")
private Long id;
/**
* 项目id
*/
private Long projectId;
/**
* 设备sn码
*/
private String sn;
/**
* 功能码
*/
private String functionCode;
/**
* 变量名
*/
private String variableName;
/**
* 数量
*/
private Double count;
/**
* 单位
*/
private String unit;
}

View File

@ -0,0 +1,66 @@
package org.dromara.shebei.domain.bo;
import org.dromara.shebei.domain.OpsSbData;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
/**
* 运维-设备管理-设备数据业务对象 ops_sb_data
*
* @author LionLi
* @date 2025-11-13
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = OpsSbData.class, reverseConvertGenerate = false)
public class OpsSbDataBo extends BaseEntity {
/**
* id
*/
private Long id;
/**
* 项目id
*/
@NotNull(message = "项目id不能为空", groups = { AddGroup.class, EditGroup.class })
private Long projectId;
/**
* 设备sn码
*/
@NotBlank(message = "设备sn码不能为空", groups = { AddGroup.class, EditGroup.class })
private String sn;
/**
* 功能码
*/
@NotBlank(message = "功能码不能为空", groups = { AddGroup.class, EditGroup.class })
private String functionCode;
/**
* 变量名
*/
@NotBlank(message = "变量名不能为空", groups = { AddGroup.class, EditGroup.class })
private String variableName;
/**
* 数量
*/
@NotNull(message = "数量不能为空", groups = { AddGroup.class, EditGroup.class })
private Double count;
/**
* 单位
*/
@NotBlank(message = "单位不能为空", groups = { AddGroup.class, EditGroup.class })
private String unit;
}

View File

@ -0,0 +1,75 @@
package org.dromara.shebei.domain.vo;
import java.math.BigDecimal;
import org.dromara.shebei.domain.OpsSbData;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 运维-设备管理-设备数据视图对象 ops_sb_data
*
* @author LionLi
* @date 2025-11-13
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = OpsSbData.class)
public class OpsSbDataVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* id
*/
@ExcelProperty(value = "id")
private Long id;
/**
* 项目id
*/
@ExcelProperty(value = "项目id")
private Long projectId;
/**
* 设备sn码
*/
@ExcelProperty(value = "设备sn码")
private String sn;
/**
* 功能码
*/
@ExcelProperty(value = "功能码")
private String functionCode;
/**
* 变量名
*/
@ExcelProperty(value = "变量名")
private String variableName;
/**
* 数量
*/
@ExcelProperty(value = "数量")
private Double count;
/**
* 单位
*/
@ExcelProperty(value = "单位")
private String unit;
}

View File

@ -0,0 +1,15 @@
package org.dromara.shebei.mapper;
import org.dromara.shebei.domain.OpsSbData;
import org.dromara.shebei.domain.vo.OpsSbDataVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 运维-设备管理-设备数据Mapper接口
*
* @author LionLi
* @date 2025-11-13
*/
public interface OpsSbDataMapper extends BaseMapperPlus<OpsSbData, OpsSbDataVo> {
}

View File

@ -0,0 +1,70 @@
package org.dromara.shebei.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.dromara.shebei.domain.OpsSbData;
import org.dromara.shebei.domain.vo.OpsSbDataVo;
import org.dromara.shebei.domain.bo.OpsSbDataBo;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import java.util.Collection;
import java.util.List;
/**
* 运维-设备管理-设备数据Service接口
*
* @author LionLi
* @date 2025-11-13
*/
public interface IOpsSbDataService extends IService<OpsSbData> {
/**
* 查询运维-设备管理-设备数据
*
* @param id 主键
* @return 运维-设备管理-设备数据
*/
OpsSbDataVo queryById(Long id);
/**
* 分页查询运维-设备管理-设备数据列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 运维-设备管理-设备数据分页列表
*/
TableDataInfo<OpsSbDataVo> queryPageList(OpsSbDataBo bo, PageQuery pageQuery);
/**
* 查询符合条件的运维-设备管理-设备数据列表
*
* @param bo 查询条件
* @return 运维-设备管理-设备数据列表
*/
List<OpsSbDataVo> queryList(OpsSbDataBo bo);
/**
* 新增运维-设备管理-设备数据
*
* @param bo 运维-设备管理-设备数据
* @return 是否新增成功
*/
Boolean insertByBo(OpsSbDataBo bo);
/**
* 修改运维-设备管理-设备数据
*
* @param bo 运维-设备管理-设备数据
* @return 是否修改成功
*/
Boolean updateByBo(OpsSbDataBo bo);
/**
* 校验并批量删除运维-设备管理-设备数据信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}

View File

@ -0,0 +1,140 @@
package org.dromara.shebei.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.shebei.domain.OpsSbLiebiao;
import org.dromara.shebei.mapper.OpsSbLiebiaoMapper;
import org.springframework.stereotype.Service;
import org.dromara.shebei.domain.bo.OpsSbDataBo;
import org.dromara.shebei.domain.vo.OpsSbDataVo;
import org.dromara.shebei.domain.OpsSbData;
import org.dromara.shebei.mapper.OpsSbDataMapper;
import org.dromara.shebei.service.IOpsSbDataService;
import java.util.List;
import java.util.Map;
import java.util.Collection;
/**
* 运维-设备管理-设备数据Service业务层处理
*
* @author LionLi
* @date 2025-11-13
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class OpsSbDataServiceImpl extends ServiceImpl<OpsSbDataMapper, OpsSbData> implements IOpsSbDataService {
private final OpsSbDataMapper baseMapper;
/**
* 查询运维-设备管理-设备数据
*
* @param id 主键
* @return 运维-设备管理-设备数据
*/
@Override
public OpsSbDataVo queryById(Long id){
return baseMapper.selectVoById(id);
}
/**
* 分页查询运维-设备管理-设备数据列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 运维-设备管理-设备数据分页列表
*/
@Override
public TableDataInfo<OpsSbDataVo> queryPageList(OpsSbDataBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<OpsSbData> lqw = buildQueryWrapper(bo);
Page<OpsSbDataVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询符合条件的运维-设备管理-设备数据列表
*
* @param bo 查询条件
* @return 运维-设备管理-设备数据列表
*/
@Override
public List<OpsSbDataVo> queryList(OpsSbDataBo bo) {
LambdaQueryWrapper<OpsSbData> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<OpsSbData> buildQueryWrapper(OpsSbDataBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<OpsSbData> lqw = Wrappers.lambdaQuery();
lqw.orderByAsc(OpsSbData::getId);
lqw.eq(bo.getProjectId() != null, OpsSbData::getProjectId, bo.getProjectId());
lqw.eq(StringUtils.isNotBlank(bo.getSn()), OpsSbData::getSn, bo.getSn());
lqw.eq(StringUtils.isNotBlank(bo.getFunctionCode()), OpsSbData::getFunctionCode, bo.getFunctionCode());
lqw.like(StringUtils.isNotBlank(bo.getVariableName()), OpsSbData::getVariableName, bo.getVariableName());
lqw.eq(bo.getCount() != null, OpsSbData::getCount, bo.getCount());
lqw.eq(StringUtils.isNotBlank(bo.getUnit()), OpsSbData::getUnit, bo.getUnit());
return lqw;
}
/**
* 新增运维-设备管理-设备数据
*
* @param bo 运维-设备管理-设备数据
* @return 是否新增成功
*/
@Override
public Boolean insertByBo(OpsSbDataBo bo) {
OpsSbData add = MapstructUtils.convert(bo, OpsSbData.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
}
return flag;
}
/**
* 修改运维-设备管理-设备数据
*
* @param bo 运维-设备管理-设备数据
* @return 是否修改成功
*/
@Override
public Boolean updateByBo(OpsSbDataBo bo) {
OpsSbData update = MapstructUtils.convert(bo, OpsSbData.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(OpsSbData entity){
//TODO 做一些数据校验,如唯一约束
}
/**
* 校验并批量删除运维-设备管理-设备数据信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteByIds(ids) > 0;
}
}

View File

@ -41,4 +41,6 @@ public class ModbusConstant {
0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
};
public static final int SOCKET_TIMEOUT = 30000;
public static final int PARAM_REFRESH_INTERVAL = 10;
}

View File

@ -1,3 +1,4 @@
// DeviceCache.java设备缓存类
package org.dromara.tcpfuwu.domain;
import lombok.Data;
@ -7,10 +8,9 @@ import java.util.concurrent.ConcurrentHashMap;
@Data
public class DeviceCache {
private String snCode; // 设备SN码
private long createTime; // 连接创建时间(毫秒)
private long lastHeartbeatTime; // 最后心跳时间(毫秒)
private boolean isExpired; // 是否过期
// 变量值缓存key=变量名value=解析后的值(带倍率)
private Map<String, Object> variableValues = new ConcurrentHashMap<>();
private String deviceAddr; // 设备地址IP:PORT
private String snCode; // 设备SN码
private long lastHeartbeatTime; // 最后心跳时间
private Map<String, Object> variableValues = new ConcurrentHashMap<>(); // 变量值缓存
}

View File

@ -0,0 +1,27 @@
package org.dromara.tcpfuwu.domain;
import lombok.Data;
import java.util.UUID;
/**
* 记录发送的请求信息,用于匹配响应
*/
@Data
public class ModbusRequestContext {
private String requestId; // 唯一标识如UUID
private String sn; // 设备SN
private String variable; // 变量名称(如“有功功率”“总发电量”)
private String unit; // 单位
private Long id;
private int slaveAddr; // 从站地址
private int funcCode; // 功能码
private int registerCount; // 读取寄存器数量
private int startRegister; // 起始寄存器地址(十进制,用于精准匹配)
private long sendTime; // 发送时间(用于超时判断)
// 构造时自动生成唯一ID
public ModbusRequestContext() {
this.requestId = UUID.randomUUID().toString().replace("-", "");
}
}

View File

@ -6,10 +6,10 @@ import lombok.Data;
public class ModbusVariable {
private Long id;
private String snCode; // 设备SN码
private Integer slaveId; // 从机地址
private Integer funcCode; // 功能码
private Integer startRegAddr; // 起始寄存器地址
private Integer regQuantity; // 寄存器数量
private int slaveId; // 从机地址
private int funcCode; // 功能码
private int startRegAddr; // 起始寄存器地址
private int regQuantity; // 寄存器数量
private String variableName; // 变量名
private String dataType; // 数据类型S16/U16/S32/U32/FLOAT
private Double multiplier; // 数据倍率

View File

@ -0,0 +1,76 @@
package org.dromara.tcpfuwu.server;
import org.dromara.shebei.service.IOpsSbDataService;
import org.dromara.shebei.service.IOpsSbLiebiaoService;
import org.dromara.tcpfuwu.domain.DeviceCache;
import org.dromara.tcpfuwu.handler.DeviceHandler;
import org.dromara.tcpfuwu.util.ThreadPoolUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Modbus TCP 主服务监听端口、接收设备连接、分发到DeviceHandler
* 注若用SpringBoot需加@Component并通过@Autowired注入IOpsSbLiebiaoService
*/
@Component
public class ModbusTcpServer {
// 全局设备缓存key=设备地址IP:PORTvalue=设备缓存信息(线程安全)
private final Map<String, DeviceCache> globalDeviceCache = new ConcurrentHashMap<>();
// 依赖注入查询设备变量的Spring服务
@Autowired
private IOpsSbLiebiaoService sbLiebiaoService;
@Autowired
private IOpsSbDataService sbDataService;
// 服务端口可配置在application.yml中
private static final int LISTEN_PORT = 12345;
/**
* 启动TCP服务
*/
public void start() {
try (ServerSocket serverSocket = new ServerSocket(LISTEN_PORT)) {
System.out.printf("【Modbus TCP服务启动成功】监听端口%d%n", LISTEN_PORT);
// 循环接收设备连接(主线程阻塞)
while (!Thread.currentThread().isInterrupted()) {
Socket clientSocket = serverSocket.accept(); // 阻塞等待连接
String deviceAddr = clientSocket.getInetAddress() + ":" + clientSocket.getPort();
System.out.printf("%n【设备上线】地址%sSocket%s%n", deviceAddr, clientSocket);
// 初始化设备缓存
DeviceCache deviceCache = new DeviceCache();
deviceCache.setDeviceAddr(deviceAddr);
deviceCache.setLastHeartbeatTime(System.currentTimeMillis());
globalDeviceCache.put(deviceAddr, deviceCache);
// 线程池提交设备处理任务替代new Thread()
ThreadPoolUtil.DEVICE_CONN_POOL.submit(() -> {
// 创建DeviceHandler处理单个设备
DeviceHandler deviceHandler = new DeviceHandler(
clientSocket,
deviceAddr,
deviceCache,
globalDeviceCache,
sbLiebiaoService, // 传递Spring服务
sbDataService
);
deviceHandler.handle(); // 启动设备通信逻辑
});
}
} catch (Exception e) {
System.err.printf("【Modbus TCP服务异常】启动失败%s%n", e.getMessage());
e.printStackTrace();
} finally {
// 服务停止时关闭所有线程池
ThreadPoolUtil.shutdownAll();
System.out.println("【Modbus TCP服务停止】已关闭所有线程池");
}
}
}

View File

@ -2,12 +2,11 @@ package org.dromara.tcpfuwu.starter;
import lombok.extern.slf4j.Slf4j;
import org.dromara.tcpfuwu.server.UnifiedTcpServer;
//import org.dromara.tcpfuwu.server.UnifiedTcpServer;
import org.dromara.tcpfuwu.server.ModbusTcpServer;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
/**
* Spring启动时自动初始化TCP服务器关闭时释放资源
*/
@ -15,11 +14,18 @@ import javax.annotation.PreDestroy;
@Slf4j
public class TcpServersStarter implements CommandLineRunner {
private final UnifiedTcpServer unifiedTcpServer;
// private final UnifiedTcpServer unifiedTcpServer;
private final ModbusTcpServer modbusTcpServer;
// 构造注入两个TCP服务器Bean
public TcpServersStarter( UnifiedTcpServer unifiedTcpServer) {
this.unifiedTcpServer = unifiedTcpServer;
// public TcpServersStarter( UnifiedTcpServer unifiedTcpServer) {
// this.unifiedTcpServer = unifiedTcpServer;
// }
public TcpServersStarter(ModbusTcpServer modbusTcpServer) {
this.modbusTcpServer = modbusTcpServer;
}
/**
@ -29,17 +35,19 @@ public class TcpServersStarter implements CommandLineRunner {
public void run(String... args) throws Exception {
log.info("【TCP服务器启动器】开始初始化Modbus和逆变器心跳服务器...");
// 启动两个服务器独立线程不阻塞Spring主线程
unifiedTcpServer.start();
// unifiedTcpServer.start();
modbusTcpServer.start();
log.info("【TCP服务器启动器】所有TCP服务器初始化完成");
}
/**
* Spring容器销毁前执行释放资源
*/
@PreDestroy
public void stopTcpServers() {
log.info("【TCP服务器启动器】开始关闭所有TCP服务器...");
unifiedTcpServer.stop();
log.info("【TCP服务器启动器】所有TCP服务器已关闭");
}
// /**
// * Spring容器销毁前执行释放资源
// */
// @PreDestroy
// public void stopTcpServers() {
// log.info("【TCP服务器启动器】开始关闭所有TCP服务器...");
//// unifiedTcpServer.stop();
//// modbusTcpServer.stop();
// log.info("【TCP服务器启动器】所有TCP服务器已关闭");
// }
}

View File

@ -0,0 +1,64 @@
package org.dromara.tcpfuwu.util;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 线程池工具类统一管理设备连接、Modbus发送线程
*/
public class ThreadPoolUtil {
// 设备连接线程池处理新设备连接核心线程2最大5
public static final ExecutorService DEVICE_CONN_POOL = new ThreadPoolExecutor(
5, 12, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100),
r -> new Thread(r, "device-conn-" + System.currentTimeMillis()),
new ThreadPoolExecutor.AbortPolicy()
);
// 设备连接线程池处理新设备连接核心线程2最大5
public static final ExecutorService RESPONSE_HANDLING = new ThreadPoolExecutor(
5, 12, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100),
r -> new Thread(r, "response-handling" + System.currentTimeMillis()),
new ThreadPoolExecutor.AbortPolicy()
);
// Modbus定时发送线程池每个设备一个定时任务核心线程5最大10
public static final ScheduledExecutorService MODBUS_SCHEDULER = new ScheduledThreadPoolExecutor(
5,
r -> new Thread(r, "modbus-schedule-" + System.currentTimeMillis()),
new ThreadPoolExecutor.AbortPolicy()
);
// 数据存储专用线程池(处理数据库保存操作)
public static final ExecutorService DATA_SAVE_POOL = new ThreadPoolExecutor(
5, 10, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000),
new ThreadFactory() {
private final AtomicInteger counter = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "data-save-" + counter.getAndIncrement());
}
},
new ThreadPoolExecutor.CallerRunsPolicy() // 队列满时让提交线程执行(避免任务丢失)
);
// 关闭线程池JVM退出时调用
public static void shutdownAll() {
DEVICE_CONN_POOL.shutdown();
MODBUS_SCHEDULER.shutdown();
try {
if (!DEVICE_CONN_POOL.awaitTermination(3, TimeUnit.SECONDS)) {
DEVICE_CONN_POOL.shutdownNow();
}
if (!MODBUS_SCHEDULER.awaitTermination(3, TimeUnit.SECONDS)) {
MODBUS_SCHEDULER.shutdownNow();
}
} catch (InterruptedException e) {
DEVICE_CONN_POOL.shutdownNow();
MODBUS_SCHEDULER.shutdownNow();
}
}
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.shebei.mapper.OpsSbDataMapper">
</mapper>