初始版本-前端

This commit is contained in:
z66
2025-12-26 17:29:22 +08:00
parent b495bc1dca
commit e19873850c
77 changed files with 6977 additions and 294 deletions
-2
View File
@@ -8,8 +8,6 @@ from .app.models import (
ChatMessage, Upload
)
# 为了向后兼容,保留 get_session 别名
get_session = get_db
__all__ = [
"engine",
+77
View File
@@ -0,0 +1,77 @@
"""
认证相关 API 端点
"""
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from sqlmodel import Session, select
from datetime import timedelta
from app.core.config import settings
from app.core.security import verify_password, create_access_token, get_password_hash
from app.db.session import get_db
from app.models.user import User
from app.schemas.user import Token, User as UserSchema, UserCreate
router = APIRouter()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl=f"{settings.API_V1_STR}/auth/login")
@router.post("/register", response_model=UserSchema, status_code=status.HTTP_201_CREATED)
def register(user_in: UserCreate, db: Session = Depends(get_db)):
"""用户注册"""
# 检查用户名是否已存在
statement = select(User).where(User.username == user_in.username)
existing_user = db.exec(statement).first()
if existing_user:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="用户名已存在"
)
# 检查邮箱是否已存在
if user_in.email:
statement = select(User).where(User.email == user_in.email)
existing_email = db.exec(statement).first()
if existing_email:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="邮箱已被注册"
)
# 创建新用户
hashed_password = get_password_hash(user_in.password)
db_user = User(
username=user_in.username,
email=user_in.email,
hashed_password=hashed_password
)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
@router.post("/login", response_model=Token)
def login(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)):
"""用户登录"""
# 查找用户
statement = select(User).where(User.username == form_data.username)
user = db.exec(statement).first()
if not user or not verify_password(form_data.password, user.hashed_password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="用户名或密码错误",
headers={"WWW-Authenticate": "Bearer"},
)
# 创建访问令牌
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
+44
View File
@@ -0,0 +1,44 @@
"""
应用配置
"""
from pydantic_settings import BaseSettings
from typing import Optional
class Settings(BaseSettings):
"""应用配置类"""
# 项目信息
PROJECT_NAME: str = "个人博客网站"
VERSION: str = "0.1.0"
API_V1_STR: str = "/api/v1"
# 数据库配置
DATABASE_URL: str = "sqlite:///./blogweb.db"
# 安全配置
SECRET_KEY: str = "your-secret-key-change-in-production" # 生产环境请修改
ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 7 # 7天
# CORS 配置
BACKEND_CORS_ORIGINS: list = [
"http://localhost:3000",
"http://localhost:5173",
"http://localhost:8080",
]
# 文件上传配置
UPLOAD_DIR: str = "./uploads"
MAX_UPLOAD_SIZE: int = 10 * 1024 * 1024 # 10MB
# 第三方 API 配置
OPENWEATHER_API_KEY: Optional[str] = None # OpenWeatherMap API Key
class Config:
env_file = ".env"
case_sensitive = True
settings = Settings()
+14
View File
@@ -0,0 +1,14 @@
"""
数据库基础配置
"""
from sqlmodel import SQLModel
# 导入所有模型以确保表被注册到 metadata
from app.models import (
User, Todo, Post, Transaction, Media, Tag, MediaTag,
ChatMessage, Upload
)
# SQLModel 的 Base 类
Base = SQLModel
+22
View File
@@ -0,0 +1,22 @@
"""
聊天消息模型
"""
from datetime import datetime
from typing import Optional
from sqlmodel import SQLModel, Field, Relationship
from .user import User
class ChatMessage(SQLModel, table=True):
"""聊天消息表"""
__tablename__ = "chat_messages"
id: Optional[int] = Field(default=None, primary_key=True)
content: str
sent_at: datetime = Field(default_factory=datetime.now)
user_id: int = Field(foreign_key="users.id", index=True)
room: str = Field(max_length=50, default="main", index=True)
# 关系
user: Optional[User] = Relationship()
-144
View File
@@ -1,144 +0,0 @@
"""
数据库模型定义
使用 SQLModel 定义所有表结构
"""
from datetime import datetime, date
from typing import Optional
from sqlmodel import SQLModel, Field, Relationship, Column
from sqlalchemy import Numeric
from decimal import Decimal
# ==================== 用户表 ====================
class User(SQLModel, table=True):
"""用户表"""
__tablename__ = "users"
id: Optional[int] = Field(default=None, primary_key=True)
username: str = Field(max_length=50, unique=True, index=True)
email: Optional[str] = Field(default=None, max_length=100, unique=True, index=True)
hashed_password: str
created_at: datetime = Field(default_factory=datetime.now)
# ==================== 待办事项表 ====================
class Todo(SQLModel, table=True):
"""待办事项表"""
__tablename__ = "todos"
id: Optional[int] = Field(default=None, primary_key=True)
title: str = Field(max_length=200)
done: bool = Field(default=False)
created_at: datetime = Field(default_factory=datetime.now)
user_id: int = Field(foreign_key="users.id", index=True)
# 关系(可选,用于ORM查询)
user: Optional[User] = Relationship()
# ==================== 博客文章表 ====================
class Post(SQLModel, table=True):
"""博客文章表"""
__tablename__ = "posts"
id: Optional[int] = Field(default=None, primary_key=True)
title: str = Field(max_length=200)
slug: str = Field(max_length=200, index=True)
content: str # 支持 Markdown
created_at: datetime = Field(default_factory=datetime.now)
updated_at: Optional[datetime] = Field(default=None)
user_id: int = Field(foreign_key="users.id", index=True)
# 关系
user: Optional[User] = Relationship()
# ==================== 记账记录表 ====================
class Transaction(SQLModel, table=True):
"""记账记录表"""
__tablename__ = "transactions"
id: Optional[int] = Field(default=None, primary_key=True)
amount: Decimal = Field(sa_column=Column(Numeric(10, 2))) # 正数为收入,负数为支出
category: str = Field(max_length=50)
description: Optional[str] = Field(default=None, max_length=200)
date: date
user_id: int = Field(foreign_key="users.id", index=True)
# 关系
user: Optional[User] = Relationship()
# ==================== 媒体-标签关联表(多对多)====================
class MediaTag(SQLModel, table=True):
"""媒体-标签关联表"""
__tablename__ = "media_tags"
media_id: int = Field(foreign_key="media.id", primary_key=True)
tag_id: int = Field(foreign_key="tags.id", primary_key=True)
# ==================== 书影音收藏表 ====================
class Media(SQLModel, table=True):
"""书影音收藏表"""
__tablename__ = "media"
id: Optional[int] = Field(default=None, primary_key=True)
title: str = Field(max_length=200)
media_type: str = Field(max_length=20) # book / movie / music
rating: Optional[float] = Field(default=None, ge=0.0, le=5.0)
comment: Optional[str] = Field(default=None)
external_id: Optional[str] = Field(default=None, max_length=100) # ISBN、IMDb ID等
cover_url: Optional[str] = Field(default=None, max_length=300)
created_at: datetime = Field(default_factory=datetime.now)
user_id: int = Field(foreign_key="users.id", index=True)
# 关系
user: Optional[User] = Relationship()
tags: list["Tag"] = Relationship(back_populates="media", link_model=MediaTag)
# ==================== 媒体标签表 ====================
class Tag(SQLModel, table=True):
"""媒体标签表"""
__tablename__ = "tags"
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(max_length=50, unique=True, index=True)
# 关系
media: list[Media] = Relationship(back_populates="tags", link_model=MediaTag)
# ==================== 聊天消息表 ====================
class ChatMessage(SQLModel, table=True):
"""聊天消息表"""
__tablename__ = "chat_messages"
id: Optional[int] = Field(default=None, primary_key=True)
content: str
sent_at: datetime = Field(default_factory=datetime.now)
user_id: int = Field(foreign_key="users.id", index=True)
room: str = Field(max_length=50, default="main", index=True)
# 关系
user: Optional[User] = Relationship()
# ==================== 文件上传记录表 ====================
class Upload(SQLModel, table=True):
"""文件上传记录表"""
__tablename__ = "uploads"
id: Optional[int] = Field(default=None, primary_key=True)
filename: str = Field(max_length=200)
stored_path: str = Field(max_length=300)
file_size: int # 字节数
mime_type: str = Field(max_length=100)
uploaded_at: datetime = Field(default_factory=datetime.now)
expires_at: Optional[datetime] = Field(default=None)
user_id: int = Field(foreign_key="users.id", index=True)
# 关系
user: Optional[User] = Relationship()