因业务需要调用另一个微服务接口,每次必须在头部携带令牌信息:Authorization
一、fegin远程接口定义
package com.song.cocoa.dmp.service.feign;
import com.song.cocoa.dmp.config.AuthorizationInterceptor;
import com.song.cocoa.dmp.config.FeignConfig;
import com.song.cocoa.dmp.model.dto.AdvertCrowdRecordPageDTO;
import com.song.cocoa.dmp.model.dto.CrowdPageDTO;
import com.song.cocoa.dmp.model.dto.SysUserDTO;
import com.song.cocoa.dmp.model.vo.ResponseVO;
import com.song.cocoa.dmp.model.vo.UserVO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.SpringQueryMap;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import java.net.URI;
import java.util.regex.Pattern;
/**
* Feign远程调用
*
* @author songjianyong
* @date 2022/04/02 09:23
*/
@FeignClient(name = "song-ad-dmp", url = "ignore", configuration = FeignConfig.class)
public interface DmpCrowdRecordFeignService {
String AUTHORIZATION_HEADER = "Authorization";
/**
* 无需令牌的方法
*/
Pattern NO_TOKEN_REQUIRED_METHOD = Pattern.compile("^(login|index)$", Pattern.CASE_INSENSITIVE);
/**
* 测试环境
*/
URI BETA_URI = URI.create("http://127.0.0.1:3006");
/**
* 开发环境
*/
URI DEV_URI = URI.create("http://127.0.0.1:3007");
/**
* 用户登录
*
* @param uri 接口地址,如:http://127.0.0.1:3007
* @param userName 用户名
* @param password 用户密码
* @return 登录成功信息
*/
@PostMapping(value = "/login", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
ResponseVO<UserVO> login(URI uri, @RequestParam("userName") String userName, @RequestParam("password") String password);
/**
* 分页查询
* <p>
* https://docs.spring.io/spring-cloud-openfeign/docs/3.0.4/reference/html/
* </p>
*
* @param uri 接口地址,如:http://127.0.0.1:3007
* @param token 令牌,可为空
* @param req 请求参数
* @return CrowdPageDTO
* @see AuthorizationInterceptor
* @deprecated 令牌已采用拦截器处理了
*/
@GetMapping(value = "/dmp/crowd/index", produces = MediaType.APPLICATION_JSON_VALUE)
@Deprecated
CrowdPageDTO index(URI uri, @RequestHeader(AUTHORIZATION_HEADER) String token, @SpringQueryMap AdvertCrowdRecordPageDTO req);
/**
* 分页查询
* <p>
* https://docs.spring.io/spring-cloud-openfeign/docs/3.0.4/reference/html/
* </p>
*
* @param uri 接口地址,如:http://127.0.0.1:3007
* @param req 请求参数
* @return CrowdPageDTO
* @see AuthorizationInterceptor
*/
@GetMapping(value = "/dmp/crowd/index", produces = MediaType.APPLICATION_JSON_VALUE)
CrowdPageDTO page(URI uri, @SpringQueryMap AdvertCrowdRecordPageDTO req);
/**
* 根据用户ID获取其详细信息
*
* @param uri 接口地址,如:http://127.0.0.1:3007
* @param userId 用户ID
* @return 用户详细信息
*/
@GetMapping("/sys/user/detail")
ResponseVO<SysUserDTO> detail(URI uri, @RequestParam("userId") Integer userId);
}
二、拦截器代码
package com.song.cocoa.dmp.config;
import com.song.cocoa.dmp.model.vo.ResponseVO;
import com.song.cocoa.dmp.model.vo.UserVO;
import com.song.cocoa.dmp.service.feign.DmpCrowdRecordFeignService;
import feign.MethodMetadata;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Objects;
import static com.song.cocoa.dmp.service.feign.DmpCrowdRecordFeignService.*;
/**
* 拦截器
*
* @author songjianyong
* @date 2022/04/02 10:33
*/
@Component
@Slf4j
public class AuthorizationInterceptor implements RequestInterceptor {
private Pair<Long, UserVO> tokenInfo;
private DmpCrowdRecordFeignService dmpCrowdRecordFeignService;
@Autowired
public void setDmpCrowdRecordFeignService(DmpCrowdRecordFeignService dmpCrowdRecordFeignService) {
this.dmpCrowdRecordFeignService = dmpCrowdRecordFeignService;
}
@Override
public void apply(RequestTemplate template) {
MethodMetadata methodMetadata = template.methodMetadata();
log.info("接口方法:{}", methodMetadata.configKey());
if (!Objects.equals(DmpCrowdRecordFeignService.class, methodMetadata.targetType())) {
return;
}
Method method = methodMetadata.method();
if (NO_TOKEN_REQUIRED_METHOD.matcher(method.getName()).matches()) {
return;
}
//一、从请求上下文中获取
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (Objects.nonNull(requestAttributes)) {
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
String token = request.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.isNotBlank(token)) {
template.header(AUTHORIZATION_HEADER, token);
return;
}
}
//二、直接调用登录接口获取令牌
//因服务端令牌失效时间8小时,这里每个隔7小时自动更新令牌
long tokenTimeout = 7 * 60 * 60 * 1000L;
if (Objects.isNull(tokenInfo) || ((System.currentTimeMillis() - tokenInfo.getLeft()) > tokenTimeout)) {
URI uri = SystemUtils.IS_OS_WINDOWS ? DEV_URI : BETA_URI;
ResponseVO<UserVO> login = dmpCrowdRecordFeignService.login(uri, "admin", "123456");
tokenInfo = Pair.of(System.currentTimeMillis(), login.getData());
log.warn("获取令牌为:{}", tokenInfo.getRight().getToken());
}
template.header(AUTHORIZATION_HEADER, tokenInfo.getRight().getToken());
}
}
三、FeignConfig配置
package com.song.cocoa.dmp.config;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import feign.*;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.optionals.OptionalDecoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;
import org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient;
import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.cloud.openfeign.support.SpringMvcContract;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.ConversionService;
import org.springframework.http.MediaType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* @author songjianyong
* @date 2021/09/22 15:00
* <p> feign配置 </p>
*/
@Configuration
@Slf4j
public class FeignConfig {
@Value("${feign.connect.timeout.millis:2000}")
private int connectTimeOutMillis;
@Value("${feign.read.timeout.millis:5000}")
private int readTimeOutMillis;
private FastJsonHttpMessageConverter getFastJsonConverter() {
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
List<MediaType> supportedMediaTypes = new ArrayList<>();
MediaType mediaTypeJson = MediaType.valueOf(MediaType.APPLICATION_JSON_VALUE);
supportedMediaTypes.add(mediaTypeJson);
supportedMediaTypes.add(MediaType.valueOf(MediaType.TEXT_HTML_VALUE));
supportedMediaTypes.add(MediaType.valueOf("text/json;charset=utf-8"));
converter.setSupportedMediaTypes(supportedMediaTypes);
FastJsonConfig config = new FastJsonConfig();
//config.getSerializeConfig().put(springfox.documentation.spring.web.json.Json.class, new SwaggerJsonSerializer());
config.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect);
converter.setFastJsonConfig(config);
return converter;
}
private ObjectFactory<HttpMessageConverters> feignHttpMessageConverter() {
final HttpMessageConverters httpMessageConverters = new HttpMessageConverters(getFastJsonConverter());
return () -> httpMessageConverters;
}
@Bean
public Encoder feignEncoder() {
return new SpringEncoder(feignHttpMessageConverter());
}
@Bean
public Decoder feignDecoder() {
return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(feignHttpMessageConverter())));
}
@Bean
public Contract feignContract(@Qualifier("mvcConversionService") ConversionService feignConversionService) {
List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList<>();
return new SpringMvcContract(parameterProcessors, feignConversionService);
}
@Bean
public Request.Options options() {
return new Request.Options(connectTimeOutMillis, TimeUnit.MILLISECONDS,
readTimeOutMillis, TimeUnit.MILLISECONDS, true);
}
@Bean
public Feign.Builder feignBuilder(Decoder feignDecoder,
Encoder feignEncoder,
Request.Options options,
Client feignClient,
//@Qualifier("feignRetryClient") Client retryClient,
Contract contract,
RequestInterceptor requestInterceptor) {
// 无需均衡负载
if (feignClient instanceof LoadBalancerFeignClient) {
feignClient = ((LoadBalancerFeignClient) feignClient).getDelegate();
}
log.info("feignClient >> {}", feignClient.getClass().getName());
//log.info("retryClient >> {}", retryClient.getClass().getName());
return Feign.builder()
.client(feignClient)
.decoder(feignDecoder)
.encoder(feignEncoder)
.contract(contract)
.options(options)
.logLevel(Logger.Level.FULL)
.requestInterceptors(Collections.singleton(requestInterceptor))
.retryer(Retryer.NEVER_RETRY);
}
}
四、测试
package com.song.cocoa.dmp.service.feign;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.song.cocoa.dmp.AdvertDmpApplicationTests;
import com.song.cocoa.dmp.model.dto.AdvertCrowdRecordPageDTO;
import com.song.cocoa.dmp.model.dto.CrowdPageDTO;
import com.song.cocoa.dmp.model.dto.SysUserDTO;
import com.song.cocoa.dmp.model.vo.ResponseVO;
import com.song.cocoa.dmp.model.vo.UserVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.SystemUtils;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.net.URI;
import static com.song.cocoa.dmp.service.feign.DmpCrowdRecordFeignService.BETA_URI;
import static com.song.cocoa.dmp.service.feign.DmpCrowdRecordFeignService.DEV_URI;
/**
* 人物画像远程接口测试
*
* @author songjianyong
* @date 2022/04/02 09:53
*/
@Slf4j
public class DmpCrowdRecordFeignServiceTests extends AdvertDmpApplicationTests {
@Autowired
private DmpCrowdRecordFeignService dmpCrowdRecordFeignService;
@Test
public void loginTest() throws JsonProcessingException {
URI uri = SystemUtils.IS_OS_WINDOWS ? DEV_URI : BETA_URI;
ResponseVO<UserVO> login = dmpCrowdRecordFeignService.login(uri, "admin", "20211213@dmp");
log.info(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(login));
}
@Test
public void indexTest() throws JsonProcessingException {
URI uri = SystemUtils.IS_OS_WINDOWS ? DEV_URI : BETA_URI;
AdvertCrowdRecordPageDTO req = AdvertCrowdRecordPageDTO.builder().pageNo(1).pageSize(10).build();
CrowdPageDTO page = dmpCrowdRecordFeignService.index(uri, null, req);
log.info(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(page));
}
@Test
public void pageTest() throws JsonProcessingException {
URI uri = SystemUtils.IS_OS_WINDOWS ? DEV_URI : BETA_URI;
AdvertCrowdRecordPageDTO req = AdvertCrowdRecordPageDTO.builder().pageNo(1).pageSize(10).build();
CrowdPageDTO page = dmpCrowdRecordFeignService.page(uri, req);
log.info(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(page));
}
@Test
public void detailTest() throws JsonProcessingException {
URI uri = SystemUtils.IS_OS_WINDOWS ? DEV_URI : BETA_URI;
ResponseVO<SysUserDTO> detail = dmpCrowdRecordFeignService.detail(uri, 1);
log.info(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(detail));
}
}