全尚开放标准API接入开发指南

1 总体接入流程步骤示意图

我司采用IP白名单+固定令牌的双重校验方式,完成对API访问控制以及身份认证。

  1. 提供调用方服务器IP: 联系我司开发人员配置调用方IP白名单,这样可以防止非白名单IP随意调用,避免对服务器造成压力以及数据的窃取。
  2. 需要固定令牌: 令牌是目前用来做用户认证的,表明此次调用是哪方系统的调用,也是我司后期权限化以及接口调用频率的根据,请妥善保存并请勿对外泄露。
  3. 测试环境双方调试:上线前必备操作,对接双方依据接口规范及文档,进行接口调试。
  4. 上线

2. 网络接口访问示意图


3. 接口规范说明

3.1 通讯协议

  • REST风格的API,使用HTTP协议发送POST请求调用接口。
  • HTTP请求头类型为 Content-Type: application/json

3.2 报文及报文编码

  • 报文为UTF8编码,支持半角、中文、英文、数字、基本标点符号,请注意不要有表情符号等各种非法字符,会导致数据库写入失败。
  • 请求报文统一用json格式

3.3 接口文档说明

  • 3.3.1 接口说明
    用于接口描述、接口级别注意事项、敏感数据,以及建议调用方式等。

  • 3.3.2请求参数说明
    描述了请求报文所有输入参数的含义及注意事项。

- 3.3.4 公共请求参数
每个接口都必须输入的参数。接口文档该部分阐述了请求报文中公共请求参数的含义及注意事项,参数详见下述表格。

http header参数名 是否必须 类型 说明
X-AUTH-TOKEN 是 string 令牌
Content-Type 是 string 请求类型,统一是 application/json
  • 3.3.4 改进版的公共请求参数
http header参数名 是否必须 类型 说明
Content-Type 是 string 请求类型,统一是 application/json
appKey 是 string 第三方的系统分配标识
timestamp 是 string 请求时间戳
nonce 是 string 随机字符串,每次请求需要重新生成
sign 是 string 接口签名,签名生成见下面的demo
  • 3.3.6 appKey 分配规则
    系统会给每一个第三方的调用系统分配唯一的标识:appKey以及appSecret。其中appKey需要需要设置在Http Header中,appSecret不需要设置在Http Header中。

4. 接口签名生成demo

第一步:把appKey、timestamp、nonce 按照按ASCII升序排列,并按照key1=value1&key2=value2的形式拼接成一个字符串,另外如果请求body(需要把请求体序列化为JSON字符串)不为空,也追加到拼接的字符串末尾。可以参考如下代码

    public static String assembleSignParam(Map<String, String> params, String body) {
        // 1. 按key的ASCII升序排列
        TreeMap<String, String> sortedParams = new TreeMap<>(params);
        // 2. 拼接:key1=value1&key2=value2&...
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
            if (StringUtils.isNotBlank(entry.getKey()) && StringUtils.isNotBlank(entry.getValue())) {
                if (sb.length() > 0) {
                    sb.append("&");
                }
                sb.append(entry.getKey()).append("=").append(entry.getValue());
            }
        }

        // 3. 如果有body,追加到拼接字符串末尾
        if (StringUtils.isNotBlank(body)) {
            if (sb.length() > 0) {
                sb.append("&");
            }
            sb.append("body").append("=").append(body);
        }
        return sb.toString();
    }

第二步:根据第一步得到的字符串以及分配的appSecret,进行SHA256加密,生成请求签名。
可以参考如下代码:

  public static String generateSign(String data, String secret) {
        try {
            SecretKeySpec secretKeySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(secretKeySpec);
            byte[] digest = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
            StringBuilder hexString = new StringBuilder();
            for (byte b : digest) {
                String hex = Integer.toHexString(0xFF & b);
                if (hex.length() == 1) {
                    hexString.append('0');
                }
                hexString.append(hex);
            }
            return hexString.toString();
        } catch (Exception e) {
            log.error("HmacSHA256签名异常", e);
            throw new RuntimeException("HmacSHA256签名异常", e);
        }
    }

第三步:需要把appKey、timestamp、nonce以及第二步生成的签名sign放到请求的Header中,发起请求。

完整示例:

@Slf4j
public class EPOpenApiUtil {


    public static void main(String[] args) {
        callEpOpenApi();
    }


    public static void callEpOpenApi() {
        // 创建一个OkHttpClient对象
        OkHttpClient okHttpClient = new Builder()
                .connectTimeout(30, TimeUnit.SECONDS)
                .writeTimeout(30, TimeUnit.SECONDS)
                .readTimeout(30, TimeUnit.SECONDS)
                .build();

        Map<String, Object> requestBodyMap = new HashMap<>();
        requestBodyMap.put("merchantCodeList", Lists.newArrayList("FQT025WXL"));
        RequestBody requestBody = RequestBody.create(MediaType.get("application/json"), JSON.toJSONString(requestBodyMap));
        Map<String, String> headersNameAndValue = new HashMap<>();
        String timestamp = String.valueOf(System.currentTimeMillis());
        //nonce可以自由定义,但是每次请求需要重新生成
        String nonce = java.util.UUID.randomUUID().toString().replace("-", "");
        String appKey = "<your app key>";
        String appSecret = "<your app secret>";
        headersNameAndValue.put("appKey", appKey);
        headersNameAndValue.put("timestamp", timestamp);
        headersNameAndValue.put("nonce", nonce);
        //对请求header进行排序并拼接
        String sortParam = assembleSignParam(headersNameAndValue, JSON.toJSONString(requestBodyMap));
        //HmacSHA256签名
        String sign = generateSign(sortParam, appSecret);
        headersNameAndValue.put("sign", sign);
        headersNameAndValue.put("Content-Type", "application/json");
        Request request = new Request.Builder().url("http://api.yptcgroup.com/thirdpart/bojun/masterdata/goods")
                .headers(Headers.of(headersNameAndValue))
                .post(requestBody).build();
        try (Response response = okHttpClient.newCall(request).execute()) {
            String body = response.body().string();
            log.info("epOpenApi response:{}", body);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static String assembleSignParam(Map<String, String> params, String body) {
        // 1. 按key的ASCII升序排列
        TreeMap<String, String> sortedParams = new TreeMap<>(params);
        // 2. 拼接:key1=value1&key2=value2&...
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
            if (StringUtils.isNotBlank(entry.getKey()) && StringUtils.isNotBlank(entry.getValue())) {
                if (sb.length() > 0) {
                    sb.append("&");
                }
                sb.append(entry.getKey()).append("=").append(entry.getValue());
            }
        }

        // 3. 如果有body,追加到拼接字符串末尾
        if (StringUtils.isNotBlank(body)) {
            if (sb.length() > 0) {
                sb.append("&");
            }
            sb.append("body").append("=").append(body);
        }
        return sb.toString();
    }

    public static String generateSign(String data, String secret) {
        try {
            SecretKeySpec secretKeySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(secretKeySpec);
            byte[] digest = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
            StringBuilder hexString = new StringBuilder();
            for (byte b : digest) {
                String hex = Integer.toHexString(0xFF & b);
                if (hex.length() == 1) {
                    hexString.append('0');
                }
                hexString.append(hex);
            }
            return hexString.toString();
        } catch (Exception e) {
            log.error("HmacSHA256签名异常", e);
            throw new RuntimeException("HmacSHA256签名异常", e);
        }
    }
}
文档更新时间: 2026-06-17 09:44   作者:朱智贤