nest.js 上传图片,文件 , apk 到 oss

4 阅读2分钟

好,这里我从 0 → 1,把 NestJS 上传图片 / 文件 / APK 到阿里云 OSS 给你完整讲一遍,包含:

  • 整体架构
  • 阿里云 OSS 控制台怎么建
  • NestJS 代码(可直接用)
  • 图片 / 普通文件 / APK 的区别处理
  • 常见坑(真实项目级)

你可以照着一步一步做


一、整体流程(先有全局概念)

前端
  ↓  multipart/form-data
NestJS(接收文件)
  ↓
阿里云 OSS SDK
  ↓
OSS Bucket(对象存储)
  ↓
返回文件访问 URL

二、阿里云 OSS:控制台从 0 新建(必做)

1️⃣ 登录阿里云 → 对象存储 OSS


2️⃣ 创建 Bucket

关键配置(重要⚠️)

项目推荐值
Bucket 名称比如:my-iot-files
地域离你服务器最近(如:华东1-杭州)
存储类型标准存储
读写权限公共读(前端要直接访问)
版本控制关闭(一般)

👉 公共读 = 不需要签名 URL


3️⃣ 获取 AccessKey(重点)

创建 RAM 用户(推荐)

  1. 进入 RAM 访问控制

  2. 新建用户

  3. 勾选 编程访问

  4. 赋权:

    • AliyunOSSFullAccess
  5. 记录:

    • AccessKeyId
    • AccessKeySecret

⚠️ 不要用主账号 Key


4️⃣ 记录 OSS 信息(你后面要用)

Region: oss-cn-hangzhou
Bucket: my-iot-files
Endpoint: https://oss-cn-hangzhou.aliyuncs.com

三、NestJS 项目准备

1️⃣ 安装依赖

npm install @nestjs/platform-express multer
npm install ali-oss

2️⃣ 安装类型(TS)

npm install -D @types/multer

四、NestJS 基础上传(核心)

1️⃣ 新建 OSS 工具类(强烈推荐)

📁 src/common/oss/oss.service.ts

import * as OSS from 'ali-oss';
import { Injectable } from '@nestjs/common';

@Injectable()
export class OssService {
  private client: OSS;

  constructor() {
    this.client = new OSS({
      region: 'oss-cn-hangzhou',
      accessKeyId: process.env.OSS_ACCESS_KEY!,
      accessKeySecret: process.env.OSS_ACCESS_SECRET!,
      bucket: process.env.OSS_BUCKET!,
    });
  }

  async uploadBuffer(
    buffer: Buffer,
    filePath: string,
    mimeType: string,
  ) {
    return await this.client.put(filePath, buffer, {
      headers: {
        'Content-Type': mimeType,
      },
    });
  }
}

2️⃣ 环境变量(非常重要)

📁 .env

OSS_ACCESS_KEY=xxxx
OSS_ACCESS_SECRET=xxxx
OSS_BUCKET=my-iot-files

五、Controller:上传文件接口

1️⃣ 上传任意文件(图片 / apk / zip)

📁 upload.controller.ts

import {
  Controller,
  Post,
  UploadedFile,
  UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { OssService } from './oss.service';
import { extname } from 'path';

@Controller('upload')
export class UploadController {
  constructor(private readonly ossService: OssService) {}

  @Post('file')
  @UseInterceptors(FileInterceptor('file'))
  async uploadFile(@UploadedFile() file: Express.Multer.File) {
    const ext = extname(file.originalname);
    const filename = `${Date.now()}${ext}`;

    const filePath = `files/${filename}`;

    const res = await this.ossService.uploadBuffer(
      file.buffer,
      filePath,
      file.mimetype,
    );

    return {
      url: res.url,
      name: file.originalname,
    };
  }
}

2️⃣ 前端上传方式

const formData = new FormData()
formData.append('file', file)

fetch('/upload/file', {
  method: 'POST',
  body: formData
})

六、区分 图片 / 文件 / APK(真实项目)

1️⃣ 目录结构建议(很重要)

oss bucket
 ├── images/
 ├── files/
 ├── apk/
 └── temp/

2️⃣ APK 专用上传接口(推荐)

@Post('apk')
@UseInterceptors(FileInterceptor('file'))
async uploadApk(@UploadedFile() file: Express.Multer.File) {
  if (!file.originalname.endsWith('.apk')) {
    throw new Error('只能上传 APK');
  }

  const filename = `app_${Date.now()}.apk`;
  const filePath = `apk/${filename}`;

  const res = await this.ossService.uploadBuffer(
    file.buffer,
    filePath,
    'application/vnd.android.package-archive',
  );

  return {
    url: res.url,
    version: '1.0.0',
  };
}

3️⃣ 图片校验(推荐)

if (!file.mimetype.startsWith('image/')) {
  throw new Error('不是图片');
}

七、APK 升级场景(你前面问过)

返回数据示例

{
  "version": "1.2.3",
  "url": "https://xxx.oss-cn-hangzhou.aliyuncs.com/apk/app_123.apk",
  "force": true
}

Flutter / Android

  • 打开 URL
  • 系统下载
  • 调起安装

八、常见坑(一定要看)

❌ 1. 上传成功但访问 403

👉 Bucket 不是 公共读


❌ 2. APK 下载后打不开

👉 Content-Type 错了
必须:

application/vnd.android.package-archive

❌ 3. 大文件内存爆炸

👉 不要用 buffer
👉 用 stream 上传

如果你需要,我可以给你 流式上传版本


❌ 4. 前端跨域

OSS 控制台 → Bucket → 跨域设置

AllowedOrigin: *
AllowedMethod: GET,POST

九、真实项目推荐架构(进阶)

upload.controller
upload.service
oss.service
file.entity(存数据库)

数据库存:

id
url
type
size
user_id
created_at