好,这里我从 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 用户(推荐)
-
进入 RAM 访问控制
-
新建用户
-
勾选 编程访问
-
赋权:
AliyunOSSFullAccess
-
记录:
AccessKeyIdAccessKeySecret
⚠️ 不要用主账号 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