优化体验
This commit is contained in:
@@ -11,7 +11,7 @@ const logger = require('../config/logger');
|
||||
const { ApiResponse } = require('../utils/response');
|
||||
const { authMiddleware } = require('../middleware/auth');
|
||||
const { fetchAndValidateCurve } = require('../services/curveClient');
|
||||
const { uploadMeasurementToS3, getMeasurementFromS3 } = require('../services/measurementStorage');
|
||||
const { uploadMeasurementToS3, getMeasurementFromS3, moveMeasurementOnS3 } = require('../services/measurementStorage');
|
||||
const { getEqCacheKeys, getEqCacheField } = require('../services/eqCacheStorage');
|
||||
const { fetchFromSquigLink, downloadTxtFile } = require('../services/squiglink');
|
||||
|
||||
@@ -499,6 +499,15 @@ router.put('/api/models/:model_id', upload.single('measurement_file'), async (re
|
||||
logger.info(`Measurement file uploaded to S3: ${s3Key}`);
|
||||
}
|
||||
|
||||
// 若未上传新文件,且路径相关字段发生变更,则迁移 S3 上的旧 CSV
|
||||
const hasNewFile = squiglink_csv || (req.file && req.file.originalname);
|
||||
if (!hasNewFile) {
|
||||
await moveMeasurementOnS3(
|
||||
{ source: dbModel.source, form: dbModel.form, brandName: dbModel.brand_name, modelName: dbModel.name },
|
||||
{ source: effSource, form: effForm, brandName: newBrandName, modelName: newName }
|
||||
);
|
||||
}
|
||||
|
||||
if (brand_name !== undefined && brand_name !== 'null') dbModel.brand_name = brand_name;
|
||||
if (name !== undefined && name !== 'null') dbModel.name = name;
|
||||
if (form !== undefined && form !== 'null') dbModel.form = form;
|
||||
|
||||
+13
-10
@@ -113,19 +113,21 @@ router.get('/api/ota/', async (req, res) => {
|
||||
const verName = req.query.verName;
|
||||
const model = req.query.model;
|
||||
const status = req.query.status !== undefined ? parseInt(req.query.status, 10) : null;
|
||||
const beta = req.query.beta !== undefined ? parseInt(req.query.beta, 10) : null;
|
||||
|
||||
const where = {};
|
||||
if (verCode !== null && !isNaN(verCode)) where.verCode = verCode;
|
||||
if (verName) where.verName = { [Op.like]: `%${verName}%` };
|
||||
if (model) where.model = { [Op.like]: `%${model}%` };
|
||||
if (status !== null && !isNaN(status)) where.status = status;
|
||||
if (beta !== null && !isNaN(beta)) where.beta = beta;
|
||||
|
||||
const total = await Ota.count({ where });
|
||||
const rows = await Ota.findAll({
|
||||
where,
|
||||
offset: skip,
|
||||
limit,
|
||||
order: [['verCode', 'DESC']],
|
||||
order: [['id', 'DESC']],
|
||||
});
|
||||
|
||||
logger.info(`Found ${rows.length} OTA records, total=${total}`);
|
||||
@@ -171,13 +173,13 @@ router.post('/api/ota/', async (req, res) => {
|
||||
const data = parsed.data;
|
||||
logger.info(`Creating OTA: verCode=${data.verCode}, verName=${data.verName}, model=${data.model}`);
|
||||
|
||||
// 检查版本号是否已存在
|
||||
// 检查版本号是否已存在(同一 verCode + model + beta 视为唯一)
|
||||
const existing = await Ota.findOne({
|
||||
where: { verCode: data.verCode, model: data.model },
|
||||
where: { verCode: data.verCode, model: data.model, beta: data.beta ?? 0 },
|
||||
});
|
||||
if (existing) {
|
||||
logger.warn(`OTA version already exists: verCode=${data.verCode}, model=${data.model}`);
|
||||
return res.json(ApiResponse.error('该版本已存在'));
|
||||
logger.warn(`OTA version already exists: verCode=${data.verCode}, model=${data.model}, beta=${data.beta ?? 0}`);
|
||||
return res.json(ApiResponse.error('该版本已存在(相同版本+灰度状态)'));
|
||||
}
|
||||
|
||||
// 处理日期字段
|
||||
@@ -213,17 +215,18 @@ router.put('/api/ota/:ota_id', async (req, res) => {
|
||||
return res.json(ApiResponse.noData('empty'));
|
||||
}
|
||||
|
||||
// 如果更新版本号,检查是否冲突
|
||||
// 如果更新版本号或灰度状态,检查是否冲突
|
||||
const newVerCode = data.verCode !== undefined && data.verCode !== null ? data.verCode : dbOta.verCode;
|
||||
const newModel = data.model !== undefined && data.model !== null ? data.model : dbOta.model;
|
||||
const newBeta = data.beta !== undefined && data.beta !== null ? data.beta : dbOta.beta;
|
||||
|
||||
if (newVerCode !== dbOta.verCode || newModel !== dbOta.model) {
|
||||
if (newVerCode !== dbOta.verCode || newModel !== dbOta.model || newBeta !== dbOta.beta) {
|
||||
const existing = await Ota.findOne({
|
||||
where: { verCode: newVerCode, model: newModel },
|
||||
where: { verCode: newVerCode, model: newModel, beta: newBeta },
|
||||
});
|
||||
if (existing) {
|
||||
logger.warn(`OTA version already exists: verCode=${newVerCode}, model=${newModel}`);
|
||||
return res.json(ApiResponse.error('该版本已存在'));
|
||||
logger.warn(`OTA version already exists: verCode=${newVerCode}, model=${newModel}, beta=${newBeta}`);
|
||||
return res.json(ApiResponse.error('该版本已存在(相同版本+灰度状态)'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* 本地开发环境:使用 AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY
|
||||
* 正式环境:通过 IAM 角色访问 S3(无需配置密钥)
|
||||
*/
|
||||
const { S3Client, PutObjectCommand, GetObjectCommand } = require('@aws-sdk/client-s3');
|
||||
const { S3Client, PutObjectCommand, GetObjectCommand, CopyObjectCommand, DeleteObjectCommand } = require('@aws-sdk/client-s3');
|
||||
const logger = require('../config/logger');
|
||||
|
||||
const AWS_REGION = process.env.AWS_REGION || 'eu-central-1';
|
||||
@@ -107,8 +107,58 @@ async function getMeasurementFromS3(params) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 S3 上迁移频响文件(复制到新路径后删除旧文件)
|
||||
* @param {object} oldParams - 旧路径参数 { source, form, brandName, modelName }
|
||||
* @param {object} newParams - 新路径参数
|
||||
* @returns {Promise<string|null>} 新 S3 Key,若旧文件不存在则返回 null
|
||||
*/
|
||||
async function moveMeasurementOnS3(oldParams, newParams) {
|
||||
const oldKey = buildMeasurementKey(oldParams);
|
||||
const newKey = buildMeasurementKey(newParams);
|
||||
|
||||
if (oldKey === newKey) {
|
||||
logger.info(`S3 move skipped: path unchanged (${oldKey})`);
|
||||
return newKey;
|
||||
}
|
||||
|
||||
const client = getS3Client();
|
||||
|
||||
try {
|
||||
// 服务端复制(无需下载再上传)
|
||||
await client.send(
|
||||
new CopyObjectCommand({
|
||||
Bucket: S3_BUCKET,
|
||||
CopySource: `${S3_BUCKET}/${encodeURIComponent(oldKey).replace(/%2F/g, '/')}`,
|
||||
Key: newKey,
|
||||
ContentType: 'text/csv',
|
||||
})
|
||||
);
|
||||
logger.info(`S3 copied: ${oldKey} -> ${newKey}`);
|
||||
|
||||
// 复制成功后删除旧文件
|
||||
await client.send(
|
||||
new DeleteObjectCommand({
|
||||
Bucket: S3_BUCKET,
|
||||
Key: oldKey,
|
||||
})
|
||||
);
|
||||
logger.info(`S3 deleted old: ${oldKey}`);
|
||||
|
||||
return newKey;
|
||||
} catch (e) {
|
||||
if (e.name === 'NoSuchKey' || e.$metadata?.httpStatusCode === 404) {
|
||||
logger.info(`S3 move skipped: old key not found (${oldKey})`);
|
||||
return null;
|
||||
}
|
||||
logger.error(`S3 measurement move failed: ${e.message}`);
|
||||
throw new Error(`S3 迁移失败:${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
uploadMeasurementToS3,
|
||||
getMeasurementFromS3,
|
||||
moveMeasurementOnS3,
|
||||
buildMeasurementKey,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user