视频地址: https://www.bilibili.com/vide...

用户治理

graph TBStart1([Start])-->logout1[登记] --> check1{判断 url 参数} --F--> destroySession[革除 Session 而后 SSO 登记] -->logout1check1 --T--> logout2[跳转 SSO 登记链接]logout2-->Stop1([Stop])Start2([Start]) --登录回调--> callback[记录 Session] --> redierct[跳回页面] --> loader[Loader 读取用户信息] --> check2{是否生效} --F--> getUserInfo[获取用户信息] --> Stop2([Stop])check2 --T--> refreshToken[通过 refreshToken 更新 accessToken] --> getUserInfo 

Session 治理

SessionStorage

import { createCookieSessionStorage } from '@remix-run/node';export const sessionStorage = createCookieSessionStorage({  cookie: {    name: '_session',    sameSite: 'lax',    path: '/',    httpOnly: true,    secrets: [process.env.COOKIE_SECRET || 's3cr3t'],    secure: process.env.NODE_ENV === 'production'  }});export const { getSession, commitSession, destroySession } = sessionStorage;

Token 治理

将 code 换 AccessToken 和 RefreshToken 换 AccessToken 两个办法封装

async function tokenRequest(body) {  const formBody = [];  // eslint-disable-next-line  for (const property in body) {    const encodedKey = encodeURIComponent(property);    // eslint-disable-next-line @typescript-eslint/ban-ts-comment    // @ts-ignore    // eslint-disable-next-line @typescript-eslint/no-unsafe-argument    const encodedValue = encodeURIComponent(body[property]);    formBody.push(`${encodedKey}=${encodedValue}`);  }  const res = await fetch(`${process.env.AUTHING_APP_DOMAIN}/oidc/token`, {    method: 'POST',    headers: {      'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'    },    body: formBody.join('&')  });  const oidcToken = (await res.json()) as OidcResponse;  return oidcToken;}export function code2Token(code: string) {  const body = {    client_id: process.env.AUTHING_APP_ID,    client_secret: process.env.AUTHING_APP_SECRET,    grant_type: 'authorization_code',    code  };  return tokenRequest(body);}export function refreshToken(token: OidcResponse) {  const body = {    client_id: process.env.AUTHING_APP_ID,    client_secret: process.env.AUTHING_APP_SECRET,    grant_type: 'refresh_token',    refresh_token: token.refresh_token  };  return tokenRequest(body);}

i18n

插件: https://remix-i18n.js.cool

装置

npm install --save remix-i18n

配置

export interface RemixI18nOptions {  // 反对的语言  supportedLanguages: string[];  // 失败备选  fallbackLng: string;}
const i18n = new RemixI18n({  supportedLanguages: ['en', 'tl', 'da', 'zh'],  fallbackLng: 'zh'});

增加语言翻译

i18n.set('locale', {  hello: '你好'});

客户端设置

// entry.client.tsximport { hydrate } from 'react-dom';import { RemixBrowser } from 'remix';import { I18nProvider } from 'remix-i18n';import { i18n, getLocale } from '~/i18n';const locale = getLocale(window.location.pathname);i18n.locale(locale);hydrate(  <I18nProvider i18n={i18n}>    <RemixBrowser />  </I18nProvider>,  document);

服务器端设置

// entry.server.tsximport { renderToString } from 'react-dom/server';import { RemixServer } from 'remix';import type { EntryContext } from 'remix';import { I18nProvider } from 'remix-i18n';import { i18n, getLocale } from '~/i18n';export default function handleRequest(  request: Request,  responseStatusCode: number,  responseHeaders: Headers,  remixContext: EntryContext) {  const locale = getLocale(new URL(request.url).pathname);  i18n.locale(locale);  const markup = renderToString(    <I18nProvider i18n={i18n}>      <RemixServer context={remixContext} url={request.url} />    </I18nProvider>  );  responseHeaders.set('Content-Type', 'text/html');  return new Response(`<!DOCTYPE html>${markup}`, {    status: responseStatusCode,    headers: responseHeaders  });}

语言切换

const i18n = useI18n();const location = useLocation();useEffect(() => {  const locale = getLocale(location.pathname);  if (locale !== i18n.locale()) {    i18n.locale(locale);  }}, [location]);

应用模板

const { t } = useI18n();// jsx<h1>{t('hello')}</h1>;

P.S.

遗留问题: 用户扩大信息的获取(抽空摸索分明再持续)

下期主题: DaisyUI 主题切换实现