FastAPI & HTML
\(FastAPI\)
仅用于记录学习进度……
\(HelloWorld\)
创建\(Helloworld.py\),
输入uvicorn HelloWorld:app --reload 启动应用
访问 http://127.0.0.1:8000 (由默认配置和网络基础规则共同决定)
可以看到 {"Hello": "World"} 的输出
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
FastAPI():创建 FastAPI 应用实例。
@app.get("/"):使用 @app.get 装饰器创建一个处理根路径的路由。
def read_root():路由处理函数,返回一个包含 {"Hello": "World"} 的字典。
from typing import Union
from fastapi import FastAPI
# 创建 FastAPI,用于定义和管理应用的各个组件,包括路由。
app = FastAPI()
# 定义根路径 / 的路由操作,通过 HTTP GET 请求访问根路径时,将执行 read_root 函数
@app.get("/")
def read_root():
return {"Hello": "World"}
# 定义带路径参数和查询参数的路由操作,使用了 @app.get("/items/{item_id}") 装饰器,表示当用户通过 HTTP GET 请求访问 /items/{item_id} 路径时,将执行 read_item 函数
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
@app.get("/items/{item_id}"):定义了一个路由路径,其中 {item_id} 是路径参数,对应于函数参数 item_id。
def read_item(item_id: int, q: str = None):路由处理函数接受一个整数类型的路径参数 item_id 和一个可选的字符串类型查询参数 q。
使用 Uvicorn 启动应用:
uvicorn main:app --reload
访问 http://127.0.0.1:8000 查看根路径的响应:
访问 http://127.0.0.1:8000/items/42?q=runoob 查看带路径参数和查询参数的响应:
\(FastAPI\) 交互式 \(API\) 文档
(网不好会打不开。。。)
默认情况下,可以通过访问 http://127.0.0.1:8000/docs 来打开 \(Swagger\) \(UI\) 风格的文档
或者通过 http://127.0.0.1:8000/redoc 来打开 \(ReDoc\) 风格的文档
交互式文档实时更新、自动验证、便于测试
# 直接在前面的代码上修改,不需要重启服务器,会自动重载(因为在 uvicorn 命令添加了 --reload 选项)
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
is_offer: Union[bool, None] = None
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
@app.put("/items/{item_id}")
def update_item(item_id: int, item: Item):
return {"item_name": item.name, "item_id": item_id}
\(FastAPT\)基本路由
路由都映射到应用程序中的一个函数
形如:
xxx = FastAPI() # 创建 FastAPI 应用实例
@xxx.get(……) # 装饰器创建一个路由
def fun():
return {……}
\(FastAPI\) 请求和响应
# 接受两个查询参数 skip 和 limit
@app.get("/items/")
def read_item(skip: int = 0, limit: int = 10):
return {"skip": skip, "limit": limit}
# http://127.0.0.1:8000/items/?skip=1&limit=5
# 把参数设置在路径上
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
return {"item_id": item_id, "q": q}
# http://127.0.0.1:8000/items/5/?q=runoob
# 使用 Pydantic 模型 Item 定义了一个请求体
class Item(BaseModel):
name: str
description: str = None
price: float
tax: float = None
@app.post("/items/")
def create_item(item: Item):
return item
使用 Header 和 Cookie 类型注解获取请求头和 Cookie 数据
from fastapi import Header, Cookie
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/")
def read_item(user_agent: str = Header(None), session_token: str = Cookie(None)):
return {"User-Agent": user_agent, "Session-Token": session_token}
使用 RedirectResponse 实现重定向,将客户端重定向到 /items/ 路由
from fastapi import Header, Cookie
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
app = FastAPI()
@app.get("/items/")
def read_item(user_agent: str = Header(None), session_token: str = Cookie(None)):
return {"User-Agent": user_agent, "Session-Token": session_token}
@app.get("/redirect")
def redirect():
return RedirectResponse(url="/items/")
# 访问 http://127.0.0.1:8000/redirect/ 会自动跳转到 http://127.0.0.1:8000/items/ 页面
使用 HTTPException 抛出异常,返回自定义的状态码和详细信息
from fastapi import Header, Cookie
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
app = FastAPI()
@app.get("/items/")
def read_item(user_agent: str = Header(None), session_token: str = Cookie(None)):
return {"User-Agent": user_agent, "Session-Token": session_token}
@app.get("/redirect")
def redirect():
return RedirectResponse(url="/items/")
# 在 item_id 为 42 会返回 404 状态码
使用 JSONResponse 自定义响应头
from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/items/{item_id}")
def read_item(item_id: int):
content = {"item_id": item_id}
headers = {"X-Custom-Header": "custom-header-value"}
return JSONResponse(content=content, headers=headers)
\(FastAPI Pydantic\) 模型
定义 \(Pydantic\) 模型
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str = None
price: float
tax: float = None
# `name` 和 `price` 是必需的字段,而 `description` 和 `tax` 是可选的字段,其默认值为 `None`
使用 \(Pydantic\) 模型
- 请求体验证
将Pydantic模型用作请求体(\(Request Body\)),以自动验证和解析客户端发送的数据
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str = None
price: float
tax: float = None
@app.post("/items/")
def create_item(item: Item):
return item
可以使用requests发送数据(pip install requests)
import requests
url = "http://127.0.0.1:8000/items/"
data = {
"name": ……,
"description": ……,
"price": ……,
"tax": ……
}
response = requests.post(url, json=data)
print(response.json())
查询参数验证
Pydantic 模型还可以用于验证查询参数、路径参数等。
from fastapi import FastAPI, Query
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str = None
price: float
tax: float = None
@app.get("/items/")
def read_item(item: Item, q: str = Query(..., max_length=10)):
return {"item": item, "q": q}
\(FastAPI\) 路径操作依赖项
- 预处理
在路由执行前预处理输入数据的功能
from fastapi import Depends, FastAPI
app = FastAPI()
# 依赖项函数
def common_parameters(q: str = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
return commons
# 用到 await,就必须加 async,现在加不加 async,运行起来完全没区别
- 后处理(没看懂\(qwq\))
在路由执行后执行一些逻辑,不过例子好像有点问题。。。
from fastapi import Depends, FastAPI, HTTPException
app = FastAPI()
# 依赖项函数
def common_parameters(q: str = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
# 路由操作函数
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
return commons
# 后处理函数
async def after_request():
# 这里可以执行一些后处理逻辑,比如记录日志
pass
# 后处理依赖项
@app.get("/items/", response_model=dict)
async def read_items_after(request: dict = Depends(after_request)):
return {"message": "Items returned successfully"}
- 多个依赖项的组合
from fastapi import Depends, FastAPI, HTTPException
app = FastAPI()
# 依赖项函数1
def common_parameters(q: str = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
# 依赖项函数2
def verify_token(token: str = Depends(common_parameters)):
if token is None:
raise HTTPException(status_code=400, detail="Token required")
return token
# 路由操作函数
@app.get("/items/")
async def read_items(token: dict = Depends(verify_token)):
return token
- 异步依赖项
依赖项函数和后处理函数可以是异步的(看不懂orz)
from fastapi import Depends, FastAPI, HTTPException
from typing import Optional
import asyncio
app = FastAPI()
# 异步依赖项函数
async def get_token():
# 模拟异步操作
await asyncio.sleep(2)
return "fake-token"
# 异步路由操作函数
@app.get("/items/")
async def read_items(token: Optional[str] = Depends(get_token)):
return {"token": token}
\(FastAPI\) 表单数据
- 声明表单数据模型
需要pip install python-multipart
from fastapi import FastAPI, Form
app = FastAPI()
@app.post("/login/")
async def login(username: str = Form(), password: str = Form()):
return {"username": username}
- 在路由中接收表单数据
from fastapi import FastAPI, Form
app = FastAPI()
# 路由操作函数
@app.post("/items/")
async def create_item(
name: str = Form(...),
description: str = Form(None),
price: float = Form(..., gt=0),
):
return {"name": name, "description": description, "price": price}
- 表单数据的验证和文档生成
使用Pydantic模型和Form类型,表单数据的验证和文档生成都是自动的。
\(FastAPI\) 将根据模型中的字段信息生成交互式 \(API\) 文档,并根据验证规则进行数据验证。
- 处理文件上传
如果表单包含文件上传,可以使用UploadFile类型处理。
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
# 路由操作函数
@app.post("/files/")
async def create_file(file: UploadFile = File(...)):
return {"filename": file.filename}
可以写以下代码进行测试
import requests
# 接口地址
url = "http://127.0.0.1:8000/files/"
# 要上传的文件(改成你电脑上的文件)
files = {
"file": open("t.html", "r") # file 必须和接口参数名一样
}
# 发送请求
response = requests.post(url, files=files)
print(response.json())
\(ASGI\) 与异步编程
\(WSGI vs ASGI\) 对比:
# WSGI 应用(同步)- 传统 Flask 风格
def wsgi_app(environ, start_response):
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return [b'Hello World']
# ASGI 应用(异步)- FastAPI 风格
async def asgi_app(scope, receive, send):
await send({
'type': 'http.response.start',
'status': 200,
'headers': [(b'content-type', b'text/plain')],
})
await send({
'type': 'http.response.body',
'body': b'Hello World',
})
同步 \(vs\) 异步执行:
import asyncio
import time
import httpx
# 同步方式 - 阻塞执行
def sync_fetch_data():
start_time = time.time()
# 模拟三个网络请求
time.sleep(1) # 第一个请求
time.sleep(1) # 第二个请求
time.sleep(1) # 第三个请求
print(f"同步执行耗时: {time.time() - start_time:.2f}秒") # 约3秒
# 异步方式 - 并发执行
async def async_fetch_data():
start_time = time.time()
# 三个请求并发执行
await asyncio.gather(
asyncio.sleep(1), # 第一个请求
asyncio.sleep(1), # 第二个请求
asyncio.sleep(1), # 第三个请求
)
print(f"异步执行耗时: {time.time() - start_time:.2f}秒") # 约1秒
# 运行示例
sync_fetch_data() # 输出: 同步执行耗时: 3.00秒
asyncio.run(async_fetch_data()) # 输出: 异步执行耗时: 1.00秒
\(FastAPI\) 中的异步路径操作:
from fastapi import FastAPI
import asyncio
app = FastAPI()
# 同步路径操作
@app.get("/sync")
def sync_endpoint():
# 同步操作会阻塞整个应用
time.sleep(2)
return {"message": "同步响应"}
# 异步路径操作(推荐)
@app.get("/async")
async def async_endpoint():
# 异步操作不会阻塞其他请求
await asyncio.sleep(2)
return {"message": "异步响应"}
# 异步数据库操作示例
@app.get("/users/{user_id}")
async def get_user(user_id: int):
# 异步数据库查询
user = await database.fetch_one(
"SELECT * FROM users WHERE id = :user_id",
{"user_id": user_id}
)
return user
适合异步的场景:
- 数据库操作
- 网络请求(\(API\) 调用)
- 文件 \(I/O\) 操作
- 长时间等待的操作
不适合异步的场景:
- \(CPU\) 密集型计算
- 简单的数据处理
- 没有 \(I/O\) 等待的操作
\(HTTP\) 方法与状态码
\(HTTP\) 方法的语义
from fastapi import FastAPI, status, HTTPException
from fastapi.responses import JSONResponse
app = FastAPI()
# GET - 安全且幂等
@app.get("/users/{user_id}")
async def get_user(user_id: int):
"""获取资源,不修改服务器状态"""
user = find_user(user_id)
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="User not found"
)
return user
# POST - 不安全且非幂等
@app.post("/users", status_code=status.HTTP_201_CREATED)
async def create_user(user: UserCreate):
"""创建新资源"""
if user_exists(user.email):
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="User already exists"
)
new_user = create_new_user(user)
return new_user
# PUT - 不安全但幂等
@app.put("/users/{user_id}")
async def replace_user(user_id: int, user: UserUpdate):
"""完整替换资源"""
if not user_exists(user_id):
# PUT 可以创建资源
return create_user_with_id(user_id, user)
return replace_existing_user(user_id, user)
# PATCH - 不安全且通常非幂等
@app.patch("/users/{user_id}")
async def update_user(user_id: int, user: UserPatch):
"""部分更新资源"""
existing_user = find_user(user_id)
if not existing_user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="User not found"
)
return patch_user_fields(existing_user, user)
# DELETE - 不安全但幂等
@app.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_user(user_id: int):
"""删除资源"""
if not user_exists(user_id):
# 幂等性:删除不存在的资源仍返回成功
return JSONResponse(status_code=status.HTTP_204_NO_CONTENT)
delete_existing_user(user_id)
return JSONResponse(status_code=status.HTTP_204_NO_CONTENT)
\(HTTP\) 状态码的使用
from fastapi import status
# 2xx 成功响应
@app.post("/users", status_code=status.HTTP_201_CREATED) # 201 Created
@app.get("/users") # 200 OK(默认)
@app.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT) # 204 No Content
# 4xx 客户端错误
@app.get("/users/{user_id}")
async def get_user(user_id: int):
if user_id < 1:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, # 400 Bad Request
detail="User ID must be positive"
)
user = find_user(user_id)
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, # 404 Not Found
detail="User not found"
)
return user
# 业务逻辑错误
@app.post("/users/{user_id}/follow")
async def follow_user(user_id: int, current_user_id: int):
if user_id == current_user_id:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, # 422 Unprocessable Entity
detail="Cannot follow yourself"
)
if is_already_following(current_user_id, user_id):
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, # 409 Conflict
detail="Already following this user"
)
# 5xx 服务器错误(通常由异常处理器处理)
@app.exception_handler(Exception)
async def general_exception_handler(request, exc):
return JSONResponse(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, # 500 Internal Server Error
content={"detail": "Internal server error"}
)
常见状态码:
# 成功状态码
200 OK # 请求成功
201 Created # 资源创建成功
204 No Content # 成功但无内容返回
206 Partial Content # 部分内容(分页、断点续传)
# 重定向
301 Moved Permanently # 永久重定向
302 Found # 临时重定向
304 Not Modified # 资源未修改(缓存)
# 客户端错误
400 Bad Request # 请求格式错误
401 Unauthorized # 未认证
403 Forbidden # 已认证但无权限
404 Not Found # 资源不存在
405 Method Not Allowed # HTTP 方法不允许
409 Conflict # 资源冲突
422 Unprocessable Entity # 数据验证失败
429 Too Many Requests # 请求过于频繁
# 服务器错误
500 Internal Server Error # 服务器内部错误
502 Bad Gateway # 网关错误
503 Service Unavailable # 服务不可用
\(SON\) 数据格式
\(JSON\) 基础与最佳实践
from datetime import datetime
from decimal import Decimal
from typing import Optional, List
from pydantic import BaseModel, Field
import json
# JSON 数据格式规范
class UserResponse(BaseModel):
id: int
username: str
email: str
full_name: Optional[str] = None
is_active: bool = True
created_at: datetime
updated_at: Optional[datetime] = None
# JSON 序列化配置
class Config:
# 允许使用字段别名
allow_population_by_field_name = True
# JSON 编码器
json_encoders = {
datetime: lambda v: v.isoformat(),
Decimal: lambda v: float(v)
}
# 响应格式标准化
class APIResponse(BaseModel):
"""标准 API 响应格式"""
success: bool = True
message: str = "操作成功"
data: Optional[dict] = None
errors: Optional[List[str]] = None
timestamp: datetime = Field(default_factory=datetime.now)
# 分页响应格式
class PaginatedResponse(BaseModel):
items: List[dict]
total: int
page: int
size: int
pages: int
@app.get("/users", response_model=PaginatedResponse)
async def get_users(page: int = 1, size: int = 10):
"""返回分页的用户列表"""
users = get_users_paginated(page, size)
total = count_users()
return PaginatedResponse(
items=users,
total=total,
page=page,
size=size,
pages=(total + size - 1) // size
)
\(JSON Schema\) 与数据验证
from pydantic import BaseModel, validator, Field
from typing import Optional, List
from enum import Enum
class UserRole(str, Enum):
ADMIN = "admin"
USER = "user"
MODERATOR = "moderator"
class UserCreate(BaseModel):
username: str = Field(..., min_length=3, max_length=50, description="用户名")
email: str = Field(..., regex=r'^[\w\.-]+@[\w\.-]+\.\w+$', description="邮箱地址")
password: str = Field(..., min_length=8, description="密码")
full_name: Optional[str] = Field(None, max_length=100, description="全名")
role: UserRole = Field(UserRole.USER, description="用户角色")
age: Optional[int] = Field(None, ge=0, le=150, description="年龄")
@validator('username')
def username_must_be_alphanumeric(cls, v):
if not v.isalnum():
raise ValueError('用户名只能包含字母和数字')
return v
@validator('password')
def password_strength(cls, v):
if not any(c.isupper() for c in v):
raise ValueError('密码必须包含至少一个大写字母')
if not any(c.islower() for c in v):
raise ValueError('密码必须包含至少一个小写字母')
if not any(c.isdigit() for c in v):
raise ValueError('密码必须包含至少一个数字')
return v
class Config:
# 生成 JSON Schema 示例
schema_extra = {
"example": {
"username": "johndoe",
"email": "john@example.com",
"password": "SecurePass123",
"full_name": "John Doe",
"role": "user",
"age": 30
}
}
# 使用自定义验证器
@app.post("/users", response_model=UserResponse)
async def create_user(user: UserCreate):
"""创建新用户,包含数据验证"""
# Pydantic 自动验证输入数据
return await create_new_user(user)
\(FastAPI\) 特有
自动文档生成
from fastapi import FastAPI, Query, Path, Body
from fastapi.openapi.utils import get_openapi
app = FastAPI(
title="我的 API",
description="这是一个示例 API,展示 FastAPI 的功能",
version="1.0.0",
terms_of_service="http://example.com/terms/",
contact={
"name": "开发者",
"url": "http://example.com/contact/",
"email": "developer@example.com",
},
license_info={
"name": "MIT",
"url": "https://opensource.org/licenses/MIT",
},
)
@app.get(
"/users/{user_id}",
summary="获取用户信息",
description="根据用户 ID 获取用户的详细信息",
response_description="用户信息对象",
tags=["用户管理"]
)
async def get_user(
user_id: int = Path(..., title="用户ID", description="要获取的用户ID", ge=1),
include_posts: bool = Query(False, title="包含文章", description="是否包含用户的文章列表")
):
"""
获取用户信息:
- **user_id**: 用户的唯一标识符
- **include_posts**: 是否在响应中包含用户的文章列表
返回用户的基本信息,如果 include_posts 为 True,还会包含文章列表。
"""
user = find_user(user_id)
if include_posts:
user.posts = get_user_posts(user_id)
return user
# 自定义 OpenAPI schema
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
openapi_schema = get_openapi(
title="自定义 API 文档",
version="2.5.0",
description="这是自定义的 OpenAPI schema",
routes=app.routes,
)
# 添加自定义信息
openapi_schema["info"]["x-logo"] = {
"url": "https://example.com/logo.png"
}
app.openapi_schema = openapi_schema
return app.openapi_schema
app.openapi = custom_openapi
依赖注入系统预览
from fastapi import Depends
# 简单依赖
def get_current_user_id() -> int:
# 从 token 中解析用户 ID
return 123
def get_database_session():
# 创建数据库会话
db = DatabaseSession()
try:
yield db
finally:
db.close()
# 使用依赖
@app.get("/profile")
async def get_profile(
user_id: int = Depends(get_current_user_id),
db = Depends(get_database_session)
):
return get_user_profile(db, user_id)
# 依赖链
def get_current_user(
user_id: int = Depends(get_current_user_id),
db = Depends(get_database_session)
):
return db.query(User).filter(User.id == user_id).first()
@app.get("/dashboard")
async def get_dashboard(
current_user: User = Depends(get_current_user) # 依赖于其他依赖
):
return generate_dashboard(current_user)
示例
from fastapi import FastAPI, HTTPException, status, Depends, Query
from pydantic import BaseModel, Field
from typing import List, Optional
from datetime import datetime
import asyncio
# 综合示例:博客 API
app = FastAPI(
title="博客 API",
description="展示 FastAPI 核心概念的博客系统",
version="1.0.0"
)
# 数据模型
class PostBase(BaseModel):
title: str = Field(..., min_length=1, max_length=200)
content: str = Field(..., min_length=1)
published: bool = Field(True)
class PostCreate(PostBase):
pass
class PostResponse(PostBase):
id: int
author_id: int
created_at: datetime
updated_at: Optional[datetime] = None
class Config:
orm_mode = True
# 模拟数据库
posts_db = []
next_id = 1
# 依赖:获取当前用户(简化版)
async def get_current_user() -> int:
# 实际应用中会从 JWT token 解析
return 1
# 异步路径操作
@app.get("/posts", response_model=List[PostResponse], tags=["文章"])
async def list_posts(
skip: int = Query(0, ge=0, description="跳过的文章数"),
limit: int = Query(10, ge=1, le=100, description="返回的文章数"),
published_only: bool = Query(True, description="只返回已发布的文章")
):
"""
获取文章列表
支持分页和筛选功能
"""
# 模拟异步数据库查询
await asyncio.sleep(0.1)
filtered_posts = posts_db
if published_only:
filtered_posts = [p for p in posts_db if p["published"]]
return filtered_posts[skip:skip + limit]
@app.post("/posts", response_model=PostResponse, status_code=status.HTTP_201_CREATED, tags=["文章"])
async def create_post(
post: PostCreate,
current_user_id: int = Depends(get_current_user)
):
"""
创建新文章
需要用户认证
"""
global next_id
# 模拟异步数据库操作
await asyncio.sleep(0.1)
new_post = {
"id": next_id,
"title": post.title,
"content": post.content,
"published": post.published,
"author_id": current_user_id,
"created_at": datetime.now(),
"updated_at": None
}
posts_db.append(new_post)
next_id += 1
return new_post
@app.get("/posts/{post_id}", response_model=PostResponse, tags=["文章"])
async def get_post(post_id: int):
"""
获取特定文章
根据文章 ID 返回文章详情
"""
# 模拟异步数据库查询
await asyncio.sleep(0.1)
post = next((p for p in posts_db if p["id"] == post_id), None)
if not post:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Post with id {post_id} not found"
)
return post
# 健康检查端点
@app.get("/health", tags=["系统"])
async def health_check():
"""系统健康检查"""
return {
"status": "healthy",
"timestamp": datetime.now(),
"posts_count": len(posts_db)
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
\(HTML\)
\(HTML\) 是用来描述网页的一种标记语言(感觉有点像\(markdown\)或\(latex\))。
<!DOCTYPE html> 声明为 \(HTML5\) 文档
<html> 元素是 \(HTML\) 页面的根元素
<head> 元素包含了文档的元(\(meta\))数据,如 <meta charset="utf-8"> 定义网页编码格式为 utf-8。
<title> 元素描述了文档的标题
<body> 元素包含了可见的页面内容
<h1> 元素定义一个大标题
<p> 元素定义一个段落
例如
<h1>这是一个标题</h1>
<h2>这是一个标题</h2>
<h3>这是一个标题</h3>
<p>这是一个段落。</p>
<p>这是另外一个段落。</p>
<a href="https://www.xxxx.com">这是一个链接</a>
<img src="/images.png" width="258" height="39" />
感觉自行查表即可 \(\rightarrow\) ,
\(JavaScript\)
写在 <script> 标签中,可以在 \(web\) 浏览器中执行
显示
- 弹出弹窗
window.alert(5+6)
window.alert("5+6")
- 操作 HTML 元素
用id属性来标识 \(HTML\) 元素,并innerHTML来获取或插入元素内容
<p id="demo">段落内容</p>
<script>
document.getElementById("demo").innerHTML = "段落已修改。";
</script>
如果在文档已完成加载后执行 document.write,整个 \(HTML\) 页面将被覆盖,以下两个代码的效果不同
<button onclick="Function()">点我</button>
<script>
function Function() {
document.write(Date());
}
</script>
<p id="demo">这是一个段落</p>
<button type="button" onclick="displayDate()">显示日期</button>
<script>
function displayDate(){
document.getElementById("demo").innerHTML=Date();
}
</script>
- 写入控制台
在浏览器中使用 $ F12 $ 来启用调试模式,可以在控制台看见输出
<script>
a = 5;
b = 6;
c = a + b;
console.log(c);
console.log(5);
console.log("c");
</script>
语法
- 字面量
固定值称为字面量
-
数字( $ Number $ )字面量: 可以是整数或者是小数,或者是科学计数( $ e $ )
-
字符串( $ String $ )字面量: 可以使用单引号或双引号
可以在字符串中使用引号,只要不匹配包围字符串的引号即可
可以用内置属性 length 来计算字符串的长度
反斜杠是一个转义字符,将特殊字符转换为字符串字符
-
表达式字面量: 用于计算
-
数组( $ Array $ )字面量: 定义一个数组,如
[40, 100, 1, 5, 25, 10]
可以用以下方式创建数组
var arr=new Array();
cars[0]="num 0";
cars[1]="num 1";
cars[2]="num 2";
var cars = new Array('num 1','num 2','num 3');
var cars = ['num 1','num 2','num 3'];
- 对象( $ Object $ )字面量: 定义一个对象,如
var person={firstname:"John", lastname:"Doe", id:5566};,声明可横跨多行
两种寻址方式name=person.lastname;、name=person["lastname"];
对象的方法定义了一个函数,并作为对象的属性存储
//创建
methodName : function() {
// 代码
}
//访问
objectName.methodName()
- 函数( $ Function $ )字面量: 定义一个函数,如
function myFunction(a, b) { return a * b;}
- 变量
使用关键字var、let和const来定义变量, 使用等号来为变量赋值,和其他任何编程语言一样, $ JavaScript $ 保留了一些关键字为自己所用
-
var: $ ES5 $ 引入的变量声明方式,具有函数作用域。 -
let: $ ES6 $ 引入的变量声明方式,具有块级作用域。 -
const: $ ES6 $ 引入的常量声明方式,具有块级作用域,且值不可变。
重新声明 $ JavaScript $ 变量,该变量的值不会丢失(也不会报错)
- 数据类型
-
值类型(基本类型):字符串( $ String $ )、数字( $ Number $ )、布尔( $ Boolean $ )、空( $ Null $ )、未定义( $ Undefined $ )、 $ Symbol $ 。
-
引用数据类型(对象类型):对象( $ Object $ )、数组( $ Array $ )、函数( $ Function $ ),还有两个特殊的对象:正则( $ RegExp $ )和日期( $ Date $ )
$ JavaScript $ 拥有动态类型,相同的变量可用作不同的类型,即以下代码合法
var x;
// x 为 undefinedvar x = 5;
// 现在 x 为数字
var x = "John"; // 现在 x 为字符串
变量的数据类型可以使用 typeof 操作符来查看,
typeof "John" // 返回 string
typeof 3.14 // 返回 number
typeof false // 返回 boolean
typeof {name:'John', age:34} // 返回 object
typeof [1,2,3,4] // 返回 object
// 正确检测数组的方法
Array.isArray([1,2,3]); // true
[1,2,3] instanceof Array; // true
Undefined 这个值表示变量不含有值。
可以通过将变量的值设置为 null 来清空变量。
声明新变量时,可以使用关键词 new 来声明其类型
JavaScript 变量均为对象,当声明一个变量时,就创建了一个新的对象
- 其他
- 分号用于分隔 $ JavaScript $ 语句
- 浏览器按照编写顺序依次执行每条语句
- 用花括号组合代码块
- 语句标识符
- 忽略多余的空格
- 在 文本字符串 中使用反斜杠对代码行进行换行
- \(html\)事件
if-elseswitch-casefor/whilebreak/continue等和其他语言类似- ……
\(markdown\)实时预览服务代码
code.py
# main.py
from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse, Response
import markdown
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
@app.get("/favicon.ico", include_in_schema=False)
async def favicon():
return Response(status_code=204)
@app.post("/api/convert")
async def convert(request: Request):
data = await request.json()
md_text = data.get("markdown", "")
html = markdown.markdown(
md_text,
extensions=["fenced_code", "codehilite", "tables", "sane_lists"]
)
return {"html": html}
@app.get("/", response_class=HTMLResponse)
async def index():
with open("static/index.html", "r", encoding="utf-8") as f:
return f.read()
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
code.html
# static/index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8"/>
<title>Markdown 实时预览</title>
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/highlight.js/11.7.0/styles/github.min.css"/>
<script src="https://cdn.bootcdn.net/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
<script>
window.MathJax = {
tex: {
inlineMath: [['$', '$'], ['\\(', '\\)']], // 支持 $公式$ 和 \(公式\)
displayMath: [['$$', '$$'], ['\\[', '\\]']], // 支持 $$公式$$ 和 \[公式\]
processEscapes: true // 处理转义字符
},
svg: {
fontCache: 'global'
},
startup: {
typeset: false // 禁用自动渲染,手动控制
}
};
</script>
<script src="https://cdn.bootcdn.net/ajax/libs/mathjax/3.2.2/es5/tex-mml-chtml.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { display: flex; height: 100vh; font-family: system-ui; }
#editor, #preview { flex: 1; height: 100%; padding: 20px; overflow-y: auto; }
#editor { border-right: 1px solid #eee; font-size: 16px; outline: none; font-family: monospace; }
#preview { background: #fafafa; }
.MathJax { font-size: 1.1em !important; }
</style>
</head>
<body>
<textarea id="editor" placeholder="输入 Markdown...
示例公式:
行内公式:$E=mc^2$
块级公式:$$\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}$$"></textarea>
<div id="preview"></div>
<script>
const editor = document.getElementById('editor');
const preview = document.getElementById('preview');
// 防抖
function debounce(fn, delay = 200) {
let t;
return (...args) => { clearTimeout(t); t = setTimeout(() => fn(...args), delay); };
}
const update = debounce(async () => {
// 保存当前编辑器滚动位置
const editorScrollTop = editor.scrollTop;
const res = await fetch('/api/convert', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ markdown: editor.value })
});
const data = await res.json();
preview.innerHTML = data.html;
// 1. 代码高亮
document.querySelectorAll('pre code').forEach(b => hljs.highlightElement(b));
// 2. 手动触发 MathJax 渲染公式
if (window.MathJax) {
MathJax.typesetClear(); // 清空之前的渲染
MathJax.typeset([preview]); // 只渲染预览区域的公式
}
// 3. 恢复滚动同步(输入后保持预览区滚动位置与编辑器一致)
syncScroll(editor, preview, editorScrollTop);
});
// 滚动同步函数(增加边界判断 + 仅主动滚动时同步)
let isScrolling = false;
function syncScroll(fromEl, toEl, fixedScrollTop = null) {
// 防止重复触发
if (isScrolling) return;
isScrolling = true;
try {
// 计算滚动比例(处理边界值,避免除以 0)
const fromHeight = fromEl.scrollHeight - fromEl.clientHeight;
const toHeight = toEl.scrollHeight - toEl.clientHeight;
// 如果内容高度小于可视高度,无需滚动
if (fromHeight <= 0 || toHeight <= 0) {
toEl.scrollTop = 0;
return;
}
// 使用固定值或实时计算滚动位置
const scrollTop = fixedScrollTop !== null ? fixedScrollTop : fromEl.scrollTop;
const scrollRatio = scrollTop / fromHeight;
toEl.scrollTop = scrollRatio * toHeight;
} finally {
// 释放锁
setTimeout(() => { isScrolling = false; }, 50);
}
}
// 绑定事件:仅在主动滚动时同步
editor.addEventListener('scroll', () => syncScroll(editor, preview));
preview.addEventListener('scroll', () => syncScroll(preview, editor));
// 输入事件:更新内容 + 同步滚动(保持预览区跟随编辑器)
editor.addEventListener('input', update);
// 初始化触发一次渲染
update();
</script>
</body>
</html>

浙公网安备 33010602011771号