Skip to content

上一篇文章,我们把整个项目代码稍微调整优化了一遍,也实现了一个简单的博客系统的前后端功能的实现,对接和展示。

那么接下来呢,我们来实现文件上传功能,为我们后续的开发可以展示丰富图文的博客文章做好准备。

话不多说,来看看文件上传效果。

文件上传效果演示

可以看到,当我们在网页中,点击选择文件,选择图片文件,就能马上把文件上传到我们项目中的 /public/uploads 目录下。

然后,在地址栏输入 http://127.0.0.1:3000/uploads/图片名称.png,就能在浏览器看到我们刚才上传的文件了。

那么这是怎么做到的呢?请听我一一道来。

html
<!doctype html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>博客管理后台 - 文件上传</title>
    </head>
    <body>
        <div class="page-upload">
            <input type="file" class="file" />
        </div>
        <script src="../utils.js"></script>
        <script>
            // 点击上传按钮
            document.querySelector('.file').addEventListener('change', async (e) => {
                const { code, msg } = await utilHttp('/api/tool/upload', e.target.files[0], 'upload');
                utilShowMsg(msg);
            });
        </script>
    </body>
</html>

首先是前端部分,我新增了一个 /public/admin/upload.html 文件,专门用来上传图片。

然后,当我们的文件组件选择文件后,就会触发 change事件,立刻将图片发送到 /api/tool/upload 接口。

至于为什么是将 e.target.files[0] 上传,请回想一下本小册编程思想:耳听为虚,眼见为实

打印一下变量 e 即可。

打印1

打印2

把上面两个图中,红色划线处的连起来就是:e.target.files[0],就找到了我们的图片了。

这也是我自己学习技术的核心方法:做实验

不要盲目地去猜,要用眼睛去看,去打印,去观察。

说实话,只要你理解了笔者的这个思路并付诸实践,写代码这个事情,将没有什么难度。

然后呢,有几处地方需要改动。

js
// 请求封装
const utilHttp = async (url, data, action = 'common') => {
    // 初始化参数
    const params = {
        method: 'POST',
        headers: {}
    };
    // 普通请求
    if (action === 'common') {
        params.headers['Content-Type'] = 'application/json;charset=utf-8';
        params.body = JSON.stringify(data);
    }
    // 上传文件
    if (action === 'upload') {
        const formData = new FormData();
        formData.append('file', data);
        params.body = formData;
    }
    // 收到的返回值
    const response = await fetch(url, params);
    // 把拿到的数据变成JSON结构
    const result = await response.json();
    return result;
};

请求封装要改造下,原来只支持普通请求,现在增加上传文件的逻辑。

这里上传文件,我们用 formData 来实现。

js
const server = createServer(async (req, res) => {
    if (req.url.startsWith('/api/') === true) {
        const apiName = req.url.replace('/api/', '');
        res.setHeader('Content-Type', 'application/json');
        if (apiName === 'tool/upload') {
            // 文件上传
            const result = await apiMaps[apiName].default(req);
            res.end(JSON.stringify(result));
        } else {
            // 普通请求
            if (apiMaps[apiName]) {
                let body = '';
                req.on('data', (chunk) => {
                    body += chunk.toString();
                });
                req.on('end', async () => {
                    try {
                        req.body = JSON.parse(body);
                        const result = await apiMaps[apiName].default(req);
                        res.end(JSON.stringify(result));
                    } catch (err) {
                        console.log('🚀 ~ req.on ~ err:', err);
                        res.end(
                            JSON.stringify({
                                code: 1,
                                msg: '请求参数结构有误'
                            })
                        );
                    }
                });
            } else {
                res.end(
                    JSON.stringify({
                        code: 1,
                        msg: '接口不存在'
                    })
                );
            }
        }
    } else {
        // 如果是资源
        let staticFile = req.url.split('?')?.[0] || '';
        if (staticFile === '/') {
            staticFile = '/index.html';
        }
        if (existsSync(`./public/${staticFile}`) === true) {
            res.setHeader('Content-Type', lookup(staticFile));
            res.end(readFileSync(`./public/${staticFile}`));
        } else {
            res.writeHead(404, { 'Content-Type': 'text/plain' });
            res.end('404 Not Found');
        }
    }
});

请看注释 文件上传 处,因为图片上传跟普通请求的传参不一样,所以要判断一下,分别处理。

会员内容

会员隐藏内容,共 [2491] 字。 👉购买地址:https://sourl.cn/NM5H5m

何以解忧,唯有代码。不忘初心,方得始终。