目录
9.1 验证器不能带入含db操作的验证逻辑, 因为db操作是异步的,Pydantic不支持, 需放到上一级服务中进行
介绍:
Kinit 是一套开箱即用的中后台解决方案,可以作为新项目的启动模版,前后端分离架构,开箱即用,在线例子:https://kinit.ktianc.top/login。
1. 后端Api应用安装及初始化
# 获取
git clone https://gitee.com/ktianc/kinit.git
# 安装依赖
cd kinit-api
pip3 install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
# 全局环境配置
文件: application/settings.py
DEBUG = True
DEMO = False
# 子环境配置(数据库, 第三方)
文件:application/config/development.py or production.py
# 数据库模型映射配置(也需设置数据库用密)
文件:alembic.ini
# 初始化数据库数据:
# (生产环境)
python3 main.py init
# (开发环境)
python3 main.py init --env dev
# 启动
# 进入项目根目录下执行
python3 main.py run
默认账号:15020221010 密码:kinit2022
1.1 数据库会话及连接参数设置
# 文件
core\database.py
# 关键方法
async def db_getter() -> AsyncGenerator[AsyncSession, None]:
# 连接参数
async_engine = create_async_engine(
SQLALCHEMY_DATABASE_URL,
echo=False,
echo_pool=False,
pool_pre_ping=True,
pool_recycle=3600,
pool_size=5,
max_overflow=5,
connect_args={}
)
2. 数据库迁移或维护
- 用的是alembic工具 Welcome to Alembic’s documentation! — Alembic 1.13.1 documentation
# 创建一个新的迁移
alembic --name dev revision -m "add age column"
编辑迁移文件
def upgrade():
op.add_column('users', sa.Column('age', sa.Integer))
def downgrade():
op.drop_column('users', 'age')
命令执行
alembic upgrade head
# 回滚上一版本
alembic downgrade -1
3. VS Code中的项目launch.json
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name":"Run Api",
"type":"python",
"request":"launch",
"program":"kinit-api/main.py",
"args":["run"],
"console":"integratedTerminal",
"justMyCode":true
},
{
"command": "pnpm --prefix kinit-admin run dev",
"name": "Run BO",
"request": "launch",
"type": "node-terminal"
}
]
}
4. 日志 logger
采用的是loguru工具 (https://github.com/Delgan/loguru)
# 使用时引入
from core.logger import logger
# 记录信息
logger.info(f"sth")
- 打开sql日志
编辑 core\crud.py, 在约179行加入logger.info(sql)
if limit != 0:
sql = sql.offset((page - 1) * limit).limit(limit)
queryset = await self.db.scalars(sql)
...
logger.info(sql)
...
5. CRUD 新模块
- 定义数据模型 apps\vadmin\iot\models\IotDevice.py
- 修改生成脚本 scripts\crud_generate\main.py
# 头部加入路径,不然引用application文件时会报找不到
sys.path.insert(0, os.path.join(os.path.abspath(os.path.dirname(__file__)), '../../'))
...
# 加入执行代码
if __name__ == '__main__':
# from apps.vadmin.auth.models import VadminUser
from apps.vadmin.iot.models.IotDevice import IotDevice
crud = CrudGenerate(IotDevice, "设备", "IotDevice")
# 只打印代码,不执行创建写入
crud.generate_codes()
# 创建并写入代码
crud.main()
- 执行生成命令, 在apps/vadmin/iot 下生成相关文件
python scripts\crud_generate\main.py
- 在 application\urls.py 加入路由
{"ApiRouter": vadmin_iot_app, "prefix": "/vadmin/iot", "tags": ["IOT管理"]},
6. 数据初始化与迁移
获取代码后, 确认环境是 dev还是pro, 对应的是不同的数据环境
| 环境 | 路径目录 | 备注:以后生成的脚本路径 |
| dev | alembic\versions_dev | |
| pro | alembic\versions_dev |
6.1 初始化:
Tip: 确保versions_dev/ 下没旧的或多余文件,否则会报 “ERROR [alembic.util.messaging] Target database is not up to date.” 错误
python main.py init --env dev
6.2 模型应用到数据库 / 数据迁移:
- 如上建立 apps\vadmin\iot\models\IotDevice.py 模型
- 编辑 alembic\env.py,引入相关模型
from apps.vadmin.iot.models import *
- 执行
python main.py migrate--env dev
7. 路由
定义文件:application\urls.py,在main.py引入,子模块下的view.py申明api入口
8. PM模型时间字段本地化转换
class DeviceSimpleOut(Device):
model_config = ConfigDict(from_attributes=True)
id: int = Field(..., title="编号")
create_datetime: DatetimeStr = Field(..., title="创建时间")
update_datetime: DatetimeStr = Field(..., title="更新时间")
@validator('create_datetime', 'update_datetime', pre=True)
def convert_utc_to_local(cls, value):
return utils.convert_utc_to_local(value)
utils.py
from pydantic import PositiveFloat, Field, EmailStr, validate_call
from datetime import datetime, timezone, timedelta
import pytz
def convert_utc_to_local(value):
# 假设本地时区是东八区
local_timezone = pytz.timezone('Asia/Shanghai')
# 将原始的UTC时间转换为本地时间
local_datetime = value.replace(tzinfo=timezone.utc).astimezone(local_timezone)
return local_datetime
9. Pydantic
9.1 验证器不能带入含db操作的验证逻辑, 因为db操作是异步的,Pydantic不支持, 需放到上一级服务中进行
9.2 为模型添加逻辑扩展字段
可以利用rooot_validator装饰器为扩展字段赋值, 例子:
假设有字段model.is_bound (init 0/1),希望输出带 is_bound_label显示
class DeviceSimpleOut(Device):
model_config = ConfigDict(from_attributes=True)
id: int = Field(..., title="编号")
create_datetime: DatetimeStr = Field(..., title="创建时间")
update_datetime: DatetimeStr = Field(..., title="更新时间")
is_bound_label: str | None
logs: list[DevicelogSimpleOut] = []
history_states: list[DevicestatehistorySimpleOut] = []
@validator("create_datetime", "update_datetime", pre=True)
def convert_utc_to_local(cls, value):
return utils.convert_utc_to_local(value)
@root_validator(pre=True)
def labels(cls, values):
values.is_bound_label = "是" if (values.is_bound == 1) else "否"
return values
10 数据查询
- kinit 自定义的查询过滤规则与写法约定
## 查询数据
### 自定义的一些查询过滤
```python
# 日期查询
# 值的类型:str
# 值得格式:%Y-%m-%d:2023-05-14
字段名称=("date", 值)
# 模糊查询
# 值的类型: str
字段名称=("like", 值)
# in 查询
# 值的类型:list
字段名称=("in", 值)
# 时间区间查询
# 值的类型:tuple 或者 list
字段名称=("between", 值)
# 月份查询
# 值的类型:str
# 值的格式:%Y-%m:2023-05
字段名称=("month", 值)
# 不等于查询
字段名称=("!=", 值)
# 大于查询
字段名称=(">", 值)
# 等于 None
字段名称=("None")
# 不等于 None
字段名称=("not None")
```
示例:
查询所有用户id为1或2或 4或6,并且email不为空,并且名称包括李:
```python
users = UserDal(db).get_datas(limit=0, id=("in", [1,2,4,6]), email=("not None", ), name=("like", "李"))
# limit=0:表示返回所有结果数据
# 这里的 get_datas 默认返回的是 pydantic 模型数据
# 如果需要返回用户对象列表,使用如下语句:
users = UserDal(db).get_datas(
limit=0,
id=("in", [1,2,4,6]),
email=("not None", ),
name=("like", "李"),
v_return_objs=True
)
```
查询所有用户id为1或2或 4或6,并且email不为空,并且名称包括李:
查询第一页,每页两条数据,并返回总数,同样可以通过 `get_datas` 实现原始查询方式:
```python
v_where = [VadminUser.id.in_([1,2,4,6]), VadminUser.email.isnot(None), VadminUser.name.like(f"%李%")]
users, count = UserDal(db).get_datas(limit=2, v_where=v_where, v_return_count=True)
# 这里的 get_datas 默认返回的是 pydantic 模型数据
# 如果需要返回用户对象列表,使用如下语句:
users, count = UserDal(db).get_datas(
limit=2,
v_where=v_where,
v_return_count=True
v_return_objs=True
)
```
### 外键查询示例
以常见问题表为主表,查询出创建用户名称为kinit的用户,创建了哪些常见问题,并加载出用户信息:
```python
v_options = [joinedload(VadminIssue.create_user)]
v_join = [["create_user"]]
v_where = [VadminUser.name == "kinit"]
datas = await crud.IssueCategoryDal(auth.db).get_datas(
limit=0,
v_options=options,
v_join=v_join,
v_where=v_where,
v_return_objs=True
)
```
11. Kinit迁移上Postgresql
初始化数据时有两个地方需要修改:
apps\vadmin\auth\crud.py 行586:
self.model.disabled == 0 => self.model.disabled == false()apps\vadmin\system\crud.py 行129
tab_id=("in", ["1", "9"]) => tab_id=("in", [1, 9])
PQ的数据类型输入比较严格, 布尔类型不能用1/0, 日期要用datetime对象, 是咋样就咋样..
参考:
- 官网: https://gitee.com/ktianc/kinit
- FastAPI: Learn - FastAPI

388

被折叠的 条评论
为什么被折叠?



