概念
sm2
SM2 是一种非对称加密算法。基于椭圆曲线密码(ECC)的公钥密码算法标准,可用于数字签名、密钥交换、公钥加密等,可替换RSA等算法。
sm3
SM3 是一种密码杂凑算法。通过在签名原始串后加上基金公司通信密钥的内容,进行SM3加密运算,形成的加密字符串即为签名结果。
sm4
SM4是一种对称加密算法。密钥长度和分组长度均为128bits,可替换DES/AES等算法。
背景
为了保证加解密的速度和数据安全性,通常对原始数据使用SM4加密,对密钥等数据量较小的数据使用SM2加密。然后对加密的数据做SM3签名处理。
HttpEncryptClient(发送加密请求,解密响应客户端)
package com.boot.util;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import cn.hutool.crypto.digest.SM3;
import cn.hutool.crypto.symmetric.SM4;
import com.boot.config.IcbccsApiProperties;
import com.boot.enums.BusinessTypeEnum;
import com.boot.enums.SignTypeEnum;
import com.boot.enums.UriTypeEnum;
import com.boot.exception.JacksonException;
import com.boot.model.domain.*;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.security.KeyPair;
import java.util.Map;
import java.util.TreeMap;
/**
* @ClassName : HttpEncryptClient
* @Description : HttpEncryptClient
* @Author : ChenRui
* @Date: 2022-11-28 23:31
*/
@Slf4j
public class HttpEncryptClient {
/** 基本参数 */
private IcbccsApiProperties icbccsApiProperties;
/** http客户端 */
private RestTemplate restTemplate;
/** sm2加密工具 */
private SM2 encryptSm2;
/** sm2解密工具 */
private SM2 decryptSm2;
/** sm3加解密工具 */
private SM3 sm3;
/** sm4加解密工具 */
private SM4 sm4;
/** sm4随机秘钥密文 */
private String sm4SecretKeyCiphertext;
public HttpEncryptClient(IcbccsApiProperties icbccsApiProperties, RestTemplate restTemplate) {
Preconditions.checkNotNull(icbccsApiProperties);
this.icbccsApiProperties = icbccsApiProperties;
this.restTemplate = restTemplate;
this.sm3 = SmUtil.sm3();
this.sm4 = SmUtil.sm4();
if(icbccsApiProperties.getSm2KeyPair() != null){
IcbccsApiProperties.Sm2KeyPair sm2KeyPair = icbccsApiProperties.getSm2KeyPair();
if(StringUtils.isNotBlank(sm2KeyPair.getFundPublicKey())){
this.encryptSm2 = SmUtil.sm2(null, sm2KeyPair.getFundPublicKey());
}
if(StringUtils.isNotBlank(sm2KeyPair.getTrustPrivateKey())){
this.decryptSm2 = SmUtil.sm2(sm2KeyPair.getTrustPrivateKey(), null);
}
}
if(encryptSm2 != null){
// 获取字符串格式的sm4随机秘钥
String sm4SecretKeyStr = HexUtil.encodeHexStr(sm4.getSecretKey().getEncoded());
// 使用sm2加密上一步得到的sm4随机秘钥
this.sm4SecretKeyCiphertext = encryptSm2.encryptHex(sm4SecretKeyStr, KeyType.PublicKey);
}
}
/**
* @Title: encryptExchange
* @Param: [t, businessTypeEnum, clazz]
* @description: 加密请求
* @author: ChenRui
* @date: 2022/11/29 0:24
* @return: E
* @throws:
*/
public <T, E extends IcbccsBaseResponse> E encryptExchange(T t, BusinessTypeEnum businessTypeEnum, Class<E> clazz) {
// 1.0.判断是否上传资源
boolean isUpload = businessTypeEnum.getUriTypeEnum() == UriTypeEnum.UPLOAD;
// 1.1.获取默认的签名类型
SignTypeEnum signTypeEnum = SignTypeEnum.getEnumByKey(icbccsApiProperties.getSignType());
if( signTypeEnum == null ){
throw new RuntimeException("不支持该类型的签名、加密算法");
}
// 1.2.包装业务参数、生成签名
Object reqData;
String sign = "";
if(isUpload){
reqData = t;
} else {
switch (signTypeEnum){
case SM3:
// 1.2.1.仅使用MS3进行签名
reqData = t;
sign = genSignToken(t);
break;
case SM4:
// 1.2.2.仅使用MS4进行加密
reqData = generatePacketRequest(t);
break;
case SM3_SM4:
// 1.2.3.使用MS4进行加密,将结果进行SM3签名
reqData = generatePacketRequest(t);
sign = genSignToken(reqData);
break;
default:
throw new RuntimeException("不支持该类型的签名、加密算法");
}
}
log.info("\r\n请求-业务明文: \r\n{}", t);
log.info("\r\n请求-包裹: \r\n{}", reqData);
log.info("\r\n请求-签名: \r\n{}", sign);
// 1.4.构造报文
IcbccsApiRequest icbccsApiRequest = IcbccsApiRequest.builder()
.serviceVersion(icbccsApiProperties.getServiceVersion())
.signType(signTypeEnum.getCode())
.sign(sign)
.instId(icbccsApiProperties.getInstId())
.businessType(businessTypeEnum.getCode())
.reqData(reqData)
.build();
// 1.5.提交请求
String ciphertextResponse;
if(isUpload){
// 1.5.1.文件流格式提交请求
ciphertextResponse = postFileForEntity(icbccsApiRequest);
} else {
// 1.5.2.json格式发送请求
ciphertextResponse = postJosnForEntity(icbccsApiRequest);
}
log.info("\r\n响应-原始数据: \r\n{}", ciphertextResponse);
// 1.6.解析响应数据
E e;
switch (signTypeEnum){
case SM3:
// 1.6.1.仅使用MS3时,直接反序列化
try {
JavaType javaType = JacksonsUtil.getMapper().getTypeFactory().constructParametricType(IcbccsApiResponse.class, clazz);
IcbccsApiResponse<E> icbccsApiResponse = JacksonsUtil.getMapper().readValue(ciphertextResponse, javaType);
log.info("\r\n响应-反序列化数据:\r\n{}", icbccsApiResponse);
e = icbccsApiResponse.getRspData();
} catch (Exception exception) {
throw new JacksonException(exception);
}
break;
case SM4: case SM3_SM4:
// 1.6.2.解密SM4
e = extractPacketResponse(ciphertextResponse, clazz);
break;
default:
throw new RuntimeException("不支持该类型的签名、加密算法");
}
log.info("\r\n响应-解析后业务数据: \r\n{}", e);
return e;
}
/**
* @Title: postJosnForEntity
* @Param: [icbccsApiRequest]
* @description: json请求
* @author: ChenRui
* @date: 2022/11/29 2:25
* @return: java.lang.String
* @throws:
*/
private String postJosnForEntity(IcbccsApiRequest icbccsApiRequest) {
// 1.1.构造请求头
HttpHeaders headers = new HttpHeaders();
// 1.1.1.设置请求编码
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
// 1.1.2.设置预期响应编码
headers.add("Accept", MediaType.APPLICATION_JSON.toString());
// 1.2.反序列化对象
String jsonStr = JacksonsUtil.writeValueAsString(icbccsApiRequest);
// 1.3.包装业务参数
MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
form.add("jsonRequestData", jsonStr);
log.info("\r\n请求-最终报文: \r\n{}", jsonStr);
// 1.4.构造请求实体
HttpEntity<MultiValueMap<String, Object>> formEntity = new HttpEntity(form, headers);
// 1.5.发送请求
String url = icbccsApiProperties.getUri().getGeneralUrl();
log.info("\r\n请求-URL: \r\n{}", url);
ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, formEntity, String.class);
return responseEntity.getBody();
}
/**
* @Title: postFileForEntity
* @Param: [icbccsApiRequest]
* @description: 文件上传
* @author: ChenRui
* @date: 2022/11/30 17:28
* @return: java.lang.String
* @throws:
*/
private String postFileForEntity(IcbccsApiRequest icbccsApiRequest) {
log.info("\r\n请求-发送转换前的报文: \r\n{}", icbccsApiRequest);
// 1.1.构造请求头
HttpHeaders headers = new HttpHeaders();
// 1.1.1.设置请求编码
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
// 1.1.2.设置预期响应编码
headers.add("Accept", MediaType.APPLICATION_JSON.toString());
// 1.3.包装业务参数
Map<String, Object> map = Maps.newHashMap();
// 1.3.1.创建拷贝规则
CopyOptions copyOptions = new CopyOptions();
copyOptions.setIgnoreNullValue(false);
copyOptions.setIgnoreCase(false);
copyOptions.setPropertiesFilter((Field field, Object y) -> {
if(field.getName().equals("reqData")){
return false;
}
return true;
});
copyOptions.setConverter((Type type, Object value) -> {
// 当为文件类型时,转成成系统文件资源对象
if( (value instanceof File || value instanceof MultipartFile) && value != null){
try {
File tempFile = null;
if(value instanceof File){
tempFile = (File) value;
} else if(value instanceof MultipartFile){
tempFile = FileUtil.multipartFileToFile((MultipartFile) value);
}
FileSystemResource resource = new FileSystemResource(tempFile);
tempFile.deleteOnExit();
return resource;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return value;
});
// 1.3.2.拷贝业务参数
BeanUtil.beanToMap(icbccsApiRequest.getReqData(), map, copyOptions);
// 1.3.3.拷贝公共参数
BeanUtil.beanToMap(icbccsApiRequest, map, copyOptions);
// 1.3.4.map转换MultiValueMap
LinkedMultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
map.entrySet().forEach(e -> form.add(e.getKey(), e.getValue()));
log.info("\r\n请求-最终报文: \r\n{}", form);
// 1.4.构造请求实体
HttpEntity<MultiValueMap<String, Object>> formEntity = new HttpEntity(form, headers);
// 1.5.发送请求
String url = icbccsApiProperties.getUri().getUploadUrl();
ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, formEntity, String.class);
return responseEntity.getBody();
}
/**
* @Title: sortMapByKey
* @Param: [t]
* @description: 按照ascii码升序排序
* @author: ChenRui
* @date: 2022/11/29 10:17
* @return: java.util.Map<java.lang.String,java.lang.Object>
* @throws:
*/
private <T> Map<String, Object> sortMapByKey(T t) {
if(t == null){
return null;
}
Map<String, Object> map = BeanUtil.beanToMap(t, new TreeMap<>(), false, false);
return map;
}
/**
* @Title: genSignToken
* @Param: [o]
* @description: 生成签名
* @author: ChenRui
* @date: 2022/11/29 1:18
* @return: java.lang.String
* @throws:
*/
public String genSignToken(Object o) {
String signTarget = "";
if(ObjectUtils.isNotEmpty(o)){
// 1.1.按照ascii码升序排序
Map<String, Object> map = sortMapByKey(o);
// 1.2.按照键值对的格式构造签名参数
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, Object> item : map.entrySet()) {
if (StringUtils.isNotBlank(item.getKey()) && ObjectUtils.isNotEmpty(item.getValue())) {
String key = item.getKey();
String val = String.valueOf(item.getValue());
sb.append(key).append("=").append(val).append("&");
}
}
// 1.3.补充基金公司密钥
sb.append("key").append("=").append(icbccsApiProperties.getSm3Key());
// 1.4.签名原始值
String signSource = sb.toString();
// 1.5.生成签名
signTarget = sm3.digestHex(signSource).toUpperCase();
}
return signTarget;
}
/**
* @Title: generatePacketRequest
* @Param: [o]
* @description: 构造加密包
* @author: ChenRui
* @date: 2022/11/30 13:57
* @return: com.boot.model.domain.IcbccsCiphertextPacketRequest
* @throws:
*/
private IcbccsCiphertextPacketRequest generatePacketRequest(Object o){
IcbccsCiphertextPacketRequest icbccsCiphertextPacketRequest = new IcbccsCiphertextPacketRequest();
if(ObjectUtils.isNotEmpty(o)){
// 1.1.序列化报文
String plaintextStr = JacksonsUtil.writeValueAsString(o);
log.info("\r\n请求加密-明文:\r\n{}", plaintextStr);
// 1.2.sm4加密
String ciphertext = sm4.encryptHex(plaintextStr);
log.info("\r\n请求加密-密文:\r\n{}", ciphertext);
// 1.3.构造对象
icbccsCiphertextPacketRequest.setKey(sm4SecretKeyCiphertext);
icbccsCiphertextPacketRequest.setEncryptData(ciphertext);
}
return icbccsCiphertextPacketRequest;
}
/**
* @Title: extractPacketResponse
* @Param: [icbccsApiResponseStr, clazz]
* @description: 提取响应业务数据
* @author: ChenRui
* @date: 2022/11/30 15:08
* @return: E
* @throws:
*/
private <E extends IcbccsBaseResponse> E extractPacketResponse(String icbccsApiResponseStr, Class<E> clazz){
if(StringUtils.isNotBlank(icbccsApiResponseStr)){
// 1.1.反序列化
IcbccsApiResponse<IcbccsCiphertextPacketResponse> icbccsApiResponse = JacksonsUtil.readValue(icbccsApiResponseStr, new TypeReference<IcbccsApiResponse<IcbccsCiphertextPacketResponse>>() {});
log.info("\r\n响应-反序列化数据:\r\n{}", icbccsApiResponse);
// 1.2.提取响应包裹
IcbccsCiphertextPacketResponse icbccsCiphertextPacketResponse = icbccsApiResponse.getRspData();
if(icbccsCiphertextPacketResponse != null){
// 1.3.提取密文秘钥、密文
String sm2CiphertextKey = icbccsCiphertextPacketResponse.getKey();
String encryptData = icbccsCiphertextPacketResponse.getEncryptData();
// 1.4.解密sm2密文,得到sm4秘钥
String sm4Key = decryptSm2.decryptStr(sm2CiphertextKey, KeyType.PrivateKey);
// 1.5.解密sm4密文
String icbccsBaseResponseStr = SmUtil.sm4(HexUtil.decodeHex(sm4Key)).decryptStr(encryptData);
// 1.6.反序列化
return JacksonsUtil.readValue(icbccsBaseResponseStr, clazz);
}
}
return null;
}
/**
* @Title: generateSm2KeyPair
* @Param: []
* @description: 生成SM2公私钥
* @author: ChenRui
* @date: 2022/11/30 16:17
* @return: void
* @throws:
*/
private static void generateSm2KeyPair() {
KeyPair pair = SecureUtil.generateKeyPair("SM2");
String privateKey = Base64.encode(pair.getPrivate().getEncoded());
String publicKey = Base64.encode(pair.getPublic().getEncoded());
log.info("\r\nSM2私钥:\r\n" + privateKey);
log.info("\r\nSM2公钥:\r\n" + publicKey);
}
public static void main(String[] args) {
generateSm2KeyPair();
}
}
加密参数类
package com.boot.config;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* @ClassName : DhccPlatformProperties
* @Description : DhccPlatformProperties
* @Author : ChenRui
* @Date: 2020-05-14 14:02
*/
@Data
@Configuration
@Slf4j
@ConfigurationProperties(prefix = "ms.icbccs-api")
public class IcbccsApiProperties {
@Schema(description = "版本号")
private String serviceVersion;
@Schema(description = "签名/加密算法类型")
private String signType;
@Schema(description = "机构标识")
private String instId;
@Schema(description = "sm3加解密key-基金公司密钥-签名")
private String sm3Key;
@Schema(description = "请求地址")
private Uri uri = new Uri();
@Schema(description = "sm2密钥对")
private Sm2KeyPair sm2KeyPair = new Sm2KeyPair();
@Data
@Schema(description = "地址资源")
public static class Uri {
@Schema(description = "基础地址")
private String baseUri;
@Schema(description = "通用地址")
private String generalUrl;
@Schema(description = "文件上传地址")
private String uploadUrl;
}
@Data
@Schema(description = "sm2密钥集合")
public static class Sm2KeyPair {
@Schema(description = "信托公司sm2私钥")
private String trustPrivateKey;
@Schema(description = "基金公司sm2公钥")
private String fundPublicKey;
}
}
sm工具类,用来生成对应的sm对象
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package cn.hutool.crypto;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.crypto.asymmetric.SM2;
import cn.hutool.crypto.digest.HMac;
import cn.hutool.crypto.digest.HmacAlgorithm;
import cn.hutool.crypto.digest.SM3;
import cn.hutool.crypto.digest.mac.BCHMacEngine;
import cn.hutool.crypto.digest.mac.MacEngine;
import cn.hutool.crypto.symmetric.SM4;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.PrivateKey;
import java.security.PublicKey;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.signers.StandardDSAEncoding;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.encoders.Hex;
public class SmUtil {
private static final int RS_LEN = 32;
public static final String SM2_CURVE_NAME = "sm2p256v1";
public static final ECDomainParameters SM2_DOMAIN_PARAMS = BCUtil.toDomainParams(GMNamedCurves.getByName("sm2p256v1"));
public static final ASN1ObjectIdentifier ID_SM2_PUBLIC_KEY_PARAM = new ASN1ObjectIdentifier("1.2.156.10197.1.301");
public SmUtil() {
}
public static SM2 sm2() {
return new SM2();
}
public static SM2 sm2(String privateKeyStr, String publicKeyStr) {
return new SM2(privateKeyStr, publicKeyStr);
}
public static SM2 sm2(byte[] privateKey, byte[] publicKey) {
return new SM2(privateKey, publicKey);
}
public static SM2 sm2(PrivateKey privateKey, PublicKey publicKey) {
return new SM2(privateKey, publicKey);
}
public static SM2 sm2(ECPrivateKeyParameters privateKeyParams, ECPublicKeyParameters publicKeyParams) {
return new SM2(privateKeyParams, publicKeyParams);
}
public static SM3 sm3() {
return new SM3();
}
public static SM3 sm3WithSalt(byte[] salt) {
return new SM3(salt);
}
public static String sm3(String data) {
return sm3().digestHex(data);
}
public static String sm3(InputStream data) {
return sm3().digestHex(data);
}
public static String sm3(File dataFile) {
return sm3().digestHex(dataFile);
}
public static SM4 sm4() {
return new SM4();
}
public static SM4 sm4(byte[] key) {
return new SM4(key);
}
public static byte[] changeC1C2C3ToC1C3C2(byte[] c1c2c3, ECDomainParameters ecDomainParameters) {
int c1Len = (ecDomainParameters.getCurve().getFieldSize() + 7) / 8 * 2 + 1;
int c3Len = true;
byte[] result = new byte[c1c2c3.length];
System.arraycopy(c1c2c3, 0, result, 0, c1Len);
System.arraycopy(c1c2c3, c1c2c3.length - 32, result, c1Len, 32);
System.arraycopy(c1c2c3, c1Len, result, c1Len + 32, c1c2c3.length - c1Len - 32);
return result;
}
public static byte[] changeC1C3C2ToC1C2C3(byte[] c1c3c2, ECDomainParameters ecDomainParameters) {
int c1Len = (ecDomainParameters.getCurve().getFieldSize() + 7) / 8 * 2 + 1;
int c3Len = true;
byte[] result = new byte[c1c3c2.length];
System.arraycopy(c1c3c2, 0, result, 0, c1Len);
System.arraycopy(c1c3c2, c1Len + 32, result, c1Len, c1c3c2.length - c1Len - 32);
System.arraycopy(c1c3c2, c1Len, result, c1c3c2.length - 32, 32);
return result;
}
public static byte[] rsAsn1ToPlain(byte[] rsDer) {
BigInteger[] decode;
try {
decode = StandardDSAEncoding.INSTANCE.decode(SM2_DOMAIN_PARAMS.getN(), rsDer);
} catch (IOException var4) {
throw new IORuntimeException(var4);
}
byte[] r = bigIntToFixedLengthBytes(decode[0]);
byte[] s = bigIntToFixedLengthBytes(decode[1]);
return ArrayUtil.addAll(new byte[][]{r, s});
}
public static byte[] rsPlainToAsn1(byte[] sign) {
if (sign.length != 64) {
throw new CryptoException("err rs. ");
} else {
BigInteger r = new BigInteger(1, Arrays.copyOfRange(sign, 0, 32));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(sign, 32, 64));
try {
return StandardDSAEncoding.INSTANCE.encode(SM2_DOMAIN_PARAMS.getN(), r, s);
} catch (IOException var4) {
throw new IORuntimeException(var4);
}
}
}
public static MacEngine createHmacSm3Engine(byte[] key) {
return new BCHMacEngine(new SM3Digest(), key);
}
public static HMac hmacSm3(byte[] key) {
return new HMac(HmacAlgorithm.HmacSM3, key);
}
private static byte[] bigIntToFixedLengthBytes(BigInteger rOrS) {
byte[] rs = rOrS.toByteArray();
if (rs.length == 32) {
return rs;
} else if (rs.length == 33 && rs[0] == 0) {
return Arrays.copyOfRange(rs, 1, 33);
} else if (rs.length < 32) {
byte[] result = new byte[32];
Arrays.fill(result, (byte)0);
System.arraycopy(rs, 0, result, 32 - rs.length, rs.length);
return result;
} else {
throw new CryptoException("Error rs: {}", new Object[]{Hex.toHexString(rs)});
}
}
}
报文类
package com.boot.model.domain;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
/**
* @ClassName : IcbccsPublicParameters
* @Description : IcbccsPublicParameters
* @Author : ChenRui
* @Date: 2022-11-28 23:53
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Builder
@Schema(description = "工银瑞信对外请求参数")
public class IcbccsApiRequest<T> implements Serializable {
@Schema(description = "版本号")
private String serviceVersion;
@Schema(description = "机构标识")
private String instId;
@Schema(description = "签名类型")
private String signType;
@Schema(description = "签名结果")
private String sign;
@Schema(description = "交易类型")
private String businessType;
@Schema(description = "业务参数")
private T reqData;
}