feat(order): 扩展订单地址字段并集成百度翻译服务

- 添加东南亚地址扩展字段到CreateCustomerOrderRequestDTO
- 在CustomerOrder实体类中新增详细地址字段和特殊地址字段JSON存储
- 实现CustomerOrderServiceImpl中地址字段的存储和转换逻辑
- 集成BaiduTranslatorUtils实现订单内容自动翻译功能
- 在CustomerOrderResponseDTO中添加特殊地址字段Map格式支持
- 配置百度翻译API相关参数到application-dev.yml
- 移除过时的架构文档和配置说明文件
This commit is contained in:
2025-12-24 11:19:48 +08:00
parent 48eece45e5
commit 425c46217e
25 changed files with 490 additions and 2415 deletions

View File

@@ -1,108 +0,0 @@
# 修复编译问题指南
## 问题描述
IDE提示"Java file is located outside of the module source root, so it won't be compiled"
## 解决步骤
### 方法1在 IntelliJ IDEA 中重新导入项目(推荐)
1. **关闭项目**
- File → Close Project
2. **重新打开项目**
- File → Open
- 选择 `E:\MTKJPAY` 目录
- 选择 "Open as Project"
3. **等待 Maven 导入完成**
- 右下角会显示 "Importing Maven projects..."
- 等待完成
4. **刷新 Maven 项目**
- 右键根目录项目 → Maven → Reload Project
- 或者View → Tool Windows → Maven → 点击刷新按钮
5. **重新构建项目**
- Build → Rebuild Project
### 方法2手动配置 Source Root
1. **打开项目结构**
- File → Project Structure (Ctrl+Alt+Shift+S)
2. **检查 Modules**
- 左侧选择 "Modules"
- 确认有两个模块:
- `MTKJPAY` (根模块)
- `mt-pay` (子模块)
3. **配置 MTKJPAY 模块**
- 选择 `MTKJPAY` 模块
- 在 "Sources" 标签页
- 确认 `src/main/java` 标记为蓝色Source Folders
- 确认 `src/main/resources` 标记为绿色Resources Folders
- 如果没有标记,右键文件夹 → Mark Directory as → Sources Root / Resources Root
4. **配置 mt-pay 模块**
- 选择 `mt-pay` 模块
- 在 "Sources" 标签页
- 确认 `mt-pay/src/main/java` 标记为蓝色
- 确认 `mt-pay/src/main/resources` 标记为绿色
5. **应用并确定**
- 点击 "Apply" → "OK"
### 方法3使用 Maven 命令编译
```bash
# 进入项目根目录
cd E:\MTKJPAY
# 清理并编译
mvn clean compile
# 或者安装到本地仓库
mvn clean install
```
### 方法4检查 IDE 设置
1. **检查 Maven 设置**
- File → Settings → Build, Execution, Deployment → Build Tools → Maven
- 确认 "Maven home directory" 正确
- 确认 "User settings file" 正确
2. **检查 Java 设置**
- File → Settings → Build, Execution, Deployment → Compiler → Java Compiler
- 确认 "Project bytecode version" 是 17
3. **检查项目 SDK**
- File → Project Structure → Project
- 确认 "SDK" 是 Java 17
- 确认 "Language level" 是 17
## 验证修复
编译成功后:
1. 在 IDE 中Java 文件不应该有红色波浪线
2. 可以正常启动 `MtkjpayApplication`
3. 控制台没有编译错误
## 如果仍然有问题
1. **删除 .idea 文件夹**(需要关闭项目)
- 关闭 IntelliJ IDEA
- 删除 `E:\MTKJPAY\.idea` 文件夹
- 重新打开项目
2. **删除 target 文件夹**
```bash
cd E:\MTKJPAY
rmdir /s /q target
rmdir /s /q mt-pay\target
```
3. **重新导入项目**
- 按照方法1重新导入

View File

@@ -1,121 +0,0 @@
# 如何正确启动后端服务
## ⚠️ 重要:启动正确的模块
项目有两个启动类,**必须启动 `mt-pay` 模块的启动类**
### ✅ 正确的启动类
- **类名**`com.mtkj.mtpay.MtPayApplication`
- **位置**`MTKJPAY/mt-pay/src/main/java/com/mtkj/mtpay/MtPayApplication.java`
- **端口**8082
- **包含**:所有业务代码、控制器、服务等
### ❌ 错误的启动类(不要启动这个!)
- **类名**`com.mtkj.mtkjpay.MtkjpayApplication`
- **位置**`MTKJPAY/src/main/java/com/mtkj/mtkjpay/MtkjpayApplication.java`
- **端口**8080
- **包含**:仅占位代码,无业务功能
## 在 IntelliJ IDEA 中启动
### 步骤1确认项目结构
确保在 Project 视图中能看到 `mt-pay` 模块:
```
MTKJPAY
├── mt-pay ← 这个模块
│ └── src
│ └── main
│ └── java
│ └── com
│ └── mtkj
│ └── mtpay
│ └── MtPayApplication.java ← 启动这个
└── src ← 根目录(不要启动这个)
└── main
└── java
└── com
└── mtkj
└── mtkjpay
└── MtkjpayApplication.java ← 不要启动这个
```
### 步骤2打开正确的启动类
1. 在 Project 视图中,导航到:
`MTKJPAY``mt-pay``src``main``java``com``mtkj``mtpay``MtPayApplication.java`
2. 双击打开 `MtPayApplication.java`
### 步骤3运行启动类
1. 右键点击 `MtPayApplication.java` 文件
2. 选择 **"Run 'MtPayApplication.main()'"**
3. 或者点击类名旁边的绿色运行按钮
### 步骤4确认启动成功
启动成功后,控制台会显示:
```
╔══════════════════════════════════════════════════════════╗
║ ║
║ ✅ MTKJ PAY 支付系统启动成功! ✅ ║
║ ║
╠══════════════════════════════════════════════════════════╣
║ 应用名称: mt-pay ║
║ 运行环境: dev ║
║ 服务端口: 8082 ║
║ 后端服务: http://localhost:8082/ ║
║ API接口: http://localhost:8082/api ║
║ 状态: 🟢 服务运行中,可以接收请求 ║
╚══════════════════════════════════════════════════════════╝
```
## 使用 Maven 命令启动
```bash
# 进入 mt-pay 模块目录
cd E:\MTKJPAY\mt-pay
# 启动应用
mvn spring-boot:run
```
## 验证启动成功
1. **检查端口**
```bash
netstat -ano | findstr :8082
```
应该能看到端口 8082 在监听
2. **访问接口**
在浏览器访问http://localhost:8082/api/product/1
如果返回 JSON 响应,说明启动成功
3. **查看日志**
控制台应该显示启动成功的标识框
## 常见错误
### 错误1启动的是根目录的 Application
- **症状**:端口是 8080没有业务功能
- **解决**:确保启动的是 `mt-pay` 模块的 `MtPayApplication`
### 错误2找不到启动类
- **症状**IDE 中找不到 `MtPayApplication`
- **解决**
1. 确认 `mt-pay` 模块已正确导入
2. 刷新 Maven 项目:右键项目 → Maven → Reload Project
3. 重新构建项目Build → Rebuild Project
### 错误3端口被占用
- **症状**:启动失败,提示端口 8082 被占用
- **解决**
1. 查找占用进程:`netstat -ano | findstr :8082`
2. 结束进程或修改端口配置
## 快速检查清单
- [ ] 启动的是 `com.mtkj.mtpay.MtPayApplication`
- [ ] 不是 `com.mtkj.mtkjpay.MtkjpayApplication`
- [ ] 端口是 8082不是 8080
- [ ] 看到启动成功的标识框
- [ ] 可以访问 http://localhost:8082/api/product/1

View File

@@ -1,68 +0,0 @@
# PayPal配置检查指南
## 错误信息
```
Username must not be null
```
这个错误表示PayPal的`clientId`Client ID配置为空。
## 问题原因
Spring Boot的`@ConfigurationProperties`在绑定配置时,需要确保:
1. 配置文件中的属性名格式正确
2. 配置类能够正确加载
3. 配置文件被正确读取
## 解决方案
### 1. 检查配置文件
确保 `application-dev.yml` 中有以下配置:
```yaml
paypal:
client-id: AdGYUZpvLuHR30dybOApvM-RNB1pVKtd74SVfh-6TK52xV-1JEBddHVMCWuDdyyHri4DXd4kABBi7Icb
client-secret: ENblspyRmwsOU_PWFurlhEYUF5Da6aYKl0pjK4ehm7p3R5aSqvbpaF_YsIIs8v0ty1c9WJu15XP-Fe_1
mode: sandbox
enabled: true
```
### 2. 检查Spring Boot配置
确保主应用类或配置类启用了配置属性绑定。
### 3. 重启应用
修改配置后,**必须重启应用**才能生效。
### 4. 验证配置加载
启动应用后检查日志中是否有PayPal配置相关的错误信息。
## 配置属性映射
Spring Boot会自动将以下格式进行映射
- `client-id` (kebab-case) → `clientId` (camelCase)
- `client-secret` (kebab-case) → `clientSecret` (camelCase)
## 调试方法
如果配置仍然无法加载,可以:
1. **添加启动日志**:在`PayPalProperties`类中添加`@PostConstruct`方法打印配置值
2. **检查环境变量**:确认使用的是`dev`环境(`spring.profiles.active=dev`
3. **检查配置文件位置**:确保`application-dev.yml``src/main/resources`目录下
## 临时解决方案
如果配置仍然无法加载,可以在代码中临时硬编码(仅用于测试):
```java
// 仅用于测试,生产环境必须使用配置文件
if (clientId == null) {
clientId = "AdGYUZpvLuHR30dybOApvM-RNB1pVKtd74SVfh-6TK52xV-1JEBddHVMCWuDdyyHri4DXd4kABBi7Icb";
clientSecret = "ENblspyRmwsOU_PWFurlhEYUF5Da6aYKl0pjK4ehm7p3R5aSqvbpaF_YsIIs8v0ty1c9WJu15XP-Fe_1";
}
```

View File

@@ -1,46 +0,0 @@
# 端口配置说明
## 端口分配
- **前端Vite开发服务器**: `3000`
- **后端Spring Boot应用**: `8082`
## 访问地址
- **前端访问地址**: `http://localhost:3000`
- **后端API地址**: `http://localhost:8082`
- **Druid监控**: `http://localhost:8082/druid`
## 代理配置
前端通过 Vite 代理将 `/api/*` 请求转发到后端:
```javascript
// vite.config.js
proxy: {
'/api': {
target: 'http://127.0.0.1:8082',
changeOrigin: true
}
}
```
## 启动顺序
1. **启动后端**:运行 `MtPayApplication`,监听 `8082` 端口
2. **启动前端**:运行 `npm run dev`,监听 `3000` 端口
## 配置文件位置
### 前端
- `MTKJPAY-FRONT/vite.config.js` - Vite配置端口 3000
### 后端
- `MTKJPAY/mt-pay/src/main/resources/application.yml` - 主配置,端口 8082
## 注意事项
1. 确保两个端口都没有被其他程序占用
2. 前端启动后会自动通过代理访问后端
3. 如果修改端口,需要同步更新相关配置

View File

@@ -1,64 +0,0 @@
# 启动后端服务
## ⚠️ 重要提示
**请启动 `mt-startup` 模块的 `MtkjpayApplication`,这是项目的唯一启动类!**
- ✅ 正确:`com.mtkj.mtkjpay.MtkjpayApplication` (mt-startup 模块,唯一启动类)
- 启动类位置:`MTKJPAY/mt-startup/src/main/java/com/mtkj/mtkjpay/MtkjpayApplication.java`
## 问题诊断
如果前端出现 `connect ECONNREFUSED ::1:8082` 错误,说明后端服务没有启动。
## 启动步骤
### 方法1使用 IDE 启动(推荐)
1. **打开根目录项目 `MTKJPAY`**
2. 找到 `mt-startup/src/main/java/com/mtkj/mtkjpay/MtkjpayApplication.java` 文件
3. 右键点击文件,选择 **"Run 'MtkjpayApplication.main()'"**
4. 确认启动的是 `com.mtkj.mtkjpay.MtkjpayApplication`(来自 mt-startup 模块)
4. 等待启动完成,看到以下日志表示启动成功:
```
╔══════════════════════════════════════════════════════════╗
║ ║
║ ✅ MTKJ PAY 支付系统启动成功! ✅ ║
║ ║
╠══════════════════════════════════════════════════════════╣
║ 应用名称: mt-pay ║
║ 运行环境: dev ║
║ 服务端口: 8082 ║
║ 后端服务: http://localhost:8082/ ║
║ API接口: http://localhost:8082/api ║
║ 状态: 🟢 服务运行中,可以接收请求 ║
╚══════════════════════════════════════════════════════════╝
```
### 方法2使用 Maven 命令启动
```bash
cd E:\MTKJPAY
mvn spring-boot:run
```
### 方法3打包后启动
```bash
cd E:\MTKJPAY
mvn clean package
java -jar target/MTKJPAY-0.0.1-SNAPSHOT.jar
```
## 验证后端是否启动
在浏览器访问http://localhost:8082/api/product/1
如果返回 JSON 响应,说明后端已启动成功。
## 常见问题
1. **端口被占用**:检查 8082 端口是否被其他程序占用
2. **数据库连接失败**:检查 `application-dev.yml` 中的数据库配置
3. **依赖缺失**:运行 `mvn clean install` 安装依赖

View File

@@ -1,104 +0,0 @@
# 后端启动问题排查指南
## 问题:前端无法连接到后端 (ECONNREFUSED)
### 1. 检查后端服务是否启动
**方法1检查端口占用**
```bash
netstat -ano | findstr :8082
```
如果没有任何输出,说明后端服务没有启动。
**方法2访问后端接口**
在浏览器访问http://localhost:8082/api/product/1
如果无法访问,说明后端服务没有启动。
### 2. 启动后端服务
#### 在 IDE 中启动(推荐)
1. 打开 `MTKJPAY/mt-pay` 项目
2. 找到 `MtPayApplication.java` 文件
3. 右键点击 → Run 'MtPayApplication.main()'
4. 查看控制台输出,确认启动成功
#### 使用 Maven 命令启动
```bash
cd E:\MTKJPAY\mt-pay
mvn clean spring-boot:run
```
### 3. 常见启动失败原因
#### 问题1Spring Boot 版本错误
- **症状**Maven 依赖下载失败,找不到 Spring Boot 4.0.0
- **解决**:已修复为 Spring Boot 3.2.0
#### 问题2数据库连接失败
- **症状**:启动时报错 "Cannot create PoolableConnectionFactory"
- **解决**
1. 检查 `application-dev.yml` 中的数据库配置
2. 确认数据库服务是否运行
3. 确认网络是否可以访问数据库服务器
#### 问题3端口被占用
- **症状**:启动时报错 "Port 8082 is already in use"
- **解决**
1. 查找占用端口的进程:`netstat -ano | findstr :8082`
2. 结束进程或修改端口配置
#### 问题4依赖缺失
- **症状**:编译错误或 ClassNotFoundException
- **解决**
```bash
cd E:\MTKJPAY\mt-pay
mvn clean install
```
### 4. 验证启动成功
启动成功后,控制台应该显示:
```
========================================
应用启动成功!
========================================
应用名称: mt-pay
运行环境: dev
访问地址: http://localhost:8082/
========================================
```
### 5. 测试后端接口
启动成功后,测试接口:
- 商品详情http://localhost:8082/api/product/1
- 图片上传POST http://localhost:8082/api/product/upload/image
### 6. 检查日志
如果启动失败,查看日志文件:
- 位置:`MTKJPAY/mt-pay/logs/` 目录
- 或查看控制台输出的错误信息
## 快速诊断命令
```bash
# 1. 检查端口占用
netstat -ano | findstr :8082
# 2. 检查 Java 进程
jps -l | findstr mtpay
# 3. 测试后端接口(需要先安装 curl
curl http://localhost:8082/api/product/1
```
## 如果仍然无法启动
请提供以下信息:
1. 启动时的完整错误日志
2. IDE 控制台的错误信息
3. Maven 构建输出(如果使用 Maven 启动)

View File

@@ -1,275 +0,0 @@
# PingPong支付对接架构设计文档
## 一、整体架构分析
### 1.1 业务流程
```
用户发起支付
创建支付订单PaymentOrderService
调用PingPong APIPingPongPayService
生成签名SignatureService
返回token和收银台地址
用户跳转到收银台完成支付
PingPong回调通知CallbackService
更新订单状态
业务系统处理
```
### 1.2 技术架构
```
Controller层API接口
Service层业务逻辑
Repository层数据访问
Entity层数据模型
```
## 二、核心模块设计
### 2.1 数据库模型设计
#### PaymentOrder支付订单表
**设计思路:**
- 存储支付订单的完整信息
- 支持订单状态跟踪
- 通过merchantTransactionId保证唯一性
**关键字段:**
- `merchantTransactionId`:商户订单号(唯一索引)
- `transactionId`PingPong交易流水号回调时更新
- `status`订单状态PENDING/SUCCESS/FAILED/REVIEW/CANCELLED
- `token`收银台token
- `paymentUrl`:收银台地址
#### PaymentRecord支付记录表
**设计思路:**
- 记录所有支付相关操作
- 用于对账和问题排查
- 支持多种记录类型CHECKOUT/CALLBACK/QUERY等
**关键字段:**
- `recordType`:记录类型
- `requestData`原始请求数据JSON
- `responseData`原始响应数据JSON
### 2.2 配置管理
#### PingPongProperties
**设计思路:**
- 使用Spring Boot的@ConfigurationProperties
- 支持多环境配置dev/prod
- 配置项包括:商户号、密钥、网关地址、环境模式等
**关键配置:**
```properties
pingpong.client-id=商户号
pingpong.acc-id=店铺编号
pingpong.secret=签名密钥
pingpong.gateway=API网关地址
pingpong.mode=环境模式sandbox/test/build
```
### 2.3 签名服务SignatureService
#### 设计思路
- 实现PingPong的签名算法
- 支持MD5和SHA256
- 自动筛选参与签名的参数
- 提供签名生成和验证功能
#### 签名流程
1. 筛选加签参数根据SIGN_SCOPE
2. 按字典序排序
3. 构建签名串:{secret}key1=val1&key2=val2...
4. MD5/SHA256运算并转大写
#### 关键方法
- `generateSign()`:生成签名
- `verifySign()`:验证签名
- `filterSignParams()`:筛选签名参数
### 2.4 支付服务PingPongPayService
#### 设计思路
- 封装PingPong API调用
- 自动处理签名生成
- 验证响应签名
- 使用RestClient进行HTTP请求
#### 关键方法
- `checkout()`:创建支付订单
- `buildRequestMap()`:构建请求参数
### 2.5 订单服务PaymentOrderService
#### 设计思路
- 管理支付订单生命周期
- 调用PingPong API
- 保存订单和记录
- 提供订单查询功能
#### 关键方法
- `createPaymentOrder()`:创建支付订单
- `findByMerchantTransactionId()`:查询订单
- `updateOrderStatus()`:更新订单状态
### 2.6 回调服务CallbackService
#### 设计思路
- 处理PingPong异步回调
- 验证回调签名
- 更新订单状态
- 触发业务逻辑处理
#### 关键方法
- `handleCallback()`:处理回调
- `mapCallbackStatusToOrderStatus()`:状态映射
- `handleBusinessLogic()`:业务逻辑处理
## 三、API接口设计
### 3.1 支付接口
#### POST /api/payment/checkout
**功能:** 创建支付订单
**请求体:** CheckoutRequestDTO
**响应:** 包含token和paymentUrl
#### GET /api/payment/order/{merchantTransactionId}
**功能:** 查询订单状态
**响应:** 订单详细信息
#### GET /api/payment/checkout/page?token={token}
**功能:** 获取收银台页面
**响应:** HTML页面包含SDK初始化
### 3.2 回调接口
#### POST /api/callback/pingpong
**功能:** 接收PingPong回调通知
**请求体:** Map<String, Object>
**响应:** 处理结果
#### GET /api/callback/result
**功能:** 支付结果页面(用户重定向)
**参数:** merchantTransactionId, status, message
## 四、DTO设计
### 4.1 请求DTO
#### CheckoutRequestDTO
- 包含所有checkout接口必需参数
- 使用Jakarta Validation进行参数校验
- 嵌套RiskInfoDTO
#### RiskInfoDTO
- 包含风控相关信息
- 嵌套多个子DTOCustomerDTO、GoodsDTO等
### 4.2 响应DTO
#### CheckoutResponseDTO
- 对应PingPong API响应
- 包含token、paymentUrl等
## 五、异常处理
### 5.1 GlobalExceptionHandler
- 统一异常处理
- 参数验证异常处理
- 运行时异常处理
- 返回统一错误格式
## 六、安全考虑
### 6.1 签名验证
- 所有回调都进行签名验证
- 防止数据篡改
### 6.2 订单号唯一性
- 数据库唯一索引保证
- 业务层检查重复
### 6.3 敏感信息
- 密钥存储在配置文件中
- 生产环境使用环境变量
## 七、扩展性设计
### 7.1 多支付平台支持
- 可以扩展为支付平台抽象接口
- 每个平台实现独立服务
### 7.2 预授权功能
- 支持AUTH/CAPTURE/VOID
- 通过paymentType区分
### 7.3 订单状态机
- 可扩展为状态机模式
- 支持更复杂的状态流转
## 八、最佳实践
### 8.1 日志记录
- 关键操作记录日志
- 包含订单号、状态等信息
### 8.2 事务管理
- 订单创建和更新使用事务
- 保证数据一致性
### 8.3 错误处理
- 友好的错误提示
- 详细的错误日志
### 8.4 配置管理
- 多环境配置分离
- 敏感信息加密
## 九、测试建议
### 9.1 单元测试
- 签名服务测试
- 服务层逻辑测试
### 9.2 集成测试
- API接口测试
- 回调处理测试
### 9.3 沙箱测试
- 使用PingPong沙箱环境
- 测试各种支付场景
## 十、部署注意事项
### 10.1 环境配置
- 开发环境使用sandbox模式
- 生产环境必须使用build模式
### 10.2 数据库
- 生产环境使用连接池
- 定期备份订单数据
### 10.3 监控
- 监控API调用成功率
- 监控回调处理情况
- 监控订单状态分布

View File

@@ -1,179 +0,0 @@
# 系统架构完整性说明
## 后端架构mt-pay
### 1. 包结构
```
com.mtkj.mtpay/
├── common/ # 通用组件
│ ├── Result.java # 统一响应结果类
│ ├── ResultCode.java # 响应码枚举
│ ├── constants/ # 常量类
│ │ └── PaymentConstants.java
│ └── enums/ # 枚举类
│ ├── OrderStatus.java
│ ├── PaymentType.java
│ └── RecordType.java
├── config/ # 配置类
│ ├── DruidDataSourceConfig.java
│ ├── MyBatisPlusConfig.java
│ ├── MyMetaObjectHandler.java
│ ├── PingPongProperties.java
│ ├── RestClientConfig.java
│ └── WebConfig.java # Web配置跨域等
├── controller/ # 控制器层
│ ├── PaymentController.java
│ └── CallbackController.java
├── dto/ # 数据传输对象
│ ├── request/
│ ├── response/
│ └── risk/
├── entity/ # 实体类
│ ├── PaymentOrder.java
│ └── PaymentRecord.java
├── exception/ # 异常处理
│ ├── BusinessException.java
│ └── GlobalExceptionHandler.java
├── mapper/ # 数据访问层
│ ├── PaymentOrderMapper.java
│ └── PaymentRecordMapper.java
├── service/ # 服务层(接口)
│ ├── SignatureService.java
│ ├── PingPongPayService.java
│ ├── PaymentOrderService.java
│ └── CallbackService.java
├── service/impl/ # 服务层(实现)
│ ├── SignatureServiceImpl.java
│ ├── PingPongPayServiceImpl.java
│ ├── PaymentOrderServiceImpl.java
│ └── CallbackServiceImpl.java
└── util/ # 工具类
├── DateUtils.java
├── StringUtils.java
└── OrderIdGenerator.java
```
### 2. 核心组件说明
#### 统一响应格式
- **Result<T>**: 统一响应结果类,所有接口返回统一格式
- **ResultCode**: 响应码枚举,定义所有业务响应码
#### 异常处理
- **BusinessException**: 业务异常类
- **GlobalExceptionHandler**: 全局异常处理器,统一处理异常
#### 枚举类
- **OrderStatus**: 订单状态枚举
- **PaymentType**: 支付类型枚举
- **RecordType**: 记录类型枚举
#### 工具类
- **DateUtils**: 日期时间工具类
- **StringUtils**: 字符串工具类
- **OrderIdGenerator**: 订单号生成器
#### 常量类
- **PaymentConstants**: 支付相关常量
#### 配置类
- **WebConfig**: Web配置跨域、拦截器等
## 前端架构MTKJPAY-FRONT
### 1. 目录结构
```
MTKJPAY-FRONT/
├── src/
│ ├── api/ # API接口
│ │ ├── request.js # Axios封装
│ │ └── payment.js # 支付相关API
│ ├── components/ # 通用组件
│ │ ├── PageHeader.vue
│ │ └── Loading.vue
│ ├── config/ # 配置文件
│ │ └── index.js
│ ├── router/ # 路由配置
│ │ └── index.js
│ ├── store/ # 状态管理
│ │ └── index.js
│ ├── utils/ # 工具函数
│ │ ├── constants.js # 常量定义
│ │ ├── helpers.js # 工具函数
│ │ └── request.js # 请求工具
│ ├── views/ # 页面组件
│ │ ├── CreateOrder.vue
│ │ ├── Checkout.vue
│ │ ├── PaymentResult.vue
│ │ └── OrderQuery.vue
│ ├── App.vue # 根组件
│ └── main.js # 入口文件
├── .env.development # 开发环境配置
├── .env.production # 生产环境配置
└── vite.config.js # Vite配置
```
### 2. 核心组件说明
#### 工具类
- **constants.js**: 常量定义(订单状态、支付类型、币种等)
- **helpers.js**: 工具函数(格式化、验证、防抖节流等)
- **request.js**: 请求工具函数
#### 通用组件
- **PageHeader.vue**: 页面头部组件
- **Loading.vue**: 加载组件
#### 配置管理
- **config/index.js**: 统一配置管理
- **.env.development**: 开发环境配置
- **.env.production**: 生产环境配置
#### 状态管理
- **store/index.js**: 简单状态管理可扩展为Pinia
## 架构特点
### 1. 分层清晰
- Controller → Service → Mapper → Entity
- 接口与实现分离
- DTO与Entity分离
### 2. 统一规范
- 统一响应格式Result
- 统一异常处理
- 统一响应码
### 3. 可扩展性
- 枚举类便于扩展
- 工具类可复用
- 组件化设计
### 4. 可维护性
- 代码结构清晰
- 命名规范统一
- 注释完整
### 5. 完整性
- 包含工具类、常量、枚举
- 包含异常处理、统一响应
- 包含配置管理、状态管理
## 最佳实践
1. **统一响应格式**: 所有接口使用Result<T>返回
2. **异常处理**: 使用BusinessException抛出业务异常
3. **枚举使用**: 使用枚举替代魔法字符串
4. **工具类**: 通用功能封装为工具类
5. **配置管理**: 配置统一管理,支持多环境
## 扩展建议
1. **日志切面**: 添加AOP日志切面
2. **接口文档**: 集成Swagger/OpenAPI
3. **缓存**: 添加Redis缓存支持
4. **消息队列**: 添加消息队列支持
5. **监控**: 添加系统监控和链路追踪

View File

@@ -1,98 +0,0 @@
# Config文件夹文件说明
## 文件清单
config文件夹中共包含5个配置文件所有文件都是必需的
### 1. DruidDataSourceConfig.java
**作用**配置Druid数据源
- 配置主数据源(@Primary
- 支持从数据源配置(可选)
- 从application.yml中读取数据源配置
**是否必需**:✅ 必需
- 用于配置Druid连接池
- 支持主从数据源切换
### 2. MyBatisPlusConfig.java
**作用**MyBatis-Plus配置
- 配置Mapper扫描路径`com.mtkj.mtpay.mapper`
- 配置分页插件MySQL
**是否必需**:✅ 必需
- MyBatis-Plus核心配置
- 启用分页功能
### 3. MyMetaObjectHandler.java原MetaObjectHandler.java
**作用**MyBatis-Plus自动填充处理器
- 自动填充创建时间insert时
- 自动填充更新时间insert和update时
**是否必需**:✅ 必需
- 自动处理实体类的createTime和updateTime字段
- 替代JPA的@CreationTimestamp和@UpdateTimestamp
- 注意类名改为MyMetaObjectHandler避免与接口名冲突
### 4. PingPongProperties.java
**作用**PingPong支付配置属性类
- 读取application.yml中的pingpong配置
- 提供PingPong相关配置属性
**是否必需**:✅ 必需
- 支付功能的核心配置
- 包含商户号、密钥、网关地址等
### 5. RestClientConfig.java
**作用**RestClient配置
- 配置HTTP客户端
- 用于调用PingPong API
**是否必需**:✅ 必需
- PingPong API调用需要
- 配置JSON消息转换器
## 文件依赖关系
```
DruidDataSourceConfig
MyBatisPlusConfig (依赖数据源)
MetaObjectHandler (MyBatis-Plus自动填充)
PingPongProperties (支付配置)
RestClientConfig (HTTP客户端)
```
## 配置说明
### 数据源配置流程
1. **DruidDataSourceConfig** 创建数据源Bean
2. **MyBatisPlusConfig** 配置MyBatis-Plus使用数据源
3. **MyMetaObjectHandler** 处理自动填充逻辑
### 支付功能配置流程
1. **PingPongProperties** 读取配置属性
2. **RestClientConfig** 配置HTTP客户端
3. 支付服务使用这两个配置调用API
## 总结
**所有配置文件都是必需的,不能删除!**
- **DruidDataSourceConfig**:数据源配置(必需)
- **MyBatisPlusConfig**MyBatis-Plus配置必需
- **MyMetaObjectHandler**:自动填充处理(必需)
- **PingPongProperties**:支付配置(必需)
- **RestClientConfig**HTTP客户端必需
## 注意事项
1. **MyBatisPlusConfig** 中的 `@MapperScan` 必须指向正确的包路径
2. **MyMetaObjectHandler** 必须实现 `MetaObjectHandler` 接口
3. **DruidDataSourceConfig** 中的主数据源必须使用 `@Primary` 注解
4. 所有配置类都需要使用 `@Configuration``@Component` 注解

View File

@@ -1,182 +0,0 @@
# Druid数据源配置说明
## 配置概述
项目已配置使用Alibaba Druid数据源支持主从数据源配置。
## 依赖配置
`pom.xml` 中已添加Druid依赖
```xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>1.2.20</version>
</dependency>
```
## 配置文件
### 主配置文件application.yml
数据源配置位于 `spring.datasource.druid` 节点下:
```yaml
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
druid:
# 主库数据源
master:
url: jdbc:mysql://...
username: ...
password: ...
initial-size: 5
min-idle: 10
max-active: 200
# ... 其他连接池配置
# 从库数据源
slave:
enabled: false
url:
username:
password:
# Druid监控配置
stat-view-servlet:
enabled: true
url-pattern: /druid/*
login-username: ruoyi
login-password: 123456
```
## 配置类
### DruidDataSourceConfig
位置:`com.mtkj.mtpay.config.DruidDataSourceConfig`
功能:
- 配置主数据源(@Primary
- 支持从数据源(可选,通过 `slave.enabled` 控制)
- 使用 `@ConfigurationProperties` 自动绑定配置属性
## 连接池参数说明
| 参数 | 说明 | 默认值 |
|------|------|--------|
| initial-size | 初始连接数 | 5 |
| min-idle | 最小空闲连接数 | 10 |
| max-active | 最大活跃连接数 | 200 |
| max-wait | 获取连接最大等待时间(毫秒) | 60000 |
| connect-timeout | 连接超时时间(毫秒) | 30000 |
| socket-timeout | 网络超时时间(毫秒) | 60000 |
| time-between-eviction-runs-millis | 空闲连接检测间隔(毫秒) | 60000 |
| min-evictable-idle-time-millis | 连接最小生存时间(毫秒) | 300000 |
| max-evictable-idle-time-millis | 连接最大生存时间(毫秒) | 900000 |
| validation-query | 连接验证SQL | SELECT 1 FROM DUAL |
| test-while-idle | 空闲时验证连接 | true |
| test-on-borrow | 获取连接时验证 | false |
| test-on-return | 归还连接时验证 | false |
## Druid监控
### 访问地址
- 监控页面:`http://localhost:8080/druid/index.html`
- 登录用户名:`ruoyi`
- 登录密码:`123456`
### 监控功能
- SQL监控查看SQL执行情况
- 连接池监控:查看连接池状态
- Web应用监控查看Web请求统计
- URI监控查看URI访问统计
- Session监控查看Session统计
## 环境配置
### 开发环境application-dev.yml
使用本地数据库配置,可覆盖主配置中的数据库连接信息。
### 生产环境application-prod.yml
使用环境变量配置数据库连接:
- `DB_HOST`:数据库主机
- `DB_NAME`:数据库名称
- `DB_USERNAME`:数据库用户名
- `DB_PASSWORD`:数据库密码
## 主从数据源
### 启用从数据源
在配置文件中设置:
```yaml
spring:
datasource:
druid:
slave:
enabled: true
url: jdbc:mysql://...
username: ...
password: ...
```
### 使用从数据源
从数据源Bean名称为 `slaveDataSource`,可以通过以下方式注入:
```java
@Resource(name = "slaveDataSource")
private DataSource slaveDataSource;
```
## 注意事项
1. **主数据源**:使用 `@Primary` 注解是Spring的默认数据源
2. **连接池大小**:根据实际并发量调整 `max-active` 参数
3. **监控安全**:生产环境建议修改监控页面的用户名和密码
4. **慢SQL记录**:可通过 `log-slow-sql``slow-sql-millis` 配置慢SQL记录
5. **防火墙配置**:生产环境建议配置 `allow` 白名单限制访问
## 常见问题
### 1. 连接池耗尽
**现象**`maxWait` 超时异常
**解决**
- 增加 `max-active` 参数
- 检查是否有连接泄漏
- 优化SQL性能
### 2. 连接验证失败
**现象**`validation-query` 执行失败
**解决**
- 检查数据库连接是否正常
- 确认 `validation-query` SQL语句正确
- 检查数据库用户权限
### 3. 监控页面无法访问
**现象**404错误
**解决**
- 确认 `stat-view-servlet.enabled``true`
- 检查 `url-pattern` 配置
- 确认Spring Boot版本兼容性
## 性能优化建议
1. **连接池大小**:根据并发量设置,一般 `max-active` 为 50-200
2. **连接验证**:使用 `test-while-idle` 而不是 `test-on-borrow`,减少性能开销
3. **连接回收**:合理设置 `min-evictable-idle-time-millis``max-evictable-idle-time-millis`
4. **监控统计**:生产环境可以关闭部分监控功能以提升性能

View File

@@ -1,190 +0,0 @@
# 环境配置文件说明
## 配置文件结构
项目采用多环境配置共包含4个配置文件
1. **application.yml** - 主配置文件(通用配置)
2. **application-dev.yml** - 开发环境配置
3. **application-test.yml** - 测试环境配置
4. **application-prod.yml** - 生产环境配置
## 配置文件说明
### application.yml主配置
包含所有环境的通用配置:
- 应用名称
- Druid监控配置
- JPA配置
- PingPong支付默认配置
- 服务器配置
**注意**:主配置文件中不包含数据库连接信息,由各环境配置文件提供。
### application-dev.yml开发环境
**数据库配置**
- 使用阿里云RDS数据库已配置具体连接信息
- 使用沙箱模式的PingPong支付
**使用方式**
```bash
java -jar app.jar --spring.profiles.active=dev
```
### application-test.yml测试环境
**数据库配置**
- 使用环境变量配置数据库连接
- 环境变量:
- `test.db.url` - 数据库连接URL
- `test.db.username` - 数据库用户名
- `test.db.password` - 数据库密码
- 使用测试模式的PingPong支付
**使用方式**
```bash
# 设置环境变量
export test.db.url=jdbc:mysql://host:3306/dbname?...
export test.db.username=username
export test.db.password=password
# 启动应用
java -jar app.jar --spring.profiles.active=test
```
### application-prod.yml生产环境
**数据库配置**
- 使用环境变量配置数据库连接
- 环境变量:
- `prod.db.url` - 数据库连接URL
- `prod.db.username` - 数据库用户名
- `prod.db.password` - 数据库密码
- 使用生产模式的PingPong支付build模式
**使用方式**
```bash
# 设置环境变量
export prod.db.url=jdbc:mysql://host:3306/dbname?...
export prod.db.username=username
export prod.db.password=password
# 启动应用
java -jar app.jar --spring.profiles.active=prod
```
## 环境变量配置示例
### 测试环境环境变量
```bash
# Linux/Mac
export test.db.url="jdbc:mysql://test-host:3306/test_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai"
export test.db.username="test_user"
export test.db.password="test_password"
```
```powershell
# Windows PowerShell
$env:test.db.url="jdbc:mysql://test-host:3306/test_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai"
$env:test.db.username="test_user"
$env:test.db.password="test_password"
```
### 生产环境环境变量
```bash
# Linux/Mac
export prod.db.url="jdbc:mysql://prod-host:3306/prod_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai"
export prod.db.username="prod_user"
export prod.db.password="prod_password"
```
```powershell
# Windows PowerShell
$env:prod.db.url="jdbc:mysql://prod-host:3306/prod_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai"
$env:prod.db.username="prod_user"
$env:prod.db.password="prod_password"
```
## Docker部署环境变量
在Docker Compose或Kubernetes中配置环境变量
```yaml
# docker-compose.yml
services:
mt-pay:
image: mt-pay:latest
environment:
- SPRING_PROFILES_ACTIVE=prod
- prod.db.url=jdbc:mysql://prod-host:3306/prod_db?...
- prod.db.username=prod_user
- prod.db.password=prod_password
```
```yaml
# kubernetes deployment
apiVersion: v1
kind: ConfigMap
metadata:
name: mt-pay-config
data:
SPRING_PROFILES_ACTIVE: "prod"
prod.db.url: "jdbc:mysql://prod-host:3306/prod_db?..."
prod.db.username: "prod_user"
---
apiVersion: v1
kind: Secret
metadata:
name: mt-pay-secret
type: Opaque
stringData:
prod.db.password: "prod_password"
```
## IDE中切换环境
### IntelliJ IDEA
1. 打开 `Run/Debug Configurations`
2.`Active profiles` 中输入:`dev``test``prod`
3. 或者在 `Environment variables` 中设置环境变量
### Eclipse
1. 右键项目 -> `Run As` -> `Run Configurations`
2.`Arguments` 标签页的 `Program arguments` 中添加:
```
--spring.profiles.active=dev
```
## 配置优先级
Spring Boot配置加载顺序优先级从高到低
1. 命令行参数
2. 环境变量
3. application-{profile}.yml
4. application.yml
## 注意事项
1. **开发环境**:使用具体的数据库连接信息,方便本地开发
2. **测试/生产环境**:使用环境变量,提高安全性
3. **PingPong模式**
- dev: `sandbox`(沙箱)
- test: `test`(测试)
- prod: `build`(生产)
4. **数据库密码**:生产环境务必使用强密码,并通过环境变量或密钥管理服务管理
5. **监控页面**生产环境建议修改Druid监控页面的用户名和密码
## 验证配置
启动应用后,可以通过以下方式验证配置是否正确:
1. 查看启动日志,确认加载的配置文件
2. 访问 `/druid/index.html` 验证数据库连接
3. 检查应用日志中的数据库连接信息(注意:密码不会显示)

View File

@@ -1,218 +0,0 @@
# 日志使用指南
## 概述
项目已全面集成SLF4J日志功能使用Logback作为日志实现框架。所有关键类都已添加日志记录便于问题排查和系统监控。
## 日志配置
### 日志配置文件
- **位置**: `src/main/resources/logback-spring.xml`
- **功能**: 配置日志输出格式、文件路径、日志级别等
### 日志级别
- **DEBUG**: 详细的调试信息,通常只在开发环境使用
- **INFO**: 一般信息,记录系统运行状态和关键操作
- **WARN**: 警告信息,表示潜在问题但不影响系统运行
- **ERROR**: 错误信息,表示系统错误需要关注
### 日志输出
1. **控制台输出**: 开发环境默认输出到控制台
2. **文件输出**:
- 所有日志: `logs/mt-pay.{日期}.log`
- 错误日志: `logs/mt-pay-error.{日期}.log`
3. **日志轮转**: 按天轮转保留30天单文件最大10MB
## 已添加日志的类
### 1. 配置类
-`MyBatisPlusConfig` - MyBatis-Plus配置日志
-`RestClientConfig` - RestClient配置日志
-`WebConfig` - Web配置跨域日志
-`DruidDataSourceConfig` - 数据源配置日志(已有)
-`MyMetaObjectHandler` - 自动填充日志(已有)
### 2. 控制器类
-`ProductController` - 商品管理接口日志(已有)
-`PaymentController` - 支付接口日志(已有)
-`CallbackController` - 回调接口日志(已有)
### 3. 服务实现类
-`ProductServiceImpl` - 商品服务日志(已增强)
-`PaymentOrderServiceImpl` - 支付订单服务日志(已增强)
-`PingPongPayServiceImpl` - PingPong支付服务日志已有
-`SignatureServiceImpl` - 签名服务日志(已有)
-`CallbackServiceImpl` - 回调服务日志(已有)
-`OssServiceImpl` - OSS服务日志已有
### 4. 工具类
-`OrderIdGenerator` - 订单号生成器日志(已添加)
### 5. 异常处理
-`GlobalExceptionHandler` - 全局异常处理日志(已有)
### 6. 主应用类
-`MtPayApplication` - 应用启动日志(已增强)
## 日志使用示例
### 在类中使用日志
```java
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class YourService {
public void doSomething() {
// 记录信息日志
log.info("开始执行操作,参数: {}", param);
try {
// 业务逻辑
log.debug("调试信息: {}", debugInfo);
} catch (Exception e) {
// 记录错误日志
log.error("操作失败,参数: {}", param, e);
throw e;
}
log.info("操作完成,结果: {}", result);
}
}
```
### 日志级别使用建议
1. **DEBUG**:
- 详细的执行流程
- 中间变量值
- SQL参数值MyBatis
2. **INFO**:
- 关键业务操作开始/结束
- 重要状态变更
- 外部接口调用
3. **WARN**:
- 业务异常(如订单不存在但继续处理)
- 配置问题
- 性能警告
4. **ERROR**:
- 系统异常
- 数据库操作失败
- 外部接口调用失败
## 日志格式
### 控制台格式
```
2025-12-19 16:37:50.530 [restartedMain] INFO com.mtkj.mtpay.MtPayApplication - 应用启动成功!
```
### 文件格式
```
2025-12-19 16:37:50.530 [http-nio-8080-exec-1] INFO com.mtkj.mtpay.controller.ProductController - 创建商品请求ProductRequestDTO(...)
```
## 环境配置
### 开发环境 (dev)
- 控制台输出: ✅
- 文件输出: ✅
- 日志级别: DEBUG
- MyBatis SQL日志: ✅
### 测试环境 (test)
- 控制台输出: ❌
- 文件输出: ✅
- 日志级别: INFO
- MyBatis SQL日志: ❌
### 生产环境 (prod)
- 控制台输出: ❌
- 文件输出: ✅
- 日志级别: INFO
- MyBatis SQL日志: ❌
## 日志文件位置
- **日志目录**: `./logs/`(项目根目录)
- **所有日志**: `logs/mt-pay.{日期}.log`
- **错误日志**: `logs/mt-pay-error.{日期}.log`
## 日志查看
### 开发环境
- 直接在IDE控制台查看
- 或查看 `logs/` 目录下的日志文件
### 生产环境
- 使用 `tail -f logs/mt-pay-*.log` 实时查看
- 使用 `grep ERROR logs/mt-pay-*.log` 查找错误
- 使用日志分析工具如ELK、Graylog等
## 最佳实践
1. **使用占位符**: 使用 `{}` 占位符而不是字符串拼接
```java
// ✅ 推荐
log.info("用户ID: {}, 操作: {}", userId, action);
// ❌ 不推荐
log.info("用户ID: " + userId + ", 操作: " + action);
```
2. **异常日志**: 记录异常时包含堆栈信息
```java
// ✅ 推荐
log.error("操作失败", e);
// ❌ 不推荐
log.error("操作失败: " + e.getMessage());
```
3. **敏感信息**: 不要记录密码、密钥等敏感信息
```java
// ❌ 不要这样做
log.info("用户密码: {}", password);
```
4. **日志级别**: 根据重要性选择合适的日志级别
- 关键业务操作: INFO
- 调试信息: DEBUG
- 警告: WARN
- 错误: ERROR
## 常见问题
### Q: 如何修改日志级别?
A: 修改 `logback-spring.xml` 中对应环境的日志级别配置。
### Q: 如何查看SQL日志
A: 开发环境默认开启,生产环境已关闭。可在 `logback-spring.xml` 中修改 `com.mtkj.mtpay.mapper` 的日志级别。
### Q: 日志文件太大怎么办?
A: 已配置日志轮转按天轮转单文件最大10MB保留30天。
### Q: 如何禁用某个类的日志?
A: 在 `logback-spring.xml` 中添加:
```xml
<logger name="com.mtkj.mtpay.YourClass" level="OFF"/>
```
## 总结
项目已全面集成SLF4J日志功能所有关键操作都有日志记录。通过合理的日志级别和格式可以快速定位问题、监控系统运行状态。

View File

@@ -1,218 +1,157 @@
# MT Pay - PingPong支付对接
# MT Pay - 支付系统
## 项目简介
本项目实现了PingPong支付平台的内嵌SDK接入支持收银台模式支付
面向东南亚地区的电商支付系统支持PayPal支付、商品管理、订单管理、货币转换等功能
## 功能特性
-支付订单创建checkout接口
-MD5/SHA256签名生成和验证
-支付回调处理
-订单状态查询
-收银台页面集成
-PayPal支付集成创建订单、捕获支付、Webhook处理
-商品管理商品、SKU、商品链接
-客户订单管理(支持东南亚地址格式)
-货币转换(实时汇率,支持多币种)
-百度翻译集成商品名称、SKU名称自动翻译
- ✅ 订单状态管理
- ✅ 支付记录管理
- ✅ 预授权支持AUTH/CAPTURE/VOID
## 技术栈
- Spring Boot 4.0.0
- Spring Data JPA
- MySQL
- MyBatis-Plus
- MySQL 5.7+
- Jackson (JSON处理)
- Lombok
- RestClient
- RestTemplate
## 数据库表结构
## 快速开始
### payment_order支付订单表
- 存储支付订单基本信息
- 包含商户订单号、PingPong交易流水号、金额、状态等
### 1. 数据库配置
### payment_record支付记录表
- 存储支付操作记录
- 包含回调记录、查询记录等
执行数据库脚本(按顺序):
```sql
-- 1. 商品相关表
source database/customer_order_schema.sql;
-- 2. 货币转换字段
source database/customer_order_currency_update.sql;
-- 3. 地址字段(混合方案)
source database/customer_order_address_optimized.sql;
```
### 2. 配置文件
编辑 `src/main/resources/application-dev.yml`
- 数据库连接信息
- PayPal API凭证Client ID、Client Secret
- 百度翻译配置App ID、Security Key
- 阿里云OSS配置
### 3. 启动项目
```bash
mvn spring-boot:run
```
```bash
mvn clean package
java -jar target/mt-pay-0.0.1-SNAPSHOT.jar
```
### 4. 访问地址
- 后端API: http://localhost:8082/api
- Druid监控: http://localhost:8082/druid
- 前端地址: http://localhost:3000
## 核心API
### 商品管理
- `GET /api/product/{id}` - 获取商品详情
- `GET /api/product/link/{linkCode}` - 通过链接码获取商品
- `POST /api/product` - 创建商品
### 订单管理
- `POST /api/order` - 创建客户订单
- `GET /api/order/{orderNo}` - 获取订单详情
- `POST /api/order/calculate-currency-conversion` - 计算货币转换
### PayPal支付
- `POST /api/paypal/order` - 创建PayPal订单
- `POST /api/paypal/capture` - 捕获支付
- `POST /api/paypal/webhook` - Webhook回调
## 项目结构
```
com.mtkj.mtpay/
├── config/ # 配置类PayPal、百度翻译、数据源等
├── controller/ # REST控制器
├── dto/ # 数据传输对象
│ ├── request/ # 请求DTO
│ └── response/ # 响应DTO
├── entity/ # 实体类
├── exception/ # 异常处理
├── mapper/ # MyBatis Mapper
├── service/ # 业务服务层
│ └── impl/ # 服务实现
└── util/ # 工具类
```
## 核心服务
- **ProductService**: 商品管理服务
- **CustomerOrderService**: 客户订单服务
- **PayPalService**: PayPal支付服务
- **PayPalWebhookService**: PayPal Webhook处理服务
- **ExchangeRateService**: 汇率转换服务
- **BaiduTranslatorUtils**: 百度翻译工具
## 配置说明
`application.properties` 中配置以下参数:
```properties
# PingPong支付配置
pingpong.client-id=your-client-id # PingPong商户号
pingpong.acc-id=your-acc-id # PingPong商户店铺编号
pingpong.secret=your-secret-key # 签名密钥
pingpong.sign-type=MD5 # 签名类型MD5或SHA256
pingpong.gateway=https://sandbox-acquirer-payment.pingpongx.com # API网关地址
pingpong.mode=sandbox # 环境模式sandbox/test/build
### PayPal配置
```yaml
paypal:
client-id: your-client-id
client-secret: your-client-secret
mode: sandbox # sandbox 或 production
enabled: true
```
## API接口
### 1. 创建支付订单
**接口地址:** `POST /api/payment/checkout`
**请求示例:**
```json
{
"accId": "2018092714313010016291",
"amount": "20.00",
"currency": "USD",
"merchantTransactionId": "MTN193495030728",
"paymentType": "SALE",
"shopperResultUrl": "http://your-domain.com/api/callback/result",
"shopperCancelUrl": "http://your-domain.com/api/callback/result",
"signType": "MD5",
"riskInfo": {
"customer": {
"firstName": "James",
"lastName": "LeBron",
"email": "demo@pingpongx.com",
"phone": "15988890852",
"registerTime": "20191101122000",
"registerIp": "222.126.52.23",
"orderTime": "20191201122000",
"orderIp": "222.126.52.23"
},
"goods": [{
"name": "Macaron",
"description": "Colorful macaron",
"sku": "20191201331",
"averageUnitPrice": "20",
"number": "1",
"virtualProduct": "N"
}],
"shipping": {
"firstName": "James",
"lastName": "LeBron",
"phone": "13588185079",
"street": "1986 Broad Street",
"postcode": "35222",
"city": "Birmingham",
"state": "Alabama",
"country": "US"
},
"billing": {
"firstName": "James",
"lastName": "LeBron",
"phone": "13588185079",
"street": "1986 Broad Street",
"postcode": "35222",
"city": "Birmingham",
"state": "Alabama",
"country": "US"
}
},
"notificationUrl": "http://your-domain.com/api/callback/pingpong"
}
### 百度翻译配置
```yaml
baidu:
translator:
app-id: your-app-id
securityKey: your-security-key
transApiHost: https://fanyi-api.baidu.com/api/trans/vip/translate
```
**响应示例:**
```json
{
"code": "0000",
"message": "订单创建成功",
"data": {
"merchantTransactionId": "MTN193495030728",
"token": "vr_YVR8u7rn7C1gG97DOg5W0OxzazxNYIE56ShWjA4lJrY4wchTnb47oNmp-9ubP",
"paymentUrl": "https://sandbox-pay-checkout.pingpongx.com/index.html?token=...",
"status": "PENDING"
}
}
```
## 数据库表
### 2. 查询订单状态
- `mt_product` - 商品表
- `mt_product_sku` - SKU表
- `mt_product_link` - 商品链接表
- `customer_order` - 客户订单表
- `payment_order` - 支付订单表
- `payment_record` - 支付记录表
**接口地址:** `GET /api/payment/order/{merchantTransactionId}`
## 地址字段设计
### 3. 获取收银台页面
采用混合方案:
- **基础字段**(独立列):国家、城市、州/省、邮编、详细地址1/2
- **特殊字段**JSON存储各国特殊字段组屋号、Barangay、泰文地址等
**接口地址:** `GET /api/payment/checkout/page?token={token}`
详见:`database/customer_order_address_optimized.sql`
### 4. 支付回调接口
## 相关文档
**接口地址:** `POST /api/callback/pingpong`
### 5. 支付结果页面
**接口地址:** `GET /api/callback/result?merchantTransactionId={id}&status={status}`
## 使用流程
1. **创建支付订单**
- 调用 `/api/payment/checkout` 接口创建订单
- 获取返回的 `token`
2. **跳转到收银台**
- 方式1直接使用返回的 `paymentUrl` 跳转
- 方式2调用 `/api/payment/checkout/page?token={token}` 获取收银台页面
3. **用户完成支付**
- 用户在收银台页面完成支付
- 支付完成后跳转到 `shopperResultUrl`
4. **接收回调通知**
- PingPong会异步调用 `notificationUrl` 通知支付结果
- 系统自动更新订单状态
## 注意事项
1. **订单号唯一性**`merchantTransactionId` 必须全局唯一,不可重复
2. **金额格式**`amount` 必须精确到两位小数,如 "20.00"
3. **环境模式**:开发测试使用 `sandbox`,生产环境必须使用 `build`
4. **签名验证**:所有回调都会进行签名验证,确保数据安全
5. **风控信息**`riskInfo` 中的 `shipping``billing``goods` 不能为空,影响交易成功率
6. **REVIEW状态**:如果订单状态为 `REVIEW`,需要及时进行内部审核
## 预授权功能
### AUTH预授权
- 在创建订单时设置 `paymentType``AUTH`
### CAPTURE预授权完成
- 调用二次交易接口,设置 `paymentType``CAPTURE`
- 需要在预授权成功后的7天内完成
### VOID预授权撤销
- 调用二次交易接口,设置 `paymentType``VOID`
- 只能全额撤销
## 开发说明
### 项目结构
```
com.mtkj.mtpay/
├── config/ # 配置类
├── controller/ # 控制器
├── dto/ # 数据传输对象
├── entity/ # 实体类
├── exception/ # 异常处理
├── repository/ # 数据访问层
└── service/ # 业务服务层
```
### 核心服务
- **SignatureService**:签名生成和验证服务
- **PingPongPayService**PingPong API调用服务
- **PaymentOrderService**:支付订单业务服务
- **CallbackService**:回调处理服务
## 数据库初始化
项目启动后JPA会自动创建表结构。也可以手动执行SQL
```sql
-- 根据实体类自动生成,或参考实体类定义手动创建
```
## 测试
1. 配置沙箱环境参数
2. 调用创建订单接口
3. 使用沙箱测试卡号完成支付测试
- `SYSTEM_ARCHITECTURE.md` - 系统架构文档
- `../PAYPAL_WEBHOOK_GUIDE.md` - PayPal Webhook配置指南
- `../PAYPAL_TEST_ACCOUNT.md` - PayPal测试账号说明
## 许可证
MIT

View File

@@ -1,135 +0,0 @@
# Service层结构说明
## 目录结构
```
service/
├── SignatureService.java # 签名服务接口
├── PingPongPayService.java # PingPong支付服务接口
├── PaymentOrderService.java # 支付订单服务接口
├── CallbackService.java # 回调处理服务接口
└── impl/ # 实现类目录
├── SignatureServiceImpl.java # 签名服务实现类
├── PingPongPayServiceImpl.java # PingPong支付服务实现类
├── PaymentOrderServiceImpl.java # 支付订单服务实现类
└── CallbackServiceImpl.java # 回调处理服务实现类
```
## 设计原则
### 1. 接口与实现分离
- **service文件夹**:只存放接口文件
- **service/impl文件夹**:存放所有实现类
### 2. 命名规范
- 接口:`XxxService`
- 实现类:`XxxServiceImpl`
### 3. 依赖注入
- Controller层注入接口不直接依赖实现类
- Spring会自动根据接口找到对应的实现类通过@Service注解
## Service接口说明
### SignatureService
**功能**:签名生成和验证服务
**方法**
- `generateSign(Map<String, Object> params)` - 生成签名
- `generateSign(Map<String, Object> params, String secret, String signType)` - 生成签名(指定密钥和类型)
- `verifySign(Map<String, Object> params)` - 验证签名
### PingPongPayService
**功能**PingPong支付API调用服务
**方法**
- `checkout(CheckoutRequestDTO request)` - 创建支付订单
### PaymentOrderService
**功能**:支付订单业务服务
**方法**
- `createPaymentOrder(CheckoutRequestDTO request)` - 创建支付订单
- `findByMerchantTransactionId(String merchantTransactionId)` - 根据商户订单号查询
- `findByTransactionId(String transactionId)` - 根据PingPong交易流水号查询
- `updateOrderStatus(String merchantTransactionId, String status, String transactionId)` - 更新订单状态
### CallbackService
**功能**:回调处理服务
**方法**
- `handleCallback(Map<String, Object> callbackData)` - 处理支付回调
## 实现类说明
### SignatureServiceImpl
- 实现签名生成和验证逻辑
- 支持MD5和SHA256签名算法
- 自动筛选参与签名的参数
### PingPongPayServiceImpl
- 实现PingPong API调用
- 自动生成请求签名
- 验证响应签名
### PaymentOrderServiceImpl
- 实现支付订单业务逻辑
- 调用PingPong API创建订单
- 保存订单和支付记录
### CallbackServiceImpl
- 实现回调处理逻辑
- 验证回调签名
- 更新订单状态
- 保存回调记录
## 使用示例
### Controller中使用Service
```java
@RestController
@RequiredArgsConstructor
public class PaymentController {
// 注入接口Spring会自动找到对应的实现类
private final PaymentOrderService paymentOrderService;
@PostMapping("/checkout")
public ResponseEntity<?> checkout(@RequestBody CheckoutRequestDTO request) {
PaymentOrder order = paymentOrderService.createPaymentOrder(request);
return ResponseEntity.ok(order);
}
}
```
### Service实现类中注入其他Service
```java
@Service
@RequiredArgsConstructor
public class PaymentOrderServiceImpl implements PaymentOrderService {
// 注入其他Service接口
private final PingPongPayService pingPongPayService;
// 实现接口方法
@Override
public PaymentOrder createPaymentOrder(CheckoutRequestDTO request) {
// 实现逻辑
}
}
```
## 优势
1. **解耦**Controller只依赖接口不依赖具体实现
2. **扩展性**:可以轻松替换实现类,不影响调用方
3. **测试**可以方便地创建Mock实现进行单元测试
4. **规范**:统一的代码结构,便于维护
## 注意事项
1. 所有Service实现类必须使用`@Service`注解
2. 实现类必须实现对应的接口
3. Controller中注入的是接口类型不是实现类
4. 如果接口有多个实现类,需要使用`@Qualifier`指定

View File

@@ -1,122 +0,0 @@
# 数据库建表SQL说明
## 文件说明
- `schema.sql` - 完整的数据库建表SQL脚本
## 表结构说明
### 1. payment_order支付订单表
**表说明**:存储支付订单的完整信息
**主要字段**
- `id` - 主键ID自增
- `merchant_transaction_id` - 商户订单号(唯一索引)
- `transaction_id` - PingPong交易流水号
- `amount` - 交易金额DECIMAL(12,2)
- `currency` - 交易币种3位ISO 4217代码
- `payment_type` - 交易类型SALE/AUTH
- `status` - 订单状态PENDING/SUCCESS/FAILED/REVIEW/CANCELLED
- `token` - PingPong返回的token
- `payment_url` - 支付收银台地址
- `create_time` - 创建时间(自动填充)
- `update_time` - 更新时间(自动更新)
**索引**
- 主键:`id`
- 唯一索引:`merchant_transaction_id`
- 普通索引:`transaction_id``status``create_time`
### 2. payment_record支付记录表
**表说明**:记录所有支付相关操作(回调、查询等)
**主要字段**
- `id` - 主键ID自增
- `transaction_id` - PingPong交易流水号
- `merchant_transaction_id` - 商户订单号
- `record_type` - 记录类型CHECKOUT/CALLBACK/QUERY/REFUND/CAPTURE/VOID
- `status` - 交易状态
- `code` - 响应码
- `description` - 响应描述
- `request_data` - 原始请求数据JSONTEXT类型
- `response_data` - 原始响应数据JSONTEXT类型
- `create_time` - 创建时间(自动填充)
**索引**
- 主键:`id`
- 普通索引:`transaction_id``merchant_transaction_id``record_type``create_time`
## 执行方式
### 方式1直接执行SQL文件
```bash
mysql -u用户名 -p密码 数据库名 < schema.sql
```
### 方式2在MySQL客户端中执行
```sql
-- 1. 连接到MySQL
mysql -u用户名 -p密码
-- 2. 选择数据库
USE mtpay;
-- 3. 执行SQL文件
SOURCE /path/to/schema.sql;
```
### 方式3使用IDE工具
1. 打开数据库管理工具如Navicat、DBeaver、DataGrip等
2. 连接到数据库
3. 打开 `schema.sql` 文件
4. 执行SQL脚本
## 注意事项
1. **字符集**:使用 `utf8mb4` 字符集支持emoji和特殊字符
2. **排序规则**:使用 `utf8mb4_unicode_ci` 排序规则
3. **存储引擎**:使用 `InnoDB` 存储引擎,支持事务和外键
4. **时间字段**:使用 `DATETIME` 类型,支持自动填充和更新
5. **金额字段**:使用 `DECIMAL(12,2)` 类型,精确到分
6. **唯一约束**`merchant_transaction_id` 必须全局唯一
## 字段长度说明
- `merchant_transaction_id`64字符根据PingPong文档
- `transaction_id`64字符根据PingPong文档
- `token`255字符根据PingPong文档
- `payment_url`500字符URL可能较长
- `remark`500字符备注信息
- `description`500字符响应描述
- `request_data`/`response_data`TEXT类型JSON数据可能很长
## 索引优化建议
1. **查询优化**:根据实际查询场景添加复合索引
2. **时间范围查询**`create_time` 索引有助于时间范围查询
3. **状态查询**`status` 索引有助于按状态筛选订单
4. **关联查询**`transaction_id``merchant_transaction_id` 索引有助于关联查询
## 数据备份建议
1. 定期备份数据库
2. 重要操作前先备份
3. 使用 `mysqldump` 命令备份:
```bash
mysqldump -u用户名 -p密码 数据库名 > backup_$(date +%Y%m%d).sql
```
## 版本更新
如果后续需要修改表结构,建议:
1. 创建迁移SQL文件`migration_v1.1.sql`
2. 使用 `ALTER TABLE` 语句修改
3. 记录版本变更日志

View File

@@ -1,111 +0,0 @@
# 客户订单表创建说明
## 问题
如果遇到错误:`Table 'mtpay.customer_order' doesn't exist`
说明数据库中没有 `customer_order`需要执行SQL脚本创建。
## 解决方案
### 方法1使用MySQL客户端执行推荐
1. 连接到数据库:
```bash
mysql -h rm-j6c3u06k2afwn8hxw6o.mysql.rds.aliyuncs.com -P 3306 -u mtkj2025 -p mtpay
```
2. 执行SQL脚本
```sql
source E:/MTKJPAY/mt-pay/database/customer_order_schema.sql
```
或者直接复制SQL内容执行
```sql
CREATE TABLE `customer_order` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`order_no` VARCHAR(64) NOT NULL UNIQUE COMMENT '订单号(全局唯一)',
`product_id` BIGINT NOT NULL COMMENT '商品ID',
`product_name` VARCHAR(500) NOT NULL COMMENT '商品名称',
`sku_id` BIGINT NOT NULL COMMENT 'SKU ID',
`sku_name` VARCHAR(500) NOT NULL COMMENT 'SKU名称/描述',
`quantity` INT NOT NULL COMMENT '购买数量',
`unit_price` DECIMAL(10, 2) NOT NULL COMMENT '单价',
`total_amount` DECIMAL(10, 2) NOT NULL COMMENT '订单总金额',
`currency` VARCHAR(3) NOT NULL COMMENT '货币代码',
`status` VARCHAR(20) NOT NULL DEFAULT 'PENDING' COMMENT '订单状态PENDING-待支付PAID-已支付SHIPPED-已发货COMPLETED-已完成CANCELLED-已取消',
-- 客户信息
`customer_name` VARCHAR(100) NOT NULL COMMENT '客户姓名',
`customer_phone` VARCHAR(20) NOT NULL COMMENT '客户电话',
`customer_email` VARCHAR(100) COMMENT '客户邮箱',
-- 收货地址
`shipping_name` VARCHAR(100) NOT NULL COMMENT '收货人姓名',
`shipping_phone` VARCHAR(20) NOT NULL COMMENT '收货人电话',
`shipping_country` VARCHAR(50) NOT NULL COMMENT '收货国家',
`shipping_state` VARCHAR(50) COMMENT '收货州/省',
`shipping_city` VARCHAR(50) NOT NULL COMMENT '收货城市',
`shipping_street` VARCHAR(200) NOT NULL COMMENT '收货街道地址',
`shipping_postcode` VARCHAR(20) COMMENT '收货邮编',
-- 支付信息
`payment_order_id` BIGINT COMMENT '关联的支付订单ID',
`payment_status` VARCHAR(20) DEFAULT 'UNPAID' COMMENT '支付状态UNPAID-未支付PAID-已支付FAILED-支付失败',
-- 备注
`remark` VARCHAR(500) COMMENT '订单备注',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_order_no` (`order_no`),
KEY `idx_product_id` (`product_id`),
KEY `idx_status` (`status`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='客户订单表';
```
### 方法2使用数据库管理工具
使用Navicat、DBeaver、MySQL Workbench等工具
1. 连接到数据库 `mtpay`
2. 打开SQL编辑器
3. 复制 `customer_order_schema.sql` 文件内容
4. 执行SQL脚本
### 方法3使用命令行Windows PowerShell
```powershell
# 读取SQL文件内容
$sql = Get-Content "E:\MTKJPAY\mt-pay\database\customer_order_schema.sql" -Raw
# 执行SQL需要先安装mysql客户端
mysql -h rm-j6c3u06k2afwn8hxw6o.mysql.rds.aliyuncs.com -P 3306 -u mtkj2025 -p mtpay -e $sql
```
## 验证
执行完成后,可以验证表是否创建成功:
```sql
-- 查看表是否存在
SHOW TABLES LIKE 'customer_order';
-- 查看表结构
DESC customer_order;
-- 或者
SHOW CREATE TABLE customer_order;
```
## 注意事项
1. 确保数据库连接信息正确
2. 确保有创建表的权限
3. 如果表已存在,可以先删除再创建:
```sql
DROP TABLE IF EXISTS `customer_order`;
```
然后重新执行创建脚本

View File

@@ -1,67 +0,0 @@
# 数据库配置说明
## 数据库信息
- **数据库名称**`mtpay`
- **字符集**`utf8mb4`
- **排序规则**`utf8mb4_general_ci`
## 数据库表
### 支付相关表
1. **payment_order** - 支付订单表
2. **payment_record** - 支付记录表
### 商品相关表
1. **mt_product** - 商品表
2. **mt_product_sku** - 商品SKU表
## 执行建表SQL
### 方式1执行所有SQL文件
```bash
# 在MySQL中执行
mysql -u用户名 -p密码 mtpay < mt_product_schema.sql
```
### 方式2在MySQL客户端中执行
```sql
-- 1. 连接到MySQL
mysql -u用户名 -p密码
-- 2. 选择数据库
USE mtpay;
-- 3. 执行SQL文件
SOURCE /path/to/mt_product_schema.sql;
```
### 方式3使用IDE工具
1. 打开数据库管理工具如Navicat、DBeaver、DataGrip等
2. 连接到数据库服务器
3. 选择 `mtpay` 数据库
4. 打开 `mt_product_schema.sql` 文件
5. 执行SQL脚本
## 注意事项
1. **执行顺序**:先执行 `mt_product_schema.sql` 创建商品表
2. **外键约束**`mt_product_sku` 表有外键关联 `mt_product`删除商品时会级联删除SKU
3. **字符集**:所有表使用 `utf8mb4` 字符集支持emoji和特殊字符
4. **索引**:已创建必要的索引,提高查询性能
## 表结构概览
```
mtpay
├── payment_order (支付订单表)
├── payment_record (支付记录表)
├── mt_product (商品表)
└── mt_product_sku (商品SKU表)
```

View File

@@ -1,139 +0,0 @@
# 商品表结构说明
## 表名规范
所有表名使用 `mt_` 前缀,如:
- `mt_product` - 商品表
- `mt_product_sku` - 商品SKU表
## 表结构
### 1. mt_product商品表
**字段说明:**
| 字段名 | 类型 | 说明 | 备注 |
|--------|------|------|------|
| id | BIGINT | 商品ID | 主键,自增 |
| name | VARCHAR(255) | 商品名称 | 必填 |
| price | DECIMAL(12,2) | 商品价格 | 基础价格默认0.00 |
| main_image | VARCHAR(4000) | 主图URL | 可选 |
| status | VARCHAR(20) | 商品状态 | ACTIVE-上架INACTIVE-下架DELETED-已删除 |
| shop_id | BIGINT | 店铺ID | 必填,关联店铺 |
| create_time | DATETIME | 创建时间 | 自动填充 |
| update_time | DATETIME | 更新时间 | 自动更新 |
**索引:**
- 主键:`id`
- 普通索引:`shop_id``status``create_time`
**排序规则:**
- 使用 `utf8mb4_general_ci` 排序规则
### 2. mt_product_sku商品SKU表
**字段说明:**
| 字段名 | 类型 | 说明 | 备注 |
|--------|------|------|------|
| id | BIGINT | SKU ID | 主键,自增 |
| product_id | BIGINT | 商品ID | 外键关联mt_product |
| sku | VARCHAR(2000) | SKU编码 | 必填 |
| price | DECIMAL(12,2) | 价格 | 必填 |
| currency | VARCHAR(3) | 货币 | ISO 4217代码默认USD |
| stock | INT | 库存数量 | 默认0 |
| sales_attrs | LONGTEXT | 销售属性 | JSON格式[{"name":"颜色","value":"红色"}] |
| sku_image | VARCHAR(4000) | SKU图片URL | 可选 |
| weight | DECIMAL(10,2) | 重量 | 单位:克 |
| size | VARCHAR(200) | 大小/尺寸 | JSON格式{"length":10,"width":5,"height":3} |
| specification | VARCHAR(2000) | 规格 | 文本描述 |
| status | VARCHAR(20) | SKU状态 | ACTIVE-启用INACTIVE-禁用 |
| create_time | DATETIME | 创建时间 | 自动填充 |
| update_time | DATETIME | 更新时间 | 自动更新 |
**索引:**
- 主键:`id`
- 普通索引:`product_id``status`
- 外键:`product_id``mt_product.id`(级联删除)
## 数据示例
### 商品示例
```sql
INSERT INTO `mt_product` (`name`, `price`, `main_image`, `status`, `shop_id`)
VALUES ('Macaron 马卡龙礼盒装', 20.00, 'https://example.com/image.jpg', 'ACTIVE', 1);
```
### SKU示例
```sql
-- SKU 1粉色6枚装
INSERT INTO `mt_product_sku` (
`product_id`, `sku`, `price`, `currency`, `stock`,
`sales_attrs`, `sku_image`, `weight`, `size`, `specification`, `status`
) VALUES (
1, 'SKU001', 18.00, 'USD', 50,
'[{"name":"颜色","value":"粉色"},{"name":"规格","value":"6枚装"}]',
'https://example.com/pink-6pcs.jpg', 500.00,
'{"length":20,"width":15,"height":5}',
'6枚装马卡龙礼盒', 'ACTIVE'
);
-- SKU 2蓝色12枚装
INSERT INTO `mt_product_sku` (
`product_id`, `sku`, `price`, `currency`, `stock`,
`sales_attrs`, `sku_image`, `weight`, `size`, `specification`, `status`
) VALUES (
1, 'SKU002', 20.00, 'USD', 100,
'[{"name":"颜色","value":"蓝色"},{"name":"规格","value":"12枚装"}]',
'https://example.com/blue-12pcs.jpg', 800.00,
'{"length":25,"width":20,"height":8}',
'12枚装马卡龙礼盒', 'ACTIVE'
);
```
## JSON字段格式说明
### sales_attrs销售属性
```json
[
{"name": "颜色", "value": "粉色"},
{"name": "规格", "value": "6枚装"}
]
```
### size尺寸
```json
{
"length": 20,
"width": 15,
"height": 5,
"unit": "cm"
}
```
## 执行SQL
```bash
# 在MySQL中执行
mysql -u用户名 -p密码 数据库名 < mt_product_schema.sql
```
或在MySQL客户端中
```sql
SOURCE /path/to/mt_product_schema.sql;
```
## 注意事项
1. **字符集**:使用 `utf8mb4` 字符集支持emoji和特殊字符
2. **排序规则**:使用 `utf8mb4_unicode_ci`
3. **存储引擎**:使用 `InnoDB`,支持事务和外键
4. **外键约束**删除商品时会级联删除所有SKU
5. **SKU字段**`sku` 字段最大2000字符不再设置唯一索引可根据业务需求决定是否唯一
6. **JSON字段**`sales_attrs``size` 字段存储JSON格式数据需要应用层进行解析

View File

@@ -1,5 +1,6 @@
package com.mtkj.mtpay;
import com.mtkj.mtpay.config.BaiduTranslatorConfig;
import com.mtkj.mtpay.config.PingPongProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
@@ -9,7 +10,7 @@ import org.springframework.core.env.Environment;
@Slf4j
@SpringBootApplication
@EnableConfigurationProperties({PingPongProperties.class})
@EnableConfigurationProperties({PingPongProperties.class, BaiduTranslatorConfig.class})
public class MtPayApplication {
public static void main(String[] args) {

View File

@@ -62,6 +62,79 @@ public class CreateCustomerOrderRequestDTO implements Serializable {
@Size(max = 20, message = "收货邮编长度不能超过20")
private String shippingPostcode;
// 东南亚地址扩展字段
/**
* 详细地址1门牌号、街道、楼栋
*/
@Size(max = 200, message = "详细地址1长度不能超过200")
private String shippingAddressLine1;
/**
* 详细地址2楼层、单元号可选
*/
@Size(max = 200, message = "详细地址2长度不能超过200")
private String shippingAddressLine2;
/**
* 行政区域(如新加坡的"社区"、菲律宾的"Barangay"
*/
@Size(max = 100, message = "行政区域长度不能超过100")
private String shippingAdministrativeArea;
/**
* 组屋号(新加坡,如 Blk 123
*/
@Size(max = 50, message = "组屋号长度不能超过50")
private String shippingBlockNumber;
/**
* 单元号(新加坡,如 #01-234
*/
@Size(max = 50, message = "单元号长度不能超过50")
private String shippingUnitNumber;
/**
* Barangay菲律宾社区编号
*/
@Size(max = 100, message = "Barangay长度不能超过100")
private String shippingBarangay;
/**
* 泰文地址(泰国,支持双语)
*/
@Size(max = 500, message = "泰文地址长度不能超过500")
private String shippingAddressThai;
/**
* 省(越南)
*/
@Size(max = 100, message = "省长度不能超过100")
private String shippingProvince;
/**
* 市/郡(越南)
*/
@Size(max = 100, message = "市/郡长度不能超过100")
private String shippingDistrict;
/**
* 区/坊(越南)
*/
@Size(max = 100, message = "区/坊长度不能超过100")
private String shippingWard;
/**
* 州属(马来西亚,如 Selangor
*/
@Size(max = 100, message = "州属长度不能超过100")
private String shippingStateMalaysia;
/**
* 楼层/单元/代收点(补充信息)
*/
@Size(max = 100, message = "楼层/单元/代收点长度不能超过100")
private String shippingFloorUnit;
@Size(max = 500, message = "订单备注长度不能超过500")
private String remark;
}

View File

@@ -42,6 +42,17 @@ public class CustomerOrderResponseDTO implements Serializable {
private String shippingCity;
private String shippingStreet;
private String shippingPostcode;
// 东南亚地址扩展字段
private String shippingAddressLine1;
private String shippingAddressLine2;
/**
* 特殊地址字段Map格式从JSON解析
* 包含blockNumber, unitNumber, barangay, addressThai, province, district, ward, stateMalaysia, administrativeArea, floorUnit
*/
private java.util.Map<String, Object> shippingSpecialFields;
private Long paymentOrderId;
private String paymentStatus;
private String remark;

View File

@@ -1,18 +1,26 @@
package com.mtkj.mtpay.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
/**
* 客户订单实体类
*/
@Slf4j
@TableName(value = "customer_order")
@Data
public class CustomerOrder {
private static final ObjectMapper objectMapper = new ObjectMapper();
/**
* 主键ID
*/
@@ -175,6 +183,37 @@ public class CustomerOrder {
@TableField(value = "shipping_postcode", jdbcType = org.apache.ibatis.type.JdbcType.VARCHAR)
private String shippingPostcode;
/**
* 详细地址1门牌号、街道、楼栋
*/
@TableField(value = "shipping_address_line1", jdbcType = org.apache.ibatis.type.JdbcType.VARCHAR)
private String shippingAddressLine1;
/**
* 详细地址2楼层、单元号可选
*/
@TableField(value = "shipping_address_line2", jdbcType = org.apache.ibatis.type.JdbcType.VARCHAR)
private String shippingAddressLine2;
/**
* 特殊地址字段JSON格式存储各国特殊字段
* 示例:
* {
* "blockNumber": "Blk 123", // 新加坡:组屋号
* "unitNumber": "#01-234", // 新加坡:单元号
* "barangay": "Barangay 12", // 菲律宾:社区编号
* "addressThai": "123 ถนนสุขุมวิท", // 泰国:泰文地址
* "province": "Thành phố Hồ Chí Minh", // 越南:省
* "district": "Quận 1", // 越南:市/郡
* "ward": "Phường Bến Nghé", // 越南:区/坊
* "stateMalaysia": "Selangor", // 马来西亚:州属
* "administrativeArea": "Tambon XXX", // 泰国Tambon
* "floorUnit": "5楼单元A" // 通用:楼层/单元/代收点
* }
*/
@TableField(value = "shipping_special_fields", jdbcType = org.apache.ibatis.type.JdbcType.VARCHAR)
private String shippingSpecialFields;
/**
* 关联的支付订单ID
*/
@@ -204,5 +243,59 @@ public class CustomerOrder {
*/
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 获取特殊字段Map从JSON字符串解析
*/
public Map<String, Object> getShippingSpecialFieldsMap() {
if (shippingSpecialFields == null || shippingSpecialFields.trim().isEmpty()) {
return new HashMap<>();
}
try {
return objectMapper.readValue(shippingSpecialFields,
new TypeReference<Map<String, Object>>() {});
} catch (Exception e) {
log.warn("解析特殊字段JSON失败: {}", shippingSpecialFields, e);
return new HashMap<>();
}
}
/**
* 设置特殊字段Map转换为JSON字符串
*/
public void setShippingSpecialFieldsMap(Map<String, Object> map) {
if (map == null || map.isEmpty()) {
this.shippingSpecialFields = null;
return;
}
try {
this.shippingSpecialFields = objectMapper.writeValueAsString(map);
} catch (Exception e) {
log.error("序列化特殊字段为JSON失败", e);
this.shippingSpecialFields = "{}";
}
}
/**
* 获取特殊字段值
*/
public String getSpecialField(String key) {
Map<String, Object> map = getShippingSpecialFieldsMap();
Object value = map.get(key);
return value != null ? value.toString() : null;
}
/**
* 设置特殊字段值
*/
public void setSpecialField(String key, String value) {
Map<String, Object> map = getShippingSpecialFieldsMap();
if (value != null && !value.trim().isEmpty()) {
map.put(key, value);
} else {
map.remove(key);
}
setShippingSpecialFieldsMap(map);
}
}

View File

@@ -12,12 +12,17 @@ import com.mtkj.mtpay.mapper.CustomerOrderMapper;
import com.mtkj.mtpay.mapper.MtProductMapper;
import com.mtkj.mtpay.mapper.MtProductSkuMapper;
import com.mtkj.mtpay.service.CustomerOrderService;
import com.mtkj.mtpay.util.BaiduTranslatorUtils;
import com.mtkj.mtpay.util.OrderIdGenerator;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.HashMap;
import java.util.Map;
import java.math.BigDecimal;
@@ -32,6 +37,7 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
private final CustomerOrderMapper customerOrderMapper;
private final MtProductMapper productMapper;
private final MtProductSkuMapper productSkuMapper;
private final BaiduTranslatorUtils baiduTranslatorUtils;
@Override
@Transactional(rollbackFor = Exception.class)
@@ -90,7 +96,7 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
order.setCustomerPhone(request.getCustomerPhone());
order.setCustomerEmail(request.getCustomerEmail());
// 收货地址
// 收货地址(基础字段)
order.setShippingName(request.getShippingName());
order.setShippingPhone(request.getShippingPhone());
order.setShippingCountry(request.getShippingCountry());
@@ -98,6 +104,45 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
order.setShippingCity(request.getShippingCity());
order.setShippingStreet(request.getShippingStreet());
order.setShippingPostcode(request.getShippingPostcode());
// 收货地址(通用扩展字段)
order.setShippingAddressLine1(request.getShippingAddressLine1());
order.setShippingAddressLine2(request.getShippingAddressLine2());
// 特殊字段转换为Map并存储到JSON
Map<String, Object> specialFields = new HashMap<>();
if (StringUtils.hasText(request.getShippingBlockNumber())) {
specialFields.put("blockNumber", request.getShippingBlockNumber());
}
if (StringUtils.hasText(request.getShippingUnitNumber())) {
specialFields.put("unitNumber", request.getShippingUnitNumber());
}
if (StringUtils.hasText(request.getShippingBarangay())) {
specialFields.put("barangay", request.getShippingBarangay());
}
if (StringUtils.hasText(request.getShippingAddressThai())) {
specialFields.put("addressThai", request.getShippingAddressThai());
}
if (StringUtils.hasText(request.getShippingProvince())) {
specialFields.put("province", request.getShippingProvince());
}
if (StringUtils.hasText(request.getShippingDistrict())) {
specialFields.put("district", request.getShippingDistrict());
}
if (StringUtils.hasText(request.getShippingWard())) {
specialFields.put("ward", request.getShippingWard());
}
if (StringUtils.hasText(request.getShippingStateMalaysia())) {
specialFields.put("stateMalaysia", request.getShippingStateMalaysia());
}
if (StringUtils.hasText(request.getShippingAdministrativeArea())) {
specialFields.put("administrativeArea", request.getShippingAdministrativeArea());
}
if (StringUtils.hasText(request.getShippingFloorUnit())) {
specialFields.put("floorUnit", request.getShippingFloorUnit());
}
order.setShippingSpecialFieldsMap(specialFields);
order.setRemark(request.getRemark());
// 保存订单
@@ -113,6 +158,12 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
CustomerOrderResponseDTO response = new CustomerOrderResponseDTO();
BeanUtils.copyProperties(order, response);
// 将JSON特殊字段转换为Map
response.setShippingSpecialFields(order.getShippingSpecialFieldsMap());
// 翻译订单内容商品名称和SKU名称
translateOrderContent(response);
return response;
}
@@ -130,6 +181,13 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
CustomerOrderResponseDTO response = new CustomerOrderResponseDTO();
BeanUtils.copyProperties(order, response);
// 将JSON特殊字段转换为Map
response.setShippingSpecialFields(order.getShippingSpecialFieldsMap());
// 翻译订单内容商品名称和SKU名称
translateOrderContent(response);
return response;
}
@@ -145,6 +203,13 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
CustomerOrderResponseDTO response = new CustomerOrderResponseDTO();
BeanUtils.copyProperties(order, response);
// 将JSON特殊字段转换为Map
response.setShippingSpecialFields(order.getShippingSpecialFieldsMap());
// 翻译订单内容商品名称和SKU名称
translateOrderContent(response);
return response;
}
@@ -222,5 +287,48 @@ public class CustomerOrderServiceImpl implements CustomerOrderService {
customerOrderMapper.updateById(order);
log.info("订单状态更新成功,订单号: {}", orderNo);
}
/**
* 翻译订单内容商品名称和SKU名称
* 根据订单的货币代码推断目标语言
*/
private void translateOrderContent(CustomerOrderResponseDTO orderDTO) {
try {
// 如果没有货币信息,不进行翻译
String currency = orderDTO.getCurrency();
if (currency == null || currency.trim().isEmpty()) {
// 尝试使用支付货币
currency = orderDTO.getPaymentCurrency();
if (currency == null || currency.trim().isEmpty()) {
log.debug("订单货币代码为空,跳过翻译,订单号: {}", orderDTO.getOrderNo());
return;
}
}
String targetLanguage = baiduTranslatorUtils.getLanguageByCurrency(currency);
log.debug("根据货币代码 {} 推断目标语言: {}, 订单号: {}", currency, targetLanguage, orderDTO.getOrderNo());
// 翻译商品名称
if (orderDTO.getProductName() != null && !orderDTO.getProductName().trim().isEmpty()) {
String translatedName = baiduTranslatorUtils.getTransResult(orderDTO.getProductName(), targetLanguage);
if (translatedName != null && !translatedName.equals(orderDTO.getProductName())) {
log.debug("商品名称翻译: {} -> {}, 订单号: {}", orderDTO.getProductName(), translatedName, orderDTO.getOrderNo());
orderDTO.setProductName(translatedName);
}
}
// 翻译SKU名称
if (orderDTO.getSkuName() != null && !orderDTO.getSkuName().trim().isEmpty()) {
String translatedSku = baiduTranslatorUtils.getTransResult(orderDTO.getSkuName(), targetLanguage);
if (translatedSku != null && !translatedSku.equals(orderDTO.getSkuName())) {
log.debug("SKU名称翻译: {} -> {}, 订单号: {}", orderDTO.getSkuName(), translatedSku, orderDTO.getOrderNo());
orderDTO.setSkuName(translatedSku);
}
}
} catch (Exception e) {
log.error("翻译订单内容失败,订单号: {}", orderDTO.getOrderNo(), e);
// 翻译失败不影响订单数据返回,只记录日志
}
}
}

View File

@@ -14,6 +14,7 @@ import com.mtkj.mtpay.exception.BusinessException;
import com.mtkj.mtpay.mapper.MtProductMapper;
import com.mtkj.mtpay.mapper.MtProductSkuMapper;
import com.mtkj.mtpay.service.ProductService;
import com.mtkj.mtpay.util.BaiduTranslatorUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -45,6 +46,9 @@ public class ProductServiceImpl implements ProductService {
@Autowired
private com.mtkj.mtpay.service.ProductLinkService productLinkService;
@Autowired
private BaiduTranslatorUtils baiduTranslatorUtils;
@Value("${server.port:8082}")
private String serverPort;
@@ -203,6 +207,9 @@ public class ProductServiceImpl implements ProductService {
response.setSkus(skuDTOs);
// 翻译功能根据SKU的货币代码推断目标语言并翻译
translateProductContent(response);
log.info("获取商品详情成功商品ID: {}, 商品名称: {}, SKU数量: {}, 主图数量: {}",
id, product.getName(), skuDTOs.size(),
response.getMainImages() != null ? response.getMainImages().size() : 0);
@@ -307,11 +314,68 @@ public class ProductServiceImpl implements ProductService {
}).collect(Collectors.toList());
dto.setSkus(skuDTOs);
// 翻译功能根据SKU的货币代码推断目标语言并翻译
translateProductContent(dto);
result.add(dto);
}
log.info("获取商品列表成功,商品数量: {}", result.size());
return result;
}
/**
* 翻译商品内容商品名称和SKU名称
* 根据SKU的货币代码推断目标语言
*/
private void translateProductContent(ProductResponseDTO productDTO) {
try {
// 如果没有SKU不进行翻译
if (productDTO.getSkus() == null || productDTO.getSkus().isEmpty()) {
return;
}
// 根据第一个SKU的货币代码推断目标语言
String firstCurrency = productDTO.getSkus().get(0).getCurrency();
if (firstCurrency == null || firstCurrency.trim().isEmpty()) {
log.debug("SKU货币代码为空跳过翻译商品ID: {}", productDTO.getId());
return;
}
String targetLanguage = baiduTranslatorUtils.getLanguageByCurrency(firstCurrency);
log.debug("根据货币代码 {} 推断目标语言: {}, 商品ID: {}", firstCurrency, targetLanguage, productDTO.getId());
// 翻译商品名称
if (productDTO.getName() != null && !productDTO.getName().trim().isEmpty()) {
String translatedName = baiduTranslatorUtils.getTransResult(productDTO.getName(), targetLanguage);
if (translatedName != null && !translatedName.equals(productDTO.getName())) {
log.debug("商品名称翻译: {} -> {}, 商品ID: {}", productDTO.getName(), translatedName, productDTO.getId());
productDTO.setName(translatedName);
}
}
// 翻译每个SKU的名称sku字段
for (ProductResponseDTO.ProductSkuResponseDTO skuDTO : productDTO.getSkus()) {
// 如果SKU的货币与第一个不同使用该SKU的货币推断语言
String skuCurrency = skuDTO.getCurrency();
String skuLanguage = targetLanguage;
if (skuCurrency != null && !skuCurrency.equals(firstCurrency)) {
skuLanguage = baiduTranslatorUtils.getLanguageByCurrency(skuCurrency);
}
// 翻译SKU名称
if (skuDTO.getSku() != null && !skuDTO.getSku().trim().isEmpty()) {
String translatedSku = baiduTranslatorUtils.getTransResult(skuDTO.getSku(), skuLanguage);
if (translatedSku != null && !translatedSku.equals(skuDTO.getSku())) {
log.debug("SKU名称翻译: {} -> {}, SKU ID: {}", skuDTO.getSku(), translatedSku, skuDTO.getId());
skuDTO.setSku(translatedSku);
}
}
}
} catch (Exception e) {
log.error("翻译商品内容失败商品ID: {}", productDTO.getId(), e);
// 翻译失败不影响商品数据返回,只记录日志
}
}
}

View File

@@ -110,4 +110,17 @@ paypal:
# 是否启用PayPal支付
enabled: true
# 百度翻译配置
baidu:
translator:
app-id: 20250909002450241
securityKey: o08lOpCcarl4Pb0BGdkq
transApiHost: https://fanyi-api.baidu.com/api/trans/vip/translate
qianfan:
app-key: ALTAKUyxxqjXU9YhPNYXj8zFQ2
app-secret: 2c43ca701e1641b0a945a3a65125c143
app_id: 9ae03450-0036-4c61-93bc-659a95cea443
Authorization: Bearer bce-v3/ALTAK-9pcPkK0ZLYIDyDJNtVwji/f32d29d61a86e6e4e8c60d5297a310538b3920a7
url: https://qianfan.baidubce.com/v2/app/conversation/runs