FastAPI + GraphQL + SQLAlchemy 实现博客系统

news2025/7/17 5:24:00

本文将详细介绍如何使用 FastAPI、GraphQL(Strawberry)和 SQLAlchemy 实现一个带有认证功能的博客系统。
在这里插入图片描述

技术栈

  • FastAPI:高性能的 Python Web 框架
  • Strawberry:Python GraphQL 库
  • SQLAlchemy:Python ORM 框架
  • JWT:用于用户认证

系统架构

1. 数据模型(Models)

使用 SQLAlchemy 定义数据模型,以用户模型为例:

class UserModel(Base):
    """SQLAlchemy model for the users table"""
    __tablename__ = "users"
    
    id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
    username: Mapped[str] = mapped_column(String(50), unique=True, index=True)
    email: Mapped[str] = mapped_column(String(100), unique=True, index=True)
    hashed_password: Mapped[str] = mapped_column(String(200))
    nickname: Mapped[str] = mapped_column(String(50), nullable=True)
    is_active: Mapped[bool] = mapped_column(Boolean, default=True)
    is_admin: Mapped[bool] = mapped_column(Boolean, default=False)
    created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
    
    # 关联关系
    posts = relationship("PostModel", back_populates="author")
    
    def verify_password(self, password: str) -> bool:
        """验证密码"""
        return pwd_context.verify(password, self.hashed_password)

2. GraphQL Schema

使用 Strawberry 定义 GraphQL schema,包括查询和变更:

from models.types import (
    UserRead,      # 用户信息读取类型
    UserCreate,    # 用户创建输入类型
    LoginInput,    # 登录输入类型
    LoginResponse, # 登录响应类型
    RegisterResponse, # 注册响应类型
    Token,        # Token类型
    PostRead,     # 文章读取类型
    PostCreate,   # 文章创建输入类型
    PageInput,    # 分页输入类型
    Page,         # 分页响应类型
    PageInfo      # 分页信息类型
)

@strawberry.type
class Query:
    @strawberry.field
    def hello(self) -> str:
        """测试接口"""
        return "Hello World"

    @strawberry.field
    def me(self, info) -> Optional[UserRead]:
        """
        获取当前用户信息
        - 需要认证
        - 返回 None 表示未登录
        - 返回 UserRead 类型表示当前登录用户信息
        """
        if not info.context.get("user"):
            return None
        return info.context["user"].to_read()

    @strawberry.field
    def my_posts(self, info, page_input: Optional[PageInput] = None) -> Page[PostRead]:
        """
        获取当前用户的文章列表
        - 需要认证
        - 支持分页查询
        - 返回带分页信息的文章列表
        
        参数:
        - page_input: 可选的分页参数
          - page: 页码(默认1)
          - size: 每页大小(默认10)
        
        返回:
        - items: 文章列表
        - page_info: 分页信息
          - total: 总记录数
          - page: 当前页码
          - size: 每页大小
          - has_next: 是否有下一页
          - has_prev: 是否有上一页
        """
        # 认证检查
        if not info.context.get("user"):
            raise ValueError("Not authenticated")
        
        # 数据库操作
        db = SessionLocal()
        try:
            # 设置分页参数
            page = page_input.page if page_input else 1
            size = page_input.size if page_input else 10
            
            # 查询总数
            total = db.query(func.count(PostModel.id)).filter(
                PostModel.author_id == info.context["user"].id
            ).scalar()
            
            # 查询分页数据
            posts = (
                db.query(PostModel)
                .options(joinedload(PostModel.author))  # 预加载作者信息
                .filter(PostModel.author_id == info.context["user"].id)
                .order_by(PostModel.created_at.desc())  # 按创建时间倒序
                .offset((page - 1) * size)
                .limit(size)
                .all()
            )
            
            # 构建分页信息
            page_info = PageInfo(
                total=total,
                page=page,
                size=size,
                has_next=total > page * size,
                has_prev=page > 1
            )
            
            return Page(
                items=[post.to_read() for post in posts],
                page_info=page_info
            )
        finally:
            db.close()

    @strawberry.field
    def user_posts(self, username: str, page_input: Optional[PageInput] = None) -> Page[PostRead]:
        """
        获取指定用户的文章列表
        - 公开接口,无需认证
        - 支持分页查询
        - 返回带分页信息的文章列表
        
        参数:
        - username: 用户名
        - page_input: 可选的分页参数
        """
        # ... 实现类似 my_posts

@strawberry.type
class Mutation:
    @strawberry.mutation
    def login(self, login_data: LoginInput) -> LoginResponse:
        """
        用户登录
        - 公开接口,无需认证
        - 验证用户名密码
        - 生成访问令牌
        
        参数:
        - login_data:
          - username: 用户名
          - password: 密码
        
        返回:
        - token: 访问令牌
        - user: 用户信息
        """
        db = SessionLocal()
        try:
            # 查找用户
            user = db.query(UserModel).filter(UserModel.username == login_data.username).first()
            # 验证密码
            if not user or not user.verify_password(login_data.password):
                raise ValueError("Incorrect username or password")
            
            # 生成访问令牌
            access_token = create_access_token(data={"sub": str(user.id)})
            token = Token(access_token=access_token)
            
            return LoginResponse(token=token, user=user.to_read())
        finally:
            db.close()

    @strawberry.mutation
    def register(self, user_data: UserCreate) -> RegisterResponse:
        """
        用户注册
        - 公开接口,无需认证
        - 检查用户名和邮箱是否已存在
        - 创建新用户
        - 生成访问令牌
        
        参数:
        - user_data:
          - username: 用户名
          - password: 密码
          - email: 邮箱
        
        返回:
        - token: 访问令牌
        - user: 用户信息
        """
        # ... 实现代码

    @strawberry.mutation
    def create_post(self, post_data: PostCreate, info) -> PostRead:
        """
        创建文章
        - 需要认证
        - 创建新文章
        - 设置当前用户为作者
        
        参数:
        - post_data:
          - title: 标题
          - content: 内容
        
        返回:
        - 创建的文章信息
        """
        # ... 实现代码

schema = strawberry.Schema(query=Query, mutation=Mutation)

认证实现

1. JWT Token 生成

def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
    """创建访问令牌"""
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

2. 认证中间件

在 FastAPI 应用中实现认证中间件,用于解析和验证 token:

async def get_context(request: Request):
    """GraphQL 上下文处理器,用于认证"""
    auth_header = request.headers.get("Authorization")
    context = {"user": None}
    
    if auth_header and auth_header.startswith("Bearer "):
        token = auth_header.split(" ")[1]
        token_data = verify_token(token)
        if token_data:
            db = SessionLocal()
            try:
                user = db.query(UserModel).filter(UserModel.id == int(token_data["sub"])).first()
                if user:
                    context["user"] = user
            finally:
                db.close()
    
    return context

3. 认证流程

  1. 用户登录:
mutation Login {
  login(loginData: {
    username: "admin",
    password: "111111"
  }) {
    token {
      accessToken
    }
    user {
      id
      username
      email
    }
  }
}
  1. 服务器验证用户名密码,生成 JWT token

  2. 后续请求中使用 token:

    • 在请求头中添加:Authorization: Bearer your_token
    • 中间件解析 token 并验证
    • 将用户信息添加到 GraphQL context
  3. 在需要认证的操作中检查用户:

if not info.context.get("user"):
    raise ValueError("Not authenticated")

API 权限设计

1. 公开接口(无需认证)

  • hello: 测试接口
  • login: 用户登录
  • register: 用户注册
  • userPosts: 获取指定用户的文章列表

2. 私有接口(需要认证)

  • me: 获取当前用户信息
  • myPosts: 获取当前用户的文章列表
  • createPost: 创建新文章

使用示例

1. 登录获取 Token

mutation Login {
  login(loginData: {
    username: "admin",
    password: "111111"
  }) {
    token {
      accessToken
    }
  }
}

2. 使用 Token 访问私有接口

在 GraphQL Playground 中设置 HTTP Headers:

{
  "Authorization": "Bearer your_token"
}

然后可以查询私有数据:

query MyPosts {
  myPosts(pageInput: {
    page: 1,
    size: 10
  }) {
    items {
      id
      title
      content
    }
  }
}

安全考虑

  1. 密码安全

    • 使用 bcrypt 进行密码哈希
    • 从不存储明文密码
  2. Token 安全

    • 使用 JWT 标准
    • 设置合理的过期时间
    • 使用安全的签名算法
  3. 数据访问控制

    • 严格的权限检查
    • 用户只能访问自己的数据

总结

本项目展示了如何使用现代化的技术栈构建一个安全的 GraphQL API:

  1. 使用 FastAPI 提供高性能的 Web 服务
  2. 使用 Strawberry 实现 GraphQL API
  3. 使用 SQLAlchemy 进行数据库操作
  4. 实现了完整的认证机制
  5. 遵循了最佳安全实践

当然图片上传一类的,还要跟以前一样写,但现在我们只写了一个/api接口就完成了项目所有接口。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2286884.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

昆仑万维Java开发面试题及参考答案

进程和线程的区别是什么? 进程和线程都是操作系统中非常重要的概念,它们在多个方面存在显著的区别。 从定义上看,进程是操作系统进行资源分配和调度的基本单位。每个进程都有自己独立的内存空间,包括代码段、数据段、堆栈段等。例如,当你在电脑上同时打开浏览器和音乐播放…

DeepSeek R1-Zero vs. R1:强化学习推理的技术突破与应用前景

📌 引言:AI 推理的新时代 近年来,大语言模型(LLM) 的规模化扩展成为 AI 研究的主流方向。然而,LLM 的扩展是否真的能推动 通用人工智能(AGI) 的实现?DeepSeek 推出的 R1…

Linux《基础指令》

在之前的Linux《Linux简介与环境的搭建》当中我们已经初步了解了Linux的由来和如何搭建Linux环境,那么接下来在本篇当中我们就要来学习Linux的基础指令。在此我们的学习是包括两个部分,即指令和关于Linux的基础知识;因此本篇指令和基础知识的…

DeepSeek-R1 模型及GRPO算法学习

总结DeepSeek-R1 模型算法,并对其中的GRPO算法做一些学习补充。 DeepSeek-R1 论文总结 提出了通过强化学习提升大语言模型推理能力的方法,开发出 DeepSeek-R1-Zero 和 DeepSeek-R1 模型,在多个推理任务上表现出色,并开源模型推动…

爬虫基础(二)Web网页的基本原理

一、网页的组成 网页由三部分构成:HTML、JavaScript、CSS。 (1)HTML HTML 相当于网页的骨架,它通过使用标签来定义网页内容的结构。 举个例子: 它把图片标签为img、把视频标签为video,然后组合到一个界面…

Kotlin开发(六):Kotlin 数据类,密封类与枚举类

引言 想象一下,你是个 Kotlin 开发者,敲着代码忽然发现业务代码中需要一堆冗长的 POJO 类来传递数据。烦得很?别急,Kotlin 贴心的 数据类 能帮你自动生成 equals、hashCode,直接省时省力!再想想需要多种状…

openssl 生成证书 windows导入证书

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的,可以在任何平台上使用。 源码指引:github源…

AJAX笔记入门篇

黑马程序员视频地址: 黑马程序员前端AJAX入门到实战全套教程https://www.bilibili.com/video/BV1MN411y7pw?vd_source0a2d366696f87e241adc64419bf12cab&spm_id_from333.788.videopod.episodes&p2https://www.bilibili.com/video/BV1MN411y7pw?vd_source…

数据分析系列--④RapidMiner进行关联分析(案例)

一、核心概念 1.1项集(Itemset) 1.2规则(Rule) 1.3支持度(Support) 1.3.1 支持度的定义 1.3.2 支持度的意义 1.3.3 支持度的应用 1.3.4 支持度的示例 1.3.5 支持度的调整 1.3.6 支持度与其他指标的…

危机13小时:追踪一场GitHub投毒事件

事件概要 自北京时间 2024.12.4 晚间6点起, GitHub 上不断出现“幽灵仓库”,仓库中没有任何代码,只有诱导性的病毒文件。当天,他们成为了 GitHub 上 star 增速最快的仓库。超过 180 个虚假僵尸账户正在传播病毒,等待不…

LLMs之WebRAG:STORM/Co-STORM的简介、安装和使用方法、案例应用之详细攻略

LLMs之WebRAG:STORM/Co-STORM的简介、安装和使用方法、案例应用之详细攻略 目录 STORM系统简介 1、Co-STORM 2、更新新闻 STORM系统安装和使用方法 1、安装 pip安装 直接克隆GitHub仓库 2、模型和数据集 两个数据集 FreshWiki数据集 WildSeek数据集 支持…

buu-rip-好久不见26

简单的栈溢出,找到后面函数和输入的个数即可

2025一区新风口:小波变换+KAN!速占!

今天给大家分享一个能让审稿人眼前一亮,好发一区的idea:小波变换KAN! 一方面:KAN刚中稿ICLR25,正是风口上,与小波变换的结合还处于起步阶段,正是红利期,创新空间广阔。 另一方面&a…

无公网IP 外网访问 本地部署夫人 hello-algo

hello-algo 是一个为帮助编程爱好者系统地学习数据结构和算法的开源项目。这款项目通过多种创新的方式,为学习者提供了一个直观、互动的学习平台。 本文将详细的介绍如何利用 Docker 在本地安装部署 hello-algo,并结合路由侠内网穿透实现外网访问本地部署…

系统思考—蝴蝶效应

“个体行为的微小差异,可能在系统中引发巨大且不可预测的结果。” — 诺贝尔经济学得主托马斯谢林 我们常说,小变动带来大影响,这种现象,在复杂系统理论中被称为“蝴蝶效应”:即使极小的变化,也能在动态系…

钉钉群机器人设置——python版本

钉钉群机器人设置——python版本 应用场景钉钉界面操作程序开发效果展示 应用场景 由于工作需要,很多项目执行程序后出现报错信息无法第一时间收到,因此实时预警对于监控程序还是有必要。(仅个人观点) 参考文档及博客&#xff1a…

【Rust自学】15.0. 智能指针(序):什么是智能指针及Rust智能指针的特性

喜欢的话别忘了点赞、收藏加关注哦,对接下来的教程有兴趣的可以关注专栏。谢谢喵!(・ω・) 15.0.1 指针的基本概念 指针是一个变量在内存中包含的是一个地址,指向另一个数据。 Rust 中最常见的指针是引用&#xff0c…

Spring AI 在微服务中的应用:支持分布式 AI 推理

1. 引言 在现代企业中,微服务架构 已成为开发复杂系统的主流方式,而 AI 模型推理 也越来越多地被集成到业务流程中。如何在分布式微服务架构下高效地集成 Spring AI,使多个服务可以协同完成 AI 任务,并支持分布式 AI 推理&#x…

QT串口通信,实现单个温湿度传感器数据的采集

1、硬件设备 RS485中继器(一进二出),usb转485模块、电源等等 => 累计115元左右。 2、核心代码 #include "MainWindow.h" #include "ui_MainWindow.h"MainWindow::

DeepSeek R1:中国AI黑马的崛起与挑战

文章目录 技术突破:从零开始的推理能力进化DeepSeek R1-Zero:纯RL训练的“自我觉醒”DeepSeek R1:冷启动与多阶段训练的平衡之道 实验验证:推理能力的全方位跃升基准测试:超越顶尖闭源模型蒸馏技术:小模型的…