• 搭建个人知识付费应用系统-(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 主题切换实现

  • 相关阅读:
    Nosql redis高可用和持久化
    代码随想录刷题|LeetCode 300.最长递增子序列 674. 最长连续递增序列 718. 最长重复子数组
    Tomcat的概述、部署、优化
    十、AT24C02
    黑客(网络安全)技术自学30天
    Java、前端、Python 现在应该如何选择,学习哪个更好?
    MySQL的高可用方案:深入Galera Cluster和ProxySQL
    如何使用SQL工具批量执行SQL文件?(以MySQL和SQLynx为例)
    2022.11.28 英语背诵
    基于51单片机锂电池电压电量检测(原理图+PCB+程序)
  • 原文地址:https://blog.csdn.net/jslygwx/article/details/126438092