From 92df6e13a0e1d77a0189b2c78f02ccb6d62f2af6 Mon Sep 17 00:00:00 2001 From: LaoShui <79132480+laoshuikaixue@users.noreply.github.com> Date: Wed, 18 Feb 2026 20:19:46 +0800 Subject: [PATCH] =?UTF-8?q?refactor(cloud):=20=E9=87=8D=E6=9E=84=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91=E5=B9=B6=E6=8F=90?= =?UTF-8?q?=E5=8F=96=E8=BE=85=E5=8A=A9=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将文件大小、MD5计算等逻辑提取到 fileHelper 工具模块 - 使用统一的文件扩展名和文件名处理函数 - 简化临时文件处理和清理逻辑 - 统一文件上传数据获取方式 - 移除重复的文件操作代码并提高可维护性 --- module/cloud.js | 106 ++++++++++++----------------------------- module/voice_upload.js | 20 +++----- plugins/songUpload.js | 24 +++------- plugins/upload.js | 16 +------ util/fileHelper.js | 90 ++++++++++++++++++++++++++++++++++ 5 files changed, 135 insertions(+), 121 deletions(-) create mode 100644 util/fileHelper.js diff --git a/module/cloud.js b/module/cloud.js index 6bdb579..cf33fa2 100644 --- a/module/cloud.js +++ b/module/cloud.js @@ -1,23 +1,24 @@ const uploadPlugin = require('../plugins/songUpload') -const crypto = require('crypto') -const fs = require('fs') const createOption = require('../util/option.js') const logger = require('../util/logger.js') +const { + isTempFile, + getFileSize, + getFileMd5, + cleanupTempFile, + getFileExtension, + sanitizeFilename, +} = require('../util/fileHelper') + let mm module.exports = async (query, request) => { mm = require('music-metadata') - let ext = 'mp3' - if (query.songFile.name.includes('.')) { - ext = query.songFile.name.split('.').pop() - } - query.songFile.name = Buffer.from(query.songFile.name, 'latin1').toString( - 'utf-8', - ) - const filename = query.songFile.name - .replace('.' + ext, '') - .replace(/\s/g, '') - .replace(/\./g, '_') + + query.songFile.name = Buffer.from(query.songFile.name, 'latin1').toString('utf-8') + const ext = getFileExtension(query.songFile.name) + const filename = sanitizeFilename(query.songFile.name) const bitrate = 999000 + if (!query.songFile) { return Promise.reject({ status: 500, @@ -28,55 +29,14 @@ module.exports = async (query, request) => { }) } - const useTempFile = !!query.songFile.tempFilePath - let fileSize = query.songFile.size - let fileMd5 = query.songFile.md5 + const useTemp = isTempFile(query.songFile) + let fileSize = await getFileSize(query.songFile) + let fileMd5 = await getFileMd5(query.songFile) - const cleanupTempFile = async () => { - if (useTempFile) { - try { - await fs.promises.unlink(query.songFile.tempFilePath) - } catch (e) { - logger.info('临时文件清理失败:', e.message) - } - } - } + query.songFile.md5 = fileMd5 + query.songFile.size = fileSize try { - if (useTempFile) { - try { - const stats = await fs.promises.stat(query.songFile.tempFilePath) - fileSize = stats.size - } catch (e) { - logger.error('获取临时文件状态失败:', e.message) - return Promise.reject({ - status: 500, - body: { - code: 500, - msg: '临时文件访问失败', - detail: e.message, - }, - }) - } - if (!fileMd5) { - fileMd5 = await new Promise((resolve, reject) => { - const hash = crypto.createHash('md5') - const stream = fs.createReadStream(query.songFile.tempFilePath) - stream.on('data', (chunk) => hash.update(chunk)) - stream.on('end', () => resolve(hash.digest('hex'))) - stream.on('error', reject) - }) - } - } else { - if (!fileMd5) { - fileMd5 = crypto.createHash('md5').update(query.songFile.data).digest('hex') - } - fileSize = query.songFile.data.byteLength - } - - query.songFile.md5 = fileMd5 - query.songFile.size = fileSize - const res = await request( `/api/cloud/upload/check`, { @@ -89,33 +49,26 @@ module.exports = async (query, request) => { }, createOption(query), ) + let artist = '' let album = '' let songName = '' + try { let metadata - if (useTempFile) { + if (useTemp) { metadata = await mm.parseFile(query.songFile.tempFilePath) } else { - metadata = await mm.parseBuffer( - query.songFile.data, - query.songFile.mimetype, - ) + metadata = await mm.parseBuffer(query.songFile.data, query.songFile.mimetype) } const info = metadata.common - - if (info.title) { - songName = info.title - } - if (info.album) { - album = info.album - } - if (info.artist) { - artist = info.artist - } + if (info.title) songName = info.title + if (info.album) album = info.album + if (info.artist) artist = info.artist } catch (error) { logger.info('元数据解析错误:', error.message) } + const tokenRes = await request( `/api/nos/token/alloc`, { @@ -189,6 +142,7 @@ module.exports = async (query, request) => { }, createOption(query), ) + return { status: 200, body: { @@ -198,6 +152,8 @@ module.exports = async (query, request) => { cookie: res.cookie, } } finally { - await cleanupTempFile() + if (useTemp) { + await cleanupTempFile(query.songFile.tempFilePath) + } } } diff --git a/module/voice_upload.js b/module/voice_upload.js index 5b993b8..79c9b8a 100644 --- a/module/voice_upload.js +++ b/module/voice_upload.js @@ -3,7 +3,10 @@ const fs = require('fs') var xml2js = require('xml2js') const createOption = require('../util/option.js') +const { getFileExtension, readFileChunk } = require('../util/fileHelper') + var parser = new xml2js.Parser() + function createDupkey() { var s = [] var hexDigits = '0123456789abcdef' @@ -15,11 +18,9 @@ function createDupkey() { s[8] = s[13] = s[18] = s[23] = '-' return s.join('') } + module.exports = async (query, request) => { - let ext = 'mp3' - if (query.songFile.name.indexOf('flac') > -1) { - ext = 'flac' - } + const ext = getFileExtension(query.songFile.name) const filename = query.songName || query.songFile.name @@ -66,15 +67,10 @@ module.exports = async (query, request) => { const useTempFile = !!query.songFile.tempFilePath let fileSize = query.songFile.size - let fileData if (useTempFile) { const stats = await fs.promises.stat(query.songFile.tempFilePath) fileSize = stats.size - fileData = query.songFile.tempFilePath - } else { - fileSize = query.songFile.data.length - fileData = query.songFile.data } const blockSize = 10 * 1024 * 1024 @@ -86,11 +82,7 @@ module.exports = async (query, request) => { while (offset < fileSize) { let chunk if (useTempFile) { - const fd = await fs.promises.open(query.songFile.tempFilePath, 'r') - const buffer = Buffer.alloc(Math.min(blockSize, fileSize - offset)) - await fd.read(buffer, 0, buffer.length, offset) - await fd.close() - chunk = buffer + chunk = await readFileChunk(query.songFile.tempFilePath, offset, Math.min(blockSize, fileSize - offset)) } else { chunk = query.songFile.data.slice(offset, Math.min(offset + blockSize, fileSize)) } diff --git a/plugins/songUpload.js b/plugins/songUpload.js index 225b236..6bc7e6b 100644 --- a/plugins/songUpload.js +++ b/plugins/songUpload.js @@ -1,17 +1,13 @@ const { default: axios } = require('axios') -const fs = require('fs') const createOption = require('../util/option.js') const logger = require('../util/logger.js') +const { getUploadData, getFileExtension, sanitizeFilename } = require('../util/fileHelper') + module.exports = async (query, request) => { - let ext = 'mp3' - if (query.songFile.name.includes('.')) { - ext = query.songFile.name.split('.').pop() - } - const filename = query.songFile.name - .replace('.' + ext, '') - .replace(/\s/g, '') - .replace(/\./g, '_') + const ext = getFileExtension(query.songFile.name) + const filename = sanitizeFilename(query.songFile.name) const bucket = 'jd-musicrep-privatecloud-audio-public' + const tokenRes = await request( `/api/nos/token/alloc`, { @@ -72,14 +68,6 @@ module.exports = async (query, request) => { } } - const useTempFile = !!query.songFile.tempFilePath - let uploadData - if (useTempFile) { - uploadData = fs.createReadStream(query.songFile.tempFilePath) - } else { - uploadData = query.songFile.data - } - try { await axios({ method: 'post', @@ -90,7 +78,7 @@ module.exports = async (query, request) => { 'Content-Type': query.songFile.mimetype || 'audio/mpeg', 'Content-Length': String(query.songFile.size), }, - data: uploadData, + data: getUploadData(query.songFile), maxContentLength: Infinity, maxBodyLength: Infinity, timeout: 300000, diff --git a/plugins/upload.js b/plugins/upload.js index 647004d..3312d5d 100644 --- a/plugins/upload.js +++ b/plugins/upload.js @@ -1,6 +1,6 @@ const { default: axios } = require('axios') -const fs = require('fs') const createOption = require('../util/option.js') +const { getUploadData } = require('../util/fileHelper') module.exports = async (query, request) => { const data = { @@ -12,20 +12,11 @@ module.exports = async (query, request) => { return_body: `{"code":200,"size":"$(ObjectSize)"}`, type: 'other', } - // 获取key和token const res = await request( `/api/nos/token/alloc`, data, createOption(query, 'weapi'), ) - // 上传图片 - const useTempFile = !!query.imgFile.tempFilePath - let uploadData - if (useTempFile) { - uploadData = fs.createReadStream(query.imgFile.tempFilePath) - } else { - uploadData = query.imgFile.data - } const res2 = await axios({ method: 'post', @@ -34,13 +25,10 @@ module.exports = async (query, request) => { 'x-nos-token': res.body.result.token, 'Content-Type': query.imgFile.mimetype || 'image/jpeg', }, - data: uploadData, + data: getUploadData(query.imgFile), }) return { - // ...res.body.result, - // ...res2.data, - // ...res3.body, url_pre: 'https://p1.music.126.net/' + res.body.result.objectKey, imgId: res.body.result.docId, } diff --git a/util/fileHelper.js b/util/fileHelper.js new file mode 100644 index 0000000..24f5a5a --- /dev/null +++ b/util/fileHelper.js @@ -0,0 +1,90 @@ +const fs = require('fs') +const crypto = require('crypto') +const path = require('path') +const logger = require('./logger') + +function isTempFile(file) { + return !!(file && file.tempFilePath) +} + +async function getFileSize(file) { + if (isTempFile(file)) { + const stats = await fs.promises.stat(file.tempFilePath) + return stats.size + } + return file.data ? file.data.byteLength : file.size || 0 +} + +async function getFileMd5(file) { + if (file.md5) { + return file.md5 + } + + if (isTempFile(file)) { + return new Promise((resolve, reject) => { + const hash = crypto.createHash('md5') + const stream = fs.createReadStream(file.tempFilePath) + stream.on('data', (chunk) => hash.update(chunk)) + stream.on('end', () => resolve(hash.digest('hex'))) + stream.on('error', reject) + }) + } + + if (file.data) { + return crypto.createHash('md5').update(file.data).digest('hex') + } + + throw new Error('无法计算文件MD5: 缺少文件数据') +} + +function getUploadData(file) { + if (isTempFile(file)) { + return fs.createReadStream(file.tempFilePath) + } + return file.data +} + +async function cleanupTempFile(filePath) { + if (!filePath) return + try { + await fs.promises.unlink(filePath) + } catch (e) { + logger.info('临时文件清理失败:', e.message) + } +} + +async function readFileChunk(filePath, offset, length) { + const fd = await fs.promises.open(filePath, 'r') + const buffer = Buffer.alloc(length) + await fd.read(buffer, 0, length, offset) + await fd.close() + return buffer +} + +async function getFileExtension(filename) { + if (!filename) return 'mp3' + if (filename.includes('.')) { + return filename.split('.').pop().toLowerCase() + } + return 'mp3' +} + +function sanitizeFilename(filename) { + if (!filename) return 'unknown' + const ext = getFileExtension(filename) + return filename + .replace(/\.[^.]+$/, '') + .replace(/\s/g, '') + .replace(/\./g, '_') +} + +module.exports = { + isTempFile, + getFileSize, + getFileMd5, + getUploadData, + cleanupTempFile, + readFileChunk, + getFileExtension, + sanitizeFilename, +}