Feign根据动态ip调用不同主机的微服务

  最近遇到这样一个需求,允许在办公内网的每个客户端安装微服务,以便能够调用客户端操作系统的命令。

  显然技术点在:根据客户端ip,springcloud调用部署在相应ip上的微服务。springcloud feign的动态url技术,能够提供这一功能的实现。

一、代码实现

1,服务端的Feign接口

1 @FeignClient(name = "BOOK-COMMAND-ENGINE", url = "http://localhost:8015")
2 public interface CommandEngineApi {
3 
4     @RequestMapping(value="startEdit")
5     JSONObject startEdit(URI uri, @RequestParam("filePath")String filePath);
6     
7 }

 2,客户端feign实现接口

 1 @RestController
 2 public class CommandController {
 3 
 4     private static Logger log = LoggerFactory.getLogger(CommandController.class);
 5 
 6     @RequestMapping(value="startEdit")
 7     public JSONObject startEdit(String filePath) {
 8         JSONObject result = new JSONObject();
 9         String cmd = String.format(CommandParam.START_EDIT_CMD, filePath);
10         try {
11             Runtime.getRuntime().exec(cmd);
12             log.info(cmd);
13         } catch (Exception e) {
14             e.printStackTrace();
15             log.error(e.getMessage());
16         }
17         log.info("execute:" + cmd);
18         result.put("code", 0);
19         return result;
20     }
21 }

3,服务端controller

 1 @RequestMapping(value = "/startAdjust")
 2     public void startAdjust(@RequestParam(value = "fileId") String fileId) {
 3         FileInfo fileInfo = fileService.queryFileByFileId(fileId);
 4         File file = new File(fileInfo.getFilePath());
 5         String clientIP = IpUtil.getIpAddress();
 6         String filePath = file.getAbsolutePath();
 7         URI uri;
 8         try {
 9              uri = new URI(String.format("http://%s:8015", clientIP));
10             logger.info("invoke:" + uri.toString() + " " + filePath);
11             commandEngineApi.startEdit(uri, filePath);
12         } catch (URISyntaxException e) {
13             e.printStackTrace();
14             logger.error(e.getMessage());
15         }
16     }

ps:获取客户端ip

 1 public static String getIpAddress() {
 2         final String UNKNOWN = "unknown";
 3         final String[] matchOptions = {"x-forwarded-for", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"};
 4         final int size = matchOptions.length;
 5         try {
 6             RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
 7             HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
 8             String ip = UNKNOWN;
 9             int index = 0;
10             while(index < size && (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip))){
11                 ip = request.getHeader(matchOptions[index]);
12                 index++;
13             }
14             if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
15                 ip = request.getRemoteAddr();
16             }
17             if (!StringUtils.isEmpty(ip) && !UNKNOWN.equalsIgnoreCase(ip) && ip.length() > 15) {
18                 String[] ips = ip.split(",");
19                 int len = ips.length;
20                 for (int i = 0; i < len; i++) {
21                     String strIp = ips[index];
22                     if (!(UNKNOWN.equalsIgnoreCase(strIp))) {
23                         ip = strIp;
24                         break;
25                     }
26                 }
27             }
28             return ip;
29         } catch (Exception e) {
30             return UNKNOWN;
31         }
32     }

二、代码分析

1,Springcloud OpenFeign自动构建实现类

  在使用方式上,OpenFeign需要手动构建代理对象,Spring Cloud OpenFeign 不同于 OpenFeign, Spring Cloud OpenFeign 帮我们自动生成了接口的代理对象(即实现类),并且注册到Spring中,我们可以很方便的使用 @Autowired 注入代理对象然后使用。

  其默认的代理对象是 LoadBalancerFeignClient。还有一个代理对象是 feign.Client.Default。

  两者区别在于:LoadBalancerFeignClient 通过服务名(下文提到)从Eureka查找相关的节点地址url,发起调用。feign.Client.Default 仅是简单的直接调用。

  open-feign底层库是使用HttpURLConnection。feign会基于配置,生成URI。当我们不配置url的时候,URI是这样的http://service-name/path。使用FeignClien是LoadBalancerFeignClient,该类在处理该URI的时候会对服务名进行解析,也就是从注册中心查询该服务名下已经注册的服务器信息,包括IP和端口。然后将服务名替换成真实的链接。而当我们不配置url的时候,使用的FeignClient是Client的默认实现Default,该类就没有解析的这一步而是直接通过HttpURLConnection进行请求。

2,.Feign接口中的name与url一定要指定

  name属性,是@FeignClient 注解必要的,不定义时会报错其默认指代Eureka上的服务名。

  url属性,一定要指定,指定什么值其实不重要,因为最终都会被方法的URI参数值替换掉,它在这里另一个重要的作用,就是将接口的代理对象变成feign.Client.Default(默认是LoadBalancerFeignClient),这样就绕过了从Eureka取节点地址这一步,毕竟第三方的地址不可能注册到我们的Eureka上。

3,Feign接口参数中会比实现类多一个参数URL

  feign实现类不能去实现feign接口,因为feign接口要比实现类多一个参数URL,动态调用时接口会根据这个参数分发到不同微服务,其他参数使用@RequestMapping、@RequestParam等正常使用。

posted @ 2022-08-30 14:14  光何  阅读(2347)  评论(0编辑  收藏  举报