对于前端知识,需要进一步巩固和加强,进入企业之后,要具备一定的接口调试,参数接收的能力,以及单体页面的开发,这里我学习一下前端知识巩固一下自身的技术栈和水平。本次笔记是跟学黑马的同名课程,巩固一下自己的前端知识水平,希望入职能用到。
Java程序员需要掌握的前端知识 一
- 前言
- 课程安排
- 课程目标
- 第一章. HTML 与 CSS
- 1. HTML 元素
- 2. HTML 页面
- 3. 常见元素
- 1) 文本
- Heading
- Paragraph
- List
- Anchor
- (1).跳转本地网页
- (2).跳转互联网网页
- (3).跳转页面内锚点
- 2) 多媒体
- Image
- Video
- Audio
- 3) 表单
- 作用与语法
- 常见的表单项
- 文本框
- 密码框
- 隐藏框
- 日期框
- 单选
- 多选
- 文件上传
- 4. HTTP 请求
- 1) 请求组成
- 请求行(必须)
- 请求头(必须)
- 请求体(非必须)
- 2) 请求方式与数据格式
- get 请求示例
- post 请求示例
- 多个参数
- post请求多参数情况
- get请求多参数情况
- 汉字发送
- json 请求示例
- multipart 请求示例
- 数据格式小结
- 3) session 原理
- 存
- 取
- 4) jwt 原理
- 生成 token
- 校验 token
- 5. CSS
- 1) 选择器
- type 选择器 - 根据标签名进行匹配(元素选择器)
- class 选择器 - 根据元素的 class 属性进行匹配
- id 选择器 - 根据元素的 id 属性进行匹配
- 2) 属性和值
- 3) 布局
- div
- template
前言
Java 程序员一提起前端知识,心情那是五味杂陈,百感交集。
- 说不学它吧,说不定进公司以后,就会被抓壮丁去时不时写点前端代码
- 说学它吧,HTML、CSS、JavaScript 哪个不得下大功夫才能精通?
- 学一点够不够用呢?如果只学基础的 JavaScript 是不够用的,前端都已经工程化了,Vue、React 这些框架你去看吧,光有点基础根本看不懂,甚至连前端页面路径在哪儿配置,如何跳转都不甚了解,所以得学,而且要学的还不少,请把前端当作 web 不可或缺的一部分来学习。
- 学习前端好处挺多,我听说过这么一句挺有道理的话:一个程序员至少应该掌握一门静态语言,如 Java,还应该掌握一门动态语言,如 JavaScript。而且,你不觉得学了前端,就如打通了程序员的任督二脉,可以独立接活了嘛
这门课为什么不由前端老师来讲?
- 前端老师不知道后端学员的痛点,他认为重要的咱不关心,他认为是常识一带而过的又恰恰是咱迷茫的地方,作为后端老师,我更懂后端学员
- 前端老师不懂后端知识,只能用前端思维来讲前端,如果授课老师懂 Java,能用一些现有知识对比讲解,能起到快速突破的效果
- 前端每个框架动辄需要数十个小时的学习,咱耗不起,我们更希望学到对我们最有用的那部分,其它的省省吧,总不能夺了前端程序员的饭碗
课程安排
- 整个课程分成五章
- HTML / CSS 这部分对咱们来说,不是重点,但又不能不讲,这俩知识作为第一章,必学
- JavaScript 这部分是重点,尤其是 ES6 以后的一些新语法,不理解这些,前端代码你根本看不懂,必学
- Vue2,Vue3,React 这三章是三选一的关系,根据你入职公司的使用的前端技术不同,有针对地学习
- 后三章会涵盖 TypeScript、VueCli、Vuex、VueRouter、ElementUI、Vite、CreateReactApp、React、Redux、ReactRouter 等库和工具的使用
- jquery 经过调研,还有一些学员毕业后确实会用到,所以也作为一个可选章节进行学习
课程安排
课程目标
第一章. HTML 与 CSS
HTML 是什么:即 HyperText Markup language 超文本标记语言,咱们熟知的网页就是用它编写的,HTML 的作用是定义网页的内容和结构。
什么是HyperText呢,我们看下资料提供的课件。
详见 代码\第1章\example1-什么是html和css 1.html
点击网页2
点击网页3
那什么是Markup language呢?
我们右键,以VS Code打开。
这些以尖括号括起来的部分,我们都称之为Markup。在前端中,我们称之为标签。
- HyperText 是指用超链接的方式组织网页,把网页联系起来
- Markup 是指用
<标签>
的方式赋予内容不同的功能和含义
CSS 是什么:即 Cascading Style Sheets 级联(层叠)样式表,它描述了网页的表现与展示效果
我们引入style.css样式。
可以看到超链接的字体和背景颜色发生了变化。
1. HTML 元素
HTML 由一系列元素 elements
组成,例如
<p>Hello, world!</p>
-
整体称之为元素
-
<p>
和</p>
分别称为起始标签和结束标签 -
标签包围起来的 Hello, world 称之为内容
-
p 是预先定义好的 html 标签,作用是将内容作为一个单独的段落
在 代码\第1章\example2-html元素下新建文件3.html我们自己写一个元素
代码如下:
<p>Hello, world!</p>
用浏览器打开
效果如下:
如果我们想增加标签数量,通过快捷键复制
ALT + SHIFT + ↓
展示结果如下:
这里小提示:如果我们熟悉IDEA的快捷键,我们可以通过下载插件,改变快捷键习惯为IDEA的
元素还可以有属性,如
<p id="p1">Hello, world! 1</p>
<p id="p2">Hello, world! 2</p>
<p id="p3">Hello, world! 3</p>
<p id="p4">Hello, world! 4</p>
- 属性一般是预先定义好的,这里的 id 属性是给元素一个唯一的标识
除此属性之外,我们还可以增加title标题属性
<p id="p1" title="标题1">Hello, world! 1</p>
<p id="p2">Hello, world! 2</p>
<p id="p3">Hello, world! 3</p>
<p id="p4">Hello, world! 4</p>
网页展示如下:鼠标悬浮后,出现标题1
元素之间可以嵌套,如
<p id="p1" title="标题1">Hello, world! 1 I am <b>Cony</b></p>
<p id="p2">Hello, world! 2</p>
<p id="p3">Hello, world! 3</p>
<p id="p4">Hello, world! 4</p>
错误嵌套写法:
标签不能交叉
<p>HTML 是一门非常<b>强大的语言</p></b>
不包含内容的元素称之为空元素,如
<img src="1.png">
<img src="1.png"/>
- img 作用是用来展示图片
- src 属性用来指明图片路径
2. HTML 页面
前面介绍的只是单独的 HTML 元素,它们可以充当一份完整的 HTML 页面的组成部分
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>测试页面</title>
</head>
<body>
<p id="p1">Hello, world!</p>
<img src="1.png">
</body>
</html>
html
元素囊括了页面中所有其它元素,整个页面只需一个,称为根元素head
元素包含的是那些不用于展现内容的元素,如title
,link
,meta(指定编码)
等body
元素包含了对用户展现内容的元素,例如后面会学到的用于展示文本、图片、视频、音频的各种元素
在 代码\第1章\example3-html页面 目录下新建网页文件2.html
快捷键!,可以直接生成html的模版
这里我们修改代码为:
<!DOCTYPE html>
<!-- 网页展示为:中文(默认) -->
<html lang="zh">
<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>这是一个完整页面</title>
</head>
<body>
<p>Hello World</p>
<img src="1.png">
</body>
</html>
用浏览器打开后的效果如下:
图片宽度比较大,我们重新设置一下宽度的像素
<img src="1.png" width="300">
再看浏览器如下:
3. 常见元素
1) 文本
Heading
<h1>1号标题</h1>
<h2>2号标题</h2>
<h3>3号标题</h3>
<h4>4号标题</h4>
<h5>5号标题</h5>
<h6>6号标题</h6>
效果如下:
Paragraph
<p>段落</p>
代码如下
<p id="p1" title="悬浮1">段落1</p>
<p id="p2" title="悬浮2">段落2</p>
<p id="p3" title="悬浮3">段落3</p>
效果:
List
无序列表 unordered list
<ul>
<li>列表项1</li>
<li>列表项2</li>
<li>列表项3</li>
</ul>
有序列表
ordered list
<ol>
<li>列表项1</li>
<li>列表项2</li>
<li>列表项3</li>
</ol>
多级列表
<ul>
<li>
北京市
<ul>
<li>海淀区</li>
<li>朝阳区</li>
<li>昌平区</li>
</ul>
</li>
<li>
河北省
<ul>
<li>石家庄</li>
<li>保定</li>
</ul>
</li>
</ul>
Anchor
锚,超链接
<a href="网页地址">超链接文本</a>
(1).跳转本地网页
本地网页(同目录)
<a href="2.html">本地网页</a>
(2).跳转互联网网页
<a href="http://www.baidu.com">互联网网页</a>
单击后
(3).跳转页面内锚点
<a href="#p1">页面内锚点</a>
<hr>
<br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br>
<p id="p1">很下面的内容 <a href="#">回到顶部</a></p>
点击后,到p1所在的标签位置
2) 多媒体
Image
<img src="文件路径">
src 格式有 3 种
- 文件地址
比如
<img src="heima.png">
- data URL,格式如下
data:媒体类型;base64,数据
这里要把图片转换为Base64的二进制数据,具体方法如下
@Test
public void test(){
String path = "E:\\黑马\\前端\\代码\\代码\\第1章\\example4-常见元素\\heima.png";
byte[] bytes = null;
try {
Path path2 = Paths.get(path);
bytes = Files.readAllBytes(path2);
// 转换成BASE64Encoder
BASE64Encoder encoder = new BASE64Encoder();
String content = encoder.encode(bytes);
System.out.println(content);
} catch (IOException e) {
e.printStackTrace();
}
}
输出结果如下:
页面展示如下:
- object URL,需要配合 javascript 使用
这个后续js的时候介绍
Video
<video src="文件路径"></video>
代码如下:
<!-- controls是显示播放进度条 -->
<video src="test.mp4" width="300" controls autoplay></video>
页面:
Audio
<audio src="文件路径"></audio>
代码如下:
<audio src="bgm.mp3" controls></audio>
页面如下:
这里推荐一个网站,遇事不决MDN
MDN
3) 表单
作用与语法
表单的作用:收集用户填入的数据,并将这些数据提交给服务器
表单的语法
<form action="服务器地址" method="请求方式" enctype="数据格式">
<!-- 表单项 -->
<input type="submit" value="提交按钮">
</form>
- method 请求方式有
- get (默认)提交时,数据跟在 URL 地址之后
- post 提交时,数据在请求体内
- enctype 在 post 请求时,指定请求体的数据格式
- application/x-www-form-urlencoded(默认)
- multipart/form-data
- 其中表单项提供多种收集数据的方式
- 有 name 属性的表单项数据,才会被发送给服务器
常见的表单项
文本框
<input type="text" name="uesrname">
这里我们自己写一个表单
<form action="https://www.baidu.com/s">
<input type="text" name="wd">
<input type="submit" value="搜索">
</form>
<hr>
页面如下:
加深难度,结合springboot
前端:
<form action="http://localhost:8077/test">
<input type="text" name="username">
<input type="submit" name="提交">
</form>
后端代码:
@Controller
public class MyController {
@RequestMapping("/test")
@ResponseBody
public String test(String username) {
System.out.println("username:" + username);
return "收到数据";
}
}
这里 @Controller + @ResponseBody 可以用@RestController替代
前端页面:
点击提交后,后端运行结果如下:
密码框
<input type="password" name="password">
增加密码框
<form action="http://localhost:8077/test">
账号:<input type="text" name="username">
<hr>
密码:<input type="password" name="password">
<input type="submit" name="提交">
</form>
页面如下:
隐藏框
<input type="hidden" name="id">
页面中没有显示
那如果我们想通过隐藏框传递值呢,这里我们可以给隐藏框赋值。
<input type="hidden" name="id" value="1">
前台代码如下:
<form action="http://localhost:8077/test">
账号:<input type="text" name="username">
<hr>
密码:<input type="password" name="password">
<br>
<input type="submit" name="提交">
<hr>
<input type="hidden" name="id" value="1">
</form>
后台代码:
@RequestMapping("/test")
public String test(User user) {
System.out.println("username:" + user.getUsername());
System.out.println("id:" + user.getId());
return "收到数据";
}
新建一个实体类
/**
* @ClassName: User
* @Description:
* @Author: wty
* @Date: 2023/7/13
*/
public class User {
private String username;
private String password;
private Integer id;
private LocalDate birthday;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public LocalDate getBirthday() {
return birthday;
}
public void setBirthday(LocalDate birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", id=" + id +
", birthday=" + birthday +
'}';
}
}
页面展示:
输出如下:
日期框
<input type="date" name="birthday">
后台增加属性
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate birthday;
public LocalDate getBirthday() {
return birthday;
}
public void setBirthday(LocalDate birthday) {
this.birthday = birthday;
}
后台输出如下:
单选
<input type="radio" name="sex" value="男" checked>
<input type="radio" name="sex" value="女">
页面如下:
<!-- 性别 -->
男<input type="radio" name="sex" value="男" checked>
女<input type="radio" name="sex" value="女">
checked属性表示,界面加载过程中默认选中一个。
界面如下:
后台输出如下:
多选
<input type="checkbox" name="fav" value="唱歌">
<input type="checkbox" name="fav" value="逛街">
<input type="checkbox" name="fav" value="游戏">
前台如下:
<!-- 多选 -->
唱歌<input type="checkbox" name="hobby" value="唱歌">
逛街<input type="checkbox" name="hobby" value="逛街">
游戏<input type="checkbox" name="hobby" value="游戏">
<hr>
页面如下:
后端如下:
private List<String> hobby;
public List<String> getHobby() {
return hobby;
}
public void setHobby(List<String> hobby) {
this.hobby = hobby;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", id=" + id +
", sex='" + sex + '\'' +
", birthday=" + birthday +
", hobby=" + hobby +
'}';
}
文件上传
<input type="file" name="avatar">
注意:上传文件必须加如下属性
<form action="http://localhost:8077/test" method="post" enctype="multipart/form-data">
<!-- 文件上传 -->
<input type="file" name="avatar">
</form>
后台如下:
private MultipartFile avatar;
@Override
public MultipartFile getAvatar() {
return avatar;
}
public void setAvatar(MultipartFile avatar) {
this.avatar = avatar;
}
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", id=" + id +
", sex='" + sex + '\'' +
", birthday=" + birthday +
", hobby=" + hobby +
", avatar=" + avatar.getSize() +
'}';
}
后台输出如下:
4. HTTP 请求
- method 请求方式有
- get (默认)提交时,数据跟在 URL 地址之后
- post 提交时,数据在请求体内
- enctype 在 post 请求时,指定请求体的数据格式
- application/x-www-form-urlencoded(默认)
- multipart/form-data
- 其中表单项提供多种收集数据的方式
- 有 name 属性的表单项数据,才会被发送给服务器
1) 请求组成
请求由三部分组成
- 请求行
- 请求头
- 请求体
请求行(必须)
红色标注
这里可以看到 GET /test2?name=tony HTTP/1.1 叫请求行
POST是请求方式
/test2?name=tony是URI
HTTP/1.1是协议版本
请求头(必须)
绿色标注
请求头:
格式:头名:头值
localhost:虚拟主机
请求体(非必须)
蓝色标注的,并不是请求都会携带
可以用 telnet 程序测试
2) 请求方式与数据格式
get 请求示例
GET /test2?name=%E5%BC%A0&age=20 HTTP/1.1
Host: localhost
- %E5%BC%A0 是【张】经过 URL 编码后的结果
java后台这么写
@RequestMapping("/test2")
@ResponseBody
public String test2(String name) {
System.out.println("name:" + name);
return "收到:" + name;
}
这里我们这么发送
GET /test2?name=tony HTTP/1.1
Host: localhost
这里要用虚拟机安装telnet,这里我的是CentOS,指令如下:
yum install telnet -y
安装好后,用虚拟机连接windows本机
telnet ip 端口号
回车,连接上后,输入
GET /test2?name=tony HTTP/1.1
Host: localhost
敲击两次回车后,返回结果:
post 请求示例
POST /test2 HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
Content-Length: 21
name=%E5%BC%A0&age=18
application/x-www-form-urlencoed 格式细节:
- 参数分成名字和值,中间用 = 分隔
- 多个参数使用 & 进行分隔
- 【张】等特殊字符需要用 encodeURIComponent() 编码为 【%E5%BC%A0】后才能发送
这里我们测试一下,请求如下:
POST /test2 HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
Content-Length: 10
name=zhang
这里Content-Length = 10不是随便定义的,根据name=zhang一个英文字符占一个长度,共10个字节。
linux收到响应
多个参数
post请求多参数情况
如果参数是多个呢,这里我们修改一下Java后端的代码
@RequestMapping("/test22")
@ResponseBody
public String test22(String name, Integer age) {
System.out.println("name:" + name + ",age:" + age);
return "收到name:" + name + ",age:" + age;
}
telnet测试如下:
POST /test22 HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
Content-Length: 16
name=Lucy&age=18
输出如下:
这里Content-Length是多少,我们可以简单学习javascript
打开浏览器输入F12在控制台中输入:
"name=Lucy&age=18".length
回车后
这里16就是Content-Length的值。
get请求多参数情况
这里我们再尝试一下get请求多参数的情况
GET /test22?name=tony&age=20 HTTP/1.1
Host: localhost
返回如下:
汉字发送
在浏览器按F12,打开控制台
encodeURIComponent("张")
回车后,就出来了汉字的编码
'%E5%BC%A0'
我们再发送一下GET请求
GET /test22?name=%E5%BC%A0&age=20 HTTP/1.1
Host: localhost
收到结果:
那么“张”这个汉字,是怎么变成URL Code的呢?我们研究一下。
也就是说%E5%BC%A0是怎么来的。
我们不妨先用Java代码,把“张”转换成对应的UTF-8格式
@Test
public void test() throws Exception {
// utf-8
byte[] bytes = "张".getBytes(StandardCharsets.UTF_8);
System.out.println(bytes);
for (byte b : bytes) {
System.out.println(Integer.toHexString(Byte.toUnsignedInt(b)));
}
}
输出结果:
[B@50de0926
e5
bc
a0
json 请求示例
POST /test3 HTTP/1.1
Host: localhost
Content-Type: application/json
Content-Length: 25
{"name":"zhang","age":18}
json 对象格式
{"属性名":属性值}
其中属性值可以是
- 字符串 “”
- 数字
- true, false
- null
- 对象
- 数组
json 数组格式
[元素1, 元素2, ...]
我们发送如下:
POST /test3 HTTP/1.1
Host: localhost
Content-Type: application/json
Content-Length: 25
{"name":"zhang","age":18}
Java后台新建实体类
package com.itheima.pojo;
/**
* @ClassName: Req
* @Description:
* @Author: wty
* @Date: 2023/7/13
*/
public class Req {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Req{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Controller如下:
这里注意,接收json格式的数据,必须加上@RequestBody注解。
@RequestMapping("/test3")
@ResponseBody
public Req test3(@RequestBody Req req) {
System.out.println(req);
return req;
}
返回如下:
这里的19和0是分块发送的,最终字符数量是0,就停止发送。
这里发送的时候Content-Length: 25 明明是25
接收的时候怎么变成19
Java后台输出如下:
如果是JDK16,还可以使用Req,替代实体类,但是Req相当于get,初次赋值后不会更改,而没有set方法,是不可变的对象。
@RequestMapping("/test3")
public Req test3(@RequestBody Req req) {
System.out.println(req);
return req;
}
record Req(String name,Integer age){}
注意:json汉字可以照常发送
打开浏览器输入F12,之后输入
'{"name":"张","age":18}'.length
返回:21(字符)
因为传递是按照字节的,UTF-8中,一个汉字是三个字节,所以
21 -1 + 3 = 23
所以Content-Length是23
POST /test3 HTTP/1.1
Host: localhost
Content-Type: application/json
Content-Length: 23
{"name":"张","age":18}
结果如下:
multipart 请求示例
POST /test22 HTTP/1.1
Host: localhost
Content-Type: multipart/form-data; boundary=123
Content-Length: 125
--123
Content-Disposition: form-data; name="name"
lisi
--123
Content-Disposition: form-data; name="age"
30
--123--
- boundary=123 用来定义分隔符
- 起始分隔符是
--分隔符
- 结束分隔符是
--分隔符--
Java后端如下:
@RequestMapping("/test22")
@ResponseBody
public String test22(String name, Integer age) {
System.out.println("name:" + name + ",age:" + age);
return "收到name:" + name + ",age:" + age;
}
telnet结果如下:
这里有个疑问
Content-Length: 125是如何计算的
·--123
Content-Disposition: form-data; name="name"
lisi
--123
Content-Disposition: form-data; name="age"
30
--123--·.length
这里要引入反引号,最后答案是116
116相比于125少了9个字节,那这9个字节差在哪儿呢。就差在换行符上
在换行中其实有两个符号\r和\n,而JavaScript中只计算了一个,而我们的多行数据,刚好是9行,所以要加9
数据格式小结
客户端发送
- 编码
- application/x-www-form-urlencoded :url 编码
- application/json:utf-8 编码
- multipart/form-data:每部分编码可以不同
- 表单只支持以 application/x-www-form-urlencoded 和 multipart/form-data 格式发送数据
- 文件上传需要用 multipart/form-data 格式
- js 代码可以支持任意格式发送数据
服务端接收
- 对 application/x-www-form-urlencoded 和 multipart/form-data 格式的数据,Spring 接收方式是统一的,只需要用 java bean 的属性名对应请求参数名即可
- 对于 applicaiton/json 格式的数据,Spring 接收需要使用 @RequestBody 注解 + java bean 的方式
3) session 原理
Http 无状态,有会话
- 无状态是指,请求之间相互独立,第一次请求的数据,第二次请求不能重用
- 有会话是指,客户端和服务端都有相应的技术,可以暂存数据,让数据在请求间共享
服务端使用了 session 技术来暂存数据
存
GET /s1?name=zhang HTTP/1.1
Host: localhost
Java后端如下:
@RequestMapping("/s1")
@ResponseBody
public String s1(HttpSession session, String name) {
session.setAttribute("name", name);
return "数据已存储";
}
返回数据如下:
jsessionid = 5EC247CB0B301ACBFE90157D6F723CC5
取
GET /s2 HTTP/1.1
Host: localhost
Cookie: JSESSIONID=5EC247CB0B301ACBFE90157D6F723CC5
Java后端代码如下:
@RequestMapping("/s2")
@ResponseBody
public String s2(HttpSession session) {
return "取出数据" + session.getAttribute("name");
}
输出结果如下:
session 技术实现身份验证
缺点:不适用于分布式项目,因为分布式项目有多台服务器,如果每一台都存储一份完整session,显然不太合理,这里引出了jwt技术。
4) jwt 原理
jwt 技术实现身份验证
生成 token
GET /j1?name=zhang&pass=123 HTTP/1.1
Host: localhost
token为
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ6aGFuZyJ9.qmta3qjU3XKd8di9n6h9DhbTznJYb75v0CESA7ZLx0E
Java后端代码
@RequestMapping("/j1")
@ResponseBody
public String j1(String name, String pass) {
if (!StringUtils.isEmpty(name) && !StringUtils.isEmpty(pass)) {
if ("zhang".equals(name) && "123".equals(pass)) {
String token = Jwts.builder().setSubject(name).signWith(key).compact();
return "验证身份通过:" + token;
} else {
return "验证身份失败";
}
}
return "用户名或者密码为空,验证身份失败";
}
校验 token
GET /j2 HTTP/1.1
Host: localhost
Authorization: eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ6aGFuZyJ9.qmta3qjU3XKd8di9n6h9DhbTznJYb75v0CESA7ZLx0E
Java后端代码
@RequestMapping("/j2")
@ResponseBody
public String j2(@RequestHeader String authorization) {
try {
System.out.println(authorization);
Jws<Claims> jws = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(authorization);
return "校验通过, 你是:" + jws.getBody().getSubject();
} catch (Exception e) {
return "校验失败";
}
}
输出结果如下:
这里有个问题,就是这个长字符串的token有什么用,这里我们在IDEA中编写一个方法
@Test
public void test() {
// header(签名算法) payload(数据) 签名
// eyJzdWIiOiJhZG1pbiJ9
// token分成了3个部分,每个部分由.进行分割
// 第一个部分 eyJhbGciOiJIUzI1NiJ9 header(签名算法)未加密
// 第二个部分 eyJzdWIiOiJ6aGFuZyJ9 payload(数据)未加密
// 第三个部分 qmta3qjU3XKd8di9n6h9DhbTznJYb75v0CESA7ZLx0E 签名 加密
String token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ6aGFuZyJ9.qmta3qjU3XKd8di9n6h9DhbTznJYb75v0CESA7ZLx0E";
// 1 2 3 ==> 6
// 1 4 3 ==> 8
// 第一个部分
System.out.println(new String(Base64.getDecoder().decode("eyJhbGciOiJIUzI1NiJ9")));
// 第二个部分
System.out.println(new String(Base64.getDecoder().decode("eyJzdWIiOiJ6aGFuZyJ9")));
// 第三个部分
// admin 权限大的用户 可以做所有的操作
//String str = "{\"sub\":\"zhang\"}";
// eyJzdWIiOiJ6aGFuZyJ9
String str = "{\"sub\":\"admin\"}";
// eyJzdWIiOiJhZG1pbiJ9
System.out.println(Base64.getEncoder().encodeToString(str.getBytes(StandardCharsets.UTF_8)));
}
我们把中间的权限名称替换一下
替换成admin用户的
GET /j2 HTTP/1.1
Host: localhost
Authorization: eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiJ9.qmta3qjU3XKd8di9n6h9DhbTznJYb75v0CESA7ZLx0E
我们访问试了下,发现没有骗过服务器
那为什么服务器能检验出问题呢,主要在第三部分签名上,签名是根据前两个部分生成了秘钥。
//
// 1 2 3 ==> 6
// 1 4 3 ==> 8
5. CSS
即 Cascading Style Sheets,它描述了网页的表现与展示效果
1) 选择器
type 选择器 - 根据标签名进行匹配(元素选择器)
新建一个文件style-exercise.css
p {
background-color: blueviolet;
}
html加上如下代码
<link rel="stylesheet" href="style-exercise.css">
效果如下:段落标签的背景颜色变为了紫色
class 选择器 - 根据元素的 class 属性进行匹配
修改html中需要修改的部分标签
<body>
<p id="p1">1111111111111</p>
<p id="p2" class="c2">2222222222222</p>
<p id="p3" class="c3">3333333333333</p>
</body>
修改style-exercise.css
/* class选择器 */
.c2 {
background-color: brown;
}
.c3 {
background-color: chartreuse;
}
效果如下:
id 选择器 - 根据元素的 id 属性进行匹配
修改html文件
<body>
<p id="p1">1111111111111</p>
<p id="p2">2222222222222</p>
<p id="p3">3333333333333</p>
</body>
修改style-exercise.css
/* id选择器 */
#p1 {
background-color: cornflowerblue;
}
效果如下:
这里我们不禁要问,如果多个选择器都匹配到了同一个元素,那么优先级是怎么样的呢?
css代码如下:
/* 元素选择器-type */
p {
background-color: blueviolet;
}
/* class选择器 */
.c2 {
background-color: brown;
}
/* id选择器 */
#p2 {
background-color: cornflowerblue;
}
我们都修改段落2的样式
最后效果如下:
结论:
id选择器 > class选择器 > 选择选择器
2) 属性和值
- background-color : red;
- …
- display
详见
CSS样式查询
这里简要讲解一下display属性
代码如下:
#p2 {
background-color: cornflowerblue;
display: none;
}
页面如下:发现p2被隐藏了
如果更改为
/* id选择器 */
#p2 {
background-color: cornflowerblue;
display: block;
}
发现p2就展示出来了
3) 布局
与布局相关的 html 元素
div
资料提供的界面如下:
<!DOCTYPE html>
<html lang="zh">
<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>布局</title>
<style>
html,body {
margin:0;
width: 100%;
height: 100%;
text-align: center;
font-size: 30px;
font-weight: bold;
}
div{
box-sizing: border-box;
}
.container {
height: 100%;
position: relative;
}
#header {
background-color:rgb(152, 152, 255);
width: 100%;
height: 80px;
padding-top: 10px;
}
#aside {
background-color:aquamarine;
float: left;
width: 200px;
height: calc(100% - 140px);
padding-top: 10px;
}
#main {
background-color:honeydew;
float: left;
width: calc(100% - 200px);
height: calc(100% - 140px);
padding-top: 10px;
padding-left: 20px;
text-align: left;
}
#footer {
background-color:darksalmon;
height: 60px;
padding-top: 10px;
}
</style>
</head>
<body>
<div class="container">
<div id="header">#header</div>
<div id="aside">#aside</div>
<div id="main">#main</div>
<div style="clear: both;"></div>
<div id="footer">#footer</div>
</div>
</body>
</html>
效果如图:
这里style可以理解为内部样式,之前我们新建css,通过link引入,这个叫做外部样式。
我们给div id ="main"中增加type类型的文本
<body>
<div class="container">
<div id="header">#header</div>
<div id="aside">#aside</div>
<div id="main">
#main
<form action="">
<input type="text" value="文本">
</form>
</div>
<div style="clear: both;"></div>
<div id="footer">#footer</div>
</div>
</body>
样式如下:增加了文本
template
先看下资料的源码
<!DOCTYPE html>
<html lang="zh">
<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>模板</title>
<style>
html,
body {
margin: 0;
width: 100%;
height: 100%;
}
.btn {
padding: 10px;
}
.out {
width: 100%;
height: 100%;
box-sizing: border-box;
background-color:darkgrey;
}
.in {
width: 200px;
box-sizing: border-box;
height: 200px;
border: solid 2px black;
padding: 10px;
background-color: antiquewhite;
margin: 10px;
float: left;
}
</style>
</head>
<body>
<div class="out">
<div class="btn">
<input type="button" value="根据模板创建" id="add">
</div>
</div>
<template id="t">
<div class="in">
<form action="">
<p><label>姓名</label> <input type="text"></p>
<p><label>年龄</label> <input type="text"></p>
<p><input type="submit" value="添加"></p>
</form>
</div>
</template>
<script>
document.getElementById("add").onclick = () => {
let t = document.getElementById("t");
let inputs = t.content.querySelectorAll("input");
inputs[0].value = randomGenerator("abcdefghijklmnopqrstuvwxyz", 5);
inputs[1].value = randomGenerator("1234567890", 2);
const c = document.importNode(t.content, true);
document.querySelector(".out").appendChild(c);
}
function randomGenerator(str, n) {
const result = [];
for (let i = 0; i < n; i++) {
result.push(str.charAt(Math.floor(Math.random() * str.length)))
}
return result.join("");
}
</script>
</body>
</html>
效果如下:点击模版之后
通过JavaScript动态将姓名和年龄加入到了该布局中