Compare commits
39 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
a8faaebc79 | |
|
|
a608b7a754 | |
|
|
fc7ba98d6e | |
|
|
eef2b68291 | |
|
|
891f60d5a5 | |
|
|
0d934f7287 | |
|
|
e5433853ec | |
|
|
7841b94872 | |
|
|
71edec48f7 | |
|
|
e651e73fa2 | |
|
|
51fce1ece6 | |
|
|
8a80b31c2f | |
|
|
071f6aafbc | |
|
|
a5631caab3 | |
|
|
b548bfbc14 | |
|
|
fd0e1c893f | |
|
|
d527e1ad0f | |
|
|
713c28a534 | |
|
|
bd252efd20 | |
|
|
44c04f81e2 | |
|
|
e2fa8d7517 | |
|
|
8e25f10b27 | |
|
|
7ae6e19ae1 | |
|
|
47831276ec | |
|
|
bad416aeab | |
|
|
f2b8a735f2 | |
|
|
770f50302e | |
|
|
3df611f809 | |
|
|
59a44f9c53 | |
|
|
a92b668ac3 | |
|
|
ed220c9981 | |
|
|
6398b0495e | |
|
|
1b0d102ef9 | |
|
|
af0ae4bac1 | |
|
|
11bc1959f0 | |
|
|
e16fbdf2d6 | |
|
|
d33d567b81 | |
|
|
d8d1a4eaf4 | |
|
|
8e00170ba1 |
|
|
@ -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
|
||||||
|
**维护人员:** 系统开发团队
|
||||||
193
README.md
193
README.md
|
|
@ -1,3 +1,192 @@
|
||||||
# agent-skill-backend
|
# 可学AI-skills平台后端
|
||||||
|
|
||||||
agent-skill-backend
|
## 项目简介
|
||||||
|
|
||||||
|
可学AI-skills平台是一个基于Spring Boot的智能技能管理系统,提供技能生成、内容管理、用户认证、支付等功能。
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
- **基础框架**:Spring Boot 3.2.2
|
||||||
|
- **持久层**:MyBatis 3.0.3
|
||||||
|
- **数据库**:MySQL
|
||||||
|
- **缓存**:Redis
|
||||||
|
- **认证**:Sa-Token 1.38.0
|
||||||
|
- **模板引擎**:Thymeleaf
|
||||||
|
- **API文档**:Swagger 3.0.0
|
||||||
|
- **文件处理**:sevenzipjbinding 16.02-2.01
|
||||||
|
- **短信服务**:SMS4J 3.3.5
|
||||||
|
- **分布式锁**:Redisson 3.23.5
|
||||||
|
- **AI集成**:DeepSeek、GLM-4.6v
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
backend/
|
||||||
|
├── .mvn/ # Maven包装器
|
||||||
|
├── db/ # 数据库脚本
|
||||||
|
├── src/
|
||||||
|
│ ├── main/
|
||||||
|
│ │ ├── java/com/kexue/skills/ # 主源码
|
||||||
|
│ │ │ ├── annotation/ # 自定义注解
|
||||||
|
│ │ │ ├── aspect/ # AOP切面
|
||||||
|
│ │ │ ├── common/ # 通用工具和常量
|
||||||
|
│ │ │ ├── config/ # 配置类
|
||||||
|
│ │ │ ├── controller/ # 控制器
|
||||||
|
│ │ │ ├── entity/ # 实体类
|
||||||
|
│ │ │ ├── exception/ # 异常处理
|
||||||
|
│ │ │ ├── interceptor/ # 拦截器
|
||||||
|
│ │ │ ├── mapper/ # 数据访问层
|
||||||
|
│ │ │ ├── service/ # 服务层
|
||||||
|
│ │ │ ├── task/ # 定时任务
|
||||||
|
│ │ │ ├── utils/ # 工具类
|
||||||
|
│ │ │ └── SkillsApp.java # 应用入口
|
||||||
|
│ │ └── resources/ # 资源文件
|
||||||
|
│ │ ├── mapper/ # MyBatis映射文件
|
||||||
|
│ │ ├── sql/ # SQL脚本
|
||||||
|
│ │ ├── static/ # 静态资源
|
||||||
|
│ │ ├── templates/ # Thymeleaf模板
|
||||||
|
│ │ ├── application-*.yml # 配置文件
|
||||||
|
│ │ └── logback-spring.xml # 日志配置
|
||||||
|
│ └── test/ # 测试代码
|
||||||
|
├── .gitignore # Git忽略文件
|
||||||
|
├── Dockerfile # Docker构建文件
|
||||||
|
├── README.md # 项目说明
|
||||||
|
├── mvnw.cmd # Maven包装器脚本
|
||||||
|
└── pom.xml # Maven依赖配置
|
||||||
|
```
|
||||||
|
|
||||||
|
## 核心功能
|
||||||
|
|
||||||
|
### 1. 用户认证与授权
|
||||||
|
- 基于Sa-Token的认证系统
|
||||||
|
- 支持账号密码登录
|
||||||
|
- 支持手机验证码登录
|
||||||
|
- 角色权限管理
|
||||||
|
- 防重复提交
|
||||||
|
|
||||||
|
### 2. 内容管理系统
|
||||||
|
- 内容分类管理
|
||||||
|
- 内容标签管理
|
||||||
|
- 内容发布与管理
|
||||||
|
- 内容点赞与浏览统计
|
||||||
|
|
||||||
|
### 3. 技能生成系统
|
||||||
|
- 技能上传与解析(支持RAR等压缩格式)
|
||||||
|
- 技能结构分析
|
||||||
|
- 技能介绍生成
|
||||||
|
|
||||||
|
### 4. 支付系统
|
||||||
|
- 微信支付集成
|
||||||
|
- 支付宝集成
|
||||||
|
- 支付订单管理
|
||||||
|
|
||||||
|
### 5. 账户管理
|
||||||
|
- 账户余额管理
|
||||||
|
- 积分管理
|
||||||
|
- 交易记录
|
||||||
|
|
||||||
|
### 6. 系统管理
|
||||||
|
- 菜单管理
|
||||||
|
- 角色管理
|
||||||
|
- 字典管理
|
||||||
|
- 系统日志
|
||||||
|
|
||||||
|
### 7. AI集成
|
||||||
|
- DeepSeek模型集成
|
||||||
|
- GLM-4.6v模型集成
|
||||||
|
- 智能内容生成
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 环境要求
|
||||||
|
- JDK 17+
|
||||||
|
- Maven 3.6+
|
||||||
|
- MySQL 5.7+
|
||||||
|
- Redis 5.0+
|
||||||
|
|
||||||
|
### 配置说明
|
||||||
|
1. 修改 `application-dev.yml` 文件中的数据库连接信息
|
||||||
|
2. 修改 `application.yml` 文件中的Redis连接信息
|
||||||
|
3. 修改 `application.yml` 文件中的AI模型API密钥
|
||||||
|
4. 修改 `application.yml` 文件中的短信服务配置
|
||||||
|
|
||||||
|
### 数据库初始化
|
||||||
|
1. 执行 `db/create_tables.sql` 创建数据库表
|
||||||
|
2. 执行 `db/init_data.sql` 初始化基础数据
|
||||||
|
|
||||||
|
### 启动项目
|
||||||
|
```bash
|
||||||
|
# 编译项目
|
||||||
|
mvn clean compile
|
||||||
|
|
||||||
|
# 运行项目
|
||||||
|
mvn spring-boot:run
|
||||||
|
```
|
||||||
|
|
||||||
|
### 访问地址
|
||||||
|
- 项目首页:http://localhost:8080
|
||||||
|
- Swagger文档:http://localhost:8080/doc.html
|
||||||
|
|
||||||
|
## 主要API
|
||||||
|
|
||||||
|
### 用户认证
|
||||||
|
- `POST /api/login` - 用户登录
|
||||||
|
- `POST /api/logout` - 用户登出
|
||||||
|
- `GET /api/currentUser` - 获取当前用户信息
|
||||||
|
|
||||||
|
### 内容管理
|
||||||
|
- `GET /api/cms/content/list` - 获取内容列表
|
||||||
|
- `POST /api/cms/content/save` - 保存内容
|
||||||
|
- `DELETE /api/cms/content/delete` - 删除内容
|
||||||
|
|
||||||
|
### 技能管理
|
||||||
|
- `POST /api/skill/upload` - 上传技能
|
||||||
|
- `POST /api/skill/analyze` - 分析技能结构
|
||||||
|
- `POST /api/skill/genIntroduce` - 生成技能介绍
|
||||||
|
|
||||||
|
### 支付管理
|
||||||
|
- `POST /api/pay/wx` - 微信支付
|
||||||
|
- `POST /api/pay/alipay` - 支付宝支付
|
||||||
|
- `GET /api/payment/order/list` - 获取支付订单列表
|
||||||
|
|
||||||
|
## 部署说明
|
||||||
|
|
||||||
|
### Docker部署
|
||||||
|
1. 构建Docker镜像
|
||||||
|
```bash
|
||||||
|
docker build -t agent-skills .
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 运行Docker容器
|
||||||
|
```bash
|
||||||
|
docker run -p 8080:8080 --name agent-skills agent-skills
|
||||||
|
```
|
||||||
|
|
||||||
|
### 生产环境部署
|
||||||
|
1. 打包项目
|
||||||
|
```bash
|
||||||
|
mvn clean package -DskipTests
|
||||||
|
|
||||||
|
配置文件直接打在jar包内
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 部署jar包
|
||||||
|
```bash
|
||||||
|
java -jar agentSkills.jar --spring.profiles.active=prod
|
||||||
|
或者执行脚本启动
|
||||||
|
./start.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. 项目使用Redis作为缓存,需要确保Redis服务正常运行
|
||||||
|
2. 项目使用阿里云短信服务,需要配置相关参数
|
||||||
|
3. 项目使用AI模型API,需要配置相关API密钥
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
本项目仅供内部使用,未经授权不得用于商业用途。
|
||||||
|
|
||||||
|
## 联系方式
|
||||||
|
|
||||||
|
如有问题,请联系项目维护人员。
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
-- 为 account_frozen 表添加 question 字段
|
||||||
|
-- 用于记录用户的问题或需求
|
||||||
|
|
||||||
|
ALTER TABLE `account_frozen`
|
||||||
|
ADD COLUMN `question` text DEFAULT NULL COMMENT '对应回答的问题或需求' AFTER `model_name`;
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
-- 为 account_transaction 表添加 call_id 字段
|
||||||
|
-- 用于关联冻结单释放时的调用ID
|
||||||
|
|
||||||
|
ALTER TABLE `account_transaction`
|
||||||
|
ADD COLUMN `call_id` varchar(100) DEFAULT NULL COMMENT '调用ID,关联冻结单' AFTER `business_type`;
|
||||||
|
|
||||||
|
-- 添加索引以提高查询性能
|
||||||
|
ALTER TABLE `account_transaction`
|
||||||
|
ADD INDEX `idx_call_id` (`call_id`);
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
-- 修改payment_order表,为pay_type字段添加默认值
|
||||||
|
ALTER TABLE `payment_order` MODIFY COLUMN `pay_type` tinyint(1) NOT NULL DEFAULT 1 COMMENT '支付方式:1.微信 2.支付宝';
|
||||||
|
|
||||||
|
-- 输出修改结果
|
||||||
|
SELECT '修改payment_order表pay_type字段默认值完成' AS result;
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
-- 更新 sys_log 表结构以支持新的日志功能
|
||||||
|
-- 作者: 王志维
|
||||||
|
-- 创建时间: 2026-04-14
|
||||||
|
|
||||||
|
-- 备份旧数据(可选)
|
||||||
|
-- CREATE TABLE sys_log_backup AS SELECT * FROM sys_log;
|
||||||
|
|
||||||
|
-- 删除旧表(如果存在)
|
||||||
|
DROP TABLE IF EXISTS `sys_log`;
|
||||||
|
|
||||||
|
-- 创建新表
|
||||||
|
CREATE TABLE `sys_log` (
|
||||||
|
`log_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||||
|
`trace_id` varchar(255) DEFAULT NULL COMMENT '链路ID',
|
||||||
|
`description` varchar(255) DEFAULT NULL COMMENT '日志描述',
|
||||||
|
`module` varchar(50) DEFAULT NULL COMMENT '所属模块',
|
||||||
|
`request_url` varchar(512) DEFAULT NULL COMMENT '请求URL',
|
||||||
|
`request_method` varchar(10) DEFAULT NULL COMMENT '请求方式',
|
||||||
|
`request_headers` text COMMENT '请求头',
|
||||||
|
`request_body` text COMMENT '请求体',
|
||||||
|
`status_code` int(11) DEFAULT NULL COMMENT '状态码',
|
||||||
|
`response_headers` text COMMENT '响应头',
|
||||||
|
`response_body` mediumtext COMMENT '响应体',
|
||||||
|
`time_taken` bigint(20) DEFAULT NULL COMMENT '耗时(ms)',
|
||||||
|
`ip` varchar(100) DEFAULT NULL COMMENT 'IP',
|
||||||
|
`address` varchar(255) DEFAULT NULL COMMENT 'IP归属地',
|
||||||
|
`browser` varchar(100) DEFAULT NULL COMMENT '浏览器',
|
||||||
|
`os` varchar(100) DEFAULT NULL COMMENT '操作系统',
|
||||||
|
`status` tinyint(1) DEFAULT '1' COMMENT '状态(1:成功;2:失败)',
|
||||||
|
`error_msg` text COMMENT '错误信息',
|
||||||
|
`create_user` bigint(20) 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已删除',
|
||||||
|
`create_by` varchar(50) DEFAULT NULL COMMENT '创建人',
|
||||||
|
`update_by` varchar(50) DEFAULT NULL COMMENT '更新人',
|
||||||
|
PRIMARY KEY (`log_id`) USING BTREE,
|
||||||
|
KEY `idx_module` (`module`) USING BTREE COMMENT '模块查询优化',
|
||||||
|
KEY `idx_ip` (`ip`) USING BTREE COMMENT 'IP查询优化',
|
||||||
|
KEY `idx_create_time` (`create_time`) USING BTREE COMMENT '时间范围查询优化',
|
||||||
|
KEY `idx_status` (`status`) USING BTREE COMMENT '状态查询优化'
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='系统操作日志表';
|
||||||
|
|
||||||
|
-- 插入测试数据(可选)
|
||||||
|
-- INSERT INTO `sys_log` (`description`, `module`, `request_url`, `request_method`, `status_code`, `time_taken`, `ip`, `status`, `create_time`)
|
||||||
|
-- VALUES ('用户登录', '登录认证', '/api/login/accountLogin', 'POST', 200, 150, '127.0.0.1', 1, NOW());
|
||||||
|
|
@ -12,7 +12,9 @@ CREATE TABLE `account` (
|
||||||
`account_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
`account_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||||
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
|
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
|
||||||
`user_name` varchar(50) DEFAULT NULL COMMENT '用户名',
|
`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 '冻结金额',
|
`frozen_amount` decimal(10,2) DEFAULT '0.00' COMMENT '冻结金额',
|
||||||
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE 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`)
|
KEY `idx_user_id` (`user_id`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='账户表,记录用户的账户信息';
|
) 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. 账户流水表
|
-- 2. 账户流水表
|
||||||
DROP TABLE IF EXISTS `account_transaction`;
|
DROP TABLE IF EXISTS `account_transaction`;
|
||||||
CREATE TABLE `account_transaction` (
|
CREATE TABLE `account_transaction` (
|
||||||
`transaction_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
`transaction_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||||
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
|
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
|
||||||
`user_name` varchar(50) DEFAULT NULL COMMENT '用户名',
|
`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 '交易金额',
|
`amount` decimal(10,2) NOT NULL COMMENT '交易金额',
|
||||||
`before_balance` decimal(10,2) NOT NULL COMMENT '交易前余额',
|
`before_balance` decimal(10,2) NOT NULL COMMENT '交易前余额',
|
||||||
`after_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_id` bigint(20) DEFAULT NULL COMMENT '关联业务ID',
|
||||||
`business_type` varchar(50) DEFAULT NULL COMMENT '业务类型',
|
`business_type` varchar(50) DEFAULT NULL COMMENT '业务类型',
|
||||||
`remark` varchar(255) 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 '创建时间',
|
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
`create_by` varchar(50) DEFAULT NULL COMMENT '创建人',
|
`create_by` varchar(50) DEFAULT NULL COMMENT '创建人',
|
||||||
|
|
@ -48,48 +80,7 @@ CREATE TABLE `account_transaction` (
|
||||||
KEY `idx_business_id` (`business_id`)
|
KEY `idx_business_id` (`business_id`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='账户流水表,记录用户的账户交易记录';
|
) 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. 内容购买记录表
|
-- 5. 内容购买记录表
|
||||||
DROP TABLE IF EXISTS `content_purchase`;
|
DROP TABLE IF EXISTS `content_purchase`;
|
||||||
|
|
@ -308,4 +299,24 @@ CREATE TABLE `sys_log` (
|
||||||
KEY `idx_log_time` (`log_time`)
|
KEY `idx_log_time` (`log_time`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='系统日志表,记录系统操作日志';
|
) 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;
|
SET FOREIGN_KEY_CHECKS = 1;
|
||||||
57
pom.xml
57
pom.xml
|
|
@ -146,11 +146,11 @@
|
||||||
<version>1.3.2</version>
|
<version>1.3.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Servlet API for javax.servlet.http -->
|
<!-- Servlet API for jakarta.servlet.http (Spring Boot 3.x uses Jakarta EE 9+) -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>javax.servlet</groupId>
|
<groupId>jakarta.servlet</groupId>
|
||||||
<artifactId>javax.servlet-api</artifactId>
|
<artifactId>jakarta.servlet-api</artifactId>
|
||||||
<version>4.0.1</version>
|
<version>6.0.0</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
@ -164,7 +164,7 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.pagehelper</groupId>
|
<groupId>com.github.pagehelper</groupId>
|
||||||
<artifactId>pagehelper-spring-boot-starter</artifactId>
|
<artifactId>pagehelper-spring-boot-starter</artifactId>
|
||||||
<version>1.4.2</version>
|
<version>1.4.6</version>
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
<groupId>org.mybatis</groupId>
|
<groupId>org.mybatis</groupId>
|
||||||
|
|
@ -182,10 +182,6 @@
|
||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
<artifactId>slf4j-api</artifactId>
|
<artifactId>slf4j-api</artifactId>
|
||||||
</exclusion>
|
</exclusion>
|
||||||
<exclusion>
|
|
||||||
<groupId>ch.qos.logback</groupId>
|
|
||||||
<artifactId>logback-classic</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
<exclusion>
|
<exclusion>
|
||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
<artifactId>log4j-over-slf4j</artifactId>
|
<artifactId>log4j-over-slf4j</artifactId>
|
||||||
|
|
@ -195,12 +191,19 @@
|
||||||
<artifactId>jul-to-slf4j</artifactId>
|
<artifactId>jul-to-slf4j</artifactId>
|
||||||
</exclusion>
|
</exclusion>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>com.github.jsqlparser</groupId>
|
||||||
<artifactId>spring-boot-starter-logging</artifactId>
|
<artifactId>jsqlparser</artifactId>
|
||||||
</exclusion>
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 统一的jsqlparser版本 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.jsqlparser</groupId>
|
||||||
|
<artifactId>jsqlparser</artifactId>
|
||||||
|
<version>4.4</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Sa-Token 核心依赖 -->
|
<!-- Sa-Token 核心依赖 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.dev33</groupId>
|
<groupId>cn.dev33</groupId>
|
||||||
|
|
@ -257,6 +260,38 @@
|
||||||
<version>1.2.83</version>
|
<version>1.2.83</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 微信支付SDK -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.binarywang</groupId>
|
||||||
|
<artifactId>weixin-java-pay</artifactId>
|
||||||
|
<version>4.4.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 支付宝SDK -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alipay.sdk</groupId>
|
||||||
|
<artifactId>alipay-sdk-java</artifactId>
|
||||||
|
<version>4.38.0.ALL</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- YAML 配置文件解析器 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.yaml</groupId>
|
||||||
|
<artifactId>snakeyaml</artifactId>
|
||||||
|
<version>2.2</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.sf.sevenzipjbinding</groupId>
|
||||||
|
<artifactId>sevenzipjbinding</artifactId>
|
||||||
|
<version>16.02-2.01</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.sf.sevenzipjbinding</groupId>
|
||||||
|
<artifactId>sevenzipjbinding-all-platforms</artifactId>
|
||||||
|
<version>16.02-2.01</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.kexue.skills.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作日志注解
|
||||||
|
* 用于记录Controller层方法的请求和响应信息
|
||||||
|
*
|
||||||
|
* @author 王志维
|
||||||
|
* @since 2026-04-14
|
||||||
|
*/
|
||||||
|
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
public @interface Log {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模块名称
|
||||||
|
* 例如:用户管理、登录认证、内容管理等
|
||||||
|
*/
|
||||||
|
String module() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志描述
|
||||||
|
* 如果不填写,将根据方法签名自动生成
|
||||||
|
*/
|
||||||
|
String description() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否忽略记录日志
|
||||||
|
* 默认false,设置为true时不记录该接口的日志
|
||||||
|
*/
|
||||||
|
boolean ignore() default false;
|
||||||
|
}
|
||||||
|
|
@ -15,7 +15,7 @@ import org.springframework.web.context.request.RequestContextHolder;
|
||||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import javax.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
/**
|
/**
|
||||||
* @author 维哥
|
* @author 维哥
|
||||||
* @Description 登录认证切面
|
* @Description 登录认证切面
|
||||||
|
|
@ -67,11 +67,10 @@ public class AuthAspect {
|
||||||
|
|
||||||
// 设置用户上下文
|
// 设置用户上下文
|
||||||
UserContextHolder.setUserName(username);
|
UserContextHolder.setUserName(username);
|
||||||
|
|
||||||
return joinPoint.proceed();
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("认证失败:{}", e.getMessage());
|
log.error("认证失败:{}", e.getMessage());
|
||||||
throw new BizException(ResultCode.TOKEN_FAILED.getCode(), "无效的token,请重新登录");
|
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 jakarta.servlet.http.HttpServletRequest;
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author 维哥
|
* @author 维哥
|
||||||
|
|
@ -85,14 +86,29 @@ public class RoleAspect {
|
||||||
// 获取用户的角色列表
|
// 获取用户的角色列表
|
||||||
String[] requiredRoles = requireRole.value();
|
String[] requiredRoles = requireRole.value();
|
||||||
if (requiredRoles != null && requiredRoles.length > 0) {
|
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);
|
UserContextHolder.setUserName(username);
|
||||||
|
|
||||||
return joinPoint.proceed();
|
|
||||||
} catch (cn.dev33.satoken.exception.NotLoginException e) {
|
} catch (cn.dev33.satoken.exception.NotLoginException e) {
|
||||||
log.error("未登录:{}", e.getMessage());
|
log.error("未登录:{}", e.getMessage());
|
||||||
throw new BizException(ResultCode.TOKEN_FAILED.getCode(), "请先登录认证后操作");
|
throw new BizException(ResultCode.TOKEN_FAILED.getCode(), "请先登录认证后操作");
|
||||||
|
|
@ -103,5 +119,6 @@ public class RoleAspect {
|
||||||
log.error("权限验证失败:{}", e.getMessage());
|
log.error("权限验证失败:{}", e.getMessage());
|
||||||
throw new BizException(ResultCode.PERMISSION_DENIED.getCode(), "权限验证失败");
|
throw new BizException(ResultCode.PERMISSION_DENIED.getCode(), "权限验证失败");
|
||||||
}
|
}
|
||||||
|
return joinPoint.proceed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -44,6 +44,15 @@ public class CommonResult<T> {
|
||||||
return new CommonResult<T>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
|
return new CommonResult<T>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 成功返回结果
|
||||||
|
*
|
||||||
|
* @param data 获取的数据
|
||||||
|
*/
|
||||||
|
public static <T> CommonResult<T> success(IErrorCode errorCode,T data) {
|
||||||
|
return new CommonResult<T>(errorCode.getCode(), errorCode.getMessage(), data);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 成功返回结果
|
* 成功返回结果
|
||||||
*
|
*
|
||||||
|
|
@ -58,11 +67,11 @@ public class CommonResult<T> {
|
||||||
/**
|
/**
|
||||||
* 成功返回结果
|
* 成功返回结果
|
||||||
*
|
*
|
||||||
* @param errorCode 获取的数据
|
* @param code 获取的数据
|
||||||
* @param message 提示信息
|
* @param message 提示信息
|
||||||
*/
|
*/
|
||||||
public static <T> CommonResult<T> success(IErrorCode errorCode, String message ) {
|
public static <T> CommonResult<T> success(Long code, String message ) {
|
||||||
return new CommonResult<T>(errorCode.getCode(), message,null);
|
return new CommonResult<T>(code, message,null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
package com.kexue.skills.common;
|
||||||
|
|
||||||
|
import com.kexue.skills.entity.request.LoginUser;
|
||||||
|
import com.kexue.skills.mapper.*;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import org.redisson.api.RedissonClient;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LoginUser缓存工具类
|
||||||
|
* 用于更新Redis中的LoginUser对象
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class LoginUserCacheUtil {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedissonClient redissonClient;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CmsContentLikeMapper cmsContentLikeMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CmsContentViewMapper cmsContentViewMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CmsContentMapper cmsContentMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ContentPurchaseMapper contentPurchaseMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新用户的收藏列表
|
||||||
|
*
|
||||||
|
* @param token 用户token
|
||||||
|
* @param userId 用户ID
|
||||||
|
*/
|
||||||
|
public void updateFavorites(String token, Long userId) {
|
||||||
|
updateLoginUserList(token, userId, "favorites", () -> cmsContentLikeMapper.queryRecentLikesByUserId(userId, 20));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新用户的查看历史列表
|
||||||
|
*
|
||||||
|
* @param token 用户token
|
||||||
|
* @param userId 用户ID
|
||||||
|
*/
|
||||||
|
public void updateHistory(String token, Long userId) {
|
||||||
|
updateLoginUserList(token, userId, "history", () -> cmsContentViewMapper.queryRecentViewsByUserId(userId, 20));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新用户的创建记录列表
|
||||||
|
*
|
||||||
|
* @param token 用户token
|
||||||
|
* @param userId 用户ID
|
||||||
|
*/
|
||||||
|
public void updateCreate(String token, Long userId) {
|
||||||
|
updateLoginUserList(token, userId, "create", () -> cmsContentMapper.queryRecentCreatedByUserId(userId, 20));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新用户的购买记录列表
|
||||||
|
*
|
||||||
|
* @param token 用户token
|
||||||
|
* @param userId 用户ID
|
||||||
|
*/
|
||||||
|
public void updateHas(String token, Long userId) {
|
||||||
|
updateLoginUserList(token, userId, "has", () -> {
|
||||||
|
List<Long> has = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
com.kexue.skills.entity.dto.ContentPurchaseDto purchaseDto = new com.kexue.skills.entity.dto.ContentPurchaseDto();
|
||||||
|
purchaseDto.setUserId(userId);
|
||||||
|
List<com.kexue.skills.entity.ContentPurchase> purchases = contentPurchaseMapper.getList(purchaseDto);
|
||||||
|
if (purchases != null && !purchases.isEmpty()) {
|
||||||
|
java.util.LinkedHashSet<Long> contentIdSet = new java.util.LinkedHashSet<>();
|
||||||
|
for (com.kexue.skills.entity.ContentPurchase purchase : purchases) {
|
||||||
|
if (contentIdSet.size() < 20) {
|
||||||
|
contentIdSet.add(purchase.getContentId());
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
has.addAll(contentIdSet);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
has = java.util.Collections.emptyList();
|
||||||
|
}
|
||||||
|
return has;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新LoginUser中的列表属性
|
||||||
|
*
|
||||||
|
* @param token 用户token
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param fieldName 字段名
|
||||||
|
* @param supplier 列表数据提供者
|
||||||
|
*/
|
||||||
|
private void updateLoginUserList(String token, Long userId, String fieldName, java.util.function.Supplier<List<Long>> supplier) {
|
||||||
|
try {
|
||||||
|
String key = "loginUser:" + token;
|
||||||
|
Object loginUserObj = redissonClient.getBucket(key).get();
|
||||||
|
if (loginUserObj != null) {
|
||||||
|
String loginUserJson = loginUserObj.toString();
|
||||||
|
com.kexue.skills.entity.request.LoginUser loginUser = com.alibaba.fastjson.JSON.parseObject(loginUserJson, com.kexue.skills.entity.request.LoginUser.class);
|
||||||
|
|
||||||
|
if (loginUser != null && loginUser.getUserInfo() != null && loginUser.getUserInfo().getUserId().equals(userId)) {
|
||||||
|
List<Long> list = supplier.get();
|
||||||
|
|
||||||
|
switch (fieldName) {
|
||||||
|
case "favorites":
|
||||||
|
loginUser.setFavorites(list);
|
||||||
|
break;
|
||||||
|
case "history":
|
||||||
|
loginUser.setHistory(list);
|
||||||
|
break;
|
||||||
|
case "create":
|
||||||
|
loginUser.setCreate(list);
|
||||||
|
break;
|
||||||
|
case "has":
|
||||||
|
loginUser.setHas(list);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写回Redis
|
||||||
|
redissonClient.getBucket(key).set(com.alibaba.fastjson.JSON.toJSONString(loginUser));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从请求中获取token
|
||||||
|
*
|
||||||
|
* @return token
|
||||||
|
*/
|
||||||
|
public String getTokenFromRequest() {
|
||||||
|
org.springframework.web.context.request.RequestAttributes requestAttributes = org.springframework.web.context.request.RequestContextHolder.getRequestAttributes();
|
||||||
|
if (requestAttributes != null && requestAttributes instanceof org.springframework.web.context.request.ServletRequestAttributes) {
|
||||||
|
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
|
||||||
|
if (request != null) {
|
||||||
|
return request.getHeader("Authorization");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前登录用户ID
|
||||||
|
*
|
||||||
|
* @return 用户ID
|
||||||
|
*/
|
||||||
|
public Long getCurrentUserId() {
|
||||||
|
try {
|
||||||
|
Object loginId = cn.dev33.satoken.stp.StpUtil.getLoginId();
|
||||||
|
return Long.parseLong(loginId.toString());
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -92,7 +92,19 @@ public enum ResultCode implements IErrorCode {
|
||||||
/**
|
/**
|
||||||
* 统一异常返回码
|
* 统一异常返回码
|
||||||
* */
|
* */
|
||||||
EXCEPTION_HANDLER(-2500,"服务异常,请联系管理员");
|
EXCEPTION_HANDLER(-2500,"服务异常,请联系管理员"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账户冻结单相关错误
|
||||||
|
* */
|
||||||
|
PARAMETER_EMPTY(-1100, "参数不能为空"),
|
||||||
|
FROZEN_ID_EMPTY(-1101, "冻结单ID不能为空"),
|
||||||
|
FROZEN_NOT_EXIST(-1102, "冻结单不存在"),
|
||||||
|
FROZEN_STATUS_ERROR(-1103, "冻结单状态不正确,无法释放"),
|
||||||
|
SESSION_ID_NOT_EXIST(-1104, "会话ID不存在"),
|
||||||
|
ACCOUNT_NOT_EXIST(-1105, "用户账户不存在"),
|
||||||
|
INSUFFICIENT_BALANCE(-1106, "账户余额不足");
|
||||||
|
|
||||||
|
|
||||||
private final long code;
|
private final long code;
|
||||||
private final String message;
|
private final String message;
|
||||||
|
|
|
||||||
|
|
@ -55,4 +55,49 @@ public class HttpUtil {
|
||||||
|
|
||||||
return response.body();
|
return response.body();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送 POST 请求到指定 URL
|
||||||
|
*
|
||||||
|
* @param url 请求 URL
|
||||||
|
* @param requestBody 请求体字符串
|
||||||
|
* @return 响应结果
|
||||||
|
* @throws Exception 异常信息
|
||||||
|
*/
|
||||||
|
public static String post(String url, String requestBody) throws Exception {
|
||||||
|
// 创建 HttpClient 实例
|
||||||
|
HttpClient client = HttpClient.newBuilder()
|
||||||
|
.connectTimeout(Duration.ofSeconds(30))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return postWithClient(url, requestBody, client);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,22 +2,119 @@ package com.kexue.skills.common.util;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description: ID管理类
|
* @description: ID管理类
|
||||||
**/
|
**/
|
||||||
|
@Component
|
||||||
public class IDUtils {
|
public class IDUtils {
|
||||||
public static final Logger logger = LoggerFactory.getLogger(IDUtils.class);
|
public static final Logger logger = LoggerFactory.getLogger(IDUtils.class);
|
||||||
|
|
||||||
|
@Value("${snowflake.workid:1}")
|
||||||
|
private long workId;
|
||||||
|
|
||||||
|
private static SnowflakeIdGenerator snowflakeIdGenerator;
|
||||||
|
|
||||||
public static String getUUID(){
|
public static String getUUID(){
|
||||||
return UUID.randomUUID().toString().replace("-","");
|
return UUID.randomUUID().toString().replace("-","");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
snowflakeIdGenerator = new SnowflakeIdGenerator(workId);
|
||||||
|
logger.info("Snowflake ID generator initialized with workId: {}", workId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getSnowflakeId() {
|
||||||
|
if (snowflakeIdGenerator == null) {
|
||||||
|
// 如果未初始化,使用默认workId
|
||||||
|
snowflakeIdGenerator = new SnowflakeIdGenerator(1L);
|
||||||
|
}
|
||||||
|
return snowflakeIdGenerator.nextId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getSnowflakeIdStr() {
|
||||||
|
if (snowflakeIdGenerator == null) {
|
||||||
|
// 如果未初始化,使用默认workId
|
||||||
|
snowflakeIdGenerator = new SnowflakeIdGenerator(1L);
|
||||||
|
}
|
||||||
|
return String.valueOf(snowflakeIdGenerator.nextId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 雪花算法实现
|
||||||
|
*/
|
||||||
|
static class SnowflakeIdGenerator {
|
||||||
|
// 起始时间戳 (2020-01-01 00:00:00)
|
||||||
|
private static final long START_TIMESTAMP = 1577808000000L;
|
||||||
|
// 机器ID位数
|
||||||
|
private static final long MACHINE_ID_BITS = 10L;
|
||||||
|
// 序列号位数
|
||||||
|
private static final long SEQUENCE_BITS = 12L;
|
||||||
|
// 机器ID最大值
|
||||||
|
private static final long MAX_MACHINE_ID = (1L << MACHINE_ID_BITS) - 1;
|
||||||
|
// 序列号最大值
|
||||||
|
private static final long MAX_SEQUENCE = (1L << SEQUENCE_BITS) - 1;
|
||||||
|
// 机器ID左移位数
|
||||||
|
private static final long MACHINE_ID_SHIFT = SEQUENCE_BITS;
|
||||||
|
// 时间戳左移位数
|
||||||
|
private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + MACHINE_ID_BITS;
|
||||||
|
|
||||||
|
private long machineId;
|
||||||
|
private long sequence = 0L;
|
||||||
|
private long lastTimestamp = -1L;
|
||||||
|
|
||||||
|
public SnowflakeIdGenerator(long machineId) {
|
||||||
|
if (machineId > MAX_MACHINE_ID || machineId < 0) {
|
||||||
|
throw new IllegalArgumentException("Machine ID must be between 0 and " + MAX_MACHINE_ID);
|
||||||
|
}
|
||||||
|
this.machineId = machineId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized long nextId() {
|
||||||
|
long timestamp = System.currentTimeMillis();
|
||||||
|
|
||||||
|
if (timestamp < lastTimestamp) {
|
||||||
|
throw new RuntimeException("Clock moved backwards. Refusing to generate id");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timestamp == lastTimestamp) {
|
||||||
|
sequence = (sequence + 1) & MAX_SEQUENCE;
|
||||||
|
if (sequence == 0) {
|
||||||
|
timestamp = tilNextMillis(lastTimestamp);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sequence = 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastTimestamp = timestamp;
|
||||||
|
|
||||||
|
return ((timestamp - START_TIMESTAMP) << TIMESTAMP_SHIFT) |
|
||||||
|
(machineId << MACHINE_ID_SHIFT) |
|
||||||
|
sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long tilNextMillis(long lastTimestamp) {
|
||||||
|
long timestamp = System.currentTimeMillis();
|
||||||
|
while (timestamp <= lastTimestamp) {
|
||||||
|
timestamp = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
System.out.println(getUUID());
|
System.out.println(getUUID());
|
||||||
logger.debug("test");
|
logger.debug("test");
|
||||||
|
|
||||||
|
// 测试雪花算法
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
System.out.println("Snowflake ID: " + getSnowflakeId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.kexue.skills.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账户扣费配置属性
|
||||||
|
*
|
||||||
|
* @author 系统生成
|
||||||
|
* @since 2026-04-13
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Component
|
||||||
|
public class AccountDeductionProperties {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扣费系数,默认2倍
|
||||||
|
* 例如:系数为2时,实际消耗1积分,扣除2积分
|
||||||
|
*/
|
||||||
|
@Value("${account.deduction.coefficient:2}")
|
||||||
|
private BigDecimal coefficient;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
package com.kexue.skills.config;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GLM API配置类
|
||||||
|
*
|
||||||
|
* @author 维哥
|
||||||
|
* @since 2026-02-27
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "spring.ai.glm")
|
||||||
|
public class GlmConfig {
|
||||||
|
|
||||||
|
private String baseUrl;
|
||||||
|
private String apiKey;
|
||||||
|
private ChatOptions chat;
|
||||||
|
|
||||||
|
public static class ChatOptions {
|
||||||
|
private String model;
|
||||||
|
private Double temperature;
|
||||||
|
private Integer maxTokens;
|
||||||
|
|
||||||
|
public String getModel() {
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModel(String model) {
|
||||||
|
this.model = model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getTemperature() {
|
||||||
|
return temperature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTemperature(Double temperature) {
|
||||||
|
this.temperature = temperature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getMaxTokens() {
|
||||||
|
return maxTokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxTokens(Integer maxTokens) {
|
||||||
|
this.maxTokens = maxTokens;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBaseUrl() {
|
||||||
|
return baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBaseUrl(String baseUrl) {
|
||||||
|
this.baseUrl = baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getApiKey() {
|
||||||
|
return apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setApiKey(String apiKey) {
|
||||||
|
this.apiKey = apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChatOptions getChat() {
|
||||||
|
return chat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChat(ChatOptions chat) {
|
||||||
|
this.chat = chat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,40 @@
|
||||||
|
package com.kexue.skills.config;
|
||||||
|
|
||||||
|
import com.kexue.skills.interceptor.LogInterceptor;
|
||||||
|
import com.kexue.skills.mapper.SysLogMapper;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志配置类
|
||||||
|
* 注册日志拦截器,启用异步支持
|
||||||
|
*
|
||||||
|
* @author 王志维
|
||||||
|
* @since 2026-04-14
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@EnableAsync // 启用异步支持
|
||||||
|
public class LogConfiguration implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private SysLogMapper sysLogMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册拦截器
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
|
LogInterceptor logInterceptor = new LogInterceptor();
|
||||||
|
logInterceptor.setSysLogMapper(sysLogMapper);
|
||||||
|
registry.addInterceptor(logInterceptor)
|
||||||
|
.addPathPatterns("/api/**") // 拦截所有 API 请求
|
||||||
|
.excludePathPatterns(
|
||||||
|
"/api/login/validateToken", // 排除 token 验证接口
|
||||||
|
"/api/captcha/**" // 排除验证码接口
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
package com.kexue.skills.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付配置类
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "payment")
|
||||||
|
@Data
|
||||||
|
public class PaymentConfig {
|
||||||
|
// 微信支付配置
|
||||||
|
private WechatPayConfig wechat;
|
||||||
|
// 支付宝支付配置
|
||||||
|
private AlipayConfig alipay;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信支付配置
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class WechatPayConfig {
|
||||||
|
// 应用ID
|
||||||
|
private String appId;
|
||||||
|
// 商户号
|
||||||
|
private String mchId;
|
||||||
|
// 商户密钥
|
||||||
|
private String mchKey;
|
||||||
|
// 商户API证书序列号
|
||||||
|
private String mchSerialNo;
|
||||||
|
// 商户私钥文件路径
|
||||||
|
private String privateKeyPath;
|
||||||
|
// 微信服务器地址
|
||||||
|
private String domain;
|
||||||
|
// 支付回调地址
|
||||||
|
private String notifyUrl;
|
||||||
|
// 支付成功跳转地址
|
||||||
|
private String returnUrl;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付宝支付配置
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class AlipayConfig {
|
||||||
|
// 应用ID
|
||||||
|
private String appId;
|
||||||
|
// 商户私钥
|
||||||
|
private String privateKey;
|
||||||
|
// 支付宝公钥
|
||||||
|
private String publicKey;
|
||||||
|
// 支付回调地址
|
||||||
|
private String notifyUrl;
|
||||||
|
// 支付成功跳转地址
|
||||||
|
private String returnUrl;
|
||||||
|
// 签名类型
|
||||||
|
private String signType;
|
||||||
|
// 字符编码
|
||||||
|
private String charset;
|
||||||
|
// 支付宝网关
|
||||||
|
private String gatewayUrl;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -24,9 +24,9 @@ public class SaTokenConfig implements WebMvcConfigurer {
|
||||||
// 拦截所有请求,除了登录、注册、文档等不需要认证的接口
|
// 拦截所有请求,除了登录、注册、文档等不需要认证的接口
|
||||||
SaRouter
|
SaRouter
|
||||||
// 放行登录接口
|
// 放行登录接口
|
||||||
.match("/login/**").stop()
|
.match("/api/login/**").stop()
|
||||||
// 放行注册接口
|
// 放行注册接口
|
||||||
.match("/register/**").stop()
|
.match("/api/register/**").stop()
|
||||||
// 放行Swagger文档
|
// 放行Swagger文档
|
||||||
.match("/doc.html").stop()
|
.match("/doc.html").stop()
|
||||||
.match("/swagger-ui/**").stop()
|
.match("/swagger-ui/**").stop()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
package com.kexue.skills.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传配置类
|
||||||
|
* 用于读取web.upload配置
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "web.upload")
|
||||||
|
public class UploadConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传路径
|
||||||
|
*/
|
||||||
|
private String path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件上传目录
|
||||||
|
*/
|
||||||
|
public String getFileUploadDir() {
|
||||||
|
return path + "file/";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片上传目录
|
||||||
|
*/
|
||||||
|
public String getImgUploadDir() {
|
||||||
|
return path + "images/";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件访问路径前缀
|
||||||
|
*/
|
||||||
|
public String getFileUrlPrefix() {
|
||||||
|
return "/upload/file/";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片访问路径前缀
|
||||||
|
*/
|
||||||
|
public String getImgUrlPrefix() {
|
||||||
|
return "/upload/images/";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,17 +1,31 @@
|
||||||
package com.kexue.skills.controller;
|
package com.kexue.skills.controller;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
import com.github.pagehelper.PageInfo;
|
import com.github.pagehelper.PageInfo;
|
||||||
|
import com.kexue.skills.annotation.Log;
|
||||||
import com.kexue.skills.annotation.RequireAuth;
|
import com.kexue.skills.annotation.RequireAuth;
|
||||||
|
import com.kexue.skills.annotation.RequireRole;
|
||||||
import com.kexue.skills.common.CommonResult;
|
import com.kexue.skills.common.CommonResult;
|
||||||
|
import com.kexue.skills.common.Const;
|
||||||
import com.kexue.skills.entity.Account;
|
import com.kexue.skills.entity.Account;
|
||||||
import com.kexue.skills.entity.dto.AccountDto;
|
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.entity.dto.AccountTransactionDto;
|
||||||
|
import com.kexue.skills.entity.dto.ConsumptionGroupedDto;
|
||||||
import com.kexue.skills.service.AccountService;
|
import com.kexue.skills.service.AccountService;
|
||||||
|
import com.kexue.skills.service.SysUserService;
|
||||||
|
import com.kexue.skills.entity.SysUser;
|
||||||
|
import com.kexue.skills.exception.BizException;
|
||||||
|
import com.kexue.skills.common.CacheManager;
|
||||||
|
import java.math.BigDecimal;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -21,6 +35,7 @@ import java.util.List;
|
||||||
* @author 王志维
|
* @author 王志维
|
||||||
* @since 2025-02-21 23:01:48
|
* @since 2025-02-21 23:01:48
|
||||||
*/
|
*/
|
||||||
|
@Log(module = "账户管理")
|
||||||
@Tag(name = "账户管理 api")
|
@Tag(name = "账户管理 api")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/account")
|
@RequestMapping("/api/account")
|
||||||
|
|
@ -31,6 +46,9 @@ public class AccountController {
|
||||||
@Resource
|
@Resource
|
||||||
private AccountService accountService;
|
private AccountService accountService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private SysUserService sysUserService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分页查询
|
* 分页查询
|
||||||
*
|
*
|
||||||
|
|
@ -63,7 +81,7 @@ public class AccountController {
|
||||||
* @param accountId 主键
|
* @param accountId 主键
|
||||||
* @return 单条数据
|
* @return 单条数据
|
||||||
*/
|
*/
|
||||||
@Operation(summary = "通过ID查询账户", description = "通过ID查询账户")
|
@Operation(summary = "通过查询账户", description = "通过ID查询账户")
|
||||||
@PostMapping("/queryById/{accountId}")
|
@PostMapping("/queryById/{accountId}")
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
public CommonResult<Account> queryById(@Parameter(description = "账户ID") @PathVariable("accountId") Long accountId) {
|
public CommonResult<Account> queryById(@Parameter(description = "账户ID") @PathVariable("accountId") Long accountId) {
|
||||||
|
|
@ -71,15 +89,123 @@ public class AccountController {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通过用户ID查询单条数据
|
* 通过当前登录用户ID查询账户
|
||||||
*
|
*
|
||||||
* @param userId 用户ID
|
|
||||||
* @return 单条数据
|
* @return 单条数据
|
||||||
*/
|
*/
|
||||||
@Operation(summary = "通过用户ID查询账户", description = "通过用户ID查询账户")
|
@Operation(summary = "通过当前登录用户查询账户", description = "通过当前登录用户查询账户")
|
||||||
@PostMapping("/queryByUserId/{userId}")
|
@PostMapping("/currentAccount")
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
public CommonResult<Account> queryByUserId(@Parameter(description = "用户ID") @PathVariable("userId") Long userId) {
|
public CommonResult<Account> currentAccount() {
|
||||||
|
Long userId = Long.parseLong(StpUtil.getLoginId().toString());
|
||||||
return CommonResult.success(this.accountService.queryByUserId(userId));
|
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")
|
||||||
|
public CommonResult<BigDecimal> reduceBalanceWithToken( @RequestBody TokenConsumptionDto tokenConsumptionDto) {
|
||||||
|
return CommonResult.success(this.accountService.reduceBalanceWithToken(tokenConsumptionDto));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 给用户赠送金额(不可提现)
|
||||||
|
* 只有管理员可以调用
|
||||||
|
*
|
||||||
|
* @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()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前登录用户交易记录
|
||||||
|
*
|
||||||
|
* @return 交易记录列表
|
||||||
|
*/
|
||||||
|
@Operation(summary = "获取当前登录用户交易记录", description = "获取当前登录用户交易记录")
|
||||||
|
@PostMapping("/getTransactions")
|
||||||
|
@RequireAuth
|
||||||
|
public CommonResult<List<com.kexue.skills.entity.AccountTransaction>> getTransactions() {
|
||||||
|
Long userId = Long.parseLong(StpUtil.getLoginId().toString());
|
||||||
|
return CommonResult.success(this.accountService.getTransactions(userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询充值记录
|
||||||
|
*
|
||||||
|
* @param queryDto 查询条件
|
||||||
|
* @return 分页结果
|
||||||
|
*/
|
||||||
|
@Operation(summary = "分页查询充值记录", description = "分页查询所有充值记录,默认根据时间倒序")
|
||||||
|
@PostMapping("/getRechargePageList")
|
||||||
|
@RequireAuth
|
||||||
|
public CommonResult<PageInfo<com.kexue.skills.entity.AccountTransaction>> getRechargePageList(@RequestBody AccountTransactionDto queryDto) {
|
||||||
|
return CommonResult.success(this.accountService.getRechargePageList(queryDto));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询消费记录(按callId分组)
|
||||||
|
*
|
||||||
|
* @param queryDto 查询条件
|
||||||
|
* @return 分页结果
|
||||||
|
*/
|
||||||
|
@Operation(summary = "分页查询消费记录", description = "分页查询所有消费记录,按callId分组,question取最早入库的记录,默认根据时间倒序")
|
||||||
|
@PostMapping("/getConsumptionGroupedPageList")
|
||||||
|
@RequireAuth
|
||||||
|
public CommonResult<PageInfo<ConsumptionGroupedDto>> getConsumptionGroupedPageList(@RequestBody AccountTransactionDto queryDto) {
|
||||||
|
return CommonResult.success(this.accountService.getConsumptionGroupedPageList(queryDto));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询赠送记录
|
||||||
|
*
|
||||||
|
* @param queryDto 查询条件
|
||||||
|
* @return 分页结果
|
||||||
|
*/
|
||||||
|
@Operation(summary = "分页查询赠送记录", description = "分页查询所有赠送记录,默认根据时间倒序")
|
||||||
|
@PostMapping("/getGiftPageList")
|
||||||
|
@RequireAuth
|
||||||
|
public CommonResult<PageInfo<com.kexue.skills.entity.AccountTransaction>> getGiftPageList(@RequestBody AccountTransactionDto queryDto) {
|
||||||
|
return CommonResult.success(this.accountService.getGiftPageList(queryDto));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
package com.kexue.skills.controller;
|
||||||
|
|
||||||
|
import com.kexue.skills.annotation.Log;
|
||||||
|
import com.kexue.skills.common.CommonResult;
|
||||||
|
import com.kexue.skills.entity.AccountFrozen;
|
||||||
|
import com.kexue.skills.entity.dto.AccountFrozenDto;
|
||||||
|
import com.kexue.skills.entity.dto.AccountReleaseDto;
|
||||||
|
import com.kexue.skills.service.AccountFrozenService;
|
||||||
|
import com.kexue.skills.common.Result;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账户冻结单控制器
|
||||||
|
*
|
||||||
|
* @author 系统生成
|
||||||
|
* @since 2026-04-11
|
||||||
|
*/
|
||||||
|
@Log(module = "账户冻结")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/accountFrozen")
|
||||||
|
@CrossOrigin(origins = "*")
|
||||||
|
@Tag(name = "账户冻结单", description = "账户冻结单管理接口")
|
||||||
|
public class AccountFrozenController {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(AccountFrozenController.class);
|
||||||
|
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AccountFrozenService accountFrozenService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建冻结单
|
||||||
|
* @param accountFrozenDto 冻结单DTO
|
||||||
|
* @return 冻结单信息
|
||||||
|
*/
|
||||||
|
@PostMapping("/frozen")
|
||||||
|
@Operation(summary = "创建冻结单", description = "创建账户冻结单")
|
||||||
|
public CommonResult<AccountFrozen> createFrozen(@RequestBody AccountFrozenDto accountFrozenDto) {
|
||||||
|
try {
|
||||||
|
logger.info("创建冻结单入参: {}", objectMapper.writeValueAsString(accountFrozenDto));
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("创建冻结单入参序列化失败", e);
|
||||||
|
}
|
||||||
|
AccountFrozen accountFrozen = accountFrozenService.createFrozen(accountFrozenDto);
|
||||||
|
return CommonResult.success(accountFrozen);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 释放冻结单
|
||||||
|
* @param accountReleaseDto 冻结单释放DTO
|
||||||
|
* @return 冻结单信息
|
||||||
|
*/
|
||||||
|
@PostMapping("/release")
|
||||||
|
@Operation(summary = "释放冻结单", description = "释放账户冻结单")
|
||||||
|
public CommonResult<AccountFrozen> releaseFrozen(@RequestBody AccountReleaseDto accountReleaseDto) {
|
||||||
|
try {
|
||||||
|
logger.info("释放冻结单入参: {}", objectMapper.writeValueAsString(accountReleaseDto));
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("释放冻结单入参序列化失败", e);
|
||||||
|
}
|
||||||
|
AccountFrozen accountFrozen = accountFrozenService.releaseFrozen(accountReleaseDto);
|
||||||
|
return CommonResult.success(accountFrozen);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,8 @@ import com.kexue.skills.entity.CmsCategory;
|
||||||
import com.kexue.skills.entity.base.IdDto;
|
import com.kexue.skills.entity.base.IdDto;
|
||||||
import com.kexue.skills.entity.dto.CmsCategoryDto;
|
import com.kexue.skills.entity.dto.CmsCategoryDto;
|
||||||
import com.kexue.skills.service.CmsCategoryService;
|
import com.kexue.skills.service.CmsCategoryService;
|
||||||
|
import com.kexue.skills.service.CmsTagService;
|
||||||
|
import com.kexue.skills.service.CmsCategoryTagService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
@ -29,6 +31,18 @@ public class CmsCategoryController {
|
||||||
*/
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private CmsCategoryService cmsCategoryService;
|
private CmsCategoryService cmsCategoryService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标签服务对象
|
||||||
|
*/
|
||||||
|
@Resource
|
||||||
|
private CmsTagService cmsTagService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分类标签关联服务对象
|
||||||
|
*/
|
||||||
|
@Resource
|
||||||
|
private CmsCategoryTagService cmsCategoryTagService;
|
||||||
/**
|
/**
|
||||||
* 分页查询
|
* 分页查询
|
||||||
*
|
*
|
||||||
|
|
@ -126,4 +140,25 @@ public class CmsCategoryController {
|
||||||
public CommonResult<java.util.Map<Long, String>> getCategoryDict() {
|
public CommonResult<java.util.Map<Long, String>> getCategoryDict() {
|
||||||
return CommonResult.success(cmsCategoryService.getCategoryDict());
|
return CommonResult.success(cmsCategoryService.getCategoryDict());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取标签列表
|
||||||
|
*
|
||||||
|
* @param categoryId 分类ID(可选),不传则返回所有标签
|
||||||
|
* @return 标签列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/tagList")
|
||||||
|
@Operation(summary = "获取标签列表", description = "获取标签列表,若传入分类ID则返回该分类下的标签,否则返回所有标签")
|
||||||
|
public CommonResult<java.util.List<com.kexue.skills.entity.CmsTag>> getTagList(@RequestParam(value = "categoryId", required = false) Long categoryId) {
|
||||||
|
if (categoryId != null) {
|
||||||
|
// 根据分类ID查询标签列表
|
||||||
|
return CommonResult.success(cmsCategoryTagService.getTagsByCategoryId(categoryId));
|
||||||
|
} else {
|
||||||
|
// 查询所有标签
|
||||||
|
com.kexue.skills.entity.dto.CmsTagDto queryDto = new com.kexue.skills.entity.dto.CmsTagDto();
|
||||||
|
queryDto.setDeleteFlag(0);
|
||||||
|
queryDto.setStatus(1);
|
||||||
|
return CommonResult.success(cmsTagService.getList(queryDto));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,17 +1,23 @@
|
||||||
package com.kexue.skills.controller;
|
package com.kexue.skills.controller;
|
||||||
|
|
||||||
import com.github.pagehelper.PageInfo;
|
import com.github.pagehelper.PageInfo;
|
||||||
|
import com.kexue.skills.annotation.Log;
|
||||||
import com.kexue.skills.annotation.RequireAuth;
|
import com.kexue.skills.annotation.RequireAuth;
|
||||||
|
import com.kexue.skills.common.Assert;
|
||||||
import com.kexue.skills.common.CommonResult;
|
import com.kexue.skills.common.CommonResult;
|
||||||
import com.kexue.skills.entity.CmsContent;
|
import com.kexue.skills.entity.CmsContent;
|
||||||
import com.kexue.skills.entity.base.IdDto;
|
import com.kexue.skills.entity.base.IdDto;
|
||||||
import com.kexue.skills.entity.dto.CmsContentDto;
|
import com.kexue.skills.entity.dto.CmsContentDto;
|
||||||
|
import com.kexue.skills.entity.dto.QueryContentDto;
|
||||||
|
import com.kexue.skills.entity.request.ImportPathDto;
|
||||||
import com.kexue.skills.service.CmsContentService;
|
import com.kexue.skills.service.CmsContentService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (CmsContent)表控制层
|
* (CmsContent)表控制层
|
||||||
|
|
@ -21,7 +27,7 @@ import javax.annotation.Resource;
|
||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("api/cmsContent")
|
@RequestMapping("api/cmsContent")
|
||||||
@Tag(name = "内容(skills)管理 Api")
|
@Tag(name = "skill(skills)管理 Api")
|
||||||
@CrossOrigin(origins = "*")
|
@CrossOrigin(origins = "*")
|
||||||
public class CmsContentController {
|
public class CmsContentController {
|
||||||
/**
|
/**
|
||||||
|
|
@ -60,7 +66,7 @@ public class CmsContentController {
|
||||||
* @return 单条数据
|
* @return 单条数据
|
||||||
*/
|
*/
|
||||||
@PostMapping("queryById/{contentId}")
|
@PostMapping("queryById/{contentId}")
|
||||||
@Operation(summary = "通过ID查询内容", description = "通过ID查询内容")
|
@Operation(summary = "通过ID查询skill", description = "通过ID查询skill")
|
||||||
public CommonResult<CmsContent> queryById(@PathVariable("contentId") Long contentId) {
|
public CommonResult<CmsContent> queryById(@PathVariable("contentId") Long contentId) {
|
||||||
// 增加阅读量
|
// 增加阅读量
|
||||||
cmsContentService.increaseViewCount(contentId);
|
cmsContentService.increaseViewCount(contentId);
|
||||||
|
|
@ -74,7 +80,7 @@ public class CmsContentController {
|
||||||
* @return 新增结果
|
* @return 新增结果
|
||||||
*/
|
*/
|
||||||
@PostMapping("/insert")
|
@PostMapping("/insert")
|
||||||
@Operation(summary = "新增内容", description = "新增内容")
|
@Operation(summary = "新增skill", description = "新增skill")
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
public CommonResult<CmsContent> insert(@RequestBody CmsContent cmsContent) {
|
public CommonResult<CmsContent> insert(@RequestBody CmsContent cmsContent) {
|
||||||
return CommonResult.success(cmsContentService.insert(cmsContent));
|
return CommonResult.success(cmsContentService.insert(cmsContent));
|
||||||
|
|
@ -87,7 +93,7 @@ public class CmsContentController {
|
||||||
* @return 编辑结果
|
* @return 编辑结果
|
||||||
*/
|
*/
|
||||||
@PostMapping("/update")
|
@PostMapping("/update")
|
||||||
@Operation(summary = "更新内容", description = "更新内容")
|
@Operation(summary = "更新skill", description = "更新skill")
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
public CommonResult<CmsContent> update(@RequestBody CmsContent cmsContent) {
|
public CommonResult<CmsContent> update(@RequestBody CmsContent cmsContent) {
|
||||||
return CommonResult.success(cmsContentService.update(cmsContent));
|
return CommonResult.success(cmsContentService.update(cmsContent));
|
||||||
|
|
@ -96,7 +102,7 @@ public class CmsContentController {
|
||||||
/**
|
/**
|
||||||
* 更新审核状态
|
* 更新审核状态
|
||||||
*
|
*
|
||||||
* @param contentId 内容ID
|
* @param contentId skillID
|
||||||
* @param auditStatus 审核状态
|
* @param auditStatus 审核状态
|
||||||
* @param reviewerId 审核人ID
|
* @param reviewerId 审核人ID
|
||||||
* @param reviewerName 审核人名称
|
* @param reviewerName 审核人名称
|
||||||
|
|
@ -117,7 +123,7 @@ public class CmsContentController {
|
||||||
/**
|
/**
|
||||||
* 更新发布状态
|
* 更新发布状态
|
||||||
*
|
*
|
||||||
* @param contentId 内容ID
|
* @param contentId skillID
|
||||||
* @param publishStatus 发布状态
|
* @param publishStatus 发布状态
|
||||||
* @param publishTime 发布时间
|
* @param publishTime 发布时间
|
||||||
* @param updateBy 更新人
|
* @param updateBy 更新人
|
||||||
|
|
@ -136,7 +142,7 @@ public class CmsContentController {
|
||||||
/**
|
/**
|
||||||
* 增加阅读量
|
* 增加阅读量
|
||||||
*
|
*
|
||||||
* @param contentId 内容ID
|
* @param contentId skillID
|
||||||
* @return 增加结果
|
* @return 增加结果
|
||||||
*/
|
*/
|
||||||
@PostMapping("/increaseViewCount/{contentId}")
|
@PostMapping("/increaseViewCount/{contentId}")
|
||||||
|
|
@ -152,7 +158,7 @@ public class CmsContentController {
|
||||||
* @return 删除数据
|
* @return 删除数据
|
||||||
*/
|
*/
|
||||||
@PostMapping("/logicDeleteById")
|
@PostMapping("/logicDeleteById")
|
||||||
@Operation(summary = "逻辑删除内容", description = "逻辑删除内容")
|
@Operation(summary = "逻辑删除skill", description = "逻辑删除skill")
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
public CommonResult<Boolean> logicDeleteById(@RequestBody IdDto idDto) {
|
public CommonResult<Boolean> logicDeleteById(@RequestBody IdDto idDto) {
|
||||||
return CommonResult.success(cmsContentService.logicDeleteById(idDto.getId(), "admin") > 0);
|
return CommonResult.success(cmsContentService.logicDeleteById(idDto.getId(), "admin") > 0);
|
||||||
|
|
@ -165,9 +171,208 @@ public class CmsContentController {
|
||||||
* @return 删除数据
|
* @return 删除数据
|
||||||
*/
|
*/
|
||||||
@PostMapping("deleteById/{contentId}")
|
@PostMapping("deleteById/{contentId}")
|
||||||
@Operation(summary = "物理删除内容", description = "物理删除内容")
|
@Operation(summary = "物理删除skill", description = "物理删除skill")
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
public CommonResult<Boolean> deleteById(@PathVariable("contentId") Long contentId) {
|
public CommonResult<Boolean> deleteById(@PathVariable("contentId") Long contentId) {
|
||||||
return CommonResult.success(cmsContentService.deleteById(contentId) > 0);
|
return CommonResult.success(cmsContentService.deleteById(contentId) > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加收藏
|
||||||
|
*
|
||||||
|
* @param contentId skillID
|
||||||
|
* @return 操作结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/addFavorite")
|
||||||
|
@Operation(summary = "添加收藏", description = "添加skill收藏")
|
||||||
|
@RequireAuth
|
||||||
|
public CommonResult<Boolean> addFavorite(@RequestParam("contentId") Long contentId) {
|
||||||
|
return CommonResult.success(cmsContentService.addFavorite(contentId) > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消收藏
|
||||||
|
*
|
||||||
|
* @param contentId skillID
|
||||||
|
* @return 操作结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/removeFavorite")
|
||||||
|
@Operation(summary = "取消收藏", description = "取消skill收藏")
|
||||||
|
@RequireAuth
|
||||||
|
public CommonResult<Boolean> removeFavorite(@RequestParam("contentId") Long contentId) {
|
||||||
|
return CommonResult.success(cmsContentService.removeFavorite(contentId) > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加查看记录
|
||||||
|
*
|
||||||
|
* @param contentId skillID
|
||||||
|
* @return 操作结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/addView")
|
||||||
|
@Operation(summary = "添加查看记录", description = "添加skill查看记录")
|
||||||
|
@RequireAuth
|
||||||
|
public CommonResult<Boolean> addView(@RequestParam("contentId") Long contentId) {
|
||||||
|
return CommonResult.success(cmsContentService.addView(contentId) > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户历史查看的内容列表
|
||||||
|
*
|
||||||
|
* @param queryDto 筛选条件,包含分页信息
|
||||||
|
* @return 查询结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/getUserHistory")
|
||||||
|
@Operation(summary = "获取用户历史查看", description = "获取当前用户历史查看的内容列表,带分页")
|
||||||
|
@RequireAuth
|
||||||
|
public CommonResult<PageInfo<CmsContent>> getUserHistory(@RequestBody CmsContentDto queryDto) {
|
||||||
|
return CommonResult.success(cmsContentService.getPageListByUserHistory(queryDto));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户收藏的内容列表
|
||||||
|
*
|
||||||
|
* @param queryDto 筛选条件,包含分页信息
|
||||||
|
* @return 查询结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/getUserFavorites")
|
||||||
|
@Operation(summary = "获取用户收藏", description = "获取当前用户收藏的内容列表,带分页")
|
||||||
|
@RequireAuth
|
||||||
|
public CommonResult<PageInfo<CmsContent>> getUserFavorites(@RequestBody CmsContentDto queryDto) {
|
||||||
|
return CommonResult.success(cmsContentService.getPageListByUserFavorites(queryDto));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户购买的内容列表
|
||||||
|
*
|
||||||
|
* @param queryDto 筛选条件,包含分页信息
|
||||||
|
* @return 查询结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/getUserPurchases")
|
||||||
|
@Operation(summary = "获取用户拥有", description = "获取当前用户购买的内容列表,带分页")
|
||||||
|
@RequireAuth
|
||||||
|
public CommonResult<PageInfo<CmsContent>> getUserPurchases(@RequestBody CmsContentDto queryDto) {
|
||||||
|
return CommonResult.success(cmsContentService.getPageListByUserPurchases(queryDto));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户创建的内容列表
|
||||||
|
*
|
||||||
|
* @param queryDto 筛选条件,包含分页信息和发布状态
|
||||||
|
* @return 查询结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/getUserCreated")
|
||||||
|
@Operation(summary = "获取用户创建", description = "获取当前用户创建的内容列表,带分页,可查询已发布、未发布")
|
||||||
|
@RequireAuth
|
||||||
|
public CommonResult<PageInfo<CmsContent>> getUserCreated(@RequestBody CmsContentDto queryDto) {
|
||||||
|
return CommonResult.success(cmsContentService.getPageListByUserCreated(queryDto));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入Excel数据到CmsContent
|
||||||
|
*
|
||||||
|
* @param file Excel文件
|
||||||
|
* @param createBy 创建人
|
||||||
|
* @return 导入结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/importFromExcel")
|
||||||
|
@Operation(summary = "导入Excel数据", description = "从Excel文件导入数据到CmsContent")
|
||||||
|
@RequireAuth
|
||||||
|
public CommonResult<Integer> importFromExcel(@RequestParam("file") MultipartFile file, @RequestParam("createBy") String createBy) {
|
||||||
|
try {
|
||||||
|
byte[] fileBytes = file.getBytes();
|
||||||
|
int successCount = cmsContentService.importFromExcel(fileBytes, createBy);
|
||||||
|
return CommonResult.success(successCount);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return CommonResult.failed("导入失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取CmsContent的content字段内容
|
||||||
|
*
|
||||||
|
* @param queryContentDto 包含contentId和languageType的DTO对象
|
||||||
|
* @return content或contentEn字段的内容
|
||||||
|
*/
|
||||||
|
@PostMapping("/getContent")
|
||||||
|
@Operation(summary = "获取内容详情", description = "根据languageType获取content或contentEn字段的内容")
|
||||||
|
public CommonResult<String> getContent(@RequestBody QueryContentDto queryContentDto) {
|
||||||
|
String content = cmsContentService.getContent(queryContentDto.getContentId(), queryContentDto.getLanguageType());
|
||||||
|
return CommonResult.success(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取CmsContent的title字段内容
|
||||||
|
*
|
||||||
|
* @param contentId 内容ID
|
||||||
|
* @return title字段的内容
|
||||||
|
*/
|
||||||
|
@PostMapping("/getTitle/{contentId}")
|
||||||
|
@Operation(summary = "获取标题", description = "根据contentId获取title字段的内容")
|
||||||
|
public CommonResult<String> getTitle(@PathVariable("contentId") Long contentId) {
|
||||||
|
String title = cmsContentService.getTitle(contentId);
|
||||||
|
return CommonResult.success(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从指定目录导入Excel数据到CmsContent
|
||||||
|
*
|
||||||
|
* @param importPathDto 导入路径请求参数
|
||||||
|
* @param createBy 创建人
|
||||||
|
* @return 导入结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/importFromPath")
|
||||||
|
@Operation(summary = "从目录导入Excel数据", description = "从指定目录导入Excel数据到CmsContent")
|
||||||
|
@RequireAuth
|
||||||
|
public CommonResult<Integer> importFromPath(@RequestBody ImportPathDto importPathDto, @RequestParam("createBy") String createBy) {
|
||||||
|
try {
|
||||||
|
int successCount = cmsContentService.importFromPath(importPathDto, createBy);
|
||||||
|
return CommonResult.success(successCount);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return CommonResult.failed("导入失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从ZIP文件批量导入Excel数据到CmsContent
|
||||||
|
*
|
||||||
|
* @param file ZIP文件
|
||||||
|
* @param createBy 创建人
|
||||||
|
* @return 导入结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/importFromZip")
|
||||||
|
@Operation(summary = "从ZIP文件批量导入Excel数据", description = "上传ZIP文件,批量导入其中的所有Excel文件到CmsContent")
|
||||||
|
@RequireAuth
|
||||||
|
public CommonResult<Integer> importFromZip(@RequestParam("file") MultipartFile file, @RequestParam("createBy") String createBy) {
|
||||||
|
try {
|
||||||
|
byte[] zipFileBytes = file.getBytes();
|
||||||
|
int successCount = cmsContentService.importFromZip(zipFileBytes, createBy);
|
||||||
|
return CommonResult.success(successCount);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return CommonResult.failed("导入失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从指定目录读取Excel数据并更新CmsContent
|
||||||
|
*
|
||||||
|
* @param importPathDto 导入路径请求参数
|
||||||
|
* @param updateBy 更新人
|
||||||
|
* @return 更新结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/updateFromPath")
|
||||||
|
@Operation(summary = "从目录更新Excel数据", description = "从指定目录读取Excel数据并更新CmsContent")
|
||||||
|
@RequireAuth
|
||||||
|
public CommonResult<Integer> updateFromPath(@RequestBody ImportPathDto importPathDto, @RequestParam("updateBy") String updateBy) {
|
||||||
|
try {
|
||||||
|
int successCount = cmsContentService.updateFromPath(importPathDto, updateBy);
|
||||||
|
return CommonResult.success(successCount);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return CommonResult.failed("更新失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.kexue.skills.controller;
|
package com.kexue.skills.controller;
|
||||||
|
|
||||||
import com.kexue.skills.common.CommonResult;
|
import com.kexue.skills.common.CommonResult;
|
||||||
|
import com.kexue.skills.config.UploadConfig;
|
||||||
import com.kexue.skills.entity.request.UploadResponse;
|
import com.kexue.skills.entity.request.UploadResponse;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
@ -8,6 +9,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
@ -21,13 +23,13 @@ import java.nio.file.StandardCopyOption;
|
||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("api/common")
|
@RequestMapping("api/common")
|
||||||
@Tag(name = "公共 Api")
|
@Tag(name = "Common Api")
|
||||||
@CrossOrigin(origins = "*")
|
@CrossOrigin(origins = "*")
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class CommonController {
|
public class CommonController {
|
||||||
|
|
||||||
private static final String FILE_UPLOAD_DIR = "/data/service/hyxp-portal/upload/file/";
|
@Resource
|
||||||
private static final String IMG_UPLOAD_DIR = "/data/service/hyxp-portal/upload/images/";
|
private UploadConfig uploadConfig;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -49,7 +51,7 @@ public class CommonController {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 创建上传目录(如果不存在)
|
// 创建上传目录(如果不存在)
|
||||||
Path uploadPath = Paths.get(FILE_UPLOAD_DIR);
|
Path uploadPath = Paths.get(uploadConfig.getFileUploadDir());
|
||||||
if (!Files.exists(uploadPath)) {
|
if (!Files.exists(uploadPath)) {
|
||||||
Files.createDirectories(uploadPath);
|
Files.createDirectories(uploadPath);
|
||||||
}
|
}
|
||||||
|
|
@ -62,7 +64,7 @@ public class CommonController {
|
||||||
|
|
||||||
UploadResponse uploadResponse = new UploadResponse();
|
UploadResponse uploadResponse = new UploadResponse();
|
||||||
uploadResponse.setFileName(file.getOriginalFilename());
|
uploadResponse.setFileName(file.getOriginalFilename());
|
||||||
uploadResponse.setFileUrl("/upload/file/" + file.getOriginalFilename());
|
uploadResponse.setFileUrl(uploadConfig.getFileUrlPrefix() + file.getOriginalFilename());
|
||||||
return CommonResult.success(uploadResponse);
|
return CommonResult.success(uploadResponse);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|
@ -94,7 +96,7 @@ public class CommonController {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 创建上传目录(如果不存在)
|
// 创建上传目录(如果不存在)
|
||||||
Path uploadPath = Paths.get(IMG_UPLOAD_DIR);
|
Path uploadPath = Paths.get(uploadConfig.getImgUploadDir());
|
||||||
if (!Files.exists(uploadPath)) {
|
if (!Files.exists(uploadPath)) {
|
||||||
Files.createDirectories(uploadPath);
|
Files.createDirectories(uploadPath);
|
||||||
}
|
}
|
||||||
|
|
@ -107,7 +109,7 @@ public class CommonController {
|
||||||
|
|
||||||
UploadResponse uploadResponse = new UploadResponse();
|
UploadResponse uploadResponse = new UploadResponse();
|
||||||
uploadResponse.setFileName(image.getOriginalFilename());
|
uploadResponse.setFileName(image.getOriginalFilename());
|
||||||
uploadResponse.setFileUrl("/upload/images/" + image.getOriginalFilename());
|
uploadResponse.setFileUrl(uploadConfig.getImgUrlPrefix() + image.getOriginalFilename());
|
||||||
return CommonResult.success(uploadResponse);
|
return CommonResult.success(uploadResponse);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.kexue.skills.controller;
|
package com.kexue.skills.controller;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
import com.github.pagehelper.PageInfo;
|
import com.github.pagehelper.PageInfo;
|
||||||
import com.kexue.skills.annotation.RequireAuth;
|
import com.kexue.skills.annotation.RequireAuth;
|
||||||
import com.kexue.skills.common.CommonResult;
|
import com.kexue.skills.common.CommonResult;
|
||||||
|
|
@ -73,7 +74,6 @@ public class ContentPurchaseController {
|
||||||
/**
|
/**
|
||||||
* 购买内容
|
* 购买内容
|
||||||
*
|
*
|
||||||
* @param userId 用户ID
|
|
||||||
* @param contentId 内容ID
|
* @param contentId 内容ID
|
||||||
* @param payType 支付方式:1.余额支付 2.积分支付
|
* @param payType 支付方式:1.余额支付 2.积分支付
|
||||||
* @return 购买结果
|
* @return 购买结果
|
||||||
|
|
@ -82,16 +82,15 @@ public class ContentPurchaseController {
|
||||||
@PostMapping("/purchase")
|
@PostMapping("/purchase")
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
public CommonResult<ContentPurchase> purchaseContent(
|
public CommonResult<ContentPurchase> purchaseContent(
|
||||||
@Parameter(description = "用户ID") @RequestParam("userId") Long userId,
|
|
||||||
@Parameter(description = "内容ID") @RequestParam("contentId") Long contentId,
|
@Parameter(description = "内容ID") @RequestParam("contentId") Long contentId,
|
||||||
@Parameter(description = "支付方式:1.余额支付 2.积分支付") @RequestParam("payType") Integer payType) {
|
@Parameter(description = "支付方式:1.余额支付 2.积分支付") @RequestParam("payType") Integer payType) {
|
||||||
|
Long userId = Long.parseLong(StpUtil.getLoginId().toString());
|
||||||
return CommonResult.success(this.contentPurchaseService.purchaseContent(userId, contentId, payType));
|
return CommonResult.success(this.contentPurchaseService.purchaseContent(userId, contentId, payType));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查用户是否有权限访问内容
|
* 检查用户是否有权限访问内容
|
||||||
*
|
*
|
||||||
* @param userId 用户ID
|
|
||||||
* @param contentId 内容ID
|
* @param contentId 内容ID
|
||||||
* @return 是否有权限
|
* @return 是否有权限
|
||||||
*/
|
*/
|
||||||
|
|
@ -99,8 +98,8 @@ public class ContentPurchaseController {
|
||||||
@PostMapping("/checkPermission")
|
@PostMapping("/checkPermission")
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
public CommonResult<Boolean> checkAccessPermission(
|
public CommonResult<Boolean> checkAccessPermission(
|
||||||
@Parameter(description = "用户ID") @RequestParam("userId") Long userId,
|
|
||||||
@Parameter(description = "内容ID") @RequestParam("contentId") Long contentId) {
|
@Parameter(description = "内容ID") @RequestParam("contentId") Long contentId) {
|
||||||
|
Long userId = Long.parseLong(StpUtil.getLoginId().toString());
|
||||||
boolean hasPermission = this.contentPurchaseService.checkAccessPermission(userId, contentId);
|
boolean hasPermission = this.contentPurchaseService.checkAccessPermission(userId, contentId);
|
||||||
return CommonResult.success(hasPermission);
|
return CommonResult.success(hasPermission);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package com.kexue.skills.controller;
|
package com.kexue.skills.controller;
|
||||||
|
|
||||||
|
import com.kexue.skills.annotation.Log;
|
||||||
import com.kexue.skills.annotation.PreventDuplicateSubmission;
|
import com.kexue.skills.annotation.PreventDuplicateSubmission;
|
||||||
|
import com.kexue.skills.common.CacheManager;
|
||||||
import com.kexue.skills.common.CommonResult;
|
import com.kexue.skills.common.CommonResult;
|
||||||
import com.kexue.skills.entity.request.LoginDto;
|
import com.kexue.skills.entity.request.LoginDto;
|
||||||
import com.kexue.skills.entity.request.LoginUserDto;
|
import com.kexue.skills.entity.request.LoginUserDto;
|
||||||
|
|
@ -13,12 +15,15 @@ import org.springframework.web.bind.annotation.*;
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import org.redisson.api.RedissonClient;
|
import org.redisson.api.RedissonClient;
|
||||||
|
|
||||||
|
import static cn.dev33.satoken.SaManager.log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (SysUser)表控制层
|
* (SysUser)表控制层
|
||||||
*
|
*
|
||||||
* @author 王志维
|
* @author 王志维
|
||||||
* @since 2024-04-13 01:25:22
|
* @since 2024-04-13 01:25:22
|
||||||
*/
|
*/
|
||||||
|
@Log(module = "登录认证")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("api/login")
|
@RequestMapping("api/login")
|
||||||
@CrossOrigin(origins = "*")
|
@CrossOrigin(origins = "*")
|
||||||
|
|
@ -58,20 +63,27 @@ public class LoginController {
|
||||||
@Operation(summary = "用户登出", description = "用户登出")
|
@Operation(summary = "用户登出", description = "用户登出")
|
||||||
public CommonResult<String> logout() {
|
public CommonResult<String> logout() {
|
||||||
try {
|
try {
|
||||||
// 获取当前用户的token
|
// 获取当前用户的 token
|
||||||
String token = cn.dev33.satoken.stp.StpUtil.getTokenValue();
|
String token = cn.dev33.satoken.stp.StpUtil.getTokenValue();
|
||||||
|
|
||||||
// 使用Sa-Token登出
|
// 从 Redis 中删除用户信息
|
||||||
cn.dev33.satoken.stp.StpUtil.logout();
|
|
||||||
|
|
||||||
// 从Redis中删除用户信息
|
|
||||||
if (token != null && !token.isEmpty()) {
|
if (token != null && !token.isEmpty()) {
|
||||||
redissonClient.getBucket("loginUser:" + token).delete();
|
redissonClient.getBucket("loginUser:" + token).delete();
|
||||||
|
// 从缓存中移除 token 映射
|
||||||
|
String username = CacheManager.getUsernameFromToken(token);
|
||||||
|
if (username != null) {
|
||||||
|
CacheManager.removeTokenFromCache(username);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 使用 Sa-Token 登出(这会清除 Sa-Token 内部的会话信息)
|
||||||
|
cn.dev33.satoken.stp.StpUtil.logout();
|
||||||
|
|
||||||
|
log.info("用户登出成功,token 已清理");
|
||||||
return CommonResult.success("登出成功");
|
return CommonResult.success("登出成功");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// 如果获取token失败,仍然执行登出操作
|
log.error("登出异常:{}", e.getMessage());
|
||||||
|
// 如果获取 token 失败,仍然执行登出操作
|
||||||
cn.dev33.satoken.stp.StpUtil.logout();
|
cn.dev33.satoken.stp.StpUtil.logout();
|
||||||
return CommonResult.success("登出成功");
|
return CommonResult.success("登出成功");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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<List<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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
package com.kexue.skills.controller;
|
||||||
|
|
||||||
|
import com.github.pagehelper.PageInfo;
|
||||||
|
import com.kexue.skills.annotation.RequireAuth;
|
||||||
|
import com.kexue.skills.common.CommonResult;
|
||||||
|
import com.kexue.skills.entity.PackageConfig;
|
||||||
|
import com.kexue.skills.entity.base.IdDto;
|
||||||
|
import com.kexue.skills.entity.dto.PackageConfigDto;
|
||||||
|
import com.kexue.skills.service.PackageConfigService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (PackageConfig)表控制层
|
||||||
|
*
|
||||||
|
* @author 系统生成
|
||||||
|
* @since 2026-04-11
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("api/packageConfig")
|
||||||
|
@Tag(name = "套餐配置管理 Api")
|
||||||
|
@CrossOrigin(origins = "*")
|
||||||
|
public class PackageConfigController {
|
||||||
|
/**
|
||||||
|
* 服务对象
|
||||||
|
*/
|
||||||
|
@Resource
|
||||||
|
private PackageConfigService packageConfigService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询
|
||||||
|
*
|
||||||
|
* @param queryDto 筛选条件
|
||||||
|
* @return 查询结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/getPageList")
|
||||||
|
@Operation(summary = "查询分页列表", description = "查询分页列表")
|
||||||
|
public CommonResult<PageInfo<PackageConfig>> getPageList(@RequestBody PackageConfigDto queryDto) {
|
||||||
|
return CommonResult.success(packageConfigService.getPageList(queryDto));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询列表
|
||||||
|
*
|
||||||
|
* @param queryDto 筛选条件
|
||||||
|
* @return 查询结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/getList")
|
||||||
|
@Operation(summary = "查询列表", description = "查询列表")
|
||||||
|
public CommonResult<PageInfo<PackageConfig>> getList(@RequestBody PackageConfigDto queryDto) {
|
||||||
|
return CommonResult.success(new PageInfo<>(packageConfigService.getList(queryDto)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过主键查询单条数据
|
||||||
|
*
|
||||||
|
* @param id 主键
|
||||||
|
* @return 单条数据
|
||||||
|
*/
|
||||||
|
@PostMapping("queryById/{id}")
|
||||||
|
@Operation(summary = "通过ID查询套餐", description = "通过ID查询套餐")
|
||||||
|
public CommonResult<PackageConfig> queryById(@PathVariable("id") Long id) {
|
||||||
|
return CommonResult.success(packageConfigService.queryById(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增数据
|
||||||
|
*
|
||||||
|
* @param packageConfig 实体
|
||||||
|
* @return 新增结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/insert")
|
||||||
|
@Operation(summary = "新增套餐", description = "新增套餐")
|
||||||
|
@RequireAuth
|
||||||
|
public CommonResult<PackageConfig> insert(@RequestBody PackageConfig packageConfig) {
|
||||||
|
return CommonResult.success(packageConfigService.insert(packageConfig));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑数据
|
||||||
|
*
|
||||||
|
* @param packageConfig 实体
|
||||||
|
* @return 编辑结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/update")
|
||||||
|
@Operation(summary = "更新套餐", description = "更新套餐")
|
||||||
|
@RequireAuth
|
||||||
|
public CommonResult<PackageConfig> update(@RequestBody PackageConfig packageConfig) {
|
||||||
|
return CommonResult.success(packageConfigService.update(packageConfig));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过主键逻辑删除
|
||||||
|
*
|
||||||
|
* @param idDto 主键
|
||||||
|
* @return 删除数据
|
||||||
|
*/
|
||||||
|
@PostMapping("/logicDeleteById")
|
||||||
|
@Operation(summary = "逻辑删除套餐", description = "逻辑删除套餐")
|
||||||
|
@RequireAuth
|
||||||
|
public CommonResult<Boolean> logicDeleteById(@RequestBody IdDto idDto) {
|
||||||
|
return CommonResult.success(packageConfigService.logicDeleteById(idDto.getId(), "admin") > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除数据
|
||||||
|
*
|
||||||
|
* @param id 主键
|
||||||
|
* @return 删除数据
|
||||||
|
*/
|
||||||
|
@PostMapping("deleteById/{id}")
|
||||||
|
@Operation(summary = "物理删除套餐", description = "物理删除套餐")
|
||||||
|
@RequireAuth
|
||||||
|
public CommonResult<Boolean> deleteById(@PathVariable("id") Long id) {
|
||||||
|
return CommonResult.success(packageConfigService.deleteById(id) > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,192 @@
|
||||||
|
package com.kexue.skills.controller;
|
||||||
|
|
||||||
|
import com.kexue.skills.common.CommonResult;
|
||||||
|
import com.kexue.skills.entity.PaymentOrder;
|
||||||
|
import com.kexue.skills.service.PayService;
|
||||||
|
import com.kexue.skills.service.PaymentOrderService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import com.kexue.skills.entity.dto.OrderStatusDto;
|
||||||
|
import com.kexue.skills.entity.dto.OrderStatusQueryDto;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付控制器
|
||||||
|
*/
|
||||||
|
@Tag(name = "支付管理 Api")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/pay")
|
||||||
|
public class PayController {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(PayController.class);
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private PayService payService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private PaymentOrderService paymentOrderService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建微信支付订单
|
||||||
|
* @param order 支付订单信息
|
||||||
|
* @param request HTTP 请求(用于获取用户 IP)
|
||||||
|
* @return 微信支付参数
|
||||||
|
*/
|
||||||
|
@Operation(summary = "创建微信支付订单", description = "创建微信支付订单")
|
||||||
|
@PostMapping("/wx/create")
|
||||||
|
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, 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("系统繁忙,请稍后重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户真实 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理微信支付回调
|
||||||
|
* @param request HTTP请求
|
||||||
|
* @return 回调响应
|
||||||
|
*/
|
||||||
|
@Operation(summary = "处理微信支付回调", description = "处理微信支付回调")
|
||||||
|
@PostMapping("/wx/notify")
|
||||||
|
public String handleWechatPayNotify(HttpServletRequest request) {
|
||||||
|
return payService.handleWechatPayNotify(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建支付宝支付订单
|
||||||
|
* @param order 支付订单信息
|
||||||
|
* @return 支付宝支付表单
|
||||||
|
*/
|
||||||
|
@Operation(summary = "创建支付宝支付订单", description = "创建支付宝支付订单")
|
||||||
|
@PostMapping("/alipay/create")
|
||||||
|
public CommonResult<String> createAlipay(@RequestBody PaymentOrder order) {
|
||||||
|
try {
|
||||||
|
// 设置支付类型为支付宝(2)
|
||||||
|
order.setPayType(2);
|
||||||
|
// 创建支付订单
|
||||||
|
PaymentOrder createdOrder = paymentOrderService.createPaymentOrder(order);
|
||||||
|
// 生成支付宝支付表单
|
||||||
|
String form = payService.createAlipay(createdOrder);
|
||||||
|
|
||||||
|
// 更新订单信息
|
||||||
|
createdOrder.setQrCode( form);
|
||||||
|
paymentOrderService.update(createdOrder);
|
||||||
|
|
||||||
|
return CommonResult.success(form);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("创建支付宝支付订单失败", e);
|
||||||
|
return CommonResult.failed("创建支付宝支付订单失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理支付宝支付回调
|
||||||
|
* @param request HTTP请求
|
||||||
|
* @return 回调响应
|
||||||
|
*/
|
||||||
|
@Operation(summary = "处理支付宝支付回调", description = "处理支付宝支付回调")
|
||||||
|
@PostMapping("/alipay/trade/notify")
|
||||||
|
public String handleAlipayNotify(HttpServletRequest request) {
|
||||||
|
return payService.handleAlipayNotify(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理支付宝支付同步回调
|
||||||
|
* @param request HTTP请求
|
||||||
|
* @return 同步回调响应
|
||||||
|
*/
|
||||||
|
@Operation(summary = "处理支付宝支付同步回调", description = "处理支付宝支付同步回调")
|
||||||
|
@GetMapping("/alipay/trade/return")
|
||||||
|
public CommonResult<Map<String, Object>> handleAlipayReturn(HttpServletRequest request) {
|
||||||
|
Map<String, Object> result = payService.handleAlipayReturn(request);
|
||||||
|
if (result.get("success").equals(true)) {
|
||||||
|
return CommonResult.success(result);
|
||||||
|
} else {
|
||||||
|
return CommonResult.failed(result.get("message").toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询订单状态
|
||||||
|
* @param queryDto 查询参数,包含 orderId 或 orderNo
|
||||||
|
* @return 订单状态信息
|
||||||
|
*/
|
||||||
|
@Operation(summary = "查询订单状态", description = "根据订单 id 或 orderNo 查询订单状态")
|
||||||
|
@PostMapping("/queryOrderStatus")
|
||||||
|
public CommonResult<OrderStatusDto> queryOrderStatus(@RequestBody OrderStatusQueryDto queryDto) {
|
||||||
|
try {
|
||||||
|
// 检查参数是否有效
|
||||||
|
if (Objects.isNull(queryDto.getOrderId()) && Objects.isNull(queryDto.getOrderNo())) {
|
||||||
|
return CommonResult.failed("请提供 orderId 或 orderNo 参数");
|
||||||
|
}
|
||||||
|
|
||||||
|
PaymentOrder order = null;
|
||||||
|
|
||||||
|
// 根据订单 id 查询
|
||||||
|
if (queryDto.getOrderId() != null) {
|
||||||
|
order = paymentOrderService.queryById(queryDto.getOrderId());
|
||||||
|
}
|
||||||
|
// 根据订单号查询
|
||||||
|
else if (queryDto.getOrderNo() != null && !queryDto.getOrderNo().trim().isEmpty()) {
|
||||||
|
order = paymentOrderService.queryByOrderNo(queryDto.getOrderNo());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (order == null) {
|
||||||
|
return CommonResult.failed("订单不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建响应数据
|
||||||
|
OrderStatusDto result = OrderStatusDto.fromPaymentOrder(order);
|
||||||
|
|
||||||
|
return CommonResult.success(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("查询订单状态失败", e);
|
||||||
|
return CommonResult.failed("系统繁忙,请稍后重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package com.kexue.skills.controller;
|
||||||
|
|
||||||
|
import com.kexue.skills.common.CommonResult;
|
||||||
|
import com.kexue.skills.entity.dto.SessionDto;
|
||||||
|
import com.kexue.skills.service.SysUserService;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session管理控制器
|
||||||
|
*
|
||||||
|
* @author 王志维
|
||||||
|
* @since 2026-03-12
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("api/session")
|
||||||
|
@Tag(name = "Session管理 Api")
|
||||||
|
@CrossOrigin(origins = "*")
|
||||||
|
public class SessionController {
|
||||||
|
/**
|
||||||
|
* 服务对象
|
||||||
|
*/
|
||||||
|
@Resource
|
||||||
|
private SysUserService sysUserService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建或获取用户会话
|
||||||
|
*
|
||||||
|
* @param userId 包含用户ID的DTO对象
|
||||||
|
* @return 会话信息,包含sessionId和isNew字段
|
||||||
|
*/
|
||||||
|
@GetMapping("/createSession")
|
||||||
|
@Operation(summary = "创建或获取会话", description = "根据用户ID创建或获取会话信息")
|
||||||
|
public CommonResult<SessionDto> createSession(@RequestParam Long userId) {
|
||||||
|
SessionDto sessionDto = sysUserService.createSession(userId);
|
||||||
|
return CommonResult.success(sessionDto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,18 @@
|
||||||
package com.kexue.skills.controller;
|
package com.kexue.skills.controller;
|
||||||
|
|
||||||
import com.kexue.skills.common.CommonResult;
|
import com.kexue.skills.common.CommonResult;
|
||||||
|
import com.kexue.skills.entity.CmsContent;
|
||||||
|
import com.kexue.skills.entity.dto.YamlContentDto;
|
||||||
|
import com.kexue.skills.entity.request.GenIntroduceRequest;
|
||||||
|
import com.kexue.skills.entity.request.SkillGenRequest;
|
||||||
|
import com.kexue.skills.entity.request.SkillPreGenRequest;
|
||||||
|
import com.kexue.skills.entity.request.SkillUploadDto;
|
||||||
import com.kexue.skills.entity.response.SkillResponse;
|
import com.kexue.skills.entity.response.SkillResponse;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -28,10 +36,16 @@ public class SkillGenController {
|
||||||
* @param request 生成请求
|
* @param request 生成请求
|
||||||
* @return 生成结果
|
* @return 生成结果
|
||||||
*/
|
*/
|
||||||
|
@PostMapping("/preGenerate")
|
||||||
|
@Operation(summary = "预生成技能", description = "生成技能")
|
||||||
|
public CommonResult<SkillResponse> preGenerate(@RequestBody SkillPreGenRequest request) {
|
||||||
|
return CommonResult.success(skillGenService.preGenerateV2(request));
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/generate")
|
@PostMapping("/generate")
|
||||||
@Operation(summary = "生成技能", description = "生成技能")
|
@Operation(summary = "生成技能", description = "生成技能")
|
||||||
public CommonResult<SkillResponse> generate(@RequestBody com.kexue.skills.entity.request.SkillGenRequest request) {
|
public CommonResult<CmsContent> generate(@RequestBody SkillGenRequest request) {
|
||||||
return CommonResult.success(skillGenService.generateSkill(request));
|
return CommonResult.success(skillGenService.generate(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -45,4 +59,93 @@ public class SkillGenController {
|
||||||
public CommonResult<String> analyze(@RequestBody com.kexue.skills.entity.request.SkillAnalyzeRequest request) {
|
public CommonResult<String> analyze(@RequestBody com.kexue.skills.entity.request.SkillAnalyzeRequest request) {
|
||||||
return CommonResult.success(skillGenService.analyzeSkill(request));
|
return CommonResult.success(skillGenService.analyzeSkill(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成技能介绍
|
||||||
|
*
|
||||||
|
* @param request 技能内容
|
||||||
|
* @return 技能介绍
|
||||||
|
*/
|
||||||
|
@PostMapping("/genIntroduce")
|
||||||
|
@Operation(summary = "生成技能介绍", description = "生成技能介绍")
|
||||||
|
public CommonResult<String> genIntroduce(@RequestBody GenIntroduceRequest request) {
|
||||||
|
return CommonResult.success(skillGenService.genIntroduce(request.getContent()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据技能描述生成技能介绍
|
||||||
|
*
|
||||||
|
* @param request 技能描述
|
||||||
|
* @return 技能介绍
|
||||||
|
*/
|
||||||
|
@PostMapping("/genIntroduceByDescription")
|
||||||
|
@Operation(summary = "根据技能描述生成技能介绍", description = "根据技能描述生成技能介绍")
|
||||||
|
public CommonResult<String> genIntroduceByDescription(@RequestBody GenIntroduceRequest request) {
|
||||||
|
return CommonResult.success(skillGenService.genIntroduceByDescription(request.getContent()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传技能压缩包
|
||||||
|
*
|
||||||
|
* @param skillUploadDto 技能压缩包URL
|
||||||
|
* @return 生成的技能内容
|
||||||
|
*/
|
||||||
|
@PostMapping("/uploadSkill")
|
||||||
|
@Operation(summary = "上传技能压缩包", description = "上传技能压缩包并生成技能")
|
||||||
|
public CommonResult<CmsContent> uploadSkill(@RequestBody SkillUploadDto skillUploadDto) {
|
||||||
|
return CommonResult.success(skillGenService.uploadSkill(skillUploadDto.getUrl()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传本地技能压缩包V2
|
||||||
|
*
|
||||||
|
* @param file 技能压缩包文件
|
||||||
|
* @return 生成的技能内容
|
||||||
|
*/
|
||||||
|
@PostMapping("/uploadSkillV2")
|
||||||
|
@Operation(summary = "上传本地技能压缩包V2", description = "上传本地zip或rar文件并生成技能")
|
||||||
|
public CommonResult<CmsContent> uploadSkillV2(
|
||||||
|
@RequestParam("file") MultipartFile file) {
|
||||||
|
try {
|
||||||
|
byte[] fileBytes = file.getBytes();
|
||||||
|
String fileName = file.getOriginalFilename();
|
||||||
|
CmsContent cmsContent = skillGenService.uploadSkillV2(fileBytes, fileName);
|
||||||
|
return CommonResult.success(cmsContent);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return CommonResult.failed("上传失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 上传本地技能压缩包V3 直接传入yamlContent
|
||||||
|
*
|
||||||
|
* @param yamlContentDto 技能压缩包文件
|
||||||
|
* @return 生成的技能内容
|
||||||
|
*/
|
||||||
|
@PostMapping("/uploadSkillV3")
|
||||||
|
@Operation(summary = "上传本地技能压缩包V3,直接传入yamlContent", description = "直接传入yamlContent")
|
||||||
|
public CommonResult<CmsContent> uploadSkillV3(@RequestBody YamlContentDto yamlContentDto) {
|
||||||
|
CmsContent cmsContent = skillGenService.uploadSkillV3(yamlContentDto.getYamlContent());
|
||||||
|
return CommonResult.success(cmsContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传本地技能压缩包V2
|
||||||
|
*
|
||||||
|
* @param file 技能压缩包文件
|
||||||
|
* @return 生成的技能内容
|
||||||
|
*/
|
||||||
|
@PostMapping("/uploadSkillV4")
|
||||||
|
@Operation(summary = "上传本地技能压缩包V2", description = "上传本地zip或rar文件并生成技能")
|
||||||
|
public CommonResult<CmsContent> uploadSkillV4(
|
||||||
|
@RequestParam("file") MultipartFile file) {
|
||||||
|
try {
|
||||||
|
byte[] fileBytes = file.getBytes();
|
||||||
|
String fileName = file.getOriginalFilename();
|
||||||
|
CmsContent cmsContent = skillGenService.uploadSkillV4(fileBytes, fileName);
|
||||||
|
return CommonResult.success(cmsContent);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return CommonResult.failed("上传失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,7 @@ package com.kexue.skills.controller;
|
||||||
import com.kexue.skills.annotation.RequireAuth;
|
import com.kexue.skills.annotation.RequireAuth;
|
||||||
import com.kexue.skills.entity.SysUser;
|
import com.kexue.skills.entity.SysUser;
|
||||||
import com.kexue.skills.entity.dto.SysUserDto;
|
import com.kexue.skills.entity.dto.SysUserDto;
|
||||||
import com.kexue.skills.entity.request.ResetPasswordDto;
|
import com.kexue.skills.entity.request.*;
|
||||||
import com.kexue.skills.entity.request.ResetPwdDto;
|
|
||||||
import com.kexue.skills.entity.request.AdminResetPasswordDto;
|
|
||||||
import com.kexue.skills.exception.BizException;
|
import com.kexue.skills.exception.BizException;
|
||||||
import com.kexue.skills.service.SysUserService;
|
import com.kexue.skills.service.SysUserService;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
@ -17,8 +15,12 @@ import com.kexue.skills.common.CacheManager;
|
||||||
import com.github.pagehelper.PageInfo;
|
import com.github.pagehelper.PageInfo;
|
||||||
import com.kexue.skills.common.CommonResult;
|
import com.kexue.skills.common.CommonResult;
|
||||||
import com.kexue.skills.entity.base.IdDto;
|
import com.kexue.skills.entity.base.IdDto;
|
||||||
import com.kexue.skills.entity.request.LoginUserDto;
|
|
||||||
import org.redisson.api.RedissonClient;
|
import org.redisson.api.RedissonClient;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (SysUser)表控制层
|
* (SysUser)表控制层
|
||||||
|
|
@ -86,7 +88,7 @@ public class SysUserController {
|
||||||
*/
|
*/
|
||||||
@PostMapping("/update")
|
@PostMapping("/update")
|
||||||
@Operation(summary = "更新用户", description = "更新用户")
|
@Operation(summary = "更新用户", description = "更新用户")
|
||||||
public CommonResult<SysUser> update(@RequestBody SysUser SysUser) {
|
public CommonResult<SysUser> update(@RequestBody SysUserUpdateDto SysUser) {
|
||||||
return CommonResult.success(sysUserService.update(SysUser));
|
return CommonResult.success(sysUserService.update(SysUser));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,25 +113,8 @@ public class SysUserController {
|
||||||
@PostMapping("/resetPassword")
|
@PostMapping("/resetPassword")
|
||||||
@Operation(summary = "管理员帮助用户重置密码", description = "管理员帮助用户重置密码")
|
@Operation(summary = "管理员帮助用户重置密码", description = "管理员帮助用户重置密码")
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
public CommonResult<Boolean> resetPasswordByAdmin(@RequestBody ResetPasswordDto resetPasswordDto, HttpServletRequest request) {
|
public CommonResult<Boolean> resetPassword(@RequestBody ResetPwdDto resetPasswordDto) {
|
||||||
// 从请求头中获取token
|
boolean result = sysUserService.resetPassword(resetPasswordDto);
|
||||||
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);
|
|
||||||
return CommonResult.success(result);
|
return CommonResult.success(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,6 +200,13 @@ public class SysUserController {
|
||||||
throw new BizException("请先登录认证后操作");
|
throw new BizException("请先登录认证后操作");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 使用Sa-Token检查token是否有效
|
||||||
|
try {
|
||||||
|
cn.dev33.satoken.stp.StpUtil.checkLogin();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new BizException("无效的token,请重新登录");
|
||||||
|
}
|
||||||
|
|
||||||
// 从Redis缓存中获取LoginUser对象
|
// 从Redis缓存中获取LoginUser对象
|
||||||
String loginUserJson = (String)redissonClient.getBucket("loginUser:" + token).get();
|
String loginUserJson = (String)redissonClient.getBucket("loginUser:" + token).get();
|
||||||
if (loginUserJson == null || loginUserJson.isEmpty()) {
|
if (loginUserJson == null || loginUserJson.isEmpty()) {
|
||||||
|
|
@ -235,4 +227,44 @@ public class SysUserController {
|
||||||
|
|
||||||
return CommonResult.success(loginUserDto);
|
return CommonResult.success(loginUserDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传用户头像
|
||||||
|
*
|
||||||
|
* @param file 头像文件
|
||||||
|
* @param request HTTP请求
|
||||||
|
* @return 上传结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/uploadAvatar")
|
||||||
|
@Operation(summary = "上传用户头像", description = "上传用户头像并更新用户信息")
|
||||||
|
@RequireAuth
|
||||||
|
public CommonResult<String> uploadAvatar(@RequestParam("file") MultipartFile file, HttpServletRequest request) {
|
||||||
|
// 从请求头中获取token
|
||||||
|
String token = request.getHeader("Authorization");
|
||||||
|
if (token == null || token.isEmpty()) {
|
||||||
|
throw new BizException("请先登录认证后操作");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从Redis中获取当前登录用户信息
|
||||||
|
String loginUserJson = (String)redissonClient.getBucket("loginUser:" + token).get();
|
||||||
|
if (loginUserJson == null || loginUserJson.isEmpty()) {
|
||||||
|
throw new BizException("无效的token,请重新登录");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析JSON字符串为LoginUser对象
|
||||||
|
com.kexue.skills.entity.request.LoginUser loginUser = cn.hutool.json.JSONUtil.toBean(loginUserJson, com.kexue.skills.entity.request.LoginUser.class);
|
||||||
|
if (loginUser == null || loginUser.getUserInfo() == null) {
|
||||||
|
throw new BizException("无效的token,请重新登录");
|
||||||
|
}
|
||||||
|
|
||||||
|
SysUser user = loginUser.getUserInfo();
|
||||||
|
if (user == null) {
|
||||||
|
throw new BizException("用户不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用服务层方法上传头像
|
||||||
|
String fileName = sysUserService.uploadAvatar(file, user.getUserId(), token);
|
||||||
|
|
||||||
|
return CommonResult.success(fileName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 ="用户名")
|
@Schema(description ="用户名")
|
||||||
private String userName;
|
private String userName;
|
||||||
|
|
||||||
@Schema(description ="账户余额")
|
@Schema(description ="账户总余额")
|
||||||
private BigDecimal balance;
|
private BigDecimal balance;
|
||||||
|
|
||||||
|
@Schema(description ="可提现余额")
|
||||||
|
private BigDecimal withdrawableBalance;
|
||||||
|
|
||||||
|
@Schema(description ="不可提现余额")
|
||||||
|
private BigDecimal nonWithdrawableBalance;
|
||||||
|
|
||||||
@Schema(description ="冻结金额")
|
@Schema(description ="冻结金额")
|
||||||
private BigDecimal frozenAmount;
|
private BigDecimal frozenAmount;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (AccountFrozen)实体类
|
||||||
|
* 账户冻结单
|
||||||
|
*
|
||||||
|
* @author 系统生成
|
||||||
|
* @since 2026-04-11
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class AccountFrozen extends BaseEntity implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description ="冻结单ID,字符型")
|
||||||
|
private String frozenId;
|
||||||
|
|
||||||
|
@Schema(description ="流水ID")
|
||||||
|
private String accountTransactionId;
|
||||||
|
|
||||||
|
@Schema(description ="用户ID,关联冻结单")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
@Schema(description ="调用ID,关联冻结单")
|
||||||
|
private String callId;
|
||||||
|
|
||||||
|
@Schema(description ="会话ID")
|
||||||
|
private String sessionId;
|
||||||
|
|
||||||
|
@Schema(description ="模型名称")
|
||||||
|
private String modelName;
|
||||||
|
|
||||||
|
@Schema(description ="对应回答的问题或需求")
|
||||||
|
private String question;
|
||||||
|
|
||||||
|
@Schema(description ="冻结金额/张数/次数/分钟")
|
||||||
|
private BigDecimal frozenAmount;
|
||||||
|
|
||||||
|
@Schema(description ="冻结类型:1.token 2.RMB(元) 99其他")
|
||||||
|
private Integer frozenType;
|
||||||
|
|
||||||
|
@Schema(description ="最终扣减,0=释放")
|
||||||
|
private BigDecimal finalAmount;
|
||||||
|
|
||||||
|
@Schema(description ="输入tokens")
|
||||||
|
private Long usageInputTokens;
|
||||||
|
|
||||||
|
@Schema(description ="输出tokens")
|
||||||
|
private Long usageOutputTokens;
|
||||||
|
|
||||||
|
@Schema(description ="总tokens")
|
||||||
|
private Long usageTotalTokens;
|
||||||
|
|
||||||
|
@Schema(description ="终结原因:success/cancel/timeout/error/system_recovery")
|
||||||
|
private String finalizeReason;
|
||||||
|
|
||||||
|
@Schema(description ="状态:RESERVED 已预留,FINALIZED 已终结")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||||
|
@Schema(description ="过期时间")
|
||||||
|
private Date expireAt;
|
||||||
|
|
||||||
|
@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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -29,7 +29,7 @@ public class AccountTransaction extends BaseEntity implements Serializable {
|
||||||
@Schema(description ="用户名")
|
@Schema(description ="用户名")
|
||||||
private String userName;
|
private String userName;
|
||||||
|
|
||||||
@Schema(description ="交易类型:1.充值 2.提现 3.购买内容 4.退款 5.其他")
|
@Schema(description ="交易类型:1.充值 2.提现 3.购买内容 4.退款 5.签到奖励 6.赠送 7.其他")
|
||||||
private Integer transactionType;
|
private Integer transactionType;
|
||||||
|
|
||||||
@Schema(description ="交易金额")
|
@Schema(description ="交易金额")
|
||||||
|
|
@ -56,9 +56,33 @@ public class AccountTransaction extends BaseEntity implements Serializable {
|
||||||
@Schema(description ="业务类型")
|
@Schema(description ="业务类型")
|
||||||
private String businessType;
|
private String businessType;
|
||||||
|
|
||||||
|
@Schema(description ="调用ID,关联冻结单")
|
||||||
|
private String callId;
|
||||||
|
|
||||||
@Schema(description ="交易备注")
|
@Schema(description ="交易备注")
|
||||||
private String remark;
|
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")
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||||
@Schema(description ="创建时间")
|
@Schema(description ="创建时间")
|
||||||
private Date createTime;
|
private Date createTime;
|
||||||
|
|
|
||||||
|
|
@ -9,33 +9,23 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (PointsAccount)实体类
|
* (CmsCategoryTag)实体类
|
||||||
* 积分账户表,记录用户的积分信息
|
|
||||||
*
|
*
|
||||||
* @author 王志维
|
* @author 王志维
|
||||||
* @since 2025-02-21 23:01:48
|
* @since 2025-02-21 23:01:48
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class PointsAccount extends BaseEntity implements Serializable {
|
public class CmsCategoryTag extends BaseEntity implements Serializable {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
@Schema(description ="主键ID")
|
@Schema(description ="主键ID")
|
||||||
private Long accountId;
|
private Long id;
|
||||||
|
|
||||||
@Schema(description ="用户ID")
|
@Schema(description ="分类ID")
|
||||||
private Long userId;
|
private Long categoryId;
|
||||||
|
|
||||||
@Schema(description ="用户名")
|
@Schema(description ="标签ID")
|
||||||
private String userName;
|
private Long tagId;
|
||||||
|
|
||||||
@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")
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||||
@Schema(description ="创建时间")
|
@Schema(description ="创建时间")
|
||||||
|
|
@ -45,13 +35,13 @@ public class PointsAccount extends BaseEntity implements Serializable {
|
||||||
@Schema(description ="更新时间")
|
@Schema(description ="更新时间")
|
||||||
private Date updateTime;
|
private Date updateTime;
|
||||||
|
|
||||||
@Schema(description ="是否删除 :0 未删除,1已删除")
|
|
||||||
private Integer deleteFlag;
|
|
||||||
|
|
||||||
@Schema(description ="创建人")
|
@Schema(description ="创建人")
|
||||||
private String createBy;
|
private String createBy;
|
||||||
|
|
||||||
@Schema(description ="更新人")
|
@Schema(description ="更新人")
|
||||||
private String updateBy;
|
private String updateBy;
|
||||||
|
|
||||||
|
@Schema(description ="是否删除 :0 未删除,1已删除")
|
||||||
|
private Integer deleteFlag;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -27,12 +27,12 @@ public class CmsContent extends BaseEntity implements Serializable {
|
||||||
@Schema(description ="标题")
|
@Schema(description ="标题")
|
||||||
private String title;
|
private String title;
|
||||||
|
|
||||||
|
@Schema(description ="英文标题")
|
||||||
|
private String titleEn;
|
||||||
|
|
||||||
@Schema(description ="是否是官方:0否,1是")
|
@Schema(description ="是否是官方:0否,1是")
|
||||||
private Boolean isOfficial;
|
private Boolean isOfficial;
|
||||||
|
|
||||||
@Schema(description ="分类ID列表,逗号分隔")
|
|
||||||
private String categoryIds;
|
|
||||||
|
|
||||||
@Schema(description ="图标")
|
@Schema(description ="图标")
|
||||||
private String icon;
|
private String icon;
|
||||||
|
|
||||||
|
|
@ -45,6 +45,21 @@ public class CmsContent extends BaseEntity implements Serializable {
|
||||||
@Schema(description ="内容摘要")
|
@Schema(description ="内容摘要")
|
||||||
private String summary;
|
private String summary;
|
||||||
|
|
||||||
|
@Schema(description ="详细描述")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Schema(description ="英文描述")
|
||||||
|
private String descriptionEn;
|
||||||
|
|
||||||
|
@Schema(description ="需求说明")
|
||||||
|
private String requirement;
|
||||||
|
|
||||||
|
@Schema(description ="介绍信息")
|
||||||
|
private String introduce;
|
||||||
|
|
||||||
|
@Schema(description ="英文介绍")
|
||||||
|
private String introduceEn;
|
||||||
|
|
||||||
@Schema(description ="分享数量")
|
@Schema(description ="分享数量")
|
||||||
private Integer shareCount;
|
private Integer shareCount;
|
||||||
|
|
||||||
|
|
@ -64,6 +79,9 @@ public class CmsContent extends BaseEntity implements Serializable {
|
||||||
@Schema(description ="内容详情")
|
@Schema(description ="内容详情")
|
||||||
private String content;
|
private String content;
|
||||||
|
|
||||||
|
@Schema(description ="英文内容")
|
||||||
|
private String contentEn;
|
||||||
|
|
||||||
@Schema(description ="封面图片")
|
@Schema(description ="封面图片")
|
||||||
private String coverImage;
|
private String coverImage;
|
||||||
|
|
||||||
|
|
@ -79,14 +97,14 @@ public class CmsContent extends BaseEntity implements Serializable {
|
||||||
@Schema(description ="审核人名称")
|
@Schema(description ="审核人名称")
|
||||||
private String reviewerName;
|
private String reviewerName;
|
||||||
|
|
||||||
@Schema(description ="审核状态(1草稿,2待审核,3审核通过,4审核拒绝)")
|
@Schema(description ="审核状态(1未发布,2待审核,3审核通过,4审核未通过)")
|
||||||
private Integer auditStatus;
|
private Integer auditStatus;
|
||||||
|
|
||||||
@Schema(description ="审核意见")
|
@Schema(description ="审核意见")
|
||||||
private String auditComment;
|
private String auditComment;
|
||||||
|
|
||||||
@Schema(description ="发布状态(1未发布,2已发布,3已下架)")
|
@Schema(description ="发布状态(1未发布,2已发布,3已下架)--> 公有还是私有:1私有,2公有")
|
||||||
private Integer publishStatus;
|
private Integer publishStatus;
|
||||||
|
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||||
@Schema(description ="发布时间")
|
@Schema(description ="发布时间")
|
||||||
|
|
@ -129,27 +147,10 @@ public class CmsContent extends BaseEntity implements Serializable {
|
||||||
@Schema(description ="副标题")
|
@Schema(description ="副标题")
|
||||||
private String subtitle;
|
private String subtitle;
|
||||||
|
|
||||||
@Schema(description ="父分类ID")
|
@Schema(description ="来源")
|
||||||
private Long parentCategoryId;
|
private String origin;
|
||||||
|
|
||||||
// 用于接收前端发送的分类ID数组
|
@Schema(description ="标签")
|
||||||
@JsonProperty("categoryIds")
|
private String tags;
|
||||||
public void setCategoryIdsFromArray(List<Long> categoryIdList) {
|
|
||||||
if (categoryIdList != null && !categoryIdList.isEmpty()) {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
for (int i = 0; i < categoryIdList.size(); i++) {
|
|
||||||
sb.append(categoryIdList.get(i));
|
|
||||||
if (i < categoryIdList.size() - 1) {
|
|
||||||
sb.append(",");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.categoryIds = sb.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 用于接收前端发送的分类ID字符串
|
|
||||||
public void setCategoryIds(String categoryIds) {
|
|
||||||
this.categoryIds = categoryIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,9 @@ public class CmsTag extends BaseEntity implements Serializable {
|
||||||
@Schema(description ="状态(1启用,2禁用)")
|
@Schema(description ="状态(1启用,2禁用)")
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
|
||||||
|
@Schema(description ="标签图标")
|
||||||
|
private String icon;
|
||||||
|
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||||
@Schema(description ="创建时间")
|
@Schema(description ="创建时间")
|
||||||
private Date createTime;
|
private Date createTime;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
package com.kexue.skills.entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志记录对象
|
||||||
|
* 用于在拦截器和持久层之间传递日志数据
|
||||||
|
*
|
||||||
|
* @author 王志维
|
||||||
|
* @since 2026-04-14
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class LogRecord implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模块名称
|
||||||
|
*/
|
||||||
|
private String module;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志描述
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求信息
|
||||||
|
*/
|
||||||
|
private LogRequest request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应信息
|
||||||
|
*/
|
||||||
|
private LogResponse response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行耗时(毫秒)
|
||||||
|
*/
|
||||||
|
private Long timeTaken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 时间戳
|
||||||
|
*/
|
||||||
|
private Instant timestamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志请求对象
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class LogRequest implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求方法(GET、POST等)
|
||||||
|
*/
|
||||||
|
private String method;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求URL
|
||||||
|
*/
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求头
|
||||||
|
*/
|
||||||
|
private Map<String, String> headers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求体
|
||||||
|
*/
|
||||||
|
private String body;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端IP
|
||||||
|
*/
|
||||||
|
private String ip;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IP归属地
|
||||||
|
*/
|
||||||
|
private String address;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 浏览器信息
|
||||||
|
*/
|
||||||
|
private String browser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作系统
|
||||||
|
*/
|
||||||
|
private String os;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志响应对象
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class LogResponse implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应头
|
||||||
|
*/
|
||||||
|
private Map<String, String> headers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应体
|
||||||
|
*/
|
||||||
|
private String body;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态码
|
||||||
|
*/
|
||||||
|
private Integer status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
@Schema(description ="计费区间下限(不包含)")
|
||||||
|
private Long minTokens;
|
||||||
|
|
||||||
|
@Schema(description ="计费区间上限(包含,-1代表无穷大)")
|
||||||
|
private Long maxTokens;
|
||||||
|
|
||||||
|
@Schema(description ="输出模式:standard=非思考模式, thinking=思考模式")
|
||||||
|
private String outputMode;
|
||||||
|
|
||||||
|
@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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
package com.kexue.skills.entity;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import com.kexue.skills.entity.base.BaseEntity;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (PackageConfig)实体类
|
||||||
|
* 套餐配置表
|
||||||
|
*
|
||||||
|
* @author 系统生成
|
||||||
|
* @since 2026-04-11
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class PackageConfig extends BaseEntity implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description ="套餐ID")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description ="套餐名称")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description ="价格")
|
||||||
|
private BigDecimal price;
|
||||||
|
|
||||||
|
@Schema(description ="基础额度")
|
||||||
|
private BigDecimal baseAmount;
|
||||||
|
|
||||||
|
@Schema(description ="赠送额度")
|
||||||
|
private BigDecimal giftAmount;
|
||||||
|
|
||||||
|
@Schema(description ="创建时间")
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
@Schema(description ="更新时间")
|
||||||
|
private Date updateTime;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -44,16 +44,22 @@ public class PaymentOrder extends BaseEntity implements Serializable {
|
||||||
@Schema(description ="支付渠道订单号")
|
@Schema(description ="支付渠道订单号")
|
||||||
private String channelOrderNo;
|
private String channelOrderNo;
|
||||||
|
|
||||||
|
@Schema(description ="微信二维码URL")
|
||||||
|
private String codeUrl;
|
||||||
|
|
||||||
|
@Schema(description ="支付宝二维码HTML内容")
|
||||||
|
private String qrCode;
|
||||||
|
|
||||||
@Schema(description ="商品名称")
|
@Schema(description ="商品名称")
|
||||||
private String productName;
|
private String productName;
|
||||||
|
|
||||||
@Schema(description ="商品描述")
|
@Schema(description ="商品描述")
|
||||||
private String productDesc;
|
private String productDesc;
|
||||||
|
|
||||||
@Schema(description ="关联业务ID")
|
@Schema(description ="关联业务ID,比如packageId(套餐ID)")
|
||||||
private Long businessId;
|
private Long businessId;
|
||||||
|
|
||||||
@Schema(description ="业务类型")
|
@Schema(description ="业务类型:recharge,purchase_content")
|
||||||
private String businessType;
|
private String businessType;
|
||||||
|
|
||||||
@Schema(description ="支付回调地址")
|
@Schema(description ="支付回调地址")
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -22,29 +22,59 @@ public class SysLog extends BaseEntity implements Serializable {
|
||||||
@Schema(description ="主键ID")
|
@Schema(description ="主键ID")
|
||||||
private Long logId;
|
private Long logId;
|
||||||
|
|
||||||
@Schema(description ="用户ID")
|
@Schema(description ="链路ID")
|
||||||
private String userId;
|
private String traceId;
|
||||||
|
|
||||||
@Schema(description ="用户名称")
|
@Schema(description ="日志描述")
|
||||||
private String userName;
|
private String description;
|
||||||
|
|
||||||
@Schema(description ="日志类型")
|
@Schema(description ="所属模块")
|
||||||
private String logType;
|
private String module;
|
||||||
|
|
||||||
@Schema(description ="日志类容")
|
@Schema(description ="请求URL")
|
||||||
private String logContent;
|
private String requestUrl;
|
||||||
|
|
||||||
@Schema(description ="服务端IP")
|
@Schema(description ="请求方式")
|
||||||
private String serverIp;
|
private String requestMethod;
|
||||||
|
|
||||||
@Schema(description ="客户端IP")
|
@Schema(description ="请求头")
|
||||||
private String clientIp;
|
private String requestHeaders;
|
||||||
|
|
||||||
@Schema(description ="yyyyMMddHHmmss")
|
@Schema(description ="请求体")
|
||||||
private String logTime;
|
private String requestBody;
|
||||||
|
|
||||||
@Schema(description ="备注")
|
@Schema(description ="状态码")
|
||||||
private String note;
|
private Integer statusCode;
|
||||||
|
|
||||||
|
@Schema(description ="响应头")
|
||||||
|
private String responseHeaders;
|
||||||
|
|
||||||
|
@Schema(description ="响应体")
|
||||||
|
private String responseBody;
|
||||||
|
|
||||||
|
@Schema(description ="耗时(ms)")
|
||||||
|
private Long timeTaken;
|
||||||
|
|
||||||
|
@Schema(description ="IP")
|
||||||
|
private String ip;
|
||||||
|
|
||||||
|
@Schema(description ="IP归属地")
|
||||||
|
private String address;
|
||||||
|
|
||||||
|
@Schema(description ="浏览器")
|
||||||
|
private String browser;
|
||||||
|
|
||||||
|
@Schema(description ="操作系统")
|
||||||
|
private String os;
|
||||||
|
|
||||||
|
@Schema(description ="状态(1:成功;2:失败)")
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
@Schema(description ="错误信息")
|
||||||
|
private String errorMsg;
|
||||||
|
|
||||||
|
@Schema(description ="创建人")
|
||||||
|
private Long createUser;
|
||||||
|
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||||
@Schema(description ="创建时间")
|
@Schema(description ="创建时间")
|
||||||
|
|
|
||||||
|
|
@ -63,4 +63,19 @@ public class SysUser extends BaseEntity implements Serializable {
|
||||||
@Schema(description ="更新人")
|
@Schema(description ="更新人")
|
||||||
private String updateBy;
|
private String updateBy;
|
||||||
|
|
||||||
|
@Schema(description ="会话ID")
|
||||||
|
private String sessionId;
|
||||||
|
|
||||||
|
@Schema(description ="邀请码(用于邀请别人)")
|
||||||
|
private String inviteCode;
|
||||||
|
|
||||||
|
@Schema(description ="被邀请码(邀请我注册的邀请码)")
|
||||||
|
private String invitedCode;
|
||||||
|
|
||||||
|
@Schema(description ="邀请人用户ID(邀请我注册的用户ID)")
|
||||||
|
private Long invitedBy;
|
||||||
|
|
||||||
|
@Schema(description ="用户头像")
|
||||||
|
private String userIcon = "defaultUserIcon.png";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,45 @@
|
||||||
|
package com.kexue.skills.entity.dto;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账户冻结单DTO
|
||||||
|
*
|
||||||
|
* @author 系统生成
|
||||||
|
* @since 2026-04-11
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class AccountFrozenDto {
|
||||||
|
|
||||||
|
@Schema(description ="会话ID")
|
||||||
|
private String sessionId;
|
||||||
|
|
||||||
|
@Schema(description ="调用ID,关联冻结单")
|
||||||
|
private String callId;
|
||||||
|
|
||||||
|
@Schema(description ="模型名称")
|
||||||
|
private String modelName;
|
||||||
|
|
||||||
|
@Schema(description ="对应回答的问题或需求")
|
||||||
|
private String question;
|
||||||
|
|
||||||
|
@Schema(description ="冻结金额/张数/次数/分钟")
|
||||||
|
private BigDecimal frozenAmount;
|
||||||
|
|
||||||
|
@Schema(description ="冻结类型:1.token 2.RMB(元) 99其他")
|
||||||
|
private Integer frozenType;
|
||||||
|
|
||||||
|
@Schema(description ="预估输入tokens")
|
||||||
|
private Long estimatedInputTokens;
|
||||||
|
|
||||||
|
@Schema(description ="预估输出tokens")
|
||||||
|
private Long estimatedOutputTokens;
|
||||||
|
|
||||||
|
@Schema(description ="过期时间")
|
||||||
|
private Date expireAt;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package com.kexue.skills.entity.dto;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账户冻结单释放DTO
|
||||||
|
*
|
||||||
|
* @author 系统生成
|
||||||
|
* @since 2026-04-11
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class AccountReleaseDto {
|
||||||
|
|
||||||
|
@Schema(description ="冻结单ID,字符型")
|
||||||
|
private String frozenId;
|
||||||
|
|
||||||
|
@Schema(description ="最终扣减,0=释放")
|
||||||
|
private BigDecimal finalAmount;
|
||||||
|
|
||||||
|
@Schema(description ="输入tokens")
|
||||||
|
private Long usageInputTokens;
|
||||||
|
|
||||||
|
@Schema(description ="输出tokens")
|
||||||
|
private Long usageOutputTokens;
|
||||||
|
|
||||||
|
@Schema(description ="总tokens")
|
||||||
|
private Long usageTotalTokens;
|
||||||
|
|
||||||
|
@Schema(description ="终结原因:success/cancel/timeout/error/system_recovery")
|
||||||
|
private String finalizeReason;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,8 @@ package com.kexue.skills.entity.dto;
|
||||||
import com.kexue.skills.entity.base.BaseQueryDto;
|
import com.kexue.skills.entity.base.BaseQueryDto;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (AccountTransaction)查询DTO类
|
* (AccountTransaction)查询DTO类
|
||||||
*
|
*
|
||||||
|
|
@ -32,4 +34,8 @@ public class AccountTransactionDto extends BaseQueryDto {
|
||||||
|
|
||||||
private Integer deleteFlag;
|
private Integer deleteFlag;
|
||||||
|
|
||||||
|
private Date createTimeStart;
|
||||||
|
|
||||||
|
private Date createTimeEnd;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.kexue.skills.entity.dto;
|
||||||
|
|
||||||
|
import com.kexue.skills.entity.base.BaseQueryDto;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (CmsCategoryTag)查询DTO
|
||||||
|
*
|
||||||
|
* @author 王志维
|
||||||
|
* @since 2025-02-21 23:01:48
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CmsCategoryTagDto extends BaseQueryDto {
|
||||||
|
|
||||||
|
@Schema(description ="主键ID")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description ="分类ID")
|
||||||
|
private Long categoryId;
|
||||||
|
|
||||||
|
@Schema(description ="标签ID")
|
||||||
|
private Long tagId;
|
||||||
|
|
||||||
|
@Schema(description ="是否删除 :0 未删除,1已删除")
|
||||||
|
private Integer deleteFlag;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -20,12 +20,6 @@ public class CmsContentDto extends BaseQueryDto {
|
||||||
|
|
||||||
private Integer contentType;
|
private Integer contentType;
|
||||||
|
|
||||||
private String categoryIds;
|
|
||||||
|
|
||||||
private Long categoryId;
|
|
||||||
|
|
||||||
private List<String> categoryIdList;
|
|
||||||
|
|
||||||
private Boolean isOfficial;
|
private Boolean isOfficial;
|
||||||
|
|
||||||
private Integer shareCount;
|
private Integer shareCount;
|
||||||
|
|
@ -44,4 +38,36 @@ public class CmsContentDto extends BaseQueryDto {
|
||||||
|
|
||||||
private Long parentCategoryId;
|
private Long parentCategoryId;
|
||||||
|
|
||||||
|
private Long tagId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标签 ID 列表,用于批量查询
|
||||||
|
*/
|
||||||
|
private List<Long> tagIdList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语言类型:0 中文,1 英文
|
||||||
|
*/
|
||||||
|
private Integer languageType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索关键字,同时搜索 title、description、tags
|
||||||
|
*/
|
||||||
|
private String keyword;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 来源
|
||||||
|
*/
|
||||||
|
private String origin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标签
|
||||||
|
*/
|
||||||
|
private String tags;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图标
|
||||||
|
*/
|
||||||
|
private String icon;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ public class CmsTagDto extends BaseQueryDto {
|
||||||
|
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
|
||||||
|
private String icon;
|
||||||
|
|
||||||
private Integer deleteFlag;
|
private Integer deleteFlag;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
package com.kexue.skills.entity.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消费记录分组DTO类(按callId分组)
|
||||||
|
*
|
||||||
|
* @author 王志维
|
||||||
|
* @since 2025-04-15
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ConsumptionGroupedDto implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description ="主键ID(取最早记录的ID)")
|
||||||
|
private Long transactionId;
|
||||||
|
|
||||||
|
@Schema(description ="用户ID")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
@Schema(description ="用户名")
|
||||||
|
private String userName;
|
||||||
|
|
||||||
|
@Schema(description ="交易类型:1.充值 2.提现 3.购买内容 4.退款 5.签到奖励 6.赠送 7.其他")
|
||||||
|
private Integer transactionType;
|
||||||
|
|
||||||
|
@Schema(description ="交易金额")
|
||||||
|
private BigDecimal amount;
|
||||||
|
|
||||||
|
@Schema(description ="交易前余额")
|
||||||
|
private BigDecimal beforeBalance;
|
||||||
|
|
||||||
|
@Schema(description ="交易后余额")
|
||||||
|
private BigDecimal afterBalance;
|
||||||
|
|
||||||
|
@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 ="调用ID,关联冻结单")
|
||||||
|
private String callId;
|
||||||
|
|
||||||
|
@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 ="对应回答的问题或需求(取最早入库的question)")
|
||||||
|
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;
|
||||||
|
|
||||||
|
@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,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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
package com.kexue.skills.entity.dto;
|
||||||
|
|
||||||
|
import com.kexue.skills.entity.PaymentOrder;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单状态DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class OrderStatusDto {
|
||||||
|
|
||||||
|
@Schema(description = "订单ID")
|
||||||
|
private Long orderId;
|
||||||
|
|
||||||
|
@Schema(description = "订单号")
|
||||||
|
private String orderNo;
|
||||||
|
|
||||||
|
@Schema(description = "状态码")
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
@Schema(description = "状态文本")
|
||||||
|
private String statusText;
|
||||||
|
|
||||||
|
@Schema(description = "支付金额")
|
||||||
|
private BigDecimal amount;
|
||||||
|
|
||||||
|
@Schema(description = "支付方式:1.微信 2.支付宝")
|
||||||
|
private Integer payType;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
@Schema(description = "支付时间")
|
||||||
|
private Date payTime;
|
||||||
|
|
||||||
|
@Schema(description = "渠道订单号")
|
||||||
|
private String channelOrderNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从PaymentOrder构建OrderStatusDto
|
||||||
|
* @param order 支付订单
|
||||||
|
* @return 订单状态DTO
|
||||||
|
*/
|
||||||
|
public static OrderStatusDto fromPaymentOrder(PaymentOrder order) {
|
||||||
|
OrderStatusDto dto = new OrderStatusDto();
|
||||||
|
dto.setOrderId(order.getOrderId());
|
||||||
|
dto.setOrderNo(order.getOrderNo());
|
||||||
|
dto.setStatus(order.getStatus());
|
||||||
|
dto.setStatusText(getStatusText(order.getStatus()));
|
||||||
|
dto.setAmount(order.getAmount());
|
||||||
|
dto.setPayType(order.getPayType());
|
||||||
|
dto.setCreateTime(order.getCreateTime());
|
||||||
|
dto.setPayTime(order.getPayTime());
|
||||||
|
dto.setChannelOrderNo(order.getChannelOrderNo());
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取状态文本
|
||||||
|
* @param status 状态码
|
||||||
|
* @return 状态文本
|
||||||
|
*/
|
||||||
|
private static String getStatusText(Integer status) {
|
||||||
|
switch (status) {
|
||||||
|
case 1: return "待支付";
|
||||||
|
case 2: return "已支付";
|
||||||
|
case 3: return "支付失败";
|
||||||
|
case 4: return "已取消";
|
||||||
|
case 5: return "已退款";
|
||||||
|
default: return "未知状态";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.kexue.skills.entity.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单状态查询DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class OrderStatusQueryDto {
|
||||||
|
|
||||||
|
@Schema(description = "订单ID")
|
||||||
|
private Long orderId;
|
||||||
|
|
||||||
|
@Schema(description = "订单号")
|
||||||
|
private String orderNo;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.kexue.skills.entity.dto;
|
||||||
|
|
||||||
|
import com.kexue.skills.entity.base.BaseQueryDto;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (PackageConfig)查询DTO类
|
||||||
|
*
|
||||||
|
* @author 系统生成
|
||||||
|
* @since 2026-04-11
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class PackageConfigDto extends BaseQueryDto {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private BigDecimal price;
|
||||||
|
|
||||||
|
private BigDecimal baseAmount;
|
||||||
|
|
||||||
|
private BigDecimal giftAmount;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,9 @@ package com.kexue.skills.entity.dto;
|
||||||
|
|
||||||
import com.kexue.skills.entity.base.BaseQueryDto;
|
import com.kexue.skills.entity.base.BaseQueryDto;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import org.checkerframework.checker.formatter.qual.Format;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (PaymentOrder)查询DTO类
|
* (PaymentOrder)查询DTO类
|
||||||
|
|
@ -30,4 +33,10 @@ public class PaymentOrderDto extends BaseQueryDto {
|
||||||
|
|
||||||
private Integer deleteFlag;
|
private Integer deleteFlag;
|
||||||
|
|
||||||
|
private Long packageId;
|
||||||
|
|
||||||
|
private Date createTimeStart;
|
||||||
|
|
||||||
|
private Date createTimeEnd;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -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,18 @@
|
||||||
|
package com.kexue.skills.entity.dto;
|
||||||
|
|
||||||
|
import com.kexue.skills.entity.base.BaseQueryDto;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (CmsContent)查询DTO类
|
||||||
|
*
|
||||||
|
* @author 王志维
|
||||||
|
* @since 2025-02-21 23:01:48
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class QueryContentDto extends BaseQueryDto {
|
||||||
|
private Long contentId;
|
||||||
|
private Integer languageType;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.kexue.skills.entity.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话DTO类
|
||||||
|
*
|
||||||
|
* @author 王志维
|
||||||
|
* @since 2026-03-12
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SessionDto {
|
||||||
|
@Schema(description = "会话ID")
|
||||||
|
private String sessionId;
|
||||||
|
|
||||||
|
@Schema(description = "是否为新会话")
|
||||||
|
private boolean isNew;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
package com.kexue.skills.entity.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 技能包信息DTO
|
||||||
|
*
|
||||||
|
* @author AI技能生成助手
|
||||||
|
* @since 2026-04-10
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SkillPackageInfoDto implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 技能名称
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 版本号
|
||||||
|
*/
|
||||||
|
private String version;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 技能描述
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 作者
|
||||||
|
*/
|
||||||
|
private String author;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建日期
|
||||||
|
*/
|
||||||
|
private String created;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标签列表
|
||||||
|
*/
|
||||||
|
private List<String> tags;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 目录结构
|
||||||
|
*/
|
||||||
|
private List<SkillStructureNodeDto> structure;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
package com.kexue.skills.entity.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 技能包目录结构节点DTO
|
||||||
|
*
|
||||||
|
* @author AI技能生成助手
|
||||||
|
* @since 2026-04-10
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SkillStructureNodeDto implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点名称(文件或目录名)
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点类型:directory 或 file
|
||||||
|
*/
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 父级路径
|
||||||
|
*/
|
||||||
|
private String path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式:dir、markdown、python
|
||||||
|
*/
|
||||||
|
private String format;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点描述
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 子节点列表(目录类型使用)
|
||||||
|
*/
|
||||||
|
private List<SkillStructureNodeDto> children;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件内容(文件类型使用)
|
||||||
|
*/
|
||||||
|
private String content;
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ package com.kexue.skills.entity.dto;
|
||||||
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
import com.kexue.skills.entity.base.BaseQueryDto;
|
import com.kexue.skills.entity.base.BaseQueryDto;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
|
@ -23,28 +24,24 @@ public class SysLogDto extends BaseQueryDto implements Serializable {
|
||||||
@Schema(description ="主键ID")
|
@Schema(description ="主键ID")
|
||||||
private Long logId;
|
private Long logId;
|
||||||
|
|
||||||
@Schema(description ="用户ID")
|
@Schema(description ="模块")
|
||||||
private String userId;
|
private String module;
|
||||||
|
|
||||||
@Schema(description ="用户名称")
|
@Schema(description ="描述")
|
||||||
private String userName;
|
private String description;
|
||||||
|
|
||||||
@Schema(description ="日志类型")
|
@Schema(description ="IP地址")
|
||||||
private String logType;
|
private String ip;
|
||||||
|
|
||||||
@Schema(description ="日志类容")
|
@Schema(description ="状态")
|
||||||
private String logContent;
|
private Integer status;
|
||||||
|
|
||||||
@Schema(description ="服务端IP")
|
@Schema(description ="开始时间")
|
||||||
private String serverIp;
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||||
|
private Date startTime;
|
||||||
|
|
||||||
@Schema(description ="客户端IP")
|
@Schema(description ="结束时间")
|
||||||
private String clientIp;
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||||
|
private Date endTime;
|
||||||
@Schema(description ="yyyyMMddHHmmss")
|
|
||||||
private String logTime;
|
|
||||||
|
|
||||||
@Schema(description ="备注")
|
|
||||||
private String note;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
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 String sessionId;
|
||||||
|
|
||||||
|
@Schema(description ="用户ID" ,hidden = true)
|
||||||
|
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" ,hidden = true)
|
||||||
|
private Long businessId;
|
||||||
|
|
||||||
|
@Schema(description ="业务类型",hidden = true)
|
||||||
|
private String businessType;
|
||||||
|
|
||||||
|
@Schema(description ="备注")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
@Schema(description ="输出模式:standard=非思考模式, thinking=思考模式")
|
||||||
|
private String outputMode;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.kexue.skills.entity.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "yaml内容")
|
||||||
|
public class YamlContentDto implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "yaml内容")
|
||||||
|
private String yamlContent;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.kexue.skills.entity.request;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 技能生成请求参数
|
||||||
|
*
|
||||||
|
* @author 维哥
|
||||||
|
* @since 2026-01-28
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ApiModel(value = "功能介绍生成请求参数")
|
||||||
|
public class GenIntroduceRequest implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "yaml或者skill.md内容", required = true)
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.kexue.skills.entity.request;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入路径请求参数
|
||||||
|
*
|
||||||
|
* @author 王志维
|
||||||
|
* @since 2025-02-21 23:01:48
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(name = "ImportPathDto", description = "导入路径请求参数")
|
||||||
|
public class ImportPathDto implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "是否追加,true表示追加,false表示清空表")
|
||||||
|
private boolean append;
|
||||||
|
|
||||||
|
@Schema(description = "文件目录")
|
||||||
|
private String filePath;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package com.kexue.skills.entity.request;
|
package com.kexue.skills.entity.request;
|
||||||
|
|
||||||
import com.kexue.skills.entity.Account;
|
import com.kexue.skills.entity.Account;
|
||||||
import com.kexue.skills.entity.PointsAccount;
|
|
||||||
import com.kexue.skills.entity.SysUser;
|
import com.kexue.skills.entity.SysUser;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
|
@ -35,11 +34,6 @@ public class LoginUser {
|
||||||
*/
|
*/
|
||||||
private Account account;
|
private Account account;
|
||||||
|
|
||||||
/**
|
|
||||||
* 积分信息
|
|
||||||
*/
|
|
||||||
private PointsAccount pointsAccount;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* token
|
* token
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -23,4 +23,7 @@ public class PhoneLoginDto implements Serializable {
|
||||||
|
|
||||||
@Schema(description ="验证码")
|
@Schema(description ="验证码")
|
||||||
private String code;
|
private String code;
|
||||||
|
|
||||||
|
@Schema(description ="邀请码")
|
||||||
|
private String inviteCode;
|
||||||
}
|
}
|
||||||
|
|
@ -15,7 +15,7 @@ import java.io.Serializable;
|
||||||
@ApiModel(value = "重置密码请求参数")
|
@ApiModel(value = "重置密码请求参数")
|
||||||
public class ResetPasswordDto implements Serializable {
|
public class ResetPasswordDto implements Serializable {
|
||||||
|
|
||||||
@Schema(description ="用户名")
|
@Schema(description ="管理员用户名")
|
||||||
private String userName;
|
private String userName;
|
||||||
|
|
||||||
@Schema(description ="旧密码")
|
@Schema(description ="旧密码")
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,23 @@
|
||||||
package com.kexue.skills.entity.request;
|
package com.kexue.skills.entity.request;
|
||||||
|
|
||||||
import io.swagger.annotations.ApiModel;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
|
||||||
* 技能生成请求参数
|
|
||||||
*
|
|
||||||
* @author 维哥
|
|
||||||
* @since 2026-01-28
|
|
||||||
*/
|
|
||||||
@Data
|
@Data
|
||||||
@ApiModel(value = "技能生成请求参数")
|
|
||||||
public class SkillGenRequest implements Serializable {
|
public class SkillGenRequest implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
@Schema(description = "用户提示词")
|
@Schema(description = "技能名称")
|
||||||
private String prompt;
|
private String name;
|
||||||
|
@Schema(description = "技能描述")
|
||||||
|
private String description;
|
||||||
|
@Schema(description = "技能标签")
|
||||||
|
private List<String> tags;
|
||||||
|
@Schema(description = "技能说明")
|
||||||
|
private String introduce;
|
||||||
|
@Schema(description = "需求说明")
|
||||||
|
private String requirement;
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package com.kexue.skills.entity.request;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 技能生成请求参数
|
||||||
|
*
|
||||||
|
* @author 维哥
|
||||||
|
* @since 2026-01-28
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ApiModel(value = "技能生成请求参数")
|
||||||
|
public class SkillPreGenRequest implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "用户提示词")
|
||||||
|
private String prompt;
|
||||||
|
|
||||||
|
@Schema(description = "文件地址")
|
||||||
|
private String fileUrl;
|
||||||
|
|
||||||
|
@Schema(description = "文件地址列表")
|
||||||
|
private List<String> fileUrls;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -32,7 +32,7 @@ public class SkillRequest implements Serializable {
|
||||||
|
|
||||||
Message userMessage = new Message();
|
Message userMessage = new Message();
|
||||||
userMessage.setRole("user");
|
userMessage.setRole("user");
|
||||||
userMessage.setContent("主题:我想要做一个将数据库设计表转换成sql schema语言并迁移到数据库服务器中的skill。请根据agent skills撰写规范帮我生成这个skill的名称、描述,并从以下标签列表中选择一个或者多个标签:\"软件开发,系统集成,网络工程,云计算,大数据,人工智能,物联网,区块链,信息安全,运维服务,测试认证,IT 咨询,外包服务,电商技术,移动开发,前端开发,后端开发,全栈开发,数据库管理\"。输出json格式,仅输出以上所提到的名称、描述、标签,节点名称分别为name、description、tags,节点内容以中文形式返回。");
|
userMessage.setContent("主题:我想要做一个将数据库设计表转换成sql schema语言并迁移到数据库服务器中的skill。请根据agent skills撰写规范帮我生成这个skill的名称、描述,并从数据库中选择一个或者多个标签。输出json格式,仅输出以上所提到的名称、描述、标签,节点名称分别为name、description、tags,节点内容以中文形式返回。");
|
||||||
this.messages.add(userMessage);
|
this.messages.add(userMessage);
|
||||||
|
|
||||||
this.temperature = 0.3;
|
this.temperature = 0.3;
|
||||||
|
|
@ -43,7 +43,7 @@ public class SkillRequest implements Serializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public SkillRequest(boolean useDefaultSettings, String model, Double temperature, Integer maxTokens,String prompt) {
|
public SkillRequest(boolean useDefaultSettings, String model, Double temperature, Integer maxTokens, String prompt, String tagsList) {
|
||||||
if (useDefaultSettings) {
|
if (useDefaultSettings) {
|
||||||
this.model = model;
|
this.model = model;
|
||||||
this.messages = new ArrayList<>();
|
this.messages = new ArrayList<>();
|
||||||
|
|
@ -55,7 +55,7 @@ public class SkillRequest implements Serializable {
|
||||||
|
|
||||||
Message userMessage = new Message();
|
Message userMessage = new Message();
|
||||||
userMessage.setRole("user");
|
userMessage.setRole("user");
|
||||||
userMessage.setContent("主题:"+ prompt +"。请根据agent skills撰写规范帮我生成这个skill的名称、描述,并从以下标签列表中选择一个或者多个标签:\"软件开发,系统集成,网络工程,云计算,大数据,人工智能,物联网,区块链,信息安全,运维服务,测试认证,IT 咨询,外包服务,电商技术,移动开发,前端开发,后端开发,全栈开发,数据库管理\"。输出json格式,仅输出以上所提到的名称、描述、标签,节点名称分别为name、description、tags,节点内容以中文形式返回。");
|
userMessage.setContent("主题:"+ prompt +"。请根据agent skills撰写规范帮我生成这个skill的名称、描述,并从以下标签列表中选择一个或者多个标签:\"" + tagsList + "\"。输出json格式,仅输出以上所提到的名称、描述、标签,节点名称分别为name、description、tags,节点内容以中文形式返回,tags只需要返回序号数组");
|
||||||
this.messages.add(userMessage);
|
this.messages.add(userMessage);
|
||||||
|
|
||||||
this.temperature = temperature;
|
this.temperature = temperature;
|
||||||
|
|
@ -66,6 +66,72 @@ public class SkillRequest implements Serializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SkillRequest(boolean useDefaultSettings, String model, String systemContent,String userContent,Double temperature, Integer maxTokens,String type) {
|
||||||
|
if (useDefaultSettings) {
|
||||||
|
this.model = model;
|
||||||
|
this.messages = new ArrayList<>();
|
||||||
|
|
||||||
|
Message systemMessage = new Message();
|
||||||
|
systemMessage.setRole("system");
|
||||||
|
systemMessage.setContent(systemContent);
|
||||||
|
this.messages.add(systemMessage);
|
||||||
|
|
||||||
|
Message userMessage = new Message();
|
||||||
|
userMessage.setRole("user");
|
||||||
|
userMessage.setContent(userContent);
|
||||||
|
this.messages.add(userMessage);
|
||||||
|
|
||||||
|
this.temperature = temperature;
|
||||||
|
this.max_tokens = maxTokens;
|
||||||
|
|
||||||
|
this.response_format = new ResponseFormat();
|
||||||
|
this.response_format.setType(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新的构造方法,支持文件URL列表
|
||||||
|
public SkillRequest(String model, String systemContent, String prompt, List<String> fileUrls, double temperature, int maxTokens) {
|
||||||
|
this.model = model;
|
||||||
|
this.messages = new ArrayList<>();
|
||||||
|
|
||||||
|
Message systemMessage = new Message();
|
||||||
|
systemMessage.setRole("system");
|
||||||
|
systemMessage.setContent(systemContent);
|
||||||
|
this.messages.add(systemMessage);
|
||||||
|
|
||||||
|
// 构建包含文件URL的用户消息
|
||||||
|
List<MessageContent> messageContents = new ArrayList<>();
|
||||||
|
|
||||||
|
// 添加文件URL
|
||||||
|
if (fileUrls != null && !fileUrls.isEmpty()) {
|
||||||
|
for (String fileUrl : fileUrls) {
|
||||||
|
MessageContent fileContent = new MessageContent();
|
||||||
|
fileContent.setType("file_url");
|
||||||
|
FileUrl fileUrlObj = new FileUrl();
|
||||||
|
fileUrlObj.setUrl(fileUrl);
|
||||||
|
fileContent.setFile_url(fileUrlObj);
|
||||||
|
messageContents.add(fileContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加文本内容
|
||||||
|
MessageContent textContent = new MessageContent();
|
||||||
|
textContent.setType("text");
|
||||||
|
textContent.setText(prompt);
|
||||||
|
messageContents.add(textContent);
|
||||||
|
|
||||||
|
Message userMessage = new Message();
|
||||||
|
userMessage.setRole("user");
|
||||||
|
userMessage.setContent(messageContents);
|
||||||
|
this.messages.add(userMessage);
|
||||||
|
|
||||||
|
this.temperature = temperature;
|
||||||
|
this.max_tokens = maxTokens;
|
||||||
|
|
||||||
|
this.response_format = new ResponseFormat();
|
||||||
|
this.response_format.setType("json_object");
|
||||||
|
}
|
||||||
|
|
||||||
public static SkillRequest createDefault() {
|
public static SkillRequest createDefault() {
|
||||||
return new SkillRequest(true);
|
return new SkillRequest(true);
|
||||||
}
|
}
|
||||||
|
|
@ -77,7 +143,27 @@ public class SkillRequest implements Serializable {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private String role;
|
private String role;
|
||||||
private String content;
|
private Object content; // 可以是String或List<MessageContent>
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public static class MessageContent implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private String type;
|
||||||
|
private String text;
|
||||||
|
private FileUrl file_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public static class FileUrl implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private String url;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.kexue.skills.entity.request;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class SkillUploadDto implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "技能包地址", required = true)
|
||||||
|
private String url;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
package com.kexue.skills.entity.request;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户更新请求参数
|
||||||
|
*
|
||||||
|
* @author 王志维
|
||||||
|
* @since 2025-02-21 23:01:48
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(name = "SysUserUpdateDto", description = "用户更新请求参数")
|
||||||
|
public class SysUserUpdateDto implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "用户ID")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
@Schema(description = "用户名")
|
||||||
|
private String userName;
|
||||||
|
|
||||||
|
@Schema(description = "密码")
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@Schema(description = "邮箱")
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@Schema(description = "手机号")
|
||||||
|
private String tel;
|
||||||
|
|
||||||
|
@Schema(description = "状态(1-正常,0-禁用)")
|
||||||
|
private Integer enable;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -12,4 +12,5 @@ public class SkillResponse implements Serializable {
|
||||||
private String name;
|
private String name;
|
||||||
private String description;
|
private String description;
|
||||||
private List<String> tags;
|
private List<String> tags;
|
||||||
|
private String summary;
|
||||||
}
|
}
|
||||||
|
|
@ -15,9 +15,9 @@ public class GlobalExceptionHandler {
|
||||||
@ExceptionHandler(BizException.class)
|
@ExceptionHandler(BizException.class)
|
||||||
public CommonResult<String> handleBizException(BizException e) {
|
public CommonResult<String> handleBizException(BizException e) {
|
||||||
if (e == null) {
|
if (e == null) {
|
||||||
return CommonResult.failed("未知错误");
|
return CommonResult.success("未知错误");
|
||||||
}
|
}
|
||||||
return CommonResult.failed(e.getMessage());
|
return CommonResult.success(e.getErrorCode(), e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 其他异常处理...
|
// 其他异常处理...
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@ControllerAdvice
|
@ControllerAdvice
|
||||||
public class BizExceptionAdvice {
|
public class BizExceptionAdvice {
|
||||||
@ExceptionHandler(value = RuntimeException.class)
|
@ExceptionHandler(value = BizException.class)
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public CommonResult handleException(BizException e){
|
public CommonResult handleException(BizException e){
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
package com.kexue.skills.interceptor;
|
||||||
|
|
||||||
|
import jakarta.servlet.*;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求响应体缓存过滤器
|
||||||
|
* 用于包装 HttpServletRequest 和 HttpServletResponse,使其可以重复读取请求体和捕获响应体
|
||||||
|
*
|
||||||
|
* @author 王志维
|
||||||
|
* @since 2026-04-14
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Order(1) // 确保在最前面执行
|
||||||
|
public class CachedBodyFilter implements Filter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
|
||||||
|
if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
|
||||||
|
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||||
|
HttpServletResponse httpResponse = (HttpServletResponse) response;
|
||||||
|
|
||||||
|
// 跳过对 multipart/form-data 格式请求的包装,因为文件上传请求不能被重复读取
|
||||||
|
String contentType = httpRequest.getContentType();
|
||||||
|
if (contentType != null && contentType.startsWith("multipart/")) {
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 包装请求,使其可以重复读取请求体
|
||||||
|
CachedBodyHttpServletRequest cachedRequest = new CachedBodyHttpServletRequest(httpRequest);
|
||||||
|
|
||||||
|
// 包装响应,使其可以捕获响应体
|
||||||
|
CachedBodyHttpServletResponse cachedResponse = new CachedBodyHttpServletResponse(httpResponse);
|
||||||
|
|
||||||
|
// 继续过滤链
|
||||||
|
chain.doFilter(cachedRequest, cachedResponse);
|
||||||
|
|
||||||
|
// 将缓存的响应体写入真实响应
|
||||||
|
cachedResponse.flushBuffer();
|
||||||
|
} else {
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
package com.kexue.skills.interceptor;
|
||||||
|
|
||||||
|
import jakarta.servlet.ReadListener;
|
||||||
|
import jakarta.servlet.ServletInputStream;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 可重复读取请求体的 HttpServletRequest 包装类
|
||||||
|
*
|
||||||
|
* @author 王志维
|
||||||
|
* @since 2026-04-14
|
||||||
|
*/
|
||||||
|
public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
|
||||||
|
|
||||||
|
private final byte[] cachedBody;
|
||||||
|
|
||||||
|
public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
|
||||||
|
super(request);
|
||||||
|
// 读取并缓存请求体
|
||||||
|
this.cachedBody = readBytes(request.getInputStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ServletInputStream getInputStream() {
|
||||||
|
return new CachedBodyServletInputStream(this.cachedBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BufferedReader getReader() {
|
||||||
|
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody);
|
||||||
|
return new BufferedReader(new InputStreamReader(byteArrayInputStream));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取缓存的请求体字符串
|
||||||
|
*/
|
||||||
|
public String getCachedBodyString() {
|
||||||
|
return new String(cachedBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从输入流读取字节数组
|
||||||
|
*/
|
||||||
|
private byte[] readBytes(InputStream inputStream) throws IOException {
|
||||||
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
|
int nRead;
|
||||||
|
byte[] data = new byte[1024];
|
||||||
|
while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
|
||||||
|
buffer.write(data, 0, nRead);
|
||||||
|
}
|
||||||
|
return buffer.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 可重复读取的 ServletInputStream
|
||||||
|
*/
|
||||||
|
private static class CachedBodyServletInputStream extends ServletInputStream {
|
||||||
|
|
||||||
|
private final ByteArrayInputStream inputStream;
|
||||||
|
|
||||||
|
public CachedBodyServletInputStream(byte[] cachedBody) {
|
||||||
|
this.inputStream = new ByteArrayInputStream(cachedBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFinished() {
|
||||||
|
return inputStream.available() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReady() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setReadListener(ReadListener listener) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() {
|
||||||
|
return inputStream.read();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
package com.kexue.skills.interceptor;
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletOutputStream;
|
||||||
|
import jakarta.servlet.WriteListener;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import jakarta.servlet.http.HttpServletResponseWrapper;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 可捕获响应体的 HttpServletResponse 包装类
|
||||||
|
*/
|
||||||
|
public class CachedBodyHttpServletResponse extends HttpServletResponseWrapper {
|
||||||
|
|
||||||
|
private final ByteArrayOutputStream cachedBody = new ByteArrayOutputStream();
|
||||||
|
private ServletOutputStream outputStream;
|
||||||
|
private PrintWriter writer;
|
||||||
|
|
||||||
|
public CachedBodyHttpServletResponse(HttpServletResponse response) {
|
||||||
|
super(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ServletOutputStream getOutputStream() throws IOException {
|
||||||
|
if (outputStream == null) {
|
||||||
|
outputStream = new CachedBodyServletOutputStream(super.getOutputStream(), cachedBody);
|
||||||
|
}
|
||||||
|
return outputStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PrintWriter getWriter() throws IOException {
|
||||||
|
if (writer == null) {
|
||||||
|
writer = new PrintWriter(getOutputStream(), true);
|
||||||
|
}
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flushBuffer() throws IOException {
|
||||||
|
if (writer != null) {
|
||||||
|
writer.flush();
|
||||||
|
}
|
||||||
|
if (outputStream != null) {
|
||||||
|
outputStream.flush();
|
||||||
|
}
|
||||||
|
super.flushBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCachedBodyString() {
|
||||||
|
return cachedBody.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class CachedBodyServletOutputStream extends ServletOutputStream {
|
||||||
|
|
||||||
|
private final ServletOutputStream outputStream;
|
||||||
|
private final ByteArrayOutputStream cachedBody;
|
||||||
|
|
||||||
|
public CachedBodyServletOutputStream(ServletOutputStream outputStream, ByteArrayOutputStream cachedBody) {
|
||||||
|
this.outputStream = outputStream;
|
||||||
|
this.cachedBody = cachedBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReady() {
|
||||||
|
return outputStream.isReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setWriteListener(WriteListener listener) {
|
||||||
|
outputStream.setWriteListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(int b) throws IOException {
|
||||||
|
outputStream.write(b);
|
||||||
|
cachedBody.write(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b) throws IOException {
|
||||||
|
outputStream.write(b);
|
||||||
|
cachedBody.write(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b, int off, int len) throws IOException {
|
||||||
|
outputStream.write(b, off, len);
|
||||||
|
cachedBody.write(b, off, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,412 @@
|
||||||
|
package com.kexue.skills.interceptor;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.http.useragent.UserAgent;
|
||||||
|
import cn.hutool.http.useragent.UserAgentUtil;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.kexue.skills.annotation.Log;
|
||||||
|
import com.kexue.skills.entity.LogRecord;
|
||||||
|
import com.kexue.skills.mapper.SysLogMapper;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
|
import org.springframework.web.method.HandlerMethod;
|
||||||
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作日志拦截器
|
||||||
|
* 基于 HandlerInterceptor 实现,捕获请求和响应信息
|
||||||
|
*
|
||||||
|
* @author 王志维
|
||||||
|
* @since 2026-04-14
|
||||||
|
*/
|
||||||
|
public class LogInterceptor implements HandlerInterceptor {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(LogInterceptor.class);
|
||||||
|
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
private SysLogMapper sysLogMapper;
|
||||||
|
|
||||||
|
// 使用 ThreadLocal 存储请求开始时间和日志记录
|
||||||
|
private static final ThreadLocal<Long> START_TIME = new ThreadLocal<>();
|
||||||
|
private static final ThreadLocal<LogRecord> LOG_RECORD = new ThreadLocal<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置 SysLogMapper
|
||||||
|
*/
|
||||||
|
public void setSysLogMapper(SysLogMapper sysLogMapper) {
|
||||||
|
this.sysLogMapper = sysLogMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||||
|
// 只处理方法级别的请求
|
||||||
|
if (!(handler instanceof HandlerMethod)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
HandlerMethod handlerMethod = (HandlerMethod) handler;
|
||||||
|
|
||||||
|
// 检查是否有 @Log 注解
|
||||||
|
Log methodLog = handlerMethod.getMethodAnnotation(Log.class);
|
||||||
|
Log classLog = handlerMethod.getBeanType().getAnnotation(Log.class);
|
||||||
|
|
||||||
|
// 如果方法和类都没有 @Log 注解,不记录日志
|
||||||
|
if (methodLog == null && classLog == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果方法级别设置了 ignore=true,不记录日志
|
||||||
|
if (methodLog != null && methodLog.ignore()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录开始时间
|
||||||
|
START_TIME.set(System.currentTimeMillis());
|
||||||
|
|
||||||
|
// 构建日志记录对象
|
||||||
|
LogRecord logRecord = new LogRecord();
|
||||||
|
|
||||||
|
// 设置模块和描述
|
||||||
|
String module = "";
|
||||||
|
String description = "";
|
||||||
|
if (classLog != null) {
|
||||||
|
module = classLog.module();
|
||||||
|
}
|
||||||
|
if (methodLog != null) {
|
||||||
|
if (StrUtil.isNotBlank(methodLog.module())) {
|
||||||
|
module = methodLog.module();
|
||||||
|
}
|
||||||
|
description = methodLog.description();
|
||||||
|
}
|
||||||
|
logRecord.setModule(module);
|
||||||
|
logRecord.setDescription(description);
|
||||||
|
|
||||||
|
// 设置请求信息
|
||||||
|
LogRecord.LogRequest logRequest = new LogRecord.LogRequest();
|
||||||
|
logRequest.setMethod(request.getMethod());
|
||||||
|
logRequest.setUrl(request.getRequestURL().toString());
|
||||||
|
logRequest.setHeaders(getRequestHeaders(request));
|
||||||
|
logRequest.setIp(getClientIp(request));
|
||||||
|
logRequest.setBrowser(getBrowserInfo(request));
|
||||||
|
logRequest.setOs(getOsInfo(request));
|
||||||
|
|
||||||
|
// 读取请求体(从包装对象中获取)
|
||||||
|
if (request instanceof CachedBodyHttpServletRequest) {
|
||||||
|
CachedBodyHttpServletRequest cachedRequest = (CachedBodyHttpServletRequest) request;
|
||||||
|
String requestBody = cachedRequest.getCachedBodyString();
|
||||||
|
// 限制请求体大小,避免过大
|
||||||
|
if (requestBody.length() > 10000) {
|
||||||
|
requestBody = requestBody.substring(0, 10000) + "... [truncated]";
|
||||||
|
}
|
||||||
|
logRequest.setBody(requestBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
logRecord.setRequest(logRequest);
|
||||||
|
logRecord.setTimestamp(Instant.now());
|
||||||
|
|
||||||
|
// 存储到 ThreadLocal
|
||||||
|
LOG_RECORD.set(logRecord);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
|
||||||
|
Object handler, Exception ex) {
|
||||||
|
// 只处理方法级别的请求
|
||||||
|
if (!(handler instanceof HandlerMethod)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取日志记录
|
||||||
|
LogRecord logRecord = LOG_RECORD.get();
|
||||||
|
Long startTime = START_TIME.get();
|
||||||
|
|
||||||
|
if (logRecord == null || startTime == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 计算耗时
|
||||||
|
long timeTaken = System.currentTimeMillis() - startTime;
|
||||||
|
logRecord.setTimeTaken(timeTaken);
|
||||||
|
|
||||||
|
// 设置响应信息
|
||||||
|
LogRecord.LogResponse logResponse = new LogRecord.LogResponse();
|
||||||
|
logResponse.setStatus(response.getStatus());
|
||||||
|
logResponse.setHeaders(getResponseHeaders(response));
|
||||||
|
|
||||||
|
// 读取响应体(从包装对象中获取)
|
||||||
|
if (response instanceof CachedBodyHttpServletResponse) {
|
||||||
|
CachedBodyHttpServletResponse cachedResponse = (CachedBodyHttpServletResponse) response;
|
||||||
|
String responseBody = cachedResponse.getCachedBodyString();
|
||||||
|
// 限制响应体大小,避免过大
|
||||||
|
if (responseBody.length() > 10000) {
|
||||||
|
responseBody = responseBody.substring(0, 10000) + "... [truncated]";
|
||||||
|
}
|
||||||
|
logResponse.setBody(responseBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
logRecord.setResponse(logResponse);
|
||||||
|
|
||||||
|
// 异步保存日志
|
||||||
|
saveLogAsync(logRecord);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("记录操作日志失败: {}", e.getMessage(), e);
|
||||||
|
} finally {
|
||||||
|
// 清理 ThreadLocal
|
||||||
|
START_TIME.remove();
|
||||||
|
LOG_RECORD.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取请求头
|
||||||
|
*/
|
||||||
|
private Map<String, String> getRequestHeaders(HttpServletRequest request) {
|
||||||
|
Map<String, String> headers = new HashMap<>();
|
||||||
|
Enumeration<String> headerNames = request.getHeaderNames();
|
||||||
|
while (headerNames.hasMoreElements()) {
|
||||||
|
String headerName = headerNames.nextElement();
|
||||||
|
headers.put(headerName, request.getHeader(headerName));
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取响应头
|
||||||
|
*/
|
||||||
|
private Map<String, String> getResponseHeaders(HttpServletResponse response) {
|
||||||
|
Map<String, String> headers = new HashMap<>();
|
||||||
|
for (String headerName : response.getHeaderNames()) {
|
||||||
|
headers.put(headerName, response.getHeader(headerName));
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取客户端IP
|
||||||
|
*/
|
||||||
|
private String getClientIp(HttpServletRequest request) {
|
||||||
|
String ip = request.getHeader("X-Forwarded-For");
|
||||||
|
if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
|
||||||
|
ip = request.getHeader("X-Real-IP");
|
||||||
|
}
|
||||||
|
if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
|
||||||
|
ip = request.getHeader("Proxy-Client-IP");
|
||||||
|
}
|
||||||
|
if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
|
||||||
|
ip = request.getHeader("WL-Proxy-Client-IP");
|
||||||
|
}
|
||||||
|
if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
|
||||||
|
ip = request.getRemoteAddr();
|
||||||
|
}
|
||||||
|
// 多个代理时,第一个IP为真实IP
|
||||||
|
if (StrUtil.isNotBlank(ip) && ip.contains(",")) {
|
||||||
|
ip = ip.split(",")[0].trim();
|
||||||
|
}
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取浏览器信息
|
||||||
|
*/
|
||||||
|
private String getBrowserInfo(HttpServletRequest request) {
|
||||||
|
String userAgent = request.getHeader("User-Agent");
|
||||||
|
if (StrUtil.isBlank(userAgent)) {
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
UserAgent ua = UserAgentUtil.parse(userAgent);
|
||||||
|
String browserName = ua.getBrowser().getName();
|
||||||
|
String version = ua.getVersion();
|
||||||
|
// 避免显示 "Unknown null"
|
||||||
|
if (StrUtil.isBlank(version) || "null".equals(version)) {
|
||||||
|
return StrUtil.blankToDefault(browserName, "Unknown");
|
||||||
|
}
|
||||||
|
return browserName + " " + version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取操作系统信息
|
||||||
|
*/
|
||||||
|
private String getOsInfo(HttpServletRequest request) {
|
||||||
|
String userAgent = request.getHeader("User-Agent");
|
||||||
|
if (StrUtil.isBlank(userAgent)) {
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
UserAgent ua = UserAgentUtil.parse(userAgent);
|
||||||
|
String osName = ua.getOs().getName();
|
||||||
|
return StrUtil.blankToDefault(osName, "Unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步保存日志
|
||||||
|
*/
|
||||||
|
@Async
|
||||||
|
public void saveLogAsync(LogRecord logRecord) {
|
||||||
|
try {
|
||||||
|
// 转换为 SysLog 实体
|
||||||
|
com.kexue.skills.entity.SysLog sysLog = new com.kexue.skills.entity.SysLog();
|
||||||
|
|
||||||
|
// 设置基本信息
|
||||||
|
sysLog.setDescription(StrUtil.blankToDefault(logRecord.getDescription(), ""));
|
||||||
|
sysLog.setModule(StrUtil.blankToDefault(logRecord.getModule(), ""));
|
||||||
|
sysLog.setTimeTaken(logRecord.getTimeTaken() != null ? logRecord.getTimeTaken() : 0L);
|
||||||
|
sysLog.setCreateTime(new java.util.Date());
|
||||||
|
sysLog.setUpdateTime(new java.util.Date());
|
||||||
|
sysLog.setDeleteFlag(0);
|
||||||
|
|
||||||
|
// 设置请求信息
|
||||||
|
LogRecord.LogRequest request = logRecord.getRequest();
|
||||||
|
if (request != null) {
|
||||||
|
sysLog.setRequestMethod(StrUtil.blankToDefault(request.getMethod(), "UNKNOWN"));
|
||||||
|
sysLog.setRequestUrl(StrUtil.blankToDefault(request.getUrl(), ""));
|
||||||
|
try {
|
||||||
|
sysLog.setRequestHeaders(objectMapper.writeValueAsString(request.getHeaders()));
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("序列化请求头失败: {}", e.getMessage());
|
||||||
|
sysLog.setRequestHeaders("{}");
|
||||||
|
}
|
||||||
|
sysLog.setRequestBody(StrUtil.blankToDefault(request.getBody(), ""));
|
||||||
|
sysLog.setIp(StrUtil.blankToDefault(request.getIp(), "127.0.0.1"));
|
||||||
|
sysLog.setAddress(StrUtil.blankToDefault(request.getAddress(), ""));
|
||||||
|
sysLog.setBrowser(StrUtil.blankToDefault(request.getBrowser(), "Unknown"));
|
||||||
|
sysLog.setOs(StrUtil.blankToDefault(request.getOs(), "Unknown"));
|
||||||
|
} else {
|
||||||
|
// 设置默认值
|
||||||
|
sysLog.setRequestMethod("UNKNOWN");
|
||||||
|
sysLog.setRequestUrl("");
|
||||||
|
sysLog.setRequestHeaders("{}");
|
||||||
|
sysLog.setRequestBody("");
|
||||||
|
sysLog.setIp("127.0.0.1");
|
||||||
|
sysLog.setAddress("");
|
||||||
|
sysLog.setBrowser("Unknown");
|
||||||
|
sysLog.setOs("Unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置响应信息
|
||||||
|
LogRecord.LogResponse response = logRecord.getResponse();
|
||||||
|
if (response != null) {
|
||||||
|
sysLog.setStatusCode(response.getStatus() != null ? response.getStatus() : 200);
|
||||||
|
try {
|
||||||
|
sysLog.setResponseHeaders(objectMapper.writeValueAsString(response.getHeaders()));
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("序列化响应头失败: {}", e.getMessage());
|
||||||
|
sysLog.setResponseHeaders("{}");
|
||||||
|
}
|
||||||
|
String responseBody = StrUtil.blankToDefault(response.getBody(), "");
|
||||||
|
sysLog.setResponseBody(responseBody);
|
||||||
|
|
||||||
|
// 判断成功/失败状态
|
||||||
|
Integer statusCode = response.getStatus();
|
||||||
|
if (statusCode != null) {
|
||||||
|
sysLog.setStatus(statusCode >= 400 ? 2 : 1);
|
||||||
|
} else {
|
||||||
|
sysLog.setStatus(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析响应体中的错误信息
|
||||||
|
if (StrUtil.isNotBlank(responseBody)) {
|
||||||
|
try {
|
||||||
|
// 尝试解析 JSON 响应体
|
||||||
|
com.fasterxml.jackson.databind.JsonNode jsonNode = objectMapper.readTree(responseBody);
|
||||||
|
|
||||||
|
// 检查是否有错误标识(status、code、success等字段)
|
||||||
|
boolean hasError = false;
|
||||||
|
String errorMsg = "";
|
||||||
|
|
||||||
|
// 方式1: 检查 status 字段(如 {"status":500,"message":"密码不正确"})
|
||||||
|
if (jsonNode.has("status")) {
|
||||||
|
int statusValue = jsonNode.get("status").asInt();
|
||||||
|
if (statusValue >= 400 || statusValue == 0) {
|
||||||
|
hasError = true;
|
||||||
|
if (jsonNode.has("message")) {
|
||||||
|
errorMsg = jsonNode.get("message").asText();
|
||||||
|
} else if (jsonNode.has("msg")) {
|
||||||
|
errorMsg = jsonNode.get("msg").asText();
|
||||||
|
} else if (jsonNode.has("error")) {
|
||||||
|
errorMsg = jsonNode.get("error").asText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 方式2: 检查 code 字段(如 {"code":500,"message":"密码不正确"})
|
||||||
|
if (!hasError && jsonNode.has("code")) {
|
||||||
|
int codeValue = jsonNode.get("code").asInt();
|
||||||
|
if (codeValue != 200 && codeValue != 0) {
|
||||||
|
hasError = true;
|
||||||
|
if (jsonNode.has("message")) {
|
||||||
|
errorMsg = jsonNode.get("message").asText();
|
||||||
|
} else if (jsonNode.has("msg")) {
|
||||||
|
errorMsg = jsonNode.get("msg").asText();
|
||||||
|
} else if (jsonNode.has("error")) {
|
||||||
|
errorMsg = jsonNode.get("error").asText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 方式3: 检查 success 字段(如 {"success":false,"message":"密码不正确"})
|
||||||
|
if (!hasError && jsonNode.has("success")) {
|
||||||
|
boolean success = jsonNode.get("success").asBoolean();
|
||||||
|
if (!success) {
|
||||||
|
hasError = true;
|
||||||
|
if (jsonNode.has("message")) {
|
||||||
|
errorMsg = jsonNode.get("message").asText();
|
||||||
|
} else if (jsonNode.has("msg")) {
|
||||||
|
errorMsg = jsonNode.get("msg").asText();
|
||||||
|
} else if (jsonNode.has("error")) {
|
||||||
|
errorMsg = jsonNode.get("error").asText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果检测到错误,更新状态和错误信息
|
||||||
|
if (hasError) {
|
||||||
|
sysLog.setStatus(2); // 失败
|
||||||
|
sysLog.setErrorMsg(StrUtil.blankToDefault(errorMsg, "业务操作失败"));
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 不是 JSON 格式或解析失败,忽略
|
||||||
|
logger.debug("响应体不是JSON格式或解析失败: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 设置默认值
|
||||||
|
sysLog.setStatusCode(200);
|
||||||
|
sysLog.setResponseHeaders("{}");
|
||||||
|
sysLog.setResponseBody("");
|
||||||
|
sysLog.setStatus(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置错误信息默认值
|
||||||
|
sysLog.setErrorMsg("");
|
||||||
|
|
||||||
|
// TODO: 从 Token 中解析用户ID
|
||||||
|
// 这里需要根据实际的认证框架来实现
|
||||||
|
// sysLog.setCreateUser(userId);
|
||||||
|
sysLog.setCreateUser(null);
|
||||||
|
|
||||||
|
// 插入数据库
|
||||||
|
sysLogMapper.insert(sysLog);
|
||||||
|
|
||||||
|
logger.debug("操作日志保存成功: {}", sysLog.getDescription());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("保存操作日志失败: {}", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
package com.kexue.skills.mapper;
|
||||||
|
|
||||||
|
import com.kexue.skills.entity.AccountFrozen;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账户冻结单Mapper
|
||||||
|
*
|
||||||
|
* @author 系统生成
|
||||||
|
* @since 2026-04-11
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface AccountFrozenMapper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据ID查询冻结单
|
||||||
|
* @param frozenId 冻结单ID
|
||||||
|
* @return 冻结单信息
|
||||||
|
*/
|
||||||
|
AccountFrozen selectByPrimaryKey(String frozenId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插入冻结单
|
||||||
|
* @param accountFrozen 冻结单信息
|
||||||
|
* @return 影响行数
|
||||||
|
*/
|
||||||
|
int insert(AccountFrozen accountFrozen);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新冻结单
|
||||||
|
* @param accountFrozen 冻结单信息
|
||||||
|
* @return 影响行数
|
||||||
|
*/
|
||||||
|
int updateByPrimaryKey(AccountFrozen accountFrozen);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据会话ID查询冻结单
|
||||||
|
* @param sessionId 会话ID
|
||||||
|
* @return 冻结单信息
|
||||||
|
*/
|
||||||
|
AccountFrozen selectBySessionId(String sessionId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户ID和状态查询冻结单
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param status 状态
|
||||||
|
* @return 冻结单列表
|
||||||
|
*/
|
||||||
|
List<AccountFrozen> selectByUserIdAndStatus(@Param("userId") Long userId, @Param("status") String status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询过期的冻结单
|
||||||
|
* @param currentTime 当前时间
|
||||||
|
* @return 冻结单列表
|
||||||
|
*/
|
||||||
|
List<AccountFrozen> selectExpiredFrozen(Date currentTime);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -99,4 +99,5 @@ public interface AccountMapper {
|
||||||
* @return 影响行数
|
* @return 影响行数
|
||||||
*/
|
*/
|
||||||
int deleteById(Long accountId);
|
int deleteById(Long accountId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ package com.kexue.skills.mapper;
|
||||||
|
|
||||||
import com.kexue.skills.entity.AccountTransaction;
|
import com.kexue.skills.entity.AccountTransaction;
|
||||||
import com.kexue.skills.entity.dto.AccountTransactionDto;
|
import com.kexue.skills.entity.dto.AccountTransactionDto;
|
||||||
|
import com.kexue.skills.entity.dto.ConsumptionGroupedDto;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
|
@ -70,4 +71,44 @@ public interface AccountTransactionMapper {
|
||||||
* @return 影响行数
|
* @return 影响行数
|
||||||
*/
|
*/
|
||||||
int deleteById(Long transactionId);
|
int deleteById(Long transactionId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过用户ID查询交易记录
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 交易记录列表
|
||||||
|
*/
|
||||||
|
List<AccountTransaction> queryByUserId(Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取消费原始记录列表(用于内存分组)
|
||||||
|
*
|
||||||
|
* @param queryDto 筛选条件
|
||||||
|
* @return 原始记录列表
|
||||||
|
*/
|
||||||
|
List<AccountTransaction> getConsumptionRawList(AccountTransactionDto queryDto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询充值记录(transactionType=1)
|
||||||
|
*
|
||||||
|
* @param queryDto 筛选条件
|
||||||
|
* @return 查询结果
|
||||||
|
*/
|
||||||
|
List<AccountTransaction> getRechargePageList(AccountTransactionDto queryDto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询赠送记录(transactionType=6)
|
||||||
|
*
|
||||||
|
* @param queryDto 筛选条件
|
||||||
|
* @return 查询结果
|
||||||
|
*/
|
||||||
|
List<AccountTransaction> getGiftPageList(AccountTransactionDto queryDto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询消费记录并按callId分组
|
||||||
|
*
|
||||||
|
* @param queryDto 筛选条件
|
||||||
|
* @return 查询结果
|
||||||
|
*/
|
||||||
|
List<ConsumptionGroupedDto> getConsumptionGroupedPageList(AccountTransactionDto queryDto);
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
package com.kexue.skills.mapper;
|
||||||
|
|
||||||
|
import com.kexue.skills.entity.CmsCategoryTag;
|
||||||
|
import com.kexue.skills.entity.dto.CmsCategoryTagDto;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (CmsCategoryTag)表数据库访问层
|
||||||
|
*
|
||||||
|
* @author 王志维
|
||||||
|
* @since 2025-02-21 23:01:48
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface CmsCategoryTagMapper {
|
||||||
|
/**
|
||||||
|
* 分页查询
|
||||||
|
*
|
||||||
|
* @param queryDto 筛选条件
|
||||||
|
* @return 查询结果
|
||||||
|
*/
|
||||||
|
List<CmsCategoryTag> getPageList(CmsCategoryTagDto queryDto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询列表
|
||||||
|
*
|
||||||
|
* @param queryDto 筛选条件
|
||||||
|
* @return 查询结果
|
||||||
|
*/
|
||||||
|
List<CmsCategoryTag> getList(CmsCategoryTagDto queryDto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过ID查询单条数据
|
||||||
|
*
|
||||||
|
* @param id 主键
|
||||||
|
* @return 实例对象
|
||||||
|
*/
|
||||||
|
CmsCategoryTag queryById(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增数据
|
||||||
|
*
|
||||||
|
* @param cmsCategoryTag 实例对象
|
||||||
|
* @return 影响行数
|
||||||
|
*/
|
||||||
|
int insert(CmsCategoryTag cmsCategoryTag);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新数据
|
||||||
|
*
|
||||||
|
* @param cmsCategoryTag 实例对象
|
||||||
|
* @return 影响行数
|
||||||
|
*/
|
||||||
|
int update(CmsCategoryTag cmsCategoryTag);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过主键逻辑删除
|
||||||
|
*
|
||||||
|
* @param id 主键
|
||||||
|
* @param updateBy 更新人
|
||||||
|
* @return 影响行数
|
||||||
|
*/
|
||||||
|
int logicDeleteById(@Param("id") Long id, @Param("updateBy") String updateBy);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过主键物理删除
|
||||||
|
*
|
||||||
|
* @param id 主键
|
||||||
|
* @return 影响行数
|
||||||
|
*/
|
||||||
|
int deleteById(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据分类ID删除关联
|
||||||
|
*
|
||||||
|
* @param categoryId 分类ID
|
||||||
|
* @param updateBy 更新人
|
||||||
|
* @return 影响行数
|
||||||
|
*/
|
||||||
|
int deleteByCategoryId(@Param("categoryId") Long categoryId, @Param("updateBy") String updateBy);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据标签ID删除关联
|
||||||
|
*
|
||||||
|
* @param tagId 标签ID
|
||||||
|
* @param updateBy 更新人
|
||||||
|
* @return 影响行数
|
||||||
|
*/
|
||||||
|
int deleteByTagId(@Param("tagId") Long tagId, @Param("updateBy") String updateBy);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量插入关联
|
||||||
|
*
|
||||||
|
* @param categoryTagList 关联列表
|
||||||
|
* @return 影响行数
|
||||||
|
*/
|
||||||
|
int batchInsert(List<CmsCategoryTag> categoryTagList);
|
||||||
|
}
|
||||||
|
|
@ -23,6 +23,24 @@ public interface CmsContentMapper {
|
||||||
*/
|
*/
|
||||||
List<CmsContent> getPageList(CmsContentDto queryDto);
|
List<CmsContent> getPageList(CmsContentDto queryDto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 带分页的查询
|
||||||
|
*
|
||||||
|
* @param queryDto 筛选条件
|
||||||
|
* @param offset 偏移量
|
||||||
|
* @param limit 限制数量
|
||||||
|
* @return 查询结果
|
||||||
|
*/
|
||||||
|
List<CmsContent> getPageListWithPagination(@Param("queryDto") CmsContentDto queryDto, @Param("offset") int offset, @Param("limit") int limit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询总记录数
|
||||||
|
*
|
||||||
|
* @param queryDto 筛选条件
|
||||||
|
* @return 总记录数
|
||||||
|
*/
|
||||||
|
int getPageListCount(CmsContentDto queryDto);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询列表
|
* 查询列表
|
||||||
*
|
*
|
||||||
|
|
@ -39,6 +57,14 @@ public interface CmsContentMapper {
|
||||||
*/
|
*/
|
||||||
CmsContent queryById(Long contentId);
|
CmsContent queryById(Long contentId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过ID和语言类型查询单条数据
|
||||||
|
*
|
||||||
|
* @param queryDto 筛选条件
|
||||||
|
* @return 实例对象
|
||||||
|
*/
|
||||||
|
CmsContent queryByIdWithLanguage(CmsContentDto queryDto);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增数据
|
* 新增数据
|
||||||
*
|
*
|
||||||
|
|
@ -47,6 +73,14 @@ public interface CmsContentMapper {
|
||||||
*/
|
*/
|
||||||
int insert(CmsContent cmsContent);
|
int insert(CmsContent cmsContent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量新增数据
|
||||||
|
*
|
||||||
|
* @param cmsContentList 实例对象列表
|
||||||
|
* @return 影响行数
|
||||||
|
*/
|
||||||
|
int batchInsert(@Param("cmsContentList") List<CmsContent> cmsContentList);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新数据
|
* 更新数据
|
||||||
*
|
*
|
||||||
|
|
@ -117,4 +151,85 @@ public interface CmsContentMapper {
|
||||||
* @return 内容ID列表
|
* @return 内容ID列表
|
||||||
*/
|
*/
|
||||||
List<Long> queryRecentCreatedByUserId(@Param("userId") Long userId, @Param("limit") int limit);
|
List<Long> queryRecentCreatedByUserId(@Param("userId") Long userId, @Param("limit") int limit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户历史查看的内容列表
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param offset 偏移量
|
||||||
|
* @param limit 限制数量
|
||||||
|
* @return 查询结果
|
||||||
|
*/
|
||||||
|
List<CmsContent> getPageListByUserHistory(@Param("userId") Long userId, @Param("offset") int offset, @Param("limit") int limit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户历史查看的内容总数
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 总记录数
|
||||||
|
*/
|
||||||
|
int getPageListByUserHistoryCount(@Param("userId") Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户收藏的内容列表
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param offset 偏移量
|
||||||
|
* @param limit 限制数量
|
||||||
|
* @return 查询结果
|
||||||
|
*/
|
||||||
|
List<CmsContent> getPageListByUserFavorites(@Param("userId") Long userId, @Param("offset") int offset, @Param("limit") int limit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户收藏的内容总数
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 总记录数
|
||||||
|
*/
|
||||||
|
int getPageListByUserFavoritesCount(@Param("userId") Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户购买的内容列表
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param offset 偏移量
|
||||||
|
* @param limit 限制数量
|
||||||
|
* @return 查询结果
|
||||||
|
*/
|
||||||
|
List<CmsContent> getPageListByUserPurchases(@Param("userId") Long userId, @Param("offset") int offset, @Param("limit") int limit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户购买的内容总数
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 总记录数
|
||||||
|
*/
|
||||||
|
int getPageListByUserPurchasesCount(@Param("userId") Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户创建的内容列表
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param publishStatus 发布状态
|
||||||
|
* @param offset 偏移量
|
||||||
|
* @param limit 限制数量
|
||||||
|
* @return 查询结果
|
||||||
|
*/
|
||||||
|
List<CmsContent> getPageListByUserCreated(@Param("userId") Long userId, @Param("publishStatus") Integer publishStatus, @Param("offset") int offset, @Param("limit") int limit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户创建的内容总数
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param publishStatus 发布状态
|
||||||
|
* @return 总记录数
|
||||||
|
*/
|
||||||
|
int getPageListByUserCreatedCount(@Param("userId") Long userId, @Param("publishStatus") Integer publishStatus);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空表数据
|
||||||
|
*
|
||||||
|
* @return 影响行数
|
||||||
|
*/
|
||||||
|
int truncateTable();
|
||||||
}
|
}
|
||||||
|
|
@ -70,4 +70,12 @@ public interface CmsTagMapper {
|
||||||
* @return 影响行数
|
* @return 影响行数
|
||||||
*/
|
*/
|
||||||
int deleteById(Long tagId);
|
int deleteById(Long tagId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据分类ID查询标签列表
|
||||||
|
*
|
||||||
|
* @param categoryId 分类ID
|
||||||
|
* @return 标签列表
|
||||||
|
*/
|
||||||
|
List<CmsTag> getTagsByCategoryId(@Param("categoryId") Long categoryId);
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue