Browse Source

feat:除开初核情况、其余信息同步

feature/tsjb-1.0
parent
commit
1ceb785c53
  1. 17
      src/main/java/com/biutag/supervision/controller/data/ComplaintCollectionController.java
  2. 49
      src/main/java/com/biutag/supervision/job/MailBoxCaptureJob.java
  3. 29
      src/main/java/com/biutag/supervision/pojo/dto/MailBoxSyncDto.java
  4. 361
      src/main/java/com/biutag/supervision/pojo/dto/NegativeDataOnlyDto.java
  5. 8
      src/main/java/com/biutag/supervision/pojo/entity/mailbox/MailBlame.java
  6. 5
      src/main/java/com/biutag/supervision/pojo/param/MailQueryParam.java
  7. 1
      src/main/java/com/biutag/supervision/repository/mail/MailResourceService.java
  8. 723
      src/main/java/com/biutag/supervision/service/MailBoxCaptureService.java
  9. 134
      src/main/java/com/biutag/supervision/service/NegativeService.java

17
src/main/java/com/biutag/supervision/controller/data/ComplaintCollectionController.java

@ -8,6 +8,7 @@ import com.biutag.supervision.pojo.request.complaintCollection.*;
import com.biutag.supervision.pojo.vo.complaintCollection.ComplaintCollectionDetailVo;
import com.biutag.supervision.pojo.vo.complaintCollection.ComplaintCollectionMailRepeattVo;
import com.biutag.supervision.pojo.vo.complaintCollection.ComplaintCollectionWatchDetailVO;
import com.biutag.supervision.service.MailBoxCaptureService;
import com.biutag.supervision.service.complaintCollection.ComplaintCollectionService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@ -19,6 +20,8 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
/**
* @ClassName ComplaintCollectionController
* @Description 涉访涉诉控制层
@ -36,6 +39,8 @@ public class ComplaintCollectionController {
private final Job job;
private final MailBoxCaptureService mailBoxCaptureService;
@Operation(description = "添加")
@PostMapping("/addComplaintCollection")
public Result<Boolean> addComplaintCollection(@RequestBody ComplaintCollectionAddRequest request){
@ -64,6 +69,18 @@ public class ComplaintCollectionController {
if ("我要下发".equals(request.getPersonInfo())){
complaintCollectionService.addNegativeTemp(request);
}
if ("阶段1".equals(request.getPersonInfo())){
LocalDateTime start = request.getCreateTimeList().get(0);
LocalDateTime end = request.getCreateTimeList().get(1);
mailBoxCaptureService.captureMayorMailbox(start, end);
}
if ("阶段2".equals(request.getPersonInfo())){
LocalDateTime start = request.getCreateTimeList().get(0);
LocalDateTime end = request.getCreateTimeList().get(1);
mailBoxCaptureService.syncBlameAndFiles(start, end);
}
return Result.success(complaintCollectionService.getComplaintCollectionPageNew(request));
}

49
src/main/java/com/biutag/supervision/job/MailBoxCaptureJob.java

@ -0,0 +1,49 @@
package com.biutag.supervision.job;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.time.LocalDateTime;
import com.biutag.supervision.service.MailBoxCaptureService;
/**
* @ClassName MailBoxCaptureJob
* @Description 局长信箱数据抓取定时任务
* - 阶段1每天2点抓取来信创建 Negative + ComplaintCollection
* - 阶段2每天3点同步涉及人和核查附件
* @Author shihao
* @Date 2026/4/28
*/
@Slf4j
@RequiredArgsConstructor
@Component
public class MailBoxCaptureJob {
private final MailBoxCaptureService mailBoxCaptureService;
/**
* 阶段1抓取来信
* 每天凌晨2点执行
*/
@Scheduled(cron = "0 0 2 * * ?")
public void mailBoxCaptureCompletedToNegative() {
LocalDateTime start = LocalDate.now().minusDays(1).atStartOfDay();
LocalDateTime end = LocalDate.now().atStartOfDay();
mailBoxCaptureService.captureMayorMailbox(start, end);
}
/**
* 阶段2同步涉及人和核查附件
* 每天凌晨3点执行
*/
@Scheduled(cron = "0 0 3 * * ?")
public void syncBlameAndFilesForCompletedMails() {
LocalDateTime start = LocalDate.now().minusDays(1).atStartOfDay();
LocalDateTime end = LocalDate.now().atStartOfDay();
mailBoxCaptureService.syncBlameAndFiles(start, end);
}
}

29
src/main/java/com/biutag/supervision/pojo/dto/MailBoxSyncDto.java

@ -0,0 +1,29 @@
package com.biutag.supervision.pojo.dto;
import com.biutag.supervision.pojo.entity.ComplaintCollection;
import com.biutag.supervision.pojo.entity.mailbox.Mail;
import com.biutag.supervision.pojo.entity.mailbox.MailBlame;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
/**
* 局长信箱阶段2同步数据DTO
* 用于在循环内传递同步所需的数据
*/
@Getter
@Setter
@Schema(description = "局长信箱阶段2同步数据DTO")
public class MailBoxSyncDto {
@Schema(description = "投诉举报收集表记录")
private ComplaintCollection cc;
@Schema(description = "局长信箱邮件数据")
private Mail mail;
@Schema(description = "涉及人列表")
private List<MailBlame> mailBlames;
}

361
src/main/java/com/biutag/supervision/pojo/dto/NegativeDataOnlyDto.java

@ -0,0 +1,361 @@
package com.biutag.supervision.pojo.dto;
import com.biutag.supervision.constants.enums.ProblemSourcesEnum;
import com.biutag.supervision.pojo.dto.flow.VerifyData;
import com.biutag.supervision.pojo.entity.NegativeBlame;
import com.biutag.supervision.pojo.entity.NegativeFile;
import com.biutag.supervision.pojo.entity.NegativeThingFile;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
/**
* Negative纯数据保存DTO - 所有字段通过传参不写死任何值
*/
@Setter
@Getter
@Schema(description = "Negative纯数据保存DTO")
public class NegativeDataOnlyDto {
// ========== Negative主表所有字段 ==========
@Schema(description = "主键(可传可不传,不传则自动生成)")
private String id;
@Schema(description = "样本来源编号")
private String originId;
@Schema(description = "编号")
private String serialNumber;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
@Schema(description = "问题发生时间")
private LocalDateTime happenTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
@Schema(description = "问题发现时间")
private LocalDateTime discoveryTime;
/**
* @see ProblemSourcesEnum
*/
@Schema(description = "问题来源code")
private String problemSourcesCode;
/**
* @see ProblemSourcesEnum
*/
@Schema(description = "问题来源")
private String problemSources;
@Schema(description = "业务类型code")
private String businessTypeCode;
@Schema(description = "业务类别名称")
private String businessTypeName;
@Schema(description = "涉嫌问题(列表,会转成逗号分隔字符串)")
private List<String> involveProblem = new ArrayList<>();
@Schema(description = "涉及警种名称")
private String policeTypeName;
@Schema(description = "涉及警种")
private String policeType;
@Schema(description = "涉及单位名称")
private String involveDepartName;
@Schema(description = "涉及单位id")
private String involveDepartId;
@Schema(description = "联系电话")
private String contactPhone;
@Schema(description = "反映人姓名")
private String responderName;
@Schema(description = "反映人身份证")
private String responderIdCard;
@Schema(description = "简要描述")
private String thingDesc;
@Schema(description = "填写人姓名")
private String fillName;
@Schema(description = "填写人部门id")
private String fillDepartId;
@Schema(description = "填写人部门名称")
private String fillDeaprtName;
@Schema(description = "审核人姓名")
private String checkName;
@Schema(description = "审核人身份证")
private String checkIdCode;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
@Schema(description = "创建时间(不传则自动填充)")
private LocalDateTime crtTime;
@Schema(description = "更新人姓名")
private String updName;
@Schema(description = "更新人")
private String updUser;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
@Schema(description = "更新时间(不传则自动填充)")
private LocalDateTime updTime;
@Schema(description = "状态")
private String status;
@Schema(description = "备注")
private String remark;
@Schema(description = "预留字段")
private String reserve;
@Schema(description = "核查情况")
private String checkStatus;
@Schema(description = "核查情况名称")
private String checkStatusName;
@Schema(description = "核查结论code")
private String checkStatusCode;
@Schema(description = "完善状态")
private String completeStatus;
@Schema(description = "最后编辑人员")
private String lastEditName;
@Schema(description = "任务ID")
private String taskId;
@Schema(description = "督察主题code")
private String supervisionSubjectCode;
@Schema(description = "督察主题名称")
private String supervisionSubjectName;
@Schema(description = "是否整改code")
private String isRectifyCode;
@Schema(description = "是否整改名称")
private String isRectifyName;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
@Schema(description = "办结时间")
private LocalDateTime completeDate;
@Schema(description = "督察报告id")
private String reportId;
@Schema(description = "流程key")
private String flowKey;
@Schema(description = "主办层级")
private String hostLevel;
@Schema(description = "办理时限")
private String timeLimit;
@Schema(description = "最大签收时长(天)")
private Integer maxSignDuration;
@Schema(description = "最大办理时长(天)")
private Integer maxHandleDuration;
@Schema(description = "最大延期时长(天)")
private Integer maxExtensionDuration;
@Schema(description = "审批流程")
private String approvalFlow;
@Schema(description = "核查情况描述")
private String checkStatusDesc;
@Schema(description = "整改情况描述")
private String rectifyDesc;
@Schema(description = "整改限制天数")
private Integer rectifyRestrictionDays;
@Schema(description = "追责对象")
private String accountabilityTarget;
@Schema(description = "办理状态")
private String processingStatus;
@Schema(description = "延期申请ID")
private Integer negativeExtensionApplyId;
@Schema(description = "是否能申请延期")
private Boolean extensionApplyFlag;
@Schema(description = "延期天数")
private Integer extensionDays;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
@Schema(description = "市局下发时间")
private LocalDateTime firstDistributeTime;
@Schema(description = "是否二级机构办理")
private Boolean isSecondHandle;
@Schema(description = "二级办理单位id")
private String handleSecondDepartId;
@Schema(description = "二级办理单位名称")
private String handleSecondDepartName;
@Schema(description = "三级办理单位id")
private String handleThreeDepartId;
@Schema(description = "三级办理单位名称")
private String handleThreeDepartName;
@Schema(description = "涉及案件/警情编号")
private String caseNumber;
@Schema(description = "办理超时(秒)")
private Long handleTimeout;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
@Schema(description = "申请办结时间")
private LocalDateTime handleTime;
@Schema(description = "当前处理对象")
private String currentProcessingObject;
@Schema(description = "专项督察")
private String specialSupervision;
@Schema(description = "通报期数")
private String reportNumber;
@Schema(description = "核查办理情况")
private String verifySituation;
@Schema(description = "佐证材料情况")
private String verifyFileSituation;
@Schema(description = "单位会签ID")
private Integer countersignApplyId;
@Schema(description = "创建单位层级")
private Integer crtDepartLevel;
@Schema(description = "市局下发意见")
private String firstDistributeComments;
@Schema(description = "是否抽检")
private Boolean spotCheckFlag;
@Schema(description = "抽检结果")
private String spotCheckResult;
@Schema(description = "抽检情况")
private String spotCheckDesc;
@Schema(description = "未整改原因")
private String unrectifyReason;
@Schema(description = "二级部门id")
private String secondInvolveDepartId;
@Schema(description = "三级部门id")
private String threeInvolveDepartId;
@Schema(description = "经办人")
private String handlePolices;
@Schema(description = "剩余办理时间")
private Long handleRemainingTime;
@Schema(description = "样本ID")
private Integer sampleId;
@Schema(description = "下发问题(JSON)")
private List<VerifyData.Problem> problems;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
@Schema(description = "核查时间")
private LocalDateTime verifyTime;
@Schema(description = "项目名称")
private String projectName;
@Schema(description = "涉及问题金额")
private Double involveMoney;
@Schema(description = "化解情况")
private String resolveSituation;
@Schema(description = "当前状态")
private String resolveStatus;
@Schema(description = "接访领导姓名")
private String visitingLeaderName;
@Schema(description = "接访领导工号")
private String visitingLeaderEmpNo;
@Schema(description = "12337办理结果")
private String handleResult12337;
@Schema(description = "12337办理结果分组")
private String handleResult12337Group;
@Schema(description = "涉及人员是否领导班子")
private String verifiedIsLeader;
@Schema(description = "办理结果")
private String processResult;
@Schema(description = "处分处理情况")
private String disciplinaryActionDesc;
@Schema(description = "来源类型")
private String sourceType;
@Schema(description = "来源类型名称")
private String sourceTypeDesc;
@Schema(description = "下发单位id")
private String issuingDepartId;
@Schema(description = "下发单位名称")
private String issuingDepartName;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
@Schema(description = "最后一次审批节点操作时间")
private LocalDateTime latestProcessTime;
@Schema(description = "二级审批累计时长(秒)")
private Long secondApprovalTime;
@Schema(description = "市局审批累计时长(秒)")
private Long firstApproveTime;
// ========== 关联表 ==========
@Schema(description = "涉及人员列表")
private List<NegativeBlame> blames = new ArrayList<>();
@Schema(description = "事件附件列表")
private List<NegativeThingFile> thingFiles = new ArrayList<>();
@Schema(description = "核查附件列表")
private List<NegativeFile> files = new ArrayList<>();
}

8
src/main/java/com/biutag/supervision/pojo/entity/mailbox/MailBlame.java

@ -43,4 +43,12 @@ public class MailBlame {
* 创建时间
*/
private LocalDateTime createTime;
private String leaderEmpNo;
private String leaderName;
private String leaderIdCode;
}

5
src/main/java/com/biutag/supervision/pojo/param/MailQueryParam.java

@ -73,4 +73,9 @@ public class MailQueryParam extends BasePage{
@Schema(description = "身份证ids")
private Set<String> contactIdCards;
/**
* 信件状态
*/
private String mailState;
}

1
src/main/java/com/biutag/supervision/repository/mail/MailResourceService.java

@ -39,6 +39,7 @@ public class MailResourceService extends BaseDAO {
queryWrapper.eq(StrUtil.isNotBlank(param.getContactName()), Mail::getContactName, param.getContactName());
queryWrapper.eq(StrUtil.isNotBlank(param.getContactPhone()), Mail::getContactPhone, param.getContactPhone());
queryWrapper.eq(StrUtil.isNotBlank(param.getContent()), Mail::getContent, param.getContent());
queryWrapper.eq(StrUtil.isNotBlank(param.getMailState()), Mail::getMailState, param.getMailState());
if (!param.getMailTime().isEmpty() && param.getMailTime().size() >= 2) {
queryWrapper.between(Mail::getMailTime, param.getMailTime().get(0), param.getMailTime().get(1));
}

723
src/main/java/com/biutag/supervision/service/MailBoxCaptureService.java

@ -0,0 +1,723 @@
package com.biutag.supervision.service;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.biutag.supervision.constants.enums.AccountabilityTargetEnum;
import com.biutag.supervision.constants.enums.BusinessTypeEnum;
import com.biutag.supervision.constants.enums.ProblemSourcesEnum;
import com.biutag.supervision.mapper.MailBlameMapper;
import com.biutag.supervision.pojo.dto.MailBoxSyncDto;
import com.biutag.supervision.pojo.dto.NegativeDataOnlyDto;
import com.biutag.supervision.pojo.dto.mail.MailAttachmentDTO;
import com.biutag.supervision.pojo.entity.*;
import com.biutag.supervision.pojo.entity.mailbox.Mail;
import com.biutag.supervision.pojo.entity.mailbox.MailBlame;
import com.biutag.supervision.pojo.enums.complaintCollection.ComplaintCollectionSourceTableEnum;
import com.biutag.supervision.pojo.enums.negative.NegativeSourceTypeEnum;
import com.biutag.supervision.pojo.param.ComplaintCollection.ComplaintCollectionQueryParam;
import com.biutag.supervision.pojo.param.ComplaintCollection.ComplaintCollectionUpdateParam;
import com.biutag.supervision.pojo.param.MailQueryParam;
import com.biutag.supervision.pojo.param.SupDepartQueryParam;
import com.biutag.supervision.pojo.param.SupExternalDepartQueryParam;
import com.biutag.supervision.repository.complaintCollection.ComplaintCollectionResourceService;
import com.biutag.supervision.repository.mail.MailResourceService;
import com.biutag.supervision.repository.supExternalDepart.SupExternalDepartResourceService;
import com.biutag.supervision.repository.supdepart.SupDepartResourceService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* @ClassName MailBoxCaptureService
* @Description 局长信箱数据抓取业务逻辑
* - 阶段1抓取来信创建 Negative 主表 + 来信附件 + ComplaintCollection
* - 阶段2同步办结后的涉及人和核查附件
* @Author shihao
* @Date 2026/4/28
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class MailBoxCaptureService {
private final NegativeService negativeService;
private final NegativeBlameService blameService;
private final NegativeProblemRelationService negativeProblemRelationService;
private final NegativeFileService fileService;
private final MailService mailService;
private final MailResourceService mailResourceService;
private final SupDepartResourceService supDepartResourceService;
private final SupExternalDepartResourceService supExternalDepartResourceService;
private final ComplaintCollectionResourceService complaintCollectionResourceService;
private final MailBlameMapper mailBlameMapper;
private final SupPoliceService supPoliceService;
private final SupDictHandleResultMapingService dictHandleResultMapingService;
private final SupDictProblemTypeMapingService dictProblemTypeMapingService;
private final SupDictProblemTypeService problemTypeService;
private final TransactionTemplate transactionTemplate;
// ==================== 阶段1:抓取来信 ====================
/**
* 抓取来信并创建 ComplaintCollection Negative
*/
public void captureMayorMailbox(LocalDateTime start, LocalDateTime end) {
log.info("【局长信箱抓取新信件】时间范围 {} ~ {}", start, end);
long startTimeMillis = System.currentTimeMillis();
try {
// 1. 查询来信的信件
List<Mail> mailList = queryNewMails(start, end);
if (CollectionUtil.isEmpty(mailList)) {
log.warn("【局长信箱已办结抓取】未查询到任何投诉举报信件数据,任务结束");
return;
}
log.info("【局长信箱抓取新信件】查询到投诉举报信件信件数量:{}", mailList.size());
// 3. 遍历处理
int successCount = 0;
int skipCount = 0;
int failCount = 0;
for (Mail mail : mailList) {
try {
// 每条记录在独立事务中执行
boolean success = Boolean.TRUE.equals(transactionTemplate.execute(status -> {
return doCaptureSingleMail(mail);
}));
if (success) {
successCount++;
log.debug("【局长信箱抓取新信件】处理成功: mailId={}", mail.getId());
} else {
skipCount++;
log.debug("【局长信箱抓取新信件】处理跳过: mailId={}", mail.getId());
}
} catch (Exception e) {
failCount++;
log.error("【局长信箱抓取新信件】处理失败: mailId={}, error={}", mail.getId(), e.getMessage(), e);
}
}
// 4. 输出统计
long cost = System.currentTimeMillis() - startTimeMillis;
log.info("【局长信箱抓取新信件】完成,总数:{},成功:{},跳过:{},失败:{},耗时:{}ms",
mailList.size(), successCount, skipCount, failCount, cost);
} catch (Exception e) {
log.error("【局长信箱抓取新信件】任务执行过程中发生严重异常,任务中断", e);
throw e;
}
}
// ==================== 阶段2:同步涉及人、核查附件、核查情况 ====================
/**
* 同步办结后的涉及人和核查附件
*/
public void syncBlameAndFiles(LocalDateTime start, LocalDateTime end) {
log.info("【阶段2同步】时间范围 {} ~ {}", start, end);
long startTimeMillis = System.currentTimeMillis();
try {
// 1. 从 ComplaintCollection 查询局长信箱记录
List<ComplaintCollection> completedList = queryCompletedMailbox();
if (CollectionUtil.isEmpty(completedList)) {
log.warn("【阶段2同步】未查询到任何需要同步的数据,任务结束");
return;
}
log.info("【阶段2同步】查询到需要同步数量:{}", completedList.size());
// 2. 遍历处理(每条记录独立事务)
int successCount = 0;
int skipCount = 0;
int failCount = 0;
for (ComplaintCollection cc : completedList) {
try {
String originId = cc.getOriginId();
// 查询信件
MailQueryParam param = new MailQueryParam();
param.setId(originId);
param.setMailState("completion");
List<Mail> mailList = mailResourceService.query(param);
if (CollectionUtil.isEmpty(mailList)) {
log.info("【阶段2同步】无对应办结Mail:originId=" + originId);
continue;
}
Mail mail = mailList.get(0);
// 查询涉及人
LambdaQueryWrapper<MailBlame> mailBlameLambdaQueryWrapper = new LambdaQueryWrapper<>();
mailBlameLambdaQueryWrapper.eq(MailBlame::getMailId, mail.getId());
List<MailBlame> mailBlames = mailBlameMapper.selectList(mailBlameLambdaQueryWrapper);
// 构建 DTO
MailBoxSyncDto dto = new MailBoxSyncDto();
dto.setCc(cc);
dto.setMail(mail);
dto.setMailBlames(mailBlames);
// 每条记录在独立事务中执行
boolean success = Boolean.TRUE.equals(transactionTemplate.execute(status -> {
return doSyncSingleRecord(dto);
}));
if (success) {
successCount++;
log.debug("【阶段2同步】处理成功: ccId={}", cc.getId());
} else {
skipCount++;
log.debug("【阶段2同步】处理跳过: ccId={}", cc.getId());
}
} catch (Exception e) {
failCount++;
log.error("【阶段2同步】处理失败: ccId={}, error={}", cc.getId(), e.getMessage(), e);
}
}
// 3. 输出统计
long cost = System.currentTimeMillis() - startTimeMillis;
log.info("【阶段2同步】完成,总数:{},成功:{},跳过:{},失败:{},耗时:{}ms",
completedList.size(), successCount, skipCount, failCount, cost);
} catch (Exception e) {
log.error("【阶段2同步】任务执行过程中发生严重异常,任务中断", e);
throw e;
}
}
// ==================== 查询相关 ====================
/**
* ComplaintCollection 查询局长信箱记录
*/
private List<ComplaintCollection> queryCompletedMailbox() {
ComplaintCollectionQueryParam param = new ComplaintCollectionQueryParam();
param.setSourceTable(ComplaintCollectionSourceTableEnum.MAYOR_MAILBOX.getCode());
param.setBlameSyncStatus("0"); // 未同步
return complaintCollectionResourceService.query(param);
}
/**
* 查询来信时间范围内的信件按来信时间查询
*/
private List<Mail> queryNewMails(LocalDateTime start, LocalDateTime end) {
LambdaQueryWrapper<Mail> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(Mail::getMailLevel, "涉警投诉");
queryWrapper.between(Mail::getMailTime, start, end);
return mailService.list(queryWrapper);
}
/**
* 判重检查检查是否已处理
*/
private boolean isAlreadyProcessed(Mail mail) {
boolean exists = negativeService.exists(mail.getId());
if (exists) {
log.debug("【局长信箱已办结抓取】跳过(已处理): mailId={}", mail.getId());
}
return exists;
}
// ==================== 单位查询 ====================
/**
* 根据外部单位ID查询单位实体外部ID -> 内部ID -> 部门实体
*
* @return 部门实体找不到时返回 null由调用方决定如何处理
*/
private SupDepart getDepartByExternalId(Integer externalId) {
if (externalId == null) {
log.warn("【局长信箱已办结抓取】外部单位ID为空");
return null;
}
// 1. 通过外部ID查询映射表,获取内部ID
SupExternalDepart externalDepart = findExternalDepart(externalId);
if (externalDepart == null) {
log.warn("【局长信箱已办结抓取】未找到SupExternalDepart映射,externalId={}", externalId);
return null;
}
String internalId = externalDepart.getInternalId();
if (StrUtil.isBlank(internalId)) {
log.warn("【局长信箱已办结抓取】SupExternalDepart.internalId为空,externalId={}", externalId);
return null;
}
// 2. 通过内部ID查询部门实体
SupDepart depart = findDepartById(internalId);
if (depart == null) {
log.warn("【局长信箱已办结抓取】未找到单位,internalId={}", internalId);
return null;
}
return depart;
}
/**
* 根据外部ID查询单位映射
*
* @return 单位映射找不到时返回 null
*/
private SupExternalDepart findExternalDepart(Integer externalId) {
SupExternalDepartQueryParam queryParam = new SupExternalDepartQueryParam();
queryParam.setSource("局长信箱");
queryParam.setExternalIds(Collections.singleton(String.valueOf(externalId)));
List<SupExternalDepart> list = supExternalDepartResourceService.query(queryParam);
if (CollectionUtil.isEmpty(list)) {
return null;
}
return list.get(0);
}
/**
* 根据内部ID查询部门
*/
private SupDepart findDepartById(String id) {
SupDepartQueryParam queryParam = new SupDepartQueryParam();
queryParam.setId(id);
List<SupDepart> list = supDepartResourceService.query(queryParam);
if (CollectionUtil.isEmpty(list)) {
return null;
}
return list.get(0);
}
// ==================== 阶段1 抓取方法 ====================
/**
* 单条记录的抓取逻辑独立事务执行
*/
public boolean doCaptureSingleMail(Mail mail) {
// 1. 判重检查
if (isAlreadyProcessed(mail)) {
return false;
}
// 2. 查询单位信息
SupDepart secondDepart = getDepartByExternalId(mail.getSecondDeptId());
SupDepart thirdDepart = getDepartByExternalId(mail.getThreeDeptId());
// 2.1 如果单位信息获取失败,抛出异常触发回滚
if (secondDepart == null || thirdDepart == null) {
throw new RuntimeException("单位映射缺失: mailId=" + mail.getId());
}
// 3. 构建并保存 Negative(阶段1:主表 + 来信附件)
NegativeDataOnlyDto dto = buildNegativeDto(mail, secondDepart, thirdDepart);
Negative negative = negativeService.saveNegativeMain(dto);
negativeService.saveThingFiles(dto, negative.getId());
// 4. 保存 ComplaintCollection
ComplaintCollection cc = buildComplaintCollection(mail, negative.getId(), secondDepart, thirdDepart);
complaintCollectionResourceService.saveOrUpdateComplaintCollection(Collections.singletonList(cc));
return true;
}
// ==================== 阶段2 同步方法 ====================
/**
* 单条记录的同步逻辑独立事务执行
*/
private boolean doSyncSingleRecord(MailBoxSyncDto dto) {
ComplaintCollection cc = dto.getCc();
Mail mail = dto.getMail();
List<MailBlame> mailBlames = dto.getMailBlames();
// 1. 通过 originId 找到对应的 Negative(只有阶段1创建过的才同步)
Negative negative = negativeService.getByOriginId(cc.getOriginId());
if (negative == null) {
log.warn("【阶段2同步】无对应Negative,跳过: originId={}", cc.getOriginId());
return false;
}
// 2. 组装数据并保存
syncBlame(mail, negative.getId(), mailBlames);
syncFiles(mail, negative.getId());
// 3. 同步核查情况到 Negative
syncCheckStatus(mail, negative);
// 4. 更新同步标记(所有同步操作成功后)
updateSyncTag(cc);
return true;
}
/**
* 同步涉及人
*/
private List<NegativeBlame> syncBlame(Mail mail, String negativeId, List<MailBlame> mailBlames) {
// 幂等性检查:已存在则跳过
LambdaQueryWrapper<NegativeBlame> existWrapper = new LambdaQueryWrapper<>();
existWrapper.eq(NegativeBlame::getNegativeId, negativeId);
if (blameService.count(existWrapper) > 0) {
log.info("【阶段2同步】涉及人已存在,跳过: negativeId={}", negativeId);
return Collections.emptyList();
}
// mailBlames 已由调用方在事务外查询好
if (CollectionUtil.isEmpty(mailBlames)) {
return Collections.emptyList();
}
List<NegativeBlame> result = new ArrayList<>();
for (MailBlame blame : mailBlames) {
// 通过姓名+警号查询 SupPolice 获取完整信息
LambdaQueryWrapper<SupPolice> supPoliceLambdaQueryWrapper = new LambdaQueryWrapper<>();
supPoliceLambdaQueryWrapper.eq(SupPolice::getIdCode, blame.getBlameIdCode());
supPoliceLambdaQueryWrapper.last("limit 1");
SupPolice police = supPoliceService.getOne(supPoliceLambdaQueryWrapper);
if (Objects.isNull(police)) {
log.warn("【阶段2同步】未找到该警员的数据! blameName={}, blameEmpNo={}", blame.getBlameName(), blame.getBlameEmpNo());
continue;
}
NegativeBlame negativeBlame = new NegativeBlame();
negativeBlame.setBlameId(IdUtil.getSnowflakeNextIdStr());
negativeBlame.setNegativeId(negativeId);
negativeBlame.setType("personal");
negativeBlame.setBlameEmpNo(police.getEmpNo());
negativeBlame.setBlameIdCode(police.getIdCode());
negativeBlame.setBlameName(police.getName());
negativeBlame.setCrtTime(LocalDateTime.now());
negativeBlame.setUpdTime(LocalDateTime.now());
negativeBlame.setLeadEmpNo(blame.getLeaderEmpNo());
negativeBlame.setLeadName(blame.getLeaderName());
negativeBlame.setLeadIdCode(blame.getLeaderIdCode());
negativeBlame.setIvPersonType(police.getPersonType());
// 责任追究映射
if (StrUtil.isNotBlank(blame.getVerifyPunish())) {
try {
List<String> externalNames = JSON.parseArray(blame.getVerifyPunish(), String.class);
List<SupDictHandleResultMaping> dictHandleResultMapings = dictHandleResultMapingService.list(externalNames);
negativeBlame.setHandleResultCode(
dictHandleResultMapings.stream()
.map(SupDictHandleResultMaping::getInternalId)
.collect(Collectors.joining(","))
);
negativeBlame.setHandleResultName(
dictHandleResultMapings.stream()
.map(SupDictHandleResultMaping::getInternalName)
.collect(Collectors.joining("、"))
);
} catch (Exception e) {
log.warn("【阶段2同步】责任追究映射失败: verifyPunish={}", blame.getVerifyPunish(), e);
}
}
// 查证属实问题映射 - 创建 NegativeProblemRelation
if (StrUtil.isNotBlank(blame.getVerifyProblem())) {
try {
List<String> externalNames = JSON.parseArray(blame.getVerifyProblem(), String.class);
List<SupDictProblemTypeMaping> problemTypeMapings = dictProblemTypeMapingService.list(externalNames);
for (SupDictProblemTypeMaping problemTypeMaping : problemTypeMapings) {
SupDictProblemType threeProblem = problemTypeService.getById(problemTypeMaping.getInternalId());
if (threeProblem == null) {
log.warn("【阶段2同步】未找到三级问题类型: internalId={}", problemTypeMaping.getInternalId());
continue;
}
NegativeProblemRelation problemRelation = new NegativeProblemRelation();
problemRelation.setBlameId(negativeBlame.getBlameId());
problemRelation.setNegativeId(negativeId);
problemRelation.setThreeLevelCode(threeProblem.getId());
problemRelation.setThreeLevelContent(threeProblem.getName());
// 二级
String parentCode = threeProblem.getParentCode();
SupDictProblemType twoProblem = null;
if (StrUtil.isNotBlank(parentCode)) {
twoProblem = problemTypeService.getById(parentCode);
if (twoProblem != null) {
problemRelation.setTwoLevelCode(twoProblem.getId());
problemRelation.setTwoLevelContent(twoProblem.getName());
}
}
// 一级
if (twoProblem != null) {
String twoParentCode = twoProblem.getParentCode();
if (StrUtil.isNotBlank(twoParentCode)) {
SupDictProblemType oneProblem = problemTypeService.getById(twoParentCode);
if (oneProblem != null) {
problemRelation.setOneLevelCode(oneProblem.getId());
problemRelation.setOneLevelContent(oneProblem.getName());
}
}
}
negativeProblemRelationService.save(problemRelation);
}
} catch (Exception e) {
log.warn("【阶段2同步】查证属实问题映射失败: verifyProblem={}", blame.getVerifyProblem(), e);
}
}
// 保存 NegativeBlame
blameService.save(negativeBlame);
result.add(negativeBlame);
log.debug("【阶段2同步】保存涉及人: negativeId={}, blameName={}", negativeId, negativeBlame.getBlameName());
}
return result;
}
/**
* 同步核查情况Mail 表的核查字段 -> Negative
*/
private void syncCheckStatus(Mail mail, Negative negative) {
String checkStatus = null;
String checkStatusName = null;
String checkStatusCode = null;
// 核查情况映射
String verifyIsTrue = mail.getVerifyIsTrue();
if ("属实".equals(verifyIsTrue)) {
checkStatus = "1";
checkStatusName = "属实";
checkStatusCode = "1";
} else if ("基本属实".equals(verifyIsTrue)) {
checkStatus = "2";
checkStatusName = "部分属实";
checkStatusCode = "2";
} else if ("不属实".equals(verifyIsTrue)) {
checkStatus = "3";
checkStatusName = "不属实";
checkStatusCode = "5";
}
// 核查结论(优先 verifyDetails,其次 completionComment)
String checkStatusDesc = null;
if (StrUtil.isNotBlank(mail.getVerifyDetails())) {
checkStatusDesc = mail.getVerifyDetails();
} else if (StrUtil.isNotBlank(mail.getCompletionComment())) {
checkStatusDesc = mail.getCompletionComment();
}
// 只有存在核查情况时才更新
if (checkStatus != null || StrUtil.isNotBlank(checkStatusDesc)) {
// 防御编程:使用 updateById 确保只更新单条记录
negative.setCheckStatus(checkStatus);
negative.setCheckStatusName(checkStatusName);
negative.setCheckStatusDesc(checkStatusDesc);
negative.setCheckStatusCode(checkStatusCode);
boolean updated = negativeService.updateById(negative);
if (updated) {
log.debug("【阶段2同步】更新核查情况: negativeId={}, checkStatus={}, checkStatusDesc={}",
negative.getId(), checkStatus, checkStatusDesc);
} else {
log.warn("【阶段2同步】更新核查情况失败,Negative不存在或已被删除: negativeId={}", negative.getId());
}
}
}
/**
* 同步涉及人MailBlame -> NegativeBlame组装并保存
* 参照 MailService#saveMailbox for 循环逻辑
*/
/**
* 同步核查附件Mail.verifyAttachments -> NegativeFile组装并保存
*/
private void syncFiles(Mail mail, String negativeId) {
if (StrUtil.isBlank(mail.getVerifyAttachments())) {
return;
}
List<MailAttachmentDTO> attachments = JSON.parseArray(mail.getVerifyAttachments(), MailAttachmentDTO.class);
if (CollectionUtil.isEmpty(attachments)) {
return;
}
// 幂等性检查:如果该 Negative 已有附件,直接跳过
long existingCount = fileService.count(
new LambdaQueryWrapper<NegativeFile>().eq(NegativeFile::getNegtiveId, negativeId)
);
if (existingCount > 0) {
log.info("【阶段2同步】附件已存在,跳过: negativeId={}", negativeId);
return;
}
for (MailAttachmentDTO att : attachments) {
NegativeFile file = new NegativeFile();
file.setFileId(IdUtil.getSnowflakeNextIdStr());
file.setNegtiveId(negativeId);
file.setFileName(StrUtil.isNotBlank(att.getOriginFilename()) ? att.getOriginFilename() : getFileName(att.getFilepath()));
file.setFilePath(buildFileUrl(att));
file.setCrtTime(LocalDateTime.now());
fileService.save(file);
log.debug("【阶段2同步】保存核查附件: negativeId={}, fileName={}", negativeId, file.getFileName());
}
}
/**
* 更新同步标记所有同步操作成功后
*
* @param cc
*/
private void updateSyncTag(ComplaintCollection cc) {
ComplaintCollectionUpdateParam complaintCollectionUpdateParam = new ComplaintCollectionUpdateParam();
complaintCollectionUpdateParam.setId(cc.getId());
complaintCollectionUpdateParam.setBlameSyncStatus("1");
boolean updated = complaintCollectionResourceService.updateSelectiveById(complaintCollectionUpdateParam);
if (!updated) {
throw new RuntimeException("更新blameSyncStatus失败: ccId=" + cc.getId());
}
}
/**
* 构建文件完整URL
*/
private String buildFileUrl(MailAttachmentDTO att) {
String filepath = null;
if (StrUtil.isNotBlank(att.getDocxFilepath())) {
filepath = "http://65.47.60.145/lan-api/api/file/stream/" + att.getDocxFilepath();
} else if (StrUtil.isNotBlank(att.getFilepath())) {
filepath = "http://65.47.60.145/lan-api/api/file/stream/" + att.getFilepath();
}
return filepath;
}
// ==================== 组装 ====================
/**
* 构建 NegativeDataOnlyDto
*/
private NegativeDataOnlyDto buildNegativeDto(Mail mail, SupDepart secondDepart, SupDepart thirdDepart) {
NegativeDataOnlyDto dto = new NegativeDataOnlyDto();
// 基础字段
dto.setOriginId(mail.getId());
dto.setDiscoveryTime(mail.getMailTime());
// 创建时间以来信时间为准
dto.setCrtTime(mail.getMailTime());
dto.setProblemSourcesCode(ProblemSourcesEnum.JZXX.getValue());
dto.setProblemSources(ProblemSourcesEnum.JZXX.getLabel());
// 业务类型(其他)
dto.setBusinessTypeCode(BusinessTypeEnum.QT.getValue());
dto.setBusinessTypeName(BusinessTypeEnum.QT.getLabel());
// 追责对象(涉及个人)
dto.setAccountabilityTarget(AccountabilityTargetEnum.PERSONAL.getValue());
// 反映人信息
dto.setResponderName(mail.getContactName());
dto.setContactPhone(mail.getContactPhone());
// 办理状态(已完成)
dto.setProcessingStatus("completed");
// 来信内容
dto.setThingDesc(mail.getContent());
// 涉及单位设置
dto.setInvolveDepartId(thirdDepart.getId());
dto.setInvolveDepartName(thirdDepart.getName());
// 办理单位设置
dto.setHandleSecondDepartId(secondDepart.getId());
dto.setHandleSecondDepartName(secondDepart.getName());
dto.setHandleThreeDepartId(thirdDepart.getId());
dto.setHandleThreeDepartName(thirdDepart.getName());
// 来信附件
dto.setThingFiles(buildThingFiles(mail.getAttachments()));
// 来源字段
dto.setSourceType(NegativeSourceTypeEnum.COMPLAINT_REPORT.getCode());
dto.setSourceTypeDesc(NegativeSourceTypeEnum.COMPLAINT_REPORT.getDesc());
// dto.setIssuingDepartId("-1");
// dto.setIssuingDepartName("自定抓取");
return dto;
}
/**
* 构建问题附件列表
*/
private List<NegativeThingFile> buildThingFiles(String attachmentsJson) {
List<MailAttachmentDTO> attachments = parseAttachments(attachmentsJson);
if (CollectionUtil.isEmpty(attachments)) {
return Collections.emptyList();
}
List<NegativeThingFile> list = new ArrayList<>();
for (MailAttachmentDTO att : attachments) {
NegativeThingFile file = new NegativeThingFile();
file.setFileName(StrUtil.isNotBlank(att.getOriginFilename()) ? att.getOriginFilename() : getFileName(att.getFilepath()));
file.setFilePath(att.getFilepath());
file.setCreateTime(LocalDateTime.now());
list.add(file);
}
return list;
}
/**
* 解析附件 JSON
*/
private List<MailAttachmentDTO> parseAttachments(String json) {
if (StrUtil.isBlank(json)) {
return Collections.emptyList();
}
try {
List<MailAttachmentDTO> list = JSON.parseArray(json, MailAttachmentDTO.class);
list.forEach(item -> {
String filepath = null;
if (StrUtil.isNotBlank(item.getDocxFilepath())) {
filepath = "http://65.47.60.145/lan-api/api/file/stream/" + item.getDocxFilepath();
} else {
filepath = "http://65.47.60.145/lan-api/api/file/stream/" + item.getFilepath();
}
item.setFilepath(filepath);
});
return list;
} catch (Exception e) {
log.warn("【局长信箱已办结抓取】附件JSON解析失败,json={}", json, e);
return Collections.emptyList();
}
}
/**
* 从路径获取文件名
*/
private String getFileName(String path) {
if (path == null) return null;
int index = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'));
return index == -1 ? path : path.substring(index + 1);
}
// ==================== ComplaintCollection 组装 ====================
/**
* 构建 ComplaintCollection
*/
private ComplaintCollection buildComplaintCollection(Mail mail, String negativeId, SupDepart secondDepart, SupDepart thirdDepart) {
ComplaintCollection cc = new ComplaintCollection();
// 设置问题编号
cc.setNegativeId(negativeId);
cc.setOriginId(mail.getId());
// 问题来源信息
String sfssSourceTableSubOne = getSfssSourceTableSubOne(mail.getSource());
cc.setSourceTable(ComplaintCollectionSourceTableEnum.MAYOR_MAILBOX.getCode());
cc.setSourceTableSubOne(sfssSourceTableSubOne);
// 单位信息
cc.setSecondDepartId(secondDepart.getId());
cc.setSecondDepartName(secondDepart.getShortName());
cc.setThirdDepartId(thirdDepart.getId());
cc.setThirdDepartName(thirdDepart.getShortName());
// 创建时间用来信时间
cc.setCreateTime(mail.getMailTime());
cc.setCreateBy("自动抓取");
// 身份证
cc.setResponderIdCode(mail.getContactIdCard());
return cc;
}
private String getSfssSourceTableSubOne(String str) {
if ("厅长信箱".equals(str)) {
return "23_tz";
}
if ("mailbox".equals(str)) {
return "23_jz";
}
if ("110_report_complaints".equals(str)) {
return "23_jb";
}
return "";
}
}

134
src/main/java/com/biutag/supervision/service/NegativeService.java

@ -23,6 +23,7 @@ import com.biutag.supervision.pojo.domain.CountersignApply;
import com.biutag.supervision.pojo.domain.NegativeDetail;
import com.biutag.supervision.pojo.domain.NegativeVo;
import com.biutag.supervision.pojo.dto.ActionDto;
import com.biutag.supervision.pojo.dto.NegativeDataOnlyDto;
import com.biutag.supervision.pojo.dto.NegativeDto;
import com.biutag.supervision.pojo.dto.complaintCollection.ComplaintCollectionPageDTO;
import com.biutag.supervision.pojo.dto.flow.FirstDistributeData;
@ -188,7 +189,7 @@ public class NegativeService extends ServiceImpl<NegativeMapper, Negative> {
}
public Negative getByOriginId(String originId) {
return getOne(new LambdaUpdateWrapper<Negative>().eq(Negative::getOriginId, originId));
return getOne(new LambdaQueryWrapper<Negative>().eq(Negative::getOriginId, originId).last("LIMIT 1"));
}
private final SupDictDataService dictDataService;
@ -552,4 +553,135 @@ public class NegativeService extends ServiceImpl<NegativeMapper, Negative> {
ComplaintCollectionPageDTO complaintCollectionPageDTO = records.get(0);
detail.setComplaintCollectionPageDTO(complaintCollectionPageDTO);
}
/**
* 保存主表数据
*
* @param dto 保存参数
* @return 保存的Negative实体
*/
public Negative saveNegativeMain(NegativeDataOnlyDto dto) {
Negative negative = new Negative();
BeanUtil.copyProperties(dto, negative);
if (StrUtil.isBlank(negative.getId())) {
negative.setId(IdUtil.getSnowflakeNextIdStr());
}
LocalDateTime now = LocalDateTime.now();
if (negative.getCrtTime() == null) {
negative.setCrtTime(now);
}
if (negative.getUpdTime() == null) {
negative.setUpdTime(now);
}
// 处理涉及单位层级
if (StrUtil.isNotBlank(dto.getInvolveDepartId()) && StrUtil.isBlank(negative.getSecondInvolveDepartId()) && StrUtil.isBlank(negative.getThreeInvolveDepartId())) {
SupDepart depart = departService.getById(dto.getInvolveDepartId());
if (depart != null) {
if (DepartLevelEnum.SECOND.getValue().equals(depart.getLevel())) {
negative.setSecondInvolveDepartId(depart.getId());
negative.setThreeInvolveDepartId(null);
} else if (DepartLevelEnum.THREE.getValue().equals(depart.getLevel())) {
negative.setThreeInvolveDepartId(depart.getId());
negative.setSecondInvolveDepartId(depart.getPid());
}
}
}
// 处理列表字段转字符串
if (CollectionUtil.isNotEmpty(dto.getInvolveProblem())) {
negative.setInvolveProblem(String.join(",", dto.getInvolveProblem()));
}
if (CollectionUtil.isNotEmpty(dto.getProblems())) {
negative.setProblems(JSON.toJSONString(dto.getProblems()));
}
save(negative);
return negative;
}
/**
* 保存来信附件
*/
public void saveThingFiles(NegativeDataOnlyDto dto, String negativeId) {
if (dto.getThingFiles().isEmpty()) {
return;
}
LocalDateTime now = LocalDateTime.now();
List<NegativeThingFile> files = dto.getThingFiles().stream().map(item -> {
NegativeThingFile negativeThingFile = new NegativeThingFile();
BeanUtil.copyProperties(item, negativeThingFile);
negativeThingFile.setNegativeId(negativeId);
if (negativeThingFile.getCreateTime() == null) {
negativeThingFile.setCreateTime(now);
}
return negativeThingFile;
}).toList();
thingFileService.saveBatch(files);
}
/**
* 保存涉及人
*/
public void saveBlames(NegativeDataOnlyDto dto, String negativeId) {
if (dto.getBlames().isEmpty()) {
return;
}
LocalDateTime now = LocalDateTime.now();
List<NegativeBlame> blames = dto.getBlames().stream().map(item -> {
NegativeBlame blame = new NegativeBlame();
BeanUtil.copyProperties(item, blame);
blame.setNegativeId(negativeId);
if (blame.getCrtTime() == null) {
blame.setCrtTime(now);
}
return blame;
}).toList();
blameService.saveBatch(blames);
}
/**
* 保存核查附件
*/
public void saveFiles(NegativeDataOnlyDto dto, String negativeId) {
if (dto.getFiles().isEmpty()) {
return;
}
LocalDateTime now = LocalDateTime.now();
List<NegativeFile> files = dto.getFiles().stream().map(item -> {
NegativeFile file = new NegativeFile();
BeanUtil.copyProperties(item, file);
file.setNegtiveId(negativeId);
if (file.getCrtTime() == null) {
file.setCrtTime(now);
}
return file;
}).toList();
fileService.saveBatch(files);
}
/**
* 纯数据保存 - 不触发工作流和历史记录
* 兼容旧方法内部调用拆分的4个方法
*
* @param dto 保存参数
* @return 保存的Negative实体
*/
@Transactional(rollbackFor = Exception.class)
public Negative saveDataOnly(NegativeDataOnlyDto dto) {
Negative negative = saveNegativeMain(dto);
saveThingFiles(dto, negative.getId());
saveBlames(dto, negative.getId());
saveFiles(dto, negative.getId());
return negative;
}
}

Loading…
Cancel
Save