优秀的编程知识分享平台

网站首页 > 技术文章 正文

Python电子发票管理工具3:FastAPI后台接口

nanyue 2024-09-26 15:25:02 技术文章 5 ℃

用一系列文章介绍如何用python写一个发票管理小工具。

本文介绍如何用Uvicorn+FastAPI搭建后台接口,如何使用loguru打印日志,以及自动打开浏览器显示前端页面。



基本思路

大家一般运行Uvicorn都是uvicorn.run()直接运行,这样uvicorn启动后会阻塞程序,后续无法执行代码。

所以这里我们使用Python多进程,一个主进程用于打开浏览器以及后续其他功能,一个独立的子进程运行Uvicorn。

配置文件

创建一个配置文件config.yml,编写config.py用于读取配置文件。使用ruamel.yaml模块读取yaml文件。

#config.py
from ruamel.yaml import YAML

class AppConfig():
    def __init__(self):
        with open("config/config.yml","r",encoding="utf-8") as configFile:
            yaml = YAML()
            conf = yaml.load(configFile)
            self.config = conf
            self.loguru = self.config['loguru']
            self.uvicorn = self.config['uvicorn']

appConfig = AppConfig()
# config.yml
# 日志配置
loguru:
  format: '{time:YYYY-MM-DDTHH:mm:ss.SSSZ} {level} tid[{extra[tid]}] {message}'
  level: DEBUG
  file: logs/application.log
  rotation: 00:00
  retention: 30 days

# uvicorn设置
uvicorn:
  host: 127.0.0.1
  port: 8088

# 等待web服务启动时间,服务启动后自动打开浏览器
waitWebServerTime: 10

独立进程运行Uvicorn

使用multiprocessing.Process创建子进程,注意设置daemon=True,这样主进程关闭时会自动关掉子进程。

import uvicorn
from multiprocessing import Process
from config import appConfig

def runUvicorn():
    uvicorn.run("webapi:app", host=appConfig.uvicorn["host"], port=appConfig.uvicorn["port"], reload=False, log_config='config/uvicornLog.json', access_log=True, workers=1)

# 启动web服务
webapiProcess = Process(target=runUvicorn, name="webapi_process", daemon=True)
webapiProcess.start()

try:
	while True:
		time.sleep(1)
except KeyboardInterrupt:
		 logger.info("user interrupt, close application")

sys.exit(0)

日志增加TID

为了方便日志追踪,一般我们都会在日志中打印一个transaction_id,用户一个请求过程中的所有日志都有这样一个唯一的transaction_id。

我们使用loguru模块来打印日志,loguru配置和使用非常简单,而且我们的程序使用了多进程,loguru是支持多进程的。

第一节的配置文件中已经有loguru需要的配置项了,下面介绍如何使用loguru,以及fastapi如何生成transaction_id,并且将tid添加到日志中。

创建log.py

from loguru import logger
import sys
from config import appConfig

logger.remove()
logger.configure(extra={"tid":""})
logger.add(sys.stderr,  enqueue=True, format=appConfig.loguru["format"], level=appConfig.loguru["level"])
logger.add(appConfig.loguru["file"], enqueue=True, format=appConfig.loguru["format"], level=appConfig.loguru["level"], rotation=appConfig.loguru["rotation"], retention=appConfig.loguru["retention"])

需要关注的是logger.configure(extra={"tid":""})这一行,将tid添加到日志record的extra中,extra是loguru提供给用户添加自定义日志字段的属性。虽然也有办法直接修改record,但是建议添加到extra中。

在webapi.py中添加middleware,在收到请求时生成tid。然后通过loguru的pach将tid添加到日志record中。

#webapi.py
from random import randint
from typing import Callable, Optional
from fastapi import FastAPI,Request
from loguru import logger
from datetime import datetime
from contextvars import ContextVar

# 用于日志记录当前请求ID
transaction_id: ContextVar[Optional[str]] = ContextVar('transaction_id', default='')

def transaction_id_filter(record):
    record['extra']['tid'] = transaction_id.get()
    return record

# 此处向日志中设置tid
logger.configure(patcher=transaction_id_filter)

app = FastAPI(title='My Invoice Folder', docs_url=None, redoc_url=None)

# 生成请求ID
@app.middleware("http")
async def create_transaction_id(request: Request, call_next: Callable):
    # tid格式为时间戳+4位随机数
    transaction_id.set(f"{datetime.now().strftime('%Y%m%d%H%M%S%f')[:-3]}{randint(1000, 9999)}")
    response = await call_next(request)
    return response

将fastapi首页设置为发票夹页面

我们之前已经写了一个发票夹html页面,这里直接将fastapi首页跳转到这个页面就可以了。

将页面资源都放到项目ui文件夹中,加载ui文件夹。

app.mount("/ui", StaticFiles(directory="ui"), name="ui")

设置首页跳转

# 首页跳转到我的发票夹页面
@app.get("/", include_in_schema=False)
def read_root():
    logger.info("access index page")
    return RedirectResponse("/ui/index.html")

自动打开浏览器显示发票夹页面

首先我们在启动Uvicorn进程后,使用request请求首页,如果请求成功,则打开系统默认浏览器并打开发票夹首页。代码如下:

# 启动系统默认浏览器打开界面
waitWebServerTime = appConfig.config["waitWebServerTime"]
openBrowserSuccess = False
while waitWebServerTime > 0:
    time.sleep(1)
    try:
         # 判断web服务是否已启动
        indexUrl = f'http://{appConfig.uvicorn["host"]}:{appConfig.uvicorn["port"]}/'
        response = requests.get(indexUrl, timeout=1)
        if response.status_code == 200:
            # 打开主页
            openBrowserSuccess = openBrowser(indexUrl)
            break
        except Exception as e:
            pass
        waitWebServerTime -= 1

总结

现在我们可以直接运行main.py,就可以运行后端api,并且自动打开浏览器看到页面了~后续逻辑抽空继续写。




代码比较多,已经上传到github(https://github.com/xy12358/my-invoice-folder.git)和gitee(https://gitee.com/xinying/my-invoice-folder)。

最近发表
标签列表