在审核文本内容的时候,我们可以调用第三方成熟的服务(例如阿里云的内容安全)来实现,但是基于不同的场景,第三方的服务不可能涉及到方方面面的敏感词,比如在游戏类的场景中,开挂一词算敏感词,但是在其它的场景中,开挂一词是一个正常的词汇。这时候需要我们根据不同场景自己维护一套敏感词,在文本审核的时候,需要验证文本中是否包含这些敏感词。
方案 | 说明 |
---|---|
数据库模糊查询 | 效率太低 |
String.indexOf(“”)查找 | 数据库量大的话也是比较慢 |
全文检索 | 分词再匹配 |
DFA算法 | 确定有穷自动机(一种数据结构) |
DFA全称为:Deterministic Finite Automaton,即确定有穷自动机。
存储:一次性的把所有的敏感词存储到了多个map中,就是下图表示这种结构
敏感词:冰毒、大麻、大坏蛋
package com.heima.utils.common; |
结果:
{冰毒=2, 法轮功=1} |
• Docker 是一个开源的应用容器引擎
• 诞生于 2013 年初,基于 Go 语言实现, dotCloud 公司出品(后改名为Docker Inc)
• Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上。
• 容器是完全使用沙箱机制,相互隔离
• 容器性能开销极低。
• Docker 从 17.03 版本之后分为 CE(Community Edition: 社区版) 和 EE(Enterprise Edition: 企业版)
1、yum 包更新到最新 |
提升下载速度
每个人的加速器地址不一样
直接将阿里云上提供的命令直接粘贴到命令行即可
查看刚才配置的文件是否存在 |
出现下图所示的内容表示配置成功
启动docker服务: |
查看镜像对应版本号的网站: https://hub.docker.com/
查看镜像: 查看本地所有的镜像 |
查看容器 |
参数说明:
• -i:保持容器运行。通常与 -t 同时使用。加入it这两个参数后,容器创建后自动进入容器中,退出容器后,容器自动关闭。
• -t:为容器重新分配一个伪输入终端,通常与 -i 同时使用。
• -d:以守护(后台)模式运行容器。创建一个容器在后台运行,需要使用docker exec 进入容器。退出后,容器不会关闭。
• -it 创建的容器一般称为交互式容器,-id 创建的容器一般称为守护式容器
• –name:为创建的容器命名。
进入容器 |
Docker中常见的应用安装部署,详细内容请参考下面文章
开发环境的搭建 | The Blog (qingling.icu)
5.1、部署MySQL
docker search mysql |
docker pull mysql:5.6 |
在/root目录下创建mysql目录用于存储mysql数据信息 |
docker run -id \ |
docker exec –it c_mysql /bin/bash |
5.2、部署Tomcat
docker search tomcat |
docker pull tomcat |
在/root目录下创建tomcat目录用于存储tomcat数据信息 |
docker run -id --name=c_tomcat \ |
参数说明:
-p 8080:8080:将容器的8080端口映射到主机的8080端口
-v $PWD:/usr/local/tomcat/webapps:将主机中当前目录挂载到容器的webapps
5.3 部署Nginx
docker search nginx |
docker pull nginx |
在/root目录下创建nginx目录用于存储nginx数据信息 |
user nginx; |
docker run -id --name=c_nginx \ |
5.4 部署Redis
docker search redis |
docker pull redis:5.0 |
docker run -id --name=c_redis -p 6379:6379 redis:5.0 |
./redis-cli.exe -h 192.168.149.135 -p 6379 |
示例-以谷粒商城项目的商品分类为例
数据中商品分类的数据表,所有的分类数据在同一张表中
商品分类的实体类
package com.atguigu.gulimall.product.entity; |
封装数据,供前端显示
controller层
/** |
service层,这里主要有一个单独的递归方法,使用的时候会改就行
/** |
返回数据的格式
{ |
前端代码
模板
<template> |
完整代码
<template> |
显示效果
页面的显示效果
使用的组件 在树形控件的基础上添加相关的代码
添加的时候弹出的对话框使用的组件
前端代码的实现
Tips:删除成功之后依然展开删除的节点
删除和添加的前端代码
<template> |
后端代码
删除的后端代码
/** |
添加的后端代码
/** |
实现的效果
]]>在写一个main主函数的时候可以直接在键盘上敲main ,然后根据提示补全全部(模板快捷键)
在写System.out.println();输出函数代码的时候可以直接在键盘上面敲sout,然后根据提示补全(模板快捷键)
在写for循环的时候,我们可以直接在键盘上面打出 fori 然后根据提示补全代码(模板快捷键)
删除当前行 Ctrl+D (并非默认Ctrl + D,默认为Ctrl + Y),需要我们自己设置
复制当前行,快速向下复制一行 Ctrl + Alt + 向下箭头(并非默认,需要我们自己设置)
代码补全 Alt + /
添加注释和取消注释 Ctrl + /
自动导入import java.util.Scanner;我们在键盘上敲Scanner in = new Scanner(System,in);的时候按快捷键Alt + Enter 可以自动导入import java.util.Scanner;
代码格式化(格式调整为正常格式,,让代码变整洁,并非删除代码) Ctrl + Alt +L
快速运行程序(并非默认,需要我们自己设置)Alt + R,如果要使用这个快捷键,我们要切换到当前的主类,否则运行的还是之前运行的程序
构造器快捷键 Alt + insert ,按住Ctrl可以选择任意个参数的构造器,也可以点击Select None选择无参构造器; 我们在写一个私有类的getXxx()和setXxx()方法的时候,我们可以使用这个快捷键,光标移动到”Getter and Setter“的位置并选择。
查看一个类的层级关系 Ctrl + H
将光标放在一个方法上,使用快捷键 Ctrl + B 可以快速定位到这个方法的代码
我们在键盘上面敲Scanner in = new Scanner(System.in);的时候,我们先敲new Scanner(System.in),再在后面加上new Scanner(System.in).var,按Enter,就会自动补全前面的代码;定义一个Person类对象的时候我们也可以使用这种方法,如new Person().var,按Enter,就会自动补全前面的代码,变成 Person person = new Person();
重写某个方法的时候我们可以直接打出这个方法名,然后根据提示,移动光标选择。
Alt+insert移动光标选择toString,可以重写toString方法,并输出对象的属性。
断点调试的快捷键:F7(跳入),F8(跳过),shift+F8(跳出),F9(resume,执行到下一个断点)
F7:跳入方法内
F8:逐行执行代码
shift+F8:跳出方法
一个普通类实现接口,就必须将该接口的所有方法都实现,我们在实现这些接口的时候可以使用快捷键Alt+Enter
Ctrl+Alt+t 可以快捷的使用try-catch
遍历一个数组,生成while循环,可以快捷生成,快捷键是 itit + 回车 ,增强的for循环的迭代器的快捷输入 是大写的 I + enter
Ctrl + J 可以显示所有的快捷模板
Ctrl + R 查找替换
Shift+Enter 快速向下换一行
Shift+Home 快速选中一行
Ctrl + F 全局查找
JWT(Json Web Token)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源。比如用在用户登录上。JWT最重要的作用就是对 token信息的防伪作用。
JWT的原理:
一个JWT由三个部分组成:公共部分、私有部分、签名部分。最后由这三者组合进行base64编码得到JWT。
1、 公共部分
主要是该JWT的相关配置参数,比如签名的加密算法、格式类型、过期时间等等。
Key=ATGUIGU
2、 私有部分
用户自定义的内容,根据实际需要真正要封装的信息。
userInfo{用户的Id,用户的昵称nickName}
3、 签名部分
SaltiP: 当前服务器的Ip地址!{linux 中配置代理服务器的ip}
主要用户对JWT生成字符串的时候,进行加密{盐值}
最终组成 key+salt+userInfo -> token!
base64编码,并不是加密,只是把明文信息变成了不可见的字符串。但是其实只要用一些工具就可以把base64编码解成明文,所以不要在JWT中放入涉及私密的信息。
<dependency> |
以下工具类根据情况任选一种即可
一种是在请求头中获取token,然后再获取用户的信息,另一种直接传入token,然后获取用户信息
package com.atguigu.yygh.common.helper; |
package com.atguigu.commonutils; |
1.登录成功之后返回token字符串
2.前端处理的思路
获取token字符串 -> 将token字符串和用户信息存放在cokkie中 -> 页面在cokkie中获取用户信息显示在页面上
前端登录与否的校验、后端网关登录与否的校验…….
之后补充完整的代码示例 这里没有遇到好的登录示例 ……….
]]> FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。
模板编写为FreeMarker Template Language (FTL)。它是简单的,专用的语言, 不是 像PHP那样成熟的编程语言。 那就意味着要准备数据在真实编程语言中来显示,比如数据库查询和业务运算, 之后模板显示已经准备好的数据。在模板中,你可以专注于如何展现数据, 而在模板之外可以专注于要展示什么数据。
技术选型对比
技术 | 说明 |
---|---|
Jsp | Jsp 为 Servlet 专用,不能单独进行使用 |
Velocity | Velocity从2010年更新完 2.0 版本后,7年没有更新。Spring Boot 官方在 1.4 版本后对此也不在支持 |
thmeleaf | 新技术,功能较为强大,但是执行的效率比较低 |
freemarker | 性能好,强大的模板语言、轻量 |
此处为了方便建的是子模块,测试使用可以创建一个单独的工程
<dependencies> |
server: |
在resources下创建templates,此目录为freemarker的默认模板存放目录
在templates下创建模板文件 01-basic.ftl ,模板中的插值表达式最终会被freemarker替换成具体的数据
|
package com.heima.freemarker; |
package com.heima.freemarker.entity; |
package com.heima.freemarker.controller; |
访问 http://localhost:8881/basic
1、注释,即<#– –>,介于其之间的内容会被freemarker忽略
<#--我是一个freemarker注释--> |
2、插值(Interpolation):即 ${..}
部分,freemarker会用真实的值代替**${..}
**
Hello ${name} |
3、FTL指令:和HTML标记类似,名字前加#予以区分,Freemarker会解析标签中的表达式或逻辑。
<# >FTL指令</#> |
4、文本,仅文本信息,这些不是freemarker的注释、插值、FTL指令的内容会被freemarker忽略解析,直接输出内容。
<#--freemarker中的普通文本--> |
1、数据模型:
在HelloController中新增如下方法:
|
2、模板:
在templates中新增02-list.ftl
文件
|
实例代码:
|
上面代码解释:
${k_index}:index:得到循环的下标,使用方法是在stu后边加”_index”,它的值是从0开始,${stu_index+1}下标从1开始
展示效果:
if 指令即判断指令,是常用的FTL指令,freemarker在解析时遇到if会进行判断,条件为真则输出if中间的内容,否则跳过内容不再输出。
<#if ></if> |
1、数据模型:
使用list指令中测试数据模型,判断名称为小红的数据字体显示为红色。
2、模板:
<table> |
实例代码:
<table> |
3、输出:
1、算数运算符
FreeMarker表达式中完全支持算术运算,FreeMarker支持的算术运算符包括:
+
-
*
/
%
模板代码
<b>算数运算符</b> |
除了 + 运算以外,其他的运算只能和 number 数字类型的计算。
2、比较运算符
=
或者==
:判断两个值是否相等. !=
:判断两个值是否不等. >
或者gt
:判断左边值是否大于右边值 >=
或者gte
:判断左边值是否大于等于右边值 <
或者lt
:判断左边值是否小于右边值 <=
或者lte
:判断左边值是否小于等于右边值= 和 == 模板代码
|
Controller 的 数据模型代码
|
比较运算符注意
=
和!=
可以用于字符串、数值和日期来比较是否相等=
和!=
两边必须是相同类型的值,否则会产生错误"x"
、"x "
、"X"
比较是不等的.因为FreeMarker是精确比较gt
等字母运算符代替>
会有更好的效果,因为 FreeMarker会把>
解释成FTL标签的结束字符<#if (x>y)>
3、逻辑运算符
逻辑运算符只能作用于布尔值,否则将产生错误 。
模板代码
<b>逻辑运算符</b> |
1、判断某变量是否存在使用 “??”
用法为:variable??,如果该变量存在,返回true,否则返回false
例:为防止stus为空报错可以加上判断如下:
<#if stus??> |
2、缺失变量默认值使用 “!”
使用!要以指定一个默认值,当变量为空时显示默认值
例: ${name!’’}表示如果name为空显示空字符串。
如果是嵌套对象则建议使用()括起来
例: ${(stu.bestFriend.name)!’’}表示,如果stu或bestFriend或name为空默认显示空字符串。
内建函数语法格式: 变量+?+函数名称
1、某个集合的大小
${集合名?size}
2、日期格式化
显示年月日: ${today?date}
显示时分秒:${today?time}
显示日期+时间:${today?datetime}
自定义格式化: ${today?string("yyyy年MM月")}
3、内建函数c
model.addAttribute(“point”, 102920122);
point是数字型,使用${point}会显示这个数字的值,每三位使用逗号分隔。
如果不想显示为每三位分隔的数字,可以使用c函数将数字型转成字符串输出
${point?c}
4、将json字符串转成对象
一个例子:
其中用到了 assign标签,assign的作用是定义一个变量。
<#assign text="{'bank':'工商银行','account':'10101920201920212'}" /> |
模板代码:
|
内建函数模板页面:
<!DOCTYPE html> |
内建函数Controller数据模型:
|
根据模板文件生成html文件
①:修改application.yml文件,添加以下模板存放位置的配置信息,完整配置如下:
server: |
②:在test下创建测试类
package com.heima.freemarker.test; |
③ :运行结果
]]>视频教程2:https://www.bilibili.com/video/BV1RU4y147eZ?vd_source=aee5e475191b69e6c781059ab6662584
具体的看视频 急速入门
<!-- 爬取视频或者音频需要使用别的依赖 --> |
下面我们以爬取京东上的商品的图片和价格为例
package com.jason; |
package com.jason; |
package com.jason; |
官网下载:https://git-scm.com/downloads
以下的操作在下载安装完毕之后进行 |
常用命令
]]>SpringBoot + zxing 生成二维码
SpringBoot + qrcode生成二维码
原视频地址: Java生成二维码教程 | 两小时学会Java生成二维码
源码地址: JasonsGong/two-dimensional-code: 使用java生成二维码 (github.com)
<dependencies> |
|
|
|
由于后端部分没有编写,这时我们访问项目的时候会返回404
package com.mine.code.controller; |
完整的项目结构
1.编写qrcode.html页面和跳转到该页面的controller
|
/** |
2.编写后端生成二维码逻辑
/** |
访问 http://localhost:8080/logo 测试
实现的效果
这里就不在重新创建工程了,直接在上面工程的基础上操作
<dependency> |
1.创建github-qrcode.html文件
|
2.编写controller处理请求
package com.mine.code.controller; |
3.访问测试
前端页面延续使用上面的github-qrcode.html
1.后端代码的编写
package com.mine.code.controller; |
实现的效果
前端页面延续使用上面的github-qrcode.html
1.后端代码的编写
package com.mine.code.controller; |
实现的效果
前端页面延续使用上面的github-qrcode.html
1.后端代码的编写
package com.mine.code.controller; |
实现的效果
前端页面延续使用上面的github-qrcode.html
1.后端代码的编写
package com.mine.code.controller; |
实现的效果
前端页面延续使用上面的github-qrcode.html
1.后端代码的编写
package com.mine.code.controller; |
实现的效果
二维码中的每一个像素点都是上传的图片,生成的速度很慢
]]>软件:VMware 、Linux镜像文件
VMWare虚拟机安装Linux教程 | The Blog (qingling.icu)
2.1.删除自带的JDK
查看系统是否有java环境 |
2.2.安装JDK
创建一个文件夹,将JDK8上传到这个文件夹中去 |
Docker容器化技术 | The Blog (qingling.icu)
3.1.1 安装Docker
1、yum 包更新到最新 |
3.1.2 配置镜像加速服务
1.创建相关的目录 |
3.1.3 启动Docker服务
启动docker服务: |
下载mysql的镜像文件 这里以下载mysql5.7为例 |
下载redis的镜像文件 直接下载最新的 |
3.4.1 安装ElasticSearch
拉取ElasticSearch |
安装成功之后:
3.4.2 安装Kibana(可视化界面)
拉取Kibana |
安装成功之后
3.4.3 安装IK分词器
安装地址: https://github.com/medcl/elasticsearch-analysis-ik
切换到es挂载在注解的plugins目录 |
方法一:
创建nginx容器挂载的目录 |
方法二
先在宿主机的/opt/nginx/conf/目录下创建nginx.conf配置文件并加入相关的配置
user root; |
创建容器
docker run -id --name=nginx \ |
直接执行运行rabbitMQ容器的命令,没有镜像会自动下载 |
账号:guest
密码:guest
拉取镜像 |
测试访问:http://虚拟机ip:8848/nacos
]]>本篇博客转载于传智播客黑马程序员,只作在线笔记使用,详细的课程资料请关注黑马程序员!
Linux相关的书籍: 技术书籍-Linux指令大全 | The Blog (qingling.icu)
Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。它能运行主要的UNIX工具软件、应用程序和网络协议。支持32位和64位硬件。Linux继承了Unix以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。
Linux安装教程: VMWare虚拟机安装Linux教程 | The Blog (qingling.icu)
Linux设置静态IP:Linux设置静态IP | The Blog (qingling.icu)
安装教程: Windows10的Linux子系统WSL的安装和使用 | The Blog (qingling.icu)
功能:列出文件夹信息
语法:ls [-l -h -a] [参数]
在Linux中以 .
开头的,均是隐藏的。
默认不显示出来,需要 -a
选项才可查看到。
功能:展示当前工作目录
语法:pwd
功能:切换工作目录
语法:cd [目标目录]
参数:目标目录,要切换去的地方,不提供默认切换到 当前登录用户HOME目录
每一个用户在Linux系统中都有自己的专属工作目录,称之为HOME目录。
/home/用户名
/root
FinalShell登陆终端后,默认的工作目录就是用户的HOME目录
相对路径,==非==/
开头的称之为相对路径
相对路径表示以 当前目录
作为起点,去描述路径,如 test/a.txt
,表示当前工作目录内的test文件夹内的a.txt文件
绝对路径,==以==/
开头的称之为绝对路径
绝对路径从 根
开始描述路径
.
,表示当前,比如./a.txt,表示当前文件夹内的 a.txt
文件..
,表示上级目录,比如 ../
表示上级目录,../../
表示上级的上级目录~
,表示用户的HOME目录,比如 cd ~
,即可切回用户HOME目录功能:创建文件夹
语法:mkdir [-p] 参数
功能:创建文件
语法:touch 参数
功能:查看文件内容
语法:cat 参数
功能:查看文件,可以支持翻页查看
语法:more 参数
空格
键翻页q
退出查看功能:复制文件、文件夹
语法:cp [-r] 参数1 参数2
示例:
功能:移动文件、文件夹
语法:mv 参数1 参数2
功能:删除文件、文件夹
语法:rm [-r -f] 参数...参数
rm命令很危险,一定要注意,特别是切换到root用户的时候。
功能:查看命令的程序本体文件路径
语法:which 参数
jason@DESKTOP-PC4GEUR:~$ which pwd |
功能:搜索文件
语法1按文件名搜索:find 路径 -name 参数
*
test表示搜索任意以test结尾的文件功能:过滤关键字
语法:grep [-n] 关键字 文件路径
参数文件路径,可以作为管道符的输入
功能:统计
语法:wc [-c -m -l -w] 文件路径
参数文件路径,可作为管道符的输入
写法:|
功能:将符号左边的结果,作为符号右边的输入
示例:
cat a.txt | grep itheima
,将cat a.txt的结果,作为grep命令的输入,用来过滤 itheima
关键字
可以支持嵌套:
cat a.txt | grep itheima | grep itcast
功能:输出内容
语法:echo 参数
功能:被两个反引号包围的内容,会作为命令执行
示例:
功能:查看文件尾部内容
语法:tail [-f] 参数
功能:查看文件头部内容
语法:head [-n] 参数
功能:将符号左边的结果,输出到右边指定的文件中去
>
,表示覆盖输出>>
,表示追加输出我们学习的一系列Linux命令,它们所拥有的选项都是非常多的。
比如,简单的ls命令就有:-a -A -b -c -C -d -D -f -F -g -G -h -H -i -I -k -l -L -m -n -N -o -p -q -Q -r-R -s -S -t -T -u -U -v -w -x -X -1等选项,可以发现选项是极其多的。
课程中, 并不会将全部的选项都进行讲解,否则,一个ls命令就可能讲解2小时之久。
课程中,会对常见的选项进行讲解, 足够满足绝大多数的学习、工作场景。
可以通过:命令 --help
查看命令的帮助手册
可以通过:man 命令
查看某命令的详细手册
yum 和 apt 均需要root权限
功能:控制系统服务的启动关闭等
语法:systemctl start | stop | restart | disable | enable | status 服务名
功能:创建文件、文件夹软链接(快捷方式)
语法:ln -s 参数1 参数2
语法:date [-d] [+格式化字符串]
-d 按照给定的字符串显示日期,一般用于日期计算
格式化字符串:通过特定的字符串标记,来控制显示的日期格式
示例:
按照2022-01-01的格式显示日期
按照2022-01-01 10:00:00的格式显示日期
-d选项日期计算
支持的时间标记为:
修改时区为中国时区
功能:同步时间
安装:yum install -y ntp
启动管理:systemctl start | stop | restart | status | disable | enable ntpd
手动校准时间:ntpdate -u ntp.aliyun.com
格式:a.b.c.d
特殊IP:
查看ip:ifconfig
功能:Linux系统的名称
查看:hostname
设置:hostnamectl set-hostname 主机名
修改VMware网络,参阅PPT,图太多
设置Linux内部固定IP
修改文件:/etc/sysconfig/network-scripts/ifcfg-ens33
示例文件内容:
TYPE="Ethernet" |
功能:查看进程信息
语法:ps -ef
,查看全部进程信息,可以搭配grep做过滤:ps -ef | grep xxx
功能:查看端口占用
用法:netstat -anp | grep xxx
测试网络是否联通
语法:ping [-c num] 参数
功能:查看主机运行状态
语法:top
,查看基础信息
可用选项:
交互式模式中,可用快捷键:
查看磁盘占用
查看CPU、磁盘的相关信息
查看网络统计
.bashrc
文件/etc/profile
记录了执行程序的搜索路径
可以将自定义路径加入PATH内,实现自定义命令在任意地方均可执行的效果
可以取出指定的环境变量的值
语法:$变量名
示例:
echo $PATH
,输出PATH环境变量的值
echo ${PATH}ABC
,输出PATH环境变量的值以及ABC
如果变量名和其它内容混淆在一起,可以使用${}
tar -zcvf 压缩包 被压缩1...被压缩2...被压缩N
zip [-r] 参数1 参数2 参数N
tar -zxvf 被解压的文件 -C 要解压去的地方
unzip [-d] 参数
切换用户
语法:su [-] [用户]
比如:
itheima ALL=(ALL) NOPASSWD: ALL |
在visudo内配置如上内容,可以让itheima用户,无需密码直接使用 sudo
修改文件、文件夹权限
语法:chmod [-R] 权限 参数
权限可以用3位数字来代表,第一位数字表示用户权限,第二位表示用户组权限,第三位表示其它用户权限。数字的细节如下:r记为4,w记为2,x记为1,可以有:
权限,要设置的权限,比如755,表示:rwxr-xr-x
参数,被修改的文件、文件夹
选项-R,设置文件夹和其内部全部内容一样生效
示例:
• chmod u=rwx,g=rx,o=x hello.txt ,将文件权限修改为:rwxr-x–x
• 其中:u表示user所属用户权限,g表示group组权限,o表示other其它用户权限
• chmod -R u=rwx,g=rx,o=x test,将文件夹test以及文件夹内全部内容权限设置为:rwxr-x–x
除此之外,还有快捷写法:chmod 751 hello.txt,所以751表示: rwx(7) r-x(5) –x(1)[数字代表的含义见上面]
rwx的含义
• r表示读权限
• w表示写权限
• x表示执行权限
针对文件、文件夹的不同,rwx的含义有细微差别
•r,针对文件可以查看文件内容
•针对文件夹,可以查看文件夹内容,如ls命令
•w,针对文件表示可以修改此文件
•针对文件夹,可以在文件夹内:创建、删除、改名等操作
•x,针对文件表示可以将文件作为程序执行
•针对文件夹,表示可以更改工作目录到此文件夹,即cd进入
举例:drwxr-xr-x,表示:
•这是一个文件夹,首字母d表示
•所属用户(右上角图序号2)的权限是:有r有w有x,rwx
•所属用户组(右上角图序号3)的权限是:有r无w有x,r-x (-表示无此权限)
•其它用户的权限是:有r无w有x,r-x
修改文件、文件夹所属用户、组
语法:chown [-R] [用户][:][用户组] 文件或文件夹
getent group
,查看系统全部的用户组
getent passwd
,查看系统全部的用户
查看系统全部的环境变量
语法:env
永久性地在防火墙配置中打开TCP的9000端口 |
用于查看历史输入的命令
root@DESKTOP-PC4GEUR:/# history |
官网:1Panel - 现代化、开源的 Linux 服务器运维管理面板
官方文档:1Panel 文档
1Panel是飞致云旗下的一款现代化、开源的 Linux 服务器运维管理面板
详细安装教程见官方文档:在线安装 - 1Panel 文档
linux命令一键安装并启动 |
安装成功后浏览器访问http://目标服务器 IP 地址:目标端口/安全入口
进入运维面板(安装成功后根据命令行返回链接和密码信息访问并登录面板即可)
简介
Fail2Ban 是一款入侵防御软件,可以保护服务器免受暴力攻击。 它是用 Python 编程语言编写的。 Fail2Ban 基于auth 日志文件工作,默认情况下它会扫描所有 auth 日志文件,如 /var/log/auth.log、/var/log/apache/access.log 等,并禁止带有恶意标志的IP,比如密码失败太多,寻找漏洞等等标志。通常,Fail2Ban 用于更新防火墙规则,用于在指定的时间内拒绝 IP 地址。 它也会发送邮件通知。 Fail2Ban 为各种服务提供了许多过滤器,如 ssh、apache、nginx、squid、named、mysql、nagios 等。(简单来说就是避免别人暴力破解服务器的连接账号密码信息,入侵服务器)
安装
详细教程: Fail2ban - 1Panel 文档
1、安装 epel 源
yum install -y epel-release |
2、安装 Fail2ban
yum install -y fail2ban |
3、启动 Fail2ban 服务
systemctl start fail2ban |
4、开机自启动
systemctl enable fail2ban |
5、查看 Fail2ban 服务状态
systemctl status fail2ban |
默认配置 (后续自定义的配置直接在运维面板中更改即可)
#DEFAULT-START |
验证
]]>1.在C盘的根目录下创建如下的文件夹C:\maven\maven-repository
2.任选下面的两个配置文件中的一个放在C:\maven目录下 配置文件的名字为 settings.xml
3.在IDEA中配置本地仓库的位置和配置文件的位置
|
|
|
Elasticsearch 是一个分布式、RESTful 风格的搜索和数据分析引擎,能够解决不断涌现出的各种用例。作为 Elastic Stack 的核心,Elasticsearch 会集中存储您的数据,让您飞快完成搜索,微调相关性,进行强大的分析,并轻松缩放规模。
1.Index(索引)
动词:相当于mysql的insert
名词:相当于mysql的database
2.Type(类型)
在index中可以定义一个或者多个类型
类似于mysql中的Table;每一种类型的数据放在一起
3.Document(文档)
保存在某个索引(index)下,某种类型(Type)的一个数据(Document),文档是JSON格式的,
Document就是像mysql中的某个Table里面的内容。
4.倒排索引
拉取ElasticSearch |
拉取Kibana |
GET/_cat/nodes 查看所有节点
GET/_cat/health 查看es的健康状态
GET/_cat/master 查看主节点
GET/_cat/indices 查看所有索引
保存操作可以发送PUT或者POST请求
put需要在路径上指定id
post请求可以不指定id,自动生成一个唯一标识
例:PUT customer/external/1 在customer索引下的external类型下保存1号数据为
-> PUT customer/external/1 |
返回的数据解析
//_开头的数据称为元数据 |
-> GET customer/external/1 |
返回数据解析
{ |
POST customer/external/1/_update //对比原先的数据,和原先的一样_version和_seq_no不会发生改变 |
使用POST customer/external/1/_update 的方式进行更新会对比原先的数据,和原先的一样version和_seq_no不会发生改变,下面是我们使用同样的数据发送两次更新请求,第二次的result显示”noop”,没有进行操作
DELETE customer/external/1 |
删除文档
删除索引
Kibana的使用
批量保存数据
POST /customer/external/_bulk |
复杂批量操作
POST /_bulk |
POST /bank/account/_bulk |
ES支持两种基本的方式检索:
一切检索从_search开始
GET bank/_search //检索bank下所有信息,包含type和docs |
完整的响应结果
{ |
GET /bank/_search |
GET /bank/_search |
根据某个字段的值进行匹配查询
//查询account_number是20的数据 |
模糊匹配
对检索的条件进行分词匹配,按照评分的进行排序
//查询地址中包含mill或者lane或者mill lane的数据 |
匹配的短语不分割进行匹配,一整个进行匹配
//查询地址中包含Mill lane短语的数据 |
//查询state和address中包含mill的数据 |
//查询性别是M和地址中包含mill并且年龄不是28,lastname最好是Hines的数据 |
使用Filter进行检索相比上面的检索,不会影响过滤结果的得分(上面的检索都可以通过Filter实现)
举例:未使用filter的查询:
//查询年龄在18-20之间的所有数据 |
有得分的变化
使用了Filter的查询:
GET /bank/_search |
没有得分的变化
Filter可以和上面的检索条件一起使用
GET /bank/_search |
和match一样,匹配某个属性的值。全文检索字段用match,其他非text字段匹配用term(精确性字段推荐使用term,例如年龄,数量等)
GET /bank/_search |
聚合提供了从数据中分组和提取数据的能力.类似于MySql中的Group by函数
搜索address中包含mill的所有人的年龄分布以及平均年龄
//搜索address中包含mill的所有人的年龄分布以及平均年龄 |
按照年龄聚合,并且获取这些年龄段的这些人的平均薪资[子聚合]
//按照年龄聚合,并且获取这些年龄段的这些人的平均薪资 |
查询所有年龄分布,并且这些年龄段中男的平均工资和女的平均工资以及这个年龄段的总体平均薪资
GET /bank/_search |
指定ES中数据的数据类型
在创建索引的时候我们可以指定属性名的数据类型
##创建一个新的索引,指定索引下字段的映射关系 |
##添加一个映射 |
对于已经存在的映射字段,我们不能更新,更新必须创建新的索引进行数据迁移
1.创建一个新的索引并指定映射
PUT /new_bank |
2.数据迁移
//新版本的写法(6.0以后) |
安装地址: https://github.com/medcl/elasticsearch-analysis-ik
切换到es挂载在注解的plugins目录 |
测试使用
1.安装nginx,用作设置ik的远程分词库
创建nginx容器挂载的目录 |
2.配置自定义的词库
在nginx外部挂载的html目录下创建相应的目录 |
3.配置es远程词库的地址
切换到ik分词器配置文件所在的目录 |
重启es |
4.测试一下(这时我们使用网络中新颖的词就可以识别成一个词语)
1.导入依赖
<!--指定版本--> |
2.编写配置文件
|
3.测试使用
package com.atguigu.gulimall.search; |
/** |
/** |
/** |
/** |
/** |
/** |
/** |
/** |
/** |
/** |
示例的完整代码
package com.atguigu.gulimall.search; |
下载之后直接解压使用(下载之前看电脑上有没有mysql的服务,如果有先删除),解压的文件路径最好不要有中文
查看的方式
Win + R 输入 services.msc 回车打开 找是否有一个名为mysqld的服务 |
删除的指令
sc delete mysql |
Mysql5.7地址:https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.19-winx64.zip
添加环境变量 : 电脑-属性-高级系统设置-环境变量,在Path 环境变量增加mysql的安装目录\bin目录
my.ini
[client] |
1.下载mysql的服务
使用管理员身份打开 cmd , 并切换到mysql-5.7.19-winx64\bin 目录下, 执行 mysqld -install
cd **/**/**/mysql-5.7.19-winx64\bin |
2.初始化数据
mysqld --initialize-insecure --user=mysql |
#开启mysql的服务 |
#9. 进入mysql 管理终端: 初次登录没有密码,直接按两下回车键进入 |
注释掉my.ini中的跳过权限检查 #skip-grant-tables
net stop mysql |
安装失败,删除服务,重新安装
推荐SQLyog Navicat
查看是否为静态ip |
如图所示的是动态IP,我们要配置静态IP
1.修改配置文件中的配置
进入配置文件的命令 |
2.重启网络
重启网络的配置 |
在主机上使用ping命令ping 刚才的设置的静态IP也可以ping通
虚拟机ping外部也可以ping通
]]>注解 mybatisPlus提供的注解
公共字段的自动填充 逻辑删除 乐观锁 雪花算法生成主键
]]> 对于每个实体类共有的属性字段,例如创建时间、修改时间、创建人、修改人,我们可以使用公共字段填充,来统一填充这些字段,这样我们在创建这些实体类的对象的时候就不需要set这些属性,实现丝滑开发
//插入的时候填充 |
第一种配置的方法
package com.itheima.config; |
第二种配置的方法
|
1.配置全局的配置文件(可以省略)
mybatis-plus: |
2.配置逻辑删除的组件(3.1.1版本之后不需要这一步,可以省略)
import com.baomidou.mybatisplus.core.injector.ISqlInjector; |
3.在实体类上逻辑删除的字段上加上逻辑删除的注解
//value:表示不删除对应的值 |
Mybatis
Spring SpringMVC MyBatis
定时任务可以单独建立一个包 package com.atguigu.schedule
加上@Component注解,交给spring管理,启动这个模块,定时任务就开启了
package com.atguigu.schedule; |
什么是swagger2
编写和维护接口文档是每个程序员的职责,根据Swagger2可以快速帮助我们编写最新的API接口文档,再也不用担心开会前仍忙于整理各种资料了,间接提升了团队开发的沟通效率。
常用注解
swagger通过注解表明该接口会生成文档,包括接口名、请求方法、参数、返回信息等等
@Api:修饰整个类,描述Controller的作用
@ApiOperation:描述一个类的一个方法,或者说一个接口
@ApiParam:单个参数描述
@ApiModel:用对象来接收参数
@ApiModelProperty:用对象接收参数时,描述对象的一个字段
@ApiImplicitParam:一个请求参数
@ApiImplicitParams:多个请求参数
注意:Swagger2和SpringBoot存在版本兼容的问题,选择的时候要根据SpringBoot的版本进行选择
<dependency> |
|
//在项目的启动类加上该注解,swagger就会扫描如下包下的controller方法,生成接口文档 |
//浏览器访问该网址 |
原视频地址: 黑马程序员 MySQL数据库入门到精通,从mysql安装到mysql高级、mysql优化全囊括
全称 Structured Query Language,结构化查询语言。操作关系型数据库的编程语言,定义了一套操作关系型数据库统一标准 。
连接本地mysql的命令
mysql -u root -p |
常用的DDL操作
-- ------------------------------------------------------数据库的操作------------------------------------------------------------------------ |
Mysql的数据类型
数值数据类型
字符串数据类型
日期时间类型
-- ------------------------------------------------DML-增删改操作--------------------------------------------------- |
测试数据准备
-- 数据准备 |
-- 查看插入的数据 |
语法: SELECT 字段列表 FROM 表名 WHERE 条件列表 ;
举例
-- 条件查询 |
语法: SELECT 聚合函数(字段列表) FROM 表名 ;
注意 : NULL值是不参与所有聚合函数运算的
-- 聚合函数 |
语法: SELECT 字段列表 FROM 表名 [ WHERE 条件 ] GROUP BY 分组字段名 [ HAVING 分组 后过滤条件 ];
where与having区别
注意事项:
• 分组之后,查询的字段一般为聚合函数和分组字段,查询其他字段无任何意义。
• 执行顺序: where > 聚合函数 > having 。
• 支持多字段分组, 具体语法为 : group by columnA,columnB
-- 分组查询 |
语法: SELECT 字段列表 FROM 表名 ORDER BY 字段1 排序方式1 , 字段2 排序方式2 ;
注意事项:
• 如果是升序, 可以不指定排序方式ASC,因为升序ASC是默认值 ;
• 如果是多字段排序,当第一个字段值相同时,才会根据第二个字段进行排序 ;
-- 排序查询 |
语法: SELECT 字段列表 FROM 表名 LIMIT 起始索引, 查询记录数 ;
注意事项:
• 起始索引从0开始,起始索引 = (查询页码 - 1)* 每页显示记录数。
• 分页查询是数据库的方言,不同的数据库有不同的实现,MySQL中是LIMIT。
• 如果查询的是第一页数据,起始索引可以省略,直接简写为 limit 10。
-- 分页查询 |
-- DQL语句案例 |
总结
查询用户: select * from mysql.user;
创建用户:CREATE USER '用户名'@'主机名' IDENTIFIED BY '密码';
修改用户密码: ALTER USER '用户名'@'主机名' IDENTIFIED WITH mysql_native_password BY '新密码' ;
删除用户:DROP USER '用户名'@'主机名' ;
注意事项:
• 在MySQL中需要通过用户名@主机名的方式,来唯一标识一个用户• 主机名可以使用 % 通配
• 这类SQL开发人员操作的比较少,主要是DBA( Database Administrator 数据库管理员)使用
-- 切换到系统中自带的mysql数据库 |
查询权限: SHOW GRANTS FOR '用户名'@'主机名' ;
授予权限: GRANT 权限列表 ON 数据库名.表名 TO '用户名'@'主机名';
撤销权限: REVOKE 权限列表 ON 数据库名.表名 FROM '用户名'@'主机名';
注意事项:
• 多个权限之间,使用逗号分隔
• 授权时, 数据库名和表名可以使用 * 进行通配,代表所有
-- 权限控制 |
总结
全部的SQL
-- ------------------------------------------------------数据库的操作------------------------------------------------------------------------ |
-- concat函数(字符串拼接) |
案例
-- 由于业务需求变更,企业员工的工号,统一为5位数,目前不足5位数的全部在前面补0 比如:1号员工的工号应该为00001 |
-- 数值函数 |
案例
-- 通过数据库函数生成一个的随机的六位验证码 |
-- 日期函数 |
案例
-- 查询所有员工的入职天数,并根据入职天数倒叙排序 |
-- 流程控制函数 |
案例
-- 案例:统计班级各个学员的成绩,展示规则如下: |
函数相关的全部sql
-- ----------------------------------------------------------函数-------------------------------------------------------- |
概念:约束是作用于表中字段上的规则,用于限制存储在表中的数据。
目的:保证数据库中数据的正确、有效性和完整性。
分类 :
案例需求: 根据需求,完成表结构的创建,需求如下:
对应的建表语句为:
create table tb_user( |
建立外键: ALTER TABLE 表名 ADD CONSTRAINT 外键名称 FOREIGN KEY (外键字段名) REFERENCES 主表 (主表列名) ;
删除外键: ALTER TABLE 表名 DROP FOREIGN KEY 外键名称;
举例
-- 外键约束 |
详细介绍见PDF文件 https://qingling.icu/posts/50465.html
添加了外键之后,再删除父表数据时产生的约束行为,我们就称为删除/更新行为。具体的删除/更新行为有以下几种:
具体语法为:
ALTER TABLE 表名 ADD CONSTRAINT 外键名称 FOREIGN KEY (外键字段) REFERENCES 主表名 (主表字段名) ON UPDATE CASCADE ON DELETE CASCADE;
约束相关的sql
-- ---------------------------------------------------约束演示------------------------------------------------------------ |
一对多
多对多
一对一
-- 多表关系 |
-- 数据准备 |
-- 内连接演示 |
一旦为表起了别名,就不能再使用表名来指定对应的字段了,此时只能够使用别名来指定字段。
-- 外连接 |
注意事项:左外连接和右外连接是可以相互替换的,只需要调整在连接查询时SQL中,表结构的先后顺序就可以了。而我们在日常开发使用时,更偏向于左外连接。
-- 自连接 |
-- 联合查询 |
union all查询出来的结果,仅仅进行简单的合并,并未去重
union 联合查询,会对查询出来的结果进行去重处理
标量子查询
子查询返回的结果是单个值(数字、字符串、日期等),最简单的形式,这种子查询称为标量子查询。常用的操作符:= <> > >= < <=
-- 标量子查询 |
列子查询
子查询返回的结果是一列(可以是多行),这种子查询称为列子查询。常用的操作符:IN 、NOT IN 、 ANY 、SOME 、 ALL
-- 列子查询 |
行子查询
子查询返回的结果是一行(可以是多列),这种子查询称为行子查询。常用的操作符:= 、<> 、IN 、NOT IN
-- 行子查询 |
表子查询
子查询返回的结果是多行多列,这种子查询称为表子查询。常用的操作符:IN
-- 表子查询 |
多表查询相关的sql语句
-- ---------------------------------------------多表查询-------------------------------------------------------- |
题目
数据准备
-- 数据准备 |
题解
-- 题解 |
总结
事务 是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。就比如: 张三给李四转账1000块钱,张三银行账户的钱减少1000,而李四银行账户的钱要增加1000。 这一组操作就必须在一个事务的范围内,要么都成功,要么都失败。
注意: 默认MySQL的事务是自动提交的,也就是说,当执行完一条DML语句时,MySQL会立即隐式的提交事务。
数据准备
-- 数据准备 |
控制事务一
控制事务二
-- 开启事务 |
为了解决并发事务所引发的问题,在数据库中引入了事务隔离级别,主要有以下几种:
“X” 表示不会出现
-- 查看事务隔离级别 |
小结
基础篇全部的Sql
-- ------------------------------------------------------数据库的操作------------------------------------------------------------------------ |
简介
存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式 。存储引擎是基于表的,而不是 基于库的,所以存储引擎也可被称为表类型。我们可以在创建表的时候,来指定选择的存储引擎,如果 没有指定将自动选择默认的存储引擎。
查询一张数据库表的建表语句
-- 查询建表语句 |
建表的时候指定存储引擎的语句
存储引擎的区别
在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎。对于复杂的应用系统,还可以根据 实际情况选择多种存储引擎进行组合。
一句话:项目中绝大多数的时候使用的都是InnoDB
索引(index)是帮助MySQL高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据, 这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。
注:下面的二叉树并不是一个真实的索引结构,而是一个举例
默认的索引结构是B+树索引结构
不同类型的存储引擎对不同的索引结构的支持情况
二叉树和红黑树的不足
B树
B+树
MySql中的B+树结构
当我们要查询name值是Arm的数据的时候,会先通过二级索引查询,然后通过聚集索引查询这一行的信息(回表查询)
思考题
第一条的执行效率高,第二条要回表查询(先查询name所在行对应的主键id,然后通过主键id查询这一行的数据)
创建|查看|删除语法
举例
-- 查看系统的状态信息 |
-- 查询慢查询日志是否开启 |
-- 是否支持profile详情功能 |
现在有一个有着10000000数据的表(tb_sku),我们根据sn这一字段查询某一条具体的数据,没有建立索引之前的查询耗费的时间是20多秒,建立索引耗费大约90秒,为sn字段建立索引之后,查询耗费时间大概是零点几秒。
100万的数据如果使用insert插入的话需要10分钟左右,如果使用文件的方式导入,只需要10多秒。
视图(View)是一种虚拟存在的表。视图中的数据并不在数据库中实际存在,行和列数据来自定义视图的查询中使用的表,并且是在使用视图时动态生成的。通俗的讲,视图只保存了查询的SQL逻辑,不保存查询结果。所以我们在创建视图的时候,主要的工作就落在创建这条SQL查询语句上。
创建
CREATE [OR REPLACE] VIEW 视图名称[(列名列表)] AS SELECT语句 [ WITH [CASCADED | LOCAL ] CHECK OPTION ] |
查询
查看创建视图语句:SHOW CREATE VIEW 视图名称; |
修改
方式一:CREATE [OR REPLACE] VIEW 视图名称[(列名列表)] AS SELECT语句 [ WITH [ CASCADED | LOCAL ] CHECK OPTION ] |
删除
DROP VIEW [IF EXISTS] 视图名称 [,视图名称] ... |
演示示例
-- 视图 |
当使用WITH CHECK OPTION子句创建视图时,MySQL会通过视图检查正在更改的每个行,例如 插入,更新,删除,以使其符合视图的定义。MySQL允许基于另一个视图创建视图,它还会检查依赖视图中的规则以保持一致性。为了确定检查的范围,mysql提供了两个选项: CASCADED 和 LOCAL,默认值为 CASCADED 。
1.CASCADED 级联
比如,v2视图是基于v1视图的,如果在v2视图创建的时候指定了检查选项为 cascaded,但是v1视图创建时未指定检查选项。 则在执行检查时,不仅会检查v2,还会级联检查v2的关联视图v1。
2.LOCAL 本地
比如,v2视图是基于v1视图的,如果在v2视图创建的时候指定了检查选项为 local ,但是v1视图创建时未指定检查选项。 则在执行检查时,知会检查v2,不会检查v2的关联视图v1。
-- 检查选项 |
要使视图可更新,视图中的行与基础表中的行之间必须存在一对一的关系。如果视图包含以下任何一项,则该视图不可更新:
A. 聚合函数或窗口函数(SUM()、 MIN()、 MAX()、 COUNT()等)
B. DISTINCT
C. GROUP BY
D. HAVING
E. UNION 或者 UNION ALL
tb_user表结构以及sql语句
create table tb_user( |
案例
-- 视图案例 |
创建
CREATE PROCEDURE 存储过程名称 ([ 参数列表 ]) |
调用
CALL 名称 ([ 参数 ]); |
查看
SELECT * FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA = 'xxx'; -- 查询指定数据库的存储过程及状态信息 |
删除
DROP PROCEDURE [ IF EXISTS ] 存储过程名称 ; |
注意:
在命令行中,执行创建存储过程的SQL时,需要通过关键字 delimiter 指定SQL语句的结束符
演示示例
-- 存储过程 |
在MySQL中变量分为三种类型: 系统变量、用户定义变量、局部变量
系统变量 是MySQL服务器提供,不是用户定义的,属于服务器层面。分为全局变量(GLOBAL)、会话 变量(SESSION)
1.查看系统变量
SHOW [ SESSION | GLOBAL ] VARIABLES ; -- 查看所有系统变量 |
2.设置系统变量
SET [ SESSION | GLOBAL ] 系统变量名 = 值 ; |
注意:
如果没有指定SESSION/GLOBAL,默认是SESSION,会话变量
1 mysql服务重新启动之后,所设置的全局参数会失效,要想不失效,可以在 /etc/my.cnf 中配置
A. 全局变量(GLOBAL): 全局变量针对于所有的会话
B. 会话变量(SESSION): 会话变量针对于单个会话,在另外一个会话窗口就不生效了
演示示例
-- 系统变量 |
用户定义变量 是用户根据需要自己定义的变量,用户变量不用提前声明,在用的时候直接用 “@变量 名” 使用就可以。其作用域为当前连接
1.赋值
方式一:
SET @var_name = expr [, @var_name = expr] ... ; |
赋值时,可以使用 = ,也可以使用 :=
方式二:
SELECT @var_name := expr [, @var_name := expr] ... ; |
2.使用
SELECT @var_name ; |
注意: 用户定义的变量无需对其进行声明或初始化,只不过获取到的值为NULL
演示示例
-- 用户定义变量 |
局部变量 是根据需要定义的在局部生效的变量,访问之前,需要DECLARE声明。可用作存储过程内的 局部变量和输入参数,局部变量的范围是在其内声明的BEGIN … END块
1**.声明**
DECLARE 变量名 变量类型 [DEFAULT ... ] ; |
变量类型就是数据库字段类型:INT、BIGINT、CHAR、VARCHAR、DATE、TIME等
2.赋值
SET 变量名 = 值 ; |
演示示例
-- 局部变量 |
介绍
if 用于做条件判断,具体的语法结构为:
IF 条件1 THEN |
在if条件判断的结构中,ELSE IF 结构可以有多个,也可以没有. ELSE结构可以有,也可以没有.
案例
根据定义的分数score变量,判定当前分数对应的分数等级
score >= 85分,等级为优秀
score >= 60分 且 score < 85分,等级为及格
score < 60分,等级为不及格
-- if判断 |
介绍
参数的类型,主要分为以下三种:IN、OUT、INOUT. 具体的含义如下:
语法
CREATE PROCEDURE 存储过程名称 ([ IN/OUT/INOUT 参数名 参数类型 ]) |
案例
1.根据传入的分数score变量,判定当前分数对应的分数等级,并返回
score >= 85分,等级为优秀
score >= 60分 且 score < 85分,等级为及格
score < 60分,等级为不及格
-- 参数 |
2.将传入的200分制的分数,进行换算,换成百分制,然后返回
-- 2.将传入的200分制的分数,进行换算,换成百分制,然后返回 |
OCR (Optical Character Recognition,光学字符识别)是指电子设备(例如扫描仪或数码相机)检查纸上打印的字符,通过检测暗、亮的模式确定其形状,然后用字符识别方法将形状翻译成计算机文字的过程
方案 | 说明 |
---|---|
百度OCR | 收费 |
Tesseract-OCR | Google维护的开源OCR引擎,支持Java,Python等语言调用 |
Tess4J | 封装了Tesseract-OCR ,支持Java调用 |
Tesseract支持UTF-8编码格式,并且可以“开箱即用”地识别100多种语言
Tesseract支持多种输出格式:纯文本,hOCR(HTML),PDF等
官方建议,为了获得更好的OCR结果,最好提供给高质量的图像
Tesseract进行识别其他语言的训练,具体的训练方式请参考官方提供的文档:https://tesseract-ocr.github.io/tessdoc/
<dependency> |
地址: https://wwvc.lanzouj.com/iuPhc1h7j46f
chi_sim.traineddata
待识别的图片
测试程序
package com.heima; |
识别的结果
package com.heima.common.tess4j; |
tess4j: |
Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,一个3M的excel用POI sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便
官网地址:https://easyexcel.opensource.alibaba.com/
<!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel --> |
不用自己设置excel表中的表头信息,easyexcel会帮我们设置
package test; |
package test; |
执行之后,空的excel表中会添加如下的数据
继承AnalysisEventListener
package test; |
package test; |
解决listener中无法操作数据库的问题
在进行读操作创建Listener的时候,注入mapper
package com.atguigu.yygh.cmn.listener; |
|
一句话介绍Knife4j: Swagger的增强版,界面更好看,功能更加的丰富
文档地址:https://doc.xiaominfo.com/
<!--引入Knife4j的官方start包,该指南选择Spring Boot版本<3.0,开发者需要注意--> |
package com.atguigu.common.config.knife4j; |
package com.atguigu.auth.controller; |
# 端口号根据项目的端口号进行改变 |
MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。
MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。Mongo最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。
适用场景
1、网站数据:Mongo非常适合实时的插入,更新与查询,并具备网站实时数据存储所需的复制及高度伸缩性。
2、缓存:由于性能很高,Mongo也适合作为信息基础设施的缓存层。在系统重启之后,由M ongo搭建的持久化缓存层可以避免下层的数据源过载。
3、大尺寸,低价值的数据:使用传统的关系型数据库存储一些数据时可能会比较昂贵, 在此之前,很多时候程序员往往会选择传统的文件进行存储。
4、高伸缩性的场景:Mongo非常适合由数十或数百台服务器组成的数据库。Mongo的路线图中已经包含对Map Reduce弓摩的内置支持。
5、用于对象及 JSON数据的存储:Mongo的BSON数据格式非常适合文档化格式的存储 及查询。
不适用场合
1、高度事务性的系统:例如银行或会计系统。传统的关系型数据库目前还是更适用于需要大量原子性复杂事务的应用程序。
2、传统的商业智能应用:针对特定问题的BI数据库会对产生高度优化的查询方式。对于此类应用,数据仓库可能是更合适的选择。
中文文档:https://docs.mongoing.com/
spring-data-mongodb提供了MongoTemplate与MongoRepository两种方式访问mongodb,MongoRepository操作简单,MongoTemplate操作灵活,我们在项目中可以灵活适用这两种方式操作mongodb,MongoRepository的缺点是不够灵活,MongoTemplate正好可以弥补不足
<dependency> |
spring.data.mongodb.uri=mongodb://localhost:27017/test |
|
//常用方法 |
Query对象 |
主要注解: |
示例代码
|
package com.atguigu.mongodb.repository; |
|
创建的时候要修改日志输出的路径
日志的级别根据需要自己修改
级别:OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL
|
Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel提供高可用,通过Redis Cluster提供自动分区。
中文文档:https://www.redis.net.cn/
<!-- redis --> |
package com.atguigu.yygh.common.config; |
说明:
@EnableCaching:标记注解 @EnableCaching,开启缓存,并配置Redis缓存管理器。@EnableCaching 注释触发后置处理器, 检查每一个Spring bean 的 public 方法是否存在缓存注解。如果找到这样的一个注释, 自动创建一个代理拦截方法调用和处理相应的缓存行为。
application.properties
spring.redis.host=localhost |
根据方法对其返回结果进行缓存,下次请求时,如果缓存存在,则直接读取缓存数据返回;如果缓存不存在,则执行方法,并把返回的结果存入缓存中。一般用在查询方法上。
属性/方法名 | 解释 |
---|---|
value | 缓存名,必填,它指定了你的缓存存放在哪块命名空间 |
cacheNames | 与 value 差不多,二选一即可 |
key | 可选属性,可以使用 SpEL 标签自定义缓存的key |
使用该注解标志的方法,每次都会执行,并将结果存入指定的缓存中。其他方法可以直接从响应的缓存中读取缓存数据,而不需要再去查询数据库。一般用在新增方法上。
属性/方法名 | 解释 |
---|---|
value | 缓存名,必填,它指定了你的缓存存放在哪块命名空间 |
cacheNames | 与 value 差不多,二选一即可 |
key | 可选属性,可以使用 SpEL 标签自定义缓存的key |
使用该注解标志的方法,会清空指定的缓存。一般用在更新或者删除方法上
属性/方法名 | 解释 |
---|---|
value | 缓存名,必填,它指定了你的缓存存放在哪块命名空间 |
cacheNames | 与 value 差不多,二选一即可 |
key | 可选属性,可以使用 SpEL 标签自定义缓存的key |
allEntries | 是否清空所有缓存,默认为 false。如果指定为 true,则方法调用后将立即清空所有的缓存 |
beforeInvocation | 是否在方法执行前就清空,默认为 false。如果指定为 true,则在方法执行前就会清空缓存 |
下面的第一个方法是查询数据位list集合
@Cacheable(value = “dict”,keyGenerator = “keyGenerator”) 对方法的结果进行缓存
value属性表示key的前缀
keyGenerator表示key的生成规则,生成规则在配置文件中配置,这里我们使用的是方法的全类名作为key的后缀
第二个方法是添加数据 添加数据会造成数据库中数据的变化 我们要清除缓存
@CacheEvict(value = “dict”,allEntries = true) 清空指定的缓存
value属性表示清空以dict为前缀的所有缓存
allEntries 属性表示是否清空所有缓存,默认为 false。如果指定为 true,则方法调用后将立即清空所有的缓存
|
key的生成规则如下图:
]]>1.导入依赖
<!-- thymeleaf--> |
2.添加配置
spring.thymeleaf.cache=false #关闭缓存(后面使用devtools进行热更新也要关闭缓存) |
3.在HTML文件中引入名称空间
<html xmlns:th="http://www.thymeleaf.org"> |
|
Tips:在使用Thymeleaf的时候修改页面我们要频繁的重启项目,非常浪费时间,这时我们可以导入devtools进行热部署
<!--devtools--> |
导入之后重启项目,会显示如下的标识
每次修改页面之后,我们只需要在当前文件下重新build一下(Ctrl+Shift+F9),就可以实现热更新
英文文档: https://www.thymeleaf.org/doc/tutorials/3.1/usingthymeleaf.html
博客文章: https://fanlychie.github.io/post/thymeleaf.html
中文PDF文档
这里下载的是7版本的 如果有其他的需要 自行安装其他的版本
下载完成之后 记录好安装的位置
阿里镜像下载地址:https://mirrors.aliyun.com/centos/7/isos/x86_64/?spm=a2c6h.25603864.0.0.4eab4511uQRsgc
这里下载的是17版本的VMWare ,可以免费试用30天,需要许可证的可自行百度搜索
官网下载地址: https://download3.vmware.com/software/WKST-1701-WIN/VMware-workstation-full-17.0.1-21139696.exe
安装步骤:
CentOs的不同版本在VmWare上面的配置略有不同
其实本地安装Linux大可不必安装远程控制工具 直接进入Linux里面控制即可
下载地址: https://wwvc.lanzouy.com/ipF2w0v6np8j
下载解压之后双击.exe文件 点击下一步安装 直到安装完成
1.获取Linux的ip地址信息 并记录ip的信息
命令 |
2.打开xShell工具
2.1创建会话
2.2 选择会话并连接
2.3 用户名和密码正确之后就连接成功
连接成功如下图
完整的配置过程
下载地址: http://www.hostbuf.com/downloads/finalshell_install.exe
Tips:
这里我们没有设置Linux的静态IP,电脑每次网络变化之后,IP地址会发生变化,这时我们需要获取最新的IP地址,重新执行以上的连接步骤。如果想要IP地址固定,我们可以设置静态IP,设置静态的IP的方法如下:
]]>在线生成代码注释模板:http://patorjk.com/software/taag/
|
VScode插件推荐:koroFileHeader (用于生成文件头部注释和函数注释的插件)
|
/** |
/* |
/* |
/* |
/* |
耶稣
/* |
/* |
/* |
/* |
/* |
/* |
Java中Robot类位于java.awt.Robot,该类用于为测试自动化,自运行演示程序和其他需要控制鼠标和键盘的应用程序生成本机系统输入事件,Robot类的主要目的是便于Java平台实现自动测试
方法名 | 使用说明 |
---|---|
delay(n) | 延迟电脑操作n毫秒,类似于Thread.sleep() |
keyPress() | 模拟手动按下电脑键盘上的某个键 |
keyRelease() | 模拟手动松开电脑键盘上的某个键(与keyPress()对应,按下一个键必须松开这个键) |
mouseMove(x,y) | 将鼠标移动到指定的x,y位置 |
mousePress() | 按下鼠标上的某个键 |
mouseRelease() | 松开鼠标上的某个键 |
getPixelColor(x,y) | 获取指定坐标处的像素颜色 |
mouseWheel(int wheelAmt) | 鼠标滚动(参数小于0,表示向上滚动;参数大于0,表示向下滚动) |
import java.awt.*; |
import java.awt.*; |
import java.awt.*; |
免费、无需登录注册、支持编辑和PDF与格式文件的相互转换!
官网地址:https://www.pdfgear.com/zh/
无需登录,无需付费、支持PDF与其他格式的文件的相互转化、拆分合并PDF、压缩PDF、OCR文字识别
github开源,需要自己使用Docker部署!
github仓库地址:https://github.com/Stirling-Tools/Stirling-PDF
部署命令
docker run -d \ |
客户端下载 –> windows 64位 –>解压后得到.exe文件
官网注册登录 –> 购买免费隧道 –>获取authtoken
natapp -authtoken=刚刚你申请的authtoken |
免费域名注册网址:https://nic.eu.org
第一步:点击: here
第二步: 点击Register
第三步:填写相关的信息
地址信息填写英国的地址,可以使用在线地址生成器生成:https://www.meiguodizhi.com/uk-address
填写地址相关的信息,其余的信息根据自己的情况填写(fax可不填)
显示下面的提示就是注册成功了
这时在系统会给刚才注册的邮箱发送一个激活链接,点击激活
打开链接之后点击的按钮Validate验证,验证之后显示如下页面表示成功
登录的账号就在刚才发送的邮件里面,密码就是注册时设置的密码
输入你要注册的域名,域名的格式是 xxxx.eu.org
域名验证
点击Submit按钮之后显示如下的内容表示这个免费的域名是可以申请到的,然后等待人工审核通过
审核通过之后,你注册时使用的邮箱就会收到信息
]]>本视频笔记地址:https://yuque.com/atguigu/springboot
本视频源码地址:https://gitee.com/leifengyang/springboot2
Spring官网: https://spring.io/
SpringBoot2的环境要求
这里的Spring指的是整个Spring生态
微服务 响应式编程 分布式 WEB开发 无服务开发 事件驱动 批处理
Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”.
能快速创建出生产级别的Spring应用
SpringBoot是整合Spring技术栈的一站式框架
SpringBoot是简化Spring技术栈的快速开发脚手架
● Create stand-alone Spring applications
○ 创建独立Spring应用
● Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)
○ 内嵌web服务器
● Provide opinionated ‘starter’ dependencies to simplify your build configuration
○ 自动starter依赖,简化构建配置
● Automatically configure Spring and 3rd party libraries whenever possible
○ 自动配置Spring以及第三方功能
● Provide production-ready features such as metrics, health checks, and externalized configuration
○ 提供生产级别的监控、健康检查及外部化配置
● Absolutely no code generation and no requirement for XML configuration
○ 无代码生成、无需编写XML
进入官网 找到SpringBoot 点击Reference Doc进入官方文档
**Maven的配置教程: **
Maven配置文件settings.xml | The Blog (qingling.icu)
开发环境的搭建 | The Blog (qingling.icu)
需求:浏览发送/hello请求,响应 Hello,Spring Boot 2
1创建maven工程
2.引入依赖
<parent> |
3.创建主程序
package com.atguigu.boot; |
4.编写Controller
package com.atguigu.boot.controller; |
server.port=8888 |
通过在pom.xml文件中插入这个插件,可以直接将项目打成jar包简化部署
<build> |
可以直接使用cmd切换到target目录下使用java -jar
命令运行jar包
注意点
去掉cmd的快速编辑模式(不去掉启动的时候点击屏幕会停止)
<!-- 项目的继承的父项目--> |
自定义mysql版本驱动
在父依赖中mysql是8版本的
可以在pom.xml中通过自定义properties修改版本
<properties> |
以spring-boot-starter-*
起头的starter为官方提供的starter,*-spring-boot-starter
为第三方提供的starter
常见的Starter
以引入spring-boot-starter-web
为例,引入该starter它会帮我们引入以下的其它依赖
自动配置Tomcat
引入依赖
配置Tomcat
自动配置SpringMvc
引入springMvc全套组件
自动配置好springMvc的常用组件(字符编码,文件上传解析器,dispatcherServlet等)
|
默认的包结构
主程序所在的包以及其的子包都可以被扫描到,无需配置包扫描
com |
如果需要扫描的文件在主程序的上级目录,我们也想扫描到它,我们需要扩大一下包的扫描范围
|
各种配置拥有默认值
按需加载所有的自动配置项
spingboot的所有配置存在于下面的这个依赖中
<dependency> |
基本使用
Full模式与Lite模式
/** |
|
//默认是单实例的 |
/* |
//放在类上面,条件成立,这个类下面的所有配置生效,放在方法上面,条件成立,这个方法下的配置才会成立 |
beans.xml*)
|
在java类中红使用@ImportResource(“classpath:beans.xml”)导入上面的配置文件
|
/** |
配置文件中的值
mycar.brand=BYD |
编写测试的controller
|
@SpringBootApplication
//代表当前是一个配置类 |
@EnableAutoConfiguration
|
@AutoConfigurationPackage
|
Registrar.class
利用Registrar给容器中导入com.atguigu.boot(主程序所在的包)包下的一系列组件
@Import(AutoConfigurationImportSelector.class)
1、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件 |
AutoConfigurationImportSelector.java
|
getAutoConfigurationEntry()方法
利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { |
**getCandidateConfigurations()**获取所有候选的配置
调用List
loadSpringFactories()
利用工厂加载 Map<String, List
private static Map<String, List<String>> loadSpringFactories( { ClassLoader classLoader) |
从META-INF/spring.factories位置来加载一个文件。默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件,spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories
在spring.factories这个配置文件中写死了springBoot一启动就要给容器加载的所有配置类,一共127个
# Initializers |
虽然我们127个场景的所有自动配置启动的时候默认全部加载。xxxxAutoConfiguration,按照条件装配规则(@Conditional),最终会按需配置,比如下面的配置是否生效还要通过@Conditional的条件判断。
简单的一句话:启动的时候加载所有,使用的时候按照条件(@Conditional)进行装配
相当于当我们在容器中注入了类型为MultipartResolver但是id不为multipartResolver的组件的时候,会帮我们规范一下命名,命名为multipartResolver。
|
SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先
|
总结:
SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定
生效的配置类就会给容器中装配很多组件
只要容器中有这些组件,相当于这些功能就有了
定制化配置
xxxxxAutoConfiguration —> 组件 —> xxxxProperties取值 —-> application.properties
引入场景依赖
查看自动配置了哪些(选做)
是否需要修改
引入依赖(使用前需要安装插件)
<dependency> |
注解
//无参构造器 |
<dependency> |
项目或者页面修改以后:Ctrl+F9;
同以前的properties用法
YAML 是 “YAML Ain’t Markup Language”(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:”Yet Another Markup Language”(仍是一种标记语言)。
非常适合用来做以数据为中心的配置文件
k: v |
行内写法: k: {k1:v1,k2:v2,k3:v3} |
行内写法: k: [v1,v2,v3] |
//和以person开头的配置文件绑定 |
# yaml表示以上对象 |
自定义的类和配置文件绑定一般没有提示
<dependency> |
只要静态资源放在类路径下: /static
(or /public
or /resources
or /META-INF/resources
访问 : 当前项目根路径/ + 静态资源名
原理: 静态映射/**
请求进来,先去找Controller看能不能处理,不能处理的所有请求又都交给静态资源处理器,静态资源也找不到则响应404页面
改变默认的静态资源路径
spring: |
默认无前缀
默认的访问路径: localhost:8080/xxx.png
设置访问前缀之后: localhost:8080/res/xxx.png
(文件在目录中的位置没有改变,只是访问的时候加上了res这一层路径)
spring: |
当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找
自动映射 /webjars/**
<dependency> |
访问地址:http://localhost:8080/webjars/jquery/3.5.1/jquery.js 后面地址要按照依赖里面的包路径
静态资源路径下 index.html
spring: |
Favicon
favicon.ico 放在静态资源目录下即可(名称为favicon.ico)
spring: |
1.SpringMvc的自动配置类WebMvcAutoConfiguration
|
2.SpringMvc给容器中配置的组件
|
3.绑定的配置
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
WebMvcProperties.class
ResourceProperties.class
@xxxMapping;
Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)
#开启使用rest风格的注解 |
<!--发送put或者delete请求--> |
|
Rest原理(表单提交要使用REST的时候)
表单提交会带上_method=PUT
<!--发送put或者delete请求--> |
请求过来被HiddenHttpMethodFilter拦截
WebMvcAutoConfiguration
|
获取到_method的值。
|
兼容以下请求;PUT.DELETE.PATCH
private static final List<String> ALLOWED_METHODS = |
原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值
过滤器链放行的时候用wrapper,以后的方法调用getMethod是调用requesWrapper的
Tips:
Rest使用客户端工具:如PostMan直接发送put、delete等方式请求,无需Filter,不需要转换
扩展点:
如何将<input name="_method" type="hidden" value="delete"/>
中的name值_method改为自定义的值?
//组件中没有依赖关系,设置成false直接放 |
DispatcherServlet 继承 FrameworkServlet
FrameworkServlet 继承 HttpServletBean
FrameworkServlet 重写了doGet和doPost方法
DispatcherServlet的doDispatch方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { |
getHandler()方法 遍历查找可以处理请求的方法
|
**HandlerMapping ** 处理器映射
默认有5个HandlerMapping
RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则
所有的请求映射都在HandlerMapping中
SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 访问 /能访问到index.html;
SpringBoot自动配置了默认的 RequestMappingHandlerMapping
请求进来,挨个尝试所有的HandlerMapping看是否有请求信息
我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping 自定义 HandlerMapping
//获取路径中的参数信息(restful风格) |
@PathVariable
接受路径中的参数也可以使用map集合接受,但是map集合必须是Map<String, String>的形式
@RequestHeader
获取指定的请求头或者所有的请求头信息
@RequestParam
可以使用map接受所有的请求参数
@CookieValue
获取cookie的值,可以直接获取字符串的值也可以封装成cookie的对象
@RequestBody
这里用一个字符串接收表单数据和平时使用的不一样,开发中使用一个对象接受表单的数据
/** |
@RequestAttribute
package com.atguigu.boot.controller; |
@MatrixVariable
开启SpringBoot的矩阵变量功能
|
测试
//1、语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd |
WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
ServletRequestMethodArgumentResolver 以上的部分参数
|
Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder
向请求域中共享数据
/** |
/** |
页面测试代码
index.html
<!DOCTYPE html> |
1.页面发送请求
http://localhost:8080/car/3/owner/lisi |
2.进入DispatcherServlet的doDispatch( )方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { |
3.封装一个方法获取对应的处理器适配器
//方法的调用 |
4.遍历所有的处理器适配器(一共有四种,如下图)找到支持的适配器
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { |
上图中四个处理器适配器中前两个是常用的处理器适配器: 0 - 支持方法上标注@RequestMapping 1 - 支持函数式编程的
5.查看当前的处理器适配器是否支持的方法
|
遍历查找之后返回的适配器是RequestMappingHandlerAdapter
6.回到DispatcherServlet执行下面的方法
//根据上面获取到的处理器适配器真正的执行处理器方法 |
7.内部的处理过程
handle( )方法
|
进入当前处理器适配器的handleInternal( )方法
|
执行invokeHandlerMethod( )方法
mav = invokeHandlerMethod(request, response, handlerMethod); |
进入invokeHandlerMethod()方法
|
注意这里的26个参数解析器,参数解析器的作用是确定将要执行的目标方法的每一个参数的值是什么
这里的每一个参数解析器都对应了我们标注在参数上的注解
SpringMVC目标方法能写多少种参数类型取决于参数解析器
参数解析器的接口设计
返回值处理器
定义了controller返回值的类型
RequestMappingHandlerAdapter的invokeAndHandle( )方法真正的执行请求
invocableMethod.invokeAndHandle(webRequest, mavContainer); |
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, |
执行的过程
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); |
|
获取方法参数值的过程
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); |
protected Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer, |
遍历判断获取支持解析该参数的参数解析器
|
解析参数的值通过调用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法
返回值的处理逻辑
创建springBoot项目的时候导入了spring-boot-starter-web依赖会自动的帮我们导入相关的json依赖,便于json数据的前后端传递
<dependency> |
自动的帮我们导入相关的json的starter
<dependency> |
在spring-boot-starter-json中引入了jackson相关的依赖
<dependency> |
返回值解析器的原理
1、返回值处理器判断是否支持这种类型返回值 supportsReturnType
2、返回值处理器调用 handleReturnValue 进行处理
3、RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。
引入jackson-dataformat-xml,测试返回是xml格式的数据
<dependency> |
开启基于请求参数的内容协商功能
发请求: http://localhost:8080/test/person?format=json //指定格式是json
http://localhost:8080/test/person?format=xml //指定格式是xml
spring: |
1、Parameter策略优先确定是要返回json数据(获取请求头中的format的值)
2、最终进行内容协商返回给客户端json即可
3、遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象(Person)
4、找到支持操作Person的converter,把converter支持的媒体类型统计出来。
5、客户端需要【application/xml】。服务端能力【10种、json、xml】
6、进行内容协商的最佳匹配媒体类型
7、用 支持 将对象转为 最佳匹配媒体类型 的converter。调用它进行转化 。
1、@ResponseBody 响应数据出去 调用 RequestResponseBodyMethodProcessor 处理
2、Processor 处理方法返回值。通过 MessageConverter 处理
3、所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)
4、内容协商找到最终的 messageConverter;
步骤:
1、添加自定义的MessageConverter进系统底层
2、系统底层就会统计出所有MessageConverter能操作哪些类型
3、客户端内容协商 [guigu—>guigu]
编写自定义的的converter
package com.atguigu.boot.converter; |
在配置文件中加入自定义的converter
package com.atguigu.boot.config; |
在浏览器url上拼接参数的形式实现内容协商
package com.atguigu.boot.config; |
视图解析:SpringBoot默认不支持 JSP,需要引入第三方模板引擎技术实现页面渲染
详细的使用教程: https://qingling.icu/posts/54835.html
1.引入依赖
<dependency> |
2.编写测试controller
|
3.页面上渲染
|
templates目录的访问规则
templates目录下的所有页面资源只能通过请求访问到
表单重复提交的问题
package com.atguigu.admin.controller; |
自定义拦截器需要实现的接口,以及需要实现接口中的方法
登录检查的业务逻辑
/** |
拦截器的配置
/** |
/** |
1、根据当前请求,找到HandlerExecutionChain[可以处理请求的handler以及handler的所有 拦截器]
2、先来顺序执行 所有拦截器的 preHandle方法
3、如果任何一个拦截器返回false,直接跳出不执行目标方法
4、所有拦截器都返回true,执行目标方法
5、倒序执行所有拦截器的postHandle方法
6、前面的步骤有任何异常都会直接倒序触发 afterCompletion
7、页面成功渲染完成以后,也会倒序触发 afterCompletion
<form method="post" action="/upload" enctype="multipart/form-data"> |
/** |
|
开发中异常处理方法: https://qingling.icu/posts/31385.html
/error
处理所有错误的映射templates/error/下的4xx,5xx页面会被自动解析
1.编写自定义的servlet继承HttpServlet
package com.atguigu.boot.servlet; |
2.在启动类上添加自定义servlet的包扫描注解
package com.atguigu.boot; |
3.启动程序测试
1.编写自定义的Filter实现Filter接口
package com.atguigu.boot.servlet; |
2.启动程序测试
1.编写自定义的MyServletContextListener实现ServletContextListener
package com.atguigu.boot.servlet; |
2.启动程序测试
不使用@WebServlet、@WebFilter、@WebListener注解的方式(注释掉自定义servlet、Filter、Listener上的注解)
package com.atguigu.boot.servlet; |
重新启动项目我们可以看到依然生效
默认支持的webServer
Tomcat
, Jetty
, or Undertow
ServletWebServerApplicationContext 容器启动寻找ServletWebServerFactory 并引导创建服务器
切换服务器
1.排除tomcat的依赖
<dependency> |
2.引入需要的服务器场景
<dependency> |
3.重启项目,可以看到服务器由tomcat变成了undertow
原理
ServletWebServerApplicationContext
ServletWebServerApplicationContext
启动的时候寻找 **ServletWebServerFactory**``(Servlet 的web服务器工厂---> Servlet 的web服务器)
TomcatServletWebServerFactory
, JettyServletWebServerFactory
, or UndertowServletWebServerFactory
底层直接会有一个自动配置类ServletWebServerFactoryAutoConfiguration
ServletWebServerFactoryAutoConfiguration导入了ServletWebServerFactoryConfiguration(配置类)
ServletWebServerFactoryConfiguration 配置类 根据动态判断系统中到底导入了那个Web服务器的包(默认是web-starter导入tomcat包),容器中就有 TomcatServletWebServerFactory
TomcatServletWebServerFactory 创建出Tomcat服务器并启动;TomcatWebServer 的构造器拥有初始化方法initialize---this.tomcat.start();
内嵌服务器,就是手动把启动服务器的代码调用(tomcat核心jar包存在)
修改配置文件中以server.xxx打头的属性信息
1.导入JDBC的场景
<dependency> |
2.导入自己使用的数据库的驱动(Mysql或者Oracle)版本号不写也可以,官方做了版本仲裁(但是要与实际安装的版本对应)
想要修改版本 |
3.添加配置
spring: |
4.测试
package com.atguigu.boot; |
自动配置的类
DataSourceAutoConfiguration : 数据源的自动配置
DataSourceTransactionManagerAutoConfiguration: 事务管理器的自动配置
JdbcTemplateAutoConfiguration: JdbcTemplate的自动配置,可以来对数据库进行crud
JndiDataSourceAutoConfiguration: jndi的自动配置
XADataSourceAutoConfiguration: 分布式事务相关的
Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库
作为最新版本的JUnit框架,JUnit5与之前版本的Junit框架有很大的不同。由三个不同子项目的几个不同模块组成
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
JUnit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入
JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行
JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎
注意:
SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容junit4需要自行引入(不能使用junit4的功能 @Test)
以前的版本
兼容以前的版本(junit4)需要额外导入的依赖(SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖)
<dependency> |
标注的注解
@SpringBootTest + @RunWith(SpringRunner.class)+@Test(4版本的@Test)
现在的版本
需要导入的依赖
<dependency> |
标注的注解
@SpringBootTest + @Test
两个不同版本的 @Test 注解
JUnit5的注解与JUnit4的注解有所变化
https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations
@BeforeEach :表示在每个单元测试之前执行
@AfterEach :表示在每个单元测试之后执行
@BeforeAll :表示在所有单元测试之前执行
@AfterAll :表示在所有单元测试之后执行
package com.atguigu.boot; |
/** |
/** |
全部的代码
package com.atguigu.boot; |
断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法。检查业务逻辑返回的数据是否合理。所有的测试运行结束以后,会有一个详细的测试报告。
用来对单个值进行简单的验证
方法 | 说明 |
---|---|
assertEquals | 判断两个对象或两个原始类型是否相等 |
assertNotEquals | 判断两个对象或两个原始类型是否不相等 |
assertSame | 判断两个对象引用是否指向同一个对象 |
assertNotSame | 判断两个对象引用是否指向不同的对象 |
assertTrue | 判断给定的布尔值是否为 true |
assertFalse | 判断给定的布尔值是否为 false |
assertNull | 判断给定的对象引用是否为 null |
assertNotNull | 判断给定的对象引用是否不为 null |
|
通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等
|
assertAll 方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言
|
在JUnit4时期,想要测试方法的异常情况时,需要用@Rule注解的ExpectedException变量还是比较麻烦的。而JUnit5提供了一种新的断言方式Assertions.assertThrows() ,配合函数式编程就可以进行使用。
|
Junit5还提供了Assertions.assertTimeout() 为测试方法设置了超时时间
|
通过 fail 方法直接使得测试失败
|
全部代码
package com.atguigu.admin; |
JUnit 5 中的前置条件(assumptions[假设])类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要
|
assumeTrue 和 assumFalse 确保给定的条件为 true 或 false,不满足条件会使得测试执行终止
assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象
只有条件满足时,Executable 对象才会被执行;当条件不满足时,测试执行并不会终止
JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制
|
参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利
利用@ValueSource等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码
@ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
@NullSource: 表示为参数化测试提供一个null的入参
@EnumSource: 表示为参数化测试提供一个枚举入参
@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
@MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
当然如果参数化测试仅仅只能做到指定普通的入参还达不到让我觉得惊艳的地步。让我真正感到他的强大之处的地方在于他可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现ArgumentsProvider接口,任何外部文件都可以作为它的入参
|
在进行迁移(Junit4迁移到Junit5)的时候需要注意如下的变化:
未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能
1.引入依赖
<dependency> |
2.测试访问 http://localhost:8080/actuator/ endpoint节点
3.设置以web的方式访问所有的端点
management: |
设置之后就可以通过web(访问 http://localhost:8080/actuator/beans)的方式访问beans端点了
支持的暴露方式
是通过web还是jmx方式访问的端点一览表
ID | JMX | Web |
---|---|---|
auditevents |
Yes | No |
beans |
Yes | No |
caches |
Yes | No |
conditions |
Yes | No |
configprops |
Yes | No |
env |
Yes | No |
flyway |
Yes | No |
health |
Yes | Yes |
heapdump |
N/A | No |
httptrace |
Yes | No |
info |
Yes | Yes |
integrationgraph |
Yes | No |
jolokia |
N/A | No |
logfile |
N/A | No |
loggers |
Yes | No |
liquibase |
Yes | No |
metrics |
Yes | No |
mappings |
Yes | No |
prometheus |
N/A | No |
scheduledtasks |
Yes | No |
sessions |
Yes | No |
shutdown |
Yes | No |
startup |
Yes | No |
threaddump |
Yes | No |
http://localhost:8080/actuator/endPoint
ID | 描述 |
---|---|
auditevents |
暴露当前应用程序的审核事件信息。需要一个AuditEventRepository组件 。 |
beans |
显示应用程序中所有Spring Bean的完整列表。 |
caches |
暴露可用的缓存。 |
conditions |
显示自动配置的所有条件信息,包括匹配或不匹配的原因。 |
configprops |
显示所有@ConfigurationProperties 。 |
env |
暴露Spring的属性ConfigurableEnvironment |
flyway |
显示已应用的所有Flyway数据库迁移。 需要一个或多个Flyway 组件。 |
health |
显示应用程序运行状况信息。 |
httptrace |
显示HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应)。需要一个HttpTraceRepository 组件。 |
info |
显示应用程序信息。 |
integrationgraph |
显示Spring integrationgraph 。需要依赖spring-integration-core 。 |
loggers |
显示和修改应用程序中日志的配置。 |
liquibase |
显示已应用的所有Liquibase数据库迁移。需要一个或多个Liquibase 组件。 |
metrics |
显示当前应用程序的“指标”信息。 |
mappings |
显示所有@RequestMapping 路径列表。 |
scheduledtasks |
显示应用程序中的计划任务。 |
sessions |
允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序。 |
shutdown |
使应用程序正常关闭。默认禁用。 |
startup |
显示由ApplicationStartup 收集的启动步骤数据。需要使用SpringApplication 进行配置BufferingApplicationStartup 。 |
threaddump |
执行线程转储。 |
如果您的应用程序是Web应用程序(Spring MVC,Spring WebFlux或Jersey),则可以使用以下附加端点:
ID | 描述 |
---|---|
heapdump |
返回hprof 堆转储文件。 |
jolokia |
通过HTTP暴露JMX bean(需要引入Jolokia,不适用于WebFlux)。需要引入依赖jolokia-core 。 |
logfile |
返回日志文件的内容(如果已设置logging.file.name 或logging.file.path 属性)。支持使用HTTPRange 标头来检索部分日志文件的内容。 |
prometheus |
以Prometheus服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus 。 |
最常用的Endpoint
健康检查端点,我们一般用于在云平台,平台会定时的检查应用的健康状况,我们就需要Health Endpoint可以为平台返回当前应用的一系列组件健康状况的集合。
重要的几点:
设置显示health端点的详细信息
management: |
{ |
提供详细的、层级的、空间指标信息,这些信息可以被pull(主动推送)或者push(被动获取)方式得到;
查看完整的监控指标信息 http://localhost:8080/actuator/metrics
{ |
查看某一个监控指标的详细信息(例如查看hikaricp.connections的详细信息) http://localhost:8080/actuator/metrics/hikaricp.connections
{ |
开启与禁用Endpoints
management: |
management: |
1.编写MyComHealthIndicator类继承AbstractHealthIndicator
package com.atguigu.boot.acutuator.health; |
myCom = MyComHealthIndicator - HealthIndicator 所以类的名字一定要以HealthIndicator为后缀
方式一: 编写配置文件
info: |
方式二: 编写InfoContributor
package com.atguigu.boot.acutuator.info; |
如果我们没有删除上面配置文件的配置,这时我们访问 http://localhost:8080/actuator/info 的时候会将配置文件和InfoContributor中的信息共同返回
class MyService{ |
package com.atguigu.boot.acutuator.endpoint; |
github地址: https://github.com/codecentric/spring-boot-admin
项目文档: https://docs.spring-boot-admin.com/current/getting-started.html
1.创建一个新的springBoot项目用作服务端(场景只需要选择web场景即可)
2.引入依赖
<dependency> |
3.在启动类上添加注解
4.修改以下端口号以防和业务的端口冲突
server.port=8888 |
6.客户端上引入依赖
<dependency> |
7.客户端的配置文件添加以下的配置
spring.boot.admin.client.url=http://localhost:8888 #服务端的地址 |
8.重启一下客户端,然后访问服务端的localhost:8080
点击应用墙,点击需要查看信息的应用就可以看到应用的详细信息
为了方便多环境适配,springboot简化了profile功能
默认配置文件 application.yaml;任何时候都会加载
指定环境配置文件 application-{env}.yaml
激活指定环境
默认配置与环境配置同时生效
同名配置项,profile配置优先
@Profile注解
//@Profile("prod") |
外部配置源
常用:Java属性文件、YAML文件、环境变量、命令行参数;
配置文件查找位置
下面的优先级覆盖上面的优先级
(1) classpath 根路径
(2) classpath 根路径下config目录
(3) jar包当前目录
(4) jar包当前目录的config目录
(5) /config子目录的直接子目录
配置文件加载顺序:
总结: 指定环境优先,外部优先,后面的可以覆盖前面的同名配置项
]]>笔记转载于黑马程序员,详细的笔记来源于:https://www.bilibili.com/video/BV1WY4y197g7/?spm_id_from=333.337.search-card.all.click&vd_source=22300b9f40de74b7db529eb8f04510a9
二、校验字符的表达式
1 汉字:^[\u4e00-\u9fa5]{0,}$
2 英文和数字:^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$
3 长度为3-20的所有字符:^.{3,20}$
4 由26个英文字母组成的字符串:^[A-Za-z]+$
5 由26个大写英文字母组成的字符串:^[A-Z]+$
6 由26个小写英文字母组成的字符串:^[a-z]+$
7 由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$
8 由数字、26个英文字母或者下划线组成的字符串:^\w+$ 或 ^\w{3,20}$
9 中文、英文、数字包括下划线:^[\u4E00-\u9FA5A-Za-z0-9_]+$
10 中文、英文、数字但不包括下划线等符号:^[\u4E00-\u9FA5A-Za-z0-9]+$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$
11 可以输入含有^%&’,;=?$"等字符:[^%&’,;=?$\x22]+
12 禁止输入含有的字符:[^\x22]+
三、特殊需求表达式
1 Email地址:^\w+([-+.]\w+)@\w+([-.]\w+).\w+([-.]\w+)$
2 域名:[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.?
3 InternetURL:[a-zA-z]+://[^\s] 或 ^https://([\w-]+.)+[\w-]+(/[\w-./?%&=])?$
4 手机号码:^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$
5 电话号码(“XXX-XXXXXXX”、”XXXX-XXXXXXXX”、”XXX-XXXXXXX”、”XXX-XXXXXXXX”、”XXXXXXX”和”XXXXXXXX):^((\d{3,4}-)|\d{3.4}-)?\d{7,8}$
6 国内电话号码(0511-4405222、021-87888822):\d{3}-\d{8}|\d{4}-\d{7}
7 身份证号:
15或18位身份证:^\d{15}|\d{18}$
15位身份证:^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$
18位身份证:^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{4}$
8 短身份证号码(数字、字母x结尾):^([0-9]){7,18}(x|X)?$ 或 ^\d{8,18}|[0-9x]{8,18}|[0-9X]{8,18}?$
9 帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$
10 密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):^[a-zA-Z]\w{5,17}$
11 强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间):^(?=.\d)(?=.[a-z])(?=.[A-Z]).{8,10}$
12 日期格式:^\d{4}-\d{1,2}-\d{1,2}
13 一年的12个月(01~09和1~12):^(0?[1-9]|1[0-2])$
14 一个月的31天(01~09和1~31):^((0?[1-9])|((1|2)[0-9])|30|31)$
15 钱的输入格式:
16 1.有四种钱的表示形式我们可以接受:”10000.00” 和 “10,000.00”, 和没有 “分” 的 “10000” 和 “10,000”:^[1-9][0-9]$
17 2.这表示任意一个不以0开头的数字,但是,这也意味着一个字符”0”不通过,所以我们采用下面的形式:^(0|[1-9][0-9])$
18 3.一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号:^(0|-?[1-9][0-9])$
19 4.这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧.下面我们要加的是说明可能的小数部分:^[0-9]+(.[0-9]+)?$
20 5.必须说明的是,小数点后面至少应该有1位数,所以”10.”是不通过的,但是 “10” 和 “10.2” 是通过的:^[0-9]+(.[0-9]{2})?$
21 6.这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样:^[0-9]+(.[0-9]{1,2})?$
22 7.这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样:^[0-9]{1,3}(,[0-9]{3})(.[0-9]{1,2})?$
23 8.1到3个数字,后面跟着任意个 逗号+3个数字,逗号成为可选,而不是必须:^([0-9]+|[0-9]{1,3}(,[0-9]{3}))(.[0-9]{1,2})?$
24 备注:这就是最终结果了,别忘了”+”可以用””替代如果你觉得空字符串也可以接受的话(奇怪,为什么?)最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里
25 xml文件:^([a-zA-Z]+-?)+[a-zA-Z0-9]+\.[x|X][m|M][l|L]$
26 中文字符的正则表达式:[\u4e00-\u9fa5]
27 双字节字符:[^\x00-\xff] (包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1))
28 空白行的正则表达式:\n\s\r (可以用来删除空白行)
29 HTML标记的正则表达式:<(\S?)[^>]>.?|<.? /> (网上流传的版本太糟糕,上面这个也仅仅能部分,对于复杂的嵌套标记依旧无能为力)
30 首尾空白字符的正则表达式:^\s|\s$或(^\s)|(\s$) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式)
31 腾讯QQ号:[1-9][0-9]{4,} (腾讯QQ号从10000开始)
32 中国邮政编码:[1-9]\d{5}(?!\d) (中国邮政编码为6位数字)
33 IP地址:\d+.\d+.\d+.\d+ (提取IP地址时有用)
MinIO基于Apache License v2.0开源协议的对象存储服务,可以做为云存储的解决方案用来保存海量的图片,视频,文档。由于采用Golang实现,服务端可以工作在Windows,Linux, OS X和FreeBSD上。配置简单,基本是复制可执行程序,单行命令可以运行起来。MinIO兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。
S3 ( Simple Storage Service简单存储服务)
基本概念
官网文档:http://docs.minio.org.cn/docs/
数据保护
Minio使用Minio Erasure Code(纠删码)来防止硬件故障。即便损坏一半以上的driver,但是仍然可以从中恢复。
高性能
作为高性能对象存储,在标准硬件条件下它能达到55GB/s的读、35GB/s的写速率
可扩容
不同MinIO集群可以组成联邦,并形成一个全局的命名空间,并跨越多个数据中心
SDK支持
基于Minio轻量的特点,它得到类似Java、Python或Go等语言的sdk支持
有操作页面
面向用户友好的简单操作界面,非常方便的管理Bucket及里面的文件资源
功能简单
这一设计原则让MinIO不容易出错、更快启动
丰富的API
支持文件资源的分享连接及分享链接的过期策略、存储桶操作、文件列表访问及文件上传下载的基本功能等。
文件变化主动通知
存储桶(Bucket)如果发生改变,比如上传对象和删除对象,可以使用存储桶事件通知机制进行监控,并通过以下方式发布出去:AMQP、MQTT、Elasticsearch、Redis、NATS、MySQL、Kafka、Webhooks等。
存储方式 | 优点 | 缺点 |
---|---|---|
服务器磁盘 | 开发便捷,成本低 | 扩展困难 |
分布式文件系统 (如MinIo) | 容易实现扩容 | 复杂度高 |
第三方存储 (如阿里OSS) | 开发简单,功能强大,免维护 | 收费 |
存储方式 | 优点 | 缺点 |
---|---|---|
FastDFS | 1,主备服务,高可用 2,支持主从文件,支持自定义扩展名 3,支持动态扩容 | 1,没有完备官方文档,近几年没有更新 2,环境搭建较为麻烦 |
MinIO | 1,性能高,准硬件条件下它能达到55GB/s的读、35GB/s的写速率 2,部署自带管理界面 3,MinIO.Inc运营的开源项目,社区活跃度高 4,提供了所有主流开发语言的SDK | 1,不支持动态增加节点 |
使用Docker的方式安装MInIo,Docker使用教程 https://qingling.icu/posts/19306.html
拉取minio的镜像 |
访问: http://192.168.200.130:9000
Access Key为minio Secret_key 为minio123 进入系统后可以看到主界面
基本概念
点击右下角的“+”号 ,创建一个桶
创建成功后
pom.xml
<dependencies> |
启动类
package com.heima.minio; |
目标:把test.html文件上传到minio中,并且可以在浏览器中访问
test.html测试文件
|
文件上传代码
package com.heima.minio; |
执行成功之后可以在MinIO中找到该文件
设置浏览器输入文件在minio中的地址可以直接访问文件的内容
设置bucket的访问权限
设置完毕后访问文件的地址可以直接访问到
黑马头条项目gitee地址: https://gitee.com/JasonsGong/heima-leadnews
在开发的过程中,有很多模块需要使用到文件服务,我们直接把文件服务封装成一个Starter,方便其余的模块调用
导入依赖
<dependencies> |
MinIOConfigProperties
package com.heima.file.config; |
MinIOConfig
package com.heima.file.config; |
FileStorageService
package com.heima.file.service; |
MinIOFileStorageService
package com.heima.file.service.impl; |
在resources中新建META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ |
第一,导入heima-file-starter的依赖
<dependency> |
第二,在微服务中添加minio所需要的配置
minio: |
第三,在对应使用的业务类中注入FileStorageService,样例如下:
|
package com.heima.minio; |
ES6, 全称 ECMAScript 6.0 ,是 JavaScript 的下一个版本标准,2015.06 发版。ES6 主要是为了解决 ES5 的先天不足,比如 JavaScript 里并没有类的概念,但是目前浏览器的 JavaScript 是 ES5 版本,大多数高版本的浏览器也支持 ES6,不过只实现了 ES6 的部分特性和功能。
|
|
|
|
|
解决回调地域的问题
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
let promise = new Promise(function(resolve, reject) { |
原有的基础上进一步的封装,链式调用
模块化就是把代码进行拆分,分辨重复使用。类似java中的导包:要使用一个包,必须先导包。而JS中没有包的概念,换来的是模块。
模块功能主要由两个命令构成: export和import
export不仅可以导出对象,一切js对象都可以导出。比如:基本类型的变量、函数、数组、对象
比如我们定义了一个js文件hello.js,里面有一个对象
const util = { |
我们可以使用export将这个对象导出
const util = { |
也可以简写成
export const util = { |
当要导出多个值时,还可以简写。比如我有一个文件: user.js
var name = "jack" |
比如我们之前定义了两个JS文件
hello.js
const util = { |
user.js
var name = "jack" |
我们在main.js文件中导入上面的两个文件,并使用上面的两个文件的方法
import util from "./hello" //这里只能是util 不能是其他的名字 |
当我们这样定义时,导入的时候可以自定义导入时使用的变量名
export default { //导出的时候这样写 |
import xxx from "./hello" //这里使用可以随意的取名 |
在MVVM之前,开发人员从后端获取需要的数据模型,然后要通过DOM操作 Model渲染到View中。而后当用户操作视图,我们还需要通过DOM获取View中的数据,然后同步到Model中。而MVVM中的VM要做的事情就是把DOM操作完全封装起来,开发人员不用再关心Model和View之间是如何互相影响的:
Vue (发音为 /vjuː/,类似 view) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任。
1.创建一个文件夹,用VSCode打开
2.下载vue的依赖
初始化项目 |
3.创建一个index.html文件
|
|
|
浏览器插件地址 直接解压,打开浏览器插件的开发者模式,选择加载已解压的文件,选择chrome目录即可
|
|
|
|
|
|
|
|
在大型应用开发的时候,页面可以划分成很多部分。往往不同的页面,也会有相同的部分。例如可能会有相同的头部导航。但是每个页面都都独自开发,这无疑增加了开发的成本。所以我们会把页面的不同部分拆分成独立的组件,然后在不同的页面就可以共享这些组件,避免重复开发。
|
|
生命周期图
1.全局安装webpack
npm install webpack -g |
2.全局安装vue脚手架
npm install -g @vue/cli-init |
3.初始化vue的项目
创建一个文件夹,打开cmd,切换到这个文件夹,执行以下的命令
vue脚手架使用webpacke模板初始化一个项目 |
4.启动vue的项目
项目中的package.json中有scripts,代表我们能运行的命令
启动项目 |
给定一个整数数组 nums
和一个整数目标值 target
,请你在该数组中找出 和为目标值 target
的那 两个 整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9 |
示例 2:
输入:nums = [3,2,4], target = 6 |
示例 3:
输入:nums = [3,3], target = 6 |
提示:
2 <= nums.length <= 104
-109 <= nums[i] <= 109
-109 <= target <= 109
代码实现
自己的代码 暴力枚举
class Solution { |
给你一个 升序排列 的数组 nums
,请你** 原地** 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums
中唯一元素的个数。
考虑 nums
的唯一元素的数量为 k
,你需要做以下事情确保你的题解可以被通过:
nums
,使 nums
的前 k
个元素包含唯一元素,并按照它们最初在 nums
中出现的顺序排列。nums
的其余元素与 nums
的大小不重要。k
。判题标准:
系统会用下面的代码来测试你的题解:
int[] nums = [...]; // 输入数组 |
如果所有断言都通过,那么您的题解将被 通过。
示例 1:
输入:nums = [1,1,2] |
示例 2:
输入:nums = [0,0,1,1,1,2,2,3,3,4] |
提示:
1 <= nums.length <= 3 * 104
-104 <= nums[i] <= 104
nums
已按 升序 排列代码实现
package com.leetcode.array; |
给你一个数组 nums
和一个值 val
,你需要 原地 移除所有数值等于 val
的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1)
额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝 |
示例 1:
输入:nums = [3,2,2,3], val = 3 |
示例 2:
输入:nums = [0,1,2,2,3,0,4,2], val = 2 |
提示:
0 <= nums.length <= 100
0 <= nums[i] <= 50
0 <= val <= 100
代码实现
性能指标
响应时间(Response Time: RT)响应时间指用户从客户端发起一个请求开始,到客户端接收到从服务器端返回的响应结束,整个过程所耗费的时间。
HPS(Hits Per Second) :每秒点击次数,单位是次/秒。
TPS(Transaction per Second):系统每秒处理交易数,单位是笔/秒。
QPS(Query per Second):系统每秒处理查询次数,单位是次/秒。对于互联网业务中,如果某些业务有且仅有一个请求连接,那么 TPS=QPS=HPS,一般情况下用 TPS 来衡量整个业务流程,用 QPS 来衡量接口查询次数,用 HPS 来表示对服务器单击请求。
无论 TPS、QPS、HPS,此指标是衡量系统处理能力非常重要的指标,越大越好,根据经验,一般情况下:
金融行业:1000TPS~50000TPS,不包括互联网化的活动
保险行业:100TPS~100000TPS,不包括互联网化的活动
制造行业:10TPS~5000TPS
互联网电子商务:10000TPS~1000000TPS
互联网中型网站:1000TPS~50000TPS
互联网小型网站:500TPS~10000TPS
最大响应时间(Max Response Time) 指用户发出请求或者指令到系统做出反应(响应)的最大时间。
最少响应时间(Mininum ResponseTime) 指用户发出请求或者指令到系统做出反应(响应)的最少时间。
90%响应时间(90% Response Time) 是指所有用户的响应时间进行排序,第 90%的响应时间。
从外部看,性能测试主要关注如下三个指标
吞吐量:每秒钟系统能够处理的请求数、任务数。
响应时间:服务处理一个请求或一个任务的耗时。
错误率:一批请求中结果出错的请求所占比例。
1.下载安装包
安装网址: https://jmeter.apache.org/download_jmeter.cgi
2.解压并双击运行
设置为中文页面
点击Options->Language->选择简体中文
永久设置成中文->在bin\jmeter.properties
配置文件中添加如下配置
jmeter发送请求响应的数据乱码的问题->在bin\jmeter.properties
配置文件中添加如下配置
1.新建线程组
右键点击测试计划->添加->线程->线程组
线程组参数详解:
线程数:虚拟用户数。一个虚拟用户占用一个进程或线程。设置多少虚拟用户数在这里也就是设置多少个线程数。
线程数:虚拟用户数。一个虚拟用户占用一个进程或线程。设置多少虚拟用户数在这里也就是设置多少个线程数。
Ramp-Up Period(in seconds)准备时长:设置的虚拟用户数需要多长时间全部启动。如果线程数为 10,准备时长为 2,那么需要 2 秒钟启动10个线程,也就是每秒钟启动 5 个线程。
循环次数:每个线程发送请求的次数。如果线程数为 10,循环次数为 100,那么每个线程发送 100 次请求。总请求数为 10*100=1000 。如勾选了“永远”,那么所有线程会一直发送请求,一到选择停止运行脚本。
Delay Thread creation until needed:直到需要时延迟线程的创建。
调度器:设置线程组启动的开始时间和结束时间(配置调度器时,需要勾选循环次数为永远)
持续时间(秒):测试持续时间,会覆盖结束时间
启动延迟(秒):测试延迟启动时间,会覆盖启动时间
启动时间:测试启动时间,启动延迟会覆盖它。当启动时间已过,手动只需测试时当前时间也会覆盖它。
结束时间:测试结束时间,持续时间会覆盖它。
2.添加HTTP请求
3.添加监听器
4.启动压测和查看分析结果
windows 本身提供的端口访问机制的问题。
Windows 提供给 TCP/IP 链接的端口为 1024-5000,并且要四分钟来循环回收他们。就导致
我们在短时间内跑大量的请求时将端口占满了。
1.cmd 中,用 regedit 命令打开注册表
2.在 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters 下,
右击 parameters,添加一个新的 DWORD,名字为 MaxUserPort,然后双击 MaxUserPort,输入数值数据为 65534,基数选择十进制(如果是分布式运行的话,控制机器和负载机器都需要这样操作)
3.修改配置完毕之后记得重启机器才会生效
官网解释:https://support.microsoft.com/zh-cn/help/196271/when-you-try-to-connect-from-tcp-ports-greater-than-5000-you-receive-t
TCPTimedWaitDelay:30
Jdk 的两个小工具 jconsole、jvisualvm(升级版的 jconsole);通过命令行启动,可监控本地和远程应用。
jvisualvm相比jconsole功能更全,一般使用jvisualvm
Visual GC插件的安装
点击工具->插件->在可用插件中选择Visual GC安装
安装成功之后显示如下
]]>c:\javacode>dir //查看C盘javacode目录下有那些文件 |
c:\javacode>cd /D d: //从C盘切换到D盘 |
c:\javacode>help cd //解释cd是怎么使用的 |
c:\javacode>cd .. //切换到上一级目录 |
c:\javacode>cd \ //切换到根目录 |
C:\tree c:\javacode //查看C盘下的子目录javacode下面的所有子目录,形成一个目录树 |
>cls //清空所有内容,清屏 |
//获取IP相关的信息 |
//开启MySQL和关闭MySQL服务 |
配置文件中设置时间的格式和时区
检查一下分页插件的配置 选择以下正确的分页插件配置
package com.atguigu.common.config.mp; |
package com.atguigu.config; |
网关中的路由匹配问题,模糊的路由放在精确的路由前面,容易是精确的路由配置失效
spring: |
使用Docer启动容器的时候设置忽略大小写,表不存在是因为表名大小写的原因。
方案一:启动的时候加上–lower_case_table_names= 1
docker run -p 3306:3306 --name mysql -v /usr/local/docker/mysql/conf:/etc/mysql -v /usr/local/docker/mysql/logs:/var/log/mysql -v /usr/local/docker/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7 --lower_case_table_names= 1 |
方案二: 修改mysql挂载在宿主机的配置文件
# 在配置文件中添加如下的配置 |
检查防火墙状态: |
查询被占用的端口号的信息 |
不是永久有效的 |
查找是否安装了mysql的服务 |
传递普通对象参数的写法
params: searchObj
getPageList(current,limit,searchObj){ |
|
json格式的传递
data: searchObj
add(sysRole){ |
|
logging: |
//这里是当children为空的时候,向前端传递数据就不带这个 |
查看node的版本 |
常规的安装,无需教程
IDEA中设置默认的JDK
选中需要设置为默认JDK的JDK
环境变量的配置
MAVEN_HOME和Path 配置文件的配置
设置默认maven,每次创建新的工程的时候就会使用这个maven
然后在弹出的页面中设置,这样每次创建新的工程就会使用当前设置的maven
展开目录树 取消勾选下面的选项
快捷键的配置 IDEA中的全局查找的快捷键会和windows的中文的繁简字体切换的快捷键冲突,直接关掉系统的快捷键
页面中字体的配置
代码字体的配置
设置代码粗体
打开的多个文件多行显示,不在同一行显示
浅色主题的配置
修改注释的颜色
修改多行注释的颜色: 修改Doc comment -> Text的颜色
具体的安装教程在《MySql5.7的安装教程的博客中》
github下载之后,直接解压运行
下载的地址:https://github.com/alibaba/nacos/releases
踩坑:单节点部署的话,启动的时候需要配置一下,不然启动的时候会闪退
下载的网址:https://nodejs.org/zh-cn/download/releases
选择.msi的下载即可
不用配置环境变量,一直下一步,直到安装成功过,node自带npm包管理工具
检验命令 |
以下的操作在下载安装完毕之后进行 |
官网简介
RuoYi 是一个后台管理系统,主要目的让开发者注重专注业务,降低技术难度,从而节省人力成本,缩短项目周期,提高软件安全质量。
单体版本技术栈:Spring Boot、Apache Shiro、MyBatis、Thymeleaf
前后端分离版本技术栈:SpringBoot、Spring Security、Jwt、Vue
微服务版本技术栈:Spring Boot、Spring Cloud & Alibaba
相关网站
总结
若依就是一个后台管理系统的通用模板,这个模板中包含了后台管理系统的常用功能,比如登录、权限管理、菜单管理、用户管理等,避免我们在开发中重复造轮子,让开发回归到业务本身。
后端:JDK、Maven、Mysql、Redis
前端:Node、Npm
1.克隆命令
git clone https://gitee.com/y_project/RuoYi-Vue.git |
2.使用Idea打开项目并初始化数据库脚本
执行克隆的项目工程目录下sql目录中的两个sql文件初始化数据库
3.修改Mysql和Redis的连接信息
在application.yml中修改redis的连接信息,在application-druid.yml中修改mysql的连接信息
application.yml
application-druid.yml
4.使用Vscode或者Idea打开前端项目并执行命令初始化前端项目
前端相关命令 |
配置的原因是在前端解决跨域
1.浏览器发送http://localhost/prod-api/captchaImage
请求
2.前端接收http://localhost/prod-api/captchaImage
请求,通过下面的配置重写url,然后再将请求发送给后端(通过['^' + process.env.VUE_APP_BASE_API]: ''
配置将http://localhost/prod-api/captchaImage
重写为http://localhost:8080/captchaImage
)
该配置在vue.config.js
配置文件中
devServer: { |
实现思路
1.后端生成一个计算表达式 ,例如 1+1=?@2
2.然后将1+1=?以流的方式传递给前端,前端展示为图片,同时后端也会传递一个UUID给前端,这个UUID为就是xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
,2作为结果value值存入Redis中。存入Redis中的key的格式为captcha_codes:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
3.用户在填写完表单之后会携带账号、密码、验证码的结果值,以及之前后端传递给前端的UUID值给后端,后端会先以captcha_codes:+UUID
作为key在reidis中查找value值,再和前端输入的验证码结果值作比较,如果验证码相同再比较账号密码,账号密码在数据库中也可以匹配上就登录成功,反之返回给前端账号密码错误;如果验证码不相同,直接返回给前端,验证码错误。
代码实现
1.前端在页面初始化或者刷新验证码的时候会调用下面的方法获取验证码和UUID值
getCode() { |
2.后端生成验证码的过程
/** |
3.前端登录提交的表单和处理登录的方法
data() { |
handleLogin() { |
4.后端登录的实现逻辑
]]>官网网址: https://www.postman.com/
Postman的首页
以一个普通的登录操作来演示Postman的使用教程
发送Post请求的: 1.修改请求的方式 2.设置请求体的数据类型
请求数据的格式
{ |
响应数据的格式
{ |
SpringBoot整合Swagger:https://qingling.icu/posts/32246.html
以一个普通的登录操作来演示Swagger的使用教程
SpringBoot整合Knife4j教程: https://qingling.icu/posts/855.html
gitee地址:https://gitee.com/xiaoym/knife4j
官方文档:https://doc.xiaominfo.com/
核心功能 :文档说明 在线调试 个性化配置 离线文档 接口排序
以一个普通的登录操作来演示knife4j的使用教程
下载离线文档
下载成功之后打开的效果
]]>微信登录使用了OAuth2解决方案
照片拥有者想要在云冲印服务上打印照片,云冲印服务需要访问云存储服务上的资源
资源拥有者:照片拥有者
客户应用:云冲印
受保护的资源:照片
用户将自己的”云存储”服务的用户名和密码,告诉”云冲印”,后者就可以读取用户的照片了。这样的做法有以下几个严重的缺点。
(1)”云冲印”为了后续的服务,会保存用户的密码,这样很不安全。
(2)Google不得不部署密码登录,而我们知道,单纯的密码登录并不安全。
(3)”云冲印”拥有了获取用户储存在Google所有资料的权力,用户没法限制”云冲印”获得授权的范围和有效期。
(4)用户只有修改密码,才能收回赋予”云冲印”的权力。但是这样做,会使得其他所有获得用户授权的第三方应用程序全部失效。
(5)只要有一个第三方应用程序被破解,就会导致用户密码泄漏,以及所有被密码保护的数据泄漏。
总结:
将受保护的资源中的用户名和密码存储在客户应用的服务器上,使用时直接使用这个用户名和密码登录
适用于同一公司内部的多个系统,不适用于不受信的第三方应用
适用于合作商或者授信的不同业务部门之间
接近OAuth2方式,需要考虑如何管理令牌、颁发令牌、吊销令牌,需要统一的协议,因此就有了OAuth2协议
令牌类比仆从钥匙
川崎高彦:OAuth2领域专家,开发了一个OAuth2 sass服务,OAuth2 as Service,并且做成了一个公司
在融资的过程中为了向投资人解释OAuth2是什么,于是写了一篇文章,《OAuth2最简向导》
现代微服务中系统微服务化以及应用的形态和设备类型增多,不能用传统的登录方式
核心的技术不是用户名和密码,而是token,由AuthServer颁发token,用户使用token进行登录
1、注册
微信开放平台:https://open.weixin.qq.com
2、邮箱激活
3、完善开发者资料
4、开发者资质认证
准备营业执照,1-2个工作日审批、300元
5、创建网站应用
提交审核,7个工作日审批
6、内网穿透
ngrok的使用
获取access_token时序图
第一步:请求CODE(生成授权URL)
第二步:通过code获取access_token(开发回调URL)
由于微信登录需要企业用户才能注册,这里我们使用的是尚硅谷的密钥
当前项目的启动端口设置为 8160 才可以获取到登录的二维码
# 微信开放平台appid |
import org.springframework.beans.factory.InitializingBean; |
生成内嵌的二维码
返回给前端的相关参数
参数 | 是否必须 | 说明 |
---|---|---|
self_redirect | 否 | true:手机点击确认登录后可以在 iframe 内跳转到 redirect_uri,false:手机点击确认登录后可以在 top window 跳转到 redirect_uri。默认为 false。 |
id | 是 | 第三方页面显示二维码的容器id |
appid | 是 | 应用唯一标识,在微信开放平台提交应用审核通过后获得 |
scope | 是 | 应用授权作用域,拥有多个作用域用逗号(,)分隔,网页应用目前仅填写snsapi_login即可 |
redirect_uri | 是 | 重定向地址,需要进行UrlEncode |
state | 否 | 用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止csrf攻击(跨站请求伪造攻击),建议第三方带上该参数,可设置为简单的随机数加session进行校验 |
style | 否 | 提供”black”、”white”可选,默认为黑色文字描述。详见文档底部FAQ |
href | 否 | 自定义样式链接,第三方可根据实际需求覆盖默认样式。详见文档底部FAQ |
生成扫描二维码的后端部分
import com.atguigu.yygh.common.result.Result; |
生成扫描二维码的前端部分
JS部分
import request from '@/utils/request'; |
引入api
import weixinApi from "@/api/weixin"; |
在mounted()方法中初始化微信js
mounted() { |
在页面中微信登录按钮绑定的是weixinLogin()点击事件,所以我们要在这个方法中初始化对象
向后端发起请求获取生成二维码需要的参数信息
weixinLogin() { |
点击微信登录之后,页面上的效果
实现的思路:
用户扫描二维码 -> 点击确认 -> 会执行回调请求(请求配置文件中这个地址wx.open.redirect_url=http://localhost:8160/api/ucenter/wx/callback) -> 获取回调请求中携带的参数,再根据参数获取扫描用户的信息,执行相关的操作
扫描成功之后发起如下的请求:
引入httpclient(不依赖浏览器发起请求)依赖
<dependency> |
导入HttpClientUtils工具类
import org.apache.commons.io.IOUtils; |
Controller
package com.atguigu.yygh.user.controller; |
<dependency> |
常用的检验注解
示例
package com.atguigu.gulimall.product.entity; |
/** |
测试查看返回的数据的格式,这里我们输入的都是不合法的数据格式,返回的结果如下
{ |
参数没有错误之后返回的数据
{ |
在统一异常处理类上加上数据校验异常的异常处理
package com.atguigu.gulimall.product.exception; |
这个时候上面的代码就可以简化为下面的格式,数据校验出现问题之后就直接在统一异常处理中处理了
/** |
例如:当我们在添加一个品牌的时候,我们不需要传入这个品牌的id信息,需要这个品牌的品牌名信息,但是在修改这个品牌的时候,我们需要这个品牌的id信息和品牌名的信息,这时我们就需要使用分组校验了
添加操作的组
package com.atguigu.common.valid; |
修改操作的组
package com.atguigu.common.valid; |
注意:使用了分组校验之后,其余的字段也要加上分组信息,否则没有加上分组信息的会失效
package com.atguigu.gulimall.product.entity; |
注意:这里如果产生数据校验的出现问题的异常,会由统一异常处理进行处理
保存的控制器方法上添加上添加分组信息
/** |
修改的分组上添加上修改的分组信息
/** |
测试:我们在新增的时候加上品牌的id,这时就会产生错误
修改的时候不带品牌的id信息
这里我们以编写一个输入的值只能是指定值的注解为例
/** |
编写自定义注解
package com.atguigu.common.valid; |
编写注解中默认提示消息的配置文件
ValidationMessages.properties
com.atguigu.common.valid.ListValue.message=必须提交指定的值 |
package com.atguigu.common.valid; |
在自定义的注解上面关联上上面自定义的校验规则
测试自定义的注解
]]>零壹贰叁肆伍陆柒捌玖拾佰仟万亿吉太拍艾分厘毫微卍卐卄巜弍弎弐朤氺曱甴囍兀々〆のぁ〡〢〣〤〥〦〧〨〩㊎㊍㊌㊋㊏㊚㊛㊐㊊㊣㊤㊥㊦㊧㊨㊒㊫㊑㊓㊔㊕㊖㊗㊘㊜㊝㊞㊟㊠㊡㊢㊩㊪㊬㊭㊮㊯㊰㊀㊁㊂㊃㊄㊅㊆㊇㊈㊉
❤❥웃유♋☮✌☏☢☠✔☑♚▲♪✈✞÷↑↓◆◇⊙■□△▽¿─│♥❣♂♀☿Ⓐ✍✉☣☤✘☒♛▼♫⌘☪≈←→◈◎☉★☆⊿※¡━┃♡ღツ☼☁❅♒✎©®™Σ✪✯☭➳卐√↖↗●◐Θ◤◥︻〖〗┄┆℃℉°✿ϟ☃☂✄¢€£∞✫★½✡×↙↘○◑⊕◣◢︼【】┅┇☽☾✚〓▂▃▄▅▆▇█▉▊▋▌▍▎▏↔↕☽☾の•▸◂▴▾┈┊①②③④⑤⑥⑦⑧⑨⑩ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ㍿▓♨♛❖♓☪✙┉┋☹☺☻تヅツッシÜϡﭢ™℠℗©®♥❤❥❣❦❧♡۵웃유ღ♋♂♀☿☼☀☁☂☄☾☽❄☃☈⊙☉℃℉❅✺ϟ☇♤♧♡♢♠♣♥♦☜☞☝✍☚☛☟✌✽✾✿❁❃❋❀⚘☑✓✔√☐☒✗✘ㄨ✕✖✖⋆✢✣✤✥❋✦✧✩✰✪✫✬✭✮✯❂✡★✱✲✳✴✵✶✷✸✹✺✻✼❄❅❆❇❈❉❊†☨✞✝☥☦☓☩☯☧☬☸✡♁✙♆。,、':∶;?‘’“”〝〞ˆˇ﹕︰﹔﹖﹑•¨….¸;!´?!~—ˉ|‖"〃`@﹫¡¿﹏﹋﹌︴々﹟#﹩$﹠&﹪%*﹡﹢﹦﹤‐ ̄¯―﹨ˆ˜﹍﹎+=<__-\ˇ~﹉﹊()〈〉‹›﹛﹜『』〖〗[]《》〔〕{}「」【】︵︷︿︹︽_﹁﹃︻︶︸﹀︺︾ˉ﹂﹄︼☩☨☦✞✛✜✝✙✠✚†‡◉○◌◍◎●◐◑◒◓◔◕◖◗❂☢⊗⊙◘◙◍⅟½⅓⅕⅙⅛⅔⅖⅚⅜¾⅗⅝⅞⅘≂≃≄≅≆≇≈≉≊≋≌≍≎≏≐≑≒≓≔≕≖≗≘≙≚≛≜≝≞≟≠≡≢≣≤≥≦≧≨≩⊰⊱⋛⋚∫∬∭∮∯∰∱∲∳%℅‰‱㊣㊎㊍㊌㊋㊏㊐㊊㊚㊛㊤㊥㊦㊧㊨㊒㊞㊑㊒㊓㊔㊕㊖㊗㊘㊜㊝㊟㊠㊡㊢㊩㊪㊫㊬㊭㊮㊯㊰㊙㉿囍♔♕♖♗♘♙♚♛♜♝♞♟ℂℍℕℙℚℝℤℬℰℯℱℊℋℎℐℒℓℳℴ℘ℛℭ℮ℌℑℜℨ♪♫♩♬♭♮♯°øⒶ☮✌☪✡☭✯卐✐✎✏✑✒✍✉✁✂✃✄✆✉☎☏➟➡➢➣➤➥➦➧➨➚➘➙➛➜➝➞➸♐➲➳⏎➴➵➶➷➸➹➺➻➼➽←↑→↓↔↕↖↗↘↙↚↛↜↝↞↟↠↡↢↣↤↥↦↧↨➫➬➩➪➭➮➯➱↩↪↫↬↭↮↯↰↱↲↳↴↵↶↷↸↹↺↻↼↽↾↿⇀⇁⇂⇃⇄⇅⇆⇇⇈⇉⇊⇋⇌⇍⇎⇏⇐⇑⇒⇓⇔⇕⇖⇗⇘⇙⇚⇛⇜⇝⇞⇟⇠⇡⇢⇣⇤⇥⇦⇧⇨⇩⇪➀➁➂➃➄➅➆➇➈➉➊➋➌➍➎➏➐➑➒➓㊀㊁㊂㊃㊄㊅㊆㊇㊈㊉ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅬⅭⅮⅯⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻⅼⅽⅾⅿ┌┍┎┏┐┑┒┓└┕┖┗┘┙┚┛├┝┞┟┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋╌╍╎╏═║╒╓╔╕╖╗╘╙╚╛╜╝╞╟╠╡╢╣╤╥╦╧╨╩╪╫╬◤◥◄►▶◀◣◢▲▼◥▸◂▴▾△▽▷◁⊿▻◅▵▿▹◃❏❐❑❒▀▁▂▃▄▅▆▇▉▊▋█▌▍▎▏▐░▒▓▔▕■□▢▣▤▥▦▧▨▩▪▫▬▭▮▯㋀㋁㋂㋃㋄㋅㋆㋇㋈㋉㋊㋋㏠㏡㏢㏣㏤㏥㏦㏧㏨㏩㏪㏫㏬㏭㏮㏯㏰㏱㏲㏳㏴㏵㏶㏷㏸㏹㏺㏻㏼㏽㏾㍙㍚㍛㍜㍝㍞㍟㍠㍡㍢㍣㍤㍥㍦㍧㍨㍩㍪㍫㍬㍭㍮㍯㍰㍘☰☲☱☴☵☶☳☷☯
♈ | ♉ | ♊ | ♋ | ♌ | ♍ | ♎ | ♏ | ♐ | ♑ | ♒ | ♓ |
---|---|---|---|---|---|---|---|---|---|---|---|
牧羊 | 金牛 | 双子 | 巨蟹 | 狮子 | 处女 | 天秤 | 天蝎 | 射手 | 摩羯 | 水瓶 | 双鱼 |
♠♣♧♡♥❤❥❣♂♀✲☀☼☾☽◐◑☺☻☎☏✿❀№↑↓←→√×÷★℃℉°◆◇⊙■□△▽¿½☯✡㍿卍卐♂♀✚〓㎡♪♫♩♬㊚㊛囍㊒㊖Φ♀♂‖$@*&#※卍卐Ψ♫♬♭♩♪♯♮⌒¶∮‖€£¥$
①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳⓪❶❷❸❹❺❻❼❽❾❿⓫⓬⓭⓮⓯⓰⓱⓲⓳⓴㊀㊁㊂㊃㊄㊅㊆㊇㊈㊉㈠㈡㈢㈣㈤㈥㈦㈧㈨㈩⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂⒃⒄⒅⒆⒇⒈⒉⒊⒋⒌⒍⒎⒏⒐⒑⒒⒓⒔⒕⒖⒗⒘⒙⒚⒛ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵
﹢﹣×÷±/=≌∽≦≧≒﹤﹥≈≡≠=≤≥<>≮≯∷∶∫∮∝∞∧∨∑∏∪∩∈∵∴⊥∥∠⌒⊙√∟⊿㏒㏑%‰⅟½⅓⅕⅙⅛⅔⅖⅚⅜¾⅗⅝⅞⅘≂≃≄≅≆≇≈≉≊≋≌≍≎≏≐≑≒≓≔≕≖≗≘≙≚≛≜≝≞≟≠≡≢≣≤≥≦≧≨≩⊰⊱⋛⋚∫∬∭∮∯∰∱∲∳%℅‰‱øØπ
♥❣ღ♠♡♤❤❥
。,、':∶;?‘’“”〝〞ˆˇ﹕︰﹔﹖﹑•¨….¸;!´?!~—ˉ|‖"〃`@﹫¡¿﹏﹋﹌︴々﹟#﹩$﹠&﹪%*﹡﹢﹦﹤‐ ̄¯―﹨ˆ˜﹍﹎+=<__-\ˇ~﹉﹊()〈〉‹›﹛﹜『』〖〗[]《》〔〕{}「」【】︵︷︿︹︽_﹁﹃︻︶︸﹀︺︾ˉ﹂﹄︼❝❞
°′″$¥〒¢£%@℃℉﹩﹪‰﹫㎡㏕㎜㎝㎞㏎m³㎎㎏㏄º○¤%$º¹²³
€£Ұ₴$₰¢₤¥₳₲₪₵元₣₱฿¤₡₮₭₩ރ円₢₥₫₦zł﷼₠₧₯₨Kčर₹ƒ₸¢
↑↓←→↖↗↘↙↔↕➻➼➽➸➳➺➻➴➵➶➷➹▶►▷◁◀◄«»➩➪➫➬➭➮➯➱⏎➲➾➔➘➙➚➛➜➝➞➟➠➡➢➣➤➥➦➧➨↚↛↜↝↞↟↠↠↡↢↣↤↤↥↦↧↨⇄⇅⇆⇇⇈⇉⇊⇋⇌⇍⇎⇏⇐⇑⇒⇓⇔⇖⇗⇘⇙⇜↩↪↫↬↭↮↯↰↱↲↳↴↵↶↷↸↹☇☈↼↽↾↿⇀⇁⇂⇃⇞⇟⇠⇡⇢⇣⇤⇥⇦⇧⇨⇩⇪↺↻⇚⇛♐
✐✎✏✑✒✍✉✁✂✃✄✆✉☎☏☑✓✔√☐☒✗✘ㄨ✕✖✖☢☠☣✈★☆✡囍㍿☯☰☲☱☴☵☶☳☷☜☞☝✍☚☛☟✌♤♧♡♢♠♣♥♦☀☁☂❄☃♨웃유❖☽☾☪✿♂♀✪✯☭➳卍卐√×■◆●○◐◑✙☺☻❀⚘♔♕♖♗♘♙♚♛♜♝♞♟♧♡♂♀♠♣♥❤☜☞☎☏⊙◎☺☻☼▧▨♨◐◑↔↕▪▒◊◦▣▤▥▦▩◘◈◇♬♪♩♭♪の★☆→あぃ£Ю〓§♤♥▶¤✲❈✿✲❈➹☀☂☁【】┱┲❣✚✪✣✤✥✦❉❥❦❧❃❂❁❀✄☪☣☢☠☭ღ▶▷◀◁☀☁☂☃☄★☆☇☈⊙☊☋☌☍ⓛⓞⓥⓔ╬『』∴☀♫♬♩♭♪☆∷﹌の★◎▶☺☻►◄▧▨♨◐◑↔↕↘▀▄█▌◦☼♪の☆→♧ぃ£❤▒▬♦◊◦♠♣▣۰•❤•۰►◄▧▨♨◐◑↔↕▪▫☼♦⊙●○①⊕◎Θ⊙¤㊣★☆♀◆◇◣◢◥▲▼△▽⊿◤◥✐✌✍✡✓✔✕✖♂♀♥♡☜☞☎☏⊙◎☺☻►◄▧▨♨◐◑↔↕♥♡▪▫☼♦▀▄█▌▐░▒▬♦◊◘◙◦☼♠♣▣▤▥▦▩◘◙◈♫♬♪♩♭♪✄☪☣☢☠♯♩♪♫♬♭♮☎☏☪♈ºº₪¤큐«»™♂✿♥ ◕‿-。 。◕‿◕。
ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζνξοπρσηθικλμτυφχψω
АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя
āáǎàōóǒòēéěèīíǐìūúǔùǖǘǚǜüêɑńňɡㄅㄆㄇㄈㄉㄊㄋㄌㄍㄎㄏㄐㄑㄒㄓㄔㄕㄖㄗㄘㄙㄚㄛㄜㄝㄞㄟㄠㄡㄢㄣㄤㄥㄦㄧㄨㄩ
ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろゎわゐゑをんゔゕゖァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶヷヸヹヺ・ーヽヾヿ゠ㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ
─ ━│┃╌╍╎╏┄ ┅┆┇┈ ┉┊┋┌┍┎┏┐┑┒┓└ ┕┖┗ ┘┙┚┛├┝┞┟┠┡┢┣ ┤┥┦┧┨┩┪┫┬ ┭ ┮ ┯ ┰ ┱ ┲ ┳ ┴ ┵ ┶ ┷ ┸ ┹ ┺ ┻┼ ┽ ┾ ┿ ╀ ╁ ╂ ╃ ╄ ╅ ╆ ╇ ╈ ╉ ╊ ╋ ╪ ╫ ╬═║╒╓╔ ╕╖╗╘╙╚ ╛╜╝╞╟╠ ╡╢╣╤ ╥ ╦ ╧ ╨ ╩ ╳╔ ╗╝╚ ╬ ═ ╓ ╩ ┠ ┨┯ ┷┏ ┓┗ ┛┳ ⊥ ﹃ ﹄┌ ╮ ╭ ╯╰
♚ ♛ ♝ ♞ ♜ ♟ ♔ ♕ ♗ ♘ ♖ ♟
😀😁😂😃😄😅😆😉😊😋😎😍😘😗😙😚☺😇😐😑😶😏😣😥😮😯😪😫😴😌😛😜😝😒😓😔😕😲😷😖😞😟😤😢😭😦😧😨😬😰😱😳😵😡😠😈👿👹👺💀👻👽👦👧👨👩👴👵👶👱👮👲👳👷👸💂🎅👰👼💆💇🙍🙎🙅🙆💁🙋🙇🙌🙏👤👥🚶🏃👯💃👫👬👭💏💑👪💪👈👉☝👆👇✌✋👌👍👎✊👊👋👏👐✍👣👀👂👃👅👄💋👓👔👕👖👗👘👙👚👛👜👝🎒💼👞👟👠👡👢👑👒🎩🎓💄💅💍🌂🙈🙉🙊🐵🐒🐶🐕🐩🐺🐱😺😸😹😻😼😽🙀😿😾🐈🐯🐅🐆🐴🐎🐮🐂🐃🐄🐷🐖🐗🐽🐏🐑🐐🐪🐫🐘🐭🐁🐀🐹🐰🐇🐻🐨🐼🐾🐔🐓🐣🐤🐥🐦🐧🐸🐊🐢🐍🐲🐉🐳🐋🐬🐟🐠🐡🐙🐚🐌🐛🐜🐝🐞🦋💐🌸💮🌹🌺🌻🌼🌷🌱🌲🌳🌴🌵🌾🌿🍀🍁🍂🍃🌍🌎🌏🌐🌑🌒🌓🌔🌕🌖🌗🌘🌙🌚🌛🌜☀🌝🌞⭐🌟🌠☁⛅☔⚡❄🔥💧🌊💩🍇🍈🍉🍊🍋🍌🍍🍎🍏🍐🍑🍒🍓🍅🍆🌽🍄🌰🍞🍖🍗🍔🍟🍕🍳🍲🍱🍘🍙🍚🍛🍜🍝🍠🍢🍣🍤🍥🍡🍦🍧🍨🍩🍪🎂🍰🍫🍬🍭🍮🍯🍼☕🍵🍶🍷🍸🍹🍺🍻🍴🎪🎭🎨🎰🚣🛀🎫🏆⚽⚾🏀🏈🏉🎾🎱🎳⛳🎣🎽🎿🏂🏄🏇🏊🚴🚵🎯🎮🎲🎷🎸🎺🎻🎬👾🌋🗻🏠🏡🏢🏣🏤🏥🏦🏨🏩🏪🏫🏬🏭🏯🏰💒🗼🗽⛪⛲🌁🌃🌆🌇🌉🌌🎠🎡🎢🚂🚃🚄🚅🚆🚇🚈🚉🚊🚝🚞🚋🚌🚍🚎🚏🚐🚑🚒🚓🚔🚕🚖🚗🚘🚚🚛🚜🚲⛽🚨🚥🚦🚧⚓⛵🚤🚢✈💺🚁🚟🚠🚡🚀🎑🗿🛂🛃🛄🛅💌💎🔪💈🚪🚽🚿🛁⌛⏳⌚⏰🎈🎉🎊🎎🎏🎐🎀🎁📯📻📱📲☎📞📟📠🔋🔌💻💽💾💿📀🎥📺📷📹📼🔍🔎🔬🔭📡💡🔦🏮📔📕📖📗📘📙📚📓📃📜📄📰📑🔖💰💴💵💶💷💸💳✉📧📨📩📤📥📦📫📪📬📭📮✏✒📝📁📂📅📆📇📈📉📊📋📌📍📎📏📐✂🔒🔓🔏🔐🔑🔨🔫🔧🔩🔗💉💊🚬🔮🚩🎌💦💨💣☠♠♥♦♣🀄🎴🔇🔈🔉🔊📢📣💤💢💬💭♨🌀🔔🔕✡✝🔯📛🔰🔱⭕✅☑✔✖❌❎➕➖➗➰➿〽✳✴❇‼⁉❓❔❕❗©®™🎦🔅🔆💯🔠🔡🔢🔣🔤🅰🆎🅱🆑🆒🆓ℹ🆔Ⓜ🆕🆖🅾🆗🅿🆘🆙🆚🈁🈂🈷🈶🈯🉐🈹🈚🈲🉑🈸🈴🈳㊗㊙🈺🈵▪▫◻◼◽◾⬛⬜🔶🔷🔸🔹🔺🔻💠🔲🔳⚪⚫🔴🔵♈♉♊♋♌♍♎♏♐♑♒♓⛎💘❤💓💔💕💖💗💙💚💛💜💝💞💟❣🌿🚧💒☎📟💽⬆↗➡↘⬇↙⬅↖↕↔↩↪⤴⤵🔃🔄🔙🔚🔛🔜🔝🔀🔁🔂▶⏩◀⏪🔼⏫🔽⏬📱📶📳📴♻🏧🚮🚰♿🚹🚺🚻🚼🚾⚠🚸⛔🚫🚳🚭🚯🚱🚷🔞
汉语字典为您提供特殊符号大全,网名符号,特殊符号☎☏✄☪☣☢☠♨« »큐〓㊚㊛囍㊒㊖☑✔☐☒✘㍿☯☰☷♥♠♤❤♂♀★☆☯✡※卍卐■□◆◇▲△▂▃▄▅▆▇█●○◎⊕⊙㊣↑↓←→↖↗↘↙㎡№§※≡✿ⓛⓞⓥⓔ∞∑√øπ×÷±∫∵∴⊥∥∠€¥℃™©®①❶㊀㈠⑴⒈Ⓐⓐ⒜
]]>全部的模板地址: JasonsGong/resume: 简历模板 (github.com)
异常返回的结果也为统一的返回结果的对象
package com.atguigu.common.exception; |
package com.atguigu.common.exception; |
/** |
字符串匹配 KMP算法
汉诺塔问题 分治算法
八皇后问题 回溯算法
马踏棋盘问题 图的深度优化遍历算法(DFS)和 贪心算法优化
(1)数据(data)结构(structure)是一门研究组织数据方式的学科,有了编程语言也就有了数据结构,学好了数据结构可以编写出更加漂亮,更加有效率的代码。
(2)程序=数据结构+算法
(3)数据结构是算法的基础
五子棋程序 稀疏数组(压缩存档) 二维数组->转化成稀疏数组->存档 读档反之
约瑟夫问题(丢手帕问题) 单向环形列表
修路问题 求最小生成树 + 普利姆算法
最短路径问题 图+弗洛伊德算法
线性结构:
线性结构作为最常用的数据结构,其特点是数据元素之间存在一对一的线性关系
线性结构有两种不同的存储结构,即顺序存储结构和链式存储结构。顺序存储的线性表称为顺序表,顺序表中的存储元素是连续的
链式存储的线性表称为链表,链表中的存储元素不一定是连续的,元素节点中存放数据元素以及相邻元素的地址信息
线性结构常见的有:数组、队列、链表和栈
非线性结构:
二维数组,多维数组,广义表,树结构,图结构
当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组。
稀疏数组的处理方法是:
1)记录数组一共有几行几列,有多少个不同的值
2)把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模
五子棋程序的存档和退出功能的实现
第一行记录的是原始的数组有几行几列有几个非零的值 例如下面的数组是一个6行7列有8个非零值的数组
package com.atguigu.sparsearray; |
先进先出
队列是一个有序列表,可以通过数组或者链表实现。
遵循先入先出的原则。即:先存入队列的数据,要先取出。后存入队列的数据要后取出。
使用数组模拟队列
package com.atguigu.queue; |
环形队列的思路分析
package com.atguigu.queue; |
在内存中不是连续存储的
链表的逻辑结构
使用带head头的单向链表实现 –水浒英雄排行榜管理 完成对英雄人物的增删改查操作
package com.atguigu.linkedlist; |
/** |
/** |
package com.atguigu.linkedlist; |
/** |
/** |
/** |
不改变链表本身的结构(不是通过链表的反转之后再打印的)
/** |
以下代码有一些bug 后期修改
/** |
package com.atguigu.linkedlist; |
双向的链表的CRUD操作于单向链表的CRUD操作类似
完整的增删改查代码
package com.atguigu.linkedlist; |
Josephu 问题为:设编号为1,2,… n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m 的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。
解决的方案:用一个不带头结点的循环链表来处理Josephu 问题:先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除算法结束。
完整的代码
package com.atguigu.linkedlist; |
1)栈的英文为(stack)
2)栈是一个先入后出(FILO-First In Last Out)的有序列表。
3)栈(stack)是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)。
4)根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除
1)子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
2)处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。
3)表达式的转换[中缀表达式转后缀表达式]与求值(实际解决)。
4)二叉树的遍历。
5)图形的深度优先(depth一first)搜索法。
package com.atguigu.stack; |
中缀表达式
package com.atguigu.stack; |
逆波兰表达式(后缀表达式)
1)输入一个逆波兰表达式(后缀表达式),使用栈(Stack),计算其结果
2)支持小括号和多位数整数,因为这里我们主要讲的是数据结构,因此计算器进行简化,只支持对整数的计算。
package com.atguigu.stack; |
将中缀表达式转化为后缀表达式(逆波兰表达式)
解题的步骤:
1)初始化两个栈:运算符栈s1和储存中间结果的栈s2;
2)从左至右扫描中缀表达式;
3)遇到操作数时,将其压s2;
4)遇到运算符时,比较其与s1栈顶运算符的优先级:
(1)如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
(2)否则,若优先级比栈顶运算符的高,也将运算符压入s1;
(3)否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符相比较
5)遇到括号时:
(1) 如果是左括号“(”,则直接压入s1
(2) 如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
6)重复步骤2至5,直到表达式的最右边
7)将s1中剩余的运算符依次弹出并压入s2
8)依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式
先将中缀表达式转化成list集合
/** |
将中缀表达式的集合转化成逆波兰表达式(后缀表达式)的集合
/** |
通过逆波兰表达式(后缀表达式)的集合得到最终的计算结果
/** |
完整的代码
(不支持小数)
package com.atguigu.stack; |
完整的逆波兰计算器,含小数点的计算
(老师的代码)
package com.atguigu.stack; |
递归就是方法自己调用自己,每次调用时传入不同的变量,递归有助于编程者解决复杂的问题,同时可以让代码变得简洁
1)各种数学问题如: 8皇后问题 , 汉诺塔, 阶乘问题, 迷宫问题, 球和篮子的问题(google编程大赛)
2)各种算法中也会使用到递归,比如快排,归并排序,二分查找,分治算法等.
3)将用栈解决的问题–>第归代码比较简洁
1)执行一个方法时,就创建一个新的受保护的独立空间(栈空间)
2)方法的局部变量是独立的,不会相互影响, 比如n变量
3)如果方法中使用的是引用类型变量(比如数组),就会共享该引用类型的数据.
4)递归必须向退出递归的条件逼近,否则就是无限递归,出现StackOverflowError,死龟了:)
5)当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕。
代码实现
package com.atguigu.recursion; |
使用回溯算法解决 类似于穷举法 后期使用别的算法优化
问题介绍
思路分析
代码实现
package com.atguigu.recursion; |
十大经典的排序算法:菜鸟教程排序算法
度量时间复杂度的两种方法
事后统计法的不足:
时间频度
基本的介绍:一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频度或时间频度。记为T(n)
时间复杂度介绍
常见的时间复杂度
排序算法的时间和空间复杂度
冒泡排序的时间复杂度:o(n^2)
同一台电脑 8万个数据 十几秒左右
优化之前的代码
package com.atguigu.sort; |
优化之后的代码(如果排序的过程中代码有序就不在排序)
package com.atguigu.sort; |
同一台电脑 8万个数据 两秒左右
选择式排序也属于内部排序法,是从欲排序的数据中,按指定的规则选出某一元素,再依规定交换位置后达到排序的目的
两种方法 一个是自己的 一个是老师的
使用老师的代码 老师的代码验证过
package com.atguigu.sort; |
同一台电脑 8万个数据 五秒左右
插入式排序属于内部排序法,是对于欲排序的元素以插入的方式找寻该元素的适当位置,以达到排序的目的。
package com.atguigu.sort; |
同一台电脑 8万个数据 十七秒左右(交换法)
同一台电脑 8万个数据 一秒左右(移动法)
package com.atguigu.sort; |
同一台电脑 8万个数据 不到一秒 800万个数据两秒钟
快速排序(Quicksort)是对冒泡排序的一种改进。基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列
package com.atguigu.sort; |
同一台电脑 8万个数据 大约一秒钟 800万个数据三秒
package com.atguigu.sort; |
同一台电脑8万个数据 一秒左右 8千万个数据会报内存不足
推导代码
package com.atguigu.sort; |
最终代码
package com.atguigu.sort; |
package com.atguigu.search; |
递归的方式解决
基本的写法
package com.atguigu.search; |
完整的代码
package com.atguigu.search; |
package com.atguigu.search; |
阿里云对象存储OSS(Object Storage Service)是一款海量、安全、低成本、高可靠的云存储服务,可提供99.9999999999%(12个9)的数据持久性,99.995%的数据可用性。多种存储类型供选择,全面优化存储成本。OSS具有与平台无关的RESTful API接口,您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。您可以使用阿里云提供的API、SDK接口或者OSS迁移工具轻松地将海量数据移入或移出阿里云OSS。数据存储到阿里云OSS以后,您可以选择标准存储(Standard)作为移动应用、大型网站、图片分享或热点音视频的主要存储方式,也可以选择成本更低、存储期限更长的低频访问存储(Infrequent Access)、归档存储(Archive)、冷归档存储(Cold Archive)作为不经常访问数据的存储方式。
(1)申请阿里云账号
(2)实名认证
(3)开通“对象存储OSS”服务
(4)进入管理控制台
点击创建Bucket
填写相关的信息
AccessKey拥有对阿里云提供服务(对象存储、短信服务、视频点播服务等等)的控制权,要妥善保管
刚开始不会使用的话,查看开发文档是入门的最好方式
创建一个SpringBoot工程
可以引入一个日期工具类joda-time 方便后面生成图片存储的路径
<dependency> |
入门案例中以下值会直接在代码中写死,先不从配置文件中获取
# 地域节点 bucket概览中查看 |
|
以下测试案例由官方开发文档提供
1.通过代码创建Bucket
public class Demo { |
2.上传文件
以下代码用于通过流式上传的方式将文件上传到OSS。
import com.aliyun.oss.ClientException; |
3.下载文件
以下代码用于通过流式下载方式从OSS下载文件。
import com.aliyun.oss.ClientException; |
4.列举文件
以下代码用于列举examplebucket存储空间下的文件。默认列举100个文件。
import com.aliyun.oss.ClientException; |
5.删除文件
以下代码用于删除指定文件。
import com.aliyun.oss.ClientException; |
controller层
import com.atguigu.yygh.common.result.Result; |
service层
import com.aliyun.oss.ClientException; |
[使用文档](aliyun-spring-boot/README-zh.md at master · alibaba/aliyun-spring-boot · GitHub)
相比与上面的Demo,这个Demo更加的简便,编写的代码比较少
项目说明
如果您的应用是 Spring Cloud 应用,且需要使用阿里云的 OSS 服务进行云端的文件存储,例如电商业务中常见的商品图片存储,那么您可以使用 OSS starter 完成 Spring Cloud 应用的对象存储。
阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。更多 OSS 相关的信息,请参考 OSS官网。
<!-- 阿里云的对象存储的依赖--> |
spring.cloud.alicloud.access-key=************ |
|
服务端签名直传 使用这种方式可以减轻本地服务器的压力
获取签名信息的接口
package com.atguigu.gulimall.thirdparty.controller; |
访问接口之后返回的数据格式
{ |
前端相关的代码
前端相关的代码可以借鉴一下谷粒商城的前端上传文件的组件,里面封装了多文件上传和单文件上传的功能
出现跨域的问题
解决方法
]]>Java工程师面试 宝典学习说明_互联网校招面试真题面经汇总_牛客网 (nowcoder.com)
Java | JavaGuide(Java面试 + 学习指南)
对比面向过程,是两种不同处理问题的角度,面向过程更注重事情的每一步骤及顺序,面向对象更注重事情有哪些参与者(对象),以及各自需要做什么。面向过程比较直接高效,面向对象易于复用、扩展和维护。
面向对象的三大基本特征:封装、继承、多态(父类应用指向子类对象)
JDK java 开发工具
JRE(Java Runtime Environment Java 运行环境)
JVM java虚拟机
JDK = JRE + 开发工具集(例如 Javac,java 编译工具等)
JRE = JVM + Java SE 标准类库(java 核心类库)
==:如果是基本数据类型,⽐较是值,如果是引⽤类型,⽐较的是引⽤地址
equals:具体看各个类重写equals⽅法之后的⽐较逻辑,⽐如String类,虽然是引⽤类型,但是String类中重写了equals⽅法,⽅法内部⽐较的是字符串中的各个字符是否全部相等。
1.String是不可变的,如果尝试去修改,会新⽣成⼀个字符串对象,StringBuffer和StringBuilder是可变的,修改是在原对象上操作的
2.StringBuffer是线程安全的,StringBuilder是线程不安全的,所以在单线程环境下StringBuilder效率会更⾼
性能: StringBuilder > StringBuffer > String
重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同、方法返回值和访问修饰符可以不同,发生在编译时。
重写:发生在父子类中,方法名,参数列表必须相同,返回值范围小于等于父类,抛出异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类访问修饰符为私有,子类不能重写该方法。
1.抽象类可以有实现的方法和抽象的方法
2.抽象类的成员变量可以是各种类型的,接口中的成员变量只能是public static final类型(默认)的
3.抽象类只能继承一个,接口可以实现多个
4.关键字不同,接口的关键字是interface,抽象类的关键字是abstract
List:有序,按对象进入的顺序保存对象,可重复,允许多个Null元素对象,可以使用iterator取出所有的元素,在逐一遍历,还可以使用get(int index)获取指定下标的元素。
Set:无序,不可重复,最多允许有一个Null元素对象,取元素时只能用iterator接口取得所有元素,再逐一遍历各个元素。
hashCode()的作用是获取哈希码,也称散列码,哈希码的作用是确定该对象在哈希表中的索引位置。
如果两个对象相等,那么它们的hashCode()值一定相同
如果两个对象hashCode()相等,它们并不一定相等
ArrayList:基于动态数组,连续内存存储,适合下标访问(随机访问)。扩容机制:因为数组长度固定,超出长度存数据时需要新建数组,将老数组的数据拷贝到新数组,然后插入需要加入到数组中的数据。
LinkedList:基于链表,可以存储在分散的内存中,适合做数据插入及删除操作,不适合查询。遍历LinkedList必须使用iterator不能使用for循环,因为for循环体内通过get(i)取得某一元素时都需要对list重新遍历,性能消耗大。
区别:
1.HashMap方法没有synchronized修饰,线程非安全,HashTable线程安全
2.HashMap允许key和value为null,而HashTable不允许
底层实现: 数组 + 链表
ConcurrentHashMap和HashTable都是线程安全的,但ConcurrentHashMap相比HashTable性能更高
1.配置文件配置包扫描路径
2.递归包扫描获取.class文件
3.反射、确定需要交给IOC管理的类
4.对需要注入的类进行依赖注入
什么是字节码?
Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器。编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转化为特定系统的机器码执行,在java中,这种供虚拟机理解的代码叫做字节码(即扩展名为.class的文件)。
采用字节码的好处是什么?
Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无需重新编译便可在多种计算机上运行。
JDK有三个类加载器:
自定义加载器的方法: 继承ClassLoader实现自定义加载器
双亲委派模型的执行流程是这样的:
1、当加载一个类时,会先从应用程序类加载器的缓存里查找相应的类,如果能找到就返回对象,如果找不到就执行下面流程;
2、在扩展加载器缓存中查找相应的类,如果能找到就返回对象,如果找不到就继续下面流程;
3、在启动类加载器中查询相应的类,如果找到就返回对象,如果找不到就继续下面流程;
4、在扩展加载器中查找并加载类,如果能找到就返回对象,并将对象加入到缓存中,如果找不到就继续下面流程;
5、在应用程序类加载器中查找并加载类,如果能找到就返回对象,并将对象加入到缓存中,如果找不到就返回 ClassNotFound 异常。
加载流程如下图所示:
一般“双亲”指的是“父亲”和“母亲”,而在这里“双亲”指的是类加载类先向上找,再向下找的流程就叫做双亲委派模型。
双亲委派模型的好处
线程通常有五种状态:创建、就绪、运行、阻塞、死亡状态
阻塞又分为三种情况:等待阻塞、同步阻塞、其他阻塞
sleep(),wait()的区别
yield()执行后线程直接进入就绪状态,马上释放了cpu,但是依然保留了cpu的执行资格,所有有可能cpu下次进行线程调度还会让这个线程取到执行权
join()执行后线程进入阻塞状态,例如在线程B中调用了A的join(),那线程B会进入到阻塞队列,直到线程A结束或者中断线程
线程安全讲的不是线程安全,应该是内存安全,堆是共享内存,可以被所有线程访问
线程安全的定义:当多个线程访问一个对象的时候,如果不用进行额外的同步控制或者其他的协调操作,调用这个对象的行为都可以获得正确的结果,我们就说这个线程数是安全的。
产生线程安全问题的原因: 在每个进程的内存空间中都会有一块特殊的公共区域,通常称为堆(内存)。进程内所有线程都可以访问到该区域,这就是造成问题的潜在原因。
守护线程:为非守护线程(用户线程)提供服务的线程,任何一个守护线程都是整个jvm中所有非守护线程的守护线程。
守护线程的作用:
举例:GC垃圾回收机制,就是一个经典的守护线程,当我们的程序不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就没事可做,所以当垃圾回收线程是jvm上仅剩的线程时,垃圾回收线程会自动离开,它始终再低级别的状态中运行,用于实时监控和管理系统中的可回收资源。
应用场景:
同一个线程中通过ThreadLocal存进去的数据,在任何位置取出来是一致的
原理
每一个Thread对象均含有一个ThreadLocalMap类型的成员变量threadLocals,它存储本线程中所有ThreadLocal对象及对应的值。
当执行set()方法时,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,获取对应的value。
get方法的执行过程类似。ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,获取对应的value。
由于每一条线程均含有各自私有的ThreadLocalMap容器,这些容器相互独立,互不影响,因此不会存在线程安全性问题,从而无需使用同步机制来保证多条线程访问容器的互斥性。
使用场景
内存泄漏:不会使用的对象或者变量占用的内存不能被回收,就是内存泄漏(内存泄漏为程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏的危害可以忽略,但内存泄漏堆积后果很严重,无论多少内存,迟早会被占光,导致OOM。)
强引用:使用最普遍的引用(通过new),一个对象具有强应用,不会被垃圾回收器回收,即使是内存不足。我们想要取消强引用,可以显示的将引用赋值为null,jvm在合适的时间就会回收该对象。
ThreadLocal内存泄漏的根源
由于ThreadLocalMap的生命周期和Thread一样长,如果没有手动删除对应key就会导致内存泄漏
怎么避免
串行在时间上不可能发生重叠,前一个任务没有搞定,下一个任务只能等
并行在时间上是重叠的,两个任务在同一时刻互不干扰的同时执行
并发允许两个任务彼此干扰,同一时间点,只有一个任务运行,交替执行
为什么
线程池参数
corePoolSize
代表核心线程数,也就是正常情况下创建工作的线程数,这些线程创建之后不会消除,而是一种常驻线程。maxnumPoolSize
代表最大线程数,它与核心线程数相对应,表示最大允许被创建的线程数,比如当前任务较多,将核心线程数都用完了,还无法满足要求时,此时就会创建新的线程,但是线程池总数不会超过最大线程数keepAliveTime
空闲线程存活时间,当一个可被回收的线程的空闲时间大于keepAliveTime,就会被回收; unit:keepAliveTime的时间单位workQueue
工作队列,存放待执行任务的队列:当提交的任务数超过核心线程数大小后,再提交的任务就存放在工作队列,任务调度时再从队列中取出任务。它仅仅用来存放被execute()方法提交的Runnable任务。工作队列实现了BlockingQueue接口。threadFactory
线程工厂,创建线程的工厂,可以设定线程名、线程编号等。handler
拒绝策略,当线程池线程数已满,并且工作队列达到限制,新提交的任务使用拒绝策略处理。可以自定义拒绝策略,拒绝策略需实现RejectedExecutionHandler接口。线程池中阻塞队列的作用?
1、一般的队列只能保证作为一个有限长度的缓冲区,如果超出了缓冲长度,就无法保留当前的任务了,阻塞队列通过阻塞可以保留住当前想要继续入队的任务。
2、阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放cpu资源。
3、阻塞队列自带阻塞和唤醒的功能,不需要额外处理,无任务执行时,线程池利用阻塞队列的take方法挂起,从而维持核心线程的存活,不至于一直占着cpu资源。
为什么是先添加队列而不是先创建最大线程?
在创建新线程的时候。是要获取全局锁的,这个时候其它就得阻塞,影响了整体效率。
就好比一个企业里面有10个(core)正式工的名额,最多招10个正式工,要是任务超过正式工人数,(task > core)的情况下,工厂领导(线程池)不是首先扩招工人,还是这10个人,但是任务会稍微的积压一下,即先放到队列去(代价低),10个正式工慢慢干,迟早会干完的,要是任务还在继续增加,超过正式工的加班忍耐极限了(队列满了),就得招外包帮忙了(注意是临时工),要是正式工加上外包还是不能完成任务,那新来的任务就会被领导拒绝了(线程池的拒绝策略)。
线程池将线程和任务解耦,线程是线程,任务是任务,摆脱了之前通过Thread创建线程时的一个线程必须对应一个任务的限制。
在线程池中,同一个线程可以从阻塞队列中不断获取新任务来执行,其核心原理在于线程池对Thread进行了封装,并不是每次执行都会调用Thread.start()来创建新线程,而是让每个线程去执行一个“循环任务”,在这个“循环任务”中不停检查是否有任务需要被执行,如果有则执行,也就是调用任务中的run方法,将run方法当成一个普通的方法执行,通过这种方式只使用固定的线程就将所有任务的run方法串联起来。
轻量级的开源的j2EE框架。他是一个容器框架,用来装javabean(java对象),中间层框架(万能胶)可以起一个连接作用,比如说把struts和hibernate粘合在一起运用,可以让我们的企业开发更快、更简洁
Spring是一个轻量级的控制反转(ioc)和面向切面(aop)的容器框架
系统是由许多不同的组件所组成的,每一个组件各负责一块特定的功能。除了实现自身的核心功能之外,这些组件还经常承担着额外的职责。例如日志、事务管理和安全这样的服务经常融入到自身具有核心业务逻辑的组件中去。这些系统服务经常被称为横切关注点,因为他们会跨域系统的多个组件。
当我们需要将分散的对象引入公共行为的时候,OOP则显得无能为力,也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能
AOP:将程序中的交叉业务逻辑(比如安全、日志、事务),封装成一个切面。然后注入到目标对象(具体业务逻辑)中去。AOP可以对某个对象或者某些功能进行增强,比如对象中的方法进行增强,可以在执行某个方法之前额外做一些事情,在某个方法执行之后额外做一些事情。
容器概念、控制反转、依赖注入三方面理解
容器概念
ioc容器:实际上就是个map(key、value),里面存的就是各种对象(在xml里配置bean节点、@Repository、@Service、@Controller),在项目启动的时候会读取配置文件路面的bean节点。
控制反转
在没有引入ioc容器之前,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。
引入ioc容器之后,对象A和对象B之间失去了直接联系。当对象A运行到需要对象B的时候,ioc容器会主动创建一个对象B注入到对象A需要的地方。
通过前后的对比,不难看出来:对象A获得依赖对象B的过程,由主动行为变成了被动行为,控制权颠倒过来了,这就是控制反转这个名称的由来。
依赖注入
“获得依赖对象的过程被反转了”。控制器反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入。依赖注入是实现IOC的方法,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。
ApplicationContext是BeanFactory的子接口
ApplicationContext提供了更完整的功能
1、继承了MessageSource,因此支持国际化
2、统一资源文件的访问方式
3、提供在监听器中注册bean的事件
4、同时加载多个配置文件
5、载入多个(有继承关系)上下文,使得每一个上下文都专注于一个特定的层次,比如应用的web层
深拷贝和浅拷贝就是指对象的拷贝,一个对象中存在两种类型的属性,一种是基本数据类型,一种是实列对象的引用
举例:A对象中有一个user属性,A1拷贝A对象,两个user指向同一个对象的话,就是浅拷贝,反之是深拷贝
TCP (Transfer Control Protocol)是一种面向连接的、可靠的、传输层通信协议
UDP (User Datagram Protocal) 是一种无连接、不可靠的、传输层通信协议
TCP为什么是三次握手,而不是两次?
如果是两次握手,可能会造成连接资源浪费的问题
MarkSweep标记清除算法
Copying拷贝算法
MarkCompack标记压缩算法
1.创建前准备阶段
这个阶段主要是在开始Bean加载之前,从Spring上下文和相关配置中解析并查找Bean有关的配置内容,比如init-method
-容器在初始化bean时调用的方法、destory-method
,容器在销毁Bean时调用的方法。以及,BeanFactoryPostProcessor这类的bean加载过程中的前置和后置处理。这些类或者配置其实是Spring提供给开发者,用来实现Bean加载过程中的扩展机制,在很多和Spring集成的中间件经常使用,比如Dubbo。
2.创建实例阶段
这个阶段主要是通过反射来创建Bean的实例对象,并且扫描和解析Bean声明的一些属性。
3.依赖注入阶段
在这个阶段,会检测被实例化的Bean是否存在其他依赖,如果存在其他依赖,就需要对这些被依赖Bean进行注入。比如通过@Autowired
、@Setter等依赖注入的配置。在这个阶段还会触发一些扩展的调用,比如常见的扩展类:BeanPostProcessors(用来实现Bean初始化前后的回调)、InitializingBean类(这个类有一个afterPropertiesSet()方法,给属性赋值)、还有BeanFactoryAware等等。
4.容器缓存阶段
容器缓存阶段主要是把Bean保存到IoC容器中缓存起来,到了这个阶段,Bean就可以被开发者使用了。这个阶段涉及到的操作,常见的有init-method
这个属性配置的方法,会在这个阶段调用。比如BeanPostProcessors方法中的后置处理器方法postProcessAfterInitialization,也是在这个阶段触发的。
5.销毁实例阶段
这个阶段,是完成Spring应用上下文关闭时,将销毁Spring上下文中所有的Bean。如果Bean实现了DisposableBean接口,或者配置了destory-method
属 性,将会在这个阶段被调用。
实例化 -> 属性赋值 -> 初始化 -> 销毁
不是线程安全的!
Sping中Bean默认是单例模式的,框架中并没有对Bean进行多线程的封装处理。
线程安全这个问题,要从单例与原型Bean分别进行说明。
「原型Bean」对于原型Bean,每次创建一个新对象,也就是线程之间并不存在Bean共享,自然是不会有线程安全的问题。
「单例Bean」对于单例Bean,所有线程都共享一个单例实例Bean,因此是存在资源的竞争。
如果单例Bean,是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行「查询」以外的操作,那么这个单例Bean是线程安全的。比如Spring mvc 的 Controller、Service、Dao等,这些Bean大多是无状态的,只关注于方法本身。
Spring 的 bean 作用域(scope)类型有5种:
1、singleton:单例,默认作用域。
2、prototype:原型,每次创建一个新对象。
3、request:请求,每次Http请求创建一个新对象,适用于WebApplicationContext环境下。
4、session:会话,同一个会话共享一个实例,不同会话使用不用的实例。
5、global-session:全局会话,所有会话共享一个实例。
实现方式
在使用Spring框架时,可以有两种使用事务的方式,一种是编程式,一种是申明式的,@Transactional注解就是申明式的。
首先,事务这个概念是数据库层面的,Spring只是基于数据库中的事务进行了扩展,以及提供了一些能让程序员更加方便操作事务的方式。比如我们可以通过在某个方法上增加@Transactional注解,就可以开启事务,这个方法中所有的sql都会在一个事务中执行,统一成功失败。
原理
在一个方法上加了@Transactional注解后,Spring会基于这个类生成一个代理对象,会将这个代理对象作为bean,当在使用这个代理对象的方法时,如果这个方法存在@Transactional注解,那么代理逻辑会先把事务的自动提价设置为false,然后再去执行原本的业务逻辑方法,如果执行业务逻辑方法没有异常,那么代理逻辑中就会将事务进行提交,如果执行业务逻辑方法出现了异常,那么则会将事务进行回滚。当然,针对哪些异常回滚事务是可以配置的,可以利用@Transactional注解中的rollbackFor属性进行配置,默认情况下会对RuntimeException和Error进行回滚。
隔离级别
spring的事务隔离级别就是数据库的隔离级别外加一个默认级别
注:数据库设置的隔离级别会被spring的配置覆盖
多个事务方法相互调用的时,事务是如何在这些方法间传播
方法A是一个事务的方法,方法A执行过程中调用了方法B,那么方法B有无事务以及方法B对事务的要求不同都会对方法A的事务具体执行造成影响,同时方法A的事务对方法B的事务执行也有影响,这种影响具体是什么就由两个方法所定义的事务传播类型所决定。
REQUIRED(Spring默认的事务传播类型)如果当前没有事务,则自己创建一个事务,如果当前存在事务,则加入这个事务
SUPPORTS:当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行
MANDATORY:当前存在事务,则加入当前事务,如果当前的事务不存在,则抛出异常
REQUIRES_NEW:创建一个新事务,如果存在当前事务,则挂起该事务
NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,则挂起当前事务
NEVER:不使用事务,如果当前事务存在,则抛出异常
NESTED:如果当前事务存在,则在嵌套事务中执行,否则REQUIRED的操作一样(开启一个事务)
Spring事务原理是AOP,进行了切面增强,那么失效的根本原因是这个AOP不起作用了,常见情况有如下几种
autowire属性有五种装配的方式
<!--手动装配:以value或ref的方式明确指定属性值都是手动装配。 需要通过‘ref’属性来连接bean--> |
<!--Cutomer的属性名称是person,Spring会将bean id为person的bean通过setter方法进行自动装配--> |
<!--Cutomer的属性person的类型为Person,Spirng会将Person类型通过setter方法进行自动装配--> |
<!--Cutomer构造函数的参数person的类型为Person,Spirng会将Person类型通过构造方法进行自动装配--> |
<!--如果有默认的构造器,则通过constructor方式进行自动装配,否则使用byType方式进行自动装配--> |
Spring是一个IOC容器,用来管理Bean,使用依赖注入实现控制反转,可以很方便的整合各种框架,提供AOP机制弥补OOP的代码重复问题,更方便将不同方法中的共同处理抽取成切面,自动注入给方法执行,比如日志、异常等
SpringMvc是spring对web框架的一个解决方案,提供了一个总的前端控制器Servlet,用来接受请求,然后定义了一套路由策略(url到handle的映射)及适配执行handle,将handle结果使用视图解析技术生成视图展现给前端
SpringBoot是Spring提供的一个快速开发工具包,让程序员更方便、更快速的开发spring + springmvc应用,简化了配置(约定了默认配置),整合了一系列的解决方案(starter机制),redis、mongodb、es可以开箱即用
Handler是用来干活的工具;HandlerMapping用于根据需要干的活找到相应的工具;HandlerAdapter是使用工具干活的人
HandlerMapping是用来查找Handler的。在SpringMVC中会有很多请求,每个请求都需要一个Handler处理,具体接收到一个请求之后使用哪个Handler进行处理呢?这就是HandlerMapping需要做的事。
HandlerAdapter,从名字上看,它就是一个适配器。因为SpringMVC中的Handler可以是任意的形式,只要能处理请求就ok,但是Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方法。如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的事情。
HandlerExceptionResolver其它组件都是用来干活的。在干活的过程中难免会出现问题,出问题后怎么办呢?这就需要有一个专门的角色对异常情况进行处理,在SpringMVC中就是HandlerExceptionResolver。具体来说,此组件的作用是根据异常设置ModelAndView,之后再交给render方法进行渲染。
ViewResolverViewResolver用来将String类型的视图名和Locale解析为View类型的视图。View是用来渲染页面的,也就是将程序返回的参数填入模板里,生成html(也可能是其它类型)文件。这里就有两个关键问题:使用哪个模板?用什么技术(规则)填入参数?这其实是ViewResolver主要要做的工作,ViewResolver需要找到渲染所用的模板和所用的技术(也就是视图的类型)进行渲染,具体的渲染过程则交由不同的视图自己完成。
RequestToViewNameTranslatorViewName是根据ViewName查找View,但有的Handler处理完后并没有设置View也没有设置ViewName,这时就需要从request获取ViewName了,如何从request中获取ViewName就是RequestToViewNameTranslator要做的事情了。RequestToViewNameTranslator在Spring MVC容器里只可以配置一个,所以所有request到ViewName的转换规则都要在一个Translator里面全部实现。
LocaleResolver解析视图需要两个参数:一是视图名,另一个是Locale。视图名是处理器返回的,Locale是从哪里来的?这就是LocaleResolver要做的事情。LocaleResolver用于从request解析出Locale,Locale就是zh-cn之类,表示一个区域,有了这个就可以对不同区域的用户显示不同的结果。SpringMVC主要有两个地方用到了Locale:一是ViewResolver视图解析的时候;二是用到国际化资源或者主题的时候。
ThemeResolver用于解析主题。SpringMVC中一个主题对应一个properties文件,里面存放着跟当前主题相关的所有资源、如图片、css样式等。SpringMVC的主题也支持国际化,同一个主题不同区域也可以显示不同的风格。SpringMVC中跟主题相关的类有 ThemeResolver、ThemeSource和Theme。主题是通过一系列资源来具体体现的,要得到一个主题的资源,首先要得到资源的名称,这是ThemeResolver的工作。然后通过主题名称找到对应的主题(可以理解为一个配置)文件,这是ThemeSource的工作。最后从主题中获取资源就可以了。
MultipartResolver用于处理上传请求。处理方法是将普通的request包装成MultipartHttpServletRequest,后者可以直接调用getFile方法获取File,如果上传多个文件,还可以调用getFileMap得到FileName->File结构的Map。此组件中一共有三个方法,作用分别是判断是不是上传请求,将request包装成MultipartHttpServletRequest、处理完后清理上传过程中产生的临时资源。
FlashMapManager用来管理FlashMap的,FlashMap主要用在redirect中传递参数。
自动装配,简单来说就是自动把第三方组件的Bean装载到Spring IOC器里面,不需要开发人员再去写Bean的装配配置。
在Spring Boot应用里面,只需要在启动类加上@SpringBootApplication注解就可以实现自动装配。@SpringBootApplication是一个复合注解,真正实现自动装配的注解是@EnableAutoConfiguration。
优点:
缺点:
1.
SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求
2. SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库
整个自动装配的过程是:Spring Boot通过@EnableAutoConfiguration注解开启自动配置,加载spring.factories中注册的各AutoConfiguration类,当某个AutoConfiguration类满足其注解@Conditional指定的生效条件(Starters提供的依赖、配置或Spring容器中是否存在某个Bean等)时,实例化该AutoConfiguration类中定义的Bean(组件等),并注入Spring容器,就可以完成依赖框架的自动配置。
原理:把无序的数据变成有序的查询
读未提交(Read Uncommitted)可能读到其他事务未提交的数据,也叫做脏读
读已提交(Read Committed)两次读取结果不一致,叫做不可重复读
可重复读(Repeatable Read)是mysql默认的隔离级别,每次读取的结果都一样,当时可能产生幻读
串行化(Serializable)一般是不会使用的,他给每一行读取的数据加锁,会导致大量超时和锁竞争的问题
Mysql 默认的事务隔离级别是可重复读(Repeatable Read)
事务的基本特性ACID分别是:
SQL查询慢的原因
根据上面的原因给出优化的措施
A 原子性由undo log日志保证,它记录了需要回滚的日志信息,事务回滚时撤销已经执行成功的sql
C 一致性由其他的三大特性保证 程序代码要保证业务上的一致性
I 隔离性由MVCC来保证
D 持久性由内存 + redo log 来保证。Mysql修改数据同时在内存和redo log记录这次操作,宕机的时候可以从redo log恢复
redis是key-value数据库,我们可以设置redis中缓存的key的过期时间。redis的过期策略就是指当redis中缓存的key过期了,redis该如何处理。
在Redis中同时使用了这两种策略
IO多路复用机制监听多个Socket
单线程快的原因:
缓存雪崩 缓存在同一时间大面积失效,所以,后面的请求都会落在数据库上,造成数据库短时间内承受大量请求而崩掉。
解决方案:
缓存穿透 缓存和数据库中都没有数据,导致所有的请求都落在数据库上,造成数据库短时间内承受大量请求
解决方案:
缓存击穿 缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没有读到数据,又同时去数据库取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案:
Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(无序集合)及zset(有序集合)
坑点:有时候遇到验证码发不出去的情况,要调整依赖的版本,更新为高版本的依赖
<!--javaMail--> |
//读取配置文件中的值 |
|
import java.util.Random; |
1.设置发送邮件的账号和授权码信息 |
阿里云的短信服务不面向个人用户,无法使用,但是使用的基本逻辑和使用邮箱类似
package com.atguigu.commonutils; |
java八股文面试全套真题+深度详解(含大厂高频面试真题) https://www.bilibili.com/video/BV1yT411H7YK/?share_source=copy_web&vd_source=aee5e475191b69e6c781059ab6662584
面试官:什么是缓存穿透 ? 怎么解决 ?
候选人:
嗯~~,我想一下
缓存穿透是指查询一个一定不存在的数据,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到 DB 去查询,可能导致 DB 挂掉。这种情况大概率是遭到了攻击。
解决方案的话,我们通常都会用布隆过滤器来解决它
面试官:好的,你能介绍一下布隆过滤器吗?
候选人:
嗯,是这样~
布隆过滤器主要是用于检索一个元素是否在一个集合中。我们当时使用的是redisson实现的布隆过滤器。
它的底层主要是先去初始化一个比较大数组,里面存放的二进制0或1。在一开始都是0,当一个key来了之后经过3次hash计算,模于数组长度找到数据的下标然后把数组中原来的0改为1,这样的话,三个数组的位置就能标明一个key的存在。查找的过程也是一样的。
当然是有缺点的,布隆过滤器有可能会产生一定的误判,我们一般可以设置这个误判率,大概不会超过5%,其实这个误判是必然存在的,要不就得增加数组的长度,其实已经算是很划分了,5%以内的误判率一般的项目也能接受,不至于高并发下压倒数据库。
面试官:什么是缓存击穿 ? 怎么解决 ?
候选人:
嗯!!
缓存击穿的意思是对于设置了过期时间的key,缓存在某个时间点过期的时候,恰好这时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把 DB 压垮。
解决方案有两种方式:
第一可以使用互斥锁:当缓存失效时,不立即去load db,先使用如 Redis 的 setnx 去设置一个互斥锁,当操作成功返回时再进行 load db的操作并回设缓存,否则重试get缓存的方法
第二种方案可以设置当前key逻辑过期,大概是思路如下:
①:在设置key的时候,设置一个过期时间字段一块存入缓存中,不给当前key设置过期时间
②:当查询的时候,从redis取出数据后判断时间是否过期
③:如果过期则开通另外一个线程进行数据同步,当前线程正常返回数据,这个数据不是最新
当然两种方案各有利弊:===============
如果选择数据的强一致性,建议使用分布式锁的方案,性能上可能没那么高,锁需要等,也有可能产生死锁的问题
如果选择key的逻辑删除,则优先考虑的高可用性,性能比较高,但是数据同步这块做不到强一致。
面试官:什么是缓存雪崩 ? 怎么解决 ?
候选人:
嗯!!
缓存雪崩意思是设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB 瞬时压力过重雪崩。与缓存击穿的区别:雪崩是很多key,击穿是某一个key缓存。
解决方案主要是可以将缓存失效时间分散开,比如可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
面试官:redis做为缓存,mysql的数据如何与redis进行同步呢?(双写一致性)
候选人:嗯!就说我最近做的这个项目,里面有xxxx(根据自己的简历上写)的功能,需要让数据库与redis高度保持一致,因为要求时效性比较高,我们当时采用的读写锁保证的强一致性。
我们采用的是redisson实现的读写锁,在读的时候添加共享锁,可以保证读读不互斥,读写互斥。当我们更新数据的时候,添加排他锁,它是读写,读读都互斥,这样就能保证在写数据的同时是不会让其他线程读数据的,避免了脏数据。这里面需要注意的是读方法和写方法上需要使用同一把锁才行。
面试官:那这个排他锁是如何保证读写、读读互斥的呢?
候选人:其实排他锁底层使用也是setnx,保证了同时只能有一个线程操作锁住的方法
面试官:你听说过延时双删吗?为什么不用它呢?
候选人:延迟双删,如果是写操作,我们先把缓存中的数据删除,然后更新数据库,最后再延时删除缓存中的数据,其中这个延时多久不太好确定,在延时的过程中可能会出现脏数据,并不能保证强一致性,所以没有采用它。
面试官:redis做为缓存,mysql的数据如何与redis进行同步呢?(双写一致性)
候选人:嗯!就说我最近做的这个项目,里面有xxxx(根据自己的简历上写)的功能,数据同步可以有一定的延时(符合大部分业务)
我们当时采用的阿里的canal组件实现数据同步:不需要更改业务代码,部署一个canal服务。canal服务把自己伪装成mysql的一个从节点,当mysql数据更新以后,canal会读取binlog数据,然后在通过canal的客户端获取到数据,更新缓存即可。
面试官:redis做为缓存,数据的持久化是怎么做的?
候选人:在Redis中提供了两种数据持久化的方式:1、RDB 2、AOF
面试官:这两种持久化方式有什么区别呢?
候选人:RDB是一个快照文件,它是把redis内存存储的数据写到磁盘上,当redis实例宕机恢复数据的时候,方便从RDB的快照文件中恢复数据。
AOF的含义是追加文件,当redis操作写命令的时候,都会存储这个文件中,当redis实例宕机恢复数据的时候,会从这个文件中再次执行一遍命令来恢复数据
面试官:这两种方式,哪种恢复的比较快呢?
候选人:RDB因为是二进制文件,在保存的时候体积也是比较小的,它恢复的比较快,但是它有可能会丢数据,我们通常在项目中也会使用AOF来恢复数据,虽然AOF恢复的速度慢一些,但是它丢数据的风险要小很多,在AOF文件中可以设置刷盘策略,我们当时设置的就是每秒批量写入一次命令
面试官:Redis的数据过期策略有哪些 ?
候选人:
嗯~,在redis中提供了两种数据过期删除策略
第一种是惰性删除,在设置该key过期时间后,我们不去管它,当需要该key时,我们在检查其是否过期,如果过期,我们就删掉它,反之返回该key。
第二种是 定期删除,就是说每隔一段时间,我们就对一些key进行检查,删除里面过期的key
定期清理的两种模式:
- SLOW模式是定时任务,执行频率默认为10hz,每次不超过25ms,以通过修改配置文件redis.conf 的 hz 选项来调整这个次数
- FAST模式执行频率不固定,每次事件循环会尝试执行,但两次间隔不低于2ms,每次耗时不超过1ms
Redis的过期删除策略:惰性删除 + 定期删除两种策略进行配合使用。
面试官:Redis的数据淘汰策略有哪些 ?
候选人:
嗯,这个在redis中提供了很多种,默认是noeviction,不删除任何数据,内部不足直接报错
是可以在redis的配置文件中进行设置的,里面有两个非常重要的概念,一个是LRU,另外一个是LFU
LRU的意思就是最少最近使用,用当前时间减去最后一次访问时间,这个值越大则淘汰优先级越高。
LFU的意思是最少频率使用。会统计每个key的访问频率,值越小淘汰优先级越高
我们在项目设置的allkeys-lru,挑选最近最少使用的数据淘汰,把一些经常访问的key留在redis中
面试官:数据库有1000万数据 ,Redis只能缓存20w数据, 如何保证Redis中的数据都是热点数据 ?
候选人:
嗯,我想一下~~
可以使用 allkeys-lru (挑选最近最少使用的数据淘汰)淘汰策略,那留下来的都是经常访问的热点数据
面试官:Redis的内存用完了会发生什么?
候选人:
嗯~,这个要看redis的数据淘汰策略是什么,如果是默认的配置,redis内存用完以后则直接报错。我们当时设置的 allkeys-lru 策略。把最近最常访问的数据留在缓存中。
面试官:Redis分布式锁如何实现 ?
候选人:嗯,在redis中提供了一个命令setnx(SET if not exists)
由于redis的单线程的,用了命令之后,只能有一个客户端对某一个key设置值,在没有过期或删除key的时候是其他客户端是不能设置这个key的
面试官:好的,那你如何控制Redis实现分布式锁有效时长呢?
候选人:嗯,的确,redis的setnx指令不好控制这个问题,我们当时采用的redis的一个框架redisson实现的。
在redisson中需要手动加锁,并且可以控制锁的失效时间和等待时间,当锁住的一个业务还没有执行完成的时候,在redisson中引入了一个看门狗机制,就是说每隔一段时间就检查当前业务是否还持有锁,如果持有就增加加锁的持有时间,当业务执行完成之后需要使用释放锁就可以了
还有一个好处就是,在高并发下,一个业务有可能会执行很快,先客户1持有锁的时候,客户2来了以后并不会马上拒绝,它会自旋不断尝试获取锁,如果客户1释放之后,客户2就可以马上持有锁,性能也得到了提升。
面试官:好的,redisson实现的分布式锁是可重入的吗?
候选人:嗯,是可以重入的。这样做是为了避免死锁的产生。这个重入其实在内部就是判断是否是当前线程持有的锁,如果是当前线程持有的锁就会计数,如果释放锁就会在计算上减一。在存储数据的时候采用的hash结构,大key可以按照自己的业务进行定制,其中小key是当前线程的唯一标识,value是当前线程重入的次数
面试官:redisson实现的分布式锁能解决主从一致性的问题吗
候选人:这个是不能的,比如,当线程1加锁成功后,master节点数据会异步复制到slave节点,此时当前持有Redis锁的master节点宕机,slave节点被提升为新的master节点,假如现在来了一个线程2,再次加锁,会在新的master节点上加锁成功,这个时候就会出现两个节点同时持有一把锁的问题。
我们可以利用redisson提供的红锁来解决这个问题,它的主要作用是,不能只在一个redis实例上创建锁,应该是在多个redis实例上创建锁,并且要求在大多数redis节点上都成功创建锁,红锁中要求是redis的节点数量要过半。这样就能避免线程1加锁成功后master节点宕机导致线程2成功加锁到新的master节点上的问题了。
但是,如果使用了红锁,因为需要同时在多个节点上都添加锁,性能就变的很低了,并且运维维护成本也非常高,所以,我们一般在项目中也不会直接使用红锁,并且官方也暂时废弃了这个红锁
面试官:好的,如果业务非要保证数据的强一致性,这个该怎么解决呢?
候选人:嗯~,redis本身就是支持高可用的,做到强一致性,就非常影响性能,所以,如果有强一致性要求高的业务,建议使用zookeeper实现的分布式锁,它是可以保证强一致性的。
面试官:Redis集群有哪些方案, 知道嘛 ?
候选人:嗯~~,在Redis中提供的集群方案总共有三种:主从复制、哨兵模式、Redis分片集群
面试官:那你来介绍一下主从同步
候选人:嗯,是这样的,单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,可以搭建主从集群,实现读写分离。一般都是一主多从,主节点负责写数据,从节点负责读数据,主节点写入数据之后,需要把数据同步到从节点中
面试官:能说一下,主从同步数据的流程
候选人:嗯~~,好!主从同步分为了两个阶段,一个是全量同步,一个是增量同步
全量同步是指从节点第一次与主节点建立连接的时候使用全量同步,流程是这样的:
第一:从节点请求主节点同步数据,其中从节点会携带自己的replication id和offset偏移量。
第二:主节点判断是否是第一次请求,主要判断的依据就是,主节点与从节点是否是同一个replication id,如果不是,就说明是第一次同步,那主节点就会把自己的replication id和offset发送给从节点,让从节点与主节点的信息保持一致。
第三:在同时主节点会执行bgsave,生成rdb文件后,发送给从节点去执行,从节点先把自己的数据清空,然后执行主节点发送过来的rdb文件,这样就保持了一致
当然,如果在rdb生成执行期间,依然有请求到了主节点,而主节点会以命令的方式记录到缓冲区,缓冲区是一个日志文件,最后把这个日志文件发送给从节点,这样就能保证主节点与从节点完全一致了,后期再同步数据的时候,都是依赖于这个日志文件,这个就是全量同步
增量同步指的是,当从节点服务重启之后,数据就不一致了,所以这个时候,从节点会请求主节点同步数据,主节点还是判断不是第一次请求,不是第一次就获取从节点的offset值,然后主节点从命令日志中获取offset值之后的数据,发送给从节点进行数据同步
面试官:怎么保证Redis的高并发高可用
候选人:首先可以搭建主从集群,再加上使用redis中的哨兵模式,哨兵模式可以实现主从集群的自动故障恢复,里面就包含了对主从服务的监控、自动故障恢复、通知;如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主;同时Sentinel也充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端,所以一般项目都会采用哨兵的模式来保证redis的高并发高可用
面试官:你们使用redis是单点还是集群,哪种集群
候选人:嗯!,我们当时使用的是主从(1主1从)加哨兵。一般单节点不超过10G内存,如果Redis内存不足则可以给不同服务分配独立的Redis主从节点。尽量不做分片集群。因为集群维护起来比较麻烦,并且集群之间的心跳检测和数据通信会消耗大量的网络带宽,也没有办法使用lua脚本和事务
面试官:redis集群脑裂,该怎么解决呢?
候选人:嗯! 这个在项目很少见,不过脑裂的问题是这样的,我们现在用的是redis的哨兵模式集群的
有的时候由于网络等原因可能会出现脑裂的情况,就是说,由于redis master节点和redis salve节点和sentinel处于不同的网络分区,使得sentinel没有能够心跳感知到master,所以通过选举的方式提升了一个salve为master,这样就存在了两个master,就像大脑分裂了一样,这样会导致客户端还在old master那里写入数据,新节点无法同步数据,当网络恢复后,sentinel会将old master降为salve,这时再从新master同步数据,这会导致old master中的大量数据丢失。
关于解决的话,我记得在redis的配置中可以设置:第一可以设置最少的salve节点个数,比如设置至少要有一个从节点才能同步数据,第二个可以设置主从数据复制和同步的延迟时间,达不到要求就拒绝请求,就可以避免大量的数据丢失
面试官:redis的分片集群有什么作用
候选人:分片集群主要解决的是,海量数据存储的问题,集群中有多个master,每个master保存不同数据,并且还可以给每个master设置多个slave节点,就可以继续增大集群的高并发能力。同时每个master之间通过ping监测彼此健康状态,就类似于哨兵模式了。当客户端请求可以访问集群任意节点,最终都会被转发到正确节点
面试官:Redis分片集群中数据是怎么存储和读取的?
候选人:
嗯~,在redis集群中是这样的
Redis 集群引入了哈希槽的概念,有 16384 个哈希槽,集群中每个主节点绑定了一定范围的哈希槽范围, key通过 CRC16 校验后对 16384 取模来决定放置哪个槽,通过槽找到对应的节点进行存储。
取值的逻辑是一样的
面试官:Redis是单线程的,但是为什么还那么快?
候选人:
嗯,这个有几个原因吧~~~
1、完全基于内存的,C语言编写
2、采用单线程,避免不必要的上下文切换可竞争条件
3、使用多路I/O复用模型,非阻塞IO
例如:bgsave 和 bgrewriteaof 都是在后台执行操作,不影响主线程的正常使用,不会产生阻塞
面试官:能解释一下I/O多路复用模型?
候选人:嗯~~,I/O多路复用是指利用单个线程来同时监听多个Socket ,并在某个Socket可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源。目前的I/O多路复用都是采用的epoll模式实现,它会在通知用户进程Socket就绪的同时,把已就绪的Socket写入用户空间,不需要挨个遍历Socket来判断是否就绪,提升了性能。
其中Redis的网络模型就是使用I/O多路复用结合事件的处理器来应对多个Socket请求,比如,提供了连接应答处理器、命令回复处理器,命令请求处理器;
在Redis6.0之后,为了提升更好的性能,在命令回复处理器使用了多线程来处理回复事件,在命令请求处理器中,将命令的转换使用了多线程,增加命令转换速度,在命令执行的时候,依然是单线程
面试官:MySQL中,如何定位慢查询?
候选人:
嗯~,我们当时做压测的时候有的接口非常的慢,接口的响应时间超过了2秒以上,因为我们当时的系统部署了运维的监控系统Skywalking ,在展示的报表中可以看到是哪一个接口比较慢,并且可以分析这个接口哪部分比较慢,这里可以看到SQL的具体的执行时间,所以可以定位是哪个sql出了问题
如果,项目中没有这种运维的监控系统,其实在MySQL中也提供了慢日志查询的功能,可以在MySQL的系统配置文件中开启这个慢日志的功能,并且也可以设置SQL执行超过多少时间来记录到一个日志文件中,我记得上一个项目配置的是2秒,只要SQL执行的时间超过了2秒就会记录到日志文件中,我们就可以在日志文件找到执行比较慢的SQL了。
面试官:那这个SQL语句执行很慢, 如何分析呢?
候选人:如果一条sql执行很慢的话,我们通常会使用mysql自动的执行计划explain来去查看这条sql的执行情况,比如在这里面可以通过key和key_len检查是否命中了索引,如果本身已经添加了索引,也可以判断索引是否有失效的情况,第二个,可以通过type字段查看sql是否有进一步的优化空间,是否存在全索引扫描或全盘扫描,第三个可以通过extra建议来判断,是否出现了回表的情况,如果出现了,可以尝试添加索引或修改返回字段来修复
面试官:了解过索引吗?(什么是索引)
候选人:嗯,索引在项目中还是比较常见的,它是帮助MySQL高效获取数据的数据结构,主要是用来提高数据检索的效率,降低数据库的IO成本,同时通过索引列对数据进行排序,降低数据排序的成本,也能降低了CPU的消耗
面试官:索引的底层数据结构了解过嘛 ?
候选人:MySQL的默认的存储引擎InnoDB采用的B+树的数据结构来存储索引,选择B+树的主要的原因是:第一阶数更多,路径更短,第二个磁盘读写代价B+树更低,非叶子节点只存储指针,叶子阶段存储数据,第三是B+树便于扫库和区间查询,叶子节点是一个双向链表
面试官:B树和B+树的区别是什么呢?
候选人:第一:在B树中,非叶子节点和叶子节点都会存放数据,而B+树的所有的数据都会出现在叶子节点,在查询的时候,B+树查找效率更加稳定
第二:在进行范围查询的时候,B+树效率更高,因为B+树都在叶子节点存储,并且叶子节点是一个双向链表
面试官:什么是聚簇索引什么是非聚簇索引 ?
候选人:
好的~,聚簇索引主要是指数据与索引放到一块,B+树的叶子节点保存了整行数据,有且只有一个,一般情况下主键在作为聚簇索引的
非聚簇索引值的是数据与索引分开存储,B+树的叶子节点保存对应的主键,可以有多个,一般我们自己定义的索引都是非聚簇索引
面试官:知道什么是回表查询嘛 ?
候选人:嗯,其实跟刚才介绍的聚簇索引和非聚簇索引是有关系的,回表的意思就是通过二级索引找到对应的主键值,然后再通过主键值找到聚集索引中所对应的整行数据,这个过程就是回表
【备注:如果面试官直接问回表,则需要先介绍聚簇索引和非聚簇索引】
面试官:知道什么叫覆盖索引嘛 ?
候选人:嗯~,清楚的
覆盖索引是指select查询语句使用了索引,在返回的列,必须在索引中全部能够找到,如果我们使用id查询,它会直接走聚集索引查询,一次索引扫描,直接返回数据,性能高。
如果按照二级索引查询数据的时候,返回的列中没有创建索引,有可能会触发回表查询,尽量避免使用select *,尽量在返回的列中都包含添加索引的字段
面试官:MYSQL超大分页怎么处理 ?
候选人:嗯,超大分页一般都是在数据量比较大时,我们使用了limit分页查询,并且需要对数据进行排序,这个时候效率就很低,我们可以采用覆盖索引和子查询来解决
先分页查询数据的id字段,确定了id之后,再用子查询来过滤,只查询这个id列表中的数据就可以了
因为查询id的时候,走的覆盖索引,所以效率可以提升很多
面试官:索引创建原则有哪些?
候选人:嗯,这个情况有很多,不过都有一个大前提,就是表中的数据要超过10万以上,我们才会创建索引,并且添加索引的字段是查询比较频繁的字段,一般也是像作为查询条件,排序字段或分组的字段这些。
还有就是,我们通常创建索引的时候都是使用复合索引来创建,一条sql的返回值,尽量使用覆盖索引,如果字段的区分度不高的话,我们也会把它放在组合索引后面的字段。
如果某一个字段的内容较长,我们会考虑使用前缀索引来使用,当然并不是所有的字段都要添加索引,这个索引的数量也要控制,因为添加索引也会导致新增改的速度变慢。
面试官:什么情况下索引会失效 ?
候选人:嗯,这个情况比较多,我说一些自己的经验,以前遇到过的
比如,索引在使用的时候没有遵循最左匹配法则,第二个是,模糊查询,如果%号在前面也会导致索引失效。如果在添加索引的字段上进行了运算操作或者类型转换也都会导致索引失效。
我们之前还遇到过一个就是,如果使用了复合索引,中间使用了范围查询,右边的条件索引也会失效
查询的时候发生了类型转换,在查询的时候做了运算的操作和模糊查询也会导致索引失效
所以,通常情况下,想要判断出这条sql是否有索引失效的情况,可以使用explain执行计划来分析
面试官:sql的优化的经验
候选人:嗯,这个在项目还是挺常见的,当然如果直说sql优化的话,我们会从这几方面考虑,比如
建表的时候、使用索引、sql语句的编写、主从复制,读写分离,还有一个是如果量比较大的话,可以考虑分库分表
直接经验:
①SELECT语句务必指明字段名称(避免直接使用select * )
②SQL语句要避免造成索引失效的写法
③尽量用union all代替union union会多一次过滤,效率低
④避免在where子句中对字段进行表达式操作
⑤Join优化 能用innerjoin 就不用left join right join,如必须使用 一定要以小表为驱动,内连接会对两个表进行优化,优先把小表放到外边,把大表放到里边。left join 或 right join,不会重新调整顺序
面试官:创建表的时候,你们是如何优化的呢?
候选人:这个我们主要参考的阿里出的那个开发手册《嵩山版》,就比如,在定义字段的时候需要结合字段的内容来选择合适的类型,如果是数值的话,像tinyint、int 、bigint这些类型,要根据实际情况选择。如果是字符串类型,也是结合存储的内容来选择char和varchar或者text类型
面试官:那在使用索引的时候,是如何优化呢?
候选人:【参考索引创建原则 进行描述】
1). 针对于数据量较大,且查询比较频繁的表建立索引。
2). 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引。
3). 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。
4). 如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。
5). 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率。
6). 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率。
7). 如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询。
面试官:你平时对sql语句做了哪些优化呢?
候选人:嗯,这个也有很多,比如SELECT语句务必指明字段名称,不要直接使用select * ,还有就是要注意SQL语句避免造成索引失效的写法;如果是聚合查询,尽量用union all代替union ,union会多一次过滤,效率比较低;如果是表关联的话,尽量使用innerjoin ,不要使用用left join right join,如必须使用 一定要以小表为驱动
面试官:事务的特性是什么?可以详细说一下吗?
候选人:嗯,这个比较清楚,ACID,分别指的是:原子性、一致性、隔离性、持久性;我举个例子:
A向B转账500,转账成功,A扣除500元,B增加500元,原子操作体现在要么都成功,要么都失败
在转账的过程中,数据要一致,A扣除了500,B必须增加500
在转账的过程中,隔离性体现在A像B转账,不能受其他事务干扰
在转账的过程中,持久性体现在事务提交后,要把数据持久化(可以说是落盘操作)
ACID介绍:
原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态。
隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。
面试官:并发事务带来哪些问题?
候选人:
我们在项目开发中,多个事务并发进行是经常发生的,并发也是必然的,有可能导致一些问题
第一是脏读, 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
第二是不可重复读:比如在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
第三是幻读(Phantom read):幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
面试官:怎么解决这些问题呢?MySQL的默认隔离级别是?
候选人:解决方案是对事务进行隔离
MySQL支持四种隔离级别,分别有:
第一个是,未提交读(read uncommitted)它解决不了刚才提出的所有问题,一般项目中也不用这个。第二个是读已提交(read committed)它能解决脏读的问题的,但是解决不了不可重复读和幻读。第三个是可重复读(repeatable read)它能解决脏读和不可重复读,但是解决不了幻读,这个也是mysql默认的隔离级别。第四个是串行化(serializable)它可以解决刚才提出来的所有问题,但是由于让是事务串行执行的,性能比较低。所以,我们一般使用的都是mysql默认的隔离级别:可重复读
面试官:undo log和redo log的区别
候选人:好的,其中redo log日志记录的是数据页的物理变化,服务宕机可用来同步数据,而undo log 不同,它主要记录的是逻辑日志,当事务回滚时,通过逆操作恢复原来的数据,比如我们删除一条数据的时候,就会在undo log日志文件中新增一条delete语句,如果发生回滚就执行逆操作;
redo log保证了事务的持久性,undo log保证了事务的原子性和一致性
面试官:事务中的隔离性是如何保证的呢?(你解释一下MVCC)
候选人:事务的隔离性是由锁和mvcc实现的。
其中mvcc的意思是多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突,它的底层实现主要是分为了三个部分,第一个是隐藏字段,第二个是undo log日志,第三个是readView读视图
隐藏字段是指:在mysql中给每个表都设置了隐藏字段,有一个是trx_id(事务id),记录每一次操作的事务id,是自增的;另一个字段是roll_pointer(回滚指针),指向上一个版本的事务版本记录地址
undo log主要的作用是记录回滚日志,存储老版本数据,在内部会形成一个版本链,在多个事务并行操作某一行记录,记录不同事务修改数据的版本,通过roll_pointer指针形成一个链表
readView解决的是一个事务查询选择版本的问题,在内部定义了一些匹配规则和当前的一些事务id判断该访问那个版本的数据,不同的隔离级别快照读是不一样的,最终的访问的结果不一样。如果是rc隔离级别,每一次执行快照读时生成ReadView,如果是rr隔离级别仅在事务中第一次执行快照读时生成ReadView,后续复用
面试官:MySQL主从同步原理
候选人:MySQL主从复制的核心就是二进制日志(DDL(数据定义语言)语句和 DML(数据操纵语言)语句),它的步骤是这样的:
第一:主库在事务提交时,会把数据变更记录在二进制日志文件 Binlog 中。
第二:从库读取主库的二进制日志文件 Binlog ,写入到从库的中继日志 Relay Log 。
第三:从库重做中继日志中的事件,将改变反映它自己的数据
面试官:你们项目用过MySQL的分库分表吗?
候选人:
嗯,因为我们都是微服务开发,每个微服务对应了一个数据库,是根据业务进行拆分的,这个其实就是垂直拆分。
面试官:那你之前使用过水平分库吗?
候选人:
嗯,这个是使用过的,我们当时的业务是(xxx),一开始,我们也是单库,后来这个业务逐渐发展,业务量上来的很迅速,其中(xx)表已经存放了超过1000万的数据,我们做了很多优化也不好使,性能依然很慢,所以当时就使用了水平分库。
我们一开始先做了3台服务器对应了3个数据库,由于库多了,需要分片,我们当时采用的mycat来作为数据库的中间件。数据都是按照id(自增)取模的方式来存取的。
当然一开始的时候,那些旧数据,我们做了一些清洗的工作,我们也是按照id取模规则分别存储到了各个数据库中,好处就是可以让各个数据库分摊存储和读取的压力,解决了我们当时性能的问题
面试官:Spring框架中的单例bean是线程安全的吗?
候选人:
嗯!
不是线程安全的,是这样的
当多用户同时请求一个服务时,容器会给每一个请求分配一个线程,这是多个线程会并发执行该请求对应的业务逻辑(成员方法),如果该处理逻辑中有对该单列状态的修改(体现为该单例的成员属性),则必须考虑线程同步问题。
Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发问题需要开发者自行去搞定。
比如:我们通常在项目中使用的Spring bean都是不可可变的状态(比如Service类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的。
如果你的bean有多种状态的话(比如 View Model对象),就需要自行保证线程安全。最浅显的解决办法就是将多态bean的作用由“singleton”变更为“prototype”。
面试官:什么是AOP
候选人:
aop是面向切面编程,在spring中用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取公共模块复用,降低耦合,一般比如可以做为公共日志保存,事务处理等
面试官:你们项目中有没有使用到AOP
候选人:
我们当时在后台管理系统中,就是使用aop来记录了系统的操作日志
主要思路是这样的,使用aop中的环绕通知+切点表达式,这个表达式就是要找到要记录日志的方法,然后通过环绕通知的参数获取请求方法的参数,比如类信息、方法信息、注解、请求方式等,获取到这些参数以后,保存到数据库
面试官:Spring中的事务是如何实现的
候选人:
spring实现的事务本质就是aop完成,对方法前后进行拦截,在执行方法之前开启事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
面试官:Spring中事务失效的场景有哪些
候选人:
嗯!这个在项目中之前遇到过,我想想啊
第一个,如果方法上异常捕获处理,自己处理了异常,没有抛出,就会导致事务失效,所以一般处理了异常以后,别忘了跑出去就行了
第二个,如果方法抛出检查异常,如果报错也会导致事务失效,最后在spring事务的注解上,就是@Transactional上配置rollbackFor属性为Exception,这样别管是什么异常,都会回滚事务
第三,我之前还遇到过一个,如果方法上不是public修饰的,也会导致事务失效
嗯,就能想起来那么多
面试官:Spring的bean的生命周期
候选人:
嗯!,这个步骤还是挺多的,我之前看过一些源码,它大概流程是这样的
首先会通过一个非常重要的类,叫做BeanDefinition获取bean的定义信息,这里面就封装了bean的所有信息,比如,类的全路径,是否是延迟加载,是否是单例等等这些信息
在创建bean的时候,第一步是调用构造函数实例化bean
第二步是bean的依赖注入,比如一些set方法注入,像平时开发用的@Autowire都是这一步完成
第三步是处理Aware接口,如果某一个bean实现了Aware接口就会重写方法执行
第四步是bean的后置处理器BeanPostProcessor,这个是前置处理器
第五步是初始化方法,比如实现了接口InitializingBean或者自定义了方法init-method标签或@PostContruct
第六步是执行了bean的后置处理器BeanPostProcessor,主要是对bean进行增强,有可能在这里产生代理对象
最后一步是销毁bean
面试官:Spring中的循环引用
候选人:
嗯,好的,我来解释一下
循环依赖:循环依赖其实就是循环引用,也就是两个或两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于A
循环依赖在spring中是允许存在,spring框架依据三级缓存已经解决了大部分的循环依赖
①一级缓存:单例池,缓存已经经历了完整的生命周期,已经初始化完成的bean对象
②二级缓存:缓存早期的bean对象(生命周期还没走完)
③三级缓存:缓存的是ObjectFactory,表示对象工厂,用来创建某个对象的
面试官:那具体解决流程清楚吗?
候选人:
第一,先实例A对象,同时会创建ObjectFactory对象存入三级缓存singletonFactories
第二,A在初始化的时候需要B对象,这个走B的创建的逻辑
第三,B实例化完成,也会创建ObjectFactory对象存入三级缓存singletonFactories
第四,B需要注入A,通过三级缓存中获取ObjectFactory来生成一个A的对象同时存入二级缓存,这个是有两种情况,一个是可能是A的普通对象,另外一个是A的代理对象,都可以让ObjectFactory来生产对应的对象,这也是三级缓存的关键
第五,B通过从通过二级缓存earlySingletonObjects 获得到A的对象后可以正常注入,B创建成功,存入一级缓存singletonObjects
第六,回到A对象初始化,因为B对象已经创建完成,则可以直接注入B,A创建成功存入一次缓存singletonObjects
第七,二级缓存中的临时对象A清除
面试官:构造方法出现了循环依赖怎么解决?
候选人:
由于bean的生命周期中构造函数是第一个执行的,spring框架并不能解决构造函数的的依赖注入,可以使用@Lazy懒加载,什么时候需要对象再进行bean对象的创建
面试官:SpringMVC的执行流程知道嘛
候选人:
嗯,这个知道的,它分了好多步骤
1、用户发送出请求到前端控制器DispatcherServlet,这是一个调度中心
2、DispatcherServlet收到请求调用HandlerMapping(处理器映射器)。
3、HandlerMapping找到具体的处理器(可查找xml配置或注解配置),生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet。
4、DispatcherServlet调用HandlerAdapter(处理器适配器)。
5、HandlerAdapter经过适配调用具体的处理器(Handler/Controller)。
6、Controller执行完成返回ModelAndView对象。
7、HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet。
8、DispatcherServlet将ModelAndView传给ViewReslover(视图解析器)。
9、ViewReslover解析后返回具体View(视图)。
10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
11、DispatcherServlet响应用户。
当然现在的开发,基本都是前后端分离的开发的,并没有视图这些,一般都是handler中使用Response直接结果返回
面试官:Springboot自动配置原理
候选人:
嗯,好的,它是这样的。
在Spring Boot项目中的引导类上有一个注解@SpringBootApplication,这个注解是对三个注解进行了封装,分别是:
- @SpringBootConfiguration
- @EnableAutoConfiguration
- @ComponentScan
其中
@EnableAutoConfiguration
是实现自动化配置的核心注解。该注解通过
@Import
注解导入对应的配置选择器。关键的是内部就是读取了该项目和该项目引用的Jar包的的classpath路径下META-INF/spring.factories文件中的所配置的类的全类名。在这些配置类中所定义的Bean会根据条件注解所指定的条件来决定是否需要将其导入到Spring容器中。
一般条件判断会有像
@ConditionalOnClass
这样的注解,判断是否有对应的class文件,如果有则加载该类,把这个配置类的所有的Bean放入spring容器中使用。面试官:Spring 的常见注解有哪些?
候选人:
嗯,这个就很多了
第一类是:声明bean,有@Component、@Service、@Repository、@Controller
第二类是:依赖注入相关的,有@Autowired、@Qualifier、@Resourse
第三类是:设置作用域 @Scope
第四类是:spring配置相关的,比如@Configuration,@ComponentScan 和 @Bean
第五类是:跟aop相关做增强的注解 @Aspect,@Before,@After,@Around,@Pointcut
面试官:SpringMVC常见的注解有哪些?
候选人:
嗯,这个也很多的
有@RequestMapping:用于映射请求路径;
@RequestBody:注解实现接收http请求的json数据,将json转换为java对象;
@RequestParam:指定请求参数的名称;
@PathViriable:从请求路径下中获取请求参数(/user/{id}),传递给方法的形式参数;@ResponseBody:注解实现将controller方法返回对象转化为json对象响应给客户端。@RequestHeader:获取指定的请求头数据,还有像@PostMapping、@GetMapping这些。
面试官:Springboot常见注解有哪些?
候选人:
嗯~~
Spring Boot的核心注解是@SpringBootApplication , 他由几个注解组成 :
- @SpringBootConfiguration: 组合了- @Configuration注解,实现配置文件的功能;
- @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项
- @ComponentScan:Spring组件扫描
面试官:MyBatis执行流程
候选人:
好,这个知道的,不过步骤也很多
①读取MyBatis配置文件:mybatis-config.xml加载运行环境和映射文件
②构造会话工厂SqlSessionFactory,一个项目只需要一个,单例的,一般由spring进行管理
③会话工厂创建SqlSession对象,这里面就含了执行SQL语句的所有方法
④操作数据库的接口,Executor执行器,同时负责查询缓存的维护
⑤Executor接口的执行方法中有一个MappedStatement类型的参数,封装了映射信息
⑥输入参数映射
⑦输出结果映射
面试官:Mybatis是否支持延迟加载?
候选人:
是支持的~
延迟加载的意思是:就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。
Mybatis支持一对一关联对象和一对多关联集合对象的延迟加载
在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false,默认是关闭的
面试官:延迟加载的底层原理知道吗?
候选人:
嗯,我想想啊
延迟加载在底层主要使用的CGLIB动态代理完成的
第一是,使用CGLIB创建目标对象的代理对象,这里的目标对象就是开启了延迟加载的mapper
第二个是当调用目标方法时,进入拦截器invoke方法,发现目标方法是null值,再执行sql查询
第三个是获取数据以后,调用set方法设置属性值,再继续查询目标方法,就有值了
面试官:Mybatis的一级、二级缓存用过吗?
候选人:
嗯~~,用过的~
mybatis的一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当Session进行flush或close之后,该Session中的所有Cache就将清空,默认打开一级缓存
关于二级缓存需要单独开启
二级缓存是基于namespace和mapper的作用域起作用的,不是依赖于SQL session,默认也是采用 PerpetualCache,HashMap 存储。
如果想要开启二级缓存需要在全局配置文件和映射文件中开启配置才行。
面试官:Mybatis的二级缓存什么时候会清理缓存中的数据
候选人:
嗯!!
当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了新增、修改、删除操作后,默认该作用域下所有 select 中的缓存将被 clear。
面试官:Spring Cloud 5大组件有哪些?
候选人:
早期我们一般认为的Spring Cloud五大组件是
- Eureka : 注册中心
- Ribbon : 负载均衡
- Feign : 远程调用
- Hystrix : 服务熔断
- Zuul/Gateway : 网关
随着SpringCloudAlibba在国内兴起 , 我们项目中使用了一些阿里巴巴的组件
- 注册中心/配置中心 Nacos
- 负载均衡 Ribbon
- 服务调用 Feign
- 服务保护 sentinel
- 服务网关 Gateway
面试官:服务注册和发现是什么意思?Spring Cloud 如何实现服务注册发现?
候选人:
我理解的是主要三块大功能,分别是服务注册 、服务发现、服务状态监控
我们当时项目采用的eureka作为注册中心,这个也是spring cloud体系中的一个核心组件
服务注册:服务提供者需要把自己的信息注册到eureka,由eureka来保存这些信息,比如服务名称、ip、端口等等
服务发现:消费者向eureka拉取服务列表信息,如果服务提供者有集群,则消费者会利用负载均衡算法,选择一个发起调用
服务监控:服务提供者会每隔30秒向eureka发送心跳,报告健康状态,如果eureka服务90秒没接收到心跳,从eureka中剔除
面试官:我看你之前也用过nacos、你能说下nacos与eureka的区别?
候选人:
我们当时xx项目就是采用的nacos作为注册中心,选择nacos还要一个重要原因就是它支持配置中心,不过nacos作为注册中心,也比eureka要方便好用一些,主要相同不同点在于几点:
- 共同点
Nacos与eureka都支持服务注册和服务拉取,都支持服务提供者心跳方式做健康检测
- Nacos与Eureka的区别
①Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
②临时实例心跳不正常会被剔除,非临时实例则不会被剔除
③Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
④Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式
面试官:你们项目负载均衡如何实现的 ?
候选人:
是这样~~
在服务调用过程中的负载均衡一般使用SpringCloud的Ribbon 组件实现 , Feign的底层已经自动集成了Ribbon , 使用起来非常简单
当发起远程调用时,ribbon先从注册中心拉取服务地址列表,然后按照一定的路由策略选择一个发起远程调用,一般的调用策略是轮询
面试官:Ribbon负载均衡策略有哪些 ?
候选人:
我想想啊,有很多种,我记得几个:
- RoundRobinRule:简单轮询服务列表来选择服务器
- WeightedResponseTimeRule:按照权重来选择服务器,响应时间越长,权重越小
- RandomRule:随机选择一个可用的服务器
- ZoneAvoidanceRule:区域敏感策略,以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询(默认)
面试官:如果想自定义负载均衡策略如何实现 ?
候选人:
提供了两种方式:
1,创建类实现IRule接口,可以指定负载均衡策略,这个是全局的,对所有的远程调用都起作用
2,在客户端的配置文件中,可以配置某一个服务调用的负载均衡策略,只是对配置的这个服务生效远程调用
面试官:什么是服务雪崩,怎么解决这个问题?
候选人:
服务雪崩是指一个服务失败,导致整条链路的服务都失败的情形,一般我们在项目解决的话就是两种方案,第一个是服务降级,第二个是服务熔断,如果流量太大的话,可以考虑限流
服务降级:服务自我保护的一种方式,或者保护下游服务的一种方式,用于确保服务不会受请求突增影响变得不可用,确保服务不会崩溃,一般在实际开发中与feign接口整合,编写降级逻辑
服务熔断:默认关闭,需要手动打开,如果检测到 10 秒内请求的失败率超过 50%,就触发熔断机制。之后每隔 5 秒重新尝试请求微服务,如果微服务不能响应,继续走熔断机制。如果微服务可达,则关闭熔断机制,恢复正常请求
面试官:你们的微服务是怎么监控的?
候选人:
我们项目中采用的skywalking进行监控的
1,skywalking主要可以监控接口、服务、物理实例的一些状态。特别是在压测的时候可以看到众多服务中哪些服务和接口比较慢,我们可以针对性的分析和优化。
2,我们还在skywalking设置了告警规则,特别是在项目上线以后,如果报错,我们分别设置了可以给相关负责人发短信和发邮件,第一时间知道项目的bug情况,第一时间修复
面试官:你们项目中有没有做过限流 ? 怎么做的 ?
候选人:
我当时做的xx项目,采用就是微服务的架构,因为xx因为,应该会有突发流量,最大QPS可以达到2000,但是服务支撑不住,我们项目都通过压测最多可以支撑1200QPS。因为我们平时的QPS也就不到100,为了解决这些突发流量,所以采用了限流。
【版本1】
我们当时采用的nginx限流操作,nginx使用的漏桶算法来实现过滤,让请求以固定的速率处理请求,可以应对突发流量,我们控制的速率是按照ip进行限流,限制的流量是每秒20
【版本2】
我们当时采用的是spring cloud gateway中支持局部过滤器RequestRateLimiter来做限流,使用的是令牌桶算法,可以根据ip或路径进行限流,可以设置每秒填充平均速率,和令牌桶总容量
面试官:限流常见的算法有哪些呢?
候选人:
比较常见的限流算法有漏桶算法和令牌桶算法
漏桶算法是把请求存入到桶中,以固定速率从桶中流出,可以让我们的服务做到绝对的平均,起到很好的限流效果
令牌桶算法在桶中存储的是令牌,按照一定的速率生成令牌,每个请求都要先申请令牌,申请到令牌以后才能正常请求,也可以起到很好的限流作用
它们的区别是,漏桶和令牌桶都可以处理突发流量,其中漏桶可以做到绝对的平滑,令牌桶有可能会产生突发大量请求的情况,一般nginx限流采用的漏桶,spring cloud gateway中可以支持令牌桶算法
面试官:什么是CAP理论?
候选人:
CAP主要是在分布式项目下的一个理论。包含了三项,一致性、可用性、分区容错性
- 一致性(Consistency)是指更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致(强一致性),不能存在中间状态。
- 可用性(Availability) 是指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。
- 分区容错性(Partition tolerance) 是指分布式系统在遇到任何网络分区故障时,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。
面试官:为什么分布式系统中无法同时保证一致性和可用性?
候选人:
嗯,是这样的~~
首先一个前提,对于分布式系统而言,分区容错性是一个最基本的要求,因此基本上我们在设计分布式系统的时候只能从一致性(C)和可用性(A)之间进行取舍。
如果保证了一致性(C):对于节点N1和N2,当往N1里写数据时,N2上的操作必须被暂停,只有当N1同步数据到N2时才能对N2进行读写请求,在N2被暂停操作期间客户端提交的请求会收到失败或超时。显然,这与可用性是相悖的。
如果保证了可用性(A):那就不能暂停N2的读写操作,但同时N1在写数据的话,这就违背了一致性的要求。
面试官:什么是BASE理论?
候选人:
嗯,这个也是CAP分布式系统设计理论
BASE是CAP理论中AP方案的延伸,核心思想是即使无法做到强一致性(StrongConsistency,CAP的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性(Eventual Consitency)。它的思想包含三方面:
1、Basically Available(基本可用):基本可用是指分布式系统在出现不可预知的故障的时候,允许损失部分可用性,但不等于系统不可用。
2、Soft state(软状态):即是指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。
3、Eventually consistent(最终一致性):强调系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。其本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
面试官:你们采用哪种分布式事务解决方案?
候选人:
我们当时是xx项目,主要使用到的seata的at模式解决的分布式事务
seata的AT模型分为两个阶段:
1、阶段一RM的工作:① 注册分支事务 ② 记录undo-log(数据快照)③ 执行业务sql并提交 ④报告事务状态
2、阶段二提交时RM的工作:删除undo-log即可
3、阶段二回滚时RM的工作:根据undo-log恢复数据到更新前
at模式牺牲了一致性,保证了可用性,不过,它保证的是最终一致性
面试官:分布式服务的接口幂等性如何设计?
候选人:
嗯,我们当时有一个xx项目的下单操作,采用的token+redis实现的,流程是这样的
第一次请求,也就是用户打开了商品详情页面,我们会发起一个请求,在后台生成一个唯一token存入redis,key就是用户的id,value就是这个token,同时把这个token返回前端
第二次请求,当用户点击了下单操作会后,会携带之前的token,后台先到redis进行验证,如果存在token,可以执行业务,同时删除token;如果不存在,则直接返回,不处理业务,就保证了同一个token只处理一次业务,就保证了幂等性
面试官:xxl-job路由策略有哪些?
候选人:
xxl-job提供了很多的路由策略,我们平时用的较多就是:轮询、故障转移、分片广播…
面试官:xxl-job任务执行失败怎么解决?
候选人:
有这么几个操作
第一:路由策略选择故障转移,优先使用健康的实例来执行任务
第二,如果还有失败的,我们在创建任务时,可以设置重试次数
第三,如果还有失败的,就可以查看日志或者配置邮件告警来通知相关负责人解决
面试官:如果有大数据量的任务同时都需要执行,怎么解决?
候选人:
我们会让部署多个实例,共同去执行这些批量的任务,其中任务的路由策略是分片广播
在任务执行的代码中可以获取分片总数和当前分片,按照取模的方式分摊到各个实例执行就可以了
]]>面试官:RabbitMQ-如何保证消息不丢失
候选人:
嗯!我们当时MYSQL和Redis的数据双写一致性就是采用RabbitMQ实现同步的,这里面就要求了消息的高可用性,我们要保证消息的不丢失。主要从三个层面考虑
第一个是开启生产者确认机制,确保生产者的消息能到达队列,如果报错可以先记录到日志中,再去修复数据
第二个是开启持久化功能,确保消息未消费前在队列中不会丢失,其中的交换机、队列、和消息都要做持久化
第三个是开启消费者确认机制为auto,由spring确认消息处理成功后完成ack,当然也需要设置一定的重试次数,我们当时设置了3次,如果重试3次还没有收到消息,就将失败后的消息投递到异常交换机,交由人工处理
面试官:RabbitMQ消息的重复消费问题如何解决的
候选人:
嗯,这个我们还真遇到过,是这样的,我们当时消费者是设置了自动确认机制,当服务还没来得及给MQ确认的时候,服务宕机了,导致服务重启之后,又消费了一次消息。这样就重复消费了
因为我们当时处理的支付(订单|业务唯一标识),它有一个业务的唯一标识,我们再处理消息时,先到数据库查询一下,这个数据是否存在,如果不存在,说明没有处理过,这个时候就可以正常处理这个消息了。如果已经存在这个数据了,就说明消息重复消费了,我们就不需要再消费了
面试官:那你还知道其他的解决方案吗?
候选人:
嗯,我想想~
其实这个就是典型的幂等的问题,比如,redis分布式锁、数据库的锁都是可以的
面试官:RabbitMQ中死信交换机 ? (RabbitMQ延迟队列有了解过嘛)
候选人:
嗯!了解过!
我们当时的xx项目有一个xx业务,需要用到延迟队列,其中就是使用RabbitMQ来实现的。
延迟队列就是用到了死信交换机和TTL(消息存活时间)实现的。
如果消息超时未消费就会变成死信,在RabbitMQ中如果消息成为死信,队列可以绑定一个死信交换机,在死信交换机上可以绑定其他队列,在我们发消息的时候可以按照需求指定TTL的时间,这样就实现了延迟队列的功能了。
我记得RabbitMQ还有一种方式可以实现延迟队列,在RabbitMQ中安装一个死信插件,这样更方便一些,我们只需要在声明交互机的时候,指定这个就是死信交换机,然后在发送消息的时候直接指定超时时间就行了,相对于死信交换机+TTL要省略了一些步骤
面试官:如果有100万消息堆积在MQ , 如何解决 ?
候选人:
我在实际的开发中,没遇到过这种情况,不过,如果发生了堆积的问题,解决方案也所有很多的
第一:提高消费者的消费能力 ,可以使用多线程消费任务
第二:增加更多消费者,提高消费速度
使用工作队列模式, 设置多个消费者消费消费同一个队列中的消息
第三:扩大队列容积,提高堆积上限
可以使用RabbitMQ惰性队列,惰性队列的好处主要是
①接收到消息后直接存入磁盘而非内存
②消费者要消费消息时才会从磁盘中读取并加载到内存
③支持数百万条的消息存储
面试官:RabbitMQ的高可用机制有了解过嘛
候选人:
嗯,熟悉的~
我们当时项目在生产环境下,使用的集群,当时搭建是镜像模式集群,使用了3台机器。
镜像队列结构是一主多从,所有操作都是主节点完成,然后同步给镜像节点,如果主节点宕机后,镜像节点会替代成新的主节点,不过在主从同步完成前,主节点就已经宕机,可能出现数据丢失
面试官:那出现丢数据怎么解决呢?
候选人:
我们可以采用仲裁队列,与镜像队列一样,都是主从模式,支持主从数据同步,主从同步基于Raft协议,强一致。
并且使用起来也非常简单,不需要额外的配置,在声明队列的时候只要指定这个是仲裁队列即可
面试官:Kafka是如何保证消息不丢失
候选人:
嗯,这个保证机制很多,在发送消息到消费者接收消息,在每个阶段都有可能会丢失消息,所以我们解决的话也是从多个方面考虑
第一个是生产者发送消息的时候,可以使用异步回调发送,如果消息发送失败,我们可以通过回调获取失败后的消息信息,可以考虑重试或记录日志,后边再做补偿都是可以的。同时在生产者这边还可以设置消息重试,有的时候是由于网络抖动的原因导致发送不成功,就可以使用重试机制来解决
第二个在broker中消息有可能会丢失,我们可以通过kafka的复制机制来确保消息不丢失,在生产者发送消息的时候,可以设置一个acks,就是确认机制。我们可以设置参数为all,这样的话,当生产者发送消息到了分区之后,不仅仅只在leader分区保存确认,在follwer分区也会保存确认,只有当所有的副本都保存确认以后才算是成功发送了消息,所以,这样设置就很大程度了保证了消息不会在broker丢失
第三个有可能是在消费者端丢失消息,kafka消费消息都是按照offset进行标记消费的,消费者默认是自动按期提交已经消费的偏移量,默认是每隔5s提交一次,如果出现重平衡的情况,可能会重复消费或丢失数据。我们一般都会禁用掉自动提价偏移量,改为手动提交,当消费成功以后再报告给broker消费的位置,这样就可以避免消息丢失和重复消费了
面试官:Kafka中消息的重复消费问题如何解决的
候选人:
kafka消费消息都是按照offset进行标记消费的,消费者默认是自动按期提交已经消费的偏移量,默认是每隔5s提交一次,如果出现重平衡的情况,可能会重复消费或丢失数据。我们一般都会禁用掉自动提价偏移量,改为手动提交,当消费成功以后再报告给broker消费的位置,这样就可以避免消息丢失和重复消费了
为了消息的幂等,我们也可以设置唯一主键来进行区分,或者是加锁,数据库的锁,或者是redis分布式锁,都能解决幂等的问题
面试官:Kafka是如何保证消费的顺序性
候选人:
kafka默认存储和消费消息,是不能保证顺序性的,因为一个topic数据可能存储在不同的分区中,每个分区都有一个按照顺序的存储的偏移量,如果消费者关联了多个分区不能保证顺序性
如果有这样的需求的话,我们是可以解决的,把消息都存储同一个分区下就行了,有两种方式都可以进行设置,第一个是发送消息时指定分区号,第二个是发送消息时按照相同的业务设置相同的key,因为默认情况下分区也是通过key的hashcode值来选择分区的,hash值如果一样的话,分区肯定也是一样的
面试官:Kafka的高可用机制有了解过嘛
候选人:
嗯,主要是有两个层面,第一个是集群,第二个是提供了复制机制
kafka集群指的是由多个broker实例组成,即使某一台宕机,也不耽误其他broker继续对外提供服务
复制机制是可以保证kafka的高可用的,一个topic有多个分区,每个分区有多个副本,有一个leader,其余的是follower,副本存储在不同的broker中;所有的分区副本的内容是都是相同的,如果leader发生故障时,会自动将其中一个follower提升为leader,保证了系统的容错性、高可用性
面试官:解释一下复制机制中的ISR
候选人:
ISR的意思是in-sync replica,就是需要同步复制保存的follower
其中分区副本有很多的follower,分为了两类,一个是ISR,与leader副本同步保存数据,另外一个普通的副本,是异步同步数据,当leader挂掉之后,会优先从ISR副本列表中选取一个作为leader,因为ISR是同步保存数据,数据更加的完整一些,所以优先选择ISR副本列表
面试官:Kafka数据清理机制了解过嘛
候选人:
嗯,了解过~~
Kafka中topic的数据存储在分区上,分区如果文件过大会分段存储segment
每个分段都在磁盘上以索引(xxxx.index)和日志文件(xxxx.log)的形式存储,这样分段的好处是,第一能够减少单个文件内容的大小,查找数据方便,第二方便kafka进行日志清理。
在kafka中提供了两个日志的清理策略:
第一,根据消息的保留时间,当消息保存的时间超过了指定的时间,就会触发清理,默认是168小时( 7天)
第二是根据topic存储的数据大小,当topic所占的日志文件大小大于一定的阈值,则开始删除最久的消息。这个默认是关闭的
这两个策略都可以通过kafka的broker中的配置文件进行设置
面试官:Kafka中实现高性能的设计有了解过嘛
候选人:
Kafka 高性能,是多方面协同的结果,包括宏观架构、分布式存储、ISR 数据同步、以及高效的利用磁盘、操作系统特性等。主要体现有这么几点:
消息分区:不受单台服务器的限制,可以不受限的处理更多的数据
顺序读写:磁盘顺序读写,提升读写效率
页缓存:把磁盘中的数据缓存到内存中,把对磁盘的访问变为对内存的访问
零拷贝:减少上下文切换及数据拷贝
消息压缩:减少磁盘IO和网络IO
分批发送:将消息打包批量发送,减少网络开销
随着智能手机的普及,人们更加习惯于通过手机来看新闻。由于生活节奏的加快,很多人只能利用碎片时间来获取信息,因此,对于移动资讯客户端的需求也越来越高。黑马头条项目正是在这样背景下开发出来。黑马头条项目采用当下火热的微服务+大数据技术架构实现。本项目主要着手于获取最新最热新闻资讯,通过大数据分析用户喜好精确推送咨询新闻。
功能架构图
技术栈
解决方案
1.解压分享的虚拟机镜像文件
2.使用VmWare打开.vmx文件
3.配置虚拟机的网络环境
4.开启虚拟机
5.使用FinalShell连接此虚拟机
用户名: root 密码:root IP地址: 192.168.200.130
Linux中开发环境的搭建 | The Blog (qingling.icu)
JDK1.8
Intellij Idea
maven-3.6.1
Git
解压heima-leadnews.zip文件并用IDEA工具打开
编码格式的设置
登录相关的表结构
表名称 | 说明 |
---|---|
ap_user | APP用户信息表 |
ap_user_fan | APP用户粉丝信息表 |
ap_user_follow | APP用户关注信息表 |
ap_user_realname | APP实名认证信息表 |
ap_user表对应的实体类
package com.heima.model.user.pojos; |
注册加盐的过程
用户在登录的时候会生成一个随机的字符串(salt),这个随机的字符串会加到密码后面然后连同密码加密存储到数据库。
登录加盐的过程
先根据账号查询是否存在该用户,如果存在的话,根据用户输入的密码和数据库中的salt进行md5加密,并和数据库中的密码比对,一致的话,比对通过,不一样的话不通过。
heima-leadnews-service父工程依赖文件
|
创建子模块并创建出对应的目录结构
在heima-leadnews-service父工程下创建工程heima-leadnews-user
编写用户模块的配置文件
server: |
在配置中心中添加数据库等相关的配置
在resources目录下添加日志的配置文件
logback.xml
|
遇到的问题:
问题一:引入@EnableDiscoveryClient注解的时候爆红
解决方案:在heima-leadnews-service父工程下加入如下的注解
<!-- Feign远程调用客户端 --> |
接口路径 | /api/v1/login/login_auth |
---|---|
请求方式 | POST |
参数 | LoginDto |
响应结果 | ResponseResult |
LoginDto
|
统一返回结果类
package com.heima.model.common.dtos; |
package com.heima.user.service.impl; |
接口测试工具使用教程:https://qingling.icu/posts/35630.html
网关的概述
项目中搭建的网关
1.在heima-leadnews-gateway导入以下依赖
<dependencies> |
2.创建网关的模块
3.创建启动类和bootstrap.yml配置文件
|
server: |
4.在nacos中创建app端网关的配置
spring: |
5.使用Postman测试网关
http://localhost:51601/user/api/v1/login/login_auth
网关的过滤流程
思路分析:
JWT认证的过滤器
/** |
通过nginx来进行配置,功能如下
①:解压资料文件夹中的压缩包nginx-1.18.0.zip
cmd切换到nginx所有的目录输入nginx启动nginx
②:解压资料文件夹中的前端项目app-web.zip
解压到一个没有中文的文件夹中,后面nginx配置中会指向这个目录
③:配置nginx.conf文件
在nginx安装的conf目录下新建一个文件夹leadnews.conf
,在当前文件夹中新建heima-leadnews-app.conf
文件
heima-leadnews-app.conf配置如下:
upstream heima-app-gateway{ |
nginx.conf 把里面注释的内容和静态资源配置相关删除,引入heima-leadnews-app.conf文件加载
#user nobody; |
④ :启动nginx
在nginx安装包中使用命令提示符打开,输入命令nginx启动项目
可查看进程,检查nginx是否启动
重新加载配置文件:nginx -s reload
⑤:打开前端项目进行测试 – > http://localhost:8801
用谷歌浏览器打开,调试移动端模式进行访问
开发前app的首页面
文章的布局展示
文章的相关的数据库
表名称 | 说明 |
---|---|
ap_article | 文章信息表,存储已发布的文章 |
ap_article_config | APP已发布文章配置表 |
ap_article_content | APP已发布文章内容表 |
ap_author | APP文章作者信息表 |
ap_collection | APP收藏信息表 |
导入资料中的sql文件创建相关的数据库表
关键的数据库表
文章基本信息表
APP已发布文章配置表
APP已发布文章内容表
APP文章作者信息表
APP收藏信息表
垂直分表
将文章相关的表分成文章配置表和文章内容表和文章信息表
垂直分表:将一个表的字段分散到多个表中,每个表存储其中一部分字段
优势:
减少IO争抢,减少锁表的几率,查看文章概述与文章详情互不影响
充分发挥高频数据的操作效率,对文章概述数据操作的高效率不会被操作文章详情数据的低效率所拖累
拆分规则:
1.把不常用的字段单独放在一张表
2.把text,blob等大字段拆分出来单独放在一张表
3.经常组合查询的字段单独放在一张表中
导入资料中的模块
在nacos中添加配置
spring: |
踩坑: 在导入项目的时候提示Caused by: java.io.FileNotFoundException: class path resource [com/heima/apis/article/IArticleClient.class] cannot be opened because it does not exist ,先删除这个项目,手动创建这个项目,然后复制资料里面文件到这个项目即可,直接复制整个项目可能会报这个错!
首页上拉和下拉的实现思路
Sql语句实现
#按照发布时间倒序查询十条文章 |
加载首页 | 加载更多 | 加载最新 | |
---|---|---|---|
接口路径 | /api/v1/article/load | /api/v1/article/loadmore | /api/v1/article/loadnew |
请求方式 | POST | POST | POST |
参数 | ArticleHomeDto | ArticleHomeDto | ArticleHomeDto |
响应结果 | ResponseResult | ResponseResult | ResponseResult |
ArticleHomeDto
package com.heima.model.article.dtos; |
①:导入heima-leadnews-article微服务,资料在当天的文件夹中
需要在nacos中添加对应的配置
②:定义接口
接口路径、请求方式、入参、出参
③:编写mapper文件
文章表与文章配置表多表查询
④:编写业务层代码
⑤:编写控制器代码
⑥:swagger测试或前后端联调测试
mapper层的代码
|
service层的代码
package com.heima.article.service.Impl; |
controller层的代码
package com.heima.article.controller.v1; |
网关中增加文章模块的路由
spring: |
静态模板展示关键技术-Freemarker
Freemarker教程: https://qingling.icu/posts/29367.html
MinIO 教程: https://qingling.icu/posts/36397.html
代码实现
1.在文章模块的pom.xml文件中加入以下的依赖
<dependencies> |
2.在nacos中有关文章模块的配置中添加以下的内容
minio: |
3.将资料中的article.ftl文件拷贝到文章模块的templates目录下,article.ftl文件内容如下
|
4.手动上传资料中index.js和index.css两个文件到MinIO中
上传index.js
|
上传index.css
|
5.测试根据文章的内容生成html文件上传到minio中
package com.heima.article.test; |
6.实现效果
需要搭建的模块
搭建步骤
1.创建自媒体模块的数据库
2.导入相应的工程文件
3.配置自媒体模块和网关模块在nacos中的配置
自媒体模块的配置
spring: |
网关模块的配置
spring: |
搭建思路
搭建步骤
heima-leadnews-wemedia配置文件
upstream heima-wemedia-gateway{ |
重启nginx访问
图片素材相关的实体类
package com.heima.model.wemedia.pojos; |
实现思路
1.token解析为用户存入header
package com.heima.wemedia.gateway.filter; |
2.创建拦截器
package com.heima.wemedia.interceptor; |
ThreadLocal工具类,实现在线程中存储、获取、清理用户信息
package com.heima.utils.thread; |
3.让拦截器生效
package com.heima.wemedia.config; |
说明 | |
---|---|
接口路径 | /api/v1/material/upload_picture |
请求方式 | POST |
参数 | MultipartFile |
响应结果 | ResponseResult |
1.在pom.xml中引入自定义minio的starter依赖
<dependency> |
2.在项目中添加minio的配置(在nacos中配置)
minio: |
3.上传图片的关键代码
package com.heima.wemedia.service.impl; |
4.测试上传功能
接口定义
接口路径 | /api//v1/material/list |
---|---|
请求方式 | POST |
参数 | WmMaterialDto |
响应结果 | ResponseResult |
请求参数的DTO
|
关键代码
/** |
MP的配置
|
实现效果
频道对应的实体类
package com.heima.model.wemedia.pojos; |
接口定义
说明 | |
---|---|
接口路径 | /api/v1/channel/channels |
请求方式 | GET |
参数 | 无 |
响应结果 | ResponseResult |
关键代码
/** |
实现效果
文章表对应的实体类
package com.heima.model.wemedia.pojos; |
接口定义
接口路径 | /api//v1/news/list |
---|---|
请求方式 | POST |
参数 | WmNewsPageReqDto |
响应结果 | ResponseResult |
关键代码
|
实现效果
文章和素材对应关系表
package com.heima.model.wemedia.pojos; |
实现流程
接口定义
说明 | |
---|---|
接口路径 | /api/v1/news/submit |
请求方式 | POST |
参数 | WmNewsDto |
响应结果 | ResponseResult |
WmNewsDto
接收前端参数的dto
package com.heima.model.wemedia.dtos; |
前端传递的json格式数据举例
{ |
保存文章和素材对应关系mapper接口
mapper接口
|
mapper.xml文件
|
使用到的常量类
package com.heima.common.constants; |
发布文章功能的关键代码
存在许多的bug
package com.heima.wemedia.service.impl; |
调用第三方的接口(阿里云内容安全审核接口)实现审核功能
功能介绍
审核流程
1 自媒体端发布文章后,开始审核文章
2 审核的主要是审核文章的内容(文本内容和图片)
3 借助第三方提供的接口审核文本
4 借助第三方提供的接口审核图片,由于图片存储到minIO中,需要先下载才能审核
5 如果审核失败,则需要修改自媒体文章的状态,status:2 审核失败 status:3 转到人工审核
6 如果审核成功,则需要在文章微服务中创建app端需要的文章
第三方审核接口
1.内容安全接口介绍:
内容安全是识别服务,支持对图片、视频、文本、语音等对象进行多样化场景检测,有效降低内容违规风险。目前很多平台都支持内容检测,如阿里云、腾讯云、百度AI、网易云等国内大型互联网公司都对外提供了API。
2.文件检测和图片检测api文档
文本垃圾内容Java SDK: https://help.aliyun.com/document_detail/53427.html?spm=a2c4g.11186623.6.717.466d7544QbU8Lr
图片垃圾内容Java SDK: https://help.aliyun.com/document_detail/53424.html?spm=a2c4g.11186623.6.715.c8f69b12ey35j4
项目中集成阿里云内容安全接口
1.依赖导入
<dependency> |
2.相关的工具类
GreenImageScan(图片审核的工具类)
package com.heima.common.aliyun; |
GreenTextScan(文字审核的工具类)
package com.heima.common.aliyun; |
3.在heima-leadnews-wemedia中的nacos配置中心添加以下配置
aliyun: |
4.编写测试类测试
import com.heima.common.aliyun.GreenImageScan; |
为什么使用分布式ID
分布式ID的技术选型
雪花算法的介绍
mybatis-plus已经集成了雪花算法,完成以下两步即可在项目中集成雪花算法
第一:在实体类中的id上加入如下配置,指定类型为id_worker
|
第二:在application.yml文件中配置数据中心id和机器id
mybatis-plus: |
datacenter-id:数据中心id(取值范围:0-31) workerId:机器id(取值范围:0-31)
由于没有阿里云相关的ak和sk,所以本部分默认每篇文章的文字和图片都审核通过(中间会注释掉调用第三方审核接口的代码)
service层代码实现
package com.heima.wemedia.service.impl; |
单元测试
package com.heima.wemedia.service; |
实现步骤:
①:在heima-leadnews-feign-api编写降级逻辑
package com.heima.apis.article.fallback; |
在自媒体微服务中添加类,扫描降级代码类的包
package com.heima.wemedia.config; |
②:远程接口中指向降级代码
package com.heima.apis.article; |
③:客户端开启降级heima-leadnews-wemedia
在wemedia的nacos配置中心里添加如下内容,开启服务降级,也可以指定服务响应的超时的时间
feign: |
④:测试
在ApArticleServiceImpl类中saveArticle方法添加代码
try { |
在自媒体端进行审核测试,会出现服务降级的现象
实现思路
在文章审核成功以后需要在app的article库中新增文章数据
1.保存文章信息 ap_article
2.保存文章配置信息 ap_article_config
3.保存文章内容 ap_article_content
保存文章的接口
说明 | |
---|---|
接口路径 | /api/v1/article/save |
请求方式 | POST |
参数 | ArticleDto |
响应结果 | ResponseResult |
ArticleDto
package com.heima.model.article.dtos; |
成功:
{ |
失败:
{ |
{ |
实现步骤
功能实现:
①:在heima-leadnews- feign-api中新增接口
第一:线导入feign的依赖
<dependency> |
第二:定义文章端的接口
package com.heima.apis.article; |
②:在heima-leadnews-article中实现该方法
package com.heima.article.feign; |
③:拷贝mapper
在资料文件夹中拷贝ApArticleConfigMapper类到mapper文件夹中
同时,修改ApArticleConfig类,添加如下构造函数
package com.heima.model.article.pojos; |
④:在ApArticleService中新增方法
/** |
实现类:
|
⑤:测试
编写junit单元测试,或使用postman进行测试
{ |
①:在自动审核的方法上加上@Async注解(标明要异步调用)
|
②:在文章发布成功后调用审核的方法
|
③:在自媒体引导类中使用@EnableAsync注解开启异步调用
|
1,nacos服务端
2,article微服务
3,wemedia微服务
4,启动wemedia网关微服务
5,启动前端系统wemedia
1,自媒体前端发布一篇正常的文章
审核成功后,app端的article相关数据是否可以正常保存,自媒体文章状态和app端文章id是否回显
2,自媒体前端发布一篇包含敏感词的文章
正常是审核失败, wm_news表中的状态是否改变,成功和失败原因正常保存
3,自媒体前端发布一篇包含敏感图片的文章
正常是审核失败, wm_news表中的状态是否改变,成功和失败原因正常保存
方案 | 说明 |
---|---|
数据库模糊查询 | 效率太低 |
String.indexOf(“”)查找 | 数据库量大的话也是比较慢 |
全文检索 | 分词再匹配 |
DFA算法 | 确定有穷自动机(一种数据结构) |
package com.heima.utils.common; |
①:创建敏感词表,导入资料中wm_sensitive到leadnews_wemedia库中,并创建对应的实体类
package com.heima.model.wemedia.pojos; |
②:拷贝对应的wm_sensitive的mapper到项目中
package com.heima.wemedia.mapper; |
③:在文章审核的代码中添加自管理敏感词审核
第一:在WmNewsAutoScanServiceImpl中的autoScanWmNews方法上添加如下代码
//从内容中提取纯文本内容和图片 |
新增自管理敏感词审核代码
|
详细教程: https://qingling.icu/posts/58456.html
①:在heima-leadnews-common中创建工具类,简单封装一下tess4j
需要先导入pom
<dependency> |
工具类
package com.heima.common.tess4j; |
在spring.factories配置中添加该类,完整如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ |
②:在heima-leadnews-wemedia中的配置中添加两个属性
tess4j: |
③:在WmNewsAutoScanServiceImpl中的handleImageScan方法上添加如下代码
try { |
最后附上文章审核的完整代码如下:
package com.heima.wemedia.service.impl; |
实现步骤
1.新建ArticleFreemarkerService ,定义创建静态文件并上传到minIO中方法
package com.heima.article.service; |
package com.heima.article.service.Impl; |
2.在ApArticleService的saveArticle实现方法中添加调用生成文件的方法
/** |
3.文章微服务开启异步调用
package com.heima.article; |
测试
电商模式 谷粒商城使用的B2C模式,销售自营的商品给客户
市面上5种常见的电商模式B2B、B2C、C2B、C2C、O2O
1、B2B模式:
business to business,是指商家与商家建立的商业关系。如阿里巴巴。
2、B2C模式:
business to consumer,商对客模式。即通常说的商业零售,供应商直接把商品卖给用户。如:苏宁易购、京东、天猫、小米商城。
3、C2B模式:
customer to business,即消费者对企业。先有消费者需求而后有企业生产。例如众筹类的商城。
4、C2C模式:
customer to consumer,客户之间直接把东西放上网上去卖。如:淘宝、闲鱼。
5、O2O模式:
online to offline,线上线下。线上快速支付,线下优质服务。如:饿了么、美团、京东到家。
项目的架构图
微服务划分图
微服务架构风格,就像是一个单独的应用程序开发为一套小服务,每个服务运行在自己的进程中,并使用轻量级机制通信,通常是HTTP API。这些服务围绕业务能力来构建,并通过完全自动化部署机制来独立部署。这些服务使用不同的编程语言编写,以及不同的数据存储技术,并保持最低限度的集中式管理。
简而言之:拒绝大型单体应用,基于业务边界进行服务微化拆分,各个应用独立部署运行。
集群是一个物理形态 ,分布式是个工作方式
分布式: 将不同的业务分布在不同的地方
集群: 集群指的是将几台服务器集中在一起,实现同一业务
分布式中的每一个节点,都可以做集群。而集群不一定是分布式的。
节点: 集群中的一个服务器
在分布式系统中,各个服务可能处于不同主机,但是服务之间不可避免的需要相互调用,我们称之为远程调用。
SpringCloud中使用HTTP+JSON的方式完成远程调用
分布式系统中,A服务需要调用B服务,B服务在多台机器上都存在,A调用任意一个服务均可完成功能。为了使每一个服务器都不要太忙或者太闲,我们可以使用负载均衡的调用每一个服务器,提升网站的健壮性。
常见的负载均衡算法:
轮询: 为第一个请求选择健康池中的第一台后端服务器,然后按顺序往后依次选择,直到最后一个,然后循环。
最小连接: 有限选择连接数量少,也就是压力最小的后端服务器,在会话较长的情况下可以考虑采取这种方式。
随机法:通过系统的随机算法,根据后端服务器的列表大小值来随机选取其中的一台服务器进行访问。由概率统计理论可以得知,随着客户端调用服务端的次数增多,其实际效果越来越接近于平均分配调用量到后端的每一台服务器,也就是轮询的结果。
源地址哈希法:源地址哈希的思想是根据获取客户端的IP地址,通过哈希函数计算得到的一个数值,用该数值对服务器列表的大小进行取模运算,得到的结果便是客服端要访问服务器的序号。采用源地址哈希法进行负载均衡,同一IP地址的客户端,当后端服务器列表不变时,它每次都会映射到同一台后端服务器进行访问。
加权轮询法:不同的后端服务器可能机器的配置和当前系统的负载并不相同,因此它们的抗压能力也不相同。给配置高、负载低的机器配置更高的权重,让其处理更多的请;而配置低、负载高的机器,给其分配较低的权重,降低其系统负载,加权轮询能很好地处理这一问题,并将请求顺序且按照权重分配到后端。
加权随机法:与加权轮询法一样,加权随机法也根据后端机器的配置,系统的负载分配不同的权重。不同的是,它是按照权重随机请求后端服务器,而非顺序。
每一个服务最终会有大量的配置,并且每个服务都可能部署在多台机器上。我们经常需要变更配置,我们可以让每个服务在配置中心获取自己的配置。
在微服务的架构中,微服务之间通过网络进行通信,存在相互依赖,当其中的一个服务不可用时,有可能会造成雪崩效应。要防止这样的情况,必须要有容错机制来保护服务。
服务熔断: 设置服务的超时,当被调用的服务经常失败到达某个阈值,我们可以开启短路保护机制,后来的请求不再去调用这个服务。本地直接返回默认的数据。
服务降级: 在运维期间,当系统处于高峰期,系统资源紧张,我们可以让非核心的业务降级运行。降级:某些业务不处理,或者简单处理[抛异常、返回NULL、调用mock数据、调用Fallback处理逻辑]。
在微服务的架构中,API Gateway作为整体架构的重要组件,他抽象了微服务中都需要的公共功能,同时提供了客户端的负载均衡,服务自动熔断,灰度发布、统一认证、限流流控、日志统计等丰富功能,帮助我们解决很多API管理难题。
需要使用虚拟机安装相关的软件和部署集群
VMWare虚拟机安装Linux教程 | The Blog (qingling.icu)
将Linxu的IP地址固定下来
Linux设置静态IP | The Blog (qingling.icu)
包含docker安装、开启开机自启动、配置镜像加速服务
Docker容器化技术 | The Blog (qingling.icu)
下载mysql的镜像文件 这里以下载mysql5.7为例 |
下载redis的镜像文件 直接下载最新的 |
其中Java使用的是java8及以上
Maven 配置阿里云的镜像 profiles
开发环境的搭建 | The Blog (qingling.icu)
IDEA中修改使用自己安装的Maven
IDEA插件:Lombok|MybatisX
VSCode插件:
官网下载:https://git-scm.com/downloads
以下的操作在下载安装完毕之后进行 |
常用命令
我们是先从码云上初始化一个项目 然后拉取到本地
创建一个仓库
复制刚才创建仓库的地址
将工程导入到IDEA中
导入成功
后台管理系统使用的是人人开源提供的一个脚手架:
仓库的地址信息:
仓库主页:https://gitee.com/renrenio
后端renren-fast:https://gitee.com/renrenio/renren-fast.git
前端renren-fast-vue:https://gitee.com/renrenio/renren-fast-vue.git
将其作为一个模块集成到gulimall中
同时创建好这个脚手架需要的数据库
修改配置文件中数据库账号密码ip的设置,启动项目看有没有报错
安装node.js 和 npm包管理工具 (node.js自带npm包管理工具)
检查是否安装成功 |
将项目导入到vscode中,并安装依赖
安装依赖的命令 |
下载完成之后,运行前端的项目
运行命令 |
运行成功过之后访问 http://localhost:8001
成功的页面显示如下
**使用账号:admin密码:admin 登录 **
可以登录成功并显示以下的页面说明前后端的联调通过
人人开源代码生成器的地址:https://gitee.com/renrenio/renren-generator.git
集成代码生成器
修改代码生成器的配置文件
1.修改yml配置文件中数据库的连接信息
2.修改properties中与代码生成相关的配置信息
配置举例 根据不同的模块 不同的数据库 配置不同
#代码生成器配置信息 |
3.运行项目
通过http://localhost:81 访问
4.使用生成代码的功能生成代码
解压之后的文件如下
压缩包中的文件如下所示
由于项目中没有使用shiro安全框架,我们注释掉安全框架的注解生成模板
配置每个模块的配置文件
server: |
测试生成的代码
1.修改代码生成器的配置文件generator.properties
#代码生成器配置信息 |
2修改要生成增删改查代码的数据库的连接配置application.yml
只用配置数据库的连接信息即可
server: |
3.启动服务生成代码,将生成的代码解压并粘贴到对应的模块中去
将main这个目录替换原来模块中的main目录即可,sql文件暂时不用管
4.创建模块的配置文件
server: |
5.启动测试生成的代码
在启动的时候,我们要处理好代码依赖的包,这些包都存在与生成代码的模块中,我们在生成代码的模块中复制过来即可,然后手动的引入相应的包,处理好报错信息。
C:. |
SpringCloud Alibaba致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务必须的组件,方便开发者通过SpringCloud编程模型轻松使用这些组件来开发分布式的应用服务。
依托SpringCloud Alibaba,你只需要添加一些注解和少量的配置,就可以将SpringCloud应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。
github的地址:https://github.com/alibaba/spring-cloud-alibaba
SpringCloud的不足:
SpringCloud Alibaba的优势:
阿里使用过的组件经历了考验,性能强悍,设计合理,开源成套的搭配和可视化的界面给开发带来极大的便利,搭建简单,学习成本低。
SpringCloud Alibaba - Nacos :注册中心(服务发现/注册)
SpringCloud Alibaba - Nacos :配置中心(动态的配置管理)
SpringCloud - Ribbon: 负载均衡
SpringCloud - Feign: 声明式HTTP客户端(远程调用服务/服务调用)
SpringCloud Alibaba - Sentinel:服务容错(限流、降级、熔断)
SpringCloud - Gateway: API网关(webflux编程模式)
SpringCloud - Sleuth:调用链监控
SpringCloud Alibaba - Seata:原Fescar,即分布式事务解决方案
在common模块中添加如下的依赖
<dependencyManagement> |
简介
Nacos 是阿里巴巴推出来的一个新开源项目,这是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。
Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施
Nacos 支持如下核心特性
1)服务发现: 支持 DNS 与 RPC 服务发现,也提供原生 SDK 、OpenAPI 等多种服务注册方式和 DNS、HTTP 与 API 等多种服务发现方式。
2)服务健康监测: Nacos 提供对服务的实时的健康检查,阻止向不健康的主机或服务实例发送请求。
3)动态配置服务: Nacos 提供配置统一管理功能,能够帮助我们将配置以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置。
4)动态 DNS 服务: Nacos 支持动态 DNS 服务权重路由,能够让我们很容易地实现中间层负载均衡、更灵活的路由策略、流量控制以及数据中心内网的简单 DNS 解析服务。
5)服务及其元数据管理: Nacos 支持从微服务平台建设的视角管理数据中心的所有服务及元数据,包括管理服务的描述、生命周期、服务的静态依赖分析、服务的健康状态、服务的流量管理、路由及安全策略、服务的 SLA 以及最首要的 metrics 统计数据。
使用nacos
使用之前需要下载nacos并启动nacos的服务
在common工程中添加依赖
<dependency> |
配置中心中配置Nacos的地址
spring: |
在每个启动类上使用注解开启服务的注册与发现功能
//开启服务的注册与发现功能 |
查看注册服务列表
通过访问: http://localhost:8848/nacos |
简介
Feign是一个声明式的HTTP客户端,它的目的是让远程调用变得更简单。Feign提供了HTTP请求的模板,通过**编写简单的接口和插入注解**,就可以定义好HTTP请求的参数、格式、地址等信息。
Feign整合了Ribbon(负载均衡)和Hystrix(服务熔断),可以让我们不再需要显式的使用这两个组件。
使用
引入依赖,每个模块都需要引入这个依赖
<dependency> |
编写代码测试远程调用,这里我们以用户模块调用优惠卷模块来获取用户的拥有的优惠卷为例来测试远程调用。
会员模块提供获取用户名下优惠卷的方法
/** |
用户模块远程调用优惠卷模块
远程调用别的服务端步骤
引入open-feign的依赖
调用端编写一个接口,告诉SpringCloud这个接口需要调用远程服务,
声明接口的每一个方法都是调用哪个远程服务的那个请求
开启远程调用的功能,在调用端的启动类上添加**@EnableFeignClients**注解
在用户模块创建一个Feign包(维护的时候见名知义),声明调用的接口,在启动类上添加注解
//注解 |
package com.atguigu.gulimall.member.feign; |
测试远程调用
|
测试的时候出现的小问题
<!--No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalanc |
处理完上面的报错之后,浏览器访问 http://localhost:8000/member/member/coupons ,显示如下的内容,说明远程调用成功
简介
简介见上面的nacos注册中心,这里的Nacos配置中心和上面的nacos注册中心是同一个东西
使用
1.在common中引入nacos配置中心的依赖,和上面的注册中心的依赖不一样
<dependency> |
2.给需要配置中心管理的模块下创建配置文件bootstrap.properties,这个文件会优先于application.properties读取
#服务名 |
3.测试
3.1.在application.properties配置文件中添加如下的配置
coupon.user.name=zhansan |
3.2 编写接口并访问测试
|
测试的结果如下:
3.3 需求:应用不重启,实时的修改配置文件中的值
3.3.1 在Nacos配置中心中创建一个名为 应用名.properties(例如:gulimall-coupon.properties) 的配置文件
进入nacos管理的可视化页面,点击配置列表,点击 + ,新建一个配置 ,同时将配置文件中name值改为lihua
发布之后,就可以在配置列表中看到这个配置了
重启项目测试,配置中心的配置是否生效
没有生效的话,添加这个依赖,springBoot2.4以上的版本需要
<dependency> |
生效的话,这时刷新页面,可以看到name已经由zhangsan变成lihua了
3.3.2 想让配置文件实时生效的话,这时我们还要添加以下的注解,然后重启应用,让注解生效
注意: @RefreshScope 这里的注解添加在Controller上,不是添加在启动类上
//给需要配置中心统一配置的模块的Controller上上添加@RefreshScope的注解 |
最终测试
这时我们在配置中心中修改配置并发布,这些配置就可以实时的生效了
我们在配置中心修改name和age的值,并发布
这时刷新请求接口的页面,我们可以看到页面中的数据发生了实时的变化
总结
实现nacos配置中心统一配置的流程
引入依赖
<dependency> |
创建一个bootstrap.properties的配置文件,添加如下的内容:
#服务名 |
需要给配置中心中新建一个数据集(Data Id)为 应用名.properties(例如:gulimall-coupon.properties)的配置。默认规则: 应用名.properties
给 应用名.properties 添加任何配置 这些配置都可以在配置中心配置生效
在Controller上添加**@RefreshScope**注解就可以实时的动态刷新配置,配置中心更新了配置并发布了之后,配置实时的生效。
注意
如果配置中心和当前应用的配置文件都配置了相同的项,优先使用配置中心的配置
细节
命名空间: 配置隔离
默认:public(保留空间)
1.可以分别设置开发、测试、生产环境的命名空间,不同的环境下使用不同的命名空间中的配置
通过命令空间实现环境隔离
在bootstrap.properties设置使用哪个命名空间,可以在配置文件中添加如下的配置
# 切换名称空间 |
2.可以为每一个微服务可以创建自己的命名空间,让每个服务的配置放在每个服务的命名空间下
配置集:所有的配置的集合
配置集ID: 类似文件名
Data Id: 就是配置集ID
配置分组:
默认所有的配置集都属于 : DEFAULT_GROUP
例如在生产环境的命名空间下,淘宝的配置分组有以下几个: 双十一,688,平时
#指定配置分组 |
Tips:同一个命名空间下可能会有不同的配置分组
每个微服务创建自己的命令空间,使用配置分组区分环境 dev、test、prod
从配置中心中读取多个配置集(配置文件):
可以在bootstrap.properties添加以下的配置
#第一个配置文件 |
总结:
1.微服务的任何配置信息,任何配置文件都可以放在配置中心中
2.加载配置中心的哪些配置文件,只需要在bootstrap.properties说明加载哪些配置文件即可
3.如果配置中心和当前应用的配置文件都配置了相同的项,优先使用配置中心的配置
简介
网关最为流量的入口,常用的功能包括**路由转发**、**权限校验**、**限流控制**等。SpringCloud gateway是springcloud官方提供的第二代网关框架,取代了Zull网关。
网关的功能
使用
创建一个springboot initializr的工程 ,勾选上Gateway的依赖
在网关模块的pom.xml文件中添加上对common工程的依赖
在网关的启动类上添加上**@EnableDiscoveryClient**的注解,把网关注册到注册中心
配置nacos的地址和服务名信息
#配置网关的地址 |
创建bootstrap.properties配置文件,将网关模块的配置通过交给nacos配置中心管理
spring.application.name=gulimall-gateway |
启动测试
启动之后出现的问题及解决方案:
a.单元测试@Test注解爆红,是我们更换了springboot的版本的原因,删除爆红的地方,重新导包即可
b.数据库相关的报错,是因为网关中没有使用数据库,就没有配置数据库的配置,我们通过@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})解决。
转发测试
测试案例:我们在浏览器的地址栏输入localhost:88?url=baidu,就给我们转发到百度,输入localhost:88?url=qq就给我们转发到腾讯qq
实现:创建一个yml的配置文件,在配置文件中添加如下的配置
spring: |
前端基础知识 | The Blog (qingling.icu)
前后端技术类比
提示:所有的API路径一定要和老师的一样,不然在后面会吃亏!!!
controller
/** |
service
package com.atguigu.gulimall.product.service.impl; |
前端访问的地址
前端代码中的配置
修改访问的基准路径,统一访问网关,再由网关做请求的转发,转发到指定的服务中去(renren-fast-vue\static\config\index.js文件)
/** |
将renren-fast注册到注册中心中去
修改pom.xml中的依赖
<!-- 修改springBoot的版本 --> |
在启动类上添加@EnableDiscoveryClient注解,在配置文件中添加上nacos地址的配置
cloud: |
修改网关中的断言,让前台的请求转发到正确的路径上
spring: |
解决登录的时候的跨域问题
浏览器中的跨域的报错信息
跨域问题的介绍
跨域流程
跨域的解决方案
跨域的配置
添加跨域的配置类
package com.eatguigu.gulimall.gateway.config; |
注掉renren-fast工程中的跨域配置,避免产生两次跨域的问题
/** |
再次测试,成功的进入后台管理系统
新增一个商品系统的目录,并在该目录下创建一个分类维护的二级分类
1.添加一个一级分类
2.在商品系统的一级分类下添加一个分类维护的二级分类
3.路径的问题
4.路径与文件的对应关系
5.发送请求的示例
<template> |
6.树形显示商品的分类信息
前端修改网关商品模块的路由(注意:匹配精确的路由放在模糊的路由的上面)
spring: |
前端发送请求,正确地获取数据
在页面上显示数据
Element的配置
前端代码
<template> |
显示的效果
前端页面修改
<template> |
配置逻辑删除
配置全局的逻辑删除规则
mybatis-plus: |
在showStatus上添加上逻辑删除的注解
后端代码的实现
controlller
/** |
service 这里后面需要优化
/** |
前端代码
<template> |
后端代码
/** |
实现的效果
前端的代码(太复杂了,没有写完)
<template> |
老师的前端的代码
<template> |
后端的代码
/** |
这里前端的代码是和先前生成的后端的代码一起生成的
商品品牌的列表显示
优化之后的前端的代码 (表头的优化 需要按钮显示的使用按钮进行显示)
<template> |
后端代码(修改品牌的显示状态)
/** |
品牌添加功能
阿里云对象存储OSS | The Blog (qingling.icu)
前端的代码
品牌管理的列表页面
<template> |
品牌管理的添加和修改组件
<template> |
后端的代码
图片上传相关的后台接口
配置文件
apllication.yml
spring: |
Controller
package com.atguigu.gulimall.thirdparty.controller; |
配置网关,通过网关访问到该接口
- id: third_party_route |
数据校验 | The Blog (qingling.icu)
1.依赖文件
<dependency> |
2.在实体类上添加上校验的规则
package com.atguigu.gulimall.product.entity; |
3.接口上开启检验,并自定义校验出错时的返回值
/** |
返回的json格式示例
统一异常处理 | The Blog (qingling.icu)
统一异常返回状态码
package com.atguigu.common.exception; |
创建异常处理类
package com.atguigu.gulimall.product.exception; |
功能截图
后端代码的实现
查询品牌和分类的关联关系
/** |
新增品牌和商品的关联关系
controller
/** |
service
/** |
注意:这里品牌和关联分类之间的关系表中使用了冗余的字段,所以在修改品牌的时候,关系表中的字段也要修改一下
|
关联的分类也是冗余的字段 也要同步修改
/** |
功能实现之后的截图
创建属性管理的菜单(通过sql导入所有的菜单,导入所有以后需要使用的菜单)
资料中的sys_menus.sql文件保存的是所有的菜单信息
谷粒商城的接口的在线文档:https://easydoc.net/s/78237135
Object的划分
PO 持久对象
PO就是对应数据库中某个表的一条记录,多个记录可以用PO的集合。PO中应该不包含对数据库的操作。
DO 领域对象
就是从现实世界中抽象出来的有形或者无形的业务实体。
TO 数据传输对象
不同的应用程序之间传输的对象
DTO 数据传输对象
泛指展示层与服务层之间的数据传输对象
VO 值对象
视图对象 接收页面传入过来的数据,封装对象;将业务处理完成的对象,封装成页面需要的数据
BO 业务对象
POJO 简单无规则的java对象
DAO 数据访问对象
实现一个分类 联动的显示相应的分组信息
抽取商品分类的组件
<template> |
属性分组页面
<template> |
页面效果
父子组件中数据的传递
子组件给父组件传递的数据,事件传递,子组件给父组件发送一个事件,事件传递
子组件调整
<template> |
父组件接受相应的数据
<template> |
后端代码
|
属性分组的添加功能
前端代码
<template> |
后端代码
在商品分类的children上加上@JsonInclude注解,解决后面的级联选择多出一个空白的选择的问题
|
修改的时候,级联选择部分数据的回显
/** |
属性关联商品的规格参数和基本属性信息
功能描述
查询商品下关联的属性分组信息
查询商品关联属性分组的后端关键代码
/** |
移除关联属性分组的功能后端的关键代码
service层的代码
/** |
dao层的代码
使用动态的sql批量的删除属性和分组的关联关系
<!--void deleteBatchRelation(List<AttrAttrgroupRelationEntity> entities);--> |
查询属性分组没有关联的属性
/** |
给属性关联相关的规格参数和销售属性
|
功能截图
新增功能的后端关键代码
/** |
查询列表后端的关键代码
这里使用的stream流处理数据,没有使用多表联合查询来查询数据
|
规格参数修改功能后端的关键代码
修改前的数据回显
查询属性的详情信息
/** |
修改的时候页面回显的效果
修改功能后端的关键代码
/** |
分类属性列表显示
controller层的代码
/** |
service层的代码
|
完整的功能展示
条件查询功能(Spu检索)
/** |
实现的功能
前端显示时间格式的数据有问题
配置文件中的配置
jackson: |
商品规格的回显
/** |
修改商品的规格
/** |
实现的功能
中间遇到的问题,点击规格,页面显示400的问题
解决方案
第一步:在gulimall-admin数据库中执行以下的sql语句
INSERT INTO sys_menu (menu_id, parent_id, name, url, perms, type, icon, order_num) VALUES (76, 37, '规格维护', 'product/attrupdate', '', 2, 'log', 0); |
第二步:将前端src/router/index.js替换成如下的内容
/** |
根据商品的分类联动的查询商品的品牌
后端关键部分的代码
/** |
根据JSON数据生成实体类
生成工具: https://www.bejson.com/
前端提交的发布商品的详细信息
{ |
下载生成的实体类代码
生成的实体类(将生成的实体类复制到项目中指定的位置)
发布商品信息(核心业务)
后端关键代码
/** |
最终的实现效果
Sku检索
/** |
实现的效果
仓库的分页加条件查询
|
实现的效果
商品库存的条件查询
/** |
实现的效果
采购需求的分页加条件查询
/** |
实现的效果
合并采购单
业务需求介绍
获取没有领取的采购单,将采购需求合并到这个采购单
/** |
合并采购需求
/** |
领取采购单
/** |
完成采购的功能
|
修改库存的功能
|
sql语句
<!--void addStock(@Param("skuId") Long skuId, @Param("wareId") Long wareId, @Param("skuNum") Integer skuNum);--> |
个人总结:
详细的教程
ElasticSearch | The Blog (qingling.icu)
1.商品Mapping
创建一个product索引和指定映射关系
PUT product |
2.上架功能后端代码
所需的实体类
package com.atguigu.common.to.es; |
商品上架的后端关键代码
/** |
/** |
<select id="selectSearchAttrs" resultType="java.lang.Long"> |
/** |
/** |
项目的页面视图部署在对应的微服务项目的静态资源中
添加thymeleaf的依赖
<!-- thymeleaf--> |
将资料中的静态资源放在微服务工程的对应位置,并在配置文件中加入对应的配置
首页页面
在index.html上加上thymeleaf的名称空间
<html xmlns:th="http://www.thymeleaf.org"> |
修改页面的时候需要频繁的重启项目,使用热更新来解决该问题
导入devtools的依赖
<!--devtools--> |
重启项目之后会显示如下的效果
这时我们在每次修改页面之后使用Ctrl+Shift+F9键可以热更新
首页面的跳转以及一级分类的显示
/** |
用于用户端首页面三级分类封装的数据
package com.atguigu.gulimall.product.vo; |
前端相关的修改
<li th:each=" category:${categorys}"> |
controller层代码
/** |
service层代码
|
页面实现的效果
1.安装SwitchHosts软件
可以快捷的编辑操作hosts文件
打开的时候以管理员的身份打开
在添加的方案中添加如下的内容,点击右下角的保存按钮保存(保存失败的更改hosts文件的权限,将只读勾选掉)
192.168.195.100 gulimall.com |
保存成功之后可以直接访问域名,解析到虚拟机的ip
Tips:有科学上网的测试的时候要先关闭科学上网的软件
安装了ES的访问gulimall.com:9200可以看到相应的内容
2.正式搭建项目的域名访问环境
nginx的配置文件
配置nginx
切换到nginx挂载在本机的配置文件所在的目录 |
niginx的配置文件如下
server { |
这时我们在访问gulimall.com的时候就可以访问一下的页面了
3.让nginx代理到网关
在nginx.conf配置文件中加上如下的配置
#配置上游服务器 |
修改gulimall.conf中的配置
server { |
在网关的配置文件中加上一下的配置
# nginx代理相关的断言 |
这时我们重启网关的时候发现无法访问到相应的页面 是由于nginx访问网关的时候丢了host
这时我们需要加上如下的配置
proxy_set_header Host $host; |
压力测试与性能监控 | The Blog (qingling.icu)
JMeter压力测试
性能监控
jconsole |
网关的优化、数据库索引的优化、thymeleaf的优化、静态资源的优化、nginx动静分离
为了系统性能的提升,我们一般都会将部分数据放入缓存中,加速访问。而 db 承担数据落盘工作。
哪些数据适合放入缓存?
1.即时性、数据一致性要求不高的
2.访问量大且更新频率不高的数据(读多,写少)
本地缓存
分布式缓存
1.安装Redis
2.项目中使用Redis
SpringBoot整合Redis | The Blog (qingling.icu)
2.1 引入依赖
<dependency> |
2.2 添加配置
spring: |
2.3 测试使用
|
lettuce堆外内存溢出bug
产生原因:
1)、springboot2.0以后默认使用 lettuce
作为操作redis的客户端,它使用netty进行网络通信
2)、lettuce的bug导致netty堆外内存溢出。netty如果没有指定堆外内存,默认使用Xms的值,可以使用-Dio.netty.maxDirectMemory进行设置
解决方案:由于是lettuce的bug造成,不要直接使用-Dio.netty.maxDirectMemory去调大虚拟机堆外内存,治标不治本。
修改依赖文件,使用jedis作为客户端工具
<dependency> |
缓存穿透
缓存雪崩
缓存击穿
分布式下如何加锁?
锁-时序问题
分布式锁演进-基本原理
分布式锁的最终形态
github地址: https://github.com/redisson/redisson
1.引入依赖
<!--redisson--> |
2.配置redisson
package com.atguigu.gulimall.product.config; |
3.测试使用
|
4.简单的使用分布式锁
上一个线程加锁之后没有解锁的话,后面的线程会一直等待前面的线程释放锁,自己再加锁。
|
my-lock对应的值会跟随线程的变化而变化
读写锁
/** |
双写模式
失效模式
缓存数据一致性-解决方案
缓存数据一致性-解决-Canal
1、简介
Spring 从 3.1 开始定义了 org.springframework.cache.Cache和 org.springframework.cache.CacheManager 接口来统一不同的缓存技术;并支持使用 JCache(JSR-107)注解简化我们开发;
Cache 接口为缓存的组件规范定义,包含缓存的各种操作集合;
Cache 接 口 下 Spring 提 供 了 各 种 xxxCache 的 实 现 ; 如 RedisCache , EhCacheCache ,ConcurrentMapCache 等;
每次调用需要缓存功能的方法时,Spring 会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
使用 Spring 缓存抽象时我们需要关注以下两点;
1、确定方法需要被缓存以及他们的缓存策略
2、从缓存中读取之前缓存存储的数据
2.以前的笔记教程
SpringBoot整合Redis | The Blog (qingling.icu)
3.项目中整合SpringCache简化开发
3.1.引入依赖
<!--我们使redis作为缓存的使用场景,在前面我们还要引入redis的依赖--> |
3.2.编写配置文件
#使用缓存的类型 |
3.3.测试使用缓存
常用的缓存注解
@Cacheable 触发将数据保存到缓存的操作
@CachePut 不影响方法执行更新缓存
@CacheEvict 触发将数据从缓存中删除的操作
@Caching 组合多个缓存操作
@CacheConfig 在类级别上共享缓存的相关配置
1.在启动类上添加缓存的注解
//开启缓存功能 |
2.测试使用
//每一个需要缓存的数据需要我们指定放到那个名字的缓存[缓存的分区(按照业务类型分)] |
自定义数据的存储
设置自定义的key值
//自定义key值 |
tips: #root.method.name是SpEL表达式
//使用方法名作为key值 |
设置缓存数据的存活时间
#设置一个小时的存活时间 |
将数据保存为json的格式
添加配置类
package com.atguigu.gulimall.product.config; |
效果
其它的常用配置
#缓存的前缀,如果指定了前缀就使用指定的前缀,没有指定前缀就使用缓存的名字作为前缀 |
Nginx的配置
server { |
网关中配置断言
#搜索 |
首页无法跳转到搜索页的原因,gumall改为gulimall即可
搜索框跳转到搜索页,修改页面中的代码
检索条件的VO
package com.atguigu.gulimall.search.vo; |
检索的结果
package com.atguigu.gulimall.search.vo; |
未完待续(学习到177)……
]]>