From e69093390c581115b6bf0185f93b0955c33bb449 Mon Sep 17 00:00:00 2001 From: dy <2389062315@qq.com> Date: Wed, 30 Jul 2025 18:42:41 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=8E=92=E7=8F=AD=E8=AF=A6=E6=83=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AttendanceArrangementController.java | 4 +- .../domain/bo/AttendanceArrangementBo.java | 11 +- .../mapper/AttendanceArrangementMapper.java | 8 +- .../IAttendanceArrangementService.java | 2 +- .../AttendanceArrangementServiceImpl.java | 175 ++++++++---------- 5 files changed, 90 insertions(+), 110 deletions(-) diff --git a/ruoyi-modules/Property/src/main/java/org/dromara/property/controller/AttendanceArrangementController.java b/ruoyi-modules/Property/src/main/java/org/dromara/property/controller/AttendanceArrangementController.java index dd5d0c02..e073bac5 100644 --- a/ruoyi-modules/Property/src/main/java/org/dromara/property/controller/AttendanceArrangementController.java +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/controller/AttendanceArrangementController.java @@ -37,7 +37,7 @@ public class AttendanceArrangementController extends BaseController { private final IAttendanceArrangementService attendanceArrangementService; /** - * 查询排班列表 + * 查询排班详情列表 */ @SaCheckPermission("Property:arrangement:list") @GetMapping("/list") @@ -46,7 +46,7 @@ public class AttendanceArrangementController extends BaseController { } /** - * 查询排班列表 + * 查询某个月的日历排班信息的排班列表 */ @SaCheckPermission("Property:arrangement:explore") @GetMapping("/explore") diff --git a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/bo/AttendanceArrangementBo.java b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/bo/AttendanceArrangementBo.java index 6cd85bde..a8a6fdec 100644 --- a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/bo/AttendanceArrangementBo.java +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/bo/AttendanceArrangementBo.java @@ -1,20 +1,17 @@ package org.dromara.property.domain.bo; -import cn.hutool.core.date.DateTime; import com.fasterxml.jackson.annotation.JsonFormat; -import org.dromara.property.domain.AttendanceArrangement; -import org.dromara.common.mybatis.core.domain.BaseEntity; -import org.dromara.common.core.validate.AddGroup; -import org.dromara.common.core.validate.EditGroup; import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotNull; import lombok.Data; import lombok.EqualsAndHashCode; -import jakarta.validation.constraints.*; +import org.dromara.common.core.validate.EditGroup; +import org.dromara.common.mybatis.core.domain.BaseEntity; +import org.dromara.property.domain.AttendanceArrangement; import org.dromara.property.domain.AttendanceScheduleCycle; import org.dromara.property.domain.AttendanceUserGroup; import org.springframework.format.annotation.DateTimeFormat; -import java.time.LocalDate; import java.time.YearMonth; import java.util.Date; import java.util.List; diff --git a/ruoyi-modules/Property/src/main/java/org/dromara/property/mapper/AttendanceArrangementMapper.java b/ruoyi-modules/Property/src/main/java/org/dromara/property/mapper/AttendanceArrangementMapper.java index 97efb77a..9940f93d 100644 --- a/ruoyi-modules/Property/src/main/java/org/dromara/property/mapper/AttendanceArrangementMapper.java +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/mapper/AttendanceArrangementMapper.java @@ -1,16 +1,12 @@ package org.dromara.property.mapper; -import cn.hutool.core.date.DateTime; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; -import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.property.domain.AttendanceArrangement; -import org.dromara.property.domain.bo.AttendanceArrangementBo; import org.dromara.property.domain.vo.AttendanceArrangementVo; import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; import java.time.LocalDate; +import java.util.Date; import java.util.List; /** @@ -22,7 +18,7 @@ import java.util.List; public interface AttendanceArrangementMapper extends BaseMapperPlus { @Select("SELECT * FROM attendance_arrangement WHERE start_date <= #{startTime} AND end_date >= #{startTime}") - AttendanceArrangementVo selectVoByTime(LocalDate startTime); + AttendanceArrangementVo selectVoByTime(Date startTime); List selectArrangementList(LocalDate calendarStartDate, LocalDate calendarEndTimeDate); diff --git a/ruoyi-modules/Property/src/main/java/org/dromara/property/service/IAttendanceArrangementService.java b/ruoyi-modules/Property/src/main/java/org/dromara/property/service/IAttendanceArrangementService.java index e889d044..ce48d6fc 100644 --- a/ruoyi-modules/Property/src/main/java/org/dromara/property/service/IAttendanceArrangementService.java +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/service/IAttendanceArrangementService.java @@ -25,7 +25,7 @@ public interface IAttendanceArrangementService { AttendanceArrangementVo queryById(Long id); /** - * 分页查询排班列表 + * 分页查询排班详情列表 * * @param bo 查询条件 * @param pageQuery 分页参数 diff --git a/ruoyi-modules/Property/src/main/java/org/dromara/property/service/impl/AttendanceArrangementServiceImpl.java b/ruoyi-modules/Property/src/main/java/org/dromara/property/service/impl/AttendanceArrangementServiceImpl.java index cca1a7d9..c0c9a82d 100644 --- a/ruoyi-modules/Property/src/main/java/org/dromara/property/service/impl/AttendanceArrangementServiceImpl.java +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/service/impl/AttendanceArrangementServiceImpl.java @@ -43,6 +43,12 @@ public class AttendanceArrangementServiceImpl implements IAttendanceArrangementS private final AttendanceArrangementGroupMapper arrangementGroupMapper; + private final AttendanceWeekSetMapper weekSetMapper; + + private final AttendanceShiftMapper attendanceShiftMapper; + + private final AttendanceScheduleCycleMapper scheduleCycleMapper; + /** * 查询排班 * @@ -62,7 +68,7 @@ public class AttendanceArrangementServiceImpl implements IAttendanceArrangementS } /** - * 分页查询排班列表 + * 分页查询排班详情列表 * * @param bo 查询条件 * @param pageQuery 分页参数 @@ -75,28 +81,80 @@ public class AttendanceArrangementServiceImpl implements IAttendanceArrangementS Page attendanceArrangementVoPage = result.setRecords(result.getRecords().stream().peek(vo -> { + //1.根据当前日期查询在开始时间和结束时间之间的排班信息 + //查询指定日期在哪些排班中 + List arrangementList = baseMapper.selectList(Wrappers.lambdaQuery().ge(AttendanceArrangement::getStartDate, bo.getCurrentDate()).le(AttendanceArrangement::getEndDate, bo.getCurrentDate())); + //根据排班查询出考勤组id + List groupIds = arrangementList.stream().map(AttendanceArrangement::getGroupId).distinct().toList(); - }).collect(Collectors.toList())); + // 2.循环将所有的考勤组id设置到考勤组中 + //循环groupIds,循环和AttendanceGroup中的id做对比,如果有相同的,则取出attendanceGroup中的所有数据 + groupIds.forEach( + groupId -> { + //从数据库查询出当前考勤组的所有数据 + AttendanceGroup group = attendanceGroupMapper.selectOne(Wrappers.lambdaQuery().eq(AttendanceGroup::getId, groupId)); + + // 3.查询出当前考勤组的所有数据,从考勤组判断当前id是固定班制还是排班制 + if (group.getAttendanceType().equals(StatusConstant.FIXEDSCHEDULE)) { + // 3.1固定班制:将当前的日期转为周几,然后与数据库中的班次周数作对比,取出当前日期的班次信息 + //将传来的日期参数转为周几 + int week = DateUtil.dayOfWeek(bo.getCurrentDate()); + //取出当前日期的周数,与数据库中的班次周数作对比,取出当前日期的班次信息 + AttendanceWeekSet weekSet = weekSetMapper.selectOne(Wrappers.lambdaQuery().eq(AttendanceWeekSet::getGroupId, groupId).eq(AttendanceWeekSet::getDayOfWeek, week)); + //将weekSet存到结果中 + AttendanceArrangementVo arrangementVo = new AttendanceArrangementVo(); + arrangementVo.setWeekSet(weekSet); + //根据weekSet取出id,根据id查询出attendanceWeekSetShift表中的shiftId + Long shiftId = weekSet.getId(); + //根据shiftId查询attendanceShift表中对应的id的数据 + AttendanceShift shift = attendanceShiftMapper.selectById(shiftId); + //将shift存到结果中 + arrangementVo.setShift(shift); + + } else if (group.getAttendanceType().equals(StatusConstant.SHIFTSCHEDULE)) { + // 3.2排班制:判断第一天是从几号开始,循环判断,判断当前是循环中的第几天,取出当前天数的班次信息。 + //取出排班中的开始时间和结束时间 + Date startDate = bo.getStartDate(); + Date endDate = bo.getEndDate(); + Date currentDate = bo.getCurrentDate(); + //取出attendanceScheduleCycle表中的天数 + Integer cycleDays = scheduleCycleMapper.selectOne(Wrappers.lambdaQuery().eq(AttendanceScheduleCycle::getGroupId, groupId)).getDayNumber(); + //在startDate和endDate之间循环,判端当前日期是cycleDays中的第几天 + int cycleDay = 0; + while (startDate.before(currentDate) || endDate.after(currentDate)) { + cycleDay++; + startDate = DateUtil.offsetDay(startDate, 1); + //判断当前日期是clcleDays中的第几天 + if (cycleDay > cycleDays) { + cycleDay = 1; + } + } + //根据cycleDay查询出当前日期的班次信息 + AttendanceScheduleCycle cycle = scheduleCycleMapper.selectOne(Wrappers.lambdaQuery().eq(AttendanceScheduleCycle::getGroupId, groupId).eq(AttendanceScheduleCycle::getDayNumber, cycleDay)); + //将cycle存到结果中 + AttendanceArrangementVo arrangementVo = new AttendanceArrangementVo(); + arrangementVo.setCycle(cycle); + + } + } + ); -// //1.根据当前日期查询出排班信息 -// AttendanceArrangementVo arrangementvo = baseMapper.selectVoByTime(bo.getCurrentDate()); -// //2.查询人员组的信息 -// //根据开始时间查询出所有的排班id -// List scheduleIdList = baseMapper.selectList(Wrappers.lambdaQuery().ge(AttendanceArrangement::getStartDate, bo.getCurrentDate())).stream().map(AttendanceArrangement::getId).collect(Collectors.toList()); -// //根据排班的id查询出排班的人员详细信息 -// List userGroupList = userGroupMapper.selectList(Wrappers.lambdaQuery().in(AttendanceUserGroup::getScheduleId, scheduleIdList)); -// //将排班人员信息添加到排班信息中 -// arrangementvo.setUserGroupList(userGroupList); + + +// AttendanceArrangementVo arrangementvo = baseMapper.selectVoByTime(bo.getCurrentDate()); +// //2.查询人员组的信息 +// //根据开始时间查询出所有的排班id +// List scheduleIdList = baseMapper.selectList(Wrappers.lambdaQuery().ge(AttendanceArrangement::getStartDate, bo.getCurrentDate())).stream().map(AttendanceArrangement::getId).collect(Collectors.toList()); +// //根据排班的id查询出排班的人员详细信息 +// List userGroupList = userGroupMapper.selectList(Wrappers.lambdaQuery().in(AttendanceUserGroup::getScheduleId, scheduleIdList)); +// //将排班人员信息添加到排班信息中 +// arrangementvo.setUserGroupList(userGroupList); // -// //3.根据排班的id查询出排班的考勤组id -// List groupIds = baseMapper.selectList(Wrappers.lambdaQuery().ge(AttendanceArrangement::getStartDate, bo.getCurrentDate())).stream().map(AttendanceArrangement::getGroupId).distinct().collect(Collectors.toList()); -// //判断考勤组是排班制还是固定班制 -// if (arrangementvo.getScheduleType().equals(StatusConstant.FIXEDSCHEDULE)) { -// //排班制 -// //根据排班的id查询出排班的考勤组id -// List weekSetList = weekSetMapper.selectList(Wrappers.lambdaQuery().in(AttendanceWeekSet::getGroupId, groupIds)); -// } +// //3.根据排班的id查询出排班的考勤组id +// List groupIds = baseMapper.selectList(Wrappers.lambdaQuery().ge(AttendanceArrangement::getStartDate, bo.getCurrentDate())).stream().map(AttendanceArrangement::getGroupId).distinct().collect(Collectors.toList()); +// //判断考勤组是排班制还是固定班制 + }).collect(Collectors.toList())); return TableDataInfo.build(attendanceArrangementVoPage); @@ -161,65 +219,6 @@ public class AttendanceArrangementServiceImpl implements IAttendanceArrangementS // 循环结束后如果 result 仍为空,则返回空列表 return Collections.emptyList(); - -// //1.如:在2号到10号有固定班制和排班制,根据当前日期查询出所有的考勤组id -// //查询指定日期在哪些排班中 -// List arrangementList = baseMapper.selectList(Wrappers.lambdaQuery().ge(AttendanceArrangement::getStartDate, bo.getCurrentDate()).le(AttendanceArrangement::getEndDate, bo.getCurrentDate())); -// //根据排班查询出考勤组id -// List groupIds = arrangementList.stream().map(AttendanceArrangement::getGroupId).distinct().toList(); -// -// // 2.循环将所有的考勤组id设置到考勤组中 -// //循环groupIds,循环和AttendanceGroup中的id做对比,如果有相同的,则取出attendanceGroup中的所有数据 -// groupIds.forEach( -// groupId -> { -// //从数据库查询出当前考勤组的所有数据 -// AttendanceGroup group = attendanceGroupMapper.selectOne(Wrappers.lambdaQuery().eq(AttendanceGroup::getId, groupId)); -// -// // 3.查询出当前考勤组的所有数据,从考勤组判断当前id是固定班制还是排班制 -// if (group.getAttendanceType().equals(StatusConstant.FIXEDSCHEDULE)) { -// // 3.1固定班制:将当前的日期转为周几,然后与数据库中的班次周数作对比,取出当前日期的班次信息 -// //将传来的日期参数转为周几 -// int week = DateUtil.dayOfWeek(bo.getCurrentDate()); -// //取出当前日期的周数,与数据库中的班次周数作对比,取出当前日期的班次信息 -// AttendanceWeekSet weekSet = weekSetMapper.selectOne(Wrappers.lambdaQuery().eq(AttendanceWeekSet::getGroupId, groupId).eq(AttendanceWeekSet::getDayOfWeek, week)); -// //将weekSet存到结果中 -// AttendanceArrangementVo arrangementVo = new AttendanceArrangementVo(); -// arrangementVo.setWeekSet(weekSet); -// //根据weekSet取出id,根据id查询出attendanceWeekSetShift表中的shiftId -// Long shiftId = weekSet.getId(); -// //根据shiftId查询attendanceShift表中对应的id的数据 -// AttendanceShift shift = attendanceShiftMapper.selectById(shiftId); -// //将shift存到结果中 -// arrangementVo.setShift(shift); -// -// } else if (group.getAttendanceType().equals(StatusConstant.SHIFTSCHEDULE)) { -// // 3.2排班制:判断第一天是从几号开始,循环判断,判断当前是循环中的第几天,取出当前天数的班次信息。 -// //取出排班中的开始时间和结束时间 -// Date startDate = bo.getStartDate(); -// Date endDate = bo.getEndDate(); -// Date currentDate = bo.getCurrentDate(); -// //取出attendanceScheduleCycle表中的天数 -// Integer cycleDays = scheduleCycleMapper.selectOne(Wrappers.lambdaQuery().eq(AttendanceScheduleCycle::getGroupId, groupId)).getDayNumber(); -// //在startDate和endDate之间循环,判端当前日期是cycleDays中的第几天 -// int cycleDay = 0; -// while (startDate.before(currentDate) || endDate.after(currentDate)) { -// cycleDay++; -// startDate = DateUtil.offsetDay(startDate, 1); -// //判断当前日期是clcleDays中的第几天 -// if (cycleDay > cycleDays) { -// cycleDay = 1; -// } -// } -// //根据cycleDay查询出当前日期的班次信息 -// AttendanceScheduleCycle cycle = scheduleCycleMapper.selectOne(Wrappers.lambdaQuery().eq(AttendanceScheduleCycle::getGroupId, groupId).eq(AttendanceScheduleCycle::getDayNumber, cycleDay)); -// //将cycle存到结果中 -// AttendanceArrangementVo arrangementVo = new AttendanceArrangementVo(); -// arrangementVo.setCycle(cycle); -// -// } -// } -// ); - // // 计算交集的开始时间(取较晚的开始时间)和结束时间(取较早的结束时间) // Date overlapStart = calendarStartDate.after(startDate) // ? calendarStartDate : startDate; @@ -229,22 +228,6 @@ public class AttendanceArrangementServiceImpl implements IAttendanceArrangementS //根据交集的时间范围查询排班信息 -// //1.根据日历的开始时间和日历的结束时间查询所有的排班信息 -//// List arrangementList = baseMapper.selectList(Wrappers.lambdaQuery().ge(AttendanceArrangement::getStartDate, bo.getCalendarStartDate()).le(AttendanceArrangement::getEndDate, bo.getCalendarEndTimeDate())); -// //2.查询人员组的信息 -// //根据开始时间查询排班的id -// List scheduleIdList = arrangementList.stream().map(AttendanceArrangement::getId).collect(Collectors.toList()); -// //根据排班的id查询出排班的人员详细信息 -// List userGroupList = userGroupMapper.selectList(Wrappers.lambdaQuery().in(AttendanceUserGroup::getScheduleId, scheduleIdList)); -// //3.将排班人员信息添加到排班信息中 -// -// return arrangementList.stream().map(arrangement -> { -// AttendanceArrangementVo vo = MapstructUtils.convert(arrangement, AttendanceArrangementVo.class); -// assert vo != null; -// vo.setUserGroupList(userGroupList.stream().filter(userGroup -> userGroup.getScheduleId().equals(arrangement.getId())).collect(Collectors.toList())); -// return vo; -// }).collect(Collectors.toList()); - } @@ -366,6 +349,10 @@ public class AttendanceArrangementServiceImpl implements IAttendanceArrangementS //获取当前排班的id List idList = new ArrayList<>(ids); + + //根据当前排班的id 删除attendanceArrangementGroup表中的数据 + arrangementGroupMapper.delete(Wrappers.lambdaQuery().in(AttendanceArrangementGroup::getArrangementId, idList)); + //根据获取的id删除attendanceUserGroup表中的数据 userGroupMapper.delete(Wrappers.lambdaQuery().in(AttendanceUserGroup::getScheduleId, idList)); return baseMapper.deleteByIds(ids) > 0; From 79f98b538715fa1abb6b42dd155e7e9b3c54dd7c Mon Sep 17 00:00:00 2001 From: zcxlsm Date: Wed, 30 Jul 2025 19:58:22 +0800 Subject: [PATCH 2/3] =?UTF-8?q?refactor(property):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E4=BA=BA=E5=91=98=E5=AF=BC=E5=85=A5=E5=92=8C=E4=BA=BA=E8=84=B8?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../property/domain/bo/ResidentPersonBo.java | 4 +- .../ResidentPersonImportListener.java | 16 ++-- .../service/IResidentPersonService.java | 10 ++- .../impl/ResidentPersonServiceImpl.java | 30 ++++++-- .../property/utils/UploadFaceUtil.java | 20 ++--- .../org/dromara/sis/task/AuthSyncTask.java | 73 ++++++++++--------- 6 files changed, 87 insertions(+), 66 deletions(-) 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/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/ResidentPersonServiceImpl.java b/ruoyi-modules/Property/src/main/java/org/dromara/property/service/impl/ResidentPersonServiceImpl.java index 7ed604b5..87d22c74 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 @@ -169,16 +169,16 @@ 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()); @@ -224,7 +224,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 +252,11 @@ 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 = remoteSisAuth.deletePersonAuth(ids, e8Ids); + Assert.isTrue(auth, "删除授权记录失败!"); + } + } return baseMapper.deleteByIds(ids) > 0; } @@ -284,4 +287,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..ead227af 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.StandardCharsets; 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(), StandardCharsets.UTF_8)) { 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/src/main/java/org/dromara/sis/task/AuthSyncTask.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/task/AuthSyncTask.java index 901f54cc..fc6acecb 100644 --- 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 @@ -61,7 +61,7 @@ public class AuthSyncTask { /** * 每两分钟执行一次 */ - @Scheduled(cron = "0 */2 * * * ?") + @Scheduled(cron = "0 */5 * * * ?") public void autoAuth() { AtomicReference> unAuthPersonRef = new AtomicReference<>(new ArrayList<>()); AtomicReference imgByteRef = new AtomicReference<>(new byte[0]); @@ -81,43 +81,44 @@ public class AuthSyncTask { // 判断是否已存在授权 SisAuthRecordVo authRecord = sisAuthRecordService.queryByGroupIdAndPersonId(person.getAuthGroupId(), person.getId()); if (ObjectUtil.isEmpty(authRecord)) { - // 无授权记录时,补录 + log.info("无授权记录:{}", person.getId()); 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("已存在授权记录:{}", person.getId()); } - - 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("无待授权人员"); From 9afeed3108a40ce8826cbca502565e13616d76a0 Mon Sep 17 00:00:00 2001 From: zcxlsm Date: Thu, 31 Jul 2025 15:12:24 +0800 Subject: [PATCH 3/3] =?UTF-8?q?feat(sis):=20=E9=87=8D=E6=9E=84=E6=99=BA?= =?UTF-8?q?=E8=83=BD=E8=AE=BE=E5=A4=87=E9=80=9A=E4=BF=A1=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-modules/Sis/pom.xml | 6 +- .../sdk/smartDevices/utils/LightingUtil.java | 299 +++++++++++------- .../sis/sdk/smartDevices/utils/MeterUtil.java | 126 ++++++++ .../smartDevices/utils/PowerMeterUtil.java | 179 ----------- .../smartDevices/utils/WaterMeterUtil.java | 168 ---------- 5 files changed, 318 insertions(+), 460 deletions(-) create mode 100644 ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/smartDevices/utils/MeterUtil.java delete mode 100644 ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/smartDevices/utils/PowerMeterUtil.java delete mode 100644 ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/smartDevices/utils/WaterMeterUtil.java 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/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); - } -}