使用nodeJs开发自己的图床应用

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
        }
    }
    

前端代码省略,基本用postman调试

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值