补贴管理

This commit is contained in:
gaowdong 2025-04-08 18:33:06 +08:00
parent 3f099d8951
commit 50cbbb1311
20 changed files with 797 additions and 32 deletions

View File

@ -0,0 +1,23 @@
package com.bonus.canteen.core.account.constants;
import java.util.Arrays;
public enum AccWalletClearTypeEnum {
CLEAR(1, "清空"),
CLEAR_BY(2, "清空至");
private final Integer key;
private final String desc;
private AccWalletClearTypeEnum(Integer key, String desc) {
this.key = key;
this.desc = desc;
}
public Integer getKey() {
return this.key;
}
public String getDesc() {
return this.desc;
}
}

View File

@ -0,0 +1,70 @@
package com.bonus.canteen.core.account.controller;
import cn.hutool.core.util.ObjectUtil;
import com.bonus.canteen.core.account.domain.AccInfo;
import com.bonus.canteen.core.account.domain.param.AccOperationQueryParam;
import com.bonus.canteen.core.account.domain.param.AccSubsidyParam;
import com.bonus.canteen.core.account.domain.param.AccountEnableDisableParam;
import com.bonus.canteen.core.account.domain.param.AccountInfoQueryParam;
import com.bonus.canteen.core.account.domain.vo.AccInfoDetailsVO;
import com.bonus.canteen.core.account.service.AccOperationHistoryService;
import com.bonus.canteen.core.account.service.AccSubService;
import com.bonus.canteen.core.account.service.IAccInfoService;
import com.bonus.common.core.utils.poi.ExcelUtil;
import com.bonus.common.core.web.controller.BaseController;
import com.bonus.common.core.web.domain.AjaxResult;
import com.bonus.common.core.web.page.TableDataInfo;
import com.bonus.common.houqin.constant.RetCodeEnum;
import com.bonus.common.houqin.i18n.I18n;
import com.bonus.common.log.annotation.SysLog;
import com.bonus.common.log.enums.OperaType;
import com.bonus.system.api.domain.SysUser;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.List;
/**
* 账户资料Controller
*
* @author xsheng
* @date 2025-04-05
*/
@Api(tags = "账户补贴接口")
@RestController
@RequestMapping("/acc/subsidy")
public class AccSubsidyController extends BaseController {
@Autowired
private AccSubService accSubService;
@ApiOperation("单人补贴")
@PostMapping("/add")
public AjaxResult individualAccSubsidyAdd(@RequestBody @Valid AccSubsidyParam param) {
this.accSubService.individualAccSubsidyAdd(param);
return AjaxResult.success();
}
@ApiOperation("补贴清空")
@PostMapping({"/clear"})
public AjaxResult individualAccSubsidyClear(@RequestBody @Valid AccSubsidyParam param) {
this.accSubService.individualAccSubsidyClear(param);
return AjaxResult.success();
}
@ApiOperation("单人补贴")
@PostMapping("/batch/add")
public AjaxResult batchAccSubsidyAdd(@RequestBody @Valid AccSubsidyParam param) {
this.accSubService.batchAccSubsidyAdd(param);
return AjaxResult.success();
}
@ApiOperation("补贴清空")
@PostMapping({"/batch/clear"})
public AjaxResult batchAccSubsidyClear(@RequestBody @Valid AccSubsidyParam param) {
this.accSubService.batchAccSubsidyClear(param);
return AjaxResult.success();
}
}

View File

@ -19,20 +19,20 @@ public class AccInfoVo {
@ApiModelProperty("账户状态 1正常 2冻结 3销户 4过期") @ApiModelProperty("账户状态 1正常 2冻结 3销户 4过期")
private Integer accStatus; private Integer accStatus;
@ApiModelProperty("账户可用总余额(不包括冻结金额)") @ApiModelProperty("账户可用总余额(不包括冻结金额)")
private Long accBalTotal; private BigDecimal accBalTotal;
@ApiModelProperty("账户总余额(包含冻结金额)") @ApiModelProperty("账户总余额(包含冻结金额)")
private Long accAllBal; private BigDecimal accAllBal;
@ApiModelProperty("个人钱包(可用)余额/分") @ApiModelProperty("个人钱包(可用)余额/分")
private Long walletBal; private BigDecimal walletBal;
@ApiModelProperty("补贴钱包(可用)余额/分") @ApiModelProperty("补贴钱包(可用)余额/分")
private Long subsidyBal; private BigDecimal subsidyBal;
@ApiModelProperty("红包余额") @ApiModelProperty("红包余额")
private Long redEnvelope; private BigDecimal redEnvelope;
@ApiModelProperty("个人钱包冻结金额") @ApiModelProperty("个人钱包冻结金额")
private Long walletFreezeBal; private BigDecimal walletFreezeBal;
@ApiModelProperty("补贴钱包冻结金额") @ApiModelProperty("补贴钱包冻结金额")
private Long subFreezeBal; private BigDecimal subFreezeBal;
private Long accFreezeBalTotal; private BigDecimal accFreezeBalTotal;
private List<AccWalletInfo> walletInfoList; private List<AccWalletInfo> walletInfoList;
} }

View File

@ -1,5 +1,6 @@
package com.bonus.canteen.core.account.domain; package com.bonus.canteen.core.account.domain;
import java.math.BigDecimal;
import java.util.Date; import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import com.bonus.common.core.annotation.Excel; import com.bonus.common.core.annotation.Excel;
@ -35,17 +36,17 @@ public class AccWalletInfo extends BaseEntity {
/** 钱包余额/分 */ /** 钱包余额/分 */
@Excel(name = "钱包余额/分") @Excel(name = "钱包余额/分")
@ApiModelProperty(value = "钱包余额/分") @ApiModelProperty(value = "钱包余额/分")
private Long walletBal; private BigDecimal walletBal;
/** 最低余额限制/分 */ /** 最低余额限制/分 */
@Excel(name = "最低余额限制/分") @Excel(name = "最低余额限制/分")
@ApiModelProperty(value = "最低余额限制/分") @ApiModelProperty(value = "最低余额限制/分")
private Long limitBalance; private BigDecimal limitBalance;
/** 冻结金额 */ /** 冻结金额 */
@Excel(name = "冻结金额") @Excel(name = "冻结金额")
@ApiModelProperty(value = "冻结金额") @ApiModelProperty(value = "冻结金额")
private Long frozenBalance; private BigDecimal frozenBalance;
/** 过期时间 */ /** 过期时间 */
@ApiModelProperty(value = "过期时间") @ApiModelProperty(value = "过期时间")

View File

@ -7,21 +7,21 @@ import java.math.BigDecimal;
@Data @Data
public class WalletBalanceVO { public class WalletBalanceVO {
@ApiModelProperty("个人钱包余额/分") @ApiModelProperty("个人钱包余额/分")
private Long walletBal; private BigDecimal walletBal;
@ApiModelProperty("补贴钱包余额/分") @ApiModelProperty("补贴钱包余额/分")
private Long subsidyBal; private BigDecimal subsidyBal;
@ApiModelProperty("红包余额") @ApiModelProperty("红包余额")
private Long redEnvelope; private BigDecimal redEnvelope;
@ApiModelProperty("个人钱包冻结金额") @ApiModelProperty("个人钱包冻结金额")
private Long walletFreezeBal; private BigDecimal walletFreezeBal;
@ApiModelProperty("补贴钱包冻结金额") @ApiModelProperty("补贴钱包冻结金额")
private Long subFreezeBal; private BigDecimal subFreezeBal;
@ApiModelProperty("冻结金额") @ApiModelProperty("冻结金额")
private Long accFreezeBalTotal; private BigDecimal accFreezeBalTotal;
@ApiModelProperty("账户总余额(包含冻结金额)") @ApiModelProperty("账户总余额(包含冻结金额)")
private Long accAllBal; private BigDecimal accAllBal;
@ApiModelProperty("账户可用余额总余额(不包括冻结金额)") @ApiModelProperty("账户可用余额总余额(不包括冻结金额)")
private Long accBalTotal; private BigDecimal accBalTotal;
@ApiModelProperty("账户状态 1正常 2冻结 3销户 4过期") @ApiModelProperty("账户状态 1正常 2冻结 3销户 4过期")
private Integer accStatus; private Integer accStatus;

View File

@ -0,0 +1,43 @@
package com.bonus.canteen.core.account.domain.param;
import com.bonus.common.core.web.domain.BaseEntity;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.util.List;
@Data
public class AccSubsidyParam extends BaseEntity {
@ApiModelProperty(
value = "用户编号",
required = true
)
private @NotNull(
message = "用户编号不能为空"
) Long userId;
@ApiModelProperty(
value = "用户编号",
required = true
)
private @NotNull(
message = "用户编号不能为空"
) List<Long> userIds;
@ApiModelProperty(
value = "充值金额/分",
required = true
)
private @NotNull(
message = "金额不能为空"
) @Max(
value = 10000000L,
message = "超过最大金额限制"
) @Min(
value = 1L,
message = "小于最小充值金额"
) BigDecimal amount;
@ApiModelProperty("清空类型 清空-1 清空至-2")
private Integer clearType = 1;
}

View File

@ -11,6 +11,8 @@ import java.util.List;
@Data @Data
public class AccountInfoQueryParam extends BaseEntity { public class AccountInfoQueryParam extends BaseEntity {
@ApiModelProperty("用户编号集合")
private List<Long> userIds;
@ApiModelProperty("账户状态 1正常 2停用") @ApiModelProperty("账户状态 1正常 2停用")
private List<Integer> accStatusList; private List<Integer> accStatusList;
@ApiModelProperty("筛选钱包类型 0-账户总余额 1-个人钱包 2-补贴钱包") @ApiModelProperty("筛选钱包类型 0-账户总余额 1-个人钱包 2-补贴钱包")

View File

@ -29,6 +29,8 @@ public class AccInfoDetailsVO {
private Integer userType; private Integer userType;
@ApiModelProperty("用户部门") @ApiModelProperty("用户部门")
private String deptName; private String deptName;
@ApiModelProperty("用户部门")
private Long deptId;
@ApiModelProperty("用户类别(展示)") @ApiModelProperty("用户类别(展示)")
@Excel(name = "用户类别(展示)") @Excel(name = "用户类别(展示)")
private String userTypeName; private String userTypeName;
@ -43,9 +45,6 @@ public class AccInfoDetailsVO {
@ApiModelProperty("补贴钱包余额/分") @ApiModelProperty("补贴钱包余额/分")
@Excel(name = "补贴钱包余额/分") @Excel(name = "补贴钱包余额/分")
private BigDecimal subsidyBal; private BigDecimal subsidyBal;
@ApiModelProperty("红包余额")
@Excel(name = "红包余额")
private BigDecimal redEnvelope;
@ApiModelProperty("冻结金额") @ApiModelProperty("冻结金额")
@Excel(name = "冻结金额") @Excel(name = "冻结金额")
private BigDecimal accFreezeBalTotal; private BigDecimal accFreezeBalTotal;
@ -55,7 +54,7 @@ public class AccInfoDetailsVO {
@ApiModelProperty("补贴钱包允许最低余额限制") @ApiModelProperty("补贴钱包允许最低余额限制")
@Excel(name = "补贴钱包允许最低余额限制") @Excel(name = "补贴钱包允许最低余额限制")
private BigDecimal minSubBalLimit; private BigDecimal minSubBalLimit;
@ApiModelProperty("账户状态 1正常 2冻结 3销户 4过期") @ApiModelProperty("账户状态 1正常 2冻结")
private Integer accStatus; private Integer accStatus;
@ApiModelProperty("账户状态名称") @ApiModelProperty("账户状态名称")
@Excel(name = "账户状态名称") @Excel(name = "账户状态名称")

View File

@ -76,4 +76,7 @@ public interface AccInfoMapper extends BaseMapper<AccInfo> {
AccInfoDetailsVO queryAccInfoBalanceSum(@Param("accountInfoQueryParam") AccountInfoQueryParam accountInfoQueryParam); AccInfoDetailsVO queryAccInfoBalanceSum(@Param("accountInfoQueryParam") AccountInfoQueryParam accountInfoQueryParam);
void updateAccInfoEndDateByUserId(LocalDate endDate, List<Long> userIds, String updateBy); void updateAccInfoEndDateByUserId(LocalDate endDate, List<Long> userIds, String updateBy);
List<AccInfoDetailsVO> queryAccInfoByUserIds(@Param("userIds") List<Long> userIds);
AccInfoDetailsVO queryAccInfoByUserId(@Param("userId") Long userId);
} }

View File

@ -21,6 +21,7 @@ public interface AccWalletInfoMapper {
* @return 钱包详情信息 * @return 钱包详情信息
*/ */
public AccWalletInfo selectAccWalletInfoByUserId(Long userId); public AccWalletInfo selectAccWalletInfoByUserId(Long userId);
public AccWalletInfo selectAccWalletInfoByUserIdAndWalletId(Long userId, Integer walletId);
List<AccWalletInfoVO> selectAccWalletInfoByUserIds(@Param("userIds") List<Long> userIds); List<AccWalletInfoVO> selectAccWalletInfoByUserIds(@Param("userIds") List<Long> userIds);
@ -72,4 +73,12 @@ public interface AccWalletInfoMapper {
@Param("updateBy") String updateBy, @Param("updateBy") String updateBy,
@Param("updateTime") LocalDate updateTime); @Param("updateTime") LocalDate updateTime);
void addAccWalletInfo(@Param("amount") BigDecimal amount,
@Param("userId") Long userId,
@Param("walletId") Integer walletId
);
void reduceAccWalletInfo(@Param("amount") BigDecimal amount,
@Param("userId") Long userId,
@Param("walletId") Integer walletId
);
} }

View File

@ -0,0 +1,17 @@
package com.bonus.canteen.core.account.service;
import com.bonus.canteen.core.account.domain.param.AccSubsidyParam;
import org.springframework.web.multipart.MultipartFile;
import java.math.BigDecimal;
import java.util.List;
public interface AccSubService {
void individualAccSubsidyAdd(AccSubsidyParam param);
void individualAccSubsidyClear(AccSubsidyParam param);
void batchAccSubsidyAdd(AccSubsidyParam param);
void batchAccSubsidyClear(AccSubsidyParam param);
}

View File

@ -10,6 +10,7 @@ import com.bonus.canteen.core.account.domain.param.AccountInfoQueryParam;
import com.bonus.canteen.core.account.domain.vo.AccInfoDetailsVO; import com.bonus.canteen.core.account.domain.vo.AccInfoDetailsVO;
import com.bonus.canteen.core.account.domain.vo.AccInfoInvalidSumVO; import com.bonus.canteen.core.account.domain.vo.AccInfoInvalidSumVO;
import com.bonus.system.api.domain.SysUser; import com.bonus.system.api.domain.SysUser;
import org.apache.ibatis.annotations.Param;
/** /**
* 账户资料Service接口 * 账户资料Service接口
@ -70,4 +71,7 @@ public interface IAccInfoService {
public String getOrderQRCode(); public String getOrderQRCode();
public WalletBalanceVO queryWalletBalance(AccInfo accInfo); public WalletBalanceVO queryWalletBalance(AccInfo accInfo);
List<AccInfoDetailsVO> queryAccInfoByUserIds(List<Long> userIds);
AccInfoDetailsVO queryAccInfoByUserId(Long userId);
void checkAccStatus(AccInfoDetailsVO accInfoVO);
} }

View File

@ -6,6 +6,7 @@ import java.util.Map;
import com.bonus.canteen.core.account.domain.AccWalletInfo; import com.bonus.canteen.core.account.domain.AccWalletInfo;
import com.bonus.canteen.core.account.domain.vo.AccWalletInfoVO; import com.bonus.canteen.core.account.domain.vo.AccWalletInfoVO;
import org.apache.ibatis.annotations.Param;
/** /**
* 钱包详情信息Service接口 * 钱包详情信息Service接口
@ -21,6 +22,7 @@ public interface IAccWalletInfoService {
* @return 钱包详情信息 * @return 钱包详情信息
*/ */
public AccWalletInfo selectAccWalletInfoByUserId(Long userId); public AccWalletInfo selectAccWalletInfoByUserId(Long userId);
public AccWalletInfo selectAccWalletInfoByUserIdAndWalletId(Long userId, Integer walletId);
/** /**
* 查询钱包详情信息列表 * 查询钱包详情信息列表
@ -67,5 +69,6 @@ public interface IAccWalletInfoService {
Map<Long, List<AccWalletInfoVO>> selectAccWalletInfoByUserIds(List<Long> userIds); Map<Long, List<AccWalletInfoVO>> selectAccWalletInfoByUserIds(List<Long> userIds);
void updateMinBalance(List<Long> userIds, Integer walletId, BigDecimal minBalance); void updateMinBalance(List<Long> userIds, Integer walletId, BigDecimal minBalance);
void addAccWalletInfo(BigDecimal amount, Long userId, Integer walletId);
void reduceAccWalletInfo(BigDecimal amount, Long userId, Integer walletId);
} }

View File

@ -8,8 +8,7 @@ import java.util.stream.Collectors;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil; import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.*;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.bonus.canteen.core.account.constants.AccWalletIdEnum; import com.bonus.canteen.core.account.constants.AccWalletIdEnum;
import com.bonus.canteen.core.account.constants.AccStatusEnum; import com.bonus.canteen.core.account.constants.AccStatusEnum;
@ -57,7 +56,6 @@ import javax.annotation.Resource;
* @date 2025-04-05 * @date 2025-04-05
*/ */
@Service @Service
@Slf4j
public class AccInfoServiceImpl implements IAccInfoService { public class AccInfoServiceImpl implements IAccInfoService {
private static final Logger log = LoggerFactory.getLogger(AccInfoServiceImpl.class); private static final Logger log = LoggerFactory.getLogger(AccInfoServiceImpl.class);
@ -94,6 +92,9 @@ public class AccInfoServiceImpl implements IAccInfoService {
@Override @Override
public List<AccInfoDetailsVO> selectAccInfoList(AccountInfoQueryParam accountInfoQueryParam) { public List<AccInfoDetailsVO> selectAccInfoList(AccountInfoQueryParam accountInfoQueryParam) {
String encryptedSearchValue = SM4EncryptUtils.sm4Encrypt(accountInfoQueryParam.getSearchValue()); String encryptedSearchValue = SM4EncryptUtils.sm4Encrypt(accountInfoQueryParam.getSearchValue());
if(CollUtil.isEmpty(accountInfoQueryParam.getAccStatusList())) {
accountInfoQueryParam.setAccStatusList(Arrays.asList(AccStatusEnum.NORMAL.getKey(), AccStatusEnum.DEACTIVATE.getKey()));
}
List<AccInfoDetailsVO> list = accInfoMapper.queryAccInfoDetails(accountInfoQueryParam, encryptedSearchValue); List<AccInfoDetailsVO> list = accInfoMapper.queryAccInfoDetails(accountInfoQueryParam, encryptedSearchValue);
handleResult(list); handleResult(list);
return list; return list;
@ -292,6 +293,9 @@ public class AccInfoServiceImpl implements IAccInfoService {
} }
public AccInfoDetailsVO queryAccInfoBalanceSum(AccountInfoQueryParam accountInfoQueryParam) { public AccInfoDetailsVO queryAccInfoBalanceSum(AccountInfoQueryParam accountInfoQueryParam) {
if(CollUtil.isEmpty(accountInfoQueryParam.getAccStatusList())) {
accountInfoQueryParam.setAccStatusList(Arrays.asList(AccStatusEnum.NORMAL.getKey(), AccStatusEnum.DEACTIVATE.getKey()));
}
return accInfoMapper.queryAccInfoBalanceSum(accountInfoQueryParam); return accInfoMapper.queryAccInfoBalanceSum(accountInfoQueryParam);
} }
@ -326,11 +330,36 @@ public class AccInfoServiceImpl implements IAccInfoService {
return walletBalanceVO; return walletBalanceVO;
} }
protected void setAccInfoVODetailList(AccInfoVo accInfoVo, List<AccWalletInfo> walletInfoList) { @Override
if (ObjectUtil.isNotEmpty(walletInfoList)) { public List<AccInfoDetailsVO> queryAccInfoByUserIds(List<Long> userIds) {
accInfoVo.setWalletBal(walletInfoList.stream().filter(o -> o.getWalletId().intValue() == AccWalletIdEnum.WALLET.getKey()).mapToLong(AccWalletInfo::getWalletBal).sum()); return accInfoMapper.queryAccInfoByUserIds(userIds);
accInfoVo.setSubsidyBal(walletInfoList.stream().filter(o -> o.getWalletId().intValue() == AccWalletIdEnum.SUBSIDY.getKey()).mapToLong(AccWalletInfo::getWalletBal).sum()); }
accInfoVo.setAccAllBal(accInfoVo.getWalletBal() + accInfoVo.getSubsidyBal()); @Override
public AccInfoDetailsVO queryAccInfoByUserId(Long userId) {
return accInfoMapper.queryAccInfoByUserId(userId);
}
@Override
public void checkAccStatus(AccInfoDetailsVO accInfoVO) {
if (ObjectUtil.isNull(accInfoVO)) {
throw new ServiceException("账户不存在");
}
AccStatusEnum statusEnum = AccStatusEnum.getEnum(accInfoVO.getAccStatus());
if (ObjectUtil.isNull(statusEnum)) {
throw new ServiceException("账户状态异常");
}
if (statusEnum == AccStatusEnum.DEACTIVATE) {
throw new ServiceException("账户已停用");
} }
} }
protected void setAccInfoVODetailList(AccInfoVo accInfoVo, List<AccWalletInfo> walletInfoList) {
// if (ObjectUtil.isNotEmpty(walletInfoList)) {
// accInfoVo.setWalletBal(walletInfoList.stream().filter(o -> o.getWalletId().intValue() == AccWalletIdEnum.WALLET.getKey()).mapToLong(AccWalletInfo::getWalletBal).sum());
// accInfoVo.setSubsidyBal(walletInfoList.stream().filter(o -> o.getWalletId().intValue() == AccWalletIdEnum.SUBSIDY.getKey()).mapToLong(AccWalletInfo::getWalletBal).sum());
// accInfoVo.setAccAllBal(accInfoVo.getWalletBal() + accInfoVo.getSubsidyBal());
// }
}
} }

View File

@ -0,0 +1,202 @@
package com.bonus.canteen.core.account.service.impl;
import cn.hutool.core.collection.CollUtil;
import com.bonus.canteen.core.account.constants.AccWalletClearTypeEnum;
import com.bonus.canteen.core.account.constants.AccWalletIdEnum;
import com.bonus.canteen.core.account.domain.AccWalletInfo;
import com.bonus.canteen.core.account.domain.param.AccSubsidyParam;
import com.bonus.canteen.core.account.domain.vo.AccInfoDetailsVO;
import com.bonus.canteen.core.account.service.AccSubService;
import com.bonus.canteen.core.account.service.IAccInfoService;
import com.bonus.canteen.core.account.service.IAccWalletInfoService;
import com.bonus.canteen.core.account.utils.AccRedisUtils;
import com.bonus.common.core.exception.ServiceException;
import com.bonus.common.houqin.i18n.I18n;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.List;
import java.util.Objects;
@Service
public class AccSubServiceImpl implements AccSubService {
private static final Logger log = LoggerFactory.getLogger(AccSubServiceImpl.class);
@Autowired
@Lazy
private IAccInfoService accInfoService;
@Autowired
@Lazy
private IAccWalletInfoService accWalletInfoService;
@Resource(
name = "smartCanteenTaskExecutor"
)
@Lazy
private AsyncTaskExecutor asyncTaskExecutor;
@Transactional(
rollbackFor = {Exception.class}
)
public void individualAccSubsidyAdd(AccSubsidyParam param) {
AccInfoDetailsVO accInfoVO = this.accInfoService.queryAccInfoByUserId(param.getUserId());
if(Objects.isNull(accInfoVO)) {
throw new ServiceException("账户不存在");
}
accInfoService.checkAccStatus(accInfoVO);
addAccWalletBalance(param.getAmount(), param.getUserId(), AccWalletIdEnum.SUBSIDY.getKey());
}
@Transactional(
rollbackFor = {Exception.class}
)
public void individualAccSubsidyClear(AccSubsidyParam param) {
AccInfoDetailsVO accInfoVO = this.accInfoService.queryAccInfoByUserId(param.getUserId());
if(Objects.isNull(accInfoVO)) {
throw new ServiceException("账户不存在");
}
accInfoService.checkAccStatus(accInfoVO);
this.clearAccSubsidyHandler(accInfoVO, param);
}
private void clearAccSubsidyHandler(AccInfoDetailsVO accInfoVO, AccSubsidyParam param) {
AccWalletInfo walletInfo = accWalletInfoService.selectAccWalletInfoByUserIdAndWalletId(accInfoVO.getUserId(), AccWalletIdEnum.SUBSIDY.getKey());
if(Objects.isNull(walletInfo)) {
throw new ServiceException("补贴钱包不存在");
}
BigDecimal walletBal = walletInfo.getWalletBal();
if (walletBal.compareTo(BigDecimal.ZERO) <= 0) {
throw new ServiceException(I18n.getMessage("补贴钱包余额不足"));
} else {
BigDecimal clearAmount = this.calculateClearAmount(walletBal, param.getClearType(), param.getAmount());
if (clearAmount.compareTo(BigDecimal.ZERO) <= 0) {
throw new ServiceException(I18n.getMessage("清空后补贴钱包余额小于等于0"));
} else {
reduceAccWalletBalance(clearAmount, param.getUserId(), AccWalletIdEnum.SUBSIDY.getKey());
}
}
}
private BigDecimal calculateClearAmount(BigDecimal walletBal, Integer clearType, BigDecimal amount) {
BigDecimal clearAmount;
if (AccWalletClearTypeEnum.CLEAR_BY.getKey().equals(clearType)) {
clearAmount = amount.compareTo(walletBal) >= 0 ? BigDecimal.ZERO : walletBal.subtract(amount);
} else {
clearAmount = amount.compareTo(walletBal) >= 0 ? walletBal : amount;
}
return clearAmount.compareTo(BigDecimal.ZERO) <= 0 ? BigDecimal.ZERO : clearAmount;
}
public void batchAccSubsidyClear(AccSubsidyParam param) {
log.info("批量清空补贴用户数量:{}", param.getUserIds().size());
if(CollUtil.isEmpty(param.getUserIds())) {
throw new ServiceException("批量清空补贴用户为空");
}
List<AccInfoDetailsVO> accInfoVOList = this.accInfoService.queryAccInfoByUserIds(param.getUserIds());
log.info("批量清空补贴根据用户编号查询到{}人", accInfoVOList.size());
if (CollUtil.isEmpty(accInfoVOList)) {
throw new ServiceException("批量清空补贴查询到的用户为空");
} else {
this.asyncTaskExecutor.execute(() -> {
this.batchAccSubsidyClearHandler(accInfoVOList, param.getAmount(), param.getClearType());
});
}
}
public void batchAccSubsidyAdd(AccSubsidyParam param) {
log.info("批量补贴用户数量:{}", param.getUserIds().size());
if(CollUtil.isEmpty(param.getUserIds())) {
throw new ServiceException("批量补贴用户为空");
}
List<AccInfoDetailsVO> accInfoVOList = this.accInfoService.queryAccInfoByUserIds(param.getUserIds());
log.info("批量补贴根据用户编号查询到{}人", accInfoVOList.size());
if (CollUtil.isEmpty(accInfoVOList)) {
throw new ServiceException("批量补贴查询到的用户为空");
} else {
this.asyncTaskExecutor.execute(() -> {
this.batchAccSubsidyAddHandler(accInfoVOList, param.getAmount());
});
}
}
private void batchAccSubsidyAddHandler(List<AccInfoDetailsVO> accInfoDetails, BigDecimal amount) {
log.info("批量补贴开始:{}", accInfoDetails.size());
AccRedisUtils.lockBatchUpdateAccWallet();
try {
accInfoDetails.forEach((accInfo) -> {
try {
AccInfoDetailsVO accInfoVO = this.accInfoService.queryAccInfoByUserId(accInfo.getUserId());
if(Objects.isNull(accInfoVO)) {
throw new ServiceException(accInfo.getNickName() + "的账户不存在");
}
accInfoService.checkAccStatus(accInfoVO);
addAccWalletBalance(amount, accInfo.getUserId(), AccWalletIdEnum.SUBSIDY.getKey());
} catch (Exception var10) {
log.error("批量补贴充值异常", var10);
}
});
} catch (Exception ex) {
log.error("批量补贴系统异常", ex);
} finally {
AccRedisUtils.unlockBatchUpdateAccWallet();
}
log.info("批量补贴结束");
}
private void batchAccSubsidyClearHandler(List<AccInfoDetailsVO> accInfoDetails, BigDecimal amount, Integer clearType) {
log.info("批量清空补贴开始:{}", accInfoDetails.size());
AccRedisUtils.lockBatchUpdateAccWallet();
try {
accInfoDetails.forEach((accInfo) -> {
try {
AccInfoDetailsVO accInfoVO = this.accInfoService.queryAccInfoByUserId(accInfo.getUserId());
if(Objects.isNull(accInfoVO)) {
throw new ServiceException(accInfo.getNickName() + "的账户不存在");
}
accInfoService.checkAccStatus(accInfoVO);
AccSubsidyParam param = new AccSubsidyParam();
param.setAmount(amount);
param.setUserId(accInfo.getUserId());
param.setClearType(clearType);
clearAccSubsidyHandler(accInfoVO, param);
} catch (Exception var10) {
log.error("批量清空补贴充值异常", var10);
}
});
} catch (Exception ex) {
log.error("批量清空补贴系统异常", ex);
} finally {
AccRedisUtils.unlockBatchUpdateAccWallet();
}
log.info("批量清空补贴结束");
}
public void addAccWalletBalance(BigDecimal amount, Long userId, Integer walletId) {
log.info("新增补贴入参: amount{} userId{}walletId{}", amount, userId, walletId);
AccRedisUtils.lockUpdateAccWalletBalance(userId);
try {
accWalletInfoService.addAccWalletInfo(amount, userId, walletId);
log.info("新增补贴结束");
} finally {
AccRedisUtils.unlockUpdateAccWalletBalance(userId);
}
}
public void reduceAccWalletBalance(BigDecimal amount, Long userId, Integer walletId) {
log.info("清空补贴入参: amount{} userId{}walletId{}", amount, userId, walletId);
AccRedisUtils.lockUpdateAccWalletBalance(userId);
try {
accWalletInfoService.reduceAccWalletInfo(amount, userId, walletId);
log.info("清空补贴结束");
} finally {
AccRedisUtils.unlockUpdateAccWalletBalance(userId);
}
}
}

View File

@ -43,6 +43,11 @@ public class AccWalletInfoServiceImpl implements IAccWalletInfoService {
return accWalletInfoMapper.selectAccWalletInfoByUserId(userId); return accWalletInfoMapper.selectAccWalletInfoByUserId(userId);
} }
@Override
public AccWalletInfo selectAccWalletInfoByUserIdAndWalletId(Long userId, Integer walletId) {
return accWalletInfoMapper.selectAccWalletInfoByUserIdAndWalletId(userId, walletId);
}
/** /**
* 查询钱包详情信息列表 * 查询钱包详情信息列表
* *
@ -146,5 +151,13 @@ public class AccWalletInfoServiceImpl implements IAccWalletInfoService {
} }
} }
@Override
public void addAccWalletInfo(BigDecimal amount, Long userId, Integer walletId) {
accWalletInfoMapper.addAccWalletInfo(amount, userId, walletId);
}
@Override
public void reduceAccWalletInfo(BigDecimal amount, Long userId, Integer walletId) {
accWalletInfoMapper.reduceAccWalletInfo(amount, userId, walletId);
}
} }

View File

@ -0,0 +1,50 @@
package com.bonus.canteen.core.account.utils;
import com.bonus.canteen.core.common.utils.RedisUtil;
import com.bonus.canteen.core.common.utils.TenantContextHolder;
import com.bonus.common.core.exception.ServiceException;
import com.bonus.common.houqin.constant.GlobalConstants;
import com.bonus.common.houqin.constant.LeConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AccRedisUtils {
private static final Logger log = LoggerFactory.getLogger(AccRedisUtils.class);
public static void lockUpdateAccWalletBalance(Long userId) {
String lockerKey = generateWalletLockerKey(userId);
if (!RedisUtil.tryLock(lockerKey, 10, 15)) {
throw new ServiceException("账户有交易正在进行中");
}
}
public static void unlockUpdateAccWalletBalance(Long userId) {
String lockerKey = generateWalletLockerKey(userId);
try {
RedisUtil.safeUnLock(lockerKey);
} catch (Exception ex) {
log.error("账户操作解锁异常", ex);
}
}
public static void lockBatchUpdateAccWallet() {
String lockerKey = generateBatchUpdateAccWalletKey();
if (!RedisUtil.setNx(lockerKey, 1, 3600)) {
throw new ServiceException("已有任务在执行,请稍后~");
}
}
public static void unlockBatchUpdateAccWallet() {
RedisUtil.delete(generateBatchUpdateAccWalletKey());
}
private static String generateWalletLockerKey(Long userId) {
return String.format("sc:acc_lock_merchant_%s_user_%s_wallet_update",
GlobalConstants.TENANT_ID, userId);
}
private static String generateBatchUpdateAccWalletKey() {
return String.format("sc:acc_lock_merchant_%s_wallet_batch_update",
GlobalConstants.TENANT_ID);
}
}

View File

@ -0,0 +1,247 @@
package com.bonus.canteen.core.common.utils;
import cn.hutool.core.collection.CollUtil;
import com.bonus.common.core.exception.ServiceException;
import com.bonus.common.houqin.utils.SpringContextHolder;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.support.atomic.RedisAtomicInteger;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
public class RedisUtil {
private static final Logger log = LoggerFactory.getLogger(RedisUtil.class);
public static void setString(String key, String value) {
setString(key, value, (Long)null);
}
public static void setString(String key, String value, Long timeOut) {
timeOut = timeoutFilter(timeOut);
if (value != null) {
if (timeOut != null) {
stringRedisTemplate().opsForValue().set(key, value, timeOut, TimeUnit.SECONDS);
} else {
stringRedisTemplate().opsForValue().set(key, value);
}
}
}
public static Boolean setIfPresentString(String key, String value, Long timeOut) {
timeOut = timeoutFilter(timeOut);
if (value != null) {
return timeOut != null ? stringRedisTemplate().opsForValue().setIfPresent(key, value, timeOut, TimeUnit.SECONDS) : stringRedisTemplate().opsForValue().setIfPresent(key, value);
} else {
return false;
}
}
public static boolean setNx(String key, Object value, int expireTime) {
Boolean ifSucc = redisTemplate().opsForValue().setIfAbsent(key, value, (long)expireTime, TimeUnit.SECONDS);
return ifSucc == null ? false : ifSucc;
}
public static String getString(String key) {
return (String)stringRedisTemplate().opsForValue().get(key);
}
public static void setObj(String key, Object value) {
setObj(key, value, (Long)null);
}
public static void setObj(String key, Object value, Long timeOut) {
timeOut = timeoutFilter(timeOut);
if (value != null) {
if (timeOut != null) {
redisTemplate().opsForValue().set(key, value, timeOut, TimeUnit.SECONDS);
} else {
redisTemplate().opsForValue().set(key, value);
}
}
}
public static Object getObj(String key) {
return redisTemplate().opsForValue().get(key);
}
public static void delete(String key) {
redisTemplate().delete(key);
}
public static void deleteByPattern(String keyPattern) {
Set<Object> keys = redisTemplate().keys(keyPattern);
if (CollUtil.isNotEmpty(keys)) {
redisTemplate().delete(keys);
}
}
private static Long timeoutFilter(Long timeout) {
Long maxExpSecond = (Long)environment().getProperty("spring.redis.custom-max-expiration-second", Long.class);
if (maxExpSecond != null && (timeout == null || timeout > maxExpSecond)) {
timeout = maxExpSecond;
}
return timeout;
}
public static List<String> keysByPattern(String pattern) {
Set<Object> keys = redisTemplate().keys(pattern);
return (List)(CollUtil.isEmpty(keys) ? new ArrayList() : (List)keys.stream().map(Object::toString).collect(Collectors.toList()));
}
public static void multiSet(Map map) {
redisTemplate().opsForValue().multiSet(map);
}
public static List multiGet(List keys) {
return redisTemplate().opsForValue().multiGet(keys);
}
public static void zAdd(String key, Object value, double score) {
redisTemplate().opsForZSet().add(key, value, score);
}
public static Set<ZSetOperations.TypedTuple<Object>> zGetList(String key, long start, long end) {
return redisTemplate().opsForZSet().rangeWithScores(key, start, end);
}
private static StringRedisTemplate stringRedisTemplate() {
return (StringRedisTemplate) SpringContextHolder.getBean(StringRedisTemplate.class);
}
private static RedisTemplate<Object, Object> redisTemplate() {
return (RedisTemplate)SpringContextHolder.getBean("redisTemplate");
}
private static Environment environment() {
return (Environment)SpringContextHolder.getBean(Environment.class);
}
public static Integer incr(String key, Long liveTime) {
return getRedisAtomicInteger(key, liveTime, true);
}
public static Integer getRedisAtomicInteger(String key, Long liveTime, Boolean incrDecrFlag) {
RedisConnectionFactory connectionFactory = redisTemplate().getConnectionFactory();
if (connectionFactory == null) {
throw new ServiceException("Redis连接异常");
} else {
RedisAtomicInteger entityIdCounter = new RedisAtomicInteger(key, connectionFactory);
if (incrDecrFlag == null) {
return entityIdCounter.get();
} else {
int increment;
if (incrDecrFlag) {
increment = entityIdCounter.getAndIncrement();
if (liveTime > 0L) {
entityIdCounter.expire(liveTime, TimeUnit.SECONDS);
}
return increment + 1;
} else {
increment = entityIdCounter.getAndDecrement();
if (liveTime > 0L) {
entityIdCounter.expire(liveTime, TimeUnit.SECONDS);
}
return increment - 1;
}
}
}
}
public static RLock getLock(String key) {
return redissonClient().getLock(key);
}
private static RedissonClient redissonClient() {
return (RedissonClient)SpringContextHolder.getBean(RedissonClient.class);
}
@Deprecated
public static boolean tryLock(String key) {
return tryLock(key, (Integer)null, (Integer)null);
}
/** @deprecated */
@Deprecated
public static boolean tryLock(String key, Integer waitSecond) {
return tryLock(key, waitSecond, (Integer)null);
}
/** @deprecated */
@Deprecated
public static boolean tryLock(String key, Integer waitSecond, Integer expireSecond) {
if (waitSecond == null && expireSecond != null) {
throw new ServiceException("不能这样使用");
} else {
RLock lock = redissonClient().getLock(key);
try {
if (waitSecond != null && expireSecond != null) {
return lock.tryLock((long)waitSecond, (long)expireSecond, TimeUnit.SECONDS);
} else {
return waitSecond != null ? lock.tryLock((long)waitSecond, TimeUnit.SECONDS) : lock.tryLock();
}
} catch (InterruptedException var5) {
log.error("【分布式锁】tryLock失败{}", var5.getMessage());
return false;
}
}
}
@Deprecated
public static void safeUnLock(String key) {
RLock lock = redissonClient().getLock(key);
if (lock.isHeldByCurrentThread() && lock.isLocked()) {
lock.unlock();
}
}
public static Long getIncr(RedisTemplate<Object, Object> redisTemplate, String key) {
RedisAtomicLong counter = new RedisAtomicLong(key, (RedisConnectionFactory) Objects.requireNonNull(redisTemplate.getConnectionFactory()));
long increment = counter.getAndIncrement();
if (increment <= 0L) {
setIncr(redisTemplate, key, 2);
return 1L;
} else {
LocalDateTime now = LocalDateTime.now();
LocalDateTime max = LocalDate.now().atTime(LocalTime.MAX);
long millis = Duration.between(now, max).toMillis();
counter.expire(millis, TimeUnit.MILLISECONDS);
return increment;
}
}
public static void setIncr(RedisTemplate<Object, Object> redisTemplate, String key, int value) {
RedisAtomicLong counter = new RedisAtomicLong(key, (RedisConnectionFactory)Objects.requireNonNull(redisTemplate.getConnectionFactory()));
counter.set((long)value);
long millis = Duration.between(LocalDateTime.now(), LocalDate.now().atTime(LocalTime.MAX)).toMillis();
counter.expire(millis, TimeUnit.MILLISECONDS);
}
public static void lock(String key) {
RLock lock = redissonClient().getLock(key);
lock.lock();
}
}

View File

@ -499,4 +499,35 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
#{userId} #{userId}
</foreach> </foreach>
</update> </update>
<select id="queryAccInfoByUserIds" resultType="com.bonus.canteen.core.account.domain.vo.AccInfoDetailsVO">
SELECT ai.acc_id,
ai.user_id,
ai.end_date,
ai.acc_status,
su.nick_name,
su.phonenumber,
su.dept_id
FROM acc_info ai
LEFT JOIN sys_user su ON su.user_id = ai.user_id
LEFT JOIN sys_dept co ON co.dept_id = su.dept_id
WHERE ai.user_id in
<foreach collection="userIds" item="userId" separator="," open="(" close=")">
#{userId}
</foreach>
</select>
<select id="queryAccInfoByUserId" resultType="com.bonus.canteen.core.account.domain.vo.AccInfoDetailsVO">
SELECT ai.acc_id,
ai.user_id,
ai.end_date,
ai.acc_status,
su.nick_name,
su.phonenumber,
su.dept_id
FROM acc_info ai
LEFT JOIN sys_user su ON su.user_id = ai.user_id
LEFT JOIN sys_dept co ON co.dept_id = su.dept_id
WHERE ai.user_id = #{userId}
</select>
</mapper> </mapper>

View File

@ -41,6 +41,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where user_id = #{userId} where user_id = #{userId}
</select> </select>
<select id="selectAccWalletInfoByUserIdAndWalletId" resultMap="AccWalletInfoResult">
<include refid="selectAccWalletInfoVo"/>
where user_id = #{userId} and wallet_id = #{walletId}
</select>
<select id="selectAccWalletInfoByUserIds" resultType="com.bonus.canteen.core.account.domain.vo.AccWalletInfoVO"> <select id="selectAccWalletInfoByUserIds" resultType="com.bonus.canteen.core.account.domain.vo.AccWalletInfoVO">
SELECT SELECT
user_id, user_id,
@ -149,4 +154,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
AND wallet_id = #{walletId} AND wallet_id = #{walletId}
</where> </where>
</update> </update>
<update id="addAccWalletInfo">
UPDATE acc_wallet_info
SET wallet_bal = wallet_bal + #{amount}
WHERE user_id = #{userId}
AND wallet_id = #{walletId}
</update>
<update id="reduceAccWalletInfo">
UPDATE acc_wallet_info
SET wallet_bal = wallet_bal - #{amount}
WHERE user_id = #{custId}
AND wallet_id = #{walletId}
</update>
</mapper> </mapper>