「这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战」
我们今天实现一下图片上传,前端用到的是antdesign中的文件上传,后端是自己封装的node的koa框架。
这个过程大致是:前端将图片提交给后端,后端将其存入后端项目的文件夹中,然后将图片所在路径返回给前端,前端得到图片路径后将图片路径再提交到后端保存的接口,存入数据库中
我这个项目的配置文件在 app/index.js
图片上传的路径在 app/public/uploads
koa-static
作用:声明一个静态文件夹,可以供上传图片找到。
安装:npm install koa-static
npm网址 : https://www.npmjs.com/package/koa-static
用法:
// 引入 koa-static
const koaStatic = require('koa-static');
// 引入koa
const Koa = require('koa');
// 实例化koa对象
const app = new Koa();
// 挂载
app.use(koaStatic(path.join(__dirname, 'public')))
koa-body
作用:配置koa的body体接收格式
安装:npm install koa-body
npm网址:https://www.npmjs.com/package/koa-body
用法:
const Koa = require('koa');
const koaBody = require('koa-body');
const app = new Koa();
app.use(koaBody());
app.use(ctx => {
ctx.body = `Request Body: ${JSON.stringify(ctx.request.body)}`;
});
我们的配置 app/index.js
const Koa = require('koa')
const KoaBody = require('koa-body')
const path = require('path')
const koaStatic = require('koa-static')
const cors = require('koa2-cors');
const app = new Koa()
// 必须是一个函数
app.use(cors());
// 1. 文件上传到的路径 __diname是当前index.js所在的位置 public文件夹跟其同级
app.use(koaStatic(path.join(__dirname, 'public')))
// 2.在注册路由前注册
app.use(KoaBody({
multipart: true,
uploadDir: path.join(__dirname, 'uploads'),
maxFileSize: 200 * 1024 * 1024, // 设置上传文件大小最大限制,默认2M
// 保留文件扩展名
// keepExtensions: true,
}))
module.exports = app
在router文件下的client.route.js中添加路由
这样添加后我们之后访问的路径就是 http://localhost:80001/client/Upload
在我们的controller文件夹下的client.controller.js中写入方法
// 图片上传
async Upload(ctx,next){
// 前端的name要于此出 file相同 。 如果叫 img 前端组件也需要name='img'
const file = ctx.request.files.file; // 获取上传文件
console.log(ctx.request,'file')
// 创建可读流
const reader = fs.createReadStream(file.path);
const random_num = random(16)
const fileName = random_num+'.'+file.name.split('.')[1]
let filePath = path.join(__dirname, '../app/public/uploads/') + `/${fileName}`;
// 创建可写流
const upStream = fs.createWriteStream(filePath);
// 可读流通过管道写入可写流
reader.pipe(upStream);
最后将图片的存储路径返回
ctx.body = {'url':`${ctx.origin}/uploads/${fileName}`}
}
antdesgin对应的文档 :https://ant.design/components/upload-cn/
import { Button, Input, Drawer, Form, Select, Tag, Upload, message } from 'antd';
<Upload
// 对应后端的 ctx.request.files.file
name="file"
listType="picture-card"
className="avatar-uploader"
showUploadList={false}
action="/api/client/Upload"
beforeUpload={beforeUpload}
onChange={handleChange}
>
{imageUrl ? (
<img src={imageUrl} alt="avatar" style={{ width: '100%', marginTop: '10px' }} />
) : (
<div>
{loading ? <LoadingOutlined /> : <PlusOutlined />}
<div style={{ marginTop: 8 }}>Upload</div>
</div>
)}
</Upload>
// 后端对应代码
const file = ctx.request.files.file; // 获取上传文件
上传列表的内建样式,支持三种基本样式 text
, picture
和 picture-card
他们的样式也不相同。可以去官网具体看。
是否展示文件列表, 可设为一个对象,用于单独设定 showPreviewIcon
, showRemoveIcon
, showDownloadIcon
, removeIcon
和 downloadIcon
也就是 form表单写法的action方法,即提交对应的的后端路径。这里的 /api
代理到了 localhost:8001
// 图片地址
const [imageUrl, setImageUrl] = useState<string>();
// 加载
const [loading, setLoading] = useState(false);
// 上传前
const beforeUpload = file => {
// 图片格式
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
if (!isJpgOrPng) {
message.error('You can only upload JPG/PNG file!');
}
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) {
message.error('Image must smaller than 2MB!');
}
return isJpgOrPng && isLt2M;
};
// 转为base64
const getBase64 = (img: Blob, callback: any) => {
const reader = new FileReader();
reader.addEventListener('load', () => callback(reader.result));
reader.readAsDataURL(img);
};
// 上传图片
const handleChange = (info: any) => {
console.log(info.file, 'info');
if (info.file.status === 'uploading') {
setLoading(true);
return;
}
if (info.file.status === 'done') {
getBase64(info.file.originFileObj, (imageUrl: any) => {
setImageUrl(imageUrl);
setSubmitParams({ ...submitParams, cover: info.file.response.url });
setLoading(false);
});
}
};
const handleUpload = e => {
const file = e.target.files[0];
console.log(file, 'hand');
};
图片提交前的处理函数
// 上传前
const beforeUpload = file => {
// 图片格式
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
if (!isJpgOrPng) {
message.error('You can only upload JPG/PNG file!');
}
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) {
message.error('Image must smaller than 2MB!');
}
return isJpgOrPng && isLt2M;
};
上传图片
// 上传图片
const handleChange = (info: any) => {
console.log(info.file, 'info');
// 上传过程中将loading状态设为true
if (info.file.status === 'uploading') {
setLoading(true);
return;
}
// 上传完成
if (info.file.status === 'done') {
getBase64(info.file.originFileObj, (imageUrl: any) => {
// 设置图片路径
setImageUrl(imageUrl);
// 设置提交参数 我这个页是个大表单 图片只是一部分。我们要把上传图片接口返回的图片的
// 存储路径返回
setSubmitParams({ ...submitParams, cover: info.file.response.url });
//上传完成将loading状态设为true
setLoading(false);
});
}
};