Files
shiran fe43b9bdce docs(README): 更新文档为中文并完善API参考
- 将README从英文翻译为中文
- 添加详细的API参考文档,包括所有管理接口和枚举值说明
- 补充安装、快速开始、认证方式等使用指南

refactor(client): 优化客户端代码结构并添加详细注释

- 为所有API方法添加中文注释和使用说明
- 改进Client结构体和Option配置的设计
- 统一错误处理和响应结构的文档说明
2026-04-18 15:54:19 +08:00

460 lines
15 KiB
Markdown
Raw Permalink 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.
# email-serverr-cli
Email Server API 的 Go 客户端库。提供管理端(ServiceAuth)与发件端(AppAuth)两种客户端,
覆盖邮件发送、账号、签名、配额、通道/发信、审核、队列、健康检查等全部后端能力。
- 模块路径: `gitea.s1f.ren/shiran/email-serverr-cli`
- 依赖: 仅使用标准库 `net/http``encoding/json`,无第三方依赖
- 风格: 对每一类资源一个文件,请求/响应类型在 `types.go`,底层请求在 `client.go`
## 安装
```bash
go get gitea.s1f.ren/shiran/email-serverr-cli
```
```go
import emailcli "gitea.s1f.ren/shiran/email-serverr-cli"
```
## 快速开始
### 管理客户端(ServiceAuth
```go
client := emailcli.NewServiceClient(
"https://your-server.com",
"your-service-token",
)
accounts, err := client.ListAccounts(context.Background(), emailcli.AccountListQuery{
PaginationQuery: emailcli.PaginationQuery{Page: 1, PageSize: 20},
})
```
### 发件客户端(AppAuth
```go
client := emailcli.NewAppClient(
"https://your-server.com",
"your-app-key",
"your-app-secret",
)
resp, err := client.SendMail(context.Background(), emailcli.SendMailReq{
To: []string{"recipient@example.com"},
Subject: "Hello",
Body: "<h1>Hello World</h1>",
// Channel 可选:不传时优先使用账号默认通道,其次使用允许通道列表首个可用项
})
```
### 客户端选项
```go
client := emailcli.NewServiceClient(baseURL, token,
emailcli.WithTimeout(60*time.Second),
emailcli.WithHTTPClient(customClient),
)
```
## 认证方式
| 模式 | 构造函数 | 请求头 | 适用范围 |
|------|----------|--------|----------|
| ServiceAuth | `NewServiceClient` | `Authorization: Bearer <token>` | 所有 `/api/v1` 管理接口 |
| AppAuth | `NewAppClient` | `X-App-Key` + `X-App-Secret` | 仅 `POST /api/v1/mail/send` |
---
## 枚举值说明
### Account.Status(账号状态)
| 值 | 含义 |
|----|------|
| 0 | 禁用 |
| 1 | 启用 |
### Account.AuditMode(审核模式)
| 值 | 含义 |
|----|------|
| 0 | 免审核(直接入队) |
| 1 | 自动(按规则判定) |
| 2 | 人工(待审核) |
### Signature.Status(签名状态)
| 值 | 含义 |
|----|------|
| 0 | 待审核 |
| 1 | 已通过 |
| 2 | 已驳回 |
### MailLog.Status(邮件状态)
| 值 | 含义 |
|----|------|
| 0 | 待审核 |
| 1 | 排队中 |
| 2 | 发送中 |
| 3 | 成功 |
| 4 | 失败 |
| 5 | 放弃 |
| 6 | 驳回 |
### MailQuota.QuotaType(配额类型)
| 值 | 含义 |
|----|------|
| 1 | 总量配额(`total` 为总发送上限) |
| 2 | 周期配额(到期自动重置) |
### MailQuota.CycleUnit(配额周期单位)
`quota_type=2` 时生效,取值:`day``week``month``year`
### MailQuota.Status
| 值 | 含义 |
|----|------|
| 0 | 禁用 |
| 1 | 启用 |
### Channel.Status / SenderAccount.Status
| 值 | 含义 |
|----|------|
| 0 | 禁用 |
| 1 | 启用 |
### Channel.Strategy(发信挑选策略)
| 值 | 含义 |
|------|------|
| `round_robin` | 轮询(默认) |
| `weight` | 按 `weight` 加权随机 |
| `least_used` | 今日发送数最少优先 |
### AuditRule.Action(规则动作)
| 值 | 含义 |
|----|------|
| 1 | 自动通过 |
| 2 | 自动驳回 |
| 3 | 转人工 |
### AuditRule.RuleType / Target(规则类型与目标)
- `RuleType` 取值: `keyword``regex``domain`
- `Target` 取值: `subject``body``to``from`
### MailAudit.AuditType(审核来源)
| 值 | 含义 |
|----|------|
| 1 | 自动 |
| 2 | 人工 |
### MailAudit.Action(审核动作)
| 值 | 含义 |
|----|------|
| 1 | 通过 |
| 2 | 驳回 |
### SendMailReq.ContentType
取值:`text/plain`(默认)、`text/html`
---
## API 参考
所有管理接口挂载在 `/api/v1` 下。以下按功能模块分组,并给出方法签名、HTTP 路径与关键参数说明。
### 一、发送邮件(AppAuth
#### `SendMail(ctx, req SendMailReq) -> *SendMailResp`
`POST /api/v1/mail/send`
`SendMailReq` 字段:
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `To` | `[]string` | 是 | 收件人列表,至少 1 个 |
| `Cc` | `[]string` | 否 | 抄送 |
| `Bcc` | `[]string` | 否 | 密送 |
| `Subject` | `string` | 是 | 主题 |
| `Body` | `string` | 是 | 正文 |
| `ContentType` | `string` | 否 | 默认 `text/html` |
| `Channel` | `string` | 否 | 通道 `code`。为空时按 账号默认通道 → 账号允许通道首个可用 顺序自动解析 |
| `SignatureID` | `*uint` | 否 | 指定签名 ID(用户必须拥有且已审核) |
| `SignatureTitle` | `string` | 否 | 按 title + user_id 选择签名;未传则使用默认签名 |
| `Attachments` | `[]AttachmentItem` | 否 | 附件(`filename` + base64 `content` |
`SendMailResp` 字段:
| 字段 | 类型 | 说明 |
|------|------|------|
| `MailLogID` | `uint` | 创建的邮件日志 ID |
| `Status` | `string` | `queued` / `pending_audit` / `rejected` |
---
### 二、账号管理(ServiceAuth
#### `CreateAccount(ctx, req CreateAccountReq) -> *CreateAccountResp`
`POST /api/v1/accounts`
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `UserID` | `int` | 是 | 关联用户 ID |
| `Name` | `string` | 是 | 账号名称(≤100) |
| `AuditMode` | `*int8` | 否 | 审核模式枚举,默认 `0` |
| `RateLimit` | `*int` | 否 | 频率限制(封/分钟),`0` 表示不限 |
| `DefaultChannelID` | `*uint` | 否 | 默认发件通道 ID |
| `AllowedChannels` | `string` | 否 | 允许发件通道 ID 列表的 JSON 字符串,例如 `"[1,2]"`。为空时视作不限制 |
| `Remark` | `string` | 否 | 备注 |
返回 `CreateAccountResp`(首次创建返回明文 `AppSecret`):
```json
{ "id": 1, "app_key": "...", "app_secret": "只在此次展示", "name": "..." }
```
#### `ListAccounts(ctx, q AccountListQuery) -> *PaginationResult[Account]`
`GET /api/v1/accounts?page=&page_size=&user_id=&status=&keyword=`
| 参数 | 类型 | 说明 |
|------|------|------|
| `Page`/`PageSize` | `int` | 分页,`PageSize` 默认 20 |
| `UserID` | `*int` | 按用户筛选 |
| `Status` | `*int8` | 账号状态(0/1 |
| `Keyword` | `string` | 模糊匹配 `name``remark` |
#### `GetAccount(ctx, id uint) -> *Account`
`GET /api/v1/accounts/{id}`
#### `UpdateAccount(ctx, id uint, req UpdateAccountReq) -> *Account`
`PUT /api/v1/accounts/{id}`
所有字段均为可选指针,只更新已传字段。
| 字段 | 类型 | 说明 |
|------|------|------|
| `Name` | `*string` | |
| `Status` | `*int8` | 启用/禁用 |
| `AuditMode` | `*int8` | |
| `RateLimit` | `*int` | |
| `DefaultChannelID` | `*uint` | 默认发件通道 |
| `AllowedChannels` | `*string` | 允许发件通道 ID 列表 JSON |
| `DefaultSignatureID` | `*uint` | 默认签名 ID |
| `Remark` | `*string` | |
#### `DeleteAccount(ctx, id uint) error`
`DELETE /api/v1/accounts/{id}`
#### `ResetAccountSecret(ctx, id uint) -> *ResetSecretResp`
`POST /api/v1/accounts/{id}/reset-secret`
返回新生成的明文 `AppSecret`
---
### 三、签名管理(ServiceAuth
#### `CreateSignature(ctx, req CreateSignatureReq) -> *Signature`
`POST /api/v1/signatures`
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `UserID` | `int` | 是 | |
| `AccountID` | `*uint` | 否 | 绑定账号,为空表示用户全局签名 |
| `Title` | `string` | 是 | 中文抬头 |
| `EnglishName` | `string` | 是 | 英文标识(用于组装 From 地址) |
| `Content` | `string` | 否 | HTML 签名内容 |
| `Applicant` | `string` | 否 | 申请人 |
| `ApplicantInfo` | `string` | 否 | 申请说明 |
新建默认为 `Status=0`(待审核)。
#### `ListSignatures(ctx, q SignatureListQuery) -> *PaginationResult[Signature]`
`GET /api/v1/signatures?page=&page_size=&user_id=&account_id=&status=&keyword=`
#### `GetSignature(ctx, id uint)` / `UpdateSignature` / `DeleteSignature`
`GET|PUT|DELETE /api/v1/signatures/{id}`
#### `AuditSignature(ctx, id uint, req AuditSignatureReq)`
`POST /api/v1/signatures/{id}/audit`
| 字段 | 类型 | 说明 |
|------|------|------|
| `Action` | `int8` | `1`=通过,`2`=驳回 |
| `RejectReason` | `string` | 驳回时建议填写 |
| `Auditor` | `string` | 审核人标识 |
---
### 四、邮件日志(ServiceAuth
#### `ListMailLogs(ctx, q MailLogListQuery) -> *PaginationResult[MailLog]`
`GET /api/v1/mail-logs`
| 参数 | 类型 | 说明 |
|------|------|------|
| `UserID` | `*int` | |
| `AccountID` | `*uint` | |
| `Status` | `*int8` | 见 `MailLog.Status` 枚举 |
| `StartDate`/`EndDate` | `string` | `YYYY-MM-DD` |
| `To` | `string` | 收件人精确匹配 |
| `Keyword` | `string` | 模糊匹配主题或收件人 |
#### `GetMailLog(ctx, id uint) -> *MailLogDetail`
`GET /api/v1/mail-logs/{id}` 返回 `MailLog` + 正文 `Body`
#### `GetMailStats(ctx) -> []MailStatItem`
`GET /api/v1/mail-logs/stats``Status` 分组的邮件数统计。
---
### 五、配额管理(ServiceAuth
#### `CreateQuota(ctx, req CreateQuotaReq) -> *MailQuota`
`POST /api/v1/quotas`
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `UserID` | `int` | 是 | |
| `AccountID` | `uint` | 是 | |
| `QuotaType` | `int8` | 是 | `1`=总量,`2`=周期 |
| `Total` | `int` | 是 | 额度上限 |
| `ExpireAt` | `string` | 否 | `YYYY-MM-DD HH:mm:ss` |
| `CycleUnit` | `string` | 否 | `day` / `week` / `month` / `year` |
| `CycleResetAt` | `string` | 否 | 周期起始时间 |
#### `ListQuotas(ctx, q QuotaListQuery)` · `GetQuotaSummary(ctx, accountID)` · `UpdateQuota(ctx, id, req)` · `DeleteQuota(ctx, id)`
`UpdateQuotaReq` 支持局部更新 `Total``Status``ExpireAt``CycleUnit``CycleResetAt`
`QuotaListQuery` 过滤参数:`UserID``AccountID``QuotaType``Status`
---
### 六、通道与发信账号(ServiceAuth
#### 通道 `Channel`
- `CreateChannel(ctx, req CreateChannelReq) -> *Channel``POST /api/v1/channels`
- `ListChannels(ctx, q ChannelListQuery) -> *PaginationResult[Channel]``GET /api/v1/channels`
- `UpdateChannel(ctx, id, req UpdateChannelReq) -> *Channel``PUT /api/v1/channels/{id}`
- `DeleteChannel(ctx, id uint) error``DELETE /api/v1/channels/{id}`
`CreateChannelReq` 字段:
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `Code` | `string` | 是 | 唯一标识(发送邮件用) |
| `Name` | `string` | 是 | |
| `Description` | `string` | 否 | |
| `Strategy` | `string` | 否 | 见 `Channel.Strategy` 枚举 |
#### 发信账号 `SenderAccount`
- `CreateSender(ctx, channelID uint, req CreateSenderReq) -> *SenderAccount``POST /api/v1/channels/{channelID}/senders`
- `ListSendersByChannel(ctx, channelID, q SenderListQuery) -> *PaginationResult[SenderAccount]``GET /api/v1/channels/{channelID}/senders`
- `UpdateSender(ctx, id uint, req UpdateSenderReq) -> *SenderAccount``PUT /api/v1/senders/{id}`
- `DeleteSender(ctx, id uint) error``DELETE /api/v1/senders/{id}`
`CreateSenderReq` 关键字段:`Name``SmtpHost``SmtpPort``SmtpUser``SmtpPassword``SmtpSSL``FromName``FromAddress``DailyLimit``Weight`
---
### 七、审核(ServiceAuth
- `ListAuditPending(ctx, q AuditPendingQuery) -> *PaginationResult[MailLog]``GET /api/v1/audits/pending`
- `GetAuditPendingDetail(ctx, id uint) -> *MailLogDetail``GET /api/v1/audits/pending/{id}`
- `ApproveAudit(ctx, id uint) error``POST /api/v1/audits/{id}/approve`
- `RejectAudit(ctx, id uint, req AuditRejectReq) error``POST /api/v1/audits/{id}/reject`
- `BatchApproveAudit(ctx, req BatchAuditApproveReq) error``POST /api/v1/audits/batch/approve`
- `BatchRejectAudit(ctx, req BatchAuditRejectReq) error``POST /api/v1/audits/batch/reject`
- `ListAuditLogs(ctx, q AuditLogQuery) -> *PaginationResult[MailAudit]``GET /api/v1/audits/logs`
- `GetAuditStats(ctx) -> *AuditStats``GET /api/v1/audits/stats`
`AuditPendingQuery` 过滤:`AccountID``UserID``Keyword`
`AuditLogQuery` 过滤:`AccountID``UserID``Action``AuditType``StartDate``EndDate`
---
### 八、审核规则(ServiceAuth
- `CreateAuditRule(ctx, req CreateAuditRuleReq) -> *AuditRule``POST /api/v1/audit-rules`
- `ListAuditRules(ctx) -> []AuditRule``GET /api/v1/audit-rules`
- `GetAuditRule(ctx, id uint) -> *AuditRule``GET /api/v1/audit-rules/{id}`
- `UpdateAuditRule(ctx, id uint, req UpdateAuditRuleReq) -> *AuditRule``PUT /api/v1/audit-rules/{id}`
- `DeleteAuditRule(ctx, id uint) error``DELETE /api/v1/audit-rules/{id}`
- `UpdateAuditRuleStatus(ctx, id uint, status int8) -> *AuditRule``PUT /api/v1/audit-rules/{id}/status`
- `TestAuditRule(ctx, req TestAuditRuleReq) -> *TestAuditRuleResp``POST /api/v1/audit-rules/test`
`CreateAuditRuleReq` 字段:
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `Name` | `string` | 是 | |
| `RuleType` | `string` | 是 | 见 `AuditRule.RuleType` 枚举 |
| `Target` | `string` | 是 | 见 `AuditRule.Target` 枚举 |
| `Condition` | `string` | 是 | 关键词或正则表达式 |
| `Action` | `int8` | 是 | 见 `AuditRule.Action` 枚举 |
| `Priority` | `int` | 否 | 数字越大优先级越高 |
| `Remark` | `string` | 否 | |
---
### 九、队列(ServiceAuth
- `GetQueueStatus(ctx) -> *QueueStatusData``GET /api/v1/queue/status`
- `ListQueuePending(ctx, q QueuePendingQuery) -> *PaginationResult[MailLog]``GET /api/v1/queue/pending`
- `CancelQueueItem(ctx, mailLogID uint) error``POST /api/v1/queue/{mailLogID}/cancel`
- `RetryQueueItem(ctx, mailLogID uint) error``POST /api/v1/queue/{mailLogID}/retry`
`QueueStatusData` 返回各通道队列长度与延迟队列长度:
```go
type QueueStatusData struct {
Queues map[string]int64 `json:"queues"` // key = channel code
DelayQueue int64 `json:"delay_queue"`
}
```
---
### 十、健康检查(ServiceAuth
- `ListCheckLogs(ctx, q CheckLogQuery) -> *PaginationResult[CheckLog]``GET /api/v1/check-logs`
- `GetCheckSummary(ctx) -> []SenderHealth``GET /api/v1/check-logs/summary`
- `TriggerCheck(ctx, senderAccountID uint) -> *TriggerCheckResp``POST /api/v1/check-logs/trigger/{senderAccountID}`
`CheckLogQuery` 过滤:`SenderAccountID``Received``StartDate``EndDate`
---
## 统一返回结构
后端所有接口统一使用:
```json
{ "code": 200, "message": "ok", "data": { /* */ } }
```
SDK 内部会解包 `data` 并在非 200 时返回 `*APIError`
```go
resp, err := client.SendMail(ctx, req)
if err != nil {
var apiErr *emailcli.APIError
if errors.As(err, &apiErr) {
fmt.Printf("API error: code=%d message=%s\n", apiErr.Code, apiErr.Message)
}
}
```
分页接口统一包装:
```go
type PaginationResult[T any] struct {
List []T `json:"list"`
Total int64 `json:"total"`
Page int `json:"page"`
PageSize int `json:"page_size"`
}
```
## License
MIT