package com.ruoyi.other.service.impl;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.ruoyi.common.core.domain.R;
|
import com.ruoyi.common.core.web.page.PageInfo;
|
import com.ruoyi.common.security.utils.SecurityUtils;
|
import com.ruoyi.other.api.domain.TGroupCompany;
|
import com.ruoyi.other.api.dto.CompanyRechargeRecordExcelDTO;
|
import com.ruoyi.other.api.dto.CompanyRechargeRecordQueryDTO;
|
import com.ruoyi.other.api.model.TCompanyRechargeRecord;
|
import com.ruoyi.other.mapper.TCompanyRechargeRecordMapper;
|
import com.ruoyi.other.mapper.TGroupCompanyMapper;
|
import com.ruoyi.other.service.CompanyAccountAccessService;
|
import com.ruoyi.other.service.TCompanyRechargeRecordService;
|
import com.ruoyi.system.api.domain.SysUser;
|
import com.ruoyi.system.api.feignClient.SysUserClient;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import javax.annotation.Resource;
|
import java.math.BigDecimal;
|
import java.time.LocalDateTime;
|
import java.time.format.DateTimeFormatter;
|
import java.util.HashMap;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Objects;
|
import java.util.Set;
|
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.stream.Collectors;
|
|
/**
|
* <p>
|
* 公司充值记录 服务实现类
|
* </p>
|
*/
|
@Slf4j
|
@Service
|
public class TCompanyRechargeRecordServiceImpl extends ServiceImpl<TCompanyRechargeRecordMapper, TCompanyRechargeRecord> implements TCompanyRechargeRecordService {
|
|
private static final DateTimeFormatter SERIAL_DTF = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
|
/** 充值状态枚举:1=成功 2=失败 3=已退款 */
|
public static final int RECHARGE_STATUS_SUCCESS = 1;
|
|
@Resource
|
private TGroupCompanyMapper groupCompanyMapper;
|
@Resource
|
private CompanyAccountAccessService companyAccountAccessService;
|
@Resource
|
private SysUserClient sysUserClient;
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public Integer recharge(TCompanyRechargeRecord record) {
|
if (record.getCompanyId() == null) {
|
throw new IllegalArgumentException("公司ID不能为空");
|
}
|
if (record.getPayAmount() == null || record.getPayAmount().compareTo(BigDecimal.ZERO) < 0) {
|
throw new IllegalArgumentException("实付金额不能为空且必须大于等于0");
|
}
|
BigDecimal giftAmount = record.getGiftAmount() == null ? BigDecimal.ZERO : record.getGiftAmount();
|
if (giftAmount.compareTo(BigDecimal.ZERO) < 0) {
|
throw new IllegalArgumentException("赠送金额不能小于0");
|
}
|
TGroupCompany company = groupCompanyMapper.selectById(record.getCompanyId());
|
if (company == null) {
|
throw new IllegalArgumentException("公司不存在");
|
}
|
BigDecimal arrival = record.getPayAmount().add(giftAmount);
|
record.setArrivalAmount(arrival);
|
record.setId(null);
|
record.setDelFlag(false);
|
record.setRechargeTime(LocalDateTime.now());
|
record.setSerialNo(buildSerialNo());
|
record.setStatus(RECHARGE_STATUS_SUCCESS);
|
try {
|
record.setOperatorId(SecurityUtils.getLoginUser().getUserid());
|
} catch (Exception ignored) {
|
// 非登录上下文(如内部调用)允许为空
|
}
|
// 更新公司余额(原子加):拼接 toString 防止 SQL 注入,BigDecimal 是受控数值
|
groupCompanyMapper.update(null, Wrappers.lambdaUpdate(TGroupCompany.class)
|
.eq(TGroupCompany::getId, record.getCompanyId())
|
.setSql("available_balance = IFNULL(available_balance, 0) + " + arrival.toPlainString()));
|
this.save(record);
|
return record.getId();
|
}
|
|
@Override
|
public PageInfo<TCompanyRechargeRecord> pageList(CompanyRechargeRecordQueryDTO query) {
|
int current = query.getCurrent() == null || query.getCurrent() < 1 ? 1 : query.getCurrent();
|
int size = query.getSize() == null || query.getSize() < 1 ? 10 : query.getSize();
|
Page<TCompanyRechargeRecord> page = new Page<>(current, size);
|
LambdaQueryWrapper<TCompanyRechargeRecord> wrapper = buildQueryWrapper(query);
|
Page<TCompanyRechargeRecord> result = this.page(page, wrapper);
|
fillNames(result.getRecords());
|
PageInfo<TCompanyRechargeRecord> pageInfo = new PageInfo<>(result.getCurrent(), result.getSize());
|
pageInfo.setRecords(result.getRecords());
|
pageInfo.setTotal(result.getTotal());
|
return pageInfo;
|
}
|
|
@Override
|
public List<CompanyRechargeRecordExcelDTO> selectExportList(CompanyRechargeRecordQueryDTO query) {
|
LambdaQueryWrapper<TCompanyRechargeRecord> wrapper = buildQueryWrapper(query);
|
List<TCompanyRechargeRecord> records = this.list(wrapper);
|
fillNames(records);
|
return records.stream().map(this::toExcelDto).collect(Collectors.toList());
|
}
|
|
/**
|
* 构建查询 wrapper:参数为 null 时不参与条件;delFlag=0;按 id 倒序
|
*/
|
private LambdaQueryWrapper<TCompanyRechargeRecord> buildQueryWrapper(CompanyRechargeRecordQueryDTO query) {
|
Integer companyId = companyAccountAccessService.resolveCompanyFilter(query.getCompanyId());
|
return Wrappers.lambdaQuery(TCompanyRechargeRecord.class)
|
.eq(TCompanyRechargeRecord::getDelFlag, false)
|
.eq(companyId != null, TCompanyRechargeRecord::getCompanyId, companyId)
|
.eq(query.getStatus() != null, TCompanyRechargeRecord::getStatus, query.getStatus())
|
.eq(query.getSerialNo() != null && !query.getSerialNo().isEmpty(),
|
TCompanyRechargeRecord::getSerialNo, query.getSerialNo())
|
.ge(query.getRechargeTimeStart() != null, TCompanyRechargeRecord::getRechargeTime, query.getRechargeTimeStart())
|
.le(query.getRechargeTimeEnd() != null, TCompanyRechargeRecord::getRechargeTime, query.getRechargeTimeEnd())
|
.orderByDesc(TCompanyRechargeRecord::getId);
|
}
|
|
/**
|
* 列表结果批量回填公司名称与操作人名称:单次查表 + 按需远程调用 sys_user
|
*/
|
private void fillNames(List<TCompanyRechargeRecord> records) {
|
if (records == null || records.isEmpty()) {
|
return;
|
}
|
Set<Integer> companyIds = records.stream()
|
.map(TCompanyRechargeRecord::getCompanyId)
|
.filter(Objects::nonNull)
|
.collect(Collectors.toSet());
|
Map<Integer, String> companyNameMap = new HashMap<>();
|
if (!companyIds.isEmpty()) {
|
try {
|
List<TGroupCompany> companies = groupCompanyMapper.selectBatchIds(companyIds);
|
if (companies != null) {
|
for (TGroupCompany c : companies) {
|
companyNameMap.put(c.getId(), c.getName());
|
}
|
}
|
} catch (Exception e) {
|
log.warn("批量查询公司名称失败:ids={}", companyIds, e);
|
}
|
}
|
Set<Long> operatorIds = records.stream()
|
.map(TCompanyRechargeRecord::getOperatorId)
|
.filter(Objects::nonNull)
|
.collect(Collectors.toSet());
|
Map<Long, String> operatorNameMap = new HashMap<>();
|
for (Long id : operatorIds) {
|
try {
|
R<SysUser> result = sysUserClient.getSysUser(id);
|
if (result != null && result.getData() != null
|
&& result.getData().getUserName() != null
|
&& !result.getData().getUserName().isEmpty()) {
|
operatorNameMap.put(id, result.getData().getUserName());
|
}
|
} catch (Exception e) {
|
log.warn("查询操作人失败:id={}", id, e);
|
}
|
}
|
for (TCompanyRechargeRecord r : records) {
|
r.setCompanyName(companyNameMap.get(r.getCompanyId()));
|
r.setOperatorName(operatorNameMap.get(r.getOperatorId()));
|
}
|
}
|
|
private CompanyRechargeRecordExcelDTO toExcelDto(TCompanyRechargeRecord r) {
|
CompanyRechargeRecordExcelDTO dto = new CompanyRechargeRecordExcelDTO();
|
dto.setSerialNo(r.getSerialNo());
|
dto.setCompanyName(r.getCompanyName());
|
dto.setPayAmount(r.getPayAmount());
|
dto.setGiftAmount(r.getGiftAmount());
|
dto.setArrivalAmount(r.getArrivalAmount());
|
dto.setOperatorName(r.getOperatorName());
|
dto.setRechargeTime(r.getRechargeTime());
|
return dto;
|
}
|
|
/**
|
* 生成充值流水号:CR + yyyyMMddHHmmss + 3 位随机数;唯一约束 uk_serial_no 兜底。
|
* 同一秒内重复概率约 1/1000,重复时由 catch DuplicateKeyException 重试。
|
*/
|
private String buildSerialNo() {
|
String prefix = "CR" + LocalDateTime.now().format(SERIAL_DTF);
|
for (int attempt = 0; attempt < 5; attempt++) {
|
String suffix = String.format("%03d", ThreadLocalRandom.current().nextInt(0, 1000));
|
String candidate = prefix + suffix;
|
if (this.lambdaQuery()
|
.eq(TCompanyRechargeRecord::getSerialNo, candidate)
|
.eq(TCompanyRechargeRecord::getDelFlag, false)
|
.count() == 0) {
|
return candidate;
|
}
|
}
|
// 重试耗尽:使用基于当前纳秒的后缀,仍受唯一索引保护
|
return prefix + String.format("%06d", System.nanoTime() % 1000000);
|
}
|
}
|