FastAPI+Nuxt单域名部署实践:无需子域名的前后端分离解决方案

FastAPI+Nuxt单域名部署实践:无需子域名的前后端分离解决方案

注:此博客写于2024年5月23日。FastAPI已经到0.111.0 版本了。

背景历史

上一个接手网站的人不管了:Wordpress —重写–> Vue

发现Vue做SEO优化很麻烦:Vue —重构–> Nuxt

发现每次改商品数据还要重新上传服务器:Nuxt —增强–> Nuxt + githubActions自动部署

发现还是得做后端:Nuxt + githubActions自动部署 —加后端–> Nuxt + githubActions + FastAPI

回顾纯前端

对于构建一个网站来说,最简单的网站肯定是纯静态网站。

只要将HTML、CSS、JavaScript、图片等资源文件放在某一个服务器的文件夹里,在宝塔里配置好域名,Nginx配置好,就可以通过域名访问到网站。

如果使用了Vue、React、Nuxt、Next等框架,就用 yarn generate pnpm build 等等的命令生成出来的dist或者output文件夹,将这些文件夹里的内容部署到服务器对应域名的文件夹里就可以了。

但这个不能接后端,这只是纯前端。

回顾后端+前端 子域名反向代理法

如果接入后端,以FastAPI举例子,我们可以用FastAPI提供的API接口,然后用Nuxt渲染出前端页面。

以前个人做法是整两个域名,一个是子域名

example.com
api.example.com

然后用SSH连接服务器,单独开一个screen,部署后端上去,单独开一个后端的接口,比如说8000。

然后再在宝塔里开一个api.example.com的站点,设置反向代理,把8000端口的代理到api子域名上。

然后Nuxt前端里就可以用fetch或者axios 请求 api.example.com 接口,获取数据,渲染出页面。

问题

本文提供一个使用FastAPI+Nuxt的方案,可以使用一个域名就把后端和前端都部署到一起。

这个Nuxt应该可以换成React、Vue、Next,都行,只要保证是可以静态打包的就可以。

所以重点不在前端了,重点在FastAPI。

fastAPI很简单,只用 app.mount一句话,就能让某一个路由绑定一个前端文件夹了。

然后我们规定 /api 开头的url全部都走我们自定义的路由函数,不要去访问前端文件夹。

但是但是!!我们如果直接用 / 作为URL绑定那个前端文件夹,经过测试发现我们自定义的api就全被覆盖了。

比如 GET: https://example.com/api/product/all

他会绑定到前端静态文件里面去了,会认为有一个叫api的页面,等等等的。而不是一个返回json的后端接口。

那不把前端绑在 / 上,把前端绑定在 /website 或者 /static 上不就可以了吗?

但这样不好,我们访问的网站url前面就必须全部加上一个 /website 了,如果我们的前端代码里某些js加了当前url的判断,这样可能会出问题。

所以不能用mount直接绑定。还少要写一个if判断。

FastAPI代码

fastAPI项目使用poetry管理。以前发过如何使用poetry的文章,这里不再重复。

backend-fastapims_backend_fastapicerts  # 存放ssl证书routers  # 路由层utils  # 工具层__init__.py__main__.py.gitignorepyproject.tomlREADME.md

整个项目结构是这样的。里面必须再套一个。符合模块化。并且里面要有双下划线夹住的init和main。

并且里面这个文件夹不能是短横线了,因为要符合模块命名,改成下划线。前面之所以多个ms只是因为这个是项目名称的缩写。

[tool.poetry]
name = "ms-backend-fastapi"
version = "0.1.0"
。。。后面的就省略了

toml里的name和那个内层文件夹一样,只是短横线和下划线的区别。

然后就是__main__.py

"""
这个模板可以通用在其他fastAPI项目里
"""
from fastapi import FastAPI, HTTPException
from fastapi.responses import FileResponse
from pathlib import Pathfrom ms_backend_fastapi.utils.path_utils import get_frontend_dir
from ms_backend_fastapi.routers import routersdef main():import uvicornimport syslocal = "-l" in sys.argv# HTTPS运行ssl_files = Path(__file__).parent / "certs"app = FastAPI()for router in routers:app.include_router(router)port = 25543print(f"http://localhost:{port}")uvicorn.run(app,host="0.0.0.0",port=port,# pem文件ssl_keyfile=None if local else (ssl_files / "website.key").as_posix(),# crt文件ssl_certfile=Noneif localelse (ssl_files / "website.pem").as_posix(),)if __name__ == "__main__":main()

记得certs文件夹里放ssl证书的key文件和pem文件。网站开https要用的。

之所以写一个 -l 是因为方便在本地测试,本地就不能用https了。

utils里面有一个get_frontend_dir,这个其实就是动态判断前端打包了的文件夹的路径

这个前端文件夹一定不要套在这个fastAPI项目里面,会导致打包巨大!并且也违背了前后端分离开发的初衷了,本来前端有一个仓库,git push之后能自动触发github Actions流水线自动部署到服务器的。

所以get_frontend_dir这个就是一个动态判断当前是Windows还是Linux系统,如果是Linux系统,干脆写死一个绝对路径了。

from functools import lru_cache@lru_cache(1)
def get_frontend_dir():import platformos_name = platform.system()if os_name == "Windows":return r"D:\啊吧啊吧什么东西什么东西\website\.output\public"elif os_name == "Linux":return r"/www/wwwroot/这里是你baota上的最终部署的那个网站文件夹"else:raise Exception("Unsupported system: " + os_name)

拿到这个前端文件夹路径就是为了开放一个 / URL的 接口,提供静态文件,形成网页用的。

这里再展开上面文件结构里的routers:

routersapi__init__.pyexample.py  # 随便举个例子product.py  # 和产品相关的增删改查接口log.py  # 和日志相关的增查接口client__init__.pyclient_path.pyroot.py

root.py 部分的内容:

from fastapi import APIRouter, HTTPExceptionrouter = APIRouter()@router.get("/")
async def read_root():# 直接返回前端的主页内容,而不是重定向index_path = Path(get_frontend_dir()) / "index.html"if index_path.exists():return FileResponse(index_path)else:raise HTTPException(status_code=404, detail="Index file not found")

client_path.py 里面的内容

from pathlib import Path
from fastapi.responses import FileResponse
from fastapi import APIRouter, HTTPException
from ms_backend_fastapi.utils.path_utils import get_frontend_dirrouter = APIRouter()@router.api_route("/{path:path}", methods=["GET"], include_in_schema=False)
async def frontend_fallback(path: str):"""此路由作为前端的回退路由,用于捕获除 "/api/" 开头以外的所有其他请求,并尝试将它们导向静态文件。注意,这应该放在所有其他路由定义之后。"""# 如果请求的路径是API路径,能走到这里说明没找到,前面没有拦截住。if path.startswith("api"):return {"message": f"找不到该API接口 `{path}`","status_code": 404}# 尝试从静态文件中提供请求的路径,处理嵌入资源(如CSS、JS等)static_file_path = Path(get_frontend_dir()) / pathif static_file_path.exists() and static_file_path.is_file():return FileResponse(static_file_path)else:# 如果静态文件不存在,可以考虑返回404页面或者前端的错误处理页面error_path = Path(get_frontend_dir()) / "404.html"if error_path.exists():return FileResponse(error_path)else:raise HTTPException(status_code=404, detail="File not found")

可以看到,重点就在上面:遇到api开头的接口

所以要先把api开头的接口写在前面拦截住。

app会注册好几个router路由,因此上面写了一个循环语句。

在router __init__.py文件里可以写:

from typing import Listfrom fastapi import APIRouterfrom .client import client_path, root
from .api import api_router
routers = [api_router,# ====================== 上面要拦截住,所以和API相关的一定要在前面client_path.router,root.router,
]

接下来就是api/example.py 里的内容了

from fastapi import APIRouterrouter = APIRouter(prefix="/example")@router.get("")
async def example_api_endpoint():return {"message": "/example 被访问了"}@router.get("/{example_id}")
async def example_api_endpoint_with_id(example_id: int):return {"message": f"含有 ID: {example_id}"}@router.post("/")
async def example_api_endpoint_post(example_data: dict):return {"message": f"post 请求数据: {example_data}"}

可以这样写,但还要注意 要用 https://example.com/api/example 才能访问到上面第一个路由函数

所以api文件夹里的__init__.py 文件里要这样写:

import importlib
from pkgutil import iter_modules
from pathlib import Pathfrom fastapi import APIRouter
from . import example, logapi_router = APIRouter(prefix="/api")
# 获取当前包的绝对路径
package_dir = Path(__file__).resolve().parent# 自动发现并注册所有router
for (_, module_name, _) in iter_modules([str(package_dir)]):# 排除掉__init__.py自身if module_name == '__init__':continue# 动态导入模块module = importlib.import_module(f".{module_name}", package=__package__)# 尝试从模块中获取router对象并注册router = getattr(module, "router", None)if router:api_router.include_router(router)print(f"模块注册成功 {module_name}")else:print(f"模块{module_name}中没有找到router对象")raise ImportError(f"模块{module_name}中没有找到router对象")

这样就能实现动态的导入,以后再开新的接口直接新建py文件就可以了!不用再去点开init文件里再import了。

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

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

相关文章

Spring6笔记(五):国际化、数据校验、提前编译

九、国际化:i18n 9.1 i18n概述 9.2 Java国际化 9.3 Spring6国际化 十、数据校验:Validation 10.1 Spring Validation 概述 10.2 实验一:通过 validator 接口实现 10.3 实验三:Bean Validation 注解 10.4 实验四:实现…

鸿蒙 DevEcoStudio:通知栏通知实现

【使用notificationManager实现通知栏功能】 【普通通知、长文本通知、多行通知、图片通知】 import notificationManager from ohos.notificationManager import image from ohos.multimedia.image Entry Component struct Index {State message: string Hello World// 将图…

html5 笔记01

01 表单类型和属性 input的type属性 单行文本框: typetext 电子邮箱 : typeemail 地址路径 : type url 定义用于输入数字的字段: typenumber 手机号码: typetel 搜索框 : typesearch 定义颜色选择器 : typecolor 滑块控件 : typerange 定义日期 :typedate 定义输入时间的控件…

PL/SQL语句中的函数、游标、及存储过程的应用

在PL/SQL(ProceduralLanguage/SQL)中,游标、函数和存储过程是重要的编程结构,能够极大地增强Oracle数据库的处理能力。 下面分别介绍它们的语法和应用。 1.游标(Cursor) 游标简介 游标用于在PL/SQL代码中逐…

04-Json/Ajax/Vue的知识

1. Json结构 1.1 Json概述 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,实现数据前后端交互。 它使得人们很容易的进行阅读和编写。同时也方便了机器进行解析和生成。 JSON采用完全独立于程序语言的文本格式。这些特性使JSON成为理想的数据交换…

英码科技算能系列边缘计算盒子再添新成员!搭载TPU处理器BM1688CV186AH,功耗更低、接口更丰富

在数据呈现指数级增长的今天,越来越多的领域和细分场景对实时、高效的数据处理和分析的需求日益增长,对智能算力的需求也不断增强。为应对新的市场趋势,英码科技凭借自身的硬件研发优势,携手算能相继推出了基于BM1684的边缘计算盒…

5.23.1 深度学习在乳腺癌成像中的应用

乳腺成像在早期发现乳腺癌以及在治疗期间监测和评估乳腺癌方面发挥着重要作用。最常用的乳腺成像方式是数字乳房X线摄影、数字乳腺断层合成、超声和磁共振成像。 传统的 CAD 系统基于传统的机器学习 (ML) 技术;预定义(手工制作)的特征是系统…

【堡垒机小知识】堡垒机和接口机的重要区别分析

在企业IT架构管理中,接口机和堡垒机各自扮演着不可或缺的角色。但不少IT小伙伴对于两者不是很了解,不知道两者之间有什么区别,今天我们就来一起分析一下。 堡垒机和接口机的重要区别分析 1、功能区别 接口机主要用于数据库层面的数据交换和…

Linux多线程系列2: 模拟封装简易语言级线程库,线程互斥和锁,线程同步和条件变量,线程其他知识点

Linux多线程系列2: 模拟封装简易语言级线程库,线程互斥和互斥锁,线程同步和条件变量,线程其他知识点 1.前言 一.模拟C11线程库自己封装简易语言级线程库1.实现框架2.迅速把构造等等函数写完3.start和work1.尝试一2.尝试二3.最终版本4.给出代码 二.模拟实现多线程(为编写线程池做…

代码随想录训练营Day34:背包问题解决打家劫舍

1.198打家劫舍 1.dp数组的含义:dp[i]表示从第零个偷到第i个能够偷到的最大价值。 2.递推公式:分成两种情况: 偷第i个的情况下的最大值,注意此时第i-1个肯定是不偷的,所以此时dp[i] dp[i-2]nums[i];>dp[j] dp[j…

Unity数据持久化2——XML

简介: 基础知识 XML文件格式 XML基本语法 XML属性 练习: C#读取存储XML XML文件存放位置 读取XML文件 练习: 存储修改XML文件 练习: 总结 实践小项目 必备知识点 必备知识点——C#中XML序列化 必备知识点——C#中XML反序列化 必备…

java 线程执行原理,java线程在jvm中执行流程

java 线程执行原理,java线程在jvm中执行流程 从jvm视角看java线程执行过程 ##首先thread.c注册jni函数 JNIEXPORT void JNICALL Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls) {(*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(…

第八课,分支语句嵌套、随机数函数、初识while循环

一,分支结构的嵌套语法 在 Python 中,分支结构可以嵌套,这意味着你可以在一个条件语句中包含另一个条件语句。嵌套的分支结构可以让你更灵活地控制程序的逻辑流程。 怎么理解呢?打个比方:放学后,请三年级…

【MySQL精通之路】MySQL8.0新增功能-原子DDL语句支持

太长不看系列: 本文一句话总结,MySQL8.0支持多条DDL语句执行时的原子性了(仅限Innodb) 本文属于下面这篇博客的子博客: 【MySQL精通之路】MySQL8.0官方文档-新增功能 1.意义描述 MySQL 8.0支持原子数据定义语言&…

知乎广告推广开户最低需要多少钱?

精准高效的广告推广,是企业成功的关键,知乎作为知识分享与交流的高端平台,汇聚了大量高质量用户群体,无疑是品牌传播与产品推广的黄金之地。云衔科技作为您数字营销旅程中的得力伙伴,正以专业的知乎广告开户及代运营服…

快速搭建本地全文搜索

MeiliSearch 说起全文检索,在项目开发中,用的最多的就是 ElaticSearch 了,ElaticSearch 是基于 Apache Lucene 开发的全文检索服务,是一个端到端的解决方案,因此,部署和维护都非常复杂。今天介绍的这个全文…

AI配音可以商用吗?

随着人工智能技术的迅猛发展,AI配音技术在近几年的进步尤为显著。从最初的机械合成音到如今的智能语音合成,AI配音已经在广告、教育、媒体等领域中崭露头角,展现出其无限的潜力和广阔的应用空间。 AI配音技术的发展历程 AI配音技术起源于语…

如何在go项目中实现发送邮箱验证码、邮箱+验证码登录

前期准备 GoLand :2024.1.1 下载官网:https://www.jetbrains.com/zh-cn/go/download/other.html Postman: 下载官网:https://www.postman.com/downloads/ 效果图(使用Postman) Google: QQ: And …

【星海随笔】微信小程序(二)

WXML 模板语法 - 数据绑定 在data中定义页面的数据 在页面对应的 .js 文件中,把数据定义到 data 对象中即可: Page({data: {// 字符串类型的数据info: init data,// 数据类型的数据msgList: [{msg: hello},{msg: world}]} })Mustache 语法的格式 把 …

jQuery值操作例子 (代码)

直接上代码 <!DOCTYPE html> <html><head></head><body><div id"x1">例子</div><script src"js/jquery-3.7.1.min.js"></script><script>console.log($("#x1").text()) // 在浏览…