From 63a0b87066e083e39e3fc2d5c85bd3885e24df8e Mon Sep 17 00:00:00 2001 From: tianfeng <769204422@qq.com> Date: Tue, 18 Nov 2025 14:48:22 +0800 Subject: [PATCH] =?UTF-8?q?wallet=E6=96=B0=E5=A2=9E=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=B7=A5=E8=B5=84=E8=B4=A6=E6=88=B7=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/SalaryAccountRestController.java | 62 ++++++ .../app/command/salary/SalaryIssueCmdExe.java | 27 +++ .../command/salary/SalaryWithdrawCmdExe.java | 24 +++ .../query/SalaryAccountBalanceQryExe.java | 41 ++++ .../query/SalaryRunningWaterQryExe.java | 79 +++++++ .../app/convertor/SalaryAccountConvertor.java | 130 ++++++++++++ .../app/service/SalaryAccountServiceImpl.java | 48 +++++ .../clientobject/SalaryAccountBalanceCO.java | 47 +++++ .../dto/clientobject/SalaryIssueResultCO.java | 31 +++ .../clientobject/SalaryRunningWaterCO.java | 97 +++++++++ .../wallet/app/dto/cmd/SalaryIssueCmd.java | 73 +++++++ .../dto/cmd/SalaryRunningWaterQueryCmd.java | 43 ++++ .../wallet/app/dto/cmd/SalaryWithdrawCmd.java | 46 ++++ .../app/service/SalaryAccountService.java | 35 +++ .../domain/gateway/SalaryAccountGateway.java | 35 +++ .../wallet/domain/salary/SalaryEvent.java | 64 ++++++ .../wallet/domain/salary/SalaryReceipt.java | 106 ++++++++++ .../domain/salary/SalaryReceiptRes.java | 42 ++++ .../wallet/domain/salary/SalaryType.java | 51 +++++ .../salary/SalaryAccountBalanceMapper.java | 35 +++ .../SalaryAccountRunningWaterMapper.java | 13 ++ .../entity/salary/SalaryAccountBalance.java | 81 +++++++ .../salary/SalaryAccountRunningWater.java | 145 +++++++++++++ .../salary/SalaryAccountBalanceService.java | 45 ++++ .../SalaryAccountRunningWaterService.java | 11 + .../impl/SalaryAccountBalanceServiceImpl.java | 78 +++++++ .../SalaryAccountRunningWaterServiceImpl.java | 17 ++ .../gateway/SalaryAccountGatewayImpl.java | 154 ++++++++++++++ .../wallet/infra/util/TimeConvertUtils.java | 199 ++++++++++++++++++ .../dao/salary/SalaryAccountBalanceMapper.xml | 42 ++++ 30 files changed, 1901 insertions(+) create mode 100644 rc-service/rc-service-wallet/wallet-adapter/src/main/java/com/red/circle/wallet/adapter/app/SalaryAccountRestController.java create mode 100644 rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/command/salary/SalaryIssueCmdExe.java create mode 100644 rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/command/salary/SalaryWithdrawCmdExe.java create mode 100644 rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/command/salary/query/SalaryAccountBalanceQryExe.java create mode 100644 rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/command/salary/query/SalaryRunningWaterQryExe.java create mode 100644 rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/convertor/SalaryAccountConvertor.java create mode 100644 rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/service/SalaryAccountServiceImpl.java create mode 100644 rc-service/rc-service-wallet/wallet-client/src/main/java/com/red/circle/wallet/app/dto/clientobject/SalaryAccountBalanceCO.java create mode 100644 rc-service/rc-service-wallet/wallet-client/src/main/java/com/red/circle/wallet/app/dto/clientobject/SalaryIssueResultCO.java create mode 100644 rc-service/rc-service-wallet/wallet-client/src/main/java/com/red/circle/wallet/app/dto/clientobject/SalaryRunningWaterCO.java create mode 100644 rc-service/rc-service-wallet/wallet-client/src/main/java/com/red/circle/wallet/app/dto/cmd/SalaryIssueCmd.java create mode 100644 rc-service/rc-service-wallet/wallet-client/src/main/java/com/red/circle/wallet/app/dto/cmd/SalaryRunningWaterQueryCmd.java create mode 100644 rc-service/rc-service-wallet/wallet-client/src/main/java/com/red/circle/wallet/app/dto/cmd/SalaryWithdrawCmd.java create mode 100644 rc-service/rc-service-wallet/wallet-client/src/main/java/com/red/circle/wallet/app/service/SalaryAccountService.java create mode 100644 rc-service/rc-service-wallet/wallet-domain/src/main/java/com/red/circle/wallet/domain/gateway/SalaryAccountGateway.java create mode 100644 rc-service/rc-service-wallet/wallet-domain/src/main/java/com/red/circle/wallet/domain/salary/SalaryEvent.java create mode 100644 rc-service/rc-service-wallet/wallet-domain/src/main/java/com/red/circle/wallet/domain/salary/SalaryReceipt.java create mode 100644 rc-service/rc-service-wallet/wallet-domain/src/main/java/com/red/circle/wallet/domain/salary/SalaryReceiptRes.java create mode 100644 rc-service/rc-service-wallet/wallet-domain/src/main/java/com/red/circle/wallet/domain/salary/SalaryType.java create mode 100644 rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/dao/salary/SalaryAccountBalanceMapper.java create mode 100644 rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/dao/salary/SalaryAccountRunningWaterMapper.java create mode 100644 rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/entity/salary/SalaryAccountBalance.java create mode 100644 rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/entity/salary/SalaryAccountRunningWater.java create mode 100644 rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/service/salary/SalaryAccountBalanceService.java create mode 100644 rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/service/salary/SalaryAccountRunningWaterService.java create mode 100644 rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/service/salary/impl/SalaryAccountBalanceServiceImpl.java create mode 100644 rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/service/salary/impl/SalaryAccountRunningWaterServiceImpl.java create mode 100644 rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/gateway/SalaryAccountGatewayImpl.java create mode 100644 rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/util/TimeConvertUtils.java create mode 100644 rc-service/rc-service-wallet/wallet-infrastructure/src/main/resources/dao/salary/SalaryAccountBalanceMapper.xml diff --git a/rc-service/rc-service-wallet/wallet-adapter/src/main/java/com/red/circle/wallet/adapter/app/SalaryAccountRestController.java b/rc-service/rc-service-wallet/wallet-adapter/src/main/java/com/red/circle/wallet/adapter/app/SalaryAccountRestController.java new file mode 100644 index 00000000..ee4a1502 --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-adapter/src/main/java/com/red/circle/wallet/adapter/app/SalaryAccountRestController.java @@ -0,0 +1,62 @@ +package com.red.circle.wallet.adapter.app; + +import com.red.circle.framework.dto.PageResult; +import com.red.circle.framework.web.controller.BaseController; +import com.red.circle.wallet.app.dto.clientobject.SalaryAccountBalanceCO; +import com.red.circle.wallet.app.dto.clientobject.SalaryIssueResultCO; +import com.red.circle.wallet.app.dto.clientobject.SalaryRunningWaterCO; +import com.red.circle.wallet.app.dto.cmd.SalaryIssueCmd; +import com.red.circle.wallet.app.dto.cmd.SalaryRunningWaterQueryCmd; +import com.red.circle.wallet.app.dto.cmd.SalaryWithdrawCmd; +import com.red.circle.wallet.app.service.SalaryAccountService; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * 工资账户控制器. + */ +@RestController +@RequestMapping("/salary-account") +@RequiredArgsConstructor +public class SalaryAccountRestController extends BaseController { + + private final SalaryAccountService salaryAccountService; + + /** + * 查询余额. + */ + @GetMapping("/balance") + public SalaryAccountBalanceCO getBalance(@RequestParam Long userId) { + return salaryAccountService.getBalance(userId); + } + + /** + * 发放工资. + */ + @PostMapping("/issue") + public SalaryIssueResultCO issueSalary(@RequestBody @Validated SalaryIssueCmd cmd) { + return salaryAccountService.issueSalary(cmd); + } + + /** + * 提现. + */ + @PostMapping("/withdraw") + public void withdrawSalary(@RequestBody @Validated SalaryWithdrawCmd cmd) { + salaryAccountService.withdrawSalary(cmd); + } + + /** + * 查询流水. + */ + @GetMapping("/running-water") + public PageResult queryRunningWater(@Validated SalaryRunningWaterQueryCmd cmd) { + return salaryAccountService.queryRunningWater(cmd); + } +} diff --git a/rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/command/salary/SalaryIssueCmdExe.java b/rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/command/salary/SalaryIssueCmdExe.java new file mode 100644 index 00000000..39e1d5b7 --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/command/salary/SalaryIssueCmdExe.java @@ -0,0 +1,27 @@ +package com.red.circle.wallet.app.command.salary; + +import com.red.circle.wallet.app.convertor.SalaryAccountConvertor; +import com.red.circle.wallet.app.dto.clientobject.SalaryIssueResultCO; +import com.red.circle.wallet.app.dto.cmd.SalaryIssueCmd; +import com.red.circle.wallet.domain.gateway.SalaryAccountGateway; +import com.red.circle.wallet.domain.salary.SalaryReceipt; +import com.red.circle.wallet.domain.salary.SalaryReceiptRes; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +/** + * 发放工资. + */ +@Component +@RequiredArgsConstructor +public class SalaryIssueCmdExe { + + private final SalaryAccountGateway salaryAccountGateway; + private final SalaryAccountConvertor salaryAccountConvertor; + + public SalaryIssueResultCO execute(SalaryIssueCmd cmd) { + SalaryReceipt receipt = salaryAccountConvertor.toSalaryReceipt(cmd); + SalaryReceiptRes res = salaryAccountGateway.changeBalance(receipt); + return salaryAccountConvertor.toIssueResultCO(res); + } +} diff --git a/rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/command/salary/SalaryWithdrawCmdExe.java b/rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/command/salary/SalaryWithdrawCmdExe.java new file mode 100644 index 00000000..445e4134 --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/command/salary/SalaryWithdrawCmdExe.java @@ -0,0 +1,24 @@ +package com.red.circle.wallet.app.command.salary; + +import com.red.circle.wallet.app.convertor.SalaryAccountConvertor; +import com.red.circle.wallet.app.dto.cmd.SalaryWithdrawCmd; +import com.red.circle.wallet.domain.gateway.SalaryAccountGateway; +import com.red.circle.wallet.domain.salary.SalaryReceipt; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +/** + * 工资提现. + */ +@Component +@RequiredArgsConstructor +public class SalaryWithdrawCmdExe { + + private final SalaryAccountGateway salaryAccountGateway; + private final SalaryAccountConvertor salaryAccountConvertor; + + public void execute(SalaryWithdrawCmd cmd) { + SalaryReceipt receipt = salaryAccountConvertor.toWithdrawReceipt(cmd); + salaryAccountGateway.changeBalance(receipt); + } +} diff --git a/rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/command/salary/query/SalaryAccountBalanceQryExe.java b/rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/command/salary/query/SalaryAccountBalanceQryExe.java new file mode 100644 index 00000000..c0a5b394 --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/command/salary/query/SalaryAccountBalanceQryExe.java @@ -0,0 +1,41 @@ +package com.red.circle.wallet.app.command.salary.query; + +import com.red.circle.wallet.app.convertor.SalaryAccountConvertor; +import com.red.circle.wallet.app.dto.clientobject.SalaryAccountBalanceCO; +import com.red.circle.wallet.infra.database.rds.entity.salary.SalaryAccountBalance; +import com.red.circle.wallet.infra.database.rds.service.salary.SalaryAccountBalanceService; +import java.math.BigDecimal; +import java.util.Objects; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +/** + * 查询工资账户余额. + */ +@Component +@RequiredArgsConstructor +public class SalaryAccountBalanceQryExe { + + private final SalaryAccountBalanceService salaryAccountBalanceService; + private final SalaryAccountConvertor salaryAccountConvertor; + + public SalaryAccountBalanceCO execute(Long userId) { + SalaryAccountBalance balance = salaryAccountBalanceService.lambdaQuery() + .eq(SalaryAccountBalance::getUserId, userId) + .one(); + + if (Objects.isNull(balance)) { + // 账户不存在,返回空余额 + SalaryAccountBalanceCO co = new SalaryAccountBalanceCO(); + co.setUserId(userId); + co.setEarnPoints(BigDecimal.ZERO); + co.setConsumptionPoints(BigDecimal.ZERO); + co.setBalance(BigDecimal.ZERO); + co.setFrozenAmount(BigDecimal.ZERO); + co.setAvailableBalance(BigDecimal.ZERO); + return co; + } + + return salaryAccountConvertor.toBalanceCO(balance); + } +} diff --git a/rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/command/salary/query/SalaryRunningWaterQryExe.java b/rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/command/salary/query/SalaryRunningWaterQryExe.java new file mode 100644 index 00000000..50f0032d --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/command/salary/query/SalaryRunningWaterQryExe.java @@ -0,0 +1,79 @@ +package com.red.circle.wallet.app.command.salary.query; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.red.circle.framework.dto.PageResult; +import com.red.circle.tool.core.text.StringUtils; +import com.red.circle.wallet.app.convertor.SalaryAccountConvertor; +import com.red.circle.wallet.app.dto.clientobject.SalaryRunningWaterCO; +import com.red.circle.wallet.app.dto.cmd.SalaryRunningWaterQueryCmd; +import com.red.circle.wallet.infra.database.rds.entity.salary.SalaryAccountRunningWater; +import com.red.circle.wallet.infra.database.rds.service.salary.SalaryAccountRunningWaterService; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +/** + * 查询工资流水. + */ +@Component +@RequiredArgsConstructor +public class SalaryRunningWaterQryExe { + + private final SalaryAccountRunningWaterService salaryAccountRunningWaterService; + private final SalaryAccountConvertor salaryAccountConvertor; + + public PageResult execute(SalaryRunningWaterQueryCmd cmd) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + + if (Objects.nonNull(cmd.getUserId())) { + wrapper.eq(SalaryAccountRunningWater::getUserId, cmd.getUserId()); + } + + if (StringUtils.isNotBlank(cmd.getSalaryType())) { + wrapper.eq(SalaryAccountRunningWater::getSalaryType, cmd.getSalaryType()); + } + + if (Objects.nonNull(cmd.getType())) { + wrapper.eq(SalaryAccountRunningWater::getType, cmd.getType()); + } + + if (Objects.nonNull(cmd.getBillBelong())) { + wrapper.eq(SalaryAccountRunningWater::getBillBelong, cmd.getBillBelong()); + } + + if (StringUtils.isNotBlank(cmd.getStartTime())) { + LocalDateTime startTime = LocalDateTime.parse(cmd.getStartTime(), + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + wrapper.ge(SalaryAccountRunningWater::getCreateTime, startTime); + } + + if (StringUtils.isNotBlank(cmd.getEndTime())) { + LocalDateTime endTime = LocalDateTime.parse(cmd.getEndTime(), + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + wrapper.le(SalaryAccountRunningWater::getCreateTime, endTime); + } + + wrapper.orderByDesc(SalaryAccountRunningWater::getCreateTime); + + Page page = new Page<>(cmd.getCurrent(), cmd.getSize()); + IPage pageResult = salaryAccountRunningWaterService.page(page, wrapper); + + List records = pageResult.getRecords().stream() + .map(salaryAccountConvertor::toRunningWaterCO) + .collect(Collectors.toList()); + + PageResult result = new PageResult<>(); + result.setRecords(records); + result.setTotal(page.getTotal()); + result.setCurrent(page.getCurrent()); + result.setSize(page.getSize()); + + return result; + } +} diff --git a/rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/convertor/SalaryAccountConvertor.java b/rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/convertor/SalaryAccountConvertor.java new file mode 100644 index 00000000..6387bc48 --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/convertor/SalaryAccountConvertor.java @@ -0,0 +1,130 @@ +package com.red.circle.wallet.app.convertor; + +import com.red.circle.common.business.core.enums.OpUserType; +import com.red.circle.common.business.core.enums.ReceiptType; +import com.red.circle.tool.core.date.ZonedDateTimeAsiaRiyadhUtils; +import com.red.circle.tool.core.tuple.PennyAmount; +import com.red.circle.wallet.app.dto.clientobject.SalaryAccountBalanceCO; +import com.red.circle.wallet.app.dto.clientobject.SalaryIssueResultCO; +import com.red.circle.wallet.app.dto.clientobject.SalaryRunningWaterCO; +import com.red.circle.wallet.app.dto.cmd.SalaryIssueCmd; +import com.red.circle.wallet.app.dto.cmd.SalaryWithdrawCmd; +import com.red.circle.wallet.domain.salary.SalaryEvent; +import com.red.circle.wallet.domain.salary.SalaryReceipt; +import com.red.circle.wallet.domain.salary.SalaryReceiptRes; +import com.red.circle.wallet.domain.salary.SalaryType; +import com.red.circle.wallet.infra.database.rds.entity.salary.SalaryAccountBalance; +import com.red.circle.wallet.infra.database.rds.entity.salary.SalaryAccountRunningWater; +import java.math.BigDecimal; +import java.util.Objects; + +import com.red.circle.wallet.infra.util.TimeConvertUtils; +import org.springframework.stereotype.Component; + +/** + * 工资账户转换器. + */ +@Component +public class SalaryAccountConvertor { + + public SalaryReceipt toSalaryReceipt(SalaryIssueCmd cmd) { + return new SalaryReceipt() + .setReceiptType(ReceiptType.INCOME) + .setUserId(cmd.getUserId()) + .setSysOrigin(cmd.getReqSysOrigin().getOrigin()) + .setSalaryType(SalaryType.of(cmd.getSalaryType())) + .setSalaryEvent(SalaryEvent.SALARY_ISSUE) + .setAmount(PennyAmount.ofDollar(cmd.getAmount())) + .setTrackId(cmd.getTrackId()) + .setBillBelong(cmd.getBillBelong()) + .setTeamId(cmd.getTeamId()) + .setBdId(cmd.getBdId()) + .setAgentId(cmd.getAgentId()) + .setEventDesc(cmd.getEventDesc()) + .setRemark(cmd.getRemark()) + .setOpUserType(OpUserType.APP) + .setOpUserId(cmd.getReqUserId()); + } + + public SalaryReceipt toWithdrawReceipt(SalaryWithdrawCmd cmd) { + SalaryReceipt receipt = new SalaryReceipt() + .setReceiptType(ReceiptType.EXPENDITURE) + .setUserId(cmd.getUserId()) + .setSysOrigin(cmd.getReqSysOrigin().getOrigin()) + .setSalaryType(SalaryType.BD_SALARY) + .setSalaryEvent(SalaryEvent.WITHDRAW) + .setAmount(PennyAmount.ofDollar(cmd.getAmount())) + .setTrackId(cmd.getTrackId()) + .setEventDesc("提现") + .setRemark(cmd.getRemark()) + .setOpUserType(OpUserType.APP) + .setOpUserId(cmd.getReqUserId()); + + if (Objects.nonNull(cmd.getServiceCharge()) && cmd.getServiceCharge() > 0) { + receipt.setServiceCharge(cmd.getServiceCharge()); + BigDecimal chargeRate = new BigDecimal(cmd.getServiceCharge()).divide(new BigDecimal("1000")); + BigDecimal actualAmount = cmd.getAmount().multiply(BigDecimal.ONE.subtract(chargeRate)); + receipt.setActualAmount(PennyAmount.ofDollar(actualAmount)); + } else { + receipt.setActualAmount(PennyAmount.ofDollar(cmd.getAmount())); + } + + return receipt; + } + + public SalaryIssueResultCO toIssueResultCO(SalaryReceiptRes res) { + SalaryIssueResultCO co = new SalaryIssueResultCO(); + co.setRecordId(res.getRecordId()); + co.setAmount(res.getAmount().getDollarAmount()); + co.setBalance(res.getBalance().getDollarAmount()); + co.setAvailableBalance(res.getAvailableBalance().getDollarAmount()); + return co; + } + + public SalaryAccountBalanceCO toBalanceCO(SalaryAccountBalance entity) { + if (Objects.isNull(entity)) { + return null; + } + SalaryAccountBalanceCO co = new SalaryAccountBalanceCO(); + co.setUserId(entity.getUserId()); + co.setEarnPoints(entity.getEarnPoints()); + co.setConsumptionPoints(entity.getConsumptionPoints()); + co.setBalance(entity.getBalance()); + co.setFrozenAmount(entity.getFrozenAmount()); + co.setAvailableBalance(entity.getAvailableBalance()); + co.setUpdatedAt(TimeConvertUtils.toLocalDateTime(entity.getUpdateTime())); + + + return co; + } + + public SalaryRunningWaterCO toRunningWaterCO(SalaryAccountRunningWater entity) { + if (Objects.isNull(entity)) { + return null; + } + SalaryRunningWaterCO co = new SalaryRunningWaterCO(); + co.setId(entity.getId()); + co.setUserId(entity.getUserId()); + co.setType(entity.getType()); + co.setSalaryType(entity.getSalaryType()); + + SalaryType salaryType = SalaryType.of(entity.getSalaryType()); + if (Objects.nonNull(salaryType)) { + co.setSalaryTypeName(salaryType.getDesc()); + } + + co.setAmount(entity.getAmount()); + co.setBalance(entity.getBalance()); + co.setAvailableBalance(entity.getAvailableBalance()); + co.setSalaryEvent(entity.getSalaryEvent()); + co.setEventDesc(entity.getEventDesc()); + co.setTrackId(entity.getTrackId()); + co.setBillBelong(entity.getBillBelong()); + co.setTeamId(entity.getTeamId()); + co.setBdId(entity.getBdId()); + co.setAgentId(entity.getAgentId()); + co.setRemark(entity.getRemark()); + co.setCreatedAt(TimeConvertUtils.toLocalDateTime(entity.getCreateTime())); + return co; + } +} diff --git a/rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/service/SalaryAccountServiceImpl.java b/rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/service/SalaryAccountServiceImpl.java new file mode 100644 index 00000000..fc6612c7 --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/service/SalaryAccountServiceImpl.java @@ -0,0 +1,48 @@ +package com.red.circle.wallet.app.service; + +import com.red.circle.framework.dto.PageResult; +import com.red.circle.wallet.app.command.salary.SalaryIssueCmdExe; +import com.red.circle.wallet.app.command.salary.SalaryWithdrawCmdExe; +import com.red.circle.wallet.app.command.salary.query.SalaryAccountBalanceQryExe; +import com.red.circle.wallet.app.command.salary.query.SalaryRunningWaterQryExe; +import com.red.circle.wallet.app.dto.clientobject.SalaryAccountBalanceCO; +import com.red.circle.wallet.app.dto.clientobject.SalaryIssueResultCO; +import com.red.circle.wallet.app.dto.clientobject.SalaryRunningWaterCO; +import com.red.circle.wallet.app.dto.cmd.SalaryIssueCmd; +import com.red.circle.wallet.app.dto.cmd.SalaryRunningWaterQueryCmd; +import com.red.circle.wallet.app.dto.cmd.SalaryWithdrawCmd; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * 工资账户服务实现. + */ +@Service +@RequiredArgsConstructor +public class SalaryAccountServiceImpl implements SalaryAccountService { + + private final SalaryAccountBalanceQryExe salaryAccountBalanceQryExe; + private final SalaryRunningWaterQryExe salaryRunningWaterQryExe; + private final SalaryIssueCmdExe salaryIssueCmdExe; + private final SalaryWithdrawCmdExe salaryWithdrawCmdExe; + + @Override + public SalaryAccountBalanceCO getBalance(Long userId) { + return salaryAccountBalanceQryExe.execute(userId); + } + + @Override + public SalaryIssueResultCO issueSalary(SalaryIssueCmd cmd) { + return salaryIssueCmdExe.execute(cmd); + } + + @Override + public void withdrawSalary(SalaryWithdrawCmd cmd) { + salaryWithdrawCmdExe.execute(cmd); + } + + @Override + public PageResult queryRunningWater(SalaryRunningWaterQueryCmd cmd) { + return salaryRunningWaterQryExe.execute(cmd); + } +} diff --git a/rc-service/rc-service-wallet/wallet-client/src/main/java/com/red/circle/wallet/app/dto/clientobject/SalaryAccountBalanceCO.java b/rc-service/rc-service-wallet/wallet-client/src/main/java/com/red/circle/wallet/app/dto/clientobject/SalaryAccountBalanceCO.java new file mode 100644 index 00000000..9b33a4fa --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-client/src/main/java/com/red/circle/wallet/app/dto/clientobject/SalaryAccountBalanceCO.java @@ -0,0 +1,47 @@ +package com.red.circle.wallet.app.dto.clientobject; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import lombok.Data; + +/** + * 工资账户余额. + */ +@Data +public class SalaryAccountBalanceCO { + + /** + * 用户ID. + */ + private Long userId; + + /** + * 累计收入. + */ + private BigDecimal earnPoints; + + /** + * 累计支出. + */ + private BigDecimal consumptionPoints; + + /** + * 当前余额. + */ + private BigDecimal balance; + + /** + * 冻结金额. + */ + private BigDecimal frozenAmount; + + /** + * 可用余额. + */ + private BigDecimal availableBalance; + + /** + * 更新时间. + */ + private LocalDateTime updatedAt; +} diff --git a/rc-service/rc-service-wallet/wallet-client/src/main/java/com/red/circle/wallet/app/dto/clientobject/SalaryIssueResultCO.java b/rc-service/rc-service-wallet/wallet-client/src/main/java/com/red/circle/wallet/app/dto/clientobject/SalaryIssueResultCO.java new file mode 100644 index 00000000..85e5e484 --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-client/src/main/java/com/red/circle/wallet/app/dto/clientobject/SalaryIssueResultCO.java @@ -0,0 +1,31 @@ +package com.red.circle.wallet.app.dto.clientobject; + +import java.math.BigDecimal; +import lombok.Data; + +/** + * 工资发放结果. + */ +@Data +public class SalaryIssueResultCO { + + /** + * 流水记录ID. + */ + private Long recordId; + + /** + * 发放金额. + */ + private BigDecimal amount; + + /** + * 变动后余额. + */ + private BigDecimal balance; + + /** + * 变动后可用余额. + */ + private BigDecimal availableBalance; +} diff --git a/rc-service/rc-service-wallet/wallet-client/src/main/java/com/red/circle/wallet/app/dto/clientobject/SalaryRunningWaterCO.java b/rc-service/rc-service-wallet/wallet-client/src/main/java/com/red/circle/wallet/app/dto/clientobject/SalaryRunningWaterCO.java new file mode 100644 index 00000000..55c3af4a --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-client/src/main/java/com/red/circle/wallet/app/dto/clientobject/SalaryRunningWaterCO.java @@ -0,0 +1,97 @@ +package com.red.circle.wallet.app.dto.clientobject; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import lombok.Data; + +/** + * 工资流水. + */ +@Data +public class SalaryRunningWaterCO { + + /** + * 流水ID. + */ + private Long id; + + /** + * 用户ID. + */ + private Long userId; + + /** + * 收支类型 0.收入 1.支出. + */ + private Integer type; + + /** + * 工资类型. + */ + private String salaryType; + + /** + * 工资类型名称. + */ + private String salaryTypeName; + + /** + * 变动金额. + */ + private BigDecimal amount; + + /** + * 变动后余额. + */ + private BigDecimal balance; + + /** + * 变动后可用余额. + */ + private BigDecimal availableBalance; + + /** + * 工资事件. + */ + private String salaryEvent; + + /** + * 事件描述. + */ + private String eventDesc; + + /** + * 业务单号. + */ + private String trackId; + + /** + * 账单归属(YYYYMM). + */ + private Integer billBelong; + + /** + * 团队ID. + */ + private Long teamId; + + /** + * BD ID. + */ + private Long bdId; + + /** + * 代理ID. + */ + private Long agentId; + + /** + * 备注. + */ + private String remark; + + /** + * 创建时间. + */ + private LocalDateTime createdAt; +} diff --git a/rc-service/rc-service-wallet/wallet-client/src/main/java/com/red/circle/wallet/app/dto/cmd/SalaryIssueCmd.java b/rc-service/rc-service-wallet/wallet-client/src/main/java/com/red/circle/wallet/app/dto/cmd/SalaryIssueCmd.java new file mode 100644 index 00000000..b5e40531 --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-client/src/main/java/com/red/circle/wallet/app/dto/cmd/SalaryIssueCmd.java @@ -0,0 +1,73 @@ +package com.red.circle.wallet.app.dto.cmd; + +import com.red.circle.common.business.dto.cmd.AppExtCommand; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.math.BigDecimal; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 发放工资命令. + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class SalaryIssueCmd extends AppExtCommand { + + /** + * 用户ID. + */ + @NotNull(message = "userId required.") + private Long userId; + + /** + * 工资类型. + */ + @NotBlank(message = "salaryType required.") + private String salaryType; + + /** + * 发放金额. + */ + @NotNull(message = "amount required.") + @DecimalMin(value = "0.01", message = "amount must > 0") + private BigDecimal amount; + + /** + * 账单归属(YYYYMM). + */ + @NotNull(message = "billBelong required.") + private Integer billBelong; + + /** + * 业务单号. + */ + @NotBlank(message = "trackId required.") + private String trackId; + + /** + * 团队ID. + */ + private Long teamId; + + /** + * BD ID. + */ + private Long bdId; + + /** + * 代理ID. + */ + private Long agentId; + + /** + * 事件描述. + */ + private String eventDesc; + + /** + * 备注. + */ + private String remark; +} diff --git a/rc-service/rc-service-wallet/wallet-client/src/main/java/com/red/circle/wallet/app/dto/cmd/SalaryRunningWaterQueryCmd.java b/rc-service/rc-service-wallet/wallet-client/src/main/java/com/red/circle/wallet/app/dto/cmd/SalaryRunningWaterQueryCmd.java new file mode 100644 index 00000000..e5ef32ed --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-client/src/main/java/com/red/circle/wallet/app/dto/cmd/SalaryRunningWaterQueryCmd.java @@ -0,0 +1,43 @@ +package com.red.circle.wallet.app.dto.cmd; + +import com.red.circle.common.business.dto.PageQueryCmd; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 工资流水查询命令. + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class SalaryRunningWaterQueryCmd extends PageQueryCmd { + + /** + * 用户ID. + */ + private Long userId; + + /** + * 工资类型. + */ + private String salaryType; + + /** + * 收支类型 0.收入 1.支出. + */ + private Integer type; + + /** + * 账单归属(YYYYMM). + */ + private Integer billBelong; + + /** + * 开始时间. + */ + private String startTime; + + /** + * 结束时间. + */ + private String endTime; +} diff --git a/rc-service/rc-service-wallet/wallet-client/src/main/java/com/red/circle/wallet/app/dto/cmd/SalaryWithdrawCmd.java b/rc-service/rc-service-wallet/wallet-client/src/main/java/com/red/circle/wallet/app/dto/cmd/SalaryWithdrawCmd.java new file mode 100644 index 00000000..c519b4fc --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-client/src/main/java/com/red/circle/wallet/app/dto/cmd/SalaryWithdrawCmd.java @@ -0,0 +1,46 @@ +package com.red.circle.wallet.app.dto.cmd; + +import com.red.circle.common.business.dto.cmd.AppExtCommand; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.math.BigDecimal; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 提现命令. + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class SalaryWithdrawCmd extends AppExtCommand { + + /** + * 用户ID. + */ + @NotNull(message = "userId required.") + private Long userId; + + /** + * 提现金额. + */ + @NotNull(message = "amount required.") + @DecimalMin(value = "0.01", message = "amount must > 0") + private BigDecimal amount; + + /** + * 业务单号. + */ + @NotBlank(message = "trackId required.") + private String trackId; + + /** + * 手续费比例(千分比). + */ + private Long serviceCharge; + + /** + * 备注. + */ + private String remark; +} diff --git a/rc-service/rc-service-wallet/wallet-client/src/main/java/com/red/circle/wallet/app/service/SalaryAccountService.java b/rc-service/rc-service-wallet/wallet-client/src/main/java/com/red/circle/wallet/app/service/SalaryAccountService.java new file mode 100644 index 00000000..ea485db2 --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-client/src/main/java/com/red/circle/wallet/app/service/SalaryAccountService.java @@ -0,0 +1,35 @@ +package com.red.circle.wallet.app.service; + +import com.red.circle.framework.dto.PageResult; +import com.red.circle.wallet.app.dto.clientobject.SalaryAccountBalanceCO; +import com.red.circle.wallet.app.dto.clientobject.SalaryIssueResultCO; +import com.red.circle.wallet.app.dto.clientobject.SalaryRunningWaterCO; +import com.red.circle.wallet.app.dto.cmd.SalaryIssueCmd; +import com.red.circle.wallet.app.dto.cmd.SalaryRunningWaterQueryCmd; +import com.red.circle.wallet.app.dto.cmd.SalaryWithdrawCmd; + +/** + * 工资账户服务. + */ +public interface SalaryAccountService { + + /** + * 查询余额. + */ + SalaryAccountBalanceCO getBalance(Long userId); + + /** + * 发放工资. + */ + SalaryIssueResultCO issueSalary(SalaryIssueCmd cmd); + + /** + * 提现. + */ + void withdrawSalary(SalaryWithdrawCmd cmd); + + /** + * 查询流水. + */ + PageResult queryRunningWater(SalaryRunningWaterQueryCmd cmd); +} diff --git a/rc-service/rc-service-wallet/wallet-domain/src/main/java/com/red/circle/wallet/domain/gateway/SalaryAccountGateway.java b/rc-service/rc-service-wallet/wallet-domain/src/main/java/com/red/circle/wallet/domain/gateway/SalaryAccountGateway.java new file mode 100644 index 00000000..e5296ee2 --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-domain/src/main/java/com/red/circle/wallet/domain/gateway/SalaryAccountGateway.java @@ -0,0 +1,35 @@ +package com.red.circle.wallet.domain.gateway; + +import com.red.circle.tool.core.tuple.PennyAmount; +import com.red.circle.wallet.domain.salary.SalaryReceipt; +import com.red.circle.wallet.domain.salary.SalaryReceiptRes; + +/** + * 工资账户 Gateway. + */ +public interface SalaryAccountGateway { + + /** + * 查询余额. + * + * @param userId 用户ID + * @return 余额 + */ + PennyAmount getBalance(Long userId); + + /** + * 查询可用余额. + * + * @param userId 用户ID + * @return 可用余额 + */ + PennyAmount getAvailableBalance(Long userId); + + /** + * 变更余额. + * + * @param receipt 工资凭证 + * @return 凭证响应 + */ + SalaryReceiptRes changeBalance(SalaryReceipt receipt); +} diff --git a/rc-service/rc-service-wallet/wallet-domain/src/main/java/com/red/circle/wallet/domain/salary/SalaryEvent.java b/rc-service/rc-service-wallet/wallet-domain/src/main/java/com/red/circle/wallet/domain/salary/SalaryEvent.java new file mode 100644 index 00000000..a0142dd4 --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-domain/src/main/java/com/red/circle/wallet/domain/salary/SalaryEvent.java @@ -0,0 +1,64 @@ +package com.red.circle.wallet.domain.salary; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 工资事件枚举. + */ +@Getter +@AllArgsConstructor +public enum SalaryEvent { + + /** + * 工资发放. + */ + SALARY_ISSUE("SALARY_ISSUE", "工资发放"), + + /** + * 工资调整. + */ + SALARY_ADJUST("SALARY_ADJUST", "工资调整"), + + /** + * 提现. + */ + WITHDRAW("WITHDRAW", "提现"), + + /** + * 提现退回. + */ + WITHDRAW_REFUND("WITHDRAW_REFUND", "提现退回"), + + /** + * 手动增加. + */ + MANUAL_ADD("MANUAL_ADD", "手动增加"), + + /** + * 手动扣减. + */ + MANUAL_DEDUCT("MANUAL_DEDUCT", "手动扣减"), + + /** + * 冻结. + */ + FREEZE("FREEZE", "冻结"), + + /** + * 解冻. + */ + UNFREEZE("UNFREEZE", "解冻"); + + private final String code; + private final String desc; + + public static SalaryEvent of(String code) { + for (SalaryEvent event : values()) { + if (event.getCode().equals(code)) { + return event; + } + } + return null; + } +} diff --git a/rc-service/rc-service-wallet/wallet-domain/src/main/java/com/red/circle/wallet/domain/salary/SalaryReceipt.java b/rc-service/rc-service-wallet/wallet-domain/src/main/java/com/red/circle/wallet/domain/salary/SalaryReceipt.java new file mode 100644 index 00000000..8e441487 --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-domain/src/main/java/com/red/circle/wallet/domain/salary/SalaryReceipt.java @@ -0,0 +1,106 @@ +package com.red.circle.wallet.domain.salary; + +import com.red.circle.common.business.core.enums.OpUserType; +import com.red.circle.common.business.core.enums.ReceiptType; +import com.red.circle.tool.core.tuple.PennyAmount; +import java.time.LocalDateTime; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 工资凭证. + */ +@Data +@Accessors(chain = true) +public class SalaryReceipt { + + /** + * 收支类型. + */ + private ReceiptType receiptType; + + /** + * 用户ID. + */ + private Long userId; + + /** + * 系统来源. + */ + private String sysOrigin; + + /** + * 工资类型. + */ + private SalaryType salaryType; + + /** + * 工资事件. + */ + private SalaryEvent salaryEvent; + + /** + * 变动金额. + */ + private PennyAmount amount; + + /** + * 业务单号(幂等键). + */ + private String trackId; + + /** + * 账单归属(YYYYMM). + */ + private Integer billBelong; + + /** + * 团队ID. + */ + private Long teamId; + + /** + * BD ID. + */ + private Long bdId; + + /** + * 代理ID. + */ + private Long agentId; + + /** + * 事件描述. + */ + private String eventDesc; + + /** + * 备注. + */ + private String remark; + + /** + * 操作用户类型. + */ + private OpUserType opUserType; + + /** + * 操作用户ID. + */ + private Long opUserId; + + /** + * 创建时间. + */ + private LocalDateTime createTime; + + /** + * 手续费比例(千分比). + */ + private Long serviceCharge; + + /** + * 实际到账金额. + */ + private PennyAmount actualAmount; +} diff --git a/rc-service/rc-service-wallet/wallet-domain/src/main/java/com/red/circle/wallet/domain/salary/SalaryReceiptRes.java b/rc-service/rc-service-wallet/wallet-domain/src/main/java/com/red/circle/wallet/domain/salary/SalaryReceiptRes.java new file mode 100644 index 00000000..4858a9fa --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-domain/src/main/java/com/red/circle/wallet/domain/salary/SalaryReceiptRes.java @@ -0,0 +1,42 @@ +package com.red.circle.wallet.domain.salary; + +import com.red.circle.tool.core.tuple.PennyAmount; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 工资凭证响应. + */ +@Data +@Accessors(chain = true) +public class SalaryReceiptRes { + + /** + * 流水记录ID. + */ + private Long recordId; + + /** + * 变动后余额. + */ + private PennyAmount balance; + + /** + * 变动后可用余额. + */ + private PennyAmount availableBalance; + + /** + * 变动金额. + */ + private PennyAmount amount; + + public static SalaryReceiptRes of(Long recordId, PennyAmount balance, + PennyAmount availableBalance, PennyAmount amount) { + return new SalaryReceiptRes() + .setRecordId(recordId) + .setBalance(balance) + .setAvailableBalance(availableBalance) + .setAmount(amount); + } +} diff --git a/rc-service/rc-service-wallet/wallet-domain/src/main/java/com/red/circle/wallet/domain/salary/SalaryType.java b/rc-service/rc-service-wallet/wallet-domain/src/main/java/com/red/circle/wallet/domain/salary/SalaryType.java new file mode 100644 index 00000000..4160899f --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-domain/src/main/java/com/red/circle/wallet/domain/salary/SalaryType.java @@ -0,0 +1,51 @@ +package com.red.circle.wallet.domain.salary; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 工资类型枚举. + */ +@Getter +@AllArgsConstructor +public enum SalaryType { + + /** + * BD工资. + */ + BD_SALARY("BD_SALARY", "BD工资"), + + /** + * 代理工资. + */ + AGENT_SALARY("AGENT_SALARY", "代理工资"), + + /** + * 主播工资. + */ + ANCHOR_SALARY("ANCHOR_SALARY", "主播工资"); + + private final String code; + private final String desc; + + public static SalaryType of(String code) { + for (SalaryType type : values()) { + if (type.getCode().equals(code)) { + return type; + } + } + return null; + } + + public boolean isBdSalary() { + return this == BD_SALARY; + } + + public boolean isAgentSalary() { + return this == AGENT_SALARY; + } + + public boolean isAnchorSalary() { + return this == ANCHOR_SALARY; + } +} diff --git a/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/dao/salary/SalaryAccountBalanceMapper.java b/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/dao/salary/SalaryAccountBalanceMapper.java new file mode 100644 index 00000000..5bfdc525 --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/dao/salary/SalaryAccountBalanceMapper.java @@ -0,0 +1,35 @@ +package com.red.circle.wallet.infra.database.rds.dao.salary; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.red.circle.wallet.infra.database.rds.entity.salary.SalaryAccountBalance; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 工资账户余额 Mapper. + */ +@Mapper +public interface SalaryAccountBalanceMapper extends BaseMapper { + + /** + * 增加收入(累计). + */ + int incrIncome(@Param("userId") Long userId, @Param("amount") Long amount); + + /** + * 增加支出(累计). + */ + int incrExpenditure(@Param("userId") Long userId, @Param("amount") Long amount); + + /** + * 冻结金额. + */ + int freezeAmount(@Param("userId") Long userId, @Param("amount") Long amount, + @Param("version") Integer version); + + /** + * 解冻金额. + */ + int unfreezeAmount(@Param("userId") Long userId, @Param("amount") Long amount, + @Param("version") Integer version); +} diff --git a/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/dao/salary/SalaryAccountRunningWaterMapper.java b/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/dao/salary/SalaryAccountRunningWaterMapper.java new file mode 100644 index 00000000..aea8ac65 --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/dao/salary/SalaryAccountRunningWaterMapper.java @@ -0,0 +1,13 @@ +package com.red.circle.wallet.infra.database.rds.dao.salary; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.red.circle.wallet.infra.database.rds.entity.salary.SalaryAccountRunningWater; +import org.apache.ibatis.annotations.Mapper; + +/** + * 工资账户流水 Mapper. + */ +@Mapper +public interface SalaryAccountRunningWaterMapper extends BaseMapper { + +} diff --git a/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/entity/salary/SalaryAccountBalance.java b/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/entity/salary/SalaryAccountBalance.java new file mode 100644 index 00000000..8efd8d4c --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/entity/salary/SalaryAccountBalance.java @@ -0,0 +1,81 @@ +package com.red.circle.wallet.infra.database.rds.entity.salary; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.Version; +import com.red.circle.framework.mybatis.entity.TimestampBaseEntity; +import java.io.Serial; +import java.math.BigDecimal; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/** + * 用户工资账户余额表. + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Accessors(chain = true) +@TableName("user_salary_account_balance") +public class SalaryAccountBalance extends TimestampBaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键标识. + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private Long id; + + /** + * 用户ID. + */ + @TableField("user_id") + private Long userId; + + /** + * 系统来源. + */ + @TableField("sys_origin") + private String sysOrigin; + + /** + * 累计收入总额. + */ + @TableField("earn_points") + private BigDecimal earnPoints; + + /** + * 累计支出总额. + */ + @TableField("consumption_points") + private BigDecimal consumptionPoints; + + /** + * 当前余额. + */ + @TableField("balance") + private BigDecimal balance; + + /** + * 冻结金额. + */ + @TableField("frozen_amount") + private BigDecimal frozenAmount; + + /** + * 可用余额. + */ + @TableField("available_balance") + private BigDecimal availableBalance; + + /** + * 乐观锁版本号. + */ + @Version + @TableField("version") + private Integer version; +} diff --git a/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/entity/salary/SalaryAccountRunningWater.java b/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/entity/salary/SalaryAccountRunningWater.java new file mode 100644 index 00000000..b12af5ed --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/entity/salary/SalaryAccountRunningWater.java @@ -0,0 +1,145 @@ +package com.red.circle.wallet.infra.database.rds.entity.salary; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.red.circle.framework.mybatis.entity.TimestampBaseEntity; +import java.io.Serial; +import java.math.BigDecimal; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/** + * 用户工资账户流水表. + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Accessors(chain = true) +@TableName("user_salary_account_running_water") +public class SalaryAccountRunningWater extends TimestampBaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键标识. + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private Long id; + + /** + * 用户ID. + */ + @TableField("user_id") + private Long userId; + + /** + * 系统来源. + */ + @TableField("sys_origin") + private String sysOrigin; + + /** + * 收支类型 0.收入 1.支出. + */ + @TableField("type") + private Integer type; + + /** + * 工资类型. + */ + @TableField("salary_type") + private String salaryType; + + /** + * 变动金额. + */ + @TableField("amount") + private BigDecimal amount; + + /** + * 变动后余额. + */ + @TableField("balance") + private BigDecimal balance; + + /** + * 变动后可用余额. + */ + @TableField("available_balance") + private BigDecimal availableBalance; + + /** + * 手续费比例(千分比). + */ + @TableField("service_charge") + private Long serviceCharge; + + /** + * 实际到账金额. + */ + @TableField("actual_amount") + private BigDecimal actualAmount; + + /** + * 临时余额. + */ + @TableField("temp_balance") + private BigDecimal tempBalance; + + /** + * 业务单号. + */ + @TableField("track_id") + private String trackId; + + /** + * 工资事件. + */ + @TableField("salary_event") + private String salaryEvent; + + /** + * 事件描述. + */ + @TableField("event_desc") + private String eventDesc; + + /** + * 备注. + */ + @TableField("remark") + private String remark; + + /** + * 创建用户来源 0.app 1.后台. + */ + @TableField("create_user_origin") + private Integer createUserOrigin; + + /** + * 账单归属(YYYYMM). + */ + @TableField("bill_belong") + private Integer billBelong; + + /** + * 团队ID. + */ + @TableField("team_id") + private Long teamId; + + /** + * BD ID. + */ + @TableField("bd_id") + private Long bdId; + + /** + * 代理ID. + */ + @TableField("agent_id") + private Long agentId; +} diff --git a/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/service/salary/SalaryAccountBalanceService.java b/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/service/salary/SalaryAccountBalanceService.java new file mode 100644 index 00000000..59204c30 --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/service/salary/SalaryAccountBalanceService.java @@ -0,0 +1,45 @@ +package com.red.circle.wallet.infra.database.rds.service.salary; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.red.circle.wallet.infra.database.rds.entity.salary.SalaryAccountBalance; + +/** + * 工资账户余额服务. + */ +public interface SalaryAccountBalanceService extends IService { + + /** + * 初始化账户. + */ + void init(String sysOrigin, Long userId); + + /** + * 查询余额(分). + */ + Long getBalance(Long userId); + + /** + * 查询可用余额(分). + */ + Long getAvailableBalance(Long userId); + + /** + * 增加收入. + */ + boolean incrIncome(Long userId, Long amount); + + /** + * 增加支出. + */ + boolean incrExpenditure(Long userId, Long amount); + + /** + * 冻结金额. + */ + boolean freezeAmount(Long userId, Long amount, Integer version); + + /** + * 解冻金额. + */ + boolean unfreezeAmount(Long userId, Long amount, Integer version); +} diff --git a/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/service/salary/SalaryAccountRunningWaterService.java b/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/service/salary/SalaryAccountRunningWaterService.java new file mode 100644 index 00000000..829387f7 --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/service/salary/SalaryAccountRunningWaterService.java @@ -0,0 +1,11 @@ +package com.red.circle.wallet.infra.database.rds.service.salary; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.red.circle.wallet.infra.database.rds.entity.salary.SalaryAccountRunningWater; + +/** + * 工资账户流水服务. + */ +public interface SalaryAccountRunningWaterService extends IService { + +} diff --git a/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/service/salary/impl/SalaryAccountBalanceServiceImpl.java b/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/service/salary/impl/SalaryAccountBalanceServiceImpl.java new file mode 100644 index 00000000..ab743ba6 --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/service/salary/impl/SalaryAccountBalanceServiceImpl.java @@ -0,0 +1,78 @@ +package com.red.circle.wallet.infra.database.rds.service.salary.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.red.circle.tool.core.sequence.IdWorkerUtils; +import com.red.circle.wallet.infra.database.rds.dao.salary.SalaryAccountBalanceMapper; +import com.red.circle.wallet.infra.database.rds.entity.salary.SalaryAccountBalance; +import com.red.circle.wallet.infra.database.rds.service.salary.SalaryAccountBalanceService; +import java.math.BigDecimal; +import java.util.Objects; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * 工资账户余额服务实现. + */ +@Service +@RequiredArgsConstructor +public class SalaryAccountBalanceServiceImpl + extends ServiceImpl + implements SalaryAccountBalanceService { + + @Override + public void init(String sysOrigin, Long userId) { + SalaryAccountBalance balance = new SalaryAccountBalance() + .setId(IdWorkerUtils.getId()) + .setUserId(userId) + .setSysOrigin(sysOrigin) + .setEarnPoints(BigDecimal.ZERO) + .setConsumptionPoints(BigDecimal.ZERO) + .setBalance(BigDecimal.ZERO) + .setFrozenAmount(BigDecimal.ZERO) + .setAvailableBalance(BigDecimal.ZERO) + .setVersion(0); + save(balance); + } + + @Override + public Long getBalance(Long userId) { + SalaryAccountBalance balance = lambdaQuery() + .eq(SalaryAccountBalance::getUserId, userId) + .one(); + if (Objects.isNull(balance)) { + return 0L; + } + return balance.getBalance().multiply(new BigDecimal("100")).longValue(); + } + + @Override + public Long getAvailableBalance(Long userId) { + SalaryAccountBalance balance = lambdaQuery() + .eq(SalaryAccountBalance::getUserId, userId) + .one(); + if (Objects.isNull(balance)) { + return 0L; + } + return balance.getAvailableBalance().multiply(new BigDecimal("100")).longValue(); + } + + @Override + public boolean incrIncome(Long userId, Long amount) { + return baseMapper.incrIncome(userId, amount) > 0; + } + + @Override + public boolean incrExpenditure(Long userId, Long amount) { + return baseMapper.incrExpenditure(userId, amount) > 0; + } + + @Override + public boolean freezeAmount(Long userId, Long amount, Integer version) { + return baseMapper.freezeAmount(userId, amount, version) > 0; + } + + @Override + public boolean unfreezeAmount(Long userId, Long amount, Integer version) { + return baseMapper.unfreezeAmount(userId, amount, version) > 0; + } +} diff --git a/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/service/salary/impl/SalaryAccountRunningWaterServiceImpl.java b/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/service/salary/impl/SalaryAccountRunningWaterServiceImpl.java new file mode 100644 index 00000000..b98a0e65 --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/service/salary/impl/SalaryAccountRunningWaterServiceImpl.java @@ -0,0 +1,17 @@ +package com.red.circle.wallet.infra.database.rds.service.salary.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.red.circle.wallet.infra.database.rds.dao.salary.SalaryAccountRunningWaterMapper; +import com.red.circle.wallet.infra.database.rds.entity.salary.SalaryAccountRunningWater; +import com.red.circle.wallet.infra.database.rds.service.salary.SalaryAccountRunningWaterService; +import org.springframework.stereotype.Service; + +/** + * 工资账户流水服务实现. + */ +@Service +public class SalaryAccountRunningWaterServiceImpl + extends ServiceImpl + implements SalaryAccountRunningWaterService { + +} diff --git a/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/gateway/SalaryAccountGatewayImpl.java b/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/gateway/SalaryAccountGatewayImpl.java new file mode 100644 index 00000000..749e57d0 --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/gateway/SalaryAccountGatewayImpl.java @@ -0,0 +1,154 @@ +package com.red.circle.wallet.infra.gateway; + +import com.red.circle.common.business.core.enums.ReceiptType; +import com.red.circle.framework.core.asserts.ResponseAssert; +import com.red.circle.framework.web.util.ValidatorUtils; +import com.red.circle.tool.core.date.TimestampUtils; +import com.red.circle.tool.core.sequence.IdWorkerUtils; +import com.red.circle.tool.core.tuple.PennyAmount; +import com.red.circle.wallet.domain.gateway.SalaryAccountGateway; +import com.red.circle.wallet.domain.salary.SalaryReceipt; +import com.red.circle.wallet.domain.salary.SalaryReceiptRes; +import com.red.circle.wallet.infra.database.rds.entity.salary.SalaryAccountBalance; +import com.red.circle.wallet.infra.database.rds.entity.salary.SalaryAccountRunningWater; +import com.red.circle.wallet.infra.database.rds.service.salary.SalaryAccountBalanceService; +import com.red.circle.wallet.infra.database.rds.service.salary.SalaryAccountRunningWaterService; +import com.red.circle.wallet.infra.util.TimeConvertUtils; +import com.red.circle.wallet.inner.error.WalletErrorCode; +import java.math.BigDecimal; +import java.util.Objects; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +/** + * 工资账户 Gateway 实现. + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class SalaryAccountGatewayImpl implements SalaryAccountGateway { + + private final SalaryAccountBalanceService salaryAccountBalanceService; + private final SalaryAccountRunningWaterService salaryAccountRunningWaterService; + + @Override + public PennyAmount getBalance(Long userId) { + return PennyAmount.ofPenny(salaryAccountBalanceService.getBalance(userId)); + } + + @Override + public PennyAmount getAvailableBalance(Long userId) { + return PennyAmount.ofPenny(salaryAccountBalanceService.getAvailableBalance(userId)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public SalaryReceiptRes changeBalance(SalaryReceipt receipt) { + if (!ValidatorUtils.validateFastPass(receipt)) { + throw new IllegalArgumentException("changeBalance required param error."); + } + + // 检查账户是否存在,不存在则初始化 + SalaryAccountBalance balance = salaryAccountBalanceService.lambdaQuery() + .eq(SalaryAccountBalance::getUserId, receipt.getUserId()) + .one(); + + if (Objects.isNull(balance)) { + salaryAccountBalanceService.init(receipt.getSysOrigin(), receipt.getUserId()); + balance = salaryAccountBalanceService.lambdaQuery() + .eq(SalaryAccountBalance::getUserId, receipt.getUserId()) + .one(); + } + + // 更新余额 + boolean updateSuccess; + if (receipt.getReceiptType() == ReceiptType.INCOME) { + updateSuccess = salaryAccountBalanceService.incrIncome( + receipt.getUserId(), + receipt.getAmount().getPennyAmount() + ); + } else { + updateSuccess = salaryAccountBalanceService.incrExpenditure( + receipt.getUserId(), + receipt.getAmount().getPennyAmount() + ); + } + + ResponseAssert.isTrue(WalletErrorCode.INSUFFICIENT_BALANCE, updateSuccess); + + // 查询更新后的余额 + Long newBalance = salaryAccountBalanceService.getBalance(receipt.getUserId()); + Long newAvailableBalance = salaryAccountBalanceService.getAvailableBalance(receipt.getUserId()); + + // 插入流水记录 + Long recordId = IdWorkerUtils.getId(); + try { + SalaryAccountRunningWater runningWater = buildRunningWater(receipt, recordId, + newBalance, newAvailableBalance); + salaryAccountRunningWaterService.save(runningWater); + } catch (DuplicateKeyException e) { + // 幂等性:trackId重复,查询已有记录 + SalaryAccountRunningWater existingWater = salaryAccountRunningWaterService.lambdaQuery() + .eq(SalaryAccountRunningWater::getTrackId, receipt.getTrackId()) + .one(); + if (Objects.nonNull(existingWater)) { + return SalaryReceiptRes.of( + existingWater.getId(), + PennyAmount.ofPenny(existingWater.getBalance() + .multiply(new BigDecimal("100")).longValue()), + PennyAmount.ofPenny(existingWater.getAvailableBalance() + .multiply(new BigDecimal("100")).longValue()), + receipt.getAmount() + ); + } + throw e; + } + + return SalaryReceiptRes.of( + recordId, + PennyAmount.ofPenny(newBalance), + PennyAmount.ofPenny(newAvailableBalance), + receipt.getAmount() + ); + } + + private SalaryAccountRunningWater buildRunningWater(SalaryReceipt receipt, Long recordId, + Long balance, Long availableBalance) { + SalaryAccountRunningWater water = new SalaryAccountRunningWater() + .setId(recordId) + .setUserId(receipt.getUserId()) + .setSysOrigin(receipt.getSysOrigin()) + .setType(receipt.getReceiptType().getType()) + .setSalaryType(receipt.getSalaryType().getCode()) + .setAmount(receipt.getAmount().getDollarAmount()) + .setBalance(PennyAmount.ofPenny(balance).getDollarAmount()) + .setAvailableBalance(PennyAmount.ofPenny(availableBalance).getDollarAmount()) + .setTrackId(receipt.getTrackId()) + .setSalaryEvent(receipt.getSalaryEvent().getCode()) + .setEventDesc(receipt.getEventDesc()) + .setRemark(receipt.getRemark()) + .setCreateUserOrigin(receipt.getOpUserType().getType()) + .setBillBelong(receipt.getBillBelong()) + .setTeamId(receipt.getTeamId()) + .setBdId(receipt.getBdId()) + .setAgentId(receipt.getAgentId()); + + if (Objects.nonNull(receipt.getServiceCharge())) { + water.setServiceCharge(receipt.getServiceCharge()); + } + if (Objects.nonNull(receipt.getActualAmount())) { + water.setActualAmount(receipt.getActualAmount().getDollarAmount()); + } + + water.setCreateTime(Objects.isNull(receipt.getCreateTime()) + ? TimestampUtils.now() : TimeConvertUtils.toTimestamp(receipt.getCreateTime())); + water.setUpdateTime(TimestampUtils.now()); + water.setCreateUser(receipt.getOpUserId()); + water.setUpdateUser(receipt.getOpUserId()); + + return water; + } +} diff --git a/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/util/TimeConvertUtils.java b/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/util/TimeConvertUtils.java new file mode 100644 index 00000000..b2fc6191 --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/util/TimeConvertUtils.java @@ -0,0 +1,199 @@ +package com.red.circle.wallet.infra.util; + +import java.sql.Timestamp; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.util.Objects; + +/** + * 时间转换工具类. + *

用于 Timestamp 和 LocalDateTime 之间的相互转换

+ */ +public class TimeConvertUtils { + + /** + * 默认时区 - UTC. + */ + private static final ZoneId DEFAULT_ZONE_ID = ZoneId.systemDefault(); + + /** + * LocalDateTime 转 Timestamp. + * + * @param localDateTime LocalDateTime + * @return Timestamp + */ + public static Timestamp toTimestamp(LocalDateTime localDateTime) { + if (Objects.isNull(localDateTime)) { + return null; + } + return Timestamp.valueOf(localDateTime); + } + + /** + * LocalDateTime 转 Timestamp(指定时区). + * + * @param localDateTime LocalDateTime + * @param zoneId 时区 + * @return Timestamp + */ + public static Timestamp toTimestamp(LocalDateTime localDateTime, ZoneId zoneId) { + if (Objects.isNull(localDateTime)) { + return null; + } + Instant instant = localDateTime.atZone(zoneId).toInstant(); + return Timestamp.from(instant); + } + + /** + * LocalDateTime 转 Timestamp(UTC时区). + * + * @param localDateTime LocalDateTime + * @return Timestamp + */ + public static Timestamp toTimestampUtc(LocalDateTime localDateTime) { + if (Objects.isNull(localDateTime)) { + return null; + } + return Timestamp.from(localDateTime.toInstant(ZoneOffset.UTC)); + } + + /** + * Timestamp 转 LocalDateTime. + * + * @param timestamp Timestamp + * @return LocalDateTime + */ + public static LocalDateTime toLocalDateTime(Timestamp timestamp) { + if (Objects.isNull(timestamp)) { + return null; + } + return timestamp.toLocalDateTime(); + } + + /** + * Timestamp 转 LocalDateTime(指定时区). + * + * @param timestamp Timestamp + * @param zoneId 时区 + * @return LocalDateTime + */ + public static LocalDateTime toLocalDateTime(Timestamp timestamp, ZoneId zoneId) { + if (Objects.isNull(timestamp)) { + return null; + } + return LocalDateTime.ofInstant(timestamp.toInstant(), zoneId); + } + + /** + * Timestamp 转 LocalDateTime(系统默认时区). + * + * @param timestamp Timestamp + * @return LocalDateTime + */ + public static LocalDateTime toLocalDateTimeDefault(Timestamp timestamp) { + if (Objects.isNull(timestamp)) { + return null; + } + return LocalDateTime.ofInstant(timestamp.toInstant(), DEFAULT_ZONE_ID); + } + + /** + * 毫秒时间戳 转 LocalDateTime. + * + * @param epochMilli 毫秒时间戳 + * @return LocalDateTime + */ + public static LocalDateTime toLocalDateTime(Long epochMilli) { + if (Objects.isNull(epochMilli)) { + return null; + } + return LocalDateTime.ofInstant(Instant.ofEpochMilli(epochMilli), DEFAULT_ZONE_ID); + } + + /** + * 毫秒时间戳 转 LocalDateTime(指定时区). + * + * @param epochMilli 毫秒时间戳 + * @param zoneId 时区 + * @return LocalDateTime + */ + public static LocalDateTime toLocalDateTime(Long epochMilli, ZoneId zoneId) { + if (Objects.isNull(epochMilli)) { + return null; + } + return LocalDateTime.ofInstant(Instant.ofEpochMilli(epochMilli), zoneId); + } + + /** + * LocalDateTime 转 毫秒时间戳. + * + * @param localDateTime LocalDateTime + * @return 毫秒时间戳 + */ + public static Long toEpochMilli(LocalDateTime localDateTime) { + if (Objects.isNull(localDateTime)) { + return null; + } + return localDateTime.atZone(DEFAULT_ZONE_ID).toInstant().toEpochMilli(); + } + + /** + * LocalDateTime 转 毫秒时间戳(指定时区). + * + * @param localDateTime LocalDateTime + * @param zoneId 时区 + * @return 毫秒时间戳 + */ + public static Long toEpochMilli(LocalDateTime localDateTime, ZoneId zoneId) { + if (Objects.isNull(localDateTime)) { + return null; + } + return localDateTime.atZone(zoneId).toInstant().toEpochMilli(); + } + + /** + * 秒时间戳 转 LocalDateTime. + * + * @param epochSecond 秒时间戳 + * @return LocalDateTime + */ + public static LocalDateTime toLocalDateTimeFromSecond(Long epochSecond) { + if (Objects.isNull(epochSecond)) { + return null; + } + return LocalDateTime.ofInstant(Instant.ofEpochSecond(epochSecond), DEFAULT_ZONE_ID); + } + + /** + * LocalDateTime 转 秒时间戳. + * + * @param localDateTime LocalDateTime + * @return 秒时间戳 + */ + public static Long toEpochSecond(LocalDateTime localDateTime) { + if (Objects.isNull(localDateTime)) { + return null; + } + return localDateTime.atZone(DEFAULT_ZONE_ID).toInstant().getEpochSecond(); + } + + /** + * 获取当前 LocalDateTime. + * + * @return 当前 LocalDateTime + */ + public static LocalDateTime now() { + return LocalDateTime.now(DEFAULT_ZONE_ID); + } + + /** + * 获取当前 Timestamp. + * + * @return 当前 Timestamp + */ + public static Timestamp nowTimestamp() { + return new Timestamp(System.currentTimeMillis()); + } +} diff --git a/rc-service/rc-service-wallet/wallet-infrastructure/src/main/resources/dao/salary/SalaryAccountBalanceMapper.xml b/rc-service/rc-service-wallet/wallet-infrastructure/src/main/resources/dao/salary/SalaryAccountBalanceMapper.xml new file mode 100644 index 00000000..406eaff5 --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-infrastructure/src/main/resources/dao/salary/SalaryAccountBalanceMapper.xml @@ -0,0 +1,42 @@ + + + + + + UPDATE user_salary_account_balance + SET earn_points = earn_points + #{amount} / 100, + balance = balance + #{amount} / 100, + available_balance = available_balance + #{amount} / 100 + WHERE user_id = #{userId} + + + + UPDATE user_salary_account_balance + SET consumption_points = consumption_points + #{amount} / 100, + balance = balance - #{amount} / 100, + available_balance = available_balance - #{amount} / 100 + WHERE user_id = #{userId} + AND available_balance >= #{amount} / 100 + + + + UPDATE user_salary_account_balance + SET frozen_amount = frozen_amount + #{amount} / 100, + available_balance = available_balance - #{amount} / 100, + version = version + 1 + WHERE user_id = #{userId} + AND version = #{version} + AND available_balance >= #{amount} / 100 + + + + UPDATE user_salary_account_balance + SET frozen_amount = frozen_amount - #{amount} / 100, + available_balance = available_balance + #{amount} / 100, + version = version + 1 + WHERE user_id = #{userId} + AND version = #{version} + AND frozen_amount >= #{amount} / 100 + + +