Merge branch 'master' of http://47.109.37.87:3000/by2025/SmartParks
Some checks are pending
Gitea Actions Demo / Explore-Gitea-Actions (push) Waiting to run

This commit is contained in:
lxj
2025-07-31 15:43:02 +08:00
16 changed files with 495 additions and 636 deletions

View File

@@ -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")

View File

@@ -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;

View File

@@ -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;
/**

View File

@@ -57,33 +57,31 @@ public class ResidentPersonImportListener extends AnalysisEventListener<Resident
@Override
public void invoke(ResidentPersonImportVo personVo, AnalysisContext context) {
ResidentUnitVo unitVo = residentUnitService.queryById(unitId);
List<ResidentPersonVo> list = new ArrayList<>();
ResidentPersonVo person = new ResidentPersonVo();
// 判断证件号是否为空
if (StringUtils.isEmpty(personVo.getIdCard())) {
failureNum++;
failureMsg.append("<br/>").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("<br/>").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<Resident
successMsg.append("<br/>").append(successNum).append("、账号 ").append(bo.getUserName()).append(" 更新成功");
} else {
failureNum++;
failureMsg.append("<br/>").append(failureNum).append("、账号 ").append(list.get(0).getUserName()).append(" 已存在");
failureMsg.append("<br/>").append(failureNum).append("、账号 ").append(personVo.getUserName()).append(" 已存在");
}
} catch (Exception e) {

View File

@@ -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<AttendanceArrangement, AttendanceArrangementVo> {
@Select("SELECT * FROM attendance_arrangement WHERE start_date <= #{startTime} AND end_date >= #{startTime}")
AttendanceArrangementVo selectVoByTime(LocalDate startTime);
AttendanceArrangementVo selectVoByTime(Date startTime);
List<AttendanceArrangement> selectArrangementList(LocalDate calendarStartDate, LocalDate calendarEndTimeDate);

View File

@@ -25,7 +25,7 @@ public interface IAttendanceArrangementService {
AttendanceArrangementVo queryById(Long id);
/**
* 分页查询排班列表
* 分页查询排班详情列表
*
* @param bo 查询条件
* @param pageQuery 分页参数

View File

@@ -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<ResidentPersonVo>
*/
List<ResidentPersonVo> queryUnAuthPerson();
/**
* 通过单位和姓名,查询人员信息
*
* @param unitId 单位id
* @param name 姓名
*/
ResidentPersonVo queryByUnitIdAndName(Long unitId, String name);
}

View File

@@ -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<AttendanceArrangementVo> attendanceArrangementVoPage = result.setRecords(result.getRecords().stream().peek(vo -> {
//1.根据当前日期查询在开始时间和结束时间之间的排班信息
//查询指定日期在哪些排班中
List<AttendanceArrangement> arrangementList = baseMapper.selectList(Wrappers.<AttendanceArrangement>lambdaQuery().ge(AttendanceArrangement::getStartDate, bo.getCurrentDate()).le(AttendanceArrangement::getEndDate, bo.getCurrentDate()));
//根据排班查询出考勤组id
List<Long> 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.<AttendanceGroup>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.<AttendanceWeekSet>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.<AttendanceScheduleCycle>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.<AttendanceScheduleCycle>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<Long> scheduleIdList = baseMapper.selectList(Wrappers.<AttendanceArrangement>lambdaQuery().ge(AttendanceArrangement::getStartDate, bo.getCurrentDate())).stream().map(AttendanceArrangement::getId).collect(Collectors.toList());
// //根据排班的id查询出排班的人员详细信息
// List<AttendanceUserGroup> userGroupList = userGroupMapper.selectList(Wrappers.<AttendanceUserGroup>lambdaQuery().in(AttendanceUserGroup::getScheduleId, scheduleIdList));
// //将排班人员信息添加到排班信息中
// arrangementvo.setUserGroupList(userGroupList);
// AttendanceArrangementVo arrangementvo = baseMapper.selectVoByTime(bo.getCurrentDate());
// //2.查询人员组的信息
// //根据开始时间查询出所有的排班id
// List<Long> scheduleIdList = baseMapper.selectList(Wrappers.<AttendanceArrangement>lambdaQuery().ge(AttendanceArrangement::getStartDate, bo.getCurrentDate())).stream().map(AttendanceArrangement::getId).collect(Collectors.toList());
// //根据排班的id查询出排班的人员详细信息
// List<AttendanceUserGroup> userGroupList = userGroupMapper.selectList(Wrappers.<AttendanceUserGroup>lambdaQuery().in(AttendanceUserGroup::getScheduleId, scheduleIdList));
// //将排班人员信息添加到排班信息中
// arrangementvo.setUserGroupList(userGroupList);
//
// //3.根据排班的id查询出排班的考勤组id
// List<Long> groupIds = baseMapper.selectList(Wrappers.<AttendanceArrangement>lambdaQuery().ge(AttendanceArrangement::getStartDate, bo.getCurrentDate())).stream().map(AttendanceArrangement::getGroupId).distinct().collect(Collectors.toList());
// //判断考勤组是排班制还是固定班制
// if (arrangementvo.getScheduleType().equals(StatusConstant.FIXEDSCHEDULE)) {
// //排班制
// //根据排班的id查询出排班的考勤组id
// List<AttendanceWeekSet> weekSetList = weekSetMapper.selectList(Wrappers.<AttendanceWeekSet>lambdaQuery().in(AttendanceWeekSet::getGroupId, groupIds));
// }
// //3.根据排班的id查询出排班的考勤组id
// List<Long> groupIds = baseMapper.selectList(Wrappers.<AttendanceArrangement>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<AttendanceArrangement> arrangementList = baseMapper.selectList(Wrappers.<AttendanceArrangement>lambdaQuery().ge(AttendanceArrangement::getStartDate, bo.getCurrentDate()).le(AttendanceArrangement::getEndDate, bo.getCurrentDate()));
// //根据排班查询出考勤组id
// List<Long> groupIds = arrangementList.stream().map(AttendanceArrangement::getGroupId).distinct().toList();
//
// // 2.循环将所有的考勤组id设置到考勤组中
// //循环groupIds循环和AttendanceGroup中的id做对比如果有相同的则取出attendanceGroup中的所有数据
// groupIds.forEach(
// groupId -> {
// //从数据库查询出当前考勤组的所有数据
// AttendanceGroup group = attendanceGroupMapper.selectOne(Wrappers.<AttendanceGroup>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.<AttendanceWeekSet>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.<AttendanceScheduleCycle>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.<AttendanceScheduleCycle>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<AttendanceArrangement> arrangementList = baseMapper.selectList(Wrappers.<AttendanceArrangement>lambdaQuery().ge(AttendanceArrangement::getStartDate, bo.getCalendarStartDate()).le(AttendanceArrangement::getEndDate, bo.getCalendarEndTimeDate()));
// //2.查询人员组的信息
// //根据开始时间查询排班的id
// List<Long> scheduleIdList = arrangementList.stream().map(AttendanceArrangement::getId).collect(Collectors.toList());
// //根据排班的id查询出排班的人员详细信息
// List<AttendanceUserGroup> userGroupList = userGroupMapper.selectList(Wrappers.<AttendanceUserGroup>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<Long> idList = new ArrayList<>(ids);
//根据当前排班的id 删除attendanceArrangementGroup表中的数据
arrangementGroupMapper.delete(Wrappers.<AttendanceArrangementGroup>lambdaQuery().in(AttendanceArrangementGroup::getArrangementId, idList));
//根据获取的id删除attendanceUserGroup表中的数据
userGroupMapper.delete(Wrappers.<AttendanceUserGroup>lambdaQuery().in(AttendanceUserGroup::getScheduleId, idList));
return baseMapper.deleteByIds(ids) > 0;

View File

@@ -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<ResidentPerson> 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<ResidentPerson> 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<ResidentPerson> lqw = Wrappers.lambdaQuery();
lqw.eq(ResidentPerson::getUnitId, unitId)
.eq(ResidentPerson::getUserName, name);
List<ResidentPersonVo> list = baseMapper.selectVoList(lqw);
return list.isEmpty() ? null : list.get(0);
}
}

View File

@@ -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<String, String> 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<ResidentPersonVo> 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++;

View File

@@ -130,9 +130,9 @@
</dependency>
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.2.5</version>
<groupId>com.ghgande</groupId>
<artifactId>j2mod</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>

View File

@@ -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);
// Header7字节
buffer.putShort((short) currentTransactionId); // 事务ID
buffer.putShort((short) 0); // 协议ID0=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 Header7字节
buffer.putShort((short) currentTransactionId); // 事务ID
buffer.putShort((short) 0); // 协议ID0=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();
// 读取PDU5字节
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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -61,7 +61,7 @@ public class AuthSyncTask {
/**
* 每两分钟执行一次
*/
@Scheduled(cron = "0 */2 * * * ?")
@Scheduled(cron = "0 */5 * * * ?")
public void autoAuth() {
AtomicReference<List<RemoteResidentPersonVo>> unAuthPersonRef = new AtomicReference<>(new ArrayList<>());
AtomicReference<byte[]> 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("无待授权人员");