upppppppp1 commited on
Commit
7a98122
·
verified ·
1 Parent(s): 70907fc

Update index.js

Browse files
Files changed (1) hide show
  1. index.js +162 -31
index.js CHANGED
@@ -17,6 +17,42 @@ const createRequestWithCancel = (cancelToken) => {
17
  }
18
  }
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  // 带重试功能的分片下载函数
21
  async function downloadChunkWithRetry(request, finalUrl, start, end, partNumber, maxRetries = 30) {
22
  let lastError = null
@@ -44,7 +80,6 @@ async function downloadChunkWithRetry(request, finalUrl, start, end, partNumber,
44
  return arrayBuffer
45
 
46
  } catch (error) {
47
- // 如果是取消请求,直接抛出
48
  if (http.isCancel(error)) {
49
  throw error
50
  }
@@ -53,8 +88,6 @@ async function downloadChunkWithRetry(request, finalUrl, start, end, partNumber,
53
  console.warn(`分片 ${partNumber} 下载失败 (尝试 ${attempt}/${maxRetries}):`, error.message)
54
 
55
  if (attempt < maxRetries) {
56
- // 指数退避策略,等待时间逐渐增加
57
- // const waitTime = Math.min(500 * Math.pow(2, attempt - 1), 5000)
58
  const waitTime = 300
59
  console.log(`等待 ${waitTime}ms 后重试分片 ${partNumber}`)
60
  await new Promise(resolve => setTimeout(resolve, waitTime))
@@ -103,7 +136,6 @@ async function uploadChunkWithRetry(request, uploadUrl, arrayBuffer, partNumber,
103
  throw new Error(`CRC32 校验失败: 期望 ${crc32_text}, 实际 ${upload_result.data.data?.crc32}`)
104
  }
105
  } catch (error) {
106
- // 如果是取消请求,直接抛出
107
  if (http.isCancel(error)) {
108
  throw error
109
  }
@@ -112,7 +144,6 @@ async function uploadChunkWithRetry(request, uploadUrl, arrayBuffer, partNumber,
112
  console.warn(`分片 ${partNumber} ${upload_host} (尝试 ${attempt}/${maxRetries}):`, error.message)
113
 
114
  if (attempt < maxRetries) {
115
- // const waitTime = Math.min(500 * Math.pow(2, attempt - 1), 5000)
116
  const waitTime = 300
117
  console.log(`等待 ${waitTime}ms 后重试上传分片 ${partNumber}`)
118
  await new Promise(resolve => setTimeout(resolve, waitTime))
@@ -129,7 +160,6 @@ async function runWithConcurrency(tasks, maxConcurrency = 50, cancelToken) {
129
  const executing = new Set()
130
 
131
  for (let i = 0; i < tasks.length; i++) {
132
- // 检查是否已取消
133
  if (cancelToken && cancelToken.reason) {
134
  console.log('检测到取消信号,停止创建新任务')
135
  break
@@ -137,7 +167,6 @@ async function runWithConcurrency(tasks, maxConcurrency = 50, cancelToken) {
137
 
138
  const task = tasks[i]
139
 
140
- // 如果当前执行的任务数达到最大并发数,等待其中一个完成
141
  if (executing.size >= maxConcurrency) {
142
  await Promise.race(executing)
143
  }
@@ -154,16 +183,13 @@ async function runWithConcurrency(tasks, maxConcurrency = 50, cancelToken) {
154
  results.push(promise)
155
  }
156
 
157
- // 等待所有剩余任务完成
158
  return Promise.allSettled(results)
159
  }
160
 
161
  app.get('*', async (req, res) => {
162
- // 创建取消令牌
163
  const cancelTokenSource = http.CancelToken.source()
164
  let isRequestCancelled = false
165
 
166
- // 监听连接关闭事件
167
  req.on('close', () => {
168
  if (!res.headersSent) {
169
  console.log('用户断开连接,取消所有请求...')
@@ -174,11 +200,11 @@ app.get('*', async (req, res) => {
174
 
175
  try {
176
  const videoUrl = req.originalUrl.replace(/^\//, '')
 
177
  if (/favicon.ico/gim.test(videoUrl)) {
178
  return
179
  }
180
 
181
- // 创建带取消令牌的请求实例
182
  const request = createRequestWithCancel(cancelTokenSource.token)
183
 
184
  // 获取上传信息
@@ -193,7 +219,6 @@ app.get('*', async (req, res) => {
193
  const uploadid = upload_part_info.data.data.uploadid
194
  const upload_list = {}
195
 
196
- // 检查是否已取消
197
  if (isRequestCancelled) {
198
  return
199
  }
@@ -207,21 +232,141 @@ app.get('*', async (req, res) => {
207
  const contentLength = headResponse.headers['content-length']
208
  const acceptRanges = headResponse.headers['accept-ranges']
209
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
  // 检查服务器是否支持范围请求
211
  if (!acceptRanges || acceptRanges === 'none' || !contentLength) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
  return res.json({
213
  video_url: videoUrl,
214
- message: '不支持范围请求'
 
 
 
 
215
  })
216
  }
217
 
218
- const fileSize = parseInt(contentLength)
219
- const chunkSize = 2000 * 1024
220
  const threadCount = Math.ceil(fileSize / chunkSize)
221
-
222
  console.log(`文件大小: ${fileSize} bytes, 分片数: ${threadCount}`)
223
 
224
- // 2. 创建所有分片任务
225
  const tasks = []
226
  for (let i = 0; i < threadCount; i++) {
227
  const start = i * chunkSize
@@ -230,23 +375,18 @@ app.get('*', async (req, res) => {
230
 
231
  tasks.push(() => (async () => {
232
  try {
233
- // 检查是否已取消
234
  if (isRequestCancelled) {
235
  throw new http.Cancel('请求已取消')
236
  }
237
 
238
- // 下载分片(带重试)
239
  const arrayBuffer = await downloadChunkWithRetry(request, finalUrl, start, end, partNumber)
240
 
241
- // 检查是否已取消
242
  if (isRequestCancelled) {
243
  throw new http.Cancel('请求已取消')
244
  }
245
 
246
- // 计算 CRC32
247
  const crc32_text = crc32(arrayBuffer)
248
 
249
- // 上传分片(带重试)
250
  const uploadResult = await uploadChunkWithRetry(
251
  request,
252
  upload_info.data.url,
@@ -257,9 +397,7 @@ app.get('*', async (req, res) => {
257
  upload_info.data.authorization
258
  )
259
 
260
- // 保存到上传列表
261
  upload_list[partNumber] = crc32_text
262
-
263
  return uploadResult
264
 
265
  } catch (error) {
@@ -277,23 +415,18 @@ app.get('*', async (req, res) => {
277
  })())
278
  }
279
 
280
- // 3. 并行执行所有分片任务,但限制最大并发数为100
281
  const chunksResults = await runWithConcurrency(tasks, 100, cancelTokenSource)
282
 
283
- // 检查是否已取消
284
  if (isRequestCancelled) {
285
  return
286
  }
287
 
288
- // 处理任务结果
289
  const chunks = chunksResults.map(result =>
290
  result.status === 'fulfilled' ? result.value : result.reason
291
  )
292
 
293
- // 过滤掉取消错误
294
  const validChunks = chunks.filter(chunk => !(chunk instanceof Error && http.isCancel(chunk)))
295
 
296
- // 检查是否所有分片都成功处理
297
  const failedChunks = validChunks.filter(chunk => chunk && chunk.status === 'failed')
298
  if (failedChunks.length > 0) {
299
  return res.status(500).json({
@@ -305,7 +438,6 @@ app.get('*', async (req, res) => {
305
  })
306
  }
307
 
308
- // 检查是否有足够的成功分片
309
  const successChunks = validChunks.filter(chunk => chunk && chunk.status === 'success')
310
  if (successChunks.length === 0) {
311
  return res.status(500).json({
@@ -342,7 +474,6 @@ app.get('*', async (req, res) => {
342
  })
343
 
344
  } catch (error) {
345
- // 如果是取消请求,不发送错误响应
346
  if (http.isCancel(error)) {
347
  console.log('请求已被用户取消')
348
  return
 
17
  }
18
  }
19
 
20
+ // 完整下载文件(用于不支持范围请求的情况)
21
+ async function downloadFullFileWithRetry(request, finalUrl, maxRetries = 30) {
22
+ let lastError = null
23
+
24
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
25
+ try {
26
+ const response = await request.get(finalUrl, {
27
+ responseType: 'arraybuffer'
28
+ })
29
+
30
+ if (response.status !== 200) {
31
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`)
32
+ }
33
+
34
+ console.log(`完整文件下载成功 (尝试 ${attempt})`)
35
+ return response.data
36
+
37
+ } catch (error) {
38
+ if (http.isCancel(error)) {
39
+ throw error
40
+ }
41
+
42
+ lastError = error
43
+ console.warn(`完整文件下载失败 (尝试 ${attempt}/${maxRetries}):`, error.message)
44
+
45
+ if (attempt < maxRetries) {
46
+ const waitTime = 300
47
+ console.log(`等待 ${waitTime}ms 后重试完整下载`)
48
+ await new Promise(resolve => setTimeout(resolve, waitTime))
49
+ }
50
+ }
51
+ }
52
+
53
+ throw new Error(`完整文件下载失败,已达到最大重试次数: ${lastError.message}`)
54
+ }
55
+
56
  // 带重试功能的分片下载函数
57
  async function downloadChunkWithRetry(request, finalUrl, start, end, partNumber, maxRetries = 30) {
58
  let lastError = null
 
80
  return arrayBuffer
81
 
82
  } catch (error) {
 
83
  if (http.isCancel(error)) {
84
  throw error
85
  }
 
88
  console.warn(`分片 ${partNumber} 下载失败 (尝试 ${attempt}/${maxRetries}):`, error.message)
89
 
90
  if (attempt < maxRetries) {
 
 
91
  const waitTime = 300
92
  console.log(`等待 ${waitTime}ms 后重试分片 ${partNumber}`)
93
  await new Promise(resolve => setTimeout(resolve, waitTime))
 
136
  throw new Error(`CRC32 校验失败: 期望 ${crc32_text}, 实际 ${upload_result.data.data?.crc32}`)
137
  }
138
  } catch (error) {
 
139
  if (http.isCancel(error)) {
140
  throw error
141
  }
 
144
  console.warn(`分片 ${partNumber} ${upload_host} (尝试 ${attempt}/${maxRetries}):`, error.message)
145
 
146
  if (attempt < maxRetries) {
 
147
  const waitTime = 300
148
  console.log(`等待 ${waitTime}ms 后重试上传分片 ${partNumber}`)
149
  await new Promise(resolve => setTimeout(resolve, waitTime))
 
160
  const executing = new Set()
161
 
162
  for (let i = 0; i < tasks.length; i++) {
 
163
  if (cancelToken && cancelToken.reason) {
164
  console.log('检测到取消信号,停止创建新任务')
165
  break
 
167
 
168
  const task = tasks[i]
169
 
 
170
  if (executing.size >= maxConcurrency) {
171
  await Promise.race(executing)
172
  }
 
183
  results.push(promise)
184
  }
185
 
 
186
  return Promise.allSettled(results)
187
  }
188
 
189
  app.get('*', async (req, res) => {
 
190
  const cancelTokenSource = http.CancelToken.source()
191
  let isRequestCancelled = false
192
 
 
193
  req.on('close', () => {
194
  if (!res.headersSent) {
195
  console.log('用户断开连接,取消所有请求...')
 
200
 
201
  try {
202
  const videoUrl = req.originalUrl.replace(/^\//, '')
203
+ console.log(videoUrl)
204
  if (/favicon.ico/gim.test(videoUrl)) {
205
  return
206
  }
207
 
 
208
  const request = createRequestWithCancel(cancelTokenSource.token)
209
 
210
  // 获取上传信息
 
219
  const uploadid = upload_part_info.data.data.uploadid
220
  const upload_list = {}
221
 
 
222
  if (isRequestCancelled) {
223
  return
224
  }
 
232
  const contentLength = headResponse.headers['content-length']
233
  const acceptRanges = headResponse.headers['accept-ranges']
234
 
235
+ const contenType = headResponse.headers['content-type']
236
+
237
+ if (/^image|^text|^application/gim.test(contenType)) {
238
+ if (!/application\/octet-stream/gim.test(contenType)) {
239
+ return res.json({
240
+ video_url: videoUrl,
241
+ message: `文件类型不支持 ${contenType}`
242
+ })
243
+ }
244
+ }
245
+
246
+ const fileSize = parseInt(contentLength)
247
+ const chunkSize = 2000 * 1024
248
+
249
  // 检查服务器是否支持范围请求
250
  if (!acceptRanges || acceptRanges === 'none' || !contentLength) {
251
+ // 不支持范围请求:完整下载后分片上传
252
+ console.log('服务器不支持范围请求,将完整下载文件...')
253
+
254
+ // 完整下载文件
255
+ const fullFileBuffer = await downloadFullFileWithRetry(request, finalUrl)
256
+
257
+ if (isRequestCancelled) {
258
+ return
259
+ }
260
+
261
+ const threadCount = Math.ceil(fileSize / chunkSize)
262
+ console.log(`文件大小: ${fileSize} bytes, 分片数: ${threadCount}`)
263
+
264
+ const tasks = []
265
+ for (let i = 0; i < threadCount; i++) {
266
+ const start = i * chunkSize
267
+ const end = Math.min(start + chunkSize, fileSize)
268
+ const partNumber = i + 1
269
+
270
+ tasks.push(() => (async () => {
271
+ try {
272
+ if (isRequestCancelled) {
273
+ throw new http.Cancel('请求已取消')
274
+ }
275
+
276
+ const chunkBuffer = fullFileBuffer.slice(start, end)
277
+ const crc32_text = crc32(chunkBuffer)
278
+
279
+ const uploadResult = await uploadChunkWithRetry(
280
+ request,
281
+ upload_info.data.url,
282
+ chunkBuffer,
283
+ partNumber,
284
+ crc32_text,
285
+ uploadid,
286
+ upload_info.data.authorization
287
+ )
288
+
289
+ upload_list[partNumber] = crc32_text
290
+ return uploadResult
291
+
292
+ } catch (error) {
293
+ if (http.isCancel(error)) {
294
+ console.log(`分片 ${partNumber} 处理被取消`)
295
+ throw error
296
+ }
297
+ console.error(`分片 ${partNumber} 处理失败:`, error.message)
298
+ return {
299
+ part_number: partNumber,
300
+ status: 'failed',
301
+ error: error.message
302
+ }
303
+ }
304
+ })())
305
+ }
306
+
307
+ const chunksResults = await runWithConcurrency(tasks, 100, cancelTokenSource)
308
+
309
+ if (isRequestCancelled) {
310
+ return
311
+ }
312
+
313
+ const chunks = chunksResults.map(result =>
314
+ result.status === 'fulfilled' ? result.value : result.reason
315
+ )
316
+
317
+ const validChunks = chunks.filter(chunk => !(chunk instanceof Error && http.isCancel(chunk)))
318
+
319
+ const failedChunks = validChunks.filter(chunk => chunk && chunk.status === 'failed')
320
+ if (failedChunks.length > 0) {
321
+ return res.status(500).json({
322
+ video_url: videoUrl,
323
+ message: '部分分片处理失败',
324
+ failed_chunks: failedChunks,
325
+ success_count: validChunks.length - failedChunks.length,
326
+ failed_count: failedChunks.length
327
+ })
328
+ }
329
+
330
+ const successChunks = validChunks.filter(chunk => chunk && chunk.status === 'success')
331
+ if (successChunks.length === 0) {
332
+ return res.status(500).json({
333
+ video_url: videoUrl,
334
+ message: '所有分片处理失败'
335
+ })
336
+ }
337
+
338
+ const finish = Object.entries(upload_list).map(i => i.join(':')).join(',')
339
+
340
+ const upload_result = await request.post(`${upload_info.data.url}?uploadid=${uploadid}&uploadmode=part&phase=finish`, finish, {
341
+ headers: {
342
+ 'authorization': upload_info.data.authorization
343
+ }
344
+ })
345
+ console.log(upload_result.data)
346
+
347
+ if (upload_result.data.code == 2000) {
348
+ return res.json({
349
+ vid: upload_info.data.vid,
350
+ url: `https://data.emmmm.eu.org/parse/zj/${upload_info.data.uri}`,
351
+ message: '文件上传成功',
352
+ total_chunks: successChunks.length
353
+ })
354
+ }
355
+
356
  return res.json({
357
  video_url: videoUrl,
358
+ chunks: successChunks,
359
+ upload_list,
360
+ fileSize,
361
+ chunkSize,
362
+ threadCount
363
  })
364
  }
365
 
366
+ // 支持范围请求:继续原有逻辑
 
367
  const threadCount = Math.ceil(fileSize / chunkSize)
 
368
  console.log(`文件大小: ${fileSize} bytes, 分片数: ${threadCount}`)
369
 
 
370
  const tasks = []
371
  for (let i = 0; i < threadCount; i++) {
372
  const start = i * chunkSize
 
375
 
376
  tasks.push(() => (async () => {
377
  try {
 
378
  if (isRequestCancelled) {
379
  throw new http.Cancel('请求已取消')
380
  }
381
 
 
382
  const arrayBuffer = await downloadChunkWithRetry(request, finalUrl, start, end, partNumber)
383
 
 
384
  if (isRequestCancelled) {
385
  throw new http.Cancel('请求已取消')
386
  }
387
 
 
388
  const crc32_text = crc32(arrayBuffer)
389
 
 
390
  const uploadResult = await uploadChunkWithRetry(
391
  request,
392
  upload_info.data.url,
 
397
  upload_info.data.authorization
398
  )
399
 
 
400
  upload_list[partNumber] = crc32_text
 
401
  return uploadResult
402
 
403
  } catch (error) {
 
415
  })())
416
  }
417
 
 
418
  const chunksResults = await runWithConcurrency(tasks, 100, cancelTokenSource)
419
 
 
420
  if (isRequestCancelled) {
421
  return
422
  }
423
 
 
424
  const chunks = chunksResults.map(result =>
425
  result.status === 'fulfilled' ? result.value : result.reason
426
  )
427
 
 
428
  const validChunks = chunks.filter(chunk => !(chunk instanceof Error && http.isCancel(chunk)))
429
 
 
430
  const failedChunks = validChunks.filter(chunk => chunk && chunk.status === 'failed')
431
  if (failedChunks.length > 0) {
432
  return res.status(500).json({
 
438
  })
439
  }
440
 
 
441
  const successChunks = validChunks.filter(chunk => chunk && chunk.status === 'success')
442
  if (successChunks.length === 0) {
443
  return res.status(500).json({
 
474
  })
475
 
476
  } catch (error) {
 
477
  if (http.isCancel(error)) {
478
  console.log('请求已被用户取消')
479
  return