闲来无事,手撸了个博客站,给孩子和自己用,想偶尔写点东西放上去。(孩子的博客: www.yufangran.com )
现在的手机拍照很方便,可是直接上传博客又有些大,而且手机的照片包含很多额外的信息,像地理位置信息,相机参数信息,便不想直接上传原图。于是想,上传时候是否可以压缩图片并删除额外的不必要的信息。
简单说,前端压缩图片并上传阿里云OSS, 思路有二:
其一是传统做法, 简单方便,弊端是比较费带宽。作为一个乞丐版服务器,带宽可怜的很。
其二前端压缩同样方便,但前端直传OSS,尤其压缩后直传案例不是很多。
当然,其他的方案还有很多,本着最省钱,也最擅长的原则,暂时选用方案二。
好了,直接上实现。
过程时序图如下:
此处用的是服务端签名直传 web直传, 点击链接可直达OSS服务端签名文档文档
import * as OSS from 'ali-oss'; async ossUploadSignup() { const client = new OSS(this.configService.ossConfig); const date = new Date(); date.setDate(date.getDate() + 1); const policy = { expiration: date.toISOString(), // 请求有效期 conditions: [ ['content-length-range', 0, 1048576000], // 设置上传文件的大小限制 { bucket: client.options.bucket }, // 限制可上传的bucket ], }; // // 跨域才设置 // res.set({ // 'Access-Control-Allow-Origin': req.headers.origin || '*', // 'Access-Control-Allow-Methods': 'PUT,POST,GET', // }); //签名 const formData = await client.calculatePostSignature(policy); //bucket域名 const host = `https://${this.configService.ossConfig.bucket}.${ (await client.getBucketLocation()).location }.aliyuncs.com`.toString(); //回调 const callback = { callbackUrl: this.configService.ossConfig.callbackUrl, callbackBody: 'bucket=${bucket}&filename=${object}&etag=${etag}&size=${size}&mimeType=${mimeType}&imageInfo.height=${imageInfo.height}&imageInfo.width=${imageInfo.width}&imageInfo.format=${imageInfo.format}&sid=${sid}&uuid=${uuid}', callbackBodyType: 'application/x-www-form-urlencoded', }; //返回参数 return { expire: new Date(Date.now() + 1000 * 60).toUTCString(), policy: formData.policy, signature: formData.Signature, accessId: formData.OSSAccessKeyId, host, callback: Buffer.from(JSON.stringify(callback)).toString('base64'), dir: this.configService.ossConfig.dir, }; }
oss 上传完成后callback:
@Post('alioss/callback') async callback(@Body() body): Promise<any> { // save file info into DB return { fileUrl: `/myblog/file/oss/get/${body.filename}` }; // alioss 文件地址本地化,后用302跳转 }
@Get('myblog/file/oss/get/*/:fileName') @Header('Cache-Control', '31104000') async getOssFile( @Param('fileName') fileName, @Req() req, @Res() res, ): Promise<any> { const host = ((await this.fileService.ossUploadSignup()) as any).host || ''; res.redirect(`${host}/${req.path.replace('/myblog/file/oss/get/', '')}`); }
话不多说, 直接上代码
import { Button, Upload, UploadFile } from 'antd'; // 省略部分依赖 export const FileUploader: = () => { // ... 省略部分state effect const beforeUpload = async (file) => { const fileType = getFileTypeFromExt(getFileExt(file.name)); if (!(await fileChecking(file))) return; // 检查文件类型, Marico在blog中就只接受图片和视频 if (fileType === 'image') { const _file: any = await compressImage(file); // 压缩图片 if (_file) { _file.name = file.name; _file.lastModifiedDate = new Date(); return _file; } else { console.log(`compress image failed`); } } return false; }; // 压缩图片主方法 const compressImage = async (file) => { const reader = new FileReader(); reader.readAsDataURL(file); return new Promise((resolve) => { reader.onload = function (event) { const img = new Image(); // 创建Image对象 img.src = event.target?.result as string; img.onload = async function () { // 创建Canvas元素 const canvas = document.createElement('canvas'); const _width = img.width; const _height = img.height; const ratio = _width / _height; const width = _width > 1980 ? 1980 : _width; // 压缩后图片尺寸, 此处Marico依据图片宽度按比例压缩 const height = width / ratio; // 根据宽高比, 计算压缩后图片高度 canvas.width = width; canvas.height = height; const ctx = canvas.getContext('2d'); ctx?.drawImage(img, 0, 0, width, height); // 将Canvas转换回Blob文件对象 canvas.toBlob( function (blob) { // 这里的blob就是压缩后的图片文件 resolve(blob); }, 'image/webp', // 此处笔者选择的是webp格式, 经过几轮测试, 发现在图片质量和图片大小方面,jpg,png,webp中webp最佳,就选它了 0.8 // 0.8 是压缩质量,可以根据需要调整 ); }; }; }); }; const handleUploadChange = async (info) => { const fileIssue = fileChecking(info.file, false); if (!fileIssue) return; onUploadChange?.(info); if (info.file.status === 'done') { // 文件上传成功 } else if (info.file.status === 'error') { // 文件上传失败 } else if (info.file.status === 'removed') { // 文件被移除 } else { // .... } }; // 此处是重点, Antd 组件直传OSS参数配置 // 查询了好多资料,各种上传处理,都太复杂了,自认为这个最简单了 // ossSignature 是server端拿到的签名信息,怎么获取不多赘述 const uploadParams: (file: UploadFile<any>) => Record<string, unknown> = (file: UploadFile) => ({ key: ossSignature?.dir + getFileName(file.name), policy: ossSignature?.policy, OSSAccessKeyId: ossSignature?.accessId, success_action_status: 200, signature: ossSignature?.signature, callback: ossSignature?.callback }); // Antd 上传组件 // 用法不多赘述,直接看antd文档即可 return ( <Upload action={ossSignature?.host} fileList={file2upload} showUploadList maxCount={maxCount} multiple={maxCount > 1} accept={getAcceptation()} onChange={handleUploadChange} beforeUpload={beforeUpload} data={uploadParams}> <Button icon={<UploadOutlined />}> 点击上传 </Button> </Upload> ); };
至此, 服务端签名, 前端web直传阿里云oss 实现图片压缩上传阿里云OSS就完成了。