Browse Source

feat(persistence): 接入 async-storage 持久化登录/引导/角色态,跨重启保留

master
doublex001 2 months ago
parent
commit
816bd2acac
  1. 1
      package.json
  2. 60
      src/context/AuthContext.tsx
  3. 15
      src/context/RoleContext.tsx
  4. 37
      src/lib/storage.ts

1
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",

60
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<MemoryAuth | null> {
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>(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);
};

15
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<RoleContextValue | undefined>(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<AppRole | null> {
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;

37
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<string | null> {
try {
return await AsyncStorage.getItem(KEY_PREFIX + key);
} catch {
return null;
}
}
/** 写入字符串值,异常时静默忽略 */
export async function storageSet(key: string, value: string): Promise<void> {
try {
await AsyncStorage.setItem(KEY_PREFIX + key, value);
} catch {
/* 存储不可用时降级为内存态,不抛出 */
}
}
/** 删除键,异常时静默忽略 */
export async function storageRemove(key: string): Promise<void> {
try {
await AsyncStorage.removeItem(KEY_PREFIX + key);
} catch {
/* 同上 */
}
}
Loading…
Cancel
Save