Skip to content

Go SDK

提供 A7Client 参考实现,仅依赖标准库(crypto/md5crypto/rc4net/httpencoding/hexnet/urlencoding/json)。

完整参考实现

a7client.go

go
package a7verify

import (
	"bytes"
	"crypto/md5"
	"crypto/rc4"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"time"
)

const baseURL = "https://api.a7p.cn/api/verify"

type A7Client struct {
	AppID       string
	AppKey      string
	ApiPassword string
	http        *http.Client
}

func New(appID, appKey string) *A7Client {
	return &A7Client{AppID: appID, AppKey: appKey, http: &http.Client{Timeout: 15 * time.Second}}
}

func md5hex(s string) string {
	h := md5.Sum([]byte(s))
	return hex.EncodeToString(h[:])
}

// rc4Hex 返回 RC4 密文的十六进制(大写)
func rc4Hex(key, plain string) (string, error) {
	c, err := rc4.NewCipher([]byte(key))
	if err != nil {
		return "", err
	}
	src := []byte(plain)
	dst := make([]byte, len(src))
	c.XORKeyStream(dst, src)
	return hex.EncodeToString(dst), nil // 转大写
}

func (c *A7Client) post(fields url.Values, headers map[string]string) (map[string]any, error) {
	req, err := http.NewRequest(http.MethodPost, baseURL, bytes.NewBufferString(fields.Encode()))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	for k, v := range headers {
		req.Header.Set(k, v)
	}
	resp, err := c.http.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, _ := io.ReadAll(resp.Body)
	var out map[string]any
	if err := json.Unmarshal(body, &out); err != nil {
		return nil, err
	}
	return out, nil
}

// Init 握手获取 ApiPassword
func (c *A7Client) Init() error {
	ts := strconv.FormatInt(time.Now().Unix(), 10)
	sign := md5hex(c.AppID + c.AppKey + ts + c.AppKey)
	fields := url.Values{}
	fields.Set("AppId", c.AppID)
	fields.Set("AppKey", c.AppKey)
	fields.Set("TimeStamp", ts)
	fields.Set("Sign", sign)
	out, err := c.post(fields, nil)
	if err != nil {
		return err
	}
	if int(out["code"].(float64)) != 200 {
		return fmt.Errorf("init 失败: %v %v", out["code"], out["message"])
	}
	data := out["data"].(map[string]any)
	c.ApiPassword = data["api_password"].(string)
	return nil
}

// Call 调用任意 action
func (c *A7Client) Call(action string, params map[string]string) (map[string]any, error) {
	if c.ApiPassword == "" {
		if err := c.Init(); err != nil {
			return nil, err
		}
	}
	// 按 key 排序拼接,保证可复现
	keys := make([]string, 0, len(params))
	for k := range params {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	plain := "action=" + action
	for _, k := range keys {
		plain += "&" + k + "=" + url.QueryEscape(params[k])
	}
	ts := strconv.FormatInt(time.Now().Unix(), 10)
	data, err := rc4Hex(c.ApiPassword, plain)
	if err != nil {
		return nil, err
	}
	data = toUpper(data)
	sign := md5hex(c.ApiPassword + ts + data)
	fields := url.Values{}
	fields.Set("TimeStamp", ts)
	fields.Set("Data", data)
	fields.Set("Sign", sign)
	return c.post(fields, map[string]string{"X-Api-Password": c.ApiPassword})
}

func toUpper(s string) string {
	b := []byte(s)
	for i, ch := range b {
		if ch >= 'a' && ch <= 'f' {
			b[i] = ch - 32
		}
	}
	return string(b)
}

使用示例

go
client := a7verify.New("150018", "YOUR_APP_KEY")
resp, err := client.Call("SingleLogin", map[string]string{
    "Card":       "UTDE-7TCX-N5AT-ZDSP",
    "Mac":        "TEST-ABC-123",
    "DeviceName": "MyPC",
})
if err != nil {
    log.Fatal(err)
}
code := int(resp["code"].(float64))
if code == 200 {
    data := resp["data"].(map[string]any)
    fmt.Println("token:", data["token"])
} else {
    fmt.Println("失败:", resp["message"])
}

注意事项

  • ApiPassword 仅保存在结构体字段,请勿序列化落盘。
  • 明文参数按 key 排序拼接,保证可复现(顺序本身不影响服务端解析)。
  • 更多接口参数见 API 参考

相关链接

A7验证 · 软件授权与网络验证平台