feat(account): 实现账户功能完善与支付集成
- 集成Sa-Token框架实现用户身份认证和权限管理 - 修改账户查询接口,基于当前登录用户自动获取用户ID - 优化账户余额操作,增加空值检查和BigDecimal精度处理 - 添加支付订单状态查询功能,支持按订单ID或订单号查询 - 实现支付回调处理,在微信和支付宝支付成功后自动更新账户余额 - 完善内容购买流程,基于当前登录用户进行权限验证 - 优化CMS内容推荐算法,改进标签匹配和去重逻辑 - 更新AI技能生成服务,优化YAML输出格式和目录结构规范
This commit is contained in:
parent
f2b8a735f2
commit
bad416aeab
|
|
@ -1,5 +1,6 @@
|
|||
package com.kexue.skills.controller;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.kexue.skills.annotation.RequireAuth;
|
||||
import com.kexue.skills.annotation.RequireRole;
|
||||
|
|
@ -10,12 +11,18 @@ import com.kexue.skills.entity.dto.AccountDto;
|
|||
import com.kexue.skills.entity.dto.TokenConsumptionDto;
|
||||
import com.kexue.skills.entity.dto.GiftBalanceDto;
|
||||
import com.kexue.skills.service.AccountService;
|
||||
import com.kexue.skills.service.SysUserService;
|
||||
import com.kexue.skills.entity.SysUser;
|
||||
import com.kexue.skills.exception.BizException;
|
||||
import com.kexue.skills.common.CacheManager;
|
||||
import java.math.BigDecimal;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
|
@ -35,6 +42,9 @@ public class AccountController {
|
|||
@Resource
|
||||
private AccountService accountService;
|
||||
|
||||
@Resource
|
||||
private SysUserService sysUserService;
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*
|
||||
|
|
@ -67,7 +77,7 @@ public class AccountController {
|
|||
* @param accountId 主键
|
||||
* @return 单条数据
|
||||
*/
|
||||
@Operation(summary = "通过ID查询账户", description = "通过ID查询账户")
|
||||
@Operation(summary = "通过查询账户", description = "通过ID查询账户")
|
||||
@PostMapping("/queryById/{accountId}")
|
||||
@RequireAuth
|
||||
public CommonResult<Account> queryById(@Parameter(description = "账户ID") @PathVariable("accountId") Long accountId) {
|
||||
|
|
@ -75,15 +85,15 @@ public class AccountController {
|
|||
}
|
||||
|
||||
/**
|
||||
* 通过用户ID查询单条数据
|
||||
* 通过当前登录用户ID查询账户
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 单条数据
|
||||
*/
|
||||
@Operation(summary = "通过用户ID查询账户", description = "通过用户ID查询账户")
|
||||
@PostMapping("/queryByUserId/{userId}")
|
||||
@Operation(summary = "通过当前登录用户查询账户", description = "通过当前登录用户查询账户")
|
||||
@PostMapping("/currentAccount")
|
||||
@RequireAuth
|
||||
public CommonResult<Account> queryByUserId(@Parameter(description = "用户ID") @PathVariable("userId") Long userId) {
|
||||
public CommonResult<Account> currentAccount() {
|
||||
Long userId = Long.parseLong(StpUtil.getLoginId().toString());
|
||||
return CommonResult.success(this.accountService.queryByUserId(userId));
|
||||
}
|
||||
|
||||
|
|
@ -111,12 +121,12 @@ public class AccountController {
|
|||
* 减少账户余额(token消费转换)
|
||||
*
|
||||
* @param tokenConsumptionDto token消费转换参数
|
||||
* @return 影响行数
|
||||
* @return 消耗的金额
|
||||
*/
|
||||
@Operation(summary = "减少账户余额(token消费转换)", description = "减少账户余额(token消费转换)")
|
||||
@PostMapping("/reduceBalanceWithToken")
|
||||
@RequireAuth
|
||||
public CommonResult<Integer> reduceBalanceWithToken( @RequestBody TokenConsumptionDto tokenConsumptionDto) {
|
||||
public CommonResult<BigDecimal> reduceBalanceWithToken( @RequestBody TokenConsumptionDto tokenConsumptionDto) {
|
||||
return CommonResult.success(this.accountService.reduceBalanceWithToken(tokenConsumptionDto));
|
||||
}
|
||||
|
||||
|
|
@ -143,17 +153,17 @@ public class AccountController {
|
|||
}
|
||||
|
||||
/**
|
||||
* 获取用户交易记录
|
||||
* 获取当前登录用户交易记录
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 交易记录列表
|
||||
*/
|
||||
@Operation(summary = "获取用户交易记录", description = "获取用户交易记录")
|
||||
@Operation(summary = "获取当前登录用户交易记录", description = "获取当前登录用户交易记录")
|
||||
@PostMapping("/getTransactions")
|
||||
@RequireAuth
|
||||
public CommonResult<List<com.kexue.skills.entity.AccountTransaction>> getTransactions(
|
||||
@RequestBody java.util.Map<String, Object> params) {
|
||||
Long userId = Long.valueOf(params.get("userId").toString());
|
||||
public CommonResult<List<com.kexue.skills.entity.AccountTransaction>> getTransactions() {
|
||||
Long userId = Long.parseLong(StpUtil.getLoginId().toString());
|
||||
return CommonResult.success(this.accountService.getTransactions(userId));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.kexue.skills.controller;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.kexue.skills.annotation.RequireAuth;
|
||||
import com.kexue.skills.common.CommonResult;
|
||||
|
|
@ -73,7 +74,6 @@ public class ContentPurchaseController {
|
|||
/**
|
||||
* 购买内容
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param contentId 内容ID
|
||||
* @param payType 支付方式:1.余额支付 2.积分支付
|
||||
* @return 购买结果
|
||||
|
|
@ -82,16 +82,15 @@ public class ContentPurchaseController {
|
|||
@PostMapping("/purchase")
|
||||
@RequireAuth
|
||||
public CommonResult<ContentPurchase> purchaseContent(
|
||||
@Parameter(description = "用户ID") @RequestParam("userId") Long userId,
|
||||
@Parameter(description = "内容ID") @RequestParam("contentId") Long contentId,
|
||||
@Parameter(description = "支付方式:1.余额支付 2.积分支付") @RequestParam("payType") Integer payType) {
|
||||
Long userId = Long.parseLong(StpUtil.getLoginId().toString());
|
||||
return CommonResult.success(this.contentPurchaseService.purchaseContent(userId, contentId, payType));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否有权限访问内容
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param contentId 内容ID
|
||||
* @return 是否有权限
|
||||
*/
|
||||
|
|
@ -99,8 +98,8 @@ public class ContentPurchaseController {
|
|||
@PostMapping("/checkPermission")
|
||||
@RequireAuth
|
||||
public CommonResult<Boolean> checkAccessPermission(
|
||||
@Parameter(description = "用户ID") @RequestParam("userId") Long userId,
|
||||
@Parameter(description = "内容ID") @RequestParam("contentId") Long contentId) {
|
||||
Long userId = Long.parseLong(StpUtil.getLoginId().toString());
|
||||
boolean hasPermission = this.contentPurchaseService.checkAccessPermission(userId, contentId);
|
||||
return CommonResult.success(hasPermission);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,11 @@ import org.springframework.web.bind.annotation.*;
|
|||
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import com.kexue.skills.entity.dto.OrderStatusDto;
|
||||
import com.kexue.skills.entity.dto.OrderStatusQueryDto;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 支付控制器
|
||||
|
|
@ -146,4 +150,43 @@ public class PayController {
|
|||
return CommonResult.failed(result.get("message").toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询订单状态
|
||||
* @param queryDto 查询参数,包含 orderId 或 orderNo
|
||||
* @return 订单状态信息
|
||||
*/
|
||||
@Operation(summary = "查询订单状态", description = "根据订单 id 或 orderNo 查询订单状态")
|
||||
@PostMapping("/queryOrderStatus")
|
||||
public CommonResult<OrderStatusDto> queryOrderStatus(@RequestBody OrderStatusQueryDto queryDto) {
|
||||
try {
|
||||
// 检查参数是否有效
|
||||
if (Objects.isNull(queryDto.getOrderId()) && Objects.isNull(queryDto.getOrderNo())) {
|
||||
return CommonResult.failed("请提供 orderId 或 orderNo 参数");
|
||||
}
|
||||
|
||||
PaymentOrder order = null;
|
||||
|
||||
// 根据订单 id 查询
|
||||
if (queryDto.getOrderId() != null) {
|
||||
order = paymentOrderService.queryById(queryDto.getOrderId());
|
||||
}
|
||||
// 根据订单号查询
|
||||
else if (queryDto.getOrderNo() != null && !queryDto.getOrderNo().trim().isEmpty()) {
|
||||
order = paymentOrderService.queryByOrderNo(queryDto.getOrderNo());
|
||||
}
|
||||
|
||||
if (order == null) {
|
||||
return CommonResult.failed("订单不存在");
|
||||
}
|
||||
|
||||
// 构建响应数据
|
||||
OrderStatusDto result = OrderStatusDto.fromPaymentOrder(order);
|
||||
|
||||
return CommonResult.success(result);
|
||||
} catch (Exception e) {
|
||||
logger.error("查询订单状态失败", e);
|
||||
return CommonResult.failed("系统繁忙,请稍后重试");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
package com.kexue.skills.entity.dto;
|
||||
|
||||
import com.kexue.skills.entity.PaymentOrder;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 订单状态DTO
|
||||
*/
|
||||
@Data
|
||||
public class OrderStatusDto {
|
||||
|
||||
@Schema(description = "订单ID")
|
||||
private Long orderId;
|
||||
|
||||
@Schema(description = "订单号")
|
||||
private String orderNo;
|
||||
|
||||
@Schema(description = "状态码")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "状态文本")
|
||||
private String statusText;
|
||||
|
||||
@Schema(description = "支付金额")
|
||||
private BigDecimal amount;
|
||||
|
||||
@Schema(description = "支付方式:1.微信 2.支付宝")
|
||||
private Integer payType;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private Date createTime;
|
||||
|
||||
@Schema(description = "支付时间")
|
||||
private Date payTime;
|
||||
|
||||
@Schema(description = "渠道订单号")
|
||||
private String channelOrderNo;
|
||||
|
||||
/**
|
||||
* 从PaymentOrder构建OrderStatusDto
|
||||
* @param order 支付订单
|
||||
* @return 订单状态DTO
|
||||
*/
|
||||
public static OrderStatusDto fromPaymentOrder(PaymentOrder order) {
|
||||
OrderStatusDto dto = new OrderStatusDto();
|
||||
dto.setOrderId(order.getOrderId());
|
||||
dto.setOrderNo(order.getOrderNo());
|
||||
dto.setStatus(order.getStatus());
|
||||
dto.setStatusText(getStatusText(order.getStatus()));
|
||||
dto.setAmount(order.getAmount());
|
||||
dto.setPayType(order.getPayType());
|
||||
dto.setCreateTime(order.getCreateTime());
|
||||
dto.setPayTime(order.getPayTime());
|
||||
dto.setChannelOrderNo(order.getChannelOrderNo());
|
||||
return dto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取状态文本
|
||||
* @param status 状态码
|
||||
* @return 状态文本
|
||||
*/
|
||||
private static String getStatusText(Integer status) {
|
||||
switch (status) {
|
||||
case 1: return "待支付";
|
||||
case 2: return "已支付";
|
||||
case 3: return "支付失败";
|
||||
case 4: return "已取消";
|
||||
case 5: return "已退款";
|
||||
default: return "未知状态";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.kexue.skills.entity.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 订单状态查询DTO
|
||||
*/
|
||||
@Data
|
||||
public class OrderStatusQueryDto {
|
||||
|
||||
@Schema(description = "订单ID")
|
||||
private Long orderId;
|
||||
|
||||
@Schema(description = "订单号")
|
||||
private String orderNo;
|
||||
|
||||
}
|
||||
|
|
@ -47,6 +47,8 @@ public interface PaymentOrderMapper {
|
|||
*/
|
||||
PaymentOrder queryByOrderNo(String orderNo);
|
||||
|
||||
PaymentOrder queryByChannelOrderNo(String channelOrderNo);
|
||||
|
||||
/**
|
||||
* 新增数据
|
||||
*
|
||||
|
|
|
|||
|
|
@ -106,9 +106,9 @@ public interface AccountService extends BaseService {
|
|||
/**
|
||||
* 减少账户余额(token消费转换)
|
||||
* @param tokenConsumptionDto token消费
|
||||
*
|
||||
* @return 消耗的金额
|
||||
*/
|
||||
int reduceBalanceWithToken(TokenConsumptionDto tokenConsumptionDto);
|
||||
BigDecimal reduceBalanceWithToken(TokenConsumptionDto tokenConsumptionDto);
|
||||
|
||||
/**
|
||||
* 增加账户余额(签到奖励token转换)
|
||||
|
|
|
|||
|
|
@ -40,11 +40,19 @@ public interface PaymentOrderService extends BaseService {
|
|||
/**
|
||||
* 通过订单号查询单条数据
|
||||
*
|
||||
* @param orderNo 订单号
|
||||
* @param orderNo 微信或者支付宝订单号
|
||||
* @return 实例对象
|
||||
*/
|
||||
PaymentOrder queryByOrderNo(String orderNo);
|
||||
|
||||
/**
|
||||
* 通过渠道订单号查询单条数据
|
||||
*
|
||||
* @param ChannelOrderNo 渠道订单号
|
||||
* @return 订单信息
|
||||
*/
|
||||
PaymentOrder queryByChannelOrderNo(String ChannelOrderNo);
|
||||
|
||||
/**
|
||||
* 新增数据
|
||||
*
|
||||
|
|
|
|||
|
|
@ -182,11 +182,14 @@ public class AccountServiceImpl implements AccountService {
|
|||
|
||||
// 3. 更新账户余额
|
||||
if (isWithdrawable) {
|
||||
account.setWithdrawableBalance(account.getWithdrawableBalance().add(amount));
|
||||
BigDecimal withdrawableBalance = account.getWithdrawableBalance() == null ? BigDecimal.ZERO : account.getWithdrawableBalance();
|
||||
account.setWithdrawableBalance(withdrawableBalance.add(amount));
|
||||
} else {
|
||||
account.setNonWithdrawableBalance(account.getNonWithdrawableBalance().add(amount));
|
||||
BigDecimal nonWithdrawableBalance = account.getNonWithdrawableBalance() == null ? BigDecimal.ZERO : account.getNonWithdrawableBalance();
|
||||
account.setNonWithdrawableBalance(nonWithdrawableBalance.add(amount));
|
||||
}
|
||||
account.setBalance(account.getBalance().add(amount));
|
||||
BigDecimal balance = account.getBalance() == null ? BigDecimal.ZERO : account.getBalance();
|
||||
account.setBalance(balance.add(amount));
|
||||
account.setUpdateTime(new Date());
|
||||
this.update(account);
|
||||
return 1;
|
||||
|
|
@ -226,7 +229,8 @@ public class AccountServiceImpl implements AccountService {
|
|||
Assert.notNull(account, "账户不存在");
|
||||
|
||||
// 2. 检查余额是否足够
|
||||
Assert.isTrue(account.getBalance().compareTo(amount) >= 0, "账户余额不足");
|
||||
BigDecimal balance = account.getBalance() == null ? BigDecimal.ZERO : account.getBalance();
|
||||
Assert.isTrue(balance.compareTo(amount) >= 0, "账户余额不足");
|
||||
|
||||
// 3. 保存交易记录
|
||||
AccountTransaction transaction = new AccountTransaction();
|
||||
|
|
@ -251,10 +255,10 @@ public class AccountServiceImpl implements AccountService {
|
|||
|
||||
/**
|
||||
* 减少账户余额(token消费转换)
|
||||
* @return 影响行数
|
||||
* @return 消耗的金额
|
||||
*/
|
||||
@Override
|
||||
public int reduceBalanceWithToken(TokenConsumptionDto dto) {
|
||||
public BigDecimal reduceBalanceWithToken(TokenConsumptionDto dto) {
|
||||
validate(dto);
|
||||
// 1. 查询账户信息
|
||||
Long userId = null ;//根据会话ID查询用户ID
|
||||
|
|
@ -289,7 +293,8 @@ public class AccountServiceImpl implements AccountService {
|
|||
BigDecimal amount = BigDecimal.valueOf(totalFee).divide(BigDecimal.valueOf(100));
|
||||
|
||||
// 4. 检查余额是否足够
|
||||
Assert.isTrue(account.getBalance().compareTo(amount) >= 0, "账户余额不足");
|
||||
BigDecimal balance = account.getBalance() == null ? BigDecimal.ZERO : account.getBalance();
|
||||
Assert.isTrue(balance.compareTo(amount) >= 0, "账户余额不足");
|
||||
|
||||
// 5. 保存交易记录
|
||||
AccountTransaction transaction = new AccountTransaction();
|
||||
|
|
@ -312,13 +317,13 @@ public class AccountServiceImpl implements AccountService {
|
|||
this.accountTransactionMapper.insert(transaction);
|
||||
|
||||
// 6. 更新账户余额
|
||||
return this.accountMapper.updateBalance(userId, amount, 2);
|
||||
this.accountMapper.updateBalance(userId, amount, 2);
|
||||
return amount;
|
||||
}
|
||||
|
||||
void validate(TokenConsumptionDto dto) {
|
||||
Assert.notNull(dto, "参数不能为空");
|
||||
Assert.notNull(dto.getSessionId(), "会话ID不能为空");
|
||||
Assert.notNull(dto.getUserId(), "用户ID不能为空");
|
||||
Assert.notNull(dto.getQuestion(), "问题不能为空");
|
||||
Assert.notNull(dto.getModelName(), "模型名称不能为空");
|
||||
Assert.notNull(dto.getQuestion(), "问题不能为空");
|
||||
|
|
@ -371,8 +376,10 @@ public class AccountServiceImpl implements AccountService {
|
|||
this.accountTransactionMapper.insert(transaction);
|
||||
|
||||
// 3. 更新账户余额(签到奖励不可提现)
|
||||
account.setNonWithdrawableBalance(account.getNonWithdrawableBalance().add(amount));
|
||||
account.setBalance(account.getBalance().add(amount));
|
||||
BigDecimal nonWithdrawableBalance = account.getNonWithdrawableBalance() == null ? BigDecimal.ZERO : account.getNonWithdrawableBalance();
|
||||
account.setNonWithdrawableBalance(nonWithdrawableBalance.add(amount));
|
||||
BigDecimal balance = account.getBalance() == null ? BigDecimal.ZERO : account.getBalance();
|
||||
account.setBalance(balance.add(amount));
|
||||
account.setUpdateTime(new Date());
|
||||
this.update(account);
|
||||
return 1;
|
||||
|
|
@ -427,6 +434,9 @@ public class AccountServiceImpl implements AccountService {
|
|||
account.setNonWithdrawableBalance(BigDecimal.ZERO);
|
||||
}
|
||||
account.setNonWithdrawableBalance(account.getNonWithdrawableBalance().add(amount));
|
||||
if (account.getBalance() == null){
|
||||
account.setBalance(BigDecimal.ZERO);
|
||||
}
|
||||
account.setBalance(account.getBalance().add(amount));
|
||||
account.setUpdateTime(new Date());
|
||||
this.update(account);
|
||||
|
|
|
|||
|
|
@ -122,22 +122,24 @@ public class CmsContentServiceImpl implements CmsContentService {
|
|||
// 查询包含这些标签的所有 skill
|
||||
List<CmsContent> taggedContents = this.cmsContentMapper.getPageList(tagQueryDto);
|
||||
|
||||
// 过滤掉已返回的内容
|
||||
// List<CmsContent> relatedContents = taggedContents.stream()
|
||||
// .filter(content -> !existingIds.contains(content.getContentId()))
|
||||
// .collect(Collectors.toList());
|
||||
|
||||
//如果 tag 传参,则根据 tag 再次过滤
|
||||
if (queryDto.getTagId() != null) {
|
||||
taggedContents = taggedContents.stream().filter(content -> content.getTags().contains(queryDto.getTagId().toString())).toList() ;
|
||||
}
|
||||
|
||||
List<CmsContent> finalList = new ArrayList<>(taggedContents);
|
||||
// 过滤掉已返回的内容,避免重复
|
||||
List<CmsContent> finalList = taggedContents.stream()
|
||||
.filter(content -> !existingIds.contains(content.getContentId()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 如果有相关 skill,添加到结果中
|
||||
if (!finalList.isEmpty()) {
|
||||
// 按 sort 和 create_time 排序
|
||||
finalList.sort((a, b) -> {
|
||||
// 将外层查询结果和内层查询结果合并
|
||||
List<CmsContent> allContents = new ArrayList<>(list);
|
||||
allContents.addAll(finalList);
|
||||
|
||||
// 按 view_count 和 create_time 排序
|
||||
allContents.sort((a, b) -> {
|
||||
int sortCompare = Integer.compare(
|
||||
a.getViewCount() != null ? a.getViewCount() : 0,
|
||||
b.getViewCount() != null ? b.getViewCount() : 0);
|
||||
|
|
@ -151,8 +153,8 @@ public class CmsContentServiceImpl implements CmsContentService {
|
|||
});
|
||||
|
||||
// 计算需要添加的数量,确保总数量不超过 pageSize
|
||||
PageInfo<CmsContent> newPageInfo = new PageInfo<>(memoryPagination(finalList, queryDto.getPageNum(), queryDto.getPageSize()));
|
||||
newPageInfo.setTotal(finalList.size());
|
||||
PageInfo<CmsContent> newPageInfo = new PageInfo<>(memoryPagination(allContents, queryDto.getPageNum(), queryDto.getPageSize()));
|
||||
newPageInfo.setTotal(allContents.size());
|
||||
pageInfo = newPageInfo;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.kexue.skills.config.PaymentConfig;
|
|||
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 org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
|
@ -59,6 +60,9 @@ public class PayServiceImpl implements PayService {
|
|||
@Resource
|
||||
private PaymentOrderService paymentOrderService;
|
||||
|
||||
@Resource
|
||||
private AccountService accountService;
|
||||
|
||||
/**
|
||||
* 生成随机字符串
|
||||
* @return 随机字符串
|
||||
|
|
@ -492,6 +496,24 @@ public class PayServiceImpl implements PayService {
|
|||
PaymentOrder order = paymentOrderService.queryByOrderNo(orderNo);
|
||||
if (order != null) {
|
||||
paymentOrderService.updateStatus(order.getOrderId(), 2, transactionId);
|
||||
|
||||
// 更新账户余额并添加交易记录
|
||||
try {
|
||||
// 增加账户余额(充值)
|
||||
accountService.addBalance(
|
||||
order.getUserId(),
|
||||
order.getAmount(),
|
||||
true, // 可提现
|
||||
transactionId,
|
||||
order.getOrderId(),
|
||||
"recharge",
|
||||
"微信支付充值"
|
||||
);
|
||||
logger.info("微信支付回调:更新账户余额成功,userId={}, amount={}", order.getUserId(), order.getAmount());
|
||||
} catch (Exception e) {
|
||||
logger.error("微信支付回调:更新账户余额失败", e);
|
||||
// 继续处理,不影响回调响应
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -616,6 +638,24 @@ public class PayServiceImpl implements PayService {
|
|||
PaymentOrder order = paymentOrderService.queryByOrderNo(orderNo);
|
||||
if (order != null) {
|
||||
paymentOrderService.updateStatus(order.getOrderId(), 2, transactionId);
|
||||
|
||||
// 更新账户余额并添加交易记录
|
||||
try {
|
||||
// 增加账户余额(充值)
|
||||
accountService.addBalance(
|
||||
order.getUserId(),
|
||||
order.getAmount(),
|
||||
true, // 可提现
|
||||
transactionId,
|
||||
order.getOrderId(),
|
||||
"recharge",
|
||||
"支付宝支付充值"
|
||||
);
|
||||
logger.info("支付宝支付回调:更新账户余额成功,userId={}, amount={}", order.getUserId(), order.getAmount());
|
||||
} catch (Exception e) {
|
||||
logger.error("支付宝支付回调:更新账户余额失败", e);
|
||||
// 继续处理,不影响回调响应
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -86,6 +86,17 @@ public class PaymentOrderServiceImpl implements PaymentOrderService {
|
|||
return this.paymentOrderMapper.queryByOrderNo(orderNo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过订单号查询单条数据
|
||||
*
|
||||
* @param channelOrderNo 订单号
|
||||
* @return 实例对象
|
||||
*/
|
||||
@Override
|
||||
public PaymentOrder queryByChannelOrderNo(String channelOrderNo) {
|
||||
return this.paymentOrderMapper.queryByChannelOrderNo(channelOrderNo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增数据
|
||||
*
|
||||
|
|
|
|||
|
|
@ -230,11 +230,12 @@ public class SkillGenServiceImpl implements SkillGenService {
|
|||
}
|
||||
}
|
||||
String systemContent = """
|
||||
你是AI技能包设计专家,仅输出【纯YAML文本】,不包含任何多余文字(无解释、无注释、无引言、无结尾)。
|
||||
你是AI技能包设计专家,仅输出【完整的纯YAML文本】,输出内容是完整可解析的技能YAML描述文件,绝非片段,不包含任何多余文字(无解释、无注释、无引言、无结尾)。
|
||||
|
||||
### 一、YAML顶层强制规则(仅一个节点:package)
|
||||
1. 顶层只能有 `package` 一个节点,所有信息(名称、版本、目录结构等)均嵌套在 `package` 下
|
||||
2. `package` 节点必含子字段:name、version、description、author、created、tags、structure(缺一不可)
|
||||
1. 顶层只能有 package 一个节点,所有信息(名称、版本、目录结构等)均嵌套在 package 下
|
||||
2. package 节点必含子字段:name、version、description、author、created、tags、structure(缺一不可)
|
||||
3. 最终必须输出完整闭合的YAML结构,禁止输出残缺片段、部分节点
|
||||
|
||||
### 二、package子字段规范(固定格式)
|
||||
1. name:技能名称(与用户提供的Skill名称完全一致,不修改)
|
||||
|
|
@ -243,58 +244,70 @@ public class SkillGenServiceImpl implements SkillGenService {
|
|||
4. author:固定为 "AI技能生成助手"
|
||||
5. created:格式为 "YYYY-MM-DD"(使用当前日期,如2026-04-01)
|
||||
6. tags:数组格式,值为用户提供的Skill标签(中文,自动去重,逗号后加空格)
|
||||
7. structure:技能包目录树根节点(必为directory类型,统一命名"skills")
|
||||
7. structure:技能包目录树,根目录固定为 /,structure下直接编写根目录的一级子文件/子文件夹,禁止重复描述根目录本身
|
||||
|
||||
### 三、structure目录树规则(仅Python脚本,无其他语言)
|
||||
#### 1. 基础必含文件(所有技能通用)
|
||||
- /skills(根目录,type: directory,path: /skills,format: dir,description: 技能包根目录)
|
||||
- /skills/skills.md(type: file,path: /skills/skills.md,format: markdown,description: 技能说明文档)
|
||||
- 根目录 / 下必生成:skills.md 文件,归属父路径 /,文件必须保留后缀名 .md
|
||||
|
||||
#### 2. Python脚本目录/文件判断逻辑
|
||||
- 若用户提供的“Skill描述”“Skill摘要”中**包含“脚本”“代码”“执行”“运行”“处理”“计算”** 等需执行逻辑的关键词:
|
||||
1. 必须新增 /skills/scripts 目录(type: directory,path: /skills/scripts,format: dir,description: Python执行脚本目录)
|
||||
2. 必须在该目录下生成 /skills/scripts/main.py(type: file,format: python,description: 技能核心Python脚本)
|
||||
- 若用户需求中**无任何执行逻辑相关描述**(如纯文档、纯说明类技能):不生成 /skills/scripts 目录,避免冗余
|
||||
#### 2. structure核心编写规则
|
||||
1. structure节点下不写根目录/的描述,直接从一级子文件/子文件夹开始编写
|
||||
2. 【path路径强制规则】path只写父级目录路径,不拼接文件名称/目录名称
|
||||
- 目录自身名称使用 name 字段体现,目录一律无后缀名
|
||||
- 文件自身名称使用 name 字段体现,文件必须保留标准后缀名
|
||||
- 示例:scripts目录在根目录下 → path: /,name: scripts
|
||||
- 示例:skills.md在根目录下 → path: /,name: skills.md
|
||||
- 示例:main.py在scripts目录下 → path: /scripts,name: main.py
|
||||
3. 所有directory/file节点均为根目录/的直接/间接子节点
|
||||
|
||||
#### 3. 节点必含字段
|
||||
- directory类型:name、type、path(绝对路径,Unix风格 `/`,如 /skills/scripts)、format(固定"dir")、description、children(空目录写 `children: []`)
|
||||
- file类型:name、type、path(绝对路径)、format(仅markdown/python两种)、description、content(非空,有实际可用内容)
|
||||
#### 3. Python脚本目录/文件判断逻辑
|
||||
- 若用户提供的“Skill描述”“Skill摘要”中包含“脚本”“代码”“执行”“运行”“处理”“计算”等需执行逻辑的关键词:
|
||||
1. 必须新增 scripts 目录,该目录为文件夹,无任何后缀名,path: /,name: scripts
|
||||
2. 必须在该目录下固定生成 main.py 文件,文件必须带 .py 后缀,不允许省略或修改文件名
|
||||
- 若用户需求中无任何执行逻辑相关描述(如纯文档、纯说明类技能):不生成 scripts 目录,避免冗余
|
||||
|
||||
#### 4. 节点必含字段
|
||||
- directory类型:name、type、path(仅父级路径)、format(固定"dir")、description、children(空目录写 children: [])
|
||||
- file类型:name、type、path(仅父级路径)、format(仅markdown/python两种)、description、content(非空,有实际可用内容)
|
||||
|
||||
### 四、文件content内容规范(Python脚本必实用)
|
||||
#### 1. /skills/skills.md(结构固定)
|
||||
#### 1. 根目录下 skills.md
|
||||
- # 技能名称(不加多余符号,居中可加空格但不强制)
|
||||
- ## 技能描述(整合用户“描述+摘要”,补充逻辑连贯性)
|
||||
- ## 标签(格式:`- 标签1\n- 标签2`,对应tags数组内容)
|
||||
- ## 使用说明(分点写:适用场景、操作步骤,需脚本则写“运行main.py脚本”,无需则写“直接参考文档使用”)
|
||||
- ## 标签(格式:- 标签1\n- 标签2)
|
||||
- ## 使用说明(分点写:适用场景、操作步骤,需脚本则写“运行scripts/main.py脚本”,无需则写“直接参考文档使用”)
|
||||
- ## 目录结构(用代码块 ``` 列出所有文件/目录路径)
|
||||
|
||||
#### 2. /skills/scripts/main.py(Python脚本必含)
|
||||
- 必含依赖导入(如 `import pandas as pd`,无依赖则不写)
|
||||
- 必含入口函数 `def execute(params: dict) -> dict:`(参数为dict,返回dict结果)
|
||||
#### 2. scripts目录下 main.py
|
||||
- 必含依赖导入(如 import pandas as pd,无依赖则不写)
|
||||
- 必含入口函数 def execute(params: dict) -> dict:(参数为dict,返回dict结果)
|
||||
- 函数内必含:
|
||||
1. 参数校验(判断必填键是否存在,缺失返回错误)
|
||||
2. 核心逻辑(匹配技能需求,如数据处理、文本分析)
|
||||
3. 结果返回(成功:`{"status": "success", "data": 结果}`;失败:`{"status": "fail", "error": 信息}`)
|
||||
- 必含注释:函数说明、参数/返回值说明、示例调用(`if __name__ == "__main__":` 块)
|
||||
3. 结果返回(成功:{"status": "success", "data": 结果};失败:{"status": "fail", "error": 信息})
|
||||
- 必含注释:函数说明、参数/返回值说明、示例调用(if __name__ == "__main__": 块)
|
||||
- 禁止空函数、语法错误,确保复制后可直接运行
|
||||
|
||||
### 五、YAML语法死规定(100%无解析错误)
|
||||
1. 缩进:统一2个空格(禁止Tab,禁止1/3/4空格,嵌套层级严格对齐)
|
||||
- package 下子字段缩进2空格
|
||||
- structure 下目录/文件节点缩进4空格(package→structure→children,每层+2空格)
|
||||
2. 路径:全部为绝对路径,Unix风格 `/`(如 /skills/scripts/main.py),禁止 `\\` 或 `./`
|
||||
2. 路径:全部遵循path只写父目录规则,Unix风格 /,禁止 \\ 或 ./
|
||||
3. 字符串:特殊字符(:、#、空格)无需转义,直接书写
|
||||
4. 数组:tags格式严格为 `tags: [标签1, 标签2]`(逗号后加空格,无多余逗号)
|
||||
5. content:多行内容用 `|` 开头,内容行首顶格,内部遵循对应格式缩进(Python用4空格)
|
||||
4. 数组:tags格式严格为 tags: [标签1, 标签2](逗号后加空格,无多余逗号)
|
||||
5. content:多行内容用 | 开头,内容行首顶格,内部遵循对应格式缩进(Python用4空格)
|
||||
|
||||
### 六、错误规避红线(绝对不能触碰)
|
||||
1. 禁止顶层出现除 `package` 外的任何节点(如name、version不能直接在顶层)
|
||||
2. 禁止在YAML前后加任何多余文字(如“生成完毕”“---”分隔符)
|
||||
3. 禁止生成非Python脚本(仅支持main.py,无.js/.sh文件)
|
||||
4. 禁止字段缺失(如package必含structure,file必含content)
|
||||
5. 禁止目录结构错误(脚本必须在/skills/scripts下)
|
||||
1. 禁止顶层出现除 package 外的任何节点
|
||||
2. 禁止在YAML前后加任何多余文字
|
||||
3. 禁止生成非Python脚本,禁止生成无后缀的脚本文件
|
||||
4. 禁止字段缺失
|
||||
5. 禁止输出YAML片段,必须输出完整可解析文件
|
||||
6. structure禁止描述根目录/,直接从一级子节点开始
|
||||
7. 严禁path中携带文件/目录名称,必须只写父级路径
|
||||
8. 严禁将scripts目录错误添加后缀名,严禁修改Python脚本名为非main.py
|
||||
|
||||
最终输出仅纯YAML,直接可复制存储、解析使用,无任何冗余或格式问题!
|
||||
最终输出仅完整纯YAML,直接可复制存储、解析使用,无任何冗余或格式问题!
|
||||
""";
|
||||
|
||||
String userContent = """
|
||||
|
|
|
|||
|
|
@ -111,6 +111,13 @@
|
|||
</if>
|
||||
</where>
|
||||
</select>
|
||||
<select id="queryByChannelOrderNo" resultType="com.kexue.skills.entity.PaymentOrder">
|
||||
select
|
||||
order_id, order_no, user_id, user_name, amount, pay_type, status, channel_order_no,
|
||||
code_url, qr_code, business_id, business_type, remark, create_time, update_time, create_by, update_by, delete_flag
|
||||
from payment_order
|
||||
where channel_order_no = #{channelOrderNo}
|
||||
</select>
|
||||
|
||||
<!--新增数据-->
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="orderId">
|
||||
|
|
|
|||
Loading…
Reference in New Issue