0%

分页查询

pagehelp
1
2
3
4
5
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper}</version>
</dependency>
1
2
3
4
5
6
 	PageHelper.startPage(employeePageQueryDTO.getPage(), 			   	 employeePageQueryDTO.getPageSize());//初始化page的页码和每页信息条数,这两条信息会由pagehelp拼接到mapper的sql语句中

Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO);
//Page中有一个列表接受对象信息
long total = page.getTotal();//获取信息条数
List<Employee> records = page.getResult();

日期类型格式转换

1). 方式一

在属性上加上注解,对日期进行格式化

image-20221112103501581

但这种方式,需要在每个时间属性上都要加上该注解,使用较麻烦,不能全局处理。

2). 方式二(推荐 )

在WebMvcConfiguration中扩展SpringMVC的消息转换器,统一对日期类型进行格式处理

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 扩展Spring MVC框架的消息转化器
* @param converters
*/
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("扩展消息转换器...");
//创建一个消息转换器对象
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
//需要为消息转换器设置一个对象转换器,对象转换器可以将Java对象序列化为json数据
converter.setObjectMapper(new JacksonObjectMapper());
//将自己的消息转化器加入容器中
converters.add(0,converter);
}

路径参数

1
2
3
4
5
6
@PostMapping("/status/{status}")
@ApiOperation("启用禁用员工账号")
public Result startOrStop(@PathVariable Integer status,Long id){
employeeService.startOrStop(status,id);
return Result.success();
}

本地仓库管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 初始化git仓库
git init
#####################创建文件并提交#####################
# 将修改加入暂存区
git add .
# 将修改提交到本地仓库,提交记录内容为:commit1
git commit -m 'commit1'
# 查看日志
git log
####################修改文件并提交######################

# 将修改加入暂存区
git add .
# # 将修改提交到本地仓库,提交记录内容为:update file
git commit --m 'update file'
# 查看日志
git log
# 以精简的方式显示提交记录
git-log
####################将最后一次修改还原##################
# 查看提交记录
git-log
# 找到倒数第2次提交的commitID
# 版本回退
git reset commitID --hard

分支管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
###########################创建并切换到dev01分支,在dev01分支提交
# [master]创建分支dev01
git branch dev01
# [master]切换到dev01
git checkout dev01
# [dev01]创建文件file02.txt
# [dev01]将修改加入暂存区并提交到仓库,提交记录内容为:add file02 on dev
git add .
git commit -m 'add file02 on dev'
# [dev01]以精简的方式显示提交记录
git-log
###########################切换到master分支,将dev01合并到master分支
# [dev01]切换到master分支
git checkout master
# [master]合并dev01到master分支
git merge dev01
# [master]以精简的方式显示提交记录
git-log
# [master]查看文件变化(目录下也出现了file02.txt)
##########################删除dev01分支
# [master]删除dev01分支
git branch -d dev01
# [master]以精简的方式显示提交记录
git-log

远程仓库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#将本地仓库推送到远程仓库

#添加远程仓库
git remote add origin git@gitee.com/**/**.git
#将master分支推送到远程仓库,并与远程仓库的master分支绑定关联关系
git push --set-upstream origin master
#将远程仓库克隆到本地
# 将远程仓库克隆到本地git_test02目录下
git clone git@gitee.com/**/**.git git_test02
# [git_test02]以精简的方式显示提交记录
git-log
##3-将本地修改推送到远程仓库
# [git_test01]创建文件file03.txt

# [git_test01]将修改加入暂存区并提交到仓库,提交记录内容为:add file03
git add .
git commit -m 'add file03'
# [git_test01]将master分支的修改推送到远程仓库
git push origin master
###########################4-将远程仓库的修改更新到本地
# [git_test02]将远程仓库修改再拉取到本地
git pull
# 以精简的方式显示提交记录
git-log
#

idea中git操作

字面量

字面量类型 说明 程序中的写法
整数 不带小数的数字 22,33
小数 带小数的数字 13.14,-5.21
字符 必须使用单引号,有且仅能一个字符 ‘m’,‘3’, ‘我’
字符串 必须使用双引号,内容可有可无 “HelloWorld”
布尔值 布尔值,表示真假,只有两个值:true,false true 、false
空值 一个特殊的值,空值 值是:null
  1. 不带小数点的数字都是整数类型的字面量。
  2. 只要带了小数点,那么就是小数类型的字面量。
  3. 只要用双引号引起来的,不管里面的内容是什么,不管里面有没有内容,都是字符串类型的字面量。
  4. 字符类型的字面量必须用单引号引起来,不管内容是什么,但是个数有且只能有一个。
  5. 字符类型的字面量只有两个值,true、false。
  6. 空类型的字面量只有一个值,null。

变量

1 什么是变量?

​ 变量就在程序中临时存储数据的容器。但是这个容器中只能存一个值。

2 变量的定义格式

​ 数据类型 变量名 = 数据值;

2.1 格式详解

​ 数据类型:限定了变量当中能存储什么类型的数据。

​ 如果要存10,那么数据类型就需要写整数类型。

​ 如果要存10.0,那么数据类型就需要写小数类型。

​ 变量名:其实就是这个容器的名字。

​ 当以后想要使用变量里面的数据时,直接使用变量名就可以了。

​ 数据值:真正存储在容器中的数据。

​ 分号:表示语句的结束,就跟以前写作文时候的句号是一样的。

数据类型

1. Java语言数据类型的分类

  • 基本数据类型
  • 引用数据类型(面向对象的时候再深入学习)

2. 基本数据类型的四类八种

数据类型 关键字 内存占用 取值范围
整数 byte 1 负的2的7次方 ~ 2的7次方-1(-128~127)
short 2 负的2的15次方 ~ 2的15次方-1(-32768~32767)
int 4 负的2的31次方 ~ 2的31次方-1
long 8 负的2的63次方 ~ 2的63次方-1
浮点数 float 4 1.401298e-45 ~ 3.402823e+38
double 8 4.9000000e-324 ~ 1.797693e+308
字符 char 2 0-65535
布尔 boolean 1 true,false
  • 如果要定义 一个整数类型的变量,不知道选择哪种数据类型了,默认使用int。
  • 如果要定义 一个小数类型的变量,不知道选择哪种数据类型了,默认使用double。
  • 如果要定义一个long类型的变量,那么在数据值的后面需要加上L后缀。(大小写都可以,建议大写。)
  • 如果要定义一个float类型的变量,那么在数据值的后面需要加上F后缀。(大小写都可以)

标识符

业内大多数程序员都在遵守阿里巴巴的命名规则。

1 硬性要求:

​ 必须要这么做,否则代码会报错。

  • 必须由数字、字母、下划线_、美元符号$组成。
  • 数字不能开头
  • 不能是关键字
  • 区分大小写的。

2 软件建议:

​ 如果不这么做,代码不会报错,但是会让代码显得比较low。

2.1 小驼峰命名法

适用于变量名和方法名

  • 如果是一个单词,那么全部小写,比如:name

  • 如果是多个单词,那么从第二个单词开始,首字母大写,比如:firstName、maxAge

2.2 大驼峰命名法

适用于类名

  • 如果是一个单词,那么首字母大写。比如:Demo、Test。

  • 如果是多个单词,那么每一个单词首字母都需要大写。比如:HelloWorld

不管起什么名字,都要做到见名知意。

阿里巴巴命名规范细节:

  1. 尽量不要用拼音。但是一些国际通用的拼音可视为英文单词。

    正确:alibaba、hangzhou、nanjing

    错误:jiage、dazhe

  2. 平时在给变量名、方法名、类名起名字的时候,不要使用下划线或美元符号。

    错误:_name

    正确:name

键盘录入

1
2
3
4
5
6
7
8
9
10
11
12
13
//导包,其实就是先找到Scanner这个类在哪
import java.util.Scanner;
public class ScannerDemo1{
public static void main(String[] args){
//2.创建对象,其实就是申明一下,我准备开始用Scanner这个类了。
Scanner sc = new Scanner(System.in);
//3.接收数据
//当程序运行之后,我们在键盘输入的数据就会被变量i给接收了
System.out.println("请输入一个数字");
int i = sc.nextInt();
System.out.println(i);
}
}

运算符

运算符

运算符是对常量或变量进行操作的符号,例如:

  • +:加法
  • -:减法
  • *:乘法
  • /:除法

表达式

表达式是用运算符将常量或变量连接起来的式子,符合 Java 语法。例如:

  • a + b:一个算术表达式

二、算术运算符详解

算术运算符包括 + - * / %。具体特点如下:

  • + - *:与小学数学运算一致。

  • /:整数相除结果只取整数部分,若需小数结果则需有小数参数参与。

    1
    2
    System.out.println(10 / 3); // 输出 3
    System.out.println(10.0 / 3); // 输出 3.3333333333333335
  • %:取模(余数),可用于判断奇偶数等。

    1
    System.out.println(10 % 3); // 输出 1

三、数值拆分练习

通过键盘录入一个三位数,利用公式拆分为个位、十位、百位并打印。公式如下:

  • 个位:数字 % 10
  • 十位:数字 / 10 % 10
  • 百位:数字 / 100 % 10

代码示例:

1
2
3
4
5
6
7
8
9
10
11
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个三位数");
int number = sc.nextInt(); // 123

int ones = number % 10; // 个位
int tens = number / 10 % 10; // 十位
int hundreds = number / 100 % 10; // 百位

System.out.println(ones);
System.out.println(tens);
System.out.println(hundreds);

四、隐式转换规则

隐式转换即自动类型提升,将取值范围小的数据或变量赋给取值范围大的变量,无需额外代码。规则如下:

  1. 取值范围小的与大的运算,小的会先提升为大的再运算。
  2. byteshortchar在运算时会先提升为int再运算。
  3. 取值范围从小到大顺序:byte short int long float double

五、隐式转换练习案例

  1. double d = 10;int类型(10)自动提升为double类型赋给d
  2. byte b = 100; int i = b;byte类型(b)自动提升为int类型赋给i
  3. int i = 10; long n = 20L;in运算时i提升为long类型,结果为long类型。
  4. int i = 10; long n = 100L; double d = 20.0;in运算时先提升为long,再与d运算时都提升为double,结果为double类型。
  5. byte b1 = 10; byte b2 = 20;b1b2运算时都提升为int类型,结果为int类型。
  6. byte b = 10; short s = 20; long n = 100L;bs先提升为int,再与n运算时提升为long,结果为long类型。

六、强制转换要点

强制转换是将取值范围大的数据或变量赋给取值范围小的变量时需进行的操作,格式为目标数据类型 变量名 = (目标数据类型)被强转的数据。例如:

1
2
double a = 12.3;
int b = (int) a; // 强制转换,可能丢失精度

注意:强制转换可能导致数据精度丢失。

七、字符串的 + 操作

+ 操作中出现字符串时,为字符串连接符,会将前后数据拼接成新字符串,且从左到右逐个执行。例如:

1
2
1 + "abc" + 1; // 结果为 "1abc1"
1 + 2 + "abc" + 2 + 1; // 结果为 "3abc21"

八、字符的 + 操作

字符的 + 操作会先查 ASCII 码表对应数字再进行计算。例如:

1
2
char c = 'a';
int result = c + 0; // 结果为 97

九、算术运算符总结

算术运算符 + - * / % 操作与小学数学类似,注意以下几点:

  • /% 的区别:/ 取结果的商,% 取结果的余数。
  • 整数运算只能得到整数,若需小数结果则需有浮点数参与。
  • 字符串只有 + 操作。

十、自增自减运算符

自增(++)和自减(--)运算符用于变量值的加 1 或减 1 操作,可放在变量前后,单独一行时结果相同。应用场景包括:

  • 年龄增长
  • 商品数量选择
  • 数据统计等

十一、赋值运算符

赋值运算符(=)将等号右边结果赋给左边变量。扩展赋值运算符(+=、-=、*=、/=、%=)会进行相应运算后将结果赋值给左边变量,且隐含强制转换。例如:

1
2
int a = 10;
a += 20; // 相当于 a = a + 20;

十二、关系运算符

关系运算符(==、!=、>、>=、<、<=)用于比较左右两边数据,结果为布尔类型。注意:

  • == 用于判断相等
  • != 用于判断不相等
  • >>=<<= 用于大小比较
  • 注意 === 的区别

十三、逻辑运算符

逻辑运算符包括:

  • &(逻辑与):两边都为真,结果才为真。
  • |(逻辑或):两边都为假,结果才为假。
  • ^(异或):两边相同,结果为假;不同,结果为真。
  • !(取反):对布尔值取反,注意取反最多用一个。

十四、短路逻辑运算符

短路逻辑运算符(&&、||)与逻辑运算符结果相同,但具有短路效果,当左边能确定整个表达式结果时,右边不会执行,提高代码效率。例如:

  • 用户名正确 && 密码正确:若用户名错误,不会验证密码
  • 有房 || 有车:若有房,不会检查是否有车

十五、三元运算符

三元运算符格式为 关系表达式 ? 表达式1 : 表达式2;,根据关系表达式结果选择执行表达式1或表达式2,结果需被使用。例如:

1
int max = a > b ? a : b;

十六、练习案例

练习 1:判断两只老虎体重是否相同

通过键盘录入两只老虎的体重,利用三元运算符输出结果。

1
2
3
4
5
6
7
8
Scanner sc = new Scanner(System.in);
System.out.println("请输入第一只老虎的体重");
int weight1 = sc.nextInt();
System.out.println("请输入第二只老虎的体重");
int weight2 = sc.nextInt();

String result = weight1 == weight2 ? "相同" : "不相同";
System.out.println(result);

练习 2:求三个和尚的最高身高

已知三个和尚的身高分别为 150cm、210cm、165cm,利用三元运算符求出最高身高。

1
2
3
4
5
6
7
8
int height1 = 150;
int height2 = 210;
int height3 = 165;

int temp = height1 > height2 ? height1 : height2;
int max = temp > height3 ? temp : height3;

System.out.println(max);

十七、运算符优先级

Java 中各运算符有优先级,但只需记住小括号优先级最高。使用小括号可以

十八、运算符优先级与结合性

优先级

  • 运算符优先级决定了表达式中运算的先后顺序。优先级高的运算符先进行计算。

  • 例如,乘除运算符(*/%)的优先级高于加减运算符(+-)。

    1
    int result = 10 + 5 * 2; // 先计算 5 * 2,再计算 10 + 10,结果为 20

结合性

  • 结合性决定了同优先级运算符的计算方向,分为左结合性和右结合性。

  • 大多数运算符是左结合的,即从左到右计算。例如:

    1
    int result = 10 - 5 - 3; // 先计算 10 - 5,再计算 5 - 3,结果为 2
  • 赋值运算符是右结合的,即从右到左计算。例如:

    1
    2
    int a, b, c;
    a = b = c = 10; // 先计算 c = 10,再计算 b = 10,最后计算 a = 10

十九、位运算符

位运算符用于对整数的二进制位进行操作,包括:

  • &:按位与
  • |:按位或
  • ^:按位异或
  • ~:按位取反
  • <<:左移
  • >>:右移(符号位填充)
  • >>>:无符号右移(零填充)

按位与(&

  • 对两个操作数的每一位进行与运算,只有当两个位都为 1 时,结果位才为 1。

    1
    2
    3
    int a = 5; // 二进制:0101
    int b = 3; // 二进制:0011
    int result = a & b; // 二进制:0001,结果为 1

按位或(|

  • 对两个操作数的每一位进行或运算,只要有一个位为 1,结果位就为 1。

    1
    2
    3
    int a = 5; // 二进制:0101
    int b = 3; // 二进制:0011
    int result = a | b; // 二进制:0111,结果为 7

按位异或(^

  • 对两个操作数的每一位进行异或运算,只有当两个位不相同时,结果位才为 1。

    1
    2
    3
    int a = 5; // 二进制:0101
    int b = 3; // 二进制:0011
    int result = a ^ b; // 二进制:0110,结果为 6

按位取反(~

  • 对操作数的每一位进行取反运算,0 变 1,1 变 0。

    1
    2
    int a = 5; // 二进制:0101
    int result = ~a; // 二进制:1010,结果为 -6(补码表示)

左移(<<

  • 将操作数的二进制位向左移动指定的位数,右侧补 0。

    1
    2
    int a = 5; // 二进制:0101
    int result = a << 1; // 二进制:1010,结果为 10

右移(>>

  • 将操作数的二进制位向右移动指定的位数,左侧补符号位(正数补 0,负数补 1)。

    1
    2
    int a = 5; // 二进制:0101
    int result = a >> 1; // 二进制:0010,结果为 2

无符号右移(>>>

  • 将操作数的二进制位向右移动指定的位数,左侧补 0,不考虑符号位。

    1
    2
    int a = -5; // 二进制:11111111 11111111 11111111 11111011(补码表示)
    int result = a >>> 1; // 二进制:01111111 11111111 11111111 11111101,结果为 2147483645

二十、实例应用

实例 1:计算两个数的平均值

1
2
3
4
5
6
7
8
public class AverageCalculator {
public static void main(String[] args) {
double num1 = 10.5;
double num2 = 20.3;
double average = (num1 + num2) / 2;
System.out.println("两个数的平均值是:" + average);
}
}

实例 2:判断一个数是否为素数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class PrimeChecker {
public static void main(String[] args) {
int number = 29;
boolean isPrime = true;

if (number <= 1) {
isPrime = false;
} else {
for (int i = 2; i <= Math.sqrt(number); i++) {
if (number % i == 0) {
isPrime = false;
break;
}
}
}

if (isPrime) {
System.out.println(number + " 是素数");
} else {
System.out.println(number + " 不是素数");
}
}
}

实例 3:实现一个简单的计算器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class SimpleCalculator {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入第一个数:");
double num1 = sc.nextDouble();
System.out.println("请输入运算符(+、-、*、/):");
char operator = sc.next().charAt(0);
System.out.println("请输入第二个数:");
double num2 = sc.nextDouble();

double result;

switch (operator) {
case '+':
result = num1 + num2;
break;
case '-':
result = num1 - num2;
break;
case '*':
result = num1 * num2;
break;
case '/':
if (num2 != 0) {
result = num1 / num2;
} else {
System.out.println("除数不能为 0");
return;
}
break;
default:
System.out.println("无效的运算符");
return;
}

System.out.println("结果:" + result);
}
}

流程控制语句

1.1 流程控制语句分类

  • 顺序结构:程序中最简单最基本的流程控制,按照代码的先后顺序依次执行。
  • 判断和选择结构ifswitch 语句,用于根据条件选择执行特定的代码块。
  • 循环结构forwhiledo...while 循环,用于在满足条件的情况下反复执行某一段代码。

1.2 顺序结构

顺序结构是程序中最简单最基本的流程控制,没有特定的语法结构,按照代码的先后顺序依次执行。

第二章 判断语句:if 语句

2.1 if 语句格式 1

1
2
3
if (关系表达式) {
语句体;
}

执行流程:

  1. 首先计算关系表达式的值。
  2. 如果关系表达式的值为 true,则执行语句体。
  3. 如果关系表达式的值为 false,则不执行语句体。
  4. 继续执行后面的语句内容。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class IfDemo {
public static void main(String[] args) {
System.out.println("开始");
int a = 10;
int b = 20;
if (a == b) {
System.out.println("a等于b");
}
int c = 10;
if (a == c) {
System.out.println("a等于c");
}
System.out.println("结束");
}
}

2.2 if 语句格式 2

1
2
3
4
5
if (关系表达式) {
语句体1;
} else {
语句体2;
}

执行流程:

  1. 首先计算关系表达式的值。
  2. 如果关系表达式的值为 true,则执行语句体1。
  3. 如果关系表达式的值为 false,则执行语句体2。
  4. 继续执行后面的语句内容。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class IfDemo02 {
public static void main(String[] args) {
System.out.println("开始");
int a = 10;
int b = 20;
if (a > b) {
System.out.println("a的值大于b");
} else {
System.out.println("a的值不大于b");
}
System.out.println("结束");
}
}

2.3 if 语句格式 3

1
2
3
4
5
6
7
8
9
if (关系表达式1) {
语句体1;
} else if (关系表达式2) {
语句体2;
}
...
else {
语句体n+1;
}

执行流程:

  1. 首先计算关系表达式1的值。
  2. 如果值为 true,则执行语句体1;如果值为 false,则计算关系表达式2的值。
  3. 如果值为 true,则执行语句体2;如果值为 false,则计算关系表达式3的值。
  4. 如果没有任何关系表达式为 true,则执行语句体n+1。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class IfDemo03 {
public static void main(String[] args) {
System.out.println("开始");
int score = 85;
if (score >= 95 && score <= 100) {
System.out.println("送自行车一辆");
} else if (score >= 90 && score <= 94) {
System.out.println("游乐场玩一天");
} else if (score >= 80 && score <= 89) {
System.out.println("变形金刚一个");
} else {
System.out.println("胖揍一顿");
}
System.out.println("结束");
}
}

第三章 switch 语句

3.1 格式

1
2
3
4
5
6
7
8
9
10
11
12
switch (表达式) {
case 1:
语句体1;
break;
case 2:
语句体2;
break;
...
default:
语句体n+1;
break;
}

3.2 执行流程

  1. 首先计算出表达式的值。
  2. 其次,和 case 依次比较,一旦有对应的值,就会执行相应的语句,在执行的过程中,遇到 break 就会结束。
  3. 最后,如果所有的 case 都和表达式的值不匹配,就会执行 default 语句体部分,然后程序结束。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class SwitchDemo2 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个整数表示星期");
int week = sc.nextInt();

switch (week) {
case 1:
System.out.println("跑步");
break;
case 2:
System.out.println("游泳");
break;
case 3:
System.out.println("慢走");
break;
case 4:
System.out.println("动感单车");
break;
case 5:
System.out.println("拳击");
break;
case 6:
System.out.println("爬山");
break;
case 7:
System.out.println("好好吃一顿");
break;
default:
System.out.println("输入错误,没有这个星期");
break;
}
}
}

3.3 switch 的扩展知识

  • default 的位置和省略情况default 可以放在任意位置,也可以省略。

  • case 穿透:不写 break 会引发 case 穿透现象。

  • switch 在 JDK12 的新特性

    1
    2
    3
    4
    5
    6
    7
    int number = 10;
    switch (number) {
    case 1 -> System.out.println("一");
    case 2 -> System.out.println("二");
    case 3 -> System.out.println("三");
    default -> System.out.println("其他");
    }
  • switch 和 if 第三种格式各自的使用场景

    • 当需要对一个范围进行判断时,用 if 的第三种格式。
    • 当把有限个数据列举出来,选择其中一个执行时,用 switch 语句。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class SwitchDemo3 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入星期");
int week = sc.nextInt();

switch (week) {
case 1, 2, 3, 4, 5 -> System.out.println("工作日");
case 6, 7 -> System.out.println("休息日");
default -> System.out.println("没有这个星期");
}
}
}

第四章 循环结构

4.1 for 循环结构

4.1.1 for 循环格式
1
2
3
for (初始化语句; 条件判断语句; 条件控制语句) {
循环体语句;
}

格式解释:

  • 初始化语句:用于表示循环开启时的起始状态。
  • 条件判断语句:用于表示循环反复执行的条件。
  • 循环体语句:用于表示循环反复执行的内容。
  • 条件控制语句:用于表示循环执行中每次变化的内容。

执行流程:

  1. 执行初始化语句。
  2. 执行条件判断语句,看其结果是 true 还是 false
    • 如果是 false,循环结束。
    • 如果是 true,继续执行。
  3. 执行循环体语句。
  4. 执行条件控制语句。
  5. 回到第 2 步继续。

示例:

1
2
3
4
5
6
7
public class ForTest01 {
public static void main(String[] args) {
for (int i = 1; i <= 5; i++) {
System.out.println("HelloWorld");
}
}
}

4.2 while 循环

4.2.1 格式
1
2
3
4
5
初始化语句;
while (条件判断语句) {
循环体;
条件控制语句;
}

示例:

1
2
3
4
5
6
7
8
9
10
public class WhileTest01 {
public static void main(String[] args) {
int i = 1;
while (i <= 5) {
System.out.println("HelloWorld");
i++;
}
System.out.println(i);
}
}

4.3 do…while 循环

格式:

1
2
3
4
5
初始化语句;
do {
循环体;
条件控制语句;
} while (条件判断语句);

特点:

  • 先执行循环体,再进行条件判断。
  • 无论条件是否满足,循环体至少执行一次。

示例:

1
2
3
4
5
6
7
8
9
10
public class DoWhileTest01 {
public static void main(String[] args) {
int i = 1;
do {
System.out.println("HelloWorld");
i++;
} while (i <= 5);
System.out.println(i);
}
}

4.4 三种循环格式的区别

  • for 循环
    • 适用于知道循环次数或循环范围的情况。
    • 语法结构紧凑,初始化、条件判断、条件控制语句都在一个地方,代码更清晰。
  • while 循环
    • 适用于不知道循环次数,但知道循环结束条件的情况。
    • 代码结构相对灵活,初始化和条件控制语句可以分散在不同位置。
  • do…while 循环
    • 适用于至少需要执行一次循环体的情况。
    • 先执行循环体,再进行条件判断,循环体至少执行一次。

条件控制语句语句总结

break 语句

  • 作用:用于终止循环或 switch 语句的执行。

continue 语句

  • 作用:用于结束当前循环的本次迭代,继续执行下一次迭代

return 语句

  • 作用:用于从方法中返回,可以返回一个值或不返回值。

数组

1. 数组概念

数组是一种容器,用于存储同种数据类型的多个值。数组在存储数据时需要结合隐式转换考虑。例如,定义了一个 int 类型的数组,那么 booleandouble 类型的数据不能存入该数组,但 byteshortint 类型的数据可以存入。

建议:容器的数据类型和存储的数据类型保持一致。

2. 数组的定义

格式一

1
数据类型[] 数组名;

例如:int[] array;

格式二

1
数据类型 数组名[];

例如:int array[];

详解

  • 数据类型:限定了数组以后能存什么类型的数据。
  • 方括号:表示现在定义的是一个数组。
  • 数组名:只是一个名字,方便以后使用。

注意点

  • 方法括号跟数组名,谁写在前面,谁写在后面都是一样的。
  • 平时习惯性使用第一种方式。

3. 数组的静态初始化

完整格式

1
数据类型[] 数组名 = new 数据类型[]{元素1, 元素2, 元素3, 元素4...};

例如:

1
2
int[] arr = new int[]{11, 22, 33};
double[] arr = new double[]{1.1, 1.2, 1.3};

格式详解

  • 数据类型:限定了数组以后能存什么类型的数据。
  • 方括号:表示现在定义的是一个数组。
  • 数组名:只是一个名字,方便以后使用。
  • new:给数组在内存中开辟一个空间。
  • 数据类型:限定了数组以后能存什么类型的数据,前后数据类型必须一致。
  • 方括号:表示现在定义的是一个数组。
  • 大括号:表示数组里面的元素,多个元素之间用逗号隔开。

注意点

  • 等号前后的数据类型必须保持一致。
  • 数组一旦创建之后,长度不能发生变化。

简化格式

1
数据类型[] 数组名 = {元素1, 元素2, 元素3, 元素4...};

例如:

1
2
int[] array = {1, 2, 3, 4, 5};
double[] array = {1.1, 1.2, 1.3};

练习

  1. 定义数组存储5个学生的年龄。

    1
    int[] agesArr = {18, 19, 20, 21, 22};
  2. 定义数组存储3个学生的姓名。

    1
    String[] namesArr = {"zhangsan", "lisi", "wangwu"};
  3. 定义数组存储4个学生的身高。

    1
    double[] heightsArr = {1.85, 1.82, 1.78, 1.65};

4. 地址值

打印数组时,实际输出的是数组的地址值。数组的地址值表示数组在内存中的位置。例如,[I@6d03e736 表示一个 int 类型的数组,6d03e736 是数组在内存中的地址值(十六进制)。

5. 数组元素访问

格式

1
数组名[索引];

作用

  • 获取数组中对应索引上的值。
  • 修改数组中对应索引上的值,修改后原来的值会被覆盖。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ArrDemo2 {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
// 获取 arr 数组中 3 索引上的值
int number = arr[3];
System.out.println(number);
System.out.println(arr[3]);

// 将 arr 数组中 3 索引上的值修改为 10
arr[3] = 10;
System.out.println("修改之后为:" + arr[3]);
}
}

6. 索引

索引也叫角标、下标,是数组容器中每一个小格子对应的编号。

索引的特点

  • 索引一定是从 0 开始的。
  • 连续不间断。
  • 逐个 +1 增长。

7. 数组的遍历

遍历是把数组里面所有的内容一个一个全部取出来。数组的长度通过 数组名.length 获取。

通用代码:

1
2
3
4
for (int i = 0; i < arr.length; i++) {
// 在循环的过程中,i 依次表示数组中的每一个索引
System.out.println(arr[i]); // 就可以把数组里面的每一个元素都获取出来,并打印在控制台上了
}

8. 数组的动态初始化

格式

1
数据类型[] 数组名 = new 数据类型[数组的长度];

举例

1
2
3
4
5
// 定义一个数组,存 3 个人的年龄,年龄未知
int[] agesArr = new int[3];

// 定义一个数组,存班级 10 名学生的考试成绩,考试成绩暂时未知,考完才知道
int[] scoresArr = new int[10];

数组的默认初始化值

  • 整数类型:0
  • 小数类型:0.0
  • 布尔类型:false
  • 字符类型:’\u0000’
  • 引用类型:null

9. 数组两种初始化方式的区别

  • 静态初始化int[] arr = {1, 2, 3, 4, 5};
    • 手动指定数组的元素,系统会根据元素的个数计算出数组的长度。
  • 动态初始化int[] arr = new int[3];
    • 手动指定数组长度,由系统给出默认初始化值。

使用场景

  • 只明确元素个数,但不明确具体的数据,推荐使用动态初始化。
  • 已经明确了要操作的所有数据,推荐使用静态初始化。

举例

  • 使用数组来存储键盘录入的 5 个整数。

    1
    int[] arr = new int[5];
  • 将全班的学生成绩存入数组中,已知学生成绩为:66, 77, 88, 99, 100

    1
    int[] arr = {66, 77, 88, 99, 100};

10. 数组常见问题

当访问了数组中不存在的索引时,会引发索引越界异常(ArrayIndexOutOfBoundsException)。

避免方法:

  • 针对任意一个数组,索引的范围:
    • 最小索引:0
    • 最大索引:数组的长度 - 1,即 数组名.length - 1

代码示例

1
2
3
4
5
6
7
8
public class ArrDemo6 {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5, 5, 5, 5, 5};
// 用索引来访问数组中的元素
System.out.println(arr[1]);
System.out.println(arr[10]); // ArrayIndexOutOfBoundsException
}
}

方法

1. 方法概述

1.1 方法的概念

方法(method)是程序中最小的执行单元。

  • 注意
    • 方法必须先创建才可以使用,该过程称为方法定义。
    • 方法创建后并不是直接可以运行的,需要手动使用后,才执行,该过程称为方法调用。

2. 方法的定义和调用

2.1 无参数方法定义和调用

  • 定义格式

    java复制

    1
    2
    3
    public static void 方法名 (   ) {
    // 方法体;
    }
  • 范例

    java复制

    1
    2
    3
    public static void method (    ) {
    // 方法体;
    }
  • 调用格式

    java复制

    1
    方法名();
  • 范例

    java复制

    1
    method();
  • 注意

    • 方法必须先定义,后调用,否则程序将报错。

2.3 无参数方法的练习

  • 需求:设计一个方法用于打印两个数中的较大数。

  • 思路

    1. 定义一个方法,用于打印两个数字中的较大数,例如 getMax()
    2. 方法中定义两个变量,用于保存两个数字。
    3. 使用分支语句分两种情况对两个数字的大小关系进行处理。
    4. main() 方法中调用定义好的方法。
  • 代码

    java复制

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class MethodTest {
    public static void main(String[] args) {
    // 在 main() 方法中调用定义好的方法
    getMax();
    }

    // 定义一个方法,用于打印两个数字中的较大数,例如 getMax()
    public static void getMax() {
    // 方法中定义两个变量,用于保存两个数字
    int a = 10;
    int b = 20;

    // 使用分支语句分两种情况对两个数字的大小关系进行处理
    if (a > b) {
    System.out.println(a);
    } else {
    System.out.println(b);
    }
    }
    }

3. 带参数方法定义和调用

3.1 带参数方法定义和调用

  • 定义格式

    参数:由数据类型和变量名组成 - 数据类型 变量名

    参数范例:int a

    java复制

    1
    2
    3
    4
    5
    6
    7
    public static void 方法名 (参数1) {
    方法体;
    }

    public static void 方法名 (参数1, 参数2, 参数3...) {
    方法体;
    }
  • 范例

    java复制

    1
    2
    3
    4
    5
    6
    7
    public static void isEvenNumber(int number) {
    ...
    }

    public static void getMax(int num1, int num2) {
    ...
    }
    • 注意
      • 方法定义时,参数中的数据类型与变量名都不能缺少,缺少任意一个程序将报错。
      • 方法定义时,多个参数之间使用逗号(,)分隔。
  • 调用格式

    java复制

    1
    2
    3
    方法名(参数);

    方法名(参数1,参数2);
  • 范例

    java复制

    1
    2
    3
    isEvenNumber(10);

    getMax(10, 20);
    • 注意
      • 方法调用时,参数的数量与类型必须与方法定义中的设置相匹配,否则程序将报错。

3.2 形参和实参

  1. 形参:方法定义中的参数。
    • 等同于变量定义格式,例如:int number
  2. 实参:方法调用中的参数。
    • 等同于使用变量或常量,例如:10 number

3.3 带参数方法练习

  • 需求:设计一个方法用于打印两个数中的较大数,数据来自于方法参数。

  • 思路

    1. 定义一个方法,用于打印两个数字中的较大数,例如 getMax()
    2. 为方法定义两个参数,用于接收两个数字。
    3. 使用分支语句分两种情况对两个数字的大小关系进行处理。
    4. main() 方法中调用定义好的方法(使用常量)。
    5. main() 方法中调用定义好的方法(使用变量)。
  • 代码

    java复制

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public class MethodTest {
    public static void main(String[] args) {
    // 在 main() 方法中调用定义好的方法(使用常量)
    getMax(10, 20);
    // 调用方法的时候,人家要几个,你就给几个,人家要什么类型的,你就给什么类型的
    // getMax(30);
    // getMax(10.0, 20.0);

    // 在 main() 方法中调用定义好的方法(使用变量)
    int a = 10;
    int b = 20;
    getMax(a, b);
    }

    // 定义一个方法,用于打印两个数字中的较大数,例如 getMax()
    // 为方法定义两个参数,用于接收两个数字
    public static void getMax(int a, int b) {
    // 使用分支语句分两种情况对两个数字的大小关系进行处理
    if (a > b) {
    System.out.println(a);
    } else {
    System.out.println(b);
    }
    }
    }

4. 带返回值方法的定义和调用

4.1 带返回值方法定义和调用

  • 定义格式

    java复制

    1
    2
    3
    public static 数据类型 方法名 ( 参数 ) { 
    return 数据 ;
    }
  • 范例

    java复制

    1
    2
    3
    4
    5
    6
    7
    public static boolean isEvenNumber( int number ) {           
    return true ;
    }

    public static int getMax( int a, int b ) {
    return 100 ;
    }
    • 注意
      • 方法定义时 return 后面的返回值与方法定义上的数据类型要匹配,否则程序将报错。
  • 调用格式

    java复制

    1
    2
    方法名 ( 参数 ) ;
    数据类型 变量名 = 方法名 ( 参数 ) ;
  • 范例

    java复制

    1
    2
    3
    isEvenNumber ( 5 ) ;

    boolean flag = isEvenNumber ( 5 );
    • 注意
      • 方法的返回值通常会使用变量接收,否则该返回值将无意义。

4.2 带返回值方法练习 1

  • 需求:设计一个方法可以获取两个数的较大值,数据来自于参数。

  • 思路

    1. 定义一个方法,用于获取两个数字中的较大数。
    2. 使用分支语句分两种情况对两个数字的大小关系进行处理。
    3. 根据题设分别设置两种情况下对应的返回结果。
    4. main() 方法中调用定义好的方法并使用变量保存。
    5. main() 方法中调用定义好的方法并直接打印结果。
  • 代码

    java复制

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class MethodTest {
    public static void main(String[] args) {
    // 在 main() 方法中调用定义好的方法并使用变量保存
    int result = getMax(10, 20);
    System.out.println(result);

    // 在 main() 方法中调用定义好的方法并直接打印结果
    System.out.println(getMax(10, 20));
    }

    // 定义一个方法,用于获取两个数字中的较大数
    public static int getMax(int a, int b) {
    // 使用分支语句分两种情况对两个数字的大小关系进行处理
    // 根据题设分别设置两种情况下对应的返回结果
    if (a > b) {
    return a;
    } else {
    return b;
    }
    }
    }

4.3 带返回值方法练习 2

  • 需求:定义一个方法,求一家商场每个季度的营业额。根据方法结果再计算出全年营业额。

  • 代码示例

    java复制

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    package com.itheima.demo;

    public class MethodDemo9 {
    public static void main(String[] args) {
    /*需求:定义一个方法,求一家商场每个季度的营业额。
    根据方法结果再计算出全年营业额。*/
    int sum1 = getSum(10, 20, 30);
    int sum2 = getSum(10, 20, 30);
    int sum3 = getSum(10, 20, 30);
    int sum4 = getSum(10, 20, 30);

    int sum = sum1 + sum2 + sum3 + sum4;
    System.out.println(sum);
    }

    // 心得:
    // 1.我要干嘛? 决定了方法体 每个季度的营业额
    // 2.我干这件事情,需要什么才能完成? 决定了形参 需要三个月的营业额 a b c
    // 3.我干完这件事情,看调用处是否需要使用方法的结果。 决定了返回值
    // 如果需要使用,那么必须返回
    // 如果不需要使用,可以返回也可以不返回
    public static int getSum(int month1, int month2, int month3) {
    int sum = month1 + month2 + month3;
    // 因为方法的调用处,需要继续使用这个结果
    // 所以我们必须要把sum返回
    return sum;
    }
    }

5. 方法的注意事项

5.1 方法的注意事项

  • 方法不能嵌套定义

    • 示例代码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      public class MethodDemo {
      public static void main(String[] args) {

      }

      public static void methodOne() {
      public static void methodTwo() {
      // 这里会引发编译错误!!!
      }
      }
      }
  • void 表示无返回值,可以省略 return,也可以单独的书写 return,后面不加数据

    • 示例代码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      public class MethodDemo {
      public static void main(String[] args) {

      }
      public static void methodTwo() {
      //return 100; 编译错误,因为没有具体返回值类型
      return;
      //System.out.println(100); return语句后面不能跟数据或代码
      }
      }

5.2 方法的通用格式

  • 格式

    1
    2
    3
    4
    public static 返回值类型 方法名(参数) {
    方法体;
    return 数据;
    }
  • 解释

    • public static:修饰符,目前先记住这个格式。
    • 返回值类型:方法操作完毕之后返回的数据的数据类型。
      • 如果方法操作完毕,没有数据返回,这里写 void,而且方法体中一般不写 return
    • 方法名:调用方法时候使用的标识。
    • 参数:由数据类型和变量名组成,多个参数之间用逗号隔开。
    • 方法体:完成功能的代码块。
    • return:如果方法操作完毕,有数据返回,用于把数据返回给调用者。
  • 定义方法时,要做到两个明确

    1. 明确返回值类型:主要是明确方法操作完毕之后是否有数据返回,如果没有,写 void;如果有,写对应的数据类型。
    2. 明确参数:主要是明确参数的类型和数量。
  • 调用方法时的注意

    • void 类型的方法,直接调用即可。
    • void 类型的方法,推荐用变量接收调用。

6. 方法重载

6.1 方法重载

  • 方法重载概念

    方法重载指同一个类中定义的多个方法之间的关系,满足下列条件的多个方法相互构成重载:

    1. 多个方法在同一个类中。
    2. 多个方法具有相同的方法名。
    3. 多个方法的参数不相同,类型不同或者数量不同。
  • 注意

    • 重载仅对应方法的定义,与方法的调用无关,调用方式参照标准格式。
    • 重载仅针对同一个类中方法的名称与参数进行识别,与返回值无关,换句话说不能通过返回值来判定两个方法是否相互构成重载。
  • 正确范例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class MethodDemo {
    public static void fn(int a) {
    // 方法体
    }
    public static int fn(double a) {
    // 方法体
    }
    }

    public class MethodDemo {
    public static float fn(int a) {
    // 方法体
    }
    public static int fn(int a, int b) {
    // 方法体
    }
    }
  • 错误范例

    ```java
    public class MethodDemo {
    public static void fn(int a) {
    // 方法体
    }
    public static int fn(int a) { /错误原因:重载与返回值无关/
    // 方法体
    }
    }

    public class MethodDemo01 {
    public static void fn(int a) {
    // 方法体
    }
    }
    public class MethodDemo02 {
    public static int fn(double a) { /错误原因:这是两个类的两个fn方法/
    // 方法体
    }
    }

面向对象

1.redis入门

1.1 Redis简介

Redis是一个基于内存的key-value结构数据库。Redis 是互联网技术领域使用最为广泛的存储中间件

主要特点:

  • 基于内存存储,读写性能高
  • 适合存储热点数据(热点商品、资讯、新闻)
  • 企业应用广泛

1.2 Redis安装

windows:解压即可用

linux:

在Linux系统安装Redis步骤:

  1. 将Redis安装包上传到Linux
  2. 解压安装包,命令:tar -zxvf redis-4.0.0.tar.gz -C /usr/local
  3. 安装Redis的依赖环境gcc,命令:yum install gcc-c++
  4. 进入/usr/local/redis-4.0.0,进行编译,命令:make
  5. 进入redis的src目录进行安装,命令:make install

安装后重点文件说明:

  • /usr/local/redis-4.0.0/src/redis-server:Redis服务启动脚本
  • /usr/local/redis-4.0.0/src/redis-cli:Redis客户端脚本
  • /usr/local/redis-4.0.0/redis.conf:Redis配置文件

1.3 Redis服务启动与停止

windows:

1
redis-server.exe redis.windows.conf

Redis服务默认端口号为 6379 ,通过快捷键Ctrl + C 即可停止Redis服务

当Redis服务启动成功后,可通过客户端进行连接。

1.3.2 客户端连接命令

通过redis-cli.exe命令默认连接的是本地的redis服务,并且使用默认6379端口。

连接参数:

  • -h ip地址
  • -p 端口号
  • -a 密码

1.3.3 修改Redis配置文件

设置Redis服务密码,修改redis.windows.conf

1
requirepass 123456

修改密码后需要重启Redis服务才能生效

1.3.4 Redis客户端图形工具

Another Redis Desktop Manager

2.五种常用数据类型介绍

  • 字符串(string):普通字符串,Redis中最简单的数据类型
  • 哈希(hash):也叫散列,类似于Java中的HashMap结构
  • 列表(list):按照插入顺序排序,可以有重复元素,类似于Java中的LinkedList
  • 集合(set):无序集合,没有重复元素,类似于Java中的HashSet
  • 有序集合(sorted set/zset):集合中每个元素关联一个分数(score),根据分数升序排序,没有重复元素

3.redis常见命令

3.1 字符串操作命令

Redis 中字符串类型常用命令:

  • SET key value 设置指定key的值
  • GET key 获取指定key的值
  • SETEX key seconds value 设置指定key的值,并将 key 的过期时间设为 seconds 秒
  • SETNX key value 只有在 key 不存在时设置 key 的值

3.2 哈希操作命令

Redis hash 是一个string类型的 field 和 value 的映射表,hash特别适合用于存储对象,常用命令:

  • HSET key field value 将哈希表 key 中的字段 field 的值设为 value
  • HGET key field 获取存储在哈希表中指定字段的值
  • HDEL key field 删除存储在哈希表中的指定字段
  • HKEYS key 获取哈希表中所有字段
  • HVALS key 获取哈希表中所有值

3.3 列表操作命令

Redis 列表是简单的字符串列表,按照插入顺序排序,常用命令:

  • LPUSH key value1 [value2] 将一个或多个值插入到列表头部
  • LRANGE key start stop 获取列表指定范围内的元素
  • RPOP key 移除并获取列表最后一个元素
  • LLEN key 获取列表长度
  • BRPOP key1 [key2 ] timeout 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超 时或发现可弹出元素为止

3.4 集合操作命令

Redis set 是string类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据,常用命令:

  • SADD key member1 [member2] 向集合添加一个或多个成员
  • SMEMBERS key 返回集合中的所有成员
  • SCARD key 获取集合的成员数
  • SINTER key1 [key2] 返回给定所有集合的交集
  • SUNION key1 [key2] 返回所有给定集合的并集
  • SREM key member1 [member2] 移除集合中一个或多个成员

3.5 有序集合操作命令

Redis有序集合是string类型元素的集合,且不允许有重复成员。每个元素都会关联一个double类型的分数。常用命令:

常用命令:

  • ZADD key score1 member1 [score2 member2] 向有序集合添加一个或多个成员
  • ZRANGE key start stop [WITHSCORES] 通过索引区间返回有序集合中指定区间内的成员
  • ZINCRBY key increment member 有序集合中对指定成员的分数加上增量 increment
  • ZREM key member [member …] 移除有序集合中的一个或多个成员

3.6 通用命令

Redis的通用命令是不分数据类型的,都可以使用的命令:

  • KEYS pattern 查找所有符合给定模式( pattern)的 key
  • EXISTS key 检查给定 key 是否存在
  • TYPE key 返回 key 所储存的值的类型
  • DEL key 该命令用于在 key 存在是删除 key

4.在Java中操作Redis

4.1 Redis的Java客户端

Redis 的 Java 客户端很多,常用的几种:

  • Jedis
  • Lettuce
  • Spring Data Redis(主要学习)

Spring 对 Redis 客户端进行了整合,提供了 Spring Data Redis,在Spring Boot项目中还提供了对应的Starter,即 spring-boot-starter-data-redis。

4.2 Spring Data Redis使用方式

4.2.1 介绍

Spring Data Redis 是 Spring 的一部分,提供了在 Spring 应用中通过简单的配置就可以访问 Redis 服务,对 Redis 底层开发包进行了高度封装。在 Spring 项目中,可以使用Spring Data Redis来简化 Redis 操作。

maven坐标:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

Spring Data Redis中提供了一个高度封装的类:RedisTemplate,对相关api进行了归类封装,将同一类型操作封装为operation接口,具体分类如下:

  • ValueOperations:string数据操作
  • SetOperations:set类型数据操作
  • ZSetOperations:zset类型数据操作
  • HashOperations:hash类型的数据操作
  • ListOperations:list类型的数据操作

4.2.2 环境搭建

1). 导入Spring Data Redis的maven坐标(已完成)

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2). 配置Redis数据源

在application-dev.yml中添加

1
2
3
4
5
6
sky:
redis:
host: localhost
port: 6379
password: 123456
database: 10

解释说明:

database:指定使用Redis的哪个数据库,Redis服务启动后默认有16个数据库,编号分别是从0到15。

可以通过修改Redis配置文件来指定数据库的数量。

在application.yml中添加读取application-dev.yml中的相关Redis配置

1
2
3
4
5
6
7
8
spring:
profiles:
active: dev
redis:
host: ${sky.redis.host}
port: ${sky.redis.port}
password: ${sky.redis.password}
database: ${sky.redis.database}

3). 编写配置类,创建RedisTemplate对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.sky.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@Slf4j
public class RedisConfiguration {

@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
log.info("开始创建redis模板对象...");
RedisTemplate redisTemplate = new RedisTemplate();
//设置redis的连接工厂对象
redisTemplate.setConnectionFactory(redisConnectionFactory);
//设置redis key的序列化器
redisTemplate.setKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
}

解释说明:

当前配置类不是必须的,因为 Spring Boot 框架会自动装配 RedisTemplate 对象,但是默认的key序列化器为

JdkSerializationRedisSerializer,导致我们存到Redis中后的数据和原始数据有差别,故设置为

StringRedisSerializer序列化器。

4). 通过RedisTemplate对象操作Redis

在test下新建测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.sky.test;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.*;

@SpringBootTest
public class SpringDataRedisTest {
@Autowired
private RedisTemplate redisTemplate;

@Test
public void testRedisTemplate(){
System.out.println(redisTemplate);
//string数据操作
ValueOperations valueOperations = redisTemplate.opsForValue();
//hash类型的数据操作
HashOperations hashOperations = redisTemplate.opsForHash();
//list类型的数据操作
ListOperations listOperations = redisTemplate.opsForList();
//set类型数据操作
SetOperations setOperations = redisTemplate.opsForSet();
//zset类型数据操作
ZSetOperations zSetOperations = redisTemplate.opsForZSet();
}
}

4.2.3 操作常见类型数据

1). 操作字符串类型数据

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 操作字符串类型的数据
*/
@Test
public void testString(){
// set get setex setnx
redisTemplate.opsForValue().set("name","小明");
String city = (String) redisTemplate.opsForValue().get("name");
System.out.println(city);
redisTemplate.opsForValue().set("code","1234",3, TimeUnit.MINUTES);
redisTemplate.opsForValue().setIfAbsent("lock","1");
redisTemplate.opsForValue().setIfAbsent("lock","2");
}

2). 操作哈希类型数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 操作哈希类型的数据
*/
@Test
public void testHash(){
//hset hget hdel hkeys hvals
HashOperations hashOperations = redisTemplate.opsForHash();

hashOperations.put("100","name","tom");
hashOperations.put("100","age","20");

String name = (String) hashOperations.get("100", "name");
System.out.println(name);

Set keys = hashOperations.keys("100");
System.out.println(keys);

List values = hashOperations.values("100");
System.out.println(values);

hashOperations.delete("100","age");
}

3). 操作列表类型数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 操作列表类型的数据
*/
@Test
public void testList(){
//lpush lrange rpop llen
ListOperations listOperations = redisTemplate.opsForList();

listOperations.leftPushAll("mylist","a","b","c");
listOperations.leftPush("mylist","d");

List mylist = listOperations.range("mylist", 0, -1);
System.out.println(mylist);

listOperations.rightPop("mylist");

Long size = listOperations.size("mylist");
System.out.println(size);
}

4). 操作集合类型数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 操作集合类型的数据
*/
@Test
public void testSet(){
//sadd smembers scard sinter sunion srem
SetOperations setOperations = redisTemplate.opsForSet();

setOperations.add("set1","a","b","c","d");
setOperations.add("set2","a","b","x","y");

Set members = setOperations.members("set1");
System.out.println(members);

Long size = setOperations.size("set1");
System.out.println(size);

Set intersect = setOperations.intersect("set1", "set2");
System.out.println(intersect);

Set union = setOperations.union("set1", "set2");
System.out.println(union);

setOperations.remove("set1","a","b");
}

5). 操作有序集合类型数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 操作有序集合类型的数据
*/
@Test
public void testZset(){
//zadd zrange zincrby zrem
ZSetOperations zSetOperations = redisTemplate.opsForZSet();

zSetOperations.add("zset1","a",10);
zSetOperations.add("zset1","b",12);
zSetOperations.add("zset1","c",9);

Set zset1 = zSetOperations.range("zset1", 0, -1);
System.out.println(zset1);

zSetOperations.incrementScore("zset1","c",10);

zSetOperations.remove("zset1","a","b");
}

6). 通用命令操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 通用命令操作
*/
@Test
public void testCommon(){
//keys exists type del
Set keys = redisTemplate.keys("*");
System.out.println(keys);

Boolean name = redisTemplate.hasKey("name");
Boolean set1 = redisTemplate.hasKey("set1");

for (Object key : keys) {
DataType type = redisTemplate.type(key);
System.out.println(type.name());
}

redisTemplate.delete("mylist");
}

@configuration

用于标记一个类为配置类,表明该类将包含一个或多个 @Bean 方法,用于定义 Spring 容器中管理的 bean。

@Configuration 表示该类是 Spring 配置的一部分,通常代替传统的 XML 配置文件。使用该注解的类会被 Spring 容器处理,并允许在该类中定义和管理多个 bean。

主要作用:
  1. 标记配置类@Configuration 用于标识一个类是配置类,这个类中的方法通常会使用 @Bean 注解来定义 Spring 容器中需要管理的 bean。
  2. 替代 XML 配置:它允许你通过 Java 代码配置 Spring 应用上下文,避免了传统 XML 配置的繁琐。
  3. 增强功能@Configuration 类默认会被 CGLIB 动态代理增强,使得在配置类中定义的 bean 能够进行更强的管理,例如支持方法级的 bean 定义。

@RequestBody 是 Spring Framework 中的一种注解,通常用于处理 HTTP 请求的正文(body)数据。它的主要作用是将请求中的数据自动绑定到 Java 对象上。

@RequestBody

用法:

@RequestBody 注解用于方法参数上,表示从 HTTP 请求的 Body 部分提取数据并将其转换为 Java 对象。Spring 会根据请求的 Content-Type 和方法参数的类型,自动将请求内容转换为相应的对象。

示例:

假设我们有一个 User 类:

1
2
3
4
5
6
javaCopy Codepublic class User {
private String name;
private int age;

// Getters and Setters
}

然后在控制器方法中使用 @RequestBody 注解来处理传递的 JSON 数据:

1
2
3
4
5
6
7
8
javaCopy Code@RestController
public class UserController {

@PostMapping("/user")
public String createUser(@RequestBody User user) {
return "User received: " + user.getName() + ", Age: " + user.getAge();
}
}
解释:
  • 当客户端发送一个 HTTP POST 请求到 /user 路径,且请求的 Body 中包含 JSON 数据时,Spring 会自动将该 JSON 数据转换为 User 对象。

  • 例如,客户端发送的请求体如下:

    1
    2
    3
    4
    jsonCopy Code{
    "name": "John",
    "age": 30
    }

    Spring 会将该 JSON 数据转换为User对象,调用createUser方法时,user

    参数将包含name和age的值。

常见用途:
  1. JSON 数据处理@RequestBody 是处理 JSON 请求的主要方式,Spring 会使用 HttpMessageConverter(如 Jackson)来自动将 JSON 数据转换为 Java 对象。
  2. 处理复杂请求:对于包含复杂嵌套结构的请求体(例如嵌套对象、数组等),@RequestBody 可以非常方便地将数据映射为相应的 Java 类。
需要注意的事项:
  • 由于使用 @RequestBody 会自动解析请求体,因此它只能用于 POSTPUTPATCH 等请求类型。
  • 在使用 @RequestBody 时,通常需要客户端设置正确的 Content-Type,如 application/json

@Autowired

@Autowired 是 Spring Framework 中的一种注解,主要用于实现依赖注入(Dependency Injection)。它可以让 Spring 自动为你的类的字段、构造方法或 setter 方法注入所需的依赖对象。

用法和作用:

  1. 自动注入依赖@Autowired 注解使得 Spring 可以自动将符合要求的 Bean 注入到你的类中,而无需手动配置。Spring 会根据类型、名称或者构造函数来寻找匹配的 Bean 并进行注入。

主要的使用方式:

1. 字段注入

通过 @Autowired 注解在类的成员变量上,Spring 会自动为这个字段赋值。最常见的使用方式。

1
2
3
4
5
6
7
8
9
javaCopy Code@Component
public class Car {
@Autowired
private Engine engine; // 自动注入 Engine 类型的 Bean

public void start() {
engine.run();
}
}

在上面的例子中,Car 类依赖 Engine 类,通过 @Autowired 注解,Spring 会自动注入一个 Engine 类型的 Bean 到 Carengine 字段。

2. 构造方法注入

@Autowired 也可以应用到构造方法上,Spring 会根据构造方法的参数类型来自动注入依赖的 Bean。

1
2
3
4
5
6
7
8
9
10
11
12
13
javaCopy Code@Component
public class Car {
private final Engine engine;

@Autowired
public Car(Engine engine) { // 自动注入 Engine 类型的 Bean
this.engine = engine;
}

public void start() {
engine.run();
}
}

这种方式是推荐的做法,特别是当依赖项为必需时,可以更好地控制依赖关系。

3. Setter 方法注入

你也可以将 @Autowired 放在 setter 方法上,Spring 会在创建对象后通过调用 setter 方法注入依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
javaCopy Code@Component
public class Car {
private Engine engine;

@Autowired
public void setEngine(Engine engine) { // 自动注入 Engine 类型的 Bean
this.engine = engine;
}

public void start() {
engine.run();
}
}

这也是一种常见的依赖注入方式。

自动注入的原理:

  • Spring 会根据 @Autowired 注解标记的字段、构造函数或 setter 方法,查找匹配的 Bean 实例并注入。默认情况下,它是根据 类型 来进行匹配。
  • 如果找到多个匹配的 Bean,且没有明确的标识,Spring 会抛出异常。在这种情况下,你可以使用 @Qualifier 来指定具体的 Bean。

例外和配置:

  • 如果没有找到匹配的 Bean:如果没有符合要求的 Bean,Spring 会抛出异常,默认情况下是 NoSuchBeanDefinitionException。如果你希望在没有匹配 Bean 时不报错,可以使用 @Autowired(required = false) 来设置为非必需的。

    1
    2
    javaCopy Code@Autowired(required = false)
    private Engine engine; // 如果没有 Engine 类型的 Bean,Spring 不会抛出异常
  • 多重 Bean 注入冲突:如果 Spring 容器中存在多个同类型的 Bean,Spring 默认会抛出 NoUniqueBeanDefinitionException。这时可以通过 @Qualifier 来明确指定要注入哪个 Bean:

    1
    2
    3
    javaCopy Code@Autowired
    @Qualifier("carEngine") // 明确指定注入哪个 Bean
    private Engine engine;

总结:

  • @Autowired 是 Spring 中用于实现自动依赖注入的注解。它可以用于字段、构造函数或 setter 方法上。
  • Spring 会根据 Bean 类型自动查找并注入依赖项。
  • 使用 @Autowired 可以大大减少手动配置和管理 Bean 之间的依赖关系,提高代码的可维护性和可扩展性。

1. 组件相关注解

  • @Controller:用于修饰 MVC 中控制器层的组件。Spring Boot 中的组件扫描功能会识别此注解,并为被修饰的类实例化一个对象。它通常与@RequestMapping 一起使用。当 Spring MVC 收到请求时,会将其转发到指定路径的方法进行处理。
1
2
3
4
@Controller
@RequestMapping("/user/admin")
public class UserAdminController {
}
  • @Service:通常用于修饰服务层的组件。声明一个对象时,会实例化该类对象并将其注入到 bean 容器中。
1
2
3
4
@Service
public class UserService {
//...
}
  • @Repository:用于修饰数据访问对象(DAO)层的组件。DAO 层的组件专注于系统数据的处理,例如数据库中的数据。它们也会被组件扫描并生成实例化对象。
1
2
3
4
@Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
//...
}
  • @Component:一般指代组件。当组件难以分类时,可以使用此注解进行标记。其功能与@Service 类似。
1
2
3
4
@Component
public class DemoHandler {
//...
}

2. 与 Bean 实例和生命周期相关的注解

  • @Bean:用于修饰方法,表示该方法将创建一个 Bean 实例,并由 Spring 容器进行管理。示例代码如下:
1
2
3
4
5
6
7
8
@Configuration
public class AppConfig {
// 相当于在 XML 中配置一个 Bean
@Bean
public Uploader initFileUploader() {
return new FileUploader();
}
}
  • @Scope:用于声明 Spring Bean 实例的作用域。作用域如下:
    • singleton:单例模式。在 Spring 容器中实例是唯一的,这是 Spring 的默认实例作用域类型。
    • prototype:原型模式。每次使用时都会重新创建实例。
    • request:在同一请求中使用相同的实例,不同请求创建新的实例。
    • session:在同一会话中使用相同的实例,不同会话创建新的实例。
1
2
3
4
5
6
7
8
@Configuration
public class RestTemplateConfig {
@Bean
@Scope("singleton")
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
  • @Primary:当存在同一对象的多个实例时,优先选择此实例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
@ComponentScan
public class JavaConfig {
// 首选
@Bean("b1")
@Primary
B b1() {
return new B();
}

@Bean("b2")
B b2() {
return new B();
}
}
  • @PostConstruct:用于修饰方法,在对象实例创建和依赖注入完成后执行,可用于初始化对象实例。
  • @PreDestroy:用于修饰方法,在对象实例即将被 Spring 容器移除时执行,可用于释放对象实例持有的资源。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Demo {
public Demo() {
System.out.println("构造方法...");
}

public void init() {
System.out.println("init...");
}
}


@PostConstruct
public void postConstruct() {
System.out.println("postConstruct...");
}

@PreDestroy
public void preDestroy() {
System.out.println("preDestroy...");
}

public void destroy() {
System.out.println("destroy...");
}

输出:

1
2
3
4
5
构造方法...
postConstruct...
init...
preDestroy...
destroy...

3. 依赖注入注解

  • @Autowired:根据对象的类型自动注入依赖对象。默认情况下,它要求注入的对象实例必须存在。可以配置 required = false 来注入可能不存在的对象。
1
2
3
4
5
6
7
8
9
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;

@Autowired(required = false)
private UserConfig userConfig;
}
  • @Resource:默认情况下,根据对象的名称自动注入依赖对象。如果要根据类型注入,可以设置属性 type = UmsAdminService.class。
1
2
3
4
5
6
@Controller
@RequestMapping("/user")
public class UserController {
@Resource(name = "userServiceImpl")
private UserService userService;
}
  • @Qualifier:当存在同一类型的多个 bean 时,使用@Autowired 导入会导致错误,表示当前对象不唯一,Spring 不知道要导入哪个依赖。此时,我们可以使用@Qualifier 进行更细粒度的控制并选择其中一个实例。它通常与@Autowired 一起使用。示例如下:
1
2
3
@Autowired
@Qualifier("deptService")
private DeptService deptService;

4. SpringMVC 相关注解

  • @RequestMapping:提供路由信息,负责将 URL 映射到 Controller 中的指定函数。当用于方法上时,可以指定请求协议,如 GET、POST、PUT、DELETE 等。
  • @RequestBody:表示请求体的 Content - Type 必须是 application/json 格式的数据。接收到数据后,会自动将数据绑定到 Java 对象。
  • @ResponseBody:表示此方法的返回结果直接写入 HTTP 响应体。返回数据的格式为 application/json。 例如,如果请求参数是 json 格式,返回参数也是 json 格式,示例代码如下:
1
2
3
4
5
6
7
8
9
10
@Controller
@RequestMapping("api")
public class LoginController {
@RequestMapping(value = "login", method = RequestMethod.POST)
@ResponseBody
public ResponseEntity login(@RequestBody UserLoginDTO request) {
//...
return new ResponseEntity(HttpStatus.OK);
}
}
  • @RestController:与@Controller 类似,用于注释控制器层组件。不同之处在于它是@ResponseBody 和@Controller 的组合。 即,当在类上使用@RestController 时,表示当前类中所有对外暴露的接口方法,返回数据的格式都是 application/json。示例代码如下:
1
2
3
4
5
6
7
8
9
@RestController
@RequestMapping("/api")
public class LoginController {
@RequestMapping(value = "/login", method = RequestMethod.POST)
public ResponseEntity login(@RequestBody UserLoginDTO request) {
//...
return new ResponseEntity(HttpStatus.OK);
}
}
  • @RequestParam:用于接收请求参数为表单类型的数据。通常用于方法的参数前面。示例代码如下:
1
2
3
4
5
6
7
8
@RequestMapping(value = "login", method = RequestMethod.POST)
@ResponseBody
public ResponseEntity login(
@RequestParam(value = "userName", required = true) String userName,
@RequestParam(value = "userPwd", required = true) String userPwd) {
//...
return new ResponseEntity(HttpStatus.OK);
}
  • @PathVariable:用于获取请求路径中的参数。通常用于 restful 风格的 API。示例代码如下:
1
2
3
4
5
6
@RequestMapping(value = "queryProduct/{id}", method = RequestMethod.POST)
@ResponseBody
public ResponseEntity queryProduct(@PathVariable("id") String id) {
//...
return new ResponseEntity(HttpStatus.OK);
}
  • @GetMapping、@PostMapping、@PutMapping、@DeleteMapping:除了@RequestMapping 能够指定请求方法外,还有一些其他注解可以用于注释接口路径请求。例如,当@GetMapping 用于方法上时,表示仅支持 get 请求方法。它等同于@RequestMapping(value = “/get”, method = RequestMethod.GET)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@GetMapping("get")
public ResponseEntity get() {
return new ResponseEntity(HttpStatus.OK);
}

@PostMapping("post")
public ResponseEntity post() {
return new ResponseEntity(HttpStatus.OK);
}

@PutMapping("put")
public ResponseEntity put() {
return new ResponseEntity(HttpStatus.OK);
}

@DeleteMapping("delete")
public ResponseEntity delete() {
return new ResponseEntity(HttpStatus.OK);
}

5. 配置相关注解

  • @Configuration:表示声明一个基于 Java 的配置类。Spring Boot 提倡基于 Java 对象的配置,相当于以前在 xml 中配置 bean。例如,声明一个配置类 AppConfig,然后初始化一个 Uploader 对象。
1
2
3
4
5
6
7
@Configuration
public class AppConfig {
@Bean
public Uploader initOSSUploader() {
return new OSSUploader();
}
}
  • @EnableAutoConfiguration:@EnableAutoConfiguration 可以帮助 Spring Boot 应用程序将所有符合条件的@Configuration 配置类加载到当前的 Spring Boot 中,创建与配置类对应的 Beans,并将 Bean 实体交给 IoC 容器管理。在某些场景下,如果我们想要避免某些配置类的扫描(包括避免一些第三方 jar 下的配置),可以这样处理。
1
2
3
4
5
6
7
@Configuration
@EnableAutoConfiguration(exclude = {
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class
})
public class AppConfig {
//...
}
  • @ComponentScan:注释哪些路径下的类需要被 Spring 扫描。用于自动发现和组装一些 Bean 对象。默认配置是扫描当前文件夹及子目录中的所有类。如果我们想要指定扫描某些包路径,可以这样处理。
1
@ComponentScan(basePackages = {"com.xxx.a", "com.xxx.b", "com.xxx.c"})
  • @SpringBootApplication:相当于使用了@Configuration、@EnableAutoConfiguration 和@ComponentScan 这三个注解。通常用于全局启动类。示例如下:
1
2
3
4
5
6
@SpringBootApplication
public class PropertyApplication {
public static void main(String[] args) {
SpringApplication.run(PropertyApplication.class, args);
}
}

用这三个注解@configuration、@EnableAutoConfiguration 和@ComponentScan 替换@springBootApplication 也可以成功启动,@springBootApplication 只是简化了这三个注解。

  • @EnableTransactionManagement:表示启用事务支持,相当于 xml 配置方式中的 tx:annotation - driven/>。
1
2
3
4
5
6
7
@SpringBootApplication
@EnableTransactionManagement
public class PropertyApplication {
public static void main(String[] args) {
SpringApplication.run(PropertyApplication.class, args);
}
}
  • @ConfigurationProperties:用于批量注入外部配置,并以对象的形式导入具有指定前缀的配置。例如,这里我们在 application.yml 中指定前缀为 secure.ignored 的属性:
1
2
3
4
5
6
7
8
9
10
11
secure:
ignored:
urls: # 安全路径白名单
- /swagger-ui/
- /swagger-resources/**
- / **/*.htm1
- / **/*.js
- / **/*.css
- / **/*.png
- /favicon.ico
- /actuator/**

然后,在 Java 类中定义一个 urls 属性,就可以导入配置文件中的属性。

1
2
3
4
5
6
7
@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "secure.ignored")
public class IgnoreUrlsConfig {
private List<String> urls = new ArrayList<>();
}
  • @Conditional:从 Spring 4 开始,@Conditional 注解可以用于有条件地加载 bean 对象。目前,在 Spring Boot 源代码中,@Condition 注解已经得到了广泛的扩展,用于实现智能自动配置,以满足各种使用场景。以下是一些常用的注解:
    • @ConditionalOnBean:当指定的 Bean 存在时,配置生效。
    • @ConditionalOnMissingBean:当指定的 Bean 不存在时,配置生效。
    • @ConditionalOnClass:当指定的类在类路径中存在时,配置生效。@ConditionalOnMissingClass:当指定的类在类路径中不存在时,配置生效。
    • @ConditionalOnExpression:当给定的 SpEL 表达式的计算结果为 true 时,配置生效。
    • @ConditionalOnProperty:当指定的配置属性具有确定的值且匹配时,配置生效。 具体应用案例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
@Configuration
public class ConditionalConfig {
/**
* 当 Test 对象存在时,创建一个对象 A
*
* @return
*/
@ConditionalOnBean(Test.class)
@Bean
public A createA() {
return new A();
}

/**
* 当 Test 对象不存在时,创建一个对象 B
*
* @return
*/
@Conditional0nMissingBean(Test.class)
@Bean
public B createB() {
return new B();
}

/**
* 当 Test 类存在时,创建一个对象 C
*
* @return
*/
@Conditional0nclass(Test.class)
@Bean
public C createC() {
return new C();
}

/**
* 当 Test 类不存在时,创建一个对象 D
*
* @return
*/
@Conditional0nMissingClass(Test.class)
@Bean
public D createD() {
return new D();
}

/**
* 当 enableConfig 的配置为 true 时,创建一个对象 E
*
* @return
*/
@Conditiona10nExpression("$ {enableConfig:false}")
@Bean
public E createE() {
return new E();
}

/**
* 当 filter.loginFilter 的配置为 true 时,创建一个对象 F
*
* @return
*/
@Conditiona10nProperty(prefix = "filter", name = "loginilter", havingalue =
"true")
@Bean
public F createF() {
return new F();
}
}
  • @Value:在任何 Spring 管理的 Bean 中,可以通过此注解获取从任何源配置的属性值。例如,在 application.properties 文件中,定义一个参数变量:
1
config.name=Dylan

在任何 bean 实例内,可以通过@Value 注解注入参数并获取参数变量的值。

1
2
3
4
5
6
7
8
9
10
@RestController
public class HelloController {
@Value("${config.name}")
private String configName;

@GetMapping("config")
public String config() {
return JSON.toJSONString(configName);
}
}
  • @ConfigurationProperties:在每个类中使用@Value 获取属性配置值的做法实际上并不推荐。 在一般的企业项目开发中,不会使用这种混乱的写法,而且维护也很麻烦。通常,一次读取一个 Java 配置类,然后在需要的地方直接引用这个类进行使用,这样可以多次访问且便于维护。示例如下: 首先,在 application.properties 文件中定义参数变量。
1
config.name=demo_1 config.value=demo_value_1

然后,创建一个 Java 配置类并注入参数变量。

1
2
3
4
5
6
7
8
@Component
@ConfigurationProperties(prefix = "config")
public class Config {
public String name;
public String value;

//... get、set 方法
}

最后,在需要的地方,通过 ioc 注入 Config 对象。

  • @PropertySource:此注解用于读取我们自定义的配置文件。例如,要导入两个配置文件 test.properties 和 bussiness.properties,用法如下:
1
2
3
4
5
6
7
@SpringBootApplication
@PropertySource(value = {"test.properties", "bussiness.properties"})
public class PropertyApplication {
public static void main(String[] args) {
SpringApplication.run(PropertyApplication.class, args);
}
}
  • @ImportResource:用于加载 xml 配置文件。例如,要导入自定义的 aaa.xml 文件,用法如下:
1
2
3
4
5
6
7
@ImportResource(locations = "classpath:aaa.xml")
@SpringBootApplication
public class PropertyApplication {
public static void main(String[] args) {
SpringApplication.run(PropertyApplication.class, args);
}
}

6. JPA 相关注解

  • @Entity 和@Table:表示这是一个实体类。这两个注解通常一起使用。但是,如果表名与实体类名相同,@Table 可以省略。
  • @Id:表示此属性字段对应数据库表中的主键字段。
  • @Column:表示此属性字段对应的数据库表中的列名。如果字段名与列名相同,可以省略。
  • @GeneratedValue:表示主键的生成策略。有以下四个选项:
    • AUTO:表示由程序控制,是默认选项。如果未设置,则为该选项。
    • IDENTITY:表示由数据库生成,使用数据库自动递增。Oracle 不支持此方法。
    • SEQUENCE:表示主键 ID 通过数据库序列生成。MySQL 不支持此方法。
    • TABLE:表示主键由指定数据库生成。此方法有利于数据库迁移。
  • @SequenceGenerator:用于定义生成主键的序列。需要与@GeneratedValue 一起使用才能生效。以 role 表为例,相应的注解配置如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Entity
@Table(name = "role")
@SequenceGenerator(name = "id_seq", sequenceName = "seq_repair", allocationSize = 1)
public class Role implements Serializable {
private static final long serialVersionUID = 1L;

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "id_seq")
private Long id;

@Column(nullable = false)
private String roleName;

@Column(nullable = false)
private String roleType;
}
  • @Transient:表示此属性不映射到数据库表的字段。ORM 框架将忽略此属性。
1
2
3
@Column(nullable = false)
@Transient
private String lastTime;
  • **@Basic(fetch = FetchType.LAZY)**:用于某些属性上,可以实现懒加载的效果。即,当使用此字段时,才会加载此属性。如果配置为 fetch = FetchType.EAGER,则表示立即加载,这也是默认的加载方式!
1
2
3
@Column(nullable = false)
@Basic(fetch = FetchType.LAZY)
private String roleType;
  • @JoinColumn:用于注释表示表关系的字段。通常与@OneToOne 和@OneToMany 一起使用。例如:
1
2
3
4
5
6
7
8
9
@Entity
@Table(name = "tb_login_log")
public class LoginLog implements Serializable {
@OneToOne
@JoinColumn(name = "user_id")
private User user;

//... get、set
}
  • @OneToOne、@OneToMany 和@ManyToOne:这三个注解相当于 hibernate 配置文件中的一对一、一对多和多对一配置。例如,在以下客户地址表中,可以通过客户 ID 查询客户信息。
1
2
3
4
5
6
7
8
9
@Entity
@Table(name = "address")
public class AddressEO implements java.io.Serializable {
@ManyToOne(cascade = {CascadeType.ALL})
@JoinColumn(name = "customer_id")
private CustomerEO customer;

//... get、set
}

7. 异常处理相关注解

  • @ControllerAdvice 和@ExceptionHandler:它们通常一起使用来处理全局异常。示例代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Slf4j
@Configuration
@ControllerAdvice
public class GlobalExceptionConfig {
private static final Integer GLOBAL_ERROR_CODE = 500;

@ExceptionHandler(value = Exception.class)
@ResponseBody
public void exceptionHandler(HttpServletRequest request, HttpServletResponse
response, Exception e) throws Exception {
log.error("统一异常处理器:", e);
ResultMsg<Object> resultMsg = new ResultMsg<>();
resultMsg.setCode(GLOBAL_ERROR_CODE);
if (e instanceof CommonException) {
CommonException ex = (CommonException) e;
if (ex.getErrCode()!= 0) {
resultMsg.setCode(ex.getErrCode());
}
resultMsg.setMsg(ex.getErrMsg());
} else {
resultMsg.setMsg(CommonErrorMsg.SYSTEM_ERROR.getMessage());
}
WebUtil.buildPrintWriter(response, resultMsg);
}
}

8. AOP 相关注解

  • @Aspect:用于定义一个切面。切面是通知和切入点的组合,它定义了在何时何地应用通知功能。
  • @Before:表示前置通知。通知方法将在目标方法调用之前执行。通知描述了切面要执行的工作以及执行的时间。
  • @After:表示后置通知。通知方法将在目标方法返回或抛出异常后执行。
  • @AfterReturning:表示返回通知。通知方法将在目标方法返回后执行。
  • @AfterThrowing:表示异常通知。通知方法将在目标方法抛出异常后执行。
  • @Around:表示环绕通知。通知方法将包装目标方法,并在目标方法调用前后执行自定义行为。
  • @Pointcut:定义切入点表达式,它定义了应用通知功能的范围。
  • @Order:用于定义组件的执行顺序。在 AOP 中,它指的是切面的执行顺序。value 属性的值越小,表示优先级越高。 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 统一日志处理切面
*/
@Aspect
@Component
@Order(1)
public class WebLogAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(WebLogAspect.class);

@Pointcut("execution(public * com.dylan.smith.web.controller.*.*(..))")
public void webLog() {
}

@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
}

@AfterReturning(value = "webLog()", returning = "ret")
public void doAfterReturning(Object ret) throws Throwable {
}

@Around("webLog()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
WebLog webLog = new WebLog();
//...
Object result = joinPoint.proceed();
LOGGER.info("{}", JSONUtil.parse(webLog));
return result;
}
}

9. 测试相关注解

  • @Test:指定一个方法为测试方法。
  • @ActiveProfiles:一般应用于测试类,用于声明活动的 Spring 配置文件。例如,指定 application - dev.properties 配置文件。
  • @RunWith 和@SpringBootTest:一般应用于测试类,用于单元测试。示例如下:
1
2
3
4
5
6
7
8
9
@ActiveProfiles("dev")
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestJunit {
@Test
public void executeTask() {
//...
}
}

nginx 负载均衡策略:

名称 说明
轮询 默认方式
weight 权重方式,默认为1,权重越高,被分配的客户端请求就越多
ip_hash 依据ip分配方式,这样每个访客可以固定访问一个后端服务
least_conn 依据最少连接方式,把请求优先分配给连接数少的后端服务
url_hash 依据url分配方式,这样相同的url会被分配到同一个后端服务
fair 依据响应时间方式,响应时间短的服务将会被优先分配

具体配置方式:

轮询:

1
2
3
4
upstream webservers{
server 192.168.100.128:8080;
server 192.168.100.129:8080;
}

weight:

1
2
3
4
upstream webservers{
server 192.168.100.128:8080 weight=90;
server 192.168.100.129:8080 weight=10;
}

ip_hash:

1
2
3
4
5
upstream webservers{
ip_hash;
server 192.168.100.128:8080;
server 192.168.100.129:8080;
}

least_conn:

1
2
3
4
5
upstream webservers{
least_conn;
server 192.168.100.128:8080;
server 192.168.100.129:8080;
}

url_hash:

1
2
3
4
5
upstream webservers{
hash &request_uri;
server 192.168.100.128:8080;
server 192.168.100.129:8080;
}

fair:

1
2
3
4
5
upstream webservers{
server 192.168.100.128:8080;
server 192.168.100.129:8080;
fair;
}

护网

基础篇

讲一下TOP10有哪些?

1.失效的访问控制

2.加密机制失效

3.注入(包括跨站脚本攻击XSS和SQL注入等)

4.不安全设计

5.安全配置错误

6.自带缺陷和过时的组件

7.身份识别和身份验证错误

8.软件和数据完整性故障

9.安全日志和监控故障

10.服务端请求伪造SSRF

常见的端口和相关的服务?
服务 端口号 说明
FTP 20 FTP服务器真正传输所用的端口,用于上传、下载
FTP 21 用于FTP的登陆认证
SSH、SFTP 22 加密的远程登录,文件传输
Telnet 23 远程登录(在本地主机上使用此端口与远程服务器的22/3389端口连接)
SMTP 25 用于发送邮件
DNS 53 域名解析
HTTP 80 用于网页浏览
POP3 110 SUN公司的RPC服务所有端口
Network News Transfer Protocol 119 NEWS新闻组传输协议,承载USENET通信
SMTP 161 Simple Network Management Protocol,简单网络管理协议
SNMP Trap 160、162 SNMP陷阱
HTTPS 443 加密的网页浏览端口
CIFS 445 公共Internet文件系统
sql server 1433 Microsoft的SQL服务开放的端口 数据库
Oracle 1521 数据库
NFS 2049 通过网络,让不同的机器、不同的操作系统实现文件共享
MySQL 3306 数据库
WIN2003远程登录 3389 Windows 2000(2003) Server远程桌面的服务端口,本地服务器开放此端口,去连接到远程的服务器
QQ· 4000 腾讯QQ客户端开放此端口
redis· 6379 数据库
WebLogic 7001 一个基于JAVAEE架构的中间件,WebLogic是用于开发、集成、部署和管理大型分布式Web应用、网络应用和数据库应用的Java应用服务器
Wingate 8010 Wingate代理开放此端口
TOMCAT 8080 WWW代理开放此端口
如何判断目标操作系统?

大小写检测:windows大小写不敏感,而linux大小写敏感。

PING指令:根据TTL值,winodws一般情况下>100,linux<100

正向代理和反向代理的区别?

正向代理即是客户端代理, 代理客户端, 服务端不知道实际发起请求的客户端

反向代理即是服务端代理, 代理服务端, 客户端不知道实际提供服务的服务端

正向shell和反向shell的区别?

正向Shell:攻击者连接被攻击者机器,可用于攻击者处于内网,被攻击者处于公网的情况。

反向Shell:被攻击者主动连接攻击者,可用于攻击者处于外网,被攻击者处于内网的情况。

序列化和反序列化的概念?

序列化:把对象转化为可传输的字节序列过程称为序列化。

反序列化:把字节序列还原为对象的过程称为反序列化。

信息收集具体收集什么信息
1.服务器的相关信息

真实IP,服务器操作系统及其版本,数据库类型及其版本,开放端口及其对应服务(常用端口扫描工具Nmap,Zenmap,Masscan等),WAF探测

2.网站相关信息

CMS(内容管理系统),web容器,web框架,CDN,web指纹识别(常用指纹识别工具潮汐指纹,云悉指纹,WhatWeb等),旁站和C段信息

3.域名相关信息

子域名,whois信息(查询域名的IP以及所有者等信息的传输协议),公司名称,注册人或者机构信息、公司或个人联系方式(邮箱,手机号码等信息),备案号

4.具体站点信息

漏洞扫描(常用漏扫工具Nessus,AppScan,AWVS,X-ray等),目录爆破(常用目录爆破工具dirsearch,dirb,dirbuster,ffuf等),robots.txt,

怎么验证是否存在CDN?

使用多地ping的服务,查看对应IP地址是否唯一,如果不唯一大概率就是存在CDN

使用nslookup命令检测,查看返回域名解析对应IP地址是否唯一,如果不唯一很可能存在CDN

怎么绕过CDN寻找真实ip?

1.查询历史DNS记录

查看IP和域名绑定的历史记录,利用站长工具,微步在线等网站说不定就能找到使用CDN之前的真实IP。

2.查询子域名

收集子域名信息,如果子域名对应的IP不存在CDN,就可以利用这些IP来辅助查找目标网站的真实IP
3.查询主域名

有些网站为了方便维护只让WWW域名使用CDN,把目标域名前面的WWW去掉ping一下说不定就是真实IP。
4.通过邮件服务器查找

一般邮件系统都在内部,没有经过CDN的解析,通过用户注册、找回密码以及RSS邮件订阅等功能接收目标网站发送的邮件,查看源码(或者信息头)就有可能得到目标网站真实IP。

5.使用国外主机解析域名

国内很多 CDN 厂商因为各种原因只做了国内的线路,而针对国外的线路可能几乎没有,此时我们使用国外的主机直接访问可能就能获取到真实IP

6.网站漏洞查找

利用网站自身存在的漏洞,很多情况下会泄露服务器的真实IP地址

7.利用SSL证书寻找真实原始IP

证书颁发机构(CA)必须将他们发布的每个SSL/TLS证书发布到公共日志中,SSL/TLS证书通常包含域名、子域名和电子邮件地址。使用Censys等工具搜索目标网站信息。

8.F5 LTM解码法

当服务器使用F5 LTM做负载均衡时,通过对set-cookie关键字的解码真实ip也可被获取,例如:Set-Cookie: BIGipServerpool_8.29_8030=487098378.24095.0000,先把第一小节的十进制数即487098378取出来,然后将其转为十六进制数1d08880a,接着从后至前,以此取四位数出来,也就是0a.88.08.1d,最后依次把他们转为十进制数10.136.8.29,也就是最后的ip。

9.网络空间搜索引擎

根据网站特征或者响应内容特征等查找目标网站信息,当然也可以直接通过域名查找。

怎么验证找到的ip是否为真实ip?

使用端口扫描工具扫描开了哪些端口,然后结合开放的端口直接访问找到的IP,看看响应的页面是不是和访问域名返回的一样。

怎么建立隐藏用户?

net user test$ 123456 /add [建立隐藏用户]

net localgroup administrators test$ /add[将隐藏用户加入管理组]

判断网站的CMS有什么意义?

查找已曝光的漏洞。

如果开源,还能下载相应的源码进行代码审计。

根据CMS特征关联同CMS框架站点,进行敏感备份文件扫描,有可能获得站点备份文件。

数据包有哪些请求方式?

目前常用八种请求方式,分别是 GET、POST、HEAD、PUT、DELETE、OPTIONS、TRACE、 CONNECT,get 和 post 最常用

漏洞篇题目

SQL注入的原理和产生原因?

SQL注入原理:

通过某种方式将恶意的sql代码添加到输入参数中,然后传递到sql服务器使其解析并执行的一种攻击手法

漏洞产生原因(实现条件):

用户对sql查询语句参数可控

原本程序要执行的SQL语句,拼接了用户输入的恶意数据

SQL注入的类型?

1.联合注入

2.堆叠注入
3.宽字节注入
4.cookie注入
5.XFF头注入
6.UA注入(user-agent注入)

7.Referer注入
8.二次注入
9.base64注入

10.万能密码
1.文件读写

盲注类型:
1.基于时间的盲注 sleep() benchmark()
2.基于布尔的注入
3.基于报错的注入

updatexml() extractvalue() floor() exp()

预防SQL注入的方法和原理?

1.预编译(数据库不会将参数的内容视为SQL命令执行,而是作为一个字段的属性值来处理)

2.PDO预处理 (本地和Mysql服务端使用字符集对输入进行转义)
3.正则表达式过滤 (如果用户输入了非法信息则利用正则表达式过滤)

SQL注入有哪些绕过方法?

1.大小写绕过注入

2.双写绕过注入

3.编码绕过注入

4.内联注释绕过注入

SQL注入有哪些危害?

1.获取数据库数据

数据库中存放的用户的隐私信息的泄露,脱取数据库中的数据内容(脱库),可获取网站管理员帐号、密码悄无声息的进行对网站后台操作等。

2.网页篡改

通过操作数据库对特定网页进行篡改,严重影响正常业务进行与受害者信誉。

3.网页挂马

将恶意文件或者木马下载链接写入数据库,修改数据库字段值,进行挂马攻击。

4.篡改数据库数据

攻击数据库服务器,篡改或者添加普通用户或者管理员帐号。

5.获取服务器权限

列取目录、读取、写入shell文件获取webshell,远程控制服务器,安装后门,经由数据库服务器提供的操作系统支持,让攻击者得以修改或控制操作系统。

XSS的原理和类型?

原理:攻击者在Web页面中注入恶意的Script代码,当用户浏览该网页时,嵌入的Script代码会被执行,从而达到攻击的目的。

类型:反射型XSS 存储型XSS DOM型XSS

XSS的绕过方法?

\1. 大小写转换;

\2. 引号的使用;

\3. 使用 / 代替空格;

\4. 编码绕过(将字符进行十进制或者十六进制转码);

5.双写绕过;

6.使用制表符 换行符和回车符

7.使用 IMG 源

XSS的危害?

窃取cookie

抓取屏幕截图

获取键盘记录

重定向

植入广告,恶意链接

网页钓鱼

网页挂马

结合网页挂马或者其他工具(Metasploit)获取服务器或者操作系统权限

XSS的防范措施?

对用户的输入进行过滤

比如说添加黑名单或者白名单规则,比如说对& “ ‘ / javascript import等敏感字符进行转义

使用 HttpOnly Cookie

如果cookie中设置了HttpOnly属性,那么通过js脚本将无法读取到cookie信息,这样能有效的防止XSS攻击,窃取cookie内容,这样可以有效的防止XSS攻击窃取cookie。

设置X-XSS-Protection属性

该属性被所有的主流浏览器默认开启。X-XSS-Protection,即XSS保护属性,是设置在响应头中目的是用来防范XSS攻击的。在检查到XSS攻击时,停止渲染页面。

开启CSP网页安全策略

CSP是网页安全策略(Content Security Policy)的缩写。开启策略后可以起到以下作用:禁止加载外域代码,防止复杂的攻击逻辑;禁止外域提交,网站被攻击后,用户的数据不会泄露到外域;禁止内联脚本执行(规则较严格,目前发现 GitHub 使用);禁止未授权的脚本执行(新特性,Google Map 移动版在使用)。合理使用上报可以及时发现 XSS,利于尽快修复问题。

避免内联事件

尽量不要使用 onLoad=“onload(’’)”、onClick=“go(’’)” 这种拼接内联事件的写法。在 JavaScript 中通过 .addEventlistener() 事件绑定会更安全。

使用模板引擎

开启模板引擎自带的 HTML 转义功能。例如: 在 ejs 中,尽量使用 而不是 ; 在 doT.js 中,尽量使用

shell

一.什么是shell

shell

​ shell是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。Shell 既是一种命令语言,又是一种程序设计语言

​ shell是一块包着系统核心的壳,处于操作系统的最外层,与用户直接对话,把用户的输入解释给操作系统,然后处理操作系统输出结果,输出到屏幕给予用户看到结果

shell的作用

解释执行用户输入的命令或程序

用户输入一条命令shell就解释一条

键盘输入命令linux给与响应的方式,称之为交互式

Bash 常用快捷键

  • 快捷键 作用
  • ctrl+A 把光标移动到命令行开头。如果我们输入的命令过长,想要把光标移动到命令行开头时使用。
  • ctrl+E 把光标移动到命令行结尾。
  • ctrl+C 强制终止当前的命令。
  • ctrl+L 清屏,相当于clear命令。
  • ctrl+U 删除或剪切光标之前的命令。我输入了一行很长的命令,不用使用退格键一个一个字符的删除,使用这个快捷键会更加方便
  • ctrl+K 删除或剪切光标之后的内容。
  • ctrl+Y 粘贴ctrl+U或ctul+K剪切的内容。
  • ctrl+R 在历史命令中搜索,按下ctrl+R之后,就会出现搜索界面,只要输入搜索内容,就会从历史命令中搜索。
  • ctrl+D 退出当前终端。
  • ctrl+Z 暂停,并放入后台。这个快捷键牵扯工作管理的内容,我们在系统管理章节详细介绍。
  • ctrl+S 暂停屏幕输出。
  • ctrl+Q 恢复屏幕输出。

二.shell基础

shell的分类

1
2
3
4
5
6
7
#!/bin/bash      /bin/sh   都是指向bash解释器
#!/user/bin/python 用python解释器解释 yum就是用python解释器解释
#!/user/bin/env 在不同平台上都能正确找到解释器的方法
#不指定会默认用$shell解释器

cat /etc/shells
在linux中有很多类型的shell,不同的shell具备不同的功能,shell还决定了脚本中函数的语法,Linux中默认的shell是 / b a s h / b a s h ( 重 点 ) \color{#FF3030}{/bash/bash(重点)}/bash/bash(重点),流行的shell有ash、bash、ksh、csh、zsh等,不同的shell都有自己的特点以及用途。

K8S安全漏洞复现

一.K8S集群搭建

1.环境准备

三台centos7,nat模式

2.网络环境说明

master: 192.168.147.xx

node1: 192.168.147.xx

node2 : 192.168.147.xx

3.修改hostname

master主机:

1
hostnamectl set-hostname master

同理修改node1、node2节点主机名

1
hostnamectl set-hostname node1

设置hostname解析,注意,此时IP与主机名必须一一对应,三台主机都要执行下面命令

1
2
3
4
5
6
cat <<EOF >>/etc/hosts
192.168.147.160 master
192.168.147.161 node1
192.168.147.162 node2
192.168.147.163 node3
EOF

4.k8s安装基础环境准备

4.1 安装docker-ce (所有机器都要安装)
1
2
# 安装docker所需的工具
yum install -y yum-utils device-mapper-persistent-data lvm2

配置阿里云的docker源

1
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

指定安装这个版本的docker-ce

1
yum install -y docker-ce-18.09.9-3.el7

启动docker

1
systemctl enable docker && systemctl start docker
4.2 防火墙及内核设置(所有机器)
1
2
3
# 关闭防火墙
systemctl disable firewalld
systemctl stop firewalld
1
2
3
4
# 关闭selinux
# 永久关闭 修改/etc/sysconfig/selinux文件设置
sed -i 's/SELINUX=permissive/SELINUX=disabled/' /etc/sysconfig/selinux
sed -i "s/SELINUX=enforcing/SELINUX=disabled/g" /etc/selinux/config
1
2
3
4
# 禁用交换分区
swapoff -a
# 永久禁用,打开/etc/fstab注释掉swap那一行。
sed -i 's/.*swap.*/#&/' /etc/fstab
1
2
3
4
5
6
# 修改内核参数
cat <<EOF > /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sysctl --system

5.K8s安装

5.1.1 master管理节点安装 kubeadm、kubelet、kubectl
1
2
3
4
5
6
7
8
9
10
# 执行配置k8s阿里云源
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
1
2
# 安装kubeadm、kubectl、kubelet
yum install -y kubectl-1.16.0-0 kubeadm-1.16.0-0 kubelet-1.16.0-0
1
2
# 启动kubelet服务
systemctl enable kubelet && systemctl start kubelet
5.1.2 初始化K8s

执行以下命令初始化k8s,注意–apiserver-advertise-address 后面的IP为master的主机ip,这条命令执行慢,需要等待两三分钟

1
kubeadm init --image-repository registry.aliyuncs.com/google_containers --kubernetes-version v1.16.0 --apiserver-advertise-address 192.168.147.160 --pod-network-cidr=10.244.0.0/16 --token-ttl 0

上面安装完后,会提示你输入如下命令,复制粘贴过来,执行即可。

以上,安装master节点完毕。可以使用kubectl get nodes查看一下,此时master处于NotReady状态,暂时不用管。

1
kubectl get nodes 

5.2 node节点配置

5.2.1 node节点安装 kubeadm、kubelet

安装步骤和上面master节点安装一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 执行配置k8s阿里云源
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF

# 安装kubeadm、kubectl、kubelet
yum install -y kubeadm-1.16.0-0 kubelet-1.16.0-0

# 启动kubelet服务
systemctl enable kubelet && systemctl start kubelet
5.2.2 node节点加入集群

这里加入集群的命令每个人都不一样,master节点安装配置好后,会返回加入集群的命令,如果忘记,可以登录master节点,使用kubeadm token create –print-join-command 来获取

1
kubeadm token create --print-join-command

节点配置完成后,在master主机上,执行kubectl get nodes 可查勘节点

6、插件安装

6.1 安装calico (master机器)
1
2
3
4
5
yum install wget
wget https://kuboard.cn/install-script/calico/calico-3.9.2.yaml
export POD_SUBNET=10.244.0.0/16
sed -i "s#192\.168\.0\.0/16#${POD_SUBNET}#" calico-3.9.2.yaml
kubectl apply -f calico-3.9.2.yaml
6.2 安装flannel(master机器)
1
2
3
4
mkdir -p ~/k8s/
cd ~/k8s
curl -O https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yaml
kubectl apply -f kube-flannel.yml

由于kube-flannel.yml 文件为国外地址,而且国内访问不了,所以将配置文件内容放在文末,可以直接创建文件,将内容复制粘贴进去。

执行完成后,在master主机上执行 kubectl get node 如果状态为Ready状态即表示安装成功。

7.kube-flannel.yml 文件内容为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
---
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: psp.flannel.unprivileged
annotations:
seccomp.security.alpha.kubernetes.io/allowedProfileNames: docker/default
seccomp.security.alpha.kubernetes.io/defaultProfileName: docker/default
apparmor.security.beta.kubernetes.io/allowedProfileNames: runtime/default
apparmor.security.beta.kubernetes.io/defaultProfileName: runtime/default
spec:
privileged: false
volumes:
- configMap
- secret
- emptyDir
- hostPath
allowedHostPaths:
- pathPrefix: "/etc/cni/net.d"
- pathPrefix: "/etc/kube-flannel"
- pathPrefix: "/run/flannel"
readOnlyRootFilesystem: false
# Users and groups
runAsUser:
rule: RunAsAny
supplementalGroups:
rule: RunAsAny
fsGroup:
rule: RunAsAny
# Privilege Escalation
allowPrivilegeEscalation: false
defaultAllowPrivilegeEscalation: false
# Capabilities
allowedCapabilities: ['NET_ADMIN']
defaultAddCapabilities: []
requiredDropCapabilities: []
# Host namespaces
hostPID: false
hostIPC: false
hostNetwork: true
hostPorts:
- min: 0
max: 65535
# SELinux
seLinux:
# SELinux is unused in CaaSP
rule: 'RunAsAny'
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: flannel
rules:
- apiGroups: ['extensions']
resources: ['podsecuritypolicies']
verbs: ['use']
resourceNames: ['psp.flannel.unprivileged']
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- apiGroups:
- ""
resources:
- nodes
verbs:
- list
- watch
- apiGroups:
- ""
resources:
- nodes/status
verbs:
- patch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: flannel
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: flannel
subjects:
- kind: ServiceAccount
name: flannel
namespace: kube-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: flannel
namespace: kube-system
---
kind: ConfigMap
apiVersion: v1
metadata:
name: kube-flannel-cfg
namespace: kube-system
labels:
tier: node
app: flannel
data:
cni-conf.json: |
{
"name": "cbr0",
"cniVersion": "0.3.1",
"plugins": [
{
"type": "flannel",
"delegate": {
"hairpinMode": true,
"isDefaultGateway": true
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "vxlan"
}
}
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: kube-flannel-ds-amd64
namespace: kube-system
labels:
tier: node
app: flannel
spec:
selector:
matchLabels:
app: flannel
template:
metadata:
labels:
tier: node
app: flannel
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: beta.kubernetes.io/os
operator: In
values:
- linux
- key: beta.kubernetes.io/arch
operator: In
values:
- amd64
hostNetwork: true
tolerations:
- operator: Exists
effect: NoSchedule
serviceAccountName: flannel
initContainers:
- name: install-cni
image: quay-mirror.qiniu.com/coreos/flannel:v0.11.0-amd64
command:
- cp
args:
- -f
- /etc/kube-flannel/cni-conf.json
- /etc/cni/net.d/10-flannel.conflist
volumeMounts:
- name: cni
mountPath: /etc/cni/net.d
- name: flannel-cfg
mountPath: /etc/kube-flannel/
containers:
- name: kube-flannel
image: quay-mirror.qiniu.com/coreos/flannel:v0.11.0-amd64
command:
- /opt/bin/flanneld
args:
- --ip-masq
- --kube-subnet-mgr
resources:
requests:
cpu: "100m"
memory: "50Mi"
limits:
cpu: "100m"
memory: "50Mi"
securityContext:
privileged: false
capabilities:
add: ["NET_ADMIN"]
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
volumeMounts:
- name: run
mountPath: /run/flannel
- name: flannel-cfg
mountPath: /etc/kube-flannel/
volumes:
- name: run
hostPath:
path: /run/flannel
- name: cni
hostPath:
path: /etc/cni/net.d
- name: flannel-cfg
configMap:
name: kube-flannel-cfg
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: kube-flannel-ds-arm64
namespace: kube-system
labels:
tier: node
app: flannel
spec:
selector:
matchLabels:
app: flannel
template:
metadata:
labels:
tier: node
app: flannel
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: beta.kubernetes.io/os
operator: In
values:
- linux
- key: beta.kubernetes.io/arch
operator: In
values:
- arm64
hostNetwork: true
tolerations:
- operator: Exists
effect: NoSchedule
serviceAccountName: flannel
initContainers:
- name: install-cni
image: quay-mirror.qiniu.com/coreos/flannel:v0.11.0-arm64
command:
- cp
args:
- -f
- /etc/kube-flannel/cni-conf.json
- /etc/cni/net.d/10-flannel.conflist
volumeMounts:
- name: cni
mountPath: /etc/cni/net.d
- name: flannel-cfg
mountPath: /etc/kube-flannel/
containers:
- name: kube-flannel
image: quay-mirror.qiniu.com/coreos/flannel:v0.11.0-arm64
command:
- /opt/bin/flanneld
args:
- --ip-masq
- --kube-subnet-mgr
resources:
requests:
cpu: "100m"
memory: "50Mi"
limits:
cpu: "100m"
memory: "50Mi"
securityContext:
privileged: false
capabilities:
add: ["NET_ADMIN"]
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
volumeMounts:
- name: run
mountPath: /run/flannel
- name: flannel-cfg
mountPath: /etc/kube-flannel/
volumes:
- name: run
hostPath:
path: /run/flannel
- name: cni
hostPath:
path: /etc/cni/net.d
- name: flannel-cfg
configMap:
name: kube-flannel-cfg
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: kube-flannel-ds-arm
namespace: kube-system
labels:
tier: node
app: flannel
spec:
selector:
matchLabels:
app: flannel
template:
metadata:
labels:
tier: node
app: flannel
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: beta.kubernetes.io/os
operator: In
values:
- linux
- key: beta.kubernetes.io/arch
operator: In
values:
- arm
hostNetwork: true
tolerations:
- operator: Exists
effect: NoSchedule
serviceAccountName: flannel
initContainers:
- name: install-cni
image: quay-mirror.qiniu.com/coreos/flannel:v0.11.0-arm
command:
- cp
args:
- -f
- /etc/kube-flannel/cni-conf.json
- /etc/cni/net.d/10-flannel.conflist
volumeMounts:
- name: cni
mountPath: /etc/cni/net.d
- name: flannel-cfg
mountPath: /etc/kube-flannel/
containers:
- name: kube-flannel
image: quay-mirror.qiniu.com/coreos/flannel:v0.11.0-arm
command:
- /opt/bin/flanneld
args:
- --ip-masq
- --kube-subnet-mgr
resources:
requests:
cpu: "100m"
memory: "50Mi"
limits:
cpu: "100m"
memory: "50Mi"
securityContext:
privileged: false
capabilities:
add: ["NET_ADMIN"]
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
volumeMounts:
- name: run
mountPath: /run/flannel
- name: flannel-cfg
mountPath: /etc/kube-flannel/
volumes:
- name: run
hostPath:
path: /run/flannel
- name: cni
hostPath:
path: /etc/cni/net.d
- name: flannel-cfg
configMap:
name: kube-flannel-cfg
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: kube-flannel-ds-ppc64le
namespace: kube-system
labels:
tier: node
app: flannel
spec:
selector:
matchLabels:
app: flannel
template:
metadata:
labels:
tier: node
app: flannel
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: beta.kubernetes.io/os
operator: In
values:
- linux
- key: beta.kubernetes.io/arch
operator: In
values:
- ppc64le
hostNetwork: true
tolerations:
- operator: Exists
effect: NoSchedule
serviceAccountName: flannel
initContainers:
- name: install-cni
image: quay-mirror.qiniu.com/coreos/flannel:v0.11.0-ppc64le
command:
- cp
args:
- -f
- /etc/kube-flannel/cni-conf.json
- /etc/cni/net.d/10-flannel.conflist
volumeMounts:
- name: cni
mountPath: /etc/cni/net.d
- name: flannel-cfg
mountPath: /etc/kube-flannel/
containers:
- name: kube-flannel
image: quay-mirror.qiniu.com/coreos/flannel:v0.11.0-ppc64le
command:
- /opt/bin/flanneld
args:
- --ip-masq
- --kube-subnet-mgr
resources:
requests:
cpu: "100m"
memory: "50Mi"
limits:
cpu: "100m"
memory: "50Mi"
securityContext:
privileged: false
capabilities:
add: ["NET_ADMIN"]
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
volumeMounts:
- name: run
mountPath: /run/flannel
- name: flannel-cfg
mountPath: /etc/kube-flannel/
volumes:
- name: run
hostPath:
path: /run/flannel
- name: cni
hostPath:
path: /etc/cni/net.d
- name: flannel-cfg
configMap:
name: kube-flannel-cfg
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: kube-flannel-ds-s390x
namespace: kube-system
labels:
tier: node
app: flannel
spec:
selector:
matchLabels:
app: flannel
template:
metadata:
labels:
tier: node
app: flannel
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: beta.kubernetes.io/os
operator: In
values:
- linux
- key: beta.kubernetes.io/arch
operator: In
values:
- s390x
hostNetwork: true
tolerations:
- operator: Exists
effect: NoSchedule
serviceAccountName: flannel
initContainers:
- name: install-cni
image: quay-mirror.qiniu.com/coreos/flannel:v0.11.0-s390x
command:
- cp
args:
- -f
- /etc/kube-flannel/cni-conf.json
- /etc/cni/net.d/10-flannel.conflist
volumeMounts:
- name: cni
mountPath: /etc/cni/net.d
- name: flannel-cfg
mountPath: /etc/kube-flannel/
containers:
- name: kube-flannel
image: quay-mirror.qiniu.com/coreos/flannel:v0.11.0-s390x
command:
- /opt/bin/flanneld
args:
- --ip-masq
- --kube-subnet-mgr
resources:
requests:
cpu: "100m"
memory: "50Mi"
limits:
cpu: "100m"
memory: "50Mi"
securityContext:
privileged: false
capabilities:
add: ["NET_ADMIN"]
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
volumeMounts:
- name: run
mountPath: /run/flannel
- name: flannel-cfg
mountPath: /etc/kube-flannel/
volumes:
- name: run
hostPath:
path: /run/flannel
- name: cni
hostPath:
path: /etc/cni/net.d
- name: flannel-cfg
configMap:
name: kube-flannel-cfg

8.完整的卸载k8s

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 首先清理运行到k8s群集中的pod,使用
kubectl delete node --all

# 使用脚本停止所有k8s服务
for service in kube-apiserver kube-controller-manager kubectl kubelet etcd kube-proxy kube-scheduler;
do
systemctl stop $service
done

# 使用命令卸载k8s
kubeadm reset -f

# 卸载k8s相关程序
yum -y remove kube*

# 删除相关的配置文件
modprobe -r ipip
lsmod

# 然后手动删除配置文件和flannel网络配置和flannel网口:
rm -rf /etc/cni
rm -rf /root/.kube
# 删除cni网络
ifconfig cni0 down
ip link delete cni0
ifconfig flannel.1 down
ip link delete flannel.1

# 删除残留的配置文件
rm -rf ~/.kube/
rm -rf /etc/kubernetes/
rm -rf /etc/systemd/system/kubelet.service.d
rm -rf /etc/systemd/system/kubelet.service
rm -rf /etc/systemd/system/multi-user.target.wants/kubelet.service
rm -rf /var/lib/kubelet
rm -rf /usr/libexec/kubernetes/kubelet-plugins
rm -rf /usr/bin/kube*
rm -rf /opt/cni
rm -rf /var/lib/etcd
rm -rf /var/etcd

# 更新镜像
yum clean all
yum makecache

二.K8s安全漏洞复现

vim学习

1.认识 VIM

VIM 常用的有四个模式,:

​ 正常模式 (Normal-mode)

​ 插入模式 (Insert-mode)

​ 命令模式 (Command-mode)

​ 可视模式 (Visual-mode)

2.命令行模式功能键

命令 功能
yy 复制当前光标所在行
[n]yy n为数字,复制当前光标开始的n行
p 粘贴复制的内容到光标所在行之下
P 粘贴复制的内容到光标所在行之上
dd 删除当前光标所在行
[n]dd 删除当前光标所在行开始的n行
cc 剪切当前光标所在行
G 光标移动到文件尾
u 取消前一个动作
. 重复前一个动作
x 删除光标当前的一个字符
ZZ 保存并退出

3.行底模式功能键

命令 功能
:w 保存
:q 退出 vi( 系统会提示保存修改 )
:q! 强行退出(对修改不做保存)
:wq 保存后退出
:w [filepath] 另存文件到 filepath
:set nu 显示行号
:n 定位到第n行
:set nonu 取消行号