nestjs 阿里云服务端签名

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、使用 客户端

下载包:

TypeScript 复制代码
npm 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;
相关推荐
wordbaby4 小时前
用 useReducer 优雅管理 React 状态
前端·react.js
不爱说话郭德纲4 小时前
还记得第一次遇到内存泄漏的场景嘛?
前端·面试·性能优化
徐礼昭|商派软件市场负责人4 小时前
最新!阿里财报电话会蒋凡与吴泳铭透露重要信息:淘宝闪购成绩斐然;零售与AI双轮驱动;阿里云推出“Agent Bay”新产品···
人工智能·阿里云·零售
南囝coding4 小时前
2025 最新!独立开发者穷鬼套餐
前端·后端
LaiYoung_4 小时前
前端国际化适配提速 90%!这款 JS 脚本 CLI 工具,自动提中文、分模块、做替换,比 AI 更稳定
前端·javascript·人工智能
AWS官方合作商4 小时前
构建企业级区块链网络:基于AWS EC2的弹性、高可用解决方案
网络·区块链·aws
低代码布道师5 小时前
CSS 伪类与伪元素:深度解析
前端·css
星月前端5 小时前
css3元素倒影效果属性:box-reflect
前端·css·css3