## ShedLock
ShedLock不是一个`定时任务`框架,而是一个`保证定时任务在分布式环境中的合理执行`的辅助框架,保证定时任务在分布式环境中同一时间最多`只执行一次`。同时一个任务在执行时,另一个任务无法获取锁时会跳过当前任务的执行。
ShedLock的实现原理是采用`公共存储实现的锁机制`,使得同一时间点只有第一个执行定时任务的服务实例能执行成功,并在公共存储中存储"我正在执行任务,从什么时候(预计)执行到什么时候",其他服务实例执行时如果发现任务正在执行,则直接跳过本次执行,从而保证同一时间一个任务只被执行一次。
参考博客:https://blog.csdn.net/johnf_nash/article/details/90741405
## Redis
### Redis如何对key进行分类存储?
Redis中的key的分类存储是通过`key的命名`来实现的。
Key的命名方式:
```java
aaa:bbb:ccc=XXX
```
其实就是我们把 key 定义的有规律一些,通过在key的字符串内部 分类,上图只是因为我们用 工具查看的时候,它会把`key中的第一个:前的部分显示成文件夹`的形式,其实实际存储的时候 还是 正常的 key:value 形式。
参考博客:https://www.cnblogs.com/libin6505/p/9799238.html
### Redis集群的三大模式
#### 1、主从模式
在主从复制中,数据库分为两类:`主数据库`(master)和`从数据库`(slave)。
> 工作机制
当slave启动后,主动向master发送SYNC命令。master接收到SYNC命令后在后台保存快照(RDB持久化)和缓存保存快照这段时间的命令,然后将保存的快照文件和缓存的命令发送给slave。slave接收到快照文件和命令后加载快照文件和缓存的执行命令。
复制初始化后,master每次接收到的写命令都会同步发送给slave,保证主从数据一致性。
> 缺点
master节点在主从模式中唯一,若master挂掉,则redis无法对外提供写服务。
#### 2、Sentinel模式
主从模式的弊端就是不具备高可用性,当master挂掉以后,Redis将不能再对外提供写入操作,因此sentinel应运而生。
sentinel中文含义为哨兵,它的作用就是`监控redis集群的运行状况`。
> 工作机制
1、每个sentinel以每秒钟一次的频率向它所知的master,slave以及其他sentinel实例发送一个PING 命令。
2、如果一个实例距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被sentinel标记为主观下线。
3、如果一个master被标记为主观下线,则正在监视这个master的所有sentinel要以每秒一次的频率确认master的确进入了主观下线状态。
4、当有足够数量的sentinel(大于等于配置文件指定的值)在指定的时间范围内确认master的确进入了主观下线状态, 则master会被标记为客观下线。
5、在一般情况下, 每个sentinel会以每 10 秒一次的频率向它已知的所有master,slave发送 INFO 命令。
6、当master被sentinel标记为客观下线时,sentinel向下线的master的所有slave发送 INFO 命令的频率会从 10 秒一次改为 1 秒一次。
7、若没有足够数量的sentinel同意master已经下线,master的客观下线状态就会被移除。
8、若master重新向sentinel的 PING 命令返回有效回复,master的主观下线状态就会被移除。
当使用sentinel模式的时候,客户端就不要直接连接Redis,而是连接sentinel的ip和port,由sentinel来提供具体的可提供服务的Redis实现,这样当master节点挂掉以后,sentinel就会感知并将新的master节点提供给使用者。
#### 3、Cluster模式
sentinel模式基本可以满足一般生产的需求,具备高可用性。但是当数据量过大到一台服务器存放不下的情况时,主从模式或sentinel模式就不能满足需求了,这个时候需要对存储的数据进行分片,将数据存储到多个Redis实例中。cluster模式的出现就是为了解决单机Redis容量有限的问题,将Redis的数据根据一定的规则分配到多台机器。
cluster可以说是sentinel和主从模式的结合体,通过cluster可以实现主从和master重选功能,所以如果配置两个副本三个分片的话,就需要六个Redis实例。因为Redis的数据是根据一定规则分配到cluster的不同机器的,当数据量过大时,可以新增机器进行扩容。
使用集群,只需要将redis配置文件中的cluster-enable配置打开即可。每个集群中至少需要三个主数据库才能正常运行,新增节点非常方便。
redis cluster集群是去中心化的,每个节点都是平等的,连接哪个节点都可以获取和设置数据。
当然,平等指的是master节点,因为slave节点根本不提供服务,只是作为对应master节点的一个备份。
参考博客:https://blog.csdn.net/miss1181248983/article/details/90056960
### Redis集群创建
Redis集群的节点数量最少需要`6`个,才能保证高可用,每个节点需要在`redis.conf`文件中配置`cluster-enabled yes`
Redis5之后使用redis-cli创建集群,命令为:
```shell
redis-cli --cluster create --cluster-replicas 1
```
比如:`redis-cli --cluster create 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384 --cluster--replicas 1`
`--cluster-replicas 1`表示每个主节点指定一个从节点
更多内容,参考博客: https://www.jianshu.com/p/9c65057d5143
### Linux下安装、启动、连接Redis
#### 安装
1、安装gcc编译环境
```shell
yum install gcc c++
```
2、使用xftp上传redis压缩包,解压
```shell
tar zxvf redis-3.0.0.tar.gz
```
3、xshell进入解压目录执行`make`命令进行编译
4、编译完成后安装,安装路径可自己指定
```shell
make install PREFIX=/usr/local/redis
```
#### 启动
##### 前端启动
进入redis安装目录
```shell
cd /usr/local/redis/bin
```
执行命令
```shell
./redis-server
```
##### 后端启动
1、复制redis.conf到redis的安装目录
```shell
cp redis.conf /usr/local/redis/bin
```
2、编辑redis.conf,配置为后台启动,然后保存退出
```shell
daemonize yes
```
3、执行命令
```shell
./redis-server redis.conf
```
4、查看是否启动成功
```shell
ps aux|grep redis
```
#### 连接
进入redis安装目录,
```shell
cd /usr/local/redis/bin/
```
执行
```shell
./redis-cli
```
参考博客:https://blog.csdn.net/wb01234567890456/article/details/78653802
### 查看redis集群节点的主从关系
1、启动redis-cli连接redis server
```shell
#若想在全局使用redis-cli命令,需把redis-cli文件复制到/usr/local/bin目录下(相当于创建一个全局快捷方式)
redis-cli -p 6379
```
2、密码验证
```shell
auth password
```
3、查看节点的主从关系
```shell
info Replication
```
示例:
```shell
[root@svc-app-t24 ~]# redis-cli -p 6379
127.0.0.1:6379> auth 123
OK
127.0.0.1:6379> info Replication
# Replication
role:master #master角色
connected_slaves:1
slave0:ip=192.168.0.1,port=8379,state=online,offset=11727611939,lag=1
master_replid:f557834ddec8c2939403bb456ee6c7e446ae123f
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:11727341997
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1045676
repl_backlog_first_byte_offset:21726563422
repl_backlog_histlen:1041576
```
## 数据结构
### 红黑树的特点
1.结点是红色或黑色。
2.根结点是黑色。
3.每个叶子结点都是黑色的空结点(NIL结点)。
4.每个红色结点的两个子结点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色结点)
5.从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点。
## 算法
### 快速排序
快速排序是对冒泡排序的一种改进。
#### 基本思想
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据小,然后再按照此方法对两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
本质:把基数大的都放在基准数的右边,把基准数小的放在基准数的左边,这样就找到了该数据在数组中的正确位置,递归时分别对前半部分和后半部分排序
#### 算法代码
```java
public class QuickSort{
public static void main(String[] args){
int arr= { 49, 38, 65, 97, 23, 22, 76, 1, 5, 8, 2, 0, -1, 22 };
quickSort(arr, 0, arr.length - 1);
System.out.println("排序后:");
for (int i : arr) {
System.out.println(i);
}
}
private static void quickSort(int[] arr,int low,int high){
if(low < high){
// 找寻基准数据的正确索引
int index=getIndex(arr,low,high);
//进行迭代对index之前和之后的数组进行相同的操作使整个数组变成有序
quickSort(arr,low,index-1);
quickSort(arr,index+1,high);
}
}
private static int getIndex(int[] arr, int low, int high) {
//基准数据
int tmp=arr[low];
while (low < high) {
// 当队尾的元素大于等于基准数据时,向前挪动high指针
while (low < high && arr[high] >= tmp) {
high--;
}
// 如果队尾元素小于tmp了,需要将其赋值给low
arr[low] = arr[high];
// 当队首元素小于等于tmp时,向前挪动low指针
while (low < high && arr[low] <= tmp) {
low++;
}
// 当队首元素大于tmp时,需要将其赋值给high
arr[high] = arr[low];
}
// 跳出循环时low和high相等,此时的low或high就是tmp的正确索引位置
arr[low] = tmp;
return low; // 返回tmp的正确位置
}
}
```
## ElasticSearch
### 查询语句
#### 查询所有
GET /school/student/_search
#### 多索引,多type查询
/_search:在所有的索引中搜索所有的类型
/school/_search:在 school 索引中搜索所有的类型
/school,ad/_search:在 school 和ad索引中搜索所有的类型
/s*,a*/_search:在所有以g和a开头的索引中查询所有的类型
/school/student/_search:在school索引中搜索student类型
/school,ad/student,phone/_search:在school和ad索引上搜索student和phone类型
/_all/student,phone/_search:在所有的索引中搜索student和phone类型
#### 按条件查询
GET /school/student/_search?q=name:houyi
......
参考博客:https://www.jianshu.com/p/c377477df7fc
### Kibana
Kibana是为ElasticSearch设计的开源分析和可视化平台,你可以使用Kibana来搜索,查看存储在ElasticSearch索引中的数据并与之交互,你可以很容易实现高级的数据分析和可视化,以图标的形式展现出来
## 前端
### Promise是什么?
Promise是异步编程的一种解决方案,在ES6中Promise被列为了正式规范,统一了用法,原生提供了Promise对象。
```javascript
// resolve代表成功 reject失败 都是一个函数
let p = new Promise(function(reslove,reject){
//reslove('成功') //状态由等待变为成功,传的参数作为then函数中成功函数的实参
reject('失败') //状态由等待变为失败,传的参数作为then函数中失败函数的实参
})
//then中有2个参数,第一个参数是状态变为成功后应该执行的回调函数,第二个参数是状态变为失败后应该执行的回调函数。
p.then((data)=>{
console.log('成功'+data)
},(err)=>{
console.log('失败'+err)
})
```
参考博客:https://www.jianshu.com/p/3023a9372e5f
### Npm强制清理缓存
```powershell
npm cache clean --force
```
## kafka
### 使用场景
- 消息
kafka更好的替换传统的消息系统,消息系统被用于各种场景(解耦数据生产者,缓存未处理的消息等),与大多数消息系统比较,kafka有更好的吞吐量,内置分区,副本和故障转移,这有利于处理大规模的消息。
根据我们的经验,消息往往用于较低的吞吐量,但需要低的`端到端`延迟,并需要提供强大的耐用性的保证。
在这一领域的kafka比得上传统的消息系统,如的`ActiveMQ`或`RabbitMQ`的。
- 网站活动追踪
kafka原本的使用场景:用户的活动追踪,网站的活动(网页游览,搜索或其他用户的操作信息)发布到不同的话题中心,这些消息可实时处理,实时监测,也可加载到Hadoop或离线处理数据仓库。
每个用户页面视图都会产生非常高的量。
- 指标
kafka也常常用于监测数据。分布式应用程序生成的统计数据集中聚合。
- 日志聚合
许多人使用Kafka作为日志聚合解决方案的替代品。日志聚合通常从服务器中收集物理日志文件,并将它们放在中央位置(可能是文件服务器或HDFS)进行处理。Kafka抽象出文件的细节,并将日志或事件数据更清晰地抽象为消息流。这允许更低延迟的处理并更容易支持多个数据源和分布式数据消费。
- 流处理
kafka中消息处理一般包含多个阶段。其中原始输入数据是从kafka主题消费的,然后汇总,丰富,或者以其他的方式处理转化为新主题,例如,一个推荐新闻文章,文章内容可能从“articles”主题获取;然后进一步处理内容,得到一个处理后的新内容,最后推荐给用户。这种处理是基于单个主题的实时数据流。从`0.10.0.0`开始,轻量,但功能强大的流处理,就可以这样进行数据处理了。
除了Kafka Streams,还有Apache Storm和Apache Samza可选择。
- 事件采集
事件采集是一种应用程序的设计风格,其中状态的变化根据时间的顺序记录下来,kafka支持这种非常大的存储日志数据的场景。
- 提交日志
kafka可以作为一种分布式的外部日志,可帮助节点之间复制数据,并作为失败的节点来恢复数据重新同步,kafka的日志压缩功能很好的支持这种用法,这种用法类似于`Apacha BookKeeper`项目。
参考网站:https://www.orchome.com/295
### windows环境下安装Kafka
#### zookeeper安装
1、安装Kafka之前必须先安装zookeeper,下载地址:
http://zookeeper.apache.org/releases.html#download
2、解压进入目录D:\Kafka\zookeeper-3.4.9\conf,复制zoo_sample.cfg,重命名为zoo.cfg
3、编辑zoo.cfg,配置dataDir
4、添加系统环境变量:ZOOKEEPER_HOME=D:\Kafka\zookeeper-3.4.9
5、Path环境变量追加路径:%ZOOKEEPER_HOME%\bin
6、cmd运行zookeeper:`zkServer`,运行后请不要关闭此窗口
#### Kafka安装
1、下载安装包:http://kafka.apache.org/downloads,注意要下载二进制版本(Binary downloads)
2、解压进入目录D:\Kafka\kafka_2.12-0.11.0.0\config,编辑server.properties,配置log.dirs
3、Kafka默认运行在9092端口运行,并连接zookeeper默认端口2181
4、进入目录D:\Kafka\kafka_2.12-0.11.0.0,cmd,输入kafka启动命令:
```shell
.\bin\windows\kafka-server-start.bat .\config\server.properties
```
参考博客:https://www.jianshu.com/p/d64798e81f3b
## Dubbo+zookeeper
### 服务提供者和消费者配置
1、服务提供者配置dubbo-provider.xml
```xml
#配置当前应用信息
#提供方配置,这里配置了一个请求过滤器
#用于配置连接注册中心相关信息
#用于配置提供服务的协议信息,协议由提供方指定,消费方被动接受
#用于暴露一个服务,一个服务可以用多个协议暴露,一个服务也可以注册到多个注册中心,ref指定服务接口的实现类引用
```
接口
```java
public interface DemoService{
String sayHello();
}
```
实现类
```java
@Service("demoService")
public calss DemoServiceImpl implement DemoService{
@override
public String sayHello(){
return "Hello World";
}
}
```
2、服务消费者配置dubbo-consumer.xml
```xml
#配置当前应用信息
#用于配置连接注册中心相关信息
#用于创建一个远程服务代理,一个引用可以指向多个注册中心
#check:启动时检查提供者是否存在,true报错,false忽略
```
3.在代码中使用`@Reference`注入接口实例
```java
@RestController
@RequestMapping("/test")
public class Test{
@Reference
private DemoService demoService;
@GetMapping("/hello")
public String sayHello(){
return demoService.sayHello();
}
}
```
注:服务提供者接口需打包导入到消费者pom依赖中
## JWT
JWT,即`JSON Web Token`,是目前最流行的跨域认证解决方案
### 原理
1、用户发送账号密码,服务器认证以后,生成一个JSON对象,发送给用户
```json
{
"姓名": "张三",
"角色": "管理员",
"到期时间": "2018年7月1日0点0分"
}
```
2、之后用户和服务器通信都要携带这个JSON对象,服务器只靠这个对象认定用户身份,为防止用户篡改数据,服务器生成该对象时应加上签名
3、服务器不保存任何session数据,服务器`无状态`,方便扩展
### 数据结构
JWT是一个很长的字符串,分为三个部分:`头部`(Header)、`负载`(Payload)和`签名`(Signature),每个部分之间用`.`隔开
1、Header是一个JSON对象,描述JWT的元数据
```json
{
"alg": "HS256",
"typ": "JWT"
}
```
2、Payload也是一个JSON对象,用了存放实际需要传递的数据,规定了7个官方字段
- iss (issuer):签发人
- exp (expiration time):过期时间
- sub (subject):主题
- aud (audience):受众
- nbf (Not Before):生效时间
- iat (Issued At):签发时间
- jti (JWT ID):编号
除了官方字段,也可以在这个部分定义私有字段,注意不要把私密信息放在这个部分
```json
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
```
3、Signature是对前两部分的签名,防止数据篡改
需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名
```json
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
```
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用`.`分隔,就可以返回给用户
参考博客:http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
### 生成token
1、引入jwt依赖
```xml
io.jsonwebtoken
jjwt
0.9.1
```
2、编写方法,传入主体信息和密钥(用于校验时解密)
```java
public static Map generateToken(String userId,String secret){
//过期时间
long EXPIRE=1000*60*60*24;
Map data = new HashMap<>(16);
Date expiration=new Date(System.currentTimeMillis() + EXPIRE);
String token = Jwts.builder()
.setHeaderParam("typ","JWT")//token的类型
.setHeaderParam("alg","HS256")//加密算法
.setExpiration(expiration)//过期时间
.setIssuedAt(new Date())//签名时间,时间戳
.claim("userId",userId)//主体信息
.signWith(SignatureAlgorithm.HS256,secret)//使用密钥进行签名加密
.compact();
//将token和过期时间返回给用户
data.put("token",token);
data.put("expire_time",new SimpleDateFormat("yyyy-MM-dd HH:ss:mm").format(expiration));
return data;
}
```
### 校验token
token解密后返回`Claims`对象,其本质上是一个Map,可以从Claims对象中获取`主体信息`
```java
public static Claims validateTokenAndGetClaims(String token, String secret){
Claims claims = Jwts.parser()
.setSigningKey(secret)//使用密钥对签名进行解密
.parseClaimsJws(token)//对token进行解析
.getBody();//获得主体信息
return claims;
}
```
查看Claims的实现类`DefaultClaims`源码,可以看到能够获取的字段的get/set方法
```java
public class DefaultClaims extends JwtMap implements Claims {
public DefaultClaims() {
}
public DefaultClaims(Map map) {
super(map);
}
public String getIssuer() {
return this.getString("iss");//iss (issuer):签发人
}
public Claims setIssuer(String iss) {
this.setValue("iss", iss);
return this;
}
public String getSubject() {
return this.getString("sub");//sub (subject):主题
}
public Claims setSubject(String sub) {
this.setValue("sub", sub);
return this;
}
public String getAudience() {
return this.getString("aud");//aud (audience):受众
}
public Claims setAudience(String aud) {
this.setValue("aud", aud);
return this;
}
public Date getExpiration() {
return (Date)this.get("exp", Date.class);//exp (expiration time):过期时间
}
public Claims setExpiration(Date exp) {
this.setDate("exp", exp);
return this;
}
public Date getNotBefore() {
return (Date)this.get("nbf", Date.class);//nbf (Not Before):生效时间
}
public Claims setNotBefore(Date nbf) {
this.setDate("nbf", nbf);
return this;
}
public Date getIssuedAt() {
return (Date)this.get("iat", Date.class);//iat (Issued At):签发时间
}
public Claims setIssuedAt(Date iat) {
this.setDate("iat", iat);
return this;
}
public String getId() {
return this.getString("jti");//jti (JWT ID):编号
}
public Claims setId(String jti) {
this.setValue("jti", jti);
return this;
}
//获取用户自己定义的claim主体信息字段
public T get(String claimName, Class requiredType) {
Object value = this.get(claimName);
if (value == null) {
return null;
} else {
if ("exp".equals(claimName) || "iat".equals(claimName) || "nbf".equals(claimName)) {
value = this.getDate(claimName);
}
return this.castClaimValue(value, requiredType);
}
}
//将claim参数的值转换为想转换的类型
private T castClaimValue(Object value, Class requiredType) {
if (requiredType == Date.class && value instanceof Long) {
value = new Date((Long)value);//Long类型转换为日期类型
}
//Integer类型转其他类型
if (value instanceof Integer) {
int intValue = (Integer)value;
if (requiredType == Long.class) {
value = (long)intValue;//转换为long类型
} else if (requiredType == Short.class && -32768 <= intValue && intValue <= 32767) {
value = (short)intValue;//转换为short类型
} else if (requiredType == Byte.class && -128 <= intValue && intValue <= 127) {
value = (byte)intValue;//转换为byte类型
}
}
if (!requiredType.isInstance(value)) {//未知的类型,直接报错
throw new RequiredTypeException("Expected value to be of type: " + requiredType + ", but was " + value.getClass());
} else {//其他类型转换
return requiredType.cast(value);
}
}
}
```
## Linux
### Linux查找软件安装目录和位置
1.`find`查找关键字
```shell
find / -name xxx
```
2.`whereis`查找文件位置
```shell
whereis xxx
```
3.`which`查询软件命令的运行文件路径
```shell
which xxx
```
4.`locate`是`find -name`的另一种写法,但是比它快,因为它不搜索具体目录,而是搜索一个数据库,但它查不到最新变动过的文件,因此查询之前可以先`update`
```shell
updatedb #手动更新数据库
locate /etc/xxx
```
参考博客:https://www.fujieace.com/linux/software-location.html
## SpringBoot
### SpringBoot添加拦截器
1、在interceptor包下创建拦截器,继承`HandlerInterceptor`,重写相应方法
```java
public class AuthenticationInterceptor implements HandlerInterceptor {
//处理请求之前调用(大部分时候都是重写此方法)
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
return true;
}
//处理请求之后调用
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
//请求结束之后调用
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
```
2、在config包下创建WebMvcConfig,继承`WebMvcConfigurer`,重写`addInterceptor`方法
```java
@Configuration
public class WebMvcConfig extends WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthenticationInterceptor())//添加拦截器
//添加拦截规则,如果设置此规则,不在拦截规则下的路径则不会被拦截
//如果不设置此规则,默认会拦截所有路径
.addPathPatterns("/faq/**");
//添加排除拦截规则,如果存在此规则,在此规则下的路径不会被拦截
.excludePathPatterns("/auth/**")
super.addInterceptors(registry);
}
}
```
SpringBoot的拦截器匹配逻辑可以查看源码`MappedInterceptor`中的`matcher`方法
```java
public boolean matches(String lookupPath, PathMatcher pathMatcher) {
PathMatcher pathMatcherToUse = this.pathMatcher != null ? this.pathMatcher : pathMatcher;
String[] var4;
int var5;
int var6;
String pattern;
//如果排除规则不为空
if (this.excludePatterns != null) {
var4 = this.excludePatterns;
var5 = var4.length;
for(var6 = 0; var6 < var5; ++var6) {
pattern = var4[var6];
//匹配到的路径返回false,即不拦截
if (pathMatcherToUse.match(pattern, lookupPath)) {
return false;
}
}
}
//如果拦截规则为空,所有的路径都会返回true,即所有路径都会被拦截
if (ObjectUtils.isEmpty(this.includePatterns)) {
return true;
} else {//如果拦截规则不为空
var4 = this.includePatterns;
var5 = var4.length;
for(var6 = 0; var6 < var5; ++var6) {
pattern = var4[var6];
//匹配到的路径会返回true,即拦截该路径
if (pathMatcherToUse.match(pattern, lookupPath)) {
return true;
}
}
//不匹配拦截规则的路径直接返回false,即不拦截
return false;
}
}
```
### @RequestBody
@RequestBody用来接收前端传递给后端的JSON字符串,需要放在请求体中,因此不能使用get方式提交数据。
**一个请求只能有一个@RequestBody,但可以有多个@RequestParam。**
示例代码
```java
@PostMapping("hello")
public String hello(HttpServletRequest request,@RequestBody(required = true) String jsonParam){
return "Hello RequestBody";
}
```
### @Valid
@Valid用于验证注解是否符合要求,直接加在Controller方法入参中,在变量中添加验证信息的要求,当不符合要求时就在方法中返回message的错误提示信息
示例代码
```java
@PostMapping
public User create (@Valid @RequestBody User user) {
System.out.println(user.getId());
System.out.println(user.getUsername());
System.out.println(user.getPassword());
user.setId("1");
return user;
}
```
在User类中添加验证信息的要求
```java
public class User {
private String id;
@NotBlank(message = "密码不能为空")
private String password;
}
```
`@NotBlank`注解所指的password字段,表示验证密码不能为空,如果为空的话,上面的Controller的create方法会将message中的“密码不能为空”返回。
更多验证注解参考博客:https://blog.csdn.net/weixin_38118016/article/details/80977207
### SpringBoot配置文件数据库密码加密jasypt
jasypt是一个加解密组件,能够配置数据库登录密码、redis登录密码以及第三方的密钥等进行加密。
#### 使用步骤
1、引入maven
```xml
com.github.ulisesbocchio
jasypt-spring-boot-starter
2.1.0
```
2、application.yml增加如下配置
```yaml
#jasypt加密的密匙
jasypt:
encryptor:
password: EbfYkitulv73I2p0mXI50JMXoaxZTKJ7
```
3、测试生成加密后的密钥
```java
@RunWith(SpringRunner.class)
@SpringBootTest
@WebAppConfiguration
public class testTest {
@Autowired
StringEncryptor encryptor;
@Test
public void getPass() {
String url = encryptor.encrypt("jdbc:mysql://47.97.192.116:3306/sell?characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2b8");
String name = encryptor.encrypt("你的数据库名");
String password = encryptor.encrypt("你的数据库密码");
System.out.println(url);
System.out.println(name);
System.out.println(password);
Assert.assertTrue(name.length() > 0);
Assert.assertTrue(password.length() > 0);
}
}
```
加密后的输出结果如下
```shell
3OW8RQaoiHu1DXfDny4FDP0W5KOSVcWN5yWNxQ6Q4UE=
ITE8wJryM8hVnofDKQodFzPZuPpTaMtX71YDoOTdh0A=
```
4、将生成的name和password替换配置文件中的数据库账户和密码,如下
```yaml
spring:
#数据库相关配置
datasource:
driver-class-name: com.mysql.jdbc.Driver
#这里加上后缀用来防止mysql乱码,serverTimezone=GMT%2b8设置时区
url: ENC(i87lLC0ceVq1vK91R+Y6M9fAJQdU7jNp5MW+ndLgacRvPDj42HR8mUE33uFwpWqjOSuDX0d1dd2NilrnW7yJbZmoxuJ3HmOmjwY5+Vhu+e3We4QPDVCr/s/RHsQgYOiWrSQ92Mjammnody/jWI5aaw==)
username: ENC(3OW8RQaoiHu1DXfDny4FDP0W5KOSVcWN5yWNxQ6Q4UE=)
password: ENC(ITE8wJryM8hVnofDKQodFzPZuPpTaMtX71YDoOTdh0A=)
#jasypt加密的密匙
jasypt:
encryptor:
password: EbfYkitulv73I2p0mXI50JMXoaxZTKJ7
```
`ENC()`是固定写法,()里面是加密后的信息
参考博客:https://www.jianshu.com/p/b047ed4a8dfa
## Http
### post和put请求的区别
`幂等性`
通俗来说是指不管进行多少次重复操作,都是实现相同的结果
get、put、delete都是`幂等性操作`,而post不是
参考博客:https://blog.csdn.net/qq_33223761/article/details/83511628
### Ajax发送put和delete请求的两种方式
#### 1、采用post+_method:delete/put+filter的方式
ajax发送put和delete请求
- 如果参数在url地址上,没问题
- 如果在data中传参,则需要修改:
- type/method设置为`post`
- data中加入`_method="DELETE"`或`_method="PUT"`参数,如:`data:{_method:"DELETE", id:issueId,userId:userId}`
- 配置filter,SpringBoot则不需要
```xml
HiddenHttpMethodFilter
org.springframework.web.filter.HiddenHttpMethodFilter
HiddenHttpMethodFilter
springmvc
```
#### 2、仍然使用put/delete请求
- data参数转换成`JSON字符串`
```javascript
$.ajax({
url:"http://localhost:8885/answer",
type:"DELETE",
contentType:"application/json",//设置请求参数类型为json字符串
data:JSON.stringify(jsonstr),//将json对象转换成json字符串发送
dataType:"json",
success:function(result){
}
});
```
- 后台controller方法使用`@RequestBody`标注入参
```java
@RequestMapping(value="/answer",method=RequestMethod.DELETE)
public ResponseResult deleteAnswer(@RequestBody Issue issue){}
```
参考博客:https://blog.csdn.net/liuyuanjiang109/article/details/78972644
## Git
### 撤销
撤销操作的常用几个命令
```shell
git commit --amend #撤销上一次提交 并将暂存区文件重新提交
git checkout -- #拉取暂存区文件 并将其替换成工作区文件
git reset HEAD -- #拉取最近一次提交到版本库的文件到暂存区 改操作不影响工作区
```
### 更新分支
gitlab新建分支,本地更新分支
```shell
git pull
```
参考博客:
https://blog.csdn.net/qq_36431213/article/details/78858848
https://blog.csdn.net/qq_32575047/article/details/85015289
### git stash
保存未提交的修改(工作区和暂存区)到堆栈中,用于后续恢复到当前工作目录
```shell
git stash #保存
git stash save 'message' #保存并添加注释
```
查看当前stash中的内容
```shell
git stash list
```
将stash中保存的内容弹出应用到当前分支对应的工作目录上,保存的内容会被删除,只能恢复一次
```shell
git stash pop stash@{num} #num是可选项,可通过git stash list查看具体值
```
将stash中保存的内容应用到当前分支对应的工作目录上,保存的内容不会被删除,可以恢复多次
```shell
git stash apply stash@{num} #num是可选项,可通过git stash list查看具体值
```
删除某个保存
```shell
git stash drop stash@{num} #num是可选项,可通过git stash list查看具体值
```
清除所有的保存
```shell
git stash clear
```
查看堆栈中最新保存的stash和当前目录的差异
```shell
git stash show
```
参考博客:https://blog.csdn.net/stone_yw/article/details/80795669
## Maven
### 编译打包时忽略测试用例
跳过测试阶段
```shell
mvn package -DskipTests
```
临时性跳过测试代码的编译(常用)
```shell
mvn package -Dmaven.test.skip=true
```
更多相关命令,参考博客:https://www.cnblogs.com/mrChangChang/p/11633704.html
## Tomcat
### 两种运行模式
**BIO**:传统的Java I/O操作,同步且阻塞IO,默认模式,配置:
```xml
protocol="HTTP/1.1"
```
**NIO**:JDK1.4开始支持,同步阻塞或同步非阻塞IO,指定使用NIO模型来接受http请求:
```xml
protocol="org.apache.coyote.http11.Http11NioProtocol"
```
### 三种部署方式
1、直接将Web项目放在webapps下,Tomcat会自动将其部署
2、在`server.xml`文件上配置``节点,设置相关的属性
3、通过Catalina来进行配置:进入到`conf\Catalina\localhost`文件下,创建一个xml文件,该文件的名字就是站点的名字,编写xml的方式来编写进行配置
### 内存调优
内存方式的设置是在catalina.sh中,调整一下JAVA_OPTS变量即可,因为后面的启动参数会把JAVA_OPTS作为JVM的启动参数来处理,具体设置如下:
```shell
JAVA_OPTS="$JAVA_OPTS -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4"
```
`-Xmx3550m`:设置JVM最大可用内存
`-Xms3550m`:设置JVM初始内存,此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存
`-Xmn2g`:设置年轻代大小为2G ,整个堆的大小=年轻代大小+年老代大小+持久代大小
`-Xss128k`:设置每个线程的堆栈大小
`-XX:NewRatio=4`:设置年轻代中伊甸区与幸存者区的大小比值
`-XX:MaxPermSize=16m`:设置持久代大小为16m
`-XX:MaxTenuringThreshold=0`:设置垃圾最大年龄(GC存活次数达到一个值就进入老年代),如果设置为0,则年轻代对象不经过幸存者区,直接进入老年代
一键复制
编辑
Web IDE
原始数据
按行查看
历史