From 816bd2acac218e06defaae751593c9dcf163c49b Mon Sep 17 00:00:00 2001 From: doublex001 <16157008+doublex001@user.noreply.gitee.com> Date: Tue, 1 Sep 2026 17:00:00 +0800 Subject: [PATCH] =?UTF-8?q?feat(persistence):=20=E6=8E=A5=E5=85=A5=20async?= =?UTF-8?q?-storage=20=E6=8C=81=E4=B9=85=E5=8C=96=E7=99=BB=E5=BD=95/?= =?UTF-8?q?=E5=BC=95=E5=AF=BC/=E8=A7=92=E8=89=B2=E6=80=81=EF=BC=8C?= =?UTF-8?q?=E8=B7=A8=E9=87=8D=E5=90=AF=E4=BF=9D=E7=95=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + src/context/AuthContext.tsx | 60 +++++++++++++++++++++++++++++-------- src/context/RoleContext.tsx | 15 ++++++---- src/lib/storage.ts | 37 +++++++++++++++++++++++ 4 files changed, 94 insertions(+), 19 deletions(-) create mode 100644 src/lib/storage.ts diff --git a/package.json b/package.json index 12b5e61..292b726 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "main": "expo-router/entry", "dependencies": { "@expo/vector-icons": "^15.0.2", + "@react-native-async-storage/async-storage": "2.2.0", "expo": "~57.0.14", "expo-blur": "~57.0.2", "expo-constants": "~57.0.12", diff --git a/src/context/AuthContext.tsx b/src/context/AuthContext.tsx index 8a8a601..983b10e 100644 --- a/src/context/AuthContext.tsx +++ b/src/context/AuthContext.tsx @@ -1,16 +1,16 @@ -import { createContext, useContext, useState, type ReactNode } from 'react'; +import { createContext, useContext, useEffect, useState, type ReactNode } from 'react'; +import { storageGet, storageSet } from '../lib/storage'; /* ===================== 鉴权(引导/登录)上下文 ===================== - * 管理「引导页是否看过」「是否已登录」「登录令牌」。 + * 管理「引导页是否看过」「是否已登录」「登录令牌」「分端标记」。 * 与 RoleContext 配合形成完整漏斗: * 首次启动 → 引导页 → 登录页 → 选择身份(identity-select) → 分端设置 → 对应端 * - 求职者:求职意向设置(job-preference) → 求职端 (tabs) * - 招聘者:企业认证(enterprise-certification) → 企业端 (enterprise) * 两个分端设置步骤均允许跳过,设置完成后置位对应标记,不再重复强制。 * - * 持久化:当前为会话级(内存)实现,App 重启后回到初始态, - * 需重新引导/登录。若已安装 @react-native-async-storage/async-storage, - * 可将底部 memoryAuth 替换为真实读写,即可跨重启记住引导/登录态。 + * 持久化:已接入 @react-native-async-storage/async-storage, + * 五字段以 JSON 整体存入 'auth' 键,App 重启后自动恢复引导/登录/身份态。 */ export interface AuthContextValue { @@ -46,8 +46,9 @@ interface MemoryAuth { enterpriseVerified: boolean; } -/** 会话级内存状态(重启即重置) */ -let memoryAuth: MemoryAuth = { +const AUTH_KEY = 'auth'; + +const DEFAULT_AUTH: MemoryAuth = { onboardingSeen: false, isLoggedIn: false, token: null, @@ -55,42 +56,75 @@ let memoryAuth: MemoryAuth = { enterpriseVerified: false, }; +/** 会话级内存镜像(同步读取用),真实来源为 async-storage */ +let memoryAuth: MemoryAuth = { ...DEFAULT_AUTH }; + +function persistAuth(state: MemoryAuth): void { + memoryAuth = state; + void storageSet(AUTH_KEY, JSON.stringify(state)); +} + +function loadAuth(): Promise { + return storageGet(AUTH_KEY).then((raw) => { + if (!raw) return null; + try { + return JSON.parse(raw) as MemoryAuth; + } catch { + return null; + } + }); +} + export function AuthProvider({ children }: { children: ReactNode }) { const [state, setState] = useState(memoryAuth); + // 启动时从 async-storage 恢复登录/引导/分端标记 + useEffect(() => { + let active = true; + loadAuth().then((stored) => { + if (active && stored) { + memoryAuth = stored; + setState(stored); + } + }); + return () => { + active = false; + }; + }, []); + const completeOnboarding = () => { const next = { ...state, onboardingSeen: true }; - memoryAuth = next; + persistAuth(next); setState(next); }; const login = (token: string) => { const next = { ...state, isLoggedIn: true, token }; - memoryAuth = next; + persistAuth(next); setState(next); }; const logout = () => { - const next = { + const next: MemoryAuth = { ...state, isLoggedIn: false, token: null, candidateSetupDone: false, enterpriseVerified: false, }; - memoryAuth = next; + persistAuth(next); setState(next); }; const completeCandidateSetup = () => { const next = { ...state, candidateSetupDone: true }; - memoryAuth = next; + persistAuth(next); setState(next); }; const completeEnterpriseVerify = () => { const next = { ...state, enterpriseVerified: true }; - memoryAuth = next; + persistAuth(next); setState(next); }; diff --git a/src/context/RoleContext.tsx b/src/context/RoleContext.tsx index c8da690..8725fed 100644 --- a/src/context/RoleContext.tsx +++ b/src/context/RoleContext.tsx @@ -5,6 +5,7 @@ import { useState, type ReactNode, } from 'react'; +import { storageGet, storageSet, storageRemove } from '../lib/storage'; /* ===================== 角色(端)上下文 ===================== * 区分「求职端 candidate」与「企业端 enterprise」两套管路。 @@ -26,16 +27,18 @@ interface RoleContextValue { const RoleContext = createContext(undefined); -let memoryRole: AppRole | null = null; +const ROLE_KEY = 'app_role'; function persistRole(role: AppRole | null): void { - memoryRole = role; - // TODO(持久化): 安装 async-storage 后改为 AsyncStorage.setItem('app_role', role ?? '') + if (role) { + void storageSet(ROLE_KEY, role); + } else { + void storageRemove(ROLE_KEY); + } } function loadRole(): Promise { - return Promise.resolve(memoryRole); - // TODO(持久化): 安装 async-storage 后改为 AsyncStorage.getItem('app_role') + return storageGet(ROLE_KEY).then((stored) => (stored ? (stored as AppRole) : null)); } export function RoleProvider({ children }: { children: ReactNode }) { @@ -44,7 +47,7 @@ export function RoleProvider({ children }: { children: ReactNode }) { useEffect(() => { let active = true; loadRole().then((stored) => { - if (active && stored) setRoleState(stored); + if (active && stored) setRoleState(stored as AppRole); }); return () => { active = false; diff --git a/src/lib/storage.ts b/src/lib/storage.ts new file mode 100644 index 0000000..82dbead --- /dev/null +++ b/src/lib/storage.ts @@ -0,0 +1,37 @@ +import AsyncStorage from '@react-native-async-storage/async-storage'; + +/* ===================== 轻量持久化封装 ===================== + * 统一封装 @react-native-async-storage/async-storage, + * 业务侧只调用 get/set/remove,不感知底层实现; + * 所有读写包 try/catch,存储不可用(如 web 未链接原生模块)时静默降级, + * 不影响内存态功能。键统一加 'mydreamjob:' 前缀避免与其他 App 冲突。 + */ + +const KEY_PREFIX = 'mydreamjob:'; + +/** 读取字符串值,缺失/异常时返回 null */ +export async function storageGet(key: string): Promise { + try { + return await AsyncStorage.getItem(KEY_PREFIX + key); + } catch { + return null; + } +} + +/** 写入字符串值,异常时静默忽略 */ +export async function storageSet(key: string, value: string): Promise { + try { + await AsyncStorage.setItem(KEY_PREFIX + key, value); + } catch { + /* 存储不可用时降级为内存态,不抛出 */ + } +} + +/** 删除键,异常时静默忽略 */ +export async function storageRemove(key: string): Promise { + try { + await AsyncStorage.removeItem(KEY_PREFIX + key); + } catch { + /* 同上 */ + } +}