Web开发项目示例

高级 120分钟 更新时间: 2024年8月

项目概述

本示例展示如何使用Claude AI辅助开发一个完整的任务管理Web应用,包括用户认证、任务CRUD操作、实时更新等功能。

技术栈

  • 后端:Node.js + Express + MongoDB
  • 前端:React + TypeScript + Tailwind CSS
  • 认证:JWT + bcrypt
  • 实时通信:Socket.io
  • 部署:Docker + Nginx

功能特性

  • 用户注册和登录
  • 任务的增删改查
  • 任务状态管理
  • 实时协作功能
  • 响应式设计

后端API开发

项目初始化

# 创建项目目录
mkdir task-manager-app
cd task-manager-app

# 初始化后端
mkdir backend
cd backend
npm init -y

# 安装依赖
npm install express mongoose bcryptjs jsonwebtoken cors dotenv
npm install -D nodemon @types/node typescript

# 创建基本目录结构
mkdir src
mkdir src/models src/routes src/middleware src/controllers

Express服务器配置

// src/app.js
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
require('dotenv').config();

const authRoutes = require('./routes/auth');
const taskRoutes = require('./routes/tasks');
const authMiddleware = require('./middleware/auth');

const app = express();

// 中间件
app.use(cors());
app.use(express.json());

// 数据库连接
mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/taskmanager', {
    useNewUrlParser: true,
    useUnifiedTopology: true,
});

// 路由
app.use('/api/auth', authRoutes);
app.use('/api/tasks', authMiddleware, taskRoutes);

// 错误处理中间件
app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).json({ message: '服务器内部错误' });
});

const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
    console.log(`服务器运行在端口 ${PORT}`);
});

module.exports = app;

数据模型定义

// src/models/User.js
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');

const userSchema = new mongoose.Schema({
    username: {
        type: String,
        required: true,
        unique: true,
        trim: true,
        minlength: 3,
        maxlength: 30
    },
    email: {
        type: String,
        required: true,
        unique: true,
        lowercase: true,
        match: [/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/, '请输入有效的邮箱地址']
    },
    password: {
        type: String,
        required: true,
        minlength: 6
    },
    avatar: {
        type: String,
        default: ''
    }
}, {
    timestamps: true
});

// 密码加密中间件
userSchema.pre('save', async function(next) {
    if (!this.isModified('password')) return next();
    
    try {
        const salt = await bcrypt.genSalt(10);
        this.password = await bcrypt.hash(this.password, salt);
        next();
    } catch (error) {
        next(error);
    }
});

// 密码验证方法
userSchema.methods.comparePassword = async function(candidatePassword) {
    return bcrypt.compare(candidatePassword, this.password);
};

module.exports = mongoose.model('User', userSchema);
// src/models/Task.js
const mongoose = require('mongoose');

const taskSchema = new mongoose.Schema({
    title: {
        type: String,
        required: true,
        trim: true,
        maxlength: 100
    },
    description: {
        type: String,
        trim: true,
        maxlength: 500
    },
    status: {
        type: String,
        enum: ['待办', '进行中', '已完成'],
        default: '待办'
    },
    priority: {
        type: String,
        enum: ['低', '中', '高'],
        default: '中'
    },
    dueDate: {
        type: Date
    },
    assignedTo: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'User',
        required: true
    },
    createdBy: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'User',
        required: true
    },
    tags: [{
        type: String,
        trim: true
    }]
}, {
    timestamps: true
});

// 索引优化
taskSchema.index({ assignedTo: 1, status: 1 });
taskSchema.index({ createdBy: 1 });
taskSchema.index({ dueDate: 1 });

module.exports = mongoose.model('Task', taskSchema);

认证控制器

// src/controllers/authController.js
const jwt = require('jsonwebtoken');
const User = require('../models/User');

const generateToken = (userId) => {
    return jwt.sign({ userId }, process.env.JWT_SECRET || 'your-secret-key', {
        expiresIn: '7d'
    });
};

const register = async (req, res) => {
    try {
        const { username, email, password } = req.body;

        // 检查用户是否已存在
        const existingUser = await User.findOne({
            $or: [{ email }, { username }]
        });

        if (existingUser) {
            return res.status(400).json({
                message: '用户名或邮箱已存在'
            });
        }

        // 创建新用户
        const user = new User({ username, email, password });
        await user.save();

        // 生成token
        const token = generateToken(user._id);

        res.status(201).json({
            message: '注册成功',
            token,
            user: {
                id: user._id,
                username: user.username,
                email: user.email,
                avatar: user.avatar
            }
        });
    } catch (error) {
        console.error('注册错误:', error);
        res.status(500).json({ message: '注册失败,请稍后重试' });
    }
};

const login = async (req, res) => {
    try {
        const { email, password } = req.body;

        // 查找用户
        const user = await User.findOne({ email });
        if (!user) {
            return res.status(401).json({ message: '邮箱或密码错误' });
        }

        // 验证密码
        const isPasswordValid = await user.comparePassword(password);
        if (!isPasswordValid) {
            return res.status(401).json({ message: '邮箱或密码错误' });
        }

        // 生成token
        const token = generateToken(user._id);

        res.json({
            message: '登录成功',
            token,
            user: {
                id: user._id,
                username: user.username,
                email: user.email,
                avatar: user.avatar
            }
        });
    } catch (error) {
        console.error('登录错误:', error);
        res.status(500).json({ message: '登录失败,请稍后重试' });
    }
};

module.exports = { register, login };

前端React应用

项目初始化

# 创建React应用
cd .. # 回到项目根目录
npx create-react-app frontend --template typescript
cd frontend

# 安装额外依赖
npm install axios react-router-dom @types/react-router-dom
npm install tailwindcss @tailwindcss/forms
npm install react-hot-toast lucide-react

# 配置Tailwind CSS
npx tailwindcss init -p

应用入口组件

// src/App.tsx
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
import { Toaster } from 'react-hot-toast';
import { AuthProvider, useAuth } from './contexts/AuthContext';
import Login from './components/Auth/Login';
import Register from './components/Auth/Register';
import Dashboard from './components/Dashboard/Dashboard';
import TaskList from './components/Tasks/TaskList';
import './App.css';

const ProtectedRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const { user, loading } = useAuth();
  
  if (loading) {
    return 
; } return user ? <>{children} : ; }; const App: React.FC = () => { return (
} /> } /> } /> } /> } />
); }; export default App;

认证上下文

// src/contexts/AuthContext.tsx
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
import axios from 'axios';
import toast from 'react-hot-toast';

interface User {
  id: string;
  username: string;
  email: string;
  avatar: string;
}

interface AuthContextType {
  user: User | null;
  loading: boolean;
  login: (email: string, password: string) => Promise;
  register: (username: string, email: string, password: string) => Promise;
  logout: () => void;
}

const AuthContext = createContext(undefined);

export const useAuth = () => {
  const context = useContext(AuthContext);
  if (context === undefined) {
    throw new Error('useAuth must be used within an AuthProvider');
  }
  return context;
};

interface AuthProviderProps {
  children: ReactNode;
}

export const AuthProvider: React.FC = ({ children }) => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  // 配置axios默认设置
  useEffect(() => {
    const token = localStorage.getItem('token');
    if (token) {
      axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
      // 验证token有效性
      verifyToken();
    } else {
      setLoading(false);
    }
  }, []);

  const verifyToken = async () => {
    try {
      const response = await axios.get('/api/auth/verify');
      setUser(response.data.user);
    } catch (error) {
      localStorage.removeItem('token');
      delete axios.defaults.headers.common['Authorization'];
    } finally {
      setLoading(false);
    }
  };

  const login = async (email: string, password: string) => {
    try {
      const response = await axios.post('/api/auth/login', { email, password });
      const { token, user } = response.data;
      
      localStorage.setItem('token', token);
      axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
      setUser(user);
      
      toast.success('登录成功!');
    } catch (error: any) {
      const message = error.response?.data?.message || '登录失败';
      toast.error(message);
      throw error;
    }
  };

  const register = async (username: string, email: string, password: string) => {
    try {
      const response = await axios.post('/api/auth/register', {
        username,
        email,
        password
      });
      const { token, user } = response.data;
      
      localStorage.setItem('token', token);
      axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
      setUser(user);
      
      toast.success('注册成功!');
    } catch (error: any) {
      const message = error.response?.data?.message || '注册失败';
      toast.error(message);
      throw error;
    }
  };

  const logout = () => {
    localStorage.removeItem('token');
    delete axios.defaults.headers.common['Authorization'];
    setUser(null);
    toast.success('已退出登录');
  };

  const value = {
    user,
    loading,
    login,
    register,
    logout
  };

  return (
    
      {children}
    
  );
};

任务列表组件

// src/components/Tasks/TaskList.tsx
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { Plus, Search, Filter } from 'lucide-react';
import TaskCard from './TaskCard';
import TaskModal from './TaskModal';
import toast from 'react-hot-toast';

interface Task {
  _id: string;
  title: string;
  description: string;
  status: '待办' | '进行中' | '已完成';
  priority: '低' | '中' | '高';
  dueDate?: string;
  tags: string[];
  createdAt: string;
  updatedAt: string;
}

const TaskList: React.FC = () => {
  const [tasks, setTasks] = useState([]);
  const [loading, setLoading] = useState(true);
  const [searchTerm, setSearchTerm] = useState('');
  const [statusFilter, setStatusFilter] = useState('all');
  const [isModalOpen, setIsModalOpen] = useState(false);
  const [editingTask, setEditingTask] = useState(null);

  useEffect(() => {
    fetchTasks();
  }, []);

  const fetchTasks = async () => {
    try {
      const response = await axios.get('/api/tasks');
      setTasks(response.data);
    } catch (error) {
      toast.error('获取任务列表失败');
    } finally {
      setLoading(false);
    }
  };

  const handleCreateTask = async (taskData: Partial) => {
    try {
      const response = await axios.post('/api/tasks', taskData);
      setTasks([response.data, ...tasks]);
      toast.success('任务创建成功');
      setIsModalOpen(false);
    } catch (error) {
      toast.error('创建任务失败');
    }
  };

  const handleUpdateTask = async (taskId: string, taskData: Partial) => {
    try {
      const response = await axios.put(`/api/tasks/${taskId}`, taskData);
      setTasks(tasks.map(task => 
        task._id === taskId ? response.data : task
      ));
      toast.success('任务更新成功');
      setEditingTask(null);
      setIsModalOpen(false);
    } catch (error) {
      toast.error('更新任务失败');
    }
  };

  const handleDeleteTask = async (taskId: string) => {
    if (!window.confirm('确定要删除这个任务吗?')) return;
    
    try {
      await axios.delete(`/api/tasks/${taskId}`);
      setTasks(tasks.filter(task => task._id !== taskId));
      toast.success('任务删除成功');
    } catch (error) {
      toast.error('删除任务失败');
    }
  };

  const filteredTasks = tasks.filter(task => {
    const matchesSearch = task.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
                         task.description.toLowerCase().includes(searchTerm.toLowerCase());
    const matchesStatus = statusFilter === 'all' || task.status === statusFilter;
    return matchesSearch && matchesStatus;
  });

  if (loading) {
    return 
; } return (

任务管理

{/* 搜索和过滤 */}
setSearchTerm(e.target.value)} className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" />
{/* 任务列表 */}
{filteredTasks.map(task => ( { setEditingTask(task); setIsModalOpen(true); }} onDelete={handleDeleteTask} /> ))}
{filteredTasks.length === 0 && (

没有找到匹配的任务

)} {/* 任务模态框 */} {isModalOpen && ( handleUpdateTask(editingTask._id, data) : handleCreateTask } onClose={() => { setIsModalOpen(false); setEditingTask(null); }} /> )}
); }; export default TaskList;

数据库设计

MongoDB集合结构

用户集合 (users)

{
  "_id": ObjectId("..."),
  "username": "张三",
  "email": "zhangsan@example.com",
  "password": "$2a$10$...", // 加密后的密码
  "avatar": "https://example.com/avatar.jpg",
  "createdAt": ISODate("2024-01-01T00:00:00Z"),
  "updatedAt": ISODate("2024-01-01T00:00:00Z")
}

任务集合 (tasks)

{
  "_id": ObjectId("..."),
  "title": "完成项目文档",
  "description": "编写项目的技术文档和用户手册",
  "status": "进行中",
  "priority": "高",
  "dueDate": ISODate("2024-09-15T00:00:00Z"),
  "assignedTo": ObjectId("..."), // 用户ID
  "createdBy": ObjectId("..."),  // 创建者ID
  "tags": ["文档", "项目"],
  "createdAt": ISODate("2024-08-01T00:00:00Z"),
  "updatedAt": ISODate("2024-08-15T00:00:00Z")
}

数据库索引优化

// MongoDB索引创建脚本
db.users.createIndex({ "email": 1 }, { unique: true });
db.users.createIndex({ "username": 1 }, { unique: true });

db.tasks.createIndex({ "assignedTo": 1, "status": 1 });
db.tasks.createIndex({ "createdBy": 1 });
db.tasks.createIndex({ "dueDate": 1 });
db.tasks.createIndex({ "createdAt": -1 });

// 文本搜索索引
db.tasks.createIndex({ 
  "title": "text", 
  "description": "text" 
});

用户认证

JWT中间件

// src/middleware/auth.js
const jwt = require('jsonwebtoken');
const User = require('../models/User');

const authMiddleware = async (req, res, next) => {
    try {
        const token = req.header('Authorization')?.replace('Bearer ', '');
        
        if (!token) {
            return res.status(401).json({ message: '访问被拒绝,请提供有效的token' });
        }

        const decoded = jwt.verify(token, process.env.JWT_SECRET || 'your-secret-key');
        const user = await User.findById(decoded.userId).select('-password');
        
        if (!user) {
            return res.status(401).json({ message: '用户不存在' });
        }

        req.user = user;
        next();
    } catch (error) {
        if (error.name === 'JsonWebTokenError') {
            return res.status(401).json({ message: '无效的token' });
        }
        if (error.name === 'TokenExpiredError') {
            return res.status(401).json({ message: 'token已过期' });
        }
        res.status(500).json({ message: '服务器错误' });
    }
};

module.exports = authMiddleware;

密码安全策略

🔐 密码加密

使用bcrypt进行密码哈希,盐值轮数设置为10

🎫 JWT配置

设置合理的过期时间,使用强密钥签名

🛡️ 输入验证

对所有用户输入进行严格的验证和清理

🚫 防护措施

实施速率限制和CORS策略

部署配置

Docker配置

# Dockerfile (后端)
FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .

EXPOSE 5000

CMD ["npm", "start"]
# docker-compose.yml
version: '3.8'

services:
  mongodb:
    image: mongo:5.0
    container_name: task-manager-db
    restart: unless-stopped
    ports:
      - "27017:27017"
    environment:
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: password
    volumes:
      - mongodb_data:/data/db

  backend:
    build: ./backend
    container_name: task-manager-api
    restart: unless-stopped
    ports:
      - "5000:5000"
    environment:
      - NODE_ENV=production
      - MONGODB_URI=mongodb://admin:password@mongodb:27017/taskmanager?authSource=admin
      - JWT_SECRET=your-super-secret-jwt-key
    depends_on:
      - mongodb

  frontend:
    build: ./frontend
    container_name: task-manager-web
    restart: unless-stopped
    ports:
      - "3000:80"
    depends_on:
      - backend

  nginx:
    image: nginx:alpine
    container_name: task-manager-proxy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./ssl:/etc/nginx/ssl
    depends_on:
      - frontend
      - backend

volumes:
  mongodb_data:

Nginx配置

# nginx.conf
events {
    worker_connections 1024;
}

http {
    upstream backend {
        server backend:5000;
    }

    upstream frontend {
        server frontend:80;
    }

    server {
        listen 80;
        server_name ccclub.club www.ccclub.club;

        # API代理
        location /api/ {
            proxy_pass http://backend;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        # 前端应用
        location / {
            proxy_pass http://frontend;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        # 静态文件缓存
        location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
            expires 1y;
            add_header Cache-Control "public, immutable";
        }
    }
}

🚀 部署检查清单

  • 环境变量:确保所有敏感信息使用环境变量
  • HTTPS配置:生产环境必须启用SSL/TLS
  • 数据库备份:设置定期数据备份策略
  • 监控日志:配置应用和错误日志监控
  • 性能优化:启用gzip压缩和静态文件缓存
  • 安全头部:配置安全相关的HTTP头部