@@ -140,4 +140,4 @@ pnpm build
- 文章遵循[ CC 4.0 BY-SA ](http://creativecommons.org/licenses/by-sa/4.0/)版权协议,转载请附上原文出处链接和声明
- 源码遵循 [MIT](https://github.com/Charles7c/charles7c.github.io/blob/main/LICENSE) 许可协议
-- Copyright © 2019-2022 Charles7c
\ No newline at end of file
+- Copyright © 2019-2022 Charles7c
diff --git a/docs/.vitepress/config/markdown.ts b/docs/.vitepress/config/markdown.ts
index 993ff39a9..03fa87da9 100644
--- a/docs/.vitepress/config/markdown.ts
+++ b/docs/.vitepress/config/markdown.ts
@@ -6,7 +6,7 @@ export const markdown: MarkdownOptions = {
// Shiki主题, 所有主题参见: https://github.com/shikijs/shiki/blob/main/docs/themes.md
theme: {
light: 'github-light',
- dark: 'github-dark-dimmed'
+ dark: 'github-dark'
},
// lineNumbers: true, // 启用行号
diff --git a/docs/.vitepress/config/nav.ts b/docs/.vitepress/config/nav.ts
index 1b147947f..f3e27abc0 100644
--- a/docs/.vitepress/config/nav.ts
+++ b/docs/.vitepress/config/nav.ts
@@ -4,19 +4,14 @@ export const nav: DefaultTheme.Config['nav'] = [
{
text: '我的分类',
items: [
- { text: 'Bug万象集', link: '/categories/issues/index', activeMatch: '/categories/issues/' },
- { text: '"杂碎"逆袭史', link: '/categories/fragments/index', activeMatch: '/categories/fragments/' },
- { text: '工具四海谈', link: '/categories/tools/index', activeMatch: '/categories/tools/' },
- { text: '方案春秋志', link: '/categories/solutions/index', activeMatch: '/categories/solutions/' }
+ { text: '分类1', link: '/categories/category1/index', activeMatch: '/categories/category1/' },
],
activeMatch: '/categories/'
},
{
text: '我的小册',
items: [
- { text: 'Java基础快速入门', link: '/courses/java/index', activeMatch: '/courses/java/' },
- { text: 'MySQL快速入门', link: '/courses/mysql/index', activeMatch: '/courses/mysql/' },
- { text: 'MyBatis快速入门', link: '/courses/mybatis/index', activeMatch: '/courses/mybatis/' }
+ { text: '小册1', link: '/courses/course1/index', activeMatch: '/courses/course1/' },
],
activeMatch: '/courses/'
},
@@ -30,12 +25,4 @@ export const nav: DefaultTheme.Config['nav'] = [
link: '/archives',
activeMatch: '/archives'
},
- {
- text: '关于',
- items: [
- { text: '关于知识库', link: '/about/index', activeMatch: '/about/index' },
- { text: '关于我', link: '/about/me', activeMatch: '/about/me' }
- ],
- activeMatch: '/about/' // // 当前页面处于匹配路径下时, 对应导航菜单将突出显示
- },
-];
\ No newline at end of file
+];
diff --git a/docs/.vitepress/config/sidebar.ts b/docs/.vitepress/config/sidebar.ts
index 5d63a0ee9..1610cf400 100644
--- a/docs/.vitepress/config/sidebar.ts
+++ b/docs/.vitepress/config/sidebar.ts
@@ -5,21 +5,16 @@ import { getChineseZodiac, getChineseZodiacAlias } from '../theme/utils.ts';
const sync = fg.sync;
export const sidebar: DefaultTheme.Config['sidebar'] = {
- '/categories/issues/': getItemsByDate("categories/issues"),
- '/categories/fragments/': getItemsByDate("categories/fragments"),
- '/categories/solutions/': getItemsByDate("categories/solutions"),
- '/categories/tools/': getItemsByDate("categories/tools"),
+ '/categories/category1/': getItemsByDate("categories/category1"),
- '/courses/java/': getItems("courses/java"),
- '/courses/mysql/': getItems("courses/mysql"),
- '/courses/mybatis/': getItems("courses/mybatis"),
+ '/courses/course1/': getItems("courses/course1"),
}
/**
* 根据 某分类/YYYY/MM/dd/xxx.md 的目录格式, 获取侧边栏分组及分组下标题
- *
+ *
* /categories/issues/2022/07/20/xxx.md
- *
+ *
* @param path 扫描基础路径
* @returns {DefaultTheme.SidebarItem[]}
*/
@@ -107,9 +102,9 @@ function getItemsByDate (path: string) {
/**
* 根据 某小课/序号-分组/序号-xxx.md 的目录格式, 获取侧边栏分组及分组下标题
- *
+ *
* courses/mybatis/01-MyBatis基础/01-xxx.md
- *
+ *
* @param path 扫描基础路径
* @returns {DefaultTheme.SidebarItem[]}
*/
@@ -163,7 +158,7 @@ function getItems (path: string) {
/**
* 添加序号
- *
+ *
* @param groups 分组数据
*/
function addOrderNumber(groups) {
@@ -182,4 +177,4 @@ function addOrderNumber(groups) {
items[j].text = `${indexStyle}${items[j].text}`;
}
}
-}
\ No newline at end of file
+}
diff --git a/docs/.vitepress/config/theme.ts b/docs/.vitepress/config/theme.ts
index 738b05982..6e80d5f99 100644
--- a/docs/.vitepress/config/theme.ts
+++ b/docs/.vitepress/config/theme.ts
@@ -16,7 +16,13 @@ export const themeConfig: DefaultTheme.Config = {
darkModeSwitchLabel: '切换日光/暗黑模式',
sidebarMenuLabel: '文章',
returnToTopLabel: '返回顶部',
- lastUpdatedText: '最后更新', // 最后更新时间文本配置, 需先配置lastUpdated为true
+ lastUpdated: {
+ text: '最后更新',
+ formatOptions:{
+ dateStyle:'full',
+ timeStyle:'short'
+ }
+ },
// 文档页脚文本配置
docFooter: {
prev: '上一篇',
@@ -57,7 +63,7 @@ export const themeConfig: DefaultTheme.Config = {
`
},
- link: 'https://cnadmin.charles7c.top/'
+ link: 'https://continew.top/'
}
],
@@ -65,7 +71,7 @@ export const themeConfig: DefaultTheme.Config = {
// @ts-ignore
articleMetadataConfig: {
author: '查尔斯', // 文章全局默认作者名称
- authorLink: '/about/me', // 点击作者名时默认跳转的链接
+ authorLink: 'https://charles7c.top', // 点击作者名时默认跳转的链接
showViewCount: false, // 是否显示文章阅读数, 需要在 docs/.vitepress/theme/api/config.js 及 interface.js 配置好相应 API 接口
},
// 自定义扩展: 文章版权配置
@@ -81,8 +87,8 @@ export const themeConfig: DefaultTheme.Config = {
// 自定义扩展: 页脚配置
footerConfig: {
showFooter: true, // 是否显示页脚
- icpRecordCode: '津ICP备2022005864号-2', // ICP备案号
- publicSecurityRecordCode: '津公网安备12011202000677号', // 联网备案号
+ icpRecordCode: '津ICP备xxxx-1', // ICP备案号
+ publicSecurityRecordCode: '津公网安备xxxx号', // 联网备案号
copyright: `Copyright © 2019-${new Date().getFullYear()} Charles7c` // 版权信息
}
-}
\ No newline at end of file
+}
diff --git a/docs/about/index.md b/docs/about/index.md
deleted file mode 100644
index 5e5782996..000000000
--- a/docs/about/index.md
+++ /dev/null
@@ -1,55 +0,0 @@
----
-title: 关于知识库
-aside: false
-editLink: false
-lastUpdated: false
----
-
-
-
-
- 📝 查尔斯的个人技术知识库,记录 & 分享个人碎片化、结构化、体系化的技术知识内容。
-
-
----
-
-::: tip 《警惕碎片化陷阱!如何让你的知识体系价值最大化?》
-
-记录,对我们来说意味着什么?
-
-把大脑从那些琐碎的小事中解放出来,去处理更精密复杂的任务?帮助记忆有价值的信息,避免遗忘?又或者是显化内容过滤重组的过程,提升梳理思路的效率......
-
-记录的意义因人而异,但所有的笔记,在记录时都隐藏着一个不被注意到的重要前提,那就是希望知识能被再利用。
-
-潜意识里,我们会觉得记录的创意灵感、知识碎片迟早有一天会派上用场,所以精心地管理个人知识库。但是,你曾记录的笔记被二次查看的频率有多高?你有没有翻阅过去的笔记却惊讶地发现完全不记得曾记录下这件事,收藏了这个知识点?你有没有发现自己难以用上之前记录的知识点?
-
-当一篇笔记完全被遗忘在记忆角落时,它对你的“价值输出”就此停止。
-
-
- -- 有道云笔记·笔记君
-
-
-
-:::
-
-笔者的知识库是关于个人碎片化、结构化、体系化技术知识内容的记录。和大多数人一样,笔者也希望这些内容能被个人再次利用,哪怕没有,也希望能为 “有缘人” 或是后继者增加一些实际靠谱的个人技术经验。
-
-不仅仅是在技术知识层面如此,笔者在各个方面也是一样。尤其是当笔者经历从未有过的程序时,例如:办理个人公积金的程序;为车辆办理上牌的程序。这种经验记录的欲望很强烈,有时候笔者也反复在想,为什么会有这种欲望?后来大概明白,很大程度上是因为吃够了 “吃一堑长一智” 的自我安慰罢了,甚至曾经想过写一个程序,能够记录各种各样的个人经验,让未来的自己或后继者多一些参考,多一些胸有成竹,少走一些弯路,这种意愿强烈到一定程度时又突然醒悟,这不就是个人版知乎吗?
-
-我们继续说回个人知识库的意义,笔者记得有这样一个哲理故事:
-
-::: tip 《一杯水的重量》
-
-哈佛大学有一位教授在课堂上倒了一杯清水,问学生:“这杯水有多重?”有的同学说8盎司(1盎司=28.35克),有的说12盎司,有的说16盎司。
-
-可这位教授却对学生说:“你们说的重量,都与这杯水的重量无关。”
-
-“这杯水的重量取决于我能坚持多久。我坚持一分钟,什么也没发生。我坚持一小时,我的手臂就会开始疼。我一整天都拿着它,我的手臂就会感到麻木。但实际上这杯水的重量没有任何变化,你长时间抓着它,只会让你感觉到更重。生活中的压力和烦恼就像这杯水,稍微想想没有问题,再多想一会,你就开始疼了。整天想着它们,你会感到瘫痪,什么都做不了。永远记住,要把水杯放下。”
-
-:::
-
-从笔者个人来讲,维护知识库也是一种整理。笔者对杂乱的事物是抱有不喜态度的,无论是个人卫生,还是环境:房间、工位,亦或是任何记录。
-
-杂乱的内容会引起笔者思绪上的混乱,被迫转移注意力,所以笔者尽可能的在各种方面定期进行整理、折腾,将知识整理好,不用再担心知识在记忆中遗忘,不用再担心掌握的知识无迹可寻,这样笔者就可以获得思绪上的梳理、 “放下”,可以更好的 “轻装前行”。:smile:
-
-
\ No newline at end of file
diff --git a/docs/about/me.md b/docs/about/me.md
deleted file mode 100644
index 4cfd32293..000000000
--- a/docs/about/me.md
+++ /dev/null
@@ -1,150 +0,0 @@
----
-title: 关于我
-aside: false
-editLink: false
-lastUpdated: false
-showComment: false
----
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
----
-- 👋 Hi, I'm Charles7c
-- 🔭 I'm currently working on backend development
-- 📫 How to reach me: [charles7c@126.com](mailto:charles7c@126.com)
-- 📖 My motto: “东隅已逝,桑榆非晚。”
----
-
-### 后端技术栈
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-### 前端技术栈
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-### DevOps
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-### 运维技术栈
-
-
-
-
-
-
-
-
-
-
-
-### 测试技术栈
-
-
-
-
-
-
-### 开发工具
-
-
-
-
-
-
-
-
-
-
-### 其他
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/docs/categories/fragments/2019/12/31/个人常用Linux命令.md b/docs/categories/category1/2019/12/31/个人常用Linux命令.md
similarity index 99%
rename from docs/categories/fragments/2019/12/31/个人常用Linux命令.md
rename to docs/categories/category1/2019/12/31/个人常用Linux命令.md
index 65b7a7905..81e371f3e 100644
--- a/docs/categories/fragments/2019/12/31/个人常用Linux命令.md
+++ b/docs/categories/category1/2019/12/31/个人常用Linux命令.md
@@ -4,7 +4,7 @@ author: 查尔斯
date: 2019/12/31 21:00
isTop: true
categories:
- - 杂碎逆袭史
+ - 分类1
tags:
- Linux
---
diff --git a/docs/categories/category1/index.md b/docs/categories/category1/index.md
new file mode 100644
index 000000000..0a8698626
--- /dev/null
+++ b/docs/categories/category1/index.md
@@ -0,0 +1,9 @@
+---
+showArticleMetadata: false
+editLink: false
+lastUpdated: false
+showComment: false
+---
+
+# 分类1
+
diff --git a/docs/categories/fragments/2019/12/28/个人SQL优化技巧.md b/docs/categories/fragments/2019/12/28/个人SQL优化技巧.md
deleted file mode 100644
index 60deab5f5..000000000
--- a/docs/categories/fragments/2019/12/28/个人SQL优化技巧.md
+++ /dev/null
@@ -1,138 +0,0 @@
----
-title: 个人 SQL 优化技巧
-author: 查尔斯
-date: 2019/12/28 10:00
-isTop: true
-categories:
- - 杂碎逆袭史
-tags:
- - SQL
- - SQL优化
----
-
-# 个人 SQL 优化技巧
-
-
-
-## 查询优化
-
-### 如果确定结果只有一条,使用 LIMIT 1
-
-我们在根据一个或多个条件查询数据时,如果确定查询结果只有一条,可以在结尾处添加 LIMIT 1 进行限制。
-
-这样既可以让 EXPLAIN 中的 type 达到 const 类型,又可以免去担忧在程序中出现接收是单个对象却返回了一个集合对象的异常问题。
-
-::: code-group
-```sql [正例]
-# email 不是主键,也没有设置唯一约束,根据熵增定律,查询结果是有可能会出现多条的
-SELECT * FROM `sys_user` WHERE `email` = 'charles7c@126.com' LIMIT 1;
-```
-
-```sql [反例]
-# user_id 是主键,主键是非空唯一的,那么不需要添加 LIMIT 进行限制
-SELECT * FROM `sys_user` WHERE `user_id` = 1;
-```
-:::
-
-### 避免隐式类型转换
-
-我们在使用 MySQL 时,或多或少都感受过 MySQL 的隐式类型转换。例如:user_id 是整数类型,但是依然可以使用字符串类型数据来进行判断。MySQL 帮你做完这种隐式类型转换是有代价的,什么代价呢? **索引不再生效了而已** 。
-
-::: code-group
-```sql [正例]
-SELECT * FROM `sys_user` WHERE `user_id` = 10;
-```
-
-```sql [反例]
-SELECT * FROM `sys_user` WHERE `user_id` = '10';
-```
-:::
-
-## 数据库表设计
-
-### 列名带上前缀
-
-部分列名带上前缀或缩写,可以有效减少在连接查询、ORM 映射等场景下刻意起别名或思考区分不同的问题。
-
-::: code-group
-```sql [正例]
-CREATE TABLE `sys_customer` (
- `customer_id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '客户ID',
- `customer_name` varchar(255) NOT NULL COMMENT '客户名称',
- PRIMARY KEY (`customer_id`) USING BTREE,
-) COMMENT = '客户表';
-
-CREATE TABLE `sys_contact_user` (
- `contact_user_id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '联系人ID',
- `contact_user_name` varchar(255) NOT NULL COMMENT '联系人名称',
- `customer_id` bigint(20) UNSIGNED NOT NULL COMMENT '客户ID',
- PRIMARY KEY (`contact_user_id`) USING BTREE,
-) COMMENT = '联系人表';
-
-# 连接查询,你完全不需要用脑去考虑到底是 c.`id` 还是 cu.`customer_id` 的问题,都是 `customer_id`
-SELECT * FROM `sys_customer` c
-LEFT JOIN `sys_contact_user` cu ON c.`customer_id` = cu.`customer_id`
-```
-:::
-
-### 非负数列添加UNSIGNED无符号约束
-
-在大部分的数据存储场景中,我们只会使用正整数,如果能确定该列为非负数,建议添加 `UNSIGNED` 无符号约束。
-
-::: code-group
-```sql [正例]
-# 不添加 UNSIGNED 约束,取值范围
-TINYINT:[-128, 127]
-# 添加 UNSIGNED 约束,取值范围
-TINYINT:[0, 255]
-```
-:::
-
-### 合理采用整数类型
-
-例如:状态类的信息,状态再多能有多少个,采用 `TINYINT` 即可,减少存储空间占用。
-
-下方表数据整理于:[MySQL 5.7官方文档/数据类型/数值数据类型/整数类型](https://dev.mysql.com/doc/refman/5.7/en/integer-types.html)
-
-| 类型 | 存储(字节) | 取值范围 | 取值范围(无符号) |
-| :-------- | :----------- | :------------------------ | :----------------- |
-| TINYINT | 1 | [-128, 127] | [0, 255] |
-| SMALLINT | 2 | [-32768, 32767] | [0, 65535] |
-| MEDIUMINT | 3 | [-8388608, 8388607] | [0, 16777215] |
-| INT | 4 | [-2147483648, 2147483647] | [0, 4294967295] |
-| BIGINT | 8 | [-2^63^, 2^63^-1] | [0, 2^64^-1] |
-
-### 布尔列采用bit类型
-
-例如:是否删除这种只有两种状态的信息,在表设计时建议对该列设置 `bit` 类型(0表示否/假/false,1表示是/真/true),在程序语言中可以采用 boolean 类型对应。
-
-::: code-group
-```sql [SQL]
-`is_deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否已删除(0否 1是)'
-```
-
-```java [Java]
-@Data
-public class User {
- /**
- * 是否已删除(0否 1是)
- */
- private Boolean isDeleted;
-}
-```
-:::
-
-## 数据库设计
-
-### 采用utf8mb4编码
-
-::: tip 如果要存储特殊字符(例如:emoij表情符),使用 utf8mb4 编码。
-MySQL 5.5.3 后增加了一个新的编码: `utf8mb4` ,其中 mb4 是 most bytes 4 的意思,用于兼容四字节的 unicode。
-
-`utf8mb4` 是 utf8 的超集,除了将编码改为 `utf8mb4` 外不需要做其他转换。
-
-设计数据库时如果想要允许用户使用特殊符号,最好使用 `utf8mb4` 编码来存储,使得数据库有更好的兼容性,但是这样设计会导致耗费更多的存储空间。
-:::
-
-
-
diff --git a/docs/categories/fragments/2019/12/29/个人常用Stream使用技巧.md b/docs/categories/fragments/2019/12/29/个人常用Stream使用技巧.md
deleted file mode 100644
index 2191d587d..000000000
--- a/docs/categories/fragments/2019/12/29/个人常用Stream使用技巧.md
+++ /dev/null
@@ -1,42 +0,0 @@
----
-title: 个人常用 Stream 使用技巧
-author: 查尔斯
-date: 2019/12/29 15:00
-isTop: true
-categories:
- - 杂碎逆袭史
-tags:
- - Java
- - Stream
- - Lambda
----
-
-# 个人常用 Stream 使用技巧
-
-
-
-## 映射并以指定分隔符进行拼接
-
-::: details 需求:将角色中的角色名称取出来,并以“,”号分隔的形式将所有角色名称拼接成一个字符串。
-实现方案:通过 `map()` 可以只保留角色名称信息, 通过 `Collectors.joining()` 可以将角色名称以指定分隔符拼接起来。
-:::
-
-```java
-// 1、准备一些实验数据, 代码不会像注释一样骗人, 所以就不单独对角色类中的字段解释了
-List roleList = new ArrayList<>(3);
-roleList.add(new Role(1, "超级管理员"));
-roleList.add(new Role(2, "管理员"));
-roleList.add(new Role(3, "普通用户"));
-
-// 2、通过 map() 可以只保留角色名称, 通过 Collectors.joining() 可以将角色名称以指定分隔符拼接起来
-String result = roleList.stream()
- .map(Role::getName)
- .collect(Collectors.joining(","));
-
-// 3、输出结果
-// 超级管理员,管理员,普通用户
-System.out.println(result);
-```
-
-
-
diff --git a/docs/categories/fragments/2019/12/30/个人常用Hutool工具类.md b/docs/categories/fragments/2019/12/30/个人常用Hutool工具类.md
deleted file mode 100644
index eab832db6..000000000
--- a/docs/categories/fragments/2019/12/30/个人常用Hutool工具类.md
+++ /dev/null
@@ -1,405 +0,0 @@
----
-title: 个人常用 Hutool 工具类
-author: 查尔斯
-date: 2019/12/30 19:00
-isTop: true
-categories:
- - 杂碎逆袭史
-tags:
- - Java
- - Java工具类
- - Hutool
----
-
-# 个人常用 Hutool工 具类
-
-**C:** 技术圈常说一句:“你要会写轮子,也要会用轮子”。工作的时候,为了提升开发效率,节约开发时间,也常常提醒自己不要重复造 “轮子”。
-
-每次开启一个新项目,除了搭建项目必备环境之外,必然要整理一下之前项目沉淀下来的工具类,然后 C V 大法。习惯了一些工具类后,新项目中用不了或是换一下总是感觉缺点什么,再加上每个人都有自己遇到或沉淀的工具类,项目中遇到工具类重复也是很常见的事儿。
-
-话说回来,这些工具类就是开发中必不可少的一种 “轮子”。对于大多数同学,受限于技术,轮子可能勉强写的出来,但是写的是不是够好,够完善,这质量就没法保证了。
-
-谁都是从一开始过来的,但好在有这么一些有志之士,将经年累月写过的轮子反复整理,反复打磨,推出了一个项目,它就是 Hutool。
-
-接下来,笔者就带大家学习一些个人常用的 Hutool 工具类。
-
-
-
-## 判断相等
-
-### 传统用法
-
-判断两个内容相等很常见了吧?
-
-Java 7 的时候,官方还在 java.util 包下给提供了一个 Objects 工具类,源代码如下:
-
-```java
-/**
- * Returns {@code true} if the arguments are equal to each other
- * and {@code false} otherwise.
- * Consequently, if both arguments are {@code null}, {@code true}
- * is returned and if exactly one argument is {@code null}, {@code
- * false} is returned. Otherwise, equality is determined by using
- * the {@link Object#equals equals} method of the first
- * argument.
- *
- * @param a an object
- * @param b an object to be compared with {@code a} for equality
- * @return {@code true} if the arguments are equal to each other
- * and {@code false} otherwise
- * @see Object#equals(Object)
- */
-public static boolean equals(Object a, Object b) {
- return (a == b) || (a != null && a.equals(b));
-}
-```
-
-这的确可以解决 80% 的判断相等问题了,也可以有效避免 NPE 问题。但是笔者比较贪心,所以一直使用的 Hutool 提供的 ObjectUtil 工具类来判断相等。
-
-### ObjectUtil
-
-在 Java 中,判断不同内容是否相等有多种情况,这在《阿里巴巴Java开发手册》中也有强调,笔者用 ObjectUtil 分别示范下不同情况的使用方法。
-
-::: warning 《阿里巴巴Java开发手册》
-【强制】所有整型包装类对象之间值的比较,全部使用 equals 方法比较。 说明:对于 Integer var = ? 在-128 至 127 之间的赋值,Integer 对象是在 IntegerCache.cache 产生, 会复用已有对象,这个区间内的 Integer 值可以直接使用==进行判断,但是这个区间之外的所有数据,都 会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用 equals 方法进行判断。
-:::
-
-```java
-System.out.println(ObjectUtil.equal(null, null)); // true
-System.out.println(ObjectUtil.equal(null, 0)); // false
-System.out.println(ObjectUtil.equal(0, 0)); // true
-System.out.println(ObjectUtil.equal(0, 0L)); // false
-System.out.println(ObjectUtil.equal(0L, 0L)); // true
-System.out.println(ObjectUtil.equal(1L, 1L)); // true
-System.out.println(ObjectUtil.notEqual(1L, 1L)); // false
-```
-
-**ObjectUtil 的 equal() 源代码,如下:**
-
-```java
-/**
- * 比较两个对象是否相等。
- * 相同的条件有两个,满足其一即可:
- *
- * obj1 == null && obj2 == null
- * obj1.equals(obj2)
- * 如果是BigDecimal比较,0 == obj1.compareTo(obj2)
- *
- *
- * @param obj1 对象1
- * @param obj2 对象2
- * @return 是否相等
- * @see Objects#equals(Object, Object)
- */
-public static boolean equal(Object obj1, Object obj2) {
- // 判断是否为 BigDecimal 类型,如果是用 compareTo 比较
- if (obj1 instanceof BigDecimal && obj2 instanceof BigDecimal) {
- return NumberUtil.equals((BigDecimal) obj1, (BigDecimal) obj2);
- }
- // 否则使用 Java 官方提供的 Objects 工具类比较
- return Objects.equals(obj1, obj2);
-}
-
-/**
- * 比较两个对象是否不相等。
- *
- * @param obj1 对象1
- * @param obj2 对象2
- * @return 是否不等
- * @since 3.0.7
- */
-public static boolean notEqual(Object obj1, Object obj2) {
- return false == equal(obj1, obj2);
-}
-```
-
-要是觉得 equal 这个单词不喜欢,你还可以用它的别名方法:(作者们考虑的是有点周到了)
-
-```java
-/**
- * 比较两个对象是否相等,此方法是 {@link #equal(Object, Object)}的别名方法。
- * 相同的条件有两个,满足其一即可:
- *
- * obj1 == null && obj2 == null
- * obj1.equals(obj2)
- * 如果是BigDecimal比较,0 == obj1.compareTo(obj2)
- *
- *
- * @param obj1 对象1
- * @param obj2 对象2
- * @return 是否相等
- * @see #equal(Object, Object)
- * @since 5.4.3
- */
-public static boolean equals(Object obj1, Object obj2) {
- return equal(obj1, obj2);
-}
-```
-
-::: warning 《阿里巴巴Java开发手册》
-【强制】BigDecimal 的等值比较应使用 compareTo()方法,而不是 equals()方法。 说明:equals()方法会比较值和精度(1.0 与 1.00 返回结果为 false),而 compareTo() 则会忽略精度。
-
-说明:equals()方法会比较值和精度(1.0 与 1.00 返回结果为 false),而 compareTo()则会忽略精度。
-:::
-
-诚然,从上方源代码中我们可以清楚地看到 ObjectUtil 比较的时候还区分了 BigDecimal 类型,这也轻松的解决了此强制问题。
-
-**在 ObjectUtil 比较 BigDecimal 类型相等时用到的 NumberUtil 中的 equals() 源代码,如下:**
-
-```java
-/**
- * 比较大小,值相等 返回true
- * 此方法通过调用{@link BigDecimal#compareTo(BigDecimal)}方法来判断是否相等
- * 此方法判断值相等时忽略精度的,即0.00 == 0
- *
- * @param bigNum1 数字1
- * @param bigNum2 数字2
- * @return 是否相等
- */
-public static boolean equals(BigDecimal bigNum1, BigDecimal bigNum2) {
- if (bigNum1 == bigNum2) {
- // 如果用户传入同一对象,省略compareTo以提高性能。
- return true;
- }
- if (bigNum1 == null || bigNum2 == null) {
- return false;
- }
- return 0 == bigNum1.compareTo(bigNum2);
-}
-```
-### NumberUtil
-
-::: warning 《阿里巴巴Java开发手册》
-
-【强制】浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals 来判断。
-
-说明:浮点数采用“尾数+阶码”的编码方式,类似于科学计数法的“有效数字+指数”的表示方式。二进 制无法精确表示大部分的十进制小数。
-
-解决方法:
-
-1. 指定一个误差范围,两个浮点数的差值在此范围之内,则认为是相等的。
-2. 使用 BigDecimal 来定义值,再进行浮点数的运算操作。
-:::
-
-浮点数比较的时候依然可以使用 ObjectUtil,但是也可以直接采用 NumberUtil:
-
-```java
-System.out.println(NumberUtil.equals(0.1, 0.1)); // true
-System.out.println(ObjectUtil.equal(BigDecimal.valueOf(0.1), BigDecimal.valueOf(0.1))); // true
-```
-
-**上方用到的 NumberUtil 的 equals() 源代码,如下:**
-
-```java
-/**
- * 比较大小,值相等 返回true
- * 此方法通过调用{@link Double#doubleToLongBits(double)}方法来判断是否相等
- * 此方法判断值相等时忽略精度的,即0.00 == 0
- *
- * @param num1 数字1
- * @param num2 数字2
- * @return 是否相等
- * @since 5.4.2
- */
-public static boolean equals(double num1, double num2) {
- return Double.doubleToLongBits(num1) == Double.doubleToLongBits(num2);
-}
-
-/**
- * 比较大小,值相等 返回true
- * 此方法通过调用{@link Float#floatToIntBits(float)}方法来判断是否相等
- * 此方法判断值相等时忽略精度的,即0.00 == 0
- *
- * @param num1 数字1
- * @param num2 数字2
- * @return 是否相等
- * @since 5.4.5
- */
-public static boolean equals(float num1, float num2) {
- return Float.floatToIntBits(num1) == Float.floatToIntBits(num2);
-}
-```
-
-## 程序计时
-
-### 传统用法
-
-为了计算程序执行耗时,通常的做法是在程序段前后分别记录一个时间毫秒值变量,然后用结束时间毫秒值减去开始时间毫秒值就可以了。
-
-```java
-long startTime = System.currentTimeMillis();
-
-// 要计时的程序片段
-// ...
-
-long endTime = System.currentTimeMillis();
-System.out.println("总耗时:" + (endTime - startTime) + "ms");
-```
-
-### TimeInterval
-
-实际上传统方法也没什么问题,但是当我们需要持续计时的时候,它就不是那么美丽了。Hutool 提供了 `TimeInterval` 来帮助我们实现灵活计时的需求。
-
-```java
-TimeInterval timer = DateUtil.timer();
-
-// 要计时的程序片段1
-// ...
-
-System.out.println("总耗时:" + timer.interval() + "ms");
-
-// 要计时的程序片段2
-// ...
-
-System.out.println("总耗时:" + timer.interval() + "ms");
-// ...
-```
-
-## UUID生成器
-
-### 传统用法
-
-利用 JDK 内置的 `java.util.UUID`,可以生成 32 位的 UUID,但一般我们使用的时候还要再对其“加工”一下,转换成字符串并去除连字符。
-
-```java
-UUID uuid = UUID.randomUUID();
-// 转换为字符串
-String uuidStr = uuid.toString();
-System.out.println(uuidStr); // 7f4c0b42-d066-4baa-bc24-4d74a69ea78e
-
-// 去除连字符
-String replaceUuidStr = uuidStr.replace("-", "");
-System.out.println(replaceUuidStr); // 7f4c0b42d0664baabc244d74a69ea78e
-```
-
-### IdUtil
-
-::: tip Hutool文档
-Hutool重写了java.util.UUID的逻辑,对应类为cn.hutool.core.lang.UUID,使生成不带-的UUID字符串不再需要做字符替换,性能提升一倍左右。
-:::
-
-```java
-String uuidStr = IdUtil.fastUUID();
-System.out.println(uuidStr); // f69c5c5c-73fd-4286-b338-133927789d71
-
-String simpleUuidStr = IdUtil.fastSimpleUUID();
-System.out.println(simpleUuidStr); // 6905bc8239c1489a9f6fb18cee1d6884
-// ...
-```
-
-## 获取Spring
-
-### SpringUtil
-
-::: tip Hutool文档
-使用Spring Boot时,通过依赖注入获取bean是非常方便的,但是在工具化的应用场景下,想要动态获取bean就变得非常困难,于是Hutool封装了Spring中Bean获取的工具类——SpringUtil。
-:::
-
-要使用 SpringUtil,首先要完成两个操作。
-
-1. 使用 `ComponentScan` 额外指定组件扫描包(记得别忘了也把自己的扫描包也加上)
-2. 使用 `@Import` 导入 SpringUtil 类
-
-```java
-@SpringBootApplication
-@Import(cn.hutool.extra.spring.SpringUtil.class)
-@ComponentScan(basePackages = {"com.xxx", "cn.hutool.extra.spring"})
-public class WebApiApplication {
- public static void main(String[] args) {
- SpringApplication.run(args);
- }
-}
-```
-
-常见用法:
-
-```java
-// 获取 Spring 容器中的指定对象
-UserMapper userMapper = SpringUtil.getBean(UserMapper.class);
-// 获取配置文件中的指定属性值
-String activeProfile = SpringUtil.getProperty("spring.profiles.active");
-// ...
-```
-
-## 集合操作
-
-集合的出现极大的解决了复杂数据处理的需要,但仅凭 JDK 内置的集合方法,还是略显“苦涩”。
-
-### MapUtil
-
-顾名思义,MapUtil 生来是为了更方便的操作 Map 集合的。
-
-::: tip 笔者说
-单是能快速帮你创建指定初始容量大小的 Map 集合这一点就爱了。
-:::
-
-::: warning 《阿里巴巴Java开发手册》
-【推荐】 集合初始化时, 指定集合初始值大小。
-
-说明: HashMap 使用 HashMap(int initialCapacity) 初始化,如果暂时无法确定集合大小, 那么指定默认值( 16) 即可。
-
-正例: initialCapacity = (需要存储的元素个数 / 负载因子) + 1。 注意负载因子(即 loader factor) 默认为 0.75,如果暂时无法确定初始值大小,请设置为 16(即默认值) 。
-
-反例: HashMap 需要放置 1024 个元素,由于没有设置容量初始大小,随着元素增加而被迫不断扩容,
-resize()方法总共会调用 8 次,反复重建哈希表和数据迁移。当放置的集合元素个数达千万级时会影响程序
-性能。
-:::
-
-```java
-// 判断是否为空、不为空
-Map map1 = null;
-Map map2 = new HashMap<>();
-boolean flag1 = MapUtil.isEmpty(map1); // true
-boolean flag2 = MapUtil.isNotEmpty(map1); // false
-boolean flag3 = MapUtil.isEmpty(map2); // true
-boolean flag4 = MapUtil.isNotEmpty(map2); // false
-System.out.println(flag1);
-System.out.println(flag2);
-System.out.println(flag3);
-System.out.println(flag4);
-
-// 快速创建单键值对的 HashMap
-HashMap map3 = MapUtil.of("CN", "中国");
-// 快速创建单键值对的 LinkedHashMap
-HashMap map4 = MapUtil.of("CN", "中国", true);
-
-// 快速创建指定初始容量大小的 HashMap
-HashMap map5 = MapUtil.newHashMap(2);
-
-// 快速创建LinkedHashMap
-HashMap map6 = MapUtil.newHashMap(true);
-// 快速创建指定初始容量大小的 LinkedHashMap
-HashMap map7 = MapUtil.newHashMap(2, true);
-// ...
-```
-
-**MapUtil 的 newHashMap() 源代码,如下:**
-
-```java
-/**
- * 新建一个HashMap
- *
- * @param Key类型
- * @param Value类型
- * @param size 初始大小,由于默认负载因子0.75,传入的size会实际初始大小为size / 0.75 + 1
- * @return HashMap对象
- */
-public static HashMap newHashMap(int size) {
- return newHashMap(size, false);
-}
-
-/**
- * 新建一个HashMap
- *
- * @param Key类型
- * @param Value类型
- * @param size 初始大小,由于默认负载因子0.75,传入的size会实际初始大小为size / 0.75 + 1
- * @param isOrder Map的Key是否有序,有序返回 {@link LinkedHashMap},否则返回 {@link HashMap}
- * @return HashMap对象
- * @since 3.0.4
- */
-public static HashMap newHashMap(int size, boolean isOrder) {
- int initialCapacity = (int) (size / DEFAULT_LOAD_FACTOR) + 1;
- return isOrder ? new LinkedHashMap<>(initialCapacity) : new HashMap<>(initialCapacity);
-}
-```
\ No newline at end of file
diff --git a/docs/categories/fragments/2021/03/12/精密计算工具类-BigDecimal.md b/docs/categories/fragments/2021/03/12/精密计算工具类-BigDecimal.md
deleted file mode 100644
index 32bfd7e7f..000000000
--- a/docs/categories/fragments/2021/03/12/精密计算工具类-BigDecimal.md
+++ /dev/null
@@ -1,311 +0,0 @@
----
-title: 精密计算工具类-BigDecimal
-author: 查尔斯
-date: 2021/03/12 18:07
-categories:
- - 杂碎逆袭史
-tags:
- - Java
- - Java工具类
----
-
-# 精密计算工具类-BigDecimal
-
-## 前言
-
-**C:** 下方有两个 double 浮点数据在进行减法计算,你先猜测一下输出结果是多少?
-
-```java
-System.out.println(16.27 - 3.12);
-```
-
-当然了,可能有小部分同学会怀疑笔者在玩弄你们的智商,其实不是,笔者是在玩弄你们的感情。
-
-
-
-圆规正转,这结果究竟是多少?不是 **13.15** 吗?还真不是,结果是 **13.149999999999999** 。
-
-
-
-**你:** 学了这么多年的数学,连两位的加减法都不行了吗?果然不是这块料儿。
-
-::: tip 笔者说
-其实,你没算错,错的是计算机。16.27 和 3.12 这两个数字都是十进制,而我们都知道,计算机中是使用二进制来进行存储的,所以这意味着我们要使用 16.27 和 3.12 的时候,它会先做一次十进制到二进制的转换。由于计算机中存储浮点数时的特殊规则,部分浮点数会在这个过程中损失精度,结果自然就会出错了。而且它也不只是 Java 语言的专属问题,大多数语言都有这问题。[1]
-:::
-
-
-
-所以啊,一些需要精确计算的时候,你还敢用 double、float 类型吗?
-
-不过也别着急,虽然不能用它们,但 Java 中也给你想好了招儿,Java 中提供了一个类型 BigDecimal ,可以帮助我们解决此类精确计算问题,一起来瞧瞧。
-
-
-
-## 简介
-
-::: tip BigDecimal 简介
-BigDecimal 是 Java 在 java.math 包中提供的一个计算类,是用来对超过 16 位有效位的数进行精确运算的。
-
-在 《Effective Java》 一书中,就有提到 float 和 double 只能用来做科学计算或者是工程计算,在商业计算中要用 java.math.BigDecimal。[2]
-:::
-
-## 使用
-
-### 构造方法
-
-BigDecimal 作为一个类,要先利用构造方法创建对象之后才能使用。
-
-它的构造方法有很多,但是我们需要了解的只有两个。
-
-```java
-// 利用 double 浮点数值作为构造参数
-BigDecimal(double val)
-
-// 利用 字符串 作为构造参数[记得使用它]
-BigDecimal(String val)
-```
-
-乍一看,是不是觉得第一种构造更方便?正好计算 double 类型数据,直接传入构造就可以用了,多好?
-
-别着急,如果直接使用第一种 double 作为构造参数的方法,在一开始它又会出现前言提到的转换后精度缺失的问题,而且在第二种构造的源码注释中,有这么一句注意:它通常是将 float 或 double 转换为 BigDecimal 的首选方式。
-
-所以说,未来我们要使用 BigDecimal 做精确计算的时候,还是得用第二种传入字符串的构造方法来创建 BigDecimal 对象。
-
-
-
-用法就是下方这样的。
-
-```java
-// 通过字符串来创建 BigDecimal 对象
-BigDecimal num1 = new BigDecimal("16.27");
-BigDecimal num2 = new BigDecimal("3.12");
-```
-
-### 减法运算
-
-创建好 BigDecimal 对象后,也别急着上来就 +、-、*、/,它可不再是基本数据类型,想要计算得用对应的方法。
-
-```java
-// 通过字符串来创建 BigDecimal 对象
-BigDecimal num1 = new BigDecimal("16.27");
-BigDecimal num2 = new BigDecimal("3.12");
-
-// 减法运算
-// subtract(BigDecimal subtrahend):BigDecimal
-BigDecimal result = num1.subtract(num2);
-System.out.println(result); // 13.15
-```
-
-你看,BigDecimal 果然没辜负我们的期望,这回就没有再出现之前的浮点数问题。
-
-如果计算完后,我们还要使用 double 来存储数据,BigDecimal 中还有对应的转换方法。
-
-```java
-// 转换为 double 类型
-// doubleValue():double
-double doubleValue = result.doubleValue();
-```
-
-### 加法运算
-
-```java
-// 通过字符串来创建 BigDecimal 对象
-BigDecimal num1 = new BigDecimal("16.27");
-BigDecimal num2 = new BigDecimal("3.12");
-
-// 加法运算
-// add(BigDecimal augend):BigDecimal
-BigDecimal result = num1.add(num2);
-System.out.println(result); // 19.39
-```
-
-### 乘法运算
-
-```java
-// 通过字符串来创建 BigDecimal 对象
-BigDecimal num1 = new BigDecimal("16.27");
-BigDecimal num2 = new BigDecimal("3.12");
-
-// 乘法运算
-// multiply(BigDecimal multiplicand):BigDecimal
-BigDecimal result = num1.multiply(num2);
-System.out.println(result); // 50.7624
-```
-
-### 除法运算
-
-除法运算稍微有些复杂,要考虑保留的小数位及舍入模式的问题。
-
-```java
-// 通过字符串来创建 BigDecimal 对象
-BigDecimal num1 = new BigDecimal("16.27");
-BigDecimal num2 = new BigDecimal("3.12");
-
-// 除法运算
-// 方法参数1:除数
-// 方法参数2:小数点后保留几位
-// 方法参数3:舍入模式,例:四舍五入等
-// divide(BigDecimal divisor, int scale, int roundingMode):BigDecimal
-
-// JDK 1.5 出现了枚举类型,定义RoundingMode枚举类优化了原来在BigDecimal中的舍入模式常量
-// divide(BigDecimal divisor, int scale, RoundingMode roundingMode):BigDecimal
-// 例如:四舍五入保留两位小数
-BigDecimal result = num1.divide(num2, 2, RoundingMode.HALF_UP);
-System.out.println(result); // 5.21
-```
-
-JDK 1.5 之前,BigDecimal 类中提供的舍入模式常量们。
-
-```java
-public class BigDecimal extends Number implements Comparable {
- // ...略...
-
- // Rounding Modes
- // 向外取整模式:向远离零的方向舍入
- public final static int ROUND_UP = 0;
- // 向内取整模式:向接近零的方向舍入
- public final static int ROUND_DOWN = 1;
- // 向上取整模式:向正无穷大的方向舍入
- public final static int ROUND_CEILING = 2;
- // 向下取整模式:向负无穷大的方向舍入
- public final static int ROUND_FLOOR = 3;
- // 四舍五入
- public final static int ROUND_HALF_UP = 4;
- // 五舍六入
- public final static int ROUND_HALF_DOWN = 5;
- // 四舍六入五取偶(银行家舍入法)
- public final static int ROUND_HALF_EVEN = 6;
- // 不需要舍入
- public final static int ROUND_UNNECESSARY = 7;
-
- // ...略...
-}
-```
-
-JDK 1.5 开始,定义了一个 RoundingMode 专门作为舍入模式的枚举类。
-
-```java
-public enum RoundingMode {
-
- UP(BigDecimal.ROUND_UP),
-
- DOWN(BigDecimal.ROUND_DOWN),
-
- CEILING(BigDecimal.ROUND_CEILING),
-
- FLOOR(BigDecimal.ROUND_FLOOR),
-
- HALF_UP(BigDecimal.ROUND_HALF_UP),
-
- HALF_DOWN(BigDecimal.ROUND_HALF_DOWN),
-
- HALF_EVEN(BigDecimal.ROUND_HALF_EVEN),
-
- UNNECESSARY(BigDecimal.ROUND_UNNECESSARY);
-
- // ...略...
-}
-```
-
-::: tip 笔者说
-关于舍入模式,非数学专业出身的同学看着中文 API 都难以理解,这些枚举值在实际使用的时候再翻阅也来得及(银行、帐户、计费等领域)。[3]
-
-当然,最常用的就是 HALF_UP,也就是四舍五入。
-:::
-
-
-
-## 封装工具类
-
-我们也看到了,如果直接使用 BigDecimal 类,光是构造方法要传入字符串,就够吃一壶的了,尤其计算完后大概率还要转回 double,程序员怎么可能接受这种代码?赶紧封装,上工具类!
-
-```java
-/**
- * 浮点数精确运算工具类
- */
-public class BigDecimalUtil {
-
- // 私有化构造
- private BigDecimalUtil() {}
-
- /**
- * 加法运算
- * @param v1 被加数
- * @param v2 加数
- * @return 和
- */
- public static double add(double v1, double v2) {
- BigDecimal b1 = new BigDecimal(Double.toString(v1));
- BigDecimal b2 = new BigDecimal(Double.toString(v2));
- return b1.add(b2).doubleValue();
- }
-
- /**
- * 减法运算
- * @param v1 被减数
- * @param v2 减数
- * @return 差
- */
- public static double subtract(double v1, double v2) {
- BigDecimal b1 = new BigDecimal(Double.toString(v1));
- BigDecimal b2 = new BigDecimal(Double.toString(v2));
- return b1.subtract(b2).doubleValue();
- }
-
- /**
- * 乘法运算
- * @param v1 被乘数
- * @param v2 乘数
- * @return 积
- */
- public static double multiply(double v1, double v2) {
- BigDecimal b1 = new BigDecimal(Double.toString(v1));
- BigDecimal b2 = new BigDecimal(Double.toString(v2));
- return b1.multiply(b2).doubleValue();
- }
-
- /**
- * 除法运算(四舍五入)
- * @param v1 被除数
- * @param v2 除数
- * @param scale 小数点后保留几位
- * @return 商
- */
- public static double divide(double v1, double v2, int scale) {
- if (scale < 0) {
- throw new IllegalArgumentException("保留的小数位不能少于0个!");
- }
- BigDecimal b1 = new BigDecimal(Double.toString(v1));
- BigDecimal b2 = new BigDecimal(Double.toString(v2));
- return b1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue();
- }
-
- /**
- * 四舍五入处理
- * @param v 需要四舍五入的数字
- * @param scale 小数点后保留几位
- * @return 四舍五入后的结果
- */
- public static double round(double v, int scale) {
- if (scale < 0) {
- throw new IllegalArgumentException("保留的小数位不能少于0");
- }
- BigDecimal b1 = new BigDecimal(Double.toString(v));
- BigDecimal b2 = new BigDecimal("1");
- return b1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue();
- }
-
-}
-```
-
-## 参考资料
-
-[1]为什么在处理金额时不能用Double和Float,计算机是如何存储浮点数的:https://blog.csdn.net/qq_41872247/article/details/107861608
-
-[2]java.math.BigDecimal类的用法:https://www.iteye.com/blog/jeelee-652003
-
-[3]BigDecimal中的取整模式:https://blog.csdn.net/u010523770/article/details/53068809
-
-## 后记
-
-**C:** 好了,BigDecimal 类的介绍到这儿就结束了,认识了 BigDecimal 之后,可千万别再用 double 、float 来进行精确计算了。
diff --git a/docs/categories/fragments/2021/05/29/设计模式之单例模式.md b/docs/categories/fragments/2021/05/29/设计模式之单例模式.md
deleted file mode 100644
index 918bff6c2..000000000
--- a/docs/categories/fragments/2021/05/29/设计模式之单例模式.md
+++ /dev/null
@@ -1,324 +0,0 @@
----
-title: 设计模式之单例模式
-author: 查尔斯
-date: 2021/05/29 13:14
-categories:
- - 杂碎逆袭史
-tags:
- - 设计模式
----
-
-# 设计模式之单例模式
-
-**C:** 在现代程序开发过程中,无论是新手还是老手,都要熟练掌握面向对象的编程思想及编程方式。
-
-而在面向对象的编程方式中,我们作为程序的掌控者,在操作一些业务单元时,都要先创建好对应的对象,然后通过操作对象来实现业务处理。
-
-
-
-正因如此,对象的创建自然是每个程序员都手到擒来的事情了。
-
-
-
-大多数情况下,我们创建对象都是采用如下方式实现的:
-
-```java
-类名 对象名 = new 类名();
-```
-
-这种方式是最基本的对象创建方式,每次执行 new 关键字,都会为该类产生一个新的对象。
-
-但在一些特殊的场景下,对于一个特定的类,我们可能希望它在全局只产生一个对象,即每次我们获取它的对象时,获取到的永远都是同一个!
-
-这个需求很常见,在众多设计模式中也恰好有一种可以满足我们这项需求的模式,它就是单例模式。
-
-## 简介
-
-单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
-
-这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
-
-作为创建型设计模式一员的单例模式,无论是在日常工作使用中,还是在基础面试过程中,亦或是面试一些框架原理时,都或多或少会提及它。
-
-但是,大多数初级程序员对单例模式,仅仅是了解到两种实现方式,即懒汉式、饿汉式的程度,而实际上呢,在面对多线程的环境下,单例模式也诞生了很多性能又佳且线程安全的实现方式,本篇笔者就带你一起研究。
-
-## 饿汉式
-
-```java
-public class Singleton {
-
- /**
- * 私有化构造方法
- */
- private Singleton() {}
-
- /**
- * 静态唯一实例
- */
- private static Singleton instance = new Singleton();
-
- /**
- * 公共的获取该类唯一实例的方法
- *
- * @return 该类唯一实例
- */
- public static Singleton getInstance() {
- return instance;
- }
-
-}
-```
-
-首先,通过私有化构造方法,外界就不能再直接通过 new 的方式来创建该类的对象了。
-
-其次,为了保证在全局只有一个该类的实例,那么就要考虑将该类的唯一实例设置为静态的,因为静态信息是随着类的加载而初始化的,所以它只会执行一次且会一直随着该类的存在而存在。
-
-最后,为了让外界能够获取到该类的唯一实例,我们就必须要准备一个公共的静态方法来提供这一唯一实例。
-
-## 懒汉式
-
-饿汉式之所以称之为饿汉式,就是因为饿汉式不管你是不是立刻需要该类的唯一实例,只要它的类加载,这个唯一实例就创建好了,像一个饿了很久的大汉一样迫不及待。
-
-但是,当我们暂时不需要该类的唯一实例时,饿汉式所提供的唯一实例依然会出现在内存中,占据着我们宝贵的内存空间。
-
-所以,随之而来的就是,我们希望这个类在我们需要它的唯一实例时再去给我们提供好,懒汉式也就随之出现了。
-
-```java
-public class Singleton {
-
- /**
- * 私有化构造方法
- */
- private Singleton() {}
-
- /**
- * 静态唯一实例
- */
- private static Singleton instance;
-
- /**
- * 公共的获取该类唯一实例的方法
- *
- * @return 该类唯一实例
- */
- public static Singleton getInstance() {
- if (null == instance) {
- instance = new Singleton();
- }
- return instance;
- }
-
-}
-```
-
-通过在 getInstance 方法中判断没有唯一实例后,再进行初始化,就可以保证这个唯一实例会在我们需要它的时候才会进行创建,我们称之为懒加载。
-
-## 线程安全的懒汉式
-
-但是,因为我们把这个创建实例的过程放在了 getInstance 方法中,如果是单线程来操作该方法,那没有任何意外。
-
-可惜的是,很多时候,我们的系统环境都是多线程的,当多线程来操作该方法时,因为线程抢占式执行,可能会出现:线程 1 在执行该方法,通过 if null 判断之后,被线程 2 抢占了 CPU 资源,这时候线程 2 在执行该方法的时候,因为该类唯一的实例还没创建,所以线程 2 也可以通过 if null 判断 ...,于是后面就会创建多个该类的实例了。
-
-为了解决多线程的安全问题,首先应该想到的就是给这个方法添加 synchronized 关键字,这样就可以保证同一时刻只允许一个线程来操作该方法了。
-
-```java
-public class Singleton {
-
- /**
- * 私有化构造方法
- */
- private Singleton() {}
-
- /**
- * 静态唯一实例
- */
- private static Singleton instance;
-
- /**
- * 公共的获取该类唯一实例的方法
- *
- * @return 该类唯一实例
- */
- public synchronized static Singleton getInstance() {
- if (null == instance) {
- instance = new Singleton();
- }
- return instance;
- }
-
-}
-```
-
-## 双重校验锁(双检索)
-
-但是在方法层面添加锁之后,意味着后续哪怕已经创建好了唯一实例,每次还是要被锁 “严防死守”,效率太低,那怎么办才好呢?
-
-```java
-public class Singleton {
-
- /**
- * 私有化构造方法
- */
- private Singleton() {}
-
- /**
- * 静态唯一实例,添加volatile关键字防止指令重排
- */
- private static volatile Singleton instance;
-
- /**
- * 公共的获取该类唯一实例的方法
- *
- * @return 该类唯一实例
- */
- public static Singleton getInstance() {
- // 提升效率的实例检测
- if (null == instance) {
- // 同步锁,解决线程安全问题
- synchronized (Singleton.class) {
- // 再次实例检测
- if (null == instance) {
- instance = new Singleton();
- }
- }
- }
- return instance;
- }
-
-}
-```
-
-在这种方式里,getInstance 方法不再添加 synchronized 关键字,而是通过在方法体中添加同步代码块的方式来解决线程安全问题。
-
-为什么要先做一次 if null 判断呢?上一种方式中也提到了,懒汉式实现中,线程不安全的地方是因为最开始还没有创建好唯一实例的时候,多个线程可能都通过了 if null 判断,然后导致多个实例被创建。而如果在创建了唯一实例之后,是不是多线程环境其实就没有什么影响了,因为多个线程过来都是无法通过 if null 判断,直接获取该类的唯一实例就可以了,所以它可以有效地提升后续多线程操作该方法的效率。
-
-至于后面的同步代码块,笔者觉得不需要解释了吧?它就是对上一种方式的锁范围进行了缩小而已,缩小之后,之前该如何,现在自然还如何。
-
-当然,笔者也明白,还是会有同学疑惑为什么同步锁内还要再判断,为什么不用对象锁?一个个再解答一下。
-
-同步锁内还要进行判断,是因为 getInstance 方法这回没加锁,如果是最开始没创建好唯一实例的时候,多线程来操作该方法,可能都会通过 if null 判断,如果同步锁内不加判断,那只不过是让创建多个实例的过程变成排队方式而已,最终它还是会出现多个实例。所以必须再加一次判断,来让即使通过了 if null 判断的线程也不能 “放肆”。正因为如此,这种方式被称为双重校验。
-
-还有,不用对象锁是因为它也用不了,静态方法内只能用静态信息啊。
-
-当然,笔者也差点忘记提的是,我们在这个静态唯一实例前添加了一个 volatile 关键字,这也是你们容易忽视掉的。
-
-为什么要添加这个关键字呢?因为 Java 中通过 new 来创建对象过程中有一些小问题存在。想要看看 new 对象究竟是如何执行的,可以通过 **javap -c 字节码文件** 指令来查看一下它所执行的 JVM 指令。
-
-笔者这里就不贴 JVM 指令代码了,简单说一下创建对象的流程。
-
-1.分配对象的内存空间
-
-2.调用构造器方法,执行对象初始化
-
-3.将对象内存地址赋值给引用变量
-
-在这些指令实际执行的过程中,指令顺序是有可能经过 JVM 和 CPU 优化而重排顺序的。以上代码 2,3 可能发生重新排序,也就是说,最终执行的顺序可能是 1,3,2,即先将分配的内存地址赋值给引用变量再初始化对象。
-
-单线程情况下,自然没有问题,但如果是多线程环境的话,当线程 1 执行完了 1,3 之后,唯一实例虽然还没有完成初始化,但是此时它也不再指向 null 了,如果这时候线程 2 抢占到了 CPU 资源,执行外层的 if null 判断后发现实例已经不为 null 了,直接返回,这时候返回的是一个未初始化的实例,如果线程 2 恰好又立即使用上了这个未初始化的实例,就会出现一系列的问题。
-
-所以,我们要禁止在 new 这个唯一实例时发生指令重排序,那就需要将它用 volatile 关键字来进行修饰,volatile 关键字可以阻止变量访问前后的指令重排,保证指令执行顺序。这样它就不会发生这个问题了,也就保证了多线程环境内的系统安全。
-
-## 静态内部类(登记式)
-
-双重校验锁形式设计的还真的挺不错的。不过,笔者相信还是会有同学不太甘心,难道就没有又能拥有懒汉式的懒加载特点,又拥有饿汉式无需关注线程安全的方式吗?
-
-你再看看下面的这种方式。
-
-```java
-public class Singleton {
-
- /**
- * 私有化构造方法
- */
- private Singleton() {}
-
- /**
- * 静态内部类
- */
- private static class SingletonHolder {
- private static final Singleton INSTANCE = new Singleton();
- }
-
- /**
- * 公共的获取该类唯一实例的方法
- *
- * @return 该类唯一实例
- */
- public static Singleton getInstance() {
- return SingletonHolder.instance;
- }
-
-}
-```
-
-在这种方式里,我们定义了一个静态内部类,在静态内部类中,定义了一个静态常量,它就是当前类的唯一实例。然后在 getInstance 方法中,我们只需要从静态内部类中将这个唯一实例取出来就可以了。
-
-因为这个唯一实例是定义在静态内部类中的,静态内部类是在使用时才会加载的,而不是在 Singleton 类加载的时候加载的,所以它拥有了懒汉式的懒加载特点。
-
-又因为它是定义在一个静态内部类中的静态常量,所以它也不存在线程安全问题,真可谓是一举两得。
-
-## 枚举
-
-除了双重校验锁方式之外,还有优雅的静态内部类方式,看起来似乎已经能够满足我们使用了。没错,在大多数情况下,它们已经没有问题了。但还不算完,上述的这些方式,它们都还存在着一种致命的问题,这个问题会破坏我们设计的单例实现。
-
-不知道你还认不认识 Java 的一个高级特性:反射呢?这个让我们又爱又恨的特性,在这个时候只能给我们带来无穷的碎碎念了。
-
-通过暴力反射,我们对这个单例类所设置的构造方法私有化,就失去了它的价值。
-
-```java
-// 获取字节码对象
-Class singletonClass = Singleton.class;
-// 获取无参构造方法
-Constructor constructor = singletonClass.getDeclaredConstructor();
-// 暴力反射
-constructor.setAccessible(true);
-// 创建两个对象
-Singleton singleton1 = constructor.newInstance();
-Singleton singleton2 = constructor.newInstance();
-// 比较结果,并非同一个对象
-System.out.println(singleton1 == singleton2); // false
-```
-
-不要急,我们再来认识最后一种比较优雅的单例实现:枚举。通过定义一个枚举类型就可以实现单例模式,并且还可以解决掉反射的问题。
-
-我们都知道枚举类型实际上就是一个继承自 Enum 类型的 final 类,枚举类型的构造默认也是私有的,而在枚举类型中定义的枚举值们则实际上是该类的一个个静态实例对象而已。所以,我们只定义一个枚举值,这不就是该类的单例了吗?
-
-再加上枚举是 Java 团队制定的一种语法,在运行时,JVM 会阻止反射方式获取枚举类的私有构造方法。这样我们的单例模式就不会被破坏掉了。
-
-```java
-public enum Singleton {
-
- /**
- * 静态唯一实例
- */
- INSTANCE
-
-}
-```
-
-如果你不相信,可以用反射去试试,你将会遇见下方的结果。
-
-```java
-Exception in thread "main" java.lang.NoSuchMethodException: com.example.pattern05.SingletonPattern.demo6.Singleton.()
- at java.lang.Class.getConstructor0(Class.java:3082)
- at java.lang.Class.getDeclaredConstructor(Class.java:2178)
- at com.example.pattern05.SingletonPattern.demo6.Test.main(Test.java:16)
-```
-
-另外,其实枚举式还有一个好处,就是如果涉及到反序列化创建单例对象时,它可以保证反序列化的返回结果是同一对象。对于其他方式实现的单例模式,如果既想要做到可序列化,又想要反序列化为同一对象,则必须实现 readResolve 方法。
-
-## 后记
-
-**C:** 其实,还有一些像 CAS 等方式实现的单例模式,有兴趣的同学们可以自行去了解一下。而上述的这些方式,是我们平时用到最多的单例模式实现方式。
-
-在实际使用的时候,需要根据实际的需求来灵活进行选择,当你确定要在全局持续使用的时候,那么饿汉式、枚举式都比较适合,而如果明确需要懒加载的时候,那么双重校验锁、静态内部类的方式就比较适合。而如果你想防止反射破坏的话,那就可以选择枚举式。如果你需要涉及反序列化创建对象,那也可以选择枚举式。但是,如果你需要对单例类进行继承等操作,那就不能选择它了。
-
-总之,能够解决需求的方式就是好方式。
-
-## 参考资料
-
-[1]单例模式:https://www.runoob.com/design-pattern/singleton-pattern.html
-
-[2]为什么双重检查锁模式需要 volatile ?https://www.cnblogs.com/goodAndyxublog/p/11356402.html
-
-[3]如何写出更优雅的单例模式?https://mp.weixin.qq.com/s/AdJI5a4w515SPPI_4gVImA
diff --git a/docs/categories/fragments/2022/02/16/个人常用SQL函数.md b/docs/categories/fragments/2022/02/16/个人常用SQL函数.md
deleted file mode 100644
index efc101735..000000000
--- a/docs/categories/fragments/2022/02/16/个人常用SQL函数.md
+++ /dev/null
@@ -1,93 +0,0 @@
----
-title: 个人常用 SQL 函数
-author: 查尔斯
-date: 2022/02/16 15:43
-isTop: true
-categories:
- - 杂碎逆袭史
-tags:
- - SQL
- - SQL函数
----
-
-# 个人常用 SQL 函数
-
-## 时间函数
-
-### 获取当前时间(MySQL)
-
-```sql
-# 输出格式为:yyyy-MM-dd HH:mm:ss
-NOW();
-```
-
-### 获取当前时间秒(MySQL)
-
-```sql
-# 从 1970年1月1日 开始到现在的秒数
-UNIX_TIMESTAMP();
-```
-
-### 计算两个时间之间的间隔(MySQL)
-
-```sql
-# unit 可选为FRAC_SECOND 毫秒、SECOND 秒、MINUTE 分钟、HOUR 小时、DAY 天、WEEK 星期、MONTH 月、QUARTER 季度、YEAR 年
-TIMESTAMPDIFF(unit, datetime_expr1, datetime_expr2)
-```
-
-## 字符串函数
-
-### 拼接字符串(MySQL)
-
-```sql
-# 将多个字符串拼接在一起
-CONCAT(str1, str2, ...)
-```
-
-::: tip 笔者说
-这个函数看起来平平无奇,但实际用起来,可不只是真香。你可能会在 MyBatis 中解决 SQL 注入的时候用到它,还可能在一些 “奇怪” 的场景用到它。
-:::
-
-#### 清空数据库中的所有表数据
-
-清空单表数据很简单。
-
-```sql
-TRUNCATE TABLE 表名;
-```
-
-但是,如果现在有 100 + 张表?你当然不会一个一个的去 `TRUNCATE`,但 MySQL 又没有提供该功能。那你可以用用下面的方法。
-
-1. 查询该数据库下的所有表,利用 `CONCAT()` 函数将 `TRUNCATE` 语句拼接起来
-
- ```shell
- SELECT
- CONCAT('TRUNCATE TABLE ', TABLE_NAME, ';')
- FROM
- information_schema.TABLES
- WHERE TABLE_SCHEMA = '数据库名';
- ```
-
-2. 将执行结果复制,直接执行即可
-
-#### 删除数据库中的所有表
-
-删除单表很简单。
-
-```sql
-DROP TABLE 表名;
-```
-
-但是,如果现在有 100 + 张表?你当然不会一个一个的去 `DROP`,但 MySQL 又没有提供该功能。那你可以用用下面的方法。
-
-1. 查询该数据库下的所有表,利用 `CONCAT()` 函数将 `DROP` 语句拼接起来
-
- ```shell
- SELECT
- CONCAT('DROP TABLE IF EXISTS ', TABLE_NAME, ';')
- FROM
- information_schema.TABLES
- WHERE TABLE_SCHEMA = '数据库名';
- ```
-
-2. 将执行结果复制,直接执行即可
diff --git a/docs/categories/fragments/2022/03/25/合并两个Git仓库的历史提交记录.md b/docs/categories/fragments/2022/03/25/合并两个Git仓库的历史提交记录.md
deleted file mode 100644
index ce2aab758..000000000
--- a/docs/categories/fragments/2022/03/25/合并两个Git仓库的历史提交记录.md
+++ /dev/null
@@ -1,104 +0,0 @@
----
-title: 合并两个Git仓库的历史提交记录
-author: 查尔斯
-date: 2022/03/25 21:30
-categories:
- - 杂碎逆袭史
-tags:
- - Git
----
-
-# 合并两个Git仓库的历史提交记录
-
-## 前言
-
-**C:** 最近在下班的时间一直在维护一个基于 EL-Admin 这个开源后台管理系统的衍生开源项目。EL-Admin 这个项目是采用前后端分离架构开发的,所以在开源平台上是分为了两个项目库,一个前端的,一个后端的。
-
-这本无可厚非,分成两个项目库,在开发时还是挺友好的,公司内部基本也是这个模式,但对于一个开源项目来说,分散为两个库还是有一些不利的方面。
-
-1. 在管理 issue 上不太方便,项目作者要兼顾查看两个项目,而且有些小伙伴在提出 issue 时并不会管你这是前端库还是后端库,提就完事了。在这方面,EL-Admin 项目的作者应该也发现了这个问题,所以直接干脆的关闭了前端库的 issue 功能,集中在后端库一起管理。
-2. 在 star 方面会造成分流,前段时间看了看微博,在不知什么时候,竟然加入了一个明星超话,意外就看到置顶帖里标注了一点禁止创建其他小号超话,现在想想这不是一个意思吗?
-3. ...
-
-本来笔者最开始也是按原项目形式创建了两个 Git 仓库,但最近更换设备开发时单独要拉两次仓库,觉得很麻烦,思索了下突然意识到上述问题,干脆趁着这热乎劲儿,以后端仓库为主,把两个仓库合并一下。
-
-## 解决方案
-
-### 将前端项目提交到后端库
-
-这是笔者首先想到的方法,即将前端项目拉下来,然后将前端项目的源码放到后端库里,提交一下。很简单粗暴,但是这种方法会造成之前前端项目历史提交记录的丢失。
-
-### 不影响提交记录,合并仓库
-
-笔者当然不希望将前端项目的历史提交记录丢失了,所以最终采用了下方的方案,完整步骤如下:
-
-::: tip 笔者说
-
-提示说明一下,后端仓库名叫:eladminx,前端仓库名叫:eladminx-web
-
-:::
-
-1. 克隆后端项目仓库到本地(笔者没有在 git bash 中操作,而是在 cmd 中进行的)
-
- ```sh
- git clone https://gitee.com/Charles7c/eladminx.git
- cd eladminx
- ```
-
- 
-
-2. 将前端仓库作为后端仓库的远程仓库,起别名为 frontend(这个随便起)
-
- ```sh
- git remote add -f frontend https://gitee.com/Charles7c/eladminx-web.git
- ```
-
- 
-
-3. 将前端仓库的 master 分支(自己选择哪个分支)合并到后端仓库
-
- ```sh
- git merge --strategy ours --no-commit frontend/master
- ```
-
- 
-
- 想法很美,但是报错了:
-
- ```
- fatal: refusing to merge unrelated histories
- ```
-
- 这是因为后端仓库的本地分支历史记录和前端仓库的历史记录不匹配,人家 Git 怀疑你是不是合并错了,但咱们知道就是要合并,写个声明 “表明出事儿与人家无关”就可以了。
-
- ```sh
- git merge --strategy ours --allow-unrelated-histories --no-commit frontend/master
- ```
-
- 
-
-4. 将前端仓库的 master 分支内容放到在后端仓库内刚建好的 eladminx-web 文件夹中
-
- ```sh
- mkdir eladminx-web
- git read-tree --prefix=eladminx-web/ -u frontend/master
- ```
-
-5. 提交刚才的修改(毕竟你刚才又合并又创建文件夹的,肯定要提交修改啊)
-
- ```sh
- git commit -m "迁移前端项目仓库,与后端项目仓库合并"
- ```
-
-6. 最后将本地的修改强制推送到远程仓库即可
-
- ```sh
- git push --force
- ```
-
-到此为止,笔者这两个项目的 master 分支就合并完了,如果你想合并其他分支,例如:dev,那就首先把后端仓库的分支切换到 dev,然后将上述中的 master 这个分支名换为 dev 就可以了。
-
-## 后记
-
-**C:** 关于这个合并,你以哪个仓库为主都可以。最后合并的提交记录是以历史提交时间进行降序排列的。
-
diff --git a/docs/categories/fragments/2022/03/26/修改Git最后一次提交记录的作者和邮箱.md b/docs/categories/fragments/2022/03/26/修改Git最后一次提交记录的作者和邮箱.md
deleted file mode 100644
index b38432f1a..000000000
--- a/docs/categories/fragments/2022/03/26/修改Git最后一次提交记录的作者和邮箱.md
+++ /dev/null
@@ -1,43 +0,0 @@
----
-title: 修改Git最后一次提交记录的作者和邮箱
-author: 查尔斯
-date: 2022/03/26 10:30
-categories:
- - 杂碎逆袭史
-tags:
- - Git
----
-
-# 修改Git最后一次提交记录的作者和邮箱
-
-## 前言
-
-**C:** 今天周末了,抽出了一点时间继续维护下之前提到过的衍生开源项目。修了一个 bug 后,提交了一下。但是突然想起来,今天开发用的是工作本,工作本中的 Git author、email 是配的真实姓名和公司邮箱,提交前忘了修改,现在已经推送到开源平台了,这肯定不行啊。
-
-但是现在即使修改了本地的 Git author、email 配置,历史提交记录也改变不了啊。别着急,看看怎么解决的。
-
-## 问题解决
-
-如果你确定是和笔者一样的情况,即确保是要修改最后一次提交记录,无论有没有推送到远程仓库都没问题。解决方法就两步,是不是很简单?
-
-1. 修改最后一次提交的作者和邮箱信息
-
- ```sh
- git commit --amend --author="Charles7c "
- ```
-
-2. 最后将本地的修改强制推送到远程仓库即可(如果你没推送到远程仓库,这步就不需要执行了)
-
- ```sh
- git push --force
- ```
-
-## 后记
-
-**C:** 另外说一下,如果你要修改最后一次提交记录的 commit message,执行下面的命令就可以了。
-
-```sh
-git commit --amend -m "要修改为的提交信息"
-```
-
-上方修改作者和邮箱信息的命令,还可以继续加参数 `--date` 来修改提交时间,有需要的话去试试去吧。
\ No newline at end of file
diff --git a/docs/categories/fragments/2022/03/27/修改Git所有提交记录中的作者和邮箱.md b/docs/categories/fragments/2022/03/27/修改Git所有提交记录中的作者和邮箱.md
deleted file mode 100644
index e43e4fd1f..000000000
--- a/docs/categories/fragments/2022/03/27/修改Git所有提交记录中的作者和邮箱.md
+++ /dev/null
@@ -1,52 +0,0 @@
----
-title: 修改Git所有提交记录中的作者和邮箱
-author: 查尔斯
-date: 2022/03/27 13:00
-categories:
- - 杂碎逆袭史
-tags:
- - Git
----
-
-# 修改Git所有提交记录中的作者和邮箱
-
-## 前言
-
-**C:** 上一篇,笔者介绍了怎么修改 Git 最后一次提交的作者和邮箱信息。那如果你是已经提交了很多次的记录,难道一个个的回退过去修改吗?显然不可能,所以本篇笔者带着大家认识一下如何批量修改 Git 提交记录中的作者和邮箱信息。
-
-## 问题解决
-
-解决方法其实就是编写一个脚本来进行批量替换。
-
-1. 新建一个 sh / bat 格式的脚本文件(如果你是在 cmd 中执行,那就用 bat 格式,如果是在 git bash 中执行就用 sh)
-
-2. 复制下方脚本内容到脚本文件中,然后编辑替换好错误邮箱、正确作者和邮箱(如果是在 cmd 中执行,#!/bin/sh 就替换为 #!/bin/bat)
-
- ```sh
- #!/bin/sh
-
- git filter-branch --env-filter '
- WRONG_EMAIL="错误的邮箱"
- NEW_NAME="正确的作者名"
- NEW_EMAIL="正确的邮箱"
-
- if [ "$GIT_COMMITTER_EMAIL" = "$WRONG_EMAIL" ]
- then
- export GIT_COMMITTER_NAME="$NEW_NAME"
- export GIT_COMMITTER_EMAIL="$NEW_EMAIL"
- fi
- if [ "$GIT_AUTHOR_EMAIL" = "$WRONG_EMAIL" ]
- then
- export GIT_AUTHOR_NAME="$NEW_NAME"
- export GIT_AUTHOR_EMAIL="$NEW_EMAIL"
- fi
- ' --tag-name-filter cat -- --branches --tags
- ```
-
-3. 保存脚本
-
-4. 将脚本文件放到要批量修改提交记录的 Git 仓库中(根目录就行)
-
-1. 执行脚本
-
-随后你就会看到,先是提示一个 warn 警告,然后它会一条条的修改以往提交记录,如果错误的提交比较多,那就耐心等一会儿吧。
diff --git a/docs/categories/fragments/2022/03/28/为指定Git仓库单独配置用户名和邮箱.md b/docs/categories/fragments/2022/03/28/为指定Git仓库单独配置用户名和邮箱.md
deleted file mode 100644
index ae1a6dc62..000000000
--- a/docs/categories/fragments/2022/03/28/为指定Git仓库单独配置用户名和邮箱.md
+++ /dev/null
@@ -1,63 +0,0 @@
----
-title: 为指定Git仓库单独配置用户名和邮箱
-author: 查尔斯
-date: 2022/03/28 21:29
-categories:
- - 杂碎逆袭史
-tags:
- - Git
----
-
-# 为指定Git仓库单独配置用户名和邮箱
-
-## 前言
-
-**C:** 在前几天里,笔者一直给大家分享关于如何在 Git 仓库中 “销赃匿迹”,究其原因是笔者最近业余时间用的是工作用笔记本来“干活”,工作用笔记本里的全局用户名和邮箱肯定是公司 GitLab 的信息了。
-
-周末和工作日切换的时候,有时候兴致上来,没有及时修改全局用户名等信息,就直接 commit ,push 了,等到发现时那肯定就要用前几天的几个法子来挽救一下了。
-
-可能会有同学问,你怎么不为指定仓库做一下局部用户名配置呢?是的,没错,有全局配置就会有局部配置,但笔者之所以还会出这种问题,主要有两点:
-
-1. clone 了不知道多少个仓库,不是只在操作一个仓库的时候,每个都要配置,很是麻烦,直接一个全局配置搞定,多么简单
-2. 兴致上来,有一两个仓库就忘了配置
-
-当然,如果后面笔者长期用工作用笔记本在业余时间“干活”的话,也完全可以将工作 Git 仓库们进行局部配置,毕竟相较来讲,工作仓库是稳定的。
-
-闲话少说,下面贴一下为指定 Git 仓库做局部用户名和邮箱配置的方法。
-
-## 全局配置用户名和邮箱
-
-我们先一起回忆下全局配置用户名和邮箱的方法,在任意 Git 仓库里进行如下配置即可:
-
-```sh
-# 全局配置用户名
-git config --global user.name "Charles7c"
-# 全局配置邮箱
-git config --global user.email "charles7c@126.com"
-```
-
-## 局部配置用户名和邮箱
-
-局部配置的方法也是非常简单,首先进入指定的 Git 仓库,然后进行如下配置即可:
-
-```sh
-# 进入指定 Git 仓库
-cd HelloWorld
-# 局部配置用户名
-git config user.name "Charles7c"
-# 局部配置邮箱
-git config user.email "charles7c@126.com"
-```
-
-没错,只需要在配置时去掉 `--global` 这个参数就可以了,配置完成后,你可以通过命令查看下配置是否成功。
-
-::: tip 笔者说
-当然了,你也可以去其他没进行局部配置的仓库看看,看看它们的用户名和邮箱有没有受到影响。
-:::
-
-```sh
-# 查看所在 Git 仓库配置的用户名
-git config user.name
-# 查看所在 Git 仓库配置的邮箱
-git config user.email
-```
\ No newline at end of file
diff --git a/docs/categories/fragments/2022/08/29/内网CentOS服务器设置网络代理.md b/docs/categories/fragments/2022/08/29/内网CentOS服务器设置网络代理.md
deleted file mode 100644
index 18b93416f..000000000
--- a/docs/categories/fragments/2022/08/29/内网CentOS服务器设置网络代理.md
+++ /dev/null
@@ -1,78 +0,0 @@
----
-title: 内网CentOS服务器设置网络代理
-author: 查尔斯
-date: 2022/08/29 20:53
-categories:
- - 杂碎逆袭史
-tags:
- - Linux
- - CentOS
- - 网络代理
----
-
-# 内网CentOS服务器设置网络代理
-
-**C:** 今天在网管那新申请了一台服务器,打算用来做测试环境。但是内网服务器没有网络,所以需要设置一下网络代理才能满足上网要求。
-
-
-
-## 设置http/https代理
-
-1. 修改 profile 文件
-
- ```sh
- vi /etc/profile
- ```
-
-2. 在 profile 文件末尾,追加下方配置内容
-
- ```shell
- # 注意:这台机器必须能够访问配置的代理服务器
- export http_proxy=http://你的代理服务器地址:你的代理服务器端口号
- export https_proxy=http://你的代理服务器地址:你的代理服务器端口号
- ```
-
- 如果你的代理服务器需要登录,那么只需要如下写法即可:
-
- ```shell
- # 注意:这台机器必须能够访问配置的代理服务器
- export http_proxy=http://用户名:密码@你的代理服务器地址:你的代理服务器端口号
- export https_proxy=http://用户名:密码@你的代理服务器地址:你的代理服务器端口号
- ```
-
-
-## 设置yum代理
-
-因为安装一些环境的时候还需要用到 yum,所以给 yum 也配置一下代理。
-
-1. 修改 yum.conf 文件
-
- ```shell
- vi /etc/yum.conf
- ```
-
-2. 在 yum.conf 文件末尾,追加下方配置内容
-
- ```shell
- proxy=http://你的代理服务器地址:你的代理服务器端口号
- ```
-
- 当然了,如果你的代理服务器需要登录,写法也和设置 http/https 代理时一样。
-
-都设置完之后,执行 `reboot` 重启服务器,让配置生效即可。
-
-## 检测是否可以上网
-
-重启后,为了确认配置是否成功,执行 `curl` 来测试一下。
-
-```shell
-curl www.baidu.com
-```
-
-很明显,看到下面的返回就知道配置成功了,如期返回了百度的页面内容。
-
-```html
-
- 百度一下,你就知道
-```
-
diff --git a/docs/categories/fragments/2022/10/01/个人常用Docker命令.md b/docs/categories/fragments/2022/10/01/个人常用Docker命令.md
deleted file mode 100644
index 6d13309a4..000000000
--- a/docs/categories/fragments/2022/10/01/个人常用Docker命令.md
+++ /dev/null
@@ -1,303 +0,0 @@
----
-title: 个人常用 Docker 命令
-author: 查尔斯
-date: 2022/10/01 22:33
-isTop: true
-categories:
- - 杂碎逆袭史
-tags:
- - Docker
----
-
-# 个人常用 Docker 命令
-
-## 镜像相关
-
-### 查看本地镜像列表
-
-```shell
-docker images
-```
-
-### 从记录中心查询镜像
-
-```shell
-docker search 镜像关键词
-```
-
-### 从记录中心拉取镜像到本地
-
-::: warning 笔者说
-如果镜像名称后不指定 **标签/版本** ,则会默认使用最新版本(latest)。
-
-例如:docker pull tomcat:8.5.0 拉取的就是 8.5.0 版本的 tomcat 镜像,而 docker pull tomcat -> 拉取的实际是 docker pull tomcat:latest,这个 latest 是跟随记录中心中的最新版本变化的,无法确定当前拉取的是哪一个版本。
-:::
-
-```shell
-docker pull 镜像名称[:标签/版本]
-```
-
-### 删除本地镜像
-
-```shell
-# 删除指定镜像
-docker rmi 镜像ID/镜像名称 [镜像ID/镜像名称...]
-
-# 删除所有镜像
-docker rmi `docker images -q`
-docker rmi $(docker images -q)
-```
-
-::: tip 笔者说
-`q` 是 quiet 的意思,加上这个参数后,docker images 输出的就不是镜像详细列表了,而是镜像 ID 列表,通常用于编写脚本时使用。
-
-所以,上方删除所有镜像的命令实际是 docker rmi 镜像ID1 镜像ID2...
-:::
-
-### 从 Dockerfile 创建镜像
-
-::: warning 笔者说
-如果镜像名称后不指定 **标签/版本** ,则会默认使用最新版本(latest)。
-:::
-
-```shell
-docker build -t 镜像名称[:标签/版本] Dockerfile文件路径
-```
-
-### 将本地镜像导出为 tar 文件
-
-::: warning 笔者说
-如果镜像名称后不指定 **标签/版本** ,则会默认使用最新版本(latest)。
-:::
-
-```shell
-docker save -o/-output 文件路径.tar 镜像名称[:标签/版本]
-```
-
-### 从 tar 文件导入为本地镜像
-
-```shell
-docker load -i/-input 文件路径.tar
-```
-
-## 容器相关
-
-### 查看容器列表
-
-```shell
-# 查看正在运行的容器
-docker ps
-
-# 查看全部容器(包含已经停止的)
-docker ps -a
-
-# 模糊查询容器
-docker ps [-a] | grep 容器关键词
-```
-
-### 创建容器并运行
-
-::: warning 笔者说
-如果镜像名称后不指定 **标签/版本** ,则会默认使用最新版本(latest)。
-
-如果本地不存在该版本的镜像,则会先从记录中心拉取到本地。
-:::
-
-```shell
-# -d 指定容器在后台运行
-# --name 指定容器名称
-# -m 限定容器内存大小
-# --restart 指定重新启动方式,always 表示始终重启
-# -e 指定环境变量配置
-# -p 指定容器和宿主机的网络端口映射
-# -v 指定容器和宿主机的目录挂载
-# --network 指定容器使用的网络
-# --network-alias 指定容器在网络中的别名
-
-docker run -d \
---name 容器名称 镜像名称[:标签/版本] \
-[-m xxxm] \
---restart=always \
-[-e 环境变量名=环境变量值] \
-[-p 宿主机端口:容器内部端口] \
-[-v 宿主机目录:容器内部目录] \
-[--network 网络名称 --network-alias 网络别名]
-```
-
-### 停止容器
-
-```shell
-# 停止指定容器
-docker stop 容器ID/容器名称 [容器ID/容器名称...]
-
-# 停止所有容器
-docker stop `docker ps -aq`
-docker stop $(docker ps -aq)
-```
-
-::: tip 笔者说
-`q` 是 quiet 的意思,加上这个参数后,docker ps 输出的就不是容器详细列表了,而是容器 ID 列表,通常用于编写脚本时使用。
-
-所以,上方停止所有容器的命令实际是 docker stop 容器1ID 容器2ID...
-:::
-
-### 启动容器
-
-```shell
-docker start 容器ID/容器名称
-```
-
-### 重启容器
-
-```shell
-docker restart 容器ID/容器名称
-```
-
-### 删除容器
-
-```shell
-# 删除指定容器
-docker rm 容器ID/容器名称 [容器ID/容器名称...]
-
-# 删除所有容器
-docker rm `docker ps -aq`
-docker rm $(docker ps -aq)
-```
-
-::: tip 笔者说
-`q` 是 quiet 的意思,加上这个参数后,docker ps 输出的就不是容器详细列表了,而是容器 ID 列表,通常用于编写脚本时使用。
-
-所以,上方删除所有容器的命令实际是 docker rm 容器1ID 容器2ID...
-:::
-
-### 进入容器内部
-
-```shell
-docker exec -it 容器ID/容器名称 bash
-
-docker exec -it 容器ID/容器名称 sh
-```
-
-### 从容器内部退出
-
-```shell
-exit
-```
-
-### 向容器内拷贝文件
-
-```shell
-docker cp 宿主机内文件路径 容器名称:容器内文件路径
-```
-
-### 查看容器日志
-
-```shell
-# -f/--flow 跟踪日志输出
-# -t/--timestamps 显示时间戳
-# -n/--tail 从日志末尾显示的行数,默认为 all
-# --since 自某个时间之后的日志
-# 例如:--since "2022-09-30" 表示显示2022年9月30日后的日志
-# 例如:--since 30m 表示显示最近 30 分钟内的日志
-# --until 某个时间之前的日志
-docker logs -f [-t] [-n 行数] [--since 开始时间] [--until 结束时间] 容器ID/容器名称
-```
-
-### 备份容器为本地镜像
-
-::: warning 笔者说
-如果镜像名称后不指定 **标签/版本** ,则会默认使用最新版本(latest)。
-:::
-
-```shell
-docker commit [-a "作者"] [-m "信息"] 容器ID/容器名称 镜像名称[:标签/版本]
-```
-
-### 将容器导出为 tar.gz 文件
-
-```shell
-docker export 容器ID/容器名称 > 文件路径.tar.gz
-```
-
-### 将 tar.gz 文件导入为镜像
-
-::: warning 笔者说
-如果镜像名称后不指定 **标签/版本** ,则会默认使用最新版本(latest)。
-:::
-
-```shell
-docker import 文件路径.tar.gz 镜像名称[:标签/版本]
-```
-
-## 网络相关
-
-### 查看网络列表
-
-```shell
-docker network ls
-```
-
-### 创建 bridge 网络
-
-```shell
-docker network create 网络名称
-```
-
-### 删除网络
-
-```shell
-docker network rm 网络ID/网络名称
-```
-
-## 其他
-
-### 查看 docker 版本
-
-```shell
-docker -v
-docker version
-```
-
-### 查看 docker 信息
-
-```shell
-docker info
-```
-
-## docker-compose命令
-
-### 启动并后台运行所有的服务
-
-```shell
-docker-compose up -d
-```
-
-### 停止并删除容器、网络、卷、镜像
-
-```shell
-docker-compose down
-```
-
-### 列出项目中目前的所有容器
-
-```shell
-docker-compose ps
-```
-
-### 停止容器
-
-```shell
-docker-compose stop 容器名
-```
-
-### 启动容器
-
-```shell
-docker-compose start 容器名
-```
-
-### 修改 yml 文件后,重新启动并后台运行
-
-```shell
-docker-compose up --force-recreate -d
-```
diff --git a/docs/categories/fragments/2022/10/05/个人常用Git命令.md b/docs/categories/fragments/2022/10/05/个人常用Git命令.md
deleted file mode 100644
index e723c35fd..000000000
--- a/docs/categories/fragments/2022/10/05/个人常用Git命令.md
+++ /dev/null
@@ -1,292 +0,0 @@
----
-title: 个人常用 Git 命令
-author: 查尔斯
-date: 2022/10/05 21:30
-isTop: true
-categories:
- - 杂碎逆袭史
-tags:
- - Git
----
-
-# 个人常用 Git 命令
-
-## 初始配置
-
-### 全局配置
-
-在进行版本管理之前,首先需要对 Git 进行用户配置。
-
-全局配置指的是当前终端上的所有仓库使用该配置,可以在任何位置设置。
-
-```shell
-# 全局配置用户名
-git config --global user.name "用户名"
-# 全局配置用户邮箱
-git config --global user.email "用户邮箱"
-```
-
-### 局部配置
-
-局部配置指的是当前终端上的指定仓库使用该配置,需要在指定仓库内进行设置。
-
-```shell
-# 局部配置用户名
-git config user.name "用户名"
-# 局部配置用户邮箱
-git config user.email "用户邮箱"
-```
-
-## 版本控制相关
-
-### 初始化仓库
-
-自动创建 master 分支。
-
-```shell
-git init
-```
-
-### 查看工作区状态
-
-```shell
-git status
-```
-
-### 将工作区的修改添加到暂存区
-
-::: tip 笔者说
-该命令可执行多次,来实现将多个文件的修改添加到暂存区。另外,如果某个文件在添加到暂存区后又发生了变更,在没有提交到版本库之前,依然需要执行一次该命令。
-:::
-
-```shell
-git add 文件名1 [文件名2...]
-```
-
-### 将暂存区的修改提交到版本库
-
-```shell
-git commit -m "提交信息"
-```
-
-### 撤销未提交到暂存区的修改
-
-```shell
-git restore 文件名
-```
-
-### 撤销暂存区的修改
-
-```shell
-git restore --staged 文件名
-```
-
-### 查看提交日志
-
-```shell
-# --oneline 以一行格式显示提交日志
-# 查看该文件的提交日志
-git log [--oneline] [文件名]
-```
-
-### 查看操作日志
-
-相比于 `git log`,reflog 可以查看到所有的操作行为,例如:回退版本······
-
-```shell
-git reflog
-```
-
-### 回退版本
-
-::: tip 笔者说
-1、回退版本的数量较少时,可以将 `HEAD~回退版本的数量` 改为 `HEAD^` 的写法。
-
-`HEAD^` 相当于 `HEAD~1`,`HEAD^^` 相当于 `HEAD~2`,依次类推。
-
-2、回退版本的数量比较多时,建议采用指定 Commit ID 来回退的方法
-:::
-
-```shell
-# --hard 回退到相应版本,放弃之前版本的修改
-git reset --hard HEAD~回退版本的数量/HEAD^/Commit ID
-
-# --soft 回退到相应版本,保留之前版本的修改
-git reset --soft HEAD~回退版本的数量/HEAD^/Commit ID
-```
-
-### 修改最后一次提交的信息
-
-::: warning 笔者说
-如果你已经将之前本地版本推送到了远程仓库,那么在下一次推送的时候就需要加上 `-f` 参数了。
-
-`git push -f`
-
-但是 GitHub 或者公司内的 GitLab 等,默认都是禁止强制推送的,需要设置一下,所以还是多加注意吧。
-:::
-
-```shell
-# 如果仅修改用户名这类信息,改完在编辑模式按 : 随后按 wq 保存即可
-# 信息格式参考 git log 输出
-git commit --amend [--author="用户名<用户邮箱>"] [--date "日期信息"] [-m 提交信息]
-```
-
-### 修改指定提交的信息
-
-::: warning 笔者说
-如果你已经将之前本地版本推送到了远程仓库,那么在下一次推送的时候就需要加上 `-f` 参数了。
-
-`git push -f`
-
-但是 GitHub 或者公司内的 GitLab 等,默认都是禁止强制推送的,需要设置一下,所以还是多加注意吧。
-:::
-
-```shell
-# 1.开始(Commit ID 是要修改提交信息的版本的上一个版本的 Commit ID)
-git rebase -i Commit ID
-# 2.打开记事本后,将对应提交前的 pick 改为 e 或 edit,保存退出
-# 3.进行修订,同上
-# 信息格式参考 git log 输出
-git commit --amend [--author="用户名<用户邮箱>"] [--date "日期信息"] [-m 提交信息]
-# 4.完成
-git rebase --continue
-```
-
-## 远程仓库相关
-
-### 生成 SSH Key
-
-```shell
-# 一路回车即可,最终会在 ${user.home}/.ssh/ 下生成 id_rsa.pub 公钥文件和 id_rsa 私钥文件
-ssh-keygen -t rsa -C "用户邮箱"
-```
-
-### 添加远程仓库
-
-```shell
-git remote add origin 远程仓库Git地址
-```
-
-### 查看远程仓库信息
-
-```shell
-git remote -v
-```
-
-### 删除远程仓库
-
-```shell
-git remote rm origin
-```
-
-### 将本地分支推送到远程仓库
-
-```shell
-# 第一次推送时,加上 [-u],后续不需要加 [-u]
-# 如果远程分支名和本地分支名相同,可以省略
-# 常见用法:
-# git push -u origin master
-# git push origin master
-# git push
-git push [-u] [远程主机名] [本地分支名]:[远程分支名]
-```
-
-**【谨慎】如果本地版本和远程版本不一致,可强制推送覆盖:**
-
-```shell
-git push [--force/-f] [远程主机名] [本地分支名]:[远程分支名]
-```
-
-### 克隆远程仓库到本地
-
-::: tip 笔者说
-适用于本地不存在仓库,远程存在的情况。
-
-例如:换了电脑,刚开始进入某个项目组等场景。
-:::
-
-```shell
-git clone 远程仓库Git地址
-```
-
-### 拉取远程分支与本地分支合并
-
-```shell
-# 如果远程分支是与当前分支合并,则冒号后面的部分可以省略
-# 常见用法:git pull origin
-git pull [远程主机名] [远程分支名]:[本地分支名]
-```
-
-## 分支相关
-
-### 查看本地分支
-
-```shell
-git branch
-```
-
-### 创建并切换分支
-
-```shell
-git switch -c 分支名
-```
-
-### 创建分支
-
-```shell
-git branch 分支名
-```
-
-### 切换分支
-
-```shell
-git switch 分支名
-```
-
-### 将指定分支合并到当前分支
-
-```shell
-git merge 分支名
-```
-
-### 重命名分支
-
-```shell
-git branch -m old-branch new-branch
-```
-
-## 标签相关
-
-### 查看标签
-
-```shell
-git tag
-```
-
-### 打标签
-
-```shell
-# 将指定版本打标签
-# 常见用法:
-# git tag -a v1.0.0 -m "version 1.0.0" b43375
-# git tag v1.0.0
-git tag [-a 标签名] [-m 标签信息] [Commit ID]
-```
-
-### 查看指定标签详细信息
-
-```shell
-git show 标签名
-```
-
-### 删除标签
-
-```shell
-git tag -d 标签名
-```
-
-### 将本地标签推送到远程仓库
-
-```shell
-git push origin -tags
-```
\ No newline at end of file
diff --git a/docs/categories/fragments/2022/10/06/个人常用快捷键.md b/docs/categories/fragments/2022/10/06/个人常用快捷键.md
deleted file mode 100644
index 1372ead19..000000000
--- a/docs/categories/fragments/2022/10/06/个人常用快捷键.md
+++ /dev/null
@@ -1,115 +0,0 @@
----
-title: 个人常用快捷键
-author: 查尔斯
-date: 2022/10/06 12:42
-isTop: true
-categories:
- - 杂碎逆袭史
-tags:
- - 快捷键
- - Windows
- - "IntelliJ IDEA"
----
-
-# 个人常用快捷键
-
-## 通用
-
-::: tip 笔者说
-下方快捷键,在大部分软件中通用。
-:::
-
-- Ctrl + C:复制光标所在行 或 复制选中内容
-- Ctrl + V:粘贴复制内容到光标所在行
-- Ctrl + X: 剪切光标所在行 或 剪切选中内容
-- Ctrl + Z:撤销上一步操作(前提是没关闭当前文件)
-- Ctrl + Y:恢复上一步操作(前提是没关闭当前文件)
-- Ctrl + S:保存
- - Ctrl + Shift + S:全部窗口保存
-- Ctrl + A:全选
-- Ctrl + F:在当前文件中进行文本查找
-- Ctrl + H:在当前文件中进行文本替换
-- Tab:向右缩进
-- Shift + Tab:向左缩进
-- Ctrl + W:关闭窗口
- - Ctrl + Shift + W:全部窗口关闭
-- Ctrl + B:粗体
-- Ctrl + I:斜体
-- Ctrl + U:下划线
-
-## Windows
-
-- Windows:打开开始菜单
-- Windows + D:显示桌面
-- Windows + L:锁屏
-- Windows + E:打开资源管理器
-- Windows + Shift + S:打开系统自带截图
-- Windows + V:打开剪贴板
-- Windows + Tab -> 方向键切换窗口,回车键进入窗口:打开多窗口视图
-- Windows + R:打开“运行”对话框
- - 输入 cmd:打开命令行程序
- - 输入 notepad:打开记事本程序
- - 输入 calc:打开计算器程序
- - 输入 mspaint:打开绘图程序
- - 输入 regedit:打开注册表
- - 输入 services.msc:打开服务列表
- - 输入 mstsc:打开远程桌面连接
- - 输入 subl:打开 Sublime Text 程序(需要安装 Sublime Text并提前设置好环境变量)
- - 输入 typora:打开 Typora 程序(需要安装 Typora)
-
-- Ctrl + Alt + Delete:打开任务管理器
-
-- Alt + Tab -> Alt 键不松手,Tab键切换窗口,松手后进入窗口:切换窗口视图
-- [Fn] + Alt + F4:关闭窗口(关闭程序)
- - Alt + 空格键 + C
-- [Fn] + F2:文件重命名
-
-## IntelliJ IDEA
-
-### 快捷键
-
-- Ctrl + Alt + L:格式化代码(代码写不规范的童鞋,起码学会这个快捷键吧)
-- Ctrl + D:复制光标所在行 或 复制选中内容,并把复制内容插入到光标位置下面
-- Ctrl + Y:删除光标所在行 或 删除选中的行(与通用快捷键不同)
-- [Fn] + Alt + Insert:弹出菜单,可以选择进行生成 getter/setter、生成构造方法,重写 toString 等(有了 Lombok 后用的频率低了很多)
-- Ctrl + Alt + 回车:在上方插入一行,光标移动到新行行首
-- Shift + 回车:在下方插入一行,光标移动到新行行首
-- Ctrl + /:给光标所在行 或 选中行代码 添加或取消单行注释(可根据当前的文件类型,使用不同的注释符号)
-- Ctrl + Shift + /:以 `多行注释` 注释掉选中行
-- Ctrl + Shift + U:切换单词大小写
-- Ctrl + Alt + T:给选中代码块添加语句块(try-catch、while等)
-- Alt + 回车:提供快速修复选择
-- Alt + Shift + 上/下键:向上或向下移动当前行/选中行
-- Ctrl + Shift + 上/下键:向上或向下移动当前方法/选中方法
-- Ctrl + Shift + 回车:在当前行任何地方可以快速在末尾生成分号
-- Ctrl + F:在当前文件中进行文本查找
- - Ctrl + Shift + F:全局查找
-- Ctrl + R:在当前文件中进行文本替换(与通用快捷键不同)
- - Ctrl + Shift + R:全局替换
-- Ctrl + H:显示当前类的层次结构
-- [Fn] + Ctrl + F12:显示当前类的结构(方法、属性等)
-- Ctrl + 鼠标左键:在变量或方法上使用此快捷键,可以找到变量或方法的定义位置(如果是已经在变量或方法的定义位置,按下就会进入或弹出它被使用的位置)
- - Ctrl + Alt + 鼠标左键:在某个使用的方法上点击,可直接定位到该方法在对应子类重写的位置(在Controller中想看看调用Service层怎么实现的)
-- [Fn] + Ctrl + End:跳转到文件尾部
-- [Fn] + Ctrl + Home:跳转到文件头部
-- Ctrl + G:跳转到指定行:列
-- Ctrl + Q:显示光标所在的类名、方法名、变量名的 java doc 注释
-- Ctrl + Alt + O:优化 import 语句,自动导入包或移除无用包
-- [Fn] + Shift + F9:调试按钮
-- [Fn] + Shift + F10:运行按钮
-- Ctrl + T:等效于工具栏 pull 按钮 - VCS(版本控制系统)
-- Ctrl + K:等效于工具栏 commit 按钮 - VCS(版本控制系统)
-- Ctrl + Alt + Z:撤销当前文件的修改(版本控制系统)
-
-### 快捷短语
-
-- psvm + 回车:生成 main 方法
-- sout + 回车:生成输出语句(System.out.println();)
-- 在创建对象时,先写完后面 new Xxx() 部分,然后输入 .var + 回车:补全前面声明部分
-- 数组/Collection系列集合,.for + 回车:生成增强 for 语句
-- 数组/Collection系列集合,.fori + 回车:生成循环下标语句(Set集合不行)
-- 返回值,.return + 回车:生成 return 返回值; 语句
-
-## 浏览器
-
-- Ctrl + 0:恢复页面默认缩放
\ No newline at end of file
diff --git a/docs/categories/fragments/2022/10/26/Docker安装OpenLDAP.md b/docs/categories/fragments/2022/10/26/Docker安装OpenLDAP.md
deleted file mode 100644
index b286675e3..000000000
--- a/docs/categories/fragments/2022/10/26/Docker安装OpenLDAP.md
+++ /dev/null
@@ -1,114 +0,0 @@
----
-title: Docker 安装 OpenLDAP 详细步骤
-author: 查尔斯
-date: 2022/10/26 20:28
-categories:
- - 杂碎逆袭史
-tags:
- - LDAP
- - Docker
- - 容器
-showComment: false
----
-
-# Docker 安装 OpenLDAP 详细步骤
-
-::: tip 笔者说
-笔者下面的步骤及配置是基于指定版本的实践,大多数程序大多数情况下在相差不大的版本时可以直接参考。(当然了,即使是非 Docker 方式安装程序也是一样道理)
-:::
-
-## 拉取镜像
-
-::: warning 笔者说
-拉取镜像时需要明确镜像版本(Tag)。
-:::
-
-不指定版本(Tag)就拉取镜像,那拉取下来的镜像版本(Tag)默认是 `latest`(最新的)。`latest` 会跟随 Docker Registry 中的记录变化,现在拉取下来的 `latest` 是 x1 版本,但隔了一段时间后你在其他机器上再拉取 `latest` 可能就是 x2 版本了。
-
-变化的版本,不利于生产环境部署的稳定。无论是后续在其他环境部署还是扩容集群等场景均要求根据架构要求指定好版本。
-
-```shell
-docker pull osixia/openldap:1.5.0
-```
-
-## 运行容器
-
-::: warning 笔者说
-**下方的配置,切记要根据个人实际情况来修改。**
-:::
-
-- 容器的名称
-- 镜像名称:版本
-- 是否设置自启动?
-- 是否端口映射?
-- 环境变量配置
-- 映射的话映射到宿主机哪个端口?
-- 是否挂载卷?
-- 挂载的话要挂载宿主机哪个目录?
-- ......
-- 等自定义配置
-
-```shell
-# LDAP_ORGANISATION 组织名称,默认为 Example Inc
-# LDAP_DOMAIN 域,默认为 example.org
-# LDAP_ADMIN_PASSWORD 管理员密码,默认为 admin
-# LDAP_TLS_VERIFY_CLIENT TLS验证客户端
-# demand:默认。检查客户端证书,没有证书或证书错误都将立即终止连接
-# try:检查客户端证书,没有证书(允许连接),证书错误(终止连接)
-# allow:检查客户端证书,没有证书或证书错误,都允许连接
-# never:不验证客户端证书
-docker run -d \
---name openldap osixia/openldap:1.5.0 \
---restart=always \
--e LDAP_ORGANISATION="baidu" \
--e LDAP_DOMAIN="baidu.com" \
--e LDAP_ADMIN_PASSWORD="123456" \
--e LDAP_TLS_VERIFY_CLIENT=try \
--p 389:389 -p 636:636 \
--v /opt/disk/docker/volumes/openldap/conf:/etc/ldap/slapd.d \
--v /opt/disk/docker/volumes/openldap/data:/var/lib/ldap \
-# 使用该参数,容器内的 root 用户才拥有真正的 root 权限
---privileged=true
-```
-
-## 验证
-
-服务器开放好相应端口或设置好安全组规则后,我们使用 Apache Directory Studio 来验证一下。
-
-
-
-
-
-
-
-## Docker Compose脚本
-
-如果你是用的 docker-compose 来安装,下方附上相应 docker-compose.yml 脚本内容。
-
-```yaml
-version: '3'
-services:
- openldap:
- container_name: openldap
- image: osixia/openldap:1.5.0
- restart: always
- environment:
- LDAP_ORGANISATION: baidu
- LDAP_DOMAIN: baidu.com
- LDAP_ADMIN_PASSWORD: 123456
- LDAP_TLS_VERIFY_CLIENT: try
- ports:
- - 389:389
- - 636:636
- volumes:
- - /opt/disk/docker/volumes/openldap/conf:/etc/ldap/slapd.d
- - /opt/disk/docker/volumes/openldap/data:/var/lib/ldap
- privileged: true
-```
-
-编写好 docker-compose.yml 脚本后,在脚本同级目录执行下方命令即可。
-
-```shell
-docker-compose up -d
-```
-
diff --git a/docs/categories/fragments/2022/10/27/Docker安装Consul.md b/docs/categories/fragments/2022/10/27/Docker安装Consul.md
deleted file mode 100644
index bbb995c6c..000000000
--- a/docs/categories/fragments/2022/10/27/Docker安装Consul.md
+++ /dev/null
@@ -1,90 +0,0 @@
----
-title: Docker 安装 Consul 详细步骤
-author: 查尔斯
-date: 2022/10/27 22:00
-categories:
- - 杂碎逆袭史
-tags:
- - Consul
- - Docker
- - 容器
-showComment: false
----
-
-# Docker 安装 Consul 详细步骤
-
-::: tip 笔者说
-笔者下面的步骤及配置是基于指定版本的实践,大多数程序大多数情况下在相差不大的版本时可以直接参考。(当然了,即使是非 Docker 方式安装程序也是一样道理)
-:::
-
-## 拉取镜像
-
-::: warning 笔者说
-拉取镜像时需要明确镜像版本(Tag)。
-:::
-
-不指定版本(Tag)就拉取镜像,那拉取下来的镜像版本(Tag)默认是 `latest`(最新的)。`latest` 会跟随 Docker Registry 中的记录变化,现在拉取下来的 `latest` 是 x1 版本,但隔了一段时间后你在其他机器上再拉取 `latest` 可能就是 x2 版本了。
-
-变化的版本,不利于生产环境部署的稳定。无论是后续在其他环境部署还是扩容集群等场景均要求根据架构要求指定好版本。
-
-```shell
-docker pull consul:1.13.3
-```
-
-## 运行容器
-
-::: warning 笔者说
-**下方的配置,切记要根据个人实际情况来修改。**
-:::
-
-- 容器的名称
-- 镜像名称:版本
-- 是否设置自启动?
-- 是否端口映射?
-- 映射的话映射到宿主机哪个端口?
-- 是否挂载卷?
-- 挂载的话要挂载宿主机哪个目录?
-- ......
-- 等自定义配置
-
-```shell
-docker run -d \
---name consul consul:1.13.3 \
---restart=always \
--p 18500:8500 \
--v /opt/disk/docker/volumes/consul/conf:/consul/conf \
--v /opt/disk/docker/volumes/consul/data:/consul/data \
-# 使用该参数,容器内的 root 用户才拥有真正的 root 权限
---privileged=true
-```
-
-## 验证
-
-服务器开放好相应端口或设置好安全组规则后,访问 `http://宿主机IP:映射的端口` (例如按上方配置那就是:http://宿主机IP:18500)即可看到 Consul 界面。
-
-## Docker Compose脚本
-
-如果你是用的 docker-compose 来安装,下方附上相应 docker-compose.yml 脚本内容。
-
-```yaml
-version: '3'
-services:
- consul:
- container_name: consul
- image: consul:1.13.3
- restart: always
- environment:
- TZ: Asia/Shanghai
- ports:
- - 18500:8500
- volumes:
- - /opt/disk/docker/volumes/consul/conf:/consul/conf
- - /opt/disk/docker/volumes/consul/data:/consul/data
- privileged: true
-```
-
-编写好 docker-compose.yml 脚本后,在脚本同级目录执行下方命令即可。
-
-```shell
-docker-compose up -d
-```
diff --git a/docs/categories/fragments/2022/10/28/Docker安装MinIO.md b/docs/categories/fragments/2022/10/28/Docker安装MinIO.md
deleted file mode 100644
index 01a590bc5..000000000
--- a/docs/categories/fragments/2022/10/28/Docker安装MinIO.md
+++ /dev/null
@@ -1,125 +0,0 @@
----
-title: Docker 安装 MinIO 详细步骤
-author: 查尔斯
-date: 2022/10/28 22:37
-categories:
- - 杂碎逆袭史
-tags:
- - MinIO
- - Docker
- - 容器
-showComment: false
----
-
-# Docker 安装 MinIO 详细步骤
-
-::: tip 笔者说
-笔者下面的步骤及配置是基于指定版本的实践,大多数程序大多数情况下在相差不大的版本时可以直接参考。(当然了,即使是非 Docker 方式安装程序也是一样道理)
-:::
-
-## 拉取镜像
-
-::: warning 笔者说
-拉取镜像时需要明确镜像版本(Tag)。
-:::
-
-不指定版本(Tag)就拉取镜像,那拉取下来的镜像版本(Tag)默认是 `latest`(最新的)。`latest` 会跟随 Docker Registry 中的记录变化,现在拉取下来的 `latest` 是 x1 版本,但隔了一段时间后你在其他机器上再拉取 `latest` 可能就是 x2 版本了。
-
-变化的版本,不利于生产环境部署的稳定。无论是后续在其他环境部署还是扩容集群等场景均要求根据架构要求指定好版本。
-
-```shell
-docker pull minio/minio:RELEASE.2022-08-02T23-59-16Z.fips
-```
-
-## 运行容器
-
-::: warning 笔者说
-**下方的配置,切记要根据个人实际情况来修改。**
-:::
-
-- 容器的名称
-- 镜像名称:版本
-- 是否设置自启动?
-- 是否端口映射?
-- 环境变量配置
-- 映射的话映射到宿主机哪个端口?
-- 是否挂载卷?
-- 挂载的话要挂载宿主机哪个目录?
-- ......
-- 等自定义配置
-
-```shell
-# MINIO_ACCESS_KEY:用户名,默认为 minioadmin
-# MINIO_SECRET_KEY:用户密码,默认为 minioadmin
-# MINIO_COMPRESS:开启压缩 on 开启 off 关闭
-# MINIO_COMPRESS_EXTENSIONS:扩展名 .pdf,.doc 为空 所有类型均压缩
-# MINIO_COMPRESS_MIME_TYPES:mime 类型 application/pdf 为空 所有类型均压缩
-# MINIO_SERVER_URL:HTTPS 需要指定服务域名,例如:https://xxx.com:9000
-# MINIO_BROWSER_REDIRECT_URL:HTTPS 需要指定服务域名,例如:https://xxx.com:9001
-docker run -d \
---name minio minio/minio:RELEASE.2022-08-02T23-59-16Z.fips \
---restart=always \
--e TZ="Asia/Shanghai" \
--e MINIO_ACCESS_KEY="minioadmin" \
--e MINIO_SECRET_KEY="minioadmin" \
--e MINIO_COMPRESS="off" \
--e MINIO_COMPRESS_EXTENSIONS="" \
--e MINIO_COMPRESS_MIME_TYPES="" \
--p 9000:9000 -p 9001:9001 \
--v /opt/disk/docker/volumes/minio/conf:/root/.minio \
--v /opt/disk/docker/volumes/minio/data:/data \
-server --address ':9000' --console-address ':9001' /data \
-# 使用该参数,容器内的 root 用户才拥有真正的 root 权限
---privileged=true
-```
-
-## 验证
-
-服务器开放好相应端口或设置好安全组规则后,访问 `http://宿主机IP:映射的端口` (例如按上方配置那就是:http://宿主机IP:9001)即可看到 MinIO 管理界面。
-
-
-
-输入你刚才指定的用户名、密码,登录进来后,可以看到当前一个 Bucket 也没有,可以点击右侧的 [Create Bucket] 来创建。
-
-
-
-## Docker Compose脚本
-
-如果你是用的 docker-compose 来安装,下方附上相应 docker-compose.yml 脚本内容。
-
-```yaml
-version: '3'
-services:
- minio:
- container_name: minio
- image: minio/minio:RELEASE.2022-08-02T23-59-16Z.fips
- restart: always
- environment:
- TZ: Asia/Shanghai
- MINIO_ACCESS_KEY: minioadmin
- MINIO_SECRET_KEY: minioadmin
- # HTTPS 需要指定域名
- #MINIO_SERVER_URL: 'https://xxx.com:9000'
- #MINIO_BROWSER_REDIRECT_URL: 'https://xxx.com:9001'
- # 开启压缩 on 开启 off 关闭
- MINIO_COMPRESS: 'off'
- # 扩展名 .pdf,.doc 为空 所有类型均压缩
- MINIO_COMPRESS_EXTENSIONS: ''
- # mime 类型 application/pdf 为空 所有类型均压缩
- MINIO_COMPRESS_MIME_TYPES: ''
- ports:
- - 9000:9000
- - 9001:9001
- volumes:
- - /opt/disk/docker/volumes/minio/conf:/root/.minio
- - /opt/disk/docker/volumes/minio/data:/data
- command: server --address ':9000' --console-address ':9001' /data
- privileged: true
-```
-
-编写好 docker-compose.yml 脚本后,在脚本同级目录执行下方命令即可。
-
-```shell
-docker-compose up -d
-```
-
diff --git a/docs/categories/fragments/2022/10/31/CentOS安装Docker.md b/docs/categories/fragments/2022/10/31/CentOS安装Docker.md
deleted file mode 100644
index d1be39f98..000000000
--- a/docs/categories/fragments/2022/10/31/CentOS安装Docker.md
+++ /dev/null
@@ -1,149 +0,0 @@
----
-title: CentOS 安装 Docker、Docker Compose
-author: 查尔斯
-date: 2022/10/31 20:56
-categories:
- - 杂碎逆袭史
-tags:
- - Docker
- - Linux
- - CentOS
----
-
-# CentOS 安装 Docker、Docker Compose
-
-::: tip 笔者说
-笔者下面的步骤及配置是基于发帖时间当下的实践,大多数程序大多数情况下在相差不大的版本时可以直接参考。
-:::
-
-## Docker 安装
-
-### 方式一
-
-1. 软件更新
-
- ```shell
- yum -y update
- ```
-
-2. 安装 yum-utils
-
- ```shell
- yum -y install yum-utils device-mapper-persistent-data lvm2
- ```
-
-3. 设置 yum 软件源
-
- ```shell
- yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
- ```
-
-4. 安装 docker-ce(免费社区版)
-
- ```shell
- yum -y install docker-ce
- ```
-
-5. 启动 docker
-
- ```shell
- systemctl start docker
- ```
-
-6. 设置 docker 开机自启
-
- ```shell
- systemctl enable docker
- ```
-
-7. 检验是否安装成功
-
- ```shell
- docker -v
- ```
-
-### 方式二(推荐)
-
-一条命令安装 docker。
-
-1. 下载并安装 docker
-2. 启动并设置 docker 开机自启
-
-```shell
-curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun && systemctl start docker && systemctl enable docker
-```
-
-## Docker 配置
-
-在 Windows 系统中安装软件时,我们都清楚要尽量不安装在 C 盘,数据存储也尽量迁移到其他空间更大的盘。不然随着程序的使用,数据越来越多,再加上大多数情况下 C 盘空间并不大,最终导致 C 盘很快会被占满。
-
-同理,不更改 docker 的数据存储目录,那它的镜像、容器等存储占用随着使用时间的增长而增长,那你的服务器系统盘很快就会被占满了。所以建议你将 docker 的数据存储目录改到你服务器的数据盘挂载目录。
-
-更改 docker 数据存储目录这一点是笔者推荐的,而设置 docker 镜像加速这一点其实根本无需笔者多言,你先不配置,用用 docker 再说,如果你 `docker pull` 速度很快,那完全不需要配置。这三个镜像加速源是笔者验证过的,当你感受到拉镜像的 “绝望” 时,不妨再来配置试一试。
-
-::: tip 笔者说
-关于镜像加速地址,你还可以从阿里云找到你专属的镜像加速地址。
-
-按下面的路径就可以找到:
-
-产品与服务 -> 容器与中间件 -> 容器服务 -> 容器镜像服务 -> 镜像加速器
-:::
-
-
-
-闲话不多说,配置只需要 3~5 步即可搞定。
-
-1. 编辑 daemon.json 配置文件
-
- ```shell
- # 如果 /etc 下没有 docker 目录,可以先创建一下
- # mkdir -p /etc/docker
-
- vim /etc/docker/daemon.json
- ```
-
-2. 将下方配置内容写入 daemon.json 配置文件
-
- ```json
- {
- "data-root": "/opt/disk/docker",
- "registry-mirrors": [
- "https://hub-mirror.c.163.com",
- "https://mirror.baidubce.com",
- "https://ustc-edu-cn.mirror.aliyuncs.com"
- ]
- }
- ```
-
-3. 重新加载服务配置文件并重启 docker 服务
-
- ```shell
- # 重新加载服务配置文件
- systemctl daemon-reload
- # 重启 docker
- systemctl restart docker
- ```
-
-## Docker Compose 安装
-
-1. 下载 docker-compose 脚本,并改名为 docker-compose
-
- ```shell
- curl -L https://github.com/docker/compose/releases/download/v2.12.2/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
- ```
-
-2. 给脚本授予可执行权限
-
- ```shell
- chmod +x /usr/local/bin/docker-compose
- ```
-
-3. 检验是否安装成功
-
- ```shell
- docker-compose -v
- ```
-
-## 参考资料
-
-1. Custom Docker daemon options#Runtime directory and storage driver:https://docs.docker.com/config/daemon/systemd/#runtime-directory-and-storage-driver
diff --git a/docs/categories/fragments/2022/11/01/使用IDEA进行远程程序调试.md b/docs/categories/fragments/2022/11/01/使用IDEA进行远程程序调试.md
deleted file mode 100644
index 5b8fb0755..000000000
--- a/docs/categories/fragments/2022/11/01/使用IDEA进行远程程序调试.md
+++ /dev/null
@@ -1,75 +0,0 @@
----
-title: 使用 IntelliJ IDEA 进行远程程序调试
-author: 查尔斯
-date: 2022/11/01 20:55
-categories:
- - 杂碎逆袭史
-tags:
- - IDE
- - "IntelliJ IDEA"
- - Java
----
-
-# 使用 IntelliJ IDEA 进行远程程序调试
-
-**C:** 今天在测试环境出现了一个 “匪夷所思” 的问题,追踪日志、排查 Feign 日志,修改配置,尝试了很多种办法,均未解决。最终决定对测试环境进行远程程序调试来看看。
-
-在开发时用 IDE 的断点调试倒是相对便捷,但到了部署好的环境,再使用 IDE 来调试,就要麻烦一些了。下面就跟着笔者来看看使用 IntelliJ IDEA 实现远程 Debug 的步骤吧。
-
-
-
-::: tip 笔者说
-由于笔者安装了 [Chinese(Simplified)Language Pack / 中文语言包] 插件,所以下方步骤的 IntelliJ IDEA 界面都是中文的,各位同学如果用的是默认语言包,那就参照着看吧。
-:::
-
-## 新增远程调试配置
-
-点开运行程序下拉菜单,点击 [编辑配置...]。
-
-
-
-在打开的 [运行/调试配置] 窗口,点击左上角 [+] 号,随后在弹出的 [添加新配置] 下拉菜单中,下拉找到 [远程 JVM 调试],点击即可添加远程调试配置。
-
-
-
-按照下图序号顺序,依次设置好 [名称]、[主机]、[端口]、[JDK 版本],然后先复制一下下图红框中的 [远程 JVM 的命令行实参],点击 [确定] 完成远程调试配置添加。
-
-::: warning 笔者说
-这里主要就注意一下主机和端口两个配置、主机是你要远程调试的程序所在服务器的 IP/域名,但端口可不是你要远程调试的程序所占用的端口。这个端口是远程调试端口,也不能和程序端口相同。
-:::
-
-
-
-## 修改启动命令
-
-添加完配置之后,就立刻能开始调试吗?当然不是了,很简单的问题,如果我们只需要在 IntelliJ IDEA 中配置两下就能直接连接程序调试,那 Java 程序的安全性也太低了吧。
-
-刚才笔者让你复制的 [远程 JVM 的命令行实参],是 IntelliJ IDEA 基于我们刚才的配置帮我们生成的,我们需要将这个 JVM 命令行参数加到你要远程调试的程序启动命令中。
-
-例如:
-
-```shell
-# -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
-java -jar -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 你的程序名.jar --spring.profiles.active=test
-```
-
-好了,还差最后一步,把你配置的远程调试端口放开。
-
-## 启动远程调试
-
-回到 IDE 中,点击 [Debug 运行] 按钮,如果控制台窗口出现下方提示,那么接下来该怎么加断点,该怎么触发断点,该怎么进行调试就不用笔者过多介绍了吧?
-
-```shell
-# Connected to the target VM, address: ''xxx:5005', transport: 'socket''
-已连接到目标 VM,地址:''xxx:5005', 传输: '套接字''
-```
-
-
-
-远程调试完后别忘了把远程调试端口关闭,该删除安全组规则就删除。
-
-::: tip 笔者说
-哦对了,笔者这 “匪夷所思” 的问题到底解决了没有呢?当然解决了,而且这个问题不是代码的问题,而是一位远程办公的同事在他本地连接了测试环境数据库(这在笔者项目开发时强调过不允许连接),他的程序一直运行着把测试环境的一些任务数据处理了。
-
-具体的不多说,因为当你调试时程序运行正常,断开调试再试就出错,而且在你没调试前添加的部分日志埋点也没输出时,其实就应该摒弃所谓的 “不可能”,即:程序没有在你预想的机器上运行。毕竟福尔摩斯有句话:“排除一切不可能的,剩下的即使再不可能,那也是真相”。
-:::
diff --git a/docs/categories/fragments/2022/12/07/网站开启灰色显示.md b/docs/categories/fragments/2022/12/07/网站开启灰色显示.md
deleted file mode 100644
index 700402ede..000000000
--- a/docs/categories/fragments/2022/12/07/网站开启灰色显示.md
+++ /dev/null
@@ -1,126 +0,0 @@
----
-title: 简单聊聊如何让网站开启灰色显示
-author: 查尔斯
-date: 2022/12/07 21:37
-categories:
- - 杂碎逆袭史
-tags:
- - CSS
- - 前端
----
-
-# 简单聊聊如何让网站开启灰色显示
-
-**C:** 在一些特殊的日子里,为了表达哀思和悼念,除禁娱之外,各网站会将首页或者全站在特定时间内以灰色显示。本篇,笔者就来和各位同学简单聊聊,如何让我们的网站以灰色显示。
-
-
-
-
-
-## 源码解析-掘金
-
-::: tip 笔者说
-作为一个技术行业从业者,对技术要时刻保持好奇心。
-:::
-
-笔者虽然是一个后端开发者,但平时在网页或 APP 上遇到一些用心的效果时,也会停下来想想,在方便的情况下也会简单探究一下。既然各网站都开启了灰色显示,想要知道它们怎么实现的,那就随便找几个网站和笔者一起来看看吧。
-
-进入掘金首页,按下 [Fn] + F12,打开开发者工具,切换到 [Elements] 选项卡,毕竟是全局性的效果,所以我们要找的”目标“ 也很明显,在 `` 标签上,有一个叫 `gray-mode` 的类,从字面意思(灰色模式)就可以猜到它的作用。
-
-
-
- `gray-mode` 类的样式代码如下:
-
-```css
-html.gray-mode {
- filter: grayscale(.95);
- -webkit-filter: grayscale(.95); /* webkit */
-}
-```
-
-很简单的代码 `filter: grayscale(.95);`,只需要这一行代码即可,第二行代码是为了保障浏览器兼容性而添加的。
-
-首先简单认识一下 filter 属性。
-
-::: tip Web开发技术/CSS/filter(滤镜)
-CSS 属性 filter 将模糊或颜色偏移等图形效果应用于元素。滤镜通常用于调整图像、背景和边框的渲染。[1]
-:::
-
-然后我们再来认识一下 `grayscale()` 函数。
-
-::: tip Web开发技术/CSS/filter/函数:grayscale()
-使用 CSS 滤镜属性时,你需要设定下面某一函数的值。如果该值无效,函数返回 none。除特殊说明外,函数的值如果接受百分比值(如 34%),那么该函数也接受小数值(如 0.34)。
-
-`grayscale(amount)` 函数将改变输入图像灰度。amount 的值定义了转换的比例。值为 100% 则完全转为灰度图像,值为 0% 图像无变化。值在 0% 到 100% 之间,则是效果的线性乘数。若未设置值,默认是 0。[1]
-:::
-
-介绍的已经很直白了,我们再简单来说一下,`filter: grayscale(.95);` 这行代码的作用就是将网页中所有的元素转换为灰色显示,`.95` 就是 `95%` 的灰度,没有完全转为灰色也是为了护眼,增强一些用户体验。
-
-## 源码分析-B站
-
-我们再来看一个网站:B站,同样开启了灰色显示。略有不同的是 B 站的整体感观更舒服一些,没有那么刺眼,一些颜色也能分辨出来。
-
-
-
-那咱们也别啰嗦。
-
-::: tip Linus Torvalds
-Talk is cheap. Show me the code.(废话少说。放码过来。)
-:::
-
-依然很快就找到了“目标”,在 `` 标签上,有一个叫 `gray` 的类。
-
-
-
-`gray` 类的样式代码如下:
-
-```css
-html.gray {
- filter: grayscale(85%) saturate(80%);
- -webkit-filter: grayscale(85%) saturate(80%); /* webkit */
- -moz-filter: grayscale(85%) saturate(80%); /* firefox */
- -ms-filter: grayscale(85%) saturate(80%); /* ie9 */
- -o-filter: grayscale(85%) saturate(80%); /* opera */
- filter: url(data:image/svg+xml;utf8, #grayscale);
- filter: progid:DXImageTransform.Microsoft.BasicImage(grayscale=.85);
- -webkit-filter: grayscale(.85) saturate(.8);
-}
-```
-
-很显然,B 站在处理灰色效果方面也是使用的 `filter: grayscale()` ,相比掘金不同的地方有 3 点:
-
-1. 灰度值设为了 85%,这让网站的刺眼程度更低了,观感也就
-2. 增加了更多浏览器兼容的代码
-3. 增加了 `saturate()` 函数来控制渲染
-
-前两个好理解,我们来看一下增加的第 3 个函数的作用。
-
-::: tip Web开发技术/CSS/filter/函数:saturate()
-`saturate(amount)` 函数转换图像饱和度。amount 的值定义转换的比例。值为 0% 则是完全不饱和,值为 100% 则图像无变化。其他值是效果的线性乘数。超过 100% 则有更高的饱和度。若未设置值,则默认为 1。[1]
-:::
-
-简单来说,这个函数就是调整页面中颜色的丰富程度的。 `filter: saturate(80%);` 这行代码将饱和度降低到了 `80%`,页面的色彩降低了。 `grayscale()` 函数搭配上它,就可以缓解因为灰度转换低而显得色彩偏亮了。
-
-## 动手实现
-
-看了两个源码之后,我们也来尝试一下吧,在网站的 `` 或 `` 标签上,添加下面这么一段样式。
-
-```css
-/* 灰度模式 */
-html {
- filter: grayscale(95%);
- -webkit-filter: grayscale(95%); /* webkit */
- -moz-filter: grayscale(95%); /* firefox */
- -ms-filter: grayscale(95%); /* ie9 */
- -o-filter: grayscale(95%); /* opera */
- filter:progid:DXImageTransform.Microsoft.BasicImage(grayscale=.95);
-}
-```
-
-实际效果如下:
-
-
-
-## 参考资料
-
-1. Web开发技术/CSS/filter:https://developer.mozilla.org/zh-CN/docs/Web/CSS/filter
\ No newline at end of file
diff --git a/docs/categories/fragments/2023/01/06/CodeReview方法论与实践总结.md b/docs/categories/fragments/2023/01/06/CodeReview方法论与实践总结.md
deleted file mode 100644
index 7522cb7ab..000000000
--- a/docs/categories/fragments/2023/01/06/CodeReview方法论与实践总结.md
+++ /dev/null
@@ -1,530 +0,0 @@
----
-title: 阿里巴巴的Code Review方法论与实践总结
-isOriginal: false
-author: 方基成(润甫)
-date: 2023/01/06 20:12
-articleTitle: 一文梳理Code Review方法论与实践总结
-articleLink: https://mp.weixin.qq.com/s/_4MFrQSYOIGYRdDGOJPDKQ
-categories:
- - 杂碎逆袭史
-tags:
- - Code Review
- - 卓越工程文化
----
-
-# 阿里巴巴的Code Review方法论与实践总结
-
-作为卓越工程文化的一部分,Code Review 其实一直在进行中,只是各团队根据自身情况张驰有度,松紧可能也不一,这里简单梳理一下 CR 的方法和团队实践。
-
-
-
-## 为什么要CR
-
-- **提前发现缺陷**
- 在CodeReview阶段发现的逻辑错误、业务理解偏差、性能隐患等时有发生,CR可以提前发现问题。
-- **提高代码质量**
- 主要体现在代码健壮性、设计合理性、代码优雅性等方面,持续CodeReview可以提升团队整体代码质量。
-- **统一规范和风格**
- 集团编码规范自不必说,对于代码风格要不要统一,可能会有不同的看法,个人观点对于风格也不强求。但代码其实不是写给自己看的,是写给下一任看的,就像经常被调侃的“程序员不喜欢写注释,更不喜欢别人不写注释”,代码风格的统一更有助于代码的可读性及继任者的快速上手。
-- **防止架构腐烂**
- 架构的维护者是谁?仅靠架构师或应用Owner是远远不够的,需要所有成员的努力,所谓人人都是架构师。架构防腐最好前置在设计阶段,但CodeReview作为对最终产出代码的检查,也算是最后一道关键工序。
-- **知识分享**
- 每一次CodeReview,都是一次知识的分享,磨合一定时间后,团队成员间会你中有我、我中有你,集百家之所长,融百家之所思。同时,业务逻辑都在代码中,团队CodeReview也是一种新人业务细节学习的途径。
-- **团队共识**
- 通过多次讨论与交流,逐步达成团队共识,特别是对架构理解和设计原则的认知,在共识的基础上团队也会更有凝聚力,特别是在较多新人加入时尤为重要。
-
-## 他山之石
-
-### 某大厂A
-
-非常重视 Code Review,基本上代码需要至少有两位以上 Reviewer 审核通过后,才会让你 Check In。
-
-#### 代码评审规则
-
-- 如果变更达到可以提升系统整体代码质量的程度,就可以让它们通过,即使它们可能还不完美。这是所有代码评审准则的最高原则。
-- 世界上没有“完美”的代码,只有更好的代码。**评审者不应该要求代码提交者在每个细节都写得很完美。评审者应该做好修改时间与修改重要性之间的权衡。**
-
-#### 代码评审原则
-
-- 以客观的技术因素与数据为准,而非个人偏好。
-- 在代码样式上,遵从代码样式指南,所有代码都应与其保持一致,任何与代码样式指南不一致的观点都是个人偏好。但如果某项代码样式在指南中未提及,那就接受作者的样式。
-- 任务涉及软件设计的问题,都应取决于基本设计原则,而不应由个人喜好来决定。当同时有多种可行方案时,如果作者能证明(以数据或公认的软件工程原理为依据)这些方案基本差不多,那就接受作者的选项;否则,应由标准的软件设计原则为准。
-- 如果没有可用的规则,那么审核者应该让作者与当前代码库保持一致,至少不会恶化代码系统的质量。(一旦恶化代码质量,就会带来**破窗效应**,导致系统的代码质量逐渐下降)
-
-#### 代码审核者应该看什么
-
-- **设计**:代码是否设计良好?这种设计是否适合当前系统?
-- **功能**:代码实现的行为与作者的期望是否相符?代码实现的交互界面是否对用户友好?
-- **复杂性**:代码可以更简单吗?如果将来有其他开发者使用这段代码,他能很快理解吗?
-- **测试**:这段代码是否有正确的、设计良好的自动化测试?
-- **命名**:在为变量、类名、方法等命名时,开发者使用的名称是否清晰易懂?
-- **注释**:所有的注释是否都一目了然?
-- **代码样式**:所有的代码是否都遵循代码样式?
-- **文档**:开发者是否同时更新了相关文档?
-
-### 某大厂B
-
-- 在开发流程上专门有这个环节,排期会明确排进日程,比如 5 天开发会排 2 天来做代码审核,分为代码自审、交叉审核、集中审核。
-- 有明确的量化指标,如 8 人时审核/每千行代码,8 个以上非提示性有效问题/每千行代码。
-
-### 某大厂C
-
-- 推行 Code Owner 机制,每个代码变更必须有 Code Owner 审核通过才可以提交。
-- 所有的一线工程师,无论职级高低,最重要的工程输出原则是 “show me the code”,而 Code Review 是最能够反应这个客观输出的。
-- 尽量让每个人的 Code Review 参与状况都公开透明,每个变更发送给项目合作者,及转发到小组内成员,小组内任何人都可以去 Review 其他人的代码。
-- 明确每个人的考评和 Code Review 表现相关,包括 Code Review 输出状况及提交代码的质量等。
-
-## 我们怎么做CR
-
-### 作为代码提交者
-
-- **发起时机**:发起 Code Review 尽量提前,开发过程小步快跑
-
- 
-
-- **代码行数**:提交 Code Review 的代码行数最好在 400 行以下。根据数据分析发现,从代码行数来看,超过 400 行的 CR,缺陷发现率会急剧下降;从CR速度来看,超过 500 行/小时后,Review 质量也会大大降低,一个高质量的 CR 最好控制在一个小时以内。
-
-- **明确意图**:编写语义明确的**标题**(必填)和**描述**(选填,可以包括背景、思路、改造点和影响面、风险等)
-
-- **善用工具**:IDEA 打开编码规约实时检测,减少代码样式、编码规约等基础性问题
- (阿里编码规约插件:https://github.com/alibaba/p3c/tree/master/idea-plugin)
-
-### 作为代码评审者
-
-#### 评审范围
-
-主要从两方面来评审:
-
-- 代码逻辑
- - **功能完整**:代码实现是否满足功能需求,实现上有没有需求的理解偏差,对用户是否友好;
- - **逻辑设计**:是否考虑了全局设计和兼容现有业务细节,是否考虑边界条件和并发控制;
- - **安全隐患**:是否存在数据安全隐患及敏感信息泄漏,如越权、SQL 注入、CSRF、敏感信息未脱敏等;
- - **性能隐患**:是否存在损害性能的隐患,如死锁、死循环、FullGC、慢 SQL、缓存数据热点等;
- - **测试用例**:单元测试用例的验证逻辑是否有效,测试用例的代码行覆盖率和分支覆盖率;
-- 代码质量
- - **编码规范**:命名、注释、领域术语、架构分层、日志打印、代码样式等是否符合规范
- - **可读性**:是否逻辑清晰、易理解,避免使用奇淫巧技,避免过度拆分
- - **简洁性**:是否有重复可简化的复杂逻辑,代码复杂度是否过高,符合 KISS 和 DRY 原则
- - **可维护性**:在可读性和简洁性基础上,是否分层清晰、模块化合理、高内聚低耦合、遵从基本设计原则
- - **可扩展性**:是否仅仅是满足一次性需求的代码,是否有必要的前瞻性扩展设计
- - **可测试性**:代码是否方便写单元测试及分支覆盖,是否便于自动化测试
-
-
-#### 评审注意事项
-
-- 尽快完成评审
-- 避免过度追求完美
-- 明确评论是否要解决
-- 避免使用反问句来评价
-
-我们主要是通过交叉 CR、集中 CR 相结合的方式,由应用 Owner + SM + 架构师 + TL 完成。
-
-## CR怎么避免流于形式
-
-CR 流于形式的因素很多,大概如下:
-
-- **不认同 CodeReview**
- - 评审者的姿态?有没有带来好处?有没有从中收获?这些都会直观影响团队成员的认可度
- - 每个 Review 建议的提出都是一次思想交流,评论要友好、中肯、具体,避免教条式及负面词汇,在遵守评审原则下,同时尊重个性展现
- - 团队集中 CodeReview 尽量不要太正式和严肃,轻松的气氛下更有助于互相理解,来点水果,聊聊业务聊聊代码
- - 在 Review 过程有时候会陷入谁对谁错的争论,只要是为了寻求真理辩证的去看问题,哪怕是讨论再激烈也是有收获的,注意只对事不对人。
-- **CodeReview 后改动太大**
- - 发布前发现问题多,改动太大,影响项目计划
- - 大项目要求编码前设计评审,小需求可以事先 Review 设计思路,避免最后的惊喜
- - 每次 Review 的代码行数最好控制在数百行以内
-- **评审者没有足够时间**
- - 评审者在任务安排上尽量预留好时间
- - 尽快评审,代码在百行以内及时响应,在千行以内当日完结
-- **评审者不了解业务和代码**
- - 代码提交人编写清晰的标题和描述
- - 有必要的情况下评审者需要了解 PRD
- - 评审者需要提前了解系统和代码
-
-- **Review 建议未修改**
- - 这一点极为重要,需要对修改后的代码再次 Review,确保理解一致,以及预防带问题上线
- - 应用可以设置 Review 建议需全部解决的卡点,同时对于非必需修改的建议可以进行打标或说明
-
-
-## CR实践中发现的几个常见代码问题
-
-笔者对个人 CR 评论问题做了个大概统计,Bug 发现数占比约 4%(直接或潜在 Bug),重复代码数占比约 5%,其他还有规范、安全、性能、设计等问题。在 CR 代码质量时,可以参考《重构:改善既有代码的设计》,书中所列的 22 种坏味道在 CR 中基本都会遇到。而此处我们主要聚焦以下几个常见问题:
-
-### DRY
-
-DRY 是 Don't Repeat Yourself 的缩写,DRY 是 Andy Hunt 和 Dave Thomas's 在《 The Pragmatic Programmer 》一书中提出的核心原则。DRY 原则描述的重复是知识和意图的重复,包含代码重复、文档重复、数据重复、表征重复,我们这里重点讲讲**代码重复**。
-
-#### 代码重复
-
-> 《重构》中对 “Duplicated Code(重复代码)” 的描述:
->
-> 坏味道行列中首当其冲的就是 Duplicated Code。如果你在一个以上的地点看到相同的程序结构,那么可以肯定:设法将它们合而为一,程序会变得更好。
->
->
-> 最单纯的 Duplicated Code 就是“同一个类的两个函数含有相同的表达式”。这时候你需要做的就是采用 Extract Method (110) 提炼出重复的代码,然后让这两个地点都调用被提炼出来的那一段代码。
->
->
-> 另一种常见情况就是“两个互为兄弟的子类内含相同表达式”。要避免这种情况,只需对两个类都使用 Extract Method (110),然后再对被提炼出来的代码使用 Pull Up Method (332),将它推入超类内。如果代码之间只是类似,并非完全相同,那么就得运用 Extract Method (110) 将相似部分和差异部分割开,构成单独一个函数。然后你可能发现可以运用 Form Template Method (345) 获得一个Template Method 设计模式。如果有些函数以不同的算法做相同的事,你可以选择其中较清晰的一个,并使用 Substitute Algorithm (139) 将其他函数的算法替换掉。
->
->
-> 如果两个毫不相关的类出现 Duplicated Code,你应该考虑对其中一个使用 Extract Class (149),将重复代码提炼到一个独立类中,然后在另一个类内使用这个新类。但是,重复代码所在的函数也可能的确只应该属于某个类,另一个类只能调用它,抑或这个函数可能属于第三个类,而另两个类应该引用这第三个类。你必须决定这个函数放在哪儿最合适,并确保它被安置后就不会再在其他任何地方出现。
-
-代码重复的几种场景:
-
-- 一个类中重复代码抽象为一个方法
-- 两个子类间重复代码抽象到父类
-- 两个不相关类间重复代码抽象到第三个类
-
-::: code-group
-``` java [反例]
-private BillVO convertBillDTO2BillVO(BillDTO billDTO) {
- if (billDTO == null) {
- return null;
- }
- BillVO billVO = new BillVO();
- Money cost = billDTO.getCost();
- if (cost != null && cost.getAmount() != null) {
- billVO.setCostDisplayText(String.format("%s %s", cost.getCurrency(), cost.getAmount()));
- }
- Money sale = billDTO.getSale();
- if (sale != null && sale.getAmount() != null) {
- billVO.setSaleDisplayText(String.format("%s %s", sale.getCurrency(), sale.getAmount()));
- }
- Money grossProfit = billDTO.getGrossProfit();
- if (grossProfit != null && grossProfit.getAmount() != null) {
- billVO.setGrossProfitDisplayText(String.format("%s %s", grossProfit.getCurrency(), grossProfit.getAmount()));
- }
- return billVO;
-}
-```
-
-``` java [正例]
-private static final String MONEY_DISPLAY_TEXT_PATTERN = "%s %s";
-
-private BillVO convertBillDTO2BillVO(BillDTO billDTO) {
- if (billDTO == null) {
- return null;
- }
- BillVO billVO = new BillVO();
- billVO.setCostDisplayText(buildMoneyDisplayText(billDTO.getCost()));
- billVO.setSaleDisplayText(buildMoneyDisplayText(billDTO.getSale()));
- billVO.setGrossProfitDisplayText(buildMoneyDisplayText(billDTO.getGrossProfit()));
- return billVO;
-}
-
-private String buildMoneyDisplayText(Money money) {
- if (money == null || money.getAmount() == null) {
- return StringUtils.EMPTY;
- }
- return String.format(MONEY_DISPLAY_TEXT_PATTERN, money.getCurrency(), money.getAmount().toPlainString());
-}
-```
-:::
-
-#### DYR实践忠告
-
-- 不要借用 DRY 之名,过度提前抽象,请遵循 **Rule of three 原则**。
-- 不要过度追求 DRY,破坏了内聚性,实践中需要**平衡复用与内聚**。
-
-### Primitive Obsession
-
-> 《重构》中对 “Primitive Obsession(基本类型偏执)” 的描述:
->
-> 大多数编程环境都有两种数据:结构类型允许你将数据组织成有意义的形式;基本类型则是构成结构类型的积木块。结构总是会带来一定的额外开销。它们可能代表着数据库中的表,如果只为做一两件事而创建结构类型也可能显得太麻烦。
->
->
-> 对象的一个极大的价值在于:它们模糊(甚至打破)了横亘于基本数据和体积较大的类之间的界限。你可以轻松编写出一些与语言内置(基本)类型无异的小型类。例如,Java 就以基本类型表示数值,而以类表示字符串和日期——这两个类型在其他许多编程环境中都以基本类型表现。
->
->
-> 对象技术的新手通常不愿意在小任务上运用小对象——像是结合数值和币种的 money 类、由一个起始值和一个结束值组成的 range 类、电话号码或邮政编码(ZIP)等的特殊字符串。你可以运用 Replace Data Valuewith Object (175) 将原本单独存在的数据值替换为对象,从而走出传统的洞窟,进入炙手可热的对象世界。如果想要替换的数据值是类型码,而它并不影响行为,则可以运用 Replace Type Code with Class (218) 将它换掉。如果你有与类型码相关的条件表达式,可运用 Replace Type Codewith Subclass (213) 或 Replace Type Code with State/Strategy (227) 加以处理。
->
->
-> 如果你有一组应该总是被放在一起的字段,可运用 Extract Class(149)。如果你在参数列中看到基本型数据,不妨试试 IntroduceParameter Object (295)。如果你发现自己正从数组中挑选数据,可运用 Replace Array with Object (186)。
-
-给我们的启示主要有两点:
-
-- 大部分业务场景和语言环境下,结构化类型导致的开销基本可以忽略
-- 结构化类型带来更清晰的语义和复用
-
-::: code-group
-``` java [反例]
-@Data
-public class XxxConfigDTO implements Serializable {
-
- private static final long serialVersionUID = 8018480763009740953L;
-
- /**
- * 租户ID
- */
- private Long tenantId;
- /**
- * 工商税务企业类型
- */
- private String companyType;
- /**
- * 企业名称
- */
- private String companyName;
- /**
- * 企业纳税人识别号
- */
- private String companyTaxNo;
- /**
- * 审单员工工号
- */
- private String auditEmpNo;
- /**
- * 审单员工姓名
- */
- private String auditEmpName;
- /**
- * 跟单员工工号
- */
- private String trackEmpNo;
- /**
- * 跟单员工姓名
- */
- private String trackEmpName;
-}
-```
-
-``` java [正例]
-@Data
-public class XxxConfigDTO2 implements Serializable {
-
- private static final long serialVersionUID = 8018480763009740953L;
-
- /**
- * 租户ID
- */
- private Long tenantId;
- /**
- * 企业信息
- */
- private Company company;
- /**
- * 审单员工信息
- */
- private Employee auditEmployee;
- /**
- * 跟单员工信息
- */
- private Employee trackEmployee;
-}
-
-@Data
-public class Company {
- /**
- * 工商税务企业类型
- */
- private String companyType;
- /**
- * 企业名称
- */
- private String companyName;
- /**
- * 企业纳税人识别号
- */
- private String companyTaxNo;
-}
-
-@Data
-public class Employee {
- /**
- * 员工工号
- */
- private String empNo;
- /**
- * 员工姓名
- */
- private String empName;
-}
-```
-:::
-
-其实就是怎么去抽象,对于特定领域的对象可以参考 DDD 里面的 Domain Primitive(DP)。
-
-### 分布式锁
-
-#### 未处理锁失败
-
-```java
-private void process(String orderId) {
- // do validate
- try {
- boolean lockSuccess = lockService.tryLock(LockBizType.ORDER, orderId);
- if (!lockSuccess) {
- // TODO 此处需要处理锁失败,重试或抛出异常
- return;
- }
- // do something
- } finally {
- lockService.unlock(LockBizType.ORDER, orderId);
- }
-}
-```
-
-分布式锁的目的是为了防止并发冲突和保证数据一致性,锁失败时未处理直接返回,会带来非预期结果的影响,除非明确失败可放弃。
-
-#### 手写解锁容易遗漏
-
-上面的加锁和解锁都是手动编写,而这两个动作一般是成对出现的,在手动编写时容易发生遗漏解锁而导致线上问题,推荐封装一个加解锁的方法来实现,会更加安全和便利。
-
-```java
-
-private void procoess(String orderId) {
- // do validate
- Boolean processSuccess = lockService.executeWithLock(LockBizType.ORDER, orderId, () -> doProcess(orderId));
- // do something
-}
-
-private Boolean doProcess(String orderId) {
- // do something
- return Boolean.TRUE;
-}
-
-// LockService
-public T executeWithLock(LockBizType bizType, String bizId, Supplier supplier) {
- return executeWithLock(bizType, bizId, 60, 3, supplier);
-}
-
-public T execteWithLock(LockBizType bizType, String bizId, int expireSeconds, int retryTimes, Supplier supplier) {
- // 尝试加锁
- int lockTimes = 1;
- boolean lock = tryLock(bizType, bizId, expireSeconds);
- while(lockTimes < retryTimes && !lock) {
- try {
- Thread.sleep(10);
- } catch (Exception e) {
- // do something
- }
- lock = tryLock(bizType, bizId, expireSeconds);
- lockTimes++;
- }
- // 锁失败抛异常
- if (!lock) {
- throw new LockException("try lock fail");
- }
- // 解锁
- try {
- return supplier.get();
- } finally {
- unlock(bizType, bizId);
- }
-}
-```
-
-#### 加锁KEY无效
-
-```java
-
-private void process(String orderId) {
- // do validate
- try {
- // 此处加锁类型与加锁KEY不匹配
- boolean lockSuccess = lockService.tryLock(LockBizType.PRODUCT, orderId);
- if (!lockSuccess) {
- // TODO 重试或抛出异常
- return;
- }
- // do something
- } finally {
- lockService.unlock(LockBizType.PRODUCT, orderId);
- }
-}
-```
-
-注意加锁类型与加锁 KEY 在同一个维度,否则加锁会失效。
-
-### 分页查询
-
-#### 完全没有分页
-
-::: code-group
-``` java [反例]
-private List queryOrderList(Long customerId) {
- if (customerId == null) {
- return Lists.newArrayList();
- }
-
- List orderDOList = orderMapper.list(customerId);
- return orderConverter.doList2dtoList(orderDOList);
-}
-```
-
-``` java [正例]
-private Page queryOrderList(OrderPageQuery query) {
- Preconditions.checkNotNull(query, "查询条件不能为空");
- Preconditions.checkArgument(query.getPageSize() <= MAX_PAGE_SIZE, "分页size不能大于" + MAX_PAGE_SIZE);
- // 分页size一般由前端传入
- // query.setPageSize(20);
- long cnt = orderMapper.count(query);
- if (cnt == 0) {
- return PageQueryUtil.buildPageData(query, null, cnt);
- }
- List orderDOList = orderMapper.list(query);
- List orderDTOList = orderConverter.doList2dtoList(orderDOList);
- return PageQueryUtil.buildPageData(query, orderDTOList, cnt);
-}
-```
-:::
-
-没有分页的列表查询对DB性能影响非常大,特别是在项目初期,因为数据量非常小问题不明显,而导致没有及时发现,会给未来留坑。
-
-#### 分页size太大
-
-::: code-group
-``` java [反例]
-private Page queryOrderList2(OrderPageQuery query) {
- Preconditions.checkNotNull(query, "查询条件不能为空");
- query.setPageSize(10000);
- long cnt = orderMapper.count(query);
- if (cnt == 0) {
- return PageQueryUtil.buildPageData(query, null, cnt);
- }
- List orderDOList = orderMapper.list(query);
- List orderDTOList = orderConverter.doList2dtoList(orderDOList);
- return PageQueryUtil.buildPageData(query, orderDTOList, cnt);
-}
-```
-:::
-
-分页 size 的大小并没有一个固定的标准,取决于业务需求、数据量及数据库等,但动辄几千上万的分页 size,会带来性能瓶颈,而大量的慢 SQL 不但影响客户体验,对系统稳定性也是极大的隐患。
-
-#### 超多分页慢SQL
-
-::: code-group
-``` xml [反例]
-
-
- SELECT
-
- FROM t_order
-
- ORDER BY id DESC
- LIMIT #{offset},#{pageSize}
-
-```
-
-``` xml [正例]
-
-
- SELECT
-
- FROM t_order a
- INNER JOIN (
- SELECT id AS bid
- FROM t_order
-
- ORDER BY id DESC
- LIMIT #{offset},#{pageSize}
- ) b ON a.id = b.bid
-
-```
-:::
-
-以上 bad case 的 SQL 在超多页分页查询时性能极其低下,存在多次回表甚至 Using Filesort 的问题,在阿里巴巴编码规范中也有明确的规避方案,此处不展开。
-
-
-
-最后,我们工程师的智慧结晶都尽在代码之中,而 Code Review 可以促进结晶更加清莹通透、纯洁无瑕、精致完美,值得大家一起持续精进!
-
diff --git a/docs/categories/fragments/2023/12/21/一文详解限流接口实现.md b/docs/categories/fragments/2023/12/21/一文详解限流接口实现.md
deleted file mode 100644
index fc30caa6e..000000000
--- a/docs/categories/fragments/2023/12/21/一文详解限流接口实现.md
+++ /dev/null
@@ -1,477 +0,0 @@
----
-title: 一文详解限流接口实现
-isOriginal: false
-author: 非有
-date: 2023/12/21 22:25
-articleTitle: 一文详解 Java 限流接口实现
-articleLink: https://mp.weixin.qq.com/s/A5VYjstIDeVvizNK2HkrTQ
-categories:
- - 杂碎逆袭史
-tags:
- - Java
- - 限流
----
-
-# 一文详解限流接口实现
-
-本文介绍的实现方式属于应用级限制,应用级限流方式只是单应用内的请求限流,不能进行全局限流。要保证系统的抗压能力,限流是一个必不可少的环节,虽然可能会造成某些用户的请求被丢弃,但相比于突发流量造成的系统宕机来说,这些损失一般都在可以接受的范围之内。。
-
-
-
-## 前言
-
-### 为什么要进行限流?
-
-- 瞬时流量过高,服务被压垮?
-
-- 恶意用户高频光顾,导致服务器宕机?
-
-- 消息消费过快,导致数据库压力过大,性能下降甚至崩溃?
-
- ……
-
-### 什么是限流?
-
-::: tip 什么是限流
-限流是对某一时间窗口内的请求数进行限制,保持系统的可用性和稳定性,防止因流量暴增而导致的系统运行缓慢或宕机。
-:::
-
-在高并发系统中,出于系统保护角度考虑,通常会对流量进行限流。在分布式系统中,高并发场景下,为了防止系统因突然的流量激增而导致的崩溃,同时保证服务的高可用性和稳定性,限流是最常用的手段。
-
-### 有哪些限流算法?
-
-常见的四种限流算法,分别是:固定窗口算法、滑动窗口算法、漏桶算法、令牌桶算法。
-
-## 限流算法
-
-### 固定窗口
-
-固定窗口又称固定窗口(又称计数器算法,Fixed Window)限流算法,是最简单的限流算法。
-
-#### 实现原理
-
-在指定周期内累加访问次数,当访问次数达到设定的阈值时,触发限流策略,当进入下一个时间周期时进行访问次数的清零。如图所示,我们要求3秒内的请求不要超过150次:
-
-
-
-#### 代码实现
-
-```java
-public class FixedWindowRateLimiter {
- Logger logger = LoggerFactory.getLogger(FixedWindowRateLimiter.class);
- // 时间窗口大小,单位毫秒
- long windowSize;
- // 允许通过的请求数
- int maxRequestCount;
- // 当前窗口通过的请求数
- AtomicInteger counter = new AtomicInteger(0);
- // 窗口右边界
- long windowBorder;
- public FixedWindowRateLimiter(long windowSize, int maxRequestCount) {
- this.windowSize = windowSize;
- this.maxRequestCount = maxRequestCount;
- this.windowBorder = System.currentTimeMillis() + windowSize;
- }
- public synchronized boolean tryAcquire() {
- long currentTime = System.currentTimeMillis();
- if (windowBorder < currentTime) {
- logger.info("window reset");
- do {
- windowBorder += windowSize;
- } while (windowBorder < currentTime);
- counter = new AtomicInteger(0);
- }
-
- if (counter.intValue() < maxRequestCount) {
- counter.incrementAndGet();
- logger.info("tryAcquire success");
- return true;
- } else {
- logger.info("tryAcquire fail");
- return false;
- }
- }
-}
-```
-
-#### 优缺点
-
-**优点:** 实现简单,容易理解
-
-**缺点:**
-
-1. 限流不够平滑。例如:限流是每秒3个,在第一毫秒发送了3个请求,达到限流,窗口剩余时间的请求都将会被拒绝,体验不好。
-
-2. 无法处理窗口边界问题。因为是在某个时间窗口内进行流量控制,所以可能会出现窗口边界效应,即在时间窗口的边界处可能会有大量的请求被允许通过,从而导致突发流量。即:如果第2到3秒内产生了150次请求,而第3到4秒内产生了150次请求,那么其实在第2秒到第4秒这两秒内,就已经发生了300次请求了,远远大于我们要求的3秒内的请求不要超过150次这个限制,如下图所示:
-
- 
-
-### 滑动窗口
-
-滑动窗口为固定窗口的改良版,解决了固定窗口在窗口切换时会受到两倍于阈值数量的请求。在滑动窗口算法中,窗口的起止时间是动态的,窗口的大小固定。这种算法能够较好地处理窗口边界问题,但是实现相对复杂,需要记录每个请求的时间戳。
-
-#### 实现原理
-
-滑动窗口在固定窗口的基础上,将时间窗口进行了更精细的分片,将一个窗口分为若干个等份的小窗口,每次仅滑动一小块的时间。每个小窗口对应不同的时间点,拥有独立的计数器,当请求的时间点大于当前窗口的最大时间点时,则将窗口向前平移一个小窗口(将第一个小窗口的数据舍弃,第二个小窗口变成第一个小窗口,当前请求放在最后一个小窗口),整个窗口的所有请求数相加不能大于阈值。其中,Sentinel 就是采用滑动窗口算法来实现限流的。如图所示:
-
-
-
-**核心步骤:**
-
-1. 把3秒钟划分为3个小窗,每个小窗限制请求不能超过50秒。
-2. 比如我们设置,3秒内不能超过150个请求,那么这个窗口就可以容纳3个小窗,并且随着时间推移,往前滑动。每次请求过来后,都要统计滑动窗口内所有小窗的请求总量。
-
-#### 代码实现
-
-```java
-
-public class SlidingWindowRateLimiter {
- Logger logger = LoggerFactory.getLogger(FixedWindowRateLimiter.class);
- // 时间窗口大小,单位毫秒
- long windowSize;
- // 分片窗口数
- int shardNum;
- // 允许通过的请求数
- int maxRequestCount;
- // 各个窗口内请求计数
- int[] shardRequestCount;
- // 请求总数
- int totalCount;
- // 当前窗口下标
- int shardId;
- // 每个小窗口大小,毫秒
- long tinyWindowSize;
- // 窗口右边界
- long windowBorder;
-
- public SlidingWindowRateLimiter(long windowSize, int shardNum, int maxRequestCount) {
- this.windowSize = windowSize;
- this.shardNum = shardNum;
- this.maxRequestCount = maxRequestCount;
- this.shardRequestCount = new int[shardNum];
- this.tinyWindowSize = windowSize / shardNum;
- this.windowBorder = System.currentTimeMillis();
- }
- public synchronized boolean tryAcquire() {
- long currentTime = System.currentTimeMillis();
- if (windowBorder < currentTime) {
- logger.info("window reset");
- do {
- shardId = (++shardId) % shardNum;
- totalCount -= shardRequestCount[shardId];
- shardRequestCount[shardId] = 0;
- windowBorder += tinyWindowSize;
- } while (windowBorder < currentTime);
- }
-
- if (totalCount < maxRequestCount) {
- logger.info("tryAcquire success:{}", shardId);
- shardRequestCount[shardId]++;
- totalCount++;
- return true;
- } else {
- logger.info("tryAcquire fail");
- return false;
- }
- }
-}
-```
-
-#### 优缺点
-
-**优点:** 解决了固定窗口算法的窗口边界问题,避免突发流量压垮服务器。
-**缺点:** 还是存在限流不够平滑的问题。例如:限流是每秒3个,在第一毫秒发送了3个请求,达到限流,剩余窗口时间的请求都将会被拒绝,体验不好。
-
-### 漏桶算法
-
-漏桶限流算法是一种常用的流量整形(Traffic Shaping)和流量控制(Traffic Policing)的算法,它可以有效地控制数据的传输速率以及防止网络拥塞。
-
-**主要的作用:**
-
-1. 控制数据注入网络的速度。
-2. 平滑网络上的突发流量
-
-#### 实现原理
-
-漏桶是一个很形象的比喻,外部请求就像是水一样不断注入水桶中,而水桶已经设置好了最大出水速率,漏桶会以这个速率匀速放行请求,而当水超过桶的最大容量后则被丢弃。不管上面的水流速度有多块,漏桶水滴的流出速度始终保持不变。消息中间件就采用的漏桶限流的思想。如图所示:
-
-
-
-**核心步骤:**
-
-1. 一个固定容量的漏桶,按照固定速率出水(处理请求);
-2. 当流入水(请求数量)的速度过大会直接溢出(请求数量超过限制则直接拒绝)。
-3. 桶里的水(请求)不够则无法出水(桶内没有请求则不处理)。
-
-#### 代码实现
-
-```java
-public class LeakyBucketRateLimiter {
- Logger logger = LoggerFactory.getLogger(LeakyBucketRateLimiter.class);
- // 桶的容量
- int capacity;
- // 桶中现存水量
- AtomicInteger water = new AtomicInteger();
- // 开始漏水时间
- long leakTimestamp;
- // 水流出的速率,即每秒允许通过的请求数
- int leakRate;
-
- public LeakyBucketRateLimiter(int capacity, int leakRate) {
- this.capacity = capacity;
- this.leakRate = leakRate;
- }
-
- public synchronized boolean tryAcquire() {
- // 桶中没有水, 重新开始计算
- if (water.get() == 0) {
- logger.info("start leaking");
- leakTimestamp = System.currentTimeMillis();
- water.incrementAndGet();
- return water.get() < capacity;
- }
- // 先漏水,计算剩余水量
- long currentTime = System.currentTimeMillis();
- int leakedWater = (int) ((currentTime - leakTimestamp) / 1000 * leakRate);
- logger.info("lastTime:{}, currentTime:{}. LeakedWater:{}", leakTimestamp, currentTime, leakedWater);
- // 可能时间不足,则先不漏水
- if (leakedWater != 0) {
- int leftWater = water.get() - leakedWater;
- // 可能水已漏光。设为0
- water.set(Math.max(0, leftWater));
- leakTimestamp = System.currentTimeMillis();
- }
- logger.info("剩余容量:{}", capacity - water.get());
- if (water.get() < capacity) {
- logger.info("tryAcquire sucess");
- water.incrementAndGet();
- return true;
- } else {
- logger.info("tryAcquire fail");
- return false;
- }
- }
-}
-```
-
-#### 优缺点
-
-**优点:**
-
-1. 平滑流量。由于漏桶算法以固定的速率处理请求,可以有效地平滑和整形流量,避免流量的突发和波动(类似于消息队列的削峰填谷的作用)。
-2. 防止过载。当流入的请求超过桶的容量时,可以直接丢弃请求,防止系统过载。
-
-**缺点:**
-
-1. 无法处理突发流量:由于漏桶的出口速度是固定的,无法处理突发流量。例如,即使在流量较小的时候,也无法以更快的速度处理请求。
-2. 可能会丢失数据:如果入口流量过大,超过了桶的容量,那么就需要丢弃部分请求。在一些不能接受丢失请求的场景中,这可能是一个问题。
-3. 不适合速率变化大的场景:如果速率变化大,或者需要动态调整速率,那么漏桶算法就无法满足需求。
-4. 资源利用率:不管当前系统的负载压力如何,所有请求都得进行排队,即使此时服务器的负载处于相对空闲的状态,这样会造成系统资源的浪费。
-
-由于漏桶的缺陷比较明显,所以在实际业务场景中,使用的比较少。
-
-### 令牌算法
-
-令牌桶算法是基于漏桶算法的一种改进,主要在于令牌桶算法能够在限制服务调用的平均速率的同时,还能够允许一定程度内的突发调用。
-
-#### 实现原理
-
-1. 系统以固定的速率向桶中添加令牌;
-
-2. 当有请求到来时,会尝试从桶中移除一个令牌,如果桶中有足够的令牌,则请求可以被处理或数据包可以被发送;
-
-3. 如果桶中没有令牌,那么请求将被拒绝;
-
-4. 桶中的令牌数不能超过桶的容量,如果新生成的令牌超过了桶的容量,那么新的令牌会被丢弃。
-
-5. 令牌桶算法的一个重要特性是,它能够应对突发流量。当桶中有足够的令牌时,可以一次性处理多个请求,这对于需要处理突发流量的应用场景非常有用。但是又不会无限制的增加处理速率导致压垮服务器,因为桶内令牌数量是有限制的。
-
-如图所示:
-
-
-
-#### 代码实现
-
-Guava中的RateLimiter就是基于令牌桶实现的,可以直接拿来使用。
-
-#### 优缺点
-
-**优点:**
-
-1. 可以处理突发流量:令牌桶算法可以处理突发流量。当桶满时,能够以最大速度处理请求。这对于需要处理突发流量的应用场景非常有用。
-
-2. 限制平均速率:在长期运行中,数据的传输率会被限制在预定义的平均速率(即生成令牌的速率)。
-3. 灵活性:与漏桶算法相比,令牌桶算法提供了更大的灵活性。例如,可以动态地调整生成令牌的速率。
-
-**缺点:**
-
-1. 可能导致过载:如果令牌产生的速度过快,可能会导致大量的突发流量,这可能会使网络或服务过载。
-
-2. 需要存储空间:令牌桶需要一定的存储空间来保存令牌,可能会导致内存资源的浪费。
-
-3. 实现稍复杂:相比于计数器算法,令牌桶算法的实现稍微复杂一些。
-
-## 应用实践
-
-Guava 中的 RateLimiter 就是基于令牌桶实现的,可以直接拿来使用。所有整个实践是基于Guava的应用。
-
-### 引入依赖
-
-```xml
-
- com.google.guava
- guava
- 32.1.3-jre
-
-```
-
-### API 直接使用
-
-**固定产生令牌:**
-
-```java
-@Test
-public void acquireTest() {
- // 每秒固定生成5个令牌
- RateLimiter rateLimiter = RateLimiter.create(5);
- for (int i = 0; i < 10; i++) {
- double time = rateLimiter.acquire();
- logger.info("等待时间:{}s", time);
- }
-}
-```
-
-
-
-可以看到,每200ms左右产生一个令牌并放行请求,也就是1秒放行5个请求,使用RateLimiter能够很好的实现单机的限流。
-
-**同时产生多个令牌:**
-
-那么再回到我们前面提到的突发流量情况,令牌桶是怎么解决的呢?RateLimiter中引入了一个预消费的概念。
-
-申请令牌的数量不同不会影响这个申请令牌这个动作本身的响应时间,acquire(1)和acquire(1000)这两个请求会消耗同样的时间返回结果,但是会影响下一个请求的响应时间。
-
-如果一个消耗大量令牌的任务到达空闲的RateLimiter,会被立即批准执行,但是当下一个请求进来时,将会额外等待一段时间,用来支付前一个请求的时间成本。
-
-至于为什么要这么做,通过举例来引申一下。当一个系统处于空闲状态时,突然来了1个需要消耗100个令牌的任务,那么白白等待100秒是毫无意义的浪费资源行为,那么可以先允许它执行,并对后续请求进行限流时间上的延长,以此来达到一个应对突发流量的效果。
-
-```java
-@Test
-public void acquireSmoothly() {
- RateLimiter rateLimiter = RateLimiter.create(5, 3, TimeUnit.SECONDS);
- long startTimeStamp = System.currentTimeMillis();
- for (int i = 0; i < 15; i++) {
- double time = rateLimiter.acquire();
- logger.info("等待时间:{}s, 总时间:{}ms", time, System.currentTimeMillis() - startTimeStamp);
- }
-}
-```
-
-
-
-可以看到,令牌发放时间从最开始的500ms多逐渐缩短,在3秒后达到了200ms左右的匀速发放。
-
-总的来说,基于令牌桶实现的RateLimiter功能还是非常强大的,在限流的基础上还可以把请求平均分散在各个时间段内,因此在单机情况下它是使用比较广泛的限流组件。
-
-### AOP 切面
-
-第一步:创建注解
-
-```java
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.METHOD})
-@Documented
-public @interface Limit {
- // 资源主键
- String key() default "";
- // 最多访问次数,代表请求总数量
- double permitsPerSeconds();
- // 时间:即timeout时间内,只允许有permitsPerSeconds个请求总数量访问,超过的将被限制不能访问
- long timeout();
- // 时间类型
- TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
- // 提示信息
- String msg() default "系统繁忙,请稍后重试";
-}
-```
-
-第二步:AOP切面实现
-
-```java
-@Aspect
-@Component
-public class LimitAspect {
- Logger logger = LoggerFactory.getLogger(LimitAspect.class);
- private final Map limitMap = Maps.newConcurrentMap();
-
- @Around("@annotation(com.alibaba.xxx.xxx.annotation.Limit)")
- public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
- MethodSignature signature = (MethodSignature) joinPoint.getSignature();
- Method method = signature.getMethod();
- // 拿limit的注解
- Limit limit = method.getAnnotation(Limit.class);
- if (limit != null) {
- // key作用:不同的接口,不同的流量控制
- String key = limit.key();
- RateLimiter rateLimiter;
- // 验证缓存是否有命中key
- if (!limitMap.containsKey(key)) {
- // 创建令牌桶
- rateLimiter = RateLimiter.create(limit.permitsPerSeconds());
- limitMap.put(key, rateLimiter);
- logger.info("新建了令牌桶={},容量={}", key, limit.permitsPerSeconds());
- }
- rateLimiter = limitMap.get(key);
- // 拿令牌
- boolean acquire = rateLimiter.tryAcquire(limit.timeout(), limit.timeUnit());
- // 拿不到令牌,直接返回异常信息
- if (!acquire) {
- logger.debug("令牌桶={},获取令牌失败", key);
- throw new RuntimeException(limit.msg());
- }
- }
- return joinPoint.proceed();
- }
-}
-```
-
-第三步:应用
-
-```java
-@Limit(key = "query", permitsPerSeconds = 1, timeout = 1, msg = "触发接口限流,请重试")
-```
-
-第四步:使用位置详解
-
-若是放在http的mapping接口上,返回如下:
-
-```json
-{
- "timestamp": "2023-12-07 11:21:47",
- "status": 500,
- "error": "Internal Server Error",
- "path": "/table/query"
-}
-```
-
-若是放在service服务的接口上,返回如下:
-
-```json
-{
- "code": -1,
- "message": "触发接口限流,请重试",
- "data": "fail"
-}
-```
-
-## 总结
-
-本文介绍的实现方式属于应用级限制,应用级限流方式只是单应用内的请求限流,不能进行全局限流。假设将应用部署到多台机器,我们需要分布式限流和接入层限流来解决这个问题。
-
-总的来说,要保证系统的抗压能力,限流是一个必不可少的环节,虽然可能会造成某些用户的请求被丢弃,但相比于突发流量造成的系统宕机来说,这些损失一般都在可以接受的范围之内。前面也说过,限流可以结合熔断、降级一起使用,多管齐下,保证服务的可用性与健壮性。
-
-## 扩展资料
-
-1.源码解析:高性能限流器Guava RateLimiter:https://zhuanlan.zhihu.com/p/358822328?utm_id=0
-
-2.拦截器版:使用Guava实现限流器:https://zhuanlan.zhihu.com/p/38100340
diff --git a/docs/categories/fragments/index.md b/docs/categories/fragments/index.md
deleted file mode 100644
index 33eb71da3..000000000
--- a/docs/categories/fragments/index.md
+++ /dev/null
@@ -1,12 +0,0 @@
----
-showArticleMetadata: false
-editLink: false
-lastUpdated: false
-showComment: false
----
-
-# "杂碎"逆袭史
-
-::: tip 笔者说
-碎片化知识的时代,纵然知晓不成体系的知识仅仅只能积累经验,却无法有效提升,但身在其中还是要如此,记录碎片知识,留作备忘。说不定后面还有机会 “把点连成线”。
-:::
\ No newline at end of file
diff --git a/docs/categories/issues/2021/12/01/F盘上的回收站已损坏。是否清空该驱动器上的回收站.md b/docs/categories/issues/2021/12/01/F盘上的回收站已损坏。是否清空该驱动器上的回收站.md
deleted file mode 100644
index c0bf9222a..000000000
--- a/docs/categories/issues/2021/12/01/F盘上的回收站已损坏。是否清空该驱动器上的回收站.md
+++ /dev/null
@@ -1,130 +0,0 @@
----
-title: F:\ 上的回收站已损坏。是否清空该驱动器上的"回收站"?
-author: 查尔斯
-date: 2021/12/01 22:36
-categories:
- - Bug万象集
-tags:
- - Windows
----
-
-# F:\ 上的回收站已损坏。是否清空该驱动器上的"回收站"?
-
-## 问题描述
-
-近期打开移动硬盘时,经常弹出一个提示框,提示内容是:**F:\ 上的"回收站"已损坏。是否清空该驱动器上的"回收站"?**
-
-
-
-这个提示框出现过好多回了,除了第一次见到时担心数据丢失,所以比较重视,后来次数多了,点过 “是”,也点过 “否”,只要当下不再影响我就先忽略了。
-
-但今天在公司打开时,它还没完没了的弹,我决定给它 "上一课"。
-
-::: tip 笔者说
-
-先说明一下我的这块移动硬盘情况,我把它分了两个区,在我的工作电脑上分别占据 E盘、F盘,E盘 打开没问题,打开 F盘就弹出这个提示。
-
-:::
-
-## 解决方案
-
-### 尝试1:重新插拔移动硬盘
-
-我首先考虑的是不是接触不良类的问题,于是我在电脑上弹出了移动硬盘,然后把硬盘接线也重新插拔了一下,再插到电脑上。
-
-再次打开该盘,依然如此,问题未解决。
-
-### 尝试2:重启电脑
-
-我又考虑是不是电脑本次出了点故障,于是我重启了电脑。
-
-
-
-果然,问题还是没解决了。
-
-::: tip 笔者说
-
-不过,重启的确应该成为解决问题的优先方法,毕竟有这么个说法:"重启可以解决世间 90% 的问题"。巨石强森的电影《摩天营救》里不也是这么演的嘛。很明显,我遇到了剩下那 10% 的问题。
-
-:::
-
-### 尝试3:删除回收站
-
-行吧,再次回归问题本身,它一直提示是回收站损坏,而且,此时我突然想到周末在使用 FreeFileSync 比较 NAS 和硬盘数据时,我看到过这块硬盘中有多一个 recylce 名词的目录(图标也是回收站图标)。
-
-而除了系统回收站,其他硬盘回收站其实对我都无所谓,那就想法找到并干掉它。
-
-然而,即使我开启了隐藏的项目显示,窗口下肉眼仍无法找到它,那就使用 CMD 吧。
-
-以管理员身份打开 CMD,复制下方命令,改成你那提示的错误盘符,回车,再输入 y 确定后删除。
-
-```shell
-# $RECYCLE.BIN 是回收站名称
-# rd 是删除命令
-# /s 代表除目录本身外,还将删除指定目录下的所有子目录和文件,适用于删除目录树。
-# f:\ 根据你自己提示的是哪个盘报错,你就将 f 改成哪个盘
-rd /s f:\$RECYCLE.BIN
-```
-
-
-
-问题解决了。
-
-## 注意事项
-
-**C:** 各位同学,在按照本文进行该项问题修复时,请一定要注意 **红色选框**,确认好你的操作是否与笔者一致,下面列出两个容易被忽略的问题。
-
-### 执行命令的终端
-
-::: tip 笔者说
-这个注意事项是一位同学在 CSDN 上评论遇到的,特别记录一下。
-:::
-
-
-
-笔者是用 CMD 来执行的命令,你如果用了 PowerShell 或其他的终端,需要采用对应的命令语法。
-
-以 CMD 和 PowerShell 中的 `rd` 命令为例,在 CMD 中 `rd` 命令的语法如下:
-
-``` shell
-# [] 代表对应参数可选
-# 一般情况下直接使用 /s /q 组合的形式,表示不需要提示确认,直接对指定目录进行删除(包含子目录)
-# 本文中的命令没加 /q 是本着稳妥的方面,让你确认一次后再删除
-rd [/s] [/q] 目录路径
-rmdir [/s] [/q] 目录路径
-```
-
-
-
-而在 PowerShell 中 `rd` 命令的语法如下:
-
-::: tip 笔者说
-说起来,命令其实差不多,因为是表达相同动作,而且外国人就那么几个单词。就和编程语言里的语法单词也相似一样。
-:::
-
-```shell
-# 下面这些命令都可以,笔者习惯在 PowerShell 中使用 rm
-# -r 是 recurse 的意思,表示递归删除(包含子目录)
-rd [-r] 目录路径
-rm [-r] 目录路径
-rmdir [-r] 目录路径
-ri [-r] 目录路径
-del [-r] 目录路径
-erase [-r] 目录路径
-remove-item [-Recurse] 目录路径
-```
-
-
-
-很显然,当你用 CMD 的 `rd` 命令语法来在 PowerShell 中删除目录时,自然会报错了。
-
-
-
-
-### 以管理员身份运行
-
-一般来说,操作系统提供商提供多用户功能的目的,一方面是隔离数据,另一方面是为了防止小白用户操作错误还要怪在他们头上。
-
-可能不需要管理员身份运行也没问题,但是为了防止部分同学机器的操作系统存在一些 “怪癖”,最好是采用管理员身份来运行,避免无权限之类的问题。
-
-好了,目前就先简单提这两点注意事项,Good luck。
diff --git a/docs/categories/issues/2021/12/08/for循环中删除集合元素隐藏的陷阱.md b/docs/categories/issues/2021/12/08/for循环中删除集合元素隐藏的陷阱.md
deleted file mode 100644
index e59e9c287..000000000
--- a/docs/categories/issues/2021/12/08/for循环中删除集合元素隐藏的陷阱.md
+++ /dev/null
@@ -1,395 +0,0 @@
----
-title: for循环中删除集合元素隐藏的陷阱
-author: 查尔斯
-date: 2021/12/08 20:00
-categories:
- - Bug万象集
-tags:
- - Java集合
----
-
-# for循环中删除集合元素隐藏的陷阱
-
-## 前言
-
-**C:** 今天在审查代码时,发现某位同事提交的代码中有一个比较基础性的错误。
-
-这部分需求的主要目的是将集合中指定的元素删除掉,而这位同事采用的方法是用 for 循环来循环集合索引,然后通过索引从集合中取出每一个元素,判断是否是要删除的元素,如果是就直接删除掉。
-
-**大概意思的代码,如下:**
-
-```java {11,15}
-// 创建集合,并初始化数据
-List list = new ArrayList<>(4);
-list.add(1);
-list.add(2);
-list.add(3);
-list.add(4);
-
-// 删除元素值为 2 的元素
-for (int i = 0; i < list.size(); i++) {
- if (Objects.equals(list.get(i), 2)) {
- list.remove(i);
- }
-}
-
-System.out.println(list); // [1, 3, 4]
-```
-
-笔者知道,肯定有同学会好奇,这结果是正确的啊,哪里有什么问题?的确,这个思路没问题,问题的关键是这位同事采用的循环方式存在问题。
-
-别着急,接下来,笔者就带各位同学好好测试一下。
-
-## 测试代码
-
-### 基础for循环中删除
-
-直接放代码吧,下方是使用基础的 for 循环(循环索引)来实现的集合元素删除,比之 前言 中的代码,无非是要删除的元素 2 有重复,变成了两个。
-
-```java {30,33}
-/**
- * List集合-循环中删除元素-测试
- *
- * @author Charles7c
- * @date 2021/12/8 20:59
- */
-@DisplayName("List集合-循环中删除元素-测试")
-public class ListRemoveEleInForLoopTest {
-
- private List list;
-
- /** 初始化数据 */
- @BeforeEach
- public void init() {
- list = new ArrayList<>(5);
- list.add(1);
- list.add(2);
- list.add(3);
- list.add(4);
- list.add(2);
- }
-
- /** 运行无异常,测试符合预期 */
- @Test
- @DisplayName("基础for循环中删除元素测试")
- void testBasicForLoop() {
- for (int i = 0; i < list.size(); i++) {
- if (Objects.equals(list.get(i), 2)) {
- // IDEA警告:Suspicious 'List.remove()' in the loop
- list.remove(i);
- }
- }
- System.out.println(list); // [1, 3, 4]
- Assertions.assertEquals(list.size(), 3);
- }
-
-}
-```
-
-测试结果也是正常的啊,莫非笔者失手了?别着急 ...
-
-我们再来测试一下,这回我们稍微调整下重复元素的位置,将重复的元素移动到相邻位置。
-
-```java {30,33}
-/**
- * List集合-循环中删除元素-测试
- *
- * @author Charles7c
- * @date 2021/12/8 20:59
- */
-@DisplayName("List集合-循环中删除元素-测试")
-public class ListRemoveEleInForLoopTest {
-
- private List list;
-
- /** 初始化数据 */
- @BeforeEach
- public void init() {
- list = new ArrayList<>(5);
- list.add(1);
- list.add(2);
- list.add(2);
- list.add(3);
- list.add(4);
- }
-
- /** 运行无异常,测试不通过 */
- @Test
- @DisplayName("基础for循环中删除元素测试")
- void testBasicForLoop() {
- for (int i = 0; i < list.size(); i++) {
- if (Objects.equals(list.get(i), 2)) {
- // IDEA警告:Suspicious 'List.remove()' in the loop
- list.remove(i);
- }
- }
- System.out.println(list); // [1, 2, 3, 4]
- Assertions.assertEquals(list.size(), 3);
- }
-
-}
-```
-
-测试不通过,why?
-
-**原因很简单:** ArrayList 是基于数组结构而来的,在实现 E remove(int index) 方法时,也是在操作数组而已。
-
-**E remove(int index) 方法的源代码,如下:**
-
-```java {20,25}
-/**
- * Removes the element at the specified position in this list.
- * Shifts any subsequent elements to the left (subtracts one from their
- * indices).
- *
- * @param index the index of the element to be removed
- * @return the element that was removed from the list
- * @throws IndexOutOfBoundsException {@inheritDoc}
- */
-public E remove(int index) {
- rangeCheck(index);
-
- modCount++;
- E oldValue = elementData(index);
-
- int numMoved = size - index - 1;
- if (numMoved > 0)
- // 表面看是在拷贝数组,但是源数组和目标数组都是同一个,所以是移动数组元素而已
- // 例如:[1, 2, 3, 4] -> [1, 3, 4, 4]
- System.arraycopy(elementData, index+1, elementData, index,
- numMoved);
- // 元素数量-1,并清除多余元素
- // 例如:[1, 2, 3, 4] -> [1, 3, 4, 4]
- // 最后一个4就是多余的,置为默认值 null
- elementData[--size] = null; // clear to let GC do its work
-
- return oldValue;
-}
-```
-
-这样的话就会导致,在循环索引中删除完某个元素,其后面的元素移动到这个元素的位置,但是循环的索引可没回退,这样在取值时就会 **跳过下一个元素** 。(看不懂的话,可以debug一下,很清晰的)
-
-如果被删除元素的下一个元素不是匹配条件的,那还问题不显,但是如果被删除元素的下一个元素也是匹配条件的,也就会出现刚才测试的结果了。
-
-知道了问题的根源,要是还想要用这种循环,加一行代码就可以了。
-
-```java {30,32,35}
-/**
- * List集合-循环中删除元素-测试
- *
- * @author Charles7c
- * @date 2021/12/8 20:59
- */
-@DisplayName("List集合-循环中删除元素-测试")
-public class ListRemoveEleInForLoopTest {
-
- private List list;
-
- /** 初始化数据 */
- @BeforeEach
- public void init() {
- list = new ArrayList<>(5);
- list.add(1);
- list.add(2);
- list.add(2);
- list.add(3);
- list.add(4);
- }
-
- /** 运行无异常,测试不通过 */
- @Test
- @DisplayName("基础for循环中删除元素测试")
- void testBasicForLoop() {
- for (int i = 0; i < list.size(); i++) {
- if (Objects.equals(list.get(i), 2)) {
- // IDEA警告:Suspicious 'List.remove()' in the loop
- list.remove(i);
- // !!!回退索引!!!
- i--;
- }
- }
- System.out.println(list); // [1, 3, 4]
- Assertions.assertEquals(list.size(), 3);
- }
-
-}
-```
-
-### 增强for循环中删除
-
-显然,在基础 for 循环中删除元素,这种方法并不是最好的,那我们就再来看看其他的循环方式吧。
-
-简单改动下代码,看看平时出场频率也很高的增强 for 循环会如何?
-
-```java {29}
-/**
- * List集合-循环中删除元素-测试
- *
- * @author Charles7c
- * @date 2021/12/8 20:59
- */
-@DisplayName("List集合-循环中删除元素-测试")
-public class ListRemoveEleInForLoopTest {
-
- private List list;
-
- /** 初始化数据 */
- @BeforeEach
- public void init() {
- list = new ArrayList<>(5);
- list.add(1);
- list.add(2);
- list.add(2);
- list.add(3);
- list.add(4);
- }
-
- /** 运行时异常:java.util.ConcurrentModificationException */
- @Test
- @DisplayName("增强for循环中删除元素测试")
- void testForEachLoop() {
- for (Integer num : list) {
- if (Objects.equals(num, 2)) {
- list.remove(num);
- }
- }
- System.out.println(list);
- Assertions.assertSame(list.size(), 3);
- }
-
-}
-```
-
-测试中断,删除一个元素后继续循环会抛出运行时异常:java.util.ConcurrentModificationException。 Pass ...
-
-### 迭代器中删除
-
-最后,我们再尝试一种循环:迭代器,可能对于部分同学来说,平时使用相对要少一些。
-
-```java {31,34}
-/**
- * List集合-循环中删除元素-测试
- *
- * @author Charles7c
- * @date 2021/12/8 20:59
- */
-@DisplayName("List集合-循环中删除元素-测试")
-public class ListRemoveEleInForLoopTest {
-
- private List list;
-
- /** 初始化数据 */
- @BeforeEach
- public void init() {
- list = new ArrayList<>(5);
- list.add(1);
- list.add(2);
- list.add(2);
- list.add(3);
- list.add(4);
- }
-
- /** 运行无异常,测试符合预期 */
- @Test
- @DisplayName("迭代器中删除元素测试")
- void testIterator() {
- Iterator iterator = list.iterator();
- while (iterator.hasNext()) {
- Integer num = iterator.next();
- if (Objects.equals(num, 2)) {
- iterator.remove();
- }
- }
- System.out.println(list); // [1, 3, 4]
- Assertions.assertSame(list.size(), 3);
- }
-
-}
-```
-
-测试通过,这种方式也是平时 **推荐大家采用** 的,而且在 Java 8 中,官方还为我们在 Collection 接口中提供了一个 default 方法来简化集合删除元素。
-
-```java {28}
-/**
- * List集合-循环中删除元素-测试
- *
- * @author Charles7c
- * @date 2021/12/8 20:59
- */
-@DisplayName("List集合-循环中删除元素-测试")
-public class ListRemoveEleInForLoopTest {
-
- private List list;
-
- /** 初始化数据 */
- @BeforeEach
- public void init() {
- list = new ArrayList<>(5);
- list.add(1);
- list.add(2);
- list.add(2);
- list.add(3);
- list.add(4);
- }
-
- /** 运行无异常,测试符合预期 */
- @Test
- @DisplayName("迭代器中删除元素测试")
- void testIterator() {
- // Java 8 在 Collection 接口中提供的 default 方法
- list.removeIf(num -> Objects.equals(num, 2));
- System.out.println(list); // [1, 3, 4]
- Assertions.assertSame(list.size(), 3);
- }
-
-}
-```
-
-**Collection 接口的 removeIf() 方法的源代码,如下:**
-
-```java {27,30}
-public interface Collection extends Iterable {
- /**
- * Removes all of the elements of this collection that satisfy the given
- * predicate. Errors or runtime exceptions thrown during iteration or by
- * the predicate are relayed to the caller.
- *
- * @implSpec
- * The default implementation traverses all elements of the collection using
- * its {@link #iterator}. Each matching element is removed using
- * {@link Iterator#remove()}. If the collection's iterator does not
- * support removal then an {@code UnsupportedOperationException} will be
- * thrown on the first matching element.
- *
- * @param filter a predicate which returns {@code true} for elements to be
- * removed
- * @return {@code true} if any elements were removed
- * @throws NullPointerException if the specified filter is null
- * @throws UnsupportedOperationException if elements cannot be removed
- * from this collection. Implementations may throw this exception if a
- * matching element cannot be removed or if, in general, removal is not
- * supported.
- * @since 1.8
- */
- default boolean removeIf(Predicate super E> filter) {
- Objects.requireNonNull(filter);
- boolean removed = false;
- final Iterator each = iterator();
- while (each.hasNext()) {
- if (filter.test(each.next())) {
- each.remove();
- removed = true;
- }
- }
- return removed;
- }
-
- // 省略其他代码...
-}
-```
-
-很显然,官方也是用的迭代器来实现的。
-
-## 后记
-
-**C:** 虽然是一个小问题,但是见到的犯错者无数,以前并未当回事,这次遇到正好记录一下,给各位同学一个提醒。
\ No newline at end of file
diff --git a/docs/categories/issues/2021/12/10/Command line is too long. Shorten command line for XXX or also for Spring Boot default configuration.md b/docs/categories/issues/2021/12/10/Command line is too long. Shorten command line for XXX or also for Spring Boot default configuration.md
deleted file mode 100644
index 22aba83db..000000000
--- a/docs/categories/issues/2021/12/10/Command line is too long. Shorten command line for XXX or also for Spring Boot default configuration.md
+++ /dev/null
@@ -1,59 +0,0 @@
----
-title: Command line is too long. Shorten command line for XXX or also for Spring Boot default configuration?
-author: 查尔斯
-date: 2021/12/10 22:11
-categories:
- - Bug万象集
-tags:
- - IDE
- - "IntelliJ IDEA"
----
-
-# Command line is too long. Shorten command line for XXX or also for Spring Boot default configuration?
-
-## 问题描述
-
-今天笔者在公司从测试环境拉取了一个 bugfix 分支之后,等待 Maven 依赖也加载完了,点了【Debug】运行按钮,想起身去接杯水,跳过项目启动的这段时间。
-
-结果,刚握住水杯,就看到 IntelliJ IDEA 在左下角弹出了一个错误提示框,如下:
-
-
-
-看提示的意思是命令行太长了,让缩短一下命令行。又点了两下【Debug】运行按钮,依然不依不饶的弹出这个提示,那就放下水杯解决吧。
-
-
-## 原因分析
-我想了想原因,就明白什么问题了,给大家贴一下启动的项目程序所在位置。
-
-- 仓库目录(.git目录)
- - 项目doc目录
- - src
- - 项目源码父级项目目录
- - 启动入口所在项目目录
- - src\main\java(三级目录)
- - com\xx\xxx(N级的包目录)
- - Spring Boot 项目启动类
- - 若干模块项目目录
- - pom.xml
-
-这个结构,不好多说什么,历史遗留, doc 和源码放在了一个仓库,层级的确挺深,但一般情况下也不会出现此问题,这次算是一个特殊情况。
-
-
-## 解决方案
-其实问题解决起来也不难,这种问题笔者以前也遇到过,不过当时也忘了怎么切到了 IntelliJ IDEA 修复提示内,选了一下就结束了。
-
-而这一次笔者没找到正确的修复入口,所以只能采用手动修改配置的方法了。
-
-双击打开项目根目录下的 `.idea` 目录,这个目录下都是 IntelliJ IDEA 自动保存的项目配置内容,一般情况下我们不需要关注它,但这次我们需要找到其中的 `workspace.xml` 配置文件,手动修改一下配置。
-
-
-
-按下 【Ctrl + F】,在弹出的搜索框中,输入【PropertiesComponent】回车,定位到该项配置后,在其所在的 `` 标签内最后部分添加一条属性配置,如下:
-
-```xml
-
-```
-
-
-
-添加完后,关闭该配置文件即可,再次点击【Debug】运行按钮,项目正常启动了,笔者也该去接水了。
\ No newline at end of file
diff --git a/docs/categories/issues/2021/12/11/SQL 注入攻击风险.md b/docs/categories/issues/2021/12/11/SQL 注入攻击风险.md
deleted file mode 100644
index fb29c9c5f..000000000
--- a/docs/categories/issues/2021/12/11/SQL 注入攻击风险.md
+++ /dev/null
@@ -1,249 +0,0 @@
----
-title: SQL 注入攻击风险
-author: 查尔斯
-date: 2021/12/11 22:51
-categories:
- - Bug万象集
-tags:
- - SQL
- - 网络攻击
----
-
-# SQL 注入攻击风险
-
-## 前言
-
-**C:** Java 开发者都知道,想要用 Java 连接关系型数据库进行操作,就要学习使用 java.sql 包下的一套 JDBC API,这套 API 的使用步骤,大致如下:
-
-```java {30}
-/**
- * JDBC,模拟登录示例
- *
- * @author Charles7c
- * @date 2021/12/11 22:51
- */
-public class JdbcLoginDemo {
- public static void main(String[] args) {
- // 录入登录信息
- Scanner input = new Scanner(System.in);
- System.out.print("请输入用户名:");
- String username = input.next();
- System.out.print("请输入密码:");
- String password = input.next();
-
- // 查询数据库,验证登录信息
- boolean loginResult = false;
- Connection conn = null;
- Statement statement = null;
- ResultSet rs = null;
- try {
- // 1、注册驱动
- Class.forName("com.mysql.jdbc.Driver");
- // 2、获取连接
- conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/school", "root", "root");
- // 3、创建语句执行平台
- statement = conn.createStatement();
- // 4、编写SQL语句
- // String sql = "SELECT * FROM `user` WHERE `username` = '" + username + "' AND `password` = '" + password + "'";
- String sql = String.format("SELECT * FROM `user` WHERE `username` = '%s' AND `password` = '%s'", username, password);
- // 5、执行SQL语句
- rs = statement.executeQuery(sql);
- // 6、解析结果集
- if (rs.next()) {
- loginResult = true;
- }
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- // 7、释放资源,先开后关
- try {
- if (rs != null) {
- rs.close();
- }
- if (statement != null) {
- statement.close();
- }
- if (conn != null) {
- conn.close();
- }
- } catch (SQLException e) {
- e.printStackTrace();
- }
- }
-
- // 输出登录结果
- System.out.println(loginResult ? "登录成功!" : "登录失败!用户名或密码错误!");
- }
-}
-```
-
-上方是一个非常经典的 JDBC 模拟登录示例,同样的,很多同学可能一眼就看出来了,它存在着一个严重的攻击漏洞:SQL 注入攻击。
-
-今天,咱们就一块来聊聊 SQL 注入(SQL Injection)这个东西。
-
-## SQL 注入攻击简介
-
- SQL 注入(SQL Injection)作为一种比较常见的网络攻击方式,在学习 JDBC 时就肯定会得到老师的重点提醒。它的出现原因就是因为开发者编写的 SQL 语句,采用拼接的方式来接受输入参数。
-
-看看上方代码的第 4 步骤,一条 通过用户名和密码来查询用户记录 的简单查询 SQL,它在接受用户名和密码两个输入参数时,是直接拼接到查询 SQL 语句上的。
-
-```java
-// 下方两种形式都一样,笔者个人相对更喜欢使用格式化字符串而已
-// String sql = "SELECT * FROM `user` WHERE `username` = '" + username + "' AND `password` = '" + password + "'";
-String sql = String.format("SELECT * FROM `user` WHERE `username` = '%s' AND `password` = '%s'", username, password);
-```
-
-假设是一个正常的用户输入:
-
-- 用户名:admin
-- 密码:123456
-
-那最终执行的查询 SQL 语句,如下:
-
-```sql
-SELECT * FROM `user` WHERE `username` = 'admin' AND `password` = '123456';
-```
-
-这倒是没什么问题,但是如果是一个攻击者恶意的输入:
-
-- 用户名:luanShuDe(胡乱输入的)
-- 密码:luanShuDe' OR '1' = '1(密码也是胡乱输入的,重点在后面部分)
-
-那最终执行的查询 SQL 语句,如下:
-
-```sql
-SELECT * FROM `user` WHERE `username` = 'luanShuDe' AND `password` = 'luanShuDe' OR '1' = '1';
-```
-
-胡乱输入的用户名和密码肯定查询不到结果,但是密码后面的内容由于是 SQL 语法,直接拼接到查询 SQL 语句内,最终也是会执行的,1和1是恒等的,而 OR 运算符是只要一个条件满足,就匹配,所以结果就会查询出所有的用户记录。
-
-这就导致本该登录失败的情况,却判定登录成功了!也就达成了一次相对简单的 SQL 注入攻击了。
-
-## 解决方案
-
-### JDBC 的 PreparedStatement
-
-问题是要解决的,而且 JDBC 早就提供了相应的解决方法。那就是采用 Statement 的子接口 PreparedStatement,使用步骤如下:
-
-::: tip 笔者说
-
-Prepared 从单词意思上就知道是:准备好的,有准备的。
-
-PreparedStatement 的对象包含了编译好的 SQL 语句。这种 “准备好” 的方式不仅能提高安全性,解决 SQL 注入问题,而且在多次执行同一个 SQL 时,无需再编译,能够提高效率。
-
-:::
-
-```java {27}
-/**
- * JDBC,模拟登录示例
- *
- * @author Charles7c
- * @date 2021/12/11 22:51
- */
-public class JdbcLoginDemo2 {
- public static void main(String[] args) {
- // 录入登录信息
- Scanner input = new Scanner(System.in);
- System.out.print("请输入用户名:");
- String username = input.next();
- System.out.print("请输入密码:");
- String password = input.next();
-
- // 查询数据库,验证登录信息
- boolean loginResult = false;
- Connection conn = null;
- PreparedStatement ps = null;
- ResultSet rs = null;
- try {
- // 1、注册驱动
- Class.forName("com.mysql.jdbc.Driver");
- // 2、获取连接
- conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/school", "root", "root");
- // 3、编写SQL语句,使用 ? 进行参数占位
- String sql = "SELECT * FROM `user` WHERE `username` = ? AND `password` = ?";
- // 4、创建语句执行平台
- ps = conn.prepareStatement(sql);
- // 5、设置参数
- ps.setString(1, username);
- ps.setString(2, password);
- // 6、执行SQL语句
- rs = ps.executeQuery();
- // 7、解析结果集
- if (rs.next()) {
- loginResult = true;
- }
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- // 8、释放资源,先开后关
- try {
- if (rs != null) {
- rs.close();
- }
- if (ps != null) {
- ps.close();
- }
- if (conn != null) {
- conn.close();
- }
- } catch (SQLException e) {
- e.printStackTrace();
- }
- }
-
- // 输出登录结果
- System.out.println(loginResult ? "登录成功!" : "登录失败!用户名或密码错误!");
- }
-}
-```
-
-### MyBatis 的 #{}
-
-在平时的开发中,我们基本上都在采用 ORM 框架来解决持久层问题,MyBatis 作为一个在国内常用的半自动 ORM 框架,底层就是对 JDBC 的封装,简化了大量模板化的代码。
-
-如果你也使用了 MyBatis,那么在 SQL 语句传参时,一定要注意使用 #{} 的方式,它最终的实现就是 JDBC 的 PreparedStatement。
-
-### 特殊符号检查过滤
-
-MyBatis 还有一种 ${} 也可以来接受参数,但是这种方式最终就是直接在 SQL 语句中拼接输入参数,所以它存在 SQL 注入攻击的风险。
-
-如果真的想用,可以采用对输入参数进行特殊符号检查过滤。检查过滤的代码,可参考如下:
-
-```java
-public class CheckUtils {
- /**
- * 校验条件参数不可包含特殊字符,并且小于255个字符
- *
- * @param 条件参数内容
- * @throws Exception 具体错误信息
- */
- public static void checkCondition(String param) throws Exception {
- if (param != null) {
- String regEx = "[`~!@#$%^&*+=|{}':;',\\[\\]<>?~!@#¥%……&*()+|{}【】‘;:”“’。,、?]|\n|\r|\t";
- Matcher matcher = Pattern.compile(regEx).matcher(param);
- if (param.length() < 0 || param.length() > 255) {
- throw new Exception("查询条件最长字符255!");
- }
- if (matcher.find()) {
- throw new Exception("查询条件中不应包含特殊字符!");
- }
- }
- }
-}
-```
-
-当然了,笔者个人认为基本上能用 ${} 的地方都可以采用 #{} 替代。不过,倒也是见过一些同事在写代码时坚持用 ${} ,代码片段类似如下:
-
-```sql
-SELECT * FROM user WHERE username LIKE '%${username}%'
-```
-
-的确,LIKE 模糊查询时,后面模糊条件的 `%` 等符号是不能直接出现在 SQL 语句里的,而是要写在由 `'` (单引号)引起的字符串内。但是 MyBatis 的 `#{}` 又无法写在由 `'` (单引号)引起的字符串内,即无法直接写成 `'%#{username}%'` (如果你不相信,可以自行尝试一下,看看控制台会有什么 “惊喜” 输出),这应该就是这部分同事不得不采用 `'%${username}%'` 写法的原因。
-
-好在,笔者这正好也提供一种解决方法,可以解决此问题,那就是使用 SQL 函数 CONCAT(),代码片段类似如下:
-
-```sql
-SELECT * FROM user WHERE username LIKE CONCAT('%', #{username}, '%')
-```
-
-没错,既然要拼接字符串,那就用 CONCAT() 函数,这个函数就是专门用来拼接字符串的,在拼接时可以使用 #{} ,所以也就不会存在 SQL 注入的问题了。
\ No newline at end of file
diff --git a/docs/categories/issues/2021/12/13/无法访问F盘。文件或目录损坏且无法读取.md b/docs/categories/issues/2021/12/13/无法访问F盘。文件或目录损坏且无法读取.md
deleted file mode 100644
index 3702b15db..000000000
--- a/docs/categories/issues/2021/12/13/无法访问F盘。文件或目录损坏且无法读取.md
+++ /dev/null
@@ -1,60 +0,0 @@
----
-title: 无法访问 F:\。文件或目录损坏且无法读取。
-author: 查尔斯
-date: 2021/12/13 22:57
-categories:
- - Bug万象集
-tags:
- - Windows
----
-
-# 无法访问 F:\。文件或目录损坏且无法读取。
-
-## 问题描述
-
-笔者这块西数的移动硬盘最近真的是问题频发,前段时间无法删除损坏的回收站,这两天在家里电脑上插上之后,双击 F 盘提示已损坏,较之以前问题更甚。
-
-这的确给了笔者一个 “惊喜”,最近两周好像没开 Drive 备份到 NAS 。硬盘要是坏了,这两周的东西还能剩下多少就不好说了。
-
-不过好在最后问题解决了,跟笔者来一起看看解决方法吧。
-
-
-
-
-## 解决方案
-### 尝试1:尝试检查与修复
-
-首先,在出现问题的磁盘上【右键】单击,然后选择【属性】。
-
-
-
-在弹出的【属性】对话框中,选择【工具】选项卡,然后点击【检查】按钮。这个功能是用来检查磁盘文件系统错误的,检查完还会有个错误修复的环节。
-
-
-
-可惜的是,不知道是笔者这台电脑登录的账号权限问题,还是系统错误,这项修复手段,笔者用不了。
-
-
-
-### 尝试2:命令行修复
-
-还是老规矩,桌面可视化中的功能只是一种手段,每一项功能都有其对应的系统命令。
-
-按下【Windows】键,弹出【开始】菜单,直接输入【cmd】来在菜单中搜索。搜索出来后,在【cmd.exe/命令行】上【右键】单击,选择【以管理员身份运行】。
-
-
-
-在弹出的 CMD 命令行窗口中,输入以下命令:
-
-```bash
-# 这条命令是用来检查磁盘并修复的,中间的 f: 换成你出现上方问题的盘符即可。
-chkdsk f: /f
-```
-
-
-
-
-
-等待检查修复结束,笔者的 F 盘又回来了。
-
-
diff --git a/docs/categories/issues/2022/01/26/JavaScript 无法存储 Java Long 类型数据问题.md b/docs/categories/issues/2022/01/26/JavaScript 无法存储 Java Long 类型数据问题.md
deleted file mode 100644
index 909be9c19..000000000
--- a/docs/categories/issues/2022/01/26/JavaScript 无法存储 Java Long 类型数据问题.md
+++ /dev/null
@@ -1,139 +0,0 @@
----
-title: JavaScript 无法存储 Java Long 类型数据问题
-author: 查尔斯
-date: 2022/01/26 09:07
-categories:
- - Bug万象集
-tags:
- - JavaScript
----
-
-# JavaScript 无法存储 Java Long 类型数据问题
-
-## 问题描述
-
-今天在解决一个需求问题的时候,遇到了一个难得一见的 JS 问题。这个问题大概是和一些同学在开发环境使用 == 来比较包装类型的整数一样,由于比较的数值在缓存范围内,因缘际会的错过了 bug 的出现。
-
-简单说一下问题经过,是这样的,笔者这个需求最终要求接口返回一组自定义结构的 k-v (不是单纯的键值对)数据,用于在前端表单中进行分类展示。
-
-后端响应的数据结构类似如下:
-
-```json {11,16,29,34}
-{
- "code": 200,
- "errorMsg": "",
- "result": [
- {
- "extend": null,
- "items": [
- {
- "extend": null,
- "key": "CentOS 8.1 64位",
- "val": 8014753905961037835
- },
- {
- "extend": null,
- "key": "CentOS 8.0 64位",
- "val": 8014753905961037838
- },
- ],
- "key": "CentOS",
- "pubProperty": "",
- "val": 14
- },
- {
- "extend": null,
- "items": [
- {
- "extend": null,
- "key": "RedHat Enterprise Linux 8.0 64位",
- "val": 7979917486755315712
- },
- {
- "extend": null,
- "key": "RedHat Enterprise Linux 7.7 64位",
- "val": 8014753905961037829
- }
- ],
- "key": "Redhat",
- "pubProperty": "",
- "val": 5
- }
- ],
- "success": true
-}
-```
-
-在前端表单中的展示效果大概如下:
-
-
-
-## 原因分析
-
-笔者相信各位同学都应该猜得到,当提交表单的时候,前端肯定会把选中的镜像的 val 值传递给后端,然后由后端继续进行处理。
-
-但是就在这个环节,由前端传给后端的 val 值竟然错了,例如:当选中了 CentOS 8.1 64 位这个镜像时,本该传递的 val 值为 8014753905961037835,实际传递的却是: 8014753905961038000。
-
-后端接口测试的响应数据没问题,那问题显然就是出在前端了。
-
-最终,配合前端开发定位这个问题的原因是因为: JavaScript 中无法存储 Java 中的 Long 类型数据,当位数超过 JavaScript 整数存储的范围,就会以0来代替了。
-
-
-
-## 解决方案
-
-JavaScript 存储不了就存储不了吧,咱们这个需求还得解决啊,最终的解决方法就是将后端响应回来的 Long 类型数据转换为字符串。
-
-```java {2}
-// 在序列化为 JSON 时将该字段转换为 String 类型
-@JsonFormat(shape = JsonFormat.Shape.STRING)
-private Long val;
-```
-
-后端响应的数据结构类似如下:
-
-```json {11,16,29,34}
-{
- "code": 200,
- "errorMsg": "",
- "result": [
- {
- "extend": null,
- "items": [
- {
- "extend": null,
- "key": "CentOS 8.1 64位",
- "val": "8014753905961037835"
- },
- {
- "extend": null,
- "key": "CentOS 8.0 64位",
- "val": "8014753905961037838"
- },
- ],
- "key": "CentOS",
- "pubProperty": "",
- "val": 14
- },
- {
- "extend": null,
- "items": [
- {
- "extend": null,
- "key": "RedHat Enterprise Linux 8.0 64位",
- "val": "7979917486755315712"
- },
- {
- "extend": null,
- "key": "RedHat Enterprise Linux 7.7 64位",
- "val": "8014753905961037829"
- }
- ],
- "key": "Redhat",
- "pubProperty": "",
- "val": 5
- }
- ],
- "success": true
-}
-```
\ No newline at end of file
diff --git a/docs/categories/issues/2022/03/24/创建一个自身类的静态对象变量,究竟会如何执行?.md b/docs/categories/issues/2022/03/24/创建一个自身类的静态对象变量,究竟会如何执行?.md
deleted file mode 100644
index 04b24aa9f..000000000
--- a/docs/categories/issues/2022/03/24/创建一个自身类的静态对象变量,究竟会如何执行?.md
+++ /dev/null
@@ -1,193 +0,0 @@
----
-title: 创建一个自身类的静态对象变量,究竟会如何执行?
-author: 查尔斯
-date: 2022/03/24 21:30
-categories:
- - Bug万象集
-tags:
- - Java
- - JVM
----
-
-# 创建一个自身类的静态对象变量,究竟会如何执行?
-
-## 前言
-
-**C:** 近两周在疯狂给项目组面试招聘,昨天晚上10点多,产品总监在面试群里发了一道题,问运行结果是什么,题目如下:
-
-```java {2-4}
-class Singleton {
- private static Singleton singleton = new Singleton();
- public static int count1;
- public static int count2 = 3;
-
- private Singleton() {
- count1++;
- count2++;
- }
-
- public static Singleton getInstance() {
- return singleton;
- }
-}
-
-public class Test {
- public static void main(String[] args) {
- Singleton singleTon = Singleton.getInstance();
- System.out.println("count1=" + singleTon.count1);
- System.out.println("count2=" + singleTon.count2);
- }
-}
-```
-
-这激起了我们几个干技术的热情,那就分析一下吧。
-
-## 简单分析
-
-1、简单看了下题目,这不是一个采用了饿汉式单例模式的单例类嘛,接下来当然是去找程序入口了。
-
-2、在 Test 类的 main 方法中,首先调用了 Singleton 类的 getInstance() 方法,很显然这是要获取 Singleton 这个单例类的唯一对象(实例)了。
-
-3、然后在获取到唯一对象(实例)之后,输出了 Singleton 类的两个静态成员变量 count1、count2 的值。(虽然通过对象名调用静态信息这种方式不推荐,但是对结果没有影响)
-
-4、看到这儿,两个类里也没别的地方有输出语句,所以最终运行结果就是要看看 count1、count2 的输出值了。
-
-5、**重点来了:** 在调用 getInstance() 方法前,由于 Singleton 类没有加载,所以肯定要先加载类,由于 count1、count2、Singleton 的唯一对象(实例)都是静态的,所以它们会随着类的加载而加载。其中 int 类型的 count1 变量没有指定初始值,那默认值就是 0,count2 指定了初始值是 3, Singleton 类的唯一对象(实例)要创建会调用构造方法,构造方法里又对 count1 和 count2 进行了自增 1 的运算,那结果自然就是 count1 是 1,count2 是 4。
-
-这么一顿火花带闪电的分析后,自信的将答案发到了群里。
-
-```
-count1=1
-count2=4
-```
-
-## 深度分析
-
-很显然答错了,不然也不会单独记录了。之所以答错了,是因为忽略了静态信息的加载顺序,静态信息的加载顺序是由编码顺序决定的,上方分析中先入为主的把 count1 和 count2 加载完了,但实际上最先执行的是 Singleton 的唯一对象(实例)创建及变量赋值,随后才是执行 count1、count2。
-
-我们可以通过 `javap -c Singleton.class` 反汇编一下字节码文件,反汇编后的 JVM 指令如下:
-
-```java
-Compiled from "Test.java"
-class org.example.Singleton {
- public static int count1;
-
- public static int count2;
-
- public static org.example.Singleton getInstance();
- Code:
- // 获取 singleton 静态对象变量,并将其值压入栈顶
- 0: getstatic #4 // Field singleton:Lorg/example/Singleton;
- // 从当前方法返回 singleton 对象引用
- 3: areturn
-
- static {};
- Code:
- // 1、创建 Singleton 类的对象,并赋值给静态对象变量 singleton
- // 1.1 创建对象
- 0: new #5 // class org/example/Singleton
- // 1.2 复制栈顶数值并将复制值压入栈顶
- 3: dup
- // 1.3 调用 Singleton 类构造方法,count1 和 count2 自增 1,此时 count1 为 1,count2 为 1
- 4: invokespecial #6 // Method "":()V
- // 1.4 对象创建成功将对象引用赋值给静态对象变量 singleton
- 7: putstatic #4 // Field singleTon:Lorg/example/Singleton;
-
- // 2、将 3 赋值给 count2
- // 2.1 将 int 型 3 推送至栈顶
- 10: iconst_3
- // 2.2 为 count2 静态变量赋值
- 11: putstatic #3 // Field count2:I
-
- // 3、结束方法
- 14: return
-}
-
-```
-
-很显然了,count2 最后是被赋值为 3 了。
-
-正确答案就是:
-
-```
-count1=1
-count2=3
-```
-
-## 额外扩展
-
-那如果真的想得到之前的结果呢?
-
-```
-count1=1
-count2=4
-```
-
-只需要将 count1、count2 两个静态变量的顺序调整到 Singleton 类的唯一对象(实例)变量上方就可以了。
-
-```java {2-4}
-class Singleton {
- public static int count1;
- public static int count2 = 3;
- private static Singleton singleton = new Singleton();
-
- private Singleton() {
- count1++;
- count2++;
- }
-
- public static Singleton getInstance() {
- return singleton;
- }
-}
-
-public class Test {
- public static void main(String[] args) {
- Singleton singleTon = Singleton.getInstance();
- System.out.println("count1=" + singleTon.count1);
- System.out.println("count2=" + singleTon.count2);
- }
-}
-```
-
-我们再次通过 `javap -c Singleton.class` 反汇编一下字节码文件,反汇编后的 JVM 指令如下:
-
-```java
-Compiled from "Test.java"
-class org.example.Singleton {
- public static int count1;
-
- public static int count2;
-
- public static org.example.Singleton getInstance();
- Code:
- // 获取 singleton 静态对象变量,并将其值压入栈顶
- 0: getstatic #4 // Field singleton:Lorg/example/Singleton;
- // 从当前方法返回 singleton 对象引用
- 3: areturn
-
- static {};
- Code:
- // 1、将 3 赋值给 count2,count2 此时为 3
- // 1.1 将 int 型 3 推送至栈顶
- 0: iconst_3
- // 1.2 为 count2 静态变量赋值
- 1: putstatic #3 // Field count2:I
-
- // 2、创建 Singleton 类的对象,并赋值给静态对象变量 singleton
- // 2.1 创建对象
- 4: new #5 // class org/example/Singleton
- // 2.2 复制栈顶数值并将复制值压入栈顶
- 7: dup
- // 2.3 调用 Singleton 类构造方法,count1 和 count2 自增 1,count1 此时为 1,count2 此时为 4
- 8: invokespecial #6 // Method "":()V
- // 2.4 对象创建成功将对象引用赋值给静态对象变量 singleton
- 11: putstatic #4 // Field singleTon:Lorg/example/Singleton;
-
- // 3、结束方法
- 14: return
-}
-
-```
-
-很显然了,count2 最后是被自增为 4 了。
\ No newline at end of file
diff --git a/docs/categories/issues/2022/08/11/执行Shell脚本,报java command not found.md b/docs/categories/issues/2022/08/11/执行Shell脚本,报java command not found.md
deleted file mode 100644
index 1748a8534..000000000
--- a/docs/categories/issues/2022/08/11/执行Shell脚本,报java command not found.md
+++ /dev/null
@@ -1,91 +0,0 @@
----
-title: "执行Shell脚本,报java: command not found"
-author: 查尔斯
-date: 2022/08/11 20:19
-categories:
- - Bug万象集
-tags:
- - Linux
- - Shell
----
-
-# 执行Shell脚本,报java: command not found
-
-## 问题描述
-
-**C:** 今天笔者在公司的 dev 环境服务器上,将一个 Java 程序启动脚本做成了一个系统服务。本来是一件很简单的事情,但是在启动服务时,却报错了。
-
-报的错误也是言简意赅:java: command not found。很直白的告诉了你,它找不到 java 命令。
-
-
-
-## 原因分析
-
-既然是找不到 java 命令,首先要排查的自然是服务器里究竟有没有安装和配置好 Java 环境了,用 `java -version` 命令检测一下就可以了。
-
-```shell
-[root@business11 ~]# java -version
-java version "1.8.0_202"
-Java(TM) SE Runtime Environment (build 1.8.0_202-b08)
-Java HotSpot(TM) 64-Bit Server VM (build 25.202-b08, mixed mode)
-```
-
-Java 环境是配置好的,那还得是看脚本自身的问题了。其实,这个问题以前印象里也出现过,不过是很久以前初次使用 Shell 脚本的时候了,最终的问题点是因为直接写的脚本内容,没有添加 `#!/bin/bash` 声明导致的。
-
-打开脚本内容看了看,这个声明也加着呢。
-
-```shell
-#!/bin/bash
-
-Java 启动脚本内容······
-```
-
-## 解决方案1
-
-既然以往的经验不能提供帮助,那就对症下药,提示说找不到 java 命令,那说明它识别不到 Java 环境配置,帮它一把就得了呗。复制一份 Java 环境配置,放在脚本内容前,相当于每次执行这个脚本的时候,先做一次临时环境配置。
-
-::: warning 笔者说
-如果你要使用下方的配置,不要直接复制了事,记得将配置中的 JDK 安装路径,替换为你自己实际的 JDK 安装路径。
-:::
-
-```shell
-#!/bin/bash
-JAVA_HOME=/opt/disk/java/jdk1.8.0_202 # 如果你要使用,记得替换为你自己实际的 JDK 安装路径
-CLASSPATH=.:$JAVA_HOME/lib.tools.jar
-PATH=$JAVA_HOME/bin:$PATH
-export JAVA_HOME CLASSPATH PATH
-
-Java 启动脚本内容······
-```
-
-## 解决方案2
-
-这个问题的根源,其实是因为 `/etc/profile` 或者 `/etc/security/limit.d` 这些文件中配置的环境变量仅对通过 pam 登录的用户生效,systemd 系统服务是不读这些配置的,所以这就造成登录到终端时查看环境变量和手动启动应用都一切正常,但是系统服务无法正常启动应用。
-
-所以,如果想让 systemd 系统服务使用环境变量也可以在编写的服务内指定好环境变量。
-
-```shell
-[Unit]
-Description=xxx
-Wants=network-online.target
-After=network-online.target
-
-[Service]
-# 如果你要使用,记得替换为你自己实际的 JDK 安装路径
-Environment="JAVA_HOME=/opt/disk/java/jdk1.8.0_202"
-Environment="CLASSPATH=.:$JAVA_HOME/lib.tools.jar"
-Environment="PATH=$JAVA_HOME/bin:$PATH"
-ExecStart=/bin/bash /opt/disk/xxx/start-schedule.sh
-KillSignal=SIGTERM
-
-[Install]
-WantedBy=multi-user.target
-```
-
-修改完系统服务,别忘了重新加载和重新启动。
-
-```shell
-systemctl daemon-reload
-systemctl restart xxx
-```
-
diff --git a/docs/categories/issues/2022/08/31/SpringBoot项目引入OpenFeign后无法启动.md b/docs/categories/issues/2022/08/31/SpringBoot项目引入OpenFeign后无法启动.md
deleted file mode 100644
index 70aba27ae..000000000
--- a/docs/categories/issues/2022/08/31/SpringBoot项目引入OpenFeign后无法启动.md
+++ /dev/null
@@ -1,352 +0,0 @@
----
-title: SpringBoot项目引入OpenFeign后无法启动
-author: 查尔斯
-date: 2022/08/31 22:39
-categories:
- - Bug万象集
-tags:
- - Java
- - "Spring Boot"
- - "Spring Cloud"
- - "Open Feign"
----
-
-# SpringBoot项目引入OpenFeign后无法启动
-
-**C:** 由于项目需要调用第三方 API,所以打算使用 Open Feign 来作为调用工具。但这次新项目用的 Spring Boot 版本有点高,花了点时间排除问题。
-
-
-
-## 问题描述
-
-先简单描述一下我们项目的技术栈,这是一个前后端分离的单体项目,前端不用提,后端部分主框架用的是 Spring Boot 2.7.2 版本。现在需要对接第三方 API,打算使用 Open Feign 来作为调用工具。Open Feign 是 Spring Cloud 开发的一个轻量级RESTful HTTP 服务客户端,多数使用的场景是用于微服务项目。
-
-### 引入依赖
-
-下方只贴出了关键部分依赖:
-
-```xml
-
- 2.0.4.RELEASE
-
-
-
-
-
-
- org.springframework.cloud
- spring-cloud-dependencies
- ${spring-cloud.version}
- pom
- import
-
-
-```
-
-```xml
-
-
-
- org.springframework.cloud
- spring-cloud-starter-openfeign
-
-
-```
-
-### 编写配置
-
-引入完依赖,自然是要简单配置一下。
-
-application.yml 中的 Feign 部分配置:
-
-```yaml
-## Feign 配置
-feign:
- client:
- config:
- default:
- connect-timeout: 20000
- read-timeout: 120000
-```
-
-FeignConfig 配置类:
-
-```java
-/**
- * Feign配置
- *
- * @author Charles7c
- * @date 2022/8/30 18:10
- */
-@Configuration
-public class FeignConfig {
-
- @Value("${spring.profiles.active}")
- private String activeProfile;
-
- /**
- * Feign 日志级别配置
- * @return /
- */
- @Bean
- Logger.Level feignLoggerLevel() {
- if ("prod".equals(activeProfile)) {
- return Logger.Level.BASIC;
- }
- return Logger.Level.FULL;
- }
-
- /**
- * 解决 javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No subject alternative names present
- * @return /
- */
- @Bean
- public Client client() {
- try {
- SSLContext context = new SSLContextBuilder().loadTrustMaterial(null, (chain, authType) -> true).build();
- return new Client.Default(context.getSocketFactory(), new NoopHostnameVerifier());
- } catch (Exception e) {
- return new Client.Default(null, null);
- }
- }
-}
-```
-
-### 启用Feign
-
-最后只需要在启动类上使用 `@EnableFeignClients` 启用一下配置即可。
-
-```java
-/**
- * 启动程序
- *
- * @author Charles7c
- * @date 2022/8/24 15:46
- */
-@EnableFeignClients
-@SpringBootApplication
-public class WebApiApplication {
- public static void main(String[] args) {
- System.setProperty("spring.devtools.restart.enabled", "false");
- SpringApplication application = new SpringApplication(WebApiApplication.class);
- application.setApplicationStartup(new BufferingApplicationStartup(2048));
- application.run(args);
- }
-}
-```
-
-### 报错信息
-
-根据以前的经验到这步也就结束了,该怎么用 Feign 就怎么用了。好的,写完之后启动项目。
-
-
-
-显然笔者被技术的 "浪潮" 又拍了一个 "跟头",技术从来都不是停滞不前的,采用新版本就肯定会有这样那样的问题,不提前做版本踩坑和梳理,就必然如此。
-
-## 原因分析
-
-从提示来看,控制台只打印了我在启动类中配置的一个系统属性,Spring Boot 项目的 Banner 都没打出来,显然是启动时都没走到打印 banner 这一步。但控制台什么错误也看不到,这可不行啊,先给启动这一步加个 try-catch 捕获下异常,起码先把异常显示出来。
-
-```java
-/**
- * 启动程序
- *
- * @author Charles7c
- * @date 2022/8/24 15:46
- */
-@EnableFeignClients
-@SpringBootApplication
-public class WebApiApplication {
- public static void main(String[] args) {
- try {
- System.setProperty("spring.devtools.restart.enabled", "false");
- SpringApplication application = new SpringApplication(WebApiApplication.class);
- application.setApplicationStartup(new BufferingApplicationStartup(2048));
- application.run(args);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-}
-```
-
-映入眼帘的就是:`java.lang.NoClassDefFoundError: org/springframework/boot/context/properties/ConfigurationBeanFactoryMetadata` 。这种错误我熟啊,八成是和版本有关。
-
-```
-已连接到目标 VM, 地址: ''127.0.0.1:59906',传输: '套接字''
-17:59:40.709 [main] INFO org.springframework.boot.devtools.restart.RestartApplicationListener - Restart disabled due to System property 'spring.devtools.restart.enabled' being set to false
-Exception in thread "main" java.lang.RuntimeException: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'configurationPropertiesBeans' defined in org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.cloud.context.properties.ConfigurationPropertiesBeans]: Factory method 'configurationPropertiesBeans' threw exception; nested exception is java.lang.NoClassDefFoundError: org/springframework/boot/context/properties/ConfigurationBeanFactoryMetadata
- at com.xxx.WebApiApplication.main(WebApiApplication.java:26)
-Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'configurationPropertiesBeans' defined in org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.cloud.context.properties.ConfigurationPropertiesBeans]: Factory method 'configurationPropertiesBeans' threw exception; nested exception is java.lang.NoClassDefFoundError: org/springframework/boot/context/properties/ConfigurationBeanFactoryMetadata
- at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:658)
- at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:486)
- at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1352)
- at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1195)
- at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582)
- at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)
- at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
- at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
- at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
- at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:213)
- at org.springframework.context.support.PostProcessorRegistrationDelegate.registerBeanPostProcessors(PostProcessorRegistrationDelegate.java:270)
- at org.springframework.context.support.AbstractApplicationContext.registerBeanPostProcessors(AbstractApplicationContext.java:762)
- at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:567)
- at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:734)
- at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408)
- at org.springframework.boot.SpringApplication.run(SpringApplication.java:308)
- at org.springframework.boot.builder.SpringApplicationBuilder.run(SpringApplicationBuilder.java:164)
- at org.springframework.cloud.bootstrap.BootstrapApplicationListener.bootstrapServiceContext(BootstrapApplicationListener.java:208)
- at org.springframework.cloud.bootstrap.BootstrapApplicationListener.onApplicationEvent(BootstrapApplicationListener.java:104)
- at org.springframework.cloud.bootstrap.BootstrapApplicationListener.onApplicationEvent(BootstrapApplicationListener.java:70)
- at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:176)
- at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:169)
- at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:143)
- at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:131)
- at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:85)
- at org.springframework.boot.SpringApplicationRunListeners.lambda$environmentPrepared$2(SpringApplicationRunListeners.java:66)
- at java.util.ArrayList.forEach(ArrayList.java:1257)
- at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:120)
- at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:114)
- at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:65)
- at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:344)
- at org.springframework.boot.SpringApplication.run(SpringApplication.java:302)
- at com.xxx.WebApiApplication.main(WebApiApplication.java:23)
-Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.cloud.context.properties.ConfigurationPropertiesBeans]: Factory method 'configurationPropertiesBeans' threw exception; nested exception is java.lang.NoClassDefFoundError: org/springframework/boot/context/properties/ConfigurationBeanFactoryMetadata
- at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185)
- at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653)
- ... 32 more
-Caused by: java.lang.NoClassDefFoundError: org/springframework/boot/context/properties/ConfigurationBeanFactoryMetadata
- at org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration.configurationPropertiesBeans(ConfigurationPropertiesRebinderAutoConfiguration.java:51)
- at org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration$$EnhancerBySpringCGLIB$$97caf445.CGLIB$configurationPropertiesBeans$1()
- at org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration$$EnhancerBySpringCGLIB$$97caf445$$FastClassBySpringCGLIB$$3f1d782c.invoke()
- at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244)
- at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331)
- at org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration$$EnhancerBySpringCGLIB$$97caf445.configurationPropertiesBeans()
- at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
- at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
- at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
- at java.lang.reflect.Method.invoke(Method.java:498)
- at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
- ... 33 more
-Caused by: java.lang.ClassNotFoundException: org.springframework.boot.context.properties.ConfigurationBeanFactoryMetadata
- at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
- at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
- at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
- at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
- ... 44 more
-与目标 VM 断开连接, 地址为: ''127.0.0.1:59906',传输: '套接字''
-
-进程已结束,退出代码1
-```
-
-## 解决方案
-
-实际上,早就等着这一步呢,笔者只是不死心尝试一下,前文也提到了,这个项目用的 Spring Boot 2.7.2 版本,而笔者引入的依赖是老项目中的依赖,老项目的 Spring Boot 是 2.0.4.RELEASE 版本,和 2.7.2 差了好几代了,怎么可能还兼容这么好呢。
-
-::: tip 笔者说
-Spring Boot 这种大版本升级,往往涉及到很多类的删除和重构,所以说学无止境啊。
-:::
-
-好了,那就去 [Spring Cloud 概述](https://spring.io/projects/spring-cloud#overview) 中摸一下 Spring Cloud 和 Spring Boot 最新的版本对应关系,然后好能确定该使用哪一个版本。
-
-| Release Train | Boot Version |
-| :------------------- | :------------------------------------ |
-| 2021.0.x aka Jubilee | 2.6.x, 2.7.x (Starting with 2021.0.3) |
-| 2020.0.x aka Ilford | 2.4.x, 2.5.x (Starting with 2020.0.3) |
-| Hoxton | 2.2.x, 2.3.x (Starting with SR5) |
-| Greenwich | 2.1.x |
-| Finchley | 2.0.x |
-| Edgware | 1.5.x |
-| Dalston | 1.5.x |
-
-根据 Spring Cloud 概述页面的介绍,2.7.x 版本的 Spring Boot 可以使用 2021.0.x 任意版本的 Spring Cloud,笔者最后使用了 2021.0.3 版本。
-
-```xml
-
- 2021.0.3
-
-
-
-
-
-
- org.springframework.cloud
- spring-cloud-dependencies
- ${spring-cloud.version}
- pom
- import
-
-
-```
-
-再启动,熟悉的 Spring Boot Banner 出现了,但是最后又报错了。
-
-```
-Exception in thread "main" java.lang.RuntimeException: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'captchaController' defined in file [D:\IdeaProjects\IdeaProjects-charles7c\fucloud-union-service\service-webapi\target\classes\com\xxx\webapi\controller\common\CaptchaController.class]: Unsatisfied dependency expressed through constructor parameter 2; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.xxx.core.rest.CloudUnionDataRestApi': Unexpected exception during bean creation; nested exception is java.lang.IllegalStateException: No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalancer?
- at com.xxx.WebApiApplication.main(WebApiApplication.java:26)
-Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'captchaController' defined in file [D:\IdeaProjects\IdeaProjects-charles7c\fucloud-union-service\service-webapi\target\classes\com\xxx\webapi\controller\common\CaptchaController.class]: Unsatisfied dependency expressed through constructor parameter 2; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.xxx.core.rest.CloudUnionDataRestApi': Unexpected exception during bean creation; nested exception is java.lang.IllegalStateException: No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalancer?
- at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:800)
- at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:229)
- at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1372)
- at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1222)
- at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582)
- at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)
- at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
- at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
- at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
- at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
- at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:955)
- at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918)
- at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583)
- at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147)
- at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:734)
- at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408)
- at org.springframework.boot.SpringApplication.run(SpringApplication.java:308)
- at com.xxx.WebApiApplication.main(WebApiApplication.java:23)
-Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.xxx.core.rest.CloudUnionDataRestApi': Unexpected exception during bean creation; nested exception is java.lang.IllegalStateException: No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalancer?
- at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:555)
- at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
- at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
- at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
- at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
- at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
- at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1391)
- at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1311)
- at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:887)
- at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
- ... 17 more
-Caused by: java.lang.IllegalStateException: No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalancer?
- at org.springframework.cloud.openfeign.FeignClientFactoryBean.loadBalance(FeignClientFactoryBean.java:382)
- at org.springframework.cloud.openfeign.FeignClientFactoryBean.getTarget(FeignClientFactoryBean.java:427)
- at org.springframework.cloud.openfeign.FeignClientFactoryBean.getObject(FeignClientFactoryBean.java:402)
- at org.springframework.cloud.openfeign.FeignClientsRegistrar.lambda$registerFeignClient$0(FeignClientsRegistrar.java:235)
- at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1249)
- at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1191)
- at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582)
- at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)
- ... 26 more
-与目标 VM 断开连接, 地址为: ''127.0.0.1:52466',传输: '套接字''
-```
-
-这回也是因为版本升级引起的,而且提示的已经很到位了,要求我们单独引入一个 `spring-cloud-loadbalancer` 依赖。
-
-::: tip 笔者说
-Spring Cloud Open Feign 在 Hoxton.M2 版本之后就不再使用 Ribbon 了,改为使用 Spring Cloud Loadbalancer。
-:::
-
-```xml
-
-
-
- org.springframework.cloud
- spring-cloud-starter-openfeign
-
-
-
- org.springframework.cloud
- spring-cloud-loadbalancer
-
-
-```
-
-最后启动成功。
diff --git a/docs/categories/issues/2022/09/05/Nginx转发请求,报13:Permission denied错误.md b/docs/categories/issues/2022/09/05/Nginx转发请求,报13:Permission denied错误.md
deleted file mode 100644
index cf9093872..000000000
--- a/docs/categories/issues/2022/09/05/Nginx转发请求,报13:Permission denied错误.md
+++ /dev/null
@@ -1,110 +0,0 @@
----
-title: "Nginx转发请求,报13:Permission denied错误"
-author: 查尔斯
-date: 2022/09/05 21:44
-categories:
- - Bug万象集
-tags:
- - Linux
- - Nginx
----
-
-# Nginx转发请求,报13:Permission denied错误
-
-## 问题描述
-
-**C:** 上周五下班前刚给新项目的测试环境添加上了 CI/CD,本以为事情就告一段落了,今天上午前端小伙伴合并了一版到测试环境,然后测试小伙伴就在群里 @ 笔者,问登录验证码怎么出不来了。
-
-赶紧去测试地址看了一下,发现验证码接口 Nginx 502 了,首先想到的就是后端服务宕了,登上测试环境看了看,人家跑得欢快的很啊,程序日志里也没出现什么错误。
-
-::: tip 笔者说
-HTTP 状态码 502:表示作为网关或代理角色的服务器,从上游服务器(如tomcat、php-fpm)中接收到的响应是无效的。
-:::
-
-既然错误不在后端服务,那就顺着调用链路往前排查看看,打开 Nginx 错误日志,看看究竟是什么原因导致的 502。
-
-```sh
-cd /var/log/nginx/
-# 查看错误日志
-tail -50 error.log
-```
-
-```
-2022/09/05 10:03:47 [crit] 8431#8431: *45 connect() to 127.0.0.1:18005 failed (13: Permission denied) while connecting to upstream, client: xx.xxx.xx.xxx, server: _, request: "GET /api/captcha?_t=1662344535439 HTTP/1.1", upstream: "http://127.0.0.1:18005/captcha?_t=1662344535439", host: "xx.x.xxx.xx", referrer: "http://xx.x.xxx.xx/"
-```
-
-
-
-## 原因分析
-
-抓住错误关键词 `Permission denied`,没有权限的意思,笔者突然灵光一现,很早以前安装 Nginx,在启动的时候遇到过一个和权限类似的错误,大意是你不能用 `root` 直接启动。 Nginx 配置文件中有一个 `user` 配置,启动 Nginx 需要用对应的用户才能启动,笔者现在是登录的 `root` 用户,今天 CI/CD 执行后出现这问题了,还是得去确认下 Nginx 配置。
-
-::: tip 笔者说
-这个 Nginx 是上周五让网管装的,代理部分配置也是直接复制的另一个项目的发给他就配上了,配完笔者直接用上了,当时手动打包前端传上来也没见出现什么问题。
-:::
-
-```
-# For more information on configuration, see:
-# * Official English Documentation: http://nginx.org/en/docs/
-# * Official Russian Documentation: http://nginx.org/ru/docs/
-
-user nginx;
-worker_processes auto;
-error_log /var/log/nginx/error.log;
-pid /run/nginx.pid;
-
-....
-```
-
-## 解决方案
-
-赶紧改一下,重新加载下配置试试。
-
-```
-# For more information on configuration, see:
-# * Official English Documentation: http://nginx.org/en/docs/
-# * Official Russian Documentation: http://nginx.org/ru/docs/
-# 将 user 配置改为 root
-user root;
-worker_processes auto;
-error_log /var/log/nginx/error.log;
-pid /run/nginx.pid;
-
-....
-```
-
-```sh
-nginx -s reload
-```
-
-问题没解决,还是 502,这可就触碰到笔者的知识盲区了,搜索一下吧,实在不行问问网管。
-
-这一搜啊,有一个搜索结果摘要引起了笔者的注意:
-
-> **解决SELinux阻止Nginx访问服务**
->
-> “在使用 yum 安装 nginx 后可能会出现配置完成后却无法访问的问题”。[1]
-
-笔者这一想,我们公司的网管当时让他安装 Nginx 没 5 分钟就告诉 OK 了,那很大可能是用 yum 安装的啊,作者博客让看一下 audit.log 有没有出现错误信息,笔者前去看了一下
-
-```sh
-tail -50 /var/log/audit/audit.log
-```
-
-果然和作者贴的图一模一样。
-
-
-
-> 根据作者所言,"出现此问题的原因是 SELinux 基于最小权限原则默认拦截了 Nginx 的请求,SELinux 是 Linux 的安全子系统,提供更安全的访问控制。"[1]
-
-解决方法,要么是直接关掉它,要么执行下方指令开启 HTTP 访问。
-
-```sh
-setsebool -P httpd_can_network_connect 1
-```
-
-执行后,立竿见影。忍不住感叹:Linux 知识学无止境。
-
-## 参考资料
-
-[1]解决SELinux阻止Nginx访问服务:https://blog.csdn.net/liweitao7610/article/details/107073852
diff --git a/docs/categories/issues/2022/09/23/解决无法重复读取请求体和响应体的问题.md b/docs/categories/issues/2022/09/23/解决无法重复读取请求体和响应体的问题.md
deleted file mode 100644
index 316c1d11a..000000000
--- a/docs/categories/issues/2022/09/23/解决无法重复读取请求体和响应体的问题.md
+++ /dev/null
@@ -1,182 +0,0 @@
----
-title: 解决无法重复读取请求体和响应体的问题
-author: 查尔斯
-date: 2022/09/23 20:55
-categories:
- - Bug万象集
-tags:
- - Java
- - "Spring Boot"
- - 过滤器
----
-
-# 解决无法重复读取请求体和响应体的问题
-
-## 项目场景
-
-**C:** 这两天实现了一个操作日志功能,需求是要记录指定操作的请求 URL,请求方式、请求头、请求体、响应码、响应头、响应体、请求耗时、操作人、操作IP、操作地址等信息。
-
-考虑了几种方案,结合以前的经验,排掉了 AOP,综合评估后这次采用的是 Spring 拦截器的方式来记录,大体的实现流程是:
-
-1. 提供一个 `@Log` 注解
-2. 在需要记录操作日志的接口类及方法上添加 `@Log` 注解,指定好资源名称和操作类型(具体为什么要在类和方法上都加,是考虑复用操作的资源名称)
-3. 提供一个拦截器,在拦截器中判断当前 Handler 是否存在 `@Log` 注解
-4. 存在该注解,就在 `preHandle()` 中开始计时,在 `afterCompletion()` 中结束计时并获取请求和响应信息
-5. 将请求和响应信息异步存储到数据库中
-
-
-## 问题描述
-
-流程很简单,但是在获取 requestBody(请求体)和 responseBody(响应体)时出了些问题。如果我在 `preHandle()` 中获取了请求体信息,那么对应 Handler 就无法获取了,反之如果我是在 `afterCompletion` 中获取请求体信息,那么就获取不到了。而对于响应体,在我获取完之后,向前端响应就没内容了。
-
-## 原因分析
-之所以如此,是由于请求体和响应体分别对应的是 InputStream 和 OutputStream,由于流的特性,使用完之后就无法再被使用了。
-
-```java
-/**
- * Retrieves the body of the request as binary data using a {@link ServletInputStream}. Either this method or
- * {@link #getReader} may be called to read the body, not both.
- *
- * @return a {@link ServletInputStream} object containing the body of the request
- *
- * @exception IllegalStateException if the {@link #getReader} method has already been called for this request
- *
- * @exception IOException if an input or output exception occurred
- */
-public ServletInputStream getInputStream() throws IOException;
-```
-
-```java
-/**
- * Returns a {@link ServletOutputStream} suitable for writing binary data in the response. The servlet container
- * does not encode the binary data.
- *
- *
- * Calling flush() on the ServletOutputStream commits the response.
- *
- * Either this method or {@link #getWriter} may be called to write the body, not both, except when {@link #reset}
- * has been called.
- *
- * @return a {@link ServletOutputStream} for writing binary data
- *
- * @exception IllegalStateException if the getWriter
method has been called on this response
- *
- * @exception IOException if an input or output exception occurred
- *
- * @see #getWriter
- * @see #reset
- */
-public ServletOutputStream getOutputStream() throws IOException;
-```
-
-想要解决的话就要想办法把这信息使用完再“塞回去”,直接“塞回去”是不可能的。
-
-
-## 解决方案
-
-为了解决这个问题,Servlet 提供了两个类 HttpServletRequestWrapper、HttpServletResponseWrapper,我们可以继承它们来实现请求体和响应体内容的缓存,达到重复读取请求体和响应体的目的。
-
-不过既然我们在使用 Spring 框架,贴心的 Spring 也提供了两个实现类:ContentCachingRequestWrapper、ContentCachingResponseWrapper,这样我们就无需再自行定义相应 Wrapper 直接使用它们就可以解决这个问题了。
-
-下面是在过滤器中对请求对象和响应对象进行包装处理的代码段:
-
-
-```java
-import org.springframework.core.Ordered;
-import org.springframework.stereotype.Component;
-import org.springframework.web.filter.OncePerRequestFilter;
-import org.springframework.web.util.ContentCachingRequestWrapper;
-import org.springframework.web.util.ContentCachingResponseWrapper;
-import org.springframework.web.util.WebUtils;
-
-import javax.servlet.FilterChain;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.util.Objects;
-
-/**
- * 缓存请求体和响应体过滤器
- *
- *
- * 由于 requestBody 和 responseBody 分别对应的是 InputStream 和 OutputStream,由于流的特性,读取完之后就无法再被使用了。
- * 所以,需要额外缓存一次流信息。
- *
- *
- * @author Charles7c
- * @since 2022/9/22 16:33
- */
-@Component
-public class ContentCachingWrapperFilter extends OncePerRequestFilter implements Ordered {
-
- @Override
- public int getOrder() {
- return Ordered.LOWEST_PRECEDENCE - 10;
- }
-
- @Override
- protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
- throws ServletException, IOException {
- // 包装流,可重复读取
- if (!(request instanceof ContentCachingRequestWrapper)) {
- request = new ContentCachingRequestWrapper(request);
- }
- if (!(response instanceof ContentCachingResponseWrapper)) {
- response = new ContentCachingResponseWrapper(response);
- }
-
- filterChain.doFilter(request, response);
- updateResponse(response);
- }
-
- /**
- * 更新响应(不操作这一步,会导致接口响应空白)
- *
- * @param response 响应对象
- * @throws IOException /
- */
- private void updateResponse(HttpServletResponse response) throws IOException {
- ContentCachingResponseWrapper responseWrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
- Objects.requireNonNull(responseWrapper).copyBodyToResponse();
- }
-}
-
-```
-
-下面是使用缓存对象来获取请求体或响应体的代码段,在你需要的地方使用就可以了:
-
-```java
-import org.apache.commons.io.IOUtils;
-// --------------------------------------------
-/**
- * 获取请求体
- *
- * @param request 请求对象
- * @return 请求体
- */
-private String getRequestBody(HttpServletRequest request) {
- String requestBody = "";
- ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
- if (wrapper != null) {
- requestBody = IOUtils.toString(wrapper.getContentAsByteArray(), StandardCharsets.UTF_8.toString());
- }
- return requestBody;
-}
-
-/**
- * 获取响应体
- *
- * @param response 响应对象
- * @return 响应体
- */
-private String getResponseBody(HttpServletResponse response) {
- String responseBody = "";
- ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
- if (wrapper != null) {
- responseBody = IOUtils.toString(wrapper.getContentAsByteArray(), StandardCharsets.UTF_8.toString());
- }
- return responseBody;
-}
-```
-
diff --git a/docs/categories/issues/2022/10/15/解决Windows桌面部分快捷方式图标变为空白的问题.md b/docs/categories/issues/2022/10/15/解决Windows桌面部分快捷方式图标变为空白的问题.md
deleted file mode 100644
index 0856cf40d..000000000
--- a/docs/categories/issues/2022/10/15/解决Windows桌面部分快捷方式图标变为空白的问题.md
+++ /dev/null
@@ -1,55 +0,0 @@
----
-title: 解决 Windows 桌面部分快捷方式图标变为空白的问题
-author: 查尔斯
-date: 2022/10/15 21:10
-categories:
- - Bug万象集
-tags:
- - Windows
----
-
-# 解决 Windows 桌面部分快捷方式图标变为空白的问题
-
-## 问题描述
-
-**C:** 今天把电脑系统从 Windows 11 换回了 Windows 10,使用了半个月的 Windows 11 真是一言难尽。
-
-换完系统就开始安装一些开发应用,安装了一会儿,突然发现桌面上 draw.io 应用快捷方式的图标变为了空白。
-
-
-
-
-
-## 解决方案
-
-1、打开本地应用数据存储位置(`C:\Users\用户名\AppData\Local`)
-
-按下 Windows + R 键,在弹出的运行对话框中输入 `%localappdata%`,回车确定。
-
-
-
-2、在打开的本地应用数据存储窗口中,找到并删除 `Iconcache.db` 文件
-
-::: tip 笔者说
-这是图标缓存文件,实际上我们的操作就是要删除图标缓存,让系统重新生成缓存。
-:::
-
-
-
-3、打开任务管理器
-
-按下 Windows + X 键,在弹出快捷菜单后,按下 T 键。
-
-::: tip 笔者说
-或者按 Ctrl + Alt + Delete 键,这个快捷键大家应该更熟悉。
-:::
-
-4、重新启动 `Windows 资源管理器` 应用
-
-右键单击 `Windows 资源管理器`,在弹出的菜单中选择 `重新启动`,屏幕会刷新一下。
-
-
-
-这时候回到桌面,就可以看到 draw.io 快捷方式的图标恢复正常显示了。
-
-
\ No newline at end of file
diff --git a/docs/categories/issues/2022/10/25/解决CentOS8执行yum安装报错.md b/docs/categories/issues/2022/10/25/解决CentOS8执行yum安装报错.md
deleted file mode 100644
index b3bd2c79f..000000000
--- a/docs/categories/issues/2022/10/25/解决CentOS8执行yum安装报错.md
+++ /dev/null
@@ -1,76 +0,0 @@
----
-title: "解决 CentOS 8 执行 yum install 报 Error: Failed to download metadata for repo 'appstream': Cannot prepare internal mirrorlist: No URLs in mirrorlist 的问题"
-author: 查尔斯
-date: 2022/10/25 21:20
-categories:
- - Bug万象集
-tags:
- - Linux
- - CentOS
----
-
-# 解决 CentOS 8 执行 yum install 报 Error: Failed to download metadata for repo 'appstream': Cannot prepare internal mirrorlist: No URLs in mirrorlist 的问题
-
-## 问题描述
-
-**C:** 今天,笔者在一台刚重装了 CentOS 8.2 操作系统的云服务器上正打算安装一下 Docker,结果开局安装依赖的环节就报错了。
-
-```
-CentOS Linux 8 - AppStream
-Error: Failed to download metadata for repo 'appstream': Cannot prepare internal mirrorlist: No URLs in mirrorlist
-```
-
-
-
-
-
-## 原因分析
-
-首先排除掉是安装 Docker 引起的错误,因为笔者起初还尝试了下把三个依赖分开安装,想看看是不是由于某个依赖安装引起的,最终发现无论执行 yum 安装哪一个包都会报这个错。
-
-而从报错信息的字面意思来看,应该是和 yum 的镜像源有关。
-
-```
-错误:从仓库 ‘appstream’ 下载元数据失败:无法准备内部镜像列表:镜像列表中没有 url
-```
-
-这可就触碰到笔者的盲区部分了,最不擅长的就是这类 Linux 系统的初始配置,所以笔者搜索了一下,发现了一个较为靠谱的答案。
-
-> 2020 年 12 月 8 号,CentOS 官方宣布了停止维护 CentOS Linux 的计划,并推出了 CentOS Stream 项目,CentOS Linux 8 作为 RHEL 8 的复刻版本,生命周期缩短,于 2021 年 12 月 31 日停止更新并停止维护(EOL),更多的信息可以查看 CentOS 官方公告。[1]
-
-## 解决方案
-
-解决起来也较为容易,那就是如果需要更新 CentOS,需要将镜像从 mirror.centos.org 更改为 vault.centos.org。
-
-1. 进入 yum 的 repos 目录
-
- ```shell
- cd /etc/yum.repos.d/
- ```
-
-2. 替换镜像
-
- ```shell
- sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*
- sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*
- ```
-
-3. 生成缓存
-
- ```shell
- yum makecache
- ```
-
-4. 更新
-
- ```shell
- yum -y update
- ```
-
-等待更新完之后,再执行 yum 安装就正常了。
-
-## 参考资料
-
-1. 【已解决】Error: Failed to download metadata for repo ‘appstream‘: Cannot prepare internal mirrorlist:https://blog.csdn.net/weixin_43252521/article/details/124409151
-1. CentOS 8 EOL如何切换源?:https://help.aliyun.com/document_detail/405635.html
-1. CentOS Project shifts focus to CentOS Stream:https://blog.centos.org/2020/12/future-is-centos-stream/
\ No newline at end of file
diff --git a/docs/categories/issues/2022/10/29/Docker设置网络代理.md b/docs/categories/issues/2022/10/29/Docker设置网络代理.md
deleted file mode 100644
index ada29db40..000000000
--- a/docs/categories/issues/2022/10/29/Docker设置网络代理.md
+++ /dev/null
@@ -1,88 +0,0 @@
----
-title: Docker 设置网络代理
-author: 查尔斯
-date: 2022/10/29 19:50
-categories:
- - Bug万象集
-tags:
- - Docker
- - Linux
- - CentOS
- - 网络代理
----
-
-# Docker 设置网络代理
-
-## 问题描述
-
-**C:** 今天笔者在公司的一台内网服务器上,打算用 docker-compose 拉起一套开发环境。结果刚回车完命令就报错了。
-
-```shell
-docker-compose up -d
-```
-
-
-
-```
-Error response from daemon: Get "https://registry-1.docker.io/v2/": x509: certificate signed by unknown authority
-```
-
-然后笔者又试了试 `docker pull`、`docker search` 这些命令,也都报这个错误。
-
-## 原因分析
-
-从报错提示上来看的话,笔者有两个怀疑的可能性:
-
-1. SSL 证书的问题
-2. 网络问题
-
-第 1 个怀疑主要是因为后面的提示部分:certificate signed by unknown authority,而且简单去搜了一下,确实有一些解决方案是冲着这个点解决的。
-
-第 2 个怀疑主要是因为前面的提示部分:Error response from daemon,前文已经提过了,这是一台内网机器,内网机器这个身份基本可以表明它本身是没有网络的,能上网也是因为设置了网络代理的原因。而且,笔者之前也记录过一个问题,那个问题产生的原因就是系统服务不会识别 `/etc/profile` 中设置的环境变量,docker 也是一种系统服务,所以这让笔者更倾向于是这种可能。
-
-## 解决方案
-
-既然有过类似的经验,那肯定就按之前的经验先操作一下试试。
-
-::: tip 笔者说
-摊牌吧,两种可能,笔者都搜了。但笔者太懒了,看了看第 1 种可能的解决方案步骤,实在懒得去操作试试。所以又去简单搜了一下 Docker 网络代理的设置,意外发现它的解决方案和笔者刚才提到记录过的问题解决方案一样,这也让笔者确定了问题的原因。
-:::
-
-首先,停止 docker 服务。
-
-```shell
-systemctl stop docker
-```
-
-然后,创建 docker 服务目录,并创建 HTTP 代理配置文件。
-
-```shell
-mkdir -p /etc/systemd/system/docker.service.d
-
-vi /etc/systemd/system/docker.service.d/http-proxy.conf
-```
-
-将下方配置贴到 HTTP 代理配置文件中,是的没错,就是添加了两个环境变量,这两个环境变量在 `/etc/profile` 中也设置过,详情见之前笔者记录过的一篇设置网络代理的文章。
-
-```shell
-[Service]
-Environment="HTTP_PROXY=http://用户名:密码@你的代理服务器地址:你的代理服务器端口号"
-Environment="HTTPS_PROXY=http://用户名:密码@你的代理服务器地址:你的代理服务器端口号"
-```
-
-最后,重新加载服务配置,重启服务。
-
-```shell
-systemctl daemon-reload
-systemctl restart docker
-```
-
-OK,再执行 docker 命令就没问题了。
-
-## 参考资料
-
-1. Control Docker with systemd#Custom Docker daemon options 之 HTTP/HTTPS proxy:https://docs.docker.com/config/daemon/systemd/#httphttps-proxy
-
-::: tip 笔者说
-这里提一下,官方文档真的很香。
-:::
diff --git a/docs/categories/issues/2022/11/04/解决Docker安装Prometheus启动报错的问题.md b/docs/categories/issues/2022/11/04/解决Docker安装Prometheus启动报错的问题.md
deleted file mode 100644
index c0c6daf1a..000000000
--- a/docs/categories/issues/2022/11/04/解决Docker安装Prometheus启动报错的问题.md
+++ /dev/null
@@ -1,90 +0,0 @@
----
-title: 解决 Docker 安装 Prometheus 启动报 permission denied 的问题
-author: 查尔斯
-date: 2022/11/04 20:30
-categories:
- - Bug万象集
-tags:
- - Prometheus
- - Docker
- - Linux
----
-
-# 解决 Docker 安装 Prometheus 启动报 permission denied 的问题
-
-## 问题描述
-
-**C:** 今天,笔者在使用 Docker 安装了 Prometheus 后,发现其容器没有能正常启动,而是处于持续重启的状态。笔者手动尝试了几次重启容器命令,依然如此。
-
-遇到这种情况,单纯去盯容器运行命令哪里有错误,排查和修复就慢了,不如先看看 Prometheus 容器的日志。
-
-```shell
-# docker logs 容器ID/容器名称
-docker logs prometheus
-```
-
-在容器日志中,笔者看到了几段重复性的日志内容,很显然这是几次重启容器出现的重复性日志,笔者从中截取了一段相对完整的日志内容。
-
-
-
-错误信息部分也很突出,level=error。
-
-```shell
-caller=query_logger.go:90 level=error component=activeQueryTracker msg="Error opening quer log file" file=/opt/bitnami/prometheus/data/queries.active err="open data/queries.active: permission denied"
-panic: Unable to create mmap-ed active query log
-```
-
-
-
-## 原因分析
-
-简单翻译一下错误信息 msg 及后面部分提示。
-
-> 信息:打开查询日志文件时出错 file=/opt/bitnami/prometheus/data/queries.active 错误:打开 data/queries.active:拒绝访问
-
-其中的关键信息是 `permission denied`(拒绝访问),从这字面意思上可以得知和权限有关。
-
-为了方便大家进行原因分析,笔者把 docker-compose 的 Prometheus 部分脚本贴在下方。
-
-```yaml
-version: '3'
-services:
- prometheus:
- container_name: prometheus
- image: bitnami/prometheus:2.38.0
- restart: always
- environment:
- TZ: Asia/Shanghai
- ports:
- - 19090:9090
- volumes:
- - /opt/disk/docker/volumes/prometheus/conf:/opt/bitnami/prometheus/conf
- - /opt/disk/docker/volumes/prometheus/data:/opt/bitnami/prometheus/data
- command:
- --config.file=/opt/bitnami/prometheus/conf/prometheus.yml
- --web.enable-lifecycle
- --storage.tsdb.retention.time=90d
- privileged: true
-```
-
-很明显,错误信息中的文件路径 `/opt/bitnami/prometheus/data/queries.active`,是 Prometheus 容器中的数据存储目录,笔者还将其挂载到了宿主机的 `/opt/disk/docker/volumes/prometheus/data` 目录。
-
-关于脚本中的两个挂载目录,笔者仅提前创建了 conf 配置目录,上传了配置文件。至于这个 data 数据目录则是 Docker 在运行容器时自动在宿主机创建的。
-
-那问题的原因已经呼之欲出了,很大可能是由于 Docker 在宿主机自动创建的 data 数据挂载目录没有写入权限。
-
-## 解决方案
-
-解决起来也较为容易,给 data 目录授予写入权限就好了。
-
-```shell
-chmod 775 /opt/disk/docker/volumes/prometheus/data
-```
-
-最后再重启一下 Prometheus 容器。
-
-```shell
-docker restart prometheus
-```
-
-此时,再通过 `docker ps` 命令查看 Prometheus 容器的状态,已经是正常的 Up 状态了。最后,笔者也是建议大家这类挂载目录尽量提前创建和授权。
diff --git a/docs/categories/issues/2022/11/06/解决DotNET安装后报错的问题.md b/docs/categories/issues/2022/11/06/解决DotNET安装后报错的问题.md
deleted file mode 100644
index fc837439b..000000000
--- a/docs/categories/issues/2022/11/06/解决DotNET安装后报错的问题.md
+++ /dev/null
@@ -1,70 +0,0 @@
----
-title: 解决 DotNet 安装完,报错:Couldn't find a valid ICU package installed on the system. Please install libicu using your package manager and try again
-author: 查尔斯
-date: 2022/11/06 15:35
-categories:
- - Bug万象集
-tags:
- - DotNet
- - Linux
- - CentOS
----
-
-# 解决 DotNet 安装完,报错:Couldn't find a valid ICU package installed on the system. Please install libicu using your package manager and try again
-
-## 问题描述
-
-**C:** 今天,笔者在一台 CentOS 7.9 服务器上手动安装完 DotNet 6.0.401 并配置好了环境变量之后,照例想查看一下是否安装成功。
-
-```shell
-dotnet --version
-```
-
-预想的版本信息没输出,倒是输出了这么一段错误。
-
-
-
-```c#
-Process terminated. Couldn't find a valid ICU package installed on the system. Please install libicu using your package manager and try again. Alternatively you can set the configuration flag System.Globalization.Invariant to true if you want to run with no globalization support. Please see https://aka.ms/dotnet-missing-libicu for more information.
- at System.Environment.FailFast(System.String)
- at System.Globalization.GlobalizationMode+Settings..cctor()
- at System.Globalization.CultureData.CreateCultureWithInvariantData()
- at System.Globalization.CultureData.get_Invariant()
- at System.Globalization.CultureInfo..cctor()
- at System.Globalization.CultureInfo.get_CurrentUICulture()
- at System.TimeZoneInfo.GetUtcStandardDisplayName()
- at System.TimeZoneInfo.CreateUtcTimeZone()
- at System.TimeZoneInfo..cctor()
- at System.DateTime.get_Now()
- at Microsoft.DotNet.Cli.Program.Main(System.String[])
-Aborted
-```
-
-
-
-## 原因分析
-
-简单翻译一下关键错误信息。
-
-> 进程终止。找不到系统上安装的有效 ICU 包。请使用包管理器安装 libicu,然后重试。或者,如果您想在不支持全球化的情况下运行,可以将配置标志 System.Globalization.Invariant 设置为 true。请访问 https://aka.ms/dotnet-missing-libicu 了解更多信息。
-
-从提示信息来看,问题的原因是当前系统没有安装 DotNet 需要的 `libicu` 库。
-
-## 解决方案
-
-实际上这也是因为笔者采用的手动安装方式才导致的问题,如果采用包管理器(在线)安装方式,这个 `libicu` 库会自动被安装好,也就不会出现这个问题了。
-
-
-
-知道了问题的原因,那就安装一下这个依赖库。
-
-```shell
-yum -y install libicu
-```
-
-安装完后,再执行查看版本命令,版本信息正常输出了。
-
-## 参考资料
-
-1. 在 CentOS 上安装 .NET SDK 或 .NET 运行时:https://learn.microsoft.com/zh-cn/dotnet/core/install/linux-centos
-2. 用于全球化的运行时配置选项:https://learn.microsoft.com/zh-cn/dotnet/core/runtime-config/globalization
diff --git a/docs/categories/issues/2022/11/23/解决Maven传递依赖污染的问题.md b/docs/categories/issues/2022/11/23/解决Maven传递依赖污染的问题.md
deleted file mode 100644
index 63be78646..000000000
--- a/docs/categories/issues/2022/11/23/解决Maven传递依赖污染的问题.md
+++ /dev/null
@@ -1,229 +0,0 @@
----
-title: 解决 Maven 传递依赖污染的问题
-author: 查尔斯
-date: 2022/11/21 21:30
-categories:
- - Bug万象集
-tags:
- - Maven
- - Java
-showComment: false
----
-
-# 解决 Maven 传递依赖污染的问题
-
-## 问题描述
-
-**C:** 下午遇到了一个很常见的情况,类似于下图:
-
-事情是这样的,笔者正想使用 `JSON` 工具来处理数据,结果 IntelliJ IDEA 提示笔者命名为 `JSON` 的工具类存在多个,到底要使用哪一个 `JSON`。
-
-
-
-再打开提示,可选择的一共有两个:
-
-1. com.alibaba.fastjson.JSON
-2. com.alibaba.fastjson2.JSON
-
-很明显,在笔者的项目中引入了 Fastjson、Fastjson2 两个依赖,这两个依赖中都有一个命名为 `JSON` 的工具类/接口。可问题来了,笔者仅在项目中显式引入了 Fastjson2 依赖,这个 Fastjson 依赖是哪来的?
-
-
-
-## 原因分析
-
-下面贴上笔者项目的 `pom.xml`,这当然不是笔者出问题的项目了,为了简单,笔者也没有特意创建父子项目场景,但其中关键的部分就是下面这些。很简单的项目依赖配置,锁定了 JustAuth 和 Fastjson2 的版本,并引入了这两个依赖。
-
-```xml
-
-
- 4.0.0
-
- org.example
- test-maven
- 1.0-SNAPSHOT
-
-
- 1.16.5
- 2.0.16
- 8
- 8
- UTF-8
-
-
-
-
-
-
-
- me.zhyd.oauth
- JustAuth
- ${justauth.version}
-
-
-
-
- com.alibaba.fastjson2
- fastjson2
- ${fastjson.version}
-
-
-
-
-
-
-
-
- me.zhyd.oauth
- JustAuth
-
-
-
-
- com.alibaba.fastjson2
- fastjson2
-
-
-
-```
-
-这问题,其实只要你全局搜索一下 `fastjson `,如果没有找到引入的话,那就是 Maven 传递依赖导致的结果了。如果想知道是谁传递依赖了它,那就打开 Maven 窗口,从每个项目模块的依赖项中挨个点开左侧有箭头的依赖。
-
-
-
-依赖多的话,这效率就有些”捉急“了,所以笔者建议采用另一种方式:分析依赖关系。
-
-
-
-
-
-根据依赖分析的结果,问题的原因就定位到了。虽然笔者没有显式引入 Fastjson 依赖,但因为 JustAuth 依赖了 Fastjson,所以笔者项目中就传递依赖了 Fastjson(如果多个依赖都依赖了 fastjson,而且版本还不一样,Maven 最后会生效哪个传递依赖?本篇重点不是介绍 Maven 的传递依赖“竞争”,所以这部分自行搜索一下吧,笔者仅是抛一下“砖”)。
-
-## 解决方案
-
-解决传递依赖其实很简单,最常见的有两种:
-
-1. 如果 JustAuth 是笔者写的项目,那么只需要将 Fastjson 依赖加上一个 optional 配置即可。
-
- ```xml
-
-
-
- com.alibaba
- fastjson
- 1.2.78
-
- true
-
-
- ```
-
-2. 如果你项目中已经有了 Fastjson 传递依赖,经过 Maven 传递依赖竞争还生效了这 JustAuth 中的传递依赖,那可以根据实际情况排除掉它。
-
- ```xml
-
- 1.16.5
- 8
- 8
- UTF-8
-
-
-
-
-
-
-
- me.zhyd.oauth
- JustAuth
- ${justauth.version}
-
-
-
- com.alibaba
- fastjson
-
-
-
-
-
-
-
-
-
-
- me.zhyd.oauth
- JustAuth
-
-
- ```
-
-可惜这两种方法,第 1 种不符合笔者情况,第 2 种倒是可以移除,但是 JustAuth 需要使用这个依赖啊。
-
-总结下笔者期望的效果:
-
-1. 笔者不希望在写代码时,被 IntelliJ IDEA 提示这个和笔者无关的依赖中的类。
-
-2. JustAuth 在使用时还能使用到这个依赖。
-
-
-其实关键是 IntelliJ IDEA 提示这块的问题,那 IntelliJ IDEA 这提示的来源是什么?实际就是它在编译项目时引入了这些依赖,那么这个依赖能不能不在编译时提供,而是仅在运行时提供呢?
-
-可以利用 Maven 的 scope(作用域)来尝试一下:
-
-1. 从 JustAuth 传递依赖中排除掉 Fastjson 依赖(不排除掉它,你也无法操控 JustAuth 中的依赖配置);
-
-2. 显式提供一个 Fastjson 依赖,并设置其 scope(作用域)为 runtime(显式声明的依赖自然可以自由配置,设置为 runtime 后 Maven 就仅在运行时提供该依赖了)。
-
-```xml
-
- 1.16.5
- 2.0.16
- 8
- 8
- UTF-8
-
-
-
-
-
-
-
- me.zhyd.oauth
- JustAuth
- ${justauth.version}
-
-
-
- com.alibaba
- fastjson
-
-
-
-
- com.alibaba
- fastjson
- 1.2.78
-
- runtime
-
-
-
-
-
-
-
-
- me.zhyd.oauth
- JustAuth
-
-
- com.alibaba
- fastjson
-
-
-```
-
-OK,问题解决,写完 `JSON` 这个单词,IntelliJ IDEA 没有任何犹豫的自动导入了笔者期望的包。
-
-
\ No newline at end of file
diff --git a/docs/categories/issues/index.md b/docs/categories/issues/index.md
deleted file mode 100644
index 1896e1386..000000000
--- a/docs/categories/issues/index.md
+++ /dev/null
@@ -1,13 +0,0 @@
----
-showArticleMetadata: false
-editLink: false
-lastUpdated: false
-showComment: false
----
-
-# Bug万象集
-
-::: tip 笔者说
-你读过的书,遇过的人,扛过的事,写过的 Bug,构成了你作为开发者的人生格局。
-
-:::
\ No newline at end of file
diff --git a/docs/categories/solutions/2021/11/18/用Java8获取近N天日期.md b/docs/categories/solutions/2021/11/18/用Java8获取近N天日期.md
deleted file mode 100644
index 69d7aece5..000000000
--- a/docs/categories/solutions/2021/11/18/用Java8获取近N天日期.md
+++ /dev/null
@@ -1,107 +0,0 @@
----
-title: 用Java8获取近N天日期
-author: 查尔斯
-date: 2021/11/18 20:55
-categories:
- - 方案春秋志
-tags:
- - Java
----
-
-# 用Java8获取近N天日期
-
-## 前言
-
-**C:** 登录进入管理类系统,首页一般都是以展示数据仪表盘为主。例如:展示一些总量、展示近一周或是近 N 天的某数据的折线图、柱状图等等。
-
-那在展示这类近 N 天的图表时,后端必然要给前端提供一个近 N 天的日期集合用于显示。
-
-至于实现的方法也有很多种,笔者在这儿就记录一种目前看来扩展性相对较好的方案。
-
-## 涉及技术栈
-
-- Spring Boot 2.3.1.RELEASE
-- MyBatis Plus 3.1.0(使用了 MyBatis Plus 的代码生成器)
-
-## Controller层
-
-```java
-/**
- * 统计控制器
- *
- * @author Charles7c
- * @date 2021/11/18 20:55
- */
-@Api(value = "统计接口", tags = "统计接口集")
-@RestController
-@RequestMapping("/statistics")
-public class StatisticsController {
-
- @Resource
- private IRequestService requestService;
-
- @GetMapping("/request/{days}")
- @ApiOperation(value = "日请求数据统计", notes = "日请求数据统计接口")
- public R> requestByDays(@ApiParam(value = "days", required = true) @PathVariable("days") Integer days) {
- return R.ok(requestService.getRequestTotal(day));
- }
-}
-```
-
-## Service层
-
-::: tip 笔者说
-Service 层接口内容略,这个应该对你没影响吧?
-:::
-
-```java {28,30,35,37}
-/**
- * 请求服务实现类
- *
- * @author Charles7c
- * @date 2021/11/18 20:55
- */
-@Service
-public class RequestServiceImpl extends ServiceImpl implements IRequestService {
-
- @Override
- public Map getRequestTotal(int days) {
- // 响应数据
- Map respMap = MapUtil.newHashMap(2);
-
- // 日期列表
- List dateList = new ArrayList<>(days);
- // 请求列表
- List requestList = new ArrayList<>();
- // ...
-
- // 指定日期格式
- DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
- // 遍历生成日期列表
- // 例如:days 为 5,现在是 2021-11-18,
- // 则可以获取到 2021-11-14、2021-11-15、2021-11-16、2021-11-17、2021-11-18
- for (int i = days - 1; i >= 0; i--) {
- // 当前日期 - i天
- LocalDateTime plusDate = LocalDateTime.now().plusDays(-i);
- // 将日期转换为 yyyy-MM-dd 格式字符串
- String plusDateStr = formatter.format(plusDate);
- // 添加到日期列表
- dateList.add(plusDateStr);
-
- // [根据日期查询指定统计数据列表(具体使用时,根据你自己需求决定查询什么表,什么字段...,此处仅为样例)]
- LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery();
- // 拼接 SQL,apply 用法,参见:https://mp.baomidou.com/guide/wrapper.html#apply
- queryWrapper.apply("date_format(create_time, '%Y-%m-%d') = {0}", plusDateStr);
- requestList.add(baseMapper.selectCount(queryWrapper).toString());
-
- // ...
- }
-
- // 添加响应数据
- respMap.put("dateList", dateList);
- respMap.put("requestList", requestList);
- // ...
- return respMap;
- }
-}
-```
diff --git a/docs/categories/solutions/2021/11/22/一条SQL查询今年每月统计信息.md b/docs/categories/solutions/2021/11/22/一条SQL查询今年每月统计信息.md
deleted file mode 100644
index f3d59301c..000000000
--- a/docs/categories/solutions/2021/11/22/一条SQL查询今年每月统计信息.md
+++ /dev/null
@@ -1,87 +0,0 @@
----
-title: 一条SQL查询今年每月统计信息
-author: 查尔斯
-date: 2021/11/22 18:22
-categories:
- - 方案春秋志
-tags:
- - SQL
----
-
-# 一条SQL查询今年每月统计信息
-
-## 前言
-
-**C:** 前不久,笔者介绍过一种统计近 N 天记录数的需求解决方案。今天,笔者再介绍一种也很常见的统计需求。
-
-::: info 示例需求: 统计今年每月的注册用户数。
-你可以基于这个示例需求,去完成各种类似的月统计需求。而且啊,笔者今天这个需求解决方案的重点是在 SQL 上,这类需求问题在 SQL 语句笔试上也挺常见,所以下回再见到类似的需求,你可以好好回想下本篇实现。
-:::
-
-## 涉及技术栈
-
-- Spring Boot 2.3.1.RELEASE
-- MyBatis Plus 3.1.0(使用了 MyBatis Plus 的代码生成器)
-- MySQL 5.6
-
-## Controller层
-
-略
-
-## Service层
-
-略
-
-## DAO层
-
-::: warning 笔者说
-记得要采用 LinkedHashMap,这样可以保证结果集的有序,即:1月、2月、......。
-:::
-
-```java
-@Select({
- "SELECT",
- "SUM(CASE MONTH(`create_time`) WHEN '1' THEN 1 ELSE 0 END) AS `1月`,",
- "SUM(CASE MONTH(`create_time`) WHEN '2' THEN 1 ELSE 0 END) AS `2月`,",
- "SUM(CASE MONTH(`create_time`) WHEN '3' THEN 1 ELSE 0 END) AS `3月`,",
- "SUM(CASE MONTH(`create_time`) WHEN '4' THEN 1 ELSE 0 END) AS `4月`,",
- "SUM(CASE MONTH(`create_time`) WHEN '5' THEN 1 ELSE 0 END) AS `5月`,",
- "SUM(CASE MONTH(`create_time`) WHEN '6' THEN 1 ELSE 0 END) AS `6月`,",
- "SUM(CASE MONTH(`create_time`) WHEN '7' THEN 1 ELSE 0 END) AS `7月`,",
- "SUM(CASE MONTH(`create_time`) WHEN '8' THEN 1 ELSE 0 END) AS `8月`,",
- "SUM(CASE MONTH(`create_time`) WHEN '9' THEN 1 ELSE 0 END) AS `9月`,",
- "SUM(CASE MONTH(`create_time`) WHEN '10' THEN 1 ELSE 0 END) AS `10月`,",
- "SUM(CASE MONTH(`create_time`) WHEN '11' THEN 1 ELSE 0 END) AS `11月`,",
- "SUM(CASE MONTH(`create_time`) WHEN '12' THEN 1 ELSE 0 END) AS `12月`,",
- "FROM `t_user`",
- "WHERE YEAR(`create_time`)= YEAR(NOW())"
-})
-LinkedHashMap countRegisterByMonth();
-```
-
-## SQL语句
-
-::: tip 笔者说
-这条 SQL 的思路就是将每条记录的 create_time(创建时间)求一下月份信息,求出的月份如果是对应的月份,那么就记为 1,否则记为 0,这样每月最后再做个 SUM 求和,就可以快速得到对应月份的记录数量了,不用 COUNT 依然可以计数。
-
-SQL语句单独放在下面,方便各位同学复制。:smile:
-:::
-
-```sql
-# 统计今年每月的注册用户数
-SELECT
- SUM(CASE MONTH(`create_time`) WHEN '1' THEN 1 ELSE 0 END) AS `1月`,
- SUM(CASE MONTH(`create_time`) WHEN '2' THEN 1 ELSE 0 END) AS `2月`,
- SUM(CASE MONTH(`create_time`) WHEN '3' THEN 1 ELSE 0 END) AS `3月`,
- SUM(CASE MONTH(`create_time`) WHEN '4' THEN 1 ELSE 0 END) AS `4月`,
- SUM(CASE MONTH(`create_time`) WHEN '5' THEN 1 ELSE 0 END) AS `5月`,
- SUM(CASE MONTH(`create_time`) WHEN '6' THEN 1 ELSE 0 END) AS `6月`,
- SUM(CASE MONTH(`create_time`) WHEN '7' THEN 1 ELSE 0 END) AS `7月`,
- SUM(CASE MONTH(`create_time`) WHEN '8' THEN 1 ELSE 0 END) AS `8月`,
- SUM(CASE MONTH(`create_time`) WHEN '9' THEN 1 ELSE 0 END) AS `9月`,
- SUM(CASE MONTH(`create_time`) WHEN '10' THEN 1 ELSE 0 END) AS `10月`,
- SUM(CASE MONTH(`create_time`) WHEN '11' THEN 1 ELSE 0 END) AS `11月`,
- SUM(CASE MONTH(`create_time`) WHEN '12' THEN 1 ELSE 0 END) AS `12月`
-FROM `t_user` # 根据自身需要确定实际业务表
-WHERE YEAR(`create_time`)= YEAR(NOW());
-```
diff --git a/docs/categories/solutions/2022/09/07/递归查询树型结构数据的性能优化方案.md b/docs/categories/solutions/2022/09/07/递归查询树型结构数据的性能优化方案.md
deleted file mode 100644
index 5bde267e8..000000000
--- a/docs/categories/solutions/2022/09/07/递归查询树型结构数据的性能优化方案.md
+++ /dev/null
@@ -1,239 +0,0 @@
----
-title: 递归查询树型结构数据的性能优化方案
-author: 查尔斯
-date: 2022/09/07 21:05
-categories:
- - 方案春秋志
-tags:
- - Java
- - 递归
- - 性能优化
----
-
-# 递归查询树型结构数据的性能优化方案
-
-**C:** 在日常开发中,像系统菜单、文件目录、多级分类这样的树型结构业务数据,我们往往会采用递归的方式来完成数据的查询处理。
-
-递归查询数据的确很方便,但稍微不注意就会造成较大的性能损耗,今天笔者就简单介绍一种优化方案。
-
-
-
-## SQL递归查询方案
-
-::: tip 查询思路
-1. SQL 查询一级数据
-2. 遍历一级数据
-3. 通过一级数据 **递归** SQL 查询二级...等子级数据
-:::
-
-```java {13,23,39,47}
-
-/**
- * 根据字典码查询树型字典
- *
- * @param code 字典码
- * @return 树型字典
- */
-public List listDictTreeByCode(String code) {
- // #####开始计时#####
- TimeInterval timer = DateUtil.timer();
-
- // 1、获取一级字典
- List oneLevelDictList = this.listDictByCodeAndParentId(code, null);
- if (CollUtil.isEmpty(oneLevelDictList)) {
- return null;
- }
-
- // 2、遍历一级字典
- List resultList = new ArrayList<>();
- oneLevelDictList.forEach(oneLevelDict -> {
- KeyValueItemVo result = new KeyValueItemVo(oneLevelDict.getDictKey(), oneLevelDict.getDictValue());
- // 3、递归获取子级字典
- result.setItems(getChildren(oneLevelDict.getDictId()));
- resultList.add(result);
- });
-
- // #####打印计时#####
- System.out.println("总耗时:" + timer.interval() + "ms");
- return resultList;
-}
-
-/**
- * 根据父ID查询子字典树
- *
- * @param parentId 父ID
- * @return 子字典树
- */
-private List getChildren(Long parentId) {
- List children = this.listDictByCodeAndParentId(null, parentId);
- if (CollUtil.isEmpty(children)) {
- return null;
- }
-
- List resultList = new ArrayList<>();
- children.forEach(child -> {
- KeyValueItemVo result = new KeyValueItemVo(child.getDictKey(), child.getDictValue());
- result.setItems(getChildren(child.getDictId()));
- resultList.add(result);
- });
- return resultList;
-}
-
-/**
- * 根据字典码和父ID查询字典列表
- *
- * @param code 字典码
- * @param parentId 父ID
- * @return 字典列表
- */
-public List listDictByCodeAndParentId(String code, Long parentId) {
- LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery()
- .eq(StrUtil.isNotBlank(code), SysDict::getCode, code)
- .orderByAsc(SysDict::getSort);
- if (parentId == null) {
- queryWrapper.isNull(SysDict::getParentId);
- } else {
- queryWrapper.eq(SysDict::getParentId, parentId);
- }
- return dictMapper.selectList(queryWrapper);
-}
-```
-
-这种方式执行后,控制台打印了好几屏幕的查询 SQL,而且层级越深,每级的数据越多,产生的 SQL 查询也会越多,程序的执行效率自然就会很低。
-
-
-
-## 程序递归处理方案
-
-既然发现了问题,那就要想办法进行优化。而性能优化除了要学治本外还要善用治标,方案一效率低的原因是由于产生了过多的 SQL 查询,对症下药自然就要减少 SQL 查询次数。
-
-::: tip 优化查询思路
-1. SQL 查询出所有数据
-2. 在程序中对数据进行整理
- 1. 过滤出一级数据
- 2. 遍历一级数据
- 3. 通过一级数据 **递归** 过滤二级...等子级数据
- :::
-
-```java {12-15,21-23,28,45-47,55}
-/**
- * 根据字典码查询树型字典
- *
- * @param code 字典码
- * @return 树型字典
- */
-public List listDictTreeByCode(String code) {
- // #####开始计时#####
- TimeInterval timer = DateUtil.timer();
-
- // 1、获取所有字典列表
- LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery()
- .eq(SysDict::getCode, code)
- .orderByAsc(SysDict::getSort);
- List dictList = dictMapper.selectList(queryWrapper);
- if (CollUtil.isEmpty(dictList)) {
- return null;
- }
-
- // 2、获取一级字典
- List oneLevelDictList = dictList.stream()
- .filter(dict -> Objects.isNull(dict.getParentId()))
- .collect(Collectors.toList());
- List resultList = new ArrayList<>();
- oneLevelDictList.forEach(oneLevelDict -> {
- KeyValueItemVo result = new KeyValueItemVo(oneLevelDict.getDictKey(), oneLevelDict.getDictValue());
- // 3、递归整理子级字典
- result.setItems(getChildren(oneLevelDict.getDictId(), dictList));
- resultList.add(result);
- });
-
- // #####打印计时#####
- System.out.println("总耗时:" + timer.interval() + "ms");
- return resultList;
-}
-
-/**
- * 根据父ID查询子字典树
- *
- * @param parentId 父ID
- * @param dictList 所有该类型子字典数据
- * @return 子字典树
- */
-private List getChildren(Long parentId, List dictList) {
- List children = dictList.stream()
- .filter(dict -> Objects.isNotNull(dict.getParentId()) && dict.getParentId().equals(parentId))
- .collect(Collectors.toList());
- if (CollUtil.isEmpty(children)) {
- return null;
- }
-
- List resultList = new ArrayList<>();
- children.forEach(child -> {
- KeyValueItemVo result = new KeyValueItemVo(child.getDictKey(), child.getDictValue());
- result.setItems(getChildren(child.getDictId(), dictList));
- resultList.add(result);
- });
- return resultList;
-}
-```
-
-优化查询后,我们再来看一下执行效果,控制台仅打印了一条查询 SQL,程序执行效率也有了很大的提升。
-
-
-
-
-::: details 涉及的 VO 类结构
-
-```java
-/**
- * 单级键值对VO
- *
- * @author Charles7c
- * @date 2022/9/7 21:23
- */
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Accessors(chain = true)
-public class KeyValueVo {
- /**
- * 文本
- */
- private Object label;
- /**
- * 值
- */
- private Object value;
-}
-```
-
-```java
-/**
- * 多级键值对VO
- *
- * @author Charles7c
- * @date 2022/9/7 21:23
- */
-@Data
-@NoArgsConstructor
-@Accessors(chain = true)
-@EqualsAndHashCode(callSuper = true)
-public class KeyValueItemVo extends KeyValueVo {
-
- /**
- * 子项
- */
- @JsonInclude(JsonInclude.Include.NON_EMPTY)
- private List items;
-
- public KeyValueItemVo(Object label, Object value, List items) {
- super(label, value);
- this.items = items;
- }
-
- public KeyValueItemVo(Object label, Object value) {
- super(label, value);
- }
-}
-```
-:::
\ No newline at end of file
diff --git a/docs/categories/solutions/index.md b/docs/categories/solutions/index.md
deleted file mode 100644
index c7cf3f9b5..000000000
--- a/docs/categories/solutions/index.md
+++ /dev/null
@@ -1,13 +0,0 @@
----
-showArticleMetadata: false
-editLink: false
-lastUpdated: false
-showComment: false
----
-
-# 方案春秋志
-
-::: tip 笔者说
-上学的时候除了有错题本之外,一般还会额外准备一个用来记录名言佳剧的本子,按当时的意图是希望日积月累来提升写作能力。
-在写代码的时候,经常会想到或遇到一些小方案,在此记录下来,大抵亦是如此。
-:::
\ No newline at end of file
diff --git a/docs/categories/tools/2021/01/14/初识Lombok.md b/docs/categories/tools/2021/01/14/初识Lombok.md
deleted file mode 100644
index 9789e9a75..000000000
--- a/docs/categories/tools/2021/01/14/初识Lombok.md
+++ /dev/null
@@ -1,413 +0,0 @@
----
-title: 初识 Lombok
-author: 查尔斯
-date: 2021/01/14 09:05
-categories:
- - 工具四海谈
-tags:
- - Java
- - Lombok
----
-
-# 初识 Lombok
-
-## 前言
-
-**C:** 在 Java 开发中,为了符合 `封装` 这一面向对象特性,在构建 JavaBean 时往往要加上 `getter/setter` 方法。
-
-在封装的概念里,`getter` 和 `setter` 方法是我们提供给外界的统一访问入口,我们可以在其中添加合理的逻辑控制语句,来处理一些业务或解决一些不合理的赋值,非常好的特性!
-
-但现代开发的实际使用中,我们编写的 JavaBean 的 `getter/setter` 方法体都是空的,显得非常冗余,但又不能去除。对此,我们在每次使用时,只能通过反复的心理暗示(IDE 自动生成快捷键、生成不费事儿)来麻痹自己。
-
-Eclipse 中是 `Alt + Shift+S > R`,IntelliJ IDEA 中是 `[FN] + Alt+Insert > Getter and Setter`。
-
-笔者相信,这些快捷键大家都很熟悉,甚至不只是它们,一般我们还会用上无参构造、带参构造、重写 `toString`、重写 `equals` 、`hashCode` 等生成快捷键,每次创建 JavaBean,写完属性之后就是一通 "火花带闪电" 快速生成,就像下面的代码一样。
-
-```java
-/**
- * 宠物类
- * @author Charles7c
- * @date 2020-01-14
- */
-public class Pet {
- // 属性声明
- /** 宠物名 */
- private String name;
- /** 健康值 */
- private int health;
-
- // getter/setter 方法
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public int getHealth() {
- return health;
- }
- public void setHealth(int health) {
- this.health = health;
- }
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- Pet pet = (Pet) o;
- return health == pet.health && name.equals(pet.name);
- }
- @Override
- public int hashCode() {
- return Objects.hash(name, health);
- }
- @Override
- public String toString() {
- return "Pet{" +
- "name='" + name + '\'' +
- ", health=" + health +
- '}';
- }
-}
-```
-
-毫无疑问,天下苦之久矣,于是 Lombok 出现了,旨在通过用简单的语法和注解(Annoation)代替众多的冗余代码,接下来我们就好好认识认识它。
-
-
-
-
-
-## 概述
-
-::: tip [Lombok](https://projectlombok.org/) 简介
-Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.
-
-Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more. [1]
-:::
-
-上方是 Lombok 官方的简介,看不懂的同学也没关系,笔者按它实际的体验简单给你介绍下。
-
-Lombok 是一个 Java 库,这个库提供了很多注解,这些注解会在代码编译的时候,帮助我们自动生成 `getter` 、`setter`、`equals`、`hashCode` 等等方法,这样我们就可以摆脱曾经的苦恼了。
-
-使用上它之后,你甚至会觉得在使用一个基于 Java 构建的新编程语言,下面就是 Lombok 优化上述 Pet 类的冗余代码后的效果。
-
-```java
-/**
- * 宠物类
- * @author Charles7c
- * @date 2020-01-14
- */
-@Data
-public class Pet {
- // 属性声明
- /** 宠物名 */
- private String name;
- /** 健康值 */
- private int health;
-}
-```
-
-一个 `@Data` 注解直接可以替代掉那些冗余方法们。怎么样,还不错吧。
-
-## 安装
-
-### IDEA插件安装
-
-::: tip 笔者说
-截止笔者发文时, IntelliJ IDEA 的 2020.3 版本已经发布了,这一版开始,已经预装了 Lombok 插件,意味着你如果用的这版及之后,可以不用看这一小节了。
-:::
-
-因为 Lombok 是在项目编译时,根据我们使用的注解,生成我们不想写的冗余代码。 但是 IDE 们可不认识它这一套 "骚操作",你在 JavaBean 中没写对应的 `getter/setter` 等方法,那么在 IDE 中写代码时想使用对应 JavaBean 的这些方法就是 "天方夜谭"。
-
-也就是说如果你在类中没写 `getter/setter` ,那在 IDE 中编写类时,哪怕已经标注了`@Data`,也无法让 IDE 提示及使用它们。
-
-想要在 IDE 中正常使用 Lombok,必须先在 IDE 中安装对应的 Lombok 插件。Lombok 为主流的 IDE 提供了插件支持,我们本次就以 IntelliJ IDEA 为例,来安装下插件,并测试使用效果。
-
-
-
-其实 Lombok 官网对各种 IDE 如何安装插件是有教程的([IDEA的教程](https://projectlombok.org/setup/intellij))。笔者个人认为写得已经很详细了,那接下来笔者就带大家实际操作一遍,你来实现的话记得按我的步骤来。
-
-首先打开 IDEA,在菜单 `File` 中找到 `Settings` 子菜单。
-
-
-
-在弹出的对话框中,选择 `Plugins`,然后在 `Marketplace` 插件市场中输入 `lombok` 回车搜索,搜索到了点击`Install` 安装就可以了。
-
-
-
-安装成功后,直接点击 `Restart IDE` 重启 IDEA,插件就安装成功了。
-
-
-
-### 引入依赖
-
-::: tip 笔者说
-如果你使用了 Spring Boot,Lombok 的版本已经被 Spring Boot 锁定了,意味着你可以不用指定版本,只需要引入 Lombok 依赖即可。
-:::
-
-安装好插件后,就可以在你的项目中引入 Lombok 依赖来使用了。
-
-Maven 依赖如下:
-
-```xml
-
- org.projectlombok
- lombok
- 1.18.24
-
-```
-
-Gradle 依赖如下:
-
-```groovy
-compile group: 'org.projectlombok', name: 'lombok', version: '1.18.24'
-```
-
-## 常用注解
-
-Lombok 的学习使用,就是要学习它的注解们。但的确很多,笔者要是全来一遍,时间花费可不少而且意义不大,所以我们挑一些常见常用的来介绍演示一下就可以了。
-
-对这些注解的使用,笔者奉行基本应用即可,特殊的类,麻烦的属性,不建议采用,以免"上头"。如果你到时候真的需要,自行搜索一下 [官网文档](https://projectlombok.org/features/all) 即可。
-
-### @NonNull
-
-我们在设值时,经常要进行非空判断,因为为空后再进行处理可能会引发业务错误,`@NonNull` 注解可以帮助我们简化此类代码。
-
-它可以用在属性或方法形参上,如果标注的属性/参数为空,则抛出 `NPE(NullPointerException)`。
-
-::: tip 笔者说
-下图中,左侧是我使用 Lombok 的代码,右侧是 `mvn compile` 之后,从 `target` 文件夹中查看的反编译后的代码(这些多出来的代码,都是 Lombok 在编译时自动生成的),后续示例笔者不再解释这一点。
-:::
-
-
-
-### @Getter/@Setter
-
-`@Getter` 和 `@Setter` 这两个注解,顾名思义就是为我们生成对应的 `getter/setter` 方法的,一般情况下直接在类上声明就可以了,这样类中所有的非静态私有属性都会生成 `public` 修饰的 `getter/setter` 方法了。
-
-::: tip 笔者说
-生成的 `getter` 遵循布尔属性的约定,例如:`boolean` 类型的 `deleted` 生成的 `getter` 方法为 `isDeleted` 而不是 `getDeleted`。
-:::
-
-
-
-当然,如果你只是想为部分属性生成对应的 `getter/setter` 方法,就在属性左侧或上方添加注解即可,它们默认生成的方法都是 `public` 修饰的。如果你想调整访问级别,可以通过注解内的属性值来进行设置。
-
-
-
-`@Getter` 和 `@Setter` 的确挺好用的,笔者个人比较喜欢使用,诚意推荐。
-
-还有同学可能担心,如果有一天需要自己在 `getter/setter` 方法中添加逻辑控制怎么办?不用担心,你直接正常写对应的方法即可,因为 Lombok 检测到你写了之后就不会生成了(这点 "眼力见儿" 还是有的)。
-
-### @ToString
-
-`@ToString` 就是帮助我们生成非静态属性的 `toString` 方法的一个注解,它的使用也很简单,一般情况下都是直接在类上面添加即可。
-
-有些时候我们在 `toString` 方法中,不想出现某个属性,可以直接在该属性上标注`@ToString.Exclude` 或采用 `@ToString(exclude={"属性名",....)` 来排除。
-
-
-
-还有,如果你不想 `toString` 方法输出属性名,那么 `includeFieldNames=false` 适合你。
-
-
-
-另外如果一个子类想要在 `toString` 中也输出父类的 `toString` 内容,那么在 `@ToString` 内添加 `callSuper` 属性为 `true` 即可,但注意父类必须也重写 `toString`,否则肯定是使用 `Object` 类默认的 `toString` 方法返回值了。
-
-
-
-### 构造类注解
-
-构造方法也是我们经常要生成的代码了,Lombok 提供了三个注解来满足你的各种构造需求。
-
-- `@NoArgsConstructor` 可以自动生成无参数构造方法
-- `@AllArgsConstructor` 可以按顺序自动生成所有参数的构造方法
-- `@RequiredArgsConstructor` 仅为标注了 `@NonNull` 注解的属性生成构造方法
-
-我举一个 `@AllArgsConstructor` 注解的例子。
-
-
-
-### @Data
-
-在上方 Lombok 概述的示例中,我们就看到了 `@Data` 这个注解,大多数人都喜欢使用此注解。但笔者认为,如果你没有特别需求,仅仅要生成 `getter/setter` 方法,那使用 `@Getter` 和 `@Setter` 就可以了。
-
-看看下方的示例吧,我仅仅写了一个干干净净的 Pet 类,但是在使用时,它却提示出很多的方法,像 `setter`、`getter`、`equals` 等都提供出来了,显然是 Lombok 的 "锅",它等价于 `@ToString`、 `@EqualsAndHashCode`, `@Getter`、`@Setter`、`@RequiredArgsConstructor` 等注解的集合。
-
-
-
-### @Cleanup
-
-我们在使用 IO 系 API 时,需要自行进行资源管理(关流),`Java 7` 中上线了 `try-with-resources` 语法来帮助我们实现自动关流,现在 Lombok 也来 "抢活" 了。它提供了一个注解来帮助你自动生成资源管理代码,孰优孰劣自行选择吧。
-
-```java
-// Java 7的try-with-resources
-try(InputStream in = new FileInputStream("d:\\a.txt")){
- // ....
-}catch (Exception e){
- e.printStackTrace();
-}
-```
-
-`@Cleanup` 注解使用后及其反编译代码如下。
-
-::: tip 笔者说
-因为 Lombok 编译生成的代码涉及到 `close()` 方法调用,所以需要提前抛出一个 IOException,否则编译会失败。
-:::
-
-
-
-### @Synchronized
-
-为了解决多线程不安全的问题,我们经常使用 `synchronized` 关键字来进行加锁,可以加在方法上也可以使用代码块来加锁。
-
-`@Synchronized` 注解的效果和 `synchronized` 关键字一样,它可以用在类方法或者实例方法上,是`synchronized` 关键字更安全的变体。
-
-区别在于锁对象不同,对于类方法和实例方法,`synchronized` 关键字的锁对象分别是类的 `class` 对象和 `this` 对象,而 `@Synchronized` 的锁对象则分别是私有静态 final 对象 `$LOCK` 和私有 final 对象 `$lock`。当然,也可以自己指定锁对象。
-
-
-
-### @Builder
-
-`@Builder` 用在类上,是类似于构建者模式的一个注解。它的作用就是帮你生成一套 `builder APIs`,将对象构建过程和细节进行封装,更简单和优雅的实现对象的创建及赋值。我们看个对象创建的对比例子吧。
-
-```java
-// 不使用 @Builder 注解 常见的创建对象方式
-Pet pet = new Pet();
-pet.setName("小白");
-pet.setHealth(100);
-// ......
-
-// 使用@Builder注解后 创建对象
-// 在IDEA中,你直接一路敲下来就可以了 链式编程简直不要太舒服
-Pet pet1 = Pet.builder()
- .name("小白")
- .health(100)
- .build();
-```
-
-那么 `@Builder` 注解实际帮助我们添加了什么代码呢?看看反编译后的内容吧。
-
-```java
-public class Pet {
- private String name;
- private int health;
- // 1.一个构造
- Pet(String name, int health) {
- this.name = name;
- this.health = health;
- }
- // 2.一个静态构建方法 返回构建类对象
- public static Pet.PetBuilder builder() {
- return new Pet.PetBuilder();
- }
- // 3.一个静态内部构建类
- public static class PetBuilder {
- private String name;
- private int health;
-
- PetBuilder() {
- }
- // 4.属性同名赋值方法
- // 【调用完还会返回构建对象,这样可以继续调用其他方法】
- public Pet.PetBuilder name(String name) {
- this.name = name;
- return this;
- }
-
- public Pet.PetBuilder health(int health) {
- this.health = health;
- return this;
- }
- // 5.最终构建对象方法
- public Pet build() {
- return new Pet(this.name, this.health);
- }
- // 6.一个toString
- public String toString() {
- return "Pet.PetBuilder(name=" + this.name + ", health=" + this.health + ")";
- }
- }
-}
-```
-
-### 日志类注解
-
-在开发中,我们常常要使用 log 来记录程序执行过程,Lombok 为我们提供了6种注解,根据不同的注解将生成不同类型的 log 实例,但是实例名称都是 `log`。
-
-- `@CommonsLog`
-- `@Log`
-- `@Log4j`
-- `@Log4j2`
-- **`@Slf4j(推荐)`**
-- `@XSlf4j`
-
-```java
-@Slf4j
-@Controller
-@RequestMapping("/user")
-public class UserController {
- @GetMapping
- public Result findAll() {
- // 直接使用 log 实例
- log.info("用户列表查询");
- }
-}
-```
-
-::: tip 笔者说
-SLF4J(Simple Logging Facade For Java,为 Java 提供的简单日志门面)。在阿里巴巴 Java 开发手册日志规约中强调,如果要使用日志 API ,必须使用日志门面 API 而不是具体日志框架的 API。
-
-
-
-所谓日志门面,其实就是类似于 Java 的 JDBC 一样的一套 API ,有了 JDBC,你无需关心未来切换成哪种关系型数据库,因为获取连接等方法用的是 JDBC 的 API 。
-
-同样日志门面,可以让我们无需关心未来切换哪种日志框架,因为获取日志实例用的是日志门面的 API。
-:::
-
-## Java 14 新特性 Records
-
-2020年3月17日,Java 14 正式 `GA`,虽然我们目前仍然主要使用 Java 8,但更新肯定是趋势,未来升级到 14 或更高的某个版本只是时间问题,所以这些新特性我们还是应该关注关注的。
-
-在 Java 14 中有一个预览特性 `Records`,Java 15 对该特性又进行了二次预览,`Records` 提供了一种紧凑的语法来声明类(我们经常用来做类声明的方式有 `class`、`enum` 等,这回又多一个),以帮助开发者写出更简洁的代码。
-
-该特性主要用在特定领域的类,这些类主要用于保存数据,不提供领域行为。再通俗的讲就是我们可以给一些简单的,一般不提供业务操作的类(`POJO类`等)更改下声明类的方法了。更多详情请查看:[Records的官方介绍](https://openjdk.java.net/jeps/359)
-
-
-
-来个简单的语法示例吧?看看下面的代码,它也自动解决了头疼的 `getter`、`setter`、`equals`、`hashCode`、无参构造等。
-
-```java
-// record 声明类语法
-{ClassModifier} record TypeIdentifier [TypeParameters]
- (RecordComponents) [SuperInterfaces] [RecordBody]
-// 示例代码:注意这是类,不是方法
-public record Pet(String name,int health){
-
-}
-```
-
-## 参考资料
-
-[1]Project Lombok 简介:https://projectlombok.org
-
-[2]Lombok常用注解:https://www.cnblogs.com/mayhh/p/10113169.html
-
-[3]Records介绍:https://openjdk.java.net/jeps/359
-
-## 后记
-
-Lombok 入门到这里也就介绍完了,感觉怎么样?这只是个入门示例,如果想再多研究研究其他注解和属性,那需要更多的篇幅和时间。
-
-实际上,在现在的业内,Lombok 的使用存在着争议,有些人认为它是 `开发利器`,有些人 `避之不及`。认为它好的是因为它的确减少了大量的冗余代码,相当于写代码随时带个生成器,阅读代码也变得更加清晰直观;认为它不好的是因为它的使用还需要安装额外的插件,且是侵入性的设计(有些人认为,它这种改变语法的事应该是语言本身自己该做的,它 "越俎代庖" 了),如果开发中 IDEA、JDK、Lombok 不配套,那结果显而易见;还有些人认为它影响了业务控制代码的添加和阅读。
-
-孰是孰非,仁者见仁智者见智。笔者个人觉得,自己平时练习和测试都可以随便用用,如果上升到公司,还是应该以实际和团队整体出发。
-
-
-
-
-
-::: info 笔者说
-对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
-
-所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
-:::
-
diff --git a/docs/categories/tools/2021/02/22/RDM快速入门.md b/docs/categories/tools/2021/02/22/RDM快速入门.md
deleted file mode 100644
index 99f8dc483..000000000
--- a/docs/categories/tools/2021/02/22/RDM快速入门.md
+++ /dev/null
@@ -1,141 +0,0 @@
----
-title: Redis Desktop Manager 快速入门
-author: 查尔斯
-date: 2021/02/22 09:41
-categories:
- - 工具四海谈
-tags:
- - Redis
- - 管理工具
----
-
-# Redis Desktop Manager 快速入门
-
-## 前言
-
-这个时代,Redis 多流行啊,10 个程序员起码有 8 个听过用过。人多了,自然有人不太喜欢使用命令行来直接操作 Redis。所以,在官方没有提供的情况下,大家一直都在寻求一款好用的 Redis 客户端管理工具,而 RDM 这款软件,在咱们国内 IT 圈子里不说人尽皆知吧,也可以说的上是小有名气的。
-
-::: tip 简介
-RDM,全称 Redis Desktop Manager。它是一个快速、简单、支持跨平台的 Redis 桌面管理工具,基于 Qt 5 开发,支持通过 SSH Tunnel 连接 [1]。它开源在了 GitHub [2] 上。
-:::
-
-
-
-长下面这样。在当前 Redis 客户端工具圈里可以说的上是 “高颜值”,而且也比较实用。
-
-
-
-但是很可惜,0.9.3.817 是它的最后一个免费版。
-
-你可能会比较好奇,它不是开源的吗?的确,它是开源的,但也仅仅是开源,即开放源代码。而大多数开源软件都会免费提供安装包,但 RDM 从 0.9.3.817 版本开始就不再免费提供了。
-
-这意味着什么?如果你懂一定的相关技术,自然可以利用它的源代码自己编译一个。而如果你不懂?不好意思,那就买它吧!看看下方的价格,其实也算良心价了。
-
-
-
-
-
-当然,笔者不是来刺激你的,早就给你准备好了一份 RDM 的第三方编译版 。
-
-
-
-
-
-在 GitHub 上,这类 RDM 第三方编译版还是挺多的,你也可以自己去搜索一下。
-
-
-
-
-
-## 下载
-
-笔者这里以 rdm-builder 这个 GitHub 仓库为示例,来介绍下第三方编译版的 Windows 版 RDM 下载和安装。
-
-打开这个仓库之后,在右侧的 Releases 显示它有15个发行版,最新的是 v2021.2 版,这也是随着官方来更新的。等你看到这篇文章的时候,或许它已经变成了更新的版本。
-
-点击最新发行版,跳转到版本下载页面。
-
-
-
-然后再点击 `xxx.exe` 即可开始下载这个第三方编译的 Windows 版 RDM 了。
-
-
-
-
-下载好了,一个普普通通的可执行程序。
-
-
-
-## 安装
-
-接下来,我们 “傻瓜式” 安装即可。
-
-
-
-
-
-改动一下安装位置,这个目录专门放开发工具,是笔者以前逐渐养成的个人习惯。
-
-
-
-
-
-
-
-
-
-## 连接服务器
-
-安装完成后,直接打开。因为不是最新版,所以每次都会弹出这个更新提示框。别管,直接点 [OK] 就行。
-
-
-
-RDM 使用起来还是比较容易的,点击左上角的 [连接到 Redis 服务器]。
-
-
-
-进入到连接设置之后,依次填写 [连接名称,Redis 服务器地址,Redis 密码(可选),用户名(可选)],可以先点击 [测试连接] 查看下是否可以连接成功。
-
-
-
-## 常见使用
-
-虽然,本篇笔者重点是给你安利第三方编译的 RDM,但思来想去还是决定为部分小白们介绍一下 RDM 的简易操作,会用的老白们就不用看了。
-
-### 查看所有数据库
-
-测试连接成功后,双击连接名,就可以看到当前 Redis 服务器的所有数据库。
-
-
-
-### 存储键
-
-
-
-### 修改值
-
-
-
-### 修改过期时间
-
-
-
-### 删除键
-
-刚才我们给 `name` 这个键设置了5秒过期之后,唯一存储的数据也没了,我们再新建一个,然后来测试一下删除功能。
-
-
-
-### 命令行操作
-
-不仅如此,当你想用命令行操作时,RDM 还可以直接打开控制台连接 Redis 服务器。
-
-
-
-## 参考资料
-
-[1]Redis Desktop Manager 介绍:https://www.oschina.net/p/redisdesktop?hmsr=aladdin1e1
-
-[2]RDM GitHub 地址:https://github.com/uglide/RedisDesktopManager/
-
-[3]RDM 的第三方编译版:https://github.com/FuckDoctors/rdm-builder
\ No newline at end of file
diff --git a/docs/categories/tools/2021/03/04/ARDM快速入门.md b/docs/categories/tools/2021/03/04/ARDM快速入门.md
deleted file mode 100644
index 73e702c35..000000000
--- a/docs/categories/tools/2021/03/04/ARDM快速入门.md
+++ /dev/null
@@ -1,132 +0,0 @@
----
-title: Another Redis Desktop Manager 快速入门
-author: 查尔斯
-date: 2021/03/04 23:19
-categories:
- - 工具四海谈
-tags:
- - Redis
- - 管理工具
----
-
-# Another Redis Desktop Manager 快速入门
-
-## 前言
-
-之前,笔者发布过一篇有关于 Redis 可视化客户端:RDM 的介绍文章,有童鞋看了后留言给笔者说:“RDM 不怎么好用,建议试试 Another Redis Desktop Manager。“
-
-实际上,虽然笔者发的是 RDM,但近段时间一直在用的就是 ARDM。当然了,这不是笔者藏着掖着,而是因为资源不得一点点发吗?而且,还是有童鞋在用 RDM 的,习惯一个工具之后,随便更换也是需要学习成本不是?
-
-
-
-OK,本篇笔者就要给童鞋们分享一下这个所谓的 ARDM。
-
-
-
-## 简介
-
-::: tip 简介
-Another Redis Desktop Manager,一个更快,更好,更稳定的 Redis 桌面管理器,兼容 Linux, windows, mac。更重要的是,它不会在加载大量的键时崩溃。[1]
-
-顾名思义,Another Redis Desktop Manager 就是 另一个 RDM 的意思,它在功能方面和 RDM 大体没什么区别,不过在 UI 和体验上的确更胜一筹。
-
-这个项目从 2019 年 2 月份就开始了,开源且免费提供打包版本,更新到今天也不短的时间了,所以关于稳定性的问题就暂时不用担心了。[2]
-:::
-
-
-
-## 下载
-
-打开 GitHub 直接搜索 AnotherRedisDesktopManager 项目,然后点击项目右下方的最新发行版,就可以跳转到对应的版本下载页面了。
-
-
-
-当然,也可以直接复制本文最后参考资料 [2] 的下载地址,然后在 PC 浏览器打开,也同样可以跳转到最新版的下载页面。
-
-然后,根据你的系统情况选择合适的版本下载吧。
-
-
-
-下载好了,一个平平无奇的 exe 安装包。
-
-
-
-## 安装
-
-接下来,我们 “傻瓜式” 安装即可。
-
-
-
-改动一下安装位置,这个目录专门放开发工具,是笔者以前逐渐养成的个人习惯。
-
-
-
-
-
-
-
-## 连接服务器
-
-安装完成后,直接打开,界面可真是简洁到家了。
-
-
-
-连接服务器的步骤也和 RDM 差不多,点击左上角的 [新建连接]。
-
-进入到新建连接界面之后,依次填写 [Redis 服务器地址,Redis 端口号,Redis 密码,连接名] 后即可点击 [确定] 来新建一个连接。
-
-::: tip 笔者说
-如果你要连接的 Redis 就在本机,并且你没改过什么默认设置(端口、密码等),你甚至只需要在这个界面点一下确定就可以新建好一个连接。
-:::
-
-
-
-
-
-## 常见使用
-
-虽然,笔者觉得 ARDM 和 RDM 在功能上大体是一样的,但为了照顾小白们的感受,笔者还是按当初介绍 RDM 的步骤再演示一下常见操作。
-
-### 查看所有键
-
-单击连接名,就可以打开单个连接,默认是处于 0 号数据库,可以根据需求进行数据库切换。另外,打开连接时默认还会在右侧打开当前 Redis 的服务监控。
-
-
-
-### 存储键
-
-ARDM 中存储键是先新增一个 key,这个 key 默认什么也没存储,你需要再为这个 key 设置下 value,这一步实际就是下面的修改操作。
-
-
-
-### 修改值
-
-
-
-### 修改过期时间
-
-
-
-### 删除键
-
-刚才我们给 `name` 这个键设置了 5 秒过期之后,唯一存储的数据也没了,我们再新建一个,然后来测试一下删除功能。
-
-
-
-### 命令行操作
-
-当你想用命令行操作时,ARDM 同样也可以直接打开控制台连接 Redis 服务器。
-
-
-
-## 参考资料
-
-[1]Another Redis Desktop Manager GitHub 地址:https://github.com/qishibo/AnotherRedisDesktopManager
-
-[2]Another Redis Desktop Manager 下载地址:https://github.com/qishibo/AnotherRedisDesktopManager/releases
-
-## 后记
-
-**C:** 好了,ARDM 的介绍就到这儿结束了,至于其他的功能,自行去发现体验吧,那样才更有乐趣,不是吗?
-
-当然,本篇介绍完,笔者暂时就不会再推荐其他 Redis 可视化客户端了,也许后面有后起之秀,到那时候再说吧,也欢迎童鞋们再留言告诉我。
\ No newline at end of file
diff --git a/docs/categories/tools/2021/03/06/Postman快速入门.md b/docs/categories/tools/2021/03/06/Postman快速入门.md
deleted file mode 100644
index f8e1a859b..000000000
--- a/docs/categories/tools/2021/03/06/Postman快速入门.md
+++ /dev/null
@@ -1,227 +0,0 @@
----
-title: Postman 快速入门
-author: 查尔斯
-date: 2021/03/06 17:55
-categories:
- - 工具四海谈
-tags:
- - Java
- - API
----
-
-# Postman 快速入门
-
-## 前言
-
-近两年前后端分离开发成为了主流趋势,前端可以专心实现自己的客户端样式和交互,而后端可以更多关注业务逻辑的处理。
-
-在后端开发了接口之后,需要进行测试工作,而由于前端的拆分,这回想要测试一下,可没有相应的页面能够提供功能入口了。
-
-莫非,要通过浏览器地址栏来进行测试?但这些接口又不仅仅是 GET 请求方式。而且,有些接口的要求很是复杂,需要传递请求头或更为复杂的参数。再不成,后端自己写个简单页面 demo 来进行测试?那也太 Low 了!
-
-本篇,笔者就要为后端开发推荐一款强大的测试工具,这个工具可是笔者从测试那儿 GET 到的。
-
-
-
-
-
-## 简介
-
-::: tip Postman 简介
-Postman 是一款功能强大的,网页调试与发送网页 HTTP 请求的工具,通过 Postman 我们可以发送几乎任何请求方式的请求,也可以附带各种类型的请求头、请求参数。
-:::
-
-
-
-## 下载
-
-Postman 最初是谷歌浏览器的一款插件,后来火了之后,人家自己也开发了相应的客户端以及 Web 端。笔者在本篇就以客户端来进行示例使用,你要是不喜欢这两种,也可以去百度找一下它的插件版。
-
-复制文章最后参考资料 [1] 的地址,然后粘贴到你的 PC 浏览器地址栏,访问之后就可以点击 [Download the App] 按钮,然后根据自己系统情况来进行客户端下载了。
-
-::: tip 笔者说
-你点击 [Download the App] 按钮的时候,它会弹出一个下拉框,让你选择 Windows 系统的某个位数版本,然后再开始下载。
-
-但实际上 Postman 也有 Mac、Linux 系统的版本,就在下载按钮下方 [Not your OS?] 那儿,点击对应系统版本的链接就可以下载了。
-:::
-
-
-
-下载好了,一个平平无奇的 exe 安装包。
-
-
-
-## 安装
-
-双击 exe 安装包,Postman 就会不识抬举的自行完成安装,想必又是装在了 C 盘。
-
-
-
-安装完成后,双击桌面上出现的 Postman 图标,打开后的 Postman 首屏如下。
-
-Postman 在首屏极力推荐我们进行注册,注册后可以实现云端同步备份,如果你有这需求可以创建一个。当然,点击 [Skip and go to the app] 跳过这一步,直接进入主界面也行。
-
-::: tip 笔者说
-如果一会儿体验过打算长期使用起来,别忘了注册个账号,还可以云端同步。
-:::
-
-
-
-和最初版本比起来,Postman 更新的还是挺快的,功能也更加丰富了,但对于咱们来讲,只需要重点关注好它的核心功能即可。
-
-
-
-安装好后,笔者将带各位同学,学习 Postman 中的三种基本操作,这也是 Postman 在应用主界面首要推荐你尝试的。
-
-在开始前,我们先准备一个数据接口,笔者这里注册并申请了天行数据的机器人 API [2],你也可以用自己的项目 API 来进行测试。
-
-
-
-
-
-## 测试请求
-
-找到 [Overview] 窗口右侧的 [Get started],然后点击 [Create a request] 来开始创建一个请求。其实你点击 [Overview] 选项卡右侧的 + 号也可以打开创建请求窗口。
-
-::: tip 笔者说
-如果你进入主界面后,不小心把 [Overview] 窗口关闭了,可以点击左侧的 [Scratch Pad] 再次打开它。
-:::
-
-
-
-在弹出的创建请求窗口里,提供了丰富的选项,几乎可以满足我们所有的接口测试需求。
-
-
-
-接下来我们按照刚才申请的接口要求,来进行请求测试吧。在大多情况下,我们用的最多的就是 GET 请求和 POST 请求,笔者就用 Postman 来分别演示一下。
-
-### GET请求
-
-按照 API 介绍依次选择并填写好请求方式,请求URL,请求参数,然后点击 [Send] 发送请求即可。
-
-
-
-接收到的响应内容默认是以 [Pretty] 漂亮的格式化好的 [JSON] 格式来展示的,你也可以调整为其他数据格式和展示方式。
-
-- Pretty:以漂亮的格式化的形式来展示响应数据,支持 JSON、XML、HTML 等数据内容的格式化;
-
-- Raw:以普通的文本形式来展示响应数据;
-
-- Preview:以预览的形式来展示响应数据,适合 HTML 格式的响应数据;(在浏览器控制台的网络选项卡中,查看某个网络请求的响应内容也有此种方式)
-
- 
-
-- Visualize:以可视化的图形来展示响应数据,但这一项需要提前在 Postman 中编写一些测试脚本。
-
-### POST请求
-
-发送 POST 请求也和 GET 差不多,我们也是依次选择并填写好请求方式,请求URL及请求参数。这里的请求参数,需要在请求体部分设置。
-
-在请求体 [Body] 中选择 [x-www-form-urlencoded] 然后填写请求参数键值对即可。
-
-::: tip 笔者说
-这个过程等价于我们在网页上编写一个 form 表单,设置请求方式为 POST,然后对表单设置好 name 和 value 值一样,以 POST 请求方式来提交 form 表单的时候,默认的 enctype (encodetype,规定了 form 表单在发送到服务器时的编码方式) 就是:application/x-www-form-urlencoded。
-:::
-
-
-
-当然,请求体部分还可以设置为其他的格式:
-
-- form-data:做文件上传的时候,我们都知道要将请求的 enctype 设置为 multipart/form-data,该选项等价于此;
-
- 
-
-- raw:该选项下,可以发送任意格式的普通文本数据,例如:Text、JSON、XML、HTML等;
-
-- binary:该选项等价于设置请求头 Content-Type 为 application/octet-stream,只可以发送二进制数据,t通常用于文件的上传,且只能上传一个,没有像 form-data 格式的键值对;
-
- 
-
-- GraphQL:顾名思义,该选项支持 GraphQL 查询。
-
- ::: tip GraphQL 简介
- GraphQL 是一种用于 API 的查询语言,GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,通过向你的 API 发出一个 GraphQL 请求就能准确获得你想要的数据,不多不少。[3]
- 我们定义的 API 在返回数据的时候,需要定义好相应的 DTO 类,否则直接返回实体类,会包含过多不需要的数据,而 GraphQL 可以有效解决此问题。
- :::
-
- 
-
-## 创建Collection
-
-我们在测试 API 的时候,有些 API 是属于用户相关的接口,有些是属于用户相关的接口,零零散散的很混乱。Postman 中提供了 collection (集合)的概念,我们通过创建一个个的 collection,就可以在 Postman 中对创建过的请求归类,更方便我们查阅和使用。
-
-在 [Overview] 窗口,点击右侧 [Get started] 中的 [Create a collection],就可以开始创建一个 collection 了。也可以点击左侧 [Collections] 菜单界面中的 + 号来开始创建。
-
-
-
-在创建 collection 界面,先为 collection 起个名,然后就可以点击 [Add a request] 来添加请求了,但是点击这个按钮添加请求,是在 collection 中创建一个新请求,我们之前的那些请求咋办?
-
-
-
-别着急,打开之前创建过的请求窗口,点击地址栏上的 [Save] 按钮,可以将其保存到指定 collection 中。
-
-
-
-在弹出的保存请求对话框中,依次填写请求名称,请求描述,选择好要保存到的 collection,最后点击保存即可。
-
-
-
-笔者只是给你打个样儿。你还可以用项目名作为 collection 的名字,然后在 collection 下可以继续创建一个个的文件夹来细分模块,还是挺方便的。
-
-
-
-::: tip 笔者说
-如果你不小心关闭过请求窗口,在 Postman 中还可以点击 [History] 菜单找到之前的历史记录。
-:::
-
-## 创建环境
-
-实际项目开发的时候,我们还要准备多套环境:开发环境、测试环境、生产环境...,这些环境的地址等信息是不同的,总不能让我们对相同接口前缀地址换来换去吧。
-
-当然不能,在 Postman 中可以通过创建 environment 来方便的进行各种环境切换,更加方便了我们对 API 的测试。
-
-在 [Overview] 窗口,点击右侧 [Get started] 中的 [Create an environment],就可以开始创建一个环境了。也可以点击左侧 [Environments] 菜单界面中的 + 号来开始创建。
-
-
-
-先给环境起个名,然后添加环境变量,为环境变量设置好初始值和当前值,最后保存。
-
-::: tip 笔者说
-关于环境变量的初始值和当前值,它俩的区别在于我们开启云端团队协作后,初始值会同步到 Postman 服务器与团队成员共享,当前值则只会存储在本地。默认情况下,你设置了初始值后,当前值会默认设置相同值。
-:::
-
-
-
-依此类推,你可以再创建一些其他环境。如果用过 Spring Boot 配置文件的 profile 设置,那这应该很好理解的。
-
-- application.yml
-- application-dev.yml
-- application-prod.yml
-
-
-
-这环境定义好后,使用起来也很容易,先用双大括号 `{{环境变量名}}` 替换掉原来 URL 的一些固定值。以后,就可以根据当前的环境需求,点击右上角的 [环境切换] 按钮,来自如切换环境了。
-
-
-
-
-
-## 参考资料
-
-[1]Postman 官方下载地址:https://www.postman.com/downloads/
-
-[2]天行数据 天行机器人 API 介绍:https://www.tianapi.com/apiview/47
-
-[3]GraphQL 官网介绍:https://graphql.cn/
-
-## 后记
-
-**C:** 好了,关于 Postman 的介绍就到此结束了,笔者介绍的是基础操作,如果你还想了解更多,可以去看看官方文档,在 Postman 中还可以编写测试脚本,配置认证信息用于 OAuth 等协议请求,有需要的时候搜索一下。
-
-
-
-其实,除了 Postman 之外,还有一些同类型的工具,例如:ApiPost、Apifox 等,有兴趣和需要的同学也可以去了解一下。
-
-
-
-
\ No newline at end of file
diff --git a/docs/categories/tools/2021/03/10/Quartz快速入门.md b/docs/categories/tools/2021/03/10/Quartz快速入门.md
deleted file mode 100644
index c4947a6af..000000000
--- a/docs/categories/tools/2021/03/10/Quartz快速入门.md
+++ /dev/null
@@ -1,394 +0,0 @@
----
-title: Quartz 快速入门
-author: 查尔斯
-date: 2021/03/10 18:58
-categories:
- - 工具四海谈
-tags:
- - Java
- - 作业调度
----
-
-# Quartz 快速入门
-
-## 前言
-
-::: tip 空巢青年们有一句话是什么来着?
-孤独到极致是什么感觉:只有 QQ 邮箱祝你生日快乐。
-:::
-
-笔者刚才也去翻了翻 QQ 邮箱以往的邮件,的确每年都有来自它的祝福,而且好几年都没换个花样儿。
-
-如果你在各类平台都填写过生日信息,这个场景你应该也不陌生:在你填写的生日那天,这些平台比你的男女朋友还准时的出现了,初次见它们的你,一下子感动的不知所措,难道这就是爱吗?
-
-
-
-很可惜,不是。这份 “宠爱” 是 “海王” 的 “雨露均沾”,是 “鱼塘塘主” 的 “千篇一律”。它们都是提前设置好的,每天都会进行的定时任务,只要服务器没炸完,它们总会准时准点。
-
-当然了,除了准点生日祝福,类似的场景一点也不少见。例如:每月 1 号的房贷,每月 9 号的花呗...
-
-好啦,本篇,笔者就要带你认识一个 Java 领域中知名的开源作业调度(根据时间,执行作业)框架,有了它,你也可以做 “海王”。
-
-
-
-
-
-## 简介
-
-::: tip Quartz简介
-Quartz 是 OpenSymphony 开源组织在 Job scheduler(作业调度)领域又一个开源项目,它可以与 J2EE 与 J2SE 应用程序相结合也可以单独使用。
-
-从最小的独立应用程序到最大的电子商务系统,Quartz 可以用来创建简单或复杂的时间表,以执行数十个、数百个甚至数万个工作。[1]
-:::
-
-
-
-## 简单使用
-
-在使用 Quartz 框架前,我们需要先了解一下 Quartz 中的三个核心概念。
-
-**Job(作业/任务):** 需要执行的具体工作任务。
-
-**Trigger(触发器):** 在特定的时间触发任务的执行。
-
-**Scheduler(调度器):** 任务的实际执行者,负责粘合任务和触发器,但记得一个 Job 可以绑定到多个 Trigger,但一个 Trigger 只能服务于一个 Job。
-
-
-
-以 QQ 邮箱发送生日祝福为例,给今天过生日的用户发送生日祝福邮件就是 Job,每天 9 点来执行就是 Trigger。了解完概念之后,接下来和笔者一起先简单的使用一下。
-
-### 引入依赖
-
-首先,我们创建一个普通的 Maven 项目,然后引入 Quartz 的依赖。
-
-::: tip 笔者说
-Quartz 的 API 在 1.x 和 2.x 区别还是很大的,2.x 系列的 API 采用的是 Domain Specific Language ( DSL)风格,也可以说是流式/链式风格(fluent interface),各种 builder 构建器。
-:::
-
-```xml
-
- org.quartz-scheduler
- quartz
- 2.3.0
-
-```
-
-### 创建任务
-
-创建一个类,实现 Job 接口,然后重写 Job 接口的 execute 方法。在 execute 方法中编写的就是需要执行的具体工作。
-
-```java
-public class SimpleJob implements Job {
-
- // 需要执行的具体工作
- @Override
- public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
- // 输出当前线程和时间
- System.out.println("SimpleJob:::" + Thread.currentThread().getName() + ":::" + SimpleDateFormat.getDateTimeInstance().format(new Date()));
- }
-
-}
-```
-
-在使用的时候,需要通过 JobDetail 来绑定好刚创建的类。
-
-::: tip 笔者说
-这个过程,你没有觉得像创建线程一样吗?实现Runnable 接口,然后通过 Thread 来绑定好 Runnable 实现类。
-:::
-
-```java
-// 创建任务对象,绑定任务类并为其命名及分组
-JobDetail jobDetail = JobBuilder.newJob(SimpleJob.class)
- .withIdentity("job1", "group1")
- .build();
-```
-
-### 创建触发器
-
-有了任务之后,我们还要为其准备好一个合适的触发器,既然是简单的使用,那我们先来创建一个每隔 3 秒就会触发的简单触发器。
-
-```java
-// 创建触发器对象,简单触发器每隔 3 秒执行一次任务
-Trigger trigger = TriggerBuilder.newTrigger()
- .withIdentity("trigger1", "group1")
- // 简单触发器
- .withSchedule(SimpleScheduleBuilder.simpleSchedule()
- .withIntervalInSeconds(3)
- .repeatForever())
- .build();
-```
-
-### 创建调度器
-
-最后,我们创建调度器,利用调度器来粘合任务和触发器,这样任务就会在指定时间触发执行了。
-
-```java
-// 创建调度器
-Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
-// 粘合任务和触发器
-scheduler.scheduleJob(jobDetail, trigger);
-// 启动调度器
-scheduler.start();
-```
-
-简单使用的完整代码如下:
-
-```java
-public class Test {
-
- public static void main(String[] args) throws Exception {
- // 创建任务对象,绑定任务类并为任务命名及分组
- JobDetail jobDetail = JobBuilder.newJob(SimpleJob.class)
- .withIdentity("job1", "group1")
- .build();
-
- // 创建触发器对象,简单触发器每隔 3 秒执行一次任务
- Trigger trigger = TriggerBuilder.newTrigger()
- .withIdentity("trigger1", "group1")
- .withSchedule(SimpleScheduleBuilder.simpleSchedule()
- .withIntervalInSeconds(3)
- .repeatForever())
- .build();
-
- // 创建调度器
- Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
- // 绑定任务和触发器
- scheduler.scheduleJob(jobDetail, trigger);
- // 启动调度器
- scheduler.start();
-
- // 注意:Quartz会开启子线程,而为了防止主线程结束,我们为主线程设置下休眠
- Thread.sleep(60000);
- }
-
-}
-```
-
-
-
-## CronTrigger
-
-刚才我们简单使用了一下 Quartz,定时任务按照触发器的设置跑起来了,但这里的触发器 SimpleTrigger ,它只能实现这种周期性触发。而我们前言中提到的,每天指定时间或每月指定时间来触发,用它就有些捉襟见肘了。
-
-所以 Quartz 中还有一种触发器叫:CronTrigger,而在 CronTrigger 中,可以通过 Cron 表达式来轻松的实现前言中的需求。
-
-### Cron表达式
-
-这个 Cron 表达式,**是由6~7个由空格分隔的时间元素组成,第7个时间元素是可选的** 。
-
-::: tip 笔者说
-秒、分、时、日、月、周、年,这 7 个时间元素能精确的定位到某个时间点,当然一般定位到某年的情况是非常少的,所以 年 这个时间元素是可选的,可以不用指定。
-:::
-
-| 位置 | 时间元素 | 允许值 | 允许的特殊字符 |
-| :--- | :------- | :------------------ | :------------- |
-| 1 | 秒 | 0 ~ 59 | , - * / |
-| 2 | 分 | 0 ~ 59 | , - * / |
-| 3 | 时 | 0 ~ 23 | , - * / |
-| 4 | 日 | 1 ~ 31 | , - * / L W |
-| 5 | 月 | 1 ~ 12 或 JAN ~ DEC | , - * / |
-| 6 | 周 | 1 ~ 7 或 SUN ~ SAT | , - * / L # |
-| 7 | 年 | 空 或 1970 ~ 2099 | , - * / |
-
-Cron 表达式到底长成啥样?先打个样儿:`0 0 8 14 2 ? 2021` ,它代表的是 2021 年 2 月 14 日上午 8 点会触发。
-
-Cron 表达式的每个时间元素,都可以设置为具体值,这当然很简单。除此之外,笔者再介绍一下 Cron 表达式中允许出现的一些特殊字符。
-
-**? 问号:** 仅用于 日 和 周 元素,表示不指定值,日和周这两个时间元素,必须有一个设置为 ? 。
-
-::: tip 笔者说
-之所以如此,是因为每月的几号一定是星期几吗?例如:2021年的3月10日是星期三,但4月10日就是星期六了... 。所以,**重点记住:日和周两个时间元素不能同时指定具体值,其中一方必须设置为 ? 就行了** 。
-:::
-
-**\* 星号:** 可用于所有时间元素,表示每一个值。例:0 0 8 * * ? 表示每天上午8点触发。
-
-**, 逗号:** 可用于所有时间元素,表示一个列表。例:0 0 8,10,14 * * ? 表示每天上午8点,上午10点,下午14点触发。
-
-**\- 中划线:** 可用于所有时间元素,表示一个范围。例:0 0 8 1-3 * ? 表示每月1日到3日的上午8点触发。
-
-**/ 斜杠:** 可用于所有时间元素,x/y,x代表起始值,y代表值的增量。例:0 0 8/2 * * ? 表示每天上午8点开始,每隔两个小时触发一次。
-
-**L:** 仅用于 日 和 周 元素,表示对应时间元素上允许的最后一个值(Last)。例1:0 30 8 L * ? 表示每月最后一天的上午8点30分触发。例2:0 30 8 ? * 3L 表示每月最后一周的星期二触发。
-
-::: tip 笔者说
-由于国外星期是从星期日开始计算,星期日的数字表示是1,因此示例中 3L 的3代表的是星期二。
-:::
-
-**W:** 仅用于 日 元素,表示指定日期的最近工作日。例:0 0 8 10W * ? 表示每月10号上午8点触发,但如果10号不是工作日那就找最近的工作日。10号是周六,那就找周五;10号是周日,那就找周一。
-
-有一种特殊的情况是:设置的是1W,如果1号不是工作日,哪怕这时候1号是周六,它也不会去找上月的周五来执行,而是找本月的周一去执行。
-
-::: tip 笔者说
-很多定时任务,都需要在工作日来执行,所以有这么一个符号表示这含义无可厚非。L和W在 日 元素上还可以一起使用,例:* * * LW * ? 表示本月最后一个工作日触发。
-:::
-
-**#:** 仅用于 周 元素,x#y,y代表对应月份中的第几周,x代表第几周的第几天。例:0 0 8 ? * 3#2:每月第2周的星期二上午8点触发。
-
-头晕了吧?笔者再提供几个常用的 Cron 表达式给你。
-
-| Cron 表达式 | 含义 |
-| :------------------ | :------------------------------- |
-| 0 0 2 1 * ? | 每月1日的凌晨2点 |
-| 0 15 10 ? * MON-FRI | 周一到周五每天上午10:15 |
-| 0 0/30 9-17 * * ? | 朝九晚五工作时间内每半小时 |
-| 0 15 10 L * ? | 每月最后一日的上午10:15 |
-| 0 15 10 ? * 6L | 每月的最后一个星期五上午10:15 |
-| 0 15 10 ? * 6#3 | 每月的第三个星期五上午10:15 |
-| 0 10,44 14 ? 3 WED | 每年三月的星期三的下午2:10和2:44 |
-
-另外,笔者再说一点,Cron 表达式也不仅仅是用于 Quartz 框架,很多与定时任务有关的工具都需要用到 Cron 表达式,所以说熟练掌握编写 Cron 表达式很重要。
-
-别害怕,笔者再教你一招,市面上有很多在线 Cron 表达式生成器(参考资料 [3] 就是笔者推荐给你的生成器),通过可视化的选择,就可以自动生成 Cron 表达式,而且还可以提供测试执行效果。
-
-
-
-### 案例实现
-
-认识完了 Cron 表达式,我们再用 Cron 表达式实现一下刚才的案例效果吧。
-
-```java
-public class Test2 {
-
- public static void main(String[] args) throws Exception {
- // 创建任务对象,绑定任务类并为任务命名及分组
- JobDetail jobDetail = JobBuilder.newJob(SimpleJob.class)
- .withIdentity("job1", "group1")
- .build();
-
- // 创建触发器对象,通过 Cron 表达式指定每隔 3 秒执行一次任务
- Trigger trigger = TriggerBuilder.newTrigger()
- .withIdentity("trigger1", "group1")
- .withSchedule(CronScheduleBuilder.cronSchedule("0/3 * * ? * *"))
- .build();
-
- // 创建调度器
- Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
- // 绑定任务和触发器
- scheduler.scheduleJob(jobDetail, trigger);
- // 启动调度器
- scheduler.start();
-
- // Quartz会开启子线程,而为了防止主线程结束,我们为主线程设置下休眠
- Thread.sleep(60000);
- }
-
-}
-```
-
-## Spring Boot整合
-
-Spring 作为 Java 界的顶流框架,怎么可能对 Quartz 没有提供整合呢?但是原生 Spring 整合 Quartz ,配置文件让人头疼,所以咱们使用 Spring Boot 来实现一下 Quartz 整合。
-
-### 引入依赖
-
-```xml
-
- org.springframework.boot
- spring-boot-starter-quartz
-
-```
-
-### 创建任务
-
-```java
-@Component
-public class SimpleJob extends QuartzJobBean {
-
- @Override
- protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
- // 输出当前线程和时间
- System.out.println("SimpleJob:::" + Thread.currentThread().getName() + ":::" + SimpleDateFormat.getDateTimeInstance().format(new Date()));
- }
-
-}
-```
-
-之前是实现 Job 接口,这回是继承 QuartzJobBean,其实没有区别。因为 QuartzJobBean 实现自 Job 接口。
-
-```java
-public abstract class QuartzJobBean implements Job {
- ....
-}
-```
-
-### 编写配置
-
-创建一个配置类,为任务和触发器提供两个 Bean 配置,记得别忘了对触发器指定任务。
-
-```java
-@Configuration
-public class SchedulerConfig {
-
- // 任务bean
- @Bean
- public JobDetail simpleJobBean() {
- return JobBuilder.newJob(SimpleJob.class)
- .withIdentity("job1", "group1")
- // Jobs added with no trigger must be durable.
- .storeDurably()
- .build();
- }
-
- // 触发器bean
- @Bean
- public Trigger simpleTrigger() {
- return TriggerBuilder.newTrigger()
- .withIdentity("trigger1", "group1")
- .withSchedule(SimpleScheduleBuilder.simpleSchedule()
- .withIntervalInSeconds(3)
- .repeatForever())
- // 指定具体任务
- .forJob(simpleJobBean())
- .build();
- }
-
-}
-```
-
-## Spring Task
-
-Spring 从 3.x 开始提供了 Spring Task,可以理解为是一种轻量级的 Quartz。接下来,我们也在 Spring Boot 中体验一下。
-
-创建好的 Spring Boot 项目,不需要你添加任何额外依赖,在任何一个 Spring 组件中,创建普通方法编写好任务(可以写多个),再通过 `@Scheduled` 注解为其指定好 Cron 表达式,一个绑定了触发器的任务就新鲜出炉了。
-
-```java
-@Component
-public class TestTask {
-
- @Scheduled(cron = "0/3 * * ? * *")
- public void task1() {
- System.out.println("当前时间是:" + LocalDateTime.now());
- }
-
-}
-```
-
-最后,还需要在启动类/任务类上使用 @EnableScheduling 启用一下任务调度功能,是不是挺简单的?
-
-```java
-@EnableScheduling // 启用任务调度
-@SpringBootApplication
-public class DemoSpringbootApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(DemoSpringbootApplication.class, args);
- }
-
-}
-```
-
-## 参考资料
-
-[1]Quartz 官网:http://www.quartz-scheduler.org/
-
-[2]Quartz GitHub地址:https://github.com/quartz-scheduler/quartz
-
-[3]MaTools 在线Cron表达式生成器:https://www.matools.com/cron
-
-## 后记
-
-**C:** 好了,Quartz 定时器的入门介绍到这儿就结束了。入门介绍中,咱们仅仅是介绍了一下 Quartz 的 RAMJobStore 模式,即 Quartz 将 Trigger 和 Job 存储在内存中,内存中存取自然是很快的,但缺陷就是内存无法持久化存储。
-
-所以, Quartz 还有一种 JDBC JobStore 模式,即 Quartz 利用 JDBC 将 Trigger 和 Job 存储到数据库中,想要实现 Quartz 的集群也得先完成这一步进阶。
-
-当然了,Java 中还有很多定时器的实现方案。例如:java.util.Timer、ScheduledThreadPoolExecutor(基于线程池设计的定时任务类)、延时队列等,有兴趣可以先去了解了解。不然就等后面,笔者有时间再介绍吧。
\ No newline at end of file
diff --git a/docs/categories/tools/index.md b/docs/categories/tools/index.md
deleted file mode 100644
index 442fc1db7..000000000
--- a/docs/categories/tools/index.md
+++ /dev/null
@@ -1,12 +0,0 @@
----
-showArticleMetadata: false
-editLink: false
-lastUpdated: false
-showComment: false
----
-
-# 工具四海谈
-
-::: tip 笔者说
-“工欲善其事,必先利其器。” 在应用程序开发的过程中,会使用到大大小小的工具组件,正因为它们的存在,我们的程序开发才会变得高效而便捷。
-:::
\ No newline at end of file
diff --git a/docs/courses/course1/01-卷1/01-章节1.md b/docs/courses/course1/01-卷1/01-章节1.md
new file mode 100644
index 000000000..7fe15f5ff
--- /dev/null
+++ b/docs/courses/course1/01-卷1/01-章节1.md
@@ -0,0 +1,13 @@
+---
+title: 章节1
+author: 查尔斯
+date: 2020/10/02 21:29
+categories:
+ - 小册1
+tags:
+ - Java
+ - 示例
+---
+
+# 章节1
+
diff --git a/docs/courses/course1/index.md b/docs/courses/course1/index.md
new file mode 100644
index 000000000..ac17ad4b7
--- /dev/null
+++ b/docs/courses/course1/index.md
@@ -0,0 +1,8 @@
+---
+showArticleMetadata: false
+editLink: false
+lastUpdated: false
+showComment: false
+---
+
+# 小册1
diff --git a/docs/courses/java/01-Java语法入门/01-开发环境搭建.md b/docs/courses/java/01-Java语法入门/01-开发环境搭建.md
deleted file mode 100644
index 511897dcc..000000000
--- a/docs/courses/java/01-Java语法入门/01-开发环境搭建.md
+++ /dev/null
@@ -1,379 +0,0 @@
----
-title: 开发环境搭建
-author: 查尔斯
-date: 2020/10/02 21:29
-categories:
- - Java基础快速入门
-tags:
- - Java
- - Java基础
- - JDK
- - 开发环境
----
-
-# 开发环境搭建
-
-## 前言
-
-**C:** 上篇的介绍是否能让你对 Java 语言有一个初步的认识呢?认识完后,大家可能着急想上手编程了吧?但就像你要去游泳,也得先找到一个泳池?所以还是先耐下性子,听笔者说,在正式开发一个 Java 程序前,我们首先应该在计算机中,准备好对应的开发环境,Java 语言所需要的开发环境是 JDK / JRE。
-
-这是万里长征的第一步,搭好 Java 基础开发环境是 Java 系开发者必须掌握的技能,所以笔者建议你,收藏好本篇教程,JDK 多安装个几遍,它又不是流氓软件,不影响(卸载不残留,重装如新装)。
-
-
-
-
-
-## JDK和JRE的概念
-
-首先我们介绍一下我们要安装的 JDK / JRE 的概念。
-
-`JDK` 的全称是 `Java Development Kit`,即 Java 开发工具包,是 Sun 公司提供的一套用于开发 Java 应用程序的开发包,它提供了编译、运行 Java 程序所需的各种工具和资源,包括 Java 编译器、Java 运行时环境(`JRE`),以及常用的 Java类库 等。
-
-`JRE`,全称 `Java Runtime Environment` ,Java 运行时环境。它是运行 Java 程序的必须条件。如果只是运行Java 程序,可以只安装 `JRE`,无需安装 `JDK`。
-
-::: tip 笔者说
-在业内,一般都是直接安装 `JDK`,因为 `JDK` 内置了一个 `JRE`,我们亦是如此。
-:::
-
-## JDK的选择
-
-### 选择谁家的?
-
-了解完 `JDK` 概念之后,我们还要了解下目前 `JDK` 的现状。Sun 公司当初开发了 Java 语言,作为 Java 语言的开发工具包, `JDK` 在发展中被 Sun 公司分化为了两大分支。( 可延伸阅读 [Java 终于开源了,采用GPLv2授权协议](https://www.51cto.com/specbook/11/35089.htm) )
-
-- `Open JDK` ,开源(源代码公开)版本,以 GPL V2(General Public License)协议的形式开源
-- `Sun JDK` ,使用 JRL(Java Research License,Java 研究授权协议)发布。
-
-::: tip 笔者说
-GPL 协议,在开源协议里被称为"病毒"协议,只要是基于 GPL 协议 **开源** 的代码来开发,那么这项目也必须开源。
-
-JRL 协议,是 Sun 公司自己搞出来的协议,理解起来就是 Sun 公司公开代码,但是代码的所有权完全归它自己所有,你们能看。
-
-不过上述协议对我们使用 JDK 没有什么影响,它影响的是那些想改动 JDK 或基于 JDK 代码二次开发的个人或公司群体,我们又不动 JDK 代码。
-:::
-
-其实两个分支版本,在发展中有很大部分的相同代码,不过`Open JDK`不如 `Sun JDK` 完整是肯定的(缺少一些特性API),且一部分代码由于产权等原因无法授权给 `Open JDK` 使用,便在 `Open JDK` 中替换为没有产权问题的代码。
-
-很多大公司为了避免版权问题,都在使用基于 `Open JDK` 开发或自主开发的 JDK 版本,例如亚马逊的 Corretto、阿里巴巴的 Dragonwell、华为的毕昇、腾讯的 Kona等(咱们国内今年井喷式开源 JDK)。
-
-另外我们都知道,Sun (升阳公司)在2009年被 Oracle(甲骨文公司)收购了,Java 相关业务及版权也就归Oracle 所有。后续的 `JDK` 更新当然也就由 Oracle 负责了,但是 Oracle 在行业内有一个"不太好"的名声,"什么都要钱,什么都死贵"(实际上,商业公司的本质就是盈利,Sun 公司当初还没做到怎么盈利就没了,Oracle 后面继续做这件事也无可厚非)。
-
-在2009年到2019年期间,Oracle 没有做什么收费的大动作,但是这种情况在2019年1月1日出现了点变化。Oracle 宣布从2019年1月1日起,`Oracle JDK 8` 的后续更新将需要收费。`Oracle JDK 8` 的 `8u211` 和 `8u212`更新,开始把许可协议从 `BCL` 换成了 `OTN`,这就意味着,你不能在生产环境使用这类版本了。
-
-::: tip Oracle 采用的许可协议介绍
-BCL协议,即Oracle Binary Code License Agreement,协议规定你可以使用JDK,但是不能进行修改(和上文的JRL类似),私用和商用都可以,但是JDK中的某些商业特性,是需要付费才可以使用的。
-
-OTN协议,即Oracle Technology Network License Agreement,目前新发布的JDK用的都是这个协议,可以私用,商用需要付费。[1]
-:::
-
-一石激起千层浪,本来就担心的事终于发生了,很多公司更是开始进行 `JDK` 版本转移和考虑以后的选择。
-
-下图是2020年初,Jrebel 在 Java 生态报告中,对 `JDK` 选择的调查结果(中国内也差不多,仅供参考)。根据结果表示,`Oracle JDK(Sun JDK)`和`Oracle Open JDK(Sun Open JDK)`还是占据比较大的市场地位,但`AdoptOpenJDK`的占有率也在迅速提升中。
-
-我们现在学习选择用 `Oracle JDK` 就可以了,公司内就看公司的架构师或领导想法了。[可延伸阅读,了解更多的 [JDK 发行版](https://www.oschina.net/news/99836/time-to-look-beyond-oracles-jdk)]
-
-
-
-### 选择哪个版本?
-
-每个版本的对应 `Open JDK` 更新也不是无限期,是有支持期限的。`Oracle JDK 8` 还有个人版、商业版。这些事其实还挺头疼的,不过我们现在学习用 `Oracle JDK` 没有问题,进公司到时候就"入乡随俗"吧。
-
-选定好发行版之后,那我们用第几版本呢?下面是 Jrebel 的报告,其中很明显是 `JDK 8` 应用最广。虽然截止笔者调整教程今天,JDK 已经快要发布到了 `JDK 16`,但是公司追求的是稳定,所以没有太大更新或修复的情况,一般升级就非常慢,你想想 Windows 7 到 Windows 10 的用户升级之路就理解了。另外 JDK 9、JDK 10 都没人用,是因为它们都是过渡版本,类似于 Windows8 一样,不是长期支持(维护)版本。
-
-
-
-## JDK下载
-
-既然我们选择了 `Oralce JDK 8`,那就前往Oracle官网下载吧。
-
-1.打开下方的链接,或者自行百度搜索 `JDK`,找到类似下方页面。
-
-- [Oracle国际官网](http://www.oracle.com/technetwork/java/javase/downloads/index.html )
-
-- [Oracle中国官网](http://www.oracle.com/technetwork/cn/java/javase/downloads/index.html )
-
-
-
-2.下拉到页面最下方,找到`Java Archive`点击进入`JDK`历史版本存档页面。
-
-
-
-你看 `Java SE 8` 分为了两个链接,`8u211及之后 `(收费)和`8u202及之前`(免费)。
-
-
-
-3.点击 `Java SE 8(8u2020 and earlier)` 进入下载页面,然后选择你所需的平台版本。大多数同学应该用的都是 Windows 64 位的系统,选择下方箭头指示的版本即可。
-
-
-
-记得勾选 `卖身协议` 。
-
-
-
-Oracle 现在要求下载 `JDK` 必须先登录,没有帐号的同学,自己先注册一个吧。网络是真慢!忍忍!
-
-
-
-4.终于下载好了,笔者家里开的热点网络,太慢了。
-
-
-
-## JDK安装
-
-下载好了,开始安装 `JDK` 吧,和安装 QQ 等软件一样,而且它不是流氓软件,不会静默给你下载一个"全家桶"。
-
-1.双击程序安装包,开始进行 `JDK` 安装,点击下一步。
-
-
-
-2.点击更改,更改 `JDK` 的安装位置。
-
-::: warning 笔者说
-为了防止出现,你自己安装的软件自己都找不到在哪儿这种问题,我们统一安装位置,任选一个磁盘,在其下新建一个 `develop` 的文件夹,用于以后安装所有开发软件。笔者演示时将 `develop` 文件夹放在了 `d` 盘下。
-:::
-
-
-
-在弹出的更改安装目录对话框中,只需要修改前面的盘符 `d:` 和文件夹 `develop`,后面的子文件夹 `Java\jdk1.8.0_xxx\` 不需要修改,然后点击确定。如果文件夹不存在,安装时会自动创建。
-
-::: danger 笔者说
-安装路径不要出现空格,中文,特殊符号等!
-:::
-
-
-
-这个时候程序将要安装的位置已经更改,点击下一步即可开始安装。
-
-
-
-### **关于我们安装JDK时到底安装了些什么?**
-
-我们选中第一个 `开发工具` 时,右侧给出了提示,这是安装的 `JDK`。它是最主要的,甚至我们可以说只需要有它就可以。
-
-
-
-当我们选中第二个 `源代码` 时右侧给出提示,这是 Java 8 的源代码,因为 Java 是公开源代码的。
-
-
-
-当我选中第三个 `公共JRE` 时,右边给出提示,这是一个独立的 `JRE`,我们可以不用安装。不过一般情况我们都选择安装,目的是为了以后如果有一些 Java 程序想单独运行,那么必须配套一个 `JRE`,到那时候就可以用上了。
-
-
-
-比如下方是做支付宝第三方支付支持时,支付宝官方给提供的一个做签名校验的 Java 程序。后缀名为 `.jar` 的是 Java 程序,但是如果想运行此程序就必须依赖上方的一个独立 `jre`,我们刚才安装选择界面看到的就是它。(支付宝官方下载下来的这工具,就给你带着这个 JRE )
-
-
-
-4.等待安装,这步只是在安装 `JDK`。
-
-
-
-安装完 `JDK` 后,会弹出一个提示框,提示我们安装的 `JDK` 版本不受到收费影响,点确定即可。
-
-
-
-5.因为刚才我们没有放弃独立 `JRE` 的安装,所以现在开始安装它,自己更改好安装路径。最好类似我下方示例,然后点击下一步。
-
-
-
-等待安装。
-
-
-
-点击关闭,即完成安装。
-
-
-
-安装完成后,你的桌面不会出现任何图标,不用大惊小怪,`JDK` 是开发环境,不是 QQ 这类软件。
-
-`JDK` 的安装目录如下。
-
-::: tip 笔者说
-刚才之所以说可以不安装那个独立 `JRE`,因为 `JDK` 本身自己就自带一个 `JRE`,为什么`JDK` 会自带一个?我们就不讨论 JDK 内自带的 Java 程序,就说我们用 `JDK` 开发 Java 程序,开发好后也需要进行测试运行啊,所以自然需要这 `JRE` 了。
-:::
-
-
-
-独立 `JRE` 的安装目录如下。
-
-
-
-安装完之后,我们想测试一下 `JDK` 是否安装成功,可以运行 `JDK` 安装目录下 `bin` 目录内的 `java.exe` 程序。如果你看不到 `.exe`,记得自行开启下计算机的扩展名显示。
-
-在 `Windows` 中我们习惯双击运行程序,但是却发现 `java.exe` 双击后会弹出一个黑窗口一闪而过。这是因为这种程序,它们需要在特别的系统内运行,比如说我们的 `DOS` 系统,下面就和笔者去学一下基本的 `DOS` 使用吧。
-
-
-
-## DOS系统
-
-### 什么是DOS?
-
-那 `DOS` 是什么呢?它的全称是 `Disk Operating System` ,即磁盘操作系统。简单点说,你看过的电影里,黑客们是不是在计算机的一个黑窗口中"运指如飞"?这个所谓的黑窗口不是 `DOS` 那就是 Linux 系。
-
-实际上 `DOS` 它就是早期主流的计算机操作系统,后来 Windows 等主打可视化的系统出现,才让计算机逐渐摆脱专业的概念,变得"平民化",走入千家万家。之前之所以专业化,就是因为这个系统需要通过命令来进行计算机操作,而不能使用鼠标点来点去,所以非专业人士去背命令和习惯这使用方式,简直"太难"了。
-
-
-
-### 进入DOS系统
-
-在Windows 系统任何位置,可以通过按下 `Windows` 键 + `r` 键,在左下角弹出的运行窗口输入 `cmd` 然后回车,就可以弹出 `DOS` 命令行。
-
-
-
-另外,还可以通过在 `开始菜单` 中直接搜索 `cmd`,然后 `右键以管理员身份运行` 的方式打开。
-
-
-
-进入了 `DOS` 命令行。要求左上方有管理员标识(如果没有此标识,你创建文件等都没有权限,有些命令甚至提示不存在)。当你是 `Windows 10` 系统,那么很可能没有,因为 `Windows 10` 对于权限的把控比较严格,所以你可以采取上方的第二种方法进入 `DOS` 命令行。
-
-
-
-进入了 `DOS` 命令行,先认识下组成,前部分是当前你在 `DOS` 系统所处的路径(当前目录/文件夹,目录就是文件夹的意思,之后不再解释),后部分就是可以输入命令的位置。
-
-
-
-上方的路径,等价于你在 `Windows` 系统中进入了如下位置。
-
-
-
-### DOS常用命令
-
-#### 查看列表
-
-在上图中,如果我们在 `Windows` 系统中进入了某个路径,可以很直观的看到当前路径下的所有文件和文件夹。那么在 `DOS` 中如何实现这一目的呢?
-
-输入 `dir` 命令,即可列出当前所处位置的文件和文件夹列表,如下图所示。
-
-
-
-#### 切换目录
-
-那如果不想待在默认的路径了,想切换到其它位置。
-
-- 相同磁盘的目录切换,直接通过 `cd 目录路径` 来切换。(这个路径必须存在,不然切换不过去)
-
- 例如:我想切换到当前目录下的 `Documents` 目录。
-
- 
-
-- 不同磁盘的目录切换,先通过 `盘符:` 来切换磁盘,然后 `cd 目录路径` (注意 cd 后有空格)再切换到对应位置。
-
- 例如:我想切换到刚才 `JDK` 的安装目录。
-
-::: tip 笔者说
-如果路径长,在输入的时候,还可以通过 `Tab` 键来进行内容补全。例如下方的输入,输入完 `De` 就可以按一下 `Tab` 键快速补全。因为 DOS 会自动识别所在目录下的内容名字,如果能匹配到就可以快速补全,当然如果有多个 `De ` 打头的内容,那就尽量输入多一些字母后再按 `Tab` ,这样就更精准。
-:::
-
- 
-
-还有一些特别的路径切换,比如返回上一级目录。在 `Windows` 系统中,鼠标点一下返回键就可以了,在 `DOS`中,可以通过 `cd ..` 命令来切换。`..`和`.` 是每个目录下都存在的两个隐藏文件夹,它们一个代表上一级目录,一个代表当前目录。
-
-
-
-
-
-还有在磁盘比较深的路径时,可以输入 `cd /` 来快速回到磁盘根目录下。
-
-
-
-#### 运行程序
-
-在 `Windows` 中如果想运行程序,我们都是双击程序快捷方式或程序启动文件。而在 `DOS` 中,如果我们想要运行程序,只需要输入程序启动文件路径,然后回车即可。
-
-例如:我想运行钉钉程序,我知道它的启动程序地址,那么就可以利用 `Tab` 快速提示着来输入好地址。下图的 `""` 是按 `Tab` 自动生成的,`DOS `里为了防止空格产生的影响,可以加 `""` 进行包裹,表示一个整体。
-
-
-
-这个路径实在太长了,如果在 `DOS` 中,使用过了且没关闭 `DOS` 窗口的情况下,还需要使用时,建议按 `↑` 或 `↓` 方向键,翻一翻历史命令。
-
-### 测试JDK是否安装成功
-
-OK,掌握了 `DOS` 基本使用,这时候我们再来通过它运行下 `java.exe`,输入 `java.exe` 路径太长了,我们可以偷点懒。
-
-先通过 `Windows` 找到 `java.exe`,然后在地址栏输入 `cmd`,回车后就可以快速进入程序所在的位置了。
-
-
-
-
-
-然后就可以运行 `java.exe `了,后面追加一个 `-version` 可以用来查看 `JDK` 的版本,如果出现下方所示内容,说明 `JDK` 的安装是完全正常的。
-
-::: tip 笔者说
-`DOS` 中可以省略 `exe` 之类的后缀
-:::
-
-
-
-## 环境变量
-
-### 概述
-
-在刚才的内容搞定后,其实我们的 Java 开发环境已经搭建完了,我们之后开发 Java 程序会一直使用刚才的`java.exe `程序。不过现在使用还是挺麻烦的,每次都要在 `DOS` 中先找到程序或输入程序路径才能运行,有没有什么办法可以在 `DOS` 任意目录使用 `java.exe` 呢?
-
-看看百度百科了解一下环境变量吧,其实环境变量就是操作系统里存储的一些参数或关键值,每个在操作系统里运行的程序都可以获取到这些存储的内容。(后面我们学到变量这一程序概念时,就可以更好的理解它的作用了,到时候记得回来再看看)
-
-
-
-### 找到环境变量设置
-
-在 `开始菜单` 中搜索 `环境变量`,点击 `编辑系统环境变量`,打开 `系统属性` 对话框。
-
-
-
-在 `高级` 选项卡中,点击 `环境变量` 就可以进入修改环境变量的对话框。
-
-
-
-
-
-### path环境变量
-
-其中 `path` 环境变量就是用来存储路径列表的,里面存储了一个个的路径。当我们在 `DOS` 命令行中直接输入程序的名字然后回车,这时候 `DOS` 会先在当前目录下搜索该文件,若找到则运行之,若找不到该文件,则根据 `path` 环境变量所设置的路径列表,顺序逐条地搜索这些路径下是否有该程序,有的话也能运行。
-
-这就是我们现在需要的,可以有效解决我们为了运行 `java.exe` 而很麻烦的输入路径等,一劳永逸。有些同学还把一些游戏启动程序存到了 `path` 环境变量。
-
-### 配置JAVA_HOME
-
-接下来就将 `java.exe` 的程序目录存储到 `path` 环境变量吧。
-
-1.点击 `系统变量` 下的 `新建`,在弹出 `新建系统变量` 窗口后,将变量值设为 JDK 安装路径(bin 目录上一级),变量名设为 `JAVA_HOME` (之所以叫这名,是因为Maven、Tomcat等日后所用开发程序大多会使用到此环境变量),所以名称不允许修改。
-
-
-
-2.上方存储的环境变量还不完整,并且没添加到 `path` 环境变量。所以我们需要在 `path` 环境变量中再做些处理。
-
-点击系统变量中的 `path` 环境变量,然后点击 `编辑`,删除其中 Oracle 默认生成的一个目录配置(JDK 1.8之后就开始自动加上了,但是这个地址对我们用处不大,删掉)。
-
-
-
-点击 `新增 `,添加一条 `%JAVA_HOME%\bin` 变量,`%JAVA_HOME%` 表示引用 `JAVA_HOME` 环境变量的值,这一条变量等价于在 `path` 中添加了 `D:\Develop\Java\jdk1.8.0_202\bin`。
-
-
-
-::: danger 笔者说
-`Win7`系统的 `path` 环境变量是全部在一起的,而不是像 `Win10` 这样一条条很清晰。自己去新加入一条:`%JAVA_HOME%\bin;`(结尾这一定要用英文`;`来分隔其他的环境变量啊!)。
-
-还有我们在配置 Java 安装路径的时候,需要格外注意不要将之前的还有一些系统的 `path` 配置删除,也不要写错。(不要在蓝色选中状态时直接输入,会全部替换的!!!)否则有很多系统命令就没法在 DOS 中便捷愉快的使用了。
-:::
-
-
-
-### 测试效果
-
-配置好 `path` 环境变量之后,关闭所有的 `DOS` 窗口。再重新打开 `DOS` 后,输入 `java -version` ,我们看到和之前一样的效果,而且我们不用在输入 `java.exe` 冗长的路径了!
-
-
-
-## 参考文献
-
-[1]闷瓜蛋子. Oracle如何对JDK收费[EB/OL]. https://zhuanlan.zhihu.com/p/64731331. 2019-06-18
-
-## 后记
-
-`JDK` 的安装环节也就介绍到这。补充了一些基础内容,比较杂的感觉,但实际是顺序流程的学习,好好看看本篇文章大纲!每个步骤一定要实践一下!加油!有问题可以邮箱或订阅号联系笔者。
-
-::: info 笔者说
-对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
-
-所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
-:::
\ No newline at end of file
diff --git a/docs/courses/java/01-Java语法入门/02-第一个Java程序.md b/docs/courses/java/01-Java语法入门/02-第一个Java程序.md
deleted file mode 100644
index 7486745ab..000000000
--- a/docs/courses/java/01-Java语法入门/02-第一个Java程序.md
+++ /dev/null
@@ -1,193 +0,0 @@
----
-title: 第一个Java程序
-author: 查尔斯
-date: 2020/10/03 20:00
-categories:
- - Java基础快速入门
-tags:
- - Java
- - Java基础
----
-
-# 第一个Java程序
-
-## 前言
-
-**C:** 在上一篇,我们搭建好了 Java 语言的开发环境,这一篇笔者就带着大家愉快的写出我们的第一个程序,不过本篇我们是使用记事本(比较原生态)来写程序,并不使用其他的高级开发工具,所以可能会显得比较低端,请大家见谅。
-
-::: tip 笔者说
-饭要一口一口的吃,工具也要一点一点的升级,技术也要一点一滴的积累。
-:::
-
-
-
-
-
-## 开发步骤
-
-一个Java程序的开发,需要经过:编写源码,编译源码和运行,这三大阶段。
-
-
-
-和笔者来体验一下吧!**源代码/源程序:程序的原始指令,由程序员编写** 。
-
-1.首先使用记事本编写源代码文件( Java 程序的源代码文件扩展名为 `.java`)。
-
-
-
-2.打开cmd命令窗口,进入源代码所在地,使用 `javac` 命令编译,生成 `.class` 文件。
-
-
-
-::: tip 笔者说
-编写完的源代码,计算机是无法直接执行的,因为它只识别二进制,所以我们需要一个 `翻译官` 帮助我们去翻译下,这样计算机才能够识别。`翻译官` 就是编译器,编译之后就会出现一个扩展名为 `.class` 的字节码文件(它并非纯二进制文件,是运行于JVM中的一种伪二进制文件),这时才能够被计算机执行。
-:::
-
-3.使用 `java` 命令运行 `.class` 文件。
-
-::: warning 笔者说
-运行 `.class` 文件时不能加上 `.class` 后缀名。
-:::
-
-
-
-这样我们就实现了第一个 Java 程序,我们借助它给计算机下达了一个在控制台输出 `Hello World!!!` 的指令。
-
-::: details 为什么我们开始编写的第一个程序要输出 Hello World 这句话?
-实际是因为《The C Programming Language》这本 C 语言书籍中使用它做了第一个演示程序,非常著名,所以后来的程序员在学习编程或进行设备调试时延续了这一习惯。
-
-一个程序员才懂的冷笑话:一位资深程序员到了退休的年纪,为了给自己的晚年生活增加点乐趣,开始学习书法,在开始学习书法的第一天,资深程序员铺开纸张,大笔一挥 `Hello World` !
-:::
-
-## 基本程序结构
-
-刚才我们编写了第一个 Java 程序,尽管背了相关单词,但肯定还是看的云里雾里的。笔者先给你个提醒,现在呢不要求你过多理解它们的含义,只需要知道它的效果和注意事项即可。随着学习的深入,慢慢就会深入认识和理解了,**千万别钻牛角尖** ,因为还没给你讲的肯定是因为笔者认为目前还不是太适合你的,**欲速则不达** 。
-
-::: warning 下方是你现在需要记忆的注意事项和规范:
-1. 类名与文件名完全一致,首字母大写(帕斯卡/大驼峰命名法)。
-
-2. main() 方法是程序的入口,四要素必不可少。
-
-3. 核心语句:System.out.print**ln**(); 从控制台输出信息,S是大写。( Java 严格区分大小写)
-
-4. { 和 }一 一对应,缺一不可。(注意模仿笔者的编写习惯)
-
-5. 注意要有层级缩进,一行只写一条语句即可。
-:::
-
-
-
-::: tip 笔者说
-“好的代码应该是给人看的,而不是给机器看的。”** 上面的5条注意事项,其实4条都在强调规范,编码规范非常重要!
-
-一个软件的生命周期中,80%的花费都在于维护。几乎没有任何一个软件,在其整个生命周期中,均由最初的开发人员来维护。编码规范可以改善软件的可读性,可以让程序员尽快而彻底地理解新的代码。更多规范除了模仿笔者文章示例习惯外,还请后续随时与笔者一起解读[《阿里巴巴Java开发手册》](https://github.com/alibaba/p3c/blob/master/Java%E5%BC%80%E5%8F%91%E6%89%8B%E5%86%8C%EF%BC%88%E5%B5%A9%E5%B1%B1%E7%89%88%EF%BC%89.pdf)。
-:::
-
-### 输出语句
-
-其实输出功能,不仅仅只有 `println()` 这一个语法,还有去掉了 `ln` 之后的 `print()` 也可以实现。
-
-```java
-System.out.print(); // 输出完不换行
-System.out.println(); // 输出完换行
-```
-
-如果想让 `print()` 实现 `println()` 的效果,可以借助转义符。
-
-| 转义符 | 说明 |
-| ------ | ------------------------------ |
-| \n | 将光标移动到下一行的第一格 |
-| \t | 将光标移动到下一个水平制表位置 |
-
-```java
-// 下方前两行和第三行代码是等效的。
-System.out.println("Hello");
-System.out.println("World");
-System.out.print("Hello \n World\n");
-```
-
-### 注释
-
-在 Java 的编写过程中我们需要对一些程序进行注释,这样除了自己更方便阅读,别人也更好理解我们的程序,所以我们一定要适时的加一些注释,可以是编程思路或者是程序的作用。
-
-Java 中有三种注释:
-
-1. 单行注释以 `//` 开始;
-2. 多行注释以 `/*` 开头,以 `*/` 结尾;
-3. JavaDoc(文档注释)注释以`/**`开头,以`*/`结尾(文档注释前期了解,后期再掌握)
-
-```java
-/**
- * HelloWorld.java
- * 第一个Java程序
- */
-public class HelloWorld{
- public static void main(String[ ] args){
- // 输出消息到控制台(单行注释后注意添加一个空格,这是一个小规范)
- System.out.println("Hello World!!!");
- }
-}
-```
-
-
-
-## 跨平台原理
-
-在《Java概述》中,笔者讲 Java 的能力时就埋了一个伏笔,Java 程序是跨平台的,何谓跨平台?笔者给你举个非跨平台的程序例子,下图是百度网盘客户端程序的下载页面,它为了能够运行在不同的平台(系统)上,开发了6套系统(其实是7套,还有一套网页版)。看到这其实你就应该能明白为什么跨平台是一个非常优秀的能力了。
-
-
-
-
-
-而经过刚才编写完第一个 Java 程序之后,我们思考一下 Java 为什么能跨平台?其实在开发步骤中就能找到原因。编译!编译会将源代码转变为字节码文件(伪二进制),而这伪二进制内容未来运行时是运行在 JVM(Java Virtual Machine)上的,换而言之,**其是因为 JVM 能跨平台安装,所以 Java 才能实现跨平台** 。
-
-由此,Java 程序员就可以不用考虑所写的程序要在哪里运行了,反正都是在 JVM 虚拟机上运行的,JVM 会负责将其变成相应平台的机器语言,而这个转变并不是程序员应该关心的。后续的很多优秀程序设计都采用了该思想。
-
-
-
-::: tip 《Java虚拟机的分析与研究》
-Java虚拟机有自己完善的硬件架构,如处理器、堆栈等,还具有相应的指令系统。
-
-Java虚拟机本质上就是一个程序,当它在命令行上启动的时候,就开始执行保存在某字节码文件中的指令。Java语言的可移植性正是建立在Java虚拟机的基础上。任何平台只要装有针对于该平台的Java虚拟机,字节码文件(.class)就可以在该平台上运行。这就是“一次编译,多次运行”。
-
-Java虚拟机不仅是一种跨平台的软件,而且是一种新的网络计算平台。该平台包括许多相关的技术,如符合开放接口标准的各种API、优化技术等。Java技术使同一种应用可以运行在不同的平台上。Java平台可分为两部分,即Java虚拟机(Java virtual machine,JVM)和Java API类库。[1]
-:::
-目前先了解到这种程度即可,JVM 深入学习是未来提升 "Java 内功" 的重要一步,但它不太适合刚学习的你。
-
-## 编译和反编译
-
-从刚才的学习中,我们清楚了编译是将源代码文件转换为了字节码文件,这字节码文件也是以后我们运行所需的。如果有一天,你的老板给你了一堆字节码文件,让你去借鉴一下内容(源代码),你当然知道字节码文件里是一堆乱码了,所以随着而来的,我们需要它再转换为源代码文件,这就是反编译,我们可以借助工具来更快,更好的批量处理。
-
-**编译:** 将源文件(.java)转换成字节码文件(.class)的过程称为编译。
-**反编译:** 将字节码文件(.class)转换回源文件(.java)的过程称为反编译。(常用有Jad、FrontEnd、jd-gui)
-
-此处仅仅演示利用 `Jad` 来将 HelloWorld.class 进行反编译,类似的工具还有 jd-gui 等。
-
-
-
-
-
-
-
-## 答题环节
-
-### 输出基本信息
-
-::: details 需求:逐行输出个人基本信息。
-提示:个人基本信息可包括:姓名、年龄、性别、身高、体重、婚否等
-:::
-
-## 参考文献
-
-[1]顾玮. Java虚拟机的分析与研究[J]. 办公自动化,2017,22(9):35-36,11
-
-## 后记
-
-今天这篇感觉怎么样?概念是否清楚了?语法是否记住了?万事开头难,加油啊同学!
-
-::: info 笔者说
-对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
-
-所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
-:::
-
diff --git a/docs/courses/java/01-Java语法入门/03-初识Eclipse.md b/docs/courses/java/01-Java语法入门/03-初识Eclipse.md
deleted file mode 100644
index 61abf243c..000000000
--- a/docs/courses/java/01-Java语法入门/03-初识Eclipse.md
+++ /dev/null
@@ -1,329 +0,0 @@
----
-title: 初识Eclipse
-author: 查尔斯
-date: 2020/10/04 22:09
-categories:
- - Java基础快速入门
-tags:
- - Java
- - Java基础
- - Eclipse
- - IDE
----
-
-# 初识Eclipse
-
-## 前言
-
-**C:** 古语有云:"工欲善其事必先利其器"。我们在上一篇开始编写第一个 Java 程序,体验过了一些单词大小写引发的"磕磕绊绊",还体验了来自中文输入法的深深"恶意"。
-
-那么我们对它们就没治了吗?只能到最后运行或单独调试才能知道错误吗?实际上对于这种语法型错误,我们可以使用专业的工具,在代码编写过程中随时解决掉,这种专业工具被称为:`IDE`。
-
-今天笔者将带着你领略一款成熟且有魅力的 Java 系 IDE。
-
-
-
-
-
-## IDE(集成开发环境)
-
-### IDE概述
-
-在前言中,笔者已经简单的说了一下 IDE 的能力,它们除了可以有效解决你的上述问题,还能让你编译和运行程序更便捷(提升开发效率),尤其使用上快捷键之后,可能鼠标都将"失业"!但是前期,笔者建议你,不要着急使用 `IDE` 的快捷键,先练练打字速度和完整语法吧。
-
-
-
-### 主流IDE介绍
-
-我们现在是学习 Java 语言的,所以自然要选择适合 Java 开发的 `IDE`。目前业内主流的 `Java IDE` 有这么几个。
-
-1. [Eclipse](https://www.eclipse.org/downloads/ )
-
- 开源免费的 `Java IDE`,由 `Eclipse` 基金会负责维护,为各种编程语言都开发了对应的版本或插件。在笔者看来,它的软件体积和使用方式比较适合初期和入门的编程人员。
-
- 
-
-2. [IntelliJ IDEA](https://www.jetbrains.com/idea/)
-
- 收费但很多"白嫖学习党"在用的 `Java IDE`,它所属的公司 `JetBrains` 也开发了适配各种编程语言的 IDE 版本。例如:适合前端开发的 `WebStorm`,适合 `PHP` 开发的 `PhpStorm`,适合`Python` 开发的 `PyCharm` 等,同样都是"价格不菲"。所以在正版收费和破解学习的斗争上,国内开发者们还需要走很远很远。
-
-3. [MyEclipse](http://www.myeclipsecn.com/ )
-
- 收费但很多"传统公司"还在用的 `Java IDE` ,这个软件看名字就知道和 `Eclipse` 脱不了关系,的确是这样的。它出身于 `Eclipse社区`,你可以理解为它是 Genuitec 公司为 `Java EE` 开发者们开发的 VIP 版本,对 `Java EE` 支持比较友好。其实学会了 `Eclipse` 之后,`MyEclipse` 基本就差不多了。
-
- 
-
-4. [VSCode(Visual Studio Code)](https://code.visualstudio.com/)
-
- 免费,微软大厂出品,本质上是一个编辑器,不算是 `IDE`。但是,国外用的挺多,需要自己安装插件进行配置才能支持更多的功能,对于新手来说还是比较麻烦的。
-
-::: tip 笔者说
-关于 `IDE` 具体哪个好,笔者最后委婉一下:所处学习阶段,"经费",个人习惯、公司团队等决定了使用哪一个,笔者个人只是从市场行情来带大家选择性学习,没有引起"IDE圣战"的意思。
-:::
-
-## 没落的王族IDE
-
-在程序开发过程中,得心应手的 `IDE` 永远令人着迷。在众多 `Java IDE` 中,如果单纯从初期学习建议和情怀角度评论的话,笔者更喜欢 `Eclipse` 。**免费** ,扩展性良好,初期使用体验不错,比较简单,这些都是笔者推荐给初期开发者的理由。
-
-虽然近两年在中高级开发中有所没落,但是不妨碍我们在初期学习一下。
-
-### Eclipse概述
-
-好的,我们再来回顾一下 Eclipse ,刚才惊鸿一瞥可能没留下太多印象。`Eclipse` 是一个开源免费的 `Java IDE`,由 `Eclipse` 基金会负责维护,为各种编程语言都开发了对应的版本或插件。在笔者看来,它的软件体积和使用方式比较适合初期和入门的编程人员。
-
-
-
-在 2020 年年初的时候,Jrebel 发布了[ 《2020年 Java 技术报告》](https://www.jrebel.com/blog/2020-java-technology-report),从来自全球 Java 开发专业人员的近 400 份回复中对 Java 技术生态进行了统计分析。其中在 IDE 的使用分析报告部分,`24%` 的人使用 Eclipse,仅次于 `IntelliJ IDEA`。
-
-
-
-在 `PYPL` 的 `TOP IDE` 热度排行榜上,`Eclipse` 则常年霸占第二。(其实也和 `Eclipse` 适配了很多编程语言版本,名字都叫 `Eclipse` 有关)。
-
-
-
-这些最新的数据看起来也还可以,实际上在更早前,数据还要更加可观。只不过在2017年左右开始,`JetBrains` 家的产品在国内开始风靡。
-
-例如:随着 `Spring Boot` 框架等现代开发技术的兴起,`JetBrains` 适配 Java 开发的 `IntelliJ IDEA` 产品以更加方便快捷的优势,快速抢占了 `Eclipse` 的市场,`Eclipse` 的使用者们纷纷"投敌"(但是 `Eclipse` 在现代开发方式中表现不争气,能咋办?很多程序员都是从 `Eclipse` 跳到`IDEA`,然后回不去了)。下面是近期 `IntelliJ IDEA` 与 `Eclipse` 的话题讨论,管中窥豹,可见一斑。
-
-
-
-好了,简单说这些,就是让你了解一下现状,见见"世面",不至于懵头懵脑的。
-
-本篇毕竟是 `Eclipse` 的主场,笔者也不再过多给你介绍其他 `IDE` 产品。想知道更多的,关于 `Eclipse` 在不同语言内的生态地位对比,自己度娘即可。
-
-### Eclipse下载
-
-接下来我们准备下载 `Eclipse`,`Eclipse` 官网有两种下载方式,一种是 `Installer`(exe 安装包,引导型安装),另一种是 `Package` (zip 包,解压就可以使用,类似于绿色软件)。
-
-笔者个人建议以 `Package` 形式下载,点击下载[Eclipse Zip版](https://www.eclipse.org/downloads/eclipse-packages/ )。
-
-下载前一定要牢记好三个注意:
-
-1. 注意要下载的 `Eclipse` 与安装的 `JDK` 版本是否契合
-
- 可查看 [Eclipse各版本JDK要求](https://wiki.eclipse.org/Eclipse/Installation)。
-
- | Eclipse版本 | 首发时间 | JDK版本 |
- | :------------------------: | :---------------: | :-------------------------------------: |
- | Eclipse 4.5 (Mars火星) | 2015年6月24日 | 1.7 |
- | Eclipse 4.6 (Neon霓虹灯) | 2016年6月22日 | 1.8 |
- | Eclipse 4.7 (Oxygen氧气) | 2017年6月28日 | 1.8及以上,1.9建议选择**4.7.1a** 及以上 |
- | Eclipse 4.8 (Photon光子) | 2018年6月27日 | 1.8及以上 |
- | Eclipse 4.9 (2018-09) | 2018年9月19日 | 1.8及以上 |
- | .......... | .......... | .......... |
- | **Eclipse 4.15 (2020-03)** | **2020年3月18日** | **1.8及以上,不再支持32位JVM** |
-
-2. 注意要下载的 `Eclipse` 与安装的 `JDK` 位数是否契合(要么都是32位,要么都是64位)
-
-3. 注意要下载的 `Eclipse` 与你的电脑系统和位数是否契合
-
-我们现在用的 `JDK 8`,下载当前的最新版 `Eclipse`(2020-03版本)就可以了。
-
-::: warning 笔者说
-时间在更替,笔者指的最新版未来不一定是现在这个,但是笔者仅在变动比较大时,会再进行内容更新,所以选择你打开时的最新版即可。
-:::
-
-
-
-在下载确认页面,我们可以点击 `Select Another Mirror` 选择其他的镜像源,可以选择国内的镜像源,不然下载太慢了。
-
-
-
-点击后进入 `Eclipse` 捐献页面,国外盛行捐赠文化,用来支持这些非盈利组织。不打算捐赠就在页面等一会儿,这个页面按理应该会自动开始下载或弹出下载提示,如果没有开始,你直接点击下图的 `click here` 也可以开始下载。
-
-
-
-等待一会儿,一个 `Eclipse` 的 `zip` 安装包就下载好了。
-
-
-
-### Eclipse安装
-
-笔者刚才的下载方式,下载的就是 zip 格式的压缩包。它就像你平时下载的一些破解版绿色软件一样,不需要双击 `.exe` 安装程序进行引导安装,只需要解压就可以使用。
-
-右键点击压缩包,寻找个位置解压,笔者还是希望你将其放到你统一的开发软件安装目录(例如:笔者之前建议的 `develop` 文件夹)。
-
-
-
-去掉勾选,否则提取的内容额外带一个压缩包同名文件夹。
-
-
-
-解压后,找到解压的位置。其中 `.exe` 结尾的就是启动程序。为了方便以后快速打开,可以右键在弹出的菜单中将其发送到桌面快捷方式。
-
-
-
-然后我们双击这个 `.exe `程序,或者双击快捷方式就可以打开 `Eclipse` 了。
-
-
-
-只要你上面三条下载注意都核查过了,并且 `JDK` 的 `JAVA_HOME` 配置按照笔者要求做了,理论上不会出现别的毛病。没配置好 `JAVA_HOME` 时,下方就是结果。
-
-::: tip 笔者说
-如果你是 `win 10` 还可能会出现一些兼容性问题:我们环境变量明明配置好了,但是还是提示下图,这时候需要你再次去看一下 `JAVA_HOME`,甚至不用动,去看环境变量然后确定关闭即可,这个问题曾经在部分同学那儿多次出现。
-:::
-
-
-
-打开之后,第一个弹出的窗口会要求你选择或输入一个文件夹地址作为工作空间,文件夹不存在时会自动创建。`Eclipse` 会使用此工作空间存储你的代码、项目和一些配置。
-
-注意:如果切换工作空间,那么你的配置等都需要重新设置,不同的工作空间是相互独立的。
-
-
-
-进入之后,则来到了欢迎界面,点击关闭 `Welcome选项卡` 进入程序主界面。
-
-
-
-## Eclipse初始配置
-
-好工具想使用好,也得调一调。
-
-### 视图窗口配置
-
-进入了主界面,自然意味着我们安装成功了。本身我们下载的就是适配 `Java EE` 开发的 `Eclipse`,所以打开之后,`Eclipse` 默认就处于 `Java EE` 视图下。但是笔者喜欢在 Java 视图操作,所以我们需要切换一下视图。不同的视图模式,一些窗口和菜单显示也不太一样,建议初期先用Java 视图模式。
-
-
-
-点击 `Open Perspective` 按钮,在弹出的对话框中,选中 Java,然后点击 `Open`,则切换到了Java 视图模式。
-
-
-
-
-
-可以在以后学习使用中,逐渐关闭一些自己用不上的选项卡窗口,也可以随意调整每个选项卡窗口的位置。如果弄乱了,不用怕,直接在右上角视图名上右击,选择 `Reset(重置)` 即可回到视图窗口初始的状态。
-
-
-
-另外还可以在 `Window` 菜单的 `Show View` 选项中去添加一些自己需要的选项卡窗口。
-
-例如:我们之后常用的 `Console` 控制台。
-
-
-
-最后初步调整后的窗口效果如下,你们后面使用久了,然后按自己习惯调整就好了。
-
-
-
-### 字符编码配置
-
-`Eclipse` 中默认使用 `GBK` 作为字符编码,但是为了解决我们之后开发的编码问题,我们要求统一字符编码的配置,将默认的字符编码更改为 `UTF-8`。因为这种字符编码具有更好的适用性,对于汉语和外语支持都比较好。
-
-点击 `Window` > `Preferences(首选项)`,Eclipse 的所有设置基本都在这里。
-
-
-
-在搜索框输入 `workspace`,然后选择图示的选项,在右侧的窗口中设置文本文件编码为 `Other` > `UTF-8`,然后点击 `Apply and Close(应用并关闭)`。
-
-
-
-### 字体配置
-
-最后一个配置,写代码没有一个合适的字体及大小怎么行?
-
-再次打开首选项窗口,依次点击 `General` > `Appearance` > `Colors and Fonts` > `Basic`,然后点击 `Basic` 里的最后一项 `Text Font(文本字体)`,双击或者点击 `Edit(编辑)` 进入修改界面。
-
-
-
-
-
-一般来讲,字体都是使用默认值 `Consolas`,只是简单改改大小,方便查看而已,选完之后,一直点确定即可应用成功。
-
-::: tip 笔者说
-实际上,Eclipse 现在已经支持在文本编辑时,通过 `Ctrl` 加 ` +号` 或 `-号` 来调整编辑区的文字大小了,这一步其实可以不用这么麻烦了。
-:::
-
-
-
-另外 `JetBrains` 为开发者设计了一套字体,据说可以降低眼疲劳,有需要的从 JetBrains 官网下载[JetBrains Mono字体](https://www.jetbrains.com/zh-cn/lp/mono/)安装即可。
-
-
-
-## 用Eclipse开发Java程序
-
-调也跳完了,接下来我们使用 `Eclipse` 来开发一个 `Hello World` 程序,看看它比我们第一章是不是要简单一些?
-
-用 `Eclipse` 开发 Java 程序,可以分为4个步骤:
-
-1. 创建一个 Java 项目(一个复杂的程序肯定需要很多源代码文件,我们以项目为单位来组织这些源文件)
-2. 手动创建 Java 源程序
-3. 编译 Java 源程序(在 Eclipse 中此步骤是自动的)
-4. 运行 Java 程序
-
-首先,我们打开配置好的 `Eclipse`,点击 `File` 菜单,选择 `New` 子菜单中的 `Java Project` 来创建一个新的 Java 项目。
-
-
-
-输入项目名称,然后确认下是否自动指定好了 `JRE运行库`(JAVA_HOME配置没有问题的话,应该无异常),此项内容很关键,决定是否能正常编写代码和编译运行。
-
-
-
-下图就是一个创建好的基本 Java 项目。
-
-
-
-那我们之前编写的源代码这时候要在哪里写?答案是在 `src(source)` 源码目录下编写。但是别直接就在 `src` 根目录下创建源文件,笔者要求你先创建一个包组织 `Package`(现在知道它是分类存放源文件的文件夹就可以了)。
-
-::: tip 笔者说
-`Package` 的命名是由公司域名的倒序组成的,例如:百度公司写com.baidu.xxx,并且包名的单词全部小写。
-:::
-
-右击 `src` 目录,然后 `New` 一个 `Package`,输入一个自定义包名,然后 `Finish`。
-
-
-
-
-
-创建好包后,然后右击 `包名` 进行创建类的操作,这一步才是上一篇我们直接写过的东西。
-
-
-
-
-
-创建好了,是不是和上篇写的一模一样了,但你发现 `pulic class xxx` 类声明这部分现在是用`Eclipse` 快速完成的,你不用再写它了,直接写程序入口 `main` 方法和内容就可以了。
-
-编写过程中,如果停下来,这时候没写完呢,`Eclipse` 会提示报错,这很正常,写完并保存后再看还报不报错。
-
-
-
-一顿火花带闪电的代码敲写,很舒服的把上篇的内容写完了,而且还有高亮及部分回车自动缩进的功能。
-
-
-
-输入完代码后,上章节我们需要 `javac` 先编译然后才能用 `java` 来运行这代码,现在有了`Eclipse`,直接在代码空白处右击,在弹出的菜单中选择 `Run As` > `Java Application`即可运行了(`Eclipse`帮你自动编译了)。
-
-
-
-
-
-::: tip 笔者说
-其实`Eclipse`还是在按照我们上篇的形式干活,只不过很多东西帮我们归了归类,做了些自动处理。
-:::
-
-你自己打开设定的工作空间地址,你会发现你刚创建的 Java 项目,实际就是一个特殊的多级文件夹。所以也无需担心玩不转 `Eclipse`,没你想的那么难,慢慢来,熟练就好了。
-
-
-
-
-
-## 后记
-
-到这里,`Eclipse` 的初步认识就结束了。`Eclipse` 还支持更改主题,自己有兴趣可以了解一下。其他的配置,未来我们需要的时候,就会进行对应讲解,目前已经够了。
-
-另外再提示一点:前期很多同学喜欢使用汉化版的软件,但是殊不知,在众多 `IDE` 中,中文的并不是很多,养成依赖之后尤为可怕,所以有“汉化一时爽,xxxx”一说。
-
-对于我们来讲,编程语言大多是英文的,如果我们连使用工具都要用中文,不仅显得不专业,而且也浪费了大好的学习英文的机会!
-
-所以谨记笔者的劝告,切勿汉化。
-
-
-
-`Eclipse` 还有一段传闻:据说是当初的 `SUN` 公司名称缩写是 `太阳` 的意思,而且 `SUN` 公司因为`Java` 的原因真的是如日中天,在互联网行业首屈一指,于是 `IBM` 希望能出一款产品来盖过太阳的光芒,所以起名为 `eclipse(日蚀)`。
-
-孰真孰假,不得而知。只是这个将伴随我们很久的开源 `IDE`,请记得善待它。
-
-::: info 笔者说
-对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
-
-所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
-:::
diff --git a/docs/courses/java/01-Java语法入门/04-程序和计算机的那点事儿.md b/docs/courses/java/01-Java语法入门/04-程序和计算机的那点事儿.md
deleted file mode 100644
index 2762958d9..000000000
--- a/docs/courses/java/01-Java语法入门/04-程序和计算机的那点事儿.md
+++ /dev/null
@@ -1,110 +0,0 @@
----
-title: 程序和计算机的那点事儿
-author: 查尔斯
-date: 2020/10/05 23:18
-categories:
- - Java基础快速入门
-tags:
- - Java
- - Java基础
----
-
-# 程序和计算机的那点事儿
-
-## 前言
-
-**C:** 在上一篇,我们已经能够很熟练的使用 Eclipse 开发一个入门的 Java 程序,给计算机下达一些简单的指令。虽然它很简陋,但麻雀虽小,五脏俱全,你平时使用的程序们该有的,它也都有。
-
-按理说呢,我们接下来就要开始学习更多的 Java 指令(语法)了,但笔者担心你的基础还不够,所以还是想给你再加点“料”。
-
-在我们讲解下一篇 《变量和常量》前,我们先对计算机中的一些基本概念,以及程序在计算机中安装、执行的原理来分析一下。
-
-
-
-## 计算机三大件
-
-我们现在使用的计算机,它是由很多的硬件组成的。但是一个程序要安装及运行,我们主要关注 **三个** 核心的硬件即可,它们分别是:
-
-| CPU | RAM | ROM |
-| :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: |
-| 中央处理器,负责 **处理数据**/**计算**,是一块超大规模的集成电路。 | 内存,**临时** 存储数据(断电之后,数据会消失),速度快,空间小。 (单位价格高) | 硬盘,**永久** 存储数据,速度慢,空间大。(单位价格低) |
-|  |  |  |
-
-我们可以拿车道的例子来理解它们三者的作用:
-
-- CPU核心数:车道数量
-- 内存:车道宽度
-- 磁盘IO:车道限速
-
-
-
-## 案例分析:QQ程序
-
-结合着 QQ 程序,我们来捋一下一个程序从安装到运行的原理。
-
-### 程序安装的原理
-
-像我们以后,在开发好了程序之后,如果需要给客户来使用,就要准备好相应的程序包,否则到了客户电脑上,缺少程序所需的环境就无法运行程序了,例如:Java 程序至少需要配套一份 JRE 。
-
-我们在平时为了使用 QQ 程序,首先要做的就是下载一个对应的安装程序,然后通过安装程序来引导我们或自动将 QQ 的一系列程序文件解压并存储到 **硬盘** 的指定位置。
-
-
-
-
-
-
-
-
-
-::: tip 笔者说
-安装程序/引导安装程序,它们也是电脑程序,但它们诞生的目的是帮助普通用户快速实现程序的环境初始化、程序文件存储等过程的。
-:::
-
-### 程序执行的原理
-
-安装好之后,而当要运行一个程序时,首先操作系统会让 **CPU** 将存储在 **硬盘** 中的程序文件们读取到 **内存** 中来,然后由 **CPU** 执行 **内存** 中的程序文件/代码来处理数据。
-
-
-
-::: tip 笔者说
-每个程序在运行过程中都会在内存中"占据"一块属于自己的空间,而这块空间的大小及内存的总大小也是决定程序是否可以自如的"施展手脚"。
-
-所以一般我们想要同时运行更多程序而不卡时,都优先考虑到买大内存的计算机。
-:::
-
-当你在双击桌面的 QQ 快捷方式时,快捷方式会链接到对应位置的 QQ 程序,启动开始了。
-
-
-
-然后 **CPU** 就会将存储在 **硬盘** 上的 QQ 程序文件加载到 **内存** 中,QQ 程序会在 **内存** 中占据一块自己的内存区域,然后由 **CPU** 执行 **内存** 中的 QQ 程序文件/代码,于是就出现了下方的界面。
-
-
-
-### 程序内存中的数据管理
-
-当我们在 QQ 程序中点击过 “记住密码”,并且登录过一次后,我们再次打开 QQ 程序登录界面,会看到 QQ 号及密码直接回显在了输入框中。
-
-此时其实是在加载 QQ 程序文件到内存后,QQ 程序代码将保存在硬盘上的帐号数据恢复到了内存中,然后将它们再插入到输入框内。
-
-
-
-那 QQ 这个程序是怎么保存用户的 **QQ 号码** 和 **QQ 密码** 的呢?
-
-1. 在内存中为 **QQ 号码** 和 **QQ 密码** 各自分配一块空间
- * 在 QQ 程序结束之前,这两块空间是由 QQ 程序负责管理的,其他任何程序都不允许使用
- * 在 QQ 自己使用完成之前,这两块空间始终都只负责保存 **QQ 号码** 和 **QQ 密码**
-2. 另外为了能够方便找到该内存空间,分别使用一个 **别名** 标记 **QQ 号码** 和 **QQ 密码** 在内存中的位置
-
-
-
-## 后记
-
-实际上,在程序内部,为 **QQ 号码** 和 **QQ 密码** 在内存中分配的空间就叫做 **变量**,这也是我们下一篇要介绍的主要内容!跟上别掉队!
-
-
-
-::: info 笔者说
-对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
-
-所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
-:::
\ No newline at end of file
diff --git a/docs/courses/java/01-Java语法入门/05-变量和常量.md b/docs/courses/java/01-Java语法入门/05-变量和常量.md
deleted file mode 100644
index f9629690d..000000000
--- a/docs/courses/java/01-Java语法入门/05-变量和常量.md
+++ /dev/null
@@ -1,362 +0,0 @@
----
-title: 变量和常量
-author: 查尔斯
-date: 2020/10/06 23:56
-categories:
- - Java基础快速入门
-tags:
- - Java
- - Java基础
----
-
-# 变量和常量
-
-## 前言
-
-**C:** 上一篇,笔者给你加了点“料”,捋了捋一个 QQ 程序从安装到运行起来究竟做了哪些事儿。在上一篇的最后,我们还分析到了一个结果就是当程序运行在内存中后,程序产生的数据也要在内存中妥善的管理起来,以方便使用和复用,这种基础的数据管理概念就是 **变量** 。
-
-
-
-不知道,大家是否还记得在[《开发环境搭建》](./01-开发环境搭建)中我们介绍过一个环境变量的概念,**环境变量就是操作系统里存储的一些环境参数或关键值,每个在操作系统里运行的程序都可以获取到这些存储的内容** 。因为它存储的是环境信息,又因为这些信息是可变的值,所以它叫环境变量。
-
-而本篇我们也要正式学习下 Java 程序中的变量,它是我们学习的第一个基础知识点,先来感受下它的用处吧,例如下方代码,在进行输出个人介绍时,如果想修改姓名的话,需要单个依次修改。
-
-此时我们就可以将个人介绍中的姓名存储成变量,然后就可以很方便的修改或重复使用了。
-
-```java
-// 未使用变量
-System.out.println("我是时间管理大师:小罗");
-System.out.println("我是时间管理大师:小罗");
-System.out.println("我是时间管理大师:小罗");
-System.out.println("我是时间管理大师:小罗");
-```
-
-```java
-// 使用变量
-String name = "小罗";
-System.out.println("我是时间管理大师:" + name);
-System.out.println("我是时间管理大师:" + name);
-System.out.println("我是时间管理大师:" + name);
-System.out.println("我是时间管理大师:" + name);
-```
-
-
-
-## 变量概述
-
-::: tip 笔者说
-变量来源于数学,是计算机语言中能 **储存计算结果或能表示值** 的抽象概念。在一些语言中,变量可能被明确为是能表示可变状态、具有存储空间的抽象(如在 Java 和 Visual Basic 中)。[1~2]
-:::
-
-如下图,当 Java 程序运行过程中,我们需要存储一些数据,此时就可以在内存开辟一个个变量空间来存储这些可变的数据。
-
-
-
-通俗的来讲:变量就是一个数据存储空间的表示,不同数据存入具有不同内存地址的空间,彼此相互独立。
-
-## 变量的组成
-
-将数据以 变量 形式存入内存之后,我们怎么找到和使用它呢?
-
-- 第1种方式:可以通过内存地址值去寻找。但是这地址值是类似于:0x12345….这种无关联的组成,每次记忆极其繁琐。
-- 第2种方式:使用变量名来快速简便的找到存入的变量,找到变量自然也就找到数据了。
-
-其实变量这个概念,可以去类比生活中去酒店入住的场景。
-
-酒店就是 `JVM` 内存,房间就是一个个的 `变量`,房间的名字就是 **变量名**(毕竟你想想酒店前台告诉你房间位置时,应该都是告诉你房间号,而不是给你指路:上3楼直走5个房间,右转后第3个房间对面);
-
-酒店还会为不同客人提供不同类型的房间,满足特别的需求。`JVM` 内存中,也是这么来安排数据的,我们称之为**变量类型** ;房间里入住的客人就是 **变量里存储的值** 。
-
-而且变量即可以变化的量,酒店房间的客人也是变动的,这么理解起来,简直太容易了。所以笔者建议你,以后想到变量就多想想开房。
-
-
-
-::: tip 笔者说
-所以,变量的组成是由:变量名、变量类型、变量值三个部分组成。
-
-变量值就没必要看了,它就是你要存储的数据,爱存啥就存啥,但是其他的两个部分我们需要详细研究研究。
-:::
-
-### 变量名
-
-**变量名也就是标识符,其实就是为了方便区分不同的变量。但这个名也不是随便起的,在长久的演化中,我们有了约定俗成的规范。**
-
-::: warning 变量名命名规范
-1. 可以使用数字、字母,下划线和 `$` 符号组成,但数字不能开头。
-
-2. 不能使用关键字(`public`、`void`等)和保留字(`goto`、`def`等)!**关键字:被系统定义了特殊含义的单词。保留字:现在还不是关键字,未来可能有特殊含义。**
-
-3. 起名要见名知意。 例如:`name` 一看就知道是名字,`password`是密码等。
-
-4. 采用小驼峰式命名法(Lower Camel Case)。(所谓小驼峰命名法,即首字母小写,如果有多个单词那么后面的每个单词首字母大写。`e.g. userPassword`) ;另外因为支持下划线,所以有些时候会有下划线连接法命名的变量。`e.g. user_password`。
-
- 
-
-5. 要么名称全为拼音,要么全为英文单词。`e.g.(X)myMingZi`。
-
- 
-:::
-
-### 数据类型
-
-不同类型的值要以不同的形式存储,那么在 Java 中,它将不同的值划分了多少类型呢?
-
-**数值型有:**
-
-- 整型:byte、short、int、long
-- 浮点型:float、double
-
-**非数值型:** char(字符型) 、boolean(布尔型) 、String(字符串型,一个比较特别的类型,先记住它不是基本数据类型,是引用数据类型即可)
-
-下方是数值型数据类型的取值范围表:
-
-| 数据类型 | 大小 | 取值范围 |
-| :------: | :-------------: | :--------------------------------------------: |
-| byte | 1字节8位 | -128 ~ +127 |
-| short | 2字节16位 | -32768 (-2^15^) ~ +32767 (+2^15^ - 1) |
-| int | 4字节32位 | -2147483648 (-2^31^) ~ +2147483647 (2^31^ - 1) |
-| long | 8字节64位 | -2^63^ ~ +2^63^ - 1 |
-| float | 4字节32位浮点数 | 1.4E-45 ~ 3.4E+38, -1.4E-45 ~ -3.4E+38 |
-| double | 8字节64位浮点数 | 4.9E-324 ~ 1.7E+308, -4.9E-324 ~ -1.7E+308 |
-
-::: tip 笔者说
-在 Java 中,如果定义一个变量,**整数数据默认为 int 类型,小数数据默认为 double 类型。**
-
-因为 int 和 double 的取值范围已经满足了我们大多数时的使用要求了。
-:::
-
-## 变量的使用步骤
-
-聊完了概念后,我们接下来一起用一下吧!
-
-第一步:声明变量,根据数据类型在内存申请空间。
-
-```java
-// 数据类型 变量名;
-int money;
-```
-
-第二步:赋值,即“将数据存储至对应的内存空间”。
-
-```java
-// 变量名 = 数值;
-money = 1000;
-```
-
-tips:第一步和第二步可以合并。
-
-```java
-// 数据类型 变量名 = 数值;
-int money = 1000;
-```
-
-第三步:使用变量,即“取出数据使用”。(也就是拿着变量名去使用)
-
-```java
-public class Demo1{
- public static void main(String[] args){
- // 在方法中声明的变量 被称为局部变量 局部变量如果没赋值前是无法使用的
- // 第1种使用方式 先声明再赋值
- // 声明变量(联想记忆:在内存中开好201双人房间)
- // 此变量将用来存储银行卡存款
- int money;
- // 赋值 =号不再是数学里的相等,而是表示将右边的内容赋值给左边的
- money = 1000;
-
- // 第2种使用方式 声明的同时并赋值
- int money1 = 1000;
- // 使用变量
- System.out.println("我的银行存款为:" + money1 + "元");
- }
-}
-```
-
-再来个小练习,吸收一下吧,案例需求如下:
-
-**要求使用变量来完成如下操作:**
-- 输出Java课考试最高分:98.5
-- 输出最高分学员姓名:张三
-- 输出最高分学员性别:男
-
-```java
-// 声明一个变量用来存储最高分
-double score = 98.5;
-// 如果是float类型来存储数据,那么必须在值后添加 f/F
-// 其实double类型的后面也应该加d/D,但Java中出现的浮点类型默认为double,所以不需要加
-// float score1 = 98.5F;
-
-// 声明一个变量用来存储学员姓名
-// 双引号包裹的内容是一个字符串
-String name = "张三";
-// 声明一个变量用来存储学员性别
-// 单引号包裹的事一个字符
-char gender = '男';
-
-// 使用变量:使用+号来拼接使用变量
-// 只要是与字符串用+号拼接的都成为了字符串
-System.out.println("最高分为:" + score);
-System.out.println("姓名为:" + name);
-System.out.println("性别为:" + gender);
-// 等价于下方
-System.out.println("最高分为:" + score + "\n姓名为:" + name + "\n性别为:" + gender);
-```
-
-## 常量概述
-
-在程序运行中,我们需要使用一些数据,但是这些数据在存储好后不应该再次发生变化(例如:`π`)。这时候,单纯使用变量存储,毕竟叫变量,难保未来可能被不小心重新赋值。
-
-此时我们可以使用常量来解决此问题。
-
-::: tip 笔者说
-在Java中,其值不能改变的变量被称为常量。常量被final修饰,被final修饰则无法二次修改值。
-:::
-
-为了和变量做出区别,常量在命名上也有一些小要求。
-
-::: warning 常量名命名规范
-1. 常量名所有字母都要大写。
-
-2. 如果有多个单词,多个单词之间使用下划线分隔。例如:`MY_NAME`
-
-3. 只能被赋值一次(被final修饰),通常定义时即对其初始化。
-:::
-
-```java
-public class Demo2{
- public static void main(String[] args){
- // 声明一个变量 用来表示π
- // 被final修饰的变量,无法再进行第二次改值 必须进行初始赋值,不能分开声明和赋值
- final double PI = 3.14;
- // 声明一个变量用来表示半径
- int radius = 7;
-
- // 计算圆的面积 π * r * r
- double area = PI * radius * radius;
- System.out.println("圆的面积为:" + area);
- }
-}
-```
-
-## 程序交互
-
-在上述的练习使用中,笔者定义的变量全是自己直接定义好值的。如果现在想要让变量值变成一个灵活动态的内容,通过键盘来灵活输入,这时候我们就需要使用 Java 给我们准备好的 `Scanner` 工具来解决。
-
-**Scanner的使用需要一个固定的步骤!前期牢记即可!后期学习`类`之后就懂了。**
-
-第一步:导入`Scanner`类。
-
-```java
-import java.util.Scanner;
-```
-
-第二步:创建`Scanner`对象。
-
-```java
-Scanner input = new Scanner(System.in);
-```
-
-第三步:获得键盘输入的数据,并自动将其转换存储为`int`类型的变量`now`。
-
-```java
-int now = input.nextInt();
-```
-
-```java
-// 1.在类声明上方 添加导入语句
-import java.util.Scanner;
-
-public class Demo3{
- public static void main(String[] args){
- // Scanner 可以用来进行键盘录入的API
- // 2.创建Scanner对象 input就是个变量名
- Scanner input = new Scanner(System.in);
-
- // 3.使用Scanner的方法 对象名.xxx()来调用方法
- System.out.print("请输入您的年龄:");
- int age = input.nextInt();
-
- System.out.println("输入的年龄为:" + age);
- }
-}
-```
-
-## 类型转换
-
-上述就是变量的基本玩法,简单吗?接下来我们再了解一些特别的小知识,加深你对数据类型的理解。
-
-### 自动类型转换
-
-看一下下方的代码,`double `类型的变量竟然在存储整数类型?而且它不会出现任何语法错误?
-
-```java
-// 自动类型转换
-double num1 = 10;
-System.out.println(num1); // 10.0
-// 两个操作数只要其中一个是double类型的,那么计算结果就会提升为double类型
-// 取值范围小的 自动类型转换为 取值范围大的
-double num2 = 10 + 10.1;
-System.out.println(num2); // 20.1
-```
-
-这是因为在 Java 中,如果两种类型相互兼容(例如整型和浮点型同属数值类型),那么取值范围小的类型(精度低),可以自动类型转换为取值范围大的类型(精度高)。
-
-### 强制类型转换
-
-有自动类型转换,那应该也有非自动的,我们称之为强制类型转换。
-
-看个小例子:去年Apple笔记本所占市场份额是20,今年增长市场份额是9.8,求今年所占份额?
-
-```java
-// apple 笔记本市场份额
-int before = 20;
-// 增长的份额
-double rise = 9.8;
-// 现在的份额
-int now = (int)before + rise; // 29 不是四舍五入,是直接取整了
-```
-
-我们发现,上面的代码最后结果使用了`int`来存储,但是却需要用`(int)`来标注一下,否则会提示语法错误。这是因为当`int`和`double`型数据计算后,它们的结果值已经自动类型转换为了`double`型,而最后又要将其转换为`int`型,也就是将一个取值范围大(精度高)的数据,存储到取值范围小的(精度低)类型里,那自然会损失精度(小数点位的值全丢了),所以需要自己手动标注一下,表明我们自己认可这件事。
-
-::: tip 帮助理解类型转换的故事
-假设现在有一个100斤的小麦口袋,还有一个40斤的大米口袋,如果我想把两个口袋换着装东西,40斤大米口袋内的大米自然可以放到100斤小麦口袋里(自动类型转换),但是反之则不行,如果非要这么做,多余的小麦肯定会洒出来(强制类型转换)。
-:::
-
-```java
-// 口袋:100斤小麦
-double mian = 100;
-// 口袋:40斤大米
-int mi = 40;
-
-mi = (int)mian;
-```
-
-## 答题环节
-
-### 变量练习
-
-::: details 需求:使用变量分别存储个人基本信息,然后逐行输出个人基本信息。
-提示:个人基本信息可包括:姓名、年龄、性别、身高、体重、婚否等
-:::
-
-::: details 需求:使用 Scanner 分别录入个人基本信息、并逐行输出。
-提示:个人基本信息可包括:姓名、年龄、性别、身高、体重、婚否等
-:::
-
-## 参考文献
-
-[1]谭浩强. C程序设计(第4版):清华大学出版社,2010.6
-
-[2]明日科技. Visual Basic从入门到精通:清华大学出版社 ,2017.6
-
-## 后记
-
-无论是学习任何编程语言,变量都是必不可少的一个基础内容,所以,结合上一篇《Java语法 | 程序的那点事儿》好好在脑海中构想一下变量在内存中的使用吧。
-
-
-
-::: info 笔者说
-对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
-
-所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
-:::
-
diff --git a/docs/courses/java/01-Java语法入门/06-常用的运算符.md b/docs/courses/java/01-Java语法入门/06-常用的运算符.md
deleted file mode 100644
index 0039fb065..000000000
--- a/docs/courses/java/01-Java语法入门/06-常用的运算符.md
+++ /dev/null
@@ -1,354 +0,0 @@
----
-title: 常用的运算符
-author: 查尔斯
-date: 2020/10/06 23:58
-categories:
- - Java基础快速入门
-tags:
- - Java
- - Java基础
----
-
-# 常用的运算符
-
-## 前言
-
-**C:** 计算机,计算机,顾名思义就是用来进行数据计算的。那在计算机中运行的程序们,也是用于计算和处理数据的,每个编程语言都准备了很多常用的运算符来支撑我们的数据处理和计算。
-
-而且我们有良好的数学打底的话,它们一点也不难。
-
-
-
-
-
-## 赋值运算符
-
-首先要介绍的就是 `=` 号,上篇我们见过了,在 Java 中它不再表示相等,而是表示赋值的意思。前期每次遇到它你都要给自己刷刷被数学洗过的脑:"它不是相等判断,它是赋值符号,即将等号右侧的内容赋值给左侧的变量"。
-
-**案例需求:学员王浩的Java成绩是80分,学员张萌的Java成绩与王浩的相同,输出张萌的成绩。**
-
-```java
-public class Demo {
- public static void main(String[] args) {
- // 王浩成绩
- int wScore = 80;
- // 张萌成绩
- int zScore;
- // 赋值:把等号右边的赋值给等号左边的
- zScore = wScore;
- System.out.println("张萌的成绩为:" + zScore);
- }
-}
-```
-
-::: tip 笔者说
-几乎所有编程语言中,= 号都表示赋值,再或者又表示赋值又表示相等。
-:::
-
-我们再来个案例感受下赋值符号。
-
-::: details 案例需求:将 num1 和 num2 实现值的交换。
-**思路:** 下方的 num1 和 num2 的值要想交换一下,可以通过第3个变量作为中间媒介来进行。
-::: tip 笔者说
-当我们想让一瓶雪碧和一瓶可乐里的液体进行交换时,我们没办法直接将它们交换,需要用到一个空瓶子来做个中转。
-:::
-
-```java
-public class Demo {
- public static void main(String[] args) {
- int num1 = 8;
- int num2 = 9;
- // 此种转换方式不可行 类似于雪碧和可乐直接对嘴换
- // num1 = num2; num1结果为9
- // num2 = num1; num2结果为9
-
- // 定义一个中间变量来实现交换
- int temp;
- temp = num1; // temp为8
- num1 = num2; // num1为9
- num2 = temp; // num2为8
- }
-}
-```
-
-## 算术运算符
-
-除了赋值符号外,我们最不陌生的应该就是算术运算符了:
-
-- `+` 加法运算符
-- `-` 减法运算符
-- `* ` 乘法运算符
-- `/` 除法运算符
-- `%` 求模运算符(求余数)
-- `++` 自增运算符
-- `--` 自减运算符
-
-### 四则运算
-
-这些运算符,前五个都挺简单,看看下面的示例:
-
-```java
-int num1 = 11;
-int num2 = 2;
-System.out.println(num1 + num2); // 13
-System.out.println(num1 - num2); // 9
-System.out.println(num1 * num2); // 22
-System.out.println(num1 / num2); // 5
-System.out.println(num1 % num2); // 1
-```
-
-::: details 你肯定很好奇为什么 11 / 2 = 5 ? 而不是等于 5.5 ?
-其实这就要涉及到数据类型的问题了,在 Java 中参与计算时,两个数参与计算会以其中取值范围大的类型为最终计算结果的类型。
-
-因为上方是两个 int 类型变量,所以计算后的结果也是 int 类型。如果你执意想要更精确的结果,那么可以将输出语句这么改造: `num1 * 1.0 / num2` 。这样的话,其中一方已经变为了更大取值范围的类型,计算结果的类型也会与之变化为取值范围大的类型。
-:::
-
-
-
-### 自增和自减
-
-但你可能对最后两个算术符号产生疑惑,`++` 和 `--` 到底是用来干嘛的?
-
-```java
-// ++ 和 --都分为前置形式和后置形式
-int num1 = 10;
-
-// 下方示例,以 ++ 符号为例,++ 和 -- 同理。
-// 1.如果单独写成一条语句,前置 ++ 和后置 ++ 都是表示自增1
-// 等价于 num1 = num1 + 1;(num1 = 10 + 1) 将num1的值+1然后再赋值给num1,这样num1的结果为11
-// num1 ++;
-// ++ num1;
-
-// 2.但如果 ++ 被放在复杂情况使用时,前置和后置是有区别的
-// System.out.println(++ num1); // 11
-System.out.println(num1 ++); // 10
-System.out.println(num1); // 11
-```
-
-::: tip 笔者说
-之所以出现此类结果,目前简单理解为前置++表示先自增然后再使用值,后置++表示先使用值再自增。
-
-即:System.out.println(++ num1); 中,先是将num1进行了自增操作,变为了11,然后再进行了输出。
-
-System.out.println(num1 ++); 中,先是使用num1进行了输出,结果是10,然后再对变量num1进行了自增操作,所以下方再打印输出num1,结果就成为了11。
-:::
-
-我们再来看下方的代码,试着去读一读它们的含义,分析下结果是否和你预期的一致。
-
-```java
-int num1 = 5;
-int num2 = 2;
-int a = num1 % num2;
-int b = num1 / num2;
-System.out.println(num1 + "%" + num2 + "=" + a); // 5 % 2 = 1
-System.out.println(num1 + "/" + num2 + "=" + b); // 5 / 2 = 2
-num1 ++;
-num2 --;
-System.out.println("num1 = " + num1); // num1 = 6
-System.out.println("num2 = " + num2); // num2 = 1
-```
-
-::: warning 笔者说
-学会阅读代码,真的很重要,你自己写的也好还是别人写的也罢,读不懂它们,就无法理清楚它想干什么,不要怕!尝试尝试!
-:::
-
-### char类型的秘密
-
-**补充:++ 和 - - 与 char 类型相遇时的问题:**
-
-```java
-// 如果字符执行数值计算 那么它会自动转换为对应码表中数值然后执行计算
-char alp1 = 'a';
-char alp2 = 'A';
-System.out.println(alp1 + 1); // 98
-System.out.println(alp2 + 1); // 67
-```
-
-char 类型是可以转换为 int 整数类型的,它的转换规则是按照 ASCII 、DBCS 这些码表来进行的。例如:小写的 a 是 97,大写的 A 是 65。
-
-
-
-::: tip ASCII ((American Standard Code for Information Interchange)
-美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是最通用的信息交换标准,并等同于国际标准ISO/IEC 646。ASCII第一次以规范标准的类型发表是在1967年,最后一次更新则是在1986年,到目前为止共定义了128个字符。[1]
-
-为了解决中国、日本和韩国的象形文字符和ASCII的某种兼容性,出现了双字节字符集(DBCS:double-byte character set)。DBCS从 第256 代码开始,就像ASCII一样,最初的128个代码是ASCII。然而,较高的128个代码中的某些总是跟随着第二个字节。这两个字节一起(称作首字节和跟随字节)定义一个字符,通常是一个复杂的象形文字。[2]
-:::
-
-```java
-// 如果执行++或-- 则是先将char类型转换为整数类型,然后计算完之后再将其转换为char类型
-char alp1 = 'a';
-alp1 ++; // alp1 = alp1 + 1;
-System.out.println(alp1); // b
-```
-
-## 复合赋值运算符
-
-在同时认识了赋值、算术两种运算符之后,笔者再带你认识 Java 中一种特别的运算符:复合赋值运算符,即整合了赋值运算符和算术运算符的特别玩法。
-
-- `+=`
-- `-=`
-- `*=`
-- `/=`
-- `%=`
-
-虽然长的奇特,但是它们理解起来真的很容易,看看下方的示例:
-
-```java
-int num1 = 10;
-num1 -= 2; // 等价于 num1 = num1 - 2;
-System.out.println(num1); // 结果为8
-```
-
-## 关系运算符
-
-Java 中还有可以用来判断的关系运算符,以后我们应用还是很多的。
-
-例如:当用户名长度小于5时,做一些错误提示等,这都是关系运算符要应用的场景。
-
-关系运算符计算的结果是一个布尔类型(boolean)的值,要么是 true,要么是 false。
-
-| 运算符 | 含义 | 示例 | 结果 |
-| :----: | :------: | :-----: | :---: |
-| \== | 等于 | 5 == 6 | false |
-| \!= | 不等于 | 5 != 6 | true |
-| \> | 大于 | 5 \> 6 | false |
-| \< | 小于 | 5 \< 6 | true |
-| \>= | 大于等于 | 5 \>= 6 | false |
-| \<= | 小于等于 | 5 \<= 6 | true |
-
-```java
-// 下方代码用 () 括起来纯粹为了让 == 和 = 分开一些,更利于阅读
-boolean result = (5 == 2);
-System.out.println(result); // false
-// 在Java中 不相等用!=表示
-boolean result1 = (5 != 2);
-System.out.println(result1); // true
-```
-
-::: tip 笔者说
-牢记的方法就是多在对应代码上写注释,= 号表示赋值运算符 == 表示相等关系。
-:::
-
-## 逻辑运算符
-
-生活中,有很多情况下不是一个关系运算符就可以做好判断的,例如:我想表示某个成绩范围 70 到 80,Java 中不可以用 `70 < x < 80`,这时候就需要用逻辑运算符了。
-
-所谓逻辑运算符,就是我们生活中的 **并且、或者** 这些意思。`70 < x < 80` 代表的就是 x 大于 70,**并且** x 小于 80,可以用逻辑运算符写为`x > 70 && x < 80`。
-
-| 运算符 | 含义 | 运算规则 |
-| :----: | :---------: | :----------------------------------------------------: |
-| \&& | **短路** 与 | 两个操作数都是true,结果才是true |
-| \|\| | **短路** 或 | 两个操作数一个是true,结果就是true |
-| \! | 非 | 操作数为true,结果为false;操作数为false,结果就为true |
-
-```java
-// 短路与:&& 理解为并且 两个条件都为true,结果才能为true true && true == true;
-// 短路或:|| 理解为或者 只要有一个条件为true,结果就为true true || false == true;
-// 非:! 理解为取反 !true == false; !false == true;
-boolean flag1 = (2 == 3); // false
-boolean flag2 = (2 == 2); // true
-boolean flag3 = flag1 && flag2;
-boolean flag4 = flag1 || flag2;
-System.out.println(flag3); // false
-System.out.println(flag4); // true
-```
-
-### 神奇的短路功能
-
-其实除了 `&&` 还有单个 `&` 也可以表达一样的意思,`||` 还有 `|` 也可以表达一样的意思。
-
-但是我们一般推荐使用 `&&` 或 `||` ,原因就是它们拥有 **短路** 功能。
-
-```java
-// ======非短路情况======
-int num1 = 7;
-// 无论第一个表达式是否成立,都会执行后面的表达式
-boolean flag2 = false & (++ num1 != 8);
-System.out.println(flag2); // false
-// ++ num1执行了,所以结果为8
-System.out.println(num1); // 8
-```
-
-```java
-// ======短路情况======
-int num1 = 7;
-// 因为第一个表达式是false,它已经可以决定flag2的值就是false,所以&&后的表达式不会再执行,构成了短路
-boolean flag2 = false && (++ num1 == 8);
-System.out.println(flag2); // false
-// ++ num1没有执行,所以保留为原值
-System.out.println(num1); // 7
-```
-
-::: tip 笔者说
-毫无疑问,短路符号可以更加节省程序的执行效率。
-:::
-
-## 三目运算符
-
-在 Java 中还支持一种特殊的运算符,三目运算符,它又被称为三元运算符。
-
-
-
-```java
-// 三目运算符(三元运算符)
-// 条件 ? 表达式1 : 表达式2
-// 如果条件为true,则执行表达式1,否则执行表达式2
-int min = (5 < 7) ? 5 : 7;
-System.out.println(min); // 5
-int max = (10 < 7) ? 7 : 10;
-System.out.println(max); // 10
-```
-
-::: tip 笔者说
-后期用的还挺多的,用好了它可以有效优化一些代码结构,简化代码。
-:::
-
-## 运算符优先级
-
-至于运算符号的优先级,笔者则认为无需记忆,只要记住:想让谁先运行,就给其加小括号即可。而且其实从我们多年的数学习惯来看,加小括号也有利于阅读。
-
-- 单目运算符包括 `!` 、`++`、 `--`,优先级别高
-- 优先级别最低的事赋值运算符
-- 可以通过 `()` 控制表达式的运算顺序,`()` 优先级最高
-- 从右向左结合性的只有赋值运算符、三目运算符和单目运算符
-- 算术运算符 > 关系运算符 > 逻辑运算符
-
-## 答题环节
-
-### 计算BMI
-
-::: details 需求:使用 Scanner 收集身高体重,并计算出相应BMI值是多少?
-
-提示:BMI的计算公式是:体重(kg) / (身高 \* 身高)
-
-例如:小明的体重是 72 kg, 身高是1.69,那么小明的 BMI 就是:72 / (1.69 \* 1.69)
-:::
-
-### BMI 判定
-
-::: details 需求:亚洲人的肥胖标准应该是 BMI 在18.5至24.9时为正常水平,根据计算的 BMI,告知使用者身体是否标准。
-
-标准:您当前的 BMI 符合正常水平!
-
-不标准:您当前的 BMI 偏离正常水平!
-:::
-
-
-
-## 参考文献
-
-[1]莫绍强、陈善国. 计算机应用基础教程:中国铁道出版社,2012年:12-13
-
-[2]钟小莉, 谢旻旻, 李永宁. 文字编码与Unicode编码研究[J]. 经营管理者, 2010(20):364-364.
-
-## 后记
-
-这些符号我们也算初步认识过了,你还 **可** 以吗?你的数学基础是否给予你了力量呢?计算机学习不仅要和英语打交道,数学更加重要,不过还好目前笔者更新的都是小白文,对数学基础要求不是太多。
-
-
-
-::: info 笔者说
-对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
-
-所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
-:::
diff --git a/docs/courses/java/01-Java语法入门/07-控制语句和流程图.md b/docs/courses/java/01-Java语法入门/07-控制语句和流程图.md
deleted file mode 100644
index c888c7f0e..000000000
--- a/docs/courses/java/01-Java语法入门/07-控制语句和流程图.md
+++ /dev/null
@@ -1,187 +0,0 @@
----
-title: 控制语句和流程图
-author: 查尔斯
-date: 2020/10/07 11:15
-categories:
- - Java基础快速入门
-tags:
- - Java
- - Java基础
----
-
-# 控制语句和流程图
-
-## 前言
-
-**C:** 现实生活中,我们每天都要面对各种选择,有句俗话叫" **选择比努力更重要** ",不同选择所对应的结果千差万别。在程序中编写的代码也是如此,我们上篇学到的关系运算符、逻辑运算符就可以让程序执行不同的选择。
-
-而且对于初级的应用开发者(码农),未来每天的工作就是用本篇的选择结构写业务逻辑,所以它们学起来也不难,因为经常要使用啊!
-
-在介绍 Java 中的选择结构语法前,笔者先带你在本篇介绍一下程序中常见的流程控制语句。
-
-
-
-## 概述
-
-在前几篇的学习中,我们也写了几行代码,而且也明白编写的代码是自上而下依次执行下来的,编写几行,就会自上而下执行几行。这种"一根筋"的代码被称为 **顺序控制语句** 。
-
-
-
-## 顺序控制语句
-
-**顺序控制语句** 是程序中最简单的流程控制,按照代码执行的先后顺序,依次执行,程序中的大多数代码都是这样执行的。[1]
-
-
-
-
-
-```mermaid
-flowchart LR
- A([开始]) --> B[语句1]
- B --> C[语句2]
- C --> D[语句3]
- D --> E([结束])
-```
-
-## 选择控制语句
-
-本篇我们要学习的选择结构就属于 **选择控制语句** 。 **选择控制语句** 也被称为分支结构语句,选择结构有特定的语法规则,代码要执行具体的逻辑运算进行判断,逻辑运算的结果有两个或多个(真或假),所以产生了选择,根据不同的选择就会执行不同的代码。[1]
-
-
-
-
-
-```mermaid
-flowchart LR
- A([开始]) --> B{条件表达式}
- B -->|是| C[语句]
- C --> D
- B -->|否| D([结束])
-```
-
-## 循环控制语句
-
-循环控制语句可以在满足循环条件的情况下,反复执行某一段代码,这段被重复执行的代码被称为循环体语句(循环操作)。当反复执行这个循环体时,需要在合适的时候把循环判断条件修改为 false ,从而结束循环,否则循环将一直执行下去,形成死循环。[1]
-
-
-
-
-
-```mermaid
-flowchart LR
- A([开始]) --> B{循环条件}
- B -->|是| C[循环操作]
- C --> B
- B -->|否| D([结束])
-```
-
-## 流程图
-
-### 概述
-
-你看上方的控制语句,笔者每个都配有一个简单图示,这种图示叫流程图。以后的程序逻辑越来越复杂,我们就可以通过流程图,用图示的方式,来反映出特定主体为了满足特定需求而进行的,有特定逻辑关系的一系列操作过程(程序步骤)。
-
-::: tip 什么是流程图?
-流程图是对过程、算法、流程的一种图像表示,在技术设计、交流及商业简报等领域有广泛的应用。通常用一些图框来表示各种类型的操作,在框内写出各个步骤,然后用带箭头的线把它们连接起来,以表示执行的先后顺序。用图形表示算法,直观形象,易于理解。有时候也被称之为输入-输出图。顾名思义,就是用来直观地描述一个工作过程的具体步骤。这种过程既可以是生产线上的工艺流程,也可以是完成一项任务所必需的管理过程。
-
-一张简明的流程图,不仅能促进产品经理与设计师、开发者的交流,还能帮助我们查漏补缺,避免功能流程、逻辑上出现遗漏,确保流程的完整性。流程图能让思路更清晰、逻辑更清楚,有助于程序的逻辑实现和有效解决实际问题。
-
-通常,对于任何希望创建流程的人来说,无论创建的是什么用的流程,流程图都是很有用的。它可以帮你:
-
-1. 设计你产品的交互流程
-2. 确保的你的产品在任何时候都是友好的(甚至包括你原来根本未曾考虑过的故障发生时)
-3. 帮助你整合零散的线框图
-4. 帮助你与不同背景的同事进行沟通:比如引导工程师开发[2]
-:::
-
-
-### 图示
-
-为便于识别,绘制流程图的习惯做法是:
-
-- 圆角矩形:表示开始与结束
-- 矩形:表示操作步骤、用于普通工作环节
-- 菱形:表示问题判断(审核/审批/评审)环节
-- 平行四边形:表示输入和输出
-- 箭头:代表工作流方向
-
-
-
-
-
-流程图中有这么多符号,想要更好更快的绘制,我们可以使用一些流程图绘制工具!例如微软的 `visio` 或是直接使用在线流程图绘制网站:[Process On](https://www.processon.com/)(使用挺顺手,就是容量小,免费用户只能创建9张图,想新建更多,要么充钱要么导出图后删除一些无用的,记得删除后再进入回收站内删除,否则也会占用容量)、还有 [亿图](https://www.edrawsoft.cn/) 也可以使用。
-
-
-
-### 示例
-
-例如:公司报销的流程,特定主体是员工,特定需求是报销,特定逻辑关系是员工报销过程中的一系列操作。下方是某办公自动化系统的报销业务流程设计图。
-
-
-
-### 注意事项
-
-1. 绘制流程图时,为了提高流程图的逻辑性,应遵循从左到右、从上到下的顺序排列,而且可以在每个元素上用阿拉伯数字进行标注。
-2. 从开始符开始,以结束符结束。开始符号只能出现一次,而结束符号可出现多次。若流程足够清晰,可省略开始、结束符号。
-3. 当各项步骤有选择或决策结果时,需要认真检查,避免出现漏洞,导致流程无法形成闭环。
-4. 处理符号应为单一入口、单一出口。
-5. 连接线不要交叉。
-6. 如果两个同一路径的下的指示箭头应只有一个。
-7. 相同流程图符号大小需要保持一致。
-8. 处理为并行关系,可以放在同一高度。
-9. 必要时应采用标注,以此来清晰地说明流程。
-10. 流程图中,如果有参考其他已经定义的流程,不需重复绘制,直接用已定义流程符号即可。
-
-## 参考文献
-
-[1]文泷Vincent. 流程控制语句—顺序、选择、循环[EB/OL]. https://blog.csdn.net/qq_34236718/article/details/80596376. 2018-06-06
-
-[2]edraw 亿图. 什么是流程图?看完你就明白了![EB/OL]. https://www.edrawsoft.cn/what-is-flowchart/. 2021-01-08
-
-## 后记
-
-我们即将进入选择结构的学习,意味着我们要开始编写稍微灵活的程序,这时候大家要注意自己的逻辑思维训练。如果无法在脑海中构建一个比较好的空间思维图,那就老实的画画流程图,然后再写程序,绝对是有用的!!!
-
-::: info 笔者说
-对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
-
-所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
-:::
diff --git a/docs/courses/java/01-Java语法入门/08-if选择结构.md b/docs/courses/java/01-Java语法入门/08-if选择结构.md
deleted file mode 100644
index e9a545269..000000000
--- a/docs/courses/java/01-Java语法入门/08-if选择结构.md
+++ /dev/null
@@ -1,428 +0,0 @@
----
-title: if选择结构
-author: 查尔斯
-date: 2020/10/07 13:20
-categories:
- - Java基础快速入门
-tags:
- - Java
- - Java基础
----
-
-# if选择结构
-
-## 前言
-
-**C:** 上一篇我们介绍了流程控制语句的概念,本篇我们要正式开始学习其中的选择控制语句。那么在 Java 中,如果要实现选择控制语句有哪些语法呢?
-
-本篇我们就来看看其中,在以后用的最多的一种选择结构语法:if 系列选择结构。
-
-
-
-
-
-## 基础if选择结构
-
-**基础 if 选择结构** 是 if 系列选择结构中,最基础的结构语法!它是最简单的选择结构,也只能用于一种流程分支的处理。
-
-```java
-// 语法
-// 条件往往是由关系运算符来组成的判断
-// 条件的结果是一个boolean值,要么为true(真),要么为false(假)
-if (条件) {
- // 条件为真时执行的语句
-}
-```
-
-
-
-```mermaid
-flowchart LR
- A([开始]) --> B{条件表达式}
- B -->|是| C[语句]
- C --> D
- B -->|否| D([结束])
-```
-
-**案例需求:如果佩奇 Java 成绩大于 90 分,笔者将会奖励她一部 OnePlus 8 Pro。**
-
-```java
-// 声明一个变量用来存储佩奇的Java成绩
-int score = 91;
-
-// 套用基础if语法
-if (score > 90) {
- System.out.println("笔者奖励了一部OnePlus 8 Pro。");
-}
-
-System.out.println("程序结束!");
-```
-
-::: tip 笔者说
-套用下语法,用用关系运算符,就实现了一个很简单的选择案例,是不是很 easy。
-:::
-
-### 复杂条件的基础if
-
-接下来再看看下方案例中的条件,它就没有那么容易了。
-
-**案例需求:如果乔治 Java 成绩大于 90 分,并且音乐成绩大于 80 分时,或者 Java 成绩等于 100 分,音乐成绩大于 70 分时,笔者会奖励他一部 OnePlus 8 Pro。**
-
-面对这种场景,单靠普通的关系运算符显然无法实现,结合逻辑运算符就可以很好解决。
-
-```java
-// 声明变量分别存储乔治的Java成绩和音乐成绩
-int jScore = 60;
-int mScore = 81;
-
-// 关系运算符结合逻辑运算符
-// 考虑到运算符的优先级,可以使用()来提升部分表达式的优先级,也是复杂条件下增加可读性的方法
-// ()的添加应该成为一种本能,它是义务教育阶段学数学后的条件反射
-if ((jScore > 90 && mScore > 80) || (jScore == 100 && mScore > 70)) {
- System.out.println("笔者奖励了一部OnePlus 8 Pro。");
-}
-
-System.out.println("程序结束!");
-```
-
-## if-else选择结构
-
-我们敲完上方的代码发现,如果条件不满足的话,好像什么处理情况也没有,这怎么可能,只有老师奖励学生,没有学生受罚?我们再来看个案例。
-
-**案例需求:如果苏西 Java 考试成绩大于 90 分,笔者就奖励她一部 OnePlus 8 Pro,否则就罚她给笔者买一部 OnePlus 8 Pro。**
-
-面对这个案例,我们还是可以采用基础If语法。
-
-```java
-// 条件成立情况
-if (score > 90) {
- System.out.println("笔者奖励了一部OnePlus 8 Pro。");
-}
-
-// 条件不成立情况
-// if (score <= 90) {
- // System.out.println("苏西哭着给笔者买了一部OnePlus 8 Pro,笔者很感动。");
-// }
-
-// 还可以这么写
-// !表示取反,也就是某种情况的相反情况
-if (!(score > 90)) {
- System.out.println("苏西哭着给笔者买了一部OnePlus 8 Pro,笔者很感动。");
-}
-```
-
-但是明知道两个结果是互斥的,还要再写一次判断,并且条件越复杂,写起来就越麻烦。
-
-例如:如果要基于刚才复杂条件的if案例来写的话。
-
-```java
-// 声明变量分别存储乔治的Java成绩和音乐成绩
-int jScore = 60;
-int mScore = 81;
-
-// 成立
-if ((jScore > 90 && mScore > 80)
- || (jScore == 100 && mScore > 70)) {
- System.out.println("笔者奖励了一部OnePlus 8 Pro。");
-}
-
-// 不成立的情况
-if (!((jScore > 90 && mScore > 80)
- || (jScore == 100 && mScore > 70))) {
- System.out.println("奖励乔治给笔者买了一部OnePlus 8 Pro。");
-}
-
-System.out.println("程序结束!");
-```
-
-**头疼** ,这时候我们可以使用 `if-else` 语法来轻松解决这种条件互斥的情况。`if-else` 选择结构可以处理简单的互斥分支情况。
-
-```java
-// 语法
-if (条件) {
- // 语句1
-} else {
- // 语句2
-}
-```
-
-
-
-
-
-```mermaid
-flowchart LR
- A([开始]) --> B{条件表达式}
- B -->|是| C[语句1]
- C --> E
- B -->|否| D[语句2]
- D --> E([结束])
-```
-
-使用 `if-else` 来解决上述案例需求。
-
-```java
-// 声明一个变量存储苏西的Java考试成绩
-int score = 90;
-// 套用if-else语法
-// if:如果 else:否则
-if (score > 90) {
- System.out.println("笔者奖励了一部iPhone 11 Pro。");
-} else {
- System.out.println("苏西哭着给笔者买了一部OnePlus 8 Pro,笔者很感动。");
-}
-```
-
-::: tip 笔者说
-实际上我们学的这些语法,在你念着案例时就能把代码写出来,如果(if)苏西的 Java 考试成绩(score)大于90分(> 90),笔者就奖励她一部 OnePlus 8 Pro(System.out.println("xxx");),否则(else)就罚她给笔者买一部 OnePlus 8 Pro(System.out.println("xxx");)。
-:::
-
-## 多重if选择结构
-
-我们掌握了 `基础if` 来处理单分支选择,也掌握了 `if-else` 来处理互斥分支,但是如果出现了更多种的分支情况怎么办?
-
-**案例需求:如果佩奇 Java 考试成绩大于等于 80 分,笔者就奖励她一部 OnePlus 8 Pro,如果佩奇 Java 考试成绩大于等于 60 分,笔者就奖励她一部一加云耳,否则就罚她给笔者买一部 iPhone 11 Pro。**
-
-面对此需求,`if-else` 只能处理互斥分支所以不能采用,那使用 `基础if` 的方案如下。
-
-```java
-// 声明一个变量存储佩奇的Java考试成绩
-int score = 90;
-
-if (score >= 80) {
- System.out.println("笔者奖励她一部OnePlus 8 Pro。");
-}
-
-// 如果不添加&&来进行限制,那么佩奇考试成绩>80分时,笔者将会破产
-if (score >= 60 && score < 80) {
- System.out.println("笔者奖励她一部一加云耳。");
-}
-
-if (score < 60) {
- System.out.println("佩奇哭着给笔者买了一部iPhone 11 Pro,笔者很感动。");
-}
-```
-
-毫无疑问,`基础if` 依然需要添加多次条件,但对于范围型判断来讲,如果不把范围控制好,那么后续的 if 条件就会出现多余判断的情况,因为每个 `基础if` 都是独立的,Java 为此提供了一种简化的方案:`多重if选择结构`。
-
-**多重if选择结构:** 可以处理分段的条件分支情况,**它是自上而下进行选择判断,只要有一个条件满足,剩下的条件都不再执行判断** 。所以不要随意排列条件,把容易满足的条件放在下方,不然容易满足的条件判断为 `true` 后,后续其他条件都不会再判断了。
-
-```java
-// 语法
-if (条件1) {
- // 语句1
-} else if(条件2) { // 可以有多个else if
- // 语句2
-} else { // 可以省略
- // 语句3
-}
-```
-
-
-
-```mermaid
-flowchart LR
- A([开始]) --> B{成绩 >= 80}
- B -->|是| C[语句1]
- C --> G
- B -->|否| D{成绩 >= 60}
- D -->|是| E[语句2]
- E --> G
- D -->|否| F[语句3]
- F --> G([结束])
-```
-
-```java
-// 存储考试成绩
-int score = 90;
-
-// 套用if - else if - else语句
-/*
- * 注意事项:
- * 1.else if 必须配合if使用,if只能写一个,else if可以写很多个
- * 2.if-else if结构是自上而下进行判断选择的,只要上方的一个条件成立,下方的其他条件不再执行
- * 3.可以结合else来使用,当上方所有条件都不成立时,就会执行else语句内容
- */
-if (score >= 80) {
- System.out.println("笔者奖励她一部OnePlus 8 Pro。");
-} else if (score >= 60) { // 不需要限制 < 80的条件了,因为只要符合>=80,就不再向下判断
- System.out.println("笔者奖励她一部一加云耳。");
-} else {
- System.out.println("佩奇哭着给笔者买了一部iPhone 11 Pro,笔者很感动。");
-}
-```
-
-## 嵌套if选择结构
-
-生活在北上广深的各位同学们,对早晚高峰的地铁站印象很深了吧?在乘坐地铁前,我们需要有两次检查,第一次是安全检查,第二次是车票检查。火车站也是类似的。
-
-面对这种多次检查,它的逻辑不是 `多重If` 这种自上而下依次执行的判断,而是当通过第一层判断后才可以进入第二层判断,面对这种场景,我们可以把 if 语句再灵活使用一点。
-
-
-
-**嵌套if** 的应用场景:**在之前条件满足的前提下,再增加额外的判断** 。它可以通过外层语句和内层语句的协作,增强程序的灵活性。在笔者看来,当业务场景描述清楚的时候,福至心灵下就会自然而然的写出来对应的代码了。
-
-```java
-if (条件1) {
- if (条件2)
- // 语句1
- } else {
- // 语句2
- }
-} else {
- // 语句3
-}
-```
-
-
-
-```mermaid
-flowchart LR
- A([开始]) --> B{条件1}
- B -->|是| C{条件2}
- C -->|是| D[语句1]
- D --> F
- C -->|否| E[语句2]
- E -->|否| F
- B -->|否| F([结束])
-```
-
-**案例需求:学校举行运动会,百米赛跑跑入10秒内的学生有资格进决赛,然后根据性别进入男子组或女子组**
-
-```java
-// 声明一个变量存储百米成绩
-int score = 9;
-// 声明一个变量存储性别
-String gender = "女";
-
-// 套用嵌套if语句
-// 注意事项:如果没有进入决赛,也就不会分配组别了
-if (score < 10) { // 可以进入决赛
- System.out.println("进入了决赛!");
- if ("男".equals(gender)) { // 分配组别
- System.out.println("进入了男子组!");
- } else {
- System.out.println("进入了女子组!");
- }
-}
-```
-
-### 字符串的比较
-
-大多数情况下,我们比较内容时都会采用 `==` 这个关系运算符,但是如果遇到特殊类型,就不可以了。
-
-**字符串的比较不要使用==,而是采用equals()方法。**
-
-::: tip 笔者说
-介绍数据类型时,笔者就说过 String 是个引用数据类型,有点特别。先了解,知道使用equals 方法来比较字符串内容即可。
-:::
-
-```java
-// == 号可以比较基本数据类型的值是否相等(八大基本数据类型)
-int num1 = 10;
-int num2 = 10;
-// System.out.println(num1 == num2); // true
-
-char gender1 = '男';
-char gender2 = '男';
-// System.out.println(gender1 == gender2); // true
-
-// ------------------
-
-// 如果是字符串,不要使用==号比较,而是使用equals()方法
-// 因为字符串是引用数据类型,==号如果比较引用数据类型,实际上比较的是数据的地址值而不是比较内容
-// 所以==很有可能导致你的字符串内容比较出问题,因为它比较的是地址值
-// 即使你发现两个字符串用==也比较为true了,那其实是涉及到常量池的概念了。
-// 先记住不要用==比较字符串就可以了!!!后期面向对象阶段学完就知道为啥了。
-String str1 = "我是一个字符串";
-String str2 = "我是一个字符串";
-System.out.println(str1.equals(str2));
-```
-
-## 答题环节
-
-光看不练假把式,来几个练习,练起来。
-
-### 模拟登录
-
-::: details 需求:使用 if 编写程序:从键盘获取用户名和密码,如果用户名和密码都正确,那么就显示"欢迎进入xxx的世界!",否则就提示"用户名或密码错误!"。
-
-提示:可以预先设定一个假的用户名和密码。
-:::
-
-
-
-### 人机猜拳
-
-从控制台输入要出的拳,1代表石头、2代表剪刀、3代表布,电脑 **随机** 出拳,比较胜负,完成此代码功能。
-
-
-
-#### 随机数的生成
-
-随机数是我们在程序中经常要使用到的,前期我们可以用它模拟很多业务,下面介绍一个 Java 中生成随机数的玩法。
-
-```java
-// Math.random()可以随机生成[0.0,1.0)之间的小数
-// [0.0,1.0)表示一个区间,等价于0.0 <= num1 < 1.0
-// 左闭右开就是包含左边数据,不包含右边的数据
-// [0.0,1.0]也表示一个区间,等价于0.0 <= num1 <= 1.0
-// 左闭右闭就是包含左边数据,也包含右边的数据
-// 想了解更多区间的内容,自己去百度下相关数学知识
-double num1 = Math.random();
-
-/*
- * 基于上方的方法,如果想指定生成某个范围内的随机数,可以推导出一个公式。
- * 随机数公式为:(int)(Math.random() * (max - min)) + min;
- */
-// 案例1:生成[0,10)之间的随机整数
-// [0.0,1.0) * 10 --> [0,10)
-int num2 = (int)(Math.random() * 10);
-
-// 案例2:生成[5,14)之间的随机整数
-int num3 = (int)(Math.random() * 9) + 5;
-
-// 案例3:如果想生成[6,20]之间的随机整数,那可以将其理解为[6,21),这样就可以再套上方的公式了
-int num4 = (int)(Math.random() * 15) + 6;
-```
-
-### 幸运抽奖
-
-::: details 需求:实现幸运抽奖功能:输入4位的会员号,如果会员号的百位数字等于产生的随机数字,那么这个会员就是幸运会员,将奖励一部 iPhone 11 Pro!进行合理结果提示。
-
-提示:获取4位数,各个位的数字举例:
-:::
-
-
-```java
-int num = 1234;
-// 千位
-int qian = num / 1000;
-
-// 百位
-int bai = num / 100 % 10;
-
-// 十位
-int shi = num / 10 % 10;
-
-// 个位
-int ge = num % 10 % 10;
-```
-
-## 后记
-
-选择结构的使用,一般不会困扰到大家。因为它与我们日常生活是非常贴近的,可读性非常强。当然如果还是不能理解,别忘了流程图哈!
-
-::: info 笔者说
-对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
-
-所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
-:::
diff --git a/docs/courses/java/01-Java语法入门/09-switch选择结构.md b/docs/courses/java/01-Java语法入门/09-switch选择结构.md
deleted file mode 100644
index 241ab98c6..000000000
--- a/docs/courses/java/01-Java语法入门/09-switch选择结构.md
+++ /dev/null
@@ -1,150 +0,0 @@
----
-title: switch选择结构
-author: 查尔斯
-date: 2020/10/07 15:30
-categories:
- - Java基础快速入门
-tags:
- - Java
- - Java基础
----
-
-# switch选择结构
-
-## 前言
-
-**C:** 上一篇我们介绍了 if 系列的选择结构语法,整体感受应该是简单易懂且易写的。本篇笔者再介绍一种选择结构语法:switch,但此 switch 非彼 switch。
-
-
-
-
-
-## Why?
-
-有了 if ,为什么还要学习使用 switch 选择结构呢?一起来看个案例。
-
-**案例需求:小杨参加了创造502节目。**
-
-- 如果获得第一名,将会担任《诛仙》女主角
-- 如果获得第二名,将会担任《斗罗大陆》女主角
-- 如果获得第三名,将会担任《永夜》女主角
-
-从需求介绍中可知,案例的条件表达式是简单的等值判断,但是条件很多而且彼此都是互斥的,可以采用多重 if 来实现。
-
-```java
-// 存储名次
-int score = 2;
-
-// 使用多重if
-if (score == 1) {
- System.out.println("小杨将会担任《诛仙》女主角!");
-} else if (score == 2) {
- System.out.println("小杨将会担任《斗罗大陆》女主角!");
-} else if (score == 3) {
- System.out.println("小杨将会担任《永夜》女主角!");
-}
-```
-
-但相比于范围判断,在等值判断时使用 `多重if` 有点大材小用,实话就是 if 多了阅读起来真挺费劲的,所以笔者才给大家安利 Java 提供的另一个选择结构语句:`switch选择结构`。
-
-我们来看看它的语法:
-
-```java
-switch (表达式) {
- case 常量1:
- 语句;
- break;
- case 常量2:
- 语句;
- break;
- // ....
- default:
- 语句;
- break;
-}
-```
-
-再套用它的语法来解决一下刚才的问题,是不是发现清晰多了?
-
-```java
-// 存储名次
-int score = 2;
-
-// 使用switch
-switch (score) {
- case 1:
- System.out.println("小杨将会担任《诛仙》女主角!");
- break;
- case 2:
- System.out.println("小杨将会担任《斗罗大陆》女主角!");
- break;
- case 3:
- System.out.println("小杨将会担任《永夜》女主角!");
- break;
-}
-```
-
-## 使用注意
-
-1. 在满足等值判断的前提下,才可以使用 `switch` 来进行判断,不可用于范围型判断。
-
-2. 如果没有特殊要求,必须给每一个 `case` 后追加 `break`。
-
- `break` 是表示结束某个 `case`,如果没有 `break`,会出现 `case` 的穿透性,即继续往下执行直到遇到下一个 `break` 结束!
-
- ```java
- // 存储名次
- int score = 1;
- // 使用switch
- switch(score){
- case 1:
- System.out.println("小杨将会担任《诛仙》女主角!");
- case 2:
- System.out.println("小杨将会担任《斗罗大陆》女主角!");
- break;
- case 3:
- System.out.println("小杨将会担任《永夜》女主角!");
- break;
- }
- ```
-
- 上方的代码,如果 `case 1` 后缺少一个 `break`,那么输出结果是将会是。
-
- ```
- 小杨将会担任《诛仙》女主角!
- 小杨将会担任《斗罗大陆》女主角!
- ```
-
-3. 建议加上一个 `default` 来进行默认处理。
-
-4. `switch` 的表达式支持的类型有:`int`、( `short`、 `byte`、`char` 可以自动类型转换为 `int`),`枚举类型(Enum)` 、`String`(自JDK1.7开始,`switch` 支持了字符串的等值判断,参考[Oracle Java7 RELEASE介绍](https://docs.oracle.com/javase/7/docs/technotes/guides/language/strings-switch.html))。
-
- 
-
- 
-
-## switch和if的对比
-
-到此为止,Java 中的选择结构我们就学习完了,别看语法挺多,论派系的话只有两个,一个是 `if`,一个是`switch`,而且 `switch` 和 `多重if` 也很相像,理解起来也比较容易了。
-
-**相同点:** 都是用来处理多分支条件的结构。
-
-**不同点:** `switch` 只能处理等值条件判断的情况,`多重if` 选择结构没有 `switch` 选择结构的限制,特别适合某个变量处于某个连续区间时的情况(范围型判断)。
-
-`switch` 从效率方面考虑,是要比 `if` 选择结构执行快(有兴趣自己百度下执行原理),但是随着硬件的发展,这两者之间的效率差距几乎可以忽略不计。
-
-::: tip 笔者说
-其实 Java 近几版本一直在对 `switch` 进行优化,switch 的使用也更加现代,后面有机会使用其他版本,再给大家开开眼。
-:::
-
-## 后记
-
-选择结构出现后,我们就可以把现实生活的业务逻辑,在程序中模拟实现了。这些流程控制语句就像汉语拼音和基本汉字一样基础,好好记忆下语法。
-
-把文章案例实现一下,千万要动手实现!因为理解和熟练掌握是两回事!就好像你在抖音上看了那么多生活上的教程:叠衣服、弹吉他.....,但你从没练过,那永远是学不会的,千万不要眼高手低。
-
-::: info 笔者说
-对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
-
-所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
-:::
diff --git a/docs/courses/java/01-Java语法入门/10-循环结构.md b/docs/courses/java/01-Java语法入门/10-循环结构.md
deleted file mode 100644
index ce7c68139..000000000
--- a/docs/courses/java/01-Java语法入门/10-循环结构.md
+++ /dev/null
@@ -1,650 +0,0 @@
----
-title: 循环结构
-author: 查尔斯
-date: 2020/10/08 8:24
-categories:
- - Java基础快速入门
-tags:
- - Java
- - Java基础
----
-
-# 循环结构
-
-## 前言
-
-**C:** 在 [《控制语句和流程图》](./07-控制语句和流程图)篇中,笔者介绍了三种控制语句,每种控制语句都有它能解决的问题范围。例如:顺序控制语句可以解决“流水‘步骤,选择控制语句可以解决条件判断问题。
-
-本篇我们要学习最后一种控制语句:循环控制语句。它可以用来解决业务中的重复、有规律性的问题。
-
-
-
-
-
-## 什么是循环
-
-### 生活
-
-::: tip 循环 [circulate;circle]
-- 以环形、回路或轨道运行;
-- 沿曲折的路线运行;
-- 特指运行一周而回到原处,再转。
-- 或说反复地连续做某事。
-:::
-
-计算机程序是来源于生活程序的。生活中,我们上班、上学、坚持某个习惯等都算是一个循环过程。
-
-
-
-甚至,在近两年的日常发言方面还有一个梗:“人类的本质是复读机”。
-
-
-
-### 计算机
-
-生活中有循环需求,在计算机程序中,类似的需求也是数不胜数的。
-
-::: tip 笔者说
-在不少实际问题中有许多具有规律性的重复操作,因此在程序中就需要重复执行某些语句。循环结构是在一定条件下反复执行某段程序的流程结构,被反复执行的程序被称为循环体(循环操作)。[1]
-:::
-
-**案例需求:熊大 Java 考试成绩未达到自己的目标。为了表明自己勤奋学习的决心,他决定写一百遍“好好学习,天天向上!”**
-
-这么有上进心的学生,真是令老师喜欢!那如何用 Java 代码来实现输出100遍 ”好好学习,天天向上” 呢?
-
-
-
-好像没什么毛病,如果掌握了 Ctrl+C 和 Ctrl+V,就变得更加简单了。但是如果现在这位有上进心的学生,决定写10000遍呢?哪怕是用CV大法,这代码工作量也是吓坏个人 !
-
-好在,Java 语言为我们准备了三种循环结构语法,它们都可以轻松解决此需求,接下来一起去看看吧。
-
-## while循环
-
-while 循环是一种经典的循环结构语法,不仅仅是在 Java 语言中,其他的编程语言中也有类似的概念和语法。
-
-
-
-### 语法
-
-它的语法,宽松来讲包含两个组成,和 if 选择结构非常相似。
-
-- 循环条件
-- 循环体
-
-```java
-while (循环条件) {
- 循环体/循环操作
-}
-```
-
-从流程图中,我们可以看出它和选择结构的主要区别在于: **当条件成立且执行完操作后,它不会直接结束,而是会再次进入条件判断,直到条件不成立为止。**
-
-
-
-```mermaid
-flowchart LR
- A([开始]) --> B{循环条件}
- B -->|是| C[循环操作]
- C --> B
- B -->|否| D([结束])
-```
-
-### 使用
-
-接下来我们通过案例来掌握一下使用步骤。
-
-::: details 案例需求1:解决上方熊大的问题。
-**思路分析:**
-
-1. 此案例为重复性步骤需求,采用循环控制语句解决
-
-2. 要应用 while 循环,需要先确定此案例中,循环的两个固定组成部分
-
- - 循环条件:`<= 100`
-
- - 循环操作:输出 "好好学习,天天向上"
-
-3. 套用 while 循环的语法来编写代码
-
- 宽松的语法只考虑循环条件和循环操作即可。但为了让各位同学编写代码时可以形成一个完整步骤思路,笔者对语法步骤做了扩展,扩展为了4部分,这样你在用循环解决问题时,就直接可以去思考4部分的组成。
-
- - 初始化 循环变量:为循环条件表达式,准备一个起始变量
-
- - 循环条件
-
- - 循环操作
-
- - 循环出口:对循环条件表达式中的变量进行变化,让循环可以在指定时机结束
-
-4. 检查是否正常在业务完成后退出循环
-
-(上方这多出来的两个组成,其实是为了补齐循环条件的使用。)
-:::
-
-**代码实现:**
-
-```java
-// 1.初始化 循环变量
-int i = 1;
-
-// 2.循环条件
-while (i <= 100) {
- // 3.循环操作
- System.out.println("第" + i + "遍:好好学习,天天向上!");
- // 4.循环出口
- // 借助变量 i 来作循环条件,如果 i 的值不变动,会出现死循环
- i += 1;
-}
-```
-
-::: tip 笔者说
-你看应用循环结构语法之后,简单的几行代码就搞定了原来的问题。而且如果要更改输出的次数,只需要改一下循环条件部分即可。
-:::
-
-::: details 案例需求2:实现打印50份试卷。
-**思路分析:**
-
-1. 此案例为重复性步骤需求,采用循环控制语句解决
-2. 要应用 while 循环,需要先确定此案例中,循环的两个固定组成部分
- - 循环条件:`<= 50`
-
- - 循环操作:打印试卷
-3. 套用 while 循环的语法来编写代码
-4. 检查是否正常在业务完成后退出循环
-:::
-
-**代码实现:**
-
-```java
-// 1.初始化 循环变量
-int i = 1;
-
-// 2.循环条件
-while (i <= 50) {
- // 3.循环操作
- System.out.println("正在打印第" + i + "份试卷");
- // 4.循环出口
- i += 1;
-}
-```
-
-## do-while循环
-
-**案例需求:老师每天检查张浩的学习任务是否合格,如果不合格,则继续进行。老师给熊二安排的每天的学习任务为:上午阅读教材,学习理论部分,下午上机编程,掌握代码部分。**
-
-这个需求,大致一看可确定也是采用循环来解决,其中循环的两个要素:
-
-- 循环条件:检查不合格继续进行
-
-- 循环操作:学习任务(上午阅读教材,学习理论部分,下午上机编程,掌握代码部分)
-
-但是我们发现,该案例与刚才的两个案例,有显著不同的地方:
-
-- 逻辑顺序:它的逻辑是先进行学习任务(循环操作),然后再由老师检查(循环条件)。
-- 条件复杂:它的条件也不再是普通次数递增,而是判断是否合格。
-
-面对此需求,条件复杂倒是可以更改相应变量即可,但逻辑顺序方面却无法与 while 循环的流程相符( **while 循环是先执行判断,再进行循环操作** )。因此,Java 语言提供的第二种循环结构语法可以派上用场了: `do-while` 循环。
-
-### 语法
-
-```java
-do {
- 循环体/循环操作
-} while (循环条件);
-```
-
-从流程图中,我们可以看出:`do-while` 循环的执行讲究一个先 do(循环操作),然后再执行循环条件。而且 **无论条件是否能成立,它至少会执行一次循环操作** 。
-
-
-
-```mermaid
-flowchart LR
- A([开始]) --> B[循环操作]
- B --> C{循环条件}
- C -->|是| B
- C -->|否| D([结束])
-```
-
-### 使用
-
-我们来使用 `do-while` 解决下熊二的问题。
-
-```java
-Scanner input = new Scanner(System.in);
-
-// 1.初始化循环变量
-String result;
-// 套用do-while语句
-do {
- // 2.循环操作
- System.out.println("熊二上午阅读教材,学习理论部分。");
- System.out.println("熊二下午上机编程,掌握代码部分。");
- // 3.循环出口
- System.out.print("老师检查是否合格(y/n):");
- result = input.next();
-} while ("n".equals(result)); // 4.循环条件
-
-System.out.println("今日学习圆满!");
-```
-
-## for循环
-
-学完了 while 系列的循环语法,接下来笔者再带你认识最后一种循环语法:for循环。
-
-### Why?
-
-**for循环是我们后期高频次使用的一种循环语法!** 为什么高频使用,一定有缘由,看看下方 “输出100遍,好好学习” 实现对比!
-
-```java
-// =============while循环=============
-// 1.初始化 循环变量
-int i = 1;
-
-// 2.循环条件
-while (i <= 100) {
- // 3.循环操作
- System.out.println("第" + i + "遍:好好学习,天天向上!");
- // 4.循环出口
- // 借助变量 i 来作循环条件,如果 i 的值不变动,会出现死循环
- i += 1;
-}
-```
-
-```java
-// =============for循环=============
-for (int i = 1; i <= 100; i += 1) {
- System.out.println("第" + i + "遍:好好学习,天天向上!");
-}
-```
-
-显而易见,**在循环次数固定的情况下,for 循环比while 循环更简洁** 。
-
-### 语法
-
-for 循环的语法要更加简洁,更加灵活,但是上方总结的循环四要素是没有变化的。只不过它要求我们将循环中经常忘却的几个组成要素,先指定好,这样就可以更专注于循环操作了。所以你可以将 for 循环理解为是 while 循环的延伸。(就像多重 if 延伸为 switch 选择结构一样)
-
-::: tip 笔者说
-对于笔者个人来讲,如果 for 和 while 都能解决的问题,我更偏向于 for 的使用。因为在使用 while 循环时,自上而下的编写代码,经常忘却的就是循环出口。
-:::
-
-```java
-for (初始化 循环变量; 循环条件; 循环出口) {
- 循环体/循环操作
-}
-```
-
-### 使用
-
-::: details 案例需求1:循环输入某同学期末考试的5门课成绩,并计算平均分。
-**思路分析:**(平均分:总成绩 / 数量)
-
-1. 此案例为重复性步骤需求,采用循环控制语句解决
-2. 确定此案例中,循环的两个固定组成部分
-
- - 循环条件:`<= 5`
- - 循环操作:输入每门课成绩,并累加求和(+=)
-3. 由于是固定次数循环,套用 for 循环的语法来编写代码
-
- - 初始化 循环变量:为循环条件表达式,准备一个起始变量
- - 循环条件
- - 循环操作
-
- - 循环出口:对循环条件表达式中的变量进行变化,让循环可以在指定时机结束
-4. 检查是否正常在业务完成后退出循环
-:::
-
-
-
-**代码实现:**
-
-```java
-Scanner input = new Scanner(System.in);
-
-System.out.print("请输入学生姓名:");
-String name = input.next();
-
-// 定义变量存储成绩和
-double sum = 0;
-
-// 输入每门课成绩
-for (int i = 1; i <= 5; i++) {
- System.out.print("请输入第" + i + "门课的成绩:");
- double score = input.nextDouble();
- // 累加成绩和
- sum += score;
-}
-
-// 计算平均分,平均分 = 成绩和 / 课数量
-double avg = sum / 5;
-System.out.println(name + "的平均分为:" + avg);
-```
-
-::: details 案例需求2:输出如图所示加法表。
-**思路分析:**
-
-1. 此案例为重复性步骤需求,采用循环控制语句解决
-
-2. 确定此案例中,循环的两个固定组成部分
-
- - 循环条件:`<= 输入的值`
- - 循环操作:要输出加法运算
- 加法运算的两个操作数
- - 第一个操作数:从0递增到你输入的值
- - 第二个操作数:从你输入的值递减到0
-
-3. 由于是固定次数循环,套用 for 循环的语法来编写代码
-
- - 初始化 循环变量:为循环条件表达式,准备一个起始变量
- - 循环条件
- - 循环操作
- - 循环出口:对循环条件表达式中的变量进行变化,让循环可以在指定时机结束
-
-4. 检查是否正常在业务完成后退出循环
-:::
-
-
-
-**代码实现:**
-
-```java
-Scanner input = new Scanner(System.in);
-
-System.out.print("请输入一个值:");
-int num = input.nextInt();
-
-System.out.println("根据这个值可以输出以下加法表:");
-// 套用 for 语法
-// 注意:int i = 0, j = num; 这也是变量定义的方式,但不常用
-for (int i = 0, j = num; i <= num; i++, j--) {
- System.out.println(i + " + " + j + " = " + (i + j));
-}
-```
-或者是:
-```java
-Scanner input = new Scanner(System.in);
-
-System.out.print("请输入一个值:");
-int num = input.nextInt();
-
-System.out.println("根据这个值可以输出以下加法表:");
-// 套用 for 语法
-// 定义两个计算变量
-int i = 0;
-int j = num;
-for (; i <= num;) {
- System.out.println(i + " + " + j + " = " + (i + j));
- // 循环出口
- i++;
- j--;
-}
-```
-
-### 注意事项
-
-- for 循环的三大项都可以省略,但是两个 `;` 号不能省!省略了三大项之后,就成了死循环。
-- for 循环中的初始化变量和循环出口可以单独拿出来,比如为了提升变量作用域的情况。
-- for 循环只能用于循环次数固定的场景。
-
-## 三种循环的区别
-
-执行顺序区别:
-
-- while 循环:先判断再执行
-- do-while 循环:先执行再判断。初始情况不满足循环条件时,do-while 至少也会执行一次。
-- for 循环:先判断再执行
-
-适用情况区别:
-
-- 循环次数固定的情况,通常选用 for 循环
-- 循环次数不确定的情况,通常选用 while 或 do-while 循环
-
-## 死循环
-
-上方介绍循环时,笔者大量提到了一个概念:死循环。死循环,就是当你循环没有出口,导致循环无法结束的一种现象。
-
-下方是最简单的三种死循环代码,但只要循环条件或循环出口的错误或缺失,其实都会导致死循环出现。
-
-```java
-while (true) {
-
-}
-```
-
-```java
-do {
-
-} while (true);
-```
-
-```java
-for (;;) {
-
-}
-```
-
-按理说死循环是不好的情况,但正如:“功法没有正邪之分,主要看使用者。“ 合理使用死循环会让你的程序编写更加便捷。
-
-例如:当你 **不知道一个循环什么时候结束的时候**,使用死循环会更加合适。但一般它要配合跳转语句来使用。
-
-### 跳转语句
-
-#### break
-
-生活中参加长跑比赛,如果在比赛过程中身体不支/不适,是可以退出比赛的。
-
-而程序中,也有需要在指定的情况下就不要再执行循环的需求,这时候跳转语句们就可以灵活的调度这个循环流程。
-
-`break` 关键字,在 switch 语句中我们就见过,表示中断的意思。它还可以使用在三大循环结构中,此时它表示 **结束当前所在的循环流程,跳转到循环块外的下一条语句** 。
-
-```java
-// 伪代码:4000米长跑
-for (int i = 0; i < 10; i++) {
- if (体力不支) {
- // 退出比赛
- break;
- }
-}
-```
-
-**案例需求:循环录入某学生5门课的成绩并计算平均分,如果某分数录入为负,停止录入并提示录入错误。**
-
-
-
-该需求,就是刚才 for 循环中实现过的案例,只不过是增加了些许要求。
-
-```java
-Scanner input = new Scanner(System.in);
-
-System.out.print("请输入学生姓名:");
-String name = input.next();
-
-double sum = 0;
-
-// 提升作用域
-// 如果 i 定义在for循环块中,那它就只能在for循环块中使用。
-int i = 1;
-for (; i <= 5; i++) {
- System.out.print("请输入第" + i + "门课的成绩:");
- double score = input.nextDouble();
- // 如果输入了负数,提示错误并结束循环
- if (score < 0) {
- System.out.println("分数录入错误!请重新开始录入!");
- break;
- }
- sum += score;
-}
-
-// 提前结束循环则不需要进行平均分计算
-if (i > 5) {
- double avg = sum / 5;
- System.out.println(name + "的平均分为:" + avg);
-}
-```
-
-#### continue
-
-除了 `break` 之外,还有一个跳转语句:`continue`。顾名思义,它的作用是 **跳过本次循环体中余下尚未执行的语句,立即进行下一次的循环条件判定,可以理解为仅结束本次循环** 。
-
-**案例需求:使用循环 + continue实现,循环录入 Java 课的学生成绩,统计分数大于等于80分的学生比例。**
-
-
-
-```java
-Scanner input = new Scanner(System.in);
-
-System.out.print("请输入班级总人数:");
-int num = input.nextInt();
-
-// 设 >= 80分的学生人数
-int count = 0;
-for (int i = 1; i <= num; i++) {
- System.out.print("请输入第" + i + "位学生的成绩:");
- double score = input.nextDouble();
- // 统计成绩 >= 80分以上的学生人数
- if (score < 80) {
- // 结束本次循环,进入下一次循环
- continue;
- }
- // 累加人数
- count ++;
-}
-
-// 计算
-double percent = count * 1.0 / num * 100;
-System.out.println("80分以上的学生人数为:" + count);
-System.out.println("80分以上的学生所占的比例为:" + percent + "%");
-```
-
-::: tip 笔者说
-这个案例,continue不是必须的,我们也可以把统计人数部分代码更换为:
-
-```java
-if (score > 80) {
- count ++;
-}
-```
-:::
-
-### 死循环 + 跳转语句
-
-好了,再介绍完跳转语句,笔者给大家介绍一种死循环结合跳转语句,实现重复输入的应用。
-
-- 死循环的使用,可以令程序不结束永远执行下去
-- break的使用,可以结束该循环
-- continue的使用,可以结束本次循环,进入下次循环
-
-
-
-```java
-Scanner input = new Scanner(System.in);
-
-while (true) {
- System.out.print("请输入用户名:");
- String username = input.next();
- System.out.print("请输入用户密码:");
- String password = input.next();
- // 失败重输
- if (!("admin".equals(username)
- && "abcd123".equals(password))) {
- System.out.println("登录失败!请重新输入!");
- // 结束本次循环,进入下次循环
- continue;
- }
- // 结束死循环
- break;
-}
-
-System.out.println("登录成功!");
-```
-
-::: tip 笔者说
-是不是感觉稍微有些烧脑了?多写几次就好了!它还可以写成下方这种。
-
-但笔者 **个人** 还是比较喜欢死循环的写法,只要"时机"到了,随时结束或进入下一次,很直接,很纯粹,很果断。
-
-
-:::
-
-```java
-Scanner input = new Scanner(System.in);
-
-boolean flag;
-do {
- // 初始化 循环变量
- // 初始为true,只有当登录成功才将flag状态改为false以结束循环
- flag = true;
-
- System.out.print("请输入用户名:");
- String username = input.next();
- System.out.print("请输入用户密码:");
- String password = input.next();
- // 失败重输
- if ("admin".equals(username)
- && "abcd123".equals(password)) {
- System.out.println("登录成功!");
- // 更改循环状态
- flag = false;
- } else {
- System.out.println("登录失败!请重新输入!");
- }
-} while (flag);
-```
-
-## 答题环节
-
-### 计算和
-
-::: details 需求:使用 while、do-while 以及 for 循环三种编程方式分别实现:计算100以内(包括100)的偶数之和。
-
-提示:偶数,能够被 2 整除的数字被称为偶数。例如:4 % 2 == 0,4为偶数。
-:::
-
-### 打印数列
-
-**需求:** 使用循环输出 100、95、90、85、.......5。
-
-### 输出所有的水仙花数
-
-::: details 需求:使用单层循环实现所有水仙花数的输出。
-**提示:什么是水仙花数?**
-
-1. 水仙花数都是三位数
-
-2. 水仙花数的个位的立方 + 十位立方 + 百位立方 = 水仙花数字本身
-
-:::
-
-### 输出星期数
-
-**需求:** 从键盘输入一位整数,当输入1 ~ 7 时,输出"星期一" ~ "星期日"。
-
-- 输入其他数字时,提示用户重新输入。
-- 输入数字0,程序结束
-
-
-
-## 参考文献
-
-[1]张桂珠、张平、陈爱国.Java面向对象程序设计(jdk1.6)第三版:北京邮电大学出版社,2005
-
-## 后记
-
-我想,学完这一篇的同学们一定会开始有些许头疼。因为循环它开始变得有所抽象,简单的几行代码却要表述出成百上千次操作,循环操作越是复杂,那需要的空间思维越是庞大,不过别担心,下一篇,笔者解救你。
-
-另外,笔者在本篇给相同的需求,反复给出不同的解决方法,这对于初次学习,以及"刻板化"学习的同学会有些难受,因为不知道选择哪个了。
-
-::: tip 笔者说
-实际上,生活从来不是一个"刻板化"的步骤,生活中遇到的问题也不总是“刻板化”的来解决,程序亦是如此,千人千面,千题千解。
-
-笔者给你一个个人建议,根据自身情况,先选择一种最喜欢的解决方法,然后当这种解决方法怎么都无法解决问题的时候,考虑下另一种或更多种。
-:::
-
-::: info 笔者说
-对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
-
-所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
-:::
diff --git a/docs/courses/java/01-Java语法入门/11-多重循环.md b/docs/courses/java/01-Java语法入门/11-多重循环.md
deleted file mode 100644
index f4ce28f44..000000000
--- a/docs/courses/java/01-Java语法入门/11-多重循环.md
+++ /dev/null
@@ -1,366 +0,0 @@
----
-title: 多重循环
-author: 查尔斯
-date: 2020/10/08 13:22
-categories:
- - Java基础快速入门
-tags:
- - Java
- - Java基础
----
-
-# 多重循环
-
-## 前言
-
-**C:** 对于很多同学来讲,上一篇,可以说是对你当前逻辑思维能力的检验篇。
-
-逻辑思维强的同学,套着思路分析步骤和语法,很容易就写完了练习。而逻辑思维不强的同学,则因为缺少相应的练习,思路不畅导致代码写的自己都看不懂,更别提遵守什么代码规范了。但是如果认真敲完笔者的案例,应该是可以很容易看得懂一些参考答案的,只不过就是自己想不出来,甚至因而变得焦躁、钻牛角尖(为什么自己想不到,为什么别人可以想到?)。
-
-
-
-那逻辑思维除了先天之外,自然也是可以后天培养的。笔者的培养方法是个笨方法:那就是多做练习。既然想不到解决方法,那就塌下心来学习别人怎么做,甚至是背代码,最后做多了,量变引发质变,就可以提升出自己的东西。
-
-这种方法当然不能称之为好,但是目前它适合普罗大众,你要做的就是塌下心来,承认自己的不足,学习解决思路。
-
-::: tip 笔者说
-这类话,笔者后面文章也会反复提起,一次两次你是听不进去的。
-:::
-
-本篇我们继续学习循环结构,不过是对循环的更高级玩法,所以难度更大。不过别担心,笔者会通过思路分析一点点的带你进行案例实现。
-
-
-
-## Why?
-
-在学完了基本的循环结构后,我们也掌握了对简单的有规律性、重复性步骤的优化能力。别飘,新的需求又来了,看看下方的图形,如何用程序在控制台打印出来呢?
-
-
-
-不同的人看待问题的角度不同,有些同学一看到它就会想:这个需求一点不难,实现很简单,看看下方已经被押送 "海王星" 的同学写的实现。
-
-```java
-// 第一个图形
-for(int i = 0; i < 5; i++){
- System.out.println("*****");
-}
-
-// 第二个图形
-System.out.println(" *****");
-System.out.println(" *****");
-System.out.println(" *****");
-System.out.println(" *****");
-System.out.println("*****");
-
-// 第三个图形
-System.out.println(" * ");
-System.out.println(" *** ");
-System.out.println(" ***** ");
-System.out.println(" ******* ");
-System.out.println("*********");
-```
-
-
-
-谁看谁不落泪?一点毛病都没有!所以也怪笔者的需求描述不清楚,那再对需求补充一个要求:使用循环,以一颗 `*` 一颗 `*` 的形式来实现打印这些图形。
-
-头疼了吧?因为现有的技术你基本无法实现。 **单层循环无法解决稍微复杂些的需求。**
-
-## 概述
-
-多重循环(嵌套循环),其实就是一个循环体内又包含另一个完整的循环结构。
-
-和嵌套 if 是一回事。各循环之间可互相嵌套,但一般不超过三层,否则导致逻辑混乱,因小失大。又因为一般都是二层嵌套,故往往称为二重循环/双重循环。
-
-**常见的多重循环伪代码如下:**
-
-```java
-while (外层循环条件) {
- ...外层循环操作...
- while (内层循环条件) {
- ...内层循环操作...
- }
-}
-```
-
-```java
-do {
- ...外层循环操作...
- do {
- ...内层循环操作...
- } while (内层循环条件);
-} while (外层循环条件);
-```
-
-```java
-for (;外层循环条件;) {
- ...外层循环操作...
- for (;内层循环条件;) {
- ...内层循环操作...
- }
-}
-```
-
-```java
-while (外层循环条件) {
- ...外层循环操作...
- for (;内层循环条件;) {
- ...内层循环操作...
- }
- for (;内层循环条件;) {
- ...内层循环操作...
- }
-}
-```
-
-::: tip 笔者说
-多重循环要比嵌套 if 难。因为 **内层循环 就是 外层循环的 循环操作的 组成部分** ,所以程序执行特点是: **外层循环执行一次,内层循环执行一遍。**
-:::
-
-## 使用
-
-接下来笔者带大家打打样,看看多重循环一般怎么用,也找找它们的使用规律。
-
-### 打印矩形
-
-之前我们的单层循环,是解决一行行具有规律性的需求,每一行输出的格式比较稳定,例如下方输出:
-
-
-
-这回这些复杂的图形,不仅要求我们考虑行的规律,还需要考虑每行内的规律(列)。
-
-有规律的内容就要靠循环实现,两个有规律就要用两个循环,三个有规律就用三个循环,以此类推。
-
-而列是包含在行内的, **所以意味着实现行规律的循环 要 包含着实现列规律的循环** ,这就是多重循环的应用场景了。
-
-::: tip 笔者说
-你就记住这类需求的一个规律,**外层循环可以控制行规律,内层循环控制每行内容(列)的规律** 。
-:::
-
-**思路分析:**
-
-1. 根据需求,行和列都有重复性规律,采用多重循环
-2. 确定此案例中,两个循环的组成部分
-
- 外层循环
- - 循环条件:`<= 5`
- - 循环操作:内层循环,每行结尾换行
-
- 内层循环
- - 循环条件:`<= 5`
- - 循环操作:打印每颗 `*`
-3. 由于两个都是固定次数循环,套用 for 循环的语法来编写代码
-
-4. 检查是否能正常结束循环
-
-```java
-// 外层循环,执行五次,每次输出一行*
-for (int i = 1; i <= 5; i++) {
- // 内层循环,执行五次,每次输出一个*
- for (int j = 1; j <= 5; j++){
- // 不换行输出*
- System.out.print("*");
- }
- // 每行结尾换行
- System.out.println();
-}
-```
-
-### 打印直角三角形
-
-
-
-**思路分析:**
-
-1. 根据需求,行和列都有重复性规律,采用多重循环
-
-2. 确定此案例中,两个循环的组成部分
-
- 外层循环
-
- - 循环条件:`<= 5`
- - 循环操作:内层循环,每行结尾换行
-
- 内层循环
-
- - 循环条件:`2 * 外层循环变量 - 1`
- - 循环操作:打印每颗 `*`
-
-3. 由于两个都是固定次数循环,套用 for 循环的语法来编写代码
-
-4. 检查是否能正常结束循环
-
-::: tip 笔者说
-每行 `*` 的规律是我们在学习数学数列时最常见的规律,第一行:1、第二行:3、第三行:5、第四行:7、第五行:9...
-:::
-
-```java
-// 外层循环
-for (int i = 1; i <= 5; i++) {
- // 内层循环
- for (int j = 1; j <= 2 * i - 1; j++) {
- System.out.print("*");
- }
- // 每行结尾换行
- System.out.println();
-}
-```
-
-### 打印等腰三角形
-
-
-
-**思路分析:**
-
-1. 根据需求,行和列都有重复性规律,采用多重循环
-
- 等腰三角形的一部分和刚才的直角三角形规律是一样的,你看看把这个图形变换一下,是不是就明白了?
-
- 
-
- 但此次多重循环不能只是两个循环了,我们需要多准备一个循环来实现每行打印规律的空格。
-
-2. 确定此案例中,三个循环的组成部分
-
- 外层循环
- - 循环条件:`<= 5`
- - 循环操作:内层循环1,内层循环2,每行结尾换行
-
- 内层循环1:
- - 循环条件:`<= 5 - 外层循环变量`
- - 循环操作:打印一个空格
-
- 内层循环2:
- - 循环条件:`2 * 外层循环变量 - 1`
- - 循环操作:打印每颗 `*`
-
-3. 由于三个都是固定次数循环,套用 for 循环的语法来编写代码
-
-4. 检查是否能正常结束循环
-
-```java
-// 外层循环
-for (int i = 1; i <= 5; i++) {
- // 内层循环1
- for(int j = 1; j <= 5 - i; j++){
- System.out.print(" ");
- }
- // 内层循环2
- for (int j = 1;j <= 2 * i - 1; j++){
- System.out.print("*");
- }
- // 每行结尾换行
- System.out.println();
-}
-```
-
-### 打印平行四边形
-
-
-
-**思路分析:**
-
-1. 根据需求,行和列都有重复性规律,采用多重循环
-2. 确定此案例中,三个循环的组成部分
-
- 外层循环
- - 循环条件:`<= 5`
- - 循环操作:内层循环1,内层循环2,每行结尾换行
-
- 内层循环1:
- - 循环条件:`<= 5 - 外层循环变量`
- - 循环操作:打印一个空格
-
- 内层循环2:
- - 循环条件:`<= 5`
- - 循环操作:打印每颗 `*`
-3. 由于三个都是固定次数循环,套用 for 循环的语法来编写代码
-4. 检查是否能正常结束循环
-
-```java
-// 外层循环
-for (int i = 1; i <= 5; i++) {
- // 内层循环1
- for (int k = 1; k <= 5 - i; k++) {
- System.out.print(" ");
- }
- // 内层循环2
- for (int j = 1; j <= 5; j++) {
- System.out.print("*");
- }
- // 每行结尾换行
- System.out.println();
-}
-```
-
-### 打印九九乘法表
-
-
-
-**思路分析:**
-
-1. 根据需求,行和列都有重复性规律,采用多重循环
-
-2. 确定此案例中,两个循环的组成部分
-
- 外层循环
-
- - 循环条件:`<= 9`
- - 循环操作:内层循环,每行结尾换行
-
- 内层循环
-
- - 循环条件:`<= 外层循环的循环变量`
-
- 第1行打印1个,第2行打印2个,第3行打印3个....
-
- - 循环操作:打印乘法运算
-
- - 第1个操作数:内循环的循环变量
- - 第2个操作数:外循环的循环变量
-
-3. 由于两个都是固定次数循环,套用 for 循环的语法来编写代码
-
-4. 检查是否能正常结束循环
-
-```java
-// 外层循环
-for (int i = 1; i <= 9; i++) {
- // 内层循环
- for (int j = 1; j <= i; j++) {
- System.out.print(j + "*" + i + "=" + (j * i) + "\t");
- }
- // 每行结尾换行
- System.out.println();
-}
-```
-
-## 答题环节
-
-### 打印数字等腰三角形
-
-**需求:** 使用多重循环根据用户输入的数字,输出如下图形。
-
-
-
-### 倒序打印九九乘法表
-
-**需求:** 使用多重循环倒序打印九九乘法表。
-
-
-
-### 打印菱形
-
-**需求:** 使用多重循环输出如下图形。
-
-
-
-## 后记
-
-你就套着思路分析的步骤,塌下心来实现一下,相似的问题一定要回忆起相似的解决方法。别学一个忘一个,下一篇笔者带你学习一种专业工具,这样就可以逐行的看程序执行了。
-
-::: info 笔者说
-对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
-
-所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
-:::
diff --git a/docs/courses/java/01-Java语法入门/12-程序调试入门.md b/docs/courses/java/01-Java语法入门/12-程序调试入门.md
deleted file mode 100644
index c4a5f2dee..000000000
--- a/docs/courses/java/01-Java语法入门/12-程序调试入门.md
+++ /dev/null
@@ -1,192 +0,0 @@
----
-title: 程序调试入门
-author: 查尔斯
-date: 2020/10/08 19:07
-categories:
- - Java基础快速入门
-tags:
- - Java
- - Java基础
----
-
-# 程序调试入门
-
-## 前言
-
-**C:** 学习到本篇的各位同学,想必对编写期、运行期的报红,报错早已见怪不怪了。
-
-
-
-出了问题后,面向百度编程,解决了一部分,还有很多隐藏在 "冰山" 之下,所以在工作中,大家都自嘲是在写 Bug。
-
-
-
-正因如此,*一个有经验的程序员不仅仅要熟练各种技术,还应该表现出成熟且稳定的 Bug 解决能力* 。笔者在本篇将会带你熟悉下常见的 Bug 解决思路及方式, **掌握了这些技巧** ,在大多数情况下你会轻松一些的。
-
-
-
-## bug和debug
-
-::: tip bug (计算机领域漏洞)
-bug 是计算机领域专业术语,原意是 "臭虫",现在用来指代计算机上存在的漏洞,原因是系统安全策略上存在的缺陷,有攻击者能够在未授权的情况下访问的危害。
-
-bug 这个术语从 "臭虫" 转换为漏洞,还有一段 "可歌可泣" 的故事 :为马克2号(Harvard Mark II)编制程序的格蕾丝·霍珀(Grace Hopper)是一位美国海军准将及计算机科学家,同时也是世界最早的一批程序设计师之一。有一天,她在调试设备时出现故障,拆开继电器后,发现有只飞蛾被夹扁在触点中间,从而“卡”住了机器的运行。于是,霍珀诙谐地把程序故障统称为“臭虫(BUG)”,把排除程序故障叫 DEBUG,而这奇怪的“称呼”,竟成为后来计算机领域的专业行话。[1]
-:::
-
-
-
-
-
-另外以后快别外行似的喊 `必忧计` 了,人家叫 `bug` 。
-
-::: details 行业内还有这么一个冷笑话是:程序员最讨厌乾隆的哪个儿子?
-八阿哥(bug)
-:::
-
-## 调试思路
-
-日常生活中,家用电路出现问题时,打了维修电话叫电工过来,电工首先会和我们沟通下大致情况,看看问题大致的方向和可能,然后再使用万用表及其他专业检测工具对电路逐段进行检测,最后再找到异常情况的部分,进行相应维修或部件更换。
-
-我们在进行程序调试(debug)时也应该遵循类似的思路进行:
-
-1. 理清需求, **观察故障提示、现象** ,看是否能确定问题大致方向和可能
-2. 阅读代码
-3. 逐条语句执行程序
-4. 观察程序执行情况
-5. 发现问题
-6. 解决问题
-
-## 调试方式
-
-调试思路掌握之后,还有一些配套的调试方式可以帮助我们快速定位及修复 bug。
-
-### 小黄鸭调试法
-
-::: tip 小黄鸭调试法
-小黄鸭调试法(又称橡皮鸭调试法,黄鸭除虫法)是软件工程中使用的调试代码方法之一。
-
-此概念是参照于一个来自《程序员修炼之道》书中的一个故事。传说中程序大师随身携带一只小黄鸭,在调试代码的时候会在桌上放上这只小黄鸭,然后详细地向鸭子解释每行代码[2]
-
-许多程序员都有过向别人(甚至可能向完全不会编程的人)提问及解释编程问题,就在解释的过程中击中了问题的解决方案。一边阐述代码的意图一边观察它实际上的意图并做调试,这两者之间的任何不协调会变得很明显,并且更容易发现自己的错误。如果没有玩具小鸭子也可以考虑向其它东西倾诉,比如桌上的花花草草,键盘鼠标。
-
-类似的,有一种现象叫做 cone of answers,这是一个常见的现象。 **你的朋友跑来问你一个问题,但是当他自己把问题说完,或者说到一半的时候就想出了答案走了,留下一脸茫然的你。是的,这个时候你就起到了那只小黄鸭的作用** 。
-
-相似的概念还有不少,例如自白调试、纸板程序员或程序员的假人、想出脑外等等。总的来说,在你试图表述自己的想法的过程中,自然地在促使自己去整理思路,重新考虑问题。[3]
-:::
-
-小黄鸭调试法是一种非常经典的代码阅读技巧。一边读代码,一边给自己或其他的媒介来解释对应代码的含义。有些简单的问题,就这么被发现了。
-
-
-
-### 输出语句
-
-单纯通过代码阅读,如果是单词类错误(单词错误是前期学习过程中,出现频次最高的)。以我们程序员这么 "护犊子" 的情况,有时候看多少遍都看不出来。*"我的代码和他的一样,我的代码没错啊?怎么就是不行?"*
-
-这种情况下,就需要我们在程序执行的过程中,找寻一些关键的地方增加输出语句,然后执行看输出效果。如果输出语句和预期效果不对,甚至干脆没有执行,那问题很大可能就出在这里了。
-
-### 专业调试工具[重要]
-
-通过阅读代码、加输出语句来查找程序错误,前期固然可行。但是当程序结构越来越复杂时,或者花费了大量时间依然没有能够发现及解决问题时,还需要配合专业的 debug 工具来调试。
-
-在 JDK 中内置了 `jdb` 可以用来在命令行调试程序,但是我们现在在用 Eclipse 这种 IDE ,人家也携带了相应的调试程序。命令行和可视化界面这种选择,毫无疑问要选择后者。
-
-## Eclipse调试
-
-接下来我们通过一个案例来演示一下具体 Eclipse 中调试工具的使用步骤。
-
-### 问题描述
-
-**案例需求:输出 1 ~ 5 这5个数字。**
-
-**代码实现:**
-
-```java
-int i = 1;
-System.out.println("程序调试演示,注意观察 i 的值:");
-while(i < 5){
- System.out.println(i);
- i++;
-}
-```
-
-**存在问题:** 只能输出到4,无法输出5这个数
-
-
-
-### 使用步骤
-
-程序运行嗖嗖的,我们根本无法逐行的观察具体真实的执行过程,Eclipse 的程序调试(debug)又被称为断点调试。所谓断点,就是你希望运行中的程序在哪一行停下来,让你可以逐行进行分析。
-
-**第一步:看故障现象,分析错误,设置断点。**
-
-在你想监测程序运行的开始代码行左侧栏,双击鼠标左键将出现一个断点标志,再双击可以取消断点。
-
-
-
-**第二步:启动调试。**
-
-这时候,我们不再以 `run as` 运行了,而是右键以 `debug as` 运行。
-
-启动时,Eclipse 会弹出一个对话框提示你是否要切换到 debug 模式视图,我们点击 switch 切换过去,debug as 运行后,它会按照正常的执行顺序进行代码执行,直到遇到断点行才停下来。此时这一行代码处于 **等待执行** ,还未执行的状态。
-
-
-
-debug 视图的界面布局如下:
-
-
-
-**第三步:单步运行。**
-
-连续点击 F6 键可以单步运行程序,即逐行执行程序,这时候我们就可以来观察程序运行过程了。
-
-
-
-**第四步:观察变量变化。**
-
-在逐行运行过程中,可以观察右侧变量表来查看变量的变化情况,鼠标直接放在变量名上,也可以直接查看它的当前值。
-
-选中表达式还可以查看表达式的计算结果。
-
-
-
-**其他调试按钮的使用:**
-
-我们可以按下 F8 ,按下它,会向下快速执行代码行到下个断点才会停住。这样我们就可以只观察想要看到的代码行效果了。
-
-::: tip 笔者说
-在运行过程中,随时可以添加断点或取消断点,非常灵活。
-:::
-
-我们也可以按下 Ctrl + F2 随时结束当前的调试。
-
-
-
-好了,我们最后完整调试一下吧,调试到最后环节时,很容易就发现 `i` 的值到了5的时候,就无法进入循环了,问题就出在这,改动条件表达式为 `i <= 5` 就没事了。
-
-调试完之后,你可以在右侧 断点表 快速清除所有的断点,然后再点击右上角的 Java 视图标志切换回之前的开发模式。
-
-
-
-## 参考文献
-
-[1]百度百科. bug (计算机领域漏洞)[EB/OL]. https://baike.baidu.com/item/bug/3353935. 2020-1-13
-
-[2]百度学术. The Pragmatic programmer:From journeyman to master[EB/OL]. https://xueshu.baidu.com/usercenter/paper/show?paperid=1971af4403d863660114ff571f6757a5&site=xueshu_se. 2021-1-13
-
-[3]百度百科. 小黄鸭调试法[EB/OL]. https://baike.baidu.com/item/小黄鸭调试法/16569594. 2021-1-13
-
-## 后记
-
-在本篇中,笔者试着添加了一些动态 gif 图来更好的展现一些操作步骤,希望它们对你有帮助,也希望你能看懂这无声的“对白”。
-
-本篇也是笔者语言入门系列的第一个程序调试使用,只是入门操作而已,后续随着系列的延续,再补充更多的使用技巧以及其他调试按钮的功能。
-
-希望你从现在开始好好用用每个 IDE 的程序调试工具,绝对比你遇到问题或思路不畅时,只靠眼睛瞪代码有效。
-
-
-
-::: info 笔者说
-对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
-
-所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
-:::
diff --git a/docs/courses/java/01-Java语法入门/13-一维数组.md b/docs/courses/java/01-Java语法入门/13-一维数组.md
deleted file mode 100644
index 8c069698f..000000000
--- a/docs/courses/java/01-Java语法入门/13-一维数组.md
+++ /dev/null
@@ -1,605 +0,0 @@
----
-title: 一维数组
-author: 查尔斯
-date: 2020/10/09 13:57
-categories:
- - Java基础快速入门
-tags:
- - Java
- - Java基础
----
-
-# 一维数组
-
-## 前言
-
-**C:** 循环是 Java 语法上的一个小坎儿,跨过来之后,我们马上就要从语法入门上岸了。
-
-本篇,笔者要带着你学习, Java 语法入门的一个进阶知识,数据结构:数组。
-
-在目前已学的 Java 知识中,如果我们想利用程序,存储一个成绩数据,如下所示即可。
-
-```java
-// 声明一个变量,存储成绩
-double score = 90;
-```
-
-但当我们要存储并使用5个、10个,甚至更多成绩数据时,变量这种单一存储的方式,其效率及使用将变得不太友好。
-
-```java
-double score1 = 90;
-double score2 = 80;
-double score3 = 70;
-double score4 = 60;
-double score5 = 59;
-...
-```
-
-所以我们需要一种更为先进的,能够同时存储多个数据、并方便使用的存储方式。
-
-
-
-
-
-## 数据结构概述
-
-在开始介绍数组前,我们先来普及一下数据结构的基本概念。
-
-::: tip 《数据结构与算法》
-数据结构是计算机存储、组织数据的方式。
-
-数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法和索引技术有关。[1]
-:::
-
-数据结构有很多种,一般来说,按照数据的逻辑结构对其进行简单的分类,包括线性结构(线性表)和非线性结构(非线性表)两类。[2]
-
-1. 线性表(Linear List),就是表中各个结点具有线性关系。数据排成像一条线一样的结构。[3]
-
- 常见的线性表数据结构有:数组,队列、栈、链表等。
-
- 
-
-2. 非线性表,就是表中各个结点之间具有多个对应关系。[2]
-
- 常见的非线性表数据结构有:树、图等。
-
- 
-
-::: tip 笔者说
-数据结构在计算机系是一门非常重要的基础学科。数据结构产生的目的,就是为了让计算机能够以更加简单、高效、便捷的方式来 **存储** 和 **使用** 数据。
-:::
-
-
-
-## 数组概述
-
-数组是线性数据结构中最为基础,最为典型的一种顺序型结构。它用一组 **连续的内存空间** ,来存储一组具有 **相同类型** 的数据。[3]
-
-我们拿变量和数组来对比一下:
-
-- 变量:就是在内存中划出 **一块合适** 的空间。
-- 数组:就是在内存中划出 **一串连续** 的空间。
-
-
-
-## 数组组成
-
-我们也再拿变量的组成要素,和数组来对比一下。
-
-| 变量 | 数组 |
-| :---------- | :----------------------------------------------------- |
-| 变量值 | **数组元素** :在数组中存储的数据 |
-| 变量数据类型 | **数组元素的类型** :在数组中存储的数据都必须是相同的数据类型 |
-| 变量名 | **数组名** :用于区分不同的数组,命名规范同变量名一致 |
-| | **数组容量** :数组中可以存储多少个元素 |
-| | **数组下标/索引** :数组是一串连续的空间,每个空间都有其相应的 "序号", **从0开始计数** |
-
-
-
-::: tip 笔者说
-**数组的下标是由数组的容量(长度)决定的** 。数组的容量为5,那么数组的下标就是0、1、2、3、4。数组容量一经定义,就不可再变。
-:::
-
-## 数组定义
-
-了解完数组的组成,接下来,我们亲自定义一个数组来使用。在 Java 中,数组的定义方式很灵活,笔者带你认识两种比较常见的定义方式。
-
-### 方式一
-
-第一种数组定义方式为:先声明好数组,再给数组赋值。
-
-```java
-// 声明数组语法
-数据类型[] 数组名 = new 数据类型[数组容量];
-```
-
-```java
-// 数组赋值语法
-数组名[下标] = 元素值;
-```
-
-这种是最传统的数组定义方式了。通过指定好数据类型及数组容量,来划定好一串连续的空间,就可以来存储数据了。
-
-::: tip 笔者说
-声明数组的语法长得和变量定义方式类似,就是多了个 `new` 关键字,这个关键字我们在使用 Scanner 时也用过,以后笔者会告诉你它的含义的,先记住语法。
-:::
-
-好了,回到我们前言中提过的需求,来使用方式一实现一下。
-
-::: details 案例需求:存储班级内5名同学的成绩,成绩分别为:90、80、70、60、50。
-
-**思路分析:**
-
-1. 根据需求,由于要进行多个数据的存储,采用数组更为合适
-2. 确定数组的各个组成
- - 数组元素:成绩
-
- - 数组元素的类型:double
-
- - 数组名:scores(一个成绩常被命名为:score,多个成绩则可命名为复数形式:scores)
-
- - 数组容量(决定下标):5
-3. 根据数组组成,套用方式一的数组语法实现
-:::
-
-```java
-// 声明一个长度为5,用来存储 double 类型数据的数组
-double[] scores = new double[5];
-
-// 给数组赋值
-// 把 90 赋值给数组的第1个空间
-scores[0] = 90;
-// 把 80 赋值给数组的第2个空间
-scores[1] = 80;
-// 把 70 赋值给数组的第3个空间
-scores[2] = 70;
-// 把 60 赋值给数组的第4个空间
-scores[3] = 60;
-// 把 50 赋值给数组的第5个空间
-scores[4] = 50;
-```
-
-存储好了数据,在使用数据的时候,也是需要借助数组名和数组下标来进行的。
-
-```java
-// 使用语法:数组名[下标]
-System.out.println(scores[0]); // 90.0
-```
-
-::: tip 笔者说
-下标是从 0 开始计数的,所以极其容易出现,超出数组容量范围的使用问题。例如:`scores[5] = 40;`
-而且这种问题,在编译期不报错,在运行时才报错(数组越界异常),需要各位同学多加小心,反复用注释等方式提醒自己。
-:::
-
-### 方式二
-
-方式一的定义方式,需要先声明一个数组,然后再进行赋值操作。步骤中规中矩,如果想快速获得一个赋值好的数组,可以通过下方的语法来实现。
-
-```java
-// 声明数组时赋值
-// 语法
-数据类型[] 数组名 = {元素1, 元素2, ....};
-```
-
-这种定义方式,非常适合快速定义一个有值数组的情况。不需要指定容量,数组会根据元素的数量指定好容量。
-
-我们来通过方式二语法,改造一下刚才的需求实现。
-
-```java
-double[] scores = {90, 80, 70, 60, 50};
-```
-
-声明数组时赋值,还可以写成下方这样。
-
-```java
-// 声明数组时赋值
-// 语法:
-数据类型[] 数组名 = new 数据类型[] {元素1, 元素2, ...};
-```
-
-再改造下刚才的需求实现。
-
-```java
-// 注意:有[],也不需要指定数组容量,它是空的,不是笔者写错了
-double[] scores1 = new double[] {90, 80, 70, 60, 50};
-```
-
-::: tip 笔者说
-肯定有同学会问:这种定义方式,和上方几乎一模一样,再学习有什么用?别着急,后续在一些篇章的场景中,这种写法可能是你使用数组唯一的、更好的选择,相信笔者。
-:::
-
-## 数组的默认值
-
-在原来,我们声明了一个局部变量后,如果不赋值,是无法使用的。而声明好数组之后,如果你不进行任何赋值就直接来使用,却会发现数组的各个空间竟然都有值。
-
-这是因为在数组声明时,会伴随一个初始化动作,初始化动作就是对数组每一个空间, **根据数组元素数据类型** ,来设置一个默认值的过程。
-
-- 整数型数组(byte、short、int、long):默认值为0
-- 浮点型数组(float、double):默认值为0.0
-- 布尔型数组(boolean):默认值为false
-- 字符型数组(char):默认值为一个空格(\u0000)
-- 字符串型数组(String):默认值为 null
-
-::: tip 笔者说
-`null` 是一种特殊的值,后期,我们在讲解引用数据类型时会再提到。
-:::
-
-```java
-double[] dArr = new double[5];
-System.out.println(dArr[0]); // 0.0
-
-int[] iArr = new int[5];
-System.out.println(iArr[0]); // 0
-
-boolean[] bArr = new boolean[5];
-System.out.println(bArr[0]); // false
-
-char[] cArr = new char[5];
-System.out.println(cArr[0]); // 一个空格,\U0000
-
-String[] sArr = new String[5];
-System.out.println(sArr[0]); // null
-```
-
-## 动态赋值
-
-在方式一的赋值中,学习过循环的我们,很快就能发现一些重复性,有规律性的操作。
-
-
-
-如果我们再结合上 Scanner 键盘输入,就可以将数组的赋值变为动态形式的。
-
-**思路分析:**
-
-1. 根据效果图分析,只有单个规律,采用单层循环即可
-2. 确定循环要素
- - 循环条件: `< 数组长度`
-
- - 循环操作: 数组名[循环变量] = 输入的元素值;
-3. 固定次数循环,采用 for 循环
-4. 检查循环是否可以正常退出
-
-```java
-// 声明数组,存储5个同学成绩
-double[] scores = new double[5];
-
-// 动态录入学生成绩
-Scanner input = new Scanner(System.in);
-// 数组名.length 可以获取数组的容量
-for (int i = 0; i < scores.length; i++) {
- System.out.print("请输入第" + (i+1) + "个学生成绩:");
- scores[i] = input.nextDouble();
-}
-
-System.out.println("第3名同学成绩:" + scores[2]);
-```
-
-## 数组遍历
-
-在动态赋值的实现中,我们巧妙利用了循环,实现了数组赋值的规律。其实在从数组取值过程中,依然存在此规律。在数组的常见操作中,有一种叫做遍历的概念会经常出现。
-
-::: tip 笔者说
-**遍历:** 将数组中的元素挨个取出来的过程,就叫遍历。
-:::
-
-接下来,我们看看,如何才能实现数组的遍历。
-
-### 循环下标遍历
-
-最常见的遍历方式,就是通过循环数组的下标,来进行遍历。刚才我们动态赋值就是用的这种循环规律。
-
-```java
-// 定义数组
-double[] scores = {90, 80, 70, 60, 50};
-
-// 循环所有的下标,然后根据下标取值
-for (int i = 0; i < scores.length; i++) {
- // 数组名[循环下标]
- System.out.println(scores[i]);
-}
-```
-
-### forEach遍历
-
-除此之外,还有一种较为特别的方式:使用 forEach 循环(俗称增强 for 循环)遍历。这种遍历方式,主要强调的就是将数组中的元素挨个取出来,每次都临时存储到一个变量中。
-
-```java
-// 声明数组并赋值
-double[] scores = {90, 80, 70, 60, 50};
-
-// for (数组元素数据类型 变量名 : 要遍历的数组名)
-// score 是在循环中,临时存储每一个元素的变量
-for (double score : scores) {
- System.out.println(score);
-}
-```
-
-::: tip 笔者说
-增强 for 循环遍历方式与循环下标遍历相比,在使用时更简单,但前期可能不太好理解,如果实在理解不好的话,就先用循环下标的遍历方式。而且有些时候,我们需要下标来作为辅助计算因素时,采用循环下标遍历更方便。
-:::
-
-## 数组使用
-
-光说不练假把式,接下来我们通过数组来解决一些需求问题。
-
-### 打印消费记录
-
-**案例需求:根据效果图实现,会员消费清单打印。**
-
-
-
-```java
-// 声明一个长度为5的数组,用来存储本月消费记录
-double[] records = new double[5];
-
-// 动态赋值
-Scanner input = new Scanner(System.in);
-System.out.println("请输入会员本月的消费记录:");
-for (int i = 0; i < records.length; i++) {
- System.out.print("请输入第" + (i+1) + "笔购物金额:");
- records[i] = input.nextDouble();
-}
-
-System.out.println();
-
-// 声明变量,存储消费总金额
-double sum = 0;
-System.out.println("序号\t\t金额(元)");
-for (int i = 0; i < records.length; i++) {
- System.out.println((i+1) + "\t\t" + records[i]);
- sum += records[i];
-}
-System.out.println("总金额\t\t" + sum);
-```
-
-### 猜数字
-
-**案例需求:有一个数列:8,4,2,1,23,344,12。**
-
-1. 循环输出数列的值
-2. 求数列中所有数值的和
-3. **猜数游戏:** 从键盘中任意输入一个数据,判断数列中是否包含此数
-
-```java
-// 声明数组,存储好数列的值
-int[] numArr = {8, 4, 2, 1, 23, 344, 12};
-
-// 1.循环输出数列的值
-System.out.println("数列中的值有:");
-for (int num : numArr) {
- System.out.println(num);
-}
-System.out.println("-------------------------");
-
-// 2.求数列中所有数值的和
-int sum = 0;
-for (int i = 0; i < numArr.length; i++) {
- sum += numArr[i];
-}
-System.out.println("数列的所有数值的和为:" + sum);
-System.out.println("-------------------------");
-
-// 3.猜数游戏:从键盘中任意输入一个数据,判断数列中是否包含此数
-// 3.1 从键盘中任意输入一个数据
-Scanner input = new Scanner(System.in);
-System.out.print("请输入一个数值:");
-int num = input.nextInt();
-
-// 3.2 判断数列中是否包含此数
-// 标志位
-boolean flag = false; // 假设数列中不包含此值
-for (int i = 0; i < numArr.length; i++) {
- if (numArr[i] == num) {
- flag = true;
- break;
- }
-}
-// 3.3 判断标志位
-if (flag) {
- System.out.println("数列中包含此值!");
-} else {
- System.out.println("数列中不包含此值!");
-}
-```
-
-### 求最高分
-
-**案例需求:从键盘输入本次Java考试五位学生的成绩,求考试成绩最高分。**
-
-::: tip 笔者说
-武侠电视剧中,经常上演 "文无第一,武无第二" 的戏码。比武招亲或武林大会:最开始有一个守擂的,随后有各大侠士争相对抗,赢的就成为新的守擂者,等结束剩下的就是最强的。
-:::
-
-```java
-// 1.从键盘输入本次 Java 考试五位学生的成绩
-double[] scoreArr = new double[5];
-
-Scanner input = new Scanner(System.in);
-for (int i = 0; i < scoreArr.length; i++) {
- System.out.print("请输入第" + (i + 1) + "位学生的成绩:");
- scoreArr[i] = input.nextDouble();
-}
-
-// 2.求考试成绩最高分
-// 假定一个最高分(从要比较的数据中假定一个数据)
-double max = scoreArr[0];
-for (double score : scoreArr) {
- // 如果挨个比较过程中,有比max还大的值
- // 那就将max的值换为最新的数据
- if (score > max) {
- max = score;
- }
-}
-System.out.println("最高分为:" + max);
-```
-
-### 求最低价
-
-**案例需求:输入4家手机店的 OnePlus 8T 价格,输出哪家店价格最低及最低手机价格。**
-
-
-
-这道题和上一题是一个思路,我们来实现一下。
-
-```java
-// 声明数组,存储4家店手机价格
-double[] prices = new double[4];
-
-// 动态赋值
-Scanner input = new Scanner(System.in);
-System.out.println("请输入4家店的 OnePlus 8T 手机价格:");
-for (int i = 0; i < prices.length; i++) {
- System.out.print("第" + (i+1) + "家店的价格:");
- prices[i] = input.nextDouble();
-}
-
-// 声明变量,假定最低价格为第1家
-double min = prices[0];
-// 声明变量,存储最低价格是第几家
-int minStore = 1;
-for (int i = 0; i < prices.length; i++) {
- // 如果有比最低价格还低的
- if (prices[i] < min) {
- // 更换最低价格
- min = prices[i];
- // 更换最低价格店铺
- minStore = i + 1;
- }
-}
-System.out.println("第" + minStore + "家价格最低,价格为:" + min);
-```
-
-## 答题环节
-
-下方这些题大多来自于互联网,笔者收集汇总在一起,针对性的练习一下。
-
-### 求最小值
-
-**需求:获取指定数组中偶数元素值的最小值**
-
-1. 定义一个 int 数组 arr
-2. 键盘录入5个整数,存入数组 arr 中,并且录入之前提示输入的是第几个数字
-3. 获取指定数组 arr 中偶数元素值的最小值,并在控制台打印
-
-### 求差值
-
-**需求1:获取到数组中最大值和最小值的差值**
-
-1. 获取键盘录入的5个整数,并存放在 int 数组 arr 中,输入前提示输入的是第几个值。
-2. 分别获取数组中最大值和最小值,并计算差值。
-3. 输出差值。
-
-**需求2:获取数组元素的偶数和与奇数和之差**
-
-1. 获取键盘录入的5个整数,并存放在 int 数组 arr 中,输入前提示输入的是第几个值;
-
-2. 分别获取数组中元素的偶数和与奇数和;
-
-3. 输出偶数和与奇数和的差值
-
-### 个数统计
-
-**需求1:获取指定数组中元素值为偶数的元素个数**
-
-1. 定义一个整数数组 arr
-2. 获取5个0~50之间(包含0和50)的随机数,并存入 arr
-3. 获取指定数组 arr 中元素值为偶数的元素个数,并打印
-
-**需求2:获取指定数组中大于指定整数的元素个数**
-
-1. 获取键盘录入的5个整数,并存放在 int 数组 arr 中,输入前提示输入的是第几个值
-
-2. 键盘录入一个需要进行比较的整数 num
-
-3. 计算数组 arr 中比整数 num 大的元素个数
-
-### 查询下标
-
-**需求:查询指定元素值在指定数组中的下标值**
-
-1. 创建 int 数组 arr,arr 包含11, 32,55, 47,55, 79,23
-2. 输入任意一个整数 num
-3. 遍历数组,如果指定数组 arr 中不存在指定整数 num,那么输出 -1,指定数组 arr 中存在多个相同的指定整数 num,那么输出 num 值对应的最大角标值(最后的num对应的角标)
-
-### 替换值
-
-**需求:用指定整数替换指定数组中的元素值**
-
-1. 创建 int 数组 arr,数组内包含0-9之间的10个整数
-
-2. 获取键盘录入的一个整数 num,如果 num 为偶数,则用 num 替换指定数组 arr 中的所有偶数下标的元素值
-
- 如果 num 为奇数,则用 num 替换指定数组 arr 中所有的奇数下标的元素值
-
-3. 输出替换值之后的数组 arr
-
-### 去除值
-
-**需求:去除数组中所有的数值0**
-
-1. 定义一个数组 oldArr,元素为1,3,4,5,0,0,6,6,0,5,4,7,6,7,0,5
-
-2. 去除所有为 0 的元素值,并存入到一个新的数组 newArr,效果为1,3,4,5,6,6,5,4,7,6,7,5
-
-3. 分别遍历两个数组
-
-### 互换值
-
-**需求:将指定数组元素值的位置前后互换,例如:[11, 32,55, 47,79,23] 置换后的数组元素为:**
-**[23, 79, 47, 55, 32, 11]**
-
-1. 定义一个整数数组 arr
-2. 键盘录入5个整数,并存入数组 arr
-3. 定义一个新数组 newArr
-4. 将指定数组 arr 的元素值的位置前后互换,并存储到新数组 newArr 中
-5. 在控制台分别横向打印 arr 和 newArr 的内容
-
-### 获取随机值
-
-**需求1:获取指定数组中随机的2个元素值**
-
-1. 定义一个 int 数组 arr
-2. 键盘录入5个整数,存入数组 arr 中,并且录入之前提示输入的是第几个数字
-3. 随机获取数组中的两个元素值并在控制台打印
-
-**需求2:随机获取4个A-Z之间(包含A和Z)的大写字母**
-
-1. 定义一个 char 数组 arr
-
-2. 生成A-Z之间的26个大写字母,并存入数组 arr 中
-
-3. 从 arr 数组中获取4个随机大写字母。
-
-### 字符串的拼接
-
-**需求:获取指定字符数组中下标为奇数的所有字符,并连接成一个字符串**
-
-1. 创建字符数组,数组包含 26 个大写字符及 0-9 字符
-2. 获取指定字符数组中,下标为奇数的所有字符,并连接成一个字符串
-3. 将字符串打印在控制台上
-
-## 参考文献
-
-[1]彭军、向毅主编.数据结构与算法:人民邮电出版社,2013年
-
-[2]刘亚东;曲心慧编.C/C++常用算法手册:中国铁道出版社,2017.09:第21页
-
-[3]冯小圆. 数据结构之数组[EB/OL]. https://www.cnblogs.com/fengxiaoyuan/p/10934399.html. 2019-05-27
-
-## 后记
-
-有了数组这一数据结构之后,多个数据存储及使用变得更加方便,随之而来的就是一些算法题型的解锁,笔者在本篇的作业中汇总了10来道练习题,希望你能通过解决这些问题,来锻炼下自己的逻辑思维。
-
-做不出来也别着急,你还可以评论求助笔者来给你参考答案。
-
-
-
-最后别忘了,牢记学习数组的原因:因为前面的数据存储方式,已经不能满足我们日益复杂的需求。而且,数组作为一种典型的,基础的线性表数据结构,有其存储上的优势。
-
-它在查找方法因为有下标的存在,效率较为不错,所以 **适合增删情况较少,查取数据较多的场景** 。但它也有不足之处:它只能存储一组定长的具有相同数据类型的数据。
-
-::: info 笔者说
-对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
-
-所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
-:::
diff --git a/docs/courses/java/01-Java语法入门/14-多维数组.md b/docs/courses/java/01-Java语法入门/14-多维数组.md
deleted file mode 100644
index 7e0a063c4..000000000
--- a/docs/courses/java/01-Java语法入门/14-多维数组.md
+++ /dev/null
@@ -1,447 +0,0 @@
----
-title: 多维数组
-author: 查尔斯
-date: 2020/10/09 15:30
-categories:
- - Java基础快速入门
-tags:
- - Java
- - Java基础
----
-
-# 多维数组
-
-## 前言
-
-**C:** 上一篇,我们学习了数组的概念,并掌握了它的定义及基本使用方法。
-
-下面,我们再来看一个案例需求。
-
-**案例需求:已知有3个班级各5名学员,请计算各个班级的总成绩。**
-
-根据需求及经验来分析,如果我们只是要计算单个班级的学生总成绩,那么定义一个普通数组,来存储这些成绩信息,再去计算即可。
-
-```java
-// 存储一个班成绩
-double[] scores = new double[5];
-// ...
-```
-
-而现在需要计算三个班级的各自总成绩,按经验推导,应该先如下定义存储结构,然后分别计算。
-
-```java
-// 存储第一个班成绩
-double[] scores1 = new double[5];
-// 存储第二个班成绩
-double[] scores2 = new double[5];
-// 存储第三个班成绩
-double[] scores3 = new double[5];
-// ...
-```
-
-自然它也是可以实现需求的。但是,在本篇,笔者将带你学习一种更为高级的方式:多维数组。
-
-
-
-
-
-## 概述
-
-上一篇,我们学的用来存储多个数据的数组,它们被称为一维数组。
-
-多维数组,类似于前面学过的嵌套 if、嵌套循环。笔者觉得,你也可以把多维数组称之为 "嵌套数组"。
-
-它的常见表现形式有:二维数组、三维数组。
-
-::: tip 笔者说
-二维数组:是以 一维数组 作为 数组元素 的数组,即 “数组的数组”。[1]
-
-三维数组:是以 二维数组 作为 数组元素 的数组。
-:::
-
-有点拗口,定义向绕口令一样,分明就是 "俄罗斯套娃" 么。
-
-
-
-不过在使用多维数组前,需要再强化些概念理解:
-
-- 一般来讲,主要使用的是二维数组,三维及以上使用较少( **本篇笔者也主要介绍二维数组的使用** )
-- 从语法上来看,Java 支持多维数组的写法
-- 从内存分配原理的角度上来看,它们都是一维数组而已
-
-## 定义
-
-### 方式一
-
-传统的一维数组的声明和赋值方式,我们都是掌握的:
-
-```java
-// 声明语法:数据类型[] 数组名 = new 数据类型[数组容量];
-// 例如:存储5名学生成绩:
-// 数组存储的元素:成绩
-// 数组元素的数据类型:double
-// 数组的容量(要存储的成绩数量):5
-double[] scores = new double[5];
-
-// 赋值语法:数组名[下标] = 元素值;
-scores[0] = 90;
-scores[1] = 80;
-scores[2] = 70;
-scores[3] = 60;
-scores[4] = 50;
-```
-
-
-
-二维数组本质上就是一维数组,只不过是以 一维数组 作为 数组元素 的数组。我们可以把二维数组分成外维数组及内维数组。
-
-```java
-// 声明二维数组
-// 语法
-数据类型[][] 数组名 = new 数据类型[外维数组容量][内维数组容量];
-
-// 给二维数组赋值
-// 语法
-数组名[外维数组下标][内维数组下标] = 元素值;
-```
-
-我们结合着二维数组的语法,来初步实现下前言中的需求。
-
-::: details 案例需求:存储3个班各5名学生成绩。
-
-**思路分析:**
-
-1. 根据需求,由于要存储的两组数据有包含关系,采用二维数组更为合适
-
-2. 确定数组的各个组成
-
- - 外维数组:
- - 数组元素:班级
-
- - 数组元素类型:double[]
- - 数组容量:5
-
- - 内维数组:
- - 数组元素:成绩
- - 数组元素类型:double
- - 数组容量:3
-
-3. 根据数组组成,套用方式一的数组语法实现
-:::
-
-```java
-// 声明一个二维数组
-double[][] scores = new double[3][5];
-
-// 给二维数组赋值
-// 第1个班
-scores[0][0] = 90; // 第1个班的第1个同学成绩为90
-scores[0][1] = 80;
-scores[0][2] = 70;
-scores[0][3] = 60;
-scores[0][4] = 50;
-// 第2个班
-scores[1][0] = 90;
-scores[1][1] = 90;
-scores[1][2] = 80;
-scores[1][3] = 70;
-scores[1][4] = 65;
-// 第3个班
-scores[2][0] = 85;
-scores[2][1] = 70;
-scores[2][2] = 75;
-scores[2][3] = 80;
-scores[2][4] = 60;
-```
-
-
-
-::: tip 笔者说
-虽然数组有些特别,不像之前学的基本数据类型:int、double..。但数组也是一种数据类型,所以 "别拿豆包不当干粮,别拿数组不当数据类型" 。
-:::
-
-### 方式二
-
-在定义二维数组时,内维数组容量不是必须立刻指定的,还可以这样写。
-
-```java
-数据类型[][] 数组名 = new 数据类型[外维数组容量][内维数组容量];
-```
-
-**案例需求:存储三个班学生成绩,1班有5个学生,2班有3个学生,3班有2个学生。**
-
-```java
-// 声明一个二维数组
-double[][] scores = new double[3][];
-// 声明内维数组
-scores[0] = new double[5]; // 第一个班
-scores[1] = new double[3]; // 第二个班
-scores[2] = new double[2]; // 第三个班
-
-// 赋值语法:数组名[外维数组下标][内维数组下标] = 元素值;
-// 第1个班
-scores[0][0] = 90;
-scores[0][1] = 80;
-scores[0][2] = 70;
-scores[0][3] = 60;
-scores[0][4] = 50;
-// 第2个班
-scores[1][0] = 90;
-scores[1][1] = 90;
-scores[1][2] = 80;
-// 第3个班
-scores[2][0] = 85;
-scores[2][1] = 70;
-```
-
-
-
-### 方式三
-
-一维数组可以在声明时,实现直接赋值,二维数组自然也是可以的。
-
-```java
-// 存储3个班各5名同学成绩
-// 每个 {} 就是一个一维数组
-double[][] scores = {{90, 80, 70, 60, 50}, {90, 90, 80, 70, 65}, {85, 70, 75, 80, 60}};
-```
-
-另外它也支持下方的写法:
-
-```java
-double[][] scores = new double[][]{{90, 80, 70, 60, 50}, {90, 90, 80, 70, 65}, {85, 70, 75, 80, 60}};
-```
-
-## 动态赋值及遍历
-
-一维数组学习的时候,我们发现在数组赋值时是重复性、有规律性的。当时我们就用 Scanner 结合循环来优化了一下赋值过程,这样就拥有了动态赋值的能力。
-
-现在二维数组赋值虽然更为复杂,但是依然保有重复性、规律性,我们也可以用 Scanner 结合循环来优化一下。
-
-
-
-**思路分析:**
-
-1. 根据效果图分析,它分别有两个规律性操作,采用二重循环
- - 班级的规律
- - 每个班级的规律
-2. 循环要素
- - 外层循环
- - 循环条件:`< 外维数组的容量`
- - 循环操作:内层循环
-
- - 内层循环
- - 循环条件:`< 内维数组的容量`
- - 循环操作: 数组名\[外层循环变量][内层循环变量] = 输入的元素值;
-3. 固定次数循环,采用 for 循环
-4. 检查循环是否可以正常退出
-
-```java
-double[][] scores = new double[3][5];
-
-Scanner input = new Scanner(System.in);
-// 循环班级
-for (int i = 0; i < scores.length; i++) {
- // 循环每个班级
- System.out.println("开始录入第" + (i+1) + "个班的学生成绩:");
- for (int j = 0; j < scores[i].length; j++) {
- System.out.print("请输入" + (i+1) + "班的第" + (j+1) + "个学生的成绩:");
- scores[i][j] = input.nextDouble();
- }
-}
-```
-
-上述代码就是二维数组的动态赋值方式。如果去除输入赋值环节后,它显然又是二维数组的遍历方式。
-
-```java
-double[][] scores = {{90, 80, 70, 60, 50}, {90, 90, 80, 70, 65}, {85, 70, 75, 80, 60}};
-
-// 循环班级
-for (int i = 0; i < scores.length; i++) {
- // 循环每个班级
- System.out.println((i+1) + "班的学生成绩如下:");
- for (int j = 0; j < scores[i].length; j++) {
- System.out.println("第" + (j+1) + "个学生的成绩是:" + scores[i][j]);
- }
-}
-```
-
-**控制台输出:**
-
-```
-1班的学生成绩如下:
-第1个学生的成绩是:90.0
-第2个学生的成绩是:80.0
-第3个学生的成绩是:70.0
-第4个学生的成绩是:60.0
-第5个学生的成绩是:50.0
-2班的学生成绩如下:
-第1个学生的成绩是:90.0
-第2个学生的成绩是:90.0
-第3个学生的成绩是:80.0
-第4个学生的成绩是:70.0
-第5个学生的成绩是:65.0
-3班的学生成绩如下:
-第1个学生的成绩是:85.0
-第2个学生的成绩是:70.0
-第3个学生的成绩是:75.0
-第4个学生的成绩是:80.0
-第5个学生的成绩是:60.0
-```
-
-::: tip 笔者说
-一维数组的时候,我们有两种遍历方式:1.循环下标遍历 2.增强 for循 环遍历。而二维数组的话,笔者建议你就采用循环下标方式即可,不要再去 "混合或者自造语法" 了。
-:::
-
-## 使用
-
-好了,介绍完二维数组的概念和定义方式,接下来我们完整的使用它,来完成前言中的案例需求。
-
-**案例需求:已知有3个班级各5名学员,请使用二维数组计算各个班级的总成绩。**
-
-```java
-double[][] scores = {{90, 80, 70, 60, 50}, {90, 90, 80, 70, 65}, {85, 70, 75, 80, 60}};
-
-// 循环班级
-// 定义变量,记录每个班的总成绩
-double sum;
-for (int i = 0; i < scores.length; i++) {
- // 每次都要初始化总成绩
- sum = 0;
- // 循环每个班级
- for (int j = 0; j < scores[i].length; j++) {
- // 累加成绩
- sum += scores[i][j];
- }
- System.out.println((i+1) + "班的学生总成绩为:" + sum);
-}
-```
-
-**控制台输出:**
-
-```
-1班的学生总成绩为:350.0
-2班的学生总成绩为:395.0
-3班的学生总成绩为:370.0
-```
-
-## Arrays工具类
-
-有了数组之后,我们就可以实现非常多的算法需求。但很多算法需求中,部分功能都是类似的,我们重复的实现,重复的写。
-
-**例如:遍历输出数组的所有内容。**
-
-```java
-String[] arr = {"佩奇", "乔治", "苏西"};
-// 循环下标来实现数组遍历
-for (int i = 0; i < arr.length; i++) {
- System.out.println(arr[i]);
-}
-```
-
-而这一切,Java 创造团队们也考虑到了,他们给我们在 Java 类库中提供了一个工具类:`Arrays`。在这个类中,提供的都是操作数组的方法。
-
-| **方法名称** | **说明** |
-| :------------------------------- | ------------------------------------------------------------ |
-| toString(array) : String | 将一个数组 array 转换成一个字符串 |
-| equals(array1,array2) : boolean | 比较 array1 和 array2 两个数组是否相等 |
-| sort(array) : void | 对数组 array 的元素进行升序排列 |
-| copyOf(array,length) : newArray | 把数组 array 复制成一个长度为 length 的新数组,返回类型与复制的数组一致 |
-| ... | ... |
-
-它和我们曾经使用过的 Scanner 都在 `java.util` 包下,所以使用的话也需要先导入一下。
-
-::: tip 笔者说
-Java 系统类库 java.util 包下的类都是工具类,使用前需要先 import。
-:::
-
-看看下方,有了 Arrays,你只需要一行代码,就可以搞定一个数组的遍历输出操作。
-
-```java
-package demo1;
-// 1.导入 Arrays
-import java.util.Arrays;
-
-public class Demo1 {
-
- public static void main(String[] args) {
- String[] arr = {"佩奇", "乔治", "苏西"};
- // 2.使用 Arrays(它不需要创建对象,直接可以用)
- String arrStr = Arrays.toString(arr);
- System.out.println(arrStr); // [佩奇, 乔治, 苏西]
- }
-
-}
-```
-
-**再例如:要比较两个数组的内容是否相同。**
-
-```java
-char[] charArr1 = {'a', 'b', 'c', 'd'};
-char[] charArr2 = {'a', 'b', 'c', 'd'};
-// 不使用 equals 方法,原生写法
-boolean flag = true;
-for (int i = 0; i < charArr1.length; i++) {
- if (charArr1[i] != charArr2[i]) {
- flag = false;
- }
-}
-System.out.println("两个数组内容是否一致?" + flag);
-```
-
-而有了 Arrays,你只需要一行代码,就可以搞定两个数组的内容比较操作。
-
-```java
-char[] charArr1 = {'a', 'b', 'c', 'd'};
-char[] charArr2 = {'a', 'b', 'c', 'd'};
-boolean flag = Arrays.equals(charArr1, charArr2);
-System.out.println("两个数组内容是否一致?" + flag);
-```
-
-我们再来看看另外两个方法的使用示例。
-
-```java
-// sort 方法
-int[] arr1 = {10, 9, 11, 7, 8};
-Arrays.sort(arr1);
-System.out.println(Arrays.toString(arr1)); // [7, 8, 9, 10, 11]
-
-// copyOf 方法
-int[] arr2 = {10, 9, 11, 7, 8};
-int[] newArr = Arrays.copyOf(arr2, arr2.length + 1);
-System.out.println(Arrays.toString(newArr)); // [10, 9, 11, 7, 8, 0]
-```
-
-::: tip 笔者说
-Arrays 工具类还有更多的数组操作方法,等以后需要使用时,笔者再告诉你。或者在未来你有需求时,也可以去查阅 Java 官方的 API 文档,来挑选适合你的数组操作方法。
-:::
-
-## 答题环节
-
-### 统计销售额
-
-**需求:统计一个公司三个销售小组中每个小组的 总销售额 以及 整个公司的销售额 。**
-
-1. 定义一个二维数组,存储三个销售小组的销售额。
-
-2. 第一小组销售额为{5, 10}万元,第二小组销售额为{10, 20, 30}万元,第三小组销售额为{10, 10, 20, 20}万元。
-
-3. 计算每个小组的总销售额和整个公司的销售额并输出。
-
-## 参考文献
-
-[1]百度百科. 二维数组[EB/OL]. https://baike.baidu.com/item/二维数组/8168543. 2021.1.17
-
-## 后记
-
-这一篇,咱们还学到了一个 Arrays 工具类,是不是感觉一些数组操作也没那么难了?的确,有了这类工具类的加入,将会更方便我们日常的需求实现。
-
-但也别忘了自身内功的提升,如果以后没有合适的工具类,难道我们就不能自己写出来了吗?
-
-下一篇,我们将围绕数组进行一些排序算法学习,毕竟数据结构和算法不分家,算法中又以排序算法最为经典,期待一下吧。
-
-::: info 笔者说
-对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
-
-所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
-:::
diff --git a/docs/courses/java/02-Java面向对象/01-类和对象.md b/docs/courses/java/02-Java面向对象/01-类和对象.md
deleted file mode 100644
index 50758fa7b..000000000
--- a/docs/courses/java/02-Java面向对象/01-类和对象.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-title: 类和对象
-author: 查尔斯
-date: 2020/10/02 21:29
-categories:
- - Java基础快速入门
-tags:
- - Java
- - Java基础
-showArticleMetadata: false
-editLink: false
-lastUpdated: false
-showComment: false
----
-
-# 类和对象
-
-::: tip 未完待续......
-
-:::
diff --git a/docs/courses/java/03-Java高级特性/01-集合与泛型-1.md b/docs/courses/java/03-Java高级特性/01-集合与泛型-1.md
deleted file mode 100644
index 0954a0337..000000000
--- a/docs/courses/java/03-Java高级特性/01-集合与泛型-1.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-title: 集合与泛型-1
-author: 查尔斯
-date: 2020/10/02 21:29
-categories:
- - Java基础快速入门
-tags:
- - Java
- - Java基础
-showArticleMetadata: false
-editLink: false
-lastUpdated: false
-showComment: false
----
-
-# 集合与泛型-1
-
-::: tip 未完待续......
-
-:::
diff --git a/docs/courses/java/04-附录/01-CentOS安装JDK.md b/docs/courses/java/04-附录/01-CentOS安装JDK.md
deleted file mode 100644
index 8eb6eeacc..000000000
--- a/docs/courses/java/04-附录/01-CentOS安装JDK.md
+++ /dev/null
@@ -1,113 +0,0 @@
----
-title: CentOS 8.2 安装 JDK 1.8.0_202
-author: 查尔斯
-date: 2022/10/23 11:29
-categories:
- - Java基础快速入门
-tags:
- - Java
- - JDK
- - Linux
- - CentOS
-showComment: false
----
-
-# CentOS 8.2 安装 JDK 1.8.0_202
-
-## 检查系统是否自带JDK
-
-::: warning 笔者说
-检查系统中是否已经安装了 JDK ,安装的基本是 OpenJDK,如果已经安装了,那就提前卸载掉它。
-:::
-
-```shell
-rpm -qa | grep jdk
-# 如果上方命令查询出了内容,就把查出的软件卸载掉
-rpm -e --nodeps 软件名
-```
-
-## 下载并上传安装包
-
-可前往 [官网](https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html) 下载 JDK Linux 安装包然后上传到服务器。
-
-
-
-也可以直接在服务器内下载。
-
-```shell
-wget https://repo.huaweicloud.com/java/jdk/8u202-b08/jdk-8u202-linux-x64.tar.gz
-```
-
-## 解压安装包
-
-::: warning 笔者说
-除去一些固定的东西,一定要记得根据你实际的情况调整好目录位置或命名。
-:::
-
-```shell
-# 解压安装包到指定目录(如指定目录不存在则需要先提前用 mkdir 创建)
-# 下方 /opt/disk 是服务器的一块数据盘挂载目录
-mkdir -p /opt/disk/java
-
-tar -zxvf jdk-8u202-linux-x64.tar.gz -C /opt/disk/java
-```
-
-切换到 `/opt/disk/java/jdk1.8.0_202` 目录下。
-
-```shell
-cd /opt/disk/java/jdk1.8.0_202
-```
-
-里面就是我们熟悉的 JDK 那些内容。
-
-```
-bin
-include
-jre
-LICENSE
-README.html
-src.zip
-THIRDPARTYLICENSEREADME.txt
-COPYRIGHT
-javafx-src.zip
-lib
-man
-release
-THIRDPARTYLICENSEREADME-JAVAFX.txt
-```
-
-## 设置环境变量
-
-::: tip 笔者说
-还差最后一步,配置环境变量 JAVA_HOME。不配好它,很多 Java 写的程序可就没法直接使用了。而且你配好了环境变量,我们也可以方便的在任何目录下使用 Java 的命令。
-:::
-
-```shell
-# 1、打开 profile 文件
-vim /etc/profile
-
-# 2、在其中插入环境变量配置
-JAVA_HOME=/opt/disk/java/jdk1.8.0_202
-CLASSPATH=.:$JAVA_HOME/lib.tools.jar
-PATH=$JAVA_HOME/bin:$PATH
-export JAVA_HOME CLASSPATH PATH
-
-# 3、重新加载 profile 文件,使最新配置生效
-source /etc/profile
-```
-
-## 检验是否安装成功
-
-执行查看 Java 版本命令。
-
-```shell
-java -version
-```
-
-如果能看到下方这么一串版本信息输出,那就道上一声恭喜。
-
-```shell
-java version "1.8.0_202"
-Java(TM) SE Runtime Environment (build 1.8.0_202-b08)
-Java HotSpot(TM) 64-Bit Server VM (build 25.202-b08, mixed mode)
-```
diff --git a/docs/courses/java/index.md b/docs/courses/java/index.md
deleted file mode 100644
index 5cfc39164..000000000
--- a/docs/courses/java/index.md
+++ /dev/null
@@ -1,147 +0,0 @@
----
-title: Java基础快速入门
-author: 查尔斯
-date: 2020/10/01 10:24
-categories:
- - Java基础快速入门
-tags:
- - Java
- - Java基础
----
-
-# Java基础快速入门
-
-**C:** Hi,大家好!从本篇起,笔者将开辟一个新的专栏《Java基础快速入门》,这个专栏初期将涵盖 Java 语法、Java OOP、Java 高级特性三大部分。
-
-当然了,此类教程在网络上不胜枚举,笔者在以前也是比较推荐大家去看谁谁谁的什么什么文章,但后来发现,这些知识是散布在网络间的,所以分散是第一个大问题;而且由于时间和作者等因素,有些内容至今还没有得到更新,这就导致一个知识的滞后性。
-
-所以,笔者再开辟此专栏的目的,就是为了继续做好知识更新延续和传播,未来对这些内容还会继续编辑更新,仅仅希望能让后辈在学习 Java 语言的路上可以走的顺畅一些,也可以在前期少走一些弯路。
-
-
-
-
-
-## 程序概述
-
-好了,言归正传。从看到本篇的那一刻起,你就已经推开了 IT 编程的大门。自此,程序一词或将改变你未来的人生轨迹。未来的你,按键行云流水,脑内 CPU 高速运转,屏幕切换间,高可用、高扩展、高安全性的” 完美”程序,将由你所著。[[未来的你](http://www.acfun.cn/v/ac4552801)]
-
-
-
-首先我们来看看程序的概念,程序在现世纪存在两种含义,即生活中的程序和计算机中的程序。
-
-
-
-### 生活中程序
-
-**生活中的程序:** 我们去银行办理业务或者在学校时因事请假再或者在工作中去办理报销,这些场景我们都不陌生。我们一遍一遍,一步一步的执行着所谓的流程和手续(套路),这就是生活中的程序。
-
-### 计算机中的程序
-
-在本世纪的今天,现实生活中的程序你可能已经把它换了个称呼,比如:流程或者手续。而程序这个概念,更多的时候已经被你安放到了手机上的APP,或者计算机上的QQ等方面。它们的确是程序,是由我们未来的同行,前辈所创造编写,**编写这些程序的过程,就是所谓的编程** 。
-
-::: tip 笔者说
-软件不是程序,软件是比应用程序更大的概念,**软件是程序、数据及相关文档的完整集合** 。可延伸阅读张海藩和吕云翔所著的《软件工程 第4版》
-:::
-
-但是如果单单这么理解程序,可就有点小儿科了。下面的[ 百度百科 ],解释的还挺不错!程序其实是一个**指令** 的集合。
-
-
-
-**何为指令呢?** 比方说:我是一位老板(目前不是),我有个女秘书(目前没有),每天我的行程安排或者一些繁琐的事项都交给了我秘书去做,我告诉秘书去帮我邀约一位客人,她就如是去做了。我告诉秘书去帮我通知一下部门要开会了,她也会如是去通知。**这个比方里老板所告诉秘书的一件件事就是一个个指令** ,而秘书就是接收指令然后去执行的人。
-
-
-
-换到计算机世界的概念,我们被称为**程序员/码农** ,如果想让计算机这些秘书去执行一些操作,例如在屏幕上输出一些内容或者自动绘制一个小猪佩奇图像等,就需要去给计算机下达指令。**学习编程就是在学习给计算机秘书下达指令的过程而已。**
-
-
-
-### 程序和程序的关系
-
-从上面我们理解了程序的两种含义,它们之间是否存在什么关系呢?其实计算机中的程序绝大多数都是因为现实中的某些流程/程序,很是麻烦,耗时或者产生一些资源的浪费(纸张),所以在有了计算机帮助之后,我们人类就将现实生活中的这些程序,以计算机中的程序表现出来了。**例如:** 在线挂号,在线购物,在线选课、OA系统等。
-
-**一句话可以阐述这个关系:** 我们编程就是将现实生活中的业务程序移植到计算机中,以计算机指令的形式表现出来。
-
-## 编程语言概述
-
-了解完程序的概念,那这些程序指令我们是怎么告诉计算机的?口头告诉?脑电波交流?显然不是,那我们写自然语言来告诉计算机要做什么?不好意思的是,计算机并不能看懂,它只能看懂二进制(0 1这种数制)。**早期的计算机从业者就是在敲打0 1来告诉计算机需要做什么,但是对于我们人类来讲,这一堆0110阅读起来太难了,所以后期的发展中,我们用自然语言定义了一些特殊的语法,再通过一个”翻译官”(编译器)帮我们翻译给计算机看,这样就能实现计算机来执行我们的指令,而且我们自己也能看懂自己写的是什么。** 上述提到的特殊语法就是编程语言,也被称为计算机语言。
-
-
-
-### 主流的编程语言
-
-编程语言太多了,怎么定义语法的都有。这也很正常,就好像当今世界,自然语言都千奇百怪呢。所以我们不需要去过多的关注偏门的”小语种”,只需要去关注一下当今IT编程领域的主角们即可。[ 参见[TIOBE ](https://www.tiobe.com/tiobe-index/)]
-
-下图是知名排行榜的统计数据,类似的网站有PYPL等。TIOBE排行榜是根据互联网上有经验的程序员、课程和第三方厂商的数量,并使用搜索引擎(如Google、Bing、Yahoo!)以及Wikipedia、Amazon、YouTube统计出排名数据,只是反映某个编程语言的热门程度,并不能说明一门编程语言好不好,或者一门语言所编写的代码数量多少。
-
-
-
-::: tip 笔者说
-在笔者看来,数据还是能说明一些语言的好坏或流行度的。截止目前,即使谦虚一些,我们要学习的 Java(爪哇/国内音译加哇,扎哇),它仍然是最热门的编程语言之一。即便在网络上有一些人每天都在喊着不要学 Java,要学 Python,学 C,学 PHP,甚至还听过 PHP 是最好的语言,这种引战言论。
-
-事实上呢?哪有什么完美的编程语言?只不过是不同的场景,谁更适合而已。**而且处于当今时代的我们,只会一种编程语言已经无法适应快速迭代的互联网应用和企业需要了(全栈)。所以不学哪种编程语言本身就是个”伪命题”** 。
-
-尤其对于刚踏入编程领域的小伙伴们来讲,笔者个人认为,Java 是一门极其合适的入门和谋生语言!它在理解难度,语法规范,性能,流传广度、社区支持,企业需要和热度等方面都属于领先地位。稍后笔者就给你详细讲讲它的来历和能力。
-:::
-
-## Java的前世今生
-
-### Java的诞生
-
-Java是SUN Microsystems(国内译为升阳公司)于1995年推出的高级编程语言。下图是Java的共同创始人之一:詹姆斯·高斯林James Gosling(被誉为Java之父)。2009年,SUN公司被Oracle并购,高斯林离职。截止目前,高斯林加入了亚马逊AWS工作。
-
-
-
-下图是 Java 的 Logo(一杯热气腾腾的咖啡,有传言Java也是由于大佬们爱喝印尼爪哇岛的咖啡而得名)。不过这两缕蒸汽是多么像大佬们头上稀疏的秀发。
-
-
-
-::: tip 《深入理解Java虚拟机》
-1991年4月,由James Gosling博士领导的绿色计划(Green Project)开始启动,此计划的目的是开发一种能够在各种消费性电子产品(如机顶盒、冰箱、收音机等)上运行的程序架构。这个计划的产品就是Java语言的前身:Oak(橡树)。Oak当时在消费品市场上并不算成功,但随着1995年互联网潮流的兴起,Oak迅速找到了最适合自己发展的市场定位并蜕变成为Java语言。[1]
-:::
-
-### Java的发展
-
-下图是Java的发展过程,重点关注一下1995年,1998年,2009年和2013年中期即可,了解下它的历史,才能让我们更好的与它"对话和结伴"。
-
-
-
-::: tip 《深入理解Java虚拟机》
-1995年5月23日,**Oak语言改名为Java** ,并且在SunWorld大会上正式发布Java 1.0版本。Java语言第一次提出了“**Write Once,Run Anywhere** ”的口号。
-
-1996年1月23日,JDK 1.0发布,Java语言有了第一个正式版本的运行环境。JDK 1.0提供了一个纯解释执行的Java虚拟机实现(Sun Classic VM)。JDK 1.0版本的代表技术包括:Java虚拟机、Applet、AWT等。
-
-1998年12月4日,JDK迎来了一个里程碑式的版本JDK 1.2,工程代号为Playground(竞技场),Sun在这个版本中把Java技术体系拆分为3个方向,分别是面向桌面应用开发的**J2SE** (Java 2 Platform,Standard Edition)、面向企业级开发的**J2EE** (Java 2 Platform,Enterprise Edition)和面向手机等移动终端开发的**J2ME** (Java 2 Platform,Micro Edition)。在这个版本中出现的代表性技术非常多,如EJB、Java Plug-in、Java IDL、Swing等,并且这个版本中Java虚拟机第一次内置了JIT(Just In Time)编译器(JDK 1.2中曾并存过3个虚拟机,Classic VM、HotSpot VM和Exact VM,其中Exact VM只在Solaris平台出现过;后面两个虚拟机都是内置JIT编译器的,而之前版本所带的Classic VM只能以外挂的形式使用JIT编译器)。
-
-1999年4月27日,**HotSpot** 虚拟机发布,HotSpot最初由一家名为“Longview Technologies”的小公司开发,因为HotSpot的优异表现,这家公司在1997年被Sun公司收购了。HotSpot虚拟机发布时是作为JDK 1.2的附加程序提供的,**后来它成为了JDK 1.3及之后所有版本的Sun JDK的默认虚拟机。**
-
-2009年2月19日,工程代号为Dolphin(海豚)的JDK 1.7完成了其第一个里程碑版本。根据JDK 1.7的功能规划,一共设置了10个里程碑。最后一个里程碑版本原计划于2010年9月9日结束,但由于各种原因,JDK 1.7最终无法按计划完成。在JDK 1.7开发期间,Sun公司由于相继在技术竞争和商业竞争中都陷入泥潭,公司的股票市值跌至仅有高峰时期的3%,已无力推动JDK 1.7的研发工作按正常计划进行。为了尽快结束JDK 1.7长期“跳票”的问题,**Oracle公司收购Sun公司** 后不久便宣布将实行“B计划”,大幅裁剪了JDK 1.7预定目标,以便保证JDK 1.7的正式版能够于2011年7月28日准时发布。[1]
-:::
-
-::: tip 笔者说
-2017年11月,**Oracle(甲骨文)将Java EE(Java Enterprise Edition)移交给** Eclipse基金会,2018年3月份Eclipse将其更名为Jakarta EE。
-:::
-
-### Java的能力
-
-从1998年的1.2版本开始,Java出现了3个方向版本,上面笔者也给你摘了一部分周老师的书籍内容。这三个版本分别是:**面向桌面应用开发的J2SE(Java 2 Platform,Standard Edition)、面向企业级开发的J2EE(Java 2 Platform,Enterprise Edition)和面向手机等移动终端开发的J2ME(Java 2 Platform,Micro Edition)** 。
-
- Java SE是基础核心,Java ME和Java EE是核心外的两个分支。无论是走Java ME还是Java EE都需要学习Java SE,我们系列课程的前三小阶段就是在学习Java SE基础。另外以后我们主要从事的分支就是Java EE,典型的产品就是天猫、京东等大型分布式应用。再进阶之后我们还可以进入热门的大数据领域,国内现在比较知名的大数据框架Hadoop就是Java语言编写的,而且现在国内做大数据的人才很多都是由原Java工程师进阶过去的。
-
-还有非常重要的一点,**Java程序是跨平台的** ,即Java程序可以运行在任何平台上,不需要做不同系统平台的兼容适配。 **“write once , run anywhere.”** ,稍后我们编写完第一个Java程序后会介绍一下这句口号及原理。
-
-::: tip 笔者说
-1. 因为windows的普及性,在桌面程序开发领域,微软的C#语言更具有优势,开发游戏方面C和C++性能和渲染上也更有优势,Java几乎没有市场。
-2. Java ME已经凉凉,被Android等抢占了市场,不知道你有没有用过诺基亚等老式手机?还记得Java 2D游戏吗?
-3. Android(Andorid不是编程语言)虽然在2017年Google将Kotlin正式列为官方支持开发语言,但Java目前仍是其主要开发语言,原因是Android的底层有大量的Java API(可延伸阅读Oracle与Google的官司大战)。
-:::
-
-
-
-
-::: tip 《深入理解Java虚拟机》
-Java不仅仅是一门编程语言,还是一个由一系列计算机软件和规范形成的技术体系,这个技术体系提供了完整的用于软件开发和跨平台部署的支持环境,并**广泛应用于嵌入式系统、移动终端、企业服务器、大型机等各种场合** 。时至今日,Java技术体系已经吸引了900多万软件开发者,这是全球最大的软件开发团队。使用Java的设备多达几十亿台,其中包括11亿多台个人计算机、30亿部移动电话及其他手持设备、数量众多的智能卡,以及大量机顶盒、导航系统和其他设备。[1]
-:::
-
-## 参考文献
-
-[1]周志明. 深入理解Java虚拟机[M]. 第3版. 北京:机械工业出版社,2019
diff --git a/docs/courses/mybatis/01-MyBatis基础/01-快速入门.md b/docs/courses/mybatis/01-MyBatis基础/01-快速入门.md
deleted file mode 100644
index 6d47bfa6d..000000000
--- a/docs/courses/mybatis/01-MyBatis基础/01-快速入门.md
+++ /dev/null
@@ -1,255 +0,0 @@
----
-title: 快速入门
-author: 查尔斯
-date: 2020/12/25 14:49
-categories:
- - MyBatis快速入门
-tags:
- - MyBatis
- - ORM框架
----
-
-# 快速入门
-
-我们将通过一个简单的 Demo 来阐述 MyBatis 的强大功能,在此之前,笔者假设你已经:
-
-- 拥有 Java 开发环境以及相应 IDE(本 Demo 采用 Eclipse 作为IDE)
-- 熟悉 Java Web 开发流程
-- 熟悉至少一个关系型数据库(本 Demo 采用 MySQL 作为数据库)
-
-## 数据库准备
-
-现有一张 `User` 表,其表结构如下:
-
-| 主键 | 姓名 | 年龄 | 邮箱 |
-| :--: | :----: | :--: | :------------: |
-| 1 | Jone | 18 | Jone@126.com |
-| 2 | Jack | 20 | Jack@126.com |
-| 3 | Tom | 28 | Tom@126.com |
-| 4 | Sandy | 21 | Sandy@126.com |
-| 5 | Billie | 24 | Billie@126.com |
-
-其对应的数据库 结构 脚本如下:
-
-```sql
--- 创建并切换数据库
-CREATE DATABASE mybatis_demo_db;
-USE mybatis_demo_db;
-
--- 创建用户数据表
-CREATE TABLE `user` (
- `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
- `name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名',
- `age` int(11) NULL DEFAULT NULL COMMENT '年龄',
- `email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '邮箱',
- PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户表' ROW_FORMAT = Compact;
-```
-
-其对应的数据库 数据 脚本如下:
-
-```sql
--- 清空用户表数据
-TRUNCATE TABLE user;
-
--- 向用户表插入测试数据
-INSERT INTO `user` VALUES (1, 'Jone', 18, 'Jone@126.com');
-INSERT INTO `user` VALUES (2, 'Jack', 20, 'Jack@126.com');
-INSERT INTO `user` VALUES (3, 'Tom', 28, 'Tom@126.com');
-INSERT INTO `user` VALUES (4, 'Sandy', 21, 'Sandy@126.com');
-INSERT INTO `user` VALUES (5, 'Billie', 24, 'Billie@126.com');
-```
-
-## 下载依赖
-
-要使用 MyBatis 框架,第一步就是下载好 MyBatis 的 jar 包,我们可以从 [MyBatis](https://github.com/mybatis/mybatis-3/releases) 在 GitHub 上的开源地址下载。
-
-
-
-笔者下载了 MyBatis 的核心压缩包(mybatis-x.x.x.zip)及其源码包(mybatis-x-mybatis-x.x.x.zip)。
-
-
-
-解压开 **mybatis-3.5.6.zip** 压缩包,目录结构如下:
-
-
-
-::: tip 笔者说
-如果 GitHub 下载太慢,可以前往 [FastGit](https://hub.fastgit.org/mybatis/mybatis-3/releases/tag/mybatis-3.5.6) 进行下载,它是 GitHub 的镜像地址,网站界面等各方面与 GitHub 几乎一模一样。
-但是注意它仅仅是一个镜像网站,可以用于克隆或下载 GitHub 资源,但登录之类的功能是不可用的。
-:::
-
-## 创建项目
-
-下载好依赖之后,我们通过 Eclipse,创建一个动态 Web 项目,并将刚才下载的 jar 包和指定数据库驱动包添加到 WebConent\WEB-INF\lib 目录,效果如下:
-
-
-
-::: tip 笔者说
-本次我们不会使用到 Servlet API,所以创建一个普通 Java 工程也没问题。
-:::
-
-## 创建POJO类
-
-在 DAO 模式开发中,第一步就是要创建实体类,而在 MyBatis 项目中,实体类"弱化"为了 POJO,这种类型是专门用于和数据库做映射的 Java 类型,数据表中的列与 POJO 类型的属性一 一对应。
-
-```java
-package com.example.pojo;
-
-/**
- * 用户POJO类(它是Java和关系数据库表映射的类型)
- * @author Charles7c
- */
-public class User {
- private Long id;
- private String name;
- private Integer age;
- private String email;
- // 省略getter/setter方法
- // 省略toString方法
-}
-```
-
-::: tip 笔者说
-POJO(Plain Old Java Objects,普通老式 Java 对象)。一般来讲,将 POJO 简单理解为实体类也无伤大雅。
-:::
-
-## 创建SQL映射文件
-
-在 DAO 模式开发中,实体类创建完之后就是要编写 BaseDao、以及不同实体的 Dao 接口和 Dao 实现类。但这一切的繁琐过程,在现在都被 MyBatis 解决了。
-
-现在,我们只需要按照 MyBatis 的要求创建好一个编写 SQL 的映射文件,在映射文件中编写好数据库的 CRUD 操作即可。
-
-```xml
-
-
-
-
-
-
- SELECT * FROM `user`
-
-
-```
-
-::: tip 笔者说
-SQL 映射文件的命名风格为:POJO类名Mapper.xml,就像命名以前的 Dao接口 一样,你可以将 SQL 映射文件理解为是以前的 Dao 实现类。
-:::
-
-## 创建核心配置文件
-
-MyBatis 为我们简化了非常多的操作,但是一些必须由我们自定义的配置还是少不了的。在 classpath 下创建一个核心配置文件命名为:`mybatis-config.xml`。
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-## 添加日志配置文件
-
-在 MyBatis 中,采用的日志框架是 log4j,所以为了能够查看到日志输出,我们需要在 classpath 下添加一个 log4j.properties 文件。
-
-```
-###############################################################
-# 输出到控制台 #
-###############################################################
-# log4j.rootLogger日志输出类别和级别:只输出不低于该级别的日志信息DEBUG < INFO < WARN < ERROR < FATAL
-# DEBUG:日志级别 CONSOLE:输出位置自己定义的一个名字
-log4j.rootLogger=DEBUG,CONSOLE
-# 配置CONSOLE输出到控制台
-log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
-# 配置CONSOLE设置为自定义布局模式
-log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
-# 配置CONSOLE日志的输出格式 [demo] 2019-08-22 22:52:12,000 %r耗费毫秒数 %p日志的优先级 %t线程名 %C所属类名通常为全类名 %L代码中的行号 %x线程相关联的NDC %m日志 %n换行
-log4j.appender.CONSOLE.layout.ConversionPattern=[demo] %d{yyyy-MM-dd HH:mm:ss,SSS} - %-4r %-5p [%t] %C:%L %x - %m%n
-```
-
-## 测试
-
-当一切准备好之后,完整的项目目录结构如下:
-
-
-
-创建好一个单元测试类,测试一下:
-
-```java
-class TestMyBatis {
-
- @Test
- void testSelectList() throws IOException {
- // 1.从classpath加载核心配置文件,构建SqlSession工厂对象
- InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
- SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
-
- // 2.获取SqlSession对象
- try (SqlSession sqlSession = sqlSessionFactory.openSession()){
-
- // 3.执行SQL语句 根据要执行的SQL语句选择合适的API
- // p1:SQL语句唯一地址 (SQL映射文件的namespace值.SQL语句的id值)
- List userList = sqlSession.selectList("userMapper.selectList");
-
- // 4.遍历数据
- userList.forEach(System.out::println);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
-}
-```
-
-**控制台输出:**
-
-```
-User [id=1, name=Jone, age=18, email=Jone@126.com]
-User [id=2, name=Jack, age=20, email=Jack@126.com]
-User [id=3, name=Tom, age=28, email=Tom@126.com]
-User [id=4, name=Sandy, age=21, email=Sandy@126.com]
-User [id=5, name=Billie, age=24, email=Billie@126.com]
-```
-
-## 后记
-
-**C:** 好了,与 MyBatis 的第一次约会结束了。怎么样?约会体验如何?使用步骤是不是还挺简单的?
-
-虽然是在学习一个新技术,但是一定要时刻想想当初 DAO 模式你是怎么一个开发步骤,这样对比着会发现 MyBatis 就是在简化、优化原来的每个环节而已。根本上还是那么回事,多想想,脑子里就能留下使用思路。
-
-::: info 笔者说
-对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
-
-所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
-:::
diff --git a/docs/courses/mybatis/01-MyBatis基础/02-核心对象.md b/docs/courses/mybatis/01-MyBatis基础/02-核心对象.md
deleted file mode 100644
index 2631e3ae0..000000000
--- a/docs/courses/mybatis/01-MyBatis基础/02-核心对象.md
+++ /dev/null
@@ -1,185 +0,0 @@
----
-title: 核心对象
-author: 查尔斯
-date: 2020/12/25 20:02
-categories:
- - MyBatis快速入门
-tags:
- - MyBatis
- - ORM框架
----
-
-# 核心对象
-
-## 前言
-
-**C:** 在上一篇,笔者带大家对 MyBatis 做了一个快速入门,不知道你是否已经掌握了 MyBatis 的使用步骤呢?本篇,笔者将继续带你学习 MyBatis,掌握对上篇中三个核心对象的概念理解和使用。
-
-在 API 使用层面,MyBatis 的核心类型有三个,分别是:SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession。
-
-
-
-## SqlSessionFactoryBuilder
-
-SqlSessionFactoryBuilder 是 MyBatis 中应用构建者模式的一个类,它的作用就是用来 **读取 MyBatis 的核心配置文件信息,然后来构建一个 SqlSessionFactory 对象** 。
-
-对于 SqlSessionFactoryBuilder 这个类,使用完就没有什么价值了,所以它的生命周期只存在于方法体内即可,也就是作为一个局部变量存在。
-
-**以下是它的使用方式:**
-
-```java
-// Resources 类是 MyBatis 中的一个资源加载工具类
-// 1.从 classpath 下将 MyBatis 核心配置文件内容加载为一个输入流对象
-InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
-// 2.构建 SqlSessionFactory 对象
-SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
-```
-
-**以下是它的常用方法:**
-
-- build(InputStream inputStream) : SqlSessionFactory 通过字节输入流构建
-- build(Reader reader) : SqlSessionFactory 通过字符输入流构建
-- build(Configuration config) : SqlSessionFactory 通过Configuration构建
-- ....
-
-::: tip 笔者说
-因为 SqlSessionFactory 对象构建需要的配置参数很多,且不能保证每个参数都是正确的或者不能一次性得到构建所需的所有参数(有些参数是初始化时需要的,如数据源配置,延迟加载配置,事务配置等,有些是框架运行过程中需要的,如SQL映射等[1]),所以不能采用简单的 new 方式来创建对象。
-:::
-
-## SqlSessionFactory
-
-SqlSessionFactory 对象比较重要,它的作用是 **创建 SqlSession 对象** 。
-
-**以下是它的常用方法:**
-
-- openSession() : SqlSession 获取 SqlSession对象
-
- 该方法是我们最常用的方法,同时也是开启事务处理的(参见下方源码)
-
-- openSession(boolean autoCommit) : SqlSession 获取SqlSession对象,自行指定是否开启事务
-
- autoCommit 参数是用来指定是否开启自动提交的
-
- 如果 autoCommit 的值为 true,代表关闭事务处理。反之,代表开启事务处理。
-
-```java
-// DefaultSqlSessionFactory 是 SqlSessionFactory的实现类
-public class DefaultSqlSessionFactory implements SqlSessionFactory {
- // ...
- @Override
- public SqlSession openSession() {
- return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
- }
- @Override
- public SqlSession openSession(boolean autoCommit) {
- return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
- }
- // ...
-}
-```
-
-每一个 MyBatis 的应用程序都以一个 SqlSessionFactory 对象为核心。SqlSessionFactory 一旦被创建, **它的生命周期应该与应用的生命周期相同** ,所以在应用运行期间不要重复创建 SqlSessionFactory 对象。
-
-为此,我们可以使用单例模式来改造获取 SqlSessionFactory 对象的方式。
-
-```java
-/**
- * MyBatis 工具类
- * @author Charles7c
- */
-public class MyBatisUtils {
-
- // 私有化构造函数
- private MyBatisUtils() {}
-
- // 静态对象
- private static volatile SqlSessionFactory SQL_SESSION_FACTORY = null;
-
- /**
- * 获取 SqlSessionFactory 对象
- * @return 单例的 SqlSessionFactory 对象
- * @throws IOException /
- */
- public static SqlSessionFactory getSqlSessionFactory() throws IOException {
- if (SQL_SESSION_FACTORY == null) {
- // 同步锁
- synchronized(MyBatisUtils.class) {
- // 双重检测机制
- if (SQL_SESSION_FACTORY == null) {
- // 从 classpath 加载核心配置文件,构建 SqlSession 工厂对象
- InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
- SQL_SESSION_FACTORY = new SqlSessionFactoryBuilder().build(is);
- }
- }
- }
- return SQL_SESSION_FACTORY;
- }
-
-}
-```
-
-## SqlSession
-
-SqlSession 是 MyBatis 执行持久化操作的关键对象,类似于 JDBC 中的 Connection。 **SqlSession 对象包含了执行 SQL 所需的所有方法** ,它的底层封装了 JDBC 连接,可以用 SqlSession 实例来直接执行被映射的 SQL 语句。
-
-**以下是它的常用方法:**
-
-- insert(String statement, Object parameter) : int 增加操作
-- delete(String statement, Object parameter) : int 删除操作
-- update(String statement, Object parameter) : int 修改操作
-- selectOne(String statement) : T 单个查询
-- selectOne(String statement, Object parameter) : T 带参数单个查询
-- selectList(String statement) : List\ 集合查询
-- selectList(String statement, Object parameter) : List\ 带参数集合查询
-- commit() : void 提交事务
-- rollback() : void 回滚事务
-- getMapper(Class\ type) : T 获取Mapper接口(Mapper接口开发)
-- ...
-
-每个线程都应该有它自己的 SqlSession 实例, SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的HttpSession。如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。[2] **使用完 SqlSeesion 之后一定记得关闭,可以采用 finally 块或 try-with-resources** 。在 SqlSession 里可以执行多次 SQL 语句,但一旦关闭了 SqlSession 就需要重新创建。
-
-结合 SqlSessionFactory 的获取方式改造,下方是在 MyBatisUtils 工具类中又增加了 SqlSession 的获取方式改造。
-
-```java
-/**
- * 获取 SqlSession 对象(开启事务处理)
- * @return SqlSession 对象
- * @throws IOException /
- */
-public static SqlSession openSession() throws IOException {
- return getSqlSessionFactory().openSession();
-}
-```
-
-## 参考文献
-
-[1]哲雪君!. 第五篇 mybatis的运行原理(2):构建者模式, SqlSessionFactoryBuilder类解析[EB/OL]. https://www.cnblogs.com/zhexuejun/p/11285206.html. 2019-08-14
-
-[2]MyBatis官网. MyBatis 入门 | 作用域(Scope)和生命周期[EB/OL]. https://mybatis.org/mybatis-3/zh/getting-started.html. 2020-12-26
-
-## 后记
-
-到此为止,笔者就介绍完了在使用 MyBatis 时,所遇到的三个核心对象。同样经过上述对这些核心对象使用的改造后,我们也看一下到底它优化到了什么程度,开开眼吧。
-
-```java
-@Test
-void testSelectList() throws IOException {
-
- // 获取SqlSession对象
- try (SqlSession sqlSession = MyBatisUtils.openSession()){
-
- // 执行SQL语句
- List userList = sqlSession.selectList("userMapper.selectList");
-
- // 遍历数据
- userList.forEach(System.out::println);
- } catch (Exception e) {
- e.printStackTrace();
- }
-}
-```
-
-::: info 笔者说
-对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
-所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
-:::
diff --git a/docs/courses/mybatis/01-MyBatis基础/03-核心配置文件.md b/docs/courses/mybatis/01-MyBatis基础/03-核心配置文件.md
deleted file mode 100644
index fa2a29344..000000000
--- a/docs/courses/mybatis/01-MyBatis基础/03-核心配置文件.md
+++ /dev/null
@@ -1,484 +0,0 @@
----
-title: 核心配置文件
-author: 查尔斯
-date: 2020/12/26 14:48
-categories:
- - MyBatis快速入门
-tags:
- - MyBatis
- - ORM框架
----
-
-# 核心配置文件
-
-## 前言
-
-**C:** 在上一篇,笔者带大家对 MyBatis 的核心对象做了介绍。本篇,笔者将继续带你学习 MyBatis,掌握对核心配置文件的使用。
-
-MyBatis 的核心/全局配置文件 mybatis-config.xml ,顾名思义就是对 MyBatis 系统的核心设置文件。包含有 MyBatis 运行时行为配置、类型别名配置、环境配置等。
-
-下方是核心配置文件的标签模板,笔者将对其中常用的一些标签的常用使用方式进行介绍。
-
-configuration 根节点
-- **properties** 属性配置
-
-- settings 运行时行为配置
-- **typeAliases** 类型别名配置
-- typeHandlers 类型处理器
-
-- objectFactory 对象工厂
-- plugins 插件配置
-- **environments** 环境配置
- - environment 单个环境配置
- - transactionManager 事务管理器配置
- - dataSource 数据源配置
-- databaseIdProvider 数据库厂商标识
-- **mappers** 映射器配置
-
-::: tip 笔者说
-这些标签在使用时一定要注意标签的顺序和允许使用次数。Eclipse 中可以通过在标签上按 F2 查看该标签下的内容模型,即标签的顺序和允许使用次数。你看下图中画圈处就是各个标签的顺序,后面的 ?号 代表指定标签最多允许使用一次。
-:::
-
-
-## properties元素
-
-如果你学过 Maven,那 properties 元素应该不难理解。在 MyBatis 的核心配置文件中,有很多配置是可能经常需要变动或复用的,如果直接将值硬编码在对应位置,将不利于统一维护管理和复用。
-
-properties 元素的作用就体现出来了,它的使用方式有两种。
-
-### 内部编写
-
-**第一种使用方式,是内部编写配置** ,示例如下:
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-### 外部引入
-
-**第二种使用方式,是在外部配置文件编写配置,然后通过 properties 引入外部配置** ,示例如下:
-
-在 classpath 下 添加 一个 properties 配置文件,记录各种配置信息。(此处笔者记录的是数据源信息)
-
-```
-# MySQL
-mysql.driver=com.mysql.jdbc.Driver
-mysql.url=jdbc:mysql://localhost:3306/mybatis_demo_db
-mysql.username=root
-mysql.password=root
-```
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-::: tip 笔者说
-大家猜一下,如果这两种方式在同时使用时遇到了相同配置,那么哪种方式的配置会生效呢?
-
-测试思路: 可以先故意改错内部配置方式的 mysql.password 值,如果测试运行正常,说明外部配置生效了,反之则内部配置生效了。 也可以再故意调错外部的值试试。
-:::
-
-```xml
-
-
-
-
-```
-
-## settings元素(下篇讲解)
-
-settings 元素是用来设置和改变 MyBatis 在运行时的一些行为的。
-
-| **设置项** | **描述** | **允许值** | **默认值** |
-| ------------------- | :----------------------------------------------------------: | :-------------------: | :--------: |
-| cacheEnabled | 对在此配置文件下的所有cache进行全局性开/关设置 | true \| false | true |
-| lazyLoadingEnabled | 全局性设置懒加载。如果设为false,则所有相关联的都会被初始化加载 | true \| false | true |
-| autoMappingBehavior | MyBatis对于resultMap自动映射匹配级别 | NONE\|PARTIAL \|FULL | PARTIAL |
-| **……(9个)** | **......** | **......** | **......** |
-
-## typeAlias元素
-
-在 SQL 映射文件中,我们在使用到某些类型时,需要编写好对应的全类名,大量的使用时,繁琐不说还容易错,如下 resultType 属性示例。
-
-```xml
-
- SELECT * FROM `user`
-
-```
-
-而typeAlias 元素就可以解决此问题,通过它的配置,可以为指定类型配置好别名,这样在 SQL 映射文件中就可以不用写全限定类名,而是直接使用配置的类型别名了。它的使用方式也有两种。
-
-### 单个配置
-
-**第一种使用方式:挨个对不同类型进行别名配置。**
-
-```xml
-
-
-
-
-```
-
-### 包扫描
-
-**第二种使用方式:当要配置别名的类型都在指定的 package 下时,可以直接开启包扫描,批量实现别名自动配置。**
-
-```xml
-
-
-
-
-
-```
-
-### 使用效果
-
-下方是有了类型别名配置之后,SQL 映射文件内使用类型的效果。
-
-```xml
-
-
- SELECT * FROM `user`
-
-```
-
-::: tip 笔者说
-在 MyBatis 中有一个类 TypeAliasRegistry ,它的作用就是进行类型别名注册和解析,Java 中常见的类型都已经被它注册好了别名。
-:::
-
-```java
-package org.apache.ibatis.type;
-// ...略...
-public class TypeAliasRegistry {
- /** Map<类型别名, 对应类型的Class对象> */
- private final Map> typeAliases = new HashMap<>();
- /** 在创建对象时进行常用 Java 类型的别名注册 */
- public TypeAliasRegistry() {
- // String 类型注册的别名为 string
- registerAlias("string", String.class);
-
- registerAlias("byte", Byte.class);
- registerAlias("long", Long.class);
- registerAlias("short", Short.class);
- registerAlias("int", Integer.class);
- registerAlias("integer", Integer.class);
- registerAlias("double", Double.class);
- registerAlias("float", Float.class);
- registerAlias("boolean", Boolean.class);
-
- registerAlias("byte[]", Byte[].class);
- registerAlias("long[]", Long[].class);
- registerAlias("short[]", Short[].class);
- registerAlias("int[]", Integer[].class);
- registerAlias("integer[]", Integer[].class);
- registerAlias("double[]", Double[].class);
- registerAlias("float[]", Float[].class);
- registerAlias("boolean[]", Boolean[].class);
-
- registerAlias("_byte", byte.class);
- registerAlias("_long", long.class);
- registerAlias("_short", short.class);
- registerAlias("_int", int.class);
- registerAlias("_integer", int.class);
- registerAlias("_double", double.class);
- registerAlias("_float", float.class);
- registerAlias("_boolean", boolean.class);
-
- registerAlias("_byte[]", byte[].class);
- registerAlias("_long[]", long[].class);
- registerAlias("_short[]", short[].class);
- registerAlias("_int[]", int[].class);
- registerAlias("_integer[]", int[].class);
- registerAlias("_double[]", double[].class);
- registerAlias("_float[]", float[].class);
- registerAlias("_boolean[]", boolean[].class);
-
- registerAlias("date", Date.class);
- registerAlias("decimal", BigDecimal.class);
- registerAlias("bigdecimal", BigDecimal.class);
- registerAlias("biginteger", BigInteger.class);
- registerAlias("object", Object.class);
-
- registerAlias("date[]", Date[].class);
- registerAlias("decimal[]", BigDecimal[].class);
- registerAlias("bigdecimal[]", BigDecimal[].class);
- registerAlias("biginteger[]", BigInteger[].class);
- registerAlias("object[]", Object[].class);
-
- registerAlias("map", Map.class);
- registerAlias("hashmap", HashMap.class);
- registerAlias("list", List.class);
- registerAlias("arraylist", ArrayList.class);
- registerAlias("collection", Collection.class);
- registerAlias("iterator", Iterator.class);
-
- registerAlias("ResultSet", ResultSet.class);
- }
-
- /**
- * 解析别名
- * @param string 要解析的别名
- * @return 该别名对应的类型的Class对象
- */
- public Class resolveAlias(String string) {
- try {
- if (string == null) {
- return null;
- }
- // MyBatis 在【别名自动配置】和【解析映射文件中别名】时,对别名进行了小写转换。
- // 所以在使用别名的时候才不区分大小写。
- String key = string.toLowerCase(Locale.ENGLISH);
- Class value;
- if (typeAliases.containsKey(key)) {
- value = (Class) typeAliases.get(key);
- } else {
- value = (Class) Resources.classForName(string);
- }
- return value;
- } catch (ClassNotFoundException e) {
- throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e);
- }
- }
- // ...略...
-}
-```
-
-## environments元素
-
-MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中,现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。
-
-**不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。**
-
-所以,如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例,依此类推,记起来很简单:
-
-- **每个数据库对应一个 SqlSessionFactory 实例** [1]
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-## mappers元素
-
-mappers 元素的作用就是用来告诉 MyBatis 去哪找我们编写的 SQL 语句,它的使用方式有两大类。
-
-### 指定映射文件
-
-这类方式主要是告诉 MyBatis 我们所编写的 SQL 映射文件的地址,我们之前在 [《快速入门》](./01-快速入门) 中使用的就是属于这类方式。它有两种实现:
-
-```xml
-
-
-
-
-```
-
-```xml
-
-
-
-
-```
-
-### 指定Mapper接口[重要]
-
-这类方式还要涉及到 SqlSession 对象的另一个使用形式:**Mapper接口开发**
-
-我们之前使用 SqlSession 是让它来直接执行指定的 SQL 语句。这种方式需要指明 SQL 映射文件 namespace的名字以及 SQL 语句的 id。因为是硬编码在代码中,维护时有诸多不便,例如:容易写错不说,还不利于我们在持久层采用面向接口编程思想。
-
-```java
-// 执行 SQL 语句
-List userList = sqlSession.selectList("userMapper.selectList");
-```
-
-而 Mapper 接口开发就可以有效解决此问题,实现方式如下:
-
-**第一步:先创建一个 Mapper 接口。** 前期就养成一个开发习惯,保持 Mapper 接口和对应 SQL 映射文件同名同包(虽然目前不同名也没事,但是先听话,养成习惯)。
-
-```java
-public interface UserMapper {
-
- /**
- * 查询用户列表
- * @return
- */
- List selectList();
-
-}
-```
-
-
-
-**第二步:将 SQL 映射文件的 namespace 值改为对应 Mapper 接口的全限定类名。**
-
-```xml
-
-
-
-
-```
-
-**第三步:将 SQL 映射文件中的 SQL 语句和 Mapper 接口中的方法进行绑定。**
-
-```xml
-
-
-
-
-
- SELECT * FROM `user`
-
-
-```
-
-**最后,我们在核心配置文件中,再配置好 Mapper 接口的全限定类名即可。**
-
-```xml
-
-
-
-
-```
-
-**测试一下。**
-
-```java
-@Test
-void testSelectList() throws IOException {
-
- // 获取SqlSession对象
- try (SqlSession sqlSession = MyBatisUtils.openSession()){
-
- // 获取 Mapper 接口,而不再直接执行 SQL 语句
- // 和以前 DAO 模式就非常相像了,只不过是 DAO 实现类变为了 SQL 映射文件
- UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
- // 执行方法
- List userList = userMapper.selectList();
-
- // 遍历数据
- userList.forEach(System.out::println);
- } catch (Exception e) {
- e.printStackTrace();
- }
-}
-```
-
-另外,在核心配置文件中,大量的配置 Mapper 接口全限定类名,还是有些麻烦,所以 MyBatis 在这也支持包扫描配置。
-
-```xml
-
-
-
-
-```
-
-::: warning 笔者说
-Mapper 接口开发是我们以后主要使用的方式,必须掌握!
-:::
-
-## 参考文献
-
-[1]MyBatis 官网. MyBatis 配置[EB/OL]. https://mybatis.org/mybatis-3/zh/configuration.html. 2020-12-26
-
-## 后记
-
-::: info 笔者说
-对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
-
-所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
-:::
diff --git a/docs/courses/mybatis/01-MyBatis基础/04-SQL映射文件之查询元素.md b/docs/courses/mybatis/01-MyBatis基础/04-SQL映射文件之查询元素.md
deleted file mode 100644
index df4696df3..000000000
--- a/docs/courses/mybatis/01-MyBatis基础/04-SQL映射文件之查询元素.md
+++ /dev/null
@@ -1,519 +0,0 @@
----
-title: SQL映射文件之查询元素
-author: 查尔斯
-date: 2020/12/27 23:35
-categories:
- - MyBatis快速入门
-tags:
- - MyBatis
- - ORM框架
----
-
-# SQL映射文件之查询元素
-
-## 前言
-
-**C:** 在上一篇,笔者带大家对 MyBatis 的核心配置文件做了介绍。本篇开始,笔者将带你学习 MyBatis 的 SQL 映射文件,它是 MyBatis 中亮点最多的部分(翻回去看看 MyBatis 特点,主要优势都在这儿),同时也是未来我们使用 MyBatis 开发时接触最多的部分。
-
-不过你也别担心,MyBatis 在 SQL 语句映射方面异常强大,但 SQL 映射文件却是相当简单。
-
-下方是 SQL 映射文件的标签模板,笔者将花两三篇对其中常用的一些标签的常用使用方式进行介绍。
-
-**mapper** 根标签
-
-- cache-ref 引用其它命名空间的缓存配置
-- **cache** 配置给定命名空间的缓存
-- ***resultMap*** (自定义结果集映射配置)用来描述数据库结果集和对象的对应关系,是最复杂也是最强大的元素
-- ~~parameterMap~~ (自定义参数映射配置)此元素已被废弃,并可能在将来被移除!请使用行内参数映射。
-- **sql** 可以重用的 SQL 块
-- **insert** 映射插入语句
-- **update** 映射更新语句
-- **delete** 映射删除语句
-- **select** 映射查询语句
-
-
-
-## mapper元素
-
-mapper 元素是 SQL 映射文件的根标签,在该标签内有一个属性 namespace(命名空间),可以理解为当前 SQL 映射文件的标识。
-
-**传统 SqlSession 开发中** ,mapper 元素的 namespace 属性和下方子元素的 id 属性联合保证了 SQL 语句的唯一标识。
-
-```xml
-
-
-
-
- SELECT * FROM `user`
-
-
-```
-
-```java
-List userList = sqlSession.selectList("userMapper.selectList");
-```
-
-**SqlSession 的 Mapper 接口开发中** ,mapper 元素的 namespace 属性必须命名为对应的 Mapper 接口的全限定类名,下方子元素也要和对应 Mapper 接口中的方法一 一对应。
-
-```java
-package com.example.mapper;
-// ...略...
-public interface UserMapper {
-
- /**
- * 查询用户列表
- * @return /
- */
- List selectList();
-
-}
-```
-
-```xml
-
-
-
-
-
- SELECT * FROM `user`
-
-
-```
-
-::: tip 笔者说
-Mapper 接口开发是我们上篇中最后部分讲解过的 SqlSession 使用方式,以后也是主要的写法,很好理解,GKD掌握。
-:::
-
-## select元素
-
-在每一个项目中,查询都是应用最频繁也是应用最困难的部分。 在 SQL 映射文件中,select 元素就是用于编写查询 SQL 的,它是 MyBatis 中最常用的元素之一。
-
-select 元素有很多属性,可以很详细的来配置每条语句的行为细节。
-
-- **id** 命名空间中唯一的标识符
-
- Mapper 接口开发中,id 值需要和接口中对应方法的名字保持一致
-
-- **parameterType** 传入SQL语句的参数类型
-
- 可以为参数类型的全限定类名或别名
-
- Mapper接口开发中,parameterType 值需要和接口中对应方法的参数类型保持一致
-
-- **resultType** SQL语句返回值类型(详细解释见 resultMap 元素部分)
-
- 可以为返回值类型的全限定类名或别名
-
- Mapper接口开发中,resultType 值需要和接口中对应方法的返回值类型保持一致
-
- **注意:** 如果返回值类型是集合,那么 resultType 值应该表示为集合的泛型类型,而不是集合类型。
-
-接下来,笔者通过几个示例来带大家掌握下 select 元素的使用。
-
-::: tip 笔者说
-笔者只是介绍了使用最为频繁的几个属性,如果想了解更多的属性含义,可以前往[官网](https://mybatis.org/mybatis-3/zh/sqlmap-xml.html)查看。
-:::
-
-### 用户名查询
-
-在《快速入门》篇的数据库基础上,我们先来实现一个根据用户名的模糊查询。
-
-首先,在 Mapper 接口中我们添加一个方法。
-
-```java
-public interface UserMapper {
-
- /**
- * 根据用户名模糊查询
- * @param name 用户名
- * @return 用户列表
- */
- List selectByName(String name);
-
-}
-```
-
-然后我们在 SQL 映射文件中再添加一个与该方法对应的查询元素。
-
-```xml
-
-
-
- select * from user where name like concat('%', #{name}, '%')
-
-```
-
-测试一下:
-
-```java
-class TestMyBatis {
-
- @Test
- void testSelectByName() throws IOException {
- // 获取SqlSession对象
- try (SqlSession sqlSession = MyBatisUtils.openSession()) {
-
- // 获取 Mapper 接口
- UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
- // 执行 SQL
- List userList = userMapper.selectByName("J");
-
- // 遍历数据
- userList.forEach(System.out::println);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
-}
-```
-
-**控制台输出:**
-
-```sql
--- 输出的 SQL 语句
-select * from user where name like concat('%', ?, '%')
-```
-
-```
-User [id=1, name=Jone, age=18, email=Jone@126.com]
-User [id=2, name=Jack, age=20, email=Jack@126.com]
-```
-
-#### #{}和${}的区别
-
-在控制台输出的 MyBatis 日志中,我们可以看到最后执行的 SQL 就是我们在传统 JDBC 开发中,为了解决 SQL 注入攻击而编写的 SQL 形式。
-
-之所以输出如此,是因为我们使用了 #{} 的形式来使用参数,\#{}表示一个占位符号,可以接收简单类型值或 POJO 属性值,通过 #{} 可以实现 preparedStatement 向占位符中设置值,自动进行 Java 类型和 JDBC 类型转换。#{} 可以有效防止 SQL 注入。
-
-**注意:** #{} 占位符不能放置在字符串中,即:select * from user where name like '%#{name}%' 是错误的。
-
----
-
-其实在 MyBatis 中还有占位符:${},但是基本不使用,至于原因,我们试试看就知道了。
-
-```xml
-
-
-
- select * from user where name like '%${name}%'
-
-```
-
-**控制台输出:**
-
-```sql
--- 输出的 SQL 语句
-select * from user where name like '%J%'
-```
-
-这回知道原因了吧? **总结一下它们的区别(面试题)** :
-
-- 在使用`#{}`参数语法时,MyBatis 会创建 `PreparedStatement` 参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样)[1]
-- 在使用 `${}` 时,MyBatis 会将 SQL 中的 `${}` 替换成对应变量的值。适合需要直接插入一个不转义的字符串时使用。
-- 使用 #{} 可以有效的防止 SQL 注入,提高系统安全性。
-
-### 多参数查询
-
-我们也都知道,在 Java 中定义方法的时候,返回值类型只能设定为一个具体类型,但是方法的参数是可以定义 N 个的,那么在面对这种方法时,MyBatis 查询元素的 parameterType 该如何使用呢?
-
-其实也非常简单,**有三种方式比较流行** :
-
-- 将多个参数封装到 POJO / 自定义对象中
-- 将多个参数封装到 Map / List 集合中
-- 将多个参数通过 @Param 注解标注
-
-我们通过一个案例感受下不同方式的区别: **案例需求:根据用户名、年龄查询用户列表**
-
-#### 封装到POJO
-
-首先,在 Mapper 接口中我们添加一个方法。
-
-```java
-public interface UserMapper {
-
- /**
- * 根据用户名和年龄查询
- * @param user 用户信息
- * @return 用户列表
- */
- List selectByUser(User user);
-
-}
-```
-
-然后我们在 SQL 映射文件中再添加一个与该方法对应的查询元素。
-
-```xml
-
-
-
- select
- *
- from
- user
- where
- name like concat('%', #{name}, '%')
- and age = #{age}
-
-```
-
-测试一下:
-
-```java
-class TestMyBatis {
-
- @Test
- void testSelectByUser() throws IOException {
- // 获取SqlSession对象
- try (SqlSession sqlSession = MyBatisUtils.openSession()) {
-
- // 获取 Mapper 接口
- UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
- // 执行 SQL
- User user = new User();
- user.setName("J");
- user.setAge(20);
- List userList = userMapper.selectByUser(user);
-
- // 遍历数据
- userList.forEach(System.out::println);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
-}
-```
-
-**控制台输出:**
-
-```sql
--- 输出的 SQL 语句
-select * from user where name like concat('%', ?, '%') and age = ?
-```
-
-```
-User [id=2, name=Jack, age=20, email=Jack@126.com]
-```
-
-#### 封装到Map集合
-
-::: tip 笔者说
-笔者个人比较喜欢这一种,Map 集合总是那么"万金油"。
-:::
-
-首先,在 Mapper 接口中我们添加一个方法。
-
-```java
-public interface UserMapper {
-
- /**
- * 根据用户名和年龄查询
- * @param params 条件参数
- * @return 用户列表
- */
- List selectByMap(Map params);
-
-}
-```
-
-然后我们在 SQL 映射文件中再添加一个与该方法对应的查询元素。
-
-```xml
-
-
-
- select
- *
- from
- user
- where
- name like concat('%', #{name}, '%')
- and age = #{age}
-
-```
-
-测试一下:
-
-```java
-class TestMyBatis {
-
- @Test
- void testSelectByMap() throws IOException {
- // 获取SqlSession对象
- try (SqlSession sqlSession = MyBatisUtils.openSession()) {
-
- // 获取 Mapper 接口
- UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
- // 执行 SQL
- Map params = new HashMap<>();
- params.put("name", "J");
- params.put("age", 20);
- List userList = userMapper.selectByMap(params);
-
- // 遍历数据
- userList.forEach(System.out::println);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
-}
-```
-
-**控制台输出:**
-
-```sql
--- 输出的 SQL 语句
-select * from user where name like concat('%', ?, '%') and age = ?
-```
-
-```
-User [id=2, name=Jack, age=20, email=Jack@126.com]
-```
-
-#### @Param注解
-
-::: tip 笔者说
-@Param 注解与上述两种方式有些不同,如果你使用了该注解,那么 parameterType 就不需要再手动指定了,使用 **普通类型参数** (Java中的 int、double、String...... 这些都属于普通类型参数,而对象和集合就不属于普通类型参数了)的方法一般都推荐使用它。
-:::
-
-首先,在 Mapper 接口中我们添加一个方法。
-
-```java
-public interface UserMapper {
-
- /**
- * 根据用户名和年龄查询
- * @param name 用户名
- * @param age 年龄
- * @return 用户列表
- */
- // @Param("参数名") 注解中传入的参数名才是占位符要使用到的名字
- List selectByParam(@Param("name") String name, @Param("age") Integer age);
-
-}
-```
-
-然后我们在 SQL 映射文件中再添加一个与该方法对应的查询元素。
-
-```xml
-
-
-
- select
- *
- from
- user
- where
- name like concat('%', #{name}, '%')
- and age = #{age}
-
-```
-
-测试一下:
-
-```java
-class TestMyBatis {
-
- @Test
- void testSelectByParam() throws IOException {
- // 获取SqlSession对象
- try (SqlSession sqlSession = MyBatisUtils.openSession()) {
-
- // 获取 Mapper 接口
- UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
- // 执行 SQL
- List userList = userMapper.selectByParam("j", 20);
-
- // 遍历数据
- userList.forEach(System.out::println);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
-}
-```
-
-**控制台输出:**
-
-```sql
--- 输出的 SQL 语句
-select * from user where name like concat('%', ?, '%') and age = ?
-```
-
-```
-User [id=2, name=Jack, age=20, email=Jack@126.com]
-```
-
-::: warning 使用注意
-超过 3 个以上的 **普通类型参数** 最好封装成对象或 Map 来入参(特别是在常规的增加和修改操作时,字段较多,封装成对象比较方便),而参数固定的业务方法,最好使用 @Param 来入参(这种方法比较灵活,代码的可读性高,可以清晰看出来这个接口方法的所需的参数是什么,并且对于固定的接口方法,参数一般是固定的,所以可以使用直接参数入参即可,无需封装对象。例如:修改个人密码的方法,根据 id 删除用户的方法,根据 id 查看用户明细的方法,都可以采取这种方式)
-:::
-
-## sql元素
-
-在同一个 SQL 映射文件中,经常面临着重复 SQL 的问题,尤其是查询类 SQL 。效果如下:
-
-```xml
-
- select
- id, name, age, email
- from
- user
- where name like concat('%', #{name}, '%')
-
-```
-
-```xml
-
- select
- id, name, age, email
- from
- user
- where id = #{id}
-
-```
-
-使用 sql 元素可以让我们得以复用一些 SQL语句。
-
-```xml
-
-
- select
- id, name, age, email
- from
- user
-
-
-
-
-
- where name like concat('%', #{name}, '%')
-
-
-
-
- where id = #{id}
-
-```
-
-## 参考文献
-
-[1]MyBatis 官网. XML 映射文件[EB/OL]. https://mybatis.org/mybatis-3/zh/sqlmap-xml.html. 2020-12-26
-
-## 后记
-
-本篇中,select 元素是重点,笔者列了好多个示例,你一定要将示例代码完整"临摹" + "思考"一遍,这样才能达到笔者所说的技术学习的第二步、第三步。
-
-::: info 笔者说
-对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
-
-所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
-:::
-
diff --git a/docs/courses/mybatis/01-MyBatis基础/05-SQL映射文件之增删改元素.md b/docs/courses/mybatis/01-MyBatis基础/05-SQL映射文件之增删改元素.md
deleted file mode 100644
index f05a253f0..000000000
--- a/docs/courses/mybatis/01-MyBatis基础/05-SQL映射文件之增删改元素.md
+++ /dev/null
@@ -1,342 +0,0 @@
----
-title: SQL映射文件之增删改元素
-author: 查尔斯
-date: 2020/12/27 23:55
-categories:
- - MyBatis快速入门
-tags:
- - MyBatis
- - ORM框架
----
-
-# SQL映射文件之增删改元素
-
-## 前言
-
-**C:** 在上一篇,笔者带大家对 MyBatis SQL 映射文件的 select 元素、sql 元素进行了学习。本篇,笔者将带你学习 MyBatis SQL 映射文件中的 insert、update、delete元素,这三个可以说是 SQL 映射文件中最为简单的,别愣神,快跟上我。
-
-
-
-## insert元素
-
-**案例需求:新增用户,PeiQi,18,PeiQi@126.com**
-
-首先,在 Mapper 接口中我们添加一个方法。
-
-```java
-public interface UserMapper {
-
- /**
- * 添加用户
- * @param user 用户信息
- * @return 影响行数
- */
- int insert(User user);
-
-}
-```
-
-::: tip 笔者说
-insert、update、delete 这类操作,本身默认就是返回影响的行数,所以不需要对 resultType 进行指定。在定义这类接口方法的时候设置返回值类型为 int 即可。
-:::
-
-然后我们在 SQL 映射文件中再添加一个与该方法对应的查询元素。
-
-```xml
-
-
- insert into user (name, age, email) values(#{name}, #{age}, #{email})
-
-```
-
-测试一下:
-
-```java
-class TestMyBatis {
-
- @Test
- void testInsert() {
- // 获取SqlSession对象
- try (SqlSession sqlSession = MyBatisUtils.openSession()) {
-
- // 获取 Mapper 接口
- UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
- // 执行 SQL
- User user = new User();
- user.setName("PeiQi");
- user.setAge(18);
- user.setEmail("PeiQi@126.com");
- int rows = userMapper.insert(user);
-
- System.out.println("影响行数为:" + rows);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-}
-```
-
-**控制台输出:**
-
-```sql
--- 输出的 SQL 语句
-insert into user (name, age, email) values(?, ?, ?)
-```
-
-```
-影响行数为:1
-```
-
----
-
-输出的结果一如既往的表示成功了,但是当你前往数据库查看时,却并没有新增数据。
-
-
-
-仔细看看执行日志吧!相比于查询元素,插入元素执行多了一行日志,大白话理解就是:**JDBC 连接正在回滚事务** 。
-
-
-
-这是因为我们在获取 SqlSession 的时候,采用的是开启事务的方式。开启事务对于查询没什么大影响,但是对于增删改,如果你不提交事务,就意味着不会将数据持久化到数据库。
-
-所以改动一下测试代码吧:
-
-```java
-class TestMyBatis {
- @Test
- void testInsert() {
- // 获取SqlSession对象
- try (SqlSession sqlSession = MyBatisUtils.openSession()) {
-
- // 获取 Mapper 接口
- UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
- // 执行 SQL
- User user = new User();
- user.setName("PeiQi");
- user.setAge(18);
- user.setEmail("PeiQi@126.com");
- int rows = userMapper.insert(user);
-
- System.out.println("影响行数为:" + rows);
-
- // 提交事务
- sqlSession.commit();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-}
-```
-
-这回测试之后就正常了。
-
-
-
-### 获取自增主键值
-
-我们在一些支持自动生成主键的数据库中设置了主键自增,当数据插入之后,我们可能需要用到刚生成的主键值,这时候传统的方法是自己去手动查询一次,而 MyBatis 则是通过在插入元素上添加几个属性就可以解决了。
-
-```xml
-
-
-
- insert into user (name, age, email) values(#{name}, #{age}, #{email})
-
-```
-
-测试一下:
-
-```java
-class TestMyBatis {
-
- @Test
- void testInsert() {
- // 获取SqlSession对象
- try (SqlSession sqlSession = MyBatisUtils.openSession()) {
-
- // 获取 Mapper 接口
- UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
- // 执行 SQL
- User user = new User();
- user.setName("PeiQi");
- user.setAge(18);
- user.setEmail("PeiQi@126.com");
- System.out.println("插入前:" + user);
- int rows = userMapper.insert(user);
- System.out.println("影响行数为:" + rows);
- System.out.println("插入后:" + user);
-
- // 提交事务
- sqlSession.commit();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
-}
-```
-
-**控制台输出:**
-
-```
-插入前:User [id=null, name=PeiQi, age=18, email=PeiQi@126.com]
-影响行数为:1
-插入后:User [id=8, name=PeiQi, age=18, email=PeiQi@126.com]
-```
-
-## update元素
-
-再来试试 update 元素。**案例需求:将id为1的用户年龄改为20。**
-
-首先,在 Mapper 接口中我们添加一个方法。
-
-```java
-public interface UserMapper {
-
- /**
- * 修改用户
- * @param user 用户信息
- * @return 影响行数
- */
- int update(User user);
-
-}
-```
-
-然后我们在 SQL 映射文件中再添加一个与该方法对应的查询元素。
-
-```xml
-
-
- update user set age = #{age} where id = #{id}
-
-```
-
-测试一下:
-
-```java
-class TestMyBatis {
-
- @Test
- void testUpdate() {
- // 获取SqlSession对象
- try (SqlSession sqlSession = MyBatisUtils.openSession()) {
-
- // 获取 Mapper 接口
- UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
- // 执行 SQL
- User user = new User();
- user.setId(1L);
- user.setAge(20);
- int rows = userMapper.update(user);
-
- System.out.println("影响行数为:" + rows);
-
- // 提交事务
- sqlSession.commit();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
-}
-```
-
-**控制台输出:**
-
-```sql
--- 输出的 SQL 语句
-update user set age = ? where id = ?
-```
-
-```
-影响行数为:1
-```
-
-## delete元素
-
-再来试试 delete 元素。**案例需求:将id为1的用户删除。**
-
-首先,在 Mapper 接口中我们添加一个方法。
-
-```java
-public interface UserMapper {
-
- /**
- * 根据ID删除用户
- * @param id 用户ID
- * @return 影响行数
- */
- int deleteById(@Param("id") Long id);
-
-}
-```
-
-然后我们在 SQL 映射文件中再添加一个与该方法对应的查询元素。
-
-```xml
-
-
- delete from user where id = #{id}
-
-```
-
-测试一下:
-
-```java
-class TestMyBatis {
-
- @Test
- void testDeleteById() {
- // 获取SqlSession对象
- try (SqlSession sqlSession = MyBatisUtils.openSession()) {
-
- // 获取 Mapper 接口
- UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
- // 执行 SQL
- int rows = userMapper.deleteById(1L);
-
- System.out.println("影响行数为:" + rows);
-
- // 提交事务
- sqlSession.commit();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
-}
-```
-
-**控制台输出:**
-
-```sql
--- 输出的 SQL 语句
-delete from user where id = ?
-```
-
-```
-影响行数为:1
-```
-
-## 参考文献
-
-[1]MyBatis 官网. XML 映射文件[EB/OL]. https://mybatis.org/mybatis-3/zh/sqlmap-xml.html. 2020-12-26
-
-## 后记
-
-你就看看,它们使用起来简单不?这还能有理由说学不动、学不会吗?
-
-::: info 笔者说
-对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
-
-所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
-:::
-
diff --git a/docs/courses/mybatis/01-MyBatis基础/06-SQL映射文件之自定义映射元素.md b/docs/courses/mybatis/01-MyBatis基础/06-SQL映射文件之自定义映射元素.md
deleted file mode 100644
index 3727e23a7..000000000
--- a/docs/courses/mybatis/01-MyBatis基础/06-SQL映射文件之自定义映射元素.md
+++ /dev/null
@@ -1,397 +0,0 @@
----
-title: SQL映射文件之自定义映射元素
-author: 查尔斯
-date: 2020/12/28 00:07
-categories:
- - MyBatis快速入门
-tags:
- - MyBatis
- - ORM框架
----
-
-# SQL映射文件之自定义映射元素
-
-## 前言
-
-**C:** 在上一篇,笔者带大家对 MyBatis SQL 映射文件的 insert、update、delete元素做了介绍。到此,CRUD 的基本操作我们就介绍完了。本篇,笔者将带你学习 MyBatis SQL 映射文件的 resultMap 元素,它是 MyBatis 中号称"最强"的元素。有多强?容易令人头秃。
-
-
-
-## resultMap元素
-
-要介绍 resultMap 元素,那必然要先详细提一下 resultType 属性。
-
-### resultType属性
-
-在 select 元素中,我们一直在使用 resultType 属性,我们可以用它来指定 SQL 查询完后的 ResultSet(结果集)到底映射为哪种类型。
-
-下方的示例中,resultType 代表的就是将查询回来的所有结果数据映射为User类型的对象。
-
-```xml
-
- select
- id, name, age, email
- from
- user
- where
- id = #{id}
-
-```
-
-MyBatis 会按照 SQL 查询出的结果数据的列名或别名来映射。
-
-
-
-如果列名和属性名不能匹配上,可以在 SELECT 语句中设置列别名来完成匹配,效果如下。
-
-```xml
-
- select
- id, name as username, age, email
- where
- id = #{id}
-
-```
-
-
-
-### 简单自定义映射
-
-在学习了上面的知识后,你会发现上面的例子没有一个需要显式配置 `ResultMap`,这就是 `ResultMap` 的优秀之处:你完全可以不用显式地配置它们。
-
-::: tip 笔者说
-实际上 `resultType` 属性的实现原理就是 `ResultMap`, MyBatis 在幕后会自动创建一个 `ResultMap`,再根据属性名来映射列到 JavaBean 的属性上。所以记得注意 **二者不能同时存在** 。
-:::
-
-虽然上面的例子不用显式配置 `ResultMap`,但为了讲解,我们来看看如果在刚刚的示例中,显式使用外部的 `resultMap` 会怎样,这也是解决列名不匹配的另外一种方式。[1]
-
-```xml
-
-
-
-
-
-
-
-
-
- select
- id, name, age, email
- from
- user
- where id = #{id}
-
-```
-
-::: tip 笔者说
-在简单自定义映射时,我们仅仅需要对结果集列名和类属性名不一致的情况作映射规则指定,其他一致的,MyBatis 依然可以帮助我们自动映射好。
-但是当出现复杂的自定义映射时,MyBatis 将会进入映射"罢工"状态,未指明自定义映射规则的部分将不再进行自动映射,看看下方的例子吧。
-:::
-
-### 复杂自定义映射[难点]
-
-除了上述简单的自定义映射元素外,在 resultMap 元素中,还有两个用于进行复杂映射的子元素(多表操作):
-
-- **association** 映射到 JavaBean 的某个“复杂类型”属性,例如:JavaBean类
-
-- **collection** 映射到 JavaBean 的某个“复杂类型”属性,例如:集合
-
-我们通过两个案例分别体会一下这两个子元素。
-
-#### association案例
-
-**案例需求:根据ID查询用户信息,同时将该用户的角色信息也查询出来。**
-
-刚才我们做了那么多练习,数据库搞的太乱了,**我们先重置回 MyBatis 第一篇的数据库** ,并做一些数据库上的调整。
-
-```sql
--- 创建并初始化数据表 role
-CREATE TABLE `role` (
- `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
- `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色名',
- PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色表' ROW_FORMAT = Compact;
-
-INSERT INTO `role` VALUES (1, '超级管理员');
-INSERT INTO `role` VALUES (2, '普通管理员');
-
--- 为 user 表添加 roleId 列
-ALTER TABLE `user` ADD COLUMN `roleId` bigint(0) NULL COMMENT '角色id';
--- 为 user 表做一些角色修改
-UPDATE user SET roleId = 1 WHERE id BETWEEN 1 AND 3;
-UPDATE user SET roleId = 2 WHERE id > 3;
-```
-
-根据数据库改动,创建及改动 POJO:
-
-```java
-/**
- * 角色 POJO
- * @author Charles7c
- */
-public class Role {
- private Long id;
- private String name;
- // 省略 getter/setter 、toString
-}
-```
-
-```java
-/**
- * 用户 POJO
- * @author Charles7c
- */
-public class User {
- private Long id;
- private String name;
- private Integer age;
- private String email;
- private Long roleId;
- // 角色对象属性
- private Role role;
- // 省略 getter/setter 、toString
-}
-```
-
-改造好数据库之后,我们直接来改造一下 SQL 映射文件中的对应查询。
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
- select
- u.*,
- r.id as rid,
- r.name as rname
- from
- user u
- left join role r on u.roleId = r.id
- where u.id = #{id}
-
-```
-
-
-
-测试一下:
-
-```java
-class TestMyBatis {
-
- @Test
- void testSelectById() {
- // 获取SqlSession对象
- try (SqlSession sqlSession = MyBatisUtils.openSession()) {
-
- // 获取 Mapper 接口
- UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
- // 执行 SQL
- User user = userMapper.selectById(2L);
-
- System.out.println(user);
- System.out.println(user.getRole());
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
-}
-```
-
-**控制台输出:**
-
-```sql
--- 输出的 SQL 语句
-select u.*, r.id as rid, r.name as rname from user u left join role r on u.roleId = r.id where u.id = ?
-```
-
-```
-User [id=2, name=null, age=null, email=null]
-Role [id=1, name=超级管理员]
-```
-
----
-
-看看这结果,MyBatis 映射是不是"罢工"了?解决方法有两种:
-
-1. 挨个的把所有结果集列与对应类属性映射好
-
-2. 在 MyBatis 核心配置文件中,更改自动映射的默认级别
-
- ```xml
-
-
-
-
- ```
-
- 再来试试,看看控制台输出的结果。
-
- ```
- User [id=2, name=Jack, age=20, email=Jack@126.com]
- Role [id=1, name=超级管理员]
- ```
-
-#### collection案例
-
-**案例需求:根据ID查询用户信息,同时将该用户的联系人列表也查询出来。**
-
-我们再来做一些数据库上的调整。
-
-```sql
--- 创建并初始化数据表 linkuser
-CREATE TABLE `linkuser` (
- `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
- `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '联系人名',
- `phone` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '联系电话',
- `address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '联系地址',
- `userId` bigint(20) NULL DEFAULT NULL COMMENT '用户id',
- PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '联系人表' ROW_FORMAT = Compact;
-
-INSERT INTO `linkuser` VALUES (1, '张三', '18822233311', '北京西城区', 2);
-INSERT INTO `linkuser` VALUES (2, '李四', '18822233322', '北京东城区', 2);
-```
-
-根据数据库改动,创建及改动 POJO:
-
-```java
-/**
- * 联系人 POJO
- *
- * @author Charles7c
- */
-public class LinkUser {
- private Long id;
- private String name;
- private String phone;
- private String address;
- private Long userId;
- // 省略 getter/setter 、toString
-}
-```
-
-```java
-/**
- * 用户 POJO
- *
- * @author Charles7c
- */
-public class User {
- private Long id;
- private String name;
- private Integer age;
- private String email;
- private Long roleId;
- // 角色对象属性
- private Role role;
- // 联系人列表
- private List linkUserList;
- // 省略 getter/setter 、toString
-}
-```
-
-改造好数据库之后,我们直接来改造一下 SQL 映射文件中的对应查询。
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
- select
- u.*,
- lku.id as lkuid,
- lku.name as lkuname,
- lku.phone as lkuphone,
- lku.address as lkuaddress
- from
- user u
- left join linkuser lku on u.id = lku.userId
- where
- u.id = #{id}
-
-```
-
-
-
-测试一下:
-
-```java
-class TestMyBatis {
-
- @Test
- void testSelectById() {
- // 获取SqlSession对象
- try (SqlSession sqlSession = MyBatisUtils.openSession()) {
-
- // 获取 Mapper 接口
- UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
- // 执行 SQL
- User user = userMapper.selectById(2L);
-
- System.out.println(user);
-
- user.getLinkUserList().forEach(System.out::println);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
-}
-```
-
-**控制台输出:**
-
-```sql
--- 输出的 SQL 语句
-select u.*, lku.id as lkuid, lku.name as lkuname, lku.phone as lkuphone, lku.address as lkuaddress from user u left join linkuser lku on u.id = lku.userId where u.id = ?
-```
-
-```
-User [id=2, name=Jack, age=20, email=Jack@126.com]
-LinkUser [id=1, name=张三, phone=18822233311, address=北京西城区, userId=null]
-LinkUser [id=2, name=李四, phone=18822233322, address=北京东城区, userId=null]
-```
-
-## 参考文献
-
-[1]MyBatis 官网. XML 映射文件[EB/OL]. https://mybatis.org/mybatis-3/zh/sqlmap-xml.html. 2020-12-26
-
-## 后记
-
-大多数人在学到复杂自定义映射时都容易犯迷糊,所以笔者说过如果要学习 Hibernate 框架,开局容易深入难,因为 Hibernate 这框架中经常要处理类似的映射,年轻人慢慢来,加油!
-
-::: info 笔者说
-对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
-
-所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
-:::
\ No newline at end of file
diff --git a/docs/courses/mybatis/01-MyBatis基础/07-SQL映射文件之缓存元素.md b/docs/courses/mybatis/01-MyBatis基础/07-SQL映射文件之缓存元素.md
deleted file mode 100644
index d1fc980d6..000000000
--- a/docs/courses/mybatis/01-MyBatis基础/07-SQL映射文件之缓存元素.md
+++ /dev/null
@@ -1,218 +0,0 @@
----
-title: SQL映射文件之缓存元素
-author: 查尔斯
-date: 2020/12/28 00:17
-categories:
- - MyBatis快速入门
-tags:
- - MyBatis
- - ORM框架
----
-
-# SQL映射文件之缓存元素
-
-## 前言
-
-**C:** 在上一篇,笔者带大家对 MyBatis SQL 映射文件的 resultMap 元素做了介绍,它大概是 MyBatis 学习中第一个 "坎儿",没跨过来的同学也没关系,慢慢来,切勿急躁,先看看本篇再说。本篇,笔者将带你学习 MyBatis SQL 映射文件的 cache 元素。
-
-cache 即缓存,任何应用都不可缺少的一个组成部分,但凡想提升性能,缓存就得拿出来说道说道。MyBatis 中自然也少不了缓存的存在,下面我们去看看吧。
-
-## cache元素
-
-cache 元素,是用于开启 MyBatis 二级缓存的关键。在 MyBatis 中缓存分为一级缓存和二级缓存 。
-
-### 一级缓存
-
-**一级缓存主要指的是 Session 缓存,默认是开启并生效的** 。
-
-一级缓存存在两种作用域范围:[2]
-
-- `SESSION`(默认)**在同一个 SqlSession 中多次执行同一个查询,除第一次走数据库,剩下的都走缓存** 。
-- `STATEMENT` 每执行完一个 Mapper 中的语句后都会将一级缓存清除(不推荐配置)。
-
-测试一下一级缓存的 `SESSION` 作用域范围:(随便找两个查询试试就可以)
-
-```java
-class TestMyBatis {
-
- @Test
- void testSelectByList() {
- // 获取SqlSession对象
- try (SqlSession sqlSession = MyBatisUtils.openSession()) {
-
- // 获取 Mapper 接口
- UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
-
- // 执行不同 SQL
- List userList1 = userMapper.selectList();
- List userList2 = userMapper.selectByName("J");
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
-}
-```
-
-很显然它执行了两条 SQL,和缓存根本搭不上关系。
-
-
-
-再来试试执行两次相同的 SQL 查询。
-
-```java
-class TestMyBatis {
-
- @Test
- void testSelectByList() {
- // 获取SqlSession对象
- try (SqlSession sqlSession = MyBatisUtils.openSession()) {
-
- // 获取 Mapper 接口
- UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
- // 执行相同 SQL
- List userList1 = userMapper.selectList();
- List userList2 = userMapper.selectList();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
-}
-```
-
-结果显而易见,MyBatis 在同一个 SqlSession 中,对相同的 SQL 查询,只执行了一次,第二次则直接使用了缓存。
-
-
-
-### 二级缓存
-
-**二级缓存是指 mapper 映射文件。** 二级缓存的作用域是同一个 namespace 下的 mapper 映射文件内容,**多个 SqlSession 之间是共享的** 。
-
-::: warning 笔者说
-可以通过核心配置文件中的 settings 元素的 cacheEnabled 对所有二级缓存进行全局性开/关设置(默认值为true)。
-:::
-
-```xml
-
-
-
-```
-
-在测试二级缓存前,我们需要先对指定的 SQL 映射文件启用二级缓存,即添加一个 cache 元素。
-
-```xml
-
-
-```
-
-上面我们仅仅添加了一个空 cache 元素 ,但其实它已经采用了很多缓存默认值,大致如下:[1]
-
-- 映射语句文件中的所有 select 语句的结果将会被缓存。
-- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存(哪怕最后没提交事务也会刷新缓存)。
-- 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
-- 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
-- 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
-- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
-
-你也可以通过修改 cache 元素的属性来调整二级缓存。
-
-```xml
-
-
-```
-
-**eviction** 代表缓存清除策略:(默认的清除策略是 LRU)
-
-- `LRU` 最近最少使用:移除最长时间不被使用的对象
-- `FIFO` 先进先出:按对象进入缓存的顺序来移除它们
-- `SOFT` 软引用:基于垃圾回收器状态和软引用规则移除对象
-- `WEAK` – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
-
-**flushInterval** 代表缓存刷新间隔:(默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新)它的属性值可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。
-
-**size** 代表可以缓存的对象引用数目:(默认值是 1024)它的属性值可以被设置为任意正整数,但要注意欲缓存对象的大小和运行环境中可用的内存资源。
-
-**readOnly** 代表缓存中的对象是否只读:(默认值是 false),它的属性值可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改,这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝,速度上会慢一些,但是更安全( **建议** )。
-
----
-
-赶快测试一下吧:
-
-```java
-class TestMyBatis {
-
- @Test
- void testSelectByList() {
- try {
- // 获取SqlSession对象
- SqlSession sqlSession1 = MyBatisUtils.openSession();
- // 执行
- UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
- List userList1 = userMapper1.selectList();
- // 【执行关闭操作,将 SqlSession 中的数据写到二级缓存区域】
- sqlSession1.close();
-
- // 获取SqlSession对象
- SqlSession sqlSession2 = MyBatisUtils.openSession();
- // 执行
- UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
- List userList2 = userMapper2.selectList();
- sqlSession2.close();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
-}
-```
-
-控制台报错了,User 没有序列化,上方 readOnly 属性刚介绍完,它默认值为 false,表示每次会通过序列化返回缓存对象的拷贝,以此实现可读写的缓存。
-
-
-
-```java
-public class User implements Serializable{
- // 略
-}
-```
-
-再测试之后,控制台输出如下,Cache Hit Ratio 表示缓存命中率,开启二级缓存后,每执行一次查询,系统都会计算一次二级缓存的命中率。第一次查询也是先从缓存中查询,只不过缓存中一定是没有的,所以命中率为0,然后再从DB中查询后缓存到二级缓存中。第二次查询的时候是从二级缓存中读取的,这一次的命中率为1/2=0.5。 当然,若有第三次查询,则命中率为1/3=0.66 ,依此类推。[3]
-
-
-
-## cache-ref元素
-
-当我们想要在多个命名空间中共享相同的缓存配置和实例时,cache-ref 元素就可以派上用场了,当同时使用了 cache 元素和 cache-ref 元素时,cache 元素的优先级更高。
-
-```xml
-
-
-```
-
-::: tip 笔者说
-二级缓存也不是万能的,需要根据实际情况来,当查询操作远远多于增删改操作的情况下,并且业务对数据的实时性要求不高的时候可以采用二级缓存,否则增删改频繁刷新二级缓存将会降低系统性能,而缓存又会导致实时效果差。
-而且 MyBatis 的二级缓存也存在着一些缺陷,使用 MyBatis 二级缓存必须有一个前提:保证所有的增删改查都在同一个 namespace 下才行,不然容易出现数据不一致问题,例如:当两个 SQL 映射文件中均存在对同一个表的操作,那么其中一方修改了表,只会引发该 SQL 映射文件的二级缓存清空,而不会清空另一个的。
-:::
-
-## 参考文献
-
-[1]MyBatis 官网. XML 映射文件[EB/OL]. https://mybatis.org/mybatis-3/zh/sqlmap-xml.html. 2020-12-26
-
-[2]花好夜猿. Mybatis【面试题】讲讲Mybatis的缓存-简答[EB/OL]. https://blog.csdn.net/qq_23202687/article/details/103708458. 2019-12-26
-
-[3]陈浩翔. 你真的懂Mybatis缓存机制吗[EB/OL]. https://mp.weixin.qq.com/s/h2x15k71rClaHjcz7u2dlQ. 2018-07-10
-
-## 后记
-
-SQL 映射文件的初步学习终于结束了,幸好有之前的文章雏形,但就这样还花费了半天时间整理和完善,但愿它能给小白用户带来一份系统的学习方案。
-
-::: info 笔者说
-对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
-
-所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
-:::
diff --git a/docs/courses/mybatis/01-MyBatis基础/08-动态SQL.md b/docs/courses/mybatis/01-MyBatis基础/08-动态SQL.md
deleted file mode 100644
index f82688ed9..000000000
--- a/docs/courses/mybatis/01-MyBatis基础/08-动态SQL.md
+++ /dev/null
@@ -1,672 +0,0 @@
----
-title: 动态SQL
-author: 查尔斯
-date: 2020/12/29 13:28
-categories:
- - MyBatis快速入门
-tags:
- - MyBatis
- - ORM框架
----
-
-# 动态SQL
-
-## 前言
-
-**C:** 一晃,都来到了本系列的最后一篇了,第一篇[《快速入门》](./01-快速入门) 中,我们曾烦恼的问题们(见下方代码),被 MyBatis 的强大能力逐个逐个的瓦解了。
-
-我们还剩下没有攻克的问题,只有 **动态参数拼接** 了。但凡使用 JDBC 做过持久层开发,你应该就能理解根据不同条件拼接 SQL 语句有多痛苦,例如:拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号[1]、解决多余的 and等。
-
-本篇我们要学习 MyBatis 的另一个强大特性:动态 SQL 元素,利用它,我们可以彻底摆脱这种痛苦。
-
-```java
-// 假设BaseDao已经封装了通用CRUD操作,详情见笔者《BaseDao封装JDBC通用CRUD》
-public class UserDaoImpl extends BaseDao implements UserDao {
-
- // 根据条件查询用户列表
- @Override
- public List findByMap(Map params) throws Exception {
- // 动态拼接SQL语句
- StringBuffer sqlBuffer = new StringBuffer();
- // 动态拼接SQL占位符参数
- List paramsList = new ArrayList<>();
-
- sqlBuffer.append(" select ");
- sqlBuffer.append(" * ");
- sqlBuffer.append(" from ");
- sqlBuffer.append(" user ");
- sqlBuffer.append(" where 1 = 1 ");
-
- // 根据用户名模糊查询
- Object name = params.get("name");
- if (EmptyUtils.isNotEmpty(name)) {
- sqlBuffer.append(" and name like CONCAT('%',?,'%') ");
- paramsList.add(name);
- }
-
- // 根据年龄查询
- Integer age = (Integer) params.get("age");
- if (EmptyUtils.isNotEmpty(age)) {
- sqlBuffer.append(" and age = ? ");
- paramsList.add(age);
- }
-
- return this.selectList(sqlBuffer.toString(), paramsList.toArray(), User.class);
- }
-
- // 保存用户
- @Override
- public int save(User user) throws Exception {
- StringBuffer sqlBuffer = new StringBuffer();
-
- sqlBuffer.append(" insert into user ");
- sqlBuffer.append(" (name, age, email) ");
- sqlBuffer.append(" values(?, ?, ?) ");
-
- Object[] params = {user.getName(), user.getAge(), user.getEmail()};
-
- return this.insert(sqlBuffer.toString(), params);
- }
-
-}
-```
-
-如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。[1]
-
-- ***if*** 判断元素
-- choose (when, otherwise) 判断元素
-- **trim (where, set)** 自动去除多余组成元素
-- *foreach* 迭代元素
-
-上方这部分介绍,主要来自 MyBatis 官网,至于它怎么介(chui)绍(xu)自己我们不用过多关注,我们自己用了还好用才是真的。
-
-## if元素
-
-SQL 映射文件篇的时候,我们实现过一个需求:根据用户名和年龄查询用户列表。关键实现部分如下:
-
-```java
-public interface UserMapper {
-
- /**
- * 根据用户名和年龄查询
- * @param params 条件参数
- * @return 用户列表
- */
- List selectByMap(Map params);
-
-}
-```
-
-```xml
-
-
-
- select
- *
- from
- user
- where
- name like concat('%', #{name}, '%')
- and age = #{age}
-
-```
-
-如果一切如下方的传值测试,那什么问题也不会发生。
-
-```java
-class TestMyBatis {
-
- @Test
- void testSelectByMap() throws IOException {
- // 获取SqlSession对象
- try (SqlSession sqlSession = MyBatisUtils.openSession()) {
-
- // 获取 Mapper 接口
- UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
- // 执行 SQL
- Map params = new HashMap<>();
- params.put("name", "J");
- params.put("age", 20);
- List userList = userMapper.selectByMap(params);
-
- // 遍历数据
- userList.forEach(System.out::println);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
-}
-```
-
-但是往往我们在做类似的需求时,对条件一般是可选的,即:name 和 age 参数可以传递值也可以不传递值,那么当你如是做之后,结果呢?我们在 Map 集合中去除掉 age 这个参数值试试。
-
-```java
-// 执行
-Map map = new HashMap();
-map.put("name", "J");
-List userList = userMapper.selectByMap(params);
-```
-
-**控制台输出:**
-
-
-
-我们去除了 age 参数值,原本的设想是不再根据 age 列进行条件筛选,但是它还是处于生效状态,因为我们并没有添加 SQL 的条件判断, age = null 的结果显而易见是不存在的。
-
----
-
-我们来使用 if 元素改造一下吧。
-
-```xml
-
-
- select
- *
- from
- user
- where
- name like concat('%', #{name},'%')
-
- and age = #{age}
-
-
-```
-
-执行刚才的测试代码,此次就没有任何问题了。
-
-
-
-::: tip 笔者说
-if 元素可以说是日常开发用的最多的了,而且又简单,又好用。
-:::
-
-## where元素
-
-有了 if 元素的加持,如果我们只是要进行 age 这一项条件查询,那就去掉 Map 集合中的 name 参数,这时候似乎只需要在 SQL 映射文件中也给 name 条件查询加上个判断即可。
-
-```xml
-
-
- select
- *
- from
- user
- where
-
- name like concat('%', #{name},'%')
-
-
- and age = #{age}
-
-
-```
-
-```java
-// 执行
-Map map = new HashMap();
-map.put("age", 20);
-List userList = userMapper.selectByMap(map);
-```
-
-但结果并不是如此,当测试执行后,控制台报错了。
-
-
-
-但实际上,你就算不运行,你好好看看上方的 SQL 部分,就可以看出问题,它一共有可能出现4种情况。
-
-1. name 和 age 都传递了值,那一切都正常执行
-2. 只传递了 name 值,那么也正常
-3. *如果只传递了 age 值,那么就会多余一个 and 出现*
-4. *如果都没有传值,那么就会多余一个 where 出现*
-
-在之前 JDBC 开发中,我们的解决方法是添加一个 `where 1 = 1` 的恒等式。
-
-```xml
-
- select
- *
- from
- user
- where
- 1 = 1
-
- and name like concat('%', #{name},'%')
-
-
- and age = #{age}
-
-
-```
-
-在 MyBatis 中,准备了一个专门的元素用来解决此类问题,比添加额外的 `1=1` 语句更简单。
-
-```xml
-
-
- select
- *
- from
- user
-
-
- name like concat('%', #{name},'%')
-
-
- and age = #{age}
-
-
-
-```
-
-## set元素
-
-set 元素,和 where 元素一样也是结合 if 元素用于去除多余内容的。顾名思义,它是和 update 语句的 set 部分有关的。
-
-我们来做一个修改示例:
-
-```java
-public interface UserMapper {
-
- /**
- * 修改用户信息
- * @param user 用户信息
- * @return 影响行数
- */
- int update(User user);
-
-}
-```
-
-```xml
-
-
- update
- user
- set
- name = #{name},
- age = #{age},
- email = #{email},
- roleId = #{roleId}
- where
- id = #{id}
-
-```
-
-```java
-class TestMyBatis {
- @Test
- void testUpdate1() {
- // 获取SqlSession
- try (SqlSession sqlSession = MyBatisUtils.openSession()) {
-
- // 获取Mapper
- UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
-
- // 只想修改1号用户的名字
- User user = new User();
- user.setName("Charles");
- user.setId(1L);
- // 执行
- int rows = userMapper.update(user);
- System.out.println("影响行数为:" + rows);
-
- // 提交事务
- sqlSession.commit();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
-}
-```
-
-执行测试后,本来我们只想修改用户名,结果却是将所有的列都清空了。
-
-
-
-
-
-所以它也需要加上 if 元素来判断是否需要修改。
-
-```xml
-
- update
- user
- set
-
- name = #{name},
-
-
- age = #{age},
-
-
- email = #{email},
-
-
- roleId = #{roleId}
-
- where
- id = #{id}
-
-```
-
-但如果仅仅这样,当最后一个条件判断不成立时,那么前面的内容只要有一个传了值就会在结尾出现多余 `,` 的问题,使用 set 元素可以很容易解决该类问题。
-
-```xml
-
- update
- user
-
-
- name = #{name},
-
-
- age = #{age},
-
-
- email = #{email},
-
-
- roleId = #{roleId}
-
-
- where
- id = #{id}
-
-```
-
-set 元素可以智能添加一个 set 关键字,并且可以去除 if 段后面多余的 `,` 号。
-
-## trim元素
-
-当我们需要灵活变更自动去除的内容时,where 和 set 就不合适了,它们是死板的,只能去除那几样。
-
-trim 元素,看名字就知道是用来去除东西,下面我们用 trim 元素分别实现一下刚才的 where 元素和 set元素。
-
-实现 where 元素。
-
-```xml
-
-
- select
- *
- from
- user
-
-
- name like concat('%', #{name},'%')
-
-
- and age = #{age}
-
-
-
-```
-
-实现 set 元素。
-
-```xml
-
-
- update
- user
-
-
- name = #{name},
-
-
- age = #{age},
-
-
- email = #{email},
-
-
- roleId = #{roleId}
-
-
-
-```
-
-## foreach元素
-
-上方的四个元素,可以说在 SQL 映射文件中,用的最多,你把它们掌握了一般情况够用了。
-
-但是有时候我们有一些特殊的需求,例如:下方的案例,根据角色 id 的集合来查询用户列表。
-
-```java
-public interface UserMapper {
-
- /**
- * 根据角色集合查询用户
- * @param roleIds 角色集合
- * @return 用户列表
- */
- List selectByRoleList(List roleIds);
-
-}
-```
-
-```xml
-
-
- select
- *
- from
- user
- where
- roleId in
-
- #{id}
-
-
-```
-
-测试一下:
-
-```java
-class TestMyBatis {
-
- @Test
- void selectByRoleList() {
- // 获取SqlSession
- try (SqlSession sqlSession = MyBatisUtils.openSession()) {
-
- // 获取Mapper
- UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
-
- // 执行
- List roleIds = Arrays.asList(1L, 2L);
- List userList = userMapper.selectByRoleList(roleIds);
-
- // 遍历
- userList.forEach(System.out::println);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-}
-```
-
-**控制台输出:**
-
-```sql
--- 输出的 SQL
-select * from user where roleId in ( ? , ? )
-```
-
-```
-User [id=2, name=JSON, age=20, email=Jack@126.com]
-User [id=3, name=Tom, age=28, email=Tom@126.com]
-User [id=4, name=Sandy, age=21, email=Sandy@126.com]
-User [id=5, name=Billie, age=24, email=Billie@126.com]
-User [id=8, name=PeiQi, age=18, email=PeiQi@126.com]
-```
-
-## choose元素
-
-最后我们再介绍一个应用不算太多的元素:choose,它比较类似于在 Java 中的 switch 选择结构。
-
-**案例需求:查询用户列表,如果参数 name 有值那就根据 name 模糊查询,否则参数 age 有值那就根据 age 查询, 如果都没值那就查询所有角色为1的用户。**
-
-```java
-public interface UserMapper {
-
- /**
- * 查询用户列表
- * name有值时,根据名字做模糊查询
- * 否则age有值时,根据年龄等值查询
- * 都没值,查询角色Id为1的用户
- * @param user 查询条件
- * @return 用户列表
- */
- List selectByUser(User user);
-
-}
-```
-
-```xml
-
- select
- *
- from
- user
-
-
-
- name like concat('%', #{name},'%')
-
-
- age = #{age}
-
-
- roleId = 1
-
-
-
-
-```
-
-测试一下,如果同时传了 name 和 age的值。
-
-```java
-@Test
-void testSelectByUser() {
- try (SqlSession sqlSession = MyBatisUtils.openSession()){
-
- UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
-
- User user = new User();
- user.setName("T");
- user.setAge(18);
- List userList = userMapper.selectByUser(user);
-
- userList.forEach(System.out::println);
- } catch (Exception e) {
- e.printStackTrace();
- }
-}
-```
-
-**控制台输出:**
-
-```sql
--- 输出的 SQL
-select * from user WHERE name like concat('%', ?,'%')
-```
-
-测试一下,如果只传了 age 的值。
-
-```java
-@Test
-void testSelectByUser() {
- try (SqlSession sqlSession = MyBatisUtils.openSession()){
-
- UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
-
- User user = new User();
- user.setAge(18);
- List userList = userMapper.selectByUser(user);
-
- userList.forEach(System.out::println);
- } catch (Exception e) {
- e.printStackTrace();
- }
-}
-```
-
-**控制台输出:**
-
-```sql
--- 输出的 SQL
-select * from user WHERE age = ?
-```
-
-测试一下,如果什么值都没传。
-
-```java
-@Test
-void testSelectByUser() {
- try (SqlSession sqlSession = MyBatisUtils.openSession()){
-
- UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
-
- List userList = userMapper.selectByUser(null);
-
- userList.forEach(System.out::println);
- } catch (Exception e) {
- e.printStackTrace();
- }
-}
-```
-
-**控制台输出:**
-
-```sql
--- 输出的 SQL
-select * from user WHERE roleId = 1
-```
-
-::: tip 笔者说
-效果很明显了,自上而下进行条件判断,只要其中一个满足,后面的条件就不再执行。这就是 switch 的机制。
-:::
-
-## 参考文献
-
-[1]MyBatis 官网. 动态 SQL[EB/OL]. https://mybatis.org/mybatis-3/zh/dynamic-sql.html. 2020-12-28
-
-## 后记
-
-《初识MyBatis》 系列到本篇就结束了,笔者的更文速度,你还能追的上吗?:smile:
-
-其实这不过是因为笔者有存稿而已,否则哪那么快,每篇文章在最初时都花费了大量的时间,在存稿基础上再来发表又花费了不短时间,所以真没什么产出速度,大概比一些"临近太监的网文"的作者快点?
-
-这个系列结束,不代表 MyBatis 系列就完全结束了,后续笔者会再次抽时间开放 MyBatis 新的系列 《MyBatis原理》,当然这可能需要一段时间,因为笔者最近在忙另一个系列。但愿那时候还在关注的你,依然保持有对 MyBatis 热爱、研究的心。:gift_heart:
-
-::: info 笔者说
-对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
-
-所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
-:::
diff --git a/docs/courses/mybatis/02-MyBatis-Plus基础/01-快速入门.md b/docs/courses/mybatis/02-MyBatis-Plus基础/01-快速入门.md
deleted file mode 100644
index 4034873ba..000000000
--- a/docs/courses/mybatis/02-MyBatis-Plus基础/01-快速入门.md
+++ /dev/null
@@ -1,381 +0,0 @@
----
-title: 快速入门
-author: 查尔斯
-date: 2021/01/16 17:58
-categories:
- - MyBatis-Plus快速入门
-tags:
- - "MyBatis Plus"
- - MyBatis
- - ORM框架
----
-
-# 快速入门
-
-## 前言
-
-**C:** 在 Java Web 的日常开发中,风靡中日韩的持久层框架 MyBatis ,想必你不会陌生。如果你不认识它,那么本篇目前不适合你,请先学习 [《MyBatis快速入门》](/courses/mybatis/index.html) 后再过来。
-
-MyBatis 框架,作为一款非常优秀的 **半自动的持久层ORM框架** 。它支持自定义 SQL、动态SQL、存储过程以及高级映射, **免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作** 。
-
-它的确很好,但是你也再看看下方代码。在 MyBatis 中,不同实体的基础数据操作几乎属于套模板一样。尤其是在项目搭建初期,要写一大堆的基础 CRUD。随着开发工作量及工作时间上升,这就又成了天下程序员苦之久矣的事儿。
-
-```java
-public interface UserMapper {
- // 增加
- int insert(User user);
- // 修改
- int update(User user);
- // 删除
- int deleteById(@Param("id") Long id);
- // 根据ID查询
- User selectById(@Param("id") Long id);
- // 根据条件查询总记录数
- Integer selectCount(Map params);
- // 根据条件查询列表
- List selectByMap(Map params);
-}
-```
-
-当然,MyBatis 官方在一开始就想到了这事儿,所以提供了一套代码生成器。其他还有一些第三方 IDE 插件也有类似功能,但都到今天这年头了,你看看最近的一篇 MyBatis 代码生成器讲解文章下的评论。
-
-
-
-类似的评论在类似的文章中,不知凡几。我们自然是要跟随上技术时代的浪潮,做技术的 "弄潮儿",接下来笔者会开启一个新的系列《MyBatis-Plus快速入门》,来讲解体验下这个人云亦云的 MyBatis Plus。
-
-
-
-## 简介
-
-::: tip 官方释义
-[MyBatis-Plus](https://baomidou.com/)(简称 MP)是一个 [MyBatis](http://www.mybatis.org/mybatis-3/) 的增强工具, **在 MyBatis 的基础上只做增强不做改变** ,为简化开发、提高效率而生。[1]
-:::
-
-
-
-顾名思义,MyBatis Plus 是 MyBatis 的 Plus 版本,也就是升级版、增强版的意思。
-
-::: tip 笔者说
-由于名字较长,后面笔者会较多的叫它的简称:MP,注意是 MP 不是 MMP。
-:::
-
-## 特点
-
-以 MP 的作者所言:MyBatis Plus 的愿景是成为 MyBatis 最好的搭档,就像魂斗罗中的 1P、2P,基友搭配,效率翻倍。[1]
-
-看看下方总结的 MyBatis Plus 特点,有没有一些心动。
-
-- **无侵入** :只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
-- **损耗小** :启动即会自动注入基本 CRUD,性能基本无损耗,直接面向对象操作
-- **强大的 CRUD 操作** :内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
-- **支持 Lambda 形式调用** :通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
-- **支持主键自动生成** :支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
-- **支持 ActiveRecord 模式** :支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
-- **支持自定义全局通用操作** :支持全局通用方法注入( Write once, use anywhere )
-- **内置代码生成器** :采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置
-- **内置分页插件** :基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
-- **分页插件支持多种数据库** :支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
-- **内置性能分析插件** :可输出 SQL语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
-- **内置全局拦截插件** :提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
-
-## 使用步骤
-
-看官方说的这么好,心动不如行动,赶快跟着笔者来体验一下吧。
-
-我们将通过一个简单的 Demo 来体验 MP 的强大功能,在此之前,笔者假设你已经:
-
-- 拥有 Java 开发环境以及相应 IDE(IDEA)
-- 熟悉 MySQL 数据库
-- 熟悉 Maven 工具
-- 熟悉 Lombok 工具
-- 熟悉 MyBatis 技术
-- 熟悉 Spring Boot 技术
-
-### 数据库准备
-
-首先我们需要准备好一个数据库,并添加点测试数据。
-
-下方是我们的测试数据:
-
-| 主键 | 姓名 | 年龄 | 邮箱 |
-| :--: | :----: | :--: | :------------: |
-| 1 | Jone | 18 | Jone@126.com |
-| 2 | Jack | 20 | Jack@126.com |
-| 3 | Tom | 28 | Tom@126.com |
-| 4 | Sandy | 21 | Sandy@126.com |
-| 5 | Billie | 24 | Billie@126.com |
-
-其对应的数据库 结构 脚本如下:
-
-```sql
--- 创建并切换数据库
-CREATE DATABASE mybatisplus_demodb;
-USE mybatisplus_demodb;
-
--- 创建用户数据表
-CREATE TABLE `user` (
- `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
- `name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名',
- `age` int(11) NULL DEFAULT NULL COMMENT '年龄',
- `email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '邮箱',
- PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户表' ROW_FORMAT = Compact;
-```
-
-其对应的数据库 数据 脚本如下:
-
-```sql
--- 清空用户表数据
-TRUNCATE TABLE user;
-
--- 向用户表插入测试数据
-INSERT INTO `user` VALUES (1, 'Jone', 18, 'Jone@126.com');
-INSERT INTO `user` VALUES (2, 'Jack', 20, 'Jack@126.com');
-INSERT INTO `user` VALUES (3, 'Tom', 28, 'Tom@126.com');
-INSERT INTO `user` VALUES (4, 'Sandy', 21, 'Sandy@126.com');
-INSERT INTO `user` VALUES (5, 'Billie', 24, 'Billie@126.com');
-```
-
-依次执行完结构及数据脚本后,最终数据库效果如下:
-
-
-
-### 创建项目
-
-在有了 Spring Boot 之后,MyBatis 开发变得非常简单。作为 MyBatis 的升级版,毫无疑问,MP 的开发团队也为 Spring Boot 开发准备了一份 starter,这让我们开发一个 MP 项目变得非常 Easy。
-
-我们使用 IntelliJ IDEA 中封装的 [Spring Initializer](https://start.spring.io/) 来快速初始化一个 Spring Boot 工程。
-
-
-
-填写好 Maven 的 GAV 信息。
-
-
-
-在项目构建选择依赖这一步的时候,直接选择好 Lombok 和 MySQL 驱动的依赖。
-
-
-
-构建好的项目结构如下。
-
-
-
-最后,我们需要调整下 Spring Boot 的版本,以及导入好 MP 的 starter 依赖。
-
-
-
-::: tip 笔者说
-为了方便使用本案例中的单元测试,建议采用 2.2.x 以上的 Spring Boot。
-:::
-
-```xml
-
- org.springframework.boot
- spring-boot-starter-parent
-
- 2.2.11.RELEASE
-
-
-
-com.example
-mybatis-plus-demo
-0.0.1-SNAPSHOT
-
-
- 1.8
-
-
-
-
-
- org.springframework.boot
- spring-boot-starter
-
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
-
-
- org.projectlombok
- lombok
- true
-
-
-
-
- mysql
- mysql-connector-java
- runtime
-
-
-
-
- com.baomidou
- mybatis-plus-boot-starter
- 3.4.1
-
-
-```
-
-::: warning 笔者说
-引入 `MP` 依赖之后,就不要再引入 `MyBatis` 以及 `MyBatis-Spring` 这些依赖了,因为 MP 都包含好了。你再引入,还可能出现因版本差异导致的其他问题。
-:::
-
-
-
-### 创建POJO类
-
-项目创建好后,按原来 MyBatis 的使用步骤,首先要根据数据库编写好 POJO 类。
-
-```java
-@Data
-public class User {
-
- private Long id;
- private String name;
- private Integer age;
- private String email;
-
-}
-```
-
-::: tip 笔者说
-此处使用了 Lombok 来简化 getter/setter 等代码,如果不会用,可以直接自己手写 getter/setter 等代码。
-:::
-
-### 创建Mapper接口
-
-有了 POJO 类之后,原来的 MyBatis 中,下一步自然就要写 Mapper 接口,写 SQL 映射文件了。但是现在你只需要写好 Mapper 接口,然后继承一个由 MP 所提供的 `BaseMapper` 接口即可。
-
-```java
-// 注:给 BaseMapper 指定好泛型,它里面的 CRUD 需要使用泛型来确定具体操作数据类型
-public interface UserMapper extends BaseMapper {
-
-}
-```
-
-::: tip 笔者说
-在 `BaseMapper` 中封装有大量的基础 CRUD 操作,这一点就直接解决了我们前言中所提到的需求。
-:::
-
-### 添加配置
-
-写好核心部分代码后,MP 需要像 MyBatis 整合 Spring Boot 一样,在 `application.yml` 配置文件中添加上数据源配置及一些 MyBatis 自定义配置。
-
-```yaml
-spring:
- # 数据源配置
- datasource:
- driver-class-name: com.mysql.cj.jdbc.Driver
- url: jdbc:mysql:///mybatisplus_demodb?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8
- username: root
- password: root
-mybatis-plus:
- configuration:
- # 控制台打印SQL日志
- log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
-```
-
-::: tip 笔者说
-一般我们还会给 MyBatis 添加些自定义配置:类型别名包扫描,指定 Mapper 映射文件地址等。这回这些配置都被 MP 整合在一起了,有的还拥有了默认值。
-例如:原来指定 Mapper 映射文件地址需要用:`mybatis.mapper-locations` ,现在 MP 中需要用:`mybatis-plus.mapper-locations`,MP 还给它准备了默认值:`classpath*:/mapper/**/*.xml`。
-这意味着啥?意味着,这种配置以后如果没有额外指定,就不需要我们再配置它啦。
-:::
-
-
-
-最后, MP 同样也需要在 Spring Boot 启动类中,添加 `@MapperScan` 注解来指定 Mapper 接口扫描包。
-
-```java
-@SpringBootApplication
-@MapperScan("com.example.mapper")
-public class MybatisPlusDemoApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(QuickStartApplication.class, args);
- }
-
-}
-```
-
-### 测试
-
-终于搞定了,我们再去测试类中编写一个测试用例,去测试一下效果就完事儿了。
-
-**测试代码:**
-
-```java
-@SpringBootTest
-class MybatisPlusDemoApplicationTests {
-
- @Autowired
- private UserMapper userMapper;
-
- @Test
- void testSelectList() {
- // 查询用户列表
- List userList = userMapper.selectList(null);
- // 遍历用户列表
- userList.forEach(System.out::println);
- }
-
-}
-```
-
-
-
-**控制台输出:**
-
-```sql
-==> Preparing: SELECT id,name,age,email FROM user
-==> Parameters:
-<== Columns: id, name, age, email
-<== Row: 1, Jone, 18, Jone@126.com
-<== Row: 2, Jack, 20, Jack@126.com
-<== Row: 3, Tom, 28, Tom@126.com
-<== Row: 4, Sandy, 21, Sandy@126.com
-<== Row: 5, Billie, 24, Billie@126.com
-<== Total: 5
-```
-
-```
-User(id=1, name=Jone, age=18, email=Jone@126.com)
-User(id=2, name=Jack, age=20, email=Jack@126.com)
-User(id=3, name=Tom, age=28, email=Tom@126.com)
-User(id=4, name=Sandy, age=21, email=Sandy@126.com)
-User(id=5, name=Billie, age=24, email=Billie@126.com)
-```
-
-::: tip 笔者说
-你在编写单元测试时,可能会看到如下报错。别担心这不是什么要紧的事儿,不影响你运行,也不影响你开法拉利。
-如果你看不惯它,那就去 UserMapper 接口上加一个 `@Repository` 或 `@Component` 注解,这问题就迎刃而解了。
-:::
-
-
-## 参考文献
-
-[1]MyBatis Plus 官网. 指南[EB/OL]. https://baomidou.com/guide/. 2021-01-16
-
-## 后记
-
-**C:** 好了,MP 的快速入门就到这儿结束了,是不是快的都让你觉得在做梦?通过以上几个简单的步骤,我们就拥有了一个具有通用 CRUD 操作功能的 Mapper 接口,甚至连 XML 文件都不用编写!
-
-当然,若是它们还不够你用,或是你需要写一些复杂的数据操作,继续按照 MyBatis 原来的玩法去写就可以了,一点不影响。
-
- `MyBatis-Plus` 的强大远不止如此,想要详细了解 MyBatis-Plus 的强大功能,那就继续查看笔者该系列的下一篇吧!
-
-**另外,提醒一下,本系列的编写思路,就是基于官网而来,甚至有同学看完官网会觉得笔者多余了。但笔者一贯以小白文著称,学起来不吃力,学重点,比官网更细致,是笔者本系列的目的。所以等等再 "恶评" 吧。**
-
-::: info 笔者说
-对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
-
-所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
-:::
-
diff --git a/docs/courses/mybatis/02-MyBatis-Plus基础/02-增删改操作.md b/docs/courses/mybatis/02-MyBatis-Plus基础/02-增删改操作.md
deleted file mode 100644
index 0682b4ade..000000000
--- a/docs/courses/mybatis/02-MyBatis-Plus基础/02-增删改操作.md
+++ /dev/null
@@ -1,702 +0,0 @@
----
-title: 增删改操作
-author: 查尔斯
-date: 2021/01/19 00:00
-categories:
- - MyBatis-Plus快速入门
-tags:
- - "MyBatis Plus"
- - MyBatis
- - ORM框架
----
-
-# 增删改操作
-
-## 前言
-
-**C:** 在上一篇,笔者带大家快速入门了 MP,不知道你是否已经掌握了 MP 的使用步骤,折服于 MP 的强大呢?本篇,笔者将继续在上一篇的 Demo 基础上带你学习 MP,掌握 MP 中常见的 CUD 操作 API。
-
-::: tip 笔者说
-CUD 不知道是什么意思?在咱们后端的圈子里,有一个常常挂在嘴边的名词 "CRUD"。甚至有些同学在去面试时,直言自己在某个项目中就是在做 xxx 的 "CRUD" 而已。
-
-至于它的含义,你使用有道词典都能搜到它的含义。CRUD 代表的是 Create, Read (Retrieve), Update, Delete 这四个单词的简写,俗称 "增删改查"/"增删查改"。
-
-因为我们做后端,避免不掉的就是操作数据库,而数据库的基本操作就是这四类。另外在行业内,它也一度成为了咱们圈子里较为 "自嘲" 类的名词,有些小伙伴儿常常自称自己是 "CRUD 工程师" ,旨在表达自己平时做的业务都很简单的意思。
-
-CUD 自然就是笔者从 CRUD 中拆出来的,意味着数据的增删改操作。
-:::
-
-
-
-## 插入操作
-
-你是不是以为,笔者要做一大堆的前情准备工作?你错了,在 MP 中,这一切都不需要。直接 "莽"。
-
-
-
-在上一篇 Demo 项目的 UserMapper 中,因为它已经继承了 [BaseMapper](https://gitee.com/baomidou/mybatis-plus/blob/3.0/mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/mapper/BaseMapper.java) 接口,所以甚至无需编写 UserMapper.xml 文件,就已获得了通用的 CRUD API。
-
-在 BaseMapper 接口中,插入操作的 API 只有一个。
-
-```java
-// 其他 API 略
-public interface BaseMapper extends Mapper {
-
- /**
- * 插入一条记录
- *
- * @param entity 实体对象
- * @return 影响行数
- */
- int insert(T entity);
-
-}
-```
-
-### 插入1条记录
-
-接下来,我们准备测试一下插入操作 API,我们先在上一篇 Demo 项目的基础上,复制粘贴一个专门用于测试 CUD 操作的单元测试类。
-
-
-
-**测试代码:**
-
-笔者提前准备好了一条测试数据。
-
-| 姓名 | 年龄 | 邮箱 |
-| :-----: | :--: | :---------------: |
-| Charles | 18 | charles7c@126.com |
-
-```java
-import org.junit.Assert;
-
-@SpringBootTest
-class MybatisPlusCUDTests {
-
- @Autowired
- private UserMapper userMapper;
-
- @Test
- public void testInsert(){
- // 创建用户对象
- User user = new User();
- user.setName("Charles");
- user.setAge(18);
- user.setEmail("charles7c@126.com");
-
- // 调用插入操作API
- int rows = userMapper.insert(user);
- Assert.assertEquals(1, rows);
-
- // MP默认就会自动回填生成的主键
- System.out.println(user.getId());
- }
-
-}
-```
-
-**控制台输出:**
-
-```sql
-==> Preparing: INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
-==> Parameters: 1352882704631181313(Long), Charles(String), 18(Integer), charles7c@126.com(String)
-<== Updates: 1
-```
-
-```
-1352882704631181313
-```
-
-### ID生成策略
-
-我们在插入成功后,看到用户的 id 值是一个很长的数值,不仔细看还以为是身份证号呢?其实,这是 MP 默认的主键生成策略:**生成分布式唯一ID** 的锅。
-
-::: tip 笔者说
-所谓的分布式唯一ID,简称分布式ID。我们都知道数据库中的每条数据都要定义好一个唯一 ID,像 MySQL 等数据库,提供了主键自增功能,以帮助我们自动生成 1、2、3...这种简单的唯一ID。但随着系统业务越来越复杂,数据库开始分库分表,这种传统的 ID 生成策略在分布式情况下有极大可能出现重复 ID,所以分布式 ID 的概念就诞生了。常见的分布式 ID 解决方案有:Redis生成ID、UUID、Snowflake(雪花算法)等。
-
-自 MP 3.3.0 开始,主键生成策略默认为:使用 雪花算法 + UUID(不含中划线) 组合而成。
-
-**UUID:** UUID是国际标准化组织(ISO)提出的一个概念,是通用唯一识别码(Universally Unique Identifier)的缩写。在所有空间和时间上被视为唯一的标识,UUID 通常由32个十六进制数字表示,以5个字符组显示,每个字符组以“-”隔开。例如:6db55ec5-ff6f-478a-911d-313de67ed563。
-
-**[Snowflake](https://github.com/twitter-archive/snowflake/releases/tag/snowflake-2010):** Snowflake 是 Twitter 开源的分布式ID生成算法,结果是一个 long 型的 ID。
-:::
-
-如果我们的需求不需要分布式ID,可以替换成 MP 中其他的主键生成策略。我们可以通过查看 MP 的 ID 类型枚举类源码,来查看它有哪些主键生成策略。
-
-```java
-/**
- * 生成ID类型枚举类
- *
- * @author hubin
- * @since 2015-11-10
- */
-@Getter
-public enum IdType {
- /**
- * 数据库ID自增
- * 该类型请确保数据库支持并设置了主键自增 否则无效
- */
- AUTO(0),
-
- /**
- * 该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
- */
- NONE(1),
-
- /**
- * 用户输入ID
- * 该类型可以通过自己注册自动填充插件进行填充
- */
- INPUT(2),
-
- /* 注意:以下类型、只有当插入对象ID 为空,才自动填充。 */
- /**
- * 【MP 默认采用此种主键生成策略】
- * 通过雪花算法分配ID (主键类型为数值或字符串),
- * @since 3.3.0 (3.3.0版本新增该策略)
- */
- ASSIGN_ID(3),
-
- /**
- * 通过UUID分配ID (主键类型为字符串)
- */
- ASSIGN_UUID(4),
-
- /* ---------已过时,不建议采用--------- */
- /**
- * 从3.3.0版本开始过时,可使用ASSIGN_ID替代
- */
- @Deprecated
- ID_WORKER(3),
- /**
- * 从3.3.0版本开始过时,可使用ASSIGN_ID替代
- */
- @Deprecated
- ID_WORKER_STR(3),
- /**
- * 从3.3.0版本开始过时,可使用ASSIGN_UUID替代
- */
- @Deprecated
- UUID(4);
-
- private final int key;
-
- IdType(int key) {
- this.key = key;
- }
-}
-```
-
-当你看中了某一个生成策略,想来改变默认的主键生成策略时,直接在对应实体类的主键属性上,添加 `@TableId` 注解即可。
-
-```java
-@Data
-public class User {
-
- // @TableId是用于标注主键属性的注解
- // value:数据表对应的列名,如果实体类属性和数据表列名不一致时使用
- // type:主键生成策略类型
- @TableId(type = IdType.AUTO)
- private Long id;
- private String name;
- private Integer age;
- private String email;
-
-}
-```
-
-当然,如果你每个实体都要更改主键生成策略,最好的方式还是直接在 `application.yml` 中进行全局配置。
-
-```yaml
-# MyBatis Plus配置
-mybatis-plus:
- # 全局配置
- global-config:
- db-config:
- # 主键生成策略
- id-type: auto
-```
-
-::: warning 笔者说
-上方的两种配置,如果你选择了全局配置, **就不需要再配置第一种了** 。
-:::
-
-更改了主键生成策略后,别忘了先 Truncate 再 Insert 来重置下当前的用户表数据,否则再测试插入时,数据库主键自增序列是从当前的 ID 最大值开始的。
-
-
-
-重置完成后,我们再去测试一下刚才的插入操作。
-
-**控制台输出:**
-
-```sql
-==> Preparing: INSERT INTO user ( name, age, email ) VALUES ( ?, ?, ? )
-==> Parameters: Charles(String), 18(Integer), charles7c@126.com(String)
-<== Updates: 1
-```
-
-```
-6
-```
-
-很显然,这一次,MP 不再为我们分配 ID 了,而是由数据库进行主键自增生成的 ID。
-
-## 修改操作
-
-在 BaseMapper 接口中,修改操作的 API 有两个,但本篇我们先只介绍一个,另一个需要等我们学完下一篇的条件构造器再介绍。
-
-```java
-// 其他 API 略
-public interface BaseMapper extends Mapper {
-
- /**
- * 根据 ID 修改
- *
- * @param entity 实体对象
- * @return 影响行数
- */
- int updateById(@Param(Constants.ENTITY) T entity);
-
- /**
- * 根据 whereEntity 条件,更新记录
- *
- * @param entity 实体对象 (set 条件值,可以为 null)
- * @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
- * @return 影响行数
- */
- int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper updateWrapper);
-
-}
-```
-
-### 根据ID修改
-
-为了测试修改操作 API,笔者也提前准备好了一条测试数据。
-
-| 主键 | 姓名 | 年龄 | 邮箱 |
-| :---: | :----: | :----: | :------------: |
-| **5** | Billie | **18** | Billie@126.com |
-
-**测试代码:**
-
-```java
-@SpringBootTest
-class MybatisPlusCUDTests {
-
- @Autowired
- private UserMapper userMapper;
-
- @Test
- void testUpdateById(){
- // 创建用户对象
- User user = new User();
- user.setId(5L);
- user.setAge(18);
-
- // 执行修改操作 API
- int rows = userMapper.updateById(user);
- Assert.assertEquals(1, rows);
- }
-
-}
-```
-
-**控制台输出:**
-
-```sql
-==> Preparing: UPDATE user SET age=? WHERE id=?
-==> Parameters: 18(Integer), 5(Long)
-<== Updates: 1
-```
-
-### 自动填充
-
-我们在项目开发过程中,经常要在最终存储数据前,进行一些数据填充工作,例如审计信息:创建人、创建时间,更新人、更新时间等。
-
-这些数据的填充工作,重复且枯燥,有时候还容易忘记,MP 中提供了自动填充功能,可以结束这类问题。
-
-接下来,我们就以自动填充实体类的 创建时间、更新时间 这两个属性为例来演示一下 MP 的自动填充功能。
-
-**第1步:我们需要给实体类、数据库表先做一些结构调整工作。**
-
-```sql
--- 给用户表添加 create_time 和 update_time 两个列
-ALTER TABLE `mybatisplus_demodb`.`user`
-ADD COLUMN `create_time` datetime(0) NULL COMMENT '创建时间' AFTER `email`,
-ADD COLUMN `update_time` datetime(0) NULL COMMENT '更新时间' AFTER `create_time`;
-```
-
-```java
-@Data
-public class User {
-
- private Long id;
- private String name;
- private Integer age;
- private String email;
- // 添加对应的实体属性:createTime、updateTime
- private LocalDateTime createTime;
- private LocalDateTime updateTime;
-
-}
-```
-
-**第2步:在 User 类中添加 @TableField 注解来给属性指定自动填充类型。**
-
-```java
-@Data
-public class User {
-
- private Long id;
- private String name;
- private Integer age;
- private String email;
-
- // @TableField是用于标注普通属性的注解
- // value:数据表对应的列名,如果实体类属性和数据表列名不一致时使用
- // fill:自动填充类型
- // INSERT:插入时自动填充
- // UPDATE:更新时自动填充
- // INSERT_UPDATE:插入或更新时自动填充
- @TableField(fill = FieldFill.INSERT)
- private LocalDateTime createTime;
- @TableField(fill = FieldFill.INSERT_UPDATE)
- private LocalDateTime updateTime;
-
-}
-```
-
-**第3步:创建自动审计处理器,实现元对象处理器接口。**
-
-`com.baomidou.mybatisplus.core.handlers.MetaObjectHandler`
-
-```java
-/**
- * 通过 MP 的自动填充功能实现自动审计
- */
-@Component
-public class AutoAuditHandler implements MetaObjectHandler {
-
- @Override
- public void insertFill(MetaObject metaObject) {
- this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用)
- this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用)
- }
-
- @Override
- public void updateFill(MetaObject metaObject) {
- this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
- }
-
-}
-```
-
-这么做完之后,我们再来测试下刚才的修改操作。
-
-**控制台输出:**
-
-```sql
-==> Preparing: UPDATE user SET age=?, update_time=? WHERE id=?
-==> Parameters: 18(Integer), 2021-01-23T16:51:18.413(LocalDateTime), 5(Long)
-<== Updates: 1
-```
-
-显而易见,执行的 SQL 中多了一个更新时间的修改,而且传的值是当前时间。
-
-::: tip 笔者说
-你也可以测试一下刚才的插入操作 API,看看新增数据时是否会自动填充 创建时间 和 更新时间 数据。
-:::
-
-## 删除操作
-
-在 BaseMapper 接口中,删除操作的 API 一共有4个,我们本篇主要介绍一下前三个,最后一个同样也在下一篇再进行介绍。
-
-```java
-// 其他 API 略
-public interface BaseMapper extends Mapper {
- /**
- * 根据 ID 删除
- *
- * @param id 主键ID
- * @return 影响行数
- */
- int deleteById(Serializable id);
-
- /**
- * 删除(根据ID 批量删除)
- *
- * @param idList 主键ID列表(不能为 null 以及 empty)
- * @return 影响行数
- */
- int deleteBatchIds(@Param(Constants.COLLECTION) Collection extends Serializable> idList);
-
- /**
- * 根据 columnMap 条件,删除记录
- *
- * @param columnMap 表字段 map 对象
- * @return 影响行数
- */
- int deleteByMap(@Param(Constants.COLUMN_MAP) Map columnMap);
-
- /**
- * 根据 entity 条件,删除记录
- *
- * @param queryWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
- * @return 影响行数
- */
- int delete(@Param(Constants.WRAPPER) Wrapper queryWrapper);
-
-}
-```
-
-### 根据ID删除
-
-**测试代码:**
-
-```java
-@SpringBootTest
-class MybatisPlusCUDTests {
-
- @Autowired
- private UserMapper userMapper;
-
- @Test
- void testDeleteById() {
- // 执行删除操作API,删除ID为1的用户数据
- int rows = userMapper.deleteById(1L);
- Assert.assertEquals(1, rows);
- }
-}
-```
-
-**控制台输出:**
-
-```sql
-==> Preparing: DELETE FROM user WHERE id=?
-==> Parameters: 1(Long)
-<== Updates: 1
-```
-
-### 批量ID删除
-
-**测试代码:**
-
-```java
-@SpringBootTest
-class MybatisPlusCUDTests {
-
- @Autowired
- private UserMapper userMapper;
-
- @Test
- void testDeleteBatchIds() {
- // 删除ID为2、3的用户数据
- List ids = Arrays.asList(2, 3);
- int rows = userMapper.deleteBatchIds(ids);
- Assert.assertEquals(2, rows);
- }
-
-}
-```
-
-**控制台输出:**
-
-```sql
-==> Preparing: DELETE FROM user WHERE id IN ( ? , ? )
-==> Parameters: 2(Integer), 3(Integer)
-<== Updates: 2
-```
-
-### 简单的带条件删除
-
-**测试代码:**
-
-```java
-@SpringBootTest
-class MybatisPlusDemoApplication {
-
- @Autowired
- private UserMapper userMapper;
-
- @Test
- void testDeleteByMap() {
- // 删除姓名为Sandy的用户数据
- // Map集合的键:表示的是数据库列名不是实体类属性名
- Map columnMap = new HashMap<>();
- columnMap.put("name", "Sandy");
- int rows = userMapper.deleteByMap(columnMap);
- Assert.assertEquals(1, rows);
- }
-
-}
-```
-
-**控制台输出:**
-
-```sql
-==> Preparing: DELETE FROM user WHERE name = ?
-==> Parameters: Sandy(String)
-<== Updates: 1
-```
-
-### 逻辑删除
-
-在项目开发过程中,为了保留用户数据,在删除用户数据时,我们会选择逻辑删除,而非物理删除。
-
-- **物理删除:** 真实删除,将对应数据从数据库中删除,即采用 `delete` SQL。
-
-- **逻辑删除:** 假删除,将对应数据中代表是否被删除的列,修改为 "被删除状态值",即采用 `update` SQL。
-
-接下来,我们也实现一下逻辑删除功能。
-
-**第1步:我们需要给实体类、数据库表先做一些结构调整工作。**
-
-```sql
--- 给用户表添加 is_delete 列
-ALTER TABLE `mybatisplus_demodb`.`user`
-ADD COLUMN `is_delete` int(2) NULL COMMENT '是否被删除' AFTER `update_time`;
-```
-
-```java
-@Data
-public class User {
-
- private Long id;
- private String name;
- private Integer age;
- private String email;
- @TableField(fill = FieldFill.INSERT)
- private LocalDateTime createTime;
- @TableField(fill = FieldFill.INSERT_UPDATE)
- private LocalDateTime updateTime;
-
- // 添加对应的实体属性:isDelete
- // 为 逻辑删除 属性指定插入数据时自动填充,并调整好数据填充处理器
- @TableField(fill = FieldFill.INSERT)
- private Integer isDelete;
-
-}
-```
-
-```java
-/**
- * 通过 MP 的自动填充功能实现自动审计
- */
-@Component
-public class AutoAuditHandler implements MetaObjectHandler {
-
- @Override
- public void insertFill(MetaObject metaObject) {
- this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
- this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
- // 插入数据时,逻辑删除属性自动填充值
- this.strictInsertFill(metaObject, "isDelete", Integer.class, 0);
- }
-
- @Override
- public void updateFill(MetaObject metaObject) {
- this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
- }
-
-}
-```
-
-**第2步:在 application.yml 中,全局配置逻辑删除的默认值和删除值。**
-
-::: tip 笔者说
-这一步也可以通过在 逻辑删除 属性上方添加 `@TableLogic` 注解实现,但还是建议采用全局配置。
-:::
-
-```yaml
-mybatis-plus:
- global-config:
- db-config:
- # 逻辑删除属性
- logic-delete-field: isDelete
- # 未删除状态值
- logic-not-delete-value: 0
- # 删除状态值
- logic-delete-value: 1
-```
-
-配置完后,我们再去测试一下刚才的 根据ID删除 操作。
-
-**测试代码:**
-
-```java
-@SpringBootTest
-class MybatisPlusCUDTests {
-
- @Autowired
- private UserMapper userMapper;
-
- @Test
- void testDeleteById() {
- // 执行删除操作API,删除ID为5的用户数据
- int rows = userMapper.deleteById(5L);
- // int rows = userMapper.deleteById(1L);
- Assert.assertEquals(1, rows);
- }
-
-}
-```
-
-**控制台输出:**
-
-```sql
-==> Preparing: UPDATE user SET is_delete=1 WHERE id=? AND is_delete=0
-==> Parameters: 5(Long)
-<== Updates: 0
-```
-
-```
-java.lang.AssertionError: expected:<1> but was:<0>
-Expected :1
-Actual :0
-```
-
-这次执行单元测试,竟然报错了!仔细看一下,原来是 `断言` 提示我们实际结果和预期结果不一致。
-
-什么原因导致失败呢?其实是因为我们加入逻辑删除列之后,数据库表中虽然多了这列,但是这一列都还没有设置过值呢。
-
-
-
-而执行的 SQL 中却需要查找逻辑删除列值为0的数据,这肯定找不到啊,影响行数自然为 0 了,和我们预期的影响行数 1 不符,于是报错了。
-
-知道原因后,那就先手动,给数据表的逻辑删除列都设置为0。
-
-
-
-再去执行一次刚才的删除测试。
-
-**控制台输出:**
-
-```sql
-==> Preparing: UPDATE user SET is_delete=1 WHERE id=? AND is_delete=0
-==> Parameters: 5(Long)
-<== Updates: 1
-```
-
-这回就不再报错了,而且显而易见,执行的 SQL 由没有做逻辑删除配置前的 `DELETE` 操作现在变为了 `UPDATE` 操作。
-
-
-
-::: tip 笔者说
-在逻辑删除配置好之后,原来的部分操作,像查询操作,在执行 SQL 时将自动带上 `where is_delete = 0` 这个条件。
-:::
-
-## 参考文献
-
-[1]MyBatis Plus 官网. 指南[EB/OL]. https://baomidou.com/guide/. 2021-01-18
-
-## 后记
-
-**C:** 学习 MyBatis 的时候,我们就没担心过 CUD,现在 MP 中自然更不存在这事儿了。而且 MP 还给我们提供了这么多实用功能。
-
-下一篇我们将会学到较为复杂的查询操作,但 MP 还是相对简单的,敬请期待吧。
-
-::: info 笔者说
-对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
-
-所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
-:::
diff --git a/docs/courses/mybatis/02-MyBatis-Plus基础/03-简单查询操作.md b/docs/courses/mybatis/02-MyBatis-Plus基础/03-简单查询操作.md
deleted file mode 100644
index 969eb4cd1..000000000
--- a/docs/courses/mybatis/02-MyBatis-Plus基础/03-简单查询操作.md
+++ /dev/null
@@ -1,348 +0,0 @@
----
-title: 简单查询操作
-author: 查尔斯
-date: 2021/01/23 19:38
-categories:
- - MyBatis-Plus快速入门
-tags:
- - "MyBatis Plus"
- - MyBatis
- - ORM框架
----
-
-# 简单查询操作
-
-## 前言
-
-**C:** 在 CRUD 中,哪一个操作是最复杂的,毫无疑问是 R(查询)操作。在大多数业务中,查询也是涉及最广泛的部分。很多初级程序员有个毛病:简单的查询不想写,难一点的查询不会写。
-
-上一篇,我们体验了 MP 的增删改操作,本篇,笔者将带你看看你想写或不想写的查询究竟被 MP 封装优化成什么样了。
-
-
-
-## 数据准备
-
-在介绍查询操作前,我们需要重置一次数据表,因为上一篇,数据表的数据已经被我们玩的不能看了。
-
-```sql
--- 清空用户表数据
-TRUNCATE TABLE user;
-
--- 向用户表插入测试数据
-INSERT INTO `user` VALUES (1, 'Jone', 18, 'Jone@126.com', NOW(), NOW(), 0);
-INSERT INTO `user` VALUES (2, 'Jack', 20, 'Jack@126.com', NOW(), NOW(), 0);
-INSERT INTO `user` VALUES (3, 'Tom', 28, 'Tom@126.com', NOW(), NOW(), 0);
-INSERT INTO `user` VALUES (4, 'Sandy', 21, 'Sandy@126.com', NOW(), NOW(), 0);
-INSERT INTO `user` VALUES (5, 'Billie', 24, 'Billie@126.com', NOW(), NOW(), 0);
-```
-
-
-
-## 根据ID查询记录
-
-在MP 的 BaseMapper 中,为我们提供了 10 来个查询操作,我们还是先介绍一些简单的 API,至于需要使用条件构造器的 API 等等再说。
-
-那第1个要介绍的查询操作是:根据 ID 查询单个实体数据,日常开发中将会频繁使用的一个 API。
-
-```java
-// 其他 API 略
-public interface BaseMapper extends Mapper {
-
- /**
- * 根据 ID 查询
- *
- * @param id 主键ID
- * @return 单个实体数据
- */
- T selectById(Serializable id);
-
-}
-```
-
-接下来,我们准备测试一下它,我们还是先在上一篇 Demo 项目的基础上,复制粘贴一个专门用于测试 R 操作的单元测试类。
-
-
-
-**测试代码:**
-
-```java
-@SpringBootTest
-class MybatisPlusRTests {
-
- @Autowired
- private UserMapper userMapper;
-
- @Test
- void testSelectById() {
- // 查询ID为1的用户数据
- User user = userMapper.selectById(5L);
- System.out.println(user);
- }
-
-}
-```
-
-**控制台输出:**
-
-```sql
-==> Preparing: SELECT id,name,age,email,create_time,update_time,is_delete FROM user WHERE id=? AND is_delete=0
-==> Parameters: 1(Long)
-<== Columns: id, name, age, email, create_time, update_time, is_delete
-<== Row: 1, Jone, 18, Jone@126.com, 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
-<== Total: 1
-```
-
-```
-User(id=1, name=Jone, age=18, email=Jone@126.com, createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
-```
-
-::: tip 笔者说
-在控制台上显示的执行 SQL 中,我们可以看到它结尾有一个 `is_delete = 0` 条件,别害怕,这是我们上一篇配置逻辑删除后的正常现象。
-:::
-
-## 批量ID查询
-
-第2个要介绍的查询操作是:根据多个 ID 来批量查询实体数据。
-
-```java
-// 其他 API 略
-public interface BaseMapper extends Mapper {
-
- /**
- * 查询(根据ID 批量查询)
- *
- * @param idList 主键ID列表(不能为 null 以及 empty)
- * @return 实体列表
- */
- List selectBatchIds(@Param(Constants.COLLECTION) Collection extends Serializable> idList);
-
-}
-```
-
-**测试代码:**
-
-```java
-@SpringBootTest
-class MybatisPlusRTests {
-
- @Autowired
- private UserMapper userMapper;
-
- @Test
- public void selectBatchIds() {
- // 查询ID为1、2的用户数据
- List users = userMapper.selectBatchIds(Arrays.asList(1, 2));
- users.forEach(System.out::println);
- }
-
-}
-```
-
-**控制台输出:**
-
-```sql
-==> Preparing: SELECT id,name,age,email,create_time,update_time,is_delete FROM user WHERE id IN ( ? , ? ) AND is_delete=0
-==> Parameters: 1(Integer), 2(Integer)
-<== Columns: id, name, age, email, create_time, update_time, is_delete
-<== Row: 1, Jone, 18, Jone@126.com, 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
-<== Row: 2, Jack, 20, Jack@126.com, 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
-<== Total: 2
-```
-
-```
-User(id=1, name=Jone, age=18, email=Jone@126.com, createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
-User(id=2, name=Jack, age=20, email=Jack@126.com, createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
-```
-
-## 简单的带条件查询
-
-第3个要介绍的查询操作是:根据 Map 集合封装好的条件来查询实体列表。
-
-```java
-// 其他 API 略
-public interface BaseMapper extends Mapper {
-
- /**
- * 查询(根据 columnMap 条件)
- *
- * @param columnMap 表字段 map 对象
- * @return 实体列表
- */
- List selectByMap(@Param(Constants.COLUMN_MAP) Map columnMap);
-
-}
-```
-
-**测试代码:**
-
-```java
-@SpringBootTest
-class MybatisPlusRTests {
-
- @Autowired
- private UserMapper userMapper;
-
- @Test
- void selectByMap() {
- // 通过 Map 封装条件
- // Map集合的键表示的是数据库列名
- HashMap columnMap = new HashMap<>();
- columnMap.put("name", "Jack");
- columnMap.put("age", "20");
-
- // 根据条件查询用户列表
- List users = userMapper.selectByMap(columnMap);
- users.forEach(System.out::println);
- }
-
-}
-```
-
-**控制台输出:**
-
-```sql
-==> Preparing: SELECT id,name,age,email,create_time,update_time,is_delete FROM user WHERE name = ? AND age = ? AND is_delete=0
-==> Parameters: Jack(String), 20(String)
-<== Columns: id, name, age, email, create_time, update_time, is_delete
-<== Row: 2, Jack, 20, Jack@126.com, 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
-<== Total: 1
-```
-
-```
-User(id=2, name=Jack, age=20, email=Jack@126.com, createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
-```
-
-## 分页
-
-在大多数批量查询操作的时候,我们都会采用分页技术来限制查询的条数,以优化效率及体验。
-
-所以第4个要介绍的查询操作就是分页查询,原来在使用 MyBatis 时,接入 [page-helper](https://pagehelper.github.io/) 就可以快速实现分页功能,而 MP 则自带分页插件,只要简单的配置就可以实现分页功能。
-
-**在项目中创建一个 MP 配置类,添加一个分页插件类的 Bean 配置。**
-
-```java
-/**
- * MyBatis Plus配置类
- */
-@Configuration
-public class MyBatisPlusConfig {
-
- @Bean
- public PaginationInterceptor paginationInterceptor() {
- PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
- // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
- // paginationInterceptor.setOverflow(false);
-
- // 设置最大单页限制数量,默认 500 条,-1 不受限制
- // paginationInterceptor.setLimit(500);
-
- // 开启 count 的 join 优化,只针对部分 left join
- paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
- return paginationInterceptor;
- }
-
-}
-```
-
-好了,接下来我们就可以测试分页查询 API 了。
-
-在 BaseMapper 中,一共提供了两种分页查询,它们都可以实现分页数据的查询,**唯一的区别在于查到的每页数据,究竟是要存储为实体数据集合,还是直接以键值对形式存到Map集合。**
-
-```java
-// 其他 API 略
-public interface BaseMapper extends Mapper {
-
- /**
- * 根据 entity 条件,查询全部记录(并翻页)
- *
- * @param page 分页查询条件(可以为 RowBounds.DEFAULT)
- * @param queryWrapper 实体对象封装操作类(可以为 null)
- * @return 分页数据(查询到的每页数据会映射为实体数据集合)
- */
- > E selectPage(E page, @Param(Constants.WRAPPER) Wrapper queryWrapper);
-
- /**
- * 根据 Wrapper 条件,查询全部记录(并翻页)
- *
- * @param page 分页查询条件
- * @param queryWrapper 实体对象封装操作类
- * @return 分页数据(查询到的每页数据会以键值对形式存入Map集合)
- */
- >> E selectMapsPage(E page, @Param(Constants.WRAPPER) Wrapper queryWrapper);
-
-}
-```
-
-笔者只带大家测试第一种分页查询 API,另一种你可以自行选择测试一下。
-
-**测试代码:**
-
-```java
-@SpringBootTest
-class MybatisPlusDemoApplication {
-
- @Autowired
- private UserMapper userMapper;
-
- @Test
- void testSelectPage() {
- // 创建分页对象,通过构造传入 当前页 和 每页显示条数
- Page page = new Page<>(1, 2);
-
- // 查询分页数据,条件构造器部分我们还是先传null
- userMapper.selectPage(page, null);
- // 分页数据输出
- System.out.println("数据总数:" + page.getTotal());
- System.out.println("总页数:" + page.getPages());
- System.out.println("当前页:" + page.getCurrent());
- System.out.println("页大小:" + page.getSize());
- // 每页的数据列表
- page.getRecords().forEach(System.out::println);
- }
-
-}
-```
-
-**控制台输出:**
-
-```sql
-==> Preparing: SELECT COUNT(1) FROM user WHERE is_delete = 0
-==> Parameters:
-<== Columns: COUNT(1)
-<== Row: 5
-==> Preparing: SELECT id,name,age,email,create_time,update_time,is_delete FROM user WHERE is_delete=0 LIMIT ?
-==> Parameters: 2(Long)
-<== Columns: id, name, age, email, create_time, update_time, is_delete
-<== Row: 1, Jone, 18, Jone@126.com, 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
-<== Row: 2, Jack, 20, Jack@126.com, 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
-<== Total: 2
-```
-
-```
-数据总数:5
-总页数:3
-当前页:1
-页大小:2
-User(id=1, name=Jone, age=18, email=Jone@126.com, createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
-User(id=2, name=Jack, age=20, email=Jack@126.com, createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
-```
-
-::: tip 笔者说
-测试完后,笔者再告诉你一件事儿,MP 的这款分页插件,支持的数据库也很多: MySQL 、MariaDB 、Oracle 、DB2 、H2 、HSQL 、SQL Lite 、PostgreSQL 、SQL Server 、Presto 、Gauss 、Firebird、Phoenix 、ClickHouse 、Sybase ASE 、 OceanBase 、达梦数据库 、虚谷数据库 、人大金仓数据库 、南大通用数据库。
-
-这么多种类的数据库支持,从我们日常使用上来讲绝对够用了!
-:::
-
-## 参考文献
-
-[1]MyBatis Plus 官网. 指南[EB/OL]. https://baomidou.com/guide/. 2021-01-18
-
-## 后记
-
-**C:** 好了,简单的查询操作就介绍到这儿了,这些基础查询是不是还挺简单的?下一篇,我们将介绍预热了两篇已久的条件构造器,肯定有很多同学等急了。别着急,好饭不怕晚,下一篇笔者就带你好好认识认识它。
-
-::: info 笔者说
-对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
-
-所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
-:::
diff --git a/docs/courses/mybatis/02-MyBatis-Plus基础/04-条件构造器.md b/docs/courses/mybatis/02-MyBatis-Plus基础/04-条件构造器.md
deleted file mode 100644
index b755d57af..000000000
--- a/docs/courses/mybatis/02-MyBatis-Plus基础/04-条件构造器.md
+++ /dev/null
@@ -1,643 +0,0 @@
----
-title: 条件构造器
-author: 查尔斯
-date: 2021/01/24 13:50
-categories:
- - MyBatis-Plus快速入门
-tags:
- - "MyBatis Plus"
- - MyBatis
- - ORM框架
----
-
-# 条件构造器
-
-## 前言
-
-**C:** 在前两篇 MP 的 CRUD API 演示中,笔者一直刻意忽略了一些带有条件构造器的 API,你说哪些是带有条件构造器的 API ?看下面。
-
-- `update(T entity, Wrapper updateWrapper) : int`
-- `delete(Wrapper queryWrapper) : int`
-- `selectOne(Wrapper queryWrapper) : T`
-- `selectCount(Wrapper queryWrapper) : Integer`
-- `selectList(Wrapper queryWrapper) : List`
-- `selectMaps(Wrapper queryWrapper) : List>`
-- `selectObjs(Wrapper queryWrapper) : List`
-- `selectPage(E page, Wrapper queryWrapper) : E`
-- `selectMapsPage(E page, Wrapper queryWrapper) : E`
-
-看到了吧?上述这些 API 中,都有一个显著的特点,即要求传递一个 Wrapper 类型的参数,而这个 Wrapper 类型就是笔者提到的条件构造器。
-
-预热了两篇之久,本篇,笔者将带着你入门 MP 的条件构造器,看看传统的 SQL 语句条件如何用 条件构造器API 组装出来。
-
-
-
-## 概述
-
-Wrapper 类型,顾名思义就是一个包装类,但它又不是我们常说的 Integer 这种包装类概念,在 MP 中它是用于包装各种丰富的查询条件的包装类。
-
-它是 MP 中条件包装类的顶级类,笔者在下方给你列出了它的类层次结构。当然,枝繁叶茂的它不可能就这几个子类,笔者只不过是挑重点的展示一下而已。
-
-
-
-但Wrapper 类是一个抽象类,所以我们不可能直接使用它,而是需要使用它的子类,那么哪些子类是我们常用的呢?一共有4个。
-
-- `QueryWrapper`:查询条件构造器
-
-- `UpdateWrapper`:更新条件构造器
-- `LambdaQueryWrapper`:使用 Lambda 语法的查询条件构造器
-- `LambdaUpdateWrapper`:使用 Lambda 语法的更新条件构造器
-
-接下来笔者就以 `QueryWrapper` 和 `LambdaQueryWrapper` 为例,带着大家体验一下条件构造器。至于其他两个,使用方法没有太大差别,所以笔者不再单独讲解了。
-
-我们还是先CV一个专用于条件构造器的单元测试类。
-
-
-
-## QueryWrapper
-
-QueryWrapper 是 MP 中主要用于查询操作的条件包装类,我们将借助《快速入门》篇提到过的查询实体列表 API 来测试一下它。
-
-```java
-// 其他 API 略
-public interface BaseMapper extends Mapper {
-
- /**
- * 根据 entity 条件,查询全部记录
- *
- * @param queryWrapper 实体对象封装操作类(可以为 null)
- */
- List selectList(@Param(Constants.WRAPPER) Wrapper queryWrapper);
-
-}
-```
-
-下方的基础 SQL 查询语法,大家应该都不陌生吧?
-
-```sql
-SELECT
- [ALL | DISTINCT] -- 是否去重
- {* | table_name.* | [table_name.field1 [AS alias1] [, table_name.field2 [AS alias2]][, ...]]} -- 查询哪些列
-FROM
- table_name [AS table_ alias] [, ...] -- 从哪些表查询
- [LEFT | RIGHT | INNER JOIN table_name2 [AS table_alias] ON 关联条件] -- 连接查询
-[WHERE ...] -- 根据条件来完成上述表数据的筛选
-[GROUP BY …] -- 根据一个或多个列分组
-[HAVING …] -- 分组查询后的筛选条件
-[ORDER BY… ] -- 根据一个或多个列排序
-[LIMIT {[offset,] row_count}] -- 限制查询结果数量和范围
-```
-
-笔者就是要以 QueryWrapper ,带大家体验一下如何用条件构造器,来实现上述的基础 SQL 查询。
-
-### 指定查询的列
-
-在写查询类 SQL 时,我们都要先指定好要查询哪些列。在 MP 的查询条件构造器中,可以通过 `select()` 方法来实现查询列的指定。
-
-- `select(String... columns) : QueryWrapper`
-- ...
-
-**测试代码:**
-
-```java
-@SpringBootTest
-class MybatisPlusWrapperTests {
-
- @Autowired
- private UserMapper userMapper;
-
- // 需求:查询用户的姓名和年龄信息
- @Test
- void testSelectList1() {
- // 创建查询条件构造器对象,通过泛型指定查询结果类型
- QueryWrapper queryWrapper = new QueryWrapper<>();
- // 指定查询 name、age 两列数据
- queryWrapper.select("name", "age");
-
- // 执行用户列表查询
- List userList = userMapper.selectList(queryWrapper);
-
- // 遍历用户列表
- userList.forEach(System.out::println);
- }
-}
-```
-
-**控制台输出:**
-
-```java
-==> Preparing: SELECT name,age FROM user WHERE is_delete=0
-==> Parameters:
-<== Columns: name, age
-<== Row: Jone, 18
-<== Row: Jack, 20
-<== Row: Tom, 28
-<== Row: Sandy, 21
-<== Row: Billie, 24
-<== Total: 5
-```
-
-```
-User(id=null, name=Jone, age=18, email=null, createTime=null, updateTime=null, isDelete=null)
-User(id=null, name=Jack, age=20, email=null, createTime=null, updateTime=null, isDelete=null)
-User(id=null, name=Tom, age=28, email=null, createTime=null, updateTime=null, isDelete=null)
-User(id=null, name=Sandy, age=21, email=null, createTime=null, updateTime=null, isDelete=null)
-User(id=null, name=Billie, age=24, email=null, createTime=null, updateTime=null, isDelete=null)
-```
-
-而且,你还可以借助这个方法实现结果去重功能,只需要在第一个指定的列名前添加 `DISTINCT` 关键字即可。
-
-```java
-// 创建查询条件构造器对象,通过泛型指定查询结果类型
-QueryWrapper queryWrapper = new QueryWrapper<>();
-// 指定查询 name、age 两列数据,并去重结果
-queryWrapper.select("DISTINCT name", "age");
-```
-
-最终执行的 SQL 就变成了这样:
-
-```sql
-SELECT DISTINCT name,age FROM user WHERE is_delete=0
-```
-
-::: tip 笔者说
-聪明如你,一定能举一反三,如果未来需要给查询列起别名、或者使用一些聚合函数,直接在 select() 方法中写就可以。
-:::
-
-### where条件子句
-
-查询类 SQL 中,在指定了查询哪些列之后,另一个非常重要的就是指定筛选结果数据的条件。
-
-where 条件子句就是 **用于检索数据表中符合条件记录** 的,**条件子句可以由一个或多个逻辑表达式组成,结果一般为真或假。**
-
-#### 关系运算符
-
-下方是 where 条件子句中常用关系运算 和 MP 中对应方法的对照表。
-
-| **SQL 运算符** | **含义** | **MP 对应方法** | 笔者帮你强化理解 |
-| -------------- | :------------- | ----------------- | -------------- |
-| = | 等于 | eq(R column, Object val) : Children | eq:equal |
-| <> 或 != | 不等于 | ne(R column, Object val) : Children | ne:not equal |
-| > | 大于 | gt(R column, Object val) : Children | gt:greater than |
-| < | 小于 | lt(R column, Object val) : Children | lt:less than |
-| >= | 大于等于 | ge(R column, Object val) : Children | ge:greater than or equal |
-| <= | 小于等于 | le(R column, Object val) : Children | le:less than or equal |
-| is null | 等于null | isNull(R column) : Children | |
-| is not null | 不等于null | isNotNull(R column) : Children | |
-
-**测试代码:**
-
-```java
-@SpringBootTest
-class MybatisPlusWrapperTests {
-
- @Autowired
- private UserMapper userMapper;
-
- // 需求:查询年龄 >= 21 的用户数据
- @Test
- void testSelectList2() {
- // 创建查询条件构造器对象,通过泛型指定查询结果类型
- QueryWrapper queryWrapper = new QueryWrapper<>();
- // ge:greater equals 大于等于
- queryWrapper.ge("age", 21);
-
- // 执行用户列表查询
- List userList = userMapper.selectList(queryWrapper);
-
- // 遍历用户列表
- userList.forEach(System.out::println);
- }
-
-}
-```
-
-**控制台输出:**
-
-```sql
-==> Preparing: SELECT id,name,age,email,create_time,update_time,is_delete FROM user WHERE is_delete=0 AND (age >= ?)
-==> Parameters: 21(Integer)
-<== Columns: id, name, age, email, create_time, update_time, is_delete
-<== Row: 3, Tom, 28, Tom@126.com, 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
-<== Row: 4, Sandy, 21, Sandy@126.com, 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
-<== Row: 5, Billie, 24, Billie@126.com, 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
-<== Total: 3
-```
-
-```
-User(id=3, name=Tom, age=28, email=Tom@126.com, createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
-User(id=4, name=Sandy, age=21, email=Sandy@126.com, createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
-User(id=5, name=Billie, age=24, email=Billie@126.com, createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
-```
-
-其他的笔者就不再测试了,你可以自己挨个试试。
-
-#### 逻辑运算符
-
-##### 普通逻辑
-
-| **SQL 运算符** | **含义** | **MP 对应方法** |
-| -------------- | :------------------------------- | ----------------------------------------- |
-| **AND** | 逻辑与,同时为真,结果才为真 | 默认使用 and 作为多个条件之间的关系表示 |
-| | 嵌套 and | and(Consumer\ consumer) : Children |
-| **OR** | 逻辑或,只要一个为真,则结果为真 | or() : Children |
-| | 嵌套 or | or(Consumer\ consumer) : Children |
-| **NOT** | 逻辑非,若操作数为假,结果则为真 | not(Consumer\ consumer) : Children |
-
-**测试代码:**
-
-```java
-@SpringBootTest
-class MybatisPlusWrapperTests {
-
- @Autowired
- private UserMapper userMapper;
-
- // 需求:查询年龄 >= 21 并且姓名为Tom的用户数据
- @Test
- void testSelectList3() {
- // 创建查询条件构造器对象,通过泛型指定查询结果类型
- QueryWrapper queryWrapper = new QueryWrapper<>();
- // 可以分开设置
- // queryWrapper.ge("age", 21);
- // queryWrapper.eq("name", "Tom");
- // 也可以链式操作
- queryWrapper.ge("age", 21)
- .eq("name", "Tom");
-
- // 执行用户列表查询
- List userList = userMapper.selectList(queryWrapper);
-
- // 遍历用户列表
- userList.forEach(System.out::println);
- }
-
-}
-```
-
-**控制台输出:**
-
-```sql
-==> Preparing: SELECT id,name,age,email,create_time,update_time,is_delete FROM user WHERE is_delete=0 AND (age >= ? AND name = ?)
-==> Parameters: 21(Integer), Tom(String)
-<== Columns: id, name, age, email, create_time, update_time, is_delete
-<== Row: 3, Tom, 28, Tom@126.com, 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
-<== Total: 1
-```
-
-```
-User(id=3, name=Tom, age=28, email=Tom@126.com, createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
-```
-
-当你编写了多个条件表达式的时候,MP 默认认为它们之间采用 `and` 关系。但如果你想实现 `or` 关系的需求,那就必须得使用 `or()` 方法了。
-
-**测试代码:**
-
-```java
-@SpringBootTest
-class MybatisPlusWrapperTests {
-
- @Autowired
- private UserMapper userMapper;
-
- // 需求:查询年龄 = 18 或者 年龄 = 24 的用户数据
- @Test
- void testSelectList4() {
- // 创建查询条件构造器对象,通过泛型指定查询结果类型
- QueryWrapper queryWrapper = new QueryWrapper<>();
- // 或者关系
- queryWrapper.eq("age", 18)
- .or()
- .eq("age", 24);
-
- // 执行用户列表查询
- List userList = userMapper.selectList(queryWrapper);
-
- // 遍历用户列表
- userList.forEach(System.out::println);
- }
-
-}
-```
-
-**控制台输出:**
-
-```sql
-==> Preparing: SELECT id,name,age,email,create_time,update_time,is_delete FROM user WHERE is_delete=0 AND (age = ? OR age = ?)
-==> Parameters: 18(Integer), 24(Integer)
-<== Columns: id, name, age, email, create_time, update_time, is_delete
-<== Row: 1, Jone, 18, Jone@126.com, 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
-<== Row: 5, Billie, 24, Billie@126.com, 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
-<== Total: 2
-```
-
-```
-User(id=1, name=Jone, age=18, email=Jone@126.com, createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
-User(id=5, name=Billie, age=24, email=Billie@126.com, createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
-```
-
-##### 嵌套逻辑
-
-我们看到上方的对照表中,还有一种是用于嵌套 or 或 嵌套 and 关系的方法,有些同学可能有点懵,看看下面这行代码是不是就明白了呢?
-
-```sql
-select xx from xx where xx = xx or (xx = xx and xx = xx)
-```
-
-**测试代码:**
-
-```java
-@SpringBootTest
-class MybatisPlusWrapperTests {
-
- @Autowired
- private UserMapper userMapper;
-
- // 需求:查询姓名中带有o字母 或者 (年龄 = 21 并且 姓名以S字母开头) 的用户数据
- @Test
- void testSelectList7() {
- // 创建查询条件构造器对象,通过泛型指定查询结果类型
- QueryWrapper queryWrapper = new QueryWrapper<>();
- queryWrapper.like("name", "o")
- .or(i -> i.eq("age", 21).likeRight("name", "S"));
-
- // 执行用户列表查询
- List userList = userMapper.selectList(queryWrapper);
-
- // 遍历用户列表
- userList.forEach(System.out::println);
- }
-
-}
-```
-
-**控制台输出:**
-
-```sql
-==> Preparing: SELECT id,name,age,email,create_time,update_time,is_delete FROM user WHERE is_delete=0 AND (name LIKE ? OR (age = ? AND name LIKE ?))
-==> Parameters: %o%(String), 21(Integer), S%(String)
-<== Columns: id, name, age, email, create_time, update_time, is_delete
-<== Row: 1, Jone, 18, Jone@126.com, 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
-<== Row: 3, Tom, 28, Tom@126.com, 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
-<== Row: 4, Sandy, 21, Sandy@126.com, 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
-<== Total: 3
-```
-
-```
-User(id=1, name=Jone, age=18, email=Jone@126.com, createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
-User(id=3, name=Tom, age=28, email=Tom@126.com, createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
-User(id=4, name=Sandy, age=21, email=Sandy@126.com, createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
-```
-
-#### 范围查询
-
-上面的 `or` 关系需求中,是对相同列的多个值的查询,这种情况在 SQL 中我们可以采用 in 查询来优化。而如果是对相同列的连续范围值的查询,则可以采用 between ... and ...查询来优化。
-
-下方是 in 查询 和 between ... and ...查询与 MP 中方法的对照表。
-
-| **SQL 运算符** | **含义** | **MP 对应方法** |
-| ----------------------- | :--------------- | --------------------------------------------------------- |
-| in (值1, 值2, ...) | 在某个列表之间 | in(R column, Object... values) : Children |
-| not in (值1, 值2, ...) | 不在某个列表之间 | notIn(R column, Object... value) : Children |
-| between ... and ... | 在某个范围之间 | between(R column, Object val1, Object val2) : Children |
-| not between ... and ... | 不在某个范围之间 | notBetween(R column, Object val1, Object val2) : Children |
-
-**测试代码:**
-
-```java
-@SpringBootTest
-class MybatisPlusWrapperTests {
-
- @Autowired
- private UserMapper userMapper;
-
- // 需求:查询年龄 = 18 或者 年龄 = 24 的用户数据
- @Test
- void testSelectList5() {
- // 创建查询条件构造器对象,通过泛型指定查询结果类型
- QueryWrapper queryWrapper = new QueryWrapper<>();
- // 如果是对相同列的多个值查询,使用in查询优化
- queryWrapper.in("age", 18, 24);
-
- // 执行用户列表查询
- List userList = userMapper.selectList(queryWrapper);
-
- // 遍历用户列表
- userList.forEach(System.out::println);
- }
-
-}
-```
-
-**控制台输出:**
-
-```sql
-==> Preparing: SELECT id,name,age,email,create_time,update_time,is_delete FROM user WHERE is_delete=0 AND (age IN (?,?))
-==> Parameters: 18(Integer), 24(Integer)
-<== Columns: id, name, age, email, create_time, update_time, is_delete
-<== Row: 1, Jone, 18, Jone@126.com, 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
-<== Row: 5, Billie, 24, Billie@126.com, 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
-<== Total: 2
-```
-
-```
-User(id=1, name=Jone, age=18, email=Jone@126.com, createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
-User(id=5, name=Billie, age=24, email=Billie@126.com, createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
-```
-
-#### 模糊查询
-
-除了精确的查询之外, 模糊查询也是使用频率很高的。下方是模糊查询 和 MP 中方法的对照表。
-
-| **SQL 运算符** | **含义** | **MP 对应方法** |
-| -------------- | :--------------- | ------------------------------------------ |
-| like '%A%' | 在某个范围之间 | like(R column, Object val) : Children |
-| not like '%A%' | 不在某个范围之间 | notLike(R column, Object val) : Children |
-| like '%A' | 查询 | likeLeft(R column, Object val) : Children |
-| like 'A%' | 不在某个列表之间 | likeRight(R column, Object val) : Children |
-
-**测试代码:**
-
-```java
-@SpringBootTest
-class MybatisPlusWrapperTests {
-
- @Autowired
- private UserMapper userMapper;
-
- // 需求:查询姓名中带有 o 字母的用户数据
- @Test
- void testSelectList6() {
- // 创建查询条件构造器对象,通过泛型指定查询结果类型
- QueryWrapper queryWrapper = new QueryWrapper<>();
- queryWrapper.like("name", "o");
-
- // 执行用户列表查询
- List userList = userMapper.selectList(queryWrapper);
-
- // 遍历用户列表
- userList.forEach(System.out::println);
- }
-
-}
-```
-
-**控制台输出:**
-
-```sql
-==> Preparing: SELECT id,name,age,email,create_time,update_time,is_delete FROM user WHERE is_delete=0 AND (name LIKE ?)
-==> Parameters: %o%(String)
-<== Columns: id, name, age, email, create_time, update_time, is_delete
-<== Row: 1, Jone, 18, Jone@126.com, 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
-<== Row: 3, Tom, 28, Tom@126.com, 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
-<== Total: 2
-```
-
-```
-User(id=1, name=Jone, age=18, email=Jone@126.com, createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
-User(id=3, name=Tom, age=28, email=Tom@126.com, createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
-```
-
-### 分组查询
-
-好了,写了这么多个查询示例,笔者相信聪明的你已经悟了。我们本篇毕竟不是去讲解 SQL 语句的,所以笔者最后把分组查询和排序查询的 API 再列一下,你想测试就自行测试一下吧。
-
-| **SQL 运算符** | **含义** | **MP 对应方法** | 示例 |
-| ----------------- | :--------------------- | ----------------------------------------------------- | ------------------------------------------------------------ |
-| group by 列1, 列2 | 根据一个列或多个列分组 | groupBy(R column) : Children | 例: groupBy("id", "name") |
-| having ... | 分组查询后的筛选条件 | having(String sqlHaving, Object... params) : Children | 例1: having("sum(age) > 10") 例2:having("sum(age) > {0}", 10) |
-
-### 排序查询
-
-| **SQL 运算符** | **含义** | **MP 对应方法** | 示例 |
-| ---------------- | :--------------- | ------------------------------------ | ----------------------------- |
-| order by 列 ASC | 按指定列升序排序 | orderByAsc(R... columns) : Children | 例: orderByAsc("id", "name") |
-| order by 列 DESC | 按指定列降序排序 | orderByDesc(R... columns) : Children | 例: orderByDesc("id", "name") |
-
-### 连接查询
-
-MP 的确很强大,但是它封装的接口都是针对单表的,所以如果我们要多表查询的时候,还是需要采用原生的 MyBatis 写法,这里笔者就不再介绍了。
-
-## LambdaQueryWrapper
-
-LambdaQueryWrapper 和 QueryWrapper 只差一个 Lambda 语法的使用而已,不知道你是否还记得《快速入门》篇中介绍的 MP 特点,其中有一条就是:**支持 Lambda 形式调用** :通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错。[1]
-
-**测试代码:**
-
-```java
-@SpringBootTest
-class MybatisPlusWrapperTests {
-
- @Autowired
- private UserMapper userMapper;
-
- // 需求:查询年龄 >= 21 的用户数据
- @Test
- void testSelectList8() {
- // 创建查询条件构造器对象,通过泛型指定查询结果类型
- LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
- // Lambda 语法无需再担心字段写错
- queryWrapper.ge(User::getAge, 21);
-
- // 执行用户列表查询
- List userList = userMapper.selectList(queryWrapper);
-
- // 遍历用户列表
- userList.forEach(System.out::println);
- }
-
-}
-```
-
-**控制台输出:**
-
-```sql
-==> Preparing: SELECT id,name,age,email,create_time,update_time,is_delete FROM user WHERE is_delete=0 AND (age >= ?)
-==> Parameters: 21(Integer)
-<== Columns: id, name, age, email, create_time, update_time, is_delete
-<== Row: 3, Tom, 28, Tom@126.com, 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
-<== Row: 4, Sandy, 21, Sandy@126.com, 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
-<== Row: 5, Billie, 24, Billie@126.com, 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
-<== Total: 3
-```
-
-```
-User(id=3, name=Tom, age=28, email=Tom@126.com, createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
-User(id=4, name=Sandy, age=21, email=Sandy@126.com, createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
-User(id=5, name=Billie, age=24, email=Billie@126.com, createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
-```
-
-## allEq
-
-最后,笔者再介绍一个比较实用的方法:`allEq`,当你得到的查询条件都被封装好在一个 Map 集合中时,为了使用 MP 查询这些条件,难道还要一个个的将数据取出来封装为 Wrapper 吗?显然我们是拒绝的。
-
-`allEq` 可以解决此问题。
-
-**测试代码:**
-
-```java
-@SpringBootTest
-class MybatisPlusWrapperTests {
-
- @Autowired
- private UserMapper userMapper;
-
- // 需求:查询年龄 = 28 并且姓名为Tom的用户数据
- @Test
- void testSelectList9() {
- // 创建查询条件构造器对象,通过泛型指定查询结果类型
- QueryWrapper queryWrapper = new QueryWrapper<>();
-
- // 模拟一个封装好查询条件的 Map 集合
- Map map = new HashMap<>();
- map.put("age", 28);
- map.put("name", "Tom");
-
- // 将Map直接传给Wrapper的allEq方法,它会自行识别查询条件
- queryWrapper.allEq(map);
-
- // 执行用户列表查询
- List userList = userMapper.selectList(queryWrapper);
-
- // 遍历用户列表
- userList.forEach(System.out::println);
- }
-
-}
-```
-
-**控制台输出:**
-
-```
-==> Preparing: SELECT id,name,age,email,create_time,update_time,is_delete FROM user WHERE is_delete=0 AND (name = ? AND age = ?)
-==> Parameters: Tom(String), 28(Integer)
-<== Columns: id, name, age, email, create_time, update_time, is_delete
-<== Row: 3, Tom, 28, Tom@126.com, 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
-<== Total: 1
-```
-
-```
-User(id=3, name=Tom, age=28, email=Tom@126.com, createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
-```
-
-::: warning 笔者说
-需要注意的是 `allEq()` 方法 **只能用于具有 and 关系且都是等值判断的情况** 。
-:::
-
-## 参考文献
-
-[1]MyBatis Plus 官网. 指南[EB/OL]. https://baomidou.com/guide/. 2021-01-18
-
-## 后记
-
-**C:** 好了,MP 的条件构造器就介绍完了。笔者有很多 API 没有带大家测试,但我想你也能明白,即便我全部测试了,它也不一定能被你用上,灵活使用它们是需要时间积累的。所以说,讲些典型的,让你 "悟了" MP 的大致规律和节奏,这才是最重要的。
-
-除了 API 之外,实际上,MP 还有一些功能,因为时间原因,笔者在本次系列并没有提及,你可以前往官网查阅一下,或者等后面笔者时间允许的时候,会再去介绍下它的其他功能。
-
-**最后的最后,笔者个人建议,在日常开发中,如果遇到的需求需要写一个比较复杂的 SQL,那还是直接使用 MyBatis 原生写法去 XML 中写吧,毕竟 MyBatis 最强大的优势还是体现在 XML 的 SQL 中。**
-
-::: info 笔者说
-对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
-
-所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
-:::
diff --git a/docs/courses/mybatis/02-MyBatis-Plus基础/05-代码生成器.md b/docs/courses/mybatis/02-MyBatis-Plus基础/05-代码生成器.md
deleted file mode 100644
index b2fee1c26..000000000
--- a/docs/courses/mybatis/02-MyBatis-Plus基础/05-代码生成器.md
+++ /dev/null
@@ -1,382 +0,0 @@
----
-title: 代码生成器
-author: 查尔斯
-date: 2021/02/24 22:34
-categories:
- - MyBatis-Plus快速入门
-tags:
- - "MyBatis Plus"
- - MyBatis
- - ORM框架
----
-
-# 代码生成器
-
-## 前言
-
-**C:** 做了程序员之后,你就会发现自己再也看不得一些繁琐重复的东西,尤其是写代码的时候。
-
-“好麻烦!这样写工作效率太低了吧!”
-
-“写这么多冗余代码干嘛?提出来不好吗?”
-
-....
-
-开玩笑的说,程序员这个岗位诞生之后,最终的目标就是 “取代同行,取悦自己,公司再取代你”。
-
-
-
-为了 “取悦” 自己(简化大量的重复工作量),各种自动化或生成工具应运而生,解决重复代码方面最为典型的就是代码生成器了。
-
-本篇,笔者就主要带你认识一下 MP 自带的代码生成器。
-
-## 简介
-
-::: tip 笔者说
-MP 诞生的目的就是为了简化 MyBatis,但简化后依然有些部分有重复性,这时候可以靠 MP 提供的一个代码生成器:AutoGenerator 来解决。
-
-通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。[1]
-:::
-
-## 使用步骤
-
-为了测试 MP 的代码生成器,记得提前将之前项目备份一下,这样我们就可以清空掉原来项目代码,然后使用 MP 的代码生成器来对之前数据库进行代码生成了。
-
-### 添加依赖
-
-MP 从 `3.0.3` 之后移除了代码生成器与模板引擎的默认依赖[1],所以,当我们需要使用代码生成器时,需要先添加一下这两个依赖。
-
-```xml
-
-
- com.baomidou
- mybatis-plus-generator
- 3.4.1
-
-
-
-
- org.freemarker
- freemarker
- 2.3.31
-
-```
-
-::: tip 笔者说
-笔者这里选择的模板引擎是 freemarker,它是 Apache 开源的一个知名模板技术。
-
-另外,MP 也支持 Velocity(默认)、Beetl,你可以选择自己熟悉的模板引擎,如果都不满足要求,也可以采用自定义模板引擎。[1]
-:::
-
-### 创建入口类
-
-添加好依赖之后,创建一个生成器入口类,就可以开始使用 MP 的代码生成器了。
-
-```java
-/**
- * MyBatis Plus 代码生成器入口类
- * 执行 main 方法控制台输入模块表名回车自动生成对应项目目录中
- *
- * @author Charles7c
- * @since 2021/1/28
- */
-public class CodeGenerator {
-
- public static void main(String[] args) {
-
- }
-
-}
-```
-
-### 编写配置
-
-MP 的代码生成器提供了大量的自定义配置参数供用户选择,能够满足绝大部分人的使用需求[1],接下来,我们在 `main` 方法中配置一下生成器。
-
-第一步:初始化代码生成器。
-
-```java
-// 创建代码生成器对象
-AutoGenerator generator = new AutoGenerator();
-// 指定模板引擎为FreeMarker,如果你使用默认的引擎velocity则无需此配置
-generator.setTemplateEngine(new FreemarkerTemplateEngine());
-```
-
-第二步:指定全局配置。
-
-```java
-// 创建全局配置对象
-GlobalConfig globalConfig = new GlobalConfig();
-// 指定生成文件的输出目录【默认 D 盘根目录】
-// System.getProperty("user.dir") 获取到的是当前项目的绝对路径
-// 输出目录示例:D:\IdeaProjects\mybatis-plus-demo\src\main\java
-globalConfig.setOutputDir(System.getProperty("user.dir") + "/src/main/java");
-// 指定开发人员
-globalConfig.setAuthor("Charles7c");
-// 指定是否打开输出目录
-globalConfig.setOpen(false);
-
-// 指定全局配置
-generator.setGlobalConfig(globalConfig);
-```
-
-第三步:指定包名配置,通过该配置,指定生成代码的包路径。
-
-```java
-// 创建包配置对象
-PackageConfig packageConfig = new PackageConfig();
-// 指定父包名
-packageConfig.setParent("com.example");
-
-// 指定包名配置
-generator.setPackageInfo(packageConfig);
-```
-
-::: tip 笔者说
-仅指定父包名就可以,MP 指定好了子包名。看看它的源码吧,实体类的包名默认是 `entity`,业务类的包名默认是 `service` ...
-:::
-
-
-
-
-第四步:指定数据源配置,通过该配置,指定需要生成代码的具体数据库。
-
-```java
-// 创建数据源配置对象
-DataSourceConfig dataSourceConfig = new DataSourceConfig();
-// 指定数据源信息
-dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/mybatisplus_demodb?serverTimezone=Asia/Shanghai&useUnicode=true&useSSL=false&characterEncoding=utf8");
-dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");
-dataSourceConfig.setUsername("root");
-dataSourceConfig.setPassword("root");
-
-// 指定数据源配置
-generator.setDataSource(dataSourceConfig);
-```
-
-第五步:指定策略配置,通过该配置,可指定需要生成哪些表或者排除哪些表。
-
-```java
-// 创建策略配置对象
-StrategyConfig strategyConfig = new StrategyConfig();
-// 指定数据库表映射到实体的命名策略
-// 下划线转驼峰命名:underline_to_camel
-// 不做任何改变,原样输出:no_change
-strategyConfig.setNaming(NamingStrategy.underline_to_camel);
-// 指定数据库表字段映射到实体的命名策略
-strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);
-// 指定实体类是否采用 lombok 注解
-strategyConfig.setEntityLombokModel(true);
-// 指定生成的Controller类是否采用@RestController注解
-// 默认false:@Controller -> true:@RestController
-strategyConfig.setRestControllerStyle(true);
-
-// 指定策略配置
-generator.setStrategy(strategyConfig);
-```
-
-第六步:执行生成。
-
-```java
-// 执行生成
-generator.execute();
-```
-
-### 测试生成
-
-
-
-### 模块化生成
-
-上方的使用方式是直接基于所有数据库表进行全部生成,但是当我们的项目进行了模块拆分,这时候我们可以对指定模块的指定表单独生成。
-
-拆分了模块的项目数据库表,示例如下:
-
-
-
-我们也模拟一下,将数据库表重命名为 `sys_user`。
-
-
-
-然后,我们改动一下第三步,添加模块名设置。
-
-```java
-// 创建包配置对象
-PackageConfig packageConfig = new PackageConfig();
-// 指定父包名
-packageConfig.setParent("com.example");
-// 指定父包下模块名,例如:用户模块user、角色模块role...
-// 如果拆分输入模块,则每个模块有自己的全套controller、service、dao...
-packageConfig.setModuleName(scanner("模块名"));
-
-// 指定包名配置
-generator.setPackageInfo(packageConfig);
-```
-
-再改动一下第五步,添加要进行代码生成的数据表设置及数据表前缀去除设置。
-
-```java
-// 创建策略配置对象
-StrategyConfig strategyConfig = new StrategyConfig();
-// 指定数据库表映射到实体的命名策略
-// 下划线转驼峰命名:underline_to_camel
-// 不做任何改变,原样输出:no_change
-strategyConfig.setNaming(NamingStrategy.underline_to_camel);
-// 指定数据库表字段映射到实体的命名策略
-strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);
-// 指定实体类是否采用 lombok 注解
-strategyConfig.setEntityLombokModel(true);
-// 指定生成的Controller类是否采用@RestController注解
-// 默认false:@Controller -> true:@RestController
-strategyConfig.setRestControllerStyle(true);
-// 指定需要包含的表名,允许正则表达式(与exclude二选一配置)
-strategyConfig.setInclude(scanner("表名,多个英文逗号分割").split(","));
-// 指定要去除的表前缀
-// 如果表是按模块划分,例如:系统模块的用户表是 sys_user,那么要去除的表前缀就是 sys_
-strategyConfig.setTablePrefix(packageConfig.getModuleName() + "_");
-
-// 指定策略配置
-generator.setStrategy(strategyConfig);
-```
-
-为了输入更加方便,我们可以定义一个 `scanner` 方法来动态输入。
-
-```java
-/**
- *
- * 读取控制台内容
- *
- */
-public static String scanner(String tip) {
- Scanner scanner = new Scanner(System.in);
- StringBuilder help = new StringBuilder();
- help.append("请输入" + tip + ":");
- System.out.println(help.toString());
- if (scanner.hasNext()) {
- String ipt = scanner.next();
- if (StringUtils.isNotBlank(ipt)) {
- return ipt;
- }
- }
- throw new MybatisPlusException("请输入正确的" + tip + "!");
-}
-```
-
-测试一下效果吧。
-
-
-
-::: tip 笔者说
-在策略配置中还可以通过 `setSuperXXClass(Class)` 系列方法对 Enity、Service、Controller 等设置自定义父类,这样生成的代码就直接会继承好指定类了。例如:`setSuperEntityClass()` 方法可以指定好 Entity 的父类,这样审计类信息就不用每个实体类里都来一份了,做数据填充设置也会更加方便。
-:::
-
-## 参考资料
-
-[1]MyBatis Plus 代码生成器:https://baomidou.com/guide/generator.html
-
-## 后记
-
-**C:** 最后在下方附上本篇的完整代码生成器配置代码,更多的配置你可以去看看 MP 官网代码生成器部分。其实所有的代码生成器都是那套思路,即模板 + 数据 + 模板引擎。后面抽时间,笔者也专门写一篇来介绍下常见的代码生成器实现思路,这样你也可以 “取悦” 一下你自己。
-
-```java
-// 演示例子,执行 main 方法控制台输入模块表名回车自动生成对应项目目录中
-public class CodeGenerator {
-
- /**
- * 读取控制台内容
- */
- public static String scanner(String tip) {
- Scanner scanner = new Scanner(System.in);
- StringBuilder help = new StringBuilder();
- help.append("请输入" + tip + ":");
- System.out.println(help.toString());
- if (scanner.hasNext()) {
- String ipt = scanner.next();
- if (StringUtils.isNotBlank(ipt)) {
- return ipt;
- }
- }
- throw new MybatisPlusException("请输入正确的" + tip + "!");
- }
-
- public static void main(String[] args) {
- // 1.初始化代码生成器
- // 创建代码生成器对象
- AutoGenerator generator = new AutoGenerator();
- // 指定模板引擎为FreeMarker,如果你使用默认的引擎velocity则无需此配置
- generator.setTemplateEngine(new FreemarkerTemplateEngine());
-
- // 2.指定全局配置
- // 创建全局配置对象
- GlobalConfig globalConfig = new GlobalConfig();
- // 指定生成文件的输出目录【默认 D 盘根目录】
- // System.getProperty("user.dir") 获取到的是当前项目的绝对路径
- // 输出目录示例:D:\IdeaProjects\mybatis-plus-demo\src\main\java
- globalConfig.setOutputDir(System.getProperty("user.dir") + "/src/main/java");
- // 指定开发人员
- globalConfig.setAuthor("Charles7c");
- // 指定是否打开输出目录
- globalConfig.setOpen(false);
-
- // 指定全局配置
- generator.setGlobalConfig(globalConfig);
-
- // 3.指定包名配置
- // 创建包配置对象
- PackageConfig packageConfig = new PackageConfig();
- // 指定父包名
- packageConfig.setParent("com.example");
- // 指定父包下模块名,例如:用户模块user、角色模块role...
- // 如果拆分输入模块,则每个模块有自己的全套controller、service、dao...
- packageConfig.setModuleName(scanner("模块名"));
-
- // 指定包名配置
- generator.setPackageInfo(packageConfig);
-
- // 4.指定数据源配置
- // 创建数据源配置对象
- DataSourceConfig dataSourceConfig = new DataSourceConfig();
- // 指定数据源信息
- dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/mybatisplus_demodb?serverTimezone=Asia/Shanghai&useUnicode=true&useSSL=false&characterEncoding=utf8");
- dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");
- dataSourceConfig.setUsername("root");
- dataSourceConfig.setPassword("root");
-
- // 指定数据源配置
- generator.setDataSource(dataSourceConfig);
-
- // 5.指定策略配置
- // 创建策略配置对象
- StrategyConfig strategyConfig = new StrategyConfig();
- // 指定数据库表映射到实体的命名策略
- // 下划线转驼峰命名:underline_to_camel
- // 不做任何改变,原样输出:no_change
- strategyConfig.setNaming(NamingStrategy.underline_to_camel);
- // 指定数据库表字段映射到实体的命名策略
- strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);
- // 指定实体类是否采用 lombok 注解
- strategyConfig.setEntityLombokModel(true);
- // 指定生成的Controller类是否采用@RestController注解
- // 默认false:@Controller -> true:@RestController
- strategyConfig.setRestControllerStyle(true);
- // 指定需要包含的表名,允许正则表达式(与exclude二选一配置)
- strategyConfig.setInclude(scanner("表名,多个英文逗号分割").split(","));
- // 指定要去除的表前缀
- // 如果表是按模块划分,例如:系统模块的用户表是 sys_user,那么要去除的表前缀就是 sys_
- strategyConfig.setTablePrefix(packageConfig.getModuleName() + "_");
-
- // 指定策略配置
- generator.setStrategy(strategyConfig);
-
- // 执行生成
- generator.execute();
- }
-
-}
-```
-
-::: info 笔者说
-对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
-
-所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
-:::
diff --git a/docs/courses/mybatis/index.md b/docs/courses/mybatis/index.md
deleted file mode 100644
index cdde01a6c..000000000
--- a/docs/courses/mybatis/index.md
+++ /dev/null
@@ -1,132 +0,0 @@
----
-title: MyBatis快速入门
-author: 查尔斯
-date: 2020/12/25 14:49
-categories:
- - MyBatis快速入门
-tags:
- - MyBatis
- - ORM框架
----
-
-# MyBatis快速入门
-
-## 前言
-
-**C:** 在 Java Web 开发中,我们通常将后台开发拆分为三层架构,分别是:表现层、业务层、持久层。
-
-在持久层中,最开始我们使用原生 JDBC 来进行数据库的 CRUD,代码繁琐的令人抓狂。后来随着学习深入,我们利用 DAO (Data Access Object) 模式对 JDBC 进行了一定的优化封装。
-
-即便如此,还是要在 Java 代码中编写大量的 SQL 语句,参数判断等,下面是我截取的一段 DAO模式封装后的代码,你简单感受一下。
-
-```java
-// 假设BaseDao已经封装了通用CRUD操作
-public class UserDaoImpl extends BaseDao implements UserDao {
-
- // 根据条件查询用户列表
- @Override
- public List findByMap(Map params) throws Exception {
- // 动态拼接SQL语句
- StringBuffer sqlBuffer = new StringBuffer();
- // 动态拼接SQL占位符参数
- List paramsList = new ArrayList<>();
-
- sqlBuffer.append(" select ");
- sqlBuffer.append(" * ");
- sqlBuffer.append(" from ");
- sqlBuffer.append(" user ");
- sqlBuffer.append(" where 1 = 1 ");
-
- // 根据用户名模糊查询
- Object name = params.get("name");
- if (EmptyUtils.isNotEmpty(name)) {
- sqlBuffer.append(" and name like CONCAT('%',?,'%') ");
- paramsList.add(name);
- }
-
- // 根据年龄查询
- Integer age = (Integer) params.get("age");
- if (EmptyUtils.isNotEmpty(age)) {
- sqlBuffer.append(" and age = ? ");
- paramsList.add(age);
- }
-
- return this.selectList(sqlBuffer.toString(), paramsList.toArray(), User.class);
- }
-
- // 保存用户
- @Override
- public int save(User user) throws Exception {
- StringBuffer sqlBuffer = new StringBuffer();
-
- sqlBuffer.append(" insert into user ");
- sqlBuffer.append(" (name, age, email) ");
- sqlBuffer.append(" values(?, ?, ?) ");
-
- Object[] params = {user.getName(), user.getAge(), user.getEmail()};
-
- return this.insert(sqlBuffer.toString(), params);
- }
-}
-```
-
-有问题存在,就不缺解决问题的人。在行业内随之诞生了大量的持久层解决方案,除了各自公司自研的方案之外,比较有名的通用开源方案有:Hibernate、MyBatis等,各有各的优劣势,在此我先不谈论它们的区别,只说一个现象:在国内来讲MyBatis应用相对广泛。
-
-
-
-所以那没什么好说的了,直接学吧?
-
-开玩笑的,其实MyBatis在国内应用广泛的原因是因为相对于Hibernate等,它可以更加灵活的编写SQL语句,对于需求变动比较频繁,业务比较复杂,高并发要求较高的应用,优势显而易见。国内这两年互联网发展的挺快,互联网用户群体基数大,而国外人群规模相对较少,相比于国内开发者,他们更关注实现效率而非极致的性能。
-
-## 简介
-
-::: tip 笔者说
-MyBatis读音是:[mai'bətɪs](买杯涕死),原是 Apache 软件基金会的一个[开源项目](https://baike.baidu.com/item/开源项目/3406069) iBatis , 2010年这个项目由 Apache 软件基金会迁移到了 Google Code 平台,并且改名为 MyBatis 。2013年11月再迁移到 [GitHub](https://baike.baidu.com/item/Github/10145341)。
-
-MyBatis 是一款优秀的 **半自动的持久层ORM框架** ,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
-
-当前,最新版本是MyBatis 3.5.6 ,其发布时间是2020年10月6日。[1]
-
-:::
-
-::: details **1.什么是 ORM ?** [2]
-ORM,对象关系映射(Object Relation Mapping,简称ORM,或O/RM,或O/R Mapping),它是随着面向对象的软件开发方法发展而产生的。
-
-面向对象的开发方法是当今企业级应用开发环境中的主流开发方法,关系数据库是企业级应用环境中永久存放数据的主流数据存储系统。对象和关系数据是业务实体的两种表现形式,业务实体在内存中表现为对象,在数据库中表现为关系数据。内存中的对象之间存在关联和继承关系,而在数据库中,关系数据无法直接表达多对多关联和继承关系。因此,对象-关系映射( ORM )系统一般以中间件的形式存在,主要实现程序对象到关系数据库数据的映射。
-
-
-面向对象是从软件工程基本原则(如耦合、聚合、封装)的基础上发展起来的,而关系数据库则是从数学理论发展而来的,两套理论存在显著的区别。为了解决这个不匹配的现象,对象关系映射技术应运而生。
-
-**它可以有效解决数据库与程序间的异构性,实现面向对象编程语言里不同类型系统的数据之间的转换。**
-
-**大白话:** 一个完整的ORM框架,可以使得我们在具体的操作业务对象的时候,不需要再去和复杂的SQL语句之流打交道,只需简单的操作对象的属性即可。例如:在指定好对象和数据库映射之后,要保存一个用户数据,只需要创建好用户对象,然后调用ORM解决方案,就会自动将对象数据持久化到数据库中。(无需关心SQL,ORM自动生成SQL) ;要更新一个用户信息,只需要在程序中对该用户对象的属性进行更新,ORM解决方案就会自动将更改后的结果持久化到数据库中。
-:::
-
-::: details **2.为什么说 MyBatis 是半自动 ORM ?** [2]
-Mybatis 在查询关联对象或关联集合对象时,需要手动编写 SQL 来完成,所以称之为半自动 ORM 映射工具。也可以说 MyBatis 是一个业务实体对象和 SQL 之间的映射工具。
-:::
-
-## 特点
-
-**C:** 笔者其实不愿意先给你介绍一个技术的特点,因为没有用过,光靠说是不行的。只有在我们实际体验之后,它的优缺点才可以感受到了,不过提前先了解一下,也更有学习动力,千万记得学习的过程中及时思考和理解这些特点。
-
-**优势:**
-
-- **简单易学:** 框架规模小,学习门槛低(官方文档简单详细),与 JDBC 相比,减少了50%以上的代码量,消除了 JDBC 大量冗余的代码,不需要手动开关连接。
-- **灵活度高:** 半自动化 ORM,程序员直接编写原生态 SQL ,可严格控制 SQL 执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如:互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。
-- **解除 SQL 与程序代码的耦合:** SQL 代码从程序代码中彻底分离,写在XML里,可重用。
-- **提供映射标签,支持对象与数据库的 ORM 字段映射**
-- **提供 XML 标签,支持编写动态 SQL :** 在使用 JDBC 的过程中, 根据条件进行 SQL 的拼接是很麻烦且很容易出错的。 MyBatis 动态 SQL 的出现, 解决了这个麻烦。
-
-**劣势:**
-
-- **SQL语句的编写工作量较大**:尤其当字段多,关联表多时,对开发人员编写SQL语句的功底有一定要求。
-- **数据库无关性、移植性差:** SQL语句依赖数据库,导致数据库移植性差,不能随意更换数据库。[3]
-
-## 参考文献
-
-[1]MyBatis 官网. MyBatis 简介[EB/OL]. https://mybatis.org/mybatis-3/zh/index.html. 2020-12-25
-
-[2]laouei. 理解 ORM 和数据持久化[EB/OL]. https://blog.csdn.net/u012585964/article/details/52412520. 2016-09-02
-
-[3]W3CSchool. MyBatis 教程[EB/OL]. https://www.w3cschool.cn/mybatis/. 2020-12-25
\ No newline at end of file
diff --git a/docs/courses/mysql/01-MySQL基础/01-MySQL安装与配置.md b/docs/courses/mysql/01-MySQL基础/01-MySQL安装与配置.md
deleted file mode 100644
index 4210f3f71..000000000
--- a/docs/courses/mysql/01-MySQL基础/01-MySQL安装与配置.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-title: MySQL安装与配置
-author: 查尔斯
-date: 2022/10/22 21:25
-categories:
- - MySQL快速入门
-tags:
- - MySQL
- - Linux
-showArticleMetadata: false
-editLink: false
-lastUpdated: false
-showComment: false
----
-
-# MySQL安装与配置
-
-::: tip 未完待续......
-
-:::
diff --git a/docs/courses/mysql/03-附录/01-CentOS安装MySQL.md b/docs/courses/mysql/03-附录/01-CentOS安装MySQL.md
deleted file mode 100644
index 29fdecca4..000000000
--- a/docs/courses/mysql/03-附录/01-CentOS安装MySQL.md
+++ /dev/null
@@ -1,214 +0,0 @@
----
-title: CentOS 8.2 安装 MySQL 5.7.39
-author: 查尔斯
-date: 2022/10/22 21:30
-categories:
- - MySQL快速入门
-tags:
- - MySQL
- - Linux
- - CentOS
-showComment: false
----
-
-# CentOS 8.2 安装 MySQL 5.7.39
-
-## 检查系统是否自带MySQL
-
-::: warning 笔者说
-检查系统中是否已经安装了 MySQL 或 MariaDB ,如果已经安装了,那就必须提前卸载掉它们,否则它们占用的端口号、服务名或是一些其他配置很可能会干扰到后续我们要安装的 MySQL 版本。
-
-以前笔者就遇到过几次由这个情况引发的问题,要么是 MySQL 无法安装成功,要么是 MySQL 安装成功后输入正确密码却登录不进去。
-:::
-
-```shell
-rpm -qa | grep mysql
-rpm -qa | grep mariadb
-# 如果上方两条命令查询出了内容,就把查出的软件卸载掉
-rpm -e --nodeps 软件名
-```
-
-## 安装依赖
-
-::: warning 笔者说
-和在 Windows 系统中一样,Linux 系统中安装程序也需要准备好所需的运行库。
-
-不确认安装好它们,那在安装 MySQL 时就会出现这样那样的依赖报错,像下面这段报错就是由于没有安装好 `libaio` 库引起的。
-
->/bin/mysqld: error while loading shared libraries: libaio.so.1: cannot open shared object file: No such file or directory
-:::
-
-```shell
-yum -y install numactl
-yum -y install libncurses*
-yum -y install libaio
-```
-
-## 下载并上传安装包
-
-可前往 [官网](https://downloads.mysql.com/archives/community) 下载 MySQL Linux 安装包然后上传到服务器。
-
-
-
-也可以直接在服务器内下载。
-
-```shell
-wget https://cdn.mysql.com/archives/mysql-5.7/mysql-5.7.39-linux-glibc2.12-x86_64.tar.gz
-```
-
-## 解压安装包
-
-::: warning 笔者说
-除去一些固定的东西,一定要记得根据你实际的情况调整好目录位置或命名。
-:::
-
-```shell
-# 解压安装包到指定目录(如指定目录不存在则需要先提前用 mkdir 创建)
-# 下方 /opt/disk 是服务器的一块数据盘挂载目录
-tar -zxvf mysql-5.7.39-linux-glibc2.12-x86_64.tar.gz -C /opt/disk
-
-# 重命名目录
-cd /opt/disk
-mv mysql-5.7.39-linux-glibc2.12-x86_64 mysql
-
-# 创建 MySQL 数据存储目录
-cd mysql
-mkdir data
-```
-
-## 创建mysql用户组和mysql用户
-
-```shell
-# 创建 mysql 用户组
-groupadd mysql
-
-# 创建 mysql 用户
-useradd -r -g mysql mysql
-
-# 将 MySQL 安装目录授权给 mysql 用户组的 mysql 用户
-chown -R mysql:mysql ./
-```
-
-## 创建配置文件
-
-创建 my_default.cnf 配置文件。
-
-```shell
-vim /opt/disk/mysql/support-files/my_default.cnf
-```
-
-将下方内容插入到配置文件中,保存并退出编辑。
-
-::: tip 笔者说
-下方的配置中,指定了 MySQL 安装目录、MySQL 数据存储目录、MySQL 服务占用端口、MySQL 默认字符集、MySQL 日志文件位置、MySQL 进程文件位置等。
-
-**一定记得根据你实际的情况调整好。**
-:::
-
-```shell
-[mysqld]
-sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
-
-basedir = /opt/disk/mysql
-datadir = /opt/disk/mysql/data
-port = 3306
-socket = /tmp/mysql.sock
-character-set-server=utf8
-
-log-error = /opt/disk/mysql/data/mysqld.log
-pid-file = /opt/disk/mysql/data/mysqld.pid
-```
-
-拷贝一份配置文件到 /etc 目录下,命名为 `my.cnf`。
-
-```shell
-cp support-files/my_default.cnf /etc/my.cnf
-```
-
-## 安装并启动MySQL服务
-
-使用 `mysqld` 命令来安装 MySQL 服务,并指定好用户名、MySQL 安装目录、MySQL 数据存储目录。
-
-```shell
-bin/mysqld --initialize --user=mysql --basedir=/opt/disk/mysql/ --datadir=/opt/disk/mysql/data/
-```
-
-拷贝一份 MySQL 服务脚本到 `/etc/init.d` 目录下,命名为 `mysql`。
-
-```shell
-cp support-files/mysql.server /etc/init.d/mysql
-```
-
-启动 MySQL 服务。
-
-```shell
-systemctl start mysql
-```
-
-配置 MySQL 开机自启动。
-
-```shell
-systemctl enable mysql
-```
-到此,MySQL 服务就安装完成了。但别着急,还需要做些配置才能真正用起来。
-
-## 设置环境变量
-
-::: tip 笔者说
-配置好环境变量,我们才能方便的在任何目录下使用 MySQL 的命令。
-:::
-
-```shell
-# 1、打开 profile 文件
-vim /etc/profile
-
-# 2、在其中插入环境变量配置
-MYSQL_HOME=/opt/disk/mysql
-PATH=$MYSQL_HOME/bin:$PATH
-export MYSQL_HOME PATH
-
-# 3、重新加载 profile 文件,使最新配置生效
-source /etc/profile
-```
-
-## 登录并修改密码
-
-第一次登录时,首先从日志文件中找到随机生成的密码。
-
-```shell
-cat /opt/disk/mysql/data/mysqld.log
-```
-
-在日志文件中找到类似于下方输出的位置,其中 `8QE2NEqhB:ks` 就是密码。
-
-```
-[Note] A temporary password is generated for root@localhost: 8QE2NEqhB:ks
-```
-
-登录到 MySQL 服务端。
-
-::: warning 笔者说
-有些时候随机生成的密码包含特殊符号,例如:`&`、`/`、`.`(你没看错,有时候最后有个 `.` 可千万别当作是句子结尾。
-
-这种密码你在登录的时候,记得用 `'` 单引号给它引起来。
-
-例如:`mysql -uroot -p'7AB5CDadE&'`
-:::
-
-```shell
-mysql -uroot -p刚才从日志中找到的随机密码
-```
-
-修改密码。
-
-```shell
-set password = password('新密码');
-```
-
-## 创建用户并授权
-
-这一步就要根据你的实际个人需要来操作了。
-
-```shell
-grant all on *.* to root@'%' identified by '你的密码';
-```
diff --git a/docs/courses/mysql/03-附录/02-Docker安装MySQL.md b/docs/courses/mysql/03-附录/02-Docker安装MySQL.md
deleted file mode 100644
index 0a0561698..000000000
--- a/docs/courses/mysql/03-附录/02-Docker安装MySQL.md
+++ /dev/null
@@ -1,148 +0,0 @@
----
-title: Docker 安装 MySQL 详细步骤
-author: 查尔斯
-date: 2022/10/30 17:36
-categories:
- - MySQL快速入门
-tags:
- - MySQL
- - Docker
- - 容器
-showComment: false
----
-
-# Docker 安装 MySQL 详细步骤
-
-::: tip 笔者说
-笔者下面的步骤及配置是基于指定版本的实践,大多数程序大多数情况下在相差不大的版本时可以直接参考。(当然了,即使是非 Docker 方式安装程序也是一样道理)
-:::
-
-## 拉取镜像
-
-::: warning 笔者说
-拉取镜像时需要明确镜像版本(Tag)。
-:::
-
-不指定版本(Tag)就拉取镜像,那拉取下来的镜像版本(Tag)默认是 `latest`(最新的)。`latest` 会跟随 Docker Registry 中的记录变化,现在拉取下来的 `latest` 是 x1 版本,但隔了一段时间后你在其他机器上再拉取 `latest` 可能就是 x2 版本了。
-
-变化的版本,不利于生产环境部署的稳定。无论是后续在其他环境部署还是扩容集群等场景均要求根据架构要求指定好版本。
-
-```shell
-docker pull mysql:8.0.29
-```
-
-## 运行容器
-
-::: warning 笔者说
-**下方的配置,切记要根据个人实际情况来修改。**
-:::
-
-- 容器的名称
-- 镜像名称:版本
-- 是否设置自启动?
-- 环境变量配置
-- 是否端口映射?
-- 映射的话映射到宿主机哪个端口?
-- 是否挂载卷?
-- 挂载的话要挂载宿主机哪个目录?
-- ......
-- 等自定义配置
-
-```shell
-# MYSQL_ROOT_PASSWORD:root 用户密码
-# MYSQL_DATABASE:初始化数据库
-# MYSQL_USER:初始化普通用户
-# MYSQL_PASSWORD:初始化普通用户密码
-docker run -d \
---name mysql mysql:8.0.29 \
---restart=always \
--e TZ=Asia/Shanghai \
--e MYSQL_ROOT_PASSWORD=123456 \
--e MYSQL_DATABASE=test \
--e MYSQL_USER=test \
--e MYSQL_PASSWORD=123456 \
--p 3306:3306 \
--v /opt/disk/docker/volumes/mysql/conf:/etc/mysql/conf.d \
--v /opt/disk/docker/volumes/mysql/data:/var/lib/mysql \
--v /opt/disk/docker/volumes/mysql/logs:/logs \
-# 使用该参数,容器内的 root 用户才拥有真正的 root 权限
---privileged=true
-```
-
-## 验证
-
-服务器开放好相应端口或设置好安全组规则后,直接用 Navicat 连接即可。
-
-## Docker Compose脚本
-
-如果你是用的 docker-compose 来安装,下方附上相应 docker-compose.yml 脚本内容。
-
-```yaml
-version: '3'
-services:
- mysql:
- container_name: mysql
- image: mysql:8.0.29
- environment:
- TZ: Asia/Shanghai
- MYSQL_ROOT_PASSWORD: 123456
- MYSQL_DATABASE: test
- MYSQL_USER: test
- MYSQL_PASSWORD: 123456
- ports:
- - 3306:3306
- volumes:
- - /opt/disk/docker/volumes/mysql/conf:/etc/mysql/conf.d
- - /opt/disk/docker/volumes/mysql/data:/var/lib/mysql
- - /opt/disk/docker/volumes/mysql/logs:/logs
- privileged: true
-```
-
-编写好 docker-compose.yml 脚本后,在脚本同级目录执行下方命令即可。
-
-```shell
-docker-compose up -d
-```
-
-## 附:安装MariaDB
-
-### 运行容器
-
-```shell
-docker run -d \
---name mariadb mariadb \
---restart=always \
--e TZ=Asia/Shanghai \
--e MYSQL_ROOT_PASSWORD=123456 \
--e MYSQL_DATABASE=test \
--e MYSQL_USER=test \
--e MYSQL_PASSWORD=123456 \
--p 3306:3306 \
--v /opt/disk/docker/volumes/mysql/conf:/etc/mysql/conf.d \
--v /opt/disk/docker/volumes/mysql/data:/var/lib/mysql \
---privileged=true
-```
-
-### Docker Compose脚本
-
-```yaml
-version: '3'
-services:
- mariadb:
- container_name: mariadb
- image: mariadb
- restart: always
- environment:
- TZ: Asia/Shanghai
- MYSQL_ROOT_PASSWORD: 123456
- MYSQL_DATABASE: test
- MYSQL_USER: test
- MYSQL_PASSWORD: 123456
- ports:
- - 3306:3306
- volumes:
- - /opt/disk/docker/volumes/mysql/conf:/etc/mysql/conf.d
- - /opt/disk/docker/volumes/mysql/data:/var/lib/mysql
- privileged: true
-```
-
diff --git a/docs/courses/mysql/index.md b/docs/courses/mysql/index.md
deleted file mode 100644
index 49ab2e8c1..000000000
--- a/docs/courses/mysql/index.md
+++ /dev/null
@@ -1,19 +0,0 @@
----
-title: MySQL快速入门
-author: 查尔斯
-date: 2022/10/22 21:21
-categories:
- - MySQL快速入门
-tags:
- - MySQL
-showArticleMetadata: false
-editLink: false
-lastUpdated: false
-showComment: false
----
-
-# MySQL快速入门
-
-::: tip 未完待续......
-
-:::
diff --git a/docs/index.md b/docs/index.md
index 641b10de1..78bd2c78c 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -14,7 +14,7 @@ hero:
actions:
- theme: brand
text: 快速开始
- link: /categories/issues/index
+ link: /categories/category1/index
- theme: alt
text: 在 GitHub 查看
link: https://github.com/Charles7c/charles7c.github.io
diff --git a/docs/public/CNAME b/docs/public/CNAME
deleted file mode 100644
index 1643c66ad..000000000
--- a/docs/public/CNAME
+++ /dev/null
@@ -1 +0,0 @@
-blog.charles7c.top
\ No newline at end of file
diff --git a/docs/public/img/2020/10/01/202010011205888.jpg b/docs/public/img/2020/10/01/202010011205888.jpg
deleted file mode 100644
index d04acbb73..000000000
Binary files a/docs/public/img/2020/10/01/202010011205888.jpg and /dev/null differ
diff --git a/docs/public/img/2020/10/01/202010011205999.jpg b/docs/public/img/2020/10/01/202010011205999.jpg
deleted file mode 100644
index de09eff02..000000000
Binary files a/docs/public/img/2020/10/01/202010011205999.jpg and /dev/null differ
diff --git a/docs/public/img/2020/10/01/202010011206888.png b/docs/public/img/2020/10/01/202010011206888.png
deleted file mode 100644
index 79e2ce7bf..000000000
Binary files a/docs/public/img/2020/10/01/202010011206888.png and /dev/null differ
diff --git a/docs/public/img/2020/10/01/202010011206999.png b/docs/public/img/2020/10/01/202010011206999.png
deleted file mode 100644
index e1ead0ce5..000000000
Binary files a/docs/public/img/2020/10/01/202010011206999.png and /dev/null differ
diff --git a/docs/public/img/2020/10/01/202010011209888.png b/docs/public/img/2020/10/01/202010011209888.png
deleted file mode 100644
index 5ea5379e7..000000000
Binary files a/docs/public/img/2020/10/01/202010011209888.png and /dev/null differ
diff --git a/docs/public/img/2020/10/01/202010011209999.png b/docs/public/img/2020/10/01/202010011209999.png
deleted file mode 100644
index 04a5eb692..000000000
Binary files a/docs/public/img/2020/10/01/202010011209999.png and /dev/null differ
diff --git a/docs/public/img/2020/10/01/202010011210888.png b/docs/public/img/2020/10/01/202010011210888.png
deleted file mode 100644
index 34cacdf0f..000000000
Binary files a/docs/public/img/2020/10/01/202010011210888.png and /dev/null differ
diff --git a/docs/public/img/2020/10/01/202010011210999.png b/docs/public/img/2020/10/01/202010011210999.png
deleted file mode 100644
index 5b234f26c..000000000
Binary files a/docs/public/img/2020/10/01/202010011210999.png and /dev/null differ
diff --git a/docs/public/img/2020/10/01/202010011211888.jpg b/docs/public/img/2020/10/01/202010011211888.jpg
deleted file mode 100644
index 9c568554a..000000000
Binary files a/docs/public/img/2020/10/01/202010011211888.jpg and /dev/null differ
diff --git a/docs/public/img/2020/10/01/202010011212999.png b/docs/public/img/2020/10/01/202010011212999.png
deleted file mode 100644
index b9b2ee249..000000000
Binary files a/docs/public/img/2020/10/01/202010011212999.png and /dev/null differ
diff --git a/docs/public/img/2020/10/01/202010011215888.png b/docs/public/img/2020/10/01/202010011215888.png
deleted file mode 100644
index 8e3542802..000000000
Binary files a/docs/public/img/2020/10/01/202010011215888.png and /dev/null differ
diff --git a/docs/public/img/2020/10/01/202010011216999.png b/docs/public/img/2020/10/01/202010011216999.png
deleted file mode 100644
index 49bb91449..000000000
Binary files a/docs/public/img/2020/10/01/202010011216999.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021219728.jpg b/docs/public/img/2020/10/02/202010021219728.jpg
deleted file mode 100644
index cf9125f4f..000000000
Binary files a/docs/public/img/2020/10/02/202010021219728.jpg and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021221992.png b/docs/public/img/2020/10/02/202010021221992.png
deleted file mode 100644
index f86884512..000000000
Binary files a/docs/public/img/2020/10/02/202010021221992.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021222666.png b/docs/public/img/2020/10/02/202010021222666.png
deleted file mode 100644
index 7693fc55d..000000000
Binary files a/docs/public/img/2020/10/02/202010021222666.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021222777.png b/docs/public/img/2020/10/02/202010021222777.png
deleted file mode 100644
index a0187717e..000000000
Binary files a/docs/public/img/2020/10/02/202010021222777.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021222888.png b/docs/public/img/2020/10/02/202010021222888.png
deleted file mode 100644
index 69271719f..000000000
Binary files a/docs/public/img/2020/10/02/202010021222888.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021223666.png b/docs/public/img/2020/10/02/202010021223666.png
deleted file mode 100644
index ebed400cb..000000000
Binary files a/docs/public/img/2020/10/02/202010021223666.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021223777.png b/docs/public/img/2020/10/02/202010021223777.png
deleted file mode 100644
index 879a9b484..000000000
Binary files a/docs/public/img/2020/10/02/202010021223777.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021224888.png b/docs/public/img/2020/10/02/202010021224888.png
deleted file mode 100644
index 9feebcf0d..000000000
Binary files a/docs/public/img/2020/10/02/202010021224888.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021224999.png b/docs/public/img/2020/10/02/202010021224999.png
deleted file mode 100644
index ed2d10b45..000000000
Binary files a/docs/public/img/2020/10/02/202010021224999.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021225560.png b/docs/public/img/2020/10/02/202010021225560.png
deleted file mode 100644
index 6d6f43455..000000000
Binary files a/docs/public/img/2020/10/02/202010021225560.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021225676.png b/docs/public/img/2020/10/02/202010021225676.png
deleted file mode 100644
index f1e0f19ef..000000000
Binary files a/docs/public/img/2020/10/02/202010021225676.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021226600.png b/docs/public/img/2020/10/02/202010021226600.png
deleted file mode 100644
index 921656d32..000000000
Binary files a/docs/public/img/2020/10/02/202010021226600.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021226999.png b/docs/public/img/2020/10/02/202010021226999.png
deleted file mode 100644
index 61b7877d1..000000000
Binary files a/docs/public/img/2020/10/02/202010021226999.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021227666.png b/docs/public/img/2020/10/02/202010021227666.png
deleted file mode 100644
index cc74fd014..000000000
Binary files a/docs/public/img/2020/10/02/202010021227666.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021227777.png b/docs/public/img/2020/10/02/202010021227777.png
deleted file mode 100644
index de181b758..000000000
Binary files a/docs/public/img/2020/10/02/202010021227777.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021227888.png b/docs/public/img/2020/10/02/202010021227888.png
deleted file mode 100644
index 5da7abe7a..000000000
Binary files a/docs/public/img/2020/10/02/202010021227888.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021228963.png b/docs/public/img/2020/10/02/202010021228963.png
deleted file mode 100644
index 1ebc3b856..000000000
Binary files a/docs/public/img/2020/10/02/202010021228963.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021228999.png b/docs/public/img/2020/10/02/202010021228999.png
deleted file mode 100644
index 4f3ee0ebb..000000000
Binary files a/docs/public/img/2020/10/02/202010021228999.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021229777.png b/docs/public/img/2020/10/02/202010021229777.png
deleted file mode 100644
index 3c5e160c6..000000000
Binary files a/docs/public/img/2020/10/02/202010021229777.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021232169.png b/docs/public/img/2020/10/02/202010021232169.png
deleted file mode 100644
index 7d223ad97..000000000
Binary files a/docs/public/img/2020/10/02/202010021232169.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021232222.png b/docs/public/img/2020/10/02/202010021232222.png
deleted file mode 100644
index 7917fc0de..000000000
Binary files a/docs/public/img/2020/10/02/202010021232222.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021232586.png b/docs/public/img/2020/10/02/202010021232586.png
deleted file mode 100644
index d63615cc5..000000000
Binary files a/docs/public/img/2020/10/02/202010021232586.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021233888.png b/docs/public/img/2020/10/02/202010021233888.png
deleted file mode 100644
index ce6844c9a..000000000
Binary files a/docs/public/img/2020/10/02/202010021233888.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021234766.png b/docs/public/img/2020/10/02/202010021234766.png
deleted file mode 100644
index 96a4a9be2..000000000
Binary files a/docs/public/img/2020/10/02/202010021234766.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021234888.png b/docs/public/img/2020/10/02/202010021234888.png
deleted file mode 100644
index f979b41e5..000000000
Binary files a/docs/public/img/2020/10/02/202010021234888.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021234999.png b/docs/public/img/2020/10/02/202010021234999.png
deleted file mode 100644
index cacc9a41d..000000000
Binary files a/docs/public/img/2020/10/02/202010021234999.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021235666.png b/docs/public/img/2020/10/02/202010021235666.png
deleted file mode 100644
index 743f17912..000000000
Binary files a/docs/public/img/2020/10/02/202010021235666.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021235777.png b/docs/public/img/2020/10/02/202010021235777.png
deleted file mode 100644
index e00a76767..000000000
Binary files a/docs/public/img/2020/10/02/202010021235777.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021235888.png b/docs/public/img/2020/10/02/202010021235888.png
deleted file mode 100644
index a94e9bcde..000000000
Binary files a/docs/public/img/2020/10/02/202010021235888.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021235999.png b/docs/public/img/2020/10/02/202010021235999.png
deleted file mode 100644
index 3b5b16128..000000000
Binary files a/docs/public/img/2020/10/02/202010021235999.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021236761.png b/docs/public/img/2020/10/02/202010021236761.png
deleted file mode 100644
index 8a1bb416e..000000000
Binary files a/docs/public/img/2020/10/02/202010021236761.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021236888.png b/docs/public/img/2020/10/02/202010021236888.png
deleted file mode 100644
index 58aee59ae..000000000
Binary files a/docs/public/img/2020/10/02/202010021236888.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021236999.png b/docs/public/img/2020/10/02/202010021236999.png
deleted file mode 100644
index 16134bf4a..000000000
Binary files a/docs/public/img/2020/10/02/202010021236999.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021237281.png b/docs/public/img/2020/10/02/202010021237281.png
deleted file mode 100644
index f70dcee2c..000000000
Binary files a/docs/public/img/2020/10/02/202010021237281.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021237777.png b/docs/public/img/2020/10/02/202010021237777.png
deleted file mode 100644
index 99444533e..000000000
Binary files a/docs/public/img/2020/10/02/202010021237777.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021237888.png b/docs/public/img/2020/10/02/202010021237888.png
deleted file mode 100644
index a24240191..000000000
Binary files a/docs/public/img/2020/10/02/202010021237888.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021238777.png b/docs/public/img/2020/10/02/202010021238777.png
deleted file mode 100644
index 12ec5a53d..000000000
Binary files a/docs/public/img/2020/10/02/202010021238777.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021238999.png b/docs/public/img/2020/10/02/202010021238999.png
deleted file mode 100644
index 7f1ea3adb..000000000
Binary files a/docs/public/img/2020/10/02/202010021238999.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021239111.png b/docs/public/img/2020/10/02/202010021239111.png
deleted file mode 100644
index d7bf9536e..000000000
Binary files a/docs/public/img/2020/10/02/202010021239111.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021240572.png b/docs/public/img/2020/10/02/202010021240572.png
deleted file mode 100644
index d88766c79..000000000
Binary files a/docs/public/img/2020/10/02/202010021240572.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021241888.png b/docs/public/img/2020/10/02/202010021241888.png
deleted file mode 100644
index 421467d52..000000000
Binary files a/docs/public/img/2020/10/02/202010021241888.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021241999.png b/docs/public/img/2020/10/02/202010021241999.png
deleted file mode 100644
index f2d51b48c..000000000
Binary files a/docs/public/img/2020/10/02/202010021241999.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021242277.png b/docs/public/img/2020/10/02/202010021242277.png
deleted file mode 100644
index 6b4e2a918..000000000
Binary files a/docs/public/img/2020/10/02/202010021242277.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021243229.png b/docs/public/img/2020/10/02/202010021243229.png
deleted file mode 100644
index b76b4f950..000000000
Binary files a/docs/public/img/2020/10/02/202010021243229.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021243777.png b/docs/public/img/2020/10/02/202010021243777.png
deleted file mode 100644
index a7f48f9d6..000000000
Binary files a/docs/public/img/2020/10/02/202010021243777.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021243888.png b/docs/public/img/2020/10/02/202010021243888.png
deleted file mode 100644
index f7b8a74fa..000000000
Binary files a/docs/public/img/2020/10/02/202010021243888.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021245777.png b/docs/public/img/2020/10/02/202010021245777.png
deleted file mode 100644
index ae63ccf91..000000000
Binary files a/docs/public/img/2020/10/02/202010021245777.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021245888.png b/docs/public/img/2020/10/02/202010021245888.png
deleted file mode 100644
index 8882f1084..000000000
Binary files a/docs/public/img/2020/10/02/202010021245888.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021245999.png b/docs/public/img/2020/10/02/202010021245999.png
deleted file mode 100644
index 5978b968d..000000000
Binary files a/docs/public/img/2020/10/02/202010021245999.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021246666.png b/docs/public/img/2020/10/02/202010021246666.png
deleted file mode 100644
index f678675a1..000000000
Binary files a/docs/public/img/2020/10/02/202010021246666.png and /dev/null differ
diff --git a/docs/public/img/2020/10/02/202010021246777.png b/docs/public/img/2020/10/02/202010021246777.png
deleted file mode 100644
index edac7c3c1..000000000
Binary files a/docs/public/img/2020/10/02/202010021246777.png and /dev/null differ
diff --git a/docs/public/img/2020/10/03/202010031920791.jpg b/docs/public/img/2020/10/03/202010031920791.jpg
deleted file mode 100644
index 25d1a9497..000000000
Binary files a/docs/public/img/2020/10/03/202010031920791.jpg and /dev/null differ
diff --git a/docs/public/img/2020/10/03/202010031921578.png b/docs/public/img/2020/10/03/202010031921578.png
deleted file mode 100644
index 9840e980a..000000000
Binary files a/docs/public/img/2020/10/03/202010031921578.png and /dev/null differ
diff --git a/docs/public/img/2020/10/03/202010031922920.png b/docs/public/img/2020/10/03/202010031922920.png
deleted file mode 100644
index 0c721b499..000000000
Binary files a/docs/public/img/2020/10/03/202010031922920.png and /dev/null differ
diff --git a/docs/public/img/2020/10/03/202010031923150.png b/docs/public/img/2020/10/03/202010031923150.png
deleted file mode 100644
index 6e1d89878..000000000
Binary files a/docs/public/img/2020/10/03/202010031923150.png and /dev/null differ
diff --git a/docs/public/img/2020/10/03/202010031923562.png b/docs/public/img/2020/10/03/202010031923562.png
deleted file mode 100644
index b3111b73b..000000000
Binary files a/docs/public/img/2020/10/03/202010031923562.png and /dev/null differ
diff --git a/docs/public/img/2020/10/03/202010031925166.png b/docs/public/img/2020/10/03/202010031925166.png
deleted file mode 100644
index 99a3d8622..000000000
Binary files a/docs/public/img/2020/10/03/202010031925166.png and /dev/null differ
diff --git a/docs/public/img/2020/10/03/202010031926318.jpeg b/docs/public/img/2020/10/03/202010031926318.jpeg
deleted file mode 100644
index 7379aacd1..000000000
Binary files a/docs/public/img/2020/10/03/202010031926318.jpeg and /dev/null differ
diff --git a/docs/public/img/2020/10/03/202010031926870.png b/docs/public/img/2020/10/03/202010031926870.png
deleted file mode 100644
index 2ff33fd55..000000000
Binary files a/docs/public/img/2020/10/03/202010031926870.png and /dev/null differ
diff --git a/docs/public/img/2020/10/03/202010031927096.png b/docs/public/img/2020/10/03/202010031927096.png
deleted file mode 100644
index 7346edd02..000000000
Binary files a/docs/public/img/2020/10/03/202010031927096.png and /dev/null differ
diff --git a/docs/public/img/2020/10/03/202010031927359.jpeg b/docs/public/img/2020/10/03/202010031927359.jpeg
deleted file mode 100644
index 540dc3f0b..000000000
Binary files a/docs/public/img/2020/10/03/202010031927359.jpeg and /dev/null differ
diff --git a/docs/public/img/2020/10/03/202010031927876.png b/docs/public/img/2020/10/03/202010031927876.png
deleted file mode 100644
index 801c97df3..000000000
Binary files a/docs/public/img/2020/10/03/202010031927876.png and /dev/null differ
diff --git a/docs/public/img/2020/10/03/202010031927888.png b/docs/public/img/2020/10/03/202010031927888.png
deleted file mode 100644
index 865cc3758..000000000
Binary files a/docs/public/img/2020/10/03/202010031927888.png and /dev/null differ
diff --git a/docs/public/img/2020/10/03/202010031928091.png b/docs/public/img/2020/10/03/202010031928091.png
deleted file mode 100644
index 46461668c..000000000
Binary files a/docs/public/img/2020/10/03/202010031928091.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042056777.jpg b/docs/public/img/2020/10/04/202010042056777.jpg
deleted file mode 100644
index c93a97ad9..000000000
Binary files a/docs/public/img/2020/10/04/202010042056777.jpg and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042057777.png b/docs/public/img/2020/10/04/202010042057777.png
deleted file mode 100644
index 76429d95e..000000000
Binary files a/docs/public/img/2020/10/04/202010042057777.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042058590.png b/docs/public/img/2020/10/04/202010042058590.png
deleted file mode 100644
index 797322f65..000000000
Binary files a/docs/public/img/2020/10/04/202010042058590.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042059387.png b/docs/public/img/2020/10/04/202010042059387.png
deleted file mode 100644
index 08692cfe0..000000000
Binary files a/docs/public/img/2020/10/04/202010042059387.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042059697.png b/docs/public/img/2020/10/04/202010042059697.png
deleted file mode 100644
index 797322f65..000000000
Binary files a/docs/public/img/2020/10/04/202010042059697.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042059777.png b/docs/public/img/2020/10/04/202010042059777.png
deleted file mode 100644
index ddcc09248..000000000
Binary files a/docs/public/img/2020/10/04/202010042059777.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042059888.png b/docs/public/img/2020/10/04/202010042059888.png
deleted file mode 100644
index 7ba27bd8a..000000000
Binary files a/docs/public/img/2020/10/04/202010042059888.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042059936.png b/docs/public/img/2020/10/04/202010042059936.png
deleted file mode 100644
index 1b419baa5..000000000
Binary files a/docs/public/img/2020/10/04/202010042059936.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042103135.png b/docs/public/img/2020/10/04/202010042103135.png
deleted file mode 100644
index 291c8742d..000000000
Binary files a/docs/public/img/2020/10/04/202010042103135.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042103962.png b/docs/public/img/2020/10/04/202010042103962.png
deleted file mode 100644
index 1e3c87561..000000000
Binary files a/docs/public/img/2020/10/04/202010042103962.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042104888.png b/docs/public/img/2020/10/04/202010042104888.png
deleted file mode 100644
index 9e2064181..000000000
Binary files a/docs/public/img/2020/10/04/202010042104888.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042105575.png b/docs/public/img/2020/10/04/202010042105575.png
deleted file mode 100644
index 38e685514..000000000
Binary files a/docs/public/img/2020/10/04/202010042105575.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042105769.png b/docs/public/img/2020/10/04/202010042105769.png
deleted file mode 100644
index 642392474..000000000
Binary files a/docs/public/img/2020/10/04/202010042105769.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042106033.png b/docs/public/img/2020/10/04/202010042106033.png
deleted file mode 100644
index ff3dd7bcc..000000000
Binary files a/docs/public/img/2020/10/04/202010042106033.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042106083.png b/docs/public/img/2020/10/04/202010042106083.png
deleted file mode 100644
index 6636af1ef..000000000
Binary files a/docs/public/img/2020/10/04/202010042106083.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042106188.png b/docs/public/img/2020/10/04/202010042106188.png
deleted file mode 100644
index 72d13e433..000000000
Binary files a/docs/public/img/2020/10/04/202010042106188.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042106777.png b/docs/public/img/2020/10/04/202010042106777.png
deleted file mode 100644
index 41d849e37..000000000
Binary files a/docs/public/img/2020/10/04/202010042106777.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042107605.png b/docs/public/img/2020/10/04/202010042107605.png
deleted file mode 100644
index 2c12bbf1d..000000000
Binary files a/docs/public/img/2020/10/04/202010042107605.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042107888.png b/docs/public/img/2020/10/04/202010042107888.png
deleted file mode 100644
index db56ce162..000000000
Binary files a/docs/public/img/2020/10/04/202010042107888.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042108666.png b/docs/public/img/2020/10/04/202010042108666.png
deleted file mode 100644
index c1e2ba33b..000000000
Binary files a/docs/public/img/2020/10/04/202010042108666.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042108777.png b/docs/public/img/2020/10/04/202010042108777.png
deleted file mode 100644
index 8282ded86..000000000
Binary files a/docs/public/img/2020/10/04/202010042108777.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042108888.png b/docs/public/img/2020/10/04/202010042108888.png
deleted file mode 100644
index f10510b7c..000000000
Binary files a/docs/public/img/2020/10/04/202010042108888.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042108999.png b/docs/public/img/2020/10/04/202010042108999.png
deleted file mode 100644
index dd9109454..000000000
Binary files a/docs/public/img/2020/10/04/202010042108999.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042109666.png b/docs/public/img/2020/10/04/202010042109666.png
deleted file mode 100644
index fac2c4ea3..000000000
Binary files a/docs/public/img/2020/10/04/202010042109666.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042109777.png b/docs/public/img/2020/10/04/202010042109777.png
deleted file mode 100644
index 415663f00..000000000
Binary files a/docs/public/img/2020/10/04/202010042109777.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042109888.png b/docs/public/img/2020/10/04/202010042109888.png
deleted file mode 100644
index 85e35f507..000000000
Binary files a/docs/public/img/2020/10/04/202010042109888.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042109999.png b/docs/public/img/2020/10/04/202010042109999.png
deleted file mode 100644
index 95a55018d..000000000
Binary files a/docs/public/img/2020/10/04/202010042109999.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042110867.png b/docs/public/img/2020/10/04/202010042110867.png
deleted file mode 100644
index 326621689..000000000
Binary files a/docs/public/img/2020/10/04/202010042110867.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042110888.png b/docs/public/img/2020/10/04/202010042110888.png
deleted file mode 100644
index 1d44261e2..000000000
Binary files a/docs/public/img/2020/10/04/202010042110888.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042111057.png b/docs/public/img/2020/10/04/202010042111057.png
deleted file mode 100644
index af8ba5b25..000000000
Binary files a/docs/public/img/2020/10/04/202010042111057.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042111555.png b/docs/public/img/2020/10/04/202010042111555.png
deleted file mode 100644
index c63a966c0..000000000
Binary files a/docs/public/img/2020/10/04/202010042111555.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042111666.png b/docs/public/img/2020/10/04/202010042111666.png
deleted file mode 100644
index 369fb391e..000000000
Binary files a/docs/public/img/2020/10/04/202010042111666.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042111777.png b/docs/public/img/2020/10/04/202010042111777.png
deleted file mode 100644
index 13b625fec..000000000
Binary files a/docs/public/img/2020/10/04/202010042111777.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042111888.png b/docs/public/img/2020/10/04/202010042111888.png
deleted file mode 100644
index 02a5f4fa8..000000000
Binary files a/docs/public/img/2020/10/04/202010042111888.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042111999.png b/docs/public/img/2020/10/04/202010042111999.png
deleted file mode 100644
index f0d1da248..000000000
Binary files a/docs/public/img/2020/10/04/202010042111999.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042112666.png b/docs/public/img/2020/10/04/202010042112666.png
deleted file mode 100644
index aa5a4a5b7..000000000
Binary files a/docs/public/img/2020/10/04/202010042112666.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042112777.png b/docs/public/img/2020/10/04/202010042112777.png
deleted file mode 100644
index 409faaea6..000000000
Binary files a/docs/public/img/2020/10/04/202010042112777.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042112888.png b/docs/public/img/2020/10/04/202010042112888.png
deleted file mode 100644
index 32e1adafc..000000000
Binary files a/docs/public/img/2020/10/04/202010042112888.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042112999.png b/docs/public/img/2020/10/04/202010042112999.png
deleted file mode 100644
index 326e59b54..000000000
Binary files a/docs/public/img/2020/10/04/202010042112999.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042115222.png b/docs/public/img/2020/10/04/202010042115222.png
deleted file mode 100644
index 86af0eca6..000000000
Binary files a/docs/public/img/2020/10/04/202010042115222.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042115555.png b/docs/public/img/2020/10/04/202010042115555.png
deleted file mode 100644
index b5cfa6ea5..000000000
Binary files a/docs/public/img/2020/10/04/202010042115555.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042115666.png b/docs/public/img/2020/10/04/202010042115666.png
deleted file mode 100644
index e1ae80baf..000000000
Binary files a/docs/public/img/2020/10/04/202010042115666.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042115777.png b/docs/public/img/2020/10/04/202010042115777.png
deleted file mode 100644
index 436da50b0..000000000
Binary files a/docs/public/img/2020/10/04/202010042115777.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042115888.png b/docs/public/img/2020/10/04/202010042115888.png
deleted file mode 100644
index 6218da227..000000000
Binary files a/docs/public/img/2020/10/04/202010042115888.png and /dev/null differ
diff --git a/docs/public/img/2020/10/04/202010042115979.jpeg b/docs/public/img/2020/10/04/202010042115979.jpeg
deleted file mode 100644
index 3ffa848aa..000000000
Binary files a/docs/public/img/2020/10/04/202010042115979.jpeg and /dev/null differ
diff --git a/docs/public/img/2020/10/05/202010052011277.jpeg b/docs/public/img/2020/10/05/202010052011277.jpeg
deleted file mode 100644
index 4e4102b2a..000000000
Binary files a/docs/public/img/2020/10/05/202010052011277.jpeg and /dev/null differ
diff --git a/docs/public/img/2020/10/05/202010052011666.png b/docs/public/img/2020/10/05/202010052011666.png
deleted file mode 100644
index 74ae6a0f0..000000000
Binary files a/docs/public/img/2020/10/05/202010052011666.png and /dev/null differ
diff --git a/docs/public/img/2020/10/05/202010052012169.jpeg b/docs/public/img/2020/10/05/202010052012169.jpeg
deleted file mode 100644
index fe744bad9..000000000
Binary files a/docs/public/img/2020/10/05/202010052012169.jpeg and /dev/null differ
diff --git a/docs/public/img/2020/10/05/202010052013521.png b/docs/public/img/2020/10/05/202010052013521.png
deleted file mode 100644
index 043c3c26c..000000000
Binary files a/docs/public/img/2020/10/05/202010052013521.png and /dev/null differ
diff --git a/docs/public/img/2020/10/05/202010052013602.png b/docs/public/img/2020/10/05/202010052013602.png
deleted file mode 100644
index 956760f08..000000000
Binary files a/docs/public/img/2020/10/05/202010052013602.png and /dev/null differ
diff --git a/docs/public/img/2020/10/05/202010052013767.png b/docs/public/img/2020/10/05/202010052013767.png
deleted file mode 100644
index 9d2bba9f0..000000000
Binary files a/docs/public/img/2020/10/05/202010052013767.png and /dev/null differ
diff --git a/docs/public/img/2020/10/05/202010052015666.png b/docs/public/img/2020/10/05/202010052015666.png
deleted file mode 100644
index 6d464cb1d..000000000
Binary files a/docs/public/img/2020/10/05/202010052015666.png and /dev/null differ
diff --git a/docs/public/img/2020/10/05/202010052016288.png b/docs/public/img/2020/10/05/202010052016288.png
deleted file mode 100644
index af62c71c2..000000000
Binary files a/docs/public/img/2020/10/05/202010052016288.png and /dev/null differ
diff --git a/docs/public/img/2020/10/05/202010052017669.png b/docs/public/img/2020/10/05/202010052017669.png
deleted file mode 100644
index aa4f7277b..000000000
Binary files a/docs/public/img/2020/10/05/202010052017669.png and /dev/null differ
diff --git a/docs/public/img/2020/10/05/202010052017777.png b/docs/public/img/2020/10/05/202010052017777.png
deleted file mode 100644
index af62c71c2..000000000
Binary files a/docs/public/img/2020/10/05/202010052017777.png and /dev/null differ
diff --git a/docs/public/img/2020/10/05/202010052018229.png b/docs/public/img/2020/10/05/202010052018229.png
deleted file mode 100644
index 2701bde6c..000000000
Binary files a/docs/public/img/2020/10/05/202010052018229.png and /dev/null differ
diff --git a/docs/public/img/2020/10/05/202010052019576.png b/docs/public/img/2020/10/05/202010052019576.png
deleted file mode 100644
index 598d75c87..000000000
Binary files a/docs/public/img/2020/10/05/202010052019576.png and /dev/null differ
diff --git a/docs/public/img/2020/10/05/202010052019666.png b/docs/public/img/2020/10/05/202010052019666.png
deleted file mode 100644
index 17ee473a9..000000000
Binary files a/docs/public/img/2020/10/05/202010052019666.png and /dev/null differ
diff --git a/docs/public/img/2020/10/05/202010052020276.jpeg b/docs/public/img/2020/10/05/202010052020276.jpeg
deleted file mode 100644
index af4d59229..000000000
Binary files a/docs/public/img/2020/10/05/202010052020276.jpeg and /dev/null differ
diff --git a/docs/public/img/2020/10/06/202010061318922.jpeg b/docs/public/img/2020/10/06/202010061318922.jpeg
deleted file mode 100644
index f08e7c780..000000000
Binary files a/docs/public/img/2020/10/06/202010061318922.jpeg and /dev/null differ
diff --git a/docs/public/img/2020/10/06/202010061319222.png b/docs/public/img/2020/10/06/202010061319222.png
deleted file mode 100644
index 9db5addf7..000000000
Binary files a/docs/public/img/2020/10/06/202010061319222.png and /dev/null differ
diff --git a/docs/public/img/2020/10/06/202010061319719.png b/docs/public/img/2020/10/06/202010061319719.png
deleted file mode 100644
index 4c3ed5115..000000000
Binary files a/docs/public/img/2020/10/06/202010061319719.png and /dev/null differ
diff --git a/docs/public/img/2020/10/06/202010061319888.jpeg b/docs/public/img/2020/10/06/202010061319888.jpeg
deleted file mode 100644
index 860d6b3a6..000000000
Binary files a/docs/public/img/2020/10/06/202010061319888.jpeg and /dev/null differ
diff --git a/docs/public/img/2020/10/06/202010061320187.jpeg b/docs/public/img/2020/10/06/202010061320187.jpeg
deleted file mode 100644
index 0696d1c23..000000000
Binary files a/docs/public/img/2020/10/06/202010061320187.jpeg and /dev/null differ
diff --git a/docs/public/img/2020/10/06/202010061320866.jpeg b/docs/public/img/2020/10/06/202010061320866.jpeg
deleted file mode 100644
index fbc23dcbe..000000000
Binary files a/docs/public/img/2020/10/06/202010061320866.jpeg and /dev/null differ
diff --git a/docs/public/img/2020/10/06/202010061322666.jpeg b/docs/public/img/2020/10/06/202010061322666.jpeg
deleted file mode 100644
index 719eb3d26..000000000
Binary files a/docs/public/img/2020/10/06/202010061322666.jpeg and /dev/null differ
diff --git a/docs/public/img/2020/10/06/202010061322880.png b/docs/public/img/2020/10/06/202010061322880.png
deleted file mode 100644
index 06e014cd8..000000000
Binary files a/docs/public/img/2020/10/06/202010061322880.png and /dev/null differ
diff --git a/docs/public/img/2020/10/06/202010061323309.png b/docs/public/img/2020/10/06/202010061323309.png
deleted file mode 100644
index 5388a48f6..000000000
Binary files a/docs/public/img/2020/10/06/202010061323309.png and /dev/null differ
diff --git a/docs/public/img/2020/10/06/202010061325322.png b/docs/public/img/2020/10/06/202010061325322.png
deleted file mode 100644
index ed1f12815..000000000
Binary files a/docs/public/img/2020/10/06/202010061325322.png and /dev/null differ
diff --git a/docs/public/img/2020/10/06/202010061326166.jpeg b/docs/public/img/2020/10/06/202010061326166.jpeg
deleted file mode 100644
index fbc23dcbe..000000000
Binary files a/docs/public/img/2020/10/06/202010061326166.jpeg and /dev/null differ
diff --git a/docs/public/img/2020/10/07/202010061032999.png b/docs/public/img/2020/10/07/202010061032999.png
deleted file mode 100644
index 7cb34a1bf..000000000
Binary files a/docs/public/img/2020/10/07/202010061032999.png and /dev/null differ
diff --git a/docs/public/img/2020/10/07/202010071016008.png b/docs/public/img/2020/10/07/202010071016008.png
deleted file mode 100644
index 1d22707e4..000000000
Binary files a/docs/public/img/2020/10/07/202010071016008.png and /dev/null differ
diff --git a/docs/public/img/2020/10/07/202010071016056.png b/docs/public/img/2020/10/07/202010071016056.png
deleted file mode 100644
index 7165a1b48..000000000
Binary files a/docs/public/img/2020/10/07/202010071016056.png and /dev/null differ
diff --git a/docs/public/img/2020/10/07/202010071026397.png b/docs/public/img/2020/10/07/202010071026397.png
deleted file mode 100644
index c8e286f9f..000000000
Binary files a/docs/public/img/2020/10/07/202010071026397.png and /dev/null differ
diff --git a/docs/public/img/2020/10/07/202010071026666.png b/docs/public/img/2020/10/07/202010071026666.png
deleted file mode 100644
index e7a280bbb..000000000
Binary files a/docs/public/img/2020/10/07/202010071026666.png and /dev/null differ
diff --git a/docs/public/img/2020/10/07/202010071027166.png b/docs/public/img/2020/10/07/202010071027166.png
deleted file mode 100644
index 56af154ee..000000000
Binary files a/docs/public/img/2020/10/07/202010071027166.png and /dev/null differ
diff --git a/docs/public/img/2020/10/07/202010071032777.png b/docs/public/img/2020/10/07/202010071032777.png
deleted file mode 100644
index 2fee03251..000000000
Binary files a/docs/public/img/2020/10/07/202010071032777.png and /dev/null differ
diff --git a/docs/public/img/2020/10/07/202010071032888.png b/docs/public/img/2020/10/07/202010071032888.png
deleted file mode 100644
index e226358bb..000000000
Binary files a/docs/public/img/2020/10/07/202010071032888.png and /dev/null differ
diff --git a/docs/public/img/2020/10/07/202010071229226.png b/docs/public/img/2020/10/07/202010071229226.png
deleted file mode 100644
index d5f921595..000000000
Binary files a/docs/public/img/2020/10/07/202010071229226.png and /dev/null differ
diff --git a/docs/public/img/2020/10/07/202010071229820.png b/docs/public/img/2020/10/07/202010071229820.png
deleted file mode 100644
index 904aa3756..000000000
Binary files a/docs/public/img/2020/10/07/202010071229820.png and /dev/null differ
diff --git a/docs/public/img/2020/10/07/202010071230720.png b/docs/public/img/2020/10/07/202010071230720.png
deleted file mode 100644
index 25ac49901..000000000
Binary files a/docs/public/img/2020/10/07/202010071230720.png and /dev/null differ
diff --git a/docs/public/img/2020/10/07/202010071230796.png b/docs/public/img/2020/10/07/202010071230796.png
deleted file mode 100644
index 9b886152f..000000000
Binary files a/docs/public/img/2020/10/07/202010071230796.png and /dev/null differ
diff --git a/docs/public/img/2020/10/07/202010071230888.png b/docs/public/img/2020/10/07/202010071230888.png
deleted file mode 100644
index b5d4abe53..000000000
Binary files a/docs/public/img/2020/10/07/202010071230888.png and /dev/null differ
diff --git a/docs/public/img/2020/10/07/202010071230999.png b/docs/public/img/2020/10/07/202010071230999.png
deleted file mode 100644
index 0ecd184da..000000000
Binary files a/docs/public/img/2020/10/07/202010071230999.png and /dev/null differ
diff --git a/docs/public/img/2020/10/07/202010071231951.png b/docs/public/img/2020/10/07/202010071231951.png
deleted file mode 100644
index f93c4dbc9..000000000
Binary files a/docs/public/img/2020/10/07/202010071231951.png and /dev/null differ
diff --git a/docs/public/img/2020/10/07/202010071232376.png b/docs/public/img/2020/10/07/202010071232376.png
deleted file mode 100644
index 70d1072d1..000000000
Binary files a/docs/public/img/2020/10/07/202010071232376.png and /dev/null differ
diff --git a/docs/public/img/2020/10/07/202010071232956.png b/docs/public/img/2020/10/07/202010071232956.png
deleted file mode 100644
index 45abfa111..000000000
Binary files a/docs/public/img/2020/10/07/202010071232956.png and /dev/null differ
diff --git a/docs/public/img/2020/10/07/202010071310906.png b/docs/public/img/2020/10/07/202010071310906.png
deleted file mode 100644
index fd3602095..000000000
Binary files a/docs/public/img/2020/10/07/202010071310906.png and /dev/null differ
diff --git a/docs/public/img/2020/10/07/202010071312989.png b/docs/public/img/2020/10/07/202010071312989.png
deleted file mode 100644
index fd011a7e0..000000000
Binary files a/docs/public/img/2020/10/07/202010071312989.png and /dev/null differ
diff --git a/docs/public/img/2020/10/07/202010071315171.png b/docs/public/img/2020/10/07/202010071315171.png
deleted file mode 100644
index ae0fff3db..000000000
Binary files a/docs/public/img/2020/10/07/202010071315171.png and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010080736666.gif b/docs/public/img/2020/10/08/202010080736666.gif
deleted file mode 100644
index 07769d5b9..000000000
Binary files a/docs/public/img/2020/10/08/202010080736666.gif and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010080737123.png b/docs/public/img/2020/10/08/202010080737123.png
deleted file mode 100644
index 9133532d4..000000000
Binary files a/docs/public/img/2020/10/08/202010080737123.png and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010080737815.jpeg b/docs/public/img/2020/10/08/202010080737815.jpeg
deleted file mode 100644
index d82b4e475..000000000
Binary files a/docs/public/img/2020/10/08/202010080737815.jpeg and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010080737888.png b/docs/public/img/2020/10/08/202010080737888.png
deleted file mode 100644
index 9e0f673c0..000000000
Binary files a/docs/public/img/2020/10/08/202010080737888.png and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010080738759.png b/docs/public/img/2020/10/08/202010080738759.png
deleted file mode 100644
index 32e5c31be..000000000
Binary files a/docs/public/img/2020/10/08/202010080738759.png and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010080738975.png b/docs/public/img/2020/10/08/202010080738975.png
deleted file mode 100644
index e7a280bbb..000000000
Binary files a/docs/public/img/2020/10/08/202010080738975.png and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010080739706.png b/docs/public/img/2020/10/08/202010080739706.png
deleted file mode 100644
index b6da20f51..000000000
Binary files a/docs/public/img/2020/10/08/202010080739706.png and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010080739719.png b/docs/public/img/2020/10/08/202010080739719.png
deleted file mode 100644
index 8973d8e74..000000000
Binary files a/docs/public/img/2020/10/08/202010080739719.png and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010080739888.png b/docs/public/img/2020/10/08/202010080739888.png
deleted file mode 100644
index 9b61a8c19..000000000
Binary files a/docs/public/img/2020/10/08/202010080739888.png and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010080739999.png b/docs/public/img/2020/10/08/202010080739999.png
deleted file mode 100644
index bb32a6515..000000000
Binary files a/docs/public/img/2020/10/08/202010080739999.png and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010080740608.png b/docs/public/img/2020/10/08/202010080740608.png
deleted file mode 100644
index 13e660373..000000000
Binary files a/docs/public/img/2020/10/08/202010080740608.png and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010080741306.png b/docs/public/img/2020/10/08/202010080741306.png
deleted file mode 100644
index 4686462b3..000000000
Binary files a/docs/public/img/2020/10/08/202010080741306.png and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010080741825.png b/docs/public/img/2020/10/08/202010080741825.png
deleted file mode 100644
index cd5762e8e..000000000
Binary files a/docs/public/img/2020/10/08/202010080741825.png and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010081210666.jpeg b/docs/public/img/2020/10/08/202010081210666.jpeg
deleted file mode 100644
index 1beb5f3eb..000000000
Binary files a/docs/public/img/2020/10/08/202010081210666.jpeg and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010081210728.png b/docs/public/img/2020/10/08/202010081210728.png
deleted file mode 100644
index 2f25e81c6..000000000
Binary files a/docs/public/img/2020/10/08/202010081210728.png and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010081210737.jpeg b/docs/public/img/2020/10/08/202010081210737.jpeg
deleted file mode 100644
index 8591210d0..000000000
Binary files a/docs/public/img/2020/10/08/202010081210737.jpeg and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010081211672.png b/docs/public/img/2020/10/08/202010081211672.png
deleted file mode 100644
index 07eccd1b5..000000000
Binary files a/docs/public/img/2020/10/08/202010081211672.png and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010081211708.png b/docs/public/img/2020/10/08/202010081211708.png
deleted file mode 100644
index d399d5cec..000000000
Binary files a/docs/public/img/2020/10/08/202010081211708.png and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010081211825.png b/docs/public/img/2020/10/08/202010081211825.png
deleted file mode 100644
index c139d05bf..000000000
Binary files a/docs/public/img/2020/10/08/202010081211825.png and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010081212507.png b/docs/public/img/2020/10/08/202010081212507.png
deleted file mode 100644
index 510d9945f..000000000
Binary files a/docs/public/img/2020/10/08/202010081212507.png and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010081212677.png b/docs/public/img/2020/10/08/202010081212677.png
deleted file mode 100644
index bad28aa08..000000000
Binary files a/docs/public/img/2020/10/08/202010081212677.png and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010081212778.png b/docs/public/img/2020/10/08/202010081212778.png
deleted file mode 100644
index 2fdd167da..000000000
Binary files a/docs/public/img/2020/10/08/202010081212778.png and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010081216912.png b/docs/public/img/2020/10/08/202010081216912.png
deleted file mode 100644
index 579498d34..000000000
Binary files a/docs/public/img/2020/10/08/202010081216912.png and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010081217251.png b/docs/public/img/2020/10/08/202010081217251.png
deleted file mode 100644
index 2a4bf7a49..000000000
Binary files a/docs/public/img/2020/10/08/202010081217251.png and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010081217621.png b/docs/public/img/2020/10/08/202010081217621.png
deleted file mode 100644
index fac110e31..000000000
Binary files a/docs/public/img/2020/10/08/202010081217621.png and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010081752199.gif b/docs/public/img/2020/10/08/202010081752199.gif
deleted file mode 100644
index 6eca83d96..000000000
Binary files a/docs/public/img/2020/10/08/202010081752199.gif and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010081752326.jpeg b/docs/public/img/2020/10/08/202010081752326.jpeg
deleted file mode 100644
index e3319deca..000000000
Binary files a/docs/public/img/2020/10/08/202010081752326.jpeg and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010081752612.png b/docs/public/img/2020/10/08/202010081752612.png
deleted file mode 100644
index 83bd6a6a2..000000000
Binary files a/docs/public/img/2020/10/08/202010081752612.png and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010081752667.png b/docs/public/img/2020/10/08/202010081752667.png
deleted file mode 100644
index e4434c57e..000000000
Binary files a/docs/public/img/2020/10/08/202010081752667.png and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010081752687.jpeg b/docs/public/img/2020/10/08/202010081752687.jpeg
deleted file mode 100644
index 48a6fad39..000000000
Binary files a/docs/public/img/2020/10/08/202010081752687.jpeg and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010081755202.png b/docs/public/img/2020/10/08/202010081755202.png
deleted file mode 100644
index 3abf36214..000000000
Binary files a/docs/public/img/2020/10/08/202010081755202.png and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010081755352.gif b/docs/public/img/2020/10/08/202010081755352.gif
deleted file mode 100644
index 92c251ee8..000000000
Binary files a/docs/public/img/2020/10/08/202010081755352.gif and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010081755573.gif b/docs/public/img/2020/10/08/202010081755573.gif
deleted file mode 100644
index 5618c024e..000000000
Binary files a/docs/public/img/2020/10/08/202010081755573.gif and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010081755606.png b/docs/public/img/2020/10/08/202010081755606.png
deleted file mode 100644
index 45fc05ae6..000000000
Binary files a/docs/public/img/2020/10/08/202010081755606.png and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010081755712.gif b/docs/public/img/2020/10/08/202010081755712.gif
deleted file mode 100644
index 7479ab5dd..000000000
Binary files a/docs/public/img/2020/10/08/202010081755712.gif and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010081755798.gif b/docs/public/img/2020/10/08/202010081755798.gif
deleted file mode 100644
index 30223625d..000000000
Binary files a/docs/public/img/2020/10/08/202010081755798.gif and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010081756376.gif b/docs/public/img/2020/10/08/202010081756376.gif
deleted file mode 100644
index 6ae3fc8df..000000000
Binary files a/docs/public/img/2020/10/08/202010081756376.gif and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010081756666.gif b/docs/public/img/2020/10/08/202010081756666.gif
deleted file mode 100644
index 70e7f9e9e..000000000
Binary files a/docs/public/img/2020/10/08/202010081756666.gif and /dev/null differ
diff --git a/docs/public/img/2020/10/08/202010081756767.jpeg b/docs/public/img/2020/10/08/202010081756767.jpeg
deleted file mode 100644
index 1a8d2d7c6..000000000
Binary files a/docs/public/img/2020/10/08/202010081756767.jpeg and /dev/null differ
diff --git a/docs/public/img/2020/10/09/202010091351206.jpg b/docs/public/img/2020/10/09/202010091351206.jpg
deleted file mode 100644
index 7b52a4682..000000000
Binary files a/docs/public/img/2020/10/09/202010091351206.jpg and /dev/null differ
diff --git a/docs/public/img/2020/10/09/202010091351392.png b/docs/public/img/2020/10/09/202010091351392.png
deleted file mode 100644
index eeedce149..000000000
Binary files a/docs/public/img/2020/10/09/202010091351392.png and /dev/null differ
diff --git a/docs/public/img/2020/10/09/202010091351582.png b/docs/public/img/2020/10/09/202010091351582.png
deleted file mode 100644
index ca2ba1aca..000000000
Binary files a/docs/public/img/2020/10/09/202010091351582.png and /dev/null differ
diff --git a/docs/public/img/2020/10/09/202010091351666.jpg b/docs/public/img/2020/10/09/202010091351666.jpg
deleted file mode 100644
index 7b94ea52a..000000000
Binary files a/docs/public/img/2020/10/09/202010091351666.jpg and /dev/null differ
diff --git a/docs/public/img/2020/10/09/202010091352057.png b/docs/public/img/2020/10/09/202010091352057.png
deleted file mode 100644
index fdcbf9df5..000000000
Binary files a/docs/public/img/2020/10/09/202010091352057.png and /dev/null differ
diff --git a/docs/public/img/2020/10/09/202010091352212.png b/docs/public/img/2020/10/09/202010091352212.png
deleted file mode 100644
index 100d1d796..000000000
Binary files a/docs/public/img/2020/10/09/202010091352212.png and /dev/null differ
diff --git a/docs/public/img/2020/10/09/202010091352999.png b/docs/public/img/2020/10/09/202010091352999.png
deleted file mode 100644
index ea6ec48b9..000000000
Binary files a/docs/public/img/2020/10/09/202010091352999.png and /dev/null differ
diff --git a/docs/public/img/2020/10/09/202010091353559.png b/docs/public/img/2020/10/09/202010091353559.png
deleted file mode 100644
index 0ca877744..000000000
Binary files a/docs/public/img/2020/10/09/202010091353559.png and /dev/null differ
diff --git a/docs/public/img/2020/10/09/202010091355285.png b/docs/public/img/2020/10/09/202010091355285.png
deleted file mode 100644
index d107d23ac..000000000
Binary files a/docs/public/img/2020/10/09/202010091355285.png and /dev/null differ
diff --git a/docs/public/img/2020/10/09/202010091355395.jpg b/docs/public/img/2020/10/09/202010091355395.jpg
deleted file mode 100644
index 3e95d8169..000000000
Binary files a/docs/public/img/2020/10/09/202010091355395.jpg and /dev/null differ
diff --git a/docs/public/img/2020/10/09/202010091457755.jpeg b/docs/public/img/2020/10/09/202010091457755.jpeg
deleted file mode 100644
index 37528d201..000000000
Binary files a/docs/public/img/2020/10/09/202010091457755.jpeg and /dev/null differ
diff --git a/docs/public/img/2020/10/09/202010091457896.png b/docs/public/img/2020/10/09/202010091457896.png
deleted file mode 100644
index ca7940d3b..000000000
Binary files a/docs/public/img/2020/10/09/202010091457896.png and /dev/null differ
diff --git a/docs/public/img/2020/10/09/202010091458322.png b/docs/public/img/2020/10/09/202010091458322.png
deleted file mode 100644
index edcfb09ec..000000000
Binary files a/docs/public/img/2020/10/09/202010091458322.png and /dev/null differ
diff --git a/docs/public/img/2020/10/09/202010091458868.png b/docs/public/img/2020/10/09/202010091458868.png
deleted file mode 100644
index 15a1d8097..000000000
Binary files a/docs/public/img/2020/10/09/202010091458868.png and /dev/null differ
diff --git a/docs/public/img/2020/10/09/202010091458957.png b/docs/public/img/2020/10/09/202010091458957.png
deleted file mode 100644
index f18a8572d..000000000
Binary files a/docs/public/img/2020/10/09/202010091458957.png and /dev/null differ
diff --git a/docs/public/img/2020/10/09/202010091459307.png b/docs/public/img/2020/10/09/202010091459307.png
deleted file mode 100644
index 6b2b80bf9..000000000
Binary files a/docs/public/img/2020/10/09/202010091459307.png and /dev/null differ
diff --git a/docs/public/img/2020/12/25/202012252219533.png b/docs/public/img/2020/12/25/202012252219533.png
deleted file mode 100644
index 42377af98..000000000
Binary files a/docs/public/img/2020/12/25/202012252219533.png and /dev/null differ
diff --git a/docs/public/img/2020/12/25/202012252221059.png b/docs/public/img/2020/12/25/202012252221059.png
deleted file mode 100644
index 84ca4526e..000000000
Binary files a/docs/public/img/2020/12/25/202012252221059.png and /dev/null differ
diff --git a/docs/public/img/2020/12/25/202012252221734.png b/docs/public/img/2020/12/25/202012252221734.png
deleted file mode 100644
index 270720f1d..000000000
Binary files a/docs/public/img/2020/12/25/202012252221734.png and /dev/null differ
diff --git a/docs/public/img/2020/12/25/202012252222518.png b/docs/public/img/2020/12/25/202012252222518.png
deleted file mode 100644
index 9e1a3d034..000000000
Binary files a/docs/public/img/2020/12/25/202012252222518.png and /dev/null differ
diff --git a/docs/public/img/2020/12/25/202012252222738.png b/docs/public/img/2020/12/25/202012252222738.png
deleted file mode 100644
index 0defda4b0..000000000
Binary files a/docs/public/img/2020/12/25/202012252222738.png and /dev/null differ
diff --git a/docs/public/img/2020/12/25/202012252222812.png b/docs/public/img/2020/12/25/202012252222812.png
deleted file mode 100644
index 61c596d65..000000000
Binary files a/docs/public/img/2020/12/25/202012252222812.png and /dev/null differ
diff --git a/docs/public/img/2020/12/25/202012252223067.png b/docs/public/img/2020/12/25/202012252223067.png
deleted file mode 100644
index 5884fe564..000000000
Binary files a/docs/public/img/2020/12/25/202012252223067.png and /dev/null differ
diff --git a/docs/public/img/2020/12/25/202012252223708.png b/docs/public/img/2020/12/25/202012252223708.png
deleted file mode 100644
index 59a6c0533..000000000
Binary files a/docs/public/img/2020/12/25/202012252223708.png and /dev/null differ
diff --git a/docs/public/img/2020/12/25/202012252243280.png b/docs/public/img/2020/12/25/202012252243280.png
deleted file mode 100644
index c05f61bf3..000000000
Binary files a/docs/public/img/2020/12/25/202012252243280.png and /dev/null differ
diff --git a/docs/public/img/2020/12/26/202012262251170.png b/docs/public/img/2020/12/26/202012262251170.png
deleted file mode 100644
index b1cad2e18..000000000
Binary files a/docs/public/img/2020/12/26/202012262251170.png and /dev/null differ
diff --git a/docs/public/img/2020/12/26/202012262252221.png b/docs/public/img/2020/12/26/202012262252221.png
deleted file mode 100644
index 4512898b2..000000000
Binary files a/docs/public/img/2020/12/26/202012262252221.png and /dev/null differ
diff --git a/docs/public/img/2020/12/27/202012271107691.png b/docs/public/img/2020/12/27/202012271107691.png
deleted file mode 100644
index ea2e41093..000000000
Binary files a/docs/public/img/2020/12/27/202012271107691.png and /dev/null differ
diff --git a/docs/public/img/2020/12/27/202012271125273.jpg b/docs/public/img/2020/12/27/202012271125273.jpg
deleted file mode 100644
index 2c7ff8a66..000000000
Binary files a/docs/public/img/2020/12/27/202012271125273.jpg and /dev/null differ
diff --git a/docs/public/img/2020/12/27/202012271125276.png b/docs/public/img/2020/12/27/202012271125276.png
deleted file mode 100644
index 02f8cc729..000000000
Binary files a/docs/public/img/2020/12/27/202012271125276.png and /dev/null differ
diff --git a/docs/public/img/2020/12/27/202012271126381.png b/docs/public/img/2020/12/27/202012271126381.png
deleted file mode 100644
index a9669f43e..000000000
Binary files a/docs/public/img/2020/12/27/202012271126381.png and /dev/null differ
diff --git a/docs/public/img/2020/12/27/202012271127794.png b/docs/public/img/2020/12/27/202012271127794.png
deleted file mode 100644
index d7c5d0365..000000000
Binary files a/docs/public/img/2020/12/27/202012271127794.png and /dev/null differ
diff --git a/docs/public/img/2020/12/28/202012281130648.jpg b/docs/public/img/2020/12/28/202012281130648.jpg
deleted file mode 100644
index 5335a8c46..000000000
Binary files a/docs/public/img/2020/12/28/202012281130648.jpg and /dev/null differ
diff --git a/docs/public/img/2020/12/28/202012281131280.png b/docs/public/img/2020/12/28/202012281131280.png
deleted file mode 100644
index 5d47e149c..000000000
Binary files a/docs/public/img/2020/12/28/202012281131280.png and /dev/null differ
diff --git a/docs/public/img/2020/12/28/202012281131380.png b/docs/public/img/2020/12/28/202012281131380.png
deleted file mode 100644
index 3c7819349..000000000
Binary files a/docs/public/img/2020/12/28/202012281131380.png and /dev/null differ
diff --git a/docs/public/img/2020/12/28/202012281132193.png b/docs/public/img/2020/12/28/202012281132193.png
deleted file mode 100644
index 0729542f4..000000000
Binary files a/docs/public/img/2020/12/28/202012281132193.png and /dev/null differ
diff --git a/docs/public/img/2020/12/28/202012281133167.png b/docs/public/img/2020/12/28/202012281133167.png
deleted file mode 100644
index 280eef726..000000000
Binary files a/docs/public/img/2020/12/28/202012281133167.png and /dev/null differ
diff --git a/docs/public/img/2020/12/28/202012281138752.png b/docs/public/img/2020/12/28/202012281138752.png
deleted file mode 100644
index 3b6759dab..000000000
Binary files a/docs/public/img/2020/12/28/202012281138752.png and /dev/null differ
diff --git a/docs/public/img/2020/12/28/202012281139250.png b/docs/public/img/2020/12/28/202012281139250.png
deleted file mode 100644
index afc7eec58..000000000
Binary files a/docs/public/img/2020/12/28/202012281139250.png and /dev/null differ
diff --git a/docs/public/img/2020/12/28/202012281139256.png b/docs/public/img/2020/12/28/202012281139256.png
deleted file mode 100644
index 67839aef0..000000000
Binary files a/docs/public/img/2020/12/28/202012281139256.png and /dev/null differ
diff --git a/docs/public/img/2020/12/28/202012281139540.png b/docs/public/img/2020/12/28/202012281139540.png
deleted file mode 100644
index 908419e5b..000000000
Binary files a/docs/public/img/2020/12/28/202012281139540.png and /dev/null differ
diff --git a/docs/public/img/2020/12/29/202012291143905.png b/docs/public/img/2020/12/29/202012291143905.png
deleted file mode 100644
index 557f20e50..000000000
Binary files a/docs/public/img/2020/12/29/202012291143905.png and /dev/null differ
diff --git a/docs/public/img/2020/12/29/202012291144689.png b/docs/public/img/2020/12/29/202012291144689.png
deleted file mode 100644
index 7f5c9efcf..000000000
Binary files a/docs/public/img/2020/12/29/202012291144689.png and /dev/null differ
diff --git a/docs/public/img/2020/12/29/202012291144692.png b/docs/public/img/2020/12/29/202012291144692.png
deleted file mode 100644
index a62ae61b5..000000000
Binary files a/docs/public/img/2020/12/29/202012291144692.png and /dev/null differ
diff --git a/docs/public/img/2020/12/29/202012291145084.png b/docs/public/img/2020/12/29/202012291145084.png
deleted file mode 100644
index 64823744a..000000000
Binary files a/docs/public/img/2020/12/29/202012291145084.png and /dev/null differ
diff --git a/docs/public/img/2020/12/29/202012291145312.png b/docs/public/img/2020/12/29/202012291145312.png
deleted file mode 100644
index eb3bdf6e0..000000000
Binary files a/docs/public/img/2020/12/29/202012291145312.png and /dev/null differ
diff --git a/docs/public/img/2021/01/14/202101140900566.jpg b/docs/public/img/2021/01/14/202101140900566.jpg
deleted file mode 100644
index f0f150fa7..000000000
Binary files a/docs/public/img/2021/01/14/202101140900566.jpg and /dev/null differ
diff --git a/docs/public/img/2021/01/14/202101140900568.png b/docs/public/img/2021/01/14/202101140900568.png
deleted file mode 100644
index ee573b6fc..000000000
Binary files a/docs/public/img/2021/01/14/202101140900568.png and /dev/null differ
diff --git a/docs/public/img/2021/01/14/202101140900569.png b/docs/public/img/2021/01/14/202101140900569.png
deleted file mode 100644
index 4138cc28c..000000000
Binary files a/docs/public/img/2021/01/14/202101140900569.png and /dev/null differ
diff --git a/docs/public/img/2021/01/14/202101140900570.png b/docs/public/img/2021/01/14/202101140900570.png
deleted file mode 100644
index 4ef62a818..000000000
Binary files a/docs/public/img/2021/01/14/202101140900570.png and /dev/null differ
diff --git a/docs/public/img/2021/01/14/202101140900571.png b/docs/public/img/2021/01/14/202101140900571.png
deleted file mode 100644
index b3cd02312..000000000
Binary files a/docs/public/img/2021/01/14/202101140900571.png and /dev/null differ
diff --git a/docs/public/img/2021/01/14/202101140900572.png b/docs/public/img/2021/01/14/202101140900572.png
deleted file mode 100644
index 193d939cb..000000000
Binary files a/docs/public/img/2021/01/14/202101140900572.png and /dev/null differ
diff --git a/docs/public/img/2021/01/14/202101140900573.png b/docs/public/img/2021/01/14/202101140900573.png
deleted file mode 100644
index 58fc663ca..000000000
Binary files a/docs/public/img/2021/01/14/202101140900573.png and /dev/null differ
diff --git a/docs/public/img/2021/01/14/202101140900575.png b/docs/public/img/2021/01/14/202101140900575.png
deleted file mode 100644
index ef38a849d..000000000
Binary files a/docs/public/img/2021/01/14/202101140900575.png and /dev/null differ
diff --git a/docs/public/img/2021/01/14/202101140900577.png b/docs/public/img/2021/01/14/202101140900577.png
deleted file mode 100644
index 5da5f0f9f..000000000
Binary files a/docs/public/img/2021/01/14/202101140900577.png and /dev/null differ
diff --git a/docs/public/img/2021/01/14/202101140900579.png b/docs/public/img/2021/01/14/202101140900579.png
deleted file mode 100644
index 8a796bf47..000000000
Binary files a/docs/public/img/2021/01/14/202101140900579.png and /dev/null differ
diff --git a/docs/public/img/2021/01/14/202101140900581.png b/docs/public/img/2021/01/14/202101140900581.png
deleted file mode 100644
index ef8003716..000000000
Binary files a/docs/public/img/2021/01/14/202101140900581.png and /dev/null differ
diff --git a/docs/public/img/2021/01/14/202101140900583.png b/docs/public/img/2021/01/14/202101140900583.png
deleted file mode 100644
index 21e27156f..000000000
Binary files a/docs/public/img/2021/01/14/202101140900583.png and /dev/null differ
diff --git a/docs/public/img/2021/01/14/202101140900585.png b/docs/public/img/2021/01/14/202101140900585.png
deleted file mode 100644
index f2b30b473..000000000
Binary files a/docs/public/img/2021/01/14/202101140900585.png and /dev/null differ
diff --git a/docs/public/img/2021/01/14/202101140900587.png b/docs/public/img/2021/01/14/202101140900587.png
deleted file mode 100644
index a615c7a11..000000000
Binary files a/docs/public/img/2021/01/14/202101140900587.png and /dev/null differ
diff --git a/docs/public/img/2021/01/14/202101140900589.png b/docs/public/img/2021/01/14/202101140900589.png
deleted file mode 100644
index 49b7aaed1..000000000
Binary files a/docs/public/img/2021/01/14/202101140900589.png and /dev/null differ
diff --git a/docs/public/img/2021/01/14/202101140900591.png b/docs/public/img/2021/01/14/202101140900591.png
deleted file mode 100644
index c4096bb4c..000000000
Binary files a/docs/public/img/2021/01/14/202101140900591.png and /dev/null differ
diff --git a/docs/public/img/2021/01/14/202101140900593.png b/docs/public/img/2021/01/14/202101140900593.png
deleted file mode 100644
index 11669c0ff..000000000
Binary files a/docs/public/img/2021/01/14/202101140900593.png and /dev/null differ
diff --git a/docs/public/img/2021/01/14/202101140900595.png b/docs/public/img/2021/01/14/202101140900595.png
deleted file mode 100644
index c8866d3cd..000000000
Binary files a/docs/public/img/2021/01/14/202101140900595.png and /dev/null differ
diff --git a/docs/public/img/2021/01/14/202101140900597.png b/docs/public/img/2021/01/14/202101140900597.png
deleted file mode 100644
index 2808dc8e2..000000000
Binary files a/docs/public/img/2021/01/14/202101140900597.png and /dev/null differ
diff --git a/docs/public/img/2021/01/16/202101161149179.png b/docs/public/img/2021/01/16/202101161149179.png
deleted file mode 100644
index 40312f184..000000000
Binary files a/docs/public/img/2021/01/16/202101161149179.png and /dev/null differ
diff --git a/docs/public/img/2021/01/16/202101161150749.jpg b/docs/public/img/2021/01/16/202101161150749.jpg
deleted file mode 100644
index ae58f5e4f..000000000
Binary files a/docs/public/img/2021/01/16/202101161150749.jpg and /dev/null differ
diff --git a/docs/public/img/2021/01/16/202101161155018.png b/docs/public/img/2021/01/16/202101161155018.png
deleted file mode 100644
index 80b2d0725..000000000
Binary files a/docs/public/img/2021/01/16/202101161155018.png and /dev/null differ
diff --git a/docs/public/img/2021/01/16/202101161155388.png b/docs/public/img/2021/01/16/202101161155388.png
deleted file mode 100644
index 989ee5ce9..000000000
Binary files a/docs/public/img/2021/01/16/202101161155388.png and /dev/null differ
diff --git a/docs/public/img/2021/01/16/202101161156256.png b/docs/public/img/2021/01/16/202101161156256.png
deleted file mode 100644
index 66a94aeb1..000000000
Binary files a/docs/public/img/2021/01/16/202101161156256.png and /dev/null differ
diff --git a/docs/public/img/2021/01/16/202101161156668.png b/docs/public/img/2021/01/16/202101161156668.png
deleted file mode 100644
index 5ab3e6445..000000000
Binary files a/docs/public/img/2021/01/16/202101161156668.png and /dev/null differ
diff --git a/docs/public/img/2021/01/16/202101161157415.png b/docs/public/img/2021/01/16/202101161157415.png
deleted file mode 100644
index 1883839c3..000000000
Binary files a/docs/public/img/2021/01/16/202101161157415.png and /dev/null differ
diff --git a/docs/public/img/2021/01/16/202101161158888.png b/docs/public/img/2021/01/16/202101161158888.png
deleted file mode 100644
index b379d2901..000000000
Binary files a/docs/public/img/2021/01/16/202101161158888.png and /dev/null differ
diff --git a/docs/public/img/2021/01/16/202101161159106.png b/docs/public/img/2021/01/16/202101161159106.png
deleted file mode 100644
index 12154e4ce..000000000
Binary files a/docs/public/img/2021/01/16/202101161159106.png and /dev/null differ
diff --git a/docs/public/img/2021/01/16/202101161159147.png b/docs/public/img/2021/01/16/202101161159147.png
deleted file mode 100644
index db24e7926..000000000
Binary files a/docs/public/img/2021/01/16/202101161159147.png and /dev/null differ
diff --git a/docs/public/img/2021/01/16/202101161159902.gif b/docs/public/img/2021/01/16/202101161159902.gif
deleted file mode 100644
index 3bb622afe..000000000
Binary files a/docs/public/img/2021/01/16/202101161159902.gif and /dev/null differ
diff --git a/docs/public/img/2021/01/16/202101161200243.png b/docs/public/img/2021/01/16/202101161200243.png
deleted file mode 100644
index 89cbf03e4..000000000
Binary files a/docs/public/img/2021/01/16/202101161200243.png and /dev/null differ
diff --git a/docs/public/img/2021/01/19/202101190000100.png b/docs/public/img/2021/01/19/202101190000100.png
deleted file mode 100644
index 77815a8a7..000000000
Binary files a/docs/public/img/2021/01/19/202101190000100.png and /dev/null differ
diff --git a/docs/public/img/2021/01/19/202101190000200.jpg b/docs/public/img/2021/01/19/202101190000200.jpg
deleted file mode 100644
index 20c0fef37..000000000
Binary files a/docs/public/img/2021/01/19/202101190000200.jpg and /dev/null differ
diff --git a/docs/public/img/2021/01/19/202101190000300.gif b/docs/public/img/2021/01/19/202101190000300.gif
deleted file mode 100644
index e97f24d22..000000000
Binary files a/docs/public/img/2021/01/19/202101190000300.gif and /dev/null differ
diff --git a/docs/public/img/2021/01/19/202101190000400.gif b/docs/public/img/2021/01/19/202101190000400.gif
deleted file mode 100644
index 4508b1fd3..000000000
Binary files a/docs/public/img/2021/01/19/202101190000400.gif and /dev/null differ
diff --git a/docs/public/img/2021/01/19/202101190000500.png b/docs/public/img/2021/01/19/202101190000500.png
deleted file mode 100644
index 828dcd41f..000000000
Binary files a/docs/public/img/2021/01/19/202101190000500.png and /dev/null differ
diff --git a/docs/public/img/2021/01/19/202101190000600.png b/docs/public/img/2021/01/19/202101190000600.png
deleted file mode 100644
index 1f83fe304..000000000
Binary files a/docs/public/img/2021/01/19/202101190000600.png and /dev/null differ
diff --git a/docs/public/img/2021/01/19/202101190000700.png b/docs/public/img/2021/01/19/202101190000700.png
deleted file mode 100644
index d138d4855..000000000
Binary files a/docs/public/img/2021/01/19/202101190000700.png and /dev/null differ
diff --git a/docs/public/img/2021/01/23/202101231220866.jpg b/docs/public/img/2021/01/23/202101231220866.jpg
deleted file mode 100644
index 25bc356bd..000000000
Binary files a/docs/public/img/2021/01/23/202101231220866.jpg and /dev/null differ
diff --git a/docs/public/img/2021/01/23/202101231220888.png b/docs/public/img/2021/01/23/202101231220888.png
deleted file mode 100644
index 280e9a352..000000000
Binary files a/docs/public/img/2021/01/23/202101231220888.png and /dev/null differ
diff --git a/docs/public/img/2021/01/23/202101231221195.gif b/docs/public/img/2021/01/23/202101231221195.gif
deleted file mode 100644
index 4f32e6386..000000000
Binary files a/docs/public/img/2021/01/23/202101231221195.gif and /dev/null differ
diff --git a/docs/public/img/2021/01/24/202101241302860.jpg b/docs/public/img/2021/01/24/202101241302860.jpg
deleted file mode 100644
index 296cad6a3..000000000
Binary files a/docs/public/img/2021/01/24/202101241302860.jpg and /dev/null differ
diff --git a/docs/public/img/2021/01/24/202101241303960.png b/docs/public/img/2021/01/24/202101241303960.png
deleted file mode 100644
index 2f9ecbb49..000000000
Binary files a/docs/public/img/2021/01/24/202101241303960.png and /dev/null differ
diff --git a/docs/public/img/2021/01/24/202101241303970.gif b/docs/public/img/2021/01/24/202101241303970.gif
deleted file mode 100644
index f84042ea2..000000000
Binary files a/docs/public/img/2021/01/24/202101241303970.gif and /dev/null differ
diff --git a/docs/public/img/2021/02/22/202102220930199.png b/docs/public/img/2021/02/22/202102220930199.png
deleted file mode 100644
index 07b30aa79..000000000
Binary files a/docs/public/img/2021/02/22/202102220930199.png and /dev/null differ
diff --git a/docs/public/img/2021/02/22/202102220930299.png b/docs/public/img/2021/02/22/202102220930299.png
deleted file mode 100644
index 1eee9cdb9..000000000
Binary files a/docs/public/img/2021/02/22/202102220930299.png and /dev/null differ
diff --git a/docs/public/img/2021/02/22/202102220930399.png b/docs/public/img/2021/02/22/202102220930399.png
deleted file mode 100644
index fb06f99ca..000000000
Binary files a/docs/public/img/2021/02/22/202102220930399.png and /dev/null differ
diff --git a/docs/public/img/2021/02/22/202102220930599.png b/docs/public/img/2021/02/22/202102220930599.png
deleted file mode 100644
index 26f22bc0e..000000000
Binary files a/docs/public/img/2021/02/22/202102220930599.png and /dev/null differ
diff --git a/docs/public/img/2021/02/22/202102220930699.png b/docs/public/img/2021/02/22/202102220930699.png
deleted file mode 100644
index d37d6543c..000000000
Binary files a/docs/public/img/2021/02/22/202102220930699.png and /dev/null differ
diff --git a/docs/public/img/2021/02/22/202102220930799.png b/docs/public/img/2021/02/22/202102220930799.png
deleted file mode 100644
index d0d24e17e..000000000
Binary files a/docs/public/img/2021/02/22/202102220930799.png and /dev/null differ
diff --git a/docs/public/img/2021/02/22/202102220930899.png b/docs/public/img/2021/02/22/202102220930899.png
deleted file mode 100644
index da63e8a32..000000000
Binary files a/docs/public/img/2021/02/22/202102220930899.png and /dev/null differ
diff --git a/docs/public/img/2021/02/22/202102220930999.png b/docs/public/img/2021/02/22/202102220930999.png
deleted file mode 100644
index a337b6e8f..000000000
Binary files a/docs/public/img/2021/02/22/202102220930999.png and /dev/null differ
diff --git a/docs/public/img/2021/02/22/202102220931199.png b/docs/public/img/2021/02/22/202102220931199.png
deleted file mode 100644
index 028ad2e3a..000000000
Binary files a/docs/public/img/2021/02/22/202102220931199.png and /dev/null differ
diff --git a/docs/public/img/2021/02/22/202102220931299.png b/docs/public/img/2021/02/22/202102220931299.png
deleted file mode 100644
index 1faca3cfe..000000000
Binary files a/docs/public/img/2021/02/22/202102220931299.png and /dev/null differ
diff --git a/docs/public/img/2021/02/22/202102220931399.png b/docs/public/img/2021/02/22/202102220931399.png
deleted file mode 100644
index 6655e25c6..000000000
Binary files a/docs/public/img/2021/02/22/202102220931399.png and /dev/null differ
diff --git a/docs/public/img/2021/02/22/202102220931599.png b/docs/public/img/2021/02/22/202102220931599.png
deleted file mode 100644
index 8eaee7a45..000000000
Binary files a/docs/public/img/2021/02/22/202102220931599.png and /dev/null differ
diff --git a/docs/public/img/2021/02/22/202102220931699.png b/docs/public/img/2021/02/22/202102220931699.png
deleted file mode 100644
index 404d393bd..000000000
Binary files a/docs/public/img/2021/02/22/202102220931699.png and /dev/null differ
diff --git a/docs/public/img/2021/02/22/202102220931799.png b/docs/public/img/2021/02/22/202102220931799.png
deleted file mode 100644
index 4a25f9d67..000000000
Binary files a/docs/public/img/2021/02/22/202102220931799.png and /dev/null differ
diff --git a/docs/public/img/2021/02/22/202102220931899.png b/docs/public/img/2021/02/22/202102220931899.png
deleted file mode 100644
index 6d50de779..000000000
Binary files a/docs/public/img/2021/02/22/202102220931899.png and /dev/null differ
diff --git a/docs/public/img/2021/02/22/202102220931999.png b/docs/public/img/2021/02/22/202102220931999.png
deleted file mode 100644
index 3f3ba8f82..000000000
Binary files a/docs/public/img/2021/02/22/202102220931999.png and /dev/null differ
diff --git a/docs/public/img/2021/02/22/202102220932199.gif b/docs/public/img/2021/02/22/202102220932199.gif
deleted file mode 100644
index 33fe2d932..000000000
Binary files a/docs/public/img/2021/02/22/202102220932199.gif and /dev/null differ
diff --git a/docs/public/img/2021/02/22/202102220932299.gif b/docs/public/img/2021/02/22/202102220932299.gif
deleted file mode 100644
index b13b7e344..000000000
Binary files a/docs/public/img/2021/02/22/202102220932299.gif and /dev/null differ
diff --git a/docs/public/img/2021/02/22/202102220932399.gif b/docs/public/img/2021/02/22/202102220932399.gif
deleted file mode 100644
index 82ee9783c..000000000
Binary files a/docs/public/img/2021/02/22/202102220932399.gif and /dev/null differ
diff --git a/docs/public/img/2021/02/22/202102220932599.gif b/docs/public/img/2021/02/22/202102220932599.gif
deleted file mode 100644
index dbbb93df1..000000000
Binary files a/docs/public/img/2021/02/22/202102220932599.gif and /dev/null differ
diff --git a/docs/public/img/2021/02/22/202102220932699.gif b/docs/public/img/2021/02/22/202102220932699.gif
deleted file mode 100644
index 3388b3b9e..000000000
Binary files a/docs/public/img/2021/02/22/202102220932699.gif and /dev/null differ
diff --git a/docs/public/img/2021/02/22/202102220932799.gif b/docs/public/img/2021/02/22/202102220932799.gif
deleted file mode 100644
index 2f18ec558..000000000
Binary files a/docs/public/img/2021/02/22/202102220932799.gif and /dev/null differ
diff --git a/docs/public/img/2021/02/24/202102241306195.png b/docs/public/img/2021/02/24/202102241306195.png
deleted file mode 100644
index 59456313d..000000000
Binary files a/docs/public/img/2021/02/24/202102241306195.png and /dev/null differ
diff --git a/docs/public/img/2021/02/24/202102241307200.png b/docs/public/img/2021/02/24/202102241307200.png
deleted file mode 100644
index 47edde951..000000000
Binary files a/docs/public/img/2021/02/24/202102241307200.png and /dev/null differ
diff --git a/docs/public/img/2021/02/24/202102241308500.gif b/docs/public/img/2021/02/24/202102241308500.gif
deleted file mode 100644
index 119b92a69..000000000
Binary files a/docs/public/img/2021/02/24/202102241308500.gif and /dev/null differ
diff --git a/docs/public/img/2021/02/24/202102241309600.png b/docs/public/img/2021/02/24/202102241309600.png
deleted file mode 100644
index a4f6aad65..000000000
Binary files a/docs/public/img/2021/02/24/202102241309600.png and /dev/null differ
diff --git a/docs/public/img/2021/02/24/202102241310700.png b/docs/public/img/2021/02/24/202102241310700.png
deleted file mode 100644
index c72d0e2ef..000000000
Binary files a/docs/public/img/2021/02/24/202102241310700.png and /dev/null differ
diff --git a/docs/public/img/2021/02/24/202102241311800.gif b/docs/public/img/2021/02/24/202102241311800.gif
deleted file mode 100644
index ea01fda64..000000000
Binary files a/docs/public/img/2021/02/24/202102241311800.gif and /dev/null differ
diff --git a/docs/public/img/2021/03/04/202103042310166.jpg b/docs/public/img/2021/03/04/202103042310166.jpg
deleted file mode 100644
index dc2b1a2e0..000000000
Binary files a/docs/public/img/2021/03/04/202103042310166.jpg and /dev/null differ
diff --git a/docs/public/img/2021/03/04/202103042310266.png b/docs/public/img/2021/03/04/202103042310266.png
deleted file mode 100644
index 021c412d3..000000000
Binary files a/docs/public/img/2021/03/04/202103042310266.png and /dev/null differ
diff --git a/docs/public/img/2021/03/04/202103042310366.png b/docs/public/img/2021/03/04/202103042310366.png
deleted file mode 100644
index b1ad2e7cb..000000000
Binary files a/docs/public/img/2021/03/04/202103042310366.png and /dev/null differ
diff --git a/docs/public/img/2021/03/04/202103042310566.png b/docs/public/img/2021/03/04/202103042310566.png
deleted file mode 100644
index 747293113..000000000
Binary files a/docs/public/img/2021/03/04/202103042310566.png and /dev/null differ
diff --git a/docs/public/img/2021/03/04/202103042310666.png b/docs/public/img/2021/03/04/202103042310666.png
deleted file mode 100644
index 4c7ad753d..000000000
Binary files a/docs/public/img/2021/03/04/202103042310666.png and /dev/null differ
diff --git a/docs/public/img/2021/03/04/202103042310766.png b/docs/public/img/2021/03/04/202103042310766.png
deleted file mode 100644
index 091ae6eb0..000000000
Binary files a/docs/public/img/2021/03/04/202103042310766.png and /dev/null differ
diff --git a/docs/public/img/2021/03/04/202103042310866.png b/docs/public/img/2021/03/04/202103042310866.png
deleted file mode 100644
index 600d05ac2..000000000
Binary files a/docs/public/img/2021/03/04/202103042310866.png and /dev/null differ
diff --git a/docs/public/img/2021/03/04/202103042310966.png b/docs/public/img/2021/03/04/202103042310966.png
deleted file mode 100644
index 04e667b30..000000000
Binary files a/docs/public/img/2021/03/04/202103042310966.png and /dev/null differ
diff --git a/docs/public/img/2021/03/04/202103042311166.png b/docs/public/img/2021/03/04/202103042311166.png
deleted file mode 100644
index d2c2ec530..000000000
Binary files a/docs/public/img/2021/03/04/202103042311166.png and /dev/null differ
diff --git a/docs/public/img/2021/03/04/202103042311266.png b/docs/public/img/2021/03/04/202103042311266.png
deleted file mode 100644
index 2c4c30610..000000000
Binary files a/docs/public/img/2021/03/04/202103042311266.png and /dev/null differ
diff --git a/docs/public/img/2021/03/04/202103042311366.png b/docs/public/img/2021/03/04/202103042311366.png
deleted file mode 100644
index 736d12c3b..000000000
Binary files a/docs/public/img/2021/03/04/202103042311366.png and /dev/null differ
diff --git a/docs/public/img/2021/03/04/202103042311566.png b/docs/public/img/2021/03/04/202103042311566.png
deleted file mode 100644
index e598da705..000000000
Binary files a/docs/public/img/2021/03/04/202103042311566.png and /dev/null differ
diff --git a/docs/public/img/2021/03/04/202103042311666.gif b/docs/public/img/2021/03/04/202103042311666.gif
deleted file mode 100644
index e8afeea75..000000000
Binary files a/docs/public/img/2021/03/04/202103042311666.gif and /dev/null differ
diff --git a/docs/public/img/2021/03/04/202103042311766.gif b/docs/public/img/2021/03/04/202103042311766.gif
deleted file mode 100644
index 36a139b66..000000000
Binary files a/docs/public/img/2021/03/04/202103042311766.gif and /dev/null differ
diff --git a/docs/public/img/2021/03/04/202103042311866.gif b/docs/public/img/2021/03/04/202103042311866.gif
deleted file mode 100644
index a79853ee1..000000000
Binary files a/docs/public/img/2021/03/04/202103042311866.gif and /dev/null differ
diff --git a/docs/public/img/2021/03/04/202103042311966.gif b/docs/public/img/2021/03/04/202103042311966.gif
deleted file mode 100644
index 18c34f812..000000000
Binary files a/docs/public/img/2021/03/04/202103042311966.gif and /dev/null differ
diff --git a/docs/public/img/2021/03/04/202103042312166.gif b/docs/public/img/2021/03/04/202103042312166.gif
deleted file mode 100644
index 6de961374..000000000
Binary files a/docs/public/img/2021/03/04/202103042312166.gif and /dev/null differ
diff --git a/docs/public/img/2021/03/04/202103042312266.gif b/docs/public/img/2021/03/04/202103042312266.gif
deleted file mode 100644
index ac3e8cf39..000000000
Binary files a/docs/public/img/2021/03/04/202103042312266.gif and /dev/null differ
diff --git a/docs/public/img/2021/03/06/202103061725199.jpg b/docs/public/img/2021/03/06/202103061725199.jpg
deleted file mode 100644
index 52f57a31a..000000000
Binary files a/docs/public/img/2021/03/06/202103061725199.jpg and /dev/null differ
diff --git a/docs/public/img/2021/03/06/202103061725222.png b/docs/public/img/2021/03/06/202103061725222.png
deleted file mode 100644
index d2dc92baa..000000000
Binary files a/docs/public/img/2021/03/06/202103061725222.png and /dev/null differ
diff --git a/docs/public/img/2021/03/06/202103061725333.png b/docs/public/img/2021/03/06/202103061725333.png
deleted file mode 100644
index 3dbd69559..000000000
Binary files a/docs/public/img/2021/03/06/202103061725333.png and /dev/null differ
diff --git a/docs/public/img/2021/03/06/202103061725566.png b/docs/public/img/2021/03/06/202103061725566.png
deleted file mode 100644
index 0365a5e6d..000000000
Binary files a/docs/public/img/2021/03/06/202103061725566.png and /dev/null differ
diff --git a/docs/public/img/2021/03/06/202103061725666.png b/docs/public/img/2021/03/06/202103061725666.png
deleted file mode 100644
index b50005bd3..000000000
Binary files a/docs/public/img/2021/03/06/202103061725666.png and /dev/null differ
diff --git a/docs/public/img/2021/03/06/202103061725677.png b/docs/public/img/2021/03/06/202103061725677.png
deleted file mode 100644
index 5b9c2a3cc..000000000
Binary files a/docs/public/img/2021/03/06/202103061725677.png and /dev/null differ
diff --git a/docs/public/img/2021/03/06/202103061725777.png b/docs/public/img/2021/03/06/202103061725777.png
deleted file mode 100644
index b583cb276..000000000
Binary files a/docs/public/img/2021/03/06/202103061725777.png and /dev/null differ
diff --git a/docs/public/img/2021/03/06/202103061725888.png b/docs/public/img/2021/03/06/202103061725888.png
deleted file mode 100644
index cc6999bde..000000000
Binary files a/docs/public/img/2021/03/06/202103061725888.png and /dev/null differ
diff --git a/docs/public/img/2021/03/06/202103061725999.png b/docs/public/img/2021/03/06/202103061725999.png
deleted file mode 100644
index ef3bc07ec..000000000
Binary files a/docs/public/img/2021/03/06/202103061725999.png and /dev/null differ
diff --git a/docs/public/img/2021/03/06/202103061726166.png b/docs/public/img/2021/03/06/202103061726166.png
deleted file mode 100644
index fe9b8f620..000000000
Binary files a/docs/public/img/2021/03/06/202103061726166.png and /dev/null differ
diff --git a/docs/public/img/2021/03/06/202103061726266.png b/docs/public/img/2021/03/06/202103061726266.png
deleted file mode 100644
index 25509077c..000000000
Binary files a/docs/public/img/2021/03/06/202103061726266.png and /dev/null differ
diff --git a/docs/public/img/2021/03/06/202103061726366.png b/docs/public/img/2021/03/06/202103061726366.png
deleted file mode 100644
index 71463d8e3..000000000
Binary files a/docs/public/img/2021/03/06/202103061726366.png and /dev/null differ
diff --git a/docs/public/img/2021/03/06/202103061726566.png b/docs/public/img/2021/03/06/202103061726566.png
deleted file mode 100644
index 56922a9dd..000000000
Binary files a/docs/public/img/2021/03/06/202103061726566.png and /dev/null differ
diff --git a/docs/public/img/2021/03/06/202103061726666.png b/docs/public/img/2021/03/06/202103061726666.png
deleted file mode 100644
index 77724b6d7..000000000
Binary files a/docs/public/img/2021/03/06/202103061726666.png and /dev/null differ
diff --git a/docs/public/img/2021/03/06/202103061726777.png b/docs/public/img/2021/03/06/202103061726777.png
deleted file mode 100644
index 384282b27..000000000
Binary files a/docs/public/img/2021/03/06/202103061726777.png and /dev/null differ
diff --git a/docs/public/img/2021/03/06/202103061726888.png b/docs/public/img/2021/03/06/202103061726888.png
deleted file mode 100644
index 6f1fd6362..000000000
Binary files a/docs/public/img/2021/03/06/202103061726888.png and /dev/null differ
diff --git a/docs/public/img/2021/03/06/202103061726999.png b/docs/public/img/2021/03/06/202103061726999.png
deleted file mode 100644
index 65bf5249a..000000000
Binary files a/docs/public/img/2021/03/06/202103061726999.png and /dev/null differ
diff --git a/docs/public/img/2021/03/06/202103061727122.png b/docs/public/img/2021/03/06/202103061727122.png
deleted file mode 100644
index ee7765234..000000000
Binary files a/docs/public/img/2021/03/06/202103061727122.png and /dev/null differ
diff --git a/docs/public/img/2021/03/06/202103061727222.png b/docs/public/img/2021/03/06/202103061727222.png
deleted file mode 100644
index e26b20933..000000000
Binary files a/docs/public/img/2021/03/06/202103061727222.png and /dev/null differ
diff --git a/docs/public/img/2021/03/06/202103061727366.png b/docs/public/img/2021/03/06/202103061727366.png
deleted file mode 100644
index bd2a887ef..000000000
Binary files a/docs/public/img/2021/03/06/202103061727366.png and /dev/null differ
diff --git a/docs/public/img/2021/03/06/202103061727521.png b/docs/public/img/2021/03/06/202103061727521.png
deleted file mode 100644
index d3314af7f..000000000
Binary files a/docs/public/img/2021/03/06/202103061727521.png and /dev/null differ
diff --git a/docs/public/img/2021/03/06/202103061727616.png b/docs/public/img/2021/03/06/202103061727616.png
deleted file mode 100644
index c0c56dda4..000000000
Binary files a/docs/public/img/2021/03/06/202103061727616.png and /dev/null differ
diff --git a/docs/public/img/2021/03/06/202103061727717.png b/docs/public/img/2021/03/06/202103061727717.png
deleted file mode 100644
index 626a69495..000000000
Binary files a/docs/public/img/2021/03/06/202103061727717.png and /dev/null differ
diff --git a/docs/public/img/2021/03/06/202103061727818.png b/docs/public/img/2021/03/06/202103061727818.png
deleted file mode 100644
index 1175397a4..000000000
Binary files a/docs/public/img/2021/03/06/202103061727818.png and /dev/null differ
diff --git a/docs/public/img/2021/03/06/202103061727919.png b/docs/public/img/2021/03/06/202103061727919.png
deleted file mode 100644
index ac0225641..000000000
Binary files a/docs/public/img/2021/03/06/202103061727919.png and /dev/null differ
diff --git a/docs/public/img/2021/03/06/202103061728199.png b/docs/public/img/2021/03/06/202103061728199.png
deleted file mode 100644
index 38f746840..000000000
Binary files a/docs/public/img/2021/03/06/202103061728199.png and /dev/null differ
diff --git a/docs/public/img/2021/03/06/202103061728299.png b/docs/public/img/2021/03/06/202103061728299.png
deleted file mode 100644
index 38ef87488..000000000
Binary files a/docs/public/img/2021/03/06/202103061728299.png and /dev/null differ
diff --git a/docs/public/img/2021/03/06/202103061728399.png b/docs/public/img/2021/03/06/202103061728399.png
deleted file mode 100644
index 8df62d207..000000000
Binary files a/docs/public/img/2021/03/06/202103061728399.png and /dev/null differ
diff --git a/docs/public/img/2021/03/06/202103061728599.png b/docs/public/img/2021/03/06/202103061728599.png
deleted file mode 100644
index b8b1e437e..000000000
Binary files a/docs/public/img/2021/03/06/202103061728599.png and /dev/null differ
diff --git a/docs/public/img/2021/03/06/202103061728699.png b/docs/public/img/2021/03/06/202103061728699.png
deleted file mode 100644
index f00cedcb5..000000000
Binary files a/docs/public/img/2021/03/06/202103061728699.png and /dev/null differ
diff --git a/docs/public/img/2021/03/10/202103101320507.png b/docs/public/img/2021/03/10/202103101320507.png
deleted file mode 100644
index 1c8903840..000000000
Binary files a/docs/public/img/2021/03/10/202103101320507.png and /dev/null differ
diff --git a/docs/public/img/2021/03/10/202103101321066.jpg b/docs/public/img/2021/03/10/202103101321066.jpg
deleted file mode 100644
index 3f4273c02..000000000
Binary files a/docs/public/img/2021/03/10/202103101321066.jpg and /dev/null differ
diff --git a/docs/public/img/2021/03/10/202103101321977.jpg b/docs/public/img/2021/03/10/202103101321977.jpg
deleted file mode 100644
index 28fe9a959..000000000
Binary files a/docs/public/img/2021/03/10/202103101321977.jpg and /dev/null differ
diff --git a/docs/public/img/2021/03/10/202103101322176.png b/docs/public/img/2021/03/10/202103101322176.png
deleted file mode 100644
index 506fa2134..000000000
Binary files a/docs/public/img/2021/03/10/202103101322176.png and /dev/null differ
diff --git a/docs/public/img/2021/03/10/202103101322327.gif b/docs/public/img/2021/03/10/202103101322327.gif
deleted file mode 100644
index f3a92ae54..000000000
Binary files a/docs/public/img/2021/03/10/202103101322327.gif and /dev/null differ
diff --git a/docs/public/img/2021/03/10/202103101322366.png b/docs/public/img/2021/03/10/202103101322366.png
deleted file mode 100644
index b6a3d5cb7..000000000
Binary files a/docs/public/img/2021/03/10/202103101322366.png and /dev/null differ
diff --git a/docs/public/img/2021/03/12/202103121800126.jpg b/docs/public/img/2021/03/12/202103121800126.jpg
deleted file mode 100644
index 200763152..000000000
Binary files a/docs/public/img/2021/03/12/202103121800126.jpg and /dev/null differ
diff --git a/docs/public/img/2021/03/12/202103121800166.webp b/docs/public/img/2021/03/12/202103121800166.webp
deleted file mode 100644
index ab3adcb73..000000000
Binary files a/docs/public/img/2021/03/12/202103121800166.webp and /dev/null differ
diff --git a/docs/public/img/2021/03/12/202103121800226.jpg b/docs/public/img/2021/03/12/202103121800226.jpg
deleted file mode 100644
index f3a0a51cd..000000000
Binary files a/docs/public/img/2021/03/12/202103121800226.jpg and /dev/null differ
diff --git a/docs/public/img/2021/03/12/202103121800266.png b/docs/public/img/2021/03/12/202103121800266.png
deleted file mode 100644
index 7588a487e..000000000
Binary files a/docs/public/img/2021/03/12/202103121800266.png and /dev/null differ
diff --git a/docs/public/img/2021/03/12/202103121800666.png b/docs/public/img/2021/03/12/202103121800666.png
deleted file mode 100644
index 667f103fa..000000000
Binary files a/docs/public/img/2021/03/12/202103121800666.png and /dev/null differ
diff --git a/docs/public/img/2021/05/29/202105291230868.jpg b/docs/public/img/2021/05/29/202105291230868.jpg
deleted file mode 100644
index 6034be66e..000000000
Binary files a/docs/public/img/2021/05/29/202105291230868.jpg and /dev/null differ
diff --git a/docs/public/img/2021/12/01/202112012236800.png b/docs/public/img/2021/12/01/202112012236800.png
deleted file mode 100644
index 7a10e6aaf..000000000
Binary files a/docs/public/img/2021/12/01/202112012236800.png and /dev/null differ
diff --git a/docs/public/img/2021/12/01/202112012236805.jpeg b/docs/public/img/2021/12/01/202112012236805.jpeg
deleted file mode 100644
index 59f4f3f06..000000000
Binary files a/docs/public/img/2021/12/01/202112012236805.jpeg and /dev/null differ
diff --git a/docs/public/img/2021/12/01/202112012236810.png b/docs/public/img/2021/12/01/202112012236810.png
deleted file mode 100644
index 02d08ebcf..000000000
Binary files a/docs/public/img/2021/12/01/202112012236810.png and /dev/null differ
diff --git a/docs/public/img/2021/12/01/202209211547666.png b/docs/public/img/2021/12/01/202209211547666.png
deleted file mode 100644
index 8f8a1dd05..000000000
Binary files a/docs/public/img/2021/12/01/202209211547666.png and /dev/null differ
diff --git a/docs/public/img/2021/12/01/202209211548777.png b/docs/public/img/2021/12/01/202209211548777.png
deleted file mode 100644
index c6ca82247..000000000
Binary files a/docs/public/img/2021/12/01/202209211548777.png and /dev/null differ
diff --git a/docs/public/img/2021/12/01/202209211550888.png b/docs/public/img/2021/12/01/202209211550888.png
deleted file mode 100644
index e1eba2c4f..000000000
Binary files a/docs/public/img/2021/12/01/202209211550888.png and /dev/null differ
diff --git a/docs/public/img/2021/12/01/202209211550999.png b/docs/public/img/2021/12/01/202209211550999.png
deleted file mode 100644
index e7da0f432..000000000
Binary files a/docs/public/img/2021/12/01/202209211550999.png and /dev/null differ
diff --git a/docs/public/img/2021/12/10/202112102211700.jpg b/docs/public/img/2021/12/10/202112102211700.jpg
deleted file mode 100644
index 6d3bdc162..000000000
Binary files a/docs/public/img/2021/12/10/202112102211700.jpg and /dev/null differ
diff --git a/docs/public/img/2021/12/10/202112102211705.png b/docs/public/img/2021/12/10/202112102211705.png
deleted file mode 100644
index 7748177e6..000000000
Binary files a/docs/public/img/2021/12/10/202112102211705.png and /dev/null differ
diff --git a/docs/public/img/2021/12/10/202112102211710.png b/docs/public/img/2021/12/10/202112102211710.png
deleted file mode 100644
index f8d7cc91f..000000000
Binary files a/docs/public/img/2021/12/10/202112102211710.png and /dev/null differ
diff --git a/docs/public/img/2021/12/13/202112132257200.png b/docs/public/img/2021/12/13/202112132257200.png
deleted file mode 100644
index 0cf4373bd..000000000
Binary files a/docs/public/img/2021/12/13/202112132257200.png and /dev/null differ
diff --git a/docs/public/img/2021/12/13/202112132257205.png b/docs/public/img/2021/12/13/202112132257205.png
deleted file mode 100644
index 732104f00..000000000
Binary files a/docs/public/img/2021/12/13/202112132257205.png and /dev/null differ
diff --git a/docs/public/img/2021/12/13/202112132257210.png b/docs/public/img/2021/12/13/202112132257210.png
deleted file mode 100644
index f62444e9c..000000000
Binary files a/docs/public/img/2021/12/13/202112132257210.png and /dev/null differ
diff --git a/docs/public/img/2021/12/13/202112132257215.png b/docs/public/img/2021/12/13/202112132257215.png
deleted file mode 100644
index 32c2fec5f..000000000
Binary files a/docs/public/img/2021/12/13/202112132257215.png and /dev/null differ
diff --git a/docs/public/img/2021/12/13/202112132257220.png b/docs/public/img/2021/12/13/202112132257220.png
deleted file mode 100644
index 18981a2f2..000000000
Binary files a/docs/public/img/2021/12/13/202112132257220.png and /dev/null differ
diff --git a/docs/public/img/2021/12/13/202112132257225.png b/docs/public/img/2021/12/13/202112132257225.png
deleted file mode 100644
index fcb6ba289..000000000
Binary files a/docs/public/img/2021/12/13/202112132257225.png and /dev/null differ
diff --git a/docs/public/img/2021/12/13/202112132257230.png b/docs/public/img/2021/12/13/202112132257230.png
deleted file mode 100644
index 54dc1dcb5..000000000
Binary files a/docs/public/img/2021/12/13/202112132257230.png and /dev/null differ
diff --git a/docs/public/img/2021/12/13/202112132257235.png b/docs/public/img/2021/12/13/202112132257235.png
deleted file mode 100644
index 2a9ba5e8a..000000000
Binary files a/docs/public/img/2021/12/13/202112132257235.png and /dev/null differ
diff --git a/docs/public/img/2022/01/26/202201260941889.png b/docs/public/img/2022/01/26/202201260941889.png
deleted file mode 100644
index 2678f2697..000000000
Binary files a/docs/public/img/2022/01/26/202201260941889.png and /dev/null differ
diff --git a/docs/public/img/2022/01/26/202201260942561.png b/docs/public/img/2022/01/26/202201260942561.png
deleted file mode 100644
index 260235583..000000000
Binary files a/docs/public/img/2022/01/26/202201260942561.png and /dev/null differ
diff --git a/docs/public/img/2022/03/25/202203252252923.png b/docs/public/img/2022/03/25/202203252252923.png
deleted file mode 100644
index 2235aaa0f..000000000
Binary files a/docs/public/img/2022/03/25/202203252252923.png and /dev/null differ
diff --git a/docs/public/img/2022/03/25/202203252252926.png b/docs/public/img/2022/03/25/202203252252926.png
deleted file mode 100644
index dbd54af8f..000000000
Binary files a/docs/public/img/2022/03/25/202203252252926.png and /dev/null differ
diff --git a/docs/public/img/2022/03/25/202203252252929.png b/docs/public/img/2022/03/25/202203252252929.png
deleted file mode 100644
index 8ccf42c46..000000000
Binary files a/docs/public/img/2022/03/25/202203252252929.png and /dev/null differ
diff --git a/docs/public/img/2022/03/25/202203252252931.png b/docs/public/img/2022/03/25/202203252252931.png
deleted file mode 100644
index 5c7ce3cee..000000000
Binary files a/docs/public/img/2022/03/25/202203252252931.png and /dev/null differ
diff --git a/docs/public/img/2022/08/11/202208112010100.png b/docs/public/img/2022/08/11/202208112010100.png
deleted file mode 100644
index 1fa8990d4..000000000
Binary files a/docs/public/img/2022/08/11/202208112010100.png and /dev/null differ
diff --git a/docs/public/img/2022/08/31/202208312238666.png b/docs/public/img/2022/08/31/202208312238666.png
deleted file mode 100644
index 7b4a82592..000000000
Binary files a/docs/public/img/2022/08/31/202208312238666.png and /dev/null differ
diff --git a/docs/public/img/2022/09/05/202209052140666.png b/docs/public/img/2022/09/05/202209052140666.png
deleted file mode 100644
index bff5ef7f7..000000000
Binary files a/docs/public/img/2022/09/05/202209052140666.png and /dev/null differ
diff --git a/docs/public/img/2022/09/05/202209052232777.png b/docs/public/img/2022/09/05/202209052232777.png
deleted file mode 100644
index ed39e1f42..000000000
Binary files a/docs/public/img/2022/09/05/202209052232777.png and /dev/null differ
diff --git a/docs/public/img/2022/09/07/202209072221666.png b/docs/public/img/2022/09/07/202209072221666.png
deleted file mode 100644
index 910ef30e8..000000000
Binary files a/docs/public/img/2022/09/07/202209072221666.png and /dev/null differ
diff --git a/docs/public/img/2022/09/07/202209072245777.png b/docs/public/img/2022/09/07/202209072245777.png
deleted file mode 100644
index 73b2080fe..000000000
Binary files a/docs/public/img/2022/09/07/202209072245777.png and /dev/null differ
diff --git a/docs/public/img/2022/10/15/202210152119199.png b/docs/public/img/2022/10/15/202210152119199.png
deleted file mode 100644
index 923a5278f..000000000
Binary files a/docs/public/img/2022/10/15/202210152119199.png and /dev/null differ
diff --git a/docs/public/img/2022/10/15/202210152120752.png b/docs/public/img/2022/10/15/202210152120752.png
deleted file mode 100644
index aa4e37b3e..000000000
Binary files a/docs/public/img/2022/10/15/202210152120752.png and /dev/null differ
diff --git a/docs/public/img/2022/10/15/202210152132275.png b/docs/public/img/2022/10/15/202210152132275.png
deleted file mode 100644
index 5a968c043..000000000
Binary files a/docs/public/img/2022/10/15/202210152132275.png and /dev/null differ
diff --git a/docs/public/img/2022/10/15/202210152132523.png b/docs/public/img/2022/10/15/202210152132523.png
deleted file mode 100644
index fa0d5132a..000000000
Binary files a/docs/public/img/2022/10/15/202210152132523.png and /dev/null differ
diff --git a/docs/public/img/2022/10/15/202210152132666.png b/docs/public/img/2022/10/15/202210152132666.png
deleted file mode 100644
index ead923374..000000000
Binary files a/docs/public/img/2022/10/15/202210152132666.png and /dev/null differ
diff --git a/docs/public/img/2022/10/22/202210222130166.png b/docs/public/img/2022/10/22/202210222130166.png
deleted file mode 100644
index d6e80b903..000000000
Binary files a/docs/public/img/2022/10/22/202210222130166.png and /dev/null differ
diff --git a/docs/public/img/2022/10/23/202210231130566.png b/docs/public/img/2022/10/23/202210231130566.png
deleted file mode 100644
index a571b4cde..000000000
Binary files a/docs/public/img/2022/10/23/202210231130566.png and /dev/null differ
diff --git a/docs/public/img/2022/10/25/202210252119166.png b/docs/public/img/2022/10/25/202210252119166.png
deleted file mode 100644
index 993c05964..000000000
Binary files a/docs/public/img/2022/10/25/202210252119166.png and /dev/null differ
diff --git a/docs/public/img/2022/10/26/202210262026166.png b/docs/public/img/2022/10/26/202210262026166.png
deleted file mode 100644
index f8262bd53..000000000
Binary files a/docs/public/img/2022/10/26/202210262026166.png and /dev/null differ
diff --git a/docs/public/img/2022/10/26/202210262026266.png b/docs/public/img/2022/10/26/202210262026266.png
deleted file mode 100644
index e9115ae24..000000000
Binary files a/docs/public/img/2022/10/26/202210262026266.png and /dev/null differ
diff --git a/docs/public/img/2022/10/26/202210262026566.png b/docs/public/img/2022/10/26/202210262026566.png
deleted file mode 100644
index e1582e586..000000000
Binary files a/docs/public/img/2022/10/26/202210262026566.png and /dev/null differ
diff --git a/docs/public/img/2022/10/28/202210282235156.png b/docs/public/img/2022/10/28/202210282235156.png
deleted file mode 100644
index be35b06da..000000000
Binary files a/docs/public/img/2022/10/28/202210282235156.png and /dev/null differ
diff --git a/docs/public/img/2022/10/28/202210282236211.png b/docs/public/img/2022/10/28/202210282236211.png
deleted file mode 100644
index 698cdfe20..000000000
Binary files a/docs/public/img/2022/10/28/202210282236211.png and /dev/null differ
diff --git a/docs/public/img/2022/10/29/202210291930211.png b/docs/public/img/2022/10/29/202210291930211.png
deleted file mode 100644
index 95568b094..000000000
Binary files a/docs/public/img/2022/10/29/202210291930211.png and /dev/null differ
diff --git a/docs/public/img/2022/10/31/202210312020985.png b/docs/public/img/2022/10/31/202210312020985.png
deleted file mode 100644
index 47476c2a5..000000000
Binary files a/docs/public/img/2022/10/31/202210312020985.png and /dev/null differ
diff --git a/docs/public/img/2022/11/01/202211012021157.png b/docs/public/img/2022/11/01/202211012021157.png
deleted file mode 100644
index 4d941a061..000000000
Binary files a/docs/public/img/2022/11/01/202211012021157.png and /dev/null differ
diff --git a/docs/public/img/2022/11/01/202211012022122.png b/docs/public/img/2022/11/01/202211012022122.png
deleted file mode 100644
index dda193d8c..000000000
Binary files a/docs/public/img/2022/11/01/202211012022122.png and /dev/null differ
diff --git a/docs/public/img/2022/11/01/202211012022222.png b/docs/public/img/2022/11/01/202211012022222.png
deleted file mode 100644
index d7c1c20ba..000000000
Binary files a/docs/public/img/2022/11/01/202211012022222.png and /dev/null differ
diff --git a/docs/public/img/2022/11/01/202211012025211.png b/docs/public/img/2022/11/01/202211012025211.png
deleted file mode 100644
index d4a100143..000000000
Binary files a/docs/public/img/2022/11/01/202211012025211.png and /dev/null differ
diff --git a/docs/public/img/2022/11/04/202211042020211.png b/docs/public/img/2022/11/04/202211042020211.png
deleted file mode 100644
index e6b471a2d..000000000
Binary files a/docs/public/img/2022/11/04/202211042020211.png and /dev/null differ
diff --git a/docs/public/img/2022/11/06/202211061520256.png b/docs/public/img/2022/11/06/202211061520256.png
deleted file mode 100644
index db7a88332..000000000
Binary files a/docs/public/img/2022/11/06/202211061520256.png and /dev/null differ
diff --git a/docs/public/img/2022/11/06/202211061523521.png b/docs/public/img/2022/11/06/202211061523521.png
deleted file mode 100644
index b83c2dcd0..000000000
Binary files a/docs/public/img/2022/11/06/202211061523521.png and /dev/null differ
diff --git a/docs/public/img/2022/11/23/202211232120211.png b/docs/public/img/2022/11/23/202211232120211.png
deleted file mode 100644
index ac58e5994..000000000
Binary files a/docs/public/img/2022/11/23/202211232120211.png and /dev/null differ
diff --git a/docs/public/img/2022/11/23/202211232120226.png b/docs/public/img/2022/11/23/202211232120226.png
deleted file mode 100644
index b88c4c1b2..000000000
Binary files a/docs/public/img/2022/11/23/202211232120226.png and /dev/null differ
diff --git a/docs/public/img/2022/11/23/202211232120521.png b/docs/public/img/2022/11/23/202211232120521.png
deleted file mode 100644
index f846e715b..000000000
Binary files a/docs/public/img/2022/11/23/202211232120521.png and /dev/null differ
diff --git a/docs/public/img/2022/11/23/202211232121256.png b/docs/public/img/2022/11/23/202211232121256.png
deleted file mode 100644
index c885b5dc3..000000000
Binary files a/docs/public/img/2022/11/23/202211232121256.png and /dev/null differ
diff --git a/docs/public/img/2022/11/23/202211232121356.png b/docs/public/img/2022/11/23/202211232121356.png
deleted file mode 100644
index 9fafa3628..000000000
Binary files a/docs/public/img/2022/11/23/202211232121356.png and /dev/null differ
diff --git a/docs/public/img/2022/11/23/202211232122166.png b/docs/public/img/2022/11/23/202211232122166.png
deleted file mode 100644
index 674aa05c4..000000000
Binary files a/docs/public/img/2022/11/23/202211232122166.png and /dev/null differ
diff --git a/docs/public/img/2022/12/07/202212072131211.png b/docs/public/img/2022/12/07/202212072131211.png
deleted file mode 100644
index c0e9f9520..000000000
Binary files a/docs/public/img/2022/12/07/202212072131211.png and /dev/null differ
diff --git a/docs/public/img/2022/12/07/202212072131521.png b/docs/public/img/2022/12/07/202212072131521.png
deleted file mode 100644
index 406f654f3..000000000
Binary files a/docs/public/img/2022/12/07/202212072131521.png and /dev/null differ
diff --git a/docs/public/img/2022/12/07/202212072132211.png b/docs/public/img/2022/12/07/202212072132211.png
deleted file mode 100644
index 4592c308f..000000000
Binary files a/docs/public/img/2022/12/07/202212072132211.png and /dev/null differ
diff --git a/docs/public/img/2022/12/07/202212072132521.png b/docs/public/img/2022/12/07/202212072132521.png
deleted file mode 100644
index becdba135..000000000
Binary files a/docs/public/img/2022/12/07/202212072132521.png and /dev/null differ
diff --git a/docs/public/img/2022/12/07/202212072135211.png b/docs/public/img/2022/12/07/202212072135211.png
deleted file mode 100644
index dc1d8a09a..000000000
Binary files a/docs/public/img/2022/12/07/202212072135211.png and /dev/null differ
diff --git a/docs/public/img/2023/01/06/202301062024211.png b/docs/public/img/2023/01/06/202301062024211.png
deleted file mode 100644
index de59a62a4..000000000
Binary files a/docs/public/img/2023/01/06/202301062024211.png and /dev/null differ
diff --git a/docs/public/img/2023/01/06/202301062027985.png b/docs/public/img/2023/01/06/202301062027985.png
deleted file mode 100644
index 42644497d..000000000
Binary files a/docs/public/img/2023/01/06/202301062027985.png and /dev/null differ
diff --git a/docs/public/img/2023/12/21/202312212227123.png b/docs/public/img/2023/12/21/202312212227123.png
deleted file mode 100644
index 857fafd3f..000000000
Binary files a/docs/public/img/2023/12/21/202312212227123.png and /dev/null differ
diff --git a/docs/public/img/2023/12/21/202312212228233.png b/docs/public/img/2023/12/21/202312212228233.png
deleted file mode 100644
index 8f4a91198..000000000
Binary files a/docs/public/img/2023/12/21/202312212228233.png and /dev/null differ
diff --git a/docs/public/img/2023/12/21/202312212230256.png b/docs/public/img/2023/12/21/202312212230256.png
deleted file mode 100644
index 2ee1ad46b..000000000
Binary files a/docs/public/img/2023/12/21/202312212230256.png and /dev/null differ
diff --git a/docs/public/img/2023/12/21/202312212231916.png b/docs/public/img/2023/12/21/202312212231916.png
deleted file mode 100644
index 39c44a9a5..000000000
Binary files a/docs/public/img/2023/12/21/202312212231916.png and /dev/null differ
diff --git a/docs/public/img/2023/12/21/202312212232922.png b/docs/public/img/2023/12/21/202312212232922.png
deleted file mode 100644
index f61331a51..000000000
Binary files a/docs/public/img/2023/12/21/202312212232922.png and /dev/null differ
diff --git a/docs/public/img/2023/12/21/202312212234232.png b/docs/public/img/2023/12/21/202312212234232.png
deleted file mode 100644
index 8245b61d5..000000000
Binary files a/docs/public/img/2023/12/21/202312212234232.png and /dev/null differ
diff --git a/docs/public/img/2023/12/21/202312212235127.png b/docs/public/img/2023/12/21/202312212235127.png
deleted file mode 100644
index b46c25c46..000000000
Binary files a/docs/public/img/2023/12/21/202312212235127.png and /dev/null differ
diff --git a/docs/public/img/svg/about-footer.svg b/docs/public/img/svg/about-footer.svg
deleted file mode 100644
index 680919a49..000000000
--- a/docs/public/img/svg/about-footer.svg
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/docs/public/img/svg/about-me-header.svg b/docs/public/img/svg/about-me-header.svg
deleted file mode 100644
index 3d042eea9..000000000
--- a/docs/public/img/svg/about-me-header.svg
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 关于我 (Charles7c)
-
-
-
\ No newline at end of file
diff --git a/docs/public/img/svg/about-repos-header.svg b/docs/public/img/svg/about-repos-header.svg
deleted file mode 100644
index d5c5ad02f..000000000
--- a/docs/public/img/svg/about-repos-header.svg
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 关于知识库 (查尔斯的知识库)
-
-
-
\ No newline at end of file
diff --git a/package.json b/package.json
index 8aa812ab7..bdc1b2a73 100644
--- a/package.json
+++ b/package.json
@@ -11,28 +11,28 @@
"preview": "vitepress preview docs"
},
"devDependencies": {
- "@arco-design/web-vue": "^2.53.3",
- "@types/blueimp-md5": "^2.18.2",
- "@types/jquery": "^3.5.29",
- "markdown-it": "^13.0.2",
- "markdown-it-footnote": "^3.0.3",
- "markdown-it-mathjax3": "^4.3.2",
+ "@arco-design/web-vue": "2.57.0",
+ "@types/blueimp-md5": "2.18.2",
+ "@types/jquery": "3.5.29",
+ "markdown-it": "14.1.0",
+ "markdown-it-footnote": "3.0.3",
+ "markdown-it-mathjax3": "4.3.2",
"mermaid": "9.3.0",
- "unplugin-vue-components": "^0.25.2",
- "vite": "^5.0.3",
- "vitepress": "1.0.0-rc.31",
+ "unplugin-vue-components": "0.25.2",
+ "vite": "5.0.3",
+ "vitepress": "1.6.3",
"vitepress-plugin-mermaid": "2.0.8",
- "vue": "^3.3.9"
+ "vue": "3.5.13"
},
"dependencies": {
"@antv/g2plot": "2.4.31",
- "axios": "^1.6.2",
- "blueimp-md5": "^2.19.0",
- "dayjs": "^1.11.10",
- "fast-glob": "^3.3.2",
- "gitalk": "^1.8.0",
- "gray-matter": "^4.0.3",
- "jquery": "^3.7.1"
+ "axios": "1.6.2",
+ "blueimp-md5": "2.19.0",
+ "dayjs": "1.11.10",
+ "fast-glob": "3.3.2",
+ "gitalk": "1.8.0",
+ "gray-matter": "4.0.3",
+ "jquery": "3.7.1"
},
"pnpm": {
"overrides": {