feat: 添加邮件服务器客户端库的基础功能
- 新增完整的Go客户端库实现,支持邮件服务器API的各种操作 - 实现账户管理、签名管理、邮件发送、审计、配额、通道等功能模块 - 提供ServiceAuth和AppAuth两种认证模式的客户端 - 添加详细的README文档,包含安装指南和使用示例 - 配置.gitignore文件以忽略构建产物和开发工具配置 - 支持分页查询、错误处理和客户端选项配置
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user