签名验证
请求格式
向https://www.fmz.com/api/v1发送POST请求,参数以表单(application/x-www-form-urlencoded)提交。服务端也接受把同样的参数放在URL查询串中的GET请求,但参数会留在各处的访问日志里,推荐使用POST。
| 参数 | 说明 |
|---|---|
| version | 版本号,固定为1.0。 |
| access_key | API KEY的AccessKey。 |
| method | 调用的方法名,例如GetNodeList。 |
| args | 方法参数组成的JSON字符串:按顺序排列的数组(如[]、[123, "ok"]),或按参数名传值的对象(如{"robotId": 123}),见扩展API接口详解。不传时按[]处理。 |
| nonce | 毫秒时间戳。与服务器时间相差不能超过1小时,并且必须大于这把API KEY上一次请求使用的nonce。 |
| sign | 签名,计算方法见下文。 |
请求中不包含SecretKey。
签名方式
按下面的格式拼接字符串,其中args是实际提交的JSON字符串原文:
plaintext
version + "|" + method + "|" + args + "|" + nonce + "|" + secretKey
对拼接结果计算MD5,转换为32位小写十六进制字符串,作为sign的值。
Python示例
python
import hashlib
import json
import time
import urllib.parse
import urllib.request
ACCESS_KEY = '' # API KEY 的 AccessKey
SECRET_KEY = '' # API KEY 的 SecretKey
def api(method, *args, **kwargs):
d = {
'version': '1.0',
'access_key': ACCESS_KEY,
'method': method,
# 位置参数传数组,关键字参数传对象(按参数名传值)
'args': json.dumps(kwargs if kwargs else list(args)),
'nonce': int(time.time() * 1000),
}
s = '%s|%s|%s|%d|%s' % (d['version'], d['method'], d['args'], d['nonce'], SECRET_KEY)
d['sign'] = hashlib.md5(s.encode('utf-8')).hexdigest()
body = urllib.parse.urlencode(d).encode('utf-8')
with urllib.request.urlopen('https://www.fmz.com/api/v1', body, timeout=10) as resp:
return json.loads(resp.read().decode('utf-8'))
print(api('GetNodeList')) # 托管者列表
print(api('GetRobotList', appId='member2')) # 按参数名传值:标签为 member2 的实盘
print(api('CommandRobot', 123, 'ok')) # 向实盘 123 发送交互命令
print(api('GetRobotDetail', 123)) # 实盘 123 的详细信息
Go示例
mylang
package main
import (
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"time"
)
const (
accessKey = "" // API KEY 的 AccessKey
secretKey = "" // API KEY 的 SecretKey
baseAPI = "https://www.fmz.com/api/v1"
)
var client = &http.Client{Timeout: 10 * time.Second}
func api(method string, args ...interface{}) (string, error) {
if args == nil {
args = []interface{}{}
}
b, err := json.Marshal(args)
if err != nil {
return "", err
}
nonce := strconv.FormatInt(time.Now().UnixMilli(), 10)
sum := md5.Sum([]byte("1.0|" + method + "|" + string(b) + "|" + nonce + "|" + secretKey))
form := url.Values{
"version": {"1.0"},
"access_key": {accessKey},
"method": {method},
"args": {string(b)},
"nonce": {nonce},
"sign": {hex.EncodeToString(sum[:])},
}
resp, err := client.PostForm(baseAPI, form)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
return string(body), err
}
func main() {
ret, err := api("GetNodeList")
fmt.Println(ret, err)
// 用新的配置重启实盘 123,settings 字段见「扩展API接口详解」中的实盘配置说明
settings := map[string]interface{}{
"name": "hedge test",
"strategy": 456,
"period": 60,
"node": 789,
"exchanges": []interface{}{
map[string]interface{}{"pid": 1001, "pair": "BTC_USDT"},
},
}
ret, err = api("RestartRobot", 123, settings)
fmt.Println(ret, err)
}