快速开始
引入依赖
<dependency>
<groupId>com.github.lianjiatech</groupId>
<artifactId>retrofit-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
如果启动失败,大概率是依赖冲突,烦请引入或者排除相关依赖。
定义HTTP接口
接口必须使用@RetrofitClient
注解标记!HTTP相关注解可参考官方文档:retrofit官方文档。
@RetrofitClient(baseUrl = "${test.baseUrl}")
public interface UserService {
/**
* 根据id查询用户姓名
*/
@POST("getName")
String getName(@Query("id") Long id);
}
自定义拦截注解
有的时候,我们需要在"拦截注解"动态传入一些参数,然后在拦截的时候使用这些参数。 这时候,我们可以使用"自定义拦截注解",步骤如下:
- 自定义注解。必须使用
@InterceptMark
标记,并且注解中必须包括include、exclude、handler
字段。 - 继承
BasePathMatchInterceptor
编写拦截处理器 - 接口上使用自定义注解
例如,我们需要"在请求头里面动态加入accessKeyId
、accessKeySecret
签名信息才能再发起HTTP请求",这时候可以自定义@Sign
注解来实现。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@InterceptMark
public @interface Sign {
String accessKeyId();
String accessKeySecret();
String[] include() default {"/**"};
String[] exclude() default {};
Class<? extends BasePathMatchInterceptor> handler() default SignInterceptor.class;
}
在@Sign
注解中指定了使用的拦截器是SignInterceptor
。
实现SignInterceptor
@Component
@Setter
public class SignInterceptor extends BasePathMatchInterceptor {
private String accessKeyId;
private String accessKeySecret;
@Override
public Response doIntercept(Chain chain) throws IOException {
Request request = chain.request();
Request newReq = request.newBuilder()
.addHeader("accessKeyId", accessKeyId)
.addHeader("accessKeySecret", accessKeySecret)
.build();
Response response = chain.proceed(newReq);
return response.newBuilder().addHeader("accessKeyId", accessKeyId)
.addHeader("accessKeySecret", accessKeySecret).build();
}
}
注意:
accessKeyId
和accessKeySecret
字段必须提供setter
方法。
拦截器的accessKeyId
和accessKeySecret
字段值会依据@Sign
注解的accessKeyId()
和accessKeySecret()
值自动注入,如果@Sign
指定的是占位符形式的字符串,则会取配置属性值进行注入。
接口上使用@Sign
@RetrofitClient(baseUrl = "${test.baseUrl}")
@Sign(accessKeyId = "${test.accessKeyId}", accessKeySecret = "${test.accessKeySecret}", include = "/api/user/getAll")
public interface InterceptorUserService {
/**
* 查询所有用户信息
*/
@GET("getAll")
Response<List<User>> getAll();
}
Request进行加签加密
获取Request的请求参数
private String getRequestParam(Request request) throws IOException{
String requestParam = "";
switch (request.method().toLowerCase()) {
case "post":
requestParam = getRequestParams4Post(request);//请求参数
break;
case "get":
requestParam = getRequestParams4Get(request.url().toString());
break;
}
return requestParam;
}
private String getRequestParams4Get(String url) {
HttpUrl uri = HttpUrl.parse(url);
JSONObject requestJson = new JSONObject();
for (String paramKey : uri.queryParameterNames()) {
List<String> paramValue = uri.queryParameterValues(paramKey);
requestJson.put(paramKey, paramValue);
}
return requestJson.toString();
}
/**
* 获取POST请求参数
*/
private String getRequestParams4Post(Request request) throws IOException {
if (request.body() != null) {
return parseRequestParams4Post(request);
}
// 说明:multipart/form-data 需要特殊处理
return "";
}
/**
* 解析请求服务器的请求参数
*/
private static String parseRequestParams4Post(Request request) throws UnsupportedEncodingException {
try {
RequestBody body = request.body();
if (body == null) {
return "";
}
Buffer requestBuffer = new Buffer();
body.writeTo(requestBuffer);
Charset charset = StandardCharsets.UTF_8;
MediaType contentType = body.contentType();
if (contentType != null) {
charset = contentType.charset(charset);
}
String text = requestBuffer.readString(charset);
if (contentType != null && !"json".equals(contentType.subtype())) {
text = URLDecoder.decode(text, convertCharset(charset));
}
return text;
} catch (IOException e) {
e.printStackTrace();
return "{\"error\": \"" + e.getMessage() + "\"}";
}
}
private static String convertCharset(Charset charset) {
String s = charset.toString();
int i = s.indexOf("[");
if (i == -1){
return s;
}
return s.substring(i + 1, s.length() - 1);
}
目前还不支持multipart/form-data类型的参数
对参数进行加签加密
private Request encrypt(Request request) throws IOException {
String aesKey = AESUtil.generateAESKey();
String key = RSAUtil.encryptByPublicKey(aesKey, rsaPublicKey);
String requestParam = getRequestParam(request);
String data = AESUtil.encrypt(requestParam, aesKey);
String timestamp = String.valueOf(System.currentTimeMillis());
String nonce = UUID.randomUUID().toString().replaceAll("-", "");
String text = SignUtil.getContent(requestParam, nonce, timestamp);
String sign = RSAUtil.signByPrivateKey(text, rsaPrivateKey);
ApiSecurityParam apiSecurityParam = new ApiSecurityParam();
apiSecurityParam.setAppId(accessKeyId);
apiSecurityParam.setData(data);
apiSecurityParam.setKey(key);
apiSecurityParam.setNonce(nonce);
apiSecurityParam.setSign(sign);
apiSecurityParam.setTimestamp(timestamp);
apiSecurityParam.toString().getBytes(StandardCharsets.UTF_8);
RequestBody requestBody = RequestBody.create(request.body().contentType(), JSON.toJSONString(apiSecurityParam));
Request newReq = request.newBuilder()
.method(request.method(),requestBody)
.build();
return newReq;
}
这里具体加签加密方式大家可以自行替换。