RestFul风格代码库 ********************************************** 以drf代码为参考基线, 路由命名参考flask文档的说明: https://flask.palletsprojects.com/en/2.3.x/views/#method-dispatching-and-apis 技术栈: * drf: https://www.django-rest-framework.org/ * go-zero: https://go-zero.dev/docs/tasks/cli/api-demo 完整版: 在bilibili-api各框架下examples案例的project目录下 * `go-zero `_ * `flask `_ * `fastapi `_ 项目工程结构 ================================ .. tabs:: .. tab:: Go-Zero .. code-block:: text project/app ; go-zero创建的api服务 model ; gorm模型 scripts/ddl ; sql脚本 gencode/configuration.go ; 生成代码脚本 tests ; 接口测试 go.mod 模型 ================================ .. tabs:: .. tab:: Drf .. tabs:: .. tab:: models.py .. literalinclude:: /../../apiCode/restful/model/models.py .. tab:: gen.py .. literalinclude:: /../../apiCode/restful/model/gen.py .. tab:: Flask .. tabs:: .. tab:: models.py .. literalinclude:: /../../apiCode/restful/model/flask/models.py .. tab:: exts.py .. literalinclude:: /../../apiCode/restful/model/flask/exts.py .. tab:: Go-zero 集成gorm库 model/story.go .. literalinclude:: /../../apiCode/restful/model/story.go :language: go model/author.gen.go .. literalinclude:: /../../apiCode/restful/model/author.gen.go :language: go author.sql .. literalinclude:: /../../apiCode/restful/model/author.sql :language: mysql 视图 ================================ .. tabs:: .. tab:: Drf application/views.py .. literalinclude:: /../../Api/django/examples/project/application/views.py .. tab:: Flask .. literalinclude:: /../../apiCode/restful/view/flask/views.py .. tab:: Go-Zero .. tabs:: .. tab:: Create .. literalinclude:: /../../apiCode/restful/view/gozero/createstorylogic.go :language: go 列表查询 ---------------------------------------- .. tabs:: .. tab:: fastapi 简单模型查询 :file:`app/api/routes/users.py` .. tabs:: .. code-tab:: python SqlAlchemy from sqlalchemy import func, select @router.get( "/", dependencies=[Depends(get_current_active_superuser)], response_model=UsersPublic, ) def read_users(session: SessionDep, skip: int = 0, limit: int = 100) -> Any: """Retrieve users.""" count_statement = select(func.count()).select_from(User) count = session.execute(count_statement).scalar_one() statement = select(User).offset(skip).limit(limit) users = session.execute(statement).scalars().all() return UsersPublic(data=users, count=count) 指定返回字段,在多字段模型下能提高查询性能 :file:`app/api/routes/items.py` .. code-block:: python from sqlalchemy import func, select from sqlalchemy.orm import load_only @router.get("/", response_model=ItemsPublic) def read_items( session: SessionDep, current_user: CurrentUser, skip: int = 0, limit: int = 100 ) -> Any: """获取项目列表""" if current_user.is_superuser: count_statement = select(func.count()).select_from(Item) # 手工指定字段数据返回 statement = select( Item.id, Item.owner_id, Item.title, Item.description ).offset(skip).limit(limit) items: list[dict] = session.execute(statement).mappings().all() else: count_statement = ( select(func.count()) .select_from(Item) .where(Item.owner_id == current_user.id) ) # 方式二: https://docs.sqlalchemy.org/en/20/orm/queryguide/columns.html#using-load-only-to-reduce-loaded-columns statement = ( select(Item) .options(load_only( Item.id, Item.owner_id, Item.title, Item.description )) .where(Item.owner_id == current_user.id) .offset(skip) .limit(limit) ) items: list[Item] = session.execute(statement).scalars().all() count = session.execute(count_statement).scalar_one() return ItemsPublic(data=items, count=count) .. tab:: GoZero :file:`app/api/internal/logic/liststorieslogic.go` .. code-block:: go func (l *ListStoriesLogic) ListStories(req *types.ListStoriesReq) (resp *types.ListStoriesResp, err error) { resp = new(types.ListStoriesResp) Story := l.svcCtx.Model.Story var conds []gen.Condition if req.KeyWord != "" { keyword := "%" + req.KeyWord + "%" conds = append(conds, Story.Title.Like(keyword)) } count, err := Story.WithContext(l.ctx).Where(conds...).Count() // 返回数据 if err != nil { return nil, errors.New("查询失败: " + err.Error()) } result, err := Story.WithContext(l.ctx).Preload(Story.Author).Where(conds...).Offset(req.Skip).Limit(req.Limit).Find() if err != nil { return nil, errors.New("获取故事列表失败: " + err.Error()) } list := make([]types.RetrieveStoryResp, len(result)) for i, story := range result { list[i] = types.RetrieveStoryResp{ Id: story.ID, Title: story.Title, UserId: int64(story.AuthorID), UserName: story.Author.FirstName + story.Author.LastName, } } resp.Data = list resp.Total = int(count) return } 路由 ================================ .. tabs:: .. tab:: Drf project/router.py .. literalinclude:: /../../apiCode/restful/router/router.py .. tab:: Flask .. literalinclude:: /../../apiCode/restful/router/flask/urls.py .. tab:: go-zero 不用管, goctl会自动处理 测试 ================================= .. tabs:: .. tab:: Drf application/tests.py .. literalinclude:: /../../apiCode/restful/test/tests.py .. tab:: Flask .. tabs:: .. tab:: test_views.py .. literalinclude:: /../../apiCode/restful/test/flask/test_views.py .. tab:: conftest.py .. literalinclude:: /../../apiCode/restful/test/flask/conftest.py .. tab:: Go-Zero :file:`app/internal/logic/liststorieslogic_test.go` .. literalinclude:: test/liststorieslogic_test.go :language: go .. group-tab:: FastApi :file:`app/tests/api/routes/test_users.py` .. code-block:: python from fastapi.testclient import TestClient def test_retrieve_users( client: TestClient, superuser_token_headers: dict[str, str], db: Session ) -> None: username = random_email() password = random_lower_string() user_in = UserCreate(email=username, password=password) crud.create_user(session=db, user_create=user_in) username2 = random_email() password2 = random_lower_string() user_in2 = UserCreate(email=username2, password=password2) crud.create_user(session=db, user_create=user_in2) r = client.get(f"{settings.API_V1_STR}/users/", headers=superuser_token_headers) all_users = r.json() assert len(all_users["data"]) > 1 assert "count" in all_users for item in all_users["data"]: assert "email" in item .. tip:: create接口的测试思路参考drf的 `example `_ * 验证状态码 * 用模型查询表的数量 -> 从返回值获取id, 然后用模型查id * 获取表的第一条数据,验证名称 swagger ====================================== .. tabs:: .. tab:: Go-Zero https://github.com/zeromicro/goctl-swagger 示例代码待补充 .. error:: 已知问题: https://github.com/zeromicro/goctl-swagger/issues/101