diff --git a/ruoyi-api/sis-api/src/main/java/org/dromara/sis/api/RemoteHikDeviceService.java b/ruoyi-api/sis-api/src/main/java/org/dromara/sis/api/RemoteDeviceService.java similarity index 56% rename from ruoyi-api/sis-api/src/main/java/org/dromara/sis/api/RemoteHikDeviceService.java rename to ruoyi-api/sis-api/src/main/java/org/dromara/sis/api/RemoteDeviceService.java index 0baa5659..95929d84 100644 --- a/ruoyi-api/sis-api/src/main/java/org/dromara/sis/api/RemoteHikDeviceService.java +++ b/ruoyi-api/sis-api/src/main/java/org/dromara/sis/api/RemoteDeviceService.java @@ -1,15 +1,17 @@ package org.dromara.sis.api; +import org.dromara.sis.api.domain.RemoteSdkChannel; import org.dromara.sis.api.domain.RemoteSisDeviceChannel; import org.dromara.sis.api.domain.RemoteSisDeviceManage; import java.util.List; -public interface RemoteHikDeviceService { +public interface RemoteDeviceService { /** * 查询所有的海康设备信息 + * * @return 返回海康设备列表 */ List queryHikDevices(); @@ -17,6 +19,7 @@ public interface RemoteHikDeviceService { /** * 更新设备在线状态 + * * @param item 设备信息 * @return 返回是否操作成功 */ @@ -24,6 +27,7 @@ public interface RemoteHikDeviceService { /** * 查询设备通道信息 + * * @param deviceIp 设备ip * @return 返回通道列表 */ @@ -31,8 +35,32 @@ public interface RemoteHikDeviceService { /** * 更新设备通道在线状态 + * * @param deviceIp 设备ip * @return 返回是否成功 */ Boolean updateDeviceChannelState(String deviceIp, Integer onLineState); + + /** + * 根据ip列表删除设备通道 + * + * @param list ip列表 + * @return 返回删除条数 + */ + Integer deleteByChannelIps(List list); + + /** + * 批量写入设备通道 + * + * @param insertData 写入数据 + * @return 返回写入条数 + */ + Boolean insertChannel(List insertData); + + /** + * 更新设备通道信息 + * @param updateData 更新数据 + * @return 返回更新成功数量 + */ + Integer updateChannelInfo(List updateData); } diff --git a/ruoyi-api/sis-api/src/main/java/org/dromara/sis/api/RemoteSisAuth.java b/ruoyi-api/sis-api/src/main/java/org/dromara/sis/api/RemoteSisAuth.java deleted file mode 100644 index d177bcc8..00000000 --- a/ruoyi-api/sis-api/src/main/java/org/dromara/sis/api/RemoteSisAuth.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.dromara.sis.api; - -import org.dromara.sis.api.domain.RemotePersonAuth; - -import java.util.Collection; - -/** - * @author lsm - * @apiNote RemoteSisAuth - * @since 2025/7/24 - */ -public interface RemoteSisAuth { - - Boolean personAuth(RemotePersonAuth personAuth); - - Boolean updatePersonAuth(RemotePersonAuth personAuth); - - Boolean deletePersonAuth(Collection personId, Collection e8Ids); -} diff --git a/ruoyi-api/sis-api/src/main/java/org/dromara/sis/api/RemoteSisAuthService.java b/ruoyi-api/sis-api/src/main/java/org/dromara/sis/api/RemoteSisAuthService.java new file mode 100644 index 00000000..254ee655 --- /dev/null +++ b/ruoyi-api/sis-api/src/main/java/org/dromara/sis/api/RemoteSisAuthService.java @@ -0,0 +1,75 @@ +package org.dromara.sis.api; + +import org.dromara.sis.api.domain.RemotePersonAuth; + +import java.util.Collection; + +/** + * @author lsm + * @apiNote RemoteSisAuthService + * @since 2025/7/24 + */ +public interface RemoteSisAuthService { + + /** + * 人员授权 + * + * @param personAuth 人员授权信息 + * @return Boolean + */ + Boolean personAuth(RemotePersonAuth personAuth); + + /** + * 删除人员授权信息 + * + * @param personIds 入驻员工ids + * @param e8Ids e8平台人员id是 + * @return Boolean + */ + Boolean deletePersonAuth(Collection personIds, Collection e8Ids); + + /** + * 查询人员授权信息 + * + * @param authGroupId 权限组id + * @param personId 人员id + * @return Boolean + */ + Boolean queryPersonAuth(Long authGroupId, Long personId); + + /** + * 通过MD5,查询图片id + * + * @param imgMd5 图片MD5 + * @return Long + */ + Long queryImgIdByImgMd5(String imgMd5); + + /** + * 图片写入华为盒子 + * + * @param person 人员信息 + * @param imgByte 图片字节数组 + * @return Long 图片id + */ + Long syncHuaweiBox(RemotePersonAuth person, byte[] imgByte); + + /** + * 更新人像信息 + * + * @param id 入驻员工id + * @param huaweiBoxId 华为盒子id + * @param md5Str 图片MD5 + * @return Boolean + */ + Boolean updateImgByPersonId(Long id, Long huaweiBoxId, String md5Str); + + /** + * 图片写入E8平台 + * + * @param person 人员信息 + * @param imgByte 图片字节数组 + * @return Long e8平台id + */ + Long syncE8Plat(RemotePersonAuth person, byte[] imgByte); +} diff --git a/ruoyi-api/sis-api/src/main/java/org/dromara/sis/api/domain/RemoteSdkChannel.java b/ruoyi-api/sis-api/src/main/java/org/dromara/sis/api/domain/RemoteSdkChannel.java index a2ab7a48..25422584 100644 --- a/ruoyi-api/sis-api/src/main/java/org/dromara/sis/api/domain/RemoteSdkChannel.java +++ b/ruoyi-api/sis-api/src/main/java/org/dromara/sis/api/domain/RemoteSdkChannel.java @@ -5,6 +5,23 @@ import lombok.Data; @Data public class RemoteSdkChannel { + + private Long deviceId; + + private String nvrIp; + + private Integer nvrPort; + + private String nvrAccount; + + private String nvrPwd; + + private String nvrFactoryNo; + + private Long groupId; + + private String tenantId; + /** * 通道id */ diff --git a/ruoyi-api/sis-api/src/main/java/org/dromara/sis/api/domain/RemoteSisDeviceManage.java b/ruoyi-api/sis-api/src/main/java/org/dromara/sis/api/domain/RemoteSisDeviceManage.java index 02253ef8..8cd5247b 100644 --- a/ruoyi-api/sis-api/src/main/java/org/dromara/sis/api/domain/RemoteSisDeviceManage.java +++ b/ruoyi-api/sis-api/src/main/java/org/dromara/sis/api/domain/RemoteSisDeviceManage.java @@ -62,4 +62,9 @@ public class RemoteSisDeviceManage { * 设备组id */ private Long groupId; + + /** + * 租户编号 + */ + private String tenantId; } diff --git a/ruoyi-modules/Property/src/main/java/org/dromara/property/controller/ResidentPersonController.java b/ruoyi-modules/Property/src/main/java/org/dromara/property/controller/ResidentPersonController.java index 8dc6a506..2ac858dc 100644 --- a/ruoyi-modules/Property/src/main/java/org/dromara/property/controller/ResidentPersonController.java +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/controller/ResidentPersonController.java @@ -3,15 +3,18 @@ package org.dromara.property.controller; import java.util.ArrayList; import java.util.List; +import cn.dev33.satoken.stp.StpUtil; import lombok.RequiredArgsConstructor; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.constraints.*; import cn.dev33.satoken.annotation.SaCheckPermission; +import lombok.extern.slf4j.Slf4j; import org.dromara.common.excel.core.ExcelResult; import org.dromara.property.domain.vo.ResidentPersonImportVo; import org.dromara.property.listener.ResidentPersonImportListener; import org.dromara.property.utils.UploadFaceUtil; import org.springframework.http.MediaType; +import org.springframework.scheduling.annotation.Async; import org.springframework.web.bind.annotation.*; import org.springframework.validation.annotation.Validated; import org.dromara.common.idempotent.annotation.RepeatSubmit; @@ -34,8 +37,9 @@ import org.springframework.web.multipart.MultipartFile; * 前端访问路由地址为:/property/person * * @author mocheng - * @date 2025-06-19 + * @since 2025-06-19 */ +@Slf4j @Validated @RequiredArgsConstructor @RestController @@ -124,8 +128,18 @@ public class ResidentPersonController extends BaseController { @SaCheckPermission("property:person:import") @PostMapping(value = "/importData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public R importData(@RequestPart("file") MultipartFile file, boolean updateSupport, Long unitId) throws Exception { - ExcelResult result = ExcelUtil.importExcel(file.getInputStream(), ResidentPersonImportVo.class, new ResidentPersonImportListener(updateSupport, unitId)); - return R.ok(result.getAnalysis()); + asyncImportExcel(StpUtil.getTokenValue(), file, updateSupport, unitId); + return R.ok("文件上传成功,请等待处理!"); + } + + @Async + public void asyncImportExcel(String tokenValue, MultipartFile file, Boolean updateSupport, Long unitId) { + try { + StpUtil.setTokenValueToStorage(tokenValue); + ExcelUtil.importExcel(file.getInputStream(), ResidentPersonImportVo.class, new ResidentPersonImportListener(updateSupport, unitId)); + } catch (Exception e) { + log.info("处理导入入驻员工Excel文件时出错!"); + } } /** @@ -139,12 +153,22 @@ public class ResidentPersonController extends BaseController { /** * 导入人脸数据 * - * @param file 导入文件 + * @param file 导入文件 * @param unitId 单位ID */ @PostMapping(value = "/importFace", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public R importFace(@RequestPart("file") MultipartFile file, Long unitId) { - uploadFaceUtil.processFaceZip(file, unitId); - return R.ok(); + asyncImportFace(StpUtil.getTokenValue(), file, unitId); + return R.ok("文件上传成功,请等待处理!"); + } + + @Async + public void asyncImportFace(String tokenValue, MultipartFile file, Long unitId) { + try { + StpUtil.setTokenValueToStorage(tokenValue); + uploadFaceUtil.processFaceZip(file, unitId); + } catch (Exception e) { + log.info("处理人脸压缩包时出错"); + } } } diff --git a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/CleanOrder.java b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/CleanOrder.java index 88a2afae..53cf2f3c 100644 --- a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/CleanOrder.java +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/CleanOrder.java @@ -56,7 +56,22 @@ public class CleanOrder extends TenantEntity { * 单价 */ private Double prices; - + /** + * 保洁类型 + */ + private String type; + /** + * 评价 + */ + private Integer serviceEvalua; + /** + * 评价文本 + */ + private String serviceEvaluaText; + /** + * 图片 + */ + private String imgUrl; /** * 总价 */ diff --git a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/Meet.java b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/Meet.java index 0262f96e..f321bb21 100644 --- a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/Meet.java +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/Meet.java @@ -32,6 +32,16 @@ public class Meet extends TenantEntity { * 会议室名称 */ private String name; + /** + * 会议室类型( + * 1标准会议室 + * 2培训会议室 + * 3治谈会议室 + * 4多媒体会议室 + * 5圆形会议室 + * 6贵宾会议室) + */ + private String meetingRoomType; /** * 位置 diff --git a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/ServiceWorkOrders.java b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/ServiceWorkOrders.java index fb90a2d4..d3313a71 100644 --- a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/ServiceWorkOrders.java +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/ServiceWorkOrders.java @@ -1,5 +1,6 @@ package org.dromara.property.domain; +import cn.idev.excel.annotation.ExcelProperty; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; @@ -38,7 +39,10 @@ public class ServiceWorkOrders extends TenantEntity { * 工单名称 */ private String orderName; - + /** + * 权重 + */ + private String processingWeight; /** * 工单类型 */ @@ -74,6 +78,7 @@ public class ServiceWorkOrders extends TenantEntity { */ private String location; + /** * 计划完成时间 */ @@ -88,6 +93,18 @@ public class ServiceWorkOrders extends TenantEntity { * 评价 */ private Integer serviceEvalua; + /** + * 评价文本 + */ + private String serviceEvaluaText; + /** + * 图片 + */ + private String imgUrl; + /** + * 备注 + */ + private String remark; /** * 是否超时 diff --git a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/TbRoom.java b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/TbRoom.java index 0c6536b0..9df7bc42 100644 --- a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/TbRoom.java +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/TbRoom.java @@ -84,6 +84,13 @@ public class TbRoom extends TenantEntity { * 状态('空置','已售','已租','自用') */ private Integer status; + /** + * 是否重要(1非常重要、2重要、3一般) + */ + private String isMatter; - + /** + * 是否重要(1非常重要、2重要、3一般) + */ + private String imgUrl; } diff --git a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/bo/CleanOrderBo.java b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/bo/CleanOrderBo.java index a7f43f2e..056fa091 100644 --- a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/bo/CleanOrderBo.java +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/bo/CleanOrderBo.java @@ -44,7 +44,11 @@ public class CleanOrderBo extends BaseEntity { * 面积 */ private Double area; - + /** + * 保洁类型 + */ + @NotBlank(message = "保洁类型不能为空", groups = { AddGroup.class, EditGroup.class }) + private String type; /** * 保洁id */ @@ -91,6 +95,18 @@ public class CleanOrderBo extends BaseEntity { * 单位id */ @NotNull(message = "单位id不能为空", groups = {AddGroup.class, EditGroup.class}) + /** + * 评价 + */ + private Integer serviceEvalua; + /** + * 评价文本 + */ + private String serviceEvaluaText; + /** + * 图片 + */ + private String imgUrl; private Long unitId; // /** diff --git a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/bo/MeetBo.java b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/bo/MeetBo.java index 65e01988..1c4cd02a 100644 --- a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/bo/MeetBo.java +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/bo/MeetBo.java @@ -1,5 +1,6 @@ package org.dromara.property.domain.bo; +import cn.idev.excel.annotation.ExcelProperty; import com.baomidou.mybatisplus.annotation.TableId; import org.dromara.property.domain.Meet; import org.dromara.common.mybatis.core.domain.BaseEntity; @@ -35,7 +36,17 @@ public class MeetBo extends BaseEntity { */ @NotBlank(message = "会议室名称不能为空") private String name; - + /** + * 会议室类型( + * 1标准会议室 + * 2培训会议室 + * 3治谈会议室 + * 4多媒体会议室 + * 5圆形会议室 + * 6贵宾会议室) + */ + @NotBlank(message = "会议室类型不能为空") + private String meetingRoomType; /** * 位置 */ diff --git a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/bo/ResidentPersonBo.java b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/bo/ResidentPersonBo.java index efd5adf6..08794779 100644 --- a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/bo/ResidentPersonBo.java +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/bo/ResidentPersonBo.java @@ -37,7 +37,7 @@ public class ResidentPersonBo extends BaseEntity { /** * 联系电话 */ - @NotBlank(message = "联系电话不能为空", groups = {AddGroup.class, EditGroup.class}) +// @NotBlank(message = "联系电话不能为空", groups = {AddGroup.class, EditGroup.class}) private String phone; /** * 人员类型 @@ -53,7 +53,7 @@ public class ResidentPersonBo extends BaseEntity { /** * 证件号 */ - @NotBlank(message = "证件号不能为空", groups = {AddGroup.class, EditGroup.class}) +// @NotBlank(message = "证件号不能为空", groups = {AddGroup.class, EditGroup.class}) private String idCard; /** diff --git a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/bo/ServiceWorkOrdersBo.java b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/bo/ServiceWorkOrdersBo.java index dc33ecea..c7c27efe 100644 --- a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/bo/ServiceWorkOrdersBo.java +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/bo/ServiceWorkOrdersBo.java @@ -1,6 +1,7 @@ package org.dromara.property.domain.bo; +import cn.idev.excel.annotation.ExcelProperty; import io.github.linpeilie.annotations.AutoMapper; import jakarta.validation.constraints.NotNull; import lombok.Data; @@ -38,7 +39,11 @@ public class ServiceWorkOrdersBo extends BaseEntity { */ @NotNull(message = "工单名称不能为空", groups = { EditGroup.class }) private String orderName; - + /** + * 权重 + */ + @ExcelProperty(value = "权重") + private String processingWeight; /** * 工单类型 */ @@ -76,7 +81,7 @@ public class ServiceWorkOrdersBo extends BaseEntity { /** * 计划完成时间 */ - @NotNull(message = "计划完成时间不能为空", groups = { EditGroup.class }) + //@NotNull(message = "计划完成时间不能为空", groups = { EditGroup.class }) private Date planCompleTime; /** @@ -88,6 +93,18 @@ public class ServiceWorkOrdersBo extends BaseEntity { * 评价 */ private Integer serviceEvalua; + /** + * 评价文本 + */ + private String serviceEvaluaText; + /** + * 图片 + */ + private String imgUrl; + /** + * 备注 + */ + private String remark; /** * 是否超时 diff --git a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/bo/TbRoomBo.java b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/bo/TbRoomBo.java index bd2a9c6a..bfc8aeeb 100644 --- a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/bo/TbRoomBo.java +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/bo/TbRoomBo.java @@ -71,5 +71,12 @@ public class TbRoomBo extends BaseEntity { */ private Integer status; - + /** + * 是否重要(1非常重要、2重要、3一般) + */ + private String isMatter; + /** + * 是否重要(1非常重要、2重要、3一般) + */ + private String imgUrl; } diff --git a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/vo/CleanOrderVo.java b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/vo/CleanOrderVo.java index 2017848a..e078a789 100644 --- a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/vo/CleanOrderVo.java +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/vo/CleanOrderVo.java @@ -51,7 +51,22 @@ public class CleanOrderVo implements Serializable { */ @ExcelProperty(value = "面积") private Long area; - + /** + * 保洁类型 + */ + private String type; + /** + * 评价 + */ + private Integer serviceEvalua; + /** + * 评价文本 + */ + private String serviceEvaluaText; + /** + * 图片 + */ + private String imgUrl; // /** // * 保洁id // */ diff --git a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/vo/MeetVo.java b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/vo/MeetVo.java index fd2c71ff..a0b2335c 100644 --- a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/vo/MeetVo.java +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/vo/MeetVo.java @@ -37,7 +37,17 @@ public class MeetVo implements Serializable { */ @ExcelProperty(value = "会议室名称") private String name; - + /** + * 会议室类型( + * 1标准会议室 + * 2培训会议室 + * 3治谈会议室 + * 4多媒体会议室 + * 5圆形会议室 + * 6贵宾会议室) + */ + @ExcelProperty(value = "会议室类型") + private String meetingRoomType; /** * 会议室位置 */ diff --git a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/vo/ServiceWorkOrderAnalysisVo.java b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/vo/ServiceWorkOrderAnalysisVo.java index 9687a5db..6c63a297 100644 --- a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/vo/ServiceWorkOrderAnalysisVo.java +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/vo/ServiceWorkOrderAnalysisVo.java @@ -39,6 +39,20 @@ public class ServiceWorkOrderAnalysisVo { this.orderCount = orderCount; } } + + /** + *获取不满意工单名称 + */ + @Data + @Accessors(chain = true) + public static class DissatisfactionVo { + private String orderName; // 工单名称 + private Long id; // 工单id + public DissatisfactionVo(String orderName, Long id) { + this.orderName = orderName; + this.id = id; + } + } /** * 满意度图表数据对象 */ @@ -86,6 +100,7 @@ public class ServiceWorkOrderAnalysisVo { private List satisfactionRateList; private List satisfactionChartList; private List recentSixMonthWorkOrders; + private List dissatisfactionVos; // 私有构造函数,确保使用Builder模式构建对象 private ServiceWorkOrderAnalysisVo() {} @@ -107,6 +122,7 @@ public class ServiceWorkOrderAnalysisVo { private List satisfactionRateList; private List satisfactionChartList; private List recentSixMonthWorkOrders; + private List dissatisfactionVos; public ServiceWorkOrderAnalysisVo build() { ServiceWorkOrderAnalysisVo vo = new ServiceWorkOrderAnalysisVo(); @@ -123,6 +139,7 @@ public class ServiceWorkOrderAnalysisVo { vo.satisfactionRateList = this.satisfactionRateList; vo.satisfactionChartList = this.satisfactionChartList; vo.recentSixMonthWorkOrders = this.recentSixMonthWorkOrders; + vo.dissatisfactionVos = this.dissatisfactionVos; return vo; } } diff --git a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/vo/ServiceWorkOrdersVo.java b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/vo/ServiceWorkOrdersVo.java index ac45e2f2..f8d2f936 100644 --- a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/vo/ServiceWorkOrdersVo.java +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/vo/ServiceWorkOrdersVo.java @@ -37,6 +37,11 @@ public class ServiceWorkOrdersVo implements Serializable { */ @ExcelProperty(value = "工单编号") private String orderNo; + /** + * 权重 + */ + @ExcelProperty(value = "权重") + private String processingWeight; /** * 工单名称 @@ -123,14 +128,20 @@ public class ServiceWorkOrdersVo implements Serializable { private String isTimeOut; /** - * 搜索值 + * 评价文本 */ - @ExcelProperty(value = "搜索值") - private String searchValue; + private String serviceEvaluaText; + /** + * 图片 + */ + private String imgUrl; + /** + * 备注 + */ + private String remark; /** * 创建时间 */ - @ExcelProperty(value = "创建时间") private Date createTime; } diff --git a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/vo/TbRoomVo.java b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/vo/TbRoomVo.java index 533b7dba..4290fc39 100644 --- a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/vo/TbRoomVo.java +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/vo/TbRoomVo.java @@ -112,5 +112,12 @@ public class TbRoomVo implements Serializable { @Translation(type = TransConstant.DICT_TYPE_TO_LABEL, mapper = "status", other = "wy_fjzt") private String statusName; - + /** + * 是否重要(1非常重要、2重要、3一般) + */ + private String isMatter; + /** + * 图片 + */ + private String imgUrl; } diff --git a/ruoyi-modules/Property/src/main/java/org/dromara/property/listener/ResidentPersonImportListener.java b/ruoyi-modules/Property/src/main/java/org/dromara/property/listener/ResidentPersonImportListener.java index 678fcf45..0231a2cf 100644 --- a/ruoyi-modules/Property/src/main/java/org/dromara/property/listener/ResidentPersonImportListener.java +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/listener/ResidentPersonImportListener.java @@ -57,33 +57,31 @@ public class ResidentPersonImportListener extends AnalysisEventListener list = new ArrayList<>(); + ResidentPersonVo person = new ResidentPersonVo(); // 判断证件号是否为空 if (StringUtils.isEmpty(personVo.getIdCard())) { failureNum++; failureMsg.append("
").append(failureNum).append("、账号 ").append(personVo.getUserName()).append(" 证件号不能为空!"); } else { - ResidentPersonBo personBo = new ResidentPersonBo(); - personBo.setUnitId(unitId); - personBo.setIdCard(personVo.getIdCard()); - list = residentPersonService.queryList(personBo); + person = residentPersonService.queryByUnitIdAndName(unitId, person.getUserName()); } try { - if (list.isEmpty()) { // 判断当前单位是否已存在该用户 + if (person == null) { // 判断当前单位是否已存在该用户 ResidentPersonBo bo = BeanUtil.toBean(personVo, ResidentPersonBo.class); ValidatorUtils.validate(bo); bo.setState(1L); bo.setUnitId(unitId); bo.setTime(new Date()); - bo.setUnitName(unitVo.getName()); + bo.setUnitName(unitVo.getName().trim()); bo.setAuthGroupId(unitVo.getAuthGroupId()); bo.setAuthBegDate(unitVo.getAuthBegDate()); bo.setAuthEndDate(unitVo.getAuthEndDate()); + bo.setUserName(personVo.getUserName().replaceAll("[\\s\u3000]", "")); residentPersonService.insertByBo(bo); successNum++; successMsg.append("
").append(successNum).append("、账号 ").append(bo.getUserName()).append(" 导入成功"); } else if (isUpdateSupport) { - Long id = list.get(0).getUserId(); + Long id = person.getUserId(); ResidentPersonBo bo = BeanUtil.toBean(personVo, ResidentPersonBo.class); bo.setId(id); ValidatorUtils.validate(bo); @@ -93,7 +91,7 @@ public class ResidentPersonImportListener extends AnalysisEventListener").append(successNum).append("、账号 ").append(bo.getUserName()).append(" 更新成功"); } else { failureNum++; - failureMsg.append("
").append(failureNum).append("、账号 ").append(list.get(0).getUserName()).append(" 已存在"); + failureMsg.append("
").append(failureNum).append("、账号 ").append(personVo.getUserName()).append(" 已存在"); } } catch (Exception e) { diff --git a/ruoyi-modules/Property/src/main/java/org/dromara/property/service/IResidentPersonService.java b/ruoyi-modules/Property/src/main/java/org/dromara/property/service/IResidentPersonService.java index fde3919a..6b9251bc 100644 --- a/ruoyi-modules/Property/src/main/java/org/dromara/property/service/IResidentPersonService.java +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/service/IResidentPersonService.java @@ -69,7 +69,7 @@ public interface IResidentPersonService { /** * 获取单位人员数量 * - * @param unitId 单元id + * @param unitId 单位id * @return Long */ Long queryPersonCount(Long unitId); @@ -80,4 +80,12 @@ public interface IResidentPersonService { * @return List */ List queryUnAuthPerson(); + + /** + * 通过单位和姓名,查询人员信息 + * + * @param unitId 单位id + * @param name 姓名 + */ + ResidentPersonVo queryByUnitIdAndName(Long unitId, String name); } diff --git a/ruoyi-modules/Property/src/main/java/org/dromara/property/service/impl/MeetAttachServiceImpl.java b/ruoyi-modules/Property/src/main/java/org/dromara/property/service/impl/MeetAttachServiceImpl.java index 2c80ce61..9e7c4419 100644 --- a/ruoyi-modules/Property/src/main/java/org/dromara/property/service/impl/MeetAttachServiceImpl.java +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/service/impl/MeetAttachServiceImpl.java @@ -70,7 +70,7 @@ public class MeetAttachServiceImpl implements IMeetAttachService { */ @Override public List queryList(MeetAttachBo bo) { - bo.setState(0); + bo.setState(1); LambdaQueryWrapper lqw = buildQueryWrapper(bo); return baseMapper.selectVoList(lqw); } diff --git a/ruoyi-modules/Property/src/main/java/org/dromara/property/service/impl/ResidentPersonServiceImpl.java b/ruoyi-modules/Property/src/main/java/org/dromara/property/service/impl/ResidentPersonServiceImpl.java index 7ed604b5..b3aed1ea 100644 --- a/ruoyi-modules/Property/src/main/java/org/dromara/property/service/impl/ResidentPersonServiceImpl.java +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/service/impl/ResidentPersonServiceImpl.java @@ -13,8 +13,7 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.extern.slf4j.Slf4j; import org.dromara.property.domain.vo.ResidentUnitVo; import org.dromara.property.service.IResidentUnitService; -import org.dromara.sis.api.RemoteSisAuth; -import org.dromara.sis.api.domain.RemotePersonAuth; +import org.dromara.sis.api.RemoteSisAuthService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; @@ -30,7 +29,6 @@ import java.util.List; import java.util.Map; import java.util.Collection; import java.util.Objects; -import java.util.stream.Collectors; /** * 入驻员工Service业务层处理 @@ -50,7 +48,7 @@ public class ResidentPersonServiceImpl implements IResidentPersonService { private IResidentUnitService residentUnitService; @DubboReference - private RemoteSisAuth remoteSisAuth; + private RemoteSisAuthService remoteSisAuthService; /** * 查询入驻员工 @@ -126,30 +124,8 @@ public class ResidentPersonServiceImpl implements IResidentPersonService { add.setAuthGroupId(ruVo.getAuthGroupId()); add.setAuthBegDate(ruVo.getAuthBegDate()); add.setAuthEndDate(ruVo.getAuthEndDate()); - boolean flag = baseMapper.insert(add) > 0; Assert.isTrue(flag, "员工入驻失败!"); - // 存在图片时,才同步授权 - if (flag && add.getImg() != null) { - log.info("开始写入授权记录, {}", bo.getUserName()); - RemotePersonAuth personAuth = new RemotePersonAuth(); - personAuth.setId(add.getId()); - personAuth.setName(bo.getUserName()); - personAuth.setSex(bo.getGender().intValue()); - personAuth.setPhone(bo.getPhone()); - personAuth.setEmail(bo.getEmail()); - personAuth.setIdCardNumber(bo.getIdCard()); - personAuth.setOssId(bo.getImg()); - personAuth.setCarNumber(bo.getCarNumber()); - - // 使用公司权限组 - personAuth.setAuthBegDate(ruVo.getAuthBegDate()); - personAuth.setAuthEndDate(ruVo.getAuthEndDate()); - personAuth.setAuthGroupId(ruVo.getAuthGroupId()); - - Boolean auth = remoteSisAuth.personAuth(personAuth); - Assert.isTrue(auth, "新增授权记录失败"); - } return flag; } @@ -169,33 +145,19 @@ public class ResidentPersonServiceImpl implements IResidentPersonService { if (update.getAuthGroupId() != null && update.getAuthEndDate() != null && update.getImg() != null) { ResidentPersonVo vo = queryById(update.getId()); Long e8Id = vo.getEEightId(); - flag = baseMapper.updateById(update) > 0; + baseMapper.updateById(update); // 显式移除e8id LambdaUpdateWrapper lqw = new LambdaUpdateWrapper<>(); lqw.eq(ResidentPerson::getId, update.getId()) .set(ResidentPerson::getEEightId, null); - baseMapper.update(lqw); + flag = baseMapper.update(lqw) > 0; - if (flag) { + if (flag && e8Id != null) { log.info("开始修改授权记录, {}", bo.getUserName()); - RemotePersonAuth personAuth = new RemotePersonAuth(); - personAuth.setId(update.getId()); - personAuth.setName(update.getUserName()); - personAuth.setSex(update.getGender().intValue()); - personAuth.setPhone(update.getPhone()); - personAuth.setEmail(update.getEmail()); - personAuth.setIdCardNumber(update.getIdCard()); - personAuth.setOssId(update.getImg()); - personAuth.setCarNumber(update.getCarNumber()); - personAuth.setE8Id(e8Id); - - personAuth.setAuthGroupId(update.getAuthGroupId()); - personAuth.setAuthBegDate(update.getAuthBegDate()); - personAuth.setAuthEndDate(update.getAuthEndDate()); - - Boolean auth = remoteSisAuth.updatePersonAuth(personAuth); + // 先删除,定时任务增加 + Boolean auth = remoteSisAuthService.deletePersonAuth(List.of(update.getId()), List.of(e8Id)); Assert.isTrue(auth, "修改授权记录失败!"); } @@ -224,7 +186,7 @@ public class ResidentPersonServiceImpl implements IResidentPersonService { private void validEntityBeforeSave(ResidentPerson entity) { //TODO 做一些数据校验,如唯一约束 LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); - lqw.eq(ResidentPerson::getIdCard, entity.getIdCard()) + lqw.eq(ResidentPerson::getIdCard, entity.getUserName()) .eq(ResidentPerson::getUnitId, entity.getUnitId()); boolean exists = baseMapper.exists(lqw); Assert.isTrue(!exists, "当前单位,{}已入驻!", entity.getUserName()); @@ -252,8 +214,10 @@ public class ResidentPersonServiceImpl implements IResidentPersonService { .map(ResidentPersonVo::getEEightId) .filter(Objects::nonNull) .toList(); - boolean auth = remoteSisAuth.deletePersonAuth(ids, e8Ids); - Assert.isTrue(auth, "删除授权记录失败!"); + if (!e8Ids.isEmpty()) { + boolean auth = remoteSisAuthService.deletePersonAuth(ids, e8Ids); + Assert.isTrue(auth, "删除授权记录失败!"); + } } return baseMapper.deleteByIds(ids) > 0; } @@ -284,4 +248,19 @@ public class ResidentPersonServiceImpl implements IResidentPersonService { .isNull(ResidentPerson::getEEightId); return baseMapper.selectVoList(lqw); } + + /** + * 通过单位和姓名,查询人员信息 + * + * @param unitId 单位id + * @param name 姓名 + */ + @Override + public ResidentPersonVo queryByUnitIdAndName(Long unitId, String name) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(ResidentPerson::getUnitId, unitId) + .eq(ResidentPerson::getUserName, name); + List list = baseMapper.selectVoList(lqw); + return list.isEmpty() ? null : list.get(0); + } } diff --git a/ruoyi-modules/Property/src/main/java/org/dromara/property/utils/UploadFaceUtil.java b/ruoyi-modules/Property/src/main/java/org/dromara/property/utils/UploadFaceUtil.java index c98d5989..7046e48b 100644 --- a/ruoyi-modules/Property/src/main/java/org/dromara/property/utils/UploadFaceUtil.java +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/utils/UploadFaceUtil.java @@ -14,9 +14,8 @@ import org.springframework.web.multipart.MultipartFile; import java.io.ByteArrayOutputStream; import java.io.File; -import java.io.IOException; +import java.nio.charset.Charset; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -39,7 +38,7 @@ public class UploadFaceUtil { // 安全配置参数(实际项目中可以从配置文件读取) private static final int MAX_TOTAL_SIZE = 50 * 1024 * 1024; // 50MB 最大解压总大小 private static final int MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB 最大单个文件大小 - private static final int MAX_FILE_COUNT = 30; // 最大文件数量 + private static final int MAX_FILE_COUNT = 50; // 最大文件数量 private static final Map CONTENT_TYPE_MAP = new HashMap<>(); static { @@ -59,7 +58,7 @@ public class UploadFaceUtil { // 重置统计信息 resetStats(); - try (ZipInputStream zis = new ZipInputStream(zipFile.getInputStream())) { + try (ZipInputStream zis = new ZipInputStream(zipFile.getInputStream(), Charset.forName("GBK"))) { ZipEntry entry; byte[] buffer = new byte[8192]; // 8KB缓冲区 long totalExtractedSize = 0; @@ -126,19 +125,16 @@ public class UploadFaceUtil { bao.write(buffer, 0, len); } - ResidentPersonBo bo = new ResidentPersonBo(); - bo.setUnitId(unitId); - bo.setUserName(name); - List personVos = residentPersonService.queryList(bo); + ResidentPersonVo personVo = residentPersonService.queryByUnitIdAndName(unitId, name); // 判断当前姓名是否存在入驻单位 - if (personVos.isEmpty()) continue; + if (personVo == null) continue; byte[] imageData = bao.toByteArray(); - RemoteFile remoteFile = remoteFileService.upload(name, name, contentType, imageData); + RemoteFile remoteFile = remoteFileService.upload(name, entry.getName(), contentType, imageData); - personVos.get(0).setImg(remoteFile.getOssId().toString()); - ResidentPersonBo updateBo = BeanUtil.toBean(personVos.get(0), ResidentPersonBo.class); + personVo.setImg(remoteFile.getOssId().toString()); + ResidentPersonBo updateBo = BeanUtil.toBean(personVo, ResidentPersonBo.class); residentPersonService.updateByBo(updateBo); totalFiles++; diff --git a/ruoyi-modules/Sis/pom.xml b/ruoyi-modules/Sis/pom.xml index ba3949e3..9aea97f7 100644 --- a/ruoyi-modules/Sis/pom.xml +++ b/ruoyi-modules/Sis/pom.xml @@ -130,9 +130,9 @@ - org.eclipse.paho - org.eclipse.paho.client.mqttv3 - 1.2.5 + com.ghgande + j2mod + 3.0.0 diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/dubbo/RemoteDeviceServiceImpl.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/dubbo/RemoteDeviceServiceImpl.java new file mode 100644 index 00000000..8b495801 --- /dev/null +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/dubbo/RemoteDeviceServiceImpl.java @@ -0,0 +1,106 @@ +package org.dromara.sis.dubbo; + +import cn.hutool.core.util.StrUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.sis.api.RemoteDeviceService; +import org.dromara.sis.api.domain.RemoteSdkChannel; +import org.dromara.sis.api.domain.RemoteSisDeviceChannel; +import org.dromara.sis.api.domain.RemoteSisDeviceManage; +import org.dromara.sis.domain.SisDeviceChannel; +import org.dromara.sis.domain.SisDeviceManage; +import org.dromara.sis.domain.covert.CommonBeanCovert; +import org.dromara.sis.sdk.hik.HikSdkConstans; +import org.dromara.sis.service.ISisDeviceChannelService; +import org.dromara.sis.service.ISisDeviceManageService; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 海康设备远程服务调用 + * + * @author lxj + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class RemoteDeviceServiceImpl implements RemoteDeviceService { + + private final ISisDeviceManageService deviceManageService; + private final ISisDeviceChannelService deviceChannelService; + + @Override + public List queryHikDevices() { + List sisDeviceManages = deviceManageService.queryHikDevices(); + return CommonBeanCovert.INSTANCE.entity2Remote(sisDeviceManages); + } + + @Override + public Boolean updateDeviceState(RemoteSisDeviceManage item) { + return deviceManageService.updateDeviceState(CommonBeanCovert.INSTANCE.Remote2Entity(item)); + } + + @Override + public List queryDeviceChannels(String deviceIp) { + List channels = deviceChannelService.queryByDeviceIp(deviceIp); + return CommonBeanCovert.INSTANCE.channelEntity2Remote(channels); + } + + @Override + public Boolean updateDeviceChannelState(String deviceIp, Integer onLineState) { + return deviceChannelService.updateDeviceChannelState(deviceIp, onLineState); + } + + @Override + public Integer deleteByChannelIps(List list) { + return deviceChannelService.deleteByChannelIps(list); + } + + @Override + public Boolean insertChannel(List insertData) { + List list = insertData.stream().map(item -> { + SisDeviceChannel channel = new SisDeviceChannel(); + // 通道设备信息 + channel.setDeviceIp(item.getChannelIp()); + channel.setDevicePort(Integer.valueOf(item.getChannelPort())); + channel.setDeviceAccount(item.getChannelAccount()); + // 目前没发获取通道设备厂商,默认跟着录像机走 + channel.setFactoryNo(item.getNvrFactoryNo()); + channel.setChannelNo(HikSdkConstans.DEFAULT_CHANNEL); + channel.setChannelState(item.getChannelStatus()); + if (StrUtil.isNotEmpty(item.getChannelPwd())) { + channel.setDevicePwd(item.getChannelPwd()); + } + // nvr 设备信息 + channel.setNvrIp(item.getNvrIp()); + channel.setNvrPort(HikSdkConstans.DEFAULT_RTSP_PORT); + channel.setNvrAccount(item.getNvrAccount()); + channel.setNvrPwd(item.getNvrPwd()); + channel.setNvrFactoryNo(item.getNvrFactoryNo()); + channel.setNvrChannelNo(item.getChannelId() + HikSdkConstans.DEFAULT_CHANNEL_PREFX); + + // 系统设备信息 + // 将设备id 更改为nvr的设备id + channel.setDeviceId(item.getDeviceId()); + channel.setGroupId(item.getGroupId()); + channel.setChannelName(item.getChannelName()); + channel.setChannelNo(item.getChannelId() + HikSdkConstans.DEFAULT_CHANNEL_PREFX); + channel.setTenantId(item.getTenantId()); + return channel; + }).toList(); + return deviceChannelService.batchInsert(list); + } + + @Override + public Integer updateChannelInfo(List updateData) { + int num = 0; + for (RemoteSisDeviceChannel item : updateData) { + Boolean b = deviceChannelService.updateDeviceChannelState(item.getDeviceIp(), item.getChannelState()); + if (b) { + num++; + } + } + return num; + } +} diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/dubbo/RemoteHikDeviceServiceImpl.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/dubbo/RemoteHikDeviceServiceImpl.java deleted file mode 100644 index a5944f8e..00000000 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/dubbo/RemoteHikDeviceServiceImpl.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.dromara.sis.dubbo; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.dromara.sis.api.RemoteHikDeviceService; -import org.dromara.sis.api.domain.RemoteSisDeviceChannel; -import org.dromara.sis.api.domain.RemoteSisDeviceManage; -import org.dromara.sis.domain.SisDeviceChannel; -import org.dromara.sis.domain.SisDeviceManage; -import org.dromara.sis.domain.covert.CommonBeanCovert; -import org.dromara.sis.service.ISisDeviceChannelService; -import org.dromara.sis.service.ISisDeviceManageService; -import org.springframework.stereotype.Component; - -import java.util.List; - -/** - * 海康设备远程服务调用 - * - * @author lxj - */ -@Slf4j -@Component -@RequiredArgsConstructor -public class RemoteHikDeviceServiceImpl implements RemoteHikDeviceService { - - private final ISisDeviceManageService deviceManageService; - private final ISisDeviceChannelService deviceChannelService; - - @Override - public List queryHikDevices() { - List sisDeviceManages = deviceManageService.queryHikDevices(); - return CommonBeanCovert.INSTANCE.entity2Remote(sisDeviceManages); - } - - @Override - public Boolean updateDeviceState(RemoteSisDeviceManage item) { - return deviceManageService.updateDeviceState(CommonBeanCovert.INSTANCE.Remote2Entity(item)); - } - - @Override - public List queryDeviceChannels(String deviceIp) { - List channels = deviceChannelService.queryByDeviceIp(deviceIp); - return CommonBeanCovert.INSTANCE.channelEntity2Remote(channels); - } - - @Override - public Boolean updateDeviceChannelState(String deviceIp, Integer onLineState) { - return deviceChannelService.updateDeviceChannelState(deviceIp, onLineState); - } -} diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/dubbo/RemoteSisAuthImpl.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/dubbo/RemoteSisAuthImpl.java deleted file mode 100644 index 6f96bf02..00000000 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/dubbo/RemoteSisAuthImpl.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.dromara.sis.dubbo; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.apache.dubbo.config.annotation.DubboService; -import org.dromara.sis.api.RemoteSisAuth; -import org.dromara.sis.api.domain.RemotePersonAuth; -import org.dromara.sis.service.ISisAuthRecordService; - -import java.util.Collection; - -/** - * @author lsm - * @apiNote RemoteSisAuthImpl - * @since 2025/7/24 - */ -@Slf4j -@DubboService -@RequiredArgsConstructor -public class RemoteSisAuthImpl implements RemoteSisAuth { - - private final ISisAuthRecordService sisAuthRecordService; - - @Override - public Boolean personAuth(RemotePersonAuth personAuth) { - return sisAuthRecordService.insertByPerson(personAuth); - } - - @Override - public Boolean updatePersonAuth(RemotePersonAuth personAuth) { - return sisAuthRecordService.updateByBo(personAuth); - } - - @Override - public Boolean deletePersonAuth(Collection ids, Collection e8Ids) { - return sisAuthRecordService.deleteByPersonIds(ids, e8Ids); - } -} diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/dubbo/RemoteSisAuthServiceImpl.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/dubbo/RemoteSisAuthServiceImpl.java new file mode 100644 index 00000000..4418cf1f --- /dev/null +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/dubbo/RemoteSisAuthServiceImpl.java @@ -0,0 +1,222 @@ +package org.dromara.sis.dubbo; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.IdUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.dubbo.config.annotation.DubboService; +import org.dromara.common.core.constant.CodePrefixConstants; +import org.dromara.sis.api.RemoteSisAuthService; +import org.dromara.sis.api.domain.RemotePersonAuth; +import org.dromara.sis.domain.vo.SisAccessControlVo; +import org.dromara.sis.domain.vo.SisAuthGroupRefVo; +import org.dromara.sis.domain.vo.SisAuthRecordVo; +import org.dromara.sis.domain.vo.SisPersonLibImgVo; +import org.dromara.sis.sdk.e8.E8PlatformApi; +import org.dromara.sis.sdk.e8.domain.accessControl.req.CustomerAuthAddReq; +import org.dromara.sis.sdk.e8.domain.custom.req.CustomAddReq; +import org.dromara.sis.sdk.e8.domain.voucher.req.IssueVoucherReq; +import org.dromara.sis.sdk.huawei.HuaWeiBoxApi; +import org.dromara.sis.sdk.huawei.domain.AddHWPersonReq; +import org.dromara.sis.service.ISisAccessControlService; +import org.dromara.sis.service.ISisAuthGroupRefService; +import org.dromara.sis.service.ISisAuthRecordService; +import org.dromara.sis.service.ISisPersonLibImgService; + +import java.util.ArrayList; +import java.util.Base64; +import java.util.Collection; +import java.util.List; + +/** + * @author lsm + * @apiNote RemoteSisAuthServiceImpl + * @since 2025/7/24 + */ +@Slf4j +@DubboService +@RequiredArgsConstructor +public class RemoteSisAuthServiceImpl implements RemoteSisAuthService { + + private final HuaWeiBoxApi huaWeiBoxApi; + private final E8PlatformApi e8PlatformApi; + private final ISisAuthRecordService sisAuthRecordService; + private final ISisPersonLibImgService sisPersonLibImgService; + private final ISisAuthGroupRefService sisAuthGroupRefService; + private final ISisAccessControlService sisAccessControlService; + + /** + * 人员授权 + * + * @param personAuth 人员授权信息 + * @return Boolean + */ + @Override + public Boolean personAuth(RemotePersonAuth personAuth) { + return sisAuthRecordService.insertByPerson(personAuth); + } + + /** + * 删除人员授权信息 + * + * @param ids 入驻员工ids + * @param e8Ids e8平台人员id是 + * @return Boolean + */ + @Override + public Boolean deletePersonAuth(Collection ids, Collection e8Ids) { + return sisAuthRecordService.deleteByPersonIds(ids, e8Ids); + } + + + /** + * 查询人员授权信息 + * + * @param authGroupId 权限组id + * @param personId 人员id + * @return Boolean + */ + @Override + public Boolean queryPersonAuth(Long authGroupId, Long personId) { + SisAuthRecordVo vo = sisAuthRecordService.queryByGroupIdAndPersonId(authGroupId, personId); + return vo != null; + } + + /** + * 通过MD5,查询图片id + * + * @param imgMd5 图片MD5 + * @return Long + */ + @Override + public Long queryImgIdByImgMd5(String imgMd5) { + SisPersonLibImgVo vo = sisPersonLibImgService.queryByImgMd5(imgMd5); + return vo != null ? vo.getId() : null; + } + + /** + * 图片写入华为盒子 + * + * @param person 人员信息 + * @param imgByte 图片字节数组 + * @return Long 图片id + */ + @Override + public Long syncHuaweiBox(RemotePersonAuth person, byte[] imgByte) { + Long pId; + try { + AddHWPersonReq req = new AddHWPersonReq(); + req.setIndex(CodePrefixConstants.PERSON_LIB_IMAGE_CODE_PREFIX + IdUtil.getSnowflakeNextIdStr()); + req.setName(person.getName()); + req.setGender(person.getSex() == 1 ? "0" : person.getSex() == 2 ? "1" : "-1"); + + ArrayList pictures = new ArrayList<>(); + pictures.add(Base64.getEncoder().encodeToString(imgByte)); + req.setPictures(pictures); + + pId = huaWeiBoxApi.addPerson(List.of(req)); + } catch (Exception e) { + return null; + } + return pId; + } + + /** + * 更新人像信息 + * + * @param id 入驻员工id + * @param huaweiBoxId 华为盒子id + * @param md5Str 图片MD5 + * @return Boolean + */ + @Override + public Boolean updateImgByPersonId(Long id, Long huaweiBoxId, String md5Str) { + return sisPersonLibImgService.updateByPersonId(id, huaweiBoxId, md5Str); + } + + /** + * 图片写入E8平台 + * + * @param person 人员信息 + * @param imgByte 图片字节数组 + * @return Long e8平台id + */ + @Override + public Long syncE8Plat(RemotePersonAuth person, byte[] imgByte) { + // 初始化步进器 + int count = 0; + Long e8Id; + + try { + log.info("e8平台上传照片"); + String e8ImgUrl = e8PlatformApi.uploadFace(imgByte); + Assert.notNull(e8ImgUrl, "图片上传E8平台失败"); + log.info("e8平台上传照片完成"); + count++; // 图片上传完成步进器+1 + + log.info("e8同步新建人员"); + CustomAddReq req = new CustomAddReq(); + req.setName(person.getName()); + req.setGender(person.getSex() != 1 ? 0 : 1); + e8Id = e8PlatformApi.addCustomer(req).getId(); + Assert.notNull(e8Id, "e8同步新建人员失败"); + log.info("e8同步新建人员完成"); + count++; // 新增人员完成步进器+1 + + log.info("e8平台开始发行凭证"); + IssueVoucherReq voucherReq = new IssueVoucherReq(); + voucherReq.setVoucherType(70); + voucherReq.setPersonID(e8Id); + voucherReq.setTxtData(e8ImgUrl); + voucherReq.setCardType(34); + Long voucherId = e8PlatformApi.issueVoucher(voucherReq); + Assert.notNull(voucherId, "e8平台发行凭证失败"); + log.info("e8平台发行凭证成功"); + count++; // 发行凭证完成步进器+1 + + // 获取门禁 + List refVos = sisAuthGroupRefService.queryListByGroupId(person.getAuthGroupId()); + Collection deviceIds = refVos.stream().filter(ref -> ref.getDeviceType() == 1).map(SisAuthGroupRefVo::getDeviceId).toList(); + if (CollUtil.isNotEmpty(deviceIds)) { + // 初始化赋值 + CustomerAuthAddReq authReq = new CustomerAuthAddReq(); + authReq.setPersonIds(List.of(e8Id)); + authReq.setStartTime(DateUtil.format(person.getAuthBegDate(), "yyyy-MM-dd HH:mm:ss")); + authReq.setEndTime(DateUtil.format(person.getAuthEndDate(), "yyyy-MM-dd HH:mm:ss")); + List list = new ArrayList<>(); + SisAccessControlVo accessControlVo; + for (Long deviceId : deviceIds) { + accessControlVo = sisAccessControlService.queryById(deviceId); + CustomerAuthAddReq.AuthGroupData authData = new CustomerAuthAddReq.AuthGroupData(); + authData.setId(Long.parseLong(accessControlVo.getOutDoorCode())); + authData.setType(0); + authData.setGatewayType(1); + list.add(authData); + } + authReq.setAuthData(list); + + log.info("e8平台开始授权"); + Boolean flag = e8PlatformApi.addCustomerAuth(authReq); + Assert.isTrue(flag, "E8平台授权失败!"); + log.info("E8平台授权完成!"); + count++; // 授权完成步进器+1 + } + } catch (Exception e) { + return null; + } + + if (count == 4) { + // 授权完成,返回e8平台人员Id + return e8Id; + } else if (count >= 2 && count < 4) { + // 人员新建完成,授权失败,删除人员 + e8PlatformApi.deleteCustomer(e8Id); + return null; + } else { + return null; + } + } + +} diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/runner/HuaweiBoxRunner.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/runner/HuaweiBoxRunner.java new file mode 100644 index 00000000..f926f6c9 --- /dev/null +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/runner/HuaweiBoxRunner.java @@ -0,0 +1,105 @@ +package org.dromara.sis.runner; + +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.json.JSONUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.redis.utils.RedisUtils; +import org.dromara.sis.sdk.huawei.utils.HuaWeiHttp; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +/** + * @author lsm + * @apiNote HuaweiBoxRunner + * @since 2025/8/3 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class HuaweiBoxRunner implements ApplicationRunner { + + @Value("${huawei.url}") + private String BASE_URL; + + private final HuaWeiHttp huaWeiHttp; + + @Override + public void run(ApplicationArguments args) throws Exception { + Boolean login = huaWeiHttp.login(); + if (login) { + log.info("华为盒子登录成功"); + this.keepAlive(); + } else { + log.error("华为盒子登录失败"); + } + } + + // 保活 + public void keepAlive() { + // 创建单线程调度器 + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + String url = BASE_URL + "/common/keepAlive"; + + // 保存任务引用,用于后续取消 + final ScheduledFuture[] heartbeatFuture = new ScheduledFuture[1]; + + // 心跳任务 + Runnable heartbeatTask = () -> { + log.info("开始心跳执行华为盒子Session保活"); + // 发送请求获取响应 + // 使用 try-with-resources 确保资源释放 + try (HttpResponse response = HttpRequest.get(url) + .header("Content-Type", "application/json") + .header("Cache-Control", "no-cache") + .header("Cookie", RedisUtils.getCacheObject("JSESSIONID")) + .execute()) { + if (response.getStatus() == 200 && JSONUtil.parseObj(response.body()).getInt("resultCode") == 0) { + RedisUtils.expire("JSESSIONID", 1800); + log.info("保活成功"); + } else { + log.info("保活失败"); + shutdownHeartbeat(scheduler, heartbeatFuture[0]); + } + } catch (Exception e) { + // 异常:立即关闭心跳 + log.error("保活请求异常: {}", e.getMessage()); + shutdownHeartbeat(scheduler, heartbeatFuture[0]); + } + }; + + // 初始延迟0秒,之后每20分钟执行一次 + heartbeatFuture[0] = scheduler.scheduleAtFixedRate(heartbeatTask, 20, 20, TimeUnit.MINUTES); + log.info("心跳任务已启动,首次执行将在20分钟后"); + + // 添加关闭钩子确保程序退出时关闭调度器 + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + if (!scheduler.isShutdown()) { + scheduler.shutdown(); + log.info("程序退出,心跳调度器已关闭"); + } + })); + } + + private static void shutdownHeartbeat(ScheduledExecutorService scheduler, ScheduledFuture future) { + // 取消心跳任务 + if (future != null && !future.isCancelled()) { + future.cancel(false); + log.error("保活失败,心跳任务已取消"); + } + + // 关闭调度器 + if (!scheduler.isShutdown()) { + scheduler.shutdown(); + log.info("心跳调度器已关闭"); + } + } +} diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/e8/utils/E8ApiUtil.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/e8/utils/E8ApiUtil.java index e7f9b45c..e23dac16 100644 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/e8/utils/E8ApiUtil.java +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/e8/utils/E8ApiUtil.java @@ -5,6 +5,7 @@ import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import cn.hutool.json.JSONUtil; import org.dromara.sis.sdk.e8.domain.ApiResp; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; @@ -21,9 +22,14 @@ import java.util.stream.Collectors; @Component public class E8ApiUtil { - private static final String BASE_URL = "http://192.168.24.8:50014"; - private static final String SECRET_KEY = "ZG4ocLq1"; - private static final String KEY = "b97c7090379f490bb4b2ead0f57fd1bf"; + @Value("e8.url") + private String BASE_URL; + + @Value("e8.secretKey") + private String SECRET_KEY; + + @Value("e8.key") + private String KEY; /** * 发起Post请求 diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/hik/calback/HikAlarmCallBack.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/hik/calback/HikAlarmCallBack.java index 8773a9fa..6feddeb9 100644 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/hik/calback/HikAlarmCallBack.java +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/hik/calback/HikAlarmCallBack.java @@ -23,7 +23,12 @@ import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.text.SimpleDateFormat; -import java.util.*; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import static org.dromara.sis.sdk.hik.HCNetSDK.*; @@ -34,8 +39,8 @@ public class HikAlarmCallBack implements HCNetSDK.FMSGCallBack_V31 { private final HuaWeiBoxApi huaWeiBoxApi; private final ISisAuthRecordService authRecordService; - private final ISisAuthGroupRefService authGroupRefService; private final ISisElevatorInfoService elevatorInfoService; + private final ISisAuthGroupRefService authGroupRefService; private final ISisDeviceBindRefService deviceBindRefService; private final ISisAccessControlService accessControlService; private final ISisElevatorFloorRefService elevatorFloorRefService; @@ -330,7 +335,7 @@ public class HikAlarmCallBack implements HCNetSDK.FMSGCallBack_V31 { } Date now = new Date(); - if(DateUtil.compare(now, authRecord.getEndDate()) > 0){ + if (DateUtil.compare(now, authRecord.getEndDate()) > 0) { log.info("当前人脸已过期,暂不处理。"); return true; } @@ -358,71 +363,61 @@ public class HikAlarmCallBack implements HCNetSDK.FMSGCallBack_V31 { }); } - // 授权记录 -// List authVoList = authRecordService.checkAuth(person); - // 获取门禁id -// Collection acIds = authVoList.stream().filter(vo -> vo.getDeviceType() == 1).map(SisAuthRecordVo::getDeviceId).toList(); -// if (CollUtil.isNotEmpty(acIds)) { -// acIds.forEach(id -> { -// Long deviceId = bindRefList.stream().filter(vo -> vo.getBindId().equals(id)).findFirst().map(SisDeviceBindRefVo::getDeviceId).orElse(null); -// SisAccessControlVo ac = accessControlService.queryById(deviceId); -// if (ac != null) { -// log.info("调用门禁服务远程开门,doorName:{}", ac.getAccessName()); -// RemoteOpenDoorReq req = new RemoteOpenDoorReq(); -// req.setType(0); -// RemoteOpenDoorReq.ControlData data = new RemoteOpenDoorReq.ControlData(); -// data.setDeviceId(Long.parseLong(ac.getOutCode())); -// data.setDoorId(Long.parseLong(ac.getOutCode())); -// req.setControlList(List.of(data)); -// Boolean flag = e8PlatformApi.remoteOpenDoor(req); -// log.info("远程开门结果,result={}", flag); -// } -// }); -// } - // 获取电梯ids -// Collection eleIds = authVoList.stream().filter(vo -> vo.getDeviceType() == 2).map(SisAuthRecordVo::getDeviceId).toList(); -// if (CollUtil.isNotEmpty(eleIds)) { -// eleIds.forEach(id -> { -// Long deviceId = bindRefList.stream().filter(vo -> vo.getBindId().equals(id)).findFirst().map(SisDeviceBindRefVo::getDeviceId).orElse(null); -// SisElevatorInfoVo ele = elevatorInfoService.queryById(deviceId); -// if (ele != null) { -// log.info("下发电梯权限"); -// // 根据单元ID获取楼层信息 -// List floorInfo = remoteFloorService.queryByUnitId(ele.getUnitId()); -// // 获取电梯⇄楼层关联信息 -// List floorRefList = elevatorFloorRefService.queryByAuthGroupId(deviceId); -// // 获取楼层数组 -// List layerArray = floorInfo.stream().map(RemoteFloorVo::getId).sorted().toList(); -// -// layerArray.forEach(layer -> { -// SisElevatorFloorRefVo floorRef = floorRefList.stream() -// .filter(vo -> Objects.equals(vo.getFloorId(), layer)) // 直接使用 layer -// .findFirst() -// .orElse(null); -// if (floorRef == null) { -// HikApiService.getInstance().controlGateway(ele.getControlIp(), layer.intValue(), 3); -// } else { -// HikApiService.getInstance().controlGateway(ele.getControlIp(), layer.intValue(), 2); -// } -// }); -// } -// }); -// } + Collection eleIds = authGroupRefVos.stream().filter(vo -> vo.getDeviceType() == 2).map(SisAuthGroupRefVo::getDeviceId).toList(); + if (CollUtil.isNotEmpty(eleIds)) { + + // 创建线程池,处理关闭梯控 + int optimalPoolSize = Runtime.getRuntime().availableProcessors() + 1; + ExecutorService executor = Executors.newFixedThreadPool(optimalPoolSize); + + eleIds.forEach(id -> { + Long deviceId = bindRefList.stream().filter(vo -> vo.getBindId().equals(id) && vo.getControlType() != 1).findFirst().map(SisDeviceBindRefVo::getBindId).orElse(null); + if (deviceId != null) { + log.info("下发电梯权限"); + // 获取电梯信息 + SisElevatorInfoVo ele = elevatorInfoService.queryById(deviceId); + // 根据单元ID获取楼层信息 + List floorInfo = remoteFloorService.queryByUnitId(deviceId); + // 获取电梯⇄楼层关联信息 + List floorRefList = elevatorFloorRefService.queryByAuthGroupId(authRecord.getGroupId()); + // 获取楼层数组 + List layerArray = floorInfo.stream().map(RemoteFloorVo::getId).sorted().toList(); + + layerArray.forEach(layer -> { + SisElevatorFloorRefVo floorRef = floorRefList.stream() + .filter(vo -> Objects.equals(vo.getFloorId(), layer)) // 直接使用 layer + .findFirst() + .orElse(null); + if (floorRef == null) { + HikApiService.getInstance().controlGateway(ele.getControlIp(), layer.intValue(), 3); + } else { + HikApiService.getInstance().controlGateway(ele.getControlIp(), layer.intValue(), 2); + } + }); + + // todo 做延时队列,关闭梯控授权 + // 提交任务到线程池 + executor.execute(() -> { + try { + // 5秒后清除权限 + Thread.sleep(5000L); + for (int i = 0; i < layerArray.size(); i++) { + HikApiService.getInstance().controlGateway(ele.getControlIp(), (i + 1), 3); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }); + + } + }); + + // 关闭线程池 + executor.shutdown(); + } log.info("权限下发执行完成,耗时:{}", System.currentTimeMillis() - s); - // todo 做延时队列,关闭梯控授权 - - -// try { -// Thread.sleep(10000L); -// List ass = Arrays.asList(3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3); -// for (int i = 0; i < ass.size(); i++) { -// HikApiService.getInstance().controlGateway("192.168.24.188", (i + 1), ass.get(i)); -// } -// } catch (InterruptedException e) { -// throw new RuntimeException(e); -// } return false; } } diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/hik/calback/HikDevWorkStateCallBack.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/hik/calback/HikDevWorkStateCallBack.java index 80121069..26e6e007 100644 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/hik/calback/HikDevWorkStateCallBack.java +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/hik/calback/HikDevWorkStateCallBack.java @@ -24,6 +24,7 @@ public class HikDevWorkStateCallBack implements HCNetSDK.DEV_WORK_STATE_CB { } lpWorkState.read(); log.info(JSON.toJSONString(lpWorkState)); + log.info("海康sdk心跳信息上报, tda = {}", JSON.toJSONString(lpWorkState)); return true; } } diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/huawei/utils/HuaWeiHttp.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/huawei/utils/HuaWeiHttp.java index 6141c570..63f15cca 100644 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/huawei/utils/HuaWeiHttp.java +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/huawei/utils/HuaWeiHttp.java @@ -8,13 +8,10 @@ import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import lombok.extern.slf4j.Slf4j; import org.dromara.common.redis.utils.RedisUtils; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.util.Map; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; /** * @author lsm @@ -25,9 +22,14 @@ import java.util.concurrent.TimeUnit; @Component public class HuaWeiHttp { - private static final String BASE_URL = "https://192.168.24.100:18531"; - private static final String USERNAME = "huawei"; - private static final String PASSWORD = "qweasd123"; + @Value("${huawei.url}") + private String BASE_URL; + + @Value("${huawei.username}") + private String USERNAME; + + @Value("${huawei.password}") + private String PASSWORD; // 每次调用请求需要登录,获取JSESSIONID public Boolean login() { @@ -48,7 +50,6 @@ public class HuaWeiHttp { if (response.getStatus() == 200 && response.getCookie("JSESSIONID") != null) { RedisUtils.setCacheObject("JSESSIONID", response.getCookie("JSESSIONID").toString()); RedisUtils.expire("JSESSIONID", 1800); - this.keepAlive(); return true; } else { log.error("华为盒子登录失败,msg:{}", response.body()); @@ -57,66 +58,6 @@ public class HuaWeiHttp { return false; } - // 保活 - public void keepAlive() { - // 创建单线程调度器 - ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); - String url = BASE_URL + "/common/keepAlive"; - - // 保存任务引用,用于后续取消 - final ScheduledFuture[] heartbeatFuture = new ScheduledFuture[1]; - - // 心跳任务 - Runnable heartbeatTask = () -> { - log.info("开始心跳执行华为盒子Session保活"); - // 发送请求获取响应 - // 使用 try-with-resources 确保资源释放 - try (HttpResponse response = HttpRequest.post(url) - .header("Content-Type", "application/json") - .header("Cache-Control", "no-cache") - .header("Cookie", RedisUtils.getCacheObject("JSESSIONID")) - .execute()) { - if (response.getStatus() == 200 && JSONUtil.parseObj(response.body()).getInt("resultCode") == 0) { - RedisUtils.expire("JSESSIONID", 1800); - log.info("保活成功"); - }else { - log.info("保活失败"); - shutdownHeartbeat(scheduler, heartbeatFuture[0]); - } - } catch (Exception e) { - // 异常:立即关闭心跳 - log.error("保活请求异常: {}", e.getMessage()); - shutdownHeartbeat(scheduler, heartbeatFuture[0]); - } - }; - - // 初始延迟0秒,之后每20分钟执行一次 - heartbeatFuture[0] = scheduler.scheduleAtFixedRate(heartbeatTask, 20, 20, TimeUnit.MINUTES); - log.info("心跳任务已启动,首次执行将在20分钟后"); - - // 添加关闭钩子确保程序退出时关闭调度器 - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - if (!scheduler.isShutdown()) { - scheduler.shutdown(); - log.info("程序退出,心跳调度器已关闭"); - } - })); - } - - private static void shutdownHeartbeat(ScheduledExecutorService scheduler, ScheduledFuture future) { - // 取消心跳任务 - if (future != null && !future.isCancelled()) { - future.cancel(false); - log.error("保活失败,心跳任务已取消"); - } - - // 关闭调度器 - if (!scheduler.isShutdown()) { - scheduler.shutdown(); - log.info("心跳调度器已关闭"); - } - } - /** * 发起Post请求 * diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/smartDevices/utils/LightingUtil.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/smartDevices/utils/LightingUtil.java index d4fa6542..61ca06ed 100644 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/smartDevices/utils/LightingUtil.java +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/smartDevices/utils/LightingUtil.java @@ -1,14 +1,16 @@ package org.dromara.sis.sdk.smartDevices.utils; - -import com.google.gson.Gson; -import com.google.gson.JsonObject; import lombok.extern.slf4j.Slf4j; -import org.eclipse.paho.client.mqttv3.MqttClient; -import org.eclipse.paho.client.mqttv3.MqttConnectOptions; -import org.eclipse.paho.client.mqttv3.MqttException; -import org.eclipse.paho.client.mqttv3.MqttMessage; -import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; +import org.springframework.stereotype.Service; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.Socket; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import static com.ghgande.j2mod.modbus.Modbus.WRITE_SINGLE_REGISTER; /** * @author lsm @@ -16,124 +18,201 @@ import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; * @since 2025/7/20 */ @Slf4j +@Service public class LightingUtil { - private final MqttClient mqttClient; - private final String productKey; - private final String deviceName; - private final Gson gson = new Gson(); + // Modbus TCP默认端口 + private static final int MODBUS_PORT = 502; + // 功能码03(读保持寄存器) + private static final byte FUNCTION_CODE = 0x03; + // 采集寄存器范围(协议地址) + private static final int START_ADDRESS = 42; // 40043 - 40001 = 42 + private static final int REGISTER_COUNT = 4; // 40046 - 40043 + 1 = 4 + private Socket socket; + private DataInputStream input; + private DataOutputStream output; + private int transactionId = 0; // 事务ID计数器 - // 初始化连接参数 - public LightingUtil(String brokerUrl, String productKey, String deviceName, - String username, String password) throws MqttException { - this.productKey = productKey; - this.deviceName = deviceName; - - MqttConnectOptions options = new MqttConnectOptions(); - options.setUserName(username); - options.setPassword(password.toCharArray()); - options.setCleanSession(true); - - mqttClient = new MqttClient(brokerUrl, deviceName, new MemoryPersistence()); - mqttClient.connect(options); - - // 订阅网关上报主题 - String subscribeTopic = "/sys/" + productKey + "/+/thing/event/+/post"; - mqttClient.subscribe(subscribeTopic, this::handleIncomingMessage); + /** + * 连接到Modbus TCP设备 + */ + public void connect(String host) throws IOException { + socket = new Socket(host, MODBUS_PORT); + input = new DataInputStream(socket.getInputStream()); + output = new DataOutputStream(socket.getOutputStream()); } - // 基础指令构造 - private JsonObject createBaseCommand(int code, String area, String address, String action) { - JsonObject command = new JsonObject(); - command.addProperty("code", code); - command.addProperty("deviceName", deviceName); - command.addProperty("area", area); - command.addProperty("address", address); - command.addProperty("action", action); - command.addProperty("identity", ""); - return command; - } - - // 灯具控制指令 - public void sendLightCommand(int code, String area, String address, String action, String params) - throws MqttException { - JsonObject command = createBaseCommand(code, area, address, action); - if (params != null) command.addProperty("params", params); - - String topic = "/" + productKey + "/" + deviceName + "/user/get"; - mqttClient.publish(topic, new MqttMessage(gson.toJson(command).getBytes())); - } - - // 常用快捷方法 - public void turnOnLight(String area, String groupAddress) throws MqttException { - sendLightCommand(200, area, groupAddress, "lightOn", null); - } - - public void turnOffLight(String area, String groupAddress) throws MqttException { - sendLightCommand(200, area, groupAddress, "lightOff", null); - } - - public void setBrightness(String area, String address, int brightness) throws MqttException { - sendLightCommand(200, area, address, "setHighBright", String.valueOf(brightness)); - } - - // 上报数据处理 - private void handleIncomingMessage(String topic, MqttMessage message) { + /** + * 断开连接 + */ + public void disconnect() { try { - JsonObject payload = gson.fromJson(new String(message.getPayload()), JsonObject.class); - String method = payload.get("method").getAsString(); - - switch (method) { - case "thing.event.heartbeat.post": - processHeartbeat(payload.getAsJsonObject("params")); - break; - case "thing.event.consumption.post": - processEnergyData(payload.getAsJsonObject("params")); - break; - case "thing.event.trigger.post": - processSensorTrigger(payload.getAsJsonObject("params")); - break; - // 添加其他事件处理... - } - } catch (Exception e) { - log.error("MQTT消息处理异常,topic: {}", topic, e); + if (input != null) input.close(); + if (output != null) output.close(); + if (socket != null) socket.close(); + } catch (IOException e) { + System.err.println("关闭连接时出错: " + e.getMessage()); } } - // 心跳处理 - private void processHeartbeat(JsonObject params) { - JsonObject value = params.getAsJsonObject("value"); - String uuid = value.get("uuid").getAsString(); - String area = value.get("area").getAsString(); - System.out.println("设备在线: " + uuid + " | 区域: " + area); + private byte[] initParse() throws IOException { + // 读取头(7字节) + byte[] header = new byte[7]; + input.readFully(header); + + // 验证事务ID + int receivedTid = ByteBuffer.wrap(header, 0, 2) + .order(ByteOrder.BIG_ENDIAN).getShort() & 0xFFFF; + if (receivedTid != transactionId - 1) { + throw new IOException("事务ID不匹配"); + } + + return header; } - // 能耗处理 - private void processEnergyData(JsonObject params) { - JsonObject value = params.getAsJsonObject("value"); - String uuid = value.get("uuid").getAsString(); - double power = value.get("power").getAsDouble(); - System.out.println("能耗报告: " + uuid + " | 功率: " + power + "W"); + /** + * 读取40043-40046寄存器数据 + * + * @return 包含4个寄存器值的int数组 + */ + public int[] readRegisters() throws IOException { + // 发送读取请求 + sendRequest(); + + // 接收并解析响应 + return parseResponse(); } - // 传感器触发处理 - private void processSensorTrigger(JsonObject params) { - JsonObject value = params.getAsJsonObject("value"); - long trigTime = value.get("trig_time").getAsLong(); - String area = value.get("area").getAsString(); - System.out.println("传感器触发: 区域=" + area + " | 时间=" + trigTime); + /** + * 构造并发送Modbus TCP请求帧 + */ + private void sendRequest() throws IOException { + // 事务ID(递增) + int currentTransactionId = transactionId++; + + // 创建请求帧(12字节) + ByteBuffer buffer = ByteBuffer.allocate(12) + .order(ByteOrder.BIG_ENDIAN); + + // Header(7字节) + buffer.putShort((short) currentTransactionId); // 事务ID + buffer.putShort((short) 0); // 协议ID(0=Modbus) + buffer.putShort((short) 6); // 长度(后续字节数) + buffer.put((byte) 1); // 单元ID + + // PDU(协议数据单元) + buffer.put(FUNCTION_CODE); // 功能码 + buffer.putShort((short) START_ADDRESS); // 起始地址 + buffer.putShort((short) REGISTER_COUNT); // 寄存器数量 + + output.write(buffer.array()); + output.flush(); } - // 网关管理 - public void rebootGateway(int delaySeconds) throws MqttException { - JsonObject command = createBaseCommand(400, "00 00", "FF FF", "reboot"); - command.addProperty("params", String.valueOf(delaySeconds)); - String topic = "/" + productKey + "/" + deviceName + "/user/get"; - mqttClient.publish(topic, new MqttMessage(gson.toJson(command).getBytes())); + /** + * 解析Modbus TCP响应 + */ + private int[] parseResponse() throws IOException { + // 读取头(7字节) + byte[] header = initParse(); + + // 读取PDU(协议数据单元) + int pduLength = ByteBuffer.wrap(header, 4, 2) + .getShort() & 0xFFFF - 1; // 减去单元ID长度 + byte[] pdu = new byte[pduLength]; + input.readFully(pdu); + + // 检查异常响应(功能码高位为1) + if ((pdu[0] & 0xFF) == (FUNCTION_CODE | 0x80)) { + throw new IOException("Modbus异常响应,错误码: " + (pdu[1] & 0xFF)); + } + + // 验证功能码和字节数 + if (pdu[0] != FUNCTION_CODE || pdu[1] != REGISTER_COUNT * 2) { + throw new IOException("无效响应格式"); + } + + // 提取寄存器数据(每个寄存器2字节) + int[] values = new int[REGISTER_COUNT]; + for (int i = 0; i < REGISTER_COUNT; i++) { + int offset = 2 + i * 2; + values[i] = ByteBuffer.wrap(pdu, offset, 2) + .order(ByteOrder.BIG_ENDIAN).getShort() & 0xFFFF; + } + return values; } - // 关闭连接 - public void disconnect() throws MqttException { - mqttClient.disconnect(); + /** + * 写单个保持寄存器(功能码06) + * + * @param registerAddress 寄存器地址(协议地址,如40044对应0x0043) + * @param value 要写入的值(0-65535) + * @return true表示写入成功 + */ + public boolean writeSingleRegister(int registerAddress, int value) throws IOException { + // 发送写请求 + sendWriteRequest(registerAddress, value); + + // 接收并验证响应 + return parseWriteResponse(); } + + /** + * 构造并发送写寄存器请求帧 + */ + private void sendWriteRequest(int registerAddress, int value) throws IOException { + int currentTransactionId = transactionId++; + + // 创建请求帧(12字节) + ByteBuffer buffer = ByteBuffer.allocate(12) + .order(ByteOrder.BIG_ENDIAN); + + // MBAP Header(7字节) + buffer.putShort((short) currentTransactionId); // 事务ID + buffer.putShort((short) 0); // 协议ID(0=Modbus) + buffer.putShort((short) 6); // 长度(后续字节数) + buffer.put((byte) 1); // 单元ID + + // PDU(协议数据单元) + buffer.put((byte) WRITE_SINGLE_REGISTER); // 功能码06 + buffer.putShort((short) registerAddress); // 寄存器地址 + buffer.putShort((short) value); // 写入的值 + + output.write(buffer.array()); + output.flush(); + } + + /** + * 解析写寄存器响应 + */ + private boolean parseWriteResponse() throws IOException { + // 读取头(7字节) + this.initParse(); + + // 读取PDU(5字节) + byte[] pdu = new byte[5]; + input.readFully(pdu); + + // 检查异常响应(功能码高位为1) + if ((pdu[0] & 0xFF) == (WRITE_SINGLE_REGISTER | 0x80)) { + throw new IOException("Modbus异常响应,错误码: " + (pdu[1] & 0xFF)); + } + + // 验证功能码和字节数 + if (pdu[0] != WRITE_SINGLE_REGISTER) { + throw new IOException("无效响应格式"); + } + + // 响应应回显写入的地址和值 + int respAddress = ByteBuffer.wrap(pdu, 1, 2).getShort() & 0xFFFF; + System.out.println("传输指令后----" + respAddress); + int respValue = ByteBuffer.wrap(pdu, 3, 2).getShort() & 0xFFFF; + System.out.println("传输指令后----" + respValue); + + // 这里可以根据需要验证回显的值是否与写入一致 + // 通常只需确认功能码正确即可认为成功 + return true; + } + + } diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/smartDevices/utils/MeterUtil.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/smartDevices/utils/MeterUtil.java new file mode 100644 index 00000000..47e5a6cc --- /dev/null +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/smartDevices/utils/MeterUtil.java @@ -0,0 +1,126 @@ +package org.dromara.sis.sdk.smartDevices.utils; + +import com.ghgande.j2mod.modbus.ModbusException; +import com.ghgande.j2mod.modbus.facade.ModbusTCPMaster; +import com.ghgande.j2mod.modbus.procimg.InputRegister; +import com.ghgande.j2mod.modbus.util.ModbusUtil; +import org.springframework.stereotype.Service; + + +/** + * @author lsm + * @apiNote MeterUtil + * @since 2025/7/31 + */ +@Service +public class MeterUtil { + private ModbusTCPMaster master; + + private static final int PORT = 502; + + // 寄存器区域定义 (基于0的起始地址) + private static final int CONSTANT_AREA_START = 0; // 常数区起始地址 (30001) + private static final int COLLECTION_AREA_START = 42; // 采集区起始地址 (30043) + private static final int REPORT_AREA_START = 4002; // 上报区起始地址 (34003) + + // 区域大小 + private static final int CONSTANT_AREA_SIZE = 21; // 0-20 共21个浮点数 + private static final int COLLECTION_AREA_SIZE = 1980; // 21-2000 共1980个浮点数 + private static final int REPORT_AREA_SIZE = 1000; // 2001-3000 共1000个浮点数 + + + /** + * 连接到Modbus TCP服务器 + * + * @throws Exception 连接失败时抛出异常 + */ + public void connect(String host) throws Exception { + if (master != null && master.isConnected()) { + return; + } + master = new ModbusTCPMaster(host, PORT); + master.setTimeout(3000); // 设置3秒超时 + master.connect(); + } + + /** + * 断开连接 + */ + public void disconnect() { + if (master != null && master.isConnected()) { + master.disconnect(); + } + } + + /** + * 从寄存器读取浮点数 + * + * @param register 寄存器起始地址 (0-based) + * @return 读取到的浮点数值 + * @throws ModbusException Modbus通信异常 + */ + private float readFloat(int register) throws ModbusException { + InputRegister[] registers = master.readInputRegisters(register, 2); + byte[] bytes = { + registers[0].toBytes()[0], + registers[0].toBytes()[1], + registers[1].toBytes()[0], + registers[1].toBytes()[1] + }; + return ModbusUtil.registersToFloat(bytes); + } + + /** + * 读取常数区数据 + * + * @param index 常数区索引 (0-20) + * @return 浮点数值 + * @throws ModbusException Modbus通信异常 + * @throws IllegalArgumentException 索引越界 + */ + public float readConstantValue(int index) throws ModbusException { + if (index < 0 || index >= CONSTANT_AREA_SIZE) { + throw new IllegalArgumentException("常数区索引范围应为 0-20"); + } + return readFloat(CONSTANT_AREA_START + index * 2); + } + + /** + * 读取采集区数据 + * + * @param index 采集区索引 (0-1979) + * @return 浮点数值 + * @throws ModbusException Modbus通信异常 + * @throws IllegalArgumentException 索引越界 + */ + public float readCollectionValue(int index) throws ModbusException { + if (index < 0 || index >= COLLECTION_AREA_SIZE) { + throw new IllegalArgumentException("采集区索引范围应为 0-1979"); + } + return readFloat(COLLECTION_AREA_START + index * 2); + } + + /** + * 读取上报区数据 + * + * @param index 上报区索引 (0-999) + * @return 浮点数值 + * @throws ModbusException Modbus通信异常 + * @throws IllegalArgumentException 索引越界 + */ + public float readReportValue(int index) throws ModbusException { + if (index < 0 || index >= REPORT_AREA_SIZE) { + throw new IllegalArgumentException("上报区索引范围应为 0-999"); + } + return readFloat(REPORT_AREA_START + index * 2); + } + + /** + * 检查连接状态 + * + * @return 是否已连接 + */ + public boolean isConnected() { + return master != null && master.isConnected(); + } +} diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/smartDevices/utils/PowerMeterUtil.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/smartDevices/utils/PowerMeterUtil.java deleted file mode 100644 index 8f4a1f01..00000000 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/smartDevices/utils/PowerMeterUtil.java +++ /dev/null @@ -1,179 +0,0 @@ -package org.dromara.sis.sdk.smartDevices.utils; - -import org.dromara.sis.sdk.smartDevices.domain.PowerFrame; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -/** - * @author lsm - * @apiNote PowerMeterUtil - * @since 2025/7/20 - */ -public class PowerMeterUtil { - - // 协议常量定义 - public static final byte FRAME_START = 0x68; - public static final byte FRAME_END = 0x16; - public static final byte[] PREAMBLE = {(byte) 0xFE, (byte) 0xFE, (byte) 0xFE, (byte) 0xFE}; - public static final int ADDR_LENGTH = 6; - public static final int MAX_READ_DATA_LEN = 200; - public static final int MAX_WRITE_DATA_LEN = 50; - - // 控制码功能定义 - public static final byte CTRL_BROADCAST_TIME = 0x08; - public static final byte CTRL_READ_DATA = 0x11; - public static final byte CTRL_READ_FOLLOW_DATA = 0x12; - public static final byte CTRL_WRITE_DATA = 0x14; - public static final byte CTRL_TRIP_CONTROL = 0x1C; - public static final byte CTRL_OUTPUT_CONTROL = 0x1D; - - // 地址通配符 - public static final byte ADDR_WILDCARD = (byte) 0xAA; - - /** - * 构建基础帧结构 - * - * @param address 6字节地址(高位在前,低位在后) - * @param ctrlCode 控制码 - * @param data 原始数据域(未加33H) - * @param isEncode 是否进行数据域处理 - * @return 完整帧数据 - */ - public byte[] buildFrame(byte[] address, byte ctrlCode, byte[] data, boolean isEncode) { - if (address.length != ADDR_LENGTH) { - throw new IllegalArgumentException("Address must be 6 bytes"); - } - - // 处理数据域:每个字节加0x33 - byte[] processedData = processDataDomain(data, isEncode); - - // 计算数据域长度 - int dataLen = (data != null) ? data.length : 0; - if (dataLen > MAX_READ_DATA_LEN) { - throw new IllegalArgumentException("Data length exceeds max limit"); - } - - // 计算总帧长度: 起始符(1) + 地址(6) + 起始符(1) + 控制码(1) + 长度(1) + 数据域 + 校验(1) + 结束符(1) - int totalLength = 11 + dataLen; - ByteBuffer buffer = ByteBuffer.allocate(totalLength) - .order(ByteOrder.LITTLE_ENDIAN); - - // 地址域处理 (传输顺序: 低字节在前) - byte[] reversedAddr = reverseAddress(address); - - // 构建帧 - buffer.put(FRAME_START) - .put(reversedAddr) - .put(FRAME_START) - .put(ctrlCode) - .put((byte) dataLen); - - if (dataLen > 0) { - buffer.put(processedData); - } - - // 计算校验码 (从第一个0x68到数据域结束) - byte[] frameWithoutCs = Arrays.copyOf(buffer.array(), buffer.position()); - byte cs = calculateChecksum(frameWithoutCs); - - buffer.put(cs) - .put(FRAME_END); - - return buffer.array(); - } - - /** - * 解析接收到的帧 - * @param frame 完整帧数据(包含前导符) - * @return 解析结果对象 - */ - public PowerFrame parseFrame(byte[] frame) { - // 跳过前导符 (0-3) - int startIndex = findFrameStart(frame); - if (startIndex == -1) { - throw new IllegalArgumentException("无效帧:未找到起始标记"); - } - - // 基本长度检查 - if (frame.length < startIndex + 12) { - throw new IllegalArgumentException("接受帧太短"); - } - - // 提取地址域 (传输顺序: 低字节在前) - byte[] reversedAddr = Arrays.copyOfRange(frame, startIndex + 1, startIndex + 7); - byte[] address = reverseAddress(reversedAddr); - - // 控制码 - byte ctrlCode = frame[startIndex + 8]; - - // 数据域长度 - int dataLen = frame[startIndex + 9] & 0xFF; - - // 数据域位置 - int dataStart = startIndex + 10; - int dataEnd = dataStart + dataLen; - - // 校验位位置 - int endPos = dataEnd + 1; - - // 验证结束符 - if (frame[endPos] != FRAME_END) { - throw new IllegalArgumentException("无效的帧结束标记"); - } - - // 提取原始数据域 (含33H处理) - byte[] rawData = Arrays.copyOfRange(frame, dataStart, dataEnd); - byte[] processedData = processDataDomain(rawData, false); - - // 验证校验和 - byte calculatedCs = calculateChecksum(Arrays.copyOfRange(frame, startIndex, dataEnd)); - byte receivedCs = frame[dataEnd]; - - if (calculatedCs != receivedCs) { - throw new IllegalArgumentException("校验和不匹配"); - } - - return new PowerFrame(address, ctrlCode, processedData); - } - - // 数据处理域:加/减33H - private byte[] processDataDomain(byte[] data, boolean isEncode) { - if (data == null || data.length == 0) return data; - - byte[] result = new byte[data.length]; - for (int i = 0; i < data.length; i++) { - result[i] = (byte) (isEncode ? (data[i] + 0x33) : (data[i] - 0x33)); - } - return result; - } - - // 地址反转 (传输顺序处理) - private byte[] reverseAddress(byte[] address) { - byte[] reversed = new byte[address.length]; - for (int i = 0; i < address.length; i++) { - reversed[i] = address[address.length - 1 - i]; - } - return reversed; - } - - // 计算校验和 (模256和) - private byte calculateChecksum(byte[] data) { - int sum = 0; - for (byte b : data) { - sum = (sum + (b & 0xFF)) & 0xFF; - } - return (byte) sum; - } - - // 在帧数据中查找起始符 - private int findFrameStart(byte[] data) { - for (int i = 0; i < data.length - 1; i++) { - if (data[i] == FRAME_START && data[i + 1] != FRAME_START) { - return i; - } - } - return -1; - } -} diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/smartDevices/utils/WaterMeterUtil.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/smartDevices/utils/WaterMeterUtil.java deleted file mode 100644 index 31fb0713..00000000 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/smartDevices/utils/WaterMeterUtil.java +++ /dev/null @@ -1,168 +0,0 @@ -package org.dromara.sis.sdk.smartDevices.utils; - -import java.nio.ByteBuffer; -import java.util.Arrays; - -/** - * @author lsm - * @apiNote WaterMeterUtil - * @since 2025/7/20 - */ -public class WaterMeterUtil { - - // 协议常量定义 - public static final byte PREAMBLE = (byte) 0xFE; - public static final byte FRAME_START = 0x68; - public static final byte FRAME_END = 0x16; - public static final byte WATER_METER_TYPE = 0x10; - public static final byte CTRL_READ = 0x01; - public static final byte CTRL_RESPONSE = (byte) 0x81; - public static final byte UNIT_TON = 0x2C; - public static final int ADDRESS_LENGTH = 7; - - /** - * 构建读表数据命令帧 - * - * @param meterAddress 12位表计地址字符串(如"000000000000012") - * @param diHighFirst 数据标识字节序:true=901Fh(高字节在前), false=1F90h(低字节在前) - * @return 完整的命令帧字节数组 - */ - public static byte[] buildReadCommand(String meterAddress, boolean diHighFirst) { - // 1. 地址转换:12位字符串 -> 7字节BCD码(逆序分组) - byte[] addressBytes = convertAddress(meterAddress); - - // 2. 构建帧主体(不含前导符和帧尾) - ByteBuffer buffer = ByteBuffer.allocate(32); - buffer.put(FRAME_START); - buffer.put(WATER_METER_TYPE); - buffer.put(addressBytes); - buffer.put(CTRL_READ); - buffer.put((byte) 0x03); // 数据域长度 - - // 数据标识处理 - if (diHighFirst) { - buffer.put((byte) 0x90); - buffer.put((byte) 0x1F); - } else { - buffer.put((byte) 0x1F); - buffer.put((byte) 0x90); - } - - buffer.put((byte) 0x00); // 序列号 - - // 3. 计算校验码(从FRAME_START到序列号) - byte[] frameBody = Arrays.copyOf(buffer.array(), buffer.position()); - byte cs = calculateChecksum(frameBody, 0, frameBody.length); - - // 4. 组装完整帧 - buffer.put(cs); - buffer.put(FRAME_END); - - // 5. 添加前导符 - byte[] fullFrame = Arrays.copyOf(buffer.array(), buffer.position()); - return addPreamble(fullFrame); - } - - /** - * 解析读表响应数据 - * - * @param response 完整响应帧(含前导符) - * @return 解析后的累积流量值(单位:吨) - * @throws IllegalArgumentException 响应格式错误 - */ - public static double parseReadResponse(byte[] response) { - // 1. 跳过前导符(0xFE x3) - int startIndex = 3; - if (response[startIndex] != FRAME_START) { - throw new IllegalArgumentException("无效帧起始符"); - } - - // 2. 基础信息解析 - int pos = startIndex + 1; - byte meterType = response[pos++]; - byte[] address = Arrays.copyOfRange(response, pos, pos + ADDRESS_LENGTH); - pos += ADDRESS_LENGTH; - - byte ctrlCode = response[pos++]; - if (ctrlCode != CTRL_RESPONSE) { - throw new IllegalArgumentException("无效控制码"); - } - - // 3. 数据域解析 - int dataLen = response[pos++] & 0xFF; - byte[] di = {response[pos++], response[pos++]}; // 数据标识 - byte ser = response[pos++]; // 序列号 - - // 4. 累积流量解析 (4字节BCD) - byte[] currentFlow = Arrays.copyOfRange(response, pos, pos + 4); - pos += 4; - - // 5. 单位校验 - if (response[pos++] != UNIT_TON) { - throw new IllegalArgumentException("无效计量单位"); - } - - // 6. 流量值转换 - return parseFlowValue(currentFlow); - } - - /** - * 计算校验码 (CJ/T188-2004标准) - * - * @param data 待计算数据 - * @param offset 起始位置 - * @param length 数据长度 - * @return 校验码 - */ - public static byte calculateChecksum(byte[] data, int offset, int length) { - int sum = 0; - for (int i = offset; i < offset + length; i++) { - sum += (data[i] & 0xFF); - } - return (byte) (sum % 256); - } - - // 地址转换:12位字符串 -> 7字节BCD码(逆序分组) - private static byte[] convertAddress(String address) { - if (address.length() != 12) { - throw new IllegalArgumentException("地址长度必须为12位"); - } - - // 填充为14位(7字节*2) - String padded = "00" + address; - byte[] result = new byte[ADDRESS_LENGTH]; - - // 逆序分组转换 - for (int i = 0; i < ADDRESS_LENGTH; i++) { - int end = padded.length() - i * 2; - int start = end - 2; - String segment = padded.substring(start, end); - result[i] = (byte) Integer.parseInt(segment, 16); - } - return result; - } - - // 添加前导符 0xFE x3 - private static byte[] addPreamble(byte[] frame) { - byte[] result = new byte[frame.length + 3]; - result[0] = PREAMBLE; - result[1] = PREAMBLE; - result[2] = PREAMBLE; - System.arraycopy(frame, 0, result, 3, frame.length); - return result; - } - - // 解析BCD流量值(4字节 -> 浮点数) - private static double parseFlowValue(byte[] data) { - // 拼接BCD数字串 - StringBuilder sb = new StringBuilder(); - for (byte b : data) { - sb.append(String.format("%02X", b)); - } - - // 转换为数值(最后2位是小数位) - String numStr = sb.toString(); - return Double.parseDouble(numStr.substring(0, numStr.length() - 2) + - Double.parseDouble(numStr.substring(numStr.length() - 2)) / 100.0); - } -} diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/ISisDeviceChannelService.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/ISisDeviceChannelService.java index 5bb6b362..76130014 100644 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/ISisDeviceChannelService.java +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/ISisDeviceChannelService.java @@ -60,6 +60,14 @@ public interface ISisDeviceChannelService { */ Boolean insert(SisDeviceChannel channel); + /** + * 批量写入通道信息 + * + * @param list 批量写入通道信息 + * @return 返回写入数量 + */ + Boolean batchInsert(List list); + /** * 修改设备通道管理 * @@ -77,6 +85,21 @@ public interface ISisDeviceChannelService { */ Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + /** + * 通过设备ids 删除设备通道信息 + * + * @param deviceIds 设备ids + * @return 返回删除数量 + */ + Integer deleteByDeviceIds(List deviceIds); + + /** + * 通过通道ip删除设备通道 + * + * @param list ip列表 + */ + Integer deleteByChannelIps(List list); + /** * 查询设备通道树 * @@ -87,13 +110,6 @@ public interface ISisDeviceChannelService { void handleHikDeviceChannel(SisDeviceManageBo bo); - /** - * 通过设备ids 删除设备通道信息 - * - * @param deviceIds 设备ids - * @return 返回删除数量 - */ - Integer deleteByDeviceIds(List deviceIds); SisDeviceChannel queryByChannelIp(String channelIp); @@ -107,8 +123,11 @@ public interface ISisDeviceChannelService { /** * 更新设备状态 + * * @param deviceIp 设备ip * @return 返回是否操作成功 */ Boolean updateDeviceChannelState(String deviceIp, Integer onLineState); + + } diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/SisAuthRecordServiceImpl.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/SisAuthRecordServiceImpl.java index 03c4b4f4..1d2456a7 100644 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/SisAuthRecordServiceImpl.java +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/SisAuthRecordServiceImpl.java @@ -1,5 +1,6 @@ package org.dromara.sis.service.impl; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import org.apache.dubbo.config.annotation.DubboReference; import org.dromara.common.core.domain.TreeNode; @@ -351,7 +352,6 @@ public class SisAuthRecordServiceImpl implements ISisAuthRecordService { Assert.isTrue(flag, "删除授权记录失败"); if (flag) { - List list = new ArrayList<>(); ids.forEach(id -> { SisPersonLibImgVo imgVo = sisPersonLibImgService.queryByPersonId(id); @@ -360,10 +360,14 @@ public class SisAuthRecordServiceImpl implements ISisAuthRecordService { } }); Collection imgIds = list.stream().map(SisPersonLibImgVo::getId).toList(); - flag = sisPersonLibImgService.deleteWithValidByIds(imgIds, false); - Assert.isTrue(flag, "删除人像库图片失败"); + if (CollUtil.isNotEmpty(imgIds)){ + flag = sisPersonLibImgService.deleteWithValidByIds(imgIds, false); + Assert.isTrue(flag, "删除人像库图片失败"); + } - e8Ids.forEach(e8PlatformApi::deleteCustomer); + if (CollUtil.isNotEmpty(e8Ids)){ + e8Ids.forEach(e8PlatformApi::deleteCustomer); + } } return flag; } diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/SisDeviceChannelServiceImpl.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/SisDeviceChannelServiceImpl.java index f09bfe08..e24d642e 100644 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/SisDeviceChannelServiceImpl.java +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/SisDeviceChannelServiceImpl.java @@ -123,6 +123,11 @@ public class SisDeviceChannelServiceImpl implements ISisDeviceChannelService { return baseMapper.insert(channel) > 0; } + @Override + public Boolean batchInsert(List list) { + return baseMapper.insertBatch(list); + } + /** * 修改设备通道管理 * @@ -302,6 +307,13 @@ public class SisDeviceChannelServiceImpl implements ISisDeviceChannelService { return baseMapper.delete(lqw); } + @Override + public Integer deleteByChannelIps(List list) { + LambdaQueryWrapper lqw = new LambdaQueryWrapper<>(); + lqw.in(SisDeviceChannel::getDeviceIp, list); + return baseMapper.delete(lqw); + } + @Override public List queryByDeviceIp(String deviceIp) { LambdaQueryWrapper lqw = new LambdaQueryWrapper<>(); diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/task/AuthSyncTask.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/task/AuthSyncTask.java deleted file mode 100644 index 901f54cc..00000000 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/task/AuthSyncTask.java +++ /dev/null @@ -1,272 +0,0 @@ -package org.dromara.sis.task; - -import cn.dev33.satoken.context.mock.SaTokenContextMockUtil; -import cn.dev33.satoken.stp.StpUtil; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.ObjectUtil; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.apache.dubbo.config.annotation.DubboReference; -import org.dromara.common.core.constant.CodePrefixConstants; -import org.dromara.property.api.RemoteResidentPersonService; -import org.dromara.property.api.domain.vo.RemoteResidentPersonVo; -import org.dromara.resource.api.RemoteFileService; -import org.dromara.sis.api.domain.RemotePersonAuth; -import org.dromara.sis.domain.vo.*; -import org.dromara.sis.sdk.e8.E8PlatformApi; -import org.dromara.sis.sdk.e8.domain.accessControl.req.CustomerAuthAddReq; -import org.dromara.sis.sdk.e8.domain.custom.req.CustomAddReq; -import org.dromara.sis.sdk.e8.domain.voucher.req.IssueVoucherReq; -import org.dromara.sis.sdk.huawei.HuaWeiBoxApi; -import org.dromara.sis.sdk.huawei.domain.AddHWPersonReq; -import org.dromara.sis.service.ISisAccessControlService; -import org.dromara.sis.service.ISisAuthGroupRefService; -import org.dromara.sis.service.ISisAuthRecordService; -import org.dromara.sis.service.ISisPersonLibImgService; -import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.annotation.Scheduled; - -import java.security.MessageDigest; -import java.util.*; -import java.util.concurrent.atomic.AtomicReference; - -/** - * @author lsm - * @apiNote AuthSyncTask - * @since 2025/7/26 - */ -@Slf4j -@Configuration -@EnableScheduling -@RequiredArgsConstructor -public class AuthSyncTask { - - @DubboReference - private RemoteFileService remoteFileService; - - @DubboReference - private RemoteResidentPersonService remoteResidentPersonService; - - private final HuaWeiBoxApi huaWeiBoxApi; - private final E8PlatformApi e8PlatformApi; - private final ISisAuthRecordService sisAuthRecordService; - private final ISisPersonLibImgService sisPersonLibImgService; - private final ISisAuthGroupRefService sisAuthGroupRefService; - private final ISisAccessControlService sisAccessControlService; - - /** - * 每两分钟执行一次 - */ - @Scheduled(cron = "0 */2 * * * ?") - public void autoAuth() { - AtomicReference> unAuthPersonRef = new AtomicReference<>(new ArrayList<>()); - AtomicReference imgByteRef = new AtomicReference<>(new byte[0]); - - // 需要先设置模拟上下文 - SaTokenContextMockUtil.setMockContext(() -> { - // 模拟登录 - StpUtil.login(1); - unAuthPersonRef.set(remoteResidentPersonService.queryUnAuthPerson()); - List unAuthPerson = unAuthPersonRef.get(); - - try { - if (CollUtil.isNotEmpty(unAuthPerson)) { - for (RemoteResidentPersonVo person : unAuthPerson) { - log.info("开始定时授权:{}", person.getId()); - - // 判断是否已存在授权 - SisAuthRecordVo authRecord = sisAuthRecordService.queryByGroupIdAndPersonId(person.getAuthGroupId(), person.getId()); - if (ObjectUtil.isEmpty(authRecord)) { - // 无授权记录时,补录 - this.syncAuthRecord(person); - } - - imgByteRef.set(remoteFileService.downloadToByteArray(Long.parseLong(person.getOssId()))); - // 读取人像 - byte[] imgByte = imgByteRef.get(); - if (imgByte == null) continue; - - String nowMd5 = this.calculateMD5(imgByte); - SisPersonLibImgVo imgVo = sisPersonLibImgService.queryByImgMd5(nowMd5); - - Long huaweiId; - Boolean update; - if (ObjectUtil.isEmpty(imgVo)) { - // 写入华为盒子 - huaweiId = syncHuaweiBox(person, imgByte); - } else { - if (imgVo.getRemoteImgId() == null) { - huaweiId = syncHuaweiBox(person, imgByte); - } else { - huaweiId = imgVo.getRemoteImgId(); - } - } - if (huaweiId == null) continue; - - // 更新人像信息huaweiBoxId - update = sisPersonLibImgService.updateByPersonId(person.getId(), huaweiId, nowMd5); - if (!update) continue; - - // 同步E8平台 - Long e8Id = syncE8Plat(person, imgByte); - if (e8Id == null) continue; - - // 更新入驻员工E8平台id - remoteResidentPersonService.updateE8Id(person.getId(), e8Id); - - } - } else { - log.info("无待授权人员"); - } - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - } - - /** - * 补录授权记录 - * - * @param person bean - */ - private void syncAuthRecord(RemoteResidentPersonVo person) { - log.info("开始补录授权记录、人像信息"); - RemotePersonAuth personAuth = new RemotePersonAuth(); - personAuth.setId(person.getId()); - personAuth.setOssId(person.getOssId()); - personAuth.setName(person.getName()); - personAuth.setPhone(person.getPhone()); - personAuth.setSex(person.getGender().intValue()); - personAuth.setIdCardNumber(person.getIdCard()); - personAuth.setAuthGroupId(person.getAuthGroupId()); - personAuth.setAuthBegDate(person.getAuthBegDate()); - personAuth.setAuthEndDate(person.getAuthEndDate()); - sisAuthRecordService.insertByPerson(personAuth); - log.info("补录授权记录、人像信息完成"); - } - - /** - * 同步华为盒子 - * - * @param vo bean - * @param imgByte 图片字节 - * @return Long - */ - private Long syncHuaweiBox(RemoteResidentPersonVo vo, byte[] imgByte) { - log.info("开始写入华为平台"); - AddHWPersonReq req = new AddHWPersonReq(); - req.setIndex(CodePrefixConstants.PERSON_LIB_IMAGE_CODE_PREFIX + IdUtil.getSnowflakeNextIdStr()); - req.setName(vo.getName()); - req.setGender(vo.getGender() == 1L ? "0" : vo.getGender() == 2L ? "1" : "-1"); - req.setCredentialType("0"); - req.setCredentialNumber(vo.getIdCard()); - - ArrayList pictures = new ArrayList<>(); - pictures.add(Base64.getEncoder().encodeToString(imgByte)); - req.setPictures(pictures); - - Long pId = huaWeiBoxApi.addPerson(List.of(req)); - Assert.notNull(pId, "调用华为盒子新增图片失败"); - log.info("写入华为盒子完成,pId={}", pId); - return pId; - } - - /** - * 同步E8平台 - * - * @param vo bean - * @param imgByte 图片字节 - * @return Long - */ - private Long syncE8Plat(RemoteResidentPersonVo vo, byte[] imgByte) { - - log.info("e8平台上传照片"); - String e8ImgUrl = e8PlatformApi.uploadFace(imgByte); - Assert.notNull(e8ImgUrl, "图片上传E8平台失败"); - log.info("e8平台上传照片完成"); - - log.info("e8同步新建人员"); - CustomAddReq req = new CustomAddReq(); - req.setName(vo.getName()); - req.setGender(vo.getGender() != 1L ? 0 : 1); - req.setIdentityType(0); - req.setIdentityNo(vo.getIdCard()); - Long e8Id = e8PlatformApi.addCustomer(req).getId(); - Assert.notNull(e8Id, "e8同步新建人员失败"); - log.info("e8同步新建人员完成"); - - - log.info("e8平台开始发行凭证"); - IssueVoucherReq voucherReq = new IssueVoucherReq(); - voucherReq.setVoucherType(70); - voucherReq.setPersonID(e8Id); - voucherReq.setTxtData(e8ImgUrl); - voucherReq.setCardType(34); - Long voucherId = e8PlatformApi.issueVoucher(voucherReq); - Assert.notNull(voucherId, "e8平台发行凭证失败"); - log.info("e8平台发行凭证成功"); - - // 获取门禁 - List refVos = sisAuthGroupRefService.queryListByGroupId(vo.getAuthGroupId()); - Collection deviceIds = refVos.stream().filter(ref -> ref.getDeviceType() == 1).map(SisAuthGroupRefVo::getDeviceId).toList(); - if (CollUtil.isNotEmpty(deviceIds)) { - // 初始化赋值 - CustomerAuthAddReq authReq = new CustomerAuthAddReq(); - authReq.setPersonIds(List.of(e8Id)); - authReq.setStartTime(DateUtil.format(vo.getAuthBegDate(), "yyyy-MM-dd HH:mm:ss")); - authReq.setEndTime(DateUtil.format(vo.getAuthEndDate(), "yyyy-MM-dd HH:mm:ss")); - List list = new ArrayList<>(); - SisAccessControlVo accessControlVo; - for (Long deviceId : deviceIds) { - accessControlVo = sisAccessControlService.queryById(deviceId); - CustomerAuthAddReq.AuthGroupData authData = new CustomerAuthAddReq.AuthGroupData(); - authData.setId(Long.parseLong(accessControlVo.getOutDoorCode())); - authData.setType(0); - authData.setGatewayType(1); - list.add(authData); - } - authReq.setAuthData(list); - - log.info("e8平台开始授权"); - Boolean flag = e8PlatformApi.addCustomerAuth(authReq); - Assert.isTrue(flag, "E8平台授权失败!"); - log.info("E8平台授权完成!"); - } - return e8Id; - } - - /** - * 直接计算字节数组的MD5值 - * - * @param data 图片的字节数组 - * @return 32位小写MD5字符串 - */ - public String calculateMD5(byte[] data) throws Exception { - if (data == null) { - throw new IllegalArgumentException("输入数据不能为null"); - } - - MessageDigest md = MessageDigest.getInstance("MD5"); - md.update(data); - return bytesToHex(md.digest()); - } - - /** - * 字节数组转十六进制字符串 - * - * @param bytes 字节数组 - * @return 32位十六进制字符串 - */ - private String bytesToHex(byte[] bytes) { - StringBuilder sb = new StringBuilder(); - for (byte b : bytes) { - // %02x 表示两位小写十六进制,不足两位补0 - sb.append(String.format("%02x", b)); - } - return sb.toString(); - } -} diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/task/SyncLiftAuthTask.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/task/SyncLiftAuthTask.java new file mode 100644 index 00000000..27ce995c --- /dev/null +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/task/SyncLiftAuthTask.java @@ -0,0 +1,104 @@ +package org.dromara.sis.task; + +import cn.hutool.core.date.DateField; +import cn.hutool.core.date.DateUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.redis.utils.RedisUtils; +import org.dromara.sis.sdk.e8.E8PlatformApi; +import org.dromara.sis.sdk.e8.domain.QueryDto; +import org.dromara.sis.sdk.e8.domain.accessControl.req.AccessRecordFindReq; +import org.dromara.sis.sdk.e8.domain.accessControl.res.AccessRecordFindRes; +import org.dromara.sis.sdk.hik.HikApiService; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; + +import java.util.Date; +import java.util.List; +import java.util.Objects; + +/** + * @author lsm + * @apiNote SyncLiftAuthTask + * @since 2025/8/3 + */ +@Slf4j +@Configuration +@EnableScheduling +@RequiredArgsConstructor +public class SyncLiftAuthTask { + + private final E8PlatformApi apiService; + + /** + * 同步电梯权限 + */ + @Scheduled(cron = "*/5 * 6-22 * * ?") + public void syncLiftAuth() { + QueryDto dto = new QueryDto(); + dto.setPageIndex(1); + dto.setMaxResultCount(20); + + // 10秒内 + String starTime = DateUtil.format(DateUtil.offset(new Date(), DateField.SECOND, -10), "yyyy-MM-dd HH:mm:ss"); + String endTime = DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"); + + AccessRecordFindReq lift = new AccessRecordFindReq(); + lift.setStartTime(starTime); + lift.setEndTime(endTime); + lift.setRecordType(2); + + + // 9号电梯 + lift.setDeviceId(550757939925061L); + dto.setQueryDto(lift); + TableDataInfo nineLiftList = apiService.getPageAccessRecordList(dto); + if (nineLiftList.getTotal() != 0) { + liftAuth(nineLiftList.getRows(), "nineLift", "192.168.24.188"); + } + + // 15号电梯 + lift.setDeviceId(545024837750853L); + dto.setQueryDto(lift); + TableDataInfo fifteenLiftList = apiService.getPageAccessRecordList(dto); + if (fifteenLiftList.getTotal() != 0) { + liftAuth(fifteenLiftList.getRows(), "fifteenLift", "192.168.24.188"); + } + + + } + + + private void liftAuth(List list, String redisKey, String controlIP) { + // 取出第一条通行记录 + Long recordId = list.get(0).getId(); + + // 判断redis是否存在数据 + if (RedisUtils.isExistsObject(redisKey)) { + // 取出redis数据 + Long redisData = RedisUtils.getCacheObject(redisKey); + + // 判断redis数据是否等于返回通行记录 + if (redisData.equals(recordId)) { + log.info("已下发权限,不处理!通行记录={}", recordId); + } else { + log.info("下发权限!通行记录={}", recordId); + RedisUtils.setCacheObject(redisKey, recordId); + RedisUtils.expire(redisKey, 10); + for (int i = 0; i < 18; i++) { + HikApiService.getInstance().controlGateway(controlIP, (i + 1), 2); + } + } + } else { + // redis不存在数据,设置数据,同时下发权限 + RedisUtils.setCacheObject(redisKey, recordId); + RedisUtils.expire(redisKey, 10); + for (int i = 0; i < 18; i++) { + HikApiService.getInstance().controlGateway(controlIP, (i + 1), 2); + } + } + } + +} diff --git a/ruoyi-modules/ruoyi-job/pom.xml b/ruoyi-modules/ruoyi-job/pom.xml index a630212a..1802fa4e 100644 --- a/ruoyi-modules/ruoyi-job/pom.xml +++ b/ruoyi-modules/ruoyi-job/pom.xml @@ -69,6 +69,7 @@ org.dromara ruoyi-api-system + org.dromara sis-api @@ -76,6 +77,18 @@ compile + + org.dromara + property-api + 2.4.0 + + + + org.dromara + ruoyi-api-resource + 2.4.0 + + diff --git a/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/sis/HikDeviceCheckStateTask.java b/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/sis/HikDeviceCheckStateTask.java index 4c0ba39e..749a6c4b 100644 --- a/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/sis/HikDeviceCheckStateTask.java +++ b/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/sis/HikDeviceCheckStateTask.java @@ -5,9 +5,10 @@ import com.aizuda.snailjob.client.job.core.annotation.JobExecutor; import com.aizuda.snailjob.client.job.core.dto.JobArgs; import com.aizuda.snailjob.client.model.ExecuteResult; import com.aizuda.snailjob.common.log.SnailJobLog; +import com.alibaba.fastjson2.JSONObject; import lombok.RequiredArgsConstructor; import org.apache.dubbo.config.annotation.DubboReference; -import org.dromara.sis.api.RemoteHikDeviceService; +import org.dromara.sis.api.RemoteDeviceService; import org.dromara.sis.api.RemoteHikSdkService; import org.dromara.sis.api.domain.RemoteSdkChannel; import org.dromara.sis.api.domain.RemoteSisDeviceChannel; @@ -15,8 +16,7 @@ import org.dromara.sis.api.domain.RemoteSisDeviceManage; import org.dromara.sis.api.enums.DeviceTypeEnum; import org.springframework.stereotype.Component; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; /** @@ -31,7 +31,7 @@ import java.util.stream.Collectors; public class HikDeviceCheckStateTask { @DubboReference - private RemoteHikDeviceService remoteHikDeviceService; + private RemoteDeviceService remoteDeviceService; @DubboReference private RemoteHikSdkService remoteHikSdkService; @@ -44,7 +44,7 @@ public class HikDeviceCheckStateTask { * @throws InterruptedException */ public ExecuteResult jobExecute(JobArgs jobArgs) throws InterruptedException { - List device = remoteHikDeviceService.queryHikDevices(); + List device = remoteDeviceService.queryHikDevices(); if (CollUtil.isEmpty(device)) { SnailJobLog.REMOTE.info("需要同步的hik设备数量为0,任务执行结束"); return ExecuteResult.success(); @@ -76,17 +76,71 @@ public class HikDeviceCheckStateTask { */ private void updateDeviceChannelStatus(RemoteSisDeviceManage sisDeviceManage) { // 查询设备通道信息 - List ls = remoteHikDeviceService.queryDeviceChannels(sisDeviceManage.getDeviceIp()); + List ls = remoteDeviceService.queryDeviceChannels(sisDeviceManage.getDeviceIp()); SnailJobLog.REMOTE.info("设备[{}]本地通道数量={}", sisDeviceManage.getDeviceIp(), ls.size()); List sdkChannels = remoteHikSdkService.getDeviceChannel(sisDeviceManage.getDeviceIp()); SnailJobLog.REMOTE.info("设备[{}]sdk通道数量={}", sisDeviceManage.getDeviceIp(), sdkChannels.size()); Map data1 = ls.stream().collect(Collectors.toMap(RemoteSisDeviceChannel::getDeviceIp, item -> item)); Map data2 = sdkChannels.stream().collect(Collectors.toMap(RemoteSdkChannel::getChannelIp, item -> item)); + Set keys = new HashSet<>(data1.size() + data2.size()); + keys.addAll(data1.keySet()); + keys.addAll(data2.keySet()); + // 在data1 不在data2 的数据 -- 删除本体通道 + List deleteData = new ArrayList<>(data1.size()); + // 在data2有 data1中没有的数据 -- 写入本地通道 + List insertData = new ArrayList<>(data1.size()); + // 在2个集合中都存在的数据 -- 更新本地通道 + List updateData = new ArrayList<>(data1.size()); + keys.forEach(item -> { + RemoteSisDeviceChannel remoteSisDeviceChannel = data1.get(item); + RemoteSdkChannel remoteSdkChannel = data2.get(item); + if (remoteSisDeviceChannel == null) { + if (remoteSdkChannel != null) { + remoteSdkChannel.setDeviceId(sisDeviceManage.getId()); + remoteSdkChannel.setNvrIp(sisDeviceManage.getDeviceIp()); + remoteSdkChannel.setNvrPort(sisDeviceManage.getDevicePort()); + remoteSdkChannel.setNvrAccount(sisDeviceManage.getDeviceAccount()); + remoteSdkChannel.setNvrPwd(sisDeviceManage.getDevicePwd()); + remoteSdkChannel.setNvrFactoryNo(sisDeviceManage.getFactoryNo()); + remoteSdkChannel.setGroupId(sisDeviceManage.getGroupId()); + remoteSdkChannel.setTenantId(sisDeviceManage.getTenantId()); + // 写入 + insertData.add(remoteSdkChannel); + } + } else { + if (remoteSdkChannel == null) { + // 删除 + deleteData.add(remoteSisDeviceChannel); + } else { + // 更新 + if (!Objects.equals(remoteSdkChannel.getChannelStatus(), remoteSisDeviceChannel.getChannelState())) { + updateData.add(remoteSisDeviceChannel); + } + } + } + }); + // 开始执行更新操作 + if (!deleteData.isEmpty()) { + List delList = deleteData.stream().map(RemoteSisDeviceChannel::getDeviceIp).toList(); + int num = remoteDeviceService.deleteByChannelIps(delList); + SnailJobLog.REMOTE.info("删除通道完成,删除数量={}", num); + SnailJobLog.REMOTE.info("删除的通道列表={}", JSONObject.toJSONString(delList)); + } + // 开始写入操作 + if (!insertData.isEmpty()) { + Boolean result = remoteDeviceService.insertChannel(insertData); + SnailJobLog.REMOTE.info("写入通道信息完成,result={}", result); + } + // 开始更新操作 + if (!updateData.isEmpty()) { + Integer i = remoteDeviceService.updateChannelInfo(updateData); + SnailJobLog.REMOTE.info("更新通道完成,通道数量={},完成数量={}", updateData.size(), i); + } } @@ -104,16 +158,17 @@ public class HikDeviceCheckStateTask { if (item.getDeviceStatus() != onLineState) { SnailJobLog.REMOTE.info("设备[{}]在线状态变更,开始更新状态。 old={},new ={} ", item.getDeviceIp(), item.getDeviceStatus(), onLineState); item.setDeviceStatus(onLineState); - Boolean result = remoteHikDeviceService.updateDeviceState(item); + Boolean result = remoteDeviceService.updateDeviceState(item); SnailJobLog.REMOTE.info("设备[{}]在线状态变更,状态更新完成。 result={} ", item.getDeviceIp(), result); // 监测当前设备是否存在通道,如果有则跟新通道信息 - List ls = remoteHikDeviceService.queryDeviceChannels(item.getDeviceIp()); + List ls = remoteDeviceService.queryDeviceChannels(item.getDeviceIp()); if (CollUtil.isNotEmpty(ls)) { - Boolean r1 = remoteHikDeviceService.updateDeviceChannelState(item.getDeviceIp(), onLineState); + Boolean r1 = remoteDeviceService.updateDeviceChannelState(item.getDeviceIp(), onLineState); SnailJobLog.REMOTE.info("设备通道[{}]在线状态变更,状态更新完成。 result={} ", item.getDeviceIp(), r1); } } return isLogin; } + } diff --git a/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/sis/SyncGrantAuthTask.java b/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/sis/SyncGrantAuthTask.java new file mode 100644 index 00000000..890b2f70 --- /dev/null +++ b/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/sis/SyncGrantAuthTask.java @@ -0,0 +1,243 @@ +package org.dromara.job.snailjob.sis; + +import cn.dev33.satoken.context.mock.SaTokenContextMockUtil; +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.collection.CollUtil; +import com.aizuda.snailjob.client.job.core.annotation.JobExecutor; +import com.aizuda.snailjob.client.job.core.dto.JobArgs; +import com.aizuda.snailjob.client.model.ExecuteResult; +import lombok.extern.slf4j.Slf4j; +import org.apache.dubbo.config.annotation.DubboReference; +import org.dromara.property.api.RemoteResidentPersonService; +import org.dromara.property.api.domain.vo.RemoteResidentPersonVo; +import org.dromara.resource.api.RemoteFileService; +import org.dromara.sis.api.RemoteSisAuthService; +import org.dromara.sis.api.domain.RemotePersonAuth; +import org.springframework.stereotype.Component; + +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +/** + * @author lsm + * @apiNote SyncGrantAuthTask + * @since 2025/8/1 + */ +@Slf4j +@Component +@JobExecutor(name = "syncGrantAuthTask") +public class SyncGrantAuthTask { + + @DubboReference + private RemoteFileService remoteFileService; + + @DubboReference + private RemoteSisAuthService remoteSisAuthService; + + @DubboReference + private RemoteResidentPersonService remoteResidentPersonService; + + public ExecuteResult jobExecute(JobArgs jobArgs) throws InterruptedException { + AtomicReference> unAuthPersonRef = new AtomicReference<>(new ArrayList<>()); + AtomicReference imgByteRef = new AtomicReference<>(new byte[0]); + + // 需要先设置模拟上下文 + SaTokenContextMockUtil.setMockContext(() -> { + // 模拟登录 + StpUtil.login(1); + unAuthPersonRef.set(remoteResidentPersonService.queryUnAuthPerson()); + List unAuthPerson = unAuthPersonRef.get(); + + try { + if (CollUtil.isNotEmpty(unAuthPerson)) { + for (RemoteResidentPersonVo person : unAuthPerson) { + log.info("开始定时授权:{}----{}", person.getName(), person.getId()); + + // 判断是否存在授权 + Boolean auth = remoteSisAuthService.queryPersonAuth(person.getAuthGroupId(), person.getId()); + if (!auth) { + log.info("无授权记录:{}----{}", person.getName(), person.getId()); + + // 补录授权记录、人像信息 + Boolean flag = syncAuthRecord(person); + if (!flag) { + log.info("补录授权记录、人像信息失败:{}----{}", person.getName(), person.getId()); + continue; + } + + // 下载人像 + byte[] imgByte; + try { + imgByteRef.set(remoteFileService.downloadToByteArray(Long.parseLong(person.getOssId()))); + imgByte = imgByteRef.get(); + } catch (Exception e) { + log.info("下载图片失败:{}----{}", person.getName(), person.getId()); + remoteSisAuthService.deletePersonAuth(List.of(person.getId()), new ArrayList<>()); + continue; + } + +// // 计算图片MD5 +// String nowMd5 = calculateMD5(imgByte); +// // 通过MD5查询相同图片 +// Long remoteId = remoteSisAuthService.queryImgIdByImgMd5(nowMd5); +// +// Long huaweiId; +// if (remoteId == null) { +// // 当前本地人像信息不存在相同照片,直接上传华为盒子 +// huaweiId = syncHuaweiBox(person, imgByte); +// } else { +// huaweiId = remoteId; +// } +// +// if (huaweiId == null) { +// log.info("华为盒子人像上传失败:{}-----{}", person.getName(), person.getId()); +// remoteSisAuthService.deletePersonAuth(List.of(person.getId()), new ArrayList<>()); +// continue; +// } +// +// Boolean update = false; +// try { +// update = remoteSisAuthService.updateImgByPersonId(person.getId(), huaweiId, nowMd5); +// } catch (Exception e) { +// remoteSisAuthService.deletePersonAuth(List.of(person.getId()), new ArrayList<>()); +// } +// +// if (!update) { +// log.info("更新人像信息失败:{}-----{}", person.getName(), person.getId()); +// remoteSisAuthService.deletePersonAuth(List.of(person.getId()), new ArrayList<>()); +// continue; +// } + + // 同步人像到E8平台 + Long e8Id = syncE8Plat(person, imgByte); + if (e8Id == null) { + log.info("同步人像到E8平台失败:{}-----{}", person.getName(), person.getId()); + remoteSisAuthService.deletePersonAuth(List.of(person.getId()), new ArrayList<>()); + continue; + } + + // 更新入驻员工E8平台id + remoteResidentPersonService.updateE8Id(person.getId(), e8Id); + } else { + // 存在授权记录,为了避免重复授权,删除授权记录 + remoteSisAuthService.deletePersonAuth(List.of(person.getId()), new ArrayList<>()); + } + } + } else { + log.info("无待授权人员"); + } + } catch (Exception e) { + log.info("同步授权异常"); + throw new RuntimeException(e); + } + }); + + + return ExecuteResult.success(); + } + + /** + * 补录授权记录 + * + * @param person bean + */ + private Boolean syncAuthRecord(RemoteResidentPersonVo person) { + log.info("开始补录授权记录、人像信息:{}----{}", person.getName(), person.getId()); + RemotePersonAuth personAuth = getRemotePersonAuth(person); + + Boolean flag; + try { + flag = remoteSisAuthService.personAuth(personAuth); + if (flag) { + log.info("补录授权记录、人像信息完成:{}----{}", person.getName(), person.getId()); + } + } catch (Exception e) { + return false; + } + return flag; + } + + /** + * 实体类转换 + */ + private RemotePersonAuth getRemotePersonAuth(RemoteResidentPersonVo person) { + RemotePersonAuth personAuth = new RemotePersonAuth(); + personAuth.setId(person.getId()); + personAuth.setOssId(person.getOssId()); + personAuth.setName(person.getName()); + personAuth.setPhone(person.getPhone()); + personAuth.setSex(person.getGender().intValue()); + personAuth.setIdCardNumber(person.getIdCard()); + personAuth.setAuthGroupId(person.getAuthGroupId()); + personAuth.setAuthBegDate(person.getAuthBegDate()); + personAuth.setAuthEndDate(person.getAuthEndDate()); + return personAuth; + } + + /** + * 同步华为盒子 + * + * @param vo bean + * @param imgByte 图片字节 + * @return Long + */ + private Long syncHuaweiBox(RemoteResidentPersonVo vo, byte[] imgByte) { + log.info("开始写入华为盒子"); + RemotePersonAuth personAuth = getRemotePersonAuth(vo); + Long pId = remoteSisAuthService.syncHuaweiBox(personAuth, imgByte); + if (pId != null) { + log.info("写入华为盒子完成,pId={}", pId); + } + return pId; + } + + /** + * 同步E8平台 + * + * @param vo bean + * @param imgByte 图片字节 + * @return Long + */ + private Long syncE8Plat(RemoteResidentPersonVo vo, byte[] imgByte) { + log.info("开始写入E8平台"); + RemotePersonAuth personAuth = getRemotePersonAuth(vo); + Long e8Id = remoteSisAuthService.syncE8Plat(personAuth, imgByte); + if (e8Id != null) { + log.info("写入E8平台完成:e8Id={}", e8Id); + } + return e8Id; + } + + /** + * 直接计算字节数组的MD5值 + * + * @param data 图片的字节数组 + * @return 32位小写MD5字符串 + */ + public String calculateMD5(byte[] data) throws Exception { + if (data == null) { + throw new IllegalArgumentException("输入数据不能为null"); + } + + MessageDigest md = MessageDigest.getInstance("MD5"); + md.update(data); + return bytesToHex(md.digest()); + } + + /** + * 字节数组转十六进制字符串 + * + * @param bytes 字节数组 + * @return 32位十六进制字符串 + */ + private String bytesToHex(byte[] bytes) { + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + // %02x 表示两位小写十六进制,不足两位补0 + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } +}