xp-multipart.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import mines from "mime-db"
  2. const br = '\r\n'
  3. const br2 = '\r\n\r\n'
  4. const boundary = generateBoundary()
  5. const splitBoundary = "--" + boundary
  6. const commonTypes = {
  7. // 空间复杂度 换取 时间复杂度
  8. // 避免每次上传图片时 getType方法 大量遍历MIME标准,提高性能
  9. }
  10. export let uploadTask = null
  11. export async function multipartUpload({
  12. url,
  13. header,
  14. fields,
  15. files,
  16. success,
  17. fail,
  18. complete
  19. }) {
  20. let data = await toBuffer(fields, files)
  21. uploadTask = wx.request({
  22. url,
  23. data,
  24. method: "POST",
  25. header: {
  26. ...header,
  27. 'Content-Type': 'multipart/form-data; boundary=' + boundary
  28. },
  29. success(s) {
  30. success(s)
  31. },
  32. fail(f) {
  33. fail && fail(f)
  34. },
  35. complete(c) {
  36. complete && complete(c)
  37. }
  38. })
  39. }
  40. /**
  41. * 转换成 ArrayBuffer
  42. * @param {Object} fields
  43. * @param {Object} files
  44. */
  45. async function toBuffer(fields, files) {
  46. const mixUints = []
  47. let fieldHeader = ''
  48. for (const key in fields) {
  49. fieldHeader += getFieldHeader(key, fields[key])
  50. }
  51. mixUints.push(str2Uint8Arr(fieldHeader))
  52. for (const key in files) {
  53. const filePath = files[key]
  54. const fileHeader = getFileHeader(key, filePath)
  55. if (fileHeader == null) continue
  56. mixUints.push(str2Uint8Arr(fileHeader))
  57. const fileUint8Arr = await file2Uint8Arr(filePath)
  58. mixUints.push(fileUint8Arr)
  59. mixUints.push(str2Uint8Arr(br))
  60. }
  61. mixUints.push(str2Uint8Arr(splitBoundary + "--")) //结尾
  62. return convert2Buffer(mixUints)
  63. }
  64. /**
  65. * mix数组转换成 arraybuffer
  66. * @param {Object} mixUints
  67. */
  68. function convert2Buffer(mixUints) {
  69. const len = mixUints.reduce((prev, cur) => {
  70. return prev + cur.length
  71. }, 0)
  72. const arrayBuffer = new ArrayBuffer(len)
  73. const buffer = new Uint8Array(arrayBuffer)
  74. let sum = 0
  75. for (let i = 0; i < mixUints.length; i++) {
  76. for (let j = 0; j < mixUints[i].length; j++) {
  77. buffer[sum + j] = mixUints[i][j]
  78. }
  79. sum += mixUints[i].length
  80. }
  81. return arrayBuffer
  82. }
  83. /**
  84. * 生成普通字段boundary串
  85. * @param {Object} key
  86. * @param {Object} val
  87. */
  88. function getFieldHeader(key, val) {
  89. return `${splitBoundary}${br}Content-Disposition: form-data; name="${key}"${br2}${val}${br}`
  90. }
  91. /**
  92. * 生成图片字段boundary串
  93. * @param {Object} name
  94. * @param {Object} filePath
  95. */
  96. function getFileHeader(name, filePath) {
  97. const contentType = getType(filePath)
  98. if (contentType == null) return null;
  99. const filename = filePath.replace(/^(.*)\/(.*)/, "$2")
  100. return `${splitBoundary}${br}Content-Disposition: form-data; name="${name}"; filename="${filename}"${br}Content-Type: ${contentType}${br2}`
  101. }
  102. /**
  103. * 生成boundary
  104. */
  105. function generateBoundary() {
  106. let boundary = "----WebKitFormBoundary"
  107. const chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';
  108. const len = chars.length
  109. for (let i = 0; i < 16; i++) {
  110. boundary += chars.charAt(~~(Math.random() * len));
  111. }
  112. return boundary;
  113. }
  114. /**
  115. * 字符串转 Uint8Array
  116. * @param {String} str
  117. */
  118. function str2Uint8Arr(str) {
  119. let bytes = [];
  120. for (let i = 0; i < str.length; i++) {
  121. let code = str.charCodeAt(i);
  122. if (0x00 <= code && code <= 0x7f) {
  123. bytes.push(code);
  124. } else if (0x80 <= code && code <= 0x7ff) {
  125. bytes.push((192 | (31 & (code >> 6))));
  126. bytes.push((128 | (63 & code)))
  127. } else if ((0x800 <= code && code <= 0xd7ff) || (0xe000 <= code && code <= 0xffff)) {
  128. bytes.push((224 | (15 & (code >> 12))));
  129. bytes.push((128 | (63 & (code >> 6))));
  130. bytes.push((128 | (63 & code)))
  131. }
  132. }
  133. for (let i = 0; i < bytes.length; i++) {
  134. bytes[i] &= 0xff;
  135. }
  136. return bytes
  137. }
  138. /**
  139. * 文件转 Uint8Array
  140. * @param {String} filePath
  141. */
  142. function file2Uint8Arr(filePath) {
  143. return new Promise(resolve => {
  144. wx.getFileSystemManager().readFile({
  145. filePath,
  146. success(res) {
  147. resolve(new Uint8Array(res.data))
  148. },
  149. fail(err) {
  150. console.error(err.errMsg)
  151. }
  152. })
  153. })
  154. }
  155. /**
  156. * 获取文件类型
  157. * @param {Object} url
  158. */
  159. function getType(url) {
  160. if (!url) return null
  161. const index = url.lastIndexOf(".");
  162. const ext = url.substr(index + 1);
  163. if (commonTypes.hasOwnProperty(ext)) {
  164. return commonTypes[ext]
  165. }
  166. for (let k in mines) {
  167. if (mines[k].extensions === undefined) continue
  168. if (mines[k].extensions.indexOf(ext) !== -1) {
  169. commonTypes[ext] = k
  170. return k
  171. }
  172. }
  173. return null
  174. }