全尚开放标准API接入开发指南
1 总体接入流程步骤示意图

我司采用IP白名单+固定令牌的双重校验方式,完成对API访问控制以及身份认证。
- 提供调用方服务器IP: 联系我司开发人员配置调用方IP白名单,这样可以防止非白名单IP随意调用,避免对服务器造成压力以及数据的窃取。
- 需要固定令牌: 令牌是目前用来做用户认证的,表明此次调用是哪方系统的调用,也是我司后期权限化以及接口调用频率的根据,请妥善保存并请勿对外泄露。
- 测试环境双方调试:上线前必备操作,对接双方依据接口规范及文档,进行接口调试。
- 上线
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.3 请求地址(url)
接口文档该部分描述了每个接口的测试和正式环境请求地址,注:不同接口变更“接口服务名”即可。
1)测试环境:http://apitest.yptcgroup.com/thirdpart/接口服务名
2)预发布环境:http://apiuat.yptcgroup.com/thirdpart/接口服务名
3)正式环境:http://api.yptcgroup.com/thirdpart/接口服务名
- 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 作者:朱智贤