买卖宝

本文详细介绍了电商平台的微服务架构,包括商品、订单、支付、商家、文件上传、购物车等多个微服务模块的设计与实现,涉及数据存储、权限管理、库存同步、支付回调等关键流程。还涵盖了秒杀系统的实现、数据同步工具Canal的使用,以及商品详情页面的静态化处理,旨在提供一个完整的电商系统架构参考。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

项目

深圳市南山区国家工程实验室大楼A座17楼
法定代表人:周发展

redis数据存放

广告:广告列表json字符串存入redis

购物车:Redis中的数据可以采用Hash类型

订单:用户订单数据以hash格式存入到Redis(秒杀业务/高并发)

redis延时队列实现

微服务

商品

表格:1.品牌表 2.相册表 3.模板表 4.规格表 5.参数表 6.商品分类表 7.SPU公共表 8.SKU商品表

相册

相册是用于存储图片的管理单元,我们通常会将商品的图片先上传到相册中,在添加商品时可以直接在相册中选择,获取相册中的图片地址,保存到商品表中

image 相册封面 VARCHAR(100) 图片地址

image_items 图片列表 TEXT 多个图片 json数据

模板/规格/参数

模板与规格是一对多关系 ,模板与参数是一对多关系

  • 发布新商品
    • 在售中/售罄/已下架
    • 操作 编辑/下架/分享商品/置顶商品
    • 商品列表
    • 商品分类
    • 商品品牌管理
    • 商品类型管理
    • 商品的规格参数
新增和修改商品
商品审核与上下架

商品新增后,审核状态为0(未审核),默认为下架状态。

审核商品,需要校验是否是被删除的商品,如果未删除则修改审核状态为1,并自动上架

下架商品,需要校验是否是被删除的商品,如果未删除则修改上架状态为0

上架商品,需要审核通过的商品

实现思路

(1)按照ID查询SPU信息

(2)判断修改审核、上架和下架状态

(3)保存SPU

删除与还原商品

商品列表中的删除商品功能,并非真正的删除,而是将删除标记的字段设置为1,

在回收站中有恢复商品的功能,将删除标记的字段设置为0

在回收站中有删除商品的功能,是真正的物理删除。

实现思路

逻辑删除商品,修改spu表is_delete字段为1

商品回收站显示spu表is_delete字段为1的记录

回收商品,修改spu表is_delete字段为0

商品列表
img
订单

表格:1.订单表2.订单明细表3.用户信息表

业务逻辑

  • 购物车增加数量超过库存时提示
  • 点击购买后跳转结算页( 收获人消息 支付方式 发货清单 发票信息 )
  • 选择银行卡支付 微信支付 支付宝支付(下一步直接跳出二维码)
  • 点击结算页的时候,会立即创建订单数据,先添加订单,再添加订单明细据,清除Redis缓存购物车数据。
  • 下单之后,应该调用商品微服务,将下单的商品库存减少,销量增加(形成订单就会减少库存 不管支没支付)
    • 控制库存数量>=销售数量,每次减少数量的时候,加个条件判断。where num>=#{num}即可。
    • UPDATE tb_sku SET num=num-#{num}, WHERE id=#{skuId} AND num>=#{num} 利用的是数据库自带的行级锁
  • 用户下单之后,订单数据会存入到MySQL中,同时会将订单对应的支付日志存入到Redis,以List+Hash的方式存储。
  • 用户下单后,进入支付页面,支付页面调用支付系统,从微信支付获取二维码数据,并在页面生成支付二维码。
  • 用户扫码支付后,微信支付服务器会通调用前预留的回调地址,并携带支付状态信息。
  • 支付系统接到支付状态信息后,将支付状态信息发送给MQ
  • 订单系统监听MQ中的消息获取支付状态,并根据支付状态修改订单状态
  • 为了防止网络问题导致notifyurl没有接到对应数据,定时任务定时获取Redis中队列数据去微信支付接口查询状态,并定时更新对应状态。

业务流程

创建订单

1.生成订单id

2.获取用户名

3.设置购买用户

4.查询出用户的所有购物车

5.统计计算(总金额/实际支付金额/总数量)

6.其他数据完善(买家昵称、是否评论、创建时间、订单号、用户id、订单id、订单创建时间、订单状态(初始状态:1,未付款)) 保存订单对象

7.添加订单

  • 校验订单

    • 校验订单是否存在
    • 校验订单中的商品是否存在
    • 校验下单用户是否存在
    • 校验商品单价是否合法
    • 校验订单商品数量是否合法
    • 校验订单通过
  • 生成预订单

    • 设置订单状态为不可见
    • 设置订单ID
    • 核算运费是否正确
    • 核算订单总价格是否正确
    • 是否使用优惠券
      • 判断优惠券是否合理
    • 设置订单支付总价
    • 设置订单添加时间
    • 保存预订单到数据库

    try

  • 扣减库存

    调用商品服务完成扣减库存

    • 判断库存是否不足
    • 减库存
    • 记录库存操作记录
  • 扣减优惠券

    • 判断用户是否使用优惠券
    • 更改优惠券状态
      • 判断请求参数是否合法
      • 更新优惠券状态为已使用
    • 判断优惠券状态执行结果
  • 增加用户积分

  • 确认订单(更改订单状态)

  • 添加订单明细(数据库里添加)

    • 为订单详情中的数据添加订单号,因为一个订单下有多个订单项
  • 线上支付,记录订单

    将支付记录存入到Reids namespace key value

  • 清除Redis缓存购物车数据

主要接口有:

创建订单

查询订单

  • 发货状态 待发货/即将延迟发货/已延迟发货/已发货未签收/即将揽件超时
  • 按钮 查询/批量导出/查看已生成报表

更新订单状态

删除订单

根据订单号生成微信付款链接

根据订单号查询支付状态

补:定时处理订单状态(未完成)

1.每次下单,都将订单存入到Reids List队列中 
2.定时每5秒检查一次Redis 队列中是否有数据,如果有,则再去查询微信服务器支付状态
3.如果已支付,则修改订单状态
4.如果没有支付,是等待支付,则再将订单存入到Redis队列中,等会再次检查
5.如果是支付失败,直接删除订单信息并修改订单状态
	延时队列实现订单关闭回滚库存:

在这里插å¥å›¾ç‰‡æè¿°

1.创建一个过期队列  Queue1
2.接收消息的队列    Queue2
3.中转交换机
4.监听Queue2
	1)SeckillStatus->检查Redis中是否有订单信息
	2)如果有订单信息,调用删除订单回滚库存->[需要先关闭微信支付]
	3)如果关闭订单时,用于已支付,修改订单状态即可
	4)如果关闭订单时,发生了别的错误,记录日志,人工处理
商家

商家服务主要功能是记录商家信息 然后赋予商家权限

表格:1.商家信息表 2.待审核表 3.审核通过表4.收件地址表 5.发件地址表 6.中间表

  • 商家管理
    • 商家分为自营商家和第三方商家
    • 增删改查
  • 商家审核
    • 1.商家信息表–待审核表–审核通过表 2.三张表共用一个id 3.信息表和待审核表分开主要为了把管理员和商家对信息的修改记录分开
    • 营业执照期限 营业执照经营范围 公司地址 商家申请的销售类别 公司法人的信息 公司联系方式
  • 物流工具
    • 发货地址管理(操作:复制 编辑 删除 使用情况 )
    • 退货地址管理
      • 商家~发货收货仓库地址信息通过中间表关联 1对多关系
到货通知

rocketmq

其他服务发消息过来,到货通知服务来处理消息,再发送消息到通知服务 后面的要和云服务对接 后面要用发送通知,这个发消息出去 谁用谁接收

没有货了,然后那个到货提醒功能

支付微服务
微信支付模式介绍
模式一(商店固定二维码)

在这里插入图片描述
业务流程说明:

1.商户后台系统根据微信支付规定格式生成二维码(规则见下文),展示给用户扫码。
2.用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。
3.微信支付系统收到客户端请求,发起对商户后台系统支付回调URL的调用。调用请求将带productid和用户的openid等参数,并要求商户系统返回交数据包,详细请见"本节3.1回调数据输入参数"
4.商户后台系统收到微信支付系统的回调请求,根据productid生成商户系统的订单。
5.商户系统调用微信支付【统一下单API】请求下单,获取交易会话标识(prepay_id)
6.微信支付系统根据商户系统的请求生成预支付交易,并返回交易会话标识(prepay_id)。
7.商户后台系统得到交易会话标识prepay_id(2小时内有效)。
8.商户后台系统将prepay_id返回给微信支付系统。返回数据见"本节3.2回调数据输出参数"
9.微信支付系统根据交易会话标识,发起用户端授权支付流程。
10.用户在微信客户端输入密码,确认支付后,微信客户端提交支付授权。
11.微信支付系统验证后扣款,完成支付交易。
12.微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。
13.微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。
14.未收到支付通知的情况,商户后台系统调用【查询订单API】。
15.商户确认订单已支付后给用户发货。
模式二(在线支付二维码)

在这里插入图片描述
业务流程说明:

1.商户后台系统根据用户选购的商品生成订单。
2.用户确认支付后调用微信支付【统一下单API】生成预支付交易;
3.微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url。
4.商户后台系统根据返回的code_url生成二维码。
5.用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。
6.微信支付系统收到客户端请求,验证链接有效性后发起用户支付,要求用户授权。
7.用户在微信客户端输入密码,确认支付后,微信客户端提交授权。
8.微信支付系统根据用户授权完成支付交易。
9.微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。
10.微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。
11.未收到支付通知的情况,商户后台系统调用【查询订单API】。
12.商户确认订单已支付后给用户发货。
微信支付
微信支付二维码生成(pay/unifiedorder)

我们主要会用到微信支付SDK的以下功能:

我们主要会用到微信支付SDK的以下功能:

获取随机字符串
WXPayUtil.generateNonceStr()
MAP转换为XML字符串(自动添加签名)
WXPayUtil.generateSignedXml(param, partnerkey)
XML字符串转换为MAP
WXPayUtil.xmlToMap(result)

步骤

  • 先将需要的订单数据传入map 再将map转成xml 带着xml调用微信服务端
  • 微信服务端会传回xml(生成二维码的数据) 转换成map后获得code_url
  • 传回前端 通过qrious生成二维码
检测支付状态(pay/orderquery)

支付状态微信会发送三次

在controller方法中轮询调用查询订单(间隔3秒),当返回状态为success时,我们会在controller方法返回结果。前端代码收到结果后跳转到成功页面

代码跟上面一样 就是换了网址接口

支付信息回调

生成二维码时里面添加的参数标注有回调接口方法名称

回调信息是输入流数据 需要转化成输出流数据 在转换成xml数据 最后转换成map再获取信息

MQ处理支付回调状态

支付系统收到回调消息 发送给消息队列 订单系统监听到后修改订单状态

在这里插å¥å›¾ç‰‡æè¿°

假如两个服务 秒杀 订单微服务系统都要对支付系统监听 可以建立两个队列监听 把队列名字通过微信支付的attach:附加数据 把提前需要传递的数据保存(队列名字 商品名字)

支付后订单状态操作

流程回顾:

1.用户下单之后,订单数据会存入到MySQL中,同时会将订单对应的支付日志存入到Redis,以List+Hash的方式存储。
2.用户下单后,进入支付页面,支付页面调用支付系统,从微信支付获取二维码数据,并在页面生成支付二维码。
3.用户扫码支付后,微信支付服务器会通调用前预留的回调地址,并携带支付状态信息。
4.支付系统接到支付状态信息后,将支付状态信息发送给RabbitMQ
5.订单系统监听RabbitMQ中的消息获取支付状态,并根据支付状态修改订单状态
6.为了防止网络问题导致notifyurl没有接到对应数据,定时任务定时获取Redis中队列数据去微信支付接口查询状态,并定时更新对应状态。
  • 支付成功

    微信支付的交易流水号/支付时间/支付状态 修改

    删除redis订单记录

  • 支付失败/超时

    删除用户订单 同时回滚库存

文件上传微服务

使用分布式文件系统FastDFS实现图片上传。

FastDFS两个主要的角色:Tracker Server 和 Storage Server 。

  • Tracker server 作用是负载均衡和调度 ,被称为追踪服务器或调度服务器。Storage server 作用是文件存储
  • Group:文件组,多台Storage Server的集群。上传一个文件到同组内的一台机器上后,FastDFS会将该文件即时同步到同组内的其它所有机器上,起到备份的作用。不同组的服务器,保存的数据不同,而且相互独立,不进行通信。
  • Tracker Cluster:跟踪服务器的集群,有一组Tracker Server(跟踪服务器)组成。
  • Storage Cluster :存储集群,有多个Group组成。

上传流程

img

1.Client通过Tracker server查找可用的Storage server。
2.Tracker server向Client返回一台可用的Storage server的IP地址和端口号。
3.Client直接通过Tracker server返回的IP地址和端口与其中一台Storage server建立连接并进行文件上传。
4.上传完成,Storage server返回Client一个文件ID,文件上传结束。

下载流程

img

1.Client通过Tracker server查找要下载文件所在的的Storage server。
2.Tracker server向Client返回包含指定文件的某个Storage server的IP地址和端口号。
3.Client直接通过Tracker server返回的IP地址和端口与其中一台Storage server建立连接并指定要下载文件。
4.下载文件成功。

 private static TrackerClient client = null;
    private static String httpPort = null;
     /***
     * 初始化fastdfs服务
     */
    static {
        try {
            //获取tracker的配置文件fdfs_client.conf的位置
            String filePath = new ClassPathResource("fdfs_client.conf").getPath();
            //加载tracker配置信息
            ClientGlobal.init(filePath);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

          //所有的跟踪服务器信息在TrackerClient对象中。
          //知道所有的跟踪服务器组
					//创建TrackerClient客户端对象
          TrackerClient trackerClient = new TrackerClient();
					//从跟踪服务器组拿到一个具体的跟踪服务器
          //通过TrackerClient获取TrackerServer对象
          TrackerServer trackerServer = trackerClient.getConnection();
					//即使传入的存储服务器对象为null也会自动分配
          //也可以指定具体存储服务器对象 一般为null
          //通过TrackerServer创建StorageClient
          StorageClient storageClient = new StorageClient(trackerServer,null);



        /****
         * 文件上传
         * @param file : 要上传的文件信息封装->FastDFSFile
         * @return String[]
         *          1:文件上传所存储的组名
         *          2:文件存储路径
         *group1/M00/00/12/wKgU_F9yra6AUyizAACoxG3uRvc315.jpg  group1-存储服务器
         
         * 获取文件信息
         * @param groupName:组名    @param remoteFileName:文件存储完整名
         * storageClient.get_file_info(groupName,remoteFileName);
         */
            //执行文件上传
						//bytes--上传的数组集合的名字
						//extName--获取文件后缀名
						//meta_list--元数据列表,(类似写的注释)上传文件习惯把上传日期,文件大小,文件真实名
            String[] uploadResults = storageClient.upload_file(bytes, extName, meta_list);

        /***
         * 文件下载
         */
        //通过StorageClient下载文件
        byte[] fileByte = storageClient.download_file(groupName, remoteFileName);
        //将字节数组转换成字节输入流
        return new ByteArrayInputStream(fileByte);

        /***
         * 文件删除实现
         */
        //表示删除文件,0成功,2失败
        int i = storageClient.delete_file(groupName,remoteFileName);

        /***
         * 生成HttpUrl   用于图片展示等
         * 得到http服务端口
         * httpPort = ClientGlobal.getG_tracker_http_port() + "";
         */
				String url ="http://"+trackerServer.getInetSocketAddress().getHostString()+":"+httpPort+"/"+path;

        /***
         * 获取组信息
         */
        //通过trackerClient获取Storage组信息
         return trackerClient.getStoreStorage(trackerServer,groupName);

        /***
         * 根据文件组名和文件存储路径获取Storage服务的IP、端口信息
         */
        //获取服务信息
        return trackerClient.getFetchStorages(trackerServer,groupName,remoteFileName);
购物车微服务

流程图

img

这幅图主要描述了两个功能:新增商品到购物车、查询购物车。

新增商品:

判断是否登录

是:则添加商品到后台Redis中

	1.获取SKU

	2.根据spu_id获取spu

	3.将SPU转换成OrderItem

	3.购物车数据存入到Redis

否:则添加商品到本地的Localstorage

无论哪种新增,完成后都需要查询购物车列表:

判断是否登录

否:直接查询localstorage中数据并展示

是:已登录,则需要先看本地是否有数据,

	有:需要提交到后台添加到redis,合并数据,而后查询

	否:直接去后台查询redis,而后返回

注意

1.用户添加购物车,只需要将要加入购物车的商品存入到Redis中即可。可以采用Hash类型。

选Hash类型可以将用户的用户名作为namespace的一部分,将指定商品加入购物车,则往对应的namespace中增加一个key和value,key是商品ID,value是加入购物车的商品详情

2.等用户下单后才将数据从Redis取出存入到MySQL中。

3.注意 : 用户将商品加入购物车,无论数量是正负,都会执行添加购物车,如果数量如果<=0,应该移除该商品的。

4.数据精度丢失问题

SkuId是Long类型,在页面输出的时候会存在精度丢失问题,我们只需要在OrderItem的SkuId上加上字符串序列化类型就可以了,代码如下:
在这里插入图片描述

5.用户要想将商品加入购物车,必须得先登录授权,登录授权后再经过微服务网关,微服务网关需要过滤判断用户请求是否存在令牌,如果存在令牌,才能再次访问微服务,此时网关会通过过滤器将令牌数据再次存入到头文件中,然后访问模板渲染服务,模板渲染服务再调用订单购物车微服务,此时也需要将令牌数据存入到头文件中,将令牌数据传递给购物车订单微服务,到了购物车订单微服务的时候,此时微服务需要校验令牌数据,如果令牌正确,才能使用购物车功能,并解析令牌数据获取用户信息

功能需求

  • 用户可以在登录状态下将商品添加到购物车
    放入数据库

    • 放入redis(采用)
  • 用户可以在未登录状态下将商品添加到购物车

    • 放入localstorage
  • 用户可以使用购物车一起结算下单

  • 用户可以查询自己的购物车

  • 用户可以在购物车中修改购买商品的数量。

  • 用户可以在购物车中删除商品。

  • 在购物车中展示商品优惠信息

  • 提示购物车商品价格变化

授权中心微服务

结合RSA的鉴权

  • 首先利用RSA生成公钥和私钥。私钥保存在授权中心,公钥保存在Zuul和各个微服务
  • 用户请求登录
    授权中心校验,通过后用私钥对JWT进行签名加密
  • 返回jwt给用户
  • 用户携带JWT访问
  • Zuul直接通过公钥解密JWT,进行验证,验证通过则放行
  • 请求到达微服务,微服务直接用公钥解析JWT,获取用户信息,无需访问授权中心

授权中心的主要职责

用户鉴权:接收用户的登录请求,通过用户中心的接口进行校验,通过后生成JWT。使用私钥生成JWT并返回
服务鉴权:微服务间的调用不经过Zuul,会有风险,需要鉴权中心进行认证。原理与用户鉴权类似,但逻辑稍微复杂一些(未实现)。

用户中心微服务

提供的接口:

  • 检查用户名和手机号是否可用
  • 发送短信验证码
  • 用户注册
  • 用户查询
  • 修改用户个人资料
网关微服务

主要功能

身份认证与安全:识别每个资源的验证要求,并拒绝那些与要求不相符的请求。(对jwt鉴权)

动态路由:动态地将请求路由到不同的后端集群。

负载均衡和熔断

评论微服务(新增)

功能需求

1.用户在确认收货后可以对商品进行评价,每个用户对订单中的商品只能发布一次顶级评论,可以追评,也可以回复别人的评论。
2.当用户确认收货后没有进行手动评价时,3天后自动五星好评

表结构设计

parent和isparent字段是用来实现评论嵌套的。

实现

使用MongoDB存储评论,基本的CRUD。

配置中心微服务

需求

在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在Spring Cloud中,有分布式配置中心组件spring cloudconfig ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。

使用SpringCloudBus来实现配置的自动更新。

组成结构

在spring cloud config 组件中,分两个角色,一是config server,二是config client。

Config Server是一个可横向扩展、集中式的配置服务器,它用于集中管理应用程序各个环境下的配置,默认使用Git存储配置文件内容,也可以使用SVN存储,或者是本地文件存存储

Config Client是Config Server的客户端,用于操作存储在Config Server中的配置内容。微服务在启动时会请求Config Server获取配置文件的内容,请求到后再启动容器

实现

创建配置中心,对Config Server进行配置,然后在其它微服务中配置Config Client。最后使用Github上的Webhooks进行配置的动态刷新,所以还要使用内网穿透工具,同时要在配置中心中添加过滤器,因为使用Webhooks提交请求时会加上一段Payload,而本地是无法解析这个Payload的,所以要将它过滤掉。

页面详情微服务

商品详情浏览量比较大,并发高,所以单独开启一个微服务用来展示商品详情,并且对其进行静态化处理,保存为静态html文件。在用户访问商品详情页面时,让nginx对商品请求进行监听,指向本地静态页面,如果本地没找到,才反向代理到页面详情微服务端口。

Eureak注册中心
搜索微服务

主要是对Elasticsearch的应用,将所有商品数据封装好后添加到Elasticsearch的索引库中,然后进行搜索过滤,查询相应的商品信息。

秒杀微服务(新增)

主要接口有:

添加参加秒杀的商品
查询秒杀商品
创建秒杀地址
验证秒杀地址

多线程业务
  • 异步实现

    • 开启异步操作,用@EnableAsync在启动类注解
    • 对应的异步方法上添加注解@Async 即表示该方法异步执行
  • 商品压入Redis缓存(并发超卖问题解决)

    解决超卖问题,可以利用Redis队列实现,给每件商品创建一个独立的商品个数队列,例如:A商品有2个,A商品的ID为1001,则可以创建一个队列,key=SeckillGoodsCountList_1001,往该队列中塞2次该商品ID。
    
    每次给用户下单的时候,先从队列中取数据,如果能取到数据,则表明有库存,如果取不到,则表明没有库存,这样就可以防止超卖问题产生了。
    
    在我们队Redis进行操作的时候,很多时候,都是先将数据查询出来,在内存中修改,然后存入到Redis,在并发场景,会出现数据错乱问题,为了控制数量准确,我们单独将商品数量整一个自增键,自增键是线程安全的,所以不担心并发场景的问题。
    
    
    //商品数据队列存储,防止高并发超卖
    Long[] ids = pushIds(seckillGood.getStockCount(), seckillGood.getId()); //获得商品id集合
    //为每个商品创建一个排队队列
    redisTemplate.boundListOps("SeckillGoodsCountList_"+seckillGood.getId()).leftPushAll(ids);
    //自增计数器  商品队列添加商品数量的id
    redisTemplate.boundHashOps("SeckillGoodsCount").increment(seckillGood.getId(),seckillGood.getStockCount());
    
  • 多线程下单(add)

    • 防止秒杀重复排队

      用户每次抢单的时候,一旦排队,我们设置一个自增值,让该值的初始值为1,每次进入抢单的时候,对它进行递增,如果值>1,则表明已经排队,不允许重复排队,如果重复排队,则对外抛出异常,并抛出异常信息100表示已经正在排队。
      
      //递增,判断是否排队
      Long userQueueCount = redisTemplate.boundHashOps("UserQueueCount").increment(username, 1);
      if(userQueueCount>1){
          //100:表示有重复抢单
          throw new RuntimeException(String.valueOf(StatusCode.REPERROR));
      }
      
    • 首先排队信息封装(秒杀用户名/创建时间/秒杀状态/秒杀的商品ID/应付金额/订单号/时间段) 将用户名 时间段 秒杀的商品ID 放入排队信息封装的对象

    • 将秒杀抢单信息存入到Redis中,这里采用List方式存储,List本身是一个队列 redisTemplate.boundListOps("SeckillOrderQueue").leftPush(seckillStatus);

    • 将抢单状态存入到Redis中redisTemplate.boundHashOps("UserQueueStatus").put(username,seckillStatus);

    • 多线程操作/创建订单

      • 从队列中获取排队信息 从队列右方获得上面封入的排队信息

        (SeckillStatus) redisTemplate.boundListOps("SeckillOrderQueue").rightPop();

      • 从队列信息id获得商品排队队列的商品数量(超卖解决)

              //从队列中获取一个商品
              Object sgood = redisTemplate.boundListOps("SeckillGoodsCountList_" + seckillStatus.getGoodsId()).rightPop();
              if(sgood==null){
                  //清理当前用户的排队信息
                  clearQueue(seckillStatus);
                  return;
              }
              
      /***
       * 清理用户排队信息
       * @param seckillStatus
       */
      public void clearQueue(SeckillStatus seckillStatus){
      //清理排队标示
       redisTemplate.boundHashOps("UserQueueCount").delete(seckillStatus.getUsername());
      //清理抢单标示  redisTemplate.boundHashOps("UserQueueStatus").delete(seckillStatus.getUsername());
      }
      
      • 根据id 从redis获取商品数据(如果没有库存,则直接抛出异常)

      • 如果有库存,则创建秒杀商品订单

      • 将秒杀订单存入到Redis中

        redisTemplate.boundHashOps("SeckillOrder").put(username,seckillOrder);

      • 库存减少

              Long surplusCount = redisTemplate.boundHashOps("SeckillGoodsCount").increment(id, -1);//商品数量递减
              goods.setStockCount(surplusCount.intValue());    //根据计数器统计
      //也可以直接获得商品排列队列里的商品数量 不通过自减
      //如果直接用上面获得的商品对象加减数量 会造成商品数据同步库存不精确
      //即没有商品了 但没有及时清除redis里的商品数据  页面显示该商品  下单还是没有数据
      
      • 判断当前商品是否还有库存
        • 如果没有了 将商品数据同步到MySQL中 清空Redis缓存中该商品
        • 如果有库存,则直数据重置到Reids中
      • 抢单成功,更新抢单状态,排队->等待支付
短信微服务

因为系统中不止注册一个地方需要短信发送,因此将短信发送抽取为微服务:leyou-sms-service,凡是需要的地方都可以使用。

另外,因为短信发送API调用时长的不确定性,为了提高程序的响应速度,短信发送我们都将采用异步发送方式,即:

  • 短信服务监听MQ消息,收到消息后发送短信。
  • 其它服务要发送短信时,通过MQ通知短信微服务。

提供的接口:

检查用户名和手机号是否可用
发送短信验证码
用户注册
用户查询
修改用户个人资料

canal微服务

canal可以用来监控数据库数据的变化,从而获得新增数据,或者修改的数据。

canal是应阿里巴巴存在杭州和美国的双机房部署,存在跨机房同步的业务需求而提出的。

原理相对比较简单:

  1. canal模拟mysql slave的交互协议,伪装自己为mysql slave,向mysql master发送dump协议
  2. mysql master收到dump请求,开始推送binary log给slave(也就是canal)
  3. canal解析binary log对象(原始为byte流)

当用户执行 数据库的操作的时候,binlog 日志会被canal捕获到,并解析出数据。我们就可以将解析出来的数据进行同步到redis中即可

同步实现

在canal微服务中修改如下:

(1)修改application.yml配置文件 配置redis

(2)启动类中开启feign 修改CanalApplication,添加@EnableFeignClients注解,代码如下:

(3)同步实现

修改监听类CanalDataEventListener,实现监听广告的增删改,并根据增删改的数据使用feign查询对应分类的所有广告,将广告存入到Redis中,代码如下:

上图代码如下:

@CanalEventListener
public class CanalDataEventListener {
   
    @Autowired
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值