github地址:点击进入
您将收获
- Node应用基本架构方式以及开发NodeJS应用的流程
- jwt的鉴权及鉴权中间件的使用
- 跨域解决方案Koa Cors的使用介绍,服务器白名单
- 基于koa-body封装文件上传中间件
服务端路由(接口)设计
- 获取token
const Router = require('koa-router');
const router = new Router({ prefix: '/api/token' });
const {
getToken,
} = require('../controllers/UploadToken')
// 获取
router.get('/', getToken);
module.exports = router;
- 上传图片
const Router = require('koa-router');
const router = new Router({ prefix: '/api/pic' });
const auth = require('../middleware/auth')()
const {
upload,
} = require('../controllers/UploadPic')
// 上传
router.post('/upload', auth, upload);
module.exports = router;
…(其他路由省略)
jwt 鉴权
项目初期的是私有的所有不公开的,不然我的乞丐版的服务器扛不住啊,我做了服务器白名单和jwt鉴权的操作,保证安全。封装的中间件代码如下。详见middleware/auth.js
const jwt = require('jsonwebtoken');
const util = require('util');
const { CallbackModel } = require('../utils')
const { secret } = require('../config');
const verify = util.promisify(jwt.verify);
module.exports = () => {
return async(ctx, next) => {
try {
const { authorization = '' } = ctx.request.header;
const token = authorization.split(' ')[1];
if (!token) {
CallbackModel(ctx, 404, '缺失Token信息', {})
return;
}
try {
// 解密payload,获取用户名和ID
const payload = await verify(token, secret);
ctx.state = {
user: {
...payload
}
};
await next(); //运行完毕,交给下一个中间件
} catch (err) {
console.info(99999, err)
if (JSON.stringify(err).search(/JsonWebTokenError/)) {
CallbackModel(ctx, 401, 'Token无效', JSON.stringify(err))
} else {
CallbackModel(ctx, 500, '未知错误', JSON.stringify(err))
}
}
} catch (err) {
CallbackModel(ctx, 500, '错误', JSON.stringify(err))
}
}
}
koa-body 上传图片
动态创建文件夹,前端只需要传递bucket 的参数即可
const { CallbackModel, timeFormat } = require('../utils')
const fs = require('fs');
const path = require('path');
// 递归创建目录 同步方法
function checkDirExist(dirname) {
if (fs.existsSync(dirname)) {
return true;
} else {
if (checkDirExist(path.dirname(dirname))) {
fs.mkdirSync(dirname);
return true;
}
}
}
// 生成新的文件名称
function getUploadFileExt(name) {
let ext = name.split('.');
let first = name.replace(ext[ext.length - 1], '');
let last = timeFormat(new Date(), 'yyyy-mm-dd-HH-mm-ss')
return `${first}${last}.${ext[ext.length - 1]}`
}
class UploadPic {
static async upload(ctx) {
try {
// 上传单个文件
const file = ctx.request.files.file; // 获取上传文件
const { bucket = 'test' } = ctx.request.body;
// console.info('bucket', bucket)
// 生成文件夹
let dir = path.join(__dirname, `../public/images/${bucket}`);
checkDirExist(dir);
// console.info('dir', dir, file)
// 生成图片文件名字
let newName = getUploadFileExt(file.name);
// console.info('newName', newName);
// 文件目录
let filePath = `${dir}/${newName}`;
// console.info('filePath', filePath)
// 创建可读流
const reader = fs.createReadStream(file.path);
// 创建可写流
const upStream = fs.createWriteStream(filePath);
// console.info('upStream', upStream)
// 可读流通过管道写入可写流
reader.pipe(upStream);
// ------ starting ------
// 删除文件(可以注释这段代码,不明白为什么产生一些重复的文件)
let delpath = path.join(__dirname, `../public/images`);
let files = fs.readdirSync(delpath);
// console.info(234, delpath, files)
files.forEach(item => {
// 判断 是不是文件夹 【true: 文件夹】
if (!fs.lstatSync(`${delpath}/${item}`).isDirectory()) {
if (item.indexOf('upload_') > -1) {
fs.unlink(`${delpath}/${item}`)
}
}
});
// ------ ending ------
let urlstr = `${ctx.origin}/images/${bucket}/${newName}`
CallbackModel(ctx, 200, '上传成功!', { url: urlstr })
} catch (err) {
console.info(33, err)
CallbackModel(ctx, 500, '上传失败!', JSON.stringify(err))
}
}
}
module.exports = UploadPic
使用glob来批量获取图片路径
const glob = require('glob');
// 获取图片列表
static async getlist(ctx) {
try {
const { bucket = 'test' } = ctx.request.query;
// console.info('bucket', bucket)
const files = glob.sync(`public/images/${bucket}/*`)
const result = files.map(item => {
return `${ctx.origin}${item.split('public')[1]}`
})
CallbackModel(ctx, 200, '获取文件成功', { files: result })
} catch (error) {
CallbackModel(ctx, 500, '获取文件失败', JSON.stringify(error))
}
}
删除图片
// 删除图片
static async delpic(ctx) {
try {
// pid ==> 1.jpg
const { bucket = 'test', pid } = ctx.request.body;
// 找到当前文件
let curPath = `public/images/${bucket}/${pid}`;
// 先判断当前文件是否存在
if (fs.existsSync(curPath) && pid) {
const err = await delFile(curPath)
if (!err) {
CallbackModel(ctx, 200, '删除成功', {})
} else {
CallbackModel(ctx, 500, '删除失败', {})
}
} else {
CallbackModel(ctx, 404, '当前图片不存在', {})
}
} catch (error) {
console.info(888, error)
CallbackModel(ctx, 500, '删除文件失败', JSON.stringify(error))
}
}
跨域解决方案Koa Cors的使用介绍,服务器白名单
import cors from 'koa2-cors'
app.use(cors({
origin: function(ctx) {
// console.log(111, ctx, ctx.url)
// 设置白名单
if (judgeHttpUrl(ctx.header.host)) {
return '*';
}
// return ['http://localhost:3092', 'http://www.zhooson.cn']; // 指定域名访问
},
exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'], // 获取额外的header信息
maxAge: 5, // 该字段可选,用来指定本次预检请求的有效期,单位为秒
credentials: true,
allowMethods: ['GET', 'POST', 'DELETE', 'PUT', 'UPDATE', 'OPTIONS'], // 请求允许的方法
allowHeaders: ['Content-Type', 'Authorization', 'Accept', 'x-requested-with'] // 允许的header字段名
}))
/**
* 设置白名单
* @params url fetch的路径
* @return Boolean
* */
judgeHttpUrl(url = '') {
// console.info('judgeHttpUrl---123', url)
if (!url) return false
const urls = ['zhooson.cn', 'localhost:3092']
if (urls.indexOf(url) > -1) {
return true
} else {
return false
}
}