# Clinic Internal API Client

统一的 Clinic 内部 API 客户端库,用于 `clinic_data_service` 调用 Clinic 的内部 API。

## 功能

- ✅ 获取集群数据 URLs 和过滤条件
- ✅ 统一的错误处理
- ✅ 可配置的超时时间
- ✅ 支持自定义 HTTP 客户端(方便测试)

## 包路径

```
github.com/pingcap-inc/clinic/clinic_data_service/pkg/clinic-internal-api
```

## 使用示例

### 在 clinic_data_service 中使用

```go
import "github.com/pingcap-inc/clinic/clinic_data_service/pkg/clinic-internal-api"

client := clinic-internal-api.NewClient("http://clinic-internal-api:80")
urls, err := client.GetClusterDataURLs(context.Background(), "10150563229127790608")
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Loki URL: %s\n", urls.LokiURL)
```

### 创建客户端

```go
// 使用默认地址
client := clinic-internal-api.NewClient("") // 默认: http://clinic-internal-api:80

// 指定地址
client := clinic-internal-api.NewClient("http://clinic-internal-api:80")

// 自定义超时
client := clinic-internal-api.NewClientWithTimeout(
    "http://clinic-internal-api:80",
    30 * time.Second,
)
```

## API 文档

### GetClusterDataURLs

获取集群的数据 URLs 和过滤条件。

```go
func (c *Client) GetClusterDataURLs(ctx context.Context, clusterID string) (*ClusterDataURLs, error)
```

**返回结构:**

```go
type ClusterDataURLs struct {
    ClusterName         string            `json:"clusterName"`
    ClusterID           string            `json:"clusterId"`
    Region              string            `json:"region"`
    Provider            string            `json:"provider"`
    ClusterMetricsURL   string            `json:"clusterMetricsUrl,omitempty"`
    SlowQueryS3URL      string            `json:"slowQueryS3Url,omitempty"`
    TopSQLS3URL         string            `json:"topsqlS3Url,omitempty"`
    DataPlaneMetricsURL string            `json:"dataPlaneMetricsUrl,omitempty"`
    LokiURL             string            `json:"lokiUrl,omitempty"`
    // ... 其他字段
}
```

## 使用场景

### 1. Log URLs

`ClusterDataURLs.LokiURL` is a historical field. Business TiDB cluster logs are routed by the Clinic backend `/log/query` path through the Grafana `o11y-loki-data` datasource. Clinic Data Service only keeps the system logs and serverless parent logs proxy paths.

### 2. DataProxy Handler

```go
// pkg/handlers/dataproxy_handler.go
type DataProxyHandler struct {
    clinicClient *clinic-internal-api.Client
}

func (h *DataProxyHandler) Query(c *gin.Context) {
    clusterURLs, err := h.clinicClient.GetClusterDataURLs(ctx, req.ClusterId)
    if err != nil {
        // 处理错误
        return
    }

    // 使用 clusterURLs.SlowQueryS3URL 或 clusterURLs.TopSQLS3URL
    req.DataSource = clusterURLs.SlowQueryS3URL
}
```

### 3. MetricsQuery Service

```go
// pkg/service/metrics_query_service.go
type MetricsQueryService struct {
    clinicClient     *clinic-internal-api.Client
    dataPlaneMetrics map[string]bool
}

func (s *MetricsQueryService) getPrometheusURL(ctx context.Context, clusterID, query string) (string, error) {
    clusterURLs, err := s.clinicClient.GetClusterDataURLs(ctx, clusterID)
    if err != nil {
        return "", fmt.Errorf("failed to fetch cluster data URLs: %w", err)
    }

    // 使用 clusterURLs.ClusterMetricsURL 或 clusterURLs.DataPlaneMetricsURL
    return clusterURLs.ClusterMetricsURL, nil
}
```

## 测试

运行单元测试:

```bash
go test ./pkg/clinic-internal-api -v
```

### 测试示例

```go
func TestYourHandler(t *testing.T) {
    // 创建 mock 服务器
    server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        json.NewEncoder(w).Encode(&clinic-internal-api.ClusterDataURLs{
            LokiURL: "https://loki.example.com",
        })
    }))
    defer server.Close()

    // 使用 mock 服务器
    client := clinic-internal-api.NewClient(server.URL)
    // ...
}
```

## 错误处理

```go
urls, err := client.GetClusterDataURLs(ctx, clusterID)
if err != nil {
    if strings.Contains(err.Error(), "cluster_id is required") {
        // 参数错误
    } else if strings.Contains(err.Error(), "returned status 404") {
        // 集群不存在
    } else if strings.Contains(err.Error(), "failed to execute request") {
        // 网络错误
    }
    return err
}
```

## 配置

### 在 clinic_data_service 中配置

```yaml
# configs/config-dataproxy.yaml
clinic_api_base: "http://clinic-internal-api:80"
```

```go
// pkg/server/router.go
clinicAPIBase := dataProxyCfg.ClinicAPIBase
if clinicAPIBase == "" {
    clinicAPIBase = "http://clinic-internal-api:80"
}

lokiHandler := handlers.NewLokiHandler(clinicAPIBase)
```

## 性能

- **默认超时**: 10 秒
- **连接复用**: HTTP 客户端自动复用连接
- **Context 支持**: 支持通过 context 取消请求

## 与 clinic 主服务的关系

此客户端是 `clinic_data_service` 模块的一部分,专门用于该服务调用 clinic 主服务的内部 API。

如果 clinic 主服务或其他服务也需要类似功能,建议:
1. 复制此代码到对应模块
2. 或提取为独立的共享模块

## License

Copyright © 2026 PingCAP, Inc.
