前言 前面我们已经实现了服务的注册与发现(请戳:SpringCloud系列Eureka服务注册与发现),并且在注册中心注册了一个服务myspringboot,本文记录多个服务之间使用Feign调用。 Feign是一个声明性web服务客户端。它使编写web服务客户机变得更容易,本质上就是一个http,内部进行了封装而已。 GitHub地址:https:github。comOpenFeignfeign 官方文档:https:cloud。spring。iospringcloudstaticspringcloudopenfeign2。1。0。RC2singlespringcloudopenfeign。html服务提供者 提供者除了要在注册中心注册之外,不需要引入其他东西,注意以下几点即可: 1、经测试,默认情况下,feign只能通过RequestBody传对象参数 2、接参只能出现一个复杂对象,例:publicResultListUserVolist(RequestBodyUserVoentityVo){。。。} 3、提供者如果又要向其他消费者提供服务,又要向浏览器提供服务,建议保持原先的Controller,新建一个专门给消费者的Controller 测试Controller接口RestControllerRequestMapping(user)publicclassUserController{AutowiredprivateUserServiceuserSRequestMapping(list)publicResultListUserVolist(RequestBodyUserVoentityVo){returnuserService。list(entityVo);}RequestMapping(get{id})publicResultUserVoget(PathVariable(id)Integerid){returnuserService。get(id);}}服务消费者 消费者maven引入jar!feigndependencygroupIdorg。springframework。cloudgroupIdspringcloudstarteropenfeignartifactIddependency 配置文件 对日期的解析,消费者要跟提供者一致,不然会报json解析错误超时时间feign。httpclient。connectiontimeout30000mvc接收参数时对日期进行格式化spring。mvc。dateformatyyyyMMddHH:mm:ssjackson对响应回去的日期参数进行格式化spring。jackson。dateformatyyyyMMddHH:mm:ssspring。jackson。timezoneGMT8 服务调用 1、springdatejpa应用名称,是服务提供者在eureka注册的名字,Feign会从注册中心获取实例 2、如果不想启动eureka服务,直连本地开发:FeignClient(namespringdatejpa,pathuser,urlhttp:localhost:10086),或者无eureka,调用第三方服务,关闭eureka客户端(eureka。client。enabledfalse),url直接指定第三方服务地址,path指定路径,接口的方法指定接口 3、如果使用RequestMapping,最好指定调用方式 4、消费者的返回值必须与提供者的返回值一致,参数对象也要一致 5、20190521补充:如需进行容错处理(服务提供者发生异常),则需要配置fallback,如果需要获取到报错信息,则要配置fallbackFactory,例:fallbackMyspringbootFeignFallback。class,fallbackFactoryMyspringbootFeignFallbackFactory。class容错处理(服务提供者发生异常,将会进入这里)ComponentpublicclassMyspringbootFeignFallbackimplementsMyspringbootFeign{OverridepublicResultUserVoget(Integerid){returnResult。of(null,false,糟糕,系统出现了点小状况,请稍后再试);}OverridepublicResultListUserVolist(UserVoentityVo){returnResult。of(null,false,糟糕,系统出现了点小状况,请稍后再试);}}只打印异常,容错处理仍交给MyspringbootFeignFallbackComponentpublicclassMyspringbootFeignFallbackFactoryimplementsFallbackFactoryMyspringbootFeign{privatefinalMyspringbootFeignFallbackmyspringbootFeignFpublicMyspringbootFeignFallbackFactory(MyspringbootFeignFallbackmyspringbootFeignFallback){this。myspringbootFeignFallbackmyspringbootFeignF}OverridepublicMyspringbootFeigncreate(Throwablecause){cause。printStackTrace();returnmyspringbootFeignF}} Feign接口 更多FeignClient注解参数配置,请参阅官方文档FeignClient(namespringdatejpa,pathuser)publicinterfaceMyspringbootFeign{RequestMapping(valueget{id})ResultUserVoget(PathVariable(id)Integerid);RequestMapping(valuelist,methodRequestMethod。GET)ResultListUserVolist(RequestBodyUserVoentityVo);} Controller层feign调用GetMapping(feignget{id})ResultUserVoget(PathVariable(id)Integerid){returnmyspringbootFeign。get(id);}feign调用GetMapping(feignlist)ResultListUserVolist(UserVouserVo){returnmyspringbootFeign。list(userVo);} 启动类 启动类加入注解:EnableFeignClientsEnableEurekaClientEnableFeignClientsSpringBootApplicationpublicclassMyspringbootApplication{publicstaticvoidmain(String〔〕args){SpringApplication。run(MyspringbootApplication。class,args);}}效果 成功注册两个服务 成功调用 报错记录 1、启动时报了个SQL错误 解决:配置文件连接数据时指定serverTimezoneGMT2B8 2、当我将之前搭好的一个springbootspringdatajpa整合项目在eureka注册时出现了一个报错 然后在网上查了下说是因为springboot版本问题(请戳:http:www。cnblogs。comhbbbsarticles8444013。html),之前这个项目用的是2。0。1。RELEASE,现在要在eureka注册,pom引入了就出现了上面的报错!eurekaclientdependencygroupIdorg。springframework。cloudgroupIdspringcloudstarternetflixeurekaclientartifactIddependency!actuatordependencygroupIdorg。springframework。bootgroupIdspringbootstarteractuatorartifactIddependencydependencyManagementdependenciesdependencygroupIdorg。springframework。cloudgroupIdspringclouddependenciesartifactIdversionGreenwich。RC1versiontypepomtypescopeimportscopedependencydependenciesdependencyManagementrepositoriesrepositoryidspringmilestonesidnameSpringMilestonesnameurlhttps:repo。spring。iomilestoneurlrepositoryrepositories 解决:升级了springboot版本,2。1。0,项目正常启动parentgroupIdorg。springframework。bootgroupIdspringbootstarterparentartifactIdversion2。1。0。RELEASEversion!version2。0。1。RELEASEversionrelativePath!lookupparentfromrepositoryparent补充 20191017补充:Feign设置header请求头 方法1,mapping的headers属性,单一设置FeignClient(namesvc,pathmodulesuser,url{feign。url})publicinterfaceUserFeignextendsBaseFeignUserVo{PostMapping(valuexxx,headers{Cookie,JSESSIONIDxxx})ResultModelListUserVoxxx(UserVoentity);} 方法2,自定义FeignInterceptor,全局设置feign请求设置header参数这里比如浏览器调用A服务,A服务Feign调用B服务,为了传递一致的sessionIdComponentpublicclassFeignInterceptorimplementsRequestInterceptor{publicvoidapply(RequestTemplaterequestTemplate){StringsessionIdRequestContextHolder。currentRequestAttributes()。getSessionId();requestTemplate。header(Cookie,JSESSIONIDsessionId);}} 这样就可以设置cookie,传递token等自定义值 常见场景1 通常我们一个服务web层、svc层、dao层,但有时候也会将拆分成两个服务: web服务提供静态资源、页面以及controller控制器控制跳转,数据通过java调用svc服务获取; svc服务,进行操作数据库以及业务逻辑处理,同时提供接口给web服务调用; 特殊情况下我们想svc服务的接口也做登录校验,所有接口(除了登录请求接口)都有做登录校验判断,未登录的无权访问,这时候就需要做sessionId传递,将web服务的sessionId通过Feign调用时传递到svc服务 web服务 注:登录成功后用sessionId作为key,登录用户的id作为value,保存到redis缓存中 登录拦截器web登录拦截器ComponentpublicclassLoginFilterimplementsFilter{AutowiredprivateStringRedisTOverridepublicvoidinit(FilterConfigfilterConfig){}OverridepublicvoiddoFilter(ServletRequestservletRequest,ServletResponseservletResponse,FilterChainfilterChain)throwsIOException,ServletException{HttpServletRequestrequest(HttpServletRequest)servletRHttpServletResponseresponse(HttpServletResponse)servletRStringrequestURIrequest。getRequestURI();除了访问首页、登录页面、登录请求,其他的都要查看Redis缓存StringsessionIdrequest。getSession()。getId();Stringredistemplate。opsForValue()。get(sessionId);if(!无需登录即可访问的接口(requestURI。contains(index)requestURI。contains(loginindex)requestURI。contains(loginlogin)静态资源requestURI。contains(。js)requestURI。contains(。css)requestURI。contains(。json)requestURI。contains(。ico)requestURI。contains(。png)requestURI。contains(。jpg))StringUtils。isEmpty(redis)){重定向登录页面response。sendRedirect(loginindex?urlrequestURI);}else{正常处理请求filterChain。doFilter(servletRequest,servletResponse);}}Overridepublicvoiddestroy(){}} 自定义FeignInterceptorfeign请求设置header参数这里比如浏览器调用A服务,A服务Feign调用B服务,为了传递一致的sessionIdComponentpublicclassFeignInterceptorimplementsRequestInterceptor{publicvoidapply(RequestTemplaterequestTemplate){StringsessionIdRequestContextHolder。currentRequestAttributes()。getSessionId();requestTemplate。header(Cookie,JSESSIONIDsessionId);}} svc服务svc登录拦截器ComponentpublicclassLoginFilterimplementsFilter{AutowiredprivateStringRedisTOverridepublicvoidinit(FilterConfigfilterConfig){}OverridepublicvoiddoFilter(ServletRequestservletRequest,ServletResponseservletResponse,FilterChainfilterChain)throwsIOException,ServletException{HttpServletRequestrequest(HttpServletRequest)servletRHttpServletResponseresponse(HttpServletResponse)servletRStringrequestURIrequest。getRequestURI();service服务,查看Redis缓存,登录后才允许访问(除了checkByAccountNameAndPassword)StringsessionIdrequest。getRequestedSessionId();if(!(requestURI。contains(modulesusercheckByAccountNameAndPassword))StringUtils。isEmpty(template。opsForValue()。get(sessionId))){提示无权访问response。setCharacterEncoding(UTF8);response。setContentType(charsetutf8);PrintWriteroutresponse。getWriter();out。print(对不起,你无权访问!);out。flush();out。close();}else{正常处理请求filterChain。doFilter(servletRequest,servletResponse);}}Overridepublicvoiddestroy(){}}七天免登陆 会话期的sessionId,关闭浏览器后就失效了,所以就会退出浏览器后就需要重新登陆,有些情况我们并不想这样,我们想实现七天免登陆,这时候就需要自定义token,并且存放在cookie 登陆拦截器web登录拦截器ComponentpublicclassLoginFilterimplementsFilter{静态资源为防止缓存,加上时间戳标志privatestaticfinalStringSTATICTAILAutowiredprivateStringRedisTOverridepublicvoidinit(FilterConfigfilterConfig){}OverridepublicvoiddoFilter(ServletRequestservletRequest,ServletResponseservletResponse,FilterChainfilterChain)throwsIOException,ServletException{HttpServletRequestrequest(HttpServletRequest)servletRHttpServletResponseresponse(HttpServletResponse)servletRStringrequestURIrequest。getRequestURI();无需登录即可访问的接口,登陆页面、登陆请求if(requestURI。contains(loginindex)requestURI。contains(loginlogin)){正常处理请求filterChain。doFilter(servletRequest,servletResponse);}静态资源if(requestURI。contains(。js)requestURI。contains(。css)requestURI。contains(。json)requestURI。contains(。woff2)requestURI。contains(。ttf)requestURI。contains(。ico)requestURI。contains(。png)requestURI。contains(。jpg)requestURI。contains(。gif)){检查是否有防缓存时间戳StringqueryStrrequest。getQueryString();if(StringUtils。isEmpty(queryStr)!queryStr。trim()。contains(STATICTAIL)){response。sendRedirect(requestURI?STATICTAILSystem。currentTimeMillis());}正常处理请求filterChain。doFilter(servletRequest,servletResponse);}剩下的要检查redis缓存Sfor(Cookiecookie:request。getCookies()){if(TOKEN。equals(cookie。getName())){tokencookie。getValue();}}Stringredistemplate。opsForValue()。get(token);if(StringUtils。isEmpty(redis)){重定向登录页面response。sendRedirect(loginindex?urlrequestURI);}如果都不符合,正常处理请求filterChain。doFilter(servletRequest,servletResponse);}Overridepublicvoiddestroy(){}} 登陆成功,设置cookiepublicResultModelUserVologin(UserVouserVo){此处省略查询操作。。。if(true){HttpServletResponseresponse((ServletRequestAttributes)RequestContextHolder。currentRequestAttributes())。getResponse();设置Redis,有效时长:7天StringuuidUUID。randomUUID()。toString();template。opsForValue()。set(uuid,userVo。getAccountNo());template。expire(uuid,76060,TimeUnit。SECONDS);设置cookie,有效时长:7天CookiecookienewCookie(TOKEN,uuid);cookie。setPath();cookie。setMaxAge(7246060);response。addCookie(cookie);returnResultModel。of(userVo,true,登录成功);}returnResultModel。of(null,false,用户名或密码错误);} 推出登陆,销毁cookiepublicResultModelUserVologout(){HttpServletRequestrequest((ServletRequestAttributes)RequestContextHolder。currentRequestAttributes())。getRequest();HttpServletResponseresponse((ServletRequestAttributes)RequestContextHolder。currentRequestAttributes())。getResponse();Sfor(Cookiecookie:request。getCookies()){if(TOKEN。equals(cookie。getName())){tokencookie。getValue();cookie。setValue(null);cookie。setPath();cookie。setMaxAge(0);立即销毁cookieresponse。addCookie(cookie);}}template。delete(token);returnResultModel。of(null,true,操作成功!);}代码开源 代码已经开源、托管到我的GitHub、码云: GitHub:https:github。comhuanziqchspringCloud 码云:https:gitee。comhuanziqchspringCloud版权声明 作者:huanziqch 出处:https:www。cnblogs。comhuanziqch 若标题中有转载字样,则本文版权归原作者所有。若无转载字样,本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。