|
@@ -454,44 +454,96 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
|
|
|
return invoiceIds;
|
|
return invoiceIds;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 开票重复校验
|
|
|
|
|
+ * 功能:DB+Redis双重校验,防止并发重复开票
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param invoicingVo 开票参数
|
|
|
|
|
+ * @return true=允许开票,false=禁止开票
|
|
|
|
|
+ */
|
|
|
@Override
|
|
@Override
|
|
|
public boolean invoicingCheck(InvoicingVo invoicingVo) {
|
|
public boolean invoicingCheck(InvoicingVo invoicingVo) {
|
|
|
|
|
+ // 1. 基础参数校验
|
|
|
|
|
+ if (invoicingVo == null || CollUtil.isEmpty(invoicingVo.getIncomeIds()) || StrUtil.isBlank(invoicingVo.getInvoiceType())) {
|
|
|
|
|
+ log.error("开票校验参数不能为空");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 数据库校验:是否已存在开票记录
|
|
|
Integer count = baseMapper.invoicingCheck(invoicingVo);
|
|
Integer count = baseMapper.invoicingCheck(invoicingVo);
|
|
|
- RKeys keys = redissonClient.getKeys();
|
|
|
|
|
- Iterable<String> allKeys = null;
|
|
|
|
|
- String redisKey = null;
|
|
|
|
|
- if (invoicingVo.getInvoiceType().equals("1")) {
|
|
|
|
|
- redisKey = CAR_INVOICING_COMMISSION_LOCK;
|
|
|
|
|
- } else if (invoicingVo.getInvoiceType().equals("2")) {
|
|
|
|
|
- redisKey = CAR_INVOICING_OTHER_LOCK;
|
|
|
|
|
- } else if (invoicingVo.getInvoiceType().equals("3")) {
|
|
|
|
|
- redisKey = JY_INVOICING_COMMISSION_LOCK;
|
|
|
|
|
- } else if (invoicingVo.getInvoiceType().equals("4")) {
|
|
|
|
|
- redisKey = JY_INVOICING_OTHER_LOCK;
|
|
|
|
|
|
|
+ if (count > 0) {
|
|
|
|
|
+ log.info("数据库已存在开票记录,直接禁止");
|
|
|
|
|
+ return false;
|
|
|
}
|
|
}
|
|
|
- allKeys = keys.getKeysByPattern(redisKey + "*");
|
|
|
|
|
- //提取所有redis的key 然后遍历是否存在重复的id
|
|
|
|
|
- for (String key : allKeys) {
|
|
|
|
|
- // 过滤掉自己的redis key
|
|
|
|
|
- if (!key.equals(key + baseController.getUserName())) {
|
|
|
|
|
- RBucket<Object> bucket = redissonClient.getBucket(key);
|
|
|
|
|
- String incomeIds = bucket.get().toString();
|
|
|
|
|
- incomeIds = incomeIds.substring(1, incomeIds.length() - 1);
|
|
|
|
|
- List<String> incomeIdList = Arrays.stream(incomeIds.split(",")).collect(Collectors.toList());
|
|
|
|
|
- boolean hasDuplicate = invoicingVo.getIncomeIds().stream().anyMatch(incomeIdList::contains);
|
|
|
|
|
- if (hasDuplicate) {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 根据开票类型获取Redis锁前缀
|
|
|
|
|
+ String redisKeyPrefix = switch (invoicingVo.getInvoiceType()) {
|
|
|
|
|
+ case "1" -> CAR_INVOICING_COMMISSION_LOCK;
|
|
|
|
|
+ case "2" -> CAR_INVOICING_OTHER_LOCK;
|
|
|
|
|
+ case "3" -> JY_INVOICING_COMMISSION_LOCK;
|
|
|
|
|
+ case "4" -> JY_INVOICING_OTHER_LOCK;
|
|
|
|
|
+ default -> throw new SystemException("不支持的开票类型");
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 当前用户的Redis Key
|
|
|
|
|
+ String currentUserName = baseController.getUserName();
|
|
|
|
|
+ String currentRedisKey = redisKeyPrefix + currentUserName;
|
|
|
|
|
+
|
|
|
|
|
+ // 5. Redis校验:同类型下,是否有其他用户占用了相同incomeId
|
|
|
|
|
+ // 把锁key存入同一个set,避免使用keys命令
|
|
|
|
|
+ RKeys keys = redissonClient.getKeys();
|
|
|
|
|
+ Iterable<String> allLockKeys = keys.getKeysByPattern(redisKeyPrefix + "*");
|
|
|
|
|
+
|
|
|
|
|
+ for (String lockKey : allLockKeys) {
|
|
|
|
|
+ // 正确过滤自己的key
|
|
|
|
|
+ if (currentRedisKey.equals(lockKey)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 安全获取缓存值
|
|
|
|
|
+ RBucket<Object> bucket = redissonClient.getBucket(lockKey);
|
|
|
|
|
+ Object cacheObj = bucket.get();
|
|
|
|
|
+ if (cacheObj == null) {
|
|
|
|
|
+ continue;
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- if (count == 0) {
|
|
|
|
|
- // 设置缓存
|
|
|
|
|
- RBucket<Object> bucket = redissonClient.getBucket(redisKey + baseController.getUserName());
|
|
|
|
|
- bucket.set(invoicingVo.getIncomeIds(), 10, TimeUnit.MINUTES);
|
|
|
|
|
|
|
+ // 解析缓存中的incomeId集合
|
|
|
|
|
+ List<String> cacheIncomeIds = this.parseIncomeIdsFromRedis(cacheObj.toString());
|
|
|
|
|
+ // 校验重复
|
|
|
|
|
+ boolean duplicate = invoicingVo.getIncomeIds().stream().anyMatch(cacheIncomeIds::contains);
|
|
|
|
|
+ if (duplicate) {
|
|
|
|
|
+ log.info("Redis校验重复,lockKey:{}", lockKey);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return count == 0;
|
|
|
|
|
|
|
+ // 6. 校验通过,存入Redis锁(10分钟自动过期)
|
|
|
|
|
+ RBucket<Object> bucket = redissonClient.getBucket(currentRedisKey);
|
|
|
|
|
+ bucket.set(invoicingVo.getIncomeIds(), 10, TimeUnit.MINUTES);
|
|
|
|
|
+ log.info("开票校验通过,Redis加锁成功,key:{}", currentRedisKey);
|
|
|
|
|
+
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 从Redis字符串解析收入ID集合(安全解析,防止异常)
|
|
|
|
|
+ */
|
|
|
|
|
+ private List<String> parseIncomeIdsFromRedis(String incomeIdsStr) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (StrUtil.isBlank(incomeIdsStr) || !incomeIdsStr.startsWith("[") || !incomeIdsStr.endsWith("]")) {
|
|
|
|
|
+ return Collections.emptyList();
|
|
|
|
|
+ }
|
|
|
|
|
+ String content = incomeIdsStr.substring(1, incomeIdsStr.length() - 1).trim();
|
|
|
|
|
+ if (StrUtil.isBlank(content)) {
|
|
|
|
|
+ return Collections.emptyList();
|
|
|
|
|
+ }
|
|
|
|
|
+ return Arrays.stream(content.split(","))
|
|
|
|
|
+ .map(String::trim)
|
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("解析Redis收入ID异常", e);
|
|
|
|
|
+ return Collections.emptyList();
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
@@ -553,11 +605,12 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
|
|
|
link.setId(IdGenerate.nextId());
|
|
link.setId(IdGenerate.nextId());
|
|
|
link.setInvoiceId(invoice.getId());
|
|
link.setInvoiceId(invoice.getId());
|
|
|
link.setIncomeId(incomeId);
|
|
link.setIncomeId(incomeId);
|
|
|
|
|
+ link.setInvoiceType(invoicingVo.getInvoiceType());
|
|
|
invoicingLinkList.add(link);
|
|
invoicingLinkList.add(link);
|
|
|
});
|
|
});
|
|
|
insPlyIncomeInvoiceLinkMapper.insert(invoicingLinkList);
|
|
insPlyIncomeInvoiceLinkMapper.insert(invoicingLinkList);
|
|
|
result.setInvoiceId(invoice.getId());
|
|
result.setInvoiceId(invoice.getId());
|
|
|
- result.setSumPrice(invoicingVo.getReceivablePremium().toString());
|
|
|
|
|
|
|
+ result.setSumPrice(null != invoicingVo.getReceivablePremium() ? invoicingVo.getReceivablePremium().toString() : null);
|
|
|
return result;
|
|
return result;
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
log.info("开票异常.invoicingVo=[{}]",JSONUtil.toJsonStr(invoicingVo), e);
|
|
log.info("开票异常.invoicingVo=[{}]",JSONUtil.toJsonStr(invoicingVo), e);
|