对接逆变器并存库
This commit is contained in:
@ -98,6 +98,11 @@
|
||||
<!-- <version>2.4.1</version>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
|
||||
}
|
||||
@ -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> {
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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<>(); // 变量值缓存
|
||||
|
||||
}
|
||||
|
||||
@ -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("-", "");
|
||||
}
|
||||
}
|
||||
@ -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; // 数据倍率
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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:PORT),value=设备缓存信息(线程安全)
|
||||
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【设备上线】地址:%s,Socket:%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服务停止】已关闭所有线程池");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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服务器已关闭");
|
||||
// }
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
Reference in New Issue
Block a user