Files
cellbot/internal/adapter/milky/api_client.go
xiaolan f3a72264af chore: update dependencies and refactor webhook handling
- Added new dependencies for SQLite support and improved HTTP client functionality in go.mod and go.sum.
- Refactored webhook server implementation to utilize a simplified version, enhancing code maintainability.
- Updated API client to leverage a generic request method, streamlining API interactions.
- Modified configuration to include access token for webhook server, improving security.
- Enhanced event handling and request processing in the API client for better performance.
2026-01-05 18:42:45 +08:00

138 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package milky
import (
"context"
"fmt"
"time"
"cellbot/pkg/net"
"github.com/bytedance/sonic"
"go.uber.org/zap"
)
// APIClient Milky API 客户端
// 使用 pkg/net.HTTPClient 提供的通用 HTTP 请求功能
type APIClient struct {
httpClient *net.HTTPClient
baseURL string
accessToken string
logger *zap.Logger
}
// NewAPIClient 创建 API 客户端
func NewAPIClient(baseURL, accessToken string, timeout time.Duration, retryCount int, logger *zap.Logger) *APIClient {
if timeout == 0 {
timeout = 30 * time.Second
}
if retryCount == 0 {
retryCount = 3
}
config := net.HTTPClientConfig{
BaseURL: baseURL,
Timeout: timeout,
RetryCount: retryCount,
}
return &APIClient{
httpClient: net.NewHTTPClient(config, logger.Named("api-client"), nil),
baseURL: baseURL,
accessToken: accessToken,
logger: logger.Named("api-client"),
}
}
// Call 调用 API
// endpoint: API 端点名称(如 "send_private_message"
// input: 输入参数(会被序列化为 JSON
// 返回: 响应数据和错误
func (c *APIClient) Call(ctx context.Context, endpoint string, input interface{}) (*APIResponse, error) {
// 序列化输入参数
var inputData []byte
var err error
if input == nil {
inputData = []byte("{}")
} else {
inputData, err = sonic.Marshal(input)
if err != nil {
return nil, fmt.Errorf("failed to marshal input: %w", err)
}
}
// 构建 URL
url := fmt.Sprintf("%s/api/%s", c.baseURL, endpoint)
c.logger.Debug("Calling API",
zap.String("endpoint", endpoint),
zap.String("url", url))
return c.doRequest(ctx, url, inputData)
}
// doRequest 执行单次请求
func (c *APIClient) doRequest(ctx context.Context, url string, inputData []byte) (*APIResponse, error) {
// 使用 pkg/net.HTTPClient 的通用请求方法
config := net.GenericRequestConfig{
URL: url,
Method: "POST",
Body: inputData,
AccessToken: c.accessToken,
}
resp, err := c.httpClient.DoGenericRequest(ctx, config)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
// 检查 HTTP 状态码
switch resp.StatusCode {
case 401:
return nil, fmt.Errorf("unauthorized: access token invalid or missing")
case 404:
return nil, fmt.Errorf("API not found: %s", url)
case 415:
return nil, fmt.Errorf("unsupported media type: Content-Type must be application/json")
case 200:
// 继续处理
default:
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
// 解析响应
var apiResp APIResponse
if err := sonic.Unmarshal(resp.Body, &apiResp); err != nil {
return nil, fmt.Errorf("failed to parse response: %w", err)
}
// 检查业务状态
if apiResp.Status != "ok" {
c.logger.Warn("API call failed",
zap.String("status", apiResp.Status),
zap.Int("retcode", apiResp.RetCode),
zap.String("message", apiResp.Message))
return &apiResp, fmt.Errorf("API error (retcode=%d): %s", apiResp.RetCode, apiResp.Message)
}
c.logger.Debug("API call succeeded",
zap.String("status", apiResp.Status),
zap.Int("retcode", apiResp.RetCode))
return &apiResp, nil
}
// CallWithoutRetry 调用 API不重试
// 注意pkg/net.HTTPClient 的通用请求已经内置了重试机制
// 这个方法保留是为了向后兼容,但仍然会使用底层的重试机制
func (c *APIClient) CallWithoutRetry(ctx context.Context, endpoint string, input interface{}) (*APIResponse, error) {
return c.Call(ctx, endpoint, input)
}
// Close 关闭客户端
func (c *APIClient) Close() error {
// pkg/net.HTTPClient 的 fasthttp.Client 不需要显式关闭
return nil
}