# 客户端使用指南 本文档详细介绍 @repo/sdk 的客户端调用方式及其配置方法。 ## 目录 1. [推荐方式:root.get (DI 风格)](#1-推荐方式rootget-di-风格) 2. [复用 Schema 和类型](#2-复用-schema-和类型) 3. [Better Auth 插件风格](#3-better-auth-插件风格) 4. [API 函数风格](#4-api-函数风格) 5. [服务端实现](#5-服务端实现) 6. [错误处理](#6-错误处理) --- ## 1. 推荐方式:root.get (DI 风格) **强烈推荐使用此方式**,保证完整的类型安全和最佳开发体验。 ### 基本用法 ```typescript import { root } from '@repo/core' import { ProjectController, TemplateController, TemplateSocialController, AigcController, FileController } from '@repo/sdk' import type { CreateProjectInput, Project, ListProjectsResult } from '@repo/sdk' // 获取控制器实例(完整类型推导) const projectCtrl = root.get(ProjectController) const templateCtrl = root.get(TemplateController) const socialCtrl = root.get(TemplateSocialController) const aigcCtrl = root.get(AigcController) const fileCtrl = root.get(FileController) // 调用方法(参数和返回值都有完整类型提示) const projects: ListProjectsResult = await projectCtrl.list({ page: 1, limit: 10 }) const project: Project = await projectCtrl.create({ title: '新项目', content: {} }) const template = await templateCtrl.get('template-id') await socialCtrl.like({ templateId: 'xxx' }) ``` ### 优势 1. **完整类型安全**:参数和返回值都有精确的类型推导 2. **IDE 支持**:自动补全、跳转定义、重构支持 3. **可测试性**:可以轻松 mock Controller 进行单元测试 4. **前后端一致**:服务端和客户端使用相同的接口定义 5. **灵活性**:可以自定义代理实现(如添加缓存、日志等) ### 前置条件 需要先初始化客户端插件(注册 HTTP 代理): ```typescript import { createAuthClient } from 'better-auth/client' import { createSkerClientPlugin } from '@repo/sdk' // 初始化(会自动将 Controller 替换为 HTTP 代理) const auth = createAuthClient({ baseURL: 'http://localhost:3000', plugins: [createSkerClientPlugin()] }) ``` ### 自定义代理(高级) ```typescript import { root } from '@repo/core' import { ProjectController } from '@repo/sdk' import type { ListProjectsInput, ListProjectsResult } from '@repo/sdk' // 注册带缓存的自定义实现 root.set([{ provide: ProjectController, useFactory: () => ({ list: async (input: ListProjectsInput): Promise => { const cacheKey = `projects-${JSON.stringify(input)}` const cached = cache.get(cacheKey) if (cached) return cached const result = await originalProxy.list(input) cache.set(cacheKey, result) return result }, // ... 其他方法 }) }]) ``` --- ## 2. 复用 Schema 和类型 **强烈推荐复用 SDK 导出的 Zod Schema 和 TypeScript 类型**,确保前后端数据一致性。 ### 导入类型(推荐) ```typescript // 始终从 @repo/sdk 导入类型,不要自己定义 import type { // 项目相关 Project, CreateProjectInput, ListProjectsInput, ListProjectsResult, UpdateProjectInput, TransferProjectInput, // 模板相关 TemplateDetail, CreateTemplateInput, RunTemplateInput, ListTemplatesInput, ListTemplatesResult, // AI 生成 AigcModel, SubmitTaskBody, SubmitTaskResult, GetTaskStatusResult, // 社交 LikeTemplateInput, CreateCommentInput, TemplateComment, GetCommentsResponse, // 消息 Message, ListMessagesInput, UnreadCountResult, // 文件 FileUploadResponse, VideoCompressRequest, ConvertToWebpRequest } from '@repo/sdk' ``` ### 复用 Zod Schema 验证 ```typescript import { CreateProjectSchema, ListProjectsSchema, RunTemplateSchema, SubmitTaskBodySchema, CreateCommentSchema } from '@repo/sdk' // 验证用户输入(抛出错误) function validateInput(data: unknown) { return CreateProjectSchema.parse(data) } // 安全验证(不抛错,返回结果对象) function safeValidate(data: unknown) { const result = CreateProjectSchema.safeParse(data) if (!result.success) { console.error('验证失败:', result.error.flatten()) return null } return result.data // 类型安全的数据 } // 部分验证(用于更新操作) const PartialProjectSchema = CreateProjectSchema.partial() function validatePartialUpdate(data: unknown) { return PartialProjectSchema.parse(data) } ``` ### 前端表单集成 ```typescript import { CreateProjectSchema } from '@repo/sdk' import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' import { root } from '@repo/core' import { ProjectController } from '@repo/sdk' // 从 Schema 推导表单类型 type FormData = z.infer function CreateProjectForm() { const { register, handleSubmit, formState: { errors } } = useForm({ resolver: zodResolver(CreateProjectSchema) // 复用 SDK Schema }) const onSubmit = async (data: FormData) => { const projectCtrl = root.get(ProjectController) const project = await projectCtrl.create(data) // 类型完全匹配 console.log('创建成功:', project.id) } return (
{errors.title && {errors.title.message}} {/* ... */}
) } ``` ### 服务端实现复用类型 ```typescript import { Injectable } from '@repo/core' import { ProjectController } from '@repo/sdk' // 直接复用 SDK 导出的类型 import type { Project, CreateProjectInput, ListProjectsInput, ListProjectsResult, UpdateProjectInput } from '@repo/sdk' @Injectable() export class ProjectControllerImpl implements ProjectController { constructor( private readonly db: DatabaseService, private readonly auth: AuthService ) {} // 参数和返回值类型直接复用 SDK 类型 async create(data: CreateProjectInput): Promise { const userId = this.auth.getCurrentUserId() return this.db.project.create({ data: { ...data, userId } }) } async list(body: ListProjectsInput): Promise { // 实现... } async update(body: UpdateProjectInput): Promise { // 实现... } } ``` --- ## 3. Better Auth 插件风格 **适用场景**:已使用 Better Auth 进行身份认证的项目 ### 安装和配置 ```typescript import { createAuthClient } from 'better-auth/client' import { createSkerClientPlugin } from '@repo/sdk' // 创建带 Sker 插件的 Auth 客户端 const auth = createAuthClient({ baseURL: 'http://localhost:3000', plugins: [createSkerClientPlugin()] }) export { auth } ``` ### 调用方式 ```typescript // 项目管理 const projects = await auth.sker.project.list({ page: 1, limit: 10 }) const project = await auth.sker.project.create({ title: '新项目', content: {} }) const detail = await auth.sker.project.get('project-id') // 模板管理 const templates = await auth.sker.template.list({ page: 1 }) const { generationId } = await auth.sker.template.run({ templateId: 'xxx', formData: {} }) // 社交功能 await auth.sker.templateSocial.like({ templateId: 'xxx' }) const comments = await auth.sker.templateSocial.getComments({ templateId: 'xxx' }) // AI 生成 const models = await auth.sker.aigc.getModels('image') const { task_id } = await auth.sker.aigc.submitTask({ model_name: 'xxx', prompt: 'xxx' }) // 文件上传 const formData = new FormData() formData.append('file', file) const { data: fileUrl } = await auth.sker.file.uploadS3(formData) ``` ### 插件工作原理 `createSkerClientPlugin()` 做了以下事情: 1. **注册 HTTP 代理**:将所有 Controller 替换为 HTTP 调用代理 2. **提供 Better Auth 风格 API**:通过 `auth.sker.xxx` 访问 3. **自动处理认证**:使用 Better Auth 的 session 进行认证 4. **生成路径映射**:自动从 Controller 元数据生成路由 ```typescript // 插件内部实现简化版 export function createSkerClientPlugin(): BetterAuthClientPlugin { return { id: 'sker', getActions: ($fetch) => { // 注册代理到 DI 容器 registerControllerProxies($fetch) // 返回 Better Auth 风格的 actions return buildBetterAuthActions(controllers, $fetch) }, pathMethods: generatePathMethods(controllers) } } ``` --- ## 4. API 函数风格 **推荐场景**:不使用 Better Auth、需要独立的 API 客户端 ### 配置客户端 ```typescript import { client, generateApiFunctions } from '@repo/sdk' // 配置基础 URL 和认证 client.setConfig({ baseUrl: 'http://localhost:3000', auth: async () => { // 返回认证 token return localStorage.getItem('token') || '' } }) // 生成所有 API 函数 const apis = generateApiFunctions() ``` ### 调用方式 函数名格式:`{httpMethod}{Path}`(驼峰命名) ```typescript // POST /loomart/project/list -> postLoomartProjectList const projects = await apis.postLoomartProjectList({ page: 1, limit: 10 }) // GET /loomart/template/get -> getLoomartTemplateGet const template = await apis.getLoomartTemplateGet({ id: 'template-id' }) // POST /loomart/template/run -> postLoomartTemplateRun const { generationId } = await apis.postLoomartTemplateRun({ templateId: 'xxx', formData: {} }) // GET /loomart/aigc/models -> getLoomartAigcModels const models = await apis.getLoomartAigcModels({ category: 'image' }) // POST /loomart/file/upload-s3 -> postLoomartFileUploadS3 const formData = new FormData() formData.append('file', file) const result = await apis.postLoomartFileUploadS3(formData) ``` ### 函数名映射规则 | 路由 | HTTP 方法 | 函数名 | |------|-----------|--------| | /loomart/project/create | POST | postLoomartProjectCreate | | /loomart/project/list | POST | postLoomartProjectList | | /loomart/project/get | GET | getLoomartProjectGet | | /loomart/template/run | POST | postLoomartTemplateRun | | /loomart/aigc/task/submit | POST | postLoomartAigcTaskSubmit | | /loomart/aigc/task/status | GET | getLoomartAigcTaskStatus | | /loomart/file/upload-s3 | POST | postLoomartFileUploadS3 | ### 动态认证 ```typescript client.setConfig({ baseUrl: 'http://localhost:3000', auth: async (authContext) => { // 可以根据上下文动态获取 token if (authContext?.refreshToken) { // 刷新 token const newToken = await refreshAccessToken(authContext.refreshToken) return newToken } return localStorage.getItem('token') || '' } }) ``` --- ## 5. 服务端实现 **场景**:在 Hono/Express 等后端框架中实现 Controller ### 注册控制器实现 ```typescript import { root } from '@repo/core' import { ProjectController } from '@repo/sdk' import { ProjectControllerImpl } from './impl/project.controller.impl' // 注册实现类 root.set([ { provide: ProjectController, useClass: ProjectControllerImpl } ]) ``` ### 实现类示例 ```typescript import { Injectable } from '@repo/core' import { ProjectController } from '@repo/sdk' import type { Project, CreateProjectInput, ListProjectsInput, ListProjectsResult } from '@repo/sdk' @Injectable() export class ProjectControllerImpl implements ProjectController { constructor( private readonly db: DatabaseService, private readonly auth: AuthService ) {} async create(data: CreateProjectInput): Promise { const userId = this.auth.getCurrentUserId() const project = await this.db.project.create({ data: { ...data, userId } }) return project } async list(body: ListProjectsInput): Promise { const userId = this.auth.getCurrentUserId() const [projects, total] = await Promise.all([ this.db.project.findMany({ where: { userId, isDeleted: false }, skip: ((body.page || 1) - 1) * (body.limit || 20), take: body.limit || 20, orderBy: { [body.sortBy || 'createdAt']: body.sortOrder || 'desc' } }), this.db.project.count({ where: { userId, isDeleted: false } }) ]) return { projects, total, page: body.page || 1, limit: body.limit || 20 } } async get(id: string): Promise { const project = await this.db.project.findUnique({ where: { id } }) if (!project) throw new Error('Project not found') return project } async update(body: UpdateProjectInput): Promise { return this.db.project.update({ where: { id: body.id }, data: body }) } async delete(body: { id: string }): Promise<{ message: string }> { await this.db.project.update({ where: { id: body.id }, data: { isDeleted: true } }) return { message: 'Project deleted' } } async transfer(body: TransferProjectInput): Promise { // 实现转移逻辑... } } ``` ### 与 Hono 集成 ```typescript import { Hono } from 'hono' import { root, CONTROLLES } from '@repo/core' const app = new Hono() // 自动注册所有控制器路由 const controllers = root.get(CONTROLLES, []) for (const controller of controllers) { // 使用 @repo/core 的路由注册逻辑 registerControllerRoutes(app, controller) } export default app ``` --- ## 6. 错误处理 ### 客户端错误处理 ```typescript import { ProjectController } from '@repo/sdk' import { root } from '@repo/core' const projectCtrl = root.get(ProjectController) try { const project = await projectCtrl.get('non-existent-id') } catch (error) { if (error.message.includes('API error')) { // API 返回的错误 console.error('API 错误:', error.message) } else { // 网络或其他错误 console.error('请求失败:', error) } } ``` ### 统一错误处理 ```typescript // 创建带错误处理的包装器 function withErrorHandling Promise>(fn: T): T { return (async (...args: Parameters) => { try { return await fn(...args) } catch (error: any) { // 统一错误处理 if (error.message.includes('401')) { // 未认证,跳转登录 window.location.href = '/login' return } if (error.message.includes('403')) { // 无权限 throw new Error('您没有权限执行此操作') } if (error.message.includes('404')) { throw new Error('资源不存在') } throw error } }) as T } // 使用 const safeList = withErrorHandling(projectCtrl.list.bind(projectCtrl)) const projects = await safeList({ page: 1 }) ``` --- ## 最佳实践总结 ### 推荐做法 | 场景 | 推荐方式 | |------|----------| | 调用 API | `root.get(Controller)` - 完整类型安全 | | 定义类型 | 从 `@repo/sdk` 导入,不要自己定义 | | 验证数据 | 复用 SDK 导出的 Zod Schema | | 表单验证 | 使用 `zodResolver(Schema)` | | 服务端实现 | `implements Controller` + 复用类型 | ### 避免做法 ```typescript // ❌ 不要自己定义类型 interface MyProject { id: string; title: string; } // ✅ 从 SDK 导入类型 import type { Project } from '@repo/sdk' // ❌ 不要自己写验证逻辑 if (!data.title || data.title.length < 1) { throw new Error('标题不能为空') } // ✅ 复用 SDK Schema import { CreateProjectSchema } from '@repo/sdk' CreateProjectSchema.parse(data) // ❌ 不要使用 any 类型 const projectCtrl: any = root.get(ProjectController) // ✅ 让 TypeScript 自动推导 const projectCtrl = root.get(ProjectController) // 类型自动推导 ```