PageSourceSearch

https://www.glenengineering.com/assets/plugins/sdk/ufile-token.js

js glenengineering.com collected 2026-10-04 04:49:32 UTC 25,020 bytes, 871 lines download raw bytes

1function UCloudUFile(bucketName, bucketUrl, tokenPublicKey, tokenPrivateKey, tokenServerUrl, prefix, appKey, getSignature) {
2  // 存储空间名称。既可以在这里配置,也可以在实例化时传参配置。
3  // 例如 bucketName = "example-name"
4  this.bucketName = bucketName || ''
5
6  // 存储空间域名。既可以在这里配置,也可以在实例化时传参配置。
7  // 例如 bucketUrl = "https://example-name.cn-bj.ufileos.com/"
8  this.bucketUrl = bucketUrl || ''
9
10  // 令牌公钥。既可以在这里配置,也可以在实例化时传参配置。
11  this.PUBLIC_KEY = tokenPublicKey || ''
12
13  // 令牌私钥。既可以在这里配置,也可以在实例化时传参配置。
14  this.PRIVATE_KEY = tokenPrivateKey || ''
15
16  // 签名服务器地址
17  this.tokenServerUrl = tokenServerUrl || ''
18
19  // 令牌配置的前缀,无前缀填空字符串
20  // 例如 PREFIX = "example-prefix"
21  this.PREFIX = prefix || ''
22
23  this.appKey = appKey || ''
24
25  // 是否服务端签名
26  this.signatureServer = !!this.tokenServerUrl || false
27
28  // 走外部sdk调用的获取签名的接口
29  this.getSignature = getSignature
30
31  this.setPrefix = function(prefix) {
32    this.PREFIX = prefix
33  }
34
35  this.createAjax = function (argument) {
36    var xmlhttp = {}
37    if (window.XMLHttpRequest) {
38      xmlhttp = new XMLHttpRequest()
39    } else {
40      xmlhttp = new ActiveXObject('Microsoft.XMLHTTP')
41    }
42
43    // 发送二进制数据
44    if (!XMLHttpRequest.prototype.sendAsBinary) {
45      XMLHttpRequest.prototype.sendAsBinary = function (datastr) {
46        function byteValue(x) {
47          return x.charCodeAt(0) & 0xff
48        }
49        var ords = Array.prototype.map.call(datastr, byteValue)
50        var ui8a = new Uint8Array(ords)
51        this.send(ui8a.buffer)
52      }
53    }
54    return xmlhttp
55  }
56
57  this.getBucketUrl = function () {
58    var bucketUrl = this.bucketUrl
59
60    // 如果不是以"/"结尾,则自动补上
61    if (bucketUrl.charAt(bucketUrl.length - 1) !== '/') {
62      bucketUrl += '/'
63    }
64    return bucketUrl
65  }
66
67  // 重命名文件
68  this.getFileName = function (file, fileRename) {
69    var fileName
70    /*
71            if (fileRename && (fileRename !== "")) {
72                fileName = fileRename + file.name.substring(file.name.lastIndexOf("."));
73            } else {
74                fileName = file.name;
75            }
76    */
77    return fileRename
78    //        return fileName;
79  }
80
81  // 增加前缀
82  this.addPrefix = function (filename) {
83    return this.PREFIX ? this.PREFIX + '/' + filename : filename
84  }
85}
86
87UCloudUFile.prototype.contentMd5 = '' // 文件的md5
88UCloudUFile.prototype.slice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
89UCloudUFile.prototype.sliceSize = 4 * 1024 * 1024 // 分片大小为4M
90UCloudUFile.prototype.getExpired = function (second) {
91  return Date.parse(new Date()) / 1000 + (second || 600)
92}
93
94// 获取文件管理签名token
95UCloudUFile.prototype.getUFileToken = function (options, success, error) {
96  var method = options.method || 'GET'
97  var file = options.file || {}
98  var fileName = options.fileName
99  var md5Required = options.md5Required
100
101  var keyName
102  var contentType = options.contentType || file.type || ''
103  var putPolicy = options.putPolicy || ''
104
105  if (fileName) {
106    keyName = fileName
107  } else if (file.FileName) {
108    keyName = file.FileName
109  } else if (file.name) {
110    keyName = this.addPrefix(file.name)
111  } else {
112    keyName = ''
113  }
114
115  var that = this
116  var expired = this.getExpired()
117
118  var publicKey = this.PUBLIC_KEY
119  var privateKey = this.PRIVATE_KEY
120
121  var token = ''
122
123  function canonicalizedResource(bucket, key) {
124    return '/' + bucket + '/' + decodeURIComponent(key)
125  }
126
127  function sign(data, put_policy) {
128    data = CryptoJS.enc.Utf8.parse(data)
129    var hash = CryptoJS.HmacSHA1(data, privateKey)
130    var signdata = hash.toString(CryptoJS.enc.Base64)
131
132    var signStr = 'UCloud ' + publicKey + ':' + signdata
133
134    if (put_policy) {
135      var putPolicyStr = JSON.stringify(put_policy).replace(/\"/g, '\\"')
136      var signPolicyStr = Base64.encode(putPolicyStr)
137      signStr += ':' + signPolicyStr
138    }
139
140    return signStr
141  }
142
143  // SDK内部签名计算
144  function signRequest(method, bucket, key, content_md5, content_type, date, put_policy) {
145    var data = ''
146    data += method.toUpperCase() + '\n'
147    data += content_md5 + '\n'
148    data += content_type + '\n'
149    data += date + '\n'
150    data += canonicalizedResource(bucket, key)
151
152    return sign(data, put_policy)
153  }
154
155  // 服务端签名计算
156  function getSignatureToken(tokenServerUrl, method, bucket, key, content_md5, content_type, date, put_policy, appKey) {
157    if (that.getSignature) {
158      that.getSignature({ method, bucket, key, content_md5, content_type, date, put_policy, appKey })
159        .then((res) => {
160          success(res.data.trim())
161        })
162    } else {
163      var ajax = that.createAjax()
164      var url = tokenServerUrl + '?method=' + method +
165        '&appkey=' + appKey +
166        '&bucket=' + bucket +
167        '&key=' + key +
168        '&content_md5=' + content_md5 +
169        '&content_type=' + content_type +
170        '&date=' + date +
171        '&put_policy=' + put_policy +
172        '&domain=jmc.dweyes.cn'
173      ajax.open('GET', url, true)
174
175      var onreadystatechange = function () {
176        if (ajax.readyState === 4) {
177          if (ajax.status === 200) {
178            var token = JSON.parse(ajax.responseText).data
179            success(token.trim())
180          } else {
181            error(ajax.responseText)
182          }
183        }
184      }
185
186      ajax.onreadystatechange = onreadystatechange
187      ajax.send()
188    }
189  }
190
191  this.getContentMd5(file, function (md5) {
192    if (md5Required === false) {
193      md5 = ''
194    }
195    console.log('that.signatureServer:', that.signatureServer)
196    if (that.signatureServer) {
197      token = getSignatureToken(that.tokenServerUrl, method, that.bucketName, encodeURIComponent(keyName).replace('%2F', '/'), md5, contentType, '', putPolicy, that.appKey)
198    } else {
199      token = signRequest(method, that.bucketName, encodeURIComponent(keyName).replace('%2F', '/'), md5, contentType, '', putPolicy)
200    }
201
202    if (token) {
203      success(token)
204    } else {
205      error(token)
206    }
207  })
208}
209
210// 获取文件的内容MD5签名
211UCloudUFile.prototype.getContentMd5 = function (file, success) {
212  // 如果file不是File对象的实例,则不需要处理Md5
213  if (file.size == undefined) {
214    success('')
215    return
216  }
217
218  var that = this
219  var fileReader = new FileReader()
220  var spark = new SparkMD5.ArrayBuffer()
221  var chunks = Math.ceil(file.size / this.sliceSize)
222  var currentChunk = 0
223
224  // 每块文件读取完毕之后的处理
225  fileReader.onload = function (e) {
226    // 每块交由sparkMD5进行计算
227
228    spark.append(e.target.result)
229    currentChunk++
230
231    // 如果文件处理完成计算MD5,如果还有分片继续处理
232    if (currentChunk < chunks) {
233      loadNext()
234    } else {
235      that.contentMd5 = spark.end()
236      success(that.contentMd5)
237    }
238  }
239
240  // 处理单片文件的上传
241  function loadNext() {
242    var start = currentChunk * that.sliceSize
243    var end = start + that.sliceSize >= file.size ? file.size : start + that.sliceSize
244
245    fileReader.readAsArrayBuffer(that.slice.call(file, start, end))
246  }
247
248  loadNext()
249}
250
251// 获取文件列表
252UCloudUFile.prototype.getFileList = function (options, success, error) {
253  var that = this
254  var method = 'GET'
255  var prefix = options.prefix || that.PREFIX
256  var marker = options.marker || ''
257  var limit = options.limit || 20
258
259  var requestToken = {
260    method: method
261  }
262
263  this.getUFileToken(requestToken, function (token) {
264    var ajax = that.createAjax()
265    var url = that.getBucketUrl() + '?list' +
266      '&prefix=' + prefix +
267      '&marker=' + marker +
268      '&limit=' + limit
269    ajax.open(method, url, true)
270    ajax.setRequestHeader('Authorization', token)
271
272    var onreadystatechange = function () {
273      if (ajax.readyState == 4) {
274        if (ajax.status == 200) {
275          success(JSON.parse(ajax.response))
276        } else {
277          error(ajax.responseText)
278        }
279      }
280    }
281
282    ajax.onreadystatechange = onreadystatechange
283    ajax.send()
284  }, error)
285}
286
287// 查看文件信息
288UCloudUFile.prototype.getFileDetail = function (fileName, success, error) {
289  var that = this
290  var method = 'HEAD'
291  var requestToken = {
292    method: method,
293    fileName: fileName
294  }
295
296  this.getUFileToken(requestToken, function (token) {
297    var ajax = that.createAjax()
298    var url = that.getBucketUrl() + encodeURIComponent(fileName).replace('%2F', '/')
299    ajax.open(method, url, true)
300    ajax.setRequestHeader('Authorization', token)
301
302    var onreadystatechange = function () {
303      if (ajax.readyState === 4) {
304        if (ajax.status === 200) {
305          var eTag = ajax.getResponseHeader('ETag')
306          var successRes = {
307            contentType: ajax.getResponseHeader('Content-Type'),
308            eTag: eTag.substring(1, eTag.length - 1),
309            status: ajax.status,
310            response: ajax.response
311          }
312          success(successRes)
313        } else {
314          error(ajax.responseText)
315        }
316      }
317    }
318
319    ajax.onreadystatechange = onreadystatechange
320    ajax.send()
321  }, error)
322}
323
324// 普通上传
325UCloudUFile.prototype.uploadFile = function (options, success, error, progress) {
326  options.file = options.file || {}
327  options.file.name = options.file.name || 'png'
328  var name = new Date().getTime()
329  name += '-' + options.file.name.toLowerCase().replace(/ |\.[a-z]{2,6}$/g, '-').replace(/[^a-z\d\-]/g, '').replace(/\-+/g, '-').replace(/^\-+|\-+$/g, '').slice(0, 200)
330  name += '.' + options.file.name.split('.').pop()
331  
332  if (options.file) {
333    options.file.cname = this.addPrefix(name)
334  } else {
335    options.cname = this.addPrefix(name)
336  }
337  var that = this
338  var method = 'PUT'
339  var file = options.file || {}
340  // 改动点(添加 || options.file.name自动获取文件名称,否则批量时报错)
341  var fileRename = options.fileRename || name // options.file.lastModified;
342  var fileName = this.addPrefix(this.getFileName(file, fileRename))
343  var putPolicy = options.putPolicy
344  var requestToken = {
345    method: method,
346    file: file,
347    fileName: fileName,
348    putPolicy: putPolicy
349  }
350
351  this.getUFileToken(requestToken, function (token) {
352    var ajax = that.createAjax()
353    var url = that.getBucketUrl() + encodeURIComponent(fileName).replace('%2F', '/')
354    ajax.open(method, url, true)
355    ajax.setRequestHeader('Authorization', token)
356    ajax.setRequestHeader('Content-MD5', that.contentMd5)
357    ajax.setRequestHeader('Content-Type', file.type)
358
359    var onreadystatechange = function () {
360      if (ajax.readyState === 4) {
361        if (ajax.status === 200) {
362          success({
363            msg: ajax.responseText,
364            file: file,
365            code: ajax.status
366          })
367        } else {
368          error({
369            msg: ajax.responseText,
370            file: file,
371            code: ajax.status
372          })
373        }
374      }
375    }
376    var onprogress = function (event) {
377      if (event.lengthComputable) {
378        progress(event.loaded / event.total)
379      }
380    }
381
382    ajax.onreadystatechange = onreadystatechange
383    ajax.upload.onprogress = onprogress
384    ajax.send(file)
385  }, error)
386}
387
388// 批量上传
389UCloudUFile.prototype.batchUpload = function (fileList, success, error, progress) {
390  var self = this
391  var successList = []
392  var errorList = []
393  var currentIndex = 0
394
395  if (fileList.length == 0) {
396    console.warn('批量上传列表为空')
397    return
398  }
399
400  var successCallBack = function (res) {
401    successList.push(res.file)
402    progress(successList.length / fileList.length)
403
404    if (successList.length == fileList.length) {
405      success(successList)
406    } else {
407      currentIndex++
408      self.uploadFile({ file: fileList[currentIndex] }, successCallBack, errorCallBack, progressCallBack)
409    }
410  }
411
412  var errorCallBack = function (res) {
413    // 改动点,解决无缘无故的报错
414    if (res) errorList.push(res.file)
415
416    if ((successList.length + errorList.length) == fileList.length) {
417      error({
418        errorList: errorList,
419        successList: successList
420      })
421    }
422  }
423  var progressCallBack = function (res) {
424
425  }
426
427  progress(0)
428  self.uploadFile({ file: fileList[currentIndex] }, successCallBack, errorCallBack, progressCallBack)
429}
430
431// 分片上传(外部调用)
432UCloudUFile.prototype.sliceUpload = function (options, success, error, progress) {
433  var that = this
434  var file = options.file || {}
435  var fileRename = options.fileRename
436  var fileName = this.addPrefix(this.getFileName(file, fileRename))
437
438  var fileReader = new FileReader()
439  var chunks = Math.ceil(file.size / this.sliceSize)
440  var currentChunk = 0
441
442  // 初始化分片
443  this.initMultipartUpload(function (intResponse) {
444    var keyName = intResponse.Key
445    var uploadId = intResponse.UploadId
446    var partNumber = 0
447    var requestSuccess = 0 // 各分片请求成功数
448    var eTags = ''
449
450    // 每块文件读取完毕之后的处理
451    fileReader.onload = function (e) {
452      currentChunk++
453
454      // 如果文件处理完,调用完成分片;否则还有分片则继续处理
455      if (currentChunk < chunks) {
456        loadNext()
457      }
458    }
459
460    // 处理单片文件的上传
461    function loadNext() {
462      var start = currentChunk * that.sliceSize
463      var end = start + that.sliceSize >= file.size ? file.size : start + that.sliceSize
464      var currentFile = that.slice.call(file, start, end, file.type)
465      currentFile.name = file.name
466
467      // 上传各分片
468      that.multipartUploading(function (multipartResponse) {
469        requestSuccess++
470        if (eTags == '') {
471          eTags = multipartResponse.eTag
472        } else {
473          eTags = eTags + ',' + multipartResponse.eTag
474        }
475
476        var sliceCompleted = {
477          status: 'uploading',
478          value: requestSuccess / chunks
479        }
480        progress(sliceCompleted) // 上传各分片进度
481
482        if (requestSuccess == chunks) {
483          // 完成分片
484          that.multipartUploaded(function (uploaded) {
485            success(uploaded)
486          }, error, progress, keyName, uploadId, file, eTags)
487        }
488      }, error, keyName, uploadId, partNumber, currentFile)
489
490      partNumber++
491      fileReader.readAsArrayBuffer(currentFile)
492    }
493
494    loadNext()
495  }, error, progress, file, fileName)
496}
497
498// 初始化上传(内部调用)
499UCloudUFile.prototype.initMultipartUpload = function (success, error, progress, file, fileName) {
500  var that = this
501  var method = 'POST'
502  var contentType = file.type || '' // application/octet-stream
503  var requestToken = {
504    method: method,
505    file: file,
506    fileName: fileName,
507    md5Required: false
508  }
509
510  this.getUFileToken(requestToken, function (token) {
511    var ajax = that.createAjax()
512    var url = that.getBucketUrl() + encodeURIComponent(fileName).replace('%2F', '/') + '?uploads'
513    ajax.open(method, url, true)
514    ajax.setRequestHeader('Authorization', token)
515    ajax.setRequestHeader('Content-Type', contentType)
516
517    var onreadystatechange = function () {
518      if (ajax.readyState === 4) {
519        if (ajax.status === 200) {
520          success(JSON.parse(ajax.response))
521        } else {
522          error(ajax.responseText)
523        }
524      }
525    }
526    var onprogress = function (event) {
527      if (event.lengthComputable) {
528        var result = {
529          status: 'init',
530          value: event.loaded / event.total
531        }
532        progress(result)
533      }
534    }
535
536    ajax.onreadystatechange = onreadystatechange
537    ajax.upload.onprogress = onprogress
538    ajax.send()
539  }, error)
540}
541
542// 上传分片(内部调用)
543UCloudUFile.prototype.multipartUploading = function (success, error, keyName, uploadId, partNumber, file) {
544  var that = this
545  var method = 'PUT'
546  var requestToken = {
547    method: method,
548    file: file,
549    fileName: keyName,
550    md5Required: false
551  }
552
553  this.getUFileToken(requestToken, function (token) {
554    var ajax = that.createAjax()
555    var url = that.getBucketUrl() + encodeURIComponent(keyName).replace('%2F', '/') +
556      '?uploadId=' + uploadId +
557      '&partNumber=' + partNumber
558    ajax.open(method, url, true)
559    ajax.setRequestHeader('Authorization', token)
560    ajax.setRequestHeader('Content-Type', file.type)
561
562    var onreadystatechange = function () {
563      if (ajax.readyState === 4) {
564        if (ajax.status === 200) {
565          var eTag = ajax.getResponseHeader('ETag')
566          var result = {
567            eTag: eTag.substring(1, eTag.length - 1),
568            response: ajax.response
569          }
570          success(result)
571        } else {
572          error(ajax.responseText)
573        }
574      }
575    }
576
577    ajax.onreadystatechange = onreadystatechange
578    ajax.send(file)
579  }, error)
580}
581
582// 完成分片(内部调用)
583UCloudUFile.prototype.multipartUploaded = function (success, error, progress, keyName, uploadId, file, eTags) {
584  var that = this
585  var method = 'POST'
586  var contentType = file.type || 'application/octet-stream'
587  var requestToken = {
588    method: method,
589    file: file,
590    fileName: keyName,
591    md5Required: false,
592    contentType: contentType
593  }
594
595  this.getUFileToken(requestToken, function (token) {
596    var ajax = that.createAjax()
597    var url = that.getBucketUrl() + encodeURIComponent(keyName).replace('%2F', '/') + '?uploadId=' + uploadId
598
599    ajax.open(method, url, true)
600    ajax.setRequestHeader('Authorization', token)
601    ajax.setRequestHeader('Content-Type', contentType)
602
603    var onreadystatechange = function () {
604      if (ajax.readyState === 4) {
605        if (ajax.status === 200) {
606          success(ajax.responseText)
607        } else {
608          error(ajax.responseText)
609        }
610      }
611    }
612    var onprogress = function (event) {
613      if (event.lengthComputable) {
614        var result = {
615          status: 'uploaded',
616          value: event.loaded / event.total
617        }
618        progress(result)
619      }
620    }
621
622    ajax.onreadystatechange = onreadystatechange
623    ajax.upload.onprogress = onprogress
624    ajax.send(eTags)
625  }, error)
626}
627
628// 表单上传
629UCloudUFile.prototype.formUpload = function (options, success, error) {
630  var that = this
631  var method = 'POST'
632  var file = options.file || {}
633  var fileRename = options.fileRename
634  var fileName = this.addPrefix(this.getFileName(file, fileRename))
635  var putPolicy = options.putPolicy
636
637  var requestToken = {
638    method: method,
639    file: file,
640    fileName: fileName,
641    putPolicy: putPolicy
642  }
643
644  this.getUFileToken(requestToken, function (token) {
645    var ajax = that.createAjax()
646    var url = that.getBucketUrl()
647    var reader = new FileReader()
648
649    // FileReader API是异步的,我们需要把读取到的内容存储下来
650    reader.addEventListener('load', function () {
651      var byteArray = new Uint8Array(reader.result)
652      var fileBinary = ''
653
654      for (var i = 0; i < byteArray.length; i++) {
655        fileBinary += String.fromCharCode(byteArray[i])
656      }
657
658      file.binary = fileBinary
659
660      // 虚拟出Blob格式的fileName
661      var blobFileName = new Blob([fileName])
662      // Blob格式的fileName的FileReader
663      var readerFileName = new FileReader()
664
665      // 取得fileName的特定编码格式
666      readerFileName.addEventListener('load', function () {
667        var innerByteArray = new Uint8Array(readerFileName.result)
668        var innerFileBinary = ''
669
670        for (var i = 0; i < innerByteArray.length; i++) {
671          innerFileBinary += String.fromCharCode(innerByteArray[i])
672        }
673
674        var reFileName = innerFileBinary
675
676        var boundary = '----UCloudPOSTFormBoundary'
677        var data = '--' + boundary + '\r\n' +
678          'Content-Disposition: form-data; ' + 'name="FileName"' + '\r\n' + '\r\n' +
679          reFileName + '\r\n' +
680          '--' + boundary + '\r\n' +
681          'Content-Disposition: form-data; ' + 'name="Authorization"' + '\r\n' + '\r\n' +
682          token + '\r\n' +
683          '--' + boundary + '\r\n' +
684          'Content-Disposition: form-data; ' + 'name="file"; ' + 'filename="' + reFileName + '"' + '\r\n' +
685          'Content-Type: ' + file.type + '\r\n' + '\r\n' +
686          file.binary + '\r\n' +
687          '--' + boundary + '--' + '\r\n'
688
689        ajax.open(method, url, true)
690        ajax.setRequestHeader('Content-MD5', that.contentMd5)
691        ajax.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary)
692
693        var onreadystatechange = function () {
694          if (ajax.readyState == 4) {
695            if (ajax.status == 200) {
696              success(ajax.response)
697            } else {
698              error(ajax.response)
699            }
700          }
701        }
702        ajax.onreadystatechange = onreadystatechange
703        ajax.sendAsBinary(data)
704      })
705
706      // 读取Blob格式的fileName
707      if (blobFileName) {
708        readerFileName.readAsArrayBuffer(blobFileName)
709      }
710    })
711
712    // 读取文件的二进制内容
713    if (file) {
714      reader.readAsArrayBuffer(file)
715    }
716  }, error)
717}
718
719// 秒传文件
720UCloudUFile.prototype.hitUpload = function (file, success, error) {
721  var that = this
722  var method = 'POST'
723
724  this.getFileDetail(this.addPrefix(file.name), function (fileDetail) {
725    var requestToken = {
726      method: method,
727      file: file,
728      md5Required: false
729    }
730
731    that.getUFileToken(requestToken, function (token) {
732      var ajax = that.createAjax()
733      var url = that.getBucketUrl() +
734        'uploadhit?Hash=' + fileDetail.eTag +
735        '&FileName=' + encodeURIComponent(that.addPrefix(file.name)).replace('%2F', '/') +
736        '&FileSize=' + file.size
737      ajax.open(method, url, true)
738      ajax.setRequestHeader('Authorization', token)
739      ajax.setRequestHeader('Content-Type', file.type)
740
741      var onreadystatechange = function () {
742        if (ajax.readyState === 4) {
743          if (ajax.status === 200) {
744            success(ajax.responseText)
745          } else {
746            error(ajax.responseText)
747          }
748        }
749      }
750
751      ajax.onreadystatechange = onreadystatechange
752      ajax.send(file)
753    }, error)
754  }, error)
755}
756
757// 下载文件
758UCloudUFile.prototype.downloadFile = function (fileName, success, error, progress) {
759  var that = this
760  var method = 'GET'
761  var requestToken = {
762    method: method,
763    fileName: fileName
764  }
765
766  this.getUFileToken(requestToken, function (token) {
767    var ajax = that.createAjax()
768    var url = that.getBucketUrl() + encodeURIComponent(fileName).replace('%2F', '/')
769    ajax.open(method, url, true)
770    ajax.responseType = 'blob'
771    ajax.setRequestHeader('Authorization', token)
772
773    var onreadystatechange = function () {
774      if (ajax.readyState == 4) {
775        if (ajax.status == 200) {
776          var aTag = document.createElement('a')
777          var blob = ajax.response
778
779          aTag.download = fileName
780          aTag.href = URL.createObjectURL(blob)
781          aTag.click()
782          URL.revokeObjectURL(blob)
783          success(ajax.response)
784        } else {
785          error(ajax.response)
786        }
787      }
788    }
789    var onprogress = function (event) {
790      if (event.lengthComputable) {
791        progress(event.loaded / event.total)
792      }
793    }
794
795    ajax.onreadystatechange = onreadystatechange
796    ajax.onprogress = onprogress
797    ajax.send()
798  }, error)
799}
800
801// 删除文件
802UCloudUFile.prototype.deleteFile = function (fileName, success, error) {
803  var that = this
804  var method = 'DELETE'
805  var requestToken = {
806    method: method,
807    fileName: fileName
808  }
809
810  this.getUFileToken(requestToken, function (token) {
811    var ajax = that.createAjax()
812    var url = that.getBucketUrl() + encodeURIComponent(fileName).replace('%2F', '/')
813    ajax.open(method, url, true)
814    ajax.setRequestHeader('Authorization', token)
815
816    var onreadystatechange = function () {
817      if (ajax.readyState == 4) {
818        if (ajax.status == 204) {
819          success({
820            msg: ajax.responseText,
821            file: fileName
822          })
823        } else {
824          error({
825            msg: ajax.responseText,
826            file: fileName
827          })
828        }
829      }
830    }
831
832    ajax.onreadystatechange = onreadystatechange
833    ajax.send()
834  }, error)
835}
836
837// 批量删除
838UCloudUFile.prototype.batchDelete = function (fileList, success, error) {
839  var self = this
840  var successList = []
841  var errorList = []
842
843  if (fileList.length == 0) {
844    console.warn('删除列表为空')
845    return
846  }
847
848  for (var i = 0; i < fileList.length; i++) {
849    var successCallBack = function (res) {
850      successList.push(res.file)
851
852      if (successList.length == fileList.length) {
853        success(successList)
854      }
855    }
856
857    var errorCallBack = function (res) {
858      errorList.push(res.file)
859
860      if ((successList.length + errorList.length) == fileList) {
861        error({
862          successList: successList,
863          errorList: errorList
864        })
865      }
866    }
867
868    this.deleteFile(fileList[i], successCallBack, errorCallBack)
869  }
870}
871

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.