網關服務的做用:html
身份認證、路由服務、爲前端服務的後端—數據聚合前端
由運維人員手動維護路由規則和服務實例列表是很是費工夫的且容易出錯。redis
好比將多個服務的數據聚合在一塊兒返回給前端spring
爲了解決上面的架構問題,API網關的概念應運而生,它的定義相似於面向對象設計模式中的Facade模式,它的存在就像是整個微服務架構系統的門面同樣,全部的外部客戶端訪問都須要通過它來進行調度和過濾。json
由它來實現請求路由、負載均衡、校驗過濾等功能,以及與服務治理框架的結合,請求轉發的熔斷機制、服務的聚合等。後端
SpringCloud Zuul組件能很是好的解決這些問題,在服務路由的時候,咱們看它如何方便的解決了這個問題。設計模式
建立一個API服務網關工程api
簡單使用:瀏覽器
一、添加依賴緩存
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> </dependency>
二、配置文件
zuul.routes.api-a-url.path=/hello/** zuul.routes.api-a-url.url=http://localhost:9000/
全部符合/hello/**規則的訪問都將被路由轉發到http://localhost:9000/地址上,其中api-a-url是路由的名字,能夠任意定義。
三、啓動類
@SpringBootApplication @EnableZuulProxy public class GatewayServiceZuulApplication { public static void main(String[] args) { SpringApplication.run(GatewayServiceZuulApplication.class, args); } }
啓動類添加@EnableZuulProxy
,支持網關路由。
面向服務的路由
實際上上面那種方式一樣須要運維人員花費大量的時間來維護各個路由path和url的關係。爲了解決這個問題,Zuul實現了於Eureka的無縫結合,咱們可讓路喲的path不是映射具體的url,而是讓它映射到某個具體的服務,而
具體的url則交給Eureka的服務發現機制去自動維護。
一、添加依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency>
增長spring-cloud-starter-eureka
包,添加對eureka的支持。
二、配置文件
配置修改成:
spring.application.name=gateway-service-zuul server.port=8888 zuul.routes.api-a.path=/producer/** zuul.routes.api-a.serviceId=spring-cloud-producer eureka.client.serviceUrl.defaultZone=http://localhost:8000/eureka/
項目採起方案
因爲項目採起的並非先後端分離的架構,全部的請求到達API 服務網關,zuul進行路由,但是並不能將各服務返回頁面的進行聚合再返回給瀏覽器。
而是採用了一種次等的策略,將全部的靜態資源放在API網關中,API網關接收請求,調用各個服務接口,將返回數據的數據進行聚合,而後給API網關的頁面進行渲染。
這裏身份認證JWT能夠單獨做爲一個認證服務被調用。基於JWT的token身份認證方案
以一個登錄請求爲例:
API網關的UserController中的方法:
@RequestMapping(value="/accounts/signin",method={RequestMethod.POST,RequestMethod.GET}) public String loginSubmit(HttpServletRequest req){ String username = req.getParameter("username"); String password = req.getParameter("password"); if (username == null || password == null) { req.setAttribute("target", req.getParameter("target")); return "/user/accounts/signin"; } User user = accountService.auth(username, password); if (user == null) { return "redirect:/accounts/signin?" + "username=" + username + "&" + ResultMsg.errorMsg("用戶名或密碼錯誤").asUrlParams(); }else { UserContext.setUser(user); return StringUtils.isNotBlank(req.getParameter("target")) ? "redirect:" + req.getParameter("target") : "redirect:/index"; } } }
API網關的UserService中的方法
public User auth(String username, String password) { if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) { return null; } User user = new User(); user.setEmail(username); user.setPasswd(password); try { user = userDao.authUser(user); } catch (Exception e) { return null; } return user; }
API網關的UserDao中的方法
@HystrixCommand public User authUser(User user) { String url = "http://" + userServiceName + "/user/auth"; ResponseEntity<RestResponse<User>> responseEntity = rest.post(url, user, new ParameterizedTypeReference<RestResponse<User>>() {}); RestResponse<User> response = responseEntity.getBody(); if (response.getCode() == 0) { return response.getResult(); }{ throw new IllegalStateException("Can not add user"); } }
這裏的方法添加了@HystrixCommand用於進行服務的熔斷,這個後面會介紹它。
通過RestTemplate攔截請求,轉發到某個服務實例上。注意它返回的是ResponseEntity<T>泛型對象,其中T是由ParameterizedTypeReference<T>中的T指定。
responseEntity.getBody()就能獲取到實際返回的對象。
User-Service服務UserController中的auth方法:
注意全部的服務API返回的數據都是RestResponse的json數據,RestResponse包含狀態碼,狀態信息,返回的數據
RestResponse
public class RestResponse<T> { private int code; private String msg; private T result; public static <T> RestResponse<T> success() { return new RestResponse<T>(); } public static <T> RestResponse<T> success(T result) { RestResponse<T> response = new RestResponse<T>(); response.setResult(result); return response; } public static <T> RestResponse<T> error(RestCode code) { return new RestResponse<T>(code.code,code.msg); } public RestResponse(){ this(RestCode.OK.code, RestCode.OK.msg); } public RestResponse(int code,String msg){ this.code = code; this.msg = msg; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public T getResult() { return result; } public void setResult(T result) { this.result = result; } @Override public String toString() { return "RestResponse [code=" + code + ", msg=" + msg + ", result=" + result + "]"; } }
@RequestMapping("auth") public RestResponse<User> auth(@RequestBody User user){ User finalUser = userService.auth(user.getEmail(),user.getPasswd()); return RestResponse.success(finalUser);
}
User-Service服務UserService中的auth方法:
/** * 校驗用戶名密碼、生成token並返回用戶對象 * @param email * @param passwd * @return */ public User auth(String email, String passwd) { if (StringUtils.isBlank(email) || StringUtils.isBlank(passwd)) { throw new UserException(Type.USER_AUTH_FAIL,"User Auth Fail"); } User user = new User(); user.setEmail(email); user.setPasswd(HashUtils.encryPassword(passwd)); //user.setEnable(1); List<User> list = getUserByQuery(user); if (!list.isEmpty()) { User retUser = list.get(0); onLogin(retUser); return retUser; } throw new UserException(Type.USER_AUTH_FAIL,"User Auth Fail"); } //生成token的操做 private void onLogin(User user) { //最後一個是時間戳 String token = JwtHelper.genToken(ImmutableMap.of("email", user.getEmail(), "name", user.getName(),"ts",Instant.now().getEpochSecond()+"")); renewToken(token,user.getEmail()); user.setToken(token); } //從新設置緩存過時時間 private String renewToken(String token, String email) { redisTemplate.opsForValue().set(email, token); redisTemplate.expire(email, 30, TimeUnit.MINUTES); return token; }