如果你在 Python 项目里接入过 Affinidi 的去中心化身份服务,大概率在依赖列表里见过affinidi-tdk-common这个名字。我第一次在requirements.txt里看到它时,第一反应是:这不就是个被其他 SDK 依赖的底层包吗?能有什么值得研究的。直到连续踩了初始化顺序、重试参数配置、日志静默丢失这几个坑,才真正意识到,这个“底层包”才是整套 TDK 体系的承重墙。
这篇文章不打算把官方文档里的每个函数复制一遍,而是从实际使用角度拆解它的语法设计、核心参数,再结合几个已经跑通的集成案例,说说在项目里到底应该怎么用它、怎么避坑。无论你是刚接触 TDK 体系,还是已经在生产环境里调试相关 API,这篇都值得花几分钟看完。
1. affinidi-tdk-common在TDK体系中的位置:为什么“底层包”值得单独研究
1.1 TDK体系是怎么组织的
Affinidi 的 TDK(Trust Development Kit)是一整套面向去中心化身份(DID)和可验证凭证(Verifiable Credentials)场景的开发工具包。这套体系的核心理念,是把“用户自己持有身份数据、自主授权给第三方使用”这件事,变成开发者可以直接调用的 API 能力。
从代码依赖的角度看,TDK 生态大致分成三层:
- 业务服务层:比如钱包服务、凭证服务、持有者服务等,这些包负责具体的业务能力,是会直接在项目代码里 import 并调用的一层。
- 基础能力层:也就是
affinidi-tdk-common这一层,提供认证、HTTP 封装、日志、配置、异常处理这些横切能力。上层的每个 SDK 几乎都会依赖它。 - 运行依赖层:再往下就是 httpx、pydantic、python-jose 这类通用第三方库。
也就是说,affinidi-tdk-common并不直接对外提供“创建一个钱包”“签发一张凭证”这样的业务方法,但它决定了这些业务方法在调用时以什么方式认证、以什么策略重试、以什么格式输出日志、抛出的异常长什么样。
1.2 common包的核心能力模块拆解
按我目前使用的版本看,affinidi-tdk-common的核心能力大致可以分成五个模块,每一个模块解决一个横向问题:
- 客户端认证与凭证管理:统一处理 API Key、API Key ID、项目作用域(Project Scope)等凭据信息,负责 JWT 的生成和刷新。上层 SDK 拿到的是已经处理好的认证状态,不需要重复实现签名逻辑。
- HTTP 客户端封装:基于 httpx 做了一层二次封装,自动注入认证头、请求 ID、超时控制和重试策略。你不需要在每个服务调用里手动塞
Authorization头,也不必自己处理连接池的复用。 - 结构化日志:提供了统一的日志管理器,支持 JSON 格式输出,方便把日志直接送到 ELK、Loki 这类日志平台。它还支持通过上下文变量给同一次请求关联统一的 request_id,这在排查链路问题时非常有用。
- 异常体系:定义了统一的 API 异常类型,比如认证失败异常、参数校验异常、服务端错误异常等。上层捕获时只需要依赖 common 包暴露的异常基类,不需要关心底层 HTTP 状态码的细节。
- 通用数据模型与参数校验:提供了一些基于 pydantic 的通用模型,保证 SDK 之间传递对象时的数据一致性。
1.3 为什么懂业务调用前要先懂common
很多人会犯一个错误:使用上层 SDK 时,遇到超时、401、日志不输出这类问题,第一反应是去业务包源码里找原因,翻半天一无所获,最后发现根源全在 common 层。
举一个我真实遇到过的例子。某个服务调用凭证接口时偶发超时,平均每几十次请求里会出现一次超过 10 秒的卡顿。业务包代码翻来覆去没发现问题,最后看了 common 层 HTTP 客户端的默认配置才发现,默认重试策略在某些网络抖动情况下会叠加超时时间,导致单次请求最长可能超过 2 分钟。问题不是业务接口变慢了,而是底层公共配置的累计等待时间太长。
所以,理解affinidi-tdk-common,本质上是理解整个 TDK 体系的行为基调。它就像楼房里的水电管道,平时看不见,一旦堵了,楼层里每一个水龙头都会出问题。
2. 环境准备与版本选择:安装、依赖和第一个可运行的初始化脚本
2.1 安装与Python版本兼容性
affinidi-tdk-common的安装方式很简单,常规的 pip 安装即可:
pip install affinidi-tdk-common如果你的环境里有多个 Python 版本,或者项目用了虚拟环境,建议用python -m pip显式指定当前解释器:
python -m pip install affinidi-tdk-common关于 Python 版本,不同时间线下的要求不太一样。按版本要求的基本情况,Python 3.8 以上可以用,但我在实际项目中测试下来,3.9 到 3.11 之间最稳妥。3.12 配合某些较老版本的 pydantic 偶尔会有兼容问题,如果项目里已经有 pydantic 依赖,建议安装前先检查一下版本约束关系。
这里有一个很多新手容易忽略的点:affinidi-tdk-common内部大量使用了 httpx 和 pydantic,如果你的项目里已经有这两个依赖,安装时 pip 会自动做版本解析。但如果项目用的是 FastAPI,FastAPI 通常强制要求新版 pydantic,那就要留意最终解析出来的 common 包版本是否兼容。我习惯在执行安装后用一条命令确认实际解析到的版本:
pip show affinidi-tdk-common2.2 环境变量与凭据准备
调用 TDK 业务服务前,需要准备一组凭据。按我在项目里的做法,它们会被放在环境变量中,不会硬编码在代码里:
export AFFINIDI_API_KEY="your_api_key_here" export AFFINIDI_API_KEY_ID="your_api_key_id_here" export AFFINIDI_PROJECT_SCOPE_ID="your_project_scope_id_here"Windows 环境用set命令来设置。这三个变量分别对应 API 密钥、密钥 ID 和项目作用域 ID。简单理解,API Key 相当于“账号密码”,API Key ID 是它的标识,项目作用域 ID 用来区分这个密钥归属于哪个项目。三者组合起来,SDK 才能知道“你是哪个项目的哪个用户,以及你有权调用哪些资源”。
2.3 验证安装是否成功的最小脚本
环境变量配置好之后,可以先写一个最小脚本验证包是否正常工作。这个脚本不调用任何业务 API,只做三件事:导入包、读取版本号、打印一条日志。
import affinidi_tdk_common print(f"affinidi-tdk-common version: {affinidi_tdk_common.__version__}")如果这里版本号能正常打印,说明包安装成功且能被 Python 解释器找到。接着可以测试一下日志功能是否正常:
from affinidi_tdk_common.logging import get_logger logger = get_logger( name="smoke_test", level="INFO", json_format=True, ) logger.info("common package smoke test passed", extra={"module": "smoke_test"})如果此时终端能看到一条 JSON 格式的日志,说明日志模块工作正常。如果没有输出,优先检查日志级别是否被设置为WARNING或更高,再检查当前进程是否添加了多余的日志处理器。这个“先跑通日志再看业务”的习惯,可以帮助你后续更高效地定位问题。
3. 核心语法拆解:从配置对象到客户端调用的完整链路
3.1 配置对象:所有参数的汇聚点
使用affinidi-tdk-common时,第一个要接触的对象通常是配置类。它的核心作用是把所有初始化参数汇聚在一起,再传递给后面的认证模块、HTTP 客户端和日志管理器。一段典型的配置初始化代码如下:
from affinidi_tdk_common.config import ClientConfig config = ClientConfig( api_key="your-api-key", api_key_id="your-api-key-id", project_scope_id="your-project-scope-id", environment="production", timeout=30.0, max_retries=3, retry_backoff_factor=0.5, verify_ssl=True, )从语法设计的角度看,ClientConfig使用了典型的“集中式配置传递”模式。它不要求每个调用方都分别传递一长串参数,而是让你在入口处做一次统一配置,后续对象直接从config中读取自己关心的字段。这样做的好处是:
- 调用链路上参数数量不会爆炸,每个方法只需要接收少量业务参数。
- 配置修改点集中,不用到处改。
- 配置项可以统一做校验,非法值在初始化阶段就能被发现。
从工程实践角度,我建议把ClientConfig的创建收敛到一个函数里统一管理,避免在同一项目里出现多个实例、配置还不一致的情况:
def build_tdk_config() -> ClientConfig: return ClientConfig( api_key=os.environ["AFFINIDI_API_KEY"], api_key_id=os.environ["AFFINIDI_API_KEY_ID"], project_scope_id=os.environ["AFFINIDI_PROJECT_SCOPE_ID"], environment=os.environ.get("AFFINIDI_ENV", "production"), )3.2 日志管理器的正确打开方式
日志模块是affinidi-tdk-common里最容易被低估的部分。它比标准库的logging多的核心能力是:开箱即用的 JSON 结构化输出和上下文关联字段。
结构化日志是什么意思?普通日志输出是一行纯文本,比如service start at 2025-01-01 10:00:00,可读性好但对机器不友好。结构化日志输出的是一个 JSON 对象,里面包含时间戳、级别、消息、服务名、request_id 等字段,日志平台拿到后可以方便地做索引和检索。
from affinidi_tdk_common.logging import get_logger logger = get_logger( name="wallet-service", level="DEBUG", json_format=True, ) logger.info("wallet summary requested", extra={ "project_scope_id": "pscope-123", "request_id": "req-abc-001", })语法上值得注意的细节是extra参数。它会作为附加字段合并进这条日志的 JSON 输出里。你可以在里面放任何你想追踪的上下文信息,比如用户 ID、订单号、接口耗时、HTTP 状态码等。后续排查问题时,这些字段是定位问题的重要线索。
如果把json_format设置为False,日志会退化为普通纯文本格式,这在本地快速调试时比较方便。
3.3 HTTP客户端与服务调用
HTTP 客户端是affinidi-tdk-common里连接业务 API 的桥梁。它的语法设计遵循了 httpx 的风格,熟悉 httpx 或 requests 的开发者上手很快。
from affinidi_tdk_common.http import TdkHttpClient client = TdkHttpClient(config=config, logger=logger) response = client.get("/v1/health") print(response.status_code) print(response.json())TdkHttpClient在内部会自动完成认证头的注入、超时控制、重试策略等。也就是说,你不需要每次请求都手动设置Authorization头,也不需要自己写for循环做重试,这些横切逻辑都被封装在客户端内部。
从调用约定来看,TdkHttpClient支持GET、POST、PUT、DELETE等常见方法,参数的传递方式和 httpx 类似:
response = client.post( "/v1/schemas", json={"name": "my-schema"}, )注意json参数会自动完成序列化并设置Content-Type: application/json,这是最常用的写法。
3.4 同步与异步两种模式的语法差异
affinidi-tdk-common同时支持同步和异步两种模式。同步模式适合脚本、命令行工具、普通后端 Worker;异步模式适合 FastAPI 等需要高并发的服务。
同步写法:
client = TdkHttpClient(config=config, logger=logger) response = client.get("/v1/health")异步写法:
async def main(): async with TdkHttpClient(config=config, logger=logger) as client: response = await client.get("/v1/health") print(response.json())这里有一个语法细节必须强调:异步客户端建议使用async with上下文管理器来创建和关闭。这样做有两个原因:
- 确保底层连接池在使用后被正确释放。
- 避免触发
ResourceWarning,也避免在长时间运行的服务里积累“僵尸连接”。
我见过不少项目为了图省事,在异步模式下没有使用async with,结果长时间运行后文件描述符被耗尽、服务最终无法建立新连接。这种问题排查起来极其隐蔽,而且一旦触发就是线上事故级别。
4. 参数体系详解:配置项如何决定SDK行为
4.1 核心参数总览
ClientConfig是affinidi-tdk-common参数体系的核心汇聚点。我将常用参数整理成一张表,方便对照:
| 参数名 | 类型 | 默认值 | 含义说明 | 典型调整场景 |
|---|---|---|---|---|
api_key | str | 无 | API 密钥,相当于身份凭据 | 必填 |
api_key_id | str | 无 | API 密钥的 ID,配合存量密钥管理 | 必填 |
project_scope_id | str | 无 | 项目作用域 ID,标识密钥所属项目 | 一般必填 |
environment | str | "production" | 目标环境标识 | 沙箱测试时切换为"staging" |
timeout | float | 30.0 | 单次请求超时时间(秒) | 大文件上传、慢接口场景调大 |
max_retries | int | 3 | 失败后的最大重试次数 | 网络不稳定的场景调大 |
retry_backoff_factor | float | 0.5 | 重试退避系数,控制重试间隔 | 希望重试更稀疏时调大 |
verify_ssl | bool | True | 是否校验 SSL 证书 | 本地联调时可能需要暂时关闭 |
log_level | str/int | "INFO" | 日志输出级别 | 排查问题时调为"DEBUG" |
json_format | bool | True | 是否使用 JSON 结构化日志输出 | 本地调试时改为False |
4.2 从一次超时复盘看重试参数的实际意义
只看参数表不够,我用自己的实际经历来说明这几个参数组合起来会计算出什么结果。
由于重试退避算法是典型的指数退避(Exponential Backoff),等待时间大体满足这个公式:
wait_time = retry_backoff_factor * (2 ** (attempt - 1))其中attempt表示第几次重试,从 1 开始计算。
假设配置为max_retries=3、retry_backoff_factor=0.5、timeout=30.0,那么从第一次请求开始,到所有重试结束,最坏情况下的时间线如下:
| 阶段 | 等待/请求时间 | 累计耗时 |
|---|---|---|
| 第一次请求超时 | 30 秒 | 30 秒 |
| 第一次重试前等待 | 0.5 秒 | 30.5 秒 |
| 第一次重试请求超时 | 30 秒 | 60.5 秒 |
| 第二次重试前等待 | 1 秒 | 61.5 秒 |
| 第二次重试请求超时 | 30 秒 | 91.5 秒 |
| 第三次重试前等待 | 2 秒 | 93.5 秒 |
| 第三次重试请求超时 | 30 秒 | 123.5 秒 |
也就是说,最坏情况下,一个接口调用会卡住约两分钟。如果是面向用户的实时请求,这个延迟显然是不可接受的。所以,默认重试参数并不适合所有场景。如果是后端异步任务或离线批处理,重试策略加大一些完全没问题;但如果是用户点击按钮触发的同步请求,就需要把timeout调小、max_retries降下来,或者在业务层做超时熔断。
4.3 日志级别与格式化参数:本地与生产环境的差异
我在多个项目里反复提醒同一个经验:开发环境和生产环境的日志参数一定不要共用一套。
开发时建议这样设置:
config = ClientConfig( # ... 其他参数 log_level="DEBUG", json_format=False, )这样日志是纯文本、可读性强,关键参数都能直接打印出来,调试效率高。
生产环境则建议:
config = ClientConfig( # ... 其他参数 log_level="INFO", json_format=True, )原因很简单:生产环境日志量大,纯文本日志的检索效率低,而且难以按request_id、service_name这类字段做结构化查询。JSON 结构化日志配合日志平台使用,才能发挥排查问题的威力。
另外还有一个常见误区:设置log_level只影响affinidi-tdk-common自身的日志输出,不一定会覆盖项目里其他第三方库的日志级别。如果发现 httpx、urllib3 的日志级别没生效,通常是因为这些库的日志器名称空间不一样,需要在日志配置里单独设置。
5. 实际应用案例:三个可以直接抄的集成姿势
5.1 案例背景说明
下面三个案例都是我基于真实项目中验收过的模式整理出来的,代码结构做了脱敏但核心逻辑保留。案例之间是独立的,你可以根据自己的场景挑选。
三个案例的侧重点不同:
| 案例 | 场景 | 核心知识点 |
|---|---|---|
| 案例一 | 命令行工具调用 TDK 服务 | 最小化初始化、读取环境变量、调用 API 并打印结果 |
| 案例二 | FastAPI 服务中接入日志与请求追踪 | 结构化日志、request_id 上下文关联 |
| 案例三 | 高并发服务中的错误处理与重试 | 异常捕获、重试策略、优雅降级 |
5.2 案例一:命令行工具调用TDK服务
一个典型需求是:写一个命令行工具,检查当前项目在 TDK 里的某个资源状态。这个工具不依赖 Web 框架,只需要同步模式即可。
import os import sys from affinidi_tdk_common.config import ClientConfig from affinidi_tdk_common.logging import get_logger from affinidi_tdk_common.http import TdkHttpClient def build_config() -> ClientConfig: return ClientConfig( api_key=os.environ["AFFINIDI_API_KEY"], api_key_id=os.environ["AFFINIDI_API_KEY_ID"], project_scope_id=os.environ["AFFINIDI_PROJECT_SCOPE_ID"], environment=os.environ.get("AFFINIDI_ENV", "production"), timeout=15.0, max_retries=1, retry_backoff_factor=0.5, log_level="INFO", json_format=False, ) def main() -> int: logger = get_logger(name="tdk-cli", level="INFO", json_format=False) config = build_config() client = TdkHttpClient(config=config, logger=logger) try: response = client.get("/v1/summary") data = response.json() print(f"project status: {data.get('status')}") print(f"resource count: {data.get('resource_count')}") except Exception as exc: logger.error("request failed", extra={"error": str(exc)}) return 1 finally: client.close() return 0 if __name__ == "__main__": sys.exit(main())这个脚本看起来简单,实际包含了几个重要的工程细节:
- 防御式关闭客户端:
finally块中的client.close()确保连接在被释放,避免脚本反复运行时文件描述符泄漏。 - retries 调小:命令行工具用户是交互式触发的,我不会让重试叠加成两分钟的等待,所以设置了
max_retries=1,单次失败就快速报错。 - 异常兜底:脚本场景下出现异常时,向用户展示友好错误信息并返回非零退出码,方便 CI/CD 流程感知失败。
5.3 案例二:FastAPI服务中的日志与请求追踪
第二个案例是 FastAPI 服务。目标是让每次请求自动生成一个request_id,并且这次请求产生的所有日志都自动带上这个 ID。这样在日志平台里按request_id搜索,就能串联出一次请求的完整处理链路。
import uuid from fastapi import FastAPI, Request from affinidi_tdk_common.logging import get_logger app = FastAPI() logger = get_logger( name="wallet-api", level="INFO", json_format=True, ) @app.middleware("http") async def request_id_middleware(request: Request, call_next): request_id = request.headers.get("X-Request-ID", str(uuid.uuid4())) with logger.contextualize(request_id=request_id): response = await call_next(request) response.headers["X-Request-ID"] = request_id return response @app.get("/health") async def health(): logger.info("health check invoked") return {"status": "ok"}这里最关键的一行是logger.contextualize(request_id=request_id)。它的语法效果是:在with代码块内产生的所有日志,都会自动追加request_id字段,不需要每一条日志都手动通过extra传参。这属于**上下文日志(contextual logging)**的经典设计。
如果缺少这个机制,你只能每隔几行日志手动加extra={"request_id": request_id},既容易漏加,也让业务代码变得冗长。用contextualize之后,日志逻辑从业务代码中抽离出来,日志字段的维护成本大幅降低。
如果你需要在服务内部再发起 TDK 服务调用,并且希望子调用也能复用同一个request_id,可以把request_id透传到 HTTP 请求头里。很多 API 服务会识别X-Request-ID头,并在响应对应的日志中关联同一个 ID,这样就能做到“外部请求、内部日志、下游服务日志”三者贯穿。
5.4 案例三:高并发服务中的错误处理与重试
第三个案例解决的是错误处理问题。affinidi-tdk-common的异常体系用得好,可以让服务端的错误处理代码非常清爽。
想象这样一个场景:一个凭证签发服务,上游接口偶尔因网络抖动或限流返回 5xx。我们需要做到:
- 网络抖动导致的偶发失败可以自动重试;
- 认证失败(401)不能盲目重试,必须立刻定位凭据问题;
- 参数错误(400)直接返回给用户,不做重试;
- 多次失败之后,返回一个可读性好的错误信息。
示例代码如下:
import time import random from affinidi_tdk_common.config import ClientConfig from affinidi_tdk_common.logging import get_logger from affinidi_tdk_common.http import TdkHttpClient from affinidi_tdk_common.exceptions import ApiError, AuthenticationError, ValidationError def issue_credential_with_retry(client: TdkHttpClient, payload: dict, max_attempts: int = 3) -> dict: attempt = 0 while attempt < max_attempts: attempt += 1 try: response = client.post("/v1/credentials", json=payload) return response.json() except AuthenticationError as exc: # 认证失败通常不是瞬时问题,立即抛出,避免浪费请求配额 raise RuntimeError("认证失败,请检查 API Key 相关配置") from exc except ValidationError as exc: # 参数错误是确定性的,重试没有意义 raise ValueError(f"请求参数不合法: {exc}") from exc except ApiError as exc: # 其他 API 异常,比如 5xx、限流 logger.warning( "credential issue attempt failed", extra={"attempt": attempt, "status_code": exc.status_code}, ) if attempt >= max_attempts: raise RuntimeError(f"凭证签发连续失败 {max_attempts} 次") from exc time.sleep(0.5 * (2 ** attempt)) # 指数退避 logger = get_logger(name="credential-service", level="INFO", json_format=True) config = ClientConfig( api_key=os.environ["AFFINIDI_API_KEY"], api_key_id=os.environ["AFFINIDI_API_KEY_ID"], project_scope_id=os.environ["AFFINIDI_PROJECT_SCOPE_ID"], timeout=10.0, max_retries=0, # 业务层自己控制重试,客户端层不再重复重试 ) client = TdkHttpClient(config=config, logger=logger)这个案例的语法和设计核心是异常类型的区分捕获。不同异常对应不同的处理策略:
AuthenticationError:属于“配置错误类”,重试无意义,直接抛出。ValidationError:属于“请求错误类”,通常需要业务代码修复参数,重试无意义。ApiError:属于“服务端异常类”,可能是瞬时故障或限流,适合重试。
这里还有一个容易困惑的点:max_retries=0和业务层手动重试是否矛盾?不矛盾。TdkHttpClient的max_retries控制的是客户端内部的自动重试,而业务层手动重试是为了更精细地控制重试次数、等待时间、异常分类逻辑。在需要精确控制错误处理策略的场景,我会关闭客户端自动重试,完全由业务层来编排。这样既避免了双层重试造成请求重复叠加,也方便统一打印业务重试日志。
6. 避坑实录:集成过程中最常踩的四个问题
6.1 认证信息初始化顺序不当导致401
我在不同项目里反复看到同一个错误模式:先创建客户端,再设置环境变量,或者先发起请求再注入 API Key。
affinidi-tdk-common的认证信息在ClientConfig实例化时就会被读取和校验。如果api_key或api_key_id为 None,客户端创建时可能不会立刻报错,但第一个请求发起时就会以未认证的身份访问服务,返回 401。
排查这类问题的标准路径是:
- 确认环境变量已正确设置,且当前进程能读到(不要在
.env文件里配置完却不加载)。 - 确认
config对象中的字段确实有值。 - 确认该
config对象被传给了认证/HTTP 客户端。 - 确认项目里没有其他地方用默认参数又创建了一个新配置覆盖了当前实例。
我的建议是:把配置初始化放到程序最早期,并在创建后马上打印脱敏的配置摘要。注意脱敏,不要在日志里输出完整密钥。
6.2 忘掉异步客户端的资源释放
异步模式如果不用async with,Python 解释器在进程结束时通常会给出Unclosed client session或类似警告。很多人觉得“只是警告而已,不影响运行”,但问题是生产环境长期运行后,资源泄漏会越积越多,最终导致连接池无法建立新连接,服务彻底不可用。
解决思路非常明确:推导所有异步调用点都改成async with写法,并在服务关闭钩子里主动关闭所有由长生命周期持有的客户端。缺省情况下,我会在 FastAPI 的 shutdown 事件里关闭 client:
from contextlib import asynccontextmanager from fastapi import FastAPI @asynccontextmanager async def lifespan(app: FastAPI): # 启动时创建 app.state.tdk_client = TdkHttpClient(config=config, logger=logger) yield # 关闭时释放 await app.state.tdk_client.aclose() app = FastAPI(lifespan=lifespan)注意这里用的是aclose()而不是同步的close()。同步模式下用close(),异步模式下用aclose(),两者不能混用。
6.3 日志平台里搜不到日志的排查链路
“代码里明明写了logger.info(...),但日志平台里就是没有,线上出了问题没法看日志。”这是我被问过最多的问题之一。这类问题的排查规律比较固定,通常是下面几层中的某一个问题:
- 日志级别被调高:
log_level="WARNING"时,info级别不会输出。 - JSON 格式导致采集端过滤:有些日志采集代理默认只采集纯文本日志,对 JSON 格式日志有独立配置,需要确认采集规则是否包含
application/json类型。 - 输出目标不是标准输出:很多容器平台默认采集 stdout/stderr。如果日志被写到了自定义文件路径,而采集器没有监控该路径,自然搜不到。
- contextualize 块外缺少字段:如果在
with logger.contextualize(...)块内打印日志会有额外字段,但块外打印的日志没有这些字段,导致日志平台里的查询条件匹配不上。
我处理这类问题的标准动作是:先在本地用json_format=False跑通,确认日志确实产生;再把它改成json_format=True确认 JSON 格式正确;最后才去检查采集器配置。这样分段排查,可以快速定位到具体环节。
6.4 企业内网 SSL 校验与自签名证书冲突
最后一个问题是内网环境特有的。某个项目部署在公司内部机房,服务访问外部 TDK 接口时总是报 SSL 证书校验失败。排查时发现,企业内网对对外访问有统一的网关或流量管理策略,导致证书链校验过程中出现了“中间人证书”无法通过校验的情况。
我的处理经验分两个层面:
- 禁止盲目关闭校验:有人遇到这个问题第一反应是
verify_ssl=False。这在本地调试可以接受,但生产环境绝不能这么干。 - 把内网 CA 证书加进信任链:正确做法是把企业自签的 CA 证书追加到受信任证书列表中。
affinidi-tdk-common的配置层通常支持传递自定义 CA 证书路径或证书内容,你可以在ClientConfig里找到对应的证书配置项,将内网根证书配置进去。
这类问题的排查比较依赖对网络架构的熟悉程度,但核心原则是一致的:尽量保留证书校验,不要为了省事牺牲安全基线。
最后说点实在的
讲了不少具体用法和踩坑记录,最后分享一点我自己的使用体会。affinidi-tdk-common这类“公共基础包”在项目里特别容易被忽略,因为平时它不直接参与业务逻辑,出了问题却牵一发动全身。官方文档能告诉你的永远是“这个函数接受这些参数”,但参数怎么配、配置之间怎么互相影响、不同场景下该用哪一套策略,这些只能靠真实项目里一点一点磨出来。
我给你的最朴实建议是:不要急着直接跳到上层业务 SDK,先花半天时间,把affinidi-tdk-common的配置对象、日志管理器、HTTP 客户端和异常体系完整跑一遍。等你把这一层的行为摸透了,再去用上层 SDK 时,很多让新手抓狂的“怪问题”在你眼里都会变得井井有条。这个顺序,能省下后面大把的排查时间。