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

用户管理

graph TB

Start1([Start])-->
logout1[注销] --> check1{判断 url 参数} --F--> destroySession[清除 Session 然后 SSO 注销] -->logout1
check1 --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.tsx
import { 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.tsx
import { 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 主题切换实现


willin
213 声望12 粉丝

欢迎在各平台 Follow 我。