96SEO 2026-02-19 21:12 13
订单请求6、订单确认页模型抽取7、订单确认页vo封装8、Feign
异步调用请求头丢失问题10、查看库存状态11、模拟计算运费12、接口幂等性1什么是接口幂等性2哪些情况要防止接口幂等性3什么情况下需要幂等性4幂等性解决方案token
全局唯一id13、订单确认页防重令牌14、提交订单二、分布式事务1、本地事务1事务的基本性质2事务的隔离级别3事务的传播行为SpringBoot
分布式事务的几种解决方案2PC模式柔性事务-TCC事务补偿型方案柔性事务-
远程调用问题四、定时关闭订单五、支付服务1、一些概念说明2、沙箱环境测试3、内网穿透4、整合阿里云支付服务5、支付成功同步回调6、异步通知内网穿透环境搭建7、支付完成8、收单六、秒杀1、定时任务与Cron表达式2、秒杀商品上架3、秒杀上架幂等性问题4、查询所有的秒杀商品时区BUG5、查询某一个秒杀
商品6、秒杀系统设计7、秒杀流程七、Sentinel1、熔断降级限流2、SpringBoot整合Sentinel3、实时监控4、自定义sentinel的返回信息5、RabbitTemplate
循环依赖问题6、熔断降级7、开启自定义的受保护资源8、网关流控9、自定义网关流控回调总结一、订单服务
订单模块是电商系统的枢纽在订单这个环节上需求获取多个模块的数据和信息同时对这
些信息进行加工处理后流向下个环节这一系列就构成了订单的信息流通
用户信息包括用户账号、用户等级、用户的收货地址、收货人、收货人电话等组成用户账
户需要绑定手机号码但是用户绑定的手机号码不一定是收货信息上的电话。
用户可以添加
多个收货信息用户等级信息可以用来和促销系统进行匹配获取商品折扣同时用户等级
订单基础信息是订单流转的核心其包括订单类型、父/子订单、订单编号、订单状态、订
1订单类型包括实体商品订单和虚拟订单商品等这个根据商城商品和服务类型进行区
2同时订单都需要做父子订单处理之前在初创公司一直只有一个订单没有做父子订
单处理后期需要进行拆单的时候就比较麻烦尤其是多商户商场和不同仓库商品的时候
3订单编号不多说了需要强调的一点是父子订单都需要有订单编号需要完善的时候
4订单状态记录订单每次流转过程后面会对订单状态进行单独的说明。
5订单流转时间需要记录下单时间支付时间发货时间结束时间/关闭时间等等
优惠信息记录用户参与的优惠活动包括优惠促销活动比如满减、满赠、秒杀等用户使
用的优惠券信息优惠券满足条件的优惠券需要默认展示出来具体方式已在之前的优惠券
因为优惠信息只是记录用户使用的条目而支付信息需要加入数据进行计算所以做为区分。
1支付流水单号这个流水单号是在唤起网关支付后支付通道返回给电商业务平台的支
2支付方式用户使用的支付方式比如微信支付、支付宝支付、钱包支付、快捷支付等。
3商品总金额每个商品加总后的金额运费物流产生的费用优惠总金额包括促
销活动的优惠金额优惠券优惠金额虚拟积分或者虚拟币抵扣的金额会员折扣的金额等
物流信息包括配送方式物流公司物流单号物流状态物流状态可以通过第三方接口来
用户提交订单后订单进行预下单目前主流电商网站都会唤起支付便于用户快速完成支
付需要注意的是待付款状态下可以对库存进行锁定锁定库存需要配置支付超时时间超
用户完成订单支付订单系统需要记录支付时间支付流水单号便于对账订单下放到
仓储将商品出库后订单进入物流环节订单系统需要同步物流信息便于用户实时知悉物
用户确认收货后订单交易完成。
后续支付侧进行结算如果订单存在问题进入售后状态
售后也同样存在各种状态当发起售后申请后生成售后订单售后订单状态为待审核等待
商家审核商家审核通过后订单状态变更为待退货等待用户将商品寄回商家收货后订单
状态更新为待退款状态退款到用户原账户后订单状态更新为售后成功。
订单流程是指从订单产生到完成整个流转的过程从而行程了一套标准流程规则。
而不同的
产品类型或业务类型在系统中的流程会千差万别比如上面提到的线上实物订单和虚拟订单
不管类型如何订单都包括正向流程和逆向流程对应的场景就是购买商品和退换货流程正
向流程就是一个正常的网购步骤订单生成–支付订单–卖家发货–确认收货–交易成功。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-832bKPJ4-1675935821383)(https://images-1313160403.cos.ap-beijing.myqcloud.com/MarkDown/%E7%94%B5%E5%95%86%E8%AE%A2%E5%8D%95%E6%B5%81%E7%A8%8B%E5%9B%BE.png)]
、支付成功后需要进行拆单根据商品打包方式所在仓库物流等进行拆单
、修改订单用户没有提交订单可以对订单一些信息进行修改比如配送信息
优惠信息及其他一些订单可修改范围的内容此时只需对数据进行变更即可。
、订单取消用户主动取消订单和用户超时未支付两种情况下订单都会取消订
单而超时情况是系统自动关闭订单所以在订单支付的响应机制上面要做支付的
限时处理尤其是在前面说的下单减库存的情形下面可以保证快速的释放库存。
另外需要需要处理的是促销优惠中使用的优惠券权益等视平台规则进行相应补
、退款在待发货订单状态下取消订单时分为缺货退款和用户申请退款。
如果是
全部退款则订单更新为关闭状态若只是做部分退款则订单仍需进行进行同时生
成一条退款的售后订单走退款流程。
退款金额需原路返回用户的账户。
、发货后的退款发生在仓储货物配送在配送过程中商品遗失用户拒收用户
收货后对商品不满意这样情况下用户发起退款的售后诉求后需要商户进行退款
的审核双方达成一致后系统更新退款状态对订单进行退款操作金额原路返
回用户的账户同时关闭原订单数据。
仅退款情况下暂不考虑仓库系统变化。
如果
发生双方协调不一致情况下可以申请平台客服介入。
在退款订单商户不处理的情
订单确认页面再次之前增加拦截器判断用户是否登录如果没有登录跳转到登录界面进行登录。
request.getSession().getAttribute(AuthServerConstant.LOGIN_USER);if
{request.getSession().setAttribute(msg,请先登录);//
没有进行登录跳转到登录界面登录response.sendRedirect(http://auth.gulimall.com/login.html);return
false;}else{threadLocal.set(memberRespVo);return
addInterceptors(InterceptorRegistry
OrderInterceptor()).addPathPatterns(/**);}
1、首先是收货人信息。
有更多地址即有多个收货地址其中有一个默认收货地址
BigDecimal(item.getCount()))).reduce(BigDecimal::add).get():
orderService;GetMapping(/toTrade)public
保存订单确认vomodel.addAttribute(orderConfirmData,vo);return
memberFeignService;Autowiredprivate
cartFeignService;Overridepublic
OrderInterceptor.threadLocal.get();//
memberFeignService.getMemberAddresses(memberRespVo.getId());//
cartFeignService.getUserCartItems();//
3、积分信息orderConfirmVo.setIntegration(memberRespVo.getIntegration());//
orderConfirmVo;}MemberFeignService
*/GetMapping(member/memberreceiveaddress/{memberId}/getMemberAddresses)public
getMemberAddresses(PathVariable(memberId)
*/GetMapping(/getUserCartItems)public
*/GetMapping(/getUserCartItems)ResponseBodypublic
cartService.getUserCartItems();}CartServiceImpl
CartInterceptor.threadLocal.get();if
过滤掉未勾选的商品。
并且商品的价格应该从数据库中查询assert
cartItems.stream().filter(CartItem::isCheck).map(cartItem
productFeignService.getSkuInfo(cartItem.getSkuId());SkuInfoVo
{});cartItem.setPrice(data.getPrice());return
cartItem;}).collect(Collectors.toList());return
null;}}MemberReceiveAddressController
GetMapping(/{memberId}/getMemberAddresses)
getMemberAddresses(PathVariable(memberId)
memberReceiveAddressService.getMemberAddresses(memberId);
}MemberReceiveAddressServiceImpl
QueryWrapperMemberReceiveAddressEntity().eq(member_id,memberId));}8、Feign
{Bean(requestInterceptor)public
RequestInterceptor(){Overridepublic
RequestContextHolder.getRequestAttributes();HttpServletRequest
attributes.getRequest();requestTemplate.header(Cookie,request.getHeader(Cookie));}};}
org.thymeleaf.exceptions.TemplateInputException:
OrderInterceptor.threadLocal.get();CompletableFutureVoid
memberFeignService.getMemberAddresses(memberRespVo.getId());orderConfirmVo.setAddress(address);},
threadPoolExecutor);CompletableFutureVoid
Feign在远程调用之前会构造请求。
ListOrderItemVo
cartFeignService.currentUserCartItems();orderConfirmVo.setItems(items);},
3、积分信息orderConfirmVo.setIntegration(memberRespVo.getIntegration());CompletableFuture.allOf(addressFuture,orderItemsFuture).get();//
原因就是当没有使用异步任务的时候无论是Controller、Service、Dao、拦截器都由一个线程执行。
因此可以共享
*/PostMapping(ware/waresku/hasStock)public
wareInfoService.getFare(addrId);return
R.ok().setData(fare);}3、计算运费的方式手机号的最后一位如有需要可对接顺丰、圆通…等各开放平台。
memberFeignService.info(addrId);MemberAddressVo
r.getData(memberReceiveAddress,
BigDecimal(fareString);fareVo.setFare(fare);fareVo.setAddress(data);return
{RequestMapping(member/memberreceiveaddress/info/{id})public
接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的不会因
为多次点击而产生了副作用比如说支付场景用户购买了商品支付扣款成功但是返回结
果的时候网络异常此时钱已经扣了用户再次点击按钮此时会进行第二次扣款返回结
果成功用户查询余额返发现多扣钱了流水记录也变成了两条,这就没有保证接口
、token.equals、redis.del(token)如果这两个操作不是原子可能导
悲观锁使用时一般伴随事务一起使用数据锁定时间可能会很长需要根据实际情况选用。
2但返回给订单服务出现了问题订单服务又一次发起调用库存服务当订
如果多个机器可能在同一时间同时处理相同的数据比如多台机器定时任务都拿到了相同数
据处理我们就可以加分布式锁锁定此数据处理完成后释放锁。
获取到锁的必须先判断
插入数据应该按照唯一索引进行插入比如订单号相同的订单就不可能有两条记录插入。
如果是分库分表场景下路由规则要保证相同请求下落地在同一个数据库和同一表中要
不然数据库主键约束就不起效果了因为是不同的数据库和表主键不相关。
他们在同一个事务中。
这个保证了重复请求时因为去重表有唯一约束导致请求失败避
免了幂等问题。
这里要注意的是去重表和业务表应该在同一库中这样就保证了在同一个
事务即使业务操作失败了也会把去重表的数据回滚。
这个很好的保证了数据一致性。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mU4EZDFU-1675935821387)(https://images-1313160403.cos.ap-beijing.myqcloud.com/MarkDown/%E8%AE%A2%E5%8D%95%E7%A1%AE%E8%AE%A4%E9%A1%B5%E6%B5%81%E7%A8%8B.png)]
UUID.randomUUID().toString().replace(-,);orderConfirmVo.setOrderToken(token);redisTemplate.opsForValue().set(OrderConstant.USER_ORDER_TOKEN_PREFIXmemberRespVo.getId(),token);14、提交订单
收集订单确认面的数据为创建订单为准备验证令牌是否是重复的请求。
【获取令牌、验证令牌必须保证原子性操作。
可使用LUA脚本或者Redisson】、验证令牌成功就是
创建订单的数据。
包括订单信息【用户信息、收货人信息、积分信息、优惠信息】订单中每个订单项信息【订单信息、spu信息、sku信息、优惠信息、积分信息、价格信息】运费、订单总金额订单创建成功进行验价比较订单支付的金额与提交订单的金额是否一致。
如果不一致说明价格发生变化提醒用户确认订单、验价完成就是锁定库存判断仓库中是否有足够的库存。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t6LYPAfq-1675935821387)(https://images-1313160403.cos.ap-beijing.myqcloud.com/MarkDown/%E6%9C%AA%E5%91%BD%E5%90%8D%E6%96%87%E4%BB%B6(41)].png)
NO_STOCK_EXCEPTION(21000,商品库存不足),6、
*/PostMapping(/submitOrder)public
orderService.submitOrder(vo);if
创建订单成功跳转到支付页面model.addAttribute(submitOrderResp,responseVo);return
商品库存不足;break;}attributes.addFlashAttribute(msg,msg);return
redirect:http://order.gulimall.com/toTrade;}}
((NoStockException)e).getMessage();attributes.addFlashAttribute(msg,message);}return
redirect:http://order.gulimall.com/toTrade;}}7、验证令牌、创建订单、验证价格、锁定库存
OverrideTransactional(rollbackFor
NoStockException{OrderSubmitResponseVo
OrderSubmitResponseVo();MemberRespVo
OrderInterceptor.threadLocal.get();//
1、验证令牌【必须保证获取令牌、删除令牌的原子性】String
Long.class),Collections.singletonList(OrderConstant.USER_ORDER_TOKEN_PREFIX
memberRespVo.getId()),orderToken);if
令牌验证失败responseVo.setCode(1);return
orderTo.getOrder().getPayAmount();if
(Math.abs(payAmount.subtract(payPrice).doubleValue())
3、验价成功保存订单saveOrder(orderTo);//
4、锁定库存库存不足抛出异常并回滚事务WareSkuLockVo
WareSkuLockVo();ListOrderItemVo
createOrderItems(orderTo.getOrder().getOrderSn()).stream().map(item
OrderItemVo();orderItemVo.setCount(item.getSkuQuantity());orderItemVo.setSkuId(item.getSkuId());orderItemVo.setTitle(item.getSkuName());return
orderItemVo;}).collect(Collectors.toList());wareSkuLockVo.setOrderSn(orderTo.getOrder().getOrderSn());wareSkuLockVo.setLocks(locks);R
wareFeignService.orderLockStock(wareSkuLockVo);if
锁定成功responseVo.setOrder(orderTo.getOrder());return
锁定失败.抛出异常responseVo.setCode(3);throw
验价失败responseVo.setCode(2);return
redisTemplate.opsForValue().get(OrderConstant.USER_ORDER_TOKEN_PREFIX
orderTo.getOrder();order.setCreateTime(new
保存订单项orderItemService.saveBatch(orderTo.getItems());}/**
IdWorker.getTimeId();orderEntity.setOrderSn(orderSn);//
null;computePrice(items,orderEntity);orderTo.setOrder(orderEntity);orderTo.setItems(items);return
computePrice(ListOrderItemEntity
循环叠加每一个订单项的优惠价格、订单总价格、积分、成长信息for
coupon.add(orderItem.getCouponAmount());promotion
promotion.add(orderItem.getPromotionAmount());integration
integration.add(orderItem.getIntegrationAmount());//
total.add(orderItem.getRealAmount());//积分信息和成长值信息integrationTotal
orderItem.getGiftIntegration();growthTotal
设置订单的总价格、优惠价格、积分信息orderEntity.setTotalAmount(total);orderEntity.setPayAmount(total.add(orderEntity.getFreightAmount()));orderEntity.setCouponAmount(coupon);orderEntity.setPromotionAmount(promotion);orderEntity.setIntegrationAmount(integration);orderEntity.setIntegration(integrationTotal);orderEntity.setGrowth(growthTotal);//设置删除状态(0-未删除1-已删除)orderEntity.setDeleteStatus(0);}/**
createOrderEntity(OrderSubmitVo
wareFeignService.fare(vo.getAddrId());if
{});orderEntity.setFreightAmount(fareVo.getFare());orderEntity.setMemberId(fareVo.getAddress().getMemberId());orderEntity.setReceiverPhone(fareVo.getAddress().getPhone());orderEntity.setReceiverCity(fareVo.getAddress().getCity());orderEntity.setReceiverName(fareVo.getAddress().getName());orderEntity.setReceiverDetailAddress(fareVo.getAddress().getDetailAddress());orderEntity.setReceiverPostCode(fareVo.getAddress().getPostCode());orderEntity.setReceiverProvince(fareVo.getAddress().getProvince());orderEntity.setReceiverRegion(fareVo.getAddress().getRegion());}//
2、设置订单相关的状态信息orderEntity.setStatus(OrderStatusEnum.CREATE_NEW.getCode());orderEntity.setAutoConfirmDay(7);orderEntity.setConfirmStatus(0);return
cartFeignService.currentUserCartItems();if
orderItemEntityListOrderItemEntity
orderItemVos.stream().map(orderItemVo
createOrderItem(orderItemVo);orderItemEntity.setOrderSn(orderSn);return
orderItemEntity;}).collect(Collectors.toList());return
productFeignService.getSpuInfoBySkuId(orderItemVo.getSkuId());if
TypeReferenceSpuInfoVo(){});orderItemEntity.setSpuId(spuInfoVo.getId());orderItemEntity.setSpuName(spuInfoVo.getSpuName());orderItemEntity.setCategoryId(spuInfoVo.getCatelogId());orderItemEntity.setSpuBrand(spuInfoVo.getBrandId().toString());}//
3、sku信息orderItemEntity.setSkuId(orderItemVo.getSkuId());orderItemEntity.setSkuName(orderItemVo.getTitle());orderItemEntity.setSkuPic(orderItemVo.getDefaultImage());//
spring提供的可以将list集合按照指定字符拼接成string字符串orderItemEntity.setSkuAttrsVals(StringUtils.collectionToDelimitedString(orderItemVo.getSkuAttr(),;));orderItemEntity.setSkuQuantity(orderItemVo.getCount());orderItemEntity.setSkuPrice(orderItemVo.getPrice());//
5、积分信息orderItemEntity.setGiftIntegration(orderItemVo.getPrice().intValue());orderItemEntity.setGiftGrowth(orderItemVo.getPrice().intValue());//
商品促销分解金额orderItemEntity.setPromotionAmount(BigDecimal.ZERO);//
优惠券优惠分解金额orderItemEntity.setCouponAmount(new
积分优惠分解金额orderItemEntity.setIntegrationAmount(new
orderItemEntity.getSkuPrice().multiply(new
BigDecimal(orderItemEntity.getSkuQuantity()));BigDecimal
initPrice.subtract(orderItemEntity.getPromotionAmount()).subtract(orderItemEntity.getCouponAmount()).subtract(orderItemEntity.getIntegrationAmount());orderItemEntity.setRealAmount(finalPrice);return
*/GetMapping(product/spuinfo/{skuId})public
getSpuInfoBySkuId(PathVariable(skuId)Long
*/PostMapping(ware/waresku/hasStock)public
*/GetMapping(ware/wareinfo/fare)public
*/PostMapping(ware/wareinfo/lock/order)public
getSpuInfoBySkuId(PathVariable(skuId)Long
spuInfoService.getSpuInfoBySkuId(skuId);return
R.ok().setData(spuInfo);}Overridepublic
skuInfoService.getById(skuId);SpuInfoEntity
this.getById(skuInfo.getSpuId());return
wareSkuService.getSkusHasStock(skuIds);return
baseMapper.getSkusHasStock(skuId);map.put(skuId,count
resultTypejava.lang.IntegerSELECT
*/PostMapping(/lock/order)public
wareSkuService.orderLockStock(vo);return
R.error(BizCodeEnum.NO_STOCK_EXCEPTION.getCode(),BizCodeEnum.NO_STOCK_EXCEPTION.getMessage());}}}/**
wareInfoService.getFare(addrId);return
R.ok().setData(fare);}Overridepublic
memberFeignService.info(addrId);MemberAddressVo
r.getData(memberReceiveAddress,
BigDecimal(fareString);fareVo.setFare(fare);fareVo.setAddress(data);return
*/OverrideTransactional(rollbackFor
vo.getLocks();ListSkuWareHasStock
SkuWareHasStock();stock.setSkuId(item.getSkuId());//
baseMapper.listWareIdHasStock(item.getSkuId());stock.setWareId(wareIds);stock.setNum(item.getCount());return
stock;}).collect(Collectors.toList());//
baseMapper.lockSkuStock(skuId,wareId,hasStock.getNum());if
NoStockException(skuId);}}return
#{num}/update!--查询哪些仓库有对应商品的库存--select
在提交订单中使用本地事务进行事务回滚但是在分布式下可能会出现一些问题
本地事务只能保证在同一个服务同一个数据库中的回滚事务无法感知其他服务中出现的异常。
原子性一系列的操作整体不可拆分要么同时成功要么同时失败一致性数据在事务的前后业务整体一致。
隔离性事务之间互相隔离。
持久性一旦事务成功数据一定会落盘在数据库。
在以往的单体应用中我们多个业务操作使用同一条连接操作不同的数据表一旦有异常
比如买东西业务扣库存下订单账户扣款是一个整体必须同时成功或者失败
该隔离级别的事务会读到其它未提交事务的数据此现象也称之为脏读。
一个事务可以读取另一个已提交的事务多次读取会造成不一样的结果此现象称为不可重
1、PROPAGATION_REQUIRED如果当前没有事务就创建一个新事务如果当前存在事务
2、PROPAGATION_SUPPORTS支持当前事务如果当前存在事务就加入该事务如果当
3、PROPAGATION_MANDATORY支持当前事务如果当前存在事务就加入该事务如果
4、PROPAGATION_REQUIRES_NEW创建新事务无论当前存不存在事务都创建新事务。
5、PROPAGATION_NOT_SUPPORTED以非事务方式执行操作如果当前存在事务就把当
6、PROPAGATION_NEVER以非事务方式执行如果当前存在事务则抛出异常。
7、PROPAGATION_NESTED如果当前存在事务则在嵌套事务内执行。
如果当前没有事务
共享一个事务。
c是一个新事物Transactional(timeout
事务中的所有配置都没有用。
Transactional(propagation
Propagation.REQUIRES_NEW)public
在同一个类里面编写两个方法内部调用的时候会导致事务设置失效。
原因是没有用到
{b();c();}Transactional(propagation
{System.out.println(b);}Transactional(propagation
Propagation.REQUIRES_NEW)public
{System.out.println(b);System.out.println(c);}解决使用动态代理调用方法
1、EnableTransactionManagement(proxyTargetClass
2、EnableAspectJAutoProxy(exposeProxytrue)
AopContext.currentProxy();orderService.b();orderService.c();}Transactional(propagation
{System.out.println(b);}Transactional(propagation
Propagation.REQUIRES_NEW)public
{System.out.println(c);}2、分布式事务
分布式事务是企业集成中的一个技术难点也是每一个分布式系统架构中都会涉及到的一个
在分布式系统中的所有数据备份在同一时刻是否同样的值。
等同于所有节点访
在集群中一部分节点故障后集群整体是否还能响应客户端的读写请求。
对数据
大多数分布式系统都分布在多个子网络。
每个子网络就叫做一个区partition。
分区容错的意思是区间通信可能失败。
比如一台服务器放在中国另一台服务
领导者。
所有系统中的请求都是通过领导者然后领导者发送给其他随从者。
动画演示http://***secretlivesofdata.com/raft/
对于多数大型互联网应用的场景主机众多、部署分散而且现在的集群规模越来越大所
基本可用是指分布式系统在出现故障的时候允许损失部分可用性例如响应时间、
功能上的可用性允许损失部分可用性。
需要注意的是基本可用绝不等价于系
软状态是指允许系统存在中间状态而该中间状态不会影响系统整体可用性。
分布
式存储中一般一份数据会有多个副本允许不同副本同步的延时就是软状态的体
最终一致性是指系统中的所有数据副本经过一定时间后最终能够达到一致的状
态。
弱一致性和强一致性相反最终一致性是弱一致性的一种特殊情况。
第一阶段事务协调器要求每个涉及到事务的数据库预提交(precommit)此操作并反映是
其中如果有任何一个数据库否决此次提交那么所有数据库都会被要求回滚它们在此事务
3PC引入了超时机制无论协调者还是参与者在向对方发送请求后若长时间
与刚性事务不同柔性事务允许一定时间内不同节点的数据不一致但要求最终一致。
按规律进行通知不保证数据一定能通知成功但会提供可查询操作接口进行核对。
这种
方案主要用在与第三方系统通讯时比如调用微信或支付宝支付后的支付结果通知。
这种
案例银行通知、商户通知等各大交易业务平台间的商户通知多次通知、查询校对、对
实现业务处理服务在业务事务提交之前向实时消息服务请求发送消息实时消息服务只
记录消息数据而不是真正的发送。
业务处理服务在业务事务提交之后向实时消息服务确
是一款开源的分布式事务解决方案致力于提供高性能和简单易用的分布式事务服务。
Seata
(TC事务协调器维护全局事务的运行状态负责协调并驱动全局事务的提交或回滚
™控制全局事务的边界负责开启一个全局事务并最终发起全局提交或全局回滚的决议
(RM控制分支事务负责分支注册、状态汇报并接收事务协调器的指令驱动分支本地事务的提交和回滚
SpringCloudAlibaba、SpringBoot、SpringCloud
、eureka、redis、zk、consul、etcd3、sofa
、eureka、redis、zk、consul、etcd3、sofatype
seataServer.properties}file.conf
--------------------------------
--------------------------------
VARCHAR(32),transaction_service_group
中创建seataServer.properties配置文件分组名、dataId、命名空间
id文件类型选择properties。
若是使用seata1.4.2之前的版本以下的每个配置项在nacos中就是一个条目需要使用script/config-center/nacos/下的nacos-config.shlinux或者windows下装git或者nacos-config.pypython脚本执行上传注册
https://seata.io/zh-cn/docs/user/configurations.html
transport.enableTmClientBatchSendRequestfalse
transport.enableRmClientBatchSendRequesttrue
transport.enableTcServerBatchSendResponsefalse
transport.rpcRmRequestTimeout30000
transport.rpcTmRequestTimeout30000
transport.rpcTcRequestTimeout30000
transport.threadFactory.bossThreadPrefixNettyBoss
transport.threadFactory.workerThreadPrefixNettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefixNettyServerBizHandler
transport.threadFactory.shareBossWorkerfalse
transport.threadFactory.clientSelectorThreadPrefixNettyClientSelector
transport.threadFactory.clientSelectorThreadSize1
transport.threadFactory.clientWorkerThreadPrefixNettyClientWorkerThread
transport.threadFactory.bossThreadSize1
transport.threadFactory.workerThreadSizedefault
service.vgroupMapping.default_tx_groupdefault
service.default.grouplist127.0.0.1:8091
service.disableGlobalTransactionfalse#Transaction
client.rm.asyncCommitBufferLimit10000
client.rm.lock.retryPolicyBranchRollbackOnConflicttrue
client.rm.tableMetaCheckEnabletrue
client.rm.tableMetaCheckerInterval60000
client.rm.reportSuccessEnablefalse
client.rm.sagaBranchRegisterEnablefalse
client.rm.sagaJsonParserfastjson
client.rm.tccActionInterceptorOrder-2147482648
client.tm.defaultGlobalTransactionTimeout60000
client.tm.degradeCheckAllowTimes10
client.tm.degradeCheckPeriod2000
client.tm.interceptorOrder-2147482648
client.undo.logSerializationjackson
client.undo.onlyCareUpdateColumnstrue
server.undo.logDeletePeriod86400000
client.undo.compress.enabletrue
client.undo.compress.threshold64k
tcc.fence.logTableNametcc_fence_log
store.file.maxBranchSessionSize16384
store.file.maxGlobalSessionSize512
store.file.fileWriteBufferCacheSize16384
store.file.sessionReloadReadSize100#
store.db.driverClassNamecom.mysql.cj.jdbc.Driver
ip:3306/seata?useUnicodetruerewriteBatchedStatementstrue
store.db.globalTableglobal_table
store.db.branchTablebranch_table
store.db.distributedLockTabledistributed_lock
store.redis.single.host127.0.0.1
store.redis.sentinel.masterName
store.redis.sentinel.sentinelHosts
server.recovery.committingRetryPeriod1000
server.recovery.asynCommittingRetryPeriod1000
server.recovery.rollbackingRetryPeriod1000
server.recovery.timeoutRetryPeriod1000
server.maxRollbackRetryTimeout-1
server.rollbackRetryTimeoutUnlockEnablefalse
server.distributedLockExpireTime10000
server.xaerNotaRetryTimeout60000
server.session.branchAsyncQueueSize5000
server.session.enableBranchAsyncRemovefalse
server.enableParallelRequestHandlefalse#
metrics.exporterProme***usPort98983双击
!--seata--dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-seata/artifactIdexclusions!--
--exclusiongroupIdio.seata/groupIdartifactIdseata-spring-boot-starter/artifactId/exclusion/exclusions/dependencydependencygroupIdio.seata/groupIdartifactIdseata-spring-boot-starter/artifactIdversion1.4.2/version/dependency3、yaml
trueenable-auto-data-source-proxy:
#是否开启数据源自动代理,默认为truetx-service-group:
#registry根据seata服务端的registry配置type:
seata.servicenacos:server-addr:
seataServer.properties##配置自己的dataId,由于搭建服务端时把客户端的配置也写在了seataServer.properties,所以这里用了和服务端一样的配置文件,实际客户端和服务端的配置文件分离出来更好
name给定全局事务实例的名称随便取唯一即可rollbackFor当发生什么样的异常时进行回滚noRollbackFor发生什么样的异常不进行回滚。
!--seata分布式事务--dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-seata/artifactIdexclusionsexclusionartifactIdseata-all/artifactIdgroupIdio.seata/groupId/exclusion/exclusions/dependencydependencygroupIdio.seata/groupIdartifactIdseata-all/artifactIdversion0.9.0/version/dependency3、使用分布式事务的服务拷贝
spring.cloud.alibaba.seata.tx-service-groupmy_test_tx_group5、手动配置数据源seata1.4.2直接使用注解即可低版本需要手动注入
dataSource(DataSourceProperties
properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();if
(StringUtils.hasText(properties.getName()))
{dataSource.setPoolName(properties.getName());}return
}8、在业务的方法入口使用全局事务注解GlobalTransactiona
哪个微服务使用了**GlobalTransactional**哪个就是TMRM事务的参与者一个数据库就是一个RM。
使用加锁方式使业务变成串行化比较适用于后台管理系统这种并发性不高的分布式事务问题。
当库存锁定成功时向交换机发送一条消息【订单号、锁定库存状态、锁了几个库存…】根据
之后会判断订单状态是否需要解锁库存。
需要解锁库存将消息转发到解锁库存的服务即可
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pyQ51jiU-1675935821391)(https://images-1313160403.cos.ap-beijing.myqcloud.com/MarkDown/%E8%AE%A2%E5%8D%95%E6%B5%81%E7%A8%8B%E5%9B%BE.png)]
Jackson2JsonMessageConverter();}RabbitListener(queues
stock.release.stock.queue)public
TopicExchange(stock-event-exchange,true,false,null);}Beanpublic
Queue(stock.release.stock.queue,true,false,false,null);}Beanpublic
设置与队列相连的死信交换机arguments.put(x-dead-letter-exchange,stock-event-exchange);//
路由键arguments.put(x-dead-letter-routing-key,stock.release);//
TTL。
超过30s就表示未支付订单准备关闭arguments.put(x-message-ttl,12000);return
Queue(stock.delay.queue,true,false,false,arguments);}Beanpublic
Binding(stock.delay.queue,Binding.DestinationType.QUEUE,stock-event-exchange,stock.locked,null);}Beanpublic
Binding(stock.release.stock.queue,Binding.DestinationType.QUEUE,stock-event-exchange,stock.release.#,null);}
}3、在锁定库存时需要保存库存的工作单表示该库存操作的状态是否成功是否需要回滚。
2、成功创建订单锁定库存但是由于接下来的业务出现异常需要自动解锁库存。
*
*/OverrideTransactional(rollbackFor
WareOrderTaskEntity();taskEntity.setOrderSn(vo.getOrderSn());wareOrderTaskService.save(taskEntity);//
vo.getLocks();ListSkuWareHasStock
SkuWareHasStock();stock.setSkuId(item.getSkuId());//
baseMapper.listWareIdHasStock(item.getSkuId());stock.setWareId(wareIds);stock.setNum(item.getCount());return
stock;}).collect(Collectors.toList());//
保存工作单详情信息WareOrderTaskDetailEntity
WareOrderTaskDetailEntity();taskDetailEntity.setWareId(wareId);taskDetailEntity.setSkuId(skuId);taskDetailEntity.setSkuNum(hasStock.getNum());taskDetailEntity.setTaskId(taskEntity.getId());taskDetailEntity.setLockStatus(1);wareOrderTaskDetailService.save(taskDetailEntity);//
StockLockedTo();stockLockedTo.setTaskId(taskEntity.getId());//
如果一共有三件商品前俩件锁定成功第三件锁定失败。
那么本地事务是会将这三件库存都会回滚。
因此如果只保存id查不到任何信息。
//
stockLockedTo.setTaskDetailId(detailEntity.getId());StockDetailLockedTo
StockDetailLockedTo();BeanUtils.copyProperties(taskDetailEntity,
stockDetailLockedTo);stockLockedTo.setTaskDetail(stockDetailLockedTo);rabbitTemplate.convertAndSend(stock-event-exchange,
NoStockException(skuId);}}return
需要有消费者接收并进行自动解锁。
并且使用手动确认机制解锁库存失败重新将消息放回队列。
等待
wareSkuService;RabbitHandlerpublic
handleStockedRelease(StockLockedTo
{System.out.println(收到库存解锁通知...);wareSkuService.unLock(to);//
手动确认channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);}
有任何异常都是解锁失败channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);e.printStackTrace();}}
如果没有这个订单需要解锁有可能订单创建成功后库存锁定成功接着创建订单又调用其他方法把自己搞回滚了。
*
如果有订单还需要判断订单的支付状态。
如果支付成功也无需解锁。
支付失败或者取消支付进行解锁。
*
应该使用手动确认机制解锁失败重新将信息放回队列。
*/Overridepublic
查询是否有工作单详情信息WareOrderTaskDetailEntity
wareOrderTaskDetailService.getById(taskDetail.getId());if
{//查出wms_ware_order_task工作单的信息WareOrderTaskEntity
wareOrderTaskService.getById(to.getTaskId());//
orderFeignService.getOrderByOrderSn(orderTaskInfo.getOrderSn());if
OrderStatusEnum.CANCLED.getCode())
(orderTaskDetailEntity.getLockStatus()
没有订单或者取消订单需要自动解锁unLockStock(taskDetail.getSkuId(),
taskDetail.getWareId(),taskDetail.getSkuNum(),taskDetail.getId());}}}else
RuntimeException(解锁失败...重新入队);}}//
库存解锁后更新库存工作单状态WareOrderTaskDetailEntity
WareOrderTaskDetailEntity();orderTaskDetailEntity.setId(detailId);//
变为已解锁orderTaskDetailEntity.setLockStatus(2);wareOrderTaskDetailService.updateById(orderTaskDetailEntity);}mapper
idunLockStockUPDATEwms_ware_sku
spring.rabbitmq.listener.simple.acknowledge-modemanual解决
orderFeignService.getOrderByOrderSn(orderTaskInfo.getOrderSn());Could
中设置了一个登录拦截器想要访问订单都必须登录才可以而我们远程调用无需登录。
Order服务中的登录拦截器中做一个匹配映射如果是远程调用的请求直接放行。
AntPathMatcher().match(order/order/status/**,
true;}我的解决方案不知道为什么我按照老师的解决方案没有管用依然报这个错误配置AntPathMatcher没有起到作用因此我直接在注册拦截器时将这个路径排除了。
Exchange、Queue、Binding...服务启动会自动向RabbitMQ创建。
*
设置与队列相连的死信交换机arguments.put(x-dead-letter-exchange,order-event-exchange);//
路由键arguments.put(x-dead-letter-routing-key,order.release.order);//
TTL。
超过1min就表示未支付订单准备关闭arguments.put(x-message-ttl,60000);return
Queue(order.delay.queue,true,false,false,arguments);}//
Queue(order.release.order.queue,true,false,false,null);}//
TopicExchange(order-event-exchange,true,false);}//
Binding(order.delay.queue,Binding.DestinationType.QUEUE,order-event-exchange,order.create.order,null);}//
——》order.release.order.queueBeanpublic
Binding(order.release.order.queue,Binding.DestinationType.QUEUE,order-event-exchange,order.release.order,null);}
创建订单成功向RabbitMQ发送消息rabbitTemplate.convertAndSend(order-event-exchange,order.create.order,orderTo.getOrder());3、监听器接受消息
orderService;RabbitHandlerpublic
{System.out.println(订单超时未支付即将关闭订单:
{orderService.closeOrder(order);//
手动确认channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);}
{channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);e.printStackTrace();}}
OrderStatusEnum.CREATE_NEW.getCode())
OrderEntity();entity.setId(orderEntity.getId());//
设置订单状态为取消entity.setStatus(OrderStatusEnum.CANCLED.getCode());this.updateById(entity);}}当超过指定时间后修改订单的状态由于
存在一定的时间差正常情况下在订单创建成功后会隔一段时间去检查订单状态解锁库存
但是这种方式还有可能会出现问题假如订单解锁有延迟解锁库存先执行完订单就不会被释放了。
——》stock.release.stock.queueBeanpublic
Binding(stock.release.stock.queue,Binding.DestinationType.QUEUE,order-event-exchange,order.release.o***r.#,null);}6、关闭订单后向MQ在发送一次消息
OrderStatusEnum.CREATE_NEW.getCode())
OrderEntity();entity.setId(orderEntity.getId());//
设置订单状态为取消entity.setStatus(OrderStatusEnum.CANCLED.getCode());this.updateById(entity);//
OrderTo();BeanUtils.copyProperties(orderEntity,orderTo);rabbitTemplate.convertAndSend(order-event-exchange,order.release.o***r,orderTo);}}7、增加监听器接收消息
handleOrderCloseRelease(OrderTo
{System.out.println(订单关闭即将解锁库存....);wareSkuService.unLock(order);//
手动确认channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);}
有任何异常都是解锁失败channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);e.printStackTrace();}}8、库存解锁解锁时需要判断工作单的锁定状态防止重复解锁
wareOrderTaskService.getOrderTaskByOrderSn(orderEntity.getOrderSn());//
只查询锁定的工作单详情防止重复解锁。
ListWareOrderTaskDetailEntity
wareOrderTaskDetailService.list(new
QueryWrapperWareOrderTaskDetailEntity().eq(task_id,
taskEntity.getId()).eq(lock_status,
解锁库存unLockStock(orderTaskDetailEntity.getSkuId(),
orderTaskDetailEntity.getWareId(),orderTaskDetailEntity.getSkuNum(),orderTaskDetailEntity.getId());}}五、支付服务
我们使用一对公私钥中的一个密钥来对数据进行加密而使用另一个密钥来进行解
给我们将要发送的数据做上一个唯一签名类似于指纹用来互相验证接收方和发送方的身份
一个数据对应一个唯一的签名如果数据被修改签名也会更改验签就是验证原数据是否被修改过。
Demo测试下载https://docs.open.alipay.com/270/106291/
使用内网穿透只需要安装内网穿透服务商的软件别人即可访问我们的电脑网站
https://dl-cdn.oray.com/hsk/windows/HskDDNS_8.6.0.48614.exe
dependencygroupIdcom.alipay.sdk/groupIdartifactIdalipay-sdk-java/artifactIdversion4.35.37.ALL/version
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCSgX/nTQ0lDS8ObaM5LGZ1hiz18GXnNpqPLhJCym4xOpn35FNPHrPkDGEoMKrZ5LJeA4cZulckD8AtpvBCpeyIkrj/i1WVmSg10hVX67MlVets4UecCHZv2hKAN0/iId76kozdqrd7Csp/YgXPquN9Np0NFotggTrmiBANkvcpTF9SCGrDq/isOoCvClfbvVJjApfLLOel3yECe5K/SZ8puiWILVm1NxEXAqJ8z0ipPZVGrXsT6Bo0pEyCPcEL0SqaC9WT0zdWQzdUknCzZV9W2wKjEXBJG9hqxay5kPaKm9leBatSkDAaDxH/N5g36HRfY7BmklwRZsp17lHinxAgMBAAECggEAfnnfck35WBKFc90a9D0FXlzrZGEV3uzKIIsb46UXFlrzC5HoVkvEWOCiJCjHiIpvbGr8xED43TZgk/IwLC/JxQLM0kVJGWo6fWoSVOIP2YSLNe620APBvaq3BdkFiMJfSYBBg2J7mkIR39SE8Nvu3j3QWmYzSNJbE2spINnwTzNBL1OPaB5h3hSjyI07KaUcOjhTBF0EZl83NlBDsxmQvy0NmuOIWAcIXXvGoIbwkA774J3LhwLVS4W2FpQj4FlxvDlPu24GeNWN7oO66T3Jp9bweO120ObhuKwZQosDGkJq0975zVSJX5QtUWHMM/QDPO8Pk24n2AoPcACQcQKBgQDS6kqDsK8dDBpkmxYopA1gJJATnur0RHFZJb5webOhnEZnePhB1hhhGvKFcrdY2hcYeQiUZkHMsnWItNUe9E9ccp4m6KKG0iV/BQda7zx1zMTTZUMvSbO282Q31YnQu7Yz6BSk4f/U5Qbu61AK53Tv1ejSAgQhXt1Pwq8KD7QKBgQCx0pkqW453tY2o4iPqFGjKYI2yk5bAH5etmOvW51OZ4Slsq/aUJKBVG6fOpRVKkiXulHhrp5csZH0/C7kaj4Hy7TjgUKSWvwlv7i7jgN0dq/bhVJz82yN9pENWvy5J0I8Kt67XH6JDEGWjlV58auifMRSx5mRJNn5pM6qrFlQKBgFyZWm/JV1fv1xVyoLjlXlTvBsbO7kMH/jpgqFwtAk1n/x3VEShJ1kayIbTOjotWSopMvCFJG9tqM0cyxWLatkELXWifAIsNpqRuYWah1FbZD2fukxLNtM0aYyCUUvZeg2cUnIOraWupxbp9e13eMpvdmWMiWXfhM18CRWEwdAoGAUwT0l076EhgUQJwm1JML0jY94eCfpmLbnNJgRe1qysEPrB1s2IslA7cOqC5we0kyRmmwsuoibQpZYwbRG7JmRAk2pZtgzDRSbpxv7a0rDoBLmbXMOU0Hraqw2Bf3v2SMc79/9FWnIvrC4EyBYZZPwGOpsNAZRSdEUQX9qrceUCgYB99OOtFFt1ixzyTCyUj3Fuiw7BsPhdI3nuMSoNTPIDNpzRBp/KFXyv/FNJ2CjTAsX3OR3D6KmEYihqUfrYeb0P5zoybcQLMxbXxKec6F2o6U2iqFIq0MKwHUqsb9X3pj4qE0ZHbFgRtIHnL2/QGV5PFJdmIZIBKZcvB8fW6ztDA;//
支付宝公钥,查看地址https://openhome.alipay.com/platform/keyManage.htm
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyQQceVUChTJGtF/a8SXufhSxDTKporieTq9NO7yDZSpDlAX1zVPT/nf0KWAlxq1TYappWMIYtyrOABhJyn6flNP6vuSBiM5lYsepHvYrtRHqlFiJruEkiaCgEZBKL5aCfBHYj0oqgQn9MpNV/PEH4cBYAVaiI4VX8CBUQfeEGjgN6OkpLULZ3X0JUkmSnVvCNJ1m3PD68IIlbOfEZXJUKCqmZhzprGR5VWswjxAg87cMwvijL4gdkSy/daG62Bz5vApcmmMkuX1k1fMWP4ajZCASVw8HDMSLRhd8We9F97gd8CW0TavzbdRmTS5H4yEgO8F9HRAsbkhV9yu0yQIDAQAB;//
需http://格式的完整路径不能加?id123这类自定义参数必须外网可以正常访问//
支付宝会悄悄的给我们发送一个请求告诉我们支付成功的信息private
需http://格式的完整路径不能加?id123这类自定义参数必须外网可以正常访问//同步通知支付成功一般跳转到成功页private
https://openapi.alipaydev.com/gateway.doprivate
https://openapi.alipaydev.com/gateway.do;public
DefaultAlipayClient(AlipayTemplate.gatewayUrl,
AlipayTemplate.merchant_private_key,
AlipayTemplate.alipay_public_key,
AlipayTemplate.sign_type);//1、根据支付宝的配置生成一个支付客户端AlipayClient
DefaultAlipayClient(gatewayUrl,app_id,
//设置请求参数AlipayTradePagePayRequest
AlipayTradePagePayRequest();alipayRequest.setReturnUrl(return_url);alipayRequest.setNotifyUrl(notify_url);//商户订单号商户网站订单系统中唯一订单号必填String
vo.getOut_trade_no();//付款金额必填String
vo.getTotal_amount();//订单名称必填String
vo.getBody();alipayRequest.setBizContent({\out_trade_no\:\
\product_code\:\FAST_INSTANT_TRADE_PAY\});String
alipayClient.pageExecute(alipayRequest).getBody();//会收到支付宝的响应响应的是一个页面只要浏览器显示这个页面就会自动来到支付宝的收银台页面System.out.println(支付宝的响应result);return
alipayTemplate;AutowiredOrderService
orderService;ResponseBodyGetMapping(/payOrder)public
payOrder(RequestParam(orderSn)String
orderService.getOrderPay(orderSn);try{//
com.atguigu.gulimall.order.vo.PayVo
this.getOrderByOrderSn(orderSn);PayVo
PayVo();payVo.setOut_trade_no(orderSn);//
QueryWrapperOrderItemEntity().eq(order_sn,
orderSn));payVo.setSubject(orderItemEntities.get(0).getSpuName());//
指定后面2位小数payVo.setTotal_amount(orderEntity.getPayAmount().setScale(2).toString());payVo.setBody(orderItemEntities.get(0).getSpuName());
actionhttps://openapi.alipaydev.com/gateway.do?charsetutf-8methodalipay.trade.page.paysignf0ALyx8f46iNFZjfTjpFgSQRrEwbXjMy5LDVKbBVylxvr%2F8V%2FhOyTisGAtu7%2Bpo6RjVOi3NzbX%2FfAHRmjegly52mninirGTBLN5FntlPn4PGXa7Isi0sWGgAvfnb%2BoQ3IiefoN6Pt3BY7QdXywoE2BHfoz8bXkV%2F%2BfjZFjhi2W5uZeDaoIlS%2BBogF5B%2FbwcCM0AUrtjIoHmrngvzoPFj0exFQ2PP2FE4xrqJyfZxoEh7tcaUzDa37u4KgG7%2BU4luio9CZryuS29jLU%2B4rKOWE7LKKw2L5v1GvIBC6sGlpyNs%2Bbxj9LCGMaa5363EjecgudDHOdm2cVirvxb2u4wUnw%3D%3Dreturn_urlhttp%3A%2F%2Fmember.gulimall.com%2FmemberOrder.htmlnotify_urlhttp%3A%2F%2F63i857228t.goho.co%2Fpayed%2Fnotifyversion1.0app_id2021000122615169sign_typeRSA2timestamp2023-02-0520%3A31%3A56alipay_sdkalipay-sdk-java-4.35.37.ALLformatjson
value{quot;out_trade_noquot;:quot;202302052031434361622211365567483906quot;,quot;total_amountquot;:quot;104484.00quot;,quot;subjectquot;:quot;华为Mate30
Proquot;,quot;bodyquot;:quot;华为Mate30
Proquot;,quot;product_codequot;:quot;FAST_INSTANT_TRADE_PAYquot;}
scriptdocument.forms[0].submit();/script修改
alipayTemplate;AutowiredOrderService
MediaType.TEXT_HTML_VALUE)public
orderService.getOrderPay(orderSn);//
alipayTemplate.pay(payVo);return
{GetMapping(/memberOrder.html)public
需http://格式的完整路径不能加?id123这类自定义参数必须外网可以正常访问//
http://63i857228t.goho.co/alipay.trade.page.pay-JAVA-UTF-8/notify_url.jspprivate
https://4287b772c5.imdo.co/payed/notify;2、修改
NginxHOST请求头不匹配找不到对应的服务网因此手动精确匹配如果是
http://gulimall;}3、通过外网访问订单服务POST
请求https://4287b772c5.imdo.co/payed/notify
LoginUserInterceptor()).addPathPatterns(/**).excludePathPatterns(/order/order/status/**).excludePathPatterns(/payed/notify);
alipayTemplate;PostMapping(/payed/notify)public
String(valueStr.getBytes(ISO-8859-1),
AlipaySignature.rsaCheckV1(params,
alipayTemplate.getAlipay_public_key(),alipayTemplate.getCharset(),
alipayTemplate.getSign_type());
{System.out.println(签名验证成功...);//去修改订单状态String
orderService.handlePayResult(vo);return
{System.out.println(签名验证失败...);return
PaymentInfoEntity();paymentInfo.setOrderSn(vo.getOut_trade_no());paymentInfo.setAlipayTradeNo(vo.getTrade_no());paymentInfo.setTotalAmount(new
BigDecimal(vo.getBuyer_pay_amount()));paymentInfo.setSubject(vo.getBody());paymentInfo.setPaymentStatus(vo.getTrade_status());paymentInfo.setCreateTime(new
Date());paymentInfo.setCallbackTime(vo.getNotify_time());//
添加到数据库中this.paymentInfoService.save(paymentInfo);//
(tradeStatus.equals(TRADE_SUCCESS)
tradeStatus.equals(TRADE_FINISHED))
//获取订单号this.updateOrderStatus(orderSn,OrderStatusEnum.PAYED.getCode());}return
{this.baseMapper.updateOrderStatus(orderSn,status);}mapper
订单在支付页不支付一直刷新订单过期了才支付订单状态改为已支付了但是库
http://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html
出现在日和周几的位置为了防止日和周冲突在周和日上如果要写通配符使
spring.task.scheduling.pool.size5
{e.printStackTrace();}log.info(hello);}
上架最近三天的所有秒杀商品、上架流程1、查询最近三天的所有秒杀场次
GetMapping(/findLatest3DaysSessions)
seckillSessionService.findLatest3DaysSessions();return
QueryWrapperSeckillSessionEntity().between(start_time,startTime(),endTime()));}//
dateTime.format(DateTimeFormatter.ofPattern(yyyy-MM-dd
dateTime.format(DateTimeFormatter.ofPattern(yyyy-MM-dd
couponFeignService;Autowiredprivate
productFeignService;Autowiredprivate
couponFeignService.findLatest3DaysSessions();if
TypeReferenceListSeckillSessionsWithSkus()
中this.saveSessionsInfos(sessionsWithSkus);//
中this.saveSessionsSkusInfos(sessionsWithSkus);}else
{System.out.println(没有秒杀场次);}}}/**
sessionsWithSkus){sessionsWithSkus.stream().forEach(item
item.getStartTime().getTime();long
item.getEndTime().getTime();String
item.getRelationSkus().stream().map(sku
sku.getSkuId().toString()).collect(Collectors.toList());redisTemplate.opsForList().leftPushAll(key,skuIds);});}/**
sessionsWithSkus){sessionsWithSkus.stream().forEach(item
准备hash操作BoundHashOperationsString,
redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);item.getRelationSkus().stream().forEach(relationSku
将商品信息保存到redis中SeckillSkuRedisTo
1、保存秒杀商品的秒杀信息BeanUtils.copyProperties(relationSku,seckillSkuRedisTo);//
productFeignService.info(relationSku.getSkuId());if
{});seckillSkuRedisTo.setSkuInfoVo(skuInfo);}//
3、保存秒杀的开始、结束时间seckillSkuRedisTo.setStartTime(item.getStartTime().getTime());seckillSkuRedisTo.setEndTime(item.getEndTime().getTime());//
UUID.randomUUID().toString().replace(-,
);seckillSkuRedisTo.setRandomCode(token);//
中ops.put(relationSku.getSkuId().toString(),
JSON.toJSONString(seckillSkuRedisTo));//
当大量请求秒杀时不可能实时去查询数据库这样会给数据库造成很大的压力//
通过将redis中设置信号量来限制秒杀商品的数量RSemaphore
redissonClient.getSemaphore(SEMAPHORE_CACHE_PREFIX
将秒杀的数量作为信号量semaphore.trySetPermits(relationSku.getSeckillCount().intValue());});});}
SeckillSessionsWithSkus保存秒杀的场次信息以及每场包含的skuId
如果定时任务在分布式的情况下可能会造成商品重复上架等问题。
因此在上架商品时使用分布式锁解决保证同一时间只有一个服务执行定时任务
seckillService;Autowiredprivate
redissonClient.getLock(UPLOAD_LOCK);lock.lock(10,
{log.info(准备上架商品...);seckillService.UploadSeckillSkuLatest3Days();log.info(上架成功...);}
couponFeignService;Autowiredprivate
productFeignService;Autowiredprivate
couponFeignService.findLatest3DaysSessions();if
TypeReferenceListSeckillSessionsWithSkus()
中this.saveSessionsInfos(sessionsWithSkus);//
中this.saveSessionsSkusInfos(sessionsWithSkus);}
{System.out.println(没有秒杀场次);}}}/**
saveSessionsInfos(ListSeckillSessionsWithSkus
{sessionsWithSkus.stream().forEach(item
item.getStartTime().getTime();long
item.getEndTime().getTime();String
item.getRelationSkus().stream().map(sku
sku.getSkuId().toString()).collect(Collectors.toList());redisTemplate.opsForList().leftPushAll(key,
saveSessionsSkusInfos(ListSeckillSessionsWithSkus
{sessionsWithSkus.stream().forEach(item
准备hash操作BoundHashOperationsString,
redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);item.getRelationSkus().stream().forEach(relationSku
UUID.randomUUID().toString().replace(-,
(!ops.hasKey(relationSku.getPromotionSessionId()_relationSku.getSkuId().toString())){//
将商品信息保存到redis中SeckillSkuRedisTo
1、保存秒杀商品的秒杀信息BeanUtils.copyProperties(relationSku,
productFeignService.info(relationSku.getSkuId());if
{});seckillSkuRedisTo.setSkuInfoVo(skuInfo);}//
3、保存秒杀的开始、结束时间seckillSkuRedisTo.setStartTime(item.getStartTime().getTime());seckillSkuRedisTo.setEndTime(item.getEndTime().getTime());//
4、保存秒杀商品的随机码seckillSkuRedisTo.setRandomCode(token);//
中。
keysessionId_skuIdops.put(relationSku.getPromotionSessionId()_relationSku.getSkuId().toString(),
JSON.toJSONString(seckillSkuRedisTo));//
当大量请求秒杀时不可能实时去查询数据库这样会给数据库造成很大的压力//
通过将redis中设置信号量来限制秒杀商品的数量RSemaphore
redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE
将秒杀的数量作为信号量semaphore.trySetPermits(relationSku.getSeckillCount().intValue());}});});}
*/GetMapping(/getCurrentSeckillSkus)public
getCurrentSeckillSkus(){ListSeckillSkuRedisTo
seckillService.getCurrentSeckillSkus();return
redisTemplate.keys(SESSION_CACHE_PREFIX
seckill:sessions:1675844252000_1675958400000
key.replace(SESSION_CACHE_PREFIX,
redisTemplate.opsForList().range(key,
k1:Mapk2,k3BoundHashOperationsString,
redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);//
由于场次信息的value与商品信息的key是一致的.可以通过场次信息的value作为商品信息的key取出valueListString
seckillSkuRedisTo.setRandomCode();
seckillSkuRedisTo;}).collect(Collectors.toList());return
从数据库封装到java中的实体类时时间可能会有差距数据库默认采用UTC建议改成东八区
redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);//
hashOps.get(key);SeckillSkuRedisTo
如果该商品正在秒杀就将随机码发送给前端页面。
如果没有就无需发送Long
seckillSkuRedisTo.getStartTime();Long
seckillSkuRedisTo.getEndTime();long
{seckillSkuRedisTo.setRandomCode();}return
*/GetMapping(/sku/seckill/{skuId})public
seckillService.getSkuSeckillInfo(skuId);return
R.ok().setData(seckillSkuRedisTo);}
*/GetMapping(/sku/seckill/{skuId})public
seckillFeignService.getSkuSeckillInfo(skuId);if
{});skuItemVo.setSeckillInfo(data);}},
request.getSession().getAttribute(AuthServerConstant.LOGIN_USER);if
{request.getSession().setAttribute(msg,请先登录);//
没有进行登录跳转到登录界面登录response.sendRedirect(http://auth.gulimall.com/login.html);return
false;}else{threadLocal.set(memberRespVo);return
addInterceptors(InterceptorRegistry
LoginUserInterceptor()).addPathPatterns(/kill);}
秒杀到创建订单没有操作数据库只是向MQ发送一条消息数据库的压力是非常轻松的。
http://seckill.gulimall.com/kill?killId2-11keyf7bde6d931e34b7f8677b3395157d426num1*
seckillService.kill(killId,key,num);return
R.ok().setData(orderSn);}SeckillServiceImpl
LoginUserInterceptor.threadLocal.get();BoundHashOperationsString,
redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);String
skuRedisTo.getPromotionSessionId().toString()
skuRedisTo.getSkuId().toString();//
skuRedisTo.getSkuId().toString();//
redisTemplate.opsForValue().setIfAbsent(redisKey,
redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE
{e.printStackTrace();}}}}}}return
Queue(order.seckill.order.queue,
orderSecKillOrrderQueueBinding()
Binding(order.seckill.order.queue,Binding.DestinationType.QUEUE,order-event-exchange,order.seckill.order,null);return
LoginUserInterceptor.threadLocal.get();BoundHashOperationsString,
redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);String
skuRedisTo.getPromotionSessionId().toString()
skuRedisTo.getSkuId().toString();//
skuRedisTo.getSkuId().toString();//
redisTemplate.opsForValue().setIfAbsent(redisKey,
redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE
IdWorker.getTimeId();SeckillOrderTo
SeckillOrderTo();orderTo.setOrderSn(timeId);orderTo.setMemberId(respVo.getId());orderTo.setNum(num);orderTo.setPromotionSessionId(skuRedisTo.getPromotionSessionId());orderTo.setSkuId(skuRedisTo.getSkuId());orderTo.setSeckillPrice(skuRedisTo.getSeckillPrice());//
向MQ发送消息rabbitTemplate.convertAndSend(order-event-exchange,order.seckill.order,orderTo);long
System.currentTimeMillis();log.info(耗时...
{e.printStackTrace();}}}}}}return
orderService;RabbitHandlerpublic
{orderService.createSeckillOrder(orderTo);channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);}
{channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);}}}5、
createSeckillOrder(SeckillOrderTo
OrderEntity();orderEntity.setOrderSn(orderTo.getOrderSn());orderEntity.setMemberId(orderTo.getMemberId());orderEntity.setCreateTime(new
orderTo.getSeckillPrice().multiply(BigDecimal.valueOf(orderTo.getNum()));orderEntity.setPayAmount(totalPrice);orderEntity.setStatus(OrderStatusEnum.CREATE_NEW.getCode());//保存订单this.save(orderEntity);//保存订单项信息OrderItemEntity
OrderItemEntity();orderItem.setOrderSn(orderTo.getOrderSn());orderItem.setRealAmount(totalPrice);orderItem.setSkuQuantity(orderTo.getNum());//保存订单项数据orderItemService.save(orderItem);}七、Sentinel
服务熔断会导致服务降级,但是和服务降级的区别就是服务熔断会恢复调用链路。
在互联网系统中当下游服务因访问压力过大而响应变慢或失败上游服务为了保护系统整体的可用性可以暂时切断对下游服务的调用。
一旦下游服务C因某些原因变得不可用积压了大量请求服务B的请求线程也随之阻塞。
线程资源逐渐耗尽使得服务B也变得不可用。
紧接着服务
整个网站处于流量高峰期服务器压力剧增根据当前业务情况及流量对一些服务和
页面进行有策略的降级[停止服务所有的调用直接返回降级数据]。
以此缓解服务器资源的
的压力以保证核心业务的正常运行同时也保持了客户和大部分客户的得到正确的相应。
对打入服务的请求流量进行控制使服务能够承担不超过自己能力的流量压力
--dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-sentinel/artifactId/dependency2、下载控制台Releases
3、配置服务与控制台的连接地址以及每个服务与控制台交互数据的端口
spring.cloud.sentinel.transport.dashboardlocalhost:8333
spring.cloud.sentinel.transport.port87193、在控制台中配置规则
management.security.enabledfalse。
暴露的
management.endpoints.web.exposure.include*。
暴露的
spring-boot-starter-actuator而不是在
!--sentinel控制台实时监控信息审计--dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-actuator/artifactId/dependency2、暴露端点
management.endpoints.web.exposure.include*4、自定义sentinel的返回信息
WebCallbackManager.setUrlBlockHandler(new
httpServletResponse.setCharacterEncoding(UTF-8);//
httpServletResponse.setContentType(application/json);//
R.error(BizCodeEnum.TOO_MANY_REQUEST.getCode(),
BizCodeEnum.TOO_MANY_REQUEST.getMessage());//
httpServletResponse.getWriter().write(JSON.toJSONString(error));////
urlBlockHandler(){UrlBlockHandler
响应编码httpServletResponse.setCharacterEncoding(UTF-8);//
数据格式httpServletResponse.setContentType(application/json);R
R.error(BizCodeEnum.TOO_MANY_REQUEST.getCode(),
BizCodeEnum.TOO_MANY_REQUEST.getMessage());//
响应到页面httpServletResponse.getWriter().write(JSON.toJSONString(error));}};WebCallbackManager.setUrlBlockHandler(urlBlockHandler);return
需要rabbitTemplate,而rabbitTemplate又要使用到rabbitConfig
Jackson2JsonMessageConverter();}/**
spring.rabbitmq.publisher-confirmstrue*
spring.rabbitmq.publisher-returnstrue*
spring.rabbitmq.template.mandatorytrue*
{rabbitTemplate.setConfirmCallback(new
RabbitTemplate.ConfirmCallback()
{System.out.println(Broker接收消息成功,
{System.out.println(Broker接收消息失败,
cause);}}});rabbitTemplate.setReturnCallback(new
RabbitTemplate.ReturnCallback()
MessageConverter而MessageConverter由依赖于
这里直接注入MessageConverter会有循环依赖的问题*
Jackson2JsonMessageConverter();//
spring.rabbitmq.publisher-confirmstrue*
spring.rabbitmq.publisher-returnstrue*
spring.rabbitmq.template.mandatorytrue*
{rabbitTemplate.setConfirmCallback(new
RabbitTemplate.ConfirmCallback()
{System.out.println(Broker接收消息成功,
{System.out.println(Broker接收消息失败,
cause);}}});rabbitTemplate.setReturnCallback(new
RabbitTemplate.ReturnCallback()
Jackson2JsonMessageConverter();}
现代微服务架构都是分布式的由非常多的服务组成。
不同服务之间相互调用组成复杂的调用链路。
复杂链路上的某一环不稳定就可能会层层级联最终导致整个链路都不可用。
因此我们需要对不稳定的弱依赖服务调用进行熔断降级暂时切断不稳定调用避免局部不稳定因素导致整体的雪崩。
熔断降级作为保护自身的手段通常在**客户端调用端**进行配置。
spring-cloud-starter-openfeign、
spring-cloud-starter-alibaba-sentinel
feign.sentinel.enabledtrue3、简单实例
*/GetMapping(/sku/seckill/{skuId})public
R.error(BizCodeEnum.TOO_MANY_REQUEST.getCode(),
BizCodeEnum.VALID_EXCEPTION.getMessage());}
资源名可使用任意有业务语义的字符串比如方法名、接口名或其它可唯一标识的字符串。
!--sentinel与Gateway整合--dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-alibaba-sentinel-gateway/artifactIdversion2.1.0.RELEASE/version/dependency9、自定义网关流控回调
{GatewayCallbackManager.setBlockHandler(new
{//网关限流了请求就会调用此回调Overridepublic
handleRequest(ServerWebExchange
R.error(BizCodeEnum.TOO_MANY_REQUEST.getCode(),
BizCodeEnum.TOO_MANY_REQUEST.getMessage());String
JSON.toJSONString(error);MonoServerResponse
ServerResponse.ok().body(Mono.just(errorJson),
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback