1、将配置文件中的信息更换成自己的即可使用 服务端
下载包
npm install ali-oss npm install @alicloud/credentials
TypeScript
import { Injectable } from "@nestjs/common";
import { ResultData } from "src/utils/result-data";
import { STS } from 'ali-oss';
import OSS from 'ali-oss';
import { getCredential } from 'ali-oss/lib/common/signUtils'
import { getStandardRegion } from 'ali-oss/lib/common/utils/getStandardRegion'
import { policy2Str } from 'ali-oss/lib/common/utils/policy2Str'
import { ConfigService } from "@nestjs/config";
@Injectable()
export class UploadService {
constructor(
private readonly configService: ConfigService,
) { }
async generateSignature() {
// 初始化STS客户端
let sts = new STS({
accessKeyId: this.configService.get('oss.accessKeyId'),
accessKeySecret: this.configService.get('oss.accessKeySecret')
});
// 调用assumeRole接口获取STS临时访问凭证
const result = await sts.assumeRole(this.configService.get('oss.assumeRole'), '', '3600');
const accessKeyId = result.credentials.AccessKeyId;
const accessKeySecret = result.credentials.AccessKeySecret;
const securityToken = result.credentials.SecurityToken;
const client = new OSS({
region: this.configService.get('oss.region'),
accessKeyId,
accessKeySecret,
stsToken: securityToken,
bucket: this.configService.get('oss.bucket'),
refreshSTSTokenInterval: 0,
refreshSTSToken: async () => {
const { accessKeyId, accessKeySecret, securityToken } = await client.getCredential();
return { accessKeyId, accessKeySecret, stsToken: securityToken };
},
});
const formData = new Map();
const date = new Date();
const expirationDate = new Date(date);
expirationDate.setMinutes(date.getMinutes() + 10);
function padTo2Digits(num) {
return num.toString().padStart(2, '0');
}
function formatDateToUTC(date) {
return (
date.getUTCFullYear() +
padTo2Digits(date.getUTCMonth() + 1) +
padTo2Digits(date.getUTCDate()) +
'T' +
padTo2Digits(date.getUTCHours()) +
padTo2Digits(date.getUTCMinutes()) +
padTo2Digits(date.getUTCSeconds()) +
'Z'
);
}
const formattedDate = formatDateToUTC(expirationDate);
// 生成x-oss-credential并设置表单数据
const credential = getCredential(formattedDate.split('T')[0], getStandardRegion(client.options.region), client.options.accessKeyId);
formData.set('x_oss_date', formattedDate);
formData.set('x_oss_credential', credential);
formData.set('x_oss_signature_version', 'OSS4-HMAC-SHA256');
// 创建policy
// 示例policy表单域只列举必填字段
const policy: { expiration: string, conditions: any[] } = {
expiration: expirationDate.toISOString(),
conditions: [
{ 'bucket': this.configService.get('oss.bucket') },
{ 'x-oss-credential': credential },
{ 'x-oss-signature-version': 'OSS4-HMAC-SHA256' },
{ 'x-oss-date': formattedDate },
],
};
// 如果存在STS Token,添加到策略和表单数据中
if (client.options.stsToken) {
policy.conditions.push({ 'x-oss-security-token': client.options.stsToken });
formData.set('security_token', client.options.stsToken);
}
// 生成签名并设置表单数据
const signature = client.signPostObjectPolicyV4(policy, date);
formData.set('policy', Buffer.from(policy2Str(policy), 'utf8').toString('base64'));
formData.set('signature', signature);
// 返回表单数据
const data = {
host: `http://${client.options.bucket}.oss-${client.options.region}.aliyuncs.com`,
policy: Buffer.from(policy2Str(policy), 'utf8').toString('base64'),
x_oss_signature_version: 'OSS4-HMAC-SHA256',
x_oss_credential: credential,
x_oss_date: formattedDate,
signature: signature,
accessKeyId: client.options.accessKeyId,
accessKeySecret: client.options.accessKeySecret,
region: client.options.region,
bucket: client.options.bucket,
dir: this.configService.get('oss.dir'), // 指定上传到OSS的文件前缀
stsToken: client.options.stsToken
};
return ResultData.ok(data)
}
}
2、使用 客户端
下载包:
TypeScriptnpm i ali-oss
主要代码:
TypeScript// OSS实例 const getOssClient = (ossInfo: any) => { const client = new OSS({ region: ossInfo.region, authorizationV4: true, accessKeyId: ossInfo.accesskeyid, accessKeySecret: ossInfo.accesskeysecret, stsToken: ossInfo.security_token, bucket: ossInfo.bucket, }); return client; }; // 普通上传 await ossClient.put(`${dir}/${fileName}`, file) //分包上传 ossClient.multipartUpload(`${dir}/${fileName}`, file, { parallel: 4, partSize: 1024 * 1024 * 10, progress: function (p: any, cpt: any) { console.log(p, cpt); }, }).then(function (res: any) { setFileCount(fileCount - 1); for (let i in res.res.requestUrls) { fileListRef.current.push({ url: res.res.requestUrls[i], courseId: '', }); } updateFileString?.(JSON.parse(JSON.stringify(fileListRef.current))); });
TypeScript
import { getOssToken } from '@/services/system';
// @ts-ignore
import OSS from 'ali-oss';
import { Button, Image, message, Spin, Upload } from 'antd';
import { useEffect, useRef } from 'react';
import { useImmer } from 'use-immer';
const UploadFile = ({
maxCount = 1,
fileList,
multiple = true,
updateFileString,
children,
accept = '*',
disabled = false,
subPackageSize = 1024 * 1024 * 100, //分包大小
}: {
maxCount?: number;
fileList: { url: string; courseId: string }[];
updateFileString?: Function;
children?: any;
multiple?: boolean;
accept?: string;
disabled?: boolean;
subPackageSize?: number;
}) => {
const fileListRef = useRef<Array<{ url: string; courseId: string }>>([]);
const [update, setUpdate] = useImmer(false);
useEffect(() => {
setUpdate(!update);
fileListRef.current = JSON.parse(JSON.stringify(fileList));
}, [fileList]);
const [fileCount, setFileCount] = useImmer(0);
useEffect(() => {
if (fileCount > 0) {
message.loading({
content: '上传中...',
duration: 0
});
} else {
message.destroy()
}
}, [fileCount]);
function getFileBaseNameAndExtension(file: File) {
// 确保传入的是 File 对象或具有 name 属性的对象
if (!file || typeof file.name === 'undefined') {
throw new Error('无效的文件对象');
}
const fullName = file.name;
const lastDotIndex = fullName.lastIndexOf('.');
let fileNameWithoutExt;
let extension;
if (lastDotIndex === -1) {
// 文件名中没有点
fileNameWithoutExt = fullName;
extension = ''; // 或者可以设为 null
} else {
// 截取点之前的部分作为文件名
fileNameWithoutExt = fullName.substring(0, lastDotIndex);
// 截取点及之后的部分作为扩展名(包含点)
extension = fullName.substring(lastDotIndex); // 例如 ".jpg", ".pdf"
// 如果你希望 extension 不包含点,使用: extension = fullName.substring(lastDotIndex + 1);
}
return {
fileName: fileNameWithoutExt + '_flyco_' + new Date().getTime(), // 不带后缀的文件名
extension: extension, // 后缀名(包含点)
};
}
function parseOSSFileName(fileName: string) {
fileName = decodeURIComponent(fileName.split('/')[fileName.split('/').length - 1]);
// 1. 找到最后一个点的位置来分离扩展名
const lastDotIndex = fileName.lastIndexOf('.');
// 2. 如果有后缀名
if (lastDotIndex !== -1) {
const extension = fileName.substring(lastDotIndex); // 包含点,如 ".docx"
const nameWithoutExt = fileName.substring(0, lastDotIndex); // 去掉后缀的部分
// 3. 使用正则匹配 _flyco_后跟纯数字 的模式
const match = nameWithoutExt.match(/^(.+?)_flyco_\d+$/);
// 4. 如果匹配成功,返回 捕获的原始名 + 后缀;否则返回原文件名
if (match) {
return match[1] + extension; // 原始名 + 后缀
} else {
return match + extension;
}
}
// 5. 如果没有后缀 或 不符合规则,直接返回原文件名
return fileName;
}
// 获取ossApi
const getOssTokenApi = (file: any) => {
getOssToken().then(async (res) => {
if (res.code == 200) {
const OssInit = getOssClient(res.data);
if (file.size > subPackageSize) {
updateSubPackageFile(res.data.dir, OssInit, file);
} else {
uploadPackageFile(res.data.dir, OssInit, file);
}
}
});
};
// OSS实例
const getOssClient = (ossInfo: any) => {
const client = new OSS({
region: ossInfo.region,
authorizationV4: true,
accessKeyId: ossInfo.accessKeyId,
accessKeySecret: ossInfo.accessKeySecret,
stsToken: ossInfo.stsToken,
bucket: ossInfo.bucket,
});
return client;
};
// 分包上传
const updateSubPackageFile = (dir: string, ossClient: any, file: any) => {
const fileBaseNameAndExtension = getFileBaseNameAndExtension(file);
const fileName = fileBaseNameAndExtension.fileName + fileBaseNameAndExtension.extension;
ossClient
.multipartUpload(`${dir}/${fileName}`, file, {
parallel: 4,
partSize: 1024 * 1024 * 10,
progress: function (p: any, cpt: any) {
console.log(p, cpt);
},
})
.then(function (res: any) {
setFileCount(fileCount - 1);
for (let i in res.res.requestUrls) {
fileListRef.current.push({
url: res.res.requestUrls[i],
courseId: '',
});
}
updateFileString?.(JSON.parse(JSON.stringify(fileListRef.current)));
});
};
// 普通上传
const uploadPackageFile = async (dir: string, ossClient: any, file: any) => {
const fileBaseNameAndExtension = getFileBaseNameAndExtension(file);
const fileName = fileBaseNameAndExtension.fileName + fileBaseNameAndExtension.extension;
const result = await ossClient.put(`${dir}/${fileName}`, file);
setFileCount(fileCount - 1);
fileListRef.current.push({
url: result.url,
courseId: '',
});
updateFileString?.(JSON.parse(JSON.stringify(fileListRef.current)));
};
return (
<div className="flex items-center flex-wrap">
{fileListRef.current.map((item: { url: string; courseId: string }, index: number) => {
const fileName = parseOSSFileName(item.url);
if (accept != 'image/*') {
return (
<div key={index} className="text-mainColor ml-2 mb-2 mr-3 border-b-[2px] border-t-[0] border-x-[0] border-mainColor border-solid flex-wrap flex max-w-[200px] cursor-pointer">
<div className="max-w-[150px] text-ellipsis">
<a target="_blank" className="max-w-[150px] text-ellipsis inline-block overflow-hidden text-nowrap" href={item.url}>
{fileName}
</a>
</div>
{!disabled && (
<div
className="iconfont icon-shanchu ml-2 cursor-pointer"
onClick={() => {
fileListRef.current.splice(index, 1);
updateFileString?.(JSON.parse(JSON.stringify(fileListRef.current)));
}}
></div>
)}
</div>
);
} else {
return (
<div className="w-[80px] h-[80px] text-center relative ml-2 border-dashed border-[#ccc] border-[1px]">
<Image
height={80}
width={'auto'}
src={item.url}
key={index}
fallback="data:image/png;base64,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"
/>
{!disabled && (
<div
className="iconfont icon-shanchu text-[red] cursor-pointer ml-2 absolute right-0 top-0 cursor-pointer"
onClick={() => {
fileListRef.current.splice(index, 1);
updateFileString?.(JSON.parse(JSON.stringify(fileListRef.current)));
}}
></div>
)}
</div>
);
}
})}
{fileListRef.current.length < maxCount && !disabled ? (
<Upload
accept={accept ? accept : '*'}
fileList={[]}
customRequest={(e: any) => {
setFileCount(fileCount + 1);
getOssTokenApi(e.file);
}}
multiple={multiple}
>
<div className=" ml-3">{children ? children : <Button type="primary">上传附件</Button>}</div>
</Upload>
) : (
''
)}
</div>
);
};
export default UploadFile;