java.util.Map getOrDefault的陷阱

深入探讨Java8中Map.getOrDefault()方法的使用误区,分析其与预期不符的行为,尤其是在处理null值时可能导致的NullPointerException问题,并对比MapUtils.getString()的安全性。
部署运行你感兴趣的模型镜像

最近使用Java 8新增加的java.util.Map.getOrDefault()方法,结果却发现是一个大坑。

先上代码。

Map<String, String> map = Maps.newHashMap();
map.put("test", null);
System.out.println(map.getOrDefault("test", "default"));
System.out.println(map.getOrDefault("test", "default") == null);
System.out.println(org.apache.commons.collections4.MapUtils.getString(map, "test", "default"));

再看执行结果。

接下来开始分析。根据方法getOrDefault(Object key, V defaultValue),我天真地认为该方法是先从Map中get(key),如果获取到的值为null,那么就会取defaultValue,类似于org.apache.commons.collections4.MapUtils.getString(final Map<? super K, ?> map, final K key, final String defaultValue)。直到使用过后遇到了NPE时,我才认真看了下源码,源码如下。

源码的这个逻辑不是一般得坑人啊!从Map中get(key)的值不为null或者包含key时,就返回get(key)的值。但是这里有一个问题,Map中包含某个key时,该key对应的值也是可以为null的!本来想用getOrDefault来避免null并设置默认值的,结果还是有可能返回null并可能造成NPE问题。

结论:java.util.Map.getOrDefault() 方法并不能很贴切实际业务的使用,还是使用MapUtils.getXxx(final Map<? super K, ?> map, final K key, final Xxx defaultValue)来安全地获取非null值吧!

您可能感兴趣的与本文相关的镜像

PyTorch 2.6

PyTorch 2.6

PyTorch
Cuda

PyTorch 是一个开源的 Python 机器学习库,基于 Torch 库,底层由 C++ 实现,应用于人工智能领域,如计算机视觉和自然语言处理

/* * 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
最新发布
10-21
package com.kucun.Config; import java.io.IOException; import java.io.InputStream; import java.util.Map; import javax.servlet.Filter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import com.fasterxml.jackson.databind.ObjectMapper; // 2. 基础安全配置 @Configuration @EnableWebSecurity // 启用Web安全功能 public class SecurityConfig extends WebSecurityConfigurerAdapter{ /** * 核心安全过滤器链配置 * @param http HTTP安全构建器 * @return 安全过滤器链 * @throws Exception 配置异常 * * █ 配置逻辑说明: * 1. authorizeHttpRequests: 定义访问控制规则 * 2. formLogin: 配置表单登录 * 3. logout: 配置注销行为 * 4. exceptionHandling: 处理权限异常[^3] */ // 修正后的配置方法 @Override protected void configure(HttpSecurity http) throws Exception { http .addFilterBefore(jsonAuthFilter(), UsernamePasswordAuthenticationFilter.class) .authorizeRequests() .antMatchers("/login.html").permitAll() .antMatchers(HttpMethod.POST, "/users/login").permitAll() .antMatchers("/users/guanli/**").hasRole("ADMIN") .antMatchers("/js/**", "/css/**", "/fonts/**", "/images/**","/*").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login.html") .loginProcessingUrl("/users/login") .successHandler(ajaxAuthenticationSuccessHandler()) // 自定义成功处理器 .failureHandler(ajaxAuthenticationFailureHandler()) // 自定义失败处理器 .defaultSuccessUrl("/index.html") .failureUrl("/login.html?error=true") .usernameParameter("andy") // 修改用户名参数名 .passwordParameter("pass") // 修改密码参数名 .and() .logout() .logoutUrl("/logout") .logoutSuccessUrl("/login.html") .and() .csrf() .ignoringAntMatchers("/users/login") .and() .headers() .frameOptions().sameOrigin() .and() .exceptionHandling() .accessDeniedHandler(accessDeniedHandler()); // 统一使用Handler } // 返回JSON格式的成功响应 @Bean public AuthenticationSuccessHandler ajaxAuthenticationSuccessHandler() { return (request, response, authentication) -> { response.setStatus(HttpStatus.OK.value()); response.getWriter().write("{\"code\":200, \"message\":\"登录成功\"}"); }; } // 返回401状态码和错误信息 @Bean public AuthenticationFailureHandler ajaxAuthenticationFailureHandler() { return (request, response, exception) -> { response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.getWriter().write("{\"code\":401, \"message\":\"认证失败\"}"); }; } // 处理未认证请求 @Bean public AuthenticationEntryPoint ajaxAuthenticationEntryPoint() { return (request, response, authException) -> { response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.getWriter().write("{\"code\":401, \"message\":\"未登录\"}"); }; } @Bean public JsonUsernamePasswordAuthenticationFilter jsonAuthFilter() throws Exception { JsonUsernamePasswordAuthenticationFilter filter = new JsonUsernamePasswordAuthenticationFilter(); filter.setAuthenticationManager(authenticationManagerBean()); filter.setUsernameParameter("andy"); // 设置自定义参数名 filter.setPasswordParameter("pass"); filter.setFilterProcessesUrl("/users/login"); return filter; } /** * 密码编码器(必须配置) * 使用BCrypt强哈希算法加密 */ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public AccessDeniedHandler accessDeniedHandler() { System.out.println("0000"); return (request, response, ex) -> { if (!response.isCommitted()) { response.sendRedirect("/error/403"); } }; } } class JsonUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter { private final ObjectMapper objectMapper = new ObjectMapper(); @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) { try { // 解析JSON请求体 InputStream is = request.getInputStream(); Map<String, String> authMap = objectMapper.readValue(is, Map.class); String username = authMap.get(getUsernameParameter()); String password = authMap.get(getPasswordParameter()); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } catch (IOException e) { throw new AuthenticationServiceException("认证请求解析失败", e); } } else { return super.attemptAuthentication(request, response); } } } /users/login 用的是ajak+post+json请求,现在是后端接收到数据也在数据库了匹配到用户,但就是返回401
05-29
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值