(4)服务消费者,面向前端或者用户的服务
本模块涉及到很多知识点:比如Swagger的应用,SpringCloud断路器的使用,服务API的检查、token的校验,feign消费者的使用。大致代码框架如下:
先看下简单的配置文件application.properties
spring.application.name=mallservice-app server.port=4444 eureka.client.serviceUrl.defaultZone=http://server1:1111/eureka/,http://server2:1112/eureka/,http://server3:1113/eureka/ hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds:5000
urifilter.properties
#urllist url.filterList[0]=/acc/signup url.filterList[1]=/acc/login
面向用户的Controller类:
package com.mallapp.api; import com.common.constant.RestApiResult; import com.common.constant.ReturnCode; import com.google.gson.Gson; import com.mallapp.Security.JWTUtils; import com.mallapp.client.IAccountFeignClient; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.UUID; @Api(value="用户服务",tags = "用户服务接口") @RestController @RequestMapping("/acc") public class IAccountController { @Autowired IAccountFeignClient accountFeignClient; @ApiOperation(value="用户注册") @RequestMapping(value="signup",method = RequestMethod.POST) public RestApiResult signUp(@RequestParam String phone, @RequestParam String password){ RestApiResult restApiResult = new Gson().fromJson(accountFeignClient.signUp(phone,password),RestApiResult.class); System.out.println(restApiResult); return restApiResult; } @ApiOperation(value="用户登录") @RequestMapping(value="login",method = RequestMethod.POST) public RestApiResult login(@RequestParam String phone ,@RequestParam String password){ RestApiResult restApiResult = new Gson().fromJson(accountFeignClient.login(phone,password),RestApiResult.class); try{ System.out.println(restApiResult); if (restApiResult.isSuccess()){ String accessToken = JWTUtils.createJWT(UUID.randomUUID().toString(),(String)restApiResult.getAddmessage(),2*60*60*1000); restApiResult.setAddmessage(accessToken); } }catch (Exception ex){ ex.printStackTrace(); } return restApiResult; } }
@Autowired IAccountFeignClient accountFeignClient;
这个是服务发现用的Feign的客户端,看一下它的实现:
package com.mallapp.client; import com.mallapp.client.hystrix.AccountFeignClientHystrix; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; @FeignClient(name="ACCOUNT-SERVICE", fallback = AccountFeignClientHystrix.class) public interface IAccountFeignClient { @RequestMapping(value = "/acc/signup",method = RequestMethod.GET) public String signUp(@RequestParam(value = "phone") String phone, @RequestParam(value = "password") String password); @RequestMapping(value = "/acc/login",method = RequestMethod.POST) public String login(@RequestParam(value = "phone") String phone, @RequestParam(value = "password") String password); }
这个接口必须和服务提供端的controller类的接口完全一致,而且参数注解一定完全一致。
看下SpringCloud所说的断路器类的实现:(意义就是服务消费者端调用服务提供端的时候,调用超时或者服务器异常等,会直接通过此接口返回响应)
package com.mallapp.client.hystrix; import com.common.constant.RestApiResult; import com.common.constant.ReturnCode; import com.google.gson.Gson; import com.mallapp.client.IAccountFeignClient; import org.springframework.stereotype.Component; @Component public class AccountFeignClientHystrix implements IAccountFeignClient { @Override public String signUp(String phone, String password) { return new Gson().toJson(new RestApiResult(false, ReturnCode.SYSTEM_ERROR,"The server is busy now......")); } @Override public String login(String phone, String password) { return new Gson().toJson(new RestApiResult(false, ReturnCode.SYSTEM_ERROR,"The server is busy now......")); } }
看下所说的AOP中的前置通知、后置通知、环绕通知等实现类:
package com.mallapp.aop; import com.common.constant.RestApiResult; import com.common.constant.ReturnCode; import com.mallapp.Security.JWTUtils; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.SignatureException; import io.jsonwebtoken.UnsupportedJwtException; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.util.Iterator; import java.util.Map; @Aspect @Component public class ApiExecuteNoticeService { private final static Logger LOG = LoggerFactory.getLogger(ApiExecuteNoticeService.class); private final static String access_token = "accessToken"; /** * 方法之前执行 * @param joinPoint * @throws Exception */ @Before("execution(public * com.mallapp.api.*.*(..))") public void doBeforeInService(JoinPoint joinPoint)throws Exception{ System.out.println("Before to check the API......"); } /** * 方法之后执行 * @param joinPoint * @throws Exception */ @After("execution(public * com.mallapp.api.*.*(..))") public void AfterInService(JoinPoint joinPoint)throws Exception{ System.out.println("After to check the API......"); } /** * 环绕通知 * @param joinPoint * @return * @throws Exception */ @Around("execution(public * com.mallapp.api.*.*(..))") public RestApiResult doAroundInService(ProceedingJoinPoint joinPoint)throws Exception{ System.out.println("Around to check the API......"); RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes)requestAttributes; HttpServletRequest request = servletRequestAttributes.getRequest(); String requestPath = request.getRequestURI(); System.out.println("uri: " + requestPath); /*需要过滤不进行检查的url地址*/ // if (requestPath.contains("acc")){ // try { // return (RestApiResult)joinPoint.proceed(); // } catch (Throwable throwable) { // throwable.printStackTrace(); // } // System.out.println("url /acc does not to check."); // return null; // } Map<String,String[]> inputMap = request.getParameterMap(); Iterator<String> keyIter = inputMap.keySet().iterator(); boolean result = false; while(keyIter.hasNext()){ String currKey = keyIter.next(); String value = ((String[])inputMap.get(currKey))[0].toString(); if (!access_token.equals(currKey)){ continue; } try{ JWTUtils.parseJWT(value); System.out.println("cuurKey="+currKey+",value="+value); result = true; }catch(ExpiredJwtException ex){ ex.printStackTrace(); }catch (UnsupportedJwtException ex){ ex.printStackTrace(); }catch (MalformedJwtException ex){ ex.printStackTrace(); }catch (SignatureException ex){ ex.printStackTrace(); }catch (IllegalArgumentException ex){ ex.printStackTrace(); } } if (!result){ return new RestApiResult(false,ReturnCode.INVALID_VALUE,"token校验失败."); } try { return (RestApiResult) joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } return new RestApiResult(false,ReturnCode.SYSTEM_ERROR,"unkonwn exception"); } }
token校验所涉及到类:
package com.mallapp.Security; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import io.jsonwebtoken.*; import org.apache.tomcat.util.codec.binary.Base64; import java.util.Date; import java.util.UUID; public class JWTUtils { private final static String SECRETKEY = "OVlpXYjNwaFJYUllVbXhXTkZaR1pEQlNiVkYzWTBac1YxWkZXbE"; /** * 由字符串生成加密key */ public static SecretKey generateKsy(String keyStr){ byte[] encodeKey = Base64.decodeBase64(keyStr); SecretKey secretKey = new SecretKeySpec(encodeKey,0,encodeKey.length,"AES"); return secretKey; } /** * 创建JWT,加密过程 */ public static String createJWT(String id,String subject,long ttlMillis)throws Exception{ SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); SecretKey key = generateKsy(SECRETKEY); JwtBuilder jwtBuilder = Jwts.builder().setIssuer("").setId(id).setIssuedAt(now).setSubject(subject) .signWith(signatureAlgorithm,key); if (ttlMillis >= 0){ long expireMillis = nowMillis + ttlMillis; Date expireDate = new Date(expireMillis); jwtBuilder.setExpiration(expireDate); } return jwtBuilder.compact(); } /** * 解析JWT,解密过程 */ public static Claims parseJWT(String jwt) throws ExpiredJwtException,UnsupportedJwtException,MalformedJwtException, SignatureException,IllegalArgumentException{ SecretKey key = generateKsy(SECRETKEY); Claims claims = Jwts.parser().setSigningKey(key).parseClaimsJws(jwt).getBody(); return claims; } // public static void main(String[] args){ // try{ // String token = createJWT(UUID.randomUUID().toString(),"",20000); // System.out.println(token); // Claims claims = parseJWT(token); // System.out.println(claims.getExpiration()+" : "+claims.getExpiration().getTime()); // }catch (Exception ex){ // ex.printStackTrace(); // } // } }
UriFilterConfig类是用来接受Spring配置的xml文件的:urlifilter.properties
package com.mallapp.config; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; import java.util.Set; /** * Created by c00415904 on 2018/5/29. */ @Component @ConfigurationProperties(prefix = "url") @PropertySource(value = {"classpath:urifilter.properties"} ,ignoreResourceNotFound = true) public class UriFilterConfig { private List<String> filterList = new ArrayList<String>(); public List<String> getFilterList() { return filterList; } public void setFilter(List<String> filterList) { this.filterList = filterList; } }
Awagger2Config类用来生成在线API文档: http://127.0.0.1:4444/swagger-ui.html 4444为消费者提供的端口号
package com.mallapp.config; import io.swagger.annotations.ApiOperation; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration @EnableSwagger2 public class Awagger2Config { @Bean public Docket createRestApi(){ return new Docket(DocumentationType.SWAGGER_2).apiInfo(getApiInfo()).select() .apis(RequestHandlerSelectors.basePackage("com.mallapp.api")) .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) .paths(PathSelectors.any()) .build(); } private ApiInfo getApiInfo(){ return new ApiInfoBuilder().title("Mall App Swagger Apis").description("For mall-service 's app use") .version("V1.0").build(); } }
服务启动类:
FeignApplication
package com.mallapp; import com.common.constant.SystemConstant; import com.common.util.JedisUtil; import com.mallapp.config.UriFilterConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.feign.EnableFeignClients; import java.util.Date; @SpringBootApplication @EnableFeignClients @EnableEurekaClient @EnableDiscoveryClient public class FeignApplication implements CommandLineRunner{ @Autowired private UriFilterConfig uriFilterConfig; public static void main(String[] args){ SpringApplication.run(FeignApplication.class,args); } @Override public void run(String... strings) throws Exception { System.out.println("Begin to init data......"+new Date()); System.out.println(uriFilterConfig.getFilterList()); for(String url : uriFilterConfig.getFilterList()){ JedisUtil.SETS.sadd(SystemConstant.URL_NEED_CHECK_KEY,url); } } }
我们分别启动服务消费者和服务提供者,然后进行postman测试或者前端测试: