From 15b25144f0f048cddbc13a334c4d226599cfac7f Mon Sep 17 00:00:00 2001 From: mocheng <3057647414@qq.com> Date: Sun, 27 Jul 2025 22:40:39 +0800 Subject: [PATCH 1/6] SOS --- .../java/org/dromara/sis/SisApplication.java | 2 + .../org/dromara/sis/config/SOSAppConfig.java | 21 ++ .../org/dromara/sis/domain/AlarmRecord.java | 201 ++++++++++++++++++ .../dromara/sis/domain/AlarmTaskOperator.java | 99 +++++++++ .../dromara/sis/mapper/AlarmRecordMapper.java | 14 ++ .../sis/mapper/AlarmTaskOperatorMapper.java | 13 ++ .../sis/service/AlarmRecordService.java | 29 +++ .../sis/service/AlarmTaskOperatorService.java | 30 +++ .../org/dromara/sis/service/ApiService.java | 18 ++ .../service/impl/AlarmRecordServiceImpl.java | 166 +++++++++++++++ .../impl/AlarmTaskOperatorServiceImpl.java | 123 +++++++++++ .../sis/service/impl/ApiServiceImpl.java | 79 +++++++ .../org/dromara/sis/task/DataSyncTask.java | 98 +++++++++ .../Sis/src/main/resources/application.yml | 7 + 14 files changed, 900 insertions(+) create mode 100644 ruoyi-modules/Sis/src/main/java/org/dromara/sis/config/SOSAppConfig.java create mode 100644 ruoyi-modules/Sis/src/main/java/org/dromara/sis/domain/AlarmRecord.java create mode 100644 ruoyi-modules/Sis/src/main/java/org/dromara/sis/domain/AlarmTaskOperator.java create mode 100644 ruoyi-modules/Sis/src/main/java/org/dromara/sis/mapper/AlarmRecordMapper.java create mode 100644 ruoyi-modules/Sis/src/main/java/org/dromara/sis/mapper/AlarmTaskOperatorMapper.java create mode 100644 ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/AlarmRecordService.java create mode 100644 ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/AlarmTaskOperatorService.java create mode 100644 ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/ApiService.java create mode 100644 ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/AlarmRecordServiceImpl.java create mode 100644 ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/AlarmTaskOperatorServiceImpl.java create mode 100644 ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/ApiServiceImpl.java create mode 100644 ruoyi-modules/Sis/src/main/java/org/dromara/sis/task/DataSyncTask.java diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/SisApplication.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/SisApplication.java index 9d5806fa..3235c677 100644 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/SisApplication.java +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/SisApplication.java @@ -5,6 +5,7 @@ import org.dromara.sis.sdk.hik.service.SdkBaseServer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup; +import org.springframework.scheduling.annotation.EnableScheduling; /** * 物业模块 @@ -12,6 +13,7 @@ import org.springframework.boot.context.metrics.buffering.BufferingApplicationSt * @author ruoyi */ @EnableDubbo +@EnableScheduling @SpringBootApplication public class SisApplication { public static void main(String[] args) { diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/config/SOSAppConfig.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/config/SOSAppConfig.java new file mode 100644 index 00000000..94788ba2 --- /dev/null +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/config/SOSAppConfig.java @@ -0,0 +1,21 @@ +package org.dromara.sis.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +/** + * 应用配置类 + */ +@Configuration +public class SOSAppConfig { + + /** + * 配置 RestTemplate 用于 API 调用 + * @return RestTemplate 实例 + */ + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } +} diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/domain/AlarmRecord.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/domain/AlarmRecord.java new file mode 100644 index 00000000..6cb61678 --- /dev/null +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/domain/AlarmRecord.java @@ -0,0 +1,201 @@ +package org.dromara.sis.domain; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * 报警记录实体类,对应数据库表 alarm_record + * 存储系统中的报警事件信息,包括设备信息、时间信息、处理状态等 + */ +@Data +@TableName("alarm_record") +public class AlarmRecord implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 报警记录唯一标识,对应 API 返回的 Id 字段 + * 采用 INPUT 策略,使用 API 返回的实际 ID 值 + */ + @TableId(type = IdType.INPUT) + private Long id; + + /** + * 任务编码,用于标识特定的报警任务 + */ + private String taskCode; + + /** + * 设备ID,关联具体的报警设备 + */ + private Integer deviceId; + + /** + * 设备名称,如 "7楼办公室" + */ + private String deviceName; + + /** + * 设备许可证ID,用于唯一标识设备 + */ + private String deviceLicenseId; + + /** + * 设备SIP号码,用于通信 + */ + private String deviceSipNum; + + /** + * 设备所在经度 + */ + private Double deviceLng; + + /** + * 设备所在纬度 + */ + private Double deviceLat; + + /** + * 会议ID,关联报警处理过程中的会议 + */ + private Long conferenceId; + + /** + * 会议SIP编码 + */ + private String confSipCode; + + /** + * 报警状态,如 "finished"(已完成)、"noAnswer"(未接听)等 + */ + private String state; + + /** + * 报警开始时间(Java Date 类型) + */ + private Date startTime; + + /** + * 报警开始时间的 Unix 时间戳(毫秒) + */ + private Long startTimeUnix; + + /** + * 报警结束时间(Java Date 类型) + */ + private Date finishTime; + + /** + * 报警结束时间的 Unix 时间戳(毫秒) + */ + private Long finishTimeUnix; + + /** + * 响铃开始时间(Java Date 类型) + */ + private Date ringingTime; + + /** + * 响铃开始时间的 Unix 时间戳(毫秒) + */ + private Long ringingTimeUnix; + + /** + * 过期时间(Java Date 类型) + */ + private Date expireTime; + + /** + * 过期时间的 Unix 时间戳(毫秒) + */ + private Long expireTimeUnix; + + /** + * 呼叫过期时间(Java Date 类型) + */ + private Date callExpireTime; + + /** + * 呼叫过期时间的 Unix 时间戳(毫秒) + */ + private Long callExpireTimeUnix; + + /** + * 呼叫开始时间(Java Date 类型) + */ + private Date callTime; + + /** + * 呼叫开始时间的 Unix 时间戳(毫秒) + */ + private Long callTimeUnix; + + /** + * 设备是否带有摄像头(0-不带,1-带) + */ + private Integer deviceWithCamera; + + /** + * 公司编码,标识所属公司 + */ + private String companyCode; + + /** + * 报警类型,如 "button"(按钮报警) + */ + private String alarmType; + + /** + * 业务类型,如 "normal"(正常业务) + */ + private String businessType; + + /** + * 分组ID,用于对设备进行分组管理 + */ + private Integer groupId; + + /** + * 报告通知级别 + */ + private Integer reportNotifyLevel; + + /** + * 是否挂起(0-未挂起,1-挂起) + */ + private Integer isHold; + + /** + * 显示的报警类型(可能为空) + */ + private String displayAlarmType; + + /** + * 接收类型(可能为空) + */ + private String acceptType; + + /** + * 分组名称(可能为空) + */ + private String groupName; + + /** + * 设备联系人(可能为空) + */ + private String deviceLinkman; + + /** + * 设备联系电话(可能为空) + */ + private String devicePhoneNum; + + /** + * 记录创建时间,由数据库自动填充 + * 使用 MyBatis-Plus 的自动填充功能,插入时自动设置为当前时间 + */ + @TableField(fill = FieldFill.INSERT) + private Date createTime; +} diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/domain/AlarmTaskOperator.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/domain/AlarmTaskOperator.java new file mode 100644 index 00000000..388b5cd6 --- /dev/null +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/domain/AlarmTaskOperator.java @@ -0,0 +1,99 @@ +package org.dromara.sis.domain; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * 报警任务操作记录实体类,对应数据库表 alarm_record_task_operator + * 记录每个报警任务的操作人信息和处理时间 + */ +@Data +@TableName("alarm_record_task_operator") +public class AlarmTaskOperator implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 自增主键 + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 关联的报警记录ID(外键) + */ + private Long alarmRecordId; + + /** + * 操作记录ID(来自API返回) + */ + private Integer operatorId; + + /** + * 关联的报警ID + */ + private Long alarmId; + + /** + * 任务编码 + */ + private String taskCode; + + /** + * 操作人ID + */ + private Integer userId; + + /** + * 操作人类型(1-普通用户,2-管理员等,具体根据业务定义) + */ + private Integer userType; + + /** + * 操作人昵称 + */ + private String nickName; + + /** + * 操作人头像URL + */ + private String avatarUrl; + + /** + * 操作人SIP号码 + */ + private String sipNum; + + /** + * 接听时间(Java Date 类型) + */ + private Date answerTime; + + /** + * 接听时间的 Unix 时间戳(毫秒) + */ + private Long answerTimeUnix; + + /** + * 处理完成时间(Java Date 类型) + */ + private Date finishTime; + + /** + * 处理完成时间的 Unix 时间戳(毫秒) + */ + private Long finishTimeUnix; + + /** + * 转接级别(如 "levelOne"、"levelTwo" 等) + */ + private String transferLevel; + + /** + * 记录创建时间,由数据库自动填充 + */ + @TableField(fill = FieldFill.INSERT) + private Date createTime; +} diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/mapper/AlarmRecordMapper.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/mapper/AlarmRecordMapper.java new file mode 100644 index 00000000..72aa362d --- /dev/null +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/mapper/AlarmRecordMapper.java @@ -0,0 +1,14 @@ +package org.dromara.sis.mapper; + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import org.dromara.sis.domain.AlarmRecord; +import org.springframework.stereotype.Repository; + +/** + * 报警记录 Mapper 接口 + */ +@Repository +public interface AlarmRecordMapper extends BaseMapper { +} diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/mapper/AlarmTaskOperatorMapper.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/mapper/AlarmTaskOperatorMapper.java new file mode 100644 index 00000000..71758569 --- /dev/null +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/mapper/AlarmTaskOperatorMapper.java @@ -0,0 +1,13 @@ +package org.dromara.sis.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import org.dromara.sis.domain.AlarmTaskOperator; +import org.springframework.stereotype.Repository; + +/** + * 报警任务操作记录 Mapper 接口 + */ +@Repository +public interface AlarmTaskOperatorMapper extends BaseMapper { +} diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/AlarmRecordService.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/AlarmRecordService.java new file mode 100644 index 00000000..dc650a55 --- /dev/null +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/AlarmRecordService.java @@ -0,0 +1,29 @@ +package org.dromara.sis.service; + + +import com.baomidou.mybatisplus.extension.service.IService; +import org.dromara.sis.domain.AlarmRecord; + +import java.util.List; +import java.util.Map; + +/** + * 报警记录服务接口 + */ +public interface AlarmRecordService extends IService { + + /** + * 从 API 返回的 Map 数据转换为实体对象 + * @param map API 返回的单个报警记录数据 + * @return 转换后的实体对象 + */ + AlarmRecord convertFromMap(Map map); + + /** + * 保存新的报警记录,并关联保存其操作记录 + * @param records 报警记录列表 + * @param operatorMap 操作记录映射,键为报警记录ID,值为对应操作记录列表 + * @return 成功保存的记录数 + */ + int saveNewRecords(List records, Map>> operatorMap); +} diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/AlarmTaskOperatorService.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/AlarmTaskOperatorService.java new file mode 100644 index 00000000..b20d9042 --- /dev/null +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/AlarmTaskOperatorService.java @@ -0,0 +1,30 @@ +package org.dromara.sis.service; + + +import com.baomidou.mybatisplus.extension.service.IService; +import org.dromara.sis.domain.AlarmTaskOperator; + + +import java.util.List; +import java.util.Map; + +/** + * 报警任务操作记录服务接口 + */ +public interface AlarmTaskOperatorService extends IService { + + /** + * 从 API 返回的 Map 数据转换为实体列表 + * @param alarmRecordId 关联的报警记录ID + * @param operatorMaps API 返回的操作人数据列表 + * @return 转换后的实体列表 + */ + List convertFromMaps(Long alarmRecordId, List> operatorMaps); + + /** + * 批量保存操作记录,自动过滤已存在的记录 + * @param operators 操作记录列表 + * @return 成功保存的记录数 + */ + int saveNewOperators(List operators); +} diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/ApiService.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/ApiService.java new file mode 100644 index 00000000..77a44d1d --- /dev/null +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/ApiService.java @@ -0,0 +1,18 @@ +package org.dromara.sis.service; + + +import java.util.Map; + +/** + * API服务接口,定义与外部API通信的方法 + */ +public interface ApiService { + + /** + * 获取报警记录列表 + * @param pageNum 页码 + * @param pageSize 每页数量 + * @return API返回的结果数据,包含状态码和记录列表 + */ + Map fetchAlarmRecords(int pageNum, int pageSize); +} diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/AlarmRecordServiceImpl.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/AlarmRecordServiceImpl.java new file mode 100644 index 00000000..043448a7 --- /dev/null +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/AlarmRecordServiceImpl.java @@ -0,0 +1,166 @@ +package org.dromara.sis.service.impl; + + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; + +import lombok.extern.slf4j.Slf4j; +import org.dromara.sis.domain.AlarmRecord; +import org.dromara.sis.domain.AlarmTaskOperator; +import org.dromara.sis.mapper.AlarmRecordMapper; +import org.dromara.sis.service.AlarmRecordService; +import org.dromara.sis.service.AlarmTaskOperatorService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 报警记录服务实现类 + */ +@Slf4j +@Service +public class AlarmRecordServiceImpl extends ServiceImpl implements AlarmRecordService { + + @Autowired + private AlarmTaskOperatorService taskOperatorService; + + @Override + @Transactional(rollbackFor = Exception.class) + public int saveNewRecords(List records, Map>> operatorMap) { + if (records == null || records.isEmpty()) return 0; + + // 提取待插入记录的ID列表 + List ids = records.stream().map(AlarmRecord::getId).collect(Collectors.toList()); + + // 查询数据库中已存在的记录 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(AlarmRecord::getId, ids); + List existingRecords = this.list(queryWrapper); + List existingIds = existingRecords.stream().map(AlarmRecord::getId).collect(Collectors.toList()); + + // 过滤出新增记录 + List newRecords = records.stream() + .filter(record -> !existingIds.contains(record.getId())) + .collect(Collectors.toList()); + + // 批量插入新记录 + int savedCount = 0; + if (!newRecords.isEmpty()) { + if (this.saveBatch(newRecords)) { + savedCount = newRecords.size(); + + // 保存关联的操作记录 + saveRelatedOperators(newRecords, operatorMap); + } + } + return savedCount; + } + + private void saveRelatedOperators(List newRecords, Map>> operatorMap) { + if (operatorMap == null || operatorMap.isEmpty()) return; + + // 收集需要保存的操作记录 + List allOperators = new ArrayList<>(); + for (AlarmRecord record : newRecords) { + Long recordId = record.getId(); + if (operatorMap.containsKey(recordId)) { + List> operatorsData = operatorMap.get(recordId); + List operators = taskOperatorService.convertFromMaps(recordId, operatorsData); + allOperators.addAll(operators); + } + } + + // 批量保存操作记录 + if (!allOperators.isEmpty()) { + taskOperatorService.saveNewOperators(allOperators); + } + } + + @Override + public AlarmRecord convertFromMap(Map map) { + if (map == null) return null; + + AlarmRecord record = new AlarmRecord(); + record.setId(getLongValue(map, "Id")); + record.setTaskCode((String) map.get("TaskCode")); + record.setDeviceId(getIntValue(map, "DeviceId")); + record.setDeviceName((String) map.get("DeviceName")); + record.setDeviceLicenseId((String) map.get("DeviceLicenseId")); + record.setDeviceSipNum((String) map.get("DeviceSipNum")); + record.setDeviceLng(getDoubleValue(map, "DeviceLng")); + record.setDeviceLat(getDoubleValue(map, "DeviceLat")); + record.setConferenceId(getLongValue(map, "ConferenceId")); + record.setConfSipCode((String) map.get("ConfSipCode")); + record.setState((String) map.get("State")); + + // 处理时间字段 + record.setStartTime(parseDate(map, "StartTime")); + record.setStartTimeUnix(getLongValue(map, "StartTimeUnix")); + record.setFinishTime(parseDate(map, "FinishTime")); + record.setFinishTimeUnix(getLongValue(map, "FinishTimeUnix")); + record.setRingingTime(parseDate(map, "RingingTime")); + record.setRingingTimeUnix(getLongValue(map, "RingingTimeUnix")); + record.setExpireTime(parseDate(map, "ExpireTime")); + record.setExpireTimeUnix(getLongValue(map, "ExpireTimeUnix")); + record.setCallExpireTime(parseDate(map, "CallExpireTime")); + record.setCallExpireTimeUnix(getLongValue(map, "CallExpireTimeUnix")); + record.setCallTime(parseDate(map, "CallTime")); + record.setCallTimeUnix(getLongValue(map, "CallTimeUnix")); + + record.setDeviceWithCamera(getIntValue(map, "DeviceWithCamera")); + record.setCompanyCode((String) map.get("CompanyCode")); + record.setAlarmType((String) map.get("AlarmType")); + record.setBusinessType((String) map.get("BusinessType")); + record.setGroupId(getIntValue(map, "GroupId")); + record.setReportNotifyLevel(getIntValue(map, "ReportNotifyLevel")); + record.setIsHold(getIntValue(map, "IsHold")); + record.setDisplayAlarmType((String) map.get("DisplayAlarmType")); + record.setAcceptType((String) map.get("AcceptType")); + record.setGroupName((String) map.get("GroupName")); + record.setDeviceLinkman((String) map.get("DeviceLinkman")); + record.setDevicePhoneNum((String) map.get("DevicePhoneNum")); + + return record; + } + + // 类型转换辅助方法 + private Long getLongValue(Map map, String key) { + Object value = map.get(key); + if (value instanceof Number) { + return ((Number) value).longValue(); + } + return null; + } + + private Integer getIntValue(Map map, String key) { + Object value = map.get(key); + if (value instanceof Number) { + return ((Number) value).intValue(); + } + return null; + } + + private Double getDoubleValue(Map map, String key) { + Object value = map.get(key); + if (value instanceof Number) { + return ((Number) value).doubleValue(); + } + return null; + } + + private java.util.Date parseDate(Map map, String key) { + try { + String dateStr = (String) map.get(key); + if (dateStr != null && !dateStr.isEmpty() && !"1970-01-01 00:00:00".equals(dateStr)) { + java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + return sdf.parse(dateStr); + } + } catch (Exception e) { + log.error("日期转换失败: {}", e.getMessage()); + } + return null; + } +} diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/AlarmTaskOperatorServiceImpl.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/AlarmTaskOperatorServiceImpl.java new file mode 100644 index 00000000..24a5e7bb --- /dev/null +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/AlarmTaskOperatorServiceImpl.java @@ -0,0 +1,123 @@ +package org.dromara.sis.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.dromara.sis.domain.AlarmTaskOperator; +import org.dromara.sis.mapper.AlarmTaskOperatorMapper; +import org.dromara.sis.service.AlarmTaskOperatorService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 报警任务操作记录服务实现类 + */ +@Slf4j +@Service +public class AlarmTaskOperatorServiceImpl extends ServiceImpl implements AlarmTaskOperatorService { + + @Override + @Transactional(rollbackFor = Exception.class) + public int saveNewOperators(List operators) { + if (operators == null || operators.isEmpty()) return 0; + + // 生成唯一键集合 (alarmRecordId_operatorId) + List uniqueKeys = operators.stream() + .map(op -> op.getAlarmRecordId() + "_" + op.getOperatorId()) + .collect(Collectors.toList()); + + // 查询已存在的记录(修正后的代码) + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + for (String uniqueKey : uniqueKeys) { + String[] parts = uniqueKey.split("_"); + if (parts.length == 2) { + Long alarmRecordId = Long.parseLong(parts[0]); + Integer operatorId = Integer.parseInt(parts[1]); + + if (!queryWrapper.isEmptyOfWhere()) { + queryWrapper.or(); + } + queryWrapper.nested(wrapper -> + wrapper.eq(AlarmTaskOperator::getAlarmRecordId, alarmRecordId) + .eq(AlarmTaskOperator::getOperatorId, operatorId) + ); + } + } + List existingRecords = this.list(queryWrapper); + + // 生成已存在的唯一键集合 + List existingKeys = existingRecords.stream() + .map(op -> op.getAlarmRecordId() + "_" + op.getOperatorId()) + .collect(Collectors.toList()); + + // 过滤出新记录 + List newRecords = operators.stream() + .filter(op -> !existingKeys.contains(op.getAlarmRecordId() + "_" + op.getOperatorId())) + .collect(Collectors.toList()); + + // 批量插入新记录 + return newRecords.isEmpty() ? 0 : this.saveBatch(newRecords) ? newRecords.size() : 0; + } + + @Override + public List convertFromMaps(Long alarmRecordId, List> operatorMaps) { + List result = new ArrayList<>(); + if (operatorMaps == null || operatorMaps.isEmpty()) return result; + + for (Map map : operatorMaps) { + AlarmTaskOperator operator = new AlarmTaskOperator(); + operator.setAlarmRecordId(alarmRecordId); + operator.setOperatorId(getIntValue(map, "Id")); + operator.setAlarmId(getLongValue(map, "AlarmId")); + operator.setTaskCode((String) map.get("TaskCode")); + operator.setUserId(getIntValue(map, "UserId")); + operator.setUserType(getIntValue(map, "UserType")); + operator.setNickName((String) map.get("NickName")); + operator.setAvatarUrl((String) map.get("AvatarUrl")); + operator.setSipNum((String) map.get("SipNum")); + operator.setAnswerTime(parseDate(map, "AnswerTime")); + operator.setAnswerTimeUnix(getLongValue(map, "AnswerTimeUnix")); + operator.setFinishTime(parseDate(map, "FinishTime")); + operator.setFinishTimeUnix(getLongValue(map, "FinishTimeUnix")); + operator.setTransferLevel((String) map.get("TransferLevel")); + result.add(operator); + } + + return result; + } + + // 类型转换辅助方法 + private Long getLongValue(Map map, String key) { + Object value = map.get(key); + if (value instanceof Number) { + return ((Number) value).longValue(); + } + return null; + } + + private Integer getIntValue(Map map, String key) { + Object value = map.get(key); + if (value instanceof Number) { + return ((Number) value).intValue(); + } + return null; + } + + private java.util.Date parseDate(Map map, String key) { + try { + String dateStr = (String) map.get(key); + if (dateStr != null && !dateStr.isEmpty() && !"1970-01-01 00:00:00".equals(dateStr)) { + java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + return sdf.parse(dateStr); + } + } catch (Exception e) { + log.error("日期转换失败: {}", e.getMessage()); + } + return null; + } +} diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/ApiServiceImpl.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/ApiServiceImpl.java new file mode 100644 index 00000000..e16dc0ea --- /dev/null +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/ApiServiceImpl.java @@ -0,0 +1,79 @@ +package org.dromara.sis.service.impl; + +import lombok.extern.slf4j.Slf4j; +import org.dromara.sis.service.ApiService; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * API 服务实现类 + * 负责与外部 API 进行通信 + */ +@Service +@Slf4j +public class ApiServiceImpl implements ApiService { + + @Value("${api.url}") + private String apiUrl; + + private final RestTemplate restTemplate; + + public ApiServiceImpl(RestTemplate restTemplate) { + this.restTemplate = restTemplate; + } + + @Override + public Map fetchAlarmRecords(int pageNum, int pageSize) { + try { + // 设置请求头 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + // 构建请求体 + Map requestBody = new HashMap<>(); + requestBody.put("PageSize", pageSize); + requestBody.put("PageNum", pageNum); + + List>> filters = List.of( + List.of(Map.of("Key", "FinishTime", "Type", "str", "Value", "2025-07-27")) + ); + requestBody.put("Filters", filters); + + requestBody.put("OrderKey", "StartTime"); + requestBody.put("Desc", "0"); + requestBody.put("OrFirst", true); + + // 发送请求 + HttpEntity> requestEntity = new HttpEntity<>(requestBody, headers); + ResponseEntity response = restTemplate.exchange( + apiUrl, + HttpMethod.POST, + requestEntity, + Map.class + ); + + // 处理响应 + if (response.getStatusCode() == HttpStatus.OK) { + Map result = response.getBody(); + if (result != null && result.get("Status").equals(0)) { + return result; + } else { + log.error("API 返回错误状态: {}", result); + return null; + } + } else { + log.error("API 请求失败,状态码: {}", response.getStatusCode()); + return null; + } + } catch (Exception e) { + log.error("调用 API 异常: {}", e.getMessage(), e); + return null; + } + } +} diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/task/DataSyncTask.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/task/DataSyncTask.java new file mode 100644 index 00000000..a2b8b30f --- /dev/null +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/task/DataSyncTask.java @@ -0,0 +1,98 @@ +package org.dromara.sis.task; + +import lombok.extern.slf4j.Slf4j; +import org.dromara.sis.domain.AlarmRecord; +import org.dromara.sis.service.AlarmRecordService; +import org.dromara.sis.service.ApiService; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 数据同步定时任务 + * 每分钟执行一次,从 API 获取最新报警记录并同步到数据库 + */ +@Slf4j +@Component +public class DataSyncTask { + + private final ApiService apiService; + private final AlarmRecordService alarmRecordService; + + @Value("${sync.pageSize:100}") + private int pageSize; + + public DataSyncTask(ApiService apiService, AlarmRecordService alarmRecordService) { + this.apiService = apiService; + this.alarmRecordService = alarmRecordService; + } + + /** + * 定时同步报警记录数据 + */ + @Scheduled(fixedRate = 60 * 1000) // 每分钟执行一次 + public void syncAlarmRecords() { + log.info("开始同步报警记录数据..."); + + int pageNum = 1; + boolean hasMoreData = true; + int totalSynced = 0; + + try { + while (hasMoreData) { + // 调用 API 获取数据 + Map apiResult = apiService.fetchAlarmRecords(pageNum, pageSize); + + if (apiResult == null) { + log.error("API 返回空结果,停止同步"); + break; + } + + // 解析数据 + int totalNum = (int) apiResult.getOrDefault("TotalNum", 0); + List> recordList = (List>) apiResult.get("RecordList"); + + if (recordList == null || recordList.isEmpty()) { + log.info("没有更多数据可同步"); + hasMoreData = false; + break; + } + + // 转换报警记录 + List records = recordList.stream() + .map(alarmRecordService::convertFromMap) + .collect(Collectors.toList()); + + // 提取操作记录数据 + Map>> operatorMap = new HashMap<>(); + for (Map recordMap : recordList) { + Long recordId = (Long) recordMap.get("Id"); + List> operators = (List>) recordMap.get("TaskOperators"); + if (operators != null && !operators.isEmpty()) { + operatorMap.put(recordId, operators); + } + } + + // 保存数据(包含操作记录) + int savedCount = alarmRecordService.saveNewRecords(records, operatorMap); + totalSynced += savedCount; + + log.info("第 {} 页同步完成,共 {} 条记录,新增 {} 条", pageNum, recordList.size(), savedCount); + + // 判断是否还有更多数据 + if (pageNum * pageSize >= totalNum) { + hasMoreData = false; + } else { + pageNum++; + } + } + + log.info("报警记录数据同步完成,共新增 {} 条记录", totalSynced); + } catch (Exception e) { + log.error("同步报警记录数据失败: {}", e.getMessage(), e); + } + } +} diff --git a/ruoyi-modules/Sis/src/main/resources/application.yml b/ruoyi-modules/Sis/src/main/resources/application.yml index ed056a75..739a643c 100644 --- a/ruoyi-modules/Sis/src/main/resources/application.yml +++ b/ruoyi-modules/Sis/src/main/resources/application.yml @@ -10,6 +10,13 @@ spring: profiles: # 环境配置 active: @profiles.active@ +# API 配置 +api: + url: https://norsos.lionking110.com/sos/v1/mntn/business/appId/alarm/list + + # 同步配置 +sync: + pageSize: 100 --- # nacos 配置 spring: From 5274fb8d6445fe5ab6119037b361e55e69ebebac Mon Sep 17 00:00:00 2001 From: mocheng <3057647414@qq.com> Date: Mon, 28 Jul 2025 01:01:25 +0800 Subject: [PATCH 2/6] =?UTF-8?q?SOS=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/dromara/sis/config/SOSAppConfig.java | 42 +-- .../sis/service/impl/ApiServiceImpl.java | 268 ++++++++++++++++-- .../org/dromara/sis/task/DataSyncTask.java | 2 +- .../Sis/src/main/resources/application.yml | 5 +- .../src/main/resources/application.properties | 4 +- 5 files changed, 268 insertions(+), 53 deletions(-) diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/config/SOSAppConfig.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/config/SOSAppConfig.java index 94788ba2..a450d955 100644 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/config/SOSAppConfig.java +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/config/SOSAppConfig.java @@ -1,21 +1,21 @@ -package org.dromara.sis.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.client.RestTemplate; - -/** - * 应用配置类 - */ -@Configuration -public class SOSAppConfig { - - /** - * 配置 RestTemplate 用于 API 调用 - * @return RestTemplate 实例 - */ - @Bean - public RestTemplate restTemplate() { - return new RestTemplate(); - } -} +//package org.dromara.sis.config; +// +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +//import org.springframework.web.client.RestTemplate; +// +///** +// * 应用配置类 +// */ +//@Configuration +//public class SOSAppConfig { +// +// /** +// * 配置 RestTemplate 用于 API 调用 +// * @return RestTemplate 实例 +// */ +// @Bean +// public RestTemplate restTemplate() { +// return new RestTemplate(); +// } +//} diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/ApiServiceImpl.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/ApiServiceImpl.java index e16dc0ea..b49606a5 100644 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/ApiServiceImpl.java +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/ApiServiceImpl.java @@ -4,16 +4,17 @@ import lombok.extern.slf4j.Slf4j; import org.dromara.sis.service.ApiService; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.*; +import org.springframework.retry.annotation.Backoff; +import org.springframework.retry.annotation.Retryable; import org.springframework.stereotype.Service; +import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; /** - * API 服务实现类 - * 负责与外部 API 进行通信 + * API服务实现类,支持获取Token、刷新Token和401响应处理 */ @Service @Slf4j @@ -22,34 +23,43 @@ public class ApiServiceImpl implements ApiService { @Value("${api.url}") private String apiUrl; + @Value("${api.authUrl}") + private String authUrl; // 认证URL: /sos/v1/mntn/account/appId/token + + @Value("${api.refreshUrl}") + private String refreshUrl; // 刷新Token URL: /sos/v1/mntn/account/refresh/token + + @Value("${api.appId}") + private String appId; + + @Value("${api.appCode}") + private String appCode; + private final RestTemplate restTemplate; + // 缓存Token信息 + private static final Map tokenCache = new ConcurrentHashMap<>(); + public ApiServiceImpl(RestTemplate restTemplate) { this.restTemplate = restTemplate; } @Override + @Retryable(value = {HttpClientErrorException.Unauthorized.class}, maxAttempts = 2, backoff = @Backoff(delay = 1000)) public Map fetchAlarmRecords(int pageNum, int pageSize) { + String token = getAccessToken(); + + // 设置请求头,添加Token + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("token", token); // 根据接口要求,使用"Token"而非"Authorization" + + // 构建请求体 + Map requestBody = buildRequestParams(pageNum, pageSize); + + log.info("请求参数:{}", requestBody); try { - // 设置请求头 - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - - // 构建请求体 - Map requestBody = new HashMap<>(); - requestBody.put("PageSize", pageSize); - requestBody.put("PageNum", pageNum); - - List>> filters = List.of( - List.of(Map.of("Key", "FinishTime", "Type", "str", "Value", "2025-07-27")) - ); - requestBody.put("Filters", filters); - - requestBody.put("OrderKey", "StartTime"); - requestBody.put("Desc", "0"); - requestBody.put("OrFirst", true); - - // 发送请求 + // 发送带Token的请求 HttpEntity> requestEntity = new HttpEntity<>(requestBody, headers); ResponseEntity response = restTemplate.exchange( apiUrl, @@ -57,23 +67,225 @@ public class ApiServiceImpl implements ApiService { requestEntity, Map.class ); + log.info("请求返回:{}", response); - // 处理响应 if (response.getStatusCode() == HttpStatus.OK) { Map result = response.getBody(); if (result != null && result.get("Status").equals(0)) { return result; } else { - log.error("API 返回错误状态: {}", result); + log.error("API返回错误状态: {}", result); return null; } } else { - log.error("API 请求失败,状态码: {}", response.getStatusCode()); + log.error("API请求失败,状态码: {}", response.getStatusCode()); return null; } + } catch (HttpClientErrorException.Unauthorized e) { + // 捕获401错误,清除缓存的Token并重试 + log.warn("Token已过期,清除缓存并尝试重新获取: {}", e.getMessage()); + tokenCache.remove(appId); + // 重新获取Token并再次调用(由@Retryable注解处理) + throw e; } catch (Exception e) { - log.error("调用 API 异常: {}", e.getMessage(), e); + log.error("调用API异常: {}", e.getMessage(), e); return null; } } + + /** + * 获取访问Token + */ + private synchronized String getAccessToken() { + // 检查缓存中是否有有效的Token + TokenInfo tokenInfo = tokenCache.get(appId); + if (tokenInfo != null && !isTokenExpired(tokenInfo)) { + return tokenInfo.getAccessToken(); + } + + // 尝试刷新Token + if (tokenInfo != null && tokenInfo.getRefreshToken() != null) { + log.info("尝试刷新现有Token..."); + try { + String refreshedToken = refreshAccessToken(tokenInfo.getRefreshToken()); + if (refreshedToken != null) { + return refreshedToken; + } + } catch (Exception e) { + log.warn("刷新Token失败,将重新获取: {}", e.getMessage()); + } + } + + // 缓存中没有有效Token或刷新失败,重新获取 + log.info("正在获取新的访问Token..."); + try { + // 构建获取Token的请求体 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + Map body = new HashMap<>(); + body.put("AppId", appId); + body.put("AppCode", appCode); + + HttpEntity> request = new HttpEntity<>(body, headers); + + // 发送获取Token的请求 + ResponseEntity response = restTemplate.postForEntity( + authUrl, + request, + Map.class + ); + log.info("接口响应:{},状态码:{}",response,response.getStatusCode()); + + if (response.getStatusCode() == HttpStatus.OK) { + Map tokenResponse = response.getBody(); + if (tokenResponse != null && tokenResponse.get("Status").equals(0)) { + String accessToken = (String) tokenResponse.get("Token"); + String refreshToken = (String) tokenResponse.get("RefreshToken"); + Integer expireIn = (Integer) tokenResponse.get("ExpireIn"); + + // 缓存新获取的Token,设置过期时间(提前1分钟,避免临界点问题) + TokenInfo newTokenInfo = new TokenInfo( + accessToken, + refreshToken, + System.currentTimeMillis() + (expireIn - 60) * 1000 + ); + tokenCache.put(appId, newTokenInfo); + + log.info("成功获取新的访问Token,有效期: {}秒", expireIn); + return accessToken; + } + } + + log.error("获取Token失败,状态码: {}", response.getStatusCode()); + throw new RuntimeException("获取访问Token失败"); + } catch (Exception e) { + log.error("获取Token异常: {}", e.getMessage(), e); + throw new RuntimeException("获取访问Token异常", e); + } + } + + /** + * 刷新访问Token + */ + private String refreshAccessToken(String refreshToken) { + try { + // 构建刷新Token的请求体 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + Map body = new HashMap<>(); + body.put("RefreshToken", refreshToken); + + HttpEntity> request = new HttpEntity<>(body, headers); + + // 发送刷新Token的请求 + ResponseEntity response = restTemplate.postForEntity( + refreshUrl, + request, + Map.class + ); + + if (response.getStatusCode() == HttpStatus.OK) { + Map tokenResponse = response.getBody(); + if (tokenResponse != null && tokenResponse.get("Status").equals(0)) { + String accessToken = (String) tokenResponse.get("Token"); + String newRefreshToken = (String) tokenResponse.get("RefreshToken"); + Integer expireIn = (Integer) tokenResponse.get("ExpireIn"); + + // 更新缓存的Token信息 + TokenInfo tokenInfo = tokenCache.get(appId); + if (tokenInfo != null) { + tokenInfo.setAccessToken(accessToken); + tokenInfo.setRefreshToken(newRefreshToken); + tokenInfo.setExpireTime(System.currentTimeMillis() + (expireIn - 60) * 1000); + } + + log.info("成功刷新访问Token,有效期: {}秒", expireIn); + return accessToken; + } + } + + log.warn("刷新Token失败,将重新获取"); + return null; + } catch (Exception e) { + log.warn("刷新Token异常: {}", e.getMessage()); + return null; + } + } + + /** + * 检查Token是否已过期 + */ + private boolean isTokenExpired(TokenInfo tokenInfo) { + return tokenInfo.getExpireTime() < System.currentTimeMillis(); + } + + /** + * 构建请求参数 + */ + private Map buildRequestParams(int pageNum, int pageSize) { + Map requestBody = new HashMap<>(); + requestBody.put("PageSize", pageSize); + requestBody.put("PageNum", pageNum); + requestBody.put("OrFirst", true); + requestBody.put("Filters", Collections.emptyList()); // 空过滤器 + requestBody.put("OrderKey", "StartTime"); + requestBody.put("Desc", 1); // 降序排列 + + return requestBody; + } +// private Map buildRequestParams(int pageNum, int pageSize) { +// Map requestBody = new HashMap<>(); +// requestBody.put("'PageSize'", pageSize); +// requestBody.put("'PageNum'", pageNum); +// +// List>> filters = new ArrayList<>(); +// requestBody.put("'Filters'", filters); +// +// requestBody.put("'OrderKey'", "'StartTime'"); +// requestBody.put("'Desc'", "1"); +// requestBody.put("'OrFirst'", true); +// +// return requestBody; +// } + + /** + * Token信息内部类(修改为可更新的类) + */ + private static class TokenInfo { + private String accessToken; + private String refreshToken; + private long expireTime; + + public TokenInfo(String accessToken, String refreshToken, long expireTime) { + this.accessToken = accessToken; + this.refreshToken = refreshToken; + this.expireTime = expireTime; + } + + public String getAccessToken() { + return accessToken; + } + + public String getRefreshToken() { + return refreshToken; + } + + public long getExpireTime() { + return expireTime; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + public void setExpireTime(long expireTime) { + this.expireTime = expireTime; + } + } } diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/task/DataSyncTask.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/task/DataSyncTask.java index a2b8b30f..f4e725df 100644 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/task/DataSyncTask.java +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/task/DataSyncTask.java @@ -69,7 +69,7 @@ public class DataSyncTask { // 提取操作记录数据 Map>> operatorMap = new HashMap<>(); for (Map recordMap : recordList) { - Long recordId = (Long) recordMap.get("Id"); + Long recordId = Long.valueOf((Integer)recordMap.get("Id")); List> operators = (List>) recordMap.get("TaskOperators"); if (operators != null && !operators.isEmpty()) { operatorMap.put(recordId, operators); diff --git a/ruoyi-modules/Sis/src/main/resources/application.yml b/ruoyi-modules/Sis/src/main/resources/application.yml index 739a643c..0715e047 100644 --- a/ruoyi-modules/Sis/src/main/resources/application.yml +++ b/ruoyi-modules/Sis/src/main/resources/application.yml @@ -13,7 +13,10 @@ spring: # API 配置 api: url: https://norsos.lionking110.com/sos/v1/mntn/business/appId/alarm/list - + authUrl: https://norsos.lionking110.com/sos/v1/mntn/account/appId/token + refreshUrl: https://norsos.lionking110.com/sos/v1/mntn/account/refresh/token + appId: dfc7ec7507de4626b8c920c4fe1ff8b1 + appCode: fe11d05aa5704dffaa0b1c4b56ba80b2 # 同步配置 sync: pageSize: 100 diff --git a/ruoyi-visual/ruoyi-nacos/src/main/resources/application.properties b/ruoyi-visual/ruoyi-nacos/src/main/resources/application.properties index 9cc5f0dd..22c1d9f4 100644 --- a/ruoyi-visual/ruoyi-nacos/src/main/resources/application.properties +++ b/ruoyi-visual/ruoyi-nacos/src/main/resources/application.properties @@ -40,9 +40,9 @@ spring.sql.init.platform=mysql db.num=1 ### Connect URL of DB: -db.url.0=jdbc:mysql://127.0.0.1:3306/ry-config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true +db.url.0=jdbc:mysql://47.109.37.87:23306/ry-config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true db.user.0=root -db.password.0=root +db.password.0=admin@123456 ### the maximum retry times for push nacos.config.push.maxRetryTime=50 From fe7620fac4a370785dae96ff3a3359e58bccf83d Mon Sep 17 00:00:00 2001 From: yuyongle <1150359267@qq.com> Date: Mon, 28 Jul 2025 15:30:13 +0800 Subject: [PATCH 3/6] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=9B=AD=E5=8C=BA?= =?UTF-8?q?=E7=AE=A1=E7=90=86bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/dromara/property/domain/bo/TbBuildingBo.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/bo/TbBuildingBo.java b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/bo/TbBuildingBo.java index 2205ff6b..458da36d 100644 --- a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/bo/TbBuildingBo.java +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/bo/TbBuildingBo.java @@ -75,13 +75,13 @@ public class TbBuildingBo extends BaseEntity { /** * 经度 */ - @NotBlank(message = "经度不能为空", groups = {AddGroup.class, EditGroup.class}) + //@NotBlank(message = "经度不能为空", groups = {AddGroup.class, EditGroup.class}) private String lon; /** * 维度 */ - @NotBlank(message = "维度不能为空", groups = {AddGroup.class, EditGroup.class}) + //@NotBlank(message = "维度不能为空", groups = {AddGroup.class, EditGroup.class}) private String lat; /** From 95b3be486a287d408a9af08fb12e9aa15e8a1707 Mon Sep 17 00:00:00 2001 From: yuyongle <1150359267@qq.com> Date: Mon, 28 Jul 2025 17:00:27 +0800 Subject: [PATCH 4/6] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=9B=AD=E5=8C=BA?= =?UTF-8?q?=E7=AE=A1=E7=90=86bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../property/controller/InspectionTaskDetailController.java | 2 +- .../java/org/dromara/property/domain/bo/ResidentPersonBo.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ruoyi-modules/Property/src/main/java/org/dromara/property/controller/InspectionTaskDetailController.java b/ruoyi-modules/Property/src/main/java/org/dromara/property/controller/InspectionTaskDetailController.java index 78f70f8b..c080cd01 100644 --- a/ruoyi-modules/Property/src/main/java/org/dromara/property/controller/InspectionTaskDetailController.java +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/controller/InspectionTaskDetailController.java @@ -40,7 +40,7 @@ public class InspectionTaskDetailController extends BaseController { /** * 查询巡检明细列表 */ - @SaCheckPermission("system:taskDetail:list") + //@SaCheckPermission("system:taskDetail:list") @GetMapping("/list") public TableDataInfo list(InspectionTaskDetailBo bo, PageQuery pageQuery) { return inspectionTaskDetailService.queryPageList(bo, pageQuery); 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 da62b997..706a9c32 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 @@ -41,7 +41,7 @@ public class ResidentPersonBo extends BaseEntity { /** * 人员类型 */ - @NotBlank(message = "人员类型不能为空", groups = { AddGroup.class, EditGroup.class }) + //@NotBlank(message = "人员类型不能为空", groups = { AddGroup.class, EditGroup.class }) private String type; /** * 性别 From 887b23e5ca1656da4b961646390aaa5220bc2424 Mon Sep 17 00:00:00 2001 From: mocheng <3057647414@qq.com> Date: Mon, 28 Jul 2025 17:04:22 +0800 Subject: [PATCH 5/6] =?UTF-8?q?SOS=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sis/service/AlarmRecordService.java | 4 + .../sis/service/AlarmTaskOperatorService.java | 4 + .../service/impl/AlarmRecordServiceImpl.java | 101 +++++++++++++++++- .../impl/AlarmTaskOperatorServiceImpl.java | 92 ++++++++++++++++ .../org/dromara/sis/task/DataSyncTask.java | 5 +- 5 files changed, 204 insertions(+), 2 deletions(-) diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/AlarmRecordService.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/AlarmRecordService.java index dc650a55..a9a55362 100644 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/AlarmRecordService.java +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/AlarmRecordService.java @@ -3,6 +3,7 @@ package org.dromara.sis.service; import com.baomidou.mybatisplus.extension.service.IService; import org.dromara.sis.domain.AlarmRecord; +import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Map; @@ -19,6 +20,9 @@ public interface AlarmRecordService extends IService { */ AlarmRecord convertFromMap(Map map); + @Transactional(rollbackFor = Exception.class) + int saveOrUpdateRecords(List records, Map>> operatorMap); + /** * 保存新的报警记录,并关联保存其操作记录 * @param records 报警记录列表 diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/AlarmTaskOperatorService.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/AlarmTaskOperatorService.java index b20d9042..8faac488 100644 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/AlarmTaskOperatorService.java +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/AlarmTaskOperatorService.java @@ -3,6 +3,7 @@ package org.dromara.sis.service; import com.baomidou.mybatisplus.extension.service.IService; import org.dromara.sis.domain.AlarmTaskOperator; +import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -21,6 +22,9 @@ public interface AlarmTaskOperatorService extends IService { */ List convertFromMaps(Long alarmRecordId, List> operatorMaps); + @Transactional(rollbackFor = Exception.class) + int saveOrUpdateOperators(List operators); + /** * 批量保存操作记录,自动过滤已存在的记录 * @param operators 操作记录列表 diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/AlarmRecordServiceImpl.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/AlarmRecordServiceImpl.java index 043448a7..59e1a7e8 100644 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/AlarmRecordServiceImpl.java +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/service/impl/AlarmRecordServiceImpl.java @@ -27,6 +27,105 @@ public class AlarmRecordServiceImpl extends ServiceImpl records, Map>> operatorMap) { + if (records == null || records.isEmpty()) return 0; + + // 提取记录ID列表 + List ids = records.stream().map(AlarmRecord::getId).collect(Collectors.toList()); + + // 查询数据库中已存在的记录 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(AlarmRecord::getId, ids); + List existingRecords = this.list(queryWrapper); + + // 创建现有记录的映射,便于快速查找 + Map existingRecordMap = existingRecords.stream() + .collect(Collectors.toMap(AlarmRecord::getId, record -> record)); + + List newRecords = new ArrayList<>(); + List updateRecords = new ArrayList<>(); + + // 分类处理记录 + for (AlarmRecord record : records) { + if (existingRecordMap.containsKey(record.getId())) { + // 如果记录已存在,检查是否需要更新 + AlarmRecord existing = existingRecordMap.get(record.getId()); + if (hasRecordChanges(existing, record)) { + updateRecords.add(record); + } + } else { + // 新记录 + newRecords.add(record); + } + } + + int result = 0; + + // 批量插入新记录 + if (!newRecords.isEmpty()) { + if (this.saveBatch(newRecords)) { + result += newRecords.size(); + // 保存关联的操作记录 + saveRelatedOperators(newRecords, operatorMap); + } + } + + // 批量更新修改过的记录 + if (!updateRecords.isEmpty()) { + if (this.updateBatchById(updateRecords)) { + result += updateRecords.size(); + // 保存关联的操作记录 + saveRelatedOperators(updateRecords, operatorMap); + } + } + + return result; + } + + /** + * 检查两个AlarmRecord对象是否有变化 + */ + private boolean hasRecordChanges(AlarmRecord existing, AlarmRecord updated) { + return !java.util.Objects.equals(existing.getTaskCode(), updated.getTaskCode()) || + !java.util.Objects.equals(existing.getDeviceId(), updated.getDeviceId()) || + !java.util.Objects.equals(existing.getDeviceName(), updated.getDeviceName()) || + !java.util.Objects.equals(existing.getDeviceLicenseId(), updated.getDeviceLicenseId()) || + !java.util.Objects.equals(existing.getDeviceSipNum(), updated.getDeviceSipNum()) || + !java.util.Objects.equals(existing.getDeviceLng(), updated.getDeviceLng()) || + !java.util.Objects.equals(existing.getDeviceLat(), updated.getDeviceLat()) || + !java.util.Objects.equals(existing.getConferenceId(), updated.getConferenceId()) || + !java.util.Objects.equals(existing.getConfSipCode(), updated.getConfSipCode()) || + !java.util.Objects.equals(existing.getState(), updated.getState()) || + !java.util.Objects.equals(existing.getStartTime(), updated.getStartTime()) || + !java.util.Objects.equals(existing.getStartTimeUnix(), updated.getStartTimeUnix()) || + !java.util.Objects.equals(existing.getFinishTime(), updated.getFinishTime()) || + !java.util.Objects.equals(existing.getFinishTimeUnix(), updated.getFinishTimeUnix()) || + !java.util.Objects.equals(existing.getRingingTime(), updated.getRingingTime()) || + !java.util.Objects.equals(existing.getRingingTimeUnix(), updated.getRingingTimeUnix()) || + !java.util.Objects.equals(existing.getExpireTime(), updated.getExpireTime()) || + !java.util.Objects.equals(existing.getExpireTimeUnix(), updated.getExpireTimeUnix()) || + !java.util.Objects.equals(existing.getCallExpireTime(), updated.getCallExpireTime()) || + !java.util.Objects.equals(existing.getCallExpireTimeUnix(), updated.getCallExpireTimeUnix()) || + !java.util.Objects.equals(existing.getCallTime(), updated.getCallTime()) || + !java.util.Objects.equals(existing.getCallTimeUnix(), updated.getCallTimeUnix()) || + !java.util.Objects.equals(existing.getDeviceWithCamera(), updated.getDeviceWithCamera()) || + !java.util.Objects.equals(existing.getCompanyCode(), updated.getCompanyCode()) || + !java.util.Objects.equals(existing.getAlarmType(), updated.getAlarmType()) || + !java.util.Objects.equals(existing.getBusinessType(), updated.getBusinessType()) || + !java.util.Objects.equals(existing.getGroupId(), updated.getGroupId()) || + !java.util.Objects.equals(existing.getReportNotifyLevel(), updated.getReportNotifyLevel()) || + !java.util.Objects.equals(existing.getIsHold(), updated.getIsHold()) || + !java.util.Objects.equals(existing.getDisplayAlarmType(), updated.getDisplayAlarmType()) || + !java.util.Objects.equals(existing.getAcceptType(), updated.getAcceptType()) || + !java.util.Objects.equals(existing.getGroupName(), updated.getGroupName()) || + !java.util.Objects.equals(existing.getDeviceLinkman(), updated.getDeviceLinkman()) || + !java.util.Objects.equals(existing.getDevicePhoneNum(), updated.getDevicePhoneNum()); + } + + + @Override @Transactional(rollbackFor = Exception.class) public int saveNewRecords(List records, Map>> operatorMap) { @@ -75,7 +174,7 @@ public class AlarmRecordServiceImpl extends ServiceImpl implements AlarmTaskOperatorService { + @Transactional(rollbackFor = Exception.class) + @Override + public int saveOrUpdateOperators(List operators) { + if (operators == null || operators.isEmpty()) return 0; + + // 生成唯一键集合 (alarmRecordId_operatorId) + List uniqueKeys = operators.stream() + .map(op -> op.getAlarmRecordId() + "_" + op.getOperatorId()) + .collect(Collectors.toList()); + + // 查询已存在的记录 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + boolean first = true; + for (String uniqueKey : uniqueKeys) { + String[] parts = uniqueKey.split("_"); + if (parts.length == 2) { + Long alarmRecordId = Long.parseLong(parts[0]); + Integer operatorId = Integer.parseInt(parts[1]); + + if (!first) { + queryWrapper.or(); + } + queryWrapper.nested(wrapper -> + wrapper.eq(AlarmTaskOperator::getAlarmRecordId, alarmRecordId) + .eq(AlarmTaskOperator::getOperatorId, operatorId) + ); + first = false; + } + } + + List existingRecords = this.list(queryWrapper); + + // 创建现有记录的映射,便于快速查找 + Map existingRecordMap = existingRecords.stream() + .collect(Collectors.toMap( + op -> op.getAlarmRecordId() + "_" + op.getOperatorId(), + op -> op + )); + + List newRecords = new ArrayList<>(); + List updateRecords = new ArrayList<>(); + + // 分类处理记录 + for (AlarmTaskOperator operator : operators) { + String key = operator.getAlarmRecordId() + "_" + operator.getOperatorId(); + if (existingRecordMap.containsKey(key)) { + // 如果记录已存在,检查是否需要更新 + AlarmTaskOperator existing = existingRecordMap.get(key); + if (hasChanges(existing, operator)) { + // 更新ID以确保正确更新 + operator.setId(existing.getId()); + updateRecords.add(operator); + } + } else { + // 新记录 + newRecords.add(operator); + } + } + + int result = 0; + // 批量插入新记录 + if (!newRecords.isEmpty()) { + result += this.saveBatch(newRecords) ? newRecords.size() : 0; + } + + // 批量更新修改过的记录 + if (!updateRecords.isEmpty()) { + result += this.updateBatchById(updateRecords) ? updateRecords.size() : 0; + } + + return result; + } + + /** + * 检查两个AlarmTaskOperator对象是否有变化 + */ + private boolean hasChanges(AlarmTaskOperator existing, AlarmTaskOperator updated) { + return !java.util.Objects.equals(existing.getAlarmId(), updated.getAlarmId()) || + !java.util.Objects.equals(existing.getTaskCode(), updated.getTaskCode()) || + !java.util.Objects.equals(existing.getUserId(), updated.getUserId()) || + !java.util.Objects.equals(existing.getUserType(), updated.getUserType()) || + !java.util.Objects.equals(existing.getNickName(), updated.getNickName()) || + !java.util.Objects.equals(existing.getAvatarUrl(), updated.getAvatarUrl()) || + !java.util.Objects.equals(existing.getSipNum(), updated.getSipNum()) || + !java.util.Objects.equals(existing.getAnswerTime(), updated.getAnswerTime()) || + !java.util.Objects.equals(existing.getAnswerTimeUnix(), updated.getAnswerTimeUnix()) || + !java.util.Objects.equals(existing.getFinishTime(), updated.getFinishTime()) || + !java.util.Objects.equals(existing.getFinishTimeUnix(), updated.getFinishTimeUnix()) || + !java.util.Objects.equals(existing.getTransferLevel(), updated.getTransferLevel()); + } + + @Override @Transactional(rollbackFor = Exception.class) public int saveNewOperators(List operators) { diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/task/DataSyncTask.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/task/DataSyncTask.java index f4e725df..438e9c12 100644 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/task/DataSyncTask.java +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/task/DataSyncTask.java @@ -1,6 +1,9 @@ package org.dromara.sis.task; import lombok.extern.slf4j.Slf4j; +import org.apache.dubbo.config.annotation.DubboReference; +import org.dromara.property.api.RemoteFloorService; +import org.dromara.property.api.domain.vo.RemoteFloorVo; import org.dromara.sis.domain.AlarmRecord; import org.dromara.sis.service.AlarmRecordService; import org.dromara.sis.service.ApiService; @@ -77,7 +80,7 @@ public class DataSyncTask { } // 保存数据(包含操作记录) - int savedCount = alarmRecordService.saveNewRecords(records, operatorMap); + int savedCount = alarmRecordService.saveOrUpdateRecords(records, operatorMap); totalSynced += savedCount; log.info("第 {} 页同步完成,共 {} 条记录,新增 {} 条", pageNum, recordList.size(), savedCount); From e3e26f46c14fe724e2d25eb88fa8b4104a9e0e1f Mon Sep 17 00:00:00 2001 From: zcxlsm Date: Tue, 29 Jul 2025 02:04:15 +0800 Subject: [PATCH 6/6] =?UTF-8?q?feat(property):=20-=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E5=85=A5=E9=A9=BB=E5=91=98=E5=B7=A5=E5=AF=BC=E5=85=A5=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=8C=E6=94=AF=E6=8C=81=E5=AF=BC=E5=85=A5=E5=91=98?= =?UTF-8?q?=E5=B7=A5=E4=BF=A1=E6=81=AF=E5=92=8C=E4=BA=BA=E8=84=B8=E6=95=B0?= =?UTF-8?q?=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ResidentPersonController.java | 46 +++- .../domain/vo/ResidentPersonImportVo.java | 75 +++++++ .../ResidentPersonImportListener.java | 141 ++++++++++++ .../impl/ResidentPersonServiceImpl.java | 7 +- .../property/utils/UploadFaceUtil.java | 205 ++++++++++++++++++ .../sis/sdk/hik/calback/HikAlarmCallBack.java | 9 +- .../sdk/huawei/domain/FinaHWPersonReq.java | 2 +- .../AuthTimer.java => task/AuthSyncTask.java} | 6 +- 8 files changed, 478 insertions(+), 13 deletions(-) create mode 100644 ruoyi-modules/Property/src/main/java/org/dromara/property/domain/vo/ResidentPersonImportVo.java create mode 100644 ruoyi-modules/Property/src/main/java/org/dromara/property/listener/ResidentPersonImportListener.java create mode 100644 ruoyi-modules/Property/src/main/java/org/dromara/property/utils/UploadFaceUtil.java rename ruoyi-modules/Sis/src/main/java/org/dromara/sis/{config/timer/AuthTimer.java => task/AuthSyncTask.java} (99%) diff --git a/ruoyi-modules/Property/src/main/java/org/dromara/property/controller/ResidentPersonController.java b/ruoyi-modules/Property/src/main/java/org/dromara/property/controller/ResidentPersonController.java index 83c1670e..8dc6a506 100644 --- a/ruoyi-modules/Property/src/main/java/org/dromara/property/controller/ResidentPersonController.java +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/controller/ResidentPersonController.java @@ -1,11 +1,17 @@ package org.dromara.property.controller; +import java.util.ArrayList; import java.util.List; import lombok.RequiredArgsConstructor; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.constraints.*; import cn.dev33.satoken.annotation.SaCheckPermission; +import org.dromara.common.excel.core.ExcelResult; +import org.dromara.property.domain.vo.ResidentPersonImportVo; +import org.dromara.property.listener.ResidentPersonImportListener; +import org.dromara.property.utils.UploadFaceUtil; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import org.springframework.validation.annotation.Validated; import org.dromara.common.idempotent.annotation.RepeatSubmit; @@ -21,6 +27,7 @@ import org.dromara.property.domain.vo.ResidentPersonVo; import org.dromara.property.domain.bo.ResidentPersonBo; import org.dromara.property.service.IResidentPersonService; import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.springframework.web.multipart.MultipartFile; /** * 入驻员工 @@ -37,6 +44,8 @@ public class ResidentPersonController extends BaseController { private final IResidentPersonService residentPersonService; + private final UploadFaceUtil uploadFaceUtil; + /** * 查询入驻员工列表 */ @@ -65,7 +74,7 @@ public class ResidentPersonController extends BaseController { @SaCheckPermission("property:person:query") @GetMapping("/{id}") public R getInfo(@NotNull(message = "主键不能为空") - @PathVariable("id") Long id) { + @PathVariable("id") Long id) { return R.ok(residentPersonService.queryById(id)); } @@ -103,4 +112,39 @@ public class ResidentPersonController extends BaseController { @PathVariable("ids") Long[] ids) { return toAjax(residentPersonService.deleteWithValidByIds(List.of(ids), true)); } + + /** + * 导入数据 + * + * @param file 导入文件 + * @param updateSupport 是否更新已存在数据 + * @param unitId 单位id + */ + @Log(title = "入驻员工", businessType = BusinessType.IMPORT) + @SaCheckPermission("property:person:import") + @PostMapping(value = "/importData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public R importData(@RequestPart("file") MultipartFile file, boolean updateSupport, Long unitId) throws Exception { + ExcelResult result = ExcelUtil.importExcel(file.getInputStream(), ResidentPersonImportVo.class, new ResidentPersonImportListener(updateSupport, unitId)); + return R.ok(result.getAnalysis()); + } + + /** + * 获取导入模板 + */ + @PostMapping("/importTemplate") + public void importTemplate(HttpServletResponse response) { + ExcelUtil.exportExcel(new ArrayList<>(), "入驻员工", ResidentPersonImportVo.class, response); + } + + /** + * 导入人脸数据 + * + * @param file 导入文件 + * @param unitId 单位ID + */ + @PostMapping(value = "/importFace", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public R importFace(@RequestPart("file") MultipartFile file, Long unitId) { + uploadFaceUtil.processFaceZip(file, unitId); + return R.ok(); + } } diff --git a/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/vo/ResidentPersonImportVo.java b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/vo/ResidentPersonImportVo.java new file mode 100644 index 00000000..b849480e --- /dev/null +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/domain/vo/ResidentPersonImportVo.java @@ -0,0 +1,75 @@ +package org.dromara.property.domain.vo; + +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.dromara.common.excel.annotation.ExcelDictFormat; +import org.dromara.common.excel.convert.ExcelDictConvert; +import org.dromara.property.domain.ResidentPerson; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + + +/** + * 入驻员工视图对象 resident_person + * + * @author mocheng + * @since 2025-06-19 + */ +@Data +@NoArgsConstructor +public class ResidentPersonImportVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + + /** + * 用户名称 + */ + @ExcelProperty(value = "用户名称") + private String userName; + + /** + * 联系电话 + */ + @ExcelProperty(value = "联系电话") + private String phone; + + /** + * 用户性别 + */ + @ExcelProperty(value = "性别", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_user_sex") + private String gender; + + /** + * 证件号 + */ + @ExcelProperty(value = "证件号") + private String idCard; + + /** + * 邮箱 + */ + @ExcelProperty(value = "邮箱") + private String email; + + + /** + * 车牌号码 + */ + @ExcelProperty(value = "车牌号码") + private String carNumber; + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; + +} 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 new file mode 100644 index 00000000..678fcf45 --- /dev/null +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/listener/ResidentPersonImportListener.java @@ -0,0 +1,141 @@ +package org.dromara.property.listener; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.http.HtmlUtil; +import cn.idev.excel.context.AnalysisContext; +import cn.idev.excel.event.AnalysisEventListener; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.core.utils.SpringUtils; +import org.dromara.common.core.utils.StreamUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.core.utils.ValidatorUtils; +import org.dromara.common.excel.core.ExcelListener; +import org.dromara.common.excel.core.ExcelResult; +import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.property.domain.bo.ResidentPersonBo; +import org.dromara.property.domain.vo.ResidentPersonImportVo; +import org.dromara.property.domain.vo.ResidentPersonVo; +import org.dromara.property.domain.vo.ResidentUnitVo; +import org.dromara.property.service.IResidentPersonService; +import org.dromara.property.service.IResidentUnitService; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * @author lsm + * @apiNote ResidentPersonImportListener + * @since 2025/7/28 + */ +@Slf4j +public class ResidentPersonImportListener extends AnalysisEventListener implements ExcelListener { + + private final IResidentPersonService residentPersonService; + + private final IResidentUnitService residentUnitService; + + private final Boolean isUpdateSupport; + + private final Long unitId; + + private int successNum = 0; + private int failureNum = 0; + private final StringBuilder successMsg = new StringBuilder(); + private final StringBuilder failureMsg = new StringBuilder(); + + public ResidentPersonImportListener(Boolean isUpdateSupport, Long unitId) { + this.residentPersonService = SpringUtils.getBean(IResidentPersonService.class); + this.residentUnitService = SpringUtils.getBean(IResidentUnitService.class); + this.isUpdateSupport = isUpdateSupport; + this.unitId = unitId; + } + + @Override + public void invoke(ResidentPersonImportVo personVo, AnalysisContext context) { + ResidentUnitVo unitVo = residentUnitService.queryById(unitId); + List list = new ArrayList<>(); + // 判断证件号是否为空 + 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); + } + try { + if (list.isEmpty()) { // 判断当前单位是否已存在该用户 + 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.setAuthGroupId(unitVo.getAuthGroupId()); + bo.setAuthBegDate(unitVo.getAuthBegDate()); + bo.setAuthEndDate(unitVo.getAuthEndDate()); + residentPersonService.insertByBo(bo); + successNum++; + successMsg.append("
").append(successNum).append("、账号 ").append(bo.getUserName()).append(" 导入成功"); + } else if (isUpdateSupport) { + Long id = list.get(0).getUserId(); + ResidentPersonBo bo = BeanUtil.toBean(personVo, ResidentPersonBo.class); + bo.setId(id); + ValidatorUtils.validate(bo); + bo.setUpdateBy(LoginHelper.getUserId()); + residentPersonService.updateByBo(bo); + successNum++; + successMsg.append("
").append(successNum).append("、账号 ").append(bo.getUserName()).append(" 更新成功"); + } else { + failureNum++; + failureMsg.append("
").append(failureNum).append("、账号 ").append(list.get(0).getUserName()).append(" 已存在"); + } + + } catch (Exception e) { + failureNum++; + String msg = "
" + failureNum + "、账号 " + HtmlUtil.cleanHtmlTag(personVo.getUserName()) + " 导入失败:"; + String message = e.getMessage(); + if (e instanceof ConstraintViolationException cvException) { + message = StreamUtils.join(cvException.getConstraintViolations(), ConstraintViolation::getMessage, ", "); + } + failureMsg.append(msg).append(message); + } + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + + } + + @Override + public ExcelResult getExcelResult() { + return new ExcelResult<>() { + @Override + public String getAnalysis() { + if (failureNum > 0) { + failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:"); + throw new ServiceException(failureMsg.toString()); + } else { + successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:"); + } + return successMsg.toString(); + } + + @Override + public List getList() { + return null; + } + + @Override + public List getErrorList() { + return null; + } + + }; + } +} 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 db0a92ae..762ad13b 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 @@ -121,10 +121,13 @@ public class ResidentPersonServiceImpl implements IResidentPersonService { // 首次入驻新用户权限组默认使用公司权限 ResidentUnitVo ruVo = residentUnitService.queryById(bo.getUnitId()); add.setAuthGroupId(ruVo.getAuthGroupId()); + add.setAuthBegDate(ruVo.getAuthBegDate()); + add.setAuthEndDate(ruVo.getAuthEndDate()); boolean flag = baseMapper.insert(add) > 0; Assert.isTrue(flag, "员工入驻失败!"); - if (flag) { + // 存在图片时,才同步授权 + if (flag && add.getImg() != null) { log.info("开始写入授权记录, {}", bo.getUserName()); RemotePersonAuth personAuth = new RemotePersonAuth(); personAuth.setId(add.getId()); @@ -190,7 +193,7 @@ public class ResidentPersonServiceImpl implements IResidentPersonService { public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { if (isValid) { LambdaQueryWrapper lqw = new LambdaQueryWrapper<>(); - lqw.eq(ResidentPerson::getId, ids); + lqw.in(ResidentPerson::getId, ids); List list = baseMapper.selectVoList(lqw); boolean hasEnabled = list.stream() 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 new file mode 100644 index 00000000..c98d5989 --- /dev/null +++ b/ruoyi-modules/Property/src/main/java/org/dromara/property/utils/UploadFaceUtil.java @@ -0,0 +1,205 @@ +package org.dromara.property.utils; + +import cn.hutool.core.bean.BeanUtil; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.dubbo.config.annotation.DubboReference; +import org.dromara.property.domain.bo.ResidentPersonBo; +import org.dromara.property.domain.vo.ResidentPersonVo; +import org.dromara.property.service.IResidentPersonService; +import org.dromara.resource.api.RemoteFileService; +import org.dromara.resource.api.domain.RemoteFile; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** + * @author lsm + * @apiNote UploadFaceUtil + * @since 2025/7/29 + */ +@Slf4j +@Service +public class UploadFaceUtil { + + @DubboReference + private RemoteFileService remoteFileService; + + @Resource + private IResidentPersonService residentPersonService; + + // 安全配置参数(实际项目中可以从配置文件读取) + 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 Map CONTENT_TYPE_MAP = new HashMap<>(); + static { + CONTENT_TYPE_MAP.put("jpg", "image/jpeg"); + CONTENT_TYPE_MAP.put("jpeg", "image/jpeg"); + CONTENT_TYPE_MAP.put("png", "image/png"); + CONTENT_TYPE_MAP.put("gif", "image/gif"); + } + + // 统计信息 + private int totalFiles = 0; + private int processedFiles = 0; + private int successUploads = 0; + private int failedUploads = 0; + + public void processFaceZip(MultipartFile zipFile, Long unitId) { + // 重置统计信息 + resetStats(); + + try (ZipInputStream zis = new ZipInputStream(zipFile.getInputStream())) { + ZipEntry entry; + byte[] buffer = new byte[8192]; // 8KB缓冲区 + long totalExtractedSize = 0; + int fileCount = 0; + + while ((entry = zis.getNextEntry()) != null) { + // 跳过目录 + if (entry.isDirectory()) { + zis.closeEntry(); + continue; + } + + // 1. 文件数量检查 + if (++fileCount > MAX_FILE_COUNT) { + throw new SecurityException("ZIP炸弹防护:文件数量超过限制 (" + MAX_FILE_COUNT + ")"); + } + + // 2. 单个文件大小检查 + long entrySize = entry.getSize(); + if (entrySize > MAX_FILE_SIZE) { + throw new SecurityException("ZIP炸弹防护:文件 '" + entry.getName() + + "' 大小超过限制 (" + formatSize(MAX_FILE_SIZE) + ")"); + } + + // 3. 总大小检查 + if (entrySize != -1) { // 有些ZIP实现可能返回-1 + if (totalExtractedSize + entrySize > MAX_TOTAL_SIZE) { + throw new SecurityException("ZIP炸弹防护:解压总大小超过限制 (" + + formatSize(MAX_TOTAL_SIZE) + ")"); + } + totalExtractedSize += entrySize; + } + + // 4. 文件类型验证 + if (!isImageFile(entry.getName())) { + zis.closeEntry(); + continue; // 跳过非图片文件 + } + + // 获取姓名(移除文件扩展名) + String name = extractName(entry.getName()); + String contentType = getContentType(entry.getName()); + + // 读取图片数据(使用安全方式) + ByteArrayOutputStream bao = new ByteArrayOutputStream(); + int len; + long actualSize = 0; + + // 流式读取并检查实际大小 + while ((len = zis.read(buffer)) > 0) { + // 检查实际读取大小是否超过限制 + actualSize += len; + if (actualSize > MAX_FILE_SIZE) { + throw new SecurityException("ZIP炸弹防护:文件 '" + entry.getName() + + "' 实际大小超过限制 (" + formatSize(MAX_FILE_SIZE) + ")"); + } + + // 检查总大小 + if (totalExtractedSize + actualSize > MAX_TOTAL_SIZE) { + throw new SecurityException("ZIP炸弹防护:解压总大小超过限制 (" + + formatSize(MAX_TOTAL_SIZE) + ")"); + } + + bao.write(buffer, 0, len); + } + + ResidentPersonBo bo = new ResidentPersonBo(); + bo.setUnitId(unitId); + bo.setUserName(name); + List personVos = residentPersonService.queryList(bo); + // 判断当前姓名是否存在入驻单位 + if (personVos.isEmpty()) continue; + + byte[] imageData = bao.toByteArray(); + + RemoteFile remoteFile = remoteFileService.upload(name, name, contentType, imageData); + + personVos.get(0).setImg(remoteFile.getOssId().toString()); + ResidentPersonBo updateBo = BeanUtil.toBean(personVos.get(0), ResidentPersonBo.class); + residentPersonService.updateByBo(updateBo); + + totalFiles++; + + // 关闭当前entry + zis.closeEntry(); + + // 更新处理进度 + processedFiles++; + } + + // 打印统计信息 + printStatistics(); + } catch (Exception e) { + throw new RuntimeException(e); + } + + } + + private void resetStats() { + totalFiles = 0; + processedFiles = 0; + successUploads = 0; + failedUploads = 0; + } + + private void printStatistics() { + System.out.println("\n===== ZIP处理统计 ====="); + System.out.println("总文件数: " + totalFiles); + System.out.println("已处理文件数: " + processedFiles); + System.out.println("成功上传: " + successUploads); + System.out.println("失败上传: " + failedUploads); + System.out.println("======================="); + } + + private String formatSize(long bytes) { + if (bytes < 1024) return bytes + " B"; + int exp = (int) (Math.log(bytes) / Math.log(1024)); + char unit = "KMGTPE".charAt(exp - 1); + return String.format("%.1f %sB", bytes / Math.pow(1024, exp), unit); + } + + private String extractName(String fileName) { + // 移除路径和扩展名(例如 "王五.jpg" -> "王五") + String baseName = new File(fileName).getName(); + int dotIndex = baseName.lastIndexOf('.'); + return (dotIndex == -1) ? baseName : baseName.substring(0, dotIndex); + } + + private boolean isImageFile(String fileName) { + // 检查常见图片扩展名 + String[] imgExtensions = {".jpg", ".jpeg", ".png", ".gif"}; + String lowerName = fileName.toLowerCase(); + for (String ext : imgExtensions) { + if (lowerName.endsWith(ext)) return true; + } + return false; + } + + public String getContentType(String filename) { + String extension = filename.substring(filename.lastIndexOf(".") + 1).toLowerCase(); + return CONTENT_TYPE_MAP.getOrDefault(extension, "application/octet-stream"); + } +} diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/hik/calback/HikAlarmCallBack.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/hik/calback/HikAlarmCallBack.java index d5097877..8773a9fa 100644 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/hik/calback/HikAlarmCallBack.java +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/hik/calback/HikAlarmCallBack.java @@ -23,10 +23,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.text.SimpleDateFormat; -import java.util.Collection; -import java.util.Date; -import java.util.List; -import java.util.Objects; +import java.util.*; import static org.dromara.sis.sdk.hik.HCNetSDK.*; @@ -420,8 +417,8 @@ public class HikAlarmCallBack implements HCNetSDK.FMSGCallBack_V31 { // try { // Thread.sleep(10000L); // List ass = Arrays.asList(3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3); -// for (int i = 0; i < arrs.size(); i++) { -// HikApiService.getInstance().controlGateway("192.168.24.188", (i + 1), arrs.get(i)); +// for (int i = 0; i < ass.size(); i++) { +// HikApiService.getInstance().controlGateway("192.168.24.188", (i + 1), ass.get(i)); // } // } catch (InterruptedException e) { // throw new RuntimeException(e); diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/huawei/domain/FinaHWPersonReq.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/huawei/domain/FinaHWPersonReq.java index 2073291f..2b87ee75 100644 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/huawei/domain/FinaHWPersonReq.java +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/sdk/huawei/domain/FinaHWPersonReq.java @@ -17,7 +17,7 @@ public class FinaHWPersonReq { /** * 相似度 */ - private String similarityThreshold = "85"; + private String similarityThreshold = "80"; /** * page */ diff --git a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/config/timer/AuthTimer.java b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/task/AuthSyncTask.java similarity index 99% rename from ruoyi-modules/Sis/src/main/java/org/dromara/sis/config/timer/AuthTimer.java rename to ruoyi-modules/Sis/src/main/java/org/dromara/sis/task/AuthSyncTask.java index dfc80dcd..c692e72c 100644 --- a/ruoyi-modules/Sis/src/main/java/org/dromara/sis/config/timer/AuthTimer.java +++ b/ruoyi-modules/Sis/src/main/java/org/dromara/sis/task/AuthSyncTask.java @@ -1,4 +1,4 @@ -package org.dromara.sis.config.timer; +package org.dromara.sis.task; import cn.dev33.satoken.context.mock.SaTokenContextMockUtil; import cn.dev33.satoken.stp.StpUtil; @@ -35,14 +35,14 @@ import java.util.concurrent.atomic.AtomicReference; /** * @author lsm - * @apiNote AuthTimer + * @apiNote AuthSyncTask * @since 2025/7/26 */ @Slf4j @Configuration @EnableScheduling @RequiredArgsConstructor -public class AuthTimer { +public class AuthSyncTask { @DubboReference private RemoteFileService remoteFileService;