如何开发一个oss-upload cli

51 阅读4分钟

前言


前些天基于web-see搭建了一个监控平台,但是遇到了sourceMap的问题,map文件不能存在于正式环境,受到sentry启发,每次生成map文件后上传到oss并删除本地文件,因此基于oss写了一个node脚本,但是由于每个项目集成过于繁琐,所以开发cli工具作为使用.

如何开发一个cli


先创建项目,用npm init -y初始化。这里我的项目名就叫sydebug-cli了,下文也将用此名称。大家可以自行命名自己的cli,比如my-cli...

bin设置

首先在package.json中加入bin字段配置

"bin": {
    "sydebug-cli": "./index.js"
  },

新建文件index.js,使得sydebug-cli命令指向index.js。把执行文件放在bin目录下是比较规范的做法,当然你可以起任何自己想要的名字及目录。由于只有一个脚本文件所以直接指向根目录了.

在index.js中写入以下代码

#!/usr/bin/env node
console.log(process.argv);

下一步使用npm link(注: mac如果permission denied,前面需要加sudo),创建软链接到全局,这样我们就可以像调用全局包一样在任何地方调用此项目。 完成link后直接在命令行中输入 sydebug-cli param1 param2

[
  '/usr/local/bin/node',
  '/usr/local/bin/sydebug-cli',
  'param1',
  'param2'
]

此时如果成功的话就没问题了,接下来进行cli代码开发

核心库

  • commander.js : 方便开发命令行工具的库,可以解析各种命令
  • ali-oss : 阿里云对象存储oss SDK
  • async/queue : 并发控制,防止node崩溃
  • fs : nodejs 文件系统
  • path : 处理文件与目录路径

程序编写


首先需要引入第三方模块

yarn add commander
yarn add async
yarn add ali-oss

upload


upload功能读取用户配置的upload.js,将静态资源传输到oss

引入资源
#!/usr/bin/env node
const fs = require('fs')
const OSS = require('ali-oss')
const path = require('path')
const  program = require("commander")

默认配置
// oss 默认配置
const ossKey = {
    bucket: 'your bucket',
    region: 'your region'
}
// oss存储路径
const fullPath = '/sourceMap/'
// 本地map文件存储路径
const mapPath = 'dist'
// 版本
const version = '0.0.1'
初始化oss
// oss对象
let client = null
// 初始化OSS
const initOss = (options) => {
  if (!validateOptions(options)) return
  client = new OSS({
    region: ossKey.region,
    accessKeyId: options.accessKeyId,
    accessKeySecret: options.accessKeySecret,
    bucket: ossKey.bucket
  })
}
验证参数

主要需要两个参数 accessKeyId , accessKeySecret连接oss服务器,apikey作为区分项目使用

const validateOptions = ({ apikey, accessKeyId, accessKeySecret}) => {
  if (!apikey) {
    console.log('apikey不能为空')
    process.exit(0)
  }
  if (!accessKeyId) {
    console.log('accessKeyId不能为空')
    process.exit(0)
  }
  if (!accessKeySecret) {
    console.log('accessKeySecret不能为空')
    process.exit(0)
  }
  return true
}
upload主程序编写
// 递归获取指定本地目录下所有文件路径
function scandir(dirpath) {
  var filesArr = [];

  function scan(filepath) {
    var statObj = fs.statSync(filepath);

    if (!statObj.isDirectory()) return filesArr.push(filepath);
    var files = fs.readdirSync(filepath);

    files.forEach((file) => {
      scan(filepath + "/" + file);
    });
  }
  scan(dirpath);
  return filesArr;
}

async function  upload(options) {
  // 验证参数
  if (!validateOptions(options)) return
  // 初始化
  initOss(options)
  const files = scandir(mapPath);
  // 筛选map文件
  const uploadFiles = files.filter(function (file) {
    return /\.map$/.test(file);
  })
  // 获取nodejs当前工作流目录
  const cmdPath = process.cwd()
  uploadFiles.forEach(async (v) => {
    try {
      // 上传本地文件
      await client.put(path.join(fullPath, options.apikey, v.split('/').pop()), path.join(cmdPath, v))
      console.log(`---- 本地文件${v}上传成功 ----`)
      // 删除已上传本地map文件
      fs.unlink(path.join(cmdPath, v), err => {
        if (err) {
          return console.error(err)
        }
        console.log(`---- 本地文件${v}删除成功!----`)
      })
    } catch (error) {
      console.log(`---- 本地文件${v}上传失败 ----: ${error}`)
    }
  })
}
编写指令
program
  .version(version)
  .command("upload")
  .description('上传Source Map文件')
  .option("--apikey [apikey]", "设置apikey")
  .option("--accessKeyId [accessKeyId]", "设置oss accessKeyId")
  .option("--accessKeySecret [accessKeySecret]", "设置oss accessKeySecret")
  .action(upload)

完成以上步奏以后即可调用全局cli 进行上传oss

sydebug-cli upload --apikey=xxx --accessKeyId=xxx --accessKeySecret=xxx

执行此命令就会查找当前 mapPath 目录下的所有map文件上传到指定oss

clear 清除oss文件


有上传自然就有清除,可以在每次上传之前清除指定目录下的oss文件

oss sdk删除示例

image.png

此示例有一些问题需要改造

  1. client.list 默认最多返回100个数据
  2. 循环删除文件调用次数太快会导致node崩溃
问题1 修改
const list = await client.list({
    prefix: prefix,
    "max-keys": 10000
})

此时文件返回过多会引出问题2

问题2 修改
// async 模块提供了不同的功能来在 nodejs 应用程序中使用异步 JavaScript。
// 该方法返回一个队列,该队列进一步用于进程的并发处理,即在一个时间/瞬间对项目进行多个处理
const queue = require('async/queue')

async function deletePrefix(prefix) {
  const list = await client.list({
    prefix: prefix,
    "max-keys": 10000
  })
  list.objects = list.objects || []
  // 并发删除
  const q = queue((task, executed) => {
    console.log("当前忙处理任务 " + task);
    handleDel(task, () => {
      executed(null, {task, tasksRemaining: q.length()});
    })
  }, 10);
  list.objects.forEach((v) => {
    q.push(v.name, (err, result) => {
      console.log("任务处理完成 " + result.task);
      console.log("剩余任务数 " + result.tasksRemaining);
    });
  });
  q.drain = function() {
    console.log("所有任务处理完成");
  }
}

clear主程序
function clear(options) {
  if (!validateOptions(options)) return
  initOss(options)
  try {
    const name = path.join(fullPath, options.apikey).slice(1) + '/'
    // 删除sourceMap文件夹
    deletePrefix(name).then((res) => {
      console.log('---- 文件夹删除成功 ----')
    })
  } catch (error) {
    console.log('---- 文件夹删除失败 ----')
  }
}
编写指令
program
  .version(version)
  .command("clear")
  .description('删除指定目录文件')
  .option("--apikey [apikey]", "设置apikey")
  .option("--accessKeyId [accessKeyId]", "设置oss accessKeyId")
  .option("--accessKeySecret [accessKeySecret]", "设置oss accessKeySecret")
  .action(clear)

完成以上步奏以后即可调用全局cli 删除oss指定文件夹文件

sydebug-cli clear --apikey=xxx --accessKeyId=xxx --accessKeySecret=xxx

以上内容有需纠正之处欢迎大佬指正

npm: sydebug-cli