中国立直麻将天梯榜 API
面向门店/雀庄的免费开放接口,用于雀士注册、对局录入与赛季排名查询。
概述
Base URL 1:https://www.mj-rate.com
Base URL 2:https://www.mj-rate.cn
所有接口均需在请求体中携带 rate_id、sequence、params、timestamp、sign,并通过签名校验。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| rate_id | string | 是 | 平台提供一个门店唯一的ID信息点击查看申请 |
| sequence | string | 是 | 携带请求的序号,用于防重放,8位长度字符串(仅限数字或字母组合,不区分大小写) |
| params | array/object | 是 | 携带的包体信息 |
| timestamp | int | 是 | 秒级时间戳 |
| sign | string | 是 | 32位大写字符串签名信息 |
Content-Type: application/json。认证与签名
平台为每个门店分配 rate_id(8 位非零数字串)与 secret。 点击查看申请
rate_id(8 位非零数字串)与 secret请向平台申请
签名步骤:
- 将请求体除
sign外的字段按键名升序排序; - 按
key=value&key=value&...拼接,末尾追加&secret=你的secret; - 对拼接串做 MD5 并转为大写,得到
sign。
JSON.stringify(不转义 unicode、不转义 /)后参与拼接,否则验签失败。
timestamp 为秒级时间戳,与服务器时间差不得超过 ±300 秒。sequence 用于防重放,同一 rate_id + sequence 在 10 分钟内不可重复使用。
请求与响应
请求体统一结构:
{
"rate_id": 12345678,
"sequence": ABCD4321,
"params": { 具体业务参数信息 },
"timestamp": 1710000000,
"sign": "XXXXXXXXXXXXXXXX"
}
响应统一结构:
{
"code": 200,
"msg": "success",
"data": { "status": 1, "msg": "操作成功", ... }
}
code 为 200 表示请求被接受;data.status === 1 表示业务成功,0 表示业务失败。
错误码
| code | 说明 |
|---|---|
| 200 | 成功 |
| 400 | 参数错误 |
| 405 | 请求方法错误(需为 POST) |
| 40001 | 签名错误 |
| 40002 | 时间戳过期 |
| 40003 | 格式错误(缺参、类型错误等) |
| 40004 | 非授权IP地址(非授权IP请求超过10次,将封禁该IP地址24小时) |
| 403 | 禁止访问(如重复请求) |
| 404 | 资源不存在 |
雀士注册
雀士信息注册。
params 参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| username | string | 是 | 雀士昵称(中文昵称请使用URL编码,并删除所有空格,最大20个中文汉字,最小2个字符,不可以数字开头或纯数字) |
| phone | string | 是 | 手机号,国内 11 位 1 开头 |
| avatar | string | 否 | 雀士头像 base64格式,解码后 ≤200KB |
| gender | int | 否 | 雀士性别 0 女 / 1 男 / 2 未知(默认 2) |
| string | 否 | 微信号,后期绑定微信机器人使用 | |
| int | 否 | QQ号码,一个QQ号码只能绑定一个雀士 | |
| string | 否 | 雀士邮箱,后期可能发送广告信息等 | |
| birthday | string | 否 | 雀士生日,格式为 YYYY-MM-DD,如果雀士已有生日,则不能修改 |
请求示例
{
"rate_id": 12345678,
"sequence": 87654321,
"params": {
"username": "Tom%20Cat",
"phone": "13800138000",
"avatar": "data:image/png;base64,iVBORw0KGgo...",
"gender": 1
},
"timestamp": 1710000000,
"sign": "YOUR_SIGN"
}
成功响应
{
"code": 200,
"msg": "success",
"data": { "uuid": "01234567", "msg": "注册成功", "status": 1 }
}
雀士编辑
门店端修改雀士资料(仅限本门店或在本店有记录的雀士)。
params 参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| phone | string | 是 | 雀士手机号 |
| username | string | 否 | 雀士昵称,最大20个中文汉字,不可以数字开头,删除所有空格,中文昵称请使用URL编码 |
| avatar | string | 否 | 雀士头像 base64格式,解码后 ≤200KB |
| gender | int | 否 | 雀士性别,0 女 / 1 男 / 2 未知(默认 2),如果雀士已有性别,则不能修改 |
| string | 否 | 微信号,后期绑定微信机器人使用 | |
| int | 否 | QQ号码,一个QQ号码只能绑定一个雀士 | |
| string | 否 | 雀士邮箱,后期可能发送广告信息等 | |
| birthday | string | 否 | 雀士生日,格式为 YYYY-MM-DD,如果雀士已有生日,则不能修改 |
成功响应
{
"code": 200,
"msg": "success",
"data": { "msg": "修改成功", "status": 1 }
}
查询雀士分数与排名
根据雀士手机号查询雀士的全局数据、各赛季数据,以及各赛季下的门店数据(区分四麻/三麻),并返回赛季全局排名与门店排名。
params 参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| phone | string | 是 | 雀士手机号 |
请求示例
{
"rate_id": 12345678,
"sequence": 87654324,
"params": {
"phone": "13800138000"
},
"timestamp": 1710000000,
"sign": "YOUR_SIGN"
}
成功响应
{
"code": 200,
"msg": "success",
"data": {
"status": 1,
"msg": "处理成功",
"userInfo": {
"uuid": "01234567",
"username": "Tom%20Cat",
"avatarUrl": "https://xxx/xxx.png"
},
"recordData": {
"global": {
"four": {
"total": 0,
"one_total": 0, "two_total": 0, "three_total": 0, "four_total": 0, "fly_total": 0,
"one_score": 0, "two_score": 0, "three_score": 0, "four_score": 0, "fly_score": 0
},
"three": {
"total": 0,
"one_total": 0, "two_total": 0, "three_total": 0, "four_total": 0, "fly_total": 0,
"one_score": 0, "two_score": 0, "three_score": 0, "four_score": 0, "fly_score": 0
}
},
"seasons": [
{
"seasonId": 1,
"seasonName": "S1",
"four": {
"total": 0,
"one_total": 0, "two_total": 0, "three_total": 0, "four_total": 0, "fly_total": 0,
"one_score": 0, "two_score": 0, "three_score": 0, "four_score": 0, "fly_score": 0,
"seasonRank": {
"id": 1,
"seasonTotal": 0,
"bestStartIndex": 0,
"bestWin": 0,
"bestSum": 0,
"bestValid": 1,
"bestGroup": [],
"rank": 0
}
},
"three": {
"total": 0,
"one_total": 0, "two_total": 0, "three_total": 0, "four_total": 0, "fly_total": 0,
"one_score": 0, "two_score": 0, "three_score": 0, "four_score": 0, "fly_score": 0,
"seasonRank": {
"id": 2,
"seasonTotal": 0,
"bestStartIndex": 0,
"bestWin": 0,
"bestSum": 0,
"bestValid": 1,
"bestGroup": [],
"rank": 0
}
},
"stores": [
{
"storeId": 100,
"storeName": "某某雀庄",
"four": {
"total": 0,
"one_total": 0, "two_total": 0, "three_total": 0, "four_total": 0, "fly_total": 0,
"one_score": 0, "two_score": 0, "three_score": 0, "four_score": 0, "fly_score": 0,
"seasonRank": {
"id": 3,
"seasonTotal": 0,
"bestStartIndex": 0,
"bestWin": 0,
"bestSum": 0,
"bestValid": 1,
"bestGroup": [],
"rank": 0
}
},
"three": {
"total": 0,
"one_total": 0, "two_total": 0, "three_total": 0, "four_total": 0, "fly_total": 0,
"one_score": 0, "two_score": 0, "three_score": 0, "four_score": 0, "fly_score": 0,
"seasonRank": {
"id": 4,
"seasonTotal": 0,
"bestStartIndex": 0,
"bestWin": 0,
"bestSum": 0,
"bestValid": 1,
"bestGroup": [],
"rank": 0
}
}
}
]
}
]
}
}
}
新增对局记录
新增一局天梯榜记录对局。name1~name4 为手机号,四个手机号均须已注册。
params 参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| type | int | 是 | 对局类型,0为四人麻将,1为三人麻将 |
| name1 | string | 是 | 第一位雀士手机号 |
| score1 | float | 是 | 第一位雀士分数 |
| name2 | string | 是 | 第二位雀士手机号 |
| score2 | float | 是 | 第二位雀士分数 |
| name3 | string | 是 | 第三位雀士手机号 |
| score3 | float | 是 | 第三位雀士分数 |
| name4 | string | 是 | 第四位雀士手机号,三人麻将时为0 |
| score4 | float | 是 | 第四位雀士分数,三人麻将时为0 |
| record_time | string | 否 | 对局时间 Y-m-d H:i:s,不传则当前时间 |
您无需负责对对局分数的顺位排序,平台会根据分数一一匹配自行排序处理。
同分可用小数区分顺位(如 35000.2、35000.1),请注意使用降序,即0.2顺位在0.1前面。
请求示例
{
"rate_id": 12345678,
"sequence": 87654323,
"params": {
"type": 0,
"name1": "13800138000", "score1": 35000,
"name2": "13900139000", "score2": 25000,
"name3": "13700137000", "score3": 22000,
"name4": "13600136000", "score4": 18000,
"record_time": "2026-03-11 20:00:00"
},
"timestamp": 1710000000,
"sign": "YOUR_SIGN"
}
成功响应
{
"code": 200,
"msg": "success",
"data": {
"msg": "添加成功",
"status": 1,
"record_id": 64位字符串,请自行保存记录,后续可用此 ID 修改和删除记录
"errCode": 200, // 错误码,见下表
}
}
修改对局记录
修改已有对局,仅限本门店记录。
params 参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| record_id | string | 是 | 对局记录 ID,64位字符串 |
| name1 | string | 是 | 第一位雀士手机号 |
| score1 | float | 是 | 第一位雀士分数 |
| name2 | string | 是 | 第二位雀士手机号 |
| score2 | float | 是 | 第二位雀士分数 |
| name3 | string | 是 | 第三位雀士手机号 |
| score3 | float | 是 | 第三位雀士分数 |
| name4 | string | 是 | 第四位雀士手机号,三人麻将时为0 |
| score4 | float | 是 | 第四位雀士分数,三人麻将时为0 |
成功响应
{
"code": 200,
"msg": "success",
"data": { "msg": "对局记录修改成功", "status": 1, "errCode": 200}
}
删除对局记录
删除对局,仅限本门店记录。params 只需 record_id。
成功响应
{
"code": 200,
"msg": "success",
"data": { "msg": "对局记录删除成功", "status": 1 }
}
查询对局记录
查询门店的对局记录信息,默认查询门店全部对局记录。支持筛选功能,包括对局类型、雀士手机号、对局时间等。
params 参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| type | int | 是 | 对局类型,0为四人麻将,1为三人麻将 |
| phone | string | 否 | 雀士手机号 |
| start_time | int/string | 否 | 查询的开始时间,支持时间戳和 Y-m-d H:i:s等多种格式 |
| end_time | int/string | 否 | 查询的结束时间,支持时间戳和 Y-m-d H:i:s等多种格式 |
| page | int | 否 | 查询页码,默认为1 |
| page_size | int | 否 | 每页数量,默认100,最大1000 |
请求示例
{
"rate_id": 12345678,
"sequence": 87654323,
"params": {
"type": 0,
"phone": "13800138000",
"start_time": 1710000000,
"end_time": "2026-03-21 20:00:00",
"page": 1,
"page_size": 100
},
"timestamp": 1710000000,
"sign": "YOUR_SIGN"
}
成功响应
{
"code": 200,
"msg": "success",
"data": {
"msg": "查询成功",
"status": 1,
"total": 888, // 符合查询条件的总记录数据
"recordList": {
{
"id": 64位字符串,
"storeName": 雀庄名称, "typeName": "天梯四麻" 或 "天梯三麻",
"name1": 一位雀士昵称, "score1": 一位雀士分数,
"name2": 二位雀士昵称, "score2": 二位雀士分数,
"name3": 三位雀士昵称, "score3": 三位雀士分数,
"name4": 四位雀士昵称 三麻时为空, "score4": 四位雀士分数 三麻时为空,
"recordTime": 对局时间, "createTime": 记录时间
},
{
"id": 64位字符串,
"storeName": 雀庄名称, "typeName": "天梯四麻" 或 "天梯三麻",
"name1": 一位雀士昵称, "score1": 一位雀士分数,
"name2": 二位雀士昵称, "score2": 二位雀士分数,
"name3": 三位雀士昵称, "score3": 三位雀士分数,
"name4": 四位雀士昵称 三麻时为空, "score4": 四位雀士分数 三麻时为空,
"recordTime": 对局时间, "createTime": 记录时间
},...
},
}
}
记录操作执行错误码
| errCode | 说明 |
|---|---|
| 200 | 成功 |
| 400 | name1~name4 或 score1~score4 数据有缺失 |
| 402 | name1~name4 中有重复雀士信息 |
| 305 | 该对局信息在最近30天内完全重叠 |
| 403 | 对局总分之和不为 1000 或 100000 (整数部分) |
| 501、502、503、504 | name1~name4 中有未在平台注册雀士 |
| 601、602、603、604 | name1~name4 有雀士分数异常状态 |
| 701、702、703、704 | name1~name4 有雀士处于记录冷却期(5分钟) |
| 801、802、803、804 | name1~name4 有雀士处于平台封禁期 |
查询用户赛季排名(该部分功能尚未完善)
按手机号查询雀士的赛季数据。
params 参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| phone | string | 是 | 手机号 |
| date | string | 否 | YYYY-MM-DD,传则查该日所在赛季;不传返回所有赛季 |
成功响应示例(单赛季)
{
"code": 200,
"msg": "success",
"data": {
"username": "张三",
"avatarUrl": "",
"status": 1,
"seasonId": 1,
"seasonName": "S1",
"seasonTotal": 12,
"bestStartIndex": 2,
"bestSum": 45.5,
"bestGroup": [{ "score": 35000, "rank": 0, "point": 40.0 }],
"createTime": "2026-03-01 00:00:00",
"msg": "查询成功",
"status": 1
}
}
门店接口
用于注册雀庄门店基础信息,并返回该门店的 API 凭证(rate_id / secret)。
params 参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| licenseType | int | 是 | 企业性质(0=个体户,1=企业) |
| companyName | string | 是 | 企业名称 |
| companyCode | string | 是 | 统一社会信用代码 |
| idCardName | string | 是 | 法人姓名 |
| companyPhone | string | 是 | 法人联系方式(数字,最多 11 位) |
| name | string | 是 | 门店名称(需唯一) |
| phone | string | 是 | 门店联系方式(数字,最多 11 位) |
| ip | string | 是 | 门店对接服务器 IP(用于生成/绑定白名单) |
| logo | string | 是 | 门店 LOGO(Base64 图片) |
请求示例
{
"rate_id": "12345678",
"sequence": "87654321",
"params": {
"licenseType": 1,
"companyName": "示例科技有限公司",
"companyCode": "91310000XXXXXXXXXX",
"idCardName": "张三",
"companyPhone": "13800138000",
"name": "示例雀庄(上海店)",
"phone": "02112345678",
"ip": "1.2.3.4",
"logo": "data:image/png;base64,...."
},
"timestamp": 1700000000,
"sign": "YOUR_SIGN"
}
成功响应
{
"code": 200,
"msg": "success",
"data": {
"rate_id": "23456789",
"secret": "YOUR_RATE_SECRET",
"msg": "注册成功",
"status": 1
}
}
用于编辑门店信息(门店必须存在且为启用状态)。可按需提交要修改的字段。
params 参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| name | string | 否 | 门店名称(需唯一) |
| phone | string | 否 | 门店联系方式 |
| logo | string | 否 | 门店 LOGO(Base64 图片) |
| cityCode | string | 否 | 城市代码 |
| address | string | 否 | 详细地址 |
| longitude | string | 否 | 经度 |
| latitude | string | 否 | 纬度 |
请求示例
{
"rate_id": "23456789",
"sequence": "11223344",
"params": {
"name": "示例雀庄(上海店)- 新名称",
"phone": "02100001111",
"address": "上海市浦东新区XX路XX号",
"longitude": "121.500000",
"latitude": "31.200000"
},
"timestamp": 1700000000,
"sign": "YOUR_SIGN"
}
成功响应
{
"code": 200,
"msg": "success",
"data": {
"msg": "编辑成功",
"status": 1
}
}
签名伪代码
客户端需与服务端保持一致的 params 序列化与字段排序。
input = { rate_id, sequence, params(object), timestamp }
secret = 门店 secret
params_str = JSON.stringify(sortKeysRecursively(params))
data = {
"params": params_str,
"rate_id": rate_id,
"sequence": sequence,
"timestamp": timestamp
}
sort keys of data asc
signStr = join(key=value, "&") + "&secret=" + secret
sign = upper(MD5(signStr))
PHP 调用示例(cURL)
<?php
class MjRateApiClient
{
const RATE_ID = 'YOUR_RATE_ID';
const RATE_SECRET = 'YOUR_RATE_SECRET';
const RATE_URL = 'https://www.mj-rate.com/api/';
public static function send(string $path, array $bizParams): array
{
$body = [
'rate_id' => self::RATE_ID,
'sequence' => (string)random_int(10000000, 99999999),
'params' => $bizParams,
'timestamp' => time(),
];
$body['sign'] = self::buildSign($body);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, self::RATE_URL . $path);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json; charset=utf-8']);
$res = curl_exec($ch);
curl_close($ch);
$json = json_decode((string)$res, true);
return is_array($json) ? $json : ['code' => 0, 'message' => '解析失败', 'data' => $res];
}
private static function buildSign(array $body): string
{
$sign = strtoupper((string)($body['sign'] ?? ''));
unset($body['sign']);
$data = [];
foreach ($body as $k => $v) {
$data[$k] = is_array($v)
? json_encode(self::sortKeysRecursive($v), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
: (string)$v;
}
ksort($data, SORT_STRING);
$parts = [];
foreach ($data as $k => $v) {
$parts[] = $k . '=' . $v;
}
$parts[] = 'secret=' . self::RATE_SECRET;
return strtoupper(md5(implode('&', $parts)));
}
private static function sortKeysRecursive(array $arr): array
{
ksort($arr, SORT_STRING);
foreach ($arr as $k => $v) {
if (is_array($v)) $arr[$k] = self::sortKeysRecursive($v);
}
return $arr;
}
}
// 示例:注册雀士
$resp = MjRateApiClient::send('user/regedit', [
'username' => rawurlencode('Tom Cat'),
'phone' => '13800138000',
'gender' => 1,
// 'avatar' => 'data:image/png;base64,...' // ≤200KB(解码后)
]);
var_dump($resp);
Java 调用示例(OkHttp)
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.*;
public class MjRateApiClient {
static final String RATE_ID = "YOUR_RATE_ID";
static final String RATE_SECRET = "YOUR_RATE_SECRET";
static final String RATE_URL = "https://www.mj-rate.com/api/";
static final ObjectMapper MAPPER = new ObjectMapper();
static final OkHttpClient HTTP = new OkHttpClient.Builder()
.callTimeout(java.time.Duration.ofSeconds(30))
.build();
public static Map<String, Object> send(String path, Map<String, Object> bizParams) throws Exception {
Map<String, Object> body = new LinkedHashMap<>();
body.put("rate_id", RATE_ID);
body.put("sequence", String.valueOf(10000000 + new Random().nextInt(90000000)));
body.put("params", bizParams);
body.put("timestamp", System.currentTimeMillis() / 1000);
body.put("sign", buildSign(body));
String json = MAPPER.writeValueAsString(body);
Request req = new Request.Builder()
.url(RATE_URL + path)
.post(RequestBody.create(json, MediaType.parse("application/json; charset=utf-8")))
.build();
try (Response resp = HTTP.newCall(req).execute()) {
String res = resp.body() != null ? resp.body().string() : "";
return MAPPER.readValue(res, new TypeReference<Map<String, Object>>() {});
}
}
private static String buildSign(Map<String, Object> body) throws Exception {
Map<String, String> data = new TreeMap<>();
for (Map.Entry<String, Object> e : body.entrySet()) {
if ("sign".equals(e.getKey())) continue;
Object v = e.getValue();
if (v instanceof Map || v instanceof List) {
data.put(e.getKey(), MAPPER.writeValueAsString(sortKeysRecursive(v)));
} else {
data.put(e.getKey(), String.valueOf(v));
}
}
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> e : data.entrySet()) {
if (sb.length() > 0) sb.append('&');
sb.append(e.getKey()).append('=').append(e.getValue());
}
sb.append("&secret=").append(RATE_SECRET);
return md5Upper(sb.toString());
}
private static Object sortKeysRecursive(Object obj) {
if (obj instanceof Map) {
Map<String, Object> m = new TreeMap<>();
((Map<?, ?>) obj).forEach((k, v) -> m.put(String.valueOf(k), sortKeysRecursive(v)));
return m;
}
if (obj instanceof List) {
List<Object> out = new ArrayList<>();
for (Object v : (List<?>) obj) out.add(sortKeysRecursive(v));
return out;
}
return obj;
}
private static String md5Upper(String s) throws Exception {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] dig = md.digest(s.getBytes(StandardCharsets.UTF_8));
StringBuilder hex = new StringBuilder();
for (byte b : dig) hex.append(String.format("%02x", b));
return hex.toString().toUpperCase(Locale.ROOT);
}
public static void main(String[] args) throws Exception {
Map<String, Object> params = new LinkedHashMap<>();
params.put("username", java.net.URLEncoder.encode("Tom Cat", "UTF-8"));
params.put("phone", "13800138000");
params.put("gender", 1);
Map<String, Object> resp = send("user/regedit", params);
System.out.println(resp);
}
}
Node.js 调用示例(fetch)
import crypto from 'node:crypto';
const RATE_ID = 'YOUR_RATE_ID';
const RATE_SECRET = 'YOUR_RATE_SECRET';
const RATE_URL = 'https://www.mj-rate.com/api/';
function sortKeysRecursive(obj) {
if (Array.isArray(obj)) return obj.map(sortKeysRecursive);
if (obj && typeof obj === 'object') {
const out = {};
Object.keys(obj).sort().forEach((k) => { out[k] = sortKeysRecursive(obj[k]); });
return out;
}
return obj;
}
function buildSign(body) {
const copy = { ...body };
delete copy.sign;
const data = {};
Object.keys(copy).forEach((k) => {
const v = copy[k];
data[k] = (v && typeof v === 'object') ? JSON.stringify(sortKeysRecursive(v)) : String(v);
});
const keys = Object.keys(data).sort();
const parts = keys.map((k) => `${k}=${data[k]}`);
parts.push(`secret=${RATE_SECRET}`);
return crypto.createHash('md5').update(parts.join('&'), 'utf8').digest('hex').toUpperCase();
}
async function send(path, bizParams) {
const body = {
rate_id: RATE_ID,
sequence: String(Math.floor(10000000 + Math.random() * 90000000)),
params: bizParams,
timestamp: Math.floor(Date.now() / 1000),
};
body.sign = buildSign(body);
const res = await fetch(RATE_URL + path, {
method: 'POST',
headers: { 'Content-Type': 'application/json; charset=utf-8' },
body: JSON.stringify(body),
});
return await res.json();
}
// 示例:注册雀士
const resp = await send('user/regedit', {
username: encodeURIComponent('Tom Cat'),
phone: '13800138000',
gender: 1,
// avatar: 'data:image/png;base64,...'
});
console.log(resp);
Python 调用示例(requests)
import hashlib
import json
import random
import time
import urllib.parse
import requests
RATE_ID = "YOUR_RATE_ID"
RATE_SECRET = "YOUR_RATE_SECRET"
RATE_URL = "https://www.mj-rate.com/api/"
def sort_keys_recursive(obj):
if isinstance(obj, list):
return [sort_keys_recursive(v) for v in obj]
if isinstance(obj, dict):
return {k: sort_keys_recursive(obj[k]) for k in sorted(obj.keys())}
return obj
def build_sign(body: dict) -> str:
data = {}
for k, v in body.items():
if k == "sign":
continue
if isinstance(v, (dict, list)):
data[k] = json.dumps(sort_keys_recursive(v), ensure_ascii=False, separators=(",", ":"))
else:
data[k] = str(v)
parts = [f"{k}={data[k]}" for k in sorted(data.keys())]
parts.append(f"secret={RATE_SECRET}")
raw = "&".join(parts)
return hashlib.md5(raw.encode("utf-8")).hexdigest().upper()
def send(path: str, biz_params: dict) -> dict:
body = {
"rate_id": RATE_ID,
"sequence": str(random.randint(10000000, 99999999)),
"params": biz_params,
"timestamp": int(time.time()),
}
body["sign"] = build_sign(body)
resp = requests.post(RATE_URL + path, json=body, timeout=30)
return resp.json()
# 示例:注册雀士
resp = send("user/regedit", {
"username": urllib.parse.quote("Tom Cat"),
"phone": "13800138000",
"gender": 1,
# "avatar": "data:image/png;base64,..."
})
print(resp)
技术支持与联系方式
如需对接支持、接口开通、问题排查,请通过以下方式联系我们: