# Express项目实战
现在我们把前面学习的知识通过项目实战的形式进行应用,项目功能列表
- 用户注册
- 用户登陆
- 王者荣耀英雄数据管理(包含增删改查)
# 1. 创建Express服务器
- 安装Express依赖
npm install express --save
- 创建服务器,监听3000端口
// app.js代码
// 引入express
const express = require('express');
// 创建个 express 服务器
const app = express();
// 端口监听
app.listen(3000, () => {
console.log('服务器启动成功提示');
});
# 2. 设置静态资源
静态资源就是我们在浏览器输入资源地址,服务器不做任何逻辑处理,直接把资源返回。
项目有两个目录需要设置为静态目录,分别是public
用于存放html、css、js、图片和uploads
用于存放上传图片目录。
// 设置静态资源目录
app.use(express.static('public'))
app.use( '/uploads' ,express.static('uploads'))
配置完毕后,可以通过
http://localhost:3000/add.html
访问public/add.html
文件
http://localhost:3000/uploads/icon.jpeg
访问uploads/icon.jpeg
文件
# 3. body-parser的使用
- 安装body-parser
npm install body-parser --save
- 配置中间件
// 使用bodyParser中间件处理post请求参数
app.use(bodyParser.urlencoded());
# 4. 封装路由
1 、新建router.js
文件,配置路由信息
const express = require('express');
const router = express.Router();
// 导出路由
module.exports = router;
2、在app.js
文件应用路由
// 引入路由
const router = require('./router')
// 使用路由
app.use(router)
# 5. 数据库准备
# 5.1 创建数据库表结构
-- 创建hero表,记录英雄信息
create table heros(
id int not null primary key auto_increment,
name varchar(255) not null,
gender varchar(255),
img varchar(255),
isdelete tinyint default 0)
character set utf8;
-- 创建user表,用来记录用户注册信息
create table user (
id int not null primary key auto_increment,
username varchar(255) not null,
password varchar(255))
character set utf8;
# 5.2 nodejs连接数据库
- 安装nodejs操作MySQL数据库依赖
npm install mysql --save
2、创建model.js
文件连接数据库
// 导入MySQL模块
const mysql = require('mysql');
// 创建一个数据库连接
var connection = mysql.createConnection({
host: '127.0.0.1' // 数据库服务器的地址
user: 'root', // 数据库的账号
password: 'root', // 数据库的密码
database: 'heima' // 数据库名称
});
// 连接数据库或打开数据库
connection.connect();
# 6. 用户注册
# 6.1 服务端代码编写
- model.js加入注册插入数据库代码
// 注册新用户
const register = function(data, callback) {
const { username, password } = data;
connection.query('insert into user set ?', { username, password }, callback)
}
// 导出方法
module.exports = {
register
}
- router.js加入注册地址监听
// 用户注册
router.post('/register', (req, res) => {
// 调用model层,把数据存到数据库
model.register(req.body, (err, result) => {
if (err) {
res.json({ code: -1, msg: err })
} else {
res.json({ code: 200, msg: '注册成功'});
}
})
})
# 6.2 客户端代码编写
<!-- 需要使用引入第三方库进行md5加密 -->
<script src="./lib/md5.min.js"></script>
function register() {
// 获取用户名和密码
const username = $('#userName').val();
// 处于安全考虑,对密码进行md5加密
const password = md5($('#userPwd').val());
if (username && password) {
$.post('/register', { username, password }, (res) => {
if (res.code === 200) {
alert('恭喜,注册成功!')
}
})
} else {
alert('用户名或密码不能为空')
}
}
# 7. 用户登陆
# 7.1 服务端代码编写
- model.js加入登录的数据查询
// 用户登录
const login = function(data, callback) {
// 查找数据库,是否有匹配的用户
const { username, password } = data;
connection.query('select * from user where username=? and password=?', [ username, password], callback)
}
- router.js加入登录/login路由
// 用户登陆
router.post('/login', (req, res) => {
model.login(req.body, (err, result) => {
if (err) {
res.json({ code: -1, msg: err })
} else if (result.length === 0) {
res.json({ code: -1, msg: '用户不存在或者密码错误' })
} else {
res.json({ code: 200, msg: '登录成功' })
}
})
});
# 7.2 客户端代码编写
<script src="./lib/md5.min.js"></script>
<script>
function login() {
// 获取用户名和密码
const username = $('#userName').val();
const password = md5($('#userPwd').val());
$.post('/login', { username, password }, (res) => {
if (res.code === 200) {
alert('登录成功')
} else {
alert(res.msg);
}
})
}
</script>
# 8.获取所有英雄数据
# 8.1 服务端实现
router.js
// 获取所有英雄数据
router.get('/getHeroList', tokenUtil.checkRole, (req, res) => {
model.getHeroList((err, result) => {
if (err) {
res.json({ code: -1, msg: err });
} else {
res.json({ code: 200, data: result });
}
});
});
model.js
// 获取所有英雄数据
const getHeroList = function(callback) {
connection.query('select * from heros where isdelete=0 order by id', callback)
}
# 8.2 客户端实现
获取英雄数据
$.ajax({
url: '/getHeroList',
success: function (res) {
// 调用模板引擎
let html = template('herosTemp', res)
$('#tbody').html(html)
}
})
使用art-template渲染模版
<script type="text/template" id='herosTemp'>
{{each data}}
<tr>
<td>{{$index + 1}}</td>
<td>{{$value.name}}</td>
<td>{{$value.gender}}</td>
<td><img src="/uploads/{{$value.img}}"></td>
<td>
<a href="./edit.html?id={{$value.id}}">修改</a>
<a href="javascript:;" onclick='del({{$value.id}})'>删除</a>
</td>
</tr>
{{/each}}
</script>
# 9.新增英雄
# 9.1 服务端实现
router.js
// 英雄数据的新增
router.post('/addHero', (req, res) => {
model.addHero(req.body, (err, result) => {
if (err) {
res.json({ code: -1, msg: err });
} else {
res.json({ code: 200, msg: '添加成功' });
}
});
});
// 图片上传
router.post('/uploadFile', (req, res) => {
var form = new formidable.IncomingForm();
form.uploadDir = __dirname + '/uploads';
form.keepExtensions = true;
form.parse(req, (err, fields, files) => {
if (err) {
res.json({
code: 201,
msg: '上传失败',
});
} else {
const filename = path.basename(files.img.path);
res.json({
code: 200,
msg: '上传成功',
img: filename,
});
}
});
});
model.js
// 新增英雄
const addHero = function(hero, callback) {
connection.query('insert into heros set ?', hero, callback)
}
# 9.2 客户端实现
// 实现文件上传
function upload() {
let myfile = $('#img')[0].files[0];
let formdata = new FormData();
formdata.append('img', myfile);
$.ajax({
type: 'post',
url: '/uploadFile',
data: formdata,
processData: false, // 告诉ajax不要进行数据的处理,formdata自己来处理
contentType: false, // 造成ajax不要对数据编码,formdata自己来编码
success: function (res) {
console.log(res);
if (res.code == 200) {
// 实现图片预览
$('#photo').attr('src', '/uploads/' + res.img);
// 将图片名称存储到指定的隐藏域
$('.userimg').val(res.img);
}
},
});
}
# 10. 登录状态保持与Token机制(重点)
在讲token机制前,先跟同学们聊一下大家有没去过长隆水上乐园,我们进入乐园前是需要在门口进行验票,验证已经购票才可以进入游玩,但是总有人喜欢翻墙,不买票就去游玩,那乐园为了防止这种情况,就增加了一个验票流程,不但在门口需要验票,在每个游乐项目都要验票。
虽然可以杜绝翻墙的现象,但是游客就不舒服了,因为每次都要验票,排队好久才能游玩,体验实在太差。游乐场又想了一个办法,在大门口验票完后给大家发一个手环,当游玩其他项目的时候,看到戴着手环的游客就放行。
这个手环其实就是我们后面要讲到的token。
# 10.1 HTTP是无状态协议
无状态的意思是,你第一次发送请求登录成功后,第二次发送请求其他接口,服务端是不知道你已经登录成功,第二次请求和第一次请求是没有任何关联的。
Http的这个特性就产生了一个问题,导致登录状态的丢失,为了解决这个问题,你当然可以像游乐场解决翻墙问题一样,在每次请求的时候都验证用户的账号密码,但是重复的验证操作,会导致计算机资源的消耗,用户的使用体验就会变差。
游乐场也发现每次验票体验太差,后来给游客们发放手环解决验证问题,同样,计算机也可以利用这一机制解决该问题,我们把这个手环称为token。
# 10.2 实现简单token
createToken: 生成token
verifyToken: 验证token
checkRole: 验证是否有权限
const jwt = require("jsonwebtoken");
// 密钥,用来验证真伪
const key = "heima";
// 生成token
const createToken = function (username) {
const token = jwt.sign({ username }, key, { expiresIn: "1h" });
return token;
};
// 验证token是否有效
const verifyToken = function (token) {
try {
jwt.verify(token, key);
return true;
} catch (err) {
return false;
}
};
// 权限验证
const checkRole = function (req, res, next) {
const token = req.headers.authorization;
if (token) {
const isValidate = verifyToken(token);
if (isValidate) {
next();
} else {
res.send({ code: -1, msg: "没有访问权限" });
}
} else {
res.send({ code: -1, msg: "token不能为空" });
}
};
module.exports = {
createToken,
verifyToken,
checkRole
};
# 10.3 改造登录功能
服务端需要在登录成功后生成token,发送给客户端
router.post('/login', (req, res) => {
model.login(req.body, (err, result) => {
if (err) {
res.json({ code: -1, msg: err })
} else if (result.length === 0) {
res.json({ code: -1, msg: '用户不存在或者密码错误' })
} else {
// 登录成功生成token
const userToken = tokenUtil.createToken(req.body.username)
res.json({ code: 200, msg: '登录成功', token: userToken })
}
})
});
客户端收到token后,需要保存token
$.post('/login', { username, password }, (res) => {
if (res.code === 200) {
// 把token存储到本地
window.localStorage.setItem('token', res.token)
alert('登录成功')
} else {
alert(res.msg)
}
})
# 10.4 请求接口时把token带上
请求获取英雄列表时,把token发送给服务端
$.ajax({
method: 'get',
url: '/getHeroList',
headers: {
'Authorization': window.localStorage.getItem('token') // 把token放到请求头
},
success: (res) => {
console.log(res)
}
})
# 10.5 服务端验证token是否有效
// 请求头获取token
const token = req.headers.authorization;
// 验证token是否有效
const result = tokenUtil.verifyToken(token);
# 10.6 更好的验证方法
可以封装一个独立的验证权限的方法checkRole
// utils/token.js
// 权限验证
const checkRole = function(req, res, next) {
const token = req.headers.authorization
if (token) {
const isValidate = verifyToken(token);
if (isValidate) {
next();
} else {
res.send({ code: -1, msg: '没有访问权限' })
}
} else {
res.send({ code: -1, msg: 'token不能为空' })
}
}
在路由调用
router.get('/getHeroList', tokenUtil.checkRole, (req, res) => {
model.getHeroList((err, result) => {
if (err) {
res.json({ code: -1, msg: err })
} else {
res.json({ code: 200, data: result});
}
});
});