一、登录模块
登录模块首先是要对用户名和密码进行校验。我这里使用的是邮箱登录,邮箱登录的代码使用的是网上csdn的公用代码,然后使用自己的QQ的SMTP权限获取授权码完成对邮箱登录的要求。
因为用户的密码是不可以明文保存到数据库,所以我这里使用的是盐加密,这样就可以保证用户的信息不会被泄露的问题。因为是后端登录验证成功首先要生成token,token内容保存用户的id信息。然后使用redis用来缓存,使用用户登录的用户名作为key,然后使用生成的token作为value保存到redis中。这样可以实现单点登录。
如果游客将商品加入了购物车但是没有登录,当点击购买就需要登录并且将信息同步到登陆后的用户的购物车,当游客将商品加入购物车时,可以将购物车数据保存在服务器的会话中。会话标识符可以通过响应的方式发送给用户的浏览器,Cookie中,以便用户的后续请求都可以带上该会话标识符。当用户点击购买并需要登录时,用户的浏览器会携带会话标识符发送到服务器。服务器根据会话标识符找到对应的会话数据,并关联到用户的身份信息上。
当然还有重复登录这个情况使用redis+JWT就可以解决了重复登录问题。
然后对于不同的用户使用不同的权限,这个权限主要在于后台管理系统。主要分为普通用户和管理用户。使用权限主要基于SpringSecurity来实现的。
二、网关模块
当用户登录以后就可以进行访问,这里使用的是gateway作为请求转发。当用户登录成功后如果访问其他接口,就从gateway网关这里进行访问。
三、课程模块
课程模块涉及内容的增删改查。因为这个模块的读操作比较多,所以经常把课程信息存入redis缓存,减少对数据库的访问压力。
这里考虑到了缓存穿透和缓存雪崩这两种情况。第一个情况使用的是逻辑过期,当把数据保存都redis的时候,在其字段添加过期时间,然后输出要过期的时间。当后面的访问访问过期的数据的时候首先在业务层从redis获取该课程的信息,然后从过期时间字段获取时间和当前时间进行判断,如果过期了,那么使用线程池新建一个线程用来重置缓存。当前线程直接返回过期时间来保证用户的体验。当子线程访问数据库重置缓存和过期时间后,就可以将最新的数据返回给用户。虽然有数据不一致问题,但是大幅提高了用户的体验。第二个情况是缓存雪崩,因为很多课程信息都在缓存,有可能突然出现大量的redis缓存失效,这里的解决方法是给每个缓存数据的过期时间添加随机TTL,避免大量数据同一时间过期导致大量访问数据库造成宕机。
对于课程模块数据不一致还有其他情况,比如对课程信息修改后,缓存中保存的依旧是旧数据,这就导致数据不一致的情况。这里可以使用双删来解决数据库和缓存数据不一致的情况。首先删除缓存,然后再去数据库修改数据,然后再去删除缓存,避免了并发删除操作导致的数据不一致性问题。
四、订单模块
这个模块使用了RabbitMQ和支付宝第三方支付。支付宝第三方支付因为是个人没有营业执照所以是在支付宝沙箱环境完成的。
对于RabbitMQ的使用,是在用户下完订单以后,那么就会将订单信息首先保存到数据库的订单表,然后再向RabbitMQ发送订单信息。一般下完订单有30分钟的支付时间,这个时间段如果没有支付就会取消订单并且删除订单表里面的信息。因此对这个功能使用的是延迟队列功能,但是由于RabbitMQ没有延迟队列,但是可以根据RabbitMQ的特性来实现延迟队列,使用死信队列+TTL来解决这个问题。
然后当30分钟到了没有购买库存系统就会从延时队列拿到消息,查询一下订单表是否支付,如果没有支付,就会删除订单表。如果用户在30分钟内支付了,就在订单表修改字段已支付,流程和上面相同。
当然也有可能因为服务器宕机出现消息丢失的情况,导致RabbitMQ将消息已经发送给库存系统,但是库存系统宕机没有收到消息,可能出现消息丢失情况。处理消息丢失分为三步,分别从是生产者采用confirm机制、MQ采用持久化、消费者采用ACK确认机制。
对于生产者,消费者处理消息后可以向MQ发送ack回执,MQ收到ack回执后才会删除该消息。这个项目采用的是手动ACK模式,异常就发送重发消息,正常结束就发送签收。
对于MQ采用持久化,这个项目中采用的是交换机持久化、队列持久化、消息持久化。
对于消费者使用ACK确认模式,只有收到消费者的确认消息才可以删除消息,否则就消息留到队列中,等待重试机制从队列中拿到消息。对于异常情况采用手动模式,只有库存系统处理成功以后手动签收,如果异常就让自动重发消息。
为了解决超卖的情况,这里使用的是分布式锁redisson来解决这个问题。因为这里需要保证强一致性,所以使用redisson来实现这个功能。当并发情况下单的时候,使用分布式锁俩避免超卖情况。
当然如果面对大量请求导致请求量过多但是消费者消费不过来出现消息堆积在队列的情况,解决方法要么是开启线程池,在CPU允许的范围内增加消费者的消费能力,或者扩大队列容积,提高堆积上限。