| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104 |
- #!/usr/bin/env node
- // 技能直连 OSS 工具:列举桶内对象与目录结构(ADR-004 凭据边界,零硬编码)
- // 用法:
- // node oss.mjs # 桶顶层:按一级目录分组统计
- // node oss.mjs recordings/ # 列出该前缀全部对象(按目录分组)
- // node oss.mjs recordings/ --json # JSON 输出 {domain,prefix,objects}
- // node oss.mjs --find-env # 打印实际采用的 .env 路径
- // node oss.mjs reports/ --env D:/1/device-management-backend/server/.env # 显式指定 .env
- // 凭据回退链: --env > SMARTBADGE_ENV_FILE > 自动发现(见 lib.mjs) > 进程环境已有 OSS_*
- // 注意:需要项目 server/services/oss-client.js 所在仓库(自动发现通常命中 D:\1\device-management-backend)
- import fs from 'fs';
- import path from 'path';
- import { pathToFileURL } from 'url';
- import { findEnvFile, loadEnv, fail } from './lib.mjs';
- const args = process.argv.slice(2);
- const isFlag = (a) => a.startsWith('--');
- const envIdx = args.indexOf('--env');
- const envPathFlag = envIdx >= 0 ? args[envIdx + 1] : undefined;
- // 非 flag 且非 flag 值 的参数才视为 prefix
- const prefix = args.filter((a, i) => !isFlag(a) && !(envIdx >= 0 && (i === envIdx + 1)))[0] ?? '';
- const json = args.includes('--json');
- const findEnv = args.includes('--find-env');
- function groupKey(name, pfx) {
- const rest = name.slice(pfx.length);
- const seg = rest.split('/')[0];
- return rest.includes('/') ? `${seg}/` : seg;
- }
- async function main() {
- const env = findEnvFile({ envPath: envPathFlag });
- if (findEnv) {
- if (env.hit) {
- console.log(`生效 .env: ${env.hit}`);
- return;
- }
- console.error('未找到 .env,候选路径:');
- for (const c of env.candidates) console.error(` ${c}`);
- process.exit(2);
- }
- if (!env.hit) {
- console.error('未找到 OSS 凭据文件(探测序列如下),三选一:');
- for (const c of env.candidates) console.error(` ${c}`);
- console.error(' → 1. node oss.mjs --env <server/.env 路径>');
- console.error(' → 2. 设置 SMARTBADGE_ENV_FILE 指向 server/.env');
- console.error(' → 3. 直接导出 OSS_* 环境变量(与 server/.env 同键名)');
- process.exit(2);
- }
- loadEnv(env.hit);
- const serverDir = path.dirname(env.hit);
- const missing = ['OSS_ACCESS_KEY_ID', 'OSS_ACCESS_KEY_SECRET', 'OSS_BUCKET'].filter((k) => !process.env[k]);
- if (missing.length) fail(`OSS 凭据不全,缺: ${missing.join(', ')}。请检查 --env 指向的 .env 或环境变量`, 4);
- const ossClientPath = path.join(serverDir, 'services', 'oss-client.js');
- if (!fs.existsSync(ossClientPath)) {
- fail(`未找到 oss-client.js: ${ossClientPath}。自动发现命中路径可能不含项目 server/;请用 --env 显式指向 <项目>/server/.env`, 3);
- }
- const { listObjects, publicDomain } = await import(pathToFileURL(ossClientPath));
- const objects = await listObjects(prefix);
- const domain = publicDomain();
- if (json) {
- console.log(JSON.stringify({
- domain, prefix,
- objects: objects.map((o) => ({ name: o.name, size: o.size, lastModified: o.lastModified })),
- }));
- return;
- }
- if (!prefix) {
- const groups = new Map();
- for (const o of objects) {
- const seg = groupKey(o.name, '');
- if (!seg) continue;
- if (!groups.has(seg)) groups.set(seg, { count: 0, bytes: 0 });
- groups.get(seg).count++;
- groups.get(seg).bytes += o.size;
- }
- console.log(`OSS 桶顶层目录(共 ${objects.length} 个对象): ${domain}`);
- for (const [seg, g] of [...groups.entries()].sort()) {
- console.log(` ${seg.padEnd(16)} ${g.count} 个文件, ${(g.bytes / 1024 / 1024).toFixed(2)} MB`);
- }
- console.log('使用: node oss.mjs <prefix> 查看子目录(如 recordings/ cleansed/ transcripts/ reports/)');
- return;
- }
- if (objects.length === 0) {
- console.log(`[${prefix}] 无对象`);
- return;
- }
- console.log(`[${prefix}] 共 ${objects.length} 个对象:`);
- for (const o of objects) {
- console.log(` ${o.name} (${(o.size / 1024 / 1024).toFixed(2)} MB, ${(o.lastModified || '').slice(0, 10)})`);
- }
- const bytes = objects.reduce((s, o) => s + o.size, 0);
- console.log(`合计 ${(bytes / 1024 / 1024).toFixed(2)} MB`);
- }
- main().catch((err) => {
- console.error(err.message);
- process.exit(1);
- });
|