这是一个集成了API签名功能的spring boot组件,提供了application/x-www-form-urlencoded、application/json两种传参方式的签名验证功能,只需要在application.yml中做简单配置即可开箱即用。下面介绍其主要代码和使用方法。
文章目录
项目结构如下:

Github地址: https://github.com/xydang2019/sign-spring-boot-starter.git
1、主要代码
1.1、第三方依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.bzyd.sign</groupId>
<artifactId>sign-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
<name>sign-spring-boot-starter</name>
<properties>
<java.version>1.8</java.version>
<fastjson.version>1.2.67</fastjson.version>
<commons-codec.version>1.13</commons-codec.version>
<lombok.version>1.18.12</lombok.version>
<commons-lang3.version>3.9</commons-lang3.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>${commons-codec.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!--打包时忽略启动类-->
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>
1.2、配置属性类
import static com.bzyd.sign.common.constants.SignConstant.CONFIG_PREFIX;
@Data
@ConfigurationProperties(prefix = CONFIG_PREFIX)
public class SignProperties {
/**
* 是否启用
*/
private boolean enabled;
/**
* 默认Content-Type,
* 1、application/x-www-form-urlencoded
* 2、application/json
*/
private String defaultContentType = SignEnum.FORM.getTag();
/**
* 验证路径
*/
private String[] includePaths;
/**
* 排除路径
*/
private String[] excludePaths;
/**
* 签名密钥
*/
@NestedConfigurationProperty
private List<SignSecretConfig> secret;
}
配置前缀:
public class SignConstant {
public static final String CONFIG_PREFIX = "com.bzyd.sign";
}
签名密钥类:
@Data
@Accessors(chain = true)
public class SignSecretConfig {
/**
* 客户端id
*/
private String appId;
/**
* 签名密钥
*/
private String secretKey;
}
1.3、自动装配类
自动装配类中主要配置签名拦截器以及请求体读取过滤器(之所以配置此过滤器,是因为以application/json的传参方式时,InputStream只能读取一次,详见下方请求包装器类RequestWrapper详解)。
import static com.bzyd.sign.common.constants.SignConstant.CONFIG_PREFIX;
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass(SignInterceptor.class)
@EnableConfigurationProperties(SignProperties.class)
@ConditionalOnProperty(prefix = CONFIG_PREFIX,value = "enabled",havingValue = "true")
public class SignAutoConfigure implements WebMvcConfigurer {
@Autowired
private SignProperties signProperties;
@Autowired(required = false)
private SignHelper signHelper;
@Autowired(required = false)
private List<SignSecretConfigHolder> signSecretConfigHolders;
//配置签名拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
if (signHelper == null){
signHelper = new DefaultSignHelper();
}
if (signSecretConfigHolders == null){
signSecretConfigHolders = new ArrayList<>();
}
signSecretConfigHolders.add(new DefaultSignSecretConfigHolder(signProperties.getSecret()));
registry.addInterceptor(new SignInterceptor(signProperties.getDefaultContentType(),signHelper,signSecretConfigHolders))
.addPathPatterns(signProperties.getIncludePaths())
.excludePathPatterns(signProperties.getExcludePaths());
}
//配置请求体读取过滤器
@Bean
public FilterRegistrationBean getFilterRegistrationBean() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new RequestBodyReadFilter(signProperties.getExcludePaths()));
bean.setName("RequestBodyReadFilter");
String[] strArr = signProperties.getIncludePaths();
for (int i = 0; i < strArr.length; i++) {
strArr[i] = strArr[i].replace("**","*");
}
bean.addUrlPatterns(strArr);
return bean;
}
}
在META-INF/spring.factories中写入自动装配类:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.bzyd.sign.autoconfigure.SignAutoConfigure
1.4、签名帮助类
public interface SignHelper {
/**
* 获取签名
* @param body 参数
* @param signKey 密钥
* @return
*/
String getSign(JSONObject body, String signKey);
/**
* 验证签名
* @param params 参数
* @param signKey 密钥
* @return
*/
boolean checkSign(JSONObject params, String signKey);
}
此接口主要提供了获取签名以及签名两个方法,需要自定义签名规则的,可以实现此接口即可。另外本组件提供了一套默认的签名规则,详见默认签名帮助类:
public class DefaultSignHelper implements SignHelper {
private DateTimeFormatter df1;
private DateTimeFormatter df2;
public DefaultSignHelper() {
df1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
df2 = DateTimeFormatter.ofPattern("yyyyMMddHHmm");
}
@Override
public String getSign(JSONObject body, String signKey) {
//对参数进行ASCII码从小到大排序(字典序)
List<Map.Entry<String, Object>> akeys = new ArrayList<>(body.entrySet());
Collections.sort(akeys,new Comparator<Map.Entry<String, Object>>() {
@Override
public int compare(Map.Entry<String, Object> o1, Map.Entry<String, Object> o2) {
return o1.getKey().compareTo(o2.getKey());
}
});
StringBuffer sb = new StringBuffer();
sb.append(signKey + ":");
for(Map.Entry<String, Object> item : akeys){
String key = item.getKey();
Object val = item.getValue();
if(null != val && !"".equals(val.toString())
&& !"sign".equals(key)) {
sb.append(key + ":" + val.toString() + ":");
}
}
sb.append(signKey);
String sign = DigestUtils.md5Hex(sb.toString()).toUpperCase();
return sign;
}
@Override
public boolean checkSign(JSONObject params, String signKey) {
try {
if(params.get("sign") != null) {
String sign = params.getString("sign");
String time = params.getString("time");
String bodyStr = params.getString("body");
JSONObject body = JSON.parseObject(bodyStr);
String signTime = df2.format(df1.parse(time));
body.put("signTime", signTime);
String _sign = getSign(body,signKey);
if(_sign.equals(sign)){
return true;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
默认签名规则:
1、设接口所有请求内容或请求处理结果(body)的数据为集合M,向集合M添加字段signTime,其值为请求时间(格式:yyyyMMddhhmm,精准到分)。
2、将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),按照key:value的格式生成键值对(即key1:value1:key2:value2:key3:value3…)拼接成字符串stringA。
3、在stringA最前与最后都拼接上秘钥signKey(分配得到)得到stringB字符串(signKey:key1:value1:key2:value2…key9:value9:signKey),并对stringB进行MD5运算,再将得到的字符串所有字符转换为大写,得到最终sign值。
注意:
① 参数名ASCII码从小到大排序(字典序);
② 如果参数的值为空不参与签名;
③ 参数名区分大小写;
④ 传送的sign参数不参与签名。
1.5、签名密钥持有器类
public interface SignSecretConfigHolder {
/**
* 获取签名密钥配置信息
* @return
*/
List<SignSecretConfig> getSignSecretConfig();
}
该接口的作用是获取签名密钥,需要自定义密钥获取方式(如从数据库获取)的实现此接口即可,本组件默认提供从配置文件获取的方式:
在application.yml配置密钥信息如下
com:
bzyd:
sign:
enabled: true
default-content-type: application/json
include-paths: /test/**
exclude-paths:
secret:
- app-id: 1
secret-key: bzyd111
- app-id: 2
secret-key: bzyd222
默认签名密钥持有器类:
public class DefaultSignSecretConfigHolder implements SignSecretConfigHolder {
private List<SignSecretConfig> signSecretConfigList;
public DefaultSignSecretConfigHolder(List<SignSecretConfig> signSecretConfigList) {
this.signSecretConfigList = signSecretConfigList;
}
@Override
public List<SignSecretConfig> getSignSecretConfig() {
return this.signSecretConfigList;
}
}
若需从数据库中获取密钥配置,可实现SignSecretConfigHolder接口,例如:
@Component
public class CustomSignSecretConfigHolder implements SignSecretConfigHolder {
@Override
public List<SignSecretConfig> getSignSecretConfig() {
//自定义签名密钥持有器,可从缓存或者数据库中获取密钥
//以下是伪代码
return new ArrayList<SignSecretConfig>() {
{
add(new SignSecretConfig().setAppId("1").setSecretKey("bzyd111"));
add(new SignSecretConfig().setAppId("2").setSecretKey("bzyd222"));
add(new SignSecretConfig().setAppId("3").setSecretKey("bzyd333"));
}
};
}
}
1.6、签名拦截器类
public class SignInterceptor implements HandlerInterceptor {
//默认Content-Type
private String defaultContentType;
//签名帮助类
private SignHelper signHelper;
//签名密钥,key为appId,value为secretKey
private Map<String, String> signSecretMap;
public SignInterceptor(String defaultContentType, SignHelper signHelper, List<SignSecretConfigHolder> signSecretConfigHolders) {
this.defaultContentType = defaultContentType;
this.signHelper = signHelper;
this.signSecretMap = new HashMap<>();
if (signSecretConfigHolders != null && !signSecretConfigHolders.isEmpty()) {
signSecretConfigHolders.forEach(signSecretConfigHolder -> {
List<SignSecretConfig> signSecretConfigList = signSecretConfigHolder.getSignSecretConfig();
if (signSecretConfigList != null && !signSecretConfigList.isEmpty()) {
for (SignSecretConfig signSecretConfig : signSecretConfigList) {
this.signSecretMap.put(signSecretConfig.getAppId(), signSecretConfig.getSecretKey());
}
}
});
}
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
if (handler.getClass().isAssignableFrom(HandlerMethod.class)) {
AuthPassport authPassport = ((HandlerMethod) handler).getMethodAnnotation(AuthPassport.class);
JSONObject params = null;//请求参数
if (authPassport != null){
if (authPassport.type().equals(SignEnum.FORM)) {
params = RequestUtil.getFormReqParams(request);
}
if (authPassport.type().equals(SignEnum.JSON)) {
params = RequestUtil.getJsonReqParams(request);
}
}else {
if (SignEnum.FORM.getTag().equals(defaultContentType)) {
params = RequestUtil.getFormReqParams(request);
}
if (SignEnum.JSON.getTag().equals(defaultContentType)) {
params = RequestUtil.getJsonReqParams(request);
}
}
if (params == null){
throw new RuntimeException("Failed to get request parameters...");
}
String appId = params.getString("appId");
String secretKey = this.signSecretMap.get(appId);
if (signHelper.checkSign(params, secretKey)) {
return true;
} else {
throw new SignException();
}
}
return false;
}
}
1.7、签名枚举类
public enum SignEnum {
/**
* 以application/x-www-form-urlencoded的形式传输
*/
FORM("application/x-www-form-urlencoded"),
/**
* 以application/json的形式传输
*/
JSON("application/json");
private final String tag;
SignEnum(String tag) {
this.tag = tag;
}
public String getTag() {
return tag;
}
}
本组件提供两种传参方式的签名认证,即application/x-www-form-urlencoded表单形式和application/json形式,可通过default-content-type属性进行配置,当不配置时默认的形式是application/x-www-form-urlencoded。
另外,本组件也提供了授权注解类:
@Documented
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthPassport {
SignEnum type();
}
API上若使用了此注解,则优先按此注解中设置的content-type进行校验,当没有使用此注解时则按照配置文件中设置的默认content-type进行校验(具体代码详见上方签名拦截器类)。
@AuthPassport(type = SignEnum.FORM)
@RequestMapping(value = "/testPostForm",method = RequestMethod.POST)
public Object testPostForm(ApiParamsForm params){
UserVO userVO = params.getBody(UserVO.class);
System.out.println("测试form形式提交的post请求,userVO: " + userVO);
return ok();
}
1.8、请求包装器类
由于请求数据流只能被读取一次,在拦截器读取了request的数据,在Controller里面@RequestBody注解获取Json就会失败就读取不到数据。解决方法就是通过重写HttpServletRequestWrapper把request的流保存下来,然后通过过滤器把保存下来的request再填充进去,这样就可以多次读取request了,此方法只对application/json方式提交的请求有效。
public class RequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public RequestWrapper(HttpServletRequest request) throws IOException {
super(request);
String sessionStream = getBodyString(request);
body = sessionStream.getBytes(Charset.forName("UTF-8"));
}
/**
* 获取请求Body
*/
public String getBodyString(final ServletRequest request) {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = cloneInputStream(request.getInputStream());
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return sb.toString();
}
/**
* Description: 复制输入流
*/
public InputStream cloneInputStream(ServletInputStream inputStream) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
try {
while ((len = inputStream.read(buffer)) > -1) {
byteArrayOutputStream.write(buffer, 0, len);
}
byteArrayOutputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
InputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
return byteArrayInputStream;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
}
1.9、请求体过滤器类
public class RequestBodyReadFilter implements Filter {
/**
* 过滤路径列表
*/
private List<String> excludeList;
/**
* Ant匹配模式
*/
private AntPathMatcher pathMatcher;
public RequestBodyReadFilter(String[] excludePaths) {
this.excludeList = Arrays.asList(excludePaths);
this.pathMatcher = new AntPathMatcher();
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 读取流后,将流继续写出去
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
if (!isExcludeUri(httpServletRequest)) {
// 这里将原始request传入,读出流并存储
ServletRequest requestWrapper = new RequestWrapper(httpServletRequest);
// 这里将原始request替换为包装后的request,此后所有进入controller的request均为包装后的
filterChain.doFilter(requestWrapper, servletResponse);//
} else {
filterChain.doFilter(servletRequest, servletResponse);
}
}
/**
* 判断是否排除掉过滤
*
* @param httpServletRequest
* @return
*/
private boolean isExcludeUri(HttpServletRequest httpServletRequest) {
String contextPath = httpServletRequest.getContextPath();
String uri = httpServletRequest.getRequestURI();
String urlStr = uri.replace(contextPath, "");
for (String pattern : this.excludeList) {
if (pathMatcher.match(pattern, urlStr)) {
return true;
}
}
return false;
}
}
1.10、请求工具类
public class RequestUtil {
/**
* 获取请求参数(application/x-www-form-urlencoded方式)
*
* @param request
* @return
*/
public static JSONObject getFormReqParams(HttpServletRequest request) {
Map properties = request.getParameterMap();
JSONObject result = new JSONObject();
Iterator entries = properties.entrySet().iterator();
Entry entry;
String name = "";
String value = "";
while (entries.hasNext()) {
entry = (Entry) entries.next();
name = (String) entry.getKey();
Object valueObj = entry.getValue();
if (null == valueObj) {
value = "";
} else if (valueObj instanceof String[]) {
String[] values = (String[]) valueObj;
for (int i = 0; i < values.length; i++) {
value = values[i] + ",";
}
value = value.substring(0, value.length() - 1);
} else {
value = valueObj.toString();
}
result.put(name, value);
}
return result;
}
/**
* 获取请求参数(application/json方式)
*
* @param request
* @return
*/
public static JSONObject getJsonReqParams(HttpServletRequest request) {
JSONObject params = null;
try {
params = JSON.parseObject(request.getInputStream(), Charset.forName("UTF-8"), JSONObject.class);
} catch (IOException e) {
e.printStackTrace();
}
return params;
}
}
2、使用方法
2.1、引入组件
<dependency>
<groupId>com.bzyd.sign</groupId>
<artifactId>sign-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
2.2、配置属性
com:
bzyd:
sign:
enabled: true
default-content-type: application/json
include-paths: /test/**
exclude-paths:
# secret:
# - app-id: 1
# secret-key: bzyd111
# - app-id: 2
# secret-key: bzyd222
2.3、测试请求
@RestController
@RequestMapping("/test")
public class TestController extends BaseController {
@AuthPassport(type = SignEnum.FORM)
@RequestMapping(value = "/testPostForm",method = RequestMethod.POST)
public Object testPostForm(ApiParamsForm params){
UserVO userVO = params.getBody(UserVO.class);
System.out.println("测试form形式提交的post请求,userVO: " + userVO);
return ok();
}
@AuthPassport(type = SignEnum.JSON)
@RequestMapping(value = "/testPostJson",method = RequestMethod.POST)
public Object testPostJson(@RequestBody ApiParamsJson<UserVO> params){
UserVO userVO = params.getBody();
System.out.println("测试json形式提交的post请求,userVO: " + userVO);
return ok();
}
}
UserVO类:
@Data
@Accessors(chain = true)
public class UserVO {
private Integer id;
private String name;
}
ApiParamsForm、ApiParamsJson是组件中提供的两种传参方式的参数接收对象:
@Data
public class ApiParamsForm implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 签名
*/
private String sign;
/**
* 时间字符串
*/
private String time;
/**
* 请求参数
*/
private String body;
/**
* 客户端id
*/
private String appId;
public <T>T getBody(Class<T> t) {
return JSON.parseObject(body,t);
}
}
@Data
public class ApiParamsJson<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 签名
*/
private String sign;
/**
* 时间字符串
*/
private String time;
/**
* 请求参数
*/
private T body;
/**
* 客户端id
*/
private String appId;
}
2.4、调用API
客户端类:
@Slf4j
public class TestSignSpringBootStarterDefaultClient {
private String baseUrl;
private String appId;
private String secretKey;
private TestSignSpringBootStarterDefaultClient(String baseUrl, String appId, String secretKey) {
this.baseUrl = baseUrl;
this.appId = appId;
this.secretKey = secretKey;
}
private static volatile TestSignSpringBootStarterDefaultClient client = null;
public static TestSignSpringBootStarterDefaultClient getInstance(String baseUrl, String appId, String secretKey){
if (client == null){
synchronized (TestSignSpringBootStarterDefaultClient.class){
if (client == null){
client = new TestSignSpringBootStarterDefaultClient(baseUrl,appId,secretKey);
}
}
}
return client;
}
public TestSignSpringBootStarterResponse doPostJson(TestSignSpringBootStarterRequest request){
try {
DateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
DateFormat sdf2 = new SimpleDateFormat("yyyyMMddHHmm");
JSONObject body = request.getBody();
if (body != null) {
Date now = new Date();
body.put("signTime", sdf2.format(now));
String sign = SignUtil.sign(body, secretKey);
body.remove("signTime");
JSONObject params = new JSONObject();
params.put("body", body);
params.put("time", sdf1.format(now));
params.put("sign", sign);
params.put("appId", appId);
log.info("调用接口【" + request.getMethod() + "】请求参数:" + params.toString());
String result = HttpConnectionPoolUtil.post(baseUrl + request.getMethod(), JSON.toJSONString(params));
if (StringUtils.isNotEmpty(result)) {
log.info("调用接口【" + request.getMethod() + "】成功,返回结果:" + result);
return JSONObject.parseObject(result, TestSignSpringBootStarterResponse.class);
} else {
log.info("调用接口【" + request.getMethod() + "】失败,result is null");
}
} else {
log.info("调用接口【" + request.getMethod() + "】失败,body is null");
}
} catch (Exception e) {
log.error("Exception", e);
}
return null;
}
public TestSignSpringBootStarterResponse doPostForm(TestSignSpringBootStarterRequest request){
try {
DateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
DateFormat sdf2 = new SimpleDateFormat("yyyyMMddHHmm");
JSONObject body = request.getBody();
if (body != null) {
Date now = new Date();
body.put("signTime", sdf2.format(now));
String sign = SignUtil.sign(body, secretKey);
body.remove("signTime");
JSONObject params = new JSONObject();
params.put("body", JSON.toJSONString(body));
params.put("time", sdf1.format(now));
params.put("sign", sign);
params.put("appId", appId);
log.info("调用接口【" + request.getMethod() + "】请求参数:" + params.toString());
String result = HttpConnectionPoolUtil.post(baseUrl + request.getMethod(), params);
if (StringUtils.isNotEmpty(result)) {
log.info("调用接口【" + request.getMethod() + "】成功,返回结果:" + result);
return JSONObject.parseObject(result, TestSignSpringBootStarterResponse.class);
} else {
log.info("调用接口【" + request.getMethod() + "】失败,result is null");
}
} else {
log.info("调用接口【" + request.getMethod() + "】失败,body is null");
}
} catch (Exception e) {
log.error("Exception", e);
}
return null;
}
}
测试方法:
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestSignSpringBootStarter {
@Test
public void test1() throws Exception{
String baseUrl = "http://localhost:8080";
String appId = "1";
String secretKey = "bzyd111";
TestSignSpringBootStarterDefaultClient client = TestSignSpringBootStarterDefaultClient.getInstance(baseUrl,appId,secretKey);
JSONObject body = new JSONObject();
body.put("id", "100");
body.put("name", "Tom");
TestSignSpringBootStarterRequest request = new TestSignSpringBootStarterRequest("/test/testPostForm", body);
TestSignSpringBootStarterResponse response = client.doPostForm(request);
System.out.println(response);
}
@Test
public void test2() throws Exception{
String baseUrl = "http://localhost:8080";
String appId = "2";
String secretKey = "bzyd222";
TestSignSpringBootStarterDefaultClient client = TestSignSpringBootStarterDefaultClient.getInstance(baseUrl,appId,secretKey);
JSONObject body = new JSONObject();
body.put("id", "100");
body.put("name", "Tom");
TestSignSpringBootStarterRequest request = new TestSignSpringBootStarterRequest("/test/testPostJson", body);
TestSignSpringBootStarterResponse response = client.doPostJson(request);
System.out.println(response);
}
}