一、微服务本地调用feign
Feign 是一个在 Java 平台上的声明式、模板化的 HTTP 客户端,它简化了使用 HTTP API 的过程。Feign 是 Netflix 开发的,旨在简化基于 HTTP 的 API 客户端开发。
1、特点
1.1:声明式 API 定义: 通过接口和注解的方式定义 API,使得 API 定义更加清晰和简洁。
1.2:集成 Ribbon: Feign 默认集成了 Ribbon 负载均衡器,可以轻松实现客户端的负载均衡。
1.3:集成 Hystrix: Feign 也可以集成 Hystrix,从而实现客户端的容错和断路器功能。
1.4:支持多种编码器和解码器: Feign 支持多种编码器和解码器,包括 JSON、XML 等。
1.5:支持动态 URL 和查询参数: 可以在接口方法中直接使用参数来构建 URL 和查询参数。
2、代码实例
2.1、添加maven支持
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.2、在服务启动类添加注解
@Slf4j//日志log
@SpringBootApplication//用于标识 Spring Boot 应用程序的注解
@EnableFeignClients//开启feign
@MapperScan("com.netrust.*.dao")//扫描包下的mapper
@EnableTransactionManagement//开启事务注解
public class BaseApplication {
public static void main(String[] args) {
SpringApplication.run(BaseApplication.class, args);
}
}
2.3、编写feign类
@FeignClient(value = ServerNameConstants.SYSTEM_BASE, contextId = "room", configuration = FeignConfiguration.class)
public interface FeignApi {
@PostMapping("/add")
String addSysApi(@RequestBody FeignBO feignBO);
@PutMapping("/edit")
String editSysApi(@RequestBody FeignBO feignBO);
@DeleteMapping("/delete")
String deleteByIdSysApi(@RequestBody FeignDto feignDto);
@GetMapping("/queryById")//@SpringQueryMap表示get可以用对象接收,不然就需要一个一个接收
String queryById(@SpringQueryMap FeignDto feignDto);
@RequestMapping(value = "file/personPhotoUpload", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
String personPhotoUpload(@RequestPart(value = "file") MultipartFile file,
@RequestParam(value = "moduleName") String moduleName,
@RequestParam(value = "functionName") String functionName,
@RequestParam(value = "needCompress") Boolean needCompress);
}
2.4、调用服务
@Resource
private FeignApi feignApi;
/**
* 通过主键查询单条数据
*/
@GetMapping("/feign/{id}")
@ApiOperation(value = "通过主键查询单条数据")
public ResultJson queryById( FeignDto feignDto, @PathVariable("id") Long id) {
feignDto.setId(id);
String s = feignApi.queryById(feignDto);
return JSONObject.parseObject(s, ResultJson.class);
}
2.5、@FeignClient内部参数详解
2.5.1、value: 指定要调用的目标服务的服务名。可以是一个单独的服务名,也可以是多个服务名组成的数组。
2.5.2、url: 指定要调用的目标服务的URL地址,可以是一个完整的URL地址,也可以是占位符形式的URL地址。
2.5.3、path: 指定要调用的目标服务的基本路径,可以是一个字符串,也可以是一个占位符形式的字符串。
2.5.4、configuration: 指定要使用的Feign客户端配置类。可以自定义配置Feign客户端的行为。
2.5.5、decode404: 指定是否将404错误解码。默认情况下,Feign会将404错误解码为null值。
2.5.6、fallback: 指定一个回退类,当调用失败时,将调用回退类中的方法。回退类必须实现@FeignClient注解中的接口。
2.5.7、fallbackFactory: 指定一个回退工厂类,用于创建回退类的实例。回退工厂类必须实现FallbackFactory接口。
2.5.8、primary: 指定Feign客户端是否为首选客户端。如果设置为true,则优先使用该客户端。
2.5.9、qualifier: 指定Feign客户端的限定符。可以与@Autowired注解配合使用,用于指定要注入的Feign客户端实例。
2.5.10、name: 同value,指定要调用的目标服务的服务名。
2.5.11、contextId: 指定Feign客户端的上下文ID。用于区分不同的Feign客户端实例。
2.5.12、url: 指定要调用的目标服务的URL地址。
二、HttpClient
1、工具类
@Slf4j
public class HttpClientUtils {
/**
* 文件上传到第三方
* @param url 网络地址
* @param inputStream 文件流
* @param headers 头文件(认证信息设置)
* @param verification 是否需要验证
* @return
* @throws IOException
*/
public static String uploadFile(String url, InputStream inputStream, Map<String, String> headers, boolean verification) throws IOException {
HttpClient httpClient = verification ? HttpClients.createDefault() : wrapClient();
HttpPost httpPost = new HttpPost(url);
//设置请求和传输超时时间
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(60000).setConnectTimeout(60000).build();
httpPost.setConfig(requestConfig);
if (headers != null) {
headers.forEach((key, value) -> {
httpPost.setHeader(key, value);
});
}
//创建文件把流文件写入到字节内
File file = File.createTempFile(UUID.randomUUID().toString(), ".jpg");
FileOutputStream os = new FileOutputStream(file);
int read = 0;
byte[] buffer = new byte[1024];
while ((read = inputStream.read(buffer, 0, 1024)) != -1) {
os.write(buffer, 0, read);
}
inputStream.close();
//发送邮件
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
FileBody fileBody = new FileBody(file, ContentType.MULTIPART_FORM_DATA);
builder.addPart("file", fileBody);
httpPost.setEntity(builder.build());
HttpResponse response = httpClient.execute(httpPost);
String httpEntityContent = getHttpEntityContent(response);
httpPost.abort();
return httpEntityContent;
}
/**
* 封装HTTP POST方法
*
* @param verification TRUE为不跳过证书检测 FALSE为跳过证书检测
* @param (如JSON串)
* @return
* @throws ClientProtocolException
* @throws IOException
*/
public static String post(String url, String body, Map<String, String> headers, boolean verification) throws ClientProtocolException, IOException {
HttpClient httpClient = verification ? HttpClients.createDefault() : wrapClient();
HttpPost httpPost = new HttpPost(url);
//设置请求和传输超时时间
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(60000).setConnectTimeout(60000).build();
httpPost.setConfig(requestConfig);
httpPost.setHeader("Content-Type", "application/json");
if (headers != null) {
headers.forEach((key, value) -> {
httpPost.setHeader(key, value);
});
}
if (body != null) {
StringEntity s = new StringEntity(body, StandardCharsets.UTF_8);
s.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE, "application/json"));
//设置参数到请求对象中
httpPost.setEntity(s);
}
HttpResponse response = httpClient.execute(httpPost);
String httpEntityContent = getHttpEntityContent(response);
httpPost.abort();
return httpEntityContent;
}
public static String put(String url, String body, Map<String, String> headers, boolean verification) throws ClientProtocolException, IOException {
HttpClient httpClient = verification ? HttpClients.createDefault() : wrapClient();
HttpPut httpPut = new HttpPut(url);
//设置请求和传输超时时间
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(60000).setConnectTimeout(60000).build();
httpPut.setConfig(requestConfig);
httpPut.setHeader(HTTP.CONTENT_TYPE, "application/json");
if (headers != null) {
headers.forEach((key, value) -> {
httpPut.setHeader(key, value);
});
}
if (body != null) {
StringEntity s = new StringEntity(body, StandardCharsets.UTF_8);
s.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE, "application/json"));
//设置参数到请求对象中
httpPut.setEntity(s);
}
HttpResponse response = httpClient.execute(httpPut);
String httpEntityContent = getHttpEntityContent(response);
httpPut.abort();
return httpEntityContent;
}
public static String delete(String url, String body, Map<String, String> headers, boolean verification) throws ClientProtocolException, IOException {
HttpClient httpClient = verification ? HttpClients.createDefault() : wrapClient();
HttpDeleteWithBody httpDelete = new HttpDeleteWithBody(url);
//设置请求和传输超时时间
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(60000).setConnectTimeout(60000).build();
httpDelete.setConfig(requestConfig);
httpDelete.setHeader(HTTP.CONTENT_TYPE, "application/json");
if (headers != null) {
headers.forEach((key, value) -> {
httpDelete.setHeader(key, value);
});
}
if (body != null) {
StringEntity s = new StringEntity(body, StandardCharsets.UTF_8);
s.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE, "application/json"));
//设置参数到请求对象中
httpDelete.setEntity(s);
}
HttpResponse response = httpClient.execute(httpDelete);
String httpEntityContent = getHttpEntityContent(response);
httpDelete.abort();
return httpEntityContent;
}
/**
* 封装HTTP GET方法
*
* @param
* @param verification TRUE为不跳过证书检测 FALSE为跳过证书检测
* @return
* @throws ClientProtocolException
* @throws IOException
*/
public static String get(String url, Map<String, Object> params, Map<String, String> headers, boolean verification) throws ClientProtocolException, IOException {
HttpClient httpClient = verification ? HttpClients.createDefault() : wrapClient();
HttpGet httpGet = new HttpGet();
//设置请求和传输超时时间
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(60000).setConnectTimeout(60000).build();
httpGet.setConfig(requestConfig);
if (headers != null) {
headers.forEach((key, value) -> {
httpGet.addHeader(key, value);
});
}
if (params != null) {
url = getUrlByParams(url, params);
}
httpGet.setURI(URI.create(url));
HttpResponse response = httpClient.execute(httpGet);
String httpEntityContent = getHttpEntityContent(response);
httpGet.abort();
return httpEntityContent;
}
private static String getUrlByParams(String url, Map<String, Object> params) {
StringBuffer sb = new StringBuffer();
params.forEach((key, value) -> {
sb.append("&" + key + "=" + value);
});
if (sb.length() > 0) {
String substring = sb.substring(1);
url += url.contains("?") ? "&" : "?" + substring;
}
return url;
}
/**
* 获得响应HTTP实体内容
*
* @param response
* @return
* @throws IOException
* @throws UnsupportedEncodingException
*/
private static String getHttpEntityContent(HttpResponse response) throws IOException, UnsupportedEncodingException {
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream is = entity.getContent();
BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
String line = br.readLine();
StringBuilder sb = new StringBuilder();
while (line != null) {
sb.append(line + "\n");
line = br.readLine();
}
return sb.toString();
}
return null;
}
public static HttpClient wrapClient() {
HttpClients.createDefault();
try {
SSLContext ctx = SSLContext.getInstance("TLS");
X509TrustManager trustManager = new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
}
};
ctx.init(null, new TrustManager[]{trustManager}, null);
SSLConnectionSocketFactory ssf = new SSLConnectionSocketFactory(ctx, NoopHostnameVerifier.INSTANCE);
return HttpClients.custom()
.setSSLSocketFactory(ssf)
.build();
} catch (Exception e) {
return HttpClients.createDefault();
}
}
}
2、调用解析
try {
HashMap<String, String> map = new HashMap<>();
map.put("app_id",appid);
map.put("app_secret",appSecret);
map.put("grant_type","client_credentials");
String res = HttpClientUtils.post(host + "/oauth/token", JSON.toJSONString(map), null, false);
JSONObject jsonObject = JSONObject.parseObject(res);
if(jsonObject.containsKey("code") && jsonObject.getString("code").equals("0")){
return JSONObject.parseObject(jsonObject.getString("result"),PushToken.class);
}else{
throw new ServiceException(res);
}
}catch (ServiceException e){
throw new ServiceException(e);
}catch (Exception e){
e.printStackTrace();
}
三、路由代理
1、工具类
@Slf4j
@Component
public class RoutingDelegateUtil {
@Resource
//@Qualifier(value = "remoteRestTemplate")
private RestTemplate restTemplate;
/**
* 上传form表单,文件
*/
private final static String CONTENT_TYPE_FORM = "multipart/form-data;";
/**
* 请求转发统一处理
*
* @param request 原请求对象
* @param routeUrl 路由地址,统一前缀,重定向目标主机域名(带协议及端口)
* @param prefix 需要去除的前缀
* @return
* @throws Exception
*/
public ResponseEntity<String> redirect(HttpServletRequest request, String routeUrl, String prefix) throws Exception {
String contentType = request.getContentType();
log.info("getContentType={}", contentType);
// multipart/form-data处理
if (StringUtils.isNotEmpty(contentType) && contentType.contains(CONTENT_TYPE_FORM)) {
return redirectFile(request, routeUrl);
} else {
return redirect(request, routeUrl, prefix, String.class);
}
}
/**
* 上传form表单,文件
* <p>
* 请求转发处理
*
* @param request 原请求对象
* @param routeUrl 重定向目标主机域名(带协议及端口)
* @return
* @throws IOException
*/
public ResponseEntity<String> redirectFile(HttpServletRequest request, String routeUrl) throws IOException {
// build up the redirect URL
String redirectUrl = createRedictUrl(request, routeUrl, "");
log.info("redirectFile redirectUrl={}", redirectUrl);
String method = request.getMethod();
//设置请求头
MultiValueMap<String, String> headers = parseRequestHeader(request);
// 组装form参数
MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
StandardMultipartHttpServletRequest standardMultipartHttpServletRequest = (StandardMultipartHttpServletRequest) request;
// 组装form参数-文件
MultiValueMap<String, MultipartFile> multiValueMap = standardMultipartHttpServletRequest.getMultiFileMap();
for (Map.Entry<String, List<MultipartFile>> entries : multiValueMap.entrySet()) {
for (MultipartFile multipartFile : entries.getValue()) {
String fileName = multipartFile.getOriginalFilename();
log.info("redirectFile MultipartFile: fileName={}", fileName);
File file = File.createTempFile("spw-", fileName);
multipartFile.transferTo(file);
FileSystemResource fileSystemResource = new FileSystemResource(file);
form.add(entries.getKey(), fileSystemResource);
}
}
// 组装form参数-一般属性
Enumeration<String> enumeration = standardMultipartHttpServletRequest.getParameterNames();
while (enumeration.hasMoreElements()) {
String name = enumeration.nextElement();
String value = standardMultipartHttpServletRequest.getParameter(name);
log.info("redirectFile enumeration: name={}, value={}", name, value);
form.add(name, value);
}
// 用HttpEntity封装整个请求报文
HttpEntity<MultiValueMap<String, Object>> formEntity = new HttpEntity<>(form, headers);
return restTemplate.exchange(redirectUrl, HttpMethod.valueOf(method), formEntity, String.class);
}
/**
* 非form-data请求转发处理
*
* @param request 原请求对象
* @param routeUrl 重定向目标主机域名(带协议及端口)
* @param prefix 需要去除的前缀
* @param clazz 结果类型
* @return
* @throws Exception
*/
public <T> ResponseEntity<T> redirect(HttpServletRequest request, String routeUrl, String prefix, Class<T> clazz) throws Exception {
// build up the redirect URL
String redirectUrl = createRedictUrl(request, routeUrl, prefix);
log.info("redirectUrl={}", redirectUrl);
RequestEntity requestEntity = createRequestEntity(request, redirectUrl);
return restTemplate.exchange(requestEntity, clazz);
}
/**
* 构建重定向地址
*
* @param request 原请求对象
* @param routeUrl 重定向目标主机域名(带协议及端口)
* @param prefix 需要去除的前缀
* @return
*/
private String createRedictUrl(HttpServletRequest request, String routeUrl, String prefix) {
String queryString = request.getQueryString();
return routeUrl + request.getRequestURI().substring(1).replace(prefix, "") +
(queryString != null ? "?" + queryString : "");
}
/**
* 构建请求实体
*
* @param request 原请求对象
* @param url 新目标路由URL
* @return
* @throws URISyntaxException
* @throws IOException
*/
private RequestEntity createRequestEntity(HttpServletRequest request, String url) throws URISyntaxException, IOException {
String method = request.getMethod();
HttpMethod httpMethod = HttpMethod.resolve(method);
MultiValueMap<String, String> headers = parseRequestHeader(request);
byte[] body = parseRequestBody(request);
return new RequestEntity<>(body, headers, httpMethod, new URI(url));
}
/**
* 解析请求体
*
* @param request
* @return
* @throws IOException
*/
private byte[] parseRequestBody(HttpServletRequest request) throws IOException {
InputStream inputStream = request.getInputStream();
return StreamUtils.copyToByteArray(inputStream);
}
/**
* 解析请求头
*
* @param request
* @return
*/
private MultiValueMap<String, String> parseRequestHeader(HttpServletRequest request) {
HttpHeaders headers = new HttpHeaders();
List<String> headerNames = Collections.list(request.getHeaderNames());
for (String headerName : headerNames) {
List<String> headerValues = Collections.list(request.getHeaders(headerName));
for (String headerValue : headerValues) {
headers.add(headerName, headerValue);
}
}
return headers;
}
}
2、工具类调用
@RequestMapping("/sys-monitor/**")
@Description("服务监控分发")
public ResultJson transmit(HttpServletRequest request, @RequestParam(value = "ip", required = false, defaultValue = "127.0.0.1") String ip) throws Exception {
String routeUrl = "http://" + ip + ":端口";
ResponseEntity<ResultJson> responseEntity = restRoute.redirect(request, routeUrl, "sys-monitor", ResultJson.class);
return responseEntity.getBody();
}