upppppppp1 commited on
Commit
bc51698
·
verified ·
1 Parent(s): 111b196

Update index.js

Browse files
Files changed (1) hide show
  1. index.js +167 -44
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,143 @@ 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 +377,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 +399,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 +417,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 +440,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({
@@ -323,22 +457,12 @@ app.get('*', async (req, res) => {
323
  })
324
  console.log(upload_result.data)
325
 
326
- // try {
327
- // const commit_result = await request.post(upload_info.data.commit.url, upload_info.data.commit.data, {
328
- // headers: {
329
- // ...upload_info.data.commit.headers
330
- // }
331
- // })
332
- // console.dir(commit_result.data, { depth: null })
333
- // } catch (e) {
334
- // console.log(e)
335
- // }
336
-
337
  if (upload_result.data.code == 2000) {
338
  return res.json({
339
  vid: upload_info?.data?.vid,
340
- url: `https://p16-cc-sg.ibyteimg.com/${upload_info.data.uri}~tplv-hdprqziq2y-png.gif`,
341
- // url: `https://sf16-sg-default.akamaized.net/obj/${upload_info.data.uri}`,
 
342
  message: '文件上传成功',
343
  total_chunks: successChunks.length
344
  })
@@ -354,7 +478,6 @@ app.get('*', async (req, res) => {
354
  })
355
 
356
  } catch (error) {
357
- // 如果是取消请求,不发送错误响应
358
  if (http.isCancel(error)) {
359
  console.log('请求已被用户取消')
360
  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://p16-oec-sg.ibyteimg.com/obj/${upload_info.data.uri}`,
351
+ url: `https://p19-creative-tool-sg.ibyteimg.com/${upload_info.data.uri}~tplv-n2703mo9gi-image.image`,
352
+ // url: `https://p16-cc-sg.ibyteimg.com/${upload_info.data.uri}~tplv-hdprqziq2y-png.gif`,
353
+ message: '文件上传成功',
354
+ total_chunks: successChunks.length
355
+ })
356
+ }
357
+
358
  return res.json({
359
  video_url: videoUrl,
360
+ chunks: successChunks,
361
+ upload_list,
362
+ fileSize,
363
+ chunkSize,
364
+ threadCount
365
  })
366
  }
367
 
368
+ // 支持范围请求:继续原有逻辑
 
369
  const threadCount = Math.ceil(fileSize / chunkSize)
 
370
  console.log(`文件大小: ${fileSize} bytes, 分片数: ${threadCount}`)
371
 
 
372
  const tasks = []
373
  for (let i = 0; i < threadCount; i++) {
374
  const start = i * chunkSize
 
377
 
378
  tasks.push(() => (async () => {
379
  try {
 
380
  if (isRequestCancelled) {
381
  throw new http.Cancel('请求已取消')
382
  }
383
 
 
384
  const arrayBuffer = await downloadChunkWithRetry(request, finalUrl, start, end, partNumber)
385
 
 
386
  if (isRequestCancelled) {
387
  throw new http.Cancel('请求已取消')
388
  }
389
 
 
390
  const crc32_text = crc32(arrayBuffer)
391
 
 
392
  const uploadResult = await uploadChunkWithRetry(
393
  request,
394
  upload_info.data.url,
 
399
  upload_info.data.authorization
400
  )
401
 
 
402
  upload_list[partNumber] = crc32_text
 
403
  return uploadResult
404
 
405
  } catch (error) {
 
417
  })())
418
  }
419
 
 
420
  const chunksResults = await runWithConcurrency(tasks, 100, cancelTokenSource)
421
 
 
422
  if (isRequestCancelled) {
423
  return
424
  }
425
 
 
426
  const chunks = chunksResults.map(result =>
427
  result.status === 'fulfilled' ? result.value : result.reason
428
  )
429
 
 
430
  const validChunks = chunks.filter(chunk => !(chunk instanceof Error && http.isCancel(chunk)))
431
 
 
432
  const failedChunks = validChunks.filter(chunk => chunk && chunk.status === 'failed')
433
  if (failedChunks.length > 0) {
434
  return res.status(500).json({
 
440
  })
441
  }
442
 
 
443
  const successChunks = validChunks.filter(chunk => chunk && chunk.status === 'success')
444
  if (successChunks.length === 0) {
445
  return res.status(500).json({
 
457
  })
458
  console.log(upload_result.data)
459
 
 
 
 
 
 
 
 
 
 
 
 
460
  if (upload_result.data.code == 2000) {
461
  return res.json({
462
  vid: upload_info?.data?.vid,
463
+ // url: `https://p16-oec-sg.ibyteimg.com/obj/${upload_info.data.uri}`,
464
+ url: `https://p19-creative-tool-sg.ibyteimg.com/${upload_info.data.uri}~tplv-n2703mo9gi-image.image`,
465
+ // url: `https://p16-cc-sg.ibyteimg.com/${upload_info.data.uri}~tplv-hdprqziq2y-png.gif`,
466
  message: '文件上传成功',
467
  total_chunks: successChunks.length
468
  })
 
478
  })
479
 
480
  } catch (error) {
 
481
  if (http.isCancel(error)) {
482
  console.log('请求已被用户取消')
483
  return