信息获取来源Eno Yao
创建脚手架
express katsuki-project(express 名称)
在katsuki-project目录下安装依赖包
npm install
上传单文件项目
在katsuki-project安装multer
模块
npm install multer --save
目录中创建一个uploads文件夹,不创建运行也会自动创建
项目结构
public
javascripts
jquery.js
upload.js
upload.html
routes
uploads.js
app.js
upload.html
<script src="javascripts/jquery.js"></script>
<script src="javascripts/upload.js"></script>
<img id="img" src="" alt="上传图片" />
<input type="file" id="file" name="headImg" />
upload.js
$(()=>{
function doUpload() {
var formData = new FormData();
//记得append("xxx")和对应标签name属性值一致 要跟后端一致
formData.append('headImg', $('#file')[0].files[0]);
$.ajax({
url: 'http://localhost:3000/uploads/upload',
type: 'POST',
cache: false,
data: formData,
processData: false,
contentType: false
}).done(function(res) {
$('#img').attr('src', `http://localhost:3000/${res.file.filename}`);
}).fail(function(res) {
console.log(res);
});
}
$('#file').on('change',()=>{
doUpload();
})
})
路由文件uploads.js
var express = require('express');
var router = express.Router();
var multer = require("multer");
var storage = multer.diskStorage({
//设置上传后文件路径,uploads文件夹会自动创建。
destination: function (req, file, cb) {
//路径要对,不然会500错误
cb(null, './uploads')
},
//给上传文件重命名,获取添加后缀名
filename: function (req, file, cb) {
var fileFormat = (file.originalname).split(".");
//给图片加上时间戳格式防止重名名
//比如把 katsuki.jpg图片切割为数组[katsuki,jpg],然后用数组长度-1来获取后缀名
cb(null, file.fieldname + '-' + Date.now() + "." + fileFormat[fileFormat.length - 1]);
}
});
var upload = multer({
storage: storage
});
router.post('/upload', upload.single('logo'), function (req, res, next) {
console.log(req);
res.json({
status: "success",
file: req.file
});
});
module.exports = router;
app.js语句添加
添加路由路径语句
var uploadsRouter = require('./routes/uploads');
app.use('/uploads',uploadsRouter);
添加上传图片存储位置语句
app.use(express.static(path.join(__dirname, 'uploads')));
文件配置完成后
npm start
网页打开路径 localhost:3000/upload.html 上传文件,这里写的html针对上传图片显示
上传后在uploads文件夹里会有该文件
令牌登录
在描述令牌登录前,补充token、localStorage&sessionStorage
token
token进行加密解密操作,配合node的crypto
内置模块使用
token.js来源 Eno Yao
token.js
var crypto = require("crypto");
// 把加密的内容一段乱码
var token = {
// 加密
createToken: function(obj, timeout) {
//后来加的时效性 token在30内都是有效的
var obj2 = {
data: obj, //payload
created: parseInt(Date.now() / 1000), //token生成的时间的,单位秒
exp: parseInt(timeout) || 10 //token有效期
};
//payload信息
var base64Str = Buffer.from(JSON.stringify(obj2), "utf8").toString("base64");
//添加签名,防篡改
var secret = "piggyyao.com";
var hash = crypto.createHmac('sha256', secret);
hash.update(base64Str);
var signature = hash.digest('base64');
return base64Str + "." + signature;
},
// 解码
decodeToken: function(token) {
var decArr = token.split(".");
if(decArr.length < 2) {
//token不合法
return false;
}
var payload = {};
//将payload json字符串 解析为对象
try {
payload = JSON.parse(Buffer.from(decArr[0], "base64").toString("utf8"));
} catch(e) {
return false;
}
//检验签名
var secret = "piggyyao.com";
var hash = crypto.createHmac('sha256', secret);
hash.update(decArr[0]);
var checkSignature = hash.digest('base64');
return {
payload: payload,
signature: decArr[1],
checkSignature: checkSignature
}
},
// 校验
checkToken: function(token) {
var resDecode = this.decodeToken(token);
if(!resDecode) {
return false;
}
//是否过期
var expState = (parseInt(Date.now() / 1000) - parseInt(resDecode.payload.created)) > parseInt(resDecode.payload.exp) ? false : true;
if(resDecode.signature === resDecode.checkSignature && expState) {
return true;
}
return false;
}
}
module.exports = exports = token;
token的基本使用,一般都在路由文件中进行加密操作
run.js
var token = require("./token.js");
console.log(token); //得到引入的token对象,内有三个函数
// 加密
let crypto = token.createToken({
name: 'katsuki',
age: 18
}, 30) //30为令牌有效时间,单位秒
//令牌
console.log(crypto);
console.log(token.decodeToken(crypto));
// 解密
console.log(token.decodeToken(crypto).payload.data);
//检测是否有令牌
console.log(token.checkToken(crypto));
打印结果
localStorage&sessionStorage
localStorage一直保存,主题颜色,白天模式或者夜间模式,token
// 增加 更新
localStorage.setItem(key,value);
// 查
let value = localStorage.getItem(key);
localStorage.removeItem(key);
// 全部清除
localStorage.clear();
sessionStorage网页如果关闭的话,就清除,页面缓存的数据
// 增加 更新
sessionStorage.setItem(key,value);
// 查
let value = sessionStorage.getItem(key);
sessionStorage.removeItem(key);
// 全部清除
sessionStorage.clear();
localStorage和sessionStorage空间大但是没有时效性
cookie具备最多功能(键值对,时效性等等),但是缺点空间小
令牌登录实现
假设已经在脚手架创建的文件目录中,完成了登录和登录跳转页
这里只对js文件对应部分进行描述
项目结构
public
javascripts
jquery.js
login.js
katsuki.js
login.html
katsuki.html (登录跳转页)
routes
user.js (脚手架创建自带路由文件)
user.js
var express = require('express');
var router = express.Router();
//这里调用之前 Node_数据库 博文写的mongodb封装
let {
find
} = require("../libs/mongod.js");
var token = require("../libs/token.js");
/* GET users listing. */
router.get('/', function (req, res, next) {
res.send('respond with a resource');
});
//登录路由
router.post('/login', async (req, res, next) => {
let {
inputName,
inputPassword
} = req.body;
let data = await find('user', {
username: inputName
});
if (data[0].password == inputPassword) {
res.send({
status: "success",
token: token.createToken({
inputName,
inputPassword
}, 60) //令牌有效时间为60秒
});
} else {
res.send({
status: "fail",
});
}
});
//后台首页判断自动登录路由
router.post('/autoLogin', async (req,res,next)=>{
res.send({
status: token.checkToken(req.headers.token)
})
})
module.exports = router;
login.js
$(() => {
//登录按钮获取
let signIn = $('#signin');
//登录ajax请求
let loginIn = (inputName, inputPassword) => {
return new Promise((resolve, reject) => {
$.ajax({
type: 'post',
url: 'http://localhost:3000/users/login',
data: {
inputName,
inputPassword
},
success(data) {
resolve(data);
}
})
})
}
//登录点击事件
signIn.click(async () => {
//用户名获取
let inputName = $('#inputName').val();
//密码获取
let inputPassword = $('#inputPassword').val();
let data = await loginIn(inputName, inputPassword);
//data拿到经过登录路由判断返回的res.send内容,进行下面操作
let fn = {
success() {
console.log('登录成功');
localStorage.setItem("token", data.token);
location.href = 'katsuki.html';
},
fail() {
console.log('登录失败');
}
}
fn[data.status]()
})
})
katsuki.js 未封装fn写法
$(() => {
//自动登录判断函数
let autoLogin = () => {
return new Promise((resolve, reject) => {
$.ajax({
type: 'post',
headers: {
token: localStorage.getItem("token")
},
url: "http://localhost:3000/users/autoLogin",
success(data) {
resolve(data);
}
})
})
}
(async () => {
let isLogin = await autoLogin();
if(isLogin.status){
//为true,有令牌,进行相应操作
}else{
//为false,无令牌,跳回登录页
location.href = "login.html";
}
})()
}
katsuki.js 封装fn写法
$(() => {
//封装的函数都放入fn,使用时用fn.函数名调用
(async () => {
let fn = {
true: async () => {
//为true,有令牌,进行相应操作
},
false() {
//为false,无令牌,跳回登录页
location.href = "login.html";
},
//自动登录判断
autoLogin() {
return new Promise((resolve, reject) => {
$.ajax({
type: 'post',
headers: {
token: localStorage.getItem("token")
},
url: "http://localhost:3000/users/autoLogin",
success(data) {
resolve(data);
}
})
})
},
kasami() {
console.log('kasami');
//this返回fn对象,能够继续点调用对象方法
return this;
},
katsuki() {
console.log('katsuki');
return this;
}
}
}
let isLogin = await fn.autoLogin();
// 异步 awiat和async
fn[isLogin.status]();
// 链式调用
fn.kasami().katsuki();
})()
})