我开发了一款无限画布应用:

在这里插入图片描述

项目背景

大家好,我是一名前端开发者。最近我完成了一个让我非常兴奋的项目——nano banana无限画布应用。这是一个集成了AI图像生成、实时协作、项目管理的现代化创意平台。今天我想和大家分享一下这个项目的开发经历和技术实现。在开始开发之前,我花了很多时间思考技术栈的选择。作为一个前端开发者,我对React比较熟悉,所以前端选择了React 19.1.1。说实话,React 19的新特性让我很兴奋,特别是并发渲染和Suspense的改进。

前端技术栈

  • React 19.1.1 - 我选择了最新版本的React,主要是想体验一下新特性
  • TypeScript - 这个选择让我在开发过程中少了很多bug,类型检查真的很重要
  • Vite - 相比Webpack,Vite的启动速度真的快很多,开发体验很棒
  • Tailwind CSS - 原子化CSS让我不用写很多自定义样式,开发效率提升明显
  • 自定义Hooks - 我把很多业务逻辑封装成了Hooks,代码复用性很好

后端技术栈

  • Node.js + Express - 选择Node.js主要是为了前后端语言统一,减少学习成本
  • MySQL 8.0+ - 关系型数据库,数据结构比较清晰
  • JWT - 无状态的认证方式,适合分布式部署
  • Socket.IO - 实现实时协作功能,用户体验很重要
  • Multer + Sharp - 文件上传和图像处理,Sharp的性能真的很不错
  • Google Gemini AI - AI功能是亮点,Gemini的API调用比较简单

前端架构:我的设计思路

1. 组件化设计:从混乱到清晰

刚开始开发的时候,我把所有逻辑都写在一个大组件里,结果代码越来越乱。后来我意识到需要重新设计架构,于是采用了高度模块化的组件设计。

核心画布组件 - InfiniteCanvas

在这里插入图片描述

这个组件是整个应用的核心,我花了很多时间优化它。让我分享一下我的实现思路:

// 无限画布的核心实现
export const InfiniteCanvas = forwardRef<CanvasApi, InfiniteCanvasProps>(({ 
  elements, 
  selectedElementIds, 
  onSelectElement,
  onMarqueeSelect, 
  onUpdateElement, 
  onInteractionEnd,
  setResetViewCallback,
  onGenerate,
  onContextMenu,
  onEditDrawing,
  imageStyle,
  onSetImageStyle,
}, ref) => {
  // 画布状态管理
  const [pan, setPan] = useState<Point>({ x: 0, y: 0 });
  const [zoom, setZoom] = useState(1);
  const [isPanning, setIsPanning] = useState(false);
  const [marqueeRect, setMarqueeRect] = useState<MarqueeRect | null>(null);

设计亮点:

  • 使用forwardRef暴露画布API给父组件,这样父组件可以直接控制画布
  • 实现了完整的2D变换系统,支持平移、缩放、旋转,用户体验很流畅
  • 支持框选和多重选择,这个功能让我调试了很久
  • 集成了AI生成功能,用户可以直接在画布上生成图像
可变换元素组件 - TransformableElement

在这里插入图片描述

这个组件是我最得意的设计之一。我设计了一个统一的元素交互系统,支持拖拽、缩放、旋转等操作:

// 支持拖拽、缩放、旋转的通用元素组件
export const TransformableElement: React.FC<TransformableElementProps> = ({ 
  element, isSelected, zoom, onSelect, onUpdate, onInteractionEnd, onContextMenu, onEditDrawing 
}) => {
  const [interaction, setInteraction] = useState<Interaction>(null);
  const [isEditing, setIsEditing] = useState(false);

我的实现思路:

  • 统一的元素交互系统,所有元素都使用相同的交互逻辑
  • 支持拖拽、缩放、旋转操作,用户操作很直观
  • 实时预览和状态同步,不会有延迟感
  • 双击编辑功能,用户体验很好

说实话,这个组件的交互逻辑让我调试了很久,特别是旋转和缩放的数学计算,我花了很多时间才搞明白。

2. 状态管理:我的Hooks设计哲学

在状态管理方面,我没有使用Redux或者Zustand这些状态管理库,而是选择了自定义Hooks的方式。我觉得这样更灵活,也更符合React的设计理念。

我的自定义Hooks设计

useAuth Hook - 认证状态管理

这个Hook让我很满意,它处理了所有的认证逻辑:

export const useAuth = () => {
  const [authState, setAuthState] = useState<AuthState>({
    user: null,
    isAuthenticated: false,
    isLoading: true,
    error: null
  });

  // 自动检查认证状态
  const checkAuth = useCallback(async () => {
    const token = localStorage.getItem('auth_token');
    if (!token) {
      setAuthState(prev => ({ ...prev, isLoading: false }));
      return;
    }
    // JWT验证逻辑...
  }, []);

useProject Hook - 项目管理

这个Hook管理项目的所有状态,包括元素、保存状态等:

export const useProject = () => {
  const [projectState, setProjectState] = useState<ProjectState>({
    currentProject: null,
    elements: [],
    isLoading: false,
    error: null,
    hasUnsavedChanges: false
  });
  
  // 防抖保存机制
  const saveTimeoutRef = useRef<NodeJS.Timeout | null>(null);

我实现了一个防抖保存机制,用户操作后2秒自动保存,这样既不会频繁调用API,也不会丢失用户的数据。

useHistoryState Hook - 历史记录管理

这个Hook是我最得意的设计之一,它实现了类似Photoshop的历史记录功能:

export const useHistoryState = <T>(initialState: T) => {
  const [history, setHistory] = useState<T[]>([initialState]);
  const [currentIndex, setCurrentIndex] = useState(0);
  
  const setState = useCallback((
    action: T | ((prevState: T) => T),
    options: SetStateOptions = { addToHistory: true }
  ) => {
    // 智能历史记录管理
  }, [history, currentIndex]);

用户可以通过Ctrl+Z撤销操作,Ctrl+Y重做操作,体验很好。

3. TypeScript:我的类型安全之路

刚开始开发的时候,我使用的是JavaScript,结果遇到了很多类型错误。后来我决定迁移到TypeScript,这个决定让我少了很多bug。

我的类型系统设计

我设计了一个严格的类型系统,特别是元素类型:

// 基础类型定义
export interface Point {
  x: number;
  y: number;
}

export type ElementType = 'note' | 'image' | 'arrow' | 'drawing';

// 元素类型系统
interface BaseElement {
  id: string;
  position: Point;
  width: number;
  height: number;
  rotation: number;
  zIndex: number;
}

export interface NoteElement extends BaseElement {
  type: 'note';
  content: string;
  color: string;
  textAlign?: 'left' | 'center' | 'right';
}

export interface ImageElement extends BaseElement {
  type: 'image';
  src: string;
}

export interface ArrowElement extends BaseElement {
  type: 'arrow';
  start: Point;
  end: Point;
  color: string;
}

export interface DrawingElement extends BaseElement {
  type: 'drawing';
  src: string; // base64 data URL
}

export type CanvasElement = NoteElement | ImageElement | ArrowElement | DrawingElement;

这个类型系统让我在开发过程中能够提前发现很多潜在的问题,特别是联合类型的使用,让代码更加安全。

后端开发:从零开始的Node.js之旅

作为一个前端开发者,后端开发对我来说是一个挑战。我选择了Node.js + Express,主要是为了保持技术栈的一致性。

1. RESTful API:我的设计思路

我采用了标准的RESTful API设计模式,这样前端调用起来比较直观。

认证系统:安全第一

用户认证是我最关注的部分,我实现了完整的JWT认证系统:

// 用户注册
router.post('/register', validateUserRegistration, async (req, res) => {
  try {
    const { username, email, password, displayName } = req.body;
    
    // 密码哈希处理
    const saltRounds = 12;
    const passwordHash = await bcrypt.hash(password, saltRounds);
    
    // JWT令牌生成
    const token = jwt.sign(
      { userId: userId },
      process.env.JWT_SECRET,
      { expiresIn: process.env.JWT_EXPIRES_IN || '7d' }
    );
    
    // 会话管理
    const tokenHash = crypto.createHash('sha256').update(token).digest('hex');
    await executeQuery(
      'INSERT INTO user_sessions (user_id, token_hash, expires_at, ip_address, user_agent) VALUES (?, ?, ?, ?, ?)',
      [userId, tokenHash, expiresAt, req.ip, req.get('User-Agent')]
    );
  } catch (error) {
    // 错误处理
  }
});

我使用了bcrypt来哈希密码,JWT来管理会话,还记录了用户的IP和User-Agent,这样安全性更高。

画布项目管理:数据一致性很重要

项目管理是我后端开发的重点,我使用了事务来保证数据一致性:

// 创建项目
router.post('/', authenticateToken, validateProjectCreation, logActivity('create_project'), async (req, res) => {
  try {
    const { title, description, isPublic, elements } = req.body;
    const projectId = require('crypto').randomUUID();
    
    // 事务处理
    const queries = [
      {
        query: 'INSERT INTO canvas_projects (id, user_id, title, description, is_public) VALUES (?, ?, ?, ?, ?)',
        params: [projectId, req.user.id, title, description, isPublic || false]
      }
    ];
    
    await executeTransaction(queries);
  } catch (error) {
    // 错误处理
  }
});

我使用了UUID作为项目ID,这样在分布式环境下也不会有冲突。事务处理让我不用担心数据不一致的问题。

2. 数据库设计:我的MySQL学习之路

数据库设计对我来说是一个新的领域,我选择了MySQL,主要是因为它比较成熟,文档也比较完善。

我的表结构设计

我设计了几个核心表来存储应用数据:

-- 用户表
CREATE TABLE IF NOT EXISTS users (
    id VARCHAR(36) PRIMARY KEY DEFAULT (UUID()),
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    display_name VARCHAR(100),
    avatar_url VARCHAR(500),
    is_active BOOLEAN DEFAULT TRUE,
    last_login_at TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

-- 画布项目表
CREATE TABLE IF NOT EXISTS canvas_projects (
    id VARCHAR(36) PRIMARY KEY DEFAULT (UUID()),
    user_id VARCHAR(36) NOT NULL,
    title VARCHAR(200) NOT NULL DEFAULT 'Untitled Canvas',
    description TEXT,
    thumbnail_url VARCHAR(500),
    is_public BOOLEAN DEFAULT FALSE,
    is_template BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

-- 画布元素表
CREATE TABLE IF NOT EXISTS canvas_elements (
    id VARCHAR(36) PRIMARY KEY DEFAULT (UUID()),
    project_id VARCHAR(36) NOT NULL,
    element_type ENUM('note', 'image', 'arrow', 'drawing') NOT NULL,
    position_x DECIMAL(10, 2) NOT NULL,
    position_y DECIMAL(10, 2) NOT NULL,
    width DECIMAL(10, 2) NOT NULL,
    height DECIMAL(10, 2) NOT NULL,
    rotation DECIMAL(5, 2) DEFAULT 0,
    z_index INT DEFAULT 0,
    element_data JSON NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (project_id) REFERENCES canvas_projects(id) ON DELETE CASCADE
);

我的设计思路:

  • 使用UUID作为主键,这样在分布式环境下也不会有冲突
  • JSON字段存储元素数据,这样我可以灵活地存储不同类型的元素
  • 完善的索引设计,查询性能更好
  • 外键约束保证数据一致性,不会出现孤儿数据

3. AI服务集成:让应用更智能

AI功能是这个应用的亮点之一,我集成了Google Gemini AI来实现图像生成和编辑功能。

我的AI服务设计

我创建了一个AIService类来封装所有的AI相关功能:

class AIService {
  constructor() {
    this.apiKey = process.env.AI_API_KEY;
    this.baseUrl = process.env.AI_API_URL || 'https://ai.juguang.chat/v1beta/models/gemini-2.5-flash-image-preview:generateContent';
  }

  async generateTextImage(prompt) {
    try {
      const response = await fetch(this.baseUrl, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${this.apiKey}`,
        },
        body: JSON.stringify({
          contents: [
            {
              parts: [
                {
                  text: prompt
                }
              ]
            }
          ]
        })
      });

      const data = await response.json();
      
      // 提取生成的图像数据
      for (const candidate of data.candidates) {
        for (const part of candidate.content.parts) {
          if (part.inlineData) {
            return `data:${part.inlineData.mimeType};base64,${part.inlineData.data}`;
          }
        }
      }
      
      return null;
    } catch (error) {
      console.error('Generate text image failed:', error);
      throw error;
    }
  }
}

我的AI功能实现:
在这里插入图片描述

  • 文本到图像生成:用户输入描述,AI生成对应的图像
  • 图像编辑和对象移除:用户可以编辑现有图像
  • 多种艺术风格支持:包括像素风、油画风、水彩风等
  • 自动项目创建:AI生成图像时自动创建项目

说实话,AI功能的集成让我学到了很多,特别是如何处理异步请求和错误处理。

4. 文件处理:让用户上传更安全

文件上传是一个重要的功能,我需要确保用户上传的文件是安全的,并且处理效率要高。

我的文件处理方案

我使用了Multer来处理文件上传,Sharp来处理图像:

// Multer配置
const storage = multer.diskStorage({
  destination: async (req, file, cb) => {
    const uploadDir = process.env.UPLOAD_DIR || 'uploads';
    try {
      await fs.mkdir(uploadDir, { recursive: true });
      cb(null, uploadDir);
    } catch (error) {
      cb(error);
    }
  },
  filename: (req, file, cb) => {
    const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
    const ext = path.extname(file.originalname);
    cb(null, `file-${uniqueSuffix}${ext}`);
  }
});

// 图像处理
if (file.mimetype.startsWith('image/')) {
  try {
    const processedPath = file.path.replace(path.extname(file.path), '_processed' + path.extname(file.path));
    
    await sharp(file.path)
      .resize(2048, 2048, { 
        fit: 'inside',
        withoutEnlargement: true 
      })
      .jpeg({ quality: 85 })
      .toFile(processedPath);

    await fs.unlink(file.path);
    await fs.rename(processedPath, file.path);
  } catch (error) {
    console.error('Image processing error:', error);
  }
}

我实现了文件类型验证、大小限制、图像压缩等功能,这样既保证了安全性,又优化了存储空间。

核心功能:我的技术实现之路

1. 无限画布:最复杂的部分

无限画布是这个应用的核心功能,也是我花费最多时间开发的部分。我需要实现一个流畅的2D变换系统。

坐标系统:数学是基础

我设计了一个双坐标系统,屏幕坐标和世界坐标之间的转换:

// 屏幕坐标到世界坐标的转换
const screenToWorld = useCallback((screenPoint: Point): Point => {
  return {
    x: (screenPoint.x - pan.x) / zoom,
    y: (screenPoint.y - pan.y) / zoom,
  };
}, [pan, zoom]);

// 世界坐标到屏幕坐标的转换
const worldToScreen = useCallback((worldPoint: Point): Point => {
  return {
    x: worldPoint.x * zoom + pan.x,
    y: worldPoint.y * zoom + pan.y,
  };
}, [pan, zoom]);

这个坐标转换让我调试了很久,特别是缩放时的坐标计算。

变换系统:用户体验的关键

元素变换是这个功能的核心,我实现了拖拽、缩放、旋转等操作:

// 元素变换处理
const handleInteractionMove = useCallback((e: MouseEvent) => {
  if (!interaction) return;

  const { type, startPoint, startElement } = interaction;
  const dx = (e.clientX - startPoint.x) / zoom;
  const dy = (e.clientY - startPoint.y) / zoom;

  if (type === 'drag') {
    const newPosition = { x: startElement.position.x + dx, y: startElement.position.y + dy };
    onUpdate({ ...startElement, position: newPosition });
  } else if (type === 'resize') {
    const rad = startElement.rotation * (Math.PI / 180);
    const cos = Math.cos(-rad);
    const sin = Math.sin(-rad);
    const rotDx = dx * cos - dy * sin;
    const rotDy = dx * sin + dy * cos;

    const newWidth = Math.max(20, startElement.width + rotDx);
    const newHeight = Math.max(20, startElement.height + rotDy);
    
    onUpdate({ 
      ...startElement, 
      width: newWidth, 
      height: newHeight
    });
  }
}, [interaction, onUpdate, zoom]);

旋转和缩放的数学计算让我重新学习了三角函数,不过最终的效果让我很满意。

2. 实时协作:让多人同时创作

实时协作功能让多个用户可以同时编辑同一个项目,这个功能让我学到了很多关于WebSocket的知识。

Socket.IO:我的实时通信方案

我选择了Socket.IO来实现实时通信,因为它提供了很好的错误处理和重连机制:

// Socket.IO服务器配置
const io = new Server(server, {
  cors: {
    origin: "*",
    methods: ["GET", "POST"],
    credentials: true
  }
});

// Socket事件处理
setupSocketHandlers(io);

// 客户端连接处理
io.on('connection', (socket) => {
  console.log('User connected:', socket.id);
  
  socket.on('join-project', (projectId) => {
    socket.join(projectId);
    console.log(`User ${socket.id} joined project ${projectId}`);
  });
  
  socket.on('element-update', (data) => {
    socket.to(data.projectId).emit('element-updated', data);
  });
});

这个功能让我体验到了真正的实时协作,用户可以看到其他用户的实时操作,就像Google Docs一样。

3. 绘图系统:让创意自由表达

绘图功能让用户可以在画布上自由创作,我实现了一个功能完整的绘图工具。

Canvas绘图:我的实现方案

我使用HTML5 Canvas来实现绘图功能,并添加了历史记录管理:

// 绘图模态框组件
export const DrawingModal: React.FC<DrawingModalProps> = ({ element, onSave, onClose }) => {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const contextRef = useRef<CanvasRenderingContext2D | null>(null);
  const [isDrawing, setIsDrawing] = useState(false);
  const [tool, setTool] = useState<'pencil' | 'eraser'>('pencil');
  const [color, setColor] = useState('#000000');
  const [brushSize, setBrushSize] = useState(5);

  // 历史记录管理
  const [history, setHistory] = useState<ImageData[]>([]);
  const [historyIndex, setHistoryIndex] = useState(-1);

  const saveHistoryState = useCallback(() => {
    const context = contextRef.current;
    if (!context) return;
    const imageData = context.getImageData(0, 0, context.canvas.width, context.canvas.height);
    
    setHistory(prev => {
      const newHistory = prev.slice(0, historyIndex + 1);
      return [...newHistory, imageData];
    });
    setHistoryIndex(prev => prev + 1);
  }, [historyIndex]);

  // 绘图事件处理
  const startDrawing = useCallback((e: React.MouseEvent) => {
    const point = getCanvasPoint(e);
    const context = contextRef.current;
    if (!point || !context) return;

    context.strokeStyle = color;
    context.lineWidth = brushSize;
    context.globalCompositeOperation = tool === 'pencil' ? 'source-over' : 'destination-out';
    context.beginPath();
    context.moveTo(point.x, point.y);
    setIsDrawing(true);
  }, [tool, color, brushSize, getCanvasPoint]);
};

我实现了铅笔、橡皮擦、多种颜色、不同画笔大小等功能,还支持撤销和重做,用户体验很好。

4. 图像编辑:AI让创意更智能

图像编辑功能是这个应用的亮点之一,我集成了AI来实现智能图像编辑。

AI图像编辑:我的实现思路

我实现了一个完整的图像编辑系统,用户可以编辑现有图像或移除不需要的对象:

// 图像编辑模态框
export const ImageEditModal: React.FC<ImageEditModalProps> = ({ element, onSave, onClose }) => {
  const [tool, setTool] = useState<'brush' | 'eraser'>('brush');
  const [brushSize, setBrushSize] = useState(20);
  const [prompt, setPrompt] = useState('');
  const [isLoading, setIsLoading] = useState(false);
  
  // 蒙版合成
  const compositeImageWithMask = useCallback(async (baseSrc: string, maskDataUrl: string): Promise<string> => {
    const image = new Image();
    image.crossOrigin = "anonymous";
    image.src = baseSrc;
    await new Promise((resolve, reject) => {
      image.onload = resolve;
      image.onerror = reject;
    });

    const maskImage = new Image();
    maskImage.src = maskDataUrl;
    await new Promise((resolve, reject) => {
      maskImage.onload = resolve;
      maskImage.onerror = reject;
    });

    const compositeCanvas = document.createElement('canvas');
    compositeCanvas.width = image.naturalWidth;
    compositeCanvas.height = image.naturalHeight;
    const ctx = compositeCanvas.getContext('2d');
    if (!ctx) throw new Error("Could not create canvas context");

    ctx.drawImage(image, 0, 0);
    ctx.drawImage(maskImage, 0, 0);
    
    return compositeCanvas.toDataURL('image/png');
  }, []);

  // AI生成处理
  const runGeneration = async (context: GenerationContext) => {
    setIsLoading(true);
    try {
      const compositeImageBase64 = await compositeImageWithMask(context.baseImageSrc, context.maskDataUrl);
      const [header, data] = compositeImageBase64.split(',');
      const mimeType = header.match(/data:(.*);base64/)?.[1] || 'image/png';

      let textPrompt = '';
      if (context.type === 'remove') {
        textPrompt = '仔细移除半透明红色高亮的对象或区域,并真实地填充背景(修复)。';
      } else {
        textPrompt = `在半透明红色高亮的区域中,${context.prompt}。使其看起来自然且无缝。`;
      }

      // 调用后端API
      const response = await fetch('/api/ai/generate-image-with-image', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${token}`
        },
        body: JSON.stringify({
          prompt: textPrompt,
          image: compositeImageBase64,
          mimeType: mimeType
        })
      });

      const result = await response.json();
      if (result.success && result.imageData) {
        setPreviewImageSrc(result.imageData);
      }
    } catch (error) {
      console.error("Error editing image:", error);
    } finally {
      setIsLoading(false);
    }
  };
};

这个功能让我体验到了AI的强大,用户只需要简单描述想要的效果,AI就能生成相应的图像。

性能优化:让应用更流畅

在这里插入图片描述

在开发过程中,我发现性能优化是一个持续的过程。我采用了多种策略来提升应用的性能。

1. 前端性能优化:用户体验的关键

虚拟化渲染:只渲染可见元素

当画布上有大量元素时,渲染所有元素会导致性能问题。我实现了虚拟化渲染:

// 只渲染视口内的元素
const visibleElements = useMemo(() => {
  return elements.filter(element => {
    const elementScreenPos = worldToScreen(element.position);
    return (
      elementScreenPos.x + element.width > 0 &&
      elementScreenPos.x - element.width < window.innerWidth &&
      elementScreenPos.y + element.height > 0 &&
      elementScreenPos.y - element.height < window.innerHeight
    );
  });
}, [elements, worldToScreen]);

这样只渲染用户能看到的元素,大大提升了性能。

防抖和节流:减少不必要的操作

我实现了防抖保存机制,避免频繁的API调用:

// 防抖保存
const saveTimeoutRef = useRef<NodeJS.Timeout | null>(null);

const updateElements = useCallback((elements: CanvasElement[]) => {
  setProjectState(prev => ({
    ...prev,
    elements,
    hasUnsavedChanges: true
  }));
  
  // 防抖保存,避免频繁API调用
  if (projectState.currentProject) {
    if (saveTimeoutRef.current) {
      clearTimeout(saveTimeoutRef.current);
    }
    
    saveTimeoutRef.current = setTimeout(() => {
      saveChanges();
    }, 2000); // 2秒后自动保存
  }
}, [projectState.currentProject, saveChanges]);

这样用户操作后2秒自动保存,既不会丢失数据,也不会频繁调用API。

2. 后端性能优化:数据库是关键

数据库连接池:提升并发性能

我使用了连接池来管理数据库连接,这样可以提高并发性能:

const createPool = () => {
  if (!pool) {
    pool = mysql.createPool({
      host: process.env.DB_HOST || '43.139.101.176',
      port: process.env.DB_PORT || 3306,
      user: process.env.DB_USER || 'ban',
      password: process.env.DB_PASSWORD || 'BBiBwsJcjCCaCHft',
      database: process.env.DB_NAME || 'ban',
      waitForConnections: true,
      connectionLimit: 10,
      queueLimit: 0,
      charset: 'utf8mb4',
      acquireTimeout: 60000,
      ssl: false,
      supportBigNumbers: true,
      bigNumberStrings: true,
      dateStrings: true
    });
  }
  return pool;
};

连接池让我不用担心数据库连接的问题,系统会自动管理连接的生命周期。

查询优化:索引很重要

我设计了复合索引来优化查询性能:

-- 复合索引优化
CREATE INDEX idx_canvas_elements_composite ON canvas_elements(project_id, element_type, z_index);
CREATE INDEX idx_file_uploads_composite ON file_uploads(user_id, project_id, created_at);
CREATE INDEX idx_activity_logs_composite ON activity_logs(user_id, project_id, created_at);

这些索引大大提升了查询速度,特别是在处理大量数据时。

安全机制:保护用户数据

安全是我最关注的问题之一,我实现了多层安全机制来保护用户数据。

1. 身份认证:JWT + 会话管理

我使用了JWT令牌认证,配合会话管理:

  • JWT令牌认证:无状态的认证方式
  • 会话管理:记录用户的登录状态
  • 密码哈希(bcrypt):安全的密码存储
  • 令牌过期机制:定期更新令牌

2. 数据验证:防止恶意输入

我实现了严格的数据验证:

// 输入验证中间件
const validateProjectCreation = [
  body('title').trim().isLength({ min: 1, max: 200 }).withMessage('标题长度必须在1-200字符之间'),
  body('description').optional().trim().isLength({ max: 1000 }).withMessage('描述长度不能超过1000字符'),
  body('isPublic').optional().isBoolean().withMessage('isPublic必须是布尔值'),
  body('elements').optional().isArray().withMessage('elements必须是数组')
];

这样可以防止恶意输入和SQL注入攻击。

3. 文件安全:保护服务器

文件上传是一个安全风险点,我实现了多重保护:

  • 文件类型验证:只允许特定类型的文件
  • 文件大小限制:防止大文件攻击
  • 安全的文件存储路径:避免路径遍历攻击
  • 病毒扫描(可扩展):未来可以集成病毒扫描功能

未来规划:继续完善应用

虽然nano banana无限画布应用已经基本完成,但我还有很多想法想要实现。

结语:我的开发感悟

开发nano banana无限画布应用是一次非常有意义的经历。从最初的想法到最终的产品,我学到了很多技术知识,也积累了不少开发经验。

开发过程中的挑战

开发过程中遇到了很多挑战:

  • 无限画布的坐标系统设计
  • 元素变换的数学计算
  • AI服务的集成和错误处理
  • 性能优化和安全防护

对未来的展望

虽然这个应用还有很多可以改进的地方,但我对它的未来充满期待。我希望能够继续完善它,让它成为一个真正有用的创意工具。

如果你对这个项目感兴趣,或者有什么建议,欢迎和我交流。技术分享是程序员成长的重要方式,我也希望通过这篇文章能够帮助到其他开发者。


这就是我开发nano banana无限画布应用的故事。虽然名字有点奇怪,但这个项目让我学到了很多,也让我对Web开发有了更深的热爱。希望我的分享能够对大家有所帮助!

Logo

欢迎加入西安开发者社区!我们致力于为西安地区的开发者提供学习、合作和成长的机会。参与我们的活动,与专家分享最新技术趋势,解决挑战,探索创新。加入我们,共同打造技术社区!

更多推荐