上一篇文章,我们把整个项目代码稍微调整优化了一遍,也实现了一个简单的博客系统的前后端功能的实现,对接和展示。
那么接下来呢,我们来实现文件上传功能,为我们后续的开发可以展示丰富图文的博客文章做好准备。
话不多说,来看看文件上传效果。
可以看到,当我们在网页中,点击选择文件,选择图片文件,就能马上把文件上传到我们项目中的 /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
即可。
把上面两个图中,红色划线处的连起来就是: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