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

amis 图片/文件上传组件

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

{
  "type": "input-image", // "type": "input-file",
  "label": "照片",
  "name": "url", 
  "imageClassName": "r w-full",
  "receiver": "/lbserver/api/FileUpload/upload/mPersonnelInfo/Images/${TIMESTAMP(NOW(),'x')}",
  "accept": ".jpeg, .jpg, .png, .gif",
  "fixedSize": false,
  "hideUploadButton": false,
  "autoUpload": true,
  "compress": false,
  "compressOptions": {},
  "crop": false
}

amis分块上传:

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

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

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

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

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

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

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

amis/src/renderers/Form/InputFile.tsx

      //调用startChunkApi 成功后执行startChunk进行分块
      self._send(file, startApi).then(startChunk).catch(reject); 

      async function startChunk(ret: Payload) {
        onProgress(startProgress);
        const tasks = getTasks(file); //根据chunkSize分块大小(默认5M)生成分块任务集合
        progressArr = tasks.map(() => 0);

        if (!ret.data) {
          throw new Error(__('File.uploadFailed'));
        }

        state = {
          key: (ret.data as any).key,
          uploadId: (ret.data as any).uploadId,
          loaded: 0,
          total: tasks.length
        };

        let results: any[] = [];
        while (tasks.length) {
          const res = await Promise.all(
            tasks.splice(0, concurrency).map(async task => {//根据concurrency 控制并行上传数量,默认是 3
              return await uploadPartFile(state, config)(task); //Blob.slice API进行分块 并调用chunkApi上传
            })
          );
          results = results.concat(res);
        }
        finishChunk(results, state);//finishChunkApi 结束分片
      }

1.amis分块上传参数配置

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

{
  "type": "input-file",
  "id": "u:dbd914e494e9",
  "label": "File",
  "name": "file",
  "autoUpload": true,
  "uploadType": "fileReceptor",
  "accept": "*",
  "receiver": "/lbserver/api/FileUpload/upload/mProjectInfo/Images/${TIMESTAMP(NOW(),'x')}",
  "startChunkApi": "/lbserver/api/FileUpload/startChunkApi",
  "chunkApi": "/lbserver/api/FileUpload/chunkApi/upload/mProjectInfo/Images",
  "finishChunkApi": "/lbserver/api/FileUpload/finishChunkApi/upload/mProjectInfo/Images",
  "hidden": false,
  "btnLabel": "文件上传",
  "submitType": "asUpload"
}

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

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

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

import { inject, service } from '@loopback/core';
import {
  del,
  get,
  getModelSchemaRef,
  param,
  patch,
  post,
  Request,
  requestBody,
  response,
  Response,
  RestBindings,
} from '@loopback/rest';
import _ from 'lodash';
import { FILE_UPLOAD_SERVICE } from '../../keys';
import { FileUploadHandler } from '../../types';

const moment = require('moment');
const SparkMD5 = require('spark-md5');
const util = require('util');
const mime = require('mime');
const fs = require('fs-extra');
const path = require('path');
const child_process = require('child_process');

function getFilesAndFields(request: Request) {
  const uploadedFiles = request.files;
  const mapper = (f: globalThis.Express.Multer.File) => ({
    fieldname: f.fieldname,
    originalname:
      request.body && request.body.key && request.body.partNumber
        ? `${request.body.key}-${request.body.partNumber}`
        : f.originalname,
    encoding: f.encoding,
    mimetype: f.mimetype,
    size: f.size,
  });
  let files: object[] = [];
  if (Array.isArray(uploadedFiles)) {
    files = uploadedFiles.map(mapper);
  } else {
    for (const filename in uploadedFiles) {
      files.push(...uploadedFiles[filename].map(mapper));
    }
  }
  return { files, fields: request.body };
}


export class FileUploadController {
  constructor(
    @inject(FILE_UPLOAD_SERVICE) private handler: FileUploadHandler,
  ) { }

  @post(`FileUpload/startChunkApi`)
  @response(200, {
    description: 'FileUpload model instance',
    content: { 'application/json': { schema: getModelSchemaRef(FileUpload) } },
  })
  async startChunkApi(@requestBody() pl: any): Promise<any> {
    let uploadId = generateUUID();
    let key = `${moment().format('X')}-${pl.filename}`;
    return {
      status: 0,
      data: {
        date: new Date(),
        uploadId: uploadId,
        key: key,
      },
    };
  }

  @post(`FileUpload/chunkApi/{upload}/{model}/{type}`)
  @response(200, {
    description: 'FileUpload model instance',
    content: { 'application/json': { schema: getModelSchemaRef(FileUpload) } },
  })
  async chunkApi(
    @param.path.string('upload') upload: string,
    @param.path.string('model') model: string,
    @param.path.string('type') type: string,
    @requestBody.file()
    request: Request,
    @inject(RestBindings.Http.RESPONSE) response: Response,
  ): Promise<any> {
    // console.log(model, type);
    return new Promise<any>((resolve, reject) => {
      this.handler(request, response, err => {
        if (err) reject(err);
        else {
          let uploadId = request.body.uploadId; // id
          // let key = request.body.key;
          // let partNumber = request.body.partNumber;
          const f = getFilesAndFields(request);
          if (f.files && f.files.length > 0) {
            for (const i in f.files) {
              const m = f.files[i] as any;
              fs.mkdirpSync(
                path.resolve(`./public/${upload}/${model}/${type}/${uploadId}`),
              );
              const o_file = `./.sandbox/${m.originalname}`;
              let eTag = SparkMD5.hashBinary(fs.readFileSync(o_file, 'binary')); //不指定编码 返回buffer对象
              const m_file = `./public/${upload}/${model}/${type}/${uploadId}/${m.originalname}`;
              fs.rename(o_file, m_file, function (err: any) {
                if (err) {
                  child_process.execSync(`mv ${o_file} ${m_file}`);
                  console.log(err);
                }
              });
              const result = {
                name: m.originalname,
                eTag: eTag,
              };
              resolve({
                status: 0,
                msg: '',
                data: result,
              });
            }
          }
        }
      });
    });
  }

  @post(`FileUpload/finishChunkApi/{upload}/{model}/{type}`)
  @response(200, {
    description: 'FileUpload model instance',
    content: { 'application/json': { schema: getModelSchemaRef(FileUpload) } },
  })
  async finishChunkApi(
    @param.path.string('upload') upload: string,
    @param.path.string('model') model: string,
    @param.path.string('type') type: string,
    @requestBody() pl: any,
  ): Promise<any> {
    let uploadId = pl.uploadId;
    let key = pl.key;
    let partList = pl.partList;
    let pathurl = `/${upload}/${model}/${type}/${key}`;
    const m_dir = `./public/${upload}/${model}/${type}/${uploadId}`;
    const filePath = `./public/${upload}/${model}/${type}/${key}`;
    // console.log(uploadId, key, partList, pathurl, " asdasd")
    let self = this;
    let size = 0;
    function mergeFile(dirPath: string, filePath: string, partList: any) {
      let total = partList.length;
      return new Promise((resolve, reject) => {
        fs.readdir(dirPath, (err: any, files: any) => {
          if (err) {
            return reject(err);
          }
          if (files.length !== total || !files.length) {
            return reject('上传失败,切片数量不符');
          }
          function merge(i: number) {
            // 合并完成
            if (i === files.length) {
              fs.rmdir(dirPath, (err: any) => {
                console.log(err, 'rmdir');
              });
              let date = new Date();
              let m = {
                originalname: pl.filename,
                path: pathurl,
                timestamp: date,
                size: size,
              };
              return resolve({
                status: 0,
                data: {
                  date: date,
                  value: pathurl,
                  url: pathurl,
                },
              });
            }
            let chunkpath = `${dirPath}/${key}-${i + 1}`;
            // console.log(chunkpath, 'chunkpath');
            fs.readFile(chunkpath, 'binary', (err: any, data: any) => {
              // console.log(data.length);
              size += data.length;
              let eTag = SparkMD5.hashBinary(data);
              if (_.find(partList, { partNumber: i + 1 }).eTag !== eTag) {
                return reject('上传失败,切片内容不符');
              }
              // 将切片追加到存储文件
              fs.appendFile(filePath, data, { encoding: 'binary' }, () => {
                // 删除切片文件
                fs.unlink(chunkpath, () => {
                  // 递归合并
                  merge(i + 1);
                });
              });
            });
          }
          merge(0);
        });
      });
    }
    try {
      return await mergeFile(m_dir, filePath, partList);
    } catch (err) {
      fs.rmdir(m_dir, { recursive: true }, (err: any) => {
        console.log(err);
      }); //出错后重新上传
      return {
        status: -1,
        msg: err,
      };
    }
  }
}

file-upload.sevice.ts:

import {
  BindingScope,
  config,
  ContextTags,
  injectable,
  Provider,
} from '@loopback/core';
import multer from 'multer';
import {FILE_UPLOAD_SERVICE} from '../keys';
import {FileUploadHandler} from '../types';

/**
 * A provider to return an `Express` request handler from `multer` middleware
 */
@injectable({
  scope: BindingScope.TRANSIENT,
  tags: {[ContextTags.KEY]: FILE_UPLOAD_SERVICE},
})
export class FileUploadProvider implements Provider<FileUploadHandler> {
  constructor(@config() private options: multer.Options = {}) {
    if (!this.options.storage) {
      // Default to in-memory storage
      this.options.storage = multer.memoryStorage();
    }
  }

  value(): FileUploadHandler {
    return multer(this.options).any();
  }
}

application.ts:

import { BootMixin } from '@loopback/boot';
import { ApplicationConfig } from '@loopback/core';
import { RepositoryMixin } from '@loopback/repository';
import { RestApplication, RestBindings } from '@loopback/rest';
import { ServiceMixin } from '@loopback/service-proxy';
import multer from 'multer';
import path from 'path';
import {  FILE_UPLOAD_SERVICE,  STORAGE_DIRECTORY } from './keys';

export class LbSmartApplication extends BootMixin(
  ServiceMixin(RepositoryMixin(RestApplication)),
) {
  constructor(options: ApplicationConfig = {}) {
    super(options);

    //...省略

    this.configureFileUpload(options.fileStorageDirectory);
  };

  /**
   * Configure `multer` options for file upload
   */
  protected configureFileUpload(destination?: string) {
    // Upload files to `dist/.sandbox` by default
    destination = destination ?? path.join(__dirname, '../.sandbox');             
    this.bind(STORAGE_DIRECTORY).to(destination);
    const multerOptions: multer.Options = {
      storage: multer.diskStorage({
        destination,
        // Use the original file name as is
        filename: (req, file, cb) => {
          file.originalname = Buffer.from(file.originalname, "latin1").toString( "utf8");
          let originalname = file.originalname;
          if (req.body && req.body.key && req.body.partNumber) {
            originalname = `${req.body.key}-${req.body.partNumber}`;
          }
          cb(null, originalname);
        },
      }),
    };
    // Configure the file upload service with multer options
    this.configure(FILE_UPLOAD_SERVICE).to(multerOptions);
  }
}

额外:加密算法介绍

在信息安全领域,经常会用到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就可能被解密。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/645035.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

大模型提示词Prompt学习

引言 关于chatGPT的Prompt Engineer&#xff0c;大家肯定耳朵都听起茧了。但是它的来由&#xff1f;&#xff0c;怎么能用好&#xff1f;很多人可能并不觉得并不是一个问题&#xff0c;或者说认定是一个很快会过时的概念。但其实也不能说得非常清楚&#xff08;因为觉得没必要深…

【Windows】 IDimager Photo Supreme 2024(图片管理软件)安装教程

软件介绍 IDimager Photo Supreme 2024是一款专业的图片管理软件&#xff0c;旨在帮助用户有效地组织、管理和浏览他们的照片收藏。以下是该软件的一些主要特点和功能&#xff1a; 图片管理&#xff1a;Photo Supreme提供强大的图片管理功能&#xff0c;可以帮助用户轻松地整理…

2024系统架构师软考考题考点回忆版

2024系统架构师软考试题/考点梳理 选择题 (75道单选题) 软件测试(P205) 静态测试:是被测程序不运行,只依靠分析和检查源程序的语句、结构、过程来检查程序是否有错误。动态测试:运行被测试程序,对得到的结果与预期的结果进行比较分析,同时分析运行效率和健壮性能等。…

关于堆排序

今天我们不刷力扣了&#xff0c;我们来复习&#xff08;手撕&#xff09;一下数据结构中的八大排序算法之一&#xff0c;堆排序 基本概念&#xff1a; 堆是一种特殊的树形数据结构&#xff0c;即完全二叉树。 堆分为大顶堆和小顶堆&#xff1a; 大顶堆&#xff1a;每个节点的值…

lspci 显示当前设备的PCI总线信息

lspci 显示当前设备的PCI总线信息 lspci 显示当前设备的PCI总线信息显示当前主机的所有PCI总线信息&#xff1a;以数字方式显示PCI厂商和设备代码同时显示数字方式还有设备代码信息以树状结构显示PCI设备的层次关系&#xff1a;更多信息 lspci 显示当前设备的PCI总线信息 lspc…

【调试笔记-20240526-Linux-在 OpenWrt-23.05 发行版上安装 cloudreve】

调试笔记-系列文章目录 调试笔记-20240526-Linux-在 OpenWrt-23.05 发行版上安装 cloudreve 文章目录 调试笔记-系列文章目录调试笔记-20240526-Linux-在 OpenWrt-23.05 发行版上安装 cloudreve 前言一、调试环境操作系统&#xff1a;Windows 10 专业版调试环境调试目标 二、调…

高职物联网专业嵌入式系统开发教学解决方案

前言 随着人工智能与物联网技术的深度融合&#xff0c;物联网&#xff08;AIoT&#xff09;已成为推动产业发展的重要力量。高职物联网专业作为培养技术人才的重要基地&#xff0c;面临着课程体系更新、教学内容优化的迫切需求。嵌入式系统开发作为物联网专业的核心课程之一&a…

面了字节大模型算法岗,太难了。。。

节前&#xff0c;我们组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、今年参加社招和校招面试的同学。 针对大模型技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备面试攻略、面试常考点等热门话题进行了深入的讨论。 汇总合集…

研二学妹面试字节,竟倒在了ThreadLocal上,这是不要应届生还是不要女生啊?

一、写在开头 今天和一个之前研二的学妹聊天&#xff0c;聊及她上周面试字节的情况&#xff0c;着实感受到了Java后端现在找工作的压力啊&#xff0c;记得在18&#xff0c;19年的时候&#xff0c;研究生计算机专业的学生&#xff0c;背背八股文找个Java开发工作毫无问题&#x…

【Java】Sping Boot中使用Javax Bean Validation

目录 Javax Bean Validation在Spring Boot中集成Javax Bean Validation使用案例功能测试配置全局异常处理器重新测试返回特定形式的信息方式一方式二 附&#xff1a;常用的注解 Javax Bean Validation Javax Bean Validation是Java平台的一项规范&#xff0c;旨在提供一种简单…

如何处理时间序列的缺失数据

您是否应该删除、插入或估算&#xff1f; 世界上没有完美的数据集。每个数据科学家在数据探索过程中都会有这样的感觉&#xff1a; df.info()看到类似这样的内容&#xff1a; 大多数 ML 模型无法处理 NaN 或空值&#xff0c;因此如果您的特征或目标包含这些值&#xff0c;则在…

Java开发大厂面试第22讲:Redis 是如何保证系统高可用的?它的实现方式有哪些?

高可用是通过设计&#xff0c;减少系统不能提供服务的时间&#xff0c;是分布式系统的基础也是保障系统可靠性的重要手段。而 Redis 作为一款普及率最高的内存型中间件&#xff0c;它的高可用技术也非常的成熟。 我们今天分享的面试题是&#xff0c;Redis 是如何保证系统高可用…

mysql - 索引原理

mysql索引原理 文中的查询, 以该表结构为例 CREATE TABLE user (id int NOT NULL COMMENT id,name varchar(255) COLLATE utf8mb4_bin NOT NULL COMMENT 姓名,age int NOT NULL COMMENT 年龄,sex tinyint(1) NOT NULL COMMENT 性别,phone varchar(255) CHARACTER SET utf8mb4…

源码编译安装LAMP

1.LAMP介绍 LAMP架构是目前成熟的企业网站应用模式之一&#xff0c;指的是协同工作的一整套系统和相关软件&#xff0c;能够提供动态Web站点服务及其应用开发环境。LAMP是一个缩写词&#xff0c;具体包括Linux操作系统、Apache网站服务器、MySQL数据库服务器、PHP&#xff08;…

公安知识学习与题目练习系统

一、系统概述 系统采用C用户小程序端、管理员Web端架构。通过UniappVueSpringboot主流技术实现。具体功能分为&#xff0c;管理侧&#xff1a;可以维护学习知识点、更新知识点详情&#xff1b;C端用户&#xff1a;可以学习知识点、在线刷题练习的功能。次系统在公安专业知识学习…

ChatGLM2-6B 模型基于 [P-Tuning v2]的微调

ChatGLM2-6B-PT 一、介绍 1、本文实现对于 ChatGLM2-6B 模型基于 [P-Tuning v2](https://github.com/THUDM/P-tuning-v2) 的微调 2、运行至少需要 7GB 显存 3、以 [ADGEN](https://aclanthology.org/D19-1321.pdf) (广告生成) 数据集为例介绍代码的使用方法。 模型部署参考…

安装termux遇到的问题-逍遥模拟器

问题一 做一次更新即可解决问题&#xff0c;pkg update 问题二 sshd&#xff1a;no hostkeys available -- exiting. 尝试了网上提供的方法 ssh-keygen -t rsa -f /data/data/com.termux/files/usr/etc/ssh/ssh_host_rsa_key ssh-keygen -t dsa -f /data/data/com.termux/…

OTA在线旅行社系统架构:连接世界的科技纽带

随着互联网的快速发展和人们对旅行需求的不断增长&#xff0c;OTA&#xff08;Online Travel Agency&#xff09;在线旅行社成为了现代旅行业中的重要一环。OTA系统架构的设计和实现将对旅行行业产生深远影响。本文将探讨OTA在线旅行社系统架构的重要性和关键组成部分&#xff…

【论文阅读|cryoET】ICE-TIDE

简介 三维cryoET重建的保真度进一步受到采集过程中物理扰动的影响。这些扰动以各种形式表现出来&#xff0c;例如连续采集之间的样本漂移&#xff0c;导致连续投影未对准&#xff0c;或者由于未散射的电子而导致二维投影中的局部变形。 传统的冷冻电子断层扫描工作流程需要对…

论文阅读--ActionCLIP

原来的动作识别问题在于标注太难太贵&#xff0c;将动作表示为短语的latent space太大 本文的贡献&#xff1a;&#xff08;1&#xff09;将CLIP的image encoder换成video encoder&#xff0c;方法与CLIP4Clip几乎一样 &#xff08;2&#xff09;CLIP的ground truth来自于文本…