黑马头条项目整体概述
我们的项目,是一个文章资讯类的项目,
自媒体文章发布与审核模块
整体架构
首先从应用层通过 nginx 反向代理到网关层,由网关层进行统一的登录和权限校验,然后将请求路由到对应的微服务服务器上,我们的微服务采用的是 nacos 进行统一注册和管理,微服务间采用 feign 进行远程调用,数据存储层主要用到的是 mysql 数据库,用于存储核心数据,另外我们采用 redis 进行热点文章,关注等数据的存储,针对点赞评论等行为数据我们采用mongoDB 进行数据的存储。
首先呢,从整体来分析一下这个业务的流程,根据需求,app端用户在实名认证成功后,将会在自媒体数据库的用户表中插入一条数据,同时也会在文章数据库的作者表中插入一条数据,这样自媒体用户就开通成功了,作为自媒体用户就可以进行发布文章的操作,发布后通过RabbitMQ发消息完成自动与人工审核,审核成功再通过RabbitMQ完成定时发布文章 文章内的图片我们选择保存到阿里云的OSS中去,并且为了方便app端查看文章,我们利用FreeMarker实现了页面静态化,并存储到Minio中.
接着我来说明文章发布的流程,首先在自媒体端发表文章时,需要在标题与文章主体中输入内容,而文章主体是可以选则输入文字或者插入图片的,当主体内容完成后,用户还可以选择封面图,文章的封面可以是无图,单图,多图,或者自动,并且自动生成封面是需要根据主体内容中的图片数量来决定的,所以根据这个需求,在数据库中定义了一个专门存储图片数量的字段,0为无图,1为单图,2为多图,-1为自动,当前端所有内容输入完成后点击发布即进入,后台逻辑.
首先查询文章表,判断该文章是新增还是要做修改,若能查到则为修改,反之则新增,将文章图片统一放到自媒体素材表中,因为文章表与素材表多对多,所以建立了文章素材中间表,用来保存文章内容中的图片与素材表的关系和封面与素材表的关系,在保存之前需要将内容中的图片路径抽取出来,因为如果前端传过来的是自动生成封面的情况,需要根据内容中的图片数量设置封面,最后保存文章与素材之前的关系,,并且将文章内的图片保存到阿里云的OSS中去.
发布文章后,会通过RabbitMQ发消息进行文章的自动审核处理,首先我们自己定义了一系列的敏感词,然后使用DFA算法,对文章标题和内容进行初步审核,该审核通过后,会调用阿里云文本内容审核的接口,再次对文章标题和内容进行审核,该审核通过后,
会调用阿里云图片审核的接口,对文章内容中的图片和文章封面图片进行审核,云接口审核是否通过的判断依据是根据调用后返回的具体信息中有一个字段名为”suggestion”的字段,该字段有三种取值,”pass”为通过,”block”为不通过.”review”为需要再次审核,根据字段取值,若都为pass则发布,若返回结果有一个是block则不通过,若有一个是review则需要人工审核,这些都是文章的状态,所以我们在设计时,赋予了文章多种状态,以便适应各种情况,分别为草稿,待审核,审核失败,待人工审核,人工审核通过,自动审核通过,发布.若自动审核成功则修改文章状态为自动审核通过,可以若有block则状态为审核失败,若有review则状态改为待人工审核,然后自动审核我就说完了.人工审核的话没有太多业务逻辑,就是修改文章状态.接下来介绍这个模块最后一个功能,延时发布
在文章审核成功后,给文章微服务发送消息,使用RabbitMQ的延时队列实现文章的定时发布.利用前台传过来的时间与前时间做对比,如果小于等于当前时间则立刻发布,如果大于则设置到延时队列中,修改文章状态为发布,发布后为了方便查看,实现了文章的页面静态化
在这个模块中,我遇到了一个问题,由于发布文章服务需要远程调用其他微服务,当发布文章失败时,当前微服务的事务可以回滚,但是被远程调用的服务没法回滚,造成数据不同步,最后我们采用了,阿里巴巴的分布式事务解决方案Seata,完美的解决了分布式事务问题.
热门文章缓存
首先从整体来看,实现该模块大致需要4步
- 首先需要筛选出文章列表中最近5天热度较高的文章在每个频道的首页展示,并且
- 其次根据用户的行为(阅读、点赞、评论、收藏)[数据在mongo中]实时计算热门文章,
- 然后定时更新文章的热度值,根据热度值替换之前缓存的文章数据
- 最后重新查询热点文章列表
在这个需求中,遇到了一个问题,首先是什么时候去统计最近五天的文章还有就是怎么保证文章热度的实时更新呢,我之前考虑过使用延时队列去实现,但是没办法保证时效性,还有就是用户的大量行为会不停对数据库进行操作,导致效率问题,解决办法就是将数据先全部存入到redis中,最后统一从对象中获取值,更新到数据库中.
将五天热门文章缓存到redis中,数据类型为String,key为业务前缀,value为文章详情对象,将用户行为也存入到redis中,数据类型为list,key为业务前缀+频道id,value为实时的行为数据,来缓解数据库压力,设计一个对象专门用来存储用户行为的操作数量,更新到数据库中,这样就解决了数据频繁增加的问题,那么时效性的问题又该怎样解决呢,我们考虑使用分布式任务调度框架xxl-job,设置每天凌晨一点查询近五天热度较高的文章,并且每隔十秒统计一次用户行为数量并更新文章热度值,这样就解决了以上问题.
文章分值定时计算
首先查询前5天的 (已上架、未删除) 文章数据,计算所查询所有文章的热度值,根据权重计算分值,为每一个频道缓存热点较高的30条文章,若为推荐频道则缓存所有文章热度值排名前30的文章
实时采集文章行为
因为之前已经有一个定时任务负责采集近期(5天内)的热点文章数据,但是当天也会有用户对文章进行操作,阅读点赞等,所以需要利用MQ进行监控,一旦有用户对某个文章进行了操作,就需要用MQ发送消息,将文章行为保存到Redis,再对数据做后续处理.
实时更新文章热度分值
利用定时任务实现每过十秒检测一次Redis中是否有用户操作的数据,对于行为数据它的发生频率非常高 可能1篇文章 最近10s被阅读了几万次,所以我们不可能每次都去修改数据库,需要把最近一段时间内该文章的所有行为先进行统计,然后再更新文章的相关热度信息
查询热点文章列表
若APP查询的是第一页,那么就去Redis中查询热点文章,并返回
若查询的不是第一页,则去数据库中查询普通文章
餐掌柜项目整体描述
这个项目是基于SaaS平台开发的一款点餐系统,拥有运营平台,商家平台,以及H5点餐平台,运营商管理基础数据模块【统一权限、日志、图片、数字字典】以及商家管理的平台,点餐后台核心业务,提供员工、店铺、桌台、菜品、订单、结算等功能,H5点餐平台,客户实现开桌、点餐、追加菜品等功能.技术架构也是基于 springcloud 体系采用 ssm 框架进行开发的,与我之前所说的项目不同的是,这个项目中微服务之间的调用是通过dubbo完成的.
桌台状态查询及开桌操作
这个模块要实现的场景,就像我们去饭店桌子上可以进行扫码点餐,点餐后我们就会对该桌台进行开台处理,如果开台成功,那么会为桌台创建一个待支付的订单,并且将桌台的状态修改为使用中,如果其他人想预约该桌台就会失败,如果想实现开台需要两个前提条件,当前桌台处于空闲状态且无订单,或当前桌台不存在处于【待付款、支付中】的订单,则可以进行开台处理,整个处理有两个关键点:
第一个就是要怎么解决两个用户同时扫码同时下单的问题,商讨过后我们选择使用分布式锁来解决这个问题,为桌台添加一个分布式锁,防止并发同步创建订单,分布式锁用的是redission框架,
第二点就是要保证桌台的幂等性,因为在网络卡顿或者线程卡顿情况下,用户可能多次点击确定按钮,发送多次请求,这时可能会会造成多次创建订单的情况,所以在创建订单前,先查询一次订单,保证不会不会重复创建如果能查到数据则说明之前该桌台已经有人下单无法进行开台处理,如果查不到数据则为该桌台创建一个新订单.
点餐平台-购物车、下单
项目中出现了什么问题?
超卖问题
怎么产生的?
- 超卖现象是并发情况下,多线程同时操作数据造成数据的不一致性,进而导致售出数量超过商户储备数量
如何解决超卖问题?
实行库存预扣,在服务启动时将菜品的库存初始化到redis里,使用Redisson的分布式整长形RAtomicLong对象操作库存保证库存的原子性
key:业务前缀+菜品id
value:菜品对应库存
扣库存:先到redis中进行预扣除,预扣成功证明库存足够,正常销售并更新数据库库存,预扣失败,库存不足,返回错误信息
购物车流程
添加部分:
当用户操作添加菜品时,后台根据传输过来的菜品id,查看redis进行预扣库存,如果成功
则扣除数据库库存,根据订单编码查看redis是否有订单信息,无则创建,有则合并购物车清单,在根据菜品id查看会否已含有此菜品,有则更新数量,无则重新创建
去除部分:
当用户操作添加菜品时,后台根据传输过来的菜品id,增添mysql的库存,根据订单编码查看redis订单信息,判断购物车中的菜品数量如果大于1,则进行菜品的数量更新,如果等于1,则删除购物车
整个流程都需要在分布式锁的联锁状态下进行,添加菜品锁是为了防止操作时,有人对菜品库存进行同步操作,导致库存数据不一致添加订单锁是为了防止对订单操作时,有人进行结账操作,导致订单状态异常,并且由于数据存在于redis与mysql属于多数据源,一定要考虑双写一致性问题,添加时先预扣redis库存,成功则扣除数据库中的库存,若是数据库库存扣除失败,则回补redis库存移除时,先添加数据库库存,失败回滚,成功则扣除redis库存,若redis库存扣除失败,则抛异常使数据库回滚
下单操作业务流程
- 对订单加锁,保证订单信息不会在操作时被别人篡改,
- 分别查询出数据库可核算订单信息,及redis购物车订单信息,
- 判断购物车订单信息是否为空,为空则直接结束,
- 不为空则使用冒泡排序思想合并购物车订单项 和 可核算订单项
- 利用冒泡排序的思想 遍历购物车订单项时,嵌套遍历可核算订单项,分别使购物车订单项中的每个菜品和可核算订单项中的菜品进行对比,若存在相同则更新菜品数量,不存在相同,则新添数据
- 合并时可核算订单中存在的菜品更新菜品数量,不存在的添加到可核算订单项中
- 保存合并后的可核算订单项并更新订单金额
- 更新成功后清空redis订单项目
下单时机: