• 搭建个人知识付费应用系统-(2)用户 Session 管理


    视频地址: https://www.bilibili.com/video/BV1qG4y1a7Ti/

    用户管理

    F
    T
    登录回调
    F
    T
    Start
    注销
    判断 url 参数
    清除 Session 然后 SSO 注销
    跳转 SSO 注销链接
    Stop
    Start
    记录 Session
    跳回页面
    Loader 读取用户信息
    是否失效
    获取用户信息
    Stop
    通过 refreshToken 更新 accessToken

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    i18n

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

    安装

    npm install --save remix-i18n
    
    • 1

    配置

    export interface RemixI18nOptions {
      // 支持的语言
      supportedLanguages: string[];
      // 失败备选
      fallbackLng: string;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    const i18n = new RemixI18n({
      supportedLanguages: ['en', 'tl', 'da', 'zh'],
      fallbackLng: 'zh'
    });
    
    • 1
    • 2
    • 3
    • 4

    添加语言翻译

    i18n.set('locale', {
      hello: '你好'
    });
    
    • 1
    • 2
    • 3

    客户端设置

    // 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
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    服务器端设置

    // 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(`${markup}`, {
        status: responseStatusCode,
        headers: responseHeaders
      });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    语言切换

    const i18n = useI18n();
    const location = useLocation();
    useEffect(() => {
      const locale = getLocale(location.pathname);
      if (locale !== i18n.locale()) {
        i18n.locale(locale);
      }
    }, [location]);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    使用模板

    const { t } = useI18n();
    
    // jsx
    <h1>{t('hello')}</h1>;
    
    • 1
    • 2
    • 3
    • 4

    P.S.

    遗留问题: 用户扩展信息的获取(抽空摸索清楚再继续)

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

  • 相关阅读:
    美国零售电商平台Target,值得入驻吗?如何入驻?
    mmocr的识别模型
    论文的章节有重复率的要求吗?
    【opencv-c++】windows10系统VisualStudio2022配置opencv_contrib-4.6.0
    SpringCloud-Sleuth服务追踪
    【Gopher 学个函数】边学边练,简单为 Go 上个分
    维修Balance Monitor动平衡仪触摸屏 SB-7705s工控电脑
    等保测评一体机是什么
    王道操作系统___第三章01
    一招教你如何高效批量导入与更新数据
  • 原文地址:https://blog.csdn.net/jslygwx/article/details/126438092