FastAPI与Vue的天作之合

FastAPI与Vue的天作之合

在Web开发的世界里,有这么一对年轻男女,分别出身于FastAPI家族(法家)与Vue家族(魏家),他们家世显赫、门当户对。时代背景且听我一一道来。

在远古时期,前端与后端混杂在一起。那是一个门伐割据的时代,jsp、asp、php三大门伐牢牢的统治着这个世界。但是,各门伐内部管理却较为混乱,就jsp来说,html、css、javascript、java代码混淆在一个jsp文件中,虽分工明确,但却管理混乱。要想他们配合做一些事情,着实不简单。

后来,javascript因对地位产生了不满,发动了一场Ajax革命。html、css追随javascript一起,渐渐脱离了java等贵族的掌控。Web开发逐渐出现了两股势力:前端和后端。随着jQuery家族的大行其道,前后端的分离越来越明显,但是,前端始终依赖于后端模板技术,无法完全独立。

Node.js的诞生拉开了近代史的序幕,前端终于可以独立的运行。但是新的问题逐渐显露,jQuery家族管理下的html、css、javascript依然是混乱的状态。javascript的强势还是引起了html和css的不满,一场革命又在酝酿这中。不久,AngularJS家族诞生了,它不再允许javascript操作DOM,这一点html和css非常赞同,于是,jQuery家族慢慢势弱。于此同时,新兴的AngularJS家族在发展过程中也暴露出了不少问题,没有办法进一步的解放生产力。于是又诞生了一个强大的家族:React。直到今日,React家族一直占据着前端开发的统治地位。

React家族的强大,是依赖于它事无巨细的法律,而这法律庞大到让人很难掌握。慢慢的,我们的女主角所在的Vue家族开始兴起。Vue家族推行精兵建政的发展策略,用的法律简单有效,得到了皇家的世间的认可。今日,Vue家族虽不能和React家族分庭抗礼,但实力却也不可小觑。

前端势力的蓬勃发展,意味着后端势力的逐渐衰退。在这样的时代背景下,后端势力,从Django到Tornado、Flask,再到男主所在的FastAPI家族。一方面,模板文件中,后端的代码越来越少,甚至FastAPI家族,不再拥有模板业务;另一方面,在抛开了前端的包袱后,后端势力向着高精尖的方向大步迈进,开发越来越精简、开发效率越来越高,从WSGI到ASGI,后端开发模式有了质的飞跃。

我们的男女主角所在的两大家族,有着一个共同的特点:简单高效。因此,他们两家可当真称的上是门当户对,两家的儿女,必定可以成就美好的姻缘。

魏家有女初长成

Vue家族自2013年才刚刚成立,不可谓不新,成长初期就能够在前端势力中占有举足轻重的地位,靠的是他的独门绝技: 虚拟DOM 。dom操作是一个非常消耗性能的操作,Vue凭借虚拟DOM技术,不再使用原生的dom操作节点,极大的解放了dom操作。 而虚拟DOM又能将视图和数据很好的分隔开来,同时在两者之间建立双向绑定的快车道,可谓一箭双雕,一石二鸟。

要了解更多Vue家族的知识,可到Vue官网学习。

明宵新月初生晕

首先搭建Vue3开发环境:

  1. 安装Node.js:

    Node.js官网下载长期支持版,并进行安装。

  2. 为npm配置淘宝源:

    Shell 复制代码
    npm config set registry https://registry.npm.taobao.org
  3. 安装vue3:

    Shell 复制代码
    npm install vue@next
  4. 安装create-vite

    Shell 复制代码
    npm i create-vite -g

接下来就可以创建Vue工程了:

  1. 创建vite工程:

    Shell 复制代码
    npm init vite
  2. 设置工程名,我们的项目名为bride(新娘):

    Shell 复制代码
    Project name: ... bride
  3. 选择工程类型:

  4. 选择变体:

  5. 安装工程依赖:

    Shell 复制代码
    cd bride
    npm i

这样,Vue家族的小美女就诞生了。

云想衣裳花想容

前端界面要做的漂亮,离不开UI库的支持,基于Vue的UI库有很多,其中较为出名的,要数Element。Element是由饿了么公司出品的开源的UI库,它有两个版本:

  • Element UI:基于Vue2
  • Element Plus:基于Vue3

我们选择了Vue3,就需要安装Element Plus:

Shell 复制代码
npm i element-plus		# 安装Elements Plust
npm install @element-plus/icons-vue		# 安装图标库

安装完成后,在main.js引入Element Plus:

JavaScript 复制代码
// 创建vue应用
import { createApp } from 'vue'
import App from './App.vue'

let app = createApp(App);

// 引入并使用Element Plus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

app.use(ElementPlus);

// 引入Element Plus的图标库
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
    app.component(key, component)
}

// 挂载dom
app.mount('#app')

然后就可以在vue页面中随心所欲的用Element来为小美女梳妆打扮了。

要了解Element各组件的使用,请移步Element Plus官网

八面玲珑七窍心

前后端完全分离之后,路由不再是只有后端才拥有的技术。我们的小美女现在只能完成单页面应用程序,需要借助vue-router的路由功能,让我们的小美女拥有多页面的能力。

与Element一样,使用npm安装vue-router,但也需要注意要安装Vue3的版本:

Shell 复制代码
npm i vue-router@next

安装完成后,在main.js中创建路由:

JavaScript 复制代码
import {createRouter, createWebHistory} from "vue-router"	// 从vue-router中引入createRouter和createWebHashHistory
import hello from "./components/hello.vue"		            // 引入hello页面
import interest from "./components/interest.vue"		    // 引入interest页面
import file from "./components/file.vue"		            // 引入file页面

// 创建路由
const VueRouter = createRouter({
    // 使用createWebHistory来管理浏览历史,如使用createWebHashHistory,URL后面会被加上#,很不舒服
    history: createWebHistory(),
    // 路由映射
    routes:[
        {
            path: "/",
            redirect: "/hello"		// 跳转到/hello
        },
        {
            path: '/hello',
            component: hello	    // 渲染hello页面
        },
        {
            path: '/interest',
            component: interest		// 渲染interest页面
        },
        {
            path: '/file',
            component: file		    // 渲染file页面
        }
    ]
})
app.use(VueRouter)

在模板中,可以使用router-link组件在页面间进行跳转:

html 复制代码
<router-link to="/hello">点我跳转</router-link>

在App.vue中,最上方使用Element Plus提供的导航条,可以很好的与vue-router配合。导航条下方,使用router-view组件,嵌入路由子页面:

html 复制代码
<script>

</script>

<template>
    <!-- 导航栏:指定router属性,开启vue-router -->
    <el-menu class="el-menu-demo" mode="horizontal" :router="true">
        <el-menu-item index="/hello">互通姓名</el-menu-item>
        <el-menu-item index="/interest">交流兴趣</el-menu-item>
        <el-menu-item index="/file">互递情书</el-menu-item>
    </el-menu>

    <!-- 使用router-view组件,嵌入路由子页面 -->
    <router-view></router-view>
</template>

<style scoped>

</style>

不要多想,只要写上router-view组件,并对它进行布局就可以了。至于router-view组件里面渲染的是哪个页面,vue-router会帮我们处理。

是不是很简单,我们的小美女已经长大成人了。

当然,路由并非那么简单的事情,还有很多高级应用,可到vue-router官网进行了解。

法家才子学艺精

FastAPI 是一个用于构建 API 的现代、快速(高性能)的web框架,使用Python 3.6+并基于标准的Python类型提示。

它拥有如下关键特性:

  • 快速:可与 NodeJS 和 Go 比肩的极高性能(归功于 Starlette 和 Pydantic)。是最快的Python web框架之一。
  • 高效编码:提高功能开发速度约 200% 至 300%。*
  • 更少 bug:减少约 40% 的人为(开发者)导致错误。*
  • 智能:极佳的编辑器支持。处处皆可自动补全,减少调试时间。
  • 简单:设计的易于使用和学习,阅读文档的时间更短。
  • 简短:使代码重复最小化。通过不同的参数声明实现丰富功能。bug 更少。
  • 健壮:生产可用级别的代码。还有自动生成的交互式文档。
  • 标准化:基于(并完全兼容)API 的相关开放标准:OpenAPI (以前被称为 Swagger) 和 JSON Schema。

还有一点不得不夸赞FastAPI,他官网做的很棒,尤其是中文翻译,虽然现在还没有翻译全,但绝对不是机器翻译,文档可读性很好,甚至一些成语用得都很到位:FastAPI官网

文曲下凡降人间

安装FastAPI,可以直接使用pip进行安装,它有两个必要的库:

Shell 复制代码
pip install fastapi
pip install uvicorn

FastAPI还依赖几个非必要的库,可以使用下面的命令一次性全部安装:

Shell 复制代码
pip install fastapi[all]

创建FastAPI项目,没有什么手脚架可以使用,因为它简单到写几行Python代码即可完成,如groom.py

Python 复制代码
from fastapi import FastAPI
import uvicorn

# 创建app
app = FastAPI(
    title="FastAPI groom",
    description="FastAPI家族的新郎官"
)


# 创建根路由
@app.get("/")
def hello():
    """
    打个招呼
    """

    return "hello world"


# 运行uvicorn服务器
if __name__ == "__main__":
    uvicorn.run("groom:app", host="0.0.0.0", port=8080)

名师指导学有成

浏览FastAPI官网介绍的各种特性时,你会发现官方有很多类似于"XXXX特性基于XXXX"的描述。是的,FastAPI聘请了天下名师指导家族子弟。

  • 自动生成文档

像Django那样,有一个自动 API 文档,开发者可以通过Web界面进行调用自己的接口 ,后端开发就可以不再依赖前端进行测试。FastAPI也提供了类似的自动 API 文档,还一下提供了两个。一个基于Swagger UI,另一个基于ReDoc。

  • 微框架

像Flask那样,只提供一个微框架,而不是像Django那样提供大而全的巨型框架。 FastAPI拥有一个简单易用的路由系统 ,可以混合其他库提供的工具。这是许多现代工具的思想,自身的包袱小了,才可以海纳百川有容乃大。借用官网的话,叫做" 导入并使用你需要的代码,而不需要它们 "。集成而非依赖,是微框架的哲学。

  • 简单直观的API

有史以来下载次数最多的Python库是什么? Requests 是其中之一。 Requests是API的客户端,而FastAPI是API的服务器。 Requests 具有非常简单直观的设计,各接口具有合理的默认值,非常易于使用。FastAPI家族的业务与之相对,却从Requests那学习到了它的精髓。

  • 自动化的数据验证

接口开发最担心什么?不是担心业务逻辑多么复杂,而是担心客户端不按约定的方式调用。使用Tornado开发API时,最先要做的事情就是对各种各样的参数进行验证和类型转换。 Webargs 、Flask都提供了自动化的数据验证,但都不如FastAPI来的直接。因为FastAPI采用了Python3.6以来类型声明系统,不但可以做到自动化的数据验证,还可以将数据的类型生成到接口文档中。

  • 异步

Sanic是最早基于 asyncio的极端快速Python框架之一。那一代Web框架完成了从WSGI到ASGI的革命。FastAPI选择Starlette, Starlette是一种轻量级的ASGI框架/工具包,是目前测试最快的Python框架,是构建高性能asyncio服务的理想选择。 FastAPI本身直接继承Starlette。因此,使用Starlette可以执行的任何操作,都可以直接使用FastAPI进行 Uvicorn是基于uvloop和httptools构建的如闪电般快速的ASGI服务器。FastAPI选择它作为推荐服务器,可以进一步提供高性能的解决方案。

FastAPI可谓站在巨人的肩膀上,集各家所长于一身。FastAPI家族的子弟,岂有不优秀的道理。

接口文档作媒人

要说我最喜欢的FastAPI的功能,就是自动生成接口文档。它的文档结合自动化的数据验证的类型系统,全面而又通俗易懂。不但有文档,还可以在文档中测试。把这样的接口文档作为媒界。后端开发者可以用来自己测试,前端开发者也可以验证自己的理解是否正确。

例如我有一个接口:

Python 复制代码
from fastapi import FastAPI
from pydantic import BaseModel      # 引入BaseModel
import uvicorn

# 创建app
app = FastAPI(
    title="测试文档",
    description="这是一个简单的 demo"
)

class Person(BaseModel):   # 创建Person类,继承自BaseModel。这是一个数据类
    name: str       # 定义姓名字段
    gender: str     # 定义性别字段


# 创建根路由
@app.post("/")
def hello(p:Person):    # 指定p的类型为Person,注意不要写成p=Person
    """
    打个招呼
    """
    return f"hello {p.gender} {p.name}"

# 运行uvicorn服务器
if __name__ == "__main__":
    uvicorn.run("hello:app", host="0.0.0.0", port=80)

运行之后,在浏览器中访问/docs,便可以看到Swagger UI风格的接口文档:

点击Try it out可以直接在页面上测试:

如果你不喜欢,那就访问/redoc,来看ReDoc风格的文档:

FastAPI聘请了这样两位媒人,何愁不能抱得美人归。

金风玉露一相逢

Ajax互传情

FastAPI提供了一系统接口,Vue就可以通过Ajax进行调用,在调用之前,先要安装axios:

Shell 复制代码
npm i axios

然后,在main.js中引用axios:

JavaScript 复制代码
// 全局引用axios,不能像element plus那像使用use
import axios from 'axios'
app.config.globalProperties.$axios = axios

这样引用之后,可以在Vue页面中使用:

  • 组合式API中:proxy.$axios
  • 选项式API中:this.$axios

男女授受不亲的思想,在Web开发的世界也有着不小的影响力,Vue家族规定自家小姐不能直接与外人见面,如果想要互相通信,需要由Vue家族的代理人转达。

代理人的配置,在vite.config.js中:

JavaScript 复制代码
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  server: {
    port: 80,     // 默认端口
    proxy: {      // 代理
      '/api': {                             // 接口前缀
        target: 'http://127.0.0.1:8080/',   // 接口域名
        changeOrigin: true,                 // 允许跨域
        ws: true,                           // 是否代理websocket
      }
    }
  }
})

这段代码配置了一个代理人/api,Vue家的小姐姐可以通过它(以/api为前缀)来访问http://127.0.0.1:8080/的接口。

准备工作已经就绪,二人的第一次约会就被提上的日程。

FastAPI家的才子写了下面的接口:

Python 复制代码
from fastapi import FastAPI
import uvicorn

# 创建app
app = FastAPI(
    title="FastAPI groom",
    description="FastAPI家族的新郎官"
)


# 创建打招呼路由
@app.get("/api/hello")
def hello(name):
    """
    打个招呼
    """

    return f"你好 {name}。 我是FastAPI家族的小男生。名叫法斯特。"


# 运行uvicorn服务器
if __name__ == "__main__":
    uvicorn.run(
        "groom:app",        # 主文件名:App对像名
        host="0.0.0.0",     # 绑定IP
        port=8080,          # 绑定端口
        reload=True         # 开启自动加载功能
    )

于是媒人来到Vue家,邀请Vue家小姐姐茶楼相亲:

Vue家的小姐姐看到接口文档后,欣然赴约:

JavaScript 复制代码
<template>
    <el-row>
        <el-col :span="4">
            姓名:
        </el-col>
        <el-col :span="4">
            <el-input v-model="name"></el-input>
        </el-col>
        <el-col :span="2">
            <el-button type="primary" @click="hello">打招呼</el-button>
        </el-col>
    </el-row>
    <el-row>
        <el-col :span="4">
            对方答复:
        </el-col>
        <el-col :span="20">
            {{ res }}
        </el-col>
    </el-row>
</template>

<script>
export default {
    name: "hello",
    data() {
        return {
            name: "魏有依",    // 小美女姓名
            res: ""           // 对方答复

        }
    },
    methods: {
        // 打招呼
        hello() {
            this.$axios.get(
                "/api/hello",
                {
                    params: {
                        name: this.name
                    }
                }
            )
            .then((response) => {
                console.log(response)
                this.res = response.data;
            })
            .catch((error) => {
                console.log(error);
            });
        }
    }
}
</script>

<style scoped>

</style>

约会成果如图:

就这样,二人互通了姓名,建立了恋爱关系。直白的GET,变成了较为私密的POST。

FastAPI家的才子为了更多的了解对方,又写了下面的接口:

Python 复制代码
from fastapi import FastAPI
from pydantic import BaseModel      # 引入BaseModel
import uvicorn

# 创建app
app = FastAPI(
    title="FastAPI groom",
    description="FastAPI家族的新郎官"
)


lass Interest(BaseModel):  # 兴趣类
    color: str      # 喜欢的颜色
    food: str       # 喜欢的食物
    fruit: str      # 喜欢的水果
    movement: str   # 喜欢的运动


# 创建聊兴趣路由
@app.post("/api/interest")
def interest(interest: Interest):
    """
    了解兴趣爱好
    """

    return {
        "color":    interest.color,
        "food":     interest.food,
        "fruit":    interest.fruit,
        "movement": interest.movement
    }


# 运行uvicorn服务器
if __name__ == "__main__":
    uvicorn.run(
        "groom:app",        # 主文件名:App对像名
        host="0.0.0.0",     # 绑定IP
        port=8080,          # 绑定端口
        reload=True         # 开启自动加载功能
    )

辛苦媒人又跑了一趟:

Vue家的小姐姐看到接口文档后,将自己的兴趣爱好告诉对方,同时得到了对方的信息:

Plain 复制代码
<template>
    <el-row>
        <el-col :span="10">
            <el-row>
                <el-col :span="24"><h1>女方兴趣</h1></el-col>
            </el-row>
            <el-row>
                <el-col :span="4">颜色:</el-col>
                <el-col :span="20"><el-input v-model="bride.color"></el-input></el-col>
            </el-row>
            <el-row>
                <el-col :span="4">食物:</el-col>
                <el-col :span="20"><el-input v-model="bride.food"></el-input></el-col>
            </el-row>
            <el-row>
                <el-col :span="4">水果:</el-col>
                <el-col :span="20"><el-input v-model="bride.fruit"></el-input></el-col>
            </el-row>
            <el-row>
                <el-col :span="4">运动:</el-col>
                <el-col :span="20"><el-input v-model="bride.movement"></el-input></el-col>
            </el-row>
        </el-col>
        <el-col :span="4">
            <el-button type="primary" @click="interest" class="interest">聊兴趣</el-button>
        </el-col>
        <el-col :span="10">
            <el-row>
                <el-col :span="24"><h1>男方兴趣</h1></el-col>
            </el-row>
            <el-row>
                <el-col :span="4">颜色:</el-col>
                <el-col :span="20"><el-input v-model="groom.color"></el-input></el-col>
            </el-row>
            <el-row>
                <el-col :span="4">食物:</el-col>
                <el-col :span="20"><el-input v-model="groom.food"></el-input></el-col>
            </el-row>
            <el-row>
                <el-col :span="4">水果:</el-col>
                <el-col :span="20"><el-input v-model="groom.fruit"></el-input></el-col>
            </el-row>
            <el-row>
                <el-col :span="4">运动:</el-col>
                <el-col :span="20"><el-input v-model="groom.movement"></el-input></el-col>
            </el-row>
        </el-col>
    </el-row>
</template>

<script>
export default {
    name: "interest",
    data() {
        return {
            // 女方兴趣
            bride: {
                color: "红色",
                food: "馒头",
                fruit: "苹果",
                movement: "逛街"
            },

            // 男方兴趣
            groom: {
                color: "",
                food: "",
                fruit: "",
                movement: ""
            },
        }
    },
    methods: {
        interest() {
            this.$axios.post(
                "/api/interest",
                this.bride
            )
            .then((response) => {
                this.groom = response.data;
            })
            .catch((error) => {
                    console.log(error);
                }
            );
        }
    }
}
</script>

<style scoped>
.interest {
    position: relative;
    top: calc(100% / 2);
    left: calc(100% / 2 - 42px);
}
</style>

约会成果如图:

这次约会意义非凡,二人发现他们的兴趣爱好竟然一模一样,感觉彼此找到了知己。于是二人感情急剧升温,不再局限于说话聊天,开始了互通情书。

第一封情书是FastAPI家的才子写的,这是Starlette老师教给他的技能。写好之后,坐立不安,等等着Vue家的小姐姐前来下载:

Python 复制代码
from fastapi import FastAPI
from starlette.responses import FileResponse    # 引入FileResponse
import uvicorn

# 创建下载情书路由
@app.get("/api/download")
def download():
    """
    下载情书
    """

    return FileResponse(
        r"D:\test\Python\fastapi\marry\groom\情书.docx",  # 实际文件路径
        filename="魏有依小姐亲启.docx"       # 浏览器上看到的文件名
    )


# 运行uvicorn服务器
if __name__ == "__main__":
    uvicorn.run(
        "groom:app",        # 主文件名:App对像名
        host="0.0.0.0",     # 绑定IP
        port=8080,          # 绑定端口
        reload=True         # 开启自动加载功能
    )

媒人:

终于,Vue家的小姐姐的下载请求来了:

JavaScript 复制代码
this.$axios.request({
  url: "/api/download",
  method: "get",
  responseType: "blob"
})
.then(response=>{
  console.log(response.headers);
  let fileinfo = response.headers["content-disposition"];
  console.log(fileinfo);
  const blob = new Blob([response.data]);
  let downloadElement = document.createElement("a");
  let href = window.URL.createObjectURL(blob);
  downloadElement.href = href;
  downloadElement.download = decodeURIComponent(fileinfo.split("filename*=utf-8''")[1]);
  document.body.appendChild(downloadElement);
  downloadElement.click();
  document.body.removeChild(downloadElement);
  window.URL.revokeObjectURL(href);
})
.catch((error) => {
  console.log(error);
});

情书取走之后,FastAPI家的才子激动万分,连夜写了回信上传接口:

Python 复制代码
from fastapi import FastAPI, File, UploadFile   # 引入File和UploadFile
import uvicorn

# 创建app
app = FastAPI(
    title="FastAPI groom",
    description="FastAPI家族的新郎官"
)


# 创建上传情书路由。因文件上传时间较长,此接口为异步接口
@app.post("/api/upload")
async def upload(file: UploadFile = File(...)):
    # 使用协程读取上传的文件
    contents = await file.read()

    # 保存文件内容
    with open(file.filename, "wb") as fp:
        fp.write(contents)

    return {"filename": file.filename, "content_type": file.content_type}


# 运行uvicorn服务器
if __name__ == "__main__":
    uvicorn.run(
        "groom:app",        # 主文件名:App对像名
        host="0.0.0.0",     # 绑定IP
        port=8080,          # 绑定端口
        reload=True         # 开启自动加载功能
    )

媒人,走你:

Vue家的小姐姐看完情书后,心情澎湃,没等多久,就写好了回信,并完善了情书传递的代码:

Plain 复制代码
<template>
<el-row>
  <el-col>
    <el-button type="primary" @click="download">下载情书</el-button>
  </el-col>
  </el-row>
<el-row>
  <el-col :span="10">
    <el-upload
        drag
        class="upload-demo"
        action="/api/upload"
        :file-list="files"
    >
      <el-icon class="el-icon--upload"><upload-filled /></el-icon>
      <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
  </el-upload>
  </el-col>
  </el-row>
</template>

<script>
  export default {
    name: "file",
    data() {
      return {
        files: []
      }
    },
    methods: {
      download() {
        this.$axios.request({
          url: "/api/download",
          method: "get",
          responseType: "blob"
        })
        .then(response=>{
          console.log(response.headers);
          let fileinfo = response.headers["content-disposition"];
          console.log(fileinfo);
          const blob = new Blob([response.data]);
          let downloadElement = document.createElement("a");
          let href = window.URL.createObjectURL(blob);
          downloadElement.href = href;
          downloadElement.download = decodeURIComponent(fileinfo.split("filename*=utf-8''")[1]);
          document.body.appendChild(downloadElement);
          downloadElement.click();
          document.body.removeChild(downloadElement);
          window.URL.revokeObjectURL(href);
        })
        .catch((error) => {
          console.log(error);
        });
      }
    }
  }
</script>

<style scoped>
  
</style>

结果如下:

Cookie相通定终身

Cookie在Web开发中的地位举足轻重,前后端之间可通过Cookie传递很多信息。

事实上,他们两人在第一次约会的时候,就已经偷偷的通过Cookie互换了生辰八字。只是因为Cookie传输比较隐蔽,没有人知道而已。

FastAPI家的才子真正的hello接口是这样的:

Python 复制代码
from fastapi import FastAPI, Response, Cookie  # 引入Response和Cookie
import uvicorn

# 创建app
app = FastAPI(
    title="FastAPI groom",
    description="FastAPI家族的新郎官"
)


# 创建打招呼路由
@app.get("/api/hello")
def hello(name, res: Response, b_horoscope=Cookie(None)):	# res参数用来写入Cookie, b_horoscope参数直接由Cookie中读取出来
    """
    打个招呼
    """

    print(b_horoscope)		# 打印女方的生辰八字

    res.set_cookie(key="g_horoscope", value="2022-07-28")  # 写入男方生辰八字

    return f"你好 {name}。 我是FastAPI家族的小男生。名叫法斯特。"


# 运行uvicorn服务器
if __name__ == "__main__":
    uvicorn.run(
        "groom:app",        # 主文件名:App对像名
        host="0.0.0.0",     # 绑定IP
        port=8080,          # 绑定端口
        reload=True         # 开启自动加载功能
    )

Vue家稍微有一点点麻烦,要想使用Cookie,需要安装vue-cookies:

Shell 复制代码
npm i vue-cookies

然后在main.js中引入,并且打开axios的Cookie开关:

JavaScript 复制代码
// 引入vue-cookies
import VueCookies from 'vue-cookies';
app.config.globalProperties.$cookies = VueCookies;

// 全局引用axios,不能像element plus那像使用use
import axios from 'axios'
axios.defaults.withCredentials=true         //开启cookie
app.config.globalProperties.$axios = axios;

然后调用hello接口的代码如下:

JavaScript 复制代码
// 打招呼
hello() {
  // 设置Cookie
  this.$cookies.set("b_horoscope", "2022-07-28");

  this.$axios.get(
    "/api/hello",
    {
      params: {
        name: this.name
      }
    }
  )
  .then((response) => {
    console.log(response)
    this.res = response.data;

    const g_horoscope = this.$cookies.get("g_horoscope");
    console.log(g_horoscope);
  })
  .catch((error) => {
    console.log(error);
  });
}

原来,他们早就瞒着所有人私定终身了啊!

天作之合结同心

二人的婚事已经定下。下面就是两家人分别为大婚做准备了。

一身红妆待出嫁

Vue家准备婚事比较简单,带着嫁妆,唱着歌,就可以过门了。

简单归简单,也是要细心准备才是。嫁妆其实还是很多的,主要集中的工程的/scr/assets中。要用的js工具啦、化妆的css和图片啦,少了一个也不行。

Vue家有位管家,叫做Vite,是一位做事很周到的人。一般用的嫁妆他都能给整理的井井有条。但是有一些小姐自己想带的(js中引用的图片),还是得知会管家一声,不然的话出现遗漏就不好了。

小姐的丫鬟(globEager)负责整理这个清单,把这个清单写到页面的数据中:

JavaScript 复制代码
export default {
    data() {
        return {          
          extras: import.meta.globEager("/scr/assets/icon/*.svg"),	// 额外的图片清单
          img_path: ""		// 要显示的图片路径
        }
    }
}

然后小姐要用的话,只须要在模板里说一声:

HTML 复制代码
<img :src="extras[path].default"/>

再就是index.html中,引用了public中的vite.svg,我们也把它放到嫁妆里面,然后在修改在下index.html的路径:

HTML 复制代码
<link rel="icon" type="image/svg+xml" href="/src/assets/vite.svg" />

到此,准备工作就已经做好了,可以通知管家打包了:

Shell 复制代码
npm run build

嫁妆准备了一马车,停在项目的/dist中,出嫁的时候把马车赶过去就好了。

鸳鸯终结两同心

FastAPI家的准备工作就要多一些了,最主要的就是宅子的安排上。新郎官亲自出马,将/api开头的接口整理到一个文件中,并让APIRouter做它们的小组长。搬完家的apis.py是这样的:

Python 复制代码
from fastapi import APIRouter, File, UploadFile, Response, Cookie
from pydantic import BaseModel
from starlette.responses import FileResponse

# 实例化路由器
router = APIRouter(prefix="/api")


# 创建打招呼路由
@router.get("/hello")
def hello(name, res: Response, b_horoscope=Cookie(None)):
    """
    打个招呼
    """

    print(b_horoscope)

    res.set_cookie(key="g_horoscope", value="2022-07-28")  # 写入男方生辰八字

    return f"你好 {name}。 我是FastAPI家族的小男生。名叫法斯特。"


class Interest(BaseModel):  # 兴趣类
    color: str      # 喜欢的颜色
    food: str       # 喜欢的食物
    fruit: str      # 喜欢的水果
    movement: str   # 喜欢的运动


# 创建聊兴趣路由
@router.post("/interest")
def interest(interest: Interest):
    """
    了解兴趣爱好
    """

    return {
        "color":    interest.color,
        "food":     interest.food,
        "fruit":    interest.fruit,
        "movement": interest.movement
    }


# 创建下载情书路由
@router.get("/download")
def download():
    """
    下载情书
    """

    return FileResponse(
        "情书.docx",  # 实际文件路径
        filename="魏有依小姐亲启.docx"       # 浏览器上看到的文件名
    )


# 创建上传情书路由。因文件上传时间较长,此接口为异步接口
@router.post("/upload")
async def upload(file: UploadFile = File(...)):
    # 使用协程读取上传的文件
    contents = await file.read()

    # 保存文件内容
    with open(file.filename, "wb") as fp:
        fp.write(contents)

    return {"filename": file.filename, "content_type": file.content_type}

再盖一座别院,名为views.py。这是专门给夫人住的:

Python 复制代码
from fastapi import APIRouter
from fastapi.responses import HTMLResponse

# 实例化路由器
router = APIRouter()


# 读取index.html的内容
def get_index():
    """
    读取index.html
    """

    with open("index.html", "r") as fp:
        return fp.read()


index = get_index()


@router.get("/", response_class=HTMLResponse)           # 创建根路由
@router.get("/hello", response_class=HTMLResponse)      # 创建打招呼路由
@router.get("/interest", response_class=HTMLResponse)   # 创建聊兴趣路由
@router.get("/file", response_class=HTMLResponse)       # 创建传情书路由
def hello():
    return index

终于可以迎接新娘子了。新娘的马车到了以后,让新娘(index.html)和新郎(groom.py)住在一起,然后把嫁妆安置在assets目录中。欢天喜地的一场宴席之后,一对璧人终于如愿以偿的结为夫妻。

夫妻双双把家还

二人成亲后,称呼也变成了老爷和夫人。夫人负责貌美如花,老爷负责赚钱养家。家族的生意做的很大,老爷凡事都要亲力亲为,恨不得要把自己当成两个人用。于是,老爷请教既是老师,又是管家的uncorn。uncorn实为一为隐世高人,在他的帮助下,老爷竟然真的练成了一套分身之术:

分身术的使用也非常简单,只需要修改一下启动方式:

Python 复制代码
uvicorn.run(
    "groom:app",        # 主文件名:App对像名
    host="0.0.0.0",     # 绑定IP
    port=8080,          # 绑定端口
    reload=True,        # 开启自动加载功能
    workers=4           # 开启多进程服务
)

也可以直接抛弃main,使用uncorn来启动:

Shell 复制代码
uncorn groom:app --host 0.0.0.0 --port 8080 --workers 4

有了分身术的加持,老爷办什么事都能举重若轻。FastAPI家族的生意顺风顺水,越做越大,直到有一天。老爷分再多的身也难以应对繁杂的事务了。

夫人建议直接出面帮老爷,不再通过老爷中转,老爷也终于意识到,凡事都要亲力亲为,实在是太过劳累。于是二人聘请了一位职业代理人:nginx。家族的生意发展成了下面的样子:

nginx不愧是专业的代理人,有些简单的问题,老爷只需要教一遍他就能学会(生成缓存),再有同样的问题不再需要经过老爷,nginx自己就可以处理。于是,老爷把家族业务分为两大部分,简单的事务(可缓存接口)教会了nginx就行了,核心业务(不可缓存接口)还是要自己牢牢掌握,不能交给外人打理。

nginx的配置如下:

conf 复制代码
worker_processes  auto;
error_log  logs/error.log notice;
pid        logs/nginx.pid;

events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
  
    # 反向代理
    upstream groom {
		server 127.0.0.1:8080;
    }

    proxy_cache_path  cache  keys_zone=CACHE:1m  levels=1:2  inactive=1m max_size=1g;
  
    server {
        listen       80;
        server_name  localhost;

        location / {
            root   ../bride/dist;				# 目录中包含index.html以及assets子目录
            index  index.html;
            try_files $uri $uri/ /index.html;
        }
      
        # 可缓存业务
        location ^~ /api/dsc/ {
            proxy_pass http://groom$request_uri;			# 转发到uncorn
          
            #以下三行,目的是将代理服务器收到的用户的信息传到真实服务器上
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          
            # 缓存
            proxy_cache CACHE;
            proxy_cache_key "$request_uri|$request_body";
            proxy_cache_min_uses 1;				# 一个用户访问后就生成缓存
            proxy_cache_methods POST;			# 即使是post请求也进行缓存
            proxy_cache_valid any 1m;			# 缓存1分钟后失效
       }

       # 不可缓存业务
       location ^~ /api/dsnc/ {
            proxy_pass http://groom$request_uri;		# 转发到uncorn
          
            #以下三行,目的是将代理服务器收到的用户的信息传到真实服务器上
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
       }      
    }
}
相关推荐
酷酷的威朗普几秒前
医院绩效考核系统
javascript·css·vue.js·typescript·node.js·echarts·html5
_Legend_King9 分钟前
vue3 + elementPlus 日期时间选择器禁用未来及过去时间
javascript·vue.js·elementui
景天科技苑24 分钟前
【vue3+vite】新一代vue脚手架工具vite,助力前端开发更快捷更高效
前端·javascript·vue.js·vite·vue项目·脚手架工具
小行星12535 分钟前
前端预览pdf文件流
前端·javascript·vue.js
join836 分钟前
解决vue-pdf的签章不显示问题
javascript·vue.js·pdf
小行星12542 分钟前
前端把dom页面转为pdf文件下载和弹窗预览
前端·javascript·vue.js·pdf
yqcoder1 小时前
Vue3 + Vite + Electron + TS 项目构建
前端·javascript·vue.js
ggdpzhk4 小时前
VUE:基于MVVN的前端js框架
前端·javascript·vue.js
活宝小娜8 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点8 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript