当前位置:   article > 正文

amis 文件上传 & 大文件分块上传

amis 文件上传 & 大文件分块上传

amis 图片/文件上传组件

receiver:参数配置为上传接口。

  1. {
  2.   "type": "input-image", // "type": "input-file",
  3.   "label": "照片",
  4.   "name": "url"
  5.   "imageClassName": "r w-full",
  6.   "receiver": "/lbserver/api/FileUpload/upload/mPersonnelInfo/Images/${TIMESTAMP(NOW(),'x')}",
  7.   "accept": ".jpeg, .jpg, .png, .gif",
  8.   "fixedSize": false,
  9.   "hideUploadButton": false,
  10.   "autoUpload": true,
  11.   "compress": false,
  12.   "compressOptions": {},
  13.   "crop": false
  14. }

amis分块上传:

分块上传所需的处理如下流程图所示:

文件上传文件如果过大的话,如果不加任何处理,这个请求就会一直处于PENDING状态(最后肯定是超时的)

pending(挂起):网络处于挂起状态,指发送的请求是“进行中”的状态,但还没有接到服务端的响应,一旦服务端做出响应,时间将被更新为总运行时间。

0、前端amis分片逻辑如下:(了解即可,一般分片逻辑无需自己实现,用现成组件库)

• 由于前端已有 Blob Api 能操作文件二进制,因此最核心逻辑就是前端运用 Blob Api 对大文件进行文件分片切割,将一个大文件切成一个个小文件,然后将这些分片文件一个个上传。

• 现在 http 请求基本是 1.1 版本,浏览器能够同时进行多个请求,通过Promise进行异步并发控制处理。

• 当前端将所有分片上传完成之后,前端再通知后端进行分片合并成文件。

amis/src/renderers/Form/InputFile.tsx

  1. //调用startChunkApi 成功后执行startChunk进行分块
  2. self._send(file, startApi).then(startChunk).catch(reject);
  3. async function startChunk(ret: Payload) {
  4. onProgress(startProgress);
  5. const tasks = getTasks(file); //根据chunkSize分块大小(默认5M)生成分块任务集合
  6. progressArr = tasks.map(() => 0);
  7. if (!ret.data) {
  8. throw new Error(__('File.uploadFailed'));
  9. }
  10. state = {
  11. key: (ret.data as any).key,
  12. uploadId: (ret.data as any).uploadId,
  13. loaded: 0,
  14. total: tasks.length
  15. };
  16. let results: any[] = [];
  17. while (tasks.length) {
  18. const res = await Promise.all(
  19. tasks.splice(0, concurrency).map(async task => {//根据concurrency 控制并行上传数量,默认是 3
  20. return await uploadPartFile(state, config)(task); //Blob.slice API进行分块 并调用chunkApi上传
  21. })
  22. );
  23. results = results.concat(res);
  24. }
  25. finishChunk(results, state);//finishChunkApi 结束分片
  26. }

1.amis分块上传参数配置

Amis上传组件如果文件过大,则可能需要使用分块上传,默认大于 5M(chunkSize 配置决定) 的文件是会自动开启,可以通过 useChunk 配置成 false 关闭。(不要手动配置useChunk:true,会导致只使用chunk切片上传)

  1. {
  2. "type": "input-file",
  3. "id": "u:dbd914e494e9",
  4. "label": "File",
  5. "name": "file",
  6. "autoUpload": true,
  7. "uploadType": "fileReceptor",
  8. "accept": "*",
  9. "receiver": "/lbserver/api/FileUpload/upload/mProjectInfo/Images/${TIMESTAMP(NOW(),'x')}",
  10. "startChunkApi": "/lbserver/api/FileUpload/startChunkApi",
  11. "chunkApi": "/lbserver/api/FileUpload/chunkApi/upload/mProjectInfo/Images",
  12. "finishChunkApi": "/lbserver/api/FileUpload/finishChunkApi/upload/mProjectInfo/Images",
  13. "hidden": false,
  14. "btnLabel": "文件上传",
  15. "submitType": "asUpload"
  16. }

2.分块上传相关的三个后端接口(loopback4.0框架 文件上传基于multer):

multer中间件只处理 multipart/form-data 类型的表单数据的函数,主要用于上传文件。

Multer在解析完请求体后,会向request对象中添加一个body对象和一个file或files对象(上传多个文件时使用files对象 )。其中,body对象中包含所提交表单中的文本字段(如果有),而file(或files)对象中包含通过表单上传的文件。

  1. import { inject, service } from '@loopback/core';
  2. import {
  3. del,
  4. get,
  5. getModelSchemaRef,
  6. param,
  7. patch,
  8. post,
  9. Request,
  10. requestBody,
  11. response,
  12. Response,
  13. RestBindings,
  14. } from '@loopback/rest';
  15. import _ from 'lodash';
  16. import { FILE_UPLOAD_SERVICE } from '../../keys';
  17. import { FileUploadHandler } from '../../types';
  18. const moment = require('moment');
  19. const SparkMD5 = require('spark-md5');
  20. const util = require('util');
  21. const mime = require('mime');
  22. const fs = require('fs-extra');
  23. const path = require('path');
  24. const child_process = require('child_process');
  25. function getFilesAndFields(request: Request) {
  26. const uploadedFiles = request.files;
  27. const mapper = (f: globalThis.Express.Multer.File) => ({
  28. fieldname: f.fieldname,
  29. originalname:
  30. request.body && request.body.key && request.body.partNumber
  31. ? `${request.body.key}-${request.body.partNumber}`
  32. : f.originalname,
  33. encoding: f.encoding,
  34. mimetype: f.mimetype,
  35. size: f.size,
  36. });
  37. let files: object[] = [];
  38. if (Array.isArray(uploadedFiles)) {
  39. files = uploadedFiles.map(mapper);
  40. } else {
  41. for (const filename in uploadedFiles) {
  42. files.push(...uploadedFiles[filename].map(mapper));
  43. }
  44. }
  45. return { files, fields: request.body };
  46. }
  47. export class FileUploadController {
  48. constructor(
  49. @inject(FILE_UPLOAD_SERVICE) private handler: FileUploadHandler,
  50. ) { }
  51. @post(`FileUpload/startChunkApi`)
  52. @response(200, {
  53. description: 'FileUpload model instance',
  54. content: { 'application/json': { schema: getModelSchemaRef(FileUpload) } },
  55. })
  56. async startChunkApi(@requestBody() pl: any): Promise<any> {
  57. let uploadId = generateUUID();
  58. let key = `${moment().format('X')}-${pl.filename}`;
  59. return {
  60. status: 0,
  61. data: {
  62. date: new Date(),
  63. uploadId: uploadId,
  64. key: key,
  65. },
  66. };
  67. }
  68. @post(`FileUpload/chunkApi/{upload}/{model}/{type}`)
  69. @response(200, {
  70. description: 'FileUpload model instance',
  71. content: { 'application/json': { schema: getModelSchemaRef(FileUpload) } },
  72. })
  73. async chunkApi(
  74. @param.path.string('upload') upload: string,
  75. @param.path.string('model') model: string,
  76. @param.path.string('type') type: string,
  77. @requestBody.file()
  78. request: Request,
  79. @inject(RestBindings.Http.RESPONSE) response: Response,
  80. ): Promise<any> {
  81. // console.log(model, type);
  82. return new Promise<any>((resolve, reject) => {
  83. this.handler(request, response, err => {
  84. if (err) reject(err);
  85. else {
  86. let uploadId = request.body.uploadId; // id
  87. // let key = request.body.key;
  88. // let partNumber = request.body.partNumber;
  89. const f = getFilesAndFields(request);
  90. if (f.files && f.files.length > 0) {
  91. for (const i in f.files) {
  92. const m = f.files[i] as any;
  93. fs.mkdirpSync(
  94. path.resolve(`./public/${upload}/${model}/${type}/${uploadId}`),
  95. );
  96. const o_file = `./.sandbox/${m.originalname}`;
  97. let eTag = SparkMD5.hashBinary(fs.readFileSync(o_file, 'binary')); //不指定编码 返回buffer对象
  98. const m_file = `./public/${upload}/${model}/${type}/${uploadId}/${m.originalname}`;
  99. fs.rename(o_file, m_file, function (err: any) {
  100. if (err) {
  101. child_process.execSync(`mv ${o_file} ${m_file}`);
  102. console.log(err);
  103. }
  104. });
  105. const result = {
  106. name: m.originalname,
  107. eTag: eTag,
  108. };
  109. resolve({
  110. status: 0,
  111. msg: '',
  112. data: result,
  113. });
  114. }
  115. }
  116. }
  117. });
  118. });
  119. }
  120. @post(`FileUpload/finishChunkApi/{upload}/{model}/{type}`)
  121. @response(200, {
  122. description: 'FileUpload model instance',
  123. content: { 'application/json': { schema: getModelSchemaRef(FileUpload) } },
  124. })
  125. async finishChunkApi(
  126. @param.path.string('upload') upload: string,
  127. @param.path.string('model') model: string,
  128. @param.path.string('type') type: string,
  129. @requestBody() pl: any,
  130. ): Promise<any> {
  131. let uploadId = pl.uploadId;
  132. let key = pl.key;
  133. let partList = pl.partList;
  134. let pathurl = `/${upload}/${model}/${type}/${key}`;
  135. const m_dir = `./public/${upload}/${model}/${type}/${uploadId}`;
  136. const filePath = `./public/${upload}/${model}/${type}/${key}`;
  137. // console.log(uploadId, key, partList, pathurl, " asdasd")
  138. let self = this;
  139. let size = 0;
  140. function mergeFile(dirPath: string, filePath: string, partList: any) {
  141. let total = partList.length;
  142. return new Promise((resolve, reject) => {
  143. fs.readdir(dirPath, (err: any, files: any) => {
  144. if (err) {
  145. return reject(err);
  146. }
  147. if (files.length !== total || !files.length) {
  148. return reject('上传失败,切片数量不符');
  149. }
  150. function merge(i: number) {
  151. // 合并完成
  152. if (i === files.length) {
  153. fs.rmdir(dirPath, (err: any) => {
  154. console.log(err, 'rmdir');
  155. });
  156. let date = new Date();
  157. let m = {
  158. originalname: pl.filename,
  159. path: pathurl,
  160. timestamp: date,
  161. size: size,
  162. };
  163. return resolve({
  164. status: 0,
  165. data: {
  166. date: date,
  167. value: pathurl,
  168. url: pathurl,
  169. },
  170. });
  171. }
  172. let chunkpath = `${dirPath}/${key}-${i + 1}`;
  173. // console.log(chunkpath, 'chunkpath');
  174. fs.readFile(chunkpath, 'binary', (err: any, data: any) => {
  175. // console.log(data.length);
  176. size += data.length;
  177. let eTag = SparkMD5.hashBinary(data);
  178. if (_.find(partList, { partNumber: i + 1 }).eTag !== eTag) {
  179. return reject('上传失败,切片内容不符');
  180. }
  181. // 将切片追加到存储文件
  182. fs.appendFile(filePath, data, { encoding: 'binary' }, () => {
  183. // 删除切片文件
  184. fs.unlink(chunkpath, () => {
  185. // 递归合并
  186. merge(i + 1);
  187. });
  188. });
  189. });
  190. }
  191. merge(0);
  192. });
  193. });
  194. }
  195. try {
  196. return await mergeFile(m_dir, filePath, partList);
  197. } catch (err) {
  198. fs.rmdir(m_dir, { recursive: true }, (err: any) => {
  199. console.log(err);
  200. }); //出错后重新上传
  201. return {
  202. status: -1,
  203. msg: err,
  204. };
  205. }
  206. }
  207. }

file-upload.sevice.ts:

  1. import {
  2. BindingScope,
  3. config,
  4. ContextTags,
  5. injectable,
  6. Provider,
  7. } from '@loopback/core';
  8. import multer from 'multer';
  9. import {FILE_UPLOAD_SERVICE} from '../keys';
  10. import {FileUploadHandler} from '../types';
  11. /**
  12. * A provider to return an `Express` request handler from `multer` middleware
  13. */
  14. @injectable({
  15. scope: BindingScope.TRANSIENT,
  16. tags: {[ContextTags.KEY]: FILE_UPLOAD_SERVICE},
  17. })
  18. export class FileUploadProvider implements Provider<FileUploadHandler> {
  19. constructor(@config() private options: multer.Options = {}) {
  20. if (!this.options.storage) {
  21. // Default to in-memory storage
  22. this.options.storage = multer.memoryStorage();
  23. }
  24. }
  25. value(): FileUploadHandler {
  26. return multer(this.options).any();
  27. }
  28. }

application.ts:

  1. import { BootMixin } from '@loopback/boot';
  2. import { ApplicationConfig } from '@loopback/core';
  3. import { RepositoryMixin } from '@loopback/repository';
  4. import { RestApplication, RestBindings } from '@loopback/rest';
  5. import { ServiceMixin } from '@loopback/service-proxy';
  6. import multer from 'multer';
  7. import path from 'path';
  8. import { FILE_UPLOAD_SERVICE, STORAGE_DIRECTORY } from './keys';
  9. export class LbSmartApplication extends BootMixin(
  10. ServiceMixin(RepositoryMixin(RestApplication)),
  11. ) {
  12. constructor(options: ApplicationConfig = {}) {
  13. super(options);
  14. //...省略
  15. this.configureFileUpload(options.fileStorageDirectory);
  16. };
  17. /**
  18. * Configure `multer` options for file upload
  19. */
  20. protected configureFileUpload(destination?: string) {
  21. // Upload files to `dist/.sandbox` by default
  22. destination = destination ?? path.join(__dirname, '../.sandbox');
  23. this.bind(STORAGE_DIRECTORY).to(destination);
  24. const multerOptions: multer.Options = {
  25. storage: multer.diskStorage({
  26. destination,
  27. // Use the original file name as is
  28. filename: (req, file, cb) => {
  29. file.originalname = Buffer.from(file.originalname, "latin1").toString( "utf8");
  30. let originalname = file.originalname;
  31. if (req.body && req.body.key && req.body.partNumber) {
  32. originalname = `${req.body.key}-${req.body.partNumber}`;
  33. }
  34. cb(null, originalname);
  35. },
  36. }),
  37. };
  38. // Configure the file upload service with multer options
  39. this.configure(FILE_UPLOAD_SERVICE).to(multerOptions);
  40. }
  41. }

额外:加密算法介绍

信息安全领域,经常会用到MD5、SHA1、SHA256算法。这三种算法都属于散列算法,或者叫作哈希算法。它们具有输入任意长度,输出长度固定,以及单向性(无法根据散列值还原出消息)的特点。

关于MD5

MD5是一个安全散列算法,输入两个不同的明文不会得到相同的输出值,根据输出值,不能得到原始的明文,即其过程是不可逆的。所以要解密MD5没有现成的算法,只能穷举法,把可能出现的明文,用MD5算法散列之后,把得到的散列值和原始的数据形成一个一对一的映射表,通过匹配从映射表中找出破解密码所对应的原始明文。

关于SHA1

SHA1是一种密码散列函数,可以生成一个被称为消息摘要的160位(20字节)散列值,散列值通常的呈现形式为40个十六进制数。该算法输入报文的长度不限,产生的输出是一个160位的报文摘要。输入是按512 位的分组进行处理的。SHA-1是不可逆的、防冲突,并具有良好的雪崩效应。

关于SHA256

sha256是一种密码散列函数,也可以说是哈希函数。对于任意长度的消息,SHA256都会产生一个256bit长度的散列值,称为消息摘要,可以用一个长度为64的十六进制字符串表示。sha256是SHA-2下细分出的一种算法。SHA-2下又可再分为六个不同的算法标准,包括了:SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。

关于RSA

是典型的非对称加密算法(对称加密算法又称传统加密算法。 加密和解密使用同一个密钥),主要具有加密解密、数字签名和加签验签的功能。

加密解密:私钥解密,公钥加密。  数字签名-俗称加签验签:私钥加签,公钥验签。 

MD5、SHA1、SHA256有哪些区别?

相同点:

都是密码散列函数,加密不可逆;

都可以实现对任何长度对象加密,都不能防止碰撞;

不同点:

1、校验值的长度不同,MD5校验位的长度是16个字节(128位);SHA1是20个字节(160位);SHA256是32个字节(256位)。

2、运行速度不同,SHA256的运行速度最慢,然后是SHA1,最后是MD5。

MD5、SHA1、SHA256安全性如何?

  在安全性方面,SHA256的安全性最高,然后是SHA1,最后是MD5。虽然SHA256的安全性比较高,但是耗时要比其他两种多很多。

md5、SHA1、SHA256不能解密吗?

  SHA256是目前比较流行的计算机算法之一,相对md5和SHA1而言,SHA256很安全。SHA256是牢不可破的函数,它的256位密钥从未被泄露过。而MD5就不一样了,单纯使用比较容易遭到撞库攻击。通过预先计算知道MD5的对应关系,存在数据库中,然后使用的时候反查,MD5就可能被解密。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/650555
推荐阅读
相关标签
  

闽ICP备14008679号