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
Some checks are pending
Gitea Actions Demo / Explore-Gitea-Actions (push) Waiting to run
This commit is contained in:
@@ -40,7 +40,7 @@ public class InspectionTaskDetailController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* 查询巡检明细列表
|
* 查询巡检明细列表
|
||||||
*/
|
*/
|
||||||
@SaCheckPermission("system:taskDetail:list")
|
//@SaCheckPermission("system:taskDetail:list")
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
public TableDataInfo<InspectionTaskDetailVo> list(InspectionTaskDetailBo bo, PageQuery pageQuery) {
|
public TableDataInfo<InspectionTaskDetailVo> list(InspectionTaskDetailBo bo, PageQuery pageQuery) {
|
||||||
return inspectionTaskDetailService.queryPageList(bo, pageQuery);
|
return inspectionTaskDetailService.queryPageList(bo, pageQuery);
|
||||||
|
@@ -1,11 +1,17 @@
|
|||||||
package org.dromara.property.controller;
|
package org.dromara.property.controller;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import jakarta.validation.constraints.*;
|
import jakarta.validation.constraints.*;
|
||||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
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.web.bind.annotation.*;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.dromara.common.idempotent.annotation.RepeatSubmit;
|
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.domain.bo.ResidentPersonBo;
|
||||||
import org.dromara.property.service.IResidentPersonService;
|
import org.dromara.property.service.IResidentPersonService;
|
||||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
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 IResidentPersonService residentPersonService;
|
||||||
|
|
||||||
|
private final UploadFaceUtil uploadFaceUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询入驻员工列表
|
* 查询入驻员工列表
|
||||||
*/
|
*/
|
||||||
@@ -103,4 +112,39 @@ public class ResidentPersonController extends BaseController {
|
|||||||
@PathVariable("ids") Long[] ids) {
|
@PathVariable("ids") Long[] ids) {
|
||||||
return toAjax(residentPersonService.deleteWithValidByIds(List.of(ids), true));
|
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<Void> importData(@RequestPart("file") MultipartFile file, boolean updateSupport, Long unitId) throws Exception {
|
||||||
|
ExcelResult<ResidentPersonImportVo> 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<Void> importFace(@RequestPart("file") MultipartFile file, Long unitId) {
|
||||||
|
uploadFaceUtil.processFaceZip(file, unitId);
|
||||||
|
return R.ok();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
private String type;
|
||||||
/**
|
/**
|
||||||
* 性别
|
* 性别
|
||||||
|
@@ -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;
|
private String lon;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 维度
|
* 维度
|
||||||
*/
|
*/
|
||||||
@NotBlank(message = "维度不能为空", groups = {AddGroup.class, EditGroup.class})
|
//@NotBlank(message = "维度不能为空", groups = {AddGroup.class, EditGroup.class})
|
||||||
private String lat;
|
private String lat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -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;
|
||||||
|
|
||||||
|
}
|
@@ -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<ResidentPersonImportVo> implements ExcelListener<ResidentPersonImportVo> {
|
||||||
|
|
||||||
|
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<ResidentPersonVo> list = new ArrayList<>();
|
||||||
|
// 判断证件号是否为空
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
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("<br/>").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("<br/>").append(successNum).append("、账号 ").append(bo.getUserName()).append(" 更新成功");
|
||||||
|
} else {
|
||||||
|
failureNum++;
|
||||||
|
failureMsg.append("<br/>").append(failureNum).append("、账号 ").append(list.get(0).getUserName()).append(" 已存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
failureNum++;
|
||||||
|
String msg = "<br/>" + 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<ResidentPersonImportVo> 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<ResidentPersonImportVo> getList() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getErrorList() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@@ -121,10 +121,13 @@ public class ResidentPersonServiceImpl implements IResidentPersonService {
|
|||||||
// 首次入驻新用户权限组默认使用公司权限
|
// 首次入驻新用户权限组默认使用公司权限
|
||||||
ResidentUnitVo ruVo = residentUnitService.queryById(bo.getUnitId());
|
ResidentUnitVo ruVo = residentUnitService.queryById(bo.getUnitId());
|
||||||
add.setAuthGroupId(ruVo.getAuthGroupId());
|
add.setAuthGroupId(ruVo.getAuthGroupId());
|
||||||
|
add.setAuthBegDate(ruVo.getAuthBegDate());
|
||||||
|
add.setAuthEndDate(ruVo.getAuthEndDate());
|
||||||
|
|
||||||
boolean flag = baseMapper.insert(add) > 0;
|
boolean flag = baseMapper.insert(add) > 0;
|
||||||
Assert.isTrue(flag, "员工入驻失败!");
|
Assert.isTrue(flag, "员工入驻失败!");
|
||||||
if (flag) {
|
// 存在图片时,才同步授权
|
||||||
|
if (flag && add.getImg() != null) {
|
||||||
log.info("开始写入授权记录, {}", bo.getUserName());
|
log.info("开始写入授权记录, {}", bo.getUserName());
|
||||||
RemotePersonAuth personAuth = new RemotePersonAuth();
|
RemotePersonAuth personAuth = new RemotePersonAuth();
|
||||||
personAuth.setId(add.getId());
|
personAuth.setId(add.getId());
|
||||||
@@ -190,7 +193,7 @@ public class ResidentPersonServiceImpl implements IResidentPersonService {
|
|||||||
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
LambdaQueryWrapper<ResidentPerson> lqw = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<ResidentPerson> lqw = new LambdaQueryWrapper<>();
|
||||||
lqw.eq(ResidentPerson::getId, ids);
|
lqw.in(ResidentPerson::getId, ids);
|
||||||
List<ResidentPersonVo> list = baseMapper.selectVoList(lqw);
|
List<ResidentPersonVo> list = baseMapper.selectVoList(lqw);
|
||||||
|
|
||||||
boolean hasEnabled = list.stream()
|
boolean hasEnabled = list.stream()
|
||||||
|
@@ -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<String, String> 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<ResidentPersonVo> 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");
|
||||||
|
}
|
||||||
|
}
|
@@ -5,6 +5,7 @@ import org.dromara.sis.sdk.hik.service.SdkBaseServer;
|
|||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
|
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
|
* @author ruoyi
|
||||||
*/
|
*/
|
||||||
@EnableDubbo
|
@EnableDubbo
|
||||||
|
@EnableScheduling
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
public class SisApplication {
|
public class SisApplication {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
@@ -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();
|
||||||
|
// }
|
||||||
|
//}
|
@@ -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;
|
||||||
|
}
|
@@ -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;
|
||||||
|
}
|
@@ -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<AlarmRecord> {
|
||||||
|
}
|
@@ -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<AlarmTaskOperator> {
|
||||||
|
}
|
@@ -23,10 +23,7 @@ import java.io.FileOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Collection;
|
import java.util.*;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import static org.dromara.sis.sdk.hik.HCNetSDK.*;
|
import static org.dromara.sis.sdk.hik.HCNetSDK.*;
|
||||||
|
|
||||||
@@ -420,8 +417,8 @@ public class HikAlarmCallBack implements HCNetSDK.FMSGCallBack_V31 {
|
|||||||
// try {
|
// try {
|
||||||
// Thread.sleep(10000L);
|
// Thread.sleep(10000L);
|
||||||
// List<Integer> ass = Arrays.asList(3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3);
|
// List<Integer> 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++) {
|
// for (int i = 0; i < ass.size(); i++) {
|
||||||
// HikApiService.getInstance().controlGateway("192.168.24.188", (i + 1), arrs.get(i));
|
// HikApiService.getInstance().controlGateway("192.168.24.188", (i + 1), ass.get(i));
|
||||||
// }
|
// }
|
||||||
// } catch (InterruptedException e) {
|
// } catch (InterruptedException e) {
|
||||||
// throw new RuntimeException(e);
|
// throw new RuntimeException(e);
|
||||||
|
@@ -17,7 +17,7 @@ public class FinaHWPersonReq {
|
|||||||
/**
|
/**
|
||||||
* 相似度
|
* 相似度
|
||||||
*/
|
*/
|
||||||
private String similarityThreshold = "85";
|
private String similarityThreshold = "80";
|
||||||
/**
|
/**
|
||||||
* page
|
* page
|
||||||
*/
|
*/
|
||||||
|
@@ -0,0 +1,33 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 报警记录服务接口
|
||||||
|
*/
|
||||||
|
public interface AlarmRecordService extends IService<AlarmRecord> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 API 返回的 Map 数据转换为实体对象
|
||||||
|
* @param map API 返回的单个报警记录数据
|
||||||
|
* @return 转换后的实体对象
|
||||||
|
*/
|
||||||
|
AlarmRecord convertFromMap(Map<String, Object> map);
|
||||||
|
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
int saveOrUpdateRecords(List<AlarmRecord> records, Map<Long, List<Map<String, Object>>> operatorMap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存新的报警记录,并关联保存其操作记录
|
||||||
|
* @param records 报警记录列表
|
||||||
|
* @param operatorMap 操作记录映射,键为报警记录ID,值为对应操作记录列表
|
||||||
|
* @return 成功保存的记录数
|
||||||
|
*/
|
||||||
|
int saveNewRecords(List<AlarmRecord> records, Map<Long, List<Map<String, Object>>> operatorMap);
|
||||||
|
}
|
@@ -0,0 +1,34 @@
|
|||||||
|
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;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 报警任务操作记录服务接口
|
||||||
|
*/
|
||||||
|
public interface AlarmTaskOperatorService extends IService<AlarmTaskOperator> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 API 返回的 Map 数据转换为实体列表
|
||||||
|
* @param alarmRecordId 关联的报警记录ID
|
||||||
|
* @param operatorMaps API 返回的操作人数据列表
|
||||||
|
* @return 转换后的实体列表
|
||||||
|
*/
|
||||||
|
List<AlarmTaskOperator> convertFromMaps(Long alarmRecordId, List<Map<String, Object>> operatorMaps);
|
||||||
|
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
int saveOrUpdateOperators(List<AlarmTaskOperator> operators);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量保存操作记录,自动过滤已存在的记录
|
||||||
|
* @param operators 操作记录列表
|
||||||
|
* @return 成功保存的记录数
|
||||||
|
*/
|
||||||
|
int saveNewOperators(List<AlarmTaskOperator> operators);
|
||||||
|
}
|
@@ -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<String, Object> fetchAlarmRecords(int pageNum, int pageSize);
|
||||||
|
}
|
@@ -0,0 +1,265 @@
|
|||||||
|
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<AlarmRecordMapper, AlarmRecord> implements AlarmRecordService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AlarmTaskOperatorService taskOperatorService;
|
||||||
|
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
@Override
|
||||||
|
public int saveOrUpdateRecords(List<AlarmRecord> records, Map<Long, List<Map<String, Object>>> operatorMap) {
|
||||||
|
if (records == null || records.isEmpty()) return 0;
|
||||||
|
|
||||||
|
// 提取记录ID列表
|
||||||
|
List<Long> ids = records.stream().map(AlarmRecord::getId).collect(Collectors.toList());
|
||||||
|
|
||||||
|
// 查询数据库中已存在的记录
|
||||||
|
LambdaQueryWrapper<AlarmRecord> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
|
queryWrapper.in(AlarmRecord::getId, ids);
|
||||||
|
List<AlarmRecord> existingRecords = this.list(queryWrapper);
|
||||||
|
|
||||||
|
// 创建现有记录的映射,便于快速查找
|
||||||
|
Map<Long, AlarmRecord> existingRecordMap = existingRecords.stream()
|
||||||
|
.collect(Collectors.toMap(AlarmRecord::getId, record -> record));
|
||||||
|
|
||||||
|
List<AlarmRecord> newRecords = new ArrayList<>();
|
||||||
|
List<AlarmRecord> 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<AlarmRecord> records, Map<Long, List<Map<String, Object>>> operatorMap) {
|
||||||
|
if (records == null || records.isEmpty()) return 0;
|
||||||
|
|
||||||
|
// 提取待插入记录的ID列表
|
||||||
|
List<Long> ids = records.stream().map(AlarmRecord::getId).collect(Collectors.toList());
|
||||||
|
|
||||||
|
// 查询数据库中已存在的记录
|
||||||
|
LambdaQueryWrapper<AlarmRecord> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
|
queryWrapper.in(AlarmRecord::getId, ids);
|
||||||
|
List<AlarmRecord> existingRecords = this.list(queryWrapper);
|
||||||
|
List<Long> existingIds = existingRecords.stream().map(AlarmRecord::getId).collect(Collectors.toList());
|
||||||
|
|
||||||
|
// 过滤出新增记录
|
||||||
|
List<AlarmRecord> 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<AlarmRecord> newRecords, Map<Long, List<Map<String, Object>>> operatorMap) {
|
||||||
|
if (operatorMap == null || operatorMap.isEmpty()) return;
|
||||||
|
|
||||||
|
// 收集需要保存的操作记录
|
||||||
|
List<AlarmTaskOperator> allOperators = new ArrayList<>();
|
||||||
|
for (AlarmRecord record : newRecords) {
|
||||||
|
Long recordId = record.getId();
|
||||||
|
if (operatorMap.containsKey(recordId)) {
|
||||||
|
List<Map<String, Object>> operatorsData = operatorMap.get(recordId);
|
||||||
|
List<AlarmTaskOperator> operators = taskOperatorService.convertFromMaps(recordId, operatorsData);
|
||||||
|
allOperators.addAll(operators);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量保存操作记录
|
||||||
|
if (!allOperators.isEmpty()) {
|
||||||
|
taskOperatorService.saveOrUpdateOperators(allOperators);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AlarmRecord convertFromMap(Map<String, Object> 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<String, Object> map, String key) {
|
||||||
|
Object value = map.get(key);
|
||||||
|
if (value instanceof Number) {
|
||||||
|
return ((Number) value).longValue();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer getIntValue(Map<String, Object> map, String key) {
|
||||||
|
Object value = map.get(key);
|
||||||
|
if (value instanceof Number) {
|
||||||
|
return ((Number) value).intValue();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Double getDoubleValue(Map<String, Object> map, String key) {
|
||||||
|
Object value = map.get(key);
|
||||||
|
if (value instanceof Number) {
|
||||||
|
return ((Number) value).doubleValue();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private java.util.Date parseDate(Map<String, Object> 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;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,215 @@
|
|||||||
|
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<AlarmTaskOperatorMapper, AlarmTaskOperator> implements AlarmTaskOperatorService {
|
||||||
|
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
@Override
|
||||||
|
public int saveOrUpdateOperators(List<AlarmTaskOperator> operators) {
|
||||||
|
if (operators == null || operators.isEmpty()) return 0;
|
||||||
|
|
||||||
|
// 生成唯一键集合 (alarmRecordId_operatorId)
|
||||||
|
List<String> uniqueKeys = operators.stream()
|
||||||
|
.map(op -> op.getAlarmRecordId() + "_" + op.getOperatorId())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// 查询已存在的记录
|
||||||
|
LambdaQueryWrapper<AlarmTaskOperator> 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<AlarmTaskOperator> existingRecords = this.list(queryWrapper);
|
||||||
|
|
||||||
|
// 创建现有记录的映射,便于快速查找
|
||||||
|
Map<String, AlarmTaskOperator> existingRecordMap = existingRecords.stream()
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
op -> op.getAlarmRecordId() + "_" + op.getOperatorId(),
|
||||||
|
op -> op
|
||||||
|
));
|
||||||
|
|
||||||
|
List<AlarmTaskOperator> newRecords = new ArrayList<>();
|
||||||
|
List<AlarmTaskOperator> 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<AlarmTaskOperator> operators) {
|
||||||
|
if (operators == null || operators.isEmpty()) return 0;
|
||||||
|
|
||||||
|
// 生成唯一键集合 (alarmRecordId_operatorId)
|
||||||
|
List<String> uniqueKeys = operators.stream()
|
||||||
|
.map(op -> op.getAlarmRecordId() + "_" + op.getOperatorId())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// 查询已存在的记录(修正后的代码)
|
||||||
|
LambdaQueryWrapper<AlarmTaskOperator> 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<AlarmTaskOperator> existingRecords = this.list(queryWrapper);
|
||||||
|
|
||||||
|
// 生成已存在的唯一键集合
|
||||||
|
List<String> existingKeys = existingRecords.stream()
|
||||||
|
.map(op -> op.getAlarmRecordId() + "_" + op.getOperatorId())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// 过滤出新记录
|
||||||
|
List<AlarmTaskOperator> 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<AlarmTaskOperator> convertFromMaps(Long alarmRecordId, List<Map<String, Object>> operatorMaps) {
|
||||||
|
List<AlarmTaskOperator> result = new ArrayList<>();
|
||||||
|
if (operatorMaps == null || operatorMaps.isEmpty()) return result;
|
||||||
|
|
||||||
|
for (Map<String, Object> 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<String, Object> map, String key) {
|
||||||
|
Object value = map.get(key);
|
||||||
|
if (value instanceof Number) {
|
||||||
|
return ((Number) value).longValue();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer getIntValue(Map<String, Object> map, String key) {
|
||||||
|
Object value = map.get(key);
|
||||||
|
if (value instanceof Number) {
|
||||||
|
return ((Number) value).intValue();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private java.util.Date parseDate(Map<String, Object> 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;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,291 @@
|
|||||||
|
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.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.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API服务实现类,支持获取Token、刷新Token和401响应处理
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
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<String, TokenInfo> tokenCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public ApiServiceImpl(RestTemplate restTemplate) {
|
||||||
|
this.restTemplate = restTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Retryable(value = {HttpClientErrorException.Unauthorized.class}, maxAttempts = 2, backoff = @Backoff(delay = 1000))
|
||||||
|
public Map<String, Object> 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<String, Object> requestBody = buildRequestParams(pageNum, pageSize);
|
||||||
|
|
||||||
|
log.info("请求参数:{}", requestBody);
|
||||||
|
try {
|
||||||
|
// 发送带Token的请求
|
||||||
|
HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(requestBody, headers);
|
||||||
|
ResponseEntity<Map> response = restTemplate.exchange(
|
||||||
|
apiUrl,
|
||||||
|
HttpMethod.POST,
|
||||||
|
requestEntity,
|
||||||
|
Map.class
|
||||||
|
);
|
||||||
|
log.info("请求返回:{}", response);
|
||||||
|
|
||||||
|
if (response.getStatusCode() == HttpStatus.OK) {
|
||||||
|
Map<String, Object> 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 (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);
|
||||||
|
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<String, Object> body = new HashMap<>();
|
||||||
|
body.put("AppId", appId);
|
||||||
|
body.put("AppCode", appCode);
|
||||||
|
|
||||||
|
HttpEntity<Map<String, Object>> request = new HttpEntity<>(body, headers);
|
||||||
|
|
||||||
|
// 发送获取Token的请求
|
||||||
|
ResponseEntity<Map> response = restTemplate.postForEntity(
|
||||||
|
authUrl,
|
||||||
|
request,
|
||||||
|
Map.class
|
||||||
|
);
|
||||||
|
log.info("接口响应:{},状态码:{}",response,response.getStatusCode());
|
||||||
|
|
||||||
|
if (response.getStatusCode() == HttpStatus.OK) {
|
||||||
|
Map<String, Object> 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<String, Object> body = new HashMap<>();
|
||||||
|
body.put("RefreshToken", refreshToken);
|
||||||
|
|
||||||
|
HttpEntity<Map<String, Object>> request = new HttpEntity<>(body, headers);
|
||||||
|
|
||||||
|
// 发送刷新Token的请求
|
||||||
|
ResponseEntity<Map> response = restTemplate.postForEntity(
|
||||||
|
refreshUrl,
|
||||||
|
request,
|
||||||
|
Map.class
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.getStatusCode() == HttpStatus.OK) {
|
||||||
|
Map<String, Object> 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<String, Object> buildRequestParams(int pageNum, int pageSize) {
|
||||||
|
Map<String, Object> 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<String, Object> buildRequestParams(int pageNum, int pageSize) {
|
||||||
|
// Map<String, Object> requestBody = new HashMap<>();
|
||||||
|
// requestBody.put("'PageSize'", pageSize);
|
||||||
|
// requestBody.put("'PageNum'", pageNum);
|
||||||
|
//
|
||||||
|
// List<List<Map<String, Object>>> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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.context.mock.SaTokenContextMockUtil;
|
||||||
import cn.dev33.satoken.stp.StpUtil;
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
@@ -35,14 +35,14 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @author lsm
|
* @author lsm
|
||||||
* @apiNote AuthTimer
|
* @apiNote AuthSyncTask
|
||||||
* @since 2025/7/26
|
* @since 2025/7/26
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableScheduling
|
@EnableScheduling
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class AuthTimer {
|
public class AuthSyncTask {
|
||||||
|
|
||||||
@DubboReference
|
@DubboReference
|
||||||
private RemoteFileService remoteFileService;
|
private RemoteFileService remoteFileService;
|
@@ -0,0 +1,101 @@
|
|||||||
|
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;
|
||||||
|
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<String, Object> apiResult = apiService.fetchAlarmRecords(pageNum, pageSize);
|
||||||
|
|
||||||
|
if (apiResult == null) {
|
||||||
|
log.error("API 返回空结果,停止同步");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析数据
|
||||||
|
int totalNum = (int) apiResult.getOrDefault("TotalNum", 0);
|
||||||
|
List<Map<String, Object>> recordList = (List<Map<String, Object>>) apiResult.get("RecordList");
|
||||||
|
|
||||||
|
if (recordList == null || recordList.isEmpty()) {
|
||||||
|
log.info("没有更多数据可同步");
|
||||||
|
hasMoreData = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换报警记录
|
||||||
|
List<AlarmRecord> records = recordList.stream()
|
||||||
|
.map(alarmRecordService::convertFromMap)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// 提取操作记录数据
|
||||||
|
Map<Long, List<Map<String, Object>>> operatorMap = new HashMap<>();
|
||||||
|
for (Map<String, Object> recordMap : recordList) {
|
||||||
|
Long recordId = Long.valueOf((Integer)recordMap.get("Id"));
|
||||||
|
List<Map<String, Object>> operators = (List<Map<String, Object>>) recordMap.get("TaskOperators");
|
||||||
|
if (operators != null && !operators.isEmpty()) {
|
||||||
|
operatorMap.put(recordId, operators);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存数据(包含操作记录)
|
||||||
|
int savedCount = alarmRecordService.saveOrUpdateRecords(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -10,6 +10,16 @@ spring:
|
|||||||
profiles:
|
profiles:
|
||||||
# 环境配置
|
# 环境配置
|
||||||
active: @profiles.active@
|
active: @profiles.active@
|
||||||
|
# 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
|
||||||
|
|
||||||
--- # nacos 配置
|
--- # nacos 配置
|
||||||
spring:
|
spring:
|
||||||
|
@@ -40,9 +40,9 @@ spring.sql.init.platform=mysql
|
|||||||
db.num=1
|
db.num=1
|
||||||
|
|
||||||
### Connect URL of DB:
|
### 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.user.0=root
|
||||||
db.password.0=root
|
db.password.0=admin@123456
|
||||||
|
|
||||||
### the maximum retry times for push
|
### the maximum retry times for push
|
||||||
nacos.config.push.maxRetryTime=50
|
nacos.config.push.maxRetryTime=50
|
||||||
|
Reference in New Issue
Block a user