身经百战——实战总结
面试题总结
1.token校验数据内部因素:
JWT令牌结构:Header、Payload、Signature三部分组成
Jwts.builder();
2.maven模式怎么切换环境
采用profile设定不同的开发环境
全局配置 maven路径conf/settings.xml
局部配置 pom.xml
pom多环境配置中我们的核心是利用编译过程中,读取不同的配置文件,然后再由spring去读取参数配置。而这里我们利用到变量${profileActive}
3.分布式环境下,多个服务器运行相同服务,如何保证服务不重复执行
定时任务监测,分布式任务调度xxl-job
4.Linux的使用
1.基础命令
mkdir,cp,mv,rm,chmod,cat,tail/head,less,more
linux文件的执行权限 :读,写,执行
linux文件的所属者:属主,属组,其他组
对于一般文件来说,权限比较容易理解:“可读”表示能够读取文件的实际内容;“可写”表示能够编辑、新增、修 改、删除文件的实际内容;“可执行”则表示能够运行一个脚本程序。但是,对于目录文件来说,理解其权限设置 来就不那么容易了。目录文件的权限设置。对目录文件来说,“可读”表示能够读取目录内的文件列表;“可写”表 示能够在目录内新增、删除、重命名文件;而“可执行”则表示能够进入该目录。
2.查看cpu、磁盘相关信息
iostart -x num1 num2
3.统计文件夹中文件个数:
*统计当前目录下文件的个数(不包含目录或子文件夹):
ls -l | grep "^-" | wc -l
*统计当前目录下文件的个数(包含子目录中的文件):
ls -lR | grep "^-" | wc -l
5.数据库的使用
索引的优化
sql的优化
数据库效率的优化
什么情况不是用索引或者少使用?
(1)数据量特别少的时候没必要,因为索引的使用需要额外的空间和维护
(2)频繁操做表中数据。比如增删改这样的数据表,不使用或少使用索引
为什么使用索引效率高?
索引进行了排序,有序的查就比无序的查速度快
6.Channel概述
Channel(通道):Channel是一个接口,可以通过它读取和写入数据, 可以把它看做是IO中的流,不同的是:Channel是双向的, Channel对象既可以调用读取的方法, 也可以调用写出的方法 。
NIO 中通过 Channel 封装了对数据源的操作,通过channel 我们可以操作数据源, 但是又不关心数据源的具体数据结构。这个数据源可能是很多种,比如,可以是文件,也可是网络 socket 。 在大多数应用中,channel 与文件描述符合或者 socket 是一一对应的。 channel 在字节缓冲区和位于通道另一侧的实体(通常是一个文件或套接字)之间有效的传输数据。
7.http和rpc的区别,rpc便捷,使用多
牛与马车的比较
rpc,远程过程调用,包括通信协议和序列化协议
通信协议:有http,tpc
序列化协议:有protobuf,thrift
http,超文本传输协议,通讯协议的一种
rpc和http的关系,前者是方法,后者是协议。不是一个类型,很难比较
传输协议:
rpc可以基于tpc,也可以基于http
http只能基于http
相对来讲,rpc更轻量级
传输效率:
rpc如果使用自定义的tcp协议,可以让请求的请求头信息更少
http的请求头有跟多无用的信息,比如refer,Keepalivetime,last-modified等信息
在流量大的时候,每次请求rpc节省几个字节,是十分可观的
性能:
rpc可以基于thrift或protobuf来实现高效的二进制传输
http也可以用protobuf,但目前主流的浏览器大部分都是JSON实现的
http性能会比rpc慢
使用场景
rpc:公司内部的服务调用,比如后端和后端直接。但是要求统一技术栈,比如大家开发都是用的java
http:对外的异构环境,比如你和淘宝对接,肯定使用http来交互的。还有前后端对接,比如浏览器的调用,app的调用。允许跨平台,跨语言,不同技术栈
8.#{},${}
#{}是一个占位符,相当于JDBC中的一个?,会对一些敏感的字符进行过滤 #{}底层采用的是PreparedStatement,会预编译(主要是里面的setString方法,对一些特殊的字符,例如’'单引号,会在值后面加上一个\\右斜线进行转义,让值无效),因此不会产生sql注入
&{},普通的字符串替换
sql注入问题,不是前端传值,都可以;前端传值,就考虑sql注入问题
9.反射
Class.forName
对象.getClass
类型.class
适用场景:自己写框架的时候
10.多线程
创建线程的方式
线程状态
死锁
11.mongodb
最像关系型数据库的非关系型数据库
12.业务场景:长链接生成短链接
长链接->压缩->存储数据库,并生成唯一标识并存储
访问长链接->通过携带参数去数据库找到对应值
做了一个301的重定向
13.有一个任务,定时某时间执行,如何补偿执行不到或者重复执行
设置是否执行的标识,采取轮询补偿方式
14.aop底层原理
15.spring注入方式(三种)
构造器注入
setter注入
注解注入
16.验证码防止恶意刷, 查ip,限制ip次数
17.复合索引也叫组合索引,一个索引包含多列,遵循最左前缀法 从左到右依次组合排列
a,b,c -> ab,ac,bc,abc,不会出现cb
18.线程池工作流程
19.拒绝策略
丢弃任务并抛出 默认
丢弃任务不抛出异常
不丢弃也不抛出,让调用者执行
20.redis使用 redis分布式锁 key过期策略
21.分布式环境保证多线程的安全
所以可以考虑使用分布式锁的方式来保证分布式中的线程的安全线,这样不同的服务不同的线程通过竞争分布式锁来获取共享资源的操作权限
22.gateway断言在哪配置
23.线程池那个参数导致OM问题 缓冲队列 ,核心线程数和最大线程数都有一个限制的
24.线程池分类
25.对称加密
26.token 清楚缓存
27.Spring 事务不回滚的几个原因 1、默认情况下spring事务只在发生未被捕获的异常 runtimeexcetpion时才回滚。
2、如果你抛出的异常是Exception,是不会回滚的,需要抛出一个RuntimeException
3、如果Service的第一层方法为非事务方法,则这个方法中调用当前类其他包含事务的方法,当发生异常时是不会回滚的。
解决方法时通过BeanFactory.getBean("***");重新获取当前类的一个实例,再调用对应的事务方法。
4、如果Service的第一层方法为非事务方法,则这个方法中调用其他类的包含事务的方法,数据可以回滚。
28.count(0/1/2) ,count(*), count(列名)
1、从执行结果上分析:
(1)、count(0)、count(1)和count(*)不会过滤空值
(2)、count(列名)会过滤空值
2、从执行效率上分析:
(1)、如果列为主键,count(列名)效率优于count(1)
(2)、如果列不为主键,count(1)效率优于count(列名)
(3)、如果表中存在主键,count(主键列名)效率最优
3、总结:
(1)、count(值),如果这个值不是null计1,如果这个值是null计0
count(0)、count(1)可以想象成在表中有一个字段,这个字段的值全是0或1
count()执行时会把翻译成字段的具体名字,效果同count(0)、count(1)一样,只不过多了个翻译的过程,效率相对会低一点
(2)、在用sum函数对某列进行求和的时候,可以先对该字段值为null的行进行赋值,以确保结果的正确性
注意:因为count(),自动会优化指定到那一个字段。所以没必要去count(1),用count(),sql会帮你完成优化的 因此: count(1)和count(*)基本没有差别!
28.Mybatis如何防止sql注入
mybatis底层实现了预编译,底层通过prepareStatement预编译实现类对当前传入的sql进行了预编译,这样就可以防止sql注入了。
29.MySql如何设置区分大小写
MYSQL区分大小写
1、linux下mysql安装完后是默认:区分表名的大小写,不区分列名的大小写;
2、用root帐号登录后,在/etc/my.cnf 中的[mysqld]后添加添加lower_case_table_names=1,重启MYSQL服务,这时已设置成功:不区分表名的大小写;
lower_case_table_names参数详解:
lower_case_table_names = 0
其中 0:区分大小写,1:不区分大小写
MySQL在Linux下数据库名、表名、列名、别名大小写规则是这样的:
1、数据库名与表名是严格区分大小写的;
2、表的别名是严格区分大小写的;
3、列名与列的别名在所有的情况下均是忽略大小写的;
4、变量名也是严格区分大小写的;
MySQL在Windows下都不区分大小写。
3、如果想在查询时区分字段值的大小写,则:字段值需要设置BINARY属性,设置的方法有多种:
A、创建时设置:
CREATE TABLE T(
A VARCHAR(10) BINARY
);
B、使用alter修改:
ALTER TABLE tablename
MODIFY COLUMN cloname
VARCHAR(45) BINARY;
C、mysql table editor中直接勾选BINARY项
30.aop在日常开发中最常见的两种使用场景是:
1.记录日志。记录日志是一方面,还可以利用aop实现拦截器的功能,例如:为某个应用提供接口,参数和响应内容都加密,我们可以在前置通知中进行参数解密,在后置通知中进行响应内容加密。 2.声明式事务处理
31.RequestMapping 和 ResponBody
@RequestMapping("/url"),其中url表示映射的地址即该类或者该方法的访问地址; 然而除了上述的路径的作用之外@RequestMapping还有一个作用就是将返回值解析为跳转路径,这样就可以返回我们所需要页面。 当我们在方法上添加ResponseBody注解的时候,方法的返回结果则不回被解析为跳转路径,而是直接写入HTTP response body,比如通过ajax异步获取json数据,当我们加上ResponseBody注解以后会直接返回json数据。 32.多个请求指向同一路径
@RequestMapping({"/myApi", "myApiOpen"})
@GetMapping({"/meth", "/myMe"})
33.数组扩容为什么2倍
取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&)操作,但是位&运算速率更高
负载因子0.75,是在时间与空间上进行了权衡
1、尽可能的减少元素位置的移动。
2、使元素均匀的散布hashmap中,减少hash碰撞。
34.线程池设置核心数量
cpu密集型任务:(cup核心数+1)
I/O密集型任务:2*cup核心数
35.http 和 https
HTTPS是一种应用层协议,本质上来说它是HTTP协议的一种变种。HTTPS比HTTP协议安全,因为HTTP是明文传输,而HTTPS是加密传输,加密过程中使用了三种加密手段,分别是证书,对称加密和非对称加密。HTTPS相比于HTTP多了一层SSL/TSL
https = http * 2
上述过程就是两次HTTP请求,其详细过程如下: 1.客户端想服务器发起HTTPS的请求,连接到服务器的443端口; 2.服务器将非对称加密的公钥传递给客户端,以证书的形式回传到客户端 3.服务器接受到该公钥进行验证,就是验证2中证书,如果有问题,则HTTPS请求无法继续;如果没有问题,则上述公钥是合格的。(第一次HTTP请求)客户端这个时候随机生成一个私钥,成为client key,客户端私钥,用于对称加密数据的。使用前面的公钥对client key进行非对称加密; 4.进行二次HTTP请求,将加密之后的client key传递给服务器; 5.服务器使用私钥进行解密,得到client key,使用client key对数据进行对称加密 6.将对称加密的数据传递给客户端,客户端使用非对称解密,得到服务器发送的数据,完成第二次HTTP请求。
总结:
(1)HTTPS是密文传输,HTTP是明文传输; (2)默认连接的端口号是不同的,HTTPS是443端口,而HTTP是80端口; (3)HTTPS请求的过程需要CA证书要验证身份以保证客户端请求到服务器端之后,传回的响应是来自于服务器端,而HTTP则不需要CA证书; (4)HTTPS=HTTP+加密+认证+完整性保护。
36.线程池, 一个线程执行完任务后等待其他线程也执行完之后再去执行新任务
(1) JKD并发包中的CountDownLatch类, 每个线程调用其countDown方法使计数器-1,主线程调用await方法阻塞等待,直到CountDownLatch计数器为0时继续执行.减技术方式,不可重复用
(2) 使用JDK并发包CyclicBarrier循环栅栏,CyclicBarrier是一个可循环的屏障,它允许多个线程在执行完相应的操作后彼此等待共同到达一个point,等所有线程都到达后再继续执行。加计数方式,可重复用
(3) 使用线程池自带join方法,将子线程加入到主线程,在子线程执行完之后,再执行主线程逻辑
37.linux查看大小
Linux查看文件大小的命令:du -sh <文件名>,
如一个文件名为test.txt的文件,查看命令如下:du -sh test.txt
38.truncate、delete、drop区别概述
1.delete与truncate都可以用来删除表中数据
2.delete删除你表中数据之后,再次插入数据索引会接着之前的,而truncate删除表中后重新插入数据索引会从初始大小开始。
3.delete在删除数据后会将删除操作作为事务存储在日志中,这样就可以进行事务回滚。而 truncate则不可以事务回滚。
它们 3 个的区别如下表所示:
区别点 | drop | truncate | delete |
---|---|---|---|
执行速度 | 快 | 较快 | 慢 |
命令分类 | DDL(数据定义语言) | DDL(数据定义语言) | DML(数据操作语言) |
删除对象 | 删除整张表和表结构,以及表的索引、约束和触发器。 | 只删除表数据,表的结构、索引、约束等会被保留。 | 只删除表的全部或部分数据,表结构、索引、约束等会被保留。 |
删除条件(where) | 不能用 | 不能用 | 可使用 |
回滚 | 不可回滚 | 不可回滚 | 可回滚 |
自增初始值 | - | 重置 | 不重置 |
39.外包字节试题
1.项目经验和开发经历通过自我介绍部分逻辑比较清晰,
2.Binlog用途 通过 原理阐述的比较清楚
Binlog它记录了所有的DDL和DML(除了数据查询语句)语句,以事件(EVENT)形式记录,还包含语句所执行的消耗的时间,MySQL的二进制日志是事务安全型的。binlog是事务提交的时候一次性将事务中所有的sql按照一定的格式记录到binlog中。而binlog更多的是用于复制,也就是在主从复制当中,从库要利用主库上的binlog进行传播,实现主从同步。还可以用于数据库基于时间点的还原,很多同步数据的中间件就是基于binlog去实现的
MySql中的redo log, undo log, bin log的区别
首先这三种日志的用途是完全不一样的
其中redo log 是事务开始之后就产生,并且redo log 落到磁盘并不是随着事务的提交才写入,而是在事务的执行过程中就开始写入了,而且redo log 保证事务的持久性,如果说mysql发生故障,比如系统宕机,尚有数据未写入磁盘时,在重启mysql服务的时候,就会根据redo log进行重做,从而达到事务的持久性概念
而undo log是在事务开始之前根据当前版本的数据生成一个undolog, 而且undo log可以保存事务发生前数据的一个版本,它可以用于回滚,同时也会提供多版本并发控制下的读,也就是mvcc(多版本并发控制)
最后是binlog, binlog是事务提交的时候一次性将事务中所有的sql按照一定的格式记录到binlog中。而binlog更多的是用于复制,也就是在主从复制当中,从库要利用主库上的binlog进行传播,实现主从同步。还可以用于数据库基于时间点的还原,很多同步数据的中间件就是基于binlog去实现的
2.慢查询解决步骤 可通过mysql配置、longquery time
mysql慢查询优化步骤:
(1)检查sql是否走到索引,如果没有,优化sql使其走到索引
(2)如果sql走了索引,要检查索引是否是最优的
(3)查询的字段是否是必须的,是否查了很多不需要的字段,导致返回数据很慢
(4)检查表中的数据是否已经足够多了,是否需要分库分表
(5)检查数据库实例所在的机器的性能
(6)读写分离
3.Hashmap如何实现 数组index 发生冲突 红黑树
4.并发锁阐述 偏向锁(乐观锁)、自旋锁
“锁主要用来实现资源共享的同步。只有获取到了锁才能访问该同步代码,否则等待其他线程使用结束释放锁。
通过锁机制,能够保证在多线程环境中,在某一个时间点上,只能有一个线程进入临界区代码,从而保证临界区中操作数据的一致性。临界资源 是一次仅允许一个进程使用的共享资源。临界区是每个进程中访问临界资源的那段程序。锁机制保证数据的一致性与安全性
偏向锁:通过cas实现同步的工具,由于不会锁定资源,当线程需要修改共享资源对象时,总会是乐观的认为,对象资源的状态值没有被其他资源修改过,而是每次都会自己去主动compare状态值,这种同步机制被称为乐观锁
悲观锁:每一次调用互斥锁都会将资源锁定,只供一个资源调用
自旋锁:能不能不对共享资源进行锁定,也能对线程调用进行协调
诞生了一种巧妙的算法,CAS, 比较并交互
oldvalue , 线程之前读到的资源的状态值
newvalue, 代表想要将资源对象的状态值更新后的值
自旋:不断尝试cas操作,通常配置自旋次数防止死循环
compare的时候要对资源进行绑定,换句话说,cas必须是原子性的。各种架构的cpu都提供了指令级别的cas原子操作
锁的级别:无锁 偏向锁 轻量级锁 重量级锁
5.代码考核 合并有序链表 整体实现
40. kafka高可用设计
1 生产端:acks(确认应答机制)和重试机制 2 服务端:主从备份机制和顺序写 3 消费端:如何解决重复消息问题
消费者重复消费产生原因: 1 生产者如果网络抖动相关问题,可能重复发送消息 2 消费者宕机、重启或者被强行kill进程,导致消费者消费的offset没有提交 3 kafka消费端会每隔5秒自动提交消费偏移量(auto.commit.interval.ms)如果网络问题没来及提交,其他消费者会重复消费消息 4 kafka消费者会每隔10秒向服务端发送心跳(session.timeout.ms)表明还活着,否则服务端认为消费者离组会触发重平衡,重平衡rebalance也会造成消息重复消费 消费者消息重复如何解决 1 kafak可以手工处理偏移量(不推荐) 2 从业务角度处理(实环情况不管采用任何消息中间件,重复消费都避免不了) 生产端 发送的消息添加唯一id(雪花算法,或者纳秒时间戳) 【注意默认不使用messageID,需要显式配置】 消费端 2.1 方案1 利用去重表去重判断 2.2 方案2 利用redis去重判断
4 优化:Kafka吞吐优化设计
1、增加分区数量、 2、异步发送消息、 3、消息压缩支持、 4、批量发送
41.kafka 删除机制
对于传统的message queue而言,一般会删除已经被消费的消息,而Kafka集群会保留所有的消息,无论其被消费与否。当然,因为磁盘限制,不可能永久保留所有数据(实际上也没必要)。 两种策略删除旧数据。一是基于时间,二是基于Partition文件大小。 例如可以通过配置$KAFKA_HOME/config/server.properties,让Kafka删除一周前的数据,也可在Partition文件超过1GB时删除旧数据。
42.es
创建倒排索引是对正向索引的一种特殊处理,流程如下:
-
将每一个文档的数据利用算法分词,得到一个个词条
-
创建表,每行数据包括词条、词条所在文档id、位置等信息
-
因为词条唯一性,可以给词条创建索引,例如hash表结构索引
倒排索引查询过程:
要先查询倒排索引,再查询倒排索引,但是无论是词条、还是文档id都建立了索引,查询速度非常快!无需全表扫描。
那么为什么一个叫做正向索引,一个叫做倒排索引呢?
-
正向索引是最传统的,根据id索引的方式。但根据词条查询时,必须先逐条获取每个文档,然后判断文档中是否包含所需要的词条,是根据文档找词条的过程。
-
而倒排索引则相反,是先找到用户要搜索的词条,根据词条得到保护词条的文档的id,然后根据id获取文档。是根据词条找文档的过程。
那么两者方式的优缺点是什么呢?
正向索引:
-
优点:
-
可以给多个字段创建索引
-
根据索引字段搜索、排序速度非常快
-
-
缺点:
-
根据非索引字段,或者索引字段中的部分词条查找时,只能全表扫描。
-
倒排索引:
-
优点:
-
根据词条搜索、模糊搜索时,速度非常快
-
-
缺点:
-
只能给词条创建索引,而不是字段
-
无法根据字段做排序
-
是不是说,我们学习了elasticsearch就不再需要mysql了呢?
并不是如此,两者各自有自己的擅长支出:
-
Mysql:擅长事务类型操作,可以确保数据的安全和一致性
-
Elasticsearch:擅长海量数据的搜索、分析、计算
因此在企业中,往往是两者结合使用:
-
对安全性要求较高的写操作,使用mysql实现
-
对查询性能要求较高的搜索需求,使用elasticsearch实现
-
两者再基于某种方式,实现数据的同步,保证一致性
43.抽象类和接口
抽象类必须有抽象方法,亦可以有普通方法。抽象类可以有main()方法,可以运行
jdk8中接口可以有默认方法和静态方法, 方法默认修饰符是public, 字段默认修饰符是public static final。接口也可以有main()方法,也可运行
44.mysql5 & mysql8d的区别
1.mysql8性能是mysql5的两倍。
2.mysql8支持创建隐式索引,当索引被隐藏的时候查询数据不会被优化器使用。可以隐藏索引测试下 效率是否降低,如果查询效率一样说明索引没用,可以考虑删除索引。
3.窗口函数,更多函数可以少写代码多实现功能。
45.并发数据库解决数据安全问题
版本号实现乐观锁的方式
46.防止sql注入
mybatis #{}
jpa ? ,:变量 具名参数
47.spring cloud 请求过程
(1)请求统一通过API网关(zuul/gateway)来访问服务内部
(2)网关接受请求后,从注册中心获取可用服务(Eureka/Nacos)
(3)Ribbon进行负载均衡后,分发到后端具体实例
(4)微服务直接通过Feign进行通信处理业务
(5)Hystrix负责处理服务超时熔断并降级
48.MQ消费者消息重复如何解决
1 kafak可以手工处理偏移量(不推荐)
2 从业务角度处理(实际情况不管采用任何消息中间件,重复消费都避免不了) 生产端 发送的消息添加唯一id(雪花算法,或者纳秒时间戳) 【注意默认不使用messageID,需要显式配置】 消费端 方案1 利用去重表去重判断 方案2 利用redis去重判断
49.[RabbitMQ]如何保证消息不丢失
出现消息丢失的情况
-
消息发送时由于网络异常导致消息发送失败
-
MQ把消息发送给消费者前宕机,存贮在内存中的消息丢失
-
消费者在处理完消息之前宕机,MQ认为消息已经发送成功,并把存储在内存中的消息删除,导致消息丢失
解决方案:
-
ack确认应答机制。MQ接收消息后通知生产者我已经接收到消息了(这个过程交ack回调)
-
消息实现持久化,将消息标记为持久化并不能完全保证不会丢失消息。如果需要更强有力的持久化策略,可以配合发布确认,来使用。当消息被持久化到磁盘之后再向生产者确认ack,可以有效的避免消息丢失。
-
消息应答机制,消息应答就是:消费者在接收到消息并且处理该消息之后,告诉 rabbitmq 它已经处理了,rabbitmq 可以把该消息删除了
50.redis 雪崩、穿透
缓存雪崩问题 缓存雪崩,即缓存同一时间大面积的失效,这个时候又来了一波 请求,结果请求都怼到数据库上,从而导致数据库连接异常。 解决方案: 1、给缓存的失效时间,加上一个随机值,避免集体失效。 2、使用互斥锁,但是该方案吞吐量明显下降了。 3、搭建 redis 集群。 缓存击穿问题 缓存穿透,即黑客故意去请求缓存中不存在的数据,导致所有的请求都怼到数据库上, 从而数据库连接异常。 解决方案: 1、利用互斥锁,缓存失效的时候,先去获得锁,得到锁了, 再去请求数据库。没得到锁,则休眠一段时间重试 2、采用异步更新策略,无论 key 是否取到值,都直接返回, value 值中维护一个缓存失效时间,缓存如果过期,异步起一个线程 去读数据库,更新缓存
51.Maven统一版本管理
方法一:在项目顶层pom添加version插件
org.codehaus.mojo versions-maven-plugin 2.3 false
在项目根目录下执行以下命令修改版本号
设置新的版本号为1.2.0-SNAPSHOT
mvn versions:set -DnewVersion=1.2.0-SNAPSHOT
以上命令会将maven-multily-module/pom.xml版本修改为1.2.0-SNAPSHOT,且会修改所有子模块内 parent的version为1.2.0-SNAPSHOT。所以建议子模块不设置version,自动从parent继承version即可
方法二:配合插件flatten-maven-plugin及${revision}属性进行全局版本号管理。 pom.xml配置
配置flatten-maven-plugin插件
<properties> <!--定义好版本号--><revision>1.0-SNAPSHOT</revision> </properties> <!--使用revision作为版本号传递--> <build><plugins><plugin><groupId>org.codehaus.mojo</groupId><artifactId>flatten-maven-plugin</artifactId><version>1.2.4</version><configuration><!-- 避免IDE将 .flattened-pom.xml 自动识别为功能模块 --><updatePomFile>true</updatePomFile><flattenMode>resolveCiFriendliesOnly</flattenMode></configuration><executions><execution><id>flatten</id><phase>process-resources</phase><goals><goal>flatten</goal></goals></execution><execution><id>flatten.clean</id><phase>clean</phase><goals><goal>clean</goal></goals></execution></executions></plugin><plugins> <build>
父模块引用
<groupId>com.version.demo</groupId> <artifactId>parent</artifactId> <packaging>pom</packaging> <version>${revision}</version> <modules><module>module-1</module><module>module-2</module> </modules>
子模块引用
<parent><groupId>com.version.demo</groupId><artifactId>child</artifactId><version>${revision}</version> </parent>
52.防止请求攻击 防止同一IP多次请求攻击。防止入侵者,通过死循环同一时间批量向服务器请求数据,导致服务器内存开销不断膨胀,最后直接瘫痪。
解决方法:
新增一个spring的拦截器 , 拦截所有请求,判断是否是多次请求 拦截器实现 HandlerInterceptor 接口,重写preHandle、postHandle、afterCompletion方法。
其他方案:
防止重放攻击:可以在每个请求中添加一个时间戳或随机数,并在服务器端进行校验,防止重放攻击。 使用验证码:可以在接口请求时要求用户输入验证码,防止自动化程序恶意请求接口。 限制请求频率:可以设置每个用户在一段时间内只能请求接口的次数,防止恶意程序疯狂请求接口。 IP地址限制:可以限制同一IP地址在一段时间内请求接口的次数,防止同一IP地址的用户恶意请求接口。 使用Token认证:可以在用户登录后,生成一个Token并在每次请求时携带该Token,服务器在收到请求时验证该Token的合法性,来保证请求的合法性。 使用SSL证书:可以使用SSL证书来加密接口数据,防止中间人攻击和数据被窃取。
知识点总结
-
hashcode 由内存地址通过一定算法得出
-
hashmap底层数组元素位置 hashcode%数组容量,因为hashmap的容量大小是2的幂次方,所以可以通过&运算来优化%运算hashcode&(数组容量-1)
-
重写equals()是因为父类equals方法比较的是哈希值,重写以后比较对象内容。
同时重写hashcode()方法,是为了通过算法实现equals相等的两个对象hashcode也相等。否则不同对象的hashcode一定不相等。
-
list集合去重的方法有哪些?
方法1:contains判断去重(有序)
list.forEach(i -> {if(!newList.contains(i)) { // 如果新集合中不存在则插入newList.add(i);}});
forEach()方法是Iterable<T>接口中的一个方法。Java容器中,所有的Collection子类(List、Set)会实现Iteratable接口以实现foreach功能。forEach()方法里面有个Consumer类型,它是Java8新增的一个消费型函数式接口,其中的accept(T t)方法代表了接受一个输入参数并且无返回的操作。
方法2:迭代器去重(无序)
Iterator<Integer> iterator = list.iterator(); while (iterator.hasNext()) { // 获取循环的值 Integer item = iterator.next(); // 如果存在两个相同的值 if (list.indexOf(item) != list.lastIndexOf(item)) { // 移除最后那个相同的值 iterator.remove(); } }
方法3:HashSet去重(无序,但本质上是有序的)
方法4:LinkedHashSet去重(有序)
方法5:TreeSet去重(有序,底层数据结构是红黑树)
方法6:Stream去重(有序)