秋招面试总结

自我介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
面试官您好,我叫方圆,目前是杭州电子科技大学通信工程专业研三的学生
熟悉Java语言,对web端的开发有一定的实践

读研的时候,是以校企合作的方式
参与数梦工厂的消息中心项目
主要负责Java后端
后来暑期实习
在华信咨询设计院参与了
中国移动浙江公司的科研数字化管理平台项目
担任前后端开发实习生

与此同时呢,我也做过一些开源项目,学到过一些技术栈
并且搭建了自己的个人网站
主要用来记录和回顾学习的知识,笔记,算法等

(然后今天面试的是测试开发嘛
因为自己在实际的开发过程中
也是做过一些功能和性能方面的测试
对测试这块也有所了解
所以。我觉得自己能够胜任这个职位)

以上就是我的自我介绍

华信-科研数智化管理平台

1
2
3
4
5
6
7
8
9
10
11
12
	2024.06-2024.09:中国移动浙江公司-科研创新数智化平台(华信咨询设计研究院有限公司)  全栈开发实习生
项目描述:
浙江移动科研创新数智化平台主要是基于ACWS5框架搭建,该系统分为系统管理、个人工作台、项目流程管理、统计分析、成
本度量、专家库管理等多个模块,目标是建成覆盖通信工程建设全过程的信息化管理平台。
技术栈:ACWS5框架+SpringBoot + MybatisPlus + Oracle+ Vue2+ ElementUI + Echarts + Activity7
责任描述:
◆系统主页面:主页面表头、近半年工时情况、一周项目动态、资料上传情况、日程安排的前后端开发、数据展示。
◆项目管理模块:数据库项目表的设计实现,前端项目列表展示页面、编辑页面、评估页面的开发
查询分页、表格拖动排序、模板下载、Excel导出、导入校验、发起评估、附件上传等功能点的前后端实现
◆统计分析模块:开发项目进度、工时、成本、智能评估质量的柱状图、饼状图的后端数据与前端展示。
◆成本度量模块:开发成本相关数据的采集与导入,成本估算、预算管理、实际支出对比、成本度量功能变更审批等功能
◆流程评估模块:后端 Activity流程图的设计、代码实现;前端流程编辑页面、审批页面的开发;

acws5框架:也是基于spring框架去搭建的,包括Shiro的安全框架+JWT的登录认证Swagger UI的API发布等

image-20241007232225034

介绍一下你负责的项目和模块

1
2
3
4
5
6
7
好的面试官,这个科研数智化平台是给移动公司提供的一个覆盖项目工程全过程的信息化管理平台
我在一下几个模块的功能业务都有所涉及,
1.系统主页面的一个前后端的开发,是对于其他模块的概要总结,因为开始进入系统是项目管理模块,没有主页面
2.然后项目管理模块是已有的,然后评审的实际主要是对一些功能的增加,比如Excel导入导出校验,拖动排序等等
3.统计分析模块:主要是对项目数据做的分析,前端可视化的展示
4.成本度量模块:主要是对于项目成本的一个管理
5.流程审批模块:存在于项目管理模块和成本度量模块中的,主要是对项目,成本的的一个审批

系统主页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
主页面主要是分了五个模块,主要是对于其他模块的一个简要总结和展示
比如这个表头去展示当前登录人员权限下的项目数量、项目代办已办、待阅已阅这些这些
前端的话就是引入他们内部的一些自定义的组件
后端去根据项目表,代办表中查找他权限下的相应字段即可

半年工时情况就是,资料上传情况也差不多
获取当年时间,往前推六个月获取当前月份数组
再去工时表中动态SQL,foreach标签去查找整个月份的数据
这边有问题就是,前端自定义组件返回值要包括线条颜色等其他信息
就需要前端再写js方法去处理
包括为了美观去截取名次,再次排序等等

比较复杂的是,日程安排这里,没有专门的表
整合日历任务表和WBS代办任务表
并且这两张表是没有外键关联的
所以就要去写建视图的代码
去做一些字段映射as,拼接contact,子查询等
写的还是比较麻烦的
去生成前端说需要的数据表,再去查询试图就行
为什么用视图?
就是为了实时性,视图是虚拟表,内容是根据底层的表实时生成的
底层表中的数据发生变化的时候,视图的数据也会变化
如果新建表的话,就不会变化,原有的提交任务代码还需要重写保存一份在新表中
就比较麻烦

(整体的开发逻辑:在评审会的时候,项目经理会设计UI,并且告诉我们开发的一些组件)
(配置api中的方法,在后端取值)
(后端主要做的就是,对前端响应数据的一个封装)
复杂:日程安排这里

项目管理模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
项目管理模块主要是对表的一些字段的增加,还有一些功能的增加
数据库表的设计主要是使用pdmaner这个软件去做
我做的主要功能有Excel的导入导出,校验,表格拖动排序,等
先做的是模板的导出,导入的话前端调用导入的方法
用的是Apache的api,
会有一个公共方法读取表单,转为list<map<String,String>>结构
后面就处理这个结构
做一些逻辑校验(人员是否存在),数据格式校验(正则匹配)等

然后就是新增了项目列表可拖动的功能
这个主要实现在前端
需要使用到 SortableJS 这样的第三方库,通常依赖于存在的 DOM 元素来进行操作,
回调方法中执行更新顺序保存到数据库
这里遇到过bug,就是这里就很奇怪,只有点击了添加附件类型才能生成 dom 元素 tag
后面也是找了很久才解决
设置监听器,用$nextTick 确保在dom已经更新完成后再获取dom元素就行

审批流程等

统计分析模块:开发项目进度、工时、成本、智能评估质量的柱状图、饼状图的后端数据与前端展示。

1
2
3
4
5
6
7
8
9
10
11
12
前端主要用到的是他们自己封装过后的echarts,因为vue2不能直接用
主要是一些柱状图,折线图,条形图等
然后后端去查询相应的数据

难点是智能评估质量这块
这边需要做算法的处理
讲项目表中的字段分为不同的评估维度
比如成本,预算,人员,交付等字段
然后去设定权重
归一化处理,计算综合得分

开始查整张表写,被骂,性能不好,后来就乖了

PDmaner

1
2
3
4
5
6
7
8
9
我们导入sql就可以看到整体的项目200多张表的结构
以及每个模块表的结构图,对应关系ER图等
然后建表的时候,也可以直接去页面加字段,自动生成sql而不用自己写
并且生成的表,后端的方法也能生成,省去自己写重复代码的过程
使用这个好处是
我觉得对于开发对数据库表的结构人生还是比较重要的
有时候就是,对表不熟悉,我自己去重写方法,后来组长说这个方法已经写过了
就白费功夫
而且,在开发的时候,也方便去注入对应的service层调用方法

成本度量模块:开发成本相关数据的采集与导入,成本估算、预算管理、实际支出对比、成本度量功能变更审批等功能

1
2
3
4
5
6
7
8
9
10
11
12
采集就是前端写一个dialog,用elementui相关的元素,然后获得成本度量相关的信息,保存到数据库中
导入就Excel批量导入,要做校验等

成本估算就是:成本表,
根据项目的开始时间和结束时间,然后项目的工作量,团队规模,其他资源等
去计算整个项目周期的估算成本

预算管理:实际预算表,各个阶段预算等,可以发起审批增加预算等等

对比的话就是,各个阶段的预算和实际的对比,计算超出百分数等

审批:对当前业务发起评估等

流程评估模块:后端 Activity流程图的设计、代码实现;前端流程编辑页面、审批页面的开发;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
activity流程引擎,它是通过一系列数据库的表
和sql语句实现流程的存储与管理
画bpmn的流程图
他本质上就是一个xml文件,去存放我们流程节点的相应数据
流程引擎解析xml文件的时候,解析成一条一条的sql插入到表中

流程定义表:
流程定义表:存储流程定义的静态信息,如流程ID、版本等。
每当一个新流程定义被部署时,相关数据会通过SQL语句插入该表。
运行时表:保存流程实例的执行状态,保存当前任务信息。
当启动一个流程实例时,操作将实例数据存入这些表;任务完成后则通过操作更新状态。
历史表:流程执行结束后,等表保存流程实例和任务的历史记录。
将运行时表的数据复制到历史表中。
流程变量表:存储流程实例中的动态变量,保存已结束流程的历史变量数据。
Activity利用这些表和SQL操作,实现流程的启动、执行、任务指派及历史追踪等功能,
确保流程执行状态和历史数据的持久化存储。

我主要做的呢
在项目管理模块呢,是对项目的一个审批,还有对资料的审批
成本度量管理模块,是对成本的一个评估

后端我们部署的流程就是
起草人(当前用户)-部门经理-公司领导等
部门经理可以审批通过也可以退回,
公司领导也可以提交和退回
根据这个
去配置每个流程节点,路由,网关等
流程节点:每个节点去写人员选择策略类,在流程实例中获取流程节点人的信息
比如我们要获取部门经理,就要先拿到当前流程实例的发起人
查询发起人所在的部门,再查询当前部门的部门经理这样
网关:实现决策的,一般用排它网关
路由:节点之前连接顺序

最后还会用到一个观察者模式
流程实例发布后,会创建事件对象给spring事件系统
然后就是使用注解@EventListener标注方法去监听
判断是不是 当前流程的事件
如果是的话,就调用通知类中的方法,去发送通知给需要审批的人员

后端部署好后
前端只需要去发起这个流程,前端主要写了两页页面,
一个是流程编辑页面,还有一个就是流程审批页面
编辑页面呢,就是对当前项目信息的编辑
在审批的页面呢,就展示审批的信息,包括
审批意见,流程监控,审批路由。人员选择的组件

开发流程

1
2
3
然后我们整个开发的流程,就是,评审后的需求文档
照着需求文档,去做
前端的话就是写一些页面,页面上的一些按钮功能,和数据展示的实现

遇到的困难

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
1.在我负责的activity项目审批流程中,动态获取审批人是我遇到的一个难点
这个问题的复杂性在于,不同的节点需要不同的审批人,
这些审批人可能根据业务规则动态生成,
无法在设计的时候静态的获取

为了解决这个问题,我去搜索了解了业务节点的处理机制
任务节点可以配置为手动指定受理人,或者通过流程变量、表达式等方式动态分配。
最后发现到,可以通过自定义审批人策略,结合业务系统的数据动态获取审批人信息。
我设计了一种基于业务规则和用户角色的动态审批人分配方案,借助Activity的TaskListener接口,
在任务创建时从数据库或外部系统动态获取审批人,并通过流程变量的方式传递到审批节点。
(比如说,从threadlocal中获取当前人,再获取当前人所在部门,在获取部门的经理这样)

2.activity流程图的设计实现,这部分也是之前没接触过的
我在流程建模、任务节点配置、用户权限管理等方面遇到了很多问题。
为了解决这些问题,我深入研究了 Activity 的官方文档,分析了其 BPMN 流程模型,
并在实践中逐步掌握了如何通过配置动态任务分配和复杂的审批流转条件来满足项目需求。

3.第二个就是我在做Excel导出的功能这块遇到的问题吧
我们用的是Apache的api,
会有一个公共方法读取表单,转为list<map<String,String>>结构
在我做项目导出,大概有几千条项目数据的时候
响应还是比较快的
但是我在做人员导出那块,就会出现响应比较慢
然后我分析就是,如果涉及到大数据量的导出的话
有可能会造成内存溢出,或者无响应的情况

针对上述问题,其实也看到过有现成的一些解决方案
放弃使用Apache的api,使用阿里的easyExcel这种
但是其实我并没有这种权限去新加依赖
后面就是用的分批分页的方法
比如说一次只查询5000条数据,5000条批量写入到excel以后,
清空当前List,释放内存
避免内存溢出的情况
(分批分页查询db数据到内存,比如一次查询5000条,分20次分批查询出来每次加载指定大小的数据到内存,
比如每次5000条,5000条批量写入到excel以后,清空当前List,
释放内存分多个sheet写入,每个sheet比如放20w,100百万数据放到5个sheet
每次从数据库批量读取指定大小的数据写入到excel,比如每次5000条)

遇到的bug

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
1.还是流程这里,我在做流程审批的时候,遇到过两个bug
第一个是退回审批不成功,第二个是流程卡在节点无法推进
然后我也是打断电去调试代码找错误
发现,退回审批不成功是我在画流程图的时候
应该从网关出的路由
画在了节点上
第二个找到是
我们在自定义审批人策略的时候
是从当前人所在的部门去获取项目经理
然后我用的是超级管理员身份登录,这个super没有部门
所以就出现问题
解决方法是,在数据库中给super创建一个部门

1.前端的调用方法是异步请求,所以我有时候查询完数据,显示列表数据方法的时候
回调方法有时候可能先一步于执行,而不是按照代码顺序,有时候会显示有时候不会显示
后来问了正式员工,就在方法的回调方法里面写
2.开始培训期做demo的时候,我没有按照代码规范写,导致出bug
就是他们的实体类,还有服务类其实还继承了很多其他的一些公共类,
这些我没有加上
还有就是,他表的主键id不是叫id,所以我开始做demo用mp做
找不到实体类,后面用mybatis试了下可以
就用mybatis写了。所所长检查代码的时候
说要遵循代码规范,遇到问题要解决问题
而不是变通,用另一种方法解决了
还有就是分页查询的时候,用的是pageHelper的插件,我自己用mybatisplus的Page,就会有问题
3. /0的错误,小错误等,数据精度处理用BigDecimal等
4.写sql,而不是在service层写一堆逻辑

实习的收获

1
2
3
4
5
6
7
8
9
10
11
12
1.新技术栈的学习,VUE前端,activity流程引擎这些,掌握技能
2.接触项目开发的一整套流程嘛,包括,评审+任务分配+合作交流完成+测试
3.更多的团队沟通和合作能力
因为我一开始我遇到问题可能不善于去问别人,先自己百度解决
如果解决不了,再去问,但是有时候可能会影响进度嘛
组长给我说,就一句话的事情就能解决问题,
以后遇到就知道了,这个还需要慢慢积累经验吧
4.解决问题能力
1.解决问题能力太暴力简单了,不考虑后果这些
1.代码规范,开始实习培训阶段,代码不规范被说
2.团队沟通,前端组件有自己的组件库,我却用elementUI硬写,写到遇到问题不会问别人,才说有组件库
3.问题的解决能力和技术能力的提升

DTphere消息中心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
这个消息中心的项目,分为平台侧(管理的人去看的)和用户侧(用户去看的)
在平台侧呢
消息的管理

模版管理
--对不同的渠道,钉钉,微信,短信,邮件设计不同的消息模版
--设计实体关系图,一张表维护不同的消息模版Id
--每个Id作为外键关联一张模板表

渠道管理
--渠道管理负责去配置各个渠道的信息,短信,邮件,企业微信,钉钉机器人等
--引入钉钉,微信等相关依赖,配置用户名密码服务器去做测试链接
用户侧:
全部类型消息的查看
--搜索,查看,批量标记已读未读,删除消息,全部已读等
告警消息
--搜索,查看,标记已读未读,删除消息等
系统消息
--搜索,查看,标记已读未读,删除消息等
审批消息
--搜索,查看,标记已读未读,删除消息等
产品消息
--搜索,查看,标记已读未读,删除消息等

对需要删除的消息:用AOP日志记录
使用yapi.pro那个平台编写接口文档,做接口文档的开发

这个消息中心的项目,分为平台侧(管理的人去看的)和用户侧(用户去看的)

平台侧

  • 消息的管理
1
2
3
--绑定管理,将产品与消息模板绑定,当前产品对应的消息模板
--这里是新建一张绑定关系表,将产品Id和模板Id对应起来
查询模版、查询产品消息、绑定、回显
  • 模版管理
image-20240513091758796
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
 --对不同的渠道,钉钉,微信,短信,邮件设计不同的消息模版
--设计实体关系图,一张表维护不同的消息模版Id,
--每个Id作为外键关联一张模板表
--我在开始数据库中创建了五张模板表,分别对应五种消息
--(后来就是五张表维护起来太麻烦了,每次增删改查还要在不同表里
还要创建一个模版表去记录创建好的所有Id)
--优化就是:因为模版表有很多相同的字段,创建一张表整合所有字段,
没有设置的字段,就默认为null
然后再新建五个vo类作为返回给前端的消息实体类,去封装对应模版的信息

不同的消息模板有不同的字段,我根据它们提供的图去在数据库中创建模板
这里是参照了它们官方提供的开发手册
定义字段的时候,选择合适的字段内容来建标
字符串就结合存储的内容来使用char 或者varchar
用户名称邮箱类型的可变,使用varchar
文章内容使用text
邮编长度使用char等等
模版类型这里,因为只有几个嘛,开始用的int,后来该改的tinyint

优化这里
1.分页和摘要
因为text字段过大,表里只存放指针,MySQL还需要二次查询
会影响查询性能,所以我们做了一个分表
将text列分离到单独的扩展表中,用ID字段做一个关联
然后,创建一个varchar(255)字段,来记录text的前255个字符作为摘要
这样在分页查询的时候,文章内容我们就只需要查询这个varchar字段就行
点进去具体查询的时候,就需要关联text表查询text字段了

2.联合索引
因为我们每次查询的未读消息的SQL语句,的where条件是
where user_id = ? and is_read = fasle
所以我们对这表中的两个字段添加复合索引
CREATE INDEX idx_user_id_is_read ON messages (user_id, is_read);
增加查询的效率

3.过期策略
用户去查找太久远的消息的几率不大
所以就去做一个分表,定期将历史消息归档在历史表中
可以按照季度或者月份归档
我们前端点击查看历史消息的时候,才会去访问这张表
  • 渠道管理

​ 下图只是举例,

​ 我们是:邮件、短信、企业微信、钉钉机器人、钉钉联系人等(字段也不同)

1
2
3
4
5
6
7
8
9
10
11
12
--渠道管理负责去配置各个渠道的信息,短信,邮件,企业微信,钉钉机器人等
--引入钉钉,微信等相关依赖,配置用户名密码服务器等

我们负责测试,前端同学将这些参数封装在json中传递
我们后端负责接受这些参数,然后在后台调用依赖
尝试去获取消息,获取成功就测试成功

邮件的话:我们是链接163邮箱,配置服务器,端口,这里是去163官网查看了相关demo
短信:引入它们自己的api,提供的了网关插件地址,去做测试链接
钉钉机器人、联系人、企业微信:都是去官方调用官方API
看它们的示例demo,去做配置
引入了第三方的依赖,去做测试链接
  • 消息分类统计展示
1
2
从不同维度统计及展示消息发送情况
例如根据消息的通知渠道、来源产品、消息分类(告警类型、工单提醒、license提醒)等。

用户侧

主要是对站内信的处理(从它们数据库中获取)

  • 全部类型消息– 告警消息、系统消息、审批消息、产品消息
  • 未读消息
  • 已读消息

表的结构

1
2
3
消息表:存储站内具体消息相关内容
用户消息表:存储每个用户接收到的站内消息,消息状态,是否已读,是否删除等
用户表:所有的用户信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
这个就是对站内消息的处理
文档中给的需求是,查看,搜索,标记已读,删除,分页查找,批量删除等
并没有给我们接口文档。我们就先在实现的时候,根据功能编写接口文档
我们找了yapi.pro去编写

我们自己定义的规范就是,基于RESTful API定义的
用户侧就/user开头
说明请求参数,路径参数或者是实体参数开头的
定义Result返回值对象,返回标准的响应值(响应码,响应信息,响应数据data等)
数据库的实体参数作为DTO类,响应给前端的不一定是全部的字段
所以创建一个vo实体类,封装专门响应给前端的信息
(这里就用到了Hutul工具类的拷贝方法,不用一行一行复制很麻烦)

在删除的时候,因为涉及到数据库的操作,所以需要记录日志
(这里自定义了@logo注解和切面类@Aspect,用AOP的相关知识)
自定义注解@log和切面类@ASpect
注解表示在切面类中使用切面表达式,@Around,@Before前置循环后置,将注解引入进来
并写入插入数据库的逻辑
这样只需要在方法中加入自定义注解就可以实现记录日志操作的过程

难点:消息推送

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
难点:未读消息的推送:
我们一开始这里写的是,轮询
客户端定时的去服务器获取最新的消息
前端写一个定时器,定时的去请求后端未读消息的字段
去遍历数据库去查询,去查询未读的字段

轮询的话其实实时性不是太好
后来考虑过其他消息推送的方法
websocket和SSE

websocket是双向通信,但我们不需要客户端向服务端通信
所以选用SSE轻量级的推送

具体的做法就是
Server-Sent Events (SSE) 是一种轻量级的、基于 HTTP 的消息推送技术。
通过维持长连接,服务器可以实时推送消息。

具体的做法就是要做两步
1.订阅,将当前用户的Id传入,
通过map的映射方式,创建一个与当前用户关联的seeEmitter对象
放到map中去
2.推送:从map中获取到与当前id绑定的seeEmitter对象
调用send方法,将我的消息推送给用户
(高并发场景下可以考虑使用消息队列,做异步的推送)

广播消息

1
2
3
4
5
6
7
1. 广播消息的基本流程

创建广播消息:由管理员或系统创建消息,标记为广播消息。
向所有用户发送消息:为每个用户生成一条用户与消息的关联记录,或在展示时判断为广播消息直接显示。
用户读取消息:用户查看广播消息,更新相应的已读状态。
消息过期或撤回:广播消息在到达过期时间后自动失效或撤回。
查了广播消息的两种实现:

计网

OSI七层模型

计算机网络通信协议划分为七个不同层次的标准化框架

image-20241107001440318
1
2
tcp、ip是在传输层
http,https是在应用层

http和https的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
http协议就是客户端与服务端的超文本传输协议
http协议分为请求协议和响应协议
请求协议:浏览器将数据以请求格式发送到服务器
包括:请求行、请求头 、请求体
像get,post都在请求行中

响应协议:服务器将数据以响应格式返回给浏览器
包括:响应行 、响应头 、响应体
常见的响应状态码就在向银行中
1XX:响应中
2XX:响应成功
3XX:等待响应
4XX:客户端错误
5XX:服务端错误
区别的话
1.就是响应的端口号不同,http是端口80,https是端口443
2.https在http的基础上,通过SSL/TSL协议对数据加密
确保在数据传输的过程中的安全性,防止数据被窃取或者篡改

9. SSL/TLS 握手过程(HTTPS 专有)
在 HTTPS 通信开始时,客户端和服务器会进行一次 SSL/TLS 握手,主要步骤如下:

客户端请求:客户端向服务器请求建立安全连接。
服务器响应并发送证书:服务器响应并向客户端发送数字证书,证书包含服务器的公钥等信息。
客户端验证证书:客户端验证证书的合法性,确保服务器是可信的。
生成会话密钥:客户端使用服务器的公钥加密一个随机生成的对称密钥,发送给服务器。
加密通信:服务器使用私钥解密出对称密钥,之后双方使用该对称密钥进行加密通信。

tcp,ip协议,三次握手,四次挥手

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
tcp、IP是传输控制、网络协议
他是一种可靠的链接
他通过三次握手。建立可靠链接
四次挥手去释放链接

三次握手(TCP连接建立过程):
SYN: 客户端发送一个SYN(同步)包,请求建立连接,标志着客户端想要与服务器建立连接。
SYN-ACK: 服务器收到SYN包后,回应一个SYN-ACK包,表示服务器同意建立连接。
ACK: 客户端收到服务器的SYN-ACK包后,发送一个ACK包确认,连接建立成功。

四次挥手(TCP连接关闭过程):
FIN: 客户端发送一个FIN包,表示客户端没有数据发送了,准备关闭连接。
ACK: 服务器收到FIN包后,发送ACK包确认,表示服务器已准备关闭连接。
FIN: 服务器发送FIN包,表示服务器也没有数据发送了,准备关闭连接。
ACK: 客户端收到服务器的FIN包后,发送ACK包确认,连接完全关闭。

10.tcp和udp的区别

1
2
3
4
5
是两种传输程序协议
tcp:传输前三次握手,建立的是可靠链接,建立链接较慢,
用在需要可靠链接的地方,web端,电子邮件等
udp:传输无需建立链接,而是直接发送数据报,建立链接较快,
用在需要实时性高但是可以接受数据部分丢失的地方,直播,视频会议,游戏等

项目:学生点评:

1
开发了类似大众点评的单体web应用,包括店铺管理,优惠券下单,以及用户互动等功能

Redis的数据类型

以及每种数据类型的使用场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
String类型:Redis最常见的数据结构,可以存储任何数据,字符串,整形,浮点型都可以,
不管用哪儿种方式,底层都是以字节数组的形式存储的,适用于绝大多数场景

列表List:相当于Java中的linkedList,列表是链表结构,
可以做简单的消息队列功能,但一般不用Redis消息队列

集合分为集合和有序集合,

集合set:没有重复元素的集合嘛,一般用于点赞,我们维护一张点赞的表,
每次用户请求点赞的时候,我们先用StringRedisTemplete.opsForSet()
得到操作Redis中set集合的接口,
调用其中的isMember(),去判断点赞是否存在,
不存在就用add加入,再跟新到数据库,存在就返回已经点赞

共同好友也是,在关注的时候,把当前ID的关注列表以set形式存储到Redis中,
然后,用set求交集的interset()
就可以求得共同的好友交集,

有序集合sorted set:相对于集合set,是有顺序的,
每个元素都会关联一个权重,按照权重进行排序,
我主要用在点赞的排行傍等,使用的权重是时间,点赞时间早的会排序在前面,
用ZSET的range(key,0,4)方法得到排行前几的,
当然不同的排行傍会有不同的权重,可以是分数或者其他,使用权重排序的方式是不变的


哈希hash:键值对,适合缓存对象的存储,
因为我们Redis本来就是以键值对key-value的形式存储的,
一般hash存储用在value属性中,value属性存储的也是一对键值对

位图bigMap:用于存储二进制数据,可以位运算,统计用户签到比较方便

地理位置GEO:用于存储地理位置的数据结构,我们在数据库中将存放地理位置坐标的位置信息用stringRedisTemplete.opsForGeo接口的add方法存储到Redis中
,再调用Geodistance查找距离,GeoRadius查询一定范围内的餐厅
(这个需要先把商铺位置数据存储到Redis中去)

HyperLogLog:基于基数的数据结构,支持对大量元素的去重统计,占用和误差很小,
用于网站的访问统计等,底层是概率估算,
我们首先要在线程池里获得当前用户的唯一标识,
针对访客数,和访问量选取不同的标识
针对访客数,因为是不可重复的,可以选用唯一账户ID等
针对访问量,可以重复,当前线程中拿到的cookie即可


项目具体功能的实现逻辑

1.短信验证码的发送,登录、注册、校验

1
2
3
4
5
6
7
8
9
10
发送短信验证码:对手机号进行格式校验,然后随机生成验证码,
引用阿里云的依赖并做配置文件的配置,然后响应给前端

登录:得到手机号和验证码之后,去判断验证码是否正确,正确就登录成功,
再去数据库中判断是否存在,不存在就创建一个用户,保存到数据库中,
同时用uuid生成一个唯一的token作为key去在Redis中存放用户数据,设置一个过期时间

校验登录状态是:设置一个拦截器prehandle,去拦截登录请求,
获取token,去Redis中判断token是否存在,如果存在的话,就保存当前用户到线程池当中,
这样我们后续在其他地方获取当前用户信息就只需要在线程池中获取即可
1
2
3
4
5
6
7
cookie和session会话跟踪技术,去管理用户的状态,因为HTTP响应是无状态的,
每次请求都是独立的,下次的请求不会携带上次的数据,所以需要会话跟踪技术
cookie是客户端会话跟踪技术,存储数据在客户端浏览器中

session是服务端会话跟踪技术,数据存储在服务端中
底层通过cookie发送,只需要发送JessieId就可以了,在服务端就可以找到对应Id所在的当前数据
相比于cookie会更安全一些

2.Redis分布式锁的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
缓存穿透:就是大量请求的数据在缓存和数据库中都不存在,就会造成给数据库请求压力大的情况

解决方法有:
缓存空对象在缓存中
布隆过滤:就是在Redis和客户端之间再加了一层布隆过滤器,存在则通过,不存在则拒绝,
布隆过滤器是用hash思想实现的,它底层是一个庞大的二进制数组
来了一个key之后对这个key取模存入数组
再来就可以判断它key是否存在了
当然有hash的地方就不可避免有hash冲突,但因为它底层数组够大,冲突不超过5%

缓存击穿:缓存击穿就是,热点击穿嘛,热点key失效,很多线程来重建key,造成数据库的压力
解决方法就是
1.互斥锁,最先来的线程获取锁,去执行key的重建,其他的线程就会被阻塞
2.逻辑过期:就是存储一个过期的时间的字段到缓存中,但我们并不设置过期时间
当请求达到的时候,从缓存中获取数据,去判断一下这个过期字段,若过期则认为失效
失效的时候,就开启另外一个线程,去更新缓存的逻辑过期时间
当前线程就返回旧的数据
利弊:互斥锁保证了数据的一致性,但是会收到锁竞争的影响,考虑死锁的问题
逻辑过期保证了高的可用性,但是可能会出现数据不一致的问题

缓存雪崩:就是我们设置key的时候,设置了一样的过期时间,导致一起失效,数据直接打到数据库的时候
缓存雪崩与击穿不同的是,击穿是一个热点key失效,雪崩是很多key通用失效
解决方法就是:在原有的失效时间的基础上,随机增加几分钟的随机值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
分布式锁的实现:简单来说就是用setnx这个方法,去在Redis中设置锁,
如果插入key成功别人就获取不到,相当于加锁
setex获取锁的过期时间,最后在完成业务逻辑之后,再去删除释放锁

因为执行逻辑和删除是有时间差的,为了确保锁的一致性,防止极端宕机情况,
最好使用lua脚本去执行锁的创建和删除
lua脚本在Redis中执行是单线程的,所以一定会确保执行的时候的原子性

当然自己实现的还是会有很多考虑不到的地方,比如可重入啊,可靠啊等等
后面都是引入Redis依赖,用redisson的分布式锁实现的
底层会有看门口机制解决死锁的问题,死锁就是业务逻辑异常导致锁无法释放的问题
看门狗机制会在获取锁成功后启动任务,更新锁的过期时间,避免死锁的发生

乐观锁和悲观锁:
悲观锁:假定一定会发生多线程的问题,就只允许单线程执行,比如Synchronized关键字
乐观锁:允许多线程,但是会有一个版本号,只是在更新数据的时候,
判断是否其他线程对数据进行了修改,是的话重试即可

3.lua脚本实现高并发环境下的一人一单和线程安全问题

1
2
3
4
5
6
7
8
9
就是在lua脚本中去定义本地的处理逻辑
检查本地的数量是否足够
判断用户是否已经下单
再去减库存
返回操作结果

rabbitMQ消息队列:
生产者去创建一个交换机,交换机负责投递消息到哪儿一个队列,队列再把消息传给消费者处理
用注解@RabbitListener去配置

4.点赞,共同关注,排行榜

5.地理位置下的店铺查询

6.统计访问量和访客量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
集合分为集合和有序集合,

集合set:没有重复元素的集合嘛,一般用于点赞,我们维护一张点赞的表,
每次用户请求点赞的时候,我们先用StringRedisTemplete.opsForSet()
得到操作Redis中set集合的接口,调用其中的isMember(),
去判断点赞是否存在,不存在就用add加入,再跟新到数据库,存在就返回已经点赞

共同好友也是,在关注的时候,把当前ID的关注列表以set形式存储到Redis中,
然后,用set求交集的interset()
就可以求得共同的好友交集,

有序集合sorted set:相对于集合set,是有顺序的,
每个元素都会关联一个权重,按照权重进行排序,
我主要用在点赞的排行傍等,使用的权重是时间,
点赞时间早的会排序在前面,用ZSET的range(key,0,4)方法得到排行前几的,
当然不同的排行傍会有不同的权重,可以是分数或者其他,使用权重排序的方式是不变的


哈希hash:键值对,适合缓存对象的存储,
因为我们Redis本来就是以键值对key-value的形式存储的,
一般hash存储用在value属性中,value属性存储的也是一对键值对

位图bigMap:用于存储二进制数据,可以位运算,统计用户签到比较方便

地理位置GEO:用于存储地理位置的数据结构,
我们在数据库中将存放地理位置坐标的位置信息用
stringRedisTemplete.opsForGeo接口的add方法存储到Redis中,
再调用Geodistance查找距离,GeoRadius查询一定范围内的餐厅
(这个需要先把商铺位置数据存储到Redis中去)

HyperLogLog:基于基数的数据结构,支持对大量元素的去重统计,占用和误差很小,
用于网站的访问统计等,底层是概率估算,
我们首先要在线程池里获得当前用户的唯一标识,
针对访客数,和访问量选取不同的标识
针对访客数,因为是不可重复的,可以选用唯一账户ID等
针对访问量,可以重复,当前线程中拿到的cookie即可

1
2
3
开始使用了session做登录校验,从当前线程里获取到session但是session在分布式中不能共享,所以
集成阿里云的短信服务,就是引入了阿里云的依赖,先是对,做了配置文件的配置,

Redis,MQ的相关面试题

Redis和MySQL如何做读写一致性

1
2
3
4
5
6
7
8
9
10
11
12
不一致的原因有:
1.更新缓存<->更新数据库的操作,当一方发生失败导致不一致
2.缓存过期或者缓存失效导致不一致
针对以上原因呢,常用的策略分两种:强一致性、最终一致性
强一致性
1.惰性加载策略:
读的时候:缓存未命中,去数据库重建缓存
写的时候:写入数据库,删除缓存就行
2.延迟双删:
更新数据库,删除缓存,等待一定时间,再删除缓存
最终一致性:
3.最终一致性:使用消息队列异步处理

Redis实现缓存优化

1
2
3
4
1.Redis中有多重的数据结构嘛,String,list,hash。set,zset等
针对具体的场景去使用具体的字段
2.对缓存击穿,缓存穿透,缓存雪崩的一个处理方式
3.预加载,在系统启动的时候,对热点数据的放入Redis中

Redis实现分布式锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
分布式锁的实现:简单来说就是用setnx这个方法,去在Redis中设置锁,
如果插入key成功别人就获取不到,相当于加锁
setex获取锁的过期时间,最后在完成业务逻辑之后,再去删除释放锁

当然自己实现的还是会有很多考虑不到的地方,比如不可重入,不可重试,超时释放等等
后面都是引入Redis依赖,用redisson的分布式锁实现的

不可重入问题:所以当一个线程,里去调用多把锁的时候,
就会出现第一把锁获取成功,其他锁获取失败的情况
解决:通过设计计数器的方式解决的,如果同一线程再次请求锁,
Redis会检查该线程的标识符,并增加计数器。只有在计数器为0时,
其他线程才能获取锁。这种机制允许同一线程多次获取锁,
而不会导致死锁,从而实现可重入性。

不可重试:获取锁失败后的重试机制
解决:获取失败后,在指定的时间间隔内重试
超时释放:防止锁的持有者未示释放锁,导致死锁问题
解决:看门狗机制
定时的发送续约请求延长锁的有效期,时间是过期时间的1/3
如果线程崩溃未能续约,就到期自动释放
确保其他线程能获取锁

乐观锁和悲观锁:
悲观锁:假定一定会发生多线程的问题,就只允许单线程执行,比如Synchronized关键字
乐观锁:允许多线程,但是会有一个版本号,只是在更新数据的时候,
判断是否其他线程对数据进行了修改,是的话重试即可

Redis解决高并发场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
这里是我在找实习前做的一个开源项目
里面有一块优惠券秒杀的并发场景
具体流程是:
1.在秒杀开始的时候,将优惠券信息存放到Redis中
检查本地的数量是否足够
判断用户是否已经下单
再去减库存
给用户返回抢购操作结果

异步的通过消息队列
去执行订单的创建过程
rabbitMQ消息队列:
生产者去创建一个交换机,交换机负责投递消息到哪儿一个队列,队列再把消息传给消费者处理
用注解@RabbitListener去配置

Redis持久化是怎么做的呢

1
2
3
两种持久化方案:
1.快照文件,把Redis存储的数据保存到磁盘上,方便宕机的时候做数据恢复
2.追加文件,Redis操作写操作的时候,会存储到这个文件中去

Redis的数据过期策略

1
2
3
惰性删除:一般不管,我们使用到key后,如果判断过期再删除
定期删除:每隔一段时间对key进行检查,定期清理
一般Redis中的过期删除策略,是结合这两部分来的

Redis淘汰策略

1
很多,一把用LRU,对最近少使用的key淘汰

Redis集群

1
2
3
主从复制:数据从一个主节点复制到其他节点
哨兵模式:哨兵作为独立的节点监控集群,发生故障时候执行故障转移
分片模式:将数据分片到多个节点上

项目部署

Linux常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

文件操作:ls,ll,
进入目录:cd.. cd/ cd
显示路径:pwd 创建目录 mkdir
删除:rf 复制:cp 移动:mv
chown修改目录权限

top / htop:查看实时系统资源使用情况。
df -h:查看磁盘空间使用情况。
du -sh <directory>:查看目录大小。
free -m:查看内存使用情况。
uptime:查看系统运行时间和负载。
uname -a:查看系统信息。
date:查看或设置系统时间。

等等
ps 显示所有进程
top 显示CPU的占用
pgrep按照进程的名称查找ID
find查找文件或目录,grep查找文件中的内容。

项目部署docker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
使用docker,我们需要将项目打个包为镜像,镜像包含应用的本身,也包含运行的环境依赖等等
docker在运行的时候会创建一个容器,运行我们的镜像文件

使用docker很方便
我用docker在我的虚拟机中创建运行了
MySQL
Redis
rabbitmq
openjdk
nginx
注意的是:Redis和MySQL需要进行目录的挂载
将容器内的目录映射到外边来
这样我们删除创建容器的时候,数据不会丢失

并且将自己的项目文件用Maven打包,构建dockerFile构建镜像
然后再执行构建镜像即可
Dockerfile
# 选择基础镜像
FROM openjdk:11-jre-slim
# 设置工作目录
WORKDIR /app
# 将本地代码拷贝到容器中
COPY target/myapp.jar /app/myapp.jar
# 暴露应用运行的端口
EXPOSE 8080
# 设置启动命令
ENTRYPOINT ["java", "-jar", "myapp.jar"]

nginx

1
2
3
4
启动nginx,在配置文件中修改参数
监听我程序运行的端口80
然后反向代理给启动的两个服务:8080和8081
选择负载均衡的方法有:轮询,最少连接,哈希等

数据库MySQL

MySQL和Oracle区别

1
2
3
4
5
6
7
8
9
10
11
我平时自己使用的是MySQL
在公司使用的是Oracle
其实他们作为关系型的数据库
在使用sql去操作方面几乎是一样的
在事物索引方面也都支持
就是建表的时候的语句不同
Oracle可能作为一种商业软件
稳定性和提供技术支持会好一些

在 MySQL 中可以使用 LIMIT 来截取数据,
而在 Oracle 中则需要使用 ROW NUM 或者 ROW_NUMBER() 来实现类似的分页效果。

数据库事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
一系列的数据库操作被当作一个单独的逻辑单元,要么全部执行,要么全部回滚
数据库事务是为了确保事务的完整性和一致性
数据库事务由四个属性组成ACID
原子性:要么全部执行,要么全部回滚
一致性:事务执行前后,数据库的数据处于一致状态
隔离性:每个事务独立于其他事务
持久性:事务执行成功,就是持久的

在sql中使用 start Transaction作为事务的开启,rollback回滚,commit提交
在spring框架中,在service层使用注解@Transactional开启事务

MySQL事物的隔离级别
未提交读:可以看到没有提交的事物
已提交读:只能看到已经提交的事物
可重复读:默认,重复读取结果一样
串行化:串行读取,隔离级别最高,性能最差

索引:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
索引是一种特殊的数据结构,用于加快数据库中数据的查询速度
类似于书中的目录,可以帮助快速查找目录

索引分为:

使用create index ...去为字段创建索引
一般来讲是where子句中频繁出现的列,在表的特定列上创建索引

主键是会自动创建索引的
索引的底层是通过B+树来存放的,我们创建索引时候
会将当前字段插入B+树
B+树是一个多路平衡查找树,
所有叶子节点包含全部的键相互连接,方便查找
非叶子节点只做索引,不含实际数据

1
2
3
4
在并发访问时候,解决数据一致性和有效性的机制
全局锁:对整个数据库加锁,在备份的时候加锁 flush tables with readlock
表级锁:锁表 lock tables..
行级锁:锁行 lock

慢查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
我们一般去排查慢查询的sql语句可以用到两个
1.就是在MySQL中开启慢查询日志我们
需要在配置文件中,指定查询时间的阈值
这个超过查询时间的sql就会被记录
我们再用explain关键字去分析查询语句
2.第三方的一些监控工具
去监控数据库的性能指标

我在实习中
我们更多的是关注于接口的响应时间
在需求书中对查询的要求就是<2秒
我一般在开发的时候,就会用sql优化的思想
避免去写一些慢的sql
比如说
1.避免使用select*这样查询额外的数据
2.使用索引的时候,避免写一些索引失效的例子
3.避免一次性join三张以上的表等等

sql优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1.建标的时候选择合适的类型
2.使用索引
3.sql语句的编写
尽量避免select*的情况,查询具体的字段
尽量少用子查询,用join关联查询代替
尽量不使用or,导致索引失效,分开几条sql写
执行的时候,批量插入,而不是一条一条插入,分批删除
避免索引失效的写法
4.主从复制
5.读写分离

建标选择合适的类型
参考的是阿里云的开发手册,定义字段的时候,选择合适的字段内容来建标
字符串就结合存储的内容来使用char 或者varchar
用户名称邮箱类型的,使用varchar
文章内容使用text
邮编长度使用char等等

sql的执行顺序

1
2
3
4
5
6
7
8
9
10
select * from where groupby having orderby limit

先执行from语句,查询确定查的表
再是链接条件,join on
再是where 过滤符合条件的
再groupby分组
having分组后过滤
最后排序,limit输出

where是分组前过滤,having是分组后过滤

MySQL的读写分离如何实现

1
2
3
4
5
6
读写分离的核心是通过主从复制实现数据同步。
主库负责写操作,从库负责读操作
主从复制是通过二进制文件binlog实现的
就是说:主库上的数据变更,插入更新删除,都会记录在binlog中
从库区读取binlog日志,按顺序执行这些操作
从而是的从库与主库保存一致

MySQL的事物隔离级别

1
2
3
4
5
6
7
MySQL的事物隔离级别就是:定义了一个事物的执行过程中
能看到其他事物对数据库的更改程度
MySQL支持四中事物隔离级别
读未提交:读其他事物未提交的数据
读已提交:只能读其他事物提交的数据
可重复读(默认):事物内多次读取同一数据,结果是相同的
串行化:事物完全串行化执行,避免发生事物冲突

数据库的三范式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
对数据库规范化的一种定义嘛
目的是保证数据的一致性,减少数据的冗余性
第一范式(1NF)规定:
数据表中的每个字段必须是原子性的,不可再分。
比如说我们有一个商品列,记录的数据内容是5台电脑,那这就不符合第一范式了,
因为五台电脑这个字段可以分为商品数量 5台 和商品名称 电脑 两个字段。

所以说1NF这意味着一个字段不能包含多个值或重复的组合。1NF是所有关系型数据库的最基本要求。


第二范式(2NF)规定:
首先,数据表必须符合第一范式(1NF)。
其次,数据表中的非主键字段必须完全依赖于主键,部分依赖都不行。

如果有一个表(学生ID,课程ID,成绩)存储学生信息和课程成绩,
成绩字段只依赖于学生ID和课程ID的组合主键,而不是单独依赖于学生ID。
此时可以将表分成两个:一个存储学生信息,另一个存储课程成绩。
所以说2NF这意味着一张表只能描述一件事情。

第三范式(3NF)规定:
首先,数据表必须符合第一范式(1NF)和第二范式(2NF)。
其次,数据表中的非主键字段之间不能存在依赖关系。
也就是说不能存在某非主键字段 A 可以确定 某非主键字段 B。

如果有一个表(学号,学生姓名,学生地址,学生班级名称),
其中“班级名称”依赖于“班级ID”,而“班级ID”又依赖于“学号”。
这种情况下,可以通过将“班级名称”移到另一个表中,减少冗余数据。

spring Web框架

spring框架

1
2
3
4
spring是Java开发的框架,有很多模块
spring框架的web开发模块,就是springMVC
springMVC + spring framework + Mybatis 就是俗称SSM
springboot整合了ssm,快速开发

springMVC

1
2
3
4
5
6
7
8
9
10
11
12
13
spring MVC是Java中的web应用的框架
采用了 model-view-control的结构
来简化web端的开发
model层:就是模型层,一般是数据库的实体、数据传输对象
view层:视图层,接受控制器中传来的模型数据
根据数据生成HTML的响应格式,返回给用户
Controller:控制层:处理用户请求,查询数据,选择合适的视图展示

整个处理流程就是:
control接受到http请求,
根据请求的 URL 和参数,确定需要执行的业务逻辑,
调用相应的服务层,获取模型数据,
并将模型和视图进行结合,最终返回响应给用户。

IOC控制反转

1
2
3
4
5
是一种设计模式,将原本在程序中手动创建对象的控制权,交给spring框架来管理
IOC容器就是工厂一样,当我们需要创建一个对象的时候
只需要用注解@Autowired,而不需要考虑对象是如何创建出来的

Bean指的是被IOC容器所管理的对象

AOP面相切面编程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
AOP是spring的核心框架之一,它在程序运行时动态将额外的行为
比如记录日志操作,事务处理,方法运行时间等功能,额外的插入到代码中

它是通过动态代理的方式实现的
JDK 动态代理:适用于实现了接口的类,代理对象会实现相同的接口,通过代理类执行增强逻辑。
Spring 利用 java.lang.reflect.Proxy 生成代理对象。
CGLIB 动态代理:适用于没有实现接口的类,CGLIB 通过生成目标类的子类并重写其方法来实现代理。
Spring 会在需要时使用 CGLIB 代理。

我在项目中用AOP的实现过程,对数据库修改操作的日志记录的实现过程
创建操作日志的表格嘛,操作人Id,时间方法参数等
根据表格创建实体类
用@interface去自定义注解,用于标记需要记录日志的方法
注解的格式:
public @interface 名称{
public 属性类型 属性名称() default 默认值;
}
注解表示在切面类中使用切面表达式,@Around,@Before前置循环后置,将注解引入进来
并写入插入日志的业务逻辑就行
这样只需要在方法中加入自定义注解就可以实现记录日志操作的过程

spring框架用到了哪儿些设计模式

1
2
3
4
5
6
7
8
9
10
11
12
单例模式:spring中的bean是单例的,通过IOC容器对Bean管理,保证整个应用只有一个Bean实例
工厂模式:springIOC负责容器的创建和管理实例,可以将IOC看做是一个工厂
我们不需要知道对象是如何创建的
只需要用注解直接注入对象就行

代理模式:spring的AOP面相切面编程就用到了代理模式
实现proxy类的方法,生成代理对象,在其他地方通过代理对象去调用方法
我在项目中遇到的
流程实例发布后,会创建事件对象给spring事件系统
然后就是使用注解@EventListener标注方法去监听
判断是不是 当前流程的事件
如果是的话,就调用通知类中的方法,去发送通知给需要审批的人员

spring事务

1
2
3
@Trancsacyional注解
默认是required
事务的传播,如果当前有事务,就加入该事务,如果没有,就创建新的事务

spring的常用注解

1
2
3
4
5
6
7
8
9
10
11
12
13
最先就是启动注解@SpringBootApplication了,里面包含三个注解
然后就是三层架构的
@RestController @Service @MApper

在controller层
去处理请求HTTP相关的注解
@GetMapping @PostMapping @DeleteMapping等
传值相关的注解
@RequestBody @PathVariable @RequestParam等
mapper层的数据库注解
@Insert @Select @Delete 等等

还要通用的 @AutWired @configuration @Transcactional等等

mybatis 和mybatisPlus的联系区别

1
2
3
4
5
6
7
8
9
mybatis是用Java操作数据的的持久层框架,主要使用xml或者注解来配置sql语句
使用时,会创建数据库连接池,解决了资源重用,提示响应

MP内置了增删改查,通过扫描实体类,基于反射获取实体类的信息,作为数据库的信息表
一般来讲,单表的增删改查就有MP实现
多表的联合查询就没法用MP了,用mybatis去写

MP自带了分页查询插件,不用去引入第三方的依赖,很方便
创建一个Page<>对象,然后调用selectPage()方法

JVM面试题

img

内存模型:各个部分的作用,保存哪儿些数据

类加载:双亲委派加载机制,常用加载器分别加载哪儿种类型的类

GC:分带回收的思想依据

性能调优:JVM工具,性能分析工具

执行模式:解释模式,编译模式,混合模式等

编译器优化:javac的变异过程

JVM内存模型

Java 运行时数据区域(JDK1.8 )

线程私有:栈,本地方法栈,程序计数器

1
2
3
4
5
6
7
8
9
10
11
12

又称方法栈,线程私有的,线程执行方法是都会创建一个栈帧,
用来存储局部变量表、操作栈、动态链接、方法出口等信息。
调用方法时执行入栈,方法返回时执行出栈

本地方法栈
与栈类似,也是用来保存执行方法的信息,
执行Java方法是使用占,执行Native方法时是使用本地方法栈

程序计数器
保存当前线程执行的字节码位置,每个线程工作时都有独立的计数器,
值为执行Java方法服务,执行Native方法时,程序计数器为空

线程共享:堆,方法区

1
2
3
4
5
6
7
8
9
10
11

JVM内存管理最大的一块,堆被线程共享,目的是存放对象的实例,几乎所有对象的实例都会放在这里。
当堆没有可用空间时,会抛出OOM异常(Out of Menory内存溢出),根据对象的存活周期不同,
JVM把对象进行分代管理,由垃圾回收器进行垃圾的回收管理
新生代内存(Young Generation)
老生代(Old Generation)
永久代(Permanent Generation)、1.8版本:元空间

方法区
又称非堆区,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器优化后的代码等数据
1.7的永久代和1.8的源空间都是方法区的一种实现

类的加载和卸载

image-20240606101850630
1
2
3
4
5
6
7
8
9
加载:通过类的全限定名,查找此类的字节码文件,利用字节码文件创建Class对象

链接:分为三个阶段
验证:确保Class文件符合当前虚拟机的要求,不会危害到虚拟机自身安全
准备:进行内存分配,为static修饰的类变量分配内存,并设置初始值(0或null),
不包含final修饰的静态变量,因为final变量在编译时就分配好了
解析:将常量池中的符号引用替换为直接引用的过程,直接引用为直接指向目标的指针或者相对偏移量等
初始化:主要完成静态块执行以及静态变量的复制,先初始化父类,再初始化当前类。
初始化是懒惰的,只有对类主动使用的时候才会初始化

加载机制:双亲委派机制

1
2
3
4
加载机制:双亲委派模式
当一个类加载器收到类加载请求时,它首先会将这个请求委托给父类加载器去处理。
如果父类加载器无法加载该类,则该类加载器才会自己去加载这个类。
优点:避免类的重复加载,避免Java的核心API被篡改

卸载过程

1
2
类卸载的实现依赖于JVM的垃圾回收机制。当一个类不再被引用时
JVM可能会通过垃圾回收机制将该类的实例回收

对象的创建过程

1
2
3
4
5
6
7
8
9
Java对象的创建过程可以概括为以下几个步骤

类加载:JVM会先检查类是否已经被加载了,如果没有则通过类加载器加载类的class文件,并将类的信息存储到方法区中
内存分配:当类被加载后,JVM会为该类的对象分配内存,根据Java对象的特点,
内存大小是在编译时就已经确定的,因此内存分配可以通过一些简单的算法来实现,例如指针碰撞和空闲列表等
初始化:内存分配完成后,JVM会对对象进行默认初始化,即将对象的成员变量赋上默认值。
基本类型的默认值是0或false,引用类型的默认值是null
构造函数:默认初始化后,JVM会调用该对象的构造函数,进行对象的属性初始化和一些其他操作
返回地址:构造函数执行完毕后,JVM会将对象的引用返回给调用者,此时对象创建过程完毕

JVM中的垃圾回收机制,大致说一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GC是JVM的垃圾回收机制
首先GC有多种算法:
标记清除:遍历整个堆,清除所有未标记的对象,将其内存空间释放。
标记压缩:将所有存活的对象压缩到堆的一端,保持连续的内存空间,清除端释放未使用的空间。
复制算法:将堆内存分成两部分,活动对象从一个区域复制到另一个区域,不活动的对象直接丢弃
(新生代)
堆内存区域划分:
新生代,老年代,元空间
GC的过程:
minor GC:年轻代满的时候发生,主要复制算法
Full GC:老年代空间不够用的时候

垃圾收集器
JVM 提供了多种垃圾收集器,不同收集器适用于不同的应用场景:

Serial 收集器:适用于单线程环境,简单高效。
Parallel 收集器:适用于多线程环境,采用并行的方式进行垃圾回收。
CMS(Concurrent Mark-Sweep)收集器:适用于低停顿时间的应用,主要用于老年代的垃圾回收。
G1(Garbage First)收集器:适用于大内存和高吞吐量的应用,采用分区算法,能够并发和并行回收内存。

有看过GC日志吗

1
2
3
4
5
没看过
调用System.gc()方法后
在IDEA中的application配置那里
输入一个什么命令去查看
-Xlog:gc*:file=<path-to-log-file>:time,uptime:filecount=10,filesize=10M

如何判断一个对象是垃圾

1
2
3
1.没有被引用的指向时候
2,无法通过引用链到达的时候
3,没有重写finalize方法
1

多线程

线程池

1
2
3
4
5
6
7
8
9
10
11
12
线程池:管理线程的池子,可以容纳多个线程,省去了频繁创建线程的操作
Java中有几种常见的线程池,都是去实现了ThreadPoolExecutor这个类
用这个类的构造器,去设置参数,核心线程,最大线程,超时时长,任务队列等
可以直接用,然后重写run方法实现的

设计一个线程池嘛
1.创建N个线程
2.把任务提交给线程运行
3.线程满的话,就放入队列
4,空闲时,从队列去除执行

使用场景:异步处理的时候要用到线程池开启线程,并发处理的时候也需要

threadLocal

1
2
3
4
5
6
7
8
9
10
11
12
Thread Local本地线程嘛,就是线程可以独立的获取自己的变量副本
而不是共享变量,从而避免多线程的资源共享问题
一般就是在拦截器intercepter中去拦截,然后将当前用户的信息放进去
这样在其他地方就可以拿到用户信息
最后remove清理变量

原理:每隔线程都会维护一个ThreadLocalMap
用于存储ThreadLocal以及其数据
调用 threadLocal.set(value) 时,
当前线程的 ThreadLocalMap 会将 ThreadLocal 对象作为键,value 作为值存储起来。
获取值:调用 threadLocal.get() 时,
当前线程的 ThreadLocalMap 会通过 ThreadLocal 对象查找并返回对应的值。

进程。线程

1
2
3
4
5
6
7
8
进程就是计算机的运行实例,就比如说我启动一个服务就是一个进程
一个进程中会有多个线程
进程间相互独立,线程间共享进程的资源等

进程中的通信方式:共享内存,允许多个进程访问同一块内存区域
线程中的通信方式:共享变量,成员变量
使用wait方法进入等待状态,等其他线程notify通知
或者使用消息队列去传递消息

死锁

1
2
3
4
5
6
7
8
9
10
11
12
死锁就是:多线程的情况下,两个或者多个线程去竞争资源,
但是都获取不到锁,从而导致卡主无法执行的情况
死锁的四个必要条件:
1.互斥条件:就是说:我得到了你就不能获取
2.占有并等待条件:一个进程已经持有至少一个资源,
但又请求其他资源,并且这些资源被其他进程持有。
3.不剥夺条件:在使用完成之前,不能被剥夺
4.循环等待条件:A等B,B等c这样

我们要解决死锁,就要破坏他的条件:
1.破坏占有并等待,就是在运行前的时候分配全部资源
2.破坏循环等待,就是对资源的优先级进行编号按熟悉处理,

搭建网站的困难

1
2
3
4
5
6
7
8
9
10
11
1.基于hexo这样一种静态的heml博客生成框架,一开始的时候,部署在GitHub上
然后发现这样访问太慢了。后来就看了一些解决方案,部署在vercel上
2.性能优化,我的网页包含大量静态资源,图片,CSS,JS等
然后我部署图片将JPG等格式转为WEPG这样的格式,
把JS放在页脚做优化
3.组件的兼容性
样式和组件集成:使用npm引入组件时,可能会遇到组件兼容性问题,
别如说我再的进度条,还有分页组件等
就会有bug,后来也是换了一个组件解决了

特别是在不同浏览器和设备上显示效果不同

为什么搭建网站

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1,这个来源于机缘巧合吧,我在今年找实习那会儿牛可看别人简历有
我想到自己其实也比较喜欢记录
记录了很多笔记
我觉得这个是一个展示机会吧
展示自己的个人学习成长过程
所以就做下去了
2.因为学习也是与遗忘做斗争的事情嘛
我自己记录的,我回顾起来很方便
打开网页就能访问,方便我在知识遗忘的过程中做回顾
3.我正做开源项目的时候,会写历程
我会写一些面试总结,面试心得和经验这些
也帮助到我的同门他们,他们在做项目或者面试的时候也会看
这个算是对我来说是一种正向激励吧
4.自己也学到了一些技术吧
接触到了hexo这样的开源框架,
使用到阿里云的OSS,DNS
去做访问优化

static关键字

1
2
3
4
5
6
7
作为一种静态关键字,可以修饰
1.修饰属性:就是说当前对象属于类本身而不是某个对象
一般用到全局常量的时候会用到static修饰
2.修饰方法:当前方法属于类的方法不属于实例的方法
3.静态代码块:在类加载的时候执行一次,所以适合去做初始化
我在做导出到处校验的时候,就会定义一个common类
定义map做一些数据的映射

软件的设计原则

1
2
开放闭合原则
高内聚低耦合

vue2前端

1
2
3
4
5
6
7
8
9
10
11
12
前端这块,我在实习中主要是
一些页面的编写
引入他们一些自定义的组件
或者说是elementUI的组件
去配置页面的路由
配置方法的api
然后我去后端写方法
获取数值,返回给前端的方法

有些还需要前端处理一些数据
想有些组件除了数据还需要颜色
这些返回值

父组件向自组件传值用props

子组建向父组件传值用emit