FastAPI + GraphQL + SQLAlchemy 实现博客系统

本文将详细介绍如何使用 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.fielddef hello(self) -> str:"""测试接口"""return "Hello World"@strawberry.fielddef me(self, info) -> Optional[UserRead]:"""获取当前用户信息- 需要认证- 返回 None 表示未登录- 返回 UserRead 类型表示当前登录用户信息"""if not info.context.get("user"):return Nonereturn info.context["user"].to_read()@strawberry.fielddef 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 1size = 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.fielddef user_posts(self, username: str, page_input: Optional[PageInput] = None) -> Page[PostRead]:"""获取指定用户的文章列表- 公开接口,无需认证- 支持分页查询- 返回带分页信息的文章列表参数:- username: 用户名- page_input: 可选的分页参数"""# ... 实现类似 my_posts@strawberry.type
class Mutation:@strawberry.mutationdef 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.mutationdef register(self, user_data: UserCreate) -> RegisterResponse:"""用户注册- 公开接口,无需认证- 检查用户名和邮箱是否已存在- 创建新用户- 生成访问令牌参数:- user_data:- username: 用户名- password: 密码- email: 邮箱返回:- token: 访问令牌- user: 用户信息"""# ... 实现代码@strawberry.mutationdef 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_deltaelse: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"] = userfinally:db.close()return context

3. 认证流程

  1. 用户登录:
mutation Login {login(loginData: {username: "admin",password: "111111"}) {token {accessToken}user {idusernameemail}}
}
  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 {idtitlecontent}}
}

安全考虑

  1. 密码安全

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

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

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

总结

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

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

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

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

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

相关文章

微服务入门(go)

微服务入门(go) 和单体服务对比:里面的服务仅仅用于某个特定的业务 一、领域驱动设计(DDD) 基本概念 领域和子域 领域:有范围的界限(边界) 子域:划分的小范围 核心域…

深入解析 Linux 内核内存管理核心:mm/memory.c

在 Linux 内核的众多组件中,内存管理模块是系统性能和稳定性的关键。mm/memory.c 文件作为内存管理的核心实现,承载着页面故障处理、页面表管理、内存区域映射与取消映射等重要功能。本文将深入探讨 mm/memory.c 的设计思想、关键机制以及其在内核中的作用,帮助读者更好地理…

安卓通过网络获取位置的方法

一 方法介绍 1. 基本权限设置 首先需要在 AndroidManifest.xml 中添加必要权限&#xff1a; xml <uses-permission android:name"android.permission.INTERNET" /> <uses-permission android:name"android.permission.ACCESS_NETWORK_STATE" /&g…

【B站保姆级视频教程:Jetson配置YOLOv11环境(二)SSH连接的三种方式】

B站同步视频教程&#xff1a;https://www.bilibili.com/video/BV1m5wUeyEQD/ 在Jetson设备上配置YOLOv11环境时&#xff0c;SSH连接是实现远程高效开发与管理的关键一环。不同的网络环境和硬件配置可能会影响SSH连接的方式&#xff0c;本文将结合相关视频内容&#xff0c;详细…

视频拼接,拼接时长版本

目录 视频较长&#xff0c;分辨率较大&#xff0c;这个效果很好&#xff0c;不耗用内存 ffmpeg imageio&#xff0c;适合视频较短 视频较长&#xff0c;分辨率较大&#xff0c;这个效果很好&#xff0c;不耗用内存 ffmpeg import subprocess import glob import os from nats…

Vue.js 什么是 Composition API?

Vue.js 什么是 Composition API&#xff1f; 今天我们来聊聊 Vue 3 引入的一个重要特性&#xff1a;组合式 API&#xff08;Composition API&#xff09;。如果你曾在开发复杂的 Vue 组件时感到代码难以维护&#xff0c;那么组合式 API 可能正是你需要的工具。 什么是组合式 …

Selenium配合Cookies实现网页免登录

文章目录 前言1 方案一&#xff1a;使用Chrome用户数据目录2 方案二&#xff1a;手动获取并保存Cookies&#xff0c;后续使用保存的Cookies3 注意事项 前言 在进行使用Selenium进行爬虫、网页自动化操作时&#xff0c;登录往往是一个必须解决的问题&#xff0c;但是Selenium每次…

计算机毕业设计Python+知识图谱大模型AI医疗问答系统 健康膳食推荐系统 食谱推荐系统 医疗大数据 机器学习 深度学习 人工智能 爬虫 大数据毕业设计

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

关于el-table翻页后序号列递增的组件封装

需求说明&#xff1a; 项目中经常会用到的一个场景&#xff0c;表格第一列显示序号&#xff08;1、2、3...&#xff09;&#xff0c;但是在翻页后要递增显示序号&#xff0c;例如10、11、12&#xff08;假设一页显示10条数据&#xff09;&#xff0c;针对这种情况&#xff0c;封…

Elasticsearch的索引生命周期管理

目录 说明零、参考一、ILM的基本概念二、ILM的实践步骤Elasticsearch ILM策略中的“最小年龄”是如何计算的&#xff1f;如何监控和调整Elasticsearch ILM策略的性能&#xff1f; 1. **监控性能**使用/_cat/thread_pool API基本请求格式请求特定线程池的信息响应内容 2. **调整…

AI大模型开发原理篇-3:词向量和词嵌入

简介 词向量是用于表示单词意义的向量&#xff0c; 并且还可以被认为是单词的特征向量或表示。 将单词映射到实向量的技术称为词嵌入。在实际应用中&#xff0c;词向量和词嵌入这两个重要的NLP术语通常可以互换使用。它们都表示将词汇表中的单词映射到固定大小的连续向量空间中…

[内网安全] 内网渗透 - 学习手册

这是一篇专栏的目录文档&#xff0c;方便读者系统性的学习&#xff0c;笔者后续会持续更新文档内容。 如果没有特殊情况的话&#xff0c;大概是一天两篇的速度。&#xff08;实验多或者节假日&#xff0c;可能会放缓&#xff09; 笔者也是一边学习一边记录笔记&#xff0c;如果…

【学术会议征稿-第二届生成式人工智能与信息安全学术会议(GAIIS 2025)】人工智能与信息安全的魅力

重要信息 时间&#xff1a;2025年2月21日-23日 地点&#xff1a;中国杭州 官网&#xff1a;http://www.ic-gaiis.org 简介 2025年第二届生成式人工智能与信息安全将于 2025年2月21日-23日在中国杭州举行。主要围绕“生成式人工智能与信息安全”的最新研究展开&#xff0c;…

Vscode的AI插件 —— Cline

简介 vscode的一款AI辅助吃插件&#xff0c;主要用来辅助创建和编辑文件&#xff0c;探索大型项目&#xff0c;使用浏览器并执行终端命令&#xff08;需要多个tokens&#xff09;&#xff0c;可以使用模型上下文协议&#xff08;MCP&#xff09;来创建新工具并扩展自己(比较慢…

2024 CVPR Highlight Learning-Feedback

图像增强 Towards Robust Event-guided Low-Light Image Enhancement: A Large-Scale Real-World Event-Image Dataset and Novel Approach 解决的主要问题是低光照条件下的图像增强 通过多尺度整体融合分支提取事件和图像的结构和纹理信息&#xff0c;并引入信噪比&#xff0…

小白一命速通JS中的windowglobal对象

笔者注意到JS中的window对象与global对象经常被混淆&#xff0c;尽管它们在相当一部分使用情况下可以等同&#xff0c;但是本质上仍然存在很多不同&#xff0c;下面是对于两者的详细拆解 1. window 对象 定义&#xff1a;window 对象表示 浏览器环境中的全局上下文。作用域&am…

机器学习2 (笔记)(朴素贝叶斯,集成学习,KNN和matlab运用)

朴素贝叶斯模型 贝叶斯定理&#xff1a; 常见类型 算法流程 优缺点 集成学习算法 基本原理 常见方法 KNN&#xff08;聚类模型&#xff09; 算法性质&#xff1a; 核心原理&#xff1a; 算法流程 优缺点 matlab中的运用 朴素贝叶斯模型 朴素贝叶斯模型是基于贝叶斯…

HTB:Active[RE-WriteUP]

目录 连接至HTB服务器并启动靶机 信息收集 使用rustscan对靶机TCP端口进行开放扫描 将靶机TCP开放端口号提取并保存 使用nmap对靶机TCP开放端口进行脚本、服务扫描 使用nmap对靶机TCP开放端口进行漏洞、系统扫描 使用nmap对靶机常用UDP端口进行开放扫描 使用nmap对靶机…

Git图形化工具【lazygit】

简要介绍一下偶然发现的Git图形化工具——「lazygit」 概述 Lazygit 是一个用 Go 语言编写的 Git 命令行界面&#xff08;TUI&#xff09;工具&#xff0c;它让 Git 操作变得更加直观和高效。 Github地址&#xff1a;https://github.com/jesseduffield/lazygit 主要特点 主要…

58.界面参数传递给Command C#例子 WPF例子

界面参数的传递&#xff0c;界面参数是如何从前台传送到后台的。 param 参数是从界面传递到命令的。这个过程通常涉及以下几个步骤&#xff1a; 数据绑定&#xff1a;界面元素&#xff08;如按钮&#xff09;的 Command 属性绑定到视图模型中的 RelayCommand 实例。同时&#x…