在Feign的请求中添加统一的请求头或请求体

使用Feign配置自定义请求头参数

最近有个需求需要对接第三方系统,在调用对方接口时需要在请求头中传入token信息。我想能不能使用Feign来调用第三方的接口,用Feign调用十分简洁,那么在Feign调用的时候如何把token信息放入请求头中呢?

创建Feign对象

在使用Feign调用内部服务时@FeignClient注解的value参数对应相应的服务名即可,在调用第三方接口时只需指定url参数为三方接口的链接即可。值得注意的是url参数也可以直接从yml中获取。

@FeignClient(name = "你的feign名字", url = "${你的yml中配置的三方url}", configuration = Remote56ApiFeignConfig.class)
public interface Remote56ApiFeignClient {

	// 获取认证token
	@GetMapping(value = "/api/oauth/v1/getToken", headers = {"No-Need-To-Token=true"})
	WlApiToken get56ApiTokenByAppId(@RequestParam("appId") String appId, @RequestParam("secret") String secret);
}

创建连接器

在@FeignClient的参数中有个configuration字段可以传入自定义的配置,那么创建你的拦截器配置实现feign包下的RequestInterceptor连接器,重写apply方法来添加你的请求头。

@Slf4j
@Configuration
public class Remote56ApiFeignConfig implements RequestInterceptor {

	// 从yml中获取密钥
	@Value("${appid}")
	private String appId;

	// 从yml中获取密钥
	@Value("${secret}")
	private String secret;

	@Resource
	@Lazy // 这里需要延迟加载
	private Remote56ApiFeignClient remote56ApiFeignClient;

	@Resource
	private RedisCacheUtil redisCacheUtil;

	/**
	 * 不需要token请求头标识
	 */
	public static final String NO_NEED_TO_TOKEN = "No-Need-To-Token";

	@Override
	public void apply(RequestTemplate requestTemplate) {
		//获取接口是否不需要加载token  注意在调用获取第三方系统token的接口不需要传token,否则会递归调用进入死循环!!!
		boolean noNeedToToken = requestTemplate.headers().containsKey(NO_NEED_TO_TOKEN);
		if (!noNeedToToken) {
			// 将token存入请求头中
			String token = this.getToken(appId, secret);
			HashMap<String, Collection<String>> map = new HashMap<>();
			map.put("token", Collections.singleton(token));
			map.put("appId", Collections.singleton(appId));
			requestTemplate.headers(map);
			// 打印请求信息
			String url = requestTemplate.url();
			Map<String, Collection<String>> headers = requestTemplate.headers();
			byte[] body = requestTemplate.body();
			String method = requestTemplate.method();
			log.info("Remote56ApiFeignClient 请求url:{}, headers:{}, body:{}, method:{}", url, JSON.toJSONString(headers), JSON.toJSONString(body), method);
		}
	}

	private String getToken(String appId, String secret) {
		log.info("开始获三方系统token,appId:{}, secret:{}", appId, secret);
		Object value = redisCacheUtil.get(ToolsCacheKeys.REMOTE_WX_API_TOKEN);
		if (value != null) {
			WlApiToken wlApiToken = JSON.parseObject(value.toString(), WlApiToken.class);
			String token = wlApiToken.getToken();
			// 判断是否过期
			if (tokenParser(token)) {
				redisCacheUtil.del(ToolsCacheKeys.REMOTE_WX_API_TOKEN);
				return getToken(appId, secret); // 注意递归调用的退出条件需成立,否则有死循环的风险
			}
			log.info("获取三方系统token成功:{}", token);
			return token;
		} else {
			WlApiToken wlApiToken = remote56ApiFeignClient.get56ApiTokenByAppId(appId, secret);
			if (!Objects.equals(wlApiToken.getCode(), 0)) {
				log.error("获取三方系统token失败:{}", wlApiToken.getMsg());
				return "test";
			}
			redisCacheUtil.set(ToolsCacheKeys.REMOTE_WX_API_TOKEN, JSON.toJSONString(wlApiToken), ToolsCacheKeys.REMOTE_WX_API_TOKEN.getExpire());
			String token = wlApiToken.getToken();
			log.info("获取三方系统token成功:{}", token);
			return token;
		}
	}

	/**
	 * 解析token是否过期
	 *
	 * @return true 过期 false 未过期
	 */
	public boolean tokenParser(String token){
		try {
			// 使用密钥解析 Token
			Claims claims = Jwts.parser()
				.setSigningKey(secret.getBytes())
				.parseClaimsJws(token)
				.getBody();
			log.info("解析token成功:{}", claims);
			Integer exp = (Integer) claims.get("exp");
			// 判断是否过期
			if (exp * 1000L < System.currentTimeMillis()) {
				log.error("token已过期");
				return true;
			}
			return false;
		} catch (Exception e) {
			log.error("解析token失败:{}", e.getMessage());
			return true;
		}
	}
}

使用Feign在对接第三方系统虽然和使用http等工具类差不太多,不过我觉得这种更加优雅,🤭