diff --git a/java/springboot系列/spring-boot参数校验.md b/java/springboot系列/spring-boot参数校验.md
new file mode 100644
index 0000000..c9cb18a
--- /dev/null
+++ b/java/springboot系列/spring-boot参数校验.md
@@ -0,0 +1,344 @@
+---
+id: "20211009"
+date: "2021-10-09 15:42:00"
+title: "SpringBoot参数校验看这篇就够了"
+tags: ["java", "SpringBoot","exception",“validation”]
+categories:
+ - "java"
+ - "spring boot学习"
+---
+
+
+**本文所用到的全部代码见文末**
+
+## 前言
+
+任何一个项目都需要对接口做参数校验,最简单粗暴的校验方式就是在代码中硬编码来一个个检查参数,这种方式显然是很不优雅的,Spring 已经为我们设计了一套比较优雅的校验方式,本篇文章将进行详细说明。
+
+## 两个注解`@Valid`和`@Validated`
+
+标题中的两个注解,想必是大家经常看到的,但是大部分人应该都不清楚这两个注解有什么区别(比如我),本节将解开你的疑惑。
+
+首先来看看下这两个注解的来源:
+
+`@Valid`:`javax.validation.Valid`,来自于 javax,属于标准 JSR-380 规范
+
+`@Validated`:`org.springframework.validation.annotation.Validated`,来自于 spring validation,属于 Spring 的 JSR-380 规范,是标准 JSR-380 的一个变种,有一些增强功能。
+
+
+
+由此可以看出这两个注解大部分功能相似,但是存在一些区别。
+
+**网上大部分文章说的是 JSR-303,JSR-303 是很老的标准了(Bean Validation 1.0),JSR-380 是目前最新的标准(Bean Validation 2.0),具体可在[此网站](https://beanvalidation.org/)中查看相关文档**
+
+### 作用域区别
+
+这两个注解的作用域定义如下:
+
+```java
+//@Valid
+@Target({ METHOD, FIELD, CONSTRUCTOR, PARAMETER, TYPE_USE })
+//@Validated
+@Target({TYPE, METHOD, PARAMETER})
+```
+
+`@Valid`可作用于方法、**成员属性**、构造函数、方法参数、使用类型的任意语句中(这个是 Java8 中新增的)
+
+`@Validated`可作用于类型、方法、方法参数上
+
+可以看到只有`@Valid`能够作用在成员属性上,也就是只能通过它能够实现嵌套验证(什么是嵌套验证见后文),单单`@Validated`不能实现。
+
+### 功能区别
+
+对比两个注解的定义可以发现,@Validated 多了一个参数`Class>[] value() default {}`,可以传入 Class 参数,此参数用于分组校验逻辑的实现。`@Valid`目前为止还不支持分组。
+
+## 校验内容
+
+**本节只说明如何进行校验,校验异常全局处理见下一节**
+
+当我们引入校验依赖后,可以发现有很多的校验注解可以使用,比如@NotNull,@Range,@Email 等等,我们仔细查看这些注解的来源,可以发现部分注解是来自于`javax.validation`,部分注解是来自于`org.hibernate.validator`,为啥呢
+
+javax 是 JSR-380 的标准实现,hibernate 是这个规范的参考实现并扩充了一些功能。如果标准实现将 hibernate 中扩充的某些校验加入到标准后,hibernate 就会将自身的校验实现标记为过期(比如 6.2 版本中的@Email 注解)
+
+具体有哪些校验注解可以[看看这篇文章](https://juejin.cn/post/6844903976270299149)
+
+## 使用
+
+### 依赖引入
+
+```xml
+
+ org.springframework.boot
+ spring-boot-starter-validation
+ 2.5.5
+
+```
+
+引入这个依赖会一起引入`javax.validation`和`org.hibernate.validator`
+
+每个校验注解中都有一个 message 参数,此参数可传入国际化的 key,或者自定义的字符串,用于自定义错误提示。
+
+### url 参数校验
+
+首先需要在类上加一个`@Validated`注解,否则无法对 url 参数进行校验
+
+```java
+@RestController
+@Validated
+public class TestController {
+}
+```
+
+然后在对具体的方法参数进行校验,比如:
+
+```java
+/**
+ * url参数校验
+ */
+@GetMapping("/test1")
+public Result test1(@NotBlank @RequestParam String param1, @Range(max = 100, min = 10) @RequestParam int param2) {
+ return null;
+}
+```
+
+### post 表单校验
+
+首先定义入参类 TestBody.java
+
+```java
+@Data
+public class TestBody {
+ @NotBlank
+ @Email(message = "请输入一个邮箱")
+ private String param1;
+ @NotNull
+ private String param2;
+}
+```
+
+然后在入口方法中对要校验的参数加上@Valid 或者@Validated
+
+```java
+@PostMapping("/test2")
+public Result test2(@Validated @RequestBody TestBody body) {
+ return null;
+}
+```
+
+### 嵌套校验
+
+何谓嵌套?就是一个对象中包含另外的对象。默认情况下是不会对潜逃对象中的属性进行校验的。比如新建一个 TestBody2 类,其中包含 TestBody
+
+```java
+@Data
+public class TestBody2 {
+ @NotBlank
+ @Email(message = "请输入一个邮箱")
+ private String param1;
+ @NotNull
+ private String param2;
+ @NotNull
+ private TestBody testBody;
+}
+```
+
+上述情况在校验时只会校验 testBody 是否为 null,并不会校验其中的属性。必须要用@Valid(@Validated 不能修饰属性,所以只能用@valid)来注解 testBody 属性,才会对其属性进行校验。
+
+入口方法如下:
+
+```java
+/**
+ * 嵌套校验
+ */
+@PostMapping("/test3")
+public Result test3(@Valid @RequestBody TestBody2 testBody2) {
+ throw new RuntimeException("我是test2");
+}
+```
+
+### 分组校验
+
+在实际的业务需求中,可能一个入参类,会被多个接口使用,每个接口有不同的校验逻辑,这是就需要对校验逻辑进行分组,以在不同的接口中调用不同的校验组和。例子如下:
+
+建立 TestBody3
+
+```java
+@Data
+public class TestBody3 {
+ @NotBlank(groups = {Insert.class})
+ private String param1;
+ @NotBlank(groups = {Update.class})
+ private String param2;
+}
+```
+
+通过**groups**属性来说明本注解是在哪些情况下生效,如 param1 只在 Insert 模式下才会进行校验,param2 只在 Update 模式下生效,默认在入口函数中定义,如下:
+
+```java
+/**
+ * 分组校验1
+ */
+@PostMapping("/test4")
+public Result test4(@Validated({Update.class}) @RequestBody TestBody3 testBody) {
+ return null;
+}
+
+/**
+ * 分组校验2
+ */
+@PostMapping("/test5")
+public Result test5(@Validated({Insert.class}) @RequestBody TestBody3 testBody) {
+ return null;
+}
+```
+
+**注意分组校验必须使用`@validated`注解**
+
+### 集合类校验
+
+集合类校验分两种
+
+一种是对象内集合的校验,直接使用@valid 注解该属性即可,例如:
+
+```java
+@Data
+public class TestBody5 {
+ @Valid
+ private List list;
+}
+```
+
+使用示例见:`com.fanxb.exceptiontest.controller.TestController#test7`
+
+另外一直直接接收集合入参,如:
+
+```java
+/**
+ * 集合校验2(对象集合)
+ */
+ @PostMapping("/test8")
+ public Result test8(@RequestBody List<@Valid TestBody4> list) {
+ return null;
+ }
+```
+
+这种需要使用`@Valid`来注解泛型类,使用示例见`com.fanxb.exceptiontest.controller.TestController#test8`
+
+### 自定义校验
+
+最后,校验依赖提供的校验逻辑是有限的,有时我们需要进行一些特殊的校验,这就需要实现自定义校验注解,实现流程如下:
+
+首先定义注解
+
+```java
+@Target({ElementType.FIELD}) //作用于字段
+@Retention(RetentionPolicy.RUNTIME)//生命周期
+@Constraint(validatedBy = CustomCheckValidator.class)//校验逻辑实现类
+public @interface CustomCheck {
+
+ String message() default "自定义校验默认错误提示";
+
+ /**
+ * 自定义参数,可传递到校验实现类CustomCheckValidator中
+ */
+ String param1() default "";
+
+ Class>[] groups() default {}; //用于分组校验
+
+ Class extends Payload>[] payload() default {};
+
+}
+```
+
+然后编写校验逻辑类
+
+```java
+public class CustomCheckValidator implements ConstraintValidator {
+ private String param1;
+
+ @Override
+ public void initialize(CustomCheck constraintAnnotation) {
+ //在此获取校验参数
+ param1 = constraintAnnotation.param1();
+ ConstraintValidator.super.initialize(constraintAnnotation);
+ }
+
+ @Override
+ public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
+ return param1 != null && param1.equals(s);
+ }
+}
+```
+
+最后即可向正常校验注解一样使用。详情见代码`TestController#test6`
+
+## 校验异常处理
+
+### `BindingResult`处理
+
+前一节讲了如何进行参数校验,那么参数校验的结果怎么处理呢?一种方式是将校验结果放在`BindingResult`中,如下:
+
+```java
+/**
+ * 自定义校验
+ */
+ @PostMapping("/test6")
+ public Result test6(@Validated @RequestBody TestBody4 testBody,BindingResult result) {
+ if(result.hasErrors()){
+ log.info("asdf");
+ }
+ return null;
+ }
+```
+显而易见这种处理方式很繁琐,需要对每个接口都做一样的处理,不推荐使用。
+
+**推荐使用全局异常处理来对校验结果进行统一处理**
+
+### 全局异常处理
+
+[上一篇:blog.fleyx.com/blog/detail/20210927/](https://blog.fleyx.com/blog/detail/20210927/)中详细说明了springboot的全局异常处理,不了解的可以去看看。
+
+在全局异常处理类`Exceptionhandle`中能对全部的异常进行捕获处理,那么我们只需要找到参数校验抛出的异常,然后针对这些异常进行处理就可以了。代码如下:
+
+```java
+@RestControllerAdvice
+@Slf4j
+public class ExceptionHandle {
+ @ExceptionHandler(Exception.class)
+ public Result handleException(Exception e) {
+ BaseException be;
+ if (e instanceof BaseException) {
+ be = (BaseException) e;
+ //手动抛出的异常,仅记录message
+ log.info("baseException:{}", be.getMessage());
+ if (be instanceof CustomBusinessException) {
+ //可在这里进行一些针对特定异常的处理逻辑
+ log.info("customBusinessException:{}", be.getMessage());
+ }
+ } else if (e instanceof ConstraintViolationException) {
+ //url参数、数组类参数校验报错类
+ ConstraintViolationException ce = (ConstraintViolationException) e;
+ //针对参数校验异常,建立了一个异常类
+ be = new CustomValidException(ce.getMessage());
+ } else if (e instanceof MethodArgumentNotValidException) {
+ //json对象类参数校验报错类
+ MethodArgumentNotValidException ce = (MethodArgumentNotValidException) e;
+ be = new CustomValidException(Objects.requireNonNull(ce.getFieldError()).getDefaultMessage());
+ } else {
+ //其它异常,非自动抛出的,无需给前端返回具体错误内容(用户不需要看见空指针之类的异常信息)
+ log.error("other exception:{}", e.getMessage(), e);
+ be = new BaseException("系统异常,请稍后重试", e);
+ }
+ return new Result(be.getCode(), be.getMessage(), null);
+ }
+}
+```
+核心是对`ConstraintViolationException`和`MethodArgumentNotValidException`两种异常的处理。
+
+## 结尾
+
+本篇收集了大量的材料,对参数校验的相关内容覆盖应该比较全面了,码字不已,望点赞收藏。
+
+**本篇用到的全部代码见:**[github](https://github.com/FleyX/demo-project/tree/master/spring-boot/paramsCheck)
+
+**本文原创发布于:[https://blog.fleyx.com/blog/detail/20211009](https://blog.fleyx.com/blog/detail/20211009)**
diff --git a/java/springboot系列/sprint-boot全局异常处理.md b/java/springboot系列/sprint-boot全局异常处理.md
new file mode 100644
index 0000000..9b0734e
--- /dev/null
+++ b/java/springboot系列/sprint-boot全局异常处理.md
@@ -0,0 +1,201 @@
+---
+id: "20210927"
+date: "2021-09-27 15:42:00"
+title: "SpringBoot异常处理看这篇就够了"
+tags: ["java", "SpringBoot","exception"]
+categories:
+ - "java"
+ - "spring boot学习"
+---
+
+**本文用到全部源码,见文末**
+
+## 为什么要有全局异常处理
+
+一个软件系统总是会遇到各种异常,包括人为抛出的业务异常,以及bug导致的异常。那怎么对这些异常进行处理呢?
+
+最简单的做法是在`controller`层用`try/catch`捕获这些异常,然后进行对应的处理.显而易见这种处理方法存在很大的问题,主要是:
+
+1. **代码冗余**:`controller`中会存在大量的`try/catch`代码,这些代码可能内容基本都是一样的,属于垃圾代码
+
+2. **不便于修改**:假设需要对某种错误类型进行特殊处理,那么需要修改所有的接口,很麻烦,也容易出错
+
+## Spring统一异常处理
+
+那么有没有一种方法能够统一处理呢?当然是有的,spring中的AOP就是用来做这样的统一处理的。当然不需要我们来实现这个AOP逻辑。spring已经帮我们实现了。通过`@ControllerAdvice`(从英文名称就能看出来这个注解用于处理controller的各种事件通知)注解,可以配合`@ExceptionHandler`、`@InitBinder`、`@ModelAttribute`等注解配套使用.既然是异常处理,那么我们需要用到的就是`@ExceptionHandler`.
+
+
+
+最简单的用法如下:
+
+建立一个ExceptionHandle类即可(**`@RestControllerAdvice`就是`@ControllerAdvice`和`@ResponseBody`的组合注解**):
+```java
+/**
+ * @author fanxb
+ * @date 2021-09-24-下午4:37
+ */
+@RestControllerAdvice
+@Slf4j
+public class ExceptionHandle {
+ @ExceptionHandler(Exception.class)
+ public Result handleException(Exception e) {
+ log.error("捕获到错误:{}", e.getMessage(), e);
+ return new Result(0, e.getMessage(), null);
+ }
+}
+```
+
+建立上述类后,**controller**层抛出的异常全部会进入到`handleException`方法中进行统一处理。
+
+**注意,只有controller抛出的异常会到这里来,过滤器、拦截器的异常是不行的,因为这里的AOP切点是controller**
+
+## 如何优雅的进行错误处理
+
+上一节说明了如何对异常进行捕获,那么在真实的项目中是如何进行处理的呢?这里介绍一种比较优雅的处理方式。
+
+### 定义统一返回类
+
+统一接口返回的数据格式,便于前后端交互,同时也方便进行一些统一的操作。
+
+```java
+/**
+ * 下面三个注解是lombok的减负注解,减少一些结构性的编码
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class Result implements Serializable {
+ private static final long serialVersionUID = 451834802206432869L;
+ /**
+ * 1:成功,其他失败
+ */
+ private int code;
+ /**
+ * message提示内容,当接口异常时提示异常信息
+ */
+ private String message;
+ /**
+ * 携带真正需要返回的数据
+ */
+ private Object data;
+
+ public static Result success(Object obj) {
+ return new Result(1, null, obj);
+ }
+}
+```
+
+### 定义异常类
+
+首先定义一个基础异常类,继承`RuntimeException`
+
+```java
+@Getter
+public class BaseException extends RuntimeException {
+ private static final long serialVersionUID = -3149747381632839680L;
+ /**
+ * 基本错误code为0
+ */
+ private static final int BASE_CODE = 0;
+ private final int code;
+ private final String message;
+
+ public BaseException() {
+ this(null, null, null);
+ }
+
+ public BaseException(String message) {
+ this(message, null, null);
+ }
+
+ public BaseException(Exception e) {
+ this(null, null, e);
+ }
+
+ public BaseException(String message, Exception e) {
+ this(message, null, e);
+ }
+
+ protected BaseException(String message, Integer code, Exception e) {
+ super(e);
+ this.message = message == null ? "" : message;
+ this.code = code == null ? BASE_CODE : code;
+ }
+
+ @Override
+ public String getMessage() {
+ if (this.message != null && this.message.length() > 0) {
+ return this.message;
+ }
+ return super.getMessage();
+ }
+}
+```
+
+可以看到其中有个构造方法是`protect`,是为了避免开发图省事直接传入错误码code(强制建立新的业务异常类)
+
+然后如果有自定义的异常,需要建立新的异常类继承`BaseException`,比如定义一个`CustomBusinessException`
+```java
+public class CustomBusinessException extends BaseException {
+ private static final long serialVersionUID = 1564935267302330109L;
+
+ /**
+ * 自定义业务异常错误码
+ */
+ private static final int ERROR_CODE = -1;
+
+ public CustomBusinessException() {
+ super("自定义义务异常", ERROR_CODE, null);
+ }
+
+ public CustomBusinessException(String message, Exception e) {
+ super(message, ERROR_CODE, e);
+ }
+}
+
+```
+
+之所以用这种新建业务异常类的方式来定义错误码,一方面是为了提高代码可读性,另一方面也是为了方便对异常进行分类处理。
+
+另外也可采用另外一种方式,将错误码定义为枚举类型,构造函数中传入枚举。
+
+### 编写统一异常处理类
+
+```java
+@RestControllerAdvice
+@Slf4j
+public class ExceptionHandle {
+ @ExceptionHandler(Exception.class)
+ public Result handleException(Exception e) {
+ BaseException be;
+ if (e instanceof BaseException) {
+ be = (BaseException) e;
+ //手动抛出的异常,仅记录message
+ log.info("baseException:{}", be.getMessage());
+ if (be instanceof CustomBusinessException) {
+ //可在这里进行一些针对特定异常的处理逻辑
+ log.info("customBusinessException:{}", be.getMessage());
+ }
+ } else {
+ //其它异常,非自动抛出的,无需给前端返回具体错误内容(用户不需要看见空指针之类的异常信息)
+ log.error("other exception:{}", e.getMessage(), e);
+ be = new BaseException("系统异常,请稍后重试", e);
+ }
+ return new Result(be.getCode(), be.getMessage(), null);
+ }
+}
+
+```
+
+可以将各种统一处理逻辑定义在这里。本类中是使用一个方法来对所有的异常进行处理,在这个方法中再对异常类型进行区分。还有另外一种写法是定义多个handle方法,每个handle处理一种异常。
+
+**另外在此还可以统一处理参数校验的异常,无需在接口代码中手动判断,下一篇中专门说明**
+
+### 如何使用
+
+通常代码会分为controller,service,dao三层,业务代码会写在service层中,因此一般是在service层中抛出异常。当然这三层中抛出的未捕获异常都能被`Exceptionhandle`捕获。
+
+**本文所用到代码见:[github](https://github.com/FleyX/demo-project/tree/master/spring-boot/exceptionTest)**
+
+
+**本文原创发布于:**[blog.fleyx.com/blog/detail/20210927](https://blog.fleyx.com/blog/detail/20210927)
diff --git a/linux/软件安装/NetworkManager自定义dns.md b/linux/软件安装/NetworkManager自定义dns.md
new file mode 100644
index 0000000..016da76
--- /dev/null
+++ b/linux/软件安装/NetworkManager自定义dns.md
@@ -0,0 +1,46 @@
+---
+id: "20211019"
+date: "2021-10-19 15:42:00"
+title: "NetworkManager如何自定义dns,永久生效"
+tags: ["linux", "NetworkManger"]
+categories:
+ - "linux"
+ - "program"
+---
+## 前言
+
+目前比较新的linux发行版都默认使用NetworkManger来管理网络了,然后如何自定义dns就成了一个比较麻烦的事,每次NetworkManager启动都会覆盖`/etc/resolv.conf`文件,特别是使用dhcp获取ip时,dns地址会变成dhcp服务器默认的dns.那么有哪些解决办法呢?
+
+## 修改/etc/resolv.conf
+
+既然这个文件会被NetworkManager修改,那么让它改不了就行了。将resolv.conf设置为不可修改。命令如下:
+```bash
+sudo chattr +i /etc/resolv.conf
+```
+这样我们自定义dns后就不会被NetworkManager重新覆盖了。
+
+
+
+## 更幽雅的配置
+
+上面虽然能达到目的,但是不太幽雅。其实NetworkManager是支持自定义dns的,办法如下:
+
+1. 修改/etc/NetworkManager/conf.d/dns.conf(如没有此文件,新建即可),增加如下两行配置:
+
+```conf
+[main]
+dns=null
+```
+
+2. 修改/etc/NetworkManager/conf.d/dns-servers.conf(如没有此文件,新建即可),增加如下两行配置,设置自定义dns:
+```conf
+[global-dns-domain-*]
+servers=::1,127.0.0.1,8.8.8.8
+```
+
+3. 重启软件`sudo systemctl restart NetworkManager`
+
+大功告成~
+
+
+**本文原创发布于:**[blog.fleyx.com/blog/detail/20210927](https://blog.fleyx.com/blog/detail/20211019)
\ No newline at end of file
diff --git a/linux/软件安装/jenkins-ssh密钥问题.md b/linux/软件安装/jenkins-ssh密钥问题.md
new file mode 100644
index 0000000..343aa5f
--- /dev/null
+++ b/linux/软件安装/jenkins-ssh密钥问题.md
@@ -0,0 +1,47 @@
+---
+id: '20211028'
+date: '2021-10-28 15:42:00'
+title: 'jenkins配置ssh密钥相关问题'
+tags: ['linux', 'ssh-rsa']
+categories:
+ - 'linux'
+ - 'program'
+---
+
+最近在使用 jenkins 时,发现了一个比较麻烦的问题,配置 ssh 密钥后,使用密钥登陆远程主机会报错。主要有两种问题:
+
+## jenkens 报错`"C:\\Users\\JE~1\\AppData\\Local\\Temp\\ssh2142299850576289882.key": invalid format`
+
+类似上面 jenkins 日志打印的错误,说无效的格式,出现这个问题的根本原因是 jenkins 支持的密钥格式比较旧,
+
+
+
+```
+-----BEGIN OPENSSH PRIVATE KEY-----
+```
+
+查看自己 ssh 密钥如果开始是上面的文本,说明是 jenkins 不支持的格式,配置到 jenkins 中会报错`invalid format `
+
+### 解决办法
+
+指定使用旧的格式即可,如下:
+
+```bash
+ssh-keygen -m PEM -t rsa -m "test"
+```
+
+增加`-m PEM`参数以使用旧的参数
+
+## 远程主机报错`pubkey: key type ssh-rsa not in PubkeyAcceptedAlgorithms`
+
+这个错误原因是因为最近 open ssh 的新版本中,已经废弃了对 ssh-rsa 的支持。
+
+### 解决办法
+
+编辑**远程主机**的 ssh 服务端配置文件,`/etc/ssh/sshd_config`,增加如下配置:
+
+```conf
+PubkeyAcceptedKeyTypes=+ssh-rsa
+```
+
+然后重启 ssh 就行了
\ No newline at end of file