用户静默注册

This commit is contained in:
2026-07-01 02:48:59 +08:00
parent d3dd3a0ad1
commit 9f4577d3d8
18 changed files with 912 additions and 93 deletions
+134 -22
View File
@@ -7,36 +7,148 @@ export const BRAND = {
};
export const apiBase = '/api/v1';
const CLIENT_APP = 'USER_H5';
export async function request<T>(
clientApp: string,
path: string,
options: RequestInit = {},
): Promise<T> {
const token = localStorage.getItem('accessToken');
const headers: Record<string, string> = {
'Content-Type': 'application/json',
'X-Client-App': clientApp,
...(options.headers as Record<string, string>),
};
if (token) headers.Authorization = `Bearer ${token}`;
export type UserProfile = {
id: string;
userNo: string;
phone: string | null;
phoneVerified: boolean;
nickname: string | null;
avatarUrl: string | null;
hasWechat: boolean;
};
const res = await fetch(`${apiBase}${path}`, { ...options, headers });
const json = await res.json();
if (json.code !== 0) throw new Error(json.message || '请求失败');
return json.data as T;
export type SessionPayload = {
accessToken: string;
refreshToken: string;
deviceKey?: string;
phoneVerified: boolean;
user?: UserProfile;
};
const DEVICE_KEY = 'deviceKey';
const ACCESS_TOKEN = 'accessToken';
const REFRESH_TOKEN = 'refreshToken';
export function getDeviceKey() {
return localStorage.getItem(DEVICE_KEY);
}
export function saveAuth(data: { accessToken: string; refreshToken?: string }) {
localStorage.setItem('accessToken', data.accessToken);
if (data.refreshToken) localStorage.setItem('refreshToken', data.refreshToken);
export function saveSession(data: SessionPayload) {
localStorage.setItem(ACCESS_TOKEN, data.accessToken);
localStorage.setItem(REFRESH_TOKEN, data.refreshToken);
if (data.deviceKey) localStorage.setItem(DEVICE_KEY, data.deviceKey);
}
export function saveAuth(data: { accessToken: string; refreshToken?: string; deviceKey?: string }) {
localStorage.setItem(ACCESS_TOKEN, data.accessToken);
if (data.refreshToken) localStorage.setItem(REFRESH_TOKEN, data.refreshToken);
if (data.deviceKey) localStorage.setItem(DEVICE_KEY, data.deviceKey);
}
export function clearAuth() {
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
localStorage.removeItem(ACCESS_TOKEN);
localStorage.removeItem(REFRESH_TOKEN);
}
export function isLoggedIn() {
return !!localStorage.getItem('accessToken');
return !!localStorage.getItem(ACCESS_TOKEN);
}
async function rawRequest<T>(
path: string,
options: RequestInit = {},
token?: string | null,
): Promise<T> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
'X-Client-App': CLIENT_APP,
...(options.headers as Record<string, string>),
};
const authToken = token ?? localStorage.getItem(ACCESS_TOKEN);
if (authToken) headers.Authorization = `Bearer ${authToken}`;
const res = await fetch(`${apiBase}${path}`, { ...options, headers });
const json = await res.json();
if (json.code !== 0) {
const err = new Error(json.message || '请求失败') as Error & { status?: number };
err.status = json.code;
throw err;
}
return json.data as T;
}
export async function request<T>(
_clientApp: string,
path: string,
options: RequestInit = {},
): Promise<T> {
return rawRequest<T>(path, options);
}
export async function bootstrapSession(): Promise<SessionPayload> {
const deviceKey = getDeviceKey();
const data = await rawRequest<SessionPayload>(
'/auth/session/bootstrap',
{
method: 'POST',
body: JSON.stringify(deviceKey ? { deviceKey } : {}),
},
null,
);
saveSession(data);
return data;
}
export async function refreshSession(): Promise<SessionPayload | null> {
const refreshToken = localStorage.getItem(REFRESH_TOKEN);
if (!refreshToken) return null;
try {
const data = await rawRequest<SessionPayload>(
'/auth/token/refresh',
{
method: 'POST',
body: JSON.stringify({ refreshToken }),
},
null,
);
saveSession(data);
return data;
} catch {
return null;
}
}
export async function ensureSession(): Promise<SessionPayload> {
if (isLoggedIn()) {
try {
const me = await rawRequest<UserProfile>('/auth/me');
return {
accessToken: localStorage.getItem(ACCESS_TOKEN) ?? '',
refreshToken: localStorage.getItem(REFRESH_TOKEN) ?? '',
deviceKey: getDeviceKey() ?? undefined,
phoneVerified: !!me.phoneVerified,
user: me,
};
} catch (e) {
const err = e as Error & { status?: number };
if (err.status === 401) {
clearAuth();
} else {
const refreshed = await refreshSession();
if (refreshed) return refreshed;
}
}
}
return bootstrapSession();
}
export async function bindPhone(phone: string, code: string): Promise<SessionPayload> {
const data = await rawRequest<SessionPayload>('/auth/phone/bind', {
method: 'POST',
body: JSON.stringify({ phone, code }),
});
saveSession(data);
return data;
}