aetherupload.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. var AetherUpload = {
  2. upload: function () {
  3. $.ajaxSetup({
  4. headers: {
  5. 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
  6. }
  7. });
  8. this.resourceDom = this.wrapperDom.find('#aetherupload-resource'),
  9. this.outputDom = this.wrapperDom.find('#aetherupload-output'),
  10. this.progressBarDom = this.wrapperDom.find('#aetherupload-progressbar'),
  11. this.resource = this.resourceDom[0].files[0],
  12. this.resourceName = this.resource.name,
  13. this.resourceSize = this.resource.size,
  14. this.resourceTempBaseName = '',
  15. this.resourceExt = '',
  16. this.chunkSize = 0,
  17. this.chunkCount = 0,
  18. this.groupSubDir = '',
  19. this.savedPath = '',
  20. this.resourceHash = '',
  21. this.blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
  22. this.i = 0,
  23. this.locale,
  24. this.messages = this.getLocalizedMessages(),
  25. this.storageHost = $('#aetherupload-storage-host').length ? $('#aetherupload-storage-host').val() : '';
  26. if (!this.blobSlice) {
  27. this.outputDom.text(this.messages.error_unsupported_browser);
  28. return;
  29. }
  30. if (this.resourceSize === 0) {
  31. this.outputDom.text(this.messages.error_invalid_resource_size);
  32. return;
  33. }
  34. if (this.resourceName.substring(this.resourceName.lastIndexOf('.') + 1, this.resourceName.length) === '') {
  35. this.outputDom.text(this.messages.error_invalid_resource_type);
  36. return;
  37. }
  38. this.outputDom.text(this.messages.status_upload_begin);
  39. if (!('FileReader' in window) || !('File' in window) || typeof SparkMD5 === 'undefined') {
  40. this.preprocess(); //浏览器不支持读取本地文件,跳过计算hash
  41. } else if (this.laxMode === true) {
  42. this.preprocess(); //宽松模式,跳过计算hash
  43. } else {
  44. this.calculateHash();
  45. }
  46. },
  47. calculateHash: function () { //计算hash
  48. var _this = this,
  49. clientChunkSize = 4000000,
  50. chunks = Math.ceil(_this.resource.size / clientChunkSize),
  51. currentChunk = 0,
  52. spark = new SparkMD5.ArrayBuffer(),
  53. fileReader = new FileReader();
  54. fileReader.onload = function (e) {
  55. spark.append(e.target.result);
  56. ++currentChunk;
  57. _this.outputDom.text(_this.messages.status_hashing + ' ' + parseInt(currentChunk / chunks * 100) + '%');
  58. if (currentChunk < chunks) {
  59. loadNext();
  60. } else {
  61. _this.resourceHash = spark.end();
  62. _this.preprocess();
  63. }
  64. };
  65. fileReader.onerror = function () {
  66. _this.preprocess();
  67. };
  68. function loadNext() {
  69. var start = currentChunk * clientChunkSize,
  70. end = start + clientChunkSize >= _this.resource.size ? _this.resource.size : start + clientChunkSize;
  71. fileReader.readAsArrayBuffer(_this.blobSlice.call(_this.resource, start, end));
  72. }
  73. loadNext();
  74. },
  75. preprocess: function () { //预处理
  76. var _this = this;
  77. $.ajax({
  78. url: _this.storageHost + _this.preprocessRoute,
  79. type: 'POST',
  80. dataType: 'json',
  81. xhrFields: {
  82. withCredentials: true
  83. },
  84. crossDomain: true,
  85. data: {
  86. resource_name: _this.resourceName,
  87. resource_size: _this.resourceSize,
  88. resource_hash: _this.resourceHash,
  89. locale: _this.locale,
  90. group: _this.group
  91. },
  92. success: function (rst) {
  93. if (rst.error) {
  94. _this.outputDom.text(rst.error);
  95. return;
  96. }
  97. _this.resourceTempBaseName = rst.resourceTempBaseName;
  98. _this.resourceExt = rst.resourceExt;
  99. _this.chunkSize = rst.chunkSize;
  100. _this.chunkCount = Math.ceil(_this.resourceSize / _this.chunkSize);
  101. _this.groupSubDir = rst.groupSubDir;
  102. if (rst.savedPath.length === 0) {
  103. _this.uploadChunk();
  104. } else {
  105. _this.progressBarDom.css('width', '100%');
  106. _this.savedPath = rst.savedPath;
  107. _this.savedPathDom.val(_this.savedPath);
  108. _this.resourceDom.attr('disabled', 'disabled');
  109. _this.outputDom.text(_this.messages.status_instant_completion_success);
  110. typeof(_this.callback) !== 'undefined' ? _this.callback() : null;
  111. }
  112. },
  113. error: function (XMLHttpRequest, textStatus, errorThrown) {
  114. _this.outputDom.text(_this.messages.error_upload_fail);
  115. }
  116. });
  117. },
  118. uploadChunk: function () {
  119. var _this = this,
  120. start = this.i * this.chunkSize,
  121. end = Math.min(this.resourceSize, start + this.chunkSize),
  122. form = new FormData();
  123. form.append('resource_chunk', this.resource.slice(start, end));
  124. form.append('resource_ext', this.resourceExt);
  125. form.append('chunk_total', this.chunkCount);
  126. form.append('chunk_index', this.i + 1);
  127. form.append('resource_temp_basename', this.resourceTempBaseName);
  128. form.append('group', this.group);
  129. form.append('group_subdir', this.groupSubDir);
  130. form.append('locale', this.locale);
  131. form.append('resource_hash', this.resourceHash);
  132. $.ajax({
  133. url: _this.storageHost + _this.uploadingRoute,
  134. type: 'POST',
  135. data: form,
  136. dataType: 'json',
  137. xhrFields: {
  138. withCredentials: true
  139. },
  140. cache: false,
  141. crossDomain: true,
  142. processData: false,
  143. contentType: false,
  144. success: function (rst) {
  145. if ((rst instanceof Object) !== true) {
  146. _this.outputDom.text(_this.messages.error_invalid_server_return);
  147. return;
  148. }
  149. if (rst.error === 'undefined' || rst.error) {
  150. _this.outputDom.text(rst.error);
  151. return;
  152. }
  153. var percent = parseInt((_this.i + 1) / _this.chunkCount * 100);
  154. _this.progressBarDom.css('width', percent + '%');
  155. _this.outputDom.text(_this.messages.status_uploading + ' ' + percent + '%');
  156. if (rst.savedPath !== 'undefined' && rst.savedPath !== '') {
  157. _this.savedPath = rst.savedPath;
  158. _this.savedPathDom.val(_this.savedPath);
  159. _this.resourceDom.attr('disabled', 'disabled');
  160. _this.outputDom.text(_this.messages.status_upload_succeed);
  161. _this.progressBarDom.css('width', '100%');
  162. typeof(_this.callback) !== 'undefined' ? _this.callback() : null;
  163. } else {
  164. ++_this.i;
  165. _this.uploadChunk();
  166. }
  167. },
  168. error: function (XMLHttpRequest, textStatus, errorThrown) {
  169. if (XMLHttpRequest.status === 0) {
  170. _this.outputDom.text(_this.messages.status_retrying);
  171. _this.sleep(5000);
  172. _this.uploadChunk();
  173. } else {
  174. _this.outputDom.text(_this.messages.error_upload_fail);
  175. }
  176. }
  177. });
  178. },
  179. sleep: function (milliSecond) {
  180. var wakeUpTime = new Date().getTime() + milliSecond;
  181. while (true) {
  182. if (new Date().getTime() > wakeUpTime) {
  183. return;
  184. }
  185. }
  186. },
  187. success: function (callback) {
  188. this.callback = callback;
  189. return this;
  190. },
  191. setPreprocessRoute: function (route) {
  192. this.preprocessRoute = route;
  193. return this;
  194. },
  195. setUploadingRoute: function (route) {
  196. this.uploadingRoute = route;
  197. return this;
  198. },
  199. setGroup: function (group) {
  200. this.group = group;
  201. return this;
  202. },
  203. setSavedPathField: function (selector) {
  204. this.savedPathDom = $(selector);
  205. return this;
  206. },
  207. setLaxMode: function (isLax) {
  208. this.laxMode = isLax;
  209. return this;
  210. },
  211. getLocalizedMessages: function () {
  212. var lang = navigator.language ? navigator.language : navigator.browserLanguage;
  213. var locales = Object.getOwnPropertyNames(this.text);
  214. for (var k in locales) {
  215. if (lang.indexOf(locales[k]) > -1) {
  216. this.locale = locales[k];
  217. return this.text[this.locale];
  218. }
  219. }
  220. this.locale = 'en';
  221. return this.text[this.locale];
  222. },
  223. text: {
  224. en: {
  225. status_upload_begin: 'upload begin',
  226. error_unsupported_browser: 'Error: unsupported browser',
  227. status_hashing: 'hashing',
  228. status_instant_completion_success: 'upload succeed (instant completion) ',
  229. status_uploading: 'uploading',
  230. status_upload_succeed: 'upload succeed',
  231. status_retrying: 'network problem, retrying...',
  232. error_upload_fail: 'Error: upload fail',
  233. error_invalid_server_return: 'Error: invalid server return value',
  234. error_invalid_resource_size: 'Error: invalid resource size',
  235. error_invalid_resource_type: 'Error: invalid resource type'
  236. },
  237. zh: {
  238. status_upload_begin: '开始上传',
  239. error_unsupported_browser: '错误:上传组件不被此浏览器支持',
  240. status_hashing: '正在哈希',
  241. status_instant_completion_success: '上传成功(秒传)',
  242. status_uploading: '正在上传',
  243. status_upload_succeed: '上传成功',
  244. status_retrying: '网络故障,正在重试……',
  245. error_upload_fail: '错误:上传失败',
  246. error_invalid_server_return: '错误:无效的服务器返回值',
  247. error_invalid_resource_size: '错误:无效的文件大小',
  248. error_invalid_resource_type: '错误:无效的文件类型'
  249. }
  250. }
  251. };
  252. /*
  253. * 创建AetherUpload对象的全局方法
  254. * resource 文件对象
  255. * group 分组名
  256. */
  257. function aetherupload(resource) {
  258. var newInstance = Object.create(AetherUpload);
  259. newInstance.wrapperDom = $(resource).parents('#aetherupload-wrapper');
  260. newInstance.group = 'file'; //分组的默认值
  261. newInstance.savedPathDom = newInstance.wrapperDom.find('#aetherupload-savedpath'); //资源储存地址所在节点的默认值
  262. newInstance.preprocessRoute = '/aetherupload/preprocess'; //预处理路由的默认值
  263. newInstance.uploadingRoute = '/aetherupload/uploading'; //上传路由的默认值
  264. newInstance.laxMode = false; //宽松模式
  265. return newInstance;
  266. }