npm.io
0.1.8 • Published 2h agoCLI

@vchar/nos-cli

Licence
Version
0.1.8
Deps
3
Size
61 kB
Vulns
0
Weekly
0

lnos-cli

NetEase NOS (网易对象存储) 命令行工具。Node.js 18+,支持单文件 PUT / 大文件分片上传(断点续传)、同桶重命名、图片/音视频信息查询,以及 CDN 缓存刷新。提供 AK/SK 直签后端 token 两种鉴权。


安装

# 官方 npm(npmjs.com)
npm i -g @vchar/nos-cli

# 或从源码
git clone <repo> && cd nos-cli && npm install && npm run build
npm i -g .          # 或 npm link

安装后即可使用 lnos-cli 命令(bin 名为 lnos-cli)。

前置要求

  • Node.js >= 18
  • 鉴权二选一:
    • AK/SK 模式:NOS AccessKey / SecretKey(网易云控制台获取)
    • token 模式:后端 token 签发端点(默认指向 https://ai-service-lofter.hz.netease.com/api/mcp/nos/token,由 virtual-service-mcp 提供)

配置管理

配置持久化保存在 ~/.nos-cli/config.json,文件权限 0600(仅当前用户可读写)。

# —— AK/SK 直签模式 ——
lnos-cli config set accessKey <你的 AccessKey>
lnos-cli config set secretKey <你的 SecretKey>
lnos-cli config set bucket <bucketName>          # AK/SK 模式必填

# —— 后端 token 模式(凭证由后端签发,无需本地 AK/SK)——
# 配了 tokenEndpoint 即启用 token 模式;默认指向公网 MCP 服务,可不配
lnos-cli config set tokenEndpoint <url>          # 可选,默认 https://ai-service-lofter.hz.netease.com/api/mcp/nos/token(可覆盖)
lnos-cli config set tokenHeaders '<json>'         # 可选,鉴权头(公网默认无需;内网/正式环境需 Bearer skt- 时配)

# —— 通用 ——
lnos-cli config set net <auto|inner|outer>      # 网络模式,默认 auto
lnos-cli config set partSize <16>                 # 分片大小 MB,默认 16
lnos-cli config set encrypt <true|false>          # secretKey AES-256-GCM 加密存储

lnos-cli config get <key>                          # 查看单项
lnos-cli config list                                # 列出全部(secretKey 显示为 ****)

说明:

  • 鉴权优先级:配了 tokenEndpoint → token 模式(凭证后端签发、x-nos-token 头、object key 由后端生成 UUID);否则 AK/SK 直签(本地 HMAC-SHA256 签名、object key 本地命名)。token 模式下 bucket 可不配(后端返回;配置则为 -b 提示后端签进 token)。
  • net 取值:
    • auto(默认)— 自动探测:TCP 连接 nos-jd.service.163.org:80(800ms 超时),可达则用内网,否则用外网。
    • inner — 强制内网端点 nos-jd.service.163.org(http)。
    • outer — 强制外网端点 nos-jd.163yun.com(https)。内网 443 暴露的是外网证书,故内网走 http 避免 TLS 域名校验失败。
  • encrypt true 启用后,secretKey 由机器指纹派生密钥加密,写入磁盘时为密文,读取时自动解密。注意这是防窥屏级别的保护,不是强加密。
  • config list 显示时 secretKey 固定输出 ****,不会泄漏明文。

上传文件

基本用法
lnos-cli upload <file...>

支持同时上传一个或多个文件。上传成功后会打印三个 URL(均不含 URL 编码,可直接访问):

✅ 上传成功:photo.jpg
   内网: https://my-bucket.nos-jd.service.163.org/20260711234200000_a1b2c3/photo.jpg
   外网: https://my-bucket.nos-jd.163yun.com/20260711234200000_a1b2c3/photo.jpg
   CDN : https://my-bucket.nosdn.127.net/20260711234200000_a1b2c3/photo.jpg
选项
选项 简写 说明
--bucket <bucket> -b 覆盖默认 Bucket
--concurrency <n> -c 上传并发数(默认 = min(CPU 核数, 6);不设上限)
--net <mode> 网络模式:auto / inner / outer
--key <objectName> 自定义对象名(仅单个文件时可用)
--part-size <mb> 分片阈值和分片大小,单位 MB
--abort 中止指定文件的分片上传并清理状态(需同时使用 --key
分片上传与断点续传
  • 文件大小 ≤ 分片大小时,走单次 PUT 上传。
  • 文件大小 > 分片大小时,自动走 Multipart Upload(分片上传)。
  • 上传过程中的状态(uploadId、已上传分片的 ETag)实时写入 ~/.nos-cli/state/ 目录。
  • 中断后重跑同一文件:自动检测已有的分片状态,跳过已上传分片,只上传缺失部分(断点续传)。
  • 文件 mtime 发生变化(被修改后重试),旧状态自动作废,从头重新上传。
  • 上传成功后,状态文件自动清理。
--key 和 --abort 约束
  • --key 仅能用于单个文件,否则会报错。
  • --abort 需要同时指定 --key(即之前上传时使用的对象名),因为随机生成的对象名无法回推。示例:
lnos-cli upload large-file.iso --key custom/object-name --abort
真实示例(端到端)
# 1. 配置
lnos-cli config set accessKey "your-ak"
lnos-cli config set secretKey "your-sk"
lnos-cli config set bucket "my-bucket"
lnos-cli config set net "auto"

# 2. 上传
lnos-cli upload ./screenshot.png ./report.pdf

# 3. 输出示例
✅ 上传成功:screenshot.png
   内网: https://my-bucket.nos-jd.service.163.org/20260711234200000_x1y2z3/screenshot.png
   外网: https://my-bucket.nos-jd.163yun.com/20260711234200000_x1y2z3/screenshot.png
   CDN : https://my-bucket.nosdn.127.net/20260711234200000_x1y2z3/screenshot.png
✅ 上传成功:report.pdf
   内网: https://my-bucket.nos-jd.service.163.org/20260711234200000_x1y2z3/report.pdf
   外网: https://my-bucket.nos-jd.163yun.com/20260711234200000_x1y2z3/report.pdf
   CDN : https://my-bucket.nosdn.127.net/20260711234200000_x1y2z3/report.pdf

对象管理

除上传外,还支持重命名和查询对象信息。这些命令的目标对象都可用两种形式指定:

  • objectKey(如 dir/photo.png)— 使用配置或 -b 指定的 Bucket;
  • 完整 NOS URL(内网 / 外网 / CDN 任一)— 自动解析出 Bucket 和 objectKey。

所有命令均支持 -b/--bucket--net 选项。

重命名 / 移动对象
lnos-cli rename <源> <新完整key>
  • 基于 NOS 的 PUT Object Move(改元数据,不拷贝物理文件)。
  • 仅支持同一 Bucket 内(NOS 不支持跨桶 move)。
  • 新完整key 是完整的目标对象名(不保留源目录前缀)。
  • 成功后打印新对象的三个 URL。
lnos-cli rename clitest/pic.png clitest/renamed.png
# ✅ 重命名成功:clitest/pic.png → clitest/renamed.png
#    内网/外网/CDN: ...
查询对象信息
lnos-cli imageinfo <target>   # 图片基本信息:Width / Height / Size / Type / Orientation
lnos-cli exif <target>        # 图片 EXIF 信息(无 EXIF 时返回空对象)
lnos-cli vinfo <target>       # 音视频信息:时长 / 码率 / 编解码 / 分辨率等

以 JSON 打印结果。示例:

lnos-cli imageinfo clitest/pic.png
# {
#   "Width": "1210",
#   "Height": "1726",
#   "Size": "1758637",
#   "Type": "PNG",
#   "Orientation": ""
# }

lnos-cli vinfo clitest/test.mp4
# { "GetVideoInfo": { "VideoInfo": { "Duration": 14149, "Width": 846, "Height": 1920, ... } } }

注:imageinfo / exif 的 NOS 响应为 XML(工具会解析为扁平对象),vinfo 响应为 JSON,工具自动识别两种格式。


CDN 缓存刷新

提交 CDN 缓存刷新任务(文件 / 目录 / 混合)。

lnos-cli cdn refresh <url...> [--type <file|dir|mix>] [--domain-id <id>] [--isp <isp>]
  • 走公网 MCP 服务 https://ai-service-lofter.hz.netease.com/api/mcp/cdn/refresh(virtual-service-mcp 在内网代调上游刷新接口,无需 VPN/内网,token 由后端内部复用,lnos-cli 不单独取 token)。
  • --typefile(文件,默认) / dir(目录) / mix(混合,mix 时目录 URL 需以 / 结尾)。
  • URL 最多 100 条,需以 http(s):// 开头。
  • --domain-id / --isp 可省略;省略时刷新所有匹配域名。
# 刷新单个文件
lnos-cli cdn refresh "https://my-bucket.nosdn.127.net/abc.jpg"

# 刷新目录
lnos-cli cdn refresh --type dir "https://example.com/dir/"

# 混合(文件 + 目录)
lnos-cli cdn refresh --type mix "https://example.com/a.jpg" "https://example.com/dir/"

# 指定域名 ID / 厂家
lnos-cli cdn refresh --domain-id 123123 --isp cmcc "https://example.com/a.jpg"

成功输出:

✅ CDN 刷新任务已提交:fusion域名my-bucket.nosdn.127.net新增文件刷新
   任务 ID: 7870403197

失败时逐条列出上游返回的 {url, detail, suggest}


对象命名规则

不指定 --key 时,对象名自动生成:

{timestamp}_{rand6}/{baseName}.{ext}
  • timestamp — 上传时刻的北京时间,格式 yyyyMMddHHmmssSSS(17 位数字)。
  • rand6 — 6 位随机字母数字(a-z, 0-9)。
  • baseName — 原文件名去除扩展名,中文转无调拼音(通过 pinyin-pro 库),非法字符(非字母数字和下划线)被移除。
  • .ext — 原文件扩展名保留。

示例:我的照片 (v2).jpg20260711234200000_a1b2c3/wodezhaopianv2.jpg

注:token 模式下 object key 由后端生成(UUID + 原扩展名),不走上述本地命名规则。


Content-Type 适配

上传时按 object 扩展名自动设置 Content-Type(参考后端 FileTypeEnum + RFC/IANA 注册类型补齐)。

  • NOS 服务端有 Content-Type 白名单:白名单外的类型(即使 RFC 注册)会被 NOS 强制回落 application/octet-stream。故 text/markdownapplication/yamlapplication/tomlapplication/sql被拒的文本类型这里回落 text/plain(RFC 2046,NOS 接受),使其在浏览器能在线渲染而非强制下载;font/*(二进制)保留 RFC 真值。
  • 无扩展名 → 不设 Content-Type(交给 NOS 嗅探);未知扩展名 → application/octet-stream

NOS 签名说明

本工具不依赖 NOS 官方 SDK,签名逻辑完全自实现:

  • 使用 HMAC-SHA256 对规范字符串进行签名,输出 Base64。
  • 规范字符串格式为 METHOD\nContent-MD5\nContent-Type\nDate\n[x-nos-* 头]\nResource,与 NOS 文档一致。其中 x-nos-* 头(如重命名用的 x-nos-move-source)按头名排序后参与签名。
  • Date 头使用上海时区(Asia/Shanghai)的 RFC 822 格式,与官方 Java SDK 对齐。
  • Resource(签名字符串中的对象路径)中的对象 key 做了 URL 编码,保证特殊字符不破坏签名。
  • 部分子资源参与签名(如分片上传的 ?uploads?uploadId),而信息查询子资源(?imageInfo?exif?vinfo参与签名——这些差异均已通过真机验证确认。

签名实现已通过官方 NOS Java SDK 生成的签名向量做字节级交叉验证:向量固化在 test/fixtures/nos-sign-vectors.json,TypeScript 实现在同一输入上输出与官方 SDK 完全一致。所有对象操作(上传 / 删除 / 重命名 / 信息查询)均已针对真实 NOS 服务端到端验证通过。


开发 / 迭代

npm test              # 运行单元测试(vitest)
npm run typecheck     # TypeScript 类型检查
npm run build         # 构建,产出 dist/cli.js

项目技术栈:TypeScript + commander(CLI 框架)+ undici(HTTP 客户端)+ vitest(测试框架)。

当前代码覆盖 55 个单元测试,涵盖签名、命名、配置、断点续传、并发池、端点探测、分片上传、URL 解析、对象操作、token 模式、mimetype、CDN 刷新等核心逻辑。


阶段二 (Go)

计划将相同的上传协议逻辑移植为 Go 单二进制工具,消除 Node.js 运行时依赖,实现更小的分发体积和更简单的部署。签名、续传、分片等已验证的协议逻辑将直接复用。