huace-aigc-oss-proxy-client 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. huace_aigc_oss_proxy_client-0.1.0/DESIGN.md +498 -0
  2. huace_aigc_oss_proxy_client-0.1.0/LICENSE +21 -0
  3. huace_aigc_oss_proxy_client-0.1.0/MANIFEST.in +7 -0
  4. huace_aigc_oss_proxy_client-0.1.0/PKG-INFO +145 -0
  5. huace_aigc_oss_proxy_client-0.1.0/QUICK_START.txt +56 -0
  6. huace_aigc_oss_proxy_client-0.1.0/README.md +106 -0
  7. huace_aigc_oss_proxy_client-0.1.0/huace_aigc_oss/__init__.py +60 -0
  8. huace_aigc_oss_proxy_client-0.1.0/huace_aigc_oss/backends/__init__.py +3 -0
  9. huace_aigc_oss_proxy_client-0.1.0/huace_aigc_oss/backends/aliyun.py +120 -0
  10. huace_aigc_oss_proxy_client-0.1.0/huace_aigc_oss/backends/base.py +34 -0
  11. huace_aigc_oss_proxy_client-0.1.0/huace_aigc_oss/backends/qiniu.py +119 -0
  12. huace_aigc_oss_proxy_client-0.1.0/huace_aigc_oss/backends/rustfs.py +135 -0
  13. huace_aigc_oss_proxy_client-0.1.0/huace_aigc_oss/circuit.py +47 -0
  14. huace_aigc_oss_proxy_client-0.1.0/huace_aigc_oss/client.py +197 -0
  15. huace_aigc_oss_proxy_client-0.1.0/huace_aigc_oss/config.py +172 -0
  16. huace_aigc_oss_proxy_client-0.1.0/huace_aigc_oss/exceptions.py +48 -0
  17. huace_aigc_oss_proxy_client-0.1.0/huace_aigc_oss/factory.py +47 -0
  18. huace_aigc_oss_proxy_client-0.1.0/huace_aigc_oss/router.py +34 -0
  19. huace_aigc_oss_proxy_client-0.1.0/huace_aigc_oss/session.py +43 -0
  20. huace_aigc_oss_proxy_client-0.1.0/huace_aigc_oss/types.py +23 -0
  21. huace_aigc_oss_proxy_client-0.1.0/huace_aigc_oss_proxy_client.egg-info/PKG-INFO +145 -0
  22. huace_aigc_oss_proxy_client-0.1.0/huace_aigc_oss_proxy_client.egg-info/SOURCES.txt +27 -0
  23. huace_aigc_oss_proxy_client-0.1.0/huace_aigc_oss_proxy_client.egg-info/dependency_links.txt +1 -0
  24. huace_aigc_oss_proxy_client-0.1.0/huace_aigc_oss_proxy_client.egg-info/requires.txt +23 -0
  25. huace_aigc_oss_proxy_client-0.1.0/huace_aigc_oss_proxy_client.egg-info/top_level.txt +1 -0
  26. huace_aigc_oss_proxy_client-0.1.0/pyproject.toml +58 -0
  27. huace_aigc_oss_proxy_client-0.1.0/setup.cfg +4 -0
  28. huace_aigc_oss_proxy_client-0.1.0/tests/test_config.py +19 -0
  29. huace_aigc_oss_proxy_client-0.1.0/tests/test_session.py +19 -0
@@ -0,0 +1,498 @@
1
+ # huace-aigc-oss-proxy-client — PyPI SDK 设计文档
2
+
3
+ > **包名(PyPI)**:`huace-aigc-oss-proxy-client`
4
+ > **导入名**:`huace_aigc_oss`(建议,避免连字符)
5
+ > **定位**:面向 AIGC 插件与 Celery Worker 的统一对象存储 SDK,通过环境变量配置多后端、优先级与降级策略,对业务完全屏蔽 RustFS / 阿里云 OSS / 七牛云差异。
6
+
7
+ ---
8
+
9
+ ## 1. 背景与目标
10
+
11
+ 当前各插件(`plugin-tts-indextts`、`plugin-voiceprint-recognition-v2` 等)各自实现 `oss_util.py` / `oss_service.py`,仅对接阿里云 OSS,且下载无内网优先、上传无「内外网模式」区分。
12
+
13
+ 本 SDK 目标:
14
+
15
+ | 目标 | 说明 |
16
+ |------|------|
17
+ | 统一入口 | 业务只依赖 `OssClient`,不感知底层实现 |
18
+ | 多后端可插拔 | RustFS(自定义 S3 兼容内网)、阿里云 OSS、七牛云 Kodo |
19
+ | 环境变量驱动 | 12-Factor,容器 / K8s / docker-compose 零代码改配置 |
20
+ | 下载智能降级 | 按优先级尝试,失败自动下一跳;**进程内会话记忆**加速后续请求 |
21
+ | 上传强可控 | 默认内网、可指定外网,**禁止**跨存储自动降级 |
22
+ | 可发布 PyPI | 标准 `pyproject.toml`、语义化版本、可选 extras |
23
+
24
+ ---
25
+
26
+ ## 2. 核心规则(业务契约)
27
+
28
+ ### 2.1 下载(Download)
29
+
30
+ ```
31
+ 优先级链(可配置) → 例如:rustfs → aliyun → qiniu
32
+ ```
33
+
34
+ 1. 按 `OSS_DOWNLOAD_PRIORITY` 顺序依次尝试。
35
+ 2. 下列情况视为「当前后端失败」,触发下一跳:
36
+ - 连接超时 / 服务不可达
37
+ - HTTP/ SDK 层请求异常
38
+ - **对象不存在**(404 / NoSuchKey)
39
+ 3. 某一后端成功后,可选地将该 `object_key`(或 key 前缀)记入**进程内会话缓存**,同进程后续同 key 的 `download` / `exists` **跳过**已失败的后端,直接命中上次成功的后端(见 §5.3)。
40
+ 4. 全部后端失败 → 抛出统一异常 `OssDownloadError`,附带各后端最后一次错误摘要。
41
+
42
+ ### 2.2 上传(Upload)
43
+
44
+ | 模式 | 行为 |
45
+ |------|------|
46
+ | `UploadMode.INTRANET`(默认) | 路由到「上传优先级」中第一个**已启用**的内网类后端(通常为 RustFS) |
47
+ | `UploadMode.EXTRANET` | 路由到指定的外网后端(如 `aliyun`),**不经过** RustFS |
48
+ | 失败 | 直接向上抛出,**不**尝试其他存储 |
49
+
50
+ > 上传不做自动降级,保证对象落盘位置可预期、可审计。
51
+
52
+ ### 2.3 存在性检查(Exists)
53
+
54
+ 与 **下载** 共用同一套优先级与降级逻辑(先内网 RustFS,失败再外网等)。
55
+
56
+ ---
57
+
58
+ ## 3. 整体架构(四层)
59
+
60
+ ```mermaid
61
+ flowchart TB
62
+ subgraph L1["1. 业务调用层"]
63
+ APP["插件 / Celery Task"]
64
+ end
65
+
66
+ subgraph L2["2. 调度核心层"]
67
+ CLIENT["OssClient"]
68
+ ROUTER["PriorityRouter"]
69
+ FALLBACK["DownloadFallbackController"]
70
+ STICKY["SessionStickyStore"]
71
+ CB["CircuitBreaker 可选"]
72
+ end
73
+
74
+ subgraph L3["3. 存储适配层"]
75
+ IFACE["StorageBackend Protocol"]
76
+ RUST["RustFsBackend"]
77
+ ALI["AliyunOssBackend"]
78
+ QIN["QiniuKodoBackend"]
79
+ end
80
+
81
+ subgraph L4["4. 配置层"]
82
+ CFG["OssConfig.from_env()"]
83
+ ENV["环境变量 / .env"]
84
+ end
85
+
86
+ APP --> CLIENT
87
+ CLIENT --> ROUTER
88
+ CLIENT --> FALLBACK
89
+ CLIENT --> STICKY
90
+ ROUTER --> IFACE
91
+ FALLBACK --> IFACE
92
+ RUST & ALI & QIN --> IFACE
93
+ CFG --> ENV
94
+ CFG --> CLIENT
95
+ ```
96
+
97
+ | 层级 | 职责 | 关键类型 |
98
+ |------|------|----------|
99
+ | 业务调用层 | 只调 `download` / `upload` / `exists` | 各插件 `oss_service` 薄封装 |
100
+ | 调度核心层 | 优先级排序、降级、会话粘性、日志与指标 | `OssClient`, `PriorityRouter` |
101
+ | 存储适配层 | 统一接口,各云厂商独立实现 | `StorageBackend` |
102
+ | 配置层 | 解析 env、默认值、校验 | `OssConfig` |
103
+
104
+ ---
105
+
106
+ ## 4. 统一存储接口(适配层)
107
+
108
+ 所有后端实现同一抽象,调度层**仅**依赖此接口:
109
+
110
+ ```python
111
+ # huace_aigc_oss/backends/base.py
112
+
113
+ from typing import Protocol, BinaryIO, Union
114
+ from pathlib import Path
115
+
116
+ class StorageBackend(Protocol):
117
+ name: str # "rustfs" | "aliyun" | "qiniu"
118
+
119
+ def download(self, key: str, local_path: Union[str, Path]) -> None: ...
120
+ def upload(self, key: str, source: Union[str, Path, bytes, BinaryIO]) -> None: ...
121
+ def exists(self, key: str) -> bool: ...
122
+ def delete(self, key: str) -> None: ... # 可选,v1.1
123
+
124
+ def health_check(self) -> bool: ... # 启动时可选探测
125
+ ```
126
+
127
+ ### 4.1 RustFS(自定义 OSS / 内网)
128
+
129
+ - 协议:**S3 兼容 API**(MinIO 客户端或 `boto3`)。
130
+ - 典型场景:机房内网 `http://rustfs.internal:9000`。
131
+ - 独立超时、重试,与公网 OSS 客户端实例隔离。
132
+
133
+ ### 4.2 阿里云 OSS
134
+
135
+ - 依赖:`oss2`(与现有 `oss_util.py` 一致)。
136
+ - 配置:`OSS_ALIYUN_*` 或兼容旧变量 `OSS_ACCESS_KEY_ID` 等(见 §7)。
137
+
138
+ ### 4.3 七牛云 Kodo
139
+
140
+ - 依赖:`qiniu` SDK。
141
+ - 配置:`OSS_QINIU_*`。
142
+ - v1.0 可与 RustFS + 阿里云同版发布;未配置时自动从优先级链剔除。
143
+
144
+ ---
145
+
146
+ ## 5. 调度核心实现原理
147
+
148
+ ### 5.1 下载:优先调用 + 异常捕获兜底
149
+
150
+ ```text
151
+ for backend in download_priority:
152
+ if session.should_skip(key, backend): continue
153
+ try:
154
+ backend.download(key, path)
155
+ session.remember_success(key, backend)
156
+ return
157
+ except (NotFound, Timeout, ConnectionError, BackendError) as e:
158
+ session.remember_failure(key, backend)
159
+ last_errors[backend] = e
160
+ continue
161
+ raise OssDownloadError(key, last_errors)
162
+ ```
163
+
164
+ - 每个 backend **独立** `connect_timeout` / `read_timeout` / `max_retries`。
165
+ - 两次链路互不阻塞:RustFS 超时不会占用阿里云的超时预算(各自计时)。
166
+
167
+ ### 5.2 上传:参数路由、无兜底
168
+
169
+ ```python
170
+ def upload(self, key: str, source: UploadSource, mode: UploadMode = UploadMode.INTRANET) -> UploadResult:
171
+ backend = self._router.resolve_upload(mode) # 确定性单点
172
+ backend.upload(key, source) # 失败即抛,不 fallback
173
+ return UploadResult(key=key, backend=backend.name, url=self._public_url(backend, key))
174
+ ```
175
+
176
+ `UploadMode.EXTRANET` 时通过 `OSS_UPLOAD_EXTRANET_BACKEND=aliyun` 指定目标(默认 `aliyun`)。
177
+
178
+ ### 5.3 进程内会话记忆(Sticky Session)
179
+
180
+ **需求**:本地 RustFS 不可达或文件不存在后,**本次进程生命周期内**同 object key 后续请求直接用阿里云,避免重复打挂掉的内网。
181
+
182
+ | 数据结构 | 说明 |
183
+ |----------|------|
184
+ | `success_map: dict[str, BackendName]` | key → 上次成功的后端 |
185
+ | `skip_map: dict[str, set[BackendName]]` | key → 已失败、本轮跳过的后端 |
186
+ | `prefix_map: dict[str, BackendName]` | 可选:按 key 前缀粘性(如某 task 目录整体走外网) |
187
+
188
+ **查找顺序**(`download` / `exists`):
189
+
190
+ 1. 若 `key in success_map` → 仅用该 backend(失败则清除并走完整优先级链)。
191
+ 2. 否则按 `OSS_DOWNLOAD_PRIORITY` 遍历,跳过 `skip_map[key]` 中的 backend。
192
+ 3. 成功后写入 `success_map`;失败写入 `skip_map`。
193
+
194
+ > 作用域:**单进程**(Celery worker 子进程、API 单 worker)。多进程不共享;需要跨进程时可后续接 Redis(v2,非 v1 范围)。
195
+
196
+ ### 5.4 故障隔离与熔断(可选)
197
+
198
+ - 各 backend 独立连接池 / 客户端单例。
199
+ - `OSS_CIRCUIT_BREAKER_ENABLED=true` 时:RustFS 连续失败 N 次 → 短期内**全局**跳过 RustFS(仅下载/exists),减少对不可用内网的等待。
200
+ - 与 key 级 sticky 正交:熔断是后端级,sticky 是 key 级。
201
+
202
+ ---
203
+
204
+ ## 6. 对外 API(极简)
205
+
206
+ ```python
207
+ from huace_aigc_oss import OssClient, UploadMode, OssConfig
208
+
209
+ # 推荐:从环境变量构造(容器内零参数)
210
+ client = OssClient.from_env()
211
+
212
+ # 或显式配置(测试 / 本地)
213
+ # client = OssClient(OssConfig(...))
214
+
215
+ # 下载:内网优先,自动降级外网
216
+ local_path = client.download("plugin-tts/task-1/out.wav", "/tmp/out.wav")
217
+
218
+ # 上传:默认内网 RustFS
219
+ client.upload("plugin-tts/task-1/out.wav", "/tmp/out.wav")
220
+
221
+ # 上传:强制阿里云外网
222
+ client.upload(
223
+ "plugin-tts/task-1/out.wav",
224
+ "/tmp/out.wav",
225
+ mode=UploadMode.EXTRANET,
226
+ )
227
+
228
+ # 存在性:与下载相同优先级
229
+ if client.exists("plugin-tts/task-1/out.wav"):
230
+ ...
231
+ ```
232
+
233
+ ### 6.1 异常体系
234
+
235
+ | 异常 | 场景 |
236
+ |------|------|
237
+ | `OssConfigError` | 环境变量缺失、优先级配置非法 |
238
+ | `OssDownloadError` | 所有下载后端均失败 |
239
+ | `OssUploadError` | 上传单后端失败 |
240
+ | `OssNotFoundError` | 所有后端均报告不存在 |
241
+
242
+ ### 6.2 返回值(建议)
243
+
244
+ ```python
245
+ @dataclass
246
+ class UploadResult:
247
+ key: str
248
+ backend: str
249
+ url: str | None # 若该后端支持生成公网 URL
250
+ ```
251
+
252
+ ---
253
+
254
+ ## 7. 环境变量规范
255
+
256
+ ### 7.1 全局
257
+
258
+ | 变量 | 默认值 | 说明 |
259
+ |------|--------|------|
260
+ | `OSS_DOWNLOAD_PRIORITY` | `rustfs,aliyun` | 逗号分隔,从左到右 |
261
+ | `OSS_UPLOAD_INTRANET_BACKEND` | `rustfs` | 内网默认上传目标 |
262
+ | `OSS_UPLOAD_EXTRANET_BACKEND` | `aliyun` | `UploadMode.EXTRANET` 目标 |
263
+ | `OSS_KEY_PREFIX` | `""` | 全局 key 前缀,自动 strip/join `/` |
264
+ | `OSS_FALLBACK_ENABLED` | `true` | `false` 时下载仅尝试第一后端 |
265
+ | `OSS_STICKY_SESSION_ENABLED` | `true` | 进程内 key 级记忆 |
266
+ | `OSS_CIRCUIT_BREAKER_ENABLED` | `false` | 内网熔断 |
267
+ | `OSS_CIRCUIT_BREAKER_THRESHOLD` | `5` | 连续失败次数 |
268
+ | `OSS_CIRCUIT_BREAKER_COOLDOWN_SEC` | `60` | 熔断恢复时间 |
269
+ | `OSS_MAX_OBJECT_SIZE_MB` | `5120` | 上传大小上限 |
270
+
271
+ ### 7.2 RustFS(自定义内网 OSS)
272
+
273
+ | 变量 | 默认值 | 说明 |
274
+ |------|--------|------|
275
+ | `OSS_RUSTFS_ENABLED` | `false` | 是否参与优先级链 |
276
+ | `OSS_RUSTFS_ENDPOINT` | — | 如 `http://10.0.0.5:9000` |
277
+ | `OSS_RUSTFS_ACCESS_KEY` | — | |
278
+ | `OSS_RUSTFS_SECRET_KEY` | — | |
279
+ | `OSS_RUSTFS_BUCKET` | — | |
280
+ | `OSS_RUSTFS_REGION` | `us-east-1` | S3 兼容 region |
281
+ | `OSS_RUSTFS_USE_SSL` | `false` | |
282
+ | `OSS_RUSTFS_CONNECT_TIMEOUT_SEC` | `3` | 内网宜短 |
283
+ | `OSS_RUSTFS_READ_TIMEOUT_SEC` | `30` | |
284
+ | `OSS_RUSTFS_MAX_RETRIES` | `2` | |
285
+
286
+ ### 7.3 阿里云 OSS
287
+
288
+ | 变量 | 默认值 | 说明 |
289
+ |------|--------|------|
290
+ | `OSS_ALIYUN_ENABLED` | `true` | |
291
+ | `OSS_ALIYUN_ENDPOINT` | `https://oss-cn-hangzhou.aliyuncs.com` | |
292
+ | `OSS_ALIYUN_ACCESS_KEY_ID` | — | **兼容** `OSS_ACCESS_KEY_ID` |
293
+ | `OSS_ALIYUN_ACCESS_KEY_SECRET` | — | **兼容** `OSS_ACCESS_KEY_SECRET` |
294
+ | `OSS_ALIYUN_BUCKET` | — | **兼容** `OSS_BUCKET_NAME` |
295
+ | `OSS_ALIYUN_CONNECT_TIMEOUT_SEC` | `10` | |
296
+ | `OSS_ALIYUN_READ_TIMEOUT_SEC` | `120` | |
297
+ | `OSS_ALIYUN_MAX_RETRIES` | `3` | |
298
+
299
+ ### 7.4 七牛云
300
+
301
+ | 变量 | 默认值 | 说明 |
302
+ |------|--------|------|
303
+ | `OSS_QINIU_ENABLED` | `false` | |
304
+ | `OSS_QINIU_ACCESS_KEY` | — | |
305
+ | `OSS_QINIU_SECRET_KEY` | — | |
306
+ | `OSS_QINIU_BUCKET` | — | |
307
+ | `OSS_QINIU_DOMAIN` | — | 下载域名,可选 |
308
+ | `OSS_QINIU_CONNECT_TIMEOUT_SEC` | `10` | |
309
+ | `OSS_QINIU_MAX_RETRIES` | `3` | |
310
+
311
+ ### 7.5 配置示例:RustFS 优先,阿里云其次
312
+
313
+ ```bash
314
+ # 启用双后端,下载 RustFS → 阿里云
315
+ OSS_RUSTFS_ENABLED=true
316
+ OSS_RUSTFS_ENDPOINT=http://rustfs.internal:9000
317
+ OSS_RUSTFS_ACCESS_KEY=minioadmin
318
+ OSS_RUSTFS_SECRET_KEY=minioadmin
319
+ OSS_RUSTFS_BUCKET=aigc-intranet
320
+
321
+ OSS_ALIYUN_ENABLED=true
322
+ OSS_ALIYUN_ENDPOINT=https://oss-cn-hangzhou.aliyuncs.com
323
+ OSS_ALIYUN_ACCESS_KEY_ID=***
324
+ OSS_ALIYUN_ACCESS_KEY_SECRET=***
325
+ OSS_ALIYUN_BUCKET=huace-shortmovie
326
+
327
+ OSS_DOWNLOAD_PRIORITY=rustfs,aliyun
328
+ OSS_UPLOAD_INTRANET_BACKEND=rustfs
329
+ OSS_UPLOAD_EXTRANET_BACKEND=aliyun
330
+ OSS_KEY_PREFIX=plugin-tts-indextts
331
+ OSS_STICKY_SESSION_ENABLED=true
332
+ OSS_FALLBACK_ENABLED=true
333
+ ```
334
+
335
+ **运行时行为**:
336
+
337
+ 1. 首次 `download("foo/bar.wav")`:请求 RustFS → 超时或 404 → 自动阿里云 → 成功 → `success_map["foo/bar.wav"] = aliyun`。
338
+ 2. 同进程第二次 `download("foo/bar.wav")`:跳过 RustFS,直连阿里云。
339
+ 3. `upload(..., INTRANET)`:仅 RustFS;失败不转阿里云。
340
+ 4. `upload(..., EXTRANET)`:仅阿里云。
341
+
342
+ ---
343
+
344
+ ## 8. 项目结构(PyPI 包)
345
+
346
+ ```text
347
+ huace-aigc-oss-proxy-client/
348
+ ├── pyproject.toml # hatchling / setuptools, dynamic version
349
+ ├── README.md
350
+ ├── DESIGN.md # 本文档
351
+ ├── LICENSE
352
+ ├── src/
353
+ │ └── huace_aigc_oss/
354
+ │ ├── __init__.py # 导出 OssClient, UploadMode
355
+ │ ├── client.py # OssClient 门面
356
+ │ ├── config.py # OssConfig.from_env()
357
+ │ ├── router.py # PriorityRouter
358
+ │ ├── fallback.py # DownloadFallbackController
359
+ │ ├── session.py # SessionStickyStore
360
+ │ ├── circuit.py # CircuitBreaker(可选)
361
+ │ ├── exceptions.py
362
+ │ ├── types.py # UploadMode, UploadResult
363
+ │ └── backends/
364
+ │ ├── base.py
365
+ │ ├── rustfs.py # boto3 / minio
366
+ │ ├── aliyun.py # oss2
367
+ │ └── qiniu.py
368
+ └── tests/
369
+ ├── test_download_fallback.py
370
+ ├── test_upload_routing.py
371
+ ├── test_sticky_session.py
372
+ └── conftest.py # moto / 本地 MinIO fixture
373
+ ```
374
+
375
+ ### 8.1 依赖分组(`pyproject.toml` extras)
376
+
377
+ ```toml
378
+ [project.optional-dependencies]
379
+ aliyun = ["oss2>=2.18.0"]
380
+ rustfs = ["boto3>=1.34.0"]
381
+ qiniu = ["qiniu>=7.12.0"]
382
+ all = ["huace-aigc-oss-proxy-client[aliyun,rustfs,qiniu]"]
383
+ dev = ["pytest", "moto", "pytest-env"]
384
+ ```
385
+
386
+ 默认 `pip install huace-aigc-oss-proxy-client[all]` 供插件使用;仅外网场景可 `pip install huace-aigc-oss-proxy-client[aliyun]`。
387
+
388
+ ### 8.2 版本与发布
389
+
390
+ - 语义化版本:`0.1.0` 起,API 稳定后 `1.0.0`。
391
+ - CI:`pytest` → `python -m build` → `twine upload`(PyPI token 在 CI secret)。
392
+ - 包名与导入名分离:`pip install huace-aigc-oss-proxy-client`,`import huace_aigc_oss`。
393
+
394
+ ---
395
+
396
+ ## 9. 日志与可观测性
397
+
398
+ | 事件 | 级别 | 字段 |
399
+ |------|------|------|
400
+ | 下载尝试 | DEBUG | `key`, `backend`, `attempt` |
401
+ | 下载降级 | WARNING | `key`, `from_backend`, `to_backend`, `reason` |
402
+ | 粘性命中 | DEBUG | `key`, `backend` |
403
+ | 上传完成 | INFO | `key`, `backend`, `mode`, `bytes` |
404
+ | 熔断打开 | WARNING | `backend`, `cooldown_sec` |
405
+
406
+ 建议结构化日志(`extra={}`),便于 ELK 统计「内网命中率 / 降级率」。
407
+
408
+ ---
409
+
410
+ ## 10. 与现有插件集成
411
+
412
+ ### 10.1 替换方式
413
+
414
+ 将各插件 `oss_service.py` 收敛为薄包装:
415
+
416
+ ```python
417
+ from huace_aigc_oss import OssClient, UploadMode
418
+
419
+ _client: OssClient | None = None
420
+
421
+ def get_client() -> OssClient:
422
+ global _client
423
+ if _client is None:
424
+ _client = OssClient.from_env()
425
+ return _client
426
+
427
+ def upload_local_file(local_path: str, oss_key: str, extranet: bool = False) -> str:
428
+ mode = UploadMode.EXTRANET if extranet else UploadMode.INTRANET
429
+ result = get_client().upload(oss_key, local_path, mode=mode)
430
+ return result.url or oss_key
431
+ ```
432
+
433
+ ### 10.2 环境变量迁移
434
+
435
+ | 旧变量(插件内) | 新变量(SDK) |
436
+ |------------------|---------------|
437
+ | `OSS_ENDPOINT` | `OSS_ALIYUN_ENDPOINT`(SDK 同时读旧名) |
438
+ | `OSS_ACCESS_KEY_ID` | `OSS_ALIYUN_ACCESS_KEY_ID` |
439
+ | `OSS_BUCKET_NAME` | `OSS_ALIYUN_BUCKET` |
440
+ | `OSS_NAME_PREFIX` | `OSS_KEY_PREFIX` |
441
+
442
+ 保证**向后兼容**:`OssConfig.from_env()` 先读 `OSS_ALIYUN_*`,缺失则 fallback 到现有 `OSS_*` 名称。
443
+
444
+ ---
445
+
446
+ ## 11. 测试策略
447
+
448
+ | 类型 | 内容 |
449
+ |------|------|
450
+ | 单元 | Router 排序、sticky 读写、熔断状态机 |
451
+ | 集成 | moto 模拟 S3 + 本地 MinIO 模拟 RustFS |
452
+ | 契约 | 各 backend 对同一 key 的 put/get/exists 行为一致 |
453
+ | 故障 | RustFS 超时后降级阿里云;上传失败不降级 |
454
+
455
+ ---
456
+
457
+ ## 12. 非目标(v1 不做)
458
+
459
+ - 跨进程 / 分布式 sticky(Redis)
460
+ - 上传自动降级、双写、异步复制
461
+ - 分片上传大文件统一封装(可 v1.1 按 backend 暴露)
462
+ - 预签名 URL 统一生成(各后端 API 差异大,v1.1)
463
+
464
+ ---
465
+
466
+ ## 13. 实施里程碑
467
+
468
+ | 阶段 | 交付 |
469
+ |------|------|
470
+ | M1 | `OssConfig` + `AliyunOssBackend` + `download`/`upload`/`exists` 单后端 |
471
+ | M2 | `RustFsBackend` + 下载降级 + sticky session |
472
+ | M3 | `UploadMode.EXTRANET` + 旧 env 兼容 + 插件试点替换 |
473
+ | M4 | `QiniuKodoBackend` + 熔断 + PyPI 0.1.0 |
474
+ | M5 | 文档、集成测试、全插件 requirements 切换 |
475
+
476
+ ---
477
+
478
+ ## 14. 附录:下载降级触发条件一览
479
+
480
+ | 条件 | 是否触发下一后端 |
481
+ |------|------------------|
482
+ | RustFS 连接超时 | 是 |
483
+ | RustFS 服务不可达(连接拒绝) | 是 |
484
+ | 对象不存在 | 是 |
485
+ | 4xx/5xx(非鉴权致命) | 是(可配置) |
486
+ | 鉴权失败(403 AK 错误) | **否**(立即失败,避免误降级) |
487
+ | 阿里云成功 | 否(结束) |
488
+
489
+ | 上传场景 | 是否跨后端重试 |
490
+ |----------|----------------|
491
+ | 内网模式 RustFS 失败 | 否 |
492
+ | 外网模式阿里云失败 | 否 |
493
+
494
+ ---
495
+
496
+ **文档版本**:1.0
497
+ **最后更新**:2026-06-02
498
+ **维护**:AIGC 插件平台组
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Huace
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,7 @@
1
+ include README.md
2
+ include LICENSE
3
+ include QUICK_START.txt
4
+ include DESIGN.md
5
+ recursive-include huace_aigc_oss *.py
6
+ global-exclude __pycache__
7
+ global-exclude *.pyc
@@ -0,0 +1,145 @@
1
+ Metadata-Version: 2.4
2
+ Name: huace-aigc-oss-proxy-client
3
+ Version: 0.1.0
4
+ Summary: 华策 AIGC OSS Proxy Client - 统一 RustFS / 阿里云 OSS / 七牛云,支持下载降级与上传路由
5
+ Author-email: Huace <support@huace.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/huace/huace-aigc-oss-proxy-client
8
+ Project-URL: Repository, https://github.com/huace/huace-aigc-oss-proxy-client
9
+ Keywords: aigc,oss,huace,sdk,rustfs,aliyun,s3
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.8
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Requires-Python: >=3.8
20
+ Description-Content-Type: text/markdown
21
+ Provides-Extra: aliyun
22
+ Requires-Dist: oss2>=2.18.0; extra == "aliyun"
23
+ Provides-Extra: rustfs
24
+ Requires-Dist: boto3>=1.34.0; extra == "rustfs"
25
+ Provides-Extra: qiniu
26
+ Requires-Dist: qiniu>=7.12.0; extra == "qiniu"
27
+ Requires-Dist: requests>=2.20.0; extra == "qiniu"
28
+ Provides-Extra: all
29
+ Requires-Dist: oss2>=2.18.0; extra == "all"
30
+ Requires-Dist: boto3>=1.34.0; extra == "all"
31
+ Requires-Dist: qiniu>=7.12.0; extra == "all"
32
+ Requires-Dist: requests>=2.20.0; extra == "all"
33
+ Provides-Extra: dev
34
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
35
+ Requires-Dist: pytest-env>=1.0.0; extra == "dev"
36
+ Requires-Dist: moto[s3]>=5.0.0; extra == "dev"
37
+ Requires-Dist: build>=1.0.0; extra == "dev"
38
+ Requires-Dist: twine>=5.0.0; extra == "dev"
39
+
40
+ # AIGC OSS Proxy Python SDK
41
+
42
+ [![Python Version](https://img.shields.io/pypi/pyversions/huace-aigc-oss-proxy-client.svg)](https://pypi.org/project/huace-aigc-oss-proxy-client/)
43
+
44
+ 面向华策 AIGC 插件的统一对象存储 SDK:内网 **RustFS**(S3 兼容)、**阿里云 OSS**、**七牛云**,支持下载自动降级与上传内外网路由。
45
+
46
+ 详细设计见 [DESIGN.md](./DESIGN.md)。
47
+
48
+ ## 安装
49
+
50
+ ```bash
51
+ # 全量后端
52
+ pip install huace-aigc-oss-proxy-client[all]
53
+
54
+ # 仅阿里云
55
+ pip install huace-aigc-oss-proxy-client[aliyun]
56
+ ```
57
+
58
+ ## 本地 RustFS 开发环境
59
+
60
+ ```bash
61
+ docker compose -f docker-compose/rustfs/docker-compose.yml up -d
62
+ ```
63
+
64
+ 控制台:http://127.0.0.1:9001 (`minioadmin` / `minioadmin`)
65
+ API:`http://127.0.0.1:9000`,默认桶 `aigc-intranet`。
66
+
67
+ ## 环境变量
68
+
69
+ ```bash
70
+ # RustFS 内网
71
+ OSS_RUSTFS_ENABLED=true
72
+ OSS_RUSTFS_ENDPOINT=http://127.0.0.1:9000
73
+ OSS_RUSTFS_ACCESS_KEY=minioadmin
74
+ OSS_RUSTFS_SECRET_KEY=minioadmin
75
+ OSS_RUSTFS_BUCKET=aigc-intranet
76
+
77
+ # 阿里云(兼容旧变量 OSS_ACCESS_KEY_ID 等)
78
+ OSS_ALIYUN_ENABLED=true
79
+ OSS_ALIYUN_ENDPOINT=https://oss-cn-hangzhou.aliyuncs.com
80
+ OSS_ALIYUN_ACCESS_KEY_ID=***
81
+ OSS_ALIYUN_ACCESS_KEY_SECRET=***
82
+ OSS_ALIYUN_BUCKET=huace-shortmovie
83
+
84
+ # 策略
85
+ OSS_DOWNLOAD_PRIORITY=rustfs,aliyun
86
+ OSS_UPLOAD_INTRANET_BACKEND=rustfs
87
+ OSS_UPLOAD_EXTRANET_BACKEND=aliyun
88
+ OSS_KEY_PREFIX=plugin-tts-indextts
89
+ OSS_STICKY_SESSION_ENABLED=true
90
+ OSS_FALLBACK_ENABLED=true
91
+ ```
92
+
93
+ ## 快速开始
94
+
95
+ ```python
96
+ from huace_aigc_oss import OssClient, UploadMode
97
+
98
+ client = OssClient.from_env()
99
+
100
+ # 下载:RustFS 优先,失败自动切阿里云;同进程会记住成功的后端
101
+ local = client.download("task-1/output.wav", "/tmp/output.wav")
102
+
103
+ # 上传:默认内网 RustFS,无自动降级
104
+ client.upload("task-1/output.wav", "/tmp/output.wav")
105
+
106
+ # 上传:强制外网阿里云
107
+ client.upload("task-1/output.wav", "/tmp/output.wav", mode=UploadMode.EXTRANET)
108
+
109
+ client.exists("task-1/output.wav")
110
+ ```
111
+
112
+ ## 插件集成示例
113
+
114
+ ```python
115
+ from huace_aigc_oss import OssClient, UploadMode
116
+
117
+ _client = None
118
+
119
+ def get_client() -> OssClient:
120
+ global _client
121
+ if _client is None:
122
+ _client = OssClient.from_env()
123
+ return _client
124
+
125
+ def upload_local_file(local_path: str, oss_key: str, extranet: bool = False) -> str:
126
+ mode = UploadMode.EXTRANET if extranet else UploadMode.INTRANET
127
+ result = get_client().upload(oss_key, local_path, mode=mode)
128
+ return result.url or oss_key
129
+ ```
130
+
131
+ ## 发布
132
+
133
+ 见 [PUBLISH.md](./PUBLISH.md)。复制 `.pypirc.example` 为 `.pypirc` 并填入 PyPI Token。
134
+
135
+ ```bash
136
+ python build_and_publish.py
137
+ ```
138
+
139
+ ## 核心行为
140
+
141
+ | 操作 | 行为 |
142
+ |------|------|
143
+ | `download` | 按优先级尝试,失败降级;进程内 sticky 记住成功后端 |
144
+ | `upload` | 确定性单后端,失败不降级 |
145
+ | `exists` | 与 download 相同优先级链 |