低空智控平台 后端go
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

156 lines
4.6 KiB

package client
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"time"
)
const defaultSimbossAPIBase = "https://api.simboss.com"
type SimbossClient struct {
appID string
secret string
apiBase string
httpClient *http.Client
}
type SimbossDeviceDetail struct {
Carrier string `json:"carrier"`
Status string `json:"status"`
DeviceStatus string `json:"deviceStatus"`
ExpireDate string `json:"expireDate"`
RatePlanID int64 `json:"ratePlanId"`
RatePlanName string `json:"iratePlanName"`
DataUsage float64 `json:"dataUsage"`
TotalDataVolume float64 `json:"totalDataVolume"`
RatePlanExpirationDate string `json:"ratePlanExpirationDate"`
}
type SimbossRatePlan struct {
RatePlanID int64 `json:"ratePlanId"`
Name string `json:"name"`
Description string `json:"description"`
DataVolume float64 `json:"dataVolume"`
TimeLength int `json:"timeLength"`
TimeUnit string `json:"timeUnit"`
MaxRechargePeriod int `json:"maxRechargePeriod"`
}
type simbossResponse struct {
Code string `json:"code"`
Message string `json:"message"`
Success bool `json:"success"`
Data json.RawMessage `json:"data"`
}
func NewSimbossClient(appID, secret, apiBase string) (*SimbossClient, error) {
if appID == "" || secret == "" {
return nil, errors.New("SIMBOSS credentials are required")
}
if apiBase == "" {
apiBase = defaultSimbossAPIBase
}
return &SimbossClient{
appID: appID, secret: secret, apiBase: strings.TrimRight(apiBase, "/"),
httpClient: &http.Client{Timeout: 30 * time.Second},
}, nil
}
func (c *SimbossClient) GetDeviceDetail(iccid string) (*SimbossDeviceDetail, error) {
var detail SimbossDeviceDetail
if err := c.post("/2.0/device/detail", map[string]string{"iccid": iccid}, &detail); err != nil {
return nil, err
}
return &detail, nil
}
func (c *SimbossClient) GetRatePlans(iccid string) ([]SimbossRatePlan, error) {
var plans []SimbossRatePlan
if err := c.post("/2.0/device/rateplans", map[string]string{"iccid": iccid}, &plans); err != nil {
return nil, err
}
return plans, nil
}
func (c *SimbossClient) Recharge(iccid string, ratePlanID int64, months int, externalOrder string) (string, error) {
if ratePlanID <= 0 || months <= 0 || externalOrder == "" {
return "", errors.New("invalid SIMBOSS recharge request")
}
var sequence string
err := c.post("/2.0/device/recharge", map[string]string{
"iccid": iccid,
"ratePlanId": strconv.FormatInt(ratePlanID, 10),
"month": strconv.Itoa(months),
"externalOrder": externalOrder,
}, &sequence)
return sequence, err
}
func (c *SimbossClient) post(path string, params map[string]string, target any) error {
params["appid"] = c.appID
params["timestamp"] = strconv.FormatInt(time.Now().UnixMilli(), 10)
params["sign"] = c.sign(params)
form := url.Values{}
for key, value := range params {
form.Set(key, value)
}
req, err := http.NewRequest(http.MethodPost, c.apiBase+path, strings.NewReader(form.Encode()))
if err != nil {
return fmt.Errorf("create SIMBOSS request: %w", err)
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded;charset=utf-8")
resp, err := c.httpClient.Do(req)
if err != nil {
return fmt.Errorf("call SIMBOSS: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
if err != nil {
return fmt.Errorf("read SIMBOSS response: %w", err)
}
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
return fmt.Errorf("SIMBOSS returned HTTP %d", resp.StatusCode)
}
var result simbossResponse
if err := json.Unmarshal(body, &result); err != nil {
return fmt.Errorf("decode SIMBOSS response: %w", err)
}
if result.Code != "0" && !result.Success {
return fmt.Errorf("SIMBOSS rejected request: code=%s", result.Code)
}
if err := json.Unmarshal(result.Data, target); err != nil {
return fmt.Errorf("decode SIMBOSS data: %w", err)
}
return nil
}
func (c *SimbossClient) sign(params map[string]string) string {
keys := make([]string, 0, len(params))
for key := range params {
keys = append(keys, key)
}
sort.Strings(keys)
var builder strings.Builder
for i, key := range keys {
if i > 0 {
builder.WriteByte('&')
}
builder.WriteString(key)
builder.WriteByte('=')
builder.WriteString(params[key])
}
builder.WriteString(c.secret)
sum := sha256.Sum256([]byte(builder.String()))
return hex.EncodeToString(sum[:])
}