从 0 到 1快速搭建一个可复用OSS 上传工具

216 阅读6分钟

Github 仓库地址:github.com/hahala2333/…

本教程面向前端开发者,旨在帮助你从 0 到 1,快速搭建一个可重复使用的、带有 CLI 和编程化接口的 OSS 上传工具。


🎯 设计思路与项目规划

  1. 功能需求
    • 通过配置文件或命令行传入阿里云 OSS 凭证及参数;
    • 自动加载 oss-uploader.config.cjs
    • 提供命令行工具(initupload)和编程接口 uploadStatic
  1. 模块划分
    • 配置层:生成/加载默认配置文件、合并命令行/API 参数;
    • 核心逻辑层:校验参数、递归遍历本地目录、调用 ali-oss SDK 上传;
    • 接口层:CLI (commander 实现) 与编程接口。
  1. 扩展与集成
    • 可在 Vite、Webpack 等打包后钩子(closeBundle)中调用;
    • 易于集成到 CI/CD 脚本或其它自动化流程;
    • 后续可添加并发控制、进度条、上传钩子等功能。

📂 目录结构

oss-uploader/
├─ src/
│  ├─ index.ts               # 核心逻辑
│  └─ cli.ts                 # CLI 实现
├─ dist/                     # 编译输出目录
├─ oss-uploader.config.cjs   # 用户配置文件模板
├─ package.json              # 项目元信息及脚本
├─ tsconfig.json             # TypeScript 配置
└─ README.md                 # 项目说明文档

一、前置准备

  1. Node.js 环境:建议使用 Node 14+,本文中以 Node 20.11 为例。
  2. 包管理器:npm 或 Yarn。
  3. 阿里云 OSS 账号,并确保拥有对应 Bucket 的读写权限。

二、项目初始化

# 创建项目目录
mkdir custom-oss-uploader && cd custom-oss-uploader

# 初始化 npm
npm init -y

# 安装运行时依赖
npm install ali-oss lodash.merge fs-extra commander

# 安装开发时依赖
npm install -D typescript @types/node @types/fs-extra @types/commander
  • ali-oss:阿里云 OSS SDK
  • lodash.merge:深度合并配置
  • fs-extra:增强版文件系统操作
  • commander:构建 CLI

三、TypeScript 配置

在项目根目录新建 tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "declaration": true,
    "outDir": "dist",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src/**/*"]
}
  • module: CommonJS 兼容大多数项目
  • preserveShebang 保留 CLI 声明行

四、实现核心逻辑(src/index.ts

  1. 核心逻辑可以拆分为以下几步:
  2. 加载配置
    • 合并参数:默认值 < 配置文件 < 命令行/调用时传入
    • 先从项目根目录同步读取用户写好的 oss-uploader.config.cjs(或 .js)配置文件,抛出异常如果文件不存在。
    • 有了文件配置后,再用一个包含默认值(空的凭证占位、默认 prefix = ""、默认 sourceDir = "dist"、默认 OSS headers)的 defaultConfig 跟用户配置以及运行时传入的 opts(CLI 参数或调用接口时传的对象)做深度合并,得到最终的 config 对象。
  1. 校验必填项
    • 从合并后的 config 里检查 accessKeyIdaccessKeySecretbucketregion 四个字段是否都已填写,否则直接抛错误提醒用户补全。
  1. 初始化 OSS 客户端
    • 基于校验通过的 config 构造 new OSS(config) 实例,用它来跟阿里云 OSS 交互。
  1. 递归遍历并上传
    • 从本地要上传的根目录 baseDir = path.join(cwd, config.sourceDir) 开始,调用 uploadDir(client, currentPath, baseDir, prefix, headers)
      • 如果 currentPath 是目录,就 readdir 一层层向下递归;
      • 如果是文件,就计算它相对于 baseDir 的相对路径 relativePath = path.relative(baseDir, currentPath).replace(/\/g, "/"),然后拼出 OSS 上的对象名 objectName = prefix + relativePath,调用 client.put(objectName, currentPath, { headers }) 上传,成功后打印日志。
  1. 输出结果
    • 每上传成功一个文件就输出一行「✔ 上传 /本地/路径 → prefix/相对/路径 成功」,方便追踪进度。
import OSS from "ali-oss";
import fs from "fs-extra";
import merge from "lodash/merge";
import path from "path";

export interface Config {
  accessKeyId: string;
  accessKeySecret: string;
  bucket: string;
  region: string;
  prefix?: string;
  headers?: Record<string, string>;
  sourceDir?: string;
}

// 从项目根目录加载 CJS 配置文件
function loadConfigFile(cwd: string): Partial<Config> {
  const cjsPath = path.join(cwd, "oss-uploader.config.cjs");
  if (fs.existsSync(cjsPath)) {
    return require(cjsPath);
  }
  throw new Error(
    "找不到配置文件 oss-uploader.config.cjs,请确保在项目根目录创建该文件,并使用 .cjs 扩展名"
  );
}

// 递归上传目录或文件
async function uploadDir(
  client: OSS,
  currentPath: string,
  baseDir: string,
  prefix: string,
  headers: Record<string, string>
) {
  const stat = await fs.stat(currentPath);
  if (stat.isDirectory()) {
    for (const name of await fs.readdir(currentPath)) {
      await uploadDir(
        client,
        path.join(currentPath, name),
        baseDir,
        prefix,
        headers
      );
    }
  } else {
    const relativePath = path
      .relative(baseDir, currentPath)
      .replace(/\/g, "/");
    const objectName = prefix + relativePath;
    const result = await client.put(objectName, currentPath, { headers });
    if (result.res.status === 200) {
      console.log(`✔ 上传 ${currentPath} → ${objectName} 成功`);
    }
  }
}

// 对外暴露的 API
export async function uploadStatic(
  cwd: string,
  opts: Partial<Config> = {}
): Promise<void> {
  const fileConfig = loadConfigFile(cwd);

  const defaultConfig: Config = {
    accessKeyId: "",
    accessKeySecret: "",
    bucket: "",
    region: "",
    prefix: "",
    headers: {
      "x-oss-storage-class": "Standard",
      "x-oss-object-acl": "public-read",
    },
    sourceDir: "dist",
  };

  const config = merge({}, defaultConfig, fileConfig, opts) as Config;
  if (
    !config.accessKeyId ||
    !config.accessKeySecret ||
    !config.bucket ||
    !config.region
  ) {
    throw new Error(
      "请在 oss-uploader.config.cjs 或命令行参数中指定 accessKeyId、accessKeySecret、bucket 和 region"
    );
  }

  const client = new OSS(config as any);
  const baseDir = path.join(cwd, config.sourceDir!);
  await uploadDir(client, baseDir, baseDir, config.prefix!, config.headers!);
}

五、构建 CLI(src/cli.ts

  • INIT 命令:一键在项目根目录生成配置文件模板。
  • UPLOAD 命令:解析命令行参数(包括覆盖默认目录 dist、OSS 凭证、bucket、region、prefix 等),然后把这些参数“打包”给核心函数 uploadStatic,由它来完成真正的文件上传。
  • 整个 CLI 文件的职责就是「收集参数 → 校验/报错 → 调用核心 API」,而不涉及具体的上传、遍历、合并逻辑。
#!/usr/bin/env node
import { Command } from "commander";
import fs from "fs-extra";
import path from "path";
import { Config, uploadStatic } from "./index";

const program = new Command();

program
  .name("oss-uploader")
  .description("将指定目录(默认 dist)上传到阿里云 OSS")
  .version("1.0.0");

program
  .command("init")
  .description("在项目根目录生成配置文件 oss-uploader.config.cjs 模板")
  .action(() => {
    const target = path.join(process.cwd(), "oss-uploader.config.cjs");
    if (fs.existsSync(target)) {
      console.error("⚠ oss-uploader.config.cjs 已存在");
      process.exit(1);
    }
    fs.writeFileSync(
      target,
      `/**
 * OSS Uploader CJS 配置文件
 */
module.exports = {
  accessKeyId: "<YOUR_ACCESS_KEY_ID>",
  accessKeySecret: "<YOUR_ACCESS_KEY_SECRET>",
  bucket: "<YOUR_BUCKET_NAME>",
  region: "<YOUR_OSS_REGION>",
  prefix: "static/",
  sourceDir: "dist",
  headers: {
    "x-oss-storage-class": "Standard",
    "x-oss-object-acl": "public-read"
  }
};\n`
    );
    console.log("✔ 已创建 oss-uploader.config.cjs,请根据实际情况填写");
  });

program
  .command("upload")
  .description("上传指定目录到 OSS,目录默认为 dist")
  .option("-a, --accessKeyId <id>")
  .option("-s, --accessKeySecret <secret>")
  .option("-b, --bucket <bucket>")
  .option("-r, --region <region>")
  .option("-p, --prefix <prefix>")
  .option("-d, --dir <directory>", "本地静态资源目录,默认 dist")
  .action(async (opts: Partial<Config> & { dir?: string }) => {
    try {
      const runOpts: Partial<Config> = { ...opts };
      if (opts.dir) runOpts.sourceDir = opts.dir;
      await uploadStatic(process.cwd(), runOpts);
    } catch (err: any) {
      console.error("✖ 上传失败:", err.message);
      process.exit(1);
    }
  });

program.parse(process.argv);

六、完整的package.json

{
  "name": "oss-uploader",
  "version": "1.0.0",
  "description": "OSS 上传工具。",
  "main": "index.js",
  "types": "dist/index.d.ts",
  "bin": {
    "oss-uploader": "dist/cli.js"
  },
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "require": "./dist/index.js"
    },
    "./cli": {
      "import": "./dist/cli.js",
      "require": "./dist/cli.js"
    }
  },
  "scripts": {
    "build": "tsc",
    "prepare": "npm run build"
  },
  "keywords": [],
  "author": "haha2333",
  "license": "ISC",
  "dependencies": {
    "ali-oss": "^6.23.0",
    "commander": "^14.0.0",
    "fs-extra": "^11.3.0",
    "lodash": "^4.17.21",
    "lodash.merge": "^4.6.2"
  },
  "devDependencies": {
    "@types/ali-oss": "^6.16.11",
    "@types/commander": "^2.12.5",
    "@types/fs-extra": "^11.0.4",
    "@types/lodash": "^4.17.20",
    "@types/node": "^24.0.14",
    "typescript": "^5.8.3"
  },
  "files": [
    "dist"
  ]
}

七、发布到私有仓库

1️⃣ 登录私有仓库

运行以下命令,登录你的私有仓库:

npm login --registry=https://your-private-registry.com/

2️⃣ 发布工具库

运行以下命令,将工具库发布到私有仓库:

npm publish

至此,你的七牛云文件上传工具库已经成功发布到私有仓库,可以在其他项目中通过 NPM 进行安装和使用。


八、使用

一、安装

在项目根目录运行:

npm install --save-dev oss-uploader

二、初始化配置

执行初始化命令:

npx oss-uploader init

该命令会在项目根目录生成 oss-uploader.config.cjs 配置文件模板。请根据实际情况修改以下字段:

module.exports = {
  accessKeyId: "<YOUR_ACCESS_KEY_ID>", // 阿里云 AccessKeyId
  accessKeySecret: "<YOUR_ACCESS_KEY_SECRET>", // 阿里云 AccessKeySecret
  bucket: "<YOUR_BUCKET_NAME>", // 要上传的 OSS Bucket 名称
  region: "oss-cn-hangzhou", // OSS 所在地域
  prefix: "static/", // 上传后在 Bucket 下的前缀目录
  sourceDir: "dist", // 本地待上传目录,默认 dist
  headers: {
    "x-oss-storage-class": "Standard",
    "x-oss-object-acl": "public-read",
  },
};

三、使用

一、CLI 使用

1. 基本命令
npx oss-uploader upload
  • 将读取 oss-uploader.config.cjs(.js) 配置,并将 sourceDir 目录中的所有文件递归上传到 OSS。
2. 参数覆盖

可以通过命令行参数动态覆盖配置文件中的任意字段:

npx oss-uploader upload \
  --dir=build            # 覆盖 sourceDir
  --prefix=assets/        # 覆盖 prefix
  --accessKeyId=AKID      # 覆盖 AccessKeyId
  --accessKeySecret=SK    # 覆盖 Secret
  --bucket=my-bucket      # 覆盖 Bucket
  --region=oss-cn-shanghai# 覆盖 Region
参数说明默认值
--dir, -d本地静态资源目录dist
--prefix, -pOSS 上的目录前缀''
--accessKeyId, -a阿里云 AccessKeyId
--accessKeySecret,-s阿里云 AccessKeySecret
--bucket, -bOSS Bucket 名称
--region, -rOSS 地域

二、npm 脚本集成

package.json 中添加:

{
  "scripts": {
    "build": "vite build",
    "deploy": "npm run build && npx oss-uploader upload"
  }
}

执行:

npm run deploy

即可完成 vite build 与静态资源上传两步操作。


三、Vite 插件模式(Vite <7)

vite.config.js 中注册 closeBundle 钩子,自动调用 uploadStatic()

// vite.config.js (Vite <=6)
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { uploadStatic } from "oss-uploader";

export default defineConfig({
  plugins: [
    vue(),
    {
      name: "oss-upload-plugin",
      closeBundle: async () => {
        console.log("📦 构建完成,开始上传 OSS...");
        try {
          await uploadStatic(process.cwd());
          console.log("✅ 上传成功");
        } catch (e) {
          console.error("❌ 上传失败", e);
        }
      },
    },
  ],
});