面試中所談的項目中的亮點

前言

我是從18年11月份入職的,一直做的是Java開發,起初和大部分人一樣都是CRUD,直到去年年底一個小項目讓我做了技術經理,雖然我在項目上受到了比較大的打擊(做己方的話如果真的遇到一個很難對付的甲方簡直讓人崩潰),但也確實讓我獲得很多技術上的成長

寫這篇博客主要爲了回顧一下自己過去做過的項目,也梳理一下面試中談到項目這塊可以說出的亮點,如果對看到這篇博客的你有幫助那就更好了O(∩_∩)O

下文中提到的項目上的亮點如下

1)、使用反射+枚舉的方式記錄變更日誌

2)、使用策略模式+工廠模式封裝銀企直連服務

3)、基於GitLab CI實現CI、CD流程,以及jib打包自簽名問題的解決

4)、使用Rancher搭建Kubernetes集羣

5)、基於OpenShift平臺部署Apollo配置中心遇到的問題,以及對Apollo實現原理、Eureka服務發現原理的思考

6)、對接單點登錄,以及OAuth授權碼模式的思考

1、使用反射+枚舉的方式記錄變更日誌

業務需求

對比原單據和變更後的單據生成變更日誌,即哪些字段發生了變更並且記錄變更前和變更後的值

使用註解的主要達到兩個效果

1)、只有該註解標註的屬性纔會生成變更日誌

2)、標註相應字段來設置屬性的描述信息(前臺頁面上的中文描述)

工具類核心方法傳參是兩個Object對象,主要實現邏輯如下:

1)、只有兩個對象都是同一類型纔有可比性

2)、通過反射循環獲取父類Class對象,直到父類爲null的時候說明到達了最上層的Object類,在循環中獲取當前類的Field放入到List中

3)、循環所有Field的List,使用setAccessible()方法設置對象的訪問權限,保證對private的屬性的訪問,判斷只有使用註解標註的屬性纔會生成變更日誌,通過isEquals()方法判斷變更前後的屬性值是否一致,不一致生成變更日誌對象放入List中

這裏調用一個新聲明的isEquals()方法主要是爲了方便子類根據不同的業務邏輯去擴展,不是所有場景下都直接調用equals()方法即可,比如說BigDecimal如果使用equals()方法比較的是數值+精度,比如1.0和1其實數值相同,但精度不同,equals()方法返回false,但其實實際金額並未發生改變,所以當時實現的時候子類判斷如果是BigDecimal類型,使用compareTo()方法來判斷是否產生了變更

詳細解決方案:https://blog.csdn.net/qq_40378034/article/details/104158786

2、使用策略模式+工廠模式封裝銀企直連服務

業務需求

資金系統需要對接10多家銀企直連,根據傳入的支付方式判斷走哪一家銀行,然後調用相關方法

實現思路

開發銀企直連服務雖然是包含10多家銀行,但業務邏輯上需要提供的方法是一致的(支付走單筆、支付走批量、查詢支付結果、查詢賬戶餘額等等),首先根據業務需求寫一個統一的Interface接口,每個Service實現這個統一的Interface

但根據傳入的支付方式判斷走哪一家銀行,調用具體的哪個實現類,是通過了注入Map的方式,Spring會自動將Strategy接口的實現類注入到這個Map中,key爲bean id,value值則爲對應的策略實現類

@Component
public class StrategyFactory {
    //Spring會自動將Strategy接口的實現類注入到這個Map中,key爲bean id,value值則爲對應的策略實現類
    @Autowired
    private Map<String, Strategy> strategyMap;

    public Strategy getBy(String strategyName) {
        return strategyMap.get(strategyName);
    }
}

然後做了一個別名轉換的處理

@Component
@PropertySource("classpath:application.properties")
@ConfigurationProperties(prefix = "strategy")
public class StrategyAliasConfig {
    private HashMap<String, String> aliasMap;

    public static final String DEFAULT_STATEGY_NAME = "defaultStrategy";

    public HashMap<String, String> getAliasMap() {
        return aliasMap;
    }

    public void setAliasMap(HashMap<String, String> aliasMap) {
        this.aliasMap = aliasMap;
    }

    public String of(String entNum) {
        return aliasMap.get(entNum);
    }
}

配置文件application.properties

strategy.aliasMap.strategy1=concreteStrategy1
strategy.aliasMap.strategy2=concreteStrategy2
@Component
public class StrategyFactory {
    @Autowired
    private StrategyAliasConfig strategyAliasConfig;

    //Spring會自動將Strategy接口的實現類注入到這個Map中,key爲bean id,value值則爲對應的策略實現類
    @Autowired
    private Map<String, Strategy> strategyMap;

    //找不到對應的策略類,使用默認的
    public Strategy getBy(String strategyName) {
        String name = strategyAliasConfig.of(strategyName);
        if (name == null) {
            return strategyMap.get(StrategyAliasConfig.DEFAULT_STATEGY_NAME);
        }
        Strategy strategy = strategyMap.get(name);
        if (strategy == null) {
            return strategyMap.get(StrategyAliasConfig.DEFAULT_STATEGY_NAME);
        }
        return strategy;

    }
}

詳細解決方案:https://blog.csdn.net/qq_40378034/article/details/104121363

3、基於GitLab CI實現CI、CD流程,以及jib打包自簽名問題的解決

項目上實現的主要流程如下

提交代碼到dev-latest之後,在GitLab上打tag來觸發CI、CD的流程,步驟一先通過jib生成鏡像推送到鏡像私庫(如果是一個完整的流程可以在步驟一之前添加單元測試、Sonarqube代碼掃描的流程),步驟二通過Rancher的devops鏡像(這裏使用的docker runner)使用Rancher的命令行執行發佈

主要遇到的坑

客戶提供的Harbor倉庫用的是自簽名Https證書,而Jib使用JVM的已批准CA證書列表來驗證SSL證書,最後是通過一個工具類生成證書相關的文件然後放到JAVA_HOME/jre/lib/security目錄下,達到的效果就是證書在JRE級別受信任

詳細解決方案:https://blog.csdn.net/qq_40378034/article/details/104750483

4、使用Rancher搭建Kubernetes集羣

在這裏插入圖片描述

使用Ranche搭建Kubernetes集羣時,每個主機有三種角色可以選擇:EtcdControl PlaneWorker

1)、Etcd

etcd節點的主要功能是數據存儲,它負責存儲Rancher Server的數據和集羣狀態。Kubernetes集羣的狀態保存在etcd中,etcd節點運行etcd數據庫。etcd數據庫組件是一個分佈式的鍵值對存儲系統,用於存儲Kubernetes的集羣數據,例如集羣協作相關和集羣狀態相關的數據

etcd更新集羣狀態前,需要集羣中的所有節點通過quorum投票機制完成投票。假設集羣中有n個節點,至少需要n/2+1n/2 + 1(向下取整) 個節點同意,才被視爲多數集羣同意更新集羣狀態。例如一個集羣中有3個etcd節點,quorum投票機制要求至少兩個節點同意,纔會更新集羣狀態

2)、Control Plane

Control Plane節點上運行的工作負載包括:Kubernetes API Server、Scheduler和Controller Mananger。這些節點負載執行日常任務,從而確保集羣狀態和集羣配置相匹配。因爲etcd節點保存了集羣的全部數據,所以Control Plane節點是無狀態的

3)、Worker

Worker節點運行以下應用:

  • Kubelet:監控節點狀態的Agent,確保容器處於健康狀態
  • 工作負載:承載應用和其他類型的部署的容器和Pod

5、基於OpenShift平臺部署Apollo配置中心遇到的問題,以及對Apollo實現原理的思考

部署架構及遇到的問題

Apollo相關的Config Service、Admin Service和Portal都是封裝在一個鏡像中,作爲一個獨立的服務,但沒有註冊到Apollo自身的Eureka Server,而是和其他應用服務共用一個Eureka Server(爲保證Apollo高可用)

當時部署是基於OpenShift平臺,我們對K8S相關的知識也不太熟悉,最後達到的效果是服務間可以通過服務名(Service Name)調用,但是不能通過註冊到Eureka Server中每個Pod的IP來訪問服務

Eureka中註冊的信息

Application IP:Port
APOLLO-ADMINSERVICE 192.169.0.1:8090
APOLLO-CONFIGSERVICE 192.169.0.1:8080

如果其他服務通過服務名Apollo+端口8080來訪問Config Service是可以訪問的,但是不能通過192.169.0.1:8080訪問

解決方案

問題1

實際上如果我們註冊到Eureka中的註冊的服務地址是服務名Apollo+端口這樣的形式,那麼問題就解決了

Application IP:Port
APOLLO-ADMINSERVICE Apollo:8090
APOLLO-CONFIGSERVICE Apollo:8080

Eureka註冊時有一個參數可以定製完整的服務URL,這個服務URL就是服務間通過服務名在Eureka中獲取的服務地址,配置參數爲eureka.instance.homePageUrl=http://Apollo:8080,這樣從Eureka Server中拿到的服務地址就是http://Apollo:8080

問題2

那本地開發時是要指定Apollo的地址來拿到配置信息,指定的是Meta Server的地址apollo.meta=http://IP:Port,Meta Server再找到Eureka Server,獲取服務地址,由於上面的改造,我們拿到的是http://Apollo:8080這種形式的,但是本地開發並不能訪問

這時本地配置改爲apollo.configService=http://域名:8080直接指定Config Service的地址來跳過Meta Server的服務發現

Apollo實現原理

在這裏插入圖片描述

  • Config Service提供配置的讀取、推送等功能,服務對象是Apollo客戶端
  • Admin Service提供配置的修改、發佈等功能,服務對象是Apollo Portal
  • Config Service和Admin Service都是多實例、無狀態部署,所以需要將自己註冊到Eureka中並保持心跳
  • 在Eureka之上架了一層Meta Server用於封裝Eureka的服務發現接口
  • Client通過域名訪問Meta Server獲取Config Service服務列表(IP+Port),而後直接通過IP+Port訪問服務,同時在Client側會做load balance、錯誤重試
  • Portal通過域名訪問Meta Server獲取Admin Service服務列表(IP+Port),而後直接通過IP+Port訪問服務,同時在Portal側會做load balance、錯誤重試

在這裏插入圖片描述

1)、客戶端和服務端保持了一個長連接,從而能第一時間獲得配置更新的推送

2)、客戶端還會定時從Apollo配置中心服務端拉取應用的最新配置(推拉結合

  • 這是一個fallback機制,爲了防止推送機制失效導致配置不更新
  • 客戶端定時拉取會上報本地版本,所以一般情況下,對於定時拉取的操作,服務端都會返回304 - Not Modified
  • 定時頻率默認爲每5分鐘拉取一次,客戶端也可以通過在運行時指定System Property: apollo.refreshInterval來覆蓋,單位爲分鐘

3)、客戶端從Apollo配置中心服務端獲取到應用的最新配置後,會保存在內存中

4)、客戶端會把從服務端獲取到的配置在本地文件系統緩存一份

在遇到服務不可用,或網絡不通的時候,依然能從本地恢復配置

Mac/Linux/opt/data/{appId}/config-cache

WindowsC:\opt\data{appId}\config-cache

文件名格式如下

{appId}+{cluster}+{namespace}.properties

6、對接單點登錄,以及OAuth2.0授權碼模式的思考

業務需求

取消我們系統的登錄頁,使用統一登錄平臺進行登錄,登錄後跳轉到系統的首頁,採用的是OAuth 2.0的授權碼模式

流程圖

下面我們系統簡稱爲A網站,統一登錄平臺簡稱爲B網站

在這裏插入圖片描述

第一步,A網站提供一個鏈接,用戶點擊後就會跳轉到B網站,授權用戶數據給A網站使用。下面就是A網站跳轉B網站的一個示意鏈接

https://b.com/oauth/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=read

response_type參數表示要求返回授權碼(code),client_id參數讓B知道是誰在請求,redirect_uri參數是B接受或拒絕請求後的跳轉網址,scope參數表示要求的授權範圍(這裏是只讀)

在這裏插入圖片描述

第二步,用戶跳轉後,B網站會要求用戶登錄,然後詢問是否同意給予A網站授權。用戶表示同意,這時B網站就會跳回redirect_uri參數指定的網址。跳轉時,會傳回一個授權碼,就像下面這樣

https://a.com/callback?code=AUTHORIZATION_CODE

code參數就是授權碼

在這裏插入圖片描述

第三步,A網站拿到授權碼以後,就可以在後端,向B網站請求令牌

https://b.com/oauth/token?client_id=CLIENT_ID&client_secret=CLIENT_SECRET&grant_type=authorization_code&code=AUTHORIZATION_CODE&redirect_uri=CALLBACK_URL

上面 URL 中,client_id參數和client_secret參數用來讓B確認A的身份(client_secret參數是保密的,因此只能在後端發請求),grant_type參數的值是AUTHORIZATION_CODE,表示採用的授權方式是授權碼,code參數是上一步拿到的授權碼,redirect_uri參數是令牌頒發後的回調網址

在這裏插入圖片描述

第四步,B網站收到請求以後,就會頒發令牌。具體做法是向redirect_uri指定的網址,發送一段JSON數據

{    
  "access_token":"ACCESS_TOKEN",
  "token_type":"bearer",
  "expires_in":2592000,
  "refresh_token":"REFRESH_TOKEN",
  "scope":"read",
  "uid":100101,
  "info":{...}
}

在這裏插入圖片描述

OAuth2.0的四種方式:http://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章