diff --git a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/constants/AccWalletClearTypeEnum.java b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/constants/AccWalletClearTypeEnum.java new file mode 100644 index 0000000..183947b --- /dev/null +++ b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/constants/AccWalletClearTypeEnum.java @@ -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; + } +} diff --git a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/controller/AccSubsidyController.java b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/controller/AccSubsidyController.java new file mode 100644 index 0000000..528f7f2 --- /dev/null +++ b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/controller/AccSubsidyController.java @@ -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(); + } +} diff --git a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/domain/AccInfoVo.java b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/domain/AccInfoVo.java index 00a457b..fcd1e5b 100644 --- a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/domain/AccInfoVo.java +++ b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/domain/AccInfoVo.java @@ -19,20 +19,20 @@ public class AccInfoVo { @ApiModelProperty("账户状态 1正常 2冻结 3销户 4过期") private Integer accStatus; @ApiModelProperty("账户可用总余额(不包括冻结金额)") - private Long accBalTotal; + private BigDecimal accBalTotal; @ApiModelProperty("账户总余额(包含冻结金额)") - private Long accAllBal; + private BigDecimal accAllBal; @ApiModelProperty("个人钱包(可用)余额/分") - private Long walletBal; + private BigDecimal walletBal; @ApiModelProperty("补贴钱包(可用)余额/分") - private Long subsidyBal; + private BigDecimal subsidyBal; @ApiModelProperty("红包余额") - private Long redEnvelope; + private BigDecimal redEnvelope; @ApiModelProperty("个人钱包冻结金额") - private Long walletFreezeBal; + private BigDecimal walletFreezeBal; @ApiModelProperty("补贴钱包冻结金额") - private Long subFreezeBal; - private Long accFreezeBalTotal; + private BigDecimal subFreezeBal; + private BigDecimal accFreezeBalTotal; private List walletInfoList; } diff --git a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/domain/AccWalletInfo.java b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/domain/AccWalletInfo.java index fe39f92..8b20d4a 100644 --- a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/domain/AccWalletInfo.java +++ b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/domain/AccWalletInfo.java @@ -1,5 +1,6 @@ package com.bonus.canteen.core.account.domain; +import java.math.BigDecimal; import java.util.Date; import com.fasterxml.jackson.annotation.JsonFormat; import com.bonus.common.core.annotation.Excel; @@ -35,17 +36,17 @@ public class AccWalletInfo extends BaseEntity { /** 钱包余额/分 */ @Excel(name = "钱包余额/分") @ApiModelProperty(value = "钱包余额/分") - private Long walletBal; + private BigDecimal walletBal; /** 最低余额限制/分 */ @Excel(name = "最低余额限制/分") @ApiModelProperty(value = "最低余额限制/分") - private Long limitBalance; + private BigDecimal limitBalance; /** 冻结金额 */ @Excel(name = "冻结金额") @ApiModelProperty(value = "冻结金额") - private Long frozenBalance; + private BigDecimal frozenBalance; /** 过期时间 */ @ApiModelProperty(value = "过期时间") diff --git a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/domain/WalletBalanceVO.java b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/domain/WalletBalanceVO.java index 791ed96..c2fa7d2 100644 --- a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/domain/WalletBalanceVO.java +++ b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/domain/WalletBalanceVO.java @@ -7,21 +7,21 @@ import java.math.BigDecimal; @Data public class WalletBalanceVO { @ApiModelProperty("个人钱包余额/分") - private Long walletBal; + private BigDecimal walletBal; @ApiModelProperty("补贴钱包余额/分") - private Long subsidyBal; + private BigDecimal subsidyBal; @ApiModelProperty("红包余额") - private Long redEnvelope; + private BigDecimal redEnvelope; @ApiModelProperty("个人钱包冻结金额") - private Long walletFreezeBal; + private BigDecimal walletFreezeBal; @ApiModelProperty("补贴钱包冻结金额") - private Long subFreezeBal; + private BigDecimal subFreezeBal; @ApiModelProperty("冻结金额") - private Long accFreezeBalTotal; + private BigDecimal accFreezeBalTotal; @ApiModelProperty("账户总余额(包含冻结金额)") - private Long accAllBal; + private BigDecimal accAllBal; @ApiModelProperty("账户可用余额总余额(不包括冻结金额)") - private Long accBalTotal; + private BigDecimal accBalTotal; @ApiModelProperty("账户状态 1正常 2冻结 3销户 4过期") private Integer accStatus; diff --git a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/domain/param/AccSubsidyParam.java b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/domain/param/AccSubsidyParam.java new file mode 100644 index 0000000..6f8f824 --- /dev/null +++ b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/domain/param/AccSubsidyParam.java @@ -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 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; +} diff --git a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/domain/param/AccountInfoQueryParam.java b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/domain/param/AccountInfoQueryParam.java index a15c9d5..900dc6b 100644 --- a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/domain/param/AccountInfoQueryParam.java +++ b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/domain/param/AccountInfoQueryParam.java @@ -11,6 +11,8 @@ import java.util.List; @Data public class AccountInfoQueryParam extends BaseEntity { + @ApiModelProperty("用户编号集合") + private List userIds; @ApiModelProperty("账户状态 1正常 2停用") private List accStatusList; @ApiModelProperty("筛选钱包类型 0-账户总余额 1-个人钱包 2-补贴钱包") diff --git a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/domain/vo/AccInfoDetailsVO.java b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/domain/vo/AccInfoDetailsVO.java index 7bc2adc..8414a40 100644 --- a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/domain/vo/AccInfoDetailsVO.java +++ b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/domain/vo/AccInfoDetailsVO.java @@ -29,6 +29,8 @@ public class AccInfoDetailsVO { private Integer userType; @ApiModelProperty("用户部门") private String deptName; + @ApiModelProperty("用户部门") + private Long deptId; @ApiModelProperty("用户类别(展示)") @Excel(name = "用户类别(展示)") private String userTypeName; @@ -43,9 +45,6 @@ public class AccInfoDetailsVO { @ApiModelProperty("补贴钱包余额/分") @Excel(name = "补贴钱包余额/分") private BigDecimal subsidyBal; - @ApiModelProperty("红包余额") - @Excel(name = "红包余额") - private BigDecimal redEnvelope; @ApiModelProperty("冻结金额") @Excel(name = "冻结金额") private BigDecimal accFreezeBalTotal; @@ -55,7 +54,7 @@ public class AccInfoDetailsVO { @ApiModelProperty("补贴钱包允许最低余额限制") @Excel(name = "补贴钱包允许最低余额限制") private BigDecimal minSubBalLimit; - @ApiModelProperty("账户状态 1正常 2冻结 3销户 4过期") + @ApiModelProperty("账户状态 1正常 2冻结") private Integer accStatus; @ApiModelProperty("账户状态名称") @Excel(name = "账户状态名称") diff --git a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/mapper/AccInfoMapper.java b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/mapper/AccInfoMapper.java index b98f987..a526b9f 100644 --- a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/mapper/AccInfoMapper.java +++ b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/mapper/AccInfoMapper.java @@ -76,4 +76,7 @@ public interface AccInfoMapper extends BaseMapper { AccInfoDetailsVO queryAccInfoBalanceSum(@Param("accountInfoQueryParam") AccountInfoQueryParam accountInfoQueryParam); void updateAccInfoEndDateByUserId(LocalDate endDate, List userIds, String updateBy); + List queryAccInfoByUserIds(@Param("userIds") List userIds); + AccInfoDetailsVO queryAccInfoByUserId(@Param("userId") Long userId); + } diff --git a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/mapper/AccWalletInfoMapper.java b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/mapper/AccWalletInfoMapper.java index f6081a6..ed8e377 100644 --- a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/mapper/AccWalletInfoMapper.java +++ b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/mapper/AccWalletInfoMapper.java @@ -21,6 +21,7 @@ public interface AccWalletInfoMapper { * @return 钱包详情信息 */ public AccWalletInfo selectAccWalletInfoByUserId(Long userId); + public AccWalletInfo selectAccWalletInfoByUserIdAndWalletId(Long userId, Integer walletId); List selectAccWalletInfoByUserIds(@Param("userIds") List userIds); @@ -72,4 +73,12 @@ public interface AccWalletInfoMapper { @Param("updateBy") String updateBy, @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 + ); } diff --git a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/service/AccSubService.java b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/service/AccSubService.java new file mode 100644 index 0000000..3ef9bbd --- /dev/null +++ b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/service/AccSubService.java @@ -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); +} diff --git a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/service/IAccInfoService.java b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/service/IAccInfoService.java index 464f9d5..1ca3584 100644 --- a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/service/IAccInfoService.java +++ b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/service/IAccInfoService.java @@ -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.AccInfoInvalidSumVO; import com.bonus.system.api.domain.SysUser; +import org.apache.ibatis.annotations.Param; /** * 账户资料Service接口 @@ -70,4 +71,7 @@ public interface IAccInfoService { public String getOrderQRCode(); public WalletBalanceVO queryWalletBalance(AccInfo accInfo); + List queryAccInfoByUserIds(List userIds); + AccInfoDetailsVO queryAccInfoByUserId(Long userId); + void checkAccStatus(AccInfoDetailsVO accInfoVO); } diff --git a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/service/IAccWalletInfoService.java b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/service/IAccWalletInfoService.java index 4d8f80b..78de9b9 100644 --- a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/service/IAccWalletInfoService.java +++ b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/service/IAccWalletInfoService.java @@ -6,6 +6,7 @@ import java.util.Map; import com.bonus.canteen.core.account.domain.AccWalletInfo; import com.bonus.canteen.core.account.domain.vo.AccWalletInfoVO; +import org.apache.ibatis.annotations.Param; /** * 钱包详情信息Service接口 @@ -21,6 +22,7 @@ public interface IAccWalletInfoService { * @return 钱包详情信息 */ public AccWalletInfo selectAccWalletInfoByUserId(Long userId); + public AccWalletInfo selectAccWalletInfoByUserIdAndWalletId(Long userId, Integer walletId); /** * 查询钱包详情信息列表 @@ -67,5 +69,6 @@ public interface IAccWalletInfoService { Map> selectAccWalletInfoByUserIds(List userIds); void updateMinBalance(List userIds, Integer walletId, BigDecimal minBalance); - + void addAccWalletInfo(BigDecimal amount, Long userId, Integer walletId); + void reduceAccWalletInfo(BigDecimal amount, Long userId, Integer walletId); } diff --git a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/service/impl/AccInfoServiceImpl.java b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/service/impl/AccInfoServiceImpl.java index f5286db..f62f036 100644 --- a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/service/impl/AccInfoServiceImpl.java +++ b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/service/impl/AccInfoServiceImpl.java @@ -8,8 +8,7 @@ import java.util.stream.Collectors; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; -import cn.hutool.core.util.NumberUtil; -import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.*; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.bonus.canteen.core.account.constants.AccWalletIdEnum; import com.bonus.canteen.core.account.constants.AccStatusEnum; @@ -57,7 +56,6 @@ import javax.annotation.Resource; * @date 2025-04-05 */ @Service -@Slf4j public class AccInfoServiceImpl implements IAccInfoService { private static final Logger log = LoggerFactory.getLogger(AccInfoServiceImpl.class); @@ -94,6 +92,9 @@ public class AccInfoServiceImpl implements IAccInfoService { @Override public List selectAccInfoList(AccountInfoQueryParam accountInfoQueryParam) { String encryptedSearchValue = SM4EncryptUtils.sm4Encrypt(accountInfoQueryParam.getSearchValue()); + if(CollUtil.isEmpty(accountInfoQueryParam.getAccStatusList())) { + accountInfoQueryParam.setAccStatusList(Arrays.asList(AccStatusEnum.NORMAL.getKey(), AccStatusEnum.DEACTIVATE.getKey())); + } List list = accInfoMapper.queryAccInfoDetails(accountInfoQueryParam, encryptedSearchValue); handleResult(list); return list; @@ -292,6 +293,9 @@ public class AccInfoServiceImpl implements IAccInfoService { } public AccInfoDetailsVO queryAccInfoBalanceSum(AccountInfoQueryParam accountInfoQueryParam) { + if(CollUtil.isEmpty(accountInfoQueryParam.getAccStatusList())) { + accountInfoQueryParam.setAccStatusList(Arrays.asList(AccStatusEnum.NORMAL.getKey(), AccStatusEnum.DEACTIVATE.getKey())); + } return accInfoMapper.queryAccInfoBalanceSum(accountInfoQueryParam); } @@ -326,11 +330,36 @@ public class AccInfoServiceImpl implements IAccInfoService { return walletBalanceVO; } - protected void setAccInfoVODetailList(AccInfoVo accInfoVo, List 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()); + @Override + public List queryAccInfoByUserIds(List userIds) { + return accInfoMapper.queryAccInfoByUserIds(userIds); + } + @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 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()); +// } + } } diff --git a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/service/impl/AccSubServiceImpl.java b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/service/impl/AccSubServiceImpl.java new file mode 100644 index 0000000..e4a9a91 --- /dev/null +++ b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/service/impl/AccSubServiceImpl.java @@ -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 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 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 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 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); + } + } +} diff --git a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/service/impl/AccWalletInfoServiceImpl.java b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/service/impl/AccWalletInfoServiceImpl.java index 2af21c2..7f93d72 100644 --- a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/service/impl/AccWalletInfoServiceImpl.java +++ b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/service/impl/AccWalletInfoServiceImpl.java @@ -43,6 +43,11 @@ public class AccWalletInfoServiceImpl implements IAccWalletInfoService { 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); + } } diff --git a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/utils/AccRedisUtils.java b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/utils/AccRedisUtils.java new file mode 100644 index 0000000..6491fc7 --- /dev/null +++ b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/account/utils/AccRedisUtils.java @@ -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); + } +} diff --git a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/common/utils/RedisUtil.java b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/common/utils/RedisUtil.java new file mode 100644 index 0000000..7e22763 --- /dev/null +++ b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/common/utils/RedisUtil.java @@ -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 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 keysByPattern(String pattern) { + Set 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> 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 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 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 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(); + } + +} diff --git a/bonus-modules/bonus-smart-canteen/src/main/resources/mapper/account/AccInfoMapper.xml b/bonus-modules/bonus-smart-canteen/src/main/resources/mapper/account/AccInfoMapper.xml index 856c1dc..7403a3d 100644 --- a/bonus-modules/bonus-smart-canteen/src/main/resources/mapper/account/AccInfoMapper.xml +++ b/bonus-modules/bonus-smart-canteen/src/main/resources/mapper/account/AccInfoMapper.xml @@ -499,4 +499,35 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{userId} + + + + \ No newline at end of file diff --git a/bonus-modules/bonus-smart-canteen/src/main/resources/mapper/account/AccWalletInfoMapper.xml b/bonus-modules/bonus-smart-canteen/src/main/resources/mapper/account/AccWalletInfoMapper.xml index 5f3a92d..fec2e57 100644 --- a/bonus-modules/bonus-smart-canteen/src/main/resources/mapper/account/AccWalletInfoMapper.xml +++ b/bonus-modules/bonus-smart-canteen/src/main/resources/mapper/account/AccWalletInfoMapper.xml @@ -41,6 +41,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" where user_id = #{userId} + +