Files
dashboard/backend/src/services/measurementStorage.js
T
2026-06-10 15:37:20 +08:00

115 lines
3.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 频响文件 S3 存储服务
* 本地开发环境:使用 AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY
* 正式环境:通过 IAM 角色访问 S3(无需配置密钥)
*/
const { S3Client, PutObjectCommand, GetObjectCommand } = require('@aws-sdk/client-s3');
const logger = require('../config/logger');
const AWS_REGION = process.env.AWS_REGION || 'eu-central-1';
const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID || '';
const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY || '';
const S3_BUCKET = process.env.AWS_S3_MEASUREMENT_BUCKET || process.env.AWS_S3_OTA_BUCKET || 'luxsin-app-bucket';
/**
* 获取 S3 客户端
* 有显式凭证则用凭证,否则走 IAM 角色
*/
function getS3Client() {
const options = { region: AWS_REGION };
if (AWS_ACCESS_KEY_ID && AWS_SECRET_ACCESS_KEY) {
options.credentials = {
accessKeyId: AWS_ACCESS_KEY_ID,
secretAccessKey: AWS_SECRET_ACCESS_KEY,
};
}
return new S3Client(options);
}
/**
* 获取品牌首字符(字母则转大写)
*/
function getBrandFirstChar(brandName) {
if (!brandName || !brandName.length) return '_';
const first = brandName[0];
return /[a-zA-Z]/.test(first) ? first.toUpperCase() : first;
}
/**
* 构建 S3 Key
* 路径:autoeq/measurements/{source}/data/{form}/{brandFirstChar}/{brand model}.csv
* @param {object} params
* @param {string} params.source - 来源,默认 Eafonyoung
* @param {string} params.form - 佩戴方式 (in-ear / over-ear / earbud)
* @param {string} params.brandName - 品牌名称
* @param {string} params.modelName - 型号名称
* @returns {string} S3 Key
*/
function buildMeasurementKey({ source, form, brandName, modelName }) {
const src = source || 'Eafonyoung';
const brandChar = getBrandFirstChar(brandName);
const fileName = `${brandName} ${modelName}.csv`;
return `autoeq/measurements/${src}/data/${form}/${brandChar}/${fileName}`;
}
/**
* 上传频响文件到 S3
* @param {Buffer} buffer - 文件内容(已转换为 CSV)
* @param {object} params - 路径参数
* @returns {Promise<string>} S3 Key
*/
async function uploadMeasurementToS3(buffer, params) {
const key = buildMeasurementKey(params);
const client = getS3Client();
try {
await client.send(
new PutObjectCommand({
Bucket: S3_BUCKET,
Key: key,
Body: buffer,
ContentType: 'text/csv',
})
);
logger.info(`Measurement uploaded to s3://${S3_BUCKET}/${key}`);
return key;
} catch (e) {
logger.error(`S3 measurement upload failed: ${e.message}`);
throw new Error(`S3 上传失败:${e.message}`);
}
}
/**
* 从 S3 读取频响 CSV 文件
* @param {object} params - 路径参数(同 buildMeasurementKey
* @returns {Promise<{ key: string, content: string }>}
*/
async function getMeasurementFromS3(params) {
const key = buildMeasurementKey(params);
const client = getS3Client();
try {
const response = await client.send(
new GetObjectCommand({
Bucket: S3_BUCKET,
Key: key,
})
);
const content = await response.Body.transformToString('utf-8');
logger.info(`Measurement read from s3://${S3_BUCKET}/${key}`);
return { key, content };
} catch (e) {
if (e.name === 'NoSuchKey' || e.$metadata?.httpStatusCode === 404) {
throw new Error('S3 上未找到该型号的频响文件');
}
logger.error(`S3 measurement read failed: ${e.message}`);
throw new Error(`S3 读取失败:${e.message}`);
}
}
module.exports = {
uploadMeasurementToS3,
getMeasurementFromS3,
buildMeasurementKey,
};