Compare commits

..

2 Commits

168 changed files with 2274 additions and 15596 deletions

View File

@ -1,603 +0,0 @@
# 支付功能使用指南
## 📋 概述
本项目已完整接入**微信支付**和**支付宝支付**,支持内容购买、账户充值等支付场景。
---
## 一、配置信息
### 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
View File

@ -1,192 +1,3 @@
# 可学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密钥
## 许可证
本项目仅供内部使用,未经授权不得用于商业用途。
## 联系方式
如有问题,请联系项目维护人员。
agent-skill-backend

View File

@ -1,5 +0,0 @@
-- 为 account_frozen 表添加 question 字段
-- 用于记录用户的问题或需求
ALTER TABLE `account_frozen`
ADD COLUMN `question` text DEFAULT NULL COMMENT '对应回答的问题或需求' AFTER `model_name`;

View File

@ -1,9 +0,0 @@
-- 为 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`);

View File

@ -1,5 +0,0 @@
-- 修改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;

View File

@ -1,46 +0,0 @@
-- 更新 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());

View File

@ -12,9 +12,7 @@ CREATE TABLE `account` (
`account_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`user_name` varchar(50) DEFAULT NULL COMMENT '用户名',
`balance` decimal(10,2) DEFAULT '0.00' COMMENT '账户总余额',
`withdrawable_balance` decimal(10,2) DEFAULT '0.00' COMMENT '可提现余额',
`non_withdrawable_balance` decimal(10,2) DEFAULT '0.00' COMMENT '不可提现余额',
`balance` decimal(10,2) DEFAULT '0.00' COMMENT '账户余额',
`frozen_amount` decimal(10,2) DEFAULT '0.00' COMMENT '冻结金额',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
@ -23,36 +21,13 @@ CREATE TABLE `account` (
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='账户表,记录用户的账户信息';
-- 18. 提现记录表
DROP TABLE IF EXISTS `withdrawal_record`;
CREATE TABLE `withdrawal_record` (
`record_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`user_name` varchar(50) DEFAULT NULL COMMENT '用户名',
`withdrawal_amount` decimal(10,2) NOT NULL COMMENT '提现金额',
`fee_amount` decimal(10,2) NOT NULL COMMENT '手续费',
`actual_amount` decimal(10,2) NOT NULL COMMENT '实际到账金额',
`status` tinyint(1) NOT NULL COMMENT '提现状态1.待处理 2.处理中 3.成功 4.失败',
`withdrawal_no` varchar(50) NOT NULL COMMENT '提现单号',
`bank_name` varchar(100) DEFAULT NULL COMMENT '银行名称',
`bank_account` varchar(100) DEFAULT NULL COMMENT '银行账号',
`bank_cardholder` varchar(50) DEFAULT NULL COMMENT '持卡人姓名',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`delete_flag` tinyint(1) DEFAULT '0' COMMENT '是否删除 0 未删除1已删除',
PRIMARY KEY (`record_id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_withdrawal_no` (`withdrawal_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='提现记录表,记录用户的提现记录';
-- 2. 账户流水表
DROP TABLE IF EXISTS `account_transaction`;
CREATE TABLE `account_transaction` (
`transaction_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`user_name` varchar(50) DEFAULT NULL COMMENT '用户名',
`transaction_type` tinyint(1) NOT NULL COMMENT '交易类型1.充值 2.提现 3.购买内容 4.退款 5.签到奖励 6.赠送 7.其他',
`transaction_type` tinyint(1) NOT NULL COMMENT '交易类型1.充值 2.提现 3.购买内容 4.退款 5.其他',
`amount` decimal(10,2) NOT NULL COMMENT '交易金额',
`before_balance` decimal(10,2) NOT NULL COMMENT '交易前余额',
`after_balance` decimal(10,2) NOT NULL COMMENT '交易后余额',
@ -62,13 +37,6 @@ CREATE TABLE `account_transaction` (
`business_id` bigint(20) DEFAULT NULL COMMENT '关联业务ID',
`business_type` varchar(50) DEFAULT NULL COMMENT '业务类型',
`remark` varchar(255) DEFAULT NULL COMMENT '交易备注',
`is_expense` tinyint(1) NOT NULL COMMENT '是否支出1.是 0.否',
`input_token` int(11) DEFAULT NULL COMMENT '输入token',
`output_token` int(11) DEFAULT NULL COMMENT '输出token',
`total_tokens` int(11) DEFAULT NULL COMMENT '合计tokens',
`model_name` varchar(100) DEFAULT NULL COMMENT '处理的模型名称',
`question` text DEFAULT NULL COMMENT '对应回答的问题或需求',
`income_type` varchar(50) DEFAULT NULL COMMENT '收入类型recharge(充值)、sign_in(签到奖励)',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`create_by` varchar(50) DEFAULT NULL COMMENT '创建人',
@ -80,7 +48,48 @@ CREATE TABLE `account_transaction` (
KEY `idx_business_id` (`business_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='账户流水表,记录用户的账户交易记录';
-- 3. 积分账户表
DROP TABLE IF EXISTS `points_account`;
CREATE TABLE `points_account` (
`account_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`user_name` varchar(50) DEFAULT NULL COMMENT '用户名',
`total_points` int(11) DEFAULT '0' COMMENT '总积分',
`available_points` int(11) DEFAULT '0' COMMENT '可用积分',
`frozen_points` int(11) DEFAULT '0' COMMENT '冻结积分',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`delete_flag` tinyint(1) DEFAULT '0' COMMENT '是否删除 0 未删除1已删除',
PRIMARY KEY (`account_id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='积分账户表,记录用户的积分信息';
-- 4. 积分流水表
DROP TABLE IF EXISTS `points_transaction`;
CREATE TABLE `points_transaction` (
`transaction_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`user_name` varchar(50) DEFAULT NULL COMMENT '用户名',
`transaction_type` tinyint(1) NOT NULL COMMENT '积分变动类型1.获取积分 2.消费积分 3.过期 4.其他',
`points` int(11) NOT NULL COMMENT '变动积分',
`before_points` int(11) NOT NULL COMMENT '变动前积分',
`after_points` int(11) NOT NULL COMMENT '变动后积分',
`status` tinyint(1) NOT NULL COMMENT '交易状态1.成功 2.失败 3.处理中',
`transaction_no` varchar(50) NOT NULL COMMENT '交易单号',
`pay_type` tinyint(1) DEFAULT NULL COMMENT '支付方式1.微信 2.支付宝 3.余额支付',
`business_id` bigint(20) DEFAULT NULL COMMENT '关联业务ID',
`business_type` varchar(50) DEFAULT NULL COMMENT '业务类型',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`create_by` varchar(50) DEFAULT NULL COMMENT '创建人',
`update_by` varchar(50) DEFAULT NULL COMMENT '更新人',
`delete_flag` tinyint(1) DEFAULT '0' COMMENT '是否删除 0 未删除1已删除',
PRIMARY KEY (`transaction_id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_transaction_no` (`transaction_no`),
KEY `idx_business_id` (`business_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='积分流水表,记录用户的积分变动情况';
-- 5. 内容购买记录表
DROP TABLE IF EXISTS `content_purchase`;
@ -299,24 +308,4 @@ CREATE TABLE `sys_log` (
KEY `idx_log_time` (`log_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='系统日志表,记录系统操作日志';
-- 16. 大模型Token价格表
DROP TABLE IF EXISTS `model_price`;
CREATE TABLE `model_price` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`vendor` varchar(64) NOT NULL COMMENT '厂商',
`model_name` varchar(128) NOT NULL COMMENT '模型名称',
`input_price` decimal(10,4) NOT NULL COMMENT '输入价格:元/百万Token',
`output_price` decimal(10,4) NOT NULL COMMENT '输出价格:元/百万Token',
`input_per_cent` bigint NOT NULL COMMENT '1分钱可购买输入Token数',
`output_per_cent` bigint NOT NULL COMMENT '1分钱可购买输出Token数',
`unit` varchar(32) DEFAULT '百万Token' COMMENT '价格单位',
`remark` varchar(255) DEFAULT '' COMMENT '备注',
`created_time` datetime DEFAULT NULL,
`updated_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_vendor` (`vendor`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='大模型Token价格表';
SET FOREIGN_KEY_CHECKS = 1;

113
pom.xml
View File

@ -146,11 +146,11 @@
<version>1.3.2</version>
</dependency>
<!-- Servlet API for jakarta.servlet.http (Spring Boot 3.x uses Jakarta EE 9+) -->
<!-- Servlet API for javax.servlet.http -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
@ -164,44 +164,41 @@
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.6</version>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</exclusion>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis.spring</artifactId>
</exclusion>
<exclusion>
<groupId>log4j-slf4j-impl</groupId>
<artifactId>org.apache.logging.log4j</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
</exclusion>
<exclusion>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 统一的jsqlparser版本 -->
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>4.4</version>
<version>2.1.0</version>
<!-- <exclusions>-->
<!-- <exclusion>-->
<!-- <groupId>org.mybatis</groupId>-->
<!-- <artifactId>mybatis</artifactId>-->
<!-- </exclusion>-->
<!-- <exclusion>-->
<!-- <groupId>org.mybatis</groupId>-->
<!-- <artifactId>mybatis.spring</artifactId>-->
<!-- </exclusion>-->
<!-- <exclusion>-->
<!-- <groupId>log4j-slf4j-impl</groupId>-->
<!-- <artifactId>org.apache.logging.log4j</artifactId>-->
<!-- </exclusion>-->
<!-- <exclusion>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>slf4j-api</artifactId>-->
<!-- </exclusion>-->
<!-- <exclusion>-->
<!-- <groupId>ch.qos.logback</groupId>-->
<!-- <artifactId>logback-classic</artifactId>-->
<!-- </exclusion>-->
<!-- <exclusion>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>log4j-over-slf4j</artifactId>-->
<!-- </exclusion>-->
<!-- <exclusion>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>jul-to-slf4j</artifactId>-->
<!-- </exclusion>-->
<!-- <exclusion>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-logging</artifactId>-->
<!-- </exclusion>-->
<!-- </exclusions>-->
</dependency>
<!-- Sa-Token 核心依赖 -->
@ -260,38 +257,6 @@
<version>1.2.83</version>
</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>
<dependencyManagement>
<dependencies>

View File

@ -1,34 +0,0 @@
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;
}

View File

@ -15,7 +15,7 @@ import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.annotation.Resource;
import javax.annotation.Resource;
/**
* @author 维哥
* @Description 登录认证切面
@ -67,10 +67,11 @@ public class AuthAspect {
// 设置用户上下文
UserContextHolder.setUserName(username);
return joinPoint.proceed();
} catch (Exception e) {
log.error("认证失败:{}", e.getMessage());
throw new BizException(ResultCode.TOKEN_FAILED.getCode(), "无效的token请重新登录");
}
return joinPoint.proceed();
}
}

View File

@ -16,7 +16,6 @@ import org.springframework.web.context.request.ServletRequestAttributes;
import jakarta.servlet.http.HttpServletRequest;
import javax.annotation.Resource;
import java.util.List;
/**
* @author 维哥
@ -86,29 +85,14 @@ public class RoleAspect {
// 获取用户的角色列表
String[] requiredRoles = requireRole.value();
if (requiredRoles != null && requiredRoles.length > 0) {
// 获取当前用户的角色列表
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]);
}
// 使用Sa-Token检查角色权限
cn.dev33.satoken.stp.StpUtil.checkRoleAnd(requiredRoles);
}
// 设置用户上下文
UserContextHolder.setUserName(username);
return joinPoint.proceed();
} catch (cn.dev33.satoken.exception.NotLoginException e) {
log.error("未登录:{}", e.getMessage());
throw new BizException(ResultCode.TOKEN_FAILED.getCode(), "请先登录认证后操作");
@ -119,6 +103,5 @@ public class RoleAspect {
log.error("权限验证失败:{}", e.getMessage());
throw new BizException(ResultCode.PERMISSION_DENIED.getCode(), "权限验证失败");
}
return joinPoint.proceed();
}
}

View File

@ -44,15 +44,6 @@ public class CommonResult<T> {
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);
}
/**
* 成功返回结果
*
@ -67,11 +58,11 @@ public class CommonResult<T> {
/**
* 成功返回结果
*
* @param code 获取的数据
* @param errorCode 获取的数据
* @param message 提示信息
*/
public static <T> CommonResult<T> success(Long code, String message ) {
return new CommonResult<T>(code, message,null);
public static <T> CommonResult<T> success(IErrorCode errorCode, String message ) {
return new CommonResult<T>(errorCode.getCode(), message,null);
}
/**

View File

@ -1,170 +0,0 @@
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;
}
}
}

View File

@ -92,19 +92,7 @@ public enum ResultCode implements IErrorCode {
/**
* 统一异常返回码
* */
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, "账户余额不足");
EXCEPTION_HANDLER(-2500,"服务异常,请联系管理员");
private final long code;
private final String message;

View File

@ -55,49 +55,4 @@ public class HttpUtil {
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();
}
}

View File

@ -2,119 +2,22 @@ package com.kexue.skills.common.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.UUID;
/**
* @description: ID管理类
**/
@Component
public class IDUtils {
public static final Logger logger = LoggerFactory.getLogger(IDUtils.class);
@Value("${snowflake.workid:1}")
private long workId;
private static SnowflakeIdGenerator snowflakeIdGenerator;
public static String getUUID(){
return UUID.randomUUID().toString().replace("-","");
}
@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());
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

View File

@ -1,26 +0,0 @@
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;
}

View File

@ -1,73 +0,0 @@
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;
}
}

View File

@ -1,26 +0,0 @@
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();
}
}

View File

@ -1,40 +0,0 @@
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/**" // 排除验证码接口
);
}
}

View File

@ -1,48 +0,0 @@
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();
}
}

View File

@ -1,66 +0,0 @@
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;
}
}

View File

@ -16,16 +16,16 @@ import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonConfig {
@Value("${common.redis.host}")
@Value("${spring.redis.host}")
private String host;
@Value("${common.redis.port}")
@Value("${spring.redis.port}")
private int port;
@Value("${common.redis.password}")
@Value("${spring.redis.password}")
private String password;
@Value("${common.redis.database}")
@Value("${spring.redis.database}")
private int database;
@Bean(destroyMethod = "shutdown")
@ -35,12 +35,16 @@ public class RedissonConfig {
// 单节点模式
config.useSingleServer()
.setAddress("redis://" + host + ":" + port)
.setPassword(password)
//.setPassword(password)
.setDatabase(database)
.setConnectionMinimumIdleSize(5)
.setConnectionPoolSize(20)
.setTimeout(10000);
// 处理密码如果有密码则设置否则不设置
if (password != null && !password.isEmpty()) {
config.useSingleServer().setPassword(password);
}
return Redisson.create(config);
}

View File

@ -24,9 +24,9 @@ public class SaTokenConfig implements WebMvcConfigurer {
// 拦截所有请求除了登录注册文档等不需要认证的接口
SaRouter
// 放行登录接口
.match("/api/login/**").stop()
.match("/login/**").stop()
// 放行注册接口
.match("/api/register/**").stop()
.match("/register/**").stop()
// 放行Swagger文档
.match("/doc.html").stop()
.match("/swagger-ui/**").stop()

View File

@ -1,48 +0,0 @@
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/";
}
}

View File

@ -1,31 +1,17 @@
package com.kexue.skills.controller;
import cn.dev33.satoken.stp.StpUtil;
import com.github.pagehelper.PageInfo;
import com.kexue.skills.annotation.Log;
import com.kexue.skills.annotation.RequireAuth;
import com.kexue.skills.annotation.RequireRole;
import com.kexue.skills.common.CommonResult;
import com.kexue.skills.common.Const;
import com.kexue.skills.entity.Account;
import com.kexue.skills.entity.dto.AccountDto;
import com.kexue.skills.entity.dto.TokenConsumptionDto;
import com.kexue.skills.entity.dto.GiftBalanceDto;
import com.kexue.skills.entity.dto.AccountTransactionDto;
import com.kexue.skills.entity.dto.ConsumptionGroupedDto;
import com.kexue.skills.service.AccountService;
import com.kexue.skills.service.SysUserService;
import com.kexue.skills.entity.SysUser;
import com.kexue.skills.exception.BizException;
import com.kexue.skills.common.CacheManager;
import java.math.BigDecimal;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import java.util.List;
/**
@ -35,7 +21,6 @@ import java.util.List;
* @author 王志维
* @since 2025-02-21 23:01:48
*/
@Log(module = "账户管理")
@Tag(name = "账户管理 api")
@RestController
@RequestMapping("/api/account")
@ -46,9 +31,6 @@ public class AccountController {
@Resource
private AccountService accountService;
@Resource
private SysUserService sysUserService;
/**
* 分页查询
*
@ -81,7 +63,7 @@ public class AccountController {
* @param accountId 主键
* @return 单条数据
*/
@Operation(summary = "通过查询账户", description = "通过ID查询账户")
@Operation(summary = "通过ID查询账户", description = "通过ID查询账户")
@PostMapping("/queryById/{accountId}")
@RequireAuth
public CommonResult<Account> queryById(@Parameter(description = "账户ID") @PathVariable("accountId") Long accountId) {
@ -89,123 +71,15 @@ public class AccountController {
}
/**
* 通过当前登录用户ID查询账户
*
* @return 单条数据
*/
@Operation(summary = "通过当前登录用户查询账户", description = "通过当前登录用户查询账户")
@PostMapping("/currentAccount")
@RequireAuth
public CommonResult<Account> currentAccount() {
Long userId = Long.parseLong(StpUtil.getLoginId().toString());
return CommonResult.success(this.accountService.queryByUserId(userId));
}
/**
* 充值账户
* 通过用户ID查询单条数据
*
* @param userId 用户ID
* @param amount 充值金额
* @param payType 支付方式1.微信 2.支付宝
* @return 充值结果
* @return 单条数据
*/
@Operation(summary = "充值账户", description = "充值账户")
@PostMapping("/recharge")
@Operation(summary = "通过用户ID查询账户", description = "通过用户ID查询账户")
@PostMapping("/queryByUserId/{userId}")
@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("充值请求已提交,请通过支付接口完成支付");
public CommonResult<Account> queryByUserId(@Parameter(description = "用户ID") @PathVariable("userId") Long userId) {
return CommonResult.success(this.accountService.queryByUserId(userId));
}
/**
* 减少账户余额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));
}
}

View File

@ -1,73 +0,0 @@
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);
}
}

View File

@ -7,8 +7,6 @@ import com.kexue.skills.entity.CmsCategory;
import com.kexue.skills.entity.base.IdDto;
import com.kexue.skills.entity.dto.CmsCategoryDto;
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.tags.Tag;
import org.springframework.web.bind.annotation.*;
@ -31,18 +29,6 @@ public class CmsCategoryController {
*/
@Resource
private CmsCategoryService cmsCategoryService;
/**
* 标签服务对象
*/
@Resource
private CmsTagService cmsTagService;
/**
* 分类标签关联服务对象
*/
@Resource
private CmsCategoryTagService cmsCategoryTagService;
/**
* 分页查询
*
@ -140,25 +126,4 @@ public class CmsCategoryController {
public CommonResult<java.util.Map<Long, String>> 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));
}
}
}

View File

@ -1,23 +1,17 @@
package com.kexue.skills.controller;
import com.github.pagehelper.PageInfo;
import com.kexue.skills.annotation.Log;
import com.kexue.skills.annotation.RequireAuth;
import com.kexue.skills.common.Assert;
import com.kexue.skills.common.CommonResult;
import com.kexue.skills.entity.CmsContent;
import com.kexue.skills.entity.base.IdDto;
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 io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.IOException;
/**
* (CmsContent)表控制层
@ -27,7 +21,7 @@ import java.io.IOException;
*/
@RestController
@RequestMapping("api/cmsContent")
@Tag(name = "skillskills管理 Api")
@Tag(name = "内容skills管理 Api")
@CrossOrigin(origins = "*")
public class CmsContentController {
/**
@ -66,7 +60,7 @@ public class CmsContentController {
* @return 单条数据
*/
@PostMapping("queryById/{contentId}")
@Operation(summary = "通过ID查询skill", description = "通过ID查询skill")
@Operation(summary = "通过ID查询内容", description = "通过ID查询内容")
public CommonResult<CmsContent> queryById(@PathVariable("contentId") Long contentId) {
// 增加阅读量
cmsContentService.increaseViewCount(contentId);
@ -80,7 +74,7 @@ public class CmsContentController {
* @return 新增结果
*/
@PostMapping("/insert")
@Operation(summary = "新增skill", description = "新增skill")
@Operation(summary = "新增内容", description = "新增内容")
@RequireAuth
public CommonResult<CmsContent> insert(@RequestBody CmsContent cmsContent) {
return CommonResult.success(cmsContentService.insert(cmsContent));
@ -93,7 +87,7 @@ public class CmsContentController {
* @return 编辑结果
*/
@PostMapping("/update")
@Operation(summary = "更新skill", description = "更新skill")
@Operation(summary = "更新内容", description = "更新内容")
@RequireAuth
public CommonResult<CmsContent> update(@RequestBody CmsContent cmsContent) {
return CommonResult.success(cmsContentService.update(cmsContent));
@ -102,7 +96,7 @@ public class CmsContentController {
/**
* 更新审核状态
*
* @param contentId skillID
* @param contentId 内容ID
* @param auditStatus 审核状态
* @param reviewerId 审核人ID
* @param reviewerName 审核人名称
@ -123,7 +117,7 @@ public class CmsContentController {
/**
* 更新发布状态
*
* @param contentId skillID
* @param contentId 内容ID
* @param publishStatus 发布状态
* @param publishTime 发布时间
* @param updateBy 更新人
@ -142,7 +136,7 @@ public class CmsContentController {
/**
* 增加阅读量
*
* @param contentId skillID
* @param contentId 内容ID
* @return 增加结果
*/
@PostMapping("/increaseViewCount/{contentId}")
@ -158,7 +152,7 @@ public class CmsContentController {
* @return 删除数据
*/
@PostMapping("/logicDeleteById")
@Operation(summary = "逻辑删除skill", description = "逻辑删除skill")
@Operation(summary = "逻辑删除内容", description = "逻辑删除内容")
@RequireAuth
public CommonResult<Boolean> logicDeleteById(@RequestBody IdDto idDto) {
return CommonResult.success(cmsContentService.logicDeleteById(idDto.getId(), "admin") > 0);
@ -171,208 +165,9 @@ public class CmsContentController {
* @return 删除数据
*/
@PostMapping("deleteById/{contentId}")
@Operation(summary = "物理删除skill", description = "物理删除skill")
@Operation(summary = "物理删除内容", description = "物理删除内容")
@RequireAuth
public CommonResult<Boolean> deleteById(@PathVariable("contentId") Long contentId) {
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());
}
}
}

View File

@ -1,7 +1,6 @@
package com.kexue.skills.controller;
import com.kexue.skills.common.CommonResult;
import com.kexue.skills.config.UploadConfig;
import com.kexue.skills.entity.request.UploadResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@ -9,7 +8,6 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@ -23,13 +21,13 @@ import java.nio.file.StandardCopyOption;
*/
@RestController
@RequestMapping("api/common")
@Tag(name = "Common Api")
@Tag(name = "公共 Api")
@CrossOrigin(origins = "*")
@Slf4j
public class CommonController {
@Resource
private UploadConfig uploadConfig;
private static final String FILE_UPLOAD_DIR = "/data/service/hyxp-portal/upload/file/";
private static final String IMG_UPLOAD_DIR = "/data/service/hyxp-portal/upload/images/";
/**
@ -51,7 +49,7 @@ public class CommonController {
try {
// 创建上传目录如果不存在
Path uploadPath = Paths.get(uploadConfig.getFileUploadDir());
Path uploadPath = Paths.get(FILE_UPLOAD_DIR);
if (!Files.exists(uploadPath)) {
Files.createDirectories(uploadPath);
}
@ -64,7 +62,7 @@ public class CommonController {
UploadResponse uploadResponse = new UploadResponse();
uploadResponse.setFileName(file.getOriginalFilename());
uploadResponse.setFileUrl(uploadConfig.getFileUrlPrefix() + file.getOriginalFilename());
uploadResponse.setFileUrl("/upload/file/" + file.getOriginalFilename());
return CommonResult.success(uploadResponse);
} catch (IOException e) {
e.printStackTrace();
@ -96,7 +94,7 @@ public class CommonController {
try {
// 创建上传目录如果不存在
Path uploadPath = Paths.get(uploadConfig.getImgUploadDir());
Path uploadPath = Paths.get(IMG_UPLOAD_DIR);
if (!Files.exists(uploadPath)) {
Files.createDirectories(uploadPath);
}
@ -109,7 +107,7 @@ public class CommonController {
UploadResponse uploadResponse = new UploadResponse();
uploadResponse.setFileName(image.getOriginalFilename());
uploadResponse.setFileUrl(uploadConfig.getImgUrlPrefix() + image.getOriginalFilename());
uploadResponse.setFileUrl("/upload/images/" + image.getOriginalFilename());
return CommonResult.success(uploadResponse);
} catch (IOException e) {
e.printStackTrace();

View File

@ -1,6 +1,5 @@
package com.kexue.skills.controller;
import cn.dev33.satoken.stp.StpUtil;
import com.github.pagehelper.PageInfo;
import com.kexue.skills.annotation.RequireAuth;
import com.kexue.skills.common.CommonResult;
@ -74,6 +73,7 @@ public class ContentPurchaseController {
/**
* 购买内容
*
* @param userId 用户ID
* @param contentId 内容ID
* @param payType 支付方式1.余额支付 2.积分支付
* @return 购买结果
@ -82,15 +82,16 @@ public class ContentPurchaseController {
@PostMapping("/purchase")
@RequireAuth
public CommonResult<ContentPurchase> purchaseContent(
@Parameter(description = "用户ID") @RequestParam("userId") Long userId,
@Parameter(description = "内容ID") @RequestParam("contentId") Long contentId,
@Parameter(description = "支付方式1.余额支付 2.积分支付") @RequestParam("payType") Integer payType) {
Long userId = Long.parseLong(StpUtil.getLoginId().toString());
return CommonResult.success(this.contentPurchaseService.purchaseContent(userId, contentId, payType));
}
/**
* 检查用户是否有权限访问内容
*
* @param userId 用户ID
* @param contentId 内容ID
* @return 是否有权限
*/
@ -98,8 +99,8 @@ public class ContentPurchaseController {
@PostMapping("/checkPermission")
@RequireAuth
public CommonResult<Boolean> checkAccessPermission(
@Parameter(description = "用户ID") @RequestParam("userId") Long userId,
@Parameter(description = "内容ID") @RequestParam("contentId") Long contentId) {
Long userId = Long.parseLong(StpUtil.getLoginId().toString());
boolean hasPermission = this.contentPurchaseService.checkAccessPermission(userId, contentId);
return CommonResult.success(hasPermission);
}

View File

@ -1,8 +1,6 @@
package com.kexue.skills.controller;
import com.kexue.skills.annotation.Log;
import com.kexue.skills.annotation.PreventDuplicateSubmission;
import com.kexue.skills.common.CacheManager;
import com.kexue.skills.common.CommonResult;
import com.kexue.skills.entity.request.LoginDto;
import com.kexue.skills.entity.request.LoginUserDto;
@ -15,15 +13,12 @@ import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import org.redisson.api.RedissonClient;
import static cn.dev33.satoken.SaManager.log;
/**
* (SysUser)表控制层
*
* @author 王志维
* @since 2024-04-13 01:25:22
*/
@Log(module = "登录认证")
@RestController
@RequestMapping("api/login")
@CrossOrigin(origins = "*")
@ -63,27 +58,20 @@ public class LoginController {
@Operation(summary = "用户登出", description = "用户登出")
public CommonResult<String> logout() {
try {
// 获取当前用户的 token
// 获取当前用户的token
String token = cn.dev33.satoken.stp.StpUtil.getTokenValue();
// Redis 中删除用户信息
if (token != null && !token.isEmpty()) {
redissonClient.getBucket("loginUser:" + token).delete();
// 从缓存中移除 token 映射
String username = CacheManager.getUsernameFromToken(token);
if (username != null) {
CacheManager.removeTokenFromCache(username);
}
}
// 使用 Sa-Token 登出这会清除 Sa-Token 内部的会话信息
// 使用Sa-Token登出
cn.dev33.satoken.stp.StpUtil.logout();
log.info("用户登出成功token 已清理");
// 从Redis中删除用户信息
if (token != null && !token.isEmpty()) {
redissonClient.getBucket("loginUser:" + token).delete();
}
return CommonResult.success("登出成功");
} catch (Exception e) {
log.error("登出异常:{}", e.getMessage());
// 如果获取 token 失败仍然执行登出操作
// 如果获取token失败仍然执行登出操作
cn.dev33.satoken.stp.StpUtil.logout();
return CommonResult.success("登出成功");
}

View File

@ -1,113 +0,0 @@
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));
}
}

View File

@ -1,121 +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.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);
}
}

View File

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

View File

@ -0,0 +1,85 @@
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));
}
}

View File

@ -1,41 +0,0 @@
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);
}
}

View File

@ -1,19 +1,19 @@
package com.kexue.skills.controller;
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.request.SkillRequest;
import com.kexue.skills.entity.response.SkillResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
/**
* 技能生成控制器
@ -21,6 +21,7 @@ import javax.annotation.Resource;
* @author 维哥
* @since 2026-01-28
*/
@Slf4j
@RestController
@RequestMapping("api/skillGen")
@Tag(name = "技能生成 Api")
@ -31,23 +32,65 @@ public class SkillGenController {
private com.kexue.skills.service.SkillGenService skillGenService;
/**
* 生成技能
* 预生成skill,产生相关的描述信息
*
* @param request 生成请求
* @return 生成结果
*/
@PostMapping("/preGenerate")
@Operation(summary = "预生成技能", description = "生成技能")
public CommonResult<SkillResponse> preGenerate(@RequestBody SkillPreGenRequest request) {
return CommonResult.success(skillGenService.preGenerateV2(request));
@Operation(summary = "预生成技能", description = "预生成技能")
public CommonResult<SkillResponse> preGenerate(com.kexue.skills.entity.request.SkillGenRequest request,
@RequestPart(value = "file", required = false) MultipartFile file) {
try {
// 如果上传了文件读取文件内容
if (file != null && !file.isEmpty()) {
String fileContent = readTextFile(file);
// 将文件内容设置到请求对象中需要修改 SkillGenRequest
// request.setFileContent(fileContent);
// request.setFileName(file.getOriginalFilename());
// request.setFileSize(file.getSize());
// request.setFileType(file.getContentType());
log.info("文件上传成功: {}, 大小: {} bytes, 类型: {}",
file.getOriginalFilename(), file.getSize(), file.getContentType());
return CommonResult.success(skillGenService.preGenerateSkill(request,fileContent));
}
return CommonResult.success(skillGenService.preGenerateSkill(request,null));
} catch (Exception e) {
log.error("处理文件上传失败", e);
return CommonResult.failed("文件处理失败: " + e.getMessage());
}
}
/**
* 读取文本文件内容为字符串
* @param file 上传的文件
* @return 文件内容字符串
*/
private String readTextFile(MultipartFile file) throws IOException {
// 使用 Apache Commons IO
try (InputStream inputStream = file.getInputStream()) {
return IOUtils.toString(inputStream, StandardCharsets.UTF_8);
}
}
/**
* 生成技能
*
* @param
* @return 分析结果
*/
@PostMapping("/generate")
@Operation(summary = "生成技能", description = "生成技能")
public CommonResult<CmsContent> generate(@RequestBody SkillGenRequest request) {
return CommonResult.success(skillGenService.generate(request));
public CommonResult<SkillResponse> generate(@RequestBody SkillRequest request) {
Integer skillId=request.getSkillId();
return CommonResult.success(skillGenService.generateSkill(skillId));
}
/**
* 分析技能
*
@ -59,93 +102,4 @@ public class SkillGenController {
public CommonResult<String> analyze(@RequestBody com.kexue.skills.entity.request.SkillAnalyzeRequest 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());
}
}
}

View File

@ -3,7 +3,9 @@ package com.kexue.skills.controller;
import com.kexue.skills.annotation.RequireAuth;
import com.kexue.skills.entity.SysUser;
import com.kexue.skills.entity.dto.SysUserDto;
import com.kexue.skills.entity.request.*;
import com.kexue.skills.entity.request.ResetPasswordDto;
import com.kexue.skills.entity.request.ResetPwdDto;
import com.kexue.skills.entity.request.AdminResetPasswordDto;
import com.kexue.skills.exception.BizException;
import com.kexue.skills.service.SysUserService;
import org.springframework.web.bind.annotation.*;
@ -15,12 +17,8 @@ import com.kexue.skills.common.CacheManager;
import com.github.pagehelper.PageInfo;
import com.kexue.skills.common.CommonResult;
import com.kexue.skills.entity.base.IdDto;
import com.kexue.skills.entity.request.LoginUserDto;
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)表控制层
@ -88,7 +86,7 @@ public class SysUserController {
*/
@PostMapping("/update")
@Operation(summary = "更新用户", description = "更新用户")
public CommonResult<SysUser> update(@RequestBody SysUserUpdateDto SysUser) {
public CommonResult<SysUser> update(@RequestBody SysUser SysUser) {
return CommonResult.success(sysUserService.update(SysUser));
}
@ -113,8 +111,25 @@ public class SysUserController {
@PostMapping("/resetPassword")
@Operation(summary = "管理员帮助用户重置密码", description = "管理员帮助用户重置密码")
@RequireAuth
public CommonResult<Boolean> resetPassword(@RequestBody ResetPwdDto resetPasswordDto) {
boolean result = sysUserService.resetPassword(resetPasswordDto);
public CommonResult<Boolean> resetPasswordByAdmin(@RequestBody ResetPasswordDto resetPasswordDto, HttpServletRequest request) {
// 从请求头中获取token
String token = request.getHeader("Authorization");
if (token == null || token.isEmpty()) {
throw new BizException("请先登录认证后操作");
}
// 从缓存中获取当前登录用户
String username = CacheManager.getUsernameFromToken(token);
if (username == null) {
throw new BizException("无效的token请重新登录");
}
SysUser adminUser = sysUserService.getByUsername(username);
if (adminUser == null) {
throw new BizException("管理员不存在");
}
boolean result = sysUserService.resetPasswordByAdmin(resetPasswordDto);
return CommonResult.success(result);
}
@ -200,13 +215,6 @@ public class SysUserController {
throw new BizException("请先登录认证后操作");
}
// 使用Sa-Token检查token是否有效
try {
cn.dev33.satoken.stp.StpUtil.checkLogin();
} catch (Exception e) {
throw new BizException("无效的token请重新登录");
}
// 从Redis缓存中获取LoginUser对象
String loginUserJson = (String)redissonClient.getBucket("loginUser:" + token).get();
if (loginUserJson == null || loginUserJson.isEmpty()) {
@ -227,44 +235,4 @@ public class SysUserController {
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);
}
}

View File

@ -1,129 +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.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);
}
}

View File

@ -29,15 +29,9 @@ public class Account extends BaseEntity implements Serializable {
@Schema(description ="用户名")
private String userName;
@Schema(description ="账户余额")
@Schema(description ="账户余额")
private BigDecimal balance;
@Schema(description ="可提现余额")
private BigDecimal withdrawableBalance;
@Schema(description ="不可提现余额")
private BigDecimal nonWithdrawableBalance;
@Schema(description ="冻结金额")
private BigDecimal frozenAmount;

View File

@ -1,80 +0,0 @@
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;
}

View File

@ -29,7 +29,7 @@ public class AccountTransaction extends BaseEntity implements Serializable {
@Schema(description ="用户名")
private String userName;
@Schema(description ="交易类型1.充值 2.提现 3.购买内容 4.退款 5.签到奖励 6.赠送 7.其他")
@Schema(description ="交易类型1.充值 2.提现 3.购买内容 4.退款 5.其他")
private Integer transactionType;
@Schema(description ="交易金额")
@ -56,33 +56,9 @@ public class AccountTransaction extends BaseEntity implements Serializable {
@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 ="对应回答的问题或需求")
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;

View File

@ -27,12 +27,12 @@ public class CmsContent extends BaseEntity implements Serializable {
@Schema(description ="标题")
private String title;
@Schema(description ="英文标题")
private String titleEn;
@Schema(description ="是否是官方0否1是")
private Boolean isOfficial;
@Schema(description ="分类ID列表逗号分隔")
private String categoryIds;
@Schema(description ="图标")
private String icon;
@ -45,21 +45,6 @@ public class CmsContent extends BaseEntity implements Serializable {
@Schema(description ="内容摘要")
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 ="分享数量")
private Integer shareCount;
@ -79,9 +64,6 @@ public class CmsContent extends BaseEntity implements Serializable {
@Schema(description ="内容详情")
private String content;
@Schema(description ="英文内容")
private String contentEn;
@Schema(description ="封面图片")
private String coverImage;
@ -97,14 +79,14 @@ public class CmsContent extends BaseEntity implements Serializable {
@Schema(description ="审核人名称")
private String reviewerName;
@Schema(description ="审核状态1未发布2待审核3审核通过4审核未通过")
@Schema(description ="审核状态1草稿2待审核3审核通过4审核拒绝")
private Integer auditStatus;
@Schema(description ="审核意见")
private String auditComment;
@Schema(description ="发布状态1未发布2已发布3已下架--> 公有还是私有1私有2公有")
private Integer publishStatus;
@Schema(description ="发布状态1未发布2已发布3已下架")
private Integer publishStatus;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Schema(description ="发布时间")
@ -147,10 +129,38 @@ public class CmsContent extends BaseEntity implements Serializable {
@Schema(description ="副标题")
private String subtitle;
@Schema(description ="来源")
private String origin;
@Schema(description ="父分类ID")
private Long parentCategoryId;
@Schema(description ="标签")
private String tags;
@Schema(description ="用户上传的文件内容")
private String userUpload;
@Schema(description ="标签列表,逗号分隔")
private String tagIds;
@Schema(description ="技能的文件地址")
private String skillPath;
@Schema(description ="技能文件内容")
private String skillContent;
// 用于接收前端发送的分类ID数组
@JsonProperty("categoryIds")
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;
}
}

View File

@ -33,9 +33,6 @@ public class CmsTag extends BaseEntity implements Serializable {
@Schema(description ="状态1启用2禁用")
private Integer status;
@Schema(description ="标签图标")
private String icon;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Schema(description ="创建时间")
private Date createTime;

View File

@ -1,120 +0,0 @@
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;
/**
* 请求方法GETPOST等
*/
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;
}
}

View File

@ -1,67 +0,0 @@
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;
}

View File

@ -1,43 +0,0 @@
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;
}

View File

@ -44,22 +44,16 @@ public class PaymentOrder extends BaseEntity implements Serializable {
@Schema(description ="支付渠道订单号")
private String channelOrderNo;
@Schema(description ="微信二维码URL")
private String codeUrl;
@Schema(description ="支付宝二维码HTML内容")
private String qrCode;
@Schema(description ="商品名称")
private String productName;
@Schema(description ="商品描述")
private String productDesc;
@Schema(description ="关联业务ID比如packageId套餐ID")
@Schema(description ="关联业务ID")
private Long businessId;
@Schema(description ="业务类型recharge,purchase_content")
@Schema(description ="业务类型")
private String businessType;
@Schema(description ="支付回调地址")

View File

@ -9,23 +9,33 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* (CmsCategoryTag)实体类
* (PointsAccount)实体类
* 积分账户表记录用户的积分信息
*
* @author 王志维
* @since 2025-02-21 23:01:48
*/
@Data
public class CmsCategoryTag extends BaseEntity implements Serializable {
public class PointsAccount extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description ="主键ID")
private Long id;
private Long accountId;
@Schema(description ="分类ID")
private Long categoryId;
@Schema(description ="用户ID")
private Long userId;
@Schema(description ="标签ID")
private Long tagId;
@Schema(description ="用户名")
private String userName;
@Schema(description ="总积分")
private Integer totalPoints;
@Schema(description ="可用积分")
private Integer availablePoints;
@Schema(description ="冻结积分")
private Integer frozenPoints;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Schema(description ="创建时间")
@ -35,13 +45,13 @@ public class CmsCategoryTag extends BaseEntity implements Serializable {
@Schema(description ="更新时间")
private Date updateTime;
@Schema(description ="是否删除 0 未删除1已删除")
private Integer deleteFlag;
@Schema(description ="创建人")
private String createBy;
@Schema(description ="更新人")
private String updateBy;
@Schema(description ="是否删除 0 未删除1已删除")
private Integer deleteFlag;
}

View File

@ -0,0 +1,78 @@
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;
}

View File

@ -22,59 +22,29 @@ public class SysLog extends BaseEntity implements Serializable {
@Schema(description ="主键ID")
private Long logId;
@Schema(description ="链路ID")
private String traceId;
@Schema(description ="用户ID")
private String userId;
@Schema(description ="日志描述")
private String description;
@Schema(description ="用户名称")
private String userName;
@Schema(description ="所属模块")
private String module;
@Schema(description ="日志类型")
private String logType;
@Schema(description ="请求URL")
private String requestUrl;
@Schema(description ="日志类容")
private String logContent;
@Schema(description ="请求方式")
private String requestMethod;
@Schema(description ="服务端IP")
private String serverIp;
@Schema(description ="请求头")
private String requestHeaders;
@Schema(description ="客户端IP")
private String clientIp;
@Schema(description ="请求体")
private String requestBody;
@Schema(description ="yyyyMMddHHmmss")
private String logTime;
@Schema(description ="状态码")
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;
@Schema(description ="备注")
private String note;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Schema(description ="创建时间")

View File

@ -63,19 +63,4 @@ public class SysUser extends BaseEntity implements Serializable {
@Schema(description ="更新人")
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";
}

View File

@ -1,75 +0,0 @@
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;
}

View File

@ -1,45 +0,0 @@
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;
}

View File

@ -1,35 +0,0 @@
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;
}

View File

@ -3,8 +3,6 @@ package com.kexue.skills.entity.dto;
import com.kexue.skills.entity.base.BaseQueryDto;
import lombok.Data;
import java.util.Date;
/**
* (AccountTransaction)查询DTO类
*
@ -34,8 +32,4 @@ public class AccountTransactionDto extends BaseQueryDto {
private Integer deleteFlag;
private Date createTimeStart;
private Date createTimeEnd;
}

View File

@ -1,28 +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;
/**
* (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;
}

View File

@ -20,6 +20,12 @@ public class CmsContentDto extends BaseQueryDto {
private Integer contentType;
private String categoryIds;
private Long categoryId;
private List<String> categoryIdList;
private Boolean isOfficial;
private Integer shareCount;
@ -38,36 +44,7 @@ public class CmsContentDto extends BaseQueryDto {
private Long parentCategoryId;
private Long tagId;
/**
* 标签 ID 列表用于批量查询
*/
private List<Long> tagIdList;
/**
* 语言类型0 中文1 英文
*/
private Integer languageType;
/**
* 搜索关键字同时搜索 titledescriptiontags
*/
private String keyword;
/**
* 来源
*/
private String origin;
/**
* 标签
*/
private String tags;
/**
* 图标
*/
private String icon;
private String userLoad;
private String tagIds;
}

View File

@ -20,8 +20,6 @@ public class CmsTagDto extends BaseQueryDto {
private Integer status;
private String icon;
private Integer deleteFlag;
}

View File

@ -1,101 +0,0 @@
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;
}

View File

@ -1,66 +0,0 @@
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;
}
}

View File

@ -1,53 +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.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;
}

View File

@ -1,77 +0,0 @@
package com.kexue.skills.entity.dto;
import com.kexue.skills.entity.PaymentOrder;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
/**
* 订单状态DTO
*/
@Data
public class OrderStatusDto {
@Schema(description = "订单ID")
private Long orderId;
@Schema(description = "订单号")
private String orderNo;
@Schema(description = "状态码")
private Integer status;
@Schema(description = "状态文本")
private String statusText;
@Schema(description = "支付金额")
private BigDecimal amount;
@Schema(description = "支付方式1.微信 2.支付宝")
private Integer payType;
@Schema(description = "创建时间")
private Date createTime;
@Schema(description = "支付时间")
private Date payTime;
@Schema(description = "渠道订单号")
private String channelOrderNo;
/**
* 从PaymentOrder构建OrderStatusDto
* @param order 支付订单
* @return 订单状态DTO
*/
public static OrderStatusDto fromPaymentOrder(PaymentOrder order) {
OrderStatusDto dto = new OrderStatusDto();
dto.setOrderId(order.getOrderId());
dto.setOrderNo(order.getOrderNo());
dto.setStatus(order.getStatus());
dto.setStatusText(getStatusText(order.getStatus()));
dto.setAmount(order.getAmount());
dto.setPayType(order.getPayType());
dto.setCreateTime(order.getCreateTime());
dto.setPayTime(order.getPayTime());
dto.setChannelOrderNo(order.getChannelOrderNo());
return dto;
}
/**
* 获取状态文本
* @param status 状态码
* @return 状态文本
*/
private static String getStatusText(Integer status) {
switch (status) {
case 1: return "待支付";
case 2: return "已支付";
case 3: return "支付失败";
case 4: return "已取消";
case 5: return "已退款";
default: return "未知状态";
}
}
}

View File

@ -1,18 +0,0 @@
package com.kexue.skills.entity.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 订单状态查询DTO
*/
@Data
public class OrderStatusQueryDto {
@Schema(description = "订单ID")
private Long orderId;
@Schema(description = "订单号")
private String orderNo;
}

View File

@ -1,27 +0,0 @@
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;
}

View File

@ -2,9 +2,6 @@ package com.kexue.skills.entity.dto;
import com.kexue.skills.entity.base.BaseQueryDto;
import lombok.Data;
import org.checkerframework.checker.formatter.qual.Format;
import java.util.Date;
/**
* (PaymentOrder)查询DTO类
@ -33,10 +30,4 @@ public class PaymentOrderDto extends BaseQueryDto {
private Integer deleteFlag;
private Long packageId;
private Date createTimeStart;
private Date createTimeEnd;
}

View File

@ -0,0 +1,52 @@
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;
}

View File

@ -0,0 +1,73 @@
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;
}

View File

@ -1,18 +0,0 @@
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;
}

View File

@ -1,19 +0,0 @@
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;
}

View File

@ -1,53 +0,0 @@
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;
}

View File

@ -1,53 +0,0 @@
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;
/**
* 格式dirmarkdownpython
*/
private String format;
/**
* 节点描述
*/
private String description;
/**
* 子节点列表目录类型使用
*/
private List<SkillStructureNodeDto> children;
/**
* 文件内容文件类型使用
*/
private String content;
}

View File

@ -2,7 +2,6 @@ package com.kexue.skills.entity.dto;
import java.io.Serializable;
import java.util.Date;
import com.kexue.skills.entity.base.BaseQueryDto;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
@ -24,24 +23,28 @@ public class SysLogDto extends BaseQueryDto implements Serializable {
@Schema(description ="主键ID")
private Long logId;
@Schema(description ="模块")
private String module;
@Schema(description ="用户ID")
private String userId;
@Schema(description ="描述")
private String description;
@Schema(description ="用户名称")
private String userName;
@Schema(description ="IP地址")
private String ip;
@Schema(description ="日志类型")
private String logType;
@Schema(description ="状态")
private Integer status;
@Schema(description ="日志类容")
private String logContent;
@Schema(description ="开始时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date startTime;
@Schema(description ="服务端IP")
private String serverIp;
@Schema(description ="结束时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date endTime;
@Schema(description ="客户端IP")
private String clientIp;
@Schema(description ="yyyyMMddHHmmss")
private String logTime;
@Schema(description ="备注")
private String note;
}

View File

@ -1,52 +0,0 @@
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;
}

View File

@ -1,41 +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.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;
}

View File

@ -1,14 +0,0 @@
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;
}

View File

@ -1,23 +0,0 @@
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;
}

View File

@ -1,26 +0,0 @@
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;
}

View File

@ -1,6 +1,7 @@
package com.kexue.skills.entity.request;
import com.kexue.skills.entity.Account;
import com.kexue.skills.entity.PointsAccount;
import com.kexue.skills.entity.SysUser;
import lombok.Data;
@ -34,6 +35,11 @@ public class LoginUser {
*/
private Account account;
/**
* 积分信息
*/
private PointsAccount pointsAccount;
/**
* token
*/

View File

@ -23,7 +23,4 @@ public class PhoneLoginDto implements Serializable {
@Schema(description ="验证码")
private String code;
@Schema(description ="邀请码")
private String inviteCode;
}

View File

@ -15,7 +15,7 @@ import java.io.Serializable;
@ApiModel(value = "重置密码请求参数")
public class ResetPasswordDto implements Serializable {
@Schema(description ="管理员用户名")
@Schema(description ="用户名")
private String userName;
@Schema(description ="旧密码")

View File

@ -1,23 +1,22 @@
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 SkillGenRequest implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "技能名称")
private String name;
@Schema(description = "技能描述")
private String description;
@Schema(description = "技能标签")
private List<String> tags;
@Schema(description = "技能说明")
private String introduce;
@Schema(description = "需求说明")
private String requirement;
@Schema(description = "用户提示词")
private String prompt;
}

View File

@ -1,30 +0,0 @@
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;
}

View File

@ -19,6 +19,8 @@ public class SkillRequest implements Serializable {
private double temperature;
private int max_tokens;
private ResponseFormat response_format;
private int skillId;
private int contentId;
public SkillRequest(boolean useDefaultSettings) {
if (useDefaultSettings) {
@ -32,7 +34,7 @@ public class SkillRequest implements Serializable {
Message userMessage = new Message();
userMessage.setRole("user");
userMessage.setContent("主题我想要做一个将数据库设计表转换成sql schema语言并迁移到数据库服务器中的skill。请根据agent skills撰写规范帮我生成这个skill的名称、描述并从数据库中选择一个或者多个标签。输出json格式仅输出以上所提到的名称、描述、标签节点名称分别为name、description、tags节点内容以中文形式返回。");
userMessage.setContent("主题我想要做一个将数据库设计表转换成sql schema语言并迁移到数据库服务器中的skill。请根据agent skills撰写规范帮我生成这个skill的名称、描述并从以下标签列表中选择一个或者多个标签:\"软件开发系统集成网络工程云计算大数据人工智能物联网区块链信息安全运维服务测试认证IT 咨询,外包服务,电商技术,移动开发,前端开发,后端开发,全栈开发,数据库管理\"。输出json格式仅输出以上所提到的名称、描述、标签节点名称分别为name、description、tags节点内容以中文形式返回。");
this.messages.add(userMessage);
this.temperature = 0.3;
@ -43,7 +45,7 @@ public class SkillRequest implements Serializable {
}
}
public SkillRequest(boolean useDefaultSettings, String model, Double temperature, Integer maxTokens, String prompt, String tagsList) {
public SkillRequest(boolean useDefaultSettings, String model, Double temperature, Integer maxTokens,String prompt) {
if (useDefaultSettings) {
this.model = model;
this.messages = new ArrayList<>();
@ -52,10 +54,10 @@ public class SkillRequest implements Serializable {
systemMessage.setRole("system");
systemMessage.setContent("你是一个专业的AI技能设计助手。请严格按照指定的JSON格式输出仅包含要求的字段以中文形式返回。");
this.messages.add(systemMessage);
//获取系统存在的标签
Message userMessage = new Message();
userMessage.setRole("user");
userMessage.setContent("主题:"+ prompt +"。请根据agent skills撰写规范帮我生成这个skill的名称、描述并从以下标签列表中选择一个或者多个标签\"" + tagsList + "\"。输出json格式仅输出以上所提到的名称、描述、标签节点名称分别为name、description、tags节点内容以中文形式返回tags只需要返回序号数组");
userMessage.setContent("主题:"+ prompt +"。请根据agent skills撰写规范帮我生成这个skill的名称、描述并从以下标签列表中选择一个或者多个标签\"软件开发系统集成网络工程云计算大数据人工智能物联网区块链信息安全运维服务测试认证IT 咨询,外包服务,电商技术,移动开发,前端开发,后端开发,全栈开发,数据库管理\"。输出json格式仅输出以上所提到的名称、描述、标签节点名称分别为name、description、tags节点内容以中文形式返回");
this.messages.add(userMessage);
this.temperature = temperature;
@ -65,73 +67,54 @@ public class SkillRequest implements Serializable {
this.response_format.setType("json_object");
}
}
// String systemPrompt = """
// 请严格按照以下格式输出信息
//
// ## 标题 ##
// [这里填写标题]
//
// ## 关键点 ##
// - 第一点
// - 第二点
// - 第三点
//
// ## 详细说明 ##
// [这里填写详细说明]
//
// ## 建议 ##
// [这里填写建议]
//
// 不要添加任何其他内容严格按照上述格式输出
// """;
public SkillRequest(boolean useDefaultSettings, String model, String systemContent,String userContent,Double temperature, Integer maxTokens,String type) {
public SkillRequest(boolean useDefaultSettings, String model, Double temperature, Integer maxTokens,String prompt,String description,String tags,String userUpload) {
String systemPrompt2="你是一个专业的AI技能设计助手。请基于用户提供的Skill名称、描述、标签生成完整的skills.md文档内容仅输出skills.md本体内容无需其他额外说明。";
if (useDefaultSettings) {
this.model = model;
this.messages = new ArrayList<>();
Message systemMessage = new Message();
systemMessage.setRole("system");
systemMessage.setContent(systemContent);
systemMessage.setContent(systemPrompt2);
this.messages.add(systemMessage);
Message userMessage = new Message();
userMessage.setRole("user");
userMessage.setContent(userContent);
if(userUpload!=null&&!userUpload.equals("")){
userMessage.setContent("请根据以下Skill信息生成skills.md文档内容Skill名称"+prompt+"Skill描述"+description+"Skill标签"+tags+"。请仅输出skills.md本体内容无需其他额外说明。内容的生成需要参考如下内容"+userUpload);
}else{
userMessage.setContent("请根据以下Skill信息生成skills.md文档内容Skill名称"+prompt+"Skill描述"+description+"Skill标签"+tags+"。请仅输出skills.md本体内容无需其他额外说明。");
}
this.messages.add(userMessage);
this.temperature = temperature;
this.max_tokens = maxTokens;
this.response_format = new ResponseFormat();
this.response_format.setType(type);
// this.response_format = new ResponseFormat();
// this.response_format.setType("json_object");
}
}
// 新的构造方法支持文件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() {
return new SkillRequest(true);
}
@ -143,27 +126,7 @@ public class SkillRequest implements Serializable {
private static final long serialVersionUID = 1L;
private String role;
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;
private String content;
}
@Data

View File

@ -1,13 +0,0 @@
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;
}

View File

@ -1,38 +0,0 @@
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;
}

View File

@ -1,5 +1,6 @@
package com.kexue.skills.entity.response;
import com.kexue.skills.entity.CmsContent;
import lombok.Data;
import java.io.Serializable;
@ -12,5 +13,6 @@ public class SkillResponse implements Serializable {
private String name;
private String description;
private List<String> tags;
private String summary;
private Long skillId;
private List<CmsContent> cmsContents;
}

View File

@ -15,9 +15,9 @@ public class GlobalExceptionHandler {
@ExceptionHandler(BizException.class)
public CommonResult<String> handleBizException(BizException e) {
if (e == null) {
return CommonResult.success("未知错误");
return CommonResult.failed("未知错误");
}
return CommonResult.success(e.getErrorCode(), e.getMessage());
return CommonResult.failed(e.getMessage());
}
// 其他异常处理...

View File

@ -10,7 +10,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
@Slf4j
@ControllerAdvice
public class BizExceptionAdvice {
@ExceptionHandler(value = BizException.class)
@ExceptionHandler(value = RuntimeException.class)
@ResponseBody
public CommonResult handleException(BizException e){
e.printStackTrace();

View File

@ -1,52 +0,0 @@
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);
}
}
}

View File

@ -1,88 +0,0 @@
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();
}
}
}

View File

@ -1,94 +0,0 @@
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);
}
}
}

View File

@ -1,412 +0,0 @@
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);
// 检查是否有错误标识statuscodesuccess等字段
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);
}
}
}

View File

@ -1,62 +0,0 @@
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);
}

View File

@ -99,5 +99,4 @@ public interface AccountMapper {
* @return 影响行数
*/
int deleteById(Long accountId);
}

View File

@ -2,7 +2,6 @@ package com.kexue.skills.mapper;
import com.kexue.skills.entity.AccountTransaction;
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.Param;
@ -71,44 +70,4 @@ public interface AccountTransactionMapper {
* @return 影响行数
*/
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);
}

View File

@ -1,100 +0,0 @@
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);
}

View File

@ -23,24 +23,6 @@ public interface CmsContentMapper {
*/
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);
/**
* 查询列表
*
@ -57,14 +39,6 @@ public interface CmsContentMapper {
*/
CmsContent queryById(Long contentId);
/**
* 通过ID和语言类型查询单条数据
*
* @param queryDto 筛选条件
* @return 实例对象
*/
CmsContent queryByIdWithLanguage(CmsContentDto queryDto);
/**
* 新增数据
*
@ -73,14 +47,6 @@ public interface CmsContentMapper {
*/
int insert(CmsContent cmsContent);
/**
* 批量新增数据
*
* @param cmsContentList 实例对象列表
* @return 影响行数
*/
int batchInsert(@Param("cmsContentList") List<CmsContent> cmsContentList);
/**
* 更新数据
*
@ -151,85 +117,4 @@ public interface CmsContentMapper {
* @return 内容ID列表
*/
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();
}

Some files were not shown because too many files have changed in this diff Show More