вступительные слова
В этом году я начал писать некоторые технические статьи с нахальным лицом, и я могу написать одну статью в среднем примерно за две недели. Выход не высокий.Одно из них то, что я обычно очень занят на работе, а другое то, что я не хочу писать ради того, чтобы писать.Если я не нахожу ничего интересного, мне трудно писать.
Как говорится, в конце хорошего комментария надо ставить суффикс, а в названии статьи - префикс.Вот и я всегда хотел написать серию.Можно добавить префикс к названию статьи статья. Но, как и замыкания, защита от сотрясения и другие базовые концепции, многие книги и документы написаны очень хорошо, я не думаю, что смогу написать какие-то новые трюки.
На самом деле, после нескольких лет работы во фронтенд-индустрии я чувствую, что произошли очевидные изменения. Так как я был новичком, я не мог делать много вещей. "сделано. Теперь я могу делать вещи. Это не более чем комбинация различных технологий. Наиболее распространенная проблема заключается в том, является ли метод реализации оптимальным, и какова обычная практика в отрасли". Поэтому я хотел бы написать серию статей, которые фиксируют решение определенной проблемы или реализуют определенную функцию в моей повседневной работе, чтобы дать идею друзьям, у которых есть аналогичные потребности в функциональном развитии, а также поделиться с вами, чтобы посмотреть, есть ли лучшая реализация плана.
Заявление о потребностях
Чтобы улучшить качество доступа пользователей, приложение компании и встроенные страницы H5 используют ускорение CDN, но несколько дней назад возникли некоторые проблемы, поскольку сервер вторичного узла поставщика услуг CDN был отключен, в результате чего мобильные пользователи в некоторых областях не могли доступ нормальный.Поскольку это только проблема сервера CDN, исходный адрес сайта по-прежнему доступен. Позже произошел подобный инцидент, и нас попросили сделать функцию переключения CDN, если у некоторых пользователей возникают проблемы с доступом через CDN, они могут переключиться на другие CDN или напрямую зайти на сайт-источник.
Для нашего внешнего интерфейса, если html-файл и импортированные статические ресурсы объединены, это на самом деле довольно просто. Но проблема в том, что наш html находится на сервере, а статические ресурсы — на выделенном OSS (службе хранения объектов), что немного хлопотно.
Введение связанных знаний
Что такое CDN
Сеть доставки контента (англ. Content Delivery Network или Content Distribution Network, аббревиатура: CDN) относится к компьютерной сетевой системе, соединенной друг с другом через Интернет, с использованием ближайшего к каждому пользователю сервера, обеспечивающего более быструю и надежную доставку музыки, изображений, видео, приложения и другие файлы отправляются пользователям для обеспечения высокой производительности, масштабируемости и недорогой доставки веб-контента пользователям.
Вышеупомянутый текстовый контент взят из Википедии и может быть несколько написан.Короче говоря, это такой процесс:
У вас есть файл a.js на сервере в Ханчжоу, а затем у вас есть адрес исходного сайта.hangzhou.oss.com/a.js, если вы запросите этот адрес, вы получите файл a.js с исходного сайта, но к этому серверу можно быстро получить доступ в Цзянсу, Чжэцзяне и Шанхае, но не так быстро в Пиндиншане (мой родной город, город в провинции Хэнань), поэтому я трачу немного денег Настроил ускорение CDN, дал мне адрес ускорения CDNcdn.oss.com/a.jsКогда я посетил этот адрес в Pingdingshan, я просил сервер CDN в Чжэнчжоу, ближайший к мне, и обнаружил, что этот сервер имел этоa.js, и срок его действия не истек (кэш-попадание), поэтому его можно вернуть напрямую. если этоa.jsПросроченный или несуществующий, будет запланирован оптимальный маршрут для поиска следующего существованияa.jsСервер CDN или прямо на исходный сайт, чтобы получить и сохранить, при следующем посещении вы можете напрямую вернуть ресурс.
Сервисы CDN делают это Ali, Tencent и Huawei, а некоторые, такие как Wangsu, Paiyun, Qiniu и т. д. Говорят, что Akamai (Akamai) неплохо подходит для ускорения зарубежных CDN. Какой из вышеперечисленных использует наша компания для ухода за своим лицом, поэтому мы не будем их называть.Вы можете оставить комментарий и порекомендовать более отличных поставщиков услуг CDN.
путь ссылки на ресурс
Как правило, существует два типа ссылочных путей для ресурсов на веб-страницах: относительные пути и абсолютные пути.
Файл, представленный относительным путем, похоже на это:
<!-- html -->
<!-- 相对路径以 ./或者直接路径名开头 -->
<link href="./style.css" rel="stylesheet" />
Относительный путь относится к пути текущей страницы, предполагая, что адрес доступа к этому htmlwww.demo.com, путь является корневым путем/, импортированный адрес сетевого запроса csswww.demo.com/style.css, Если адрес доступа к этому htmlwww.demo.com/login, путь/login, адрес сетевого запроса csswww.demo.com/login/style.css.
Последний сетевой запрос ресурса, импортированного по относительному пути, можно рассматривать какlocation.host + location.pathname + 文件路径.
Файл, представленный абсолютным путем, выглядит следующим образом:
<!-- html -->
<!-- 绝对路径以一个/开头,可能有见过//www.demo.com/a.js这种的,这是表示引入资源协议(protocol)和当前页面一致 -->
<link href="/style.css" rel="stylesheet" />
Ресурсы, введенные абсолютным путем, не имеют ничего общего с путем текущей страницы, то есть он начинается с корневого пути, независимо от того, какой адрес доступа у вашего html.www.demo.comещеwww.demo.com/login, все сетевые запросы на файлы стилей CSS, представленные абсолютным путем выше,www.demo.com/style.css.
Последний сетевой запрос ресурса, импортированного по относительному пути, можно рассматривать какlocation.host + 文件路径
Как правило, не рекомендуется использовать относительные пути для импорта ресурсов на веб-страницы, особенно теперь, когда многие маршруты внешнего управления приложениями SPA изменяют пути, а относительные пути могут вызвать много путаницы и проблем. Обычно вы можете использовать относительные пути при разработке проектов, а затем использовать инструмент упаковки, такой как веб-пакет, для замены относительных путей при упаковке, настроив общедоступный путь. Для проектов, созданных официальными скаффолдингами vue и react, общедоступный путь к веб-пакету по умолчанию:/, все пути к ресурсам в окончательном упакованном файле начинаются с/Абсолютный путь для начала.
общественный путь
Публичный путь в веб-пакете используется для настройки общедоступного пути. Публичный путь — это основной путь к ресурсам после упаковки проекта. Его также можно понимать как префикс. Обычно это/, указывающий абсолютный путь текущего адреса доступа. Но иногда мы будем хранить js, css, изображения и другие статические ресурсы на другом сервере, тогда мы можем установить общедоступный путь к соответствующему доменному имени. Например, мы устанавливаем publicPath вhttps://oss.demo.com, то что-то вродеstyle.cssЭталонный путь будетhttps://oss.demo.com/style.cssТакой полный сетевой адрес.
Теперь подумайте, почему так просто переключить CDN, объединив html-файл и импортированные статические ресурсы. Очевидно, что при совместном использовании абсолютных путей за статическими ресурсами следуют адреса доступа. кcdn.demo.comДоступ к html, запросы ресурсов в html всеcdn.demo.comначни, ты меняешьсяoss.demo.comто естьoss.demo.com, что эквивалентно автоматическому переключению без каких-либо действий с нашей стороны. Но когда наши статические ресурсы и html находятся на разных серверах, в пути импорта уже прописан адрес префикса, как показано вышеhttps://oss.demo.com/style.css, независимо от того, как вы обращаетесь к html с любого адреса, запрос css всегда будетhttps://oss.demo.com/style.css.
Такие проекты, как create-react-app, перед извлечением файла конфигурации веб-пакета с помощью npm run eject могут изменить общедоступный путь, создав или изменив поле домашней страницы в package.json.
Идеи схемы
1. Запрос прокси приложения
Первым решением, о котором я подумал в то время, был запрос прокси APP, потому что наша страница встроена в APP, и все сетевые запросы на странице могут быть перехвачены APP.После переключения CDN APP заменит все перехваченные запросы веб-страницы с переключением После адреса CDN схема выглядит следующим образом.
Перед переключением:
Преимущество этого решения в том, что интерфейсную страницу не нужно модифицировать, а если в будущем будут добавлены другие CDN, то нужно будет только настроить еще несколько прокси-адресов на стороне приложения, это решает клиент. после оценки целесообразности. Однако они предлагают перехватывать сетевые запросы на обработку веб-страниц, что может повлиять на производительность приложения.Мы надеемся, что наш интерфейс сможет реализовать этот переключатель самостоятельно в будущем.
Перед тем, как предложить это решение, я проверил его.У Android, Apple и Windows разные уровни сложности реализации, и перехват POST-запросов в WKWebView ios приведет к потере тела.Решения есть, но это будет немного хлопотно. Рекомендуется, чтобы переключение адреса вызова API контролировалось внешним интерфейсом, а приложение отвечало только за прокси-перехват загрузки ресурсов.
2. Интерфейс создается несколько раз
Так что я должен придумать другой план, то есть интерфейс будет переключаться сам по себе Теперь давайте посмотрим на ситуацию с нашим проектом:
- Одностраничное приложение React, упакованное с помощью webpack.
- Адрес онлайн-доступа: https://www.demo.com.
- Адрес сервера статических ресурсов, используемого веб-страницей, — https://oss.demo.com.
- Адрес сервера статических ресурсов для ускорения CDN: https://cdn.demo.com.
- Общедоступный путь ко всем статическим ресурсам на веб-странице был установлен на https://cdn.demo.com во время сборки.
Простой и грубый способ заключается в том, что я создаю дважды, один раз с общедоступным путем, установленным на https://oss.demo.com, один раз с общедоступным путем, установленным на https://cdn.demo.com, и помещаю его на https. ://oss.demo.com по разным путям. Два html-файла размещены на https://www.demo.com, и вы можете отвечать на разные html-файлы, прослушивая разные порты или различая параметры.
Преимущество этого метода в том, что он простой, и нет необходимости заниматься проектом.Недостаток в том, что требуется два комплекта всего проекта, а для добавления строки cdn в дальнейшем будет добавлен еще один комплект. , и требуется сотрудничество среднего слоя, поэтому это решение было мною исключено.
3. Динамическое переключение общедоступных путей
Поскольку вы не хотите собирать несколько раз, вам нужно добиться того, чтобы был только один html, а публичный путь в проекте каким-то образом динамически переключался. запрос ресурсов, чтобы достичь цели переключения CDN.
Во-первых, динамический, самый простой способ — передать параметры через URL, причем разные параметры соответствуют разным адресам. Например, мы устанавливаем параметр cdn, если адрес доступаhttps://www.demo.com?cdn=1когда мы используемhttps://cdn.demo.comкак общественный путь,https://www.demo.com?cdn=0использовать, когдаhttps://oss.demo.comкак общественный путь.
Переключить публичный путь сложнее, выше было сказано, что публичный путь прописывается в проект через конфигурацию при сборке и упаковке вебпака, то есть публичный путь был определен в момент упаковки. упакованный код js, и вы найдете такой код.
// __webpack_public_path__
__webpack_require__.p = "https://oss.demo.com";
(function(module, exports, __webpack_require__) {
eval("module.exports = __webpack_require__.p + \"a.f58ad020.jpg\";\n\n//# sourceURL=webpack:///./a.jpg?");
}),
Мы видим, что общедоступный путь назначен на__webpack_require__.p, если вы хотите динамически переключать публичный путь, это означает, что нам нужно изменить его позже__webpack_require__.pзначение, webpack предоставляет уникальную переменную__webpack_public_path__.
Переменные, специфичные для Webpack, то есть Webpack оборачивает слой функций при упаковке нашего кода.Некоторые переменные передаются через параметры, чтобы мы могли использовать их в коде, даже если эти переменные недоступны в хост-среде (например, браузеры) (например, require , import, export и т. д.).
Нам нужно только присвоить значение __webpack_public_path__, чтобы изменить общедоступный путь.Рекомендуется поместить его в начало файла записи следующим образом:
// publicConfig.js
__webpack_public_path__ = 'https://cdn.demo.com';
// 入口文件 index.js
import './publicConfig.js'
import React from 'react';
import ReactDOM from 'react-dom';
После того, как этот код будет запакован, будет такой кусок контента.
(function(module, exports, __webpack_require__) {
eval("__webpack_require__.p = \"https://cdn.demo.com\";\r\n\n\n//# sourceURL=webpack:///./src/publicConfig.js?");
}),
без сомнений__webpack_require__.pпереназначается, хотя__webpack_require__передается как параметр, но поскольку это ссылочный тип,pтакже изменился.
Вроде проблема решилась легко, но эта модификация только для публичного пути в js файле, и никак не влияет на адреса css и js файлов в html, и картинки вносимые методом url() в файл стиля css также недействителен.
Следует отметить, что если вы не разделяете файл стилей CSS, как в нашем проекте, он имеет форму css в js, и динамическая модификация общего пути к файлу стиля CSS вступит в силу.
Если это проект, созданный Create-React-App, модификация __Webpack_PUBLABLE_PATH__ может не вступить в силу. Он должен быть решаемым, удаляя элемент конфигурации Procer Configuration (не устанавливая его вместо того, чтобы настроить его для пустого), но причина не была глубоко проанализирован.
Окончательное предложение
Может быть сложно решить все наши проблемы за один шаг, просто изменив конфигурацию веб-пакета, возможно, нам придется выполнять разные операции с разными файлами.
- js и css: два набора, публичные пути в каждом наборе файлов разные.
- html: файл html, но так как есть два набора js и css, разные файлы js и css должны динамически загружаться в html.
- Картинки, аудио и видео и другие ресурсы: поскольку используются одни и те же ресурсы, один набор.
Поскольку упаковка веб-пакета на самом деле занимает много времени (это также связано с размером проекта), есть надежда, что все вышеперечисленные шаги можно будет выполнить только один раз.
Реализация схемы
Третий элемент обрабатывать не нужно, просто упаковать его нормально, начиная со второго элемента.
Паковка генерирует два набора файлов js и css с разными публичными путями одновременно, я считаю, что этой функции можно добиться модификацией конфигурации webpack или определенного плагина webpack, но здесь я решил использовать самый простой и грубый способ, замену текста .
Сначала мы начинаем сhttps://oss.demo.comУпакуйте копию для общедоступного пути, затем скопируйте папки js и css, выполните текстовый поиск и замену всех файлов в скопированных двух папках и заменитеhttps://oss.demo.comзаменитьhttps://cdn.demo.com.
Конечно, мы не можем сделать эти вещи вручную.Напишите скрипт узла.Рекомендуется настроить информацию, связанную с CDN, в специальный файл json для удобства управления и будущих добавлений и удалений.Примерный код выглядит следующим образом.
{
"cdnList": ["Cdn","Cdn2"],
"cdnUrl": {
"Default": "oss.demo.com",
"Cdn": "cdn.demo.com",
"Cdn2": "cdn2.demo.com",
}
}
// scripts/replace.js
const path = require("path");
const fs = require("fs-extra"); //fs增强版,用了复制文件夹
const replace = require("replace-in-file"); //替换文件中的文本
const { cdnList, cdnUrl } = require("../project.json");
//创建一个替换任务 oss.demo.com -> cdn.demo.com
const createReplaceOptions = (dir, cdn) => {
return {
files: `${path.resolve(__dirname, `../build/static/${dir}${cdn}/`)}/*.*`,
from: new RegExp(`${cdnUrl.Default}`, "g"),
to: cdnUrl[cdn]
};
};
//创建一个拷贝任务
const createCopy = (dir, cdn) => {
return fs
//文件夹拷贝
.copy(
path.resolve(__dirname, `../build/static/${dir}`),
path.resolve(__dirname, `../build/static/${dir}${cdn}`)
)
//对拷贝后文件夹中所有文件进行文本替换
.then(() => {
const options = createReplaceOptions(dir, cdn);
return replace(options)
.then(results => {
console.log("替换结果:", results);
})
});
};
//根据cdn列表创建对应拷贝替换任务
cdnList.forEach(item => {
const jsCopy = createCopy("js", item);
const cssCopy = createCopy("css", item);
Promise.all([jsCopy, cssCopy])
.then(() => console.log("处理完成!"))
.catch(err => console.error("处理失败:",err));
});
В package.json измените команду сборки и после выполнения упаковки веб-пакета выполните операцию копирования и замены.
{
"scripts": {
"build_cdn": "node scripts/build.js && node scripts/replace.js",
}
}
Советы: используйте && для соединения двух команд.Следующая команда будет выполнена после выполнения предыдущей команды.& это фоновое выполнение предыдущей команды и параллельное выполнение последней команды, поэтому обратите внимание на влияние команды порядок выполнения при его использовании.
Давайте посмотрим на эффект:
Этот шаг решает проблему с открытым путем импорта ресурсов url() в css и js, импортирующих ресурсы напрямую через сетевые адреса. Далее следует динамически импортировать соответствующие файлы css и js. Метод заключается в создании тегов ссылок и тегов скриптов в соответствии с условиями, и вставьте html в.
// cdn.js
var query = parseQueryString(window.location.href); //格式化url参数这里就不写详细代码了
var cdn = query.cdn;
var cdnList = {
Default: "https://oss.demo.com/",
Cdn: "https://cdn.demo.com/",
Cdn2: "https://cdn2.demo.com/"
};
//将判断后的公共路径存储在window上,后面有用。
if (cdnList[cdn]) {
window.publicPath = cdnList[cdn];
} else {
cdn = "";
window.publicPath = cdnList.Default;
}
//动态加载css和js
function asyncAppendNode(tagName, fileName) {
//css,js文件地址
function createUrl(type) {
return window.publicPath + "static/" + type + cdn + "/" + fileName;
}
var node = document.createElement(tagName);
if (tagName === "link") {
node.type = "text/css";
node.rel = "stylesheet";
node.href = createUrl("css");
document.head.appendChild(node);
} else {
node.src = createUrl("js");
document.body.appendChild(node);
}
}
Вносим этот файл cdn.js в заголовок html (или пишем его прямо в html), чтобы он выполнялся первым после загрузки страницы. Однако такой метод введения приведет к тому, что этот файл не будет упакован webpack и скомпилирован без babel, поэтому для совместимости с большим количеством браузеров рекомендуется не использовать слишком новые функции js.
Далее давайте подумаем о введении js и css после оригинальной упаковки webpack.Сначала упаковка webpack будет обрабатывать css и js, и генерировать запакованный файл с хеш-значением в имени файла (для контроля версий), а затем передавать html -webpack.-plugin Этот плагин вводит упакованный файл в html.
Например есть два файла a.css и b.js После упаковки вставка html станет такой.
<head>
<!-- 这个文件是直接在html中添加的所以webpack没有打包 -->
<script src="/cdn.js"></script>
<link href="https://oss.demo.com/a.388e587e.css" rel="stylesheet">
</head>
<body>
<script src="https://oss.demo.com/b.6b602746.js"></script>
</body>
И мы хотим, чтобы генерированный HTML выглядел как следующее.
<head>
<script src="/cdn.js"></script>
<script>
asyncAppendNode("link","a.388e587e.css");
</script>
</head>
<body>
<script>
asyncAppendNode("script","b.6b602746.js");
</script>
</body>
Так как мы написали метод динамической загрузки asyncAppendNode в cdn.js, мы можем вызвать его прямо здесь и передать нужные параметры, я могу вручную модифицировать его после упаковки, но лучше сразу упаковать в желаемом виде.
Теперь последний вопрос, как изменить способ импорта файлов css и js в вызовы функций, не надо думать, что мы можем только через html-webpack-плагин гонять, а только через конфигурацию не можем удовлетворить наши потребности, к счастью, html -webpack-plugin предоставляет нам плагины-расширения, мы можем написать свои собственные плагины для html-webpack-plugin для достижения требуемых функций.
const HtmlWebpackPlugin = require('html-webpack-plugin');
class DynamicLoadHtmlWebpackPlugin {
constructor(options = {}) {
// 配置插件用到的参数,callbackName就是动态加载函数的函数名
// cdnVariableName就我们上面讲过的公共路径存储的变量名,我们cdn.js中是存到了window.publicPath上。
const { callbackName = 'callback', cdnVariableName } = options;
this.callbackName = callbackName;
this.cdnVariableName = cdnVariableName;
}
// 重写html-webpack-plugin的生成数据
rewriteData(node, data, fnName, publicPath) {
//将插入css引用,改为插入函数调用的script。
if (node === 'script') {
const fileNames = data.map((item) =>
item.attributes.href.split('/').pop(),
);
const styleHtml = fileNames
.map((item) => `${fnName}('${node}','${item}');`)
.join('');
return [
{ tagName: 'script', voidTag: false, innerHTML: styleHtml },
];
} else {
//js插入有两类,一类是js文件引用,我们改为插入函数调用的script。还有一类是内联script代码,我们不用改为插入函数调用的形式。但是create-react-app创建的项目,环境变量赋值__webpack_require__.p = xxx是写在这里的,我们就处理一下,将公共路径替换为我们传入的变量名。
const inlineScript = [];
const srcScript = [];
data.forEach((item) => {
if (item.innerHTML) {
if (
typeof publicPath === 'string' &&
this.cdnVariableName
) {
const html = item.innerHTML;
const newHtml = html.replace(
`="${publicPath}"`,
`=${this.cdnVariableName}`,
);
item.innerHTML = newHtml;
}
inlineScript.push(item);
} else {
srcScript.push(item.attributes.src.split('/').pop());
}
});
const scriptHtml = srcScript
.map((item) => `${fnName}('${node}','${item}');`)
.join('');
return [
...inlineScript,
{ tagName: 'script', closeTag: true, innerHTML: scriptHtml },
];
}
}
// HtmlWebpackPlugin在打包过程中,不同生命周期的回调,详细可以参考官方文档,不同的生命周期,数据的内容不同。
apply(compiler) {
compiler.hooks.compilation.tap(
'DynamicLoadHtmlWebpackPlugin',
(compilation) => {
HtmlWebpackPlugin.getHooks(
compilation,
).beforeAssetTagGeneration.tapAsync(
(data, cb) => {
//在这个生命周期中可以拿到webpack配置的publicPath,保存一下。
this.publicPath = data.assets.publicPath;
cb(null, data);
},
);
HtmlWebpackPlugin.getHooks(
compilation,
).afterTemplateExecution.tapAsync(
(data, cb) => {
//在这个生命周期中,js和css的文件名已经确认,要插入标签的相关信息都放在一个数组对象中,很好处理,我们对其进行重写。
const newStyleData = this.rewriteData(
'link',
data.headTags,
this.callbackName,
);
data.headTags = newStyleData;
const newScriptData = this.rewriteData(
'script',
data.bodyTags,
this.callbackName,
this.publicPath,
);
data.bodyTags = newScriptData;
cb(null, data);
},
);
},
);
}
}
module.exports = DynamicLoadHtmlWebpackPlugin;
После того, как плагин написан, его можно внедрить и использовать в webpack.config.js.
const HtmlWebpackPlugin = require("html-webpack-plugin");
const DynamicLoadHtmlWebpackPlugin = require("./dynamicLoadHtmlWebpackPlugin");
module.exports = {
...
plugins:[
new HtmlWebpackPlugin(),
new DynamicLoadHtmlWebpackPlugin({
callbackName: "asyncAppendNode",
cdnVariableName: "window.publicPath"
}),
]
...
}
Затем посмотрите на упакованный html.
Показать результаты
На данный момент разработана функция динамического переключения публичного пути проекта.Мы можем управлять сетевым префиксом ресурсов во всем проекте, изменяя параметр url.Наконец, мы моделируем реальную ситуацию.Адрес сайта - localhost :3000, а адрес CDN - localhost соответственно. :3001 и localhost:3002, пусть зависнет адрес 3001.
Эпилог
На самом деле позже возникла мысль, что в период фронтенд-разработки «слэш-энд-бёрн» мы полностью контролировали весь проект, и подобные вышеперечисленные вещи были достаточно просты. Позже фронтенд-проекты постепенно проектировались и автоматизировались, что приносило нам удобство, в то время как некоторые особые и индивидуальные потребности зачастую требовали больше времени и размышлений. Это похоже на то, что изначально было сделано вручную. Если вы хотите что-то изменить, вы можете изменить это, но теперь это производится с помощью машинных форм. Если вы хотите что-то изменить, вы должны начать с форм и машин, которые могут быть более хлопотно. Это напоминает мне точку зрения злодея в американской драме "Бомбардировщик", которую я смотрел некоторое время назад, приносят ли нам промышленность и технологии прогресс или сдержанность, если вам интересно, вы можете посмотреть ту американскую драму.
Конечно, это всего лишь вопрос свободного времени, чтобы поболтать со всеми, я не против инженерии и автоматизации фронтенда, наоборот, я наслаждаюсь пользой, которую это приносит.
Наконец, я хотел бы еще раз подчеркнуть, что это не полный учебник, и я рекомендую всем сделать это, а только предложить идею, Некоторые решения, используемые в нем, могут быть упомянуты, но комбинация не гарантирует, что они будут лучшими. Решение Эта статья или даже эта серия Цель статьи больше состоит в том, чтобы вызвать у всех размышления и обсуждение. Поверхностно получить на бумаге, и я не знаю, что это дело нужно реализовать.Практика работы - лучший способ проверить технологию.Я надеюсь, что эта статья может помочь всем.