feat(cloud): 支持临时文件上传功能

- 使用 crypto 模块替换 md5 模块进行文件哈希计算
- 添加对临时文件上传的支持,当存在 tempFilePath 时使用文件流处理
- 实现临时文件的 MD5 计算和元数据解析功能
- 在上传完成后自动清理临时文件
- 配置服务器端文件上传中间件启用临时文件支持
- 修改上传插件以支持临时文件读取流上传方式
- 增加文件大小获取和验证的兼容性处理
This commit is contained in:
LaoShui 2026-02-18 16:44:19 +08:00
parent 26d55255e0
commit ba7d1a8574
3 changed files with 63 additions and 17 deletions

View File

@ -1,5 +1,6 @@
const uploadPlugin = require('../plugins/songUpload') const uploadPlugin = require('../plugins/songUpload')
const md5 = require('md5') const crypto = require('crypto')
const fs = require('fs')
const createOption = require('../util/option.js') const createOption = require('../util/option.js')
const logger = require('../util/logger.js') const logger = require('../util/logger.js')
let mm let mm
@ -26,17 +27,40 @@ module.exports = async (query, request) => {
}, },
}) })
} }
if (!query.songFile.md5) {
query.songFile.md5 = md5(query.songFile.data) const useTempFile = !!query.songFile.tempFilePath
query.songFile.size = query.songFile.data.byteLength let fileSize = query.songFile.size
let fileMd5 = query.songFile.md5
if (useTempFile) {
const stats = fs.statSync(query.songFile.tempFilePath)
fileSize = stats.size
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( const res = await request(
`/api/cloud/upload/check`, `/api/cloud/upload/check`,
{ {
bitrate: String(bitrate), bitrate: String(bitrate),
ext: '', ext: '',
length: query.songFile.size, length: fileSize,
md5: query.songFile.md5, md5: fileMd5,
songId: '0', songId: '0',
version: 1, version: 1,
}, },
@ -46,10 +70,15 @@ module.exports = async (query, request) => {
let album = '' let album = ''
let songName = '' let songName = ''
try { try {
const metadata = await mm.parseBuffer( let metadata
if (useTempFile) {
metadata = await mm.parseFile(query.songFile.tempFilePath)
} else {
metadata = await mm.parseBuffer(
query.songFile.data, query.songFile.data,
query.songFile.mimetype, query.songFile.mimetype,
) )
}
const info = metadata.common const info = metadata.common
if (info.title) { if (info.title) {
@ -73,7 +102,7 @@ module.exports = async (query, request) => {
local: false, local: false,
nos_product: 3, nos_product: 3,
type: 'audio', type: 'audio',
md5: query.songFile.md5, md5: fileMd5,
}, },
createOption(query), createOption(query),
) )
@ -98,15 +127,22 @@ module.exports = async (query, request) => {
} catch (uploadError) { } catch (uploadError) {
logger.error('Upload failed:', uploadError) logger.error('Upload failed:', uploadError)
return Promise.reject(uploadError) return Promise.reject(uploadError)
} finally {
if (useTempFile && fs.existsSync(query.songFile.tempFilePath)) {
fs.unlinkSync(query.songFile.tempFilePath)
}
} }
} else { } else {
logger.info('File already exists, skip upload') logger.info('File already exists, skip upload')
if (useTempFile && fs.existsSync(query.songFile.tempFilePath)) {
fs.unlinkSync(query.songFile.tempFilePath)
}
} }
const res2 = await request( const res2 = await request(
`/api/upload/cloud/info/v2`, `/api/upload/cloud/info/v2`,
{ {
md5: query.songFile.md5, md5: fileMd5,
songid: res.body.songId, songid: res.body.songId,
filename: query.songFile.name, filename: query.songFile.name,
song: songName || filename, song: songName || filename,

View File

@ -1,4 +1,5 @@
const { default: axios } = require('axios') const { default: axios } = require('axios')
const fs = require('fs')
const createOption = require('../util/option.js') const createOption = require('../util/option.js')
const logger = require('../util/logger.js') const logger = require('../util/logger.js')
module.exports = async (query, request) => { module.exports = async (query, request) => {
@ -71,6 +72,14 @@ 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 { try {
await axios({ await axios({
method: 'post', method: 'post',
@ -81,7 +90,7 @@ module.exports = async (query, request) => {
'Content-Type': 'audio/mpeg', 'Content-Type': 'audio/mpeg',
'Content-Length': String(query.songFile.size), 'Content-Length': String(query.songFile.size),
}, },
data: query.songFile.data, data: uploadData,
maxContentLength: Infinity, maxContentLength: Infinity,
maxBodyLength: Infinity, maxBodyLength: Infinity,
timeout: 300000, timeout: 300000,

View File

@ -183,11 +183,12 @@ async function consturctServer(moduleDefs) {
app.use(fileUpload({ app.use(fileUpload({
limits: { limits: {
fileSize: 500 * 1024 * 1024 // 500MB fileSize: 500 * 1024 * 1024
}, },
useTempFiles: false, useTempFiles: true,
tempFileDir: '/tmp/', tempFileDir: require('os').tmpdir(),
abortOnLimit: true abortOnLimit: true,
parseNested: true
})) }))
/** /**