学习到支付服务
This commit is contained in:
@@ -33,58 +33,71 @@ public class OrderController {
|
|||||||
|
|
||||||
@PostMapping("/create")
|
@PostMapping("/create")
|
||||||
public Result<Object> createOrder(@RequestBody Map<String, Object> jsonMap) {
|
public Result<Object> createOrder(@RequestBody Map<String, Object> jsonMap) {
|
||||||
|
// ========== 1. 校验businessId(原有逻辑保留) ==========
|
||||||
Object businessIdObj = jsonMap.get("businessId");
|
Object businessIdObj = jsonMap.get("businessId");
|
||||||
String businessId = null;
|
String businessId = null;
|
||||||
if (businessIdObj != null) {
|
if (businessIdObj != null) {
|
||||||
businessId = businessIdObj.toString().trim(); // 转字符串并去除首尾空格
|
businessId = businessIdObj.toString().trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 判断businessId是否存在(核心逻辑)
|
|
||||||
if (businessId == null || businessId.isEmpty()) {
|
if (businessId == null || businessId.isEmpty()) {
|
||||||
// businessId不存在/为空 → 返回参数错误
|
|
||||||
return Result.fail(400, "businessId不能为空");
|
return Result.fail(400, "businessId不能为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. 安全获取userId:先转Number,再取int值(兼容Integer/Long)
|
// ========== 2. 安全解析参数(原有逻辑保留,仅优化提示) ==========
|
||||||
|
// 2.1 解析userId
|
||||||
Number userIdNum = (Number) jsonMap.get("userId");
|
Number userIdNum = (Number) jsonMap.get("userId");
|
||||||
if (userIdNum == null) {
|
if (userIdNum == null) {
|
||||||
return Result.fail(400, "userId不能为空");
|
return Result.fail(400, "userId不能为空");
|
||||||
}
|
}
|
||||||
int userId = userIdNum.intValue();
|
int userId = userIdNum.intValue();
|
||||||
|
|
||||||
// 2. 安全获取stockid:先转Number,再取long值(兼容Integer/Long)
|
// 2.2 解析stockid
|
||||||
Number stockIdNum = (Number) jsonMap.get("stockid");
|
Number stockIdNum = (Number) jsonMap.get("stockid");
|
||||||
if (stockIdNum == null) {
|
if (stockIdNum == null) {
|
||||||
return Result.fail(400, "stockid不能为空");
|
return Result.fail(400, "stockid不能为空");
|
||||||
}
|
}
|
||||||
Long stockId = stockIdNum.longValue();
|
Long stockId = stockIdNum.longValue();
|
||||||
|
|
||||||
// 3. 安全获取deductnum:先转Number,再取int值(兼容Integer/Long)
|
// 2.3 解析deductnum
|
||||||
Number deductNumNum = (Number) jsonMap.get("deductnum");
|
Number deductNumNum = (Number) jsonMap.get("deductnum");
|
||||||
if (deductNumNum == null) {
|
if (deductNumNum == null) {
|
||||||
return Result.fail(400, "deductnum不能为空");
|
return Result.fail(400, "deductnum不能为空");
|
||||||
}
|
}
|
||||||
int deductNum = deductNumNum.intValue();
|
int deductNum = deductNumNum.intValue();
|
||||||
|
|
||||||
// 4. 组装DTO
|
// ========== 3. 组装DTO ==========
|
||||||
StockDeductDTO stockDeductDTO = new StockDeductDTO();
|
StockDeductDTO stockDeductDTO = new StockDeductDTO();
|
||||||
stockDeductDTO.setId(stockId);
|
stockDeductDTO.setId(stockId);
|
||||||
stockDeductDTO.setDeductNum(deductNum);
|
stockDeductDTO.setDeductNum(deductNum);
|
||||||
|
|
||||||
// 5. 调用服务
|
// ========== 4. 调用Service(核心改动:接收Map结果,获取订单号) ==========
|
||||||
int serviceResult = orderService.createOrder(stockDeductDTO, userId,businessId);
|
// 注意:原Service返回int,需改为返回Map(包含success、msg、orderNo)
|
||||||
if (serviceResult != 0) {
|
Map<String, Object> serviceResult = orderService.createOrder(stockDeductDTO, userId, businessId);
|
||||||
// 库存扣减/订单创建失败
|
|
||||||
return Result.fail("库存扣减失败,订单创建失败");
|
|
||||||
}
|
|
||||||
// 成功:可以返回额外数据(比如订单ID)
|
|
||||||
return Result.success("库存扣减成功,订单创建成功");
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
// 4.1 解析Service返回结果
|
||||||
// 捕获所有异常,返回友好提示
|
boolean isSuccess = (boolean) serviceResult.get("success");
|
||||||
|
if (!isSuccess) {
|
||||||
|
return Result.fail(500, serviceResult.get("msg").toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4.2 成功:返回订单号(供前端轮询支付链接)
|
||||||
|
String orderNo = serviceResult.get("orderNo").toString();
|
||||||
|
return Result.success(
|
||||||
|
Map.of(
|
||||||
|
"msg", "订单创建成功,已触发支付流程",
|
||||||
|
"orderNo", orderNo, // 核心:返回订单号
|
||||||
|
"tips", "请轮询 /order/detail?orderNo=" + orderNo + " 获取支付链接"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
// 补充类型转换异常处理(前端传参类型错误)
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return Result.fail("订单创建异常:" + e.getMessage());
|
return Result.fail(400, "参数类型错误:userId/stockid/deductnum需为数字");
|
||||||
}
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return Result.fail(500, "订单创建异常:" + e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package cn.mayiming.Service;
|
|||||||
|
|
||||||
import cn.mayiming.Mapper.OrderMapper;
|
import cn.mayiming.Mapper.OrderMapper;
|
||||||
import cn.mayiming.entity.Order;
|
import cn.mayiming.entity.Order;
|
||||||
|
import cn.mayiming.entity.PayTriggerMsgDTO;
|
||||||
import cn.mayiming.entity.StockDeductDTO;
|
import cn.mayiming.entity.StockDeductDTO;
|
||||||
import cn.mayiming.entity.User;
|
import cn.mayiming.entity.User;
|
||||||
import cn.mayiming.feign.StockFeignClient;
|
import cn.mayiming.feign.StockFeignClient;
|
||||||
@@ -13,7 +14,9 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class OrderService {
|
public class OrderService {
|
||||||
@@ -29,6 +32,8 @@ public class OrderService {
|
|||||||
@Resource
|
@Resource
|
||||||
private RocketMQTemplate rocketMQTemplate;
|
private RocketMQTemplate rocketMQTemplate;
|
||||||
|
|
||||||
|
private static final String PAY_TRIGGER_TOPIC = "pay_topic";
|
||||||
|
|
||||||
public User SearchUserbyname (User user) {
|
public User SearchUserbyname (User user) {
|
||||||
return userFeignClient.selectByUsername(user);
|
return userFeignClient.selectByUsername(user);
|
||||||
}
|
}
|
||||||
@@ -59,54 +64,75 @@ public class OrderService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public int createOrder(StockDeductDTO stockDeductDTO,int userId,String businessId) {
|
public Map<String, Object> createOrder(StockDeductDTO stockDeductDTO, int userId, String businessId) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
|
||||||
|
// ========== 原有核心逻辑(幂等+用户+扣库存+插订单)==========
|
||||||
Integer count = orderMapper.countByBusinessId(businessId);
|
Integer count = orderMapper.countByBusinessId(businessId);
|
||||||
if (count != null && count > 0) {
|
if (count != null && count > 0) {
|
||||||
return 1; // 已存在,直接返回失败
|
result.put("success", false);
|
||||||
|
result.put("msg", "重复请求,下单失败");
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
// 1.2 校验用户是否存在(远程只读,无事务管控)
|
|
||||||
if (userFeignClient.GetUserByid(userId) == null) {
|
if (userFeignClient.GetUserByid(userId) == null) {
|
||||||
return 1; // 用户不存在,直接返回失败
|
result.put("success", false);
|
||||||
|
result.put("msg", "用户不存在,下单失败");
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Order order = null; // 声明订单对象,方便后续发送消息
|
||||||
try {
|
try {
|
||||||
// 步骤1:调用库存服务扣减库存(远程操作,需库存服务自身保证事务)
|
|
||||||
int stockResult = stockFeignClient.deductstock(stockDeductDTO);
|
int stockResult = stockFeignClient.deductstock(stockDeductDTO);
|
||||||
if (stockResult == 0) { // 假设返回0是失败,非0是成功(按你原有逻辑)
|
if (stockResult == 0) {
|
||||||
throw new RuntimeException("库存扣减失败,返回码:" + stockResult);
|
throw new RuntimeException("库存不足,扣减失败");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 步骤2:构建订单对象(适配t_order表)
|
// 构建并插入订单
|
||||||
Order order = new Order();
|
order = new Order();
|
||||||
order.setOrderNo(orderMapper.generateOrderNo()); // 生成唯一订单号
|
order.setOrderNo(orderMapper.generateOrderNo());
|
||||||
order.setUserId(Long.valueOf(userId)); // 转换为Long适配表结构
|
order.setUserId(Long.valueOf(userId));
|
||||||
order.setGoodsId(stockDeductDTO.getId()); // 商品ID(从库存DTO获取)
|
order.setGoodsId(stockDeductDTO.getId());
|
||||||
order.setOrderAmount(new BigDecimal("99.99")); // 订单金额(实际需从商品服务获取)
|
order.setOrderAmount(new BigDecimal("99.99"));
|
||||||
order.setOrderStatus(0); // 0-待支付
|
order.setOrderStatus(0); // 0-待支付
|
||||||
|
|
||||||
// 步骤3:插入订单记录(核心!事务管控)
|
|
||||||
Integer orderInsertResult = orderMapper.insertOrder(order);
|
Integer orderInsertResult = orderMapper.insertOrder(order);
|
||||||
if (orderInsertResult == null || orderInsertResult == 0) {
|
if (orderInsertResult == null || orderInsertResult == 0) {
|
||||||
throw new RuntimeException("订单插入失败");
|
throw new RuntimeException("订单插入失败");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 步骤4:插入幂等记录(占坑,事务管控)
|
// 插入幂等记录
|
||||||
Integer idempotentInsertResult = orderMapper.insertIdempotentRecord(businessId, 1); // 直接标记为成功
|
Integer idempotentInsertResult = orderMapper.insertIdempotentRecord(businessId, 1);
|
||||||
if (idempotentInsertResult == null || idempotentInsertResult == 0) {
|
if (idempotentInsertResult == null || idempotentInsertResult == 0) {
|
||||||
throw new RuntimeException("幂等记录插入失败");
|
throw new RuntimeException("幂等记录插入失败");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 所有操作成功,返回0
|
// ========== 核心新增:发送RocketMQ支付触发消息 ==========
|
||||||
return 0;
|
PayTriggerMsgDTO payMsg = new PayTriggerMsgDTO();
|
||||||
|
payMsg.setOrderNo(order.getOrderNo());
|
||||||
|
payMsg.setUserId(order.getUserId());
|
||||||
|
payMsg.setPayAmount(order.getOrderAmount());
|
||||||
|
payMsg.setBusinessId(businessId);
|
||||||
|
payMsg.setGoodsId(order.getGoodsId());
|
||||||
|
|
||||||
|
// 发送异步消息(本地模拟用同步发送,确保消息投递成功)
|
||||||
|
try {
|
||||||
|
rocketMQTemplate.convertAndSend(PAY_TRIGGER_TOPIC, payMsg);
|
||||||
|
System.out.println("订单" + order.getOrderNo() + "支付触发消息已发送到RocketMQ");
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("发送支付触发消息失败", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回结果(包含订单号,前端后续用订单号调用模拟支付接口)
|
||||||
|
result.put("success", true);
|
||||||
|
result.put("msg", "订单创建成功,已触发支付流程");
|
||||||
|
result.put("orderNo", order.getOrderNo());
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// 任何异常触发事务回滚:订单插入、幂等插入都会回滚
|
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
// 可选:幂等记录标记为失败(方便排查)
|
|
||||||
orderMapper.updateIdempotentStatus(businessId, 2);
|
orderMapper.updateIdempotentStatus(businessId, 2);
|
||||||
return 1;
|
result.put("success", false);
|
||||||
|
result.put("msg", "下单失败:" + e.getMessage());
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package cn.mayiming.entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class PayTriggerMsgDTO {
|
||||||
|
private String orderNo; // 订单号(核心关联)
|
||||||
|
private Long userId; // 用户ID
|
||||||
|
private BigDecimal payAmount; // 支付金额
|
||||||
|
private String businessId; // 幂等ID(透传)
|
||||||
|
private Long goodsId; // 商品ID(用于关单恢复库存)
|
||||||
|
}
|
||||||
@@ -1,18 +1,35 @@
|
|||||||
package cn.mayiming.Consumer;
|
package cn.mayiming.Consumer;
|
||||||
|
|
||||||
|
import cn.mayiming.Entity.PayTriggerMsgDTO;
|
||||||
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||||
import org.apache.rocketmq.spring.core.RocketMQListener;
|
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@RocketMQMessageListener(
|
@RocketMQMessageListener(
|
||||||
topic = "pay_topic",
|
topic = "pay_topic",
|
||||||
consumerGroup = "pay-service-consumer",
|
consumerGroup = "pay-service-consumer",
|
||||||
selectorExpression = "pay_success"
|
selectorExpression = "*"
|
||||||
)
|
)
|
||||||
public class payConsumer implements RocketMQListener<String> {
|
public class payConsumer implements RocketMQListener<PayTriggerMsgDTO> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMessage(String s) {
|
public void onMessage(PayTriggerMsgDTO msg) {
|
||||||
System.out.println("接收到消息"+s);
|
// 消费者接收到消息,模拟支付流程(本地仅生成支付链接,记录到日志/订单)
|
||||||
|
try {
|
||||||
|
System.out.println("========== 接收到支付触发消息 ==========");
|
||||||
|
System.out.println("订单号:" + msg.getOrderNo());
|
||||||
|
System.out.println("用户ID:" + msg.getUserId());
|
||||||
|
System.out.println("支付金额:" + msg.getPayAmount());
|
||||||
|
System.out.println("模拟支付链接:http://localhost:8080/order/mock/pay?orderNo=" + msg.getOrderNo());
|
||||||
|
System.out.println("=======================================");
|
||||||
|
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
// 本地模拟:消息消费失败可记录日志,生产环境需配置重试
|
||||||
|
System.err.println("消费支付触发消息失败,订单号:" + msg.getOrderNo() + ",原因:" + e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package cn.mayiming.Entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class PayTriggerMsgDTO {
|
||||||
|
private String orderNo; // 订单号(核心关联)
|
||||||
|
private Long userId; // 用户ID
|
||||||
|
private BigDecimal payAmount; // 支付金额
|
||||||
|
private String businessId; // 幂等ID(透传)
|
||||||
|
private Long goodsId; // 商品ID(用于关单恢复库存)
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package cn.mayiming.Mapper;
|
||||||
|
|
||||||
|
public interface payMapper {
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user