feat(account): 实现账户积分系统及套餐功能
- 将账户余额系统改造为积分系统,充值金额按1元=100积分计算 - 新增套餐配置功能,支持套餐购买并获取基础额度和赠送额度 - 在账户冻结功能中集成模型价格计算,根据预估tokens自动计算冻结金额 - 更新支付流程以支持套餐ID关联和积分计算 - 修改全局异常处理器返回格式,统一使用CommonResult - 优化账户交易记录的备注信息显示 - 添加雪花算法配置用于分布式ID生成 - 扩展账户冻结DTO添加预估tokens字段 - 重构账户服务中的金额处理逻辑为积分处理逻辑 - 实现套餐配置的CRUD操作接口和相关实体类 - 更新支付回调逻辑以正确处理套餐购买场景
This commit is contained in:
parent
51fce1ece6
commit
e651e73fa2
|
|
@ -44,6 +44,15 @@ public class CommonResult<T> {
|
|||
return new CommonResult<T>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功返回结果
|
||||
*
|
||||
* @param data 获取的数据
|
||||
*/
|
||||
public static <T> CommonResult<T> success(IErrorCode errorCode,T data) {
|
||||
return new CommonResult<T>(errorCode.getCode(), errorCode.getMessage(), data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功返回结果
|
||||
*
|
||||
|
|
@ -58,11 +67,11 @@ public class CommonResult<T> {
|
|||
/**
|
||||
* 成功返回结果
|
||||
*
|
||||
* @param errorCode 获取的数据
|
||||
* @param code 获取的数据
|
||||
* @param message 提示信息
|
||||
*/
|
||||
public static <T> CommonResult<T> success(IErrorCode errorCode, String message ) {
|
||||
return new CommonResult<T>(errorCode.getCode(), message,null);
|
||||
public static <T> CommonResult<T> success(Long code, String message ) {
|
||||
return new CommonResult<T>(code, message,null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.kexue.skills.controller;
|
||||
|
||||
import com.kexue.skills.common.CommonResult;
|
||||
import com.kexue.skills.entity.AccountFrozen;
|
||||
import com.kexue.skills.entity.dto.AccountFrozenDto;
|
||||
import com.kexue.skills.entity.dto.AccountReleaseDto;
|
||||
|
|
@ -7,12 +8,16 @@ import com.kexue.skills.service.AccountFrozenService;
|
|||
import com.kexue.skills.common.Result;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
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.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 账户冻结单控制器
|
||||
|
|
@ -25,6 +30,9 @@ import javax.annotation.Resource;
|
|||
@Tag(name = "账户冻结单", description = "账户冻结单管理接口")
|
||||
public class AccountFrozenController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AccountFrozenController.class);
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Resource
|
||||
private AccountFrozenService accountFrozenService;
|
||||
|
||||
|
|
@ -35,9 +43,14 @@ public class AccountFrozenController {
|
|||
*/
|
||||
@PostMapping("/frozen")
|
||||
@Operation(summary = "创建冻结单", description = "创建账户冻结单")
|
||||
public Result<AccountFrozen> createFrozen(@RequestBody AccountFrozenDto accountFrozenDto) {
|
||||
public CommonResult<AccountFrozen> createFrozen(@RequestBody AccountFrozenDto accountFrozenDto) {
|
||||
try {
|
||||
logger.info("创建冻结单入参: {}", objectMapper.writeValueAsString(accountFrozenDto));
|
||||
} catch (IOException e) {
|
||||
logger.error("创建冻结单入参序列化失败", e);
|
||||
}
|
||||
AccountFrozen accountFrozen = accountFrozenService.createFrozen(accountFrozenDto);
|
||||
return new Result<AccountFrozen>().ok().data(accountFrozen);
|
||||
return CommonResult.success(accountFrozen);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -47,9 +60,14 @@ public class AccountFrozenController {
|
|||
*/
|
||||
@PostMapping("/release")
|
||||
@Operation(summary = "释放冻结单", description = "释放账户冻结单")
|
||||
public Result<AccountFrozen> releaseFrozen(@RequestBody AccountReleaseDto accountReleaseDto) {
|
||||
public CommonResult<AccountFrozen> releaseFrozen(@RequestBody AccountReleaseDto accountReleaseDto) {
|
||||
try {
|
||||
logger.info("释放冻结单入参: {}", objectMapper.writeValueAsString(accountReleaseDto));
|
||||
} catch (IOException e) {
|
||||
logger.error("释放冻结单入参序列化失败", e);
|
||||
}
|
||||
AccountFrozen accountFrozen = accountFrozenService.releaseFrozen(accountReleaseDto);
|
||||
return new Result<AccountFrozen>().ok().data(accountFrozen);
|
||||
return CommonResult.success(accountFrozen);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,121 @@
|
|||
package com.kexue.skills.controller;
|
||||
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.kexue.skills.annotation.RequireAuth;
|
||||
import com.kexue.skills.common.CommonResult;
|
||||
import com.kexue.skills.entity.PackageConfig;
|
||||
import com.kexue.skills.entity.base.IdDto;
|
||||
import com.kexue.skills.entity.dto.PackageConfigDto;
|
||||
import com.kexue.skills.service.PackageConfigService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* (PackageConfig)表控制层
|
||||
*
|
||||
* @author 系统生成
|
||||
* @since 2026-04-11
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("api/packageConfig")
|
||||
@Tag(name = "套餐配置管理 Api")
|
||||
@CrossOrigin(origins = "*")
|
||||
public class PackageConfigController {
|
||||
/**
|
||||
* 服务对象
|
||||
*/
|
||||
@Resource
|
||||
private PackageConfigService packageConfigService;
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*
|
||||
* @param queryDto 筛选条件
|
||||
* @return 查询结果
|
||||
*/
|
||||
@PostMapping("/getPageList")
|
||||
@Operation(summary = "查询分页列表", description = "查询分页列表")
|
||||
public CommonResult<PageInfo<PackageConfig>> getPageList(@RequestBody PackageConfigDto queryDto) {
|
||||
return CommonResult.success(packageConfigService.getPageList(queryDto));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*
|
||||
* @param queryDto 筛选条件
|
||||
* @return 查询结果
|
||||
*/
|
||||
@PostMapping("/getList")
|
||||
@Operation(summary = "查询列表", description = "查询列表")
|
||||
public CommonResult<PageInfo<PackageConfig>> getList(@RequestBody PackageConfigDto queryDto) {
|
||||
return CommonResult.success(new PageInfo<>(packageConfigService.getList(queryDto)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过主键查询单条数据
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 单条数据
|
||||
*/
|
||||
@PostMapping("queryById/{id}")
|
||||
@Operation(summary = "通过ID查询套餐", description = "通过ID查询套餐")
|
||||
public CommonResult<PackageConfig> queryById(@PathVariable("id") Long id) {
|
||||
return CommonResult.success(packageConfigService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增数据
|
||||
*
|
||||
* @param packageConfig 实体
|
||||
* @return 新增结果
|
||||
*/
|
||||
@PostMapping("/insert")
|
||||
@Operation(summary = "新增套餐", description = "新增套餐")
|
||||
@RequireAuth
|
||||
public CommonResult<PackageConfig> insert(@RequestBody PackageConfig packageConfig) {
|
||||
return CommonResult.success(packageConfigService.insert(packageConfig));
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑数据
|
||||
*
|
||||
* @param packageConfig 实体
|
||||
* @return 编辑结果
|
||||
*/
|
||||
@PostMapping("/update")
|
||||
@Operation(summary = "更新套餐", description = "更新套餐")
|
||||
@RequireAuth
|
||||
public CommonResult<PackageConfig> update(@RequestBody PackageConfig packageConfig) {
|
||||
return CommonResult.success(packageConfigService.update(packageConfig));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过主键逻辑删除
|
||||
*
|
||||
* @param idDto 主键
|
||||
* @return 删除数据
|
||||
*/
|
||||
@PostMapping("/logicDeleteById")
|
||||
@Operation(summary = "逻辑删除套餐", description = "逻辑删除套餐")
|
||||
@RequireAuth
|
||||
public CommonResult<Boolean> logicDeleteById(@RequestBody IdDto idDto) {
|
||||
return CommonResult.success(packageConfigService.logicDeleteById(idDto.getId(), "admin") > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除数据
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 删除数据
|
||||
*/
|
||||
@PostMapping("deleteById/{id}")
|
||||
@Operation(summary = "物理删除套餐", description = "物理删除套餐")
|
||||
@RequireAuth
|
||||
public CommonResult<Boolean> deleteById(@PathVariable("id") Long id) {
|
||||
return CommonResult.success(packageConfigService.deleteById(id) > 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package com.kexue.skills.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
import com.kexue.skills.entity.base.BaseEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* (PackageConfig)实体类
|
||||
* 套餐配置表
|
||||
*
|
||||
* @author 系统生成
|
||||
* @since 2026-04-11
|
||||
*/
|
||||
@Data
|
||||
public class PackageConfig extends BaseEntity implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description ="套餐ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description ="套餐名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description ="价格")
|
||||
private BigDecimal price;
|
||||
|
||||
@Schema(description ="基础额度")
|
||||
private BigDecimal baseAmount;
|
||||
|
||||
@Schema(description ="赠送额度")
|
||||
private BigDecimal giftAmount;
|
||||
|
||||
@Schema(description ="创建时间")
|
||||
private Date createTime;
|
||||
|
||||
@Schema(description ="更新时间")
|
||||
private Date updateTime;
|
||||
|
||||
}
|
||||
|
|
@ -56,7 +56,7 @@ public class PaymentOrder extends BaseEntity implements Serializable {
|
|||
@Schema(description ="商品描述")
|
||||
private String productDesc;
|
||||
|
||||
@Schema(description ="关联业务ID")
|
||||
@Schema(description ="关联业务ID,比如packageId(套餐ID)")
|
||||
private Long businessId;
|
||||
|
||||
@Schema(description ="业务类型:recharge,purchase_content")
|
||||
|
|
|
|||
|
|
@ -30,6 +30,12 @@ public class AccountFrozenDto {
|
|||
@Schema(description ="冻结类型:1余额 2图片张数 3时间 4次数 5积分 99其他")
|
||||
private Integer frozenType;
|
||||
|
||||
@Schema(description ="预估输入tokens")
|
||||
private Long estimatedInputTokens;
|
||||
|
||||
@Schema(description ="预估输出tokens")
|
||||
private Long estimatedOutputTokens;
|
||||
|
||||
@Schema(description ="过期时间")
|
||||
private Date expireAt;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
package com.kexue.skills.entity.dto;
|
||||
|
||||
import com.kexue.skills.entity.base.BaseQueryDto;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* (PackageConfig)查询DTO类
|
||||
*
|
||||
* @author 系统生成
|
||||
* @since 2026-04-11
|
||||
*/
|
||||
@Data
|
||||
public class PackageConfigDto extends BaseQueryDto {
|
||||
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
private BigDecimal price;
|
||||
|
||||
private BigDecimal baseAmount;
|
||||
|
||||
private BigDecimal giftAmount;
|
||||
|
||||
}
|
||||
|
|
@ -30,4 +30,6 @@ public class PaymentOrderDto extends BaseQueryDto {
|
|||
|
||||
private Integer deleteFlag;
|
||||
|
||||
private Long packageId;
|
||||
|
||||
}
|
||||
|
|
@ -15,9 +15,9 @@ public class GlobalExceptionHandler {
|
|||
@ExceptionHandler(BizException.class)
|
||||
public CommonResult<String> handleBizException(BizException e) {
|
||||
if (e == null) {
|
||||
return CommonResult.failed("未知错误");
|
||||
return CommonResult.success("未知错误");
|
||||
}
|
||||
return CommonResult.failed(e.getMessage());
|
||||
return CommonResult.success(e.getErrorCode(), e.getMessage());
|
||||
}
|
||||
|
||||
// 其他异常处理...
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
package com.kexue.skills.mapper;
|
||||
|
||||
import com.kexue.skills.entity.PackageConfig;
|
||||
import com.kexue.skills.entity.dto.PackageConfigDto;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* (PackageConfig)表数据库访问层
|
||||
*
|
||||
* @author 系统生成
|
||||
* @since 2026-04-11
|
||||
*/
|
||||
@Mapper
|
||||
public interface PackageConfigMapper {
|
||||
/**
|
||||
* 分页查询
|
||||
*
|
||||
* @param queryDto 筛选条件
|
||||
* @return 查询结果
|
||||
*/
|
||||
List<PackageConfig> getPageList(PackageConfigDto queryDto);
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*
|
||||
* @param queryDto 筛选条件
|
||||
* @return 查询结果
|
||||
*/
|
||||
List<PackageConfig> getList(PackageConfigDto queryDto);
|
||||
|
||||
/**
|
||||
* 通过主键查询单条数据
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 实例对象
|
||||
*/
|
||||
PackageConfig queryById(Long id);
|
||||
|
||||
/**
|
||||
* 新增数据
|
||||
*
|
||||
* @param packageConfig 实例对象
|
||||
* @return 影响行数
|
||||
*/
|
||||
int insert(PackageConfig packageConfig);
|
||||
|
||||
/**
|
||||
* 更新数据
|
||||
*
|
||||
* @param packageConfig 实例对象
|
||||
* @return 影响行数
|
||||
*/
|
||||
int update(PackageConfig packageConfig);
|
||||
|
||||
/**
|
||||
* 通过主键删除
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 影响行数
|
||||
*/
|
||||
int deleteById(Long id);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
package com.kexue.skills.service;
|
||||
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.kexue.skills.entity.PackageConfig;
|
||||
import com.kexue.skills.entity.dto.PackageConfigDto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* (PackageConfig)表服务接口
|
||||
*
|
||||
* @author 系统生成
|
||||
* @since 2026-04-11
|
||||
*/
|
||||
public interface PackageConfigService extends BaseService {
|
||||
/**
|
||||
* 分页查询
|
||||
*
|
||||
* @param queryDto 筛选条件
|
||||
* @return 查询结果
|
||||
*/
|
||||
PageInfo<PackageConfig> getPageList(PackageConfigDto queryDto);
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*
|
||||
* @param queryDto 筛选条件
|
||||
* @return 查询结果
|
||||
*/
|
||||
List<PackageConfig> getList(PackageConfigDto queryDto);
|
||||
|
||||
/**
|
||||
* 通过主键查询单条数据
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 实例对象
|
||||
*/
|
||||
PackageConfig queryById(Long id);
|
||||
|
||||
/**
|
||||
* 新增数据
|
||||
*
|
||||
* @param packageConfig 实例对象
|
||||
* @return 实例对象
|
||||
*/
|
||||
PackageConfig insert(PackageConfig packageConfig);
|
||||
|
||||
/**
|
||||
* 更新数据
|
||||
*
|
||||
* @param packageConfig 实例对象
|
||||
* @return 实例对象
|
||||
*/
|
||||
PackageConfig update(PackageConfig packageConfig);
|
||||
|
||||
/**
|
||||
* 通过主键逻辑删除
|
||||
*
|
||||
* @param id 主键
|
||||
* @param updateBy 更新人
|
||||
* @return 影响行数
|
||||
*/
|
||||
int logicDeleteById(Long id, String updateBy);
|
||||
|
||||
/**
|
||||
* 通过主键物理删除
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 影响行数
|
||||
*/
|
||||
int deleteById(Long id);
|
||||
|
||||
}
|
||||
|
|
@ -62,9 +62,6 @@ public class AccountFrozenServiceImpl implements AccountFrozenService {
|
|||
if (accountFrozenDto.getSessionId() == null) {
|
||||
throw new BizException(ResultCode.SESSION_ID_NOT_EXIST.getCode(), ResultCode.SESSION_ID_NOT_EXIST.getMessage());
|
||||
}
|
||||
if (accountFrozenDto.getFrozenAmount() == null || accountFrozenDto.getFrozenAmount().compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new BizException(ResultCode.PARAMETER_EMPTY.getCode(), "冻结金额必须大于0");
|
||||
}
|
||||
if (accountFrozenDto.getFrozenType() == null) {
|
||||
throw new BizException(ResultCode.PARAMETER_EMPTY.getCode(), "冻结类型不能为空");
|
||||
}
|
||||
|
|
@ -82,16 +79,47 @@ public class AccountFrozenServiceImpl implements AccountFrozenService {
|
|||
throw new BizException(ResultCode.ACCOUNT_NOT_EXIST.getCode(), ResultCode.ACCOUNT_NOT_EXIST.getMessage());
|
||||
}
|
||||
|
||||
// 4. 检查余额是否足够(账户总余额 - 已冻结金额 >= 本次冻结金额)
|
||||
// 4. 当冻结类型为余额时,根据预估tokens计算冻结金额
|
||||
BigDecimal finalFrozenAmount = accountFrozenDto.getFrozenAmount();
|
||||
if (accountFrozenDto.getFrozenType() != null && accountFrozenDto.getFrozenType() == 1) {
|
||||
if (accountFrozenDto.getEstimatedInputTokens() != null &&
|
||||
accountFrozenDto.getEstimatedOutputTokens() != null &&
|
||||
accountFrozenDto.getModelName() != null) {
|
||||
|
||||
// 查询模型价格信息
|
||||
ModelPrice modelPrice = modelPriceService.queryByModelName(accountFrozenDto.getModelName());
|
||||
if (modelPrice != null) {
|
||||
// 计算token费用
|
||||
long inputFee = accountFrozenDto.getEstimatedInputTokens() / modelPrice.getInputPerCent();
|
||||
if (accountFrozenDto.getEstimatedInputTokens() % modelPrice.getInputPerCent() > 0) {
|
||||
inputFee += 1;
|
||||
}
|
||||
|
||||
long outputFee = accountFrozenDto.getEstimatedOutputTokens() / modelPrice.getOutputPerCent();
|
||||
if (accountFrozenDto.getEstimatedOutputTokens() % modelPrice.getOutputPerCent() > 0) {
|
||||
outputFee += 1;
|
||||
}
|
||||
|
||||
// 总费用(分)
|
||||
long totalFee = inputFee + outputFee;
|
||||
// 转换为元
|
||||
BigDecimal feeInYuan = BigDecimal.valueOf(totalFee).divide(BigDecimal.valueOf(100));
|
||||
// 转换为积分(1元=100积分)
|
||||
finalFrozenAmount = feeInYuan.multiply(BigDecimal.valueOf(100));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 检查余额是否足够(账户总余额 - 已冻结金额 >= 本次冻结金额)
|
||||
BigDecimal balance = account.getBalance() == null ? BigDecimal.ZERO : account.getBalance();
|
||||
BigDecimal frozenAmount = account.getFrozenAmount() == null ? BigDecimal.ZERO : account.getFrozenAmount();
|
||||
BigDecimal availableBalance = balance.subtract(frozenAmount);
|
||||
if (availableBalance.compareTo(accountFrozenDto.getFrozenAmount()) < 0) {
|
||||
if (availableBalance.compareTo(finalFrozenAmount) < 0) {
|
||||
throw new BizException(ResultCode.INSUFFICIENT_BALANCE.getCode(), ResultCode.INSUFFICIENT_BALANCE.getMessage());
|
||||
}
|
||||
|
||||
// 5. 更新账户冻结金额
|
||||
account.setFrozenAmount(frozenAmount.add(accountFrozenDto.getFrozenAmount()));
|
||||
account.setFrozenAmount(frozenAmount.add(finalFrozenAmount));
|
||||
account.setUpdateTime(new Date());
|
||||
accountMapper.update(account);
|
||||
|
||||
|
|
@ -102,7 +130,7 @@ public class AccountFrozenServiceImpl implements AccountFrozenService {
|
|||
accountFrozen.setSessionId(accountFrozenDto.getSessionId());
|
||||
accountFrozen.setCallId(accountFrozenDto.getCallId());
|
||||
accountFrozen.setModelName(accountFrozenDto.getModelName());
|
||||
accountFrozen.setFrozenAmount(accountFrozenDto.getFrozenAmount());
|
||||
accountFrozen.setFrozenAmount(finalFrozenAmount);
|
||||
accountFrozen.setFrozenType(accountFrozenDto.getFrozenType());
|
||||
accountFrozen.setStatus("RESERVED");
|
||||
accountFrozen.setExpireAt(accountFrozenDto.getExpireAt());
|
||||
|
|
@ -173,7 +201,9 @@ public class AccountFrozenServiceImpl implements AccountFrozenService {
|
|||
// 总费用(分)
|
||||
long totalFee = inputFee + outputFee;
|
||||
// 转换为元
|
||||
finalAmount = BigDecimal.valueOf(totalFee).divide(BigDecimal.valueOf(100));
|
||||
BigDecimal feeInYuan = BigDecimal.valueOf(totalFee).divide(BigDecimal.valueOf(100));
|
||||
// 转换为积分(1元=100积分)
|
||||
finalAmount = feeInYuan.multiply(BigDecimal.valueOf(100));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -188,10 +218,32 @@ public class AccountFrozenServiceImpl implements AccountFrozenService {
|
|||
|
||||
// 如果需要扣减余额
|
||||
if (finalAmount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
if (balance.compareTo(finalAmount) < 0) {
|
||||
throw new BizException(ResultCode.INSUFFICIENT_BALANCE.getCode(), ResultCode.INSUFFICIENT_BALANCE.getMessage());
|
||||
// 检查实际扣减是否大于预扣减
|
||||
if (finalAmount.compareTo(accountFrozen.getFrozenAmount()) > 0) {
|
||||
// 实际扣减大于预扣减,根据实际扣减进行扣除
|
||||
// 如果余额不够实际扣减,将balance设置为0
|
||||
if (balance.compareTo(finalAmount) < 0) {
|
||||
account.setBalance(BigDecimal.ZERO);
|
||||
} else {
|
||||
account.setBalance(balance.subtract(finalAmount));
|
||||
}
|
||||
} else {
|
||||
// 实际扣减小于等于预扣减,正常扣减
|
||||
if (balance.compareTo(finalAmount) < 0) {
|
||||
account.setBalance(BigDecimal.ZERO);
|
||||
} else {
|
||||
account.setBalance(balance.subtract(finalAmount));
|
||||
}
|
||||
|
||||
// 实际扣减小于预扣减,将剩余预扣减加回balance
|
||||
if (finalAmount.compareTo(accountFrozen.getFrozenAmount()) < 0) {
|
||||
BigDecimal remainingFrozen = accountFrozen.getFrozenAmount().subtract(finalAmount);
|
||||
account.setBalance(account.getBalance().add(remainingFrozen));
|
||||
}
|
||||
}
|
||||
account.setBalance(balance.subtract(finalAmount));
|
||||
} else {
|
||||
// 最终扣减为0,将预扣减全部加回balance
|
||||
account.setBalance(balance.add(accountFrozen.getFrozenAmount()));
|
||||
}
|
||||
|
||||
account.setUpdateTime(new Date());
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@ import com.kexue.skills.mapper.AccountTransactionMapper;
|
|||
import com.kexue.skills.mapper.SysUserMapper;
|
||||
import com.kexue.skills.service.AccountService;
|
||||
import com.kexue.skills.service.ModelPriceService;
|
||||
import com.kexue.skills.service.PackageConfigService;
|
||||
import com.kexue.skills.entity.ModelPrice;
|
||||
import com.kexue.skills.entity.PackageConfig;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -43,6 +45,9 @@ public class AccountServiceImpl implements AccountService {
|
|||
@Resource
|
||||
private ModelPriceService modelPriceService;
|
||||
|
||||
@Resource
|
||||
private PackageConfigService packageConfigService;
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*
|
||||
|
|
@ -162,34 +167,63 @@ public class AccountServiceImpl implements AccountService {
|
|||
this.insert(account);
|
||||
}
|
||||
|
||||
// 2. 保存交易记录
|
||||
// 2. 计算积分
|
||||
BigDecimal points = BigDecimal.ZERO;
|
||||
String rechargeRemark = "";
|
||||
|
||||
// 检查是否有套餐ID
|
||||
if (businessId != null) {
|
||||
// 查询套餐配置
|
||||
PackageConfig packageConfig = packageConfigService.queryById(businessId);
|
||||
if (packageConfig != null) {
|
||||
// 套餐的baseAmount和giftAmount已经是积分,直接使用
|
||||
points = packageConfig.getBaseAmount().add(packageConfig.getGiftAmount());
|
||||
// 根据套餐信息生成remark
|
||||
rechargeRemark = "购买套餐:" + packageConfig.getName() + ",获得" + points + "积分";
|
||||
} else {
|
||||
// 没有找到套餐,按照原来的逻辑计算积分
|
||||
points = amount.multiply(BigDecimal.valueOf(100));
|
||||
rechargeRemark = "充值" + amount + "元,获得" + points + "积分";
|
||||
}
|
||||
} else {
|
||||
// 没有套餐ID,按照原来的逻辑计算积分
|
||||
points = amount.multiply(BigDecimal.valueOf(100));
|
||||
rechargeRemark = "充值" + amount + "元,获得" + points + "积分";
|
||||
}
|
||||
|
||||
// 3. 保存交易记录
|
||||
AccountTransaction transaction = new AccountTransaction();
|
||||
transaction.setUserId(userId);
|
||||
transaction.setUserName(account.getUserName());
|
||||
transaction.setTransactionType(1); // 充值
|
||||
transaction.setAmount(amount);
|
||||
transaction.setAmount(points); // 存储充值金额(积分)
|
||||
transaction.setBeforeBalance(account.getBalance());
|
||||
transaction.setAfterBalance(account.getBalance().add(amount));
|
||||
transaction.setAfterBalance(account.getBalance().add(points)); // 余额为积分
|
||||
transaction.setStatus(1); // 成功
|
||||
transaction.setTransactionNo(transactionNo);
|
||||
transaction.setPayType(3); // 余额支付
|
||||
transaction.setBusinessId(businessId);
|
||||
transaction.setBusinessId(businessId);// 套餐ID
|
||||
transaction.setBusinessType(businessType);
|
||||
transaction.setRemark(remark);
|
||||
|
||||
// 添加额外备注
|
||||
if (remark != null && !remark.isEmpty()) {
|
||||
rechargeRemark += " - " + remark;
|
||||
}
|
||||
transaction.setRemark(rechargeRemark);
|
||||
transaction.setIsExpense(0); // 收入
|
||||
transaction.setIncomeType("recharge"); // 充值
|
||||
this.accountTransactionMapper.insert(transaction);
|
||||
|
||||
// 3. 更新账户余额
|
||||
// 4. 更新账户余额(使用积分)
|
||||
if (isWithdrawable) {
|
||||
BigDecimal withdrawableBalance = account.getWithdrawableBalance() == null ? BigDecimal.ZERO : account.getWithdrawableBalance();
|
||||
account.setWithdrawableBalance(withdrawableBalance.add(amount));
|
||||
account.setWithdrawableBalance(withdrawableBalance.add(points));
|
||||
} else {
|
||||
BigDecimal nonWithdrawableBalance = account.getNonWithdrawableBalance() == null ? BigDecimal.ZERO : account.getNonWithdrawableBalance();
|
||||
account.setNonWithdrawableBalance(nonWithdrawableBalance.add(amount));
|
||||
account.setNonWithdrawableBalance(nonWithdrawableBalance.add(points));
|
||||
}
|
||||
BigDecimal balance = account.getBalance() == null ? BigDecimal.ZERO : account.getBalance();
|
||||
account.setBalance(balance.add(amount));
|
||||
account.setBalance(balance.add(points));
|
||||
account.setUpdateTime(new Date());
|
||||
this.update(account);
|
||||
return 1;
|
||||
|
|
@ -327,15 +361,13 @@ public class AccountServiceImpl implements AccountService {
|
|||
Assert.notNull(dto.getQuestion(), "问题不能为空");
|
||||
Assert.notNull(dto.getModelName(), "模型名称不能为空");
|
||||
Assert.notNull(dto.getQuestion(), "问题不能为空");
|
||||
Assert.notNull(dto.getModelName(), "模型名称不能为空");
|
||||
Assert.notNull(dto.getQuestion(), "问题不能为空");
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加账户余额(签到奖励token转换)
|
||||
* 增加账户积分(签到奖励token转换)
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param amount 增加金额
|
||||
* @param amount 增加积分
|
||||
* @param transactionNo 交易单号
|
||||
* @param businessId 业务ID
|
||||
* @param businessType 业务类型
|
||||
|
|
@ -362,15 +394,20 @@ public class AccountServiceImpl implements AccountService {
|
|||
transaction.setUserId(userId);
|
||||
transaction.setUserName(account.getUserName());
|
||||
transaction.setTransactionType(5); // 签到奖励
|
||||
transaction.setAmount(amount);
|
||||
transaction.setAmount(amount); // 存储签到奖励积分
|
||||
transaction.setBeforeBalance(account.getBalance());
|
||||
transaction.setAfterBalance(account.getBalance().add(amount));
|
||||
transaction.setAfterBalance(account.getBalance().add(amount)); // 余额为积分
|
||||
transaction.setStatus(1); // 成功
|
||||
transaction.setTransactionNo(transactionNo);
|
||||
transaction.setPayType(3); // 余额支付
|
||||
transaction.setBusinessId(businessId);
|
||||
transaction.setBusinessType(businessType);
|
||||
transaction.setRemark(remark);
|
||||
// 在备注中添加积分信息
|
||||
String signInRemark = "签到奖励" + amount + "积分";
|
||||
if (remark != null && !remark.isEmpty()) {
|
||||
signInRemark += " - " + remark;
|
||||
}
|
||||
transaction.setRemark(signInRemark);
|
||||
transaction.setIsExpense(0); // 收入
|
||||
transaction.setIncomeType("sign_in"); // 签到奖励
|
||||
this.accountTransactionMapper.insert(transaction);
|
||||
|
|
@ -386,10 +423,10 @@ public class AccountServiceImpl implements AccountService {
|
|||
}
|
||||
|
||||
/**
|
||||
* 给用户赠送金额(不可提现)
|
||||
* 给用户赠送积分(不可提现)
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param amount 赠送金额
|
||||
* @param amount 赠送积分
|
||||
* @param transactionNo 交易单号
|
||||
* @param businessId 业务ID
|
||||
* @param businessType 业务类型
|
||||
|
|
@ -416,20 +453,25 @@ public class AccountServiceImpl implements AccountService {
|
|||
transaction.setUserId(userId);
|
||||
transaction.setUserName(account.getUserName());
|
||||
transaction.setTransactionType(6); // 赠送
|
||||
transaction.setAmount(amount);
|
||||
transaction.setAmount(amount); // 存储赠送积分
|
||||
transaction.setBeforeBalance(account.getBalance());
|
||||
transaction.setAfterBalance(account.getBalance().add(amount));
|
||||
transaction.setAfterBalance(account.getBalance().add(amount)); // 余额为积分
|
||||
transaction.setStatus(1); // 成功
|
||||
transaction.setTransactionNo(transactionNo);
|
||||
transaction.setPayType(3); // 余额支付
|
||||
transaction.setBusinessId(businessId);
|
||||
transaction.setBusinessType(businessType);
|
||||
transaction.setRemark(remark);
|
||||
// 在备注中添加积分信息
|
||||
String giftRemark = "赠送" + amount + "积分";
|
||||
if (remark != null && !remark.isEmpty()) {
|
||||
giftRemark += " - " + remark;
|
||||
}
|
||||
transaction.setRemark(giftRemark);
|
||||
transaction.setIsExpense(0); // 收入
|
||||
transaction.setIncomeType("gift"); // 赠送
|
||||
this.accountTransactionMapper.insert(transaction);
|
||||
|
||||
// 3. 更新账户余额(赠送金额不可提现)
|
||||
// 3. 更新账户余额(赠送积分不可提现)
|
||||
if (account.getNonWithdrawableBalance() == null){
|
||||
account.setNonWithdrawableBalance(BigDecimal.ZERO);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
package com.kexue.skills.service.impl;
|
||||
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.kexue.skills.entity.PackageConfig;
|
||||
import com.kexue.skills.entity.dto.PackageConfigDto;
|
||||
import com.kexue.skills.mapper.PackageConfigMapper;
|
||||
import com.kexue.skills.service.PackageConfigService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* (PackageConfig)表服务实现类
|
||||
*
|
||||
* @author 系统生成
|
||||
* @since 2026-04-11
|
||||
*/
|
||||
@Service
|
||||
public class PackageConfigServiceImpl implements PackageConfigService {
|
||||
|
||||
@Resource
|
||||
private PackageConfigMapper packageConfigMapper;
|
||||
|
||||
@Override
|
||||
public PageInfo<PackageConfig> getPageList(PackageConfigDto queryDto) {
|
||||
PageHelper.startPage(queryDto.getPageNum(), queryDto.getPageSize());
|
||||
List<PackageConfig> list = packageConfigMapper.getList(queryDto);
|
||||
return new PageInfo<>(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PackageConfig> getList(PackageConfigDto queryDto) {
|
||||
return packageConfigMapper.getList(queryDto);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PackageConfig queryById(Long id) {
|
||||
return packageConfigMapper.queryById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PackageConfig insert(PackageConfig packageConfig) {
|
||||
packageConfigMapper.insert(packageConfig);
|
||||
return packageConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PackageConfig update(PackageConfig packageConfig) {
|
||||
packageConfigMapper.update(packageConfig);
|
||||
return packageConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int logicDeleteById(Long id, String updateBy) {
|
||||
// 由于package_config表没有delete_flag字段,这里直接调用物理删除
|
||||
return packageConfigMapper.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deleteById(Long id) {
|
||||
return packageConfigMapper.deleteById(id);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -11,6 +11,8 @@ import com.kexue.skills.entity.PaymentOrder;
|
|||
import com.kexue.skills.service.PayService;
|
||||
import com.kexue.skills.service.PaymentOrderService;
|
||||
import com.kexue.skills.service.AccountService;
|
||||
import com.kexue.skills.service.PackageConfigService;
|
||||
import com.kexue.skills.entity.PackageConfig;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
|
@ -63,6 +65,9 @@ public class PayServiceImpl implements PayService {
|
|||
@Resource
|
||||
private AccountService accountService;
|
||||
|
||||
@Resource
|
||||
private PackageConfigService packageConfigService;
|
||||
|
||||
/**
|
||||
* 生成随机字符串
|
||||
* @return 随机字符串
|
||||
|
|
@ -505,11 +510,11 @@ public class PayServiceImpl implements PayService {
|
|||
order.getAmount(),
|
||||
true, // 可提现
|
||||
transactionId,
|
||||
order.getOrderId(),
|
||||
order.getBusinessId(),
|
||||
"recharge",
|
||||
"微信支付充值"
|
||||
);
|
||||
logger.info("微信支付回调:更新账户余额成功,userId={}, amount={}", order.getUserId(), order.getAmount());
|
||||
logger.info("微信支付回调:更新账户余额成功,userId={}, amount={}, actualAmount={}", order.getUserId(), order.getAmount(), order.getAmount());
|
||||
} catch (Exception e) {
|
||||
logger.error("微信支付回调:更新账户余额失败", e);
|
||||
// 继续处理,不影响回调响应
|
||||
|
|
@ -564,6 +569,7 @@ public class PayServiceImpl implements PayService {
|
|||
bizContent.put("out_trade_no", order.getOrderNo());
|
||||
bizContent.put("total_amount", order.getAmount().toString());
|
||||
bizContent.put("subject", order.getProductName());
|
||||
bizContent.put("businessId", order.getBusinessId());
|
||||
bizContent.put("body", order.getProductDesc());
|
||||
bizContent.put("timeout_express", "30m");
|
||||
bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
|
||||
|
|
@ -647,11 +653,11 @@ public class PayServiceImpl implements PayService {
|
|||
order.getAmount(),
|
||||
true, // 可提现
|
||||
transactionId,
|
||||
order.getOrderId(),
|
||||
order.getBusinessId(),
|
||||
"recharge",
|
||||
"支付宝支付充值"
|
||||
);
|
||||
logger.info("支付宝支付回调:更新账户余额成功,userId={}, amount={}", order.getUserId(), order.getAmount());
|
||||
logger.info("支付宝支付回调:更新账户余额成功,userId={}, amount={}, actualAmount={}", order.getUserId(), order.getAmount(), order.getAmount());
|
||||
} catch (Exception e) {
|
||||
logger.error("支付宝支付回调:更新账户余额失败", e);
|
||||
// 继续处理,不影响回调响应
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -94,3 +94,7 @@ web:
|
|||
upload:
|
||||
path: /kexue/agentSkills/upload/
|
||||
|
||||
# 雪花算法配置
|
||||
snowflake:
|
||||
workid: 1 # 机器ID,分布式部署时需要保证唯一
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,90 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.kexue.skills.mapper.PackageConfigMapper">
|
||||
|
||||
<resultMap type="com.kexue.skills.entity.PackageConfig" id="PackageConfigMap">
|
||||
<result property="id" column="id" jdbcType="BIGINT"/>
|
||||
<result property="name" column="name" jdbcType="VARCHAR"/>
|
||||
<result property="price" column="price" jdbcType="DECIMAL"/>
|
||||
<result property="baseAmount" column="base_amount" jdbcType="DECIMAL"/>
|
||||
<result property="giftAmount" column="gift_amount" jdbcType="DECIMAL"/>
|
||||
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
|
||||
<result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
|
||||
</resultMap>
|
||||
|
||||
<!--查询单个-->
|
||||
<select id="queryById" resultMap="PackageConfigMap">
|
||||
select
|
||||
id, name, price, base_amount, gift_amount, create_time, update_time
|
||||
from package_config
|
||||
where id = #{id}
|
||||
</select>
|
||||
|
||||
<!--分页查询-->
|
||||
<select id="getPageList" resultMap="PackageConfigMap">
|
||||
select
|
||||
id, name, price, base_amount, gift_amount, create_time, update_time
|
||||
from package_config
|
||||
<where>
|
||||
<if test="name != null and name != ''">
|
||||
and name like concat('%', #{name}, '%')
|
||||
</if>
|
||||
</where>
|
||||
<if test="sortBy != null and sortBy != ''">
|
||||
order by ${sortBy} ${sortDesc ? 'desc' : 'asc'}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<!--查询列表-->
|
||||
<select id="getList" resultMap="PackageConfigMap">
|
||||
select
|
||||
id, name, price, base_amount, gift_amount, create_time, update_time
|
||||
from package_config
|
||||
<where>
|
||||
<if test="name != null and name != ''">
|
||||
and name like concat('%', #{name}, '%')
|
||||
</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
<!--新增数据-->
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||
insert into package_config
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
<if test="name != null">name,</if>
|
||||
<if test="price != null">price,</if>
|
||||
<if test="baseAmount != null">base_amount,</if>
|
||||
<if test="giftAmount != null">gift_amount,</if>
|
||||
<if test="createTime != null">create_time,</if>
|
||||
<if test="updateTime != null">update_time,</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="name != null">#{name},</if>
|
||||
<if test="price != null">#{price},</if>
|
||||
<if test="baseAmount != null">#{baseAmount},</if>
|
||||
<if test="giftAmount != null">#{giftAmount},</if>
|
||||
<if test="createTime != null">#{createTime},</if>
|
||||
<if test="updateTime != null">#{updateTime},</if>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
<!--更新数据-->
|
||||
<update id="update">
|
||||
update package_config
|
||||
<set>
|
||||
<if test="name != null">name = #{name},</if>
|
||||
<if test="price != null">price = #{price},</if>
|
||||
<if test="baseAmount != null">base_amount = #{baseAmount},</if>
|
||||
<if test="giftAmount != null">gift_amount = #{giftAmount},</if>
|
||||
<if test="updateTime != null">update_time = #{updateTime},</if>
|
||||
</set>
|
||||
where id = #{id}
|
||||
</update>
|
||||
|
||||
<!--删除-->
|
||||
<delete id="deleteById">
|
||||
delete from package_config
|
||||
where id = #{id}
|
||||
</delete>
|
||||
|
||||
</mapper>
|
||||
Loading…
Reference in New Issue