Node.js Basics (2)

  • JSONP: 浏览器端通过<script>标签的src属性, 请求服务器上的数据; 服务器返回一个函数的调用

必须在配置CORS中间件之前声明JSONP的接口

1
2
3
4
5
6
app.get("/api/jsonp", (req, res) => {
const funcName = req.query.callback // 得到函数名称
const data = {name: "zhangsan", age: 20} // 定义要发送给客户端的数据对象
const scriptStr = "${funcName}(${JSON.stringify(data)})"
res.send(scriptStr)
})
  • mysql模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
npm init -y
npm install mysql

const mysql = require("mysql")
const db = mysql.createPool({
host: "127.0.0.1",
user: "zyliang",
password: "123123123",
database: "my_db_01"
})

// 调用db.query()函数, 指定要执行的SQL语句
// select
db.query("select * from users", (err, results) => {
if(err) return console.log(err.message)
console.log(results)
})
// insert
const user = {username: "test1", password: "test2"}
const sqlStr = "insert into users (username, password) values (?, ?)"
db.query(sqlStr, [user.username, user.password], (err, results) => {
if(err) return console.log(err.message)
if(results.affectedRows === 1){
console.log("插入数据成功")
}
})
// 简化写法
const sqlStr = "insert into users set ?"
db.query(sqlStr, user, (err, results) => {
if(err) return console.log(err.message)
if(results.affectedRows === 1){
console.log("插入数据成功")
}
})
// update
const user = {id: 7, username: "test1", password: "test2"}
const sqlStr = "update users set username=?, password=? where id=?"
db.query(sqlStr, [user.username, user.password, user.id], (err, results) => {
if(err) return console.log(err.message)
if(results.affectedRows === 1){
console.log("更新数据成功")
}
})
// 简化写法
const sqlStr = "update users set ? where id=?"
db.query(sqlStr, [user, user.id], (err, results) => {
if(err) return console.log(err.message)
if(results.affectedRows === 1){
console.log("更新数据成功")
}
})
// delete
const sqlStr = "delete from users where id=?"
db.query(sqlStr, 7, (err, results) => {
if(err) return console.log(err.message)
if(results.affectedRows === 1){
console.log("删除数据成功")
}
})
  • web开发模式:

基于服务器渲染(传统): 服务器发送给客户端的HTML页面, 是在服务器通过字符串拼接动态生成的; 用Session认证机制来进行身份认证

1
2
3
4
5
app.get("index.html", (req, res) => {
const user = {name: "zhangsan", age: 20}
const html = "<h1>姓名: ${user.name}, 年龄: ${user.age}</h1>"
res.send(html)
})

基于前后端分离(新型): 后端只负责提供API接口, 前端使用Ajax调用接口; 用JWT认证机制来进行身份认证

HTTP协议的无状态性: 每次HTTP请求都是独立的, 服务器不会保留每次HTTP请求的状态

Cookie: 存储在用户浏览器中的一段不超过4KB的字符串, 有1个名称, 1个值, 几个可选属性(关于有效期, 安全性, 使用范围), 类似于现实中会员卡身份认证方式

Cookie特性: 自动发送(客户端发起请求时); 域名独立; 有过期时限; 4KB限制

客户端第一次请求服务器时, 服务器通过响应头的形式, 向客户端发送一个身份验证相关的Cookie, 客户端会自动将Cookie保存在浏览器中; 当客户端再次请求服务器, 浏览器会自动将身份验证相关的Cookie通过请求头的形式发送给服务器, 服务器即可验证客户端的身份

  • express-session中间件: 使用session认证

局限性: Cookie不支持跨域访问, 前端跨域请求后端接口时需要额外配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
npm install express-session
var session = require("express-session")
app.use(session({
secret: "keyboard cat", // 可为任意字符串
resave: false,
saveUninitialized: true
}))
// express-session中间件配置成功后, 即可通过req.session来访问和使用session对象
// 从而存储用户的关键信息

// 登录的API接口
app.post("/api/login", (req, res) => {
if(req.body.username !== "admin" || req.body.password !== "123123123"){
return res.send({status: 1, msg="登陆失败"})
}
req.session.user = req.body
req.session.islogin = true
res.send({status: 0, msg: "登陆成功"})
})
// 获取用户姓名的接口
app.post("/api/username", (req, res) => {
if(!req.session.islogin){
return res.send({status: 1, msg="fail"})
}
res.send({status: 0, msg: "success", username: req.session.user.username})
})
// 退出登录的接口
app.post("/api/logout", (req, res) => {
req.session.destroy()
res.send({status: 0, msg: "退出登陆成功"})
})
  • JWT(JSON Web Token): 目前最流行的跨域认证解决方案

JWT由三部分组成: Header(头部), Payload(有效载荷, 即用户信息经过加密后生成的字符串), Signature(签名), 三者之间通过”.”进行分隔

使用: 客户端收到服务器返回的JWT后, 通常会将其存储在localStorage或sessionStorage中

之后客户端每次与服务器通信, 都要带上这个JWT字符串进行身份验证, 最好是把JWT放在HTTP请求头的Authorization字段中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
Authorization: Bearer <token>
npm install jsonwebtoken express-jwt
// jsonwebtoken用于生成JWT字符串
// express-jwt用于将JWT字符串解析还原成JSON对象
const jwt = require("jsonwebtoken")
const expressJWT = require("express-jwt")
// 为防止JWT字符串在网络传输过程中被破解, 需要专门定义一个用于加密和解密的secret密钥
// 生成JWT字符串时需要用secret密钥对用户信息进行加密
// 当把JWT字符串解析还原成JSON对象时需要使用secret密钥进行解密

// 登陆成功后, 调用jwt.sign()方法来生成JWT字符串, 并通过token属性发送给客户端
const secretKey = "zyliang666"
// 参数: 用户的信息对象, 加密的密钥, 配置对象
const tokenStr jwt.sign({username: userinfo.username}, secretKey, {expiresIn: "1h"})

app.post("/api/login", function(req, res){
res.send({
status: 200,
message: "登陆成功",
token: jwt.sign({username: userinfo.username}, secretKey, {expiresIn: "1h"})
})
})

// 将JWT字符串还原为JSON对象
app.use(expressJWT({secret: secretKey})).unless({path: [/^\\/api\\//]})
// 以"/api"开头的不需要访问权限; 以"/my"开头的需要访问权限
// 配置express-jwt中间件后, 就能把解析出来的用户信息挂载到req.user属性上
// 可通过如下方式获取用户信息
app.get("/admin/getinfo", function(req, res){
console.log(req.user)
res.send({
status: 200,
message: "获取用户信息成功",
data: req.user,
})
})

// 全局错误处理中间件
app.use((err, req, res, next) => {
if(err.name === "UnauthorizedError"){
return res.send({
status: 401,
message: "无效token",
})
}
res.send({
status: 500,
message: "未知错误"
})
})
  • AJAX: 异步的JS和XML; 可以在浏览器中向服务器发送异步请求; 优势为不需要刷新就能获取数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
const express = require("express");
const app = express();
// req是对请求报文的封装, res是对响应报文的封装
app.get("/server", (req, res) => {
// 设置响应头, 允许跨域
res.setHeader("Access-Control-Allow-Origin", "*");
// 设置响应体
res.send("hello ajax")
})

app.post("/server", (req, res) => {
// 设置响应头, 允许跨域
res.setHeader("Access-Control-Allow-Origin", "*");
// 设置响应体
res.send("hello ajax post")
})

app.all("/json-server", (req, res) => {
// 设置响应头, 允许跨域
res.setHeader("Access-Control-Allow-Origin", "*")
// 响应头
res.setHeader("Access-Control-Allow-Headers", "*")
// 响应一个数据
const data = {
name: "mytest"
};
let str = JSON.stringify(data);
// 设置响应体
res.send(str);
})

app.all("/jquery-server", (req, res) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Headers", "*")
// res.send("hello jquery ajax");
const data = {name: "test"};
res.send(JSON.stringify(data));
})

app.all("/axios-server", (req, res) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Headers", "*")
const data = {name: "test"};
res.send(JSON.stringify(data));
})
...
<body>
<button>点击发送请求</button>
<div id="result"></div>
<script>
const btn = document.getElementByTagName("button")[0];
const result = document.getElementById("result");
// const result = document.querySelector("#result");
btn.onclick = function(){
// console.log("test");
// 创建对象
const xhr = new XMLHttpRequest();
// 初始化, 设置请求方法和url
xhr.open("GET", "<http://127.0.0.1:8000/server>");
// 解决ajax的ie缓存问题
xhr.open("GET", "<http://127.0.0.1:8000/server?t=>" + Date.now());
// 设置请求参数
// xhr.open("GET", "<http://127.0.0.1:8000/server?a=100&b=200&c=300>");
// 发送
xhr.send();
// 事件绑定, 处理服务器返回的结果
// readystate是xhr对象中的属性, 表示状态0 1 2 3 4
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){ // 此时服务器端已返回所有结果
// 200-299均为成功
if(xhr.status >= 200 && xhr.status < 300){
// 响应行
console.log(xhr.status); // 状态码
console.log(xhr.statusText); // 状态字符串
console.log(xhr.getAllResponseHeaders()); // 所有响应头
console.log(xhr.response);
// 设置result的文本
result.innerHTML = xhr.response; // 即hello ajax
}
}
}
}

result.addEventListener("mouseover", function(){
const xhr = new XMLHttpRequest();
xhr.open("POST", "<http://127.0.0.1:8000/server>");
xhr.send();
// xhr.send("a=100&b=200&c=300") // Request Payload
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if(xhr.status >= 200 && xhr.status < 300){
result.innerHTML = xhr.response; // 即hello ajax post
}
}
}
})

// 绑定键盘按下事件
window.onkeydown = function(){
const xhr = new XMLHttpRequest();
xhr.open("GET", "<http://127.0.0.1:8000/json-server>");
xhr.send();
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if(xhr.status >= 200 && xhr.status < 300){
// result.innerHTML = xhr.response;
// 手动对数据进行转换
// let data = JSON.parse(xhr.response);
// console.log(data);
// result.innerHTML = data.name;
// 自动转换
result.innerHTML = xhr.response.name;
}
}
}
}
$("button").eq(0).click(function(){
$.get("<http://127.0.0.1:8000/jquery-server>", {a:100, b:200}, function(data){
console.log(data);
})
})
$("button").eq(1).click(function(){
$.post("<http://127.0.0.1:8000/jquery-server>", {a:100, b:200}, function(data){
console.log(data);
}, "json")
})
$("button").eq(2).click(function(){
$.ajax({
url: "<http://127.0.0.1:8000/jquery-server>",
data: {a: 100, b: 200},
type: "GET",
// 成功的回调
success: function(data){
console.log(data);
}
// 超时时间
timeout: 2000,
// 失败的回调
error: function(){
console.log("出错");
}
// 头信息
headers: {
c: 300,
d: 400
}
})
})

const btns = document.querySelectorAll("button");
btns[0].onclick = function(){
axios.get("/axios-server", {
// url
params: {
id: 100,
vip: 7
},
// 请求头参数
headers: {
name: "test",
age: 20
}
}).then(value => {
console.log(value)
})
}
btns[0].onclick = function(){
axios.post("/axios-server", {
username: "zyliang",
password: "zyliang666"
}, {
params: {
id: 100,
vip: 7
},
headers: {
name: "test",
age: 20
}
}).then(value => {
console.log(value)
})
}
btns[0].onclick = function(){
axios({
url: "/axios-server",
params: {
id: 100,
vip: 7
},
headers: {
name: "test",
age: 20
},
data: {
username: "zyliang",
password: "zyliang666"
}
}).then(res => {
console.log(res);
console.log(res.status); // 响应状态码
console.log(res.statusText); // 响应状态字符串
console.log(res.headers); // 响应状态头
console.log(res.data); // 响应状态体
})
}
</script>
</body>
...