1. 程式人生 > >[良心推薦] 客戶管理系統ServiceComb微服務化實戰-PartI

[良心推薦] 客戶管理系統ServiceComb微服務化實戰-PartI

     在今年的LC3大會上,ServiceComb展臺所展示的demo視訊“30分鐘開發雛形CRM應用”引起了參會者的廣泛關注,大家紛紛對其背後的技術表現出濃厚的興趣。本文將從房地產企業的客戶管理管理場景入手,使用領域驅動設計,深入技術細節,詳解如何快速開發落地一個微服務化的客戶管理系統。

牛刀小試

開啟瀏覽器,輸入地址http://start.servicecomb.io/開啟SERVICECOMB SPRING INITIALIZR,修改Project Metadata中的Group,Artifact和ServiceComb Parameters中的ServiceCenter Address,Governance等,點選GenerateProject,解壓生成下載的demo.zip。

執行它也很簡單,使用IDE開啟專案,DEBUG -> Application.java,或在命令列:

稍等微服務啟動就緒,開啟瀏覽器輸入http://localhost:9080/hello驗證一下:

是不是非常輕鬆呢?

腳手架

在建築領域,腳手架是施工現場為方便工人操作並解決垂直和水平運輸而搭設的各種支架以及平臺。

在軟體開發領域,它引申為預提供一些基礎框架程式碼加速開發過程,避免從零開始構建專案。使用者只需要依據需求場景選擇合適的腳手架,然後填充定製的業務邏輯即可,不必再去處理一些基礎功能,例如資料庫連線、日誌實現、RPC傳輸等。

微服務框架一般都會提供腳手架功能,例如Spring,提供了SPRING INITIALIZR;ServiceComb基於SPRING INITIALIZR,提供了更具優勢的特性:

  1. 生成的專案除了在POM中自動新增必要的依賴,還會提供Producer和Consumer示例程式碼(Hello World);

  2. 會進一步提供Edge ServerAuthcation Server等更貼近業務的腳手架專案,讓使用者能快速構建體系完整的微服務系統。

那麼什麼叫一個完整的微服務系統呢?我們可以拿一個具體的場景做例子,會更有感覺:

場景:地產CRM

您經營著一家房地產開發商,銷售房產,迫切需要一套銷售系統,考慮到微服務的優勢,您決定使用微服務的方式構建系統;主要的業務流程也非常簡單:使用者前來購買購買產品(房產),首先需要登記使用者資訊,並繳納一定數量的定金,待交易當日,挑選心儀的產品(房產),支付尾款,完成交易。

1、使用DDD指導地產CRM系統的設計

微服務系統的設計方面,領域驅動設計(Domain-Driven Design,DDD)是一個從業務出發的好選擇,它由Eric Evans提出,是一種全新的系統設計和建模方法,這裡的模型指的就是領域模型(DomainModel)。領域模型通過聚合(Aggregate)組織在一起,聚合間有明顯的業務邊界,這些邊界將領域劃分為一個個限界上下文(Bounded Context)。Martin Fowler對它們都有詳細的解讀

理論概念都搞清楚了,那麼怎麼來找模型和聚合呢?一個非常流行的方法就是Event Storming,它是由AlbertoBrandolini發明,經歷了DDD社群和很多團隊的實踐,也是一種非常有參與感的團隊活動:

上圖就是我們對地產CRM這個場景使用Event Storming探索的結果,現在我們能夠將限界上下文清晰的梳理出來:

提示:Event Storming是一項非常有創造性的活動,也是一個持續討論和反覆改進的過程,不同的團隊關注的核心域(Core Domain)不同,得到的最終結果也會有差異。我們的目的是為了演示完整的微服務系統構建的過程,並不涉及商業核心競爭力方面的探討,因此沒有Core Domain和Sub Domain之類的偏重。

2、將分析成果轉化為方案域設計

當我們完成所有的限界上下文的識別後,可以直接將它們落地為微服務:

  1. 使用者服務:提供使用者資訊管理服務,這裡儲存這使用者的賬號和密碼,負責登入和認證;

  2. 產品(房產)服務:提供產品管理服務,儲存著房產的資訊諸如價格、是否已售出等資訊;

  3. 支付服務:提供交易時支付服務,模擬對接銀行支付定金,以及購房時支付尾款;

   由於完成一筆交易是一個複雜的流程,與這三個微服務都有關聯,因此我們引入了一個複合服務——交易服務:

交易服務:提供產品交易服務。它通過編排呼叫將整個交易流程串起來,交易服務中有兩個流程:

  • 定金支付
    • Step1:通過使用者服務驗證使用者身份;
    • Step2:通過支付服務請求銀行扣款,增加定金賬號內的定金;
  • 購房交易
    • Step1:通過使用者服務驗證使用者身份;
    • Step2:通過資源服務確定使用者希望購買的資源(房產)尚未售出;
    • Step3:通過資源服務標記目標資源(房產)已售出;
    • Step4:通過支付服務請求扣減定金賬號內的定金,以及銀行扣剩下的尾款;

最後兩個步驟需要保證事務一致性,其中Step4包含兩個扣款操作。

之後,我們引入Edge服務提供統一入口:

Edge服務:很多時候也被稱為API閘道器(API Gateway),負責集中認證、動態路由等等;

    提示:Edge服務需要依賴服務註冊-發現機制,因此同時匯入了ServiceCenter。

最後還需要提供UI:

前端UI(同樣以微服務方式提供):使用者互動介面;

至此,DDD設計地產CRM的工作就結束了。

快速實現客戶關係管理系統的使用者服務

1、使用者微服務並不簡單

使用者微服務是所有系統中不可或缺的部分,它承載了認證和授權等核心功能——無論是登入一個網站、還是開啟一個APP,當涉及到需要身份識別後才能夠執行的操作,都需要使用者微服務把關。例如觀看視訊網站上的視訊,匿名使用者會插播廣告,如果希望遮蔽廣告,則需要登入併購買VIP會員,登入即是身份認證的過程,而VIP遮蔽廣告即是授權的過程。

認證

認證不僅僅是一次性驗證使用者名稱和密碼的過程,還需要能反覆使用認證的結果,確保後繼所有操作都是合法的,這就涉及到“有狀態”,但HTTP是一個無狀態協議,如何能夠將登入成功後的認證資訊與後繼的請求關聯起來呢?

我們非常熟悉的做法是使用Session或Cookie:

  • Session儲存在服務端,因此具備良好的防篡改能力,但弊端是使服務有狀態,微服務系統中,同一個微服務會依據系統壓力的大小彈性伸縮出多個執行例項負載均衡,跨例項訪問會狀態丟失。

  • Cookie儲存在客戶端,它正好與Session相反,優勢是服務不必保持狀態,但弊端是客戶比較容易的篡改Cookie資訊,例如修改過期時間以逃避驗證,而且瀏覽器對Cookie也有較多限制。

那麼,如何兼顧這兩方面的需求呢?Token就是一個比較好的解決方案。

Token中文翻譯為令牌,它將登入認證後的資訊簽名後返回,服務端不儲存,客戶端請求的時候將認證的完整資訊附帶上提供給服務端驗籤,簽名可以保證資訊不被篡改。瞭解了了解Token的原理,自然要關注Token的格式,JWT就是這樣一個基於JSON的開放標準RFC-7519

JWT (Java Web Token)規範

簡而言之JWT規範由三部分構成:

1、Header: 宣告Token的型別也就是JWT,以及加密演算法,例如:

{

  "typ":"JWT",

  "alg":"HS256"

}

2、Playload:存放有效資訊,既包含標準簽發者、使用者、簽發時間、過期時間,唯一標識等資訊;也可以存放使用者自定義的宣告資訊,例如許可權控制相關的內容,例如:

{

  "sub":"1234567890",

  "name":"YangYongZheng",

  "iat":1516239022

}

3、Signature:簽名信息,包含Header和Playload的原始資訊(Base64編碼過)以及簽名過後的資訊。

授權

授權的本意是指將完成某項工作所必須的權力授給下屬人員,在軟體系統中往往引申為使人或角色具備訪問特定資源或更改行為的許可。例如之前提到的VIP遮蔽廣告,即是視訊網站允許播放終端在特定的帳號登入後跳過廣告播放環節(行為)的許可。

授權系統比較常見的做法有ACL和RBAC:

  • ACL:ACL全稱Access Control List,它是以受控資源為核心,每一個受控資源,都有一個許可權控制列表記錄哪些使用者或角色對這項資源執行具體操作(也被稱為授權點)的許可權設定,例如查詢(可見)、修改、刪除等等。Windows中的檔案系統安全即是一個經典的ACL實現案例:

  • RBAC:RBAC全稱Role Based Access Control,與ACL相比,它以角色為核心,許可權落地在角色上,不為特定使用者授權。它的優勢是大幅簡化了使用者與許可權的管理,在受控物件不多或控制粒度要求不高(例如介面訪問控制)的場景下非常適用。

由於微服務系統的許可權控制主要是介面訪問控制上,並且多采用使用者組方式組織使用者,因此RBAC是比較流行的做法。

2、實現使用者微服務

第一步:建立微服務專案

使用SERVICECOMBSPRING INITIALIZR建立使用者微服務,建立完畢後使用IDEA或Eclipse開啟專案,我們刪掉HelloImpl和HelloConsumer,之後新增自己的實現。

第二步:使用MySQL持久化使用者資訊

使用者微服務需要持久化使用者資訊,我們使用MySQL資料庫,ORM使用SpringData JPA:

引入依賴

<dependency>

  <groupId>mysql</groupId>

  <artifactId>mysql-connector-java</artifactId>

</dependency>

<dependency>

  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-starter-data-jpa</artifactId>

</dependency>

定義儲存User資訊的UserEntity實體

@Entity

@Table(name ="T_User")

public class UserEntity{

  @Id

  private String name;

  private String password;



  public String getName(){

    return name;

  }



  public void setName(String name){

    this.name = name;

  }



  public String getPassword(){

    return password;

  }



  public void setPassword(String password){

    this.password = password;

  }



  public UserEntity(){

  }



  public UserEntity(String name, String password){

    this.name = name;

    this.password = password;

  }

}

在CodeFist模式下,Spring Data JPA會在資料庫中自動建立T_User表與此實體對映。

實現UserEntity實體的Repository

我們繼承JPA的PagingAndSortingRepository來實現ORM操作:

@Repository

public interface UserRepository extends PagingAndSortingRepository<UserEntity, Long>{

 UserEntity findByName(String name);

}

配置資料庫連線

在專案的resources目錄下新增application.properties檔案,寫入資料庫連線資訊:

spring.datasource.url=jdbc:mysql://localhost:3306/user_db?useSSL=false

spring.datasource.username=root

spring.datasource.password=pwd

spring.jpa.hibernate.ddl-auto=update

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect

提示:關於Spring Data JPA的更多資料請參見這篇文件 ,為了能夠簡化依賴的引入我們實際上使用的是Spring Boot JPA Starter,詳細的例子請參見這篇文件 

第三步:實現JWT認證

定義JWT介面

public interface TokenStore{

 String generate(String userName);

  booleanvalidate(String token);

}

generate用於生成Token,validate用於驗證Token是否正確。

實現TokenStore

我們使用jjwt提供的JWT實現,建立JwtTokenStore類,繼承TokenStore介面,並重寫方法:

@Component

@Component

public class JwtTokenStore implements TokenStore{

  private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenStore.class);

  private final String secretKey;

  private final int secondsToExpire;



  public JwtTokenStore(){

    this.secretKey ="someSecretKeyForAuthentication";

    this.secondsToExpire =60*60*24;

  }



  public JwtTokenStore(String secretKey,int secondsToExpire){

    this.secretKey = secretKey;

    this.secondsToExpire = secondsToExpire;

  }



  @Override

  public String generate(String userName){

    return Jwts.builder().setSubject(userName)

       .setExpiration(Date.from(ZonedDateTime.now().plusSeconds(secondsToExpire).toInstant()))

       .signWith(HS512, secretKey).compact();

  }



  @Override

  public boolean validate(String token){

    try{

      return StringUtils.isNotEmpty(Jwts.parser()

      .setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject());

    }catch(JwtException| IllegalArgumentException e){

     LOGGER.info("validateToken token : "+ token +" failed", e);

    }

    returnfalse;

  }

}

第四步:實現使用者服務

定義UserService介面

public interface UserService{

 ResponseEntity<Boolean>logon(UserDTO user);

 ResponseEntity<Boolean>login(UserDTO user);

}

logon用於新使用者註冊,login用於使用者登入驗證,UserDTO用於引數傳遞:

public class UserDTO{

  private String name;

  private String password;

  public String getName(){

    return name;

  }

  public String getPassword(){

    return password;

  }

  public UserDTO(){

  }

  public UserDTO(String name, String password){

    this.name = name;

    this.password = password;

  }

}

實現併發布UserService

建立UserServiceImpl,繼承UserService介面:

@RestSchema(schemaId ="user")

@RequestMapping(path ="/")

public class UserServiceImpl implements UserService{

  private final UserRepository repository;



  private final TokenStore tokenStore;



  @Autowired

  public UserServiceImpl(UserRepository repository, TokenStoretokenStore){

    this.repository = repository;

    this.tokenStore = tokenStore;

  }



  @Override

  @PostMapping(path ="logon")

  public ResponseEntity<Boolean>logon(@RequestBody UserDTOuser){

    if(validateUser(user)){

     UserEntity dbUser = repository.findByName(user.getName());

      if(dbUser == null){

       UserEntity entity =new UserEntity(user.getName(), user.getPassword());

       repository.save(entity);

       return new ResponseEntity<>(true, HttpStatus.OK);

      }

      throw new InvocationException(BAD_REQUEST,"user namehad exist");

    }

    throw new InvocationException(BAD_REQUEST,"incorrectuser");

  }



  @Override

  @PostMapping(path ="login")

  public ResponseEntity<Boolean>login(@RequestBody UserDTOuser){

    if(validateUser(user)){

     UserEntity dbUser = repository.findByName(user.getName());

      if(dbUser != null){

       if(dbUser.getPassword().equals(user.getPassword())){

         String token = tokenStore.generate(user.getName());

         HttpHeaders headers =generateAuthenticationHeaders(token);

         //addauthentication header

         return new ResponseEntity<>(true, headers, HttpStatus.OK);

       }

       throw new InvocationException(BAD_REQUEST,"wrongpassword");

      }

      throw new InvocationException(BAD_REQUEST,"user namenot exist");

    }

    throw new InvocationException(BAD_REQUEST,"incorrectuser");

  }



  private boolean validateUser(UserDTO user){

    return user != null &&         StringUtils.isNotEmpty(user.getName())&& StringUtils.isNotEmpty(user.getPassword());

  }



  private HttpHeaders generateAuthenticationHeaders(String token){

   HttpHeaders headers =newHttpHeaders();

   headers.add(AUTHORIZATION, token);

    return headers;

  }

}

登入成功後,會從TokenStore生成Token,並將其寫入Key為AUTHORIZATION的Header。

由於我們允許任何使用者註冊和登入,所以目前還沒有授權的需求,經過上面四步,具有基本註冊和登入功能的使用者微服務就構建好了。

3、驗證實現的使用者服務

啟動使用者微服務,我們先註冊一個賬號:

顯示註冊成功,現在我們使用這個賬號登入:

返回登入成功,Response中已經包含了AUTHORIZATIONHeader,後繼的所有請求都需要使用這個Token值進行合法認證。

至此,實現客戶關係管理系統的使用者服務工作就結束了,現在我們會將目光轉移到Edge服務,通過Edge服務作為微服務呼叫的統一入口,在它之上構建統一認證,應對海量級呼叫的挑戰。

開發高效能邊緣服務

1、什麼是邊緣服務(Edge Service)

邊緣服務也是一個微服務,微服務化系統通常使用邊緣服務(Edge Service)作為所有其它微服務的統一入口,因此它也常常會被稱為APIGateway,使用邊緣服務的好處有如下幾點:

  • 動態路由:動態配置URL地址與微服務之間的對應關係,便於擴充套件,以及實現版本灰度釋出等;

  • 統一認證:在入口處進行訪問認證,避免需要在所有的微服務中都承載重複的認證機制;

  • 集中監控:與統一認證類似,在邊緣服務對入口呼叫進行監控,容易統計流量資訊。

2、邊緣服務的作用和原理

我們先來看不使用邊緣服務,UI直接呼叫使用者服務的場景:

可以看出這種呼叫方式,UI缺乏一定的靈活性,體現在:

  • UI的實現綁定了Chassis的程式語言Java,無法使用PHP等其它前端技術開發;

  • UI訪問微服務的路徑無法動態配置,如果作為後端的微服務系統發生調整,則UI很可能需要修改;

  • UI很容易混入複合(編排)呼叫的邏輯,使得結構變得複雜難以維護。

我們再看引入邊緣服務後,UI如何通過邊緣服務呼叫使用者服務:

Edge服務將在9090埠上接受http rest呼叫,我們設計了下面的轉發規則:

http://{edge-host-name}:9090/{ServiceComb微服務Name}/{服務路徑&引數}

使用者微服務名(service_description.name)是user-service,因此login呼叫URL:cse://user-service/login可以通過http://{edge-host-name}:9090/user-service/login 訪問。

如此一來,微服務名成為了路徑的一部分,http協議的hostname和port將固定指向Edge服務保持不變,靈活性大大增加了。

到此我們還可以再做一點點改進,引入一個自定義配置edge.routing-short-path.{簡稱},對映微服務名:

edge:

  routing-short-path:

    user: user-service

上面的配置代表:http://{edge-host-name}:9090/user/login 等效於:http://{edge-host-name}:9090/user-service/login,如此一來:

  • URL能夠更加簡潔;

  • 當微服務名發生變化,只需要調整對應的配置,不需要更改前端UI路徑程式碼。

3、實現邊緣服務

第一步:引入Edge Core依賴

<dependency>

  <groupId>org.apache.servicecomb</groupId>

  <artifactId>edge-core</artifactId>

</dependency>

Copy

第二步:編寫排程器Dispatcher

Edge服務的核心就是排程器Dispatcher,ServiceComb Edge Core中的Dispatcher基於高效能的Vertx Reactive,輕鬆應對百萬量級API請求的挑戰;只需要繼承AbstractEdgeDispatcher抽象類,新增對應的邏輯即可:

public class EdgeDispatcher extends AbstractEdgeDispatcher{

  private static final Logger LOGGER = LoggerFactory.getLogger(EdgeDispatcher.class);



 //此Dispatcher的優先順序,Order級越小,路由策略優先順序越高

  public int getOrder(){

    return 10000;

  }



  //初始化Dispatcher的路由策略

  public void init(Router router){

   //捕獲 {ServiceComb微服務Name}/{服務路徑&引數} 的URL

   String regex ="/([^\\\\/]+)/(.*)";

   router.routeWithRegex(regex).handler(CookieHandler.create());

   router.routeWithRegex(regex).handler(createBodyHandler());

   router.routeWithRegex(regex).failureHandler(this::onFailure).handler(this::onRequest);

  }



  //處理請求,請注意

  private void onRequest(RoutingContext context){

    Map<String, String> pathParams = context.pathParams();

    //從匹配的param0拿到{ServiceComb微服務Name}

    final String service = pathParams.get("param0");

   //從匹配的param1拿到{服務路徑&引數}

   String path ="/"+ pathParams.get("param1");



    //還記得我們之前說的做出一點點改進嗎?引入一個自定義配置edge.routing-short-path.{簡稱},對映微服務名;如果簡稱沒有配置,那麼就認為直接是微服務的名

    final String serviceName = DynamicPropertyFactory.getInstance()

       .getStringProperty("edge.routing-short-path."+ service, service).get();



    //建立一個Edge轉發

   EdgeInvocation edgeInvocation =new EdgeInvocation();

    //允許接受任意版本的微服務例項作為Provider,未來我們會使用此(設定版本)能力實現灰度釋出

   edgeInvocation.setVersionRule(DefinitionConst.VERSION_RULE_ALL);

   edgeInvocation.init(serviceName, context, path, httpServerFilters);

   edgeInvocation.edgeInvoke();

  }

}

第三步:載入排程器Dispatcher

ServiceComb Edge使用SPI(ServiceProvider Interface)的方式載入已經編寫好的排程器Dispatcher,在resources目錄下建立META-INF.services/org.apache.servicecomb.transport.rest.vertx.VertxHttpDispatcher配置檔案,寫入上一步EdgeDispatcher的類全名:

{EdgeDispatcher的包名}.EdgeDispatcher

第四步:配置microservice.yaml

邊緣服務本身也是一個微服務,同樣需要配置microservice.yaml:

APPLICATION_ID: scaffold

service_description:

  name: edge-service

  version: 0.0.1

servicecomb:

  service:

    registry:

     #配置ServiceCenter使得Edge能夠發現其他微服務

      address: http://127.0.0.1:30100

  #配置Rest Endpoint

  rest:

    address: 0.0.0.0:9090



#自定義的簡稱機制配置(這是我們自行擴充套件實現的)

edge:

  routing-short-path:

    user: user-service

提示:

  1. 除了配置Rest Endpoint,我們也支援配置Highway Endpoint,但Highway Endpoint只支援ServiceComb開發的微服務呼叫;

  2. microservice.yaml中沒有配置Handler,Edge支援所有Consumer端Handler,不支援Producer端Handler,呼叫鏈原理如下:

4、驗證邊緣服務

啟動使用者微服務和Edge服務,使用Postman註冊一個使用者:

成功,現在我們使用新註冊的使用者名稱ldg登入:

同樣成功,並在Response中已經包含了正確的AUTHORIZATIONHeader。

5、效能比拼

ServiceComb JavaChassis也支援整合Netflix Zuul作為閘道器服務,我們做了一次效能比較,使用ServiceComb Edge作為閘道器吞吐能力大幅優於Netflix Zuul,效能測試專案原始碼在這裡

擴充套件邊緣服務支援統一認證

1、設計思路

正如前面提到的,統一認證的目的是在Edge入口處進行訪問認證,避免需要在所有的微服務中都承載重複的認證機制,因此:

  1. 我們先要將認證功能作為一個獨立的Procuder釋出出來,使Edge服務能夠隨時認證Token,我們將其命名為AuthenticationService,放在使用者服務中;

  2. 將無需認證的訪問請求識別出來,包括:

功能

描述

login

登入驗證,通過後為使用者生成Token

logon

新使用者註冊

除此之外其他業務請求都需要做Token認證;

  1. Edge服務轉發訪問請求之前,對需要認證的請求先做統一認證,認證通過之後才轉發,我們使用HttpServerFilter擴充套件這個能力:

統一認證流程時序圖為:

2、實現統一認證

第一步:釋出認證服務

定義AuthenticationService

public interface AuthenticationService{

 String validate(String token);

}

Copy

實現併發布AuthenticationService

@RestSchema(schemaId ="authentication")

@RequestMapping(path ="/")

public class AuthenticationServiceImpl implements AuthenticationService{

  private final TokenStore tokenStore;



  @Autowired

  publicAuthenticationServiceImpl(TokenStore tokenStore){

    this.tokenStore = tokenStore;

  }



  @Override

  @GetMapping(path ="validate")

  public String validate(String token){

   String userName = tokenStore.validate(token);

    if(userName == null){

      throw new InvocationException(BAD_REQUEST,"incorrecttoken");

    }

    return userName;

  }

}





第二步:實現統一認證AuthenticationFilter

public class AuthenticationFilter implements HttpServerFilter{

   private final RestTemplate template =RestTemplateBuilder.create();

   private static final String USER_SERVICE_NAME ="user-service";

   public static final String EDGE_AUTHENTICATION_NAME ="edge-authentication-name"; 

  private static final Set<String>NOT_REQUIRED_VERIFICATION_USER_SERVICE_METHODS =new HashSet<>(

     Arrays.asList("login","logon","validate"));



  @Override

  public int getOrder(){

    return0;

  }



  @Override

  public Response afterReceiveRequest(Invocation invocation,HttpServletRequestEx httpServletRequestEx){

    if(isInvocationNeedValidate(invocation.getMicroserviceName(), invocation.getOperationName())){

     String token = httpServletRequestEx.getHeader(AUTHORIZATION);

      if(StringUtils.isNotEmpty(token)){

       String userName = template

           .getForObject("cse://"+ USER_SERVICE_NAME +"/validate?token={token}", String.class, token);

       if(StringUtils.isNotEmpty(userName)){

         //Add header

         invocation.getContext().put(EDGE_AUTHENTICATION_NAME, userName);

       }else{

         return Response

             .failResp(new InvocationException(Status.UNAUTHORIZED,"authentication failed, invalid token"));

       }

      }else{

       return Response.failResp(

           new InvocationException(Status.UNAUTHORIZED,"authenticationfailed, missing AUTHORIZATION header"));

      }

    }

    return null;

  }



  private boolean isInvocationNeedValidate(String serviceName, String operationPath){

    if(USER_SERVICE_NAME.equals(serviceName)){

      for(String method :NOT_REQUIRED_VERIFICATION_USER_SERVICE_METHODS){

       if(operationPath.startsWith(method)){

         return false;

       }

      }

    }

    return true;

  }

}

Copy

別忘了通過SPI機制載入它,在resources\META-INF\services目錄中建立org.apache.servicecomb.common.rest.filter.HttpServerFilter檔案:

org.apache.servicecomb.scaffold.edge.filter.AuthenticationFilter

第三步:在使用者微服務中增加修改密碼的功能用於驗證

現有的login和logon都無需認證,因此我們在使用者微服務中增加需要認證的修改密碼的功能用於驗證統一認證。

在UserService中新增修改密碼

public interface UserService{

 ResponseEntity<Boolean>logon(UserDTO user);



 ResponseEntity<Boolean>login(UserDTO user);

 //需要認證的修改密碼功能

 ResponseEntity<Boolean>changePassword(UserUpdateDTO userUpdate);

}

在UserServiceImpl中實現修改密碼

@Override

@PostMapping(path ="changePassword")

public ResponseEntity<Boolean>changePassword(@RequestBody UserUpdateDTO userUpdate){

  if(validateUserUpdate(userUpdate)){

   UserEntity dbUser = repository.findByName(userUpdate.getName());

    if(dbUser != null){

      if(dbUser.getPassword().equals(userUpdate.getOldPassword())){

       dbUser.setPassword(userUpdate.getNewPassword());

       repository.save(dbUser);

       return newResponseEntity<>(true, HttpStatus.OK);

      }

      throw new InvocationException(BAD_REQUEST,"wrongpassword");

    }

    throw new InvocationException(BAD_REQUEST,"user namenot exist");

  }

  throw new InvocationException(BAD_REQUEST,"incorrectuser");

}

3、驗證實現的統一認證

確認AuthenticationFilter在Edge服務中成功載入

在Edge服務的啟動日誌中能夠找到:

2018-07-13 14:38:48,756 [INFO]   1.org.apache.servicecomb.scaffold.edge.filter.AuthenticationFilter.org.apache.servicecomb.foundation.common.utils.SPIServiceUtils.loadSortedService(SPIServiceUtils.java:79)

使用者登入

使用zhengyangyong登入:

拿到的Token值為:

eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ6aGVuZ3lhbmd5b25nIiwiZXhwIjoxNTMwNjA4OTczfQ.90teWUNbypPZvds_SD7Kus_y7wLc4b6VzC_aIVg8sLItKxwQ0g4V9BDU665PlqQY5KM-mnk8y0R6ENL1T8YVFg

不帶Authorization Header請求changePassword

返回的失敗資訊是:

authenticationfailed, missing AUTHORIZATION header

使用錯誤的Token請求changePassword

返回的失敗資訊是:

authenticationfailed : InvocationException: code=400;msg=CommonExceptionData[message=incorrect token]

使用正確的Token請求changePassword

修改密碼成功。

這裡可能有疑問,使用zhengyangyong登入後,是可以通過這個Token修改其他使用者例如lidagang的密碼的,這是因為我們目前構建的validate僅檢查Token的有效性,而不做許可權檢查,基於RBAC的角色許可權管理系統將會在未來構建。

提示:

小結

本文詳細介紹瞭如何使用http://start.servicecomb.io腳手架快速構建微服務專案、使用領域驅動設計(Domain-Driven Design,DDD)設計地產CRM系統、使用Edge Service構建統一認證邊緣服務等內容。至此,一個地產客戶關係管理系統的骨架已經初步搭建起來,剩下的模組,我們將在接下來的文章裡詳細介紹

ServiceComb相關資料

官方網站

加入社群

JIRA

相關推薦

[良心推薦] 客戶管理系統ServiceComb服務化實戰-PartI

     在今年的LC3大會上,ServiceComb展臺所展示的demo視訊“30分鐘開發雛形CRM應用”引起了參會者的廣泛關注,大家紛紛對其背後的技術表現出濃厚的興趣。本文將從房地產企業的客戶管理管理場景入手,使用領域驅動設計,深入技術細節,詳解如何快速開發落地一個微服

客戶管理系統之模塊設計(八)

說了 sender windows .net children comm eve exceptio client 2,加入信息投訴和改動投訴信息模塊 關於投訴信息的加入模塊和改動模塊均使用的是一個窗口,其其差別是依據向窗口中所傳遞的參數

小程序管理系統商有什麽好處

小程序微商管理系統 小程序微商管理系統有哪些功能介紹 1、代理授權管理 通過小程序微商管理系統後臺開啟在線申請授權代理商,在線生成授權;企業通過系統後臺生成獨一無二的邀請鏈接去發展代理,下級代理商通過鏈接申請完成授權認證很好的解決了授權產品線、等級混亂問題。 2、授權信

js學習總結----crm客戶管理系統之node編寫API接口

準備 獲取 ring length urn 使用 col asc ati 具體API代碼如下 var http = require(‘http‘), url = require(‘url‘), fs = require(‘fs‘); var server

js學習總結----crm客戶管理系統之前端頁面開發及數據渲染

bmi length element || useradd attribute xxx tle exe 具體代碼如下: index.html <!DOCTYPE html> <html lang="en"> <head> <

奇蛙聯合ServiceComb服務化,打造無人機智慧控制大腦

序   南京奇蛙智慧科技有限公司,聚焦於發展工業級無人飛行器,在無人機領域有十年技術和經驗積累,其智慧控制業務在無人機領域擁有核心競爭力,貫穿端到雲的全流程,向用戶提供實時直播、遠端控制及多屏/多人互動的無人機管理和資訊共享,覆蓋公共安全、環保氣象、能源電力等領域。 奇蛙聯

簡易的客戶管理系統,實現使用者資訊的錄入及查詢!!!

package CustomerManagementSystem; public class Customer {// 描述客戶物件資訊類 //描述客戶資訊 private String name; private String sex; privat

客戶管理系統 (SSM)全配置

package cn.itcast.common.utils; import java.io.IOException; import java.util.Map; import javax.servlet.http.HttpServletRequest; import j

go案例:客戶管理系統流程 mvc模式 分層設計

下面是一個簡要的客服系統,主要是演示分層計。。 model : 資料部份: package model import "fmt" //宣告一個結構體,表示一個客戶資訊 type Customer struct{ Id int Name string

go案例:客戶管理系統流程 mvc模式 分層設計

pri 結構 mep service 循環 刪除 phone 列表 bre 下面是一個簡要的客服系統,主要是演示分層計。。 model : 數據部份: package model import "fmt" //聲明一個結構體,表示一個客戶信息 type Custom

Zoho CRM客戶管理系統的優勢有哪些?

Zoho是提供線上軟體數量最多的軟體供應商,5000多人的研發團隊,運用當下一流的技術做出最出色的軟體,CRM客戶管理系統越來越多的應用於企業辦公中,Zoho CRM客戶管理系統的優勢有哪些? 操作簡潔,功能強大 Zoho CRM客戶管理系統,介面簡潔,提供您在市場營銷、銷售和服務中所需要的

Zoho CRM客戶管理系統的幾個實用工具

Zoho CRM客戶管理系統本身是一個強大的工具,而如果能靈活運用一些實用的小技巧,會令生活更輕鬆。     巨集 巨集可以做什麼呢?它是一系列規則命令,可以用來建立任務、更新欄位、傳送郵件等。 例如,當關閉了交易,收到了付款之後,每次都要對每個客戶做同

ssm專案——CRM客戶管理系統開發準備

這個專案是學習完spring,springmvc,mybatis後為了加強知識所做的ssm專案,為CRM客戶管理系統  一、CRM專案外觀 二、資料庫準備 資料庫sql檔案可以從下方連結中下載。 三、工程搭建 工程使用spring,springmvc

JavaEE(SSM教材練習) BOOT客戶管理系統(二)——ssm框架搭建

一、匯入jar包 二、編寫配置檔案 在專案目錄下建立config資料夾,其中包括spring配置檔案、springmvc配置檔案、mybatis配置檔案、資料庫常量配置檔案、log4j配置檔案、資源配置檔案 下面一一說明: 1.資料庫常量配置檔案db.ptoperti

[Java] 原創ssm實現簡單的客戶管理系統

執行環境jdk8+tomcat8+mysql+eclipse專案技術spring+spring mvc+mybatis+bootstrap+jqueryjar包檔案jar包一併在專案裡面,匯入專案直接執行專案截圖執行截圖 http://localhost :8080/SSMB

SSM整合專案:CRM客戶管理系統

CRM專案RequestMethod類,列舉型別。MultipartFile類,配置介面的實現類。把之前的資料表拿過來修改一下用。新建資料庫,再執行sql檔案,即可匯入資料庫。整合SSM框架新建maven為什麼沒有生成資料夾啊?新建maven專案,maven軟體的安裝位置,m

那裡有外匯CRM客戶管理系統原始碼

出售一套PHP外匯CRM客戶管理系統原始碼 現一次性低價出售(包安裝 帶跟單系統 風控系統  後期維護) 原始碼主要功能: 對接MT4開戶 出入金,傳送郵件, 線上直播功能 分級代理 跟單系統 風控系統 功能齊全,原始

Java SSM 商戶管理系統 客戶管理 庫存管理 銷售報表 項目源碼

java ssm 商戶管理系統 客戶管理 庫存管理 需求分析:有個廠家,下面有很多代理商(商戶或門頭等),之前商戶進貨、庫存、銷售、客戶資料等記錄在excel表格中或者無記錄,管理比較混亂,盈利情況不明。不能有效了解店鋪經營情況和客戶跟蹤記錄廠家也不能實時了解下面代理商的經營狀況和庫存情況

JavaWeb項目------客戶關系管理系統(1)

dom dao 教學 系統 技術分享 準備 web 視圖 進行 一、前言 這個小項目源於傳智播客教學視頻,做這個小項目,目的是結合數據庫熟悉MVC模型,核心操作就是對數據庫進行一個簡單的單表查詢。 二、準備工作,搭建項目結構 1.導入原型 copy jsp頁面,作為項目的

商企業怎麽做商後臺管理系統

微商後臺管理系統 微商經過長期的發展很多的問題也在發展中不斷的暴露出來,微商怎麽解決各種痛點,微商後臺管理系統是專門為解決微商經銷商在線下單,訂單都難以結算,激勵代理商而生的,輕松解決微商企業的痛點。想了解跟多微商後臺管理系統找小編:156-0239-6317可微信。 微商企業做