diff --git a/PAYMENT_GUIDE.md b/PAYMENT_GUIDE.md
new file mode 100644
index 0000000..c004fcb
--- /dev/null
+++ b/PAYMENT_GUIDE.md
@@ -0,0 +1,603 @@
+# 支付功能使用指南
+
+## 📋 概述
+
+本项目已完整接入**微信支付**和**支付宝支付**,支持内容购买、账户充值等支付场景。
+
+---
+
+## 一、配置信息
+
+### 1.1 微信支付配置
+
+位置:`src/main/resources/application-dev.yml` 和 `src/main/resources/application-prod.yml`
+
+```yaml
+payment:
+ wechat:
+ appId: wx7d13d99de5be3bfa # 微信应用 ID
+ mchId: 1673321732 # 商户号
+ mchKey: UDuZXDcmy5Eb6o0nTNZhu6ek4DDh4K8B # 商户密钥
+ mchSerialNo: 5EFC47D3AA59BFD1AAE548F96B5E19E1C60F067D # 商户证书序列号
+ privateKeyPath: apiclient_key.pem # 商户私钥文件路径
+ domain: https://api.mch.weixin.qq.com # 微信服务器地址
+ notifyUrl: http://127.0.0.1:19001/api/pay/wx/notify # 支付回调地址
+ returnUrl: http://127.0.0.1:19001/api/pay/success # 支付成功跳转地址
+```
+
+### 1.2 支付宝支付配置
+
+```yaml
+payment:
+ alipay:
+ appId: 2021004138642603 # 支付宝应用 ID
+ publicKey: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnakP04nUmsoFoveIvOhbLqkA1xQuYtvkrqq2AVvTsbtqpsEOTm9G095e2rBYLp89oDcf6L6BhtJPwdrhnA+qifUyVmACI9sprrsGeRYQgndK7y4c6spQcSnsnakSxlIp22j7pvBXNAZuqud2hQV+TOLKEUh1W3izTgMj/Ejoh3ZsCjgDRtTVgaytzSdHYrhNku+pIrl15/xVGJED99RYXkR8GHawxuK+vWVmxU0tiTCwTsqLz43v6TtCZ+/UfLL/luwp9B4ZvB+0qon82LILYr6oxs10kE2IAvryuDToAc1s/v/36jgt+7DXwqzfUDksHhVLHdJHChyc4ax5HmMsBwIDAQAB" # 支付宝公钥
+ privateKey: "" # 商户私钥(需配置)
+ notifyUrl: http://127.0.0.1:19001/api/pay/alipay/trade/notify # 支付回调地址
+ returnUrl: https://shuziren.xueai.art/alipay-success # 支付成功跳转地址
+ signType: RSA2 # 签名类型
+ charset: UTF-8 # 字符编码
+ gatewayUrl: https://openapi.alipay.com/gateway.do # 支付宝网关
+```
+
+---
+
+## 二、API 接口说明
+
+### 2.1 创建微信支付订单
+
+**接口地址:** `POST /api/pay/wx/create`
+
+**请求参数:**
+
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| orderNo | String | 否 | 订单号(不传则自动生成) |
+| userId | Long | 是 | 用户 ID |
+| userName | String | 是 | 用户名 |
+| amount | BigDecimal | 是 | 支付金额(单位:元) |
+| productName | String | 是 | 商品名称 |
+| productDesc | String | 否 | 商品描述 |
+| businessId | Long | 是 | 关联业务 ID |
+| businessType | String | 是 | 业务类型:recharge(充值)/purchase_content(购买内容) |
+
+**请求示例:**
+
+```bash
+curl -X POST http://localhost:19001/api/pay/wx/create \
+ -H "Content-Type: application/json" \
+ -d '{
+ "userId": 123,
+ "userName": "张三",
+ "amount": 0.01,
+ "productName": "测试商品",
+ "productDesc": "商品描述",
+ "businessId": 456,
+ "businessType": "purchase_content"
+ }'
+```
+
+**响应示例:**
+
+```json
+{
+ "code": 200,
+ "message": "success",
+ "data": {
+ "code_url": "weixin://wxpay/bizpayurl?pr=xxx",
+ "order_no": "ORDER_20260331_001"
+ }
+}
+```
+
+**字段说明:**
+- `code_url`: 微信支付二维码链接,前端可使用此链接生成二维码
+- `order_no`: 支付订单号,用于后续查询订单状态
+
+---
+
+### 2.2 创建支付宝支付订单
+
+**接口地址:** `POST /api/pay/alipay/create`
+
+**请求参数:** 与微信支付相同
+
+**请求示例:**
+
+```bash
+curl -X POST http://localhost:19001/api/pay/alipay/create \
+ -H "Content-Type: application/json" \
+ -d '{
+ "userId": 123,
+ "userName": "张三",
+ "amount": 0.01,
+ "productName": "测试商品",
+ "businessId": 456,
+ "businessType": "purchase_content"
+ }'
+```
+
+**响应示例:**
+
+```json
+{
+ "code": 200,
+ "message": "success",
+ "data": "
"
+}
+```
+
+**使用说明:**
+- 返回的是 HTML 表单字符串
+- 前端将此外壳写入页面后会自动提交跳转到支付宝支付页面
+
+---
+
+### 2.3 支付回调接口(系统自动处理)
+
+#### 微信支付回调
+
+**接口地址:** `POST /api/pay/wx/notify`
+
+**说明:**
+- 由微信支付服务器自动调用
+- 处理支付结果并更新订单状态
+- 返回 XML 格式响应给微信服务器
+
+#### 支付宝支付回调
+
+**异步回调:** `POST /api/pay/alipay/trade/notify`
+
+**同步回调:** `GET /api/pay/alipay/trade/return`
+
+**说明:**
+- 异步回调由支付宝服务器自动调用
+- 同步回调用于用户支付完成后跳转回指定页面
+
+---
+
+## 三、使用场景示例
+
+### 3.1 场景 1:购买付费内容
+
+**业务流程:**
+
+1. 用户点击购买按钮
+2. 创建购买记录(待支付状态)
+3. 创建支付订单
+4. 用户扫码或跳转支付
+5. 支付成功后更新订单和购买记录状态
+
+**前端调用示例:**
+
+```javascript
+// 步骤 1:创建购买记录
+const purchaseResponse = await fetch('/api/content/purchase', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ userId: 123,
+ contentId: 456,
+ payType: 3 // 3=微信支付,4=支付宝支付
+ })
+});
+
+const purchaseResult = await purchaseResponse.json();
+
+// 步骤 2:创建支付订单
+const payResponse = await fetch('/api/pay/wx/create', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ userId: 123,
+ amount: 9.99,
+ productName: 'AI 技能教程',
+ productDesc: '高级 AI 技能培训内容',
+ businessId: 456,
+ businessType: 'purchase_content'
+ })
+});
+
+const payResult = await payResponse.json();
+
+if (payResult.code === 200) {
+ // 步骤 3:显示支付二维码
+ const codeUrl = payResult.data.code_url;
+ // 使用 QRCode 库生成二维码
+ new QRCode(document.getElementById('qrcode'), {
+ text: codeUrl,
+ width: 200,
+ height: 200
+ });
+
+ // 步骤 4:轮询查询订单状态
+ const checkOrderStatus = setInterval(async () => {
+ const statusResponse = await fetch(`/api/pay/order/query?orderNo=${payResult.data.order_no}`);
+ const statusResult = await statusResponse.json();
+
+ if (statusResult.data.status === 2) { // 2=已支付
+ clearInterval(checkOrderStatus);
+ alert('支付成功!');
+ window.location.reload();
+ }
+ }, 3000); // 每 3 秒查询一次
+
+ // 设置超时(15 分钟后停止查询)
+ setTimeout(() => {
+ clearInterval(checkOrderStatus);
+ alert('支付超时,请重新下单');
+ }, 900000);
+}
+```
+
+---
+
+### 3.2 场景 2:账户充值
+
+**业务流程:**
+
+```javascript
+// 创建充值订单
+const rechargeResponse = await fetch('/api/pay/wx/create', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ userId: 123,
+ amount: 100.00, // 充值 100 元
+ productName: '账户充值',
+ businessId: 123,
+ businessType: 'recharge' // 充值业务类型
+ })
+});
+
+const result = await rechargeResponse.json();
+if (result.code === 200) {
+ // 显示支付二维码
+ showQRCode(result.data.code_url);
+}
+```
+
+**说明:**
+- 支付成功后,系统会自动通过回调将充值金额添加到用户账户余额
+
+---
+
+### 3.3 场景 3:支付宝网页支付
+
+```javascript
+// 创建支付宝订单
+const aliPayResponse = await fetch('/api/pay/alipay/create', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ userId: 123,
+ amount: 0.01,
+ productName: '测试商品',
+ businessId: 456,
+ businessType: 'purchase_content'
+ })
+});
+
+const result = await aliPayResponse.json();
+if (result.code === 200) {
+ // 将返回的 HTML 表单写入页面
+ document.body.innerHTML = result.data;
+ // 表单会自动提交,跳转到支付宝支付页面
+}
+```
+
+---
+
+## 四、前端集成完整示例
+
+### 4.1 微信支付页面
+
+```html
+
+
+
+
+ 微信支付
+
+
+
+
+
+
微信支付
+
+
请使用微信扫码支付
+
等待支付...
+
+
+
+
+
+```
+
+### 4.2 支付宝支付页面
+
+```html
+
+
+
+
+ 支付宝支付
+
+
+
+
正在跳转到支付宝支付页面...
+
请稍候
+
+
+
+
+
+```
+
+---
+
+## 五、核心代码文件
+
+| 文件路径 | 说明 |
+|---------|------|
+| `src/main/java/com/kexue/skills/controller/PayController.java` | 支付接口控制器 |
+| `src/main/java/com/kexue/skills/service/PayService.java` | 支付服务接口 |
+| `src/main/java/com/kexue/skills/service/impl/PayServiceImpl.java` | 支付服务实现类 |
+| `src/main/java/com/kexue/skills/config/PaymentConfig.java` | 支付配置类 |
+| `src/main/java/com/kexue/skills/entity/PaymentOrder.java` | 支付订单实体类 |
+| `src/main/java/com/kexue/skills/service/PaymentOrderService.java` | 支付订单服务接口 |
+| `src/main/java/com/kexue/skills/service/impl/PaymentOrderServiceImpl.java` | 支付订单服务实现类 |
+
+---
+
+## 六、注意事项
+
+### 6.1 开发环境测试
+
+⚠️ **重要提示:** 本地开发环境下,微信和支付宝无法直接访问到你的 localhost 回调地址。
+
+**解决方案:**
+
+1. **使用内网穿透工具**(推荐)
+ ```bash
+ # 使用 ngrok
+ ngrok http 19001
+
+ # 将生成的域名配置到回调地址
+ # 例如:https://abc123.ngrok.io/api/pay/wx/notify
+ ```
+
+2. **部署到测试服务器**
+ - 直接部署到具有公网 IP 的服务器进行测试
+
+3. **修改配置文件**
+ ```yaml
+ payment:
+ wechat:
+ notifyUrl: https://your-domain.ngrok.io/api/pay/wx/notify
+ alipay:
+ notifyUrl: https://your-domain.ngrok.io/api/pay/alipay/trade/notify
+ ```
+
+### 6.2 金额单位
+
+- 前端传入金额单位:**元**
+- 微信支付内部转换:**分**(代码自动处理)
+- 建议最小金额:0.01 元
+
+### 6.3 业务类型说明
+
+| 业务类型 | 说明 | 支付成功后操作 |
+|---------|------|--------------|
+| `recharge` | 账户充值 | 增加用户账户余额 |
+| `purchase_content` | 购买内容 | 更新内容购买记录状态 |
+
+### 6.4 支付状态码
+
+| 状态码 | 说明 |
+|-------|------|
+| 1 | 待支付 |
+| 2 | 已支付 |
+| 3 | 支付失败 |
+| 4 | 已取消 |
+| 5 | 已退款 |
+
+### 6.5 安全建议
+
+1. **权限验证**:所有支付接口都应添加权限验证(已部分实现)
+2. **签名验证**:确保支付回调的签名验证正确
+3. **幂等性处理**:支付回调应支持重复通知(已实现)
+4. **日志记录**:完整的支付日志便于问题排查
+
+---
+
+## 七、常见问题
+
+### Q1: 如何查询订单状态?
+
+```java
+// 通过订单号查询
+PaymentOrder order = paymentOrderService.queryByOrderNo(orderNo);
+
+// 或通过主键查询
+PaymentOrder order = paymentOrderService.queryById(orderId);
+```
+
+### Q2: 如何处理支付回调失败?
+
+系统已实现幂等性处理,同一订单多次回调不会重复处理。如果回调失败,微信/支付宝会按一定频率重试。
+
+### Q3: 如何申请退款?
+
+当前版本暂未实现退款功能,如需退款,需要:
+1. 调用微信/支付宝退款 API
+2. 更新支付订单状态为已退款(5)
+3. 恢复用户余额或购买权限
+
+### Q4: 测试时使用真实资金吗?
+
+是的,对接的是正式环境。如需测试环境,需要:
+- 微信:申请沙箱环境
+- 支付宝:使用测试账号
+
+---
+
+## 八、技术支持
+
+如遇问题,请检查以下内容:
+
+1. ✅ 配置文件中的商户号、密钥是否正确
+2. ✅ 回调地址是否可被外网访问
+3. ✅ 商户私钥文件是否存在且路径正确
+4. ✅ 查看日志文件中的详细错误信息
+5. ✅ 确认微信/支付宝商户号状态正常
+
+---
+
+**文档版本:** v1.0
+**更新时间:** 2026-03-31
+**维护人员:** 系统开发团队
diff --git a/db/create_tables.sql b/db/create_tables.sql
index c309b66..3416c1a 100644
--- a/db/create_tables.sql
+++ b/db/create_tables.sql
@@ -12,7 +12,9 @@ CREATE TABLE `account` (
`account_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`user_name` varchar(50) DEFAULT NULL COMMENT '用户名',
- `balance` decimal(10,2) DEFAULT '0.00' COMMENT '账户余额',
+ `balance` decimal(10,2) DEFAULT '0.00' COMMENT '账户总余额',
+ `withdrawable_balance` decimal(10,2) DEFAULT '0.00' COMMENT '可提现余额',
+ `non_withdrawable_balance` decimal(10,2) DEFAULT '0.00' COMMENT '不可提现余额',
`frozen_amount` decimal(10,2) DEFAULT '0.00' COMMENT '冻结金额',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
@@ -21,13 +23,36 @@ CREATE TABLE `account` (
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='账户表,记录用户的账户信息';
+-- 18. 提现记录表
+DROP TABLE IF EXISTS `withdrawal_record`;
+CREATE TABLE `withdrawal_record` (
+ `record_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `user_id` bigint(20) NOT NULL COMMENT '用户ID',
+ `user_name` varchar(50) DEFAULT NULL COMMENT '用户名',
+ `withdrawal_amount` decimal(10,2) NOT NULL COMMENT '提现金额',
+ `fee_amount` decimal(10,2) NOT NULL COMMENT '手续费',
+ `actual_amount` decimal(10,2) NOT NULL COMMENT '实际到账金额',
+ `status` tinyint(1) NOT NULL COMMENT '提现状态:1.待处理 2.处理中 3.成功 4.失败',
+ `withdrawal_no` varchar(50) NOT NULL COMMENT '提现单号',
+ `bank_name` varchar(100) DEFAULT NULL COMMENT '银行名称',
+ `bank_account` varchar(100) DEFAULT NULL COMMENT '银行账号',
+ `bank_cardholder` varchar(50) DEFAULT NULL COMMENT '持卡人姓名',
+ `remark` varchar(255) DEFAULT NULL COMMENT '备注',
+ `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `delete_flag` tinyint(1) DEFAULT '0' COMMENT '是否删除 :0 未删除,1已删除',
+ PRIMARY KEY (`record_id`),
+ KEY `idx_user_id` (`user_id`),
+ KEY `idx_withdrawal_no` (`withdrawal_no`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='提现记录表,记录用户的提现记录';
+
-- 2. 账户流水表
DROP TABLE IF EXISTS `account_transaction`;
CREATE TABLE `account_transaction` (
`transaction_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`user_name` varchar(50) DEFAULT NULL COMMENT '用户名',
- `transaction_type` tinyint(1) NOT NULL COMMENT '交易类型:1.充值 2.提现 3.购买内容 4.退款 5.其他',
+ `transaction_type` tinyint(1) NOT NULL COMMENT '交易类型:1.充值 2.提现 3.购买内容 4.退款 5.签到奖励 6.赠送 7.其他',
`amount` decimal(10,2) NOT NULL COMMENT '交易金额',
`before_balance` decimal(10,2) NOT NULL COMMENT '交易前余额',
`after_balance` decimal(10,2) NOT NULL COMMENT '交易后余额',
@@ -37,6 +62,13 @@ CREATE TABLE `account_transaction` (
`business_id` bigint(20) DEFAULT NULL COMMENT '关联业务ID',
`business_type` varchar(50) DEFAULT NULL COMMENT '业务类型',
`remark` varchar(255) DEFAULT NULL COMMENT '交易备注',
+ `is_expense` tinyint(1) NOT NULL COMMENT '是否支出:1.是 0.否',
+ `input_token` int(11) DEFAULT NULL COMMENT '输入token',
+ `output_token` int(11) DEFAULT NULL COMMENT '输出token',
+ `total_tokens` int(11) DEFAULT NULL COMMENT '合计tokens',
+ `model_name` varchar(100) DEFAULT NULL COMMENT '处理的模型名称',
+ `question` text DEFAULT NULL COMMENT '对应回答的问题或需求',
+ `income_type` varchar(50) DEFAULT NULL COMMENT '收入类型:recharge(充值)、sign_in(签到奖励)',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`create_by` varchar(50) DEFAULT NULL COMMENT '创建人',
@@ -48,48 +80,7 @@ CREATE TABLE `account_transaction` (
KEY `idx_business_id` (`business_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='账户流水表,记录用户的账户交易记录';
--- 3. 积分账户表
-DROP TABLE IF EXISTS `points_account`;
-CREATE TABLE `points_account` (
- `account_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
- `user_id` bigint(20) NOT NULL COMMENT '用户ID',
- `user_name` varchar(50) DEFAULT NULL COMMENT '用户名',
- `total_points` int(11) DEFAULT '0' COMMENT '总积分',
- `available_points` int(11) DEFAULT '0' COMMENT '可用积分',
- `frozen_points` int(11) DEFAULT '0' COMMENT '冻结积分',
- `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
- `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
- `delete_flag` tinyint(1) DEFAULT '0' COMMENT '是否删除 :0 未删除,1已删除',
- PRIMARY KEY (`account_id`),
- KEY `idx_user_id` (`user_id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='积分账户表,记录用户的积分信息';
--- 4. 积分流水表
-DROP TABLE IF EXISTS `points_transaction`;
-CREATE TABLE `points_transaction` (
- `transaction_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
- `user_id` bigint(20) NOT NULL COMMENT '用户ID',
- `user_name` varchar(50) DEFAULT NULL COMMENT '用户名',
- `transaction_type` tinyint(1) NOT NULL COMMENT '积分变动类型:1.获取积分 2.消费积分 3.过期 4.其他',
- `points` int(11) NOT NULL COMMENT '变动积分',
- `before_points` int(11) NOT NULL COMMENT '变动前积分',
- `after_points` int(11) NOT NULL COMMENT '变动后积分',
- `status` tinyint(1) NOT NULL COMMENT '交易状态:1.成功 2.失败 3.处理中',
- `transaction_no` varchar(50) NOT NULL COMMENT '交易单号',
- `pay_type` tinyint(1) DEFAULT NULL COMMENT '支付方式:1.微信 2.支付宝 3.余额支付',
- `business_id` bigint(20) DEFAULT NULL COMMENT '关联业务ID',
- `business_type` varchar(50) DEFAULT NULL COMMENT '业务类型',
- `remark` varchar(255) DEFAULT NULL COMMENT '备注',
- `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
- `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
- `create_by` varchar(50) DEFAULT NULL COMMENT '创建人',
- `update_by` varchar(50) DEFAULT NULL COMMENT '更新人',
- `delete_flag` tinyint(1) DEFAULT '0' COMMENT '是否删除 :0 未删除,1已删除',
- PRIMARY KEY (`transaction_id`),
- KEY `idx_user_id` (`user_id`),
- KEY `idx_transaction_no` (`transaction_no`),
- KEY `idx_business_id` (`business_id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='积分流水表,记录用户的积分变动情况';
-- 5. 内容购买记录表
DROP TABLE IF EXISTS `content_purchase`;
@@ -308,4 +299,24 @@ CREATE TABLE `sys_log` (
KEY `idx_log_time` (`log_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='系统日志表,记录系统操作日志';
+
+
+-- 16. 大模型Token价格表
+DROP TABLE IF EXISTS `model_price`;
+CREATE TABLE `model_price` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `vendor` varchar(64) NOT NULL COMMENT '厂商',
+ `model_name` varchar(128) NOT NULL COMMENT '模型名称',
+ `input_price` decimal(10,4) NOT NULL COMMENT '输入价格:元/百万Token',
+ `output_price` decimal(10,4) NOT NULL COMMENT '输出价格:元/百万Token',
+ `input_per_cent` bigint NOT NULL COMMENT '1分钱可购买输入Token数',
+ `output_per_cent` bigint NOT NULL COMMENT '1分钱可购买输出Token数',
+ `unit` varchar(32) DEFAULT '百万Token' COMMENT '价格单位',
+ `remark` varchar(255) DEFAULT '' COMMENT '备注',
+ `created_time` datetime DEFAULT NULL,
+ `updated_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`),
+ KEY `idx_vendor` (`vendor`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='大模型Token价格表';
+
SET FOREIGN_KEY_CHECKS = 1;
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index a5089f3..ba83ffa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -182,10 +182,6 @@
org.slf4j
slf4j-api
-
- ch.qos.logback
- logback-classic
-
org.slf4j
log4j-over-slf4j
@@ -194,10 +190,6 @@
org.slf4j
jul-to-slf4j
-
- org.springframework.boot
- spring-boot-starter-logging
-
com.github.jsqlparser
jsqlparser
diff --git a/src/main/java/com/kexue/skills/aspect/AuthAspect.java b/src/main/java/com/kexue/skills/aspect/AuthAspect.java
index 8754658..9c024ad 100644
--- a/src/main/java/com/kexue/skills/aspect/AuthAspect.java
+++ b/src/main/java/com/kexue/skills/aspect/AuthAspect.java
@@ -15,7 +15,7 @@ import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import jakarta.servlet.http.HttpServletRequest;
-import javax.annotation.Resource;
+import jakarta.annotation.Resource;
/**
* @author 维哥
* @Description 登录认证切面
@@ -67,11 +67,10 @@ public class AuthAspect {
// 设置用户上下文
UserContextHolder.setUserName(username);
-
- return joinPoint.proceed();
} catch (Exception e) {
log.error("认证失败:{}", e.getMessage());
throw new BizException(ResultCode.TOKEN_FAILED.getCode(), "无效的token,请重新登录");
}
+ return joinPoint.proceed();
}
}
\ No newline at end of file
diff --git a/src/main/java/com/kexue/skills/aspect/RoleAspect.java b/src/main/java/com/kexue/skills/aspect/RoleAspect.java
index ee3c19e..0efafc3 100644
--- a/src/main/java/com/kexue/skills/aspect/RoleAspect.java
+++ b/src/main/java/com/kexue/skills/aspect/RoleAspect.java
@@ -16,6 +16,7 @@ import org.springframework.web.context.request.ServletRequestAttributes;
import jakarta.servlet.http.HttpServletRequest;
import javax.annotation.Resource;
+import java.util.List;
/**
* @author 维哥
@@ -85,14 +86,29 @@ public class RoleAspect {
// 获取用户的角色列表
String[] requiredRoles = requireRole.value();
if (requiredRoles != null && requiredRoles.length > 0) {
- // 使用Sa-Token检查角色权限
- cn.dev33.satoken.stp.StpUtil.checkRoleAnd(requiredRoles);
+ // 获取当前用户的角色列表
+ List userRoles = cn.dev33.satoken.stp.StpUtil.getRoleList();
+ log.info("当前用户的角色列表:{}", String.join(",", userRoles));
+
+ // 检查用户是否拥有所有必需的角色
+ boolean hasAllRoles = true;
+ for (String role : requiredRoles) {
+ if (!userRoles.contains(role)) {
+ hasAllRoles = false;
+ log.error("用户缺少角色:{}", role);
+ break;
+ }
+ }
+
+ if (!hasAllRoles) {
+ throw new cn.dev33.satoken.exception.NotRoleException(requiredRoles[0]);
+ }
}
// 设置用户上下文
UserContextHolder.setUserName(username);
- return joinPoint.proceed();
+
} catch (cn.dev33.satoken.exception.NotLoginException e) {
log.error("未登录:{}", e.getMessage());
throw new BizException(ResultCode.TOKEN_FAILED.getCode(), "请先登录认证后操作");
@@ -103,5 +119,6 @@ public class RoleAspect {
log.error("权限验证失败:{}", e.getMessage());
throw new BizException(ResultCode.PERMISSION_DENIED.getCode(), "权限验证失败");
}
+ return joinPoint.proceed();
}
}
\ No newline at end of file
diff --git a/src/main/java/com/kexue/skills/common/util/HttpUtil.java b/src/main/java/com/kexue/skills/common/util/HttpUtil.java
index 2ee86b5..452b3e0 100644
--- a/src/main/java/com/kexue/skills/common/util/HttpUtil.java
+++ b/src/main/java/com/kexue/skills/common/util/HttpUtil.java
@@ -57,29 +57,47 @@ public class HttpUtil {
}
/**
- * 发送POST请求到指定URL
+ * 发送 POST 请求到指定 URL
*
- * @param url 请求URL
+ * @param url 请求 URL
* @param requestBody 请求体字符串
* @return 响应结果
* @throws Exception 异常信息
*/
public static String post(String url, String requestBody) throws Exception {
- // 创建HttpClient实例
+ // 创建 HttpClient 实例
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(30))
.build();
+
+ return postWithClient(url, requestBody, client);
+ }
- // 构建HttpRequest
+ /**
+ * 发送 POST 请求到指定 URL(使用指定的 HttpClient,支持连接复用)
+ *
+ * @param url 请求 URL
+ * @param requestBody 请求体字符串
+ * @param client HttpClient 实例
+ * @return 响应结果
+ * @throws Exception 异常信息
+ */
+ public static String postWithClient(String url, String requestBody, HttpClient client) throws Exception {
+ // 构建 HttpRequest
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Content-Type", "application/xml")
+ .header("Accept", "application/xml")
.POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))
.build();
-
+
// 发送请求并获取响应
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+ // 打印响应状态码和响应体
+ System.out.println("HTTP 状态码: " + response.statusCode());
+ System.out.println("响应体: " + response.body());
+
return response.body();
}
}
\ No newline at end of file
diff --git a/src/main/java/com/kexue/skills/config/JacksonConfig.java b/src/main/java/com/kexue/skills/config/JacksonConfig.java
new file mode 100644
index 0000000..edea504
--- /dev/null
+++ b/src/main/java/com/kexue/skills/config/JacksonConfig.java
@@ -0,0 +1,26 @@
+package com.kexue.skills.config;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
+
+import java.text.SimpleDateFormat;
+import java.util.TimeZone;
+
+/**
+ * Jackson 序列化配置
+ */
+@Configuration
+public class JacksonConfig {
+
+ @Bean
+ public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
+ return builder.createXmlMapper(false)
+ .simpleDateFormat("yyyy-MM-dd HH:mm:ss")
+ .timeZone(TimeZone.getTimeZone("GMT+8"))
+ .featuresToDisable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
+ .build();
+ }
+}
diff --git a/src/main/java/com/kexue/skills/config/MyStpInterfaceImpl.java b/src/main/java/com/kexue/skills/config/MyStpInterfaceImpl.java
new file mode 100644
index 0000000..52059f8
--- /dev/null
+++ b/src/main/java/com/kexue/skills/config/MyStpInterfaceImpl.java
@@ -0,0 +1,48 @@
+package com.kexue.skills.config;
+
+import com.kexue.skills.service.SysUserService;
+import cn.dev33.satoken.stp.StpInterface;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * Sa-Token 权限验证接口实现
+ */
+@Component
+public class MyStpInterfaceImpl implements StpInterface {
+
+ @Resource
+ private SysUserService sysUserService;
+
+ /**
+ * 获取用户的角色列表
+ * @param loginId 用户登录ID
+ * @param loginType 登录类型
+ * @return 角色列表
+ */
+ @Override
+ public List getRoleList(Object loginId, String loginType) {
+ // 从数据库查询用户角色列表
+ try {
+ Long userId = Long.parseLong(loginId.toString());
+ return sysUserService.queryUserRoles(userId);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return java.util.Collections.emptyList();
+ }
+ }
+
+ /**
+ * 获取用户的权限列表
+ * @param loginId 用户登录ID
+ * @param loginType 登录类型
+ * @return 权限列表
+ */
+ @Override
+ public List getPermissionList(Object loginId, String loginType) {
+ // 暂时返回空列表,后续可根据需要从数据库查询权限列表
+ return java.util.Collections.emptyList();
+ }
+}
diff --git a/src/main/java/com/kexue/skills/controller/AccountController.java b/src/main/java/com/kexue/skills/controller/AccountController.java
index b1fd59f..e597777 100644
--- a/src/main/java/com/kexue/skills/controller/AccountController.java
+++ b/src/main/java/com/kexue/skills/controller/AccountController.java
@@ -2,9 +2,13 @@ package com.kexue.skills.controller;
import com.github.pagehelper.PageInfo;
import com.kexue.skills.annotation.RequireAuth;
+import com.kexue.skills.annotation.RequireRole;
import com.kexue.skills.common.CommonResult;
+import com.kexue.skills.common.Const;
import com.kexue.skills.entity.Account;
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 io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@@ -82,4 +86,86 @@ public class AccountController {
public CommonResult queryByUserId(@Parameter(description = "用户ID") @PathVariable("userId") Long userId) {
return CommonResult.success(this.accountService.queryByUserId(userId));
}
+
+ /**
+ * 充值账户
+ *
+ * @param userId 用户ID
+ * @param amount 充值金额
+ * @param payType 支付方式:1.微信 2.支付宝
+ * @return 充值结果
+ */
+ @Operation(summary = "充值账户", description = "充值账户")
+ @PostMapping("/recharge")
+ @RequireAuth
+ public CommonResult recharge(
+ @Parameter(description = "用户ID") @RequestParam("userId") Long userId,
+ @Parameter(description = "充值金额") @RequestParam("amount") java.math.BigDecimal amount,
+ @Parameter(description = "支付方式:1.微信 2.支付宝") @RequestParam("payType") Integer payType) {
+ // 这里可以根据需要实现充值逻辑
+ // 实际的支付流程需要通过支付接口完成
+ return CommonResult.success("充值请求已提交,请通过支付接口完成支付");
+ }
+
+ /**
+ * 减少账户余额(token消费转换)
+ *
+ * @param tokenConsumptionDto token消费转换参数
+ * @return 影响行数
+ */
+ @Operation(summary = "减少账户余额(token消费转换)", description = "减少账户余额(token消费转换)")
+ @PostMapping("/reduceBalanceWithToken")
+ @RequireAuth
+ public CommonResult reduceBalanceWithToken(
+ @RequestBody TokenConsumptionDto tokenConsumptionDto) {
+ return CommonResult.success(this.accountService.reduceBalanceWithToken(
+ tokenConsumptionDto.getUserId(),
+ tokenConsumptionDto.getInputToken(),
+ tokenConsumptionDto.getOutputToken(),
+ tokenConsumptionDto.getTotalTokens(),
+ tokenConsumptionDto.getModelName(),
+ tokenConsumptionDto.getQuestion(),
+ tokenConsumptionDto.getTransactionNo(),
+ tokenConsumptionDto.getBusinessId(),
+ tokenConsumptionDto.getBusinessType(),
+ tokenConsumptionDto.getRemark()
+ ));
+ }
+
+ /**
+ * 给用户赠送金额(不可提现)
+ * 只有管理员可以调用
+ *
+ * @param giftBalanceDto 赠送金额参数
+ * @return 影响行数
+ */
+ @Operation(summary = "给用户赠送金额(不可提现)", description = "给用户赠送金额(不可提现),只有管理员可以调用")
+ @PostMapping("/addGiftBalance")
+ @RequireAuth
+ @RequireRole({"ADMIN"})
+ public CommonResult addGiftBalance(@RequestBody GiftBalanceDto giftBalanceDto) {
+ return CommonResult.success(this.accountService.addGiftBalance(
+ giftBalanceDto.getUserId(),
+ giftBalanceDto.getAmount(),
+ giftBalanceDto.getTransactionNo(),
+ giftBalanceDto.getBusinessId(),
+ giftBalanceDto.getBusinessType(),
+ giftBalanceDto.getRemark()
+ ));
+ }
+
+ /**
+ * 获取用户交易记录
+ *
+ * @param userId 用户ID
+ * @return 交易记录列表
+ */
+ @Operation(summary = "获取用户交易记录", description = "获取用户交易记录")
+ @PostMapping("/getTransactions")
+ @RequireAuth
+ public CommonResult> getTransactions(
+ @RequestBody java.util.Map params) {
+ Long userId = Long.valueOf(params.get("userId").toString());
+ return CommonResult.success(this.accountService.getTransactions(userId));
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/kexue/skills/controller/ModelPriceController.java b/src/main/java/com/kexue/skills/controller/ModelPriceController.java
new file mode 100644
index 0000000..4440aca
--- /dev/null
+++ b/src/main/java/com/kexue/skills/controller/ModelPriceController.java
@@ -0,0 +1,113 @@
+package com.kexue.skills.controller;
+
+import com.github.pagehelper.PageInfo;
+import com.kexue.skills.entity.ModelPrice;
+import com.kexue.skills.entity.dto.ModelPriceDto;
+import com.kexue.skills.service.ModelPriceService;
+import com.kexue.skills.common.CommonResult;
+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;
+import java.util.List;
+
+/**
+ * (ModelPrice)表控制层
+ * 大模型Token价格表
+ *
+ * @author 王志维
+ * @since 2026-03-26 10:15:00
+ */
+@RestController
+@RequestMapping("/api/modelPrice")
+@Tag(name = "大模型Token价格表管理", description = "大模型Token价格表管理接口")
+public class ModelPriceController {
+ @Resource
+ private ModelPriceService modelPriceService;
+
+ /**
+ * 分页查询
+ *
+ * @param queryDto 筛选条件
+ * @return 查询结果
+ */
+ @Operation(summary = "分页查询", description = "分页查询大模型Token价格表")
+ @PostMapping("/getPageList")
+ public CommonResult> getPageList(@RequestBody ModelPriceDto queryDto) {
+ return CommonResult.success(this.modelPriceService.getPageList(queryDto));
+ }
+
+ /**
+ * 查询列表
+ *
+ * @param queryDto 筛选条件
+ * @return 查询结果
+ */
+ @Operation(summary = "查询列表", description = "查询大模型Token价格表列表")
+ @PostMapping("/getList")
+ public CommonResult> getList(@RequestBody ModelPriceDto queryDto) {
+ return CommonResult.success(this.modelPriceService.getList(queryDto));
+ }
+
+ /**
+ * 通过主键查询单条数据
+ *
+ * @param id 主键
+ * @return 实例对象
+ */
+ @Operation(summary = "通过主键查询", description = "通过主键查询大模型Token价格表")
+ @GetMapping("/queryById/{id}")
+ public CommonResult queryById(@PathVariable("id") Long id) {
+ return CommonResult.success(this.modelPriceService.queryById(id));
+ }
+
+ /**
+ * 通过模型名称查询数据
+ *
+ * @param modelName 模型名称
+ * @return 实例对象
+ */
+ @Operation(summary = "通过模型名称查询", description = "通过模型名称查询大模型Token价格表")
+ @GetMapping("/queryByModelName/{modelName}")
+ public CommonResult queryByModelName(@PathVariable("modelName") String modelName) {
+ return CommonResult.success(this.modelPriceService.queryByModelName(modelName));
+ }
+
+ /**
+ * 新增数据
+ *
+ * @param modelPrice 实例对象
+ * @return 实例对象
+ */
+ @Operation(summary = "新增数据", description = "新增大模型Token价格表")
+ @PostMapping("/insert")
+ public CommonResult insert(@RequestBody ModelPrice modelPrice) {
+ return CommonResult.success(this.modelPriceService.insert(modelPrice));
+ }
+
+ /**
+ * 更新数据
+ *
+ * @param modelPrice 实例对象
+ * @return 实例对象
+ */
+ @Operation(summary = "更新数据", description = "更新大模型Token价格表")
+ @PostMapping("/update")
+ public CommonResult update(@RequestBody ModelPrice modelPrice) {
+ return CommonResult.success(this.modelPriceService.update(modelPrice));
+ }
+
+ /**
+ * 通过主键删除数据
+ *
+ * @param id 主键
+ * @return 影响行数
+ */
+ @Operation(summary = "通过主键删除", description = "通过主键删除大模型Token价格表")
+ @PostMapping("/deleteById/{id}")
+ public CommonResult deleteById(@PathVariable("id") Long id) {
+ return CommonResult.success(this.modelPriceService.deleteById(id));
+ }
+
+}
diff --git a/src/main/java/com/kexue/skills/controller/PayController.java b/src/main/java/com/kexue/skills/controller/PayController.java
index 3a6656b..a0fab51 100644
--- a/src/main/java/com/kexue/skills/controller/PayController.java
+++ b/src/main/java/com/kexue/skills/controller/PayController.java
@@ -33,24 +33,54 @@ public class PayController {
/**
* 创建微信支付订单
* @param order 支付订单信息
+ * @param request HTTP 请求(用于获取用户 IP)
* @return 微信支付参数
*/
@Operation(summary = "创建微信支付订单", description = "创建微信支付订单")
@PostMapping("/wx/create")
- public CommonResult