apppublic/aidocs/oauth_client.md
2025-10-05 11:23:33 +08:00

9.7 KiB
Raw Blame History

以下是为提供的 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"
}

流程说明

  1. 查找 desc[mapi] 是否存在;
  2. 使用 datalize() 替换路径中的 ${} 变量;
  3. 构造完整 URL
  4. 处理 headers、params、data应用转换器
  5. 发起异步 HTTP 请求;
  6. 解析响应为 DictObject
  7. 检查是否符合 error_if 条件;
  8. 按照 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": "..."
}

💡 支持从响应中提取 codemessage


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.