From a2b22aca88282077b25b01d2857645629e7eaaad Mon Sep 17 00:00:00 2001
From: fengbaichao <fengbaichaoZK@163.com>
Date: Sun, 18 Apr 2021 10:41:20 +0800
Subject: [PATCH 01/22] =?UTF-8?q?=E4=BD=BF=E7=94=A8PowerMockRunner?=
 =?UTF-8?q?=E5=92=8CMockito=E7=BC=96=E5=86=99=E5=8D=95=E5=85=83=E6=B5=8B?=
 =?UTF-8?q?=E8=AF=95=E7=94=A8=E4=BE=8B=E8=AF=A6=E8=A7=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: fengbaichao <fengbaichaoZK@163.com>
---
 docs/basis/PowerMockRunnerAndMockito.md | 177 ++++++++++++++++++++++++
 1 file changed, 177 insertions(+)
 create mode 100644 docs/basis/PowerMockRunnerAndMockito.md

diff --git a/docs/basis/PowerMockRunnerAndMockito.md b/docs/basis/PowerMockRunnerAndMockito.md
new file mode 100644
index 0000000..dbc2b0a
--- /dev/null
+++ b/docs/basis/PowerMockRunnerAndMockito.md
@@ -0,0 +1,177 @@
+单元测试可以提高测试开发的效率,减少代码错误率,提高代码健壮性,提高代码质量。在Spring框架中常用的两种测试框架:PowerMockRunner和SpringRunner两个单元测试,鉴于SpringRunner启动的一系列依赖和数据连接的问题,推荐使用PowerMockRunner,这样能有效的提高测试的效率,并且其提供的API能覆盖的场景广泛,使用方便,可谓是Java单元测试之模拟利器。
+
+#### 1. PowerMock是什么?
+
+PowerMock是一个Java模拟框架,可用于解决通常认为很难甚至无法测试的测试问题。使用PowerMock,可以模拟静态方法,删除静态初始化程序,允许模拟而不依赖于注入,等等。PowerMock通过在执行测试时在运行时修改字节码来完成这些技巧。PowerMock还包含一些实用程序,可让您更轻松地访问对象的内部状态。
+
+
+举个例子,你在使用Junit进行单元测试时,并不想让测试数据进入数据库,怎么办?这个时候就可以使用PowerMock,拦截数据库操作,并模拟返回参数。
+
+#### 2. PowerMock包引入
+
+```xml
+<!-- 单元测试 依赖-->
+<dependency>
+    <groupId>org.powermock</groupId>
+    <artifactId>powermock-core</artifactId>
+    <version>2.0.2</version>
+    <scope>test</scope>
+</dependency>
+<dependency>
+<groupId>org.mockito</groupId>
+<artifactId>mockito-core</artifactId>
+<version>2.23.0</version>
+</dependency>
+<dependency>
+<groupId>org.powermock</groupId>
+<artifactId>powermock-module-junit4</artifactId>
+<version>2.0.4</version>
+<scope>test</scope>
+</dependency>
+<dependency>
+<groupId>org.powermock</groupId>
+<artifactId>powermock-api-mockito2</artifactId>
+<version>2.0.2</version>
+<scope>test</scope>
+</dependency>
+<dependency>
+<groupId>com.github.jsonzou</groupId>
+<artifactId>jmockdata</artifactId>
+<version>4.3.0</version>
+</dependency>
+        <!-- 单元测试 依赖-->
+```
+
+#### 3. 重要注解说明
+
+```java
+@RunWith(PowerMockRunner.class) // 告诉JUnit使用PowerMockRunner进行测试
+@PrepareForTest({RandomUtil.class}) // 所有需要测试的类列在此处,适用于模拟final类或有final, private, static, native方法的类
+@PowerMockIgnore("javax.management.*") //为了解决使用powermock后,提示classloader错误
+```
+
+
+
+#### 4. 使用示例
+
+##### 4.1 模拟接口返回
+
+首先对接口进行mock,然后录制相关行为
+
+```java
+InterfaceToMock mock = Powermockito.mock(InterfaceToMock.class)
+
+        Powermockito.when(mock.method(Params…)).thenReturn(value)
+
+        Powermockito.when(mock.method(Params..)).thenThrow(Exception)
+```
+
+##### 4.2 设置对象的private属性
+
+需要使用whitebox向class或者对象中赋值。
+
+如我们已经对接口尽心了mock,现在需要将此mock加入到对象中,可以采用如下方法:
+
+```java
+Whitebox.setInternalState(Object object, String fieldname, Object… value);
+```
+
+其中object为需要设置属性的静态类或对象。
+
+##### 4.3 模拟构造函数
+
+对于模拟构造函数,也即当出现new InstanceClass()时可以将此构造函数拦截并替换结果为我们需要的mock对象。
+
+注意:使用时需要加入标记:
+
+```java
+@RunWith(PowerMockRunner.class)
+
+@PrepareForTest({ InstanceClass.class })
+
+@PowerMockIgnore("javax.management.\*")
+
+Powermockito.whenNew(InstanceClass.class).thenReturn(Object value)
+```
+
+##### 4.4 模拟静态方法
+
+模拟静态方法类似于模拟构造函数,也需要加入注释标记。
+
+```java
+@RunWith(PowerMockRunner.class)
+
+@PrepareForTest({ StaticClassToMock.class })
+
+@PowerMockIgnore("javax.management.\*")
+
+Powermockito.mockStatic(StaticClassToMock.class);
+
+        Powermockito.when(StaticClassToMock.method(Object.. params)).thenReturn(Object value)
+```
+
+##### 4.5 模拟final方法
+
+Final方法的模拟类似于模拟静态方法。
+
+```java
+@RunWith(PowerMockRunner.class)
+
+@PrepareForTest({ FinalClassToMock.class })
+
+@PowerMockIgnore("javax.management.\*")
+
+Powermockito.mockStatic(FinalClassToMock.class);
+
+        Powermockito.when(StaticClassToMock.method(Object.. params)).thenReturn(Object value)
+```
+
+##### 4.6 模拟静态类
+
+模拟静态类类似于模拟静态方法。
+
+##### 4.7 使用spy方法避免执行被测类中的成员函数
+
+如被测试类为:TargetClass,想要屏蔽的方法为targetMethod.
+
+```java
+1) PowerMockito.spy(TargetClass.class);
+
+        2) Powemockito.when(TargetClass.targetMethod()).doReturn()
+
+        3) 注意加入
+
+@RunWith(PowerMockRunner.class)
+
+@PrepareForTest(DisplayMoRelationBuilder.class)
+
+@PowerMockIgnore("javax.management.*")
+```
+
+##### 4.8 参数匹配器
+
+有时我们在处理doMethod(Param param)时,不想进行精确匹配,这时可以使用Mockito提供的模糊匹配方式。
+
+如:Mockito.anyInt(),Mockito.anyString()
+
+##### 4.9 处理public void型的静态方法
+
+```java
+Powermockito.doNothing.when(T class2mock, String method, <T>… params>
+```
+
+#### 5. 单元测试用例可选清单
+
+输入数据验证:这些检查通常可以对输入到应用程序系统中的数据采用。
+
+- 必传项测试
+- 唯一字段值测试
+- 空值测试
+- 字段只接受允许的字符
+- 负值测试
+- 字段限于字段长度规范
+- 不可能的值
+- 垃圾值测试
+- 检查字段之间的依赖性
+- 等效类划分和边界条件测试
+- 错误和异常处理测试
\ No newline at end of file

From 88b764a6e806b28cfa10617e616c019a18415026 Mon Sep 17 00:00:00 2001
From: guide <koushuangbwcx@163.com>
Date: Mon, 19 Apr 2021 16:30:40 +0800
Subject: [PATCH 02/22] Update SpringBoot-ScheduleTasks.md

---
 docs/advanced/SpringBoot-ScheduleTasks.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/advanced/SpringBoot-ScheduleTasks.md b/docs/advanced/SpringBoot-ScheduleTasks.md
index 135eb2d..a443545 100644
--- a/docs/advanced/SpringBoot-ScheduleTasks.md
+++ b/docs/advanced/SpringBoot-ScheduleTasks.md
@@ -12,7 +12,7 @@
 
 >  Cron 表达式:  主要用于定时作业(定时任务)系统定义执行时间或执行频率的表达式,非常厉害,你可以通过 Cron 表达式进行设置定时任务每天或者每个月什么时候执行等等操作。
 >
-> 推荐一个在线Cron表达式生成器:[http://cron.qqe2.com/](http://cron.qqe2.com/)
+> 推荐一个在线Cron表达式生成器: [https://crontab.guru/](https://crontab.guru/)
 
 ```java
 import org.slf4j.Logger;

From 0dffa1aca96c9c8148f59049968960ca963d99ae Mon Sep 17 00:00:00 2001
From: fengbaichao <fengbaichaoZK@163.com>
Date: Mon, 19 Apr 2021 23:56:50 +0800
Subject: [PATCH 03/22] =?UTF-8?q?=E9=80=9A=E8=BF=87=E6=BA=90=E7=A0=81?=
 =?UTF-8?q?=E5=88=86=E6=9E=90Spring=20Security=E7=94=A8=E6=88=B7=E8=AE=A4?=
 =?UTF-8?q?=E8=AF=81=E6=B5=81=E7=A8=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 ...g-security-login-authentication-process.md | 196 ++++++++++++++++++
 docs/basis/PowerMockRunnerAndMockito.md       |   8 +-
 2 files changed, 200 insertions(+), 4 deletions(-)
 create mode 100644 docs/advanced/spring-security-login-authentication-process.md

diff --git a/docs/advanced/spring-security-login-authentication-process.md b/docs/advanced/spring-security-login-authentication-process.md
new file mode 100644
index 0000000..89d0328
--- /dev/null
+++ b/docs/advanced/spring-security-login-authentication-process.md
@@ -0,0 +1,196 @@
+Spring Security的登录主要是由一系列的过滤器组成,我们如果需要修改登录的校验逻辑,只需要在过滤器链路上添加修改相关的逻辑即可。这里主要通过Spring Security的源码来了解相关的认证登录的逻辑。
+
+#### 1.Spring Security的认证流程
+
+主要分析:
+
+1. 认证用户的流程
+2. 如何进行认证校验
+3. 认证成功后怎么获取用户信息
+
+具体的过滤器链路如下所示:
+
+[![cT2G4g.png](https://z3.ax1x.com/2021/04/19/cT2G4g.png)](https://imgtu.com/i/cT2G4g)
+
+Spring Security的认证流程图如下,认证的主要过程有:
+
+1. 用户提交用户名和密码,然后通过UsernamePasswordAuthenticationFilter对其进行封装成为UsernamePasswordAuthenticationToken对象,这个是AbstractAuthenticationToken的子类,而AbstractAuthenticationToken又是Authentication的一个实现,所以可以看到后续获取的都是Authentication类型的对象实例;
+2. 将第一步的UsernamePasswordAuthenticationToken对象传递给AuthenticationManager;
+3. 通过AbstractUserDetailsAuthenticationProvider的默认实现类DaoAuthenticationProvider的retrieveUser方法,这个方法会调用UserDetailsService的loadUserByUsername方法来进行用户名和密码的判断,使用的默认的逻辑进行处理;
+4. 将成功认证后的用户信息放入到SecurityContextHolder中,之后可以通过SecurityContext获取用户的相关信息。
+
+[![coGpvR.png](https://z3.ax1x.com/2021/04/19/coGpvR.png)](https://imgtu.com/i/coGpvR)
+
+spring-security源码下载地址:
+
+```java
+https://github.com/spring-projects/spring-security
+```
+
+#### 2.Spring Security的认证源码分析
+
+##### 2.1 搭建项目并访问
+
+首先我们搭建一个Spring Security的项目,使用Spring Boot可以很方便的进行集成开发,主要引入如下的依赖即可(当然也可以查看官网,选择合适的版本):
+
+```java
+<dependency>
+  <groupId>org.springframework.boot</groupId>
+  <artifactId>spring-boot-starter-security</artifactId>
+</dependency>
+```
+
+启动项目后会随机生成一个密码串,这里需要复制保存以便登录的时候使用:
+
+[![coJ0ld.png](https://z3.ax1x.com/2021/04/19/coJ0ld.png)](https://imgtu.com/i/coJ0ld)
+
+访问登录地址:
+
+```java
+http://localhost:8080/login
+```
+
+[![coJfpQ.png](https://z3.ax1x.com/2021/04/19/coJfpQ.png)](https://imgtu.com/i/coJfpQ)
+
+默认的账户名和密码:
+
+```java
+账户名: user
+密码:   项目启动时生成的密码串
+```
+
+##### 2.2 进行源码分析
+
+1. 进行断点后会发现首先进入的是UsernamePasswordAuthenticationFilter的attemptAuthentication(HttpServletRequest request, HttpServletResponse response)方法,会对用户名和密码进行封装成UsernamePasswordAuthenticationToken对象,然后调用this.getAuthenticationManager().authenticate(authRequest)方法进入到AuthenticationManager中。
+
+   attemptAuthentication方法源码如下所示:
+
+```java
+@Override
+	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
+			throws AuthenticationException {
+		if (this.postOnly && !request.getMethod().equals("POST")) {
+			throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
+		}
+		String username = obtainUsername(request);
+		username = (username != null) ? username : "";
+		username = username.trim();
+		String password = obtainPassword(request);
+		password = (password != null) ? password : "";
+		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
+		// Allow subclasses to set the "details" property
+		setDetails(request, authRequest);
+		return this.getAuthenticationManager().authenticate(authRequest);
+	}
+```
+
+2. 随后请求进入到WebSecurityConfigurerAdapter的AuthenticationManagerDelegator中,AuthenticationManagerDelegator是AuthenticationManager的一个子类,最后封装成为UsernamePasswordAuthenticationToken对象,供DaoAuthenticationProvider使用。
+
+   AuthenticationManagerDelegator的源码如下:
+
+   ```java
+   static final class AuthenticationManagerDelegator implements AuthenticationManager {
+           private AuthenticationManagerBuilder delegateBuilder;
+           private AuthenticationManager delegate;
+           private final Object delegateMonitor = new Object();
+           private Set<String> beanNames;
+   
+           AuthenticationManagerDelegator(AuthenticationManagerBuilder delegateBuilder, ApplicationContext context) {
+               Assert.notNull(delegateBuilder, "delegateBuilder cannot be null");
+               Field parentAuthMgrField = ReflectionUtils.findField(AuthenticationManagerBuilder.class, "parentAuthenticationManager");
+               ReflectionUtils.makeAccessible(parentAuthMgrField);
+               this.beanNames = getAuthenticationManagerBeanNames(context);
+               validateBeanCycle(ReflectionUtils.getField(parentAuthMgrField, delegateBuilder), this.beanNames);
+               this.delegateBuilder = delegateBuilder;
+           }
+   
+           public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+               if (this.delegate != null) {
+                   return this.delegate.authenticate(authentication);
+               } else {
+                   synchronized(this.delegateMonitor) {
+                       if (this.delegate == null) {
+                           this.delegate = (AuthenticationManager)this.delegateBuilder.getObject();
+                           this.delegateBuilder = null;
+                       }
+                   }
+   
+                   return this.delegate.authenticate(authentication);
+               }
+           }
+   
+           private static Set<String> getAuthenticationManagerBeanNames(ApplicationContext applicationContext) {
+               String[] beanNamesForType = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, AuthenticationManager.class);
+               return new HashSet(Arrays.asList(beanNamesForType));
+           }
+   
+           private static void validateBeanCycle(Object auth, Set<String> beanNames) {
+               if (auth != null && !beanNames.isEmpty() && auth instanceof Advised) {
+                   TargetSource targetSource = ((Advised)auth).getTargetSource();
+                   if (targetSource instanceof LazyInitTargetSource) {
+                       LazyInitTargetSource lits = (LazyInitTargetSource)targetSource;
+                       if (beanNames.contains(lits.getTargetBeanName())) {
+                           throw new FatalBeanException("A dependency cycle was detected when trying to resolve the AuthenticationManager. Please ensure you have configured authentication.");
+                       }
+                   }
+               }
+           }
+       }
+   ```
+
+   org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration.AuthenticationManagerDelegator#authenticate
+
+   ```java
+   @Override
+   		public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+   			if (this.delegate != null) {
+   				return this.delegate.authenticate(authentication);
+   			}
+   			synchronized (this.delegateMonitor) {
+   				if (this.delegate == null) {
+   					this.delegate = this.delegateBuilder.getObject();
+   					this.delegateBuilder = null;
+   				}
+   			}
+   			return this.delegate.authenticate(authentication);
+   		}
+   ```
+
+3. 进入到DaoAuthenticationProvider的retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)方法进行用户的认证,这里的认证主要会调用默认的UserDetailsService对用户名和密码进行校验,如果是使用的类似于Mysql的数据源,其默认的实现是JdbcDaoImpl。
+
+   org.springframework.security.authentication.dao.DaoAuthenticationProvider#retrieveUser
+
+   ```java
+   @Override
+   	protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
+   			throws AuthenticationException {
+   		prepareTimingAttackProtection();
+   		try {
+   			UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
+   			if (loadedUser == null) {
+   				throw new InternalAuthenticationServiceException(
+   						"UserDetailsService returned null, which is an interface contract violation");
+   			}
+   			return loadedUser;
+   		}
+   		catch (UsernameNotFoundException ex) {
+   			mitigateAgainstTimingAttack(authentication);
+   			throw ex;
+   		}
+   		catch (InternalAuthenticationServiceException ex) {
+   			throw ex;
+   		}
+   		catch (Exception ex) {
+   			throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
+   		}
+   	}
+   
+   ```
+
+4. 将上一步认证后的用户实例放入SecurityContextHolder中,至此我们可以很方便的从SecurityContextHolder中获取用户信息,方法如下:
+
+   ```java
+      Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+   ```
+
+   
diff --git a/docs/basis/PowerMockRunnerAndMockito.md b/docs/basis/PowerMockRunnerAndMockito.md
index dbc2b0a..d5a7b12 100644
--- a/docs/basis/PowerMockRunnerAndMockito.md
+++ b/docs/basis/PowerMockRunnerAndMockito.md
@@ -61,9 +61,9 @@ PowerMock是一个Java模拟框架,可用于解决通常认为很难甚至无
 ```java
 InterfaceToMock mock = Powermockito.mock(InterfaceToMock.class)
 
-        Powermockito.when(mock.method(Params…)).thenReturn(value)
+Powermockito.when(mock.method(Params…)).thenReturn(value)
 
-        Powermockito.when(mock.method(Params..)).thenThrow(Exception)
+Powermockito.when(mock.method(Params..)).thenThrow(Exception)
 ```
 
 ##### 4.2 设置对象的private属性
@@ -137,9 +137,9 @@ Powermockito.mockStatic(FinalClassToMock.class);
 ```java
 1) PowerMockito.spy(TargetClass.class);
 
-        2) Powemockito.when(TargetClass.targetMethod()).doReturn()
+2) Powemockito.when(TargetClass.targetMethod()).doReturn()
 
-        3) 注意加入
+3) 注意加入
 
 @RunWith(PowerMockRunner.class)
 

From ce64c42252e6889b046971b9248ce4f2b7da3cbe Mon Sep 17 00:00:00 2001
From: guide <koushuangbwcx@163.com>
Date: Wed, 21 Apr 2021 21:45:14 +0800
Subject: [PATCH 04/22] =?UTF-8?q?[feat]SpringBoot=E5=8F=82=E6=95=B0?=
 =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E9=87=8D=E6=9E=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 README.md                                     |   6 +-
 docs/advanced/spring-bean-validation.md       | 574 ++++++++++--------
 .../advanced/bean-validation-demo/pom.xml     |  13 +-
 .../controller/PersonController.java          |  17 +-
 .../beanvalidationdemo/entity/Person.java     |  33 +-
 .../entity/PersonRequest.java                 |  44 ++
 .../service/PersonService.java                |   3 +-
 .../validation/PhoneNumber.java               |   2 +
 .../validation/PhoneNumberValidator.java      |   9 +-
 .../HelloWorldControllerTest.java             |  28 -
 .../PersonControllerTest.java                 |  76 +--
 .../beanvalidationdemo/PersonServiceTest.java |  23 +-
 12 files changed, 412 insertions(+), 416 deletions(-)
 create mode 100644 source-code/advanced/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/entity/PersonRequest.java
 delete mode 100644 source-code/advanced/bean-validation-demo/src/test/java/com/example/beanvalidationdemo/HelloWorldControllerTest.java

diff --git a/README.md b/README.md
index 68e0616..e13f958 100644
--- a/README.md
+++ b/README.md
@@ -30,10 +30,10 @@
 ### 进阶
 
 1. Bean映射工具 :[Bean映射工具之Apache BeanUtils VS Spring BeanUtils](./docs/advanced/Apache-BeanUtils-VS-SpringBean-Utils.md) 、[5种常见Bean映射工具的性能比对](./docs/advanced/Performance-of-Java-Mapping-Frameworks.md)
-3. **[如何在 Spring/Spring Boot 中做参数校验?你需要了解的都在这里!](./docs/advanced/spring-bean-validation.md)**
+3. [如何在 Spring/Spring Boot 中做参数校验?](./docs/advanced/spring-bean-validation.md)
 4. [5分钟搞懂如何在Spring Boot中Schedule Tasks](./docs/advanced/SpringBoot-ScheduleTasks.md) 
-5. **[新手也能看懂的 Spring Boot 异步编程指南](./docs/advanced/springboot-async.md)**
-7. **[Kafka 入门+SpringBoot整合Kafka系列](https://github.com/Snailclimb/springboot-kafka)**
+5. [新手也能看懂的 Spring Boot 异步编程指南](./docs/advanced/springboot-async.md)
+7. [Kafka 入门+SpringBoot整合Kafka系列](https://github.com/Snailclimb/springboot-kafka)
 8. [超详细,新手都能看懂 !使用Spring Boot+Dubbo 搭建一个分布式服务](./docs/advanced/springboot-dubbo.md)
 9. [从零入门 !Spring Security With JWT(含权限验证)](https://github.com/Snailclimb/spring-security-jwt-guide)
 
diff --git a/docs/advanced/spring-bean-validation.md b/docs/advanced/spring-bean-validation.md
index 902220a..e7a350d 100644
--- a/docs/advanced/spring-bean-validation.md
+++ b/docs/advanced/spring-bean-validation.md
@@ -1,142 +1,157 @@
-**数据的校验的重要性就不用说了,即使在前端对数据进行校验的情况下,我们还是要对传入后端的数据再进行一遍校验,避免用户绕过浏览器直接通过一些 HTTP 工具直接向后端请求一些违法数据。**
+数据的校验的重要性就不用说了,即使在前端对数据进行校验的情况下,我们还是要对传入后端的数据再进行一遍校验,避免用户绕过浏览器直接通过一些 HTTP 工具直接向后端请求一些违法数据。
 
-本文结合自己在项目中的实际使用经验,可以说**文章介绍的内容很实用,不了解的朋友可以学习一下,后面可以立马实践到项目上去。**
+最普通的做法就像下面这样。我们通过 `if/else` 语句对请求的每一个参数一一校验。
 
-下面我会通过实例程序演示如何在 Java 程序中尤其是 Spring 程序中优雅地的进行参数验证。
-
-## 基础设施搭建
+```java
+@RestController
+@RequestMapping("/api/person")
+public class PersonController {
 
-### 相关依赖
+    @PostMapping
+    public ResponseEntity<PersonRequest> save(@RequestBody PersonRequest personRequest) {
+        if (personRequest.getClassId() == null
+                || personRequest.getName() == null
+                || !Pattern.matches("(^Man$|^Woman$|^UGM$)", personRequest.getSex())) {
 
-如果开发普通 Java 程序的的话,你需要可能需要像下面这样依赖:
-
-```xml
-   <dependency>
-            <groupId>org.hibernate.validator</groupId>
-            <artifactId>hibernate-validator</artifactId>
-            <version>6.0.9.Final</version>
-   </dependency>
-   <dependency>
-             <groupId>javax.el</groupId>
-             <artifactId>javax.el-api</artifactId>
-             <version>3.0.0</version>
-     </dependency>
-     <dependency>
-            <groupId>org.glassfish.web</groupId>
-            <artifactId>javax.el</artifactId>
-            <version>2.2.6</version>
-     </dependency>
+        }
+        return ResponseEntity.ok().body(personRequest);
+    }
+}
 ```
 
-使用 Spring Boot 程序的话只需要`spring-boot-starter-web` 就够了,它的子依赖包含了我们所需要的东西。除了这个依赖,下面的演示还用到了 lombok ,所以不要忘记添加上相关依赖。
-
-```xml
-    <dependencies>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-web</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.projectlombok</groupId>
-            <artifactId>lombok</artifactId>
-            <optional>true</optional>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-test</artifactId>
-            <scope>test</scope>
-        </dependency>
-    </dependencies>
-```
+这样的代码,小伙伴们在日常开发中一定不少见,很多开源项目都是这样对请求入参做校验的。
 
-### 实体类
+但是,不太建议这样来写,这样的代码明显违背了 **单一职责原则**。大量的非业务代码混杂在业务代码中,非常难以维护,还会导致业务层代码冗杂!
 
-下面这个是示例用到的实体类。
+实际上,我们是可以通过一些简单的手段对上面的代码进行改进的!这也是本文主要要介绍的内容!
 
-```java
-@Data
-@AllArgsConstructor
-@NoArgsConstructor
-public class Person {
+废话不多说!下面我会结合自己在项目中的实际使用经验,通过实例程序演示如何在 SpringBoot 程序中优雅地的进行参数验证(普通的 Java 程序同样适用)。
 
-    @NotNull(message = "classId 不能为空")
-    private String classId;
+不了解的朋友一定要好好看一下,学完马上就可以实践到项目上去。
 
-    @Size(max = 33)
-    @NotNull(message = "name 不能为空")
-    private String name;
+并且,本文实例项目使用的是目前最新的 Spring Boot 版本 2.4.5!(截止到 2021-04-21)
 
-    @Pattern(regexp = "((^Man$|^Woman$|^UGM$))", message = "sex 值不在可选范围")
-    @NotNull(message = "sex 不能为空")
-    private String sex;
+## 添加相关依赖
 
-    @Email(message = "email 格式不正确")
-    @NotNull(message = "email 不能为空")
-    private String email;
+如果开发普通 Java 程序的的话,你需要可能需要像下面这样依赖:
 
-}
+```xml
+<dependency>
+    <groupId>org.hibernate.validator</groupId>
+    <artifactId>hibernate-validator</artifactId>
+    <version>6.0.9.Final</version>
+</dependency>
+<dependency>
+    <groupId>javax.el</groupId>
+    <artifactId>javax.el-api</artifactId>
+    <version>3.0.0</version>
+</dependency>
+<dependency>
+    <groupId>org.glassfish.web</groupId>
+    <artifactId>javax.el</artifactId>
+    <version>2.2.6</version>
+</dependency>
 ```
 
-> 正则表达式说明:
->
-> ```
-> - ^string : 匹配以 string 开头的字符串
-> - string$ :匹配以 string 结尾的字符串
-> - ^string$ :精确匹配 string 字符串
-> - ((^Man$|^Woman$|^UGM$)) : 值只能在 Man,Woman,UGM 这三个值中选择
-> ```
+不过,相信大家都是使用的 Spring Boot 框架来做开发。
 
-下面这部分校验注解说明内容参考自:https://www.cnkirito.moe/spring-validation/ ,感谢@[徐靖峰](https://github.com/lexburner)。
+基于 Spring Boot 的话,就比较简单了,只需要给项目添加上 `spring-boot-starter-web` 依赖就够了,它的子依赖包含了我们所需要的东西。另外,我们的示例项目中还使用到了 Lombok。
 
-**JSR提供的校验注解**:
+![](https://img-blog.csdnimg.cn/20210421172058450.png)
 
+```xml
+<dependencies>
+    <dependency>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-web</artifactId>
+    </dependency>
+    <dependency>
+        <groupId>org.projectlombok</groupId>
+        <artifactId>lombok</artifactId>
+        <optional>true</optional>
+    </dependency>
+    <dependency>
+        <groupId>junit</groupId>
+        <artifactId>junit</artifactId>
+        <version>4.13.1</version>
+        <scope>test</scope>
+    </dependency>
+    <dependency>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-test</artifactId>
+        <scope>test</scope>
+    </dependency>
+</dependencies>
+```
 
-- `@Null`   被注释的元素必须为 null 
-- `@NotNull`    被注释的元素必须不为 null
-- `@AssertTrue`     被注释的元素必须为 true 
-- `@AssertFalse`    被注释的元素必须为 false 
-- `@Min(value) `    被注释的元素必须是一个数字,其值必须大于等于指定的最小值 
-- `@Max(value) `    被注释的元素必须是一个数字,其值必须小于等于指定的最大值 
-- `@DecimalMin(value) ` 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
-- `@DecimalMax(value)`  被注释的元素必须是一个数字,其值必须小于等于指定的最大值 
-- `@Size(max=, min=) `  被注释的元素的大小必须在指定的范围内 
-- `@Digits (integer, fraction) `    被注释的元素必须是一个数字,其值必须在可接受的范围内 
-- `@Past `  被注释的元素必须是一个过去的日期 
-- `@Future`     被注释的元素必须是一个将来的日期  
-- `@Pattern(regex=,flag=) ` 被注释的元素必须符合指定的正则表达式
-
+但是!!! Spring Boot 2.3 1 之后,`spring-boot-starter-validation` 已经不包括在了 `spring-boot-starter-web` 中,需要我们手动加上!
 
-**Hibernate Validator提供的校验注解**:
+![](https://img-blog.csdnimg.cn/20210421170846695.png)
 
+```xml
+<dependency>
+    <groupId>org.springframework.boot</groupId>
+    <artifactId>spring-boot-starter-validation</artifactId>
+</dependency>
+```
 
-- `@NotBlank(message =) `  验证字符串非null,且长度必须大于0 
-- `@Email`  被注释的元素必须是电子邮箱地址 
-- `@Length(min=,max=) ` 被注释的字符串的大小必须在指定的范围内 
-- `@NotEmpty `  被注释的字符串的必须非空 
-- `@Range(min=,max=,message=)`  被注释的元素必须在合适的范围内
+## 验证 Controller 的输入
 
-## 验证Controller的输入
+### 验证请求体
 
-### 验证请求体(RequestBody)
+验证请求体即使验证被 `@RequestBody` 注解标记的方法参数。
 
-**Controller:**
+**`PersonController`**
 
-我们在需要验证的参数上加上了`@Valid`注解,如果验证失败,它将抛出`MethodArgumentNotValidException`。默认情况下,Spring会将此异常转换为HTTP Status 400(错误请求)。
+我们在需要验证的参数上加上了`@Valid`注解,如果验证失败,它将抛出`MethodArgumentNotValidException`。默认情况下,Spring 会将此异常转换为 HTTP Status 400(错误请求)。
 
 ```java
-
 @RestController
-@RequestMapping("/api")
+@RequestMapping("/api/person")
+@Validated
 public class PersonController {
 
-    @PostMapping("/person")
-    public ResponseEntity<Person> getPerson(@RequestBody @Valid Person person) {
-        return ResponseEntity.ok().body(person);
+    @PostMapping
+    public ResponseEntity<PersonRequest> save(@RequestBody @Valid PersonRequest personRequest) {
+        return ResponseEntity.ok().body(personRequest);
     }
 }
 ```
 
-**ExceptionHandler:**
+**`PersonRequest`**
+
+我们使用校验注解对请求的参数进行校验!
+
+```java
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class PersonRequest {
+
+    @NotNull(message = "classId 不能为空")
+    private String classId;
+
+    @Size(max = 33)
+    @NotNull(message = "name 不能为空")
+    private String name;
+
+    @Pattern(regexp = "(^Man$|^Woman$|^UGM$)", message = "sex 值不在可选范围")
+    @NotNull(message = "sex 不能为空")
+    private String sex;
+
+}
+
+```
+
+正则表达式说明:
+
+- `^string` : 匹配以 string 开头的字符串
+- `string$` :匹配以 string 结尾的字符串
+- `^string$` :精确匹配 string 字符串
+- `(^Man$|^Woman$|^UGM$)` : 值只能在 Man,Woman,UGM 这三个值中选择
+
+**`GlobalExceptionHandler`**
 
 自定义异常处理器可以帮助我们捕获异常,并进行一些简单的处理。如果对于下面的处理异常的代码不太理解的话,可以查看这篇文章 [《SpringBoot 处理异常的几种常见姿势》](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485568&idx=2&sn=c5ba880fd0c5d82e39531fa42cb036ac&chksm=cea2474bf9d5ce5dcbc6a5f6580198fdce4bc92ef577579183a729cb5d1430e4994720d59b34&token=1924773784&lang=zh_CN#rd)。
 
@@ -157,14 +172,11 @@ public class GlobalExceptionHandler {
 }
 ```
 
-**通过测试验证:**
-
-下面我通过 MockMvc 模拟请求 Controller 的方式来验证是否生效,当然你也可以通过 Postman 这种工具来验证。
+**通过测试验证**
 
-我们试一下所有参数输入正确的情况。
+下面我通过 `MockMvc` 模拟请求 `Controller` 的方式来验证是否生效。当然了,你也可以通过 `Postman` 这种工具来验证。
 
 ```java
-@RunWith(SpringRunner.class)
 @SpringBootTest
 @AutoConfigureMockMvc
 public class PersonControllerTest {
@@ -173,109 +185,93 @@ public class PersonControllerTest {
 
     @Autowired
     private ObjectMapper objectMapper;
-
+    /**
+     * 验证出现参数不合法的情况抛出异常并且可以正确被捕获
+     */
     @Test
-    public void should_get_person_correctly() throws Exception {
-        Person person = new Person();
-        person.setName("SnailClimb");
-        person.setSex("Man");
-        person.setClassId("82938390");
-        person.setEmail("Snailclimb@qq.com");
-
-        mockMvc.perform(post("/api/person")
-                .contentType(MediaType.APPLICATION_JSON_UTF8)
-                .content(objectMapper.writeValueAsString(person)))
-                .andExpect(MockMvcResultMatchers.jsonPath("name").value("SnailClimb"))
-                .andExpect(MockMvcResultMatchers.jsonPath("classId").value("82938390"))
-                .andExpect(MockMvcResultMatchers.jsonPath("sex").value("Man"))
-                .andExpect(MockMvcResultMatchers.jsonPath("email").value("Snailclimb@qq.com"));
-    }
-}
-```
-
-验证出现参数不合法的情况抛出异常并且可以正确被捕获。
-
-```java
- @Test
     public void should_check_person_value() throws Exception {
-        Person person = new Person();
-        person.setSex("Man22");
-        person.setClassId("82938390");
-        person.setEmail("SnailClimb");
-
-        mockMvc.perform(post("/api/person")
-                .contentType(MediaType.APPLICATION_JSON_UTF8)
-                .content(objectMapper.writeValueAsString(person)))
+        PersonRequest personRequest = PersonRequest.builder().sex("Man22")
+                .classId("82938390").build();
+        mockMvc.perform(post("/api/personRequest")
+                .contentType(MediaType.APPLICATION_JSON)
+                .content(objectMapper.writeValueAsString(personRequest)))
                 .andExpect(MockMvcResultMatchers.jsonPath("sex").value("sex 值不在可选范围"))
-                .andExpect(MockMvcResultMatchers.jsonPath("name").value("name 不能为空"))
-                .andExpect(MockMvcResultMatchers.jsonPath("email").value("email 格式不正确"));
+                .andExpect(MockMvcResultMatchers.jsonPath("name").value("name 不能为空"));
     }
+}
 ```
 
-使用 Postman 验证结果如下:
+**使用 `Postman` 验证**
+
+![](https://img-blog.csdnimg.cn/20210421175345253.png)
 
-![Postman 验证结果](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/postman-validation.png)
+### 验证请求参数
 
-### 验证请求参数(Path Variables 和 Request Parameters)
+验证请求参数(Path Variables 和 Request Parameters)即是验证被 `@PathVariable` 以及 `@RequestParam` 标记的方法参数。
 
-**Controller:**
+**`PersonController`**
 
 **一定一定不要忘记在类上加上 `Validated` 注解了,这个参数可以告诉 Spring 去校验方法参数。**
 
 ```java
 @RestController
-@RequestMapping("/api")
+@RequestMapping("/api/persons")
 @Validated
 public class PersonController {
 
-    @GetMapping("/person/{id}")
-    public ResponseEntity<Integer> getPersonByID(@Valid @PathVariable("id") @Max(value = 5,message = "超过 id 的范围了") Integer id) {
+    @GetMapping("/{id}")
+    public ResponseEntity<Integer> getPersonByID(@Valid @PathVariable("id") @Max(value = 5, message = "超过 id 的范围了") Integer id) {
         return ResponseEntity.ok().body(id);
     }
 
-    @PutMapping("/person")
-    public ResponseEntity<String> getPersonByName(@Valid @RequestParam("name") @Size(max = 6,message = "超过 name 的范围了") String name) {
+    @PutMapping
+    public ResponseEntity<String> getPersonByName(@Valid @RequestParam("name") @Size(max = 6, message = "超过 name 的范围了") String name) {
         return ResponseEntity.ok().body(name);
     }
 }
-
 ```
 
-**ExceptionHandler:**
+**`ExceptionHandler`**
 
 ```java
-    @ExceptionHandler(ConstraintViolationException.class)
-    ResponseEntity<String> handleConstraintViolationException(ConstraintViolationException e) {
-        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
-    }
+  @ExceptionHandler(ConstraintViolationException.class)
+  ResponseEntity<String> handleConstraintViolationException(ConstraintViolationException e) {
+     return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
+  }
 ```
 
-**通过测试验证:**
+**通过测试验证**
 
 ```java
-  @Test
-    public void should_check_param_value() throws Exception {
+@Test
+public void should_check_path_variable() throws Exception {
+    mockMvc.perform(get("/api/person/6")
+                    .contentType(MediaType.APPLICATION_JSON))
+      .andExpect(status().isBadRequest())
+      .andExpect(content().string("getPersonByID.id: 超过 id 的范围了"));
+}
 
-        mockMvc.perform(get("/api/person/6")
-                .contentType(MediaType.APPLICATION_JSON_UTF8))
-                .andExpect(status().isBadRequest())
-                .andExpect(content().string("getPersonByID.id: 超过 id 的范围了"));
-    }
+@Test
+public void should_check_request_param_value2() throws Exception {
+    mockMvc.perform(put("/api/person")
+                    .param("name", "snailclimbsnailclimb")
+                    .contentType(MediaType.APPLICATION_JSON))
+      .andExpect(status().isBadRequest())
+      .andExpect(content().string("getPersonByName.name: 超过 name 的范围了"));
+}
+```
 
-    @Test
-    public void should_check_param_value2() throws Exception {
+**使用 `Postman` 验证**
 
-        mockMvc.perform(put("/api/person")
-                .param("name","snailclimbsnailclimb")
-                .contentType(MediaType.APPLICATION_JSON_UTF8))
-                .andExpect(status().isBadRequest())
-                .andExpect(content().string("getPersonByName.name: 超过 name 的范围了"));
-    }
-```
+![](https://img-blog.csdnimg.cn/20210421190508416.png)
+
+![](https://img-blog.csdnimg.cn/20210421190810975.png)
 
 ## 验证 Service 中的方法
 
-我们还可以验证任何Spring组件的输入,而不是验证控制器级别的输入,我们可以使用`@Validated`和`@Valid`注释的组合来实现这一需求。
+我们还可以验证任何 Spring Bean 的输入,而不仅仅是 `Controller` 级别的输入。通过使用`@Validated`和`@Valid`注释的组合即可实现这一需求!
+
+一般情况下,我们在项目中也更倾向于使用这种方案。
 
 **一定一定不要忘记在类上加上 `Validated` 注解了,这个参数可以告诉 Spring 去校验方法参数。**
 
@@ -284,9 +280,10 @@ public class PersonController {
 @Validated
 public class PersonService {
 
-    public void validatePerson(@Valid Person person){
+    public void validatePersonRequest(@Valid PersonRequest personRequest) {
         // do something
     }
+
 }
 ```
 
@@ -295,53 +292,60 @@ public class PersonService {
 ```java
 @RunWith(SpringRunner.class)
 @SpringBootTest
-@AutoConfigureMockMvc
 public class PersonServiceTest {
     @Autowired
     private PersonService service;
 
-    @Test(expected = ConstraintViolationException.class)
-    public void should_throw_exception_when_person_is_not_valid() {
-        Person person = new Person();
-        person.setSex("Man22");
-        person.setClassId("82938390");
-        person.setEmail("SnailClimb");
-        service.validatePerson(person);
+    @Test
+    public void should_throw_exception_when_person_request_is_not_valid() {
+        try {
+            PersonRequest personRequest = PersonRequest.builder().sex("Man22")
+                    .classId("82938390").build();
+            service.validatePersonRequest(personRequest);
+        } catch (ConstraintViolationException e) {
+           // 输出异常信息
+            e.getConstraintViolations().forEach(constraintViolation -> System.out.println(constraintViolation.getMessage()));
+        }
     }
-
 }
 ```
 
+输出结果如下:
+
+```
+name 不能为空
+sex 值不在可选范围
+```
+
 ## Validator 编程方式手动进行参数验证
 
 某些场景下可能会需要我们手动校验并获得校验结果。
 
+我们通过 `Validator` 工厂类获得的 `Validator` 示例。另外,如果是在 Spring Bean 中的话,还可以通过 `@Autowired` 直接注入的方式。
+
 ```java
-    @Test
-    public void check_person_manually() {
-
-        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
-        Validator validator = factory.getValidator();
-        Person person = new Person();
-        person.setSex("Man22");
-        person.setClassId("82938390");
-        person.setEmail("SnailClimb");
-        Set<ConstraintViolation<Person>> violations = validator.validate(person);
-        //output:
-        //email 格式不正确
-        //name 不能为空
-        //sex 值不在可选范围
-        for (ConstraintViolation<Person> constraintViolation : violations) {
-            System.out.println(constraintViolation.getMessage());
-        }
-    }
+@Autowired
+Validator validate
 ```
 
-上面我们是通过 `Validator` 工厂类获得的 `Validator` 示例,当然你也可以通过 `@Autowired`  直接注入的方式。但是在非 Spring Component 类中使用这种方式的话,只能通过工厂类来获得 `Validator`。
+具体使用情况如下:
 
 ```java
-@Autowired
-Validator validate
+ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+Validator validator = factory.getValidator()
+PersonRequest personRequest = PersonRequest.builder().sex("Man22")
+  .classId("82938390").build();
+Set<ConstraintViolation<PersonRequest>> violations = validator.validate(personRequest);
+// 输出异常信息
+violations.forEach(constraintViolation -> System.out.println(constraintViolation.getMessage()));
+}
+```
+
+输出结果如下:
+
+```
+sex 值不在可选范围
+name 不能为空
 ```
 
 ## 自定以 Validator(实用)
@@ -350,9 +354,9 @@ Validator validate
 
 ### 案例一:校验特定字段的值是否在可选范围
 
-比如我们现在多了这样一个需求:Person类多了一个 region 字段,region 字段只能是`China`、`China-Taiwan`、`China-HongKong`这三个中的一个。
+比如我们现在多了这样一个需求:`PersonRequest` 类多了一个 `Region` 字段,`Region` 字段只能是`China`、`China-Taiwan`、`China-HongKong`这三个中的一个。
 
-第一步你需要创建一个注解:
+**第一步,你需要创建一个注解 `Region`。**
 
 ```java
 @Target({FIELD})
@@ -369,13 +373,9 @@ public @interface Region {
 }
 ```
 
-第二步你需要实现 `ConstraintValidator`接口,并重写`isValid` 方法:
+**第二步,你需要实现 `ConstraintValidator`接口,并重写`isValid` 方法。**
 
 ```java
-import javax.validation.ConstraintValidator;
-import javax.validation.ConstraintValidatorContext;
-import java.util.HashSet;
-
 public class RegionValidator implements ConstraintValidator<Region, String> {
 
     @Override
@@ -393,10 +393,25 @@ public class RegionValidator implements ConstraintValidator<Region, String> {
 现在你就可以使用这个注解:
 
 ```java
-    @Region
-    private String region;
+@Region
+private String region;
 ```
 
+**通过测试验证**
+
+```java
+PersonRequest personRequest = PersonRequest.builder()
+ 	 .region("Shanghai").build();
+mockMvc.perform(post("/api/person")
+                .contentType(MediaType.APPLICATION_JSON)
+                .content(objectMapper.writeValueAsString(personRequest)))
+  .andExpect(MockMvcResultMatchers.jsonPath("region").value("Region 值不在可选范围内"));
+```
+
+**使用 `Postman` 验证**
+
+![](https://img-blog.csdnimg.cn/20210421203330978.png)
+
 ### 案例二:校验电话号码
 
 校验我们的电话号码是否合法,这个可以通过正则表达式来做,相关的正则表达式都可以在网上搜到,你甚至可以搜索到针对特定运营商电话号码段的正则表达式。
@@ -404,15 +419,6 @@ public class RegionValidator implements ConstraintValidator<Region, String> {
 `PhoneNumber.java`
 
 ```java
-import javax.validation.Constraint;
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
-import static java.lang.annotation.ElementType.FIELD;
-import static java.lang.annotation.ElementType.PARAMETER;
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
 @Documented
 @Constraint(validatedBy = PhoneNumberValidator.class)
 @Target({FIELD, PARAMETER})
@@ -427,10 +433,7 @@ public @interface PhoneNumber {
 `PhoneNumberValidator.java`
 
 ```java
-import javax.validation.ConstraintValidator;
-import javax.validation.ConstraintValidatorContext;
-
-public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber,String> {
+public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber, String> {
 
     @Override
     public boolean isValid(String phoneField, ConstraintValidatorContext context) {
@@ -438,9 +441,15 @@ public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber,Str
             // can be null
             return true;
         }
-        return phoneField.matches("^1(3[0-9]|4[57]|5[0-35-9]|8[0-9]|70)\\d{8}$") && phoneField.length() > 8 && phoneField.length() < 14;
+        //  大陆手机号码11位数,匹配格式:前三位固定格式+后8位任意数
+        // ^ 匹配输入字符串开始的位置
+        // \d 匹配一个或多个数字,其中 \ 要转义,所以是 \\d
+        // $ 匹配输入字符串结尾的位置
+        String regExp = "^[1]((3[0-9])|(4[5-9])|(5[0-3,5-9])|([6][5,6])|(7[0-9])|(8[0-9])|(9[1,8,9]))\\d{8}$";
+        return phoneField.matches(regExp);
     }
 }
+
 ```
 
 搞定,我们现在就可以使用这个注解了。
@@ -451,11 +460,28 @@ public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber,Str
 private String phoneNumber;
 ```
 
+**通过测试验证**
+
+```java
+PersonRequest personRequest = PersonRequest.builder()
+  	.phoneNumber("1816313815").build();
+mockMvc.perform(post("/api/person")
+                .contentType(MediaType.APPLICATION_JSON)
+                .content(objectMapper.writeValueAsString(personRequest)))
+  .andExpect(MockMvcResultMatchers.jsonPath("phoneNumber").value("phoneNumber 格式不正确"));
+```
+
+![](https://img-blog.csdnimg.cn/20210421204116640.png)
+
 ## 使用验证组
 
-某些场景下我们需要使用到验证组,这样说可能不太清楚,说简单点就是对对象操作的不同方法有不同的验证规则,示例如下(这个就我目前经历的项目来说使用的比较少,因为本身这个在代码层面理解起来是比较麻烦的,然后写起来也比较麻烦)。
+验证组我们基本是不会用到的,也不太建议在项目中使用,理解起来比较麻烦,写起来也比较麻烦。简单了解即可!
+
+当我们对对象操作的不同方法有不同的验证规则的时候才会用到验证组。
 
-先创建两个接口:
+我写一个简单的例子,你们就能看明白了!
+
+**1.先创建两个接口,代表不同的验证组**
 
 ```java
 public interface AddPersonGroup {
@@ -463,23 +489,23 @@ public interface AddPersonGroup {
 public interface DeletePersonGroup {
 }
 ```
-我们可以这样去使用验证组
 
-```java
-@NotNull(groups = DeletePersonGroup.class)
-@Null(groups = AddPersonGroup.class)
-private String group;
-```
+**2.使用验证组**
 
 ```java
+@Data
+public class Person {
+    // 当验证组为 DeletePersonGroup 的时候 group 字段不能为空
+    @NotNull(groups = DeletePersonGroup.class)
+    // 当验证组为 AddPersonGroup 的时候 group 字段需要为空
+    @Null(groups = AddPersonGroup.class)
+    private String group;
+}
+
 @Service
 @Validated
 public class PersonService {
 
-    public void validatePerson(@Valid Person person) {
-        // do something
-    }
-
     @Validated(AddPersonGroup.class)
     public void validatePersonGroupForAdd(@Valid Person person) {
         // do something
@@ -497,44 +523,56 @@ public class PersonService {
 
 ```java
   @Test(expected = ConstraintViolationException.class)
-    public void should_check_person_with_groups() {
-        Person person = new Person();
-        person.setSex("Man22");
-        person.setClassId("82938390");
-        person.setEmail("SnailClimb");
-        person.setGroup("group1");
-        service.validatePersonGroupForAdd(person);
-    }
+  public void should_check_person_with_groups() {
+      Person person = new Person();
+      person.setGroup("group1");
+      service.validatePersonGroupForAdd(person);
+  }
 
-    @Test(expected = ConstraintViolationException.class)
-    public void should_check_person_with_groups2() {
-        Person person = new Person();
-        person.setSex("Man22");
-        person.setClassId("82938390");
-        person.setEmail("SnailClimb");
-        service.validatePersonGroupForDelete(person);
-    }
+  @Test(expected = ConstraintViolationException.class)
+  public void should_check_person_with_groups2() {
+      Person person = new Person();
+      service.validatePersonGroupForDelete(person);
+  }
 ```
 
-使用验证组这种方式的时候一定要小心,这是一种反模式,还会造成代码逻辑性变差。
+验证组使用下来的体验就是有点反模式的感觉,让代码的可维护性变差了!尽量不要使用!
+
+## 常用校验注解总结
 
-代码地址:https://github.com/Snailclimb/springboot-guide/tree/master/source-code/advanced/bean-validation-demo
+`JSR303` 定义了 `Bean Validation`(校验)的标准 `validation-api`,并没有提供实现。`Hibernate Validation`是对这个规范/规范的实现 `hibernate-validator`,并且增加了 `@Email`、`@Length`、`@Range` 等注解。`Spring Validation` 底层依赖的就是`Hibernate Validation`。
 
-## `@NotNull` vs `@Column(nullable = false)`(重要)
+**JSR 提供的校验注解**:
 
-在使用 JPA 操作数据的时候会经常碰到 `@Column(nullable = false)`  这种类型的约束,那么它和 `@NotNull` 有何区别呢?搞清楚这个还是很重要的!
+- `@Null` 被注释的元素必须为 null
+- `@NotNull` 被注释的元素必须不为 null
+- `@AssertTrue` 被注释的元素必须为 true
+- `@AssertFalse` 被注释的元素必须为 false
+- `@Min(value)` 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
+- `@Max(value)` 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
+- `@DecimalMin(value)` 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
+- `@DecimalMax(value)` 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
+- `@Size(max=, min=)` 被注释的元素的大小必须在指定的范围内
+- `@Digits (integer, fraction)` 被注释的元素必须是一个数字,其值必须在可接受的范围内
+- `@Past` 被注释的元素必须是一个过去的日期
+- `@Future` 被注释的元素必须是一个将来的日期
+- `@Pattern(regex=,flag=)` 被注释的元素必须符合指定的正则表达式
 
-- `@NotNull`是 JSR 303 Bean验证批注,它与数据库约束本身无关。
-- `@Column(nullable = false)` : 是JPA声明列为非空的方法。
+**Hibernate Validator 提供的校验注解**:
 
-总结来说就是即前者用于验证,而后者则用于指示数据库创建表的时候对表的约束。
+- `@NotBlank(message =)` 验证字符串非 null,且长度必须大于 0
+- `@Email` 被注释的元素必须是电子邮箱地址
+- `@Length(min=,max=)` 被注释的字符串的大小必须在指定的范围内
+- `@NotEmpty` 被注释的字符串的必须非空
+- `@Range(min=,max=,message=)` 被注释的元素必须在合适的范围内
 
-## TODO
+## 拓展
 
-- [ ] 原理分析
+经常有小伙伴问到:“`@NotNull` 和 `@Column(nullable = false)` 两者有什么区别?”
 
-## 参考
+我这里简单回答一下:
 
-- https://reflectoring.io/bean-validation-with-spring-boot/
-- https://www.cnkirito.moe/spring-validation//
+- `@NotNull`是 JSR 303 Bean 验证批注,它与数据库约束本身无关。
+- `@Column(nullable = false)` : 是 JPA 声明列为非空的方法。
 
+总结来说就是即前者用于验证,而后者则用于指示数据库创建表的时候对表的约束。
\ No newline at end of file
diff --git a/source-code/advanced/bean-validation-demo/pom.xml b/source-code/advanced/bean-validation-demo/pom.xml
index d5779cb..c5a9e2f 100644
--- a/source-code/advanced/bean-validation-demo/pom.xml
+++ b/source-code/advanced/bean-validation-demo/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-parent</artifactId>
-        <version>2.1.8.RELEASE</version>
+        <version>2.4.5</version>
         <relativePath/> <!-- lookup parent from repository -->
     </parent>
     <groupId>com.example</groupId>
@@ -19,22 +19,25 @@
     </properties>
 
     <dependencies>
-
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-devtools</artifactId>
-            <scope>runtime</scope>
-            <optional>true</optional>
+            <artifactId>spring-boot-starter-validation</artifactId>
         </dependency>
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
             <optional>true</optional>
         </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.13.1</version>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>
diff --git a/source-code/advanced/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/controller/PersonController.java b/source-code/advanced/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/controller/PersonController.java
index 1b22d08..c0482b5 100644
--- a/source-code/advanced/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/controller/PersonController.java
+++ b/source-code/advanced/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/controller/PersonController.java
@@ -1,12 +1,9 @@
 package com.example.beanvalidationdemo.controller;
 
-import com.example.beanvalidationdemo.entity.Person;
+import com.example.beanvalidationdemo.entity.PersonRequest;
 import org.springframework.http.ResponseEntity;
-import org.springframework.validation.BindingResult;
-import org.springframework.validation.Errors;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.Mapping;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.PutMapping;
@@ -20,21 +17,21 @@
 import javax.validation.constraints.Size;
 
 @RestController
-@RequestMapping("/api")
+@RequestMapping("/api/persons")
 @Validated
 public class PersonController {
 
-    @PostMapping("/person")
-    public ResponseEntity<Person> getPerson(@RequestBody @Valid Person person) {
-        return ResponseEntity.ok().body(person);
+    @PostMapping
+    public ResponseEntity<PersonRequest> save(@RequestBody @Valid PersonRequest personRequest) {
+        return ResponseEntity.ok().body(personRequest);
     }
 
-    @GetMapping("/person/{id}")
+    @GetMapping("/{id}")
     public ResponseEntity<Integer> getPersonByID(@Valid @PathVariable("id") @Max(value = 5, message = "超过 id 的范围了") Integer id) {
         return ResponseEntity.ok().body(id);
     }
 
-    @PutMapping("/person")
+    @PutMapping
     public ResponseEntity<String> getPersonByName(@Valid @RequestParam("name") @Size(max = 6, message = "超过 name 的范围了") String name) {
         return ResponseEntity.ok().body(name);
     }
diff --git a/source-code/advanced/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/entity/Person.java b/source-code/advanced/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/entity/Person.java
index d095320..b1b6dec 100644
--- a/source-code/advanced/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/entity/Person.java
+++ b/source-code/advanced/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/entity/Person.java
@@ -1,48 +1,21 @@
 package com.example.beanvalidationdemo.entity;
 
-import com.example.beanvalidationdemo.constants.Constants;
 import com.example.beanvalidationdemo.service.AddPersonGroup;
 import com.example.beanvalidationdemo.service.DeletePersonGroup;
-import com.example.beanvalidationdemo.validation.PhoneNumber;
-import com.example.beanvalidationdemo.validation.Region;
 import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
-import javax.validation.constraints.Email;
 import javax.validation.constraints.NotNull;
 import javax.validation.constraints.Null;
-import javax.validation.constraints.Pattern;
-import javax.validation.constraints.Size;
 
 @Data
-@AllArgsConstructor
-@NoArgsConstructor
 public class Person {
 
-    @NotNull(message = "classId 不能为空")
-    private String classId;
-
-    @Size(max = 33)
-    @NotNull(message = "name 不能为空")
-    private String name;
-
-    @Pattern(regexp = Constants.sexs, message = "sex 值不在可选范围")
-    @NotNull(message = "sex 不能为空")
-    private String sex;
-
-    @Email(message = "email 格式不正确")
-    @NotNull(message = "email 不能为空")
-    private String email;
-
-    @PhoneNumber(message = "phoneNumber 格式不正确")
-    @NotNull(message = "phoneNumber 不能为空")
-    private String phoneNumber;
-
-    @Region
-    private String region;
-
+    // 当验证组为 DeletePersonGroup 的时候 group 字段不能为空
     @NotNull(groups = DeletePersonGroup.class)
+    // 当验证组为 AddPersonGroup 的时候 group 字段需要为空
     @Null(groups = AddPersonGroup.class)
     private String group;
 }
diff --git a/source-code/advanced/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/entity/PersonRequest.java b/source-code/advanced/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/entity/PersonRequest.java
new file mode 100644
index 0000000..9b2c789
--- /dev/null
+++ b/source-code/advanced/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/entity/PersonRequest.java
@@ -0,0 +1,44 @@
+package com.example.beanvalidationdemo.entity;
+
+import com.example.beanvalidationdemo.validation.PhoneNumber;
+import com.example.beanvalidationdemo.validation.Region;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+
+/**
+ * 微信搜 JavaGuide 回复"面试突击"即可免费领取个人原创的 Java 面试手册
+ *
+ * @author Guide哥
+ * @date 2021/04/21 20:48
+ **/
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class PersonRequest {
+
+    @NotNull(message = "classId 不能为空")
+    private String classId;
+
+    @Size(max = 33)
+    @NotNull(message = "name 不能为空")
+    private String name;
+
+    @Pattern(regexp = "(^Man$|^Woman$|^UGM$)", message = "sex 值不在可选范围")
+    @NotNull(message = "sex 不能为空")
+    private String sex;
+
+    @Region
+    private String region;
+
+    @PhoneNumber(message = "phoneNumber 格式不正确")
+    @NotNull(message = "phoneNumber 不能为空")
+    private String phoneNumber;
+
+}
diff --git a/source-code/advanced/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/service/PersonService.java b/source-code/advanced/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/service/PersonService.java
index 99856bf..08e8e14 100644
--- a/source-code/advanced/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/service/PersonService.java
+++ b/source-code/advanced/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/service/PersonService.java
@@ -1,6 +1,7 @@
 package com.example.beanvalidationdemo.service;
 
 import com.example.beanvalidationdemo.entity.Person;
+import com.example.beanvalidationdemo.entity.PersonRequest;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
@@ -10,7 +11,7 @@
 @Validated
 public class PersonService {
 
-    public void validatePerson(@Valid Person person) {
+    public void validatePersonRequest(@Valid PersonRequest personRequest) {
         // do something
     }
 
diff --git a/source-code/advanced/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/validation/PhoneNumber.java b/source-code/advanced/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/validation/PhoneNumber.java
index 8226e99..afd5373 100644
--- a/source-code/advanced/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/validation/PhoneNumber.java
+++ b/source-code/advanced/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/validation/PhoneNumber.java
@@ -15,6 +15,8 @@
 @Retention(RUNTIME)
 public @interface PhoneNumber {
     String message() default "Invalid phone number";
+
     Class[] groups() default {};
+
     Class[] payload() default {};
 }
diff --git a/source-code/advanced/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/validation/PhoneNumberValidator.java b/source-code/advanced/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/validation/PhoneNumberValidator.java
index 4fc63c9..85acdcd 100644
--- a/source-code/advanced/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/validation/PhoneNumberValidator.java
+++ b/source-code/advanced/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/validation/PhoneNumberValidator.java
@@ -3,7 +3,7 @@
 import javax.validation.ConstraintValidator;
 import javax.validation.ConstraintValidatorContext;
 
-public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber,String> {
+public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber, String> {
 
     @Override
     public boolean isValid(String phoneField, ConstraintValidatorContext context) {
@@ -11,6 +11,11 @@ public boolean isValid(String phoneField, ConstraintValidatorContext context) {
             // can be null
             return true;
         }
-        return phoneField.matches("^1(3[0-9]|4[57]|5[0-35-9]|8[0-9]|70)\\d{8}$") && phoneField.length() > 8 && phoneField.length() < 14;
+        //  大陆手机号码11位数,匹配格式:前三位固定格式+后8位任意数
+        // ^ 匹配输入字符串开始的位置
+        // \d 匹配一个或多个数字,其中 \ 要转义,所以是 \\d
+        // $ 匹配输入字符串结尾的位置
+        String regExp = "^[1]((3[0-9])|(4[5-9])|(5[0-3,5-9])|([6][5,6])|(7[0-9])|(8[0-9])|(9[1,8,9]))\\d{8}$";
+        return phoneField.matches(regExp);
     }
 }
diff --git a/source-code/advanced/bean-validation-demo/src/test/java/com/example/beanvalidationdemo/HelloWorldControllerTest.java b/source-code/advanced/bean-validation-demo/src/test/java/com/example/beanvalidationdemo/HelloWorldControllerTest.java
deleted file mode 100644
index ea998ca..0000000
--- a/source-code/advanced/bean-validation-demo/src/test/java/com/example/beanvalidationdemo/HelloWorldControllerTest.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.example.beanvalidationdemo;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.junit4.SpringRunner;
-import org.springframework.test.web.servlet.MockMvc;
-
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
-
-/**
- * 验证基本的项目基本环境是否有问题
- */
-@RunWith(SpringRunner.class)
-@SpringBootTest
-@AutoConfigureMockMvc
-public class HelloWorldControllerTest {
-    @Autowired
-    private MockMvc mockMvc;
-
-    @Test
-    public void should_get_hello() throws Exception {
-        mockMvc.perform(get("/api/hello")).andExpect(content().string("Hello"));
-    }
-}
diff --git a/source-code/advanced/bean-validation-demo/src/test/java/com/example/beanvalidationdemo/PersonControllerTest.java b/source-code/advanced/bean-validation-demo/src/test/java/com/example/beanvalidationdemo/PersonControllerTest.java
index 0ff4eb4..f020f05 100644
--- a/source-code/advanced/bean-validation-demo/src/test/java/com/example/beanvalidationdemo/PersonControllerTest.java
+++ b/source-code/advanced/bean-validation-demo/src/test/java/com/example/beanvalidationdemo/PersonControllerTest.java
@@ -1,15 +1,13 @@
 package com.example.beanvalidationdemo;
 
 
-import com.example.beanvalidationdemo.entity.Person;
+import com.example.beanvalidationdemo.entity.PersonRequest;
 import com.fasterxml.jackson.databind.ObjectMapper;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.http.MediaType;
-import org.springframework.test.context.junit4.SpringRunner;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
 
@@ -25,7 +23,6 @@
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 
-@RunWith(SpringRunner.class)
 @SpringBootTest
 @AutoConfigureMockMvc
 public class PersonControllerTest {
@@ -35,85 +32,52 @@ public class PersonControllerTest {
     @Autowired
     private ObjectMapper objectMapper;
 
-    @Test
-    public void should_get_person() throws Exception {
-        Person person = new Person();
-        person.setName("SnailClimb");
-        person.setSex("Man");
-        person.setClassId("82938390");
-        person.setEmail("Snailclimb@qq.com");
-        person.setRegion("China");
-        person.setPhoneNumber("13615833391");
-
-
-        mockMvc.perform(post("/api/person")
-                .contentType(MediaType.APPLICATION_JSON_UTF8)
-                .content(objectMapper.writeValueAsString(person)))
-                .andExpect(MockMvcResultMatchers.jsonPath("name").value("SnailClimb"))
-                .andExpect(MockMvcResultMatchers.jsonPath("classId").value("82938390"))
-                .andExpect(MockMvcResultMatchers.jsonPath("sex").value("Man"))
-                .andExpect(MockMvcResultMatchers.jsonPath("email").value("Snailclimb@qq.com"));
-        ;
-    }
 
+    /**
+     * 验证出现参数不合法的情况抛出异常并且可以正确被捕获
+     */
     @Test
     public void should_check_person_value() throws Exception {
-        Person person = new Person();
-        person.setSex("Man22");
-        person.setClassId("82938390");
-        person.setEmail("SnailClimb");
-        person.setRegion("BeiGuo");
-        person.setPhoneNumber("1361583339");
-
+        PersonRequest personRequest = PersonRequest.builder().sex("Man22")
+                .classId("82938390")
+                .region("Shanghai")
+                .phoneNumber("1816313815").build();
         mockMvc.perform(post("/api/person")
-                .contentType(MediaType.APPLICATION_JSON_UTF8)
-                .content(objectMapper.writeValueAsString(person)))
+                .contentType(MediaType.APPLICATION_JSON)
+                .content(objectMapper.writeValueAsString(personRequest)))
                 .andExpect(MockMvcResultMatchers.jsonPath("sex").value("sex 值不在可选范围"))
                 .andExpect(MockMvcResultMatchers.jsonPath("name").value("name 不能为空"))
-                .andExpect(MockMvcResultMatchers.jsonPath("email").value("email 格式不正确"))
                 .andExpect(MockMvcResultMatchers.jsonPath("region").value("Region 值不在可选范围内"))
                 .andExpect(MockMvcResultMatchers.jsonPath("phoneNumber").value("phoneNumber 格式不正确"));
-
     }
 
     @Test
-    public void should_check_param_value() throws Exception {
-
+    public void should_check_path_variable() throws Exception {
         mockMvc.perform(get("/api/person/6")
-                .contentType(MediaType.APPLICATION_JSON_UTF8))
+                .contentType(MediaType.APPLICATION_JSON))
                 .andExpect(status().isBadRequest())
                 .andExpect(content().string("getPersonByID.id: 超过 id 的范围了"));
     }
 
     @Test
-    public void should_check_param_value2() throws Exception {
-
+    public void should_check_request_param_value2() throws Exception {
         mockMvc.perform(put("/api/person")
                 .param("name", "snailclimbsnailclimb")
-                .contentType(MediaType.APPLICATION_JSON_UTF8))
+                .contentType(MediaType.APPLICATION_JSON))
                 .andExpect(status().isBadRequest())
                 .andExpect(content().string("getPersonByName.name: 超过 name 的范围了"));
     }
 
     /**
-     * 手动校验对象,很多场景下需要使用这种方式
+     * 手动校验对象
      */
     @Test
     public void check_person_manually() {
-
         ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
         Validator validator = factory.getValidator();
-        Person person = new Person();
-        person.setSex("Man22");
-        person.setClassId("82938390");
-        person.setEmail("SnailClimb");
-        Set<ConstraintViolation<Person>> violations = validator.validate(person);
-        //output:
-        //email 格式不正确
-        //name 不能为空
-        //sex 值不在可选范围
-        for (ConstraintViolation<Person> constraintViolation : violations) {
-            System.out.println(constraintViolation.getMessage());
-        }
+        PersonRequest personRequest = PersonRequest.builder().sex("Man22")
+                .classId("82938390").build();
+        Set<ConstraintViolation<PersonRequest>> violations = validator.validate(personRequest);
+        violations.forEach(constraintViolation -> System.out.println(constraintViolation.getMessage()));
     }
 }
diff --git a/source-code/advanced/bean-validation-demo/src/test/java/com/example/beanvalidationdemo/PersonServiceTest.java b/source-code/advanced/bean-validation-demo/src/test/java/com/example/beanvalidationdemo/PersonServiceTest.java
index 377d7e1..88b5da3 100644
--- a/source-code/advanced/bean-validation-demo/src/test/java/com/example/beanvalidationdemo/PersonServiceTest.java
+++ b/source-code/advanced/bean-validation-demo/src/test/java/com/example/beanvalidationdemo/PersonServiceTest.java
@@ -1,6 +1,7 @@
 package com.example.beanvalidationdemo;
 
 import com.example.beanvalidationdemo.entity.Person;
+import com.example.beanvalidationdemo.entity.PersonRequest;
 import com.example.beanvalidationdemo.service.PersonService;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -16,21 +17,20 @@ public class PersonServiceTest {
     @Autowired
     private PersonService service;
 
-    @Test(expected = ConstraintViolationException.class)
-    public void should_throw_exception_when_person_is_not_valid() {
-        Person person = new Person();
-        person.setSex("Man22");
-        person.setClassId("82938390");
-        person.setEmail("SnailClimb");
-        service.validatePerson(person);
+    @Test
+    public void should_throw_exception_when_person_request_is_not_valid() {
+        try {
+            PersonRequest personRequest = PersonRequest.builder().sex("Man22")
+                    .classId("82938390").build();
+            service.validatePersonRequest(personRequest);
+        } catch (ConstraintViolationException e) {
+            e.getConstraintViolations().forEach(constraintViolation -> System.out.println(constraintViolation.getMessage()));
+        }
     }
 
     @Test(expected = ConstraintViolationException.class)
     public void should_check_person_with_groups() {
         Person person = new Person();
-        person.setSex("Man22");
-        person.setClassId("82938390");
-        person.setEmail("SnailClimb");
         person.setGroup("group1");
         service.validatePersonGroupForAdd(person);
     }
@@ -38,9 +38,6 @@ public void should_check_person_with_groups() {
     @Test(expected = ConstraintViolationException.class)
     public void should_check_person_with_groups2() {
         Person person = new Person();
-        person.setSex("Man22");
-        person.setClassId("82938390");
-        person.setEmail("SnailClimb");
         service.validatePersonGroupForDelete(person);
     }
 

From 25f893f6851fa8f07db5b24af5d6c4656ea8d5a2 Mon Sep 17 00:00:00 2001
From: guide <koushuangbwcx@163.com>
Date: Wed, 21 Apr 2021 21:47:15 +0800
Subject: [PATCH 05/22] =?UTF-8?q?[feat]SpringBoot=E5=8F=82=E6=95=B0?=
 =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E4=BB=A3=E7=A0=81=E6=A0=BC=E5=BC=8F=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../java/com/example/beanvalidationdemo/entity/Person.java     | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/source-code/advanced/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/entity/Person.java b/source-code/advanced/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/entity/Person.java
index b1b6dec..80bb5e8 100644
--- a/source-code/advanced/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/entity/Person.java
+++ b/source-code/advanced/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/entity/Person.java
@@ -2,10 +2,7 @@
 
 import com.example.beanvalidationdemo.service.AddPersonGroup;
 import com.example.beanvalidationdemo.service.DeletePersonGroup;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
 import lombok.Data;
-import lombok.NoArgsConstructor;
 
 import javax.validation.constraints.NotNull;
 import javax.validation.constraints.Null;

From 47e3be859c7a76a07a079f973f6fef528b00da35 Mon Sep 17 00:00:00 2001
From: guide <koushuangbwcx@163.com>
Date: Wed, 21 Apr 2021 21:49:35 +0800
Subject: [PATCH 06/22] =?UTF-8?q?[feat]=E4=BB=A3=E7=A0=81=E4=BD=8D?=
 =?UTF-8?q?=E7=BD=AE=E7=A7=BB=E5=8A=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .github/.DS_Store                             | Bin 6148 -> 0 bytes
 .DS_Store => source-code/.DS_Store            | Bin 6148 -> 6148 bytes
 .../.gitignore                                |   0
 .../.mvn/wrapper/MavenWrapperDownloader.java  | 114 +++++++
 .../.mvn/wrapper/maven-wrapper.jar            | Bin
 .../.mvn/wrapper/maven-wrapper.properties     |   0
 source-code/bean-validation-demo/mvnw         | 298 ++++++++++++++++++
 .../mvnw.cmd                                  |   0
 source-code/bean-validation-demo/pom.xml      |  57 ++++
 .../BeanValidationDemoApplication.java        |  13 +
 .../constants/Constants.java                  |   5 +
 .../controller/HelloWorldController.java      |  19 ++
 .../controller/PersonController.java          |  38 +++
 .../beanvalidationdemo/entity/Person.java     |  18 ++
 .../entity/PersonRequest.java                 |  44 +++
 .../exception/GlobalExceptionHandler.java     |  33 ++
 .../service/AddPersonGroup.java               |   4 +
 .../service/DeletePersonGroup.java            |   4 +
 .../service/PersonService.java                |  28 ++
 .../validation/PhoneNumber.java               |  22 ++
 .../validation/PhoneNumberValidator.java      |  21 ++
 .../beanvalidationdemo/validation/Region.java |  26 ++
 .../validation/RegionValidator.java           |  17 +
 .../src/main/resources/application.properties |   0
 .../PersonControllerTest.java                 |  83 +++++
 .../beanvalidationdemo/PersonServiceTest.java |  44 +++
 source-code/hello-world/.gitignore            |  31 ++
 .../.mvn/wrapper/MavenWrapperDownloader.java  |   0
 .../.mvn/wrapper/maven-wrapper.jar            | Bin 0 -> 48337 bytes
 .../.mvn/wrapper/maven-wrapper.properties     |   1 +
 source-code/{start => }/hello-world/mvnw      |   0
 source-code/hello-world/mvnw.cmd              | 161 ++++++++++
 source-code/{start => }/hello-world/pom.xml   |   0
 .../helloworld/HelloWorldApplication.java     |   0
 .../helloworld/controller/UserController.java |   0
 .../com/example/helloworld/dto/UserDto.java   |   0
 .../com/example/helloworld/entity/User.java   |   0
 .../src/main/resources/application.properties |   0
 .../com/example/dto2entity/BeanUtilsTest.java |   0
 .../HelloWorldApplicationTests.java           |   0
 40 files changed, 1081 insertions(+)
 delete mode 100644 .github/.DS_Store
 rename .DS_Store => source-code/.DS_Store (77%)
 rename source-code/{start/hello-world => bean-validation-demo}/.gitignore (100%)
 create mode 100644 source-code/bean-validation-demo/.mvn/wrapper/MavenWrapperDownloader.java
 rename source-code/{start/hello-world => bean-validation-demo}/.mvn/wrapper/maven-wrapper.jar (100%)
 rename source-code/{start/hello-world => bean-validation-demo}/.mvn/wrapper/maven-wrapper.properties (100%)
 create mode 100755 source-code/bean-validation-demo/mvnw
 rename source-code/{start/hello-world => bean-validation-demo}/mvnw.cmd (100%)
 create mode 100644 source-code/bean-validation-demo/pom.xml
 create mode 100644 source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/BeanValidationDemoApplication.java
 create mode 100644 source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/constants/Constants.java
 create mode 100644 source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/controller/HelloWorldController.java
 create mode 100644 source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/controller/PersonController.java
 create mode 100644 source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/entity/Person.java
 create mode 100644 source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/entity/PersonRequest.java
 create mode 100644 source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/exception/GlobalExceptionHandler.java
 create mode 100644 source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/service/AddPersonGroup.java
 create mode 100644 source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/service/DeletePersonGroup.java
 create mode 100644 source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/service/PersonService.java
 create mode 100644 source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/validation/PhoneNumber.java
 create mode 100644 source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/validation/PhoneNumberValidator.java
 create mode 100644 source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/validation/Region.java
 create mode 100644 source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/validation/RegionValidator.java
 create mode 100644 source-code/bean-validation-demo/src/main/resources/application.properties
 create mode 100644 source-code/bean-validation-demo/src/test/java/com/example/beanvalidationdemo/PersonControllerTest.java
 create mode 100644 source-code/bean-validation-demo/src/test/java/com/example/beanvalidationdemo/PersonServiceTest.java
 create mode 100644 source-code/hello-world/.gitignore
 rename source-code/{start => }/hello-world/.mvn/wrapper/MavenWrapperDownloader.java (100%)
 create mode 100644 source-code/hello-world/.mvn/wrapper/maven-wrapper.jar
 create mode 100644 source-code/hello-world/.mvn/wrapper/maven-wrapper.properties
 rename source-code/{start => }/hello-world/mvnw (100%)
 create mode 100644 source-code/hello-world/mvnw.cmd
 rename source-code/{start => }/hello-world/pom.xml (100%)
 rename source-code/{start => }/hello-world/src/main/java/com/example/helloworld/HelloWorldApplication.java (100%)
 rename source-code/{start => }/hello-world/src/main/java/com/example/helloworld/controller/UserController.java (100%)
 rename source-code/{start => }/hello-world/src/main/java/com/example/helloworld/dto/UserDto.java (100%)
 rename source-code/{start => }/hello-world/src/main/java/com/example/helloworld/entity/User.java (100%)
 rename source-code/{start => }/hello-world/src/main/resources/application.properties (100%)
 rename source-code/{start => }/hello-world/src/test/java/com/example/dto2entity/BeanUtilsTest.java (100%)
 rename source-code/{start => }/hello-world/src/test/java/com/example/helloworld/HelloWorldApplicationTests.java (100%)

diff --git a/.github/.DS_Store b/.github/.DS_Store
deleted file mode 100644
index c29fa0aac579c0be056cac352053dbf583864b9c..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 6148
zcmeHK&5qMB5FWQ>*Xasv4@l+c3%4FpK$JbK6lKeS+e$%j0MsUGmqvtm)ubD--J(1O
zywSb^ufvS(R#Yt)4hU5<()b(Ck1c<W>=*z<xR~?-IshP12^$_ZdxYYoJJN6-LSe?Z
zf($fNP$bn(Haq@D259d*+%XMv_zb`8UvZseI>G3Da-F3`>H9xLquF|JbS!0CwjZ`X
zMl)SSWn3<bFrL4s-ep|Q5@)~BI@{QB@;04Tqt2rXotJT%Pv@pMolG(1!@D$}=ql8U
zd{UTkA{&s>lisM)T`tdF_SCcI{;H>zgMr^uFZ`#gl_$GT`sc65U%uWh?^fTwTY3e4
ziAGKxuHgq7dsccA&GSs>pD?<vTvsqM1Iz$3aHtHp^G==|>H>LP%m6d+*9_44pil_|
zhqXm>bYMfjj}&halAuj*2}0#Ca9CT!2#T<!h_+O?Cx)=)=vOW;a9CTk<si(=IF6fH
zxEG2rv!h>`bP$0>ZkYjQV4H!a?RMz?fA#10|8^1gm;q+spcoLXVKfYJN$zglSsdN9
u3U!Q1LV2~tO$i$2D#l#8iXWpILBApcF>qL0L=Ou82pAf;VFvyw1HS;AXj9|>

diff --git a/.DS_Store b/source-code/.DS_Store
similarity index 77%
rename from .DS_Store
rename to source-code/.DS_Store
index e848d82458ef7b90e87b4b72e9c66496134514de..7ad59510222be3cc1d27a91e171274be0e7e536c 100644
GIT binary patch
delta 353
zcmZoMXfc=|#>B!ku~2NHo}wrV0|Nsi1A_nqLmopiLn=c`Qh9N~W<loV%!(i>4u(XA
z6oxV&i6otrlb-~XJ2_d4si9t?y4uvzOh>`kz^qnBq1wX2LPx>O(zLdglS5q9(AF~{
zx3a3brnYV-PzMk&0xbgrekcv2W&s%(wv`1J<>ln(r32+a`dAr~fG#O!$Ydx6ITPd#
ykR!nkNv5VlkSs!W0x(>@GHzz);O77aAdvH&c{0C<BL~oFOh9KaY>p6F!wdjB?M6HR

delta 182
zcmZoMXfc=|#>B)qu~2NHo}wrd0|Nsi1A_nqLk2@BLsC+CaY0hf#=_-{lMO^zq}dtt
z7}6Os8A_04^OJyjPE6KkZO{;}uC}z$Q7|?!sMS%ZHa9oWQ7|(ws;%YZ5LY#{^-RdE
ztg5c5t(!6V2CKaAOrSP212?m=F*0pzU}E0P&cV+Cbk$}-j_=Hq`9&N#fQms5W7!-b
HvW6J|&kZW)

diff --git a/source-code/start/hello-world/.gitignore b/source-code/bean-validation-demo/.gitignore
similarity index 100%
rename from source-code/start/hello-world/.gitignore
rename to source-code/bean-validation-demo/.gitignore
diff --git a/source-code/bean-validation-demo/.mvn/wrapper/MavenWrapperDownloader.java b/source-code/bean-validation-demo/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 0000000..7f91a56
--- /dev/null
+++ b/source-code/bean-validation-demo/.mvn/wrapper/MavenWrapperDownloader.java
@@ -0,0 +1,114 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.util.Properties;
+
+public class MavenWrapperDownloader {
+
+    /**
+     * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
+     */
+    private static final String DEFAULT_DOWNLOAD_URL =
+            "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar";
+
+    /**
+     * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
+     * use instead of the default one.
+     */
+    private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
+            ".mvn/wrapper/maven-wrapper.properties";
+
+    /**
+     * Path where the maven-wrapper.jar will be saved to.
+     */
+    private static final String MAVEN_WRAPPER_JAR_PATH =
+            ".mvn/wrapper/maven-wrapper.jar";
+
+    /**
+     * Name of the property which should be used to override the default download url for the wrapper.
+     */
+    private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
+
+    public static void main(String args[]) {
+        System.out.println("- Downloader started");
+        File baseDirectory = new File(args[0]);
+        System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
+
+        // If the maven-wrapper.properties exists, read it and check if it contains a custom
+        // wrapperUrl parameter.
+        File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
+        String url = DEFAULT_DOWNLOAD_URL;
+        if (mavenWrapperPropertyFile.exists()) {
+            FileInputStream mavenWrapperPropertyFileInputStream = null;
+            try {
+                mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
+                Properties mavenWrapperProperties = new Properties();
+                mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
+                url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
+            } catch (IOException e) {
+                System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
+            } finally {
+                try {
+                    if (mavenWrapperPropertyFileInputStream != null) {
+                        mavenWrapperPropertyFileInputStream.close();
+                    }
+                } catch (IOException e) {
+                    // Ignore ...
+                }
+            }
+        }
+        System.out.println("- Downloading from: : " + url);
+
+        File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
+        if (!outputFile.getParentFile().exists()) {
+            if (!outputFile.getParentFile().mkdirs()) {
+                System.out.println(
+                        "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'");
+            }
+        }
+        System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
+        try {
+            downloadFileFromURL(url, outputFile);
+            System.out.println("Done");
+            System.exit(0);
+        } catch (Throwable e) {
+            System.out.println("- Error downloading");
+            e.printStackTrace();
+            System.exit(1);
+        }
+    }
+
+    private static void downloadFileFromURL(String urlString, File destination) throws Exception {
+        URL website = new URL(urlString);
+        ReadableByteChannel rbc;
+        rbc = Channels.newChannel(website.openStream());
+        FileOutputStream fos = new FileOutputStream(destination);
+        fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+        fos.close();
+        rbc.close();
+    }
+
+}
diff --git a/source-code/start/hello-world/.mvn/wrapper/maven-wrapper.jar b/source-code/bean-validation-demo/.mvn/wrapper/maven-wrapper.jar
similarity index 100%
rename from source-code/start/hello-world/.mvn/wrapper/maven-wrapper.jar
rename to source-code/bean-validation-demo/.mvn/wrapper/maven-wrapper.jar
diff --git a/source-code/start/hello-world/.mvn/wrapper/maven-wrapper.properties b/source-code/bean-validation-demo/.mvn/wrapper/maven-wrapper.properties
similarity index 100%
rename from source-code/start/hello-world/.mvn/wrapper/maven-wrapper.properties
rename to source-code/bean-validation-demo/.mvn/wrapper/maven-wrapper.properties
diff --git a/source-code/bean-validation-demo/mvnw b/source-code/bean-validation-demo/mvnw
new file mode 100755
index 0000000..c188654
--- /dev/null
+++ b/source-code/bean-validation-demo/mvnw
@@ -0,0 +1,298 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#    https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven2 Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+#   JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+#   M2_HOME - location of maven2's installed home dir
+#   MAVEN_OPTS - parameters passed to the Java VM when running Maven
+#     e.g. to debug Maven itself, use
+#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ]; then
+
+  if [ -f /etc/mavenrc ]; then
+    . /etc/mavenrc
+  fi
+
+  if [ -f "$HOME/.mavenrc" ]; then
+    . "$HOME/.mavenrc"
+  fi
+
+fi
+
+# OS specific support.  $var _must_ be set to either true or false.
+cygwin=false
+darwin=false
+mingw=false
+case "$(uname)" in
+CYGWIN*) cygwin=true ;;
+MINGW*) mingw=true ;;
+Darwin*)
+  darwin=true
+  # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+  # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+  if [ -z "$JAVA_HOME" ]; then
+    if [ -x "/usr/libexec/java_home" ]; then
+      export JAVA_HOME="$(/usr/libexec/java_home)"
+    else
+      export JAVA_HOME="/Library/Java/Home"
+    fi
+  fi
+  ;;
+esac
+
+if [ -z "$JAVA_HOME" ]; then
+  if [ -r /etc/gentoo-release ]; then
+    JAVA_HOME=$(java-config --jre-home)
+  fi
+fi
+
+if [ -z "$M2_HOME" ]; then
+  ## resolve links - $0 may be a link to maven's home
+  PRG="$0"
+
+  # need this for relative symlinks
+  while [ -h "$PRG" ]; do
+    ls=$(ls -ld "$PRG")
+    link=$(expr "$ls" : '.*-> \(.*\)$')
+    if expr "$link" : '/.*' >/dev/null; then
+      PRG="$link"
+    else
+      PRG="$(dirname "$PRG")/$link"
+    fi
+  done
+
+  saveddir=$(pwd)
+
+  M2_HOME=$(dirname "$PRG")/..
+
+  # make it fully qualified
+  M2_HOME=$(cd "$M2_HOME" && pwd)
+
+  cd "$saveddir"
+  # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME=$(cygpath --unix "$M2_HOME")
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
+  [ -n "$CLASSPATH" ] &&
+    CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME="$( (
+      cd "$M2_HOME"
+      pwd
+    ))"
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME="$( (
+      cd "$JAVA_HOME"
+      pwd
+    ))"
+  # TODO classpath?
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+  javaExecutable="$(which javac)"
+  if [ -n "$javaExecutable" ] && ! [ "$(expr \"$javaExecutable\" : '\([^ ]*\)')" = "no" ]; then
+    # readlink(1) is not available as standard on Solaris 10.
+    readLink=$(which readlink)
+    if [ ! $(expr "$readLink" : '\([^ ]*\)') = "no" ]; then
+      if $darwin; then
+        javaHome="$(dirname \"$javaExecutable\")"
+        javaExecutable="$(cd \"$javaHome\" && pwd -P)/javac"
+      else
+        javaExecutable="$(readlink -f \"$javaExecutable\")"
+      fi
+      javaHome="$(dirname \"$javaExecutable\")"
+      javaHome=$(expr "$javaHome" : '\(.*\)/bin')
+      JAVA_HOME="$javaHome"
+      export JAVA_HOME
+    fi
+  fi
+fi
+
+if [ -z "$JAVACMD" ]; then
+  if [ -n "$JAVA_HOME" ]; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ]; then
+      # IBM's JDK on AIX uses strange locations for the executables
+      JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+      JAVACMD="$JAVA_HOME/bin/java"
+    fi
+  else
+    JAVACMD="$(which java)"
+  fi
+fi
+
+if [ ! -x "$JAVACMD" ]; then
+  echo "Error: JAVA_HOME is not defined correctly." >&2
+  echo "  We cannot execute $JAVACMD" >&2
+  exit 1
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+  echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+  if [ -z "$1" ]; then
+    echo "Path not specified to find_maven_basedir"
+    return 1
+  fi
+
+  basedir="$1"
+  wdir="$1"
+  while [ "$wdir" != '/' ]; do
+    if [ -d "$wdir"/.mvn ]; then
+      basedir=$wdir
+      break
+    fi
+    # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+    if [ -d "${wdir}" ]; then
+      wdir=$(
+        cd "$wdir/.."
+        pwd
+      )
+    fi
+    # end of workaround
+  done
+  echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+  if [ -f "$1" ]; then
+    echo "$(tr -s '\n' ' ' <"$1")"
+  fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(pwd)")
+if [ -z "$BASE_DIR" ]; then
+  exit 1
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+  if [ "$MVNW_VERBOSE" = true ]; then
+    echo "Found .mvn/wrapper/maven-wrapper.jar"
+  fi
+else
+  if [ "$MVNW_VERBOSE" = true ]; then
+    echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+  fi
+  jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
+  while IFS="=" read key value; do
+    case "$key" in wrapperUrl)
+      jarUrl="$value"
+      break
+      ;;
+    esac
+  done <"$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+  if [ "$MVNW_VERBOSE" = true ]; then
+    echo "Downloading from: $jarUrl"
+  fi
+  wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+
+  if command -v wget >/dev/null; then
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Found wget ... using wget"
+    fi
+    wget "$jarUrl" -O "$wrapperJarPath"
+  elif command -v curl >/dev/null; then
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Found curl ... using curl"
+    fi
+    curl -o "$wrapperJarPath" "$jarUrl"
+  else
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Falling back to using Java to download"
+    fi
+    javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+    if [ -e "$javaClass" ]; then
+      if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo " - Compiling MavenWrapperDownloader.java ..."
+        fi
+        # Compiling the Java class
+        ("$JAVA_HOME/bin/javac" "$javaClass")
+      fi
+      if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+        # Running the downloader
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo " - Running MavenWrapperDownloader.java ..."
+        fi
+        ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+      fi
+    fi
+  fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+  echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME=$(cygpath --path --windows "$M2_HOME")
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
+  [ -n "$CLASSPATH" ] &&
+    CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
+  [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+    MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
+fi
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+  $MAVEN_OPTS \
+  -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+  "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/source-code/start/hello-world/mvnw.cmd b/source-code/bean-validation-demo/mvnw.cmd
similarity index 100%
rename from source-code/start/hello-world/mvnw.cmd
rename to source-code/bean-validation-demo/mvnw.cmd
diff --git a/source-code/bean-validation-demo/pom.xml b/source-code/bean-validation-demo/pom.xml
new file mode 100644
index 0000000..c5a9e2f
--- /dev/null
+++ b/source-code/bean-validation-demo/pom.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.4.5</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+    <groupId>com.example</groupId>
+    <artifactId>bean-validation-demo</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>bean-validation-demo</name>
+    <description>Demo project for Spring Boot</description>
+
+    <properties>
+        <java.version>1.8</java.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.13.1</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/BeanValidationDemoApplication.java b/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/BeanValidationDemoApplication.java
new file mode 100644
index 0000000..3f934bc
--- /dev/null
+++ b/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/BeanValidationDemoApplication.java
@@ -0,0 +1,13 @@
+package com.example.beanvalidationdemo;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class BeanValidationDemoApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(BeanValidationDemoApplication.class, args);
+    }
+
+}
diff --git a/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/constants/Constants.java b/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/constants/Constants.java
new file mode 100644
index 0000000..8c20af3
--- /dev/null
+++ b/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/constants/Constants.java
@@ -0,0 +1,5 @@
+package com.example.beanvalidationdemo.constants;
+
+public final class Constants {
+    public static final String sexs = "((^Man$|^Woman$|^UGM$))";
+}
diff --git a/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/controller/HelloWorldController.java b/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/controller/HelloWorldController.java
new file mode 100644
index 0000000..8742cbf
--- /dev/null
+++ b/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/controller/HelloWorldController.java
@@ -0,0 +1,19 @@
+package com.example.beanvalidationdemo.controller;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author shuang.kou
+ * 验证基本环境搭建是否正确
+ */
+@RestController
+@RequestMapping("/api")
+public class HelloWorldController {
+
+    @GetMapping("/hello")
+    public String hello() {
+        return "Hello";
+    }
+}
diff --git a/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/controller/PersonController.java b/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/controller/PersonController.java
new file mode 100644
index 0000000..c0482b5
--- /dev/null
+++ b/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/controller/PersonController.java
@@ -0,0 +1,38 @@
+package com.example.beanvalidationdemo.controller;
+
+import com.example.beanvalidationdemo.entity.PersonRequest;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.Valid;
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Size;
+
+@RestController
+@RequestMapping("/api/persons")
+@Validated
+public class PersonController {
+
+    @PostMapping
+    public ResponseEntity<PersonRequest> save(@RequestBody @Valid PersonRequest personRequest) {
+        return ResponseEntity.ok().body(personRequest);
+    }
+
+    @GetMapping("/{id}")
+    public ResponseEntity<Integer> getPersonByID(@Valid @PathVariable("id") @Max(value = 5, message = "超过 id 的范围了") Integer id) {
+        return ResponseEntity.ok().body(id);
+    }
+
+    @PutMapping
+    public ResponseEntity<String> getPersonByName(@Valid @RequestParam("name") @Size(max = 6, message = "超过 name 的范围了") String name) {
+        return ResponseEntity.ok().body(name);
+    }
+}
diff --git a/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/entity/Person.java b/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/entity/Person.java
new file mode 100644
index 0000000..80bb5e8
--- /dev/null
+++ b/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/entity/Person.java
@@ -0,0 +1,18 @@
+package com.example.beanvalidationdemo.entity;
+
+import com.example.beanvalidationdemo.service.AddPersonGroup;
+import com.example.beanvalidationdemo.service.DeletePersonGroup;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Null;
+
+@Data
+public class Person {
+
+    // 当验证组为 DeletePersonGroup 的时候 group 字段不能为空
+    @NotNull(groups = DeletePersonGroup.class)
+    // 当验证组为 AddPersonGroup 的时候 group 字段需要为空
+    @Null(groups = AddPersonGroup.class)
+    private String group;
+}
diff --git a/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/entity/PersonRequest.java b/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/entity/PersonRequest.java
new file mode 100644
index 0000000..9b2c789
--- /dev/null
+++ b/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/entity/PersonRequest.java
@@ -0,0 +1,44 @@
+package com.example.beanvalidationdemo.entity;
+
+import com.example.beanvalidationdemo.validation.PhoneNumber;
+import com.example.beanvalidationdemo.validation.Region;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+
+/**
+ * 微信搜 JavaGuide 回复"面试突击"即可免费领取个人原创的 Java 面试手册
+ *
+ * @author Guide哥
+ * @date 2021/04/21 20:48
+ **/
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class PersonRequest {
+
+    @NotNull(message = "classId 不能为空")
+    private String classId;
+
+    @Size(max = 33)
+    @NotNull(message = "name 不能为空")
+    private String name;
+
+    @Pattern(regexp = "(^Man$|^Woman$|^UGM$)", message = "sex 值不在可选范围")
+    @NotNull(message = "sex 不能为空")
+    private String sex;
+
+    @Region
+    private String region;
+
+    @PhoneNumber(message = "phoneNumber 格式不正确")
+    @NotNull(message = "phoneNumber 不能为空")
+    private String phoneNumber;
+
+}
diff --git a/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/exception/GlobalExceptionHandler.java b/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/exception/GlobalExceptionHandler.java
new file mode 100644
index 0000000..c4f4e0a
--- /dev/null
+++ b/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/exception/GlobalExceptionHandler.java
@@ -0,0 +1,33 @@
+package com.example.beanvalidationdemo.exception;
+
+import com.example.beanvalidationdemo.controller.PersonController;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.FieldError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+
+import javax.validation.ConstraintViolationException;
+import java.util.HashMap;
+import java.util.Map;
+
+@ControllerAdvice(assignableTypes = {PersonController.class})
+public class GlobalExceptionHandler {
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    public ResponseEntity<Map<String, String>> handleValidationExceptions(
+            MethodArgumentNotValidException ex) {
+        Map<String, String> errors = new HashMap<>();
+        ex.getBindingResult().getAllErrors().forEach((error) -> {
+            String fieldName = ((FieldError) error).getField();
+            String errorMessage = error.getDefaultMessage();
+            errors.put(fieldName, errorMessage);
+        });
+        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errors);
+    }
+
+    @ExceptionHandler(ConstraintViolationException.class)
+    ResponseEntity<String> handleConstraintViolationException(ConstraintViolationException e) {
+        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
+    }
+}
diff --git a/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/service/AddPersonGroup.java b/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/service/AddPersonGroup.java
new file mode 100644
index 0000000..1f2950d
--- /dev/null
+++ b/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/service/AddPersonGroup.java
@@ -0,0 +1,4 @@
+package com.example.beanvalidationdemo.service;
+
+public interface AddPersonGroup {
+}
diff --git a/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/service/DeletePersonGroup.java b/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/service/DeletePersonGroup.java
new file mode 100644
index 0000000..92f6713
--- /dev/null
+++ b/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/service/DeletePersonGroup.java
@@ -0,0 +1,4 @@
+package com.example.beanvalidationdemo.service;
+
+public interface DeletePersonGroup {
+}
diff --git a/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/service/PersonService.java b/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/service/PersonService.java
new file mode 100644
index 0000000..08e8e14
--- /dev/null
+++ b/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/service/PersonService.java
@@ -0,0 +1,28 @@
+package com.example.beanvalidationdemo.service;
+
+import com.example.beanvalidationdemo.entity.Person;
+import com.example.beanvalidationdemo.entity.PersonRequest;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.Valid;
+
+@Service
+@Validated
+public class PersonService {
+
+    public void validatePersonRequest(@Valid PersonRequest personRequest) {
+        // do something
+    }
+
+    @Validated(AddPersonGroup.class)
+    public void validatePersonGroupForAdd(@Valid Person person) {
+        // do something
+    }
+
+    @Validated(DeletePersonGroup.class)
+    public void validatePersonGroupForDelete(@Valid Person person) {
+        // do something
+    }
+
+}
diff --git a/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/validation/PhoneNumber.java b/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/validation/PhoneNumber.java
new file mode 100644
index 0000000..afd5373
--- /dev/null
+++ b/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/validation/PhoneNumber.java
@@ -0,0 +1,22 @@
+package com.example.beanvalidationdemo.validation;
+
+import javax.validation.Constraint;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Documented
+@Constraint(validatedBy = PhoneNumberValidator.class)
+@Target({FIELD, PARAMETER})
+@Retention(RUNTIME)
+public @interface PhoneNumber {
+    String message() default "Invalid phone number";
+
+    Class[] groups() default {};
+
+    Class[] payload() default {};
+}
diff --git a/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/validation/PhoneNumberValidator.java b/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/validation/PhoneNumberValidator.java
new file mode 100644
index 0000000..85acdcd
--- /dev/null
+++ b/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/validation/PhoneNumberValidator.java
@@ -0,0 +1,21 @@
+package com.example.beanvalidationdemo.validation;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber, String> {
+
+    @Override
+    public boolean isValid(String phoneField, ConstraintValidatorContext context) {
+        if (phoneField == null) {
+            // can be null
+            return true;
+        }
+        //  大陆手机号码11位数,匹配格式:前三位固定格式+后8位任意数
+        // ^ 匹配输入字符串开始的位置
+        // \d 匹配一个或多个数字,其中 \ 要转义,所以是 \\d
+        // $ 匹配输入字符串结尾的位置
+        String regExp = "^[1]((3[0-9])|(4[5-9])|(5[0-3,5-9])|([6][5,6])|(7[0-9])|(8[0-9])|(9[1,8,9]))\\d{8}$";
+        return phoneField.matches(regExp);
+    }
+}
diff --git a/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/validation/Region.java b/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/validation/Region.java
new file mode 100644
index 0000000..8b0a204
--- /dev/null
+++ b/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/validation/Region.java
@@ -0,0 +1,26 @@
+package com.example.beanvalidationdemo.validation;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * @author shuang.kou
+ */
+@Target({FIELD})
+@Retention(RUNTIME)
+@Constraint(validatedBy = RegionValidator.class)
+@Documented
+public @interface Region {
+
+    String message() default "Region 值不在可选范围内";
+
+    Class<?>[] groups() default {};
+
+    Class<? extends Payload>[] payload() default {};
+}
diff --git a/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/validation/RegionValidator.java b/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/validation/RegionValidator.java
new file mode 100644
index 0000000..9490c5b
--- /dev/null
+++ b/source-code/bean-validation-demo/src/main/java/com/example/beanvalidationdemo/validation/RegionValidator.java
@@ -0,0 +1,17 @@
+package com.example.beanvalidationdemo.validation;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+import java.util.HashSet;
+
+public class RegionValidator implements ConstraintValidator<Region, String> {
+
+    @Override
+    public boolean isValid(String value, ConstraintValidatorContext context) {
+        HashSet<Object> regions = new HashSet<>();
+        regions.add("China");
+        regions.add("China-Taiwan");
+        regions.add("China-HongKong");
+        return regions.contains(value);
+    }
+}
diff --git a/source-code/bean-validation-demo/src/main/resources/application.properties b/source-code/bean-validation-demo/src/main/resources/application.properties
new file mode 100644
index 0000000..e69de29
diff --git a/source-code/bean-validation-demo/src/test/java/com/example/beanvalidationdemo/PersonControllerTest.java b/source-code/bean-validation-demo/src/test/java/com/example/beanvalidationdemo/PersonControllerTest.java
new file mode 100644
index 0000000..f020f05
--- /dev/null
+++ b/source-code/bean-validation-demo/src/test/java/com/example/beanvalidationdemo/PersonControllerTest.java
@@ -0,0 +1,83 @@
+package com.example.beanvalidationdemo;
+
+
+import com.example.beanvalidationdemo.entity.PersonRequest;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.Validation;
+import javax.validation.Validator;
+import javax.validation.ValidatorFactory;
+import java.util.Set;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+public class PersonControllerTest {
+    @Autowired
+    private MockMvc mockMvc;
+
+    @Autowired
+    private ObjectMapper objectMapper;
+
+
+    /**
+     * 验证出现参数不合法的情况抛出异常并且可以正确被捕获
+     */
+    @Test
+    public void should_check_person_value() throws Exception {
+        PersonRequest personRequest = PersonRequest.builder().sex("Man22")
+                .classId("82938390")
+                .region("Shanghai")
+                .phoneNumber("1816313815").build();
+        mockMvc.perform(post("/api/person")
+                .contentType(MediaType.APPLICATION_JSON)
+                .content(objectMapper.writeValueAsString(personRequest)))
+                .andExpect(MockMvcResultMatchers.jsonPath("sex").value("sex 值不在可选范围"))
+                .andExpect(MockMvcResultMatchers.jsonPath("name").value("name 不能为空"))
+                .andExpect(MockMvcResultMatchers.jsonPath("region").value("Region 值不在可选范围内"))
+                .andExpect(MockMvcResultMatchers.jsonPath("phoneNumber").value("phoneNumber 格式不正确"));
+    }
+
+    @Test
+    public void should_check_path_variable() throws Exception {
+        mockMvc.perform(get("/api/person/6")
+                .contentType(MediaType.APPLICATION_JSON))
+                .andExpect(status().isBadRequest())
+                .andExpect(content().string("getPersonByID.id: 超过 id 的范围了"));
+    }
+
+    @Test
+    public void should_check_request_param_value2() throws Exception {
+        mockMvc.perform(put("/api/person")
+                .param("name", "snailclimbsnailclimb")
+                .contentType(MediaType.APPLICATION_JSON))
+                .andExpect(status().isBadRequest())
+                .andExpect(content().string("getPersonByName.name: 超过 name 的范围了"));
+    }
+
+    /**
+     * 手动校验对象
+     */
+    @Test
+    public void check_person_manually() {
+        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+        Validator validator = factory.getValidator();
+        PersonRequest personRequest = PersonRequest.builder().sex("Man22")
+                .classId("82938390").build();
+        Set<ConstraintViolation<PersonRequest>> violations = validator.validate(personRequest);
+        violations.forEach(constraintViolation -> System.out.println(constraintViolation.getMessage()));
+    }
+}
diff --git a/source-code/bean-validation-demo/src/test/java/com/example/beanvalidationdemo/PersonServiceTest.java b/source-code/bean-validation-demo/src/test/java/com/example/beanvalidationdemo/PersonServiceTest.java
new file mode 100644
index 0000000..88b5da3
--- /dev/null
+++ b/source-code/bean-validation-demo/src/test/java/com/example/beanvalidationdemo/PersonServiceTest.java
@@ -0,0 +1,44 @@
+package com.example.beanvalidationdemo;
+
+import com.example.beanvalidationdemo.entity.Person;
+import com.example.beanvalidationdemo.entity.PersonRequest;
+import com.example.beanvalidationdemo.service.PersonService;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import javax.validation.ConstraintViolationException;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class PersonServiceTest {
+    @Autowired
+    private PersonService service;
+
+    @Test
+    public void should_throw_exception_when_person_request_is_not_valid() {
+        try {
+            PersonRequest personRequest = PersonRequest.builder().sex("Man22")
+                    .classId("82938390").build();
+            service.validatePersonRequest(personRequest);
+        } catch (ConstraintViolationException e) {
+            e.getConstraintViolations().forEach(constraintViolation -> System.out.println(constraintViolation.getMessage()));
+        }
+    }
+
+    @Test(expected = ConstraintViolationException.class)
+    public void should_check_person_with_groups() {
+        Person person = new Person();
+        person.setGroup("group1");
+        service.validatePersonGroupForAdd(person);
+    }
+
+    @Test(expected = ConstraintViolationException.class)
+    public void should_check_person_with_groups2() {
+        Person person = new Person();
+        service.validatePersonGroupForDelete(person);
+    }
+
+}
diff --git a/source-code/hello-world/.gitignore b/source-code/hello-world/.gitignore
new file mode 100644
index 0000000..a2a3040
--- /dev/null
+++ b/source-code/hello-world/.gitignore
@@ -0,0 +1,31 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**
+!**/src/test/**
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+
+### VS Code ###
+.vscode/
diff --git a/source-code/start/hello-world/.mvn/wrapper/MavenWrapperDownloader.java b/source-code/hello-world/.mvn/wrapper/MavenWrapperDownloader.java
similarity index 100%
rename from source-code/start/hello-world/.mvn/wrapper/MavenWrapperDownloader.java
rename to source-code/hello-world/.mvn/wrapper/MavenWrapperDownloader.java
diff --git a/source-code/hello-world/.mvn/wrapper/maven-wrapper.jar b/source-code/hello-world/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 0000000000000000000000000000000000000000..01e67997377a393fd672c7dcde9dccbedf0cb1e9
GIT binary patch
literal 48337
zcmbTe1CV9Qwl>;j+wQV$+qSXFw%KK)%eHN!%U!l@+x~l>b1vR}@9y}|TM-#CBjy|<
zb7YRpp)Z$$Gzci_H%LgxZ{NNV{%Qa9gZlF*E2<($D=8;N5Asbx8se{Sz5)O13x)rc
z5cR(k$_mO!iis+#(8-D=#R@|AF(8UQ`L7dVNSKQ%v^P|1A%aF~Lye$@HcO@sMYOb3
zl`5!ThJ1xSJwsg7hVYFtE5vS^5UE0$iDGCS{}RO;R#3y#{w-1hVSg*f1)7^vf<UNf
z)wF9Zei#8V-NF%1V9ztyu}jkfz%ltu-L599&CCMEtekl-a$QWIu_l|YD8-Tey^<{;
zUd!B-&aB8$pr=GFy(VMTLe*B7;e4W7y>kxrm!!N|oTR0H<e&!n_DAynZHf^7yQDS%
zR}<TRYof6J-{cl{bj|=PfTP8~#fbdx82w{hv~G?7dwUbde~T^a|2<oKI~zKCN4q~g
zI9r%FQA^WOO^#1Buue12Fz-sykJ8g#%hXMbFHuX5%h8PPOVLn`$Vki30YM*<B^Mo@
zBx8`GqZXn&0R2PDR#9(bx&Cy``KSHwa}WCGt(~LUza<dNzi0sV03&mge~T39|4I7)
zZ?XRx@4x!~Cxic+RRQ`R`;z(pYb5`{NXX94*~$6OTFU|~Y?Yk>j?N~IbVk+yC#NK}
z5myv()UMzV^!zkX@O=Yf!(Z_bF7}W>k*U4@--&RH0tHiHY0IpeezqrF#@8{E$9d=-
z7^kT=1Bl;(Q0k{*_vzz1Et{+*lbz%mkIOw(UA8)EE-Pkp{JtJhe@VXQ8sPNTn$Vkj
zicVp)sV%0omhsj;NCmI0l8zzA<n{*HrZNk)fVH4KV?ENGO1G=cPJmr_+WtHO6TZ#_
zU>ipDV#tp(Jr7p_BlL$}Pys_Soljzt<y0m+t#_9lNo5ko*WG$rsI|axK1TT}vfzT#
z-t`1XF@oZ*m~f!yj9H7Y6I=J|NG#BX6zMWb@sRGp=<7}1MKI9JDIx-vTk6bw$Y4W0
zsM_s+^yRRR3yIaFqsIBjwqSYg*_2iZgN0(kx05BnTr4`9b{2BW4b8$Zg>S%G-Wg+t
z&Q#=<03Hoga0R1&L!B);r{Cf~b<VpLDFb*Sji6t(W@f2k_JUey8!WU#ng<zkzxPfz
z^>$G5p#@?R-NNXMS8@cTWE^7V!?ixz(Ag>lld;>COenWc$RZ61W+pOW0wh>sN{~j;
zCBj!2<aQbZFtp}f8_b=$a(6{D0n2GMtH?dJh4$LJ<=V=~rYfS)M`4xygRLge-85Q|
zF2gNRM4<8alE_9(@&R<Gs^prKHzt<1jvF9jbZD8zBPe3^d5DrP0TGd{BH<KBsga}h
zJl&d^8S!b*J0?n{)NjT!R8EvJ)Kq%Cs>nn|4~CO<QmVZr&WhG07ycm*a9Ch9&Bisp
z$y8(vBej9?j3(MDN`}EZGNHddxmBi6`%quZ<-|6bj)YUmBrQ4n=SPXFMIBoyMeQT=
zYO0*MIPwR!iq~>wSgXHFH?BDr8pK323zvmDK-84E<JrxVa`cMM5IN`^3)tH#A7((U
zdUuPcXQ+vPH^B1%;|vkju2NL|wgC|A*Qvi$KC*6@dKF#Sont0t%Lr??rH?ac?x1Rw
zA~INBNM{X+<38CG`{@m#E6%LF&QFQ4%)qO9xseE=?i;cqq4%_C0pOVSjIB|@c6<4B
zU3LO7D#UO+o|+-0LsL|(3xcQ@gB6!hV@h{7tWGM^Y0KZWDf0`SbXKCjL)<KCJ#FGx
zC+XSQ|JAjhFNgNb$ER{FXa&@IRJ}{=EhAx~t56j`TrH>Sq25b;Tg%9(%NneBcs3;r
znZpzntG%E^XsSh|md^r-k0Oen5qE@awGLfpg;8P@a-s<{Fwf?w3WapWe|b-CQkqlo
z46GmTdPtkGYdI$e(d9Zl=?TU&uv9<Wy%Lb-oqYu7A+&>4VR`g|=7xB2Ur%=6id&R2
z4e@fP7`y58O2sl;YBCQFu7>0(lVt-r$9|06Q5V>4=>ycnT}Fyz#9p;3?86`ZD23@7
z<SM~oW?+u?MwYW}u=vHDueoIpH#KkXYzp_m3R|<=ruc=O9s6p<f%71)SHAOZ{+)?W
zrT>7n&`!LXzjx<cVSk9qlt-BbF^#m8TT_3ZZsjr>yg*P4Tz`>WVvpU9-<5MDSDcb1
zZaUyN@7mKLEPGS$^odZcW=GLe?3E$JsMR0kcL4#Z=b4P94Q#7O%_60{h>0D(6P*VH
z3<dJZ_fdw@BjQ3znq9u4ZPZB=r8MM3@)|N4E!CS@wH&X=!{!lwLcK}9Lc6%tMiOq!
zI^B9AZftkY)hnDguo?4QGvffqdR&i^kSAXugX1M}zQwtNua}&j>}>$stt2s!)w4C4
z{zsj!EyQm$2ARSHiRm49r7u)59ZyE}ZznFE7AdF&O&!-&(y=?-7$<LjK!5oKv&-*8
zLSWy%89@Jc+w?z=Xyv~<qD4$h0WQ|gB6e=J)^-466Gswd4<~068-@QgpMRd*Jp^5x
z%}s2bEsOxpcK<rUs}gnXkOfggwm=%CTb7knYJHm?$8=THx|n)X5wTHF8lzR?i`A7e
z0TWEZrlCY%Qg+q_;rzb{OfrE~EG1VXZR?#(4>LWcn4L_Yj%w`qzwz`cLqPRem1zN;
z)r)07;JFTnPODe09Z)SF5@^uRuGP~Mjil??oWmJTaCb;yx4?T?d**;AW!pOC^@GnT
zaY`WF609J>fG+h?5&#}OD1<%&;_lzM2vw70FNwn2U`-jMH7bJxdQM#6+dPNiiRFGT
z7zc{F6bo_V%NILyM?rBnNsH2>Bx~zj)pJ}*FJxW^D<LlO_B^`j&jpUcCqq&ni4-{`
z)lM(AYoBU`Ju#}&RTpf3m;Vi~E-up;faD0yUMrK@Vov-D1Q@H39k4))P+Q^WP8y<h
z3i?v|0Fv5+2Vy&kNYW?e$slsiL#11+-7)xJd6`b>C2NLlOI~18Mk`7sl=t`)To6Ui
zu4<E~kQwn-Xj<B<avK4AJfnkj0(FyrR0-^Xy|5Y0nW57BE_9fLsG~~kWX|JyovP5u
zfdIJhCaT2FCOJ<K>GK6KJx^6Ms4PP?jTn~jW6TOFLl3e2-q&ftT=31P1~a1%7=1XB
z+H~<1dh6%L)PbBmtsAr38>m~)?k3}<->1Bs+;227M@?!S+%X&M49o_e)X8|vZiLVa
z;zWb1<P8CXPo_(|zw{95dz=ZxA6MM|j~nz~KGOfxLxz7#5C6kYu=2xI#q%Xw*+{c4
zv8ZQBp*5Gzy*O+~_fM3iX0e#1E0o-@QECP{Gk!8=HgO}%O0!WG0RrnpMMa|zGgPdL
zh$zg<km%|>gYokP;Sbao^qD+2ZD_kUn=m=d{Q9_kpGxcbdQ0d5<_OZJ!bZJcmgBRf
z!Cdh`qQ_1NLhCulgn{V`C%|wLE8E6vq1Ogm`wb;7Dj+xpwik~?kEzDT$LS?#%!@_{
zhOoXOC95lVcQU^pK5x$D<Ga5<KcEbqSQwB++<f2}av+Nn9B&@<y2%H|9a1thB&K{c
zH{w(f{i_!pti!y<z!Yw(u?nhy0##&a$w<;G#^NAUq7o$oJ!UK=U>a$TscVXo19Pps
zA!(Mk>N|tskqBn=a#aDC4K%jV#+<Cebe<7;`T<qB5k`8y&PK%^AElH2V)Nm+_Xtu6
zp4ZK2T|7HIO$|1jGA27MqaxKL^idR}_O)_tgBW1dUZ$-E8Dz8bgz6Zl=WP+z;@HsV
zh}RBaAlRPm7>qI$$dPOK6;fPO)0$0j$`OV+mWhE+TqJoF5dgA=TH-}5DH<D~>_)H_
zh?b(tUu@65G-O)1ah%|CsU8>cLEy0!Y~#ut#Q|UT92MZok0b4V1INUL-)Dvvq`RZ4
zTU)YVX^r%_lXp<gNU^L<&XFXgc4NGH!`SfMd*}MtP>n_cwv`H=y49?!m{krF3Rh7O
z^z7l4D<+^7E?ji(L5CptsPGttD+Z7{N6c-`0V^lfFjsdO{aJMFfLG9+wClt<=Rj&G
zf6NgsPSKMrK6@Kvgarmx{&S48uc+ZLIvk0fbH}q-HQ4FSR33$+%FvNEusl6xin!?e
z@rrWUP5U?MbBDeYSO~L;S$hjxISwLr&0BOSd?fOyeCWm6hD~)|_9#jo+PVbAY3wzf
zcZS*2pX+8EHD~LdAl>sA*P>`g>>+&B{l94LNLp#KmC)t6`EPhL95s&MMph4<A;%qB
zIagPddar{rhm1VfBzrHH*@%k(*%UvOKDsq9F%%STba)NepJx<BsCf}<DIMwtsw<=G
zRGM69wuwN<IpWo56|;3+j!TVT!3h1_Z!IZqRPZ79n5s)PNLsHrBKKVR=s7s2_N000
zWvnLErkgjQ`c8eZ=*yp>6Sk^9x%B$RK!2MI--j8nvN31MNLAJBsG`+WMvo1}xpaoq
z%+W95_I`J1Pr&Xj`=)eN9<y}dfjf?FtuFDU&dKE({$RI-55h9okkNI~sceSElDgkw
zqb;CC&lxO>!Yt?LWKs3-`7nf)`G6#6#f+=JK!v943*F&veRQxKy-dm(VcnmA?K_l~
zfDWPYl6hhN?17d~^6Zuo@>Hswhq@HrQ)sb<SL-fs>7KK^TRhaM2f&td)$6zOn7we@
zd)x4-`?!qzTGDNS-E(^mjM%d46n>vPeMa;%7IJD<X3<y~ZnpT8!7ICMtKEq1dk!mG
zwZTgUp7ZTnxVNfYbXj~ou<b~?@+VuXBF8uKWl3(9mQz!a8-Aq4`2B<BcRJaMnOUJh
zYxQ>T(nC)T+WM5F-M$|p(78W!^ck6)A_!6|1o!D97tw8k|5@0(!8W&q9*ovYl)afk
z2mxnniCOSh7yHcSoEu8k`i15#oOi^O>u<t~yav7Wi-I&?DunI1!pn+MBYO2~_1Uc5
zn!<kY&)c`cQ`>O_oMpT=KQx4Ou{&C4vqZG}YD0q!{RX=`#5wmcHT=hqW3;Yvg5Y^^
ziVun<pi6L{vj9(}hJu=?;Y~4v@T=i5OwP5rwU&Uo2`IRF$Iug!UQb-Y3v*o|yrv+o
zxPgT3J9wU82mZVS?JVlA!ilUx8fp|NqGj!&GLd8S*);9c(doApw9JgKkWqIwBm<WQ
zC|2yS{Q^rYQx=7gB*<ob>z9V)>2&b^rI{ssTPx26OxTuCw|+{tt_M0TqD?Bg7cWN4
z%UH{38(EW1L^!b~rtWl)#<HDNzfT7|FBH#x9MmXqJK;$1v1hd}o6--HPNan|SBS}Y
zgd&WW4z=8)4<(-74F=9(s4~U%u}ha7P(zt7#7M_f6OT+VtRYHA^5@KfIgV&Ej}vG;
zA9PCk>i}=8IUa_oU8**_UEIw+SYMekH;Epx*SA7Hf!EN&t!)zuUca@_Q^zW(u_iK_
zrSw{nva4E6-Npy9?lHAa;b(O<b_#7xFpDRzXTB|N`=8x@KM<KY0tsdVN1tPN#iPWG
zz&G48uE=PVD(bPxSbTT2ZQ@!i(fYsgX=P*NriOXyi1|t5<V80RoQNEwr$oNr!n{C>
z`I74A{jNEXj(#r|eS^Vfj-I!aHv{fEkzv4=F%z0m;3^PXa27k0Hq#RN@J7TwQT4u7
ztisbp3w6#k!RC~!5g-RyjpTth$lf!5HIY_5pfZ8k#q!=q*n>~@93dD|V>=GvH^`zn
zVNwT@LfA8^4rpWz%FqcmzX2qEAhQ|_#u}md1$6G9qD%FXLw;fWWvqudd_m+PzI~g3
z`#WPz`M1XUKfT3&T4~XkUie-C#E`GN#P~S(Zx9%CY?EC?KP5<l-Ym%*%Qj3^&sK1G
zeuN@x2T77K`+eGxeeHR4fTZSL<O^Y%J0UV9_5K``JggJ}7^l}GDeIPsjKxL6gic4B
z(1{W)2v$BaLSW?~`63DVwR*orFxTnQzsE*2riw$o72x$|=z_>KNK`aLlI1;pJvq@d
z&0wI|dx##t6Gut6%Y9c-L|+kMov(7Oay++QemvI`JOle{8iE|2kZb=4x%a32?>-B~
z-%W$0t&=mr+WJ3o8d(|^209BapD`@6IMLbcBlWZlrr*Yrn^uRC1(}BGNr!<o(UVOW
zmzSeh<!_gn??N<b#sq|Zksu=XB{#axDk0-0fC7^-)DwS~wlJRyu6EE~Z7i}#FV>ct
z>xzEMV(&;ExHj5cce`pk%6!Xu=)QWtx2gfrAkJY@AZlHWiEe%^_}mdzvs(6>k7$e;
ze4i;rv$_Z$K>1Yo9f4&Jbx80?@X!+S{&QwA3j#sAA4<?h{i%7fcXI=x-4opP>U4#v
zwZqJ8%l~t7V+~BT%j4Bwga#Aq0&#rBl6p$QFqS{DalLd~MNR8Fru+cd<quwTsFlwj
zxz%oJ3++rIYBFtNAK}7TH?3%V5++~|4OeIMh5L)Q(Y+E3b&}Ix%xq|o9V(L*kB~ar
z7e5XJDa&QXMVoRPeHXH!AuVOx70L<*$z;Zn#?RoyD!YxJbJ5l=$U%aq@E=1TpN#~4
zrexZYLj!`$U(U7mWri9&LX4960WL`Dz0v%8-oht-tEMCz=}|8zg50xpR5m@i8|}21
z^@+MqbpYyeD+drgL6^<$HZ+9lAxDrU$n>oQ78Dl^K}@l#pmH1-e3?_0tZKdj@d2qu
z_{-B11*iuywLJgGUUxI|aen-((KcAZZdu8685Zi1b(#@_pmyAwTr?}#O7zNB7U6P3
zD=_g*ZqJk<zc!=8KIXfivuRU<uI=`xvHk&Y9q~w{$+ZB*CVilI%}CGbm}qcw%?A6;
z0XS%#HW!&a#c*4eI?a^{!VX`*@d%Q{$zSNNSM!)#@vtG7<n2{sBJ9mju(oG+p=Eu2
zmC(VGy@404BLeb@0g^Rotyo>g_9_X3lStTA-ENl1r>Q?p$X{6wU6~e7OKNIX_l9T#
z>XS?PlNEM>P&ycY3sbivwJYAqbQH^)z@PobVRER*Ud*bUi-hjADId`5WqlZ&o+^x=
z-Lf_80rC9>tqFBF%x#`o>69>D5f5Kp->>YPi5ArvgDwV#I6!UoP_F0YtfKoF2YduA
zCU!1`EB5;r68;WyeL-;(1K2!9sP)at9C?$hhy(dfKKBf}>skPqvcRl>UTAB05SRW!
z;`}sPVFFZ4I%YrPEtEsF(|F8gnfGkXI-2DLsj4_>%$_ZX8zVPrO=_$7412)Mr9BH{
zwKD;e13jP2XK&EpbhD-|`T~aI`N(*}*@yeDUr^;-J_`fl*NTSNbupyHLxMxjwmbuw
zt3@H|(hvcRldE+OHGL1Y;jtBN76Ioxm@UF1K}DPbgzf_a<Y=Q2jdO^gst1fHvD~4Y
z`>{`o<v8+8gs@^U5Cw_{S3@ObvFre^q+$AWj&Nnz@IvH$S&j2r4;1X@%I*4hELT$E
zaD(O11t4}`O?!jT*Kl?P1J<k2_Gc-(U(LIlVOiJkYd?edF>hXp_u4=ps@x-6-ZT>F
z)dU`Jpu~Xn&Qkq2kg%VsM?mKC)ArP5c%r8m4aLqimgTK$atIxt^b8lDVPEGDOJu!)
z%rvASo5|v`u_}vleP#wyu1$L5Ta%9YOyS5;w2I!UG&nG0t<o}kOP)%99Laiv(IbAk
zU$-92SK_lzLO)J`Yk-$<?9_JcJcXj)J=p`oksG^*)5V^sRcsj04MEXDxyav@sp{8<
za1ZHAdqqfDnhc;;xBPlZ{6YcM^AlN%?Z#eGk<cE<JLWO-kcD^P(Mb07!|!D)0gq6}
zulc(n);#Gpqn*mG)-=i8;kZ=b0R>2YL|DWxr#T7P#Ww8MXDg;-gr`x1?|V`wy&0vm
z=hqozzA!zqjOm~*DSI9jk8(9nc4^PL6VOS$?&^!o^Td8z0|eU$9x8s{8H!9zK|)NO
zqvK*dKfzG^Dy^vkZU|p9c+uVV<e?6G4GLMkSq!(QFws2FKN!81BZiraen($1&cG5t
zuK}@t#xt>3>esY)8SU1v4o{dZ+dPP$OT@XCB&@GJ<5U&$Pw#iQ9qzuc`I_%uT@%-v
zLf|?9w=mc;b0G%%{o==Z7AIn{nHk`>(!e(QG%(DN75xfc#H&S)DzSF<DtNhmWIAf}
zf_tZ47$xc=<zzA(to%hM4U_Nhw?|6Zv4|2*hN7}^8#*^uKIWJDEdP$-lUzdg-M~t9
zx(1Er`Wt`m7X|j0bkvy{i6YPDpnTFXkuJG>B6`J(cH!@mX3mv_!BJv?ByIN%r-i{Y
zBJU)}Vhu)6oGoQjT2tw&tt4n=9=S*nQV`D_MSw7V8u1-$TE>F-R6Vo0giKnEc4NYZ
zAk2$+Tba~}N0wG{$_7eaoCeb<NotLt60!NRCAlzA$?||-4B-mlh@o;a$HY`BzZiH5
z)Z%?8jo-TA#O>*Ubc0<yMx}$D{Q$Qud6!Y=xaFp7{E(!!Oe5N)K}QvD%}G?tD!|s*
zJWXXT#w3P_A{^fsFYB^+r8@e$0b6})dw9J<y_d9$*(+PmW-t6l*1SOK3=xlU7+WqV
zwYXB0k2qb+M1(`AHZ~T6Yo}-jM=XnU1tINV)w??nAXrwD)+*KQ9vZ13+{e&ciksgm
zWkZp0Y1C7A(PX7qxyaOLERk(RfC3!CkpLH3?ZK9CDPxw7N6Mt{vT0aj&U~WtSm+c>
zq~id50^$U>WZjm<q`}!T786Qi_+E}_4=k`H&5^sh1>cnIgsDione)f+T)0ID$xtgM
zpGZXmVez0DN!)ioW1E45{!`G9^Y1P1oXhP^rc@c?o+c$^Kj_bn(Uo1H2$|g7=92v-
z%Syv9Vo3VcibvH)b78USOTwIh{3%;3skO_htlfS?Cluwe`p&TMwo_WK6Z3Tz#nOoy
z_E17(!pJ>`C2KECOo38F1uP0hqB<U@aw1^&*=unvyt$E~a3Twf)tgW#gTScs-oItx
zV<mqF#P>r>%E=LCCCG{j6$b?;r?Fd$4@V-qjEzgWvzbQN%_nlBg?Ly`x-BzO2Nnd1
zuO|li(oo^Rubh?@$q8RVYn*aLnlWO_dhx8y(qzXN6~j>}-^Cuq4>=d|I>vhcjzhSO
zU`lu_UZ?JaNs1nH$I1Ww+NJI32^qUikAUfz&k!gM&E_L=e_9}!<JD%S8haU5QQKNv
zC!zRB`6DjW4S6{~t*#IU&v{&q%&R4iqcCp|t2W_}ZJvWV><(?BfH~aCmI&hfzHi1~
zraRkci>zMPLkad=A&NEnVtQQ#YO8Xh&K*;6pMm$ap_38m;XQej5zEqUr`HdP&cf0i
z5DX_<V=$v>c86@15jlm<r>*F}u-+a*^v%u_hpzwN2eT66Zj_1w)UdPz*jI|f<X{W6
zs=-Ks$~fr0xe>Jb#kSD_8Q-7q9gf}zNu2h=q{)O*XH8FU)l|m;I;rV^QpXRvMJ|7%
zWKTBX*cn`VY6k>mS#cq!uNw7H=GW3?wM$8@odjh$ynPiV7=Ownp}-|fhULZ)5{Z!Q
z20oT!6BZTK;-zh=i~RQ$Jw>BTA=T(J)WdnTObDM#61lUm>IFRy@QJ3RBZr)A9CN!T
z4k7%)I4yZ-0_n5d083t!=YcpSJ}M5E8`{uIs3L0lIaQws1l2}+<u<u@A+^}dd(wpG
zsG8FhotnIP^;H&oczN0FJAJ#-Dhl3MGbxpyP&^g$Q#0y0qj5bP!JR)|MTbS_tHmvF
z4Dhl{cxji!)^x<Ro9BLV)#Bn<oZv8o$u39YAoZHMu4T0%XkUM8e^81g_lWjE=QZ|3
zq1!g~j@=_L3{5>w2(}hW&evDlMnC!WV?9U^YXF}!N*iyBGyCyJ<(2(Ca<>!$rID`(
zR?V~-53&$6%DhW=)Hbd-oetTXJ-&XykowOx61}1f`V?LF=n8Nb-RLFGqheS7zNM_0
z1ozNap9J4GIM1CHj-%chrCdqPlP307wfrr^=XciOqn?YPL1|ozZ#LNj8QoCtAzY^q
z<aNn@TQ^N9J)3H3tA|{HV^(8UI#zG#%FkH|rPh{wv0K!=(@-h%t(K$N-OnGzJey;<
zzHg-q7g2E^rTsuGJMdZt{EF}#tt=dM&72s@8Cg!X1G8+NG02zcq5Qi#7MHmH9KzG9
zv&I;hp|W5E4D8uecF<X+n7q+K4ugcN#1_fsV^UAZMfYquKf!fd`KRG8^&y3q3V$f?
zo(<JAsU5pH#pkDz+WDI;f|5YWt2#6LH<HZ<*<Erz()TB*_b1TLnl5jp?Tom1*F9KE
z%V<kV**C)~ukWNL!mvON%raY@&w*ORq)9$zwZD-l$U>7&b^^K&?fNSWD@*`&I+`l9
zP2SlD0IO?MK60nbucIQWgz85l#+*<{*SKk1K~|x{ux+hn=SvE_XE`oFlr7$oHt-&7
zP{+x)*y}Hnt?WKs_Ymf(J^aoe2(wsMMRPu>Pg8H#x|zQ_=(G5&ieVhvjEXHg1zY?U
zW-hcH!DJPr+6Xnt)MslitmnHN(Kgs4)Y`PFcV0Qvemj;GG`kf<>?p})@kd9DA7dqs
zNtGRKVr0%x#Yo*lXN+vT;TC{MR}}4JvUHJHDLd-g88unUj1<S4DI9uM-?veQzZb|%
zAl!ZsT7K0!rHfkNr_GJ`@~rI6$0C35rTlbGXEu4?nm%^-ls1xTzNFZ21|P%59ky&Y
zq~(qDsF^5QX{fw!Ln(3;oja4)_YUQLJV<Iy9|p$4aZmit7Xs#<=_GGJy61qlwX~a9
zFyG$TiaUUld2B*RVn7ggvCo!Lx79zcH2M*;er^moky>(#7CM<%r!Z1Ve>DD)FneZ|
z8Q0yI@i4asJaJ^ge%JPl>zC3+UZ;UDUr7JvUYNMf=M2t{It56OW1nw#K8%sXdX$Yg
zpw3T=n}Om?j3-7lu)^XfBQkoaZ(qF0D=Aw&D%-bsox~`8Y|!whzpd5JZ{dmM^A5)M
zOwWEM>bj}~885z9bo{kWFA0H(hv(vL$G2;pF$@_M%DSH#g%V*R(>;7Z7eKX&AQv1~
z+lKq=488TbTwA!VtgSHwduwAkGycunrg}>6oiX~;Kv@cZlz=E}POn%BWt{EEd;*GV
zmc%PiT~k<(TA`J$#6HVg2HzF6Iw5w9{C63y`Y7?OB$W<O3y)Mrd+XeIl+-?B5_#+x
z%KOq3#~CJ{ywE4XUy=1lc#RI|A213c{@+E`q<<G#%h;Kj{YO|Gth{DBCxFPCQP{lK
zYMYKs2c)GXB?&yQL!zfeD56SI*eD8Gq@+JRL{m+z-?u6^SaOYpTJ|0P`5PZJ<C#cT
z9Fg@3H@DN_^LX9I*;8!JH~gLO7~p;(8s1QZ)(-m2I0VssTq!<G$P;J7DHJ8~@I237
zq3clY`<&b#AUnmdo_2Q=ky)2yAZtbpiyOaU-|w-6>sC$~6WMm3`UHaWRZLN3nKiV#
zE;iiu_)wTr7ZiELH$M^!i5eC9aRU#-RYZhCl1z_aNs@f`tD4A^$xd7I_ijCgI!$+|
zsulIT$KB&PZ}T-G;Ibh@UPafvOc-=p7{H<d#e3q0+%RuK-3K2(JT0@94+LYS^)H4c
zZFqCGM37o&#`_f#uHK;f4UhQIYmnw+`3Ale=BN`?snIX<B`=4t()0;>-<LgPQxoF?
zLX8D*o?O<AQaf_3cYu;ly5Ll(!osLzcv!~=P$VY_Q-T;e9LRKUX4OH|#0=HZa3hTs
zes&XuggA!hBf}m$GbUQJL;%kNmd4W`@E#fId_?zPOrTo@-J<QkzfbTKxF5=U;%TY6
zlz+g%__dgOM6v?MMUiTawMHK}+&KwVr27O7nWc)i&hd$zB@RxSWDF?xvSgK5vxU1l
zHiwd1%MV)*fySndRnI~;m~ligLKJLC6B2y=qsYl3R}Ogp1AMXp`8#!@|2uV({R<4G
z{!h%&Ur>~P)s{3M+;PmXe7}}&Mn+9WT#(Jmt5DW%73OBA$t<uD+FW^WplyXVBE6gG
ze_pQ7jmYIz)>C#U<XS@~W;KGX%r{b21T&3pdIfdm3HbNDZH>g!j1BR~=Xbnaz4hGq
zUOjC*z3mKNbrJm1Q!Ft^5{Nd54Q-O7<;n})TTQeLDY3C}RBGwhy*&wgnl8dB4lwkG
zBX6Xn#hn|!v7fp@@tj9mUPrdD!9B;tJh8-$aE^t26n_<4^=u~s_MfbD?lHnSd<OEr
z@GvCq^5O0J+3mmcGv;^guT|4A8IJT5Sc|&C%RqgDt}}IEro1&c6g8ca5pTSomxOH_
zp-rgz`=P6>^FGGL6the7a|AbltRGhfET*X;P7=AL?WPjBtt;3IXgUHLFMRBz(aWW_
zZ?%%SEPFu&+O?{JgTNB6^5nR@)rL6DFqK$KS$b<ko|6{-ybF=NrJ9}l!*ZZ#=USzU
zJ|(+CIwZL=k_Mhbw<?}16qWU`NmQ;zu>vE#&hrPs>sYsW=?XzOyD6ixglJ8rdt{P8
zPAa*+qKt(%ju&jDkbB6x7aE(={xIb*&l=GF(yEnWP<Klfsh)03zB(-sjeCr`x4$DS
z>j)><_8U5m#gQIIa@l49W_=Qn^RCsYqlEy6Om%<R1#uW4!tL11h|`1^LAF02ja(Y)
zB#*t4CG`r9D6!EJ8{LYP@eS{&5Hwp>!&e~6mCAfD<GrCq<8+)5xh*#}`b7HL4{S6W
z#|}g7<T=A-#|teqJmgImizMOptXzFQXU2YzRY@XrXCs8-K!n$sKyu1swB;ret*pi8
zR@;i>geXe3aYpHQAA!N|kmIW~Rk}+p<HPCBQ5qa?LoTl)R3Q#ozhWdO<c{?`j77C<
zs5i}Hl_gDjS@w@FX35H~F{{<DQ5{R;1ks3F{jIZPxT5PYHI=hkdQ2tSopnS}Nu1Ky
zxYDl>6B2U5@|1@7iVbm5&e7E3;c9q@XQlb^JS(gmJl%j9!N|eNQ$*OZf`3!;raRLJ
z;X-h>nvB=S?mG!-VH{65kwX-UwNRMQB9<m;@#GpPZRe8?u9;j#4?1fDS>S3ZRf`hL
z#WR)+rn4C(AG(T*FU}`&UJOU4#wT&oDyZfHP^s9#>V@ens??pxuu-6RCk=Er`DF)X
z>yH=P9RtrtY;2|Zg3Tnx3Vb!(lRLedVRmK##_#;Kjnlwq)eTbsY8|D{@Pjn_=kGYO
zJq0T<_b;aB37{U`5g6OSG=>|pkj&PohM%*O#>kCPGK2{0*=m(-gKBEOh`fFa6*~Z!
zVxw@7BS%e?cV^8{a`Ys4;w=tH4&0izFxgqjE#}UfsE^?w)cYEQjlU|uuv6{>nFTp|
zNLjRRT1{g{?U2b6C^w{!s+LQ(n}FfQPDfYPsNV?KH_1HgscqG7z&n3Bh|xNYW4i5i
zT4Uv-&mXciu3ej=+4X9h2uBW9o(SF*N~%4%=g|48R-~N32QNq!*{M4~Y!cS4+N=Zr
z?32_`YpAeg5&r_hdhJkI4|i(-&BxCKru`zm9`v+CN8p3r9P_RHfr{U$H~RddyZKw{
zR?g5i>ad^Ge&h?LHlP7l%4uvOv_n&WGc$vhn}2d!xIWrPV|%x#2Q-cCbQqQ|-yoTe
z_C(P))5e*WtmpB`Fa~#b*yl#vL4D_h<fz{p{Hd&U%VxxpXWDwhxv<px?ngxI2w@S+
zomjQ6&>;CidEbI9tsE%+{-4ZLKh#9^{mvY24#u}S6oiUr8b0xLYaga!(Fe7Dxi}v6
z%5xNDa~i%tN`Cy_6jbk@aMaY(xO2#vWZh9U?mrNrLs5-*n>04(-Dlp%6AXsy;f|a+
z^g~X2LhLA>xy(8aNL9U2wr=ec%;J2hEyOkL*D%t4cNg7WZF@m?kF5YGvCy`L5jus#
zGP8@iGTY|ov#t&F$<aO_`4zACJh+ONRNqngjc&LX4fNmgZ7xWzr@)#`k<O&kW001d
z!1wN+i%2eO_PtXBKYj(-$a*outi0m9%k|n}to~WbH;?A~gzk7Ozx@)cag#Z@cg;cl
zYjq{j9>%gkWDoMR7v*UezIWMeg$C2~WE9*5%}$3!eFiFJ?hypfIA(PQT@=B|^Ipcu
z{9cM3?rPF|gM~{G)j*af1hm+l92W7HR<Ukf%^!B+FzyOr_D718fxbF}euriBp)ex@
zQli%k-Lx2CpL4|pS)&G{!YVWgzg=MN2)|3D;bo0k^KYZ@`TOit{}wRv@p7~Jlf+gF
z+{;GkJbHa$W3(8=nBOAqtBGJ0dZ5aemv1Gpl5y{^5tx`27j}(yO%V3*lzW8A?rCw=
z7F;r5unMA-OfP_Q96e&cO<gXH_(5TGdJvR+pqR~tuO-^XgXSep(B@SkZw)AA`Kbq+
zerds!nmS)Fl$sX{4#(`SgRdOejK2^q8LCd758ks()l6-;_8xAR$EkWAM?SeQlbST`
z6EJ)~YDz813uD11U)kt0<gBOj+Y^zeCp6hkqYbTZv;l)qR}V4dr6+#B`QjG0NfZB?
zhP+%=k@!nQu}|mp5LLcLZ|?fA^Y(quF>pQ*hSMDbh(auwr}VBG7`ldp>`FZ^amvau
zTa~Y7%tH@>|BB6kSRGiWZFK?MIzxEHKGz#P!>rB-90Q_UsZ=uW6aTzxY{MPP@1rw-
z&RP^Ld%HTo($y?6*aNMz8h&E?_PiO{jq%u4kr#*uN&Q+Yg1Rn831U4A6u#XOzaSL4
zrcM+0v@%On8N*Mj!)&IzXW6A80bUK&3w|<y{`IM*S_3bA@F!}Fqi973&MQsS2Pqn9
z9;ZgXQ>z0<Tq#Aipd)-qh}q3N7LO13RA&D~KeM|n^wgN=n!x)H#E-HfKNpc2AQRXs
zXs;J%N;)%Kbwh$b*>6cP!UD^?_rb_(L-u$m+#%YilEjkrlxthGCLQ@Q?J!p?ggv~0
z!qipxy&`w48T0(Elsz<^hp_^#1O1cNJ1UG=61Nc=)rlRo_P6v&&h??Qvv$ifC3oJh
zo)ZZhU5enAqU%YB>+FU!1vW)i$m-Z%w!c&<h6xWZEtd{*OPDU|5jw8<I&<jkewliI
zBPay#;mI#y_DW1Q-n9&Sz@3x;1#z3$&ekl1^zdX%Y)Bk*%Cag=8`4&YmVk$`)Da=6
zT5Y|=YUh#7V~sj0*zB?9!T-7Hs8Z?RpG>92M1?))n4z1a#4-FufZ$DatpJ^q)_Zif
z;Br{HmZ|8LYRTi`#?TUfd;#>c4@2qM5_(H+Clt@kkQT+kx78KACyvY)?^zhyuN_Z&
z-*9_o_f3IC2lX^(aLeqv#>qnelb6_jk+lgQh;TN>+6AU9*6O2h_*=74m;xSPD1^C9
zE0#!+B;utJ@8P6_DKTQ9kNOf`C*Jj0QAzsngKMQVDUsp=k~hd@wt}f{@$O*xI!a?p
z6Gti>uE}IKAaQwKHRb0DjmhaF#+{9*=*^0)M-~6lPS-kCI#RFGJ-GyaQ+rhbmhQef
zwco))WNA1LFr|J3Qsp4ra=_j?<EUO<0lpDUk;5qVyhHpO+$EpCmHSpN(Wwyb2E)d0
z6RhNXQ5_m8UEI?_TXb0Y&r-w#5gRm8Rm0k0*;U!JLd|=H{7!a-(d0!WPsH*JASge9
zEHu@Ayf*3EL)&wggg`J!$?K)OQ<C27K6n`;1UWNI<vx6e1Zr{-iA&U`uYZ&}k%RBY
z5B@*|S26z1gXa4?4_X1>Y%b{JWMX6Zr`$;*V`l`g7P0sP?Y1y<akjAgmqQ(`q$`i2
zh{)SO+o7PY(fk0yF9>OY;e0Sb!AOW0Em=U8&i8EKxTd$dX6=^Iq5ZC%zMT5Jjj%0_
zbf|}I=pWjBKAx7wY<4-4o&E6vVStc<kK0!v^JK_~oYfr_x+_wAgFq`rT(&ore@w97
zhTDu%){?F%P9jc4-)I>NlT?I18f5TYP9!s|5yQ_C!MNnRyDt7~u~^VS@kKd}Zwc~?
z=_;2}`Zl^xl3f?ce8$}g^V)`b8Pz88=9FwYuK_x%R?sbAF-dw`*@wokEC3mp0Id>P
z>OpMGxtx!um8@gW2#5|)RHpRez+)}_p;`+|*m&3&qy{b@X>uphcgAVgWy`?Nc|NlH
z75_k2%3h7Fy~EkO{vBMuzV7lj4B}*1Cj(Ew7oltspA6`d69P`q#Y+rHr5-m5&be&(
zS1GcP5u#aM9V{fUQTfHSYU`kW&Wsxeg;S*{H_Cd<rvd3|*7vAvd4;z@7YMftc;-2i
z?g6G4MNx#O)_U6}%hp)dU#4PXb!m$1S_s$n<!DYMF)%ZBLHzV79nBn#FdvcrCqjJ%
zI3rCJ`@7)vfy0k)pz@q*G(LD!0!M$v?YMSRk{y2nE$6=tv}pdX)#RU1R+id}AIcMw
zkCk*X4LUG%>Z$?N<Y+u?Fj*rgNH~41(wM7{zXMKvGP;N0rmCwtc#A~_K8eljo1C=g
zR>>S$JPv!_6T(NqYPaS{yp0H7F~7vy#>UHJr^lV?<J;9&=L_49glF|{ve0rGu5@K+
zjpG5?I`hfS%pP-#&|n*KF;<sCIeW*EcODz?ze)_ATx=wz4_TR0a5H+U&2+$QPP)_b
zW-8_qgi+K(hi8S=$xFzT3NX-u#7~D3s%=u+^uV|wXyzPP=dxyQMR~*2zz}R4at)TA
z2bPtEL6lY11xulU7tEY@{4-shy_U7+Ep)?;G)7>=^vt4?8$v8vkI-1eJ4{iZ!7D5A
zg_!ZxZV+9Wx5EIZ1%rbg8`-m|=>knmTE1cpaBVew_iZpC1>d>qd3`b6<(-)mtJBmd
zjuq-qIxyKvIs!w4$qpl{0cp^-oq<=<Nz^Cpruz}iWZa$|&EW^BDN{oRU4*zoBtP7!
zk3Dizso=L6U<fYC_f1c29X*Le{h(vYIYRcJT~(NI77Fpj^D}5WE*Jc*ePbgi+9e{Z
z=(VI@r)kyM@nVxvX1v_ihb47H**(L4HPZ;yQbQKwaJU;3U)>-IDEYV7{pvfBM7tU+
zfX3fc+VGtqjPIIx`^I0i>*L-NfY=gFS+|sC75Cg;2<)!Y`&p&-AxfOHVADHSv1?7t
zlOKyXxi|7HdwG5s4T0))dWudvz8SZpxd<{z&rT<34l}XaaP86x)Q=2u5}1@Sgc41D
z2gF)|aD7}UVy)bnm788oYp}Es!?|j73=tU<<Bzq%Z@Jee=IEO#jG%NN+Sg}<?(5;n
zR&=oFHv-W7&;%>_+A4<GVLAj|3q{pgf5f#&6!yut|1@EvQWd-fDw4fDOmiqw4Or3I
z0yjQ6nb!X4k614Oky|s?y*iVx4{a*guHGtTCJ$#dDTKI$_8iMSvKIqMTVse6wB$HZ
zkHP9ofp4d9-ewaX6&B&zjl8EB^KidihKP#_lDh_y-Bw^0(Y!-PKG(iy&iSY;=ca=u
z-Kv=;PkGgl0H7nkqHiD4^CZnOK_loDmrP|judlcp+m3Xdcz}tEEqmtR>s5&it~_K4
z;^$i0Vnz8y&I!abOkzN|Vz;kUTya#Wi07>}Xf^<lUW7sJyK@(8yd@$KRj${^_mR8*
zjK5ba-;sFb>7joZMiHH3Mdy@e_7t?l8^A!r#jTBau^wn#{|!tTg=w01EQUKJOca!I
zV*>St2399#)bMF++1qS8T2iO3^oA`i^Px*i)T_=j=H^Kp4$Zao(<q)zKd;FmCQyPf
z@=XjI^cZdzUZ1>>Y)kpZ=l#dSgcUqY=7QbGz9mP9lHnII8vl?yY9rU+i%X)-j0&--
zrtaJsbkQ$;DXyIqDqqq)LIJQ!`MIsI;goVbW}73clAjN;1Rtp7%{67uAfFNe_hyk=
zn=8Q1x*zHR?txU)x9$nQu~nq7{Gbh7?tbgJ>i8%QX3Y8%T{^58W^{}(!9oPOM+zF3
zW`%<~q@W}9hoes56uZnNdLkgtcRqPQ%W8>o7mS(j5Sq_nN=b0A`Hr%13P{uvH?25L
zMfC&Z0!{JBGiKoVwcIhbbx{I35o}twdI_ckbs%1%AQ(Tdb~Xw+sXAYcOoH_9WS(yM
z2dIzNLy4D%le8Fxa31fd;5SuW?ERAsagZVEo^i};yjBhbxy9&*XChFtOPV8G77{8!
zlYemh2vp7aBDMGT;YO#=YltE~(Qv~e7c=6$VKOxHwvrehtq>n|w}vY*YvXB%a58}n
zqEBR4zueP@A~uQ2x~W-{o3|-xS@o>Ad@W99)ya--dRx;TZLL?5E(xstg(6SwDIpL5
zMZ)+)+&(hYL(--dxIKB*#v4<fxk!yCv|@d2Kv&v5q{37y^?qK+5_}pV8uj_ajQp?-
zSvtF-+*s!bhoC0ZP-IzCs5r{10MP%1o^F=0m(b9iJ)*j<v7*$j97i0J8=>mDq=0ve
zNU<q(%{s#bJ0`q9l9Ob)&b>~~jk426b<X0^!MeJk+~;KF?z<>XlS8%lcqsvuqbpgn
zbFgxap;17;@xVh+Y~9@+-lX@LQv^Mw=yCM&2!%VCfZsiwN>DI=O?vHupbv9!4d*>K
zcj@a5vqjcjpwkm@<sKio7{@okPSn_SbL}SW{;=N5eLbwcI{_j649H5UO_@Nna`xAw
zi$6=m;8$AFj_kqUuX>!2dxzzJGQ7#ujW(IndUuYC)i3N2<*doRGX8a$bSbyRO#0rA
zUpFyEGx4S9$TKuP9BybRtjcAn$bGH-9>e(V{pKYPM3waYrihBCQf+UmIC#E=9v?or
z_7*yzZfT|)8R6>s(lv6uzosT%WoR`bQIv(?llcH2Bd@26?zU%r1K25qscRrE1<E`b
zYRcw#u^W4w?|p%|1j_I3@eA+knxw=&C%{kW<sS+4P;crS{y&JlP58g_-`M`nF#pd>
z9TI<P^#281Q`OW)QOER+1qJVG%_|iYozF*vR77e5wOBALf{P5vSLlG<Nrfe2-ZLhe
zp17>IP_?`78@uJ{%I|_K;*syVinV;pCW!+zY-!^#n{3It^6EKw{~WIA0pf_hVzEZy
zFzE=d-NC#mge{4Fn}we02-%Zh$JHKpXX3qF<#8__*I}+)Npxm?26dgldWyCmtwr9c
zOXI|P0zCzn8M_Auv*h9;2lG}x*E|u2!*-s}moqS%Z`?O$<0amJG9n`dOV4**mypG-
zE}In1pOQ|;@@Jm;I#m}jkQegIXag4K%J;C7<@R2X8IdsCNqrbsaUZZRT|#6=N!~H}
zlc2hPngy9r+Gm_%tr9V&HetvI#QwUBKV&6NC~PK>HNQ3@fHz;J&rR7XB>sWkXKp%A
ziLlogA`I*$Z7KzLaX^H_j)6R<XJ@kJ)Qr`WAifZ%j-fh_ok^Gwl%@JOa&FuMC6r$o
zs{G|2uaK73X-&=L>|9Q>IHc?<W_gTkPC4j9Y~^991y4Irw3pUgMA&$#fkl_nU}~w>
z{s0MsOW>%xW|JW=RUxY@@0!toq`QXa=`j;)o2iDBiDZ7c4Bc>BiDTw+zk}<qyZ~T~
zQD`&I<63HHT}TUKau6K8JH{w|ItNb3b+cB@vql6N&5H!?rGigVB#W=+t)WWSTigS4
zl`$<~uzPa82j%`~4Wh=Ts{u)pucbzDB;>Jm&vvH8qX$R`M6Owo>m%n`eizBf!&9X6
z)f{GpMak@NWF+HNg*t#H5yift5@QhoYgT7)jxvl&O=U54Z>FxT5prvlDER}AwrK4Q
z*&JP9^k332OxC$(E6^H`#zw|K#cpwy0i*+!z{T23;dqUKbjP!-r*@_!sp+Uec@^f0
zIJMjqhp?A#YoX5EB%iWu;mxJ1&W6Nb4QQ@GElqNjFNRc*=@aGc$PHdoUptckkoOZC
zk@c9i+WVnDI=GZ1?lKjobDl%nY2vW~d)eS6L<E_5TsrpY3$@H(bSiZZ^i}%eYQsKY
zKayFP3dSQtz$Pc7Hwo6vjr{S7vT0<B@R9;Wa_cDSBrq1@ws^EZ<W?v))raUN>ch&J
zDi~}*fzj9#<%xg<5z-4(c}V4*pj~<Zc~a8XME$bHbF;VecMV`!mhERQqQ@&DNtT1N
z3jV4*CXth`PK!@H*$92;4Gp$B=mPxrQRFq^WxEu|*z+yXe%@AXmOR02FqcieX!Bo*
zW^Zka2jNxb`{fv*t#(J7Kk$|%%XB?fHb%qTg=~4J_-pbkEWx}AdU4YU3Yx7v;2<P9
zc<MtIDL1FtR`Eu6o{{f;3H;>1z2z60gZc}sAmys^yvobWz)DKDGWuVpp^4-(!2Nn7
z3pO})bO)({KboXlQA>3PIlg@Ie$a=G;MzVeft@<Ke;}=bxR7*h)PObOn&#xTQRli)
zmvKT@_m%g`7ER3aeL7WHr7_~}{equ;`!-oXa2F&=$PjK?EsnLsIOLW2vsrnUL_SL=
z&>OMcKEjIr=?;=G0AH?dE_DcNo%n$_bFjqQ8GjeIyJP^NkX~7e&@+PqnU-c3@ABap
z=}IZvC0N{@fMDOpatOp*LZ7J6Hz@XnJzD!Yh|S8p2O($2>A4hbpW{8?#WM`uJG>?}
zwkDF3dimqejl$3uYoE7&pr5^f4QP-5TvJ;5^M?ZeJM8ywZ#Dm`kR)tpYieQU;t2S!
z05~aeOBqKMb+`vZ2zfR*2(&z`Y1VROAcR(^Q7ZyYlFCLHSrTOQm;pnhf3Y@WW#gC1
z7b$_W*ia0@2grK??$pMHK>a$;J)xIx&fALD<l)xWrw5Dy*aYSf=56xEqI(KK*6}JO
z|D5c_`r$^kt(8|djppH$ScjR@Qtdb<5p0?Idz2NPZFAG>4)w=xlT=EzrwD!)1g$2q
zy8GQ+r8N@?^_tuCKVi*<IIQygq(j@u<*l52tWi7w(9x=0hIf8}iW5CL*{eVvB4dzp
z=GA1`rGehHr+{fIIreG+3w@P&w<I@K==K)uf59yCR$cF6>q_G*!#NxxY#hpaV~hF}
zF1xXy#XS|q#)`SMAA|46+UnJZ__lETDwy}uecTSfz69@YO)u&QORO~F^>^^j-6q?V
z-WK*o?XSw~u<mx%`B3S#@s@e8*;trh4)HpDuo~BMBejRf8L&?gb3j{btc;nI1<TpR
z@3Kx=i@|@!JT#7eBJa(mO6_Z$c7%>kjoIT9p6$6*OStr`=+;HrF#)p>*>e|gy0D9G
z#TN(VSC11^F}H#?^|^ona|%;xC<mN2ZJy-W_Uwn^>C!~H3~+a>vjyRC5MPGxFqkj6
zttv9I_fv+5$vWl2r8+pX<A#^M8H&t(q<;>P&^yudvLxP44;9XzUr&a$&`?VNhU^$J
z`3m68BAuA?ia*IF%Hs)@>xre4W0YoB^(X8RwlZ?pKR)rvGX?u&K`kb8XBs^pe}2v*
z_NS*z7;4%Be$ts_emapc#zKjVMEqn8;aCX=dISG3zvJP>l4zHdpUwARLixQSFzLZ0
z$$Q+9fAnVjA?7PqANPiH*XH~VhrVfW11#NkAKjfjQN-UNz?ZT}SG#*sk*)VUXZ1$P
zdxiM@I2RI7Tr043ZgWd3G^k56$Non@LKE|zLwBgXW#e~{7C{iB3&UjhKZPEj#)cH9
z%HUDub<p{T#1uLOSB!|bTLfCwHK3-vax8E4LNk2TrUlJ+F9&+JBm|TDz9STVm*4*`
z4Txhcr;PfR1}zY=ocJU06Vjh2F+k<|9vd>c0u@}dBz>4zU;sTluxBtCl!O4>g9ywc
z<cKNG8?#V`H3{jv#T94>hEiM-!|!C&LMjMNs6dr6Q!h{nvTrNN0hJ+w*h+EfxW=ro
zxAB%*!~&)uaqXyuh~O`J(6e!YsD0o0l_ung1rCAZt~%4R{#izD2jT~${>f}m{O!i4
z`#UG<UmIW4-NeY{Kd-?isY7|8ETQp&IFdvHP}mHTRRB=+ou!2&CMBES*oV|;j0_=*
zj4Z4XezKmL2QfBEt`^$FCtqsMlG&JRwJ$Xq%(O!3N@iSo`N{TVKbew54}9CXd$QQ{
za$0hl>biSh{L=FR`Q`e~9wrKHSj?I>eXHduB`;%TcCTYNG<)l@A%*Ld?PK=fJi}J?
z9T-|Ib8*r<yfQgM1~xipajrQadGmArx4qxf%+JrfUS;u62szTw(0HGIVk2lG5Y?AN
z8?3ewUg+e<)V6@CgL6e%?RjBym0BJn)Mw95a~#vl(j?W|>LE)v_3|1+Hqa!0ch>f%
zfNFz@o6r5S`QQJCwRa4zgx$7AyQ7ZTv2EM7ZQHh!72CFL+qT`Y)k!)|Zr;7mcfV8T
z)PB$1r*5rUzgE@y^E_kDG3Ol5n6q}eU2hJcXY7PI1}N=>nwC6k%nqxBIAx4Eix*`W
zch0}3aPFe5*lg1P(=7J^0ZXvpOi9v2l*b?j>dI%iamGp$SmFaxp<R9!A`K2g)}?00
z5iUcCgAEZwW5$j4<ZiO-lhSAjkchyp<)$%-R2u1GQRNjRQW5%L27fS6e5EZ__!(3%
zBYO6iIXx&FLZX4VO+?@fkmi1;!|F!_yjmt5Ru61BtLQ`S^K8}_U>Zod*TgYiyhF0=
za44lXRu%9MA~QWN;YX@8LM32BqKs&W4&a3ve9C~ndQq>S{zjRNj9&&8k-?>si8)^m
zW%~)EU)*$2YJzTXjRV=-dPAu;;n2EDY<Yt3;43!NQJ2tmZaOv-5c%W=-ywct0Y5O5
zX)Sf`ZMxv0v&aVMf?U_mn4?IYgV2bw+0a-;nM&O}lH#PebP2A*y@uH1lkVYED?brn
z1=dSkz(8-KbYa{i1v@JN33EtLLhG2N^t)1=y)YC`G@xG(WUI{GuNK1|#&2Y;kA|yQ
z^@}oa78<gsM5w?=wwxnSi78bJdC1=;M<!$&|CmVEAUQSPvbK%*25TN&=Jqqwz2Yu8
zdxfg2#iS5@kl`plcHwexZuf(mU>b=6XFyz`D0f2#29(mUX}*5~KU3k>$LwN#OvBx@
zl6lC>UnN#0?mK9*+*DMiboas!mmGnoG%gSYeThXI<=rE(!Pf-}oW}?yDY0804dH3o
zo;RMFJzxP|srP-6ZmZ_peiVycfvH<`WJa9R`Z#suW3Kr<HUL~bw#TFa3n4kJ1y0uO
zDeOdM^41)A>I*>cECF(_CB({ToWXSS18#3%vihZZJ{BwJPa?m^(6xyd1(oidUkrOU
zlqyRQUbb@W_C)5Q)%5bT3K0l)w(2cJ-%?R>wK35XNl&}JR&Pn*laf1M#|s4yVXQS#
zJvkT$HR;^3k{6C{E+{`)J+~=mPA%lv1T|r#kN8kZP}os;n39exCXz^cc{AN(Ksc%}
zA561&OeQU8gIQ5U&Y;Ca<DPNfVa;8}E-Ta9>1TatzG`K6*`9LV<|GL-^=qg+nOx~6
zBEMIM7Q^rkuhMtw(CZtpU(%JlBeV?KC+kjVDL34GG1sac&6(XN>nd+@Loqjo%i6I~
zjNKFm^n}K=`z8EugP20fd_%~$Nfu(J(sLL1gvXhxZt|uvibd6rLXvM%!s2{g0oNA8
z#Q~RfoW8T?HE{ge3W>L9bx1s2_L83Odx)u1XUo<`?a~V-_ZlCeB=N-RWHfs1(Yj!_
zP@oxCRysp9H<ry}>8Yy@6qIc69TQx(1P`{iCh)8_kH)_vw1=*5JXLD(njxE?2vkOJ
z>qQz!*r`>X!I69i#1ogdVVB=TB40sVHX;gak=fu27xf*}n^d>@*f~qbtVMEW!_|+2
zXS`<ESl@lgPcWDY98k09FUn5#Un(7e$=QJUXN00MubF6elsVML6R}{Yd(F!;60?8^
zWqxi)J?Y92YRvk1wn90yfa6~cWWOU`rFi_AXw}_at2yvVGTi}$wu;$dBdmZw9}o-h
zxG()uESmAWg|!fB!z{ca;8~&OL)Yx}HoP4@LTskVb*=<7HzRT!7j9j?A?pl&8pGYV
zHWdW9isKp(cfvbfl&-R%CB&YE&g}Q0*13G|7$)!psxiD>-E%v`_>(m2sQnc6+OA3R
z-6K{6$KZsM+lF&<ad6xo${yEclY+B!t~Vj#77=#-9zXr6C;V}Eqs9Pl+XXrBHo8Kr
znZ6YD#z^y(8G5AV#u=jEj*TWIj(Cqg<}rzmCkEo!s>sn~w4u_md6J#+FzqmtncY;_
z-Q^D=%LVM{<P2w8OW33)5V6<Ei?K*2GVV}=+xV0_Z}Och9MH*u1?@yzOxl%cAFNzG
zl)V#D9L{|KD(Tz^)F}TMZvP!yr0PxWpjk))H%~wbu?Q-$Mz|J)1ywwy|AxU?RUcy`
z{4a9akhgcV9Wb(Yd-eWEFJLQtju{yEdfDzU6!8Q~w9FAx_us<v#yGlZmK7>A0@VCf
zV9;?kF?vV}*=N@FgqC>n-QhKJD+IT7J!6llTEH2nmUxKiBa*DO4&PD5=HwuD$aa(1
z+uGf}UT40OZAH@$jjWoI7FjOQAGX6roHvf_wiFKBfe4w|YV{V;le}#aT3_Bh^$`Pp
zJZGM_()iFy#@8I^t{ryOKQLt%kF7xq&ZeD$$ghlTh@bLMv~||?Z$#B2_A4M&8)PT{
zyq$BzJpRrj+=?F}zH+8XcPvhRP+a(nnX2^#LbZqgWQ7uydmIM&FlXNx4o6m;Q5}rB
z^ryM&o|~a-Zb20>UCfSFwdK4zfk$*~<|90v0=^!I?JnHBE{N}74iN;w6XS=#79G+P
zB|iewe$kk;9^4LinO>)~KIT%%4Io6iFFXV9gJcIvu-(!um{WfKAwZDmTrv=wb#|71
zWqRjN8{3cRq4Ha2r5{tw^S>0DhaC3m!i}tk9q08o>6PtUx1GsUd{Z17<J{i^DG}Z)
zlC0vdur~2VdU-K7>FH45rIoS+oym1>3S0B`>;uo``+ADrd_Um+8s$8V6tKsA8KhAm
z{pTv@zj~@+{~g&ewEBD3um9@q!23V_8Nb0_R#1jcg0|MyU)?7ua~tEY63XSvqwD`D
zJ+qY0Wia^BxCtXpB)X6htj~*7)%un+HYgSsSJPAFED7*WdtlFhuJj5d3!h8gt6$(s
ztrx=0hFH8z(Fi9}=kvPI?07j&KTkssT=Vk!d{-M50r!TsMD8fPqhN&%(m5LGpO>}L
zse;sGl_>63FJ)(8&8(7Wo2&|~G!Lr^cc!uuUBxGZE)ac7Jtww7euxPo)MvxLXQXlk
zeE>E*nMqAPwW0&r3*!o`S7wK&078Q#1bh!hNbAw0MFnK-2gU25&8R@@j5}^5-kHeR
z!%krca(JG%&qL2mjFv3<p4i&LWsDupV*;=lI>80Gvb*eTLllTaIpVr3$gLH2e3^xo
z=qXjG0VmES%OXAIsOQG|>{aj3fv+ZWdoo+a9tu8)4AyntBP>+}5VEmv@WtpTo<-aH
zF4C(M#dL)MyZmU3sl*=TpAqU#r>c8f?-zWMq`wjEcp^jG2H`8m$p-%TW?n#E5#Th+
z7Zy#D>PPOA4|G@-I$!<aW((R7ZjVOb^>#Yees_9Ku{i_Y%GQyM)_*u^nl+bXMH!f_
z8>BM|OTex;vYWu`AhgfXFn)0~--Z<Y7zSjyb-U>7E0WR-v|n$XB-NOvjM156WR(eu
z(qKJvJ%0n+%+%YQP=2Iz-hkgI_R>7+=)#FWjM#M~Y1xM8m_t8%=FxV~Np$BJ{^rg9
z5(BOvYfIY{$h1+IJyz-h`@jhU1g^Mo4K`vQvR<3wrynWD>p{*S!kre-(MT&`7-WK!
zS}2ceK+{KF1yY*x7FH&E-1^8b$zrD~Ny9|9(!1Y)a#)*zf^Uo@gy~#%+*u`U!R`^v
zCJ#N!^*u_gFq7;-XIYKXvac$_=booOzPgrMBkonnn%@#{srUC<((e*&7@YR?`CP;o
zD2*OE0c%EsrI72QiN`3FpJ#^Bgf2~qOa#PHVmbzonW=dcrs92>6#{pEnw19AWk%;H
zJ4uqiD-dx*w2pHf8&Jy{NXvG<G2fGidx4PcnQN8MUPd!ajb`-x1=|}Vd6)Mu9~Gr9
z=>F^Gg!ungr2StHpMQ<HDiupjOyQqCAy%tIRvYmC;RHb%7B;acCsA@g{nr=m>K5^+
zEmDjjBonrrT?d9X;BHSJeU@lX19|?On)(Lz2y-_;_!|}QQMsq4Ww9SmzGkzVPQTr*
z)YN>_8i^rTM>Bz@%!!v)UsF&Nb{Abz>`1msFHcf{)Ufc_a-mYUPo@ei#*%I_jWm#7
zX01=Jo<@6tl`c;P_uri^gJxDVHOpCano2Xc5jJE8(;r@y6THDE>x*#-hSKuMQ_@nc
z68-JLZyag_BTRE(B)Pw{B;L0+Zx!5jf%z-Z<YclUMBp`|xB#6)P?afpE7|oxiGTU7
z%y?w}o-6E+hmfhV=rZIPn4oR~vu0gm`B1LGNrpzZ5rTi+iLW3pn3H(3l>qug*og@^
zs{y3{Za(0ywO6zYvES>SW*cd4gwCN^o9KQYF)Lm^hzr$w&spGNah6g>EQBufQCN!y
zI5WH$K#67$+ic{yKAsX@el=SbBcjRId*cs~xk~3BBpQs<W0Q3`moZ~`0b7lKm0C%I
zUYYh7HI*;3UdgNIK;LpP(#{(_<BHsfFF;nfs~1P?Gp4SYh{kkp!B`>f%IsoPG)LGs
zdK0_rwz7?L0XGC^2$dktLQ9qjwMsc1rpGx2Yt?zmYvUGnURx(1k!kmfPUC@2Pv;r9
z`-Heo+_sn+!QUJTAt;uS_z5SL-GWQc#pe0uA+^MCWH=d~s*h$XtlN)uCI4$KDm4L$
zIBA|m0o6@?%4HtAHRcDwmzd^(5|KwZ89#UKor)8zNI^EsrI<q&1*MkAAa2rAmDnpd
zM2X7DdB(hR7ImdXc}V=nO{o7COdb?F1m#nJfcgUKV%Ly#@RiBcB*;zbcWku*Rf29K
zHdWVo#O`553R{z~=1%N~tCK8qpoo3-lw7}ZiCLOk4v`E;S|bMOaYWRegFScH_B4>k
z1QLDBnNU1!PpE3iQg9^HI){x7QXQV{&D>2U%b_II>*2*HF2%>KZ>bxM)Jx4<SdmPW
zl{^c0P>}|CCEa`186nD_B9h`mv6l45vRp*L+z_nx5i#9KvHi>rqxJIjKOeG(5lCeo
zLC|-b(JL3YP1Ds=t;U!Y&Gln*Uwc0TnDSZCnh3m$N=xWMcs~&Rb<Jjj;hQe6jNpQJ
z@svSId4GTd`3AIlx30VX&Nd=&tFTxkGVfkn0(`nURlV#P-&55<E_QWk4RhX&F@PN<
z+`BhLo{Y~`BY-dd*(adMg9Ap(CzwWnR;pBnZ!I7Se)bG?xf%gChkBDM?YUaVaF&mp
z4<&?q3$9yAX}xs>?w}l51ubtz=QUZsWQhWOX;*AYb)o(^<$zU_v=cFwN~ZVrlSLx|
zpr)Q7!_v*%U}!@PAnZLqOZ&EbviFbej-GwbeyaTq)HSBB+tLH=-nv1{MJ-rGW%uQ1
znDgP2bU@}!Gd=-;3`KlJYqB@U#Iq8Ynl%eE!9g;d*2|PbC{A}>mgAc<ZUVyw%frPJ
zN7{+qB73J=E!<sfbu2?%?_U`74k?Ujb)2@J-`j>8LK<69qcm)pi<uOj0|S_z{=`u+
z)GI|%Grst&fPD8oZr)MFejnidD@a-#<B3#%b#SMl|1L=Wk96ceg+zV_Q)lZhcQau(
zLu0%D+s)02<(KWJ`#JI{yy}#X2PuLWwFwd#AsUEJFNBmZ6bOH1%v4}<o@@i$lT3;T
z=ktv>u?`y~3K8zlZ1>~K_4T{%4zJG6H?6%{q3B-}iP_SGXELeSv*bvBq~^&C=3TsP
z9{cff4KD2ZYzkArq=;H(Xd)1CAd%byUXZdBHcI*%a24Zj{Hm@XA}wj$=7~$Q*>&4}
z2-<EqrttZ@QKQp>V62ek{rKhPvvB711`qtAy+q{f1yWuFDcYt}hP)Vd>G?;VTb^P4
z(QDa?zvetCoB_)iGdmQ4VbG@QQ5Zt9a&t(D5Rf#|hC`LrONeUkbV)QF`ySE5x+t_v
z-(cW{S13ye9>gtJm6w&>WwJynxJQm8U2My?#>+(|)JK}bEufIYSI5Y}T;vs?rzmLE
zAIk%;^qbd@9WUMi*cGCr=oe1-nthYRQlhVHqf{ylD^0S09pI}qOQO=3&dBsD)BWo#
z$NE2Ix&L&4|Aj{;ed*A?4z4S!7o_Kg^8@%#ZW26_F<>y4ghZ0b|3+unIoWDUVfen~
z`4`-cD7qxQSm9h<NO70xW2WQA-R<)q*l*5k&3y!NF~)rA{com)2orL%sAKUU#1Dcp
zoc2HI>F-;6WvCbu$t5r$LCOh}=`k1(W<&bG-xK{VXFl-cD%^Q*x-9eq;k8FzxAqZB
zH@ja_3%O7XF~>owf3LSC_Yn!iO}|1Uc5uN{Wr-2lS=7&JlsYSp3IA%=E?H6JNf()z
zh>jA>JVsH}VC>3Be>^UXk&3o&rK?eYHgLwE-qCHNJyzDLmg4G(uOFX5g1f(C{>W3u
zn~j`zexZ=sawG8W+|SErqc?uEvQP(YT(YF;u%%6r00FP;yQeH)M9l+1Sv^yddvGo-
z%>u>5SYyJ|#8_j&<HRV#h}90d=<D9bJ$bmnWPbtr>%h3#auTJ!4y@yEg<(wp#(~NH
zXP7B#sv@cW{D4Iz1&H@5wW(F82?-JmcBt@G<R&5ZqJZ${!H$`uNtw)#4NAUBu7`B1
z>w1}WK+>FRXnX(8vwSeUw{3i%HX6-pvQS-~Omm#x-udgp{=9#!>kDiLwqs_7fYy{H
z)jx_^CY?5l9#fR$wukoI>4aETnU>n<$UY!JDlIvEti908)Cl2Ziyjjtv|P&&_8di>
z<^amHu|WgwMBKHNZ)t)AHII#SqDIG<yo-WOPN&wvnO(_8-5PCfJX}35$eJldPfG22
ztDY_MT2V>TAd<(I0Q_LNPk*?UmK>C5=rIN^gs}@65VR*!J{W;wp5|&aF8605*l-Sj
zQk+C#V<#;=Sl-)hzre6n0n{}|F=(#JF)X4I4MPhtm~qKeR8qM?a@h!-kKDyUaDrqO
z1xstrCRCmDvdIFOQ7I4qesby8`-5Y>t_E1tUTVOPuNA1<D8n2Zmq6@fD2bCb>De9|
z8{B0NBp*X2-ons_BNzb*Jk{cAJ(^F}skK~i;p0V(R7PKEV3bB;syZ4(hOw47M*-r8
z3qtuleeteUl$FHL$)LN|q8&e;QUN4(id`Br{rtsjpBdriO}WHLcr<;aqGyJP{&d6?
zMKuMeLbc=2X0Q_qvSbl3r?F8A^oWw9Z{5@uQ`ySGm@DUZ=XJ^mKZ-i<wTH#vg5dYq
zPj$jYJ(2di4pR$XX;q05i&7tJxLg^P<96Rnkm>pJtmiXjcu<%z?Nj%-1QY*O{NfHd
z=V}Y(UnK=f?xLb-_~H1b2T&0%O*2Z3bBDf06-nO*q%6uEaLs;=omaux7nqqW%tP$i
zoF-PC%pxc(ymH{^MR_aV{@fN@0D1g&zv`1$Pyu3cvdR~(r*3Y%DJ@&EU?EserVEJ`
zEprux{EfT+(Uq1m4F?S!TrZ+!AssSdX)fyhyPW6C`}ko~@y#7acRv<fn`H`JS|B0R
z5LL%=k13ys;X;W8jiSn(2a6?dnHm2tId}WyP!k@?3YNemFZbv!6Q(vR)QkLt_!cQ=
zJ`W5Gj&g=U`gp6uo@Jix<W6Ar1vX25KRDX9-<=6?9mn1w?{yQn>iE(4>moNe$HXzf
zY@@fJa~o_r5nTeZ7ceiXI=k=ISkdp1gd1p)J;SlRn^5;rog!MlTr<<6-U9|oboRBN
zlG~o*dR;%?9+2=g==&ZK;Cy0pyQFe)x!I!8g6;hGl`{{3q1_UzZy)J@c{lBIEJVZ&
z!;q{8h*zI!kzY#RO8z3TNlN$}l;qj10=}du!tIKJs8O+?KMJDoZ+y)Iu`x`yJ@krO
zwxETN$i!bz8{!>BKqHpPha{96eriM?mST)_9Aw-1X^7&;Bf=c^?17k)5&s08^E$m^
zRt02U_r!99xfiow-XC~Eo|Yt8t>32z=rv$Z;Ps|^26H73JS1Xle?;-nisDq$K5G3y
znR|l8@rlvv^wj%tdgw+}@F#Ju{SkrQdqZ?5zh;}|IPIdhy3ivi0Q41C@4934naAaY
z%+otS8%Muvrr{S-Y96G?b2j0ldu1&coOqsq^vfcUT3}#+=#;fii6@M+hDp}dr9A0Y
zjbhvqmB03%4jhsZ{_KQfGh5HKm-=dFxN;3tnwBej^uzcVL<O?AG~Dz6u1X+?B>rrs
z<EHIiN@*PjI2~=CnydPB+9!1Lx@K!Gi*Jk&&B{qJD$~d2iu5%r5o;C{N2b|JE1&-e
zxh$%+k_cbUuGwEE;r}{E{*RU5f69?^`VNj?(rtc|uXc-wxr3vVqO-w&{QOU$?v#q=
zU+V3TVrnX?3FS7o9%UNp+F#n?V$_cn;z@v!f%@bjmzFh}8_72~9)o;-V*2j1n}SHk
zb)X>>eFP-jb#~LE$qTP9JJ;#$nVOw%&;}y>ezA6&i8S^7YK#w&t4!A36Ub|or)MJT
z^GGrzgcnQf6D+!rtfuX|Pna`Kq*ScO#H=de2B7%;t+Ij<>N5@(Psw%>nT4cW338WJ
z>TNgQ^!285hS1JoHJcBk;3I8%#(jBmcpEkHk<Zl$0EpA&>QDk%!4ygr;Q2a%0T==W
zT#dDH>hxQx2E<dX8b?@yg((2bG4D9^cs+I(p027?&|zIutZXiNzr$4Uhv+GDyDk9-
zaLX;na|6W_bN!P0!eb2=3YF>8+jE~jFY$FligkN&{vUZeIn*#I_Ca!l&;yf){eghi
z>&?fXc-C$z8ab$IYS`7g!2#!3F@!)cUquAGR2oiR0~1pO<$3Y$B_@S2dFwu~B0e4D
z6(WiE@O{(!vP<(t{p|S5#r$jl6h;3@+ygrPg|bBDjKgil!@Sq)5;rXNjv#2)N5_nn
zuqEURL>(itBYrT&3mu-|q;soBd52?jMT75cvXYR!uFuVP`QMot+Yq?CO%D9$Jv24r
zhq1Q5`FD$r9%&}9VlYcqNiw2#=3dZsho0cKKkv$%X&gmVuv<g|(w$he1~O|8$Sf%r
zu2S#`%@&%i+rt`152do5Q7PAq9>&S__zyz@0zmZdZI59~s)1xFs~kZS0C^271hR*O
z9nt$5=y0gjEI#S-iV0paHx!|MUNUq&$*zi>DGt<#?;y;Gms|dS{2#wF-S`G3$^$7g
z1#@7C65g$=4Ij?|Oz?X4=zF=QfixmicIw{0oDL5N7iY}Q-vcVXdyQNMb>o_?3A?e6
z$4`S_=6ZUf&KbMgpn6Zt>6n~)zxI1>{HSge3uKBiN$01WB9OXscO?jd!)`?y5#%yp
zJvgJU0h+|^MdA{!g@E=dJuyHPOh}i&alC+cY*I3rjB<~DgE{`p(FdHuXW;p$a+%5`
zo{}x#Ex3{Sp-PPi)N8jGVo{K!$^;z%tVWm?b^oG8M?Djk)L)c{_-`@F|8LNu|BTUp
zQY6QJVzVg8S{8{Pe&o}Ux=ITQ6d42;0l}OSEA&Oci$p?-BL187L6rJ>Q)aX0)Wf%T
zneJF2;<-V%-VlcA?X03zpf;wI&8z9@Hy0BZm&ac-Gdtgo>}VkZYk##OOD+nVOKLFJ
z5hgXAhkIzZtCU%2M#xl=D7EQPwh?^gZ_@0p$HLd*tF>qgA_P*dP;l^cWm&iQSPJZE
zBoipodanrwD0}}{H#5o&PpQ<LaEnZ8#Jva^_4H8uBoRdO{scuj(&2$o9r%}p6aIkM
zIgN>pCh61auqlckZq2_Eg__8;G-CwyH#h1r0iyD#Hd_$WgM89n+ldz;=b!@pvr4;x
zs|YH}rQuCyZO!FWMy%lUyDE*0)(HR}QEYxIXFexCkq7SHmSUQ)2tZM2s`G<9dq;Vc
ziNVj5hiDyqET?chgEA*YBzfzYh_RX#0MeD@xco%)ON%6B7E3#3iFBkPK^P_=&8$pf
zpM<0>QmE~1FX1>mztm>JkRoosOq8cdJ1gF5?%*zMDak%qubN}SM!dW6fgH<*F>4M7
zX}%^g{>ng^2_xRNGi^a(epr8SPSP>@rg7s=0PO-#5*s}VOH~4GpK9<4;g=+zuJY!&
ze_ld=ybcca?dUI-qyq2Mwl~-N%iCGL;LrE<#N}DRbGow7@5wMf&d`kT-m-@geUI&U
z0NckZmgse~(#gx;tsChgNd|i1Cz$quL<EK{n`fg<ef00Gc^+ZB*9RnZKY_dN{0;pr
zkWwN1+g+D*qwS_<f<$=X3|W|&KY>>qLzEO}<kBKUnp-Vc#!UE*%7|=&I>ndg&Pg4f
zy`?VSk9X5&Ab_TyKe=oiIiuNTWCsk6s9Ie2UYyg1y|i}B7h0k2X#YY0CZ;B7!dDg7
z_a#pK*I7#9-$#Iev5BpN@xMq@mx@TH@SoNWc5dv%^8!V}nADI&0K#xu_#y)k%P2m~
zqNqQ{(fj6X8JqMe5%;>MIkUDd#n@J9Dm~7_wC^z-Tcqqnsfz54jPJ1*+^;SjJzJhG
zIq!F`Io}+fRD>h#wjL;g+w?Wg`%BZ{f()%Zj)sG8permeL0eQ9vzqcRLyZ?IplqMg
zpQaxM11^`|6%3hUE9AiM5V)zWpPJ7nt<!aS$)Ag9!M8rzwQ;kFq)-anCmSw}H|pNi
z1DG+Eo|ayui<(#}-c}*`$y+V=;K3oVwNIAeNA$5NJixmY%<JSek+-oOo5tNAkhLc&
zt5dV7XTB@OjUl2Ch+yh~f#K<2mUpA{O&5ZPXosBtdagQyUlxskn+z$IXU+<j|INKo
zcEHJQD8FypW?SK@Gvo<)^WSU?n5FaM5BqopG<geq*XFt^=*-apkSL;&Nb=d|Z}AlX
zI1BYcT|AkDlfpJDbt)V%JJlF1Q&C<(C3Ka0gw?-r{G%7x1VdE;vTO^m6Z2=l`p=xv
z>*^FDga?ZP!U1v1aeYrV2Br|l`J^tgLm;~%gX^2l-L9L`B?UDHE9_+jaMxy|dzBY4
zjsR2rcZ6HbuyyXsDV(K0#%uPd<!rL4+ciDxHS?tqBRQXah|;FqtzvBGPqLsbqRr#w
z<Sk{f80^}(X>#<^V%@9c7{6Qd_kQEZL&;z_Jf+eabr)NF%@Ulz_a1e(qWqJC$tTC!
zwF&P-+~VN1Vt9OPf`H2N{6L@UF@=g+xCC_^^DZ`8jURfhR_yFD7#VFmklCR*&qk;A
zzyw8IH~jFm+zGWHM5|EyBI>n3?2vq3W?aKt8bC+K1`YjklQx4*>$GezfU%E|>Or9Y
zNRJ@s<XJ4A2dKt<$dy>(>L{WBXdNiJiL|^In*1VA`xiE#D)%V+C;KuoQi{1t3~4*8
z;tbUGJ2@2@$XB?1!U;)MxQ}r67D&C49k{ceku^9NyFuSgc}DC2pD|+S=qLH&L}Vd4
zM=-UK4{?L?xzB@v;qCy}Ib65*jCWUh<P9`3(_wR{5tM}<KF>(FVc&r<v}^+-5p<;w
zf$AZgR|M~k(PTz1ruoVo&&H@ShaJ#EI4z2ihN6+wwf-g24YI@{VHdfzxlG@Cu1%y&
zv=gon4}aCn2sYsqw`N&FS$C=hE5^4n6}_1OROmiwSWO=9aanpIJGd6PgT<JJ3l@~T
zulI*30S6*#`E<$|X4KdDOVnF*8eP)SZ{EvqpIF4jj28iJ`>g|+KnopG`%<cjXx(}m
zDhDiVgS0DlIj1Y(nID2rVC(-Ff?WK3+1dI^tKa_CpH}JL4=RHCj>cb>t;RNv=1%4=
z#)@CB7i~<xD6pOWmxlBI-D;rt#e(^Neweg46<HNE`-Q8>$$JDM>q@4ll8{Ja5Rsq0
z$^|nRac)f7oZH^=-VdQldC~E_=5%JRZSm!z8TJocv`w<_e0>^teZ1en^x!yQse%Lf
z;JA5?0vUIso|MS03y${dX19A&bU4wXS~*T7h+*4cgSIX1<eZeql*kC#k-NksnG0*_
zSGXV>1EB?XGiBS39hvWWuyP{!5AY^x5j{!c?z<}7f-kz27%b>llPq%Z7hq+CU|Ev2
z*jh(wt-^7oL`DQ~Zw+<nes=Wvy?R#{)hU{ia3tUl=s(&#GofelTggXVN<4<EBd%pW
zDsi$W)}HAdt`o&;qFTC*%1O}21We0;YNXPjso3_(&f;{avARd3xG$>GMH}V*ndCc~
zr>WVQH<LLyB?ms=)Qi0+X1}IqM%QU2`}JM0#$koA>JQ8ZqF^<u)IDEkib<fQBVR``
zBf(aJ;7~!Bdf&&+OV4ffpJUPa{Jd7|B7n^+7qsw6?sV;|?<N|{M<1+hXoj?@|3^JE
z)m`|Q2GIoJm>A7sH{N5~PbeDihT$<E0@?NqkY7B>;tUP`OwWn=j6@L+!=T|+ze%YQ
zO+|c}I)o_F!T(^YLygYOTxz&PYDh9DDiv_|Ewm~i7|&Ck^$jsv_0n_}q-U5<VG|f6
zOs<^m$Xm>|_1>*L44)nt!W|;4q?n&k#;c4wpSx5atrznZbPc;uQI^I}4h5Fy`9J)l
z7yYa7Rg~f@0oMHO;seQl|E@~fd|532lLG#e6n<?_ax(rW8N5_Gb^WP~^4V1=$UFsX
zNI2&wS@u1CdOkPQfKCu(&<tEJ)hK|`tf)f@l2h4Q*%_!cdV`9>#vXrfdh~?NP){lZ
z&3-33d;bUTEAG=!4_{YHd3%GCV=WS|2b)vZgX{JC)?rsljjzWw@Hflbwg3kIs^l%y
zm3fVP-55Btz;<-p`X(ohmi@3qgdHmwXfu=gExL!S^ve^Msi<ORqbsqFVB;6Utm>mP
zNCBV>2>=BjLTobY^67f;8mXQ1YbM_N<rEN<7n?tG55}lyZdU}NVxTF=%^+z>A3R^s
z{zhY+5@9iYKMS-)S>zSCQuFl!Sd-f@v%;;*fW5hme#xAvh0QPtJ##}b>&tth$)6!$
z0S&b2OV-SE<|4Vh^8rs*jN;v9aC}S2EiPKo<g-o%Z)V&dOU@7g7EN()e30oRHq1Hj
zae3;xpA+O=62|3plwyE@JJ4jA=a!}BCg6($aFt&ID9&+w?$+9<lwXGX22{JtMTp31
zy;Vtwko}t))OGL(HcR4E0YBLyf?SQjJe)mSmNW9@!kaZAsF#Y?z#aqp;D1E-HY;3`
z>(G&<6C|%$JQ{;JEg-L|Yob*<-`z?AsI(~U(P>cC=1V$OET<Eh@-1QlC_z}>G$7i#
zG#^QwW|HZuf3|X|&86lOm+M+BE>UJJS<KI4FEwf{E!nInPboEh%N6ef8$TpBbiKb?
zKsp#T(JIi}k0j6e5r*>SAAij<fio-i3?p3H-sa7MSEpJ_5#VfgXx65pr%vDBp%z7r
zsA-Q4A$4k!vvFOwgfje~G^Qb~j4Rgh<M(o@V0uBZwdJhXnRpVD5>knNp*eyLUq=Au
z7&aqR(x8h|>`&^n%p#TPc<zhIEW{4G1XPwm4~`;7YwNNs)mU&qlb4(j6a>C@8@PG%
zM&7k6IT*o-NK61P1XGeq0?{8kA`x;#O+|7`GTcbmyWgf^JvWU8Y?^7hpe^85<a4!`
z!TYiE-CO~WQjvPJOfK#<y@Wmd)D*~hr3ydI^kW-VT-6-I3c+mzdw|+;nP&Et;^=B)
zfbrsHu}e&<daK!gC(x=2Bhr8>_VuRq7yS~8uZ=Cf%W^OfwF_cbBhr`TMw^MH0<{3y
zU=y;22&oVlrH55eGNvoklhfPM`bPX`|C_q#*etS^O@5PeLk(-DrK`l|P*@#T4(kRZ
z`AY7^%&{!mqa5}q%<=x1e29}KZ63=O>8<Lf?05av_NumIYB^@XvSz)^6E}D`O1IEP
zn#`mp%nuo+G$lHa>9Q)yO4G@0USgbGhR#r~OvWI4+yu4*<p7_l-Shz>F8o`f?EG~x
zBCEND=ImLu2b(FDF3sOk_|LPL!wrzx<ar9u{M5Ht**W7-^TwKEEnR%L1L4<1#+x=7
z?A9uD0;+{;={gzo3gTO^``~rW9lEN7Y>_G-?&^EUof1C~A{feam{2&eAf@2GWem7!
z|LV-lff1Dk+mvTw@=*8~0@_Xu@?5u?-u*r8E7>_l1JRMpi{9sZqYG+#Ty4%Mo$`ds
zsVROZH*QoCErDeU7&=&-ma>IUM|i_Egxp4M^|%^I7ecXzq@K8_oz!}cHK#>&+<I`V
z0J$%}%_bCZ`uG4zJx_B>$E4rs2H8Fyc)@Bva?(KO%+oc!+3G0&Rv1<g*|~0#-rmJt
z#15-ESH3r^#1Wo70Az~l87NDd-EX!|q%>cP)e9u_Y|dXr#!J;n%T4+9rTF>^m_4X3
z(g+$G6Zb@RW*J-IO;HtWHvopoVCr7zm4*h{rX!>cglE`j&;l_m(FTa?hUpgv%LNV9
zkSnUu1TXF3=tX)^<E=swga+@_NOW=^zl;~ayF!!ph!-PL<Yg_F<FWy=pBUe3ly^^i
zsh2x9E$goEYlhZUV_l>}kDZk|AF%7FmLv6sh?XCORzhTU%d>y4cC;4W5mn=i6vLf2
ztbTQ8RM@1gn|y$*jZa8&u?yTOlNo{coXPgc%s;_Y!VJw2Z1bf%57p%kC1*5e{bepl
zwm?2YGk~x=#69_Ul8A~(BB}>UP27=M)#aKrxWc-)rLL+97=>x|?}j)_5ewvoAY?P|
z{ekQQbmjbGC%E$X*x-M=;Fx}oLHbzyu=Dw>&WtypMHnOc92LSDJ~PL7sU!}sZw`MY
z&3jd_wS8>a!si2Y=<F}g&Sf6{aKvftX#GKASHm|#FN8^Y<JT){wym=hJH%W1TG~50
z!}~-R<Ll4XR4%i3Dvos()=d|UicS%cl3!zN&!i??MHOv)o4n(D3?)>ijCo(rMnAqq
z-o2uzz}Fd5wD%MAMD*Y&=Ct?|B6!f0jfiJt;hvkIyO8me(u=fv_;C;O4X^vbO}R_%
zo&Hx7C@EcZ!r%oy<O&32XuX7-K^N*%xxZI_Evvx;Ow>}|S-8CvPR?Ns0$j`FtMB;h
z`#0Qq)+6Fxx;RCVnh<?2=#PkDIv$AIDNnSVfS59UFO=}Ne{@?#Jjq#PzG_(MuVtbC
zzt*wR=8leEB=et%{A<kopR#yW(m#mi$mc?GGL0q;WGPuV<SJDC9`ko$gg!LNSUroq
zjYg^Q^sO}$7ys0!{2Cu*Tq1cfuN&Z}yhu}*6pdV3Y4L?D_5??tyB$aK&$ssn%w8-U
zlUO~s-Lm!}%+6+Ns+4O;^Got=GjQW2tPPDnS}7A%_0&UPs@-x4wpKeW#@<~BtJzu#
z4^w_W2m*f&c!d<2LF}W?>wp`%>0H4hk(>Kd!(Y}>U+Tr_6Yp?W%jt_zdusOcA$pTA
z(4l9$K=VXT2ITDs!OcShuUlG=R6#x@t74B2x7Dle%LGwsZrtiqtTuZGFUio_Xwpl}
z=T7jdfT~ld#U${?)B67E*mP*E)XebDuMO(=3~Y=}Z}rm;*4f~7ka196QIHj;JK%DU
z?AQw4I4ZufG}gmfVQ3w{snkpkgU~Xi;}V~S5j~;No^-9eZEYvA`Et=Q4(5@qcK=Pr
zk9mo>v!%S>YD^GQc7t4c!C4*qU76b}r(hJhO*m-s9OcsktiXY#O1<<Xx}%D;DrM2u
zgR`7O#hM$qpaiXf!xs98k~~PmrS%$$H?RY`A<I1P$hIO_|76iq;X#Ay3~3zyy>OoH
z#J^Y@1A;nRrrxNFh?3t@Hx9d>EZK*kMb-oe`2J!gZ;~I*QJ*f1p93>$lU|4qz!_zH
z&mOaj#(^uiFf{*Nq?_4&9ZssrZeCgj1J$1VKn`j+bH%9#C5Q5Z@9LYX1mlm^+jkHf
z+CgcdXlX5);Ztq6OT@;UK_zG(M5sv%I`d2(i1)>O`VD|d1_l(_aH(h>c7fP_$LA@d
z6Wgm))NkU!v^YaRK_IjQy-_+>f_y(LeS@z+B$5be|FzXqqg}`{eYpO;sXLrU{*fJT
zQHUEXoWk%wh%Kal`E~jiu@(Q@&d&dW*!~9;T=gA{{~NJwQvULf;s43Ku#A$NgaR^1
z%U3BNX`J^YE-#2dM*<VS$mHL0>Ov*CzGdP9^`iI&`tmD~Bwqy4*N=DHt%RycykhF*
zc7BcXG28Jvv(5G8@-?OATk<VlF#JF;VFj42oue$8vk#utzT9sSL9u~Kb&>6|l{Rg1
zwdU2Md1Qv?#$EO3E}zk&9>x1sQiD*sO0dGSUPkCN-gjuppdE*%*d*9tEWyQ%hRp*7
zT`N^=$PSaW<j2^)OgKnb4YTq=_%Tjodhp-|4g<7VcoD4;JVfZ20F>D>f;h@$d2Ca7
z<IsxIDw8g%14f<dxXJ?1k%K10l;ryNsZhk|UHjR=E>8bNsm14sdOS%FQhMn9yC83$
z-YATg3X!>lWbLUU7iNk-`O%W8MrgI03%}@6l$9+}1KJ1cTCiT3>^e}-cTP&aEJcUt
zCTh_xG@Oa-v#t_UDKKfd#w0tJfA+Ash!0>X&`&;2%qv$!Gogr4*rfMcKfFl%@{ztA
zwoAarl`DEU&W_DUcIq-{xaeRu(ktyQ64-uw?1S*A>7pRHH5_F)_yC+2o@+&APivkn
zwxDBp%e=?P?3&tiVQb8pODI}tSU8cke~T#JLAxhyrZ(yx)>fUhig`c`%;#7Ot9le#
zSaep4L&sRBd-n&>6=$R4#mU8>T>=pB)feU9;*@j2kyFHIvG`>hWYJ_yqv?Kk2XTw`
z42;hd=hm4Iu0h{^M>-&c9zKPtqD>+c$~>k&Wvq#>%FjOyifO%Ro<b$&RjUsqT|Wxj
zW2lbJ(T7~%nc=aoD7AV4774WoR(WT%>Fgh*XW$%Hz$y2-W!@W6+rFJja=pw<H_#)8
zu5}XKw&Jv1p26(TLOG(jn&Xg^Fg`(UmF1jhtZr7y5Twax${5fPdi{qlX~!&gaQPQR
zO8yoi!vB8l_fLrY*TzJV(*GIHRVF%6<NX3fw$P-}4iA-xi%?ET2a~c8yT6o^8IS)O
z(QWPR+=SfhL7|C5&wSt(bfX<xvEorf4uv!@n!1=wPr7!q>-u_s0O3WMVgLb<CumOd
zDZ)7wYC^fq&#tZIt0XjOlVP|ml_~l+r!kAYsI5fTkua54YNeW0gQ&G8mf;ERufn<W
zQi9ayS({yq=ayz=&~7T3SIEvwJ^+7+2beaE1?b{7i2(J>&CrCQ)8I^6g!iQj%a%#h
z<~<0S#^NV4n!@tiKb!OZbkiSPp~31?f9Aj#fosfd*v}j6&7YpRGgQ5hI_eA2m+Je)
zT2QkD;A@crB<HIzTi?5d7<?fe`P<Q92mz?7Kt;dSgVi~LNFwz&<yssPZq9y>zA>7T
zw4o1MZ_d$)puHvFA2J|`IwSXKZyI_iK_}FvkLDaFj^&6}e|5@mrHr^prr{fPVuN1+
z4=9}DkfKLYqUq7Q7@qa$)o6&2)kJx-3|go}k9HCI6ahL?NPA&khLUL}k_;mU&7GcN
zNG6(xXW}<sgS8zIjVOZ!{0Co080O~_R?WpP|KoC=7x0-Hu?jFf@GLGg&<YdOLrFny
zI6>(+a%IT80=-13-Q~sBo>$F2m`)7~<Thgw;&?`BbJK}_`@#bKwjlQ!R4KF$zn@YU
zB5bX9cR%17A{Am)F?&AnK^6@sI$*a?sltpm4)FVN)gkcucU+7MZBGoi6<qmPJ=Egc
zxp<#|NfpUai(zMd0lO_up>wjW&XKndrz8soC*br=F*A_>Sh_Y}2Mt!#A1~2l?|hj)
z9wpN&jISjW)?nl{@t`yuLviwvj)vyZQ4KR#mU-LE)mQ$yThO1oohRv;93oEXE8mYE
zXPQSVCK~Lp3hIA_46A{8DdA+rguh@98p?VG2+Nw(4mu=W(sK<#S`IoS9nwuOM}C0)
zH9U|6N=BXf!jJ#o;z#6vi=Y3NU5XT>ZNGe^z4u$i&x4ty^Sl;t_#`|^hmur~;r;o-
z*CqJb?KWBoT`4`St5}10d*RL?!hm`GaFyxLMJPgbBvjVD??f7GU9*o?4!>NabqqR!
z{BGK7%_}96G95B299eErE5_rkGmSWKP~590$HXvsRGJN5-%6d@=~Rs_68BLA1RkZb
zD%ccBqGF0oGuZ?jbulkt!M}{S1;9gwAVkgdilT^_AS`w6?UH5Jd=wTUA-d$_O0DuM
z|9E9XZ<SSp+loefIL6G9M*^0yf2%$Y7Xt!6el@rIQi=p2<f%`KaPVBkR9mBQR!FzE
z<ob{rKk_TD*oLO%oSPn+q}3sXY(Jnrn$U&usUaNN(>Fl$tZctd`Bq=OfI(cw4A)|t
zl$W<!?9%H$y6ECn?$C+NfY*HN(Da|-Qf(#&W874P^~Pl0B-g6Nf~2@~8$JsJe3REE
z5`PI+4%<)#4RHPSWm46I(V<pT7ZQbW{o=Kvx}6cx0}p+)m%$qG<DpwO2ku6$mK0$Z
zAsc{@4QUfJXZzPZfXq#rPyAAs75%Nc?0+#v{bwBgXPfsw#;A(_G^kV5Xs)y@1BHAF
zgPf;nr1beEy7Ysdgam{s6~pUyeHXc8bH~(Gz-`GBlvk3FME=*-+8!gJV;fbF@UY;J
z?JUo)7@F3$VC$LF1C%N#Ef_9$7g$WZ-mjCQB55T)<AE9Kq6wCK$*~-;V*yc9cjETD
z4O{?Sil&8d<WVKR_ck449`F{tikM8<S=`jwjDHzAOBz6uSNk-w5LJWl@}4>~3_RkP
zFA6wSu+^efs79KH@)0~c3Dn1nSkNj_s)qBUGs6q?G0vjT&C5Y3ax-seA_+_}m`aj}
zvW04)0TSIpqQkD@#NXZBg9z@GK1^ru*aKLrc4{J0PjhNfJT}J;vEeJ1ov?*KVNBy<
zXtNIY3TqLZ=o1Byc^wL!1L6#i6n(088T9W<_iu~$S&VWGfmD|wNj?Q?Dnc#6iskoG
z<gz?q1P)tgamdQ#70IQ58PSN12z$;a^9As1;`GqTD0KI8wjYIPb6@Z`<*?yu(eyVM
zph=IxX(dP}JU7A4DL+i~CQ70;at&mI%k$wQO|wY@K~({H(9D~JN}sY2C2XZZAyco@
zpBq0RE!Ng3N|ldogXwKD-#K<vo{RUurnEB|OkH9bO<iMmWJ<%6kM4ftcuKv5VSN{j
zlyWjh8wQ8hEQcOSrbsl%z-1!83ob~q-F^dT=~o6eT+@Fo4yp*M)7H|b+v!vKaR^5?
zd~$(*zu%qS3iL1p8EG>t^u26JqFnt=xjS-=|ACC%(=YQh{_a<pmh9*Pm2y*M8e^q_
z%`06NDg95vQB!-Q7R32QA7K>lLW1tk;+tz1ujzeQ--lEu)W^Jk>UmHK(H303f}P2i
zrsrQ*nEz`&{V!%2O446^8qLR~-Pl;2Y==NYj^B*j1vD}R5plk>%)GZSSjbi|tx>YM
zVd@IS7b>&Uy%v==*35wGwIK4^iV{31mc)dS^LnN8j%#M}s%B@$=bPF<JH+(OJe)<e
zS-6EZ2aG*Y(j7hgR55gB`7wuZ&m8<&?ghoO3P>I_ifcyPd4<H!)^d@xVsN2@A72aa
z=LGGEuw+Zq8#;W+D&oSv2amC>hilEWm71chIwfIR(-SeQaf20{;EF*(K(Eo+hu{}I
zZkjXyF}{(x@Ql~*yig5lAq7%>-O5E++KSzEe(sqiqf1>{Em)pN`wf~WW1PntPpzKX
zn;14G3FK7IQf!~n>Y=cd?=jhAw1+bwlVcY_kVuRyf!rSFNmR4fOc(g7(fR{ANvcO<
zbG|cnYvKLa>dU(Z<hSAJ^1c6kseQH%>9YP796`Au?gz)Ys?w!af`F}1#W>x_O|k9Q
z>#<6bKDt3Y<u?_%q#7-6?_$s8V`h}#zHm)N0*lj#ujOESlqnt!sxj0GgEZvCf%&BU
za=|26!Ct<JXh*i}1OyJ;)?W_Y5E$Yw2QYT;)^V(2*@BD(>}?KT2tmhU>H6Umn}J5M
zarILVggiZs=kschc2TKib2`gl^9f|(37W93>80keUkrC3ok1q{;PO6<B_@t(eTV)o
zJ4>HMbm{cZ^ROcT#tWWsQy?8qKWt<42BGryC(Dx>^ohIa0u7$^)V@Bn17^(VUgBD>
zAr*Wl6UwQ&AAP%YZ;q2cZ;@2M(QeYFtW@PZ+mOO5gD1v-JzyE3^zceyE5H?WLW?$4
zhBP*+3i<09M$#XU;jwi7>}kW~v%9agMDM_V1$WlMV|U-Ldmr|<_nz<HPxAjgy(dh4
zt%>*<G$Xyq;j9=BhB5A&=POQVP)Cf6Cya+LY+a<JUYo&IFqp(0J#2$;Js1olM&<Sm
z2hx57HkN5EQ7ygA@zRqwhcW&6`TP~`8?BuYp{N`BX&&WpKc+bAPgR9B+$OY`BA2dS
z*A00zNam9#iEIdL$*z<j8v;c00fI!@!6ZB2yx=SIPXP}I{fAXmpWorJHA|i#71%I^
zfn3YsYJC)WMs}nr`nqS1b8tj?^I=>F_kcgrJnrViguEnJt{=Mk5f4Foin<QP2@Cdn
zqT#j5=Wj?`(v3C2RWCmT`eGX5fhedx+#7a(!IrZ$n^>7(3vUXC>4gyJ>sK<;-p{h7
z2_mr&Fca!E^7R6VvodGznqJn3o)Ibd`gk>uKF7aemX*b~Sn#=NYl5j?v*T4FWZF2D
zaX(M9hJ2YuEi%b~4?RkJwT*?aCRT@ecBkq$O!i}EJJEw`*++J_a>gsMo0CG^pZ3x+
zdfTSbCgRwtvAhL$p=iIf7%Vyb!j*UJsmOMler--IauWQ;(ddOk+U$WgN-RBle~v9v
z9m2~@h|x*3t@m+4{U2}fKzRoVePrF<RJ94G+_hAjxr-thjMmJ&7|b`1Q!`fA)vYK~
zqDxNSpgXWAJJ$!kTO_lC{3z9&{s(P%by5tx_@WK9e>-}U{`YT|vW?~64Bv*7|Dz03
zRYM^Yquhf*ZqkN?+NK4Ffm1;6BR0ZyW3MOFuV1ljP~V(=-tr^Tgu#7$`}nSd<8?cP
z`VKtIz5$~InI0YnxAmn|pJZj+nPlI3zWsykXTKRnDCBm~Dy*m^<fEQ$Tt{H-xP+V(
z?lk2!#!!;wpuQT!K9LO7UnSvslJ-9DqH;-!{3Vfm+0G~BLKg+x<O<i4{QNQ+N<ng@
zHek-(Bn1R%65UUC!Bh5AL3EhWcvKssK9>^qTuY+8dSl@>&B8~0H$Y0Zc25APo|?R=
z>_#h^kcfs#ae|iNe{BWA7K1mLuM%K!_V?fDyEqLkkT&<`SkEJ;E+Py^%hP<GSs9m{
z_ohgeP_Vx|5GE(plB^1x3T?4chG?iNc3MoNf7KQ~<wjW|Z&9O^+}KwsVVrT_s9WoQ
zI66#cE%M+^o5l*!jEcZswEun#o^IG+UqrN|_uGK?Pd`4AU1cqR1tHh6HNXi=6{4VO
zDlr-wTA+mNr^zyx>VZ(%a2P4vL=vglF|X_`Z$^}q470V+7I4;U<H_P?r<Yk7yA*R@
zWO+KoOQp4>YdcZ7vU=41dd{d#KmI+|Z<KAK%~wx}r>Ga>C10g6w1a?wxAc&?iYsEv
zuCwWvcw4FoG=Xrq=JNyPG*yIT@xbOeV`$s_kx`pH0DXPf0S7L?F208x4ET~j;yQ2c
zhtq=S{T%82U7GxlUUKMf-NiuhHD$5*x{6}}_eZ8_kh}(}BxSPS9<(x2m$Rn0sx>)a
zt$+qLRJU}0)5X>PXVxE?Jxpw(kD0W43ctKkj8DjpYq}lFZE98Je+v2t7uxuKV;p0l
z5b9smYi5~k2%4aZe+~6HyobTQ@4_z<!e;(%({JaOWCE6^+lW4}7H4g=0%z28{u=VO
zQ~JqFKkVR#4s(b5_z)V5No6a%*cr3W(wOsCY?kFZn;ZFx%}ak<fc-nqI{q1y|AT95
zKzS)GrG5I28C^{tK;oy4;)#I};2-3Xm5P58{1z4K7bO53ut~%^)@wvEE#>#*lRHl#
zSA`s~Jl@RGq=B3SNQF$+puBQv>DaQ--V!alvRSI~ZoOJx3VP4sbk!NdgMNBVbG&BX
zdG*@)^g4#M#qoT`^NTR538vx~rdyOZcfzd7GBHl68-rG|fkofiGAXTJx~`~%a&boY
zZ#M4sYwHIOnu-Mr!Ltpl8!NrX^p74tq{f_F4%M@&<=le;>xc5pAi&qn4P>04D$fp`
z(OuJXQia--?vD0DIE6?HC|+DjH-?Cl|GqRKvs8PSe027_NH=}+8km9Ur8(JrVx@*x
z0lHuHd=7*O+&AU_B;k{>hRvV}^Uxl^L1-c-2j4V^TG?2v66BRxd~&-GMfcvKhWgwu
z60u{2)M{ZS)r*=&J4%z*rtqs2syPiOQq(`V0UZF)boPOql@E0U39>d>MP=BqFeJzz
zh?HDKtY3%mR~<ofQ-VL$Gn7@0K>reR7S2rsR0aDMA^a|L^_*8XM9<sh>KjabpYSBu
z;zkfzU~12|X_W_*VNA=e^%Za14PMOC!z`5Xt|Fl$2bP9fz>(|&VJFZ9{z;;eEGhOl
zl7OqqDJzvgZvaWc7Nr!5lfl*Qy7_-fy9%f(v#t#&2#9o-ba%J3(%s#C=@dagx*I{d
zB&AzGT9EEiknWJU^naNdz7Logo%#OFV!eyCIQuzgpZDDN-1F}JJTdGXiLN85p|GT!
zGOfNd8^RD;MsK*^3gatg2#W0J<8j)UCkUYoZRR|R*UibOm-G)S#|(`$hPA7UmH+fT
ziZxTgei<VPFgYE!2)5*kZf(Yz8JO!eX?K#xrGvA+y2kV%lB4O7if+2!)C8G2()%aA
zA=<_&c<78cA-haE31_PB^xZ2I&D_1EB9#j8G%_+_RqSZQ^xvCMD0(D^HPsdcs%#4;
z62L0wD#Ss-ESmsc`x%k@RLMzVV?Q@iipKif4(l$~%IaM%PayFerP!#19qST5`h+K)
zgtN?1pec1wxp`6GfpUI>R_yzvNS1s+T!xw)QgNSH(_?B@O?uTBwMj`G)2c^8%g8zu
zxMu5SrQ^J+K91tkPrP%*nTpyZor#4`)}(T-Y8eLd(|sv8xcIoHnicKyAlQfm1YPyI
z!$zimjMlEcmJu?M6z|RtdouAN1U5lKmEWY3gajkPuUHYRvTVeM05CE@`@VZ%dNoZN
z>=Y3~f$~Gosud$AN{}!DwV<6CHm3TPU^qcR!_0$cY#S5a+GJU-2I2Dv;ktonSLRRH
zALlc(lvX9rm-b5`09uNu904c}sU(hlJZMp@%nvkcgwkT;Kd7-=Z_z9rYH@8V6Assf
zKpXju&hT<=x4+tCZ{elYtH+_F$V=tq@-`oC%vdO>0Wmu#w*&?_=LEWRJpW|spYc8V
z=$)u#r}Pu<hbr&#Xz+AeeY_c~s6!?7Q=WRCkYY{0wM>7kvjSuM{FSyy9_&851CO^B
zTm$`pF+lBWU!q>X#;AO1&=tOt=i!=9BVPC#kPJU}K$pO&8Ads)XOFr33<kp6h^@$J
zo?dagP)((=Dnqf=8F~`NaZlRPmIBq#f7eQpVs2-Ak5@?%lueG*iUCc}`jXW>6_Iyn
z$d{MTGYQLX9;@mdO;_%2Ayw3hv}_$UT00*e{hWxS?r=KT^ymEwBo429b5i}LFmSk`
zo)-*bF1g;y@&o=34TW|6jCjUx{55EH&DZ?7wB_EmUg*B4zc6l7<aT}0SpY<Oa**T|
z_9AyQb*!Lot&_`cUT*0J3f_l;<S$R4uzj4Yix7{V;60CFDjJ3^;8Cpb%v<;n@!Y+<
zqKyqux_`duK#m{2upJ3)H`Kvsb!-$^8YL)ePe$2}h?$?UKg`PFHZq0jO~YevDQkmo
zT5hhoeu7*fVln6+%&|p0Ao9)hQ83-1$G|jRJ@Grca?*sT8qvM;O72=4D+1fRM9_hO
zVnGFGerr)NYYJdAR0o5N>x-}qYLQR@^7o6rrgkoujR<XIIHctw_)Fe%f2t48tPQC{
z6<Rn<QfyY(e5f=WMY{#(?1kFRJ_x(q*-OMuT5s9JGPBfJuQbjDV9pobOEUygVLV+x
zd2$syWHUsLcNKair#m>Nym9O)K>wNfvY+uy+4Om{XgRHi#Hpg*bZ36_X%pP`m7FIF
z?n?G*g&>kt$>J_PiXIDzgw3IupL3QZbysSzP&}?JQ-6TN-aEYbA$X>=(Zm}0{hm6J
zJnqQnEFCZGmT06LAdJ^T#o`&)CA*eIYu?zzDJi#c$1H9zX}hdATSA|zX0Vb^q$mgg
z&6kAJ=~gIARct<X!BzXY$C1<R!3H!^FKTL$eKy1%Dx%wp&o(=jb~%$sk<Z9kmCu4$
zo-sXQV#0q*HO%@J-F}wHnt>>}4z&kzWWvaD9#1WK=P>A_aQxe#+4cpJtcRvd)TCu!
z>eqrt)r(`qYw6JPKRXSU#;zYNB7a@MYoGuAT0Nzxr`>$=vk`uEq2t@k9?jYqg)MXl
z67MA3^5_}Ig*mycsGeH0_VtK3bNo;8#0fFQ&qDAj=;lMU9%G<UqQvs5tjZU_m%VfH
zl&Mkh0;oSEW~%P2=xE9hVGwn+AfysI)n|1QPLvN%4NM(mM>)&HL>NO|lWU3z+m4t7
zfV*3gSuZ++r<i;qA=~DP_Nh|E&<-2Z&aFfHX3R96<7T^_3m7_X;3U5iPdEZgVv~1P
z&tD_v;R9E5<a#7C83VbdmzLvjnMdeFreo|8+V|zi3E!to{L=`wV6A3<k#ueVOPMq&
zr}e2(?u0hg<k(8pg?OvSm{t~ZB8l%BqttArB#yV(tiG78=PRPge7ljYluT02%G9#p
zc=8z-A!oOz%cA6O$r^fOq|8I!XSMEzpRO)P5lm-LC=7;t-wh$ssp;DFm&`1xejgf(
z$}mke!PpZDo40zpgaxh$dl3AzpGuCm&Qe}QLq>IWsinX@QaT>dsbD>Xp8%8c`<?=-
zWLa05h}VRqMNEf2?UdwL2G$a@>HLamm~(i{7L&S0uZ;`W-tqU4XAgQclM$PxE7<J8
zGd+j`0+W(m8dPnT*2M7-(Y_6%Zo*V1O1S3})8V$Oq}K$zJoI{vRmL|#J<?lBr5%0t
z&@uPNiGG_u4Z8WM8Sja~BQMr169y@KL>6OH(PSjHjR$(nh({vsNnawhP!!HcP!l)5
zG;C=k0xL<^q+4rpbp{sGzcc~ZfGv9J*k~PPl}e~t$>WPSxzi0}05(D6d<=5+E}Y4e
z@<Yjm&6F?CT4`lC!wyHrTMqCTt(Pgyo7(NVWv#k*j46wyIR=sYR;=i8_~FY&tQp;h
zfpL%+S1V`(Zhe&<?uR&V5$pmN-`kHFv#^4B>_QZtDcC7qh4#dQFYb6Pulf_8iAYYE
z1SWJfNe5@auBbE5O=oeO@o*H5mS(pm%$!5yz-71~lEN5=x0eN|V`xAeP;eTje?eC=
z53WneK;6n35{OaIH2Oh6Hx)kV-jL-wMzFlynGI8Wk_A<~_|06rKB#Pi_QY2XtIGW_
zYr)RECK_JRzR1tMd(pM(L=F98y~7wd4QBKAmFF(AF(e~+80$GLZpFc;a{kj1h}g4l
z3SxIRlV=h%Pl1yRacl^g>9q%>U+`P(J`oh-w8i82mFCn|NJ5oX*^VKODX2>~HLUky
z3D(ak0Sj=Kv^&8dUhU(3Ab!U5TIy97PKQ))&`Ml~hik%cHNspUpCn24cqH@dq6ZVo
zO9xz!cEMm;NL;#z-tThlFF%=^ukE8S0;hDMR_`rv#e<aYbPGO~W9lUzuBOTPL90&c
zR82d!Vbie&tJD03mxAC>TYg7io1w9n_vJpK+6%=c#Y?wjAs_(#RQA0gr&Va2BQTq`
zUc8)wHEDl&Uyo<>-PHksM;b-y(`E_t8Rez@Iw+eogcEI*FDg@Bc;;?3j3&kPsq(mx
z+Yr_J#?G<iViBF_i-C`^0W<4y5Y<7upFVbe+w4SEx8<0WO)7k9q+=SHv8&2`sGd31
zkP3T*TR{1=Imw7Bxva>6D?t2G%O9o&e7Gbf&>#(-)|8)GIbG_a${TU26cVrIQSt=%
zQ~XY-b1<AergIK&x^rak248~9*8uk*oc6$o#Zn(iVc~|=$h-*}4E^AdA4l#_08y1S
zYGVuNW}QWwu7GIm89Ib{_{rX5a@%Tm1dS!m8ET;#8F;-U+>VQVc>IV=7um0^Li>dF
z`zSm_o*i@ra4B+Tw5jdguVqx`O(f4?_USIMJzLvS$*kvBfEuToq-VR%K*%1VHu=++
zQ`=cG3cCnEv{ZbP-h9qbkF}%qT$j|Z7ZB2?s7nK@gM{bAD=eoDKCCMlm4LG~yre!-
zzPP#Rn9ZDUg<W_~9$+r!l&aHZxl-|!w`b*`F?^8{WN|Fl(ZYCkU|l%GSZ3jDXB3!3
z@B?Ll8<PL?Ko@VKqa3t7oCX}UNZw$4iU1C#kYm)23vR~w>b4++M78-V&VX<1ah(DN
z(4O5b`Fif%*k?L|t%!WY`W$C_C`tzC`tI7XC`->oJs_Ezs=K*O_{*#SgNcvYdmBbG
zHd8!UTzGApZC}n7LUp1fe0L<3|B5GdLbxX@{ETeUB2vymJgWP0q2E<&!Dtg4>v`aa
zw(QcLoA&eK{6?Rb&6P0kY+YszBLXK49i~F!jr)7|xcnA*mOe1aZgkdmt4{Nq2!!SL
z`aD{6M>c00muqJt4$P+RAj*cV^vn99UtJ*s${&agQ;C>;SEM|l%KoH_^kAcmX=%)*
zHpByMU_F12iGE#68rHGAHO_ReJ#<2ijo|T7`{PSG)V-bKw}mpTJwtCl%cq2zxB__m
zM_p2k8pDmwA*$v@cmm>I)TW|7a7ng*X7afyR1dcuVGl|BQzy$MM+zD{d~n#)9?1qW
zdk(th4Ljb-vpv5VUt&9iuQBnQ$JicZ)+HoL`&)B^Jr9F1wvf=*1and~v}3u{+7u7F
zf0U`l4Qx-ANfaB3bD1uIeT^zeXerps8nIW(tmIxYSL;5~!&&ZOLVug2j4t7G=zzK+
zmPy5<4h%vq$Fw)i1)ya{D;GyEm3fybsc8$=$`y^bRdmO{XU#95EZ$I$bBg)FW#=}s
z@@&c?xwLF3|C7$%>}T7xl0toBc6N^C{!>a8vWc=G!bAFKmn{AKS6RxOWIJB<mSe9A
zwUk5~;ttm-UwXh*gg5olvLGjDrJF~aE)zw$2Cx_eu%5KNe;go|?QkK;r66LMH$Q{i
z(o$Px!SY-^Yt$SqP8{b!^ECQ;8ws+BoP3c3|Li3_f4T#5JuXaP83(&fL-hPNOc59?
znfTb~SLKNXcJO_UhW#o^NzATJJ`ny)XA=kT!Ksrt0Xa@;!{BT9F}vwy=edtakafQD
z=(}1}Nz&PD@?L&t;Imei8b~`}i&1->ZXP&0CyXAi<q;~b<N8*s9G=^}i4s)D#$vcL
zyoq3g&u7Tt(OJAod?dG1{^^Vuq3YRSWq4`w0-bvlYFVR;^C&sH+UzI?O$GcL8apjR
zBLj5TOsv@Cm8hg8bezCoS{oA(leEXhmjHBtcA+h4Pm!GlcHAU!ed9Vw-1M+HCy#uO
zH%T1#Vjl}leAtLQ&d&RC)sO2tvt_Xc&<0tk)QkAJ8r!~IY(q0`d(^U5`NE3@Sq-TR
zPLeTrxJ@0$k2pP&(e@cSpW~{9G<MOgPxaYWw-STSzeukb!@_O(y7rRZdA?gf***U)
z?}>Hd?7R#S46K6UXYXl#c_#APL5SfW<<-|rcfX&B6e*isa|L^RK=0}D`4q-T0VAs0
zToyrF6`_k$UFGAGhY^&gg)(Fq0p%J{h?E)WQ(h@Gy=f6o<Dj$lh>xUSAuT4ir<zaZ
z>}jI)36|NnmnI|<V;nFnIxNnd$1}$bE4<H-by6>vtij;t!jT?6Jf-E19}9Lf9(+N+
z)+0)I5mST_?3diP*n2=ZONTYdXkjKsZ%E$jjU@0w_lL+UHJOz|K{{Uh%Zy0dhiqyh
zofWXzgRyFzY>zpMC8-L^43>u#+-zlaTMOS(uS!p{Jw#u3_9s)(s)L6j-+`M5sq?f+
zIIcjq$}~j9b<R^nFdDNl9nxz0ZvWBFP}9lsRa;+d{AySj+c<6#i^S0IHmk%&jX5^c
z;~Zw15>`0_hIz~?4?b(Sqdpi(;1=8~wkIABU+APWQd<Zk3H$p=O%NeU9`uQnD5Y7U
zZNq%5c(I+iD%=SLJySp!k^{YMT|y)xH?yL?HBbUR2<-$rQ*O?xKYbb$@+_#vqFTep
zFO+s7Sok3?#8&7hvsDrKw8<Fz&N4_Yl#i_L(k>f5v@g=1c{c{d*J(X5+cfEdG?qxq
z{GKkF;)8^H&Xdi~fb~hwtJRsfg#tdExEuDRY^x9l6=E+|fxczIW4Z29NS~-oLa$Iq
z93;5$(M0N8ba%8&q>vFc=1}a8T?P~_nrL5t<WLfpVju(QTm11|XlZC+^uwVFqS$p#
z>Ye~X>G=3QoFlBae8vVt-K!^@vusN<8gQJ!WD7H%{*YgY0#(tXxXy##C@o^U7ysxe
zLmUWN@4)JBjjZ3G-_)mrA`|NPCc8Oe!%Ios4$HWpBmJse7q?)@Xk%$x&lIY>vX$7L
zpfNWlXxy2p7TqW`Wq22}Q3OC2OWTP_X(*#kRx1WPe%}$C!Qn^FvdYmvqgk>^nyk;6
zXv*S#P~NVx1n6pdbXuX9x_}h1SY#3ZyvLZ&VnWVva4<x~i=T>)9D|i7kjGY{>am&^
z-_x1UYM1RU#z17=AruK~{BK$A65Sajj_OW|cpYQBGWO*xfGJXSn4E&VMWchq%>0yP
z{M2q=zx!VnO71gb8}Al2i+uxb=ffIyx@oso@8Jb88ld6M#wgXd=WcX$q$91o(94Ek
zjeBqQ+CZ64hI>sZ@#tjdL}JeJu?GS7N^s$WCIzO`cvj60*d&#&-BQ>+qK#7l+!u1t
zBuyL-Cqups?2>)ek2Z|QnAqs_`u1#y8=~Hvsn^2Jtx-O`limc*w;byk^2D-!*zqRi
zVcX+4lzwcCgb+(lRO<!u{im0+jt`r2`Em5{bbf@HeK<qd*JgT&gFYTPE6SUsguim&
zK56=?e>WJ~qi;q2!t6;?%qjGcIza=C6{T7q6_?A@qrK#+)+?drrs3U}4Fov+Y}`>M
z<N<x^M%)G=No8uzY`9zTx~0*Hu=^Ld!u9U7w&?RTuZ{ao=<@qCI9ASbxvZDy67qU+
z>#40OUPpwpaC-8&q8yW0XWGw`RcSpBX+7hZ@xar<?ckho#G5?>fCNnrl-{k@`@Vv>
zYWB*T=4hLJ1SOb<!(sRM0mK6-l~UfZMKxR;X!5H!sx-KdXIoUf6XLl$A&bwC7x=7g
zE{M?vA9|hz;)XD_9e*XW?EE<4#XI4fx!OVDw18`ZapG8Wgp14}w$W<njWlPG@NmQw
zb~xmmLM`PJWd~P~50FcGc!QIPF_@WW?}@s3h27Q)2Xa~Xetg4#b@WtlHT|2bpgBQ?
z&jfXV?}L-R5x7o0^Qn74{~(FLnMxT-;o@&K{uBgPOZg2ZF;`bMBW*it(_K-Gj}_AO
z;%B^em2d-m3l@E=bOcyNQ|iI(`9>SF_)2AaX*g(#(88~bVG9w)ZE91eIQWflNecYC
zzUt}ov<&)S&i$}?LlbIi9i&-g=UUgjWTq*v$!0$;8u&hwL*S^V!GPSpM3PR3Ra5*d
z7d77UC4M{#587NcZS4+JN=m#i)7T0`jWQ{HK3rIIlr3cDFt4odV25yu9H1!}BVW-&
zrq<Ip6S3)|!|AK+z9KY7{3P+HsCYhp4u>M5DjDzbd^pE^Q<-$1^_tX)dX8;97ILK{
z!{kF{!h`(`6__+1UD5=8sS&#!R>*KqN9_?(Z$<p9Q<$woFmSv?@e7n`z`~aA(Nn*e
z;v+G%*|zYj6!lp7`k)nC@k5v}N5SsG2cM0yW7~<A@aBNL4LVCJ4<Jd3UW1px)sL^U
zs$`DajP9W|Y>4cY#B)pG8>2pZqI;RiYW6aUt7kk*s^D~Rml_fg$m+4+O5?J&p1)wE
zp5L-X(6og1s(?d7X#l-RWO+5Jj(pAS{nz1abM^O;8hb^X4pC7ADpzUlS{F~RUoZp^
zuJCU_fq}V!9;knx^uYD2S9E`RnEsyF^ZO$;`8uWNI%hZzKq=t`q12cKEvQjJ9dww9
zCerpM3n@Ag+XZJztlqHRs!9X(Dv&P;_}zz$N&xwA@~Kfnd3}YiABK*T)Ar2E?OG6V
z<;mFs`D?U7>Rradv7(?3oCZZS_0Xr#3NNkpM1@q<KMz!5%$Nu5i+)sIFy{P_q+hSM
z;iw?+TF)i*sZH;kvB>n-X$;aNLYL;yIMX4uubh^Xb?HloImt$=^s8vm)3g!{H1D|k
zmbg_Rr-ypQokGREIcG<<SfM<L)C{35M|z0ho<S)c5EtqO=v6$Z0H~eIX80m~(tU=R
zL3N5~h#B=Iwc7k;i`(a8I3mQ}afulUJBj$0^q3_DT(<p-SI?0%aPpz+DIj$*B}iUA
zhegR2LsLk|nW3_Ft7Vxf_NE5qvIo(&BBB9MEdjy3?4<J*(1g-rfO?l&x<uP@KxaW4
zLtk%eP39}E5G>8u(=W^+<Zfal!4GQuNFjsaBFn4Oh`dNX4~%@PtEduGHBQ*)LSAqw
zGMaE}MFf-^tLx9oM*4|DvNUeij`G%RgcN$mGW*$nj-Zq#C7#X9O?0d`E+u#IQh{=*
zZ2%~#v+7MnjcTDC+oY<>oxelI&t0U`dT=bBMe1fl+9!l&vEPFFu~yAu!XIv4@S{;|
z8?%<1@hJp%7AfZPYRARF1hf`cq_VFQ-y74;EdMob{z&qec2hiQJOQa>f-?Iz^VXOr
z-wnfu*uT$(5WmLsGsVkHULPBvTRy0H(}S<w+S-%VXz-mEN`|2vC7HKn=EI0e2(Ez_
z;KT<*I@#}{vu3;1dggp)8=E+#`C(~F8+4AX@0ZwSOO<2E`ZL;@94hBVC4XO0xwpgQ
zgWPEX<Q(51hsL35g>0SQ<i>18W0kp_U}8Phc3gz!Hj#*VYh$AiDE245!YA0M$Q@rM
zT;}1DQ}MxV<)*j{hknSHyihgMPCK=H)b-iz9N~KT%<&Qmjf39L@&7b;;>9nQkDax-
zk%7ZMA%o41l#(G5K=k{D{80E@P|I;aufYpOlIJXv!dS+T^plIVpPeZ)Gp`vo+?BWt
z8U8u=C51u%>yDCWt>`VGkE5~2dD4y_8+n_+I9mFN(4jHJ&x!+l*>%}b4Z>z#(tb~<
z+<+X~GIi`sDb=SI-7m>*krlqE3aQD?D5Wi<Ba$(_sO)#OHmv}v5DU%=iw?a*va39O
zLikPo6&<mEHY~avK_Thox6G2xQ}qJ~GR5g`luw}pOtt+%uFMfaEk<Wo{@i4iz;nGs
z&34iJfD)yG>YX;#8m|ENYKw}H^95u!=n=xr3jxhCB&InJ7>zgLJg;i?Sjjd`YW!2;
z%+y=LwB+MMnSGF@iu#I%!mvt)aXzQ*NW$cHNHwjoaLtqKCHqB}LW^ozBX?`D4&h%#
zeMZ3ZumBn}5y9&odo3<n86WE!41U!tFm&A8g~@)NM3ZJ|Ku8PFpX}%!(RCzkS1D!l
z!C~f5(eg|Xa&Ankm8nSd0`z5`y<K&JIyJ!O?c#^2&$HOUyPo|WK`Z|NP<{jJPtziw
z|7!N{3%}j^PI8YmF*~wH8ix_p?@CAz8G<&#E;o3ME-xmePXCjLV4!N6O{Bchj-)hs
zv2h1+I7&=46*UPu5C2oxc`OLQHE6=oVEZ3ecxMxyPj{Yzoi}S^J$|E*{Ys^+1*a4z
z2<G{AD7x*JsY8ghqnyC`U3F>=hN$Q&SRte*^-SNZg2<}6>OzRpF91oy0{RuZU(Q0I
zvx%|9>;)-Ca9#<P7Wrw4fJ;!T3I~=XuG4qUzdn><gies%pptp`D%~itKS+c*D)6Ji
zagzx*N#WVK-OielWmXV0&Y|dWbpkXyV&u+INL-|+>L)HQt~axu0q{745Ac;s1XQKV
ze3D9I5gV5SP-J>&3U!lg1`HN>n5B6XxYpwhL^t0Z)4$`YK93vTd^7BD%<)cIm|<Bo
zSavI~^av=K6Fvm&YAAA`a`(9a&nhq_KHDFQvszY{8Kd`T5?UIAdi-8pqdSO|Mx%b8
zrsPt=&$DA44$wCzXtmXkTXzUW+mKO-d(gvEml_OMPSg1DKH3M_iD`5j<$Cr%IIWij
zf@YuWx7i1WKm|hugN6ptLW2>4e!;*%9}B-3NX+J*Nr@;5(27Zmf(TmfHsej^Bz+J1
zXKIjJ)H{thL4W<HnUsLEm?$U*g-%NJzK82ZH^2yYJP?C?{>Ouro|6&aPw=-JW8G=2
z|L4YL)^rYf7J7DOKXpTX$4$Y{-2B!jT4y^w8yh3LKRKO3-4DOshFk}N^^Q{r(0K0+
z?7w}x>(s{Diq6K)8sy)>%*g&{u>)l+-Lg~=gteW?pE`B@FE`N!F-+aE;XhjF+2|RV
z8vV2((yeA-VDO;3=^E;fhW~b=Wd5r8otQrO{Vu)M1{j(+?+^q%xpYCo<JWR(HwFa1
zR$=;6FK*ljT3b6@>jc<H>6rmQ<&ytZ2ly?bw*X)WB8(n^B4Gmxr^1bQ&=m;I4O$g{
z3m|M{tmkOyAPnMHu(Z}Q1X1GM|A+)VDP3Fz934zSl)z>N|D^`G-+>Mej|VcK+?iew
zQ3=DH4zz;i>z{Yv_l@j*?{936kxM{c7eK$1cf8wxL>>O#`+vsu*KR)te$adfTD*w(
zAStXnZk<6N3V-Vs#GB%vXZat+(EFWbk<QmU2dzIV(A1LN@j6KS6R(oiCMGxH5BFW(
z7Okhh<C1{vCoZL~KYacW>bky#{yGY`rOvN)?{5qUuFv=r=dyYZrULf%MppWuNRUWc
z8|YaIn}P0DGkwSZ(njAO$Zhr3Yw`3O1A+&F*2UjO{0`P%kK(qL;kEkfjRC=lxPRjL
z{{4PO3-*5RZ_B3LUB&?ZpJ4nk1E4L&eT~HX0Jo(|uGQCW3<z!_{A+-_T5P}h{+lP|
zwb;pjO%aqB`nRFKmOi=P&D+up*WBVa1_V$4m8)M1pWFw#O>utB@p)rF@W*n$==TlS
zKiTfzhrLbAeRqru%D;fUwXOUcHud{pw@Ib1xxQ}<2)?KC&%y5PVef<7rcu2<w7)SR
zc!t_P2f2OTa3A3|3FO_WLeu%@2)`#z{)C+MKJ0DgJ%O81fv!OCTK)eG_V+>l!8dsy
z?lvdaHJ#s$0m18y{x#fB$o=l)-sV?Qya5GWf#8Vd{~Grn@qgX#!EI`Y>++l%1A;eL
z{_7t6jMeEr@a+oxyCL^+_}9Qc;i0&Xd%LXp?to*R|26LKH<iF&R{P%vzFiM0aua6I
z6$oDA_CEsOuc5vVdAs!Ry3q8-fZ)Ja|0Cq@EBO}B$9>G(m0)*QF4*h;5%YG5<9)c>
z1vq!7bIJSv1^27i-mcH!zX>ep3Iw0^{nx<1jOy)N_UoFD8v}x~2mEWapI3m~kMQkR
z#&@4FuEGBn`mgtSx6je<B80yC1;U@s&i@+W_EF>Y7vUQNf=^}sTZErIEpH!cy|@7Z
zU4h_Oxxd2s=f{}$XXy4}%JqTSjRC<i@_vDOf3JLNpYx}!2PEiK2N)O~=qCsCwHvMA
H`mg^3F4joe

literal 0
HcmV?d00001

diff --git a/source-code/hello-world/.mvn/wrapper/maven-wrapper.properties b/source-code/hello-world/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..cd0d451
--- /dev/null
+++ b/source-code/hello-world/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip
diff --git a/source-code/start/hello-world/mvnw b/source-code/hello-world/mvnw
similarity index 100%
rename from source-code/start/hello-world/mvnw
rename to source-code/hello-world/mvnw
diff --git a/source-code/hello-world/mvnw.cmd b/source-code/hello-world/mvnw.cmd
new file mode 100644
index 0000000..fef5a8f
--- /dev/null
+++ b/source-code/hello-world/mvnw.cmd
@@ -0,0 +1,161 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements.  See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership.  The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License.  You may obtain a copy of the License at
+@REM
+@REM    https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied.  See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven2 Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM     e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on"  echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
+FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO (
+	IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+    echo Found %WRAPPER_JAR%
+) else (
+    echo Couldn't find %WRAPPER_JAR%, downloading it ...
+	echo Downloading from: %DOWNLOAD_URL%
+    powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"
+    echo Finished downloading %WRAPPER_JAR%
+)
+@REM End of extension
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/source-code/start/hello-world/pom.xml b/source-code/hello-world/pom.xml
similarity index 100%
rename from source-code/start/hello-world/pom.xml
rename to source-code/hello-world/pom.xml
diff --git a/source-code/start/hello-world/src/main/java/com/example/helloworld/HelloWorldApplication.java b/source-code/hello-world/src/main/java/com/example/helloworld/HelloWorldApplication.java
similarity index 100%
rename from source-code/start/hello-world/src/main/java/com/example/helloworld/HelloWorldApplication.java
rename to source-code/hello-world/src/main/java/com/example/helloworld/HelloWorldApplication.java
diff --git a/source-code/start/hello-world/src/main/java/com/example/helloworld/controller/UserController.java b/source-code/hello-world/src/main/java/com/example/helloworld/controller/UserController.java
similarity index 100%
rename from source-code/start/hello-world/src/main/java/com/example/helloworld/controller/UserController.java
rename to source-code/hello-world/src/main/java/com/example/helloworld/controller/UserController.java
diff --git a/source-code/start/hello-world/src/main/java/com/example/helloworld/dto/UserDto.java b/source-code/hello-world/src/main/java/com/example/helloworld/dto/UserDto.java
similarity index 100%
rename from source-code/start/hello-world/src/main/java/com/example/helloworld/dto/UserDto.java
rename to source-code/hello-world/src/main/java/com/example/helloworld/dto/UserDto.java
diff --git a/source-code/start/hello-world/src/main/java/com/example/helloworld/entity/User.java b/source-code/hello-world/src/main/java/com/example/helloworld/entity/User.java
similarity index 100%
rename from source-code/start/hello-world/src/main/java/com/example/helloworld/entity/User.java
rename to source-code/hello-world/src/main/java/com/example/helloworld/entity/User.java
diff --git a/source-code/start/hello-world/src/main/resources/application.properties b/source-code/hello-world/src/main/resources/application.properties
similarity index 100%
rename from source-code/start/hello-world/src/main/resources/application.properties
rename to source-code/hello-world/src/main/resources/application.properties
diff --git a/source-code/start/hello-world/src/test/java/com/example/dto2entity/BeanUtilsTest.java b/source-code/hello-world/src/test/java/com/example/dto2entity/BeanUtilsTest.java
similarity index 100%
rename from source-code/start/hello-world/src/test/java/com/example/dto2entity/BeanUtilsTest.java
rename to source-code/hello-world/src/test/java/com/example/dto2entity/BeanUtilsTest.java
diff --git a/source-code/start/hello-world/src/test/java/com/example/helloworld/HelloWorldApplicationTests.java b/source-code/hello-world/src/test/java/com/example/helloworld/HelloWorldApplicationTests.java
similarity index 100%
rename from source-code/start/hello-world/src/test/java/com/example/helloworld/HelloWorldApplicationTests.java
rename to source-code/hello-world/src/test/java/com/example/helloworld/HelloWorldApplicationTests.java

From 2355126d2186923f7a1eb7d98ff2f3e927883e1f Mon Sep 17 00:00:00 2001
From: guide <koushuangbwcx@163.com>
Date: Wed, 21 Apr 2021 21:55:33 +0800
Subject: [PATCH 07/22] Delete workspace.xml

---
 springboot-schedule-task/.idea/workspace.xml | 118 -------------------
 1 file changed, 118 deletions(-)
 delete mode 100644 springboot-schedule-task/.idea/workspace.xml

diff --git a/springboot-schedule-task/.idea/workspace.xml b/springboot-schedule-task/.idea/workspace.xml
deleted file mode 100644
index 7120d02..0000000
--- a/springboot-schedule-task/.idea/workspace.xml
+++ /dev/null
@@ -1,118 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="ChangeListManager">
-    <list default="true" id="7fff5b14-8bce-456a-9934-0e81cd72736e" name="Default Changelist" comment="">
-      <change beforePath="$PROJECT_DIR$/../source-code/advanced/springboot-schedule-tast/.gitignore" beforeDir="false" />
-      <change beforePath="$PROJECT_DIR$/../source-code/advanced/springboot-schedule-tast/build.gradle" beforeDir="false" />
-      <change beforePath="$PROJECT_DIR$/../source-code/advanced/springboot-schedule-tast/gradle/wrapper/gradle-wrapper.jar" beforeDir="false" />
-      <change beforePath="$PROJECT_DIR$/../source-code/advanced/springboot-schedule-tast/gradle/wrapper/gradle-wrapper.properties" beforeDir="false" />
-      <change beforePath="$PROJECT_DIR$/../source-code/advanced/springboot-schedule-tast/gradlew" beforeDir="false" />
-      <change beforePath="$PROJECT_DIR$/../source-code/advanced/springboot-schedule-tast/gradlew.bat" beforeDir="false" />
-      <change beforePath="$PROJECT_DIR$/../source-code/advanced/springboot-schedule-tast/settings.gradle" beforeDir="false" />
-      <change beforePath="$PROJECT_DIR$/../source-code/advanced/springboot-schedule-tast/src/main/java/com/example/demo/AsyncScheduledTasks.java" beforeDir="false" />
-      <change beforePath="$PROJECT_DIR$/../source-code/advanced/springboot-schedule-tast/src/main/java/com/example/demo/DemoApplication.java" beforeDir="false" />
-      <change beforePath="$PROJECT_DIR$/../source-code/advanced/springboot-schedule-tast/src/main/java/com/example/demo/scheduletask/ScheduledTasks.java" beforeDir="false" />
-      <change beforePath="$PROJECT_DIR$/../source-code/advanced/springboot-schedule-tast/src/main/java/com/example/demo/scheduletask/config/SchedulerConfig.java" beforeDir="false" />
-      <change beforePath="$PROJECT_DIR$/../source-code/advanced/springboot-schedule-tast/src/main/resources/application.properties" beforeDir="false" />
-      <change beforePath="$PROJECT_DIR$/../source-code/advanced/springboot-schedule-tast/src/main/resources/templates/hello/hello.html" beforeDir="false" />
-      <change beforePath="$PROJECT_DIR$/../source-code/advanced/springboot-schedule-tast/src/test/java/com/example/demo/DemoApplicationTests.java" beforeDir="false" />
-    </list>
-    <option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
-    <option name="SHOW_DIALOG" value="false" />
-    <option name="HIGHLIGHT_CONFLICTS" value="true" />
-    <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
-    <option name="LAST_RESOLUTION" value="IGNORE" />
-  </component>
-  <component name="Git.Settings">
-    <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
-  </component>
-  <component name="ProjectId" id="1TY8xB1yxWTXSY2NKZ93or04Qdd" />
-  <component name="ProjectLevelVcsManager" settingsEditedManually="true" />
-  <component name="PropertiesComponent">
-    <property name="RequestMappingsPanelOrder0" value="0" />
-    <property name="RequestMappingsPanelOrder1" value="1" />
-    <property name="RequestMappingsPanelWidth0" value="75" />
-    <property name="RequestMappingsPanelWidth1" value="75" />
-    <property name="SHARE_PROJECT_CONFIGURATION_FILES" value="true" />
-    <property name="WebServerToolWindowFactoryState" value="false" />
-    <property name="aspect.path.notification.shown" value="true" />
-  </component>
-  <component name="RecentsManager">
-    <key name="MoveClassesOrPackagesDialog.RECENTS_KEY">
-      <recent name="github.javaguide.springbootscheduletask" />
-    </key>
-    <key name="MoveFile.RECENT_KEYS">
-      <recent name="$PROJECT_DIR$/src/main/resources/templates" />
-    </key>
-  </component>
-  <component name="RunDashboard">
-    <option name="ruleStates">
-      <list>
-        <RuleState>
-          <option name="name" value="ConfigurationTypeDashboardGroupingRule" />
-        </RuleState>
-        <RuleState>
-          <option name="name" value="StatusDashboardGroupingRule" />
-        </RuleState>
-      </list>
-    </option>
-  </component>
-  <component name="RunManager" selected="Spring Boot.SpringbootScheduleTaskApplication">
-    <configuration name="DemoApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" temporary="true" nameIsGenerated="true">
-      <module name="springboot-schedule-task" />
-      <extension name="coverage">
-        <pattern>
-          <option name="PATTERN" value="github.javaguide.springbootscheduletask.*" />
-          <option name="ENABLED" value="true" />
-        </pattern>
-      </extension>
-      <option name="SPRING_BOOT_MAIN_CLASS" value="github.javaguide.springbootscheduletask.DemoApplication" />
-      <method v="2">
-        <option name="Make" enabled="true" />
-      </method>
-    </configuration>
-    <configuration name="SpringbootScheduleTaskApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot">
-      <module name="springboot-schedule-task" />
-      <option name="SPRING_BOOT_MAIN_CLASS" value="github.javaguide.springbootscheduletask.SpringbootScheduleTaskApplication" />
-      <method v="2">
-        <option name="Make" enabled="true" />
-      </method>
-    </configuration>
-    <recent_temporary>
-      <list>
-        <item itemvalue="Spring Boot.DemoApplication" />
-      </list>
-    </recent_temporary>
-  </component>
-  <component name="SshConsoleOptionsProvider">
-    <option name="myEncoding" value="UTF-8" />
-  </component>
-  <component name="SvnConfiguration">
-    <configuration />
-  </component>
-  <component name="TaskManager">
-    <task active="true" id="Default" summary="Default task">
-      <changelist id="7fff5b14-8bce-456a-9934-0e81cd72736e" name="Default Changelist" comment="" />
-      <created>1573625912874</created>
-      <option name="number" value="Default" />
-      <option name="presentableId" value="Default" />
-      <updated>1573625912874</updated>
-      <workItem from="1573625920100" duration="921000" />
-    </task>
-    <servers />
-  </component>
-  <component name="TypeScriptGeneratedFilesManager">
-    <option name="version" value="1" />
-  </component>
-  <component name="Vcs.Log.Tabs.Properties">
-    <option name="TAB_STATES">
-      <map>
-        <entry key="MAIN">
-          <value>
-            <State />
-          </value>
-        </entry>
-      </map>
-    </option>
-  </component>
-</project>
\ No newline at end of file

From c1a4017eafcde5adc99b354071ecaa950cfaa270 Mon Sep 17 00:00:00 2001
From: guide <koushuangbwcx@163.com>
Date: Wed, 21 Apr 2021 21:56:44 +0800
Subject: [PATCH 08/22] Delete .DS_Store

---
 source-code/.DS_Store | Bin 6148 -> 0 bytes
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 source-code/.DS_Store

diff --git a/source-code/.DS_Store b/source-code/.DS_Store
deleted file mode 100644
index 7ad59510222be3cc1d27a91e171274be0e7e536c..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 6148
zcmeHK%}xR_5T5d*1!Lr3BFA35aex>I985Nd2X7{8^q>ZIT_T&{E@2mmB4%Gh-^eHM
zb)0F70nx;hG0;h7zG<h^&3sAQ9ROg>kyimI002iN%;eF0BNQiHl9ZmwATqf}05{MD
z&mBjDrPXG_Fkl$?Zw%1c<=}t^_h{$NuiFn@jJ>&oz8}Tq@;ghVGue$z&htFKoxiqw
z;n<Fycoa3A;U$euow(;}|11pqpT6Us``vN7uzM5+vEv8bp-lF@E-tyc@B=R#H^Wij
zMRFfo1vt;~T)R-5Osb7qNgR}G(~_9f>-CbTHLBBTj_>VP4o^CF508_l>GO+Xmf=Mt
zWk=%-UeQ@9^xhr@{V=%280BTbMG_+LA)+g66)Bi2@k{YDq71GJ1Nx}&t#v8b)M*$n
z4E&k_x*r58p`|fVD6bAQvIRh--v}w_Q!ha|LZhWIQHT)~rcx1AD$^|nQ|UM_^g2sp
zqEMv+)6ECdUuL>PVe;#^zmUs;SqhCc3>XF$8Az*Pf!_b)pZovCBr`G$7zS2~0hVdm
zttOVF-`1t#=&co~9aIupmnf7eX!LU|9eRq3s8TQ{$U(F;CJJ!|#rz0J8jLXv{3!z;
D4SSX|


From f7a5770b131e90f72125822771a5a14dc1ba84b4 Mon Sep 17 00:00:00 2001
From: guide <koushuangbwcx@163.com>
Date: Wed, 21 Apr 2021 21:57:44 +0800
Subject: [PATCH 09/22] Update spring-bean-validation.md

---
 docs/advanced/spring-bean-validation.md | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/docs/advanced/spring-bean-validation.md b/docs/advanced/spring-bean-validation.md
index e7a350d..dc6323d 100644
--- a/docs/advanced/spring-bean-validation.md
+++ b/docs/advanced/spring-bean-validation.md
@@ -29,7 +29,9 @@ public class PersonController {
 
 不了解的朋友一定要好好看一下,学完马上就可以实践到项目上去。
 
-并且,本文实例项目使用的是目前最新的 Spring Boot 版本 2.4.5!(截止到 2021-04-21)
+并且,本文示例项目使用的是目前最新的 Spring Boot 版本 2.4.5!(截止到 2021-04-21)
+
+示例项目源代码地址:[https://github.com/CodingDocs/springboot-guide/tree/master/source-code/bean-validation-demo](https://github.com/CodingDocs/springboot-guide/tree/master/source-code/bean-validation-demo) 。
 
 ## 添加相关依赖
 

From df3a24817a7b89629868e6e4b51e8580804d4f1e Mon Sep 17 00:00:00 2001
From: guide <koushuangbwcx@163.com>
Date: Wed, 21 Apr 2021 22:05:49 +0800
Subject: [PATCH 10/22] =?UTF-8?q?[feat]=E6=96=87=E4=BB=B6=E4=BD=8D?=
 =?UTF-8?q?=E7=BD=AE=E7=A7=BB=E5=8A=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 docs/{advanced => }/spring-bean-validation.md |  0
 docs/start/springboot-hello-world.md          | 53 ++++++++++---------
 2 files changed, 27 insertions(+), 26 deletions(-)
 rename docs/{advanced => }/spring-bean-validation.md (100%)

diff --git a/docs/advanced/spring-bean-validation.md b/docs/spring-bean-validation.md
similarity index 100%
rename from docs/advanced/spring-bean-validation.md
rename to docs/spring-bean-validation.md
diff --git a/docs/start/springboot-hello-world.md b/docs/start/springboot-hello-world.md
index 5a8d16d..9d6d49a 100644
--- a/docs/start/springboot-hello-world.md
+++ b/docs/start/springboot-hello-world.md
@@ -1,22 +1,26 @@
-### 新建 Spring Boot 项目常用的两种方式
+示例项目源代码地址:[https://github.com/CodingDocs/springboot-guide/tree/master/source-code/hello-world](https://github.com/CodingDocs/springboot-guide/tree/master/source-code/hello-world) 。
 
-你可以通过 https://start.spring.io/ 这个网站来生成一个 Spring Boot 的项目。
+## 新建 Spring Boot 项目
 
-![start.spring.io](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/spring.start.io.png)
+**1. 你可以通过 [https://start.spring.io/](https://start.spring.io/) 这个网站来生成一个 Spring Boot 的项目。**
+
+![start.spring.io](https://img-blog.csdnimg.cn/img_convert/69f88b648b5f94fe8fa509fc4d987057.png)
 
 注意勾选上 Spring Web 这个模块,这是我们所必需的一个依赖。当所有选项都勾选完毕之后,点击下方的按钮 Generate 下载这个 Spring Boot 的项目。下载完成并解压之后,我们直接使用 IDEA 打开即可。
 
-当然你也可以直接通过 IDEA 来生成一个 Spring Boot 的项目,具体方法和上面类似:`File->New->Project->Spring Initializr`。
+另外,阿里也有一个类似的网站 [https://start.aliyun.com/bootstrap.html](https://start.aliyun.com/bootstrap.html) ,功能甚至还要更强大点,支持生成特定应用架构的项目模板!
 
-### Spring Boot 项目结构分析
+![](https://img-blog.csdnimg.cn/20210421220259726.png)
 
-成功打开项目之后,项目长下面这个样子:
+**2.你也可以直接通过 IDEA 来生成一个 Spring Boot 的项目,具体方法和上面类似:`File->New->Project->Spring Initializr`。**
 
-<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/springboot-hellowold-structure.png" style="zoom:50%;" />
+## Spring Boot 项目结构分析
 
+成功打开项目之后,项目长下面这个样子:
 
+![](https://img-blog.csdnimg.cn/img_convert/1f1a3ee40347e941891988cbb72ceee1.png#pic_center)
 
-以 Application为后缀名的 Java 类一般就是 Spring Boot 的启动类,比如本项目的启动项目就是`HelloWorldApplication` 。我们直接像运行普通 Java 程序一样运行它,由于 Spring Boot 本身就嵌入servlet容器的缘故,我们的 web 项目就运行成功了, 非常方便。
+以 Application 为后缀名的 Java 类一般就是 Spring Boot 的启动类,比如本项目的启动项目就是`HelloWorldApplication` 。我们直接像运行普通 Java 程序一样运行它,由于 Spring Boot 本身就嵌入 servlet 容器的缘故,我们的 web 项目就运行成功了, 非常方便。
 
 需要注意的一点是 **Spring Boot 的启动类是需要最外层的,不然可能导致一些类无法被正确扫描到,导致一些奇怪的问题。** 一般情况下 Spring Boot 项目结构类似下面这样
 
@@ -35,19 +39,19 @@ com
       |
       +- controller
       |  +- CustomerController.java
-      |  
+      |
       +- config
       |  +- swagerConfig.java
       |
 ```
 
-1. ` Application.java`是项目的启动类
-2. domain目录主要用于实体(Entity)与数据访问层(Repository)
+1. `Application.java`是项目的启动类
+2. domain 目录主要用于实体(Entity)与数据访问层(Repository)
 3. service 层主要是业务类代码
 4. controller 负责页面访问控制
 5. config 目录主要放一些配置类
 
-### @SpringBootApplication 注解分析
+## @SpringBootApplication 注解分析
 
 `HelloWorldApplication`
 
@@ -91,21 +95,21 @@ public @interface SpringBootConfiguration {
 }
 ```
 
-可以看出大概可以把 `@SpringBootApplication `看作是 `@Configuration`、`@EnableAutoConfiguration`、`@ComponentScan ` 注解的集合。根据 SpringBoot官网,这三个注解的作用分别是:
+可以看出大概可以把 `@SpringBootApplication`看作是 `@Configuration`、`@EnableAutoConfiguration`、`@ComponentScan` 注解的集合。根据 SpringBoot 官网,这三个注解的作用分别是:
 
 - `@EnableAutoConfiguration`:启用 SpringBoot 的自动配置机制
-- `@ComponentScan`: 扫描被`@Component` (`@Service`,`@Controller`)注解的bean,注解默认会扫描该类所在的包下所有的类。
-- `@Configuration`:允许在上下文中注册额外的bean或导入其他配置类。
+- `@ComponentScan`: 扫描被`@Component` (`@Service`,`@Controller`)注解的 bean,注解默认会扫描该类所在的包下所有的类。
+- `@Configuration`:允许在上下文中注册额外的 bean 或导入其他配置类。
 
-所以说 `@SpringBootApplication `就是几个重要的注解的组合,为什么要有它?当然是为了省事,避免了我们每次开发 Spring Boot 项目都要写一些必备的注解。这一点在我们平时开发中也经常用到,比如我们通常会提一个测试基类,这个基类包含了我们写测试所需要的一些基本的注解和一些依赖。
+所以说 `@SpringBootApplication`就是几个重要的注解的组合,为什么要有它?当然是为了省事,避免了我们每次开发 Spring Boot 项目都要写一些必备的注解。这一点在我们平时开发中也经常用到,比如我们通常会提一个测试基类,这个基类包含了我们写测试所需要的一些基本的注解和一些依赖。
 
-### 新建一个 Controller
+## 新建一个 Controller
 
 上面说了这么多,我们现在正式开始写 Spring Boot 版的 “Hello World” 吧。
 
-新建一个 controller 文件夹,并在这个文件夹下新建一个名字叫做 `HelloWorldController` 的类。
+新建一个 `controller` 文件夹,并在这个文件夹下新建一个名字叫做 `HelloWorldController` 的类。
 
-`@RestController`是Spring 4 之后新加的注解,如果在Spring4之前开发 RESTful Web服务的话,你需要使用`@Controller` 并结合`@ResponseBody`注解,也就是说`@Controller` +`@ResponseBody`= `@RestController`。对于这两个注解,我在基础篇单独抽了一篇文章来介绍。
+`@RestController`是 Spring 4 之后新加的注解,如果在 Spring4 之前开发 RESTful Web 服务的话,你需要使用`@Controller` 并结合`@ResponseBody`注解,也就是说`@Controller` +`@ResponseBody`= `@RestController`。对于这两个注解,我在基础篇单独抽了一篇文章来介绍。
 
 `com.example.helloworld.controller`
 
@@ -134,9 +138,9 @@ public class HelloWorldController {
 server.port=8333
 ```
 
-### 大功告成,运行项目
+## 大功告成,运行项目
 
-运行 `HelloWorldApplication` ,运行成功控制台会打印出一些消息,不要忽略这些消息,它里面会有一些比较有用的信息。 
+运行 `HelloWorldApplication` ,运行成功控制台会打印出一些消息,不要忽略这些消息,它里面会有一些比较有用的信息。
 
 ```
 2019-10-03 09:24:47.757  INFO 26326 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8333 (http) with context path ''
@@ -146,9 +150,6 @@ server.port=8333
 
 浏览器 http://localhost:8333/test/hello 如果你可以在页面正确得到 "Hello World" 的话,说明你已经成功完成了这部分内容。
 
-### 总结
-
-通过本文我们学到了如何新建 Spring Boot 项目、SpringBoot 项目常见的项目结构分析、`@SpringBootApplication` 注解分析,最后实现了 Spring Boot 版的 "Hello World"。
-
-代码地址: [https://github.com/Snailclimb/springboot-guide/tree/master/source-code/start/hello-world](https://github.com/Snailclimb/springboot-guide/tree/master/source-code/start/hello-world)(建议自己手敲一遍!!!)
+## 总结
 
+通过本文我们学到了如何新建 Spring Boot 项目、SpringBoot 项目常见的项目结构分析、`@SpringBootApplication` 注解分析,最后实现了 Spring Boot 版的 "Hello World"。
\ No newline at end of file

From 90536114964eaa324ac0fd07131854d0a7935cb9 Mon Sep 17 00:00:00 2001
From: guide <koushuangbwcx@163.com>
Date: Wed, 21 Apr 2021 22:06:18 +0800
Subject: [PATCH 11/22] Update README.md

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index e13f958..f3f2212 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,7 @@
 ### 进阶
 
 1. Bean映射工具 :[Bean映射工具之Apache BeanUtils VS Spring BeanUtils](./docs/advanced/Apache-BeanUtils-VS-SpringBean-Utils.md) 、[5种常见Bean映射工具的性能比对](./docs/advanced/Performance-of-Java-Mapping-Frameworks.md)
-3. [如何在 Spring/Spring Boot 中做参数校验?](./docs/advanced/spring-bean-validation.md)
+3. [如何在 Spring/Spring Boot 中做参数校验?](./docs/spring-bean-validation.md)
 4. [5分钟搞懂如何在Spring Boot中Schedule Tasks](./docs/advanced/SpringBoot-ScheduleTasks.md) 
 5. [新手也能看懂的 Spring Boot 异步编程指南](./docs/advanced/springboot-async.md)
 7. [Kafka 入门+SpringBoot整合Kafka系列](https://github.com/Snailclimb/springboot-kafka)

From 74188190bfbf0b6c8de4fb36bcd7dd0d801e66d8 Mon Sep 17 00:00:00 2001
From: guide <koushuangbwcx@163.com>
Date: Wed, 21 Apr 2021 22:29:03 +0800
Subject: [PATCH 12/22] =?UTF-8?q?[feat]=E4=BD=BF=E7=94=A8=20PowerMockRunne?=
 =?UTF-8?q?r=20=E5=92=8C=20Mockito=20=E7=BC=96=E5=86=99=E5=8D=95=E5=85=83?=
 =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 README.md                               |   9 +-
 docs/PowerMockRunnerAndMockito.md       | 161 +++++++++++++++++++++
 docs/basis/PowerMockRunnerAndMockito.md | 177 ------------------------
 3 files changed, 166 insertions(+), 181 deletions(-)
 create mode 100644 docs/PowerMockRunnerAndMockito.md
 delete mode 100644 docs/basis/PowerMockRunnerAndMockito.md

diff --git a/README.md b/README.md
index f3f2212..73eeb7f 100644
--- a/README.md
+++ b/README.md
@@ -30,12 +30,13 @@
 ### 进阶
 
 1. Bean映射工具 :[Bean映射工具之Apache BeanUtils VS Spring BeanUtils](./docs/advanced/Apache-BeanUtils-VS-SpringBean-Utils.md) 、[5种常见Bean映射工具的性能比对](./docs/advanced/Performance-of-Java-Mapping-Frameworks.md)
-3. [如何在 Spring/Spring Boot 中做参数校验?](./docs/spring-bean-validation.md)
+3. [如何在 Spring/Spring Boot 中优雅地做参数校验?](./docs/spring-bean-validation.md)
+3. [使用 PowerMockRunner 和 Mockito 编写单元测试用例](./docs/PowerMockRunnerAndMockito.md)
 4. [5分钟搞懂如何在Spring Boot中Schedule Tasks](./docs/advanced/SpringBoot-ScheduleTasks.md) 
 5. [新手也能看懂的 Spring Boot 异步编程指南](./docs/advanced/springboot-async.md)
-7. [Kafka 入门+SpringBoot整合Kafka系列](https://github.com/Snailclimb/springboot-kafka)
-8. [超详细,新手都能看懂 !使用Spring Boot+Dubbo 搭建一个分布式服务](./docs/advanced/springboot-dubbo.md)
-9. [从零入门 !Spring Security With JWT(含权限验证)](https://github.com/Snailclimb/spring-security-jwt-guide)
+6. [Kafka 入门+SpringBoot整合Kafka系列](https://github.com/Snailclimb/springboot-kafka)
+7. [超详细,新手都能看懂 !使用Spring Boot+Dubbo 搭建一个分布式服务](./docs/advanced/springboot-dubbo.md)
+8. [从零入门 !Spring Security With JWT(含权限验证)](https://github.com/Snailclimb/spring-security-jwt-guide)
 
 ### 补充
 
diff --git a/docs/PowerMockRunnerAndMockito.md b/docs/PowerMockRunnerAndMockito.md
new file mode 100644
index 0000000..469e075
--- /dev/null
+++ b/docs/PowerMockRunnerAndMockito.md
@@ -0,0 +1,161 @@
+单元测试可以提高测试开发的效率,减少代码错误率,提高代码健壮性,提高代码质量。在 Spring 框架中常用的两种测试框架:`PowerMockRunner` 和 `SpringRunner` 两个单元测试,鉴于 `SpringRunner` 启动的一系列依赖和数据连接的问题,推荐使用 `PowerMockRunner`,这样能有效的提高测试的效率,并且其提供的 API 能覆盖的场景广泛,使用方便,可谓是 Java 单元测试之模拟利器。
+
+## 1. PowerMock 是什么?
+
+`PowerMock` 是一个 Java 模拟框架,可用于解决通常认为很难甚至无法测试的测试问题。使用 `PowerMock`,可以模拟静态方法,删除静态初始化程序,允许模拟而不依赖于注入,等等。`PowerMock` 通过在执行测试时在运行时修改字节码来完成这些技巧。`PowerMock` 还包含一些实用程序,可让您更轻松地访问对象的内部状态。
+
+举个例子,你在使用 `Junit` 进行单元测试时,并不想让测试数据进入数据库,怎么办?这个时候就可以使用 `PowerMock`,拦截数据库操作,并模拟返回参数。
+
+## 2. PowerMock 包引入
+
+```xml
+<!-- 单元测试 依赖-->
+<dependency>
+    <groupId>org.powermock</groupId>
+    <artifactId>powermock-core</artifactId>
+    <version>2.0.2</version>
+    <scope>test</scope>
+</dependency>
+<dependency>
+  <groupId>org.mockito</groupId>
+  <artifactId>mockito-core</artifactId>
+  <version>2.23.0</version>
+</dependency>
+<dependency>
+  <groupId>org.powermock</groupId>
+  <artifactId>powermock-module-junit4</artifactId>
+  <version>2.0.4</version>
+  <scope>test</scope>
+</dependency>
+<dependency>
+  <groupId>org.powermock</groupId>
+  <artifactId>powermock-api-mockito2</artifactId>
+  <version>2.0.2</version>
+  <scope>test</scope>
+</dependency>
+<dependency>
+  <groupId>com.github.jsonzou</groupId>
+  <artifactId>jmockdata</artifactId>
+  <version>4.3.0</version>
+</dependency>
+```
+
+## 3. 重要注解说明
+
+```java
+@RunWith(PowerMockRunner.class) // 告诉JUnit使用PowerMockRunner进行测试
+@PrepareForTest({RandomUtil.class}) // 所有需要测试的类列在此处,适用于模拟final类或有final, private, static, native方法的类
+@PowerMockIgnore("javax.management.*") //为了解决使用powermock后,提示classloader错误
+```
+
+## 4. 使用示例
+
+### 4.1 模拟接口返回
+
+首先对接口进行 mock,然后录制相关行为
+
+```java
+InterfaceToMock mock = Powermockito.mock(InterfaceToMock.class)
+Powermockito.when(mock.method(Params…)).thenReturn(value)
+Powermockito.when(mock.method(Params..)).thenThrow(Exception)
+```
+
+### 4.2 设置对象的 private 属性
+
+需要使用 `Whitebox` 向 class 或者对象中赋值。
+
+如我们已经对接口尽心了 mock,现在需要将此 mock 加入到对象中,可以采用如下方法:
+
+```java
+Whitebox.setInternalState(Object object, String fieldname, Object… value);
+```
+
+其中 object 为需要设置属性的静态类或对象。
+
+### 4.3 模拟构造函数
+
+对于模拟构造函数,也即当出现 `new InstanceClass()` 时可以将此构造函数拦截并替换结果为我们需要的 mock 对象。
+
+注意:使用时需要加入标记:
+
+```java
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({ InstanceClass.class })
+@PowerMockIgnore("javax.management.\*")
+
+Powermockito.whenNew(InstanceClass.class).thenReturn(Object value)
+```
+
+##### 4.4 模拟静态方法
+
+模拟静态方法类似于模拟构造函数,也需要加入注释标记。
+
+```java
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({ StaticClassToMock.class })
+@PowerMockIgnore("javax.management.\*")
+
+Powermockito.mockStatic(StaticClassToMock.class);
+Powermockito.when(StaticClassToMock.method(Object.. params)).thenReturn(Object value)
+```
+
+##### 4.5 模拟 final 方法
+
+Final 方法的模拟类似于模拟静态方法。
+
+```java
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({ FinalClassToMock.class })
+@PowerMockIgnore("javax.management.\*")
+
+Powermockito.mockStatic(FinalClassToMock.class);
+Powermockito.when(StaticClassToMock.method(Object.. params)).thenReturn(Object value)
+```
+
+### 4.6 模拟静态类
+
+模拟静态类类似于模拟静态方法。
+
+### 4.7 使用 spy 方法避免执行被测类中的成员函数
+
+如被测试类为:TargetClass,想要屏蔽的方法为 targetMethod.
+
+```java
+1) PowerMockito.spy(TargetClass.class);
+
+2) Powemockito.when(TargetClass.targetMethod()).doReturn()
+
+3) 注意加入
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(DisplayMoRelationBuilder.class)
+@PowerMockIgnore("javax.management.*")
+```
+
+### 4.8 参数匹配器
+
+有时我们在处理 `doMethod(Param param)` 时,不想进行精确匹配,这时可以使用 `Mockito` 提供的模糊匹配方式。
+
+如:`Mockito.anyInt()`,`Mockito.anyString()`
+
+### 4.9 处理 public void 型的静态方法
+
+```java
+Powermockito.doNothing.when(T class2mock, String method, <T>… params>
+```
+
+## 5. 单元测试用例可选清单
+
+输入数据验证:这些检查通常可以对输入到应用程序系统中的数据采用。
+
+- 必传项测试
+- 唯一字段值测试
+- 空值测试
+- 字段只接受允许的字符
+- 负值测试
+- 字段限于字段长度规范
+- 不可能的值
+- 垃圾值测试
+- 检查字段之间的依赖性
+- 等效类划分和边界条件测试
+- 错误和异常处理测试单元测试可以提高测试开发的效率,减少代码错误率,提高代码健壮性,提高代码质量。在 Spring 框架中常用的两种测试框架:PowerMockRunner 和 SpringRunner 两个单元测试,鉴于 SpringRunner 启动的一系列依赖和数据连接的问题,推荐使用 PowerMockRunner,这样能有效的提高测试的效率,并且其提供的 API 能覆盖的场景广泛,使用方便,可谓是 Java 单元测试之模拟利器。
\ No newline at end of file
diff --git a/docs/basis/PowerMockRunnerAndMockito.md b/docs/basis/PowerMockRunnerAndMockito.md
deleted file mode 100644
index d5a7b12..0000000
--- a/docs/basis/PowerMockRunnerAndMockito.md
+++ /dev/null
@@ -1,177 +0,0 @@
-单元测试可以提高测试开发的效率,减少代码错误率,提高代码健壮性,提高代码质量。在Spring框架中常用的两种测试框架:PowerMockRunner和SpringRunner两个单元测试,鉴于SpringRunner启动的一系列依赖和数据连接的问题,推荐使用PowerMockRunner,这样能有效的提高测试的效率,并且其提供的API能覆盖的场景广泛,使用方便,可谓是Java单元测试之模拟利器。
-
-#### 1. PowerMock是什么?
-
-PowerMock是一个Java模拟框架,可用于解决通常认为很难甚至无法测试的测试问题。使用PowerMock,可以模拟静态方法,删除静态初始化程序,允许模拟而不依赖于注入,等等。PowerMock通过在执行测试时在运行时修改字节码来完成这些技巧。PowerMock还包含一些实用程序,可让您更轻松地访问对象的内部状态。
-
-
-举个例子,你在使用Junit进行单元测试时,并不想让测试数据进入数据库,怎么办?这个时候就可以使用PowerMock,拦截数据库操作,并模拟返回参数。
-
-#### 2. PowerMock包引入
-
-```xml
-<!-- 单元测试 依赖-->
-<dependency>
-    <groupId>org.powermock</groupId>
-    <artifactId>powermock-core</artifactId>
-    <version>2.0.2</version>
-    <scope>test</scope>
-</dependency>
-<dependency>
-<groupId>org.mockito</groupId>
-<artifactId>mockito-core</artifactId>
-<version>2.23.0</version>
-</dependency>
-<dependency>
-<groupId>org.powermock</groupId>
-<artifactId>powermock-module-junit4</artifactId>
-<version>2.0.4</version>
-<scope>test</scope>
-</dependency>
-<dependency>
-<groupId>org.powermock</groupId>
-<artifactId>powermock-api-mockito2</artifactId>
-<version>2.0.2</version>
-<scope>test</scope>
-</dependency>
-<dependency>
-<groupId>com.github.jsonzou</groupId>
-<artifactId>jmockdata</artifactId>
-<version>4.3.0</version>
-</dependency>
-        <!-- 单元测试 依赖-->
-```
-
-#### 3. 重要注解说明
-
-```java
-@RunWith(PowerMockRunner.class) // 告诉JUnit使用PowerMockRunner进行测试
-@PrepareForTest({RandomUtil.class}) // 所有需要测试的类列在此处,适用于模拟final类或有final, private, static, native方法的类
-@PowerMockIgnore("javax.management.*") //为了解决使用powermock后,提示classloader错误
-```
-
-
-
-#### 4. 使用示例
-
-##### 4.1 模拟接口返回
-
-首先对接口进行mock,然后录制相关行为
-
-```java
-InterfaceToMock mock = Powermockito.mock(InterfaceToMock.class)
-
-Powermockito.when(mock.method(Params…)).thenReturn(value)
-
-Powermockito.when(mock.method(Params..)).thenThrow(Exception)
-```
-
-##### 4.2 设置对象的private属性
-
-需要使用whitebox向class或者对象中赋值。
-
-如我们已经对接口尽心了mock,现在需要将此mock加入到对象中,可以采用如下方法:
-
-```java
-Whitebox.setInternalState(Object object, String fieldname, Object… value);
-```
-
-其中object为需要设置属性的静态类或对象。
-
-##### 4.3 模拟构造函数
-
-对于模拟构造函数,也即当出现new InstanceClass()时可以将此构造函数拦截并替换结果为我们需要的mock对象。
-
-注意:使用时需要加入标记:
-
-```java
-@RunWith(PowerMockRunner.class)
-
-@PrepareForTest({ InstanceClass.class })
-
-@PowerMockIgnore("javax.management.\*")
-
-Powermockito.whenNew(InstanceClass.class).thenReturn(Object value)
-```
-
-##### 4.4 模拟静态方法
-
-模拟静态方法类似于模拟构造函数,也需要加入注释标记。
-
-```java
-@RunWith(PowerMockRunner.class)
-
-@PrepareForTest({ StaticClassToMock.class })
-
-@PowerMockIgnore("javax.management.\*")
-
-Powermockito.mockStatic(StaticClassToMock.class);
-
-        Powermockito.when(StaticClassToMock.method(Object.. params)).thenReturn(Object value)
-```
-
-##### 4.5 模拟final方法
-
-Final方法的模拟类似于模拟静态方法。
-
-```java
-@RunWith(PowerMockRunner.class)
-
-@PrepareForTest({ FinalClassToMock.class })
-
-@PowerMockIgnore("javax.management.\*")
-
-Powermockito.mockStatic(FinalClassToMock.class);
-
-        Powermockito.when(StaticClassToMock.method(Object.. params)).thenReturn(Object value)
-```
-
-##### 4.6 模拟静态类
-
-模拟静态类类似于模拟静态方法。
-
-##### 4.7 使用spy方法避免执行被测类中的成员函数
-
-如被测试类为:TargetClass,想要屏蔽的方法为targetMethod.
-
-```java
-1) PowerMockito.spy(TargetClass.class);
-
-2) Powemockito.when(TargetClass.targetMethod()).doReturn()
-
-3) 注意加入
-
-@RunWith(PowerMockRunner.class)
-
-@PrepareForTest(DisplayMoRelationBuilder.class)
-
-@PowerMockIgnore("javax.management.*")
-```
-
-##### 4.8 参数匹配器
-
-有时我们在处理doMethod(Param param)时,不想进行精确匹配,这时可以使用Mockito提供的模糊匹配方式。
-
-如:Mockito.anyInt(),Mockito.anyString()
-
-##### 4.9 处理public void型的静态方法
-
-```java
-Powermockito.doNothing.when(T class2mock, String method, <T>… params>
-```
-
-#### 5. 单元测试用例可选清单
-
-输入数据验证:这些检查通常可以对输入到应用程序系统中的数据采用。
-
-- 必传项测试
-- 唯一字段值测试
-- 空值测试
-- 字段只接受允许的字符
-- 负值测试
-- 字段限于字段长度规范
-- 不可能的值
-- 垃圾值测试
-- 检查字段之间的依赖性
-- 等效类划分和边界条件测试
-- 错误和异常处理测试
\ No newline at end of file

From c365d999fbff2c03d345b503f746551b3b9ee6d7 Mon Sep 17 00:00:00 2001
From: zhouweixu <zhouweixu@tianan-life.com>
Date: Fri, 23 Apr 2021 15:33:42 +0800
Subject: [PATCH 13/22] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=AF=B7=E6=B1=82?=
 =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E8=B7=AF=E5=BE=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../example/beanvalidationdemo/PersonControllerTest.java    | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/source-code/bean-validation-demo/src/test/java/com/example/beanvalidationdemo/PersonControllerTest.java b/source-code/bean-validation-demo/src/test/java/com/example/beanvalidationdemo/PersonControllerTest.java
index f020f05..4f7e594 100644
--- a/source-code/bean-validation-demo/src/test/java/com/example/beanvalidationdemo/PersonControllerTest.java
+++ b/source-code/bean-validation-demo/src/test/java/com/example/beanvalidationdemo/PersonControllerTest.java
@@ -42,7 +42,7 @@ public void should_check_person_value() throws Exception {
                 .classId("82938390")
                 .region("Shanghai")
                 .phoneNumber("1816313815").build();
-        mockMvc.perform(post("/api/person")
+        mockMvc.perform(post("/api/persons")
                 .contentType(MediaType.APPLICATION_JSON)
                 .content(objectMapper.writeValueAsString(personRequest)))
                 .andExpect(MockMvcResultMatchers.jsonPath("sex").value("sex 值不在可选范围"))
@@ -53,7 +53,7 @@ public void should_check_person_value() throws Exception {
 
     @Test
     public void should_check_path_variable() throws Exception {
-        mockMvc.perform(get("/api/person/6")
+        mockMvc.perform(get("/api/persons/6")
                 .contentType(MediaType.APPLICATION_JSON))
                 .andExpect(status().isBadRequest())
                 .andExpect(content().string("getPersonByID.id: 超过 id 的范围了"));
@@ -61,7 +61,7 @@ public void should_check_path_variable() throws Exception {
 
     @Test
     public void should_check_request_param_value2() throws Exception {
-        mockMvc.perform(put("/api/person")
+        mockMvc.perform(put("/api/persons")
                 .param("name", "snailclimbsnailclimb")
                 .contentType(MediaType.APPLICATION_JSON))
                 .andExpect(status().isBadRequest())

From 4842b8b92da407c95e5f3959d195b899056b3658 Mon Sep 17 00:00:00 2001
From: guide <koushuangbwcx@163.com>
Date: Wed, 21 Apr 2021 22:33:02 +0800
Subject: [PATCH 14/22] Delete spring-security-login-authentication-process.md

---
 ...g-security-login-authentication-process.md | 196 ------------------
 1 file changed, 196 deletions(-)
 delete mode 100644 docs/advanced/spring-security-login-authentication-process.md

diff --git a/docs/advanced/spring-security-login-authentication-process.md b/docs/advanced/spring-security-login-authentication-process.md
deleted file mode 100644
index 89d0328..0000000
--- a/docs/advanced/spring-security-login-authentication-process.md
+++ /dev/null
@@ -1,196 +0,0 @@
-Spring Security的登录主要是由一系列的过滤器组成,我们如果需要修改登录的校验逻辑,只需要在过滤器链路上添加修改相关的逻辑即可。这里主要通过Spring Security的源码来了解相关的认证登录的逻辑。
-
-#### 1.Spring Security的认证流程
-
-主要分析:
-
-1. 认证用户的流程
-2. 如何进行认证校验
-3. 认证成功后怎么获取用户信息
-
-具体的过滤器链路如下所示:
-
-[![cT2G4g.png](https://z3.ax1x.com/2021/04/19/cT2G4g.png)](https://imgtu.com/i/cT2G4g)
-
-Spring Security的认证流程图如下,认证的主要过程有:
-
-1. 用户提交用户名和密码,然后通过UsernamePasswordAuthenticationFilter对其进行封装成为UsernamePasswordAuthenticationToken对象,这个是AbstractAuthenticationToken的子类,而AbstractAuthenticationToken又是Authentication的一个实现,所以可以看到后续获取的都是Authentication类型的对象实例;
-2. 将第一步的UsernamePasswordAuthenticationToken对象传递给AuthenticationManager;
-3. 通过AbstractUserDetailsAuthenticationProvider的默认实现类DaoAuthenticationProvider的retrieveUser方法,这个方法会调用UserDetailsService的loadUserByUsername方法来进行用户名和密码的判断,使用的默认的逻辑进行处理;
-4. 将成功认证后的用户信息放入到SecurityContextHolder中,之后可以通过SecurityContext获取用户的相关信息。
-
-[![coGpvR.png](https://z3.ax1x.com/2021/04/19/coGpvR.png)](https://imgtu.com/i/coGpvR)
-
-spring-security源码下载地址:
-
-```java
-https://github.com/spring-projects/spring-security
-```
-
-#### 2.Spring Security的认证源码分析
-
-##### 2.1 搭建项目并访问
-
-首先我们搭建一个Spring Security的项目,使用Spring Boot可以很方便的进行集成开发,主要引入如下的依赖即可(当然也可以查看官网,选择合适的版本):
-
-```java
-<dependency>
-  <groupId>org.springframework.boot</groupId>
-  <artifactId>spring-boot-starter-security</artifactId>
-</dependency>
-```
-
-启动项目后会随机生成一个密码串,这里需要复制保存以便登录的时候使用:
-
-[![coJ0ld.png](https://z3.ax1x.com/2021/04/19/coJ0ld.png)](https://imgtu.com/i/coJ0ld)
-
-访问登录地址:
-
-```java
-http://localhost:8080/login
-```
-
-[![coJfpQ.png](https://z3.ax1x.com/2021/04/19/coJfpQ.png)](https://imgtu.com/i/coJfpQ)
-
-默认的账户名和密码:
-
-```java
-账户名: user
-密码:   项目启动时生成的密码串
-```
-
-##### 2.2 进行源码分析
-
-1. 进行断点后会发现首先进入的是UsernamePasswordAuthenticationFilter的attemptAuthentication(HttpServletRequest request, HttpServletResponse response)方法,会对用户名和密码进行封装成UsernamePasswordAuthenticationToken对象,然后调用this.getAuthenticationManager().authenticate(authRequest)方法进入到AuthenticationManager中。
-
-   attemptAuthentication方法源码如下所示:
-
-```java
-@Override
-	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
-			throws AuthenticationException {
-		if (this.postOnly && !request.getMethod().equals("POST")) {
-			throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
-		}
-		String username = obtainUsername(request);
-		username = (username != null) ? username : "";
-		username = username.trim();
-		String password = obtainPassword(request);
-		password = (password != null) ? password : "";
-		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
-		// Allow subclasses to set the "details" property
-		setDetails(request, authRequest);
-		return this.getAuthenticationManager().authenticate(authRequest);
-	}
-```
-
-2. 随后请求进入到WebSecurityConfigurerAdapter的AuthenticationManagerDelegator中,AuthenticationManagerDelegator是AuthenticationManager的一个子类,最后封装成为UsernamePasswordAuthenticationToken对象,供DaoAuthenticationProvider使用。
-
-   AuthenticationManagerDelegator的源码如下:
-
-   ```java
-   static final class AuthenticationManagerDelegator implements AuthenticationManager {
-           private AuthenticationManagerBuilder delegateBuilder;
-           private AuthenticationManager delegate;
-           private final Object delegateMonitor = new Object();
-           private Set<String> beanNames;
-   
-           AuthenticationManagerDelegator(AuthenticationManagerBuilder delegateBuilder, ApplicationContext context) {
-               Assert.notNull(delegateBuilder, "delegateBuilder cannot be null");
-               Field parentAuthMgrField = ReflectionUtils.findField(AuthenticationManagerBuilder.class, "parentAuthenticationManager");
-               ReflectionUtils.makeAccessible(parentAuthMgrField);
-               this.beanNames = getAuthenticationManagerBeanNames(context);
-               validateBeanCycle(ReflectionUtils.getField(parentAuthMgrField, delegateBuilder), this.beanNames);
-               this.delegateBuilder = delegateBuilder;
-           }
-   
-           public Authentication authenticate(Authentication authentication) throws AuthenticationException {
-               if (this.delegate != null) {
-                   return this.delegate.authenticate(authentication);
-               } else {
-                   synchronized(this.delegateMonitor) {
-                       if (this.delegate == null) {
-                           this.delegate = (AuthenticationManager)this.delegateBuilder.getObject();
-                           this.delegateBuilder = null;
-                       }
-                   }
-   
-                   return this.delegate.authenticate(authentication);
-               }
-           }
-   
-           private static Set<String> getAuthenticationManagerBeanNames(ApplicationContext applicationContext) {
-               String[] beanNamesForType = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, AuthenticationManager.class);
-               return new HashSet(Arrays.asList(beanNamesForType));
-           }
-   
-           private static void validateBeanCycle(Object auth, Set<String> beanNames) {
-               if (auth != null && !beanNames.isEmpty() && auth instanceof Advised) {
-                   TargetSource targetSource = ((Advised)auth).getTargetSource();
-                   if (targetSource instanceof LazyInitTargetSource) {
-                       LazyInitTargetSource lits = (LazyInitTargetSource)targetSource;
-                       if (beanNames.contains(lits.getTargetBeanName())) {
-                           throw new FatalBeanException("A dependency cycle was detected when trying to resolve the AuthenticationManager. Please ensure you have configured authentication.");
-                       }
-                   }
-               }
-           }
-       }
-   ```
-
-   org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration.AuthenticationManagerDelegator#authenticate
-
-   ```java
-   @Override
-   		public Authentication authenticate(Authentication authentication) throws AuthenticationException {
-   			if (this.delegate != null) {
-   				return this.delegate.authenticate(authentication);
-   			}
-   			synchronized (this.delegateMonitor) {
-   				if (this.delegate == null) {
-   					this.delegate = this.delegateBuilder.getObject();
-   					this.delegateBuilder = null;
-   				}
-   			}
-   			return this.delegate.authenticate(authentication);
-   		}
-   ```
-
-3. 进入到DaoAuthenticationProvider的retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)方法进行用户的认证,这里的认证主要会调用默认的UserDetailsService对用户名和密码进行校验,如果是使用的类似于Mysql的数据源,其默认的实现是JdbcDaoImpl。
-
-   org.springframework.security.authentication.dao.DaoAuthenticationProvider#retrieveUser
-
-   ```java
-   @Override
-   	protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
-   			throws AuthenticationException {
-   		prepareTimingAttackProtection();
-   		try {
-   			UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
-   			if (loadedUser == null) {
-   				throw new InternalAuthenticationServiceException(
-   						"UserDetailsService returned null, which is an interface contract violation");
-   			}
-   			return loadedUser;
-   		}
-   		catch (UsernameNotFoundException ex) {
-   			mitigateAgainstTimingAttack(authentication);
-   			throw ex;
-   		}
-   		catch (InternalAuthenticationServiceException ex) {
-   			throw ex;
-   		}
-   		catch (Exception ex) {
-   			throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
-   		}
-   	}
-   
-   ```
-
-4. 将上一步认证后的用户实例放入SecurityContextHolder中,至此我们可以很方便的从SecurityContextHolder中获取用户信息,方法如下:
-
-   ```java
-      Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
-   ```
-
-   

From 57f2cf90807ae4e04956da4c102becd859bed888 Mon Sep 17 00:00:00 2001
From: Sin <42839671+Fenmul@users.noreply.github.com>
Date: Mon, 26 Apr 2021 17:19:03 +0800
Subject: [PATCH 15/22] Update spring-bean-validation.md
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

修改错别字 「自定以」
补充 「Validator 编程方式手动进行参数验证」 中遗漏代码
---
 docs/spring-bean-validation.md | 22 +++++++++++++---------
 1 file changed, 13 insertions(+), 9 deletions(-)

diff --git a/docs/spring-bean-validation.md b/docs/spring-bean-validation.md
index dc6323d..5839a28 100644
--- a/docs/spring-bean-validation.md
+++ b/docs/spring-bean-validation.md
@@ -333,13 +333,17 @@ Validator validate
 具体使用情况如下:
 
 ```java
-ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
-Validator validator = factory.getValidator()
-PersonRequest personRequest = PersonRequest.builder().sex("Man22")
-  .classId("82938390").build();
-Set<ConstraintViolation<PersonRequest>> violations = validator.validate(personRequest);
-// 输出异常信息
-violations.forEach(constraintViolation -> System.out.println(constraintViolation.getMessage()));
+/**
+ * 手动校验对象
+ */
+@Test
+public void check_person_manually() {
+    ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+    Validator validator = factory.getValidator();
+    PersonRequest personRequest = PersonRequest.builder().sex("Man22")
+            .classId("82938390").build();
+    Set<ConstraintViolation<PersonRequest>> violations = validator.validate(personRequest);
+    violations.forEach(constraintViolation -> System.out.println(constraintViolation.getMessage()));
 }
 ```
 
@@ -350,7 +354,7 @@ sex 值不在可选范围
 name 不能为空
 ```
 
-## 自定以 Validator(实用)
+## 自定义 Validator(实用)
 
 如果自带的校验注解无法满足你的需求的话,你还可以自定义实现注解。
 
@@ -577,4 +581,4 @@ public class PersonService {
 - `@NotNull`是 JSR 303 Bean 验证批注,它与数据库约束本身无关。
 - `@Column(nullable = false)` : 是 JPA 声明列为非空的方法。
 
-总结来说就是即前者用于验证,而后者则用于指示数据库创建表的时候对表的约束。
\ No newline at end of file
+总结来说就是即前者用于验证,而后者则用于指示数据库创建表的时候对表的约束。

From 1344d0e61d5928c29cfd4050ec024baf28efe7ec Mon Sep 17 00:00:00 2001
From: guide <koushuangbwcx@163.com>
Date: Tue, 27 Apr 2021 20:35:13 +0800
Subject: [PATCH 16/22] Update README.md

---
 README.md | 21 ++++++++++++++++-----
 1 file changed, 16 insertions(+), 5 deletions(-)

diff --git a/README.md b/README.md
index 73eeb7f..e1b0fae 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,12 @@
+👍推荐[2021最新实战项目源码下载](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=100018862&idx=1&sn=858e00b60c6097e3ba061e79be472280&chksm=4ea1856579d60c73224e4d852af6b0188c3ab905069fc28f4b293963fd1ee55d2069fb229848#rd)
+
+👍[《JavaGuide 面试突击版》PDF 版本](#公众号) 。[图解计算机基础 PDF 版](#优质原创PDF资源)
+
+书单已经被移动到[awesome-cs](https://github.com/CodingDocs/awesome-cs) 这个仓库。
+
+
+
+
 <p align="center">
 <a href="https://github.com/Snailclimb/springboot-guide" target="_blank">
 	<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/spring-boot-guide.png" width=""/>
@@ -10,9 +19,13 @@
   <a href="#公众号"><img src="https://img.shields.io/badge/%E5%85%AC%E4%BC%97%E5%8F%B7-JavaGuide-lightgrey.svg" alt="公众号"></a>
   <a href="#公众号"><img src="https://img.shields.io/badge/PDF-Java面试突击-important.svg" alt="公众号"></a>
 </p>
-
 **在线阅读** :  https://snailclimb.gitee.io/springboot-guide (上面的地址访问速度缓慢的建议使用这个路径访问)
 
+**开源的目的是为了大家能一起完善,如果你觉得内容有任何需要完善/补充的地方,欢迎提交 issue/pr。**
+
+- Github地址:https://github.com/CodingDocs/springboot-guide
+- 码云地址:https://gitee.com/SnailClimb/springboot-guide(Github无法访问或者访问速度比较慢的小伙伴可以看码云上的对应内容)
+
 ## 重要知识点
 
 ### 基础
@@ -54,11 +67,9 @@
 1. 项目 logo 由 [logoly](https://logoly.pro/#/) 生成。
 2. 利用 docsify 生成文档部署在 Github Pages 和 Gitee Pages: [docsify 官网介绍](https://docsify.js.org/#/)
 
-### 联系我
-
-添加我的微信备注“Github”,回复关键字 **“加群”** 即可入群。
+### 优质原创PDF资源
 
-![个人微信](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/wechat3.jpeg)
+![](https://cdn.jsdelivr.net/gh/javaguide-tech/blog-images-2@main/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%B8%93%E4%B8%9A/image-20201027160348395.png)
 
 ### 公众号
 

From b4c41a87678724c9219d122a8c44ece8d9084691 Mon Sep 17 00:00:00 2001
From: guide <koushuangbwcx@163.com>
Date: Tue, 27 Apr 2021 20:38:37 +0800
Subject: [PATCH 17/22] Update README.md

---
 README.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/README.md b/README.md
index e1b0fae..2dda8de 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,7 @@
   <a href="#公众号"><img src="https://img.shields.io/badge/%E5%85%AC%E4%BC%97%E5%8F%B7-JavaGuide-lightgrey.svg" alt="公众号"></a>
   <a href="#公众号"><img src="https://img.shields.io/badge/PDF-Java面试突击-important.svg" alt="公众号"></a>
 </p>
+
 **在线阅读** :  https://snailclimb.gitee.io/springboot-guide (上面的地址访问速度缓慢的建议使用这个路径访问)
 
 **开源的目的是为了大家能一起完善,如果你觉得内容有任何需要完善/补充的地方,欢迎提交 issue/pr。**

From b522d00f669c3bb9216c69e6631c6a64e3be3dbf Mon Sep 17 00:00:00 2001
From: guide <koushuangbwcx@163.com>
Date: Tue, 27 Apr 2021 20:56:10 +0800
Subject: [PATCH 18/22] Update README.md

---
 README.md | 1 -
 1 file changed, 1 deletion(-)

diff --git a/README.md b/README.md
index 2dda8de..e1b0fae 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,6 @@
   <a href="#公众号"><img src="https://img.shields.io/badge/%E5%85%AC%E4%BC%97%E5%8F%B7-JavaGuide-lightgrey.svg" alt="公众号"></a>
   <a href="#公众号"><img src="https://img.shields.io/badge/PDF-Java面试突击-important.svg" alt="公众号"></a>
 </p>
-
 **在线阅读** :  https://snailclimb.gitee.io/springboot-guide (上面的地址访问速度缓慢的建议使用这个路径访问)
 
 **开源的目的是为了大家能一起完善,如果你觉得内容有任何需要完善/补充的地方,欢迎提交 issue/pr。**

From 0c9c92a309293c8a9a235b21133b107a93616ed4 Mon Sep 17 00:00:00 2001
From: guide <koushuangbwcx@163.com>
Date: Tue, 11 May 2021 12:41:19 +0800
Subject: [PATCH 19/22] =?UTF-8?q?=E6=95=B0=E6=8D=AE=E8=84=B1=E6=95=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../.gitignore                                |  33 ++
 .../.mvn/wrapper/MavenWrapperDownloader.java  | 118 +++++++
 .../.mvn/wrapper/maven-wrapper.jar            | Bin 0 -> 50710 bytes
 .../.mvn/wrapper/maven-wrapper.properties     |   2 +
 .../springboot-data-desensitization/mvnw      | 322 ++++++++++++++++++
 .../springboot-data-desensitization/mvnw.cmd  | 182 ++++++++++
 .../springboot-data-desensitization/pom.xml   |  54 +++
 ...ingbootDataDesensitizationApplication.java |  13 +
 .../JsonDesensitizationSerializer.java        |  59 ++++
 .../annotation/JsonDesensitization.java       |  25 ++
 .../desensitizer/AbstractDesensitizer.java    |  71 ++++
 .../desensitizer/Desensitizer.java            |  11 +
 .../desensitizer/DesensitizerFactory.java     |  45 +++
 .../impl/AddressDesensitizer.java             |  15 +
 .../impl/BankCardDesensitizer.java            |  15 +
 .../impl/BirthdayDesensitizer.java            |  15 +
 .../impl/DefaultDesensitizer.java             |  15 +
 .../desensitizer/impl/EmailDesensitizer.java  |  15 +
 .../desensitizer/impl/IdCardDesensitizer.java |  15 +
 .../impl/LandlineDesensitizer.java            |  15 +
 .../desensitizer/impl/MobileDesensitizer.java |  15 +
 .../impl/PasswordDesensitizer.java            |  15 +
 .../enums/DesensitizationType.java            |  46 +++
 .../exception/DesensitizationException.java   |  14 +
 .../entity/User.java                          |  35 ++
 .../src/main/resources/application.properties |   1 +
 .../JsonDesensitizationSerializerTest.java    |  33 ++
 27 files changed, 1199 insertions(+)
 create mode 100644 source-code/springboot-data-desensitization/.gitignore
 create mode 100644 source-code/springboot-data-desensitization/.mvn/wrapper/MavenWrapperDownloader.java
 create mode 100644 source-code/springboot-data-desensitization/.mvn/wrapper/maven-wrapper.jar
 create mode 100644 source-code/springboot-data-desensitization/.mvn/wrapper/maven-wrapper.properties
 create mode 100755 source-code/springboot-data-desensitization/mvnw
 create mode 100644 source-code/springboot-data-desensitization/mvnw.cmd
 create mode 100644 source-code/springboot-data-desensitization/pom.xml
 create mode 100644 source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/SpringbootDataDesensitizationApplication.java
 create mode 100644 source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/JsonDesensitizationSerializer.java
 create mode 100644 source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/annotation/JsonDesensitization.java
 create mode 100644 source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/AbstractDesensitizer.java
 create mode 100644 source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/Desensitizer.java
 create mode 100644 source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/DesensitizerFactory.java
 create mode 100644 source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/AddressDesensitizer.java
 create mode 100644 source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/BankCardDesensitizer.java
 create mode 100644 source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/BirthdayDesensitizer.java
 create mode 100644 source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/DefaultDesensitizer.java
 create mode 100644 source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/EmailDesensitizer.java
 create mode 100644 source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/IdCardDesensitizer.java
 create mode 100644 source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/LandlineDesensitizer.java
 create mode 100644 source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/MobileDesensitizer.java
 create mode 100644 source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/PasswordDesensitizer.java
 create mode 100644 source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/enums/DesensitizationType.java
 create mode 100644 source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/exception/DesensitizationException.java
 create mode 100644 source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/entity/User.java
 create mode 100644 source-code/springboot-data-desensitization/src/main/resources/application.properties
 create mode 100644 source-code/springboot-data-desensitization/src/test/java/com/github/springbootdatadesensitization/desensitize/JsonDesensitizationSerializerTest.java

diff --git a/source-code/springboot-data-desensitization/.gitignore b/source-code/springboot-data-desensitization/.gitignore
new file mode 100644
index 0000000..549e00a
--- /dev/null
+++ b/source-code/springboot-data-desensitization/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/source-code/springboot-data-desensitization/.mvn/wrapper/MavenWrapperDownloader.java b/source-code/springboot-data-desensitization/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 0000000..a45eb6b
--- /dev/null
+++ b/source-code/springboot-data-desensitization/.mvn/wrapper/MavenWrapperDownloader.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2007-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.net.*;
+import java.io.*;
+import java.nio.channels.*;
+import java.util.Properties;
+
+public class MavenWrapperDownloader {
+
+    private static final String WRAPPER_VERSION = "0.5.6";
+    /**
+     * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
+     */
+    private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+            + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
+
+    /**
+     * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
+     * use instead of the default one.
+     */
+    private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
+            ".mvn/wrapper/maven-wrapper.properties";
+
+    /**
+     * Path where the maven-wrapper.jar will be saved to.
+     */
+    private static final String MAVEN_WRAPPER_JAR_PATH =
+            ".mvn/wrapper/maven-wrapper.jar";
+
+    /**
+     * Name of the property which should be used to override the default download url for the wrapper.
+     */
+    private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
+
+    public static void main(String args[]) {
+        System.out.println("- Downloader started");
+        File baseDirectory = new File(args[0]);
+        System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
+
+        // If the maven-wrapper.properties exists, read it and check if it contains a custom
+        // wrapperUrl parameter.
+        File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
+        String url = DEFAULT_DOWNLOAD_URL;
+        if (mavenWrapperPropertyFile.exists()) {
+            FileInputStream mavenWrapperPropertyFileInputStream = null;
+            try {
+                mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
+                Properties mavenWrapperProperties = new Properties();
+                mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
+                url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
+            } catch (IOException e) {
+                System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
+            } finally {
+                try {
+                    if (mavenWrapperPropertyFileInputStream != null) {
+                        mavenWrapperPropertyFileInputStream.close();
+                    }
+                } catch (IOException e) {
+                    // Ignore ...
+                }
+            }
+        }
+        System.out.println("- Downloading from: " + url);
+
+        File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
+        if (!outputFile.getParentFile().exists()) {
+            if (!outputFile.getParentFile().mkdirs()) {
+                System.out.println(
+                        "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
+            }
+        }
+        System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
+        try {
+            downloadFileFromURL(url, outputFile);
+            System.out.println("Done");
+            System.exit(0);
+        } catch (Throwable e) {
+            System.out.println("- Error downloading");
+            e.printStackTrace();
+            System.exit(1);
+        }
+    }
+
+    private static void downloadFileFromURL(String urlString, File destination) throws Exception {
+        if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
+            String username = System.getenv("MVNW_USERNAME");
+            char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
+            Authenticator.setDefault(new Authenticator() {
+                @Override
+                protected PasswordAuthentication getPasswordAuthentication() {
+                    return new PasswordAuthentication(username, password);
+                }
+            });
+        }
+        URL website = new URL(urlString);
+        ReadableByteChannel rbc;
+        rbc = Channels.newChannel(website.openStream());
+        FileOutputStream fos = new FileOutputStream(destination);
+        fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+        fos.close();
+        rbc.close();
+    }
+
+}
diff --git a/source-code/springboot-data-desensitization/.mvn/wrapper/maven-wrapper.jar b/source-code/springboot-data-desensitization/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 0000000000000000000000000000000000000000..2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054
GIT binary patch
literal 50710
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
EfAhh=DF6Tf

literal 0
HcmV?d00001

diff --git a/source-code/springboot-data-desensitization/.mvn/wrapper/maven-wrapper.properties b/source-code/springboot-data-desensitization/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..ffdc10e
--- /dev/null
+++ b/source-code/springboot-data-desensitization/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
diff --git a/source-code/springboot-data-desensitization/mvnw b/source-code/springboot-data-desensitization/mvnw
new file mode 100755
index 0000000..3c8a553
--- /dev/null
+++ b/source-code/springboot-data-desensitization/mvnw
@@ -0,0 +1,322 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#    https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+#   JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+#   M2_HOME - location of maven2's installed home dir
+#   MAVEN_OPTS - parameters passed to the Java VM when running Maven
+#     e.g. to debug Maven itself, use
+#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ]; then
+
+  if [ -f /etc/mavenrc ]; then
+    . /etc/mavenrc
+  fi
+
+  if [ -f "$HOME/.mavenrc" ]; then
+    . "$HOME/.mavenrc"
+  fi
+
+fi
+
+# OS specific support.  $var _must_ be set to either true or false.
+cygwin=false
+darwin=false
+mingw=false
+case "$(uname)" in
+CYGWIN*) cygwin=true ;;
+MINGW*) mingw=true ;;
+Darwin*)
+  darwin=true
+  # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+  # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+  if [ -z "$JAVA_HOME" ]; then
+    if [ -x "/usr/libexec/java_home" ]; then
+      export JAVA_HOME="$(/usr/libexec/java_home)"
+    else
+      export JAVA_HOME="/Library/Java/Home"
+    fi
+  fi
+  ;;
+esac
+
+if [ -z "$JAVA_HOME" ]; then
+  if [ -r /etc/gentoo-release ]; then
+    JAVA_HOME=$(java-config --jre-home)
+  fi
+fi
+
+if [ -z "$M2_HOME" ]; then
+  ## resolve links - $0 may be a link to maven's home
+  PRG="$0"
+
+  # need this for relative symlinks
+  while [ -h "$PRG" ]; do
+    ls=$(ls -ld "$PRG")
+    link=$(expr "$ls" : '.*-> \(.*\)$')
+    if expr "$link" : '/.*' >/dev/null; then
+      PRG="$link"
+    else
+      PRG="$(dirname "$PRG")/$link"
+    fi
+  done
+
+  saveddir=$(pwd)
+
+  M2_HOME=$(dirname "$PRG")/..
+
+  # make it fully qualified
+  M2_HOME=$(cd "$M2_HOME" && pwd)
+
+  cd "$saveddir"
+  # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME=$(cygpath --unix "$M2_HOME")
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
+  [ -n "$CLASSPATH" ] &&
+    CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME="$( (
+      cd "$M2_HOME"
+      pwd
+    ))"
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME="$( (
+      cd "$JAVA_HOME"
+      pwd
+    ))"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+  javaExecutable="$(which javac)"
+  if [ -n "$javaExecutable" ] && ! [ "$(expr \"$javaExecutable\" : '\([^ ]*\)')" = "no" ]; then
+    # readlink(1) is not available as standard on Solaris 10.
+    readLink=$(which readlink)
+    if [ ! $(expr "$readLink" : '\([^ ]*\)') = "no" ]; then
+      if $darwin; then
+        javaHome="$(dirname \"$javaExecutable\")"
+        javaExecutable="$(cd \"$javaHome\" && pwd -P)/javac"
+      else
+        javaExecutable="$(readlink -f \"$javaExecutable\")"
+      fi
+      javaHome="$(dirname \"$javaExecutable\")"
+      javaHome=$(expr "$javaHome" : '\(.*\)/bin')
+      JAVA_HOME="$javaHome"
+      export JAVA_HOME
+    fi
+  fi
+fi
+
+if [ -z "$JAVACMD" ]; then
+  if [ -n "$JAVA_HOME" ]; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ]; then
+      # IBM's JDK on AIX uses strange locations for the executables
+      JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+      JAVACMD="$JAVA_HOME/bin/java"
+    fi
+  else
+    JAVACMD="$(which java)"
+  fi
+fi
+
+if [ ! -x "$JAVACMD" ]; then
+  echo "Error: JAVA_HOME is not defined correctly." >&2
+  echo "  We cannot execute $JAVACMD" >&2
+  exit 1
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+  echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+  if [ -z "$1" ]; then
+    echo "Path not specified to find_maven_basedir"
+    return 1
+  fi
+
+  basedir="$1"
+  wdir="$1"
+  while [ "$wdir" != '/' ]; do
+    if [ -d "$wdir"/.mvn ]; then
+      basedir=$wdir
+      break
+    fi
+    # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+    if [ -d "${wdir}" ]; then
+      wdir=$(
+        cd "$wdir/.."
+        pwd
+      )
+    fi
+    # end of workaround
+  done
+  echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+  if [ -f "$1" ]; then
+    echo "$(tr -s '\n' ' ' <"$1")"
+  fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(pwd)")
+if [ -z "$BASE_DIR" ]; then
+  exit 1
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+  if [ "$MVNW_VERBOSE" = true ]; then
+    echo "Found .mvn/wrapper/maven-wrapper.jar"
+  fi
+else
+  if [ "$MVNW_VERBOSE" = true ]; then
+    echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+  fi
+  if [ -n "$MVNW_REPOURL" ]; then
+    jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+  else
+    jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+  fi
+  while IFS="=" read key value; do
+    case "$key" in wrapperUrl)
+      jarUrl="$value"
+      break
+      ;;
+    esac
+  done <"$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+  if [ "$MVNW_VERBOSE" = true ]; then
+    echo "Downloading from: $jarUrl"
+  fi
+  wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+  if $cygwin; then
+    wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
+  fi
+
+  if command -v wget >/dev/null; then
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Found wget ... using wget"
+    fi
+    if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+      wget "$jarUrl" -O "$wrapperJarPath"
+    else
+      wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+    fi
+  elif command -v curl >/dev/null; then
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Found curl ... using curl"
+    fi
+    if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+      curl -o "$wrapperJarPath" "$jarUrl" -f
+    else
+      curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+    fi
+
+  else
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Falling back to using Java to download"
+    fi
+    javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+    # For Cygwin, switch paths to Windows format before running javac
+    if $cygwin; then
+      javaClass=$(cygpath --path --windows "$javaClass")
+    fi
+    if [ -e "$javaClass" ]; then
+      if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo " - Compiling MavenWrapperDownloader.java ..."
+        fi
+        # Compiling the Java class
+        ("$JAVA_HOME/bin/javac" "$javaClass")
+      fi
+      if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+        # Running the downloader
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo " - Running MavenWrapperDownloader.java ..."
+        fi
+        ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+      fi
+    fi
+  fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+  echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME=$(cygpath --path --windows "$M2_HOME")
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
+  [ -n "$CLASSPATH" ] &&
+    CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
+  [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+    MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+  $MAVEN_OPTS \
+  -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+  "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/source-code/springboot-data-desensitization/mvnw.cmd b/source-code/springboot-data-desensitization/mvnw.cmd
new file mode 100644
index 0000000..c8d4337
--- /dev/null
+++ b/source-code/springboot-data-desensitization/mvnw.cmd
@@ -0,0 +1,182 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements.  See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership.  The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License.  You may obtain a copy of the License at
+@REM
+@REM    https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied.  See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM     e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on"  echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+
+FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+    IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Found %WRAPPER_JAR%
+    )
+) else (
+    if not "%MVNW_REPOURL%" == "" (
+        SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+    )
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Couldn't find %WRAPPER_JAR%, downloading it ...
+        echo Downloading from: %DOWNLOAD_URL%
+    )
+
+    powershell -Command "&{"^
+		"$webclient = new-object System.Net.WebClient;"^
+		"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+		"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+		"}"^
+		"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+		"}"
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Finished downloading %WRAPPER_JAR%
+    )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/source-code/springboot-data-desensitization/pom.xml b/source-code/springboot-data-desensitization/pom.xml
new file mode 100644
index 0000000..baa65b9
--- /dev/null
+++ b/source-code/springboot-data-desensitization/pom.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.4.5</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+    <groupId>com.github.javaguide</groupId>
+    <artifactId>springboot-data-desensitization</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>springboot-data-desensitization</name>
+    <description>Demo project for Spring Boot</description>
+    <properties>
+        <java.version>1.8</java.version>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok</artifactId>
+                        </exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/SpringbootDataDesensitizationApplication.java b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/SpringbootDataDesensitizationApplication.java
new file mode 100644
index 0000000..8fd6367
--- /dev/null
+++ b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/SpringbootDataDesensitizationApplication.java
@@ -0,0 +1,13 @@
+package com.github.springbootdatadesensitization;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SpringbootDataDesensitizationApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(SpringbootDataDesensitizationApplication.class, args);
+    }
+
+}
diff --git a/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/JsonDesensitizationSerializer.java b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/JsonDesensitizationSerializer.java
new file mode 100644
index 0000000..8ea4523
--- /dev/null
+++ b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/JsonDesensitizationSerializer.java
@@ -0,0 +1,59 @@
+package com.github.springbootdatadesensitization.desensitize;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.BeanProperty;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.ContextualSerializer;
+import com.github.springbootdatadesensitization.desensitize.annotation.JsonDesensitization;
+import com.github.springbootdatadesensitization.desensitize.desensitizer.Desensitizer;
+import com.github.springbootdatadesensitization.desensitize.desensitizer.DesensitizerFactory;
+import com.github.springbootdatadesensitization.desensitize.enums.DesensitizationType;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * 微信搜 JavaGuide 回复"面试突击"即可免费领取个人原创的 Java 面试手册
+ *
+ * @author Guide哥
+ * @date 2021/05/10 20:26
+ **/
+public class JsonDesensitizationSerializer extends JsonSerializer<String> implements ContextualSerializer {
+
+
+    private DesensitizationType type;
+
+    public JsonDesensitizationSerializer() {
+    }
+
+    public JsonDesensitizationSerializer(DesensitizationType type) {
+        this.type = type;
+    }
+
+    @Override
+    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty property) throws JsonMappingException {
+        if (property != null) {
+            if (Objects.equals(property.getType().getRawClass(), String.class)) {
+                JsonDesensitization jsonDesensitization = property.getAnnotation(JsonDesensitization.class);
+                if (jsonDesensitization == null) {
+                    jsonDesensitization = property.getContextAnnotation(JsonDesensitization.class);
+                }
+                if (jsonDesensitization != null) {
+                    return new JsonDesensitizationSerializer(jsonDesensitization.value());
+                }
+            }
+            return serializerProvider.findValueSerializer(property.getType(), property);
+        } else {
+            return serializerProvider.findNullValueSerializer(null);
+        }
+    }
+
+    @Override
+    public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
+        Desensitizer desensitizer = DesensitizerFactory.get(this.type);
+        System.out.println(s);
+        jsonGenerator.writeString(desensitizer.desensitize(s));
+    }
+}
diff --git a/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/annotation/JsonDesensitization.java b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/annotation/JsonDesensitization.java
new file mode 100644
index 0000000..d7fbf5d
--- /dev/null
+++ b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/annotation/JsonDesensitization.java
@@ -0,0 +1,25 @@
+package com.github.springbootdatadesensitization.desensitize.annotation;
+
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.github.springbootdatadesensitization.desensitize.JsonDesensitizationSerializer;
+import com.github.springbootdatadesensitization.desensitize.enums.DesensitizationType;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 微信搜 JavaGuide 回复"面试突击"即可免费领取个人原创的 Java 面试手册
+ *
+ * @author Guide哥
+ * @date 2021/05/10 20:36
+ **/
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD})
+@JacksonAnnotationsInside
+@JsonSerialize(using = JsonDesensitizationSerializer.class)
+public @interface JsonDesensitization {
+    DesensitizationType value();
+}
diff --git a/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/AbstractDesensitizer.java b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/AbstractDesensitizer.java
new file mode 100644
index 0000000..32bbcd1
--- /dev/null
+++ b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/AbstractDesensitizer.java
@@ -0,0 +1,71 @@
+package com.github.springbootdatadesensitization.desensitize.desensitizer;
+
+import com.github.springbootdatadesensitization.desensitize.exception.DesensitizationException;
+
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 微信搜 JavaGuide 回复"面试突击"即可免费领取个人原创的 Java 面试手册
+ * 数据脱敏所使用的的策略
+ *
+ * @author Guide哥
+ * @date 2021/05/10 20:15
+ **/
+public abstract class AbstractDesensitizer implements Desensitizer {
+
+    /**
+     * 左边的明文长度
+     */
+    private final Integer leftPlainTextLen;
+    /**
+     * 右边的明文长度
+     */
+    private Integer rightPlainTextLen;
+
+    public AbstractDesensitizer(Integer leftPlainTextLen) {
+        this.leftPlainTextLen = leftPlainTextLen;
+    }
+
+    public AbstractDesensitizer(Integer leftPlainTextLen, Integer rightPlainTextLen) {
+        this.leftPlainTextLen = leftPlainTextLen;
+        this.rightPlainTextLen = rightPlainTextLen;
+    }
+
+    @Override
+    public String desensitize(String origin) {
+        if (origin == null || origin.isEmpty()) {
+            return "";
+        }
+        // 处理邮箱的特殊情况
+        if (isEmail(origin)) {
+            int index = origin.indexOf("@");
+            rightPlainTextLen = origin.length() - index;
+        }
+        if (leftPlainTextLen == 0 && rightPlainTextLen == 0) {
+            return getEncryptedStr(6);
+        }
+        if (leftPlainTextLen < 0 || rightPlainTextLen < 0) {
+            throw new DesensitizationException("leftPlainTextLen and rightPlainTextLen must > 0");
+        }
+        if (leftPlainTextLen + rightPlainTextLen >= origin.length()) {
+            throw new DesensitizationException("leftPlainTextLen+rightPlainTextLen should <= the length of origin");
+        }
+        StringBuilder result = new StringBuilder();
+        result.append(origin, 0, leftPlainTextLen);
+        result.append(getEncryptedStr(origin.length() - leftPlainTextLen - rightPlainTextLen));
+        result.append(origin, origin.length() - rightPlainTextLen, origin.length());
+        return result.toString();
+    }
+
+    private Boolean isEmail(String s) {
+        String emailRegex = "^[\\w-.]+@([\\w-]+\\.)+[\\w-]{2,4}$";
+        Pattern emailPattern = Pattern.compile(emailRegex);
+        return emailPattern.matcher(s).matches();
+    }
+
+    private String getEncryptedStr(int length) {
+        return Stream.generate(() -> "*").limit(length).collect(Collectors.joining());
+    }
+}
diff --git a/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/Desensitizer.java b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/Desensitizer.java
new file mode 100644
index 0000000..721009f
--- /dev/null
+++ b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/Desensitizer.java
@@ -0,0 +1,11 @@
+package com.github.springbootdatadesensitization.desensitize.desensitizer;
+
+/**
+ * 微信搜 JavaGuide 回复"面试突击"即可免费领取个人原创的 Java 面试手册
+ *
+ * @author Guide哥
+ * @date 2021/05/10 20:36
+ **/
+public interface Desensitizer {
+    String desensitize(String origin);
+}
diff --git a/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/DesensitizerFactory.java b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/DesensitizerFactory.java
new file mode 100644
index 0000000..f7d6fb3
--- /dev/null
+++ b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/DesensitizerFactory.java
@@ -0,0 +1,45 @@
+package com.github.springbootdatadesensitization.desensitize.desensitizer;
+
+import com.github.springbootdatadesensitization.desensitize.desensitizer.impl.AddressDesensitizer;
+import com.github.springbootdatadesensitization.desensitize.desensitizer.impl.BankCardDesensitizer;
+import com.github.springbootdatadesensitization.desensitize.desensitizer.impl.BirthdayDesensitizer;
+import com.github.springbootdatadesensitization.desensitize.desensitizer.impl.DefaultDesensitizer;
+import com.github.springbootdatadesensitization.desensitize.desensitizer.impl.EmailDesensitizer;
+import com.github.springbootdatadesensitization.desensitize.desensitizer.impl.IdCardDesensitizer;
+import com.github.springbootdatadesensitization.desensitize.desensitizer.impl.LandlineDesensitizer;
+import com.github.springbootdatadesensitization.desensitize.desensitizer.impl.MobileDesensitizer;
+import com.github.springbootdatadesensitization.desensitize.desensitizer.impl.PasswordDesensitizer;
+import com.github.springbootdatadesensitization.desensitize.enums.DesensitizationType;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 微信搜 JavaGuide 回复"面试突击"即可免费领取个人原创的 Java 面试手册
+ *
+ * @author Guide哥
+ * @date 2021/05/10 23:18
+ **/
+public class DesensitizerFactory {
+    public static final Map<DesensitizationType, Desensitizer> desensitizers = new HashMap<>();
+
+    static {
+        desensitizers.put(DesensitizationType.ADDRESS, new AddressDesensitizer());
+        desensitizers.put(DesensitizationType.BANK_CARD, new BankCardDesensitizer());
+        desensitizers.put(DesensitizationType.EMAIL, new EmailDesensitizer());
+        desensitizers.put(DesensitizationType.ID_CARD, new IdCardDesensitizer());
+        desensitizers.put(DesensitizationType.LANDLINE, new LandlineDesensitizer());
+        desensitizers.put(DesensitizationType.MOBILE, new MobileDesensitizer());
+        desensitizers.put(DesensitizationType.PASSWORD, new PasswordDesensitizer());
+        desensitizers.put(DesensitizationType.DEFAULT, new DefaultDesensitizer());
+        desensitizers.put(DesensitizationType.BIRTHDAY, new BirthdayDesensitizer());
+    }
+
+    public static Desensitizer get(DesensitizationType desensitizationType) {
+        Desensitizer desensitizer = desensitizers.get(desensitizationType);
+        if (desensitizer == null) {
+            return desensitizers.get(DesensitizationType.DEFAULT);
+        }
+        return desensitizer;
+    }
+}
diff --git a/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/AddressDesensitizer.java b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/AddressDesensitizer.java
new file mode 100644
index 0000000..cf9e429
--- /dev/null
+++ b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/AddressDesensitizer.java
@@ -0,0 +1,15 @@
+package com.github.springbootdatadesensitization.desensitize.desensitizer.impl;
+
+import com.github.springbootdatadesensitization.desensitize.desensitizer.AbstractDesensitizer;
+
+/**
+ * 地址脱敏
+ *
+ * @author Guide哥
+ * @date 2021/05/10 20:15
+ **/
+public class AddressDesensitizer extends AbstractDesensitizer {
+    public AddressDesensitizer() {
+        super(3, 3);
+    }
+}
diff --git a/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/BankCardDesensitizer.java b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/BankCardDesensitizer.java
new file mode 100644
index 0000000..b9620f9
--- /dev/null
+++ b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/BankCardDesensitizer.java
@@ -0,0 +1,15 @@
+package com.github.springbootdatadesensitization.desensitize.desensitizer.impl;
+
+import com.github.springbootdatadesensitization.desensitize.desensitizer.AbstractDesensitizer;
+
+/**
+ * 银行卡号脱敏
+ *
+ * @author Guide哥
+ * @date 2021/05/10 20:28
+ **/
+public class BankCardDesensitizer extends AbstractDesensitizer {
+    public BankCardDesensitizer() {
+        super(3, 3);
+    }
+}
diff --git a/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/BirthdayDesensitizer.java b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/BirthdayDesensitizer.java
new file mode 100644
index 0000000..1f8d300
--- /dev/null
+++ b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/BirthdayDesensitizer.java
@@ -0,0 +1,15 @@
+package com.github.springbootdatadesensitization.desensitize.desensitizer.impl;
+
+import com.github.springbootdatadesensitization.desensitize.desensitizer.AbstractDesensitizer;
+
+/**
+ * 密码脱敏
+ *
+ * @author Guide哥
+ * @date 2021/05/10 20:28
+ **/
+public class BirthdayDesensitizer extends AbstractDesensitizer {
+    public BirthdayDesensitizer() {
+        super(4, 0);
+    }
+}
diff --git a/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/DefaultDesensitizer.java b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/DefaultDesensitizer.java
new file mode 100644
index 0000000..9a833de
--- /dev/null
+++ b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/DefaultDesensitizer.java
@@ -0,0 +1,15 @@
+package com.github.springbootdatadesensitization.desensitize.desensitizer.impl;
+
+import com.github.springbootdatadesensitization.desensitize.desensitizer.AbstractDesensitizer;
+
+/**
+ * 密码脱敏
+ *
+ * @author Guide哥
+ * @date 2021/05/10 20:28
+ **/
+public class DefaultDesensitizer extends AbstractDesensitizer {
+    public DefaultDesensitizer() {
+        super(0, 0);
+    }
+}
diff --git a/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/EmailDesensitizer.java b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/EmailDesensitizer.java
new file mode 100644
index 0000000..3ea0fb4
--- /dev/null
+++ b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/EmailDesensitizer.java
@@ -0,0 +1,15 @@
+package com.github.springbootdatadesensitization.desensitize.desensitizer.impl;
+
+import com.github.springbootdatadesensitization.desensitize.desensitizer.AbstractDesensitizer;
+
+/**
+ * 邮箱脱敏
+ *
+ * @author Guide哥
+ * @date 2021/05/10 20:28
+ **/
+public class EmailDesensitizer extends AbstractDesensitizer {
+    public EmailDesensitizer() {
+        super(0);
+    }
+}
diff --git a/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/IdCardDesensitizer.java b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/IdCardDesensitizer.java
new file mode 100644
index 0000000..d979913
--- /dev/null
+++ b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/IdCardDesensitizer.java
@@ -0,0 +1,15 @@
+package com.github.springbootdatadesensitization.desensitize.desensitizer.impl;
+
+import com.github.springbootdatadesensitization.desensitize.desensitizer.AbstractDesensitizer;
+
+/**
+ * 身份证号脱敏
+ *
+ * @author Guide哥
+ * @date 2021/05/10 20:28
+ **/
+public class IdCardDesensitizer extends AbstractDesensitizer {
+    public IdCardDesensitizer() {
+        super(3, 4);
+    }
+}
diff --git a/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/LandlineDesensitizer.java b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/LandlineDesensitizer.java
new file mode 100644
index 0000000..5bec86e
--- /dev/null
+++ b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/LandlineDesensitizer.java
@@ -0,0 +1,15 @@
+package com.github.springbootdatadesensitization.desensitize.desensitizer.impl;
+
+import com.github.springbootdatadesensitization.desensitize.desensitizer.AbstractDesensitizer;
+
+/**
+ * 座机号脱敏
+ *
+ * @author Guide哥
+ * @date 2021/05/10 20:28
+ **/
+public class LandlineDesensitizer extends AbstractDesensitizer {
+    public LandlineDesensitizer() {
+        super(2, 2);
+    }
+}
diff --git a/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/MobileDesensitizer.java b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/MobileDesensitizer.java
new file mode 100644
index 0000000..2482a9c
--- /dev/null
+++ b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/MobileDesensitizer.java
@@ -0,0 +1,15 @@
+package com.github.springbootdatadesensitization.desensitize.desensitizer.impl;
+
+import com.github.springbootdatadesensitization.desensitize.desensitizer.AbstractDesensitizer;
+
+/**
+ * 手机号脱敏
+ *
+ * @author Guide哥
+ * @date 2021/05/10 20:28
+ **/
+public class MobileDesensitizer extends AbstractDesensitizer {
+    public MobileDesensitizer() {
+        super(3, 4);
+    }
+}
diff --git a/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/PasswordDesensitizer.java b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/PasswordDesensitizer.java
new file mode 100644
index 0000000..62639ff
--- /dev/null
+++ b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/desensitizer/impl/PasswordDesensitizer.java
@@ -0,0 +1,15 @@
+package com.github.springbootdatadesensitization.desensitize.desensitizer.impl;
+
+import com.github.springbootdatadesensitization.desensitize.desensitizer.AbstractDesensitizer;
+
+/**
+ * 密码脱敏
+ *
+ * @author Guide哥
+ * @date 2021/05/10 20:28
+ **/
+public class PasswordDesensitizer extends AbstractDesensitizer {
+    public PasswordDesensitizer() {
+        super(0, 0);
+    }
+}
diff --git a/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/enums/DesensitizationType.java b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/enums/DesensitizationType.java
new file mode 100644
index 0000000..502cf70
--- /dev/null
+++ b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/enums/DesensitizationType.java
@@ -0,0 +1,46 @@
+package com.github.springbootdatadesensitization.desensitize.enums;
+
+/**
+ * 微信搜 JavaGuide 回复"面试突击"即可免费领取个人原创的 Java 面试手册
+ *
+ * @author Guide哥
+ * @date 2021/05/10 20:36
+ **/
+public enum DesensitizationType {
+
+    DEFAULT,
+    /**
+     * 座机号
+     */
+    LANDLINE,
+    /**
+     * 手机
+     */
+    MOBILE,
+    /**
+     * 邮箱
+     */
+    EMAIL,
+
+    /**
+     * 生日🎂
+     */
+    BIRTHDAY,
+    /**
+     * 密码
+     */
+    PASSWORD,
+    /**
+     * 身份证
+     */
+    ID_CARD,
+    /**
+     * 银行卡
+     */
+    BANK_CARD,
+    /**
+     * 地址
+     */
+    ADDRESS,
+
+}
diff --git a/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/exception/DesensitizationException.java b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/exception/DesensitizationException.java
new file mode 100644
index 0000000..50a70b4
--- /dev/null
+++ b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/desensitize/exception/DesensitizationException.java
@@ -0,0 +1,14 @@
+package com.github.springbootdatadesensitization.desensitize.exception;
+
+import lombok.Getter;
+
+/**
+ * @author shuang.kou
+ * @date 2017/12/19 14:25
+ */
+@Getter
+public class DesensitizationException extends RuntimeException {
+    public DesensitizationException(String message) {
+        super(message);
+    }
+}
\ No newline at end of file
diff --git a/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/entity/User.java b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/entity/User.java
new file mode 100644
index 0000000..6dcbda5
--- /dev/null
+++ b/source-code/springboot-data-desensitization/src/main/java/com/github/springbootdatadesensitization/entity/User.java
@@ -0,0 +1,35 @@
+package com.github.springbootdatadesensitization.entity;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.github.springbootdatadesensitization.desensitize.annotation.JsonDesensitization;
+import com.github.springbootdatadesensitization.desensitize.enums.DesensitizationType;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class User {
+    private Long id;
+    @JsonDesensitization(DesensitizationType.EMAIL)
+    private String email;
+
+    @JsonDesensitization(DesensitizationType.ID_CARD)
+    private String idCard;
+
+    @JsonDesensitization(DesensitizationType.PASSWORD)
+    private String password;
+
+    @JsonDesensitization(DesensitizationType.MOBILE)
+    private String phone;
+
+    @JsonFormat(pattern = "yyyy-MM-dd", locale = "zh", timezone = "GMT+8")
+    @JsonDesensitization(DesensitizationType.BIRTHDAY)
+    private Date birthday;
+}
diff --git a/source-code/springboot-data-desensitization/src/main/resources/application.properties b/source-code/springboot-data-desensitization/src/main/resources/application.properties
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/source-code/springboot-data-desensitization/src/main/resources/application.properties
@@ -0,0 +1 @@
+
diff --git a/source-code/springboot-data-desensitization/src/test/java/com/github/springbootdatadesensitization/desensitize/JsonDesensitizationSerializerTest.java b/source-code/springboot-data-desensitization/src/test/java/com/github/springbootdatadesensitization/desensitize/JsonDesensitizationSerializerTest.java
new file mode 100644
index 0000000..3debd98
--- /dev/null
+++ b/source-code/springboot-data-desensitization/src/test/java/com/github/springbootdatadesensitization/desensitize/JsonDesensitizationSerializerTest.java
@@ -0,0 +1,33 @@
+package com.github.springbootdatadesensitization.desensitize;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.github.springbootdatadesensitization.entity.User;
+import org.junit.jupiter.api.Test;
+
+import java.util.Date;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class JsonDesensitizationSerializerTest {
+
+    @Test
+    void should_desensitize_entity() throws JsonProcessingException {
+        ObjectMapper objectMapper = new ObjectMapper();
+        User user = User.builder()
+                .birthday(new Date())
+                .phone("1816313814")
+                .email("136148@qq.com")
+                .password("12dsfs@dfsdg431")
+                .idCard("463827319960397495")
+                .build();
+        String s = objectMapper.writeValueAsString(user);
+        System.out.println(s);
+        User desensitizedUser = objectMapper.readValue(s, User.class);
+        assertEquals("******@qq.com", desensitizedUser.getEmail());
+        assertEquals("463***********7495", desensitizedUser.getIdCard());
+        assertEquals("******", desensitizedUser.getPassword());
+        assertEquals("181***3814", desensitizedUser.getPhone());
+
+    }
+}
\ No newline at end of file

From 5a184f795d1e554f95dade65359c8fcbaf969a7a Mon Sep 17 00:00:00 2001
From: guide <koushuangbwcx@163.com>
Date: Wed, 19 May 2021 08:58:45 +0800
Subject: [PATCH 20/22] =?UTF-8?q?MyBatis-Plus=20=20=E4=BB=8E=E5=85=A5?=
 =?UTF-8?q?=E9=97=A8=E5=88=B0=E4=B8=8A=E6=89=8B=E5=B9=B2=E4=BA=8B=EF=BC=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 README.md           |   7 +-
 docs/MyBatisPlus.md | 975 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 979 insertions(+), 3 deletions(-)
 create mode 100644 docs/MyBatisPlus.md

diff --git a/README.md b/README.md
index e1b0fae..025fee7 100644
--- a/README.md
+++ b/README.md
@@ -36,9 +36,10 @@
 4. [Spring 如何优雅读取配置文件?](./docs/basis/read-config-properties.md) 
 5. **异常处理** :[Spring Boot 异常处理的几种方式](./docs/advanced/springboot-handle-exception.md)、[Spring Boot 异常处理在实际项目中的应用](./docs/advanced/springboot-handle-exception-plus.md)
 6. **JPA** : [ Spring Boot JPA 基础:常见操作解析](./docs/basis/springboot-jpa.md) 、 [JPA 中非常重要的连表查询就是这么简单](./docs/basis/springboot-jpa-lianbiao.md)
-7. **拦截器和过滤器** :[SpringBoot 实现过滤器](./docs/basis/springboot-filter.md) 、[SpringBoot 实现拦截器](./docs/basis/springboot-interceptor.md)
-8. **MyBatis**  :[整合 SpringBoot+Mybatis](./docs/basis/springboot-mybatis.md) 、[SpirngBoot2.0+ 的 SpringBoot+Mybatis 多数据源配置](./docs/basis/springboot-mybatis-mutipledatasource.md) (TODO:早期文章,不建议阅读,待重构~)
-9. [SpringBoot 2.0+ 集成 Swagger 官方 Starter + knife4j 增强方案](./docs/basis/swagger.md)
+7. [MyBatis-Plus  从入门到上手干事!](./docs/MyBatisPlus.md)
+8. **拦截器和过滤器** :[SpringBoot 实现过滤器](./docs/basis/springboot-filter.md) 、[SpringBoot 实现拦截器](./docs/basis/springboot-interceptor.md)
+9. **MyBatis**  :[整合 SpringBoot+Mybatis](./docs/basis/springboot-mybatis.md) 、[SpirngBoot2.0+ 的 SpringBoot+Mybatis 多数据源配置](./docs/basis/springboot-mybatis-mutipledatasource.md) (TODO:早期文章,不建议阅读,待重构~)
+10. [SpringBoot 2.0+ 集成 Swagger 官方 Starter + knife4j 增强方案](./docs/basis/swagger.md)
 
 ### 进阶
 
diff --git a/docs/MyBatisPlus.md b/docs/MyBatisPlus.md
new file mode 100644
index 0000000..afd4043
--- /dev/null
+++ b/docs/MyBatisPlus.md
@@ -0,0 +1,975 @@
+`MyBatis` 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射,而实际开发中,我们都会选择使用 `MyBatisPlus`,它是对 `MyBatis` 框架的进一步增强,能够极大地简化我们的持久层代码,下面就一起来看看 `MyBatisPlus` 中的一些奇淫巧技吧。
+
+> 说明:本篇文章需要一定的 `MyBatis` 与 `MyBatisPlus` 基础
+
+MyBatis-Plus 官网地址 : https://baomidou.com/ 。
+
+## CRUD
+
+使用 `MyBatisPlus` 实现业务的增删改查非常地简单,一起来看看吧。
+
+**1.首先新建一个 SpringBoot 工程,然后引入依赖:**
+
+```xml
+<dependency>
+  <groupId>com.baomidou</groupId>
+  <artifactId>mybatis-plus-boot-starter</artifactId>
+  <version>3.4.2</version>
+</dependency>
+<dependency>
+  <groupId>mysql</groupId>
+  <artifactId>mysql-connector-java</artifactId>
+  <scope>runtime</scope>
+</dependency>
+<dependency>
+  <groupId>org.projectlombok</groupId>
+  <artifactId>lombok</artifactId>
+</dependency>
+```
+
+**2.配置一下数据源:**
+
+```yaml
+spring:
+  datasource:
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    username: root
+    url: jdbc:mysql:///mybatisplus?serverTimezone=UTC
+    password: 123456
+```
+
+**3.创建一下数据表:**
+
+```sql
+CREATE DATABASE `mybatisplus`;
+
+USE `mybatisplus`;
+
+DROP TABLE IF EXISTS `tbl_employee`;
+
+CREATE TABLE `tbl_employee` (
+  `id` bigint(20) NOT NULL,
+  `last_name` varchar(255) DEFAULT NULL,
+  `email` varchar(255) DEFAULT NULL,
+  `gender` char(1) DEFAULT NULL,
+  `age` int(11) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=gbk;
+
+insert  into `tbl_employee`(`id`,`last_name`,`email`,`gender`,`age`) values (1,'jack','jack@qq.com','1',35),(2,'tom','tom@qq.com','1',30),(3,'jerry','jerry@qq.com','1',40);
+```
+
+**4.创建对应的实体类:**
+
+```java
+@Data
+public class Employee {
+
+    private Long id;
+    private String lastName;
+    private String email;
+    private Integer age;
+}
+```
+
+**4.编写 `Mapper` 接口:**
+
+```java
+public interface EmployeeMapper extends BaseMapper<Employee> {
+}
+```
+
+我们只需继承 `MyBatisPlus` 提供的 `BaseMapper` 接口即可,现在我们就拥有了对 `Employee` 进行增删改查的 API,比如:
+
+```java
+@SpringBootTest
+@MapperScan("com.wwj.mybatisplusdemo.mapper")
+class MybatisplusDemoApplicationTests {
+
+    @Autowired
+    private EmployeeMapper employeeMapper;
+
+    @Test
+    void contextLoads() {
+        List<Employee> employees = employeeMapper.selectList(null);
+        employees.forEach(System.out::println);
+    }
+}
+```
+
+运行结果:
+
+```java
+org.springframework.jdbc.BadSqlGrammarException:
+### Error querying database.  Cause: java.sql.SQLSyntaxErrorException: Table 'mybatisplus.employee' doesn't exist
+```
+
+程序报错了,原因是不存在 `employee` 表,这是因为我们的实体类名为 `Employee`,`MyBatisPlus` 默认是以类名作为表名进行操作的,可如果类名和表名不相同(实际开发中也确实可能不同),就需要在实体类中使用 `@TableName` 注解来声明表的名称:
+
+```java
+@Data
+@TableName("tbl_employee") // 声明表名称
+public class Employee {
+
+    private Long id;
+    private String lastName;
+    private String email;
+    private Integer age;
+}
+```
+
+重新执行测试代码,结果如下:
+
+```java
+Employee(id=1, lastName=jack, email=jack@qq.com, age=35)
+Employee(id=2, lastName=tom, email=tom@qq.com, age=30)
+Employee(id=3, lastName=jerry, email=jerry@qq.com, age=40)
+```
+
+`BaseMapper` 提供了常用的一些增删改查方法:
+
+![](https://img-blog.csdnimg.cn/20210519084059865.png)
+
+具体细节可以查阅其源码自行体会,注释都是中文的,非常容易理解。
+
+在开发过程中,我们通常会使用 `Service` 层来调用 `Mapper` 层的方法,而 `MyBatisPlus` 也为我们提供了通用的 `Service`:
+
+```java
+public interface EmployeeService extends IService<Employee> {
+}
+
+@Service
+public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService {
+}
+```
+
+事实上,我们只需让 `EmployeeServiceImpl` 继承 `ServiceImpl` 即可获得 `Service` 层的方法,**那么为什么还需要实现 `EmployeeService` 接口呢?**
+
+这是因为实现 `EmployeeService` 接口能够更方便地对业务进行扩展,一些复杂场景下的数据处理,`MyBatisPlus` 提供的 `Service` 方法可能无法处理,此时我们就需要自己编写代码,这时候只需在 `EmployeeService` 中定义自己的方法,并在 `EmployeeServiceImpl` 中实现即可。
+
+先来测试一下 `MyBatisPlus` 提供的 `Service` 方法:
+
+```java
+@SpringBootTest
+@MapperScan("com.wwj.mybatisplusdemo.mapper")
+class MybatisplusDemoApplicationTests {
+
+    @Autowired
+    private EmployeeService employeeService;
+
+    @Test
+    void contextLoads() {
+        List<Employee> list = employeeService.list();
+        list.forEach(System.out::println);
+    }
+}
+```
+
+运行结果:
+
+```java
+Employee(id=1, lastName=jack, email=jack@qq.com, age=35)
+Employee(id=2, lastName=tom, email=tom@qq.com, age=30)
+Employee(id=3, lastName=jerry, email=jerry@qq.com, age=40)
+```
+
+接下来模拟一个自定义的场景,我们来编写自定义的操作方法,首先在 `EmployeeMapper` 中进行声明:
+
+```java
+public interface EmployeeMapper extends BaseMapper<Employee> {
+
+    List<Employee> selectAllByLastName(@Param("lastName") String lastName);
+}
+```
+
+此时我们需要自己编写配置文件实现该方法,在 `resource` 目录下新建一个 `mapper` 文件夹,然后在该文件夹下创建 `EmployeeMapper.xml` 文件:
+
+```xml
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="com.wwj.mybatisplusdemo.mapper.EmployeeMapper">
+
+    <sql id="Base_Column">
+        id, last_name, email, gender, age
+    </sql>
+
+    <select id="selectAllByLastName" resultType="com.wwj.mybatisplusdemo.bean.Employee">
+        select <include refid="Base_Column"/>
+        from tbl_employee
+        where last_name = #{lastName}
+    </select>
+</mapper>
+```
+
+`MyBatisPlus` 默认扫描的是类路径下的 `mapper` 目录,这可以从源码中得到体现:
+
+![](https://img-blog.csdnimg.cn/20210519084046472.png)
+
+所以我们直接将 `Mapper` 配置文件放在该目录下就没有任何问题,可如果不是这个目录,我们就需要进行配置,比如:
+
+```yaml
+mybatis-plus:
+  mapper-locations: classpath:xml/*.xml
+```
+
+编写好 `Mapper` 接口后,我们就需要定义 `Service` 方法了:
+
+```java
+public interface EmployeeService extends IService<Employee> {
+
+    List<Employee> listAllByLastName(String lastName);
+}
+
+@Service
+public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService {
+
+    @Override
+    public List<Employee> listAllByLastName(String lastName) {
+        return baseMapper.selectAllByLastName(lastName);
+    }
+}
+```
+
+在 `EmployeeServiceImpl` 中我们无需将 `EmployeeMapper` 注入进来,而是使用 `BaseMapper`,查看 `ServiceImpl` 的源码:
+
+![](https://img-blog.csdnimg.cn/2021051908403249.png)
+
+可以看到它为我们注入了一个 `BaseMapper` 对象,而它是第一个泛型类型,也就是 `EmployeeMapper` 类型,所以我们可以直接使用这个 `baseMapper` 来调用 `Mapper` 中的方法,此时编写测试代码:
+
+```java
+@SpringBootTest
+@MapperScan("com.wwj.mybatisplusdemo.mapper")
+class MybatisplusDemoApplicationTests {
+
+    @Autowired
+    private EmployeeService employeeService;
+
+    @Test
+    void contextLoads() {
+        List<Employee> list = employeeService.listAllByLastName("tom");
+        list.forEach(System.out::println);
+    }
+}
+```
+
+运行结果:
+
+```java
+Employee(id=2, lastName=tom, email=tom@qq.com, age=30)
+```
+
+## ID 策略
+
+在创建表的时候我故意没有设置主键的增长策略,现在我们来插入一条数据,看看主键是如何增长的:
+
+```java
+@Test
+void contextLoads() {
+    Employee employee = new Employee();
+    employee.setLastName("lisa");
+    employee.setEmail("lisa@qq.com");
+    employee.setAge(20);
+    employeeService.save(employee);
+}
+```
+
+插入成功后查询一下数据表:
+
+```sql
+mysql> select * from tbl_employee;
++---------------------+-----------+--------------+--------+------+
+| id                  | last_name | email        | gender | age  |
++---------------------+-----------+--------------+--------+------+
+|                   1 | jack      | jack@qq.com  | 1      |   35 |
+|                   2 | tom       | tom@qq.com   | 1      |   30 |
+|                   3 | jerry     | jerry@qq.com | 1      |   40 |
+| 1385934720849584129 | lisa      | lisa@qq.com  | NULL   |   20 |
++---------------------+-----------+--------------+--------+------+
+4 rows in set (0.00 sec)
+```
+
+可以看到 id 是一串相当长的数字,这是什么意思呢?提前剧透一下,这其实是分布式 id,那又何为分布式 id 呢?
+
+我们知道,对于一个大型应用,其访问量是非常巨大的,就比如说一个网站每天都有人进行注册,注册的用户信息就需要存入数据表,随着日子一天天过去,数据表中的用户越来越多,此时数据库的查询速度就会受到影响,所以一般情况下,当数据量足够庞大时,数据都会做分库分表的处理。
+
+然而,一旦分表,问题就产生了,很显然这些分表的数据都是属于同一张表的数据,只是因为数据量过大而分成若干张表,那么这几张表的主键 id 该怎么管理呢?每张表维护自己的 id?那数据将会有很多的 id 重复,这当然是不被允许的,其实,我们可以使用算法来生成一个绝对不会重复的 id,这样问题就迎刃而解了,事实上,分布式 id 的解决方案有很多:
+
+1. UUID
+1. SnowFlake
+1. TinyID
+1. Uidgenerator
+1. Leaf
+6. Tinyid
+7. ......
+
+以 UUID 为例,它生成的是一串由数字和字母组成的字符串,显然并不适合作为数据表的 id,而且 id 保持递增有序会加快表的查询效率,基于此,`MyBatisPlus` 使用的就是 `SnowFlake`(雪花算法)。
+
+`Snowflake` 是 Twitter 开源的分布式 ID 生成算法。`Snowflake` 由 64 bit 的二进制数字组成,这 64bit 的二进制被分成了几部分,每一部分存储的数据都有特定的含义:
+
+- **第 0 位**: 符号位(标识正负),始终为 0,没有用,不用管。
+- **第 1~41 位** :一共 41 位,用来表示时间戳,单位是毫秒,可以支撑 2 ^41 毫秒(约 69 年)
+- **第 42~52 位** :一共 10 位,一般来说,前 5 位表示机房 ID,后 5 位表示机器 ID(实际项目中可以根据实际情况调整)。这样就可以区分不同集群/机房的节点。
+- **第 53~64 位** :一共 12 位,用来表示序列号。 序列号为自增值,代表单台机器每毫秒能够产生的最大 ID 数(2^12 = 4096),也就是说单台机器每毫秒最多可以生成 4096 个 唯一 ID。
+
+![](https://oscimg.oschina.net/oscnet/up-a7e54a77b5ab1d9fa16d5ae3a3c50c5aee9.png)
+
+这也就是为什么插入数据后新的数据 id 是一长串数字的原因了,我们可以在实体类中使用 `@TableId` 来设置主键的策略:
+
+```java
+@Data
+@TableName("tbl_employee")
+public class Employee {
+
+    @TableId(type = IdType.AUTO) // 设置主键策略
+    private Long id;
+    private String lastName;
+    private String email;
+    private Integer age;
+}
+```
+
+`MyBatisPlus` 提供了几种主键的策略:
+![](https://img-blog.csdnimg.cn/20210519084011745.png)
+其中 `AUTO` 表示数据库自增策略,该策略下需要数据库实现主键的自增(auto_increment),`ASSIGN_ID` 是雪花算法,默认使用的是该策略,`ASSIGN_UUID` 是 UUID 策略,一般不会使用该策略。
+
+这里多说一点, 当实体类的主键名为 id,并且数据表的主键名也为 id 时,此时 `MyBatisPlus` 会自动判定该属性为主键 id,倘若名字不是 id 时,就需要标注 `@TableId` 注解,若是实体类中主键名与数据表的主键名不一致,则可以进行声明:
+
+```java
+@TableId(value = "uid",type = IdType.AUTO) // 设置主键策略
+private Long id;
+```
+
+还可以在配置文件中配置全局的主键策略:
+
+```yaml
+mybatis-plus:
+  global-config:
+    db-config:
+      id-type: auto
+```
+
+这样能够避免在每个实体类中重复设置主键策略。
+
+## 属性自动填充
+
+翻阅《阿里巴巴Java开发手册》,在第 5 章 MySQL 数据库可以看到这样一条规范:
+![](https://img-blog.csdnimg.cn/20210426214821389.png)
+对于一张数据表,它必须具备三个字段:
+
+- `id` : 唯一ID
+- `gmt_create` : 保存的是当前数据创建的时间
+- `gmt_modified` : 保存的是更新时间
+
+我们改造一下数据表:
+
+```sql
+alter table tbl_employee add column gmt_create datetime not null;
+alter table tbl_employee add column gmt_modified datetime not null;
+```
+
+然后改造一下实体类:
+
+```java
+@Data
+@TableName("tbl_employee")
+public class Employee {
+
+    @TableId(type = IdType.AUTO) // 设置主键策略
+    private Long id;
+    private String lastName;
+    private String email;
+    private Integer age;
+    private LocalDateTime gmtCreate;
+    private LocalDateTime gmtModified;
+}
+```
+
+此时我们在插入数据和更新数据的时候就需要手动去维护这两个属性:
+
+```java
+@Test
+void contextLoads() {
+    Employee employee = new Employee();
+    employee.setLastName("lisa");
+    employee.setEmail("lisa@qq.com");
+    employee.setAge(20);
+    // 设置创建时间
+    employee.setGmtCreate(LocalDateTime.now());
+    employee.setGmtModified(LocalDateTime.now());
+    employeeService.save(employee);
+}
+
+@Test
+void contextLoads() {
+    Employee employee = new Employee();
+    employee.setId(1385934720849584130L);
+    employee.setAge(50);
+    // 设置创建时间
+    employee.setGmtModified(LocalDateTime.now());
+    employeeService.updateById(employee);
+}
+```
+
+每次都需要维护这两个属性未免过于麻烦,好在 `MyBatisPlus` 提供了字段自动填充功能来帮助我们进行管理,需要使用到的是 `@TableField` 注解:
+
+```java
+@Data
+@TableName("tbl_employee")
+public class Employee {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private String lastName;
+    private String email;
+    private Integer age;
+    @TableField(fill = FieldFill.INSERT) // 插入的时候自动填充
+    private LocalDateTime gmtCreate;
+    @TableField(fill = FieldFill.INSERT_UPDATE) // 插入和更新的时候自动填充
+    private LocalDateTime gmtModified;
+}
+```
+
+然后编写一个类实现 MetaObjectHandler 接口:
+
+```java
+@Component
+@Slf4j
+public class MyMetaObjectHandler implements MetaObjectHandler {
+
+    /**
+     * 实现插入时的自动填充
+     * @param metaObject
+     */
+    @Override
+    public void insertFill(MetaObject metaObject) {
+        log.info("insert开始属性填充");
+        this.strictInsertFill(metaObject,"gmtCreate", LocalDateTime.class,LocalDateTime.now());
+        this.strictInsertFill(metaObject,"gmtModified", LocalDateTime.class,LocalDateTime.now());
+    }
+
+    /**
+     * 实现更新时的自动填充
+     * @param metaObject
+     */
+    @Override
+    public void updateFill(MetaObject metaObject) {
+        log.info("update开始属性填充");
+        this.strictInsertFill(metaObject,"gmtModified", LocalDateTime.class,LocalDateTime.now());
+    }
+}
+```
+
+该接口中有两个未实现的方法,分别为插入和更新时的填充方法,在方法中调用 `strictInsertFill()` 方法 即可实现属性的填充,它需要四个参数:
+
+1. `metaObject`:元对象,就是方法的入参
+1. `fieldName`:为哪个属性进行自动填充
+1. `fieldType`:属性的类型
+1. `fieldVal`:需要填充的属性值
+
+此时在插入和更新数据之前,这两个方法会先被执行,以实现属性的自动填充,通过日志我们可以进行验证:
+
+```java
+@Test
+void contextLoads() {
+    Employee employee = new Employee();
+    employee.setId(1385934720849584130L);
+    employee.setAge(15);
+    employeeService.updateById(employee);
+}
+```
+
+运行结果:
+
+```java
+INFO 15584 --- [           main] c.w.m.handler.MyMetaObjectHandler        : update开始属性填充
+2021-04-24 21:32:19.788  INFO 15584 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
+2021-04-24 21:32:21.244  INFO 15584 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
+```
+
+属性填充其实可以进行一些优化,考虑一些特殊情况,对于一些不存在的属性,就不需要进行属性填充,对于一些设置了值的属性,也不需要进行属性填充,这样可以提高程序的整体运行效率:
+
+```java
+@Component
+@Slf4j
+public class MyMetaObjectHandler implements MetaObjectHandler {
+
+    @Override
+    public void insertFill(MetaObject metaObject) {
+        boolean hasGmtCreate = metaObject.hasSetter("gmtCreate");
+        boolean hasGmtModified = metaObject.hasSetter("gmtModified");
+        if (hasGmtCreate) {
+            Object gmtCreate = this.getFieldValByName("gmtCreate", metaObject);
+            if (gmtCreate == null) {
+                this.strictInsertFill(metaObject, "gmtCreate", LocalDateTime.class, LocalDateTime.now());
+            }
+        }
+        if (hasGmtModified) {
+            Object gmtModified = this.getFieldValByName("gmtModified", metaObject);
+            if (gmtModified == null) {
+                this.strictInsertFill(metaObject, "gmtModified", LocalDateTime.class, LocalDateTime.now());
+            }
+        }
+    }
+
+    @Override
+    public void updateFill(MetaObject metaObject) {
+        boolean hasGmtModified = metaObject.hasSetter("gmtModified");
+        if (hasGmtModified) {
+            Object gmtModified = this.getFieldValByName("gmtModified", metaObject);
+            if (gmtModified == null) {
+                this.strictInsertFill(metaObject, "gmtModified", LocalDateTime.class, LocalDateTime.now());
+            }
+        }
+    }
+}
+```
+
+## 逻辑删除
+
+逻辑删除对应的是物理删除,分别介绍一下这两个概念:
+
+1. **物理删除** :指的是真正的删除,即:当执行删除操作时,将数据表中的数据进行删除,之后将无法再查询到该数据
+1. **逻辑删除** :并不是真正意义上的删除,只是对于用户不可见了,它仍然存在与数据表中
+
+在这个数据为王的时代,数据就是财富,所以一般并不会有哪个系统在删除某些重要数据时真正删掉了数据,通常都是在数据库中建立一个状态列,让其默认为 0,当为 0 时,用户可见;当执行了删除操作,就将状态列改为 1,此时用户不可见,但数据还是在表中的。
+
+![](https://img-blog.csdnimg.cn/20210426215107210.png)
+
+按照《阿里巴巴Java开发手册》第 5 章 MySQL 数据库相关的建议,我们来为数据表新增一个`is_deleted` 字段:
+
+```sql
+alter table tbl_employee add column is_deleted tinyint not null;
+```
+
+在实体类中也要添加这一属性:
+
+```java
+@Data
+@TableName("tbl_employee")
+public class Employee {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private String lastName;
+    private String email;
+    private Integer age;
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime gmtCreate;
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private LocalDateTime gmtModified;
+    /**
+     * 逻辑删除属性
+     */
+    @TableLogic
+    @TableField("is_deleted")
+    private Boolean deleted;
+}
+```
+
+![](https://img-blog.csdnimg.cn/2021042621530162.png)
+
+还是参照《阿里巴巴Java开发手册》第 5 章 MySQL 数据库相关的建议,对于布尔类型变量,不能加 is 前缀,所以我们的属性被命名为 `deleted`,但此时就无法与数据表的字段进行对应了,所以我们需要使用 `@TableField` 注解来声明一下数据表的字段名,而 `@TableLogin` 注解用于设置逻辑删除属性;此时我们执行删除操作:
+
+```java
+@Test
+void contextLoads() {
+    employeeService.removeById(3);
+}
+```
+
+查询数据表:
+
+```sql
+mysql> select * from tbl_employee;
++---------------------+-----------+--------------+--------+------+---------------------+---------------------+------------+
+| id                  | last_name | email        | gender | age  | gmt_create          | gmt_modified        | is_deleted |
++---------------------+-----------+--------------+--------+------+---------------------+---------------------+------------+
+|                   1 | jack      | jack@qq.com  | 1      |   35 | 0000-00-00 00:00:00 | 0000-00-00 00:00:00 |          0 |
+|                   2 | tom       | tom@qq.com   | 1      |   30 | 0000-00-00 00:00:00 | 0000-00-00 00:00:00 |          0 |
+|                   3 | jerry     | jerry@qq.com | 1      |   40 | 0000-00-00 00:00:00 | 0000-00-00 00:00:00 |          1 |
+| 1385934720849584129 | lisa      | lisa@qq.com  | NULL   |   20 | 0000-00-00 00:00:00 | 0000-00-00 00:00:00 |          0 |
+| 1385934720849584130 | lisa      | lisa@qq.com  | NULL   |   15 | 2021-04-24 21:14:18 | 2021-04-24 21:32:19 |          0 |
++---------------------+-----------+--------------+--------+------+---------------------+---------------------+------------+
+5 rows in set (0.00 sec)
+```
+
+可以看到数据并没有被删除,只是 `is_deleted` 字段的属性值被更新成了 1,此时我们再来执行查询操作:
+
+```java
+@Test
+void contextLoads() {
+    List<Employee> list = employeeService.list();
+    list.forEach(System.out::println);
+}
+```
+
+执行结果:
+
+```java
+Employee(id=1, lastName=jack, email=jack@qq.com, age=35, gmtCreate=2021-04-24T21:14:18, gmtModified=2021-04-24T21:14:18, deleted=false)
+Employee(id=2, lastName=tom, email=tom@qq.com, age=30, gmtCreate=2021-04-24T21:14:18, gmtModified=2021-04-24T21:14:18, deleted=false)
+Employee(id=1385934720849584129, lastName=lisa, email=lisa@qq.com, age=20, gmtCreate=2021-04-24T21:14:18, gmtModified=2021-04-24T21:14:18, deleted=false)
+Employee(id=1385934720849584130, lastName=lisa, email=lisa@qq.com, age=15, gmtCreate=2021-04-24T21:14:18, gmtModified=2021-04-24T21:32:19, deleted=false)
+```
+
+会发现第三条数据并没有被查询出来,它是如何实现的呢?我们可以输出 `MyBatisPlus` 生成的 SQL 来分析一下,在配置文件中进行配置:
+
+```yaml
+mybatis-plus:
+  configuration:
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 输出SQL日志
+```
+
+运行结果:
+
+```java
+==>  Preparing: SELECT id,last_name,email,age,gmt_create,gmt_modified,is_deleted AS deleted FROM tbl_employee WHERE is_deleted=0
+==> Parameters:
+<==    Columns: id, last_name, email, age, gmt_create, gmt_modified, deleted
+<==        Row: 1, jack, jack@qq.com, 35, 2021-04-24 21:14:18, 2021-04-24 21:14:18, 0
+<==        Row: 2, tom, tom@qq.com, 30, 2021-04-24 21:14:18, 2021-04-24 21:14:18, 0
+<==        Row: 1385934720849584129, lisa, lisa@qq.com, 20, 2021-04-24 21:14:18, 2021-04-24 21:14:18, 0
+<==        Row: 1385934720849584130, lisa, lisa@qq.com, 15, 2021-04-24 21:14:18, 2021-04-24 21:32:19, 0
+<==      Total: 4
+```
+
+原来它在查询时携带了一个条件: `is_deleted=0` ,这也说明了 `MyBatisPlus` 默认 0 为不删除,1 为删除。
+若是你想修改这个规定,比如设置-1 为删除,1 为不删除,也可以进行配置:
+
+```yaml
+mybatis-plus:
+  global-config:
+    db-config:
+      id-type: auto
+      logic-delete-field: deleted # 逻辑删除属性名
+      logic-delete-value: -1 # 删除值
+      logic-not-delete-value: 1 # 不删除值
+```
+
+但建议使用默认的配置,阿里巴巴开发手册也规定 1 表示删除,0 表示未删除。
+
+## 分页插件
+
+对于分页功能,`MyBatisPlus` 提供了分页插件,只需要进行简单的配置即可实现:
+
+```java
+@Configuration
+public class MyBatisConfig {
+
+    /**
+     * 注册分页插件
+     * @return
+     */
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor() {
+        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
+        return interceptor;
+    }
+}
+```
+
+接下来我们就可以使用分页插件提供的功能了:
+
+```java
+@Test
+void contextLoads() {
+    Page<Employee> page = new Page<>(1,2);
+    employeeService.page(page, null);
+    List<Employee> employeeList = page.getRecords();
+    employeeList.forEach(System.out::println);
+    System.out.println("获取总条数:" + page.getTotal());
+    System.out.println("获取当前页码:" + page.getCurrent());
+    System.out.println("获取总页码:" + page.getPages());
+    System.out.println("获取每页显示的数据条数:" + page.getSize());
+    System.out.println("是否有上一页:" + page.hasPrevious());
+    System.out.println("是否有下一页:" + page.hasNext());
+}
+```
+
+其中的 `Page` 对象用于指定分页查询的规则,这里表示按每页两条数据进行分页,并查询第一页的内容,运行结果:
+
+```java
+Employee(id=1, lastName=jack, email=jack@qq.com, age=35, gmtCreate=2021-04-24T21:14:18, gmtModified=2021-04-24T21:14:18, deleted=0)
+Employee(id=2, lastName=tom, email=tom@qq.com, age=30, gmtCreate=2021-04-24T21:14:18, gmtModified=2021-04-24T21:14:18, deleted=0)
+获取总条数:4
+获取当前页码:1
+获取总页码:2
+获取每页显示的数据条数:2
+是否有上一页:false
+是否有下一页:true
+```
+
+倘若在分页过程中需要限定一些条件,我们就需要构建 QueryWrapper 来实现:
+
+```java
+@Test
+void contextLoads() {
+    Page<Employee> page = new Page<>(1, 2);
+    employeeService.page(page, new QueryWrapper<Employee>()
+                         .between("age", 20, 50)
+                         .eq("gender", 1));
+    List<Employee> employeeList = page.getRecords();
+    employeeList.forEach(System.out::println);
+}
+```
+
+此时分页的数据就应该是年龄在 20~50 岁之间,且 gender 值为 1 的员工信息,然后再对这些数据进行分页。
+
+## 乐观锁
+
+当程序中出现并发访问时,就需要保证数据的一致性。以商品系统为例,现在有两个管理员均想对同一件售价为 100 元的商品进行修改,A 管理员正准备将商品售价改为 150 元,但此时出现了网络问题,导致 A 管理员的操作陷入了等待状态;此时 B 管理员也进行修改,将商品售价改为了 200 元,修改完成后 B 管理员退出了系统,此时 A 管理员的操作也生效了,这样便使得 A 管理员的操作直接覆盖了 B 管理员的操作,B 管理员后续再进行查询时会发现商品售价变为了 150 元,这样的情况是绝对不允许发生的。
+
+要想解决这一问题,可以给数据表加锁,常见的方式有两种:
+
+1. 乐观锁
+1. 悲观锁
+
+悲观锁认为并发情况一定会发生,所以在某条数据被修改时,为了避免其它人修改,会直接对数据表进行加锁,它依靠的是数据库本身提供的锁机制(表锁、行锁、读锁、写锁)。
+
+而乐观锁则相反,它认为数据产生冲突的情况一般不会发生,所以在修改数据的时候并不会对数据表进行加锁的操作,而是在提交数据时进行校验,判断提交上来的数据是否会发生冲突,如果发生冲突,则提示用户重新进行操作,一般的实现方式为 `设置版本号字段` 。
+
+就以商品售价为例,在该表中设置一个版本号字段,让其初始为 1,此时 A 管理员和 B 管理员同时需要修改售价,它们会先读取到数据表中的内容,此时两个管理员读取到的版本号都为 1,此时 B 管理员的操作先生效了,它就会将当前数据表中对应数据的版本号与最开始读取到的版本号作一个比对,发现没有变化,于是修改就生效了,此时版本号加 1。
+
+而 A 管理员马上也提交了修改操作,但是此时的版本号为 2,与最开始读取到的版本号并不对应,这就说明数据发生了冲突,此时应该提示 A 管理员操作失败,并让 A 管理员重新查询一次数据。
+
+![](https://img-blog.csdnimg.cn/20210426221408623.png)
+
+乐观锁的优势在于采取了更加宽松的加锁机制,能够提高程序的吞吐量,适用于读操作多的场景。
+
+那么接下来我们就来模拟这一过程。
+
+**1.创建一张新的数据表:**
+
+```sql
+create table shop(
+	id bigint(20) not null auto_increment,
+  name varchar(30) not null,
+  price int(11) default 0,
+  version int(11) default 1,
+  primary key(id)
+);
+
+insert into shop(id,name,price) values(1,'笔记本电脑',8000);
+```
+
+**2.创建实体类:**
+
+```java
+@Data
+public class Shop {
+
+    private Long id;
+    private String name;
+    private Integer price;
+    private Integer version;
+}
+```
+
+**3.创建对应的 `Mapper` 接口:**
+
+```java
+public interface ShopMapper extends BaseMapper<Shop> {
+}
+```
+
+**4.编写测试代码:**
+
+```java
+@SpringBootTest
+@MapperScan("com.wwj.mybatisplusdemo.mapper")
+class MybatisplusDemoApplicationTests {
+
+    @Autowired
+    private ShopMapper shopMapper;
+
+    /**
+     * 模拟并发场景
+     */
+    @Test
+    void contextLoads() {
+        // A、B管理员读取数据
+        Shop A = shopMapper.selectById(1L);
+        Shop B = shopMapper.selectById(1L);
+        // B管理员先修改
+        B.setPrice(9000);
+        int result = shopMapper.updateById(B);
+        if (result == 1) {
+            System.out.println("B管理员修改成功!");
+        } else {
+            System.out.println("B管理员修改失败!");
+        }
+        // A管理员后修改
+        A.setPrice(8500);
+        int result2 = shopMapper.updateById(A);
+        if (result2 == 1) {
+            System.out.println("A管理员修改成功!");
+        } else {
+            System.out.println("A管理员修改失败!");
+        }
+        // 最后查询
+        System.out.println(shopMapper.selectById(1L));
+    }
+}
+```
+
+执行结果:
+
+```java
+B管理员修改成功!
+A管理员修改成功!
+Shop(id=1, name=笔记本电脑, price=8500, version=1)
+```
+
+**问题出现了,B 管理员的操作被 A 管理员覆盖,那么该如何解决这一问题呢?**
+
+其实 `MyBatisPlus` 已经提供了乐观锁机制,只需要在实体类中使用 `@Version` 声明版本号属性:
+
+```java
+@Data
+public class Shop {
+
+    private Long id;
+    private String name;
+    private Integer price;
+    @Version // 声明版本号属性
+    private Integer version;
+}
+```
+
+然后注册乐观锁插件:
+
+```java
+@Configuration
+public class MyBatisConfig {
+
+    /**
+     * 注册插件
+     * @return
+     */
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor() {
+        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        // 分页插件
+        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
+        // 乐观锁插件
+        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
+        return interceptor;
+    }
+}
+```
+
+重新执行测试代码,结果如下:
+
+```java
+B管理员修改成功!
+A管理员修改失败!
+Shop(id=1, name=笔记本电脑, price=9000, version=2)
+```
+
+此时 A 管理员的修改就失败了,它需要重新读取最新的数据才能再次进行修改。
+
+## 条件构造器
+
+在分页插件中我们简单地使用了一下条件构造器(`Wrapper`),下面我们来详细了解一下。
+先来看看 `Wrapper` 的继承体系:
+![](https://img-blog.csdnimg.cn/20210519083944315.png)
+分别介绍一下它们的作用:
+
+- `Wrapper`:条件构造器抽象类,最顶端的父类
+  - `AbstractWrapper`:查询条件封装抽象类,生成 SQL 的 where 条件
+    - `QueryWrapper`:用于对象封装
+    - `UpdateWrapper`:用于条件封装
+  - `AbstractLambdaWrapper`:Lambda 语法使用 Wrapper
+    - `LambdaQueryWrapper`:用于对象封装,使用 Lambda 语法
+    - `LambdaUpdateWrapper`:用于条件封装,使用 Lambda 语法
+
+通常我们使用的都是 `QueryWrapper` 和 `UpdateWrapper`,若是想使用 Lambda 语法来编写,也可以使用 `LambdaQueryWrapper` 和 `LambdaUpdateWrapper`,通过这些条件构造器,我们能够很方便地来实现一些复杂的筛选操作,比如:
+
+```java
+@SpringBootTest
+@MapperScan("com.wwj.mybatisplusdemo.mapper")
+class MybatisplusDemoApplicationTests {
+
+    @Autowired
+    private EmployeeMapper employeeMapper;
+
+    @Test
+    void contextLoads() {
+        // 查询名字中包含'j',年龄大于20岁,邮箱不为空的员工信息
+        QueryWrapper<Employee> wrapper = new QueryWrapper<>();
+        wrapper.like("last_name", "j");
+        wrapper.gt("age", 20);
+        wrapper.isNotNull("email");
+        List<Employee> list = employeeMapper.selectList(wrapper);
+        list.forEach(System.out::println);
+    }
+}
+```
+
+运行结果:
+
+```java
+Employee(id=1, lastName=jack, email=jack@qq.com, age=35, gmtCreate=2021-04-24T21:14:18, gmtModified=2021-04-24T21:14:18, deleted=0)
+```
+
+条件构造器提供了丰富的条件方法帮助我们进行条件的构造,比如 `like` 方法会为我们建立模糊查询,查看一下控制台输出的 SQL:
+
+```sql
+==>  Preparing: SELECT id,last_name,email,age,gmt_create,gmt_modified,is_deleted AS deleted FROM tbl_employee WHERE is_deleted=0 AND (last_name LIKE ? AND age > ? AND email IS NOT NULL)
+==> Parameters: %j%(String), 20(Integer)
+```
+
+可以看到它是对 `j` 的前后都加上了 `%` ,若是只想查询以 `j` 开头的名字,则可以使用 `likeRight` 方法,若是想查询以 `j` 结尾的名字,,则使用 `likeLeft` 方法。
+
+年龄的比较也是如此, `gt` 是大于指定值,若是小于则调用 `lt` ,大于等于调用 `ge` ,小于等于调用 `le` ,不等于调用 `ne` ,还可以使用 `between` 方法实现这一过程,相关的其它方法都可以查阅源码进行学习。
+
+因为这些方法返回的其实都是自身实例,所以可使用链式编程:
+
+```java
+@Test
+void contextLoads() {
+    // 查询名字中包含'j',年龄大于20岁,邮箱不为空的员工信息
+    QueryWrapper<Employee> wrapper = new QueryWrapper<Employee>()
+        .likeLeft("last_name", "j")
+        .gt("age", 20)
+        .isNotNull("email");
+    List<Employee> list = employeeMapper.selectList(wrapper);
+    list.forEach(System.out::println);
+}
+```
+
+也可以使用 `LambdaQueryWrapper` 实现:
+
+```java
+@Test
+void contextLoads() {
+    // 查询名字中包含'j',年龄大于20岁,邮箱不为空的员工信息
+    LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<Employee>()
+        .like(Employee::getLastName,"j")
+        .gt(Employee::getAge,20)
+        .isNotNull(Employee::getEmail);
+    List<Employee> list = employeeMapper.selectList(wrapper);
+    list.forEach(System.out::println);
+}
+```
+
+这种方式的好处在于对字段的设置不是硬编码,而是采用方法引用的形式,效果与 `QueryWrapper` 是一样的。
+
+`UpdateWrapper` 与 `QueryWrapper` 不同,它的作用是封装更新内容的,比如:
+
+```java
+@Test
+void contextLoads() {
+    UpdateWrapper<Employee> wrapper = new UpdateWrapper<Employee>()
+        .set("age", 50)
+        .set("email", "emp@163.com")
+        .like("last_name", "j")
+        .gt("age", 20);
+    employeeMapper.update(null, wrapper);
+}
+```
+
+将名字中包含 `j` 且年龄大于 20 岁的员工年龄改为 50,邮箱改为 emp@163.com,`UpdateWrapper` 不仅能够封装更新内容,也能作为查询条件,所以在更新数据时可以直接构造一个 `UpdateWrapper` 来设置更新内容和条件。
\ No newline at end of file

From be8de72725a23512861f6c1036cd9716d9fc55a4 Mon Sep 17 00:00:00 2001
From: guide <koushuangbwcx@163.com>
Date: Wed, 19 May 2021 11:45:30 +0800
Subject: [PATCH 21/22] Update springboot-mybatis.md

---
 docs/basis/springboot-mybatis.md | 2 --
 1 file changed, 2 deletions(-)

diff --git a/docs/basis/springboot-mybatis.md b/docs/basis/springboot-mybatis.md
index 47e6bd4..1955c8c 100644
--- a/docs/basis/springboot-mybatis.md
+++ b/docs/basis/springboot-mybatis.md
@@ -35,8 +35,6 @@ SpringBoot 整合 Mybatis 有两种常用的方式,一种就是我们常见的
 - 数据库:MySQL
 - SpringBoot版本:2.1.0
 
-
-
 ### 1.2 创建工程
 
 创建一个基本的 SpringBoot 项目,我这里就不多说这方面问题了,具体可以参考前面的文章。

From 76c018d7d898d61d9ce07a975c0056a36a5f9358 Mon Sep 17 00:00:00 2001
From: guide <koushuangbwcx@163.com>
Date: Wed, 19 May 2021 11:45:32 +0800
Subject: [PATCH 22/22] Update README.md

---
 README.md | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index 025fee7..e98b843 100644
--- a/README.md
+++ b/README.md
@@ -36,9 +36,9 @@
 4. [Spring 如何优雅读取配置文件?](./docs/basis/read-config-properties.md) 
 5. **异常处理** :[Spring Boot 异常处理的几种方式](./docs/advanced/springboot-handle-exception.md)、[Spring Boot 异常处理在实际项目中的应用](./docs/advanced/springboot-handle-exception-plus.md)
 6. **JPA** : [ Spring Boot JPA 基础:常见操作解析](./docs/basis/springboot-jpa.md) 、 [JPA 中非常重要的连表查询就是这么简单](./docs/basis/springboot-jpa-lianbiao.md)
-7. [MyBatis-Plus  从入门到上手干事!](./docs/MyBatisPlus.md)
-8. **拦截器和过滤器** :[SpringBoot 实现过滤器](./docs/basis/springboot-filter.md) 、[SpringBoot 实现拦截器](./docs/basis/springboot-interceptor.md)
-9. **MyBatis**  :[整合 SpringBoot+Mybatis](./docs/basis/springboot-mybatis.md) 、[SpirngBoot2.0+ 的 SpringBoot+Mybatis 多数据源配置](./docs/basis/springboot-mybatis-mutipledatasource.md) (TODO:早期文章,不建议阅读,待重构~)
+7. **拦截器和过滤器** :[SpringBoot 实现过滤器](./docs/basis/springboot-filter.md) 、[SpringBoot 实现拦截器](./docs/basis/springboot-interceptor.md)
+8. **MyBatis**  :[整合 SpringBoot+Mybatis](./docs/basis/springboot-mybatis.md) 、[SpirngBoot2.0+ 的 SpringBoot+Mybatis 多数据源配置](./docs/basis/springboot-mybatis-mutipledatasource.md) (TODO:早期文章,不建议阅读,待重构~)
+9. [MyBatis-Plus  从入门到上手干事!](./docs/MyBatisPlus.md)
 10. [SpringBoot 2.0+ 集成 Swagger 官方 Starter + knife4j 增强方案](./docs/basis/swagger.md)
 
 ### 进阶