📌 内容摘要

  • 用 Claude 开发 Web 应用原型的完整工作流:需求 → 架构 → 代码 → 调试 → 部署,每个阶段的最优 Prompt 策略。
  • 实战案例:从零构建一个”AI 写作助手”Web 应用,前端 React + 后端 FastAPI + Claude API 集成。
  • Claude 最擅长哪些开发任务、最容易在哪里出错——根据实战经验总结的效率最大化策略。
  • 文末附部署方案:Vercel(前端)+ Render(后端),免费额度内完成上线。

一、用 Claude 开发的正确心态

用 Claude 做原型开发,最大的误区是把它当成”代码自动生成机器”——给一个模糊需求,期待得到一个完整可运行的应用。这种方式成功率低,挫败感高。

更有效的心态是把 Claude 当成一个高级结对编程伙伴:你负责把握方向、做架构决策、验证输出;Claude 负责写代码、解释原理、调试问题。你们分工明确,而不是你完全甩手。

Claude 擅长的任务 需要你主导的任务
样板代码、CRUD 接口、UI 组件 产品方向和核心功能取舍
调试报错信息、解释复杂逻辑 技术栈选择(要适合你的能力)
数据库 Schema 设计、API 文档 验证代码是否真正能运行
已有代码的重构和优化建议 处理业务逻辑的边界情况

二、第一阶段:需求拆解和技术选型

在写第一行代码之前,用 Claude 把需求和技术方案想清楚。这一步花20分钟,能节省后面几小时的返工。

Prompt:需求拆解

我要做一个 Web 应用,描述如下:
[用2-3句话描述你的想法]

请帮我:
1. 用 5 个以内的用户故事描述核心功能(格式:作为[用户],我想要[功能],以便[价值])
2. 列出 MVP(最小可行产品)必须有的功能和可以之后再加的功能
3. 识别出技术上最复杂/最有风险的部分
4. 提出 3 个你觉得我可能没想清楚的问题

不要讨论技术实现,只聚焦需求层面。

Prompt:技术选型建议

基于以下需求,帮我选择技术栈:

应用描述:[需求描述]
我的技术背景:[你熟悉哪些语言/框架]
约束条件:
- 这是一个原型,需要在 1-3 天内做完
- 优先选择我熟悉的技术,其次考虑生态成熟度
- 需要能快速部署到公网

请给我推荐一个技术栈组合,说明:
1. 推荐的前端/后端/数据库选择
2. 每个选择的理由(特别是为什么比其他选项更适合这个场景)
3. 这个组合最大的风险点
4. 如果要扩展成生产应用,哪里需要重做

三、实战案例:AI 写作助手

以下用一个具体项目演示完整流程——一个”AI 写作助手”Web 应用:用户输入写作主题和要求,应用调用 Claude API 生成内容,支持流式输出和历史记录。

技术栈选择:React(前端)+ FastAPI(后端)+ SQLite(数据库)+ Claude API

项目结构

writing-assistant/
├── backend/
│   ├── main.py          # FastAPI 应用入口
│   ├── models.py        # 数据模型
│   ├── database.py      # 数据库连接
│   └── requirements.txt
└── frontend/
    ├── src/
    │   ├── App.jsx
    │   ├── components/
    │   │   ├── Editor.jsx      # 写作编辑器
    │   │   ├── Sidebar.jsx     # 历史记录
    │   │   └── StreamOutput.jsx # 流式输出
    │   └── api.js              # API 调用层
    └── package.json

四、第二阶段:后端开发

Prompt:生成后端基础代码

用 FastAPI + SQLite + anthropic SDK 实现一个写作助手后端。

功能需求:
1. POST /api/generate:接收写作主题和要求,流式返回 Claude 的生成内容
2. GET /api/history:返回历史生成记录列表
3. GET /api/history/{id}:返回单条记录详情
4. DELETE /api/history/{id}:删除记录

数据模型:
- 每条记录包含:id, title, prompt, content, created_at, tokens_used

技术要求:
- 使用 Claude Sonnet 4.6 模型
- 流式输出用 SSE(Server-Sent Events)
- 数据库操作用 SQLAlchemy
- 加 CORS 支持(允许 localhost:5173)
- 环境变量读取 ANTHROPIC_API_KEY

请生成完整可运行的代码,不要省略任何部分。

后端完整代码(main.py)

from fastapi import FastAPI, HTTPException, Depends
from fastapi.responses import StreamingResponse
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from sqlalchemy import create_engine, Column, Integer, String, Text, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
from datetime import datetime
import anthropic
import json
import os

# ── 初始化 ────────────────────────────────────────
app = FastAPI(title="AI 写作助手")
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:5173", "http://localhost:3000"],
    allow_methods=["*"],
    allow_headers=["*"],
)

claude = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])

# ── 数据库 ────────────────────────────────────────
engine = create_engine("sqlite:///./writing.db", connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(bind=engine)
Base = declarative_base()

class Article(Base):
    __tablename__ = "articles"
    id          = Column(Integer, primary_key=True, index=True)
    title       = Column(String(200))
    prompt      = Column(Text)
    content     = Column(Text)
    created_at  = Column(DateTime, default=datetime.utcnow)
    tokens_used = Column(Integer, default=0)

Base.metadata.create_all(bind=engine)

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

# ── 请求/响应模型 ──────────────────────────────────
class GenerateRequest(BaseModel):
    title:  str
    prompt: str
    style:  str = "专业"         # 写作风格
    length: str = "中等"          # 篇幅:简短/中等/详细

class ArticleResponse(BaseModel):
    id:          int
    title:       str
    prompt:      str
    content:     str
    created_at:  datetime
    tokens_used: int

    class Config:
        from_attributes = True

# ── 接口 ──────────────────────────────────────────
@app.post("/api/generate")
async def generate(req: GenerateRequest, db: Session = Depends(get_db)):
    """流式生成内容,完成后保存到数据库"""

    length_map = {"简短": "300字以内", "中等": "500-800字", "详细": "1000字以上"}
    length_desc = length_map.get(req.length, "500-800字")

    system = f"""你是一名专业写作助手,风格{req.style}。
生成的内容要求:篇幅{length_desc},结构清晰,语言流畅。
直接输出内容,不要加"好的,我来写"等前缀。"""

    full_content = []
    total_tokens = {"input": 0, "output": 0}

    def event_stream():
        with claude.messages.stream(
            model="claude-sonnet-4-6",
            max_tokens=2048,
            system=system,
            messages=[{"role": "user", "content": req.prompt}],
        ) as stream:
            for text in stream.text_stream:
                full_content.append(text)
                yield f"data: {json.dumps({'text': text}, ensure_ascii=False)}\n\n"

            final = stream.get_final_message()
            total_tokens["input"]  = final.usage.input_tokens
            total_tokens["output"] = final.usage.output_tokens

        # 流结束后保存到数据库
        article = Article(
            title       = req.title,
            prompt      = req.prompt,
            content     = "".join(full_content),
            tokens_used = total_tokens["input"] + total_tokens["output"],
        )
        db.add(article)
        db.commit()
        db.refresh(article)

        yield f"data: {json.dumps({'done': True, 'id': article.id, 'tokens': article.tokens_used})}\n\n"

    return StreamingResponse(
        event_stream(),
        media_type="text/event-stream",
        headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
    )


@app.get("/api/history", response_model=list[ArticleResponse])
def get_history(skip: int = 0, limit: int = 20, db: Session = Depends(get_db)):
    return db.query(Article).order_by(Article.created_at.desc()).offset(skip).limit(limit).all()


@app.get("/api/history/{article_id}", response_model=ArticleResponse)
def get_article(article_id: int, db: Session = Depends(get_db)):
    article = db.query(Article).filter(Article.id == article_id).first()
    if not article:
        raise HTTPException(status_code=404, detail="文章不存在")
    return article


@app.delete("/api/history/{article_id}")
def delete_article(article_id: int, db: Session = Depends(get_db)):
    article = db.query(Article).filter(Article.id == article_id).first()
    if not article:
        raise HTTPException(status_code=404, detail="文章不存在")
    db.delete(article)
    db.commit()
    return {"status": "deleted"}


if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

五、第三阶段:前端开发

Prompt:生成前端主组件

用 React + Tailwind CSS 实现写作助手的前端界面。

布局:左侧边栏(历史记录)+ 右侧主编辑区
主编辑区功能:
- 标题输入框
- 写作要求 textarea
- 风格选择(专业/创意/简洁)
- 篇幅选择(简短/中等/详细)
- 生成按钮
- 流式输出展示区(支持 markdown 渲染)

左侧边栏:
- 历史记录列表(标题 + 时间)
- 点击可加载历史内容
- 删除按钮

技术要求:
- 用 fetch + ReadableStream 处理 SSE
- 生成中显示动画光标
- 生成完成后可以复制全文
- 响应式,移动端友好

API 地址:http://localhost:8000
请生成完整代码,包含所有子组件。

前端核心代码(App.jsx)

import { useState, useEffect, useRef } from "react";

const API = "http://localhost:8000";

export default function App() {
  const [title,   setTitle]   = useState("");
  const [prompt,  setPrompt]  = useState("");
  const [style,   setStyle]   = useState("专业");
  const [length,  setLength]  = useState("中等");
  const [output,  setOutput]  = useState("");
  const [loading, setLoading] = useState(false);
  const [history, setHistory] = useState([]);
  const [copied,  setCopied]  = useState(false);
  const outputRef = useRef(null);

  // 加载历史记录
  useEffect(() => { fetchHistory(); }, []);

  async function fetchHistory() {
    const res = await fetch(`${API}/api/history`);
    setHistory(await res.json());
  }

  // 生成内容
  async function generate() {
    if (!title.trim() || !prompt.trim()) {
      alert("请填写标题和写作要求");
      return;
    }
    setLoading(true);
    setOutput("");

    const res = await fetch(`${API}/api/generate`, {
      method:  "POST",
      headers: { "Content-Type": "application/json" },
      body:    JSON.stringify({ title, prompt, style, length }),
    });

    const reader  = res.body.getReader();
    const decoder = new TextDecoder();
    let   buffer  = "";

    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      buffer += decoder.decode(value, { stream: true });
      const lines = buffer.split("\n");
      buffer = lines.pop() ?? "";

      for (const line of lines) {
        if (!line.startsWith("data: ")) continue;
        try {
          const data = JSON.parse(line.slice(6));
          if (data.text) {
            setOutput(prev => prev + data.text);
            // 自动滚动到底部
            outputRef.current?.scrollTo(0, outputRef.current.scrollHeight);
          }
          if (data.done) {
            fetchHistory();   // 刷新历史记录
          }
        } catch {}
      }
    }
    setLoading(false);
  }

  // 加载历史文章
  async function loadArticle(id) {
    const res = await fetch(`${API}/api/history/${id}`);
    const article = await res.json();
    setTitle(article.title);
    setPrompt(article.prompt);
    setOutput(article.content);
  }

  // 删除历史记录
  async function deleteArticle(id, e) {
    e.stopPropagation();
    if (!confirm("确认删除?")) return;
    await fetch(`${API}/api/history/${id}`, { method: "DELETE" });
    fetchHistory();
    setOutput("");
  }

  // 复制全文
  async function copyContent() {
    await navigator.clipboard.writeText(output);
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  }

  return (
    
{/* 左侧边栏 */} {/* 主编辑区 */}
{/* 输入区 */}
setTitle(e.target.value)} placeholder="文章标题" className="w-full text-lg font-medium border-0 outline-none placeholder-gray-300" />