Compare commits
4 Commits
10d0bfa9f6
...
bbf235362c
| Author | SHA1 | Date | |
|---|---|---|---|
| bbf235362c | |||
| 56ae5a5892 | |||
| 2d9a9c3668 | |||
| f8d116f9a3 |
220
502_ERROR_TROUBLESHOOTING.md
Normal file
220
502_ERROR_TROUBLESHOOTING.md
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
# 502 Bad Gateway 错误排查指南
|
||||||
|
|
||||||
|
## 错误说明
|
||||||
|
|
||||||
|
`502 Bad Gateway` 表示 Nginx 无法连接到后端服务。通常是因为:
|
||||||
|
1. **后端服务未启动**
|
||||||
|
2. **Nginx配置中的端口不正确**(最常见)
|
||||||
|
3. **后端服务启动失败**
|
||||||
|
4. **防火墙阻止连接**
|
||||||
|
|
||||||
|
## 排查步骤
|
||||||
|
|
||||||
|
### 1. 检查后端服务是否启动
|
||||||
|
|
||||||
|
在服务器上执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查18082端口是否在监听
|
||||||
|
netstat -tlnp | grep 18082
|
||||||
|
# 或
|
||||||
|
ss -tlnp | grep 18082
|
||||||
|
|
||||||
|
# 检查Java进程
|
||||||
|
ps aux | grep java
|
||||||
|
# 或
|
||||||
|
jps -l
|
||||||
|
```
|
||||||
|
|
||||||
|
**如果端口没有监听:**
|
||||||
|
- 后端服务没有启动
|
||||||
|
- 需要启动后端服务
|
||||||
|
|
||||||
|
### 2. 检查Nginx配置(最重要)
|
||||||
|
|
||||||
|
在宝塔面板中:
|
||||||
|
1. 网站 → 设置 → 配置文件
|
||||||
|
2. 找到 `location /api/` 配置块
|
||||||
|
3. **确认 `proxy_pass` 端口是 `18082`**
|
||||||
|
|
||||||
|
**正确的配置:**
|
||||||
|
```nginx
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://127.0.0.1:18082; # ← 必须是18082,不是8082
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**错误的配置(会导致502):**
|
||||||
|
```nginx
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://127.0.0.1:8082; # ← 错误:端口还是8082
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**修改后必须:**
|
||||||
|
1. 点击"保存"
|
||||||
|
2. 点击"重载配置"或"重启Nginx"
|
||||||
|
|
||||||
|
### 3. 测试后端服务是否正常
|
||||||
|
|
||||||
|
在服务器上执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 测试后端API是否可访问
|
||||||
|
curl http://127.0.0.1:18082/api/health
|
||||||
|
# 或
|
||||||
|
curl http://127.0.0.1:18082/api/erp/user/login -X POST -H "Content-Type: application/json" -d '{"username":"test","password":"test"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**如果返回连接拒绝:**
|
||||||
|
- 后端服务没有启动
|
||||||
|
- 检查启动日志
|
||||||
|
|
||||||
|
**如果返回正常响应:**
|
||||||
|
- 后端服务正常
|
||||||
|
- 问题在Nginx配置
|
||||||
|
|
||||||
|
### 4. 检查后端启动日志
|
||||||
|
|
||||||
|
查看后端启动日志,确认:
|
||||||
|
- 服务是否成功启动
|
||||||
|
- 是否监听在 `18082` 端口
|
||||||
|
- 是否有错误信息
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 如果使用nohup启动
|
||||||
|
tail -f app.log
|
||||||
|
|
||||||
|
# 或查看Spring Boot启动日志
|
||||||
|
# 应该看到类似信息:
|
||||||
|
# Tomcat started on port(s): 18082 (http)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 检查防火墙
|
||||||
|
|
||||||
|
在宝塔面板中:
|
||||||
|
1. 安全 → 防火墙
|
||||||
|
2. 确认端口 `18082` 已开放(如果后端需要外部访问)
|
||||||
|
3. **注意:** 如果后端只监听 `127.0.0.1`,不需要开放防火墙
|
||||||
|
|
||||||
|
### 6. 检查后端监听地址
|
||||||
|
|
||||||
|
确认后端配置:
|
||||||
|
|
||||||
|
**application-dev.yml:**
|
||||||
|
```yaml
|
||||||
|
server:
|
||||||
|
port: 18082
|
||||||
|
# 如果没有配置 address,默认监听 0.0.0.0(所有网卡)
|
||||||
|
# 如果配置了 address: 127.0.0.1,只监听本地
|
||||||
|
```
|
||||||
|
|
||||||
|
**如果后端只监听 127.0.0.1:**
|
||||||
|
- Nginx的 `proxy_pass` 必须使用 `http://127.0.0.1:18082`
|
||||||
|
- 不能使用 `http://175.178.252.59:18082`
|
||||||
|
|
||||||
|
## 快速修复步骤
|
||||||
|
|
||||||
|
### 步骤1:确认后端服务已启动
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 在服务器上执行
|
||||||
|
java -jar mt-startup-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev
|
||||||
|
|
||||||
|
# 或后台运行
|
||||||
|
nohup java -jar mt-startup-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev > app.log 2>&1 &
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤2:修改Nginx配置
|
||||||
|
|
||||||
|
在宝塔面板中修改配置文件:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://127.0.0.1:18082; # 确保是18082
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
# 添加超时配置(可选)
|
||||||
|
proxy_connect_timeout 60s;
|
||||||
|
proxy_send_timeout 60s;
|
||||||
|
proxy_read_timeout 60s;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤3:重载Nginx
|
||||||
|
|
||||||
|
在宝塔面板中:
|
||||||
|
1. 保存配置
|
||||||
|
2. 重载配置
|
||||||
|
|
||||||
|
### 步骤4:验证
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 在服务器上测试
|
||||||
|
curl http://127.0.0.1/api/erp/user/login -X POST -H "Content-Type: application/json" -d '{"username":"test","password":"test"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### Q1: 修改了Nginx配置,但还是502?
|
||||||
|
|
||||||
|
**A:** 检查是否:
|
||||||
|
- 保存了配置
|
||||||
|
- 重载了Nginx(不是只保存)
|
||||||
|
- 确认端口是 `18082`,不是 `8082`
|
||||||
|
|
||||||
|
### Q2: 后端服务启动了,但Nginx还是502?
|
||||||
|
|
||||||
|
**A:** 检查:
|
||||||
|
- Nginx配置中的端口是否正确
|
||||||
|
- 后端是否真的在 `18082` 端口监听
|
||||||
|
- Nginx错误日志:`/www/wwwlogs/175.178.252.59.error.log`
|
||||||
|
|
||||||
|
### Q3: 如何查看Nginx错误日志?
|
||||||
|
|
||||||
|
**A:** 在宝塔面板中:
|
||||||
|
- 网站 → 日志 → 错误日志
|
||||||
|
- 或直接查看:`/www/wwwlogs/175.178.252.59.error.log`
|
||||||
|
|
||||||
|
查看是否有类似错误:
|
||||||
|
```
|
||||||
|
connect() failed (111: Connection refused) while connecting to upstream
|
||||||
|
```
|
||||||
|
这表示无法连接到后端。
|
||||||
|
|
||||||
|
## 验证清单
|
||||||
|
|
||||||
|
- [ ] 后端服务已启动
|
||||||
|
- [ ] 后端监听在 `18082` 端口
|
||||||
|
- [ ] Nginx配置中 `proxy_pass` 端口是 `18082`
|
||||||
|
- [ ] Nginx配置已保存并重载
|
||||||
|
- [ ] 防火墙已开放(如果需要)
|
||||||
|
- [ ] 后端启动日志无错误
|
||||||
|
|
||||||
|
## 如果还是无法解决
|
||||||
|
|
||||||
|
1. **查看Nginx错误日志**
|
||||||
|
```bash
|
||||||
|
tail -f /www/wwwlogs/175.178.252.59.error.log
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **查看后端启动日志**
|
||||||
|
```bash
|
||||||
|
tail -f app.log
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **检查端口占用**
|
||||||
|
```bash
|
||||||
|
netstat -tlnp | grep 18082
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **测试后端直接访问**
|
||||||
|
```bash
|
||||||
|
curl http://127.0.0.1:18082/api/health
|
||||||
|
```
|
||||||
|
|
||||||
72
DEPLOYMENT_README.md
Normal file
72
DEPLOYMENT_README.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# 部署说明
|
||||||
|
|
||||||
|
## 快速部署指南
|
||||||
|
|
||||||
|
### 后端部署
|
||||||
|
|
||||||
|
1. **配置文件已更新**
|
||||||
|
- ✅ `app.frontend.url` 已设置为 `http://175.178.252.59:3000`
|
||||||
|
- ✅ `paypal.webhook-url` 已设置为 `http://175.178.252.59:18082/api/paypal/webhook`
|
||||||
|
- ✅ 前端 `.env.production` 已创建,API地址为 `/api`(相对路径)
|
||||||
|
|
||||||
|
2. **打包应用**
|
||||||
|
```bash
|
||||||
|
cd E:\MTKJPAY
|
||||||
|
mvn clean package -DskipTests
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **上传并启动**
|
||||||
|
```bash
|
||||||
|
# 上传 jar 文件到服务器
|
||||||
|
# 启动应用
|
||||||
|
java -jar mt-pay-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev
|
||||||
|
|
||||||
|
# 或后台运行
|
||||||
|
nohup java -jar mt-pay-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev > app.log 2>&1 &
|
||||||
|
```
|
||||||
|
|
||||||
|
### 前端部署
|
||||||
|
|
||||||
|
1. **构建前端**
|
||||||
|
```bash
|
||||||
|
cd E:\MTKJPAY-FRONT
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **本地测试打包结果(验证打包是否正确)**
|
||||||
|
```bash
|
||||||
|
cd dist
|
||||||
|
python -m http.server 3000
|
||||||
|
# 或
|
||||||
|
npx http-server -p 3000
|
||||||
|
# 访问 http://localhost:3000(应该能看到页面,不是空白)
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **API地址配置**
|
||||||
|
- ✅ `.env.production` 文件已创建
|
||||||
|
- ✅ API地址已设置为 `/api`(相对路径,通过Nginx代理)
|
||||||
|
- ⚠️ **重要:** 必须使用相对路径 `/api`,不要使用完整URL
|
||||||
|
|
||||||
|
4. **上传到服务器**
|
||||||
|
```bash
|
||||||
|
# 上传dist目录下的所有文件到服务器
|
||||||
|
scp -r dist/* root@175.178.252.59:/path/to/web/root/
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **服务器配置**
|
||||||
|
- 如果服务器已有Web服务器,将dist内容放到Web根目录
|
||||||
|
- 确保支持Vue Router的history模式(配置 `try_files`)
|
||||||
|
|
||||||
|
## 配置检查清单
|
||||||
|
|
||||||
|
部署前请检查以下配置:
|
||||||
|
|
||||||
|
- [x] 后端 `app.frontend.url` 已设置为 `http://175.178.252.59:3000`
|
||||||
|
- [x] 后端 `paypal.webhook-url` 已设置为 `http://175.178.252.59:18082/api/paypal/webhook`
|
||||||
|
- [x] 前端API地址已配置为 `/api`(相对路径,通过Nginx代理)
|
||||||
|
- [ ] 数据库连接信息正确
|
||||||
|
- [ ] 服务器防火墙已开放必要端口(18082、3000等)
|
||||||
|
- [ ] PayPal控制台Webhook URL已更新为 `http://175.178.252.59:18082/api/paypal/webhook`
|
||||||
|
- [ ] **Nginx配置中的proxy_pass端口已更新为18082**
|
||||||
|
|
||||||
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
# PayPal沙箱测试账号说明
|
|
||||||
|
|
||||||
## API凭证(已配置到 application-dev.yml)
|
|
||||||
|
|
||||||
- **Client ID(API密钥)**: `AdGYUZpvLuHR30dybOApvM-RNB1pVKtd74SVfh-6TK52xV-1JEBddHVMCWuDdyyHri4DXd4kABBi7Icb`
|
|
||||||
- **Client Secret(密钥)**: `ENblspyRmwsOU_PWFurlhEYUF5Da6aYKl0pjK4ehm7p3R5aSqvbpaF_YsIIs8v0ty1c9WJu15XP-Fe_1`
|
|
||||||
|
|
||||||
这些凭证已自动配置到 `application-dev.yml` 中,无需手动配置。
|
|
||||||
|
|
||||||
## 沙箱测试账号(用于测试支付流程)
|
|
||||||
|
|
||||||
### 商家账号(Business Account)
|
|
||||||
- **用户名**: `sb-vtcmz48304367@business.example.com`
|
|
||||||
- **密码**: `iN)7:z#4`
|
|
||||||
|
|
||||||
### 买家账号(Personal Account)
|
|
||||||
PayPal沙箱环境通常会提供多个测试账号,你可以在PayPal开发者控制台查看。
|
|
||||||
|
|
||||||
## 测试流程
|
|
||||||
|
|
||||||
1. **启动应用**:确保使用 `dev` 环境配置启动
|
|
||||||
2. **创建订单**:在系统中创建测试订单
|
|
||||||
3. **点击支付**:在订单确认页面点击"立即支付"
|
|
||||||
4. **PayPal登录**:使用上述商家账号或买家账号登录PayPal沙箱
|
|
||||||
5. **完成支付**:在PayPal页面完成支付流程
|
|
||||||
6. **查看结果**:支付完成后会自动跳转回系统,查看订单状态
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
1. **环境切换**:当前配置为沙箱环境(`mode: sandbox`)
|
|
||||||
2. **生产环境**:上线前需要:
|
|
||||||
- 在PayPal开发者控制台获取生产环境的Client ID和Secret
|
|
||||||
- 修改 `application-prod.yml` 中的配置
|
|
||||||
- 将 `mode` 改为 `production`
|
|
||||||
3. **测试账号**:沙箱测试账号仅用于开发测试,不能用于真实交易
|
|
||||||
|
|
||||||
## PayPal沙箱控制台
|
|
||||||
|
|
||||||
访问地址:https://developer.paypal.com/dashboard/
|
|
||||||
|
|
||||||
在这里可以:
|
|
||||||
- 查看API凭证
|
|
||||||
- 管理测试账号
|
|
||||||
- 查看交易记录
|
|
||||||
- 配置Webhook等
|
|
||||||
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
# PayPal Webhook处理指南
|
|
||||||
|
|
||||||
## 概述
|
|
||||||
|
|
||||||
PayPal Webhook是PayPal向你的服务器发送事件通知的机制。当支付状态发生变化时,PayPal会主动推送事件到你的Webhook URL。
|
|
||||||
|
|
||||||
## Webhook URL配置
|
|
||||||
|
|
||||||
在你的PayPal开发者控制台中配置Webhook URL:
|
|
||||||
- **开发环境**: `https://你的域名/api/paypal/webhook`
|
|
||||||
- **生产环境**: `https://你的生产域名/api/paypal/webhook`
|
|
||||||
|
|
||||||
## 支持的事件类型
|
|
||||||
|
|
||||||
系统已实现以下事件类型的处理:
|
|
||||||
|
|
||||||
### 1. PAYMENT.CAPTURE.COMPLETED
|
|
||||||
**触发时机**: 当PayPal成功捕获资金时触发
|
|
||||||
|
|
||||||
**处理逻辑**:
|
|
||||||
- 提取capture信息(金额、货币、状态等)
|
|
||||||
- 通过`supplementary_data.related_ids.order_id`或`custom_id`获取ERP订单号
|
|
||||||
- 更新ERP订单状态为"已支付"(PAID)
|
|
||||||
|
|
||||||
### 2. PAYMENT.CAPTURE.DENIED
|
|
||||||
**触发时机**: 当支付捕获被拒绝时触发
|
|
||||||
|
|
||||||
**处理逻辑**:
|
|
||||||
- 记录支付失败信息
|
|
||||||
- 更新ERP订单状态为"支付失败"
|
|
||||||
|
|
||||||
### 3. PAYMENT.CAPTURE.REFUNDED
|
|
||||||
**触发时机**: 当支付被退款时触发
|
|
||||||
|
|
||||||
**处理逻辑**:
|
|
||||||
- 记录退款信息
|
|
||||||
- 更新ERP订单状态为"已退款"
|
|
||||||
|
|
||||||
### 4. CHECKOUT.ORDER.APPROVED
|
|
||||||
**触发时机**: 当订单被顾客批准时触发
|
|
||||||
|
|
||||||
**处理逻辑**:
|
|
||||||
- 记录订单批准信息
|
|
||||||
- 可以触发自动捕获逻辑(如果需要)
|
|
||||||
|
|
||||||
### 5. CHECKOUT.ORDER.COMPLETED
|
|
||||||
**触发时机**: 当订单完成时触发
|
|
||||||
|
|
||||||
**处理逻辑**:
|
|
||||||
- 记录订单完成信息
|
|
||||||
- 根据业务需求处理订单完成逻辑
|
|
||||||
|
|
||||||
### 6. CHECKOUT.ORDER.CANCELLED
|
|
||||||
**触发时机**: 当订单被取消时触发
|
|
||||||
|
|
||||||
**处理逻辑**:
|
|
||||||
- 记录订单取消信息
|
|
||||||
- 更新ERP订单状态为"已取消"
|
|
||||||
|
|
||||||
## 签名验证
|
|
||||||
|
|
||||||
### 开发环境(沙箱)
|
|
||||||
- 签名验证可以暂时跳过(已实现自动跳过)
|
|
||||||
- 但建议在测试时也启用验证,确保代码正确
|
|
||||||
|
|
||||||
### 生产环境
|
|
||||||
- **必须启用签名验证**
|
|
||||||
- 需要在配置文件中设置`webhook-id`
|
|
||||||
- 系统会自动验证每个Webhook请求的签名
|
|
||||||
|
|
||||||
## 配置Webhook ID
|
|
||||||
|
|
||||||
### 1. 获取Webhook ID
|
|
||||||
|
|
||||||
1. 登录PayPal开发者控制台:https://developer.paypal.com/dashboard/
|
|
||||||
2. 进入你的应用设置
|
|
||||||
3. 找到Webhook配置部分
|
|
||||||
4. 创建或查看Webhook,获取Webhook ID
|
|
||||||
|
|
||||||
### 2. 配置到系统
|
|
||||||
|
|
||||||
在`application-dev.yml`或`application-prod.yml`中添加:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
paypal:
|
|
||||||
webhook-id: YOUR_WEBHOOK_ID
|
|
||||||
```
|
|
||||||
|
|
||||||
## 订单号关联
|
|
||||||
|
|
||||||
PayPal Webhook事件中需要关联ERP订单号,系统会按以下顺序尝试获取:
|
|
||||||
|
|
||||||
1. **从`supplementary_data.related_ids.order_id`获取**
|
|
||||||
- 这是PayPal自动填充的,包含原始订单ID
|
|
||||||
- 然后通过查询PayPal订单详情获取`reference_id`(即ERP订单号)
|
|
||||||
|
|
||||||
2. **从`custom_id`获取**
|
|
||||||
- 如果创建PayPal订单时设置了`custom_id`,可以直接获取
|
|
||||||
|
|
||||||
3. **通过`order_id`查询订单详情**
|
|
||||||
- 如果上述方法都无法获取,会通过PayPal API查询订单详情
|
|
||||||
- 从订单的`purchase_units[0].reference_id`获取ERP订单号
|
|
||||||
|
|
||||||
## 测试Webhook
|
|
||||||
|
|
||||||
### 使用PayPal Webhook模拟器
|
|
||||||
|
|
||||||
1. 登录PayPal开发者控制台
|
|
||||||
2. 进入Webhook配置页面
|
|
||||||
3. 使用"Send test webhook"功能
|
|
||||||
4. 选择要测试的事件类型
|
|
||||||
5. 系统会收到测试事件并处理
|
|
||||||
|
|
||||||
### 本地测试
|
|
||||||
|
|
||||||
如果需要在本地测试,可以使用以下工具:
|
|
||||||
|
|
||||||
1. **ngrok**(推荐):
|
|
||||||
```bash
|
|
||||||
ngrok http 8082
|
|
||||||
```
|
|
||||||
然后将ngrok提供的URL配置到PayPal Webhook URL
|
|
||||||
|
|
||||||
2. **PayPal Webhook模拟器**:
|
|
||||||
使用Postman等工具模拟Webhook请求
|
|
||||||
|
|
||||||
## 日志查看
|
|
||||||
|
|
||||||
Webhook处理的所有步骤都会记录日志,包括:
|
|
||||||
- 接收到的Webhook事件
|
|
||||||
- 签名验证结果
|
|
||||||
- 事件处理过程
|
|
||||||
- 订单状态更新结果
|
|
||||||
|
|
||||||
查看日志位置:
|
|
||||||
- 应用日志文件
|
|
||||||
- 控制台输出
|
|
||||||
|
|
||||||
## 常见问题
|
|
||||||
|
|
||||||
### 1. Webhook未收到事件
|
|
||||||
- 检查Webhook URL是否正确配置
|
|
||||||
- 确认服务器可以访问(防火墙、网络等)
|
|
||||||
- 检查PayPal控制台中的Webhook状态
|
|
||||||
|
|
||||||
### 2. 签名验证失败
|
|
||||||
- 确认Webhook ID配置正确
|
|
||||||
- 检查请求头是否完整
|
|
||||||
- 确认使用的是正确的环境(沙箱/生产)
|
|
||||||
|
|
||||||
### 3. 无法关联ERP订单
|
|
||||||
- 确认创建PayPal订单时设置了`reference_id`
|
|
||||||
- 检查订单号格式是否正确
|
|
||||||
- 查看日志中的详细信息
|
|
||||||
|
|
||||||
## 安全建议
|
|
||||||
|
|
||||||
1. **生产环境必须启用签名验证**
|
|
||||||
2. **使用HTTPS**(PayPal要求)
|
|
||||||
3. **记录所有Webhook事件**(用于审计和调试)
|
|
||||||
4. **实现幂等性处理**(防止重复处理同一事件)
|
|
||||||
5. **设置超时和重试机制**
|
|
||||||
|
|
||||||
## 代码位置
|
|
||||||
|
|
||||||
- **Webhook控制器**: `PayPalController.webhook()`
|
|
||||||
- **Webhook服务**: `PayPalWebhookService` 和 `PayPalWebhookServiceImpl`
|
|
||||||
- **事件DTO**: `PayPalWebhookEventDTO`
|
|
||||||
- **配置**: `PayPalProperties.webhookId`
|
|
||||||
|
|
||||||
39
PROJECT_IMPROVEMENT_PLAN.md
Normal file
39
PROJECT_IMPROVEMENT_PLAN.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# 项目改进计划
|
||||||
|
|
||||||
|
## 已完成功能
|
||||||
|
|
||||||
|
- ✅ 商品管理(创建、查询、下架、分页)
|
||||||
|
- ✅ 订单管理(创建、查询、分页、状态管理)
|
||||||
|
- ✅ PayPal支付集成(创建订单、捕获支付、Webhook处理)
|
||||||
|
- ✅ 库存管理(扣减、恢复)
|
||||||
|
- ✅ 货币转换(实时汇率、多币种支持)
|
||||||
|
- ✅ ERP用户系统(注册、登录、信息管理)
|
||||||
|
- ✅ 国际化支持(6种语言)
|
||||||
|
|
||||||
|
## 待实现功能(按优先级)
|
||||||
|
|
||||||
|
### 高优先级
|
||||||
|
1. **商品编辑功能** - 支持修改商品信息
|
||||||
|
2. **订单详情页面** - 显示完整订单和支付信息
|
||||||
|
3. **订单状态管理** - 发货、完成、取消功能
|
||||||
|
4. **密码加密升级** - MD5升级为BCrypt
|
||||||
|
|
||||||
|
### 中优先级
|
||||||
|
5. **JWT Token实现** - 替换MD5+Base64 Token
|
||||||
|
6. **Redis缓存集成** - PayPal token和汇率缓存
|
||||||
|
7. **角色权限系统** - 角色和权限管理
|
||||||
|
8. **操作日志系统** - 记录用户操作
|
||||||
|
|
||||||
|
### 低优先级
|
||||||
|
9. **单元测试** - 测试覆盖率>70%
|
||||||
|
10. **Swagger文档** - API文档自动生成
|
||||||
|
11. **数据统计** - 销售和订单统计
|
||||||
|
12. **容器化部署** - Docker支持
|
||||||
|
|
||||||
|
## 实施建议
|
||||||
|
|
||||||
|
**第一阶段(1-2周)**:商品编辑、订单详情、订单状态管理、密码加密升级
|
||||||
|
|
||||||
|
**第二阶段(2-3周)**:JWT Token、角色权限系统
|
||||||
|
|
||||||
|
**第三阶段(3-4周)**:Redis缓存、操作日志、性能优化
|
||||||
763
hs_err_pid15540.log
Normal file
763
hs_err_pid15540.log
Normal file
@@ -0,0 +1,763 @@
|
|||||||
|
#
|
||||||
|
# A fatal error has been detected by the Java Runtime Environment:
|
||||||
|
#
|
||||||
|
# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x00007ffbad9b3ff4, pid=15540, tid=5008
|
||||||
|
#
|
||||||
|
# JRE version: Java(TM) SE Runtime Environment (17.0.12+8) (build 17.0.12+8-LTS-286)
|
||||||
|
# Java VM: Java HotSpot(TM) 64-Bit Server VM (17.0.12+8-LTS-286, mixed mode, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, windows-amd64)
|
||||||
|
# Problematic frame:
|
||||||
|
# V [jvm.dll+0x6a3ff4]
|
||||||
|
#
|
||||||
|
# No core dump will be written. Minidumps are not enabled by default on client versions of Windows
|
||||||
|
#
|
||||||
|
# If you would like to submit a bug report, please visit:
|
||||||
|
# https://bugreport.java.com/bugreport/crash.jsp
|
||||||
|
#
|
||||||
|
|
||||||
|
--------------- S U M M A R Y ------------
|
||||||
|
|
||||||
|
Command Line: -Dmaven.multiModuleProjectDirectory=E:\MTKJPAY -Djansi.passthrough=true -Dmaven.home=D:/develop/Maven/apache-maven-3.9.4 -Dclassworlds.conf=D:\develop\Maven\apache-maven-3.9.4\bin\m2.conf -Dmaven.ext.class.path=D:\develop\IntelliJ IDEA 2025.1\plugins\maven\lib\maven-event-listener.jar -javaagent:D:\develop\IntelliJ IDEA 2025.1\lib\idea_rt.jar=57907 -Dfile.encoding=UTF-8 org.codehaus.classworlds.Launcher -Didea.version=2025.1.1.1 -s D:\develop\Maven\apache-maven-3.9.4\conf\settings.xml -Dmaven.repo.local=D:\develop\Maven\repository package
|
||||||
|
|
||||||
|
Host: Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz, 12 cores, 31G, Windows 11 , 64 bit Build 26100 (10.0.26100.7309)
|
||||||
|
Time: Fri Dec 26 13:43:45 2025 Windows 11 , 64 bit Build 26100 (10.0.26100.7309) elapsed time: 11.659119 seconds (0d 0h 0m 11s)
|
||||||
|
|
||||||
|
--------------- T H R E A D ---------------
|
||||||
|
|
||||||
|
Current thread (0x000000007c137d50): JavaThread "C2 CompilerThread1" daemon [_thread_in_native, id=5008, stack(0x0000000076500000,0x0000000076600000)]
|
||||||
|
|
||||||
|
|
||||||
|
Current CompileTask:
|
||||||
|
C2: 11659 12251 ! 4 java.lang.ClassLoader::loadClass (121 bytes)
|
||||||
|
|
||||||
|
Stack: [0x0000000076500000,0x0000000076600000], sp=0x00000000765fbd50, free space=1007k
|
||||||
|
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
|
||||||
|
V [jvm.dll+0x6a3ff4]
|
||||||
|
V [jvm.dll+0x1ba717]
|
||||||
|
V [jvm.dll+0x216997]
|
||||||
|
V [jvm.dll+0x215c6f]
|
||||||
|
V [jvm.dll+0x1a2960]
|
||||||
|
V [jvm.dll+0x22610b]
|
||||||
|
V [jvm.dll+0x2242ab]
|
||||||
|
V [jvm.dll+0x79075c]
|
||||||
|
V [jvm.dll+0x78abea]
|
||||||
|
V [jvm.dll+0x678f35]
|
||||||
|
C [ucrtbase.dll+0x37b0]
|
||||||
|
C [KERNEL32.DLL+0x2e8d7]
|
||||||
|
C [ntdll.dll+0x8c53c]
|
||||||
|
|
||||||
|
|
||||||
|
siginfo: EXCEPTION_ACCESS_VIOLATION (0xc0000005), reading address 0x00007ffbacbe6160
|
||||||
|
|
||||||
|
|
||||||
|
Registers:
|
||||||
|
RAX=0x00007ffbacbe6070, RBX=0x000000000000026c, RCX=0x000000007be2a4f8, RDX=0x000000007e434050
|
||||||
|
RSP=0x00000000765fbd50, RBP=0x00000000774112b0, RSI=0x00000000765fc3c0, RDI=0x000000000000026c
|
||||||
|
R8 =0x0000000000030101, R9 =0x00000000000002ac, R10=0x000000000000000d, R11=0x00000000785b23f0
|
||||||
|
R12=0x000000007be2a4f8, R13=0x000000007dcf0c50, R14=0x000000007dcf0c50, R15=0x000000000000026d
|
||||||
|
RIP=0x00007ffbad9b3ff4, EFLAGS=0x0000000000010246
|
||||||
|
|
||||||
|
|
||||||
|
Register to memory mapping:
|
||||||
|
|
||||||
|
RIP=0x00007ffbad9b3ff4 jvm.dll
|
||||||
|
RAX=0x00007ffbacbe6070 is an unknown value
|
||||||
|
RBX=0x000000000000026c is an unknown value
|
||||||
|
RCX=0x000000007be2a4f8 points into unknown readable memory: 0x00007ffbacbe6070 | 70 60 be ac fb 7f 00 00
|
||||||
|
RDX=0x000000007e434050 points into unknown readable memory: 0x0000000000000000 | 00 00 00 00 00 00 00 00
|
||||||
|
RSP=0x00000000765fbd50 is pointing into the stack for thread: 0x000000007c137d50
|
||||||
|
RBP=0x00000000774112b0 points into unknown readable memory: 0x000000007930b540 | 40 b5 30 79 00 00 00 00
|
||||||
|
RSI=0x00000000765fc3c0 is pointing into the stack for thread: 0x000000007c137d50
|
||||||
|
RDI=0x000000000000026c is an unknown value
|
||||||
|
R8 =0x0000000000030101 points into unknown readable memory: 00 00 00 c8 5c 00 00
|
||||||
|
R9 =0x00000000000002ac is an unknown value
|
||||||
|
R10=0x000000000000000d is an unknown value
|
||||||
|
R11=0x00000000785b23f0 points into unknown readable memory: 0x00007ffbadbe6070 | 70 60 be ad fb 7f 00 00
|
||||||
|
R12=0x000000007be2a4f8 points into unknown readable memory: 0x00007ffbacbe6070 | 70 60 be ac fb 7f 00 00
|
||||||
|
R13=0x000000007dcf0c50 points into unknown readable memory: 0x000000007930b540 | 40 b5 30 79 00 00 00 00
|
||||||
|
R14=0x000000007dcf0c50 points into unknown readable memory: 0x000000007930b540 | 40 b5 30 79 00 00 00 00
|
||||||
|
R15=0x000000000000026d is an unknown value
|
||||||
|
|
||||||
|
|
||||||
|
Top of Stack: (sp=0x00000000765fbd50)
|
||||||
|
0x00000000765fbd50: 00000000785b23f0 0000000000001310
|
||||||
|
0x00000000765fbd60: 00000000774112b0 0000000081f3fd80
|
||||||
|
0x00000000765fbd70: 00000000774112b0 000000007dcf0c50
|
||||||
|
0x00000000765fbd80: 0000000000000262 00000000ffffffff
|
||||||
|
0x00000000765fbd90: 000000007dcf0c50 000000007be2a4f8
|
||||||
|
0x00000000765fbda0: 0000000081f3fd80 000000007f6bae38
|
||||||
|
0x00000000765fbdb0: 0000000081318ff0 0000000000000510
|
||||||
|
0x00000000765fbdc0: 00000000000022f4 00000000785eb5d0
|
||||||
|
0x00000000765fbdd0: 000000007f7ea3f0 000000007c36c550
|
||||||
|
0x00000000765fbde0: 0000004000000022 000000007c36c550
|
||||||
|
0x00000000765fbdf0: 0000000000000000 00000000785eb5a0
|
||||||
|
0x00000000765fbe00: 000000007930b540 000000007f7e88c0
|
||||||
|
0x00000000765fbe10: 000000007f7ea3f0 000000007f7f08a8
|
||||||
|
0x00000000765fbe20: 000000000041f988 000000007d23aab0
|
||||||
|
0x00000000765fbe30: 00000000000062f8 0000000000000000
|
||||||
|
0x00000000765fbe40: 000000000000752f 00007ffbad4cadc3
|
||||||
|
|
||||||
|
Instructions: (pc=0x00007ffbad9b3ff4)
|
||||||
|
0x00007ffbad9b3ef4: 70 48 69 d2 b0 00 00 00 48 03 51 20 8b c8 e8 79
|
||||||
|
0x00007ffbad9b3f04: 58 02 00 be 01 00 00 00 44 8b e0 3b c6 0f 8e 9e
|
||||||
|
0x00007ffbad9b3f14: 00 00 00 66 0f 1f 84 00 00 00 00 00 44 8b 4d 08
|
||||||
|
0x00007ffbad9b3f24: 41 8b df 2b de 8b fb 41 3b d9 73 0b 48 8b 45 10
|
||||||
|
0x00007ffbad9b3f34: 48 83 3c d8 00 75 6f 81 fb bf 02 00 00 7d 33 48
|
||||||
|
0x00007ffbad9b3f44: 6b 44 24 70 16 4c 8b c7 49 c1 e8 06 4c 03 c0 48
|
||||||
|
0x00007ffbad9b3f54: 8b 84 24 f0 01 00 00 48 8b 40 70 48 8b 50 20 8b
|
||||||
|
0x00007ffbad9b3f64: c3 83 e0 3f 0f b6 c8 4a 8b 44 c2 38 48 0f a3 c8
|
||||||
|
0x00007ffbad9b3f74: 73 34 41 3b d9 72 0a 8b d3 48 8b cd e8 6b 4e fb
|
||||||
|
0x00007ffbad9b3f84: ff 48 8b 45 10 48 8b 4c 24 58 48 89 0c d8 41 3b
|
||||||
|
0x00007ffbad9b3f94: 5d 08 72 0a 8b d3 49 8b cd e8 4e 4e fb ff 49 8b
|
||||||
|
0x00007ffbad9b3fa4: 45 10 4c 89 34 d8 ff c6 41 3b f4 0f 8c 6b ff ff
|
||||||
|
0x00007ffbad9b3fb4: ff 48 8b b4 24 f0 01 00 00 4c 8b 64 24 48 8b 84
|
||||||
|
0x00007ffbad9b3fc4: 24 f8 01 00 00 ff c0 89 84 24 f8 01 00 00 41 3b
|
||||||
|
0x00007ffbad9b3fd4: 44 24 18 0f 82 f3 fd ff ff 4c 8b 74 24 40 41 8b
|
||||||
|
0x00007ffbad9b3fe4: 44 24 2c 24 03 3c 02 75 11 49 8b 04 24 49 8b cc
|
||||||
|
0x00007ffbad9b3ff4: ff 90 f0 00 00 00 8b f8 eb 02 33 ff bb 01 00 00
|
||||||
|
0x00007ffbad9b4004: 00 41 39 5c 24 18 76 59 4c 8b 6c 24 40 4c 8b 7c
|
||||||
|
0x00007ffbad9b4014: 24 50 66 66 0f 1f 84 00 00 00 00 00 3b fb 4d 8b
|
||||||
|
0x00007ffbad9b4024: cf 44 8b c3 49 8b d4 0f 95 c0 48 8b ce 88 44 24
|
||||||
|
0x00007ffbad9b4034: 30 4c 89 6c 24 28 48 89 6c 24 20 e8 dc e6 ff ff
|
||||||
|
0x00007ffbad9b4044: 44 8b 94 24 00 02 00 00 ff c3 44 2b d0 44 89 94
|
||||||
|
0x00007ffbad9b4054: 24 00 02 00 00 41 3b 5c 24 18 72 c0 4d 8b f5 eb
|
||||||
|
0x00007ffbad9b4064: 08 44 8b 94 24 00 02 00 00 49 63 4c 24 28 48 8b
|
||||||
|
0x00007ffbad9b4074: 86 08 01 00 00 8b 14 88 85 d2 0f 84 be 06 00 00
|
||||||
|
0x00007ffbad9b4084: 48 8b 46 70 44 8b f2 4c 8b bc 24 f0 01 00 00 48
|
||||||
|
0x00007ffbad9b4094: 69 fa b0 00 00 00 48 8b 48 20 49 8b d4 8b 74 39
|
||||||
|
0x00007ffbad9b40a4: 30 49 8b cf e8 e3 07 00 00 48 8b d8 48 89 44 24
|
||||||
|
0x00007ffbad9b40b4: 70 48 8b 44 24 40 44 8b ee 44 8b e6 3b 70 08 73
|
||||||
|
0x00007ffbad9b40c4: 46 48 8b 48 10 48 8d 50 10 48 89 54 24 58 48 83
|
||||||
|
0x00007ffbad9b40d4: 3c f1 00 74 3b 48 8b c1 48 8b 0c f1 83 79 20 00
|
||||||
|
0x00007ffbad9b40e4: 75 2e 48 c7 04 f0 00 00 00 00 3b 75 08 72 0a 8b
|
||||||
|
|
||||||
|
|
||||||
|
Stack slot to memory mapping:
|
||||||
|
stack at sp + 0 slots: 0x00000000785b23f0 points into unknown readable memory: 0x00007ffbadbe6070 | 70 60 be ad fb 7f 00 00
|
||||||
|
stack at sp + 1 slots: 0x0000000000001310 is an unknown value
|
||||||
|
stack at sp + 2 slots: 0x00000000774112b0 points into unknown readable memory: 0x000000007930b540 | 40 b5 30 79 00 00 00 00
|
||||||
|
stack at sp + 3 slots: 0x0000000081f3fd80 points into unknown readable memory: 0x00007ffbadbd1c30 | 30 1c bd ad fb 7f 00 00
|
||||||
|
stack at sp + 4 slots: 0x00000000774112b0 points into unknown readable memory: 0x000000007930b540 | 40 b5 30 79 00 00 00 00
|
||||||
|
stack at sp + 5 slots: 0x000000007dcf0c50 points into unknown readable memory: 0x000000007930b540 | 40 b5 30 79 00 00 00 00
|
||||||
|
stack at sp + 6 slots: 0x0000000000000262 is an unknown value
|
||||||
|
stack at sp + 7 slots: 0x00000000ffffffff is an unknown value
|
||||||
|
|
||||||
|
|
||||||
|
--------------- P R O C E S S ---------------
|
||||||
|
|
||||||
|
Threads class SMR info:
|
||||||
|
_java_thread_list=0x00000000764809e0, length=15, elements={
|
||||||
|
0x0000000002c571b0, 0x000000002ed7d0f0, 0x000000002ed7e1d0, 0x000000002ed973e0,
|
||||||
|
0x000000002ed97cb0, 0x000000002ed98990, 0x000000002ed9a270, 0x000000002eda2e80,
|
||||||
|
0x000000002eda5210, 0x000000002edadc60, 0x000000002fc10d30, 0x000000002fc11240,
|
||||||
|
0x000000002fc11750, 0x000000007c137d50, 0x000000007c139d30
|
||||||
|
}
|
||||||
|
|
||||||
|
Java Threads: ( => current thread )
|
||||||
|
0x0000000002c571b0 JavaThread "main" [_thread_in_native, id=7516, stack(0x0000000002320000,0x0000000002420000)]
|
||||||
|
0x000000002ed7d0f0 JavaThread "Reference Handler" daemon [_thread_blocked, id=13644, stack(0x0000000075000000,0x0000000075100000)]
|
||||||
|
0x000000002ed7e1d0 JavaThread "Finalizer" daemon [_thread_blocked, id=1312, stack(0x0000000075100000,0x0000000075200000)]
|
||||||
|
0x000000002ed973e0 JavaThread "Signal Dispatcher" daemon [_thread_blocked, id=25640, stack(0x0000000075200000,0x0000000075300000)]
|
||||||
|
0x000000002ed97cb0 JavaThread "Attach Listener" daemon [_thread_blocked, id=23972, stack(0x0000000075300000,0x0000000075400000)]
|
||||||
|
0x000000002ed98990 JavaThread "Service Thread" daemon [_thread_blocked, id=9868, stack(0x0000000075400000,0x0000000075500000)]
|
||||||
|
0x000000002ed9a270 JavaThread "Monitor Deflation Thread" daemon [_thread_blocked, id=25172, stack(0x0000000075500000,0x0000000075600000)]
|
||||||
|
0x000000002eda2e80 JavaThread "C2 CompilerThread0" daemon [_thread_in_native, id=17288, stack(0x0000000075600000,0x0000000075700000)]
|
||||||
|
0x000000002eda5210 JavaThread "C1 CompilerThread0" daemon [_thread_blocked, id=12136, stack(0x0000000075700000,0x0000000075800000)]
|
||||||
|
0x000000002edadc60 JavaThread "Sweeper thread" daemon [_thread_blocked, id=17968, stack(0x0000000075800000,0x0000000075900000)]
|
||||||
|
0x000000002fc10d30 JavaThread "Common-Cleaner" daemon [_thread_blocked, id=21392, stack(0x0000000075900000,0x0000000075a00000)]
|
||||||
|
0x000000002fc11240 JavaThread "Monitor Ctrl-Break" daemon [_thread_in_native, id=5348, stack(0x0000000075a00000,0x0000000075b00000)]
|
||||||
|
0x000000002fc11750 JavaThread "Notification Thread" daemon [_thread_blocked, id=1984, stack(0x0000000075b00000,0x0000000075c00000)]
|
||||||
|
=>0x000000007c137d50 JavaThread "C2 CompilerThread1" daemon [_thread_in_native, id=5008, stack(0x0000000076500000,0x0000000076600000)]
|
||||||
|
0x000000007c139d30 JavaThread "C2 CompilerThread2" daemon [_thread_in_native, id=18648, stack(0x00000000775d0000,0x00000000776d0000)]
|
||||||
|
|
||||||
|
Other Threads:
|
||||||
|
0x000000002ed7a710 VMThread "VM Thread" [stack: 0x000000002fea0000,0x000000002ffa0000] [id=9784]
|
||||||
|
0x0000000002d1e780 WatcherThread [stack: 0x0000000075c00000,0x0000000075d00000] [id=11184]
|
||||||
|
0x0000000002d04cd0 GCTaskThread "GC Thread#0" [stack: 0x000000002c8d0000,0x000000002c9d0000] [id=19496]
|
||||||
|
0x0000000076634f80 GCTaskThread "GC Thread#1" [stack: 0x00000000776d0000,0x00000000777d0000] [id=11360]
|
||||||
|
0x0000000076635240 GCTaskThread "GC Thread#2" [stack: 0x00000000777d0000,0x00000000778d0000] [id=1392]
|
||||||
|
0x00000000764fe650 GCTaskThread "GC Thread#3" [stack: 0x00000000778d0000,0x00000000779d0000] [id=16964]
|
||||||
|
0x00000000762f3540 GCTaskThread "GC Thread#4" [stack: 0x00000000779d0000,0x0000000077ad0000] [id=8364]
|
||||||
|
0x00000000762f3800 GCTaskThread "GC Thread#5" [stack: 0x0000000077ad0000,0x0000000077bd0000] [id=4364]
|
||||||
|
0x00000000762f3ac0 GCTaskThread "GC Thread#6" [stack: 0x0000000077bd0000,0x0000000077cd0000] [id=15400]
|
||||||
|
0x00000000762f3f90 GCTaskThread "GC Thread#7" [stack: 0x0000000077cd0000,0x0000000077dd0000] [id=12028]
|
||||||
|
0x00000000764eb660 GCTaskThread "GC Thread#8" [stack: 0x0000000077dd0000,0x0000000077ed0000] [id=21332]
|
||||||
|
0x00000000764eb920 GCTaskThread "GC Thread#9" [stack: 0x0000000077ed0000,0x0000000077fd0000] [id=18796]
|
||||||
|
0x0000000002d176a0 ConcurrentGCThread "G1 Main Marker" [stack: 0x000000002c9d0000,0x000000002cad0000] [id=21656]
|
||||||
|
0x0000000002d17fc0 ConcurrentGCThread "G1 Conc#0" [stack: 0x000000002cad0000,0x000000002cbd0000] [id=11828]
|
||||||
|
0x00000000769f6a20 ConcurrentGCThread "G1 Conc#1" [stack: 0x00000000790a0000,0x00000000791a0000] [id=9600]
|
||||||
|
0x0000000076b1a8a0 ConcurrentGCThread "G1 Conc#2" [stack: 0x000000007a320000,0x000000007a420000] [id=25132]
|
||||||
|
0x000000002ecb5ee0 ConcurrentGCThread "G1 Refine#0" [stack: 0x000000002f8a0000,0x000000002f9a0000] [id=2668]
|
||||||
|
0x0000000079057ee0 ConcurrentGCThread "G1 Refine#1" [stack: 0x000000007b3f0000,0x000000007b4f0000] [id=10724]
|
||||||
|
0x0000000079059950 ConcurrentGCThread "G1 Refine#2" [stack: 0x000000007b4f0000,0x000000007b5f0000] [id=10936]
|
||||||
|
0x00000000790581d0 ConcurrentGCThread "G1 Refine#3" [stack: 0x000000007b5f0000,0x000000007b6f0000] [id=14896]
|
||||||
|
0x00000000790584c0 ConcurrentGCThread "G1 Refine#4" [stack: 0x000000007b6f0000,0x000000007b7f0000] [id=11324]
|
||||||
|
0x0000000079058aa0 ConcurrentGCThread "G1 Refine#5" [stack: 0x000000007ba10000,0x000000007bb10000] [id=24032]
|
||||||
|
0x000000007905a800 ConcurrentGCThread "G1 Refine#6" [stack: 0x000000007cd50000,0x000000007ce50000] [id=24052]
|
||||||
|
0x0000000079057bf0 ConcurrentGCThread "G1 Refine#7" [stack: 0x000000007b7f0000,0x000000007b8f0000] [id=15260]
|
||||||
|
0x0000000076a78150 ConcurrentGCThread "G1 Refine#8" [stack: 0x000000007b8f0000,0x000000007b9f0000] [id=18652]
|
||||||
|
0x000000002ecb66c0 ConcurrentGCThread "G1 Service" [stack: 0x000000002f9a0000,0x000000002faa0000] [id=1916]
|
||||||
|
|
||||||
|
Threads with active compile tasks:
|
||||||
|
C2 CompilerThread0 11691 12541 4 com.sun.tools.javac.jvm.ClassReader::setParameters (334 bytes)
|
||||||
|
C2 CompilerThread1 11691 12251 ! 4 java.lang.ClassLoader::loadClass (121 bytes)
|
||||||
|
C2 CompilerThread2 11691 12575 4 com.sun.tools.javac.code.Types$SignatureGenerator::assembleSig (525 bytes)
|
||||||
|
|
||||||
|
VM state: not at safepoint (normal execution)
|
||||||
|
|
||||||
|
VM Mutex/Monitor currently owned by a thread: None
|
||||||
|
|
||||||
|
Heap address: 0x0000000602c00000, size: 8148 MB, Compressed Oops mode: Zero based, Oop shift amount: 3
|
||||||
|
|
||||||
|
CDS archive(s) mapped at: [0x0000000030000000-0x0000000030bd0000-0x0000000030bd0000), size 12386304, SharedBaseAddress: 0x0000000030000000, ArchiveRelocationMode: 1.
|
||||||
|
Compressed class space mapped at: 0x0000000031000000-0x0000000071000000, reserved size: 1073741824
|
||||||
|
Narrow klass base: 0x0000000030000000, Narrow klass shift: 0, Narrow klass range: 0x100000000
|
||||||
|
|
||||||
|
GC Precious Log:
|
||||||
|
CPUs: 12 total, 12 available
|
||||||
|
Memory: 32591M
|
||||||
|
Large Page Support: Disabled
|
||||||
|
NUMA Support: Disabled
|
||||||
|
Compressed Oops: Enabled (Zero based)
|
||||||
|
Heap Region Size: 4M
|
||||||
|
Heap Min Capacity: 8M
|
||||||
|
Heap Initial Capacity: 512M
|
||||||
|
Heap Max Capacity: 8148M
|
||||||
|
Pre-touch: Disabled
|
||||||
|
Parallel Workers: 10
|
||||||
|
Concurrent Workers: 3
|
||||||
|
Concurrent Refinement Workers: 10
|
||||||
|
Periodic GC: Disabled
|
||||||
|
|
||||||
|
Heap:
|
||||||
|
garbage-first heap total 270336K, used 175129K [0x0000000602c00000, 0x0000000800000000)
|
||||||
|
region size 4096K, 24 young (98304K), 1 survivors (4096K)
|
||||||
|
Metaspace used 41184K, committed 41664K, reserved 1114112K
|
||||||
|
class space used 4542K, committed 4800K, reserved 1048576K
|
||||||
|
|
||||||
|
Heap Regions: E=young(eden), S=young(survivor), O=old, HS=humongous(starts), HC=humongous(continues), CS=collection set, F=free, OA=open archive, CA=closed archive, TAMS=top-at-mark-start (previous, next)
|
||||||
|
| 0|0x0000000602c00000, 0x0000000603000000, 0x0000000603000000|100%| O| |TAMS 0x0000000603000000, 0x0000000602c00000| Untracked
|
||||||
|
| 1|0x0000000603000000, 0x0000000603400000, 0x0000000603400000|100%| O| |TAMS 0x0000000603400000, 0x0000000603000000| Untracked
|
||||||
|
| 2|0x0000000603400000, 0x0000000603800000, 0x0000000603800000|100%| O| |TAMS 0x0000000603800000, 0x0000000603400000| Untracked
|
||||||
|
| 3|0x0000000603800000, 0x0000000603c00000, 0x0000000603c00000|100%| O| |TAMS 0x0000000603c00000, 0x0000000603800000| Untracked
|
||||||
|
| 4|0x0000000603c00000, 0x0000000604000000, 0x0000000604000000|100%| O| |TAMS 0x0000000604000000, 0x0000000603c00000| Untracked
|
||||||
|
| 5|0x0000000604000000, 0x0000000604400000, 0x0000000604400000|100%| O| |TAMS 0x0000000604400000, 0x0000000604000000| Untracked
|
||||||
|
| 6|0x0000000604400000, 0x0000000604800000, 0x0000000604800000|100%| O| |TAMS 0x0000000604800000, 0x0000000604400000| Untracked
|
||||||
|
| 7|0x0000000604800000, 0x0000000604c00000, 0x0000000604c00000|100%| O| |TAMS 0x0000000604c00000, 0x0000000604800000| Untracked
|
||||||
|
| 8|0x0000000604c00000, 0x0000000605000000, 0x0000000605000000|100%| O| |TAMS 0x0000000605000000, 0x0000000604c00000| Untracked
|
||||||
|
| 9|0x0000000605000000, 0x0000000605400000, 0x0000000605400000|100%| O| |TAMS 0x0000000605400000, 0x0000000605000000| Untracked
|
||||||
|
| 10|0x0000000605400000, 0x0000000605800000, 0x0000000605800000|100%| O| |TAMS 0x0000000605800000, 0x0000000605400000| Untracked
|
||||||
|
| 11|0x0000000605800000, 0x0000000605c00000, 0x0000000605c00000|100%|HS| |TAMS 0x0000000605800000, 0x0000000605800000| Complete
|
||||||
|
| 12|0x0000000605c00000, 0x0000000606000000, 0x0000000606000000|100%| O| |TAMS 0x0000000606000000, 0x0000000605c00000| Untracked
|
||||||
|
| 13|0x0000000606000000, 0x0000000606400000, 0x0000000606400000|100%| O| |TAMS 0x0000000606400000, 0x0000000606000000| Untracked
|
||||||
|
| 14|0x0000000606400000, 0x0000000606800000, 0x0000000606800000|100%| O| |TAMS 0x0000000606800000, 0x0000000606400000| Untracked
|
||||||
|
| 15|0x0000000606800000, 0x0000000606c00000, 0x0000000606c00000|100%| O| |TAMS 0x00000006068f1200, 0x0000000606800000| Untracked
|
||||||
|
| 16|0x0000000606c00000, 0x0000000607000000, 0x0000000607000000|100%| O| |TAMS 0x0000000606c00000, 0x0000000606c00000| Untracked
|
||||||
|
| 17|0x0000000607000000, 0x0000000607400000, 0x0000000607400000|100%| O| |TAMS 0x0000000607000000, 0x0000000607000000| Untracked
|
||||||
|
| 18|0x0000000607400000, 0x0000000607800000, 0x0000000607800000|100%| O| |TAMS 0x0000000607400000, 0x0000000607400000| Untracked
|
||||||
|
| 19|0x0000000607800000, 0x0000000607c00000, 0x0000000607c00000|100%| O| |TAMS 0x0000000607800000, 0x0000000607800000| Untracked
|
||||||
|
| 20|0x0000000607c00000, 0x0000000607e57600, 0x0000000608000000| 58%| O| |TAMS 0x0000000607c00000, 0x0000000607c00000| Untracked
|
||||||
|
| 21|0x0000000608000000, 0x0000000608000000, 0x0000000608400000| 0%| F| |TAMS 0x0000000608000000, 0x0000000608000000| Untracked
|
||||||
|
| 22|0x0000000608400000, 0x0000000608400000, 0x0000000608800000| 0%| F| |TAMS 0x0000000608400000, 0x0000000608400000| Untracked
|
||||||
|
| 23|0x0000000608800000, 0x0000000608800000, 0x0000000608c00000| 0%| F| |TAMS 0x0000000608800000, 0x0000000608800000| Untracked
|
||||||
|
| 24|0x0000000608c00000, 0x0000000608c00000, 0x0000000609000000| 0%| F| |TAMS 0x0000000608c00000, 0x0000000608c00000| Untracked
|
||||||
|
| 25|0x0000000609000000, 0x0000000609000000, 0x0000000609400000| 0%| F| |TAMS 0x0000000609000000, 0x0000000609000000| Untracked
|
||||||
|
| 26|0x0000000609400000, 0x0000000609400000, 0x0000000609800000| 0%| F| |TAMS 0x0000000609400000, 0x0000000609400000| Untracked
|
||||||
|
| 27|0x0000000609800000, 0x0000000609800000, 0x0000000609c00000| 0%| F| |TAMS 0x0000000609800000, 0x0000000609800000| Untracked
|
||||||
|
| 28|0x0000000609c00000, 0x0000000609c00000, 0x000000060a000000| 0%| F| |TAMS 0x0000000609c00000, 0x0000000609c00000| Untracked
|
||||||
|
| 29|0x000000060a000000, 0x000000060a000000, 0x000000060a400000| 0%| F| |TAMS 0x000000060a000000, 0x000000060a000000| Untracked
|
||||||
|
| 30|0x000000060a400000, 0x000000060a400000, 0x000000060a800000| 0%| F| |TAMS 0x000000060a400000, 0x000000060a400000| Untracked
|
||||||
|
| 31|0x000000060a800000, 0x000000060a800000, 0x000000060ac00000| 0%| F| |TAMS 0x000000060a800000, 0x000000060a800000| Untracked
|
||||||
|
| 32|0x000000060ac00000, 0x000000060ac00000, 0x000000060b000000| 0%| F| |TAMS 0x000000060ac00000, 0x000000060ac00000| Untracked
|
||||||
|
| 33|0x000000060b000000, 0x000000060b000000, 0x000000060b400000| 0%| F| |TAMS 0x000000060b000000, 0x000000060b000000| Untracked
|
||||||
|
| 34|0x000000060b400000, 0x000000060b400000, 0x000000060b800000| 0%| F| |TAMS 0x000000060b400000, 0x000000060b400000| Untracked
|
||||||
|
| 35|0x000000060b800000, 0x000000060b800000, 0x000000060bc00000| 0%| F| |TAMS 0x000000060b800000, 0x000000060b800000| Untracked
|
||||||
|
| 36|0x000000060bc00000, 0x000000060bc00000, 0x000000060c000000| 0%| F| |TAMS 0x000000060bc00000, 0x000000060bc00000| Untracked
|
||||||
|
| 37|0x000000060c000000, 0x000000060c000000, 0x000000060c400000| 0%| F| |TAMS 0x000000060c000000, 0x000000060c000000| Untracked
|
||||||
|
| 38|0x000000060c400000, 0x000000060c400000, 0x000000060c800000| 0%| F| |TAMS 0x000000060c400000, 0x000000060c400000| Untracked
|
||||||
|
| 39|0x000000060c800000, 0x000000060c800000, 0x000000060cc00000| 0%| F| |TAMS 0x000000060c800000, 0x000000060c800000| Untracked
|
||||||
|
| 40|0x000000060cc00000, 0x000000060cc00000, 0x000000060d000000| 0%| F| |TAMS 0x000000060cc00000, 0x000000060cc00000| Untracked
|
||||||
|
| 41|0x000000060d000000, 0x000000060d000000, 0x000000060d400000| 0%| F| |TAMS 0x000000060d000000, 0x000000060d000000| Untracked
|
||||||
|
| 42|0x000000060d400000, 0x000000060d800000, 0x000000060d800000|100%| E| |TAMS 0x000000060d400000, 0x000000060d400000| Complete
|
||||||
|
| 43|0x000000060d800000, 0x000000060dc00000, 0x000000060dc00000|100%| E|CS|TAMS 0x000000060d800000, 0x000000060d800000| Complete
|
||||||
|
| 44|0x000000060dc00000, 0x000000060e000000, 0x000000060e000000|100%| E|CS|TAMS 0x000000060dc00000, 0x000000060dc00000| Complete
|
||||||
|
| 45|0x000000060e000000, 0x000000060e400000, 0x000000060e400000|100%| E|CS|TAMS 0x000000060e000000, 0x000000060e000000| Complete
|
||||||
|
| 46|0x000000060e400000, 0x000000060e800000, 0x000000060e800000|100%| E|CS|TAMS 0x000000060e400000, 0x000000060e400000| Complete
|
||||||
|
| 47|0x000000060e800000, 0x000000060ec00000, 0x000000060ec00000|100%| E|CS|TAMS 0x000000060e800000, 0x000000060e800000| Complete
|
||||||
|
| 48|0x000000060ec00000, 0x000000060f000000, 0x000000060f000000|100%| E|CS|TAMS 0x000000060ec00000, 0x000000060ec00000| Complete
|
||||||
|
| 49|0x000000060f000000, 0x000000060f400000, 0x000000060f400000|100%| E|CS|TAMS 0x000000060f000000, 0x000000060f000000| Complete
|
||||||
|
| 50|0x000000060f400000, 0x000000060f800000, 0x000000060f800000|100%| E|CS|TAMS 0x000000060f400000, 0x000000060f400000| Complete
|
||||||
|
| 51|0x000000060f800000, 0x000000060fc00000, 0x000000060fc00000|100%| E|CS|TAMS 0x000000060f800000, 0x000000060f800000| Complete
|
||||||
|
| 52|0x000000060fc00000, 0x0000000610000000, 0x0000000610000000|100%| E|CS|TAMS 0x000000060fc00000, 0x000000060fc00000| Complete
|
||||||
|
| 53|0x0000000610000000, 0x0000000610400000, 0x0000000610400000|100%| E|CS|TAMS 0x0000000610000000, 0x0000000610000000| Complete
|
||||||
|
| 54|0x0000000610400000, 0x0000000610800000, 0x0000000610800000|100%| E|CS|TAMS 0x0000000610400000, 0x0000000610400000| Complete
|
||||||
|
| 55|0x0000000610800000, 0x0000000610c00000, 0x0000000610c00000|100%| E|CS|TAMS 0x0000000610800000, 0x0000000610800000| Complete
|
||||||
|
| 56|0x0000000610c00000, 0x0000000611000000, 0x0000000611000000|100%| E|CS|TAMS 0x0000000610c00000, 0x0000000610c00000| Complete
|
||||||
|
| 57|0x0000000611000000, 0x0000000611400000, 0x0000000611400000|100%| E|CS|TAMS 0x0000000611000000, 0x0000000611000000| Complete
|
||||||
|
| 58|0x0000000611400000, 0x0000000611800000, 0x0000000611800000|100%| E|CS|TAMS 0x0000000611400000, 0x0000000611400000| Complete
|
||||||
|
| 92|0x0000000619c00000, 0x000000061a000000, 0x000000061a000000|100%| E|CS|TAMS 0x0000000619c00000, 0x0000000619c00000| Complete
|
||||||
|
| 93|0x000000061a000000, 0x000000061a400000, 0x000000061a400000|100%| E|CS|TAMS 0x000000061a000000, 0x000000061a000000| Complete
|
||||||
|
| 94|0x000000061a400000, 0x000000061a800000, 0x000000061a800000|100%| E|CS|TAMS 0x000000061a400000, 0x000000061a400000| Complete
|
||||||
|
| 95|0x000000061a800000, 0x000000061ac00000, 0x000000061ac00000|100%| E|CS|TAMS 0x000000061a800000, 0x000000061a800000| Complete
|
||||||
|
| 96|0x000000061ac00000, 0x000000061b000000, 0x000000061b000000|100%| E|CS|TAMS 0x000000061ac00000, 0x000000061ac00000| Complete
|
||||||
|
| 97|0x000000061b000000, 0x000000061b0af1a0, 0x000000061b400000| 17%| S|CS|TAMS 0x000000061b000000, 0x000000061b000000| Complete
|
||||||
|
| 127|0x0000000622800000, 0x0000000622c00000, 0x0000000622c00000|100%| E|CS|TAMS 0x0000000622800000, 0x0000000622800000| Complete
|
||||||
|
|
||||||
|
Card table byte_map: [0x000000001aa50000,0x000000001ba40000] _byte_map_base: 0x0000000017a3a000
|
||||||
|
|
||||||
|
Marking Bits (Prev, Next): (CMBitMap*) 0x0000000002d06210, (CMBitMap*) 0x0000000002d06250
|
||||||
|
Prev Bits: [0x000000001ca30000, 0x0000000024980000)
|
||||||
|
Next Bits: [0x0000000024980000, 0x000000002c8d0000)
|
||||||
|
|
||||||
|
Polling page: 0x0000000000a50000
|
||||||
|
|
||||||
|
Metaspace:
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
Non-class: 35.78 MB used.
|
||||||
|
Class: 4.44 MB used.
|
||||||
|
Both: 40.22 MB used.
|
||||||
|
|
||||||
|
Virtual space:
|
||||||
|
Non-class space: 64.00 MB reserved, 36.00 MB ( 56%) committed, 1 nodes.
|
||||||
|
Class space: 1.00 GB reserved, 4.69 MB ( <1%) committed, 1 nodes.
|
||||||
|
Both: 1.06 GB reserved, 40.69 MB ( 4%) committed.
|
||||||
|
|
||||||
|
Chunk freelists:
|
||||||
|
Non-Class: 11.94 MB
|
||||||
|
Class: 11.16 MB
|
||||||
|
Both: 23.09 MB
|
||||||
|
|
||||||
|
MaxMetaspaceSize: unlimited
|
||||||
|
CompressedClassSpaceSize: 1.00 GB
|
||||||
|
Initial GC threshold: 21.00 MB
|
||||||
|
Current GC threshold: 59.06 MB
|
||||||
|
CDS: on
|
||||||
|
MetaspaceReclaimPolicy: balanced
|
||||||
|
- commit_granule_bytes: 65536.
|
||||||
|
- commit_granule_words: 8192.
|
||||||
|
- virtual_space_node_default_size: 8388608.
|
||||||
|
- enlarge_chunks_in_place: 1.
|
||||||
|
- new_chunks_are_fully_committed: 0.
|
||||||
|
- uncommit_free_chunks: 1.
|
||||||
|
- use_allocation_guard: 0.
|
||||||
|
- handle_deallocations: 1.
|
||||||
|
|
||||||
|
|
||||||
|
Internal statistics:
|
||||||
|
|
||||||
|
num_allocs_failed_limit: 6.
|
||||||
|
num_arena_births: 372.
|
||||||
|
num_arena_deaths: 0.
|
||||||
|
num_vsnodes_births: 2.
|
||||||
|
num_vsnodes_deaths: 0.
|
||||||
|
num_space_committed: 651.
|
||||||
|
num_space_uncommitted: 0.
|
||||||
|
num_chunks_returned_to_freelist: 6.
|
||||||
|
num_chunks_taken_from_freelist: 1957.
|
||||||
|
num_chunk_merges: 6.
|
||||||
|
num_chunk_splits: 1360.
|
||||||
|
num_chunks_enlarged: 1023.
|
||||||
|
num_inconsistent_stats: 0.
|
||||||
|
|
||||||
|
CodeHeap 'non-profiled nmethods': size=120000Kb used=7852Kb max_used=7865Kb free=112147Kb
|
||||||
|
bounds [0x0000000011fe0000, 0x0000000012790000, 0x0000000019510000]
|
||||||
|
CodeHeap 'profiled nmethods': size=120000Kb used=22769Kb max_used=22769Kb free=97230Kb
|
||||||
|
bounds [0x000000000a510000, 0x000000000bb50000, 0x0000000011a40000]
|
||||||
|
CodeHeap 'non-nmethods': size=5760Kb used=1252Kb max_used=1438Kb free=4507Kb
|
||||||
|
bounds [0x0000000011a40000, 0x0000000011cb0000, 0x0000000011fe0000]
|
||||||
|
total_blobs=10766 nmethods=10205 adapters=472
|
||||||
|
compilation: enabled
|
||||||
|
stopped_count=0, restarted_count=0
|
||||||
|
full_count=0
|
||||||
|
|
||||||
|
Compilation events (20 events):
|
||||||
|
Event: 11.646 Thread 0x000000002eda5210 12663 ! 3 java.util.zip.Deflater::needsInput (55 bytes)
|
||||||
|
Event: 11.646 Thread 0x000000002eda5210 nmethod 12663 0x000000000bb3fa90 code [0x000000000bb3fc40, 0x000000000bb3fff8]
|
||||||
|
Event: 11.646 Thread 0x000000002eda5210 12664 3 org.springframework.boot.loader.tools.AbstractJarWriter::updateLayerIndex (64 bytes)
|
||||||
|
Event: 11.647 Thread 0x000000002eda5210 nmethod 12664 0x000000000bb40110 code [0x000000000bb40340, 0x000000000bb40dd8]
|
||||||
|
Event: 11.647 Thread 0x000000002eda5210 12665 3 org.springframework.boot.loader.tools.JarWriter::writeToArchive (60 bytes)
|
||||||
|
Event: 11.647 Thread 0x000000002eda5210 nmethod 12665 0x000000000bb41110 code [0x000000000bb41380, 0x000000000bb42158]
|
||||||
|
Event: 11.647 Thread 0x000000002eda5210 12666 3 org.springframework.boot.loader.tools.AbstractJarWriter$InputStreamEntryWriter::<init> (10 bytes)
|
||||||
|
Event: 11.647 Thread 0x000000002eda5210 nmethod 12666 0x000000000bb42590 code [0x000000000bb42720, 0x000000000bb428d8]
|
||||||
|
Event: 11.649 Thread 0x000000002eda5210 12667 3 org.apache.commons.compress.archivers.zip.ZipArchiveEntry::<init> (126 bytes)
|
||||||
|
Event: 11.650 Thread 0x000000002eda5210 nmethod 12667 0x000000000bb42990 code [0x000000000bb42ca0, 0x000000000bb44178]
|
||||||
|
Event: 11.650 Thread 0x000000002eda5210 12668 3 java.time.LocalTime::create (26 bytes)
|
||||||
|
Event: 11.650 Thread 0x000000002eda5210 nmethod 12668 0x000000000bb44690 code [0x000000000bb44840, 0x000000000bb44af8]
|
||||||
|
Event: 11.654 Thread 0x000000002eda5210 12669 3 java.time.LocalDateTime::ofEpochSecond (77 bytes)
|
||||||
|
Event: 11.654 Thread 0x000000002eda5210 nmethod 12669 0x000000000bb44c10 code [0x000000000bb44e40, 0x000000000bb45578]
|
||||||
|
Event: 11.654 Thread 0x000000002eda5210 12670 3 java.util.zip.ZipUtils::get64 (17 bytes)
|
||||||
|
Event: 11.654 Thread 0x000000002eda5210 nmethod 12670 0x000000000bb45990 code [0x000000000bb45b40, 0x000000000bb45cb8]
|
||||||
|
Event: 11.654 Thread 0x000000002eda5210 12671 3 java.time.temporal.ValueRange::checkValidIntValue (25 bytes)
|
||||||
|
Event: 11.654 Thread 0x000000002eda5210 nmethod 12671 0x000000000bb45d90 code [0x000000000bb45f80, 0x000000000bb46558]
|
||||||
|
Event: 11.654 Thread 0x000000002eda5210 12672 3 org.apache.commons.compress.archivers.zip.ZipShort::getBytes (17 bytes)
|
||||||
|
Event: 11.654 Thread 0x000000002eda5210 nmethod 12672 0x000000000bb46710 code [0x000000000bb468c0, 0x000000000bb46ac8]
|
||||||
|
|
||||||
|
GC Heap History (20 events):
|
||||||
|
Event: 5.489 GC heap before
|
||||||
|
{Heap before GC invocations=10 (full 0):
|
||||||
|
garbage-first heap total 135168K, used 101138K [0x0000000602c00000, 0x0000000800000000)
|
||||||
|
region size 4096K, 13 young (53248K), 1 survivors (4096K)
|
||||||
|
Metaspace used 29754K, committed 30080K, reserved 1114112K
|
||||||
|
class space used 3318K, committed 3456K, reserved 1048576K
|
||||||
|
}
|
||||||
|
Event: 5.496 GC heap after
|
||||||
|
{Heap after GC invocations=11 (full 0):
|
||||||
|
garbage-first heap total 176128K, used 54864K [0x0000000602c00000, 0x0000000800000000)
|
||||||
|
region size 4096K, 2 young (8192K), 2 survivors (8192K)
|
||||||
|
Metaspace used 29754K, committed 30080K, reserved 1114112K
|
||||||
|
class space used 3318K, committed 3456K, reserved 1048576K
|
||||||
|
}
|
||||||
|
Event: 5.696 GC heap before
|
||||||
|
{Heap before GC invocations=11 (full 0):
|
||||||
|
garbage-first heap total 176128K, used 132688K [0x0000000602c00000, 0x0000000800000000)
|
||||||
|
region size 4096K, 21 young (86016K), 2 survivors (8192K)
|
||||||
|
Metaspace used 29800K, committed 30144K, reserved 1114112K
|
||||||
|
class space used 3319K, committed 3456K, reserved 1048576K
|
||||||
|
}
|
||||||
|
Event: 5.704 GC heap after
|
||||||
|
{Heap after GC invocations=12 (full 0):
|
||||||
|
garbage-first heap total 176128K, used 59591K [0x0000000602c00000, 0x0000000800000000)
|
||||||
|
region size 4096K, 3 young (12288K), 3 survivors (12288K)
|
||||||
|
Metaspace used 29800K, committed 30144K, reserved 1114112K
|
||||||
|
class space used 3319K, committed 3456K, reserved 1048576K
|
||||||
|
}
|
||||||
|
Event: 5.808 GC heap before
|
||||||
|
{Heap before GC invocations=12 (full 0):
|
||||||
|
garbage-first heap total 176128K, used 133319K [0x0000000602c00000, 0x0000000800000000)
|
||||||
|
region size 4096K, 21 young (86016K), 3 survivors (12288K)
|
||||||
|
Metaspace used 29812K, committed 30144K, reserved 1114112K
|
||||||
|
class space used 3319K, committed 3456K, reserved 1048576K
|
||||||
|
}
|
||||||
|
Event: 5.817 GC heap after
|
||||||
|
{Heap after GC invocations=13 (full 0):
|
||||||
|
garbage-first heap total 176128K, used 60441K [0x0000000602c00000, 0x0000000800000000)
|
||||||
|
region size 4096K, 2 young (8192K), 2 survivors (8192K)
|
||||||
|
Metaspace used 29812K, committed 30144K, reserved 1114112K
|
||||||
|
class space used 3319K, committed 3456K, reserved 1048576K
|
||||||
|
}
|
||||||
|
Event: 6.071 GC heap before
|
||||||
|
{Heap before GC invocations=13 (full 0):
|
||||||
|
garbage-first heap total 176128K, used 134169K [0x0000000602c00000, 0x0000000800000000)
|
||||||
|
region size 4096K, 20 young (81920K), 2 survivors (8192K)
|
||||||
|
Metaspace used 29952K, committed 30272K, reserved 1114112K
|
||||||
|
class space used 3322K, committed 3456K, reserved 1048576K
|
||||||
|
}
|
||||||
|
Event: 6.078 GC heap after
|
||||||
|
{Heap after GC invocations=14 (full 0):
|
||||||
|
garbage-first heap total 176128K, used 61766K [0x0000000602c00000, 0x0000000800000000)
|
||||||
|
region size 4096K, 2 young (8192K), 2 survivors (8192K)
|
||||||
|
Metaspace used 29952K, committed 30272K, reserved 1114112K
|
||||||
|
class space used 3322K, committed 3456K, reserved 1048576K
|
||||||
|
}
|
||||||
|
Event: 6.302 GC heap before
|
||||||
|
{Heap before GC invocations=14 (full 0):
|
||||||
|
garbage-first heap total 176128K, used 135494K [0x0000000602c00000, 0x0000000800000000)
|
||||||
|
region size 4096K, 20 young (81920K), 2 survivors (8192K)
|
||||||
|
Metaspace used 29976K, committed 30336K, reserved 1114112K
|
||||||
|
class space used 3322K, committed 3456K, reserved 1048576K
|
||||||
|
}
|
||||||
|
Event: 6.312 GC heap after
|
||||||
|
{Heap after GC invocations=15 (full 0):
|
||||||
|
garbage-first heap total 270336K, used 61575K [0x0000000602c00000, 0x0000000800000000)
|
||||||
|
region size 4096K, 1 young (4096K), 1 survivors (4096K)
|
||||||
|
Metaspace used 29976K, committed 30336K, reserved 1114112K
|
||||||
|
class space used 3322K, committed 3456K, reserved 1048576K
|
||||||
|
}
|
||||||
|
Event: 7.593 GC heap before
|
||||||
|
{Heap before GC invocations=15 (full 0):
|
||||||
|
garbage-first heap total 270336K, used 213127K [0x0000000602c00000, 0x0000000800000000)
|
||||||
|
region size 4096K, 38 young (155648K), 1 survivors (4096K)
|
||||||
|
Metaspace used 32451K, committed 32832K, reserved 1114112K
|
||||||
|
class space used 3563K, committed 3776K, reserved 1048576K
|
||||||
|
}
|
||||||
|
Event: 7.598 GC heap after
|
||||||
|
{Heap after GC invocations=16 (full 0):
|
||||||
|
garbage-first heap total 270336K, used 63871K [0x0000000602c00000, 0x0000000800000000)
|
||||||
|
region size 4096K, 2 young (8192K), 2 survivors (8192K)
|
||||||
|
Metaspace used 32451K, committed 32832K, reserved 1114112K
|
||||||
|
class space used 3563K, committed 3776K, reserved 1048576K
|
||||||
|
}
|
||||||
|
Event: 9.282 GC heap before
|
||||||
|
{Heap before GC invocations=16 (full 0):
|
||||||
|
garbage-first heap total 270336K, used 215423K [0x0000000602c00000, 0x0000000800000000)
|
||||||
|
region size 4096K, 39 young (159744K), 2 survivors (8192K)
|
||||||
|
Metaspace used 33942K, committed 34304K, reserved 1114112K
|
||||||
|
class space used 3709K, committed 3904K, reserved 1048576K
|
||||||
|
}
|
||||||
|
Event: 9.294 GC heap after
|
||||||
|
{Heap after GC invocations=17 (full 0):
|
||||||
|
garbage-first heap total 270336K, used 82884K [0x0000000602c00000, 0x0000000800000000)
|
||||||
|
region size 4096K, 5 young (20480K), 5 survivors (20480K)
|
||||||
|
Metaspace used 33942K, committed 34304K, reserved 1114112K
|
||||||
|
class space used 3709K, committed 3904K, reserved 1048576K
|
||||||
|
}
|
||||||
|
Event: 9.761 GC heap before
|
||||||
|
{Heap before GC invocations=17 (full 0):
|
||||||
|
garbage-first heap total 270336K, used 132036K [0x0000000602c00000, 0x0000000800000000)
|
||||||
|
region size 4096K, 18 young (73728K), 5 survivors (20480K)
|
||||||
|
Metaspace used 35675K, committed 36096K, reserved 1114112K
|
||||||
|
class space used 3911K, committed 4096K, reserved 1048576K
|
||||||
|
}
|
||||||
|
Event: 9.768 GC heap after
|
||||||
|
{Heap after GC invocations=18 (full 0):
|
||||||
|
garbage-first heap total 270336K, used 80167K [0x0000000602c00000, 0x0000000800000000)
|
||||||
|
region size 4096K, 1 young (4096K), 1 survivors (4096K)
|
||||||
|
Metaspace used 35675K, committed 36096K, reserved 1114112K
|
||||||
|
class space used 3911K, committed 4096K, reserved 1048576K
|
||||||
|
}
|
||||||
|
Event: 10.950 GC heap before
|
||||||
|
{Heap before GC invocations=19 (full 0):
|
||||||
|
garbage-first heap total 270336K, used 248103K [0x0000000602c00000, 0x0000000800000000)
|
||||||
|
region size 4096K, 27 young (110592K), 1 survivors (4096K)
|
||||||
|
Metaspace used 39221K, committed 39680K, reserved 1114112K
|
||||||
|
class space used 4328K, committed 4544K, reserved 1048576K
|
||||||
|
}
|
||||||
|
Event: 10.954 GC heap after
|
||||||
|
{Heap after GC invocations=20 (full 0):
|
||||||
|
garbage-first heap total 270336K, used 82867K [0x0000000602c00000, 0x0000000800000000)
|
||||||
|
region size 4096K, 2 young (8192K), 2 survivors (8192K)
|
||||||
|
Metaspace used 39221K, committed 39680K, reserved 1114112K
|
||||||
|
class space used 4328K, committed 4544K, reserved 1048576K
|
||||||
|
}
|
||||||
|
Event: 10.988 GC heap before
|
||||||
|
{Heap before GC invocations=20 (full 0):
|
||||||
|
garbage-first heap total 270336K, used 82867K [0x0000000602c00000, 0x0000000800000000)
|
||||||
|
region size 4096K, 3 young (12288K), 2 survivors (8192K)
|
||||||
|
Metaspace used 39234K, committed 39744K, reserved 1114112K
|
||||||
|
class space used 4328K, committed 4544K, reserved 1048576K
|
||||||
|
}
|
||||||
|
Event: 10.992 GC heap after
|
||||||
|
{Heap after GC invocations=21 (full 0):
|
||||||
|
garbage-first heap total 270336K, used 80921K [0x0000000602c00000, 0x0000000800000000)
|
||||||
|
region size 4096K, 1 young (4096K), 1 survivors (4096K)
|
||||||
|
Metaspace used 39234K, committed 39744K, reserved 1114112K
|
||||||
|
class space used 4328K, committed 4544K, reserved 1048576K
|
||||||
|
}
|
||||||
|
|
||||||
|
Deoptimization events (20 events):
|
||||||
|
Event: 11.567 Thread 0x0000000002c571b0 DEOPT PACKING pc=0x00000000126c85e0 sp=0x000000000241db60
|
||||||
|
Event: 11.567 Thread 0x0000000002c571b0 DEOPT UNPACKING pc=0x0000000011a923a3 sp=0x000000000241db00 mode 2
|
||||||
|
Event: 11.579 Thread 0x0000000002c571b0 Uncommon trap: trap_request=0xffffff6e fr.pc=0x000000001269723c relative=0x000000000000065c
|
||||||
|
Event: 11.579 Thread 0x0000000002c571b0 Uncommon trap: reason=loop_limit_check action=maybe_recompile pc=0x000000001269723c method=org.codehaus.plexus.util.SelectorUtils.matchAntPathPattern([[C[[CZ)Z @ 231 c2
|
||||||
|
Event: 11.579 Thread 0x0000000002c571b0 DEOPT PACKING pc=0x000000001269723c sp=0x000000000241dd20
|
||||||
|
Event: 11.579 Thread 0x0000000002c571b0 DEOPT UNPACKING pc=0x0000000011a923a3 sp=0x000000000241dc70 mode 2
|
||||||
|
Event: 11.580 Thread 0x0000000002c571b0 Uncommon trap: trap_request=0xffffffde fr.pc=0x00000000121e3028 relative=0x0000000000002ae8
|
||||||
|
Event: 11.580 Thread 0x0000000002c571b0 Uncommon trap: reason=class_check action=maybe_recompile pc=0x00000000121e3028 method=org.apache.maven.model.Plugin.clone()Lorg/apache/maven/model/Plugin; @ 94 c2
|
||||||
|
Event: 11.580 Thread 0x0000000002c571b0 DEOPT PACKING pc=0x00000000121e3028 sp=0x000000000241d9d0
|
||||||
|
Event: 11.580 Thread 0x0000000002c571b0 DEOPT UNPACKING pc=0x0000000011a923a3 sp=0x000000000241d9f0 mode 2
|
||||||
|
Event: 11.593 Thread 0x0000000002c571b0 Uncommon trap: trap_request=0xffffffde fr.pc=0x00000000124f7304 relative=0x0000000000000064
|
||||||
|
Event: 11.593 Thread 0x0000000002c571b0 Uncommon trap: reason=class_check action=maybe_recompile pc=0x00000000124f7304 method=java.lang.invoke.Invokers$Holder.linkToTargetMethod(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; @ 1 c2
|
||||||
|
Event: 11.593 Thread 0x0000000002c571b0 DEOPT PACKING pc=0x00000000124f7304 sp=0x000000000241e000
|
||||||
|
Event: 11.594 Thread 0x0000000002c571b0 DEOPT UNPACKING pc=0x0000000011a923a3 sp=0x000000000241df90 mode 2
|
||||||
|
Event: 11.595 Thread 0x0000000002c571b0 DEOPT PACKING pc=0x000000000aec2cfe sp=0x000000000241d7f0
|
||||||
|
Event: 11.595 Thread 0x0000000002c571b0 DEOPT UNPACKING pc=0x0000000011a92b43 sp=0x000000000241cd70 mode 0
|
||||||
|
Event: 11.599 Thread 0x0000000002c571b0 DEOPT PACKING pc=0x000000000aec2cfe sp=0x000000000241d7f0
|
||||||
|
Event: 11.599 Thread 0x0000000002c571b0 DEOPT UNPACKING pc=0x0000000011a92b43 sp=0x000000000241cd70 mode 0
|
||||||
|
Event: 11.608 Thread 0x0000000002c571b0 DEOPT PACKING pc=0x000000000aec2cfe sp=0x000000000241d850
|
||||||
|
Event: 11.608 Thread 0x0000000002c571b0 DEOPT UNPACKING pc=0x0000000011a92b43 sp=0x000000000241cdd0 mode 0
|
||||||
|
|
||||||
|
Classes unloaded (0 events):
|
||||||
|
No events
|
||||||
|
|
||||||
|
Classes redefined (0 events):
|
||||||
|
No events
|
||||||
|
|
||||||
|
Internal exceptions (20 events):
|
||||||
|
Event: 11.509 Thread 0x0000000002c571b0 Exception <a 'sun/nio/fs/WindowsException'{0x000000060f221a40}> (0x000000060f221a40)
|
||||||
|
thrown [s\open\src\hotspot\share\prims\jni.cpp, line 516]
|
||||||
|
Event: 11.509 Thread 0x0000000002c571b0 Exception <a 'sun/nio/fs/WindowsException'{0x000000060f22cc88}> (0x000000060f22cc88)
|
||||||
|
thrown [s\open\src\hotspot\share\prims\jni.cpp, line 516]
|
||||||
|
Event: 11.509 Thread 0x0000000002c571b0 Exception <a 'sun/nio/fs/WindowsException'{0x000000060f22cf58}> (0x000000060f22cf58)
|
||||||
|
thrown [s\open\src\hotspot\share\prims\jni.cpp, line 516]
|
||||||
|
Event: 11.509 Thread 0x0000000002c571b0 Exception <a 'sun/nio/fs/WindowsException'{0x000000060f236488}> (0x000000060f236488)
|
||||||
|
thrown [s\open\src\hotspot\share\prims\jni.cpp, line 516]
|
||||||
|
Event: 11.509 Thread 0x0000000002c571b0 Exception <a 'sun/nio/fs/WindowsException'{0x000000060f2366c8}> (0x000000060f2366c8)
|
||||||
|
thrown [s\open\src\hotspot\share\prims\jni.cpp, line 516]
|
||||||
|
Event: 11.509 Thread 0x0000000002c571b0 Exception <a 'sun/nio/fs/WindowsException'{0x000000060f24d168}> (0x000000060f24d168)
|
||||||
|
thrown [s\open\src\hotspot\share\prims\jni.cpp, line 516]
|
||||||
|
Event: 11.509 Thread 0x0000000002c571b0 Exception <a 'sun/nio/fs/WindowsException'{0x000000060f24d438}> (0x000000060f24d438)
|
||||||
|
thrown [s\open\src\hotspot\share\prims\jni.cpp, line 516]
|
||||||
|
Event: 11.509 Thread 0x0000000002c571b0 Exception <a 'sun/nio/fs/WindowsException'{0x000000060f24ee10}> (0x000000060f24ee10)
|
||||||
|
thrown [s\open\src\hotspot\share\prims\jni.cpp, line 516]
|
||||||
|
Event: 11.509 Thread 0x0000000002c571b0 Exception <a 'sun/nio/fs/WindowsException'{0x000000060f24f170}> (0x000000060f24f170)
|
||||||
|
thrown [s\open\src\hotspot\share\prims\jni.cpp, line 516]
|
||||||
|
Event: 11.510 Thread 0x0000000002c571b0 Exception <a 'sun/nio/fs/WindowsException'{0x000000060f262b00}> (0x000000060f262b00)
|
||||||
|
thrown [s\open\src\hotspot\share\prims\jni.cpp, line 516]
|
||||||
|
Event: 11.510 Thread 0x0000000002c571b0 Exception <a 'sun/nio/fs/WindowsException'{0x000000060f262de8}> (0x000000060f262de8)
|
||||||
|
thrown [s\open\src\hotspot\share\prims\jni.cpp, line 516]
|
||||||
|
Event: 11.510 Thread 0x0000000002c571b0 Exception <a 'sun/nio/fs/WindowsException'{0x000000060f2690f0}> (0x000000060f2690f0)
|
||||||
|
thrown [s\open\src\hotspot\share\prims\jni.cpp, line 516]
|
||||||
|
Event: 11.510 Thread 0x0000000002c571b0 Exception <a 'sun/nio/fs/WindowsException'{0x000000060f269348}> (0x000000060f269348)
|
||||||
|
thrown [s\open\src\hotspot\share\prims\jni.cpp, line 516]
|
||||||
|
Event: 11.510 Thread 0x0000000002c571b0 Exception <a 'sun/nio/fs/WindowsException'{0x000000060f2792b8}> (0x000000060f2792b8)
|
||||||
|
thrown [s\open\src\hotspot\share\prims\jni.cpp, line 516]
|
||||||
|
Event: 11.510 Thread 0x0000000002c571b0 Exception <a 'sun/nio/fs/WindowsException'{0x000000060f279598}> (0x000000060f279598)
|
||||||
|
thrown [s\open\src\hotspot\share\prims\jni.cpp, line 516]
|
||||||
|
Event: 11.510 Thread 0x0000000002c571b0 Exception <a 'sun/nio/fs/WindowsException'{0x000000060f27af78}> (0x000000060f27af78)
|
||||||
|
thrown [s\open\src\hotspot\share\prims\jni.cpp, line 516]
|
||||||
|
Event: 11.510 Thread 0x0000000002c571b0 Exception <a 'sun/nio/fs/WindowsException'{0x000000060f27b2d8}> (0x000000060f27b2d8)
|
||||||
|
thrown [s\open\src\hotspot\share\prims\jni.cpp, line 516]
|
||||||
|
Event: 11.516 Thread 0x0000000002c571b0 Exception <a 'sun/nio/fs/WindowsException'{0x000000060f3ceca0}> (0x000000060f3ceca0)
|
||||||
|
thrown [s\open\src\hotspot\share\prims\jni.cpp, line 516]
|
||||||
|
Event: 11.518 Thread 0x0000000002c571b0 Exception <a 'sun/nio/fs/WindowsException'{0x000000060f3e6198}> (0x000000060f3e6198)
|
||||||
|
thrown [s\open\src\hotspot\share\prims\jni.cpp, line 516]
|
||||||
|
Event: 11.519 Thread 0x0000000002c571b0 Exception <a 'sun/nio/fs/WindowsException'{0x000000060f3fd538}> (0x000000060f3fd538)
|
||||||
|
thrown [s\open\src\hotspot\share\prims\jni.cpp, line 516]
|
||||||
|
|
||||||
|
VM Operations (20 events):
|
||||||
|
Event: 10.085 Executing VM operation: HandshakeAllThreads
|
||||||
|
Event: 10.085 Executing VM operation: HandshakeAllThreads done
|
||||||
|
Event: 10.365 Executing VM operation: HandshakeAllThreads
|
||||||
|
Event: 10.365 Executing VM operation: HandshakeAllThreads done
|
||||||
|
Event: 10.429 Executing VM operation: HandshakeAllThreads
|
||||||
|
Event: 10.429 Executing VM operation: HandshakeAllThreads done
|
||||||
|
Event: 10.854 Executing VM operation: ICBufferFull
|
||||||
|
Event: 10.854 Executing VM operation: ICBufferFull done
|
||||||
|
Event: 10.878 Executing VM operation: HandshakeAllThreads
|
||||||
|
Event: 10.878 Executing VM operation: HandshakeAllThreads done
|
||||||
|
Event: 10.879 Executing VM operation: HandshakeAllThreads
|
||||||
|
Event: 10.879 Executing VM operation: HandshakeAllThreads done
|
||||||
|
Event: 10.950 Executing VM operation: G1CollectForAllocation
|
||||||
|
Event: 10.954 Executing VM operation: G1CollectForAllocation done
|
||||||
|
Event: 10.988 Executing VM operation: G1CollectForAllocation
|
||||||
|
Event: 10.992 Executing VM operation: G1CollectForAllocation done
|
||||||
|
Event: 11.523 Executing VM operation: HandshakeAllThreads
|
||||||
|
Event: 11.523 Executing VM operation: HandshakeAllThreads done
|
||||||
|
Event: 11.577 Executing VM operation: ICBufferFull
|
||||||
|
Event: 11.577 Executing VM operation: ICBufferFull done
|
||||||
|
|
||||||
|
Events (20 events):
|
||||||
|
Event: 11.571 loading class java/lang/constant/DirectMethodHandleDescImpl$1
|
||||||
|
Event: 11.571 loading class java/lang/constant/DirectMethodHandleDescImpl$1 done
|
||||||
|
Event: 11.571 loading class java/lang/constant/DirectMethodHandleDesc$1
|
||||||
|
Event: 11.571 loading class java/lang/constant/DirectMethodHandleDesc$1 done
|
||||||
|
Event: 11.571 loading class java/lang/constant/PrimitiveClassDescImpl
|
||||||
|
Event: 11.571 loading class java/lang/constant/DynamicConstantDesc
|
||||||
|
Event: 11.571 loading class java/lang/constant/DynamicConstantDesc done
|
||||||
|
Event: 11.571 loading class java/lang/constant/PrimitiveClassDescImpl done
|
||||||
|
Event: 11.571 loading class java/lang/constant/DynamicConstantDesc$AnonymousDynamicConstantDesc
|
||||||
|
Event: 11.571 loading class java/lang/constant/DynamicConstantDesc$AnonymousDynamicConstantDesc done
|
||||||
|
Event: 11.591 Thread 0x00000000784a98e0 Thread added: 0x00000000784a98e0
|
||||||
|
Event: 11.591 Thread 0x00000000784ab230 Thread added: 0x00000000784ab230
|
||||||
|
Event: 11.591 Thread 0x00000000784a7a80 Thread added: 0x00000000784a7a80
|
||||||
|
Event: 11.591 Thread 0x00000000784ab740 Thread added: 0x00000000784ab740
|
||||||
|
Event: 11.591 Thread 0x00000000784a7f90 Thread added: 0x00000000784a7f90
|
||||||
|
Event: 11.592 Thread 0x00000000784a7f90 Thread exited: 0x00000000784a7f90
|
||||||
|
Event: 11.592 Thread 0x00000000784ab230 Thread exited: 0x00000000784ab230
|
||||||
|
Event: 11.592 Thread 0x00000000784a98e0 Thread exited: 0x00000000784a98e0
|
||||||
|
Event: 11.592 Thread 0x00000000784ab740 Thread exited: 0x00000000784ab740
|
||||||
|
Event: 11.592 Thread 0x00000000784a7a80 Thread exited: 0x00000000784a7a80
|
||||||
|
|
||||||
|
|
||||||
|
Dynamic libraries:
|
||||||
|
0x00007ff767f90000 - 0x00007ff767fa0000 D:\develop\JDK17\bin\java.exe
|
||||||
|
0x00007ffcd8da0000 - 0x00007ffcd9007000 C:\WINDOWS\SYSTEM32\ntdll.dll
|
||||||
|
0x00007ffcd7bc0000 - 0x00007ffcd7c89000 C:\WINDOWS\System32\KERNEL32.DLL
|
||||||
|
0x00007ffcd62f0000 - 0x00007ffcd66df000 C:\WINDOWS\System32\KERNELBASE.dll
|
||||||
|
0x00007ffcd5ef0000 - 0x00007ffcd603b000 C:\WINDOWS\System32\ucrtbase.dll
|
||||||
|
0x00007ffccfe30000 - 0x00007ffccfe49000 D:\develop\JDK17\bin\jli.dll
|
||||||
|
0x00007ffca8f40000 - 0x00007ffca8f5b000 D:\develop\JDK17\bin\VCRUNTIME140.dll
|
||||||
|
0x00007ffcd6eb0000 - 0x00007ffcd6f64000 C:\WINDOWS\System32\ADVAPI32.dll
|
||||||
|
0x00007ffcd84d0000 - 0x00007ffcd8579000 C:\WINDOWS\System32\msvcrt.dll
|
||||||
|
0x00007ffcd7e70000 - 0x00007ffcd7f16000 C:\WINDOWS\System32\sechost.dll
|
||||||
|
0x00007ffcd8bb0000 - 0x00007ffcd8cc8000 C:\WINDOWS\System32\RPCRT4.dll
|
||||||
|
0x00007ffcd89e0000 - 0x00007ffcd8ba5000 C:\WINDOWS\System32\USER32.dll
|
||||||
|
0x00007ffcd6b10000 - 0x00007ffcd6b37000 C:\WINDOWS\System32\win32u.dll
|
||||||
|
0x00007ffcd4ff0000 - 0x00007ffcd5283000 C:\WINDOWS\WinSxS\amd64_microsoft.windows.common-controls_6595b64144ccf1df_6.0.26100.7309_none_3e05feeae336a044\COMCTL32.dll
|
||||||
|
0x00007ffcd83f0000 - 0x00007ffcd841b000 C:\WINDOWS\System32\GDI32.dll
|
||||||
|
0x00007ffcd66f0000 - 0x00007ffcd681c000 C:\WINDOWS\System32\gdi32full.dll
|
||||||
|
0x00007ffcd60d0000 - 0x00007ffcd6173000 C:\WINDOWS\System32\msvcp_win.dll
|
||||||
|
0x00007ffccc960000 - 0x00007ffccc96b000 C:\WINDOWS\SYSTEM32\VERSION.dll
|
||||||
|
0x00007ffcd7f20000 - 0x00007ffcd7f51000 C:\WINDOWS\System32\IMM32.DLL
|
||||||
|
0x00007ffccff40000 - 0x00007ffccff4c000 D:\develop\JDK17\bin\vcruntime140_1.dll
|
||||||
|
0x00007ffc79be0000 - 0x00007ffc79c6e000 D:\develop\JDK17\bin\msvcp140.dll
|
||||||
|
0x00007ffbad310000 - 0x00007ffbadef0000 D:\develop\JDK17\bin\server\jvm.dll
|
||||||
|
0x00007ffcd8d50000 - 0x00007ffcd8d58000 C:\WINDOWS\System32\PSAPI.DLL
|
||||||
|
0x00007ffcbb3e0000 - 0x00007ffcbb3ea000 C:\WINDOWS\SYSTEM32\WSOCK32.dll
|
||||||
|
0x00007ffcc6bf0000 - 0x00007ffcc6c25000 C:\WINDOWS\SYSTEM32\WINMM.dll
|
||||||
|
0x00007ffcd8960000 - 0x00007ffcd89d4000 C:\WINDOWS\System32\WS2_32.dll
|
||||||
|
0x00007ffcd4370000 - 0x00007ffcd438b000 C:\WINDOWS\SYSTEM32\kernel.appcore.dll
|
||||||
|
0x00007ffcc2eb0000 - 0x00007ffcc2eba000 D:\develop\JDK17\bin\jimage.dll
|
||||||
|
0x00007ffcd56a0000 - 0x00007ffcd58e2000 C:\WINDOWS\SYSTEM32\DBGHELP.DLL
|
||||||
|
0x00007ffcd8580000 - 0x00007ffcd8905000 C:\WINDOWS\System32\combase.dll
|
||||||
|
0x00007ffcd7c90000 - 0x00007ffcd7d66000 C:\WINDOWS\System32\OLEAUT32.dll
|
||||||
|
0x00007ffcd5390000 - 0x00007ffcd53cc000 C:\WINDOWS\SYSTEM32\dbgcore.DLL
|
||||||
|
0x00007ffcd6a60000 - 0x00007ffcd6b05000 C:\WINDOWS\System32\bcryptPrimitives.dll
|
||||||
|
0x00007ffcb1ab0000 - 0x00007ffcb1abe000 D:\develop\JDK17\bin\instrument.dll
|
||||||
|
0x00007ffc9a370000 - 0x00007ffc9a395000 D:\develop\JDK17\bin\java.dll
|
||||||
|
0x00007ffc79760000 - 0x00007ffc79837000 D:\develop\JDK17\bin\jsvml.dll
|
||||||
|
0x00007ffcd6f70000 - 0x00007ffcd76c3000 C:\WINDOWS\System32\SHELL32.dll
|
||||||
|
0x00007ffcd6180000 - 0x00007ffcd62ea000 C:\WINDOWS\System32\wintypes.dll
|
||||||
|
0x00007ffcd30a0000 - 0x00007ffcd38fe000 C:\WINDOWS\SYSTEM32\windows.storage.dll
|
||||||
|
0x00007ffcd6d30000 - 0x00007ffcd6e25000 C:\WINDOWS\System32\SHCORE.dll
|
||||||
|
0x00007ffcd6e30000 - 0x00007ffcd6e96000 C:\WINDOWS\System32\shlwapi.dll
|
||||||
|
0x00007ffcd5e10000 - 0x00007ffcd5e39000 C:\WINDOWS\SYSTEM32\profapi.dll
|
||||||
|
0x00007ffc91c60000 - 0x00007ffc91c78000 D:\develop\JDK17\bin\zip.dll
|
||||||
|
0x00007ffc8a530000 - 0x00007ffc8a549000 D:\develop\JDK17\bin\net.dll
|
||||||
|
0x00007ffccd3a0000 - 0x00007ffccd4c8000 C:\WINDOWS\SYSTEM32\WINHTTP.dll
|
||||||
|
0x00007ffcd48a0000 - 0x00007ffcd490b000 C:\WINDOWS\system32\mswsock.dll
|
||||||
|
0x00007ffc79bc0000 - 0x00007ffc79bd6000 D:\develop\JDK17\bin\nio.dll
|
||||||
|
0x0000000000d30000 - 0x0000000000d54000 C:\Users\18969\AppData\Local\Temp\jansi-2.4.0-7461ad60e422ef5a-jansi.dll
|
||||||
|
0x00007ffca8f30000 - 0x00007ffca8f40000 D:\develop\JDK17\bin\verify.dll
|
||||||
|
|
||||||
|
dbghelp: loaded successfully - version: 4.0.5 - missing functions: none
|
||||||
|
symbol engine: initialized successfully - sym options: 0x614 - pdb path: .;D:\develop\JDK17\bin;C:\WINDOWS\SYSTEM32;C:\WINDOWS\WinSxS\amd64_microsoft.windows.common-controls_6595b64144ccf1df_6.0.26100.7309_none_3e05feeae336a044;D:\develop\JDK17\bin\server;C:\Users\18969\AppData\Local\Temp
|
||||||
|
|
||||||
|
VM Arguments:
|
||||||
|
jvm_args: -Dmaven.multiModuleProjectDirectory=E:\MTKJPAY -Djansi.passthrough=true -Dmaven.home=D:/develop/Maven/apache-maven-3.9.4 -Dclassworlds.conf=D:\develop\Maven\apache-maven-3.9.4\bin\m2.conf -Dmaven.ext.class.path=D:\develop\IntelliJ IDEA 2025.1\plugins\maven\lib\maven-event-listener.jar -javaagent:D:\develop\IntelliJ IDEA 2025.1\lib\idea_rt.jar=57907 -Dfile.encoding=UTF-8
|
||||||
|
java_command: org.codehaus.classworlds.Launcher -Didea.version=2025.1.1.1 -s D:\develop\Maven\apache-maven-3.9.4\conf\settings.xml -Dmaven.repo.local=D:\develop\Maven\repository package
|
||||||
|
java_class_path (initial): D:\develop\Maven\apache-maven-3.9.4\boot\plexus-classworlds-2.7.0.jar;D:\develop\Maven\apache-maven-3.9.4\boot\plexus-classworlds.license
|
||||||
|
Launcher Type: SUN_STANDARD
|
||||||
|
|
||||||
|
[Global flags]
|
||||||
|
intx CICompilerCount = 4 {product} {ergonomic}
|
||||||
|
uint ConcGCThreads = 3 {product} {ergonomic}
|
||||||
|
uint G1ConcRefinementThreads = 10 {product} {ergonomic}
|
||||||
|
size_t G1HeapRegionSize = 4194304 {product} {ergonomic}
|
||||||
|
uintx GCDrainStackTargetSize = 64 {product} {ergonomic}
|
||||||
|
size_t InitialHeapSize = 536870912 {product} {ergonomic}
|
||||||
|
size_t MarkStackSize = 4194304 {product} {ergonomic}
|
||||||
|
size_t MaxHeapSize = 8543797248 {product} {ergonomic}
|
||||||
|
size_t MaxNewSize = 5125439488 {product} {ergonomic}
|
||||||
|
size_t MinHeapDeltaBytes = 4194304 {product} {ergonomic}
|
||||||
|
size_t MinHeapSize = 8388608 {product} {ergonomic}
|
||||||
|
uintx NonNMethodCodeHeapSize = 5839372 {pd product} {ergonomic}
|
||||||
|
uintx NonProfiledCodeHeapSize = 122909434 {pd product} {ergonomic}
|
||||||
|
uintx ProfiledCodeHeapSize = 122909434 {pd product} {ergonomic}
|
||||||
|
uintx ReservedCodeCacheSize = 251658240 {pd product} {ergonomic}
|
||||||
|
bool SegmentedCodeCache = true {product} {ergonomic}
|
||||||
|
size_t SoftMaxHeapSize = 8543797248 {manageable} {ergonomic}
|
||||||
|
bool UseCompressedClassPointers = true {product lp64_product} {ergonomic}
|
||||||
|
bool UseCompressedOops = true {product lp64_product} {ergonomic}
|
||||||
|
bool UseG1GC = true {product} {ergonomic}
|
||||||
|
bool UseLargePagesIndividualAllocation = false {pd product} {ergonomic}
|
||||||
|
|
||||||
|
Logging:
|
||||||
|
Log output configuration:
|
||||||
|
#0: stdout all=warning uptime,level,tags
|
||||||
|
#1: stderr all=off uptime,level,tags
|
||||||
|
|
||||||
|
Environment Variables:
|
||||||
|
PATH=D:\develop\VMware\VMware Workstation\bin\;C:\Program Files\Common Files\Oracle\Java\javapath;D:\develop\JDK17bin;D:\Develop\jdk14\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\WINDOWS\System32\OpenSSH\;D:\bandizip\;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\WINDOWS\System32\OpenSSH\;C:\Program Files\NVIDIA Corporation\NVIDIA App\NvDLISR;D:\develop\Maven\apache-maven-3.9.4\bin;D:\develop\Maven\apache-maven-3.9.4\bin;D:\Apps\MyselfApp\Bandizip\;D:\develop\MySQL\Program Files\bin;D:\develop\Git\cmd;D:\develop\nodejs\;D:\develop\;D:\develop\<><CEA2>web<65><62><EFBFBD><EFBFBD><EFBFBD>߹<EFBFBD><DFB9><EFBFBD>\dll;D:\develop\OllamaModels;C:\Program Files\dotnet\;D:\develop\cursor\resources\app\bin;C:\Users\18969\AppData\Local\Microsoft\WindowsApps;;D:\develop\IntelliJ IDEA 2025.1\bin;;D:\develop\DataGrip 2024.3.4\bin;;D:\develop\Microsoft VS Code\Microsoft VS Code\bin;C:\Users\18969\AppData\Roaming\npm;D:\develop\cpolar\;D:\develop\Ollama
|
||||||
|
USERNAME=18969
|
||||||
|
OS=Windows_NT
|
||||||
|
PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 165 Stepping 2, GenuineIntel
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--------------- S Y S T E M ---------------
|
||||||
|
|
||||||
|
OS:
|
||||||
|
Windows 11 , 64 bit Build 26100 (10.0.26100.7309)
|
||||||
|
OS uptime: 0 days 0:15 hours
|
||||||
|
|
||||||
|
CPU: total 12 (initial active 12) (6 cores per cpu, 2 threads per core) family 6 model 165 stepping 2 microcode 0xe0, cx8, cmov, fxsr, ht, mmx, 3dnowpref, sse, sse2, sse3, ssse3, sse4.1, sse4.2, popcnt, lzcnt, tsc, tscinvbit, avx, avx2, aes, erms, clmul, bmi1, bmi2, adx, fma, vzeroupper, clflush, clflushopt
|
||||||
|
|
||||||
|
Memory: 4k page, system-wide physical 32591M (11550M free)
|
||||||
|
TotalPageFile size 38479M (AvailPageFile size 17112M)
|
||||||
|
current process WorkingSet (physical memory assigned to process): 563M, peak: 564M
|
||||||
|
current process commit charge ("private bytes"): 597M, peak: 691M
|
||||||
|
|
||||||
|
vm_info: Java HotSpot(TM) 64-Bit Server VM (17.0.12+8-LTS-286) for windows-amd64 JRE (17.0.12+8-LTS-286), built on Jun 5 2024 06:46:59 by "mach5one" with MS VC++ 17.6 (VS2022)
|
||||||
|
|
||||||
|
END.
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -1,43 +1,9 @@
|
|||||||
# ERP用户管理API文档
|
# ERP用户管理API
|
||||||
|
|
||||||
## 概述
|
## 接口列表
|
||||||
|
|
||||||
ERP用户管理模块提供了用户注册、登录等基础功能,支持账号、密码、店铺号管理。
|
|
||||||
|
|
||||||
## 数据库表
|
|
||||||
|
|
||||||
### erp_user 表结构
|
|
||||||
|
|
||||||
执行以下SQL创建表:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 文件位置:mt-pay/database/erp_user_schema.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
表字段说明:
|
|
||||||
- `id`: 主键ID
|
|
||||||
- `username`: 账号(唯一,3-50个字符,只能包含字母、数字和下划线)
|
|
||||||
- `password`: 密码(MD5加密,6-20个字符)
|
|
||||||
- `nick_name`: 用户名称(可选,最大50个字符)
|
|
||||||
- `phone`: 手机号(可选,唯一,格式:1开头11位数字)
|
|
||||||
- `email`: 邮箱(可选,唯一,最大100个字符)
|
|
||||||
- `store_code`: 店铺号(最大50个字符)
|
|
||||||
- `status`: 状态(ACTIVE-激活,DISABLED-禁用)
|
|
||||||
- `last_login_time`: 最后登录时间
|
|
||||||
- `last_login_ip`: 最后登录IP
|
|
||||||
- `create_time`: 创建时间
|
|
||||||
- `update_time`: 更新时间
|
|
||||||
|
|
||||||
## API接口
|
|
||||||
|
|
||||||
### 1. 用户注册
|
### 1. 用户注册
|
||||||
|
**POST** `/api/erp/user/register`
|
||||||
**接口地址:** `POST /api/erp/user/register`
|
|
||||||
|
|
||||||
**请求头:**
|
|
||||||
```
|
|
||||||
Content-Type: application/json
|
|
||||||
```
|
|
||||||
|
|
||||||
**请求体:**
|
**请求体:**
|
||||||
```json
|
```json
|
||||||
@@ -51,49 +17,8 @@ Content-Type: application/json
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**参数说明:**
|
|
||||||
- `username` (必填): 账号,3-50个字符,只能包含字母、数字和下划线
|
|
||||||
- `password` (必填): 密码,6-20个字符
|
|
||||||
- `storeCode` (必填): 店铺号,最大50个字符
|
|
||||||
- `nickName` (可选): 用户名称,最大50个字符
|
|
||||||
- `phone` (可选): 手机号,格式:1开头11位数字(如:13800138000),必须唯一
|
|
||||||
- `email` (可选): 邮箱,最大100个字符,必须唯一
|
|
||||||
|
|
||||||
**响应示例:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": "0000",
|
|
||||||
"message": "注册成功",
|
|
||||||
"data": {
|
|
||||||
"id": 1,
|
|
||||||
"username": "testuser",
|
|
||||||
"nickName": "测试用户",
|
|
||||||
"phone": "13800138000",
|
|
||||||
"email": "test@example.com",
|
|
||||||
"storeCode": "STORE001",
|
|
||||||
"status": "ACTIVE",
|
|
||||||
"createTime": "2024-12-24T10:00:00"
|
|
||||||
},
|
|
||||||
"timestamp": 1703412000000
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**错误响应:**
|
|
||||||
- 账号已存在:`{"code": "6000", "message": "账号已存在"}`
|
|
||||||
- 手机号已被注册:`{"code": "6000", "message": "手机号已被注册"}`
|
|
||||||
- 邮箱已被注册:`{"code": "6000", "message": "邮箱已被注册"}`
|
|
||||||
- 参数验证失败:`{"code": "4001", "message": "参数验证失败"}`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. 用户登录
|
### 2. 用户登录
|
||||||
|
**POST** `/api/erp/user/login`
|
||||||
**接口地址:** `POST /api/erp/user/login`
|
|
||||||
|
|
||||||
**请求头:**
|
|
||||||
```
|
|
||||||
Content-Type: application/json
|
|
||||||
```
|
|
||||||
|
|
||||||
**请求体:**
|
**请求体:**
|
||||||
```json
|
```json
|
||||||
@@ -103,151 +28,59 @@ Content-Type: application/json
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**参数说明:**
|
**响应:**
|
||||||
- `username` (必填): 账号
|
|
||||||
- `password` (必填): 密码
|
|
||||||
|
|
||||||
**响应示例:**
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"code": "0000",
|
"code": "0000",
|
||||||
"message": "登录成功",
|
"message": "登录成功",
|
||||||
"data": {
|
"data": {
|
||||||
"id": 1,
|
"token": "xxx",
|
||||||
"username": "testuser",
|
"userInfo": {
|
||||||
"nickName": "测试用户",
|
"id": 1,
|
||||||
"phone": "13800138000",
|
"username": "testuser",
|
||||||
"email": "test@example.com",
|
"nickName": "测试用户"
|
||||||
"storeCode": "STORE001",
|
}
|
||||||
"status": "ACTIVE",
|
}
|
||||||
"token": "MTIzNDU2Nzg5MGFiY2RlZjoxMjM0NTY3ODkwYWJjZGVmOjE3MDM0MTIwMDAwMDA6YWJjZGVmMTIzNDU2Nzg5MA==",
|
|
||||||
"tokenExpireTime": 1704016800000,
|
|
||||||
"lastLoginTime": "2024-12-24T10:00:00",
|
|
||||||
"lastLoginIp": "192.168.1.100"
|
|
||||||
},
|
|
||||||
"timestamp": 1703412000000
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**错误响应:**
|
### 3. 获取用户信息
|
||||||
- 账号或密码错误:`{"code": "4002", "message": "账号或密码错误"}`
|
**GET** `/api/erp/user/info`
|
||||||
- 账号已被禁用:`{"code": "4003", "message": "账号已被禁用"}`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Token使用说明
|
|
||||||
|
|
||||||
### Token生成
|
|
||||||
|
|
||||||
登录成功后,系统会返回一个`token`字段,该token的有效期为7天。
|
|
||||||
|
|
||||||
### Token验证
|
|
||||||
|
|
||||||
后续需要认证的接口,可以在请求头中携带token:
|
|
||||||
|
|
||||||
|
**请求头:**
|
||||||
```
|
```
|
||||||
Authorization: Bearer {token}
|
Authorization: Bearer {token}
|
||||||
```
|
```
|
||||||
|
|
||||||
或者使用自定义header:
|
### 4. 更新用户信息
|
||||||
|
**PUT** `/api/erp/user/info`
|
||||||
|
|
||||||
|
**请求头:**
|
||||||
```
|
```
|
||||||
X-Auth-Token: {token}
|
Authorization: Bearer {token}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Token验证方法
|
### 5. 修改密码
|
||||||
|
**POST** `/api/erp/user/change-password`
|
||||||
|
|
||||||
在需要认证的接口中,可以通过以下方式验证token:
|
**请求头:**
|
||||||
|
```
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
```
|
||||||
|
|
||||||
```java
|
**请求体:**
|
||||||
@Autowired
|
```json
|
||||||
private ErpUserService erpUserService;
|
{
|
||||||
|
"oldPassword": "123456",
|
||||||
// 验证token
|
"newPassword": "654321"
|
||||||
ErpUser user = erpUserService.validateToken(token);
|
|
||||||
if (user == null) {
|
|
||||||
// Token无效或已过期
|
|
||||||
return Result.fail(ResultCode.TOKEN_INVALID);
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
## 认证方式
|
||||||
|
|
||||||
## 错误码说明
|
所有需要认证的接口都需要在请求头中携带Token:
|
||||||
|
```
|
||||||
| 错误码 | 说明 |
|
Authorization: Bearer {token}
|
||||||
|--------|------|
|
|
||||||
| 0000 | 操作成功 |
|
|
||||||
| 4000 | 参数错误 |
|
|
||||||
| 4001 | 参数验证失败 |
|
|
||||||
| 4002 | 未授权(账号或密码错误) |
|
|
||||||
| 4003 | 禁止访问(账号被禁用) |
|
|
||||||
| 6000 | 业务错误(账号已存在等) |
|
|
||||||
| 7001 | 用户不存在 |
|
|
||||||
| 7002 | 用户已存在 |
|
|
||||||
| 7003 | 密码错误 |
|
|
||||||
| 7004 | Token无效或已过期 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 安全说明
|
|
||||||
|
|
||||||
1. **密码加密**:密码使用MD5加密存储,生产环境建议使用BCrypt或Argon2等更安全的加密方式。
|
|
||||||
|
|
||||||
2. **Token安全**:
|
|
||||||
- 当前实现使用简单的MD5+Base64编码,生产环境建议使用JWT(JSON Web Token)
|
|
||||||
- Token包含用户ID、用户名、时间戳和签名
|
|
||||||
- Token有效期为7天,过期后需要重新登录
|
|
||||||
|
|
||||||
3. **IP记录**:系统会记录用户最后登录的IP地址,可用于安全审计。
|
|
||||||
|
|
||||||
4. **账号状态**:支持账号禁用功能,禁用后的账号无法登录。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 后续扩展建议
|
|
||||||
|
|
||||||
1. **JWT Token**:将当前简单的Token实现替换为标准的JWT
|
|
||||||
2. **密码策略**:添加密码复杂度要求、密码过期策略
|
|
||||||
3. **登录限制**:添加登录失败次数限制、IP白名单等功能
|
|
||||||
4. **权限管理**:添加角色和权限管理功能
|
|
||||||
5. **操作日志**:记录用户的操作日志,便于审计
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 测试示例
|
|
||||||
|
|
||||||
### 使用curl测试注册接口
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X POST http://localhost:8080/api/erp/user/register \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"username": "testuser",
|
|
||||||
"password": "123456",
|
|
||||||
"storeCode": "STORE001",
|
|
||||||
"nickName": "测试用户",
|
|
||||||
"phone": "13800138000",
|
|
||||||
"email": "test@example.com"
|
|
||||||
}'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 使用curl测试登录接口
|
Token通过登录接口获取,有效期由后端配置决定。
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X POST http://localhost:8080/api/erp/user/login \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"username": "testuser",
|
|
||||||
"password": "123456"
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
1. 执行数据库表创建SQL后,才能使用注册和登录功能
|
|
||||||
2. 生产环境建议修改`TokenUtils`中的`TOKEN_SECRET`,使用更复杂的密钥
|
|
||||||
3. 建议在生产环境使用HTTPS协议,保护密码和Token传输安全
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
# PayPal订单生命周期和字段填充说明
|
|
||||||
|
|
||||||
## 订单状态流转
|
|
||||||
|
|
||||||
PayPal订单会经历以下状态:
|
|
||||||
|
|
||||||
1. **CREATED** - 订单已创建(初始状态)
|
|
||||||
2. **PAYER_ACTION_REQUIRED** - 需要付款人操作(跳转到PayPal登录页)
|
|
||||||
3. **APPROVED** - 订单已批准(用户在PayPal批准支付)
|
|
||||||
4. **COMPLETED** - 订单已完成(订单被捕获后)
|
|
||||||
|
|
||||||
## 字段填充时机
|
|
||||||
|
|
||||||
### 创建订单时(CREATED状态)
|
|
||||||
|
|
||||||
以下字段在创建订单时通常为 **null**,这是**正常现象**:
|
|
||||||
|
|
||||||
- `payer_email` - null(用户尚未登录PayPal)
|
|
||||||
- `payer_name` - null(用户尚未登录PayPal)
|
|
||||||
- `payer_id` - null(用户尚未登录PayPal)
|
|
||||||
- `payment_status` - null(尚未支付)
|
|
||||||
- `capture_id` - null(尚未捕获订单)
|
|
||||||
|
|
||||||
**原因**:创建订单时,用户还没有在PayPal登录和批准支付,所以PayPal不会返回这些信息。
|
|
||||||
|
|
||||||
### 用户批准订单后(APPROVED状态)
|
|
||||||
|
|
||||||
以下字段会被填充:
|
|
||||||
|
|
||||||
- `payer_email` - 用户PayPal邮箱
|
|
||||||
- `payer_name` - 用户PayPal姓名
|
|
||||||
- `payer_id` - 用户PayPal账户ID
|
|
||||||
- `payment_status` - 仍为null(尚未捕获)
|
|
||||||
- `capture_id` - 仍为null(尚未捕获)
|
|
||||||
|
|
||||||
**触发时机**:
|
|
||||||
- 用户点击"立即支付"后跳转到PayPal
|
|
||||||
- 用户在PayPal登录并批准支付
|
|
||||||
- 系统通过Webhook事件 `CHECKOUT.ORDER.APPROVED` 或查询订单详情获取
|
|
||||||
|
|
||||||
### 订单被捕获后(COMPLETED状态)
|
|
||||||
|
|
||||||
以下字段会被填充:
|
|
||||||
|
|
||||||
- `payment_status` - 支付状态(如:COMPLETED)
|
|
||||||
- `capture_id` - 支付捕获ID
|
|
||||||
|
|
||||||
**触发时机**:
|
|
||||||
- 调用 `/api/paypal/orders/{orderId}/capture` 接口捕获订单
|
|
||||||
- 系统通过Webhook事件 `PAYMENT.CAPTURE.COMPLETED` 获取
|
|
||||||
|
|
||||||
## 与沙箱测试的关系
|
|
||||||
|
|
||||||
**这些字段为null与沙箱测试环境无关**,这是PayPal订单的正常生命周期:
|
|
||||||
|
|
||||||
- 在**生产环境**中,如果订单状态为CREATED,这些字段同样为null
|
|
||||||
- 只有在用户完成PayPal登录、批准支付、订单被捕获后,这些字段才会被填充
|
|
||||||
- 沙箱环境和生产环境的行为是一致的
|
|
||||||
|
|
||||||
## 如何查看完整信息
|
|
||||||
|
|
||||||
1. **查看 `order_data` 字段**:该字段存储了PayPal返回的完整JSON响应,包含所有信息
|
|
||||||
2. **查询订单详情**:调用 `/api/paypal/orders/{orderId}` 接口,系统会自动更新订单信息
|
|
||||||
3. **等待Webhook事件**:PayPal会发送Webhook事件,系统会自动更新订单信息
|
|
||||||
|
|
||||||
## 代码更新逻辑
|
|
||||||
|
|
||||||
系统会在以下时机自动更新订单信息:
|
|
||||||
|
|
||||||
1. **创建订单时**:调用 `createPaymentOrder` 方法
|
|
||||||
2. **查询订单时**:调用 `getOrder` 接口,自动调用 `updateOrderFromPayPal` 方法
|
|
||||||
3. **捕获订单时**:调用 `captureOrder` 接口,自动调用 `updateOrderFromPayPal` 方法
|
|
||||||
4. **Webhook事件**:处理Webhook事件时,自动调用 `updateOrderFromPayPal` 方法
|
|
||||||
|
|
||||||
## 建议
|
|
||||||
|
|
||||||
如果需要在订单创建后立即获取payer信息,可以:
|
|
||||||
|
|
||||||
1. 在用户从PayPal返回后,调用 `getOrder` 接口查询订单详情
|
|
||||||
2. 配置Webhook监听 `CHECKOUT.ORDER.APPROVED` 事件
|
|
||||||
3. 在订单确认页面加载时,主动查询一次PayPal订单详情
|
|
||||||
|
|
||||||
@@ -1,101 +1,44 @@
|
|||||||
# PayPal Webhook 配置说明
|
# PayPal Webhook 配置
|
||||||
|
|
||||||
## 内网穿透配置
|
## 配置步骤
|
||||||
|
|
||||||
当前使用内网穿透服务(cpolar)将本地服务暴露到公网:
|
|
||||||
|
|
||||||
- **内网地址**: `http://localhost:8082`
|
|
||||||
- **公网地址**: `https://2646b437.r33.cpolar.top`
|
|
||||||
- **Webhook URL**: `https://2646b437.r33.cpolar.top/api/paypal/webhook`
|
|
||||||
|
|
||||||
## 配置文件
|
|
||||||
|
|
||||||
Webhook URL 已配置在 `application-dev.yml` 中:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
paypal:
|
|
||||||
webhook-url: https://2646b437.r33.cpolar.top/api/paypal/webhook
|
|
||||||
```
|
|
||||||
|
|
||||||
## PayPal 控制台配置步骤
|
|
||||||
|
|
||||||
1. **登录 PayPal 开发者控制台**
|
1. **登录 PayPal 开发者控制台**
|
||||||
- 沙箱环境:https://developer.paypal.com/dashboard/
|
- 沙箱环境:https://developer.paypal.com/dashboard/
|
||||||
- 使用你的 PayPal 开发者账号登录
|
- 选择你的沙箱应用
|
||||||
|
|
||||||
2. **选择应用**
|
2. **配置 Webhook**
|
||||||
- 进入 "My Apps & Credentials"
|
- 进入应用详情页面,找到 "Webhooks" 部分
|
||||||
- 选择你的沙箱应用(Sandbox App)
|
- 点击 "Add Webhook"
|
||||||
|
- 输入 Webhook URL: `https://你的域名/api/paypal/webhook`
|
||||||
3. **配置 Webhook**
|
- 选择事件类型:
|
||||||
- 在应用详情页面,找到 "Webhooks" 部分
|
|
||||||
- 点击 "Add Webhook" 或 "Edit Webhook"
|
|
||||||
- 输入 Webhook URL: `https://2646b437.r33.cpolar.top/api/paypal/webhook`
|
|
||||||
- 选择要监听的事件类型:
|
|
||||||
- `PAYMENT.CAPTURE.COMPLETED` - 支付捕获完成
|
- `PAYMENT.CAPTURE.COMPLETED` - 支付捕获完成
|
||||||
- `PAYMENT.CAPTURE.DENIED` - 支付捕获被拒绝
|
|
||||||
- `PAYMENT.CAPTURE.REFUNDED` - 支付退款
|
|
||||||
- `CHECKOUT.ORDER.APPROVED` - 订单已批准
|
- `CHECKOUT.ORDER.APPROVED` - 订单已批准
|
||||||
- `CHECKOUT.ORDER.COMPLETED` - 订单已完成
|
- `CHECKOUT.ORDER.COMPLETED` - 订单已完成
|
||||||
- `CHECKOUT.ORDER.CANCELLED` - 订单已取消
|
- `CHECKOUT.ORDER.CANCELLED` - 订单已取消
|
||||||
- 保存配置
|
- `CHECKOUT.ORDER.DECLINED` - 订单被拒绝
|
||||||
|
|
||||||
4. **获取 Webhook ID**
|
3. **获取 Webhook ID**
|
||||||
- 配置完成后,PayPal 会生成一个 Webhook ID
|
- 配置完成后,获取 Webhook ID
|
||||||
- 将此 Webhook ID 配置到 `application-dev.yml` 中的 `paypal.webhook-id`
|
- 配置到 `application-dev.yml` 中的 `paypal.webhook-id`
|
||||||
|
|
||||||
|
## 配置文件
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
paypal:
|
||||||
|
webhook-url: https://你的域名/api/paypal/webhook
|
||||||
|
webhook-id: YOUR_WEBHOOK_ID
|
||||||
|
```
|
||||||
|
|
||||||
## 注意事项
|
## 注意事项
|
||||||
|
|
||||||
1. **内网穿透地址变化**
|
- **生产环境必须启用签名验证**
|
||||||
- 免费版 cpolar 的地址可能会变化
|
- **必须使用 HTTPS**
|
||||||
- 如果地址变化,需要:
|
- 内网穿透地址变化时需要更新配置
|
||||||
- 更新 `application-dev.yml` 中的 `webhook-url`
|
|
||||||
- 在 PayPal 控制台更新 Webhook URL
|
|
||||||
|
|
||||||
2. **HTTPS 要求**
|
## 本地测试
|
||||||
- PayPal Webhook 要求使用 HTTPS
|
|
||||||
- cpolar 提供的地址默认支持 HTTPS
|
|
||||||
|
|
||||||
3. **测试 Webhook**
|
|
||||||
- 在 PayPal 控制台可以测试 Webhook
|
|
||||||
- 选择 "Send test event" 发送测试事件
|
|
||||||
- 检查后端日志确认是否收到事件
|
|
||||||
|
|
||||||
4. **生产环境**
|
|
||||||
- 生产环境需要使用固定的域名
|
|
||||||
- 建议使用自己的域名配置 SSL 证书
|
|
||||||
- 更新 `application-prod.yml` 中的 `webhook-url`
|
|
||||||
|
|
||||||
## 验证配置
|
|
||||||
|
|
||||||
启动应用后,查看日志中的 PayPal 配置信息:
|
|
||||||
|
|
||||||
|
使用内网穿透工具(如 ngrok 或 cpolar):
|
||||||
|
```bash
|
||||||
|
ngrok http 8082
|
||||||
```
|
```
|
||||||
═══════════════════════════════════════════════════════════
|
将生成的HTTPS地址配置到PayPal Webhook URL。
|
||||||
PayPal配置加载验证:
|
|
||||||
- Client ID: ✅ 已配置 (...)
|
|
||||||
- Client Secret: ✅ 已配置 (...)
|
|
||||||
- Mode: sandbox
|
|
||||||
- Enabled: true
|
|
||||||
- Base URL: https://api-m.sandbox.paypal.com
|
|
||||||
- Webhook URL: ✅ https://2646b437.r33.cpolar.top/api/paypal/webhook
|
|
||||||
═══════════════════════════════════════════════════════════
|
|
||||||
```
|
|
||||||
|
|
||||||
如果 Webhook URL 显示为 "❌ 未配置",请检查配置文件。
|
|
||||||
|
|
||||||
## 后端接口
|
|
||||||
|
|
||||||
Webhook 回调接口路径:`/api/paypal/webhook`
|
|
||||||
|
|
||||||
- **方法**: POST
|
|
||||||
- **路径**: `/api/paypal/webhook`
|
|
||||||
- **完整URL**: `https://2646b437.r33.cpolar.top/api/paypal/webhook`
|
|
||||||
|
|
||||||
## 前端配置
|
|
||||||
|
|
||||||
前端不需要修改,因为:
|
|
||||||
- 前端使用 `window.location.origin` 自动获取当前域名
|
|
||||||
- 用户访问的是前端地址,不是后端地址
|
|
||||||
- 支付成功/取消回调使用前端路由,不涉及 Webhook
|
|
||||||
|
|
||||||
|
|||||||
121
mt-pay/README.md
121
mt-pay/README.md
@@ -1,41 +1,15 @@
|
|||||||
# MT Pay - 支付系统
|
# MT Pay - 支付系统
|
||||||
|
|
||||||
## 项目简介
|
|
||||||
|
|
||||||
面向东南亚地区的电商支付系统,支持PayPal支付、商品管理、订单管理、货币转换等功能。
|
面向东南亚地区的电商支付系统,支持PayPal支付、商品管理、订单管理、货币转换等功能。
|
||||||
|
|
||||||
## 功能特性
|
|
||||||
|
|
||||||
- ✅ PayPal支付集成(创建订单、捕获支付、Webhook处理)
|
|
||||||
- ✅ 商品管理(商品、SKU、商品链接)
|
|
||||||
- ✅ 客户订单管理(支持东南亚地址格式)
|
|
||||||
- ✅ 货币转换(实时汇率,支持多币种)
|
|
||||||
- ✅ 百度翻译集成(商品名称、SKU名称自动翻译)
|
|
||||||
- ✅ 订单状态管理
|
|
||||||
- ✅ 支付记录管理
|
|
||||||
|
|
||||||
## 技术栈
|
|
||||||
|
|
||||||
- Spring Boot 4.0.0
|
|
||||||
- MyBatis-Plus
|
|
||||||
- MySQL 5.7+
|
|
||||||
- Jackson (JSON处理)
|
|
||||||
- Lombok
|
|
||||||
- RestTemplate
|
|
||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
|
|
||||||
### 1. 数据库配置
|
### 1. 数据库配置
|
||||||
|
|
||||||
执行数据库脚本(按顺序):
|
执行数据库脚本(按顺序):
|
||||||
```sql
|
```sql
|
||||||
-- 1. 商品相关表
|
|
||||||
source database/customer_order_schema.sql;
|
source database/customer_order_schema.sql;
|
||||||
|
|
||||||
-- 2. 货币转换字段
|
|
||||||
source database/customer_order_currency_update.sql;
|
source database/customer_order_currency_update.sql;
|
||||||
|
|
||||||
-- 3. 地址字段(混合方案)
|
|
||||||
source database/customer_order_address_optimized.sql;
|
source database/customer_order_address_optimized.sql;
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -53,13 +27,6 @@ source database/customer_order_address_optimized.sql;
|
|||||||
mvn spring-boot:run
|
mvn spring-boot:run
|
||||||
```
|
```
|
||||||
|
|
||||||
或
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mvn clean package
|
|
||||||
java -jar target/mt-pay-0.0.1-SNAPSHOT.jar
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 访问地址
|
### 4. 访问地址
|
||||||
|
|
||||||
- 后端API: http://localhost:8082/api
|
- 后端API: http://localhost:8082/api
|
||||||
@@ -69,89 +36,35 @@ java -jar target/mt-pay-0.0.1-SNAPSHOT.jar
|
|||||||
## 核心API
|
## 核心API
|
||||||
|
|
||||||
### 商品管理
|
### 商品管理
|
||||||
|
- `POST /api/product` - 创建商品
|
||||||
- `GET /api/product/{id}` - 获取商品详情
|
- `GET /api/product/{id}` - 获取商品详情
|
||||||
- `GET /api/product/link/{linkCode}` - 通过链接码获取商品
|
- `GET /api/product/link/{linkCode}` - 通过链接码获取商品
|
||||||
- `POST /api/product` - 创建商品
|
- `POST /api/product/query` - 查询商品列表(分页)
|
||||||
|
- `PUT /api/product/{id}/off-shelf` - 下架商品
|
||||||
|
|
||||||
### 订单管理
|
### 订单管理
|
||||||
- `POST /api/order` - 创建客户订单
|
- `POST /api/order` - 创建客户订单
|
||||||
- `GET /api/order/{orderNo}` - 获取订单详情
|
- `GET /api/order/{orderNo}` - 获取订单详情
|
||||||
- `POST /api/order/calculate-currency-conversion` - 计算货币转换
|
- `POST /api/order/query` - 查询订单列表(分页)
|
||||||
|
|
||||||
### PayPal支付
|
### PayPal支付
|
||||||
- `POST /api/paypal/order` - 创建PayPal订单
|
- `POST /api/paypal/orders` - 创建PayPal订单
|
||||||
- `POST /api/paypal/capture` - 捕获支付
|
- `POST /api/paypal/orders/{orderId}/capture` - 捕获支付
|
||||||
- `POST /api/paypal/webhook` - Webhook回调
|
- `POST /api/paypal/webhook` - Webhook回调
|
||||||
|
|
||||||
## 项目结构
|
### ERP用户
|
||||||
|
- `POST /api/erp/user/register` - 用户注册
|
||||||
|
- `POST /api/erp/user/login` - 用户登录
|
||||||
|
- `GET /api/erp/user/info` - 获取用户信息
|
||||||
|
|
||||||
```
|
## 技术栈
|
||||||
com.mtkj.mtpay/
|
|
||||||
├── config/ # 配置类(PayPal、百度翻译、数据源等)
|
|
||||||
├── controller/ # REST控制器
|
|
||||||
├── dto/ # 数据传输对象
|
|
||||||
│ ├── request/ # 请求DTO
|
|
||||||
│ └── response/ # 响应DTO
|
|
||||||
├── entity/ # 实体类
|
|
||||||
├── exception/ # 异常处理
|
|
||||||
├── mapper/ # MyBatis Mapper
|
|
||||||
├── service/ # 业务服务层
|
|
||||||
│ └── impl/ # 服务实现
|
|
||||||
└── util/ # 工具类
|
|
||||||
```
|
|
||||||
|
|
||||||
## 核心服务
|
- Spring Boot 4.0.0
|
||||||
|
- MyBatis-Plus
|
||||||
- **ProductService**: 商品管理服务
|
- MySQL 5.7+
|
||||||
- **CustomerOrderService**: 客户订单服务
|
- Vue 3 + Element Plus
|
||||||
- **PayPalService**: PayPal支付服务
|
|
||||||
- **PayPalWebhookService**: PayPal Webhook处理服务
|
|
||||||
- **ExchangeRateService**: 汇率转换服务
|
|
||||||
- **BaiduTranslatorUtils**: 百度翻译工具
|
|
||||||
|
|
||||||
## 配置说明
|
|
||||||
|
|
||||||
### PayPal配置
|
|
||||||
```yaml
|
|
||||||
paypal:
|
|
||||||
client-id: your-client-id
|
|
||||||
client-secret: your-client-secret
|
|
||||||
mode: sandbox # sandbox 或 production
|
|
||||||
enabled: true
|
|
||||||
```
|
|
||||||
|
|
||||||
### 百度翻译配置
|
|
||||||
```yaml
|
|
||||||
baidu:
|
|
||||||
translator:
|
|
||||||
app-id: your-app-id
|
|
||||||
securityKey: your-security-key
|
|
||||||
transApiHost: https://fanyi-api.baidu.com/api/trans/vip/translate
|
|
||||||
```
|
|
||||||
|
|
||||||
## 数据库表
|
|
||||||
|
|
||||||
- `mt_product` - 商品表
|
|
||||||
- `mt_product_sku` - SKU表
|
|
||||||
- `mt_product_link` - 商品链接表
|
|
||||||
- `customer_order` - 客户订单表
|
|
||||||
- `payment_order` - 支付订单表
|
|
||||||
- `payment_record` - 支付记录表
|
|
||||||
|
|
||||||
## 地址字段设计
|
|
||||||
|
|
||||||
采用混合方案:
|
|
||||||
- **基础字段**(独立列):国家、城市、州/省、邮编、详细地址1/2
|
|
||||||
- **特殊字段**(JSON存储):各国特殊字段(组屋号、Barangay、泰文地址等)
|
|
||||||
|
|
||||||
详见:`database/customer_order_address_optimized.sql`
|
|
||||||
|
|
||||||
## 相关文档
|
## 相关文档
|
||||||
|
|
||||||
- `SYSTEM_ARCHITECTURE.md` - 系统架构文档
|
- `PAYPAL_WEBHOOK_SETUP.md` - PayPal Webhook配置
|
||||||
- `../PAYPAL_WEBHOOK_GUIDE.md` - PayPal Webhook配置指南
|
- `ERP_USER_API.md` - ERP用户API文档
|
||||||
- `../PAYPAL_TEST_ACCOUNT.md` - PayPal测试账号说明
|
|
||||||
|
|
||||||
## 许可证
|
|
||||||
|
|
||||||
MIT
|
|
||||||
|
|||||||
@@ -1,162 +0,0 @@
|
|||||||
# 系统架构完整性说明
|
|
||||||
|
|
||||||
## 架构检查清单
|
|
||||||
|
|
||||||
### ✅ 后端架构完整性
|
|
||||||
|
|
||||||
#### 1. 分层架构
|
|
||||||
- ✅ **Controller层**: 接口控制器,处理HTTP请求
|
|
||||||
- ✅ **Service层**: 业务逻辑层(接口+实现分离)
|
|
||||||
- ✅ **Mapper层**: 数据访问层(MyBatis-Plus)
|
|
||||||
- ✅ **Entity层**: 实体类(数据库映射)
|
|
||||||
|
|
||||||
#### 2. 通用组件
|
|
||||||
- ✅ **Result<T>**: 统一响应结果类
|
|
||||||
- ✅ **ResultCode**: 响应码枚举
|
|
||||||
- ✅ **BusinessException**: 业务异常类
|
|
||||||
- ✅ **GlobalExceptionHandler**: 全局异常处理器
|
|
||||||
|
|
||||||
#### 3. 枚举类
|
|
||||||
- ✅ **OrderStatus**: 订单状态枚举
|
|
||||||
- ✅ **PaymentType**: 支付类型枚举
|
|
||||||
- ✅ **RecordType**: 记录类型枚举
|
|
||||||
|
|
||||||
#### 4. 常量类
|
|
||||||
- ✅ **PaymentConstants**: 支付相关常量
|
|
||||||
|
|
||||||
#### 5. 工具类
|
|
||||||
- ✅ **DateUtils**: 日期时间工具类
|
|
||||||
- ✅ **StringUtils**: 字符串工具类
|
|
||||||
- ✅ **OrderIdGenerator**: 订单号生成器
|
|
||||||
|
|
||||||
#### 6. 配置类
|
|
||||||
- ✅ **DruidDataSourceConfig**: 数据源配置
|
|
||||||
- ✅ **MyBatisPlusConfig**: MyBatis-Plus配置
|
|
||||||
- ✅ **MyMetaObjectHandler**: 自动填充处理器
|
|
||||||
- ✅ **PingPongProperties**: PingPong配置属性
|
|
||||||
- ✅ **RestClientConfig**: HTTP客户端配置
|
|
||||||
- ✅ **WebConfig**: Web配置(跨域等)
|
|
||||||
|
|
||||||
#### 7. DTO类
|
|
||||||
- ✅ **请求DTO**: CheckoutRequestDTO
|
|
||||||
- ✅ **响应DTO**: CheckoutResponseDTO
|
|
||||||
- ✅ **风控DTO**: RiskInfoDTO及其子DTO
|
|
||||||
|
|
||||||
### ✅ 前端架构完整性
|
|
||||||
|
|
||||||
#### 1. 目录结构
|
|
||||||
- ✅ **api/**: API接口封装
|
|
||||||
- ✅ **components/**: 通用组件
|
|
||||||
- ✅ **config/**: 配置文件
|
|
||||||
- ✅ **router/**: 路由配置
|
|
||||||
- ✅ **store/**: 状态管理
|
|
||||||
- ✅ **utils/**: 工具函数
|
|
||||||
- ✅ **views/**: 页面组件
|
|
||||||
|
|
||||||
#### 2. 工具类
|
|
||||||
- ✅ **constants.js**: 常量定义
|
|
||||||
- ✅ **helpers.js**: 工具函数
|
|
||||||
- ✅ **request.js**: 请求工具
|
|
||||||
|
|
||||||
#### 3. 通用组件
|
|
||||||
- ✅ **PageHeader.vue**: 页面头部组件
|
|
||||||
- ✅ **Loading.vue**: 加载组件
|
|
||||||
|
|
||||||
#### 4. 配置管理
|
|
||||||
- ✅ **config/index.js**: 统一配置
|
|
||||||
- ✅ **.env.development**: 开发环境配置
|
|
||||||
- ✅ **.env.production**: 生产环境配置
|
|
||||||
|
|
||||||
#### 5. 状态管理
|
|
||||||
- ✅ **store/index.js**: 简单状态管理
|
|
||||||
|
|
||||||
## 架构特点
|
|
||||||
|
|
||||||
### 1. 统一规范
|
|
||||||
- ✅ 统一响应格式(Result<T>)
|
|
||||||
- ✅ 统一异常处理(BusinessException + GlobalExceptionHandler)
|
|
||||||
- ✅ 统一响应码(ResultCode枚举)
|
|
||||||
- ✅ 统一命名规范
|
|
||||||
|
|
||||||
### 2. 代码复用
|
|
||||||
- ✅ 工具类封装通用功能
|
|
||||||
- ✅ 枚举类替代魔法字符串
|
|
||||||
- ✅ 常量类集中管理配置
|
|
||||||
- ✅ 通用组件可复用
|
|
||||||
|
|
||||||
### 3. 可扩展性
|
|
||||||
- ✅ 接口与实现分离
|
|
||||||
- ✅ 枚举类易于扩展
|
|
||||||
- ✅ 配置统一管理
|
|
||||||
- ✅ 模块化设计
|
|
||||||
|
|
||||||
### 4. 可维护性
|
|
||||||
- ✅ 代码结构清晰
|
|
||||||
- ✅ 注释完整
|
|
||||||
- ✅ 职责单一
|
|
||||||
- ✅ 依赖注入
|
|
||||||
|
|
||||||
## 包结构总览
|
|
||||||
|
|
||||||
### 后端(mt-pay)
|
|
||||||
```
|
|
||||||
com.mtkj.mtpay/
|
|
||||||
├── common/ # 通用组件
|
|
||||||
│ ├── Result.java
|
|
||||||
│ ├── ResultCode.java
|
|
||||||
│ ├── constants/
|
|
||||||
│ └── enums/
|
|
||||||
├── config/ # 配置类
|
|
||||||
├── controller/ # 控制器
|
|
||||||
├── dto/ # 数据传输对象
|
|
||||||
├── entity/ # 实体类
|
|
||||||
├── exception/ # 异常处理
|
|
||||||
├── mapper/ # 数据访问层
|
|
||||||
├── service/ # 服务接口
|
|
||||||
├── service/impl/ # 服务实现
|
|
||||||
└── util/ # 工具类
|
|
||||||
```
|
|
||||||
|
|
||||||
### 前端(MTKJPAY-FRONT)
|
|
||||||
```
|
|
||||||
src/
|
|
||||||
├── api/ # API接口
|
|
||||||
├── components/ # 通用组件
|
|
||||||
├── config/ # 配置
|
|
||||||
├── router/ # 路由
|
|
||||||
├── store/ # 状态管理
|
|
||||||
├── utils/ # 工具函数
|
|
||||||
└── views/ # 页面组件
|
|
||||||
```
|
|
||||||
|
|
||||||
## 最佳实践
|
|
||||||
|
|
||||||
1. ✅ **统一响应格式**: 所有接口返回Result<T>
|
|
||||||
2. ✅ **异常处理**: 使用BusinessException抛出业务异常
|
|
||||||
3. ✅ **枚举使用**: 使用枚举替代魔法字符串
|
|
||||||
4. ✅ **工具类**: 通用功能封装为工具类
|
|
||||||
5. ✅ **配置管理**: 配置统一管理,支持多环境
|
|
||||||
6. ✅ **接口分离**: Service接口与实现分离
|
|
||||||
7. ✅ **跨域配置**: WebConfig统一配置跨域
|
|
||||||
8. ✅ **常量管理**: 常量集中管理
|
|
||||||
|
|
||||||
## 系统完整性评分
|
|
||||||
|
|
||||||
- **后端架构**: ⭐⭐⭐⭐⭐ (5/5)
|
|
||||||
- **前端架构**: ⭐⭐⭐⭐⭐ (5/5)
|
|
||||||
- **代码规范**: ⭐⭐⭐⭐⭐ (5/5)
|
|
||||||
- **可维护性**: ⭐⭐⭐⭐⭐ (5/5)
|
|
||||||
- **可扩展性**: ⭐⭐⭐⭐⭐ (5/5)
|
|
||||||
|
|
||||||
## 总结
|
|
||||||
|
|
||||||
系统架构已完整,包含:
|
|
||||||
- ✅ 完整的分层架构
|
|
||||||
- ✅ 统一的响应格式和异常处理
|
|
||||||
- ✅ 完善的工具类和枚举类
|
|
||||||
- ✅ 规范的代码结构
|
|
||||||
- ✅ 可扩展的设计
|
|
||||||
- ✅ 完整的配置管理
|
|
||||||
|
|
||||||
系统已具备生产环境使用的基础架构!
|
|
||||||
|
|
||||||
@@ -15,12 +15,16 @@ public class WebConfig implements WebMvcConfigurer {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 配置跨域
|
* 配置跨域
|
||||||
|
* 注意:生产环境建议限制为具体的前端域名
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void addCorsMappings(CorsRegistry registry) {
|
public void addCorsMappings(CorsRegistry registry) {
|
||||||
log.info("配置跨域访问,路径: /api/**, 允许所有来源");
|
// 开发环境允许所有来源,生产环境建议限制为具体域名
|
||||||
|
String allowedOrigins = System.getProperty("cors.allowed.origins", "*");
|
||||||
|
log.info("配置跨域访问,路径: /api/**, 允许来源: {}", allowedOrigins);
|
||||||
|
|
||||||
registry.addMapping("/api/**")
|
registry.addMapping("/api/**")
|
||||||
.allowedOriginPatterns("*")
|
.allowedOriginPatterns(allowedOrigins)
|
||||||
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||||||
.allowedHeaders("*")
|
.allowedHeaders("*")
|
||||||
.allowCredentials(true)
|
.allowCredentials(true)
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ package com.mtkj.mtpay.controller;
|
|||||||
import com.mtkj.mtpay.common.Result;
|
import com.mtkj.mtpay.common.Result;
|
||||||
import com.mtkj.mtpay.dto.request.CalculateCurrencyConversionRequestDTO;
|
import com.mtkj.mtpay.dto.request.CalculateCurrencyConversionRequestDTO;
|
||||||
import com.mtkj.mtpay.dto.request.CreateCustomerOrderRequestDTO;
|
import com.mtkj.mtpay.dto.request.CreateCustomerOrderRequestDTO;
|
||||||
|
import com.mtkj.mtpay.dto.request.OrderQueryRequestDTO;
|
||||||
import com.mtkj.mtpay.dto.response.CurrencyConversionDTO;
|
import com.mtkj.mtpay.dto.response.CurrencyConversionDTO;
|
||||||
import com.mtkj.mtpay.dto.response.CustomerOrderResponseDTO;
|
import com.mtkj.mtpay.dto.response.CustomerOrderResponseDTO;
|
||||||
|
import com.mtkj.mtpay.dto.response.PageResult;
|
||||||
import com.mtkj.mtpay.service.CustomerOrderService;
|
import com.mtkj.mtpay.service.CustomerOrderService;
|
||||||
import com.mtkj.mtpay.service.ExchangeRateService;
|
import com.mtkj.mtpay.service.ExchangeRateService;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
@@ -134,6 +136,18 @@ public class CustomerOrderController {
|
|||||||
return Result.success(order);
|
return Result.success(order);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询订单列表(支持多条件查询和分页)
|
||||||
|
*/
|
||||||
|
@PostMapping("/query")
|
||||||
|
public Result<PageResult<com.mtkj.mtpay.dto.response.OrderListResponseDTO>> queryOrders(
|
||||||
|
@RequestBody OrderQueryRequestDTO query) {
|
||||||
|
log.info("查询订单列表,查询条件:{}", query);
|
||||||
|
PageResult<com.mtkj.mtpay.dto.response.OrderListResponseDTO> pageResult =
|
||||||
|
customerOrderService.queryOrders(query);
|
||||||
|
return Result.success(pageResult);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建汇率说明文本
|
* 构建汇率说明文本
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package com.mtkj.mtpay.dto.request;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单列表查询请求DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class OrderQueryRequestDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单号(精确查询)
|
||||||
|
*/
|
||||||
|
private String orderNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PayPal订单ID(精确查询)
|
||||||
|
*/
|
||||||
|
private String paypalOrderId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商户订单号(精确查询,等同于orderNo)
|
||||||
|
*/
|
||||||
|
private String merchantOrderNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单状态(PENDING-待支付,PAID-已支付,SHIPPED-已发货,COMPLETED-已完成,CANCELLED-已取消)
|
||||||
|
*/
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付状态(UNPAID-未支付,PAID-已支付,FAILED-支付失败)
|
||||||
|
*/
|
||||||
|
private String paymentStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PayPal订单状态(CREATED-已创建,APPROVED-已批准,COMPLETED-已完成,VOIDED-已取消等)
|
||||||
|
*/
|
||||||
|
private String paypalStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PayPal支付状态(COMPLETED-已完成,DENIED-被拒绝,PENDING-待处理等)
|
||||||
|
*/
|
||||||
|
private String paypalPaymentStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户姓名(模糊查询)
|
||||||
|
*/
|
||||||
|
private String customerName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户电话(模糊查询)
|
||||||
|
*/
|
||||||
|
private String customerPhone;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户邮箱(模糊查询)
|
||||||
|
*/
|
||||||
|
private String customerEmail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商品名称(模糊查询)
|
||||||
|
*/
|
||||||
|
private String productName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付者邮箱(PayPal,模糊查询)
|
||||||
|
*/
|
||||||
|
private String payerEmail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付者姓名(PayPal,模糊查询)
|
||||||
|
*/
|
||||||
|
private String payerName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始时间(订单创建时间)
|
||||||
|
*/
|
||||||
|
private String startTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 结束时间(订单创建时间)
|
||||||
|
*/
|
||||||
|
private String endTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前页码(从1开始,默认1)
|
||||||
|
*/
|
||||||
|
private Integer pageNum = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每页大小(默认10)
|
||||||
|
*/
|
||||||
|
private Integer pageSize = 10;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
package com.mtkj.mtpay.dto.response;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单列表响应DTO(包含客户订单和PayPal订单信息)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class OrderListResponseDTO implements Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户订单ID
|
||||||
|
*/
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单号
|
||||||
|
*/
|
||||||
|
private String orderNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商品ID
|
||||||
|
*/
|
||||||
|
private Long productId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商品名称
|
||||||
|
*/
|
||||||
|
private String productName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SKU名称
|
||||||
|
*/
|
||||||
|
private String skuName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 购买数量
|
||||||
|
*/
|
||||||
|
private Integer quantity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单总金额(原始货币)
|
||||||
|
*/
|
||||||
|
private BigDecimal totalAmount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原始货币代码
|
||||||
|
*/
|
||||||
|
private String originalCurrency;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原始订单金额
|
||||||
|
*/
|
||||||
|
private BigDecimal originalAmount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付货币代码
|
||||||
|
*/
|
||||||
|
private String paymentCurrency;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付金额
|
||||||
|
*/
|
||||||
|
private BigDecimal paymentAmount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单状态(PENDING-待支付,PAID-已支付,SHIPPED-已发货,COMPLETED-已完成,CANCELLED-已取消)
|
||||||
|
*/
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付状态(UNPAID-未支付,PAID-已支付,FAILED-支付失败)
|
||||||
|
*/
|
||||||
|
private String paymentStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户姓名
|
||||||
|
*/
|
||||||
|
private String customerName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户电话
|
||||||
|
*/
|
||||||
|
private String customerPhone;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户邮箱
|
||||||
|
*/
|
||||||
|
private String customerEmail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收货国家
|
||||||
|
*/
|
||||||
|
private String shippingCountry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收货城市
|
||||||
|
*/
|
||||||
|
private String shippingCity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PayPal订单ID
|
||||||
|
*/
|
||||||
|
private String paypalOrderId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PayPal订单状态(CREATED-已创建,APPROVED-已批准,COMPLETED-已完成,VOIDED-已取消等)
|
||||||
|
*/
|
||||||
|
private String paypalStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PayPal支付状态(COMPLETED-已完成,DENIED-被拒绝,PENDING-待处理等)
|
||||||
|
*/
|
||||||
|
private String paypalPaymentStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付者邮箱(PayPal)
|
||||||
|
*/
|
||||||
|
private String payerEmail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付者姓名(PayPal)
|
||||||
|
*/
|
||||||
|
private String payerName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付捕获ID(PayPal)
|
||||||
|
*/
|
||||||
|
private String captureId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单创建时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单更新时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PayPal订单创建时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime paypalCreateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PayPal订单更新时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime paypalUpdateTime;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -5,6 +5,8 @@ import com.mtkj.mtpay.entity.MtProductSku;
|
|||||||
import org.apache.ibatis.annotations.Insert;
|
import org.apache.ibatis.annotations.Insert;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
import org.apache.ibatis.annotations.Update;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -31,5 +33,38 @@ public interface MtProductSkuMapper extends BaseMapper<MtProductSku> {
|
|||||||
"</foreach>" +
|
"</foreach>" +
|
||||||
"</script>")
|
"</script>")
|
||||||
int insertBatch(@Param("skuList") List<MtProductSku> skuList);
|
int insertBatch(@Param("skuList") List<MtProductSku> skuList);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扣减库存(使用SELECT FOR UPDATE锁防止超卖)
|
||||||
|
* 使用悲观锁确保并发安全
|
||||||
|
*
|
||||||
|
* @param skuId SKU ID
|
||||||
|
* @param quantity 扣减数量
|
||||||
|
* @return 更新行数(1表示成功,0表示库存不足)
|
||||||
|
*/
|
||||||
|
@Update("UPDATE mt_product_sku SET stock = stock - #{quantity}, update_time = NOW() " +
|
||||||
|
"WHERE id = #{skuId} AND stock >= #{quantity} AND status = 'ACTIVE'")
|
||||||
|
int deductStock(@Param("skuId") Long skuId, @Param("quantity") Integer quantity);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 恢复库存(订单取消时使用)
|
||||||
|
*
|
||||||
|
* @param skuId SKU ID
|
||||||
|
* @param quantity 恢复数量
|
||||||
|
* @return 更新行数
|
||||||
|
*/
|
||||||
|
@Update("UPDATE mt_product_sku SET stock = stock + #{quantity}, update_time = NOW() " +
|
||||||
|
"WHERE id = #{skuId}")
|
||||||
|
int restoreStock(@Param("skuId") Long skuId, @Param("quantity") Integer quantity);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询并锁定SKU(使用SELECT FOR UPDATE)
|
||||||
|
* 用于在事务中锁定SKU记录,防止并发问题
|
||||||
|
*
|
||||||
|
* @param skuId SKU ID
|
||||||
|
* @return SKU实体
|
||||||
|
*/
|
||||||
|
@Select("SELECT * FROM mt_product_sku WHERE id = #{skuId} FOR UPDATE")
|
||||||
|
MtProductSku selectByIdForUpdate(@Param("skuId") Long skuId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.mtkj.mtpay.service;
|
package com.mtkj.mtpay.service;
|
||||||
|
|
||||||
import com.mtkj.mtpay.dto.request.CreateCustomerOrderRequestDTO;
|
import com.mtkj.mtpay.dto.request.CreateCustomerOrderRequestDTO;
|
||||||
|
import com.mtkj.mtpay.dto.request.OrderQueryRequestDTO;
|
||||||
import com.mtkj.mtpay.dto.response.CustomerOrderResponseDTO;
|
import com.mtkj.mtpay.dto.response.CustomerOrderResponseDTO;
|
||||||
|
import com.mtkj.mtpay.dto.response.PageResult;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 客户订单服务接口
|
* 客户订单服务接口
|
||||||
@@ -58,5 +60,18 @@ public interface CustomerOrderService {
|
|||||||
String paymentCurrency,
|
String paymentCurrency,
|
||||||
java.math.BigDecimal paymentAmount,
|
java.math.BigDecimal paymentAmount,
|
||||||
java.math.BigDecimal exchangeRate);
|
java.math.BigDecimal exchangeRate);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询订单列表(支持多条件查询和分页)
|
||||||
|
* @param query 查询条件(包含分页参数)
|
||||||
|
* @return 分页结果
|
||||||
|
*/
|
||||||
|
PageResult<com.mtkj.mtpay.dto.response.OrderListResponseDTO> queryOrders(OrderQueryRequestDTO query);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消订单(恢复库存)
|
||||||
|
* @param orderNo 订单号
|
||||||
|
*/
|
||||||
|
void cancelOrder(String orderNo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,14 @@ package com.mtkj.mtpay.service.impl;
|
|||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.mtkj.mtpay.common.ResultCode;
|
import com.mtkj.mtpay.common.ResultCode;
|
||||||
import com.mtkj.mtpay.common.enums.ProductStatus;
|
import com.mtkj.mtpay.common.enums.ProductStatus;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.mtkj.mtpay.dto.request.CreateCustomerOrderRequestDTO;
|
import com.mtkj.mtpay.dto.request.CreateCustomerOrderRequestDTO;
|
||||||
|
import com.mtkj.mtpay.dto.request.OrderQueryRequestDTO;
|
||||||
import com.mtkj.mtpay.dto.response.CustomerOrderResponseDTO;
|
import com.mtkj.mtpay.dto.response.CustomerOrderResponseDTO;
|
||||||
|
import com.mtkj.mtpay.dto.response.OrderListResponseDTO;
|
||||||
|
import com.mtkj.mtpay.dto.response.PageResult;
|
||||||
|
import com.mtkj.mtpay.entity.PayPalPaymentOrder;
|
||||||
import com.mtkj.mtpay.entity.CustomerOrder;
|
import com.mtkj.mtpay.entity.CustomerOrder;
|
||||||
import com.mtkj.mtpay.entity.MtProduct;
|
import com.mtkj.mtpay.entity.MtProduct;
|
||||||
import com.mtkj.mtpay.entity.MtProductSku;
|
import com.mtkj.mtpay.entity.MtProductSku;
|
||||||
@@ -12,6 +18,7 @@ import com.mtkj.mtpay.exception.BusinessException;
|
|||||||
import com.mtkj.mtpay.mapper.CustomerOrderMapper;
|
import com.mtkj.mtpay.mapper.CustomerOrderMapper;
|
||||||
import com.mtkj.mtpay.mapper.MtProductMapper;
|
import com.mtkj.mtpay.mapper.MtProductMapper;
|
||||||
import com.mtkj.mtpay.mapper.MtProductSkuMapper;
|
import com.mtkj.mtpay.mapper.MtProductSkuMapper;
|
||||||
|
import com.mtkj.mtpay.mapper.PayPalPaymentOrderMapper;
|
||||||
import com.mtkj.mtpay.service.CustomerOrderService;
|
import com.mtkj.mtpay.service.CustomerOrderService;
|
||||||
import com.mtkj.mtpay.util.BaiduTranslatorUtils;
|
import com.mtkj.mtpay.util.BaiduTranslatorUtils;
|
||||||
import com.mtkj.mtpay.util.OrderIdGenerator;
|
import com.mtkj.mtpay.util.OrderIdGenerator;
|
||||||
@@ -38,47 +45,54 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
|
|||||||
private final CustomerOrderMapper customerOrderMapper;
|
private final CustomerOrderMapper customerOrderMapper;
|
||||||
private final MtProductMapper productMapper;
|
private final MtProductMapper productMapper;
|
||||||
private final MtProductSkuMapper productSkuMapper;
|
private final MtProductSkuMapper productSkuMapper;
|
||||||
|
private final PayPalPaymentOrderMapper payPalPaymentOrderMapper;
|
||||||
private final BaiduTranslatorUtils baiduTranslatorUtils;
|
private final BaiduTranslatorUtils baiduTranslatorUtils;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public CustomerOrderResponseDTO createOrder(CreateCustomerOrderRequestDTO request) {
|
public CustomerOrderResponseDTO createOrder(CreateCustomerOrderRequestDTO request) {
|
||||||
log.info("创建客户订单,商品ID: {}, SKU ID: {}, 数量: {}",
|
|
||||||
request.getProductId(), request.getSkuId(), request.getQuantity());
|
|
||||||
|
|
||||||
// 验证商品是否存在
|
// 验证商品是否存在
|
||||||
MtProduct product = productMapper.selectById(request.getProductId());
|
MtProduct product = productMapper.selectById(request.getProductId());
|
||||||
if (product == null) {
|
if (product == null) {
|
||||||
log.warn("商品不存在,商品ID: {}", request.getProductId());
|
|
||||||
throw new BusinessException(ResultCode.DATA_NOT_FOUND, "商品不存在");
|
throw new BusinessException(ResultCode.DATA_NOT_FOUND, "商品不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证商品状态:下架商品不能创建订单
|
// 验证商品状态:下架商品不能创建订单
|
||||||
if (ProductStatus.INACTIVE.getCode().equals(product.getStatus())) {
|
if (ProductStatus.INACTIVE.getCode().equals(product.getStatus())) {
|
||||||
log.warn("商品已下架,无法创建订单,商品ID: {}", request.getProductId());
|
|
||||||
throw new BusinessException(ResultCode.BUSINESS_ERROR, "商品已下架,无法创建订单");
|
throw new BusinessException(ResultCode.BUSINESS_ERROR, "商品已下架,无法创建订单");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证SKU是否存在
|
// 验证SKU是否存在
|
||||||
MtProductSku sku = productSkuMapper.selectById(request.getSkuId());
|
MtProductSku sku = productSkuMapper.selectById(request.getSkuId());
|
||||||
if (sku == null || !sku.getProductId().equals(request.getProductId())) {
|
if (sku == null || !sku.getProductId().equals(request.getProductId())) {
|
||||||
log.warn("SKU不存在或不属于该商品,SKU ID: {}, 商品ID: {}",
|
|
||||||
request.getSkuId(), request.getProductId());
|
|
||||||
throw new BusinessException(ResultCode.DATA_NOT_FOUND, "SKU不存在");
|
throw new BusinessException(ResultCode.DATA_NOT_FOUND, "SKU不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证库存:库存为0或不足时不能创建订单
|
// 验证库存:库存为0或不足时不能创建订单
|
||||||
if (sku.getStock() == null || sku.getStock() <= 0) {
|
if (sku.getStock() == null || sku.getStock() <= 0) {
|
||||||
log.warn("库存为0,无法创建订单,SKU ID: {}, 库存: {}",
|
|
||||||
request.getSkuId(), sku.getStock());
|
|
||||||
throw new BusinessException(ResultCode.BUSINESS_ERROR, "商品库存为0,无法创建订单");
|
throw new BusinessException(ResultCode.BUSINESS_ERROR, "商品库存为0,无法创建订单");
|
||||||
}
|
}
|
||||||
if (sku.getStock() < request.getQuantity()) {
|
if (sku.getStock() < request.getQuantity()) {
|
||||||
log.warn("库存不足,SKU ID: {}, 库存: {}, 需要: {}",
|
|
||||||
request.getSkuId(), sku.getStock(), request.getQuantity());
|
|
||||||
throw new BusinessException(ResultCode.BUSINESS_ERROR, "库存不足");
|
throw new BusinessException(ResultCode.BUSINESS_ERROR, "库存不足");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 关键:使用SELECT FOR UPDATE锁定SKU记录,防止并发超卖
|
||||||
|
MtProductSku lockedSku = productSkuMapper.selectByIdForUpdate(request.getSkuId());
|
||||||
|
if (lockedSku == null) {
|
||||||
|
throw new BusinessException(ResultCode.DATA_NOT_FOUND, "SKU不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 再次验证库存(锁定后的最新库存)
|
||||||
|
if (lockedSku.getStock() == null || lockedSku.getStock() < request.getQuantity()) {
|
||||||
|
throw new BusinessException(ResultCode.BUSINESS_ERROR, "库存不足");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 扣减库存(使用原子操作,确保并发安全)
|
||||||
|
int deductResult = productSkuMapper.deductStock(request.getSkuId(), request.getQuantity());
|
||||||
|
if (deductResult <= 0) {
|
||||||
|
throw new BusinessException(ResultCode.BUSINESS_ERROR, "库存扣减失败,可能库存不足");
|
||||||
|
}
|
||||||
|
|
||||||
// 创建订单
|
// 创建订单
|
||||||
CustomerOrder order = new CustomerOrder();
|
CustomerOrder order = new CustomerOrder();
|
||||||
order.setOrderNo(OrderIdGenerator.generateMerchantTransactionId());
|
order.setOrderNo(OrderIdGenerator.generateMerchantTransactionId());
|
||||||
@@ -90,17 +104,12 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
|
|||||||
if (currency != null && !currency.trim().isEmpty()) {
|
if (currency != null && !currency.trim().isEmpty()) {
|
||||||
try {
|
try {
|
||||||
String targetLanguage = baiduTranslatorUtils.getLanguageByCurrency(currency);
|
String targetLanguage = baiduTranslatorUtils.getLanguageByCurrency(currency);
|
||||||
log.debug("订单货币: {}, 推断目标语言: {}, 原始商品名称: {}",
|
|
||||||
currency, targetLanguage, product.getName());
|
|
||||||
String translated = baiduTranslatorUtils.getTransResult(product.getName(), targetLanguage);
|
String translated = baiduTranslatorUtils.getTransResult(product.getName(), targetLanguage);
|
||||||
if (translated != null && !translated.equals(product.getName())) {
|
if (translated != null && !translated.equals(product.getName())) {
|
||||||
translatedProductName = translated;
|
translatedProductName = translated;
|
||||||
log.info("商品名称翻译: {} -> {} (货币: {}, 语言: {})",
|
|
||||||
product.getName(), translatedProductName, currency, targetLanguage);
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("翻译商品名称失败,使用原始名称,商品ID: {}, 货币: {}",
|
// 翻译失败使用原始名称,静默处理
|
||||||
request.getProductId(), currency, e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
order.setProductName(translatedProductName);
|
order.setProductName(translatedProductName);
|
||||||
@@ -182,11 +191,12 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
|
|||||||
// 保存订单
|
// 保存订单
|
||||||
int result = customerOrderMapper.insert(order);
|
int result = customerOrderMapper.insert(order);
|
||||||
if (result <= 0) {
|
if (result <= 0) {
|
||||||
log.error("创建订单失败,商品ID: {}", request.getProductId());
|
|
||||||
throw new BusinessException(ResultCode.SYSTEM_ERROR, "创建订单失败");
|
throw new BusinessException(ResultCode.SYSTEM_ERROR, "创建订单失败");
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("客户订单创建成功,订单ID: {}, 订单号: {}", order.getId(), order.getOrderNo());
|
log.info("订单创建成功,订单号: {}, 商品ID: {}, SKU ID: {}, 数量: {}, 库存扣减: {}",
|
||||||
|
order.getOrderNo(), request.getProductId(), request.getSkuId(),
|
||||||
|
request.getQuantity(), request.getQuantity());
|
||||||
|
|
||||||
// 转换为响应DTO
|
// 转换为响应DTO
|
||||||
CustomerOrderResponseDTO response = new CustomerOrderResponseDTO();
|
CustomerOrderResponseDTO response = new CustomerOrderResponseDTO();
|
||||||
@@ -202,13 +212,11 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CustomerOrderResponseDTO getOrderByOrderNo(String orderNo) {
|
public CustomerOrderResponseDTO getOrderByOrderNo(String orderNo) {
|
||||||
log.debug("查询订单,订单号: {}", orderNo);
|
|
||||||
LambdaQueryWrapper<CustomerOrder> queryWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<CustomerOrder> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
queryWrapper.eq(CustomerOrder::getOrderNo, orderNo);
|
queryWrapper.eq(CustomerOrder::getOrderNo, orderNo);
|
||||||
CustomerOrder order = customerOrderMapper.selectOne(queryWrapper);
|
CustomerOrder order = customerOrderMapper.selectOne(queryWrapper);
|
||||||
|
|
||||||
if (order == null) {
|
if (order == null) {
|
||||||
log.warn("订单不存在,订单号: {}", orderNo);
|
|
||||||
throw new BusinessException(ResultCode.DATA_NOT_FOUND, "订单不存在");
|
throw new BusinessException(ResultCode.DATA_NOT_FOUND, "订单不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,11 +233,9 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CustomerOrderResponseDTO getOrderById(Long id) {
|
public CustomerOrderResponseDTO getOrderById(Long id) {
|
||||||
log.debug("查询订单,订单ID: {}", id);
|
|
||||||
CustomerOrder order = customerOrderMapper.selectById(id);
|
CustomerOrder order = customerOrderMapper.selectById(id);
|
||||||
|
|
||||||
if (order == null) {
|
if (order == null) {
|
||||||
log.warn("订单不存在,订单ID: {}", id);
|
|
||||||
throw new BusinessException(ResultCode.DATA_NOT_FOUND, "订单不存在");
|
throw new BusinessException(ResultCode.DATA_NOT_FOUND, "订单不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,14 +252,11 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updatePaymentStatus(String orderNo, String paymentStatus, Long paymentOrderId) {
|
public void updatePaymentStatus(String orderNo, String paymentStatus, Long paymentOrderId) {
|
||||||
log.info("更新订单支付状态,订单号: {}, 支付状态: {}, 支付订单ID: {}",
|
|
||||||
orderNo, paymentStatus, paymentOrderId);
|
|
||||||
LambdaQueryWrapper<CustomerOrder> queryWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<CustomerOrder> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
queryWrapper.eq(CustomerOrder::getOrderNo, orderNo);
|
queryWrapper.eq(CustomerOrder::getOrderNo, orderNo);
|
||||||
CustomerOrder order = customerOrderMapper.selectOne(queryWrapper);
|
CustomerOrder order = customerOrderMapper.selectOne(queryWrapper);
|
||||||
|
|
||||||
if (order == null) {
|
if (order == null) {
|
||||||
log.warn("订单不存在,订单号: {}", orderNo);
|
|
||||||
throw new BusinessException(ResultCode.DATA_NOT_FOUND, "订单不存在");
|
throw new BusinessException(ResultCode.DATA_NOT_FOUND, "订单不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,10 +268,10 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
|
|||||||
// 如果支付成功,更新订单状态为已支付
|
// 如果支付成功,更新订单状态为已支付
|
||||||
if ("PAID".equals(paymentStatus)) {
|
if ("PAID".equals(paymentStatus)) {
|
||||||
order.setStatus("PAID");
|
order.setStatus("PAID");
|
||||||
|
log.info("订单支付成功,订单号: {}", orderNo);
|
||||||
}
|
}
|
||||||
|
|
||||||
customerOrderMapper.updateById(order);
|
customerOrderMapper.updateById(order);
|
||||||
log.info("订单支付状态更新成功,订单号: {}", orderNo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -277,16 +280,12 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
|
|||||||
String paymentCurrency,
|
String paymentCurrency,
|
||||||
BigDecimal paymentAmount,
|
BigDecimal paymentAmount,
|
||||||
BigDecimal exchangeRate) {
|
BigDecimal exchangeRate) {
|
||||||
log.info("更新订单货币转换信息,订单号: {}, 原始: {} {}, 支付: {} {} (汇率: {})",
|
|
||||||
orderNo, originalAmount, originalCurrency, paymentAmount, paymentCurrency, exchangeRate);
|
|
||||||
|
|
||||||
CustomerOrder order = customerOrderMapper.selectOne(
|
CustomerOrder order = customerOrderMapper.selectOne(
|
||||||
new LambdaQueryWrapper<CustomerOrder>()
|
new LambdaQueryWrapper<CustomerOrder>()
|
||||||
.eq(CustomerOrder::getOrderNo, orderNo)
|
.eq(CustomerOrder::getOrderNo, orderNo)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (order == null) {
|
if (order == null) {
|
||||||
log.warn("订单不存在,订单号: {}", orderNo);
|
|
||||||
throw new BusinessException(ResultCode.DATA_NOT_FOUND, "订单不存在");
|
throw new BusinessException(ResultCode.DATA_NOT_FOUND, "订单不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,24 +298,247 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
|
|||||||
order.setRateLockedAt(java.time.LocalDateTime.now());
|
order.setRateLockedAt(java.time.LocalDateTime.now());
|
||||||
|
|
||||||
customerOrderMapper.updateById(order);
|
customerOrderMapper.updateById(order);
|
||||||
log.info("订单货币转换信息更新成功,订单号: {}", orderNo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateOrderStatus(String orderNo, String status) {
|
public void updateOrderStatus(String orderNo, String status) {
|
||||||
log.info("更新订单状态,订单号: {}, 状态: {}", orderNo, status);
|
|
||||||
LambdaQueryWrapper<CustomerOrder> queryWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<CustomerOrder> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
queryWrapper.eq(CustomerOrder::getOrderNo, orderNo);
|
queryWrapper.eq(CustomerOrder::getOrderNo, orderNo);
|
||||||
CustomerOrder order = customerOrderMapper.selectOne(queryWrapper);
|
CustomerOrder order = customerOrderMapper.selectOne(queryWrapper);
|
||||||
|
|
||||||
if (order == null) {
|
if (order == null) {
|
||||||
log.warn("订单不存在,订单号: {}", orderNo);
|
|
||||||
throw new BusinessException(ResultCode.DATA_NOT_FOUND, "订单不存在");
|
throw new BusinessException(ResultCode.DATA_NOT_FOUND, "订单不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
order.setStatus(status);
|
order.setStatus(status);
|
||||||
customerOrderMapper.updateById(order);
|
customerOrderMapper.updateById(order);
|
||||||
log.info("订单状态更新成功,订单号: {}", orderNo);
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PageResult<OrderListResponseDTO> queryOrders(OrderQueryRequestDTO query) {
|
||||||
|
// 处理分页参数
|
||||||
|
int pageNum = query.getPageNum() != null && query.getPageNum() > 0 ? query.getPageNum() : 1;
|
||||||
|
int pageSize = query.getPageSize() != null && query.getPageSize() > 0 ? query.getPageSize() : 10;
|
||||||
|
|
||||||
|
// 限制每页最大数量
|
||||||
|
if (pageSize > 100) {
|
||||||
|
pageSize = 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 构建客户订单查询条件
|
||||||
|
LambdaQueryWrapper<CustomerOrder> orderWrapper = new LambdaQueryWrapper<>();
|
||||||
|
|
||||||
|
// 订单号查询
|
||||||
|
if (query.getOrderNo() != null && !query.getOrderNo().trim().isEmpty()) {
|
||||||
|
orderWrapper.eq(CustomerOrder::getOrderNo, query.getOrderNo().trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订单状态查询
|
||||||
|
if (query.getStatus() != null && !query.getStatus().trim().isEmpty()) {
|
||||||
|
orderWrapper.eq(CustomerOrder::getStatus, query.getStatus().trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 支付状态查询
|
||||||
|
if (query.getPaymentStatus() != null && !query.getPaymentStatus().trim().isEmpty()) {
|
||||||
|
orderWrapper.eq(CustomerOrder::getPaymentStatus, query.getPaymentStatus().trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客户姓名模糊查询
|
||||||
|
if (query.getCustomerName() != null && !query.getCustomerName().trim().isEmpty()) {
|
||||||
|
orderWrapper.like(CustomerOrder::getCustomerName, query.getCustomerName().trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客户电话模糊查询
|
||||||
|
if (query.getCustomerPhone() != null && !query.getCustomerPhone().trim().isEmpty()) {
|
||||||
|
orderWrapper.like(CustomerOrder::getCustomerPhone, query.getCustomerPhone().trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客户邮箱模糊查询
|
||||||
|
if (query.getCustomerEmail() != null && !query.getCustomerEmail().trim().isEmpty()) {
|
||||||
|
orderWrapper.like(CustomerOrder::getCustomerEmail, query.getCustomerEmail().trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 商品名称模糊查询
|
||||||
|
if (query.getProductName() != null && !query.getProductName().trim().isEmpty()) {
|
||||||
|
orderWrapper.like(CustomerOrder::getProductName, query.getProductName().trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时间范围查询
|
||||||
|
if (query.getStartTime() != null && !query.getStartTime().trim().isEmpty()) {
|
||||||
|
try {
|
||||||
|
java.time.LocalDateTime start = java.time.LocalDateTime.parse(query.getStartTime().trim());
|
||||||
|
orderWrapper.ge(CustomerOrder::getCreateTime, start);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("开始时间格式错误: {}", query.getStartTime(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (query.getEndTime() != null && !query.getEndTime().trim().isEmpty()) {
|
||||||
|
try {
|
||||||
|
java.time.LocalDateTime end = java.time.LocalDateTime.parse(query.getEndTime().trim());
|
||||||
|
orderWrapper.le(CustomerOrder::getCreateTime, end);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("结束时间格式错误: {}", query.getEndTime(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果指定了PayPal相关查询条件,需要先查询PayPal订单,获取对应的商户订单号
|
||||||
|
java.util.List<String> merchantOrderNos = null;
|
||||||
|
if (query.getPaypalOrderId() != null && !query.getPaypalOrderId().trim().isEmpty() ||
|
||||||
|
query.getMerchantOrderNo() != null && !query.getMerchantOrderNo().trim().isEmpty() ||
|
||||||
|
query.getPaypalStatus() != null && !query.getPaypalStatus().trim().isEmpty() ||
|
||||||
|
query.getPaypalPaymentStatus() != null && !query.getPaypalPaymentStatus().trim().isEmpty() ||
|
||||||
|
query.getPayerEmail() != null && !query.getPayerEmail().trim().isEmpty() ||
|
||||||
|
query.getPayerName() != null && !query.getPayerName().trim().isEmpty()) {
|
||||||
|
|
||||||
|
// 构建PayPal订单查询条件
|
||||||
|
LambdaQueryWrapper<PayPalPaymentOrder> paypalWrapper = new LambdaQueryWrapper<>();
|
||||||
|
|
||||||
|
if (query.getPaypalOrderId() != null && !query.getPaypalOrderId().trim().isEmpty()) {
|
||||||
|
paypalWrapper.eq(PayPalPaymentOrder::getPaypalOrderId, query.getPaypalOrderId().trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.getMerchantOrderNo() != null && !query.getMerchantOrderNo().trim().isEmpty()) {
|
||||||
|
paypalWrapper.eq(PayPalPaymentOrder::getMerchantOrderNo, query.getMerchantOrderNo().trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.getPaypalStatus() != null && !query.getPaypalStatus().trim().isEmpty()) {
|
||||||
|
paypalWrapper.eq(PayPalPaymentOrder::getStatus, query.getPaypalStatus().trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.getPaypalPaymentStatus() != null && !query.getPaypalPaymentStatus().trim().isEmpty()) {
|
||||||
|
paypalWrapper.eq(PayPalPaymentOrder::getPaymentStatus, query.getPaypalPaymentStatus().trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.getPayerEmail() != null && !query.getPayerEmail().trim().isEmpty()) {
|
||||||
|
paypalWrapper.like(PayPalPaymentOrder::getPayerEmail, query.getPayerEmail().trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.getPayerName() != null && !query.getPayerName().trim().isEmpty()) {
|
||||||
|
paypalWrapper.like(PayPalPaymentOrder::getPayerName, query.getPayerName().trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询PayPal订单,获取商户订单号列表
|
||||||
|
java.util.List<PayPalPaymentOrder> paypalOrders = payPalPaymentOrderMapper.selectList(paypalWrapper);
|
||||||
|
merchantOrderNos = paypalOrders.stream()
|
||||||
|
.map(PayPalPaymentOrder::getMerchantOrderNo)
|
||||||
|
.filter(java.util.Objects::nonNull)
|
||||||
|
.distinct()
|
||||||
|
.collect(java.util.stream.Collectors.toList());
|
||||||
|
|
||||||
|
if (merchantOrderNos.isEmpty()) {
|
||||||
|
// 如果PayPal订单查询结果为空,直接返回空分页结果
|
||||||
|
log.debug("PayPal订单查询结果为空,返回空列表");
|
||||||
|
return new PageResult<>((long) pageNum, (long) pageSize, 0L, new java.util.ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将商户订单号作为客户订单的查询条件
|
||||||
|
orderWrapper.in(CustomerOrder::getOrderNo, merchantOrderNos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按创建时间降序排序
|
||||||
|
orderWrapper.orderByDesc(CustomerOrder::getCreateTime);
|
||||||
|
|
||||||
|
// 使用MyBatis-Plus分页查询
|
||||||
|
Page<CustomerOrder> page = new Page<>(pageNum, pageSize);
|
||||||
|
IPage<CustomerOrder> orderPage = customerOrderMapper.selectPage(page, orderWrapper);
|
||||||
|
|
||||||
|
java.util.List<CustomerOrder> orders = orderPage.getRecords();
|
||||||
|
|
||||||
|
if (orders.isEmpty()) {
|
||||||
|
return new PageResult<>((long) pageNum, (long) pageSize, orderPage.getTotal(), new java.util.ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 批量查询PayPal订单信息(优化N+1问题)
|
||||||
|
java.util.List<String> orderNos = orders.stream()
|
||||||
|
.map(CustomerOrder::getOrderNo)
|
||||||
|
.filter(java.util.Objects::nonNull)
|
||||||
|
.collect(java.util.stream.Collectors.toList());
|
||||||
|
|
||||||
|
java.util.Map<String, PayPalPaymentOrder> paypalOrderMap = new java.util.HashMap<>();
|
||||||
|
if (!orderNos.isEmpty()) {
|
||||||
|
LambdaQueryWrapper<PayPalPaymentOrder> paypalWrapper = new LambdaQueryWrapper<>();
|
||||||
|
paypalWrapper.in(PayPalPaymentOrder::getMerchantOrderNo, orderNos);
|
||||||
|
java.util.List<PayPalPaymentOrder> paypalOrders = payPalPaymentOrderMapper.selectList(paypalWrapper);
|
||||||
|
|
||||||
|
paypalOrderMap = paypalOrders.stream()
|
||||||
|
.collect(java.util.stream.Collectors.toMap(
|
||||||
|
PayPalPaymentOrder::getMerchantOrderNo,
|
||||||
|
order -> order,
|
||||||
|
(existing, replacement) -> existing // 如果有重复,保留第一个
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 组装响应数据
|
||||||
|
java.util.List<OrderListResponseDTO> result = new java.util.ArrayList<>();
|
||||||
|
for (CustomerOrder order : orders) {
|
||||||
|
OrderListResponseDTO dto = new OrderListResponseDTO();
|
||||||
|
BeanUtils.copyProperties(order, dto);
|
||||||
|
|
||||||
|
// 设置PayPal订单信息
|
||||||
|
PayPalPaymentOrder paypalOrder = paypalOrderMap.get(order.getOrderNo());
|
||||||
|
if (paypalOrder != null) {
|
||||||
|
dto.setPaypalOrderId(paypalOrder.getPaypalOrderId());
|
||||||
|
dto.setPaypalStatus(paypalOrder.getStatus());
|
||||||
|
dto.setPaypalPaymentStatus(paypalOrder.getPaymentStatus());
|
||||||
|
dto.setPayerEmail(paypalOrder.getPayerEmail());
|
||||||
|
dto.setPayerName(paypalOrder.getPayerName());
|
||||||
|
dto.setCaptureId(paypalOrder.getCaptureId());
|
||||||
|
dto.setPaypalCreateTime(paypalOrder.getCreateTime());
|
||||||
|
dto.setPaypalUpdateTime(paypalOrder.getUpdateTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
result.add(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建分页结果
|
||||||
|
return new PageResult<>(
|
||||||
|
orderPage.getCurrent(),
|
||||||
|
orderPage.getSize(),
|
||||||
|
orderPage.getTotal(),
|
||||||
|
result
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void cancelOrder(String orderNo) {
|
||||||
|
// 查询订单
|
||||||
|
LambdaQueryWrapper<CustomerOrder> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
|
queryWrapper.eq(CustomerOrder::getOrderNo, orderNo);
|
||||||
|
CustomerOrder order = customerOrderMapper.selectOne(queryWrapper);
|
||||||
|
|
||||||
|
if (order == null) {
|
||||||
|
throw new BusinessException(ResultCode.DATA_NOT_FOUND, "订单不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查订单状态,只有未完成或未取消的订单才能取消
|
||||||
|
String currentStatus = order.getStatus();
|
||||||
|
if ("CANCELLED".equals(currentStatus)) {
|
||||||
|
return; // 已取消,无需重复操作
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("COMPLETED".equals(currentStatus)) {
|
||||||
|
throw new BusinessException(ResultCode.BUSINESS_ERROR, "订单已完成,无法取消");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 恢复库存(只有在订单状态为PENDING或PAID时才恢复库存)
|
||||||
|
if (("PENDING".equals(currentStatus) || "PAID".equals(currentStatus))
|
||||||
|
&& order.getSkuId() != null && order.getQuantity() != null) {
|
||||||
|
try {
|
||||||
|
productSkuMapper.restoreStock(order.getSkuId(), order.getQuantity());
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 库存恢复失败不影响订单取消,继续执行
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新订单状态为已取消
|
||||||
|
order.setStatus("CANCELLED");
|
||||||
|
if ("UNPAID".equals(order.getPaymentStatus())) {
|
||||||
|
order.setPaymentStatus("CANCELLED");
|
||||||
|
}
|
||||||
|
customerOrderMapper.updateById(order);
|
||||||
|
|
||||||
|
log.info("订单取消成功,订单号: {}, 原状态: {}, 库存已恢复", orderNo, currentStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,26 +76,17 @@ public class PayPalWebhookServiceImpl implements PayPalWebhookService {
|
|||||||
*/
|
*/
|
||||||
@Async("paypalWebhookExecutor")
|
@Async("paypalWebhookExecutor")
|
||||||
public void processWebhookEventAsync(PayPalWebhookEventDTO event) {
|
public void processWebhookEventAsync(PayPalWebhookEventDTO event) {
|
||||||
log.info("开始异步处理PayPal Webhook事件,事件ID: {}, 事件类型: {}", event.getId(), event.getEventType());
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
processWebhookEvent(event);
|
processWebhookEvent(event);
|
||||||
long elapsedTime = System.currentTimeMillis() - startTime;
|
|
||||||
log.info("✅ Webhook事件异步处理完成,事件ID: {}, 事件类型: {}, 处理时间: {}ms",
|
|
||||||
event.getId(), event.getEventType(), elapsedTime);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
long elapsedTime = System.currentTimeMillis() - startTime;
|
log.error("Webhook事件处理异常,事件ID: {}, 事件类型: {}",
|
||||||
log.error("❌ Webhook事件异步处理异常,事件ID: {}, 事件类型: {}, 处理时间: {}ms",
|
event.getId(), event.getEventType(), e);
|
||||||
event.getId(), event.getEventType(), elapsedTime, e);
|
|
||||||
// 异步处理中的异常不会影响HTTP响应,已在上层捕获
|
// 异步处理中的异常不会影响HTTP响应,已在上层捕获
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void processWebhookEvent(PayPalWebhookEventDTO event) {
|
public void processWebhookEvent(PayPalWebhookEventDTO event) {
|
||||||
log.info("处理PayPal Webhook事件,事件ID: {}, 事件类型: {}", event.getId(), event.getEventType());
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String eventType = event.getEventType();
|
String eventType = event.getEventType();
|
||||||
|
|
||||||
@@ -119,13 +110,10 @@ public class PayPalWebhookServiceImpl implements PayPalWebhookService {
|
|||||||
Map<String, Object> relatedIds = (Map<String, Object>) supplementaryData.get("related_ids");
|
Map<String, Object> relatedIds = (Map<String, Object>) supplementaryData.get("related_ids");
|
||||||
if (relatedIds != null) {
|
if (relatedIds != null) {
|
||||||
paypalOrderId = (String) relatedIds.get("order_id");
|
paypalOrderId = (String) relatedIds.get("order_id");
|
||||||
log.debug("从supplementary_data提取PayPal订单ID: {}", paypalOrderId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("提取PayPal订单ID: {}, 事件类型: {}", paypalOrderId, eventType);
|
|
||||||
|
|
||||||
// 尝试获取商户订单号
|
// 尝试获取商户订单号
|
||||||
if (paypalOrderId != null) {
|
if (paypalOrderId != null) {
|
||||||
try {
|
try {
|
||||||
@@ -133,10 +121,9 @@ public class PayPalWebhookServiceImpl implements PayPalWebhookService {
|
|||||||
if (paypalOrder != null && paypalOrder.getPurchaseUnits() != null
|
if (paypalOrder != null && paypalOrder.getPurchaseUnits() != null
|
||||||
&& !paypalOrder.getPurchaseUnits().isEmpty()) {
|
&& !paypalOrder.getPurchaseUnits().isEmpty()) {
|
||||||
merchantOrderNo = paypalOrder.getPurchaseUnits().get(0).getReferenceId();
|
merchantOrderNo = paypalOrder.getPurchaseUnits().get(0).getReferenceId();
|
||||||
log.debug("提取商户订单号: {}", merchantOrderNo);
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.debug("无法获取PayPal订单详情,PayPal订单ID: {}", paypalOrderId, e);
|
// 静默处理
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -165,23 +152,15 @@ public class PayPalWebhookServiceImpl implements PayPalWebhookService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 重要:更新PayPal支付订单信息(无论事件类型,只要获取到订单ID就更新)
|
// 重要:更新PayPal支付订单信息(无论事件类型,只要获取到订单ID就更新)
|
||||||
// 这样可以确保所有字段都被正确填充
|
|
||||||
if (paypalOrderId != null) {
|
if (paypalOrderId != null) {
|
||||||
try {
|
try {
|
||||||
log.info("Webhook事件触发订单信息更新,PayPal订单ID: {}, 事件类型: {}", paypalOrderId, eventType);
|
|
||||||
var paypalOrder = payPalService.getOrder(paypalOrderId);
|
var paypalOrder = payPalService.getOrder(paypalOrderId);
|
||||||
if (paypalOrder != null) {
|
if (paypalOrder != null) {
|
||||||
payPalPaymentOrderService.updateOrderFromPayPal(paypalOrderId, paypalOrder);
|
payPalPaymentOrderService.updateOrderFromPayPal(paypalOrderId, paypalOrder);
|
||||||
log.info("Webhook事件订单信息更新成功,PayPal订单ID: {}, 状态: {}, 事件类型: {}",
|
|
||||||
paypalOrderId, paypalOrder.getStatus(), eventType);
|
|
||||||
} else {
|
|
||||||
log.warn("查询PayPal订单详情返回null,PayPal订单ID: {}", paypalOrderId);
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("更新PayPal支付订单信息失败,PayPal订单ID: {}, 事件类型: {}", paypalOrderId, eventType, e);
|
// 静默处理,不影响主流程
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
log.warn("无法提取PayPal订单ID,无法更新订单信息,事件类型: {}, 事件ID: {}", eventType, event.getId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据事件类型处理不同的业务逻辑
|
// 根据事件类型处理不同的业务逻辑
|
||||||
@@ -211,10 +190,10 @@ public class PayPalWebhookServiceImpl implements PayPalWebhookService {
|
|||||||
handleOrderDeclined(event);
|
handleOrderDeclined(event);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
log.info("未处理的事件类型: {}(已记录到数据库,但未执行特定业务逻辑)", eventType);
|
// 未处理的事件类型,已记录到数据库
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("处理Webhook事件异常,事件ID: {}", event.getId(), e);
|
log.error("处理Webhook事件异常,事件ID: {}, 事件类型: {}", event.getId(), event.getEventType(), e);
|
||||||
throw new RuntimeException("处理Webhook事件失败: " + e.getMessage(), e);
|
throw new RuntimeException("处理Webhook事件失败: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -319,6 +298,9 @@ public class PayPalWebhookServiceImpl implements PayPalWebhookService {
|
|||||||
try {
|
try {
|
||||||
customerOrderService.updatePaymentStatus(referenceId, "PAID", null);
|
customerOrderService.updatePaymentStatus(referenceId, "PAID", null);
|
||||||
log.info("ERP订单支付状态已更新,订单号: {}", referenceId);
|
log.info("ERP订单支付状态已更新,订单号: {}", referenceId);
|
||||||
|
// 注意:库存已在创建订单时扣减,支付成功时不需要再次扣减
|
||||||
|
// 这里只记录日志,确认库存扣减已完成
|
||||||
|
log.info("订单支付成功,库存扣减已确认,订单号: {}", referenceId);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("更新ERP订单状态失败,订单号: {}", referenceId, e);
|
log.error("更新ERP订单状态失败,订单号: {}", referenceId, e);
|
||||||
}
|
}
|
||||||
@@ -461,8 +443,6 @@ public class PayPalWebhookServiceImpl implements PayPalWebhookService {
|
|||||||
* 处理订单取消事件
|
* 处理订单取消事件
|
||||||
*/
|
*/
|
||||||
private void handleOrderCancelled(PayPalWebhookEventDTO event) {
|
private void handleOrderCancelled(PayPalWebhookEventDTO event) {
|
||||||
log.info("处理订单取消事件,事件ID: {}", event.getId());
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Map<String, Object> resource = event.getResource();
|
Map<String, Object> resource = event.getResource();
|
||||||
if (resource == null) {
|
if (resource == null) {
|
||||||
@@ -470,9 +450,6 @@ public class PayPalWebhookServiceImpl implements PayPalWebhookService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String orderId = (String) resource.get("id");
|
String orderId = (String) resource.get("id");
|
||||||
String status = (String) resource.get("status");
|
|
||||||
|
|
||||||
log.info("订单已取消/作废,Order ID: {}, 状态: {}", orderId, status);
|
|
||||||
|
|
||||||
// 更新PayPal支付订单信息
|
// 更新PayPal支付订单信息
|
||||||
if (orderId != null) {
|
if (orderId != null) {
|
||||||
@@ -480,15 +457,29 @@ public class PayPalWebhookServiceImpl implements PayPalWebhookService {
|
|||||||
var paypalOrder = payPalService.getOrder(orderId);
|
var paypalOrder = payPalService.getOrder(orderId);
|
||||||
if (paypalOrder != null) {
|
if (paypalOrder != null) {
|
||||||
payPalPaymentOrderService.updateOrderFromPayPal(orderId, paypalOrder);
|
payPalPaymentOrderService.updateOrderFromPayPal(orderId, paypalOrder);
|
||||||
log.info("PayPal支付订单信息已更新(订单取消/作废后),PayPal订单ID: {}, 状态: {}",
|
|
||||||
orderId, paypalOrder.getStatus());
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("更新PayPal支付订单信息失败,PayPal订单ID: {}", orderId, e);
|
// 静默处理
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: 根据业务需求处理订单取消逻辑(如更新ERP订单状态为已取消)
|
// 处理订单取消逻辑:更新ERP订单状态为已取消,并恢复库存
|
||||||
|
if (orderId != null) {
|
||||||
|
try {
|
||||||
|
var paypalOrder = payPalService.getOrder(orderId);
|
||||||
|
if (paypalOrder != null && paypalOrder.getPurchaseUnits() != null
|
||||||
|
&& !paypalOrder.getPurchaseUnits().isEmpty()) {
|
||||||
|
String merchantOrderNo = paypalOrder.getPurchaseUnits().get(0).getReferenceId();
|
||||||
|
|
||||||
|
if (merchantOrderNo != null) {
|
||||||
|
customerOrderService.cancelOrder(merchantOrderNo);
|
||||||
|
log.info("订单已取消,订单号: {}", merchantOrderNo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("处理订单取消失败,PayPal订单ID: {}", orderId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("处理订单取消事件异常,事件ID: {}", event.getId(), e);
|
log.error("处理订单取消事件异常,事件ID: {}", event.getId(), e);
|
||||||
@@ -500,8 +491,6 @@ public class PayPalWebhookServiceImpl implements PayPalWebhookService {
|
|||||||
* 当用户拒绝支付或支付失败时触发
|
* 当用户拒绝支付或支付失败时触发
|
||||||
*/
|
*/
|
||||||
private void handleOrderDeclined(PayPalWebhookEventDTO event) {
|
private void handleOrderDeclined(PayPalWebhookEventDTO event) {
|
||||||
log.info("处理订单拒绝事件,事件ID: {}", event.getId());
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Map<String, Object> resource = event.getResource();
|
Map<String, Object> resource = event.getResource();
|
||||||
if (resource == null) {
|
if (resource == null) {
|
||||||
@@ -509,9 +498,6 @@ public class PayPalWebhookServiceImpl implements PayPalWebhookService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String orderId = (String) resource.get("id");
|
String orderId = (String) resource.get("id");
|
||||||
String status = (String) resource.get("status");
|
|
||||||
|
|
||||||
log.info("订单已被拒绝,Order ID: {}, 状态: {}", orderId, status);
|
|
||||||
|
|
||||||
// 更新PayPal支付订单信息
|
// 更新PayPal支付订单信息
|
||||||
if (orderId != null) {
|
if (orderId != null) {
|
||||||
@@ -519,15 +505,29 @@ public class PayPalWebhookServiceImpl implements PayPalWebhookService {
|
|||||||
var paypalOrder = payPalService.getOrder(orderId);
|
var paypalOrder = payPalService.getOrder(orderId);
|
||||||
if (paypalOrder != null) {
|
if (paypalOrder != null) {
|
||||||
payPalPaymentOrderService.updateOrderFromPayPal(orderId, paypalOrder);
|
payPalPaymentOrderService.updateOrderFromPayPal(orderId, paypalOrder);
|
||||||
log.info("PayPal支付订单信息已更新(订单拒绝后),PayPal订单ID: {}, 状态: {}",
|
|
||||||
orderId, paypalOrder.getStatus());
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("更新PayPal支付订单信息失败,PayPal订单ID: {}", orderId, e);
|
// 静默处理
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: 根据业务需求处理订单拒绝逻辑(如更新ERP订单状态为支付失败)
|
// 处理订单拒绝逻辑:更新ERP订单状态为支付失败,并恢复库存
|
||||||
|
if (orderId != null) {
|
||||||
|
try {
|
||||||
|
var paypalOrder = payPalService.getOrder(orderId);
|
||||||
|
if (paypalOrder != null && paypalOrder.getPurchaseUnits() != null
|
||||||
|
&& !paypalOrder.getPurchaseUnits().isEmpty()) {
|
||||||
|
String merchantOrderNo = paypalOrder.getPurchaseUnits().get(0).getReferenceId();
|
||||||
|
|
||||||
|
if (merchantOrderNo != null) {
|
||||||
|
customerOrderService.cancelOrder(merchantOrderNo);
|
||||||
|
log.info("订单已拒绝,订单号: {}", merchantOrderNo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("处理订单拒绝失败,PayPal订单ID: {}", orderId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("处理订单拒绝事件异常,事件ID: {}", event.getId(), e);
|
log.error("处理订单拒绝事件异常,事件ID: {}", event.getId(), e);
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ public class ProductServiceImpl implements ProductService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private BaiduTranslatorUtils baiduTranslatorUtils;
|
private BaiduTranslatorUtils baiduTranslatorUtils;
|
||||||
|
|
||||||
@Value("${server.port:8082}")
|
@Value("${server.port:18082}")
|
||||||
private String serverPort;
|
private String serverPort;
|
||||||
|
|
||||||
@Value("${server.servlet.context-path:}")
|
@Value("${server.servlet.context-path:}")
|
||||||
@@ -128,9 +128,6 @@ public class ProductServiceImpl implements ProductService {
|
|||||||
|
|
||||||
// 创建SKU(优化:并行翻译 + 批量插入)
|
// 创建SKU(优化:并行翻译 + 批量插入)
|
||||||
if (request.getSkus() != null && !request.getSkus().isEmpty()) {
|
if (request.getSkus() != null && !request.getSkus().isEmpty()) {
|
||||||
long skuStartTime = System.currentTimeMillis();
|
|
||||||
log.debug("开始创建SKU,数量: {}", request.getSkus().size());
|
|
||||||
|
|
||||||
// 第一步:并行翻译所有SKU名称
|
// 第一步:并行翻译所有SKU名称
|
||||||
List<CompletableFuture<SkuTranslationResult>> translationFutures = new ArrayList<>();
|
List<CompletableFuture<SkuTranslationResult>> translationFutures = new ArrayList<>();
|
||||||
for (CreateProductRequestDTO.CreateProductSkuDTO skuDTO : request.getSkus()) {
|
for (CreateProductRequestDTO.CreateProductSkuDTO skuDTO : request.getSkus()) {
|
||||||
@@ -145,12 +142,9 @@ public class ProductServiceImpl implements ProductService {
|
|||||||
translatedSkuName = baiduTranslatorUtils.getTransResult(originalSkuName, targetLanguage);
|
translatedSkuName = baiduTranslatorUtils.getTransResult(originalSkuName, targetLanguage);
|
||||||
if (translatedSkuName == null || translatedSkuName.equals(originalSkuName)) {
|
if (translatedSkuName == null || translatedSkuName.equals(originalSkuName)) {
|
||||||
translatedSkuName = originalSkuName; // 翻译失败或无需翻译,使用原文
|
translatedSkuName = originalSkuName; // 翻译失败或无需翻译,使用原文
|
||||||
} else {
|
|
||||||
log.debug("SKU名称翻译: {} -> {} (货币: {}, 语言: {})",
|
|
||||||
originalSkuName, translatedSkuName, currency, targetLanguage);
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("翻译SKU名称失败,使用原文,SKU: {}, 货币: {}", originalSkuName, currency, e);
|
// 翻译失败使用原文,静默处理
|
||||||
translatedSkuName = originalSkuName;
|
translatedSkuName = originalSkuName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,7 +160,6 @@ public class ProductServiceImpl implements ProductService {
|
|||||||
CompletableFuture.allOf(translationFutures.toArray(new CompletableFuture[0]))
|
CompletableFuture.allOf(translationFutures.toArray(new CompletableFuture[0]))
|
||||||
.get(30, TimeUnit.SECONDS);
|
.get(30, TimeUnit.SECONDS);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("等待SKU翻译完成超时或失败", e);
|
|
||||||
// 继续执行,使用原文
|
// 继续执行,使用原文
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,7 +188,6 @@ public class ProductServiceImpl implements ProductService {
|
|||||||
|
|
||||||
skuList.add(sku);
|
skuList.add(sku);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("获取SKU翻译结果失败,使用原文,SKU: {}", skuDTO.getSku(), e);
|
|
||||||
// 使用原始SKU信息
|
// 使用原始SKU信息
|
||||||
MtProductSku sku = new MtProductSku();
|
MtProductSku sku = new MtProductSku();
|
||||||
sku.setProductId(product.getId());
|
sku.setProductId(product.getId());
|
||||||
@@ -222,9 +214,6 @@ public class ProductServiceImpl implements ProductService {
|
|||||||
throw new BusinessException(ResultCode.SYSTEM_ERROR,
|
throw new BusinessException(ResultCode.SYSTEM_ERROR,
|
||||||
String.format("批量创建SKU失败,期望插入%d个,实际插入%d个", skuList.size(), insertCount));
|
String.format("批量创建SKU失败,期望插入%d个,实际插入%d个", skuList.size(), insertCount));
|
||||||
}
|
}
|
||||||
long skuEndTime = System.currentTimeMillis();
|
|
||||||
log.info("SKU批量创建成功,商品ID: {}, SKU数量: {}, 耗时: {}ms",
|
|
||||||
product.getId(), skuList.size(), skuEndTime - skuStartTime);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ spring:
|
|||||||
|
|
||||||
# 服务器配置(所有环境通用)
|
# 服务器配置(所有环境通用)
|
||||||
server:
|
server:
|
||||||
port: 8082
|
port: 18082
|
||||||
servlet:
|
servlet:
|
||||||
context-path: /
|
context-path: /
|
||||||
# 文件上传配置
|
# 文件上传配置
|
||||||
@@ -79,7 +79,7 @@ server:
|
|||||||
app:
|
app:
|
||||||
# 前端访问地址(用于生成商品详情页URL等)
|
# 前端访问地址(用于生成商品详情页URL等)
|
||||||
frontend:
|
frontend:
|
||||||
url: http://localhost:3000
|
url: http://175.178.252.59:3000
|
||||||
|
|
||||||
# 阿里云OSS相关配置(所有环境通用)
|
# 阿里云OSS相关配置(所有环境通用)
|
||||||
aliyun:
|
aliyun:
|
||||||
@@ -111,9 +111,9 @@ paypal:
|
|||||||
mode: sandbox
|
mode: sandbox
|
||||||
# 是否启用PayPal支付
|
# 是否启用PayPal支付
|
||||||
enabled: true
|
enabled: true
|
||||||
# Webhook URL(内网穿透公网地址 + 回调接口路径)
|
# Webhook URL(服务器公网地址)
|
||||||
# 注意:需要在PayPal控制台配置此URL
|
# 注意:需要在PayPal控制台配置此URL
|
||||||
webhook-url: https://2646b437.r33.cpolar.top/api/paypal/webhook
|
webhook-url: http://175.178.252.59:18082/api/paypal/webhook
|
||||||
# Webhook ID(从PayPal控制台获取,用于验证Webhook签名)
|
# Webhook ID(从PayPal控制台获取,用于验证Webhook签名)
|
||||||
webhook-id: 0SX6117212808615P
|
webhook-id: 0SX6117212808615P
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,22 @@ spring:
|
|||||||
config:
|
config:
|
||||||
multi-statement-allow: true
|
multi-statement-allow: true
|
||||||
|
|
||||||
|
# 服务器配置
|
||||||
|
server:
|
||||||
|
port: ${server.port:18082}
|
||||||
|
servlet:
|
||||||
|
context-path: /
|
||||||
|
multipart:
|
||||||
|
max-file-size: 10MB
|
||||||
|
max-request-size: 50MB
|
||||||
|
file-size-threshold: 2MB
|
||||||
|
|
||||||
|
# 应用配置
|
||||||
|
app:
|
||||||
|
# 前端访问地址
|
||||||
|
frontend:
|
||||||
|
url: ${app.frontend.url:http://175.178.252.59:3000}
|
||||||
|
|
||||||
# PingPong支付配置(生产环境)
|
# PingPong支付配置(生产环境)
|
||||||
pingpong:
|
pingpong:
|
||||||
client-id: ${pingpong.client-id}
|
client-id: ${pingpong.client-id}
|
||||||
@@ -71,13 +87,20 @@ pingpong:
|
|||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
# PayPal支付配置(生产环境)
|
# PayPal支付配置(生产环境)
|
||||||
|
# 注意:当前为测试环境,使用沙箱凭证
|
||||||
|
# 正式环境需要替换为生产环境的Client ID和Secret
|
||||||
paypal:
|
paypal:
|
||||||
# PayPal Client ID(API密钥)- 从环境变量或配置中心获取
|
# PayPal Client ID(API密钥)- 从环境变量或配置中心获取
|
||||||
client-id: AdGYUZpvLuHR30dybOApvM-RNB1pVKtd74SVfh-6TK52xV-1JEBddHVMCWuDdyyHri4DXd4kABBi7Icb
|
client-id: AdGYUZpvLuHR30dybOApvM-RNB1pVKtd74SVfh-6TK52xV-1JEBddHVMCWuDdyyHri4DXd4kABBi7Icb
|
||||||
# PayPal Client Secret(密钥)- 从环境变量或配置中心获取
|
# PayPal Client Secret(密钥)- 从环境变量或配置中心获取
|
||||||
client-secret: ENblspyRmwsOU_PWFurlhEYUF5Da6aYKl0pjK4ehm7p3R5aSqvbpaF_YsIIs8v0ty1c9WJu15XP-Fe_1
|
client-secret: ENblspyRmwsOU_PWFurlhEYUF5Da6aYKl0pjK4ehm7p3R5aSqvbpaF_YsIIs8v0ty1c9WJu15XP-Fe_1
|
||||||
# 环境模式:sandbox(沙箱)或 production(生产)
|
# 环境模式:sandbox(沙箱)或 production(生产)
|
||||||
|
# 当前为测试环境,使用sandbox
|
||||||
mode: sandbox
|
mode: sandbox
|
||||||
# 是否启用PayPal支付
|
# 是否启用PayPal支付
|
||||||
enabled: true
|
enabled: true
|
||||||
|
# Webhook URL(部署时请修改为服务器的公网地址)
|
||||||
|
webhook-url: ${paypal.webhook-url:https://your-domain.com/api/paypal/webhook}
|
||||||
|
# Webhook ID(从PayPal控制台获取)
|
||||||
|
webhook-id: ${paypal.webhook-id:}
|
||||||
|
|
||||||
|
|||||||
5965
replay_pid15540.log
Normal file
5965
replay_pid15540.log
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user