1. 程式人生 > >SpringCloud框架搭建+實際例子+講解+系列五

SpringCloud框架搭建+實際例子+講解+系列五

(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測試或者前端測試: