最新面试题2

文章目录


string为什么被final修饰

1.为了实现字符串池
    首先你要理解final的用途,在分析String为什么要用final修饰,final可以修饰类,方法和变量,并且被修饰的类或方法,被final修饰的类不能被继承,即它不能拥有自己的子类,被final修饰的方法不能被重写, final修饰的变量,无论是类属性、对象属性、形参还是局部变量,都需要进行初始化操作。
  final修饰的String,代表了String的不可继承性,final修饰的char[]代表了被存储的数据不可更改性。但是:虽然final代表了不可变,但仅仅是引用地址不可变,并不代表了数组本身不会变,
2.为了线程安全
     因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。
 3.为了实现String可以创建HashCode不可变性
     因为字符串是不可变的,所以在它创建的时候HashCode就被缓存了,不需要重新计算。

设计模式
装饰模式又叫油漆工模式,只做增强不做改变,如MyBatis-Blus

Java常见异常有哪些

java.lang.InstantiationError:实例化错误。当一个应用试图通过Java的new操作符构造一个抽象类或者

接口时抛出该异常.

java.lang.OutOfMemoryError:内存不足错误。当可用内存不足以让Java虚拟机分配给一个对象时抛出

该错误。

java.lang.StackOverflowError:堆栈溢出错误。当一个应用递归调用的层次太深而导致堆栈溢出或者陷入死循环时抛出该错误。

java.lang.ClassCastException:类造型异常。假设有类A和B(A不是B的父类或子类),O是A的实例,那么当强制将O构造为类B的实例时抛出该异常。该异常经常被称为强制类型转换异常。

java.lang.ClassNotFoundException:找不到类异常。当应用试图根据字符串形式的类名构造类,而在
遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。

java.lang.ArithmeticException:算术条件异常。譬如:整数除零等。

java.lang.ArrayIndexOutOfBoundsException:数组索引越界异常。当对数组的索引值为负数或大于等
于数组大小时抛出。

java.lang.IndexOutOfBoundsException:索引越界异常。当访问某个序列的索引值小于0或大于等于序
列大小时,抛出该异常。 
java.lang.InstantiationException:实例化异常。当试图通过newInstance()方法创建某个类的实例,而该类是一个抽象类或接口时,抛出该异常。

java.lang.NoSuchFieldException:属性不存在异常。当访问某个类的不存在的属性时抛出该异常。

java.lang.NoSuchMethodException:方法不存在异常。当访问某个类的不存在的方法时抛出该异常。

java.lang.NullPointerException:空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等。

java.lang.NumberFormatException:数字格式异常。当试图将一个String转换为指定的数字类型,而
该字符串确不满足数字类型要求的格式时,抛出该异常。

java.lang.StringIndexOutOfBoundsException:字符串索引越界异常。当使用索引值访问某个字符串
中的字符,而该索引值小于0或大于等于序列大小时,抛出该异常

数据库

SQL语句分类

DML  数据库操纵语言
DDL  数据库定义语言
DCL  数据库控制语言
DQL  数据库查询语言

库的操作

创建数据库

create database cgb2108 default character set utf8;

查看所有数据库

show databases;

删除数据库

drop databse cgb2108;

使用数据库

use cgb2108;

表的操作

创建表

create table tb_door(id int primary key auto_increment,
door_name varcher (100),
tel varchar (50)
);

查看所有表

show tables;

查看表结构

desc tb_door;

修改表

alter table tb_door add column money numeric(7,2);

删除表

delete table tb_door;

表中数据

插入表数据

insert into tb_door values(null, "永和大王",666);

查询表数据

select * from tb_door;

修改表数据

update tb_door set tel=555 where id =1;

删除表中数据

delete from tb_door where id = 2;

排序

select * from tb_door order by tel desc;

查询中记录数

select count (*) from tb_door;

字段约束

主键约束

create table a( id int primary key auto_increment);

外键约束

外键约束:防止了冗余的数据,通过外键来描述两张表的关系
#特点是:当子表中添加数据时,子表的主键值 必须 取自主表!
#当主表删除数据时,子表没有相关的记录
#语法:foreign key(本表的主键名) references 对方表名(对方表的主键)
foreign key(user_id) REFERENCES tb_user(id)  

检查约束

#2.检查约束:给字段使用check添加合法性的检查
CREATE TABLE f(
 id INT PRIMARY KEY AUTO_INCREMENT,
 age INT,
 CHECK(age<100))         #检查约束,age录入不合法数据时无法保存

默认约束

#1.默认约束:给字段使用default添加默认值
CREATE TABLE e(
 id INT PRIMARY KEY AUTO_INCREMENT,
 sex VARCHAR(10) DEFAULT '男' )#默认约束,设置默认值

非空约束

create table user(id int primary key auto_increment,password varchar(50) not null);
show tables;
insert into user values(null,null);//不符合非空约束
insert into user values(null,123;);//OK

唯一约束

create table test(id int primary key auto_increment,username varchar(50) unique);
insert into test values(null,'lisi');
insert into test values(null,'lisi');--username的值要唯一,重复会报错的

基础函数

# lower upper  length substr concat  replace  ifnull 

SELECT 'ABC',LOWER('ABC') from dept; --数据转小写
select upper(dname) from dept --数据转大写
select length(dname) from dept --数据的长度
SELECT dname,SUBSTR(dname,1,3) FROM dept; --截取[1,3]
select dname,concat(dname,'123') X from dept --拼接数据
select dname,replace(dname,'a','666') X from dept --把a字符替换成666
select ifnull(comm,10) comm from dept2 #判断,如果comm是null,用10替换

# round & celi & floor 

select comm,round(comm) from emp
select comm,ceil(comm) ,floor(comm) from emp

# uuid now  year & month & day

select uuid() 
select now(),year(now()),month(now()),day(now()) from emp ;

# 转义 \
select "xi'an" from dept;
select 'xi\'an' 

条件查询

distinct  去重         
select distinct loc from dept;

where  注意:where中不能使用别名
select * from 表名 where ( =, > , < , ro , and , in , notin )

like  模糊查询
select * from emp where like 'l%' # 以l开头的 ,'%a'以a结尾 ,'%a%' 包含,
'l__'l后面有两个字符的 _代表一个字符位置

null  不能用 = ,使用is
select * from emp where mgr is null --过滤字段值为空的
select * from emp where mgr is not null --过滤字段值不为空的

between and
select * from emp where sal<=3000 and sal>=10000--等效
select * from emp where sal between 3000 and 10000--等效

limit 分页	 第一个开始位置n+1,第二个展示条数
select * from emp limit 1,2 --从第二条开始,展示2条记录

order by  升降序
SELECT * FROM emp order by sal #默认升序
SELECT * FROM emp order by sal desc #降序

聚合函数

count 
select count(*) from emp --底层优化了
select count(1) from emp --效果和*一样
select count(comm) from emp --慢,只统计非NULL的

max/min
select max(sal) from emp --求字段的最大值
SELECT ename,MAX(sal) FROM emp group by ename --分组

sum/avg
select count(*) from emp --总记录数
select sum(sal) from emp --求和
select avg(sal) from emp --平均数

group by  # 分组  
SELECT deptno,job,MAX(sal),AVG(sal) FROM emp

having  # 过滤条件
select deptno, AVG(sal) from emp
group by deptno #按部门分组
having AVG(sal)<8000 #查询条件,类似where,但是group by只能配合having

事务处理

开启事务 :start transaction / #BEGIN 关闭事务自动提交

结束事务:commit(提交) / rollback (回滚)

索引

好处:提高查询效率,坏处:索引会单独创建一张表

为何索引快?
明显查询索引表比直接查询数据表要快的多,首先,索引表是排序了,可以类似二分查找,非常有效的提高了查询的速度。

排序,tree结构,类似二分查找
索引表小

**单值索引:**一个索引只包含一个字段

**唯一索引:**一个索引包含一个字段,字段的值不能重复

复合索引:一个索引可以包含多个字段,使用复合索引,遵循最左特性

# 查看索引:
show index from emp;
# 创建单值索引:
create index ename_index on emp (ename);
####  查询sql执行性能/计划 
explain 
select * from emp where ename='jack';

# 创建唯一索引
create unique index uni_sal on emp (sal);
# explain select * from emp where sal=10000;

# 创建复合索引
create index fuhe_index on emp(comm,job);
#使用复合索引时必须遵循最左特性.

视图

可以吧视图当成表来使用,视图中存的数据使SQL查询到的结果,视图一旦创建,SQL无法被优化

# 创建视图
create view name_view AS select * from emp where ename like '%a%';
# 使用视图
select * from name_view;

多表关联

笛卡尔积
#方式1:笛卡尔积,通过逗号连接表名,where 描述两个之间的关系
select * from A , B 
where A.ano = B.ano
and  A.ano = 1;
连接查询(内,左,右连接)
#方式2:连接查询(三种)  join   on 描述两个之间的关系 where 过滤条件    
#内连接inner join:取两张表的交集
#左连接left join:取左表的所有和右表满足条件的
#右连接right join:取右表的所有和左表满足条件的
select *  from A inner join B 
on A.ano=B.ano  
where A.ano=1;
子查询
#方式3:子查询/嵌套查询:把上次的查询结果作为这次的查询条件
# 根据名字查ano,根据ano查地址  
select addr from B where ano =(
	select ano from A where name = 'jack');
# 子查询中查询多个结果,必须用in不能用=   # name in ('jack','rose')
select addr from B where ano in (
	select ano from A where name ='jack' and name 'rose');
    
通配符
              1% 匹配任意字符:
                     实例:select * from score where name like ‘李%’
                     说明:从score表中查询所有姓李的同学的数据,包含三个名字的。
              2、_ 匹配任意单个字符:
                     实例:select * from score where name like ‘李_’
                     说明:从score表中查询所有姓李的同学的数据,只包含两个名字的同学。
              3[ ] 匹配包括括号内任意的单个字符:
                     实例:select * from score where name like ‘[,,]%’
                     说明:从score表中查询所有姓李,,王的同学的数据
              4[^][!] 匹配不包括括号内任意的单个字符:
                     实例:select * from score where name like ‘[^,,]%’
                     说明:从score表中查询所有不是姓李,,王的同学的数据

SQL优化

  • 查询sql 尽量不要使用 select * ,而是具体的字段,节省资源,减少网络开销,select *进行查询时,会全表扫描,不会用到索引

  • 避免在where 子句中使用or 来连接,能用and 不要用 or ,使用or 可能造成索引失效,从而全局扫描, 避免在where子句中使用!=或<>操作符,否则引擎将放弃使用索引而进行全表扫描

  • 模糊查询 ,like 很可能造成索引失效

  • 使用explain 分析sql 执行计划,看sql 是否使用 索引

  • 复合索引使用时 ,不遵循最左特性,会造成索引失效

  • 使用varchar 代替 char , varchar 可变字段 按数据实际大小存储 ,节省存储空间,char 按声明 大小存储,不足补空格,对于查询来说,相对较小的字段内搜索,效率更高

  • 使用数组代替字符串类型 sex : 1-男 0-女 数据类型使用 tinyint

  • 查询尽量避免返回大量数据,查询返回数据量很大,就会造成查询时间过长,网络传输时间过长,通常采用分页。

  • 批量插入性能提升,默认新增SQL有事务控制,导致每条都需要事务开启和事务提交;而批量处理是一次事务开启和提交,速度快。多次提交,速度慢

  • 批量删除优化 ,同时修改或删除过多数据,因为会造成cpu利用率过高,会造成锁表操作,从而影响对数据库的访问,分批处理数据

  • 伪删除设计,删除只是一个标识,并没有从数据库表中真正删除,用户看不到,不影响用户使用,优点数据量较大时,操作速度快

  • 提高group by 语句的效率 ,where 先过滤,在分组 ,having 先分组,在过滤,执行顺序不同, 能用where 不要用having。

事务4个特性ACID

原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中如果发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

隔离级别

事务隔离分为不同级别,包括

读未提交(Read uncommitted) 安全性最差,可能发生并发数据问题,性能最好
读提交(read committed) Oracle默认的隔离级别
可重复读(repeatable read)MySQL默认的隔离级别,安全性较好,性能一般
串行化(Serializable) 表级锁,读写都加锁,效率低下,安全性高,不能并发

一、脏读:

事物A修改了数据表中的一个数据num,但是没有提交,此时事物B读取了num,事物A rollback ,num改变为原来的值,那么事物B读到的num即为脏数据。

二、幻读:

事物A在用一个表,此时事物B在表中增加/删除了一条数据,A发现多了/少了一条数据,即为幻读。

三、不可重复读:

A在用num为1,B将num改为2,且已经提交,A再读num为2,1 != 2 ,重复读取一个数据,前后不一致。

四、丢失更新:

A、B同时操作一条数据,B的修改覆盖了A的,即出现丢失更新

数据库引擎

MySQL - 常见的三种数据库存储引擎

InnoDB: 支持事务处理,支持外键,支持崩溃修复能力和并发控制。如果需要对事务的完整性要求比较高(比如银行),要求实现并发控制(比如售票),那选择InnoDB有很大的优势。如果需要频繁的更新、删除操作的数据库,也可以选择InnoDB,因为支持事务的提交(commit)和回滚(rollback)。

MyISAM: 插入数据快,空间和内存使用比较低。如果表主要是用于插入新记录和读出记录,那么选择MyISAM能实现处理高效率。如果应用的完整性、并发性要求比较低,也可以使用。

MEMORY: 所有的数据都在内存中,数据的处理速度快,但是安全性不高。如果需要很快的读写速度,对数据的安全性要求较低,可以选择MEMOEY。它对表的大小有要求,不能建立太大的表。所以,这类数据库只使用在相对较小的数据库表。

同一个数据库也可以使用多种存储引擎的表。如果一个表要求比较高的事务处理,可以选择InnoDB。这个数据库中可以将查询要求比较高的表选择MyISAM存储。如果该数据库需要一个用于查询的临时表,可以选择 存储引擎。
在创建表的时候通过engine=…或type=…来指定所要使用的引擎;

show table status from DBname来查看指定表的引擎

InnoDB引擎的4大特性

插入缓冲(insertbuffer)

二次写(doublewrite)

自适应哈希索引(ahi)

预读(readahead)

存储引擎选择

9bbb如果没有特别的需求,使用默认的Innodb即可。

MyISAM:以读写插入为主的应用程序,比如博客系统、新闻门户网站。

Innodb:更新(删除)操作频率也高如:博客,或者要保证数据的完整性,并发量高,

支持事务和外键。比如OA自动化办公系统。

MEMORY:如果数据库需要一个用于查询的临时表,可以选择 存储引擎。

mysql有关权限的表都有哪几个

user权限表:记录允许连接到服务器的用户帐号信息,里面的权限是全局级的。

db权限表:记录各个帐号在各个数据库上的操作权限。

table_priv权限表:记录数据表级的操作权限。

columns_priv权限表:记录数据列级的操作权限。

host权限表:配合db权限表对给定主机上数据库级操作权限作更细致的控制。这个权限表不受GRANT和REVOKE语句的影响

sql执行顺序

(1) FROM [left_table] 选择表
(2) ON <join_condition> 链接条件
(3) <join_type> JOIN <right_table> 链接
(4) WHERE <where_condition> 条件过滤
(5) GROUP BY <group_by_list> 分组
(6) AGG_FUNC(column or expression),... 聚合
(7) HAVING <having_condition> 分组过滤
(8) SELECT (9) DISTINCT column,... 选择字段、去重
(9) ORDER BY <order_by_list> 排序
(10) LIMIT count OFFSET count; 分页

数据库的三大范式

第一范式:强调的是列的原子性,即列不能再分成其他几列

违反第一范式的示例

第二范式:2NF在1NF的基础之上,确保表中的每列都和主键相关,表里的每个字段都要依赖于主键

第三范式:3NF在2NF的基础之上,保每列都和主键列直接相关,而不是间接相关

数据库的数类型

char :(0~255)长度固定,不足使用空格填充,char(11)存储abc,占11位。查询速度极快但浪费空间

varchar:(0~65535)长字符串,varchar(11)存储abc,只占3位。查询稍慢,但节省空间。

text:(065535)用来存储长文本,-longtext(04294967295) 极大文本文件

blob:(0~65535)存储二进制文件的容器,一张图片或一个声音

date:年 月 日 ,time:时 分 秒 ,year :年份 ,datetime:年月日时分秒 timestaMp:时间戳,毫秒值

decimal和表示精确的整数数字

int:整数数据 ,bigint (Java中long)整数数据

JDBC

  1. 导入jar 包

  2. 注册驱动 Class.forName()

  3. 获取数据库连接 DriverManager.getConnection

  4. 获取传输器 c . createStatement

  5. 执行SQL s.excuteQuery(查)/ excuteUpdate(增,删,改)

  6. 解析结果集 使用 resultset 中的.next() 和.get()

  7. 释放资源 .close()

    public class Test1 {
        public static void main(String[] args) throws Exception {
            //1,注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2,获取和数据库的连接
    String url= "jdbc:mysql:///cgb2108?characterEncoding=utf8";//指定要连接哪个数据库
            String user= "root" ; //使用的用户名
            String pwd= "root" ; //使用的密码
            Connection c = DriverManager.getConnection(url, user, pwd);
            //3,获取传输器,执行SQL
            Statement s= c.createStatement();//preparedStatement可以使防止sql注入
            //4,执行SQL
            ResultSet r = s.executeQuery("select * from students");
            //5,解析结果集
            while( r.next() ){//next()判断结果集中是否有数据
                for (int i = 1; i <= 5 ; i++) {
                    //获取每列的值并打印
                    System.out.println( r.getString(i) ); }
            }
            //6,释放资源
            r.close(); //关闭结果集
            s.close();//关闭传输器
            c.close();//关闭连接   }   }
    

url防止中文乱码

//jdbc连接mysql数据库的协议//本机:端口号/数据库的名字 解决中文乱码   指定时区 关闭权限检验
"jdbc:mysql://localhost:3306/cgb2108?characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false"

sql攻击/注入

使用传输器 createStatement ,用"+a+" 动态拼接参数,sql语句中出现了 # ,导致sql语义改变,当成注释使用了。

使用preparedStatement ,用sql骨架, 用?代替了参数位置,?表示占位符

资源一定会被释放吗?

 static public void close(ResultSet r, PreparedStatement s,Connection c){
        if(r != null){ //避免了空指针异常
            try {
                r.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if(s != null) {
            try {
                s.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if(c != null) {
            try {
                c.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }

HTML

前端技术栈

HTML超文本标记语言实现页面展现,形成静态网页

CSS层叠样式表实现页面美化

JS javascript脚本语言实现页面前端和后端的数据交互,形成动态网页

React facebook出品前端、移动端JavaScript框架

Angular google 出品基于TypeScript的开源 Web 应用框架

Vue 国人出品,阿里收购构建用户界面的渐进式框架,自底向上开发

NodeJS 基于 Chrome V8 引擎的 JavaScript 运行环境

html( 超文本标记语言)

<!DOCTYPE html> <!-- 文档声明行,声明文档的类型 -->
<html> <!-- 标志着这是HTML文档,要有开始标签和结束标签-->
	<head> <!-- 是网页的头部分,设置网页的标题和编码-->
		<meta charset="utf-8"><!-- 设置了网页的编码,u8避免中文乱码-->
		<title>这是测试文件</title> <!-- 设置了网页的标题-->
	</head>
	<body> <!-- 是网页的体部分,用来做展示-->
		你好 html~ <br/>  <!-- br是换行标签, &nbsp;是一个空格-->
		<!-- 1.标题标签:在网页中显示标题内容,h1大~h6小 -->
			<h1>1号标题</h1>
			<h2>2号标题</h2>
		<!-- 2.列表标签:在网页中添加列表内容,ul+li/ol+li -->
			<ul> <!-- 无序列表 unorderlist-->
				<li>直击山西水灾:住房地基塌陷</li>
				<li>经济日报解读美团垄断案</li>
			</ul>
			<ol> <!-- 有序列表 orderlist-->
				<li>直击山西水灾:住房地基塌陷</li>
				<li>经济日报解读美团垄断案</li>
			</ol>
		<!-- 3.图片标签:在网页中插入图片
			src是img标签的属性,用来指定图片的路径
			width:图片的宽度,单位是px像素,height:图片的高度,单位是px像素
		-->	
			<img src="2.jpg" width="300px" height="500px"/>
		<!-- 4.超链接标签:给元素添加链接效果
			href:指定跳转目标
			target:指定网页的打开方式.默认是_self用当前窗口打开,_blank用新窗口
		 -->
			<a href="https://www.baidu.com/" target="_blank">百度一下</a>
			<!-- 给图片添加链接效果 -->
			<a href="https://www.baidu.com/" target="_blank">
				<img src="a.png" /> 
			</a>
			<!-- 给列表添加链接效果 -->
			<ol> <!-- 有序列表 orderlist-->
				<li> <a href="https://www.baidu.com">直击山西水灾:地基塌陷</a> </li>
				<li> <a href="#">经济日报解读美团垄断案</a> </li>
			</ol>
			<!-- 锚定:回到顶部 -->
			<a name="top">我是顶部</a>
				<h3>北京富婆通讯录</h3>
				<h3>北京富婆通讯录</h3>
			<a href="#top">点击,回到顶部</a> <!-- 回到name=top的位置处-->
		<!-- 5.输入框标签: -->
			普通的输入框: <input type="text" />
			密码输入框: <input type="password" />
			数字输入框: <input type="number" />
			年月日输入框: <input type="date" />
			周输入框: <input type="week" />
			按钮: 
				<input type="button" value="保存"/>
				<button>登录</button>
			提交按钮:把前端页面输入的数据提交给后端java程序处理
				<input type="submit" value="注册"/>
				<button type="submit">提交</button>
			单选:<input type="radio" />男
			多选:<input type="checkbox"/>杨幂
				<input type="checkbox"/>迪丽热巴
				<input type="checkbox"/>Anglelababa
            下拉框:        <select> <!-- 定义下拉框-->
							<option>-请选择-</option> <!-- 定义下拉选项-->
							<option>北京</option>
							<option>广东</option>
						</select>
             文本域:<textarea></textarea>
             文件上传:<input type="file" />
          <!-- 6.添加音频,视频: --> 
                <audio controls="controls"> 
                    <source src="jay.mp3"></source>
                </audio>
                        <!-- 添加视频 -->
                <video controls="controls">
                    <source src="b.mp4"></source>
                </video>
          <!-- 7.表格和表单: -->
            <!-- 表格结构:table表格,tr行,td列
			border属性用来设置表格的边框宽度,bordercolor边框的颜色
			width属性用来设置表格的宽度,bgcolor属性用来设置表格的背景色
			cellspacing属性用来设置表格里单元格的间距
			单元格的合并:行合并rowspan/列合并colspan-->
		<table border="2px" width="500px" bgcolor="pink"
			   bordercolor="yellow" cellspacing="0">
			<tr>
				<td colspan="2">11</td>
				<td>13</td>
			</tr>
			<tr>
				<td>21</td>
				<td>22</td>
				<td rowspan="2">23</td>
			</tr>
			<tr>
				<td>31</td>
				<td>32</td>
			</tr>
		</table>
        <!--1.表单:本质就是表格,只有表单才能提交数据(把浏览器输入的数据交给java程序处理)
		form标签专门用来提交数据:form标签 + 必须有submit按钮 + 必须配置name属性
		地址栏: http://127.0.0.1:8848/cgb2108/test3.html?user=jack
		?用来拼接用户输入的数据,user=jack,其中user是给标签配置的name属性的值,jack是用户从          浏览器上输入数据
	    面试题:提价数据的两种方式: get方式 和  post方式
		get方式:被拼接在地址栏,方便看,坏处是不安全,长度受限--不推荐
		post方式:不方便看,好处是安全--推荐!!!
		method属性用来指定数据的提交方式,默认是get
		action属性用来指定数据将要交给哪个程序处理
	-->
		<form method="post" action="#">
			<table>
				<tr>
					<td>用户名:</td>
					<td>
						<input type="text" name="user"/>
					</td>
				</tr>
				<tr>
					<td>密码:</td>
					<td>
						<input type="password" name="pwd" />
					</td>
				</tr>
				<tr>
					<td colspan="2" align="center">
						<input type="submit" />
						<input type="reset" value="重置"/>
					</td>
				</tr>
			</table>
	</body>
</html>

CSS

全称是 层叠样式表 stylesheet ,作用是用来修饰HTML网页.
语法: 选择器{ 属性名:属性值 ; 属性名:属性值 ; 样式3…}
学习重点: 选择器 + 各种属性
位置:
1, 行内CSS:只作用在当前行, 给当前元素使用style属性来修饰样式
html

我是h1


2, 内部CSS: 在HTML代码里使用style标签,包裹着CSS代码.提高了CSS代码的复用性
3, 外部CSS: 把HTML代码和CSS代码分离,在HTML中引入CSS文件

html

选择器

<!-- 用HTML提供的style标签包裹css代码 -->
		<style>
			/* 1.标签名选择器:根据标签的名字选中 */
				div{/*修饰div的样式 */
					color: red;/* 字的颜色 */
				}
				span{/*修饰span的样式 */
					font-size: 30px;/* 字号 字体 */
				}
			/* 2.class选择器:根据class属性的值选中元素,class的值能相同
				步骤:给元素加class属性+通过.获取元素 */
				.a{/* 修饰div1的样式*/
					font-size: 25px;/* 字号 */
				}
				.b{/* 修饰span1的样式 */
					opacity:0.4 ;/* 透明度 0.0~1.0,数值越小越透明 */
				}
			/* 3.id选择器:根据id属性的值选中元素,要求id的值不能相同 
				步骤:给元素添加id属性 + 通过#获取值*/
				#c{/* 练习5:修饰a2的样式 */
					border-style: solid; /* 边框的样式:实线*/
				}
            <style>
			/* 4.分组选择器:把多种选择器的结果,组成一组进行修饰,逗号隔开 */
				/* 利用基本选择器,选中一组元素,修饰样式 */
				.x , #y , input{
					font-size: 20px;
				}
			/* 5.属性选择器:按照标签的属性来选择元素,标签名[属性名]*/
				a[href]{ /* 选中拥有href属性的a标签*/
					font-size: 50px;
				}
		</style>
	</head>
	<body>
		<div class="a">我是div1</div>
		<span class="b">我是span1</span>
		<span>我是span2</span>
		<a class="a b">我是a1</a> <!--同时拥有a b两种样式的修饰-->
		<a id="c">我是a2</a>
         <a href="#" class="x">我是a2</a>
         <input type="text" />
	</body>

盒子模型

CSS认为HTML里的每个元素都是一个盒子.
外边距margin: 盒子间的距离,可以设置左边距,右,上,下
边框border: 盒子的边框,可以设置宽度/颜色/样式
内边距padding: 盒子里的内容和盒子的边框的距离,可以设置左边距,右,上,下
内容: 可以设置 width 和 height

		<!-- 外边距: 单选框是一个盒子    男也是一个盒子   两个盒子的距离margin -->
		<input type="radio" style="margin: 20px;"/><!-- 内边距: 一个盒子里, 内容到边框的距离padding -->
		<input type="text" placeholder="你好" style="padding: 20px;"/>

maven

​ 它是一个Apache的开源项目,主要服务于基于Java平台的项目构建、依赖管理和项目信息管理,maven跨平台项目管理工具,管理,下载,编译jar包

例如:两个项目A B,项目A需要依赖一些jar包,项目B也需要依赖这些jar包,那么此时如果都把jar包引入到项目中,就是在重复造轮子,我们应该把这些所有的jar包放到一个地方,需要用的时候过去取即可。

maven四大特征

仓库:远程仓库(中央仓库),镜像仓库,本地仓库

依赖:pom.xml文件

坐标:唯一定位jar包的方法.

指令:idea中简化了指令操作

IDEA中使用maven工程

下载并解压maven文件,http://maven.apache.org/download.htm

配置conf/setting.xml文件中,本地仓库和远程仓库

创建maven工程 ,在settings工程中配置maven

Bulid,bulid Tools-Maven 1.maven解压的位置 2.解压后maven文件setting文件 3.指定本地库

框架的分类

Spring框架: 整个框架中负责"宏观调控"的(主导),负责整合其它的第三方的框架.
SpringMVC框架: 主要负责实现前后端数据的交互
Mybatis框架/MybatisPlus框架:持久层框架,简化了JDBC操作数据库的方式, 提高效率.
SpringBoot框架/工具: SpringBoot采用了一种更加简化的方式封装了之前的框架.让程序变得更加简单

Springboot

是由Spring团队开发的,整合了Spring框架,SpringMVC框架的所有jar包.其设计目的是用来简化新Spring应用的初始搭建以及开发过程,理解为框架的框架

  • 创建独立的Spring应用程序
  • 嵌入的Tomcat,无需部署WAR文件,SpringBoot项目可以快速启动/关闭,就是像服务器(Tomcat)
  • 简化Maven配置,以前自己找jar包的坐标,现在直接创建springboot工程勾选你要的功能
  • 自动配置Spring

springboot注解:

@SpringBootApplication:包含了@ComponentScan、@Configuration和@EnableAutoConfiguration注解。其中

@ComponentScan:让spring Boot扫描到Configuration类并把它加入到程序上下文。

@Configuration :等同于spring的XML配置文件;使用Java代码可以检查类型安全。

@EnableAutoConfiguration :自动配置。

@ComponentScan :组件扫描,可自动发现和装配一些Bean。

SpringMVC

SpringMVC是Spring框架用来接受请求 + 做出响应,前后端交互的产品,遵循MVC设计模式
MVC设计模式: 最终实现松耦合
M是Model是模型层,用来封装数据
V是View是视图层,用来展示数据
C是Controller是控制层, 接受浏览器发来的请求,并做出数据的响应

工作原理:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XH0fEJst-1623044111711)(RackMultipart20210607-4-13lyxac_html_e58ee630c0df1991.png)]

  1. 用户发送请求到前端控制器DispatcherServlet
  2. 前端控制器收到请求调用HandlerMapper处理器映射器,处理器映射器找到对应的处理器,生成处理器对象和拦截器一并返回给前端控制器DispatcherServler
  3. 前端控制器DispatcherServler调用 HanderAdater处理器适配器,处理器适配器调用具体的后端控制器(controller),contrlloer执行完返回ModelAndView
  4. 前端控制器DispatcherServler调用视图解析器ViewReslover,视图解析器解析后返回View
  5. 前端控制器DispatcherServler根据view进行渲染视图
  6. 前端控制器DispatcherServler响应用户

springMVC注解:

@Controller 标识是一个Controller,Spring包扫描创建实例

@RequestMapping 请求后的映射路径

@PathVariable 标识接收单个参数

@ResponseBody 返回对象利用jackson工具类转换为json字符串

@RequestParam 参数名和请求参数名称不同时使用,可以设置默认值

@RestControllerAdvice 全局异常处理、全局数据绑定、全局数据预处理, 加了Rest就是表示该注解拥有Rest风格,等价于@ControllerAdvice+@ResponseBody,表示该方法返回json数据。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x0xUZCTV-1645145763660)(C:\Users\27588\AppData\Roaming\Typora\typora-user-images\image-20220106141830121.png)]

BeanFactory 和 ApplicationContext 有什么区别

BeanFactory和ApplicationContext都是接口,

并且ApplicationContext是BeanFactory的子接口。

同时它们都是spring的容器对象

作用:

  1. 读取配置文件 或者扫描注解

  2. 创建bean对象,装配到容器(ConCurrentHashMap)ConCurrentHashMap保存bean对象:

​ 情况一: map的key: bean类的名称(类名首字母小写)

​ map的value: bean的对象

​ 情况二:map的key: bean类的class字节码

​ map的value: bean的对象

BeanFactory是Spring中最底层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能。而ApplicationContext是Spring的一个更高级的容器,提供了更多的有用的功能。

ApplicationContext提供的额外的功能:国际化的功能、消息发送、响应机制、统一加载资源的功能、强大的事件机制、对Web应用的支持等等。

加载方式的区别:BeanFactory采用的是延迟加载的形式来注入Bean;ApplicationContext则相反的,它是在Ioc启动时就一次性创建所有的Bean,好处是可以马上发现Spring配置文件中的错误,坏处是造成浪费

Spring两大核心

  1. 怎么理解spring的ioc?
    = IOC:控制反转, 把创建bean对象的权力反转给spring容器管理.
    = spring容器怎么创建bean对象?
    说明: spring容器,这个容器指的就是ConCurrentHashMap
    1.基于配置文件开发spring.
    底层:
    在构建spring容器对象时,通过Dom4j解析spring的配置文件(读文件的过程),得到类的全路径,
    根据反射创建bean对象(class.newInstance(),默认是使用bean的无参数的构造方法)
    向map集合put对象
    方式一: map的key: bean类名首字母小写, map的value: bean对象
    方式二: map的key: bean的class字节码, map的value: bean对象

​ 2.基于注解开发
​ 底层:
​ 在构建spring容器对象时, 通过扫描包下面的注解(@ComponentScan) ,看下那些包下面有注解
​ @Component, @Service, @Repository, 获取到该注解下面的类的全路径,利用反射创建bean对 象(class.newInstance(),默认是使用bean的无参数的构造方法)

@Component
@Scope(“singleton”)
@Lazy(true)// lazy-init=true, 懒加载, false=立即加载
public class Cat {

​ }

IOC

Spring中IOC的XML方式实现

创建类,创建配置文件在resources文件夹位置Spring.xml,配置文件中使用bean标签 id属性是唯一标识,class属性是类的全路径名。

ClassPathXmlApplicationContext 读取配置文件,getBean获取对象

*{"hello",Class.forName("cn.tedu.spring.Hello").newInstance()} scope="prototype" 可以设置bean对象是单例的还是多例的,默认是singleton是单例的*

*<!--IOC:底层就是Map结构,key是id的值,value是根据class的值反射创建的对象 {"student",Class.forName("cn.tedu.spring.Student").newInstance() } -->*

Spring中IOC注解方式(@Component @Service @Controller @Repository)

创建类,使用注解@Component ,配置包扫描,component-scan扫描

 <!--用包扫描的机制,代替以前bean标签的配置
    IOC的本质是底层创建一个Map集合{类名的首字母变小写,类的对象}
    区别:以前一个类必须配置对应的bean标签,
        现在是只要在扫描的范围内而且使用了IOC注解的spring都会new
    base-package:指定包的路径
    包扫描的范围要适当的控制,范围越小扫描的速度越快  -->
    <context:component-scan base-package="cn.tedu.spring"></context:component-scan>

DI依赖注入

当A类想用B类的资源时,直接把B类依赖注入A类中(把B类当做A类的成员变量,加上注解@Autowired,前提必须Ioc)

@Component
class A{
  @Autowired  //di注解,前提是先完成IOC
  B b ;
}
@Component
class B{  }

什么时候需要IOC?

需要把创建对象的权利交给Spring框架管理时

什么时候需要DI?

当A类想用B类的属性或者方法时,直接把B类依赖注入到A类就可以,DI的前提是完成IOC

Spring框架支持以下五种bean的作用域:

singleton : bean在每个Spring ioc 容器中只有一个实例。

prototype:一个bean的定义可以有多个实例。

request:每次http请求都会创建一个bean,该作用域仅在基于web的SpringApplicationContext

情形下有效。

session:在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring

ApplicationContext情形下有效。

global-session:在一个全局的HTTP Session中,一个bean定义对应一个

实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。

注意: 缺省的Spring bean 的作用域是Singleton。使用 prototype 作用域需要慎重的思考,因为频繁创

建和销毁 bean 会带来很大的性能开销。

请谈一下你对IOC/DI的看法(开放)

历史: 传统代码其中的属性对象一般都是通过new关键字手动创建,这样的代码耦合性高,不方便扩展
功能:

  1. IOC: 由Spring容器管理对象的生命周期.
  2. 使得对象与对象之间的耦合性降低.
  3. DI是依赖注入. 只有被spring容器管理的对象才可以被依赖注入. 默认的条件下采用类型注入.如果有特殊需求也可以采用名称注入(@Qualifier(“cat”))
  4. Spring中 IOC和DI相互配合,可以极大程度上降低耦合性.

意识:
Spring由于采用了IOC/DI的设计方式,可以整合其它的第三方框架.使得程序的调用"浑然一体"

AOP

面向切面编程思想:把一些共性代码,提取形成切面,提供丰富的通知,聚焦了程序员的关注点,只关注业务本身,AOP使OOP由原来的二维变为三维了,由平面变成立体了。从技术上来说,AOP基本上是通过代理机制实现的。

切面Aspect:一个类

切点PointCut:触发通知执行方法的时间点

通知Advice:类里的方法

前置通知@Before, 是指调用业务方法前会被执行的功能(适用于权限管理,缓存管理)
后置通知@after, 是指调用业务方法后会被执行的功能(适用于释放资源)
环绕通知@around, 是指调用业务方法的前 后 会被执行的功能(适用于事务管理,性能分析)
返回后通知@afterReturning, 是指调用业务方法并返回了结果后 会被执行的功能
异常通知@afterThrowing, 是指调用业务方法并抛出异常后 会被执行的功能

使用AOP

@Aspect:作用是把当前类标识为一个切面供容器读取

1.添加依赖,类上添加注解@Aspect,2.@Pointcut 切点表达式 3.通知注解如@Before("pointcut()")

  //切点:什么时候要触发通知的功能
                     //方法返回值    包名     类名 方法名(参数列表)
    @Pointcut("execution(* cn.tedu.controller.*.*(..))")
    public void pointcut(){}
  @Before("pointcut()")
    public void beforeMethod(JoinPoint joinPoint){
        //TODO 检查缓存,权限,日志
        System.out.println("我是前置通知~~~");
    }
    //这是后置通知,在调用目标方法后 触发执行
    @After("pointcut()")
    public void afterMethod(JoinPoint joinPoint){
        //TODO 关流 ...
        System.out.println("我是 后置通知~~~");
    }
    //这是环绕通知,在调用目标方法前后 触发执行
    @Around("pointcut()")
    public Object aroundMethod(ProceedingJoinPoint joinPoint)
                throws Throwable {
        //TODO 事务管理 性能分析  日志管理
        //统计每个方法的执行性能
        long start = System.currentTimeMillis();//计时开始
        Object o = joinPoint.proceed();//找到目标方法并执行
        long end = System.currentTimeMillis(); //计时结束
        System.out.println("aop统计的结果表示,方法的耗时是:"+(end-start));
        return o ;//放行,执行目标方法
    }

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

Spring事务

什么是事务?
事务一般是指数据库事务,是指作为一个程序执行单元执行的一系列操作,要么完全执行,要么完全不执行。事务就是判断以结果为导向的标准。
(1)原子性(atomicity)
原子性就是一个不可分割的工作单位。简单地说,就是在日常工作中,老板交给你做一件事情,然后你做到半路做不动了或者说做到最后没做完,那么这个老板就会认为你什么都没有做,做一半的东西不算数,因为老板提前告诉你他只看结果,所以最终没有看到结果,不算是做了。通俗的说就是要么全部完成,要么全部没完成回到起始的地方。
(2)一致性(consistency)
一致性就是事务必须是使一个一致性状态变成另一个一致性状态。比如说我们写一个电商项目,一个用户买商品下单的时候一般都是两步,一步就是选择商品下单,第二步就是把商品在数据库中扣除库存,如果我们买了十件商品,那么数据库中扣除了不是十件商品,或者三件或者四件或者别的,那么这就不满足一致性。
(3)隔离性(isolation)
隔离性就是一个事物的执行不能被另一个事务干扰。还是说我们的电商,一个用户下单之后在减库存的过程中,另一个用户也下单了,他也要减库存,在这个用户还没有减的时候,另一个用户给减掉了,那么库存没有了,这就是事务被另一个事务干扰了。
(4)持久性(durability)
持久性就是一个事务一旦被提交,它对数据库中数据的改变就应该是永久性的。就是说我们在执行一个sql语句之后还没有被提交,这是系统宕机了,那么数据并没有被保存下来,但是当我们把数据提交之后,存在了文件中,这时不管怎么样我们的数据都不会遭到破坏。

spring 控制事务

spring中提供了注解@Transactional 用来控制事务,常见业务 增/删/改 一般需要事务控制,查询一般不需要控制可以控制只读属性 @Transactional (readOnly =true)

规则:

  1. Spring 中的事务注解,默认条件下只处理运行时异常,遇到检查时异常,事务控制没有效果

  2. 注解的属性控制:

    @Transactional (rollbackFor = IOException.class), 遇到某种IO 异常实现事务的回滚 noRollbackFor = 遇到某种异常事务不回滚. readOnly = true 该操作是只读的,不能修改. 公告/合同等

配置文件说明

properties语法说明

  1. 数据结构 key=value
  2. value中前后不要有空格
  3. properties文件 程序默认读取采用ISO-8859-1编码结构 中文必定乱码.
  4. pro文件中key的名称不能复用.

YML语法说明

  1. 数据结构 key:(空格)value
  2. key的关键字有层级缩进效果, 注意缩进.
  3. YML文件默认采用UTF-8编码格式 所以对中文友好.
  4. value中不要有多余的空格
  5. YML文件中key的前缀可以复用. 注意层级

SpringmMVC中为属性动态赋值

yml

1.key,value均在application.yml文件中配置 name: wangwu

2.spel表达式 作用从容器中获取数据需要指定key ,@Value${name}

properties

YML文件是Spring的核心配置文件. 主要的目的是为了整合第三方框架而使用的. 如果将
大量的业务数据写到YML文件中,则会导致代码结构混乱. 可以使用pro文件实现业务数据处理.

1.key,value均在application.yml文件中配置 name=wangwu

2.spel表达式 作用从容器中获取数据需要指定key ,@Value${name}

3.该属性是pro文件中,必须要求spring容器加载pro文件,在类上加注解@PropertySource

4.@PropertySource(value = "classpath:/name.properties",encoding = "UTF-8")

作用:加载指定配置文件,并且设定字符集编码格式

Spring容器管理自定义对象

1.使用注解@Configuration标识该类是一个配置类 ,是一块区域,在区域中编辑自定义对象

@Bean修饰的方法,将方法名当做key–user,将返回值对象当做值value , 根据Key-value 存储到Map集合中 交给Spring容器管理

2.在Test文件中类上使用注解@SpringBootTest只要在该类中 执行@Test测试方法,则就会启动Spring容器*,@Test修饰方法

javaweb三大组件: Servlet, Filter, Listener

Servlet作用: 接收请求和响应数据到客户端.
Filter作用: 拦截请求和响应.场景: 处理全局的中文乱码问题,权限校验,加载配置文件等操作
实现拦截资源: 静态资源(html, css, js, 图片等等)和动态资源(后台与客户端交互的类,比如: controller)都会拦截
配置拦截路径: 1. /资源名称 2. /* 拦截所有资源 3. .扩展名, 比如: .html,
Listener作用: 监听对象状态的改变,以及加载配置文件.

Servlet特点,线程是否安全

  • Servlet是单例的(Servlet对象在内存中只有一个,所有请求访问的是同一个servlet)

  • Serlet是线程不安全的.不能避免,尽量减少安全问题
    在servlet里面尽量避免定义成员变量.

  • 说明: 凡是单例对象,尽量减少安全问题, 在单列类避免定义成员变量.

    servlet和springmvc什么关系?

  1. springmvc底层: 封装的servlet

  2. springmvc中央控制器: 就是一个servlet,分发浏览器的请求.

Servlet是安全的吗?

  • 是线程不安全的,因为servlet是单例模式,当多个客户端共同访问的时候线程不安全。
    尽量用局部变量,同步块,如果当前字段是不会改变的,用final修饰

Filter过滤器作用是什么, 后面学习的springmvc有什么关系, 和springmvc的拦截器的区别?

自定义过滤器: 实现Filter接口,
有个方法doFilter(req, response): 拦截请求和响应,放行: Filterchain对象放行方法
SpringMVC大量用到了过滤器,
比如: CharacterEncodingFilter, 处理乱码问题,以及加载springmvc的配置文件.
过滤器和springmvc的拦截器有关系有什么区别?
过滤器是web是三大组件之一,基于web开发环境(web项目).
springmvc的拦截器和过滤器没有关系,拦截器实现基于代理(默认jdk动态代理)方式实现的.
2.4 Listener的分类, 作用是什么,后期那些框架里面用到了监听器?
自定义一个监听器: 实现Listener接口
监听器:分为三大类,八个监听器.
监听器: 在spring框架里面,springmvc框架里面使用,springboot框架里面.
*比如: spring框架,使用ContextLoaderListener在web服务器启动时,读取spring配置文件,初始化bean对象,装配到spring容器中(ConCurrentHashMap)

过滤器有哪些作用,以及过滤器的生命周期

  • 生命周期:每个Filter在tomcat启动时进行初始化,每个Filter只有一个实例对象
    (1). Init:在服务器启动时会创建Filter实例
    (2). doFilto:这个方法会在用户每次访问“目标资源”时执行
    (3). destroy():服务器关闭时销毁Filter对象
  • 作用:
    (1). 验证客户是否来自可信网络
    (2). 对客户提交的数据进行重新编码
    (3). 过滤掉客户的某些不应该出现的词汇
    (4). 验证用户是否可以登录
    (5). 验证客户的浏览器是否支持当前的应用
    (6). 记录系统日志

MyBatis框架

Mybatis是一个基于Java的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO为数据库中的记录。

总结: MyBatis 是一款优秀的持久层框架,利用ORM思想实现了数据库持久化操作.
补充说明: 也有人把mybatis称之为半自动化的ORM映射框架.

ORM思想

对象关系映射,是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换,

关系映射:
对象映射-------映射表
对象中有属性-------表中有字段

使用MyBatis

添加mybatis依赖

  <!--mybatis依赖包-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>
  1. 在resource文件下,创建mybatis-config.xml文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <!--核心配置文件-->
    <configuration>
        <!--环境配置标签
            mybatis支持多个数据源的配置,default="默认连接数据源"-->
        <environments default="development">
            <!--编辑开发环境-->
            <environment id="development">
                <!--mybatis 采用jdbc的方式控制数据库事务. -->
                <transactionManager type="JDBC"/>
                <!--
                    type="POOLED" 创建一个数据源链接池,每次从池中获取链接.
                 -->
                <dataSource type="POOLED">
                    <!--版本信息:
                        如果数据源采用5.x的版本  value:com.mysql.jdbc.Driver
                        如果数据源采用8.x的版本  value:com.mysql.cj.jdbc.Driver
                    -->
                    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://127.0.0.1:3306/jt?serverTimezone=GMT%2B8&amp;useUnicode=true&amp;characterEncoding=utf8&amp;autoReconnect=true&amp;allowMultiQueries=true"/>
                    <property name="username" value="root"/>
                    <property name="password" value="root"/>
                </dataSource>
            </environment>
        </environments>
        <!--Mybatis加载Mapper映射文件-->
        <mappers>
            <mapper resource="mybatis/mappers/UserMapper.xml"/>
        </mappers>
    </configuration>
    
    

    3.编辑Usermapper接口

    public interface Usermapper{
     	public List<User> findAll();
       // @Param 将参数封装为map,变成单值传递
        List<User> findParam(@Param("minAge") int minAge,
                             @Param("maxAge") int maxAge);
        
    }
    

    4.编辑Mapper的映射文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!--namespace是mybaits映射文件的唯一标识,与接口对应-->
    <mapper namespace="com.jt.mapper.UserMapper">
        <!--
            1.标签说明:
                   1.查询操作  select标签
                   2.新增操作  insert标签
            2.select标签介绍:
                1. id属性 必须与方法名称一致.
                2. resultType 返回值的pojo类型
            3.sql:全部小写  ctrl + shift + y
            4.转义说明:xml文件中个别需要转义
    		 大于 >  &gt;  | 小于 > &lt;  | 与号  &  &amp;
    		转义标签   <![CDATA[ 需要转义内容]]>
    	    5.mybatis取值语法: #{属性名}
    	    6.别名在mybatis-config.xml文件中配置
    		<typeAliases>
            type:  POJO对象的路径  alias: 为当前的POJO对象起的别名
            <typeAlias type="com.jt.pojo.User" alias="User"/>
            设置别名包 
            <package name="com.jt.pojo"/>
            </typeAliases> 
    -->
           <!--
          如果配置了别名包: 则映射时会自动的拼接包路径
          include refid="引用SqlID"
         -->
          <!-- 7.mybatis的集合操作
            知识点: 如果遇到集合参数传递,需要将集合遍历
            标签: foreach 循环遍历集合
            标签属性说明:
                1.collection 表示遍历的集合类型
                    1.1 数组      关键字 array
                    1.2 List集合  关键字 list
                    1.3 Map集合   关键字 Map中的key
                2. open  循环开始标签
                   close 循环结束标签  包裹循环体
                3. separator 分割符
                4. item  当前循环遍历的数据的变量
          -->
        <!-- 8.动态Sql语句
            核心思想: 自动判断是否为null,
                    如果为null,该字段不参与sql
            动态Sql规则:
                1.  <if test="写判断条件,可以直接获取属性值"></if>
                        true: 会拼接 字段条件
                        false: 不会拼接字段条件
                2. 多余的关键字
                    由于动态sql拼接必然会导致多余的and 或者 or
                3. where标签说明 可以去除 where后边多余的and 或者 or
        -->
        <select id="findSqlWhere" resultType="User">
            select * from demo_user
                <where>
                    <if test="id != null"> id = #{id}</if>
                    <if test="name != null">and name = #{name}</if>
                    <if test="age != null ">and age  = #{age}</if>
                    <if test="sex != null ">and sex  = #{sex}</if>
                </where>
        </select>
    	<!--
            set标签用法: 去除set条件中多余的,号
        -->
        <update id="updateSqlSet">
            update demo_user
                <set>
                    <if test="name !=null"> name=#{name}, </if>
                    <if test="age !=null">  age = #{age}, </if>
                    <if test="sex !=null">  sex = #{sex} </if>
                </set>
                where id = #{id}
        </update>
        
        <!--
            resultType:
                要求: 对象的属性名称与表中的字段一一对应.
                对象: User(id,name,age,sex)
                表:   demo_user(id,name,age,sex)
                总结: resultType适合单表映射,并且属性名称一致.
    
            resultMap:
                 功能: 如果发现表中的字段与属性名称不一致时,使用resultMap映射
                 对象: Dog(dogId,dogName)
                 表:   dog(dog_id,dog_name) 属性不匹配.所以映射失败
        -->
        <select id="findAll" resultMap="dogRM">
            select * from dog
        </select>
        <!--
            id: rm的唯一标识   type:封装之后的POJO对象
        -->
        <resultMap id="dogRM" type="com.jt.pojo.Dog">
            <!--1.标识主键-->
            <id column="dog_id" property="dogId"/>
            <!--2.映射其它属性-->
            <result column="dog_name" property="dogName"/>
        </resultMap>
    
        <select id="findUser" resultType="User">
            select <include refid="user_cloumn"/>  from demo_user
        </select>
        <!--简化Sql-->
        <sql id="user_cloumn">
            id,name,age,sex
        </sql>
        	
        
        <select id="findIn" resultType="User">
            select * from demo_user where id in
                <foreach collection="array" open="(" close=")"
                    separator="," item="id">
                    #{id}
                </foreach>
        </select>
    
        <select id="findAll" resultType="com.jt.pojo.User">
            select * from demo_user
        </select>
    </mapper>
    

    5.编辑MyBatis测试类

    class SpringbootDemo2ApplicationTests {
        /**
         * 核心问题: SqlSession 理解为Mybatis操作数据的连接
         * 1. 通过 SqlSessionFactoryBuilder 构建SqlSessionFactory
         */
        @Test
        public void testMybatis01() throws IOException {
            //1.定义核心配置文件的路径
            String resource = "mybatis/mybatis-config.xml";
            //2.通过工具API读取资源文件
            InputStream inputStream = Resources.getResourceAsStream(resource);
            //3.构建SqlSessionFactory
            SqlSessionFactory sqlSessionFactory =
                        new SqlSessionFactoryBuilder().build(inputStream);
            //4.获取SqlSession
            SqlSession sqlSession = sqlSessionFactory.openSession(true);//开启自动提交
            //5.获取Mapper的接口对象
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            //6.调用接口方法 获取返回值数据
            List<User> userList = userMapper.findAll();
            System.out.println(userList);
            //7.连接用完记得关闭
            sqlSession.close();
           // sqlSession.commit(); 新增和删除注意提交事务
           
        }    }
    

关于Mybatis的注解开发说明

要在mybatis-config.xml文件配置

<!-- 使用注解时 mybatis需要管理mapper的接口 -->
        <mapper class="com.jt.mapper.UserAnnoMapper"></mapper>
  1. 注解开发 只适用于 单表CURD操作. 多表操作一定出问题
  2. 如果设计到复杂标签时 where/set/foreach 等标签时,不可以使用注解.
  3. 所以应该熟练掌握xml映射文件的写法,注解开发只是辅助的作用.

MyBatis关联查询

 <!-- 一对一关联查询 
 	 resultMap多表关联查询
 -->
    <select id="findAll" resultMap="empRM">
        select e.emp_id,e.emp_name,
               d.dept_id,d.dept_name
	    from emp e,dept d
	    where e.dept_id = d.dept_id
    </select>

    <!--3.完成一对一封装 
        固定用法:
            1.association: 将结果集封装为单独的对象 dept
            2.property 需要封装的属性名称
            3.javaType 固定写法: 属性的类型
    -->
    <resultMap id="empRM" type="Emp">
        <!--1.主键字段 -->
        <id property="empId" column="emp_id"></id>
        <!--2.映射其它属性字段-->
        <result property="empName" column="emp_name"></result>
        <association property="dept" javaType="Dept">
            <!--3.完成dept对象的封装-->
            <id property="deptId" column="dept_id"/>
            <result property="deptName" column="dept_name"/>
        </association>
    </resultMap>

resultMap 万能的数据封装的结构体系
一对一: association property=“dept” javaType=“Dept”
一对多: collection property=“emps” ofType=“Emp”

MyBatis开启驼峰命名规则

要在mybatis-config.xml文件配置

  <!--配置settings信息-->
    <settings>
        <!--开启驼峰映射规则-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    
     <!--查询用户信息  开启驼峰映射规则
        resultType:
                1.适用与单表查询,同时要求属性名称与字段相同.
                2.如果属性与字段满足驼峰命名规则,开启驼峰映射之后,
                  可以使用resultType
        resultMap:
                1.如果字段不一致时使用
                2.多表关联查询时使用.
                3.如果开启了驼峰映射规则, 则自动映射的属性可以省略,最好标识主键
                4.如果使用驼峰规则映射时,需要映射封装对象时(一对一/一对多),默认条件下.驼峰规则失效.
                  可以使用: autoMapping="true" 要求开启驼峰映射.
                5.默认条件下 一对一,一对多不会自动完成驼峰规则映射.
                  需要配置 autoMapping="true"才能自动映射
     -->

MyBatis 缓存机制

**一级缓存 ** sqlSession级别

在同一个sqlSession 内部,执行多次查询方法,缓存有效,只查询一次,一级缓存默认开启,可以直接使用

二级缓存 sqlSessionFactory 级别

利用同一个sqlSessionFactory工厂,创建不同的sqlSession可以实现数据的共享(缓存机制),二级缓存默认开启的,使用时需要使用< cache/>标签

使用缓存注意事项:
  1. 使用一级/二级缓存时:实体类(pojo)必须实现序列换接口,否则缓存不生效
  2. 使用二级缓存时:第一个sqlSession使用后必须关闭,否则二级缓存不生效
测试缓存
 //一级缓存
 @Test
    public void testCache(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
        List<Emp> list1 = empMapper.getAll();
        List<Emp> list2 = empMapper.getAll();
        List<Emp> list3 = empMapper.getAll();
        sqlSession.close();
    } 
  //二级缓存(使用二级缓存时,要在自己的映射文件中 使用<cache/>标签)
    @Test
    public void testCache2(){
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        EmpMapper empMapper = sqlSession1.getMapper(EmpMapper.class);
        List<Emp> list1 = empMapper.getAll();
        sqlSession1.close(); //sqlSession使用之后必须关闭.否则二级缓存不生效.
        //利用同一个SqlSessionFactory 创建不同的SqlSession
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        EmpMapper empMapper2 = sqlSession2.getMapper(EmpMapper.class);
        List<Emp> list2 = empMapper2.getAll();
        sqlSession2.close();
    }

SpringBoot整合MyBatis

1.编辑配置文件 application.yml

#配置端口号
server:
  port: 8090

#管理数据源
spring:
  datasource:
    #高版本驱动使用
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/jt?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
    #设定用户名和密码
    username: root
    password: root

#SpringBoot整合Mybatis
mybatis:
  #指定别名包
  type-aliases-package: com.jt.pojo
  #扫描指定路径下的映射文件
  mapper-locations: classpath:/mybatis/mappers/*.xml
  #开启驼峰映射
  configuration:
    map-underscore-to-camel-case: true

2.将Mapper接口交给Spring容器管理

// 第一方式  Mapper接口上加上@Mapper
@Mapper //将Mapper接口交给Spring容器管理.
public interface UserMapper {
    List<User> findAll();
}

//第二方式,在启动类上加上@MapperScan
@SpringBootApplication
//按照指定的包路径,扫描mapper的接口管理对象
@MapperScan("com.jt.mapper")
public class SpringbootSsmApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootSsmApplication.class, args);
    }
}

3.SpringBoot整合sqlSession

借助Spring容器管理Mybatis,封装了sqlSession底层实现,然后DI注入接口代理对象,完成业务逻辑

代理接口

将接口交给Spring容器管理,spring会根据class类型,动态的选择创建代理对象. 最终将代理对象交给spring容器管理.

代理方式介绍

JDK动态代理
  1. 要求别代理者,必须有接口
  2. 默认条件下如果有接口,则使用JDK代理
CGLIB动态代理
  1. 不管代理者是否有接口,都可以为其创建代理对象
  2. 代理对象是目标对象的子类
总结 :

Spring中如果有接口,默认使用JDK动态代理,如果没有接口,则使用CGLIB动态代理

Spring5 以后,自身接口对象创建代理对象时,使用CGLIB

MyBatis 使用的是JDK代理创建对象

MyBatis中#和$的区别?

#将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。如:order by #user_id#,如果传入的值是111,那么解析成sql时的值为order by “111”, 如果传入的值是id,则解析成的sql为order by “id”.
  
\2. $将传入的数据直接显示生成在sql中。如:order by u s e r i d user_id userid,如果传入的值是111,那么解析成sql时的值为order by user_id, 如果传入的值是id,则解析成的sql为order by id.
  
\3. #方式能够很大程度防止sql注入。
  
4.$方式无法防止Sql注入。

5.KaTeX parse error: Expected 'EOF', got '#' at position 32: …传入表名.    6.一般能用#̲的就别用.

Mybatis-Plus

在Mybatis的基础上只做增强不做改变,为简化开发,提高效率而生

使用MP的流程

1.在pom文件中,删除MyBatis的包,添加 Mybatis-Plus,内部包含了 Mybatis的包
		<dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
        </dependency>
2.在POJO对象上,添加注解,继承BasePojo
    @TableName("demo_user")       //对象与表一一对应 如果对象名和表名一致则表名可以省略
    @TableId(type = IdType.AUTO)  //主键自增
    @TableField(value = "name")   //如果名称与属性一致则注解可以省略
    @TableField(exist = false)  //属性不是表字
3.Mapper接口需要继承 BaseMapper<User> 注意必须添加泛型   
        public interface UserMapper extends BaseMapper<User> {
            List<User> findAll();      }
4.修改YML配置文件  
# mybatis:
mybatis-plus:

MP工作原理

实质: MP动态生成Sql语句.
铺垫:

  1. insert into 表名(字段名…) value (属性值…)

工作原理:

  1. 用户执行userMapper.insert(user); 操作
  2. 根据UserMapper的接口找到父级BaseMapper.根据BaseMapper的接口查找泛型对象User.
  3. 根据User.class 获取注解@TableName(“demo_user”),获取表名
  4. 根据User.class 获取所有的属性,根据属性获取指定的注解@TableField(value = “name”),获取字段名称
  5. 根据属性获取属性的值.之后动态拼接成Sql语句
  6. 将生成的Sql交给Mybatis执行入库操作.
  7. insert into demo_user(id,name,age,sex) value (null,xx,xx,xx)

MP使用特点: 根据其中不为null的属性进行业务操作!!!

@SpringBootTest //Spring专门为测试准备的注解  启动了Spring容器
class JtApplicationTests {
    @Autowired
    private UserMapper userMapper;
    /**
     *  MP新增操作测试
     *  思想: 全自动的ORM  映射是自动的, sql自动生成的
     */
    @Test
    public void insertUser(){
        User user = new User(null,"猴子",18, "男");
        userMapper.insert(user);
        System.out.println("新增入库成功!!!!");
    }
    /*关于MP编码的思想:
          根据其中不为null的属性进行业务操作!!!! */
    //1.根据Id查询用户  id=1的用户
    @Test
    public void testFindById(){
        User user = userMapper.selectById(1);
        System.out.println(user);
    }
    //1.查询name="黑熊精"的数据
    //QueryWrapper条件构造器 动态拼接where条件的
    //select * from demo_user where name=#{xxx}
    @Test
    public void testFindByName(){
        User user = new User();
        user.setName("黑熊精");
        QueryWrapper queryWrapper = new QueryWrapper(user);
        List<User> userList = userMapper.selectList(queryWrapper);
        System.out.println(userList);
    }
    //1.查询age > 18岁的用户   age=18!!!!
    // > gt, < lt, = eq, >= ge, <= le, <> ne
    @Test
    public void testFindByAge(){
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.gt("age",18);
        List<User> userList = userMapper.selectList(queryWrapper);
        System.out.println(userList);
    }
    //1.查询age > 18岁的并且 name 包含"乔"的用户
    //select * from demo_user where age > 18 and name like "%乔%"
    //select * from demo_user where age > 18 and name like "%乔"
    //条件构造器如果多个条件时,默认使用and
    @Test
    public void testFindByLike(){
        QueryWrapper<User> queryWrapper = new QueryWrapper();
        queryWrapper.gt("age",18)
                    .likeLeft("name","乔");
        List<User> userList = userMapper.selectList(queryWrapper);
        System.out.println(userList);
    }
    /*查询id=1,3,4,6,7的数据,并且根据年龄降序排列
      Sql: select * from demo_user where id in (1,3,4,6,7)
           order by age desc*/
    @Test
    public void testFindByOrder(){
        Integer[] ids = {1,3,4,6,7};
        QueryWrapper<User> queryWrapper = new QueryWrapper();
        queryWrapper.in("id",ids)
                    .orderByDesc("age", "sex");
        List<User> userList = userMapper.selectList(queryWrapper);
        System.out.println(userList);
    }
    /*动态sql的查询
      根据name/sex查询数据. 但是name/sex可能为null
      Sql: select * from demo_user where name=xxx and sex=xxx
      关于condition的说明:
            condition=true   负责拼接条件
            condition=false  不拼接条件*/
    @Test
    public void testFindByIf(){
        String name = null;
        String sex = "女";
        QueryWrapper<User> queryWrapper = new QueryWrapper();
        //判断字符串是否为null
        boolean nameFlag = StringUtils.hasLength(name);
        boolean sexFlag = StringUtils.hasLength(sex);
        //动态Sql的写法
        queryWrapper.eq(nameFlag,"name",name)
                    .eq(sexFlag,"sex",sex);
        List<User> userList = userMapper.selectList(queryWrapper);
        System.out.println(userList);
    }
    /* 需求: 动态查询主键字段(第一列数据) 适用范围 进行关联查询时使用.
       Sql: select id from demo_user
       设计: 关联查询 修改为 多张单表查询*/
    @Test
    public void testFindByObjs(){
        List<Object> idList = userMapper.selectObjs(null);
        System.out.println(idList);
    }
}

VUE

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>点击事件操作</title>
	</head>
	<body>
		<div id="app">
			<!-- 1.点击事件的案例
				  1.事件绑定使用基本语法  v-on:
				  2.简化写法   v-on: 使用@符号代替
			 -->
            <!-- 2.利用差值表达式 获取数据
			{{msg}}
-->
            
			<!-- 3.双向数据绑定的规则:v-model
				1. data中的数据变化,则页面数据变化.
				2. 页面数据变化,则data中的数据变化
			 -->
			
			
			<button @click="addNum">自增</button>
		</div>
		
		<script src="../js/vue.js"></script>
		<script>
			/*  
			3.JS中的变量名称
				1. var 最常规的变量声明,没有作用域
				2. let 代替var 有作用域
				3. const 定义常量
			 */
			const app = new Vue({
				el: "#app",
				data: {
					num: 100  
				}
			})
		</script>
	</body>
</html>

vue基础语法

		div id="app">
			<h1>遍历数组对象</h1>
			<p v-for="user in list">
				id号: {{user.id}} | 名称: {{user.name}}
			</p>
			<h1>分支结构测试</h1>
			请输入分数: <input type="text" v-model="score" />
			<!-- 判断原则: 如果判断为true 则展现标签 -->
			<p v-if="score >= 90">优秀</p>
			<p v-else-if="score >= 80">良好</p>
			<p v-else>再接再厉</p>
		</div>
		<script src="../js/vue.js"></script>
		<script>
			const app = new Vue({
				el: "#app",
				data: {
				score: '',
				list: [
						{id: 1, name: "张三"},
						{id: 2, name: "李四"},
						{id: 3, name: "王五"}
					]
				}})
		</script>

双向数据绑定的原理

v-model 双向数据绑定

<input type="text" v-model="msg"/>

  1. data中数据变化,则页面数据变化
  2. 页面数据变化,则data中数据变化
设计模型:MVVM

M : model代表数据,大概率事件,代表data中的数据

VM :ViewModel 视图模型层,在内部计算数据,在页面上展现数据

v :View 视图层 用户看到的页面展示的效果

内存原理

虚拟DOM是实现数据实时更新的重要组件,内存数据变化,则页面数据实时变化

DOM:JS中所有页面元素对象的统称

vue声明周期函数

第一类:对象的初始化阶段

beforeCreate created beforeMount mounted

第二类:数据修改阶段

beforeUpdate updated

第三类:对象的销毁阶段

befoDestroy destroyed

初始化阶段

1.beforeCreate 创建了一个VUE对象,只是还没有加载其中的属性,只是一个空对象

2.created VUE对象开始填充数据,表示配置完成

3.beforeMount 对象根据已经配置的内容,在指定的区域开始加载数据(属性的值,保存到内存中)

4.mounted 在指定的区域中,渲染页面(为页面填充数据),页面初始化完成

远程调用

什么是跨域

浏览器解析Ajax时,要求浏览器的网址,与Ajax请求网址,必须满足三要素:

端口号相同,域名相同,协议相同

三要素满足则叫做同域请求,有一项不满足,则叫做跨域请求

解决跨域问题

在后端代码上加上注解@CrossOrigin

HTML5中模板字符串

语法:` `(两个反引号)
1.保留代码片段格式
2.可以动态取值 ${}

页面参数说明

前端JS对象,进过浏览器解析发起Ajax请求是,JS对象被解析为JSON串

前端和后端交互的媒介:HTTP协议 协议中要求传递字符串

前端传递的是JSON串,后端不可以使用对象接受需要使用注解@RequestBody

@RequestBodyJSON串转化成对象

@ResponseBody对象可以转成JSON串

Get和Post请求方式的区别?

  • get地址栏有参数显示 post不会再地址栏显示参数(参数是放在了请求体)
  • get不安全 post相对安全
  • get限制大小 post理论上不限制

京淘项目技术点

MD5加密算法

只支持密明文转换成密文,不可以反编译

String password = user.getPassword();
//2.利用md5加密算法 进行加密
String md5Pass = DigestUtils.md5DigestAsHex(password.getBytes());
user.setPassword(md5Pass);
破解MD5加密算法

不能破解,一般通过穷举字符的方式,创建对应明文的数据库,反向查询数据库

秘钥Token

一般在登录认证系统中,都会返回秘钥信息,来作为用户登录的凭证,秘钥的特点:最好独一无二

动态生成秘钥:UUID

 //说明: 用户名和密码正确,返回秘钥
  String uuid = UUID.randomUUID().toString().replace("-","");
  return uuid;

保存会话数据的两种技术Session和Cookie

cookie数据保存在客户端,session数据保存在服务端

Session 会话控制技术

Session 生命周期:会话结束,对象销毁

Session数据存储 在内存中

Session 只能临时存储,不能永久存储

Cookie 小型文本文件

Cookie 文件通常加密

Cookie 可以临时存储,也可以永久存储

会话

用户打开浏览器,点击超链接,访问web资源,直到用户关闭浏览器,整个过程称为一个会话

路由导航守卫

拦截器策略:

1.如果用户访问/login登录页面 直接放行

2.如果访问其它页面,则校验是否有token

​ 有token 放行 没有token 跳转到登录页面

/*  参数说明:
 *    1.to 到哪里去
 *    2.from 从哪里来
 *    3.next 请求放行*/
router.beforeEach((to,from,next) => {
  if(to.path === '/login') return next()
  //获取token数据信息
  let token = window.sessionStorage.getItem('token')
  if(token === null || token === ''){
     return next("/login")
  }
  //放行请求
  next()
})

左侧三级菜单展现

原理说明: 左侧菜单划分为3级.但是显示时只显示2级.
一级菜单获取: select * from rights where parent_id = 0
二级菜单获取: select * from rights where parent_id = 一级ID
三级菜单获取: select * from rights where parent_id = 二级ID

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mk6mb3rg-1645145763662)(C:\Users\27588\AppData\Roaming\Typora\typora-user-images\image-20220118164347186.png)]

​ 思路: 获取1-2级的菜单信息.

  • 一级菜单: parent_id = 0
  • 二级菜单: parent_id = 一级ID
  • 将二级菜单数据,封装到一级菜单的children属性
  • 方案二: 通过关联查询,利用mybatis实现数据一对多的封装.
<mapper namespace="com.jt.mapper.RightsMapper">
    <select id="getRightsList" resultMap="rightsRM">
       select p.id,p.name,p.parent_id,p.path,p.level,p.created,p.updated,
       c.id c_id,c.name c_name,c.parent_id c_parent_id,c.path c_path,
       c.level c_level,c.created c_created,c.updated c_updated
	    from  rights p
	    left join   rights c
	    on  c.parent_id = p.id
        where p.parent_id = 0
    </select>
    <resultMap id="rightsRM" type="Rights" autoMapping="true">
        <id column="id" property="id"/>
        <!--一对一封装子级菜单List集合-->
        <collection property="children" ofType="Rights">
            <!--封装主键ID-->
            <id column="c_id" property="id"/>
            <result column="c_name" property="name"/>
            <result column="c_parent_id" property="parentId"/>
            <result column="c_path" property="path"/>
            <result column="c_level" property="level"/>
            <result column="c_created" property="created"/>
            <result column="c_updated" property="updated"/>
        </collection>
    </resultMap>
</mapper>

Ajax 同步和异步的区别

Ajax 特点:同步刷新 异步访问

异步访问的核心组件是Ajax引擎,默认async:true 异步 | async:false 同步

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TkXk1jKo-1645145763662)(C:\Users\27588\AppData\Roaming\Typora\typora-user-images\image-20220118171145312.png)]

**异步处理:**我们通过事件触发 ajax,请求服务器,在这个期间无论服务器有没有响应,客户端的其他代码一样可以运行。异步不必等待,一般需要监听异步的结果

**同步处理:**我们通过事件触发 ajax,请求服务器,在这个期间等待服务器处理请求, 在这个期间客户端不能做任何处理。当 ajax 执行完毕才会继续执行其他代码。

同步是在一条直线上的队列,异步不在一个队列上 各走各的

用户列表分页查询

要求查询 1页10条
特点: 数组的结果 口诀: 含头不含尾
语 法: select * from user limit 起始位置,查询的条数
第一页: select * from user limit 0,10 0-9
第二页: select * from user limit 10,10 10-19
第三页: select * from user limit 20,10 20-29
第N页: select * from user limit (n-1)*10,10

方案一:

@Override
    public PageResult getUserList(PageResult pageResult) {
        //1.记录总数 total
        long total = userMapper.getTotal();
        //2.分页后的数据
        int size = pageResult.getPageSize();
        int start = (pageResult.getPageNum() - 1) * size;
        List<User> rows = userMapper.findUserListByPage(start,size);
        return pageResult.setTotal(total).setRows(rows);
    }
UserMapper
	@Select("select * from user limit #{start},#{size}")
    List<User> findUserListByPage(@Param("start") int start,@Param("size") int size);

方案二:

	 <!--利用子查询的方式实现数据获取
        1.查询主表信息
     -->
    <select id="getRightsList" resultMap="rightsRM">
        select * from rights where parent_id = 0
    </select>
    <resultMap id="rightsRM" type="Rights" autoMapping="true">
        <!--主键信息-->
        <id property="id" column="id"></id>
        <collection property="children" ofType="Rights"
                    select="findChildren" column="id"/>
    </resultMap>
    <select id="findChildren" resultType="Rights">
        select * from rights where parent_id = #{id}
    </select>

作用域插槽

一般在表格数据展现时,可以动态获取当前行对象.
用法:
1.template
2.slot-scope属性=“变量”

 		   <el-table-column prop="status" label="状态">
              <!-- <template slot-scope="scope">
                  {{scope.row.status}}
              </template> -->
             <template slot-scope="scope">
                <el-switch v-model="scope.row.status" @change="updateStatus(scope.row)"
                  active-color="#13ce66" inactive-color="#ff4949">
                </el-switch>
             </template>
           </el-table-column>

常规请求方式 get/delete ?key=value&key2=value2
post/put data: JS对象 后端接收@RequestBody
restFul风格 /url/arg1/arg2/arg3 使用对象接收 或者 @PathVariable 接受单个参数

Spring 全局异常处理机制

  1. 后端业务执行时,无法保证 业务运行不出错. 所以一般添加try-catch进行异常的捕获.
  2. 捕获异常是为了按照特定的要求 返回用户可以识别的有效数据.
  3. 如果添加了try-catch 则会对代码的可读性造成影响.
  4. 如果通过try-catch控制异常,则所有的controller方法都必须添加, 导致代码繁琐.
解决问题: AOP 在代码结构上实现松耦合 @RestControllerAdvice

加了Rest就是表示该注解拥有Rest风格,等价于@ControllerAdvice+@ResponseBody,表示该方法返回json数据。 @ControllerAdvice是@Controller的一个增强版,可以实现三个方面的功能:全局异常处理、全局数据绑定、全局数据预处理。 这是SpringMVC提供的功能,简化了我们很多工作

新建包 config , 类 SystemException 统一处理异常

package com.jt.config;
import com.jt.vo.SysResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
//定义全局异常处理机制 内部封装了AOP
@RestControllerAdvice   //标识Controller层 返回值JSON串  
public class SystemException {
    /* 业务说明: 后端服务器报错
       返回值:   SysResult对象(status=201)
       异常类型:  运行时异常*/
    @ExceptionHandler(value = RuntimeException.class)
    public SysResult fail(Exception e){
        //输出异常
        e.printStackTrace();
        return SysResult.fail();
    }
}

三级商品菜单查询

方案一:

@Service
public class ItemCatServiceImpl implements ItemCatService{
    @Autowired
    private ItemCatMapper itemCatMapper;
    
    @Override
    public List<ItemCat> findItemCatList(Integer level) {
        //1.查询一级菜单
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.eq("parent_id",0);
        List<ItemCat> oneList = itemCatMapper.selectList(queryWrapper);
        //2.查询二级菜单 二级数据是一级数据的子级 封装到一级数据中.
        for(ItemCat oneItemCat : oneList){
            int oneId = oneItemCat.getId(); //一级对象ID
            //清空原始条件  必须有
            queryWrapper.clear();
            queryWrapper.eq("parent_id",oneId);
            List<ItemCat> twoList = itemCatMapper.selectList(queryWrapper);
            for(ItemCat twoItemCat : twoList){
                //获取二级分类ID
                int twoId = twoItemCat.getId();
                //查询三级列表信息
                queryWrapper.clear();
                queryWrapper.eq("parent_id",twoId);
                List<ItemCat> threeList = itemCatMapper.selectList(queryWrapper);
                //将三级列表 封装到二级对象中
                twoItemCat.setChildren(threeList);
            }
            //将二级数据封装到一级对象中
            oneItemCat.setChildren(twoList);
        }
        return oneList;
    }
}

思路:

  1. 查询一级菜单,parent_id=0, 拿到一级对象
  2. 循环遍历一级对象,获取一级id,通过parent_id=一级的id,查询二级菜单 对象,并封装到一级数据里,返回
  3. 循环遍历二级对象,获取二级id,通过parent_id=二级的id,查询三级菜单 对象,并封装到二级数据里,返回

缺点:

  • 采用多级循环的方式,将会耗费服务器资源 外层100次,内层100次,总循环一万次 ,耗时430毫 秒
  • 频繁访问数据库,导致数据库压力增大,严重时可能导致数据库服务器宕机.

方案二:

思路:

  1. 用户查询所有的数据库信息. (1-2-3所有数据)

  2. 数据结构 Map<k,v> key唯一的, value可以任意类型.
    思路: Map<parentId,List>
    例子:

  3. Map<0,List<一级ItemCat对象>>

  4. Map<249,List<二级ItemCat对象>>

  5. Map<281,List<三级ItemCat对象>>
    利用map 封装父子关系.

@Service
public class ItemCatServiceImpl implements ItemCatService{
    @Autowired
    private ItemCatMapper itemCatMapper
    /**
     * 利用Map集合封装所有的数据库记录
     * 封装数据:
     *      1.遍历所有的数据信息.
     *      2.获取每一个parentId的值.
     * 例子:
     *      1.{id=1,parentId=0,name="张三"}
     *      2.{id=2,parentId=0,name="李四"}
     *      3.{id=3,parentId=1,name="王五"}
     *      Map= {
     *          key : value
     *          0   : List[张三对象,李四对象.....],
     *          1   : List[王五对象......]
     *      }
     * @return
     */
    public Map<Integer,List<ItemCat>> getMap(){
        Map<Integer,List<ItemCat>> map = new HashMap<>();
        //1.查询所有的数据库信息
        List<ItemCat> itemCatList = itemCatMapper.selectList(null);
        //2.将数据封装到map集合中
        for (ItemCat itemCat : itemCatList){
            Integer key = itemCat.getParentId(); //获取parentId当做key
            //3.判断map集合中是否有值.
            if(map.containsKey(key)){
                //有值: 获取List集合,将自己追加到其中
                map.get(key).add(itemCat);
            }else{
                //没值: 添加数据.将自己作为第一个元素填充
                List<ItemCat> list = new ArrayList<>();
                list.add(itemCat);
                map.put(key,list);
            }
        }
        return map;
    }

    @Override
    public List<ItemCat> findItemCatList(Integer level) {
        long startTime = System.currentTimeMillis();
        Map<Integer,List<ItemCat>> map = getMap();
        //根据level获取子级信息
        if(level == 1){ //只获取一级列表信息
            return map.get(0);
        }
        if(level == 2){ //获取一级和二级数据
            return getTwoList(map);
        }
        List<ItemCat> oneList = getThreeList(map);
        long endTime = System.currentTimeMillis();
        System.out.println("优化前的耗时: 500ms,优化后耗时:"+(endTime - startTime)+"ms");
        return oneList;
    }

    //获取三级列表信息  先获取1级数据,再获取2级数据.再获取3级数据

    private List<ItemCat> getThreeList(Map<Integer, List<ItemCat>> map) {
        //1.调用2级菜单方法.
        List<ItemCat> oneList = getTwoList(map);
        //2.实现思路 遍历一级集合,获取二级数据. 封装三级菜单
        for(ItemCat oneItemCat : oneList){
            //2.1 获取二级数据
            List<ItemCat> twoList = oneItemCat.getChildren();
            if(twoList == null || twoList.size()==0){
                //判断二级集合是否为null.如果为null,表示没有二级菜单.
                continue;
            }
            for (ItemCat twoItemCat : twoList){
                int twoId = twoItemCat.getId();
                List<ItemCat> threeList = map.get(twoId);
                twoItemCat.setChildren(threeList);
            }
        }
        return oneList;
    }
    //通过map集合 获取一级二级菜单信息.
    private List<ItemCat> getTwoList(Map<Integer, List<ItemCat>> map) {
        List<ItemCat> oneList = map.get(0);
        //获取二级信息,应该先遍历一级集合
        for (ItemCat oneItemCat : oneList){
            int oneId = oneItemCat.getId();
            //根据一级Id,获取二级集合
            List<ItemCat> twoList = map.get(oneId);
            oneItemCat.setChildren(twoList);
        }
        return oneList;
    }
}

商品三级菜单删除

删除三级菜单,可以直接删除

删除二级菜单,先删除三级在,删除二级

删除一级菜单,先删除三级在,删除二级,在删除一级

直接删除父级数据不删除自己数据会产生僵尸数据

 @Override
    public void deleteItemCat(Integer id, Integer level) {
        //判断是否为3级菜单
        if(level == 3){
            itemCatMapper.deleteById(id);
        }
        if(level == 2){
            //如果是二级,应该先获取三级数据之后删除,再删除自己
            //delete from item_cat where parent_id=#{id} or id = #{id}
            QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("parent_id",id)
                        .or()
                        .eq("id",id);
            itemCatMapper.delete(queryWrapper);
        }
        /**
         * 如何删除一级菜单?
         *  1.获取二级ID
         *  终极sql: delete from item_cat where parent_id in (twoIds)
         *          or  id in (twoIds)
         *          or  id = #{id}*/
        if(level == 1){
            QueryWrapper<ItemCat> queryWrapper = new QueryWrapper();
            queryWrapper.eq("parent_id",id);
            List twoIds = itemCatMapper.selectObjs(queryWrapper);
            //清空数据
            queryWrapper.clear();
            //规则: 如果2级菜单有值,才会删除 2级和三级
            queryWrapper.in(twoIds.size()>0,"parent_id",twoIds)
                        .or()
                        .in(twoIds.size()>0,"id",twoIds)
                        .or()
                        .eq("id",id);
            itemCatMapper.delete(queryWrapper);
        }
    }

数据自动填充

每次新增/更新时 都需要添加创建时间/修改时间 ,参考Mybatis-blus 官方文档,使用自动填充

  1. 在pojo类,添加自动填充注解

    @TableField(fill = FieldFill.INSERT) 		//新增操作时,实现自动填充
    @TableField(fill = FieldFill.INSERT_UPDATE) //新增/修改操作时,自动填充
    
  2. 配置自动填充类

    @Component
    public class MyMetaObjectHandler implements MetaObjectHandler {
        //框架用法说明: MP根据实现类,自动的完成数据的注入. MP框架自动调用
        //metaObject: 指定默认的规则
        @Override
        public void insertFill(MetaObject metaObject) {
            Date date = new Date();
            this.setFieldValByName("created", date, metaObject);
            this.setFieldValByName("updated", date, metaObject);
        }
        @Override
        public void updateFill(MetaObject metaObject) {
            this.setFieldValByName("updated", new Date(), metaObject);  }
    }
    

    MP中分页构造器

    旧版

    @Service
    public class ItemServiceImpl implements ItemService{
        @Autowired
        private ItemMapper itemMapper;
        @Override
        public PageResult getItemList(PageResult pageResult) {
            //1.构建分页对象  参数1: 第几页   参数2: 多少条
            Page<Item> page = new Page<>				             
            (pageResult.getPageNum(),pageResult.getPageSize());
            //2.准备条件构造器 构建模糊查询
            QueryWrapper queryWrapper = new QueryWrapper();
            String query = pageResult.getQuery();
            boolean flag = StringUtils.hasLength(query);
            queryWrapper.like(flag,"title",query);
            //3.根据MP查询 实现分页数据的自动封装
            page = itemMapper.selectPage(page,queryWrapper);
            //4.获取数据,返回分页对象
            long total = page.getTotal();
            //获取分页结果
            List<Item> rows = page.getRecords();
            return pageResult.setTotal(total).setRows(rows);
        }
    }
    

    新版

    @Configuration  //这是配置类
    public class MybatisConfig 
        //需要通过配置文件 指定数据库类型.
        // 最新版
        @Bean
        public MybatisPlusInterceptor mybatisPlusInterceptor() {
            MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
            interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MARIADB));
            return interceptor;
        }
    }
    

    图片上传

    1. 一般前端想后端发送字节信息,由外到内实现数据传输 ,采用输入信息 InputStream file
      使用字节流的弊端 1.必须手动关闭 2.代码操作繁琐
      底层实现:使用springMVC提供高级API :
      MultipartFile 专门处理IO流操作(注意:MultipartFile 默认支持1M的数据)
    2. 文件图片类型验证 使用正则表达式效验后缀名
      如图片的类型 :(jpg|png|gif …) 注意bug 后缀名可能大写,注意转小写
    3. 用户图片传入名称可能重复,使用UUID充当图片名称 ,保证了图片名称的唯一性
    4. 效验用户上传的文件是否为恶意程序,判断图片的高度和宽度 if(width0 || height0){}

Nginx

Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器

二.Nginx 特点
 1.占用内存少,不超过2M       |    tomcat服务器占用内存 200M
 2.并发能力强3-5万次/秒       |    tomcat支持的并发能力220~260个/秒 调优1000个/秒
 3.开发语言 C语言开发         |    tomcat 使用Java 写的
    
    注意:并发能力:多个用户同时访问服务器
          并行:计算机中的一种处理方式

反向代理:

  1. 反向代理服务器介于用户和目标服务器之间
  2. 用户的资源从反向代理服务器中获取.
  3. 用户不清楚真实的服务器到底是谁. 保护了服务器的信息. 称之为服务器端代理.

正向代理:

  1. 反向代理服务器介于用户和目标服务器之间
  2. 用户的资源从正向代理服务器中获取.
  3. 客户端通过正向代理服务器,指向目标服务器.(用户非常清楚的了解目标服务器的存在.) 服务器端不清楚到底是谁访问的服务器.以为只是代理服务器访问.

反向和正向代理说明:

每一次请求服务器,都伴随着正向代理和反向代理.
正向主要提供网络服务, 反向主要提供数据支持

Nginx服务器命令

需要在nginx的根目录中执行如下的命令dos窗口

1.启动 nginx  start nginx
2.重启 nginx  nginx -s reload
3.关闭 nginx  nginx -s stop

Nginx中conf文件的配置

http {
server {
    	# 监听80端口
        listen       80;
        # 拦截的域名
        server_name  localhost;
		# 拦截所有的请求路径/ 根目录
        location / {
        	#代理的是一个目录
            root   html;
            #默认页面 
            index  index.html index.htm;
        }
     }
}

本机Hosts文件 (本机域名与IP的映射)

文件路径:C:\Windows\System32\drivers\etc
修改信息:
127.0.0.1   localhost
#图片服务器
127.0.0.1 	image.jt.com
#后台服务器
127.0.0.1 	manage.jt.com
#前端服务器
127.0.0.1 	www.jt.com  

域名实现前端/后端访问

#配置图片代理  写完之后记得保存 ctrl+s
	server {
		listen 80;
		server_name image.jt.com;
		location / {
			root E:/project3/images;
		}
	}
	#配置后端服务器代理
	server {
		listen 80;
		server_name manage.jt.com;
		location / {
			#代理请求协议 
			proxy_pass http://localhost:8091;
		}
	}
	#配置前端服务器代理  www.jt.com  localhost:8080
	server {
		listen 80;
		server_name www.jt.com;
		location / {
			#代理请求协议 
			proxy_pass http://localhost:8080;
		}
	}

Nginx负载均衡

随着网站、应用访问量的增加,一台服务器已经不能满足应用的需求,而需要多台服务器集群,这时就会用到负载均衡

  • 负载均衡优化了访问请求在服务器组之间的分配,消除了服务器之间的负载不平衡,从而提高了系统的反应速度与总体性能

负载均衡的作用
1.解决并发压力,提高应用处理性能,加强网络处理能力
2.提供故障转移,实现高可用;
3.通过添加或者减少服务器数量,提供网站伸展性(扩展性);
4.安全防护(负载均衡设备上做过滤,黑白名单等处理);

负载均衡准备工作
动态获取当前服务器的端口号  
http://localhost:8091/getPort  要求获取 :8091端口号.
创建一个PortController类,通过@value("${server.post}") 动态获取当前服务器的端口号
@getMapper("getPort")注解,接受浏览器的get请求
修改application.yml中的端口号,开启多个Tomcat 服务器 。
注意:去除热部署,否则你多开时会报错端口号被占

一.轮询策略

根据配置文件中的服务器的顺序,依次访问服务器

#通过 upstream 关键字定义服务器集群
#定义tomcat服务器集群
	upstream tomcats {
		server  localhost:8091;
		server  localhost:8092;
		server  localhost:8093;
	}
	
	#配置后端服务器代理  manage.jt.com  localhost:8091
	server {
		listen 80;
		server_name manage.jt.com;
		location / {
			#代理请求协议 
			#proxy_pass http://localhost:8091;
			proxy_pass  http://tomcats;
		}
	}

二.权重策略

权重是会根据负载大小变化的,如果负载一直增加,那么权重就会一直减少到不是最大,也就不会再分配任务给它了,反而分配给新的权重最大的服务器。

根据服务器的性能,灵活的进行访问的配比

upstream tomcats {     #通过weight属性分配权重
		server  localhost:8091 weight=6;
		server  localhost:8092 weight=3;
		server  localhost:8093 weight=1;
	}

三.IPHASH策略

  1.有时会将用户的数据,存储到后端服务器的Session中.并且保证每次用户访问都访问同一个tomcat服务器.
  2.需要用户与tomcat服务器进行绑定,可以选用iphash策略
	upstream tomcats {    #实现iphash策略   ip_hash 
		ip_hash;
		server  localhost:8091 weight=6;
		server  localhost:8092 weight=3;
		server  localhost:8093 weight=1;
	}

iphash实现原理以及弊端

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eowKjjJQ-1645145763662)(C:\Users\27588\AppData\Roaming\Typora\typora-user-images\image-20211117194931409.png)]

Nginx 高可用实现

nginx关键字:
1.down属性:用来表示tomcat服务器,告知nginx 该服务器已经下线,以后不会访问该服务器 
2.backup属性:设定备用机,正常情况下,不会主动提供服务,当服务区遇忙时/或者主机全部下线时,才起作用

在线部署步骤	
原有的服务必须关闭,之后才能升级.
步骤:	1. 将设计部署的策略
	 2. 通过down属性标识下线.
	 3. 之后部署新的项目,启动测试.如果没有问题.则上线运行.
	 4. 重复执行上述的操作即可.
	 5. 上述的操作 一般都有脚本完成 几分钟即可实现上线操作. Linux运维
	 
tomcat高可用实现
   1. max_falis=1 ;     指定最大的失败次数,规定最大失败次数为1
   2.fali_timeout=60s ; 如果失败次数达到最大失败次数时,60s内 nginx不会再次访问故障机

	upstream tomcats {
		#ip_hash;  weight=6
		server  localhost:8091 max_fails=1 fail_timeout=60s;
		server  localhost:8092 max_fails=1 fail_timeout=60s;
		server  localhost:8093 max_fails=1 fail_timeout=60s;
	}

Nginx前端项目部署

  1. 前端包含html/css/js静态资源文件,将前端项目按照静态资源的方式打成文件目录,之后通过NGINX实现反向代理,实现前端项目的发布
  2. 打包bulid入编译成功,在jtadmin的目录下生成dist目录文件,里面就是去前端项目
  3. 导入dist文件导nginx文件中
  4. 反向代理实现项目发布
server {
		listen 80;
		server_name www.jt.com;
		location / {
			root dist;
			index index.html;
		}
	}

项目说明:

  1. 前端项目 是静态资源文件 部署在nginx内部
  2. 后端项目 多个tomcat服务器 8091/8092/8093
  3. 用户通过www.jt.com 反向代理到前端服务
  4. 前端获取数据通过manage.jt.com 访问后端服务器集群. 通过nginx实现负载均衡

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w1YO8cP8-1645145763663)(C:\Users\27588\AppData\Roaming\Typora\typora-user-images\image-20211117201020595.png)]

LINUX

Linux特点 。
  1. 系统开源并且免费
  2. 对硬件要求很低 800M 3-4
  3. 系统稳定性强
  4. 系统安全性更好(军工企业 断网)
Linux命令

Linux 全部都是基于文件的,并且Linux中的目录都是属性结构

1.基本命令   
   ipconfig / ip addr   检查IP地址
    pwd                  检查当前的位置
    tab键                自动补齐(注意唯一性)
2.cd 命令集
	cd /                 返回根目录
	cd ~                 用户主目录
	cd . 			    当前目录
	cd ..                返回到上一级目录
	cd -                 返回上一个目录
	cd                   直接回家
3.ls 目录和文件
 	ls -l                详细格式,文件权限,时间
 	ll 和 ls -l 作用相同
 	ls * .txt 查看所有的 txt类型文档
4.目录操作
 	mkdir                 创建目录
 	mkdir  a              创建a目录
 	mkdir  -p a/b         创建a目录,并在a 目录里创建 b 目录
 	rmdir                 删除目录(如果目录里面有文件,则不能适应此命令)
5.vi/vim创建/查看/编辑文件
  命令行:Esc 切换到命令行模式
  编辑模式:
    按 i   在光标前开始编辑
    按 a   在光标后开始编辑
    按 o   在当前行下一行开始编辑
    按 u   撤销之前的操作
 底行模式 :按shift + :冒号
    :q !   不保存直接退出(不建议使用)
    :wq    保存退出
    :/world  从当前光标处,向上查找 world 关键字
    :?world  从当前光标处,向后查找 world 关键字
6.删除文件:
	rm              删除文件
	rm a.txt        提示 y 删除,n 放弃
	rm -f a.txt     跳过提示,直接删除
	rm -rf dirname  不提示递归删除目录下所有内容
	rm -rf *        删除所有文件
	rm -rf /*       删除所有子目录的所有和文件(删除时慎重)
7.复制和移动文件
	cp              复制文件
	cp nginx.conf n.txt
	cp-R tmocat tomcat2    复制整个目录
	mv               修改文件名,移动文件
	mv n.txt m.txt 修改文件名称
8.浏览文件
	cat              输入文件所有的内容
	more             输入文档所有的内容,分页输出,空格浏览下一屏,qtuichu1
    more 			输出文档所有的内容,分页输出,空格浏览下一屏,q退出
    less 			用法和more相同,只是通过PgUp、PgOn键来控制
    tail 			用于显示文件后几号,使用频繁
    tail -10 nginx.conf 查看nginx.conf的最后10行
    tail –f nginx.conf 动态查看日志,方便查看日志新增的信息
    ctrl+c			结束查看
9.打包命令 
	tar命令位于/bin目录下,它能够将用户所指定的文件或目录打包成一个文件,但不做压缩。一般Linux上常用的压缩方式是选用tar将许多文件打包成一个文件,再以gzip压缩命令压缩成name.tar.gz的文件。
    -c 				创建一个新的tar文件
    -v 				显示运行过程的信息
    -f 				指定文件名
    -z 				调用gzip压缩命令进行压缩
    -t 				查看压缩文件的内容
    -x 				解开tar文件
    tar –cvf n.tar ./* 		 压缩当前目录下的所有文件和目录,文件名为n.tar
    tar –xvf n.tar 			 解压压缩包中的文件到当前目录(如果长时间未解压成功 Ctrl+C推出)
    tar –cvzf m.tar.gz ./* 	 压缩文件
    tar -zxvf m.tar.gz		 解压m.tar文件到当前目录
10.grep命令
	grep root  /ect/passwd           		在文件中查找关键字 root
	grep root  /ect/passwd --color			高度显示
	grep root  /ect/passwd -A5 -B5           高亮显示,A后5行,B前5行
	grep -n root /etc/passwd  				查找并显示行数
	grep -v root /etc/passwd   				取反,查出不含root的数据
11.防火墙
     firewall-cmd --state                     检查防火墙状态             
     systemctl start firewalld.service        启动防火墙
     systemctl stop firewalld.service         关闭防火墙 
     firewall-cmd --reload                    重启防火墙

微服务

分布式

系统中的多个模块在不同服务器上部署,即可称为分布式系统,如Tomcat和数据库分别部署在不同的服务器上,或两个相同功能的Tomcat分别部署在不同服务器上

高可用

系统中部分节点失效时,其他节点能够接替它继续提供服务,则可认为系统具有高可用性

集群

一个特定领域的软件部署在多台服务器上并作为一个整体提供一类服务,这个整体称为集群

微服务

微服务架构(MSA)的基础是将单个应用程序开发为一组小型独立服务,这些独立服务在自己的进程中运行,独立开发和部署

微服务SpringCloud聚合工程创建

GitCGB2108IVProjects (工作区/空项目)
├── 01-sca   //(微服务父工程)
     ├── sca-provider            //服务提供方法
     ├── sca-consumer         //服务消费方法
     ├── sca-gateway            //网关服务

  • 项目初始化配置:创建一个Empty Project ,就是一个空的文件夹,作为代码工作区

    1. 配置Maven环境,idea中Settings/Bulid/Maven
    2. 配置JDK编译环境,idea中Settings/Bulid/Complier,勾选 Bulid 和 Compiler 开头的选项(自动编译/编译独立模块),Java Compiler选项卡中,java版本改成 8
    3. 配置项目工作区中项目编码格式 三个位置全改成 UTF-8
  • 创建父工程模块:创建Maven模块

    1. 父工程没有父类,Parent:None | Name:01-sca|GroupId:com.jt|Artifactld:01-sca ,删除src目录
    2. 修改项目pom.xml 文件,具体内容在下面
  • 创建服务提供方模块:创建Maven模块(sca-provider) ,继承01-sca

  • 创建服务消费方模块:创建Maven模块(sca-consumer) ,继承01-sca

  • 创建API网关服务模块:创建Maven模块(sca-gateway) ,继承01-sca

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!--当前工程的坐标-->
    <groupId>com.jt</groupId>
    <artifactId>01-sca</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!--有的同学在创建maven工程时,可能会有如下有两句话,这两句话用于指定
    当前项目的jdk编译版本以及运行版本,也可以不指定,后续我们自己通过maven插件方式进行配置-->
    <!--
    <properties>
         <maven.compiler.source>8</maven.compiler.source>
         <maven.compiler.target>8</maven.compiler.target>
    </properties>
    -->
    <!--maven父工程的pom文件中一般要定义子模块,
    子工程中所需依赖版本的管理,公共依赖并且父工程的
    打包方式一般为pom方式-->
    <!--第一步: 定义子工程中核心依赖的版本管理(注意,只是版本管理)-->
    <dependencyManagement>
        <dependencies>
            <!--spring boot 核心依赖版本定义(spring官方定义)-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.3.2.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!--Spring Cloud 微服务规范(由spring官方定义)-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR9</version>
                <type>pom</type><!--假如scope是import,type必须为pom-->
                <scope>import</scope><!--引入三方依赖的版本设计-->
            </dependency>
            <!--Spring Cloud alibaba 依赖版本管理 (参考官方说明)-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.2.6.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <!--第二步: 添加子工程的所需要的公共依赖-->
    <dependencies>
        <!--lombok 依赖,子工程中假如需要lombok,不需要再引入-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope><!--provided 表示此依赖仅在编译阶段有效-->
        </dependency>
        <!--单元测试依赖,子工程中需要单元测试时,不需要再次引入此依赖了-->
         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope><!--test表示只能在test目录下使用此依赖-->
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--其它依赖...-->
    </dependencies>
    <!--第三步: 定义当前工程模块及子工程的的统一编译和运行版本-->
    <build><!--项目构建配置,我们基于maven完成项目的编译,测试,打包等操作,
    都是基于pom.xml完成这一列的操作,但是编译和打包的配置都是要写到build元素
    内的,而具体的编译和打包配置,又需要plugin去实现,plugin元素不是必须的,maven
    有默认的plugin配置,常用插件可去本地库进行查看-->
        <plugins>
            <!--通过maven-compiler-plugin插件设置项目
            的统一的jdk编译和运行版本-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                 <!--假如本地库没有这个版本,这里会出现红色字体错误-->
                <version>3.8.1</version>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

微服务常见问题

微服务架构诞生背景:软件及服务,将大型软件,拆分成若干个小系统,分而治之(一个中国银行有多个网点)

为什么需要微服务 :系统分而治之,解决了因高并发访问过大带来的系统复杂性

微服务的特点:单一职责,独立进程,可靠性高,升级难度小。但会带来一定的维护成本

微服务解决方案:SpringCloud Netflix 、Alibaba 、自研

创建聚合工程的目的:实现工程之间资源的共享,简化工程管理

微服务中聚合工程之间的引用设计:将一个工程作为依赖的方式添加到其他工程

Nacos注册中心

Nacos(DynamicNaming and Configuration Service 是一个应用与服务注册与发现、配置管理的平台

准备与安装:

  1. 确保电脑已配置了JAVA_HOME环境变量,JDK 1.8 以上

  2. MySQL版本5.7以上,MariDB 10.5以上

  3. 解压后,在MySQL执行nacos-mysql-sql 脚本

  4. 修改nacos 中 /nacos/conf/application.properties 中配置信息

  ### If use MySQL as datasource:
  spring.datasource.platform=mysql
  ### Count of DB:
  db.num=1
  ### Connect URL of DB:
  db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?  characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
  db.user.0=root
  db.password.0=root
  1. 启动nacos服务,在bin目录下执行启动命令startup.cmd -m standalone
  2. 访问nacos服务 ,浏览器地址:http://locahost:8848/naxos ,账号密码nacos

NACOS服务的创建及注册

1.工程中添加依赖

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
 </dependency>

2.创建并修改配置文件application.yml 或者.properties

server:
   port: 8081
spring:
  application:
    name: sca-provider #进行服务注册必须配置服务名
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

3.启动启动类,刷新Nacos服务,在服务列表中,注册的nacos-provider 有体现

基于RestTemplate实现服务之间的调用

  1. 创建提供方Provider和消费方Consumer,都在Nacos中进行注册

  2. 在sca-provider项目中创建提供方对象,基于此对象提供服务

        package com.jt.provider.controller;
        @RestController
        public class ProviderController{
            //@Value默认读取项目配置文件中配置的内容,8080为没有读到server.port的值时,给定的默认值
            @Value("${server.port:8080}")
            private String server;
            //http://localhost:8081/provider/echo/tedu
            @GetMapping("/provider/echo/{msg}")
            public String doRestEcho1(@PathVariable String msg){
                return server+" say hello "+msg;
            }
        }
    

    3.在sca-consumer启动类中添加,RestTemplate对象

    @Bean
    public RestTemplate restTemplate(){//基于此对象实现远端服务调用
        return new RestTemplate();
    }
    

    4.定义sca-consumer服务消费端,在RestTemplate对象内部,实现远程调用

    package com.jt.consumer.controller;
    @RestController
    public class ConsumerController {
         // 从spring容器获取一个RestTemplate对象
        @Autowired
        private RestTemplate restTemplate;
         /* 在此方法中通过一个RestTemplate对象调用远端sca-provider中的服务
            访问此方法的url: http://localhost:8090/consumer/doRestEcho1   */
        @GetMapping("/consumer/doRestEcho1")
        public String doRestEcho01(){
            //1.定义要调用的远端服务的url
            String url="http://localhost:8081/provider/echo/8090";
            //2.基于restTemplate对象中的相关方法进行服务调用
            return restTemplate.getForObject(url, String.class);
        }
    }
    

    5.启动两个服务,在浏览器中访问:http://localhost:8090/consumer/doRestEcho1

    Ncaosc常见问题

    什么是Nacos:是Alibaba基于SpringBoo技术实现的一个注册中心,本质上是一个web服务

    如何理解服务注册中心:存储服务信息的一个服务

    市场上常见的注册中心:Zookeeper,Eureka,Nacos,Consul

    注册中心的选型:社区活跃度、稳定性、功能、性能、学习成本

    Nacos的基本架构:Client/Server架构

    Nacos的主要功能:服务的注册、发现、配置

    Nacos需要的依赖:Web,discovery

    Nacos如何检查服务状态:通过心跳包实现,服务启动时会定时向nacos发送心跳包-BeatInfo 5/15/30

    服务之间调用的API :RestTemplate,用此对象之前要先创建这个对象并交给spring管理

    RestTemplate谁提供的:Spring 提供的一个 HTTP 请求工具,实现远程调用

服务负载均衡设计及实现、

一个服务实例可以处理请求是有限的,服务实例的并发访问比价大,启动服务实例,可以采用策略均衡(轮训、权重、hash等) 处理并发请求

LoadBalancerClient 对象

可以从Nacos中基于服务名获取实例,然后再工程中基于算法实现负载均衡方式调用

  1. 修改ConsumerController类,注入LoadBalancerClient对象,添加doRestEcho2方法,进行服务的访问

    
      @Autowired
      private LoadBalancerClient loadBalancerClient;
     
      @Value("${spring.application.name:8090}")
      private String appName;
       
      @GetMapping("/consumer/doRestEcho02")
     public String doRestEcho02(){
         ServiceInstance serviceInstance = loadBalancerClient.choose("sca-provider");
         String url = String.format("http://%s:%s/provider/echo/%s",serviceInstance.getHost(),serviceInstance.getPort(),appName);
         System.out.println("request url:"+url);
         return restTemplate.getForObject(url, String.class);
         }
     }
    
  2. 修改sca-provider的配置文件端口号,分别以8091、8092端口启动

  3. 启动成功以后,访问Nacos的服务列表,检测服务是否成功注册,nacos-peovider实例数为2

  4. 启动sca-consumer项目模块,打开浏览器对consumer服务进行访问,访问时不断刷新,检测页面数据变化

说明,这里多个实例并发提供服务的方式为负载均衡,这里的负载均衡实现默认是因为Nacos集成了Ribbon来实现的,Ribbon配合RestTemplate,可以非常容易的实现服务之间的访问。Ribbon是Spring Cloud核心组件之一,它提供的最重要的功能就是客户端的负载均衡(客户端可以采用一定算法,例如轮询访问,访问服务端实例信息),这个功能可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡方式的服务调用。

@LoadBalanced

当使用RestTemplate进行远程服务调用时,假如需要负载均衡,还可以在RestTemplate对象构建时,使用@LoadBalanced对构建RestTemplate的方法进行修饰,例如在ConsumerApplication中构建名字为loadBalancedRestTemplate的RestTemplate对象:

@Bean
@LoadBalanced
public RestTemplate loadBalancedRestTemplate(){
    return new RestTemplate();}

在需要RestTemplate实现负载均衡调用的地方进行依赖注入.例如在ConsumerController类中添加loadBalancedRestTemplate属性

@Autowired
private RestTemplate loadBalancedRestTemplate;

接下来,可以在对应的服务端调用方的方法内,基于RestTemplate借助服务名进行服务调用, 例如:

@GetMapping("/consumer/doRestEcho3")
public String doRestEcho03(){
    String url=String.format("http://%s/provider/echo/%s","sca-provider",appName);
    //向服务提供方发起http请求,获取响应数据
    return loadBalancedRestTemplate.getForObject(
            url,//要请求的服务的地址
            String.class);//String.class为请求服务的响应结果类型
}

RestTemplate在发送请求的时候会被LoadBalancerInterceptor拦截,它的作用就是用于RestTemplate的负载均衡,LoadBalancerInterceptor将负载均衡的核心逻辑交给了loadBalancer

public ClientHttpResponse intercept(final HttpRequest request, 
    final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
	final URI originalUri = request.getURI();
	String serviceName = originalUri.getHost();
	return this.loadBalancer.execute(serviceName, 
    requestFactory.createRequest(request, body, execution));
}

@LoadBalanced注解是属于Spring,而不是Ribbon的,Spring在初始化容器的时候,如果检测到Bean被@LoadBalanced注解,Spring会为其设置LoadBalancerInterceptor的拦截器。

Ribbon负载均衡策略

基于Ribbon方式的负载均衡,Netflix默认提供了七种负载均衡策略,对于SpringCloud Alibaba解决方案中又提供了NacosRule策略,默认的负载均衡策略是轮训策略

当系统提供的负载均衡策略不能满足我们需求时,我们还可以基于IRule接口自己定义策略

核心知识

  • 基于LoadBalancerClient 对象从注册中心获取服务列表(服务发现)

​ 从nacos注册中心获取服务实例列表,然后本地基于负载均衡算法获取具体服务实例

  • 基于Ribbon (一个负载均衡组件,这个组件中提供一套负载均衡算法)实现负载均衡

    • Ribbon 是什么?(Netflix公司提供的负载均衡客户端,一般应用于服务的消费方法)
    • Ribbon 可以解决什么问题? (基于负载均衡策略进行服务调用, 所有策略都会实现IRule接口)
    • Ribbon 内置的负载策略都有哪些?(8种,可以通过查看IRule接口的实现类进行分析)
  • @Loadbalanced注解的应用

    • (描述RestTemplate对象,用于告诉Spring框架,在使用RestTempalte进行服务调用时,这个调用过程会被一个拦截器进行拦截,然后在拦截器内部,启动负载均衡策略)
  • 自定义负载均衡策略

    • 基于IRule接口进行策略定义,也可以参考NacosRule进行实现

    • 配置文件->例如application.yml,配置类->例如启功类(方便维护,后期可写到配置中心,动态更新)

    sca-provider: #这个是要进行远程调用的服务id(服务名)
      ribbon:
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #负载均衡算法
    
    • 在配置类中,引入负载均衡算法(可维护性差,项目打包后无法修改)

      @Bean
      public IRule ribbonRule() {
          return new RandomRule();
      }
      

    基于Feign的远程服务调用

    背景

    服务消费方基于rest方式请求服务提供方的服务时,一种直接的方式就是自己拼接url,拼接参数然后实现服务调用,但每次服务调用都需要这样拼接,代码量复杂且不易维护,此时Feign诞生

    Feign (OpenFeign)

    Feign是一种声明式Web服务客户端,底层封装了对Rest技术的应用,实现服务之间的调用

    Feign应用

    1. 添加项目依赖

      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-openfeign</artifactId>
      </dependency>
      
    2. 在启动类中,添加@EnableFeigenClients注解

      @EnableFeignClients
      @SpringBootApplication
      public class ConsumerApplication {}
      
    3. 定义HTTP请求API,基于此API借助OpenFeign访问远程服务

      package com.jt.consumer.service;
      @FeignClient(name="sca-provider")//sca-provider为服务提供者名称
      public interface RemoteProviderService{
          @GetMapping("/provider/echo/{string}")//前提是远端需要有这个服务
          public String echoMessage(@PathVariable("string") String string);
      }
      

      其中,@Feginclient描述的接口底层会为其创建实现类

    4. 创建FeignConsumerController中并添加feign访问

      package com.jt.consumer.controller;
      @RestController
      @RequestMapping("/consumer/ ")
      public class FeignConsumerController {
          @Autowired
          private RemoteProviderService remoteProviderService;
          /**基于feign方式的服务调用*/
          @GetMapping("/echo/{msg}")
          public String doFeignEcho(@PathVariable  String msg){
              //基于feign方式进行远端服务调用(前提是服务必须存在)
              return remoteProviderService.echoMessage(msg);
          }
      }
      
    5. 启动消费者服务,在浏览器中,访问Feign客户端,

      端口号在8081,8082之间不断转换,说明feign方式的远程调用,底层水自动基于ribbon组件实现负载均衡

    Feign配置进阶实践

    ContextId

    ​ 一个服务提供方通常会提供很多资源服务,服务消费方基于同一个服务提供方写了很多服务调用接口,此时假如没有指定contextId,服务启动就会失败

    ​ 为远程调用服务接口指定一个contextId,作为远程调用服务的唯一标识(这个标识是Bean对象的名字)

    @FeignClient(name="sca-provider",contextId="remoteProviderService")//sca-provider为服务提供者名称
    interface RemoteProviderService{
        @GetMapping("/provider/echo/{string}")//前提是远端需要有这个服务
        public String echoMessage(@PathVariable("string") String string);
    }
    
    FallbackFactory

    在进行远程服务调用时,假如调用的服务突然不可用了或者调用过程超时了,怎么办呢?一般服务消费端会给出具体的容错方案,例如,在Feign应用中通过FallbackFactory接口的实现类进行默认的相关处理

    1. 定义FallbackFactory接口实现
    package com.jt.service.factory;
     //基于此对象处理RemoteProviderService接口调用时出现的服务中断,超时等问题
    @Component
    public class ProviderFallbackFactory implements FallbackFactory<RemoteProviderService> {
        /**
         * 此方法会在RemoteProviderService接口服务调用时,出现了异常后执行.
         * @param throwable 用于接收异常
         */
        @Override
        public RemoteProviderService create(Throwable throwable) {
            return (msg)->{
                    return "服务维护中,稍等片刻再访问";
            };
        }
    
    1. 在Feign访问接口中应用FallbackFactory对象

      @FeignClient(name = "sca-provider", contextId = "remoteProviderService",
                   fallbackFactory = ProviderFallbackFactory.class)//sca-provider为nacos中的服务名
      public interface RemoteProviderService {
          @GetMapping("/provider/echo/{msg}")
          public String echoMsg(@PathVariable String msg);
      }
      

      3.在配置文件application.yml中添加如下配置,启动feign方式调用时的服务中断处理机制

      feign:  
        hystrix:
          enabled: true #默认值为false
      

      Feign调用过程

      1)通过 @EnableFeignCleints 注解告诉springcloud,启动 Feign Starter 组件。
      2) Feign Starter 会在项目启动过程中注册全局配置,扫描包下所由@FeignClient注解描述的接口,然后由系统底层创建接口实现类(JDK代理类),并构建类的对象,然后交给spring管理(注册 IOC 容器)。
      3) Feign接口被调用时,底层代理对象会将接口中的请求信息通过编码器创建 Request对象,基于此对象进行远程过程调用。
      4) Feign客户端请求对象会经Ribbon进行负载均衡,挑选出一个健康的 Server 实例(instance)。
      5) Feign客户端会携带 Request 调用远端服务并返回一个响应。
      6) Feign客户端对象对Response信息进行解析然后返回客户端

Nacos服务配置中心应用实践

​ 配置中心最基础的功能就是存储一个键值对,用户发布一个配置(configKey),然后客户端获取这个配置项(configValue);进阶的功能就是当某个配置项发生变更时,不停机就可以动态刷新服务内部的配置项

Nacos配置中心使用

1.创建ProviderLogController对象

@RestController
public class ProviderLogController {
    //org.slf4j.Logger (Java中的日志API规范,基于这个规范有Log4J,Logback等日志库)
    //org.slf4j.LoggerFactory
    //log对象在哪个类中创建,getLogger方法中的就传入哪个类的字节码对象
    //记住:以后只要Java中使用日志对象,你就采用下面之中方式创建即可.
    //假如在log对象所在类上使用了@Slf4j注解,log不再需要我们手动创建,lombok会帮我们创建
   private static Logger log=
           LoggerFactory.getLogger(ProviderLogController.class);
    @GetMapping("/provider/log/doLog01")
    public String doLog01(){//trace<debug<info<warn<error
        System.out.println("==doLog01==");
        log.trace("===trace===");
        log.debug("===debug===");
        log.info("===info====");
        return "log config test";
    }
}

2.添加依赖在sca-peovider项目中

  <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
  </dependency>
  1. 将项目sca-provider的application.yml的名字修改为bootstrap.yml(启动优先级最高)
spring:
  application:
    name: sca-provider
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
      config:
        server-addr: 127.0.0.1:8848
        file-extension: yml # Configure the data format of the content, default to properties

​ 4.打开Nacos配置中心,新建配置

在这里插入图片描述5. 配置创建好以后,启动sca-provider服务,然后打开浏览器,输入http://localhost:8081/provider/log/doLog01,检测idea控制台日志输出。然后再打开nacos控制台动态更新日志级别,再访问资源并检测后台日志输出.

@RefreshScope注解的应用

配置中心的相关配置发生变化以后,能够及时看到类中属性值的更新(底层是通过重新创建Controller对象的方式,对属性进行了重新初始化)

@RefreshScope
@RestController
public class ProviderLogController{
  //.....
}

Nacos配置管理模型

  • Namespace:命名空间,对不同的环境进⾏隔离,⽐如隔离开发环境和⽣产环境。

    • 新建命名空间后,需要修改对应的bootstrap.yml文件,namespace后面的字符串为命名空间的id
    • spring: cloud: nacos: config: namespace: 6058fd3f-0d4d-44f2-85d6-5fc7d2348046 ……
  • Group:分组,将若⼲个服务或者若⼲个配置集归为⼀组。

    • 一个服务在不同时间节点(节假日,活动等)切换不同的配置,可以在新建配置时指定分组

    • useLocalCache为自己定义的配置值,表示是否使用本地缓存

    • nacos: config: server-addr: 127.0.0.1:8848 group: DEFAULT_GROUP_51 # Group, default is DEFAULT_GROUP file-extension: yml namespace: 7da4aa75-f64c-43c6-b101-9d77ad96f1c0

    • 共享配置设计

    • 当同一个namespace的多个配置文件中都有相同配置时,可以对这些配置进行提取,然后存储到nacos配置中心的一个或多个指定配置文件,哪个微服务需要,就在服务的配置中设置读取即可

    •    shared-configs[0]:
                      data-id: app-public.yml
                      refresh: true #默认false,共享配置更新,引用此配置地方是否要更新
      
  • Service/DataId:某⼀个服务或配置集,一般对应一个配置文件

Nacos配置中心常见问题
  • Nacos客户端(微服务业务)如何动态感知配置中心数据变化的?(nacos2.0之前nacos客户端采用长轮询机制每隔30秒拉取nacos配置信息.
  • Nacos配置中心宕机了,我们的服务还可以读取到配置信息吗?(可以从内存,客户端获取了配置中心的配置信息以后,会将配置信息在本地内存中存储一份
  • 为什么需要配置中心?(动态管理发布配置,无需重启服务,更好保证服务的可用)
  • Nacos配置管理模型是怎样的?(命名空间-namespace,分组-group,服务实例-dataId)
  • Nacos配置管理模型是怎样的?(命名空间-namespace,分组-group,服务实例-dataId)

Sentinel限流熔断应用

Sentinel (分布式系统的流量防卫兵) 是阿里开源的一套用于服务容错的综合性解决方案。它以流量为切入点, 从流量控制、熔断降级、系统负载保护等多个维度来保护服务的稳定性Sentinel

核心分为两个部分:
  • 核心库(Java 客户端):能够运行于所有 Java 运行时环境,同时对Dubbo /Spring Cloud 等框架也有较好的支持。
  • 控制台(Dashboard):基于 Spring Boot 开发,打包后可以直接运行。
安装Sentinel服务
  1. 下载网站https://github.com/alibaba/Sentinel/releases,下载jar包
  2. 在sentinel对用目录中,打开命令行(cmd),启用sentiel
java -Dserver.port=8180 -Dcsp.sentinel.dashboard.server=localhost:8180 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.1.jar
  1. 启动后,通过浏览器访问localhost:8180,账号和密码都是sentinel

Sentinel限流使用

  1. Sentinel 应用于服务提供方(sca-provider),在服务提供方添加依赖如下
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
  1. 打开服务提供方配置文件bootstrap.yml,添加sentinel配置
spring:
  cloud:
    sentinel:
      transport:
         dashboard: localhost:8180 # 指定sentinel控制台地址。
  1. 创建演示限流操作Controller
@RestController
@RequestMapping("/provider")
public class ProviderSentinelController {
       @GetMapping("/sentinel01")
       public String doSentinel01(){
           return "sentinel 01 test  ...";
       }
}
  1. 启动并测试 sca-provider 服务,然后访问localhost:8081/provider/sentinel01
  2. 刷新sentinel控制台,实时监控信息

Sentinel流控规则

阈值类型
  • QPS(Queries Per Second):当调用相关url对应的资源时,QPS达到单机阈值时,就会限流。
  • 线程数:当调用相关url对应的资源时,线程数达到单机阈值时,就会限流
设置限流模式
  • 直接模式
    • Sentinel默认的流控处理就是【直接->快速失败
  • 关联模式
    • 当关联的资源达到指定阈值,就限流自己。例如设置了关联资源为ur2时,假如关联资源url2的qps阀值超过1时,就限流url1接口
  • 链路模式
    • 只记录指定链路入口的流量,当多个服务对指定资源调用时,假如流量超出了指定阈值,则进行限流,可以基于@SentinelResource注解描述的方法进行限流后的异常进行自定义处理
      • 1)方法修饰符为public * 2)必须为static方法 * 3)返回值类型与@SentinelResource注解描述的方法相同 * 4)参数类型为BlockException * 5)方法名自己定义
限流效果
  • 快速失败
  • 预热
  • 排队

sentinel降级应用

Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出DegradeException)

Sentinel异常处理(自定义处理机制)

  • Sentinel中限流,降级的异常父类(BlockException)
  • Sentinel 出现降级熔断时,系统底层抛出的异常(DegradeException)
  • Sentinel中异常处理接口(BlockExceptionHandler)
  • Sentinel中异常处理接口下默认的实现类(DefaultBlockExceptionHandler)

直接或间接实现BlockExceptionHandler接口,交给Spring容器进行管理

// 自定义限流,降级等异常处理对象
@Slf4j
@Component
public class ServiceBlockExceptionHandler
     implements BlockExceptionHandler {
    //用于处理BlockException类型以及子类类型异常
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
     BlockException e) throws Exception {
        //设置响应数据编码
        response.setCharacterEncoding("utf-8");
        //告诉客户端响应数据的类型,以及客户端显示内容的编码
        response.setContentType("text/html;charset=utf-8");
        //向客户端响应一个json格式的字符串
        //String str="{\"status\":429,\"message\":\"访问太频繁了\"}";
        Map<String,Object> map=new HashMap<>();
        map.put("status", 429);
        map.put("message","访问太频繁了");
        String jsonStr=new ObjectMapper().writeValueAsString(map);
        PrintWriter out = response.getWriter();
        out.print(jsonStr);
        out.flush();
        out.close();
    }
}
Sentiel热点规则

热点参数限流会统计传入参数中的热点数据,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效

  • 热点数据的限流规则是怎样的(主要是针对参数进行限流设计)
  • 热点数据中的特殊参数如何理解(热点限流中的某个参数值的阈值设计)
  • 对于热点数据的访问出现限流以后底层异常(ParamFlowException)

Sentinel系统规则

​ 监控服务器的状态,看服务器CPU、内存、IO等的使用率;主要目的就是保证服务器正常的运行,不能被某些应用搞崩溃了;而且在保证稳定的前提下,保持系统的最大吞吐量

系统规则

Load(仅对 Linux/Unix-like 机器生效):当系统 load1 超过阈值,且系统当前的并发线程数超过系统容量时才会触发系统保护。系统容量由系统的 maxQps * minRt 计算得出。设定参考值一般是 CPU cores * 2.5

CPU使用率:当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0)

RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒

线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护

入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护

分析
  • 如何理解sentinel中的系统规则?(是对所有链路的控制规则,是一种系统保护策略)
  • Sentinel的常用系统规则有哪些?(RT,QPS,CPU,线程,Load-linux,unix)
  • Sentinel系统保护规则被触发以后底层会抛出什么异常?(SystemBlockException)
Sentinel授权规则

​ 使用 Sentinel 的黑白名单控制的功能。黑白名单根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过

黑白名单规则(AuthorityRule)非常简单,主要有以下配置项:

  • 资源名:即限流规则的作用对象
  • 流控应用:对应的黑名单/白名单中设置的规则值,多个值用逗号隔开.
  • 授权类型:白名单,黑名单(不允许访问).

GateWay API网关

网关本质上要提供一个各种服务访问的入口,并提供服务接收并转发所有内外部的客户端调用,还有就是权限认证,限流控制

GateWay 使用

  1. 添加依赖

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-gateway</artifactId>
            </dependency>
    
  2. 创建application.yml

server:
  port: 9000
spring:
  application:
    name: sca-gateway
  cloud:
    gateway:
        routes: #配置网关路由规则
          - id: route01  #路由id,自己指定一个唯一值即可
            uri: http://localhost:8081/ #网关帮我们转发的url
            predicates: ###断言(谓词):匹配请求规则
              - Path=/nacos/provider/echo/**  #请求路径定义,此路径对应uri中的资源
            filters: ##网关过滤器,用于对谓词中的内容进行判断分析以及处理
              - StripPrefix=1 #转发之前去掉path中第一层路径,例如nacos

其中:路由(Route) 是 gateway 中最基本的组件之一,表示一个具体的路由信息载体。主要定义了下面的几个信息:

id,路由标识符,区别于其他 Route。
uri,路由指向的目的地 uri,即客户端请求最终被转发到的微服务。
predicate,断言(谓词)的作用是进行条件判断,只有断言都返回真,才会执行路由。
filter,过滤器用于修改请求和响应信息。

断言

Predicate(断言)又称谓词,用于条件判断,只有断言结果都为真,才会真正的执行路由。断言其本质就是定义路由转发的条件

Predicate 内置工厂

SpringCloud Gateway包括一些内置的谓词工厂(所有工厂都直接或间接的实现了RoutePredicateFactory接口),这些断言或谓词工程负责创建谓词对象,并通过这些谓词对象判断http请求的合法性。常见谓词工厂如下:

  • 基于Datetime类型的断言工厂

    1) AfterRoutePredicateFactory:判断请求日期是否晚于指定日期
    2) BeforeRoutePredicateFactory:判断请求日期是否早于指定日期
    3) BetweenRoutePredicateFactory:判断请求日期是否在指定时间段内

  • 基于header的断言工厂HeaderRoutePredicateFactory

    ​ 判断请求Header是否具有给定名称且值与正则表达式匹配。例如:- Header=X-Request-Id, \d+

  • 基于Method请求方法的断言工厂

    ​ MethodRoutePredicateFactory接收一个参数,判断请求类型是否跟指定的类型匹配。例如:-Method=GET

  • 基于Query请求参数的断言工厂,QueryRoutePredicateFactory

    ​ 接收两个参数,请求param和正则表达式, 判断请求参数是否具 有给定名称且值与正则表达式匹配。例如:-Query=pageSize,\d+

Redis

redis 是一个key-value存储系统

在Docker环境下启动redis
    docker start redis01
docker 中查看redis 服务
    docker ps
查看启动redis进程信息
	ps -ef|grep redis
进入redis容器
	docker exec -it redis01 bash
登录redis
	redis-cli

redis命令

查看信息  info
清空屏幕  clear
退出服务  exit
关闭服务  shutdown
系统帮助  help

数据存储操作

查看key          key *
存储k/v数据       set test 123
基于key获取数据   get test
清除数据          fushdb
清楚数据库数据    fushall

key有效时间设计
expire   设置生效时长(秒)   EXPIRE key seconds
persist  取消时长设置       persist key
pexpire  设置生效时长(毫秒)PEXPIRE key milliseconds

redis数据类型

String
	incr/incrby
	递增/指定增长数
	decr/decrby
	递减/指定减少数
	append
	尾部追加值
	strlen
	返回字符串长度
	mset/mget
	同时设置/获取多个键值
Hash	
	hset/hget
	赋值/取值
	hmset/hmget
	设置/获取多个值
	hexists
	属性是否存在
	hdel
	删除属性
	hkeys/hvals
	只获取字段名/字段值
List
	lpush   在key对应list的头部添加字符串元素
	rpush   在key对应list的尾部添加字符串元素
	del     清空集合元素
	linsert 在特定位置添加元素
    lset    设置指定下标元素值
    ltrim   保存指定key值范围的数据
    lpop    删除头部元素,返回删除元素
    rpop    删除头部元素,返回删除元素
    llen    返回key对应list的长度
    lindex  返回对应索引中元素
set 
	sadd      添加元素,重复添加返回0
	smembers  获取集合中成员
	scard     获取集合中成员个数
	smove     移动元素到另一个集合中
	sunion    合并集合	

行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效

  • 热点数据的限流规则是怎样的(主要是针对参数进行限流设计)
  • 热点数据中的特殊参数如何理解(热点限流中的某个参数值的阈值设计)
  • 对于热点数据的访问出现限流以后底层异常(ParamFlowException)

Sentinel系统规则

​ 监控服务器的状态,看服务器CPU、内存、IO等的使用率;主要目的就是保证服务器正常的运行,不能被某些应用搞崩溃了;而且在保证稳定的前提下,保持系统的最大吞吐量

系统规则

Load(仅对 Linux/Unix-like 机器生效):当系统 load1 超过阈值,且系统当前的并发线程数超过系统容量时才会触发系统保护。系统容量由系统的 maxQps * minRt 计算得出。设定参考值一般是 CPU cores * 2.5

CPU使用率:当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0)

RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒

线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护

入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护

分析
  • 如何理解sentinel中的系统规则?(是对所有链路的控制规则,是一种系统保护策略)
  • Sentinel的常用系统规则有哪些?(RT,QPS,CPU,线程,Load-linux,unix)
  • Sentinel系统保护规则被触发以后底层会抛出什么异常?(SystemBlockException)
Sentinel授权规则

​ 使用 Sentinel 的黑白名单控制的功能。黑白名单根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过

黑白名单规则(AuthorityRule)非常简单,主要有以下配置项:

  • 资源名:即限流规则的作用对象
  • 流控应用:对应的黑名单/白名单中设置的规则值,多个值用逗号隔开.
  • 授权类型:白名单,黑名单(不允许访问).

GateWay API网关

网关本质上要提供一个各种服务访问的入口,并提供服务接收并转发所有内外部的客户端调用,还有就是权限认证,限流控制

GateWay 使用

  1. 添加依赖

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-gateway</artifactId>
            </dependency>
    
  2. 创建application.yml

server:
  port: 9000
spring:
  application:
    name: sca-gateway
  cloud:
    gateway:
        routes: #配置网关路由规则
          - id: route01  #路由id,自己指定一个唯一值即可
            uri: http://localhost:8081/ #网关帮我们转发的url
            predicates: ###断言(谓词):匹配请求规则
              - Path=/nacos/provider/echo/**  #请求路径定义,此路径对应uri中的资源
            filters: ##网关过滤器,用于对谓词中的内容进行判断分析以及处理
              - StripPrefix=1 #转发之前去掉path中第一层路径,例如nacos

其中:路由(Route) 是 gateway 中最基本的组件之一,表示一个具体的路由信息载体。主要定义了下面的几个信息:

id,路由标识符,区别于其他 Route。
uri,路由指向的目的地 uri,即客户端请求最终被转发到的微服务。
predicate,断言(谓词)的作用是进行条件判断,只有断言都返回真,才会执行路由。
filter,过滤器用于修改请求和响应信息。

断言

Predicate(断言)又称谓词,用于条件判断,只有断言结果都为真,才会真正的执行路由。断言其本质就是定义路由转发的条件

Predicate 内置工厂

SpringCloud Gateway包括一些内置的谓词工厂(所有工厂都直接或间接的实现了RoutePredicateFactory接口),这些断言或谓词工程负责创建谓词对象,并通过这些谓词对象判断http请求的合法性。常见谓词工厂如下:

  • 基于Datetime类型的断言工厂

    1) AfterRoutePredicateFactory:判断请求日期是否晚于指定日期
    2) BeforeRoutePredicateFactory:判断请求日期是否早于指定日期
    3) BetweenRoutePredicateFactory:判断请求日期是否在指定时间段内

  • 基于header的断言工厂HeaderRoutePredicateFactory

    ​ 判断请求Header是否具有给定名称且值与正则表达式匹配。例如:- Header=X-Request-Id, \d+

  • 基于Method请求方法的断言工厂

    ​ MethodRoutePredicateFactory接收一个参数,判断请求类型是否跟指定的类型匹配。例如:-Method=GET

  • 基于Query请求参数的断言工厂,QueryRoutePredicateFactory

    ​ 接收两个参数,请求param和正则表达式, 判断请求参数是否具 有给定名称且值与正则表达式匹配。例如:-Query=pageSize,\d+

Redis

redis 是一个key-value存储系统

在Docker环境下启动redis
    docker start redis01
docker 中查看redis 服务
    docker ps
查看启动redis进程信息
	ps -ef|grep redis
进入redis容器
	docker exec -it redis01 bash
登录redis
	redis-cli

redis命令

查看信息  info
清空屏幕  clear
退出服务  exit
关闭服务  shutdown
系统帮助  help

数据存储操作

查看key          key *
存储k/v数据       set test 123
基于key获取数据   get test
清除数据          fushdb
清楚数据库数据    fushall

key有效时间设计
expire   设置生效时长(秒)   EXPIRE key seconds
persist  取消时长设置       persist key
pexpire  设置生效时长(毫秒)PEXPIRE key milliseconds

redis数据类型

String
	incr/incrby
	递增/指定增长数
	decr/decrby
	递减/指定减少数
	append
	尾部追加值
	strlen
	返回字符串长度
	mset/mget
	同时设置/获取多个键值
Hash	
	hset/hget
	赋值/取值
	hmset/hmget
	设置/获取多个值
	hexists
	属性是否存在
	hdel
	删除属性
	hkeys/hvals
	只获取字段名/字段值
List
	lpush   在key对应list的头部添加字符串元素
	rpush   在key对应list的尾部添加字符串元素
	del     清空集合元素
	linsert 在特定位置添加元素
    lset    设置指定下标元素值
    ltrim   保存指定key值范围的数据
    lpop    删除头部元素,返回删除元素
    rpop    删除头部元素,返回删除元素
    llen    返回key对应list的长度
    lindex  返回对应索引中元素
set 
	sadd      添加元素,重复添加返回0
	smembers  获取集合中成员
	scard     获取集合中成员个数
	smove     移动元素到另一个集合中
	sunion    合并集合	
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值