9.7 KiB
以下是为提供的 OAuthClient 类编写的 Markdown 格式技术文档,涵盖了类的功能、设计思想、使用方式、参数说明及示例等内容。
OAuthClient 技术文档
OAuthClient 是一个基于 HTTP(S) 的异步客户端工具类,用于调用由第三方服务发布的 RESTful API 接口。它通过预定义的接口描述(desc)动态构建请求,并解析响应数据,支持自定义数据转换、错误判断逻辑和返回结构处理。
该类广泛应用于需要与 OAuth 认证或标准 JSON API 交互的服务中,具有良好的可配置性和扩展性。
📦 模块依赖
import json
from appPublic.httpclient import HttpClient, RESPONSE_TEXT, RESPONSE_JSON, RESPONSE_BIN, RESPONSE_FILE, RESPONSE_STREAM, HttpError
from appPublic.argsConvert import ArgsConvert
from appPublic.dictObject import DictObject
HttpClient: 异步 HTTP 客户端,支持多种响应类型。ArgsConvert: 用于模板字符串替换(如${key})。DictObject: 支持通过属性访问字典键值的对象封装。
🧱 类定义
class OAuthClient:
def __init__(self, desc, converters={})
构造函数参数
| 参数 | 类型 | 说明 |
|---|---|---|
desc |
dict |
接口描述对象,必须包含 data 字段,定义了基础数据和各 API 方法的配置。详见下方“接口描述格式”章节。 |
converters |
dict[str, callable] |
可选的数据转换函数映射表,键为转换器名称,值为函数引用。 |
⚠️ 注意:
desc['data']必须存在,否则会抛出断言错误。
🔧 接口描述格式 (desc)
desc 是一个字典,其结构如下:
{
"data": { /* 基础共享数据,可在请求时被引用 */ },
"method_name": {
"path": "/api/v1/users/${userId}",
"method": "GET", // 可选,默认 GET
"headers": [ ... ],
"params": [ ... ],
"data": [ ... ], // 请求体中的数据
"resp": [
{
"name": "result",
"resp_keys": ["user", "name"],
"converter": "to_upper" // 可选
}
],
"error_if": {
"error_keys": ["status"],
"op": "==", // 比较操作符:'==' 或 '!='
"value": "fail",
"code_keys": ["code"], // 错误码路径
"msg_keys": ["message"] // 错误信息路径
}
}
}
字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
data |
dict |
全局上下文数据,可用于模板替换。 |
method_name |
dict |
每个 API 接口的配置项,方法名作为 key。 |
path |
str |
接口路径,支持 ${} 占位符变量。 |
method |
str |
HTTP 方法(GET/POST/PUT/DELETE 等),默认为 GET。 |
headers |
list[dict] |
请求头列表,每个元素形如 {name: "Header-Key", value: "HeaderValue", converter?: "funcName"} |
params |
list[dict] |
URL 查询参数,格式同上。 |
data |
list[dict] |
请求体数据(JSON 格式),格式同上。 |
resp |
list[dict] |
响应数据提取规则。 |
name |
str |
返回结果中字段的名称。 |
resp_keys |
list[str] |
在响应 JSON 中获取数据的路径(嵌套键)。 |
converter |
str |
转换函数名(需在 converters 中注册)。 |
error_if |
dict |
自定义错误判断条件。 |
error_keys |
list[str] |
提取用于比较的响应字段路径。 |
op |
str |
比较操作符:== 或 !=。 |
value |
any | 期望值,用于对比。 |
code_keys |
list[str] |
错误码字段路径(可选)。 |
msg_keys |
list[str] |
错误消息字段路径(可选)。 |
🧩 核心方法
__call__(self, host, mapi, params) -> dict
发起一次 API 调用。
参数
| 参数 | 类型 | 说明 |
|---|---|---|
host |
str |
服务主机地址,例如 "https://api.example.com" |
mapi |
str |
接口方法名,对应 desc 中的键名。 |
params |
dict |
动态传入的参数,用于替换模板和构造请求。 |
返回值
成功时:
{
"status": "ok",
"data": { /* 根据 resp 配置提取的数据 */ }
}
失败时:
{
"status": "error",
"code": "400",
"message": "Bad Request"
}
流程说明
- 查找
desc[mapi]是否存在; - 使用
datalize()替换路径中的${}变量; - 构造完整 URL;
- 处理 headers、params、data(应用转换器);
- 发起异步 HTTP 请求;
- 解析响应为
DictObject; - 检查是否符合
error_if条件; - 按照
resp规则提取并转换返回数据。
📌 日志输出:请求详情(URL、method、params 等)将打印到控制台。
setup_req_data(data, ns) -> dict or None
将一组 {name, value, converter} 结构的数据根据命名空间 ns 进行模板替换和转换。
参数
data: 数据项列表(如 headers、params)ns: 命名空间(通常是params)
内部调用
setup_req_kv(): 处理单个键值对,执行模板替换和转换。
datalize(dic, data={}) -> dict
执行模板替换(${key} → 实际值)。
说明
- 合并
self.data(全局数据)与传入的data; - 使用
ArgsConvert('${', '}')执行替换; - 支持嵌套字典和字符串。
示例
self.data = {'token': 'abc123'}
dic = {'url': '/api?tk=${token}'}
result = self.datalize(dic) # {'url': '/api?tk=abc123'}
get_resp_data(resp, keys, converter=None)
从响应对象中按 keys 路径提取数据,并可选地进行转换。
参数
resp:DictObject类型的响应体;keys: 键路径列表,如['data', 'user', 'id'];converter: 转换函数名。
示例
resp = DictObject(data={"user": {"name": "alice"}})
value = get_resp_data(resp, ['data', 'user', 'name']) # "alice"
setup_return_data(resp) -> dict
根据 api.resp 配置,构建最终返回的 data 对象。
示例配置
"resp": [
{
"name": "username",
"resp_keys": ["data", "user", "name"],
"converter": "to_upper"
}
]
输出
{
"status": "ok",
"data": {
"username": "ALICE"
}
}
check_if_error(resp) -> dict or None
检查响应是否满足预设的错误条件。
判断逻辑
v = resp.get_data_by_keys(ei.error_keys)
if ei.op == '==' and v == ei.value → 触发错误
if ei.op == '!=' and v != ei.value → 触发错误
成功返回
None表示无错误。
失败返回
{
"status": "error",
"code": "...",
"message": "..."
}
💡 支持从响应中提取
code和message。
set_data(resp_data, data_desc)
将响应中的某些字段保存回 self.data,用于后续请求共享状态(如 token 刷新)。
参数
resp_data: 原始响应数据(dict)data_desc: 描述如何映射字段
data_desc = [
{'field': 'access_token', 'name': 'token'},
{'field': 'expires_in', 'name': 'expires'}
]
执行后:
self.data['token'] = resp_data['access_token']
self.data['expires'] = resp_data['expires_in']
✅ 使用示例
1. 定义接口描述
desc = {
"data": {
"client_id": "your_client_id",
"client_secret": "your_secret"
},
"get_user": {
"path": "/users/${user_id}",
"method": "GET",
"headers": [
{"name": "Authorization", "value": "Bearer ${token}"}
],
"resp": [
{
"name": "userInfo",
"resp_keys": ["data"],
"converter": "strip_data"
}
],
"error_if": {
"error_keys": ["status"],
"op": "==",
"value": "error",
"code_keys": ["error_code"],
"msg_keys": ["error_msg"]
}
}
}
2. 定义转换器
def to_upper(s):
return s.upper() if isinstance(s, str) else s
converters = {
"to_upper": to_upper,
"strip_data": lambda x: x.get('data') if isinstance(x, dict) else x
}
3. 创建并调用客户端
client = OAuthClient(desc, converters=converters)
result = await client(
host="https://api.example.com",
mapi="get_user",
params={
"user_id": "123",
"token": "xyz987"
}
)
print(result)
# 输出:
# {
# "status": "ok",
# "data": {
# "userInfo": { ... }
# }
# }
🛠️ 调试信息
所有请求细节将通过 print() 输出:
print(f'{url=}, {method=}, {myparams=}, {mydata=}, {myheaders=}')
建议在生产环境中移除或替换为日志记录。
❗ 异常处理
- 若指定的
mapi未在desc中定义,抛出Exception; - HTTP 请求异常捕获为
HttpError,返回标准错误结构; - 网络级错误(如连接超时)也会被捕获并返回
"https error"。
🧩 扩展建议
- 添加日志模块替代
print(); - 支持更多响应类型(如文件下载);
- 增加中间件钩子(before_request / after_response);
- 支持分页自动迭代。
📚 总结
OAuthClient 是一个灵活、声明式的 API 客户端封装,适用于微服务间通信、第三方接口集成等场景。通过配置驱动的方式,实现了解耦与复用,适合构建统一的外部服务网关层。
📎 版本:1.0
📅 最后更新:2025年4月5日
© 2025 Your Company. All rights reserved.