forked from xiaohei/taiji-AI-PAD
更新修改方案
This commit is contained in:
@@ -0,0 +1,567 @@
|
||||
# 渠道资源分配接口整合方案
|
||||
|
||||
> **版本**: v1.0.0
|
||||
> **创建时间**: 2026-01-07
|
||||
> **状态**: 待实施
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景分析
|
||||
|
||||
### 1.1 现有接口
|
||||
|
||||
**`PUT /api/channel/tenants/{tenantId}/resources`** ([`channel.py:244`](services/mcp-server/app/routes/channel.py:244))
|
||||
|
||||
现有请求参数 ([`schemas.py:303`](services/mcp-server/app/schemas.py:303)):
|
||||
```python
|
||||
class AllocateResourcesRequest(BaseModel):
|
||||
agents: List[ResourceAgentAllocation] # 平台端 Agent 分配
|
||||
models: List[ResourceModelAllocation] # 模型资源分配
|
||||
customAgentResources: Optional[CustomAgentResources] # 已废弃
|
||||
customAgentQuota: Optional[CustomAgentQuotaConfig] # 自定义 Agent 配额
|
||||
```
|
||||
|
||||
现有模型分配参数 ([`schemas.py:269`](services/mcp-server/app/schemas.py:269)):
|
||||
```python
|
||||
class ResourceModelAllocation(BaseModel):
|
||||
modelName: str
|
||||
rpm: int
|
||||
tpm: int
|
||||
```
|
||||
|
||||
### 1.2 LiteLLM 新增接口
|
||||
|
||||
**`PUT /api/channel/tenants/{id}/models`** ([`channel.py:1126`](services/mcp-server/app/routes/channel.py:1126))
|
||||
|
||||
新增参数:
|
||||
- `max_budget`: 最大预算
|
||||
- `budget_duration`: 预算周期(monthly/total)
|
||||
|
||||
### 1.3 问题
|
||||
|
||||
1. **功能重叠**:两个接口都可以分配模型给租户
|
||||
2. **参数不一致**:新接口有 `max_budget`、`budget_duration`,旧接口没有
|
||||
3. **计费逻辑不清晰**:配额(quota)与余额(balance)的关系需要明确
|
||||
|
||||
---
|
||||
|
||||
## 2. 整合方案
|
||||
|
||||
### 2.1 方案概述
|
||||
|
||||
**统一使用现有接口** `PUT /api/channel/tenants/{tenantId}/resources`,扩展其参数以支持 LiteLLM 的预算控制功能。
|
||||
|
||||
### 2.2 Schema 修改
|
||||
|
||||
#### 修改 `ResourceModelAllocation` ([`schemas.py:269`](services/mcp-server/app/schemas.py:269))
|
||||
|
||||
```python
|
||||
class ResourceModelAllocation(BaseModel):
|
||||
"""模型资源分配"""
|
||||
modelName: str = Field(..., description="模型名称,如 azure/gpt-4")
|
||||
rpm: int = Field(60, ge=0, description="每分钟请求数限制")
|
||||
tpm: int = Field(10000, ge=0, description="每分钟 Token 数限制")
|
||||
|
||||
# LiteLLM 预算控制(新增)
|
||||
maxBudget: Optional[float] = Field(None, ge=0, description="最大预算金额")
|
||||
budgetDuration: Optional[str] = Field(
|
||||
None,
|
||||
pattern="^(monthly|total)$",
|
||||
description="预算周期:monthly(月度)或 total(总计)"
|
||||
)
|
||||
```
|
||||
|
||||
### 2.3 接口行为修改
|
||||
|
||||
#### 修改 `allocate_tenant_resources` ([`channel.py:244`](services/mcp-server/app/routes/channel.py:244))
|
||||
|
||||
**当前行为**(第 334-350 行):
|
||||
```python
|
||||
for model_alloc in req.models:
|
||||
# 查找模型供应商
|
||||
result = await db.execute(
|
||||
select(ModelProvider).where(ModelProvider.name == model_alloc.modelName)
|
||||
)
|
||||
model_provider = result.scalar_one_or_none()
|
||||
|
||||
if model_provider:
|
||||
allocation = ResourceAllocation(
|
||||
target_id=tenant_id,
|
||||
target_type="tenant",
|
||||
resource_type="model",
|
||||
resource_id=str(model_provider.id),
|
||||
rpm=model_alloc.rpm,
|
||||
tpm=model_alloc.tpm,
|
||||
)
|
||||
db.add(allocation)
|
||||
```
|
||||
|
||||
**修改后行为**:
|
||||
```python
|
||||
for model_alloc in req.models:
|
||||
# 1. 验证渠道是否有该模型的权限
|
||||
model_allocation_result = await db.execute(
|
||||
select(ResourceAllocation).where(
|
||||
and_(
|
||||
ResourceAllocation.target_id == channel_id,
|
||||
ResourceAllocation.target_type == "channel",
|
||||
ResourceAllocation.resource_type == "model",
|
||||
ResourceAllocation.resource_id == model_alloc.modelName
|
||||
)
|
||||
)
|
||||
)
|
||||
if not model_allocation_result.scalar_one_or_none():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"渠道没有模型 '{model_alloc.modelName}' 的权限"
|
||||
)
|
||||
|
||||
# 2. 检查租户是否已有该模型的 Key
|
||||
existing_key_result = await db.execute(
|
||||
select(TenantModelKey).where(
|
||||
and_(
|
||||
TenantModelKey.tenant_id == tenant_id,
|
||||
TenantModelKey.model_name == model_alloc.modelName
|
||||
)
|
||||
)
|
||||
)
|
||||
existing_key = existing_key_result.scalar_one_or_none()
|
||||
|
||||
if existing_key:
|
||||
# 3a. 更新现有 Key 的配额
|
||||
await _update_tenant_model_quota(
|
||||
db=db,
|
||||
tenant_key=existing_key,
|
||||
rpm_limit=model_alloc.rpm,
|
||||
tpm_limit=model_alloc.tpm,
|
||||
max_budget=model_alloc.maxBudget,
|
||||
budget_duration=model_alloc.budgetDuration,
|
||||
)
|
||||
else:
|
||||
# 3b. 创建新的 LiteLLM Key
|
||||
await _create_tenant_model_key(
|
||||
db=db,
|
||||
tenant=tenant,
|
||||
channel=channel,
|
||||
model_name=model_alloc.modelName,
|
||||
rpm_limit=model_alloc.rpm,
|
||||
tpm_limit=model_alloc.tpm,
|
||||
max_budget=model_alloc.maxBudget,
|
||||
budget_duration=model_alloc.budgetDuration,
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 计费逻辑说明
|
||||
|
||||
### 3.1 配额(Quota)vs 余额(Balance)
|
||||
|
||||
| 概念 | 说明 | 控制方式 | 用途 |
|
||||
|------|------|---------|------|
|
||||
| **余额 (Balance)** | 租户账户中的实际金额 | `POST /api/channel/tenants/{id}/recharge` | 实际扣费来源 |
|
||||
| **授信额度 (Credit Limit)** | 允许透支的金额 | `PUT /api/channel/tenants/{id}/credit` | 余额不足时的缓冲 |
|
||||
| **模型配额 (Model Quota)** | 速率限制和预算上限 | `PUT /api/channel/tenants/{id}/resources` | 防止单个模型过度消费 |
|
||||
|
||||
### 3.2 计费流程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[租户调用模型] --> B{检查模型配额}
|
||||
B -->|超过 RPM/TPM| C[拒绝请求 - 429]
|
||||
B -->|超过 maxBudget| D[拒绝请求 - 预算超限]
|
||||
B -->|配额内| E{检查余额}
|
||||
E -->|余额 + 授信 >= 费用| F[扣费并执行]
|
||||
E -->|余额 + 授信 < 费用| G[拒绝请求 - 余额不足]
|
||||
F --> H[更新余额]
|
||||
H --> I[记录计费]
|
||||
```
|
||||
|
||||
### 3.3 配额与余额的关系
|
||||
|
||||
1. **配额是上限控制**:即使租户有 $10000 余额,如果模型配额设置为 `maxBudget=$500/月`,该模型每月最多消费 $500
|
||||
2. **余额是实际扣费来源**:所有模型消费都从租户余额中扣除
|
||||
3. **配额不影响余额**:配额只是限制,不会预扣余额
|
||||
|
||||
### 3.4 示例场景
|
||||
|
||||
**场景**:租户 A 的配置
|
||||
- 余额:$1000
|
||||
- 授信额度:$500
|
||||
- 模型配额:
|
||||
- gpt-4: maxBudget=$300/月, rpm=100
|
||||
- gpt-3.5: maxBudget=$200/月, rpm=200
|
||||
|
||||
**结果**:
|
||||
- 租户可用总额:$1000 + $500 = $1500
|
||||
- gpt-4 每月最多消费 $300(即使余额充足)
|
||||
- gpt-3.5 每月最多消费 $200
|
||||
- 两个模型合计每月最多消费 $500
|
||||
|
||||
---
|
||||
|
||||
## 4. 实施步骤
|
||||
|
||||
### 4.1 Schema 修改
|
||||
|
||||
**文件**: [`services/mcp-server/app/schemas.py`](services/mcp-server/app/schemas.py)
|
||||
|
||||
```python
|
||||
# 修改 ResourceModelAllocation 类(第 269-273 行)
|
||||
class ResourceModelAllocation(BaseModel):
|
||||
"""模型资源分配"""
|
||||
modelName: str = Field(..., description="模型名称,如 azure/gpt-4")
|
||||
rpm: int = Field(60, ge=0, description="每分钟请求数限制")
|
||||
tpm: int = Field(10000, ge=0, description="每分钟 Token 数限制")
|
||||
|
||||
# LiteLLM 预算控制(新增)
|
||||
maxBudget: Optional[float] = Field(None, ge=0, description="最大预算金额")
|
||||
budgetDuration: Optional[str] = Field(
|
||||
None,
|
||||
pattern="^(monthly|total)$",
|
||||
description="预算周期:monthly(月度)或 total(总计)"
|
||||
)
|
||||
```
|
||||
|
||||
### 4.2 接口逻辑修改
|
||||
|
||||
**文件**: [`services/mcp-server/app/routes/channel.py`](services/mcp-server/app/routes/channel.py)
|
||||
|
||||
1. **添加辅助函数**(在文件顶部导入区域后):
|
||||
|
||||
```python
|
||||
async def _create_tenant_model_key(
|
||||
db: AsyncSession,
|
||||
tenant: User,
|
||||
channel: Channel,
|
||||
model_name: str,
|
||||
rpm_limit: int,
|
||||
tpm_limit: int,
|
||||
max_budget: Optional[float],
|
||||
budget_duration: Optional[str],
|
||||
) -> TenantModelKey:
|
||||
"""创建租户的 LiteLLM Key"""
|
||||
from app.litellm_client import get_litellm_client, LiteLLMClientError
|
||||
|
||||
if not channel.litellm_team_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="渠道尚未关联 LiteLLM team,请联系管理员"
|
||||
)
|
||||
|
||||
litellm_client = get_litellm_client()
|
||||
|
||||
key = await litellm_client.generate_key(
|
||||
team_id=channel.litellm_team_id,
|
||||
models=[model_name],
|
||||
rpm_limit=rpm_limit,
|
||||
tpm_limit=tpm_limit,
|
||||
max_budget=max_budget,
|
||||
budget_duration=budget_duration,
|
||||
key_name=f"tenant-{tenant.id}-{model_name}",
|
||||
metadata={
|
||||
"tenant_id": str(tenant.id),
|
||||
"tenant_name": tenant.name,
|
||||
"channel_id": str(channel.id),
|
||||
"channel_name": channel.name,
|
||||
"model": model_name,
|
||||
}
|
||||
)
|
||||
|
||||
encrypted_key = litellm_client.encrypt_key(key.key)
|
||||
|
||||
tenant_key = TenantModelKey(
|
||||
tenant_id=str(tenant.id),
|
||||
channel_id=str(channel.id),
|
||||
model_name=model_name,
|
||||
litellm_key_id=key.key,
|
||||
litellm_key_hash=encrypted_key,
|
||||
rpm_limit=rpm_limit,
|
||||
tpm_limit=tpm_limit,
|
||||
max_budget=max_budget,
|
||||
budget_duration=budget_duration or "monthly",
|
||||
status="active",
|
||||
)
|
||||
db.add(tenant_key)
|
||||
|
||||
return tenant_key
|
||||
|
||||
|
||||
async def _update_tenant_model_quota(
|
||||
db: AsyncSession,
|
||||
tenant_key: TenantModelKey,
|
||||
rpm_limit: Optional[int],
|
||||
tpm_limit: Optional[int],
|
||||
max_budget: Optional[float],
|
||||
budget_duration: Optional[str],
|
||||
) -> None:
|
||||
"""更新租户的 LiteLLM Key 配额"""
|
||||
from app.litellm_client import get_litellm_client, LiteLLMClientError
|
||||
|
||||
litellm_client = get_litellm_client()
|
||||
|
||||
await litellm_client.update_key(
|
||||
key=tenant_key.litellm_key_id,
|
||||
rpm_limit=rpm_limit,
|
||||
tpm_limit=tpm_limit,
|
||||
max_budget=max_budget,
|
||||
budget_duration=budget_duration,
|
||||
)
|
||||
|
||||
# 更新数据库记录
|
||||
if rpm_limit is not None:
|
||||
tenant_key.rpm_limit = rpm_limit
|
||||
if tpm_limit is not None:
|
||||
tenant_key.tpm_limit = tpm_limit
|
||||
if max_budget is not None:
|
||||
tenant_key.max_budget = max_budget
|
||||
if budget_duration is not None:
|
||||
tenant_key.budget_duration = budget_duration
|
||||
```
|
||||
|
||||
2. **修改 `allocate_tenant_resources` 函数**(第 334-350 行):
|
||||
|
||||
```python
|
||||
# 分配模型资源(集成 LiteLLM)
|
||||
for model_alloc in req.models:
|
||||
# 验证渠道是否有该模型的权限
|
||||
model_allocation_result = await db.execute(
|
||||
select(ResourceAllocation).where(
|
||||
and_(
|
||||
ResourceAllocation.target_id == str(channel_id),
|
||||
ResourceAllocation.target_type == "channel",
|
||||
ResourceAllocation.resource_type == "model",
|
||||
ResourceAllocation.resource_id == model_alloc.modelName
|
||||
)
|
||||
)
|
||||
)
|
||||
if not model_allocation_result.scalar_one_or_none():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"渠道没有模型 '{model_alloc.modelName}' 的权限"
|
||||
)
|
||||
|
||||
# 检查租户是否已有该模型的 Key
|
||||
existing_key_result = await db.execute(
|
||||
select(TenantModelKey).where(
|
||||
and_(
|
||||
TenantModelKey.tenant_id == tenant_id,
|
||||
TenantModelKey.model_name == model_alloc.modelName
|
||||
)
|
||||
)
|
||||
)
|
||||
existing_key = existing_key_result.scalar_one_or_none()
|
||||
|
||||
try:
|
||||
if existing_key:
|
||||
# 更新现有 Key 的配额
|
||||
await _update_tenant_model_quota(
|
||||
db=db,
|
||||
tenant_key=existing_key,
|
||||
rpm_limit=model_alloc.rpm,
|
||||
tpm_limit=model_alloc.tpm,
|
||||
max_budget=model_alloc.maxBudget,
|
||||
budget_duration=model_alloc.budgetDuration,
|
||||
)
|
||||
logger.info(f"更新租户 {tenant.name} 的模型 {model_alloc.modelName} 配额")
|
||||
else:
|
||||
# 获取渠道信息
|
||||
channel_result = await db.execute(
|
||||
select(Channel).where(Channel.id == channel_id)
|
||||
)
|
||||
channel = channel_result.scalar_one_or_none()
|
||||
|
||||
if channel and channel.litellm_team_id:
|
||||
# 创建新的 LiteLLM Key
|
||||
await _create_tenant_model_key(
|
||||
db=db,
|
||||
tenant=tenant,
|
||||
channel=channel,
|
||||
model_name=model_alloc.modelName,
|
||||
rpm_limit=model_alloc.rpm,
|
||||
tpm_limit=model_alloc.tpm,
|
||||
max_budget=model_alloc.maxBudget,
|
||||
budget_duration=model_alloc.budgetDuration,
|
||||
)
|
||||
logger.info(f"为租户 {tenant.name} 创建模型 {model_alloc.modelName} 的 LiteLLM Key")
|
||||
else:
|
||||
# 渠道未配置 LiteLLM,仅记录 ResourceAllocation
|
||||
logger.warning(f"渠道 {channel_id} 未配置 LiteLLM team,仅记录资源分配")
|
||||
|
||||
# 同时记录到 ResourceAllocation(兼容旧逻辑)
|
||||
allocation = ResourceAllocation(
|
||||
target_id=tenant_id,
|
||||
target_type="tenant",
|
||||
resource_type="model",
|
||||
resource_id=model_alloc.modelName,
|
||||
rpm=model_alloc.rpm,
|
||||
tpm=model_alloc.tpm,
|
||||
)
|
||||
db.add(allocation)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"模型分配失败: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"模型 '{model_alloc.modelName}' 分配失败: {str(e)}"
|
||||
)
|
||||
```
|
||||
|
||||
### 4.3 保留独立接口
|
||||
|
||||
保留以下独立接口用于精细化管理:
|
||||
|
||||
| 接口 | 用途 | 说明 |
|
||||
|------|------|------|
|
||||
| `GET /api/channel/tenants/{id}/models` | 获取租户模型列表 | 查看已分配的模型及配额 |
|
||||
| `DELETE /api/channel/tenants/{id}/models/{model}` | 取消单个模型分配 | 精细化管理 |
|
||||
| `PUT /api/channel/tenants/{id}/models/{model}/quota` | 更新单个模型配额 | 精细化管理 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 前端对接说明
|
||||
|
||||
### 5.1 现有接口扩展
|
||||
|
||||
前端调用 `PUT /api/channel/tenants/{tenantId}/resources` 时,`models` 数组中的每个元素可以包含新增字段:
|
||||
|
||||
**请求示例**:
|
||||
```json
|
||||
{
|
||||
"agents": [
|
||||
{"agentId": "echo_agent", "quantity": 3}
|
||||
],
|
||||
"models": [
|
||||
{
|
||||
"modelName": "azure/gpt-4",
|
||||
"rpm": 100,
|
||||
"tpm": 50000,
|
||||
"maxBudget": 500.0,
|
||||
"budgetDuration": "monthly"
|
||||
},
|
||||
{
|
||||
"modelName": "azure/gpt-3.5-turbo",
|
||||
"rpm": 200,
|
||||
"tpm": 100000,
|
||||
"maxBudget": 200.0,
|
||||
"budgetDuration": "monthly"
|
||||
}
|
||||
],
|
||||
"customAgentQuota": {
|
||||
"cpuQuota": 2.0,
|
||||
"memoryQuota": 4.0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 新增字段说明
|
||||
|
||||
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|
||||
|------|------|------|--------|------|
|
||||
| `maxBudget` | float | 否 | null | 最大预算金额(美元),null 表示不限制 |
|
||||
| `budgetDuration` | string | 否 | "monthly" | 预算周期:`monthly`(月度重置)或 `total`(总计不重置) |
|
||||
|
||||
### 5.3 向后兼容
|
||||
|
||||
- 如果前端不传 `maxBudget` 和 `budgetDuration`,行为与之前一致
|
||||
- 新字段为可选,不影响现有前端代码
|
||||
|
||||
---
|
||||
|
||||
## 6. 测试用例
|
||||
|
||||
### 6.1 基本功能测试
|
||||
|
||||
```bash
|
||||
# 1. 分配模型(不带预算控制)
|
||||
curl -X PUT "http://localhost:8002/api/channel/tenants/{tenant_id}/resources" \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"models": [
|
||||
{"modelName": "azure/gpt-4", "rpm": 100, "tpm": 50000}
|
||||
]
|
||||
}'
|
||||
|
||||
# 2. 分配模型(带预算控制)
|
||||
curl -X PUT "http://localhost:8002/api/channel/tenants/{tenant_id}/resources" \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"models": [
|
||||
{
|
||||
"modelName": "azure/gpt-4",
|
||||
"rpm": 100,
|
||||
"tpm": 50000,
|
||||
"maxBudget": 500.0,
|
||||
"budgetDuration": "monthly"
|
||||
}
|
||||
]
|
||||
}'
|
||||
|
||||
# 3. 查看租户模型列表
|
||||
curl -X GET "http://localhost:8002/api/channel/tenants/{tenant_id}/models" \
|
||||
-H "Authorization: Bearer {token}"
|
||||
|
||||
# 4. 更新单个模型配额
|
||||
curl -X PUT "http://localhost:8002/api/channel/tenants/{tenant_id}/models/azure%2Fgpt-4/quota?rpm_limit=200&max_budget=1000" \
|
||||
-H "Authorization: Bearer {token}"
|
||||
|
||||
# 5. 取消模型分配
|
||||
curl -X DELETE "http://localhost:8002/api/channel/tenants/{tenant_id}/models/azure%2Fgpt-4" \
|
||||
-H "Authorization: Bearer {token}"
|
||||
```
|
||||
|
||||
### 6.2 边界条件测试
|
||||
|
||||
1. **渠道无模型权限**:应返回 403 错误
|
||||
2. **重复分配同一模型**:应更新配额而非创建新记录
|
||||
3. **渠道未配置 LiteLLM**:应记录 ResourceAllocation 但跳过 LiteLLM Key 创建
|
||||
4. **预算超限**:LiteLLM 应拒绝请求
|
||||
|
||||
---
|
||||
|
||||
## 7. 文档更新
|
||||
|
||||
### 7.1 需要更新的文档
|
||||
|
||||
1. [`Docs/渠道合作伙伴平台-接口对接文档.md`](Docs/渠道合作伙伴平台-接口对接文档.md) - 更新分配资源接口说明
|
||||
2. [`plans/LiteLLM集成接口变动清单.md`](plans/LiteLLM集成接口变动清单.md) - 标注接口整合情况
|
||||
|
||||
### 7.2 接口文档更新
|
||||
|
||||
**`PUT /api/channel/tenants/{tenantId}/resources`**
|
||||
|
||||
新增请求参数:
|
||||
|
||||
| 参数路径 | 类型 | 必填 | 说明 |
|
||||
|----------|------|------|------|
|
||||
| `models[].maxBudget` | float | 否 | 最大预算金额(美元) |
|
||||
| `models[].budgetDuration` | string | 否 | 预算周期:monthly/total |
|
||||
|
||||
---
|
||||
|
||||
## 8. 总结
|
||||
|
||||
### 8.1 修改范围
|
||||
|
||||
| 文件 | 修改内容 |
|
||||
|------|----------|
|
||||
| `schemas.py` | 扩展 `ResourceModelAllocation` 类 |
|
||||
| `channel.py` | 修改 `allocate_tenant_resources` 函数,添加辅助函数 |
|
||||
|
||||
### 8.2 影响评估
|
||||
|
||||
- **前端影响**:无(新字段可选,向后兼容)
|
||||
- **数据库影响**:无(使用现有 `TenantModelKey` 表)
|
||||
- **LiteLLM 影响**:无(使用现有 LiteLLM 客户端)
|
||||
|
||||
### 8.3 待确认事项
|
||||
|
||||
1. **预算超限时的行为**:是否需要通知渠道管理员?
|
||||
2. **预算重置时间**:月度预算是每月 1 日重置还是从分配日期开始计算?
|
||||
3. **多模型预算汇总**:是否需要显示租户所有模型的预算使用汇总?
|
||||
|
||||
---
|
||||
|
||||
*文档创建时间: 2026-01-07*
|
||||
Reference in New Issue
Block a user