предисловие
Дизайн архитектуры разрешений перед интерфейсом всегда был техническим моментом, который привлекал большое внимание.Внедрив в проект схему управления разрешениями, мы можем гибко настроить разрешения пользователей на доступ к связанным страницам.
Например, какие страницы открыты для туристов, к каким страницам можно получить доступ только после входа в систему, а к каким страницам могут получить доступ только определенные роли (например, суперадминистраторы). in, но ограничен ролью части страницы.
Для нужд практической работы во многих проектах (особенно в системах фонового управления) необходимо ввести контроль разрешений.Если общая архитектура разрешений плохо спроектирована или не разработана, это приведет к тому, что различные коды разрешений в проекте будут смешиваться с бизнес-кодами. , что приводит к структурной путанице, и, во-вторых, введение элементов управления разрешениями или расширений функциональных возможностей в новые модули может быть сложным.
Несмотря на то, что внешний интерфейс может выполнять некоторые действия на уровне разрешений, к сожалению, на самом деле разрешения проверяет серверная часть.Например, в программной системе внешний интерфейс не записывает строку кода разрешения, когда пользователь заходит на страницу, на доступ к которой у него нет разрешения.В это время серверная часть может судить о том, что у него есть несанкционированный доступ, и отказываться возвращать данные.Видно, что вся система может нормально работать, даже если передняя часть ничего не делает, но опыт этого приложения очень плохой.Еще одна важная причина заключается в том, что внешний интерфейс делает все проверки разрешений можно обойти путем локальной подделки данных.
Если внешний интерфейс может определить, что пользователь имеет несанкционированный доступ к странице, не позволяйте ему заходить на эту страницу, а затем выводить всплывающую подсказку о том, что у него нет прав доступа, потому что опыт очень плохой.Лучшее решение заключается в том, чтобы напрямую закрыть вход на эти страницы и позволить ему видеть только ту страницу, к которой он может получить доступ.404страница.
Управление разрешениями, выполняемое внешним интерфейсом, вероятно, заключается в том, чтобы сначала принять данные разрешения, отправленные фоном, а затем ввести данные в приложение.Все приложение должно начать управлять отображаемым содержимым и логикой навигации по странице, чтобы для достижения цели контроля разрешений.Контроль разрешений, осуществляемый внешним интерфейсом Хотя он может обеспечить уровень защиты, основной целью является оптимизация опыта.
В этой статье мы будем продвигаться по следующим трем уровням, от простого к сложному, шаг за шагом, чтобы описать реализацию текущей основной схемы управления разрешениями внешнего интерфейса (следующий код будет основан наvue3иvue-router 4демо)
- Контроль разрешений на вход
- Контроль разрешений страницы
- Контроль прав на контент
Контроль разрешений на вход
Что нужно сделать для контроля разрешений на вход в систему, так это понять, какие страницы могут быть доступны туристам, а какие страницы могут быть доступны только после входа в систему. В некоторых программных системах, которые не вводят роли, возможность доступа к странице оценивается по тому, авторизоваться или нет В реальной работе Очень часто.
Реализация этой функции также очень проста, сначала определите маршрут в соответствии с соглашением.
export const routes = [
{
path: '/login', //登录页面
name: 'Login',
component: Login,
},
{
path:"/list", // 列表页
name:"List",
component: List,
},
{
path:"/myCenter", // 个人中心
name:"MyCenter",
component: MyCenter,
meta:{
need_login:true //需要登录
}
}
]
Предположим, что есть три страницы: страница входа, страница списка и страница личного центра. Страница входа и страница списка доступны всем, но для просмотра страницы личного центра необходимо войти в систему. маршрут к этому маршруту.metaобъект и будетneed_loginустановлен вtrue;
Кроме того, для тех страниц, которые можно просматривать только после входа в систему, если пользователь получает доступ без входа в систему, страница будет перенаправлена на страницу входа.После того, как он введет имя пользователя и пароль и нажмет «Войти», он сразу перейдет на страницу, которую он хочет посетить.
На уровне кода,router.beforeEachВышеупомянутое может быть легко достигнуто, оно будет вызываться каждый раз при переходе страницыrouter.beforeEachОбернутая функция, код выглядит следующим образом.
toэто информация о маршрутизации, к которой нужно получить доступ, и получить ее от нееneed_loginЗначение может определить, следует ли входить в систему. Затем изvuexПолучить данные для входа пользователя.
Используется, если пользователь не вошел в систему, а страница для посещения требует входа в систему.nextПерейдите на страницу входа и передайте имя маршрута страницы, к которой нужно получить доступ черезredirect_pageПередайте его в прошлом, и вы можете получить его на странице входаredirect_pageПерейти сразу после успешного входа в систему.
//vue-router4 创建路由实例
const router = createRouter({
history: createWebHashHistory(),
routes,
});
router.beforeEach((to, from, next) => {
const { need_login = false } = to.meta;
const { user_info } = store.state; //从vuex中获取用户的登录信息
if (need_login && !user_info) {
// 如果页面需要登录但用户没有登录跳到登录页面
const next_page = to.name; // 配置路由时,每一条路由都要给name赋值
next({
name: 'Login',
params: {
redirect_page: next_page,
...from.params, //如果跳转需要携带参数就把参数也传递过去
},
});
} else {
//不需要登录直接放行
next();
}
});
Контроль разрешений страницы
Проблема, которая будет обсуждаться в управлении разрешениями на страницы, заключается в том, как назначить разные разрешения на доступ к страницам для разных ролей.Далее давайте разберемся с концепцией ролей.
В некоторых системах с относительно простыми настройками разрешений достаточно использовать первый способ выше, но если в системе вводятся роли, необходимо дополнительно преобразовать и расширить возможности управления разрешениями на основе вышеизложенного.
Роль роли заключается в более персонализированной настройке списка разрешений. Например, в текущей системе есть три роли: обычный член, администратор и суперадминистратор. Обычные члены могут просматривать все содержимое программной системы, но не могут редактировать и удалить контент.Администратор Обладает всеми возможностями обычных участников, а также может удалять и редактировать контент.Суперадминистратор имеет все разрешения системы программного обеспечения, и только он имеет возможность назначить учетную запись администратором или удалить ее личность.
Как только концепция ролей будет введена в программную систему, каждой учетной записи будет назначена соответствующая роль после регистрации и, следовательно, будут соответствующие разрешения.Что нам нужно сделать во внешнем интерфейсе, так это предоставить ему разрешения на доступ и управление соответствующими страницы в соответствии с разными ролями.. Здесь следует отметить, что объект, основанный на внешнем интерфейсе, является ролью, а не учетной записью, потому что учетная запись основана на роли.
Расположение ролей, таких как обычные участники, администраторы и суперадминистраторы, по-прежнему является очень простым способом разделения.В реальных проектах распределение ролей гораздо более детально.Например, для некоторых общих фоновых бизнес-систем система программного обеспечения будет разделены в соответствии с компанией.Каждый отдел компании устанавливает роли, такие как отдел маркетинга, отдел продаж, отдел исследований и разработок и т. д. Каждый член компании будет разделен на соответствующие роли, так что они имеют только те разрешения, которые имеет роль .
Учетные записи некоторых других высших руководителей компании будут разделены на обычных администраторов или старших администраторов, поэтому у них будет больше разрешений, чем у других ролей.
Представленная выше концепция такого количества ролей на самом деле предназначена для понимания структуры разрешений с точки зрения всего стека, но нет необходимости иметь дело с логикой ролей, когда она фактически реализована во внешнем проекте, и эта часть функция в основном выполняется серверной частью.
Теперь предположим, что серверная часть не справляется с ролью внешнего интерфейса, чтобы сделать это полностью.Сначала создайте новый файл конфигурации во внешнем интерфейсе, предполагая, что текущая система устанавливает три роли: обычные участники, администраторы и суперадминистраторы, а также те, к которым может получить доступ каждая роль.Список страниц (псевдокод ниже).
export const permission_list = {
member:["List","Detail"], //普通会员
admin:["List","Detail","Manage"], // 管理员
super_admin:["List","Detail","Manage","Admin"] // 超级管理员
}
Каждое значение в массиве соответствует конфигурации маршрутизации внешнего интерфейса.nameЗначение. Обычные участники могут получить доступ列表页и详情页, администратор может дополнительно получить доступ内容管理页面, суперадминистратор может дополнительно получить доступ人员管理页面.
Весь процесс операции кратко описан следующим образом: когда пользователь успешно входит в систему, данные пользователя и роль, которой они принадлежат, становятся известны через возвращаемое значение интерфейса.После получения значения роли перейдите в файл конфигурации, чтобы вынуть список страниц, к которым может получить доступ роль, а затем поместите эту часть данных разрешения. Загрузите ее в приложение для достижения цели управления разрешениями.
Из приведенного выше процесса также можно разместить роли в конфигурации внешнего интерфейса, однако, если проект уже находится в сети, менеджер продукта запрашивает, что проекту срочно необходимо добавить новую роль.合作伙伴, и поместите исходного существующего пользователя张三перейти к合作伙伴Ниже роли. Такое изменение приведет к тому, что внешний интерфейс изменит файл кода и создаст новую роль в исходном файле конфигурации, чтобы удовлетворить этому требованию.
Можно заметить, что очень негибко и подвержено ошибкам настраивать список ролей во внешнем интерфейсе, поэтому оптимальное решение — предоставить его для настройки внутреннему серверу. Интерфейс напрямую возвращает список разрешений, принадлежащих учетной записи. Что касается роли, к которой принадлежит учетная запись, и разрешений страницы, которые имеет роль, все они оставляются для обработки в бэкэнде.
После успешного входа пользователя в систему данные внутреннего интерфейса возвращаются следующим образом.
{
user_id:1,
user_name:"张三",
permission_list:["List","Detail","Manage"]
}
Фронтенду не нужно обращать внимание на то, какой роли сейчас принадлежит Чжан Сан, ему нужно только дать ему соответствующие права доступа согласно списку разрешений Чжан Саня, а остальное передается на обработку бэкенду.
возвращаемое значение через интерфейсpermission_listВидно, что Чжан Сан может получить доступ列表页,详情页а также内容管理页Вернемся к странице конфигурации маршрутизации, чтобы посмотреть, как ее настроить.
//静态路由
export const routes = [
{
path: '/login', //登录页面
name: 'Login',
component: Login,
},
{
path:"/myCenter", // 个人中心
name:"MyCenter",
component: MyCenter,
meta:{
need_login:true //需要登录
}
},
{
path:"/", // 首页
name:"Home",
component: Home,
}
]
//动态路由
export const dynamic_routes = [
{
path:"/list", // 列表页
name:"List",
component: List
},
{
path:"/detail", // 详情页
name:"Detail",
component: Detail
},
{
path:"/manage", // 内容管理页
name:"Manage",
component: Manage
},
{
path:"/admin", // 人员管理页
name:"Admin",
component: Admin
}
]
Теперь разделите все маршруты на две части, статические маршрутыroutesи динамическая маршрутизацияdynamic_routes.К страницам статического маршрута могут получить доступ все роли.Он в основном различает доступ с входом и доступ без входа.Логика обработки такая же, как описано выше.登录权限控制Последовательный.
динамическая маршрутизацияdynamic_routesВ нем хранятся страницы, связанные с настройкой ролей.Теперь продолжаем смотреть на данные интерфейса Чжан Саня ниже, как установить для него разрешения.
{
user_id:1,
user_name:"张三",
permission_list:["List","Detail","Manage"]
}
После успешного входа пользователя указанная выше информация об интерфейсе обычно сохраняется вvuexиlocalStorageВнутри.Если в это время браузер обновляется, нам нужно динамически добавлять информацию о маршрутизации.
import store from "@/store";
export const routes = [...]; //静态路由
export const dynamic_routes = [...]; //动态路由
const router = createRouter({ //创建路由对象
history: createWebHashHistory(),
routes,
});
//动态添加路由
if(store.state.user != null){ //从vuex中拿到用户信息
//用户已经登录
const { permission_list } = store.state.user; // 从用户信息中获取权限列表
const allow_routes = dynamic_routes.filter((route)=>{ //过滤允许访问的路由
return permission_list.includes(route.name);
})
allow_routes.forEach((route)=>{ // 将允许访问的路由动态添加到路由栈中
router.addRoute(route);
})
}
export default router;
Основной код заключается в динамическом добавлении маршрутизации, которая в основном используетvue-router 4который предоставилAPIкоторыйrouter.addRoute, он может продолжать добавлять информацию о маршрутизации в уже созданный экземпляр маршрутизации.
Давайте начнем сvuexОн получает список разрешений текущего пользователя, а затем проходит массив динамической маршрутизации.dynamic_routes, отфильтруйте маршруты, к которым разрешен доступ, и, наконец, динамически добавьте эти маршруты в экземпляр маршрутизации.
Таким образом, пользователь может получить доступ к соответствующим страницам только в соответствии с правилами в его соответствующем списке разрешений.Что касается тех страниц, на доступ к которым у него нет разрешения, экземпляр маршрутизации вообще не добавляет соответствующую информацию о маршрутизации, поэтому даже если пользователь принудительно вводит путь в браузере Неавторизованный доступ также недоступен.
так какvue-router 4отменил предыдущийrouter.addRoutes, заменяетсяrouter.addRoute, Вы можете добавлять информацию о маршрутизации только одну за другой, поэтомуallow_routesПовторите цикл, чтобы добавить.
Лучше всего инкапсулировать эту часть кода динамически добавляя маршруты, потому что, когда пользователь не вошел в систему в первый раз,store.state.userЕсли он пуст, описанная выше логика динамического добавления маршрутов будет пропущена.Затем, после того, как пользователь успешно войдет в систему и получит информацию из списка разрешений, описанную выше логику динамического добавления маршрутов необходимо выполнить снова.
Добавить вложенные дочерние маршруты
Если форма статического маршрута выглядит следующим образом, теперь я хочу добавить страницу списка вTabsвложенная маршрутизацияchildrenв.
const routes = [
{
path: '/', //标签容器
name: 'Tabs',
component: Tabs,
children: [{
path: '', //首页
name: 'Home',
component: Home,
}]
}
]
export const dynamic_routes = [
{
path:"/list", // 列表页
name:"List",
component: List
}
]
официальныйrouter.addRouteДля удовлетворения таких требований приводится соответствующая конфигурация (код выглядит следующим образом).router.addRouteПринимает два параметра, первый параметр соответствует родительскому маршрутуnameВторой параметр — это информация о дочерней маршрутизации, которую необходимо добавить.
router.addRoute("Tabs", {
path: "/list",
name: "List",
component: List,
});
поменять пользователя
Переключение информации о пользователе является очень распространенной функцией, но приложение может вызвать некоторые проблемы после переключения на другую учетную запись. Например, пользователь сначала входит в систему с помощью суперадминистратора. Поскольку суперадминистратор может получить доступ ко всем страницам, вся информация о маршрутизации страниц будет быть добавлен в экземпляр маршрутизации.
В это время пользователь выходит из учетной записи и входит в систему с учетной записью обычного участника.Без обновления браузера информация о маршрутизации всех страниц по-прежнему хранится в экземпляре маршрутизации, даже если текущая учетная запись является обычным участником, если он заходит на соответствующие страницы без авторизации, маршрут все равно будет прыгать, а это не тот результат, который нам нужен.
Есть два решения.Первое заключается в том, что пользователь обновляет браузер и перезагружается каждый раз после переключения учетных записей.Обновленный экземпляр маршрутизации перенастраивается, поэтому этой проблемы можно избежать, но обновление страницы принесет неприятный опыт.
Второе решение — очистить информацию о стеке маршрутизации, хранящуюся в экземпляре маршрутизации, после того, как пользователь решит выйти из системы (код выглядит следующим образом).
const router = useRouter(); // 获取路由实例
const logOut = () => { //登出函数
//将整个路由栈清空
const old_routes = router.getRoutes();//获取所有路由信息
old_routes.forEach((item) => {
const name = item.name;//获取路由名词
router.removeRoute(name); //移除路由
});
//生成新的路由栈
routes.forEach((route) => {
router.addRoute(route);
});
router.push({ name: "Login" }); //跳转到登录页面
};
Удаление одного маршрута в основном использует официально предоставленныйAPI,которыйrouter.removeRoute.
После очистки стека маршрутизации ни к каким страницам нельзя получить доступ, даже к странице входа, поэтому необходимо добавить список статической маршрутизации.routesпринести, использоватьrouter.addRouteЗатем добавьте запись, чтобы восстановить исходное состояние стека маршрутизации.
Контроль прав на контент
页面权限控制Он может разрешить разным ролям доступ к разным страницам, но для некоторых проектов с меньшей степенью детализации, например, ожидается, что разные роли смогут войти на страницу, но содержимое страницы, которое они должны видеть, отличается, что требует разрешения. контроль содержания..
Предположим, что интерфейс серверной бизнес-системы показан на рисунке ниже. В таблице хранятся данные списка. При нажатии на запрос на выпуск вы перейдете на новую страницу. После проверки определенного фрагмента данных в список, нажмите кнопку «Изменить», чтобы отобразить изменение.Всплывающее окно фрагмента данных.Также нажмите кнопку «Удалить», чтобы отобразить всплывающее окно для удаления фрагмента данных.
Предположим, что в системе есть три роли для требований проекта: персонал, руководство и высшее руководство.修改,删除а также发布需求функция, он может только просматривать列表.Когда персонал входит на страницу, страница отображает только列表содержание, остальные три кнопки удаляются.
Сохранение руководящей роли列表и发布需求Кнопка Старшие руководители сохраняют за собой все содержимое страницы.
После того, как мы получим картинку, мы должны сначала проанализировать содержимое страницы в целом, согласно增删查改Четыре параметра для категоризации содержимого страницы. Используйте сокращения.CURDидентифицировать (CURDсоответственно представляют творение (Create), обновить (Update), читать (Retrieve) и удалить (Delete)).
На фото выше列表Контент является операцией запроса, поэтому для него задано значениеR.всеRпользователи с разрешениями будут отображать列表содержание.
发布需求Он принадлежит только что добавленной операции.CЭта кнопка отображается для пользователей с правами доступа.
так же修改кнопка соответствуетUразрешения,删除кнопка соответствуетDразрешения.
Отсюда можно сделать вывод, что полномочия роли сотрудника на этой странице закодированы какR, он может только просматривать列表Контентом нельзя манипулировать.
Разрешения, соответствующие роли лидера, кодируются какCR, Код полномочий, соответствующий старшему руководителю,CURD.
Теперь, когда пользователь вошел в систему, предположим, что данные, возвращаемые внутренним интерфейсом, выглядят следующим образом (сохраните эти данные вvuex):
{
user_id:1,
user_name:"张三",
permission_list:{
"List":"CR", //权限编码
"Detail":"CURD" //权限编码
}
}
В дополнение к страницам, заданным статическим маршрутом, Чжан Сан может посещать только дополнительныеListстраница со списком иDetailСтраница сведений. У него есть разрешение только на создание и добавление на странице списка, и у него есть все разрешения на добавление, удаление, проверку и изменение страницы сведений. Затем, когда Чжан Сан получает доступ к странице на изображении выше, страница должна только отображать列表и发布需求кнопка.
Что нам нужно сделать сейчас, так это разработать схему, максимально упрощающую управление содержимым страницы с помощью кодирования разрешений.Сначала создайте глобальную пользовательскую директиву.permission, код показан ниже:
import router from './router';
import store from './store';
const app = createApp(App); //创建vue的根实例
app.directive('permission', {
mounted(el, binding, vnode) {
const permission = binding.value; // 获取权限值
const page_name = router.currentRoute.value.name; // 获取当前路由名称
const have_permissions = store.state.permission_list[page_name] || ''; // 当前用户拥有的权限
if (!have_permissions.includes(permission)) {
el.parentElement.removeChild(el); //不拥有该权限移除dom元素
}
},
});
Когда элемент смонтирован, перейдитеbinding.valueПолучите код разрешения, требуемый элементом.Затем получите текущее имя маршрута, через имя маршрута вы можетеvuexПолучите код разрешения, который есть у пользователя на странице.Если у пользователя нет разрешения на доступ к элементу, поместите элементdomУдалить.
В соответствии с приведенным выше случаем используйте его на странице следующим образом.v-permissionинструкция.
<template>
<div>
<button v-permission="'U'">修改</button> <button v-permission="'D'">删除</button>
</div>
<p>
<button v-permission="'C'">发布需求</button>
</p>
<!--列表页-->
<div v-permission="'R'">
...
</div>
</template>
Сочетая приведенный выше код шаблона и пользовательские инструкции, легко понять логику всего управления правами доступа к контенту.Во-первых, при разработке передней страницы страницу следует снова проанализировать, и каждый фрагмент контента должен быть классифицирован в соответствии с код разрешения. Например, кнопка изменения принадлежитU, кнопка удаления принадлежитD.Использовать вместеv-permissionЗаполните результаты анализа.
Когда страница загружается, все определения на страницеv-permissionбудет запущена внутри пользовательской директивы, она начнется сvuexУдалите код разрешения, принадлежащий пользователю, а затем объедините его с кодом, установленным элементом, чтобы определить, имеет ли терминал разрешение на отображение, и удалите элемент, если разрешение не существует.
Хотя процесс анализа немного сложен, для каждой новой страницы очень удобно получать доступ к контролю разрешений в будущем.domэлемент добавляетv-permissionИ кодирование разрешений завершено, и остальная часть работы передана пользовательской инструкции для ее выполнения.
продлевать
Если операция удаления в элементе размещена не в одной кнопке, а в комплекте со списком и помещена в последний столбец таблицы, как показано на следующем рисунке.
Такие стили интерфейса очень часто встречаются в реальной работе, но кажется, что вышеперечисленноеv-permissionПоддерживать такой стиль недружелюбно, хотя в этом случае нельзя использовать пользовательские директивы, мы все же можем использовать ту же идею для оптимизации существующей структуры кода.
Например, код шаблона выглядит следующим образом: весь список инкапсулирован в компонентList, затем вListВнутри можно написать много логического управления.
НапримерListкомпоненты также могут бытьvuexПолучить код разрешения пользователя на текущей странице, если он обнаруженDРазрешения отображаются в конце списка删除Тот столбец, иначе он не будет отображаться.Что касается отображения и скрытия всего списка, то его еще можно использоватьv-permissionконтролировать.
<template>
<div>
<button v-permission="'C'">添加资源</button>
</div>
<!--列表页-->
<List v-permission="'R'">
...
</List>
</template>
Динамическая навигация
Динамическая навигация на рисунке ниже также является очень распространенным требованием в реальной работе.Например, все сотрудники отдела продаж могут видеть только две страницы в модуле продаж, и аналогично сотрудники отдела закупок могут видеть только страницы в разделе модуль закупок.
Следующие компоненты навигации боковой панели должны отображать разные структуры страниц в соответствии с разными разрешениями, чтобы соответствовать требованиям разных групп ролей.
Нам нужно использовать этот компонент, который необходимо персонализировать с помощью вышеуказанногоv-permissionРежим управления различается Причина, по которой можно использовать вышеуказанные страницыv-permissionОсновная причина в том, что менеджер по продукту проектирует страницы всей программной системы в соответствии с增删查改Поэтому мы можем абстрагироваться от существующих в ней общих черт и законов, а затем использовать пользовательские инструкции для упрощения разработки разрешительной системы.
Однако, как правило, в глобальном масштабе существует только один компонент боковой панели, и специального правила нет, его нужно использовать только внутри компонента.v-ifОн может динамически отображаться в соответствии со значением разрешения.
Например, фоновый интерфейс выглядит следующим образом:
{
user_id:1,
user_name:"张三",
permission_list:{
"SALE":true, //显示销售大类
"S_NEED":"CR", //权限编码
"S_RESOURCE":"CURD", //权限编码
}
}
Чжан Сан имеет доступ需求и资源страница, но обратите вниманиеSALEЭто не соответствует какой странице, это просто указывает, отображать ли销售Это уровень навигации.
Далее в компоненте боковой панели черезvuexПолучите данные разрешения, а затем динамически визуализируйте страницу.
<template>
<div v-if="permission_list['HOME']">系统首页</div>
<div v-if="permission_list['SALE']">
<p>销售</p>
<div v-if="permission_list['S_NEED']">需求</div>
<div v-if="permission_list['S_RESOURCE']">资源</div>
</div>
<div v-if="permission_list['PURCHASE']">
<p>采购</p>
<div v-if="permission_list['P_NEED']">需求</div>
<div v-if="permission_list['P_RESOURCE']">资源</div>
</div>
</template>
конечные слова
Контроль разрешений, предоставляемый внешним интерфейсом, усиливает уровень страховки для приложения, но в то же время мы должны также знать, что проверка, установленная внешним интерфейсом, может быть взломана техническими средствами.Проблема разрешений связана с безопасность всех данных в программной системе, и ее важность очевидна.
Чтобы обеспечить бесперебойную работу системы, как передняя, так и задняя части должны защищать свои собственные разрешения.