zWebApi


NamezWebApi JSON
Version 0.11.1 PyPI version JSON
download
home_pageNone
Summary一个功能丰富、开箱即用的 Python Web 框架,基于 FastAPI 构建。
upload_time2025-07-31 09:29:25
maintainerNone
docs_urlNone
authorNone
requires_python>=3.8
licenseMIT
keywords zwebapi fastapi framework web api
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # zWebApi
一个功能丰富、开箱即用的 Python Web 框架,基于 FastAPI 构建。它旨在通过约定优于配置的原则,简化 API 开发流程,提供自动路由、统一异常处理、日志记录和可扩展工具集。

![](https://badge.fury.io/py/myframework.svg)  
![](https://img.shields.io/badge/License-MIT-yellow.svg)

## 目录
+ [特性](#特性)
+ [安装](#安装)
+ [快速开始](#快速开始)
    - [1. 项目结构](#1-项目结构)
    - [2. 创建应用 (](#2-创建应用-mainpy)`main.py`[)](#2-创建应用-mainpy)
    - [3. 定义路由 (](#3-定义路由-action)`action/`[)](#3-定义路由-action)
    - [4. 运行应用](#4-运行应用)
+ [核心概念](#核心概念)
    - [应用创建与配置](#应用创建与配置)
    - [路由自动注册](#路由自动注册)
    - [路由函数签名规范](#路由函数签名规范)
    - [统一响应与异常处理](#统一响应与异常处理)
        * [全局异常处理](#全局异常处理)
        * [自定义 ](#自定义-panic-异常)`Panic`[ 异常](#自定义-panic-异常)
    - [日志记录](#日志记录)
    - [工具模块](#工具模块)
+ [高级用法](#高级用法)
    - [CORS 配置](#cors-配置)
    - [使用框架日志](#使用框架日志)
+ [API 文档](#api-文档)
+ [贡献](#贡献)
+ [许可证](#许可证)

## 特性
+ **🚀**** 快速启动**: 基于 FastAPI 和 Uvicorn,提供异步高性能。
+ **🧭**** 自动路由注册**: 只需按约定在 `action/` 目录下组织代码,路由自动生效。
+ **🔒**** 路由签名强制**: 确保所有路由函数遵循统一的 `query`/`body` 参数规范。
+ **🛡️**** 统一异常处理**: 全局捕获异常,返回格式统一的 JSON 错误响应。
+ **🚨**** 自定义 **`Panic`** 异常**: 简单易用的自定义异常类,用于主动返回错误。
+ **📋**** 全面日志记录**: 自动记录应用启动、路由、异常等信息,支持文件和控制台输出,并提供日志查看接口。
+ **🛠️**** 可扩展工具模块**: 通过 `zWebApi.tools.*` 轻松访问和扩展框架功能。
+ **🌐**** CORS 支持**: 内置 CORS 中间件,轻松配置跨域资源共享。
+ **📦**** 易于打包和分发**: 标准 Python 包,可通过 `pip` 安装。

## 安装
```bash
pip install zWebApi
pip install zWebApi -i https://pypi.tuna.tsinghua.edu.cn/simple/
```

## 打包
```bash
python -m build
twine upload dist/*
```

## 快速开始
### 1. 项目结构
创建一个符合框架约定的项目目录结构:

```plain
my_project/
├── main.py              # 应用入口点
├── action/              # 路由定义目录 (必须)
│   └── user/            # 模块目录 (例如 'user')
│       └── user.py      # 路由文件 (必须与模块目录同名)
├── domain/              # 业务逻辑层 (可选,但推荐)
├── dao/                 # 数据访问层 (可选,但推荐)
├── utils/               # 项目通用工具 (可选)
└── zwebApi.log      # (运行后自动生成) 日志文件
```

### 2. 创建应用 (`main.py`)
这是你应用的入口文件。

```python
# main.py
from zWebApi import create_app

# 创建应用实例,并设置 API 标题和基础路径前缀
app = create_app(title="我的酷炫API")

# --- 可选:添加自定义中间件或配置 ---
# from fastapi.middleware import Middleware
# app.add_middleware(SomeMiddleware)

if __name__ == "__main__":
    # 使用框架封装的 run 方法启动服务器
    # 等效于 uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
    app.run(host="0.0.0.0", port=8000, reload=True) # reload=True 适用于开发环境
```

### 3. 定义路由 (`action/`)
在 `action/` 目录下创建模块和路由文件。

**创建 **`action/user/user.py`

```python
# action/user/user.py
# 导入自定义异常
from zWebApi import zRouter, Panic
# 导入校验参数
from schema.user import (
    UserQueryParams,
    UserCreate,
    UserResponse
)

# 必须创建一个 APIRouter 实例,变量名必须为 'router'
router = zRouter(tags=["用户管理"]) # tags 用于 API 文档分组

# --- 定义路由处理函数 ---
# 注意:函数签名必须遵循规范,只使用 'query' 和 'body' 作为参数名,
# 且它们必须是 Pydantic BaseModel 或 None。

@router.get("/info")
async def get_user_info(query: UserQueryParams = None):
    """获取用户信息"""
    if query and query.user_id:
        # 模拟业务逻辑
        if query.user_id == 999:
            # 使用 Panic 异常返回自定义错误
            raise Panic(code=404, msg="用户未找到", error="请求的用户ID不存在。")
        return {
            "user_id": query.user_id,
            "name": f"User_{query.user_id}",
            "filter_used": query.name_filter
        }
    return {"message": "请提供 user_id 查询参数"}

@router.post("/create")
async def create_user(body: UserCreate = None):
    """创建新用户"""
    if body is None:
        # 使用 Panic 异常返回错误
        raise Panic(code=400, msg="请求体缺失", error="创建用户必须提供请求体。")
    
    # 模拟创建用户
    new_user = UserResponse(id=123, name=body.name, age=body.age)
    return {"message": "用户创建成功", "user": new_user}

```

在`schema/`目录下创建校验参数

**创建 **`schema/user.py`

```python
# --- 定义 Pydantic 模型用于参数校验 ---
from pydantic import BaseModel
from typing import Optional

# 查询参数模型
class UserQueryParams(BaseModel):
    user_id: Optional[int] = None
    name_filter: Optional[str] = None

# 请求体模型
class UserCreate(BaseModel):
    name: str
    age: int

# 响应体模型 (可选但推荐)
class UserResponse(BaseModel):
    id: int
    name: str
    age: int
```

### 4. 运行应用
在你的项目根目录 (`your_project/`) 下打开终端,运行:

```bash
python main.py
```

应用将在 `http://0.0.0.0:8000` 启动。

+ **API 根路径**: `http://localhost:8000/`
+ **用户模块路径**: `http://localhost:8000/我的酷炫api/user/...`
+ **API 文档**: `http://localhost:8000/docs`
+ **日志查看**: `http://localhost:8000/我的酷炫api/api/error/logs`

## 核心概念
### 应用创建与配置
使用 `zWebApi.create_app` 工厂函数创建 FastAPI 应用实例。

```python
app = create_app(
    title="My App",                    # API 标题,也用作基础路径前缀
    enable_cors=True,                  # 是否启用 CORS
    cors_origins=["*"],                # CORS 允许的源
    cors_allow_credentials=True,
    cors_allow_methods=["*"],
    cors_allow_headers=["*"]
)
```

### 路由自动注册
框架启动时会自动扫描 `action/` 目录。

+ 遍历 `action/` 下的每个子目录(如 `user/`)。
+ 在每个子目录中查找与目录同名的 Python 文件(如 `user.py`)。
+ 导入该文件,并查找名为 `router` 的 `APIRouter` 实例。
+ 将该 `router` 挂载到以目录名(如 `/user`)为前缀的路径下。
+ 最终的基础路径是 `/title` (title 转为小写并用下划线替换空格)。

### 路由函数签名规范
为了保持一致性并利用框架的校验功能,路由处理函数 **必须** 遵循以下签名:

+ **只接受** `query` 和 `body` 两个命名参数。
+ **参数类型** 必须是 Pydantic `BaseModel` 的子类或 `None`。
+ **默认值** 应为 `None` 以使其成为可选参数。
+ **参数名** 必须严格是 `query` 和/或 `body`。

**正确示例:**

```python
# 只有 query
async def get_items(query: ItemQueryParams = None): ...

# 只有 body
async def create_item(body: CreateItemRequest = None): ...

# 两者都有
async def update_item(query: UpdateQuery = None, body: UpdateItemRequest = None): ...
```

**错误示例 (会导致应用启动失败):**

```python
# 错误:使用了不允许的参数名 'item_id'
async def get_item(item_id: int): ...

# 错误:没有使用 BaseModel
async def search(name: str = ""): ...
```

### 统一响应与异常处理
#### 全局异常处理
框架自动注册了多个全局异常处理器,确保所有错误都返回统一的 JSON 格式:

```json
{
  "code": 400,
  "msg": "错误的请求",
  "error": "具体错误信息...",
  "data": null
}
```

+ `HTTPException`: 处理 FastAPI/Starlette 抛出的 HTTP 异常 (如 404, 403)。
+ `RequestValidationError`: 处理 Pydantic 模型校验失败 (422)。
+ `Exception`: 捕获所有未处理的服务器内部错误 (500)。
+ `Panic`: 处理用户自定义的 `Panic` 异常。

#### 自定义 `Panic` 异常
用户可以在任何地方(路由、domain、dao)主动抛出 `Panic` 来返回自定义错误。

```python
from zWebApi import Panic

# 在路由、服务或数据访问层
def some_business_logic(user_id):
    if user_id <= 0:
        # 主动抛出 Panic 异常
        raise Panic(
            code=400,                    # HTTP 状态码和业务码
            msg="无效的用户ID",           # 用户友好信息
            error="用户ID必须是正整数。", # 技术错误详情
            data={"provided_id": user_id} # 可选的附加数据
        )
```

### 日志记录
框架使用 Python `logging` 模块提供全面的日志功能。

+ **格式**: `[级别][年月日时分秒][文件名][行号]: 消息`
    - 例如: `[INFO][20240521180000][app.py][150]: 应用创建完成。`
    - 例如: `[ERROR][20240521180001][user.py][30]: 无效的用户ID`
+ **输出**: 同时记录到控制台(开发)和项目根目录下的 `weblog.log` 文件。
+ **轮转**: 使用 `TimedRotatingFileHandler`,默认每10天轮转一次日志文件。
+ **查看**: 提供内置 API 接口 `GET /<title>/api/error/logs` 查看日志内容。
    - 可通过 `?lines=N` 参数指定返回最后 N 行。

### 工具模块
框架提供了一个可扩展的 `tools` 包,用于存放通用功能模块。

**导入方式:**

```python
# 从框架内置工具导入
from zWebApi.tools.db.mysql import testsql, MySQLHelper

# 未来可扩展
# from zWebApi.tools.cache.redis_client import RedisManager
```

**创建自定义工具:**

在框架源码的 `src/zWebApi/tools/` 下创建新的子目录和 `.py` 文件即可。用户安装更新后的包即可使用。

## 高级用法
### CORS 配置
在 `create_app` 时配置 CORS:

```python
app = create_app(
    title="API",
    enable_cors=True,
    cors_origins=["http://localhost:3000", "https://myfrontend.com "],
    cors_allow_credentials=True,
    cors_allow_methods=["GET", "POST", "PUT", "DELETE"],
    cors_allow_headers=["*"],
)
```

### 使用框架日志
在你的项目代码中,可以使用框架配置好的日志记录器:

```python
# 在你的 action, domain, dao 等模块中
from zWebApi import get_logger

logger = get_logger()

@router.get("/some-path")
async def my_endpoint():
    logger.info("处理 /some-path 请求")
    try:
        # ... 业务逻辑 ...
        logger.debug("业务逻辑执行成功")
        return {"result": "ok"}
    except Exception as e:
        logger.error(f"处理请求时出错: {e}", exc_info=True)
        raise # 让全局异常处理器捕获
```

## API 文档
框架完全兼容 FastAPI 的自动生成文档功能。

+ **Swagger UI**: `http://<your-host>:<port>/docs`
+ **ReDoc**: `http://<your-host>:<port>/redoc`

## 贡献
欢迎提交 Issue 和 Pull Request!

1. Fork 本仓库
2. 创建你的特性分支 (`git checkout -b feature/AmazingFeature`)
3. 提交你的更改 (`git commit -m 'Add some AmazingFeature'`)
4. 推送到分支 (`git push origin feature/AmazingFeature`)
5. 开启一个 Pull Request

## 许可证
本项目采用 MIT 许可证。详情请见 [LICENSE](LICENSE) 文件。


            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "zWebApi",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": "zWebApi, fastapi, framework, web, api",
    "author": null,
    "author_email": "Fatosy <fatosy@163.com>",
    "download_url": "https://files.pythonhosted.org/packages/30/c8/266e04334cc24eb3926076b0d4d37afbf1ae4f1eccb8676d6b7b793702da/zwebapi-0.11.1.tar.gz",
    "platform": null,
    "description": "# zWebApi\n\u4e00\u4e2a\u529f\u80fd\u4e30\u5bcc\u3001\u5f00\u7bb1\u5373\u7528\u7684 Python Web \u6846\u67b6\uff0c\u57fa\u4e8e FastAPI \u6784\u5efa\u3002\u5b83\u65e8\u5728\u901a\u8fc7\u7ea6\u5b9a\u4f18\u4e8e\u914d\u7f6e\u7684\u539f\u5219\uff0c\u7b80\u5316 API \u5f00\u53d1\u6d41\u7a0b\uff0c\u63d0\u4f9b\u81ea\u52a8\u8def\u7531\u3001\u7edf\u4e00\u5f02\u5e38\u5904\u7406\u3001\u65e5\u5fd7\u8bb0\u5f55\u548c\u53ef\u6269\u5c55\u5de5\u5177\u96c6\u3002\n\n![](https://badge.fury.io/py/myframework.svg)  \n![](https://img.shields.io/badge/License-MIT-yellow.svg)\n\n## \u76ee\u5f55\n+ [\u7279\u6027](#\u7279\u6027)\n+ [\u5b89\u88c5](#\u5b89\u88c5)\n+ [\u5feb\u901f\u5f00\u59cb](#\u5feb\u901f\u5f00\u59cb)\n    - [1. \u9879\u76ee\u7ed3\u6784](#1-\u9879\u76ee\u7ed3\u6784)\n    - [2. \u521b\u5efa\u5e94\u7528 (](#2-\u521b\u5efa\u5e94\u7528-mainpy)`main.py`[)](#2-\u521b\u5efa\u5e94\u7528-mainpy)\n    - [3. \u5b9a\u4e49\u8def\u7531 (](#3-\u5b9a\u4e49\u8def\u7531-action)`action/`[)](#3-\u5b9a\u4e49\u8def\u7531-action)\n    - [4. \u8fd0\u884c\u5e94\u7528](#4-\u8fd0\u884c\u5e94\u7528)\n+ [\u6838\u5fc3\u6982\u5ff5](#\u6838\u5fc3\u6982\u5ff5)\n    - [\u5e94\u7528\u521b\u5efa\u4e0e\u914d\u7f6e](#\u5e94\u7528\u521b\u5efa\u4e0e\u914d\u7f6e)\n    - [\u8def\u7531\u81ea\u52a8\u6ce8\u518c](#\u8def\u7531\u81ea\u52a8\u6ce8\u518c)\n    - [\u8def\u7531\u51fd\u6570\u7b7e\u540d\u89c4\u8303](#\u8def\u7531\u51fd\u6570\u7b7e\u540d\u89c4\u8303)\n    - [\u7edf\u4e00\u54cd\u5e94\u4e0e\u5f02\u5e38\u5904\u7406](#\u7edf\u4e00\u54cd\u5e94\u4e0e\u5f02\u5e38\u5904\u7406)\n        * [\u5168\u5c40\u5f02\u5e38\u5904\u7406](#\u5168\u5c40\u5f02\u5e38\u5904\u7406)\n        * [\u81ea\u5b9a\u4e49 ](#\u81ea\u5b9a\u4e49-panic-\u5f02\u5e38)`Panic`[ \u5f02\u5e38](#\u81ea\u5b9a\u4e49-panic-\u5f02\u5e38)\n    - [\u65e5\u5fd7\u8bb0\u5f55](#\u65e5\u5fd7\u8bb0\u5f55)\n    - [\u5de5\u5177\u6a21\u5757](#\u5de5\u5177\u6a21\u5757)\n+ [\u9ad8\u7ea7\u7528\u6cd5](#\u9ad8\u7ea7\u7528\u6cd5)\n    - [CORS \u914d\u7f6e](#cors-\u914d\u7f6e)\n    - [\u4f7f\u7528\u6846\u67b6\u65e5\u5fd7](#\u4f7f\u7528\u6846\u67b6\u65e5\u5fd7)\n+ [API \u6587\u6863](#api-\u6587\u6863)\n+ [\u8d21\u732e](#\u8d21\u732e)\n+ [\u8bb8\u53ef\u8bc1](#\u8bb8\u53ef\u8bc1)\n\n## \u7279\u6027\n+ **\ud83d\ude80**** \u5feb\u901f\u542f\u52a8**: \u57fa\u4e8e FastAPI \u548c Uvicorn\uff0c\u63d0\u4f9b\u5f02\u6b65\u9ad8\u6027\u80fd\u3002\n+ **\ud83e\udded**** \u81ea\u52a8\u8def\u7531\u6ce8\u518c**: \u53ea\u9700\u6309\u7ea6\u5b9a\u5728 `action/` \u76ee\u5f55\u4e0b\u7ec4\u7ec7\u4ee3\u7801\uff0c\u8def\u7531\u81ea\u52a8\u751f\u6548\u3002\n+ **\ud83d\udd12**** \u8def\u7531\u7b7e\u540d\u5f3a\u5236**: \u786e\u4fdd\u6240\u6709\u8def\u7531\u51fd\u6570\u9075\u5faa\u7edf\u4e00\u7684 `query`/`body` \u53c2\u6570\u89c4\u8303\u3002\n+ **\ud83d\udee1\ufe0f**** \u7edf\u4e00\u5f02\u5e38\u5904\u7406**: \u5168\u5c40\u6355\u83b7\u5f02\u5e38\uff0c\u8fd4\u56de\u683c\u5f0f\u7edf\u4e00\u7684 JSON \u9519\u8bef\u54cd\u5e94\u3002\n+ **\ud83d\udea8**** \u81ea\u5b9a\u4e49 **`Panic`** \u5f02\u5e38**: \u7b80\u5355\u6613\u7528\u7684\u81ea\u5b9a\u4e49\u5f02\u5e38\u7c7b\uff0c\u7528\u4e8e\u4e3b\u52a8\u8fd4\u56de\u9519\u8bef\u3002\n+ **\ud83d\udccb**** \u5168\u9762\u65e5\u5fd7\u8bb0\u5f55**: \u81ea\u52a8\u8bb0\u5f55\u5e94\u7528\u542f\u52a8\u3001\u8def\u7531\u3001\u5f02\u5e38\u7b49\u4fe1\u606f\uff0c\u652f\u6301\u6587\u4ef6\u548c\u63a7\u5236\u53f0\u8f93\u51fa\uff0c\u5e76\u63d0\u4f9b\u65e5\u5fd7\u67e5\u770b\u63a5\u53e3\u3002\n+ **\ud83d\udee0\ufe0f**** \u53ef\u6269\u5c55\u5de5\u5177\u6a21\u5757**: \u901a\u8fc7 `zWebApi.tools.*` \u8f7b\u677e\u8bbf\u95ee\u548c\u6269\u5c55\u6846\u67b6\u529f\u80fd\u3002\n+ **\ud83c\udf10**** CORS \u652f\u6301**: \u5185\u7f6e CORS \u4e2d\u95f4\u4ef6\uff0c\u8f7b\u677e\u914d\u7f6e\u8de8\u57df\u8d44\u6e90\u5171\u4eab\u3002\n+ **\ud83d\udce6**** \u6613\u4e8e\u6253\u5305\u548c\u5206\u53d1**: \u6807\u51c6 Python \u5305\uff0c\u53ef\u901a\u8fc7 `pip` \u5b89\u88c5\u3002\n\n## \u5b89\u88c5\n```bash\npip install zWebApi\npip install zWebApi -i https://pypi.tuna.tsinghua.edu.cn/simple/\n```\n\n## \u6253\u5305\n```bash\npython -m build\ntwine upload dist/*\n```\n\n## \u5feb\u901f\u5f00\u59cb\n### 1. \u9879\u76ee\u7ed3\u6784\n\u521b\u5efa\u4e00\u4e2a\u7b26\u5408\u6846\u67b6\u7ea6\u5b9a\u7684\u9879\u76ee\u76ee\u5f55\u7ed3\u6784\uff1a\n\n```plain\nmy_project/\n\u251c\u2500\u2500 main.py              # \u5e94\u7528\u5165\u53e3\u70b9\n\u251c\u2500\u2500 action/              # \u8def\u7531\u5b9a\u4e49\u76ee\u5f55 (\u5fc5\u987b)\n\u2502   \u2514\u2500\u2500 user/            # \u6a21\u5757\u76ee\u5f55 (\u4f8b\u5982 'user')\n\u2502       \u2514\u2500\u2500 user.py      # \u8def\u7531\u6587\u4ef6 (\u5fc5\u987b\u4e0e\u6a21\u5757\u76ee\u5f55\u540c\u540d)\n\u251c\u2500\u2500 domain/              # \u4e1a\u52a1\u903b\u8f91\u5c42 (\u53ef\u9009\uff0c\u4f46\u63a8\u8350)\n\u251c\u2500\u2500 dao/                 # \u6570\u636e\u8bbf\u95ee\u5c42 (\u53ef\u9009\uff0c\u4f46\u63a8\u8350)\n\u251c\u2500\u2500 utils/               # \u9879\u76ee\u901a\u7528\u5de5\u5177 (\u53ef\u9009)\n\u2514\u2500\u2500 zwebApi.log      # (\u8fd0\u884c\u540e\u81ea\u52a8\u751f\u6210) \u65e5\u5fd7\u6587\u4ef6\n```\n\n### 2. \u521b\u5efa\u5e94\u7528 (`main.py`)\n\u8fd9\u662f\u4f60\u5e94\u7528\u7684\u5165\u53e3\u6587\u4ef6\u3002\n\n```python\n# main.py\nfrom zWebApi import create_app\n\n# \u521b\u5efa\u5e94\u7528\u5b9e\u4f8b\uff0c\u5e76\u8bbe\u7f6e API \u6807\u9898\u548c\u57fa\u7840\u8def\u5f84\u524d\u7f00\napp = create_app(title=\"\u6211\u7684\u9177\u70abAPI\")\n\n# --- \u53ef\u9009\uff1a\u6dfb\u52a0\u81ea\u5b9a\u4e49\u4e2d\u95f4\u4ef6\u6216\u914d\u7f6e ---\n# from fastapi.middleware import Middleware\n# app.add_middleware(SomeMiddleware)\n\nif __name__ == \"__main__\":\n    # \u4f7f\u7528\u6846\u67b6\u5c01\u88c5\u7684 run \u65b9\u6cd5\u542f\u52a8\u670d\u52a1\u5668\n    # \u7b49\u6548\u4e8e uvicorn.run(\"main:app\", host=\"0.0.0.0\", port=8000, reload=True)\n    app.run(host=\"0.0.0.0\", port=8000, reload=True) # reload=True \u9002\u7528\u4e8e\u5f00\u53d1\u73af\u5883\n```\n\n### 3. \u5b9a\u4e49\u8def\u7531 (`action/`)\n\u5728 `action/` \u76ee\u5f55\u4e0b\u521b\u5efa\u6a21\u5757\u548c\u8def\u7531\u6587\u4ef6\u3002\n\n**\u521b\u5efa **`action/user/user.py`\n\n```python\n# action/user/user.py\n# \u5bfc\u5165\u81ea\u5b9a\u4e49\u5f02\u5e38\nfrom zWebApi import zRouter, Panic\n# \u5bfc\u5165\u6821\u9a8c\u53c2\u6570\nfrom schema.user import (\n    UserQueryParams,\n    UserCreate,\n    UserResponse\n)\n\n# \u5fc5\u987b\u521b\u5efa\u4e00\u4e2a APIRouter \u5b9e\u4f8b\uff0c\u53d8\u91cf\u540d\u5fc5\u987b\u4e3a 'router'\nrouter = zRouter(tags=[\"\u7528\u6237\u7ba1\u7406\"]) # tags \u7528\u4e8e API \u6587\u6863\u5206\u7ec4\n\n# --- \u5b9a\u4e49\u8def\u7531\u5904\u7406\u51fd\u6570 ---\n# \u6ce8\u610f\uff1a\u51fd\u6570\u7b7e\u540d\u5fc5\u987b\u9075\u5faa\u89c4\u8303\uff0c\u53ea\u4f7f\u7528 'query' \u548c 'body' \u4f5c\u4e3a\u53c2\u6570\u540d\uff0c\n# \u4e14\u5b83\u4eec\u5fc5\u987b\u662f Pydantic BaseModel \u6216 None\u3002\n\n@router.get(\"/info\")\nasync def get_user_info(query: UserQueryParams = None):\n    \"\"\"\u83b7\u53d6\u7528\u6237\u4fe1\u606f\"\"\"\n    if query and query.user_id:\n        # \u6a21\u62df\u4e1a\u52a1\u903b\u8f91\n        if query.user_id == 999:\n            # \u4f7f\u7528 Panic \u5f02\u5e38\u8fd4\u56de\u81ea\u5b9a\u4e49\u9519\u8bef\n            raise Panic(code=404, msg=\"\u7528\u6237\u672a\u627e\u5230\", error=\"\u8bf7\u6c42\u7684\u7528\u6237ID\u4e0d\u5b58\u5728\u3002\")\n        return {\n            \"user_id\": query.user_id,\n            \"name\": f\"User_{query.user_id}\",\n            \"filter_used\": query.name_filter\n        }\n    return {\"message\": \"\u8bf7\u63d0\u4f9b user_id \u67e5\u8be2\u53c2\u6570\"}\n\n@router.post(\"/create\")\nasync def create_user(body: UserCreate = None):\n    \"\"\"\u521b\u5efa\u65b0\u7528\u6237\"\"\"\n    if body is None:\n        # \u4f7f\u7528 Panic \u5f02\u5e38\u8fd4\u56de\u9519\u8bef\n        raise Panic(code=400, msg=\"\u8bf7\u6c42\u4f53\u7f3a\u5931\", error=\"\u521b\u5efa\u7528\u6237\u5fc5\u987b\u63d0\u4f9b\u8bf7\u6c42\u4f53\u3002\")\n    \n    # \u6a21\u62df\u521b\u5efa\u7528\u6237\n    new_user = UserResponse(id=123, name=body.name, age=body.age)\n    return {\"message\": \"\u7528\u6237\u521b\u5efa\u6210\u529f\", \"user\": new_user}\n\n```\n\n\u5728`schema/`\u76ee\u5f55\u4e0b\u521b\u5efa\u6821\u9a8c\u53c2\u6570\n\n**\u521b\u5efa **`schema/user.py`\n\n```python\n# --- \u5b9a\u4e49 Pydantic \u6a21\u578b\u7528\u4e8e\u53c2\u6570\u6821\u9a8c ---\nfrom pydantic import BaseModel\nfrom typing import Optional\n\n# \u67e5\u8be2\u53c2\u6570\u6a21\u578b\nclass UserQueryParams(BaseModel):\n    user_id: Optional[int] = None\n    name_filter: Optional[str] = None\n\n# \u8bf7\u6c42\u4f53\u6a21\u578b\nclass UserCreate(BaseModel):\n    name: str\n    age: int\n\n# \u54cd\u5e94\u4f53\u6a21\u578b (\u53ef\u9009\u4f46\u63a8\u8350)\nclass UserResponse(BaseModel):\n    id: int\n    name: str\n    age: int\n```\n\n### 4. \u8fd0\u884c\u5e94\u7528\n\u5728\u4f60\u7684\u9879\u76ee\u6839\u76ee\u5f55 (`your_project/`) \u4e0b\u6253\u5f00\u7ec8\u7aef\uff0c\u8fd0\u884c\uff1a\n\n```bash\npython main.py\n```\n\n\u5e94\u7528\u5c06\u5728 `http://0.0.0.0:8000` \u542f\u52a8\u3002\n\n+ **API \u6839\u8def\u5f84**: `http://localhost:8000/`\n+ **\u7528\u6237\u6a21\u5757\u8def\u5f84**: `http://localhost:8000/\u6211\u7684\u9177\u70abapi/user/...`\n+ **API \u6587\u6863**: `http://localhost:8000/docs`\n+ **\u65e5\u5fd7\u67e5\u770b**: `http://localhost:8000/\u6211\u7684\u9177\u70abapi/api/error/logs`\n\n## \u6838\u5fc3\u6982\u5ff5\n### \u5e94\u7528\u521b\u5efa\u4e0e\u914d\u7f6e\n\u4f7f\u7528 `zWebApi.create_app` \u5de5\u5382\u51fd\u6570\u521b\u5efa FastAPI \u5e94\u7528\u5b9e\u4f8b\u3002\n\n```python\napp = create_app(\n    title=\"My App\",                    # API \u6807\u9898\uff0c\u4e5f\u7528\u4f5c\u57fa\u7840\u8def\u5f84\u524d\u7f00\n    enable_cors=True,                  # \u662f\u5426\u542f\u7528 CORS\n    cors_origins=[\"*\"],                # CORS \u5141\u8bb8\u7684\u6e90\n    cors_allow_credentials=True,\n    cors_allow_methods=[\"*\"],\n    cors_allow_headers=[\"*\"]\n)\n```\n\n### \u8def\u7531\u81ea\u52a8\u6ce8\u518c\n\u6846\u67b6\u542f\u52a8\u65f6\u4f1a\u81ea\u52a8\u626b\u63cf `action/` \u76ee\u5f55\u3002\n\n+ \u904d\u5386 `action/` \u4e0b\u7684\u6bcf\u4e2a\u5b50\u76ee\u5f55\uff08\u5982 `user/`\uff09\u3002\n+ \u5728\u6bcf\u4e2a\u5b50\u76ee\u5f55\u4e2d\u67e5\u627e\u4e0e\u76ee\u5f55\u540c\u540d\u7684 Python \u6587\u4ef6\uff08\u5982 `user.py`\uff09\u3002\n+ \u5bfc\u5165\u8be5\u6587\u4ef6\uff0c\u5e76\u67e5\u627e\u540d\u4e3a `router` \u7684 `APIRouter` \u5b9e\u4f8b\u3002\n+ \u5c06\u8be5 `router` \u6302\u8f7d\u5230\u4ee5\u76ee\u5f55\u540d\uff08\u5982 `/user`\uff09\u4e3a\u524d\u7f00\u7684\u8def\u5f84\u4e0b\u3002\n+ \u6700\u7ec8\u7684\u57fa\u7840\u8def\u5f84\u662f `/title` (title \u8f6c\u4e3a\u5c0f\u5199\u5e76\u7528\u4e0b\u5212\u7ebf\u66ff\u6362\u7a7a\u683c)\u3002\n\n### \u8def\u7531\u51fd\u6570\u7b7e\u540d\u89c4\u8303\n\u4e3a\u4e86\u4fdd\u6301\u4e00\u81f4\u6027\u5e76\u5229\u7528\u6846\u67b6\u7684\u6821\u9a8c\u529f\u80fd\uff0c\u8def\u7531\u5904\u7406\u51fd\u6570 **\u5fc5\u987b** \u9075\u5faa\u4ee5\u4e0b\u7b7e\u540d\uff1a\n\n+ **\u53ea\u63a5\u53d7** `query` \u548c `body` \u4e24\u4e2a\u547d\u540d\u53c2\u6570\u3002\n+ **\u53c2\u6570\u7c7b\u578b** \u5fc5\u987b\u662f Pydantic `BaseModel` \u7684\u5b50\u7c7b\u6216 `None`\u3002\n+ **\u9ed8\u8ba4\u503c** \u5e94\u4e3a `None` \u4ee5\u4f7f\u5176\u6210\u4e3a\u53ef\u9009\u53c2\u6570\u3002\n+ **\u53c2\u6570\u540d** \u5fc5\u987b\u4e25\u683c\u662f `query` \u548c/\u6216 `body`\u3002\n\n**\u6b63\u786e\u793a\u4f8b:**\n\n```python\n# \u53ea\u6709 query\nasync def get_items(query: ItemQueryParams = None): ...\n\n# \u53ea\u6709 body\nasync def create_item(body: CreateItemRequest = None): ...\n\n# \u4e24\u8005\u90fd\u6709\nasync def update_item(query: UpdateQuery = None, body: UpdateItemRequest = None): ...\n```\n\n**\u9519\u8bef\u793a\u4f8b (\u4f1a\u5bfc\u81f4\u5e94\u7528\u542f\u52a8\u5931\u8d25):**\n\n```python\n# \u9519\u8bef\uff1a\u4f7f\u7528\u4e86\u4e0d\u5141\u8bb8\u7684\u53c2\u6570\u540d 'item_id'\nasync def get_item(item_id: int): ...\n\n# \u9519\u8bef\uff1a\u6ca1\u6709\u4f7f\u7528 BaseModel\nasync def search(name: str = \"\"): ...\n```\n\n### \u7edf\u4e00\u54cd\u5e94\u4e0e\u5f02\u5e38\u5904\u7406\n#### \u5168\u5c40\u5f02\u5e38\u5904\u7406\n\u6846\u67b6\u81ea\u52a8\u6ce8\u518c\u4e86\u591a\u4e2a\u5168\u5c40\u5f02\u5e38\u5904\u7406\u5668\uff0c\u786e\u4fdd\u6240\u6709\u9519\u8bef\u90fd\u8fd4\u56de\u7edf\u4e00\u7684 JSON \u683c\u5f0f\uff1a\n\n```json\n{\n  \"code\": 400,\n  \"msg\": \"\u9519\u8bef\u7684\u8bf7\u6c42\",\n  \"error\": \"\u5177\u4f53\u9519\u8bef\u4fe1\u606f...\",\n  \"data\": null\n}\n```\n\n+ `HTTPException`: \u5904\u7406 FastAPI/Starlette \u629b\u51fa\u7684 HTTP \u5f02\u5e38 (\u5982 404, 403)\u3002\n+ `RequestValidationError`: \u5904\u7406 Pydantic \u6a21\u578b\u6821\u9a8c\u5931\u8d25 (422)\u3002\n+ `Exception`: \u6355\u83b7\u6240\u6709\u672a\u5904\u7406\u7684\u670d\u52a1\u5668\u5185\u90e8\u9519\u8bef (500)\u3002\n+ `Panic`: \u5904\u7406\u7528\u6237\u81ea\u5b9a\u4e49\u7684 `Panic` \u5f02\u5e38\u3002\n\n#### \u81ea\u5b9a\u4e49 `Panic` \u5f02\u5e38\n\u7528\u6237\u53ef\u4ee5\u5728\u4efb\u4f55\u5730\u65b9\uff08\u8def\u7531\u3001domain\u3001dao\uff09\u4e3b\u52a8\u629b\u51fa `Panic` \u6765\u8fd4\u56de\u81ea\u5b9a\u4e49\u9519\u8bef\u3002\n\n```python\nfrom zWebApi import Panic\n\n# \u5728\u8def\u7531\u3001\u670d\u52a1\u6216\u6570\u636e\u8bbf\u95ee\u5c42\ndef some_business_logic(user_id):\n    if user_id <= 0:\n        # \u4e3b\u52a8\u629b\u51fa Panic \u5f02\u5e38\n        raise Panic(\n            code=400,                    # HTTP \u72b6\u6001\u7801\u548c\u4e1a\u52a1\u7801\n            msg=\"\u65e0\u6548\u7684\u7528\u6237ID\",           # \u7528\u6237\u53cb\u597d\u4fe1\u606f\n            error=\"\u7528\u6237ID\u5fc5\u987b\u662f\u6b63\u6574\u6570\u3002\", # \u6280\u672f\u9519\u8bef\u8be6\u60c5\n            data={\"provided_id\": user_id} # \u53ef\u9009\u7684\u9644\u52a0\u6570\u636e\n        )\n```\n\n### \u65e5\u5fd7\u8bb0\u5f55\n\u6846\u67b6\u4f7f\u7528 Python `logging` \u6a21\u5757\u63d0\u4f9b\u5168\u9762\u7684\u65e5\u5fd7\u529f\u80fd\u3002\n\n+ **\u683c\u5f0f**: `[\u7ea7\u522b][\u5e74\u6708\u65e5\u65f6\u5206\u79d2][\u6587\u4ef6\u540d][\u884c\u53f7]: \u6d88\u606f`\n    - \u4f8b\u5982: `[INFO][20240521180000][app.py][150]: \u5e94\u7528\u521b\u5efa\u5b8c\u6210\u3002`\n    - \u4f8b\u5982: `[ERROR][20240521180001][user.py][30]: \u65e0\u6548\u7684\u7528\u6237ID`\n+ **\u8f93\u51fa**: \u540c\u65f6\u8bb0\u5f55\u5230\u63a7\u5236\u53f0\uff08\u5f00\u53d1\uff09\u548c\u9879\u76ee\u6839\u76ee\u5f55\u4e0b\u7684 `weblog.log` \u6587\u4ef6\u3002\n+ **\u8f6e\u8f6c**: \u4f7f\u7528 `TimedRotatingFileHandler`\uff0c\u9ed8\u8ba4\u6bcf10\u5929\u8f6e\u8f6c\u4e00\u6b21\u65e5\u5fd7\u6587\u4ef6\u3002\n+ **\u67e5\u770b**: \u63d0\u4f9b\u5185\u7f6e API \u63a5\u53e3 `GET /<title>/api/error/logs` \u67e5\u770b\u65e5\u5fd7\u5185\u5bb9\u3002\n    - \u53ef\u901a\u8fc7 `?lines=N` \u53c2\u6570\u6307\u5b9a\u8fd4\u56de\u6700\u540e N \u884c\u3002\n\n### \u5de5\u5177\u6a21\u5757\n\u6846\u67b6\u63d0\u4f9b\u4e86\u4e00\u4e2a\u53ef\u6269\u5c55\u7684 `tools` \u5305\uff0c\u7528\u4e8e\u5b58\u653e\u901a\u7528\u529f\u80fd\u6a21\u5757\u3002\n\n**\u5bfc\u5165\u65b9\u5f0f:**\n\n```python\n# \u4ece\u6846\u67b6\u5185\u7f6e\u5de5\u5177\u5bfc\u5165\nfrom zWebApi.tools.db.mysql import testsql, MySQLHelper\n\n# \u672a\u6765\u53ef\u6269\u5c55\n# from zWebApi.tools.cache.redis_client import RedisManager\n```\n\n**\u521b\u5efa\u81ea\u5b9a\u4e49\u5de5\u5177:**\n\n\u5728\u6846\u67b6\u6e90\u7801\u7684 `src/zWebApi/tools/` \u4e0b\u521b\u5efa\u65b0\u7684\u5b50\u76ee\u5f55\u548c `.py` \u6587\u4ef6\u5373\u53ef\u3002\u7528\u6237\u5b89\u88c5\u66f4\u65b0\u540e\u7684\u5305\u5373\u53ef\u4f7f\u7528\u3002\n\n## \u9ad8\u7ea7\u7528\u6cd5\n### CORS \u914d\u7f6e\n\u5728 `create_app` \u65f6\u914d\u7f6e CORS\uff1a\n\n```python\napp = create_app(\n    title=\"API\",\n    enable_cors=True,\n    cors_origins=[\"http://localhost:3000\", \"https://myfrontend.com \"],\n    cors_allow_credentials=True,\n    cors_allow_methods=[\"GET\", \"POST\", \"PUT\", \"DELETE\"],\n    cors_allow_headers=[\"*\"],\n)\n```\n\n### \u4f7f\u7528\u6846\u67b6\u65e5\u5fd7\n\u5728\u4f60\u7684\u9879\u76ee\u4ee3\u7801\u4e2d\uff0c\u53ef\u4ee5\u4f7f\u7528\u6846\u67b6\u914d\u7f6e\u597d\u7684\u65e5\u5fd7\u8bb0\u5f55\u5668\uff1a\n\n```python\n# \u5728\u4f60\u7684 action, domain, dao \u7b49\u6a21\u5757\u4e2d\nfrom zWebApi import get_logger\n\nlogger = get_logger()\n\n@router.get(\"/some-path\")\nasync def my_endpoint():\n    logger.info(\"\u5904\u7406 /some-path \u8bf7\u6c42\")\n    try:\n        # ... \u4e1a\u52a1\u903b\u8f91 ...\n        logger.debug(\"\u4e1a\u52a1\u903b\u8f91\u6267\u884c\u6210\u529f\")\n        return {\"result\": \"ok\"}\n    except Exception as e:\n        logger.error(f\"\u5904\u7406\u8bf7\u6c42\u65f6\u51fa\u9519: {e}\", exc_info=True)\n        raise # \u8ba9\u5168\u5c40\u5f02\u5e38\u5904\u7406\u5668\u6355\u83b7\n```\n\n## API \u6587\u6863\n\u6846\u67b6\u5b8c\u5168\u517c\u5bb9 FastAPI \u7684\u81ea\u52a8\u751f\u6210\u6587\u6863\u529f\u80fd\u3002\n\n+ **Swagger UI**: `http://<your-host>:<port>/docs`\n+ **ReDoc**: `http://<your-host>:<port>/redoc`\n\n## \u8d21\u732e\n\u6b22\u8fce\u63d0\u4ea4 Issue \u548c Pull Request\uff01\n\n1. Fork \u672c\u4ed3\u5e93\n2. \u521b\u5efa\u4f60\u7684\u7279\u6027\u5206\u652f (`git checkout -b feature/AmazingFeature`)\n3. \u63d0\u4ea4\u4f60\u7684\u66f4\u6539 (`git commit -m 'Add some AmazingFeature'`)\n4. \u63a8\u9001\u5230\u5206\u652f (`git push origin feature/AmazingFeature`)\n5. \u5f00\u542f\u4e00\u4e2a Pull Request\n\n## \u8bb8\u53ef\u8bc1\n\u672c\u9879\u76ee\u91c7\u7528 MIT \u8bb8\u53ef\u8bc1\u3002\u8be6\u60c5\u8bf7\u89c1 [LICENSE](LICENSE) \u6587\u4ef6\u3002\n\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "\u4e00\u4e2a\u529f\u80fd\u4e30\u5bcc\u3001\u5f00\u7bb1\u5373\u7528\u7684 Python Web \u6846\u67b6\uff0c\u57fa\u4e8e FastAPI \u6784\u5efa\u3002",
    "version": "0.11.1",
    "project_urls": {
        "Homepage": "https://github.com/Fatosy/zWebApi",
        "Repository": "https://github.com/Fatosy/zWebApi.git"
    },
    "split_keywords": [
        "zwebapi",
        " fastapi",
        " framework",
        " web",
        " api"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "470529c708214fb3235bddfb251ff58b27f0c2e2198be5c0a3104b86743507aa",
                "md5": "fb5bdf51d3e61286459e5ed28abca607",
                "sha256": "a47ca34f656a75dbde9f0d6992904c5a11a74347ae1552471be02f1aba12ecad"
            },
            "downloads": -1,
            "filename": "zwebapi-0.11.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "fb5bdf51d3e61286459e5ed28abca607",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 24021,
            "upload_time": "2025-07-31T09:29:24",
            "upload_time_iso_8601": "2025-07-31T09:29:24.592872Z",
            "url": "https://files.pythonhosted.org/packages/47/05/29c708214fb3235bddfb251ff58b27f0c2e2198be5c0a3104b86743507aa/zwebapi-0.11.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "30c8266e04334cc24eb3926076b0d4d37afbf1ae4f1eccb8676d6b7b793702da",
                "md5": "ca33279190e1fff81b38416d159dc8c5",
                "sha256": "bc1f809463a67736c99d98b49aaf08608380a6b2ece29121d84407ddc938019a"
            },
            "downloads": -1,
            "filename": "zwebapi-0.11.1.tar.gz",
            "has_sig": false,
            "md5_digest": "ca33279190e1fff81b38416d159dc8c5",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 25759,
            "upload_time": "2025-07-31T09:29:25",
            "upload_time_iso_8601": "2025-07-31T09:29:25.860929Z",
            "url": "https://files.pythonhosted.org/packages/30/c8/266e04334cc24eb3926076b0d4d37afbf1ae4f1eccb8676d6b7b793702da/zwebapi-0.11.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-07-31 09:29:25",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "Fatosy",
    "github_project": "zWebApi",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "zwebapi"
}
        
Elapsed time: 1.96979s