Compare commits
5 Commits
3862fc379d
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 3ed5f81f6d | |||
| 8eac1edc90 | |||
| dffb65432c | |||
| 9b4c3092c1 | |||
| eaf90fb8b5 |
@ -98,6 +98,11 @@
|
|||||||
<!-- <version>2.4.1</version>-->
|
<!-- <version>2.4.1</version>-->
|
||||||
<!-- </dependency>-->
|
<!-- </dependency>-->
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.redisson</groupId>
|
||||||
|
<artifactId>redisson-spring-boot-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package org.dromara.inspection.service.impl;
|
|||||||
import org.apache.dubbo.config.annotation.DubboReference;
|
import org.apache.dubbo.config.annotation.DubboReference;
|
||||||
import org.apache.seata.spring.annotation.GlobalTransactional;
|
import org.apache.seata.spring.annotation.GlobalTransactional;
|
||||||
import org.dromara.common.core.domain.R;
|
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.MapstructUtils;
|
||||||
import org.dromara.common.core.utils.StringUtils;
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||||
@ -115,8 +116,11 @@ public class OpsInspectionRepairServiceImpl implements IOpsInspectionRepairServi
|
|||||||
public Boolean insertByBo(OpsInspectionRepairBo bo) {
|
public Boolean insertByBo(OpsInspectionRepairBo bo) {
|
||||||
OpsInspectionRepair add = MapstructUtils.convert(bo, OpsInspectionRepair.class);
|
OpsInspectionRepair add = MapstructUtils.convert(bo, OpsInspectionRepair.class);
|
||||||
validEntityBeforeSave(add);
|
validEntityBeforeSave(add);
|
||||||
|
if (add == null){
|
||||||
|
throw new ServiceException("新增数据不能为空!!");
|
||||||
|
}
|
||||||
|
|
||||||
if (add.getFileId() != null){
|
if (add.getFileId() != null || !add.getFileId().isEmpty()){
|
||||||
String[] split = add.getFileId().split(",");
|
String[] split = add.getFileId().split(",");
|
||||||
List<String> urls = new ArrayList<>();
|
List<String> urls = new ArrayList<>();
|
||||||
for (String s : split) {
|
for (String s : split) {
|
||||||
|
|||||||
@ -129,7 +129,7 @@ public class OpsInspectionReportServiceImpl implements IOpsInspectionReportServi
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (add.getFileId() != null){
|
if (add.getFileId() != null || !add.getFileId().isEmpty()){
|
||||||
String[] split = add.getFileId().split(",");
|
String[] split = add.getFileId().split(",");
|
||||||
List<String> urls = new ArrayList<>();
|
List<String> urls = new ArrayList<>();
|
||||||
for (String s : split) {
|
for (String s : split) {
|
||||||
@ -283,8 +283,14 @@ public class OpsInspectionReportServiceImpl implements IOpsInspectionReportServi
|
|||||||
//完成率
|
//完成率
|
||||||
//完成数
|
//完成数
|
||||||
int finishCount = thisMonth.stream().filter(vo -> vo.getStatus().equals("3")).toList().size();
|
int finishCount = thisMonth.stream().filter(vo -> vo.getStatus().equals("3")).toList().size();
|
||||||
BigDecimal divide = BigDecimal.valueOf(finishCount).divide(BigDecimal.valueOf(thisMonth.size()), 2, RoundingMode.HALF_UP);
|
BigDecimal divide = BigDecimal.ZERO;
|
||||||
recordVo.setWcl(divide.multiply(new BigDecimal("100")).toString());
|
if (thisMonth.size() == 0){
|
||||||
|
divide = BigDecimal.valueOf(finishCount).divide(BigDecimal.valueOf(thisMonth.size()), 2, RoundingMode.HALF_UP);
|
||||||
|
recordVo.setWcl(divide.multiply(new BigDecimal("100")).toString());
|
||||||
|
}else {
|
||||||
|
recordVo.setWcl("0");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//统计上一月的数据
|
//统计上一月的数据
|
||||||
firstDay = LocalDate.now().minusMonths(1).withDayOfMonth(1);
|
firstDay = LocalDate.now().minusMonths(1).withDayOfMonth(1);
|
||||||
|
|||||||
@ -172,7 +172,7 @@ public class OpsBeipinBeijianServiceImpl extends ServiceImpl<OpsBeipinBeijianMap
|
|||||||
for (OpsBeipinBeijianDto item : records) {
|
for (OpsBeipinBeijianDto item : records) {
|
||||||
long kucunCount = item.getRuKuCount() - item.getChuKuCount();
|
long kucunCount = item.getRuKuCount() - item.getChuKuCount();
|
||||||
zonCount += kucunCount;
|
zonCount += kucunCount;
|
||||||
if (kucunCount == 0 || (item.getRuKuCount() > 0 && item.getRuKuCount() / 2 > item.getKucunCount())){
|
if (kucunCount == 0 || (item.getRuKuCount() > 0 && item.getRuKuCount() / 2 > kucunCount)){
|
||||||
diCount++;
|
diCount++;
|
||||||
}
|
}
|
||||||
if (!map.containsKey(item.getShebeiType())) {
|
if (!map.containsKey(item.getShebeiType())) {
|
||||||
|
|||||||
@ -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,
|
0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
|
||||||
0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
|
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;
|
package org.dromara.tcpfuwu.domain;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@ -7,10 +8,9 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class DeviceCache {
|
public class DeviceCache {
|
||||||
private String snCode; // 设备SN码
|
private String deviceAddr; // 设备地址(IP:PORT)
|
||||||
private long createTime; // 连接创建时间(毫秒)
|
private String snCode; // 设备SN码
|
||||||
private long lastHeartbeatTime; // 最后心跳时间(毫秒)
|
private long lastHeartbeatTime; // 最后心跳时间
|
||||||
private boolean isExpired; // 是否过期
|
private Map<String, Object> variableValues = new ConcurrentHashMap<>(); // 变量值缓存
|
||||||
// 变量值缓存:key=变量名,value=解析后的值(带倍率)
|
|
||||||
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 {
|
public class ModbusVariable {
|
||||||
private Long id;
|
private Long id;
|
||||||
private String snCode; // 设备SN码
|
private String snCode; // 设备SN码
|
||||||
private Integer slaveId; // 从机地址
|
private int slaveId; // 从机地址
|
||||||
private Integer funcCode; // 功能码
|
private int funcCode; // 功能码
|
||||||
private Integer startRegAddr; // 起始寄存器地址
|
private int startRegAddr; // 起始寄存器地址
|
||||||
private Integer regQuantity; // 寄存器数量
|
private int regQuantity; // 寄存器数量
|
||||||
private String variableName; // 变量名
|
private String variableName; // 变量名
|
||||||
private String dataType; // 数据类型(S16/U16/S32/U32/FLOAT)
|
private String dataType; // 数据类型(S16/U16/S32/U32/FLOAT)
|
||||||
private Double multiplier; // 数据倍率
|
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 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.boot.CommandLineRunner;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import javax.annotation.PreDestroy;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spring启动时自动初始化TCP服务器,关闭时释放资源
|
* Spring启动时自动初始化TCP服务器,关闭时释放资源
|
||||||
*/
|
*/
|
||||||
@ -15,11 +14,18 @@ import javax.annotation.PreDestroy;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class TcpServersStarter implements CommandLineRunner {
|
public class TcpServersStarter implements CommandLineRunner {
|
||||||
|
|
||||||
private final UnifiedTcpServer unifiedTcpServer;
|
// private final UnifiedTcpServer unifiedTcpServer;
|
||||||
|
private final ModbusTcpServer modbusTcpServer;
|
||||||
|
|
||||||
|
|
||||||
// 构造注入两个TCP服务器Bean
|
// 构造注入两个TCP服务器Bean
|
||||||
public TcpServersStarter( UnifiedTcpServer unifiedTcpServer) {
|
// public TcpServersStarter( UnifiedTcpServer unifiedTcpServer) {
|
||||||
this.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 {
|
public void run(String... args) throws Exception {
|
||||||
log.info("【TCP服务器启动器】开始初始化Modbus和逆变器心跳服务器...");
|
log.info("【TCP服务器启动器】开始初始化Modbus和逆变器心跳服务器...");
|
||||||
// 启动两个服务器(独立线程,不阻塞Spring主线程)
|
// 启动两个服务器(独立线程,不阻塞Spring主线程)
|
||||||
unifiedTcpServer.start();
|
// unifiedTcpServer.start();
|
||||||
|
modbusTcpServer.start();
|
||||||
log.info("【TCP服务器启动器】所有TCP服务器初始化完成");
|
log.info("【TCP服务器启动器】所有TCP服务器初始化完成");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Spring容器销毁前执行(释放资源)
|
// * Spring容器销毁前执行(释放资源)
|
||||||
*/
|
// */
|
||||||
@PreDestroy
|
// @PreDestroy
|
||||||
public void stopTcpServers() {
|
// public void stopTcpServers() {
|
||||||
log.info("【TCP服务器启动器】开始关闭所有TCP服务器...");
|
// log.info("【TCP服务器启动器】开始关闭所有TCP服务器...");
|
||||||
unifiedTcpServer.stop();
|
//// unifiedTcpServer.stop();
|
||||||
log.info("【TCP服务器启动器】所有TCP服务器已关闭");
|
//// 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