commit
Some checks failed
API接口参数变更检测 / api-param-check (push) Has been cancelled

This commit is contained in:
2026-06-05 16:18:40 +08:00
parent 1ca34c6bb2
commit 3cba3bb74e
4393 changed files with 450030 additions and 103 deletions

View File

@@ -0,0 +1,120 @@
package jnpf.storecertificatephoto.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import jnpf.base.ActionResult;
import jnpf.certificate.service.CertificateInstanceService;
import jnpf.model.certificate.po.CertificateInstanceEntity;
import jnpf.model.storecertificatephoto.po.StoreCertificatePhotoEntity;
import jnpf.model.storecertificatephoto.req.StoreCertificatePhotoAddReq;
import jnpf.model.storecertificatephoto.req.StoreCertificatePhotoUpdateReq;
import jnpf.model.storecertificatephoto.vo.StoreCertificatePhotoVO;
import jnpf.model.warningnotice.enums.CertificateTypeEnum;
import jnpf.permission.StoreApi;
import jnpf.permission.V2OrganizeApi;
import jnpf.permission.dto.store.StoreBaseInfoIdsQueryDTO;
import jnpf.permission.vo.store.BaseStoreVO;
import jnpf.permission.vo.v2.organzie.OrganizeGeneralDetailVO;
import jnpf.storecertificatephoto.service.StoreCertificatePhotoService;
import jnpf.util.UserProvider;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.constraints.NotBlank;
import java.util.*;
import java.util.stream.Collectors;
/**
* web门店自定义证件配置照控制器
*/
@RestController
@Validated
@RequestMapping("/web/store-certificate-photo")
public class StoreCertificatePhotoController {
@Autowired
private StoreCertificatePhotoService storeCertificatePhotoService;
@Autowired
private CertificateInstanceService certificateInstanceService;
@Autowired
private V2OrganizeApi v2OrganizeApi;
@Autowired
private StoreApi storeApi;
/**
* 新增门店证件照配置
*
* @param req 门店证件照请求参数
* @return 操作结果
*/
@PostMapping("/add")
public ActionResult<Void> add(@Validated @RequestBody StoreCertificatePhotoAddReq req) {
storeCertificatePhotoService.add(req);
return ActionResult.success();
}
/**
* 编辑门店自定义证件配置配置
*
* @param req 门店证件照编辑参数
* @return 操作结果
*/
@PutMapping("/update")
public ActionResult<Void> update(@Validated @RequestBody StoreCertificatePhotoUpdateReq req) {
storeCertificatePhotoService.update(req);
return ActionResult.success();
}
/**
* 删除门店自定义证件配置配置
*
* @param id 门店证照自定义配置id
* @return 操作结果
*/
@DeleteMapping("/delete/{id}")
public ActionResult<Void> delete(@PathVariable("id") String id) {
Set<String> orgIds = certificateInstanceService.list(new LambdaQueryWrapper<CertificateInstanceEntity>()
.eq(CertificateInstanceEntity::getTemplateId,id)
.eq(CertificateInstanceEntity::getEnabledMark,0))
.stream()
.map(CertificateInstanceEntity::getSubjectId)
.collect(Collectors.toSet());
if(CollectionUtils.isNotEmpty(orgIds)){
List<BaseStoreVO> baseStoreVOS = storeApi.listBaseInfoByIds(new StoreBaseInfoIdsQueryDTO(orgIds,UserProvider.getUser().getTenantId()));
String orgNames = baseStoreVOS.stream().map(BaseStoreVO::getStoreName).collect(Collectors.joining(","));
return ActionResult.fail(400,"已被["+orgNames+"]门店使用,不能删除!");
}
boolean succ = storeCertificatePhotoService.update(new LambdaUpdateWrapper<StoreCertificatePhotoEntity>()
.eq(StoreCertificatePhotoEntity::getId,id)
.set(StoreCertificatePhotoEntity::getEnabledMark,1)
.set(StoreCertificatePhotoEntity::getLastModifyUserId, UserProvider.getLoginUserId())
.set(StoreCertificatePhotoEntity::getLastModifyTime,new Date()));
return ActionResult.success();
}
/**
* 查询门店自定义证件配置列表
*
* @return 门店证件照列表
*/
@GetMapping("/query-list")
public ActionResult<List<StoreCertificatePhotoVO>> queryList() {
return ActionResult.success(storeCertificatePhotoService.queryList());
}
/**
* 根据ID查询门店自定义证件配置照详情
*
* @param id 主键ID
* @return 门店证件照详情
*/
@GetMapping("/query-info/{id}")
public ActionResult<StoreCertificatePhotoVO> queryInfo(@PathVariable("id") @NotBlank(message = "主键ID不能为空") String id) {
return ActionResult.success(storeCertificatePhotoService.queryInfo(id));
}
}

View File

@@ -0,0 +1,109 @@
package jnpf.storecertificatephoto.controller;
import jnpf.base.ActionResult;
import jnpf.model.certificate.vo.CertificateTypeOptionVO;
import jnpf.model.storecertificatephoto.vo.StoreCertificatePhotoIdNameVO;
import jnpf.model.warningnotice.enums.CertificateTypeEnum;
import jnpf.model.warningnotice.req.WarningNoticeSaveReq;
import jnpf.model.warningnotice.vo.WarningNoticeVO;
import jnpf.storecertificatephoto.service.StoreCertificatePhotoService;
import jnpf.storecertificatephoto.service.WarningNoticeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import java.util.ArrayList;
import java.util.List;
/**
* web预警通知控制器
*/
@RestController
@Validated
@RequestMapping("/web/warning-notice")
public class WarningNoticeController {
@Autowired
private WarningNoticeService warningNoticeService;
@Autowired
private StoreCertificatePhotoService storeCertificatePhotoService;
/**
* 保存预警通知配置
*
* @param req 保存请求参数
* @return 预警通知配置
*/
@PostMapping("/save")
public ActionResult<WarningNoticeVO> save(@Validated @RequestBody WarningNoticeSaveReq req) {
return ActionResult.success("保存成功", warningNoticeService.save(req));
}
/**
* 批量保存预警通知配置
*
* @param reqList 批量保存请求参数
* @return 预警通知配置列表
*/
@PostMapping("/saveList")
public ActionResult<List<WarningNoticeVO>> saveList(
@RequestBody @NotEmpty(message = "保存参数不能为空") List<@Valid WarningNoticeSaveReq> reqList) {
return ActionResult.success("保存成功", warningNoticeService.saveList(reqList));
}
/**
* 根据证照类型查询预警通知配置
*
* @param type 证照类型
* @return 预警通知配置
*/
@GetMapping("/query-by-type")
public ActionResult<WarningNoticeVO> queryByType(@RequestParam("type") @NotBlank(message = "证照类型或者模板id不能为空!") String type) {
return ActionResult.success("获取成功", warningNoticeService.queryByType(type));
}
/**
* 查询所有预警通知配置
*
* @return 预警通知配置列表
*/
@GetMapping("/query-all")
public ActionResult<List<WarningNoticeVO>> queryAll() {
return ActionResult.success("获取成功", warningNoticeService.queryAll());
}
/**
* 查询证照类型选项。
*
* @return 证照类型选项
*/
@GetMapping("/query-certificate-type-list")
public ActionResult<List<CertificateTypeOptionVO>> queryCertificateTypeList() {
List<StoreCertificatePhotoIdNameVO> customerCertificateList = storeCertificatePhotoService.queryIdNameList();
List<CertificateTypeOptionVO> result = new ArrayList<>(CertificateTypeEnum.values().length + customerCertificateList.size());
for (CertificateTypeEnum type : CertificateTypeEnum.values()){
CertificateTypeOptionVO option = new CertificateTypeOptionVO();
option.setLabel(type.getLabel());
option.setKey(type.getType());
option.setType(type.getType());
result.add(option);
}
for (StoreCertificatePhotoIdNameVO customerCertificate : customerCertificateList){
CertificateTypeOptionVO option = new CertificateTypeOptionVO();
option.setLabel(customerCertificate.getCertificateName());
option.setKey(customerCertificate.getId());
option.setType(CertificateTypeEnum.STORE_CUSTOM_CERTIFICATE.getType());
result.add(option);
}
return ActionResult.success(result);
}
}

View File

@@ -0,0 +1,51 @@
package jnpf.storecertificatephoto.helper;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import jnpf.model.storecertificatephoto.po.StoreCertificatePhotoEntity;
import jnpf.storecertificatephoto.mapper.StoreCertificatePhotoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
@Component
public class StoreCertificatePhotoHelper {
@Autowired
private StoreCertificatePhotoMapper storeCertificatePhotoMapper;
public Map<String,String> buildStoreCertificateIdAndNames(Collection<String> templateIds,Integer status){
if(CollectionUtil.isEmpty(templateIds)){
return Collections.emptyMap();
}
LambdaQueryWrapper<StoreCertificatePhotoEntity> queryWrapper = new LambdaQueryWrapper<StoreCertificatePhotoEntity>()
.select(StoreCertificatePhotoEntity::getId, StoreCertificatePhotoEntity::getCertificateName)
.in(StoreCertificatePhotoEntity::getId, templateIds);
if(Objects.nonNull(status)){
queryWrapper.eq(StoreCertificatePhotoEntity::getStatus, status);
}
return storeCertificatePhotoMapper.selectList(queryWrapper)
.stream()
.collect(Collectors.toMap(StoreCertificatePhotoEntity::getId, StoreCertificatePhotoEntity::getCertificateName));
}
public Map<String,StoreCertificatePhotoEntity> buildStoreCertificateById(Collection<String> templateIds,Integer status){
if(CollectionUtil.isEmpty(templateIds)){
return Collections.emptyMap();
}
LambdaQueryWrapper<StoreCertificatePhotoEntity> queryWrapper = new LambdaQueryWrapper<StoreCertificatePhotoEntity>()
.in(StoreCertificatePhotoEntity::getId, templateIds);
if(Objects.nonNull(status)){
queryWrapper.eq(StoreCertificatePhotoEntity::getStatus, status);
}
return storeCertificatePhotoMapper.selectList(queryWrapper)
.stream()
.collect(Collectors.toMap(StoreCertificatePhotoEntity::getId, v->v));
}
}

View File

@@ -0,0 +1,81 @@
package jnpf.storecertificatephoto.mapper;
import jnpf.base.mapper.SuperMapper;
import jnpf.model.warningnotice.po.FtbParamEntity;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 基础参数Mapper
*/
public interface BaseParamMapper extends SuperMapper<FtbParamEntity> {
/**
* 根据参数键查询参数
*
* @param key 参数键
* @return 基础参数
*/
FtbParamEntity selectByKey(@Param("key") String key);
/**
* 根据参数键更新参数
*
* @param sourceKey 原参数键
* @param entity 基础参数
* @return 更新条数
*/
int updateByKey(@Param("sourceKey") String sourceKey, @Param("entity") FtbParamEntity entity);
/**
* 新增参数
*
* @param entity 基础参数
* @return 新增条数
*/
int insertParam(@Param("entity") FtbParamEntity entity);
/**
* 批量新增参数
*
* @param entities 参数列表
* @return 新增条数
*/
int batchInsertParam(@Param("entities") List<FtbParamEntity> entities);
/**
* 批量按ID更新参数
*
* @param entities 参数列表
* @return 更新条数
*/
int batchUpdateById(@Param("entities") List<FtbParamEntity> entities);
/**
* 根据key递增参数值
*
* @param key 参数key
* @param number 递增值
* @return 影响行数
*/
int incrementValueByKey(@Param("key") String key, @Param("number") Long number);
/**
* 根据key删除参数
*
* @param key 参数key
* @return 影响行数
*/
int deleteByKey(@Param("key") String key);
/**
* 根据type删除参数。not key
*
* @param type 参数type
* @param notKey 不包含的key
* @return 影响行数
*/
int deleteByTypeNotKey(@Param("type") String type,@Param("notKey") String notKey);
}

View File

@@ -0,0 +1,11 @@
package jnpf.storecertificatephoto.mapper;
import jnpf.base.mapper.SuperMapper;
import jnpf.model.storecertificatephoto.po.StoreCertificatePhotoItemEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* 门店证件照子项Mapper
*/
public interface StoreCertificatePhotoItemMapper extends SuperMapper<StoreCertificatePhotoItemEntity> {
}

View File

@@ -0,0 +1,11 @@
package jnpf.storecertificatephoto.mapper;
import jnpf.base.mapper.SuperMapper;
import jnpf.model.storecertificatephoto.po.StoreCertificatePhotoEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* 门店证件照Mapper
*/
public interface StoreCertificatePhotoMapper extends SuperMapper<StoreCertificatePhotoEntity> {
}

View File

@@ -0,0 +1,52 @@
package jnpf.storecertificatephoto.service;
import com.baomidou.mybatisplus.extension.service.IService;
import jnpf.model.storecertificatephoto.po.StoreCertificatePhotoEntity;
import jnpf.model.storecertificatephoto.req.StoreCertificatePhotoAddReq;
import jnpf.model.storecertificatephoto.req.StoreCertificatePhotoUpdateReq;
import jnpf.model.storecertificatephoto.vo.StoreCertificatePhotoIdNameVO;
import jnpf.model.storecertificatephoto.vo.StoreCertificatePhotoVO;
import java.util.List;
/**
* 门店证件照服务
*/
public interface StoreCertificatePhotoService extends IService<StoreCertificatePhotoEntity> {
/**
* 新增门店证件照配置
*
* @param req 门店证件照请求参数
*/
void add(StoreCertificatePhotoAddReq req);
/**
* 编辑门店证件照配置
*
* @param req 门店证件照编辑参数
*/
void update(StoreCertificatePhotoUpdateReq req);
/**
* 查询门店证件照列表
*
* @return 门店证件照列表
*/
List<StoreCertificatePhotoVO> queryList();
/**
* 查询门店证件照ID和名称列表
*
* @return 门店证件照ID和名称列表
*/
List<StoreCertificatePhotoIdNameVO> queryIdNameList();
/**
* 根据ID查询门店证件照详情
*
* @param id 主键ID
* @return 门店证件照详情
*/
StoreCertificatePhotoVO queryInfo(String id);
}

View File

@@ -0,0 +1,43 @@
package jnpf.storecertificatephoto.service;
import jnpf.model.warningnotice.req.WarningNoticeSaveReq;
import jnpf.model.warningnotice.vo.WarningNoticeVO;
import java.util.List;
/**
* 预警通知服务
*/
public interface WarningNoticeService {
/**
* 保存预警通知配置
*
* @param req 保存请求参数
* @return 预警通知配置
*/
WarningNoticeVO save(WarningNoticeSaveReq req);
/**
* 批量保存预警通知配置
*
* @param reqList 批量保存请求参数
* @return 预警通知配置列表
*/
List<WarningNoticeVO> saveList(List<WarningNoticeSaveReq> reqList);
/**
* 根据证照类型查询预警通知配置
*
* @param type 证照类型
* @return 预警通知配置
*/
WarningNoticeVO queryByType(String type);
/**
* 查询所有预警通知配置
*
* @return 预警通知配置
*/
List<WarningNoticeVO> queryAll();
}

View File

@@ -0,0 +1,518 @@
package jnpf.storecertificatephoto.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jnpf.certificate.mapper.CertificateInstanceItemMapper;
import jnpf.certificate.mapper.CertificateInstanceMapper;
import jnpf.model.certificate.po.CertificateInstanceEntity;
import jnpf.model.certificate.po.CertificateInstanceItemEntity;
import jnpf.model.storecertificatephoto.po.StoreCertificatePhotoEntity;
import jnpf.model.storecertificatephoto.po.StoreCertificatePhotoItemEntity;
import jnpf.model.storecertificatephoto.req.StoreCertificatePhotoAddReq;
import jnpf.model.storecertificatephoto.req.StoreCertificatePhotoItemReq;
import jnpf.model.storecertificatephoto.req.StoreCertificatePhotoUpdateReq;
import jnpf.model.storecertificatephoto.vo.StoreCertificatePhotoIdNameVO;
import jnpf.model.storecertificatephoto.vo.StoreCertificatePhotoItemVO;
import jnpf.model.storecertificatephoto.vo.StoreCertificatePhotoVO;
import jnpf.model.warningnotice.enums.CertificateTypeEnum;
import jnpf.storecertificatephoto.mapper.StoreCertificatePhotoItemMapper;
import jnpf.storecertificatephoto.mapper.StoreCertificatePhotoMapper;
import jnpf.storecertificatephoto.service.StoreCertificatePhotoService;
import jnpf.util.UserProvider;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 门店证件照服务实现
*/
@Service
public class StoreCertificatePhotoServiceImpl extends ServiceImpl<StoreCertificatePhotoMapper, StoreCertificatePhotoEntity>
implements StoreCertificatePhotoService {
private static final int ITEM_TYPE_IMAGE = 1;
private static final int ITEM_TYPE_TEXT = 2;
@Autowired
private StoreCertificatePhotoItemMapper storeCertificatePhotoItemMapper;
@Autowired
private CertificateInstanceItemMapper certificateInstanceItemMapper;
@Autowired
private CertificateInstanceMapper certificateInstanceMapper;
@Autowired
private RedissonClient redissonClient;
private static final String lockPrefix = "certificate:customer:add:";
/**
* 新增门店证件照配置
*
* @param req 门店证件照请求参数
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void add(StoreCertificatePhotoAddReq req) {
validateAddReq(req);
String certificateName = req.getCertificateName().trim();
if (existsCertificateName(certificateName, null)) {
throw new RuntimeException("证照名称已存在");
}
String lockKey = buildCertificateCustomerAddLock();
RLock lock = redissonClient.getLock(lockKey);
try {
lock.lock(10, TimeUnit.SECONDS);
Long count = baseMapper.selectCount(new LambdaQueryWrapper<StoreCertificatePhotoEntity>()
.eq(StoreCertificatePhotoEntity::getEnabledMark,0));
if(Objects.nonNull(count) && count >= 15){
throw new RuntimeException("超出15个证照无法添加!");
}
}finally {
lock.unlock();
}
StoreCertificatePhotoEntity entity = req.convert();
insertPhotoEntity(entity);
List<StoreCertificatePhotoItemEntity> items = new ArrayList<>();
items.addAll(buildItems(entity.getId(), req.getImageItemList(), ITEM_TYPE_IMAGE));
items.addAll(buildItems(entity.getId(), req.getTextItemList(), ITEM_TYPE_TEXT));
for (StoreCertificatePhotoItemEntity item : items) {
storeCertificatePhotoItemMapper.insert(item);
}
}
private String buildCertificateCustomerAddLock() {
return lockPrefix+UserProvider.getUser().getTenantId();
}
/**
* 编辑门店证件照配置
*
* @param req 门店证件照编辑参数
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void update(StoreCertificatePhotoUpdateReq req) {
validateAddReq(req);
String id = req.getId().trim();
StoreCertificatePhotoEntity dbEntity = getEntityById(id);
// 先查询当前模板项,再与传入项对比,识别本次删除项。
List<StoreCertificatePhotoItemEntity> originItems = queryCurrentItems(id);
List<String> deletedItemIds = collectDeletedItemIds(originItems, req);
// 删除前先校验是否已被证照实例使用。
validateDeleteItemReference(deletedItemIds);
// 未被使用的项执行逻辑删除。
deleteItemsByIds(deletedItemIds);
String certificateName = req.getCertificateName().trim();
if (existsCertificateName(certificateName, id)) {
throw new RuntimeException("证照名称已存在");
}
StoreCertificatePhotoEntity entity = req.convert();
entity.setId(dbEntity.getId());
entity.setEnabledMark(dbEntity.getEnabledMark());
updatePhotoEntity(entity);
if(!dbEntity.getStatus().equals(entity.getStatus())){
certificateInstanceMapper.update(null,new LambdaUpdateWrapper<CertificateInstanceEntity>()
.eq(CertificateInstanceEntity::getTemplateId, req.getId())
.eq(CertificateInstanceEntity::getCertificateType, CertificateTypeEnum.STORE_CUSTOM_CERTIFICATE.getType())
.eq(CertificateInstanceEntity::getEnabledMark, 0)
.set(CertificateInstanceEntity::getTemplateStatus, req.getStatus())
.set(CertificateInstanceEntity::getLastModifyUserId, UserProvider.getLoginUserId())
.set(CertificateInstanceEntity::getLastModifyTime, new Date()));
}
upsertItems(id, req.getImageItemList(), ITEM_TYPE_IMAGE);
upsertItems(id, req.getTextItemList(), ITEM_TYPE_TEXT);
}
/**
* 查询门店证件照列表
*
* @return 门店证件照列表
*/
@Override
public List<StoreCertificatePhotoVO> queryList() {
LambdaQueryWrapper<StoreCertificatePhotoEntity> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.eq(StoreCertificatePhotoEntity::getEnabledMark, 0);
queryWrapper.orderByDesc(StoreCertificatePhotoEntity::getCreatorTime);
List<StoreCertificatePhotoEntity> entityList = baseMapper.selectList(queryWrapper);
if (CollUtil.isEmpty(entityList)) {
return Collections.emptyList();
}
Map<String, List<StoreCertificatePhotoItemEntity>> itemMap = queryItemMap(entityList.stream()
.map(StoreCertificatePhotoEntity::getId)
.collect(Collectors.toList()));
return entityList.stream().map(entity -> buildVO(entity, itemMap)).collect(Collectors.toList());
}
@Override
public List<StoreCertificatePhotoIdNameVO> queryIdNameList() {
LambdaQueryWrapper<StoreCertificatePhotoEntity> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.select(StoreCertificatePhotoEntity::getId, StoreCertificatePhotoEntity::getCertificateName);
queryWrapper.eq(StoreCertificatePhotoEntity::getEnabledMark, 0);
queryWrapper.orderByDesc(StoreCertificatePhotoEntity::getCreatorTime);
return baseMapper.selectList(queryWrapper).stream()
.map(StoreCertificatePhotoIdNameVO::convert)
.collect(Collectors.toList());
}
/**
* 根据ID查询门店证件照详情
*
* @param id 主键ID
* @return 门店证件照详情
*/
@Override
public StoreCertificatePhotoVO queryInfo(String id) {
String photoId = id.trim();
StoreCertificatePhotoEntity entity = getEntityById(photoId);
Map<String, List<StoreCertificatePhotoItemEntity>> itemMap = queryItemMap(Collections.singletonList(photoId));
return buildVO(entity, itemMap);
}
/**
* 校验新增参数
*
* @param req 门店证件照请求参数
*/
private void validateAddReq(StoreCertificatePhotoAddReq req) {
if (req.getImageCount() == null || req.getImageCount() < 0) {
throw new RuntimeException("证照图片数量必须大于等于0");
}
if (req.getImageCount() != req.getImageItemList().size()) {
throw new RuntimeException("证照图片数量必须与图片名称数量一致");
}
if (Objects.nonNull(req.getExpireDateEnabled()) &&
1== req.getExpireDateEnabled()&&
StrUtil.isBlank(req.getExpireDateName())) {
throw new RuntimeException("到期日期名称不能为空");
}
validateItemNames(req.getImageItemList(), "图片名称");
validateItemNames(req.getTextItemList(), "文本名称");
validateItemIds(req.getImageItemList(), req.getTextItemList());
}
/**
* 校验子项名称
*
* @param itemList 子项列表
* @param label 提示文案
*/
private void validateItemNames(List<StoreCertificatePhotoItemReq> itemList, String label) {
Set<String> nameSet = new LinkedHashSet<>();
for (StoreCertificatePhotoItemReq item : itemList) {
if (item == null || StrUtil.isBlank(item.getItemName())) {
throw new RuntimeException(label + "不能为空");
}
if (!nameSet.add(item.getItemName().trim())) {
throw new RuntimeException(label + "不能重复");
}
}
}
/**
* 校验更新请求中子项ID不能重复
*
* @param imageItemList 图片子项列表
* @param textItemList 文本子项列表
*/
private void validateItemIds(List<StoreCertificatePhotoItemReq> imageItemList,
List<StoreCertificatePhotoItemReq> textItemList) {
Set<String> itemIdSet = new LinkedHashSet<>();
List<StoreCertificatePhotoItemReq> allItems = new ArrayList<>();
if (CollUtil.isNotEmpty(imageItemList)) {
allItems.addAll(imageItemList);
}
if (CollUtil.isNotEmpty(textItemList)) {
allItems.addAll(textItemList);
}
for (StoreCertificatePhotoItemReq itemReq : allItems) {
if (itemReq == null || StrUtil.isBlank(itemReq.getId())) {
continue;
}
String itemId = StrUtil.trim(itemReq.getId());
if (!itemIdSet.add(itemId)) {
throw new RuntimeException("子项ID不能重复");
}
}
}
/**
* 构建门店证件照子项
*
* @param photoId 门店证件照ID
* @param itemReqList 子项请求列表
* @param itemType 子项类型
* @return 子项实体列表
*/
private List<StoreCertificatePhotoItemEntity> buildItems(String photoId,
List<StoreCertificatePhotoItemReq> itemReqList,
Integer itemType) {
List<StoreCertificatePhotoItemEntity> itemList = new ArrayList<>();
for (int i = 0; i < itemReqList.size(); i++) {
StoreCertificatePhotoItemReq itemReq = itemReqList.get(i);
StoreCertificatePhotoItemEntity itemEntity = new StoreCertificatePhotoItemEntity();
itemEntity.setId(StrUtil.isNotBlank(itemReq.getId()) ? StrUtil.trim(itemReq.getId()) : null);
itemEntity.setPhotoId(photoId);
itemEntity.setItemType(itemType);
itemEntity.setItemName(itemReq.getItemName().trim());
itemEntity.setTextPrompt(ITEM_TYPE_TEXT == itemType && StrUtil.isNotBlank(itemReq.getTextPrompt())
? itemReq.getTextPrompt().trim()
: null);
itemEntity.setSorts((long) i + 1);
itemEntity.setEnabledMark(0);
itemList.add(itemEntity);
}
return itemList;
}
/**
* 更新时按子项ID进行增改
*
* @param photoId 门店证件照ID
* @param itemReqList 子项请求列表
* @param itemType 子项类型
*/
private void upsertItems(String photoId, List<StoreCertificatePhotoItemReq> itemReqList, Integer itemType) {
if (CollUtil.isEmpty(itemReqList)) {
return;
}
List<String> itemIds = itemReqList.stream()
.filter(item -> item != null && StrUtil.isNotBlank(item.getId()))
.map(item -> StrUtil.trim(item.getId()))
.distinct()
.collect(Collectors.toList());
Map<String, StoreCertificatePhotoItemEntity> dbItemMap = new HashMap<>();
if (CollUtil.isNotEmpty(itemIds)) {
LambdaQueryWrapper<StoreCertificatePhotoItemEntity> itemWrapper = Wrappers.lambdaQuery();
itemWrapper.in(StoreCertificatePhotoItemEntity::getId, itemIds);
itemWrapper.eq(StoreCertificatePhotoItemEntity::getPhotoId, photoId);
itemWrapper.eq(StoreCertificatePhotoItemEntity::getEnabledMark, 0);
List<StoreCertificatePhotoItemEntity> dbItems = storeCertificatePhotoItemMapper.selectList(itemWrapper);
dbItemMap = dbItems.stream()
.collect(Collectors.toMap(StoreCertificatePhotoItemEntity::getId, item -> item, (a, b) -> a));
}
List<StoreCertificatePhotoItemEntity> items = buildItems(photoId, itemReqList, itemType);
for (StoreCertificatePhotoItemEntity item : items) {
if (StrUtil.isBlank(item.getId())) {
storeCertificatePhotoItemMapper.insert(item);
continue;
}
StoreCertificatePhotoItemEntity dbItem = dbItemMap.get(item.getId());
if (dbItem == null) {
throw new RuntimeException("子项ID不存在或不属于当前配置");
}
item.setItemType(itemType);
item.setEnabledMark(dbItem.getEnabledMark());
storeCertificatePhotoItemMapper.updateById(item);
}
}
/**
* 判断证照名称是否已存在
*
* @param certificateName 证照名称
* @param excludeId 排除的主键ID
* @return 是否存在
*/
private boolean existsCertificateName(String certificateName, String excludeId) {
LambdaQueryWrapper<StoreCertificatePhotoEntity> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.eq(StoreCertificatePhotoEntity::getCertificateName, certificateName);
queryWrapper.eq(StoreCertificatePhotoEntity::getEnabledMark, 0);
queryWrapper.ne(StrUtil.isNotBlank(excludeId), StoreCertificatePhotoEntity::getId, excludeId);
return baseMapper.selectCount(queryWrapper) > 0;
}
/**
* 查询当前模板下所有有效项。
*
* @param photoId 模板ID
* @return 当前模板项列表
*/
private List<StoreCertificatePhotoItemEntity> queryCurrentItems(String photoId) {
LambdaQueryWrapper<StoreCertificatePhotoItemEntity> wrapper = Wrappers.lambdaQuery();
wrapper.eq(StoreCertificatePhotoItemEntity::getPhotoId, photoId);
wrapper.eq(StoreCertificatePhotoItemEntity::getEnabledMark, 0);
return storeCertificatePhotoItemMapper.selectList(wrapper);
}
/**
* 对比原有模板项与本次请求项得到被删除的模板项ID。
*
* @param originItems 原有模板项
* @param req 编辑请求
* @return 待删除模板项ID列表
*/
private List<String> collectDeletedItemIds(List<StoreCertificatePhotoItemEntity> originItems,
StoreCertificatePhotoUpdateReq req) {
if (CollUtil.isEmpty(originItems)) {
return Collections.emptyList();
}
Set<String> currentItemIds = new LinkedHashSet<>();
appendReqItemIds(currentItemIds, req.getImageItemList());
appendReqItemIds(currentItemIds, req.getTextItemList());
return originItems.stream()
.map(StoreCertificatePhotoItemEntity::getId)
.filter(StrUtil::isNotBlank)
.map(StrUtil::trim)
.filter(itemId -> !currentItemIds.contains(itemId))
.collect(Collectors.toList());
}
/**
* 收集请求中的子项ID仅收集更新场景中有值的itemId
*
* @param targetSet 目标ID集合
* @param itemReqList 请求项列表
*/
private void appendReqItemIds(Set<String> targetSet, List<StoreCertificatePhotoItemReq> itemReqList) {
if (CollUtil.isEmpty(itemReqList)) {
return;
}
for (StoreCertificatePhotoItemReq itemReq : itemReqList) {
if (itemReq == null || StrUtil.isBlank(itemReq.getId())) {
continue;
}
targetSet.add(StrUtil.trim(itemReq.getId()));
}
}
/**
* 校验待删除模板项是否已被证照实例使用。
*
* @param deletedItemIds 待删除模板项ID列表
*/
private void validateDeleteItemReference(List<String> deletedItemIds) {
if (CollUtil.isEmpty(deletedItemIds)) {
return;
}
LambdaQueryWrapper<CertificateInstanceItemEntity> wrapper = Wrappers.lambdaQuery();
wrapper.in(CertificateInstanceItemEntity::getTemplateItemId, deletedItemIds);
wrapper.eq(CertificateInstanceItemEntity::getEnabledMark, 0);
Long count = certificateInstanceItemMapper.selectCount(wrapper);
if (count != null && count > 0) {
throw new RuntimeException("该字段已经被使用,无法删除");
}
}
/**
* 执行模板项逻辑删除。
*
* @param deletedItemIds 待删除模板项ID列表
*/
private void deleteItemsByIds(List<String> deletedItemIds) {
if (CollUtil.isEmpty(deletedItemIds)) {
return;
}
for (String itemId : deletedItemIds) {
StoreCertificatePhotoItemEntity item = new StoreCertificatePhotoItemEntity();
item.setId(itemId);
item.setEnabledMark(1);
storeCertificatePhotoItemMapper.updateById(item);
}
}
/**
* 根据ID查询有效的门店证件照配置。
*
* @param id 主键ID
* @return 门店证件照实体
*/
private StoreCertificatePhotoEntity getEntityById(String id) {
StoreCertificatePhotoEntity entity = baseMapper.selectById(id);
if (entity == null || !Integer.valueOf(0).equals(entity.getEnabledMark())) {
throw new RuntimeException("门店证件照配置不存在");
}
return entity;
}
/**
* 查询子项映射
*
* @param photoIds 门店证件照ID列表
* @return 子项映射
*/
private Map<String, List<StoreCertificatePhotoItemEntity>> queryItemMap(List<String> photoIds) {
if (CollUtil.isEmpty(photoIds)) {
return Collections.emptyMap();
}
LambdaQueryWrapper<StoreCertificatePhotoItemEntity> itemWrapper = Wrappers.lambdaQuery();
itemWrapper.in(StoreCertificatePhotoItemEntity::getPhotoId, photoIds);
itemWrapper.eq(StoreCertificatePhotoItemEntity::getEnabledMark, 0);
itemWrapper.orderByAsc(StoreCertificatePhotoItemEntity::getPhotoId)
.orderByAsc(StoreCertificatePhotoItemEntity::getItemType)
.orderByAsc(StoreCertificatePhotoItemEntity::getSorts);
return storeCertificatePhotoItemMapper.selectList(itemWrapper).stream()
.collect(Collectors.groupingBy(StoreCertificatePhotoItemEntity::getPhotoId));
}
/**
* 构建门店证件照返回对象
*
* @param entity 门店证件照实体
* @param itemMap 子项映射
* @return 门店证件照返回对象
*/
private StoreCertificatePhotoVO buildVO(StoreCertificatePhotoEntity entity,
Map<String, List<StoreCertificatePhotoItemEntity>> itemMap) {
StoreCertificatePhotoVO vo = StoreCertificatePhotoVO.convert(entity);
List<StoreCertificatePhotoItemEntity> currentItems = itemMap.getOrDefault(entity.getId(), Collections.emptyList());
vo.setImageItemList(currentItems.stream()
.filter(item -> ITEM_TYPE_IMAGE == item.getItemType())
.map(StoreCertificatePhotoItemVO::convert)
.collect(Collectors.toList()));
vo.setTextItemList(currentItems.stream()
.filter(item -> ITEM_TYPE_TEXT == item.getItemType())
.map(StoreCertificatePhotoItemVO::convert)
.collect(Collectors.toList()));
return vo;
}
/**
* 新增门店证件照主表数据
*
* @param entity 门店证件照实体
*/
private void insertPhotoEntity(StoreCertificatePhotoEntity entity) {
try {
baseMapper.insert(entity);
} catch (DuplicateKeyException e) {
throw new RuntimeException("证照名称已存在");
}
}
/**
* 更新门店证件照主表数据
*
* @param entity 门店证件照实体
*/
private void updatePhotoEntity(StoreCertificatePhotoEntity entity) {
try {
baseMapper.updateById(entity);
} catch (DuplicateKeyException e) {
throw new RuntimeException("证照名称已存在");
}
}
}

View File

@@ -0,0 +1,641 @@
package jnpf.storecertificatephoto.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import jnpf.model.storecertificatephoto.po.StoreCertificatePhotoEntity;
import jnpf.model.warningnotice.enums.CertificateTypeEnum;
import jnpf.model.warningnotice.po.FtbParamEntity;
import jnpf.model.warningnotice.req.WarningNoticeSaveReq;
import jnpf.model.warningnotice.req.WarningNoticeTargetReq;
import jnpf.model.warningnotice.req.WarningNoticeUserConfigReq;
import jnpf.model.warningnotice.vo.WarningNoticeTargetVO;
import jnpf.model.warningnotice.vo.WarningNoticeUserConfigVO;
import jnpf.model.warningnotice.vo.WarningNoticeVO;
import jnpf.storecertificatephoto.helper.StoreCertificatePhotoHelper;
import jnpf.storecertificatephoto.mapper.BaseParamMapper;
import jnpf.storecertificatephoto.service.WarningNoticeService;
import jnpf.util.FtbUtil;
import jnpf.util.JsonUtil;
import jnpf.util.ServiceException;
import jnpf.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate;
import java.util.*;
import java.util.stream.Collectors;
/**
* 预警通知服务实现
*/
@Service
public class WarningNoticeServiceImpl implements WarningNoticeService {
private static final String CONFIG_KEY_PREFIX = "ftbWarningNotice_";
/**
* 预警配置类型
*/
public static final String certificateWarnNoticeType = "certificateWarnNoticeType";
private static final Long DEFAULT_SORT = 0L;
@Autowired
private BaseParamMapper baseParamMapper;
@Autowired
private PlatformTransactionManager transactionManager;
@Autowired
private StoreCertificatePhotoHelper storeCertificatePhotoHelper;
/**
* 保存预警通知配置
*
* @param req 保存请求参数
* @return 预警通知配置
*/
@Override
public WarningNoticeVO save(WarningNoticeSaveReq req) {
List<WarningNoticeVO> result = saveList(Collections.singletonList(req));
ServiceException.isTrue(CollUtil.isNotEmpty(result), "保存预警配置失败");
return result.get(0);
}
/**
* 批量保存预警通知配置
*
* @param reqList 批量保存请求参数
* @return 预警通知配置列表
*/
@Override
public List<WarningNoticeVO> saveList(List<WarningNoticeSaveReq> reqList) {
ServiceException.isTrue(CollUtil.isNotEmpty(reqList), "保存参数不能为空");
List<WarningNoticePreparedData> preparedList = prepareSaveDataList(reqList);
validateBatchUniqueness(preparedList);
BatchParamSplit split = splitById(preparedList);
executeBatch(split);
// 批量更新/插入后按key回查最新数据保证返回包含数据库最终ID
List<String> keys = preparedList.stream()
.map(item -> item.getEntity().getKey())
.collect(Collectors.toList());
List<WarningNoticeVO> result = queryByKeys(keys);
ServiceException.isTrue(result.size() == keys.size(), "保存预警配置失败");
return result;
}
/**
* 根据证照类型查询预警通知配置
*
* @param type 证照类型
* @return 预警通知配置
*/
@Override
public WarningNoticeVO queryByType(String type) {
String key = buildConfigKey(type);
FtbParamEntity entity = baseParamMapper.selectByKey(key);
if (entity == null || StrUtil.isBlank(entity.getValue())) {
return null;
}
WarningNoticeVO warningNoticeVO = buildWarningNoticeVO(entity);
return normalizeWarningNoticeVO(warningNoticeVO);
}
private WarningNoticeVO buildWarningNoticeVO(FtbParamEntity entity) {
WarningNoticeVO warningNoticeVO = JsonUtil.getJsonToBean(entity.getValue(), WarningNoticeVO.class);
ServiceException.notNull(warningNoticeVO, "预警通知配置数据异常");
warningNoticeVO.setKey(entity.getKey());
String certificateType = buildCertificateTypeOrTemplateId(entity.getKey());
Optional<CertificateTypeEnum> certificateTypeEnumOptional = CertificateTypeEnum.getByType(certificateType);
ServiceException.isTrue(certificateTypeEnumOptional.isPresent(), "预警通知配置数据异常.证照类型异常");
CertificateTypeEnum certificateTypeEnum = certificateTypeEnumOptional.get();
warningNoticeVO.setType(certificateTypeEnum.getType());
warningNoticeVO.setTypeOrTemplateId(certificateType);
warningNoticeVO.setId(entity.getId());
return warningNoticeVO;
}
@Override
public List<WarningNoticeVO> queryAll() {
List<FtbParamEntity> ftbParamEntities = baseParamMapper.selectList(new LambdaQueryWrapper<FtbParamEntity>().eq(FtbParamEntity::getType, certificateWarnNoticeType));
if(CollectionUtil.isEmpty(ftbParamEntities)){
return Collections.emptyList();
}
return ftbParamEntities.stream()
.map(this::buildWarningNoticeVO)
.collect(Collectors.toList());
}
/**
* 构建基础参数实体
*
* @param saveData 预警通知配置
* @param reqId 请求中的ID
* @return 基础参数实体
*/
private FtbParamEntity buildBaseParamEntity(WarningNoticeVO saveData, String reqId) {
FtbParamEntity entity = new FtbParamEntity();
entity.setId(StrUtil.isNotBlank(reqId) ? normalizeText(reqId) : FtbUtil.getId());
entity.setKey(saveData.getKey());
entity.setType(certificateWarnNoticeType);
entity.setValue(JsonUtil.getObjectToString(saveData));
entity.setSort(DEFAULT_SORT);
return entity;
}
/**
* 构建保存对象
*
* @param req 保存请求参数
* @param certificateType 证照类型
* @param key 参数键
* @return 预警通知配置
*/
private WarningNoticeVO buildSaveData(WarningNoticeSaveReq req, CertificateTypeEnum certificateType, String key) {
WarningNoticeVO warningNoticeVO = new WarningNoticeVO();
warningNoticeVO.setKey(key);
warningNoticeVO.setType(certificateType.getType());
warningNoticeVO.setExpiryReminderDays(req.getExpiryReminderDays());
warningNoticeVO.setNoticeFrequencyDays(req.getNoticeFrequencyDays());
List<WarningNoticeUserConfigVO> noticeConfigList = new ArrayList<>();
for (WarningNoticeUserConfigReq configReq : req.getNoticeConfigList()) {
WarningNoticeUserConfigVO configVO = new WarningNoticeUserConfigVO();
configVO.setNoticeUserType(normalizeText(configReq.getNoticeUserType()));
List<WarningNoticeTargetVO> noticeUserList = new ArrayList<>();
for (WarningNoticeTargetReq targetReq : configReq.getNoticeUserList()) {
WarningNoticeTargetVO targetVO = new WarningNoticeTargetVO();
targetVO.setId(normalizeText(targetReq.getId()));
targetVO.setName(normalizeText(targetReq.getName()));
noticeUserList.add(targetVO);
}
configVO.setNoticeUserList(noticeUserList);
noticeConfigList.add(configVO);
}
warningNoticeVO.setNoticeConfigList(noticeConfigList);
return warningNoticeVO;
}
/**
* 规范化返回对象
*
* @param warningNoticeVO 预警通知配置
* @return 预警通知配置
*/
private WarningNoticeVO normalizeWarningNoticeVO(WarningNoticeVO warningNoticeVO) {
warningNoticeVO.setId(normalizeText(warningNoticeVO.getId()));
warningNoticeVO.setKey(normalizeText(warningNoticeVO.getKey()));
warningNoticeVO.setType(normalizeText(warningNoticeVO.getType()));
if (warningNoticeVO.getNoticeConfigList() == null) {
warningNoticeVO.setNoticeConfigList(new ArrayList<>());
return warningNoticeVO;
}
for (WarningNoticeUserConfigVO configVO : warningNoticeVO.getNoticeConfigList()) {
configVO.setNoticeUserType(normalizeText(configVO.getNoticeUserType()));
if (configVO.getNoticeUserList() == null) {
configVO.setNoticeUserList(new ArrayList<>());
continue;
}
for (WarningNoticeTargetVO targetVO : configVO.getNoticeUserList()) {
targetVO.setId(normalizeText(targetVO.getId()));
targetVO.setName(normalizeText(targetVO.getName()));
}
}
return warningNoticeVO;
}
/**
* 校验通知人员配置
*
* @param noticeConfigList 通知人员配置列表
*/
private void validateNoticeConfigList(List<WarningNoticeUserConfigReq> noticeConfigList) {
ServiceException.isTrue(CollUtil.isNotEmpty(noticeConfigList), "临期通知人员不能为空");
Set<String> noticeTypeSet = new HashSet<>();
for (WarningNoticeUserConfigReq configReq : noticeConfigList) {
ServiceException.notNull(configReq, "通知人员配置不能为空");
String noticeUserType = normalizeText(configReq.getNoticeUserType());
ServiceException.isTrue(StrUtil.isNotBlank(noticeUserType), "通知人员类型不能为空");
ServiceException.isTrue(noticeTypeSet.add(noticeUserType), "通知人员类型不能重复");
validateNoticeTargetList(configReq.getNoticeUserList());
}
}
/**
* 校验通知对象列表
*
* @param noticeUserList 通知对象列表
*/
private void validateNoticeTargetList(List<WarningNoticeTargetReq> noticeUserList) {
ServiceException.isTrue(CollUtil.isNotEmpty(noticeUserList), "通知对象不能为空");
Set<String> targetIdSet = new HashSet<>();
for (WarningNoticeTargetReq targetReq : noticeUserList) {
ServiceException.notNull(targetReq, "通知对象不能为空");
String id = normalizeText(targetReq.getId());
String name = normalizeText(targetReq.getName());
ServiceException.isTrue(StrUtil.isNotBlank(id), "通知对象ID不能为空");
ServiceException.isTrue(targetIdSet.add(id), "同一通知人员类型下的通知对象不能重复");
}
}
/**
* 生成配置键
*
* @param certificateType 证照类型
* @return 配置键
*/
private String buildConfigKey(CertificateTypeEnum certificateType,String typeOrTemplateId) {
if(CertificateTypeEnum.STORE_CUSTOM_CERTIFICATE.equals(certificateType)){
return buildConfigKey(typeOrTemplateId);
}
return buildConfigKey(certificateType.getType());
}
/**
* 生成配置键
*
* @param certificateType 证照类型
* @return 配置键
*/
private String buildConfigKey(String certificateType) {
return CONFIG_KEY_PREFIX + certificateType;
}
/**
* 根据配置键找出证照类型如果是自定义证照则是模板id
*
* @param key 配置键
* @return 证照类型
*/
private String buildCertificateTypeOrTemplateId(String key) {
if(StringUtil.isBlank(key)){
return null;
}
return key.substring(CONFIG_KEY_PREFIX.length());
}
/**
* 规范化文本
*
* @param text 原始文本
* @return 规范化后的文本
*/
private String normalizeText(String text) {
return StrUtil.trim(text);
}
/**
* 批量执行保存。
* 先按ID分流为更新/插入若出现唯一键冲突则根据type全量回查后重新分流并重试一次。
*
* @param split 批量分流数据
*/
private void executeBatch(BatchParamSplit split) {
try {
executeBatchOnceInTransaction(split);
} catch (Exception e) {
if (!isDuplicateKeyException(e)) {
throw e;
}
BatchParamSplit retrySplit = rebuildSplitByTypeFromDbInNewTransaction(split.getAllEntities());
try {
executeBatchOnceInTransaction(retrySplit);
} catch (Exception ex) {
if (!isDuplicateKeyException(ex)) {
throw ex;
}
throw new ServiceException("该证照类型的预警配置已存在");
}
}
}
/**
* 单次执行批量更新+批量插入。
*
* @param split 分流结果
*/
private void executeBatchOnce(BatchParamSplit split) {
if (split == null || CollUtil.isEmpty(split.getAllEntities())) {
return;
}
if (CollUtil.isNotEmpty(split.getUpdateEntities())) {
baseParamMapper.batchUpdateById(split.getUpdateEntities());
}
if (CollUtil.isNotEmpty(split.getInsertEntities())) {
baseParamMapper.batchInsertParam(split.getInsertEntities());
}
}
/**
* 在事务中执行一次批量写入。
*
* @param split 分流结果
*/
private void executeBatchOnceInTransaction(BatchParamSplit split) {
TransactionTemplate template = buildTransactionTemplate(TransactionDefinition.PROPAGATION_REQUIRED, false);
template.execute(status -> {
executeBatchOnce(split);
return null;
});
}
/**
* 预处理批量请求校验参数并构建VO/实体。
*
* @param reqList 请求列表
* @return 预处理结果列表
*/
private List<WarningNoticePreparedData> prepareSaveDataList(List<WarningNoticeSaveReq> reqList) {
List<WarningNoticePreparedData> result = new ArrayList<>(reqList.size());
Map<String, StoreCertificatePhotoEntity> stringStoreCertificatePhotoEntityMap = storeCertificatePhotoHelper.buildStoreCertificateById(reqList.stream()
.map(WarningNoticeSaveReq::getTypeOrTemplateId)
.filter(this::isStoreCertificateType)
.collect(Collectors.toSet()),null);
for (WarningNoticeSaveReq req : reqList) {
ServiceException.notNull(req, "保存参数不能为空");
String typeOrTemplateId = req.getTypeOrTemplateId();
Optional<CertificateTypeEnum> certificateTypeEnumOptional= CertificateTypeEnum.getByType(typeOrTemplateId);
ServiceException.isTrue(certificateTypeEnumOptional.isPresent(), "证照类型不能为空");
CertificateTypeEnum certificateType = certificateTypeEnumOptional.get();
Integer expiryReminderDays = req.getExpiryReminderDays();
if (certificateType != CertificateTypeEnum.STORE_CUSTOM_CERTIFICATE) {
ServiceException.notNull(expiryReminderDays, "临期提醒时间不能为空");
ServiceException.isTrue(expiryReminderDays >= 0, "临期提醒时间必须大于等于0");
ServiceException.isTrue(expiryReminderDays<=180, "临期提醒时间最大180");
}else{
StoreCertificatePhotoEntity storeCertificatePhotoEntity = stringStoreCertificatePhotoEntityMap.get(typeOrTemplateId);
ServiceException.notNull(storeCertificatePhotoEntity, "自定义证照不存在["+typeOrTemplateId+"]");
expiryReminderDays = storeCertificatePhotoEntity.getExpiryReminderDays();
}
ServiceException.notNull(req.getNoticeFrequencyDays(), "通知频率不能为空");
if(Objects.nonNull(expiryReminderDays) && expiryReminderDays > 0){
ServiceException.isTrue(req.getNoticeFrequencyDays() <= expiryReminderDays, "通知频率必须小于等于临期提醒");
}
// validateNoticeConfigList(req.getNoticeConfigList());
String key = buildConfigKey(certificateType,typeOrTemplateId);
WarningNoticeVO saveData = buildSaveData(req, certificateType, key);
String reqId = normalizeText(req.getId());
FtbParamEntity entity = buildBaseParamEntity(saveData, reqId);
result.add(new WarningNoticePreparedData(typeOrTemplateId, reqId, entity));
}
return result;
}
private boolean isStoreCertificateType(String typeOrTemplateId) {
Optional<CertificateTypeEnum> certificateTypeEnumOptional= CertificateTypeEnum.getByType(typeOrTemplateId);
ServiceException.isTrue(certificateTypeEnumOptional.isPresent(), "证照类型不能为空");
CertificateTypeEnum certificateType = certificateTypeEnumOptional.get();
return CertificateTypeEnum.STORE_CUSTOM_CERTIFICATE.equals(certificateType);
}
/**
* 按是否有ID进行分流有ID走更新无ID走插入。
*
* @param preparedList 预处理结果列表
* @return 分流结果
*/
private BatchParamSplit splitById(List<WarningNoticePreparedData> preparedList) {
List<FtbParamEntity> updateEntities = new ArrayList<>();
List<FtbParamEntity> insertEntities = new ArrayList<>();
List<FtbParamEntity> allEntities = new ArrayList<>();
for (WarningNoticePreparedData data : preparedList) {
FtbParamEntity entity = data.getEntity();
String reqId = data.getReqId();
allEntities.add(entity);
if (StrUtil.isNotBlank(reqId)) {
updateEntities.add(entity);
} else {
insertEntities.add(entity);
}
}
return new BatchParamSplit(updateEntities, insertEntities, allEntities);
}
/**
* 批量参数唯一性校验。
* 同一次请求中同一type只能出现一次同一id只能出现一次。
*
* @param preparedList 预处理列表
*/
private void validateBatchUniqueness(List<WarningNoticePreparedData> preparedList) {
Set<String> typeSet = new HashSet<>();
Set<String> reqIdSet = new HashSet<>();
for (WarningNoticePreparedData data : preparedList) {
String type = normalizeText(data.getType());
ServiceException.isTrue(StrUtil.isNotBlank(type), "证照类型不能为空");
ServiceException.isTrue(typeSet.add(type), "同一次请求中证照类型不能重复");
String reqId = normalizeText(data.getReqId());
if (StrUtil.isBlank(reqId)) {
continue;
}
ServiceException.isTrue(reqIdSet.add(reqId), "同一次请求中ID不能重复");
}
}
/**
* duplicate key 后按 type 全量回查,重建更新/插入集合。
*
* @param entities 本次要保存的实体
* @return 重建后的分流结果
*/
private BatchParamSplit rebuildSplitByTypeFromDb(List<FtbParamEntity> entities) {
if (CollUtil.isEmpty(entities)) {
return new BatchParamSplit(Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
}
List<FtbParamEntity> dbList = baseParamMapper.selectList(
new LambdaQueryWrapper<FtbParamEntity>().eq(FtbParamEntity::getType, certificateWarnNoticeType)
);
Map<String, FtbParamEntity> dbByKey = dbList.stream()
.filter(Objects::nonNull)
.filter(item -> StrUtil.isNotBlank(item.getKey()))
.collect(Collectors.toMap(item -> StrUtil.trim(item.getKey()), item -> item, (a, b) -> a));
List<FtbParamEntity> updateEntities = new ArrayList<>();
List<FtbParamEntity> insertEntities = new ArrayList<>();
List<FtbParamEntity> allEntities = new ArrayList<>();
for (FtbParamEntity entity : entities) {
if (entity == null || StrUtil.isBlank(entity.getKey())) {
continue;
}
FtbParamEntity saveEntity = cloneParamEntity(entity);
FtbParamEntity dbEntity = dbByKey.get(StrUtil.trim(saveEntity.getKey()));
if (dbEntity != null && StrUtil.isNotBlank(dbEntity.getId())) {
saveEntity.setId(StrUtil.trim(dbEntity.getId()));
updateEntities.add(saveEntity);
} else {
if (StrUtil.isBlank(saveEntity.getId())) {
saveEntity.setId(FtbUtil.getId());
}
insertEntities.add(saveEntity);
}
allEntities.add(saveEntity);
}
return new BatchParamSplit(updateEntities, insertEntities, allEntities);
}
/**
* 在新事务中按type回查并重建分流结果。
*
* @param entities 本次要保存的实体
* @return 重建后的分流结果
*/
private BatchParamSplit rebuildSplitByTypeFromDbInNewTransaction(List<FtbParamEntity> entities) {
TransactionTemplate template = buildTransactionTemplate(TransactionDefinition.PROPAGATION_REQUIRES_NEW, true);
BatchParamSplit split = template.execute(status -> rebuildSplitByTypeFromDb(entities));
return split == null
? new BatchParamSplit(Collections.emptyList(), Collections.emptyList(), Collections.emptyList())
: split;
}
/**
* 构建事务模板。
*
* @param propagationBehavior 事务传播级别
* @param readOnly 是否只读
* @return 事务模板
*/
private TransactionTemplate buildTransactionTemplate(int propagationBehavior, boolean readOnly) {
TransactionTemplate template = new TransactionTemplate(transactionManager);
template.setPropagationBehavior(propagationBehavior);
template.setReadOnly(readOnly);
return template;
}
/**
* 按key列表查询预警配置返回值与入参顺序一致。
*
* @param keys 配置key列表
* @return 预警配置列表
*/
private List<WarningNoticeVO> queryByKeys(List<String> keys) {
if (CollUtil.isEmpty(keys)) {
return Collections.emptyList();
}
List<FtbParamEntity> dbList = baseParamMapper.selectList(
new LambdaQueryWrapper<FtbParamEntity>()
.eq(FtbParamEntity::getType, certificateWarnNoticeType)
.in(FtbParamEntity::getKey, keys)
);
Map<String, WarningNoticeVO> voByKey = dbList.stream()
.filter(Objects::nonNull)
.filter(item -> StrUtil.isNotBlank(item.getKey()))
.collect(Collectors.toMap(
item -> StrUtil.trim(item.getKey()),
item -> normalizeWarningNoticeVO(buildWarningNoticeVO(item)),
(a, b) -> a
));
List<WarningNoticeVO> result = new ArrayList<>(keys.size());
for (String key : keys) {
WarningNoticeVO vo = voByKey.get(StrUtil.trim(key));
if (vo != null) {
result.add(vo);
}
}
return result;
}
/**
* 复制参数实体,避免重试时修改原对象。
*
* @param source 原实体
* @return 复制后的实体
*/
private FtbParamEntity cloneParamEntity(FtbParamEntity source) {
FtbParamEntity target = new FtbParamEntity();
target.setId(normalizeText(source.getId()));
target.setKey(normalizeText(source.getKey()));
target.setType(normalizeText(source.getType()));
target.setValue(source.getValue());
target.setSort(source.getSort());
return target;
}
/**
* 判断异常链中是否包含唯一键冲突。
*
* @param throwable 异常
* @return 是否唯一键冲突
*/
private boolean isDuplicateKeyException(Throwable throwable) {
Throwable current = throwable;
while (current != null) {
if (current instanceof DuplicateKeyException) {
return true;
}
String message = current.getMessage();
if (StrUtil.isNotBlank(message)
&& StrUtil.containsIgnoreCase(message, "duplicate")
&& StrUtil.containsIgnoreCase(message, "key")) {
return true;
}
current = current.getCause();
}
return false;
}
/**
* 预处理数据载体。
*/
private static class WarningNoticePreparedData {
private final String type;
private final String reqId;
private final FtbParamEntity entity;
private WarningNoticePreparedData(String type, String reqId, FtbParamEntity entity) {
this.type = type;
this.reqId = reqId;
this.entity = entity;
}
private String getType() {
return type;
}
private String getReqId() {
return reqId;
}
private FtbParamEntity getEntity() {
return entity;
}
}
/**
* 批量更新/插入分流结果。
*/
private static class BatchParamSplit {
private final List<FtbParamEntity> updateEntities;
private final List<FtbParamEntity> insertEntities;
private final List<FtbParamEntity> allEntities;
private BatchParamSplit(List<FtbParamEntity> updateEntities,
List<FtbParamEntity> insertEntities,
List<FtbParamEntity> allEntities) {
this.updateEntities = updateEntities;
this.insertEntities = insertEntities;
this.allEntities = allEntities;
}
private List<FtbParamEntity> getUpdateEntities() {
return updateEntities;
}
private List<FtbParamEntity> getInsertEntities() {
return insertEntities;
}
private List<FtbParamEntity> getAllEntities() {
return allEntities;
}
}
}