refactor(data): move dto and schemas out of services directory

This commit is contained in:
2026-06-09 17:11:11 +08:00
parent 372b93fe45
commit 50940961ec
88 changed files with 279 additions and 356 deletions
-118
View File
@@ -1,118 +0,0 @@
/**
* Auth 存储 - localStorage 包装
*
* 存储登录 token、刷新 token、游客 token、设备 ID。
* 原始 Dart: lib/data/services/storage/auth_storage.dart
*
* 注:localStorage 仅在浏览器环境可用;
* 在 Server Components 中调用会抛错,应仅在 Client Components 或拦截器中使用。
*/
const STORAGE_KEYS = {
loginToken: "cozsweet.loginToken",
refreshToken: "cozsweet.refreshToken",
guestToken: "cozsweet.guestToken",
deviceId: "cozsweet.deviceId",
} as const;
function getStorage(): Storage | null {
if (typeof window === "undefined") return null;
try {
return window.localStorage;
} catch {
return null;
}
}
export class AuthStorage {
// ============ 登录 Token ============
getLoginToken(): string | null {
return getStorage()?.getItem(STORAGE_KEYS.loginToken) ?? null;
}
setLoginToken(token: string): void {
getStorage()?.setItem(STORAGE_KEYS.loginToken, token);
}
removeLoginToken(): void {
getStorage()?.removeItem(STORAGE_KEYS.loginToken);
}
// ============ 刷新 Token ============
getRefreshToken(): string | null {
return getStorage()?.getItem(STORAGE_KEYS.refreshToken) ?? null;
}
setRefreshToken(token: string): void {
getStorage()?.setItem(STORAGE_KEYS.refreshToken, token);
}
removeRefreshToken(): void {
getStorage()?.removeItem(STORAGE_KEYS.refreshToken);
}
// ============ 游客 Token ============
getGuestToken(): string | null {
return getStorage()?.getItem(STORAGE_KEYS.guestToken) ?? null;
}
setGuestToken(token: string): void {
getStorage()?.setItem(STORAGE_KEYS.guestToken, token);
}
removeGuestToken(): void {
getStorage()?.removeItem(STORAGE_KEYS.guestToken);
}
// ============ 设备 ID ============
getDeviceId(): string | null {
return getStorage()?.getItem(STORAGE_KEYS.deviceId) ?? null;
}
setDeviceId(deviceId: string): void {
getStorage()?.setItem(STORAGE_KEYS.deviceId, deviceId);
}
removeDeviceId(): void {
getStorage()?.removeItem(STORAGE_KEYS.deviceId);
}
// ============ 复合操作 ============
/**
* 是否有登录 token
*/
hasLoginToken(): boolean {
const token = this.getLoginToken();
return token !== null && token.length > 0;
}
/**
* 获取认证 token(优先登录 token,其次游客 token
*/
getAuthToken(): string | null {
const loginToken = this.getLoginToken();
if (loginToken !== null && loginToken.length > 0) return loginToken;
const guestToken = this.getGuestToken();
if (guestToken !== null && guestToken.length > 0) return guestToken;
return null;
}
/**
* 清除所有 auth 数据(登录/刷新/游客 token)
*/
clearAuthData(): void {
this.removeLoginToken();
this.removeRefreshToken();
this.removeGuestToken();
}
}
/**
* 全局单例
*/
export const authStorage = new AuthStorage();
-123
View File
@@ -1,123 +0,0 @@
"use client";
/**
* localStorage 通用包装
*
* 设计要点:
* 1. 全部方法返回 `Promise<Result<T>>`,与 `LocalChatStorage`Dexie/异步)保持签名一致,
* 调用方可以统一 `await` 而不必关心底层是同步还是异步。
* 2. 三重 SSR 保护:
* - 文件顶 `"use client"` 指令
* - 构造时探测 `typeof window`,服务端拿不到 `window.localStorage` 时存 `null`
* - 每次调用入口再做可用性检查,避免运行时炸
* 3. 测试友好:构造函数接受可选 `Storage` 注入,单例可通过 `_resetForTests` 重置。
* 4. `getString` 把空串视作 `null`(对齐 Dart `SpAsyncUtil.getString` 行为)。
* 5. `getJson` / `setJson` 在边界做 Zod 校验,读写都走 schema。
*/
import { type ZodType } from "zod";
import { Result, type Result as ResultT } from "@/utils/result";
const SSR_ERROR_MSG =
"localStorage is not available in this environment (SSR or non-browser)";
export class LocalStorage {
private readonly storage: Storage | null;
private static _instance: LocalStorage | null = null;
constructor(storage?: Storage | null) {
this.storage =
storage !== undefined
? storage
: typeof window !== "undefined"
? window.localStorage
: null;
}
/** 全局单例(仅在浏览器环境有效)。 */
static getInstance(): LocalStorage {
if (!LocalStorage._instance) {
LocalStorage._instance = new LocalStorage();
}
return LocalStorage._instance;
}
/** @internal 测试用:重置单例以便每个测试用例拿到独立实例。 */
static _resetForTests(): void {
LocalStorage._instance = null;
}
private isAvailable(): boolean {
return this.storage !== null;
}
async getString(key: string): Promise<ResultT<string | null>> {
if (!this.isAvailable()) return Result.err(new Error(SSR_ERROR_MSG));
try {
const v = this.storage!.getItem(key);
if (v === null || v === "") return Result.ok(null);
return Result.ok(v);
} catch (e) {
return Result.err(e);
}
}
async setString(key: string, value: string): Promise<ResultT<void>> {
if (!this.isAvailable()) return Result.err(new Error(SSR_ERROR_MSG));
try {
this.storage!.setItem(key, value);
return Result.ok(undefined);
} catch (e) {
return Result.err(e);
}
}
async getJson<T>(key: string, schema: ZodType<T>): Promise<ResultT<T | null>> {
const r = await this.getString(key);
if (!r.success) return Result.err(r.error);
if (r.data === null) {
const value: T | null = null;
return Result.ok(value);
}
try {
const parsed = schema.parse(JSON.parse(r.data));
return Result.ok(parsed);
} catch (e) {
return Result.err(e);
}
}
async setJson<T>(
key: string,
value: T,
schema?: ZodType<T>,
): Promise<ResultT<void>> {
if (!this.isAvailable()) return Result.err(new Error(SSR_ERROR_MSG));
try {
const validated = schema ? schema.parse(value) : value;
this.storage!.setItem(key, JSON.stringify(validated));
return Result.ok(undefined);
} catch (e) {
return Result.err(e);
}
}
async remove(key: string): Promise<ResultT<void>> {
if (!this.isAvailable()) return Result.err(new Error(SSR_ERROR_MSG));
try {
this.storage!.removeItem(key);
return Result.ok(undefined);
} catch (e) {
return Result.err(e);
}
}
async has(key: string): Promise<ResultT<boolean>> {
if (!this.isAvailable()) return Result.err(new Error(SSR_ERROR_MSG));
try {
return Result.ok(this.storage!.getItem(key) !== null);
} catch (e) {
return Result.err(e);
}
}
}