图:
1、后端代码:
# blog_api.py - 纯API后端
from fastapi import FastAPI, HTTPException, Depends
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import List, Optional
from datetime import datetime
import uuid
import json
import os
from pathlib import Path
app = FastAPI(title="Blog API", description="博客系统API接口", version="1.0.0")
# 允许CORS,让HTML页面可以跨域访问
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 生产环境应该指定具体域名
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 数据模型
class PostCreate(BaseModel):
title: str
content: str
author: str
tags: List[str] = []
published: bool = True
class PostResponse(PostCreate):
id: str
created_at: str
updated_at: str
views: int = 0
class CommentCreate(BaseModel):
author: str
content: str
email: Optional[str] = None
class CommentResponse(CommentCreate):
id: str
post_id: str
created_at: str
class UserCreate(BaseModel):
username: str
email: str
password: str
class UserLogin(BaseModel):
username: str
password: str
# 数据存储
class Database:
def __init__(self, data_dir="data"):
self.data_dir = Path(data_dir)
self.data_dir.mkdir(exist_ok=True)
self.posts_file = self.data_dir / "posts.json"
self.comments_file = self.data_dir / "comments.json"
self.users_file = self.data_dir / "users.json"
self.posts = {}
self.comments = {}
self.users = {}
self.load_data()
def load_data(self):
# 加载文章
if self.posts_file.exists():
with open(self.posts_file, 'r', encoding='utf-8') as f:
self.posts = json.load(f)
# 加载评论
if self.comments_file.exists():
with open(self.comments_file, 'r', encoding='utf-8') as f:
self.comments = json.load(f)
# 加载用户
if self.users_file.exists():
with open(self.users_file, 'r', encoding='utf-8') as f:
self.users = json.load(f)
def save_posts(self):
with open(self.posts_file, 'w', encoding='utf-8') as f:
json.dump(self.posts, f, ensure_ascii=False, indent=2)
def save_comments(self):
with open(self.comments_file, 'w', encoding='utf-8') as f:
json.dump(self.comments, f, ensure_ascii=False, indent=2)
def save_users(self):
with open(self.users_file, 'w', encoding='utf-8') as f:
json.dump(self.users, f, ensure_ascii=False, indent=2)
db = Database()
# 初始化示例数据
@app.on_event("startup")
async def startup_event():
if not db.posts:
# 添加示例文章
sample_post = {
"id": "1",
"title": "欢迎来到博客系统",
"content": "这是一个基于FastAPI构建的博客系统API",
"author": "管理员",
"tags": ["欢迎", "介绍"],
"published": True,
"created_at": datetime.now().isoformat(),
"updated_at": datetime.now().isoformat(),
"views": 0
}
db.posts["1"] = sample_post
db.save_posts()
# API路由
@app.get("/")
async def root():
return {"message": "博客系统API", "status": "running", "version": "1.0.0"}
# 文章相关API
@app.get("/api/posts", response_model=List[PostResponse])
async def get_posts(skip: int = 0, limit: int = 10, tag: Optional[str] = None):
"""获取文章列表"""
posts = list(db.posts.values())
# 按标签过滤
if tag:
posts = [p for p in posts if tag in p.get("tags", [])]
# 只返回已发布的文章
posts = [p for p in posts if p.get("published", True)]
# 按时间排序(最新的在前)
posts.sort(key=lambda x: x.get("created_at", ""), reverse=True)
return posts[skip:skip + limit]
@app.get("/api/posts/{post_id}", response_model=PostResponse)
async def get_post(post_id: str):
"""获取单篇文章"""
if post_id not in db.posts:
raise HTTPException(status_code=404, detail="文章不存在")
# 增加浏览量
db.posts[post_id]["views"] = db.posts[post_id].get("views", 0) + 1
db.save_posts()
return db.posts[post_id]
@app.post("/api/posts", response_model=PostResponse)
async def create_post(post: PostCreate):
"""创建文章"""
post_id = str(uuid.uuid4())
now = datetime.now().isoformat()
new_post = {
"id": post_id,
**post.dict(),
"created_at": now,
"updated_at": now,
"views": 0
}
db.posts[post_id] = new_post
db.save_posts()
return new_post
@app.put("/api/posts/{post_id}", response_model=PostResponse)
async def update_post(post_id: str, post: PostCreate):
"""更新文章"""
if post_id not in db.posts:
raise HTTPException(status_code=404, detail="文章不存在")
updated_post = {
**db.posts[post_id],
**post.dict(),
"id": post_id,
"updated_at": datetime.now().isoformat()
}
db.posts[post_id] = updated_post
db.save_posts()
return updated_post
@app.delete("/api/posts/{post_id}")
async def delete_post(post_id: str):
"""删除文章"""
if post_id not in db.posts:
raise HTTPException(status_code=404, detail="文章不存在")
del db.posts[post_id]
# 删除相关评论
if post_id in db.comments:
del db.comments[post_id]
db.save_posts()
db.save_comments()
return {"message": "文章删除成功"}
# 评论相关API
@app.get("/api/posts/{post_id}/comments", response_model=List[CommentResponse])
async def get_comments(post_id: str):
"""获取文章评论"""
if post_id not in db.posts:
raise HTTPException(status_code=404, detail="文章不存在")
return db.comments.get(post_id, [])
@app.post("/api/posts/{post_id}/comments", response_model=CommentResponse)
async def create_comment(post_id: str, comment: CommentCreate):
"""创建评论"""
if post_id not in db.posts:
raise HTTPException(status_code=404, detail="文章不存在")
comment_id = str(uuid.uuid4())
new_comment = {
"id": comment_id,
"post_id": post_id,
**comment.dict(),
"created_at": datetime.now().isoformat()
}
if post_id not in db.comments:
db.comments[post_id] = []
db.comments[post_id].append(new_comment)
db.save_comments()
return new_comment
# 搜索API
@app.get("/api/search")
async def search_posts(q: str, limit: int = 10):
"""搜索文章"""
results = []
for post in db.posts.values():
if (q.lower() in post.get("title", "").lower() or
q.lower() in post.get("content", "").lower() or
any(q.lower() in tag.lower() for tag in post.get("tags", []))):
results.append(post)
return results[:limit]
# 统计API
@app.get("/api/stats")
async def get_stats():
"""获取统计信息"""
total_posts = len(db.posts)
total_views = sum(p.get("views", 0) for p in db.posts.values())
total_comments = sum(len(comments) for comments in db.comments.values())
return {
"total_posts": total_posts,
"total_views": total_views,
"total_comments": total_comments,
"latest_posts": list(db.posts.values())[:5]
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
2、前端代码:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NiceGUI 博客系统</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.1/font/bootstrap-icons.css">
<style>
:root {
--primary-color: #4e73df;
--secondary-color: #858796;
--success-color: #1cc88a;
--info-color: #36b9cc;
--warning-color: #f6c23e;
--danger-color: #e74a3b;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f8f9fc;
}
.navbar {
background: linear-gradient(135deg, var(--primary-color) 0%, #224abe 100%);
box-shadow: 0 0.15rem 1.75rem 0 rgba(58, 59, 69, 0.15);
}
.post-card {
transition: transform 0.3s, box-shadow 0.3s;
border: none;
box-shadow: 0 0.15rem 1.75rem 0 rgba(58, 59, 69, 0.15);
}
.post-card:hover {
transform: translateY(-5px);
box-shadow: 0 0.5rem 2rem 0 rgba(58, 59, 69, 0.2);
}
.tag-badge {
font-size: 0.8rem;
padding: 0.25rem 0.5rem;
margin-right: 0.25rem;
}
.comment-card {
border-left: 3px solid var(--primary-color);
background-color: #f8f9fa;
}
.loading {
display: none;
text-align: center;
padding: 2rem;
}
.loading.active {
display: block;
}
</style>
</head>
<body>
<!-- 导航栏 -->
<nav class="navbar navbar-expand-lg navbar-dark">
<div class="container">
<a class="navbar-brand" href="#">
<i class="bi bi-journal-text me-2"></i>
NiceGUI 博客
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link active" href="#" onclick="loadHome()">
<i class="bi bi-house-door me-1"></i>首页
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" onclick="loadPosts()">
<i class="bi bi-newspaper me-1"></i>博客
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" onclick="loadAbout()">
<i class="bi bi-info-circle me-1"></i>关于
</a>
</li>
</ul>
<div class="d-flex">
<input class="form-control me-2" type="search" placeholder="搜索文章..." id="searchInput">
<button class="btn btn-light" onclick="searchPosts()">
<i class="bi bi-search"></i>
</button>
</div>
</div>
</div>
</nav>
<!-- 主要内容区域 -->
<div class="container mt-4">
<div id="content">
<!-- 内容由JavaScript动态加载 -->
</div>
<!-- 加载动画 -->
<div id="loading" class="loading">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">加载中...</span>
</div>
<p class="mt-2">加载中...</p>
</div>
</div>
<!-- 页脚 -->
<footer class="bg-dark text-white mt-5 py-4">
<div class="container text-center">
<p>© 2024 NiceGUI 博客系统. 所有权利保留.</p>
<p>API地址: <code id="apiUrl">http://localhost:8000</code></p>
</div>
</footer>
<!-- 模态框 - 查看文章详情 -->
<div class="modal fade" id="postModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="postModalTitle">文章详情</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="postModalBody">
<!-- 文章内容 -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- 主JavaScript文件 -->
<script>
const API_BASE_URL = 'http://localhost:8000';
// 显示/隐藏加载动画
function showLoading(show) {
document.getElementById('loading').classList.toggle('active', show);
}
// 获取文章列表
async function loadPosts() {
showLoading(true);
try {
const response = await fetch(`${API_BASE_URL}/api/posts?limit=20`);
const posts = await response.json();
let html = `
<div class="row mb-4">
<div class="col">
<h2><i class="bi bi-newspaper me-2"></i>最新文章</h2>
<p class="text-muted">共 ${posts.length} 篇文章</p>
</div>
<div class="col-auto">
<button class="btn btn-primary" onclick="showCreateForm()">
<i class="bi bi-plus-circle me-1"></i>写文章
</button>
</div>
</div>
`;
if (posts.length === 0) {
html += `
<div class="alert alert-info">
<i class="bi bi-info-circle me-2"></i>
暂无文章,快来写第一篇吧!
</div>
`;
} else {
html += '<div class="row">';
posts.forEach(post => {
html += `
<div class="col-md-6 col-lg-4 mb-4">
<div class="card post-card h-100">
<div class="card-body">
<h5 class="card-title">${post.title}</h5>
<p class="card-text text-muted">
${post.content.substring(0, 100)}...
</p>
<div class="mb-3">
${post.tags.map(tag =>
`<span class="badge bg-primary tag-badge">${tag}</span>`
).join('')}
</div>
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted">
<i class="bi bi-person me-1"></i>${post.author}
<i class="bi bi-eye ms-3 me-1"></i>${post.views}
</small>
<button class="btn btn-sm btn-outline-primary"
onclick="viewPost('${post.id}')">
阅读全文
</button>
</div>
</div>
<div class="card-footer text-muted">
<small>${new Date(post.created_at).toLocaleDateString()}</small>
</div>
</div>
</div>
`;
});
html += '</div>';
}
document.getElementById('content').innerHTML = html;
} catch (error) {
console.error('加载文章失败:', error);
document.getElementById('content').innerHTML = `
<div class="alert alert-danger">
<i class="bi bi-exclamation-triangle me-2"></i>
加载文章失败,请检查API服务器是否运行
</div>
`;
} finally {
showLoading(false);
}
}
// 查看文章详情
async function viewPost(postId) {
showLoading(true);
try {
const response = await fetch(`${API_BASE_URL}/api/posts/${postId}`);
const post = await response.json();
// 获取评论
const commentsResponse = await fetch(`${API_BASE_URL}/api/posts/${postId}/comments`);
const comments = await commentsResponse.json();
let html = `
<div class="card mb-4">
<div class="card-body">
<button class="btn btn-link mb-3" onclick="loadPosts()">
<i class="bi bi-arrow-left"></i> 返回列表
</button>
<h1 class="card-title">${post.title}</h1>
<div class="mb-4">
${post.tags.map(tag =>
`<span class="badge bg-primary tag-badge">${tag}</span>`
).join('')}
</div>
<div class="d-flex align-items-center mb-4 text-muted">
<i class="bi bi-person me-1"></i>${post.author}
<i class="bi bi-calendar ms-3 me-1"></i>${new Date(post.created_at).toLocaleDateString()}
<i class="bi bi-eye ms-3 me-1"></i>${post.views}次阅读
</div>
<div class="card-text" style="font-size: 1.1rem; line-height: 1.8;">
${post.content.replace(/\n/g, '<br>')}
</div>
</div>
</div>
<!-- 评论区域 -->
<div class="card">
<div class="card-header">
<h5 class="mb-0">
<i class="bi bi-chat-left-text me-2"></i>
评论 (${comments.length})
</h5>
</div>
<div class="card-body">
${comments.length === 0 ?
'<p class="text-muted">暂无评论,快来发表第一条评论吧!</p>' :
comments.map(comment => `
<div class="card comment-card mb-3">
<div class="card-body">
<div class="d-flex justify-content-between">
<h6 class="card-subtitle mb-2">
<i class="bi bi-person-circle me-1"></i>
${comment.author}
</h6>
<small class="text-muted">
${new Date(comment.created_at).toLocaleString()}
</small>
</div>
<p class="card-text">${comment.content}</p>
</div>
</div>
`).join('')
}
<!-- 添加评论表单 -->
<div class="mt-4">
<h6>发表评论</h6>
<form id="commentForm" onsubmit="addComment(event, '${postId}')">
<div class="mb-3">
<input type="text" class="form-control" id="commentAuthor"
placeholder="你的昵称" required>
</div>
<div class="mb-3">
<textarea class="form-control" id="commentContent"
rows="3" placeholder="评论内容" required></textarea>
</div>
<button type="submit" class="btn btn-primary">
<i class="bi bi-send me-1"></i>提交评论
</button>
</form>
</div>
</div>
</div>
`;
document.getElementById('content').innerHTML = html;
} catch (error) {
console.error('加载文章详情失败:', error);
document.getElementById('content').innerHTML = `
<div class="alert alert-danger">
加载文章失败
</div>
`;
} finally {
showLoading(false);
}
}
// 添加评论
async function addComment(event, postId) {
event.preventDefault();
const author = document.getElementById('commentAuthor').value;
const content = document.getElementById('commentContent').value;
if (!author || !content) {
alert('请填写昵称和评论内容');
return;
}
try {
const response = await fetch(`${API_BASE_URL}/api/posts/${postId}/comments`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
author: author,
content: content
})
});
if (response.ok) {
alert('评论发表成功!');
viewPost(postId); // 重新加载页面
} else {
alert('发表评论失败');
}
} catch (error) {
console.error('发表评论失败:', error);
alert('发表评论失败');
}
}
// 显示创建文章表单
function showCreateForm() {
const html = `
<div class="card">
<div class="card-header">
<h5 class="mb-0">
<i class="bi bi-pencil-square me-2"></i>
写文章
</h5>
</div>
<div class="card-body">
<form id="postForm" onsubmit="createPost(event)">
<div class="mb-3">
<label class="form-label">标题</label>
<input type="text" class="form-control" id="postTitle" required>
</div>
<div class="mb-3">
<label class="form-label">作者</label>
<input type="text" class="form-control" id="postAuthor" value="匿名作者" required>
</div>
<div class="mb-3">
<label class="form-label">标签(用逗号分隔)</label>
<input type="text" class="form-control" id="postTags"
placeholder="Python, 教程, Web">
</div>
<div class="mb-3">
<label class="form-label">内容</label>
<textarea class="form-control" id="postContent" rows="10" required></textarea>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="postPublished" checked>
<label class="form-check-label">立即发布</label>
</div>
<button type="submit" class="btn btn-primary">
<i class="bi bi-check-circle me-1"></i>发布文章
</button>
<button type="button" class="btn btn-secondary ms-2" onclick="loadPosts()">
取消
</button>
</form>
</div>
</div>
`;
document.getElementById('content').innerHTML = html;
}
// 创建文章
async function createPost(event) {
event.preventDefault();
const title = document.getElementById('postTitle').value;
const author = document.getElementById('postAuthor').value;
const tags = document.getElementById('postTags').value
.split(',')
.map(tag => tag.trim())
.filter(tag => tag);
const content = document.getElementById('postContent').value;
const published = document.getElementById('postPublished').checked;
try {
const response = await fetch(`${API_BASE_URL}/api/posts`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: title,
author: author,
tags: tags,
content: content,
published: published
})
});
if (response.ok) {
alert('文章创建成功!');
loadPosts(); // 重新加载文章列表
} else {
alert('创建文章失败');
}
} catch (error) {
console.error('创建文章失败:', error);
alert('创建文章失败');
}
}
// 搜索文章
async function searchPosts() {
const query = document.getElementById('searchInput').value;
if (!query.trim()) {
loadPosts();
return;
}
showLoading(true);
try {
const response = await fetch(`${API_BASE_URL}/api/search?q=${encodeURIComponent(query)}`);
const results = await response.json();
let html = `
<div class="mb-4">
<h2>搜索结果</h2>
<p class="text-muted">搜索词: "${query}",找到 ${results.length} 条结果</p>
</div>
`;
if (results.length === 0) {
html += `
<div class="alert alert-warning">
<i class="bi bi-search me-2"></i>
没有找到相关文章
</div>
<button class="btn btn-outline-primary" onclick="loadPosts()">
返回文章列表
</button>
`;
} else {
html += '<div class="row">';
results.forEach(post => {
html += `
<div class="col-md-6 mb-4">
<div class="card post-card">
<div class="card-body">
<h5 class="card-title">${post.title}</h5>
<p class="card-text text-muted">
${post.content.substring(0, 150)}...
</p>
<button class="btn btn-sm btn-outline-primary"
onclick="viewPost('${post.id}')">
阅读全文
</button>
</div>
</div>
</div>
`;
});
html += '</div>';
}
document.getElementById('content').innerHTML = html;
} catch (error) {
console.error('搜索失败:', error);
document.getElementById('content').innerHTML = `
<div class="alert alert-danger">
搜索失败
</div>
`;
} finally {
showLoading(false);
}
}
// 加载首页
function loadHome() {
const html = `
<div class="text-center mb-5">
<h1 class="display-4 mb-3">欢迎来到 NiceGUI 博客</h1>
<p class="lead text-muted mb-4">一个现代化的博客系统,使用NiceGUI作为后端API</p>
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-body">
<h5 class="card-title">系统特性</h5>
<div class="row mt-4">
<div class="col-md-4 mb-3">
<div class="text-center">
<i class="bi bi-lightning-charge text-primary" style="font-size: 2rem;"></i>
<h6 class="mt-2">快速高效</h6>
<p class="text-muted small">基于FastAPI的高性能API</p>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="text-center">
<i class="bi bi-layout-text-window text-success" style="font-size: 2rem;"></i>
<h6 class="mt-2">响应式设计</h6>
<p class="text-muted small">适配各种设备屏幕</p>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="text-center">
<i class="bi bi-code-slash text-info" style="font-size: 2rem;"></i>
<h6 class="mt-2">RESTful API</h6>
<p class="text-muted small">标准化的API接口</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">如何开始?</h5>
<ol class="mt-3">
<li>启动API服务器:<code>python blog_api.py</code></li>
<li>用浏览器打开这个HTML文件</li>
<li>开始浏览和创建文章</li>
</ol>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">API文档</h5>
<ul class="mt-3">
<li><code>GET /api/posts</code> - 获取文章列表</li>
<li><code>POST /api/posts</code> - 创建文章</li>
<li><code>GET /api/posts/{id}</code> - 获取文章详情</li>
<li><code>POST /api/posts/{id}/comments</code> - 添加评论</li>
</ul>
</div>
</div>
</div>
</div>
`;
document.getElementById('content').innerHTML = html;
}
// 加载关于页面
function loadAbout() {
const html = `
<div class="card">
<div class="card-body">
<h2 class="card-title">关于 NiceGUI 博客系统</h2>
<div class="mt-4">
<h5>技术架构</h5>
<ul>
<li><strong>后端</strong>: FastAPI + NiceGUI (Python)</li>
<li><strong>前端</strong>: HTML5 + CSS3 + JavaScript</li>
<li><strong>UI框架</strong>: Bootstrap 5</li>
<li><strong>数据存储</strong>: JSON文件 (可扩展为数据库)</li>
</ul>
</div>
<div class="mt-4">
<h5>功能特性</h5>
<ul>
<li>文章管理 (CRUD操作)</li>
<li>评论系统</li>
<li>标签分类</li>
<li>文章搜索</li>
<li>响应式设计</li>
<li>RESTful API</li>
</ul>
</div>
<div class="mt-4">
<h5>部署说明</h5>
<p>这是一个前后端分离的架构:</p>
<ol>
<li>API服务器运行在 <code>http://localhost:8000</code></li>
<li>前端HTML文件可以放在任何Web服务器上</li>
<li>支持跨域请求 (CORS enabled)</li>
</ol>
</div>
<div class="mt-4">
<h5>开源协议</h5>
<p>本项目采用 MIT License 开源协议。</p>
</div>
</div>
</div>
`;
document.getElementById('content').innerHTML = html;
}
// 页面加载时显示首页
document.addEventListener('DOMContentLoaded', loadHome);
</script>
</body>
</html>
3. 使用说明
第一步:启动API服务器
bash
复制下载
# 安装依赖
pip install fastapi uvicorn pydantic
# 运行API服务器
python blog_api.py
第二步:使用HTML前端
方法1:直接浏览器打开
- 将HTML文件保存为
index.html - 直接用浏览器打开
- 访问API地址:
http://localhost:8000
方法2:使用简单HTTP服务器
bash
复制下载
# Python内置HTTP服务器
python -m http.server 8080
# 然后访问 http://localhost:8080
4. 项目结构
text
复制下载
blog_project/
├── blog_api.py # FastAPI后端
├── index.html # HTML前端
├── data/ # 数据存储目录
│ ├── posts.json # 文章数据
│ ├── comments.json # 评论数据
│ └── users.json # 用户数据
└── requirements.txt # Python依赖
5. API接口文档
| 方法 | 端点 | 描述 |
|---|---|---|
| GET | /api/posts | 获取文章列表 |
| GET | /api/posts/{id} | 获取文章详情 |
| POST | /api/posts | 创建文章 |
| PUT | /api/posts/{id} | 更新文章 |
| DELETE | /api/posts/{id} | 删除文章 |
| GET | /api/posts/{id}/comments | 获取文章评论 |
| POST | /api/posts/{id}/comments | 添加评论 |
| GET | /api/search?q=关键词 | 搜索文章 |
| GET | /api/stats | 获取统计信息 |
这样你就有了一个完整的前后端分离的博客系统!前端是纯HTML/JS,后端是FastAPI API。