feat: init sms-server-cli SDK

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
shiran
2026-06-05 15:03:55 +08:00
commit 69be4bcb82
11 changed files with 1508 additions and 0 deletions
+272
View File
@@ -0,0 +1,272 @@
# sms-server-cli
短信服务 Go SDK,用于对接 [sms-server](https://gitea.s1f.ren/shiran/sms-server) 的 HTTP API。
```
模块路径: gitea.s1f.ren/shiran/sms-server-cli
Go 版本: 1.21+
```
## 安装
```bash
go get gitea.s1f.ren/shiran/sms-server-cli
```
## 快速开始
### 使用 ServiceToken(服务端对服务端)
```go
package main
import (
"context"
"fmt"
"log"
smscli "gitea.s1f.ren/shiran/sms-server-cli"
)
func main() {
client := smscli.NewServiceClient("https://sms.example.com", "your-service-token")
ctx := context.Background()
// 创建签名
sig, err := client.CreateSignature(ctx, smscli.CreateSignatureReq{
Title: "我的应用",
ApplicantName: "张三",
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("签名 ID: %d\n", sig.ID)
// 批量发送短信
resp, err := client.SendBatch(ctx, smscli.SendBatchReq{
SignatureID: sig.ID,
TemplateID: 1,
Params: map[string]string{"code": "123456"},
Phones: []string{"13800138000"},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("消息 ID: %s, 计费条数: %d\n", resp.MsgID, resp.FeeCount)
}
```
### 使用 UserToken(用户令牌)
```go
client := smscli.NewUserTokenClient("https://sms.example.com", "your-user-token")
resp, err := client.SendBatch(ctx, smscli.SendBatchReq{
SignatureID: 1,
TemplateID: 2,
Params: map[string]string{"code": "654321"},
Phones: []string{"13900139000"},
})
```
## 认证方式
| 方式 | 构造函数 | Header | 适用场景 |
|------|---------|--------|---------|
| ServiceAuth | `NewServiceClient(baseURL, token)` | `Authorization: ServiceToken <token>` | 服务端对服务端调用 |
| BearerAuth | `NewBearerClient(baseURL, token)` | `Authorization: Bearer <token>` | 已登录用户调用 |
| UserTokenAuth | `NewUserTokenClient(baseURL, token)` | `Authorization: UserToken <token>` | 用户令牌调用 |
## 枚举值
### ReviewStatus(审核状态)
| 常量 | 值 | 说明 |
|------|---|------|
| `ReviewStatusDraft` | 0 | 草稿 |
| `ReviewStatusReviewing` | 1 | 审核中 |
| `ReviewStatusApproved` | 2 | 已通过 |
| `ReviewStatusRejected` | 3 | 已驳回 |
### QuotaType(额度类型)
| 常量 | 值 | 说明 |
|------|---|------|
| `QuotaTypeLongTerm` | 1 | 长期额度 |
| `QuotaTypeShortTerm` | 2 | 短期额度 |
| `QuotaTypeCycle` | 3 | 周期额度 |
### SendStatus(发送状态)
| 常量 | 值 | 说明 |
|------|---|------|
| `SendStatusPending` | 0 | 待发送 |
| `SendStatusSubmitted` | 1 | 已提交 |
| `SendStatusSuccess` | 2 | 发送成功 |
| `SendStatusFailed` | 3 | 发送失败 |
| `SendStatusRejected` | 4 | 已拒绝 |
### AuthType(认证类型)
| 常量 | 值 | 说明 |
|------|---|------|
| `AuthTypeAuthToken` | 1 | 登录令牌 |
| `AuthTypeServiceToken` | 2 | 服务令牌 |
| `AuthTypeUserToken` | 3 | 用户令牌 |
## API 参考
### 签名管理(Signature
#### 用户接口
| 方法 | 说明 |
|------|------|
| `CreateSignature(ctx, req)` | 创建签名 |
| `ListSignatures(ctx, query)` | 获取签名列表 |
| `GetSignature(ctx, id)` | 获取签名详情 |
| `UpdateSignature(ctx, id, req)` | 更新签名 |
| `DeleteSignature(ctx, id)` | 删除签名 |
| `SubmitSignature(ctx, id)` | 提交签名审核 |
#### 管理员接口
| 方法 | 说明 |
|------|------|
| `AdminListSignatures(ctx, query)` | 获取签名列表(支持 UserID/Status 筛选) |
| `AdminGetSignature(ctx, id)` | 获取签名详情 |
| `AdminCreateSignature(ctx, req)` | 创建签名(可指定用户) |
| `AdminUpdateSignature(ctx, id, req)` | 更新签名 |
| `AdminDeleteSignature(ctx, id)` | 删除签名 |
| `AdminSubmitSignature(ctx, id)` | 提交签名审核 |
| `ApproveSignature(ctx, id)` | 审核通过签名 |
| `RejectSignature(ctx, id, req)` | 驳回签名 |
### 模板管理(Template
#### 用户接口
| 方法 | 说明 |
|------|------|
| `CreateTemplate(ctx, req)` | 创建模板 |
| `ListTemplates(ctx, query)` | 获取模板列表 |
| `GetTemplate(ctx, id)` | 获取模板详情 |
| `UpdateTemplate(ctx, id, req)` | 更新模板 |
| `DeleteTemplate(ctx, id)` | 删除模板 |
| `SubmitTemplate(ctx, id)` | 提交模板审核 |
| `ListRecommendedTemplates(ctx, query)` | 获取推荐模板列表 |
#### 管理员接口
| 方法 | 说明 |
|------|------|
| `AdminListTemplates(ctx, query)` | 获取模板列表(支持 UserID/Status 筛选) |
| `AdminListRecommendedTemplates(ctx, query)` | 获取推荐模板列表 |
| `AdminGetTemplate(ctx, id)` | 获取模板详情 |
| `AdminCreateTemplate(ctx, req)` | 创建模板(可指定用户) |
| `AdminUpdateTemplate(ctx, id, req)` | 更新模板 |
| `AdminDeleteTemplate(ctx, id)` | 删除模板 |
| `AdminSubmitTemplate(ctx, id)` | 提交模板审核 |
| `ApproveTemplate(ctx, id)` | 审核通过模板 |
| `RejectTemplate(ctx, id, req)` | 驳回模板 |
| `CreateRecommendedTemplate(ctx, req)` | 创建推荐模板 |
| `UpdateRecommendedTemplate(ctx, id, req)` | 更新推荐模板 |
| `DeleteRecommendedTemplate(ctx, id)` | 删除推荐模板 |
### 用户令牌管理(Token
#### 用户接口
| 方法 | 说明 |
|------|------|
| `CreateUserToken(ctx, req)` | 创建令牌 |
| `ListUserTokens(ctx, query)` | 获取令牌列表 |
| `GetUserToken(ctx, id)` | 获取令牌详情 |
| `UpdateUserToken(ctx, id, req)` | 更新令牌 |
| `DeleteUserToken(ctx, id)` | 删除令牌 |
| `ToggleUserToken(ctx, id)` | 切换令牌启用/禁用 |
#### 管理员接口
| 方法 | 说明 |
|------|------|
| `AdminListUserTokens(ctx, query)` | 获取令牌列表(支持 UserID/Status 筛选) |
| `AdminGetUserToken(ctx, id)` | 获取令牌详情 |
| `AdminCreateUserToken(ctx, req)` | 创建令牌(可指定用户) |
| `AdminUpdateUserToken(ctx, id, req)` | 更新令牌 |
| `AdminDeleteUserToken(ctx, id)` | 删除令牌 |
| `AdminToggleUserToken(ctx, id)` | 切换令牌启用/禁用 |
### 额度管理(Quota
#### 用户接口
| 方法 | 说明 |
|------|------|
| `ListQuotas(ctx, query)` | 获取额度列表 |
| `GetQuotaSummary(ctx)` | 获取额度汇总 |
#### 管理员接口
| 方法 | 说明 |
|------|------|
| `AdminListQuotas(ctx, query)` | 获取额度列表(支持 UserID 筛选) |
| `AdminGetQuotaSummary(ctx, userID)` | 获取指定用户的额度汇总 |
| `CreateQuota(ctx, req)` | 创建额度 |
| `UpdateQuota(ctx, id, req)` | 更新额度 |
| `DeleteQuota(ctx, id)` | 删除额度 |
### 发送管理(Send
#### 用户接口
| 方法 | 说明 |
|------|------|
| `SendBatch(ctx, req)` | 批量发送(相同内容,多号码) |
| `SendMulti(ctx, req)` | 个性化群发(不同参数,多号码) |
| `ListSendRecords(ctx, query)` | 获取发送记录列表 |
| `GetSendStatus(ctx, msgID)` | 查询发送状态 |
#### 管理员接口
| 方法 | 说明 |
|------|------|
| `AdminListSendRecords(ctx, query)` | 获取发送记录列表(支持 UserID 筛选) |
| `AdminGetSendStatus(ctx, msgID)` | 查询发送状态 |
### 适配器管理(Adapter
#### 管理员接口
| 方法 | 说明 |
|------|------|
| `GetAdapterBalance(ctx)` | 查询适配器余额 |
## 响应结构
所有 API 的 HTTP 响应统一为以下 JSON 结构:
```json
{
"code": 0,
"msg": "success",
"data": { }
}
```
SDK 会自动处理响应解析,`code != 0` 时返回包含 `msg` 的 error。
分页列表接口返回 `PaginationResult[T]`
```json
{
"list": [],
"total": 100,
"page": 1
}
```
## License
MIT
+13
View File
@@ -0,0 +1,13 @@
package smscli
import "context"
// ──────────────────────────────────────────────
// 管理员接口
// ──────────────────────────────────────────────
// GetAdapterBalance 管理员查询短信适配器余额。
// GET /api/sms/admin/adapter/balance
func (c *Client) GetAdapterBalance(ctx context.Context) ([]AdapterBalance, error) {
return get[[]AdapterBalance](c, ctx, "/api/sms/admin/adapter/balance", nil)
}
+322
View File
@@ -0,0 +1,322 @@
package smscli
import (
"bytes"
"context"
"crypto/md5"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"sort"
"strings"
"time"
"unicode"
)
type authMode int
const (
authModeBearer authMode = 1
authModeUserToken authMode = 2
)
// Client 是访问 SMS Server 后端的 HTTP 客户端。
//
// 构造方式:
// - NewServiceClient: 管理端(自动调用 service-token-login 获取 Bearer Token
// - NewBearerClient: 管理端(使用已有的 Bearer Token
// - NewUserTokenClient: 发送端(X-SMS-Token + 请求签名)
type Client struct {
baseURL string
mode authMode
bearer string
userToken string
httpClient *http.Client
}
// Option 用于在构造 Client 时传入可选配置。
type Option func(*Client)
// WithHTTPClient 使用自定义 *http.Client。
func WithHTTPClient(hc *http.Client) Option {
return func(c *Client) { c.httpClient = hc }
}
// WithTimeout 修改底层 HTTP 客户端超时(默认 30 秒)。
func WithTimeout(d time.Duration) Option {
return func(c *Client) { c.httpClient.Timeout = d }
}
// NewServiceClient 创建管理端客户端。
//
// 自动调用 POST /api/auth/service-token-login 将 serviceToken
// 兑换为 Bearer Token,后续请求通过 Authorization: Bearer 认证。
// 可访问全部管理接口(签名/模板/Token/额度/发送记录/适配器等)。
func NewServiceClient(baseURL, serviceToken string, opts ...Option) (*Client, error) {
c := &Client{
baseURL: strings.TrimRight(baseURL, "/"),
mode: authModeBearer,
httpClient: &http.Client{Timeout: 30 * time.Second},
}
for _, o := range opts {
o(c)
}
token, err := c.serviceTokenLogin(context.Background(), serviceToken)
if err != nil {
return nil, fmt.Errorf("service token login: %w", err)
}
c.bearer = token
return c, nil
}
// NewBearerClient 创建使用已有 Bearer Token 的客户端。
//
// 适用于已通过其他方式获取 Bearer Token 的场景(如用户中心 Token)。
func NewBearerClient(baseURL, bearerToken string, opts ...Option) *Client {
c := &Client{
baseURL: strings.TrimRight(baseURL, "/"),
mode: authModeBearer,
bearer: bearerToken,
httpClient: &http.Client{Timeout: 30 * time.Second},
}
for _, o := range opts {
o(c)
}
return c
}
// NewUserTokenClient 创建发送端客户端。
//
// 使用 X-SMS-Token 头认证,POST/PUT 请求体会自动计算 sign 签名。
// 主要用于调用短信发送接口,也可管理当前用户的签名/模板/Token 等资源。
func NewUserTokenClient(baseURL, userToken string, opts ...Option) *Client {
c := &Client{
baseURL: strings.TrimRight(baseURL, "/"),
mode: authModeUserToken,
userToken: userToken,
httpClient: &http.Client{Timeout: 30 * time.Second},
}
for _, o := range opts {
o(c)
}
return c
}
// APIResponse 是后端统一响应体。
type APIResponse[T any] struct {
Code int `json:"code"`
Message string `json:"message"`
Data T `json:"data,omitempty"`
}
// APIError 表示后端业务错误(code != 200)。
//
// 使用 errors.As 判断:
//
// var apiErr *smscli.APIError
// if errors.As(err, &apiErr) { ... }
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) fullURL(path string) string {
return c.baseURL + path
}
func (c *Client) setAuth(req *http.Request) {
switch c.mode {
case authModeBearer:
req.Header.Set("Authorization", "Bearer "+c.bearer)
case authModeUserToken:
req.Header.Set("X-SMS-Token", c.userToken)
}
}
// CalcSign 按 sms-server 签名算法计算 sign 值。
//
// 算法:排序 key → "key:value" 拼接 ";" → 去空白 → base64 → MD5(base64+token)。
func CalcSign(body map[string]interface{}, token string) string {
keys := make([]string, 0, len(body))
for k := range body {
if k == "sign" {
continue
}
keys = append(keys, k)
}
sort.Strings(keys)
pairs := make([]string, 0, len(keys))
for _, k := range keys {
v := body[k]
var valStr string
switch tv := v.(type) {
case string:
valStr = tv
case json.Number:
valStr = tv.String()
default:
b, _ := json.Marshal(v)
valStr = string(b)
}
pairs = append(pairs, k+":"+valStr)
}
joined := strings.Join(pairs, ";")
var sb strings.Builder
for _, r := range joined {
if !unicode.IsSpace(r) {
sb.WriteRune(r)
}
}
b64 := base64.StdEncoding.EncodeToString([]byte(sb.String()))
hash := md5.Sum([]byte(b64 + token))
return fmt.Sprintf("%x", hash)
}
func structToMap(v interface{}) (map[string]interface{}, error) {
b, err := json.Marshal(v)
if err != nil {
return nil, err
}
decoder := json.NewDecoder(bytes.NewReader(b))
decoder.UseNumber()
var m map[string]interface{}
if err := decoder.Decode(&m); err != nil {
return nil, err
}
return m, nil
}
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 {
if c.mode == authModeUserToken && (method == http.MethodPost || method == http.MethodPut) {
bodyMap, err := structToMap(body)
if err != nil {
return zero, fmt.Errorf("marshal body to map: %w", err)
}
bodyMap["sign"] = CalcSign(bodyMap, c.userToken)
b, err := json.Marshal(bodyMap)
if err != nil {
return zero, fmt.Errorf("marshal signed body: %w", err)
}
bodyReader = bytes.NewReader(b)
} else {
b, err := json.Marshal(body)
if err != nil {
return zero, fmt.Errorf("marshal body: %w", err)
}
bodyReader = bytes.NewReader(b)
}
}
reqURL := c.fullURL(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 (c *Client) serviceTokenLogin(ctx context.Context, serviceToken string) (string, error) {
body := map[string]string{"service_token": serviceToken}
b, _ := json.Marshal(body)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.fullURL("/api/auth/service-token-login"), bytes.NewReader(b))
if err != nil {
return "", err
}
req.Header.Set("Content-Type", "application/json")
resp, err := c.httpClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
var apiResp APIResponse[map[string]interface{}]
if err := json.Unmarshal(respBody, &apiResp); err != nil {
return "", fmt.Errorf("unmarshal login response: %w\nbody: %s", err, string(respBody))
}
if apiResp.Code != 200 {
return "", &APIError{Code: apiResp.Code, Message: apiResp.Message}
}
token, ok := apiResp.Data["token"].(string)
if !ok {
return "", fmt.Errorf("login response missing token")
}
return token, nil
}
// Ping 健康检查。
//
// GET /api/index/ping
func (c *Client) Ping(ctx context.Context) error {
_, err := get[any](c, ctx, "/api/index/ping", nil)
return err
}
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)
}
+3
View File
@@ -0,0 +1,3 @@
module gitea.s1f.ren/shiran/sms-server-cli
go 1.22.0
+72
View File
@@ -0,0 +1,72 @@
package smscli
import (
"fmt"
"net/url"
)
// buildQuery 将 map[string]interface{} 转成 url.Values。
//
// nil / 空字符串 / 值为 0 的 int/uint 会被忽略;
// int8 不做零值过滤(0 可能是合法状态值);
// 指针类型为 nil 时忽略,否则取值写入。
func buildQuery(params map[string]interface{}) url.Values {
q := url.Values{}
for k, v := range params {
if v == nil {
continue
}
switch val := v.(type) {
case string:
if val != "" {
q.Set(k, val)
}
case int:
if val != 0 {
q.Set(k, fmt.Sprintf("%d", val))
}
case int8:
q.Set(k, fmt.Sprintf("%d", val))
case uint:
if val != 0 {
q.Set(k, fmt.Sprintf("%d", val))
}
case *int:
if val != nil {
q.Set(k, fmt.Sprintf("%d", *val))
}
case *int8:
if val != nil {
q.Set(k, fmt.Sprintf("%d", *val))
}
case *uint:
if val != nil {
q.Set(k, fmt.Sprintf("%d", *val))
}
default:
q.Set(k, fmt.Sprintf("%v", v))
}
}
return q
}
func paginationParams(p PaginationQuery) map[string]interface{} {
m := map[string]interface{}{}
if p.Page > 0 {
m["page"] = p.Page
}
if p.PageSize > 0 {
m["page_size"] = p.PageSize
}
return m
}
func mergeParams(maps ...map[string]interface{}) map[string]interface{} {
result := map[string]interface{}{}
for _, m := range maps {
for k, v := range m {
result[k] = v
}
}
return result
}
+62
View File
@@ -0,0 +1,62 @@
package smscli
import (
"context"
"fmt"
)
// ──────────────────────────────────────────────
// 用户接口
// ──────────────────────────────────────────────
// ListQuotas 获取当前用户的额度列表(分页)。
// GET /api/sms/quota/list
func (c *Client) ListQuotas(ctx context.Context, q PaginationQuery) (PaginationResult[SmsQuota], error) {
params := paginationParams(q)
return get[PaginationResult[SmsQuota]](c, ctx, "/api/sms/quota/list", buildQuery(params))
}
// GetQuotaSummary 获取当前用户的额度汇总。
// GET /api/sms/quota/summary
func (c *Client) GetQuotaSummary(ctx context.Context) (QuotaSummary, error) {
return get[QuotaSummary](c, ctx, "/api/sms/quota/summary", nil)
}
// ──────────────────────────────────────────────
// 管理员接口
// ──────────────────────────────────────────────
// AdminListQuotas 管理员获取额度列表,可按 UserID 筛选。
// GET /api/sms/admin/quota/list
func (c *Client) AdminListQuotas(ctx context.Context, q QuotaListQuery) (PaginationResult[SmsQuota], error) {
params := mergeParams(paginationParams(q.PaginationQuery), map[string]interface{}{
"user_id": q.UserID,
})
return get[PaginationResult[SmsQuota]](c, ctx, "/api/sms/admin/quota/list", buildQuery(params))
}
// AdminGetQuotaSummary 管理员获取指定用户的额度汇总。
// GET /api/sms/admin/quota/summary
func (c *Client) AdminGetQuotaSummary(ctx context.Context, userID uint) (QuotaSummary, error) {
q := buildQuery(map[string]interface{}{"user_id": userID})
return get[QuotaSummary](c, ctx, "/api/sms/admin/quota/summary", q)
}
// CreateQuota 管理员创建额度。
// POST /api/sms/admin/quota
func (c *Client) CreateQuota(ctx context.Context, req CreateQuotaReq) (SmsQuota, error) {
return post[SmsQuota](c, ctx, "/api/sms/admin/quota", req)
}
// UpdateQuota 管理员更新指定额度。
// PUT /api/sms/admin/quota/:id
func (c *Client) UpdateQuota(ctx context.Context, id uint, req UpdateQuotaReq) (SmsQuota, error) {
return put[SmsQuota](c, ctx, fmt.Sprintf("/api/sms/admin/quota/%d", id), req)
}
// DeleteQuota 管理员删除指定额度。
// DELETE /api/sms/admin/quota/:id
func (c *Client) DeleteQuota(ctx context.Context, id uint) error {
_, err := del[any](c, ctx, fmt.Sprintf("/api/sms/admin/quota/%d", id))
return err
}
+67
View File
@@ -0,0 +1,67 @@
package smscli
import (
"context"
"fmt"
)
// ──────────────────────────────────────────────
// 用户接口
// ──────────────────────────────────────────────
// SendBatch 批量发送短信(相同内容发送到多个号码)。
// POST /api/sms/send/batch
func (c *Client) SendBatch(ctx context.Context, req SendBatchReq) (SendResp, error) {
return post[SendResp](c, ctx, "/api/sms/send/batch", req)
}
// SendMulti 个性化群发短信(每个号码使用不同参数)。
// POST /api/sms/send/multi
func (c *Client) SendMulti(ctx context.Context, req SendMultiReq) (SendResp, error) {
return post[SendResp](c, ctx, "/api/sms/send/multi", req)
}
// ListSendRecords 获取当前用户的发送记录列表(分页,支持多条件筛选)。
// GET /api/sms/send/record
func (c *Client) ListSendRecords(ctx context.Context, q SendRecordQuery) (PaginationResult[SmsSendRecord], error) {
params := mergeParams(paginationParams(q.PaginationQuery), map[string]interface{}{
"signature_id": q.SignatureID,
"template_id": q.TemplateID,
"phone": q.Phone,
"status": q.Status,
"start_time": q.StartTime,
"end_time": q.EndTime,
})
return get[PaginationResult[SmsSendRecord]](c, ctx, "/api/sms/send/record", buildQuery(params))
}
// GetSendStatus 根据消息 ID 查询发送状态。
// GET /api/sms/send/status/:msgId
func (c *Client) GetSendStatus(ctx context.Context, msgID string) (SmsSendRecord, error) {
return get[SmsSendRecord](c, ctx, fmt.Sprintf("/api/sms/send/status/%s", msgID), nil)
}
// ──────────────────────────────────────────────
// 管理员接口
// ──────────────────────────────────────────────
// AdminListSendRecords 管理员获取发送记录列表,可按 UserID 及其他条件筛选。
// GET /api/sms/admin/send/record
func (c *Client) AdminListSendRecords(ctx context.Context, q AdminSendRecordQuery) (PaginationResult[SmsSendRecord], error) {
params := mergeParams(paginationParams(q.PaginationQuery), map[string]interface{}{
"user_id": q.UserID,
"signature_id": q.SignatureID,
"template_id": q.TemplateID,
"phone": q.Phone,
"status": q.Status,
"start_time": q.StartTime,
"end_time": q.EndTime,
})
return get[PaginationResult[SmsSendRecord]](c, ctx, "/api/sms/admin/send/record", buildQuery(params))
}
// AdminGetSendStatus 管理员根据消息 ID 查询发送状态。
// GET /api/sms/admin/send/status/:msgId
func (c *Client) AdminGetSendStatus(ctx context.Context, msgID string) (SmsSendRecord, error) {
return get[SmsSendRecord](c, ctx, fmt.Sprintf("/api/sms/admin/send/status/%s", msgID), nil)
}
+109
View File
@@ -0,0 +1,109 @@
package smscli
import (
"context"
"fmt"
)
// ──────────────────────────────────────────────
// 用户接口
// ──────────────────────────────────────────────
// CreateSignature 创建短信签名。
// POST /api/sms/signature
func (c *Client) CreateSignature(ctx context.Context, req CreateSignatureReq) (SmsSignature, error) {
return post[SmsSignature](c, ctx, "/api/sms/signature", req)
}
// ListSignatures 获取当前用户的签名列表(分页)。
// GET /api/sms/signature/list
func (c *Client) ListSignatures(ctx context.Context, q PaginationQuery) (PaginationResult[SmsSignature], error) {
params := paginationParams(q)
return get[PaginationResult[SmsSignature]](c, ctx, "/api/sms/signature/list", buildQuery(params))
}
// GetSignature 获取指定签名详情。
// GET /api/sms/signature/:id
func (c *Client) GetSignature(ctx context.Context, id uint) (SmsSignature, error) {
return get[SmsSignature](c, ctx, fmt.Sprintf("/api/sms/signature/%d", id), nil)
}
// UpdateSignature 更新指定签名。
// PUT /api/sms/signature/:id
func (c *Client) UpdateSignature(ctx context.Context, id uint, req UpdateSignatureReq) (SmsSignature, error) {
return put[SmsSignature](c, ctx, fmt.Sprintf("/api/sms/signature/%d", id), req)
}
// DeleteSignature 删除指定签名。
// DELETE /api/sms/signature/:id
func (c *Client) DeleteSignature(ctx context.Context, id uint) error {
_, err := del[any](c, ctx, fmt.Sprintf("/api/sms/signature/%d", id))
return err
}
// SubmitSignature 提交签名进入审核。
// POST /api/sms/signature/:id/submit
func (c *Client) SubmitSignature(ctx context.Context, id uint) error {
_, err := post[any](c, ctx, fmt.Sprintf("/api/sms/signature/%d/submit", id), nil)
return err
}
// ──────────────────────────────────────────────
// 管理员接口
// ──────────────────────────────────────────────
// AdminListSignatures 管理员获取签名列表,可按 UserID、Status 筛选。
// GET /api/sms/admin/signature/list
func (c *Client) AdminListSignatures(ctx context.Context, q SignatureListQuery) (PaginationResult[SmsSignature], error) {
params := mergeParams(paginationParams(q.PaginationQuery), map[string]interface{}{
"user_id": q.UserID,
"status": q.Status,
})
return get[PaginationResult[SmsSignature]](c, ctx, "/api/sms/admin/signature/list", buildQuery(params))
}
// AdminGetSignature 管理员获取指定签名详情。
// GET /api/sms/admin/signature/:id
func (c *Client) AdminGetSignature(ctx context.Context, id uint) (SmsSignature, error) {
return get[SmsSignature](c, ctx, fmt.Sprintf("/api/sms/admin/signature/%d", id), nil)
}
// AdminCreateSignature 管理员创建签名(可指定 UserID)。
// POST /api/sms/admin/signature
func (c *Client) AdminCreateSignature(ctx context.Context, req AdminCreateSignatureReq) (SmsSignature, error) {
return post[SmsSignature](c, ctx, "/api/sms/admin/signature", req)
}
// AdminUpdateSignature 管理员更新指定签名。
// PUT /api/sms/admin/signature/:id
func (c *Client) AdminUpdateSignature(ctx context.Context, id uint, req AdminUpdateSignatureReq) (SmsSignature, error) {
return put[SmsSignature](c, ctx, fmt.Sprintf("/api/sms/admin/signature/%d", id), req)
}
// AdminDeleteSignature 管理员删除指定签名。
// DELETE /api/sms/admin/signature/:id
func (c *Client) AdminDeleteSignature(ctx context.Context, id uint) error {
_, err := del[any](c, ctx, fmt.Sprintf("/api/sms/admin/signature/%d", id))
return err
}
// AdminSubmitSignature 管理员提交签名进入审核。
// POST /api/sms/admin/signature/:id/submit
func (c *Client) AdminSubmitSignature(ctx context.Context, id uint) error {
_, err := post[any](c, ctx, fmt.Sprintf("/api/sms/admin/signature/%d/submit", id), nil)
return err
}
// ApproveSignature 管理员审核通过签名。
// POST /api/sms/admin/signature/:id/approve
func (c *Client) ApproveSignature(ctx context.Context, id uint) error {
_, err := post[any](c, ctx, fmt.Sprintf("/api/sms/admin/signature/%d/approve", id), nil)
return err
}
// RejectSignature 管理员驳回签名,需提供驳回原因。
// POST /api/sms/admin/signature/:id/reject
func (c *Client) RejectSignature(ctx context.Context, id uint, req RejectReq) error {
_, err := post[any](c, ctx, fmt.Sprintf("/api/sms/admin/signature/%d/reject", id), req)
return err
}
+142
View File
@@ -0,0 +1,142 @@
package smscli
import (
"context"
"fmt"
)
// ──────────────────────────────────────────────
// 用户接口
// ──────────────────────────────────────────────
// CreateTemplate 创建短信模板。
// POST /api/sms/template
func (c *Client) CreateTemplate(ctx context.Context, req CreateTemplateReq) (SmsTemplate, error) {
return post[SmsTemplate](c, ctx, "/api/sms/template", req)
}
// ListTemplates 获取当前用户的模板列表(分页)。
// GET /api/sms/template/list
func (c *Client) ListTemplates(ctx context.Context, q PaginationQuery) (PaginationResult[SmsTemplate], error) {
params := paginationParams(q)
return get[PaginationResult[SmsTemplate]](c, ctx, "/api/sms/template/list", buildQuery(params))
}
// GetTemplate 获取指定模板详情。
// GET /api/sms/template/:id
func (c *Client) GetTemplate(ctx context.Context, id uint) (SmsTemplate, error) {
return get[SmsTemplate](c, ctx, fmt.Sprintf("/api/sms/template/%d", id), nil)
}
// UpdateTemplate 更新指定模板。
// PUT /api/sms/template/:id
func (c *Client) UpdateTemplate(ctx context.Context, id uint, req UpdateTemplateReq) (SmsTemplate, error) {
return put[SmsTemplate](c, ctx, fmt.Sprintf("/api/sms/template/%d", id), req)
}
// DeleteTemplate 删除指定模板。
// DELETE /api/sms/template/:id
func (c *Client) DeleteTemplate(ctx context.Context, id uint) error {
_, err := del[any](c, ctx, fmt.Sprintf("/api/sms/template/%d", id))
return err
}
// SubmitTemplate 提交模板进入审核。
// POST /api/sms/template/:id/submit
func (c *Client) SubmitTemplate(ctx context.Context, id uint) error {
_, err := post[any](c, ctx, fmt.Sprintf("/api/sms/template/%d/submit", id), nil)
return err
}
// ListRecommendedTemplates 获取推荐模板列表(分页)。
// GET /api/sms/template/recommended
func (c *Client) ListRecommendedTemplates(ctx context.Context, q PaginationQuery) (PaginationResult[SmsRecommendedTemplate], error) {
params := paginationParams(q)
return get[PaginationResult[SmsRecommendedTemplate]](c, ctx, "/api/sms/template/recommended", buildQuery(params))
}
// ──────────────────────────────────────────────
// 管理员接口
// ──────────────────────────────────────────────
// AdminListTemplates 管理员获取模板列表,可按 UserID、Status 筛选。
// GET /api/sms/admin/template/list
func (c *Client) AdminListTemplates(ctx context.Context, q TemplateListQuery) (PaginationResult[SmsTemplate], error) {
params := mergeParams(paginationParams(q.PaginationQuery), map[string]interface{}{
"user_id": q.UserID,
"status": q.Status,
})
return get[PaginationResult[SmsTemplate]](c, ctx, "/api/sms/admin/template/list", buildQuery(params))
}
// AdminListRecommendedTemplates 管理员获取推荐模板列表(分页)。
// GET /api/sms/admin/template/recommended
func (c *Client) AdminListRecommendedTemplates(ctx context.Context, q PaginationQuery) (PaginationResult[SmsRecommendedTemplate], error) {
params := paginationParams(q)
return get[PaginationResult[SmsRecommendedTemplate]](c, ctx, "/api/sms/admin/template/recommended", buildQuery(params))
}
// AdminGetTemplate 管理员获取指定模板详情。
// GET /api/sms/admin/template/:id
func (c *Client) AdminGetTemplate(ctx context.Context, id uint) (SmsTemplate, error) {
return get[SmsTemplate](c, ctx, fmt.Sprintf("/api/sms/admin/template/%d", id), nil)
}
// AdminCreateTemplate 管理员创建模板(可指定 UserID)。
// POST /api/sms/admin/template
func (c *Client) AdminCreateTemplate(ctx context.Context, req AdminCreateTemplateReq) (SmsTemplate, error) {
return post[SmsTemplate](c, ctx, "/api/sms/admin/template", req)
}
// AdminUpdateTemplate 管理员更新指定模板。
// PUT /api/sms/admin/template/:id
func (c *Client) AdminUpdateTemplate(ctx context.Context, id uint, req AdminUpdateTemplateReq) (SmsTemplate, error) {
return put[SmsTemplate](c, ctx, fmt.Sprintf("/api/sms/admin/template/%d", id), req)
}
// AdminDeleteTemplate 管理员删除指定模板。
// DELETE /api/sms/admin/template/:id
func (c *Client) AdminDeleteTemplate(ctx context.Context, id uint) error {
_, err := del[any](c, ctx, fmt.Sprintf("/api/sms/admin/template/%d", id))
return err
}
// AdminSubmitTemplate 管理员提交模板进入审核。
// POST /api/sms/admin/template/:id/submit
func (c *Client) AdminSubmitTemplate(ctx context.Context, id uint) error {
_, err := post[any](c, ctx, fmt.Sprintf("/api/sms/admin/template/%d/submit", id), nil)
return err
}
// ApproveTemplate 管理员审核通过模板。
// POST /api/sms/admin/template/:id/approve
func (c *Client) ApproveTemplate(ctx context.Context, id uint) error {
_, err := post[any](c, ctx, fmt.Sprintf("/api/sms/admin/template/%d/approve", id), nil)
return err
}
// RejectTemplate 管理员驳回模板,需提供驳回原因。
// POST /api/sms/admin/template/:id/reject
func (c *Client) RejectTemplate(ctx context.Context, id uint, req RejectReq) error {
_, err := post[any](c, ctx, fmt.Sprintf("/api/sms/admin/template/%d/reject", id), req)
return err
}
// CreateRecommendedTemplate 管理员创建推荐模板。
// POST /api/sms/admin/template/recommended
func (c *Client) CreateRecommendedTemplate(ctx context.Context, req CreateRecommendedTemplateReq) (SmsRecommendedTemplate, error) {
return post[SmsRecommendedTemplate](c, ctx, "/api/sms/admin/template/recommended", req)
}
// UpdateRecommendedTemplate 管理员更新推荐模板。
// PUT /api/sms/admin/template/recommended/:id
func (c *Client) UpdateRecommendedTemplate(ctx context.Context, id uint, req UpdateRecommendedTemplateReq) (SmsRecommendedTemplate, error) {
return put[SmsRecommendedTemplate](c, ctx, fmt.Sprintf("/api/sms/admin/template/recommended/%d", id), req)
}
// DeleteRecommendedTemplate 管理员删除推荐模板。
// DELETE /api/sms/admin/template/recommended/:id
func (c *Client) DeleteRecommendedTemplate(ctx context.Context, id uint) error {
_, err := del[any](c, ctx, fmt.Sprintf("/api/sms/admin/template/recommended/%d", id))
return err
}
+95
View File
@@ -0,0 +1,95 @@
package smscli
import (
"context"
"fmt"
)
// ──────────────────────────────────────────────
// 用户接口
// ──────────────────────────────────────────────
// CreateUserToken 创建用户令牌。
// POST /api/sms/token
func (c *Client) CreateUserToken(ctx context.Context, req CreateTokenReq) (SmsUserToken, error) {
return post[SmsUserToken](c, ctx, "/api/sms/token", req)
}
// ListUserTokens 获取当前用户的令牌列表(分页)。
// GET /api/sms/token/list
func (c *Client) ListUserTokens(ctx context.Context, q PaginationQuery) (PaginationResult[SmsUserToken], error) {
params := paginationParams(q)
return get[PaginationResult[SmsUserToken]](c, ctx, "/api/sms/token/list", buildQuery(params))
}
// GetUserToken 获取指定令牌详情。
// GET /api/sms/token/:id
func (c *Client) GetUserToken(ctx context.Context, id uint) (SmsUserToken, error) {
return get[SmsUserToken](c, ctx, fmt.Sprintf("/api/sms/token/%d", id), nil)
}
// UpdateUserToken 更新指定令牌。
// PUT /api/sms/token/:id
func (c *Client) UpdateUserToken(ctx context.Context, id uint, req UpdateTokenReq) (SmsUserToken, error) {
return put[SmsUserToken](c, ctx, fmt.Sprintf("/api/sms/token/%d", id), req)
}
// DeleteUserToken 删除指定令牌。
// DELETE /api/sms/token/:id
func (c *Client) DeleteUserToken(ctx context.Context, id uint) error {
_, err := del[any](c, ctx, fmt.Sprintf("/api/sms/token/%d", id))
return err
}
// ToggleUserToken 切换令牌的启用/禁用状态。
// POST /api/sms/token/:id/toggle
func (c *Client) ToggleUserToken(ctx context.Context, id uint) error {
_, err := post[any](c, ctx, fmt.Sprintf("/api/sms/token/%d/toggle", id), nil)
return err
}
// ──────────────────────────────────────────────
// 管理员接口
// ──────────────────────────────────────────────
// AdminListUserTokens 管理员获取令牌列表,可按 UserID、Status 筛选。
// GET /api/sms/admin/token/list
func (c *Client) AdminListUserTokens(ctx context.Context, q TokenListQuery) (PaginationResult[SmsUserToken], error) {
params := mergeParams(paginationParams(q.PaginationQuery), map[string]interface{}{
"user_id": q.UserID,
"status": q.Status,
})
return get[PaginationResult[SmsUserToken]](c, ctx, "/api/sms/admin/token/list", buildQuery(params))
}
// AdminGetUserToken 管理员获取指定令牌详情。
// GET /api/sms/admin/token/:id
func (c *Client) AdminGetUserToken(ctx context.Context, id uint) (SmsUserToken, error) {
return get[SmsUserToken](c, ctx, fmt.Sprintf("/api/sms/admin/token/%d", id), nil)
}
// AdminCreateUserToken 管理员创建令牌(可指定 UserID)。
// POST /api/sms/admin/token
func (c *Client) AdminCreateUserToken(ctx context.Context, req AdminCreateTokenReq) (SmsUserToken, error) {
return post[SmsUserToken](c, ctx, "/api/sms/admin/token", req)
}
// AdminUpdateUserToken 管理员更新指定令牌。
// PUT /api/sms/admin/token/:id
func (c *Client) AdminUpdateUserToken(ctx context.Context, id uint, req UpdateTokenReq) (SmsUserToken, error) {
return put[SmsUserToken](c, ctx, fmt.Sprintf("/api/sms/admin/token/%d", id), req)
}
// AdminDeleteUserToken 管理员删除指定令牌。
// DELETE /api/sms/admin/token/:id
func (c *Client) AdminDeleteUserToken(ctx context.Context, id uint) error {
_, err := del[any](c, ctx, fmt.Sprintf("/api/sms/admin/token/%d", id))
return err
}
// AdminToggleUserToken 管理员切换令牌的启用/禁用状态。
// POST /api/sms/admin/token/:id/toggle
func (c *Client) AdminToggleUserToken(ctx context.Context, id uint) error {
_, err := post[any](c, ctx, fmt.Sprintf("/api/sms/admin/token/%d/toggle", id), nil)
return err
}
+351
View File
@@ -0,0 +1,351 @@
package smscli
import "time"
// PaginationResult 分页返回。
type PaginationResult[T any] struct {
List []T `json:"list"`
Total int64 `json:"total"`
Page int `json:"page"`
}
// PaginationQuery 分页参数。
type PaginationQuery struct {
Page int `json:"page,omitempty"`
PageSize int `json:"page_size,omitempty"`
}
// GormModel 对应后端 gorm.Model。
type GormModel struct {
ID uint `json:"ID"`
CreatedAt time.Time `json:"CreatedAt"`
UpdatedAt time.Time `json:"UpdatedAt"`
DeletedAt *time.Time `json:"DeletedAt"`
}
const (
ReviewStatusDraft = 0
ReviewStatusReviewing = 1
ReviewStatusApproved = 2
ReviewStatusRejected = 3
)
const (
QuotaTypeLongTerm = 1
QuotaTypeShortTerm = 2
QuotaTypeCycle = 3
)
const (
SendStatusPending = 0
SendStatusSubmitted = 1
SendStatusSuccess = 2
SendStatusFailed = 3
SendStatusRejected = 4
)
const (
AuthTypeAuthToken = 1
AuthTypeServiceToken = 2
AuthTypeUserToken = 3
)
// SmsSignature 短信签名。Status: 0=草稿 1=审核中 2=已通过 3=已驳回
type SmsSignature struct {
GormModel
UserID uint `json:"user_id"`
Title string `json:"title"`
ApplicantName string `json:"applicant_name"`
ApplicantIDCard string `json:"applicant_id_card"`
ApplicantCompany string `json:"applicant_company"`
LicenseURL string `json:"license_url"`
Status int8 `json:"status"`
RejectReason string `json:"reject_reason"`
ReviewedBy *uint `json:"reviewed_by"`
ReviewedAt *time.Time `json:"reviewed_at"`
}
type CreateSignatureReq struct {
Title string `json:"title"`
ApplicantName string `json:"applicant_name"`
ApplicantIDCard string `json:"applicant_id_card,omitempty"`
ApplicantCompany string `json:"applicant_company,omitempty"`
LicenseURL string `json:"license_url,omitempty"`
}
type UpdateSignatureReq struct {
Title string `json:"title,omitempty"`
ApplicantName string `json:"applicant_name,omitempty"`
ApplicantIDCard string `json:"applicant_id_card,omitempty"`
ApplicantCompany string `json:"applicant_company,omitempty"`
LicenseURL string `json:"license_url,omitempty"`
}
type AdminCreateSignatureReq struct {
UserID uint `json:"user_id"`
Title string `json:"title"`
ApplicantName string `json:"applicant_name"`
ApplicantIDCard string `json:"applicant_id_card,omitempty"`
ApplicantCompany string `json:"applicant_company,omitempty"`
LicenseURL string `json:"license_url,omitempty"`
}
type AdminUpdateSignatureReq struct {
Title string `json:"title,omitempty"`
ApplicantName string `json:"applicant_name,omitempty"`
ApplicantIDCard string `json:"applicant_id_card,omitempty"`
ApplicantCompany string `json:"applicant_company,omitempty"`
LicenseURL string `json:"license_url,omitempty"`
Status *int8 `json:"status,omitempty"`
}
type RejectReq struct {
RejectReason string `json:"reject_reason"`
}
type SignatureListQuery struct {
PaginationQuery
UserID *uint `json:"user_id,omitempty"`
Status *int8 `json:"status,omitempty"`
}
type TemplateParam struct {
Key string `json:"key"`
Type string `json:"type"`
MaxLen int `json:"max_len"`
}
// SmsTemplate 短信模板。Status: 0=草稿 1=审核中 2=已通过 3=已驳回
type SmsTemplate struct {
GormModel
UserID uint `json:"user_id"`
Name string `json:"name"`
Content string `json:"content"`
Params []TemplateParam `json:"params"`
RecommendedID *uint `json:"recommended_id"`
IsModified int8 `json:"is_modified"`
Status int8 `json:"status"`
RejectReason string `json:"reject_reason"`
ReviewedBy *uint `json:"reviewed_by"`
ReviewedAt *time.Time `json:"reviewed_at"`
}
type CreateTemplateReq struct {
Name string `json:"name"`
Content string `json:"content"`
Params []TemplateParam `json:"params,omitempty"`
RecommendedID *uint `json:"recommended_id,omitempty"`
}
type UpdateTemplateReq struct {
Name string `json:"name,omitempty"`
Content string `json:"content,omitempty"`
Params []TemplateParam `json:"params,omitempty"`
}
type AdminCreateTemplateReq struct {
UserID uint `json:"user_id"`
Name string `json:"name"`
Content string `json:"content"`
Params []TemplateParam `json:"params,omitempty"`
RecommendedID *uint `json:"recommended_id,omitempty"`
}
type AdminUpdateTemplateReq struct {
Name string `json:"name,omitempty"`
Content string `json:"content,omitempty"`
Params []TemplateParam `json:"params,omitempty"`
Status *int8 `json:"status,omitempty"`
}
type TemplateListQuery struct {
PaginationQuery
UserID *uint `json:"user_id,omitempty"`
Status *int8 `json:"status,omitempty"`
}
type SmsRecommendedTemplate struct {
ID uint `json:"id"`
Name string `json:"name"`
Content string `json:"content"`
Params []TemplateParam `json:"params"`
Category string `json:"category"`
CreatedBy uint `json:"created_by"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type CreateRecommendedTemplateReq struct {
Name string `json:"name"`
Content string `json:"content"`
Params []TemplateParam `json:"params,omitempty"`
Category string `json:"category,omitempty"`
}
type UpdateRecommendedTemplateReq struct {
Name string `json:"name,omitempty"`
Content string `json:"content,omitempty"`
Params []TemplateParam `json:"params,omitempty"`
Category string `json:"category,omitempty"`
}
type SmsUserToken struct {
GormModel
UserID uint `json:"user_id"`
Token string `json:"token"`
Name string `json:"name"`
QuotaLimit *int `json:"quota_limit"`
QuotaUsed int `json:"quota_used"`
TemplateIDs []uint `json:"template_ids"`
ExpireAt *time.Time `json:"expire_at"`
IsActive int8 `json:"is_active"`
LastUsedAt *time.Time `json:"last_used_at"`
LastUsedIP string `json:"last_used_ip"`
}
type CreateTokenReq struct {
Name string `json:"name,omitempty"`
QuotaLimit *int `json:"quota_limit,omitempty"`
TemplateIDs []uint `json:"template_ids,omitempty"`
ExpireAt string `json:"expire_at,omitempty"`
}
type UpdateTokenReq struct {
Name string `json:"name,omitempty"`
QuotaLimit *int `json:"quota_limit,omitempty"`
TemplateIDs []uint `json:"template_ids,omitempty"`
ExpireAt string `json:"expire_at,omitempty"`
}
type AdminCreateTokenReq struct {
UserID uint `json:"user_id"`
Name string `json:"name,omitempty"`
QuotaLimit *int `json:"quota_limit,omitempty"`
TemplateIDs []uint `json:"template_ids,omitempty"`
ExpireAt string `json:"expire_at,omitempty"`
}
type TokenListQuery struct {
PaginationQuery
UserID *uint `json:"user_id,omitempty"`
Status *int8 `json:"status,omitempty"`
}
// SmsQuota 短信额度。QuotaType: 1=长期 2=短期 3=周期
type SmsQuota struct {
GormModel
UserID uint `json:"user_id"`
QuotaType int8 `json:"quota_type"`
Total int `json:"total"`
Used int `json:"used"`
ExpireAt *time.Time `json:"expire_at"`
CycleUnit string `json:"cycle_unit"`
CycleValue int `json:"cycle_value"`
NextRefreshAt *time.Time `json:"next_refresh_at"`
IsActive int8 `json:"is_active"`
}
type CreateQuotaReq struct {
UserID uint `json:"user_id"`
QuotaType int8 `json:"quota_type"`
Total int `json:"total"`
ExpireAt string `json:"expire_at,omitempty"`
CycleUnit string `json:"cycle_unit,omitempty"`
CycleValue int `json:"cycle_value,omitempty"`
}
type UpdateQuotaReq struct {
Total *int `json:"total,omitempty"`
ExpireAt string `json:"expire_at,omitempty"`
CycleUnit string `json:"cycle_unit,omitempty"`
CycleValue *int `json:"cycle_value,omitempty"`
IsActive *int8 `json:"is_active,omitempty"`
}
type QuotaListQuery struct {
PaginationQuery
UserID *uint `json:"user_id,omitempty"`
}
type QuotaSummary struct {
TotalRemaining int `json:"total_remaining"`
LongTerm int `json:"long_term"`
ShortTerm int `json:"short_term"`
Cycle int `json:"cycle"`
}
type SendBatchReq struct {
UserID uint `json:"user_id,omitempty"`
SignatureID uint `json:"signature_id"`
TemplateID uint `json:"template_id"`
Params map[string]string `json:"params,omitempty"`
Phones []string `json:"phones"`
Adapter string `json:"adapter,omitempty"`
}
type SendMultiReq struct {
UserID uint `json:"user_id,omitempty"`
SignatureID uint `json:"signature_id"`
TemplateID uint `json:"template_id"`
Items []SendMultiItem `json:"items"`
Adapter string `json:"adapter,omitempty"`
}
type SendMultiItem struct {
Phone string `json:"phone"`
Params map[string]string `json:"params,omitempty"`
}
type SendResp struct {
RecordID uint `json:"record_id"`
MsgID string `json:"msg_id"`
FeeCount int `json:"fee_count"`
PhoneCount int `json:"phone_count"`
}
type SmsSendRecord struct {
ID uint `json:"id"`
UserID uint `json:"user_id"`
AuthType int8 `json:"auth_type"`
TokenID *uint `json:"token_id"`
SignatureID uint `json:"signature_id"`
TemplateID uint `json:"template_id"`
SendType int8 `json:"send_type"`
Params map[string]interface{} `json:"params"`
FinalContent string `json:"final_content"`
PhoneNumbers []string `json:"phone_numbers"`
SourceIP string `json:"source_ip"`
AdapterName string `json:"adapter_name"`
AdapterMsgID string `json:"adapter_msg_id"`
Status int8 `json:"status"`
CallbackRaw map[string]interface{} `json:"callback_raw"`
FeeCount int `json:"fee_count"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type SendRecordQuery struct {
PaginationQuery
SignatureID uint `json:"signature_id,omitempty"`
TemplateID uint `json:"template_id,omitempty"`
Phone string `json:"phone,omitempty"`
Status *int `json:"status,omitempty"`
StartTime string `json:"start_time,omitempty"`
EndTime string `json:"end_time,omitempty"`
}
type AdminSendRecordQuery struct {
PaginationQuery
UserID *uint `json:"user_id,omitempty"`
SignatureID uint `json:"signature_id,omitempty"`
TemplateID uint `json:"template_id,omitempty"`
Phone string `json:"phone,omitempty"`
Status *int `json:"status,omitempty"`
StartTime string `json:"start_time,omitempty"`
EndTime string `json:"end_time,omitempty"`
}
type AdapterBalance struct {
Adapter string `json:"adapter"`
Balance interface{} `json:"balance"`
}