shuiclub一期面试问题记录已以及回答
秋水 Lv5

项目介绍

这个项目主要是我在学校和几个小伙伴一起来做的,背景主要是我们是学校的 电子信息 专业,大家在求职的过程中,免不了要进行面试题的学习,网上的 pdf 很多,大家一般都是基于此来进行背诵或者收集,重复的题目及答案的质量参差不齐,我们就有一个想法,做一个线上化的面试题网站,来进行资源整合。学习面试的同时,将所学习的技术结合到一起。我们采用的是主流的微服务架构 alibaba,配合主流的中间件,前端主要是以 react,整体为一个社区的形式,主要实现的功能有刷题,练题。我在这里面承担的角色是负责人,主要是输出想法设计,技术选型,架构设计,功能的设计及落地。其中刷题模块是我来进行主要落地实现的。

你认为项目的难点和亮点是什么

好的,面试官,这个项目对于我来说还是有一定的挑战性的,在此过程中,我也成长和学习到了不少。亮点和难点方面我主要从三个方面来阐述,一个是业务方面,一个是整体设计方面,一个是技术方面。业务方面来说,这个是我主导的,从0到1来进行落地的一个项目,前期的业务调研和学生交流,对于我的沟通能力还是有很大的提升的。最终从不同人反馈的需求下,进行提炼,沉淀,发散。用户想要的是1,仅限于一个小点,不断挖掘用户背后的真实需求点,才是最好的。形成一套合理的业务形式。从整体设计方面,我们采用的是微服务架构的形式,采取了一个整洁一点的分层模型架构,目前划分出来,题目、权限、文件、网关几个服务。每个服务之间解藕。整体采用的是springcloud alibaba作为后端的技术框架,前端采用的是react,中间件相关的东西,用docker进行中间件的部署。

DDD的设计理念

  1. 领域(Domain):业务领域,是指企业的业务逻辑和规则。每个领域通常包含多个子领域,每个子领域负责具体的业务功能。\

  2. 聚合(Aggregate):由一个或多个实体和值对象组成的聚合体,它们一起被视为一个修改单元。每个聚合都有一个根实体(Aggregate Root),通过它来控制对聚合的访问。

  3. 资源库(Repository):用于管理实体和聚合的持久化,提供从数据库中查找、保存和删除对象的机制。

  4. 定义限界上下文:为每个子领域定义清晰的边界,确保领域模型在其边界内一致。

ddd与传统mvc相比

mvc即时一种思想,也是一种框架,mvc主要分为三层 1.模型 2.视图 3. 控制

说说你的项目整体架构情况

整体部署上,我部署在一台服务器上,如果有资金的可以拆分一下的其实,因为是学生,所以只买了一台,如果服务器多的话,可以每一台部署一套应用在里面,这样也不影响我们的整体服务,前端项目打包,部署nginx,nginx反向代理到网关,网关在进行请求的分发,解析道前端服务器暴漏公网,其他的后端口的分发。

技术上,我们选取的是spring cloud alibaba, ssm,权限Satoken,gateway网关,配合mysql,redis等中间件作为项目架构。

你在这里面主要负责哪些东西?

我在这个项目里面主要负责技术选型,框架的构建,刷题模块的设计与开发,技术选型主要是评估用户量,服务器资源,工时的预估,选取最快能达成结果和比较主流的技术栈。框架构建这个过程也是看了很多资料,传统的三层,四层,洋葱等等,最终决定选择三层架构。将我们项目分为了,基础设施层,领域层,控制层,公共层,启动层。

你的技术选型是怎么做的?

技术主要从几个方面进行考虑的,需求背景,上手程度,技术生态,工期等,项目工期比较充足,所以我们做了框架选型后才开始业务开发,不需要选取一些已经做好框架和业务,在上面二次开发,加上二次开发维护行比较差,我们从0到1做这个项目需求社区设计的功能比较多,为了后续更好的扩展,我们选取了微服务的形式来进行开发,选用的cloud alibaba,相比传统的cloud更方便使用,数据库的数据量不大,使用的mysql,缓冲用的是redis。

前后端开发模式是什么样子的

我们这个项目采取的方式是前后端分离开发的模式,前后端通过接口文档配合原型来进行开发设计。整体的设计规范由我们后端的接口开发者来进行定义,定义好接口的出入参之后,和前端进行沟通对接。双方确认后,进行开发,文档用的是 apipost,进行团队的协作。按照文档开发完成后,进行前后端联调。联调无问题则进行测试。在此过程中,有任何的变更,都要在联调之间,双方沟通好,保证一致。

编写接口对接的时候,你觉得应该注意些什么?

从两个层面来说,一个是前端的接口规范,这块要保证可理解性高,语意简单,定义好公共的出参格式,另一层面是就是我们要注意一个重要的原则就是,让前端只负责充值,逻辑让后端来处理。

刷题业务是如何做的

刷题业务整体是这样进行设计的,我先讲一下业务,刷题主要是为了方便看题库,题目是主要的信息,那么题目会分为几个类型,单选,多选,判断,简答,这块可以拓展为设计模式,工厂+策略来做。然后就是题目为了方便查询,会涉及到其中分类,标签。分类是大类,比如框架,数据库等等,标题是题目的小类,一个题目属于多个标签,比如一到mysql题目,既可以属于基础标签,也可以属于mysql标签,进行了难度的划分,还有题目解析。

其中用到了设计模式,这样的设计有什么好处呢

在目前的项目中,用了几处设计模式的,第一块就是题目录入的时候,因为题目的类型有四种,未来可以做一些变种题目类型的变更,于是工厂+策略模式来进行了一波处理,这样处理的好处是,四个题型各写各的策略,互相解偶,互相没有任何影响。然后主逻辑的 service 层代码。也不需要任何变动。当我新增加一种题目类型的时候,也是不需要修改任何主逻辑的代码。直接增加枚举,然后处理其中的逻辑即可。 另一处设计模式的使用,是文件相关的适配器模式。在文件的 sdk 上封装一层 adapter,主要是为了操作底层文件的时候,不用处理任何的上层代码。上层业务无需进行任何修改,即可更换文件存储 oss。

数据库加密解密怎么做的

数据库的密码加密主要是保证信息的安全性。直接使用了 druid 连接池的方式。利用 druid 提供的加密工具,先生成公钥和私钥。然后将密码进行加密。在项目配置文件,进行配置,其中 druid 提供了一个 ConnectionProperty,可以开启加密的使用,然后配置密钥。就实现了密码在配置文件的加密。

分类和标签这个业务怎么做的

这个分类和标签的设计主要是调研了一下用户需求,然后一开始设计的是一个分类对应一个标签,然后一个题目只能打一个标签,后来发现,比如redis为什么这么快,从分析上来看,属于缓存分类,属于缓存分类,属于redis和基础两个标签,那么就要打破1对1的关系,其次就是标签为基础,不仅仅是redis下面的,也可以是其他分类下面的,所以做了一个标签池的概念,只和分类做关联,这样之后就方便录题,也方便查询。

自动部署是怎么做的

自动部署主要采用了jenkins来做,通过jenkins构建一个maven项目,在jenkins的系统工具里面配置好maven环境,jdk环境,gitcode地址和用户密码,进行代码的拉取,然后就可以从git上拉取代码并进行打包,生成我们的jar包,然后要配合shell脚本,shell脚本主要是将我们打包好的jar包,复制到目录进行统一管理,然后实现自启动。

自动部署如何往多台服务器部署呢

jenkins的一个插件叫publish ssh,当jenkins服务器打包完成后,可以通过插件将jar包发送到不同的机器,发送完毕之后,可以执行配置好的shell脚本,这样就可以实现了多台机器的自动部署。

nacos的动态配置原理是什么

nacos主要是长轮询的方式获取数据,client也就是我们的服务会像naocs的server发送请求,判断数据是否有变更的情况,如果与本地数据的对比有变化则进行配置的更新,其中判断是否更新采取的是整理加密后的md5串对比,如果无变化,则证明无更新,有变化,则拉取最新的配置。

搭建的项目是如何解决包冲突的问题和各种组件的兼容的

在最新的选择的时候,比如boot和cloud与cloud alibaba的冲突适配问题,我先选择了springboot的版本,选了2.4.2然后通过springboot提供给我们的组件选择,还有阿里云的脚手架工具,就可以配置我们使用微服务的组件,进行兼容的版本获取,这个是项目层面的,如果在开发过程中的maven设计,maven产生了包依赖的冲突,主要靠mavenhelper插件,它可以帮助我们将整个pom梳理成树形结构,同时对冲突的会变成红色进行标注。

鉴权是如何设计的

鉴权模型采用的是rbac,我们用的传统的,基于角色帮助我们把授权和用户的访问控制来做结合,用户就是我们使用者,权限就是我们对系统的操作,访问那些东西,可以操作写入等等。实际的例子,比如新增角色,Role就是我们去把一组的权限,去做集合,就用到了角色,就得到了角色,核心思想就是把角色和权限做关联,实现整体的灵活访问。

权限数据你放到了哪里

权限数据我们是放到了redis中,在微服务架构下一个难题便是会话信息同步,单机版的session在分布式环境下一般不能正常工作,为此我们需要做一些处理,处理的方法通常有几种,比如强制session同步,通过算法保证用户落到同一请求,基于redis建立会话中心,节点改为无状态,从实现上我们选择了主流的redis来存储权限信息,同时配合了网管gateway实现权限接口,来实现统一的鉴权处理,避免了与数据库的频繁的交互。

为什么选取 satoken 来作为你们的权限框架

首先就是技术选型上来说,权限是每个项目都绕不开的,如何快速的介入,并且具备好的拓展性,是我们最开始需要考虑的,satoken轻量化,功能齐全,学习成本低,可以快速介入,另一方面是复杂性的考虑,satoken权限认证主要解决,登录认证,权限认证,微服务网关鉴权等一系列权限相关的问题,正好符合我们微服务项目的场景。无需实现任何接口,无需创建任何配置文件,只需要一句静态代码就能调用,

token机制的鉴权游什么好处

说到token就必须要谈到cookie。传统一般是由cookie完成,cookie两个特性,可以后端控制写入,每次请求自动提交,使得我们在前端代码中,无需任何的操作就可以完成鉴权的全部流程,但实在app和小程序等前后端分离的项目中,一般没有cookie的,所以我们就引入了token的概念,拆解出来主要是两步,1是登录后,后端返回token,另一部分是前端请求带着token,将token放到header里面,实现了token的传递之后,token的生成过程,可以包含任何信息,比如用户名,权限都可以包含在里面,这样一个token就可以帮助我们解决了很多问题。

gateway网关是如何设计的

gate网关,作为我们项目的整个流量入口,目前主要实现了路由,负载,统一鉴权,全局过滤器,异常处理这些功能,路由和负载了后台微服务的uri转发和前缀匹配。统一鉴权主要是配合satoken,在gateway集成redis,同时实现satoken提供的权限读取接口,在其中自定义读取逻辑,实现鉴权的校验。在其中还实现了登录拦截器,用于传递 loginId 到微服务中,借助了 header 的传递。

分布式会话的鉴权在微服务中的是怎么做的

分布式会话鉴权的重点主要是如何获取到数据权限,然后进行校验处理,一般游三种形式,在网关集成orm框架,直接从数据库中查询数据。2先从redis中获取数据,获取不到时走orm框架查询数据库。3从redis缓冲中获取数据,获取不到时走orm。我们采取的是第三种直接从redis中获取缓存的权限数据,这有一个要求,就是我们的redis高可用性必须要高,因为我们内部采用,这块可以采取刷新等措施,

gateway 如何实现的全局异常处理

gateway的全局异常处理主要需要我们实现一个接口ErrorWebExceptionHandler,实现其中的handler方法,在方法内,我们能获取到其中request和response,webhandler会帮助我们拦截所有异常情况,然后我们可以在里面做拦截的进一步处理,更改状态码,错误信息等。最后通过reponse可以将其返回出去。

用户登录的密码加密你做了吗

加密算法主要有摘要加密,对称加密,非对称加密,我们采用的是md5配合加盐,盐是随机的字符串,加盐实现二次加密。

缓存与数据一致性,你有什么样的理解吗

常规有三种处理方式

  1. 更新数据库,删除缓存

  2. 延迟双删,先删除缓存,在更新数据库,在删除一次缓存。

  3. cache-aside : 更新数据库,基于binlog监听进行缓存删除

    但这三个适合的业务方向不太一样,比如binlog监听,如果我们在数据频繁的写入,那你一直监听,这个方法显然不是适合这里的。使用缓存的原因不是因为缓存快,而是因为方便, 所以我们需要根据具体的业务来决定使用什么样的策略, 读写不频繁的,重要的不能出错的,我们依赖数据库,或者说分布式锁,不重要的我们写数据库,更新缓存,后期查询完更新缓存,读频繁写不频繁的,数据重要的:双删策略,读写频繁,完全依赖缓存,数据库保底,定期轮训更新,从数据库更新到缓存。

你是如何对接公众号登录的

对接公众号主要是希望用vx的唯一的openId,来作为唯一标识,整体的流程主要是用户公众号,然后发送一条消息,验证码,我们通过api回复一个随机的码,存入redis。redis 的主要结构,就是 openId 加验证码。用户在验证码框输入之后,点击登录,进入我们的注册模块,同时关联角色和权限。就实现了网关的统一鉴权。用户就可以进行操作,用户可以根据个人的 openId 来维护个人信息。用户登录成功之后,返回 token,前端的所有请求都带着 token 就可以访问。从服务涉及上,我们开一个新服务,专门用于对接某信的 api 和微信的消息的回调。

如何监听用户发给公众号的消息的

主要是对接公众号的回调消息平台配置,重要分三步 :

  1. 填写服务器配置,验证服务器地址的有效性,依据接口文档实现业务逻辑在

  2. 公众号配置界面填写服务器地址url,token,和EncodingAESKey,其中url是开发者用来接受微信消息和事件接口的URL,token可以由开发者自己填写,EncodingAESKey由开发者手动填写或随机生成,将用作消息体加解密密钥

  3. 验证消息的确来自微信服务器:某信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带参数有签名,内容,时间戳之类的,后台服务要通过一样的加密形式来进行校验。

回调消息的验证校验是如何做的?

开发者通过检验signature对请求进行校验。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。加密/校验流程如下:

1)将token、timestamp 时间戳、nonce随机数 三个参数进行字典序排序

2)将三个参数字符串拼接成一个字符串进行sha1加密

3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信

多线程你是哪里用到的,怎么用的

我们前端请求分类的时候,每个分类都是串行获取的,比如他有10个分类,我们需要循环10次,导致他速度特别慢,于是我们转换成了多线程的执行,然后统一进行组装,在返回给前端,提高了性能,前期用的FutureTask来实现的,后面我们改成了CompletableFuture来实现的。

为什么用CompletableFuture而不是FutureTask

FutureTask

调用get()方法求结果,一旦计算没有完成,会导致程序阻塞。

如果采用轮询的机制,判断是否完成 while(futureTask.isDone()) , 完成后调用get()方法返回结果,缺点是比较耗费性能,

CompletableFuture可以使用链式调用

可以传入回调对象,当任务完成或异常时,自动调用回调对象的回调方法,多个任务前后依赖组合处理

自定义线程工厂的好处

比如可以设置自定义的线程名,方便我们开发调试,问题日志查找及定位。可以设置守护线程。可以设置线程优先级尅处理未捕获的异常:在执行一个任务时,线程可能会由于未捕获的异常而终止,默认处理是将异常打印到控制台。

全局的用户上下文你是怎么打通的

当用户的请求来临的时候,前端会带着 token,token 里面有用户的 loginId 信息,首先经过网关的全局拦截器,拦截器会帮我们放入 header 里面,然后实现webmvc的HandlerInterceptor ,把logindid放入到LoginContextHolder,LoginContextHolder中通过InheritableThreadLocal存储当前线程的用户信息,然后提供一个util的context全局操作

这里我用的InheritableThreadLocal,为什么不用ThreadLocal

同一个 ThreadLocal 变量不能在子线程中获取到,而 InheritableThreadLocal 变量中的值可以在父子线程之间传递。

本地缓存那里使用的

本地缓存主要是在分类标签和查询中使用的,分类和标签的数据很少产生变更,加个缓存更快,guava,这里也是为了学习缓存技术,防止redis压力过大的时候,可以加一层一级缓存,所以选择使用了guava,然后还封装了guava通用工具类,查询缓存是否存在,如果有就直接返回,如果没有,传入calzz对象,通过java8的一个新特性function函数,去调用他的方法,然后返回结果。

你这个函数式编程配合范性是为了解决什么问题

对于我们大多数的场景,我们先查询缓存,缓存内,没有数据,我们就去查询数据库,然后返回结果,这个过程其实是固定的,我们可以在查询数据库的地方,抽象为函数function,返回数据和入参可以作为范型,中间和缓存交互,可以通过序列化,在工具内部判断,如果有缓存就返回,如果没有就执行查询动作。

全文检索怎么做的, 有高亮吗

全文检索只要是引入了 es 模块。依靠其强大的倒排索引的能力来做关键词的反向搜索,获取搜索结果相关性的评分,评分高的证明相关性越高,排序排在最前面。其中的高亮主要是用了 hignlight 的方式,确定高亮数据要展示的标签,比如我们加了一个 span red,这样返回给前端之后,前端将其展示出来就变为了红色。其中的分词我使用了 ik 分词器,更加的符合中文的切分逻辑。

排行榜的设计思路

一般来说排行榜有实时的,非实时的

实时

数据库 : 通过创建时间,然后groupy by返回结构,接受数据量比较小,并发比较小的情况

redis的sorted set 有序集合,不允许重复的成员,然后每一个 key 都会包含一个 score 分数的概念。redis 根据分数可以帮助我们做从小到大,和从大到小的一个处理。

非实时

xxl-job 定时统计,然后写入缓存,更新数据。

点赞和收藏你是怎么做的

点赞和收藏的逻辑是一样的,

首先我们要知道题目被多少人点过赞,还要知道每个人点赞了那些题目

点赞的业务特性,频繁,用户一多,时时刻刻都在进行点赞,如果我们采用传统的数据库模式,交互非常大,很难去扛住并发,所以我么选择了用redis的方式去做。所以我们redis存了什么 1. 点赞的人 2.点赞的题目数量 3持久化准备的点赞数据的明细。

用了三个结构 1. hash 存储了一个题目,有多少用户点赞,key是 题目 id value是 用户id statue

  1. count是用string来记录的
  2. 细节 每个用户一个key 一个set

  • Post title:shuiclub一期面试问题记录已以及回答
  • Post author:秋水
  • Create time:2024-06-20 20:46:08
  • Post link:tai769.github.io2024/06/20/shuiclub一期面试问题记录已以及回答/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.