feat(account): 实现账户功能完善与支付集成

- 集成Sa-Token框架实现用户身份认证和权限管理
- 修改账户查询接口,基于当前登录用户自动获取用户ID
- 优化账户余额操作,增加空值检查和BigDecimal精度处理
- 添加支付订单状态查询功能,支持按订单ID或订单号查询
- 实现支付回调处理,在微信和支付宝支付成功后自动更新账户余额
- 完善内容购买流程,基于当前登录用户进行权限验证
- 优化CMS内容推荐算法,改进标签匹配和去重逻辑
- 更新AI技能生成服务,优化YAML输出格式和目录结构规范
This commit is contained in:
wangzhiwei 2026-04-02 18:14:29 +08:00
parent f2b8a735f2
commit bad416aeab
14 changed files with 314 additions and 74 deletions

View File

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

View File

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

View File

@ -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("系统繁忙,请稍后重试");
}
}
}

View File

@ -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 "未知状态";
}
}
}

View File

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

View File

@ -47,6 +47,8 @@ public interface PaymentOrderMapper {
*/
PaymentOrder queryByOrderNo(String orderNo);
PaymentOrder queryByChannelOrderNo(String channelOrderNo);
/**
* 新增数据
*

View File

@ -106,9 +106,9 @@ public interface AccountService extends BaseService {
/**
* 减少账户余额token消费转换
* @param tokenConsumptionDto token消费
*
* @return 消耗的金额
*/
int reduceBalanceWithToken(TokenConsumptionDto tokenConsumptionDto);
BigDecimal reduceBalanceWithToken(TokenConsumptionDto tokenConsumptionDto);
/**
* 增加账户余额签到奖励token转换

View File

@ -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);
/**
* 新增数据
*

View File

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

View File

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

View File

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

View File

@ -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);
}
/**
* 新增数据
*

View File

@ -230,11 +230,12 @@ public class SkillGenServiceImpl implements SkillGenService {
}
}
String systemContent = """
你是AI技能包设计专家仅输出纯YAML文本不包含任何多余文字无解释无注释无引言无结尾
你是AI技能包设计专家仅输出完整的纯YAML文本输出内容是完整可解析的技能YAML描述文件绝非片段不包含任何多余文字无解释无注释无引言无结尾
### YAML顶层强制规则仅一个节点package
1. 顶层只能有 `package` 一个节点所有信息名称版本目录结构等均嵌套在 `package`
2. `package` 节点必含子字段nameversiondescriptionauthorcreatedtagsstructure缺一不可
1. 顶层只能有 package 一个节点所有信息名称版本目录结构等均嵌套在 package
2. package 节点必含子字段nameversiondescriptionauthorcreatedtagsstructure缺一不可
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: directorypath: /skillsformat: dirdescription: 技能包根目录
- /skills/skills.mdtype: filepath: /skills/skills.mdformat: markdowndescription: 技能说明文档
- 根目录 / 下必生成skills.md 文件归属父路径 /文件必须保留后缀名 .md
#### 2. Python脚本目录/文件判断逻辑
- 若用户提供的Skill描述Skill摘要**包含脚本代码执行运行处理计算** 等需执行逻辑的关键词
1. 必须新增 /skills/scripts 目录type: directorypath: /skills/scriptsformat: dirdescription: Python执行脚本目录
2. 必须在该目录下生成 /skills/scripts/main.pytype: fileformat: pythondescription: 技能核心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: /scriptsname: main.py
3. 所有directory/file节点均为根目录/的直接/间接子节点
#### 3. 节点必含字段
- directory类型nametypepath绝对路径Unix风格 `/` /skills/scriptsformat固定"dir"descriptionchildren空目录写 `children: []`
- file类型nametypepath绝对路径format仅markdown/python两种descriptioncontent非空有实际可用内容
#### 3. Python脚本目录/文件判断逻辑
- 若用户提供的Skill描述Skill摘要中包含脚本代码执行运行处理计算等需执行逻辑的关键词
1. 必须新增 scripts 目录该目录为文件夹无任何后缀名path: /name: scripts
2. 必须在该目录下固定生成 main.py 文件文件必须带 .py 后缀不允许省略或修改文件名
- 若用户需求中无任何执行逻辑相关描述如纯文档纯说明类技能不生成 scripts 目录避免冗余
#### 4. 节点必含字段
- directory类型nametypepath仅父级路径format固定"dir"descriptionchildren空目录写 children: []
- file类型nametypepath仅父级路径format仅markdown/python两种descriptioncontent非空有实际可用内容
### 文件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.pyPython脚本必含
- 必含依赖导入 `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空格packagestructurechildren每层+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` 外的任何节点如nameversion不能直接在顶层
2. 禁止在YAML前后加任何多余文字生成完毕---分隔符
3. 禁止生成非Python脚本仅支持main.py.js/.sh文件
4. 禁止字段缺失如package必含structurefile必含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 = """

View File

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