Files
email-serverr-cli/client.go
T
shiran 5b4774e9c1 feat: 添加邮件服务器客户端库的基础功能
- 新增完整的Go客户端库实现,支持邮件服务器API的各种操作
- 实现账户管理、签名管理、邮件发送、审计、配额、通道等功能模块
- 提供ServiceAuth和AppAuth两种认证模式的客户端
- 添加详细的README文档,包含安装指南和使用示例
- 配置.gitignore文件以忽略构建产物和开发工具配置
- 支持分页查询、错误处理和客户端选项配置
2026-04-18 10:36:45 +08:00

154 lines
3.6 KiB
Go

package emailcli
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
)
type Client struct {
baseURL string
serviceToken string
appKey string
appSecret string
httpClient *http.Client
}
type Option func(*Client)
func WithHTTPClient(hc *http.Client) Option {
return func(c *Client) { c.httpClient = hc }
}
func WithTimeout(d time.Duration) Option {
return func(c *Client) { c.httpClient.Timeout = d }
}
// NewServiceClient creates a client authenticated with a service token (management APIs).
func NewServiceClient(baseURL, serviceToken string, opts ...Option) *Client {
c := &Client{
baseURL: strings.TrimRight(baseURL, "/"),
serviceToken: serviceToken,
httpClient: &http.Client{Timeout: 30 * time.Second},
}
for _, o := range opts {
o(c)
}
return c
}
// NewAppClient creates a client authenticated with AppKey/AppSecret (mail sending API).
func NewAppClient(baseURL, appKey, appSecret string, opts ...Option) *Client {
c := &Client{
baseURL: strings.TrimRight(baseURL, "/"),
appKey: appKey,
appSecret: appSecret,
httpClient: &http.Client{Timeout: 30 * time.Second},
}
for _, o := range opts {
o(c)
}
return c
}
type APIResponse[T any] struct {
Code int `json:"code"`
Message string `json:"message"`
Data T `json:"data,omitempty"`
}
type APIError struct {
Code int
Message string
}
func (e *APIError) Error() string {
return fmt.Sprintf("api error %d: %s", e.Code, e.Message)
}
func (c *Client) url(path string) string {
return c.baseURL + path
}
func (c *Client) setAuth(req *http.Request) {
if c.serviceToken != "" {
req.Header.Set("Authorization", "Bearer "+c.serviceToken)
}
if c.appKey != "" {
req.Header.Set("X-App-Key", c.appKey)
req.Header.Set("X-App-Secret", c.appSecret)
}
}
func doRequest[T any](c *Client, ctx context.Context, method, path string, body interface{}, query url.Values) (T, error) {
var zero T
var bodyReader io.Reader
if body != nil {
b, err := json.Marshal(body)
if err != nil {
return zero, fmt.Errorf("marshal body: %w", err)
}
bodyReader = bytes.NewReader(b)
}
reqURL := c.url(path)
if len(query) > 0 {
reqURL += "?" + query.Encode()
}
req, err := http.NewRequestWithContext(ctx, method, reqURL, bodyReader)
if err != nil {
return zero, fmt.Errorf("new request: %w", err)
}
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
c.setAuth(req)
resp, err := c.httpClient.Do(req)
if err != nil {
return zero, fmt.Errorf("do request: %w", err)
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return zero, fmt.Errorf("read response: %w", err)
}
var apiResp APIResponse[T]
if err := json.Unmarshal(respBody, &apiResp); err != nil {
return zero, fmt.Errorf("unmarshal response (status %d): %w\nbody: %s", resp.StatusCode, err, string(respBody))
}
if apiResp.Code != 200 {
return zero, &APIError{Code: apiResp.Code, Message: apiResp.Message}
}
return apiResp.Data, nil
}
func get[T any](c *Client, ctx context.Context, path string, query url.Values) (T, error) {
return doRequest[T](c, ctx, http.MethodGet, path, nil, query)
}
func post[T any](c *Client, ctx context.Context, path string, body interface{}) (T, error) {
return doRequest[T](c, ctx, http.MethodPost, path, body, nil)
}
func put[T any](c *Client, ctx context.Context, path string, body interface{}) (T, error) {
return doRequest[T](c, ctx, http.MethodPut, path, body, nil)
}
func del[T any](c *Client, ctx context.Context, path string) (T, error) {
return doRequest[T](c, ctx, http.MethodDelete, path, nil, nil)
}