# 美团企业版签名实例
# 1 参考示例
假设美团企业版分配的秘钥信息如下(真实秘钥数据以实际分配为准):
| 名称 | 取值 | 备注 |
|---|---|---|
| entId | 617 | 美团企业版分配的企业ID |
| accessKey | AB4O3CP2R3ER-TK | 请求体中需要传此值 |
| secretKey | 2Ce0eqKh/ug5+pNnnKAsWA== | 参照2.3中AES加密使用 |
# 2 加密说明
以查询订单详情接口为例,加密流程图如下:

# 2.1 请求体
content为请求体加密内容,具体请求体内容参照各个接口的请求体说明。
| 名称 | 类型 | 是否必填 | 说明 | 实例 |
|---|---|---|---|---|
| accessKey | String | 是 | 美团企业版分配给客户的接入秘钥 | AB4O3CP2R3ER-TK |
| content | String | 是 | 请求体内容,将请求参数JSON序列化后进行加密的结果值,加密秘钥使用secretKey参数 | O38ebrt4UfPIF8hZOE0LC4S7OZmI_f-YmO1qUi8g4hrbE6Gx8DviEwOGXvCI7Wko |
# 2.2 请求体中加密content对应接口的明文请求参数
查询请求:orderDetailQueryRequest
OrderDetailQueryRequest orderDetailQueryRequest = new OrderDetailQueryRequest();
orderDetailQueryRequest.setEntId(617L);
orderDetailQueryRequest.setTs(1512963578L);
orderDetailQueryRequest.setSqtBizOrderId(1L);
# 2.3 待加密的json串明文
plainText = JsonUtil.object2json(orderDetailQueryReqest)
json串明文如下:
{
"ts":1512963578,
"entId":617,
"sqtBizOrderId":1
}
# 2.4 请求参数明文通过AES加密,得到请求体中的加密content
// 加密(此处只针对Java版本调用,其他语言参考第四章各语言加解密参考)
// EncryptUtil类具体内容参照下面第4项的具体内容
// 根据上面所给数据,加密结果为:O38ebrt4UfPIF8hZOE0LC4S7OZmI_f-YmO1qUi8g4hrbE6Gx8DviEwOGXvCI7Wko
content = EncryptUtil.aesEncrypt(plainText, secretKey)
# 3 解密说明
解密流程图如下:

# 3.1 响应参数
data为响应参数加密后的内容,data加密前的数据结构参考各接口规范说明。
| 名称 | 类型 | 说明 | 实例 |
|---|---|---|---|
| traceId | String | 日志查询ID | 56a0af18ae30a168d4006c7c |
| status | Integer | 0: 调用成功 其他值均为:调用失败 | 0 |
| msg | String | 失败时的错误描述 | |
| data | String | 响应数据,将响应参数JSON序列化后进行加密的结果值,解密秘钥使用secretKey参数 | O38ebrt4UfPIF8hZOE0LC4S7OZmI_f-YmO1qUi8g4hrbE6Gx8DviEwOGXvCI7Wko |
# 3.2 响应参数通过AES解密,得到data加密前的数据
// 解密(此处只针对Java版本调用,其他语言参考第四章各语言加解密参考)
// EncryptUtil类具体内容参照下面第4项的具体内容
plainText = EncryptUtil.aesDecrypt(data, secretKey)
# 4 各语言加密解密参考
# 4.1 Java加密解密参考
// Base64依赖的pom文件如下
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.11</version>
</dependency>
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
public class EncryptUtil {
private EncryptUtil() {
}
private static final Logger LOGGER = LoggerFactory.getLogger(EncryptUtil.class);
private static final String ALGORITHM_HMAC_SHA1 = "HmacSHA1";
private static final String BASE_NUMBER = "0123456789";
// AES加密
public static String aesEncrypt(String originText, String secret) throws Exception {
AesCypher cypher = new AesCypher(secret);
return cypher.encrypt(originText);
}
// AES解密
public static String aesDecrypt(String encryptedText, String secret) throws Exception{
AesCypher cypher = new AesCypher(secret);
return cypher.decrypt(encryptedText);
}
// 内部aes类
static class AesCypher {
private static final Logger LOGGER = LoggerFactory.getLogger(AesCypher.class);
private static String DEFAULT_SECRET = "2Ce0eqKh/ug5+pNnnKAsWA==";
private byte[] linebreak;
private SecretKey key;
private Cipher cipher;
private Base64 coder;
public AesCypher(String secret) {
this.linebreak = new byte[0];
try {
this.coder = new Base64(32, this.linebreak, true);
byte[] secrets = this.coder.decode(secret);
// 转换为AES专用密钥
this.key = new SecretKeySpec(secrets, "AES");
// 创建密码器,算法/工作模式/补码方式 提供商
this.cipher = Cipher.getInstance("AES/ECB/PKCS5Padding", "SunJCE");
} catch (Exception e) {
LOGGER.error("AesCypher.genKey NoSuchAlgorithmException", e);
}
}
public AesCypher() {
this(DEFAULT_SECRET);
}
public synchronized String encrypt(String plainText) throws Exception {
this.cipher.init(Cipher.ENCRYPT_MODE, this.key);
byte[] cipherText = this.cipher.doFinal(plainText.getBytes("UTF-8"));
return new String(this.coder.encode(cipherText));
}
public synchronized String decrypt(String codedText) throws Exception {
byte[] encypted = this.coder.decode(codedText.getBytes());
this.cipher.init(Cipher.DECRYPT_MODE, this.key);
byte[] decrypted = this.cipher.doFinal(encypted);
return new String(decrypted, "UTF-8");
}
}
}
# 4.2 Node.js加密解密参考
const crypto = require('crypto');
var SqtAESUtil = {};
SqtAESUtil.MyConstanst = {
ENCODING: 'utf8',
BASE64: 'base64',
MODE: 'aes-128-ecb',
IV: new Buffer(''),
BUFFER: 'buffer',
SECRETKEY: 'xd1nzb/N9Nx3+VoImzCsnw==' //美团企业版提供的测试密钥
};
/**
* 加密
* @param plainText 要加密的明文内容
* @returns {string} 返回字符串
*/
SqtAESUtil.aesEncrypt = function (plainText) {
var secretkey = new Buffer(SqtAESUtil.MyConstanst.SECRETKEY, SqtAESUtil.MyConstanst.BASE64);
var cipherChunks = [];
var cipher = crypto.createCipheriv(SqtAESUtil.MyConstanst.MODE, secretkey, SqtAESUtil.MyConstanst.IV);
cipher.setAutoPadding(true);
cipherChunks.push(cipher.update(new Buffer(plainText, SqtAESUtil.MyConstanst.ENCODING), SqtAESUtil.MyConstanst.BUFFER, SqtAESUtil.MyConstanst.BASE64));
cipherChunks.push(cipher.final(SqtAESUtil.MyConstanst.BASE64));
return cipherChunks.join('');
}
/**
* 解密
* @param encryptText 密文
* @returns {string} 字符串
*/
SqtAESUtil.aesDecrypt = function (encryptText) {
var secretkey = new Buffer(SqtAESUtil.MyConstanst.SECRETKEY, SqtAESUtil.MyConstanst.BASE64);
var cipherChunks = [];
var decipher = crypto.createDecipheriv(SqtAESUtil.MyConstanst.MODE, secretkey, SqtAESUtil.MyConstanst.IV);
decipher.setAutoPadding(true);
cipherChunks.push(decipher.update(encryptText, SqtAESUtil.MyConstanst.BASE64, SqtAESUtil.MyConstanst.ENCODING));
cipherChunks.push(decipher.final(SqtAESUtil.MyConstanst.ENCODING));
return cipherChunks.join('');
}
/**
* 测试加密解密
*/
SqtAESUtil.test = function () {
var data = '{"sign":"sgW1bxc7oatFhOJXAeHnNg==","ts":1512964057,"method":"waimai.poi.list","longitude":116488645,"latitude":40007069}';
var result = SqtAESUtil.aesEncrypt(data);
console.log("加密之前明文内容: " + data);
console.log("nodeJs加密结果: " + result);
console.log("nodeJs解密结果: " + SqtAESUtil.aesDecrypt(result));
var sqt = 'UgJn07uNgW7S7fJK0R0xVbaLxoCGPQIzoP-_K4Hmp4RduGszhm2mbUs2toZhCtXKP5JGXVTZ9kGts2Wx3IJQCd90ptMoJTDB0vu7mkedEr4KZCvZn77EZLssMC5SpXilmQ-5RXHzvMIT0ASH-IXepTP_O16U37QqCkEb5L1WLy4';
console.log("美团企业版java生成的密文: " + sqt);
console.log("nodejs解密: " + SqtAESUtil.aesDecrypt(sqt));
}
// 测试
SqtAESUtil.test();
# 4.3 C#加密解密&HTTP调用参考
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
public class EncryptUtil
{
#region AES加密
/// <summary>
/// AES加密
/// </summary>
/// <param name="text">明文</param>
/// <param name="key">密钥,长度为16的字符串</param>
/// <returns>密文</returns>
public static string AESEncode(string text, string key)
{
byte[] keys = Convert.FromBase64String(key);
RijndaelManaged rijndaelCipher = new RijndaelManaged();
rijndaelCipher.Mode = CipherMode.ECB;
rijndaelCipher.Padding = PaddingMode.PKCS7;
rijndaelCipher.KeySize = 128;
rijndaelCipher.BlockSize = 128;
byte[] pwdBytes = keys;
byte[] keyBytes = new byte[16];
int len = pwdBytes.Length;
if (len > keyBytes.Length)
len = keyBytes.Length;
Array.Copy(pwdBytes, keyBytes, len);
rijndaelCipher.Key = keyBytes;
ICryptoTransform transform = rijndaelCipher.CreateEncryptor();
byte[] plainText = Encoding.UTF8.GetBytes(text);
byte[] cipherBytes = transform.TransformFinalBlock(plainText, 0, plainText.Length);
return ConvertHelper.ToBase64StringURLSafe(cipherBytes);//输出为Base64
}
#endregion
#region AES解密
/// <summary>
/// AES解密
/// </summary>
/// <param name="text">密文</param>
/// <param name="key">密钥,长度为16的字符串</param>
/// <returns>明文</returns>
public static string AESDecode(string text, string key)
{
RijndaelManaged rijndaelCipher = new RijndaelManaged();
rijndaelCipher.Mode = CipherMode.ECB;
rijndaelCipher.Padding = PaddingMode.PKCS7;
rijndaelCipher.KeySize = 128;
rijndaelCipher.BlockSize = 128;
byte[] encryptedData = ConvertHelper.FromBase64StringURLSafe(text);
byte[] pwdBytes = Convert.FromBase64String(key);
byte[] keyBytes = new byte[16];
int len = pwdBytes.Length;
if (len > keyBytes.Length)
len = keyBytes.Length;
Array.Copy(pwdBytes, keyBytes, len);
rijndaelCipher.Key = keyBytes;
ICryptoTransform transform = rijndaelCipher.CreateDecryptor();
byte[] plainText = transform.TransformFinalBlock(encryptedData, 0, encryptedData.Length);
return Encoding.UTF8.GetString(plainText);
}
#endregion
}
/// C# HTTP调用代码实例参照
var handler = new HttpClientHandler();
handler.AllowAutoRedirect = false;
var httpClient = new HttpClient(handler);
var dict = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("accessKey", ApiConfigManager.MeiTuanTakeoutAccessKey),
new KeyValuePair<string, string>("content", loginRequestBody)
};
var formUrlEncodedContent = new FormUrlEncodedContent(dict);
formUrlEncodedContent.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
formUrlEncodedContent.Headers.ContentType.CharSet = "utf-8";
var response = await httpClient.PostAsync(LOGIN_URL_TEST, formUrlEncodedContent);
if (response.StatusCode == HttpStatusCode.Redirect)
{
return response.Headers.Location.ToString();
}
依赖的ConvertHelper如下:
/// 美团企业版基于Java实现,标准的加解密格式是urlSafe的Base64编码,该格式会在基础Base64编码的之上,额外处理'+'、'/'、'='
public class ConvertHelper
{
/// <summary>
/// 将Java安全的base64字符串转换为byte数组
/// </summary>
/// <param name="convert"></param>
/// <param name="javaURLSafeString"></param>
/// <returns></returns>
public static byte[] FromBase64StringURLSafe(string javaURLSafeString)
{
javaURLSafeString = javaURLSafeString.Replace("-", "+").Replace("_", "/");
var base64 = Encoding.ASCII.GetBytes(javaURLSafeString);
var padding = base64.Length * 3 % 4;
if (padding != 0)
{
javaURLSafeString = javaURLSafeString.PadRight(javaURLSafeString.Length + padding, '=');
}
return Convert.FromBase64String(javaURLSafeString);
}
/// <summary>
/// 将byte数组转换为java安全的base64字符串
/// </summary>
/// <param name="convert"></param>
/// <param name="bytes"></param>
/// <returns></returns>
public static string ToBase64StringURLSafe(byte[] bytes)
{
string base64String = Convert.ToBase64String(bytes);
return base64String.Replace("+", "-")
.Replace("/", "_")
.Replace("=", "");
}
}
# 4.4 GO语言加密解密参考
package main
import (
"bytes"
"crypto/aes"
"encoding/base64"
"errors"
"fmt"
"strconv"
)
func main() {
keys, err := base64.StdEncoding.DecodeString("xd1nzb/N9Nx3+VoImzCsnw==")
if err != nil {
fmt.Println(err)
return
}
plainText := `{"sign":"sgW1bxc7oatFhOJXAeHnNg==","ts":1512964057,"method":"waimai.poi.list","longitude":116488645,"latitude":40007069}`
//aes encryption
cipherText, err := AESEcbEncrypt(plainText, keys)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("cipherText:", cipherText)
// aes decryption
plainText, err = AESEcbDecrypt(cipherText, keys)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("plainText", plainText)
}
//aes/ecb/pkcs7
func AESEcbEncrypt(plainText string, key []byte) (cipherText string, err error) {
if len(key) != 16 && len(key) != 24 && len(key) != 32 {
return "", errors.New("crypto/aes: invalid key size " + strconv.Itoa(len(key)))
}
block, err := aes.NewCipher(key)
if err != nil {
return
}
plainData := pkcs7Padding([]byte(plainText), block.BlockSize())
if plainData == nil {
err = errors.New("unsupported content to be encrypted")
return
}
decrypted := make([]byte, len(plainData))
size := block.BlockSize()
for bs, be := 0, size; bs < len(plainData); bs, be = bs+size, be+size {
block.Encrypt(decrypted[bs:be], plainData[bs:be])
}
//urlSafe
cipherText = base64.RawURLEncoding.EncodeToString(decrypted)
return
}
//aes/ecb/pkcs7
func AESEcbDecrypt(cipherText string, key []byte) (plainText string, err error) {
cipherData, err := base64.RawURLEncoding.DecodeString(cipherText)
if err != nil {
return
}
block, err := aes.NewCipher(key)
if err != nil {
return
}
if len(cipherData)%block.BlockSize() != 0 {
err = errors.New("cipher text is not a multiple of the block size")
return
}
size := block.BlockSize()
decrypted := make([]byte, len(cipherData))
for bs, be := 0, size; bs < len(cipherData); bs, be = bs+size, be+size {
block.Decrypt(decrypted[bs:be], cipherData[bs:be])
}
plainData := pkcsUnPadding(decrypted)
return string(plainData), nil
}
// The blockSize argument should be 16, 24, or 32.
// Corresponding AES-128, AES-192, or AES-256.
func pkcs7Padding(plainText []byte, blockSize int) []byte {
paddingSize := blockSize - len(plainText)%blockSize
paddingText := bytes.Repeat([]byte{byte(paddingSize)}, paddingSize)
return append(plainText, paddingText...)
}
func pkcsUnPadding(plainText []byte) []byte {
length := len(plainText)
number := int(plainText[length-1])
return plainText[:length-number]
}
# 4.5 PHP语言加密解密参考(以免登为例)
<?php
function aes_encrypt(array $data, $secretKey)
{
// echo json_encode($data,JSON_UNESCAPED_UNICODE);
$data = openssl_encrypt(json_encode($data,JSON_UNESCAPED_UNICODE), 'AES-128-ECB', base64_decode($secretKey));
// var_dump($data);
$data = str_replace('/', '_', $data);
$data = str_replace('+', '-', $data);
$data = str_replace('=', '', $data);
// dd($data);
return $data;
}
function aes_decrypt(string $str, $secretKey)
{
$str = str_replace('_', '/', $str);
$str = str_replace('-', '+', $str);
$data = openssl_decrypt($str, 'AES-128-ECB', base64_decode($secretKey));
// dd($data);
// var_dump($data);
return json_decode($data, TRUE);
}
function loginFree2Post($url, $data)
{
// $data=json_encode($data,JSON_UNESCAPED_UNICODE);
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HEADER, TRUE);
curl_setopt($curl, CURLOPT_TIMEOUT, 1000);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_POST, TRUE);
curl_setopt($curl, CURLOPT_HTTPHEADER, array(
'Content-Type: application/x-www-form-urlencoded;charset=UTF-8',
'Accept: application/json',
));//重点
curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data));
$response = curl_exec($curl);
if (curl_errno($curl)) {
return curl_error($curl);
}
return $response;
}
function getMillisecond() {
$t = explode(' ', microtime());
list($s1, $s2) = explode(' ', microtime());
return (float)sprintf('%.0f',(floatval($s1) + floatval($s2)) * 1000);
}
function randstr($length = 16) {
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
$str = "";
for ($i = 0; $i < $length; $i++) {
$str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
return $str;
}
//美团外卖入口
function mt_waimai($params)
{
$url = 'https://waimai-openapi.apigw.test.meituan.com/api/sqt/open/login/h5/loginFree/redirection?test_open_swimlane=test-open';
$staffPhone = isset($params['mobile'])?$params['mobile']:''; //员工手机号 1. 登录时, staffPhone/staffEmail/staffNum 三者必填一个, 与企业员工唯一识别对应
$staffEmail = isset($params['staffEmail'])?$params['staffEmail']:''; //员工邮箱
$staffNum = isset($params['staffNum'])?$params['staffNum']:''; //员工工号
$externalOrgId = isset($params['externalOrgId'])?$params['externalOrgId']:''; //部门唯一标识
$orderId = isset($params['orderId'])?$params['orderId']:''; //唯一订单号
$ts = getMillisecond();
$staffInfo = ['staffPhone'=> $staffPhone];
$nonce = randstr(32);
$longitude = isset($params['longitude'])?$params['longitude']:''; //经度 116.480881
$latitude = isset($params['latitude'])?$params['latitude']:''; //纬度 39.989410
$geotype = isset($params['geotype'])?$params['geotype']:'wgs84'; //gcj02(火星坐标系)或者wgs84(国际坐标系)
$address = isset($params['address'])?$params['address']:''; //经纬度对应的中文地址北京市朝阳区阜通东大街6号
$location = ['longitude' => $longitude, 'latitude' => $latitude, 'geotype' => $geotype, 'address' => $address];
$bizParam = ['location' => $location];
$bizParam = [];
$data = ['productType' => 'mt_waimai', 'ts'=>$ts, 'entId' => '103393', 'staffInfo' => $staffInfo, 'nonce' => $nonce,];
$content = aes_encrypt($data, '+AN4Qre9BaJsmPQBSzEXGA==');
$postData = ['accessKey' => 'CPQ3H92TTWQQ-TK', 'content' => $content];
$result = loginFree2Post($url, $postData);
return $result;
}
# 4.6 Python语言加密解密参考
from Crypto.Cipher import AES
from base64 import b64decode, b64encode, urlsafe_b64decode, urlsafe_b64encode
BLOCK_SIZE = AES.block_size
class AESCipher:
def __init__(self, key):
self.key = b64decode(key)
@staticmethod
def pad(text):
return text + (BLOCK_SIZE - len(text.encode()) % BLOCK_SIZE) * chr(BLOCK_SIZE - len(text.encode()) % BLOCK_SIZE)
@staticmethod
def un_pad(text):
return text[:-ord(text[len(text) - 1:])]
def encrypt(self, text):
"""
加密
"""
text = self.pad(text).encode()
cipher = AES.new(key=self.key, mode=AES.MODE_ECB)
encrypted_text = cipher.encrypt(text)
return urlsafe_b64encode(encrypted_text).decode('utf-8')
def decrypt(self, encrypted_text):
"""
解密
"""
encrypted_text = pad_content(encrypted_text)
encrypted_text = urlsafe_b64decode(encrypted_text)
cipher = AES.new(key=self.key, mode=AES.MODE_ECB)
decrypted_text = cipher.decrypt(encrypted_text)
return self.un_pad(decrypted_text).decode('utf-8')
def pad_content(content):
mod_result = len(content) % 4
if mod_result > 0:
padding_size = 4 - mod_result
content += '=' * padding_size
return content
if __name__ == '__main__':
# 美团企业版secretKey
secret = 'GFfzmJDuV88zYFvCBHML6g=='
cipher = AESCipher(key=secret)
# 请求体
plain_text = '{"sign":"sgW1bxc7oatFhOJXAeHnNg==","ts":1512964057,"method":"waimai.poi.list","longitude":116488645,"latitude":40007069}'
# 加密
cipherText = cipher.encrypt(plain_text)
print(cipherText)
# 解密
plain_text = cipher.decrypt(cipherText)
print(plain_text)
# 5 完整调用实例
# 5.1 SqtClient类(main方法入口开始)
import com.meituan.sqt.request.BaseApiRequest;
import com.meituan.sqt.request.OrderDetailQueryReq;
import com.meituan.sqt.utils.EncryptUtil;
import com.meituan.sqt.utils.HttpClientUtil;
import com.meituan.sqt.utils.JsonUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SqtClient {
private static final Logger log = LoggerFactory.getLogger(SqtClient.class);
private static final String FORM_URLENCODED = "application/x-www-form-urlencoded";
private static final String ACCEPT_JSON = "application/json";
// 测试环境域名
protected String host = "https://inf-openapi.apigw.test.meituan.com/api/sqt/openapi";
protected String accessKey;
protected Long entId;
protected String secretKey;
public SqtClient(String accessKey, Long entId, String secretKey) {
this.accessKey = accessKey;
this.entId = entId;
this.secretKey = secretKey;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public String getAccessKey() {
return accessKey;
}
public void setAccessKey(String accessKey) {
this.accessKey = accessKey;
}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public String commonPostInvoke(String url, BaseApiRequest request) {
request.setTs(System.currentTimeMillis()/1000);
request.setEntId(this.entId);
try {
String rawContent = JsonUtil.object2Json(request, false);
String body = "accessKey=" + getAccessKey() + "&content=" + EncryptUtil.aesEncrypt(rawContent, getSecretKey());
String responseStr = HttpClientUtil.invokePost(url, body, FORM_URLENCODED, ACCEPT_JSON);
return responseStr;
} catch (Exception e) {
log.error("HTTP调用失败, url:{}", url, e);
}
return null;
}
public String queryOrderDetail(OrderDetailQueryReq req) {
return this.commonPostInvoke(this.host + "/queryOrderDetail", req);
}
public static void main(String[] args) {
// 调用
SqtClient sqtClient = new SqtClient("AB4O3CP2R3ER-TK", 617L, "2Ce0eqKh/ug5+pNnnKAsWA==");
OrderDetailQueryReq request = new OrderDetailQueryReq();
request.setSqtBizOrderId(1L);
String result = sqtClient.queryOrderDetail(request);
}
}
# SqtClient类依赖的相关类如下:
# HttpClientUtil
// 基于apache httpclient实现
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.io.InputStream;
import java.net.SocketTimeoutException;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Properties;
public class HttpClientUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientUtil.class);
private static PoolingHttpClientConnectionManager httpClientConnectionManager = new PoolingHttpClientConnectionManager();
private static CloseableHttpClient httpClient = null;
private static CloseableHttpClient httpsClient = null;
private static HttpClientUtil.HttpConfig httpConfig = null;
private static SSLConnectionSocketFactory sslsf = null;
// 连接池最大连接数和每路由最大连接数可通过httpconf.properties进行配置
private static final String HTTP_CONF_FILE_NAME = "fenxiao-httpconf.properties";
private static final int MAX_TOTAL_CONNECTION = 800;
private static final int MAX_PER_ROUTE = 150;
private static String UTF_8 = "UTF-8";
interface ContentType {
String FORM_URLENCODED = "application/x-www-form-urlencoded";
String JSON = "application/json; charset=utf-8";
}
interface AcceptType {
String ACCEPT_JSON = "application/json";
String ACCEPT_ANNY = "*/*";
}
private HttpClientUtil() {
// prevent instantialization
}
static {
loadConf();
httpClientConnectionManager.setMaxTotal(httpConfig.getMaxTotalConnection()); // 连接池最大连接数
httpClientConnectionManager.setDefaultMaxPerRoute(httpConfig.getMaxPerRoute()); // 每路由最大连接数
// 通过连接池获取的httpClient
httpClient = HttpClients.custom().setConnectionManager(httpClientConnectionManager).build();
// 通过连接池获取的httpsClient, 能支持https
httpsClient = HttpClients.custom().setSSLSocketFactory(sslsf).setConnectionManager(httpClientConnectionManager).build();
LOGGER.info("HttpClient initialization");
}
/**
* 如果配置httpconf.properties, 则从该文件中读取http连接池的配置参数, 否则使用默认值
*/
private static void loadConf() {
if (httpConfig == null) {
httpConfig = new HttpConfig();
}
Properties properties = new Properties();
try {
InputStream inputStream = HttpClientUtil.class.getClassLoader().getResourceAsStream(HTTP_CONF_FILE_NAME);
if (inputStream == null) {
LOGGER.warn("httpConfig file={} does not exist", HTTP_CONF_FILE_NAME);
return;
}
properties.load(inputStream);
int maxTotalConnection = Integer.parseInt(properties.getProperty("max_total_connection"));
int maxPerRoute = Integer.parseInt(properties.getProperty("max_per_route"));
LOGGER.info("max_total_connection={}, max_per_route={}", maxTotalConnection, maxPerRoute);
httpConfig.setMaxTotalConnection(maxTotalConnection);
httpConfig.setMaxPerRoute(maxPerRoute);
} catch (IOException e) {
LOGGER.warn("read httpConfig from file={} failed", HTTP_CONF_FILE_NAME, e);
} catch (NumberFormatException e) {
LOGGER.warn("read httpConfig from file={} failed", HTTP_CONF_FILE_NAME, e);
} catch (Exception e) {
LOGGER.warn("read httpConfig from file={} failed", HTTP_CONF_FILE_NAME, e);
}
}
private static void initHttps() {
X509TrustManager trustManager = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
// 初始化SSL上下文
sslContext.init(null, new TrustManager[]{trustManager}, null);
// SSL套接字连接工厂,NoopHostnameVerifier为信任所有服务器
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
} catch (NoSuchAlgorithmException e) {
LOGGER.error("初始化https支持失败", e);
} catch (KeyManagementException e) {
LOGGER.error("初始化https支持失败", e);
}
}
static class HttpConfig {
private int maxTotalConnection = MAX_TOTAL_CONNECTION;
private int maxPerRoute = MAX_PER_ROUTE;
public int getMaxTotalConnection() {
return maxTotalConnection;
}
public void setMaxTotalConnection(int maxTotalConnection) {
this.maxTotalConnection = maxTotalConnection;
}
public int getMaxPerRoute() {
return maxPerRoute;
}
public void setMaxPerRoute(int maxPerRoute) {
this.maxPerRoute = maxPerRoute;
}
}
public static String invokePost(String url, String body, String contentType, String accept) throws Exception {
URIBuilder uriBuilder = new URIBuilder();
valueForUriBuilder(url, uriBuilder);
HttpPost httpPost = new HttpPost(uriBuilder.build());
httpPost.addHeader("Content-type",contentType);
httpPost.setHeader("Accept", accept);
httpPost.setEntity(new StringEntity(body, Charset.forName(UTF_8)));
return sendRequest(url, httpPost);
}
private static String sendRequest(String url, HttpRequestBase request) throws Exception {
CloseableHttpClient client;
if (url.startsWith("https")) {
client = httpsClient;
} else {
client = httpClient;
}
long st = System.currentTimeMillis();
String responseStr;
int status = 0;
try {
CloseableHttpResponse response = client.execute(request);
HttpEntity entity = response.getEntity();
status = response.getStatusLine().getStatusCode();
if (entity != null) {
responseStr = EntityUtils.toString(entity, UTF_8);
response.close();
} else {
responseStr = StringUtils.EMPTY;
}
} catch (SocketTimeoutException e) {
LOGGER.error("HttpClient.sendRequest, url:{}, use_time:{} ms",
request.getURI(), (System.currentTimeMillis() - st), e);
throw e;
} catch (ClientProtocolException e) {
LOGGER.error("HttpClient.sendRequest, url:{}, use_time:{} ms",
request.getURI(), (System.currentTimeMillis() - st), e);
throw e;
} catch (IOException e) {
LOGGER.error("HttpClient.sendRequest, url:{}, use_time:{} ms",
request.getURI(), (System.currentTimeMillis() - st), e);
throw e;
} finally {
request.releaseConnection();
}
LOGGER.info("HttpClient Success, status:{}, url:{}, use_time:{} ms",
status, request.getURI(), (System.currentTimeMillis() - st));
return responseStr;
}
private static void valueForUriBuilder(String url, URIBuilder uriBuilder) {
Integer apartIndex = url.indexOf("?");
if (apartIndex == -1) {
uriBuilder.setPath(url);
} else {
uriBuilder.setPath(url.substring(0, apartIndex));
uriBuilder.setCustomQuery(url.substring(apartIndex + 1, url.length()));
}
}
}
# JsonUtil
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
import java.util.Map;
public class JsonUtil {
private JsonUtil() {
}
private static final Logger LOGGER = LoggerFactory.getLogger(JsonUtil.class);
private static ObjectMapper JSON_TO_OBJECT_MAPPER;
private static ObjectMapper OBJECT_TO_JSON_MAPPER; // 对null对象不转换
private static ObjectMapper OBJECT_TO_JSON_MAPPER_CONTAIN_NULL; // 包含null对象
static {
JSON_TO_OBJECT_MAPPER = new ObjectMapper();
JSON_TO_OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
OBJECT_TO_JSON_MAPPER_CONTAIN_NULL = new ObjectMapper();
OBJECT_TO_JSON_MAPPER_CONTAIN_NULL.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
OBJECT_TO_JSON_MAPPER = new ObjectMapper();
OBJECT_TO_JSON_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL);
OBJECT_TO_JSON_MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
}
public static <T> T json2Object(String json, Class<T> clazz) {
try {
return json2ObjectThrowException(json, clazz);
} catch (Exception e) {
LOGGER.error("json error", e);
return null;
}
}
public static <T> T json2Object(String json, TypeReference typeReference) {
try {
return json2ObjectThrowException(json, typeReference);
} catch (Exception e) {
LOGGER.error("json error", e);
return null;
}
}
public static <T> List<T> json2ObjectList(String json, Class<T> clazz) {
try {
return json2ObjectListThrowException(json, clazz);
} catch (Exception e) {
LOGGER.error("json error", e);
return null;
}
}
public static String object2Json(Object object) {
return object2Json(object, false);
}
public static String object2Json(Object object, boolean containNull) {
try {
return object2JsonThrowException(object, containNull);
} catch (Exception e) {
LOGGER.error("json error", e);
return null;
}
}
public static Map json2Map(String json) {
try {
return json2MapThrowException(json);
} catch (Exception e) {
LOGGER.error("json error", e);
return null;
}
}
public static <T> T json2ObjectThrowException(String json, Class<T> clazz) throws Exception{
if (StringUtils.isBlank(json)) {
return null;
}
try {
return JSON_TO_OBJECT_MAPPER.readValue(json, clazz);
} catch (Exception e) {
LOGGER.error("json2Object error, json: {}", json, e);
throw e;
}
}
public static <T> T json2ObjectThrowException(String json, TypeReference typeReference) throws Exception{
if (StringUtils.isBlank(json)) {
return null;
}
try {
return JSON_TO_OBJECT_MAPPER.readValue(json, typeReference);
} catch (Exception e) {
LOGGER.error("json2Object error, json: {}", json, e);
throw e;
}
}
public static <T> List<T> json2ObjectListThrowException(String json, Class<T> clazz) throws IOException {
if (StringUtils.isBlank(json)) {
return null;
}
try {
List<T> objects = JSON_TO_OBJECT_MAPPER.readValue(json, TypeFactory.defaultInstance().constructCollectionType(List.class, clazz));
return objects;
} catch (Exception e) {
LOGGER.error("json2ObjectList error, json: {}", json, e);
throw e;
}
}
public static String object2JsonThrowException(Object object, boolean containNull) throws Exception{
if (object == null) {
return "";
}
try {
if (!containNull) {
return OBJECT_TO_JSON_MAPPER.writeValueAsString(object);
} else {
return OBJECT_TO_JSON_MAPPER_CONTAIN_NULL.writeValueAsString(object);
}
} catch (Exception e) {
LOGGER.error("object2Json error", e);
throw e;
}
}
public static Map json2MapThrowException(String json) throws Exception{
if (StringUtils.isBlank(json)) {
return null;
}
try {
return JSON_TO_OBJECT_MAPPER.readValue(json, Map.class);
} catch (Exception e) {
LOGGER.error("json2Map error, json: {}", json, e);
throw e;
}
}
}
# OrderDetailQueryReq
public class OrderDetailQueryReq extends BaseApiRequest {
private Long sqtBizOrderId;
public Long getSqtBizOrderId() {
return sqtBizOrderId;
}
public void setSqtBizOrderId(Long sqtBizOrderId) {
this.sqtBizOrderId = sqtBizOrderId;
}
}
# BaseApiRequest
import javax.validation.constraints.NotNull;
public class BaseApiRequest {
@NotNull(message = "不允许为空")
private Long ts;
@NotNull(message = "不允许为空")
private Long entId;
public Long getTs() {
return ts;
}
public void setTs(Long ts) {
this.ts = ts;
}
public Long getEntId() {
return entId;
}
public void setEntId(Long entId) {
this.entId = entId;
}
}
← 第三方收银台对接方案 SDK下载 →