feat(account): 扩展账户余额管理功能
- 新增可提现余额和不可提现余额字段,完善账户余额结构 - 添加充值接口支持微信和支付宝支付方式 - 实现token消费转换扣费功能,支持AI模型调用计费 - 增加管理员赠送金额接口,仅管理员可调用 - 完善交易记录查询功能,支持用户查看历史交易明细 - 集成模型价格服务,实现token费用自动计算 - 重构余额增加逻辑,区分可提现和不可提现金额 - 优化账户实体类初始化逻辑,确保余额字段正确设置 - 更新交易记录实体类,新增token相关和收支类型字段 - 修改支付配置,更新微信和支付宝回调地址为生产环境域名
This commit is contained in:
parent
3df611f809
commit
770f50302e
|
|
@ -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": "<form name=\"alipay\" method=\"post\" action=\"https://openapi.alipay.com/gateway.do\">...</form>"
|
||||
}
|
||||
```
|
||||
|
||||
**使用说明:**
|
||||
- 返回的是 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
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>微信支付</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/qrcode@1.5.0/build/qrcode.min.js"></script>
|
||||
<style>
|
||||
.payment-container {
|
||||
max-width: 600px;
|
||||
margin: 50px auto;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.qrcode-box {
|
||||
border: 1px solid #ddd;
|
||||
padding: 20px;
|
||||
display: inline-block;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.status-tip {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="payment-container">
|
||||
<h2>微信支付</h2>
|
||||
<div class="qrcode-box">
|
||||
<div id="qrcode"></div>
|
||||
</div>
|
||||
<p class="status-tip">请使用微信扫码支付</p>
|
||||
<p class="status-tip" id="statusTip">等待支付...</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 从 URL 参数获取商品信息
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const productId = urlParams.get('productId');
|
||||
const amount = urlParams.get('amount');
|
||||
|
||||
// 创建支付订单
|
||||
async function createPayment() {
|
||||
try {
|
||||
const response = await fetch('/api/pay/wx/create', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
userId: 123, // 实际应从登录信息中获取
|
||||
amount: parseFloat(amount),
|
||||
productName: '商品-' + productId,
|
||||
businessId: productId,
|
||||
businessType: 'purchase_content'
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
if (result.code === 200) {
|
||||
// 生成二维码
|
||||
QRCode.toCanvas(document.getElementById('qrcode'), result.data.code_url, {
|
||||
width: 200,
|
||||
height: 200
|
||||
});
|
||||
|
||||
// 开始轮询订单状态
|
||||
checkOrderStatus(result.data.order_no);
|
||||
} else {
|
||||
alert('创建订单失败:' + result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('创建订单失败:', error);
|
||||
alert('网络错误,请稍后重试');
|
||||
}
|
||||
}
|
||||
|
||||
// 轮询订单状态
|
||||
async function checkOrderStatus(orderNo) {
|
||||
const maxAttempts = 300; // 最多查询 300 次(15 分钟)
|
||||
let attempts = 0;
|
||||
|
||||
const timer = setInterval(async () => {
|
||||
try {
|
||||
const response = await fetch(`/api/pay/order/query?orderNo=${orderNo}`);
|
||||
const result = await response.json();
|
||||
|
||||
if (result.code === 200 && result.data) {
|
||||
const status = result.data.status;
|
||||
const statusText = document.getElementById('statusTip');
|
||||
|
||||
if (status === 2) { // 已支付
|
||||
clearInterval(timer);
|
||||
statusText.textContent = '✅ 支付成功!';
|
||||
statusText.style.color = 'green';
|
||||
|
||||
setTimeout(() => {
|
||||
window.location.href = '/pay-success.html?orderNo=' + orderNo;
|
||||
}, 1000);
|
||||
return;
|
||||
} else if (status === 3 || status === 4) { // 支付失败或已取消
|
||||
clearInterval(timer);
|
||||
statusText.textContent = '❌ 支付失败';
|
||||
statusText.style.color = 'red';
|
||||
return;
|
||||
}
|
||||
|
||||
attempts++;
|
||||
if (attempts >= maxAttempts) {
|
||||
clearInterval(timer);
|
||||
statusText.textContent = '⏰ 支付超时,请重新下单';
|
||||
statusText.style.color = 'orange';
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('查询订单状态失败:', error);
|
||||
}
|
||||
}, 3000); // 每 3 秒查询一次
|
||||
}
|
||||
|
||||
// 页面加载时创建支付订单
|
||||
createPayment();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### 4.2 支付宝支付页面
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>支付宝支付</title>
|
||||
</head>
|
||||
<body>
|
||||
<div style="text-align: center; padding: 50px;">
|
||||
<h2>正在跳转到支付宝支付页面...</h2>
|
||||
<p>请稍候</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 从 URL 获取商品信息
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const productId = urlParams.get('productId');
|
||||
const amount = urlParams.get('amount');
|
||||
|
||||
// 创建支付订单
|
||||
async function createAlipay() {
|
||||
try {
|
||||
const response = await fetch('/api/pay/alipay/create', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
userId: 123,
|
||||
amount: parseFloat(amount),
|
||||
productName: '商品-' + productId,
|
||||
businessId: productId,
|
||||
businessType: 'purchase_content'
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
if (result.code === 200) {
|
||||
// 将返回的 HTML 表单写入页面并提交
|
||||
document.body.innerHTML = result.data;
|
||||
document.forms[0].submit();
|
||||
} else {
|
||||
alert('创建订单失败:' + result.message);
|
||||
setTimeout(() => {
|
||||
window.location.href = '/index.html';
|
||||
}, 2000);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('创建订单失败:', error);
|
||||
alert('网络错误,请稍后重试');
|
||||
}
|
||||
}
|
||||
|
||||
createAlipay();
|
||||
</script>
|
||||
</body>
|
||||
</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
|
||||
**维护人员:** 系统开发团队
|
||||
|
|
@ -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;
|
||||
8
pom.xml
8
pom.xml
|
|
@ -182,10 +182,6 @@
|
|||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>log4j-over-slf4j</artifactId>
|
||||
|
|
@ -194,10 +190,6 @@
|
|||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>jul-to-slf4j</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-logging</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.github.jsqlparser</groupId>
|
||||
<artifactId>jsqlparser</artifactId>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String> 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
// 打印响应状态码和响应体
|
||||
System.out.println("HTTP 状态码: " + response.statusCode());
|
||||
System.out.println("响应体: " + response.body());
|
||||
|
||||
return response.body();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String> 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<String> getPermissionList(Object loginId, String loginType) {
|
||||
// 暂时返回空列表,后续可根据需要从数据库查询权限列表
|
||||
return java.util.Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Account> 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<String> 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<Integer> 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<Integer> 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<List<com.kexue.skills.entity.AccountTransaction>> getTransactions(
|
||||
@RequestBody java.util.Map<String, Object> params) {
|
||||
Long userId = Long.valueOf(params.get("userId").toString());
|
||||
return CommonResult.success(this.accountService.getTransactions(userId));
|
||||
}
|
||||
}
|
||||
|
|
@ -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<PageInfo<ModelPrice>> getPageList(@RequestBody ModelPriceDto queryDto) {
|
||||
return CommonResult.success(this.modelPriceService.getPageList(queryDto));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*
|
||||
* @param queryDto 筛选条件
|
||||
* @return 查询结果
|
||||
*/
|
||||
@Operation(summary = "查询列表", description = "查询大模型Token价格表列表")
|
||||
@PostMapping("/getList")
|
||||
public CommonResult<List<ModelPrice>> getList(@RequestBody ModelPriceDto queryDto) {
|
||||
return CommonResult.success(this.modelPriceService.getList(queryDto));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过主键查询单条数据
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 实例对象
|
||||
*/
|
||||
@Operation(summary = "通过主键查询", description = "通过主键查询大模型Token价格表")
|
||||
@GetMapping("/queryById/{id}")
|
||||
public CommonResult<ModelPrice> queryById(@PathVariable("id") Long id) {
|
||||
return CommonResult.success(this.modelPriceService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过模型名称查询数据
|
||||
*
|
||||
* @param modelName 模型名称
|
||||
* @return 实例对象
|
||||
*/
|
||||
@Operation(summary = "通过模型名称查询", description = "通过模型名称查询大模型Token价格表")
|
||||
@GetMapping("/queryByModelName/{modelName}")
|
||||
public CommonResult<ModelPrice> queryByModelName(@PathVariable("modelName") String modelName) {
|
||||
return CommonResult.success(this.modelPriceService.queryByModelName(modelName));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增数据
|
||||
*
|
||||
* @param modelPrice 实例对象
|
||||
* @return 实例对象
|
||||
*/
|
||||
@Operation(summary = "新增数据", description = "新增大模型Token价格表")
|
||||
@PostMapping("/insert")
|
||||
public CommonResult<ModelPrice> insert(@RequestBody ModelPrice modelPrice) {
|
||||
return CommonResult.success(this.modelPriceService.insert(modelPrice));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新数据
|
||||
*
|
||||
* @param modelPrice 实例对象
|
||||
* @return 实例对象
|
||||
*/
|
||||
@Operation(summary = "更新数据", description = "更新大模型Token价格表")
|
||||
@PostMapping("/update")
|
||||
public CommonResult<ModelPrice> update(@RequestBody ModelPrice modelPrice) {
|
||||
return CommonResult.success(this.modelPriceService.update(modelPrice));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过主键删除数据
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 影响行数
|
||||
*/
|
||||
@Operation(summary = "通过主键删除", description = "通过主键删除大模型Token价格表")
|
||||
@PostMapping("/deleteById/{id}")
|
||||
public CommonResult<Integer> deleteById(@PathVariable("id") Long id) {
|
||||
return CommonResult.success(this.modelPriceService.deleteById(id));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -33,24 +33,54 @@ public class PayController {
|
|||
/**
|
||||
* 创建微信支付订单
|
||||
* @param order 支付订单信息
|
||||
* @param request HTTP 请求(用于获取用户 IP)
|
||||
* @return 微信支付参数
|
||||
*/
|
||||
@Operation(summary = "创建微信支付订单", description = "创建微信支付订单")
|
||||
@PostMapping("/wx/create")
|
||||
public CommonResult<Map<String, String>> createWechatPay(@RequestBody PaymentOrder order) {
|
||||
public CommonResult<Map<String, String>> createWechatPay(@RequestBody PaymentOrder order, HttpServletRequest request) {
|
||||
try {
|
||||
// 设置支付类型为微信支付(1)
|
||||
order.setPayType(1);
|
||||
// 创建支付订单
|
||||
PaymentOrder createdOrder = paymentOrderService.createPaymentOrder(order);
|
||||
// 获取用户真实 IP
|
||||
String ipAddress = getUserIpAddress(request);
|
||||
// 生成微信支付参数
|
||||
Map<String, String> payParams = payService.createWechatPay(createdOrder);
|
||||
Map<String, String> payParams = payService.createWechatPay(createdOrder, ipAddress);
|
||||
return CommonResult.success(payParams);
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error("参数错误:{}", e.getMessage());
|
||||
return CommonResult.failed(e.getMessage());
|
||||
} catch (Exception e) {
|
||||
logger.error("创建微信支付订单失败", e);
|
||||
return CommonResult.failed("创建微信支付订单失败: " + e.getMessage());
|
||||
return CommonResult.failed("系统繁忙,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户真实 IP 地址
|
||||
*/
|
||||
private String getUserIpAddress(HttpServletRequest request) {
|
||||
String ip = request.getHeader("X-Forwarded-For");
|
||||
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("X-Real-IP");
|
||||
}
|
||||
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("Proxy-Client-IP");
|
||||
}
|
||||
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("WL-Proxy-Client-IP");
|
||||
}
|
||||
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getRemoteAddr();
|
||||
}
|
||||
// 如果是多代理,取第一个 IP
|
||||
if (ip != null && !ip.isEmpty() && ip.contains(",")) {
|
||||
ip = ip.split(",")[0].trim();
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理微信支付回调
|
||||
|
|
|
|||
|
|
@ -1,85 +0,0 @@
|
|||
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.PointsAccount;
|
||||
import com.kexue.skills.entity.dto.PointsAccountDto;
|
||||
import com.kexue.skills.service.PointsAccountService;
|
||||
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 java.util.List;
|
||||
|
||||
/**
|
||||
* (PointsAccount)表控制层
|
||||
* 积分账户管理控制器
|
||||
*
|
||||
* @author 王志维
|
||||
* @since 2025-02-21 23:01:48
|
||||
*/
|
||||
@Tag(name = "积分账户管理 Api")
|
||||
@RestController
|
||||
@RequestMapping("/api/pointsAccount")
|
||||
public class PointsAccountController {
|
||||
/**
|
||||
* 服务对象
|
||||
*/
|
||||
@Resource
|
||||
private PointsAccountService pointsAccountService;
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*
|
||||
* @param queryDto 查询参数
|
||||
* @return 分页结果
|
||||
*/
|
||||
@Operation(summary = "分页查询积分账户", description = "分页查询积分账户")
|
||||
@PostMapping("/pageList")
|
||||
@RequireAuth
|
||||
public CommonResult<PageInfo<PointsAccount>> getPageList(@RequestBody PointsAccountDto queryDto) {
|
||||
return CommonResult.success(this.pointsAccountService.getPageList(queryDto));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*
|
||||
* @param queryDto 查询参数
|
||||
* @return 列表结果
|
||||
*/
|
||||
@Operation(summary = "查询积分账户列表", description = "查询积分账户列表")
|
||||
@PostMapping("/list")
|
||||
@RequireAuth
|
||||
public CommonResult<List<PointsAccount>> getList(@RequestBody PointsAccountDto queryDto) {
|
||||
return CommonResult.success(this.pointsAccountService.getList(queryDto));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过主键查询单条数据
|
||||
*
|
||||
* @param accountId 主键
|
||||
* @return 单条数据
|
||||
*/
|
||||
@Operation(summary = "通过ID查询积分账户", description = "通过ID查询积分账户")
|
||||
@PostMapping("/queryById/{accountId}")
|
||||
@RequireAuth
|
||||
public CommonResult<PointsAccount> queryById(@Parameter(description = "账户ID") @PathVariable("accountId") Long accountId) {
|
||||
return CommonResult.success(this.pointsAccountService.queryById(accountId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过用户ID查询单条数据
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 单条数据
|
||||
*/
|
||||
@Operation(summary = "通过用户ID查询积分账户", description = "通过用户ID查询积分账户")
|
||||
@PostMapping("/queryByUserId/{userId}")
|
||||
@RequireAuth
|
||||
public CommonResult<PointsAccount> queryByUserId(@Parameter(description = "用户ID") @PathVariable("userId") Long userId) {
|
||||
return CommonResult.success(this.pointsAccountService.queryByUserId(userId));
|
||||
}
|
||||
}
|
||||
|
|
@ -108,25 +108,8 @@ public class SysUserController {
|
|||
@PostMapping("/resetPassword")
|
||||
@Operation(summary = "管理员帮助用户重置密码", description = "管理员帮助用户重置密码")
|
||||
@RequireAuth
|
||||
public CommonResult<Boolean> resetPasswordByAdmin(@RequestBody ResetPasswordDto resetPasswordDto, HttpServletRequest request) {
|
||||
// 从请求头中获取token
|
||||
String token = request.getHeader("Authorization");
|
||||
if (token == null || token.isEmpty()) {
|
||||
throw new BizException("请先登录认证后操作");
|
||||
}
|
||||
|
||||
// 从缓存中获取当前登录用户
|
||||
String username = CacheManager.getUsernameFromToken(token);
|
||||
if (username == null) {
|
||||
throw new BizException("无效的token,请重新登录");
|
||||
}
|
||||
|
||||
SysUser adminUser = sysUserService.getByUsername(username);
|
||||
if (adminUser == null) {
|
||||
throw new BizException("管理员不存在");
|
||||
}
|
||||
|
||||
boolean result = sysUserService.resetPasswordByAdmin(resetPasswordDto);
|
||||
public CommonResult<Boolean> resetPassword(@RequestBody ResetPwdDto resetPasswordDto) {
|
||||
boolean result = sysUserService.resetPassword(resetPasswordDto);
|
||||
return CommonResult.success(result);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,129 @@
|
|||
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.WithdrawalRecord;
|
||||
import com.kexue.skills.entity.dto.WithdrawalRecordDto;
|
||||
import com.kexue.skills.service.WithdrawalRecordService;
|
||||
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 java.util.List;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 提现记录控制器
|
||||
*
|
||||
* @author 王志维
|
||||
* @since 2026-03-25
|
||||
*/
|
||||
@Tag(name = "提现记录管理 Api")
|
||||
@RestController
|
||||
@RequestMapping("/api/withdrawalRecord")
|
||||
public class WithdrawalRecordController {
|
||||
/**
|
||||
* 服务对象
|
||||
*/
|
||||
@Resource
|
||||
private WithdrawalRecordService withdrawalRecordService;
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*
|
||||
* @param queryDto 查询参数
|
||||
* @return 分页结果
|
||||
*/
|
||||
@Operation(summary = "分页查询提现记录", description = "分页查询提现记录")
|
||||
@PostMapping("/pageList")
|
||||
@RequireAuth
|
||||
public CommonResult<PageInfo<WithdrawalRecord>> getPageList(@RequestBody WithdrawalRecordDto queryDto) {
|
||||
return CommonResult.success(this.withdrawalRecordService.getPageList(queryDto));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*
|
||||
* @param queryDto 查询参数
|
||||
* @return 列表结果
|
||||
*/
|
||||
@Operation(summary = "查询提现记录列表", description = "查询提现记录列表")
|
||||
@PostMapping("/list")
|
||||
@RequireAuth
|
||||
public CommonResult<List<WithdrawalRecord>> getList(@RequestBody WithdrawalRecordDto queryDto) {
|
||||
return CommonResult.success(this.withdrawalRecordService.getList(queryDto));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过主键查询单条数据
|
||||
*
|
||||
* @param recordId 主键
|
||||
* @return 单条数据
|
||||
*/
|
||||
@Operation(summary = "通过ID查询提现记录", description = "通过ID查询提现记录")
|
||||
@PostMapping("/queryById/{recordId}")
|
||||
@RequireAuth
|
||||
public CommonResult<WithdrawalRecord> queryById(@Parameter(description = "提现记录ID") @PathVariable("recordId") Long recordId) {
|
||||
return CommonResult.success(this.withdrawalRecordService.queryById(recordId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过用户ID查询提现记录
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 提现记录列表
|
||||
*/
|
||||
@Operation(summary = "通过用户ID查询提现记录", description = "通过用户ID查询提现记录")
|
||||
@PostMapping("/queryByUserId/{userId}")
|
||||
@RequireAuth
|
||||
public CommonResult<List<WithdrawalRecord>> queryByUserId(@Parameter(description = "用户ID") @PathVariable("userId") Long userId) {
|
||||
return CommonResult.success(this.withdrawalRecordService.queryByUserId(userId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交提现申请
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param amount 提现金额
|
||||
* @param bankName 银行名称
|
||||
* @param bankAccount 银行账号
|
||||
* @param bankCardholder 持卡人姓名
|
||||
* @param remark 备注
|
||||
* @return 提现记录
|
||||
*/
|
||||
@Operation(summary = "提交提现申请", description = "提交提现申请")
|
||||
@PostMapping("/submit")
|
||||
@RequireAuth
|
||||
public CommonResult<WithdrawalRecord> submitWithdrawal(
|
||||
@Parameter(description = "用户ID") @RequestParam("userId") Long userId,
|
||||
@Parameter(description = "提现金额") @RequestParam("amount") BigDecimal amount,
|
||||
@Parameter(description = "银行名称") @RequestParam("bankName") String bankName,
|
||||
@Parameter(description = "银行账号") @RequestParam("bankAccount") String bankAccount,
|
||||
@Parameter(description = "持卡人姓名") @RequestParam("bankCardholder") String bankCardholder,
|
||||
@Parameter(description = "备注") @RequestParam("remark") String remark) {
|
||||
WithdrawalRecord record = this.withdrawalRecordService.submitWithdrawal(userId, amount, bankName, bankAccount, bankCardholder, remark);
|
||||
return CommonResult.success(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理提现
|
||||
*
|
||||
* @param recordId 记录ID
|
||||
* @param status 状态
|
||||
* @param remark 备注
|
||||
* @return 处理结果
|
||||
*/
|
||||
@Operation(summary = "处理提现", description = "处理提现")
|
||||
@PostMapping("/process")
|
||||
@RequireAuth
|
||||
public CommonResult<Integer> processWithdrawal(
|
||||
@Parameter(description = "提现记录ID") @RequestParam("recordId") Long recordId,
|
||||
@Parameter(description = "状态:3.成功 4.失败") @RequestParam("status") Integer status,
|
||||
@Parameter(description = "备注") @RequestParam("remark") String remark) {
|
||||
int result = this.withdrawalRecordService.processWithdrawal(recordId, status, remark);
|
||||
return CommonResult.success(result);
|
||||
}
|
||||
}
|
||||
|
|
@ -29,9 +29,15 @@ public class Account extends BaseEntity implements Serializable {
|
|||
@Schema(description ="用户名")
|
||||
private String userName;
|
||||
|
||||
@Schema(description ="账户余额")
|
||||
@Schema(description ="账户总余额")
|
||||
private BigDecimal balance;
|
||||
|
||||
@Schema(description ="可提现余额")
|
||||
private BigDecimal withdrawableBalance;
|
||||
|
||||
@Schema(description ="不可提现余额")
|
||||
private BigDecimal nonWithdrawableBalance;
|
||||
|
||||
@Schema(description ="冻结金额")
|
||||
private BigDecimal frozenAmount;
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ public class AccountTransaction extends BaseEntity implements Serializable {
|
|||
@Schema(description ="用户名")
|
||||
private String userName;
|
||||
|
||||
@Schema(description ="交易类型:1.充值 2.提现 3.购买内容 4.退款 5.其他")
|
||||
@Schema(description ="交易类型:1.充值 2.提现 3.购买内容 4.退款 5.签到奖励 6.赠送 7.其他")
|
||||
private Integer transactionType;
|
||||
|
||||
@Schema(description ="交易金额")
|
||||
|
|
@ -59,6 +59,27 @@ public class AccountTransaction extends BaseEntity implements Serializable {
|
|||
@Schema(description ="交易备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description ="是否支出:1.是 0.否")
|
||||
private Integer isExpense;
|
||||
|
||||
@Schema(description ="输入token")
|
||||
private Integer inputToken;
|
||||
|
||||
@Schema(description ="输出token")
|
||||
private Integer outputToken;
|
||||
|
||||
@Schema(description ="合计tokens")
|
||||
private Integer totalTokens;
|
||||
|
||||
@Schema(description ="处理的模型名称")
|
||||
private String modelName;
|
||||
|
||||
@Schema(description ="对应回答的问题或需求")
|
||||
private String question;
|
||||
|
||||
@Schema(description ="收入类型:recharge(充值)、sign_in(签到奖励)")
|
||||
private String incomeType;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
@Schema(description ="创建时间")
|
||||
private Date createTime;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
package com.kexue.skills.entity;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
import java.io.Serializable;
|
||||
import com.kexue.skills.entity.base.BaseEntity;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* (ModelPrice)实体类
|
||||
* 大模型Token价格表
|
||||
*
|
||||
* @author 王志维
|
||||
* @since 2026-03-26 10:15:00
|
||||
*/
|
||||
@Data
|
||||
public class ModelPrice extends BaseEntity implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description ="主键ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description ="厂商")
|
||||
private String vendor;
|
||||
|
||||
@Schema(description ="模型名称")
|
||||
private String modelName;
|
||||
|
||||
@Schema(description ="输入价格:元/百万Token")
|
||||
private BigDecimal inputPrice;
|
||||
|
||||
@Schema(description ="输出价格:元/百万Token")
|
||||
private BigDecimal outputPrice;
|
||||
|
||||
@Schema(description ="1分钱可购买输入Token数")
|
||||
private Long inputPerCent;
|
||||
|
||||
@Schema(description ="1分钱可购买输出Token数")
|
||||
private Long outputPerCent;
|
||||
|
||||
@Schema(description ="价格单位")
|
||||
private String unit;
|
||||
|
||||
@Schema(description ="备注")
|
||||
private String remark;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
@Schema(description ="创建时间")
|
||||
private Date createdTime;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
@Schema(description ="更新时间")
|
||||
private Date updatedTime;
|
||||
|
||||
}
|
||||
|
|
@ -53,7 +53,7 @@ public class PaymentOrder extends BaseEntity implements Serializable {
|
|||
@Schema(description ="关联业务ID")
|
||||
private Long businessId;
|
||||
|
||||
@Schema(description ="业务类型")
|
||||
@Schema(description ="业务类型:recharge,purchase_content")
|
||||
private String businessType;
|
||||
|
||||
@Schema(description ="支付回调地址")
|
||||
|
|
|
|||
|
|
@ -1,57 +0,0 @@
|
|||
package com.kexue.skills.entity;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import java.io.Serializable;
|
||||
import com.kexue.skills.entity.base.BaseEntity;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* (PointsAccount)实体类
|
||||
* 积分账户表,记录用户的积分信息
|
||||
*
|
||||
* @author 王志维
|
||||
* @since 2025-02-21 23:01:48
|
||||
*/
|
||||
@Data
|
||||
public class PointsAccount extends BaseEntity implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description ="主键ID")
|
||||
private Long accountId;
|
||||
|
||||
@Schema(description ="用户ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description ="用户名")
|
||||
private String userName;
|
||||
|
||||
@Schema(description ="总积分")
|
||||
private Integer totalPoints;
|
||||
|
||||
@Schema(description ="可用积分")
|
||||
private Integer availablePoints;
|
||||
|
||||
@Schema(description ="冻结积分")
|
||||
private Integer frozenPoints;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
@Schema(description ="创建时间")
|
||||
private Date createTime;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
@Schema(description ="更新时间")
|
||||
private Date updateTime;
|
||||
|
||||
@Schema(description ="是否删除 :0 未删除,1已删除")
|
||||
private Integer deleteFlag;
|
||||
|
||||
@Schema(description ="创建人")
|
||||
private String createBy;
|
||||
|
||||
@Schema(description ="更新人")
|
||||
private String updateBy;
|
||||
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
package com.kexue.skills.entity;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import java.io.Serializable;
|
||||
import com.kexue.skills.entity.base.BaseEntity;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* (PointsTransaction)实体类
|
||||
* 积分流水表,记录用户的积分变动情况
|
||||
*
|
||||
* @author 王志维
|
||||
* @since 2025-02-21 23:01:48
|
||||
*/
|
||||
@Data
|
||||
public class PointsTransaction extends BaseEntity implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description ="主键ID")
|
||||
private Long transactionId;
|
||||
|
||||
@Schema(description ="用户ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description ="用户名")
|
||||
private String userName;
|
||||
|
||||
@Schema(description ="积分变动类型:1.获取积分 2.消费积分 3.过期 4.其他")
|
||||
private Integer transactionType;
|
||||
|
||||
@Schema(description ="变动积分")
|
||||
private Integer points;
|
||||
|
||||
@Schema(description ="变动前积分")
|
||||
private Integer beforePoints;
|
||||
|
||||
@Schema(description ="变动后积分")
|
||||
private Integer afterPoints;
|
||||
|
||||
@Schema(description ="交易状态:1.成功 2.失败 3.处理中")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description ="交易单号")
|
||||
private String transactionNo;
|
||||
|
||||
@Schema(description ="支付方式:1.微信 2.支付宝 3.余额支付")
|
||||
private Integer payType;
|
||||
|
||||
@Schema(description ="关联业务ID")
|
||||
private Long businessId;
|
||||
|
||||
@Schema(description ="业务类型")
|
||||
private String businessType;
|
||||
|
||||
@Schema(description ="备注")
|
||||
private String remark;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
@Schema(description ="创建时间")
|
||||
private Date createTime;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
@Schema(description ="更新时间")
|
||||
private Date updateTime;
|
||||
|
||||
@Schema(description ="创建人")
|
||||
private String createBy;
|
||||
|
||||
@Schema(description ="更新人")
|
||||
private String updateBy;
|
||||
|
||||
@Schema(description ="是否删除 :0 未删除,1已删除")
|
||||
private Integer deleteFlag;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package com.kexue.skills.entity;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
import java.io.Serializable;
|
||||
import com.kexue.skills.entity.base.BaseEntity;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 提现记录实体类
|
||||
*
|
||||
* @author 王志维
|
||||
* @since 2026-03-25
|
||||
*/
|
||||
@Data
|
||||
public class WithdrawalRecord extends BaseEntity implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description ="主键ID")
|
||||
private Long recordId;
|
||||
|
||||
@Schema(description ="用户ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description ="用户名")
|
||||
private String userName;
|
||||
|
||||
@Schema(description ="提现金额")
|
||||
private BigDecimal withdrawalAmount;
|
||||
|
||||
@Schema(description ="手续费")
|
||||
private BigDecimal feeAmount;
|
||||
|
||||
@Schema(description ="实际到账金额")
|
||||
private BigDecimal actualAmount;
|
||||
|
||||
@Schema(description ="提现状态:1.待处理 2.处理中 3.成功 4.失败")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description ="提现单号")
|
||||
private String withdrawalNo;
|
||||
|
||||
@Schema(description ="银行名称")
|
||||
private String bankName;
|
||||
|
||||
@Schema(description ="银行账号")
|
||||
private String bankAccount;
|
||||
|
||||
@Schema(description ="持卡人姓名")
|
||||
private String bankCardholder;
|
||||
|
||||
@Schema(description ="备注")
|
||||
private String remark;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
@Schema(description ="创建时间")
|
||||
private Date createTime;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
@Schema(description ="更新时间")
|
||||
private Date updateTime;
|
||||
|
||||
@Schema(description ="是否删除 :0 未删除,1已删除")
|
||||
private Integer deleteFlag;
|
||||
|
||||
@Schema(description ="创建人")
|
||||
private String createBy;
|
||||
|
||||
@Schema(description ="更新人")
|
||||
private String updateBy;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package com.kexue.skills.entity.dto;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 赠送金额参数DTO
|
||||
*
|
||||
* @author 王志维
|
||||
* @since 2026-03-26
|
||||
*/
|
||||
public class GiftBalanceDto {
|
||||
private Long userId;
|
||||
private BigDecimal amount;
|
||||
private String transactionNo;
|
||||
private Long businessId;
|
||||
private String businessType;
|
||||
private String remark;
|
||||
|
||||
public Long getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(Long userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public BigDecimal getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public void setAmount(BigDecimal amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public String getTransactionNo() {
|
||||
return transactionNo;
|
||||
}
|
||||
|
||||
public void setTransactionNo(String transactionNo) {
|
||||
this.transactionNo = transactionNo;
|
||||
}
|
||||
|
||||
public Long getBusinessId() {
|
||||
return businessId;
|
||||
}
|
||||
|
||||
public void setBusinessId(Long businessId) {
|
||||
this.businessId = businessId;
|
||||
}
|
||||
|
||||
public String getBusinessType() {
|
||||
return businessType;
|
||||
}
|
||||
|
||||
public void setBusinessType(String businessType) {
|
||||
this.businessType = businessType;
|
||||
}
|
||||
|
||||
public String getRemark() {
|
||||
return remark;
|
||||
}
|
||||
|
||||
public void setRemark(String remark) {
|
||||
this.remark = remark;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package com.kexue.skills.entity.dto;
|
||||
|
||||
import com.kexue.skills.entity.base.BaseQueryDto;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* (ModelPrice)数据传输对象
|
||||
* 大模型Token价格表
|
||||
*
|
||||
* @author 王志维
|
||||
* @since 2026-03-26 10:15:00
|
||||
*/
|
||||
@Data
|
||||
public class ModelPriceDto extends BaseQueryDto {
|
||||
|
||||
@Schema(description ="主键ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description ="厂商")
|
||||
private String vendor;
|
||||
|
||||
@Schema(description ="模型名称")
|
||||
private String modelName;
|
||||
|
||||
@Schema(description ="输入价格:元/百万Token")
|
||||
private BigDecimal inputPrice;
|
||||
|
||||
@Schema(description ="输出价格:元/百万Token")
|
||||
private BigDecimal outputPrice;
|
||||
|
||||
@Schema(description ="1分钱可购买输入Token数")
|
||||
private Long inputPerCent;
|
||||
|
||||
@Schema(description ="1分钱可购买输出Token数")
|
||||
private Long outputPerCent;
|
||||
|
||||
@Schema(description ="价格单位")
|
||||
private String unit;
|
||||
|
||||
@Schema(description ="备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description ="创建时间")
|
||||
private Date createdTime;
|
||||
|
||||
@Schema(description ="更新时间")
|
||||
private Date updatedTime;
|
||||
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
package com.kexue.skills.entity.dto;
|
||||
|
||||
import com.kexue.skills.entity.base.BaseQueryDto;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* (PointsAccount)查询条件封装类
|
||||
* 积分账户查询条件
|
||||
*
|
||||
* @author 王志维
|
||||
* @since 2025-02-21 23:01:48
|
||||
*/
|
||||
@Data
|
||||
public class PointsAccountDto extends BaseQueryDto {
|
||||
|
||||
@Schema(description ="主键ID")
|
||||
private Long accountId;
|
||||
|
||||
@Schema(description ="用户ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description ="用户名")
|
||||
private String userName;
|
||||
|
||||
@Schema(description ="总积分")
|
||||
private Integer totalPoints;
|
||||
|
||||
@Schema(description ="可用积分")
|
||||
private Integer availablePoints;
|
||||
|
||||
@Schema(description ="冻结积分")
|
||||
private Integer frozenPoints;
|
||||
|
||||
@Schema(description ="创建时间开始")
|
||||
private Date createTimeStart;
|
||||
|
||||
@Schema(description ="创建时间结束")
|
||||
private Date createTimeEnd;
|
||||
|
||||
@Schema(description ="更新时间开始")
|
||||
private Date updateTimeStart;
|
||||
|
||||
@Schema(description ="更新时间结束")
|
||||
private Date updateTimeEnd;
|
||||
|
||||
@Schema(description ="是否删除 :0 未删除,1已删除")
|
||||
private Integer deleteFlag;
|
||||
|
||||
}
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
package com.kexue.skills.entity.dto;
|
||||
|
||||
import com.kexue.skills.entity.base.BaseQueryDto;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* (PointsTransaction)查询条件封装类
|
||||
* 积分交易记录查询条件
|
||||
*
|
||||
* @author 王志维
|
||||
* @since 2025-02-21 23:01:48
|
||||
*/
|
||||
@Data
|
||||
public class PointsTransactionDto extends BaseQueryDto {
|
||||
|
||||
@Schema(description ="主键ID")
|
||||
private Long transactionId;
|
||||
|
||||
@Schema(description ="用户ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description ="用户名")
|
||||
private String userName;
|
||||
|
||||
@Schema(description ="交易类型:1.获得积分 2.使用积分")
|
||||
private Integer transactionType;
|
||||
|
||||
@Schema(description ="积分数量")
|
||||
private Integer points;
|
||||
|
||||
@Schema(description ="交易前积分")
|
||||
private Integer beforePoints;
|
||||
|
||||
@Schema(description ="交易后积分")
|
||||
private Integer afterPoints;
|
||||
|
||||
@Schema(description ="状态:1.成功 2.失败")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description ="交易单号")
|
||||
private String transactionNo;
|
||||
|
||||
@Schema(description ="支付方式:1.微信 2.支付宝 3.余额支付")
|
||||
private Integer payType;
|
||||
|
||||
@Schema(description ="业务ID")
|
||||
private Long businessId;
|
||||
|
||||
@Schema(description ="业务类型")
|
||||
private String businessType;
|
||||
|
||||
@Schema(description ="备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description ="创建时间开始")
|
||||
private Date createTimeStart;
|
||||
|
||||
@Schema(description ="创建时间结束")
|
||||
private Date createTimeEnd;
|
||||
|
||||
@Schema(description ="更新时间开始")
|
||||
private Date updateTimeStart;
|
||||
|
||||
@Schema(description ="更新时间结束")
|
||||
private Date updateTimeEnd;
|
||||
|
||||
@Schema(description ="是否删除 :0 未删除,1已删除")
|
||||
private Integer deleteFlag;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.kexue.skills.entity.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Token消费转换DTO
|
||||
* 用于传输token消费转换的参数
|
||||
*
|
||||
* @author 王志维
|
||||
* @since 2026-03-26 15:00:00
|
||||
*/
|
||||
@Data
|
||||
public class TokenConsumptionDto {
|
||||
|
||||
@Schema(description ="用户ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description ="输入token")
|
||||
private Integer inputToken;
|
||||
|
||||
@Schema(description ="输出token")
|
||||
private Integer outputToken;
|
||||
|
||||
@Schema(description ="合计tokens")
|
||||
private Integer totalTokens;
|
||||
|
||||
@Schema(description ="处理的模型名称")
|
||||
private String modelName;
|
||||
|
||||
@Schema(description ="对应回答的问题或需求")
|
||||
private String question;
|
||||
|
||||
@Schema(description ="交易单号")
|
||||
private String transactionNo;
|
||||
|
||||
@Schema(description ="业务ID")
|
||||
private Long businessId;
|
||||
|
||||
@Schema(description ="业务类型")
|
||||
private String businessType;
|
||||
|
||||
@Schema(description ="备注")
|
||||
private String remark;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.kexue.skills.entity.dto;
|
||||
|
||||
import com.kexue.skills.entity.base.BaseQueryDto;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 提现记录DTO类
|
||||
*
|
||||
* @author 王志维
|
||||
* @since 2026-03-25
|
||||
*/
|
||||
@Data
|
||||
public class WithdrawalRecordDto extends BaseQueryDto {
|
||||
@Schema(description ="用户ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description ="用户名")
|
||||
private String userName;
|
||||
|
||||
@Schema(description ="提现金额")
|
||||
private BigDecimal withdrawalAmount;
|
||||
|
||||
@Schema(description ="状态")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description ="提现单号")
|
||||
private String withdrawalNo;
|
||||
|
||||
@Schema(description ="银行名称")
|
||||
private String bankName;
|
||||
|
||||
@Schema(description ="银行账号")
|
||||
private String bankAccount;
|
||||
|
||||
@Schema(description ="持卡人姓名")
|
||||
private String bankCardholder;
|
||||
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package com.kexue.skills.entity.request;
|
||||
|
||||
import com.kexue.skills.entity.Account;
|
||||
import com.kexue.skills.entity.PointsAccount;
|
||||
import com.kexue.skills.entity.SysUser;
|
||||
import lombok.Data;
|
||||
|
||||
|
|
@ -35,11 +34,6 @@ public class LoginUser {
|
|||
*/
|
||||
private Account account;
|
||||
|
||||
/**
|
||||
* 积分信息
|
||||
*/
|
||||
private PointsAccount pointsAccount;
|
||||
|
||||
/**
|
||||
* token
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import java.io.Serializable;
|
|||
@ApiModel(value = "重置密码请求参数")
|
||||
public class ResetPasswordDto implements Serializable {
|
||||
|
||||
@Schema(description ="用户名")
|
||||
@Schema(description ="管理员用户名")
|
||||
private String userName;
|
||||
|
||||
@Schema(description ="旧密码")
|
||||
|
|
|
|||
|
|
@ -70,4 +70,12 @@ public interface AccountTransactionMapper {
|
|||
* @return 影响行数
|
||||
*/
|
||||
int deleteById(Long transactionId);
|
||||
|
||||
/**
|
||||
* 通过用户ID查询交易记录
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 交易记录列表
|
||||
*/
|
||||
List<AccountTransaction> queryByUserId(Long userId);
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
package com.kexue.skills.mapper;
|
||||
|
||||
import com.kexue.skills.entity.ModelPrice;
|
||||
import com.kexue.skills.entity.dto.ModelPriceDto;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* (ModelPrice)表数据库访问层
|
||||
* 大模型Token价格表
|
||||
*
|
||||
* @author 王志维
|
||||
* @since 2026-03-26 10:15:00
|
||||
*/
|
||||
@Mapper
|
||||
public interface ModelPriceMapper {
|
||||
/**
|
||||
* 分页查询
|
||||
*
|
||||
* @param queryDto 筛选条件
|
||||
* @return 查询结果
|
||||
*/
|
||||
List<ModelPrice> getPageList(ModelPriceDto queryDto);
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*
|
||||
* @param queryDto 筛选条件
|
||||
* @return 查询结果
|
||||
*/
|
||||
List<ModelPrice> getList(ModelPriceDto queryDto);
|
||||
|
||||
/**
|
||||
* 通过主键查询单条数据
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 实例对象
|
||||
*/
|
||||
ModelPrice queryById(Long id);
|
||||
|
||||
/**
|
||||
* 通过模型名称查询数据
|
||||
*
|
||||
* @param modelName 模型名称
|
||||
* @return 实例对象
|
||||
*/
|
||||
ModelPrice queryByModelName(String modelName);
|
||||
|
||||
/**
|
||||
* 新增数据
|
||||
*
|
||||
* @param modelPrice 实例对象
|
||||
* @return 影响行数
|
||||
*/
|
||||
int insert(ModelPrice modelPrice);
|
||||
|
||||
/**
|
||||
* 更新数据
|
||||
*
|
||||
* @param modelPrice 实例对象
|
||||
* @return 影响行数
|
||||
*/
|
||||
int update(ModelPrice modelPrice);
|
||||
|
||||
/**
|
||||
* 通过主键删除数据
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 影响行数
|
||||
*/
|
||||
int deleteById(Long id);
|
||||
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
package com.kexue.skills.mapper;
|
||||
|
||||
import com.kexue.skills.entity.PointsAccount;
|
||||
import com.kexue.skills.entity.dto.PointsAccountDto;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* (PointsAccount)表数据库访问层
|
||||
*
|
||||
* @author 王志维
|
||||
* @since 2025-02-21 23:01:48
|
||||
*/
|
||||
@Mapper
|
||||
public interface PointsAccountMapper {
|
||||
/**
|
||||
* 分页查询
|
||||
*
|
||||
* @param queryDto 筛选条件
|
||||
* @return 查询结果
|
||||
*/
|
||||
List<PointsAccount> getPageList(PointsAccountDto queryDto);
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*
|
||||
* @param queryDto 筛选条件
|
||||
* @return 查询结果
|
||||
*/
|
||||
List<PointsAccount> getList(PointsAccountDto queryDto);
|
||||
|
||||
/**
|
||||
* 通过主键查询单条数据
|
||||
*
|
||||
* @param accountId 主键
|
||||
* @return 实例对象
|
||||
*/
|
||||
PointsAccount queryById(Long accountId);
|
||||
|
||||
/**
|
||||
* 通过用户ID查询积分账户
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 实例对象
|
||||
*/
|
||||
PointsAccount queryByUserId(Long userId);
|
||||
|
||||
/**
|
||||
* 新增数据
|
||||
*
|
||||
* @param pointsAccount 实例对象
|
||||
* @return 影响行数
|
||||
*/
|
||||
int insert(PointsAccount pointsAccount);
|
||||
|
||||
/**
|
||||
* 更新数据
|
||||
*
|
||||
* @param pointsAccount 实例对象
|
||||
* @return 影响行数
|
||||
*/
|
||||
int update(PointsAccount pointsAccount);
|
||||
|
||||
/**
|
||||
* 更新积分
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param points 变动积分
|
||||
* @param type 变动类型:1.增加 2.减少
|
||||
* @return 影响行数
|
||||
*/
|
||||
int updatePoints(@Param("userId") Long userId, @Param("points") Integer points, @Param("type") Integer type);
|
||||
|
||||
/**
|
||||
* 通过主键逻辑删除
|
||||
*
|
||||
* @param accountId 主键
|
||||
* @return 影响行数
|
||||
*/
|
||||
int logicDeleteById(@Param("accountId") Long accountId, @Param("updateBy") String updateBy);
|
||||
|
||||
/**
|
||||
* 通过主键物理删除
|
||||
*
|
||||
* @param accountId 主键
|
||||
* @return 影响行数
|
||||
*/
|
||||
int deleteById(Long accountId);
|
||||
}
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
package com.kexue.skills.mapper;
|
||||
|
||||
import com.kexue.skills.entity.PointsTransaction;
|
||||
import com.kexue.skills.entity.dto.PointsTransactionDto;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* (PointsTransaction)表数据库访问层
|
||||
*
|
||||
* @author 王志维
|
||||
* @since 2025-02-21 23:01:48
|
||||
*/
|
||||
@Mapper
|
||||
public interface PointsTransactionMapper {
|
||||
/**
|
||||
* 分页查询
|
||||
*
|
||||
* @param queryDto 筛选条件
|
||||
* @return 查询结果
|
||||
*/
|
||||
List<PointsTransaction> getPageList(PointsTransactionDto queryDto);
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*
|
||||
* @param queryDto 筛选条件
|
||||
* @return 查询结果
|
||||
*/
|
||||
List<PointsTransaction> getList(PointsTransactionDto queryDto);
|
||||
|
||||
/**
|
||||
* 通过主键查询单条数据
|
||||
*
|
||||
* @param transactionId 主键
|
||||
* @return 实例对象
|
||||
*/
|
||||
PointsTransaction queryById(Long transactionId);
|
||||
|
||||
/**
|
||||
* 新增数据
|
||||
*
|
||||
* @param transaction 实例对象
|
||||
* @return 影响行数
|
||||
*/
|
||||
int insert(PointsTransaction transaction);
|
||||
|
||||
/**
|
||||
* 更新数据
|
||||
*
|
||||
* @param transaction 实例对象
|
||||
* @return 影响行数
|
||||
*/
|
||||
int update(PointsTransaction transaction);
|
||||
|
||||
/**
|
||||
* 通过主键逻辑删除
|
||||
*
|
||||
* @param transactionId 主键
|
||||
* @return 影响行数
|
||||
*/
|
||||
int logicDeleteById(@Param("transactionId") Long transactionId, @Param("updateBy") String updateBy);
|
||||
|
||||
/**
|
||||
* 通过主键物理删除
|
||||
*
|
||||
* @param transactionId 主键
|
||||
* @return 影响行数
|
||||
*/
|
||||
int deleteById(Long transactionId);
|
||||
}
|
||||
|
|
@ -84,4 +84,12 @@ public interface SysUserRoleMapper {
|
|||
*/
|
||||
int deleteById(Long roleId);
|
||||
|
||||
/**
|
||||
* 通过用户 ID 查询角色编码列表(关联查询 sys_role 和 sys_user_role)
|
||||
*
|
||||
* @param userId 用户 ID
|
||||
* @return 角色编码列表
|
||||
*/
|
||||
List<String> queryRoleCodesByUserId(@Param("userId") Long userId);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
package com.kexue.skills.mapper;
|
||||
|
||||
import com.kexue.skills.entity.WithdrawalRecord;
|
||||
import com.kexue.skills.entity.dto.WithdrawalRecordDto;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 提现记录Mapper接口
|
||||
*
|
||||
* @author 王志维
|
||||
* @since 2026-03-25
|
||||
*/
|
||||
public interface WithdrawalRecordMapper {
|
||||
/**
|
||||
* 通过ID查询单条数据
|
||||
*
|
||||
* @param recordId 主键
|
||||
* @return 实例对象
|
||||
*/
|
||||
WithdrawalRecord queryById(Long recordId);
|
||||
|
||||
/**
|
||||
* 通过用户ID查询提现记录
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 实例对象
|
||||
*/
|
||||
List<WithdrawalRecord> queryByUserId(Long userId);
|
||||
|
||||
/**
|
||||
* 通过提现单号查询提现记录
|
||||
*
|
||||
* @param withdrawalNo 提现单号
|
||||
* @return 实例对象
|
||||
*/
|
||||
WithdrawalRecord queryByWithdrawalNo(String withdrawalNo);
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*
|
||||
* @param queryDto 筛选条件
|
||||
* @return 查询结果
|
||||
*/
|
||||
List<WithdrawalRecord> getPageList(WithdrawalRecordDto queryDto);
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*
|
||||
* @param queryDto 筛选条件
|
||||
* @return 查询结果
|
||||
*/
|
||||
List<WithdrawalRecord> getList(WithdrawalRecordDto queryDto);
|
||||
|
||||
/**
|
||||
* 新增数据
|
||||
*
|
||||
* @param withdrawalRecord 实例对象
|
||||
* @return 影响行数
|
||||
*/
|
||||
int insert(WithdrawalRecord withdrawalRecord);
|
||||
|
||||
/**
|
||||
* 更新数据
|
||||
*
|
||||
* @param withdrawalRecord 实例对象
|
||||
* @return 影响行数
|
||||
*/
|
||||
int update(WithdrawalRecord withdrawalRecord);
|
||||
|
||||
/**
|
||||
* 更新提现状态
|
||||
*
|
||||
* @param params 参数
|
||||
* @return 影响行数
|
||||
*/
|
||||
int updateStatus(Map<String, Object> params);
|
||||
|
||||
/**
|
||||
* 通过主键逻辑删除
|
||||
*
|
||||
* @param params 参数
|
||||
* @return 影响行数
|
||||
*/
|
||||
int logicDeleteById(Map<String, Object> params);
|
||||
|
||||
/**
|
||||
* 通过主键物理删除
|
||||
*
|
||||
* @param recordId 主键
|
||||
* @return 影响行数
|
||||
*/
|
||||
int deleteById(Long recordId);
|
||||
}
|
||||
|
|
@ -67,6 +67,20 @@ public interface AccountService extends BaseService {
|
|||
*
|
||||
* @param userId 用户ID
|
||||
* @param amount 增加金额
|
||||
* @param isWithdrawable 是否可提现
|
||||
* @param transactionNo 交易单号
|
||||
* @param businessId 业务ID
|
||||
* @param businessType 业务类型
|
||||
* @param remark 备注
|
||||
* @return 影响行数
|
||||
*/
|
||||
int addBalance(Long userId, BigDecimal amount, boolean isWithdrawable, String transactionNo, Long businessId, String businessType, String remark);
|
||||
|
||||
/**
|
||||
* 增加账户余额(默认不可提现)
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param amount 增加金额
|
||||
* @param transactionNo 交易单号
|
||||
* @param businessId 业务ID
|
||||
* @param businessType 业务类型
|
||||
|
|
@ -88,6 +102,49 @@ public interface AccountService extends BaseService {
|
|||
*/
|
||||
int reduceBalance(Long userId, BigDecimal amount, String transactionNo, Long businessId, String businessType, String remark);
|
||||
|
||||
/**
|
||||
* 减少账户余额(token消费转换)
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param inputToken 输入token
|
||||
* @param outputToken 输出token
|
||||
* @param totalTokens 合计tokens
|
||||
* @param modelName 处理的模型名称
|
||||
* @param question 对应回答的问题或需求
|
||||
* @param transactionNo 交易单号
|
||||
* @param businessId 业务ID
|
||||
* @param businessType 业务类型
|
||||
* @param remark 备注
|
||||
* @return 影响行数
|
||||
*/
|
||||
int reduceBalanceWithToken(Long userId, Integer inputToken, Integer outputToken, Integer totalTokens, String modelName, String question, String transactionNo, Long businessId, String businessType, String remark);
|
||||
|
||||
/**
|
||||
* 增加账户余额(签到奖励token转换)
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param amount 增加金额
|
||||
* @param transactionNo 交易单号
|
||||
* @param businessId 业务ID
|
||||
* @param businessType 业务类型
|
||||
* @param remark 备注
|
||||
* @return 影响行数
|
||||
*/
|
||||
int addSignInBalance(Long userId, BigDecimal amount, String transactionNo, Long businessId, String businessType, String remark);
|
||||
|
||||
/**
|
||||
* 给用户赠送金额(不可提现)
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param amount 赠送金额
|
||||
* @param transactionNo 交易单号
|
||||
* @param businessId 业务ID
|
||||
* @param businessType 业务类型
|
||||
* @param remark 备注
|
||||
* @return 影响行数
|
||||
*/
|
||||
int addGiftBalance(Long userId, BigDecimal amount, String transactionNo, Long businessId, String businessType, String remark);
|
||||
|
||||
/**
|
||||
* 通过主键逻辑删除
|
||||
*
|
||||
|
|
@ -104,4 +161,12 @@ public interface AccountService extends BaseService {
|
|||
* @return 影响行数
|
||||
*/
|
||||
int deleteById(Long accountId);
|
||||
|
||||
/**
|
||||
* 获取用户交易记录
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 交易记录列表
|
||||
*/
|
||||
List<com.kexue.skills.entity.AccountTransaction> getTransactions(Long userId);
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
package com.kexue.skills.service;
|
||||
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.kexue.skills.entity.ModelPrice;
|
||||
import com.kexue.skills.entity.dto.ModelPriceDto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* (ModelPrice)表服务接口
|
||||
* 大模型Token价格表
|
||||
*
|
||||
* @author 王志维
|
||||
* @since 2026-03-26 10:15:00
|
||||
*/
|
||||
public interface ModelPriceService extends BaseService {
|
||||
/**
|
||||
* 分页查询
|
||||
*
|
||||
* @param queryDto 筛选条件
|
||||
* @return 查询结果
|
||||
*/
|
||||
PageInfo<ModelPrice> getPageList(ModelPriceDto queryDto);
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*
|
||||
* @param queryDto 筛选条件
|
||||
* @return 查询结果
|
||||
*/
|
||||
List<ModelPrice> getList(ModelPriceDto queryDto);
|
||||
|
||||
/**
|
||||
* 通过主键查询单条数据
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 实例对象
|
||||
*/
|
||||
ModelPrice queryById(Long id);
|
||||
|
||||
/**
|
||||
* 通过模型名称查询数据
|
||||
*
|
||||
* @param modelName 模型名称
|
||||
* @return 实例对象
|
||||
*/
|
||||
ModelPrice queryByModelName(String modelName);
|
||||
|
||||
/**
|
||||
* 新增数据
|
||||
*
|
||||
* @param modelPrice 实例对象
|
||||
* @return 实例对象
|
||||
*/
|
||||
ModelPrice insert(ModelPrice modelPrice);
|
||||
|
||||
/**
|
||||
* 更新数据
|
||||
*
|
||||
* @param modelPrice 实例对象
|
||||
* @return 实例对象
|
||||
*/
|
||||
ModelPrice update(ModelPrice modelPrice);
|
||||
|
||||
/**
|
||||
* 通过主键删除数据
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 影响行数
|
||||
*/
|
||||
int deleteById(Long id);
|
||||
|
||||
}
|
||||
|
|
@ -11,9 +11,10 @@ public interface PayService {
|
|||
/**
|
||||
* 创建微信支付订单
|
||||
* @param order 支付订单信息
|
||||
* @param ipAddress 用户真实 IP 地址(从 Controller 传入)
|
||||
* @return 微信支付参数
|
||||
*/
|
||||
Map<String, String> createWechatPay(PaymentOrder order);
|
||||
Map<String, String> createWechatPay(PaymentOrder order, String ipAddress);
|
||||
|
||||
/**
|
||||
* 处理微信支付回调
|
||||
|
|
|
|||
|
|
@ -1,106 +0,0 @@
|
|||
package com.kexue.skills.service;
|
||||
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.kexue.skills.entity.PointsAccount;
|
||||
import com.kexue.skills.entity.dto.PointsAccountDto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* (PointsAccount)表服务接口
|
||||
*
|
||||
* @author 王志维
|
||||
* @since 2025-02-21 23:01:48
|
||||
*/
|
||||
public interface PointsAccountService extends BaseService {
|
||||
/**
|
||||
* 分页查询
|
||||
*
|
||||
* @param queryDto 筛选条件
|
||||
* @return 查询结果
|
||||
*/
|
||||
PageInfo<PointsAccount> getPageList(PointsAccountDto queryDto);
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*
|
||||
* @param queryDto 筛选条件
|
||||
* @return 查询结果
|
||||
*/
|
||||
List<PointsAccount> getList(PointsAccountDto queryDto);
|
||||
|
||||
/**
|
||||
* 通过主键查询单条数据
|
||||
*
|
||||
* @param accountId 主键
|
||||
* @return 实例对象
|
||||
*/
|
||||
PointsAccount queryById(Long accountId);
|
||||
|
||||
/**
|
||||
* 通过用户ID查询积分账户
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 实例对象
|
||||
*/
|
||||
PointsAccount queryByUserId(Long userId);
|
||||
|
||||
/**
|
||||
* 新增数据
|
||||
*
|
||||
* @param pointsAccount 实例对象
|
||||
* @return 实例对象
|
||||
*/
|
||||
PointsAccount insert(PointsAccount pointsAccount);
|
||||
|
||||
/**
|
||||
* 更新数据
|
||||
*
|
||||
* @param pointsAccount 实例对象
|
||||
* @return 实例对象
|
||||
*/
|
||||
PointsAccount update(PointsAccount pointsAccount);
|
||||
|
||||
/**
|
||||
* 增加积分
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param points 增加积分
|
||||
* @param transactionNo 交易单号
|
||||
* @param businessId 业务ID
|
||||
* @param businessType 业务类型
|
||||
* @param remark 备注
|
||||
* @return 影响行数
|
||||
*/
|
||||
int addPoints(Long userId, Integer points, String transactionNo, Long businessId, String businessType, String remark);
|
||||
|
||||
/**
|
||||
* 减少积分
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param points 减少积分
|
||||
* @param transactionNo 交易单号
|
||||
* @param businessId 业务ID
|
||||
* @param businessType 业务类型
|
||||
* @param remark 备注
|
||||
* @return 影响行数
|
||||
*/
|
||||
int reducePoints(Long userId, Integer points, String transactionNo, Long businessId, String businessType, String remark);
|
||||
|
||||
/**
|
||||
* 通过主键逻辑删除
|
||||
*
|
||||
* @param accountId 主键
|
||||
* @param updateBy 更新人
|
||||
* @return 影响行数
|
||||
*/
|
||||
int logicDeleteById(Long accountId, String updateBy);
|
||||
|
||||
/**
|
||||
* 通过主键物理删除
|
||||
*
|
||||
* @param accountId 主键
|
||||
* @return 影响行数
|
||||
*/
|
||||
int deleteById(Long accountId);
|
||||
}
|
||||
|
|
@ -3,11 +3,7 @@ import com.github.pagehelper.PageInfo;
|
|||
|
||||
import com.kexue.skills.entity.SysUser;
|
||||
import com.kexue.skills.entity.dto.SysUserDto;
|
||||
import com.kexue.skills.entity.request.LoginDto;
|
||||
import com.kexue.skills.entity.request.LoginUserDto;
|
||||
import com.kexue.skills.entity.request.PhoneLoginDto;
|
||||
import com.kexue.skills.entity.request.ResetPasswordDto;
|
||||
import com.kexue.skills.entity.request.SysUserUpdateDto;
|
||||
import com.kexue.skills.entity.request.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -78,9 +74,7 @@ public interface SysUserService extends BaseService {
|
|||
|
||||
LoginUserDto login(LoginDto loginDto);
|
||||
|
||||
boolean resetPassword(ResetPasswordDto resetPasswordDto);
|
||||
|
||||
boolean resetPasswordByAdmin(ResetPasswordDto resetPasswordDto);
|
||||
boolean resetPassword(ResetPwdDto resetPasswordDto);
|
||||
|
||||
SysUser getByUsername(String username);
|
||||
|
||||
|
|
@ -135,4 +129,12 @@ public interface SysUserService extends BaseService {
|
|||
* @return 会话信息
|
||||
*/
|
||||
com.kexue.skills.entity.dto.SessionDto createSession(Long userId);
|
||||
|
||||
/**
|
||||
* 查询用户角色列表
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 角色编码列表
|
||||
*/
|
||||
List<String> queryUserRoles(Long userId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,119 @@
|
|||
package com.kexue.skills.service;
|
||||
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.kexue.skills.entity.WithdrawalRecord;
|
||||
import com.kexue.skills.entity.dto.WithdrawalRecordDto;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 提现记录Service接口
|
||||
*
|
||||
* @author 王志维
|
||||
* @since 2026-03-25
|
||||
*/
|
||||
public interface WithdrawalRecordService {
|
||||
/**
|
||||
* 分页查询
|
||||
*
|
||||
* @param queryDto 筛选条件
|
||||
* @return 查询结果
|
||||
*/
|
||||
PageInfo<WithdrawalRecord> getPageList(WithdrawalRecordDto queryDto);
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*
|
||||
* @param queryDto 筛选条件
|
||||
* @return 查询结果
|
||||
*/
|
||||
List<WithdrawalRecord> getList(WithdrawalRecordDto queryDto);
|
||||
|
||||
/**
|
||||
* 通过主键查询单条数据
|
||||
*
|
||||
* @param recordId 主键
|
||||
* @return 实例对象
|
||||
*/
|
||||
WithdrawalRecord queryById(Long recordId);
|
||||
|
||||
/**
|
||||
* 通过用户ID查询提现记录
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 实例对象
|
||||
*/
|
||||
List<WithdrawalRecord> queryByUserId(Long userId);
|
||||
|
||||
/**
|
||||
* 通过提现单号查询提现记录
|
||||
*
|
||||
* @param withdrawalNo 提现单号
|
||||
* @return 实例对象
|
||||
*/
|
||||
WithdrawalRecord queryByWithdrawalNo(String withdrawalNo);
|
||||
|
||||
/**
|
||||
* 新增数据
|
||||
*
|
||||
* @param withdrawalRecord 实例对象
|
||||
* @return 实例对象
|
||||
*/
|
||||
WithdrawalRecord insert(WithdrawalRecord withdrawalRecord);
|
||||
|
||||
/**
|
||||
* 更新数据
|
||||
*
|
||||
* @param withdrawalRecord 实例对象
|
||||
* @return 实例对象
|
||||
*/
|
||||
WithdrawalRecord update(WithdrawalRecord withdrawalRecord);
|
||||
|
||||
/**
|
||||
* 更新提现状态
|
||||
*
|
||||
* @param recordId 记录ID
|
||||
* @param status 状态
|
||||
* @return 影响行数
|
||||
*/
|
||||
int updateStatus(Long recordId, Integer status);
|
||||
|
||||
/**
|
||||
* 通过主键逻辑删除
|
||||
*
|
||||
* @param recordId 主键
|
||||
* @param updateBy 更新人
|
||||
* @return 影响行数
|
||||
*/
|
||||
int logicDeleteById(Long recordId, String updateBy);
|
||||
|
||||
/**
|
||||
* 通过主键物理删除
|
||||
*
|
||||
* @param recordId 主键
|
||||
* @return 影响行数
|
||||
*/
|
||||
int deleteById(Long recordId);
|
||||
|
||||
/**
|
||||
* 提交提现申请
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param amount 提现金额
|
||||
* @param bankName 银行名称
|
||||
* @param bankAccount 银行账号
|
||||
* @param bankCardholder 持卡人姓名
|
||||
* @param remark 备注
|
||||
* @return 提现记录
|
||||
*/
|
||||
WithdrawalRecord submitWithdrawal(Long userId, java.math.BigDecimal amount, String bankName, String bankAccount, String bankCardholder, String remark);
|
||||
|
||||
/**
|
||||
* 处理提现
|
||||
*
|
||||
* @param recordId 记录ID
|
||||
* @param status 状态
|
||||
* @param remark 备注
|
||||
* @return 影响行数
|
||||
*/
|
||||
int processWithdrawal(Long recordId, Integer status, String remark);
|
||||
}
|
||||
|
|
@ -10,6 +10,8 @@ import com.kexue.skills.exception.BizException;
|
|||
import com.kexue.skills.mapper.AccountMapper;
|
||||
import com.kexue.skills.mapper.AccountTransactionMapper;
|
||||
import com.kexue.skills.service.AccountService;
|
||||
import com.kexue.skills.service.ModelPriceService;
|
||||
import com.kexue.skills.entity.ModelPrice;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -33,6 +35,9 @@ public class AccountServiceImpl implements AccountService {
|
|||
@Resource
|
||||
private AccountTransactionMapper accountTransactionMapper;
|
||||
|
||||
@Resource
|
||||
private ModelPriceService modelPriceService;
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*
|
||||
|
|
@ -96,6 +101,12 @@ public class AccountServiceImpl implements AccountService {
|
|||
if (account.getBalance() == null) {
|
||||
account.setBalance(BigDecimal.ZERO);
|
||||
}
|
||||
if (account.getWithdrawableBalance() == null) {
|
||||
account.setWithdrawableBalance(BigDecimal.ZERO);
|
||||
}
|
||||
if (account.getNonWithdrawableBalance() == null) {
|
||||
account.setNonWithdrawableBalance(BigDecimal.ZERO);
|
||||
}
|
||||
if (account.getFrozenAmount() == null) {
|
||||
account.setFrozenAmount(BigDecimal.ZERO);
|
||||
}
|
||||
|
|
@ -124,6 +135,7 @@ public class AccountServiceImpl implements AccountService {
|
|||
*
|
||||
* @param userId 用户ID
|
||||
* @param amount 增加金额
|
||||
* @param isWithdrawable 是否可提现
|
||||
* @param transactionNo 交易单号
|
||||
* @param businessId 业务ID
|
||||
* @param businessType 业务类型
|
||||
|
|
@ -131,7 +143,7 @@ public class AccountServiceImpl implements AccountService {
|
|||
* @return 影响行数
|
||||
*/
|
||||
@Override
|
||||
public int addBalance(Long userId, BigDecimal amount, String transactionNo, Long businessId, String businessType, String remark) {
|
||||
public int addBalance(Long userId, BigDecimal amount, boolean isWithdrawable, String transactionNo, Long businessId, String businessType, String remark) {
|
||||
// 1. 查询账户信息
|
||||
Account account = this.queryByUserId(userId);
|
||||
if (account == null) {
|
||||
|
|
@ -139,6 +151,8 @@ public class AccountServiceImpl implements AccountService {
|
|||
account = new Account();
|
||||
account.setUserId(userId);
|
||||
account.setBalance(BigDecimal.ZERO);
|
||||
account.setWithdrawableBalance(BigDecimal.ZERO);
|
||||
account.setNonWithdrawableBalance(BigDecimal.ZERO);
|
||||
account.setFrozenAmount(BigDecimal.ZERO);
|
||||
this.insert(account);
|
||||
}
|
||||
|
|
@ -157,10 +171,36 @@ public class AccountServiceImpl implements AccountService {
|
|||
transaction.setBusinessId(businessId);
|
||||
transaction.setBusinessType(businessType);
|
||||
transaction.setRemark(remark);
|
||||
transaction.setIsExpense(0); // 收入
|
||||
transaction.setIncomeType("recharge"); // 充值
|
||||
this.accountTransactionMapper.insert(transaction);
|
||||
|
||||
// 3. 更新账户余额
|
||||
return this.accountMapper.updateBalance(userId, amount, 1);
|
||||
if (isWithdrawable) {
|
||||
account.setWithdrawableBalance(account.getWithdrawableBalance().add(amount));
|
||||
} else {
|
||||
account.setNonWithdrawableBalance(account.getNonWithdrawableBalance().add(amount));
|
||||
}
|
||||
account.setBalance(account.getBalance().add(amount));
|
||||
account.setUpdateTime(new Date());
|
||||
this.update(account);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加账户余额(默认不可提现)
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param amount 增加金额
|
||||
* @param transactionNo 交易单号
|
||||
* @param businessId 业务ID
|
||||
* @param businessType 业务类型
|
||||
* @param remark 备注
|
||||
* @return 影响行数
|
||||
*/
|
||||
@Override
|
||||
public int addBalance(Long userId, BigDecimal amount, String transactionNo, Long businessId, String businessType, String remark) {
|
||||
return addBalance(userId, amount, false, transactionNo, businessId, businessType, remark);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -197,12 +237,193 @@ public class AccountServiceImpl implements AccountService {
|
|||
transaction.setBusinessId(businessId);
|
||||
transaction.setBusinessType(businessType);
|
||||
transaction.setRemark(remark);
|
||||
transaction.setIsExpense(1); // 支出
|
||||
this.accountTransactionMapper.insert(transaction);
|
||||
|
||||
// 4. 更新账户余额
|
||||
return this.accountMapper.updateBalance(userId, amount, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 减少账户余额(token消费转换)
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param amount 减少金额
|
||||
* @param inputToken 输入token
|
||||
* @param outputToken 输出token
|
||||
* @param totalTokens 合计tokens
|
||||
* @param modelName 处理的模型名称
|
||||
* @param question 对应回答的问题或需求
|
||||
* @param transactionNo 交易单号
|
||||
* @param businessId 业务ID
|
||||
* @param businessType 业务类型
|
||||
* @param remark 备注
|
||||
* @return 影响行数
|
||||
*/
|
||||
@Override
|
||||
public int reduceBalanceWithToken(Long userId, Integer inputToken, Integer outputToken, Integer totalTokens, String modelName, String question, String transactionNo, Long businessId, String businessType, String remark) {
|
||||
// 1. 查询账户信息
|
||||
Account account = this.queryByUserId(userId);
|
||||
Assert.notNull(account, "账户不存在");
|
||||
|
||||
// 2. 查询模型价格信息
|
||||
ModelPrice modelPrice = modelPriceService.queryByModelName(modelName);
|
||||
Assert.notNull(modelPrice, "模型价格信息不存在");
|
||||
|
||||
// 3. 计算金额
|
||||
// 输入token费用:输入token数量 / inputPerCent,不足1分按1分计算
|
||||
long inputFee = inputToken / modelPrice.getInputPerCent();
|
||||
if (inputToken % modelPrice.getInputPerCent() > 0) {
|
||||
inputFee += 1;
|
||||
}
|
||||
|
||||
// 输出token费用:输出token数量 / outputPerCent,不足1分按1分计算
|
||||
long outputFee = outputToken / modelPrice.getOutputPerCent();
|
||||
if (outputToken % modelPrice.getOutputPerCent() > 0) {
|
||||
outputFee += 1;
|
||||
}
|
||||
|
||||
// 总费用(分)
|
||||
long totalFee = inputFee + outputFee;
|
||||
// 转换为元
|
||||
BigDecimal amount = BigDecimal.valueOf(totalFee).divide(BigDecimal.valueOf(100));
|
||||
|
||||
// 4. 检查余额是否足够
|
||||
Assert.isTrue(account.getBalance().compareTo(amount) >= 0, "账户余额不足");
|
||||
|
||||
// 5. 保存交易记录
|
||||
AccountTransaction transaction = new AccountTransaction();
|
||||
transaction.setUserId(userId);
|
||||
transaction.setUserName(account.getUserName());
|
||||
transaction.setTransactionType(3); // 购买内容
|
||||
transaction.setAmount(amount);
|
||||
transaction.setBeforeBalance(account.getBalance());
|
||||
transaction.setAfterBalance(account.getBalance().subtract(amount));
|
||||
transaction.setStatus(1); // 成功
|
||||
transaction.setTransactionNo(transactionNo);
|
||||
transaction.setPayType(3); // 余额支付
|
||||
transaction.setBusinessId(businessId);
|
||||
transaction.setBusinessType(businessType);
|
||||
transaction.setRemark(remark);
|
||||
transaction.setIsExpense(1); // 支出
|
||||
transaction.setInputToken(inputToken);
|
||||
transaction.setOutputToken(outputToken);
|
||||
transaction.setTotalTokens(totalTokens);
|
||||
transaction.setModelName(modelName);
|
||||
transaction.setQuestion(question);
|
||||
this.accountTransactionMapper.insert(transaction);
|
||||
|
||||
// 6. 更新账户余额
|
||||
return this.accountMapper.updateBalance(userId, amount, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加账户余额(签到奖励token转换)
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param amount 增加金额
|
||||
* @param transactionNo 交易单号
|
||||
* @param businessId 业务ID
|
||||
* @param businessType 业务类型
|
||||
* @param remark 备注
|
||||
* @return 影响行数
|
||||
*/
|
||||
@Override
|
||||
public int addSignInBalance(Long userId, BigDecimal amount, String transactionNo, Long businessId, String businessType, String remark) {
|
||||
// 1. 查询账户信息
|
||||
Account account = this.queryByUserId(userId);
|
||||
if (account == null) {
|
||||
// 创建账户
|
||||
account = new Account();
|
||||
account.setUserId(userId);
|
||||
account.setBalance(BigDecimal.ZERO);
|
||||
account.setWithdrawableBalance(BigDecimal.ZERO);
|
||||
account.setNonWithdrawableBalance(BigDecimal.ZERO);
|
||||
account.setFrozenAmount(BigDecimal.ZERO);
|
||||
this.insert(account);
|
||||
}
|
||||
|
||||
// 2. 保存交易记录
|
||||
AccountTransaction transaction = new AccountTransaction();
|
||||
transaction.setUserId(userId);
|
||||
transaction.setUserName(account.getUserName());
|
||||
transaction.setTransactionType(5); // 签到奖励
|
||||
transaction.setAmount(amount);
|
||||
transaction.setBeforeBalance(account.getBalance());
|
||||
transaction.setAfterBalance(account.getBalance().add(amount));
|
||||
transaction.setStatus(1); // 成功
|
||||
transaction.setTransactionNo(transactionNo);
|
||||
transaction.setPayType(3); // 余额支付
|
||||
transaction.setBusinessId(businessId);
|
||||
transaction.setBusinessType(businessType);
|
||||
transaction.setRemark(remark);
|
||||
transaction.setIsExpense(0); // 收入
|
||||
transaction.setIncomeType("sign_in"); // 签到奖励
|
||||
this.accountTransactionMapper.insert(transaction);
|
||||
|
||||
// 3. 更新账户余额(签到奖励不可提现)
|
||||
account.setNonWithdrawableBalance(account.getNonWithdrawableBalance().add(amount));
|
||||
account.setBalance(account.getBalance().add(amount));
|
||||
account.setUpdateTime(new Date());
|
||||
this.update(account);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给用户赠送金额(不可提现)
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param amount 赠送金额
|
||||
* @param transactionNo 交易单号
|
||||
* @param businessId 业务ID
|
||||
* @param businessType 业务类型
|
||||
* @param remark 备注
|
||||
* @return 影响行数
|
||||
*/
|
||||
@Override
|
||||
public int addGiftBalance(Long userId, BigDecimal amount, String transactionNo, Long businessId, String businessType, String remark) {
|
||||
// 1. 查询账户信息
|
||||
Account account = this.queryByUserId(userId);
|
||||
if (account == null) {
|
||||
// 创建账户
|
||||
account = new Account();
|
||||
account.setUserId(userId);
|
||||
account.setBalance(BigDecimal.ZERO);
|
||||
account.setWithdrawableBalance(BigDecimal.ZERO);
|
||||
account.setNonWithdrawableBalance(BigDecimal.ZERO);
|
||||
account.setFrozenAmount(BigDecimal.ZERO);
|
||||
this.insert(account);
|
||||
}
|
||||
|
||||
// 2. 保存交易记录
|
||||
AccountTransaction transaction = new AccountTransaction();
|
||||
transaction.setUserId(userId);
|
||||
transaction.setUserName(account.getUserName());
|
||||
transaction.setTransactionType(6); // 赠送
|
||||
transaction.setAmount(amount);
|
||||
transaction.setBeforeBalance(account.getBalance());
|
||||
transaction.setAfterBalance(account.getBalance().add(amount));
|
||||
transaction.setStatus(1); // 成功
|
||||
transaction.setTransactionNo(transactionNo);
|
||||
transaction.setPayType(3); // 余额支付
|
||||
transaction.setBusinessId(businessId);
|
||||
transaction.setBusinessType(businessType);
|
||||
transaction.setRemark(remark);
|
||||
transaction.setIsExpense(0); // 收入
|
||||
transaction.setIncomeType("gift"); // 赠送
|
||||
this.accountTransactionMapper.insert(transaction);
|
||||
|
||||
// 3. 更新账户余额(赠送金额不可提现)
|
||||
if (account.getNonWithdrawableBalance() == null){
|
||||
account.setNonWithdrawableBalance(BigDecimal.ZERO);
|
||||
}
|
||||
account.setNonWithdrawableBalance(account.getNonWithdrawableBalance().add(amount));
|
||||
account.setBalance(account.getBalance().add(amount));
|
||||
account.setUpdateTime(new Date());
|
||||
this.update(account);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过主键逻辑删除
|
||||
*
|
||||
|
|
@ -225,4 +446,15 @@ public class AccountServiceImpl implements AccountService {
|
|||
public int deleteById(Long accountId) {
|
||||
return this.accountMapper.deleteById(accountId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户交易记录
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 交易记录列表
|
||||
*/
|
||||
@Override
|
||||
public List<com.kexue.skills.entity.AccountTransaction> getTransactions(Long userId) {
|
||||
return this.accountTransactionMapper.queryByUserId(userId);
|
||||
}
|
||||
}
|
||||
|
|
@ -775,13 +775,19 @@ public class CmsContentServiceImpl implements CmsContentService {
|
|||
|
||||
// 转换为Map<String, Object>
|
||||
Map<String, Object> row = new HashMap<>();
|
||||
|
||||
|
||||
|
||||
for (int i = 0; i < headerList.size() && i < rowList.size(); i++) {
|
||||
row.put(headerList.get(i), rowList.get(i));
|
||||
}
|
||||
if (row.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// 打印映射后的数据
|
||||
System.out.println("映射后的数据:" + row);
|
||||
|
||||
CmsContent cmsContent = new CmsContent();
|
||||
|
||||
// 设置创建时间和更新时间
|
||||
|
|
@ -798,6 +804,7 @@ public class CmsContentServiceImpl implements CmsContentService {
|
|||
cmsContent.setLikeCount(0);
|
||||
cmsContent.setCommentCount(0);
|
||||
cmsContent.setSort(0);
|
||||
cmsContent.setIsOfficial(true); // 固定设置为1
|
||||
|
||||
// 读取Excel中的字段
|
||||
// content_id - 数据库自动生成,不需要读取
|
||||
|
|
@ -814,16 +821,16 @@ public class CmsContentServiceImpl implements CmsContentService {
|
|||
cmsContent.setTags(tags.replaceAll(" ",""));
|
||||
|
||||
cmsContent.setIcon(getStringValue(row, "icon"));
|
||||
cmsContent.setIsOfficial(getBooleanValue(row, "is_official"));
|
||||
cmsContent.setIsOfficial(true); // 固定设置为1
|
||||
cmsContent.setPrice(getBigDecimalValue(row, "price"));
|
||||
cmsContent.setLikeCount(getIntegerValue(row, "like_count"));
|
||||
cmsContent.setShareCount(getIntegerValue(row, "share_count"));
|
||||
cmsContent.setLikeCount(100 + new Random().nextInt(3000));
|
||||
cmsContent.setShareCount(100 + new Random().nextInt(1000));
|
||||
cmsContent.setContentType(getIntegerValue(row, "content_type"));
|
||||
cmsContent.setContent(getStringValue(row, "content"));
|
||||
cmsContent.setContentEn(getStringValue(row, "content_en"));
|
||||
cmsContent.setAuditStatus(getIntegerValue(row, "audit_status"));
|
||||
cmsContent.setPublishStatus(getIntegerValue(row, "publish_status"));
|
||||
cmsContent.setPublishTime(getDateValue(row, "publish_time"));
|
||||
cmsContent.setPublishTime(now); // 设置为与update_time一致
|
||||
cmsContent.setViewCount(getIntegerValue(row, "view_count"));
|
||||
cmsContent.setCommentCount(getIntegerValue(row, "comment_count"));
|
||||
cmsContent.setIsPaid(getIntegerValue(row, "is_paid"));
|
||||
|
|
@ -839,7 +846,7 @@ public class CmsContentServiceImpl implements CmsContentService {
|
|||
batchList.add(cmsContent);
|
||||
|
||||
// 达到批量大小或最后一行时,执行批量插入
|
||||
if (batchList.size() >= BATCH_SIZE || rowIndex == totalRows - 1) {
|
||||
if (batchList.size() >= BATCH_SIZE) {
|
||||
if (!batchList.isEmpty()) {
|
||||
// 执行批量插入
|
||||
this.cmsContentMapper.batchInsert(batchList);
|
||||
|
|
@ -854,6 +861,13 @@ public class CmsContentServiceImpl implements CmsContentService {
|
|||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理最后一批不足BATCH_SIZE的数据
|
||||
if (!batchList.isEmpty()) {
|
||||
this.cmsContentMapper.batchInsert(batchList);
|
||||
successCount += batchList.size();
|
||||
batchList.clear();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
|
@ -1029,4 +1043,6 @@ public class CmsContentServiceImpl implements CmsContentService {
|
|||
|
||||
return totalSuccessCount;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -12,7 +12,6 @@ import com.kexue.skills.mapper.ContentPurchaseMapper;
|
|||
import com.kexue.skills.service.AccountService;
|
||||
import com.kexue.skills.service.CmsContentService;
|
||||
import com.kexue.skills.service.ContentPurchaseService;
|
||||
import com.kexue.skills.service.PointsAccountService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -41,9 +40,6 @@ public class ContentPurchaseServiceImpl implements ContentPurchaseService {
|
|||
|
||||
@Resource
|
||||
private AccountService accountService;
|
||||
|
||||
@Resource
|
||||
private PointsAccountService pointsAccountService;
|
||||
|
||||
@Resource
|
||||
private LoginUserCacheUtil loginUserCacheUtil;
|
||||
|
|
@ -211,22 +207,6 @@ public class ContentPurchaseServiceImpl implements ContentPurchaseService {
|
|||
// 扣除用户余额
|
||||
this.accountService.reduceBalance(userId, price, transactionNo, contentId, "purchase_content", "购买内容:" + content.getTitle());
|
||||
|
||||
// 6. 更新购买记录状态
|
||||
purchase.setStatus(2); // 已支付
|
||||
purchase.setPurchaseTime(new Date());
|
||||
this.insert(purchase);
|
||||
isPaid = true;
|
||||
} else if (payType == 2) {
|
||||
// 积分支付
|
||||
Assert.isTrue(content.getSupportPointsPay() == 1, "该内容不支持积分支付");
|
||||
|
||||
Integer points = content.getRequiredPoints();
|
||||
purchase.setAmount(BigDecimal.ZERO);
|
||||
purchase.setPoints(points);
|
||||
|
||||
// 扣除用户积分
|
||||
this.pointsAccountService.reducePoints(userId, points, transactionNo, contentId, "purchase_content", "购买内容:" + content.getTitle());
|
||||
|
||||
// 6. 更新购买记录状态
|
||||
purchase.setStatus(2); // 已支付
|
||||
purchase.setPurchaseTime(new Date());
|
||||
|
|
|
|||
|
|
@ -0,0 +1,114 @@
|
|||
package com.kexue.skills.service.impl;
|
||||
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.kexue.skills.entity.ModelPrice;
|
||||
import com.kexue.skills.entity.dto.ModelPriceDto;
|
||||
import com.kexue.skills.mapper.ModelPriceMapper;
|
||||
import com.kexue.skills.service.ModelPriceService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* (ModelPrice)表服务实现类
|
||||
* 大模型Token价格表
|
||||
*
|
||||
* @author 王志维
|
||||
* @since 2026-03-26 10:15:00
|
||||
*/
|
||||
@Service("modelPriceService")
|
||||
public class ModelPriceServiceImpl implements ModelPriceService {
|
||||
@Resource
|
||||
private ModelPriceMapper modelPriceMapper;
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*
|
||||
* @param queryDto 筛选条件
|
||||
* @return 查询结果
|
||||
*/
|
||||
@Override
|
||||
public PageInfo<ModelPrice> getPageList(ModelPriceDto queryDto) {
|
||||
PageHelper.startPage(queryDto.getPageNum(), queryDto.getPageSize());
|
||||
List<ModelPrice> list = this.modelPriceMapper.getPageList(queryDto);
|
||||
return new PageInfo<>(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*
|
||||
* @param queryDto 筛选条件
|
||||
* @return 查询结果
|
||||
*/
|
||||
@Override
|
||||
public List<ModelPrice> getList(ModelPriceDto queryDto) {
|
||||
return this.modelPriceMapper.getList(queryDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过主键查询单条数据
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 实例对象
|
||||
*/
|
||||
@Override
|
||||
public ModelPrice queryById(Long id) {
|
||||
return this.modelPriceMapper.queryById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过模型名称查询数据
|
||||
*
|
||||
* @param modelName 模型名称
|
||||
* @return 实例对象
|
||||
*/
|
||||
@Override
|
||||
public ModelPrice queryByModelName(String modelName) {
|
||||
return this.modelPriceMapper.queryByModelName(modelName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增数据
|
||||
*
|
||||
* @param modelPrice 实例对象
|
||||
* @return 实例对象
|
||||
*/
|
||||
@Override
|
||||
public ModelPrice insert(ModelPrice modelPrice) {
|
||||
// 设置创建时间
|
||||
modelPrice.setCreatedTime(new Date());
|
||||
// 保存数据
|
||||
this.modelPriceMapper.insert(modelPrice);
|
||||
return modelPrice;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新数据
|
||||
*
|
||||
* @param modelPrice 实例对象
|
||||
* @return 实例对象
|
||||
*/
|
||||
@Override
|
||||
public ModelPrice update(ModelPrice modelPrice) {
|
||||
// 设置更新时间
|
||||
modelPrice.setUpdatedTime(new Date());
|
||||
// 更新数据
|
||||
this.modelPriceMapper.update(modelPrice);
|
||||
return this.queryById(modelPrice.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过主键删除数据
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 影响行数
|
||||
*/
|
||||
@Override
|
||||
public int deleteById(Long id) {
|
||||
return this.modelPriceMapper.deleteById(id);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -20,9 +20,13 @@ import java.io.BufferedReader;
|
|||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.http.HttpClient;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 支付服务实现类
|
||||
|
|
@ -31,6 +35,23 @@ import java.util.*;
|
|||
public class PayServiceImpl implements PayService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PayServiceImpl.class);
|
||||
|
||||
// 单例 HTTP 客户端,提升性能
|
||||
private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(30))
|
||||
.build();
|
||||
|
||||
// 微信支付下单 URL
|
||||
private static final String WECHAT_PAY_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
|
||||
|
||||
// 最大金额限制(元)
|
||||
private static final BigDecimal MAX_AMOUNT = new BigDecimal("100000");
|
||||
|
||||
// 最小金额限制(元)
|
||||
private static final BigDecimal MIN_AMOUNT = new BigDecimal("0.01");
|
||||
|
||||
// 商品描述最大长度(字节)
|
||||
private static final int MAX_BODY_LENGTH = 128;
|
||||
|
||||
@Resource
|
||||
private PaymentConfig paymentConfig;
|
||||
|
|
@ -66,6 +87,10 @@ public class PayServiceImpl implements PayService {
|
|||
}
|
||||
}
|
||||
sb.append("key=").append(key);
|
||||
|
||||
// 打印签名字符串(注意:生产环境应移除)
|
||||
logger.info("生成签名的字符串: {}", sb.toString());
|
||||
logger.info("使用的密钥: {}", key);
|
||||
|
||||
// 生成MD5
|
||||
try {
|
||||
|
|
@ -79,7 +104,9 @@ public class PayServiceImpl implements PayService {
|
|||
}
|
||||
result.append(hex);
|
||||
}
|
||||
return result.toString().toUpperCase();
|
||||
String signature = result.toString().toUpperCase();
|
||||
logger.info("生成的签名: {}", signature);
|
||||
return signature;
|
||||
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
|
||||
logger.error("生成签名失败", e);
|
||||
throw new RuntimeException("生成签名失败", e);
|
||||
|
|
@ -95,10 +122,14 @@ public class PayServiceImpl implements PayService {
|
|||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("<xml>");
|
||||
for (Map.Entry<String, String> entry : params.entrySet()) {
|
||||
sb.append("<").append(entry.getKey()).append(">").append(entry.getValue()).append("</").append(entry.getKey()).append(">");
|
||||
sb.append("<").append(entry.getKey()).append("><![CDATA[")
|
||||
.append(entry.getValue())
|
||||
.append("]]></").append(entry.getKey()).append(">");
|
||||
}
|
||||
sb.append("</xml>");
|
||||
return sb.toString();
|
||||
String xml = sb.toString();
|
||||
logger.info("生成的XML: {}", xml);
|
||||
return xml;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -109,19 +140,44 @@ public class PayServiceImpl implements PayService {
|
|||
private Map<String, String> xmlToMap(String xml) {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
try {
|
||||
// 简单的XML解析
|
||||
String content = xml.replaceAll("<xml>", "").replaceAll("</xml>", "");
|
||||
String[] elements = content.split("</");
|
||||
for (String element : elements) {
|
||||
if (element.isEmpty()) continue;
|
||||
int idx = element.indexOf(">");
|
||||
if (idx == -1) continue;
|
||||
String key = element.substring(1, idx);
|
||||
String value = element.substring(idx + 1);
|
||||
map.put(key, value);
|
||||
// 使用更健壮的XML解析方法
|
||||
xml = xml.trim();
|
||||
if (!xml.startsWith("<xml>") || !xml.endsWith("</xml>")) {
|
||||
throw new RuntimeException("无效的XML格式");
|
||||
}
|
||||
|
||||
// 移除xml标签
|
||||
String content = xml.substring(5, xml.length() - 6).trim();
|
||||
|
||||
// 按标签分割
|
||||
int pos = 0;
|
||||
while (pos < content.length()) {
|
||||
// 找到开始标签
|
||||
int startTagStart = content.indexOf('<', pos);
|
||||
if (startTagStart == -1) break;
|
||||
int startTagEnd = content.indexOf('>', startTagStart);
|
||||
if (startTagEnd == -1) break;
|
||||
|
||||
String tagName = content.substring(startTagStart + 1, startTagEnd).trim();
|
||||
|
||||
// 找到结束标签
|
||||
int endTagStart = content.indexOf("</" + tagName + ">", startTagEnd);
|
||||
if (endTagStart == -1) break;
|
||||
|
||||
String value = content.substring(startTagEnd + 1, endTagStart).trim();
|
||||
|
||||
// 处理CDATA标签
|
||||
if (value.startsWith("<![CDATA[") && value.endsWith("]]>") ) {
|
||||
value = value.substring(9, value.length() - 3).trim();
|
||||
}
|
||||
|
||||
map.put(tagName, value);
|
||||
pos = endTagStart + tagName.length() + 3;
|
||||
}
|
||||
|
||||
logger.info("XML解析结果: {}", map);
|
||||
} catch (Exception e) {
|
||||
logger.error("XML解析失败", e);
|
||||
logger.error("XML解析失败: {}", xml, e);
|
||||
throw new RuntimeException("XML解析失败", e);
|
||||
}
|
||||
return map;
|
||||
|
|
@ -130,64 +186,255 @@ public class PayServiceImpl implements PayService {
|
|||
/**
|
||||
* 创建微信支付订单
|
||||
* @param order 支付订单信息
|
||||
* @param ipAddress 用户真实 IP 地址(从 Controller 传入)
|
||||
* @return 微信支付参数
|
||||
* @throws IllegalArgumentException 参数错误时抛出
|
||||
* @throws RuntimeException 网络异常或微信侧错误时抛出
|
||||
*/
|
||||
@Override
|
||||
public Map<String, String> createWechatPay(PaymentOrder order) {
|
||||
public Map<String, String> createWechatPay(PaymentOrder order, String ipAddress) {
|
||||
// 1. 参数校验
|
||||
validatePaymentOrder(order);
|
||||
|
||||
try {
|
||||
// 构建微信支付参数
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("appid", paymentConfig.getWechat().getAppId());
|
||||
params.put("mch_id", paymentConfig.getWechat().getMchId());
|
||||
params.put("nonce_str", generateNonceStr());
|
||||
params.put("body", order.getProductName());
|
||||
params.put("out_trade_no", order.getOrderNo());
|
||||
params.put("total_fee", String.valueOf((int) (order.getAmount().doubleValue() * 100)));
|
||||
params.put("spbill_create_ip", "127.0.0.1");
|
||||
params.put("notify_url", paymentConfig.getWechat().getNotifyUrl());
|
||||
params.put("trade_type", "NATIVE");
|
||||
|
||||
// 生成签名
|
||||
// 2. 构建微信支付参数
|
||||
Map<String, String> params = buildWechatPayParams(order, ipAddress);
|
||||
|
||||
// 3. 生成签名并构建 XML
|
||||
String sign = generateSignature(params, paymentConfig.getWechat().getMchKey());
|
||||
params.put("sign", sign);
|
||||
|
||||
// 构建请求XML
|
||||
|
||||
String xml = mapToXml(params);
|
||||
logger.info("微信支付请求XML: {}", xml);
|
||||
|
||||
// 发送请求到微信支付接口
|
||||
String xmlResult = null;
|
||||
try {
|
||||
logger.info("开始发送微信支付请求到: https://api.mch.weixin.qq.com/pay/unifiedorder");
|
||||
xmlResult = HttpUtil.post("https://api.mch.weixin.qq.com/pay/unifiedorder", xml);
|
||||
logger.info("微信支付接口返回XML: {}", xmlResult);
|
||||
} catch (Exception e) {
|
||||
logger.error("发送微信支付请求失败", e);
|
||||
throw new RuntimeException("发送微信支付请求失败: " + e.getMessage());
|
||||
}
|
||||
logRequest(xml);
|
||||
|
||||
if (xmlResult == null || xmlResult.isEmpty()) {
|
||||
logger.error("微信支付接口返回空响应");
|
||||
throw new RuntimeException("微信支付接口返回空响应");
|
||||
}
|
||||
// 4. 发送请求到微信支付接口
|
||||
String xmlResult = sendWechatRequest(xml);
|
||||
|
||||
Map<String, String> result = xmlToMap(xmlResult);
|
||||
logger.info("解析后的微信支付响应: {}", result);
|
||||
|
||||
// 处理响应
|
||||
if ("SUCCESS".equals(result.get("return_code")) && "SUCCESS".equals(result.get("result_code"))) {
|
||||
Map<String, String> payParams = new HashMap<>();
|
||||
payParams.put("code_url", result.get("code_url"));
|
||||
payParams.put("order_no", order.getOrderNo());
|
||||
return payParams;
|
||||
} else {
|
||||
String returnMsg = result.get("return_msg") != null ? result.get("return_msg") : "未知错误";
|
||||
logger.error("微信支付下单失败: {}", returnMsg);
|
||||
throw new RuntimeException("微信支付下单失败: " + returnMsg);
|
||||
}
|
||||
// 5. 解析并处理响应
|
||||
return processWechatResponse(xmlResult, order);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error("参数校验失败:{}", e.getMessage());
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
logger.error("创建微信支付订单失败", e);
|
||||
throw new RuntimeException("创建微信支付订单失败", e);
|
||||
throw new RuntimeException("系统繁忙,请稍后重试", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验支付订单参数
|
||||
*/
|
||||
private void validatePaymentOrder(PaymentOrder order) {
|
||||
if (order == null) {
|
||||
throw new IllegalArgumentException("支付订单不能为空");
|
||||
}
|
||||
|
||||
// 校验金额
|
||||
if (order.getAmount() == null) {
|
||||
throw new IllegalArgumentException("支付金额不能为空");
|
||||
}
|
||||
if (order.getAmount().compareTo(MIN_AMOUNT) < 0) {
|
||||
throw new IllegalArgumentException("支付金额不能小于 0.01 元");
|
||||
}
|
||||
if (order.getAmount().compareTo(MAX_AMOUNT) > 0) {
|
||||
throw new IllegalArgumentException("单笔支付金额不能超过 10 万元");
|
||||
}
|
||||
|
||||
// 校验订单号
|
||||
if (order.getOrderNo() == null || order.getOrderNo().trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("订单号不能为空");
|
||||
}
|
||||
if (order.getOrderNo().length() > 32) {
|
||||
throw new IllegalArgumentException("订单号长度不能超过 32 位");
|
||||
}
|
||||
if (!order.getOrderNo().matches("^[a-zA-Z0-9_]+$")) {
|
||||
throw new IllegalArgumentException("订单号只能包含字母、数字和下划线");
|
||||
}
|
||||
|
||||
// 校验商品名称
|
||||
if (order.getProductName() == null || order.getProductName().trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("商品名称不能为空");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建微信支付请求参数
|
||||
*/
|
||||
private Map<String, String> buildWechatPayParams(PaymentOrder order, String ipAddress) {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
|
||||
// 应用ID
|
||||
String appId = paymentConfig.getWechat().getAppId();
|
||||
logger.info("使用的appId: {}", appId);
|
||||
params.put("appid", appId);
|
||||
|
||||
// 商户号
|
||||
String mchId = paymentConfig.getWechat().getMchId();
|
||||
logger.info("使用的mchId: {}", mchId);
|
||||
params.put("mch_id", mchId);
|
||||
|
||||
// 随机字符串
|
||||
String nonceStr = generateNonceStr();
|
||||
logger.info("生成的nonce_str: {}", nonceStr);
|
||||
params.put("nonce_str", nonceStr);
|
||||
|
||||
params.put("sign_type", "MD5");
|
||||
|
||||
// 商品描述
|
||||
String body = limitBodyLength(order.getProductName());
|
||||
logger.info("商品描述: {}", body);
|
||||
params.put("body", body);
|
||||
|
||||
// 商户订单号
|
||||
String outTradeNo = order.getOrderNo();
|
||||
logger.info("商户订单号: {}", outTradeNo);
|
||||
params.put("out_trade_no", outTradeNo);
|
||||
|
||||
// 金额转换:元转分
|
||||
BigDecimal totalFee = order.getAmount()
|
||||
.multiply(BigDecimal.valueOf(100))
|
||||
.setScale(0, BigDecimal.ROUND_HALF_UP);
|
||||
String totalFeeStr = String.valueOf(totalFee.intValue());
|
||||
logger.info("计算的 total_fee: {} 分", totalFeeStr);
|
||||
params.put("total_fee", totalFeeStr);
|
||||
|
||||
// 使用传入的 IP 地址,如果为空则使用默认值
|
||||
String ip = ipAddress != null ? ipAddress : "127.0.0.1";
|
||||
// 转换IPv6地址为IPv4
|
||||
if (ip.equals("0:0:0:0:0:0:0:1")) {
|
||||
ip = "127.0.0.1";
|
||||
}
|
||||
// 确保IP地址格式正确
|
||||
ip = ip.trim();
|
||||
logger.info("使用的IP地址: {}", ip);
|
||||
params.put("spbill_create_ip", ip);
|
||||
|
||||
// 使用配置的回调地址
|
||||
String notifyUrl = paymentConfig.getWechat().getNotifyUrl();
|
||||
// 确保回调地址格式正确
|
||||
notifyUrl = notifyUrl.trim();
|
||||
logger.info("使用的回调地址: {}", notifyUrl);
|
||||
params.put("notify_url", notifyUrl);
|
||||
|
||||
// 交易类型
|
||||
params.put("trade_type", "NATIVE");
|
||||
|
||||
// 商品ID(NATIVE支付必传)
|
||||
params.put("product_id", order.getOrderNo());
|
||||
|
||||
// 打印构建的参数
|
||||
logger.info("构建的微信支付参数: {}", params);
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 限制商品描述长度(最多 128 字节)
|
||||
*/
|
||||
private String limitBodyLength(String body) {
|
||||
if (body == null) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
// 移除特殊字符,只保留中文、英文、数字和常见标点
|
||||
body = body.replaceAll("[^\u4e00-\u9fa5a-zA-Z0-9\\s,.!?!,。]", "");
|
||||
|
||||
byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
|
||||
if (bytes.length <= MAX_BODY_LENGTH) {
|
||||
return body;
|
||||
}
|
||||
// 截断并添加省略号
|
||||
String truncated = new String(bytes, 0, Math.min(MAX_BODY_LENGTH - 3, bytes.length), StandardCharsets.UTF_8);
|
||||
return truncated + "...";
|
||||
} catch (Exception e) {
|
||||
logger.warn("商品描述编码失败,使用默认值", e);
|
||||
return body.length() > 60 ? body.substring(0, 60) + "..." : body;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录请求日志(敏感信息脱敏)
|
||||
*/
|
||||
private void logRequest(String xml) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("微信支付请求 XML: {}", maskSensitiveInfo(xml));
|
||||
}
|
||||
logger.info("开始发送微信支付请求到:{}", WECHAT_PAY_URL);
|
||||
}
|
||||
|
||||
/**
|
||||
* 脱敏敏感信息
|
||||
*/
|
||||
private String maskSensitiveInfo(String xml) {
|
||||
return xml.replaceAll("(<mch_key><!\\[CDATA\\[)(.*?)(]]></mch_key>)", "$1***$3")
|
||||
.replaceAll("(<sign><!\\[CDATA\\[)(.*?)(]]></sign>)", "$1***$3");
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送微信支付请求
|
||||
*/
|
||||
private String sendWechatRequest(String xml) throws Exception {
|
||||
logger.info("请求参数:appId={}, mchId={}",
|
||||
paymentConfig.getWechat().getAppId(),
|
||||
paymentConfig.getWechat().getMchId());
|
||||
|
||||
String xmlResult = HttpUtil.postWithClient(WECHAT_PAY_URL, xml, HTTP_CLIENT);
|
||||
|
||||
if (xmlResult == null || xmlResult.trim().isEmpty()) {
|
||||
logger.error("微信支付接口返回空响应");
|
||||
throw new RuntimeException("微信支付接口返回空响应");
|
||||
}
|
||||
|
||||
logger.info("微信支付接口返回 XML: {}", xmlResult);
|
||||
return xmlResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理微信支付响应
|
||||
*/
|
||||
private Map<String, String> processWechatResponse(String xmlResult, PaymentOrder order) {
|
||||
try {
|
||||
Map<String, String> result = xmlToMap(xmlResult);
|
||||
logger.info("解析后的微信支付响应:{}", result);
|
||||
|
||||
String returnCode = result.get("return_code");
|
||||
String resultCode = result.get("result_code");
|
||||
String returnMsg = result.get("return_msg");
|
||||
String errCode = result.get("err_code");
|
||||
String errCodeDes = result.get("err_code_des");
|
||||
|
||||
logger.info("微信支付返回码:return_code={}, result_code={}, return_msg={}, err_code={}, err_code_des={}",
|
||||
returnCode, resultCode, returnMsg, errCode, errCodeDes);
|
||||
|
||||
// 检查通信标识
|
||||
if (!"SUCCESS".equals(returnCode)) {
|
||||
String errorMsg = "通信失败:" + (returnMsg != null ? returnMsg : "未知错误");
|
||||
logger.error(errorMsg);
|
||||
throw new RuntimeException(errorMsg);
|
||||
}
|
||||
|
||||
// 检查业务结果
|
||||
if (!"SUCCESS".equals(resultCode)) {
|
||||
String errorMsg = returnMsg != null ? returnMsg : "未知错误";
|
||||
if (errCodeDes != null) {
|
||||
errorMsg += " (" + errCodeDes + ")";
|
||||
}
|
||||
logger.error("微信支付下单失败:err_code={}, error_msg={}", errCode, errorMsg);
|
||||
throw new RuntimeException("支付下单失败:" + errorMsg);
|
||||
}
|
||||
|
||||
// 返回成功结果
|
||||
Map<String, String> payParams = new HashMap<>();
|
||||
payParams.put("code_url", result.get("code_url"));
|
||||
payParams.put("order_no", order.getOrderNo());
|
||||
return payParams;
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
logger.error("解析微信支付响应失败", e);
|
||||
throw new RuntimeException("解析响应失败:" + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ public class PaymentOrderServiceImpl implements PaymentOrderService {
|
|||
|
||||
@Resource
|
||||
private com.kexue.skills.service.ContentPurchaseService contentPurchaseService;
|
||||
|
||||
@Resource
|
||||
private com.kexue.skills.service.CmsContentService cmsContentService;
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
|
|
@ -199,36 +202,130 @@ public class PaymentOrderServiceImpl implements PaymentOrderService {
|
|||
// 查询订单
|
||||
PaymentOrder order = this.queryByOrderNo(orderNo);
|
||||
if (order == null) {
|
||||
logger.warn("支付回调订单不存在:{}", orderNo);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// 更新订单状态
|
||||
this.updateStatus(order.getOrderId(), status, channelOrderNo);
|
||||
|
||||
|
||||
// 如果支付成功,处理相关业务逻辑
|
||||
if (status == 2) { // 2.已支付
|
||||
logger.info("支付订单回调成功,orderNo: {}, businessType: {}", orderNo, order.getBusinessType());
|
||||
|
||||
// 根据业务类型处理不同的业务逻辑
|
||||
if ("recharge".equals(order.getBusinessType())) {
|
||||
// 充值业务,增加用户余额
|
||||
this.accountService.addBalance(order.getUserId(), order.getAmount(), orderNo,
|
||||
order.getBusinessId(), order.getBusinessType(), "充值成功");
|
||||
} else if ("purchase_content".equals(order.getBusinessType())) {
|
||||
// 购买内容业务,更新购买记录状态
|
||||
try {
|
||||
// 查找对应的购买记录并更新状态
|
||||
com.kexue.skills.entity.ContentPurchase purchase = contentPurchaseService.queryByUserIdAndContentId(order.getUserId(), order.getBusinessId());
|
||||
if (purchase != null && purchase.getStatus() == 1) { // 1.待支付
|
||||
contentPurchaseService.updateStatus(purchase.getPurchaseId(), 2); // 2.已支付
|
||||
}
|
||||
this.accountService.addBalance(order.getUserId(), order.getAmount(), orderNo,
|
||||
order.getBusinessId(), order.getBusinessType(), "充值成功");
|
||||
logger.info("充值业务处理完成,userId: {}, amount: {}", order.getUserId(), order.getAmount());
|
||||
} catch (Exception e) {
|
||||
// 记录错误但不影响支付回调的处理
|
||||
logger.error("更新购买记录状态失败: {}", e.getMessage(), e);
|
||||
logger.error("充值业务处理失败:{}", e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
} else if ("purchase_content".equals(order.getBusinessType())) {
|
||||
// 购买内容业务,创建或更新购买记录
|
||||
try {
|
||||
handleContentPurchaseCallback(order);
|
||||
} catch (Exception e) {
|
||||
logger.error("购买内容业务处理失败:{}", e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理内容购买回调逻辑
|
||||
*
|
||||
* @param order 支付订单
|
||||
*/
|
||||
private void handleContentPurchaseCallback(PaymentOrder order) {
|
||||
Long userId = order.getUserId();
|
||||
Long contentId = order.getBusinessId();
|
||||
|
||||
// 1. 先查询是否已存在购买记录
|
||||
com.kexue.skills.entity.ContentPurchase existingPurchase =
|
||||
contentPurchaseService.queryByUserIdAndContentId(userId, contentId);
|
||||
|
||||
if (existingPurchase != null) {
|
||||
// 2. 如果已存在购买记录且状态为待支付,更新为已支付
|
||||
if (existingPurchase.getStatus() == 1) { // 1.待支付
|
||||
contentPurchaseService.updateStatus(existingPurchase.getPurchaseId(), 2); // 2.已支付
|
||||
logger.info("更新购买记录状态为已支付,userId: {}, contentId: {}", userId, contentId);
|
||||
} else {
|
||||
logger.warn("购买记录已存在且状态为已支付,无需更新,userId: {}, contentId: {}", userId, contentId);
|
||||
}
|
||||
} else {
|
||||
// 3. 如果不存在购买记录,创建新的购买记录
|
||||
logger.info("购买记录不存在,创建新记录,userId: {}, contentId: {}", userId, contentId);
|
||||
createContentPurchaseRecord(order);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建内容购买记录
|
||||
*
|
||||
* @param order 支付订单
|
||||
*/
|
||||
private void createContentPurchaseRecord(PaymentOrder order) {
|
||||
try {
|
||||
// 查询内容信息获取标题
|
||||
String contentTitle = order.getProductName();
|
||||
if (order.getBusinessId() != null) {
|
||||
com.kexue.skills.entity.CmsContent content = cmsContentService.queryById(order.getBusinessId());
|
||||
if (content != null) {
|
||||
contentTitle = content.getTitle();
|
||||
}
|
||||
}
|
||||
|
||||
// 创建购买记录
|
||||
com.kexue.skills.entity.ContentPurchase purchase = new com.kexue.skills.entity.ContentPurchase();
|
||||
purchase.setUserId(order.getUserId());
|
||||
purchase.setContentId(order.getBusinessId());
|
||||
purchase.setContentTitle(contentTitle);
|
||||
purchase.setPayType(mapPayType(order.getPayType())); // 转换支付方式类型
|
||||
purchase.setAmount(order.getAmount());
|
||||
purchase.setPoints(0); // 微信/支付宝支付不计积分
|
||||
purchase.setStatus(2); // 2.已支付
|
||||
purchase.setPurchaseTime(new Date());
|
||||
purchase.setCreateTime(new Date());
|
||||
purchase.setUpdateTime(new Date());
|
||||
purchase.setDeleteFlag(0);
|
||||
|
||||
contentPurchaseService.insert(purchase);
|
||||
logger.info("创建购买记录成功,userId: {}, contentId: {}, purchaseId: {}",
|
||||
order.getUserId(), order.getBusinessId(), purchase.getPurchaseId());
|
||||
} catch (Exception e) {
|
||||
logger.error("创建购买记录失败:{}", e.getMessage(), e);
|
||||
throw new RuntimeException("创建购买记录失败:" + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 映射支付方式类型
|
||||
* payment_order.pay_type: 1.微信 2.支付宝
|
||||
* content_purchase.pay_type: 3.微信支付 4.支付宝支付
|
||||
*
|
||||
* @param payType 支付订单的支付方式
|
||||
* @return 购买记录的支付方式
|
||||
*/
|
||||
private int mapPayType(Integer payType) {
|
||||
if (payType == null) {
|
||||
return 3; // 默认微信支付
|
||||
}
|
||||
switch (payType) {
|
||||
case 1: // 微信
|
||||
return 3;
|
||||
case 2: // 支付宝
|
||||
return 4;
|
||||
default:
|
||||
return 3; // 默认微信支付
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成订单号
|
||||
|
|
|
|||
|
|
@ -1,229 +0,0 @@
|
|||
package com.kexue.skills.service.impl;
|
||||
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.kexue.skills.entity.PointsAccount;
|
||||
import com.kexue.skills.entity.PointsTransaction;
|
||||
import com.kexue.skills.entity.dto.PointsAccountDto;
|
||||
import com.kexue.skills.common.Assert;
|
||||
import com.kexue.skills.exception.BizException;
|
||||
import com.kexue.skills.mapper.PointsAccountMapper;
|
||||
import com.kexue.skills.mapper.PointsTransactionMapper;
|
||||
import com.kexue.skills.service.PointsAccountService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* (PointsAccount)表服务实现类
|
||||
*
|
||||
* @author 王志维
|
||||
* @since 2025-02-21 23:01:48
|
||||
*/
|
||||
@Service("pointsAccountService")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public class PointsAccountServiceImpl implements PointsAccountService {
|
||||
@Resource
|
||||
private PointsAccountMapper pointsAccountMapper;
|
||||
|
||||
@Resource
|
||||
private PointsTransactionMapper pointsTransactionMapper;
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*
|
||||
* @param queryDto 筛选条件
|
||||
* @return 查询结果
|
||||
*/
|
||||
@Override
|
||||
public PageInfo<PointsAccount> getPageList(PointsAccountDto queryDto) {
|
||||
PageHelper.startPage(queryDto.getPageNum(), queryDto.getPageSize());
|
||||
List<PointsAccount> list = this.pointsAccountMapper.getPageList(queryDto);
|
||||
return new PageInfo<>(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*
|
||||
* @param queryDto 筛选条件
|
||||
* @return 查询结果
|
||||
*/
|
||||
@Override
|
||||
public List<PointsAccount> getList(PointsAccountDto queryDto) {
|
||||
return this.pointsAccountMapper.getList(queryDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过主键查询单条数据
|
||||
*
|
||||
* @param accountId 主键
|
||||
* @return 实例对象
|
||||
*/
|
||||
@Override
|
||||
public PointsAccount queryById(Long accountId) {
|
||||
return this.pointsAccountMapper.queryById(accountId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过用户ID查询积分账户
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 实例对象
|
||||
*/
|
||||
@Override
|
||||
public PointsAccount queryByUserId(Long userId) {
|
||||
return this.pointsAccountMapper.queryByUserId(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增数据
|
||||
*
|
||||
* @param pointsAccount 实例对象
|
||||
* @return 实例对象
|
||||
*/
|
||||
@Override
|
||||
public PointsAccount insert(PointsAccount pointsAccount) {
|
||||
// 设置创建时间和更新时间
|
||||
Date now = new Date();
|
||||
pointsAccount.setCreateTime(now);
|
||||
pointsAccount.setUpdateTime(now);
|
||||
// 设置默认值
|
||||
pointsAccount.setDeleteFlag(0);
|
||||
if (pointsAccount.getTotalPoints() == null) {
|
||||
pointsAccount.setTotalPoints(0);
|
||||
}
|
||||
if (pointsAccount.getAvailablePoints() == null) {
|
||||
pointsAccount.setAvailablePoints(0);
|
||||
}
|
||||
if (pointsAccount.getFrozenPoints() == null) {
|
||||
pointsAccount.setFrozenPoints(0);
|
||||
}
|
||||
// 保存数据
|
||||
this.pointsAccountMapper.insert(pointsAccount);
|
||||
return pointsAccount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新数据
|
||||
*
|
||||
* @param pointsAccount 实例对象
|
||||
* @return 实例对象
|
||||
*/
|
||||
@Override
|
||||
public PointsAccount update(PointsAccount pointsAccount) {
|
||||
// 设置更新时间
|
||||
pointsAccount.setUpdateTime(new Date());
|
||||
// 更新数据
|
||||
this.pointsAccountMapper.update(pointsAccount);
|
||||
return this.queryById(pointsAccount.getAccountId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加积分
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param points 增加积分
|
||||
* @param transactionNo 交易单号
|
||||
* @param businessId 业务ID
|
||||
* @param businessType 业务类型
|
||||
* @param remark 备注
|
||||
* @return 影响行数
|
||||
*/
|
||||
@Override
|
||||
public int addPoints(Long userId, Integer points, String transactionNo, Long businessId, String businessType, String remark) {
|
||||
// 1. 查询积分账户信息
|
||||
PointsAccount account = this.queryByUserId(userId);
|
||||
if (account == null) {
|
||||
// 创建积分账户
|
||||
account = new PointsAccount();
|
||||
account.setUserId(userId);
|
||||
account.setTotalPoints(0);
|
||||
account.setAvailablePoints(0);
|
||||
account.setFrozenPoints(0);
|
||||
this.insert(account);
|
||||
}
|
||||
|
||||
// 2. 保存积分交易记录
|
||||
PointsTransaction transaction = new PointsTransaction();
|
||||
transaction.setUserId(userId);
|
||||
transaction.setUserName(account.getUserName());
|
||||
transaction.setTransactionType(1); // 1.获得积分
|
||||
transaction.setPoints(points);
|
||||
transaction.setBeforePoints(account.getAvailablePoints());
|
||||
transaction.setAfterPoints(account.getAvailablePoints() + points);
|
||||
transaction.setStatus(1); // 成功
|
||||
transaction.setTransactionNo(transactionNo);
|
||||
transaction.setBusinessId(businessId);
|
||||
transaction.setBusinessType(businessType);
|
||||
transaction.setRemark(remark);
|
||||
this.pointsTransactionMapper.insert(transaction);
|
||||
|
||||
// 3. 更新积分账户
|
||||
return this.pointsAccountMapper.updatePoints(userId, points, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 减少积分
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param points 减少积分
|
||||
* @param transactionNo 交易单号
|
||||
* @param businessId 业务ID
|
||||
* @param businessType 业务类型
|
||||
* @param remark 备注
|
||||
* @return 影响行数
|
||||
*/
|
||||
@Override
|
||||
public int reducePoints(Long userId, Integer points, String transactionNo, Long businessId, String businessType, String remark) {
|
||||
// 1. 查询积分账户信息
|
||||
PointsAccount account = this.queryByUserId(userId);
|
||||
Assert.notNull(account, "积分账户不存在");
|
||||
|
||||
// 2. 检查积分是否足够
|
||||
Assert.isTrue(account.getAvailablePoints() >= points, "积分不足");
|
||||
|
||||
// 3. 保存积分交易记录
|
||||
PointsTransaction transaction = new PointsTransaction();
|
||||
transaction.setUserId(userId);
|
||||
transaction.setUserName(account.getUserName());
|
||||
transaction.setTransactionType(2); // 2.使用积分
|
||||
transaction.setPoints(points);
|
||||
transaction.setBeforePoints(account.getAvailablePoints());
|
||||
transaction.setAfterPoints(account.getAvailablePoints() - points);
|
||||
transaction.setStatus(1); // 成功
|
||||
transaction.setTransactionNo(transactionNo);
|
||||
transaction.setBusinessId(businessId);
|
||||
transaction.setBusinessType(businessType);
|
||||
transaction.setRemark(remark);
|
||||
this.pointsTransactionMapper.insert(transaction);
|
||||
|
||||
// 4. 更新积分账户
|
||||
return this.pointsAccountMapper.updatePoints(userId, points, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过主键逻辑删除
|
||||
*
|
||||
* @param accountId 主键
|
||||
* @param updateBy 更新人
|
||||
* @return 影响行数
|
||||
*/
|
||||
@Override
|
||||
public int logicDeleteById(Long accountId, String updateBy) {
|
||||
return this.pointsAccountMapper.logicDeleteById(accountId, updateBy);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过主键物理删除
|
||||
*
|
||||
* @param accountId 主键
|
||||
* @return 影响行数
|
||||
*/
|
||||
@Override
|
||||
public int deleteById(Long accountId) {
|
||||
return this.pointsAccountMapper.deleteById(accountId);
|
||||
}
|
||||
}
|
||||
|
|
@ -225,13 +225,90 @@ public class SkillGenServiceImpl implements SkillGenService {
|
|||
}
|
||||
tagsList.append(tag.getTagName());
|
||||
if (i < tags.size() - 1) {
|
||||
tagsList.append(",");
|
||||
tagsList.append(",");
|
||||
}
|
||||
}
|
||||
}
|
||||
String systemContent = "你是一个专业的AI技能设计助手。请基于用户提供的Skill名称、描述、标签,按照skills目录结构输出完整的skills内容,包括skills.md本体内容、scripts目录中的脚本等,并打包成一个YAML文件技能包。请严格遵循以下规范:1. 包含必需的文件和目录;2. 多行内容使用 | 字面块;3. 内容从行首开始;4. 空目录用 children: [] 表示;5. 文件内容要实际有用;6.输出一个完整的YAML文档,概要含核心属性:name、version、description、author、created、tags 等,其中 structure 为核心节点,用于描述 skill 包的文件目录结构;structure 下的每个节点均含基础属性:name、type、path、format、description,content 和 children 为互选属性,由 type 决定:type 为 file 时,展示文件具体内容在 content 字段,必须保证content内容的缩进为2个字符;type 为 directory 时,展示子目录 / 文件节点在 children [] 数组中,无需其他额外说明。";
|
||||
String userContent = "请根据以下Skill信息生成skills.md文档内容:Skill名称:SKILL_NAME,Skill描述:DESCRIPTION,Skill标签:TAGS ,摘要:SUMMARY。";
|
||||
userContent = userContent.replace("SKILL_NAME", request.getName()).replace("DESCRIPTION", request.getDescription()).replace("TAGS", tagsList.toString()).replace("SUMMARY", request.getRequirement());
|
||||
String systemContent = """
|
||||
你是AI技能包设计专家,仅输出【纯YAML文本】,不包含任何多余文字(无解释、无注释、无引言、无结尾)。
|
||||
|
||||
### 一、YAML顶层强制规则(仅一个节点:package)
|
||||
1. 顶层只能有 `package` 一个节点,所有信息(名称、版本、目录结构等)均嵌套在 `package` 下
|
||||
2. `package` 节点必含子字段:name、version、description、author、created、tags、structure(缺一不可)
|
||||
|
||||
### 二、package子字段规范(固定格式)
|
||||
1. name:技能名称(与用户提供的Skill名称完全一致,不修改)
|
||||
2. version:固定为 "1.0.0"
|
||||
3. description:用户提供的Skill描述(完整复制,不增删任何内容)
|
||||
4. author:固定为 "AI技能生成助手"
|
||||
5. created:格式为 "YYYY-MM-DD"(使用当前日期,如2026-04-01)
|
||||
6. tags:数组格式,值为用户提供的Skill标签(中文,自动去重,逗号后加空格)
|
||||
7. structure:技能包目录树根节点(必为directory类型,统一命名"skills")
|
||||
|
||||
### 三、structure目录树规则(仅Python脚本,无其他语言)
|
||||
#### 1. 基础必含文件(所有技能通用)
|
||||
- /skills(根目录,type: directory,path: /skills,format: dir,description: 技能包根目录)
|
||||
- /skills/skills.md(type: file,path: /skills/skills.md,format: markdown,description: 技能说明文档)
|
||||
|
||||
#### 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 目录,避免冗余
|
||||
|
||||
#### 3. 节点必含字段
|
||||
- directory类型:name、type、path(绝对路径,Unix风格 `/`,如 /skills/scripts)、format(固定"dir")、description、children(空目录写 `children: []`)
|
||||
- file类型:name、type、path(绝对路径)、format(仅markdown/python两种)、description、content(非空,有实际可用内容)
|
||||
|
||||
### 四、文件content内容规范(Python脚本必实用)
|
||||
#### 1. /skills/skills.md(结构固定)
|
||||
- # 技能名称(不加多余符号,居中可加空格但不强制)
|
||||
- ## 技能描述(整合用户“描述+摘要”,补充逻辑连贯性)
|
||||
- ## 标签(格式:`- 标签1\n- 标签2`,对应tags数组内容)
|
||||
- ## 使用说明(分点写:适用场景、操作步骤,需脚本则写“运行main.py脚本”,无需则写“直接参考文档使用”)
|
||||
- ## 目录结构(用代码块 ``` 列出所有文件/目录路径)
|
||||
|
||||
#### 2. /skills/scripts/main.py(Python脚本必含)
|
||||
- 必含依赖导入(如 `import pandas as pd`,无依赖则不写)
|
||||
- 必含入口函数 `def execute(params: dict) -> dict:`(参数为dict,返回dict结果)
|
||||
- 函数内必含:
|
||||
1. 参数校验(判断必填键是否存在,缺失返回错误)
|
||||
2. 核心逻辑(匹配技能需求,如数据处理、文本分析)
|
||||
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),禁止 `\\` 或 `./`
|
||||
3. 字符串:特殊字符(:、#、空格)无需转义,直接书写
|
||||
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下)
|
||||
|
||||
最终输出仅纯YAML,直接可复制存储、解析使用,无任何冗余或格式问题!
|
||||
""";
|
||||
|
||||
String userContent = """
|
||||
基于以下信息生成技能包YAML,严格遵守system指令(仅Python脚本,无其他语言):
|
||||
1. Skill名称:%s
|
||||
2. Skill描述:%s
|
||||
3. Skill标签:%s(中文,直接使用,不修改)
|
||||
4. Skill摘要/需求:%s(用于完善skills.md的“使用说明”章节)
|
||||
""".formatted(
|
||||
request.getName(),
|
||||
request.getDescription(),
|
||||
tagsList.toString(),
|
||||
request.getRequirement()
|
||||
);
|
||||
SkillRequest skillRequest = new SkillRequest(true, deepSeekConfig.getChat().getModel(),systemContent,userContent,deepSeekConfig.getChat().getTemperature(), 8192,"text");
|
||||
String deepseekResponse = "";
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.kexue.skills.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.alicp.jetcache.anno.CacheInvalidate;
|
||||
import com.alicp.jetcache.anno.CachePenetrationProtect;
|
||||
import com.alicp.jetcache.anno.Cached;
|
||||
|
|
@ -11,29 +10,34 @@ import com.kexue.skills.common.CacheManager;
|
|||
import com.kexue.skills.common.Const;
|
||||
import com.kexue.skills.common.LoginUserCacheUtil;
|
||||
import com.kexue.skills.config.CaptchaConfig;
|
||||
import com.kexue.skills.entity.*;
|
||||
import com.kexue.skills.entity.Account;
|
||||
import com.kexue.skills.entity.ContentPurchase;
|
||||
import com.kexue.skills.entity.SysUser;
|
||||
import com.kexue.skills.entity.SysUserRole;
|
||||
import com.kexue.skills.entity.dto.ContentPurchaseDto;
|
||||
import com.kexue.skills.entity.dto.SessionDto;
|
||||
import com.kexue.skills.entity.dto.SysUserDto;
|
||||
import com.kexue.skills.entity.request.*;
|
||||
import com.kexue.skills.entity.request.SysUserUpdateDto;
|
||||
import com.kexue.skills.mapper.*;
|
||||
import com.kexue.skills.exception.BizException;
|
||||
import com.kexue.skills.mapper.AccountMapper;
|
||||
import com.kexue.skills.mapper.ContentPurchaseMapper;
|
||||
import com.kexue.skills.mapper.CmsContentLikeMapper;
|
||||
import com.kexue.skills.mapper.CmsContentViewMapper;
|
||||
import com.kexue.skills.mapper.CmsContentMapper;
|
||||
import com.kexue.skills.mapper.SysUserMapper;
|
||||
import com.kexue.skills.mapper.SysUserRoleMapper;
|
||||
import com.kexue.skills.service.SysUserService;
|
||||
import com.kexue.skills.utils.MD5Util;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.api.SmsBlend;
|
||||
import org.dromara.sms4j.core.factory.SmsFactory;
|
||||
import org.redisson.api.RBucket;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Random;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* (SysUser)表服务实现类
|
||||
|
|
@ -48,17 +52,13 @@ public class SysUserServiceImpl implements SysUserService {
|
|||
@Resource
|
||||
private SysUserMapper sysUserMapper;
|
||||
|
||||
|
||||
|
||||
@Resource
|
||||
private RedissonClient redissonClient;
|
||||
|
||||
@Resource
|
||||
private CaptchaConfig captchaConfig;
|
||||
|
||||
@Resource
|
||||
private PointsAccountMapper pointsAccountMapper;
|
||||
|
||||
|
||||
@Resource
|
||||
private AccountMapper accountMapper;
|
||||
|
||||
|
|
@ -157,16 +157,6 @@ public class SysUserServiceImpl implements SysUserService {
|
|||
account.setDeleteFlag(Const.DELETE_FLAG_NO); // 初始未删除
|
||||
accountMapper.insert(account);
|
||||
|
||||
// 初始化积分账户
|
||||
PointsAccount pointsAccount = new PointsAccount();
|
||||
pointsAccount.setUserId(sysUser.getUserId());
|
||||
pointsAccount.setUserName(sysUser.getUserName());
|
||||
pointsAccount.setTotalPoints(0); // 初始总积分为0
|
||||
pointsAccount.setAvailablePoints(0); // 初始可用积分为0
|
||||
pointsAccount.setFrozenPoints(0); // 初始冻结积分为0
|
||||
pointsAccount.setDeleteFlag(Const.DELETE_FLAG_NO); // 初始未删除
|
||||
pointsAccountMapper.insert(pointsAccount);
|
||||
|
||||
// 将返回的用户密码设置为null
|
||||
sysUser.setPwd(null);
|
||||
return sysUser;
|
||||
|
|
@ -304,8 +294,13 @@ public class SysUserServiceImpl implements SysUserService {
|
|||
log.info("用户:{}的旧token已失效", sysUser.getUserName());
|
||||
}
|
||||
|
||||
// 生成token
|
||||
String token = generateToken(sysUser.getUserId());
|
||||
// 查询用户角色列表
|
||||
List<String> roles = queryUserRoles(sysUser.getUserId());
|
||||
log.info("用户{}的角色列表:{}", sysUser.getUserName(), String.join(",", roles));
|
||||
|
||||
// 生成token并设置角色
|
||||
String token = generateToken(sysUser.getUserId(), roles);
|
||||
log.info("设置后Sa-Token中的角色列表:{}", String.join(",", cn.dev33.satoken.stp.StpUtil.getRoleList()));
|
||||
|
||||
// 构建登录用户信息
|
||||
LoginUser loginUser = buildLoginUser(sysUser, token);
|
||||
|
|
@ -402,10 +397,16 @@ public class SysUserServiceImpl implements SysUserService {
|
|||
* 生成token
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param roles 角色列表
|
||||
* @return token
|
||||
*/
|
||||
private String generateToken(Long userId) {
|
||||
private String generateToken(Long userId, List<String> roles) {
|
||||
// 登录用户
|
||||
cn.dev33.satoken.stp.StpUtil.login(userId);
|
||||
// 添加角色
|
||||
if (!roles.isEmpty()) {
|
||||
cn.dev33.satoken.stp.StpUtil.getRoleList().addAll(roles);
|
||||
}
|
||||
return cn.dev33.satoken.stp.StpUtil.getTokenValue();
|
||||
}
|
||||
|
||||
|
|
@ -432,8 +433,7 @@ public class SysUserServiceImpl implements SysUserService {
|
|||
// 查询并设置用户账户信息
|
||||
loginUser.setAccount(queryUserAccount(sysUser.getUserId()));
|
||||
|
||||
// 查询并设置用户积分信息
|
||||
loginUser.setPointsAccount(queryUserPointsAccount(sysUser.getUserId()));
|
||||
|
||||
|
||||
// 查询并设置用户最近点赞记录
|
||||
loginUser.setFavorites(queryRecentFavorites(sysUser.getUserId()));
|
||||
|
|
@ -456,24 +456,18 @@ public class SysUserServiceImpl implements SysUserService {
|
|||
/**
|
||||
* 查询用户角色列表
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 角色列表
|
||||
* @param userId 用户 ID
|
||||
* @return 角色编码列表
|
||||
*/
|
||||
private List<String> queryUserRoles(Long userId) {
|
||||
List<String> roles = new java.util.ArrayList<>();
|
||||
@Override
|
||||
public List<String> queryUserRoles(Long userId) {
|
||||
try {
|
||||
SysUserRole userRole = new SysUserRole();
|
||||
userRole.setUserId(userId);
|
||||
List<SysUserRole> userRoles = sysUserRoleMapper.queryAll(userRole);
|
||||
|
||||
for (SysUserRole ur : userRoles) {
|
||||
roles.add("ROLE_" + ur.getRoleId());
|
||||
}
|
||||
// 直接通过关联查询获取角色编码列表
|
||||
return sysUserRoleMapper.queryRoleCodesByUserId(userId);
|
||||
} catch (Exception e) {
|
||||
log.error("查询用户角色列表失败:{}", e.getMessage());
|
||||
roles = java.util.Collections.emptyList();
|
||||
return java.util.Collections.emptyList();
|
||||
}
|
||||
return roles;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -509,16 +503,8 @@ public class SysUserServiceImpl implements SysUserService {
|
|||
return accountMapper.queryByUserId(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询用户积分信息
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 积分信息
|
||||
*/
|
||||
private PointsAccount queryUserPointsAccount(Long userId) {
|
||||
return pointsAccountMapper.queryByUserId(userId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 查询用户最近点赞记录
|
||||
*
|
||||
|
|
@ -626,34 +612,31 @@ public class SysUserServiceImpl implements SysUserService {
|
|||
private LoginUserCacheUtil loginUserCacheUtil;
|
||||
|
||||
@Override
|
||||
public boolean resetPassword(ResetPasswordDto resetPasswordDto) {
|
||||
Assert.notNull(resetPasswordDto.getUserName(), "用户名或手机号不能位空");
|
||||
Assert.notNull(resetPasswordDto.getOldPassword(), "旧密码不能位空");
|
||||
Assert.notNull(resetPasswordDto.getNewPassword(), "新密码不能位空");
|
||||
|
||||
// 获取当前登录用户ID
|
||||
public boolean resetPassword(ResetPwdDto resetPasswordDto) {
|
||||
// 1. 检查登录状态
|
||||
Long currentUserId = loginUserCacheUtil.getCurrentUserId();
|
||||
Assert.notNull(currentUserId, "请先登录");
|
||||
|
||||
// 查询用户(支持用户名或手机号)
|
||||
SysUser sysUser = getUserByUsernameOrPhone(resetPasswordDto.getUserName());
|
||||
// 2. 检查参数
|
||||
Assert.notNull(resetPasswordDto.getUserId(), "用户ID不能为空");
|
||||
Assert.notNull(resetPasswordDto.getNewPassword(), "新密码不能为空");
|
||||
|
||||
// 检查是否是用户自己修改密码,或者是管理员
|
||||
boolean isAdmin = Const.ADMIN_USER_LIST.contains(sysUser.getUserName().toLowerCase());
|
||||
boolean isSelf = currentUserId.equals(sysUser.getUserId());
|
||||
Assert.isTrue(isAdmin || isSelf, "只能修改自己的密码");
|
||||
// 3. 检查是否是管理员
|
||||
boolean isAdmin = isAdminUser(currentUserId);
|
||||
if (!isAdmin) {
|
||||
throw new BizException("只有管理员才能重置密码");
|
||||
}
|
||||
|
||||
// 4. 查询用户(支持用户名或手机号)
|
||||
SysUser sysUser = sysUserMapper.queryById(resetPasswordDto.getUserId());
|
||||
|
||||
try {
|
||||
// 假设客户端已经对密码进行了一次MD5加密,服务端使用双重加密验证
|
||||
String oldEncryptedPwd = MD5Util.doubleEncrypt(resetPasswordDto.getOldPassword(), sysUser.getSalt());
|
||||
Assert.equals(oldEncryptedPwd, sysUser.getPwd(), "旧密码不正确");
|
||||
|
||||
// 对新密码进行双重加密
|
||||
// 5. 参照密码创建时候的逻辑,对新密码进行双重加密
|
||||
String newEncryptedPwd = MD5Util.doubleEncrypt(resetPasswordDto.getNewPassword(), sysUser.getSalt());
|
||||
sysUser.setPwd(newEncryptedPwd);
|
||||
sysUserMapper.update(sysUser);
|
||||
|
||||
// 清除旧的token
|
||||
// 6. 清除旧的token
|
||||
CacheManager.removeTokenFromCache(sysUser.getUserName());
|
||||
|
||||
return true;
|
||||
|
|
@ -662,27 +645,29 @@ public class SysUserServiceImpl implements SysUserService {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean resetPasswordByAdmin(ResetPasswordDto resetPasswordDto) {
|
||||
Assert.notNull(resetPasswordDto.getUserName(), "用户名或手机号不能位空");
|
||||
Assert.notNull(resetPasswordDto.getNewPassword(), "新密码不能位空");
|
||||
|
||||
// 查询用户(支持用户名或手机号)
|
||||
SysUser sysUser = getUserByUsernameOrPhone(resetPasswordDto.getUserName());
|
||||
|
||||
try {
|
||||
// 假设客户端已经对密码进行了一次MD5加密,服务端使用双重加密验证
|
||||
String newEncryptedPwd = MD5Util.doubleEncrypt(resetPasswordDto.getNewPassword(), sysUser.getSalt());
|
||||
sysUser.setPwd(newEncryptedPwd);
|
||||
sysUserMapper.update(sysUser);
|
||||
|
||||
// 清除旧的token
|
||||
CacheManager.removeTokenFromCache(sysUser.getUserName());
|
||||
|
||||
/**
|
||||
* 检查用户是否是管理员
|
||||
*
|
||||
* @param userId 用户 ID
|
||||
* @return 是否是管理员
|
||||
*/
|
||||
private boolean isAdminUser(Long userId) {
|
||||
// 先检查是否在管理员用户列表中
|
||||
SysUser currentUser = sysUserMapper.queryById(userId);
|
||||
if (currentUser != null && Const.ADMIN_USER_LIST.contains(currentUser.getUserName().toLowerCase())) {
|
||||
return true;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
// 检查用户是否有管理员角色(通过角色编码判断)
|
||||
List<String> roles = queryUserRoles(userId);
|
||||
// 只要有一个角色编码是 "admin",就是管理员
|
||||
for (String role : roles) {
|
||||
if ("admin".equalsIgnoreCase(role)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -870,10 +855,13 @@ public class SysUserServiceImpl implements SysUserService {
|
|||
log.info("用户:{}的旧token已失效", sysUser.getUserName());
|
||||
}
|
||||
|
||||
// 使用Sa-Token登录,生成token
|
||||
cn.dev33.satoken.stp.StpUtil.login(sysUser.getUserId());
|
||||
// 获取生成的token
|
||||
String token = cn.dev33.satoken.stp.StpUtil.getTokenValue();
|
||||
// 查询用户角色列表
|
||||
List<String> roles = queryUserRoles(sysUser.getUserId());
|
||||
log.info("用户{}的角色列表:{}", sysUser.getUserName(), String.join(",", roles));
|
||||
|
||||
// 生成token并设置角色
|
||||
String token = generateToken(sysUser.getUserId(), roles);
|
||||
log.info("设置后Sa-Token中的角色列表:{}", String.join(",", cn.dev33.satoken.stp.StpUtil.getRoleList()));
|
||||
|
||||
// 创建LoginUser对象
|
||||
LoginUser loginUser = new LoginUser();
|
||||
|
|
@ -881,23 +869,6 @@ public class SysUserServiceImpl implements SysUserService {
|
|||
// 设置用户基本信息
|
||||
sysUser.setPwd(null);
|
||||
loginUser.setUserInfo(sysUser);
|
||||
|
||||
// 查询用户角色列表
|
||||
List<String> roles = new java.util.ArrayList<>();
|
||||
try {
|
||||
// 创建查询条件,查询用户的角色关联记录
|
||||
SysUserRole userRole = new SysUserRole();
|
||||
userRole.setUserId(sysUser.getUserId());
|
||||
List<SysUserRole> userRoles = sysUserRoleMapper.queryAll(userRole);
|
||||
|
||||
// 遍历角色关联记录,获取角色名称
|
||||
for (SysUserRole ur : userRoles) {
|
||||
roles.add("ROLE_" + ur.getRoleId());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("查询用户角色列表失败:{}", e.getMessage());
|
||||
roles = java.util.Collections.emptyList();
|
||||
}
|
||||
loginUser.setRoles(roles);
|
||||
|
||||
// 查询用户已购买的内容ID列表
|
||||
|
|
@ -922,10 +893,6 @@ public class SysUserServiceImpl implements SysUserService {
|
|||
Account account = accountMapper.queryByUserId(sysUser.getUserId());
|
||||
loginUser.setAccount(account);
|
||||
|
||||
// 查询用户积分信息
|
||||
PointsAccount pointsAccount = pointsAccountMapper.queryByUserId(sysUser.getUserId());
|
||||
loginUser.setPointsAccount(pointsAccount);
|
||||
|
||||
// 查询并设置用户最近点赞记录
|
||||
loginUser.setFavorites(queryRecentFavorites(sysUser.getUserId()));
|
||||
|
||||
|
|
@ -999,16 +966,6 @@ public class SysUserServiceImpl implements SysUserService {
|
|||
account.setDeleteFlag(Const.DELETE_FLAG_NO);
|
||||
accountMapper.insert(account);
|
||||
|
||||
// 初始化积分账户
|
||||
PointsAccount pointsAccount = new PointsAccount();
|
||||
pointsAccount.setUserId(sysUser.getUserId());
|
||||
pointsAccount.setUserName(sysUser.getUserName());
|
||||
pointsAccount.setTotalPoints(0);
|
||||
pointsAccount.setAvailablePoints(0);
|
||||
pointsAccount.setFrozenPoints(0);
|
||||
pointsAccount.setDeleteFlag(Const.DELETE_FLAG_NO);
|
||||
pointsAccountMapper.insert(pointsAccount);
|
||||
|
||||
return sysUser;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,288 @@
|
|||
package com.kexue.skills.service.impl;
|
||||
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.kexue.skills.entity.Account;
|
||||
import com.kexue.skills.entity.WithdrawalRecord;
|
||||
import com.kexue.skills.entity.dto.WithdrawalRecordDto;
|
||||
import com.kexue.skills.common.Assert;
|
||||
import com.kexue.skills.exception.BizException;
|
||||
import com.kexue.skills.mapper.WithdrawalRecordMapper;
|
||||
import com.kexue.skills.service.AccountService;
|
||||
import com.kexue.skills.service.WithdrawalRecordService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 提现记录Service实现类
|
||||
*
|
||||
* @author 王志维
|
||||
* @since 2026-03-25
|
||||
*/
|
||||
@Service("withdrawalRecordService")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public class WithdrawalRecordServiceImpl implements WithdrawalRecordService {
|
||||
@Resource
|
||||
private WithdrawalRecordMapper withdrawalRecordMapper;
|
||||
|
||||
@Resource
|
||||
private AccountService accountService;
|
||||
|
||||
/**
|
||||
* 提现手续费比例
|
||||
*/
|
||||
private static final BigDecimal FEE_RATE = new BigDecimal("0.02");
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*
|
||||
* @param queryDto 筛选条件
|
||||
* @return 查询结果
|
||||
*/
|
||||
@Override
|
||||
public PageInfo<WithdrawalRecord> getPageList(WithdrawalRecordDto queryDto) {
|
||||
PageHelper.startPage(queryDto.getPageNum(), queryDto.getPageSize());
|
||||
List<WithdrawalRecord> list = this.withdrawalRecordMapper.getPageList(queryDto);
|
||||
return new PageInfo<>(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*
|
||||
* @param queryDto 筛选条件
|
||||
* @return 查询结果
|
||||
*/
|
||||
@Override
|
||||
public List<WithdrawalRecord> getList(WithdrawalRecordDto queryDto) {
|
||||
return this.withdrawalRecordMapper.getList(queryDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过主键查询单条数据
|
||||
*
|
||||
* @param recordId 主键
|
||||
* @return 实例对象
|
||||
*/
|
||||
@Override
|
||||
public WithdrawalRecord queryById(Long recordId) {
|
||||
return this.withdrawalRecordMapper.queryById(recordId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过用户ID查询提现记录
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 实例对象
|
||||
*/
|
||||
@Override
|
||||
public List<WithdrawalRecord> queryByUserId(Long userId) {
|
||||
return this.withdrawalRecordMapper.queryByUserId(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过提现单号查询提现记录
|
||||
*
|
||||
* @param withdrawalNo 提现单号
|
||||
* @return 实例对象
|
||||
*/
|
||||
@Override
|
||||
public WithdrawalRecord queryByWithdrawalNo(String withdrawalNo) {
|
||||
return this.withdrawalRecordMapper.queryByWithdrawalNo(withdrawalNo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增数据
|
||||
*
|
||||
* @param withdrawalRecord 实例对象
|
||||
* @return 实例对象
|
||||
*/
|
||||
@Override
|
||||
public WithdrawalRecord insert(WithdrawalRecord withdrawalRecord) {
|
||||
// 设置创建时间和更新时间
|
||||
Date now = new Date();
|
||||
withdrawalRecord.setCreateTime(now);
|
||||
withdrawalRecord.setUpdateTime(now);
|
||||
// 设置默认值
|
||||
withdrawalRecord.setDeleteFlag(0);
|
||||
// 保存数据
|
||||
this.withdrawalRecordMapper.insert(withdrawalRecord);
|
||||
return withdrawalRecord;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新数据
|
||||
*
|
||||
* @param withdrawalRecord 实例对象
|
||||
* @return 实例对象
|
||||
*/
|
||||
@Override
|
||||
public WithdrawalRecord update(WithdrawalRecord withdrawalRecord) {
|
||||
// 设置更新时间
|
||||
withdrawalRecord.setUpdateTime(new Date());
|
||||
// 更新数据
|
||||
this.withdrawalRecordMapper.update(withdrawalRecord);
|
||||
return this.queryById(withdrawalRecord.getRecordId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新提现状态
|
||||
*
|
||||
* @param recordId 记录ID
|
||||
* @param status 状态
|
||||
* @return 影响行数
|
||||
*/
|
||||
@Override
|
||||
public int updateStatus(Long recordId, Integer status) {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("recordId", recordId);
|
||||
params.put("status", status);
|
||||
return this.withdrawalRecordMapper.updateStatus(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过主键逻辑删除
|
||||
*
|
||||
* @param recordId 主键
|
||||
* @param updateBy 更新人
|
||||
* @return 影响行数
|
||||
*/
|
||||
@Override
|
||||
public int logicDeleteById(Long recordId, String updateBy) {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("recordId", recordId);
|
||||
return this.withdrawalRecordMapper.logicDeleteById(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过主键物理删除
|
||||
*
|
||||
* @param recordId 主键
|
||||
* @return 影响行数
|
||||
*/
|
||||
@Override
|
||||
public int deleteById(Long recordId) {
|
||||
return this.withdrawalRecordMapper.deleteById(recordId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交提现申请
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param amount 提现金额
|
||||
* @param bankName 银行名称
|
||||
* @param bankAccount 银行账号
|
||||
* @param bankCardholder 持卡人姓名
|
||||
* @param remark 备注
|
||||
* @return 提现记录
|
||||
*/
|
||||
@Override
|
||||
public WithdrawalRecord submitWithdrawal(Long userId, BigDecimal amount, String bankName, String bankAccount, String bankCardholder, String remark) {
|
||||
// 1. 查询账户信息
|
||||
Account account = accountService.queryByUserId(userId);
|
||||
Assert.notNull(account, "账户不存在");
|
||||
|
||||
// 2. 检查可提现余额是否足够
|
||||
Assert.isTrue(account.getWithdrawableBalance().compareTo(amount) >= 0, "可提现余额不足");
|
||||
|
||||
// 3. 计算手续费和实际到账金额
|
||||
BigDecimal feeAmount = amount.multiply(FEE_RATE);
|
||||
BigDecimal actualAmount = amount.subtract(feeAmount);
|
||||
|
||||
// 4. 生成提现单号
|
||||
String withdrawalNo = generateWithdrawalNo();
|
||||
|
||||
// 5. 创建提现记录
|
||||
WithdrawalRecord record = new WithdrawalRecord();
|
||||
record.setUserId(userId);
|
||||
record.setUserName(account.getUserName());
|
||||
record.setWithdrawalAmount(amount);
|
||||
record.setFeeAmount(feeAmount);
|
||||
record.setActualAmount(actualAmount);
|
||||
record.setStatus(1); // 1.待处理
|
||||
record.setWithdrawalNo(withdrawalNo);
|
||||
record.setBankName(bankName);
|
||||
record.setBankAccount(bankAccount);
|
||||
record.setBankCardholder(bankCardholder);
|
||||
record.setRemark(remark);
|
||||
record.setCreateTime(new Date());
|
||||
record.setUpdateTime(new Date());
|
||||
record.setDeleteFlag(0);
|
||||
this.withdrawalRecordMapper.insert(record);
|
||||
|
||||
// 6. 冻结可提现余额
|
||||
account.setWithdrawableBalance(account.getWithdrawableBalance().subtract(amount));
|
||||
account.setFrozenAmount(account.getFrozenAmount().add(amount));
|
||||
account.setUpdateTime(new Date());
|
||||
accountService.update(account);
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理提现
|
||||
*
|
||||
* @param recordId 记录ID
|
||||
* @param status 状态
|
||||
* @param remark 备注
|
||||
* @return 影响行数
|
||||
*/
|
||||
@Override
|
||||
public int processWithdrawal(Long recordId, Integer status, String remark) {
|
||||
// 1. 查询提现记录
|
||||
WithdrawalRecord record = this.queryById(recordId);
|
||||
Assert.notNull(record, "提现记录不存在");
|
||||
|
||||
// 2. 检查状态是否为待处理
|
||||
Assert.isTrue(record.getStatus() == 1, "提现记录状态不正确");
|
||||
|
||||
// 3. 更新提现状态
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("recordId", recordId);
|
||||
params.put("status", status);
|
||||
int result = this.withdrawalRecordMapper.updateStatus(params);
|
||||
|
||||
// 4. 如果提现成功,减少可提现余额,增加冻结金额
|
||||
if (status == 3) { // 3.成功
|
||||
Account account = accountService.queryByUserId(record.getUserId());
|
||||
if (account != null) {
|
||||
// 减少冻结金额
|
||||
account.setFrozenAmount(account.getFrozenAmount().subtract(record.getWithdrawalAmount()));
|
||||
// 减少总余额
|
||||
account.setBalance(account.getBalance().subtract(record.getWithdrawalAmount()));
|
||||
account.setUpdateTime(new Date());
|
||||
accountService.update(account);
|
||||
}
|
||||
} else if (status == 4) { // 4.失败
|
||||
// 解冻金额
|
||||
Account account = accountService.queryByUserId(record.getUserId());
|
||||
if (account != null) {
|
||||
account.setFrozenAmount(account.getFrozenAmount().subtract(record.getWithdrawalAmount()));
|
||||
account.setWithdrawableBalance(account.getWithdrawableBalance().add(record.getWithdrawalAmount()));
|
||||
account.setUpdateTime(new Date());
|
||||
accountService.update(account);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成提现单号
|
||||
*
|
||||
* @return 提现单号
|
||||
*/
|
||||
private String generateWithdrawalNo() {
|
||||
// 提现单号生成规则:W + 时间戳 + 6位随机数
|
||||
String timestamp = String.valueOf(System.currentTimeMillis());
|
||||
String random = UUID.randomUUID().toString().substring(0, 6).replaceAll("-", "");
|
||||
return "W" + timestamp + random;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDHxqhqf/A5f+Ya
|
||||
Qpbu9/vVwlvbMQULdcpzBXgDAQ6WyEz3/0kYRW8Kg3iAkQyjCH3nPiaC5XwWtItu
|
||||
CmNYz8gon3N5xLS0y0usD+aQWGMFplm4cj/UgwwhZqiapTNPncKhVFsaLFkDxDlJ
|
||||
vd58H9LD3TnJt1wlNQ/gUSfHx0BtKNj0c3tV3sa7lY4gTvVyCjK1Okfm9xYzFJmL
|
||||
dnBQUOUNnMwpsUV7BI6yFZZWAz/n+BXK84FLRrLxg5ErZ9d/qV8VdIbHD1+pVFHt
|
||||
g6HQcV7+DEPyffOeF/4QWUAGGUDnii/emcwARn9sDuMo3pihDJ9uhh/GmZDchGos
|
||||
MvfGbe5vAgMBAAECggEAKJESie3I3iQ2mYaEF6qLnPCGro/ZsmYM4iZuJE4GpF+w
|
||||
IXvZX/BZiA0CXzkVE9YZmudn8pSfCg1HcuTxH4ux3W3jiQqEl2Hgz+O6sf0Avpj1
|
||||
BxtMEt85i17JRf3d2YDzkMcMaNgwiy9BYtQHZbUgm11E6s0tDPEldCuGpG61inSx
|
||||
FX+LPonQ60IicBoTacZqcSFpEDOdW45anfq3xmwVE3lbCvbVeOGkakp/6Tf0yzx1
|
||||
E89b3r2uoi3JIjtGYZptX+HboFwALUytAnl8W42GJltLBVQPWTonOFJ2bbAjjYgu
|
||||
b+fEshrqzYa9r97tNSJ5Yn2KkDluPBfebW5BmiizQQKBgQDpbe7uwk0TMECBjHeQ
|
||||
iFwwyOntEuFHP6+cPyX1b/xglHlIEljuWS+upCmmnymLUqc0n0HFXEH2T0UYhmVb
|
||||
jP1gOikiU3+9aE8dD5IExPj2iS5n5QcVQz1dazUNnGGJFWezgQ/WBtluiXyrGCJB
|
||||
5elHm+pLdUsNuYNkMSdJhu+J9wKBgQDbF7QCucp/ggo5jEpwJlrmxRL8idGuV24a
|
||||
r2U4wfBrjRkUb6Spn7uI89uU2KHhdVv0K9b7c2m9owFjo08cXSZcEiwB/pwqbHqB
|
||||
SneFtcsD95IKhCIPk2FtvGx5M1gggzFp+TxPgO+MmyKpgsIWyZwtVHAG1ljGltgx
|
||||
gdebT69hSQKBgQDVlUYC5nvZa9QJ9TnNYEdiR+NqjVTdeVM4VrtnqHC2+gNCw67l
|
||||
X2t2kzSdBZLgrN7bEkD+0Vx1f7CMMSR6sTWBH5ZVlysRZmjFAWMsxAG8qmZwn6ls
|
||||
dhqm6Johew3vfUtmfle8EIZQUrJkZm+p9jEN2YZ2RrtGspCbUzJMX4+7nwKBgQCh
|
||||
YN8+Fr3al92SMAzweMACNW1byORC57F5RHJpkSjW/7JWhDmkm7yWDxFRnRP4LurR
|
||||
eq06v/NGNNgkHTl7af2EWfpCadl7wjWmIETTn2lvfZ770gIIuQVNwDmiOLiUEi6G
|
||||
oYfUA+PvDKJGe8Mc59n65bQyxRXVCW0rYjl+8/35yQKBgDFiaVey4oLTFAGwCFSu
|
||||
FDccbNdhSXnljgmU03uUf/5F/BMWAfHxjIg5vmcKq+ULi/sUGqnZmR5R4+FsACcm
|
||||
xRnU2YUbfYg9PX8CcyCrks5IP+ut07Pu7WxT0KxI01cHfIyO3Bx0AZpx1ceMwJOX
|
||||
i1aeYnJYFT1jitBgYWIH2Lx5
|
||||
-----END PRIVATE KEY-----
|
||||
|
|
@ -105,14 +105,12 @@ web:
|
|||
upload:
|
||||
path: /kexue/agentSkills/upload/
|
||||
|
||||
# 支付配置
|
||||
|
||||
payment:
|
||||
# 微信支付配置
|
||||
wechat:
|
||||
appId: wx7d13d99de5be3bfa
|
||||
mchId: 1673321732
|
||||
mchKey: UDuZXDcmy5Eb6o0nTNZhu6ek4DDh4K8B
|
||||
mchKey: e10adc3949ba59abbe56e057f20f883e
|
||||
mchSerialNo: 5EFC47D3AA59BFD1AAE548F96B5E19E1C60F067D
|
||||
privateKeyPath: apiclient_key.pem
|
||||
domain: https://api.mch.weixin.qq.com
|
||||
|
|
|
|||
|
|
@ -107,19 +107,19 @@ payment:
|
|||
wechat:
|
||||
appId: wx7d13d99de5be3bfa
|
||||
mchId: 1673321732
|
||||
mchKey: UDuZXDcmy5Eb6o0nTNZhu6ek4DDh4K8B
|
||||
mchKey: e10adc3949ba59abbe56e057f20f883e
|
||||
mchSerialNo: 5EFC47D3AA59BFD1AAE548F96B5E19E1C60F067D
|
||||
privateKeyPath: apiclient_key.pem
|
||||
domain: https://api.mch.weixin.qq.com
|
||||
notifyUrl: http://43.248.97.19:19000/pay/wx/notify
|
||||
returnUrl: http://43.248.97.19:19000/pay/success
|
||||
notifyUrl: https://skills.xueai.art/api/pay/wx/notify
|
||||
returnUrl: https://skills.xueai.art/pay/success
|
||||
# 支付宝支付配置
|
||||
alipay:
|
||||
appId: 2021004138642603
|
||||
privateKey: "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCBzqh/sBfx8UsiKIzSaGm3QwMape+PEmHaFYsP0XxRLyRe5d3H1r4JBvE/GBCDarXkZMvJ3PKeUpO61i7LZtNBN7M6XbMvs/eiaipUmWCNF4IK+ilhOqr8GNryjb6tBnO0uzd1c7UmAvxF+MKkxti0qfscP+Tr6pkuF14DofOyITY56x0y36DmEc85rr213llQ/bSX5nVPHWfddtrqB+TNFvrnAN10NxnUurDK+wd7fKq3TgMjYNDINPGxFYbYezeIGvYj6E34mFS3b3wyxTcB5DeL4GV1FAWqJ0Yk+3hHT3OdmK/UnNEr55w+r3liQBCDRBoE4fldbG5CtW2/ER0lAgMBAAECggEAC2kgNMFFCZaddS49Ws2k5WA1qKUHjvsdsO8N32EZ3YUYXGM2gLem0uJSWKqD4RmDTcVyiJcsmLBHnjfvux+Z2HTOA4ZzFvFqBlPwzqkA7MYxP0fIVWyz1R9WN9Yv+cPEbhG7CU8XkHTYuknoylVUfWUn1s7jD736oyuYrxcQdgsHOHpLHngvsELLa1pv2EURohvr4p+zirMjFGuz3BVaGgVpWegn0nJ/8n1y0ZTM04Mvm/zXpGQQxfuUxrK2owQMFViY7BrRQXlPVeUM/IPAx7cvxLkR5hl9UgAl+nH6FCsm5osEvUln6VLhGTmNFBLCN9piX6sZaPEKZKRBCz0GQQKBgQDD4Dq+dGQAoCmnXdkzI6DHGXOePa+sGnj1Y55dRcrPWPKtCPeEnIPxhVCJ2+cqYK49K6youhLYzs3h8y/M4lh0JQLKP4zTSyZnARmdYHW/SyTu9BcHCdiPwZBrmn7bCWotGf3r5QRIJT6ilZEj8cLnF+9+gd0YyLyRE7Rmihq2UQKBgQCpps7qghRv3JIB0Hb4nBCbAeyPMjHj+7BSUsui2Dhdg3MeTk48a7RLl+r139pMzgTm5Pj0VG6qslUeqP2HlQ46o0Z2bPeohfXH4zMJi2amh4MvAFp4t8eNCc6faeqpJPTTQj3hS4drFnHEHBeFfgFCXZKhjYeP7SP2WVLOQvUAlQKBgFIq6fmjEaBBj7ep4sdVFsjuoFWtQthLcppd47z03hMFGSgFLu/uSFs0tYhfOyXH0M/QVmmhRO62Mh+qyE6GVNzD+dultQmd6Mok5/3gzQQmHaQvuMk3FCWZ6V96O+Temi+5S499TsKE/TVu0Kfnbv9KRykmiP0wmAmz3mV1YadBAoGABc5quIX5MxbmfF9pIvscamG3efMq1/WuRDMHOyyRSUoNb5UYgmLhSdEKPp4Jt6U5b7mYd6xIGVl/Jkx8WN6WHRWnfLggBcmH7u5sub/mpH5w0/P8JLONhds3Eieq210jb/ONcJ+II/chr6eSeoQkgOP498SDRj7Eg1LtTZfnEL0CgYBnMeXQWKUae+xES5NsEX7D0lwSCotb7attTHeE6vZOI77TzsURb4jOEAhYVsdJ8lm+J1Sv1Wjnr2yMxiRH2G+I1tUxGcI8OkRT26FxFdMl1RdbTf+gDM8IjMiu+Li+plIzb28VtF8q/Umgd+5LTlSBmM2yoiL8RKtmStjr5iIuhA=="
|
||||
publicKey: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnakP04nUmsoFoveIvOhbLqkA1xQuYtvkrqq2AVvTsbtqpsEOTm9G095e2rBYLp89oDcf6L6BhtJPwdrhnA+qifUyVmACI9sprrsGeRYQgndK7y4c6spQcSnsnakSxlIp22j7pvBXNAZuqud2hQV+TOLKEUh1W3izTgMj/Ejoh3ZsCjgDRtTVgaytzSdHYrhNku+pIrl15/xVGJED99RYXkR8GHawxuK+vWVmxU0tiTCwTsqLz43v6TtCZ+/UfLL/luwp9B4ZvB+0qon82LILYr6oxs10kE2IAvryuDToAc1s/v/36jgt+7DXwqzfUDksHhVLHdJHChyc4ax5HmMsBwIDAQAB"
|
||||
notifyUrl: http://43.248.97.19:19000/pay/ali-pay/trade/notify
|
||||
returnUrl: https://shuziren.xueai.art/alipay-success
|
||||
notifyUrl: https://skills.xueai.art/api/pay/alipay/trade/notify
|
||||
returnUrl: https://skills.xueai.art/alipay-success
|
||||
signType: RSA2
|
||||
charset: UTF-8
|
||||
gatewayUrl: https://openapi.alipay.com/gateway.do
|
||||
|
|
@ -8,7 +8,7 @@ spring:
|
|||
pathmatch:
|
||||
matching-strategy: ANT_PATH_MATCHER
|
||||
static-path-pattern: /**
|
||||
date-format: yyyy-MM-dd
|
||||
date-format: yyyy-MM-dd HH:mm:ss
|
||||
favicon:
|
||||
enabled: true
|
||||
thymeleaf:
|
||||
|
|
|
|||
|
|
@ -16,6 +16,13 @@
|
|||
<result property="businessId" column="business_id" jdbcType="BIGINT"/>
|
||||
<result property="businessType" column="business_type" jdbcType="VARCHAR"/>
|
||||
<result property="remark" column="remark" jdbcType="VARCHAR"/>
|
||||
<result property="isExpense" column="is_expense" jdbcType="INTEGER"/>
|
||||
<result property="inputToken" column="input_token" jdbcType="INTEGER"/>
|
||||
<result property="outputToken" column="output_token" jdbcType="INTEGER"/>
|
||||
<result property="totalTokens" column="total_tokens" jdbcType="INTEGER"/>
|
||||
<result property="modelName" column="model_name" jdbcType="VARCHAR"/>
|
||||
<result property="question" column="question" jdbcType="LONGVARCHAR"/>
|
||||
<result property="incomeType" column="income_type" jdbcType="VARCHAR"/>
|
||||
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
|
||||
<result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
|
||||
<result property="createBy" column="create_by" jdbcType="VARCHAR"/>
|
||||
|
|
@ -27,7 +34,8 @@
|
|||
<select id="queryById" resultMap="AccountTransactionMap">
|
||||
select
|
||||
transaction_id, user_id, user_name, transaction_type, amount, before_balance, after_balance, status,
|
||||
transaction_no, pay_type, business_id, business_type, remark, create_time, update_time, create_by, update_by, delete_flag
|
||||
transaction_no, pay_type, business_id, business_type, remark, is_expense, input_token, output_token,
|
||||
total_tokens, model_name, question, income_type, create_time, update_time, create_by, update_by, delete_flag
|
||||
from account_transaction
|
||||
where transaction_id = #{transactionId}
|
||||
</select>
|
||||
|
|
@ -36,7 +44,8 @@
|
|||
<select id="getPageList" resultMap="AccountTransactionMap">
|
||||
select
|
||||
transaction_id, user_id, user_name, transaction_type, amount, before_balance, after_balance, status,
|
||||
transaction_no, pay_type, business_id, business_type, remark, create_time, update_time, create_by, update_by, delete_flag
|
||||
transaction_no, pay_type, business_id, business_type, remark, is_expense, input_token, output_token,
|
||||
total_tokens, model_name, question, income_type, create_time, update_time, create_by, update_by, delete_flag
|
||||
from account_transaction
|
||||
<where>
|
||||
<if test="userId != null">
|
||||
|
|
@ -73,7 +82,8 @@
|
|||
<select id="getList" resultMap="AccountTransactionMap">
|
||||
select
|
||||
transaction_id, user_id, user_name, transaction_type, amount, before_balance, after_balance, status,
|
||||
transaction_no, pay_type, business_id, business_type, remark, create_time, update_time, create_by, update_by, delete_flag
|
||||
transaction_no, pay_type, business_id, business_type, remark, is_expense, input_token, output_token,
|
||||
total_tokens, model_name, question, income_type, create_time, update_time, create_by, update_by, delete_flag
|
||||
from account_transaction
|
||||
<where>
|
||||
<if test="userId != null">
|
||||
|
|
@ -119,6 +129,13 @@
|
|||
<if test="businessId != null">business_id,</if>
|
||||
<if test="businessType != null">business_type,</if>
|
||||
<if test="remark != null">remark,</if>
|
||||
<if test="isExpense != null">is_expense,</if>
|
||||
<if test="inputToken != null">input_token,</if>
|
||||
<if test="outputToken != null">output_token,</if>
|
||||
<if test="totalTokens != null">total_tokens,</if>
|
||||
<if test="modelName != null">model_name,</if>
|
||||
<if test="question != null">question,</if>
|
||||
<if test="incomeType != null">income_type,</if>
|
||||
<if test="createTime != null">create_time,</if>
|
||||
<if test="updateTime != null">update_time,</if>
|
||||
<if test="createBy != null">create_by,</if>
|
||||
|
|
@ -138,6 +155,13 @@
|
|||
<if test="businessId != null">#{businessId},</if>
|
||||
<if test="businessType != null">#{businessType},</if>
|
||||
<if test="remark != null">#{remark},</if>
|
||||
<if test="isExpense != null">#{isExpense},</if>
|
||||
<if test="inputToken != null">#{inputToken},</if>
|
||||
<if test="outputToken != null">#{outputToken},</if>
|
||||
<if test="totalTokens != null">#{totalTokens},</if>
|
||||
<if test="modelName != null">#{modelName},</if>
|
||||
<if test="question != null">#{question},</if>
|
||||
<if test="incomeType != null">#{incomeType},</if>
|
||||
<if test="createTime != null">#{createTime},</if>
|
||||
<if test="updateTime != null">#{updateTime},</if>
|
||||
<if test="createBy != null">#{createBy},</if>
|
||||
|
|
@ -162,6 +186,13 @@
|
|||
<if test="businessId != null">business_id = #{businessId},</if>
|
||||
<if test="businessType != null">business_type = #{businessType},</if>
|
||||
<if test="remark != null">remark = #{remark},</if>
|
||||
<if test="isExpense != null">is_expense = #{isExpense},</if>
|
||||
<if test="inputToken != null">input_token = #{inputToken},</if>
|
||||
<if test="outputToken != null">output_token = #{outputToken},</if>
|
||||
<if test="totalTokens != null">total_tokens = #{totalTokens},</if>
|
||||
<if test="modelName != null">model_name = #{modelName},</if>
|
||||
<if test="question != null">question = #{question},</if>
|
||||
<if test="incomeType != null">income_type = #{incomeType},</if>
|
||||
<if test="updateTime != null">update_time = #{updateTime},</if>
|
||||
<if test="updateBy != null">update_by = #{updateBy},</if>
|
||||
<if test="deleteFlag != null">delete_flag = #{deleteFlag},</if>
|
||||
|
|
@ -183,4 +214,16 @@
|
|||
delete from account_transaction
|
||||
where transaction_id = #{transactionId}
|
||||
</delete>
|
||||
|
||||
<!--通过用户ID查询交易记录-->
|
||||
<select id="queryByUserId" resultMap="AccountTransactionMap">
|
||||
select
|
||||
transaction_id, user_id, user_name, transaction_type, amount, before_balance, after_balance, status,
|
||||
transaction_no, pay_type, business_id, business_type, remark, is_expense, input_token, output_token,
|
||||
total_tokens, model_name, question, income_type, create_time, update_time, create_by, update_by, delete_flag
|
||||
from account_transaction
|
||||
where user_id = #{userId}
|
||||
and delete_flag = 0
|
||||
order by create_time desc
|
||||
</select>
|
||||
</mapper>
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
<?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.ModelPriceMapper">
|
||||
|
||||
<resultMap type="com.kexue.skills.entity.ModelPrice" id="ModelPriceMap">
|
||||
<result property="id" column="id" jdbcType="BIGINT"/>
|
||||
<result property="vendor" column="vendor" jdbcType="VARCHAR"/>
|
||||
<result property="modelName" column="model_name" jdbcType="VARCHAR"/>
|
||||
<result property="inputPrice" column="input_price" jdbcType="DECIMAL"/>
|
||||
<result property="outputPrice" column="output_price" jdbcType="DECIMAL"/>
|
||||
<result property="inputPerCent" column="input_per_cent" jdbcType="BIGINT"/>
|
||||
<result property="outputPerCent" column="output_per_cent" jdbcType="BIGINT"/>
|
||||
<result property="unit" column="unit" jdbcType="VARCHAR"/>
|
||||
<result property="remark" column="remark" jdbcType="VARCHAR"/>
|
||||
<result property="createdTime" column="created_time" jdbcType="TIMESTAMP"/>
|
||||
<result property="updatedTime" column="updated_time" jdbcType="TIMESTAMP"/>
|
||||
</resultMap>
|
||||
|
||||
<!--查询单个-->
|
||||
<select id="queryById" resultMap="ModelPriceMap">
|
||||
select
|
||||
id, vendor, model_name, input_price, output_price, input_per_cent, output_per_cent, unit, remark, created_time, updated_time
|
||||
from model_price
|
||||
where id = #{id}
|
||||
</select>
|
||||
|
||||
<!--通过模型名称查询-->
|
||||
<select id="queryByModelName" resultMap="ModelPriceMap">
|
||||
select
|
||||
id, vendor, model_name, input_price, output_price, input_per_cent, output_per_cent, unit, remark, created_time, updated_time
|
||||
from model_price
|
||||
where model_name = #{modelName}
|
||||
</select>
|
||||
|
||||
<!--分页查询-->
|
||||
<select id="getPageList" resultMap="ModelPriceMap">
|
||||
select
|
||||
id, vendor, model_name, input_price, output_price, input_per_cent, output_per_cent, unit, remark, created_time, updated_time
|
||||
from model_price
|
||||
<where>
|
||||
<if test="vendor != null and vendor != ''">
|
||||
and vendor = #{vendor}
|
||||
</if>
|
||||
<if test="modelName != null and modelName != ''">
|
||||
and model_name like concat('%', #{modelName}, '%')
|
||||
</if>
|
||||
</where>
|
||||
<if test="sortBy != null and sortBy != ''">
|
||||
order by ${sortBy} ${sortDesc ? 'desc' : 'asc'}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<!--查询列表-->
|
||||
<select id="getList" resultMap="ModelPriceMap">
|
||||
select
|
||||
id, vendor, model_name, input_price, output_price, input_per_cent, output_per_cent, unit, remark, created_time, updated_time
|
||||
from model_price
|
||||
<where>
|
||||
<if test="vendor != null and vendor != ''">
|
||||
and vendor = #{vendor}
|
||||
</if>
|
||||
<if test="modelName != null and modelName != ''">
|
||||
and model_name like concat('%', #{modelName}, '%')
|
||||
</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
<!--新增数据-->
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||
insert into model_price
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
<if test="vendor != null">vendor,</if>
|
||||
<if test="modelName != null">model_name,</if>
|
||||
<if test="inputPrice != null">input_price,</if>
|
||||
<if test="outputPrice != null">output_price,</if>
|
||||
<if test="inputPerCent != null">input_per_cent,</if>
|
||||
<if test="outputPerCent != null">output_per_cent,</if>
|
||||
<if test="unit != null">unit,</if>
|
||||
<if test="remark != null">remark,</if>
|
||||
<if test="createdTime != null">created_time,</if>
|
||||
<if test="updatedTime != null">updated_time,</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="vendor != null">#{vendor},</if>
|
||||
<if test="modelName != null">#{modelName},</if>
|
||||
<if test="inputPrice != null">#{inputPrice},</if>
|
||||
<if test="outputPrice != null">#{outputPrice},</if>
|
||||
<if test="inputPerCent != null">#{inputPerCent},</if>
|
||||
<if test="outputPerCent != null">#{outputPerCent},</if>
|
||||
<if test="unit != null">#{unit},</if>
|
||||
<if test="remark != null">#{remark},</if>
|
||||
<if test="createdTime != null">#{createdTime},</if>
|
||||
<if test="updatedTime != null">#{updatedTime},</if>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
<!--更新数据-->
|
||||
<update id="update">
|
||||
update model_price
|
||||
<set>
|
||||
<if test="vendor != null">vendor = #{vendor},</if>
|
||||
<if test="modelName != null">model_name = #{modelName},</if>
|
||||
<if test="inputPrice != null">input_price = #{inputPrice},</if>
|
||||
<if test="outputPrice != null">output_price = #{outputPrice},</if>
|
||||
<if test="inputPerCent != null">input_per_cent = #{inputPerCent},</if>
|
||||
<if test="outputPerCent != null">output_per_cent = #{outputPerCent},</if>
|
||||
<if test="unit != null">unit = #{unit},</if>
|
||||
<if test="remark != null">remark = #{remark},</if>
|
||||
<if test="createdTime != null">created_time = #{createdTime},</if>
|
||||
<if test="updatedTime != null">updated_time = #{updatedTime},</if>
|
||||
</set>
|
||||
where id = #{id}
|
||||
</update>
|
||||
|
||||
<!--删除数据-->
|
||||
<delete id="deleteById">
|
||||
delete from model_price
|
||||
where id = #{id}
|
||||
</delete>
|
||||
|
||||
</mapper>
|
||||
|
|
@ -1,156 +0,0 @@
|
|||
<?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.PointsAccountMapper">
|
||||
|
||||
<resultMap type="com.kexue.skills.entity.PointsAccount" id="PointsAccountMap">
|
||||
<result property="accountId" column="account_id" jdbcType="BIGINT"/>
|
||||
<result property="userId" column="user_id" jdbcType="BIGINT"/>
|
||||
<result property="userName" column="user_name" jdbcType="VARCHAR"/>
|
||||
<result property="totalPoints" column="total_points" jdbcType="INTEGER"/>
|
||||
<result property="availablePoints" column="available_points" jdbcType="INTEGER"/>
|
||||
<result property="frozenPoints" column="frozen_points" jdbcType="INTEGER"/>
|
||||
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
|
||||
<result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
|
||||
<result property="createBy" column="create_by" jdbcType="VARCHAR"/>
|
||||
<result property="updateBy" column="update_by" jdbcType="VARCHAR"/>
|
||||
<result property="deleteFlag" column="delete_flag" jdbcType="INTEGER"/>
|
||||
</resultMap>
|
||||
|
||||
<!--查询单个-->
|
||||
<select id="queryById" resultMap="PointsAccountMap">
|
||||
select
|
||||
account_id, user_id, user_name, total_points, available_points, frozen_points, create_time, update_time, create_by, update_by, delete_flag
|
||||
from points_account
|
||||
where account_id = #{accountId}
|
||||
</select>
|
||||
|
||||
<!--通过用户ID查询-->
|
||||
<select id="queryByUserId" resultMap="PointsAccountMap">
|
||||
select
|
||||
account_id, user_id, user_name, total_points, available_points, frozen_points, create_time, update_time, create_by, update_by, delete_flag
|
||||
from points_account
|
||||
where user_id = #{userId}
|
||||
</select>
|
||||
|
||||
<!--分页查询-->
|
||||
<select id="getPageList" resultMap="PointsAccountMap">
|
||||
select
|
||||
account_id, user_id, user_name, total_points, available_points, frozen_points, create_time, update_time, create_by, update_by, delete_flag
|
||||
from points_account
|
||||
<where>
|
||||
<if test="userId != null">
|
||||
and user_id = #{userId}
|
||||
</if>
|
||||
<if test="userName != null and userName != ''">
|
||||
and user_name like concat('%', #{userName}, '%')
|
||||
</if>
|
||||
<if test="totalPoints != null">
|
||||
and total_points <![CDATA[ >= ]]> #{totalPoints}
|
||||
</if>
|
||||
<if test="deleteFlag != null">
|
||||
and delete_flag = #{deleteFlag}
|
||||
</if>
|
||||
</where>
|
||||
<if test="sortBy != null and sortBy != ''">
|
||||
order by ${sortBy} ${sortDesc ? 'desc' : 'asc'}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<!--查询列表-->
|
||||
<select id="getList" resultMap="PointsAccountMap">
|
||||
select
|
||||
account_id, user_id, user_name, total_points, available_points, frozen_points, create_time, update_time, create_by, update_by, delete_flag
|
||||
from points_account
|
||||
<where>
|
||||
<if test="userId != null">
|
||||
and user_id = #{userId}
|
||||
</if>
|
||||
<if test="userName != null and userName != ''">
|
||||
and user_name like concat('%', #{userName}, '%')
|
||||
</if>
|
||||
<if test="totalPoints != null">
|
||||
and total_points <![CDATA[ >= ]]> #{totalPoints}
|
||||
</if>
|
||||
<if test="deleteFlag != null">
|
||||
and delete_flag = #{deleteFlag}
|
||||
</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
<!--新增数据-->
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="accountId">
|
||||
insert into points_account
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
<if test="userId != null">user_id,</if>
|
||||
<if test="userName != null">user_name,</if>
|
||||
<if test="totalPoints != null">total_points,</if>
|
||||
<if test="availablePoints != null">available_points,</if>
|
||||
<if test="frozenPoints != null">frozen_points,</if>
|
||||
<if test="createTime != null">create_time,</if>
|
||||
<if test="updateTime != null">update_time,</if>
|
||||
<if test="createBy != null">create_by,</if>
|
||||
<if test="updateBy != null">update_by,</if>
|
||||
<if test="deleteFlag != null">delete_flag,</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="userId != null">#{userId},</if>
|
||||
<if test="userName != null">#{userName},</if>
|
||||
<if test="totalPoints != null">#{totalPoints},</if>
|
||||
<if test="availablePoints != null">#{availablePoints},</if>
|
||||
<if test="frozenPoints != null">#{frozenPoints},</if>
|
||||
<if test="createTime != null">#{createTime},</if>
|
||||
<if test="updateTime != null">#{updateTime},</if>
|
||||
<if test="createBy != null">#{createBy},</if>
|
||||
<if test="updateBy != null">#{updateBy},</if>
|
||||
<if test="deleteFlag != null">#{deleteFlag},</if>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
<!--更新数据-->
|
||||
<update id="update">
|
||||
update points_account
|
||||
<set>
|
||||
<if test="userId != null">user_id = #{userId},</if>
|
||||
<if test="userName != null">user_name = #{userName},</if>
|
||||
<if test="totalPoints != null">total_points = #{totalPoints},</if>
|
||||
<if test="availablePoints != null">available_points = #{availablePoints},</if>
|
||||
<if test="frozenPoints != null">frozen_points = #{frozenPoints},</if>
|
||||
<if test="updateTime != null">update_time = #{updateTime},</if>
|
||||
<if test="updateBy != null">update_by = #{updateBy},</if>
|
||||
<if test="deleteFlag != null">delete_flag = #{deleteFlag},</if>
|
||||
</set>
|
||||
where account_id = #{accountId}
|
||||
</update>
|
||||
|
||||
<!--更新积分-->
|
||||
<update id="updatePoints">
|
||||
update points_account
|
||||
<set>
|
||||
<if test="type == 1">
|
||||
total_points = total_points + #{points},
|
||||
available_points = available_points + #{points}
|
||||
</if>
|
||||
<if test="type == 2">
|
||||
total_points = total_points - #{points},
|
||||
available_points = available_points - #{points}
|
||||
</if>
|
||||
update_time = now()
|
||||
</set>
|
||||
where user_id = #{userId}
|
||||
</update>
|
||||
|
||||
<!--逻辑删除-->
|
||||
<update id="logicDeleteById">
|
||||
update points_account
|
||||
set delete_flag = 1,
|
||||
update_by = #{updateBy},
|
||||
update_time = now()
|
||||
where account_id = #{accountId}
|
||||
</update>
|
||||
|
||||
<!--物理删除-->
|
||||
<delete id="deleteById">
|
||||
delete from points_account
|
||||
where account_id = #{accountId}
|
||||
</delete>
|
||||
</mapper>
|
||||
|
|
@ -1,186 +0,0 @@
|
|||
<?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.PointsTransactionMapper">
|
||||
|
||||
<resultMap type="com.kexue.skills.entity.PointsTransaction" id="PointsTransactionMap">
|
||||
<result property="transactionId" column="transaction_id" jdbcType="BIGINT"/>
|
||||
<result property="userId" column="user_id" jdbcType="BIGINT"/>
|
||||
<result property="userName" column="user_name" jdbcType="VARCHAR"/>
|
||||
<result property="transactionType" column="transaction_type" jdbcType="INTEGER"/>
|
||||
<result property="points" column="points" jdbcType="INTEGER"/>
|
||||
<result property="beforePoints" column="before_points" jdbcType="INTEGER"/>
|
||||
<result property="afterPoints" column="after_points" jdbcType="INTEGER"/>
|
||||
<result property="status" column="status" jdbcType="INTEGER"/>
|
||||
<result property="transactionNo" column="transaction_no" jdbcType="VARCHAR"/>
|
||||
<result property="payType" column="pay_type" jdbcType="INTEGER"/>
|
||||
<result property="businessId" column="business_id" jdbcType="BIGINT"/>
|
||||
<result property="businessType" column="business_type" jdbcType="VARCHAR"/>
|
||||
<result property="remark" column="remark" jdbcType="VARCHAR"/>
|
||||
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
|
||||
<result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
|
||||
<result property="createBy" column="create_by" jdbcType="VARCHAR"/>
|
||||
<result property="updateBy" column="update_by" jdbcType="VARCHAR"/>
|
||||
<result property="deleteFlag" column="delete_flag" jdbcType="INTEGER"/>
|
||||
</resultMap>
|
||||
|
||||
<!--查询单个-->
|
||||
<select id="queryById" resultMap="PointsTransactionMap">
|
||||
select
|
||||
transaction_id, user_id, user_name, transaction_type, points, before_points, after_points, status,
|
||||
transaction_no, pay_type, business_id, business_type, remark, create_time, update_time, create_by, update_by, delete_flag
|
||||
from points_transaction
|
||||
where transaction_id = #{transactionId}
|
||||
</select>
|
||||
|
||||
<!--分页查询-->
|
||||
<select id="getPageList" resultMap="PointsTransactionMap">
|
||||
select
|
||||
transaction_id, user_id, user_name, transaction_type, points, before_points, after_points, status,
|
||||
transaction_no, pay_type, business_id, business_type, remark, create_time, update_time, create_by, update_by, delete_flag
|
||||
from points_transaction
|
||||
<where>
|
||||
<if test="userId != null">
|
||||
and user_id = #{userId}
|
||||
</if>
|
||||
<if test="transactionType != null">
|
||||
and transaction_type = #{transactionType}
|
||||
</if>
|
||||
<if test="status != null">
|
||||
and status = #{status}
|
||||
</if>
|
||||
<if test="transactionNo != null and transactionNo != ''">
|
||||
and transaction_no = #{transactionNo}
|
||||
</if>
|
||||
<if test="businessType != null and businessType != ''">
|
||||
and business_type = #{businessType}
|
||||
</if>
|
||||
<if test="createTimeStart != null">
|
||||
and create_time <![CDATA[ >= ]]> #{createTimeStart}
|
||||
</if>
|
||||
<if test="createTimeEnd != null">
|
||||
and create_time <![CDATA[ <= ]]> #{createTimeEnd}
|
||||
</if>
|
||||
<if test="deleteFlag != null">
|
||||
and delete_flag = #{deleteFlag}
|
||||
</if>
|
||||
</where>
|
||||
<if test="sortBy != null and sortBy != ''">
|
||||
order by ${sortBy} ${sortDesc ? 'desc' : 'asc'}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<!--查询列表-->
|
||||
<select id="getList" resultMap="PointsTransactionMap">
|
||||
select
|
||||
transaction_id, user_id, user_name, transaction_type, points, before_points, after_points, status,
|
||||
transaction_no, pay_type, business_id, business_type, remark, create_time, update_time, create_by, update_by, delete_flag
|
||||
from points_transaction
|
||||
<where>
|
||||
<if test="userId != null">
|
||||
and user_id = #{userId}
|
||||
</if>
|
||||
<if test="transactionType != null">
|
||||
and transaction_type = #{transactionType}
|
||||
</if>
|
||||
<if test="status != null">
|
||||
and status = #{status}
|
||||
</if>
|
||||
<if test="transactionNo != null and transactionNo != ''">
|
||||
and transaction_no = #{transactionNo}
|
||||
</if>
|
||||
<if test="businessType != null and businessType != ''">
|
||||
and business_type = #{businessType}
|
||||
</if>
|
||||
<if test="createTimeStart != null">
|
||||
and create_time <![CDATA[ >= ]]> #{createTimeStart}
|
||||
</if>
|
||||
<if test="createTimeEnd != null">
|
||||
and create_time <![CDATA[ <= ]]> #{createTimeEnd}
|
||||
</if>
|
||||
<if test="deleteFlag != null">
|
||||
and delete_flag = #{deleteFlag}
|
||||
</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
<!--新增数据-->
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="transactionId">
|
||||
insert into points_transaction
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
<if test="userId != null">user_id,</if>
|
||||
<if test="userName != null">user_name,</if>
|
||||
<if test="transactionType != null">transaction_type,</if>
|
||||
<if test="points != null">points,</if>
|
||||
<if test="beforePoints != null">before_points,</if>
|
||||
<if test="afterPoints != null">after_points,</if>
|
||||
<if test="status != null">status,</if>
|
||||
<if test="transactionNo != null">transaction_no,</if>
|
||||
<if test="payType != null">pay_type,</if>
|
||||
<if test="businessId != null">business_id,</if>
|
||||
<if test="businessType != null">business_type,</if>
|
||||
<if test="remark != null">remark,</if>
|
||||
<if test="createTime != null">create_time,</if>
|
||||
<if test="updateTime != null">update_time,</if>
|
||||
<if test="createBy != null">create_by,</if>
|
||||
<if test="updateBy != null">update_by,</if>
|
||||
<if test="deleteFlag != null">delete_flag,</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="userId != null">#{userId},</if>
|
||||
<if test="userName != null">#{userName},</if>
|
||||
<if test="transactionType != null">#{transactionType},</if>
|
||||
<if test="points != null">#{points},</if>
|
||||
<if test="beforePoints != null">#{beforePoints},</if>
|
||||
<if test="afterPoints != null">#{afterPoints},</if>
|
||||
<if test="status != null">#{status},</if>
|
||||
<if test="transactionNo != null">#{transactionNo},</if>
|
||||
<if test="payType != null">#{payType},</if>
|
||||
<if test="businessId != null">#{businessId},</if>
|
||||
<if test="businessType != null">#{businessType},</if>
|
||||
<if test="remark != null">#{remark},</if>
|
||||
<if test="createTime != null">#{createTime},</if>
|
||||
<if test="updateTime != null">#{updateTime},</if>
|
||||
<if test="createBy != null">#{createBy},</if>
|
||||
<if test="updateBy != null">#{updateBy},</if>
|
||||
<if test="deleteFlag != null">#{deleteFlag},</if>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
<!--更新数据-->
|
||||
<update id="update">
|
||||
update points_transaction
|
||||
<set>
|
||||
<if test="userId != null">user_id = #{userId},</if>
|
||||
<if test="userName != null">user_name = #{userName},</if>
|
||||
<if test="transactionType != null">transaction_type = #{transactionType},</if>
|
||||
<if test="points != null">points = #{points},</if>
|
||||
<if test="beforePoints != null">before_points = #{beforePoints},</if>
|
||||
<if test="afterPoints != null">after_points = #{afterPoints},</if>
|
||||
<if test="status != null">status = #{status},</if>
|
||||
<if test="transactionNo != null">transaction_no = #{transactionNo},</if>
|
||||
<if test="payType != null">pay_type = #{payType},</if>
|
||||
<if test="businessId != null">business_id = #{businessId},</if>
|
||||
<if test="businessType != null">business_type = #{businessType},</if>
|
||||
<if test="remark != null">remark = #{remark},</if>
|
||||
<if test="updateTime != null">update_time = #{updateTime},</if>
|
||||
<if test="updateBy != null">update_by = #{updateBy},</if>
|
||||
<if test="deleteFlag != null">delete_flag = #{deleteFlag},</if>
|
||||
</set>
|
||||
where transaction_id = #{transactionId}
|
||||
</update>
|
||||
|
||||
<!--逻辑删除-->
|
||||
<update id="logicDeleteById">
|
||||
update points_transaction
|
||||
set delete_flag = 1,
|
||||
update_by = #{updateBy},
|
||||
update_time = now()
|
||||
where transaction_id = #{transactionId}
|
||||
</update>
|
||||
|
||||
<!--物理删除-->
|
||||
<delete id="deleteById">
|
||||
delete from points_transaction
|
||||
where transaction_id = #{transactionId}
|
||||
</delete>
|
||||
</mapper>
|
||||
|
|
@ -195,7 +195,7 @@
|
|||
select
|
||||
user_id, user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag, session_id
|
||||
from sys_user
|
||||
where user_name = #{userName}
|
||||
where (user_name = #{userName} or tel = #{userName})
|
||||
and delete_flag = 0
|
||||
limit 1
|
||||
</select>
|
||||
|
|
|
|||
|
|
@ -94,4 +94,13 @@
|
|||
delete from sys_user_role where role_id = #{roleId}
|
||||
</delete>
|
||||
|
||||
<!--通过用户 ID 查询角色编码列表(关联查询)-->
|
||||
<select id="queryRoleCodesByUserId" resultType="java.lang.String">
|
||||
select r.role_code
|
||||
from sys_user_role ur
|
||||
inner join sys_role r on ur.role_id = r.role_id
|
||||
where ur.user_id = #{userId}
|
||||
and r.delete_flag = '0'
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
<?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.WithdrawalRecordMapper">
|
||||
|
||||
<resultMap id="BaseResultMap" type="com.kexue.skills.entity.WithdrawalRecord">
|
||||
<id column="record_id" property="recordId" jdbcType="BIGINT"/>
|
||||
<result column="user_id" property="userId" jdbcType="BIGINT"/>
|
||||
<result column="user_name" property="userName" jdbcType="VARCHAR"/>
|
||||
<result column="withdrawal_amount" property="withdrawalAmount" jdbcType="DECIMAL"/>
|
||||
<result column="fee_amount" property="feeAmount" jdbcType="DECIMAL"/>
|
||||
<result column="actual_amount" property="actualAmount" jdbcType="DECIMAL"/>
|
||||
<result column="status" property="status" jdbcType="TINYINT"/>
|
||||
<result column="withdrawal_no" property="withdrawalNo" jdbcType="VARCHAR"/>
|
||||
<result column="bank_name" property="bankName" jdbcType="VARCHAR"/>
|
||||
<result column="bank_account" property="bankAccount" jdbcType="VARCHAR"/>
|
||||
<result column="bank_cardholder" property="bankCardholder" jdbcType="VARCHAR"/>
|
||||
<result column="remark" property="remark" jdbcType="VARCHAR"/>
|
||||
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="delete_flag" property="deleteFlag" jdbcType="TINYINT"/>
|
||||
<result column="create_by" property="createBy" jdbcType="VARCHAR"/>
|
||||
<result column="update_by" property="updateBy" jdbcType="VARCHAR"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="Base_Column_List">
|
||||
record_id, user_id, user_name, withdrawal_amount, fee_amount, actual_amount, status, withdrawal_no, bank_name, bank_account, bank_cardholder, remark, create_time, update_time, delete_flag, create_by, update_by
|
||||
</sql>
|
||||
|
||||
<select id="queryById" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List"/>
|
||||
FROM withdrawal_record
|
||||
WHERE record_id = #{recordId} AND delete_flag = 0
|
||||
</select>
|
||||
|
||||
<select id="queryByUserId" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List"/>
|
||||
FROM withdrawal_record
|
||||
WHERE user_id = #{userId} AND delete_flag = 0
|
||||
ORDER BY create_time DESC
|
||||
</select>
|
||||
|
||||
<select id="queryByWithdrawalNo" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List"/>
|
||||
FROM withdrawal_record
|
||||
WHERE withdrawal_no = #{withdrawalNo} AND delete_flag = 0
|
||||
</select>
|
||||
|
||||
<select id="getPageList" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List"/>
|
||||
FROM withdrawal_record
|
||||
WHERE delete_flag = 0
|
||||
<if test="userId != null">
|
||||
AND user_id = #{userId}
|
||||
</if>
|
||||
<if test="userName != null and userName != ''">
|
||||
AND user_name LIKE CONCAT('%', #{userName}, '%')
|
||||
</if>
|
||||
<if test="status != null">
|
||||
AND status = #{status}
|
||||
</if>
|
||||
<if test="withdrawalNo != null and withdrawalNo != ''">
|
||||
AND withdrawal_no LIKE CONCAT('%', #{withdrawalNo}, '%')
|
||||
</if>
|
||||
ORDER BY create_time DESC
|
||||
</select>
|
||||
|
||||
<select id="getList" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List"/>
|
||||
FROM withdrawal_record
|
||||
WHERE delete_flag = 0
|
||||
<if test="userId != null">
|
||||
AND user_id = #{userId}
|
||||
</if>
|
||||
<if test="userName != null and userName != ''">
|
||||
AND user_name LIKE CONCAT('%', #{userName}, '%')
|
||||
</if>
|
||||
<if test="status != null">
|
||||
AND status = #{status}
|
||||
</if>
|
||||
<if test="withdrawalNo != null and withdrawalNo != ''">
|
||||
AND withdrawal_no LIKE CONCAT('%', #{withdrawalNo}, '%')
|
||||
</if>
|
||||
ORDER BY create_time DESC
|
||||
</select>
|
||||
|
||||
<insert id="insert" parameterType="com.kexue.skills.entity.WithdrawalRecord">
|
||||
INSERT INTO withdrawal_record (
|
||||
user_id, user_name, withdrawal_amount, fee_amount, actual_amount, status, withdrawal_no, bank_name, bank_account, bank_cardholder, remark, create_time, update_time, delete_flag, create_by, update_by
|
||||
) VALUES (
|
||||
#{userId}, #{userName}, #{withdrawalAmount}, #{feeAmount}, #{actualAmount}, #{status}, #{withdrawalNo}, #{bankName}, #{bankAccount}, #{bankCardholder}, #{remark}, #{createTime}, #{updateTime}, #{deleteFlag}, #{createBy}, #{updateBy}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="update" parameterType="com.kexue.skills.entity.WithdrawalRecord">
|
||||
UPDATE withdrawal_record
|
||||
<set>
|
||||
<if test="userId != null">user_id = #{userId},</if>
|
||||
<if test="userName != null">user_name = #{userName},</if>
|
||||
<if test="withdrawalAmount != null">withdrawal_amount = #{withdrawalAmount},</if>
|
||||
<if test="feeAmount != null">fee_amount = #{feeAmount},</if>
|
||||
<if test="actualAmount != null">actual_amount = #{actualAmount},</if>
|
||||
<if test="status != null">status = #{status},</if>
|
||||
<if test="withdrawalNo != null">withdrawal_no = #{withdrawalNo},</if>
|
||||
<if test="bankName != null">bank_name = #{bankName},</if>
|
||||
<if test="bankAccount != null">bank_account = #{bankAccount},</if>
|
||||
<if test="bankCardholder != null">bank_cardholder = #{bankCardholder},</if>
|
||||
<if test="remark != null">remark = #{remark},</if>
|
||||
<if test="createBy != null">create_by = #{createBy},</if>
|
||||
<if test="updateBy != null">update_by = #{updateBy},</if>
|
||||
update_time = #{updateTime}
|
||||
</set>
|
||||
WHERE record_id = #{recordId}
|
||||
</update>
|
||||
|
||||
<update id="updateStatus" parameterType="java.util.Map">
|
||||
UPDATE withdrawal_record
|
||||
SET status = #{status}, update_time = CURRENT_TIMESTAMP
|
||||
WHERE record_id = #{recordId}
|
||||
</update>
|
||||
|
||||
<update id="logicDeleteById" parameterType="java.util.Map">
|
||||
UPDATE withdrawal_record
|
||||
SET delete_flag = 1, update_time = CURRENT_TIMESTAMP
|
||||
WHERE record_id = #{recordId}
|
||||
</update>
|
||||
|
||||
<delete id="deleteById" parameterType="java.lang.Long">
|
||||
DELETE FROM withdrawal_record
|
||||
WHERE record_id = #{recordId}
|
||||
</delete>
|
||||
|
||||
</mapper>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE account ADD COLUMN withdrawable_balance DECIMAL(18,2) DEFAULT 0.00 COMMENT '可提现余额';
|
||||
ALTER TABLE account ADD COLUMN non_withdrawable_balance DECIMAL(18,2) DEFAULT 0.00 COMMENT '不可提现余额';
|
||||
|
|
@ -0,0 +1,336 @@
|
|||
package com.kexue.skills;
|
||||
|
||||
import com.kexue.skills.entity.PaymentOrder;
|
||||
import com.kexue.skills.service.PayService;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* 微信支付接口自测类
|
||||
*/
|
||||
@SpringBootTest
|
||||
@ActiveProfiles("dev") // 使用开发环境配置
|
||||
class WechatPayTest {
|
||||
|
||||
@Resource
|
||||
private PayService payService;
|
||||
|
||||
private PaymentOrder testOrder;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
// 准备测试订单数据
|
||||
testOrder = new PaymentOrder();
|
||||
testOrder.setUserId(1L);
|
||||
testOrder.setUserName("测试用户");
|
||||
testOrder.setProductName("测试商品-" + System.currentTimeMillis());
|
||||
testOrder.setProductDesc("这是一个测试商品");
|
||||
testOrder.setAmount(new BigDecimal("0.01")); // 测试金额 0.01 元
|
||||
testOrder.setBusinessId(1L);
|
||||
testOrder.setBusinessType("purchase_content");
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 1:创建微信支付订单 - 正常场景
|
||||
*/
|
||||
@Test
|
||||
void testCreateWechatPay_Success() {
|
||||
System.out.println("========== 测试 1:创建微信支付订单(正常场景) ==========");
|
||||
|
||||
try {
|
||||
// 生成唯一订单号
|
||||
String orderNo = "TEST_" + System.currentTimeMillis();
|
||||
testOrder.setOrderNo(orderNo);
|
||||
|
||||
// 调用支付接口(传入测试 IP)
|
||||
Map<String, String> result = payService.createWechatPay(testOrder, "192.168.1.100");
|
||||
|
||||
// 验证返回结果
|
||||
assertNotNull(result, "返回结果不应为空");
|
||||
assertTrue(result.containsKey("code_url"), "应包含 code_url");
|
||||
assertTrue(result.containsKey("order_no"), "应包含 order_no");
|
||||
|
||||
System.out.println("✅ 测试通过!");
|
||||
System.out.println("订单号:" + result.get("order_no"));
|
||||
System.out.println("二维码链接:" + result.get("code_url"));
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("❌ 测试失败:" + e.getMessage());
|
||||
e.printStackTrace();
|
||||
fail("创建微信支付订单失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 2:创建微信支付订单 - 金额为空
|
||||
*/
|
||||
@Test
|
||||
void testCreateWechatPay_NullAmount() {
|
||||
System.out.println("\n========== 测试 2:金额为空(应抛出异常) ==========");
|
||||
|
||||
testOrder.setOrderNo("TEST_NULL_AMOUNT_" + System.currentTimeMillis());
|
||||
testOrder.setAmount(null); // 设置金额为空
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
payService.createWechatPay(testOrder, "192.168.1.100");
|
||||
}, "金额为空时应抛出 IllegalArgumentException");
|
||||
|
||||
System.out.println("✅ 测试通过:正确捕获了金额为空的异常");
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 3:创建微信支付订单 - 金额过小
|
||||
*/
|
||||
@Test
|
||||
void testCreateWechatPay_AmountTooSmall() {
|
||||
System.out.println("\n========== 测试 3:金额过小(应抛出异常) ==========");
|
||||
|
||||
testOrder.setOrderNo("TEST_SMALL_AMOUNT_" + System.currentTimeMillis());
|
||||
testOrder.setAmount(new BigDecimal("0.001")); // 小于 0.01 元
|
||||
|
||||
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
|
||||
payService.createWechatPay(testOrder, "192.168.1.100");
|
||||
});
|
||||
|
||||
System.out.println("✅ 测试通过:捕获异常 - " + exception.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 4:创建微信支付订单 - 金额过大
|
||||
*/
|
||||
@Test
|
||||
void testCreateWechatPay_AmountTooLarge() {
|
||||
System.out.println("\n========== 测试 4:金额过大(应抛出异常) ==========");
|
||||
|
||||
testOrder.setOrderNo("TEST_LARGE_AMOUNT_" + System.currentTimeMillis());
|
||||
testOrder.setAmount(new BigDecimal("200000")); // 超过 10 万元
|
||||
|
||||
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
|
||||
payService.createWechatPay(testOrder, "192.168.1.100");
|
||||
});
|
||||
|
||||
System.out.println("✅ 测试通过:捕获异常 - " + exception.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 5:创建微信支付订单 - 订单号为空
|
||||
*/
|
||||
@Test
|
||||
void testCreateWechatPay_NullOrderNo() {
|
||||
System.out.println("\n========== 测试 5:订单号为空(应抛出异常) ==========");
|
||||
|
||||
testOrder.setOrderNo(null); // 订单号为空
|
||||
|
||||
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
|
||||
payService.createWechatPay(testOrder, "192.168.1.100");
|
||||
});
|
||||
|
||||
System.out.println("✅ 测试通过:捕获异常 - " + exception.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 6:创建微信支付订单 - 订单号格式不正确
|
||||
*/
|
||||
@Test
|
||||
void testCreateWechatPay_InvalidOrderNo() {
|
||||
System.out.println("\n========== 测试 6:订单号格式不正确(应抛出异常) ==========");
|
||||
|
||||
testOrder.setOrderNo("TEST@INVALID#ORDER"); // 包含特殊字符
|
||||
|
||||
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
|
||||
payService.createWechatPay(testOrder, "192.168.1.100");
|
||||
});
|
||||
|
||||
System.out.println("✅ 测试通过:捕获异常 - " + exception.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 7:创建微信支付订单 - 订单号超长
|
||||
*/
|
||||
@Test
|
||||
void testCreateWechatPay_OrderNoTooLong() {
|
||||
System.out.println("\n========== 测试 7:订单号超长(应抛出异常) ==========");
|
||||
|
||||
testOrder.setOrderNo("TEST_" + UUID.randomUUID().toString().replace("-", "")); // 超过 32 位
|
||||
|
||||
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
|
||||
payService.createWechatPay(testOrder, "192.168.1.100");
|
||||
});
|
||||
|
||||
System.out.println("✅ 测试通过:捕获异常 - " + exception.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 8:创建微信支付订单 - 商品名称为空
|
||||
*/
|
||||
@Test
|
||||
void testCreateWechatPay_NullProductName() {
|
||||
System.out.println("\n========== 测试 8:商品名称为空(应抛出异常) ==========");
|
||||
|
||||
testOrder.setOrderNo("TEST_NULL_PRODUCT_" + System.currentTimeMillis());
|
||||
testOrder.setProductName(null); // 商品名称为空
|
||||
|
||||
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
|
||||
payService.createWechatPay(testOrder, "192.168.1.100");
|
||||
});
|
||||
|
||||
System.out.println("✅ 测试通过:捕获异常 - " + exception.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 9:创建微信支付订单 - IP 地址为 null(应使用默认值)
|
||||
*/
|
||||
@Test
|
||||
void testCreateWechatPay_NullIpAddress() {
|
||||
System.out.println("\n========== 测试 9:IP 地址为 null(应使用默认 127.0.0.1) ==========");
|
||||
|
||||
testOrder.setOrderNo("TEST_NULL_IP_" + System.currentTimeMillis());
|
||||
|
||||
try {
|
||||
Map<String, String> result = payService.createWechatPay(testOrder, null);
|
||||
assertNotNull(result, "即使 IP 为 null,也应返回结果");
|
||||
System.out.println("✅ 测试通过:IP 为 null 时使用了默认值");
|
||||
} catch (Exception e) {
|
||||
fail("IP 为 null 时不应抛出异常:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 10:创建微信支付订单 - 长商品描述(应自动截断)
|
||||
*/
|
||||
@Test
|
||||
void testCreateWechatPay_LongProductDescription() {
|
||||
System.out.println("\n========== 测试 10:长商品描述(应自动截断) ==========");
|
||||
|
||||
testOrder.setOrderNo("TEST_LONG_DESC_" + System.currentTimeMillis());
|
||||
|
||||
// 创建一个很长的商品描述(超过 128 字节)
|
||||
StringBuilder longDesc = new StringBuilder();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
longDesc.append("这是非常长的商品描述测试");
|
||||
}
|
||||
testOrder.setProductName(longDesc.toString());
|
||||
|
||||
try {
|
||||
Map<String, String> result = payService.createWechatPay(testOrder, "192.168.1.100");
|
||||
assertNotNull(result, "长描述应该也能成功创建订单");
|
||||
System.out.println("✅ 测试通过:长商品描述已自动处理");
|
||||
} catch (Exception e) {
|
||||
fail("长商品描述不应导致失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 11:边界值测试 - 最小金额
|
||||
*/
|
||||
@Test
|
||||
void testCreateWechatPay_MinimumAmount() {
|
||||
System.out.println("\n========== 测试 11:最小金额边界测试(0.01 元) ==========");
|
||||
|
||||
testOrder.setOrderNo("TEST_MIN_AMOUNT_" + System.currentTimeMillis());
|
||||
testOrder.setAmount(new BigDecimal("0.01")); // 最小金额
|
||||
|
||||
try {
|
||||
Map<String, String> result = payService.createWechatPay(testOrder, "192.168.1.100");
|
||||
assertNotNull(result, "最小金额应该可以成功下单");
|
||||
System.out.println("✅ 测试通过:0.01 元可以正常下单");
|
||||
} catch (Exception e) {
|
||||
fail("最小金额不应抛出异常:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 12:边界值测试 - 最大金额
|
||||
*/
|
||||
@Test
|
||||
void testCreateWechatPay_MaximumAmount() {
|
||||
System.out.println("\n========== 测试 12:最大金额边界测试(100000 元) ==========");
|
||||
|
||||
testOrder.setOrderNo("TEST_MAX_AMOUNT_" + System.currentTimeMillis());
|
||||
testOrder.setAmount(new BigDecimal("100000")); // 最大金额限制
|
||||
|
||||
try {
|
||||
Map<String, String> result = payService.createWechatPay(testOrder, "192.168.1.100");
|
||||
assertNotNull(result, "最大金额应该可以成功下单");
|
||||
System.out.println("✅ 测试通过:100000 元可以正常下单");
|
||||
} catch (Exception e) {
|
||||
fail("最大金额不应抛出异常:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 13:真实场景模拟 - 购买内容
|
||||
*/
|
||||
@Test
|
||||
void testCreateWechatPay_PurchaseContent() {
|
||||
System.out.println("\n========== 测试 13:真实场景 - 购买内容 ==========");
|
||||
|
||||
PaymentOrder order = new PaymentOrder();
|
||||
order.setOrderNo("BUY_" + System.currentTimeMillis());
|
||||
order.setUserId(1L);
|
||||
order.setUserName("张三");
|
||||
order.setAmount(new BigDecimal("9.99")); // 9.99 元
|
||||
order.setProductName("AI 技能高级教程");
|
||||
order.setProductDesc("这是一套完整的 AI 技能培训课程,包含从入门到精通的所有内容。");
|
||||
order.setBusinessId(100L);
|
||||
order.setBusinessType("purchase_content");
|
||||
|
||||
try {
|
||||
Map<String, String> result = payService.createWechatPay(order, "10.0.0.1");
|
||||
assertNotNull(result, "购买内容场景应成功");
|
||||
System.out.println("✅ 测试通过:购买内容场景模拟成功");
|
||||
System.out.println("订单号:" + result.get("order_no"));
|
||||
} catch (Exception e) {
|
||||
fail("购买内容场景不应失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 14:真实场景模拟 - 账户充值
|
||||
*/
|
||||
@Test
|
||||
void testCreateWechatPay_Recharge() {
|
||||
System.out.println("\n========== 测试 14:真实场景 - 账户充值 ==========");
|
||||
|
||||
PaymentOrder order = new PaymentOrder();
|
||||
order.setOrderNo("RECHARGE_" + System.currentTimeMillis());
|
||||
order.setUserId(1L);
|
||||
order.setUserName("李四");
|
||||
order.setAmount(new BigDecimal("100.00")); // 充值 100 元
|
||||
order.setProductName("账户充值");
|
||||
order.setProductDesc("用户账户余额充值");
|
||||
order.setBusinessId(1L);
|
||||
order.setBusinessType("recharge");
|
||||
|
||||
try {
|
||||
Map<String, String> result = payService.createWechatPay(order, "172.16.0.100");
|
||||
assertNotNull(result, "充值场景应成功");
|
||||
System.out.println("✅ 测试通过:账户充值场景模拟成功");
|
||||
System.out.println("充值金额:¥" + order.getAmount());
|
||||
} catch (Exception e) {
|
||||
fail("充值场景不应失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 辅助方法:打印测试结果
|
||||
*/
|
||||
private void printTestResult(String testName, boolean success, String message) {
|
||||
System.out.println("===========================================");
|
||||
System.out.println("测试:" + testName);
|
||||
System.out.println("状态:" + (success ? "✅ 通过" : "❌ 失败"));
|
||||
if (!success) {
|
||||
System.out.println("原因:" + message);
|
||||
}
|
||||
System.out.println("===========================================\n");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,252 @@
|
|||
package com.kexue.skills;
|
||||
|
||||
import com.kexue.skills.entity.PaymentOrder;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* 微信支付参数校验单元测试(不依赖 Spring 容器)
|
||||
*/
|
||||
@DisplayName("微信支付参数校验测试")
|
||||
class WechatPayValidationTest {
|
||||
|
||||
private PaymentOrder testOrder;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
testOrder = new PaymentOrder();
|
||||
testOrder.setUserId(1L);
|
||||
testOrder.setUserName("测试用户");
|
||||
testOrder.setProductName("测试商品");
|
||||
testOrder.setProductDesc("这是一个测试商品");
|
||||
testOrder.setAmount(new BigDecimal("0.01"));
|
||||
testOrder.setBusinessId(1L);
|
||||
testOrder.setBusinessType("purchase_content");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("订单号格式验证 - 正常情况")
|
||||
void testOrderNo_Valid() {
|
||||
// 有效的订单号
|
||||
assertTrue(isValidOrderNo("ORDER_20260331_001"));
|
||||
assertTrue(isValidOrderNo("TEST_1234567890"));
|
||||
assertTrue(isValidOrderNo("PAY_abc_123_XYZ"));
|
||||
assertTrue(isValidOrderNo("a1b2c3d4e5f6"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("订单号格式验证 - 无效情况")
|
||||
void testOrderNo_Invalid() {
|
||||
// 包含特殊字符
|
||||
assertFalse(isValidOrderNo("ORDER@2026"));
|
||||
assertFalse(isValidOrderNo("TEST#ORDER"));
|
||||
assertFalse(isValidOrderNo("PAY$123"));
|
||||
|
||||
// 包含中文
|
||||
assertFalse(isValidOrderNo("订单_123"));
|
||||
|
||||
// 空字符串
|
||||
assertFalse(isValidOrderNo(""));
|
||||
assertFalse(isValidOrderNo(" "));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("订单号长度验证")
|
||||
void testOrderNo_Length() {
|
||||
// 正好 32 位
|
||||
assertTrue(isValidOrderNo("12345678901234567890123456789012"));
|
||||
|
||||
// 超过 32 位
|
||||
assertFalse(isValidOrderNo("123456789012345678901234567890123"));
|
||||
|
||||
// 很长的 UUID(去掉横杠后通常超过 32 位)
|
||||
String longUuid = "12345678-1234-1234-1234-123456789012".replace("-", "");
|
||||
assertFalse(isValidOrderNo(longUuid));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("金额验证 - 有效金额")
|
||||
void testAmount_Valid() {
|
||||
// 最小金额
|
||||
assertTrue(isValidAmount(new BigDecimal("0.01")));
|
||||
|
||||
// 正常金额
|
||||
assertTrue(isValidAmount(new BigDecimal("1.00")));
|
||||
assertTrue(isValidAmount(new BigDecimal("9.99")));
|
||||
assertTrue(isValidAmount(new BigDecimal("100.00")));
|
||||
|
||||
// 最大金额
|
||||
assertTrue(isValidAmount(new BigDecimal("100000.00")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("金额验证 - 无效金额")
|
||||
void testAmount_Invalid() {
|
||||
// 金额为 null
|
||||
assertFalse(isValidAmount(null));
|
||||
|
||||
// 金额过小
|
||||
assertFalse(isValidAmount(new BigDecimal("0.001")));
|
||||
assertFalse(isValidAmount(new BigDecimal("0.00")));
|
||||
|
||||
// 负数金额
|
||||
assertFalse(isValidAmount(new BigDecimal("-1.00")));
|
||||
|
||||
// 金额过大
|
||||
assertFalse(isValidAmount(new BigDecimal("100000.01")));
|
||||
assertFalse(isValidAmount(new BigDecimal("200000.00")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("商品描述长度验证")
|
||||
void testProductDescription_Length() {
|
||||
// 短描述(正常)
|
||||
StringBuilder shortDesc = new StringBuilder();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
shortDesc.append("测试");
|
||||
}
|
||||
assertTrue(getByteLength(shortDesc.toString()) <= 128);
|
||||
|
||||
// 长描述(应截断)
|
||||
StringBuilder longDesc = new StringBuilder();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
longDesc.append("这是非常长的商品描述");
|
||||
}
|
||||
|
||||
// 原始长度应该超过 128 字节
|
||||
assertTrue(getByteLength(longDesc.toString()) > 128);
|
||||
|
||||
// 处理后应该不超过 128 字节
|
||||
String processed = limitBodyLength(longDesc.toString());
|
||||
assertTrue(getByteLength(processed) <= 128, "处理后的描述不应超过 128 字节");
|
||||
assertTrue(processed.endsWith("..."), "处理后的描述应以省略号结尾");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("IP 地址格式验证")
|
||||
void testIpAddress_Format() {
|
||||
// IPv4 地址
|
||||
assertTrue(isValidIpAddress("192.168.1.100"));
|
||||
assertTrue(isValidIpAddress("10.0.0.1"));
|
||||
assertTrue(isValidIpAddress("172.16.0.100"));
|
||||
assertTrue(isValidIpAddress("127.0.0.1"));
|
||||
|
||||
// 无效 IP
|
||||
assertFalse(isValidIpAddress("256.256.256.256"));
|
||||
assertFalse(isValidIpAddress("192.168.1"));
|
||||
assertFalse(isValidIpAddress("invalid.ip"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("业务类型验证")
|
||||
void testBusinessType_Valid() {
|
||||
assertTrue(isValidBusinessType("recharge"));
|
||||
assertTrue(isValidBusinessType("purchase_content"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("业务类型验证 - 无效")
|
||||
void testBusinessType_Invalid() {
|
||||
assertFalse(isValidBusinessType(null));
|
||||
assertFalse(isValidBusinessType(""));
|
||||
assertFalse(isValidBusinessType("unknown_type"));
|
||||
assertFalse(isValidBusinessType("RECHARGE")); // 区分大小写
|
||||
}
|
||||
|
||||
// ===== 辅助验证方法 =====
|
||||
|
||||
/**
|
||||
* 验证订单号格式
|
||||
*/
|
||||
private boolean isValidOrderNo(String orderNo) {
|
||||
if (orderNo == null || orderNo.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (orderNo.length() > 32) {
|
||||
return false;
|
||||
}
|
||||
return orderNo.matches("^[a-zA-Z0-9_]+$");
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证金额
|
||||
*/
|
||||
private boolean isValidAmount(BigDecimal amount) {
|
||||
if (amount == null) {
|
||||
return false;
|
||||
}
|
||||
if (amount.compareTo(new BigDecimal("0.01")) < 0) {
|
||||
return false;
|
||||
}
|
||||
if (amount.compareTo(new BigDecimal("100000")) > 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字符串的字节长度(UTF-8 编码)
|
||||
*/
|
||||
private int getByteLength(String text) {
|
||||
try {
|
||||
return text.getBytes(java.nio.charset.StandardCharsets.UTF_8).length;
|
||||
} catch (Exception e) {
|
||||
return text.length();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 限制商品描述长度(模拟服务层逻辑)
|
||||
*/
|
||||
private String limitBodyLength(String body) {
|
||||
if (body == null) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
byte[] bytes = body.getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||
if (bytes.length <= 128) {
|
||||
return body;
|
||||
}
|
||||
String truncated = new String(bytes, 0, Math.min(128 - 3, bytes.length), java.nio.charset.StandardCharsets.UTF_8);
|
||||
return truncated + "...";
|
||||
} catch (Exception e) {
|
||||
return body.length() > 60 ? body.substring(0, 60) + "..." : body;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证 IP 地址格式
|
||||
*/
|
||||
private boolean isValidIpAddress(String ip) {
|
||||
if (ip == null || ip.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
String[] parts = ip.split("\\.");
|
||||
if (parts.length != 4) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
for (String part : parts) {
|
||||
int num = Integer.parseInt(part);
|
||||
if (num < 0 || num > 255) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证业务类型
|
||||
*/
|
||||
private boolean isValidBusinessType(String businessType) {
|
||||
return "recharge".equals(businessType) || "purchase_content".equals(businessType);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue