【忆往昔项目实战1】文智 AI 全栈交互系统——“解剖” 那两周边学边做的AI 全栈项目 (面试版)

10 阅读19分钟

项目概述

文智 AI 全栈交互系统是一个集前端 React 应用、后端 NestJS 服务和 AI 能力于一体的全栈项目。该项目实现了用户登录、文章管理、AI 聊天和语义搜索等核心功能,采用了现代化的技术栈和架构设计,为用户提供了流畅的交互体验。

技术栈概览

前端技术栈

  • React + TypeScript
  • React Router DOM(前端路由)
  • Zustand(状态管理)
  • Tailwind CSS(样式框架)
  • Shadcn UI(组件库)
  • Axios(HTTP 请求库)
  • MockJS(前后端解耦开发)

后端技术栈

  • NestJS(企业级后端框架)
  • PostgreSQL(关系型数据库)
  • Prisma(ORM 工具)
  • JWT(身份认证)
  • bcrypt(密码加密)

AI 技术

  • LangChain(AI 应用开发框架)
  • Embedding(语义搜索)
  • Chatbot(流式输出)
  • AI 生成头像
  • Commit Message 生成

核心功能模块

  1. 用户认证系统:登录、注册、JWT 身份验证
  2. 文章管理系统:文章列表、详情、发布、评论
  3. AI 交互系统:智能聊天、语义搜索、AI 生成头像、Commit Message 生成
  4. 性能优化:图片懒加载、无限滚动、组件缓存

面试回答框架

对于每个技术点,我将采用以下回答框架:

  1. 技术点概述:简要介绍该技术的作用和重要性
  2. 核心实现:详细说明实现方案和关键代码
  3. 使用场景:说明在项目中的具体应用
  4. 优化策略:分享相关的性能优化或最佳实践
  5. 常见问题:解答面试官可能会问的高频问题

前端技术栈面试回答

React + TypeScript

技术点概述:React 是一个用于构建用户界面的 JavaScript 库,TypeScript 是 JavaScript 的超集,提供了静态类型检查。两者结合使用可以提高代码的可维护性和可靠性。

核心实现

// 函数式组件示例
import type { Todo } from '../types/todo';

interface Props {
  todo: Todo;
  onToggle: (id: number) => void;
  onRemove: (id: number) => void;
}

export default function TodoItem({
  todo,
  onToggle,
  onRemove
}: Props) {
  return (
    <li>
      <input
        type="checkbox"
        checked={todo.completed}
        onChange={() => onToggle(todo.id)}
      />
      <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
        {todo.title}
      </span>
      <button onClick={() => onRemove(todo.id)}>删除</button>
    </li>
  );
}

使用场景:项目中所有 UI 组件都使用 React + TypeScript 开发,确保类型安全和代码质量。

优化策略

  • 使用函数式组件和 Hooks
  • 合理使用 React.memo 和 useMemo 优化渲染性能
  • 类型定义清晰,提高代码可读性和可维护性

常见问题

  • Q: 如何处理 React 中的状态管理? A: 对于简单状态使用 useState,对于复杂状态使用 Zustand 进行全局状态管理。
  • Q: TypeScript 在项目中的优势是什么? A: 提供静态类型检查,减少运行时错误,提高代码可读性和可维护性。

React Router DOM

技术点概述:React Router DOM 是 React 官方的路由库,用于实现单页应用的路由管理。

核心实现

// 路由配置示例
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import MainLayout from './layouts/MainLayout';
import Home from './pages/Home';
import Login from './pages/Login';
import Search from './pages/Search';
import RAG from './pages/RAG';
import Chat from './pages/Chat';
import Mine from './pages/Mine';
import Post from './pages/post';

const router = createBrowserRouter([
  {
    path: '/',
    element: <MainLayout />,
    children: [
      {
        path: '',
        element: <Home />,
      },
      {
        path: 'login',
        element: <Login />,
      },
      {
        path: 'search',
        element: <Search />,
      },
      {
        path: 'rag',
        element: <RAG />,
      },
      {
        path: 'chat',
        element: <Chat />,
      },
      {
        path: 'mine',
        element: <Mine />,
      },
      {
        path: 'post/:id',
        element: <Post />,
      },
    ],
  },
]);

function App() {
  return <RouterProvider router={router} />;
}

export default App;

使用场景:实现页面之间的导航和路由管理,支持动态路由和嵌套路由。

优化策略

  • 使用路由懒加载减少初始加载时间
  • 合理使用嵌套路由提高代码组织性
  • 实现路由守卫保护需要认证的路由

常见问题

  • Q: 前端路由和后端路由的区别是什么? A: 前端路由在客户端实现,通过 JavaScript 监听 URL 变化并渲染对应组件;后端路由在服务器端实现,根据 URL 路径返回不同的资源。
  • Q: 如何实现路由懒加载? A: 使用 React.lazy 和 Suspense 组件实现路由组件的懒加载。

Zustand 状态管理

技术点概述:Zustand 是一个轻量级的状态管理库,基于 Hooks 实现,相比 Redux 更加简洁易用。

核心实现

// 用户状态管理示例
import { create } from 'zustand';

interface UserState {
  user: {
    id: number;
    name: string;
    avatar: string;
  } | null;
  token: string | null;
  isLogin: boolean;
  setUser: (user: any) => void;
  setToken: (token: string) => void;
  logout: () => void;
}

export const useUserStore = create<UserState>((set) => ({
  user: null,
  token: localStorage.getItem('token'),
  isLogin: !!localStorage.getItem('token'),
  setUser: (user) => set({ user }),
  setToken: (token) => {
    localStorage.setItem('token', token);
    set({ token, isLogin: true });
  },
  logout: () => {
    localStorage.removeItem('token');
    set({ user: null, token: null, isLogin: false });
  },
}));

使用场景:管理全局状态,如用户信息、登录状态等。

优化策略

  • 按需创建 store,避免全局单一 store 的臃肿
  • 合理使用 selectors 优化组件渲染
  • 利用 persist 中间件实现状态持久化

常见问题

  • Q: Zustand 相比 Redux 的优势是什么? A: 代码更简洁,无需 action、reducer 等繁琐概念,基于 Hooks 实现,学习成本低。
  • Q: 如何在非 React 环境中访问 Zustand store? A: 使用 store.getState() 方法获取当前状态。

Tailwind CSS 和 Shadcn UI

技术点概述:Tailwind CSS 是一个实用优先的 CSS 框架,Shadcn UI 是基于 Tailwind CSS 的组件库,提供了丰富的 UI 组件。

核心实现

// Tailwind CSS 配置
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite';

export default defineConfig({
  plugins: [
    react(),
    tailwindcss()
  ],
});

// Shadcn UI 组件使用
import { Button } from '@/components/ui/button';

function Example() {
  return (
    <Button variant="default">
      Click me
    </Button>
  );
}

使用场景:快速构建响应式 UI,提高开发效率。

优化策略

  • 按需引入组件,减少打包体积
  • 合理使用 Tailwind 工具类,避免自定义 CSS
  • 利用 Shadcn UI 的主题系统实现统一的设计风格

常见问题

  • Q: Tailwind CSS 的优势是什么? A: 实用优先,无需编写传统 CSS,减少样式冲突,响应式设计简单。
  • Q: 如何自定义 Shadcn UI 组件? A: 可以直接修改组件源码,或通过覆盖 CSS 变量实现自定义。

MockJS

技术点概述:MockJS 是一个用于生成模拟数据的库,用于前后端解耦开发,允许前端在后端 API 未完成时独立开发和测试。

核心实现

// mock 配置
import Mock from 'mockjs';

// 模拟用户数据
Mock.mock('/api/users', 'get', {
  'code': 200,
  'data|10-20': [{
    'id|+1': 1,
    'name': '@name',
    'avatar': '@image(100x100, @color, @name)',
    'createdAt': '@datetime'
  }]
});

// 模拟登录接口
Mock.mock('/api/login', 'post', (options) => {
  const { name, password } = JSON.parse(options.body);
  if (name === 'admin' && password === '123456') {
    return {
      code: 200,
      data: {
        token: 'mock-token-123456',
        user: {
          id: 1,
          name: 'admin',
          avatar: '@image(100x100, @color, admin)'
        }
      }
    };
  } else {
    return {
      code: 401,
      message: '用户名或密码错误'
    };
  }
});

// 模拟文章列表
Mock.mock('/api/posts', 'get', {
  'code': 200,
  'data|10-20': [{
    'id|+1': 1,
    'title': '@title(5, 10)',
    'content': '@paragraph(3, 5)',
    'userId': '@integer(1, 10)',
    'createdAt': '@datetime'
  }]
});

// 前端 API 服务
import axios from 'axios';

const api = axios.create({
  baseURL: '/api',
  timeout: 10000
});

export const userApi = {
  getUsers: () => api.get('/users'),
  login: (data) => api.post('/login', data)
};

export const postApi = {
  getPosts: () => api.get('/posts')
};

使用场景

  • 后端 API 未完成时,前端可以独立开发和测试
  • 模拟各种边界情况和错误状态
  • 减少对后端服务的依赖,提高开发效率

优化策略

  • 按模块组织 mock 数据,提高可维护性
  • 模拟真实的 API 响应结构,包括错误处理
  • 使用环境变量控制 mock 的启用和禁用

常见问题

  • Q: MockJS 如何与真实 API 切换? A: 使用环境变量或配置文件控制,开发环境使用 mock,生产环境使用真实 API。
  • Q: 如何模拟复杂的 API 场景? A: 使用 MockJS 的模板语法和函数功能,模拟各种复杂的数据结构和逻辑。

后端技术栈面试回答

NestJS

技术点概述:NestJS 是一个基于 TypeScript 的企业级后端框架,提供了模块化架构和依赖注入系统。

核心实现

// 模块示例
import { Module } from '@nestjs/common';
import { PostsController } from './posts.controller';
import { PostsService } from './posts.service';
import { PrismaModule } from '../prisma/prisma.module';

@Module({
  imports: [PrismaModule],
  controllers: [PostsController],
  providers: [PostsService],
})
export class PostsModule {}

// 控制器示例
import { Controller, Get, Query } from '@nestjs/common';
import { PostsService } from './posts.service';
import { PostQueryDto } from './dto/post-query.dto';

@Controller('posts')
export class PostsController {
  constructor(private readonly postsService: PostsService) {}

  @Get()
  findAll(@Query() query: PostQueryDto) {
    return this.postsService.findAll(query);
  }
}

使用场景:构建后端 API 服务,处理业务逻辑。

优化策略

  • 合理使用模块划分,提高代码组织性
  • 利用依赖注入系统实现解耦
  • 使用中间件和拦截器处理横切关注点

常见问题

  • Q: NestJS 相比 Express 的优势是什么? A: 提供了模块化架构、依赖注入、TypeScript 支持等企业级特性,代码结构更清晰。
  • Q: 如何实现 NestJS 中的错误处理? A: 使用异常过滤器捕获和处理错误,或直接抛出内置的异常类。

Prisma

技术点概述:Prisma 是一个现代的 ORM 工具,用于数据库访问和管理。

核心实现

// schema.prisma 文件
model User {
  id        Int      @id @default(autoincrement())
  name      String   @unique
  password  String
  posts     Post[]
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  userId    Int
  user      User     @relation(fields: [userId], references: [id])
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

// Prisma 服务
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  async onModuleInit() {
    await this.$connect();
  }

  async onModuleDestroy() {
    await this.$disconnect();
  }
}

使用场景:数据库操作,包括查询、插入、更新和删除。

优化策略

  • 合理使用索引提高查询性能
  • 使用事务确保数据一致性
  • 避免 N+1 查询问题

常见问题

  • Q: Prisma 相比传统 ORM 的优势是什么? A: 类型安全,自动生成类型定义,查询语法更简洁,支持 migrations。
  • Q: 如何处理 Prisma 中的关系查询? A: 使用 include 或 select 方法进行关联查询。

PostgreSQL

技术点概述:PostgreSQL 是一个功能强大的开源关系型数据库,支持复杂查询和事务。

核心实现

-- 创建用户表
CREATE TABLE users (
  id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
  name VARCHAR(255) NOT NULL UNIQUE,
  password VARCHAR(255) NOT NULL,
  created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);

-- 创建文章表
CREATE TABLE posts (
  id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
  title VARCHAR(255) NOT NULL,
  content TEXT,
  "userId" BigInt NOT NULL,
  CONSTRAINT fk_posts_user FOREIGN KEY ("userId") REFERENCES users(id) ON DELETE CASCADE,
  created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);

使用场景:存储和管理应用数据,如用户信息、文章内容等。

优化策略

  • 合理设计数据库 schema,避免冗余
  • 使用索引提高查询性能
  • 定期进行数据库维护和优化

常见问题

  • Q: PostgreSQL 相比 MySQL 的优势是什么? A: 支持更复杂的数据类型和查询,事务支持更完善,开源且功能强大。
  • Q: 如何优化 PostgreSQL 查询性能? A: 使用合适的索引,避免全表扫描,优化查询语句。

JWT 身份认证

技术点概述:JWT(JSON Web Token)是一种用于身份验证的令牌,支持无状态认证。

核心实现

// JWT 策略
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PrismaService } from '../prisma/prisma.service';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(private prisma: PrismaService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: process.env.JWT_SECRET,
    });
  }

  async validate(payload: any) {
    const user = await this.prisma.user.findUnique({
      where: { id: payload.sub },
    });
    return user;
  }
}

// 登录服务
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { PrismaService } from '../prisma/prisma.service';
import * as bcrypt from 'bcrypt';

@Injectable()
export class AuthService {
  constructor(
    private prisma: PrismaService,
    private jwtService: JwtService,
  ) {}

  async login(name: string, password: string) {
    const user = await this.prisma.user.findUnique({ where: { name } });
    if (!user) {
      throw new Error('用户不存在');
    }

    const isPasswordValid = await bcrypt.compare(password, user.password);
    if (!isPasswordValid) {
      throw new Error('密码错误');
    }

    const payload = { sub: user.id, name: user.name };
    const accessToken = this.jwtService.sign(payload, {
      expiresIn: '15m',
    });
    const refreshToken = this.jwtService.sign(payload, {
      expiresIn: '7d',
    });

    return { accessToken, refreshToken };
  }
}

使用场景:用户身份验证和授权。

优化策略

  • 使用双 token 机制(access_token 和 refresh_token)提高安全性
  • 设置合理的 token 过期时间
  • 实现 token 刷新机制

常见问题

  • Q: JWT 的优势是什么? A: 无状态,便于水平扩展,支持跨域认证。
  • Q: 如何处理 JWT 过期? A: 使用 refresh_token 机制,当 access_token 过期时,使用 refresh_token 获取新的 access_token。

AI 相关技术面试回答

LangChain

技术点概述:LangChain 是一个用于开发基于语言模型应用的框架,提供了链式调用语言模型的能力。

核心实现

// AI 服务
import { Injectable } from '@nestjs/common';
import { ChatOpenAI } from '@langchain/openai';
import { HumanMessage } from '@langchain/core/messages';

@Injectable()
export class AiService {
  private chatModel: ChatOpenAI;

  constructor() {
    this.chatModel = new ChatOpenAI({
      modelName: 'gpt-3.5-turbo',
      temperature: 0.7,
    });
  }

  async chat(messages: { role: string; content: string }[]) {
    const formattedMessages = messages.map(msg => 
      msg.role === 'user' 
        ? new HumanMessage(msg.content)
        : new AIMessage(msg.content)
    );

    const response = await this.chatModel.invoke(formattedMessages);
    return response.content;
  }

  async streamChat(messages: { role: string; content: string }[]) {
    const formattedMessages = messages.map(msg => 
      msg.role === 'user' 
        ? new HumanMessage(msg.content)
        : new AIMessage(msg.content)
    );

    const stream = await this.chatModel.stream(formattedMessages);
    return stream;
  }
}

使用场景:实现智能聊天功能,处理用户的自然语言请求。

优化策略

  • 合理设置模型参数,如温度、最大 tokens 等
  • 使用流式输出提高用户体验
  • 实现上下文管理,保持对话连贯性

常见问题

  • Q: LangChain 的核心概念是什么? A: 链(Chains)、代理(Agents)、记忆(Memory)等,用于构建复杂的语言模型应用。
  • Q: 如何优化 LangChain 应用的性能? A: 使用流式输出,合理设置模型参数,实现缓存机制。

Embedding 语义搜索

技术点概述:Embedding 是将文本转换为向量表示的技术,用于实现语义搜索。

核心实现

// 语义搜索服务
import { Injectable } from '@nestjs/common';
import { OpenAIEmbeddings } from '@langchain/openai';
import * as fs from 'fs/promises';
import * as path from 'path';

@Injectable()
export class SearchService {
  private embeddings: OpenAIEmbeddings;
  private postsWithEmbeddings: any[];

  constructor() {
    this.embeddings = new OpenAIEmbeddings();
    this.loadEmbeddings();
  }

  private async loadEmbeddings() {
    const filePath = path.join(process.cwd(), 'src', 'data', 'posts-embedding.json');
    const data = await fs.readFile(filePath, 'utf8');
    this.postsWithEmbeddings = JSON.parse(data);
  }

  async search(query: string, topK: number = 5) {
    const queryEmbedding = await this.embeddings.embedQuery(query);
    
    const results = this.postsWithEmbeddings.map(post => {
      const similarity = this.cosineSimilarity(queryEmbedding, post.embedding);
      return { ...post, similarity };
    }).sort((a, b) => b.similarity - a.similarity).slice(0, topK);

    return results;
  }

  private cosineSimilarity(vec1: number[], vec2: number[]): number {
    const dotProduct = vec1.reduce((sum, val, i) => sum + val * vec2[i], 0);
    const norm1 = Math.sqrt(vec1.reduce((sum, val) => sum + val * val, 0));
    const norm2 = Math.sqrt(vec2.reduce((sum, val) => sum + val * val, 0));
    return dotProduct / (norm1 * norm2);
  }
}

使用场景:实现基于语义的搜索功能,提高搜索准确性。

优化策略

  • 预计算和存储文本向量,减少实时计算开销
  • 使用高效的向量相似度计算算法
  • 考虑使用向量数据库提高搜索性能

常见问题

  • Q: 语义搜索相比传统关键词搜索的优势是什么? A: 语义搜索理解文本的含义,而不仅仅是关键词匹配,提高搜索准确性。
  • Q: 如何处理大规模文本的 Embedding? A: 分批处理,使用向量数据库存储和索引向量。

Chatbot 流式输出

技术点概述:Chatbot 流式输出是指 AI 模型生成回复时,逐字或逐句输出,而不是等待整个回复生成完成。

核心实现

// 前端 Chatbot Hook
import { useChat } from '@ai-sdk/react';

export function useChatBot() {
  const {
    messages,
    input,
    handleInputChange,
    handleSubmit,
    isLoading,
  } = useChat({
    api: '/api/chat',
    stream: true,
  });

  return {
    messages,
    input,
    handleInputChange,
    handleSubmit,
    isLoading,
  };
}

// 后端控制器
import { Controller, Post, Body, Res } from '@nestjs/common';
import { AiService } from './ai.service';
import { Response } from 'express';

@Controller('ai')
export class AiController {
  constructor(private readonly aiService: AiService) {}

  @Post('chat')
  async chat(@Body() body: { messages: { role: string; content: string }[] }, @Res() res: Response) {
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');

    const stream = await this.aiService.streamChat(body.messages);

    for await (const chunk of stream) {
      res.write(`data: ${JSON.stringify({ content: chunk.content })}\n\n`);
    }

    res.write(`data: ${JSON.stringify({ done: true })}\n\n`);
    res.end();
  }
}

使用场景:实现智能聊天功能,提高用户体验。

优化策略

  • 使用 SSE(Server-Sent Events)实现流式输出
  • 合理设置缓冲区大小,平衡实时性和性能
  • 实现错误处理和重试机制

常见问题

  • Q: 流式输出相比传统一次性输出的优势是什么? A: 响应更快,用户体验更好,减少等待时间。
  • Q: 如何实现流式输出? A: 使用 SSE 或 WebSocket 技术,后端逐块发送数据,前端逐块接收和渲染。
  • Q: WebSocket 和 SSE 的相同和不同点是什么? A: 相同点:都支持服务器向客户端推送数据,实现实时通信。不同点:WebSocket 是双向通信,SSE 是单向通信;WebSocket 建立的是持久连接,SSE 是基于 HTTP 的长连接;WebSocket 支持二进制数据,SSE 只支持文本数据;WebSocket 兼容性更好,SSE 在某些旧浏览器中可能不支持。
  • Q: 怎么实现流式输出? A: 后端使用 SSE 或 WebSocket 技术,逐块发送数据;前端使用 EventSource 或 WebSocket API 接收数据,实时渲染。具体实现:后端设置响应头为 text/event-stream,使用 res.write() 逐块发送数据;前端使用 new EventSource() 监听数据,或使用 WebSocket 连接接收数据。
  • Q: 防抖和节流的区别并且怎么实现防抖和节流? A: 区别:防抖是在事件触发后等待一段时间再执行,如果在等待期间再次触发,则重新计时;节流是在一定时间内只执行一次,无论事件触发多少次。 实现:
    • 防抖:
    function debounce(func, delay) {
      let timer;
      return function() {
        const context = this;
        const args = arguments;
        clearTimeout(timer);
        timer = setTimeout(() => {
          func.apply(context, args);
        }, delay);
      };
    }
    
    • 节流:
    function throttle(func, delay) {
      let lastTime = 0;
      return function() {
        const context = this;
        const args = arguments;
        const now = Date.now();
        if (now - lastTime >= delay) {
          func.apply(context, args);
          lastTime = now;
        }
      };
    }
    

AI 生成头像

技术点概述:AI 生成头像是利用 AI 模型根据用户输入的描述或特征生成个性化的头像图片。

核心实现

// AI 头像生成服务
import { Injectable } from '@nestjs/common';
import { OpenAI } from 'openai';

@Injectable()
export class AvatarService {
  private openai: OpenAI;

  constructor() {
    this.openai = new OpenAI({
      apiKey: process.env.OPENAI_API_KEY,
    });
  }

  async generateAvatar(prompt: string): Promise<string> {
    const response = await this.openai.images.generate({
      prompt: `Generate a professional avatar for a user based on: ${prompt}. The avatar should be clean, modern, and suitable for a professional profile.`,
      n: 1,
      size: '512x512',
    });

    return response.data[0].url || '';
  }
}

// 前端组件
import React, { useState } from 'react';
import axios from 'axios';

export default function AvatarGenerator() {
  const [prompt, setPrompt] = useState('');
  const [avatarUrl, setAvatarUrl] = useState('');
  const [isLoading, setIsLoading] = useState(false);

  const handleGenerate = async () => {
    setIsLoading(true);
    try {
      const response = await axios.post('/api/avatar/generate', { prompt });
      setAvatarUrl(response.data.url);
    } catch (error) {
      console.error('Error generating avatar:', error);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <div className="max-w-md mx-auto">
      <h2 className="text-2xl font-bold mb-4">AI 头像生成</h2>
      <input
        type="text"
        value={prompt}
        onChange={(e) => setPrompt(e.target.value)}
        placeholder="描述你想要的头像风格..."
        className="w-full p-2 border rounded mb-4"
      />
      <button
        onClick={handleGenerate}
        disabled={isLoading}
        className="w-full bg-blue-500 text-white p-2 rounded hover:bg-blue-600"
      >
        {isLoading ? '生成中...' : '生成头像'}
      </button>
      {avatarUrl && (
        <div className="mt-4">
          <img src={avatarUrl} alt="Generated Avatar" className="w-full h-auto rounded" />
        </div>
      )}
    </div>
  );
}

使用场景:用户注册或个人资料设置时,生成个性化头像。

优化策略

  • 缓存生成的头像,避免重复生成
  • 提供预设的头像风格选项,简化用户输入
  • 实现头像生成进度的实时反馈

常见问题

  • Q: 如何提高 AI 生成头像的质量? A: 提供更详细的描述,指定风格、颜色和特征等细节。
  • Q: 如何处理生成失败的情况? A: 实现错误处理和重试机制,提供默认头像作为 fallback。

Commit Message 生成

技术点概述:Commit Message 生成是利用 AI 分析代码变更,自动生成符合规范的 commit 消息。

核心实现

// Commit Message 生成服务
import { Injectable } from '@nestjs/common';
import { ChatOpenAI } from '@langchain/openai';
import { HumanMessage } from '@langchain/core/messages';

@Injectable()
export class CommitMessageService {
  private chatModel: ChatOpenAI;

  constructor() {
    this.chatModel = new ChatOpenAI({
      modelName: 'gpt-3.5-turbo',
      temperature: 0.3,
    });
  }

  async generateCommitMessage(diff: string): Promise<string> {
    const messages = [
      new HumanMessage(`Generate a concise and descriptive commit message for the following code changes:\n\n${diff}\n\nFollow the conventional commit format: <type>(<scope>): <description>\n\nTypes: feat, fix, docs, style, refactor, test, chore`),
    ];

    const response = await this.chatModel.invoke(messages);
    return response.content;
  }
}

// 前端组件
import React, { useState } from 'react';
import axios from 'axios';

export default function CommitMessageGenerator() {
  const [diff, setDiff] = useState('');
  const [commitMessage, setCommitMessage] = useState('');
  const [isLoading, setIsLoading] = useState(false);

  const handleGenerate = async () => {
    setIsLoading(true);
    try {
      const response = await axios.post('/api/commit/generate', { diff });
      setCommitMessage(response.data.message);
    } catch (error) {
      console.error('Error generating commit message:', error);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <div className="max-w-2xl mx-auto">
      <h2 className="text-2xl font-bold mb-4">Commit Message 生成</h2>
      <textarea
        value={diff}
        onChange={(e) => setDiff(e.target.value)}
        placeholder="粘贴你的代码变更 diff..."
        className="w-full p-2 border rounded mb-4 h-40"
      />
      <button
        onClick={handleGenerate}
        disabled={isLoading}
        className="w-full bg-blue-500 text-white p-2 rounded hover:bg-blue-600"
      >
        {isLoading ? '生成中...' : '生成 Commit Message'}
      </button>
      {commitMessage && (
        <div className="mt-4">
          <h3 className="font-bold mb-2">生成的 Commit Message:</h3>
          <div className="p-2 border rounded bg-gray-50">
            {commitMessage}
          </div>
        </div>
      )}
    </div>
  );
}

使用场景:开发者提交代码时,自动生成符合规范的 commit 消息。

优化策略

  • 分析代码变更的具体内容,生成更精准的 commit 消息
  • 支持不同的 commit 消息格式,如 conventional commits
  • 提供手动编辑的选项,允许开发者调整生成的消息

常见问题

  • Q: 如何确保生成的 commit 消息符合项目规范? A: 在提示中明确指定 commit 消息的格式和类型要求。
  • Q: 如何处理大型代码变更的 commit 消息生成? A: 对 diff 进行摘要处理,提取关键变更信息,避免 token 超限。

性能优化面试回答

图片懒加载

技术点概述:图片懒加载是指仅当图片进入视口时才加载,减少初始加载时间和带宽消耗。

核心实现

// 图片懒加载组件
import React, { useEffect, useRef } from 'react';

interface LazyImageProps {
  src: string;
  alt: string;
  className?: string;
}

export default function LazyImage({ src, alt, className }: LazyImageProps) {
  const imgRef = useRef<HTMLImageElement>(null);

  useEffect(() => {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          const img = entry.target as HTMLImageElement;
          img.src = img.dataset.src || '';
          observer.unobserve(img);
        }
      });
    });

    if (imgRef.current) {
      observer.observe(imgRef.current);
    }

    return () => {
      if (imgRef.current) {
        observer.unobserve(imgRef.current);
      }
    };
  }, []);

  return (
    <img
      ref={imgRef}
      data-src={src}
      src="/placeholder.jpg"
      alt={alt}
      className={className}
    />
  );
}

使用场景:文章列表、图片画廊等包含大量图片的页面。

优化策略

  • 使用 IntersectionObserver API 实现高效的懒加载
  • 提供合适的占位符,提升用户体验
  • 合理设置图片尺寸和格式,减少加载时间

常见问题

  • Q: 如何实现图片懒加载? A: 使用 IntersectionObserver API 监听图片是否进入视口,当进入视口时设置真实的 src 属性。
  • Q: 图片懒加载的优势是什么? A: 减少初始加载时间,节省带宽,提高页面性能。

无限滚动

技术点概述:无限滚动是指当用户滚动到页面底部时,自动加载更多数据,提供流畅的浏览体验。

核心实现

// 无限滚动组件
import React, { useEffect, useRef } from 'react';

interface InfiniteScrollProps {
  children: React.ReactNode;
  hasMore: boolean;
  isLoading: boolean;
  onLoadMore: () => void;
}

export default function InfiniteScroll({ children, hasMore, isLoading, onLoadMore }: InfiniteScrollProps) {
  const sentinelRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const observer = new IntersectionObserver((entries) => {
      const entry = entries[0];
      if (entry.isIntersecting && hasMore && !isLoading) {
        onLoadMore();
      }
    }, {
      threshold: 0.0
    });

    if (sentinelRef.current) {
      observer.observe(sentinelRef.current);
    }

    return () => {
      if (sentinelRef.current) {
        observer.unobserve(sentinelRef.current);
      }
    };
  }, [hasMore, isLoading, onLoadMore]);

  return (
    <div>
      {children}
      {hasMore && (
        <div ref={sentinelRef} className="h-10 w-full" />
      )}
      {isLoading && (
        <div className="text-center py-4">加载中...</div>
      )}
    </div>
  );
}

使用场景:文章列表、商品列表等需要加载大量数据的页面。

优化策略

  • 使用 IntersectionObserver API 实现高效的滚动检测
  • 合理设置加载阈值,提前触发加载
  • 实现防抖和节流,避免频繁触发加载

常见问题

  • Q: 如何实现无限滚动? A: 使用 IntersectionObserver API 监听页面底部的哨兵元素,当元素进入视口时加载更多数据。
  • Q: 无限滚动的优势是什么? A: 提供流畅的浏览体验,避免分页导航,减少用户操作。

组件缓存

技术点概述:组件缓存是指在路由切换时保持组件的状态和 DOM 结构,避免重复渲染和数据加载。

核心实现

// 使用 react-activation 实现组件缓存
import { KeepAlive, AliveScope } from 'react-activation';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import MainLayout from './layouts/MainLayout';
import Home from './pages/Home';
import Login from './pages/Login';

const router = createBrowserRouter([
  {
    path: '/',
    element: (
      <AliveScope>
        <MainLayout />
      </AliveScope>
    ),
    children: [
      {
        path: '',
        element: (
          <KeepAlive name="home">
            <Home />
          </KeepAlive>
        ),
      },
      {
        path: 'login',
        element: <Login />,
      },
    ],
  },
]);

function App() {
  return <RouterProvider router={router} />;
}

export default App;

使用场景:首页、仪表盘等用户频繁访问的页面。

优化策略

  • 合理使用 KeepAlive 组件,避免过度缓存
  • 实现缓存清理机制,避免内存泄漏
  • 结合路由懒加载,进一步提高性能

常见问题

  • Q: 如何实现组件缓存? A: 使用 react-activation 库的 KeepAlive 组件,或手动实现基于 Map 的组件缓存。
  • Q: 组件缓存的优势是什么? A: 保持组件状态,避免重复渲染和数据加载,提高用户体验。

项目亮点总结

  1. 技术栈现代化:使用 React、TypeScript、NestJS、MockJS 等现代技术栈,确保代码质量和可维护性。

  2. 架构设计合理:前后端分离,模块化架构,依赖注入,提高代码组织性和可扩展性。

  3. AI 能力集成:整合 LangChain、Embedding、Chatbot、AI 生成头像、Commit Message 生成等 AI 技术,实现智能交互功能。

  4. 性能优化到位:图片懒加载、无限滚动、组件缓存等优化措施,提高用户体验。

  5. 安全措施完善:JWT 双 token 机制、bcrypt 密码加密、错误处理等安全措施,保障系统安全。

面试小贴士

  1. 准备充分:熟悉项目的技术栈、架构和核心功能,准备好相关的代码示例。

  2. 结构清晰:回答问题时按照技术点概述、核心实现、使用场景、优化策略、常见问题的框架组织语言。

  3. 突出亮点:强调项目中的技术亮点和解决的问题,展示自己的技术能力。

  4. 代码示例:准备一些关键的代码片段,展示自己的编码风格和技术水平。

  5. 自信表达:保持自信,清晰表达自己的思路和见解,展示良好的沟通能力。


通过以上内容,你可以在面试时自信地介绍文智 AI 全栈交互系统的技术实现和项目亮点,展示自己的全栈开发能力和技术深度。祝你面试成功!