refactor(data): add I*Repository interfaces and depend on them in stores
为 /src/data/repositories/ 下每个仓库类增加对应的接口契约,
接口文件存放在独立的 interfaces/ 子目录中。
改动:
- 新建 4 个接口文件 (iauth_repository / ichat_repository /
imetrics_repository / iuser_repository),纯 type-only,方法签名
与实现类完全一致
- 4 个实现类加 implements I{Name}Repository 子句,编译器自动校验契约
- 3 个 stores 文件 (auth / chat / user state machines) 改为依赖接口
类型:通过 `const xxxRepo: I{Name}Repository = xxxRepository` 局部别名,
actor 内部调用全部走别名
- barrelsby.json 加入 interfaces/ 目录,让自动 barrel 能覆盖
- Auto-regenerated repositories/index.ts 与 interfaces/index.ts
参照 storage 层已有的 IAuthStorage / IChatStorage / IUserStorage 模式。
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"directory": [
|
||||
"./src/data/repositories",
|
||||
"./src/data/repositories/interfaces",
|
||||
"./src/data/services/api",
|
||||
"./src/data/dto/auth",
|
||||
"./src/data/dto/chat",
|
||||
|
||||
@@ -27,6 +27,7 @@ import { RegisterRequest } from "@/data/dto/auth/register_request";
|
||||
import { SendCodeRequest } from "@/data/dto/auth/send_code_request";
|
||||
import { User } from "@/data/dto/user/user";
|
||||
import { Result } from "@/utils/result";
|
||||
import type { IAuthRepository } from "@/data/repositories/interfaces/iauth_repository";
|
||||
import { AuthStorage } from "@/data/storage/auth/auth_storage";
|
||||
import type { IAuthStorage } from "@/data/storage/auth/iauth_storage";
|
||||
import { UserStorage } from "@/data/storage/user/user_storage";
|
||||
@@ -35,7 +36,7 @@ import type { IUserStorage } from "@/data/storage/user/iuser_storage";
|
||||
/** 硬编码平台名,对齐 Dart `PlatformUtil.platformName.toLowerCase()`(Web 平台)。 */
|
||||
const WEB_PLATFORM = "web";
|
||||
|
||||
export class AuthRepository {
|
||||
export class AuthRepository implements IAuthRepository {
|
||||
constructor(
|
||||
private readonly api: AuthApi,
|
||||
private readonly storage: IAuthStorage,
|
||||
|
||||
@@ -21,10 +21,11 @@ import { ChatMessage } from "@/data/dto/chat/chat_message";
|
||||
import { ChatSendResponse } from "@/data/dto/chat/chat_send_response";
|
||||
import { SendMessageRequest } from "@/data/dto/chat/send_message_request";
|
||||
import { Result } from "@/utils/result";
|
||||
import type { IChatRepository } from "@/data/repositories/interfaces/ichat_repository";
|
||||
import { LocalChatStorage } from "@/data/storage/chat/local_chat_storage";
|
||||
import { LocalMessage } from "@/data/storage/chat/local_message";
|
||||
|
||||
export class ChatRepository {
|
||||
export class ChatRepository implements IChatRepository {
|
||||
constructor(
|
||||
private readonly api: ChatApi,
|
||||
private readonly localStorage: LocalChatStorage,
|
||||
|
||||
@@ -6,3 +6,7 @@ export * from "./auth_repository";
|
||||
export * from "./chat_repository";
|
||||
export * from "./metrics_repository";
|
||||
export * from "./user_repository";
|
||||
export * from "./interfaces/iauth_repository";
|
||||
export * from "./interfaces/ichat_repository";
|
||||
export * from "./interfaces/imetrics_repository";
|
||||
export * from "./interfaces/iuser_repository";
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* IAuthRepository 接口
|
||||
*
|
||||
* 对齐 Dart 端 `AuthRepository` 抽象(lib/data/repositories/auth_repository.dart):
|
||||
* - 登录 / 登出 / 刷新 token / 第三方 OAuth 登录的公共契约
|
||||
* - 登出强制清本地态(无论 API 成败)
|
||||
* - `getCurrentUser` 成功后尽力写本地 User 缓存(best-effort)
|
||||
* - 所有方法返回 `Promise<Result<T>>`,与 Dart `Future<Result<T>>` 对齐
|
||||
*
|
||||
* 注:仓库内私有方法(`_socialLogin` / `_saveLoginData`)不暴露在接口上。
|
||||
* 原始 Dart: lib/data/repositories/auth_repository_impl.dart
|
||||
*/
|
||||
|
||||
import type { Result } from "@/utils/result";
|
||||
import type { GuestLoginResponse } from "@/data/dto/auth/guest_login_response";
|
||||
import type { LoginResponse } from "@/data/dto/auth/login_response";
|
||||
import type { RefreshTokenResponse } from "@/data/dto/auth/refresh_token_response";
|
||||
import type { User } from "@/data/dto/user/user";
|
||||
|
||||
export interface IAuthRepository {
|
||||
/** 用户注册。注册成功不会自动登录(与 Dart 行为一致)。 */
|
||||
register(input: {
|
||||
username: string;
|
||||
email: string;
|
||||
password: string;
|
||||
guestId?: string;
|
||||
}): Promise<Result<void>>;
|
||||
|
||||
/** 邮箱/用户名 + 密码登录。 */
|
||||
emailLogin(input: {
|
||||
email?: string;
|
||||
username?: string;
|
||||
password: string;
|
||||
guestId?: string;
|
||||
}): Promise<Result<LoginResponse>>;
|
||||
|
||||
/** 发送邮箱验证码。 */
|
||||
sendCode(email: string): Promise<Result<void>>;
|
||||
|
||||
/**
|
||||
* 退出登录:先调 API 注销服务端会话,**无论成败**都清本地登录态。
|
||||
*/
|
||||
logout(): Promise<Result<void>>;
|
||||
|
||||
/** 游客登录:使用 deviceId 换取 guest token。 */
|
||||
guestLogin(deviceId: string): Promise<Result<GuestLoginResponse>>;
|
||||
|
||||
/** Google 登录。 */
|
||||
googleLogin(input: {
|
||||
idToken: string;
|
||||
guestId?: string;
|
||||
}): Promise<Result<LoginResponse>>;
|
||||
|
||||
/** Facebook 登录(accessToken 流程)。 */
|
||||
facebookLogin(input: {
|
||||
accessToken: string;
|
||||
guestId?: string;
|
||||
}): Promise<Result<LoginResponse>>;
|
||||
|
||||
/** Facebook ID 登录(v7.0 新增,fbId 流程)。 */
|
||||
facebookIdLogin(input: {
|
||||
fbId: string;
|
||||
avatarUrl?: string;
|
||||
}): Promise<Result<LoginResponse>>;
|
||||
|
||||
/** Apple 登录。 */
|
||||
appleLogin(identityToken: string): Promise<Result<LoginResponse>>;
|
||||
|
||||
/** 刷新 token:先读本地的 refresh token,空则返回错误。 */
|
||||
refreshToken(): Promise<Result<RefreshTokenResponse>>;
|
||||
|
||||
/** 获取当前登录用户。成功后尽力写本地 User 缓存。 */
|
||||
getCurrentUser(): Promise<Result<User>>;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* IChatRepository 接口
|
||||
*
|
||||
* 对齐 Dart 端 `ChatRepository` 抽象(lib/data/repositories/chat_repository.dart):
|
||||
* - 远程:发送消息、获取历史
|
||||
* - 本地:写入 / 批量覆盖 / 读取 / 清空 / 计数
|
||||
*
|
||||
* 私有助手(`_chatToLocal` / `_localToChat` / `_mapLocalStorageResult`)不暴露。
|
||||
* 原始 Dart: lib/data/repositories/chat_repository_impl.dart
|
||||
*/
|
||||
|
||||
import type { Result } from "@/utils/result";
|
||||
import type { ChatHistoryResponse } from "@/data/dto/chat/chat_history_response";
|
||||
import type { ChatMessage } from "@/data/dto/chat/chat_message";
|
||||
import type { ChatSendResponse } from "@/data/dto/chat/chat_send_response";
|
||||
|
||||
export interface IChatRepository {
|
||||
/** 发送一条消息。 */
|
||||
sendMessage(
|
||||
message: string,
|
||||
options?: { image?: string; useWebSocket?: boolean },
|
||||
): Promise<Result<ChatSendResponse>>;
|
||||
|
||||
/** 获取聊天历史,分页参数默认 limit=50, offset=0。 */
|
||||
getHistory(limit?: number, offset?: number): Promise<Result<ChatHistoryResponse>>;
|
||||
|
||||
/** 把一条消息写入本地存储。 */
|
||||
saveMessageToLocal(message: ChatMessage): Promise<Result<void>>;
|
||||
|
||||
/** 批量覆盖写入:先清空本地存储,再写入新列表。 */
|
||||
saveMessagesToLocal(messages: readonly ChatMessage[]): Promise<Result<void>>;
|
||||
|
||||
/** 读取所有本地消息,按 dbId 升序。 */
|
||||
getLocalMessages(): Promise<Result<ChatMessage[]>>;
|
||||
|
||||
/** 清空本地消息。 */
|
||||
clearLocalMessages(): Promise<Result<void>>;
|
||||
|
||||
/** 获取本地消息数量。 */
|
||||
getLocalMessageCount(): Promise<Result<number>>;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* IMetricsRepository 接口
|
||||
*
|
||||
* 对齐 Dart 端 `MetricsRepository` 抽象(lib/data/repositories/metrics_repository.dart):
|
||||
* - 纯远程 fire-and-forget 上报,无本地状态
|
||||
*
|
||||
* 原始 Dart: lib/data/repositories/metrics_repository_impl.dart
|
||||
*/
|
||||
|
||||
import type { Result } from "@/utils/result";
|
||||
|
||||
export interface IMetricsRepository {
|
||||
/** 上报 PWA 事件。自动注入当前秒级时间戳。 */
|
||||
reportPwaEvent(input: {
|
||||
deviceId: string;
|
||||
deviceType: string;
|
||||
pwaInstalled: boolean;
|
||||
pwaSupported: boolean;
|
||||
}): Promise<Result<void>>;
|
||||
|
||||
/** 上报用户环境信息(浏览器、UA 等)。 */
|
||||
reportUserInfo(input: {
|
||||
userId: string;
|
||||
browser: string;
|
||||
userAgent: string;
|
||||
}): Promise<Result<void>>;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* @file Automatically generated by barrelsby.
|
||||
*/
|
||||
|
||||
export * from "./iauth_repository";
|
||||
export * from "./ichat_repository";
|
||||
export * from "./imetrics_repository";
|
||||
export * from "./iuser_repository";
|
||||
@@ -0,0 +1,37 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* IUserRepository 接口
|
||||
*
|
||||
* 对齐 Dart 端 `UserRepository` 抽象(lib/data/repositories/user_repository.dart):
|
||||
* - 纯远程调用,不直接写本地 storage(持久化由 AuthRepository 在登录流程中处理)
|
||||
*
|
||||
* 原始 Dart: lib/data/repositories/user_repository_impl.dart
|
||||
*/
|
||||
|
||||
import type { Result } from "@/utils/result";
|
||||
import type { CreditsData } from "@/data/dto/user/credits_data";
|
||||
import type { CreditsHistoryData } from "@/data/dto/user/credits_history_data";
|
||||
import type { UpdateProfileRequest } from "@/data/dto/user/update_profile_request";
|
||||
import type { User } from "@/data/dto/user/user";
|
||||
import type { UserStatsResponse } from "@/data/dto/user/user_stats_response";
|
||||
|
||||
export interface IUserRepository {
|
||||
/** 获取用户统计信息。 */
|
||||
getUserStats(): Promise<Result<UserStatsResponse>>;
|
||||
|
||||
/** 获取当前登录用户信息。 */
|
||||
getCurrentUser(): Promise<Result<User>>;
|
||||
|
||||
/** 更新个人资料。 */
|
||||
updateProfile(request: UpdateProfileRequest): Promise<Result<User>>;
|
||||
|
||||
/** 查询积分余额。 */
|
||||
getCredits(): Promise<Result<CreditsData>>;
|
||||
|
||||
/** 查询积分操作历史,分页参数默认 limit=50, offset=0。 */
|
||||
getCreditsHistory(
|
||||
limit?: number,
|
||||
offset?: number,
|
||||
): Promise<Result<CreditsHistoryData>>;
|
||||
}
|
||||
@@ -11,8 +11,9 @@ import { MetricsApi, metricsApi } from "@/data/services/api";
|
||||
import { AppEvent } from "@/data/dto/metrics/app_event";
|
||||
import { PwaEvent } from "@/data/dto/metrics/pwa_event";
|
||||
import { Result } from "@/utils/result";
|
||||
import type { IMetricsRepository } from "@/data/repositories/interfaces/imetrics_repository";
|
||||
|
||||
export class MetricsRepository {
|
||||
export class MetricsRepository implements IMetricsRepository {
|
||||
constructor(private readonly api: MetricsApi) {}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,8 +15,9 @@ import { UpdateProfileRequest } from "@/data/dto/user/update_profile_request";
|
||||
import { User } from "@/data/dto/user/user";
|
||||
import { UserStatsResponse } from "@/data/dto/user/user_stats_response";
|
||||
import { Result } from "@/utils/result";
|
||||
import type { IUserRepository } from "@/data/repositories/interfaces/iuser_repository";
|
||||
|
||||
export class UserRepository {
|
||||
export class UserRepository implements IUserRepository {
|
||||
constructor(private readonly api: UserApi) {}
|
||||
|
||||
/** 获取用户统计信息。 */
|
||||
|
||||
@@ -14,6 +14,7 @@ import { signIn } from "next-auth/react";
|
||||
import type { AuthProvider } from "@/lib/auth/auth_platform";
|
||||
import type { LoginStatus } from "@/models/auth/login-status";
|
||||
import { authRepository } from "@/data/repositories/auth_repository";
|
||||
import type { IAuthRepository } from "@/data/repositories/interfaces";
|
||||
import { AuthStorage } from "@/data/storage/auth/auth_storage";
|
||||
import { deviceIdentifier } from "@/utils/device_identifier";
|
||||
import { Result } from "@/utils/result";
|
||||
@@ -40,11 +41,13 @@ async function readGuestId(): Promise<string | undefined> {
|
||||
// ============================================================
|
||||
// Actors(异步服务)
|
||||
// ============================================================
|
||||
// 仓库以接口类型注入:调用面只看接口,运行时仍是同一单例
|
||||
const authRepo: IAuthRepository = authRepository;
|
||||
|
||||
const emailLoginActor = fromPromise<LoginStatus, { email: string; password: string }>(
|
||||
async ({ input }) => {
|
||||
const guestId = await readGuestId();
|
||||
const result = await authRepository.emailLogin({ ...input, guestId });
|
||||
const result = await authRepo.emailLogin({ ...input, guestId });
|
||||
if (Result.isErr(result)) throw result.error;
|
||||
return "email" as LoginStatus;
|
||||
},
|
||||
@@ -56,11 +59,11 @@ const emailRegisterThenLoginActor = fromPromise<
|
||||
>(async ({ input }) => {
|
||||
const guestId = await readGuestId();
|
||||
|
||||
const registerResult = await authRepository.register({ ...input, guestId });
|
||||
const registerResult = await authRepo.register({ ...input, guestId });
|
||||
if (Result.isErr(registerResult)) throw registerResult.error;
|
||||
|
||||
// 注册后自动登录(对齐 Dart 行为)
|
||||
const loginResult = await authRepository.emailLogin({
|
||||
const loginResult = await authRepo.emailLogin({
|
||||
email: input.email,
|
||||
password: input.password,
|
||||
guestId,
|
||||
@@ -84,7 +87,7 @@ const oauthSignInActor = fromPromise<void, AuthProvider>(async ({ input }) => {
|
||||
const syncGoogleBackendActor = fromPromise<LoginStatus, { idToken: string }>(
|
||||
async ({ input }) => {
|
||||
const guestId = await readGuestId();
|
||||
const result = await authRepository.googleLogin({
|
||||
const result = await authRepo.googleLogin({
|
||||
idToken: input.idToken,
|
||||
guestId,
|
||||
});
|
||||
@@ -98,7 +101,7 @@ const syncFacebookBackendActor = fromPromise<
|
||||
{ accessToken: string }
|
||||
>(async ({ input }) => {
|
||||
const guestId = await readGuestId();
|
||||
const result = await authRepository.facebookLogin({
|
||||
const result = await authRepo.facebookLogin({
|
||||
accessToken: input.accessToken,
|
||||
guestId,
|
||||
});
|
||||
|
||||
@@ -18,6 +18,7 @@ import { setup, fromPromise, assign } from "xstate";
|
||||
|
||||
import type { UiMessage } from "@/models/chat/ui-message";
|
||||
import { chatRepository } from "@/data/repositories/chat_repository";
|
||||
import type { IChatRepository } from "@/data/repositories/interfaces";
|
||||
import { ChatStorage } from "@/data/storage/chat/chat_storage";
|
||||
import { AuthStorage } from "@/data/storage/auth/auth_storage";
|
||||
import { formatDate } from "@/utils/date";
|
||||
@@ -72,6 +73,9 @@ function mapTotalQuotaResult(
|
||||
return fallback;
|
||||
}
|
||||
|
||||
// 仓库以接口类型注入:调用面只看接口,运行时仍是同一单例
|
||||
const chatRepo: IChatRepository = chatRepository;
|
||||
|
||||
interface InitResult {
|
||||
localMessages: UiMessage[];
|
||||
isGuest: boolean;
|
||||
@@ -86,7 +90,7 @@ async function readInitData(): Promise<InitResult> {
|
||||
const [dailyResult, totalResult, localResult] = await Promise.all([
|
||||
chatStorage.getGuestDailyChatQuota(),
|
||||
chatStorage.getGuestTotalQuota(),
|
||||
isGuest ? chatRepository.getLocalMessages() : Promise.resolve(null),
|
||||
isGuest ? chatRepo.getLocalMessages() : Promise.resolve(null),
|
||||
]);
|
||||
|
||||
return {
|
||||
@@ -115,11 +119,11 @@ const sendMessageHttpActor = fromPromise<
|
||||
{ messages: UiMessage[] },
|
||||
{ content: string }
|
||||
>(async ({ input }) => {
|
||||
const result = await chatRepository.sendMessage(input.content);
|
||||
const result = await chatRepo.sendMessage(input.content);
|
||||
if (Result.isErr(result)) throw result.error;
|
||||
|
||||
// 拉取最新本地历史
|
||||
const local = await chatRepository.getLocalMessages();
|
||||
const local = await chatRepo.getLocalMessages();
|
||||
if (Result.isOk(local) && local.data) {
|
||||
return { messages: localMessagesToUi(local.data) };
|
||||
}
|
||||
@@ -130,7 +134,7 @@ const loadMoreHistoryActor = fromPromise<
|
||||
{ messages: UiMessage[]; hasMore: boolean; newOffset: number },
|
||||
{ offset: number }
|
||||
>(async ({ input }) => {
|
||||
const result = await chatRepository.getHistory(PAGE_SIZE, input.offset);
|
||||
const result = await chatRepo.getHistory(PAGE_SIZE, input.offset);
|
||||
if (Result.isErr(result)) throw result.error;
|
||||
|
||||
const page = localMessagesToUi(result.data.messages);
|
||||
|
||||
@@ -14,6 +14,10 @@ import { setup, fromPromise, assign } from "xstate";
|
||||
import type { UserView } from "@/models/user/user";
|
||||
import { userRepository } from "@/data/repositories/user_repository";
|
||||
import { authRepository } from "@/data/repositories/auth_repository";
|
||||
import type {
|
||||
IAuthRepository,
|
||||
IUserRepository,
|
||||
} from "@/data/repositories/interfaces";
|
||||
import { UserStorage } from "@/data/storage/user/user_storage";
|
||||
import { Result } from "@/utils/result";
|
||||
|
||||
@@ -81,10 +85,14 @@ async function readInitData(): Promise<InitData> {
|
||||
// ============================================================
|
||||
// Actors
|
||||
// ============================================================
|
||||
// 仓库以接口类型注入:调用面只看接口,运行时仍是同一单例
|
||||
const userRepo: IUserRepository = userRepository;
|
||||
const authRepo: IAuthRepository = authRepository;
|
||||
|
||||
const userInitActor = fromPromise<InitData>(async () => readInitData());
|
||||
|
||||
const userFetchActor = fromPromise<UserView | null>(async () => {
|
||||
const result = await userRepository.getCurrentUser();
|
||||
const result = await userRepo.getCurrentUser();
|
||||
if (Result.isErr(result)) return null;
|
||||
const view = toView(result.data.toJson());
|
||||
// 持久化到本地
|
||||
@@ -93,7 +101,7 @@ const userFetchActor = fromPromise<UserView | null>(async () => {
|
||||
});
|
||||
|
||||
const userLogoutActor = fromPromise<void>(async () => {
|
||||
await authRepository.logout();
|
||||
await authRepo.logout();
|
||||
await userStorage.clearUserData();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user