feat(account): 实现账户积分系统及套餐功能

- 将账户余额系统改造为积分系统,充值金额按1元=100积分计算
- 新增套餐配置功能,支持套餐购买并获取基础额度和赠送额度
- 在账户冻结功能中集成模型价格计算,根据预估tokens自动计算冻结金额
- 更新支付流程以支持套餐ID关联和积分计算
- 修改全局异常处理器返回格式,统一使用CommonResult
- 优化账户交易记录的备注信息显示
- 添加雪花算法配置用于分布式ID生成
- 扩展账户冻结DTO添加预估tokens字段
- 重构账户服务中的金额处理逻辑为积分处理逻辑
- 实现套餐配置的CRUD操作接口和相关实体类
- 更新支付回调逻辑以正确处理套餐购买场景
This commit is contained in:
wangzhiwei 2026-04-11 21:11:53 +08:00
parent 51fce1ece6
commit e651e73fa2
18 changed files with 835 additions and 856 deletions

View File

@ -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);
}
/**

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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")

View File

@ -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;

View File

@ -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;
}

View File

@ -30,4 +30,6 @@ public class PaymentOrderDto extends BaseQueryDto {
private Integer deleteFlag;
private Long packageId;
}

View File

@ -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());
}
// 其他异常处理...

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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());

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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);
// 继续处理不影响回调响应

View File

@ -94,3 +94,7 @@ web:
upload:
path: /kexue/agentSkills/upload/
# 雪花算法配置
snowflake:
workid: 1 # 机器ID分布式部署时需要保证唯一

View File

@ -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>