ITPub博客

首页 > 应用开发 > IT综合 > tRPC类型安全API开发指南:技术面试手册实践解析

tRPC类型安全API开发指南:技术面试手册实践解析

投稿 IT综合 作者:bhluo 时间:2025-12-22 17:24:52 0 删除 编辑

# tRPC类型安全API开发指南:技术面试手册实践解析


tRPC作为现代化的类型安全API框架,为全栈TypeScript开发提供了端到端的类型安全保障。本文结合技术面试常见场景,深入探讨其实践应用。


## tRPC基础架构设计


### 1. 核心架构与配置

tRPC采用分层架构,确保类型安全贯穿整个开发流程:


```typescript

// 项目基础结构

/*

client/           # 前端应用

server/           # 后端服务

  ├── trpc/       # tRPC核心配置

  ├── routers/    # 路由定义

  └── procedures/ # 业务过程

shared/           # 共享类型定义

*/


// trpc/initTRPC.ts - 初始化配置

import { initTRPC, TRPCError } from '@trpc/server';

import { z } from 'zod';

import superjson from 'superjson';


// 创建上下文类型

export interface Context {

  user?: {

    id: string;

    email: string;

    role: 'admin' | 'user';

  };

  req: Request;

  res: Response;

}


// 初始化tRPC

const t = initTRPC.context<Context>().create({

  transformer: superjson, // 支持Date/Map/Set等类型

  errorFormatter({ shape, error }) {

    return {

      ...shape,

      data: {

        ...shape.data,

        zodError: error.cause instanceof z.ZodError 

          ? error.cause.flatten() 

          : null,

      },

    };

  },

});


// 导出基础类型

export const router = t.router;

export const middleware = t.middleware;

export const publicProcedure = t.procedure;

export const mergeRouters = t.mergeRouters;


// 认证中间件

const isAuthenticated = middleware(({ ctx, next }) => {

  if (!ctx.user) {

    throw new TRPCError({ 

      code: 'UNAUTHORIZED',

      message: '请先登录' 

    });

  }

  return next({

    ctx: {

      ...ctx,

      user: ctx.user, // 类型细化

    },

  });

});


// 角色检查中间件

const isAdmin = middleware(({ ctx, next }) => {

  if (ctx.user?.role !== 'admin') {

    throw new TRPCError({

      code: 'FORBIDDEN',

      message: '需要管理员权限'

    });

  }

  return next();

});


// 创建受保护的过程

export const protectedProcedure = t.procedure.use(isAuthenticated);

export const adminProcedure = protectedProcedure.use(isAdmin);

```


### 2. 路由与过程定义

组织API路由结构,实现业务逻辑:


```typescript

// routers/user.router.ts - 用户相关路由

import { z } from 'zod';

import { 

  router, 

  publicProcedure, 

  protectedProcedure,

  adminProcedure 

} from '../trpc/initTRPC';

import { 

  userService,

  type UserProfile,

  type CreateUserInput 

} from '../services/user.service';


// 输入验证模式

const userSchemas = {

  create: z.object({

    email: z.string().email('邮箱格式不正确'),

    password: z.string()

      .min(8, '密码至少8位')

      .max(32, '密码最多32位')

      .regex(

        /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/,

        '密码必须包含大小写字母和数字'

      ),

    name: z.string().min(2, '姓名至少2位').max(50, '姓名最多50位'),

    role: z.enum(['admin', 'user']).default('user')

  }),

  

  update: z.object({

    name: z.string().min(2).max(50).optional(),

    avatar: z.string().url().optional(),

    bio: z.string().max(500).optional()

  }),

  

  pagination: z.object({

    page: z.number().int().min(1).default(1),

    limit: z.number().int().min(1).max(100).default(20),

    search: z.string().optional(),

    sortBy: z.enum(['createdAt', 'name', 'email']).default('createdAt'),

    order: z.enum(['asc', 'desc']).default('desc')

  })

};


export const userRouter = router({

  // 公共API:用户注册

  register: publicProcedure

    .input(userSchemas.create)

    .mutation(async ({ input, ctx }) => {

      try {

        const user = await userService.createUser(input);

        

        // 记录操作日志

        await ctx.logService.create({

          action: 'USER_REGISTER',

          userId: user.id,

          metadata: { 

            email: input.email,

            userAgent: ctx.req.headers['user-agent']

          }

        });

        

        return {

          success: true,

          data: user,

          message: '注册成功'

        };

      } catch (error) {

        if (error instanceof Error) {

          throw new TRPCError({

            code: 'CONFLICT',

            message: error.message,

            cause: error

          });

        }

        throw error;

      }

    }),

  

  // 公共API:用户登录

  login: publicProcedure

    .input(z.object({

      email: z.string().email(),

      password: z.string()

    }))

    .mutation(async ({ input, ctx }) => {

      const result = await userService.authenticate(input);

      

      if (!result.success) {

        throw new TRPCError({

          code: 'UNAUTHORIZED',

          message: '邮箱或密码错误'

        });

      }

      

      // 设置会话

      await ctx.sessionService.createSession(

        result.user.id,

        ctx.req

      );

      

      return {

        success: true,

        data: {

          user: result.user,

          token: result.token

        },

        message: '登录成功'

      };

    }),

  

  // 受保护API:获取当前用户资料

  getProfile: protectedProcedure

    .query(async ({ ctx }) => {

      const user = await userService.findById(ctx.user.id, {

        includeSensitive: true

      });

      

      if (!user) {

        throw new TRPCError({

          code: 'NOT_FOUND',

          message: '用户不存在'

        });

      }

      

      return user;

    }),

  

  // 受保护API:更新用户资料

  updateProfile: protectedProcedure

    .input(userSchemas.update)

    .mutation(async ({ input, ctx }) => {

      const updatedUser = await userService.update(

        ctx.user.id,

        input

      );

      

      return {

        success: true,

        data: updatedUser,

        message: '资料更新成功'

      };

    }),

  

  // 管理员API:获取用户列表

  listUsers: adminProcedure

    .input(userSchemas.pagination)

    .query(async ({ input, ctx }) => {

      const { users, total } = await userService.listUsers(input);

      

      return {

        data: users,

        pagination: {

          page: input.page,

          limit: input.limit,

          total,

          pages: Math.ceil(total / input.limit)

        }

      };

    }),

  

  // 管理员API:删除用户

  deleteUser: adminProcedure

    .input(z.object({ userId: z.string().uuid() }))

    .mutation(async ({ input, ctx }) => {

      // 不能删除自己

      if (input.userId === ctx.user.id) {

        throw new TRPCError({

          code: 'BAD_REQUEST',

          message: '不能删除自己的账户'

        });

      }

      

      await userService.deleteUser(input.userId);

      

      return {

        success: true,

        message: '用户删除成功'

      };

    })

});

```


## 技术面试实践场景


### 3. 面试问题API实现

模拟技术面试系统的核心功能:


```typescript

// routers/interview.router.ts - 面试系统路由

import { z } from 'zod';

import { router, protectedProcedure } from '../trpc/initTRPC';

import { 

  questionService,

  interviewService,

  type Question,

  type InterviewSession 

} from '../services/interview.service';


export const interviewRouter = router({

  // 获取面试问题分类

  getCategories: protectedProcedure

    .query(async ({ ctx }) => {

      const categories = await questionService.getCategories();

      

      return categories.map(category => ({

        id: category.id,

        name: category.name,

        description: category.description,

        questionCount: category._count.questions,

        difficulty: category.difficulty

      }));

    }),

  

  // 根据筛选条件获取问题

  getQuestions: protectedProcedure

    .input(z.object({

      categoryId: z.string().optional(),

      difficulty: z.enum(['easy', 'medium', 'hard']).optional(),

      tags: z.array(z.string()).optional(),

      limit: z.number().int().min(1).max(50).default(10),

      random: z.boolean().default(true)

    }))

    .query(async ({ input, ctx }) => {

      const questions = await questionService.findQuestions({

        where: {

          categoryId: input.categoryId,

          difficulty: input.difficulty,

          tags: input.tags ? {

            some: {

              name: {

                in: input.tags

              }

            }

          } : undefined

        },

        take: input.limit,

        include: {

          category: true,

          tags: true,

          solutions: {

            where: {

              language: 'typescript'

            },

            take: 1

          }

        },

        orderBy: input.random 

          ? { id: 'desc' } // 实际应为随机排序

          : { createdAt: 'desc' }

      });

      

      return questions.map(q => ({

        id: q.id,

        title: q.title,

        description: q.description,

        difficulty: q.difficulty,

        category: q.category.name,

        tags: q.tags.map(t => t.name),

        hints: q.hints,

        timeLimit: q.timeLimit,

        memoryLimit: q.memoryLimit,

        template: q.solutions[0]?.code

      }));

    }),

  

  // 开始新的面试会话

  startInterview: protectedProcedure

    .input(z.object({

      categoryIds: z.array(z.string()),

      questionCount: z.number().int().min(1).max(20).default(5),

      timeLimit: z.number().int().min(300).max(3600).default(1800) // 30分钟

    }))

    .mutation(async ({ input, ctx }) => {

      const interview = await interviewService.createSession({

        userId: ctx.user.id,

        categoryIds: input.categoryIds,

        questionCount: input.questionCount,

        timeLimit: input.timeLimit

      });

      

      // 生成面试问题

      const questions = await questionService.generateInterviewQuestions({

        categoryIds: input.categoryIds,

        count: input.questionCount,

        userId: ctx.user.id // 用于个性化推荐

      });

      

      return {

        sessionId: interview.id,

        questions: questions.map(q => ({

          id: q.id,

          title: q.title,

          difficulty: q.difficulty,

          category: q.category.name

        })),

        expiresAt: interview.expiresAt,

        totalTime: input.timeLimit

      };

    }),

  

  // 提交问题答案

  submitAnswer: protectedProcedure

    .input(z.object({

      sessionId: z.string().uuid(),

      questionId: z.string().uuid(),

      code: z.string(),

      language: z.enum(['typescript', 'javascript', 'python', 'java']),

      explanation: z.string().optional()

    }))

    .mutation(async ({ input, ctx }) => {

      // 验证会话有效性

      const session = await interviewService.validateSession(

<"8v.6w5t7.cn"><"2m.9v7k2.cn"><"5w.3p1w2.cn">

        input.sessionId,

        ctx.user.id

      );

      

      if (!session) {

        throw new TRPCError({

          code: 'NOT_FOUND',

          message: '面试会话不存在或已过期'

        });

      }

      

      // 执行代码测试

      const testResult = await interviewService.runTests({

        questionId: input.questionId,

        code: input.code,

        language: input.language

      });

      

      // 保存答案

      const submission = await interviewService.saveSubmission({

        sessionId: input.sessionId,

        questionId: input.questionId,

        userId: ctx.user.id,

        code: input.code,

        language: input.language,

        explanation: input.explanation,

        testResults: testResult,

        passed: testResult.passed

      });

      

      return {

        submissionId: submission.id,

        passed: testResult.passed,

        testCases: testResult.testCases.map(tc => ({

          name: tc.name,

          passed: tc.passed,

          actual: tc.actual,

          expected: tc.expected

        })),

        executionTime: testResult.executionTime,

        memoryUsage: testResult.memoryUsage

      };

    }),

  

  // 获取面试结果

  getInterviewResults: protectedProcedure

    .input(z.object({

      sessionId: z.string().uuid()

    }))

    .query(async ({ input, ctx }) => {

      const results = await interviewService.getResults(

        input.sessionId,

        ctx.user.id

      );

      

      return {

        session: {

          id: results.session.id,

          startedAt: results.session.startedAt,

          completedAt: results.session.completedAt,

          totalQuestions: results.session.totalQuestions,

          completedQuestions: results.completedQuestions

        },

        submissions: results.submissions.map(s => ({

          questionId: s.questionId,

          questionTitle: s.question.title,

          difficulty: s.question.difficulty,

          submittedAt: s.submittedAt,

          passed: s.passed,

          executionTime: s.executionTime,

          codeQuality: s.codeQualityScore

        })),

        overallScore: results.overallScore,

        strengths: results.strengths,

        areasForImprovement: results.areasForImprovement,

        recommendations: results.recommendations

      };

    })

});

```


### 4. 实时协作面试功能

实现面试官与候选人的实时交互:


```typescript

// routers/liveInterview.router.ts - 实时面试路由

import { z } from 'zod';

import { EventEmitter } from 'events';

import { router, protectedProcedure } from '../trpc/initTRPC';

import { observable } from '@trpc/server/observable';


// 实时事件类型

type LiveEvent = {

  type: 'CODE_CHANGE' | 'CURSOR_MOVE' | 'CHAT_MESSAGE' | 'TEST_RESULT';

  roomId: string;

  senderId: string;

  timestamp: number;

  payload: any;

};


// 共享的事件发射 器

const interviewEmitter = new EventEmitter();


export const liveInterviewRouter = router({

  // 加入面试房间

  joinRoom: protectedProcedure

    .input(z.object({

      roomId: z.string(),

      role: z.enum(['interviewer', 'candidate'])

    }))

    .mutation(async ({ input, ctx }) => {

<"9x.6e1w7.cn"><"3c.5w7x1.cn"><"6y.9v1n2.cn"><"0h.2n8j1.cn">

      const room = await interviewService.getRoom(input.roomId);

      

      if (!room) {

        throw new TRPCError({

          code: 'NOT_FOUND',

          message: '面试房间不存在'

        });

      }

      

      // 验证权限

      if (input.role === 'interviewer' && 

          room.interviewerId !== ctx.user.id) {

        throw new TRPCError({

          code: 'FORBIDDEN',

          message: '没有权限作为面试官加入'

        });

      }

      

      // 更新房间状态

      await interviewService.updateRoomPresence(

        input.roomId,

        ctx.user.id,

        'joined'

      );

      

      // 广播加入事件

      interviewEmitter.emit('room_event', {

        type: 'USER_JOINED',

        roomId: input.roomId,

        userId: ctx.user.id,

        role: input.role,

        timestamp: Date.now()

      });

      

      return {

        success: true,

        room: {

          id: room.id,

          title: room.title,

          participants: room.participants,

          question: room.currentQuestion,

          startedAt: room.startedAt

        }

      };

    }),

  

  // 实时事件订阅

  onRoomEvent: protectedProcedure

    .input(z.object({ roomId: z.string() }))

    .subscription(({ input }) => {

      return observable<LiveEvent>((emit) => {

        const handler = (event: LiveEvent) => {

          if (event.roomId === input.roomId) {

            emit.next(event);

          }

        };

        

        interviewEmitter.on('room_event', handler);

        

        return () => {

          interviewEmitter.off('room_event', handler);

        };

      });

    }),

  

  // 发送代码变更

  sendCodeChange: protectedProcedure

    .input(z.object({

      roomId: z.string(),

      code: z.string(),

      cursorPosition: z.object({

        line: z.number(),

        column: z.number()

      }).optional()

    }))

    .mutation(async ({ input, ctx }) => {

      const event: LiveEvent = {

        type: 'CODE_CHANGE',

        roomId: input.roomId,

        senderId: ctx.user.id,

        timestamp: Date.now(),

        payload: {

          code: input.code,

          cursorPosition: input.cursorPosition

        }

      };

      

      interviewEmitter.emit('room_event', event);

      

      // 保存代码快照

      await interviewService.saveCodeSnapshot({

        roomId: input.roomId,

        userId: ctx.user.id,

        code: input.code,

        cursorPosition: input.cursorPosition

      });

      

      return { success: true };

    }),

  

  // 发送聊天消息

  sendChatMessage: protectedProcedure

    .input(z.object({

      roomId: z.string(),

      message: z.string().max(1000),

      type: z.enum(['question', 'hint', 'feedback']).default('question')

    }))

    .mutation(async ({ input, ctx }) => {

      const event: LiveEvent = {

        type: 'CHAT_MESSAGE',

        roomId: input.roomId,

        senderId: ctx.user.id,

        timestamp: Date.now(),

        payload: {

          message: input.message,

          type: input.type,

          senderName: ctx.user.name

        }

      };

      

      interviewEmitter.emit('room_event', event);

      

      // 保存聊天记录

      await interviewService.saveChatMessage({

        roomId: input.roomId,

        userId: ctx.user.id,

        message: input.message,

        type: input.type

      });

      

      return { success: true };

    }),

  

  // 运行测试并实时返回结果

  runTests: protectedProcedure

    .input(z.object({

      roomId: z.string(),

      code: z.string(),

      questionId: z.string().uuid()

    }))

    .mutation(async ({ input, ctx }) => {

      // 开始测试

      const testId = await interviewService.startTestRun({

        code: input.code,

        questionId: input.questionId,

        userId: ctx.user.id

      });

      

      // 实时发送测试进度

      const progressCallback = (progress: any) => {

        const event: LiveEvent = {

          type: 'TEST_RESULT',

          roomId: input.roomId,

          senderId: 'system',

          timestamp: Date.now(),

          payload: {

            type: 'PROGRESS',

            testId,

            progress

          }

        };

        interviewEmitter.emit('room_event', event);

      };

      

      // 执行测试

      const results = await interviewService.executeTests({

        code: input.code,

        questionId: input.questionId,

        onProgress: progressCallback

      });

      

      // 发送最终结果

      const finalEvent: LiveEvent = {

        type: 'TEST_RESULT',

        roomId: input.roomId,

        senderId: 'system',

        timestamp: Date.now(),

        payload: {

          type: 'COMPLETE',

          testId,

          results: results.map(r => ({

            name: r.name,

            passed: r.passed,

            executionTime: r.executionTime

          }))

        }

      };

      

      interviewEmitter.emit('room_event', finalEvent);

      

      return {

        testId,

        total: results.length,

        passed: results.filter(r => r.passed).length,

        executionTime: results.reduce((sum, r) => sum + r.executionTime, 0)

      };

    })

});

```


## 客户端集成与类型安全


### 5. React客户端集成

在React应用中安全地使用tRPC:


```typescript

// client/trpc/client.ts - 客户端配置

import { createTRPCReact } from '@trpc/react-query';

import { httpBatchLink, loggerLink } from '@trpc/client';

import type { AppRouter } from '../../server/routers/_app';

import superjson from 'superjson';


// 创建tRPC React客户端

export const trpc = createTRPCReact<AppRouter>();


// 创建tRPC客户端配置

export const createClient = () => {

  return trpc.createClient({

    transformer: superjson,

    links: [

      loggerLink({

        enabled: (opts) => 

          process.env.NODE_ENV === 'development' ||

          (opts.direction === 'down' && opts.result instanceof Error)

      }),

      httpBatchLink({

        url: '/api/trpc',

        headers: () => {

          const token = localStorage.getItem('token');

          return token ? { 

            Authorization: `Bearer ${token}` 

          } : {};

        },

        // 错误处理

        fetch: async (input, init) => {

          const response = await fetch(input, init);

          

          if (response.status === 401) {

            // 处理未授权

            window.location.href = '/login';

            throw new Error('未授权,请重新登录');

          }

          

          return response;

        }

      })

    ]

  });

};


// client/components/InterviewSession.tsx - 面试组件

import React, { useState } from 'react';

import { trpc } from '../trpc/client';


const InterviewSession: React.FC = () => {

  const [sessionId, setSessionId] = useState<string>('');

  const [currentQuestion, setCurrentQuestion] = useState(0);

  

  // 使用tRPC查询 - 类型安全!

  const { data: session, isLoading } = trpc.interview.startInterview.useQuery(

    {

      categoryIds: ['algorithms', 'system-design'],

      questionCount: 5,

      timeLimit: 1800

    },

    {

      onSuccess: (data) => {

        setSessionId(data.sessionId);

      },

      refetchOnWindowFocus: false

    }

  );

  

  // 提交答案的Mutation

  const submitAnswer = trpc.interview.submitAnswer.useMutation({

    onSuccess: (data) => {

      console.log('答案提交成功:', data);

    },

    onError: (error) => {

      console.error('提交失败:', error.message);

    }

  });

  

  // 实时订阅

  trpc.liveInterview.onRoomEvent.useSubscription(

    { roomId: sessionId },

    {

      onData: (event) => {

        switch (event.type) {

          case 'CODE_CHANGE':

            // 处理代码变更

            break;

          case 'CHAT_MESSAGE':

            // 显示聊天消息

            break;

          case 'TEST_RESULT':

            // 更新测试结果

            break;

        }

      },

      enabled: !!sessionId

    }

  );

  

  const handleSubmitCode = async (code: string) => {

    if (!session || !session.questions[currentQuestion]) return;

    

    await submitAnswer.mutateAsync({

      sessionId: session.sessionId,

      questionId: session.questions[currentQuestion].id,

      code,

      language: 'typescript',

      explanation: '我的解题思路...'

    });

  };

  

  if (isLoading) return <div>加载中...</div>;

  if (!session) return <div>会话创建失败</div>;

  

  return (

    <div className="interview-container">

      <h2>技术面试</h2>

      <div className="timer">

        剩余时间: {Math.floor(session.totalTime / 60)}分钟

      </div>

      

      <div className="question-progress">

        问题 {currentQuestion + 1} / {session.questions.length}

      </div>

      

      {session.questions.map((question, index) => (

        <QuestionCard

          key={question.id}

          question={question}

          isActive={index === currentQuestion}

          >

<"7z.1f6d1.cn"><"1a.7e5s1.cn">

        />

      ))}

    </div>

  );

};


// 自动生成的类型安全Hook

interface QuestionCardProps {

  question: {

    id: string;

    title: string;

    difficulty: 'easy' | 'medium' | 'hard';

    category: string;

  };

  isActive: boolean;

  onSubmitCode: (code: string) => void;

}


const QuestionCard: React.FC<QuestionCardProps> = ({ 

  question, 

  isActive,

  onSubmitCode 

}) => {

  // 获取问题详情 - 类型安全!

  const { data: questionDetail } = trpc.interview.getQuestions.useQuery({

    categoryId: question.category,

    difficulty: question.difficulty,

    limit: 1

  }, {

    enabled: isActive

  });

  

  const [code, setCode] = useState('');

  

  return (

    <div className={`question-card ${isActive ? 'active' : ''}`}>

      <h3>{question.title}</h3>

      <div className="difficulty-badge">{question.difficulty}</div>

      

      {questionDetail && questionDetail[0] && (

        <>

          <div className="description">

            {questionDetail[0].description}

          </div>

          <div className="hints">

            {questionDetail[0].hints.map((hint, i) => (

              <div key={i} className="hint">{hint}</div>

            ))}

          </div>

        </>

      )}

      

      <textarea

        value={code}

        => setCode(e.target.value)}

        placeholder="在此处输入你的代码..."

      />

      

      <button => onSubmitCode(code)}>

        提交代码

      </button>

    </div>

  );

};

```


### 6. 测试与质量保障

确保API的可靠性和类型安全:


```typescript

// tests/interview.test.ts - 测试用例

import { describe, it, expect, beforeEach } from 'vitest';

import { createCaller } from '../server/trpc/router';

import { createTestContext } from './test-utils';


describe('面试系统API测试', () => {

  let caller: ReturnType<typeof createCaller>;

  let testContext: any;

  

  beforeEach(async () => {

    testContext = await createTestContext();

    caller = createCaller(testContext);

  });

  

  describe('用户认证', () => {

    it('应该成功注册新用户', async () => {

      const result = await caller.user.register({

        email: 'test@example.com',

        password: 'Password123',

        name: '测试用户'

      });

      

      expect(result.success).toBe(true);

      expect(result.data).toHaveProperty('id');

      expect(result.data.email).toBe('test@example.com');

    });

    

    it('应该拒绝无效的邮箱格式', async () => {

      await expect(

        caller.user.register({

          email: 'invalid-email',

          password: 'Password123',

          name: '测试用户'

        })

      ).rejects.toThrow('邮箱格式不正确');

    });

    

    it('应该成功登录并返回令牌', async () => {

      // 先注册

      await caller.user.register({

        email: 'login@example.com',

        password: 'Password123',

        name: '登录测试'

      });

      

      // 再登录

      const result = await caller.user.login({

        email: 'login@example.com',

        password: 'Password123'

      });

      

      expect(result.success).toBe(true);

      expect(result.data).toHaveProperty('token');

      expect(result.data.user.email).toBe('login@example.com');

    });

  });

  

  describe('面试流程', () => {

    it('应该能创建面试会话', async () => {

      const result = await caller.interview.startInterview({

        categoryIds: ['algorithms'],

        questionCount: 3,

        timeLimit: 900

      });

      

      expect(result).toHaveProperty('sessionId');

      expect(result.questions).toHaveLength(3);

      expect(result.totalTime).toBe(900);

    });

    

    it('应该验证会话过期时间', async () => {

      const result = await caller.interview.startInterview({

        categoryIds: ['algorithms'],

        questionCount: 3,

        timeLimit: 300 // 5分钟

      });

      

      const expiresAt = new Date(result.expiresAt);

      const now = new Date();

      const timeDiff = (expiresAt.getTime() - now.getTime()) / 1000;

      

      // 允许10秒误差

      expect(Math.abs(timeDiff - 300)).toBeLessThan(10);

    });

    

    it('应该正确提交和评估代码', async () => {

      const session = await caller.interview.startInterview({

        categoryIds: ['algorithms'],

        questionCount: 1

      });

      

      const result = await caller.interview.submitAnswer({

        sessionId: session.sessionId,

        questionId: session.questions[0].id,

        code: 'function sum(a, b) { return a + b; }',

        language: 'javascript'

      });

      

      expect(result).toHaveProperty('submissionId');

      expect(result).toHaveProperty('testCases');

      expect(Array.isArray(result.testCases)).toBe(true);

    });

  });

  

  describe('实时协作', () => {

    it('应该允许加入面试房间', async () => {

      const room = await caller.liveInterview.joinRoom({

        roomId: 'test-room-123',

        role: 'candidate'

      });

      

      expect(room.success).toBe(true);

      expect(room.room).toHaveProperty('id');

      expect(room.room.id).toBe('test-room-123');

    });

    

    it('应该验证面试官权限', async () => {

      await expect(

        caller.liveInterview.joinRoom({

          roomId: 'admin-room',

          role: 'interviewer'

        })

      ).rejects.toThrow('没有权限作为面试官加入');

    });

  });

});

```


tRPC通过端到端的类型安全,为技术面试系统等复杂应用提供了坚实的开发基础。结合Zod进行运行时验证、利用React Query进行状态管理、实现实时订阅功能,可以构建出既安全又高效的全栈应用。在实际开发中,建议采用模块化的路由设计、完善的错误处理机制,并充分利用TypeScript的类型推导能力,确保代码质量和开发效率。


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/19545/viewspace-3105911/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2015-11-01

  • 博文量
    895
  • 访问量
    945181