基于fastapi-vue2开发的前后端CURD项目模板

项目结构

image-20231127221727991

项目地址

启动项目

配置相关

配置文件位置

后端在 configs/ .env 前端在 .env.development /  .env.production /

后端

配置后端
# .env文件
端口配置 8888

其中要使用【注册功能】,同时需要在后端 core/config.py Settings类中 配置前端的域名和端口

安装依赖

cd ./backend/app   # 进入到后端程序代码的根目录

# 也可以使用命令创建虚拟环境
python -m virtualenv venv     # 创建虚拟环境

# 安装依赖(确保在cd ./backend/app 目录下)
pip install -r requirements.txt --target=./venv/Lib/site-packages  -i  https://pypi.doubanio.com/simple
启动后端
cd ./backend/app
pyhton main.py

顺手打开一下文档

# docs
http://localhost:8888/docs

# redoc
http://localhost:8888/redoc

前端

node环境(安装和配置参考我其它文章)

image-20231208111246038

配置前端
 .env.development 
 前端端口配置(和后端端口保持一致)
 VUE_APP_BASE_API='http://127.0.0.1:8888/api/v1/'

安装依赖

cd .\frontend\dashborad\
npm i  # 安装包
启动前端
cd .\frontend\dashborad\
npm run dev

账号:

| 角色 | 用户名 | 密码 | | ——– | —— | ——– | | 管理员 | admin | admin123 | | 运维员 | opt | opt123 | | 普通用户 | user | 123456 |

技术点

概述:先看一个简单的业务流动。

前端一个请求过来,前端如何找到页面视图 并 组装入参,将请求发出去?

image-20231124110755364

image-20231124110905873

image-20231124111013968

image-20231128105318571

基于 JWT+OAuth2实现登录功能

JSON 网络令牌(JSON Web Tokens)

登录接口将 token 返回了, 同时存了一份在redis , key以user_login_token_开头

前端登录后,存账号、密码、是否记住我、 生成的token 存到cookie(当然也可以存到本地缓存)

我这里是存到cookie,以处理cookie举例。建议封装好 处理token的方法

image-20231205111417388

image-20231205114612971

image-20231205114420966

请求拦截器每次请求前都读取一下cookie或本地缓存中的token数据,设置到请求头中,后端接口再获取请求头中的token去验证。

关于如何设置的请求头中,下面图片有展示 ,至于为什么要这么做。因为这样符合 JWT+OAuth2的规范, 会自动检验取出headers[’Authorization’] 、 ‘Bearer’ + ‘ ‘ 对应的token信息进行解码

config.headers['Authorization'] = 'Bearer' + ' ' + getToken()

image-20231205104502199

image-20231205121438594

image-20231205121530326

权限验证

登录验证:

token(JWT+OAuth2)

菜单权限:

  1. 登录后 user/info 获取包含角色的个人信息,没角色先提醒配置角色。

    image-20231215130553766

  2. user/routers连表查询出 用户 拥有的所有角色对应的全部 角色菜单。

    image-20231215122810714

按钮级别的权限控制(redis缓存-权限标识)

image-20231218193723411

以访问【用户列表】为例,

定义接口时 先设定好 权限标识,标识信息通常的做法是 模块+功能+请求方式

image-20231218163658123

前端访问到这个接口,具体是怎么鉴权的?

逻辑:找出指定标识(一个或多个)关联的所有角色,再查一下该用户拥有的所有角色,如果有共同角色,说明有权限,返回User对象。

image-20231215212340953

image-20231215212629197

# 以上关联查询sql
SELECT DISTINCT
	t_perm_label_role.role_id AS id
FROM
	t_roles,
	t_perm_label_role
	INNER JOIN t_perm_label ON t_perm_label.id = t_perm_label_role.label_id 
WHERE
	t_perm_label.label IN ('perm:user:get') 
	AND t_perm_label.STATUS IN (0) 
	AND t_roles.is_deleted = 0
	AND t_perm_label_role.is_deleted = 0;

前端怎么控制 列表或者按钮 是否显示呢?

具体可通过 控制标识(所属任一角色绑定了这个标识就有权限)或控制角色(有这个角色就有权限)

举例 用户列表:通过标识 控制列表展示

image-20231218190105760

举例:通过角色控制 按钮展示

image-20231218193325631

原版的若依框架,新增菜单的时候,可以选择新增对象,比如菜单或按钮,新增了按钮之后可以在对角色分配权限时,自由选择下不同按钮,列表权限。

image-20231218203830054

目前我这里将按钮 权限控制,操作起来灵活性相对麻烦一点点,但是功能可以实现,后期再优化。 更多功能可参考 >> https://blog.csdn.net/Keep__Me/article/details/132595597

菜单功能 快速新增和删除(待更新)

缓存功能实现

对不会频繁变动的数据(参数配置、全局字典、权限标识等等)进行缓存,达到减轻数据库压力的目的,同时提高效率。

导入pyhon中第三方 cachetools 库 ,通过@cached 装饰器 直接装饰到查询 参数配置和全局字典的方法上 即可实现缓存效果。

from cachetools import cached, TTLCache  # 可以缓存curd的函数,指定里面的key

image-20231208123159774

技术实现之参数配置

观察下图代码,细心的你可以发现,这里我使用了双缓存(redis+cachetools),其实选择其中之一就可以,这里只是记录一下,方便灵活使用。

其中cachetools缓存时长做了配置(redis写死了15分钟),可以自行配置。

image-20231213215427873

image-20231213213719656

image-20231213214148512

技术实现之全局字典

image-20231213235642572

image-20231214000321012

image-20231213235703292

从登录接口说一下 封装和后端的路由(APIRouter)分发

前端 按照模块 统一请求接口 ,比如user模考,全部写到use.js

image-20231122171826156

后端 根据不同的模块,进行路由分发

image-20231128103137661

from fastapi import APIRouter, Depends, Header, Request, UploadFile, HTTPException

fastapi中的依赖注入Depends https://blog.csdn.net/NeverLate_gogogo/article/details/112472480

常用的依赖注入方法 封装在 common/deps.py中

用户路由权限、get_current_user(从token获取用户数据,比如 用户id)、解析验证token有效性(check_jwt_token)、get_db (获取SQLAlchemy会话)、获取redis连接(redis = request.app.state.redis)、获取用户IP、邮件发送实例等等

需要登录身份的接口 如何访问

这里以user/info为例,token 携带在headers中

image-20231122180535630

image-20231122192026160

image-20231122193748809

生成token以及token的结构

image-20231122192741264

orm进行增删改查

image-20231122195624437

贴一下 用户和角色的模型类

image-20231122195838332

有一点需要注意:设计的所有的数据表里都有 以下三个列: is_deleted、modifier_id、modifier_time

image-20231123101050626

因为很多模块都要对 通过orm对数据库进行增删改查,所以我们将 curd 操作封装到为一个公共基类,其它模块只需要继承基类,就可以直接调用公共方法;当然如果不满意,可以重写父类方法(包括 构造函数和其它方法)

python中继承了父类,如何直接调用父类方法?

class Parent:
	
    def greet(self):
        print("Hello from Parent class")

class Child(Parent):
    def greet(self):
        # 调用父类的方法
        super().greet()
        # 补充子类特有的功能
        print("Hello from Child class")

# 创建子类对象
child = Child()
# 调用子类的方法
child.greet()

python中继承了父类,如何重写父类方法?

同时使用orm操作数据库 之前 都要建立 该模块自己的模型类,这里以User功能下的User模型类为例,我们将 抽离出部分字段(其它模块也要用到)作为 公共模型基类(目前是放在common/base_class.py),供不同模块继承。

image-20231123113151420

image-20231123113209172

pydantic

git提交

# 教程: https://blog.csdn.net/weixin_45697805/article/details/106197919
git status  #查看工作区代码相对于暂存区的差别
git add .    #将当前目录下修改的所有代码从工作区添加到暂存区 . 代表当前目录 add后面一个空格然后输入点

# 将缓存区内容添加到本地仓库。--no-verify:不验证代码格式 , -a -m 可以将暂存区的所有内容合并提交到本地仓库
git commit --no-verify -m "20231128备份"

# 拉下git最新代码,必须要执行,不然会覆盖掉别人刚提交过的代码。
git pull 
# 推送。  origin #是远程主机,master表示是远程服务器上的master分支,分支名是可以修改成其他分支 的名字的
git push origin master 

获取我的后端项目中查询出来的行数据中包含有datatime的字段,使用JSONResponse返回数据,会报错

json序列化时会报错

了解更多 FastAPI- JSONResponse

关于fastapi不错的系列文章

官方教程:https://fastapi.tiangolo.com/zh/learn/

目前是通过orm查询数据

有两种方案:

第一种,针对具体返回字段进行数据处理(比如 转为 时间字符串)再 通过 from fastapi.responses import JSONResponse 返回出去。

第二种,不使用JSONResponse

使用FastAPI和Celery的异步任务(更多内容可搜索:celery和redis fastapi)

FastApi结合loguru日志使用

https://blog.csdn.net/qq_51967017/article/details/134045236

关于跨域

image-20231124150549572

关于数据库

连接池机制

避免频繁的创建、断开数据库连接造成的开销,提高性能。

session线程是不安全的,在多线程情况下,当多个线程共享一个session时,数据处理就会发生错误。为了保证线程安全,需使用scoped_session方法,它可以 返回一个可跨线程使用的局部会话。当多个线程使用会话时,会自动为每一个线程创建一个会话,用完会自动关闭。

事务管理

一系列操作的组合,要么全部正确的执行,要不全都不执行。进而保障数据的一致性。

使用 SQLAlchemy 的 ORM 模型时进行数据库操作,ORM 会在默认情况下自动开启事务,并且在会话结束时自动提交事务,如果出错了会自动回滚。

但是在执行原始的 SQL 语句时,需要手动管理事务的提交和回滚。像这样

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

engine = create_engine('sqlite:///:memory:')
Session = sessionmaker(bind=engine)
session = Session()

try:
    # 执行一些操作,可能会出现异常
    with session.begiin():
    	session.execute()
    	session.execute()
    # 提交事务
    session.commit()	
except:
    # 回滚
    session.rollback()

finally:
	session.colse()

原本

pymysql并不是线程安全的,所以当有多个线程同时使用pymysql操作数据库时,就会出现问题。

所以从线程安全和支持更高的并发的角度,我们一般会使用数据库连接池来解决,一般自己实现一个比较麻烦,通常是使用第三方库,如 DBUtils

发现sqlalchemy 除了orm操作数据库,还可以操作手写sql

尽管SQLAlchemy 有链接池, 既可以使用orm功能,也可以执行手写sql的功能, 但是我不想用它执行手写sql的功能,我可以使用其它工具代替吗?

封装sqlalchemy手动执行sql增删改查自用

方法2 :

java中使用 Druid 连接池操作数据库


我的项目

image-20231128124655907


关于搜索 和时间相关

日期时间时区: https://juejin.cn/post/7277111344003186742

SQL SQLAlchemy orm:如何筛选日期字段(获取时间范围内的数据)

其中deadline_data中的列表数据 是时间字符串即可。可以是年月日 或者 年月日时分秒

查询功能来举例, 使用SQLAlchemy orm 如何进行时间大小比较或者一段时间范围内搜索。 比如数据表中的deadline(截止时间)类型是datetime,

如果前端传时间戳给后端。 第一种情况:后端可以直接对比时间戳

比较大小

image-20231130230150283

时间范围内– between() 括号内的类型保持一致即可, 都是年月日 或者 年月日时分秒或者时间戳。

image-20231129211001127

不嫌麻烦你当然也可以将前端传入的时间戳转为时间字符串,同时将数据表里的datatime类型转为时间字符串再进行比较

from datetime import datetime
# 假设end_time_str是一个时间戳
end_time_timestamp = 1630863600  # 2021-09-05 12:00:00 的时间戳

# 将时间戳转换为datetime对象
end_time = datetime.fromtimestamp(end_time_timestamp)

# 将datetime对象格式化为时间字符串
end_time_str = end_time.strftime('%Y-%m-%d %H:%M:%S')

第二种情况: 前端传时间字符串,可以先转为时间戳再对比大小

from datetime import datetime

# 假设end_time是时间字符串
end_time_str = "2023-12-31 23:59:59"

# 将时间字符串转换为datetime类型
end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S')

# 将datetime对象转换为时间戳
end_time_timestamp = end_time.timestamp()

现在控制输出模型并没有生效,后面要解决一下 。

Post请求的3种编码格式:application/x-www-form-urlencoded和multipart/form-data和application/json

1、application/x-www-form-urlencoded

1)浏览器的原生form表单 2) 提交的数据按照 key1=val1&key2=val2 的方式进行编码,key和val都进行了URL转码

如何发x-www-form-urlencoded请求

https://blog.csdn.net/u013258447/article/details/101107743

通过创建人id查出对应的账号名,显示再下拉选择中

期间遇到了另外的问题

比如

  1. 返回的字段的排序问题 如何能按定义模型类的字段顺序 输出。

    image-20231129211244713

  2. deadline 是一个列表 ,如果我想 指定列表里的字典的key 比如 {’start_time’ : ‘’, ‘end_time’ : ‘’}

  3. 定义好的 deadline 在文档里能清晰的展示出结构比如这样

    image-20231129211719921

    使用 FastAPI ,你可以定义、校验、记录文档并使用任意深度嵌套的模型(归功于Pydantic)

    https://fastapi.tiangolo.com/zh/tutorial/body-nested-models/#__tabbed_2_3

fastapi简单的demo

https://www.cnblogs.com/Detector/p/17458890.html


传递数据的方式

在 FastAPI 中,可以使用路径参数、查询参数、请求体参数和标头参数来传递数据。路径参数用于从 URL 中提取值,查询参数用于在 URL 中以查询字符串的形式传递值,请求体参数用于 POST、PUT 等请求的请求体中传递值,标头参数用于在 HTTP 标头中传递值。

对于 GET 请求,通常会使用查询参数来传递数据。查询参数可以通过查询字符串的方式直接附加在 URL 后面,因此非常适合用于 GET 请求。

请求体的作用

请求体嵌套模型

from typing import List, Set, Union

from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl

app = FastAPI()


class Image(BaseModel):
    url: HttpUrl
    name: str


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[float, None] = None
    tags: Set[str] = set()
    images: Union[List[Image], None] = None


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results
    
    
    
    
{
    "name": "Foo",
    "description": "The pretender",
    "price": 42.0,
    "tax": 3.2,
    "tags": [
        "rock",
        "metal",
        "bar"
    ],
    "images": [
        {
            "url": "http://example.com/baz.jpg",
            "name": "The Foo live"
        },
        {
            "url": "http://example.com/dave.jpg",
            "name": "The Baz"
        }
    ]
}

常用的定义 可选参数的方式有

from typing import Union, Optional

description: Union[str, int, None] = None  设置默认,就是可选,不设置就是必填。
description: Optional[str]  # 可选参数,Optional可以不设定默认值(默认为None),这里指定了 str类型,所以取值可以是str或者None


id: int = Form(...)  # 三个点 表示必填,这里指必填;并且没有设定默认值,当然你可以自己设置默认值

请求体参数:您可以使用 pydantic 的 Field 在pydantic内部(也就是我们定义的Schemas) 声明出入参类型和校验规则。

image-20231130114814351

在 路径函数中 使用Query更进一步 进行默认值、长度、正则、描述信息校验

通常情况下,FastAPI会自动解析QueryPathBody等函数中的参数信息,并将其显示在生成的API文档中。

image-20231130102644700

def Query(  # noqa: N802
    default: Any = Undefined,
    *,
    alias: Optional[str] = None,
    title: Optional[str] = None,
    description: Optional[str] = None,  
    gt: Optional[float] = None,  # greater than  大于
    ge: Optional[float] = None,  # greater than or equal to  大于或等于
    lt: Optional[float] = None,  # less than 小于
    le: Optional[float] = None,  # less than or equal to   小于或等于
    min_length: Optional[int] = None,  # 最短
    max_length: Optional[int] = None,  # 最长
    regex: Optional[str] = None,
    example: Any = Undefined,
    examples: Optional[Dict[str, Any]] = None,
    deprecated: Optional[bool] = None,
    include_in_schema: bool = True,
    **extra: Any,

查询参数比较多的情况下,如果全部写在接口定义文件中,会变动很长。起初我是考虑将其放在 pydantic的参数模型中定义,然后接口文件中,导入模型使用即可,但是发现这样的操作的话,入参变成了请求体,并非预期的查询参数,很显然,正常情况下get请求我们使用查询参数更合适。

所以网上找到了一种方法:使用 Depends进行一次转化。

image-20231204104925655

image-20231204105053369

image-20231204105245222

发送表单数据

fastapi表单数据

如果请求体中的数据类型是 json或者表单数据 ,应该怎么处理

https://deepinout.com/fastapi/fastapi-questions/71_fastapi_supporting_both_form_and_json_encoded_bodys_with_fastapi.html

常见的有原生表单(application/x-www-form-urlencoded)、form-data格式

目前表单格式定义,只能再接口定义文件中写,不能使用参数模型(使用了就会变成请求体参数-类型为json)

image-20231204110833494

返回与输入相同的数据

image-20231130115032817

输入使用一个模型,输出使用另一个模型。

image-20231130115144640

想要模型中有很多字段,这些字段不少是有默认值,响应返回的时候,其实我不想返回所有,因为这样会有很多没有实际数据 只有默认值的字段。我想忽略它们

response_model_exclude_unset=True

image-20231130115616422

fastapi(65)- 路由函数指定了 response_model,在返回自定义 JSONResponse 时, 不会限制它返回的数据结构… https://blog.csdn.net/qq_33801641/article/details/121313492

枚举(找机会整理一下用法)

image-20231203232646508

查询参数、请求体参数 是否有必要使用 Pydantic模型指定参数类、 长度大小文档生成 等等 如果不使用会怎么样?

先看看请求体参数: https://juejin.cn/post/7068491099425210404

再看看查询参数


触发器

// 计算 deadline 的值。触发器的逻辑可以使用 SQL 表达式来实现 first_active_time + reg_times 的计算,并将结果存储到 deadline 字段中

DELIMITER //

CREATE TRIGGER calculate_endtime_trigger
BEFORE INSERT ON t_reg_code
FOR EACH ROW
BEGIN
    SET NEW.deadline = DATE_ADD(NEW.first_active_time, INTERVAL NEW.reg_times DAY);
END;
//

DELIMITER ;



// 表 t_reg_code 中 字段first_active_time,有值了,就更新 字段 state的值为1

DELIMITER //

CREATE TRIGGER update_state_trigger
BEFORE UPDATE ON t_reg_code
FOR EACH ROW
BEGIN
    IF NEW.first_active_time IS NOT NULL THEN
        SET NEW.state = 1;
    END IF;
END;
//

DELIMITER ;


若依框架—权限管理设计

http://wed.xjx100.cn/news/260010.html?action=onClick


image-20231211001543332

image-20231211001138848

另外 查询出来的字段太多了,而且还有空值的字段显示出来,如何自动去掉空值字段,并且可以方便的自定义排除的一些字段?

image-20231211001127519


orm 连表查

定义不定义外键都可以连表查。

只不过orm模型定义了外键 可以更方便的反向查询;同时 使用join(不使用where) 会自动查找外键关联,不用像where一样必须写公共列

因为代码里只更多的是使用

使用外键(where和)和不使用外键(where 条件)

https://blog.csdn.net/weixin_40123451/article/details/117252473

https://www.cnblogs.com/shengs/p/10473524.html

https://www.cnblogs.com/drizzle-xu/p/10239437.html(不带外键关系)


注册功能实现(邮箱验证)

防止重复注册、恶意注册(通过获取请求中的ip识别)、发送确认邮件等

image-20231120195017783

image-20231214101151328

image-20231214110309272

目前存在一个小问题 需要更新

注册成功后要挂上默认角色;成功后,哈希密码 为空

fastapi结合alembic实现数据库热更+数据备份功能

热更功能

可实现数据库结构管理(字段类型变更升级,加表,加项),可一键同步到数据库,我们只需要管理好 项目中的 models即可

实际的开发过程中,正常是通过维护数据模型 管理数据结构,比如添加了一个字段,维护字段类型等,然后 通过迁移 同步数据库。

同步过程中:不会重复建表。

模型结构变动就会更新; 注意只会更新结构,不会更新数据(需要自行备份数据-具体可参考:如何备份数据)。

迁移是单向的,只能模型到数据库;如果手动在数据库新增了 字段更改字段类型等,迁移操作并不会更新模型数据。

image-20231212111622748

image-20231212111709848

数据库热更之技术实现

结合 alembic ,执行 cmd命令。

# 先根据当前数据库状态(modle相关信息)自动【生成迁移脚本】 再 【执行迁移】
alembic revision --autogenerate -m "auto_update" && alembic upgrade head
python -m alembic revision --autogenerate -m "auto_update" && python -m alembic upgrade head

# 注意 多个命令是可以通过 && 连接,系统会按顺序一个一个执行。

执行 alembic 可能出现的问题 :

  1. 找不到alembic命令

    • 虚拟环境未激活

      image-20231212114246291

      pycharm如何激活虚拟环境

      image-20231214125414766

    • 环境激活了还是无法执行 alembic命令,试试命令前面加上 python -m

      python -m alembic revision --autogenerate -m "auto_update" && python -m alembic upgrade head
      
  2. 成功执行alembic命令但是报错

    解决方案:删除迁移文件,重新生成,再次执行迁移即可(参考教程:https://www.jianshu.com/p/4aa58499e9d8)

    image-20231214124226128

    同时 删除 数据库中 自动生成的表(里面存着version中迁移文件对应的版本号,不删的话不会再次生成,导致版本对应不上)

    image-20231212160320927

    image-20231212161021499

  3. 如果项目没有 alembic文件夹 ,可自己初始化一个(目前用不到)

    参考步骤如下
    进入后端项目根目录(app目录下) 
    cd fastapi_vue\backend\app>
    
    初始化
    alembic init alembic
    
    找到生成的alembic.ini 文件,修改sqlalchemy.url
    sqlalchemy.url = mysql+pymysql://root:root@localhost:3306/fastapi_vue
    
    找到alembic文件夹下的env.py,修改target_metadata
    
    from apps.system.models.config_settings import ConfigSettings
    target_metadata = ConfigSettings.metadata
    
    生成迁移文件
    alembic revision --autogenerate -m "auto_update"
    
    执行迁移
    alembic upgrade head
    

数据库数据备份

因为热更只能维护数据结构,表里的数据被没有同步,所以增加一个数据导出、导入功能(也可以使用Navicat导出导入sql文件)

可以使用Navicat导出导入sql文件(推荐);也可以使用自己的脚本:

脚本技术实现:

核心原理将数据库中的表,导出成csv文件,需要的时候可以根据csv文件,插入到数据库中。

脚本操作过程中遇到的问题:

对于设置外键的关联表需要重点关注一下,插入数据时的执行顺序(基于这点,目前建议使用 Navicat,等我后期优化)


定时任务功能

image-20231213002954497

image-20231213003757216

技术实现: 使用apscheduler调度任务

使用 scheduler 异步实例对象来调度执行脚本时,实际上是将脚本封装成了一个异步函数,然后将其添加到调度器的任务队列中。这样,当调度器执行任务时,它会异步地调用这个异步函数,从而实现脚本的异步执行;同时我们可以对 scheduler 异步实例对象进行初始化操作,比如设置进程池的大小。

这里说一下项目的初始化包括的几个模块,跨域请求、日志、注册路由、redis、自定义异常(HTTPException)、app事件监听(startup、shutdown)、事件中做定时任务调度(开启和关闭),我就是在app主程序的监听事件中,启动和初始化定时任务的。

下图是项目中生成 异步实例对象的方法

image-20231214140328792

尽管调度是异步的,但是如果要更好的提升执行效能,我们的定时任务脚本最好也是通过异步编程,这样才可以异步执行。具体可以使用asyncio库,进行异步编程。

# 异步编程

import asyncio

async def my_async_function():
    print("Hello, world!")
    await asyncio.sleep(1)
    print("Hello again!")
遇到的问题
  1. 每次重启项目后APScheduler中的任务就会从内存中删除,导致APScheduler中的任务和数据库任务状态不一致

    解决方案:项目启动时将数据库中任务状态初始化为暂停状态,再从数据库 查询出 所有开启的任务 重新调度执行 任务状态调整为 运行中

    当然添加到调度管理器时,会处理一下逻辑,比如 任务不可重复添加,每个任务使用的执行策略(立刻执行、执行一次、放弃执行等等),是不是支持并发,cron表达式(指定定时任务执行时间),调度日志、钉钉或者邮件通知这些。

  2. 不管任务是否被调度,前端页面,我想执行脚本,如果直接执行脚本,会卡住。

    解决方案:改为多线程执行。这样主线程可以继续执行其他任务,而脚本函数会在另一个线程中运行,不会影响主线程的执行。

    image-20231214143237513

  3. 脚本传参问题。

    scheduler.add_job() 本身支持 位置和字典传参 所以我们的脚本方法写好后,具体参数,可以在前端页面直接配置。所以我们的脚本方法写好后,具体参数,可以在前端页面直接配置。

    image-20231214202447968

    import time
    
    def print_numbers(start, end, *args, **kwargs):
        for i in range(start, end):
            print(i)
            time.sleep(1)
    
        for arg in args:
        	print(arg)
    
        for key, value in kwargs.items():
            print(f"{key}: {value}")
    
    print_numbers(1,10,name='zhangsan',age=20)
    
  4. 000

  5. 151


文件导入导出功能

image-20231212224622913

基于importlib.import_module() 动态导入模块,实现导入导出文件功能。

导出板块:

更进一步,可以根据 前端传过来的筛选条件,查询后台数据。通过openpyxl和二进制流工具类输出二进制信息,再通过fastapi响应(可指定headers),异步导出文件。更多自定义响应(Html、文件、流、其它)可参考官方文档: https://fastapi.tiangolo.com/zh/advanced/custom-response/?h=

技术实现(导入导出单独存放在一个report文件夹下,方便管理):

image-20231212201705027

image-20231212201907144

image-20231212202131058

导入板块(待完善)

项目部署

注意: 本源码中所有配置文件都使用 配置文件模板(.example)的形式上传, 目的是为了方便我自己的配置信息不被泄露。 部署项目时需要把.example后缀去掉才能使用。需要用到配置文件的地方均在后续说明有列出。

FIRST

克隆项目主分支


数据库中创建DB

CREATE DATABASE fastapi_vue;  -- 仅供参考根据自己项目名和所用的数据库类型 修改SQL, 

运行脚本初始化数据库数据

python initial_data.py

或者直接导入初始化sql数据到数据库

USE fastapi_vue;
SOURCE fastapi_vue.sql;   -- 仅供参考

APP

  1. 安装python3、virtualenv、Nginx、 supervisor

# 略
  1. 安装必要第三方库

cd ./fastAPI-vue/backend/app   # 进入到后端程序代码的根目录

python3 -m virtualenv venv     # 创建虚拟环境

source ./venv/bin/activate      # 进入虚拟环境

pip install -r requirements.txt   # 安装库  可使用谷内源:  -i https://pypi.tuna.tsinghua.edu.cn/simple
  1. 准备程序配置文件

cp ./configs/.env.example  ./configs/.env    # 复制配置模板

vim ./configs/.env     # 拷贝配置文件

# python main.py   # 测试项目是否成功运行,

根据需要修改.env的配置内容,配置所有的参数参考 ./core/config.py -> class Settings

  1. 使用supervisor管理项目(生产环境)

cp ./configs/supervisor.conf.example  ./configs/supervisor.conf   # 拷贝配置文件

sudo ln -s /home/ubuntu/opt/fastAPI-vue/backend/app/configs/supervisor.conf /etc/supervisor/conf.d/fastapi_vue_backend.conf   # 配置文件软链到supervisor的配置文件目录, 此处目录路径仅供参考

vim ./configs/supervisor.conf    # 编辑配置文件,已有参考配置,按需修改

sudo supervisorctl update     # 更新supervisor

sudo supervisorctl start fastapi-backend:   # 启动项目

WEB

  1. 安装node、npm

# 略
  1. 安装相关第三方包

cd ./fastAPI-vue/frontend/dashborad   # 进入项目目录

npm i  # 安装包
  1. 开发环境配置

cp .env.development.example .env.development  # 复制配置文件

vim .env.development  # 编辑配置文件

npm run dev    # 测试运行程序 
  1. 生产环境配置

cp .env.production.example .env.production  # 复制配置文件

vim .env.production  # 编辑配置文件

npm run build:stage  # 打包项目文件 (可以考虑在本地打包后把dies文件上传服务器部署)

NGINX

使用Nginx反向代理后端项目和前端文件夹。

cd ./fastAPI-vue  # 进入项目根目录,此处目录仅供参加

cp ./nginx.conf.example  ./nginx.conf   # 拷贝配置文件

sudo ln -s /home/ubuntu/opt/fastAPI-vue/nginx.conf /etc/nginx/sites-enabled/fastapi_vue.conf   # 配置文件软链到Nginx的配置文件目录, 此处目录路径仅供参考

vim ./nginx.conf    # 编辑配置文件,已有参考配置,按需修改

nginx -t   # 检查Nginx配置文件 

nginx -s reload   # 重启Nginx

部署

运行容器 redis 使用 https://blog.csdn.net/yu_hongrun/article/details/107577674 根据redis镜像 启动一个 redis容器,并设置映射端口。 安装redis 可视化工具 :Redis Desktop Manager,测试连接

前端vue项目打包,稍后部署用

因为下面要进行nginx识别转发,我们在env.production中提前配置,所有前端项目的请求base_api。

image-20231221145839597

# 生产环境打包
npm run build:prod

image-20231221145911246

云服务器部署发布

使用MobaXterm连接云服务器,我这里使阿里云服务器:centos

MobaXterm具体操作可查看往期的笔记内容。

该步骤是最重要的一步,实现过程中一定要注意是在服务端操作还是在本地计算机上操作。若在服务器上操作,还要注意是使用root用户进行操作还是使用git用户进行操作。

修改云服务器密码

passwd
# 然后输入两次密码

Nginx安装和配置

请参考电子书文档:中间件-Nginx相关内容

前端文件部署

将打包好的前端文件 上传到项目部署目录中/home/www/fastapivue2/

/home/www/fastapivue2/

image-20231221152339634

打包遇到的问题:

  1. npm run build 之后生成的index.html页面打开为空白

    https://blog.csdn.net/qq_44785351/article/details/129320814

  2. nginx部署,可以访问首页index,直接访问其他项目路径 全部404

    https://blog.csdn.net/HD243608836/article/details/133706915

    vue的history模式,要设置 不管访问哪个目录路径,都会访问根目录的index.html文件

    location / {
                root   /home/ruoyi/projects/ruoyi-ui;
                try_files $uri $uri/ /index.html;
                index  index.html index.htm;
            }
    

后端项目部署

部署目录

指定一个后端项目文件存放路径,我用的是root账号,这里我直接放在 /root/fv2/app/目录下 。

image-20231221153259019

项目工程安装python虚拟环境

安装 virtualenv 工具包,并创建虚拟环境 并安装依赖

supervisor

创建虚拟环境 并安装依赖
安装 virtualenv 
pip38 install virtualenv -i https://pypi.tuna.tsinghua.edu.cn/simple/

cd /root/fv2/app   # 进入到后端程序代码的根目录

python38 -m virtualenv venv     # 创建虚拟环境

source ./venv/bin/activate      # 进入虚拟环境

pip38 install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple/   # 安装库

安装 supervisor 工具包,具体怎么做(待更新)

启动后端项目

cd /root/fv2/app

# 激活虚拟环境
source ./venv/bin/activate

# 解决Linux关闭终端(关闭SSH等)后运行的程序或者服务自动停止【后台运行程序】
nohup python main.py

# 访问
http://fv2.51automate.cn/


--- 收藏项目  端口9090---------
cd /root/myFavorite

# 解决Linux关闭终端(关闭SSH等)后运行的程序或者服务自动停止【后台运行程序】
nohup python3 myFavorite.py

# 访问
http://myfav.51automate.cn/

后台运行 nohup python main.py 如果要修改后端内容,端口会一直被占用,无法更新最新内容

如何解决端口被占用

lsof -i:8888  或者 netstat -ap|grep 8888

kill -9  进程号(pid)
或者
kill -s  进程号(pid)

alembic [ə'lembɪk]数据库结构维护(热更)、APScheduler [ˈAPʃɛdjuːlər] (异步任务调度库,可灵活执行各种定时任务,支持CRON表达式) 、使用supervisor管理项目(生产环境)、

celery是基于进程来执行。celery是一个分布式的任务队列(你定义的任务会被Celery分发到一个任务队列中),每个任务都是在独立进程中执行,用于处理异步任务,还支持 任务调度、定时任务(发邮件、数据处理、生成报告)等功能。

git status  #查看工作区代码相对于暂存区的差别
git add .    #将当前目录下修改的所有代码从工作区添加到暂存区 . 代表当前目录 add后面一个空格然后输入点

# 将缓存区内容添加到本地仓库。--no-verify:不验证代码格式 , -a -m 可以将暂存区的所有内容合并提交到本地仓库
git commit --no-verify -m "20231128备份"

# 拉下git最新代码,必须要执行,不然会覆盖掉别人刚提交过的代码。
git pull 
# 推送。  origin #是远程主机,master表示是远程服务器上的master分支,分支名是可以修改成其他分支 的名字的
git push origin master