web前端
1.Web标准
- Web标准也称为网页标准,由一系列的标准组成
- 三个组成部分:
- HTML:负责网页的结构(页面元素和内容)
- CSS:负责网页的表现(页面元素的外观、位置等页面样式,如颜色、大小等)
- JavaScript:负责网页的行为(交互效果)
2.HTML、CSS
什么是HTML、CSS?
- HTML(HyperText Markup Language):超文本标记语言
- 超文本:超越了文本的限制,比普通文本更强大。除了文字信息,还可以定义图片、音频、视频等内容
- 标记语言:由标签构成的语言
- HTML标签都是预定义好的。例如:使用展示超链接,使用
展示图片,
- HTML代码直接在浏览器中运行,HTML标签由浏览器解析
- HTML标签都是预定义好的。例如:使用展示超链接,使用
- CSS(Cascading Style Sheet):层叠样式表,用于控制页面的样式
案例:HTML快速入门
- 新建文本文件,后缀名改为.html
- 编写HTML结构标签
- 在<body>中填写内容
小结
- HTML结构标签
<html>
<head>
<title>HTML</title>
</head>
<body>
</body>
</html>
- 特点
- HTML标签不区分大小写
- HTML标签属性值单双引号都可以
- HTML语法松散
基础标签&样式
标题排版
1.标题标签
- 标签:<h1>…</h1>(h1->h6重要程度依次降低)
- 注意:HTML标签都是预定义好的,不能自己随意定义
2.水平线标签:<hr>
3.图片标签:
<img src="..." width="..." height="...">
- 绝对路径
- 绝对磁盘路径(本地文件路径)
- 绝对网络路径(从互联网上加载并展示相应文件,访问必须联网)
- 相对路径:从当前文件开始查找
- ./:代表当前目录,./可以省略
- ../:代表上一级目录,不可省略
示例代码
<!-- 声明文档类型为HTML -->
<!DOCTYPE html>
<html lang="en">
<head>
<!-- 字符集为UTF-8 -->
<meta charset="UTF-8">
<!-- 浏览器兼容性 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 标题 -->
<title>焦点访谈:中国底气</title>
</head>
<body>
<!-- img标签:
src:图片资源路径
width:宽度(px:像素;%,相对于父元素的百分比)
height:高度 (px:像素;%,相对于父元素的百分比)
一般只指定一种长度,另外一个长度会等比例缩放
路径书写方式:
绝对路径:
1.绝对磁盘路径(本地文件路径)
2.绝对网络路径(从互联网上加载并展示相应文件,访问必须联网)
相对路径:
./:代表当前目录,./可以省略
../:代表上一级目录,不可省略
-->
<img src="img/news_logo.png"> 新浪政务 > 正文
<h1>焦点访谈:中国底气</h1>
<hr>
2023年03月02日 21:50 央视网
<hr>
</body>
</html>
标题样式
- CSS引入方式
- 行内样式:<h1 style="...">
- 内嵌样式:
- 外联样式:xxx.css
- 颜色标识
- 关键字:red、green…
- rgb表示法:rgb(255, 0, 0)、rgb(134, 100, 89)
- 十六进制:#ff0000、#cccccc、#ccc
- 颜色属性
- color:设置文本内容的颜色
- 标签
- 是一个在开发网页时大量会用到的没有语义的布局标签
- 特点:一行可以显示多个(组合行内元素),宽度和高度默认由内容撑开
- CSS选择器
- 元素选择器:标签名 {…}
- id选择器:#id属性值 {…}
- 类选择器:.class属性值 {…}
- 优先级:id选择器 > 类选择器 > 元素选择器
- CSS属性
- color:设置文本的颜色
- font-size:字体大小
示例代码
<!-- 声明文档类型为HTML -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>焦点访谈:中国底气</title>
<!-- 方式二:内嵌样式,对于当前页面有效,只要是当前页面的h1标题,都应用这个样式 -->
<style>
h1 {
/* color: red; */
color: #4D4F53;
}
/* 元素选择器 */
/* span {
color: #968d92;
} */
/* 类选择器 */
/* .cls {
color: #968d92;
} */
/* ID选择器 */
#time {
color: #968d92;
/* 设置字体大小 */
font-size: 13px;
}
</style>
<!-- 方式三:外联样式,将样式单独定义在一个css文件中 -->
<!-- <link rel="stylesheet" href="css/news.css"> -->
</head>
<body>
<img src="img/news_logo.png"> 新浪政务 > 正文
<!-- 方式一:行内样式(不推荐,仅仅针对当前标签有效) -->
<!-- <h1 style="color: red;">焦点访谈:中国底气</h1> -->
<h1>焦点访谈:中国底气</h1>
<hr>
<span class="cls" id="time">2023年03月02日 21:50</span> <span class="cls">央视网</span>
<hr>
</body>
</html>
超链接
- 超链接
- CSS属性
- text-decoration:规定添加到文本的修饰,none表示定义标准的文本(如去除超链接的下划线等)
- color:定义文本颜色
示例代码
<!-- 声明文档类型为HTML -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>焦点访谈:中国底气</title>
<style>
h1 {
color: #4D4F53;
}
#time {
color: #968d92;
/* 设置字体大小 */
font-size: 13px;
}
a {
color: black;
/* 设置文本为标准文本,去除下划线 */
text-decoration: none;
}
</style>
</head>
<body>
<img src="img/news_logo.png"> <a href="http://gov.sina.com.cn/" target="_self">新浪政务</a> > 正文
<h1>焦点访谈:中国底气</h1>
<hr>
<span class="cls" id="time">2023年03月02日 21:50</span> <span> <a href="https://news.cctv.com/2023/03/02/ARTIUCKFf9kE9eXgYE46ugx3230302.shtml" target="_blank">央视网</a> </span>
<hr>
</body>
</html>
正文排版
- 音频、视频标签
<audio>、<video> 注意使用这两个标签时需要加上controls,将播放键显示出来
- 换行、段落标签
换行:<br> ;段落:<p>
- 文本加粗标签
<b> <strong>两种标签
- CSS样式
- line-height:设置行高
- text-indent:定义第一个行内容的缩进
- text-align:规定元素中的文本的水平对齐方式
- 注意
在HTML中无论输入多少个空格,都只会显示一个。要显示多个空格,可以使用空格占位符: 
示例代码
<!-- 声明文档类型为HTML -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>焦点访谈:中国底气</title>
<style>
h1 {
color: #4D4F53;
}
#time {
color: #968d92;
/* 设置字体大小 */
font-size: 13px;
}
a {
color: black;
/* 设置文本为标准文本,去除下划线 */
text-decoration: none;
}
p {
/* 设置首行缩进 */
text-indent: 35px;
/* 设置行高 */
line-height: 40px;
}
#plast {
/* 对齐方式 */
text-align: right;
}
</style>
</head>
<body>
<img src="img/news_logo.png"> <a href="http://gov.sina.com.cn/" target="_self">新浪政务</a> > 正文
<h1>焦点访谈:中国底气</h1>
<hr>
<span class="cls" id="time">2023年03月02日 21:50</span> <span> <a href="https://news.cctv.com/2023/03/02/ARTIUCKFf9kE9eXgYE46ugx3230302.shtml" target="_blank">央视网</a> </span>
<hr>
<!-- 正文 -->
<!-- 定义视频 -->
<video src="video/1.mp4" controls width="950px"></video>
<!-- 定义音频 -->
<!-- <audio src="audio/1.mp3" controls></audio> -->
<p>
<strong>央视网消息</strong> (焦点访谈):党的十八大以来,以习近平同志为核心的党中央始终把解决粮食安全问题作为治国理政的头等大事,重农抓粮一系列政策举措有力有效,我国粮食产量站稳1.3万亿斤台阶,实现谷物基本自给、口粮绝对安全。我们把饭碗牢牢端在自己手中,为保障经济社会发展提供了坚实支撑,为应对各种风险挑战赢得了主动。连续八年1.3万亿斤,这个沉甸甸的数据是如何取得的呢?
</p>
<p>
人勤春来早,春耕农事忙。立春之后,由南到北,我国春耕春管工作陆续展开,春天的田野处处生机盎然。
</p>
<img src="img/1.jpg">
<p>
今年,我国启动了新一轮千亿斤粮食产能提升行动,这是一个新的起点。2015年以来,我国粮食产量连续8年稳定在1.3万亿斤以上,人均粮食占有量始终稳稳高于国际公认的400公斤粮食安全线。从十年前的约12200亿斤到2022年的约13700亿斤,粮食产量提高了1500亿斤。
</p>
<img src="img/2.jpg">
<p>
中国式现代化一个重要的中国特色是人口规模巨大的现代化。我们粮食生产的发展,意味着我们要立足国内,解决14亿多人吃饭的问题。仓廪实,天下安。保障粮食安全是一个永恒的课题,任何时候都不能放松。在以习近平同志为核心的党中央坚强领导下,亿万中国人民辛勤耕耘、不懈奋斗,我们就一定能够牢牢守住粮食安全这一“国之大者”,把中国人的饭碗牢牢端在自己手中,夯实中国式现代化基础。
</p>
<p id="plast">
责任编辑:王树淼 SN242
</p>
</body>
</html>
页面布局
盒子模型
- 盒子:页面中所有的元素(标签),都可以看作是一个盒子,由盒子将页面中的元素包含在一个矩形区域内,通过盒子的视角更方便的进行页面布局
- 盒子模型组成:内容区域(content)、内边距区域(padding)、边框区域(border)、外边距区域(margin)
注意:如果只需要设置某一个方位的边框、内边距、外边距,可以在属性名后加上:“-位置”,如:padding-top、padding-left、padding-right、padding-bottom…
其中margin并不包含在盒子之内
- 布局标签:实际开发网页时,会大量地使用div和span这两个没有语义的布局标签
- 标签:<div>
- 特点
- div标签
- 一行只显示一个(独占一行)
- 宽度默认是父元素的宽度,高度默认由内容撑开
- 可以设置宽高
- span标签
- 一行可以显示多个
- 宽度和高度默认由内容撑开
- 不可以设置宽高
- div标签
示例代码
<!-- 声明文档类型为HTML -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>焦点访谈:中国底气</title>
<style>
h1 {
color: #4D4F53;
}
#time {
color: #968d92;
/* 设置字体大小 */
font-size: 13px;
}
a {
color: black;
/* 设置文本为标准文本,去除下划线 */
text-decoration: none;
}
p {
/* 设置首行缩进 */
text-indent: 35px;
/* 设置行高 */
line-height: 40px;
}
#plast {
/* 对齐方式 */
text-align: right;
}
#center {
width: 65%;
/* 外边距:上、右、下、左 */
/* margin: 0% 17.5% 0% 17.5%; */
/* 若margin只指定两个值,就代表上下外边距为第一个值,左右外边距为第二个值 */
/* auto代表自动计算外边距 */
margin: 0 auto;
}
</style>
</head>
<body>
<div id="center">
<img src="img/news_logo.png"> <a href="http://gov.sina.com.cn/" target="_self">新浪政务</a> > 正文
<h1>焦点访谈:中国底气</h1>
<hr>
<span class="cls" id="time">2023年03月02日 21:50</span> <span> <a href="https://news.cctv.com/2023/03/02/ARTIUCKFf9kE9eXgYE46ugx3230302.shtml" target="_blank">央视网</a> </span>
<hr>
<!-- 正文 -->
<!-- 定义视频 -->
<video src="video/1.mp4" controls width="950px"></video>
<!-- 定义音频 -->
<!-- <audio src="audio/1.mp3" controls></audio> -->
<p>
<strong>央视网消息</strong> (焦点访谈):党的十八大以来,以习近平同志为核心的党中央始终把解决粮食安全问题作为治国理政的头等大事,重农抓粮一系列政策举措有力有效,我国粮食产量站稳1.3万亿斤台阶,实现谷物基本自给、口粮绝对安全。我们把饭碗牢牢端在自己手中,为保障经济社会发展提供了坚实支撑,为应对各种风险挑战赢得了主动。连续八年1.3万亿斤,这个沉甸甸的数据是如何取得的呢?
</p>
<p>
人勤春来早,春耕农事忙。立春之后,由南到北,我国春耕春管工作陆续展开,春天的田野处处生机盎然。
</p>
<img src="img/1.jpg">
<p>
今年,我国启动了新一轮千亿斤粮食产能提升行动,这是一个新的起点。2015年以来,我国粮食产量连续8年稳定在1.3万亿斤以上,人均粮食占有量始终稳稳高于国际公认的400公斤粮食安全线。从十年前的约12200亿斤到2022年的约13700亿斤,粮食产量提高了1500亿斤。
</p>
<img src="img/2.jpg">
<p>
中国式现代化一个重要的中国特色是人口规模巨大的现代化。我们粮食生产的发展,意味着我们要立足国内,解决14亿多人吃饭的问题。仓廪实,天下安。保障粮食安全是一个永恒的课题,任何时候都不能放松。在以习近平同志为核心的党中央坚强领导下,亿万中国人民辛勤耕耘、不懈奋斗,我们就一定能够牢牢守住粮食安全这一“国之大者”,把中国人的饭碗牢牢端在自己手中,夯实中国式现代化基础。
</p>
<p id="plast">
责任编辑:王树淼 SN242
</p>
</div>
</body>
</html>
表格、表单标签
表格标签
- 场景:在网页中以表格形式整齐展示数据,如:班级表
- 标签
注意:必须有border才能看见表格的边框
示例代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTML-表格</title>
<style>
td {
text-align: center; /* 单元格内容居中展示 */
}
</style>
</head>
<body>
<table border="1px" cellspacing="0" width="600px">
<tr>
<th>序号</th>
<th>品牌Logo</th>
<th>品牌名称</th>
<th>企业名称</th>
</tr>
<tr>
<td>1</td>
<td> <img src="img/huawei.jpg" width="100px"> </td>
<td>华为</td>
<td>华为技术有限公司</td>
</tr>
<tr>
<td>2</td>
<td> <img src="img/alibaba.jpg" width="100px"> </td>
<td>阿里</td>
<td>阿里巴巴集团控股有限公司</td>
</tr>
</table>
</body>
</html>
表单标签
- 场景:在网页中主要负责数据采集功能,如登录、注册等数据收集
-
标签:<form>
- 表单项:不同类型的input元素、下拉列表、文本域等
<input>:定义表单项,通过type属性控制输入形式
<select>:定义下拉列表
<textarea>:定义文本域
- 属性
- action:规定当提交表单时向何处发送表单数据,URL
- method:规定用于发送表单数据的方式。GET、POST
注意:表单项必须有name属性才可以提交
示例代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTML-表单</title>
</head>
<body>
<!--
form表单属性:
action: 表单提交的url, 往何处提交数据 . 如果不指定, 默认提交到当前页面
method: 表单的提交方式 .
get: 在url后面拼接表单数据, 比如: ?username=Tom&age=12 , url长度有限制 . 默认值
post: 在消息体(请求体)中传递的, 参数大小无限制的.
-->
<form action="" method="post">
用户名:<input type="text" name="username">
年龄:<input type="text" name="age">
<input type="submit" value="提交">
</form>
</body>
</html>
表单项
<input>:表单项,通过type属性控制输入形式
<select>:定义下拉列表,<option>定义列表项
<textarea>:文本域
示例代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTML-表单项标签</title>
</head>
<body>
<!-- value: 表单项提交的值 -->
<form action="" method="post">
姓名: <input type="text" name="name"> <br><br>
密码: <input type="password" name="password"> <br><br>
性别: <label><input type="radio" name="gender" value="1"> 男</label>
<label><input type="radio" name="gender" value="2"> 女 </label> <br><br>
爱好: <label><input type="checkbox" name="hobby" value="java"> java </label>
<label><input type="checkbox" name="hobby" value="game"> game </label>
<label><input type="checkbox" name="hobby" value="sing"> sing </label> <br><br>
图像: <input type="file" name="image"> <br><br>
生日: <input type="date" name="birthday"> <br><br>
时间: <input type="time" name="time"> <br><br>
日期时间: <input type="datetime-local" name="datetime"> <br><br>
邮箱: <input type="email" name="email"> <br><br>
年龄: <input type="number" name="age"> <br><br>
学历: <select name="degree">
<option value="">----------- 请选择 -----------</option>
<option value="1">大专</option>
<option value="2">本科</option>
<option value="3">硕士</option>
<option value="4">博士</option>
</select> <br><br>
描述: <textarea name="description" cols="30" rows="10"></textarea> <br><br>
<input type="hidden" name="id" value="1">
<!-- 表单常见按钮 -->
<input type="button" value="按钮">
<input type="reset" value="重置">
<input type="submit" value="提交">
<br>
</form>
</body>
</html>
3.JavaScript
引入方式
- 内部脚本引入
- 外部脚本引入
示例代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JS-引入方式</title>
<!-- 内部脚本 -->
<!-- <script>
alert('hello js');
</script> -->
<!-- 外部脚本,且script标签不能自闭和 -->
<script src="js/demo.js"></script>
</head>
<body>
</body>
</html>
JS基础语法
-
书写语法
-
区分大小写
-
每行结尾分号可有可无
-
注释和java一样,单行、多行
-
大括号表示代码块
// 判断 if (cnt == 3) { alert(cnt); }
-
-
输出语句
- 使用 window.alert() 写入警告框
- 使用 document.write() 写入HTML输出
- 使用 console.log() 写入浏览器控制台
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JS-基本语法</title>
</head>
<body>
</body>
<script>
/* alert("JS"); */
// 方式一:弹出警告框
window.alert("hello.js");
// 方式二:写入html页面
document.write("hello.js");
// 方式三:浏览器控制台输出
console.log("hello.js");
</script>
</html>
- 变量
- JavaScript中用 var 关键字(variable的缩写)来声明变量
- JavaScript是一门弱类型语言,变量可以存放不同类型的值
- 变量名需要遵循如下规则
- 组成字符可以是任何字母、数字、下划线或美元符号
- 数字不能开头
- 建议用驼峰命名
var a = 20;
a = "张三";
注意:JavaScript中除了用 var 可以声明变量,还可以用 let 和 const 声明变量
var 声明的变量作用域大,是全局变量,可以重复定义
let 所声明的变量是局部变量,不能重复定义
const 声明的变量是常量,不能改变
示例代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JS-基础语法</title>
</head>
<body>
</body>
<script>
// var定义变量
/* var a = 10;
a = "张三";
alert(a); */
// 特点1:var作用域大,属于全局变量
// 特点2:var可以重复定义
/* {
var x = 1;
var x = "A";
}
alert(x); */
// let定义变量:局部变量,且不能重复定义
/* {
let x = 1;
}
alert(x); */
// const:常量,不能改变
const pi = 3.14;
pi = 3.15
alert(pi);
</script>
</html>
-
数据类型、运算符、流程控制语句
JavaScript中分为:原始类型和引用类型(对象)
-
原始类型
- number:数字(整数、小数、NaN(Not a Number))
- string:字符串,单双引号皆可
- boolean
- null
- undefined:当声明的变量未初始化,该变量的默认值为undefined
使用 typeof 运算符可以获取数据类型
-
运算符
和 java 运算符基本相同,但是多了一个比较运算符“===”
- “===”用于判断是否相等,且不会进行类型转换(即必须要两个变量类型和值都相等,才会判断相等)
-
类型转换
- 字符串转为数字:将字符串字面值转为数字。如果字面值不是数字,则转为NaN
- 其他类型转为boolean
- Number:0和 NaN 为false,其他均为true
- String:空字符串为false,其余为true
- Null 和 undefined:均为false
-
流程控制语句
和 java 相同
-
示例代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JS-运算符</title>
</head>
<body>
</body>
<script>
var age = 20;
var _age = "20";
var $age = 20;
// alert(age == _age);
// alert(age === _age);
// alert(age === $age);
// 类型转换 - 其他类型转为数字
alert(parseInt("12")); // 12
alert(parseInt("12A45")); // 12,从头往后匹配,如果遇到不是数字的内容,则停止匹配
alert(parseInt("A45"));
// 类型转换 - 其他类型转为boolean
// if(0){
// alert("0 转换为false");
// }
// if(NaN){
// alert("NaN 转换为false");
// }
// if(1){
// alert("除0和NaN其他数字都转为 true");
// }
// if(""){
// alert("空字符串为 false, 其他都是true");
// }
// if(null){
// alert("null 转化为false");
// }
// if(undefined){
// alert("undefined 转化为false");
// }
</script>
</html>
-
函数
- js 函数通过
function
关键字进行定义
function functionName(参数1, 参数2...) { // 要执行的代码 }
- 参数不需要类型,返回值也不需要定义类型,直接在函数内部 return 返回即可
- 调用:函数名(实际参数列表)
- 另一种定义方式:
var functionName = function(参数1, 参数2) { // 要执行的代码 }
- 函数可以接受任意个参数,但超出参数个数的参数并没有用
- js 函数通过
-
对象
-
基础对象:Array、String、JSON
-
Array对象的定义、属性和方法
- 定义
var array = new Array(元素列表); // 方式一 var arr = new Array(1,2,3,4); var array = [元素列表]; // 方式二 var arr = [1,2,3,4]; // 数组访问和 java 一样,arr[index] = value // 特点:数组的长度类型均可变 var arr1 = [1,2,3,4]; arr1[10] = 50; // 不会报错 arr1[9] = true; arr1[8] = 'A';
- 属性:length :设置或返回数组中元素的数量
var arr = [1,2,3,4]; for (let index = 0; index < arr.length; i++) { console.log(arr[index]); }
- 方法
var arr = [1,2,3,4]; // forEach:遍历数组中有值的元素,如果数组中有的值未定义,forEach不会遍历 arr.forEach(function(e) { console.log(e); }) // 以上代码用箭头函数可简化 -- 简化函数定义 // 箭头函数:(...) => {...} // 如果给箭头函数起名:var xxx = (...) => {...} arr.forEach((e) => { console.log(e); }) // push:添加元素到数组末尾 arr.push(7,8,9); console.log(arr); // splice:删除元素 arr.splice(start, delcnt); // 从start索引开始删除delcnt个元素 arr.splice(2, 2); // 从索引为2的位置开始,删除2个元素 console.log(arr); // 删掉了3和4
-
String对象的定义、属性和方法
- 定义
var 变量名 = new String("..."); // 方式一 var str = new String("hello string"); var 变量名 = "..."; // 方式二 // 单引号和双引号相同 var str = "hello string"; var str = 'hello string';
- 属性:length:获取长度
var str = "hello string"; console.log(str.length); // 输出为12
- 方法
var str = "hello string"; // charAt:获取指定位置的字符 console.log(charAt(3)); // 输出为l // indexOf:检索子字符串在字符串的位置 console.log("indexOf("lo")"); // 输出3 // trim:去除字符串左右两侧的空格 var str = " hello string "; var s = str.trim(); console.log(s); // 输出为"hello string" // substring(start, end) 截取字符串,含头不含尾 console.log(s.substring(0, 5)); // 输出为"hello"
-
JSON对象
- 定义(JSON本质是字符串,用字符串的方式定义)
var 变量名 = '{"key1": value1, "key2": value2}'; var userStr = '{"name":"Jerry", "age":18, "addr":["北京","上海","西安"]}'; // 此时不能通过 userStr.name 的方式获取 name ,因为 userStr 此时是字符串,并不是对象
- JSON 字符串和 JS 对象的相互转换
var 变量名 = '{"key1": value1, "key2": value2}'; var userStr = '{"name":"Jerry", "age":18, "addr":["北京","上海","西安"]}'; // json字符串--js对象 var obj = JSON.parse(jsonstr); console.log(obj.name); // js对象--json字符串 var jsonStr = JSON.stringify(obj); console.log(jsonStr);
-
BOM对象:浏览器对象模型,允许 JS 和浏览器对话
-
组成:Window浏览器窗口对象、Navigator浏览器对象、Screen屏幕对象、History历史记录对象、Location地址栏对象
-
Window浏览器窗口对象
- 获取:直接使用 window ,其中 window 可以省略
window.alert("hello window"); alert("hello window");
- 属性
- history:对 History 对象的只读引用
- location:用于窗口或框架的 Location 对象
- navigator:对 Navigator 对象的只读引用
- 方法
- alert():显示带有一段信息和一个确认按钮的警告框
- confirm():显示带有一段信息以及确认按钮和取消按钮的对话框
- setInterval():按照指定的周期(以毫秒记)来调用函数或计算表达式
- setTimeout():在指定的毫秒数后调用函数或计算表达式
-
Location地址栏对象
- 获取:使用 window.location 获取,其中 window. 可省略
window.location.属性; location.属性;
-
属性
- href:设置或返回完整的 URL
location.href = "https://www.itcast.cn";
-
示例代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>JS-对象-BOM</title> </head> <body> </body> <script> //获取 window.alert("Hello BOM"); alert("Hello BOM Window"); //方法 //confirm - 对话框 -- 确认: true , 取消: false var flag = confirm("您确认删除该记录吗?"); alert(flag); //定时器 - setInterval -- 周期性的执行某一个函数 var i = 0; setInterval(function(){ i++; console.log("定时器执行了"+i+"次"); },2000); //定时器 - setTimeout -- 延迟指定时间执行一次 setTimeout(function(){ alert("JS"); },3000); //location alert(location.href); location.href = "https://www.itcast.cn"; </script> </html>
-
-
DOM对象:文档对象模型
- 将标记语言的各个组成部分封装为对应的对象
- Document:整个文档对象
- Element:元素对象
- Attribute:属性对象
- Text:文本对象
-
Comment:注释对象
- JS 通过 DOM ,就能够对 HTML 进行操作
- 改变 HTML 元素的内容
- 改变 HTML 元素的样式
- 对 HTML DOM 事件作出反应
- 添加和删除 HTML 元素
<html> <head> <title>DOM</title> </head> <body> <h1>DOM对象标题</h1> <a href="https://itcast.cn">传智教育</a> </body> </html>
-
-
事件监听
- 事件绑定
// 方式1:通过 html 标签中的事件属性进行绑定 <input type="button" onclick="on()" value="按钮1"> <script> function on() { alert('我被点击了!''); } </script> // 方式2:通过 DOM 元素属性绑定 <input type="button" id="btn" value="按钮2"> <script> document.getElementById('btn').onclick=function() { alert('我被点击了!'); } </script>
- 常见事件
4.Vue
Vue 是一套前端框架,免除原生 JavaScript 中的 DOM 操作,简化书写。基于 MVVM(Model-View-ViewModel) 思想,实现数据的双向绑定,将编程的关注点放在数据上
-
Vue 快速入门
- 新建 html 页面,引入 Vue.js 文件
<script src="js/vue.js"></script>
- 在 JS 代码区域,创建 Vue 核心对象,定义数据模型
<script> new Vue({ el: "#app", data: { message: "hello vue!" } }) </script>
- 编写视图
<div id="app"> <input type="text" v-model="message"> </div>
-
Vue 常用指令
- html 标签上带有 v- 前缀的特殊属性,不同指令具有不同含义
- v-bind 和 v-on
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vue-指令-v-bind</title> <script src="js/vue.js"></script> </head> <body> <div id="app"> <a v-bind:href="url">链接1</a> <a :href="url">链接2</a> <input type="text" v-model="url"> </div> </body> <script> //定义Vue对象 new Vue({ el: "#app", //vue接管区域 data:{ url: "https://www.baidu.com" } }) </script> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vue-指令-v-on</title> <script src="js/vue.js"></script> </head> <body> <div id="app"> <input type="button" value="点我一下" v-on:click="handle()"> <input type="button" value="点我一下" @click="handle()"> </div> </body> <script> //定义Vue对象 new Vue({ el: "#app", //vue接管区域 data:{ }, methods: { handle: function(){ alert("你点我了一下..."); } } }) </script> </html>
- v-if 和 v-show
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vue-指令-v-if与v-show</title> <script src="js/vue.js"></script> </head> <body> <div id="app"> 年龄<input type="text" v-model="age">经判定,为: <span v-if="age <= 35">年轻人(35及以下)</span> <span v-else-if="age > 35 && age < 60">中年人(35-60)</span> <span v-else>老年人(60及以上)</span> <br><br> 年龄<input type="text" v-model="age">经判定,为: <span v-show="age <= 35">年轻人(35及以下)</span> <span v-show="age > 35 && age < 60">中年人(35-60)</span> <span v-show="age >= 60">老年人(60及以上)</span> </div> </body> <script> //定义Vue对象 new Vue({ el: "#app", //vue接管区域 data:{ age: 20 }, methods: { } }) </script> </html>
- v-for
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vue-指令-v-for</title> <script src="js/vue.js"></script> </head> <body> <div id="app"> <div v-for="addr in addrs"></div> <hr> <div v-for="(addr,index) in addrs"> : </div> </div> </body> <script> //定义Vue对象 new Vue({ el: "#app", //vue接管区域 data:{ addrs:["北京", "上海", "西安", "成都", "深圳"] }, methods: { } }) </script> </html>
-
Vue 生命周期
一般来说只用掌握 mounted 状态。
mounted:挂载完成,Vue 初始化成功,html 页面渲染成功。(发送请求到服务端,加载数据)
-
前端项目工程化
- 默认首页: index.html、入口文件:main.js
- Vue 的组件文件以
.vue
结尾,每个组件由三个部分组成:<template>、<script>、<style>
。
-
Element-UI 组件库
-
如何使用 Element-UI 组件库
- 在当前工程的目录终端下执行
npm install element-ui@2.15.3
- 在 main.js 中引入 Element-UI 组件库
import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; Vue.use(ElementUI);
-
访问官网复制组件代码即可
-
-
Vue 路由
- 前端路由:URL 中的 hash 与组件之间的对应关系
- Vue Router:Vue 的官方路由
- 组成
VueRouter
:路由器类,根据路由请求在路由视图中动态渲染选中的组件<router-link>
:请求链接组件,浏览器会解析成<a>
<router-view>
:动态视图组件,用来渲染展示与路由路径对应的组件
- 组成
5.Ajax
-
概念:Asynchronous JavaScript And XML,异步的 JavaScript 和 XML
-
作用
- 数据交换:通过 Ajax 可以给服务器发送请求,获取服务器响应数据
- 异步交互,可以在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页的技术,比如搜索联想、用户名是否可用的校验等等
-
Axios:对原生的 Ajax 了封装,简化书写
- 要使用 Axios ,首先需要引入 Axios 的 js 文件,再使用 Axios 发送请求获取响应结果
- Axios 自带了请求方式别名
axios.get(url [, config])
axios.delete(url [, config])
axios.post(url [, data[, config]])
axios.put(url [, data[, config]])
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Ajax-Axios</title> <script src="js/axios-0.18.0.js"></script> </head> <body> <input type="button" value="获取数据GET" onclick="get()"> <input type="button" value="删除数据POST" onclick="post()"> </body> <script> function get(){ // 通过axios发送异步请求-get axios({ method: "get", url: "http://yapi.smart-xwork.cn/mock/169327/emp/list" }).then(result => { console.log(result.data); }) // 使用axios自带的get别名 axios.get("http://yapi.smart-xwork.cn/mock/169327/emp/list").then(result => { console.log(result.data); }) } function post(){ // 通过axios发送异步请求-post axios({ method: "post", url: "http://yapi.smart-xwork.cn/mock/169327/emp/deleteById", data: "id=1" }).then(result => { console.log(result.data); }) // 使用axios自带的post别名 axios.post("http://yapi.smart-xwork.cn/mock/169327/emp/deleteById","id=1").then(result => { console.log(result.data); }) } </script> </html>
关于打包部署
可以用 Nginx 进行本地部署测试
web后端
1.maven
-
maven 是一款用于管理和构建 java 项目的工具
-
maven 的作用
- 依赖管理:方便快捷的管理项目依赖资源(jar 包),避免版本冲突问题
- 同一项目结构:提供标准、统一的项目结构
- 项目构建:标准跨平台(Linux、Windows、MacOS)的自动化项目构建方式
-
仓库:用于存储资源,管理各种 jar 包
- 本地仓库:本地计算机上的仓库目录
- 中央仓库:Maven 官方维护的全球唯一的仓库
- 远程仓库(私服):一般由公司团队搭建的私有仓库
-
生命周期
-
maven 有 3 套相互独立的生命周期:
- clean:清理工作
- default:核心工作,如:编译、测试、打包、安装、部署等
- site:生成报告、发布站点等(使用较少)
红色部分是主要生命周期阶段
-
生命周期阶段
- clean:移除上一次构建生成的文件
- compile:编译项目源代码
- test:使用合适的单元测试框架进行运行测试(比如 junit)
- package:将编译后的文件打包,如:jar、war等
- install:安装项目到本地仓库
-
2.HTTP协议
- 概念:HTTP:Hyper Text Transfer Protocol,超文本传输协议,规定了浏览器和服务器之间数据传输的规则
- 特点
- 基于 TCP 协议:面向连接,安全
- 基于请求-响应模型的:一次请求对应一次响应
- HTTP 协议是无状态的协议:对于事务处理没有记忆能力。每次请求-响应是独立的。
- 缺点:多次请求间不能共享数据
- 优点:速度快
- HTTP 请求数据格式
- 请求行:请求数据第一行(请求方式、资源路径、协议)
- 请求头:第二行开始,格式 key: value
- 请求体:POST请求,存放请求参数
补充:GET 请求更适合用于获取信息,并且这些信息不需要改变服务器上的状态。而 POST 请求适用于更改服务器上的状态或者发送敏感数据
- HTTP 响应数据格式
- 响应行:响应数据第一行(协议、状态码、描述)
- 响应头:第二行开始,格式 key: value
- 响应体:最后一部分,存放响应数据
关于响应行中的状态码
一、状态码大类
状态码分类 | 说明 |
---|---|
1xx | 响应中——临时状态码,表示请求已经接受,告诉客户端应该继续请求或者如果它已经完成则忽略它 |
2xx | 成功——表示请求已经被成功接收,处理已完成 |
3xx | 重定向——重定向到其它地方:它让客户端再发起一个请求以完成整个处理。 |
4xx | 客户端错误——处理发生错误,责任在客户端,如:客户端的请求一个不存在的资源,客户端未被授权,禁止访问等 |
5xx | 服务器端错误——处理发生错误,责任在服务端,如:服务端抛出异常,路由出错,HTTP版本不支持等 |
二、常见的响应状态码
状态码 | 英文描述 | 解释 |
---|---|---|
==200== | OK |
客户端请求成功,即处理成功,这是我们最想看到的状态码 |
302 | Found |
指示所请求的资源已移动到由Location 响应头给定的 URL,浏览器会自动重新访问到这个页面 |
304 | Not Modified |
告诉客户端,你请求的资源至上次取得后,服务端并未更改,你直接用你本地缓存吧。隐式重定向 |
400 | Bad Request |
客户端请求有语法错误,不能被服务器所理解 |
403 | Forbidden |
服务器收到请求,但是拒绝提供服务,比如:没有权限访问相关资源 |
==404== | Not Found |
请求资源不存在,一般是URL输入有误,或者网站资源被删除了 |
405 | Method Not Allowed |
请求方式有误,比如应该用GET请求方式的资源,用了POST |
428 | Precondition Required |
服务器要求有条件的请求,告诉客户端要想访问该资源,必须携带特定的请求头 |
429 | Too Many Requests |
指示用户在给定时间内发送了太多请求(“限速”),配合 Retry-After(多长时间后可以请求)响应头一起使用 |
431 | Request Header Fields Too Large |
请求头太大,服务器不愿意处理请求,因为它的头部字段太大。请求可以在减少请求头域的大小后重新提交。 |
==500== | Internal Server Error |
服务器发生不可预期的错误。服务器出异常了,赶紧看日志去吧 |
503 | Service Unavailable |
服务器尚未准备好处理请求,服务器刚刚启动,还未初始化好 |
状态码大全:https://cloud.tencent.com/developer/chapter/13553
3.web服务器
- web 服务器
- 对 HTTP 协议操作进行封装,简化 web 程序开发
- 部署 web 项目,对外提供网上信息浏览服务
- TomCat 服务器
- 一个开源免费的轻量级 web 服务器,支持 Servlet/JSP 少量 JavaEE规范
- 也被称为 web 容器、servlet 容器
- 将项目部署到 TomCat 服务器上
- 将项目放置到 webapps 目录下,即部署完成
4.请求响应
- 请求(HttpServletRequest):获取请求数据
- 响应(HttpServletResponse):设置响应数据
- BS架构:Browser/Server,浏览器/服务器架构模式。客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端
- CS架构:Client/Server,客户端/服务器架构模式
请求
-
简单参数
- 原始方式:通过 HttpServletRequest 对象手动获取
// 原始方式 @RequestMapping("/simpleParam") public String simpleParam(HttpServletRequest request) { // 获取请求参数 String name = request.getParameter("name"); String ageStr = request.getParameter("age"); int age = Integer.parseInt(ageStr); System.out.println(name + ":" + age); return "OK"; }
- SpringBoot 方式:参数名和形参变量名相同,定义形参即可接收参数
// springboot方式 @RequestMapping("/simpleParam") public String simpleParam(String name, Integer age) { System.out.println(name + ":" + age); return "OK"; }
注意:如果方法形参名称和请求参数名称不匹配,可以使用
@RequestParam
完成映射。并且,@RequestParam
中有一个required
属性,默认为 true ,代表该请求参数必须传递,不传递将报错。如果该参数是可选的,可以将required
设置为 false// 此处请求参数名称为 name ,方法形参为 username @RequestMapping("/simpleParam") public String simpleParam(@RequestParam(name = "name")String username, Integer age) { System.out.println(username + ":" + age); return "OK"; } // 使用required @RequestMapping("/simpleParam") public String simpleParam(@RequestParam(name = "name", required = false)String username, Integer age) { System.out.println(username + ":" + age); return "OK"; }
-
实体参数
- 对于简单实体参数,请求参数名和形参对象属性名相同,定义 POJO 接收即可
// 定义的 pojo 类 public class User { private String name; private Integer age; // getter and setter... // toString... } // controller 中的接收简单实体参数方法 @RequestMapping("/simplePojo") public String simplePojo(User user) { System.out.println(user); return "OK"; }
- 对于复杂实体参数,请求参数名和形参对象属性名相同,按照对象层次结构关系即可接收嵌套 POJO 属性参数
// 定义的 pojo 类 public class User { private String name; private Integer age; private Address address; // 新增了一个 address 属性 // getter and setter... // toString... } // Address 类 public class Address { private String province; private String city; // getter and setter... // toString... } // controller 中的复杂实体参数传递 // 复杂实体参数 @RequestMapping("/complexPojo") public String complexPojo(User user) { System.out.println(user); return "OK"; }
以下是 postman 中传递的信息
-
数组集合参数
- 数组参数:请求参数名与形参数组名称相同且请求参数为多个,定义数组类型形参即可接收参数
// 数组集合参数 @RequestMapping("/arrayParam") public String arrayParam(String[] hobby) { System.out.println(Arrays.toString(hobby)); return "OK"; }
- 集合参数:请求参数名与形参集合名称相同且请求参数为多个,需要用
@RequestParam
绑定参数关系
@RequestMapping("/listParam") public String listParam(@RequestParam List<String> hobby) { System.out.println(hobby); return "OK"; }
两种情况在 postman 请求方式类似
-
日期参数:需要使用
@DateTimeFormat
注解完成日期参数格式转换
// 日期参数
@RequestMapping("/dateParam")
public String dateParam(@DateTimeFormat (pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime updateTime) {
System.out.println(updateTime);
return "OK";
}
postman 中的请求如下
- JSON 参数:JSON数据键名与形参对象属性名相同,定义 POJO 类型形参即可接收参数,需要使用
@RequestBody
标识接收 JSON 格式的数据。同时,JSON 只能通过 POST 请求上传
// JSON参数
@RequestMapping("/jsonParam")
public String jsonParam(@RequestBody User user) {
System.out.println(user);
return "OK";
}
postman 中的请求如下
-
路径参数:通过请求 URL 直接传递参数,使用
{...}
来标识该路径参数,需要使用@PathVariable
来获取路径参数- 单个路径参数
// 单个路径参数 @RequestMapping("/path/{id}") public String pathParam(@PathVariable Integer id) { System.out.println(id); return "OK"; }
postman 请求
- 多个路径参数
// 多个路径参数 @RequestMapping("/path/{id}/{name}") public String pathParam2(@PathVariable Integer id, @PathVariable String name) { System.out.println(id + ":" + name); return "OK"; }
postman 请求
响应
-
响应数据
@ResponseBody
:方法注解,位置在 Controller 方法上/类上,作用:将方法返回值直接响应,如果返回值类型是 实体对象/集合,将会转换为 JSON 格式响应(@RestController = @Controller + @ResponseBody
)- 为了便于管理项目和维护项目,需要把每个接口响应回前端的内容统一成一个响应结果。为了达到这一目的,我们可以将响应结果封装成一个实体对象。该对象主要包含:1.响应码、2.提示信息、3.返回的数据
public class Result { // 响应码,1 代表成功;0 代表失败 private Integer code; // 提示信息 private String msg; // 返回的数据 private Object data; // ...... }
// 响应结果如下 { "code": 1, "msg": "操作成功", "data": ... }
-
先在 pojo 中定义 Result 类
package com.itheima.pojo;
/**
* 统一响应结果封装类
*/
public class Result {
private Integer code ;//1 成功 , 0 失败
private String msg; //提示信息
private Object data; //数据 data
public Result() {
}
public Result(Integer code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static Result success(Object data){
return new Result(1, "success", data);
}
public static Result success(){
return new Result(1, "success", null);
}
public static Result error(String msg){
return new Result(0, msg, null);
}
@Override
public String toString() {
return "Result{" +
"code=" + code +
", msg='" + msg + '\'' +
", data=" + data +
'}';
}
}
然后将原来的 Controller 中的方法修改,返回对象统一设置成 Result ,return 的时候可以调用 Result 类的静态方法 success 返回
package com.itheima.controller;
import com.itheima.pojo.Address;
import com.itheima.pojo.Result;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@RestController
public class ResponseController {
@RequestMapping("/hello")
public Result hello() {
System.out.println("hello world");
//return new Result(1, "success", "Hello World~");
return Result.success("hello world");
}
@RequestMapping("/getAddr")
public Result getAddr() {
Address addr = new Address();
addr.setProvince("广东");
addr.setCity("深圳");
return Result.success(addr);
}
@RequestMapping("/listAddr")
public Result listAddr() {
List<Address> list = new ArrayList<>();
Address addr1 = new Address();
addr1.setProvince("广东");
addr1.setCity("深圳");
Address addr2 = new Address();
addr2.setProvince("陕西");
addr2.setCity("西安");
list.add(addr1);
list.add(addr2);
return Result.success(list);
}
}
- 总的来说响应有两点:
@ResponseBody
注解- 位置:在 Controller 类上/方法上
- 作用:将方法返回值直接响应,若返回值类型是 实体对象/集合,直接转成 JSON 格式响应
- 统一响应结果:
Result (code、msg、data)
5.分层解耦
- 三层架构
- controller:控制层,接收前端发送的请求,对请求进行处理并响应数据
- service:业务逻辑层,处理具体的业务逻辑
- dao:数据访问层(Data Access Object)(持久层),负责数据访问操作,包括数据的增删改查
- 分层解耦:使用 Spring 框架的 IOC&DI,具体可见:Spring框架学习笔记 (yuk1pedia.github.io)
6.mysql
-
单表操作常用命令:mysql常用指令 (yuk1pedia.github.io)
-
多表设计与操作
-
多表设计:在项目开发的业务模块设计时,会有不同的表之间的联系:一对多(多对一)、多对多、一对一
- 一对多:比如部门与员工(一个部门有多个员工)
- 外键约束:数据库层面建立两张表或多张表的关联,保证数据的一致性。在”多“的一方添加外键来关联“一”的一方的主键
- 一对一:比如用户与身份证信息的关系(一个人对应唯一的身份证)。这个关系多用于单表拆分,将一张表的基础字段放在一张表中,其他字段放在另一张表中,可以提高操作效率。
- 外键约束:在一张表上设置关联另一张表的唯一外键即可
- 多对多:比如一个学生可以有多个老师,一个老师可以有多个学生。实现多对多可以添加一张中间表,中间表至少包含两个外键,分别关联两个表的主键。
- 一对多:比如部门与员工(一个部门有多个员工)
-
多表查询:从多张表中进行查询,最简单的例子:
select * from tb_emp, tb_dept;
,但以上例子查询的是两张表组成的笛卡尔积,存在较多的数据冗余。改善上述情况可以增加连接条件来消除无效的笛卡尔积:select * from tb_emp, tb_dept where tb_emp.dept_id = tb_dept.id;
多表查询又可以分为连接查询和子查询。连接查询又可以分为内连接与外连接。
-
连接查询
- 内连接:相当于查询 A、B 交集部分数据
举个例子,现在有员工表和部门表两张表,我要查询员工姓名,以及所属部门名称,可以用以下 sql 语句实现:
select tb_emp.name, tb_dept.name from tb_emp, tb_dept where tb_emp.dept_id = tb_dept.id;
为了减少表名的重复书写,可以用给表起别名的方式来写 sql ,具体如下:
select e.name, d.name from tb_emp e, tb_dept d where e.dept_id = d.id;
- 外连接
- 左外连接:查询左表所有数据(包括两张表交集部分数据)
- 右外连接:查询右表所有数据(包括两张表交集部分数据)
比如查询员工表中所有员工的姓名和对应的部门名称可以用如下 sql 语句:
select e.name, d.name from tb_emp e left join tb_dept d on e.dept_id = d.id;
-
子查询(查询中嵌套查询)
-
形式:
select * from t1 where column1 = (select column1 from t2 ...);
-
子查询外部的语句可以是
insert/updata/delete/select
的任何一个,最常见的是select
-
分类
-
标量子查询:子查询返回结果为单个值
比如:查询 “教研部” 的所有员工信息
可以分为两步:
1.查询 “教研部” 的部门ID:
select id from tb_dept where name = '教研部';
2.查询该部门ID下的员工信息:
select * from tb_emp where dept_id = 2;
上述两句 sql 合并成一句子查询:
select * from tb_emp where dept_id = (select id from tb_dept where name = '教研部');
-
列子查询:子查询返回结果为一列
比如:查询 “教研部” 和 “咨询部” 的所有员工信息
可以分为两步:
1.查询 “教研部” 和 “咨询部” 的部门ID:
select id from tb_dept where name = '教研部' or name = '咨询部';
2.根据部门ID,查询该部门下的员工信息:
select * from tb_emp where dept_id in (3, 2);
可以合并为:
select * from tb_emp where dept_id in (select id from tb_dept where name = '教研部' or name = '咨询部');
-
行子查询:子查询返回的结果为一行
比如:查询与 “龙玉涛” 的入职日期及职位都相同的员工信息
可以分为两步:
1.查询 “龙玉涛” 的入职日期及职位:
select entrydate, job from tb_emp where name = '龙玉涛';
2.查询与其入职日期及职位都相同的员工信息:
select * from tb_emp where entrydate = '2007-01-01' and job = 2;
可以合并成一句:
select * from tb_emp where (entrydate, job) = (select entrydate, job from tb_emp where name = '龙玉涛');
-
表子查询:子查询返回的结果为多行多列,常作为临时表
比如:查询入职日期时 “2006-01-01” 之后的员工信息,及其部门名称
可以分为两步:
1.查询入职日期是 “2006-01-01” 之后的员工信息:
select * from tb_emp where entrydate > '2006-01-01';
2.查询这部分员工信息及其部门名称(直接合并)
select e.*, d.name from (select * from tb_emp where entrydate > '2006-01-01') e, tb_dept d where e.dept_id = d.id;
-
-
-
-
事务:一组操作的集合,是一个不可分割的工作单位。事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败
-
事务控制
- 开始事务:start transaction; / begin;
- 提交事务:commit
- 回滚事务:rollback
-- 开始事务 start transaction; -- 删除部门 delete from tb_dept where id = 2; -- 删除部门下的圆拱 delete from rb_emp where dept_id = 2; --提交事务 commit; -- 回滚事务 rollback;
-
事务四大特性
- 原子性:事务是不可分割的最小单元,要么全部成功,要么全部失败
- 一致性:事务完成时,必须使所有的数据都保持一致状态
- 隔离性:数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行
- 持久性:事务一旦提交或回滚,他对数据库中的数据的改变就是永久的
-
-
索引:帮助数据库高效获取数据的数据结构(比如二叉搜索树、红黑树、B+树等等)
- 优缺点
- 优点
- 提高数据查询的效率,降低数据库的 IO 成本
- 通过索引列对数据进行排序,降低数据排序的成本,降低 CPU 消耗
- 缺点
- 索引会占用存储空间
- 索引大大提高了查询效率,同时却也降低了 insert、update、delete 的效率
- 优点
- mysql 的索引结构:mysql 支持的索引结构很多,如:Hash 索引、B+Tree 索引、Full-Text 索引等。如果没有特别指明,都是指默认的 B+Tree 结构组织的索引
-
语法
- 创建索引
create [ unique ] index 索引名 on 表名(字段名, ...);
- 查看索引
show index from 表名;
- 删除索引
drop index 索引名 on 表名;
注意:1.主键字段在建表时,会自动创建主键索引;2.添加唯一约束时,数据库实际上会添加唯一索引。
- 优缺点
-
7.mybatis
- 什么是 mybatis?
- 持久层(dao层)框架,支持自定义 SQL、存储过程及高级映射
- 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。 mybatis 可以通过简单的 xml 或注解来配置和映射原始类型、接口和 Java POJO 为数据库中的记录
- JDBC与数据库连接池:参考JDBC学习笔记 (yuk1pedia.github.io)
- lombok:一个实用的 java 类库,能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString等方法,并可以自动化生成日志变量,简化开发
-
预编译 sql:一种在数据库应用程序中使用的技术。这种技术允许应用程序将SQL语句发送给数据库,但不直接绑定具体的值到查询中。相反,预编译SQL使用占位符(通常是问号
?
或者特定于库的符号如:param
)来表示将在运行时提供的值- 性能更高
- 更安全(防止 sql 注入)
-
基础操作(以下配置 sql 的方法均为注解配置)
-
删除操作:比如删除员工表某一行数据
接口方法如下,这里使用了一个
#{id}
占位符,用于向 sql 语句传递参数@Mapper public interface EmpMapper { // 根据 id 删除 @Delete("delete from emp where id = #{id}") void delete(Integer id); }
测试类如下
@SpringBootTest class SpringbootMybatisCrudApplicationTests { @Autowired private EmpMapper empMapper; @Test public void testDelete() { empMapper.delete(17); } }
补充:参数占位符的区别
-
新增操作
接口方法如下,其中的占位符和 java 类型的实体类的属性相一致
@Mapper public interface EmpMapper { // 新增员工 @Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) " + "values (#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime})") public void insert(Emp emp); }
主键返回:在数据添加成功后,需要获取插入数据库数据的主键。如:添加套餐数据时,还需要维护套餐菜品关系表(中间表)数据。具体实现如下:
@Mapper public interface EmpMapper { // 会自动将生成的主键值,赋值给 emp 对象的 id 属性 @Options(keyProperty="id", useGeneratedKeys=true) @Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) " + "values (#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime})") public void insert(Emp emp); }
-
更新操作
接口方法如下
@Mapper public interface EmpMapper { // 更新员工 @Update("update emp set username = #{username}, name = #{name}, gender = #{gender}, image = #{image}," + "job = #{job}, entrydate = #{entrydate}, dept_id = #{deptId}, update_time = #{updateTime} where id = #{id}") public void update(Emp emp); }
-
查询操作
接口方法如下
@Mapper public interface EmpMapper { // 根据 id 查询单个员工 @Select("select * from emp where id = #{id}") public Emp getById(Integer id); }
但是这种方法在封装实体类时,如果实体类
Emp
的属性名和数据库中的字段名不同,就不能将数据库中的数据封装到对应的 java 属性中,有以下三种方法可以解决这种情况,其中第三种:打开 mybatis 的驼峰命名自动映射开关最为常用。使用第三种方法时需要在springboot
的配置文件中添加如下配置# 方式三:打开 mybatis 的驼峰命名自动映射开关 a_column -----> aColumn mybatis.configuration.map-underscore-to-camel-case=true
@Mapper public interface EmpMapper { // 根据 id 查询单个员工 // 方式三:打开 mybatis 的驼峰命名自动映射开关 @Select("select * from emp where id = #{id}") public Emp getById(Integer id); // 方式一:给字段起别名,解决部分查询结果不能封装的问题 @Select("select id, username, password, name, gender, image, job, entrydate, " + "dept_id deptId, create_time createTime, update_time updateTime from emp where id = #{id}") public Emp getById(Integer id); // 方式二:用@Result进行手动映射封装 @Results({ @Result(column = "dept_id", property = "deptId"), @Result(column = "create_time", property = "createTime"), @Result(column = "update_time", property = "updateTime") }) @Select("select * from emp where id = #{id}") public Emp getById(Integer id); }
-
条件查询操作
接口方法如下
@Mapper public interface EmpMapper { @Select("select * from emp where name like '%${name}%' and gender = #{gender} and " + "entrydate between #{begin} and #{end} order by update_time desc ") public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end); }
此处
'%${}%'
不使用#{}
的原因是#{}
不能出现在引号中,如果要与字符串拼接可以用下面的concat
方式@Mapper public interface EmpMapper { @Select("select * from emp where name like concat('%', #{name}, '%') and gender = #{gender} and " + "entrydate between #{begin} and #{end} order by update_time desc ") public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end); }
补充说明:早期
springboot
编译后的字节码文件中不会保留实体类属性名,需要使用@Param
注解
-
-
XML 映射文件(配置 sql 语句)
-
规范
- XML 映射文件的名称与 Mapper 接口名称一致,并且将 XML 映射文件和 Mapper 接口放置在相同包下(同包同名)
- XML 映射文件的 namespace 属性为 Mapper 接口全限定名一致
- XML 映射文件中的 sql 语句的 id 与 Mapper 接口中的方法名一致,并保持返回类型一致
-
动态 SQL:随着用户的输入或外部条件的变化而变化的 SQL 语句
-
<if>
:用于判断条件是否成立,使用test
属性进行条件判断,如果条件成立则拼接 SQL 语句 -
<where>
:where 元素只会在子元素有内容的情况下才会插入 where 子句,而且会自动去除字句开头的 AND 和 OR
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.itheima.mapper.EmpMapper"> <select id="list" resultType="com.itheima.pojo.Emp"> select * from emp <where> <if test="name != null"> name like concat('%', #{name}, '%') </if> <if test="gender != null"> and gender = #{gender} </if> <if test="begin != null and end != null"> and entrydate between #{begin} and #{end} </if> </where> order by update_time desc </select> </mapper>
<set>
:动态的在行首插入 SET 关键字,并会删掉额外的逗号(用在 update 语句中)
-
<foreach>
:具体如下- sql 语句:
delete from emp where id in (1, 2, 3)
- 接口方法:
public void deleteByIds(List<Integer> ids);
- XML 映射文件
<!-- 批量删除员工--> <!-- collection:遍历的集合--> <!-- item:遍历到的元素--> <!-- separator:分隔符--> <!-- open:遍历开始前拼接的 sql 片段--> <!-- close:遍历结束后拼接的 sql 片段--> <!-- 最终拼接的 sql 片段:delete from emp where id in (1, 2, 3)--> <delete id="deleteByIds"> delete from emp where id in <foreach collection="ids" item="id" separator="," open="(" close=")"> #{id} </foreach> </delete>
- sql 语句:
-
<sql>
和<include>
<sql>
:定义可重用的 SQL 片段<include>
:通过属性 refid,指定包含的 SQL 片段
-
-
8.文件上传
-
文件上传:将本地图片、视频、音频等文件上传到服务器,供其他用户
-
前端页面
- 表单项:
type = "file"
- 表单提交方式:
post
- 表单的
enctype
属性为multipart/form-data
- 表单项:
-
后端服务端接收文件
- 使用 Springboot 自带的
MultipartFile
接收
@Slf4j @RestController public class UploadController { @PostMapping("/upload") public Result upload(String username, Integer age, MultipartFile image) { log.info("文件上传:{}, {}, {}", username, age, image); return Result.success(); } }
- 使用 Springboot 自带的
-
本地存储
- 文件名使用
uuid
防止重复 - 获取上传文件的扩展名用
MultipartFile
的方法getOriginalFilename
- 文件名使用
@Slf4j
@RestController
public class UploadController {
@PostMapping("/upload")
public Result upload(String username, Integer age, MultipartFile image) throws Exception {
log.info("文件上传:{}, {}, {}", username, age, image);
// 获取原始文件名 -1.jpg
String originalFilename = image.getOriginalFilename();
// 构造唯一文件名(不重复) -uuid(通用唯一识别码)bc5cbf0c-f831-40f7-8e6f-e31302b10f61
int index = originalFilename.lastIndexOf(".");
String extName = originalFilename.substring(index);
String newFileName = UUID.randomUUID().toString() + extName;
log.info("新的文件名:{}", newFileName);
// 将接收到的文件存储到本地磁盘目录中
image.transferTo(new File("C:\\Users\\86186\\Desktop\\image_test\\" + originalFilename));
return Result.success();
}
}
-
阿里云OSS
- 使用阿里云 OSS 对象存储服务工具类
@Component public class AliOSSUtils { private String endpoint = "example"; private String accessKeyId = "example"; private String accessKeySecret = "example"; private String bucketName = "example"; /** * 实现上传图片到OSS */ public String upload(MultipartFile file) throws IOException { // 获取上传的文件的输入流 InputStream inputStream = file.getInputStream(); // 避免文件覆盖 String originalFilename = file.getOriginalFilename(); String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf(".")); //上传文件到 OSS OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); ossClient.putObject(bucketName, fileName, inputStream); //文件访问路径 String url = endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + fileName; // 关闭ossClient ossClient.shutdown(); return url;// 把上传到oss的路径返回 } }
9.配置文件
- 参数配置化
项目很大时如果将参数硬编码到代码段中,需要修改时不便于查找
将参数写到 springboot
的配置文件中,并在对应代码段使用 @Value
进行值注入即可
-
yml 配置文件:参考Springboot框架学习笔记 (yuk1pedia.github.io)
-
@ConfigurationProperties
- 当一个类中需要注入的参数很多时,使用上面的
@Value
注解会比较繁琐,这里可以用@ConfigurationProperties
这个注解将配置文件的参数注入到 bean 对象中
- 同时,可以选择在项目的 pom 文件中导入一个依赖,在编写配置文件时也可以有 bean 对象对应的提示
@ConfigurationProperties
和@Value
- 相同点:
- 都是用来注入外部配置的属性的
- 不同点:
@Value
注解只能一个一个的进行外部属性的注入@ConfigurationProperties
可以批量的将外部的属性配置注入到 bean 对象的属性中
- 相同点:
- 当一个类中需要注入的参数很多时,使用上面的
10.登录认证
-
基础登录功能:根据用户输入的用户名与密码在数据库中进行匹配,如果用户名和密码均正确就放行
- LoginController
@Slf4j @RestController public class LoginController { @Autowired private EmpService empService; @PostMapping("/login") public Result login(@RequestBody Emp emp) { log.info("员工登录:{}", emp); Emp e = empService.login(emp); return e != null ? Result.success(e) : Result.error("用户名或密码错误!"); } }
- EmpServiceImpl 中对应的方法
@Override public Emp login(Emp emp) { return empMapper.getByUsernameAndPassword(emp); }
- EmpMapper 中对应的方法
/** * 根据用户名和密码查询用户 * @param emp * @return */ @Select("select * from emp where username = #{username} and password = #{password}") Emp getByUsernameAndPassword(Emp emp);
-
登录校验
-
会话技术
-
会话:用户打开浏览器,访问 web 服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。比如下图中就有三次会话。
-
会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自同一浏览器,以便在同一次会话的多次请求间共享数据。
-
会话跟踪方案:
-
客户端会话跟踪技术:
Cookie
- 浏览器第一次向服务端发起请求,比如请求的
login
接口,执行完成后服务端会生成一个cookie
,其中携带有用户名等信息,然后将带有cookie
的响应头发给浏览器。浏览器接收到后将cookie
存到本地,之后浏览器每次向服务器发送请求时都会在请求头携带有cookie
,表明这是同一次会话。 - 优点:该技术是 HTTP 协议支持的技术
- 缺点:
- 移动端 APP 无法使用
Cookie
- 不安全,用户可以自己禁用
Cookie
Cookie
不能跨域
- 移动端 APP 无法使用
@Slf4j @RestController public class SessionController { //设置Cookie @GetMapping("/c1") public Result cookie1(HttpServletResponse response){ response.addCookie(new Cookie("login_username","itheima")); //设置Cookie/响应Cookie return Result.success(); } //获取Cookie @GetMapping("/c2") public Result cookie2(HttpServletRequest request){ Cookie[] cookies = request.getCookies(); for (Cookie cookie : cookies) { if(cookie.getName().equals("login_username")){ System.out.println("login_username: "+cookie.getValue()); //输出name为login_username的cookie } } return Result.success(); } }
-
补充:什么是跨域?如下图,前后端的域名并不相同,出现了跨域,此时
Cookie
无法使用
- 浏览器第一次向服务端发起请求,比如请求的
-
服务端会话跟踪技术:
Session
Session
底层是基于Cookie
实现的,当浏览器第一次请求服务器时,服务器会自动创建一个会话对象Session
,每一个Session
都会有一个id
称为sessionid
。之后服务器向浏览器响应数据时,会将sessionid
通过Cookie
响应给浏览器(在响应头中增加Set-Cookie
这个响应头)。之后浏览器会将带有sessionid
的Cookie
存放在本地,之后的每次请求都会在请求头携带上带有sessionid
的Cookie
,然后服务器根据这个sessionid
找到对应的会话对象。- 优点:存储在服务器,较安全
- 缺点:
- 服务器集群环境下无法使用
Session
- 包含
Cookie
的缺点
- 服务器集群环境下无法使用
@Slf4j @RestController public class SessionController { @GetMapping("/s1") public Result session1(HttpSession session){ log.info("HttpSession-s1: {}", session.hashCode()); session.setAttribute("loginUser", "tom"); //往session中存储数据 return Result.success(); } @GetMapping("/s2") public Result session2(HttpServletRequest request){ HttpSession session = request.getSession(); log.info("HttpSession-s2: {}", session.hashCode()); Object loginUser = session.getAttribute("loginUser"); //从session中获取数据 log.info("loginUser: {}", loginUser); return Result.success(loginUser); } }
-
补充:集群环境下
Session
为什么不能使用?比如下图中的集群环境,浏览器先在第一台服务器创建了Session
对象,之后负载均衡服务器将浏览器的第二次请求发送到第二台服务器,此时第二台服务器并没有sessionid
为 1 的Session
对象,所以不能获取到之前的会话。
-
令牌技术
- 优点:
- 支持 PC 端、移动端
- 解决集群环境下的认证问题
- 减轻服务器端存储压力
- 缺点:需要自己实现
- 优点:
-
-
-
JWT 令牌
- JWT:
- 全称为 JSON Web Token
- 定义了一种简洁的、自包含的格式,用于在通信双方以 json 数据格式安全地传输信息。由于数字签名的存在,这些信息是可靠的
- 组成:
- 第一部分:Header(头),记录令牌类型、签名算法等。例如:{“alg”:”HS256”,”type”:”JWT”}
- 第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。例如:{“id”:”1”,”username”:”Tom”}
- 第三部分:Signature(签名),防止 Token 被篡改、确保安全性。将 header、payload,并加入指定密钥,通过指定签名算法计算而来。
- JWT 令牌应用场景:
- 基于 java 代码生成 JWT 令牌
/** * 生成 JWT 令牌 */ @Test public void testGenJwt() { Map<String, Object> claims = new HashMap<>(); claims.put("id", 1); claims.put("name", "Tom"); String jwt = Jwts.builder() .signWith(SignatureAlgorithm.HS256, "yukipedia") // 设置签名算法与签名密钥 .setClaims(claims) // 自定义内容(载荷) .setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000)) // 设置令牌有效期为 1h .compact(); System.out.println(jwt); }
- 基于 java 代码解析 JWT 令牌
/** * 解析 JWT 令牌 */ @Test public void testParseJwt() { Claims claims = Jwts.parser() // 指定签名密钥 .setSigningKey("yukipedia") .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiVG9tIiwiaWQiOjEsImV4cCI6MTcyMzU1OTE5MH0.vHoj9V7y4FvJ2PpIetXC9uUo5KWW9hDthpkm5d_RX5E") .getBody(); System.out.println(claims); }
- JWT:
-
过滤器(Filter)
- Filter 过滤器,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一
- 过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能
- 过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等
- Filter 执行流程
- 首先执行放行前地逻辑
- 然后被过滤器拦截下,之后放行
- 再访问服务器资源
- 最后回到 Filter 中,执行放行之后的逻辑
- Filter 拦截路径
- 过滤器链:一个 web 应用中,可以配置多个过滤器,这多个过滤器就形成了一个过滤器链
- 过滤器的执行顺序是按照过滤器类名的字典序来决定的
- 具体执行流程如下图
- 用过滤器实现 JWT 令牌的登录校验
@Slf4j //@WebFilter(urlPatterns = "/*") public class LoginCheckFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; //1.获取请求url。 String url = req.getRequestURL().toString(); log.info("请求的url: {}",url); //2.判断请求url中是否包含login,如果包含,说明是登录操作,放行。 if(url.contains("login")){ log.info("登录操作, 放行..."); chain.doFilter(request,response); return; } //3.获取请求头中的令牌(token)。 String jwt = req.getHeader("token"); //4.判断令牌是否存在,如果不存在,返回错误结果(未登录)。 if(!StringUtils.hasLength(jwt)){ log.info("请求头token为空,返回未登录的信息"); Result error = Result.error("NOT_LOGIN"); //手动转换 对象--json --------> 阿里巴巴fastJSON String notLogin = JSONObject.toJSONString(error); resp.getWriter().write(notLogin); return; } //5.解析token,如果解析失败,返回错误结果(未登录)。 try { JwtUtils.parseJWT(jwt); } catch (Exception e) {//jwt解析失败 e.printStackTrace(); log.info("解析令牌失败, 返回未登录错误信息"); Result error = Result.error("NOT_LOGIN"); //手动转换 对象--json --------> 阿里巴巴fastJSON String notLogin = JSONObject.toJSONString(error); resp.getWriter().write(notLogin); return; } //6.放行。 log.info("令牌合法, 放行"); chain.doFilter(request, response); } }
-
拦截器(Interceptor)
- 概念:是一种动态拦截方法调用的机制,类似于过滤器。Spring 框架中提供的,用动态拦截控制器方法的执行
- 作用:拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码
-
使用拦截器时按照下面的步骤
- 定义拦截器,实现
HandlerInterceptor
接口,并重写其所有方法。
@Slf4j @Component public class LoginCheckInterceptor implements HandlerInterceptor { @Override //目标资源方法运行前运行, 返回true: 放行, 放回false, 不放行 public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception { //1.获取请求url。 String url = req.getRequestURL().toString(); log.info("请求的url: {}",url); //2.判断请求url中是否包含login,如果包含,说明是登录操作,放行。 if(url.contains("login")){ log.info("登录操作, 放行..."); return true; } //3.获取请求头中的令牌(token)。 String jwt = req.getHeader("token"); //4.判断令牌是否存在,如果不存在,返回错误结果(未登录)。 if(!StringUtils.hasLength(jwt)){ log.info("请求头token为空,返回未登录的信息"); Result error = Result.error("NOT_LOGIN"); //手动转换 对象--json --------> 阿里巴巴fastJSON String notLogin = JSONObject.toJSONString(error); resp.getWriter().write(notLogin); return false; } //5.解析token,如果解析失败,返回错误结果(未登录)。 try { JwtUtils.parseJWT(jwt); } catch (Exception e) {//jwt解析失败 e.printStackTrace(); log.info("解析令牌失败, 返回未登录错误信息"); Result error = Result.error("NOT_LOGIN"); //手动转换 对象--json --------> 阿里巴巴fastJSON String notLogin = JSONObject.toJSONString(error); resp.getWriter().write(notLogin); return false; } //6.放行。 log.info("令牌合法, 放行"); return true; } @Override //目标资源方法运行后运行 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("postHandle ..."); } @Override //视图渲染完毕后运行, 最后运行 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion..."); } }
- 注册拦截器
@Configuration //配置类 public class WebConfig implements WebMvcConfigurer { @Autowired private LoginCheckInterceptor loginCheckInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**").excludePathPatterns("/login"); } }
- 定义拦截器,实现
-
拦截器-拦截路径
- 拦截器-执行流程
- 拦截器和过滤器的区别:
- 接口规范不同:过滤器需要实现
Filter
接口,而拦截器需要实现HandlerInterceptor
接口 - 拦截范围不同:过滤器
Filter
会拦截所有资源,而Interceptor
只会拦截Spring
环境中的资源
- 接口规范不同:过滤器需要实现
-
异常处理
-
当程序出现异常时,可以用以下两种方法进行处理
- 方案一:在
Controller
的方法中进行try...catch
处理,但是这种处理方法会使得代码比较臃肿 - 方案二:全局异常处理器
- 全局异常处理器主要涉及两个注解:
@RestControllerAdvice
:在全局异常处理器类上加的注解,程序中出现的异常都会由这个类进行处理@ExceptionHandler
:在方法上的注解,通过该注解明确当前方法要捕获什么类型的异常
- 全局异常处理器主要涉及两个注解:
- 方案一:在
-
-
10.Spring 事务管理
-
@Transactional
注解- 位置:业务层(Service)的方法上、类上、接口上
- 作用:将当前方法交给 Spring 进行事务管理,方法执行前,开启事务;成功执行完毕,提交事务;出现异常,回滚事务
rollbackFor
属性:- 默认情况下,只有出现
RuntimeException
时,@Transactional
注解才会回滚异常。此时需要使用该注解中的rollbackFor
属性,该属性用于控制出现何种异常类型,回滚事务。
- 默认情况下,只有出现
-
propagation
属性- 事务传播行为:当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制
- 其中常用的属性值为
REQUIRED
和REQUIRES_NEW
REQUIRED
:大部分情况下都是用该传播行为即可REQUIRES_NEW
:当我们不希望事务之间相互影响时,可以使用该传播行为。比如:下订单前需要记录日志,不论订单保存成功与否,都需要保证日志记录能够记录成功
11.AOP
- 什么是 AOP ?
- AOP:Aspect Oriented Programming(面向切面编程,面向方面编程),其实就是面向特定方法编程
- 实现:动态代理是面向切面编程最主流的实现。而 SpringAOP 是 Spring 框架的高级技术,旨在管理 bean 对象的过程中,主要通过底层的动态代理机制,对特定的方法进行编程
- 动态代理补充:
- 动态代理(Dynamic Proxy)是一种设计模式,允许在运行时动态创建代理类和代理对象,而无需在编译时明确定义代理类。它主要用于拦截和控制方法调用,在不修改原始代码的情况下增强类的功能
- 在面向切面编程中,动态代理可以用来在方法调用前后添加横切关注点,如日志记录、事务管理等
- AOP 核心概念
- 连接点:JoinPoint,可以被 AOP 控制的方法(暗含方法执行时的相关信息)
- 通知:Advice,指重复的逻辑,也就是共性功能(最终体现为一个方法)
- 切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用
- 切面:Aspect,描述通知与切入点的对应关系(通知+切入点)
- 目标对象:Target,通知所应用的对象
- AOP 执行流程
- 程序会自动创建一个代理对象,代理对象中会包含有切面对象中的方法和原来对象中的方法,然后创建对象时创建的就不是原来的对象,而是代理对象,比如下图中
DeptController
中的deptService
,创建的就是DeptServiceProxy
这个代理对象
- 程序会自动创建一个代理对象,代理对象中会包含有切面对象中的方法和原来对象中的方法,然后创建对象时创建的就不是原来的对象,而是代理对象,比如下图中
- AOP 通知类型:参考Spring框架学习笔记 (yuk1pedia.github.io)
- AOP 通知顺序
- 当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行
- 执行顺序:
- 不同切面类中,默认按照切面类的类名字典序排序:
- 目标方法前的通知方法:字典序排名靠前的先执行
- 目标方法后的通知方法:字典序排名靠后的先执行
- 用
@Order(数字)
加在切面类上来控制顺序- 目标方法前的通知方法:数字小的先执行
- 目标方法后的通知方法:数字小的后执行
- 不同切面类中,默认按照切面类的类名字典序排序:
-
AOP 切入点表达式
-
execution(......)
:根据方法的签名来匹配,参考Spring框架学习笔记 (yuk1pedia.github.io) -
@annotation(......)
:根据注解匹配- 先定义一个自己的注解
@Mylog
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Mylog() { }
- 在切入点方法上加上
@Mylog
注解
@Mylog @Override public List<Dept> list() { List<Dept> deptList = deptMapper.list(); return deptList; } @Mylog @Override public void delete(Integer id) { deptMapper.delete(id); }
- 在切入点表达式中使用
@annotation
注解指定@Mylog
的全类名
@Pointcut("@annotation(com.itheima.aop.Mylog)") private void pt() {}
- 先定义一个自己的注解
-
连接点
- 在 Spring 中用
JoinPoint
抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等 - 对于
@Around
通知,获取连接点信息只能使用ProceedingJoinPoint
- 对于其他四种通知,获取连接点信息只能使用
JoinPoint
,它是ProceedingJoinPoint
的父类型
- 在 Spring 中用
-
12.Springboot 原理
参考这两篇博客:
maven高级
1.分模块设计与开发
- 概念:将项目按照功能拆分成若干子模块,方便项目的管理维护、扩展,也方便模块间的相互调用,资源共享
- 分模块设计需要先针对模块功能进行设计,再进行编码。不是先将工程开发完毕,再进行拆分
- 比如下面的项目工程
2.继承与聚合
-
继承
- 概念:继承描述的是两个工程间的关系,与 java 中的继承相似,子工程可以继承父工程中的配置信息,常见于依赖关系的继承
- 作用:简化依赖配置、统一管理依赖
-
实现:在工程的 pom 文件中使用
<parent> ... </parent>
标签指定父工程,从而继承父工程的依赖配置 -
继承关系实现:
-
创建 maven 模块
tlias-parent
,该工程为父工程,设置打包方式为pom
(打包方式如下)-
jar
:普通模块打包,springboot 项目基本都是 jar 包,使用内嵌的 tomcat 运行 war
:普通 web 程序打包,需要部署在外部的 tomcat 服务器中运行pom
:父工程或聚合工程,该模块不写代码,仅进行依赖管理
-
-
在子工程的 pom.xml 文件中配置继承关系
-
在父工程中配置各个工程共有的依赖(子工程会自动继承父工程的依赖)
-
上图中的
<relativePath>
标签标记的是父工程中 pom 文件的相对路径- 补充:项目的工程结构可以用以下的另外一种方式构建
-
版本锁定
- 在 maven 中,可以在父工程的 pom 文件中通过
<dependencyManagement>
来统一管理依赖版本。父工程的<dependencyManagement>
并不能注入对应的依赖,只是锁定了对应依赖的版本,子工程需要使用时还是需要导入对应的依赖,但此时不需要指定<version>
版本号
-
自定义属性/引用属性
- 父工程中依赖较多时,管理版本号不是很方便,这里可以用 maven 自带的自定义属性功能进行版本号的定义
- 在 maven 中,可以在父工程的 pom 文件中通过
-
聚合
- 聚合:将多个模块组织成一个整体,同时进行项目的构建
- 聚合工程:不具有业务功能的“空”工程(有且仅有一个 pom 文件)
- 作用:快速构建项目(不需根据依赖关系手动构建,直接在聚合工程上构建即可)
- maven 中可以通过
<modules>
设置当前聚合工程所包含的子模块名称
3.私服
- 私服:
- 私服是一种特殊的远程仓库,它是架设在局域网内的仓库服务,用来代理位于外部的中央仓库,用于解决团队内部的资源共享与资源同步问题
- 依赖查找顺序:本地仓库 -> 私服 -> 中央仓库
-
资源上传与下载
具体参考下面的私服配置说明
私服配置说明
访问私服:http://192.168.150.101:8081
访问密码:admin/admin
使用私服,需要在maven的settings.xml配置文件中,做如下配置:
-
需要在 servers 标签中,配置访问私服的个人凭证(访问的用户名和密码)
<server> <id>maven-releases</id> <username>admin</username> <password>admin</password> </server> <server> <id>maven-snapshots</id> <username>admin</username> <password>admin</password> </server>
-
在 mirrors 中只配置我们自己私服的连接地址(如果之前配置过阿里云,需要直接替换掉)
<mirror> <id>maven-public</id> <mirrorOf>*</mirrorOf> <url>http://192.168.150.101:8081/repository/maven-public/</url> </mirror>
-
需要在 profiles 中,增加如下配置,来指定snapshot快照版本的依赖,依然允许使用
<profile> <id>allow-snapshots</id> <activation> <activeByDefault>true</activeByDefault> </activation> <repositories> <repository> <id>maven-public</id> <url>http://192.168.150.101:8081/repository/maven-public/</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> </profile>
-
如果需要上传自己的项目到私服上,需要在项目的pom.xml文件中,增加如下配置,来配置项目发布的地址(也就是私服的地址)
<distributionManagement> <!-- release版本的发布地址 --> <repository> <id>maven-releases</id> <url>http://192.168.150.101:8081/repository/maven-releases/</url> </repository> <!-- snapshot版本的发布地址 --> <snapshotRepository> <id>maven-snapshots</id> <url>http://192.168.150.101:8081/repository/maven-snapshots/</url> </snapshotRepository> </distributionManagement>
-
发布项目,直接运行 deploy 生命周期即可 (发布时,建议跳过单元测试)
启动本地私服
-
解压: apache-maven-nexus.zip
-
进入目录: apache-maven-nexus\nexus-3.39.0-01\bin
- 启动服务:双击 start.bat
- 访问服务:localhost:8081
- 私服配置说明:将上述配置私服信息的 192.168.150.101 改为 localhost