/*
* Copyright (c) 2025, TP-Link. All rights reserved.
*/
package com.tplink.smb.common.data.management.system.modules.application.service.impl;
import java.io.IOException;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Random;
import java.util.UUID;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.tplink.smb.common.data.management.system.modules.application.constant.enums.ApplicationDataType;
import com.tplink.smb.common.data.management.system.modules.application.constant.enums.ApplicationStatus;
import com.tplink.smb.common.data.management.system.modules.application.constant.enums.ApplicationType;
import com.tplink.smb.common.data.management.system.modules.application.constant.enums.ApprovalResult;
import com.tplink.smb.common.data.management.system.modules.application.constant.enums.DeviceInfoStatus;
import com.tplink.smb.common.data.management.system.modules.application.domain.ApplicationFormPO;
import com.tplink.smb.common.data.management.system.modules.application.domain.VigiDeviceInfoPO;
import com.tplink.smb.common.data.management.system.modules.application.domain.vo.ApplicationFormVO;
import com.tplink.smb.common.data.management.system.modules.application.domain.vo.mixs.ApproverInfo;
import com.tplink.smb.common.data.management.system.modules.application.repository.ApplicationFormRepository;
import com.tplink.smb.common.data.management.system.modules.application.repository.OmadaDeviceInfoRepository;
import com.tplink.smb.common.data.management.system.modules.application.service.ApplicationFormService;
import com.tplink.smb.common.data.management.system.modules.application.service.DataInfoService;
import com.tplink.smb.common.data.management.system.modules.application.service.OmadaDeviceInfoService;
import com.tplink.smb.common.data.management.system.modules.application.service.VigiDeviceInfoService;
import com.tplink.smb.common.data.management.system.modules.application.service.dto.ApplicationFormDto;
import com.tplink.smb.common.data.management.system.modules.application.service.dto.ApplicationFormQueryCriteria;
import com.tplink.smb.common.data.management.system.modules.application.service.dto.PendingApprovalDto;
import com.tplink.smb.common.data.management.system.modules.application.service.mapstruct.ApplicationFormMapper;
import com.tplink.smb.common.data.management.system.service.S3StorageService;
import com.tplink.smb.common.data.management.system.utils.PageResult;
import com.tplink.smb.common.data.management.system.utils.PageUtil;
import com.tplink.smb.common.data.management.system.utils.QueryHelp;
import com.tplink.smb.common.data.management.system.utils.StringUtils;
/**
* @author Chen Jiayuan
* @version 1.0
* @since 2025/9/30
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class ApplicationFormServiceImpl implements ApplicationFormService {
private final ApplicationFormRepository applicationFormRepository;
private final ApplicationFormMapper applicationFormMapper;
private final OmadaDeviceInfoRepository omadaDeviceInfoRepository;
private final OmadaDeviceInfoService omadaDeviceInfoService;
private final VigiDeviceInfoService vigiDeviceInfoService;
private final S3StorageService s3StorageService;
/**
* 将 VO 中的数据复制到 JPA Entity
*/
private final ObjectMapper objectMapper = new ObjectMapper(); // Jackson
@Autowired
private PlatformTransactionManager transactionManager;
@Override
public PageResult<ApplicationFormDto> queryAll(
ApplicationFormQueryCriteria criteria, Pageable pageable) {
Page<ApplicationFormPO> page =
applicationFormRepository.findAll(
(root, criteriaQuery, criteriaBuilder) ->
QueryHelp.getPredicate(root, criteria, criteriaBuilder),
pageable);
// 转换为 DTO 并添加审批信息
Page<ApplicationFormDto> dtoPage =
page.map(
applicationForm -> {
ApplicationFormDto dto = applicationFormMapper.toDto(applicationForm);
// 添加审批信息
enrichApprovalInfo(dto, applicationForm.getId());
// 添加图片预签名URL信息
enrichImageUrls(dto, applicationForm);
enrichSpecificationUrl(dto, applicationForm);
return dto;
});
return PageUtil.toPage(dtoPage);
}
@Override
public List<ApplicationFormDto> queryAll(ApplicationFormQueryCriteria criteria) {
List<ApplicationFormPO> forms =
applicationFormRepository.findAll(
(root, criteriaQuery, criteriaBuilder) ->
QueryHelp.getPredicate(root, criteria, criteriaBuilder));
// 转换为 DTO 并添加审批信息
return forms.stream()
.map(
form -> {
ApplicationFormDto dto = applicationFormMapper.toDto(form);
// 添加审批信息
enrichApprovalInfo(dto, form.getId());
// 添加图片预签名URL信息到dataDetails中
enrichImageUrls(dto, form);
enrichSpecificationUrl(dto, form);
return dto;
})
.collect(Collectors.toList());
}
/**
* 丰富申请单的审批信息
*
* @param dto 申请单 DTO
* @param applicationFormId 申请单 ID
*/
private void enrichApprovalInfo(ApplicationFormDto dto, Integer applicationFormId) {
ApplicationFormPO form = applicationFormRepository.findById(applicationFormId).orElse(null);
if (form != null && form.getApprovalRecords() != null) {
dto.setApprovalRecords(form.getApprovalRecords()); // 直接使用 approvalRecords 字段
// 计算每个审批人的最新审批状态和意见
enrichApproverStatusAndComments(dto, form);
} else {
dto.setApprovalRecords("[]");
}
}
/**
* 丰富每个审批人的状态和意见信息
*
* @param dto 申请单 DTO
* @param form 申请单实体
*/
private void enrichApproverStatusAndComments(ApplicationFormDto dto, ApplicationFormPO form) {
List<Map<String, Object>> records = getApprovalRecordsFromForm(form);
// 获取最新轮次
int maxRound = form.getRound();
// 过滤出最新轮次的记录
List<Map<String, Object>> latestRoundRecords =
records.stream()
.filter(r -> (Integer) r.getOrDefault("round", 0) == maxRound)
.collect(Collectors.toList());
// 为每个审批人设置状态和意见
for (Map<String, Object> record : latestRoundRecords) {
String approverUserName = (String) record.get("approverUserName");
Integer approvalStatus = (Integer) record.get("approvalStatus");
String comment = (String) record.get("comment");
// 根据审批人用户名匹配并设置状态和意见
if (Objects.equals(approverUserName, dto.getApprover1())) {
dto.setApprover1Status(approvalStatus);
dto.setApprover1Comment(comment);
} else if (Objects.equals(approverUserName, dto.getApprover2())) {
dto.setApprover2Status(approvalStatus);
dto.setApprover2Comment(comment);
} else if (Objects.equals(approverUserName, dto.getApprover3())) {
dto.setApprover3Status(approvalStatus);
dto.setApprover3Comment(comment);
} else if (Objects.equals(approverUserName, dto.getApprover4())) {
dto.setApprover4Status(approvalStatus);
dto.setApprover4Comment(comment);
} else if (Objects.equals(approverUserName, dto.getApprover5())) {
dto.setApprover5Status(approvalStatus);
dto.setApprover5Comment(comment);
} else if (Objects.equals(approverUserName, dto.getApprover6())) {
dto.setApprover6Status(approvalStatus);
dto.setApprover6Comment(comment);
}
}
}
private void enrichImageUrls(ApplicationFormDto dto, ApplicationFormPO applicationFormPO) {
String dataDetails = applicationFormPO.getDataDetails();
if (StringUtils.isBlank(dataDetails)) {
return;
}
Map<String, Object> dataMap;
try {
dataMap = objectMapper.readValue(dataDetails, Map.class);
} catch (Exception e) {
//log.warn("Failed to parse dataDetails JSON for image enrichment: {}", dataDetails, e);
return; // 解析失败,跳过图片处理
}
try {
// 提取并解析 imageInfo
Map<String, Object> imageInfoMap = parseImageInfoField(dataMap.get("imageInfo"));
if (imageInfoMap == null) return;
@SuppressWarnings("unchecked")
Map<String, String> pathMap = (Map<String, String>) imageInfoMap.get("img_bucket_path");
if (!(pathMap instanceof Map) || pathMap.isEmpty()) return;
Map<String, String> fieldMapping = Map.of(
"small_img_bucket_path_for_web", "small_img_bucket_url_for_web",
"big_img_bucket_path_for_web", "big_img_bucket_url_for_web",
"heatmap_img_bucket_path_for_web", "heatmap_img_bucket_url_for_web",
"hdpi_img_bucket_path_for_app", "hdpi_img_bucket_url_for_app"
);
boolean updated = false;
for (Map.Entry<String, String> entry : fieldMapping.entrySet()) {
String s3Path = pathMap.get(entry.getKey());
if (StringUtils.isNotBlank(s3Path)) {
String url = generatePresignedUrl(s3Path.trim());
if (url != null) {
dataMap.put(entry.getValue(), url);
updated = true;
}
}
}
if (updated) {
dto.setDataDetails(objectMapper.writeValueAsString(dataMap));
}
} catch (Exception e) {
//log.warn("Unexpected error during image URL enrichment", e);
// 不抛出,不影响 specification 处理
}
}
private void enrichSpecificationUrl(ApplicationFormDto dto, ApplicationFormPO applicationFormPO) {
String dataDetails = applicationFormPO.getDataDetails();
if (StringUtils.isBlank(dataDetails)) {
return;
}
Map<String, Object> dataMap;
try {
dataMap = objectMapper.readValue(dataDetails, Map.class);
} catch (Exception e) {
//log.warn("Failed to parse dataDetails JSON for specification enrichment: {}", dataDetails, e);
return;
}
try {
// 获取 specification 字段(S3路径)
String specS3Path = (String) dataMap.get("specification");
if (StringUtils.isBlank(specS3Path)) {
return;
}
String presignedUrl = generatePresignedUrl(specS3Path.trim());
if (presignedUrl != null) {
dataMap.put("specificationUrl", presignedUrl); // 添加新字段
// 写回 DTO
dto.setDataDetails(objectMapper.writeValueAsString(dataMap));
}
} catch (Exception e) {
//log.warn("Unexpected error during specification URL enrichment", e);
// 继续执行其他流程
}
}
// 解析 imageInfo:支持字符串或对象
private Map<String, Object> parseImageInfoField(Object imageInfoObj) {
if (imageInfoObj == null) return null;
if (imageInfoObj instanceof String str && StringUtils.isNotBlank(str)) {
try {
return objectMapper.readValue(str.trim(), Map.class);
} catch (Exception e) {
//log.warn("Invalid JSON in imageInfo: {}", str, e);
return null;
}
} else if (imageInfoObj instanceof Map<?, ?> map) {
@SuppressWarnings("unchecked")
Map<String, Object> result = (Map<String, Object>) map;
return result;
}
return null;
}
// 生成 S3 预签名 URL(安全封装)
private String generatePresignedUrl(String s3Path) {
if (StringUtils.isBlank(s3Path)) return null;
try {
String url = s3StorageService.generatePresignedUrl(s3Path, 3600); // 1小时有效
return StringUtils.isNotBlank(url) ? url : null;
} catch (Exception e) {
//log.warn("Failed to generate presigned URL for path: {}", s3Path, e);
return null;
}
}
@Override
public void download(List<ApplicationFormDto> all, HttpServletResponse response)
throws IOException {
List<Map<String, Object>> list = new ArrayList<>();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void submitApplication(ApplicationFormVO vo) {
// 查询是否已存在该申请单(根据 id 判断)
ApplicationFormPO form;
boolean isNew = vo.getId() == null;
if (isNew) {
form = new ApplicationFormPO();
form.setUuid(generateApplicationNo());
form.setRound(1); // 第一次提交,轮次为1
} else {
form =
applicationFormRepository
.findById(vo.getId())
.orElseThrow(() -> new RuntimeException("Application Form does not exist: " + vo.getId()));
// 只能不在申请流程中的状态,即草稿、已撤回、已驳回状态的申请单
if (ApplicationStatus.fromStatus(form.getStatus()).isInApprovalProcess()) {
throw new RuntimeException("Only application forms in draft, withdrawn, or rejected status can be updated");
}
// 从已有申请单提交,申请的轮次记录+1
form.setRound(form.getRound() + 1); // 草稿的轮次为0 只要提交就+1
}
// 手动映射 VO 字段到 Entity
mapVoToEntity(form, vo);
// 保存申请单
form.setStatus(ApplicationStatus.SUBMITTED.getStatus());
applicationFormRepository.save(form);
}
/**
* 生成申请单编号 格式: APP + 年月日 + 6位序列号 (例如: APP20250918000001)
*
* @return 申请单编号
*/
private String generateApplicationNo() {
// 获取当前日期
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
String dateStr = sdf.format(new Date());
// 生成6位随机数或序列号
// 这里使用随机数,实际项目中可以使用数据库序列或其他方式保证唯一性
Random random = new Random();
int randomNumber = 100000 + random.nextInt(900000); // 生成100000-999999之间的数字
return "APP" + dateStr + randomNumber;
}
private void mapVoToEntity(ApplicationFormPO entity, ApplicationFormVO vo) {
// 基础字段(标题、理由等)
entity.setApplicationTitle(vo.getApplicationTitle());
entity.setApplicationReason(vo.getApplicationReason());
entity.setApplicationType(vo.getApplicationType());
entity.setApplicationDataType(vo.getApplicationDataType());
entity.setApplicationDataId(vo.getApplicationDataId());
// 设置审批人信息
entity.setApprover1(vo.getApprover1());
entity.setApprover1Level(vo.getApprover1Level());
entity.setApprover2(vo.getApprover2());
entity.setApprover2Level(vo.getApprover2Level());
entity.setApprover3(vo.getApprover3());
entity.setApprover3Level(vo.getApprover3Level());
entity.setApprover4(vo.getApprover4());
entity.setApprover4Level(vo.getApprover4Level());
entity.setApprover5(vo.getApprover5());
entity.setApprover5Level(vo.getApprover5Level());
entity.setApprover6(vo.getApprover6());
entity.setApprover6Level(vo.getApprover6Level());
// 申请人信息
entity.setApplicantUserName(vo.getApplicantUserName());
// 根据数据类型存储不同的详细信息
entity.setOriginDataDetails(vo.getOriginDataDetails());
entity.setDataDetails(vo.getDataDetails());
}
// 修改 ApplicationFormServiceImpl.java 中的 withdrawApplication 方法
@Override
@Transactional(rollbackFor = Exception.class)
public void withdrawApplication(Integer applicationFormId, String applicantName) {
ApplicationFormPO form =
applicationFormRepository
.findById(applicationFormId)
.orElseThrow(() -> new RuntimeException("申请单不存在"));
// 检查申请人是否匹配
if (!form.getApplicantUserName().equals(applicantName)) {
throw new RuntimeException("只有申请人才能撤回申请单");
}
// 更新状态为已撤回
form.setStatus(ApplicationStatus.WITHDRAWN.getStatus());
applicationFormRepository.save(form);
}
@Override
@Transactional(readOnly = true)
public PageResult<PendingApprovalDto> getPendingApprovals(
ApplicationFormQueryCriteria criteria, Pageable pageable) {
// 先查询所有申请单
List<ApplicationFormPO> allForms = applicationFormRepository.findAll();
// 过滤出包含当前审批人的申请单,减少不必要的数据库查询
List<ApplicationFormPO> filteredForms =
allForms.stream()
.filter(
form ->
ApplicationStatus.fromStatus(form.getStatus()).isInApprovalProcess())
.filter(form -> containsApprover(form, criteria.getApproverUserName())) // 先粗筛
.filter(form -> isUserCurrentApprover(form, criteria.getApproverUserName())) // 再细筛
.collect(Collectors.toList());
// 处理筛选后的申请单
List<PendingApprovalDto> result =
filteredForms.stream()
.map(
form -> {
ApplicationFormDto formDto = applicationFormMapper.toDto(form);
enrichApprovalInfo(formDto, form.getId());
PendingApprovalDto dto = new PendingApprovalDto();
dto.setApplicationForm(formDto);
// 直接使用表中的轮次值
dto.setRound(form.getRound());
dto.setStepOrder(getCurrentApprovalLevel(form, criteria.getApproverUserName()));
return dto;
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
// 手动分页
int total = result.size();
int start = (int) pageable.getOffset();
int end = Math.min(start + pageable.getPageSize(), total);
List<PendingApprovalDto> pagedContent = result.subList(start, end);
// 构造 PageResult(使用项目中定义的字段)
PageResult<PendingApprovalDto> pageResult = new PageResult<>();
pageResult.setContent(pagedContent);
pageResult.setTotalElements(total);
return pageResult;
}
/**
* 获取所有有效的审批人信息列表
*/
private List<ApproverInfo> getApproverInfos(ApplicationFormPO form) {
List<ApproverInfo> list = new ArrayList<>();
addIfValid(list, form.getApprover1(), form.getApprover1Level(), form.getApprover1Role());
addIfValid(list, form.getApprover2(), form.getApprover2Level(), form.getApprover2Role());
addIfValid(list, form.getApprover3(), form.getApprover3Level(), form.getApprover3Role());
addIfValid(list, form.getApprover4(), form.getApprover4Level(), form.getApprover4Role());
addIfValid(list, form.getApprover5(), form.getApprover5Level(), form.getApprover5Role());
addIfValid(list, form.getApprover6(), form.getApprover6Level(), form.getApprover6Role());
return list;
}
/**
* 条件添加:仅当用户名非空时才加入
*/
private void addIfValid(List<ApproverInfo> list, String username, Integer level, String role) {
if (username != null && !username.trim().isEmpty()) {
list.add(new ApproverInfo(username, level, role));
}
}
/**
* 粗筛:判断申请单是否包含指定审批人
*/
private boolean containsApprover(ApplicationFormPO form, String approverUserName) {
return getApproverInfos(form).stream()
.anyMatch(info -> Objects.equals(info.username(), approverUserName));
}
// ApplicationFormServiceImpl.java
@Override
@Transactional(readOnly = true)
public PageResult<ApplicationFormDto> getApprovedApplications(
ApplicationFormQueryCriteria criteria, Pageable pageable) {
// 先查询所有申请单
List<ApplicationFormPO> allForms = applicationFormRepository.findAll();
// 过滤出当前用户已审批的申请单
List<ApplicationFormPO> filteredForms =
allForms.stream()
.filter(form -> containsApprover(form, criteria.getApproverUserName()))
.filter(form -> isUserApprovedApplication(form, criteria.getApproverUserName()))
.filter(form -> matchesCriteria(form, criteria))
.collect(Collectors.toList());
// 转换为 ApplicationFormDto 列表
List<ApplicationFormDto> result =
filteredForms.stream()
.map(
form -> {
ApplicationFormDto dto = applicationFormMapper.toDto(form);
enrichApprovalInfo(dto, form.getId());
return dto;
})
.collect(Collectors.toList());
// 手动分页
int total = result.size();
int start = (int) pageable.getOffset();
int end = Math.min(start + pageable.getPageSize(), total);
List<ApplicationFormDto> pagedContent = new ArrayList<>();
if (start < end) {
pagedContent = result.subList(start, end);
}
// 构造 PageResult(使用项目中定义的字段)
PageResult<ApplicationFormDto> pageResult = new PageResult<>();
pageResult.setContent(pagedContent);
pageResult.setTotalElements(total);
return pageResult;
}
/**
* 判断用户是否已审批该申请单
*/
private boolean isUserApprovedApplication(ApplicationFormPO form, String approverUserName) {
List<Map<String, Object>> records = getApprovalRecordsFromForm(form);
return records.stream()
.anyMatch(
r -> {
Object approverUserNameObj = r.get("approverUserName");
Object approvalStatusObj = r.get("approvalStatus");
return approverUserNameObj != null
&& approvalStatusObj != null
&& approverUserNameObj.equals(approverUserName)
&& ((Integer) approvalStatusObj == ApprovalResult.APPROVE.getValue()
|| (Integer) approvalStatusObj == ApprovalResult.REJECT.getValue());
});
}
/**
* 判断申请单是否匹配查询条件
*/
private boolean matchesCriteria(ApplicationFormPO form, ApplicationFormQueryCriteria criteria) {
// 根据申请单UUID过滤
if (criteria.getUuid() != null && !criteria.getUuid().isEmpty()) {
if (form.getUuid() == null || !form.getUuid().contains(criteria.getUuid())) {
return false;
}
}
// 根据申请人姓名过滤
if (criteria.getApplicantUserName() != null && !criteria.getApplicantUserName().isEmpty()) {
if (form.getApplicantUserName() == null
|| !form.getApplicantUserName().contains(criteria.getApplicantUserName())) {
return false;
}
}
// 根据申请单类型过滤
if (criteria.getApplicationType() != null) {
if (form.getApplicationType() == null
|| !form.getApplicationType().equals(criteria.getApplicationType())) {
return false;
}
}
// 根据申请单数据类型过滤
if (criteria.getApplicationDataType() != null) {
if (form.getApplicationDataType() == null
|| !form.getApplicationDataType().equals(criteria.getApplicationDataType())) {
return false;
}
}
// 根据申请单标题过滤
if (criteria.getApplicationTitle() != null && !criteria.getApplicationTitle().isEmpty()) {
return form.getApplicationTitle() != null
&& form.getApplicationTitle().contains(criteria.getApplicationTitle());
}
return true;
}
@Transactional(rollbackFor = Exception.class)
@Override
public void approveApplication(
Integer applicationFormId, String approverUserName, Integer approvalStatus, String comment) {
Optional<ApplicationFormPO> formOpt = applicationFormRepository.findById(applicationFormId);
if (!formOpt.isPresent()) {
throw new RuntimeException("Application Form Id: " + applicationFormId + " not found");
}
ApplicationFormPO form = formOpt.get();
ApplicationStatus status = ApplicationStatus.fromStatus(form.getStatus()); // 自动映射
ApplicationType applicationType = ApplicationType.fromType(form.getApplicationType());
Timestamp now = new Timestamp(System.currentTimeMillis());
// 检查申请单状态是否为已提交或待审批
if (!status.equals(ApplicationStatus.SUBMITTED)
&& !status.equals(ApplicationStatus.REVIEW)) {
throw new RuntimeException("The application status is incorrect, and approval is not possible");
}
// 检查该用户是否是当前需要审批的人
if (!isUserCurrentApprover(form, approverUserName)) {
throw new RuntimeException("The current user is not the one requiring approval");
}
// 获取当前审批轮次(直接从实体中获取)
Integer currentRound = form.getRound();
// 如果申请单状态是已提交,将其改为待审批
if (status.equals(ApplicationStatus.SUBMITTED)) {
form.setStatus(ApplicationStatus.REVIEW.getStatus());
}
// 创建审批记录
Map<String, Object> record = new LinkedHashMap<>();
record.put("id", UUID.randomUUID().toString());
record.put("applicationFormId", applicationFormId);
record.put("round", currentRound);
record.put("approverLevel", getCurrentApprovalLevel(form, approverUserName));
record.put("approverRole", getApproverRole(form, approverUserName));
record.put("approverUserName", approverUserName);
record.put("approvalStatus", approvalStatus);
record.put("comment", comment);
record.put("approvedAt", now);
// 更新审批记录到 JSON 字段
updateApprovalRecords(form, record);
// 处理审批结果
int currentApprovalLevel = getCurrentApprovalLevel(form, approverUserName);
if (Objects.equals(approvalStatus, ApprovalResult.REJECT.getValue())) {
// 审批被驳回,更新申请单状态
status = ApplicationStatus.fromApprovalLevel(currentApprovalLevel,false);
} else {
// 审批通过,检查当前层级是否全部完成
if (isCurrentLevelCompleted(form, currentRound, approverUserName)) {
// 设置申请单状态为当前层级审批通过
status = ApplicationStatus.fromApprovalLevel(currentApprovalLevel,true);
// 一级审核过后进入灰度发布状态
if(currentApprovalLevel == 1) {
status = ApplicationStatus.SYNC_GRAY;
}
// 检查是否还有更高层级需要审批
if (hasNextLevel(form, currentRound)) {
// 还有更高层级,保持待审批状态
} else {
// 所有层级都完成,状态改为审批通过
status = ApplicationStatus.REVIEW_APPROVED;
// 如果是新增和修改,修改状态为正式发布
if(applicationType.equals(ApplicationType.ADD)||applicationType.equals(ApplicationType.EDIT)) {
try {
Map dataDetailMap = objectMapper.readValue(form.getDataDetails(), Map.class);
dataDetailMap.put("status", DeviceInfoStatus.ONLINE.getStatus());
form.setDataDetails(objectMapper.writeValueAsString(dataDetailMap));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
}
}
}
// 审批结果处理完成,存储审批状态
form.setStatus(status.getStatus());
applicationFormRepository.save(form);
startAutoProcess(applicationFormId);
// 如果审批通过(所有层级均通过),处理后续流程
if (status.equals(ApplicationStatus.REVIEW_APPROVED)) {
// 启动新线程执行自动流程,不使用事务
new Thread(
() -> {
try {
} catch (Exception e) {
// 记录日志,不影响主流程
}
})
.start();
}
}
// 更新审批记录
private void updateApprovalRecords(ApplicationFormPO form, Map<String, Object> record) {
List<Map<String, Object>> records = new ArrayList<>();
// 如果已有记录,先解析出来
if (form.getApprovalRecords() != null && !form.getApprovalRecords().isEmpty()) {
try {
ObjectMapper objectMapper = new ObjectMapper();
records =
objectMapper.readValue(
form.getApprovalRecords(), new TypeReference<List<Map<String, Object>>>() {
});
} catch (Exception e) {
}
}
// 添加新记录
records.add(record);
// 序列化回 JSON
try {
ObjectMapper objectMapper = new ObjectMapper();
form.setApprovalRecords(objectMapper.writeValueAsString(records));
} catch (Exception e) {
// 序列化失败则设为空数组
// form.setApprovalRecords("[]");
}
}
// 从表单中获取审批记录
private List<Map<String, Object>> getApprovalRecordsFromForm(ApplicationFormPO form) {
if (form.getApprovalRecords() == null || form.getApprovalRecords().isEmpty()) {
return new ArrayList<>();
}
try {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(
form.getApprovalRecords(), new TypeReference<List<Map<String, Object>>>() {
});
} catch (Exception e) {
return new ArrayList<>();
}
}
/**
* 判断用户是否是当前需要审批的人
*/
private boolean isUserCurrentApprover(ApplicationFormPO form, String approverUserName) {
// 获取当前用户在审批流程中的层级
int currentUserLevel = getCurrentApprovalLevel(form, approverUserName);
// 获取所有审批记录
List<Map<String, Object>> records = getApprovalRecordsFromForm(form);
// 检查当前用户是否已经在该层级审批过(无论通过还是驳回)
boolean userAlreadyApprovedInLevel =
records.stream()
.anyMatch(
r -> {
Object approverLevel = r.get("approverLevel");
Object approverUserNameObj = r.get("approverUserName");
return approverLevel != null
&& approverUserNameObj != null
&& (Integer) approverLevel == currentUserLevel
&& approverUserNameObj.equals(approverUserName);
});
// 如果用户已在该层级审批过,则不是当前需要审批的人
if (userAlreadyApprovedInLevel) {
return false;
}
// 确定当前应该审批的层级
// 查找最小的未完成审批的层级
int currentExpectedLevel = determineCurrentApprovalLevel(form, records);
// 检查当前用户是否在应该审批的层级上
if (currentUserLevel != currentExpectedLevel) {
return false;
}
// 获取当前层级的所有审批人
List<String> currentLevelApprovers = getCurrentLevelApprovers(form, currentUserLevel);
// 检查当前用户是否是该层级的审批人
return currentLevelApprovers.contains(approverUserName);
}
/**
* 确定当前应该审批的层级 规则:从层级1开始,检查每个层级是否所有审批人都已完成审批 如果某一层级还有未审批的审批人,则该层级为当前审批层级 如果某一层级所有审批人都已审批,则检查下一层级
*/
private int determineCurrentApprovalLevel(ApplicationFormPO form, List<Map<String, Object>> records) {
// 获取所有非空层级并排序
List<Integer> sortedLevels = getApproverInfos(form).stream()
.map(ApproverInfo::level)
.filter(Objects::nonNull)
.distinct()
.sorted()
.toList();
if (sortedLevels.isEmpty()) {
return 0;
}
// 遍历每个层级,检查是否已全部通过
for (Integer level : sortedLevels) {
List<String> levelApprovers = getCurrentLevelApprovers(form, level);
if (levelApprovers.isEmpty()) continue;
long approvedCount = records.stream()
.filter(r -> {
Object rLevel = r.get("approverLevel");
Object status = r.get("approvalStatus");
Object usernameObj = r.get("approverUserName");
return rLevel != null &&
status != null &&
usernameObj != null &&
((Integer) rLevel).equals(level) &&
(Integer) status == ApprovalResult.APPROVE.getValue() &&
levelApprovers.contains(usernameObj.toString());
})
.count();
// 只要有一个没通过,就停留在这一层
if (approvedCount < levelApprovers.size()) {
return level;
}
}
// 所有层级都通过,返回最大层级 + 1(表示流程结束或异常)
return sortedLevels.get(sortedLevels.size() - 1) + 1;
}
/**
* 获取指定层级的所有审批人
*/
private List<String> getCurrentLevelApprovers(ApplicationFormPO form, int level) {
return getApproverInfos(form).stream()
.filter(info -> info.level() != null && info.level().equals(level))
.map(ApproverInfo::username)
.toList();
}
/**
* 获取用户在审批流程中的层级
*/
private int getCurrentApprovalLevel(ApplicationFormPO form, String approverUserName) {
return getApproverInfos(form).stream()
.filter(info -> Objects.equals(info.username(), approverUserName))
.map(info -> info.level() != null ? info.level() : 0)
.findFirst()
.orElse(0);
}
/**
* 获取审批人角色
*/
private String getApproverRole(ApplicationFormPO form, String approverUserName) {
return getApproverInfos(form).stream()
.filter(info -> Objects.equals(info.username(), approverUserName))
.map(ApproverInfo::role)
.findFirst()
.orElse("unknown");
}
/**
* 判断当前层级是否全部完成
*/
private boolean isCurrentLevelCompleted(
ApplicationFormPO form, Integer currentRound, String approverUserName) {
// 获取所有审批记录
List<Map<String, Object>> records = getApprovalRecordsFromForm(form);
// 获取当前轮次的记录
List<Map<String, Object>> currentRoundRecords =
records.stream()
.filter(
r -> {
Object round = r.get("round");
return round != null && round == currentRound;
})
.collect(Collectors.toList());
// 获取当前审批人所在层级
int currentLevel = getCurrentApprovalLevel(form, approverUserName);
// 检查当前层级是否全部审批通过
long approvedCount =
currentRoundRecords.stream()
.filter(
r -> {
Object approverLevel = r.get("approverLevel");
Object approvalStatus = r.get("approvalStatus");
return approverLevel != null
&& approvalStatus != null
&& (Integer) approverLevel == currentLevel
&& (Integer) approvalStatus == ApprovalResult.APPROVE.getValue();
})
.count();
// 获取当前层级的审批人数
long totalCount = getCurrentLevelApprovers(form, currentLevel).size();
return approvedCount == totalCount;
}
/**
* 判断是否还有更高层级需要审批
*/
private boolean hasNextLevel(ApplicationFormPO form, Integer currentRound) {
// 获取所有审批记录
List<Map<String, Object>> records = getApprovalRecordsFromForm(form);
// 获取当前轮次的记录
List<Map<String, Object>> currentRoundRecords =
records.stream()
.filter(
r -> {
Object round = r.get("round");
return round != null && round == currentRound;
})
.collect(Collectors.toList());
// 获取当前最高层级
int maxLevel =
currentRoundRecords.stream()
.mapToInt(
r -> {
Object approverLevel = r.get("approverLevel");
return approverLevel != null ? (Integer) approverLevel : 0;
})
.max()
.orElse(0);
// 检查是否有更高层级的审批人
return hasHigherLevelApprover(form, maxLevel);
}
/**
* 判断是否有更高层级的审批人
*/
private boolean hasHigherLevelApprover(ApplicationFormPO form, int currentLevel) {
return getApproverInfos(form).stream()
.map(ApproverInfo::level)
.filter(Objects::nonNull)
.anyMatch(level -> level > currentLevel);
}
private void startAutoProcess(Integer applicationFormId) {
ApplicationFormPO form =
applicationFormRepository
.findById(applicationFormId)
.orElseThrow(() -> new RuntimeException("Application Form does not exist id: " + applicationFormId));
// 只有新增、修改、上线、下线申请才需要自动处理
if (form.getApplicationType() >= 0 && form.getApplicationType() <= 3) {
// 异步执行自动处理流程
completeProcessBasedOnType(form);
} else {
// 其他类型直接完成
form.setStatus(ApplicationStatus.COMPLETED.getStatus());
applicationFormRepository.save(form);
}
}
@Override
public void ApplicationPostProcess(Integer applicationFormId) {
ApplicationFormPO form =
applicationFormRepository
.findById(applicationFormId)
.orElseThrow(() -> new RuntimeException("Application Form does not exist id: " + applicationFormId));
// 根据申请数据类型处理不同类型的申请
switch (ApplicationDataType.fromDataType(form.getApplicationDataType())) {
case OMADA:
processApplicationData(form, omadaDeviceInfoService);
break;
case VIGI:
processApplicationData(form, vigiDeviceInfoService);
break;
}
}
private void processApplicationData(ApplicationFormPO form, DataInfoService dataInfoService) {
switch (ApplicationType.fromType(form.getApplicationType())) {
case ADD: // 新增
Integer applicationDataId = dataInfoService.createFromJson(form.getDataDetails());
// 更新申请单的application_data_id
form.setApplicationDataId(applicationDataId);
// 检查application_data_id是否已经存在,如果存在就不更新
if (applicationDataId != null
&& applicationFormRepository.findByApplicationDataId(applicationDataId) == null) {
applicationFormRepository.save(form);
}
break;
case EDIT: // 修改
dataInfoService.createFromJson(form.getDataDetails());
break;
case ONLINE: // 上线
dataInfoService.setDataStatus(form.getApplicationDataId(), VigiDeviceInfoPO.STATUS_ONLINE);
break;
case OFFLINE: // 下线
dataInfoService.setDataStatus(form.getApplicationDataId(), VigiDeviceInfoPO.STATUS_OFFLINE);
break;
default:
throw new RuntimeException("Invalid application type: " + form.getApplicationType());
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void saveDraft(ApplicationFormVO vo) {
ApplicationFormPO form;
if (vo.getId() == null) {
form = new ApplicationFormPO();
form.setUuid(generateApplicationNo());
form.setRound(0);
form.setStatus(ApplicationStatus.DRAFT.getStatus());
} else {
// 更新草稿
form =
applicationFormRepository
.findById(vo.getId())
.orElseThrow(() -> new RuntimeException("Application Form does not exist id: " + vo.getId()));
// 只能不在申请流程中的状态,即草稿、已撤回、已驳回状态的申请单
if (ApplicationStatus.fromStatus(form.getStatus()).isInApprovalProcess()) {
throw new RuntimeException("Only application forms in draft, withdrawn, or rejected status can be updated");
}
}
// 映射VO到Entity
mapVoToEntity(form, vo);
ApplicationFormPO save = applicationFormRepository.save(form);
// TODO 测试用,稍后要删除 处理设备信息申请
// 存储到数据库
ApplicationPostProcess(save.getId());
executeSync(save);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void manualTriggerFirmwareVerify(Integer applicationFormId) {
ApplicationFormPO form =
applicationFormRepository
.findById(applicationFormId)
.orElseThrow(() -> new RuntimeException("Application Form does not exist id: " + applicationFormId));
// 检查是否可以手动触发固件校验
ApplicationStatus applicationStatus = ApplicationStatus.fromStatus(form.getStatus());
if (!applicationStatus.equals(ApplicationStatus.VERIFY_FIRMWARE_FAILED)) {
throw new RuntimeException("The current status does not allow manual triggering of firmware verification");
}
// 异步执行固件校验
executeAutoFirmwareVerification(form);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void manualTriggerSync(Integer applicationFormId) {
ApplicationFormPO form =
applicationFormRepository
.findById(applicationFormId)
.orElseThrow(() -> new RuntimeException("Application Form does not exist id: " + applicationFormId));
// 检查是否可以手动触发同步
// 仅有等待灰度发布,灰度发布失败,正式发布失败时可以启用手动将同步
ApplicationStatus applicationStatus = ApplicationStatus.fromStatus(form.getStatus());
if (!applicationStatus.equals(ApplicationStatus.SYNC)
&&!applicationStatus.equals(ApplicationStatus.SYNC_GRAY_FAILED)
&&!applicationStatus.equals(ApplicationStatus.SYNC_FORMAL_FAILED)) {
throw new RuntimeException("The current state does not allow manual triggering of synchronization");
}
// 执行同步
executeSync(form);
}
// 固件校验预留方法
private void executeAutoFirmwareVerification(ApplicationFormPO form) {
// TODO: 实现固件校验逻辑
try {
// 模拟固件校验过程
firmwareVerification(form);
// 异步执行同步
executeSync(form);
} catch (Exception e) {
// 记录错误日志
log.error("Firmware verify failed, error:{}", e.getMessage(),e);
}
}
/**
* 固件校验模拟方法
*/
private void firmwareVerification(ApplicationFormPO form) {
Random random = new Random();
// 随机生成 true 或 false,各占 50%
boolean success = random.nextBoolean();
ApplicationStatus resultStatus = success ?
ApplicationStatus.VERIFY_FIRMWARE_SUCCEED :
ApplicationStatus.VERIFY_FIRMWARE_FAILED;
form.setStatus(resultStatus.getStatus());
applicationFormRepository.save(form);
}
/**
* 同步预留方法
*/
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void executeSync(ApplicationFormPO form) {
ApplicationStatus applicationStatus = ApplicationStatus.fromStatus(form.getStatus());
ApplicationDataType applicationDataType = ApplicationDataType.fromDataType(form.getApplicationDataType());
try {
// 根据申请单数据类型执行不同的同步逻辑
if (applicationDataType.equals(ApplicationDataType.OMADA)) {
// 调用设备信息服务的同步方法
omadaDeviceInfoService.syncDeviceInfo(
form.getApplicationDataId());
// 同步成功后完成流程
if(applicationStatus.equals(ApplicationStatus.SYNC_GRAY)
||applicationStatus.equals(ApplicationStatus.SYNC_FORMAL_FAILED)) {
// 灰度发布
applicationStatus = ApplicationStatus.SYNC_GRAY_SUCCEED;
}else{
// 其余情况
applicationStatus = ApplicationStatus.SYNC_FORMAL_SUCCEED;
// 保存信息入库
ApplicationPostProcess(form.getId());
}
} else if (applicationDataType.equals(ApplicationDataType.VIGI)) {
// TODO: 实现VIGI同步逻辑
applicationStatus = ApplicationStatus.SYNC_FORMAL_SUCCEED;
} else {
throw new RuntimeException("Invalid data type: " + form.getApplicationDataType());
}
} catch (Exception e) {
// 同步失败后完的处理
if(applicationStatus.equals(ApplicationStatus.SYNC_GRAY)
||applicationStatus.equals(ApplicationStatus.SYNC_FORMAL_FAILED)) {
// 灰度发布
applicationStatus = ApplicationStatus.SYNC_GRAY_FAILED;
}else{
// 其余情况
applicationStatus = ApplicationStatus.SYNC_FORMAL_FAILED;;
}
log.error("Sync failed, error:{}", e.getMessage());
} finally {
// 更新状态
form.setStatus(applicationStatus.getStatus());
applicationFormRepository.save(form);
}
}
// 根据申请类型完成流程
private void completeProcessBasedOnType(ApplicationFormPO form) {
ApplicationType applicationType = ApplicationType.fromType(form.getApplicationType());
switch (applicationType) {
case ADD: // 新增
case EDIT: // 修改
// 新增和修改需要固件校验和同步
executeAutoFirmwareVerification(form);
break;
case ONLINE: // 上线
case OFFLINE: // 下线
// 上线和下线只需要同步
executeSync(form);
break;
default:
throw new RuntimeException("Invalid application type: " + form.getApplicationType());
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteApplicationForm(Integer applicationFormId, String applicantName) {
ApplicationFormPO form =
applicationFormRepository
.findById(applicationFormId)
.orElseThrow(() -> new RuntimeException("Application Form does not exist id: " + applicationFormId));
// 检查申请人是否匹配
if (!form.getApplicantUserName().equals(applicantName)) {
throw new RuntimeException("Only the applicant can delete the application form");
}
// 检查当前状态是否允许删除(只有草稿状态才能删除)
if (!ApplicationStatus.fromStatus(form.getStatus()).equals(ApplicationStatus.DRAFT)) {
throw new RuntimeException("Only application forms in draft status can be deleted");
}
// 删除申请单本身(审批记录已包含在内)
applicationFormRepository.deleteById(applicationFormId);
}
}
加了 @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class),executesyn方法显示@Transactional self-invocation (in effect, a method within the target object calling another method of the target object) does not lead to an actual transaction at runtime
最新发布