说明

客户端调用

  • 正式服: 外网:https://comb-platform.hortorgames.com

  • 测试服 外网:https://comb-platform-test.hortorgames.com

  • 开发服 外网:https://comb-platform-dev.hortorgames.com

服务器调用

  • 正式服: 外网:https://comb-sapi.hortorgames.com

  • 测试服 外网:https://comb-sapi-test.hortorgames.com

  • 开发服 外网:https://comb-sapi-dev.hortorgames.com

签名

签名字符串示例如下

sign = md5("content=a54456bf13582e773df04a35eab49ecbgameId=yydssecret=57a1fe2376ef483b2fa9cd1cbcf454f7timestamp=1639622430")

其中 content、gameId、secret、timestamp 在字符串中的顺序要严格按照上面示例中的顺序(字典序)

注意: 如果 query 里有其他参数, 也要参与签名, 并按照字典序排序

签名字符串中的content json 数据整个 body 进行 md5 加密作为 content

步骤

  1. 读取Request获取完整的Body请求参数
  2. 将body进行md5加密,得到content
  3. 读取Query参数form,并设置"content" = content,"secret" = secret
  4. 将参数按照字典序排序,拼接成字符串进行md5加密,得到sign

golang 示例


func CheckSign(c *gin.Context){
    ctx := c.Request.Context()
    body, err := io.ReadAll(c.Request.Body)
    if err != nil {
        resp.Error(err, c)
        return
    }
    defer c.Request.Body.Close()
    content = StringMd5(string(body))
	
    data := c.Request.ParseForm()
    data.Set("content", content)
	if data.Get("sign") != SignWithSecret(data, secret) {
        resp.Error(errors.New("签名错误"), c)
        return
    }
}

func SignWithSecret(data url.Values, secret string) (sign string) {
	data.Set("secret", secret)
	defer data.Del("secret")
	keys := make([]string, 0)
	for k := range data {
		if k == "sign" {
			continue
		}
		keys = append(keys, k)
	}
	sort.Strings(keys)
	str := ""
	for _, k := range keys {
		str += fmt.Sprintf("%s=%s", k, data.Get(k))
	}
	fmt.Println("str:", str)
	fmt.Println("md5Str:", StringMd5(str))
	return StringMd5(str)
}


func StringMd5(str string) string {
    hash := md5.New()
    hash.Write([]byte(str))

    return hex.EncodeToString(hash.Sum(nil))
}

mandarin 框架示例


func (p *pay) TestSign(ctx http.IHttpContext) {
	//TODO:更新为自己的req与gameId
	var req *payCallbackReq
    gameId := "yyds"
	_ = ctx.DecodeData(&req)
	if req == nil {
		ctx.JSON(200, map[string]any{
			"errcode": -1,
			"errmsg":  "参数错误",
		})
		return
	}
	

	if gameId != req.GameId {
		ctx.JSON(200, map[string]any{
			"errcode": -1,
			"errmsg":  "参数错误",
		})
		return
	}
	rawData, err := ctx.GetRawData()
	if err != nil {
		ctx.JSON(200, map[string]any{
			"errcode": -1,
			"errmsg":  err.Error(),
		})
		return
	}
	content := utils.MD5(rawData)
	ps := map[string]any{
		"gameId":    req.GameId,
		"timestamp": req.Timestamp,
		"content":   content,
		"sign":      req.Sign,
	}
	params := bon.O2B(ps).AsDocument()
	if req.Sign != p.platform.sign(params) {
		ctx.JSON(200, map[string]any{
			"errcode": -1,
			"errmsg":  "签名错误",
		})
		return
	}

	//DONE
}

Last Updated:
Contributors: 杨帆, jslgo, yuhongyu