十分钟搞定Nestjs上传文件到阿里云OSS

97 阅读3分钟

背景:接到产品需求,需要将运营数据生成excel,再上传到阿里云的oss,再返回可下载的连接。此处使用Nestjs简化操作,放核心代码。

这也是后台系统中常见的操作,现在开始十分钟教大家完成此项功能。

步骤:

  1. 生成excel
  2. 文件上传到阿里云: 使用oss sdk将文件Buffer上传的到oss
  3. 整合到Nestjs服务

注意事项: 如果是私有桶,需要获取带签名的下载链接才能正常下载哈。

先浏览下成果

image.png

1. 生成excel

先安装包pnpm i xlsx

生成excel, 这一步比较简单,可直接写在服务里

import * as XLSX from 'xlsx';
import { Buffer } from 'buffer'; // 明确导入 Buffer

  // 创建Excel
  async createExcel({ sheetName = 'test1' }: any) {
    const users = [
      { id: 1, name: '张三2', age: 25, email: 'zhangsan@example.com' },
      { id: 2, name: '李四', age: 30, email: 'lisi@example.com' },
      { id: 3, name: '王五', age: 28, email: 'wangwu@example.com' },
    ];
    const worksheet = XLSX.utils.json_to_sheet(users);
    // 新建表格
    const workbook = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(workbook, worksheet, sheetName);
    const buffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'buffer' });
    return buffer as Buffer;
  }

2. 封装阿里云OSS上传工具函数

先安装包pnpm i ali-oss

在 util 中封装上传函数 oss.util.ts

import * as OSS from 'ali-oss';

export class OssUtil {
  private static client: OSS | null = null;

  /**
   * 初始化,必须在应用启动时或首次使用是完成初始化
   * config: 阿里云配置
   */
  public static init(config: any): void {
    // 如果已初始化,跳过初始化
    if (this.client) return;
    // 从配置文件中取oss配置
    this.client = new OSS({
      region: config.region,
      accessKeyId: config.accessKeyId,
      accessKeySecret: config.accessKeySecret,
      bucket: config.bucket,
    });
  }

  /**
   * 获取 OSS 客户端实例,如果未初始化则抛出错误
   */
  private static getClient(): OSS {
    if (!this.client) {
      throw new Error('OSS 客户端未初始化,请先调用 OssUtil.initialize(config) 方法。');
    }
    return this.client;
  }

  /**
   * 上传文件 Buffer 到阿里云 OSS
   * @param objectName - 在 OSS 中的目标路径和文件名 (e.g., 'exports/report_timestamp.xlsx')
   * @param buffer - 要上传的文件内容的 Node.js Buffer
   * @returns 包含文件 URL 的 Promise
   */
  public static async uploadBuffer(objectName: string, buffer: Buffer): Promise<string> {
    const client = this.getClient();

    const options: OSS.PutObjectOptions = {
      // 如果需要,可以在这里设置 Content-Type, 例如 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
      // headers: { 'Content-Type': 'application/octet-stream' }, 
    };

    try {
      const result = await client.put(objectName, buffer, options);
      console.log('==== oss.util result: ', result);
      // result.url 是文件在 OSS 上的完整访问地址
      return result.url;
    } catch (error) {
      console.error(`[OSS ERROR] 上传文件 ${objectName} 失败:`, error);
      throw new Error(`文件上传到 OSS 失败: ${error.message}`);
    }
  }
  
  /**
   * 生成带有访问权限的签名 URL
   * @param objectName - OSS 中的文件路径和名称
   * @param expirationSeconds - 链接的有效期 (秒), 默认 30 天
   * @returns 签名 URL
   */
  public static getSignedUrl(objectName: string, expirationSeconds: number = 2592000): string {
    const client = this.getClient();

    const url = client.signatureUrl(objectName, {
      expires: expirationSeconds, // 有效期 (秒)
      method: 'GET',
    });
    return url;
  }
}

2.1. 初始化OssUtil

在main.ts中初始化,然后才能在业务服务中使用

import { OssUtil } from './util/oss.util';

async function bootstrap() {
  // 初始化oss
  const ossConfig = {
    region: configService.get<string>('ALIYUN_OSS_REGION'),
    accessKeyId: configService.get<string>('ALIYUN_OSS_ACCESS_KEY_ID'),
    accessKeySecret: configService.get<string>('ALIYUN_OSS_ACCESS_KEY_SECRET'),
    bucket: configService.get<string>('ALIYUN_OSS_BUCKET'),
  };
  OssUtil.init(ossConfig);

}
bootstrap();

在业务服务中使用,简单示例

  // 3. 上传到 OSS
  const ossResult = await OssUtil.uploadBuffer(objectName, excelBuffer);

3. 整合Service,完成最终上传

在红果服务中使用生成excel+上传阿里云

import * as XLSX from 'xlsx';
import { Buffer } from 'buffer'; // 明确导入 Buffer
import { OssUtil } from 'src/util/oss.util';

@Injectable()
export class HongguoService {
  // 业务
  async zyf_create_short_url(params: any) {
    // 1. 创建Excel
    const excelBuffer = await this.createExcel({ sheetName: 'test1' });
    console.log('===excelBuffer: ', excelBuffer);

    // 2. 上传到 OSS
    const timestamp = new Date().getTime();
    const fileName = `node/test_${timestamp}.xlsx`;
    // 如果是私有桶,此处返回的连接无法下载,需要获取带签名的下载链接
    await OssUtil.uploadBuffer(fileName, excelBuffer);
    // 获取带签名的下载链接(30天有效期)
    const signedUrl = OssUtil.getSignedUrl(fileName);

    return {
      signedUrl
    }
  }

  // 创建Excel
  async createExcel({ sheetName = 'test1' }: any) {
    const users = [
      { id: 1, name: '张三2', age: 25, email: 'zhangsan@example.com' },
      { id: 2, name: '李四', age: 30, email: 'lisi@example.com' },
      { id: 3, name: '王五', age: 28, email: 'wangwu@example.com' },
    ];
    const worksheet = XLSX.utils.json_to_sheet(users);
    // 新建表格
    const workbook = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(workbook, worksheet, sheetName);
    const buffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'buffer' });
    return buffer as Buffer;
  }
  
}

搞定,控制器就不演示了,直接调这个服务即可。

博客-每天进步一点点.jpg

对你有帮助的话请一键三连哈

博客-谢谢.gif