sysom1/template
SunnyQjm 7a52b99e9d feat(deploy): Support deploy by docker 2024-01-17 06:12:36 +00:00
..
fastapi feat(deploy): Support deploy by docker 2024-01-17 06:12:36 +00:00
README.md feat(alarm): Support return StandardListResponse which contains total 2023-09-20 11:53:27 +00:00

README.md

微服务模板

1. 快速新建微服务

# 在 sysom/script 下调用如下命令
./sysom.sh create server <微服务名称> <微服务监听端口>

注意:

  • 微服务名称不用带 sysom_ 前缀,会自动补充;
  • 微服务的名称不能和现有的微服务冲突 => 如果冲突创建过程会报错提示
  • 微服务监听端口不能和现有的微服务冲突 => 如果冲突创建过程会报错提示

比如创建一个名称为 demo 的微服务可以执行下列命令:

./sysom.sh create server demo 7010

执行后的目录结果如下:

├── conf
│   └── config.yml
├── cookie
├── deps
├── docker
├── docs
├── environment
├── LICENSE
├── package_rpm_offline.sh
├── package_rpm_online.sh
├── package.sh
├── README.md
├── republish.sh
├── script
│   ├── deploy
    ├── server
    │   ├── clear_exclude
    │   ├── clear.sh
    │   ├── deploy_exclude
    │   ├── init.sh
    │   ├── sysom_api
    │   ├── sysom_channel
    │   ├── sysom_demo                 => 新增 demo 微服务的部署脚本
    │   ├── sysom_diagnosis
    │   ├── sysom_hotfix
    │   ├── sysom_hotfix_builder
    │   ├── sysom_migration
    │   ├── sysom_monitor_server
    │   ├── sysom_vmcore
    │   └── sysom_vul
│   ├── sysom_clear.sh
│   ├── sysom_create.sh
│   ├── sysom_dbmigrate.sh
│   ├── sysom_deploy.sh
│   └── sysom.sh
├── sysom_server
│   ├── clear_exclude
│   ├── conf
│   ├── deploy_exclude
│   ├── sysom_api
│   ├── sysom_channel
│   ├── sysom_demo                      => 新增 demo 微服务目录
│   ├── sysom_diagnosis
│   ├── sysom_hotfix
│   ├── sysom_hotfix_builder
│   ├── sysom_migration
│   ├── sysom_monitor_server
│   ├── sysom_vmcore
│   └── sysom_vul
├── sysom_web
├── template

2. fastapi_helper

fastapi_helper 对使用 fastapi 编写微服务的一些常用操作进行了封装,可以提供如下开箱即用的能力:

  • 查询辅助(支持分页、排序、按字段过滤等等)
  • 响应辅助(支持快速返回标准 JSON 格式的响应数据)

2.1 查询辅助

由于 fastapi 框架下面并没有类似 Django REST Framework 这种开箱即用的支持分页、排序、过滤的封装,因此想要快速实现支持分页、排序、过滤的数据查询代码需要不少额外的工作,因此本节设计了 BaseQueryParams 辅助查询类,可以用于快速实现列表查询。使用示例如下:

2.1.1 基础使用

#######################################################################################
# model.py
#######################################################################################
from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.sql import func
from app.database import Base

class Person(Base):
    __tablename__ = "sys_person"
    id = Column(Integer, primary_key=True)
    name = Column(String(254), unique=True)
    age = Column(Integer)
    created_at = Column(DateTime(timezone=True), server_default=func.now())

#######################################################################################
# schemas.py
#######################################################################################
from pydantic import BaseModel
from datetime import datetime

class Person(BaseModel):
    id: int
    name: str
    age: int
    created_at: datetime

    class Config:
        orm_mode = True

#######################################################################################
# query.py
#######################################################################################
from typing import Optional
from app import models
from sysom_utils import BaseQueryParams


class QueryParams(BaseQueryParams):

    # 1. 指定要查询的模型(数据库模型)
    __modelclass__ = models.Person

    # 2. 定义排序字段(这边定义按创建时间降序,如果期望按时间升序,直接使用 "created_at" 即可)
    sort: str = "-created_at"

    # 3. 定义支持用于过滤的参数
    # - 支持在query参数里面使用 name=A 过滤名字为A的用户;
    # - 需要过滤出多个用户可以用逗号连接,比如 name=A,B
    # - Optional 表示这个过滤字段是可选的,不传不过滤
    name: Optional[str] = None
    # - 支持在query参数里面使用 age=18 过滤年龄为18的用户;
    # - 需要过滤出多个年龄的用户可以用逗号连接,比如 name=18,28
    # - Optional 表示这个过滤字段是可选的,不传不过滤
    age: Optional[str] = None


#######################################################################################
# crud.py
#######################################################################################
from typing import Optional, List
from sqlalchemy.orm import Session
from app import models, schemas, query

def get_person_list(db: Session, query_params: query.PersonQueryParams) -> List[models.Person]:
    return (
        query_params.get_query_exp(db)
        .all()
    )

#######################################################################################
# routes/person.py
#######################################################################################

from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.query import PersonQueryParams
from app.database import get_db
from app.crud import get_person_list
from app.schemas import Person
from sysom_utils import StandardListResponse


router = APIRouter()


@router.get("/list")
async def get_persons(
    query_params: PersonQueryParams = Depends(), db: Session = Depends(get_db)
):
    person_list = get_person_list(db, query_params)
    return StandardListResponse(person_list, schema_class=Person)

2.1.1 查询列表的同时获取条目总数

查询数据列表时,通常会设置过滤条件、排序、分页,获取总的数据数量应该是过滤之后的数量,此时可以通过如下方式同时获取到数据列表和总数:

#######################################################################################
# crud.py
#######################################################################################
from typing import Optional, List, Tuple
from sqlalchemy.orm import Session
from app import models, schemas, query

def get_person_list(db: Session, query_params: query.PersonQueryParams) -> Tuple[List[models.Person], int]:
    query_builder = query_params.get_query_builder(db)

    # 1. Get total count after apply filter
    query_builder.apply_filter()
    total_count = len(query_builder.build().all())

    # 2. Get alert list after apply sorter and paging
    alert_list = query_builder.apply_sorter().apply_paging().build().all()

    return alert_list, total_count

2.2 响应辅助

sysom_utils 设计了 StandardReponseStandardListResponse 分别代表对象类型的响应数据和列表类型的响应数据,建议使用这两个响应对象封装 JSON 格式的响应数据。

2.2.1 StandardReponse 使用示例

from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.database import get_db
from app.crud import get_person_by_name
from app.schemas import Person
from sysom_utils import StandardResponse


router = APIRouter()


@router.get("/get")
async def get_specific_person(
    person_name: str, db: Session = Depends(get_db)
):
    person = get_person_by_name(db, person_name)
    return StandardResponse(person, schema_class=Person)

#######################################################################################
# 响应数据的格式如下
# {
#   "code": 200,            => 状态码200 表示成功500表示服务端错误
#   "message": "",          => 错误信息,如果请求不成功这边可以存放错误
#   "data": {               => 字典类型的响应数据
#     "id": 1,
#     "name": "test",
#     "age": 18
#   }
# }
#
# 成功响应实例 => StandardResponse.success(data=person, schema_class=Person) 或者 StandardResponse(code=200, data=person, schema_class=Person)
# 错误响应实例 => StandardResponse.error(message="Username not exists") 或者 StandardResponse(code=500, message="Username not exists")
#######################################################################################

2.2.1 StandardListReponse 使用示例

from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.query import PersonQueryParams
from app.database import get_db
from app.crud import get_person_list
from app.schemas import Person
from sysom_utils import StandardListResponse


router = APIRouter()


@router.get("/list")
async def get_persons(
    query_params: PersonQueryParams = Depends(), db: Session = Depends(get_db)
):
    person_list = get_person_list(db, query_params)
    return StandardListResponse(person_list, schema_class=Person)

#######################################################################################
# 响应数据的格式如下
# {
#   "code": 200,            => 状态码200 表示成功500表示服务端错误
#   "message": "",          => 错误信息,如果请求不成功这边可以存放错误
#   "data": [               => 列表类型的响应数据
#    {
#      "id": 1,
#      "name": "test",
#      "age": 18
#    }
#   ]
# }
#
# 成功响应实例 => StandardResponse.success(data=person_list, schema_class=Person) 或者 StandardResponse(code=200, data=person_list, schema_class=Person)
# 错误响应实例 => StandardResponse.error(message="Username not exists") 或者 StandardResponse(code=500, message="Username not exists")
#######################################################################################