9.3 KiB
9.3 KiB
🔥🔥🔥 支付失败 - 事务回滚问题修复
问题时间: 2026-01-23
问题描述: 支付页面显示"支付成功",但后端返回 400 错误:"Transaction rolled back because it has been marked as rollback-only"
🔍 问题分析
现象
- 前端支付页面显示"支付成功"
- 控制台显示后端返回 400 错误
- 错误信息:
Transaction rolled back because it has been marked as rollback-only
根本原因
问题出在 UserPackage 实体类的时间字段配置:
@TableField(value = "create_time", fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
冲突点:
@TableField注解指定了自动填充(fill = FieldFill.INSERT)PackageServiceImpl.purchasePackage()方法中手动设置了这些字段- 如果没有配置
MetaObjectHandler,自动填充会失败 - 导致事务回滚
调用链
前端支付
→ OrderController.payOrder()
→ OrderServiceImpl.payOrder()
→ PackageServiceImpl.purchasePackage() ← 这里出错
→ userPackageMapper.insert(userPackage) ← 事务回滚
🔧 解决方案
方案1: 移除自动填充配置(推荐)
优点: 简单直接,不需要额外配置
缺点: 需要手动设置时间字段
修改 UserPackage.java
@Data
@TableName("user_package")
public class UserPackage {
@TableId(type = IdType.AUTO)
private Long id;
private Long tenantId;
private Long userId;
private Long packageId;
private String packageName;
private String packageType;
private BigDecimal totalHours;
private BigDecimal usedHours;
private BigDecimal remainingHours;
private Integer totalCount;
private Integer usedCount;
private Integer remainingCount;
private BigDecimal price;
private Integer status;
private LocalDate startDate;
private LocalDate expireDate;
// 🔥 移除 fill 属性,只保留字段映射
@TableField(value = "create_time")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
@TableField(value = "update_time")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
}
方案2: 配置 MetaObjectHandler(完整方案)
优点: 统一管理时间字段,其他实体也能受益
缺点: 需要额外配置类
创建 MyMetaObjectHandler.java
package com.peidu.config;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("开始插入填充...");
// 自动填充 createTime
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
// 自动填充 updateTime
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("开始更新填充...");
// 自动填充 updateTime
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
修改 PackageServiceImpl.java
@Transactional
public UserPackage purchasePackage(Long userId, Long packageId) {
Package pkg = packageMapper.selectById(packageId);
if (pkg == null) {
throw new RuntimeException("套餐不存在");
}
if (pkg.getStatus() != 1) {
throw new RuntimeException("套餐已下架");
}
// 创建用户套餐
UserPackage userPackage = new UserPackage();
userPackage.setUserId(userId);
userPackage.setPackageId(packageId);
userPackage.setPackageName(pkg.getPackageName());
userPackage.setPackageType(pkg.getPackageType());
userPackage.setTotalHours(pkg.getTotalHours());
userPackage.setUsedHours(BigDecimal.ZERO);
userPackage.setRemainingHours(pkg.getTotalHours());
userPackage.setTotalCount(pkg.getServiceCount());
userPackage.setUsedCount(0);
userPackage.setRemainingCount(pkg.getServiceCount());
userPackage.setPrice(pkg.getPrice());
userPackage.setStatus(1);
userPackage.setStartDate(LocalDate.now());
userPackage.setExpireDate(LocalDate.now().plusDays(pkg.getValidDays()));
// 🔥 移除手动设置时间字段,让 MetaObjectHandler 自动填充
// userPackage.setCreateTime(LocalDateTime.now());
// userPackage.setUpdateTime(LocalDateTime.now());
userPackageMapper.insert(userPackage);
return userPackage;
}
🚀 推荐实施步骤
步骤1: 使用方案1(快速修复)
- 修改
UserPackage.java,移除fill属性 - 重新编译后端
- 测试支付功能
步骤2: 修复前端显示逻辑
问题: 前端在后端返回 400 错误时仍然显示"支付成功"
修改 peidu/uniapp/src/pages/payment/index.vue:
// 处理支付
async handlePay() {
if (this.paying) return
// ... 验证逻辑 ...
this.paying = true
uni.showLoading({ title: '支付中...' })
try {
console.log('=== 开始支付 ===')
console.log('订单ID:', this.orderId)
console.log('支付方式:', this.selectedMethod)
console.log('支付金额:', this.finalAmount)
// 构建支付参数
const paymentData = {
orderId: this.orderId,
paymentMethod: this.selectedMethod,
amount: this.finalAmount
}
// 添加优惠券
if (this.selectedCoupon) {
paymentData.couponId = this.selectedCoupon.id
}
// 添加次卡
if (this.selectedMethod === 'timecard' && this.selectedTimecard) {
paymentData.timecardId = this.selectedTimecard.id
}
// 添加套餐
if (this.selectedMethod === 'package' && this.selectedPackage) {
paymentData.packageId = this.selectedPackage.id
}
console.log('支付参数:', paymentData)
// 调用支付接口
const response = await orderApi.payOrder(paymentData)
console.log('支付响应:', response)
// 🔥 修复:检查响应状态
if (response && response.code === 200) {
// 支付成功
this.handlePaymentSuccess()
} else {
// 🔥 修复:支付失败,抛出错误
throw new Error(response.message || '支付失败')
}
} catch (error) {
console.error('支付失败:', error)
uni.hideLoading()
this.paying = false
// 🔥 修复:显示详细错误信息
uni.showModal({
title: '支付失败',
content: error.message || '支付过程中出现错误,请重试',
showCancel: false
})
}
}
关键修改:
- 检查
response.code === 200才认为支付成功 - 如果
code !== 200,抛出错误 - 在
catch块中显示错误信息
步骤3: 移除重复的 handlePay 方法
问题: payment/index.vue 中有两个 handlePay() 方法定义
修改: 删除第一个 handlePay() 方法(第 456-502 行),保留第二个完整的方法
🧪 测试步骤
1. 测试支付成功场景
1. 创建订单
2. 进入支付页面
3. 选择支付方式(微信/钱包/次卡/套餐)
4. 点击"立即支付"
5. 预期:显示"支付成功",跳转到"待服务"订单列表
2. 测试支付失败场景
1. 创建订单
2. 进入支付页面
3. 选择钱包支付(余额不足)
4. 点击"立即支付"
5. 预期:显示"钱包余额不足"错误提示
3. 测试事务回滚修复
1. 创建订单
2. 进入支付页面
3. 选择任意支付方式
4. 点击"立即支付"
5. 预期:
- 不再出现 "Transaction rolled back" 错误
- 支付成功或失败都有明确提示
- 订单状态正确更新
📝 修改文件清单
后端修改
peidu/backend/src/main/java/com/peidu/entity/UserPackage.java- 移除fill属性- (可选)
peidu/backend/src/main/java/com/peidu/config/MyMetaObjectHandler.java- 创建自动填充处理器
前端修改
peidu/uniapp/src/pages/payment/index.vue- 修复支付响应处理逻辑peidu/uniapp/src/pages/payment/index.vue- 删除重复的handlePay()方法
⚠️ 注意事项
- 编译顺序: 先修改后端,重新编译,再修改前端
- 测试覆盖: 测试所有支付方式(微信、钱包、次卡、套餐)
- 数据库检查: 确认
user_package表的create_time和update_time字段存在 - 日志监控: 观察后端日志,确认没有事务回滚错误
🎯 预期效果
修复前
- ❌ 支付页面显示"支付成功"
- ❌ 后端返回 400 错误
- ❌ 事务回滚,数据未保存
- ❌ 用户体验混乱
修复后
- ✅ 支付成功时显示"支付成功"
- ✅ 支付失败时显示具体错误信息
- ✅ 事务正常提交,数据正确保存
- ✅ 用户体验清晰明确
🚀 立即执行
下一步: 按照步骤1修复 UserPackage.java,重新编译后端
预计时间: 10分钟
风险: 低 - 只修改字段配置,不影响业务逻辑