乐尚代驾十订单支付seata、rabbitmq异步消息、redisson延迟队列
账单信息
- 司机结束代驾之后,生成账单(包含账单信息和分账信息)
- 司机发送账单给乘客
- 乘客获取账单之后,进行支付

获取账单信息
order_bill表记录的账单信息,我们直接获取即可
@Operation(summary = "根据订单id获取实际账单信息")
@GetMapping("/getOrderBillInfo/{orderId}")
public Result<OrderBillVo> getOrderBillInfo(@PathVariable Long orderId) {return Result.ok(orderInfoService.getOrderBillInfo(orderId));
}@Override
public OrderBillVo getOrderBillInfo(Long orderId) {LambdaQueryWrapper<OrderBill> wrapper = new LambdaQueryWrapper<>();wrapper.eq(OrderBill::getOrderId,orderId);OrderBill orderBill = orderBillMapper.selectOne(wrapper);OrderBillVo orderBillVo = new OrderBillVo();BeanUtils.copyProperties(orderBill,orderBillVo);return orderBillVo;
}/*** 根据订单id获取实际账单信息* @param orderId* @return*/
@GetMapping("/order/info/getOrderBillInfo/{orderId}")
Result<OrderBillVo> getOrderBillInfo(@PathVariable("orderId") Long orderId);
获取分账信息
@Operation(summary = "根据订单id获取实际分账信息")
@GetMapping("/getOrderProfitsharing/{orderId}")
public Result<OrderProfitsharingVo> getOrderProfitsharing(@PathVariable Long orderId) {return Result.ok(orderInfoService.getOrderProfitsharing(orderId));
}@Override
public OrderProfitsharingVo getOrderProfitsharing(Long orderId) {LambdaQueryWrapper<OrderProfitsharing> wrapper = new LambdaQueryWrapper<>();wrapper.eq(OrderProfitsharing::getOrderId,orderId);OrderProfitsharing orderProfitsharing = orderProfitsharingMapper.selectOne(wrapper);OrderProfitsharingVo orderProfitsharingVo = new OrderProfitsharingVo();BeanUtils.copyProperties(orderProfitsharing,orderProfitsharingVo);return orderProfitsharingVo;
}/*** 根据订单id获取实际分账信息* @param orderId* @return*/
@GetMapping("/order/info/getOrderProfitsharing/{orderId}")
Result<OrderProfitsharingVo> getOrderProfitsharing(@PathVariable("orderId") Long orderId);
司机端获取账单信息
@Operation(summary = "获取订单账单详细信息")
@GuiguLogin
@GetMapping("/getOrderInfo/{orderId}")
public Result<OrderInfoVo> getOrderInfo(@PathVariable Long orderId) {Long driverId = AuthContextHolder.getUserId();return Result.ok(orderService.getOrderInfo(orderId, driverId));
}@Override
public OrderInfoVo getOrderInfo(Long orderId, Long driverId) {OrderInfo orderInfo = orderInfoFeignClient.getOrderInfo(orderId).getData();if(orderInfo.getDriverId() != driverId) {throw new GuiguException(ResultCodeEnum.ILLEGAL_REQUEST);}//获取账单和分账数据,封装到vo里面OrderBillVo orderBillVo = null;OrderProfitsharingVo orderProfitsharingVo = null;//判断,是否结束代驾if(orderInfo.getStatus() >= OrderStatus.END_SERVICE.getStatus()) {//账单信息orderBillVo = orderInfoFeignClient.getOrderBillInfo(orderId).getData();//分账信息orderProfitsharingVo = orderInfoFeignClient.getOrderProfitsharing(orderId).getData();}OrderInfoVo orderInfoVo = new OrderInfoVo();orderInfoVo.setOrderId(orderId);BeanUtils.copyProperties(orderInfo,orderInfoVo);orderInfoVo.setOrderBillVo(orderBillVo);orderInfoVo.setOrderProfitsharingVo(orderProfitsharingVo);return orderInfoVo;
}
司机发送账单
- 发送账单就是更新订单状态,未支付
@Operation(summary = "发送账单信息")
@GetMapping("/sendOrderBillInfo/{orderId}/{driverId}")
Result<Boolean> sendOrderBillInfo(@PathVariable Long orderId, @PathVariable Long driverId) {return Result.ok(orderInfoService.sendOrderBillInfo(orderId, driverId));
}@Override
public Boolean sendOrderBillInfo(Long orderId, Long driverId) {//更新订单信息LambdaQueryWrapper<OrderInfo> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(OrderInfo::getId, orderId);queryWrapper.eq(OrderInfo::getDriverId, driverId);//更新字段OrderInfo updateOrderInfo = new OrderInfo();updateOrderInfo.setStatus(OrderStatus.UNPAID.getStatus());//只能更新自己的订单int row = orderInfoMapper.update(updateOrderInfo, queryWrapper);if(row == 1) {return true;} else {throw new GuiguException(ResultCodeEnum.UPDATE_ERROR);}
}/*** 司机发送账单信息* @param orderId* @param driverId* @return*/
@GetMapping("/order/info/sendOrderBillInfo/{orderId}/{driverId}")
Result<Boolean> sendOrderBillInfo(@PathVariable("orderId") Long orderId, @PathVariable("driverId") Long driverId);@Operation(summary = "司机发送账单信息")
@GuiguLogin
@GetMapping("/sendOrderBillInfo/{orderId}")
public Result<Boolean> sendOrderBillInfo(@PathVariable Long orderId) {Long driverId = AuthContextHolder.getUserId();return Result.ok(orderService.sendOrderBillInfo(orderId, driverId));
}@Override
public Boolean sendOrderBillInfo(Long orderId, Long driverId) {return orderInfoFeignClient.sendOrderBillInfo(orderId, driverId).getData();
}
乘客获取账单
@Override
public OrderInfoVo getOrderInfo(Long orderId, Long customerId) {OrderInfo orderInfo = orderInfoFeignClient.getOrderInfo(orderId).getData();//判断if(orderInfo.getCustomerId() != customerId) {throw new GuiguException(ResultCodeEnum.ILLEGAL_REQUEST);}//获取司机信息DriverInfoVo driverInfoVo = null;Long driverId = orderInfo.getDriverId();if(driverId != null) {driverInfoVo = driverInfoFeignClient.getDriverInfo(driverId).getData();}//获取账单信息OrderBillVo orderBillVo = null;if(orderInfo.getStatus() >= OrderStatus.UNPAID.getStatus()) {orderBillVo = orderInfoFeignClient.getOrderBillInfo(orderId).getData();}OrderInfoVo orderInfoVo = new OrderInfoVo();orderInfoVo.setOrderId(orderId);BeanUtils.copyProperties(orderInfo,orderInfoVo);orderInfoVo.setOrderBillVo(orderBillVo);orderInfoVo.setDriverInfoVo(driverInfoVo);return orderInfoVo;
}
微信支付
获取乘客openid
@Operation(summary = "获取客户OpenId")
@GetMapping("/getCustomerOpenId/{customerId}")
public Result<String> getCustomerOpenId(@PathVariable Long customerId) {return Result.ok(customerInfoService.getCustomerOpenId(customerId));
}@Override
public String getCustomerOpenId(Long customerId) {LambdaQueryWrapper<CustomerInfo> wrapper = new LambdaQueryWrapper<>();wrapper.eq(CustomerInfo::getId,customerId);CustomerInfo customerInfo = customerInfoMapper.selectOne(wrapper);return customerInfo.getWxOpenId();
}/*** 获取客户OpenId* @param customerId* @return*/
@GetMapping("/customer/info/getCustomerOpenId/{customerId}")
Result<String> getCustomerOpenId(@PathVariable("customerId") Long customerId);
获取司机openid
- 与获取乘客openid基本一致
获取订单支付信息
@Operation(summary = "获取订单支付信息")
@GetMapping("/getOrderPayVo/{orderNo}/{customerId}")
public Result<OrderPayVo> getOrderPayVo(@PathVariable String orderNo, @PathVariable Long customerId) {return Result.ok(orderInfoService.getOrderPayVo(orderNo, customerId));
}@Override
public OrderPayVo getOrderPayVo(String orderNo, Long customerId) {OrderPayVo orderPayVo = orderInfoMapper.selectOrderPayVo(orderNo,customerId);if(orderPayVo != null) {String content = orderPayVo.getStartLocation() + " 到 "+orderPayVo.getEndLocation();orderPayVo.setContent(content);}return orderPayVo;
}<select id="selectOrderPayVo" resultType="com.atguigu.daijia.model.vo.order.OrderPayVo">selectinfo.id as order_id,info.customer_id,info.driver_id,info.order_no,info.start_location,info.end_location,info.status,bill.pay_amount,bill.coupon_amountfrom order_info info inner join order_bill bill on info.id = bill.order_idwhere info.customer_id = #{customerId}and info.order_no = #{orderNo}
</select>/*** 获取订单支付信息* @param orderNo* @param customerId* @return*/
@GetMapping("/order/info/getOrderPayVo/{orderNo}/{customerId}")
Result<OrderPayVo> getOrderPayVo(@PathVariable("orderNo") String orderNo, @PathVariable("customerId") Long customerId);
申请并绑定微信支付
微信支付商户平台:https://pay.weixin.qq.com/index.php/core/home/login
对于商家来说,想要开通微信支付,必须要去“微信支付商户平台”注册,然后把需要的资料提交上去,经过审核通过,你就开通了微信支付功能。
企业申请资料:
1、营业执照:彩色扫描件或数码照片
2、组织机构代码证:彩色扫描件或数码照片,若已三证合一,则无需提供
3、对公银行账户:包含开户行省市信息,开户账号
4、法人身份证:彩色扫描件或数码照片
如果想要在网站或者小程序上面使用微信支付,还要在微信公众平台上面关联你自己的微信商户账号。前提是你的微信开发者账号必须是企业身份,个人身份的开发者账号是无法调用微信支付API的。
支付密钥和数字证书
因为调用微信支付平台的API接口,必须要用到支付密钥和数字证书,这些参数在微信支付商户平台都可以获取。
<dependencies><dependency><groupId>com.github.wechatpay-apiv3</groupId><artifactId>wechatpay-java</artifactId></dependency>
</dependencies>wx:v3pay:#小程序微信公众平台appIdappid: wxcc651fcbab275e33#商户号merchantId: 163*******#商户API私钥路径privateKeyPath: /root/daijia/apiclient_key.pem#商户证书序列号merchantSerialNumber: 4AE80**********#商户APIV3密钥apiV3key: 84***************#异步回调地址notifyUrl: http://139.198.127.41:8600/payment/wxPay/notifypackage com.atguigu.daijia.payment.config;import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
@ConfigurationProperties(prefix="wx.v3pay") //读取节点
@Data
public class WxPayV3Properties {private String appid;/** 商户号 */public String merchantId;/** 商户API私钥路径 */public String privateKeyPath;/** 商户证书序列号 */public String merchantSerialNumber;/** 商户APIV3密钥 */public String apiV3key;/** 回调地址 */private String notifyUrl;@Beanpublic RSAAutoCertificateConfig getConfig(){return new RSAAutoCertificateConfig.Builder().merchantId(this.getMerchantId()).privateKeyFromPath(this.getPrivateKeyPath()).merchantSerialNumber(this.getMerchantSerialNumber()).apiV3Key(this.getApiV3key()).build();}
}
微信支付接口
@Tag(name = "微信支付接口")
@RestController
@RequestMapping("payment/wxPay")
@Slf4j
public class WxPayController {@Operation(summary = "创建微信支付")@PostMapping("/createJsapi")public Result<WxPrepayVo> createWxPayment(@RequestBody PaymentInfoForm paymentInfoForm) {return Result.ok(wxPayService.createWxPayment(paymentInfoForm));}
}@Override
public WxPrepayVo createWxPayment(PaymentInfoForm paymentInfoForm) {try {//1 添加支付记录到支付表里面//判断:如果表存在订单支付记录,不需要添加LambdaQueryWrapper<PaymentInfo> wrapper = new LambdaQueryWrapper<>();wrapper.eq(PaymentInfo::getOrderNo,paymentInfoForm.getOrderNo());PaymentInfo paymentInfo = paymentInfoMapper.selectOne(wrapper);if(paymentInfo == null) {paymentInfo = new PaymentInfo();BeanUtils.copyProperties(paymentInfoForm,paymentInfo);paymentInfo.setPaymentStatus(0);paymentInfoMapper.insert(paymentInfo);}//2 创建微信支付使用对象JsapiServiceExtension service =new JsapiServiceExtension.Builder().config(rsaAutoCertificateConfig).build();//3 创建request对象,封装微信支付需要参数PrepayRequest request = new PrepayRequest();Amount amount = new Amount();amount.setTotal(paymentInfoForm.getAmount().multiply(new BigDecimal(100)).intValue());request.setAmount(amount);request.setAppid(wxPayV3Properties.getAppid());request.setMchid(wxPayV3Properties.getMerchantId());//string[1,127]String description = paymentInfo.getContent();if(description.length() > 127) {description = description.substring(0, 127);}request.setDescription(description);request.setNotifyUrl(wxPayV3Properties.getNotifyUrl());request.setOutTradeNo(paymentInfo.getOrderNo());//获取用户信息Payer payer = new Payer();payer.setOpenid(paymentInfoForm.getCustomerOpenId());request.setPayer(payer);//是否指定分账,不指定不能分账SettleInfo settleInfo = new SettleInfo();settleInfo.setProfitSharing(true);request.setSettleInfo(settleInfo);//4 调用微信支付使用对象里面方法实现微信支付调用PrepayWithRequestPaymentResponse response = service.prepayWithRequestPayment(request);//5 根据返回结果,封装到WxPrepayVo里面WxPrepayVo wxPrepayVo = new WxPrepayVo();BeanUtils.copyProperties(response,wxPrepayVo);wxPrepayVo.setTimeStamp(response.getTimeStamp());return wxPrepayVo;}catch (Exception e) {throw new GuiguException(ResultCodeEnum.DATA_ERROR);}
}@FeignClient(value = "service-payment")
public interface WxPayFeignClient {/*** 创建微信支付* @param paymentInfoForm* @return*/@PostMapping("/payment/wxPay/createWxPayment")Result<WxPrepayVo> createWxPayment(@RequestBody PaymentInfoForm paymentInfoForm);
}@Operation(summary = "创建微信支付")
@GuiguLogin
@PostMapping("/createWxPayment")
public Result<WxPrepayVo> createWxPayment(@RequestBody CreateWxPaymentForm createWxPaymentForm) {Long customerId = AuthContextHolder.getUserId();createWxPaymentForm.setCustomerId(customerId);return Result.ok(orderService.createWxPayment(createWxPaymentForm));
}@Autowired
private WxPayFeignClient wxPayFeignClient;@Override
public WxPrepayVo createWxPayment(CreateWxPaymentForm createWxPaymentForm) {//获取订单支付信息OrderPayVo orderPayVo = orderInfoFeignClient.getOrderPayVo(createWxPaymentForm.getOrderNo(),createWxPaymentForm.getCustomerId()).getData();//判断if(orderPayVo.getStatus() != OrderStatus.UNPAID.getStatus()) {throw new GuiguException(ResultCodeEnum.ILLEGAL_REQUEST);}//获取乘客和司机openidString customerOpenId = customerInfoFeignClient.getCustomerOpenId(orderPayVo.getCustomerId()).getData();String driverOpenId = driverInfoFeignClient.getDriverOpenId(orderPayVo.getDriverId()).getData();//封装需要数据到实体类,远程调用发起微信支付PaymentInfoForm paymentInfoForm = new PaymentInfoForm();paymentInfoForm.setCustomerOpenId(customerOpenId);paymentInfoForm.setDriverOpenId(driverOpenId);paymentInfoForm.setOrderNo(orderPayVo.getOrderNo());paymentInfoForm.setAmount(orderPayVo.getPayAmount());paymentInfoForm.setContent(orderPayVo.getContent());paymentInfoForm.setPayWay(1);WxPrepayVo wxPrepayVo = wxPayFeignClient.createWxPayment(paymentInfoForm).getData();return wxPrepayVo;
}
查询支付状态

- 判断微信支付是否成功,有两种方式,就是以上两种红字
- 微信支付成功/失败就会回调我们的接作
- 收到调用之后我们就可以继续接下来的操作
- 用消息队列保证数据的最终一致性,在支付成功后我们后续还有很多的操作,如果把所有的操作全部成功再向用户进行返回,那就太慢了,如果支付成功,我们就先向用户返回结果,并向消息队列发送关键信息用于后续的操作
根据订单编号查询支付状态
@Operation(summary = "支付状态查询")
@GetMapping("/queryPayStatus/{orderNo}")
public Result queryPayStatus(@PathVariable String orderNo) {return Result.ok(wxPayService.queryPayStatus(orderNo));
}//查询支付状态
@Override
public Boolean queryPayStatus(String orderNo) {//1 创建微信操作对象JsapiServiceExtension service =new JsapiServiceExtension.Builder().config(rsaAutoCertificateConfig).build();//2 封装查询支付状态需要参数QueryOrderByOutTradeNoRequest queryRequest = new QueryOrderByOutTradeNoRequest();queryRequest.setMchid(wxPayV3Properties.getMerchantId());queryRequest.setOutTradeNo(orderNo);//3 调用微信操作对象里面方法实现查询操作Transaction transaction = service.queryOrderByOutTradeNo(queryRequest);//4 查询返回结果,根据结果判断if(transaction != null&& transaction.getTradeState() == Transaction.TradeStateEnum.SUCCESS) {//5 如果支付成功,调用其他方法实现支付后处理逻辑this.handlePayment(transaction);return true;}return false;
}/*** 支付状态查询* @param orderNo* @return*/
@GetMapping("/payment/wxPay/queryPayStatus/{orderNo}")
Result<Boolean> queryPayStatus(@PathVariable("orderNo") String orderNo);@Operation(summary = "支付状态查询")
@GuiguLogin
@GetMapping("/queryPayStatus/{orderNo}")
public Result<Boolean> queryPayStatus(@PathVariable String orderNo) {return Result.ok(orderService.queryPayStatus(orderNo));
}@Override
public Boolean queryPayStatus(String orderNo) {return wxPayFeignClient.queryPayStatus(orderNo).getData();
}
微信支付成功回调接口
@Operation(summary = "微信支付异步通知接口")
@PostMapping("/notify")
public Map<String,Object> notify(HttpServletRequest request) {try {wxPayService.wxnotify(request);//返回成功Map<String,Object> result = new HashMap<>();result.put("code", "SUCCESS");result.put("message", "成功");return result;} catch (Exception e) {e.printStackTrace();}//返回失败Map<String,Object> result = new HashMap<>();result.put("code", "FAIL");result.put("message", "失败");return result;
}//微信支付成功后,进行的回调@Overridepublic void wxnotify(HttpServletRequest request) {
//1.回调通知的验签与解密//从request头信息获取参数//HTTP 头 Wechatpay-Signature// HTTP 头 Wechatpay-Nonce//HTTP 头 Wechatpay-Timestamp//HTTP 头 Wechatpay-Serial//HTTP 头 Wechatpay-Signature-Type//HTTP 请求体 body。切记使用原始报文,不要用 JSON 对象序列化后的字符串,避免验签的 body 和原文不一致。String wechatPaySerial = request.getHeader("Wechatpay-Serial");String nonce = request.getHeader("Wechatpay-Nonce");String timestamp = request.getHeader("Wechatpay-Timestamp");String signature = request.getHeader("Wechatpay-Signature");String requestBody = RequestUtils.readData(request);//2.构造 RequestParamRequestParam requestParam = new RequestParam.Builder().serialNumber(wechatPaySerial).nonce(nonce).signature(signature).timestamp(timestamp).body(requestBody).build();//3.初始化 NotificationParserNotificationParser parser = new NotificationParser(rsaAutoCertificateConfig);//4.以支付通知回调为例,验签、解密并转换成 TransactionTransaction transaction = parser.parse(requestParam, Transaction.class);if(null != transaction && transaction.getTradeState() == Transaction.TradeStateEnum.SUCCESS) {//5.处理支付业务this.handlePayment(transaction);}}
内网穿透配置

- 还可以把项目部署到腾讯云、华为云等来解决这个问题
- 测试的时候我们没有域名,就可以使用内网穿透的工具
这里使用的工具是ngrok.cc
支付成功后续处理
封装RabbitMQ
@Service
public class RabbitService {@Autowiredprivate RabbitTemplate rabbitTemplate;//发送消息public boolean sendMessage(String exchange,String routingkey,Object message) {rabbitTemplate.convertAndSend(exchange,routingkey,message);return true;}
}
- 修改nacos配置
发送端
//如果支付成功,调用其他方法实现支付后处理逻辑
public void handlePayment(Transaction transaction) {//1 更新支付记录,状态修改为 已经支付//订单编号String orderNo = transaction.getOutTradeNo();//根据订单编号查询支付记录LambdaQueryWrapper<PaymentInfo> wrapper = new LambdaQueryWrapper<>();wrapper.eq(PaymentInfo::getOrderNo,orderNo);PaymentInfo paymentInfo = paymentInfoMapper.selectOne(wrapper);//如果已经支付,不需要更新if(paymentInfo.getPaymentStatus() == 1) {return;}paymentInfo.setPaymentStatus(1);paymentInfo.setOrderNo(transaction.getOutTradeNo());paymentInfo.setTransactionId(transaction.getTransactionId());paymentInfo.setCallbackTime(new Date());paymentInfo.setCallbackContent(JSON.toJSONString(transaction));paymentInfoMapper.updateById(paymentInfo);//2 发送端:发送mq消息,传递 订单编号// 接收端:获取订单编号,完成后续处理rabbitService.sendMessage(MqConst.EXCHANGE_ORDER,MqConst.ROUTING_PAY_SUCCESS,orderNo);
}
接收端
@Component
public class PaymentReceiver {@Autowiredprivate WxPayService wxPayService;@RabbitListener(bindings = @QueueBinding(value = @Queue(value = MqConst.QUEUE_PAY_SUCCESS,durable = "true"),exchange = @Exchange(value = MqConst.EXCHANGE_ORDER),key = {MqConst.ROUTING_PAY_SUCCESS}))public void paySuccess(String orderNo, Message message, Channel channel) {wxPayService.handleOrder(orderNo);}
}//支付成功后续处理
@Override
public void handleOrder(String orderNo) {//1 远程调用:更新订单状态:已经支付orderInfoFeignClient.updateOrderPayStatus(orderNo);//2 远程调用:获取系统奖励,打入到司机账户OrderRewardVo orderRewardVo = orderInfoFeignClient.getOrderRewardFee(orderNo).getData();if(orderRewardVo != null && orderRewardVo.getRewardFee().doubleValue()>0) {TransferForm transferForm = new TransferForm();transferForm.setTradeNo(orderNo);transferForm.setTradeType(TradeType.REWARD.getType());transferForm.setContent(TradeType.REWARD.getContent());transferForm.setAmount(orderRewardVo.getRewardFee());transferForm.setDriverId(orderRewardVo.getDriverId());driverAccountFeignClient.transfer(transferForm);}//3 TODO 其他}
订单状态更新接口
@Operation(summary = "更改订单支付状态")
@GetMapping("/updateOrderPayStatus/{orderNo}")
public Result<Boolean> updateOrderPayStatus(@PathVariable String orderNo) {return Result.ok(orderInfoService.updateOrderPayStatus(orderNo));
}@Override
public Boolean updateOrderPayStatus(String orderNo) {//1 根据订单编号查询,判断订单状态LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();wrapper.eq(OrderInfo::getOrderNo,orderNo);OrderInfo orderInfo = orderInfoMapper.selectOne(wrapper);if(orderInfo == null || orderInfo.getStatus() == OrderStatus.PAID.getStatus()) {return true;}//2 更新状态LambdaQueryWrapper<OrderInfo> updateWrapper = new LambdaQueryWrapper<>();updateWrapper.eq(OrderInfo::getOrderNo,orderNo);OrderInfo updateOrderInfo = new OrderInfo();updateOrderInfo.setStatus(OrderStatus.PAID.getStatus());updateOrderInfo.setPayTime(new Date());int rows = orderInfoMapper.update(updateOrderInfo, updateWrapper);if(rows == 1) {return true;} else {throw new GuiguException(ResultCodeEnum.UPDATE_ERROR);}
}/*** 更改订单支付状态* @param orderNo* @return*/
@GetMapping("/order/info//updateOrderPayStatus/{orderNo}")
Result<Boolean> updateOrderPayStatus(@PathVariable("orderNo") String orderNo);
获取订单系统奖励
@Operation(summary = "获取订单的系统奖励")
@GetMapping("/getOrderRewardFee/{orderNo}")
public Result<OrderRewardVo> getOrderRewardFee(@PathVariable String orderNo) {return Result.ok(orderInfoService.getOrderRewardFee(orderNo));
}@Override
public OrderRewardVo getOrderRewardFee(String orderNo) {//根据订单编号查询订单表OrderInfo orderInfo = orderInfoMapper.selectOne(new LambdaQueryWrapper<OrderInfo>().eq(OrderInfo::getOrderNo, orderNo).select(OrderInfo::getId,OrderInfo::getDriverId));//根据订单id查询系统奖励表OrderBill orderBill = orderBillMapper.selectOne(new LambdaQueryWrapper<OrderBill>().eq(OrderBill::getOrderId, orderInfo.getId()).select(OrderBill::getRewardFee));//封装到vo里面OrderRewardVo orderRewardVo = new OrderRewardVo();orderRewardVo.setOrderId(orderInfo.getId());orderRewardVo.setDriverId(orderInfo.getDriverId());orderRewardVo.setRewardFee(orderBill.getRewardFee());return orderRewardVo;
}/*** 获取订单的系统奖励* @param orderNo* @return*/
@GetMapping("/order/info//getOrderRewardFee/{orderNo}")
Result<OrderRewardVo> getOrderRewardFee(@PathVariable("orderNo") String orderNo);
系统奖励打入司机账户
@Tag(name = "司机账户API接口管理")
@RestController
@RequestMapping(value="/driver/account")
@SuppressWarnings({"unchecked", "rawtypes"})
public class DriverAccountController {@Autowiredprivate DriverAccountService driverAccountService;@Operation(summary = "转账")@PostMapping("/transfer")public Result<Boolean> transfer(@RequestBody TransferForm transferForm) {return Result.ok(driverAccountService.transfer(transferForm));}
}@Override
public Boolean transfer(TransferForm transferForm) {//1 去重LambdaQueryWrapper<DriverAccountDetail> wrapper = new LambdaQueryWrapper<>();wrapper.eq(DriverAccountDetail::getTradeNo,transferForm.getTradeNo());Long count = driverAccountDetailMapper.selectCount(wrapper);if(count > 0) {return true;}//2 添加奖励到司机账户表driverAccountMapper.add(transferForm.getDriverId(),transferForm.getAmount());//3 添加交易记录 DriverAccountDetail driverAccountDetail = new DriverAccountDetail();BeanUtils.copyProperties(transferForm,driverAccountDetail);driverAccountDetailMapper.insert(driverAccountDetail);return true;
}<update id="add">update driver_accountset total_amount = total_amount + #{amount}, available_amount = available_amount + #{amount}, total_income_amount = total_income_amount + #{amount}where driver_id = #{driverId}
</update>@FeignClient(value = "service-driver")
public interface DriverAccountFeignClient {/*** 转账* @param transferForm* @return*/@PostMapping("/driver/account/transfer")Result<Boolean> transfer(@RequestBody TransferForm transferForm);
}
seata实现分布式事务
订单支付成功后,订单状态更新、获取订单系统奖励、系统奖励打入司机账户都是通过远程调用来实现的,我们就在这儿使用seata分布式事务
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<dependency><groupId>io.seata</groupId><artifactId>seata-spring-boot-starter</artifactId><version>1.7.1</version>
</dependency>seata.tx-service-group=tingshu-tx-group
seata.service.vgroup-mapping.tingshu-tx-group=default
seata.service.grouplist.default=127.0.0.1:8091// 在事务的入口添加@GlobalTransactional
// 其他的小事务依然使用@Transactional
乘客下单功能
- 乘客登录之后,选择代驾开始和结束地址,之后乘客呼叫代驾
- 乘客呼叫代驾之后,等待司机接单
- 但是,如果在乘客呼叫代驾之后15分钟之后,如果还是没有司机接单,订单自动取消,如果15分钟以内有司机接单,完成代驾过程
- 总结:15分钟没有司机接单,自动取消订单
- 总体思路:使用延迟队列消息实现订单到时间自动取消功能
- 有很多种方式:
第一种 使用RabbitMQ里面TTL和死信队列实现
第二种 在RabbitMQ安装延迟队列实现
- 就是装个插件,在这个队列中的消息不能被消费,只有延时时间到了才能被消费,第三种 使用Redisson实现
使用RabbitMQ里面TTL和死信队列实现
- TTL:Time To Live,消息存活时间
发送端:发送消息,设置存活时间10s
接收端:发送完成之后,10s以内如果从队列获取发送过来消息,操作结束
如果超过10s消息没有被消费,消息超时了,无法被消费,成为死信
- 死信队列:无法被消费的消息
– 超过存活时间没有被消费的消息
– 消息端拒绝接收,不能放回队列里面
– 队列最大长度

使用Redisson实现
- 利用redissonClient 发送延迟消息
redissonClient.getBlockingDeque(): 创建一个阻塞队列redissonClient.getDelayedQueue(): 获取延迟队列delayedQueue.offer(): 向队列中存储数据blockingDeque.take(): 从队列中获取数据
//乘客下单
@Override
public Long saveOrderInfo(OrderInfoForm orderInfoForm) {//order_info添加订单数据OrderInfo orderInfo = new OrderInfo();BeanUtils.copyProperties(orderInfoForm,orderInfo);//订单号String orderNo = UUID.randomUUID().toString().replaceAll("-","");orderInfo.setOrderNo(orderNo);//订单状态orderInfo.setStatus(OrderStatus.WAITING_ACCEPT.getStatus());orderInfoMapper.insert(orderInfo);//生成订单之后,发送延迟消息this.sendDelayMessage(orderInfo.getId());//记录日志this.log(orderInfo.getId(),orderInfo.getStatus());//向redis添加标识//接单标识,标识不存在了说明不在等待接单状态了redisTemplate.opsForValue().set(RedisConstant.ORDER_ACCEPT_MARK,"0", RedisConstant.ORDER_ACCEPT_MARK_EXPIRES_TIME, TimeUnit.MINUTES);return orderInfo.getId();
}
- 编写发送延迟消息的方法
//生成订单之后,发送延迟消息
private void sendDelayMessage(Long orderId) {try{//1 创建队列RBlockingQueue<Object> blockingDueue = redissonClient.getBlockingQueue("queue_cancel");//2 把创建队列放到延迟队列里面RDelayedQueue<Object> delayedQueue = redissonClient.getDelayedQueue(blockingDueue);//3 发送消息到延迟队列里面//设置过期时间delayedQueue.offer(orderId.toString(),15,TimeUnit.MINUTES);}catch (Exception e) {e.printStackTrace();throw new GuiguException(ResultCodeEnum.DATA_ERROR);}
}
- 监听延迟队列消息
//监听延迟队列
@Component
public class RedisDelayHandle {@Autowiredprivate RedissonClient redissonClient;@Autowiredprivate OrderInfoService orderInfoService;@PostConstructpublic void listener() {new Thread(()->{// while true 实现一直监听while(true) {//获取延迟队列里面阻塞队列RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue("queue_cancel");//从队列获取消息try {String orderId = blockingQueue.take();//取消订单if(StringUtils.hasText(orderId)) {//调用方法取消订单orderInfoService.orderCancel(Long.parseLong(orderId));}} catch (InterruptedException e) {throw new RuntimeException(e);}}}).start();}
}//调用方法取消订单
@Override
public void orderCancel(long orderId) {//orderId查询订单信息OrderInfo orderInfo = orderInfoMapper.selectById(orderId);//判断if(orderInfo.getStatus()==OrderStatus.WAITING_ACCEPT.getStatus()) {//修改订单状态:取消状态orderInfo.setStatus(OrderStatus.CANCEL_ORDER.getStatus());int rows = orderInfoMapper.updateById(orderInfo);if(rows == 1) {//删除接单标识redisTemplate.delete(RedisConstant.ORDER_ACCEPT_MARK);}}
}
相关文章:
乐尚代驾十订单支付seata、rabbitmq异步消息、redisson延迟队列
账单信息 司机结束代驾之后,生成账单(包含账单信息和分账信息)司机发送账单给乘客乘客获取账单之后,进行支付 获取账单信息 order_bill表记录的账单信息,我们直接获取即可 Operation(summary "根据订单id获取…...
HCIP--3实验- 链路聚合,VLAN间通讯,Super VLAN,MSTP,VRRPip配置,静态路由,环回,缺省,空接口,NAT
学习目标: 链路聚合VLAN间通讯Super VLANMSTPVRRPip配置,静态路由,环回,缺省,空接口NAT 学习内容: 实验拓扑实验需求实验需求分析实验配置内容 (每一个设备的每一步操作)实验结果验证 1.实验拓扑 搭建 …...
Apple提出MM1.5:多模态大型语言模型微调的方法、分析和见解_mm1.5 模型下载
摘要 我们介绍了 MM1.5,一个新的多模态大型语言模型 (MLLM) 家族,旨在增强在富文本图像理解、视觉参照和定位以及多图像推理方面的能力。 在 MM1 架构的基础上,MM1.5 采用以数据为中心的模型训练方法,系统地探索了整个模型训练生…...
【毫米波雷达(三)】汽车控制器启动流程——BootLoader
汽车控制器启动流程——BootLoader 一、什么是Bootloader(BT)?二、FBL、PBL、SBL、ESS的区别三、MCU的 A/B分区的实现 一、什么是Bootloader(BT)? BT就是一段程序,一段引导程序。它包含了启动代码、中断、主程序等。 雷达启动需要由BT跳转到…...
AI 搜索来势汹汹,互联网将被颠覆还是进化?
最近,美国新闻集团起诉了知名 AI 搜索引擎 Perplexity AI。也许你会想,这不就是又一起“AI 惹官司”吗?其实,这次情况不太一样,甚至可能会改变我们未来上网的方式! 争议的焦点是什么?是未来的 …...
《二分查找算法:在有序数组中搜索目标值》
目录 一、问题分析 二、二分查找算法原理 三、代码实现 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target,我们要写一个函数来搜索 nums 中的 target,如果目标值存在就返回它的下标,否则返回 -1。 …...
【万字总结】数据结构常考应用大题做法画法详解_树_哈希表_图_排序大总结
文章目录 1.树相关应用大题1.1 已知二叉树的中序序列和前序or中序,画出二叉树1.2 二叉树的遍历、树的遍历、森林的遍历总结1.3二叉树与森林之间的转换1.3.1 已知树的先序序列和中序序列,画出森林 1.4 二叉树的线索化1.5 二叉排序树1.5.1 二叉排序树的删除…...
Docker + Jenkins + gitee 实现CICD环境搭建
目录 前言 关于Jenkins 安装Jenkins docker中运行Jenkins注意事项 通过容器中的Jenkins,把服务打包到docker进行部署 启动Jenkins 创建第一个任务 前言 CI/CD(持续集成和持续交付/持续部署),它可以实现自动化的构建、测试和部署…...
rabbitMq怎么保证消息不丢失?消费者没有接收到消息怎么处理
在使用RabbitMQ时,保证消息不丢失以及处理消费者未接收到消息的情况可以通过以下几个方法: 1. 确保消息的持久化 队列持久化:在声明队列时将其设置为持久化(durabletrue),这样RabbitMQ在重启后也会保留队…...
商务数据分析在提升客户体验方面的作用体现在哪些环节
在当今竞争激烈的商业环境中,客户体验已成为企业成功的关键因素。商务数据分析犹如一把神奇的钥匙,在提升客户体验的征程中发挥着至关重要的作用,贯穿于多个环节。 一、客户需求分析:洞察客户内心的窗口 客户需求分析是商务数据…...
cooladmin使用整理
1、后端关键字自动生成没有代码段提示,原因是拉取的项目代码中没有.vscode文件夹,复制一套至项目src同级即可 2、前端快速创建,在Entity完成后就去快速创建中选数据结构,这时没有对应的内容,数据结构是和controller层a…...
CentOS 7 更换软件仓库
CentOS 7 于2024年6月30日停止维护,官方仓库已经没有软件了,想要继续使用 ,需要更换软件仓库,这里更换到阿里云的软件仓库 https://developer.aliyun.com/mirror/ 查看目前可用的软件数量 yum repolist 更换软件仓库:…...
现代Web开发:React Hooks深入解析
💓 博客主页:瑕疵的CSDN主页 📝 Gitee主页:瑕疵的gitee主页 ⏩ 文章专栏:《热点资讯》 现代Web开发:React Hooks深入解析 现代Web开发:React Hooks深入解析 现代Web开发:React Hook…...
HarmonyOS使用arkTS拉起指定第三方应用程序
HarmonyOS使用arkTS拉起指定第三方应用程序 前言代码及说明bundleName获取abilityName获取 前言 本篇只说采用startAbility方式拉起第三方应用,需要用到两个必备的参数bundleName,abilityName,本篇就介绍如何获取参数… 代码及说明 bundle…...
flex安装学习笔记
https://zhuanlan.zhihu.com/p/2783726096 3.下载 Flux 模型 FLUX.1 [dev] :官方版本满配版,最低显存要求 24G;FLUX.1 [dev] fp8:大佬优化 [dev] 后版本,建议选择此版本,最低 12G 显存可跑;FLU…...
09-结构化搜索、搜索的相关性算分
term 查询执行精确值匹配,要求文档中的字段值与指定的词项完全相等。对于日期字段等精确值字段,通常使用 term 查询可以快速有效地匹配文档。match 查询执行全文搜索,会对输入的文本进行分析,生成查询词项,并试图找到与…...
手机屏幕上进行OCR识别方案
在手机屏幕上进行OCR识别,可以通过一些主流方案实现高效、准确的文本识别。以下是几种常见方案: 1. 使用 Tesseract OCR 原理:Tesseract 是一个开源的 OCR 引擎,支持多种语言。可以通过一些优化提升其对手机屏幕文本的识别效果。…...
遗传算法与深度学习实战(22)——使用Numpy构建神经网络
遗传算法与深度学习实战(22)——使用Numpy构建神经网络 0. 前言1. 神经网络基础1.1 简单神经网络的架构1.2 神经网络的训练 2. 使用 Numpy 构建神经网络2.1 网络架构2.2 实现神经网络 小结系列链接 0. 前言 我们已经学习了如何使用进化算法来优化深度学…...
react->Antd->Table调整checkbox默认样式
checkbox默认不展示,hover此行时,出现checkbox,选中后不消失: hover前,设置透明边框; hover时,checkbox出现 选中后 代码块: .ant-checkbox {.ant-checkbox-inner {border: transparent;}}.ant…...
一种ESB的设计
系统架构 ESB包括: ESB总控服务、业务应用集群、业务消息WEB服务、业务消息日志服务、运维管理平台、业务设计器。如下图所示 ESB总控服务 ESB总控服务承载了各项业务的运维和管理。主要包括: 业务流程的管理ESB内部不同模块间的通讯ESB系统设置和管理…...
智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用
1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
【C++进阶篇】智能指针
C内存管理终极指南:智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...
基于Springboot+Vue的办公管理系统
角色: 管理员、员工 技术: 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能: 该办公管理系统是一个综合性的企业内部管理平台,旨在提升企业运营效率和员工管理水…...
规则与人性的天平——由高考迟到事件引发的思考
当那位身着校服的考生在考场关闭1分钟后狂奔而至,他涨红的脸上写满绝望。铁门内秒针划过的弧度,成为改变人生的残酷抛物线。家长声嘶力竭的哀求与考务人员机械的"这是规定",构成当代中国教育最尖锐的隐喻。 一、刚性规则的必要性 …...
