title: [Frodo-Communications] Шаблоны, добавления, удаления, модификации и аутентификация макет: сообщение дата: 2020-06-09 тег: примечание автор: Чжи-кай Ян
Реализована первая версия Frodo.Перед следующей версией я организую текущие идеи разработки в три статьи, а именно статьи о данных, статьи о коммуникации и асинхронные статьи.
Эта статья подходит к логическому процессу реализации конкретных функций.В резюме веб-приложений я лично предпочитаю называть бизнес-процессы «общением». Поскольку весь процесс заключается в организации и обработке данных от фона к интерфейсу, протокол этого процесса может быть другим (http(s), websocket), метод может быть другим (rcp, ajax, mq) и формат возвращаемого контента разный (json, xml, html) (шаблоны), Flash в ранние годы и т. д.); я только что говорил о взаимодействии между интерфейсом и сервером. На самом деле взаимодействие происходит между логическими модулями , между процессами и даже между последующими контейнерами. В этой статье впервые представлены основы веб-коммуникации, интерфейсной и внутренней связи.
Технология шаблонов и разделение фронтенда и бэкэнда
-
Технология шаблонов: широко распространенная веб-технология в 2000-х годах, более известная как
MVCмодель. Основная идея состоит в том, чтобы использовать внутренний код для записи данных в HTML-шаблон, а механизм шаблонов вернет обработанный HTML-код.DjangoЭта технология встроена, и другие фреймворки Python должны полагаться на нее.jinjia,Makoи другие отдельные шаблоны. другие языки, такие как javaJSPЭтот режим также используется. Его характеристика в том, что операция прямая, и соответствующие данные записываются прямо туда, где они нужны. Вы также можете напрямую использовать внутренний язык для написания логики на странице, и скорость разработки высока. Но недостатки тоже очевидны: передняя и задняя части сильно связаны, а обслуживание сложное, поэтому для масштабных проектов не подходит.- Протокол: http
- метод: все
- Контент: html(шаблоны)
-
Разделение фронтенда и бэкенда: Текущая основная модель, когда проект становится все больше и больше, возникает спрос на фронтенд-инжиниринг.
webpackинструмент. впоследствииVue,React,Angularфокус кадраMVVCРежим, то есть получать данные только из бэкенда, а рендеринг и бизнес-логику помещать во внешний фреймворк. Таким образом, фронтенд и бэкенд разработчики могут быть максимально разделены.- Соглашение: все
- метод: все
- Контент: json/xml
Мако Шаблоны и его друг FastAPI - Мако
Frodo использует шаблоны для отображения лицевой части блога, учитывая, что эта часть страницы небольшая, логика проста, а обслуживающий персонал прост в обслуживании, а шаблона вполне достаточно.Нет устаревших технологий, есть неподходящие технологии.
MakoЭто один из основных шаблонов Python, его собственный интерфейс можно использовать напрямую, но необходимо упаковать некоторую повторяющуюся логику:
- В шаблоне необходимо зафиксировать несколько переменных контекста.
- Объект запроса (объект запроса, используемый внутренней структурой, в
Flask,Django,fastapiсуществует), шаблон должен использовать некоторые из своих методов и свойств, например, обратную адресациюrequest.url_for(),request.host,Четноеrequest.Sessionсодержание в - Контекст контекста запроса (в основном относится к телу, друзья, которые связались с веб-разработкой, могут перечислить основные тела запроса: Formdata, QueryParam, PathParam, они могут использоваться в шаблонах)
- Вернуть контекст (без инкапсуляции, предоставленной Ye Tao)
- Объект запроса (объект запроса, используемый внутренней структурой, в
- Автоматическая адресация файла шаблона
- Статическая адресация файлов
- Обработка исключений шаблона
такой жеFlaskТакой же,fastapiМаршрутизация также функциональна.Чтобы инкапсулировать вышеуказанную функцию шаблона в функцию маршрутизации, прямым методом является использование декоратора python. Окончательный результат выглядит следующим образом:
from fastapi import APIRouter, Request, HTTPException
from fastapi.responses import HTMLResponse
from models import cache, Post
from ext import mako
@router.get('/archives', name='archives', response_class=HTMLResponse)
@mako.template('archives.html') # 指定模板文件名称
@cache(MC_KEY_ARCHIVES)
async def archives(request: Request): # 需要显示地传递 Request
post_data = await Post.async_filter(status=Post.STATUS_ONLINE)
post_obj = [Post(**p) for p in post_data]
post_obj = sorted(post_obj, key=lambda p: p.created_at, reverse=True)
rv = dict()
for year, items in groupby(post_obj, lambda x: x.created_at.year):
if year in rv: rv[year].extend(list(items))
else: rv[year] = list(items)
archives = sorted(rv.items(), key=lambda x: x[0], reverse=True)
# 只返回上下文
return {'archives': archives}
На самом деле это очень легко понять, единственное, что нужно объяснить, это то, почему это явно передаетсяrequest, fastapiИзбегайте прохождения как можно большеrequest, это иFlaskИдея та же, с помощью стека локальных потоков можно полностью различать контекст разных запросов. Но часто в шаблоне нужна обратная адресация, что-то вроде:
% for year, posts in archives:
<h3 class="archive-year-wrap">
<a href="${ request.url_for('archives', year=year) }" class="archive-year">
${ year }
</a>
</h3>
% endfor
@makoПростой декоратор завершен в LouisYZK/FastAPI-Mako, заинтересованные друзья могут взглянуть.
Помимо этого есть@cachedДекоратор, который кэширует шаблон результата функции. Если данные текущей страницы не изменяются, в следующий раз данные будут получены напрямую из Redis. Подробная логика будет представлена в логике CRUD ниже.
Логика связи CRUD
В этом разделе описываются все модели данных, отдельные, такие какPosts,ActivityСколько им нужно способов хранения данных, им нужно больше уловок. И все операции с данными следуют следующему процессу:

контрольный вариант использованияDataModelЭто класс данных, разработанный в главе о данных.У них есть несколько методов для удовлетворения потребностей CRUD.Два наиболее важных момента:
- Сгенерируйте SQL для операции
- Сгенерируйте ключ, используемый кешем базы данных KV.
Оба вышеперечисленных используют некоторыеsqlalchemyи небольшая хитрость декораторов Python. Вы можете сосредоточиться на ссылке на исходный кодmodels/base.pyа такжеmodels/mc.py.
Очень стоит упомянуть, что реализация обновления кеша удаления:
## mc.py中的clear_mc方法
async def clear_mc(*keys):
redis = await get_redis()
print(f'Clear cached: {keys}')
assert redis is not None
await asyncio.gather(*[redis.delete(k) for k in keys],
return_exceptions=True)
## base类中的__flush__方法
from models.mc import clear_mc
@classmethod
async def __flush__(cls, target):
await asyncio.gather(
clear_mc(MC_KEY_ITEM_BY_ID % (target.__class__.__name__, target.id)),
target.clear_mc(), return_exceptions=True
)
## target是具体数据实例,他们重写clear_mc()方法,用于删除指定不同的key, 例如下面的Post类的重写:
async def clear_mc(self):
keys = [
MC_KEY_FEED, MC_KEY_SITEMAP, MC_KEY_SEARCH, MC_KEY_ARCHIVES,
MC_KEY_TAGS, MC_KEY_RELATED % (self.id, 4),
MC_KEY_POST_BY_SLUG % self.slug,
MC_KEY_ARCHIVE % self.created_at.year
]
for i in [True, False]:
keys.append(MC_KEY_ALL_POSTS % i)
for tag in await self.tags:
keys.append(MC_KEY_TAG % tag.id)
await clear_mc(*keys)
Это гарантирует, что каждый раз, когда данные создаются, обновляются и удаляются, соответствующие кэши могут быть удалены для обеспечения согласованности данных. Как вы могли заметить, операция по удалению кеша является ожидаемой, а это значит, что асинхронность здесь может использовать преимущество параллелизма. Итак, мы видимasyncio.gather(*coros)С помощью , он может удалить несколько ключей одновременно, потому что Redis создает пул соединений, который не использует многопоточность,asyncioВот как реализован параллелизм ввода-вывода. (Вообще-то этот пункт надо бы внести в асинхронную статью, но это очень важно).
Сертификация
Необходимость сертификации возникает из двух источников:
-
Серверная часть системы управления контентом может управляться только владельцем блога, например, публикация в блоге, изменение пароля и т. д.
-
Комментарии посетителей требуют аутентификации.
Аутентификация администратора -- с использованием JWT
JWT — один из широко используемых методов аутентификации, и его преимущества перед файлами cookie можно найти в соответствующих статьях. а такжеfastapiПоддержка JWT встроена, и нам очень удобно использовать ее для проверки.
Прежде чем говорить о конкретной реализации, нам еще нужно понять логику его коммуникации:

Приведенный выше поток представляет собой логику входа в систему и общую логику доступа к API, который необходимо аутентифицировать. Вы нашли проблему?TokenГде он хранится?
Где существует Токен? Сервер генерирует Токен и клиент его получает, а ему принесет следующий запрос. Наиболее удобно хранить такие часто используемые и небольшие по объему данные непосредственно в памяти. В языке программирования необходимо совместно использовать глобальные переменные, такие как
multiprocess.Valueзаключается в том, чтобы решить эту проблему. Но асинхронность изучается для цикла событий, в настоящее время нет понятия процесса потока.contextvarОн специально разработан для решения проблемы асинхронного совместного использования переменных и требует, чтобы Python был выше 3.7.
fastapiЧтобы помочь нам поддерживать этот токен, нам нужно только следующее простое определение:
from fastapi.security import OAuth2PasswordBearer
oauth2_scheme = OAuth2PasswordBearer(tokenUrl='/auth')
Это означает, что путь генерации токена/auth,в то же времяoauth2_schemeФорма используется как источник токена для глобальных зависимостей.Всякий раз, когда интерфейсу нужно использовать токен, ему нужно только:
@router.get('/users')
async def list_users(token: str = Depends(oauth2_scheme)) -> schemas.CommonResponse:
users: list = await User.async_all()
users = [schemas.User(**u) for u in users]
return {'items': users, 'total': len(users)}
DependsЭто особенность fastapi, она прописывается прямо в параметрах функции интерфейса и может выполнять некоторую логику перед запросом, аналогично middleware. Логика здесь заключается в том, чтобы проверить, есть ли в заголовке запросаAuth: Bear+Token, без этого запроса сделать нельзя.
Логика генерации токена завершается в интерфейсе входа в систему, что почтиFrodoСамая сложная логика:
@app.post('/auth')
async def login(req: Request, username: str=Form(...), password: str=Form(...)):
user_auth: schemas.User = \
await user.authenticate_user(username, password)
if not user_auth:
raise HTTPException(status_code=400,
detail='Incorrect User Auth.')
access_token_expires = timedelta(
minutes=int(config.ACCESS_TOKEN_EXPIRE_MINUTES)
)
access_token = await user.create_access_token(
data={'sub': user_auth.name},
expires_delta=access_token_expires)
return {
'access_token': access_token,
'refresh_token': access_token,
'token_type': 'bearer'
}
В основном следуйте диаграмме последовательности в этом разделе.
Аутентификация доступа -- используйте сеанс
Аутентификаций доступа много.Ограниченный контентом блога, любой, кто обращается к Frodo, должен иметь Github.Поэтому при использовании его аутентификации логика следующая:

Вся логика очень проста, она следует логике аутентификации Github, если вы меняете способ, например код сканирования WeChat, вам нужно его изменить. Просто обратите внимание на перенаправленный URL. При этом информация посетителя сохраняется и не используется.JWTТеперь, поскольку нет ограничений на устаревшие и т. д., сеансовые файлы cookie являются наиболее прямыми.
@router.get('/oauth')
async def oauth(request: Request):
if 'error' in str(request.url):
raise HTTPException(status_code=400)
client = GithubClient()
rv = await client.get_access_token(code=request.query_params.get('code'))
token = rv.get('access_token', '')
try:
user_info = await client.user_info(token)
except:
return RedirectResponse(config.OAUTH_REDIRECT_PATH)
rv = await create_github_user(user_info)
## 使用session存储
request.session['github_user'] = rv
return RedirectResponse(request.session.get('post_url'))
УведомлениеfastapiЧтобы открыть сеанс, вам нужно добавить промежуточное ПО
from starlette.middleware.sessions import SessionMiddleware
app.add_middleware(SessionMiddleware, secret_key='YOUR KEY')
starletteЧто это? нетfastapi? простоstarletteа такжеfastapiотношения сwerkzurgа такжеFlaskОтношения между WSGI и ASGI такие же, разница между WSGI и ASGI, теперь идея ASGI состоит в том, чтобы выйти за рамки WSGI, конечно, вы должны разработать набор основных стандартов и библиотек инструментов.
Хорошо, коммуникационная логика в основном такая, а коммуникационных моделей, используемых Фродо, все еще очень мало. Следующая статья «Асинхронный» связана как с коммуникацией, так и с данными, разница между асинхронными блогами и блогами, реализованными на общем питоне, здесь.