接口通讯规范

  • 签名规则:

    1
    2
    3
    4
    5
    6
    7
    8
    
    // 签名有效期暂定为 600 秒
    var platform = "baidu"     // 对接平台,正式测试环境保持一致
    var secret = "test_123456"   // 测试环境密钥,生产环境密钥私聊
    var requestTime = 1640163102 // 秒级时间戳
    var loginStaffId = 123 // 请求员工ID,便于权限控制
    
    // 生成签名
    sign := Md5(fmt.Sprintf("%d-%s-%s-%d", requestTime, platform, secret, loginStaffId))
    
  • 数据规范:

    • 数据类型规范
      • 字段约定返回 int 型,则不能返回 string 等其他类型,其他类型同理
      • 字段约定返回 array ,如果为空则需返回[]
      • 字段约定返回 object,如果为空则需返回null
    • 接口请求规范:
      • header头必带参数
      • 请求参数传递使用 json body 的方式
    header参数 数据类型 参数说明 补充说明
    sign string 签名串
    request-time int 秒级时间戳
    request-staff int 请求员工ID,便于权限控制
    • 数据响应规范
    响应参数 数据类型 参数说明 补充说明
    code int 响应状态码:0-接口响应成功,1-接口请求失败直接报错,2-签名无效 http状态码:0-返回200,1-返回400,500 等,2-返回401
    message string 结果提示信息
    data object|array 响应内容

接口提供方的代码示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 配置信息:config/sign.go
package config

// 对外接口签名配置,支持多平台对接
var (
    SignTTL int64 = 600 // 签名生命周期设置,单位秒

    // 签名类型/对接平台
    SignTypeForTeamA = "teamA"   // a项目
    SignTypeForTeamB = "teamB" // b项目

    // 签名类型映射密钥
    SignTypeSecretMap = map[string]string{
        SignTypeForTeamA:   "test_123456",
        SignTypeForTeamB: "test_123456",
    }
)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// 中间件:logic/middleware/sign.go
package middleware

import (
    "errors"
    "fmt"
    "github.com/labstack/echo/v4"
    "xxx/xxx/pkg/helper"
    "xxx/xxx/pkg/xecho"
    "xxx/xxx/config"
    "xxx/xxx/dao"
    "xxx/xxx/global"
    "net/http"
    "strconv"
    "time"
)

const ErrCodeSignInvalid = 3 // 签名无效

type sign struct {
    name   string // 签名类型/平台
    secret string // 密钥
    ttl    int64  // 签名生命周期
}

var (
    SignTeamB = &sign{
        name:   config.SignTypeForTeamB,
        secret: config.SignTypeSecretMap[config.SignTypeForTeamB],
        ttl:    config.SignTTL,
    }
)

func (s *sign) Auth(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        var (
            sign              = c.Request().Header.Get("sign")
            requestTime, _    = strconv.ParseInt(c.Request().Header.Get("request-time"), 10, 64)
            requestStaffId, _ = strconv.ParseUint(c.Request().Header.Get("request-staff"), 10, 64)
        )

        if sign == "" {
            return c.JSON(http.StatusUnauthorized, xecho.FailCode("sign参数不能为空", ErrCodeSignInvalid))
        }
        if requestTime <= 0 {
            return c.JSON(http.StatusUnauthorized, xecho.FailCode("request-time参数不能为空", ErrCodeSignInvalid))
        }
        if requestStaffId <= 0 {
            return c.JSON(http.StatusUnauthorized, xecho.FailCode("request-staff参数不能为空", ErrCodeSignInvalid))
        }
        if err := s.CheckSign(sign, requestTime, requestStaffId); err != nil {
            return c.JSON(http.StatusUnauthorized, xecho.FailCode(err.Error(), ErrCodeSignInvalid))
        }

        staff := dao.Staff.GetById(requestStaffId)
        c.Set(global.KVStaff, staff)

        return next(c)
    }
}

// 签名校验
func (s *sign) CheckSign(sign string, requestTime int64, requestStaffId uint64) error {
    if s.secret == "" {
        return errors.New("该签名类型还没配置密钥")
    }

    if sign != helper.Md5(fmt.Sprintf("%d-%s-%s-%d", requestTime, s.name, s.secret, requestStaffId)) {
        return errors.New("签名有误")
    }

    currentTime := time.Now().Unix()
    if currentTime < requestTime || (currentTime-requestTime) > s.ttl {
        return errors.New("签名已过期")
    }

    return nil
}

// 生成签名
func (s *sign) GenerateSign(requestStaffId uint64) (string, int64, error) {
    requestTime := time.Now().Unix()
    sign := helper.Md5(fmt.Sprintf("%d-%s-%s-%d", requestTime, s.name, s.secret, requestStaffId))
    return sign, requestTime, nil
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 控制器层:logic/controller/b.go
package controller

import (
    "github.com/labstack/echo/v4"
    "xxx/xxx/pkg/xecho"
    "net/http"
)

type bController struct{}

var BController = new(bController)

func init() {
    BRouter = Echo.Group("/b", middleware.SignTeamB.Auth)
    BRouter.GET("/customer-data", BController.GetCustomerData).Name = "获取客户数据"
}

func (bController) GetCustomerData(c echo.Context) error {
    return c.JSON(http.StatusOK, xecho.Success(resp))
}

接口请求方的代码示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
package teamA

import (
    "errors"
    "fmt"
    "github.com/tidwall/gjson"
    "github.com/valyala/fasthttp"
    "xxx/xxx/pkg/helper"
    "xxx/xxx/config"
    "xxx/xxx/lib/xhttp"
    "net/http"
    "net/url"
    "strconv"
    "strings"
    "time"
)

type RequestForm struct {
    Uri      string // 路由
    Params   []byte // json字节
    Header   url.Values
    RespBody string
    Resp     interface{} // 响应结果结构化
    LoginStaffId uint64 // 请求员工ID
}

type client struct{}

type IClient interface {
    Post(dto *RequestForm) error
}

var Client IClient = new(client)

const (
    ttl = 5 * time.Second
)

// 带上签名参数
func makeSign(req *fasthttp.Request, loginStaffId uint64) error {
    requestTime := time.Now().Unix()
    sign := helper.Md5(fmt.Sprintf("%d-%s-%s-%d", requestTime, config.TeamASignType, config.TeamASignSecret, loginStaffId))

    req.Header.Set("sign", sign)
    req.Header.Set("request-time", strconv.FormatInt(requestTime, 10))
    req.Header.Set("request-staff", loginStaffId)
    return nil
}

func (c *client) Post(dto *RequestForm) error {
    if dto.Uri == "" {
        return errors.New("请求路由不能为空")
    }

    req := fasthttp.AcquireRequest()
    resp := fasthttp.AcquireResponse()
    defer func() {
        fasthttp.ReleaseResponse(resp)
        fasthttp.ReleaseRequest(req)
    }()

    req.SetRequestURI(config.TeamAUrl + dto.Uri)
    req.Header.SetContentType("application/json")
    req.SetBody(dto.Params)
    req.Header.SetMethod(http.MethodPost)

    if err := makeSign(req, dto.LoginStaffId); err != nil {
        return err
    }

    if dto.Header != nil {
        for k, v := range dto.Header {
            req.Header.Set(k, strings.Join(v, ","))
        }
    }

    if err := xhttp.FastClient.DoTimeout(req, resp, ttl); err != nil {
        return err
    }

    dto.RespBody = string(resp.Body())

    if resp.StatusCode() != fasthttp.StatusOK || gjson.Get(dto.RespBody, "code").Int() != 0 {
        return errors.New(gjson.Get(dto.RespBody, "message").String())
    }

    if dto.Resp != nil {
        helper.JsonUnmarshal(dto.RespBody, dto.Resp)
    }

    return nil
}