闲来无事,手撸了个博客站,给孩子和自己用,想偶尔写点东西放上去。(孩子的博客: 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就完成了。