В этой статье вы шаг за шагом раскрываете тайну «загрузки файла».

Node.js внешний интерфейс JavaScript

Привет всем, я Qiufeng, сегодняшняя тема о文件下载, прежде чем я разместил статью о загрузке файлов (Одна статья для понимания всего процесса загрузки файлов (углубленный анализ 1,8 слова, необходимый для продвинутых пользователей).200+ лайков), ответ неплохой, после многих дней, из-за недавнего исследования некоторых работ, связанных со СМИ, я планирую организовать загрузку, поэтому родилась его братская глава, чтобы показать вам тайну загрузки файлов. Эта статья займет у вас много времени для чтения.Рекомендуется сначала добавить ее в закладки/лайкнуть, а затем проверить интересующие вас части.Также ее можно использовать как словарь для запросов.

:) Не знаю, может быть, я не знаю, но у меня так много ситуаций, я просто хочу быть мальчиком-пажем.

предисловие

Изображение полного текста, вы можете сначала посмотреть, подходит ли вам набросок, и, если он вам понравился, продолжить чтение.

一文了解文件下载

В этом разделе мы в основном представляем некоторые предварительные знания и некоторые базовые знания, если вы думаете, что вы это. ⬇️⬇️⬇️, предисловие можно пропустить.

ceeb653ely1g0tken1v5aj20ly0iqgm8.jpeg

Внешняя загрузка файлов в основном осуществляется через<a>,Плюсdownloadсвойства, благодаря которым наши загрузки становятся проще.

downloadЭто свойство указывает браузеру загружать URL-адрес вместо перехода к нему, поэтому пользователю будет предложено сохранить его как локальный файл. Если свойство имеет значение, то это значение будет использоваться в качестве предварительно заполненного имени файла во время загрузки и сохранения (имя файла все еще может быть изменено, если пользователь желает). Этот атрибут не имеет ограничений на допустимые значения, но/а также\будут преобразованы в символы подчеркивания. Большинство файловых систем ограничивают использование пунктуации в именах файлов, поэтому браузеры соответствующим образом корректируют предлагаемые имена файлов. (взято изdeveloper.Mozilla.org/this-cn/docs/…)

Уведомление:

  • Это свойство относится только кURL того же источника.
  • Хотя URL-адрес HTTP должен быть из того же источника, вы можете использоватьblob: URLа такжеdata: URL, чтобы пользователи могли загружать контент, созданный с помощью JavaScript (например, фотографии, созданные с помощью веб-приложения для рисования в Интернете).

Таким образом, существует три основных способа загрузки URL-адресов. (Большая часть этой статьи демонстрируется в виде BLOB-объектов)

image-20200830153314861

совместимость

Видно, что его совместимость тоже очень впечатляет(woohoo. руб. newser.com/#search=Dow…

image-20200817232216749

Чтобы избежать большого дублирования кода, потому что я извлек несколько публичных функций. (Эту часть можно пропустить, названия более читабельны, а если потом не разберетесь, то можете найти здесь)

export function downloadDirect(url) {
    const aTag = document.createElement('a');
    aTag.download = url.split('/').pop();
    aTag.href = url;
    aTag.click()
}
export function downloadByContent(content, filename, type) {
    const aTag = document.createElement('a');
    aTag.download = filename;
    const blob = new Blob([content], { type });
    const blobUrl = URL.createObjectURL(blob);
    aTag.href = blobUrl;
    aTag.click();
    URL.revokeObjectURL(blob);
}
export function downloadByDataURL(content, filename, type) {
    const aTag = document.createElement('a');
    aTag.download = filename;
    const dataUrl = `data:${type};base64,${window.btoa(unescape(encodeURIComponent(content)))}`;
    aTag.href = dataUrl;
    aTag.click();
}
export function downloadByBlob(blob, filename) {
    const aTag = document.createElement('a');
    aTag.download = filename;
    const blobUrl = URL.createObjectURL(blob);
    aTag.href = blobUrl;
    aTag.click();
    URL.revokeObjectURL(blob);
}
export function base64ToBlob(base64, type) {
    const byteCharacters = atob(base64);
    const byteNumbers = new Array(byteCharacters.length);
    for (let i = 0; i < byteCharacters.length; i++) {
        byteNumbers[i] = byteCharacters.charCodeAt(i);
    }
    const buffer = Uint8Array.from(byteNumbers);
    const blob = new Blob([buffer], { type });
    return blob;
}

🚅🚅🚅🚅🚅🚅🚅🚅🚅🚅🚅🚅🚅🚅🚅🚅🚅🚅🚅🚅🚅🚅🚅🚅🚅🚅🚅🚅🚅

(Вручную нарисуйте разделительную линию для большого парня, который не читает вышеуказанный контент)

🇨🇳

Все примеры адресов Github:GitHub.com/flower1995116/…

Онлайн-демонстрация:Осенний бриз.blue/demo/file-of…

前端文件下载

задняя часть

Все примеры в бэкенде этой статьи реализованы на koa/native js.

Бэкенд возвращает файловый поток

Эта ситуация очень проста, нам нужно только напрямую открыть файловый поток, возвращаемый бэкендом, в новом окне, а затем загрузить его напрямую.

// 前端代码
<button id="oBtnDownload">点击下载</button>
<script>
oBtnDownload.onclick = function(){
    window.open('http://localhost:8888/api/download?filename=1597375650384.jpg', '_blank')
}
</script>
// 后端代码
router.get('/api/download', async (ctx) => {
    const { filename } = ctx.query;
    const fStats = fs.statSync(path.join(__dirname, './static/', filename));
    ctx.set({
        'Content-Type': 'application/octet-stream',
        'Content-Disposition': `attachment; filename=${filename}`,
        'Content-Length': fStats.size
    });
    ctx.body = fs.readFileSync(path.join(__dirname, './static/', filename));
})

Есть две основные ситуации, в которых браузер может автоматически скачивать файлы:

а для использованияContent-DispositionАтрибуты.

Давайте посмотрим на описание этого поля.

В обычном HTTP-ответеContent-DispositionЗаголовок ответа указывает, в какой форме должно отображаться содержание ответа.в линиюформе (то есть веб-странице или части страницы) или в формеПриложениеЗагрузите и сохраните в локальный --- источник MDN(developer.Mozilla.org/this-cn/docs/…)

Давайте посмотрим на его синтаксис

Content-Disposition: inline
Content-Disposition: attachment
Content-Disposition: attachment; filename="filename.jpg"

Очень просто, пока я устанавливаю последнюю форму, я могу успешно загрузить файл из бэкэнда.

Другой тип не распознается браузером.

Например введитеhttp://localhost:8888/static/demo.sh, браузер не распознает этот тип и загрузит его автоматически.

Не знаю, сталкивались ли мои друзья с такой ситуацией, вводим правильный статический js адрес, конфигурации нетContent-Disposition, но случайно загружается.

Например, следующая ситуация.

2020-08-30-17.01.52

006r3PQBjw1fav4dsikh6j308c0g5gm1

Скорее всего, это связано с вашимnginxЭта строка конфигурации отсутствует.

include mime.types;

вызвать значение по умолчаниюapplication/octet-stream, файл был загружен, но не был распознан браузером.

Бэкенд возвращает статический адрес сайта

Загрузка через статический сайт может быть разделена на два случая: во-первых, служба может иметь собственный статический каталог, что является ситуацией того же происхождения, а во-вторых, применима сторонняя статическая платформа хранения, такая как Alibaba. Облако и Tencent Cloud, Класс управляемый, то есть негомологичный (конечно, некоторые платформы вернутся напрямую).

гомология

В случае с одинаковым происхождением все очень просто, вы можете легко скачать код, напрямую вызвав функцию.

import {downloadDirect} from '../js/utils.js';
axios.get('http://localhost:8888/api/downloadUrl').then(res => {
        if(res.data.code === 0) {
            downloadDirect(res.data.data.url);
        }
})

негомологичный

Мы также можем видеть из MDN, что, хотя загрузка ограничивает негомологичную ситуацию, но! ! но! ! Но вы можете использоватьblob: URLа такжеdata: URL, поэтому нам нужно только загрузить содержимое файла и преобразовать его вblobВот и все.

Весь процесс выглядит следующим образом

image-20200830174735143

<button id="oBtnDownload">点击下载</button>
    <script type="module">
        import {downloadByBlob} from '../js/utils.js';
        function download(url) {
            axios({
                method: 'get',
                url,
                responseType: 'blob'
            }).then(res => {
                downloadByBlob(res.data, url.split('/').pop());
            }) 
        }
        oBtnDownload.onclick = function(){
           axios.get('http://localhost:8888/api/downloadUrl').then(res => {
                if(res.data.code === 0) {
                    download(res.data.data.url);
                }
            })
        }
    </script>

Теперь негомологичные тоже могут с удовольствием скачиваться.

Серверная часть возвращает строку (base64)

Иногда мы также сталкиваемся с некоторыми начинающими бэкендами, возвращающими строки.Это редкость, но мы не паникуем, когда приходим.Кстати, мы можем показать волну операций младшему брату бэкенда.Неважно какие данные, мы можем это сделать , Скачать его для вас.

PS: Посылка - безопасные и экологически чистые ресурсы :) , светится вывеска серьезных статей.

В данном случае мне нужно смоделировать работу back-end брата, поэтому у меня есть back-end код.

994b6f2egy1fgryfevtpvj208c08cmxd

основной процесс

image-20200830174752476

// node 端
router.get('/api/base64', async (ctx) => {
    const { filename } = ctx.query;
    const content = fs.readFileSync(path.join(__dirname, './static/', filename));
    const fStats = fs.statSync(path.join(__dirname, './static/', filename));
    console.log(fStats);
    ctx.body = {
        code: 0,
        data: {
            base64: content.toString('base64'),
            filename,
            type: mime.getType(filename)
        }
    }
})
// 前端
<button id="oBtnDownload">点击下载</button>
<script type="module">
import {base64ToBlob, downloadByBlob} from '../js/utils.js';
function download({ base64, filename, type }) {
    const blob = base64ToBlob(blob, type);
    downloadByBlob(blob, filename);
}
oBtnDownload.onclick = function(){
    axios.get('http://localhost:8888/api/base64?filename=1597375650384.jpg').then(res => {
        if(res.data.code === 0) {
            download(res.data.data);
        }
    })
}
</script>

Идея на самом деле использует то, что мы сказали выше<a>Этикетка. Но перед этим шагом еще один шаг заключается в том, что нам нужно преобразовать нашbase64Строки конвертируются в бинарные потоки, об этом часто упоминалось в моей предыдущей закачке файлов, ведь файлы существуют в виде бинарных потоков. Но это тоже очень просто, в js есть встроенные функцииatob. Значительно повысить эффективность нашего преобразования.

чистый передний конец

Выше описаны связанные методы использования серверной части для завершения загрузки файлов.Далее мы представим некоторые методы чистого интерфейса для завершения загрузки файлов.

метод первый:blob: URL

image-20200831230800538

Способ второй:data: URL

image-20200831230810963

Поскольку data:URL имеет ограничение по длине, во всех приведенных ниже примерах для демонстрации будет использоваться blob.

json/text

Загрузка текста и JSON очень проста, и вы можете напрямую создать Blob.

Blob(blobParts[, options])
返回一个新创建的 Blob 对象,其内容由参数中给定的数组串联组成。
// html
<textarea name="" id="text" cols="30" rows="10"></textarea>
<button id="textBtn">下载文本</button>
<p></p>
<textarea name="" id="json" cols="30" rows="10" disabled>
{
    "name": "秋风的笔记"
}
</textarea>
<button id="jsonBtn">下载JSON</button>
//js
import {downloadByContent, downloadByDataURL} from '../js/utils.js';
textBtn.onclick = () => {
        const value = text.value;
        downloadByContent(value, 'hello.txt', 'text/plain');
  		// downloadByDataURL(value, 'hello.txt', 'text/plain');
}
jsonBtn.onclick = () => {
        const value = json.value;
        downloadByContent(value, 'hello.json', 'application/json');
     // downloadByDataURL(value, 'hello.json', 'application/json');
}

визуализация

2020-08-30-17.53.32

Код комментария является отображаемой частью data:URL. Поскольку это первый пример, я расскажу о коде отображения, который будет опущен позже, но вы также можете вызватьdownloadByDataURLметод, если вы не можете найти определение метода, перейдите к началу статьи~

excel

Можно сказать, что Excel — это сцена, где некоторые из наших интерфейсов имеют глубокие отношения.Какие данные находятся в середине, нам нужно экспортировать различные отчеты каждый день. В прошлом клиентская часть запрашивала у серверной части адрес файла Excel. Теперь давайте покажем, как чистый интерфейс реализует загрузку Excel.

простой эксель

Форма выглядит так, относительно простая форма

image-20200829170347728

const template = '<html xmlns:o="urn:schemas-microsoft-com:office:office" '
            +'xmlns:x="urn:schemas-microsoft-com:office:excel" '
            +'xmlns="http://www.w3.org/TR/REC-html40">'
            +'<head>'
            +'</head>'
            +'<body><table border="1" style="width:60%; text-align: center;">{table}</table><\/body>'
            +'<\/html>';
    const context = template.replace('{table}', document.getElementById('excel').innerHTML);
    downloadByContent(context, 'qiufengblue.xls', 'application/vnd.ms-excel');

Но написание не сложное, оно все такое же, как и прежде, путем построенияexcel, преобразовать его в большой двоичный объект для загрузки.

Окончательный экспортный эффект

image-20200829170625763

форма экспорта element-ui

Правильно, этоelement-uiофициальныйtableпример.

image-20200829170543891

Эффект экспорта заключается в следующем, который можно назвать идеальным.

image-20200829170912128

Здесь мы используем плагинGitHub.com/лист JS/Змея…

Он очень прост в использовании.

<template>
      <el-table id="ele" border :data="tableData" style="width: 100%">
        <el-table-column prop="date" label="日期" width="180">
        </el-table-column>
        <el-table-column prop="name" label="姓名" width="180">
        </el-table-column>
        <el-table-column prop="address" label="地址">
        </el-table-column>
      </el-table>
      <button @click="exportExcel">导出excel</button>
</template>
<script>
...
methods: {
  exportExcel() {
     let wb = XLSX.utils.table_to_book(document.getElementById('ele'));
     XLSX.writeFile(wb, 'qiufeng.blue.xlsx');
	}
}
...
</script>

完美表情

word

законченныйexcel Давай поговоримwordЭто еще один отличный инструмент для Office Three Musketeers. Здесь мы по-прежнему используем описанный выше метод загрузки больших двоичных объектов.

Простой пример

2020-08-29-20.13.25

Отображение кода

exportWord.onclick = () => {
    const template = '<html xmlns:o="urn:schemas-microsoft-com:office:office" '
            +'xmlns:x="urn:schemas-microsoft-com:office:word" '
            +'xmlns="http://www.w3.org/TR/REC-html40">'
            +'<head>'
            +'</head>'
            +'<body>{table}<\/body>'
            +'<\/html>';
    const context = template.replace('{table}', document.getElementById('word').innerHTML);
    downloadByContent(context, 'qiufeng.blue.doc', 'application/msword');
}

Показать результаты

image-20200830164208184

использоватьdocx.js плагин

Если вы хотите более продвинутое использование, вы можете использоватьdocx.js эта библиотека. Конечно, описанный выше метод также можно настроить.

код

<button type="button" onclick="generate()">下载word</button>

    <script>
        async function generate() {
            const res = await axios({
                method: 'get',
                url: 'http://localhost:8888/static/1597375650384.jpg',
                responseType: 'blob'
            })
            const doc = new docx.Document();
            const image1 = docx.Media.addImage(doc, res.data, 300, 400)
            doc.addSection({
                properties: {},
                children: [
                    new docx.Paragraph({
                        children: [
                            new docx.TextRun("欢迎关注[秋风的笔记]公众号").break(),
                            new docx.TextRun("").break(),
                            new docx.TextRun("定期发送优质文章").break(),
                            new docx.TextRun("").break(),
                            new docx.TextRun("美团点评2020校招-内推").break(),
                        ],
                    }),
                    new docx.Paragraph(image1),
                ],
            }); 

            docx.Packer.toBlob(doc).then(blob => {
                console.log(blob);
                saveAs(blob, "qiufeng.blue.docx");
                console.log("Document created successfully");
            });
        }
    </script>

Эффект (без рекламы... просто нашел картинку, насильно не допуская в серию)

9150e4e5ly1fl8qavz6quj20hs0hsjvl

2020-08-30-18.32.09

zip скачать

Внешнее сжатие по-прежнему очень полезно, и в определенных сценариях оно может сэкономить трафик. И эта сцена более часто используется, например, загрузка упакованного изображения переднего плана, значок загрузки упакованного переднего плана.

Сначала я думал, чтоtinypng.com/Я только что использовал это и обнаружил, что был неправ... Подумайте об этом внимательно, потому что сжатые изображения хранятся в бэкэнде.Если вы используете внешний пакет, вам придется запрашивать все сжатые изображения чтобы получить поток изображений. Если вы используете внутреннее сжатие, вы можете эффективно экономить трафик. Эм. . . Пример неудачи заканчивается.

Позже я подумалwww.iconfont.cn/ При упаковке и загрузке иконок используйте….

image-20200829204540440

Его официальный веб-сайт представляет собой значок, отображаемый в формате svg. При загрузке svg вы можете использовать интерфейсный пакет для загрузки. Однако он также поддерживает форматы шрифта и jpg, поэтому для унификации используется back-end загрузка, что понятно. Потом будем реализовывать эту незаконченную функцию.Конечно, нам также нужно использовать плагин, которыйjszip.

Здесь я нашел две иконки svg сверху.

image-20200829204937044

код реализации

download.onclick = () => {
        const zip = new JSZip();
        const svgList = [{
            id: 'demo1',
        }, {
            id: 'demo2',
        }]
        svgList.map(item => {
            zip.file(item.id + '.svg', document.getElementById(item.id).outerHTML);
        })
        zip.generateAsync({ 
            type: 'blob'
        }).then(function(content) {
            // 下载的文件名
            var filename = 'svg' + '.zip';
            // 创建隐藏的可下载链接
            var eleLink = document.createElement('a');
            eleLink.download = filename;
            // 下载内容转变成blob地址
            eleLink.href = URL.createObjectURL(content);
            // 触发点击
            eleLink.click();
            // 然后移除
        });
    }

2020-08-29-20.52.42

Просмотрите каталог папки, SVG упакован и загружен.

image-20200829205329532

файловая система браузера (экспериментальная)

image-20200817234129788

У меня на компе стоит такой браузер для обучения и отладкиchromeПоследние новые функции, если на вашем компьютере их нет, рекомендуется их установить.

Игра с этой функцией требует открытия экспериментальных функций Chrome.chrome://flags => #native-file-system-api => enable, потому что экспериментальные функции будут поставляться с некоторой безопасностью или влиять на исходное поведение рендеринга, поэтому я снова настоятельно рекомендую загрузить канареечную версию chrome, чтобы поиграть с ней.

<textarea name="" id="textarea" cols="30" rows="10"></textarea>
<p><button id="btn">下载</button></p>
<script>
    btn.onclick = async () => {
        const handler = await window.chooseFileSystemEntries({
            type: 'save-file',
            accepts: [{
                description: 'Text file',
                extensions: ['txt'],
                mimeTypes: ['text/plain'],
            }],
        });

        const writer = await handler.createWritable();
        await writer.write(textarea.value);
        await writer.close();
    }
</script>

Это очень просто реализовать. Но такое ощущение, что летишь.

2020-08-18-00.13.29

другие сцены

Загрузка файла H5

Как правило, большинство загрузок в h5 — это загрузки в формате pdf или apk.

Android

В браузерах Android браузер загружает файлы напрямую.

ios

Из-за ограничений ios загрузка не может быть выполнена, поэтому вместо загрузки можно использовать копию URL.

import {downloadDirect} from '../js/utils.js';
const btn = document.querySelector('#download-ios');
if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) {
    const clipboard = new ClipboardJS(btn);
    clipboard.on('success', function () {
        alert('已复制链接,打开浏览器粘贴链接下载');
    });
    clipboard.on('error', function (e) {
        alert('系统版本过低,复制链接失败');
    });
} else {
    btn.onclick = () => {
        downloadDirect(btn.dataset.clipboardText)
    }
}

Более

Вы можете использовать этот пакет для загрузки пакетов, таких как apk (я еще не тестировал его, и у меня мало с ним контактов. Я вернусь и добавлю его после того, как ознакомлюсь с ним).

GitHub.com/jaw ID-limited/Web-…

image-20200830145258473

Частичная загрузка больших файлов

Когда я недавно разрабатывал работу, связанную с потоковой передачей мультимедиа, я обнаружил интересное явление при загрузке файлов mp4.Видеопотоку не нужно загружать весь mp4 для воспроизведения, и он сопровождается множеством кодов состояния 206. Запрос, на первый взгляд немного похоже на прелести потокового мультимедиа (HLS и т.п.).

2020-08-29-21.31.29

Я думаю, что это явление очень интересное, он может загружать ресурсы в шарды, что очень полезно для опыта или экономии трафика. В конце концов выяснилось, что у него есть заголовок под названием Range. Давайте посмотрим на объяснение MDN.

The Rangeэто заголовок запроса, который сообщает серверу, какую часть файла нужно вернуть. вRangeВ заголовке можно запросить сразу несколько частей, и сервер вернет их в виде составного файла. Если сервер возвращает ответ диапазона, вам нужно использовать206 Partial Contentкод состояния. Взято из МДН

грамматика

Range: <unit>=<range-start>-
Range: <unit>=<range-start>-<range-end>
Range: <unit>=<range-start>-<range-end>, <range-start>-<range-end>
Range: <unit>=<range-start>-<range-end>, <range-start>-<range-end>, <range-start>-<range-end>

Реализация узла

Теперь, когда мы знаем, как это работает, давайте реализуем это сами.

router.get('/api/rangeFile', async(ctx) => {
    const { filename } = ctx.query;
    const { size } = fs.statSync(path.join(__dirname, './static/', filename));
    const range = ctx.headers['range'];
    if (!range) {
        ctx.set('Accept-Ranges', 'bytes');
        ctx.body = fs.readFileSync(path.join(__dirname, './static/', filename));
        return;
    }
    const { start, end } = getRange(range);
    if (start >= size || end >= size) {
        ctx.response.status = 416;
        ctx.set('Content-Range', `bytes */${size}`);
        ctx.body = '';
        return;
    }
    ctx.response.status = 206;
    ctx.set('Accept-Ranges', 'bytes');
    ctx.set('Content-Range', `bytes ${start}-${end ? end : size - 1}/${size}`);
    ctx.body = fs.createReadStream(path.join(__dirname, './static/', filename), { start, end });
})

Реализация Nginx

Я обнаружил, что nginx поддерживает заголовок диапазона по умолчанию без написания кода. Я подумал, что должен знать, поддерживает ли он его, или какие модули были добавлены, или какую конфигурацию я включил по умолчанию. После долгих поисков я дополнительных настроек не нашел.

3630px-Nginx_logo-1

Как раз когда я собирался сдаться, меня осенило, и я пошел смотреть исходный код, может быть, я что-то найду, я проверил содержимое, связанное с исходным кодом nginx, и я использовал обычный метод обратного нажатия, только чтобы узнать, что это былоmax_rangesэто поле.

GitHub.com/Nginx/Nginx…

Также моя вина в том, что я недостаточно внимательно прочитал документацию в начале и потратил впустую много времени.

:) На самом деле, я не знаком с исходным кодом nginx.Я могу использовать небольшую хитрость, чтобы искать 206 прямо в библиотеке исходного кода и найти макрокоманду

#define NGX_HTTP_PARTIAL_CONTENT           206

Затем следуйте по виноградным лозам и найдите эту макрокоманду напрямую.NGX_HTTP_PARTIAL_CONTENTТам, где мы его используем, мы можем постепенно находить то, что хотим, шаг за шагом.

По умолчанию nginx автоматически открывает заголовок диапазона, если настройка не требуется, настройтеmax_range: 0;

Документация по настройке NginxNginx.org/en/docs/red-glowing…

Суммировать

Мы можем резюмировать, на самом деле, полный текст в основном говорит о (xbb) двух основных знаниях, одно из которыхblobОдин alabel, а также обратите внимание на стратегию оптимизации сервера для больших файлов, которые можно пропустить черезRangeк загрузке осколков.

image-20200830181216353

использованная литература

GitHub.com/ohlanmiao/до…

GitHub.com/лист JS/Змея…

nuggets.capable/post/684490…

наконец

Если моя статья поможет вам, надеюсь, вы сможете помочь и мне, добро пожаловать, обратите внимание на мой паблик аккаунт秋风的笔记,Отвечать好友Во второй раз вы можете добавить друзей и присоединиться к группе обмена,秋风的笔记всегда будет рядом с вами.