讓ERP的服務更開放! ——用微服務架構搭建的一套基於EBS的API服務系統

samt007發表於2018-04-18

1. 原始碼下載地址

原始碼連結: https://github.com/samt007/xygerp-api-demo

這是用Spring Cloud微服務架構搭建的一套基於EBS的API服務系統

如對本文有任何的疑問,請聯絡我:samt007@qq.com

2. Introduction介紹

這是一篇傳統ERP系統和基於Java的微服務架構有效結合的技術文件。

傳統ERP關注的是企業內部的資訊化管理。當ERP系統能將其服務釋出出去之後(結合微服務架構),就可以很好實現與第三方系統的無縫對接,同時也可以實現擴充套件ERP本身的功能。 目標是:讓ERP的服務更開放!

2.1 它有什麼用?

簡單來說,就是:

相當於做一箇中間服務平臺,把ERP的API做成Web Service與其它系統整合。

下面具體說明它的作用。

1. 如果沒有它...

如果沒有一個統一的API對接平臺,那麼ERP和第三方系統做對接,那會是下圖所示的結構:

這裡寫圖片描述

從上圖可以看出,各個第三方系統分別和ERP做對接,無論是DBLINK還是通過自己的Web Service, 都是雜亂的,沒能統一管理的。簡單來說就是各自為政,為對接而需要做的事情都是零碎的。

當第三方系統越來越多的時候,那對於日常的運維將會是一個災難的問題。舉個例子,就一個簡單的運維:密碼修改,需要調整的地方就會很多,也很容易遺漏。

特別指出的是,介面功能的複用方面也是個難題。假設一個查詢庫存的介面,CRM系統和線上下單系統都可以用的,需要開發2次。

2. 自從有了它...

有了這套統一的API系統之後,ERP系統和別的系統之間的對接就變成了這個結構:

這裡寫圖片描述

所以,有了它,相當於ERP的API都可以通過這服務平臺給開發出去,基本上所有的介面可以完成的業務,都可以通過這套服務平臺來完成。

可以實現:

  • 對外服務的統一
  • API服務之間可以實現互相呼叫,並且實現服務取數和處理的邏輯的統一
  • 程式碼的統一,提高開發效率。特別是comm程式碼的部分。
  • 提高與第三方系統對接的穩定性:只需要關注該微服務系統的執行穩定性即可。

3. 有哪些例項?

舉個例子:

1) 成品進出條碼管理系統:

大概這個需求: 成品入庫的時候,直接可以用條碼槍掃條碼或者二維碼就可以入庫;銷售出庫的時候,也可以通過刷成品的條碼直接進行出庫。JIT管理。

通過這個系統的實現邏輯是: 通過EBS的使用者名稱和密碼可以登入條碼槍上的APP系統。然後,刷條碼的時候,通過該Web Service可以產生對應的事務處理!例如完工入庫,處理物料搬運單等。

下面是該系統的一些截圖

注意:該功能後臺API由該微服務提供,前臺是安卓的APP

這裡寫圖片描述

2) 與各個第三方系統的整合:

每個企業內部都有各種第三方系統,這些系統或多或少都需要和EBS進行整合。傳統的整合方法是通過DBLINK。

但是有這套Web Service系統之後,就可以統一通過該Web Service作為中介,和EBS進行資料的整合互動!

2.2 什麼是微服務?

關於它的解析,網上資料很多。 這裡引用某位大神的總結(引用:http://blog.51cto.com/ityouknow/1974080),解析得比較到位:

微服務的概念源於2014年3月Martin Fowler所寫的一篇文章“Microservices”。

微服務架構是一種架構模式,它提倡將單一應用程式劃分成一組小的服務,服務之間互相協調、互相配合,為使用者提供最終價值。每個服務執行在其獨立的程式中,服務與服務間採用輕量級的通訊機制互相溝通(通常是基於HTTP的RESTful API)。每個服務都圍繞著具體業務進行構建,並且能夠被獨立地部署到生產環境、類生產環境等。另外,應儘量避免統一的、集中式的服務管理機制,對具體的一個服務而言,應根據業務上下文,選擇合適的語言、工具對其進行構建。

微服務是一種架構風格,一個大型複雜軟體應用由一個或多個微服務組成。系統中的各個微服務可被獨立部署,各個微服務之間是鬆耦合的。每個微服務僅關注於完成一件任務並很好地完成該任務。在所有情況下,每個任務代表著一個小的業務能力。

微服務架構優勢

複雜度可控:在將應用分解的同時,規避了原本複雜度無止境的積累。每一個微服務專注於單一功能,並通過定義良好的介面清晰表述服務邊界。由於體積小、複雜度低,每個微服務可由一個小規模開發團隊完全掌控,易於保持高可維護性和開發效率。

獨立部署:由於微服務具備獨立的執行程式,所以每個微服務也可以獨立部署。當某個微服務發生變更時無需編譯、部署整個應用。由微服務組成的應用相當於具備一系列可並行的釋出流程,使得釋出更加高效,同時降低對生產環境所造成的風險,最終縮短應用交付週期。

技術選型靈活:微服務架構下,技術選型是去中心化的。每個團隊可以根據自身服務的需求和行業發展的現狀,自由選擇最適合的技術棧。由於每個微服務相對簡單,故需要對技術棧進行升級時所面臨的風險就較低,甚至完全重構一個微服務也是可行的。

容錯:當某一組建發生故障時,在單一程式的傳統架構下,故障很有可能在程式內擴散,形成應用全域性性的不可用。在微服務架構下,故障會被隔離在單個服務中。若設計良好,其他服務可通過重試、平穩退化等機制實現應用層面的容錯。

擴充套件:單塊架構應用也可以實現橫向擴充套件,就是將整個應用完整的複製到不同的節點。當應用的不同元件在擴充套件需求上存在差異時,微服務架構便體現出其靈活性,因為每個服務可以根據實際需求獨立進行擴充套件。

2.3 ERP API微服務系統架構說明

2.3.1 系統開發邏輯說明

從上面的解析得知:微服務是一種技術架構,將一個龐大的服務體系拆分為若干個子服務執行。 問題來了,應該如何拆分呢?就是服務的拆分原則是什麼。

這個問題就像是一個大表如何進行分割槽一樣,其實我覺得是具體問題具體分析。 由於我開發的是基於EBS的微服務系統,正常來說,比較合理的劃分規則應該是以EBS的模組來分。

相當於每個模組都劃分為一個單獨的微服務。例如FND模組,INV模組,WIP模組等等。

有時候,為了某個目的,可能有些功能是定製的,需要提取幾個模組的資料來用,而且被別的模組重用的概率很低。所以,實際上也可以以定製的功能來劃分微服務。

目前來說,該系統包括2個子服務:

  • xygerp-ald服務:ald模組

這個是整個微服務API的核心ald模組。這個模組的主要功能是驗證使用者的登入,為所有的api模組提供統一的token認證。相當於ebs的FND模組。

  • xygerp-albc服務:albc子模組

這個專案是屬於微服務的API模組之一:條碼管理系統提供資料以及資料處理的API。 主要是為條形碼傳輸系統用。

當然,未來可以新增若干個服務。微服務架構的優勢是有很好的橫向擴充套件能力!

2.3.2 微服務系統架構圖

該系統的架構圖如下所示。

這裡寫圖片描述

注意:

1.Spring Cloud模組中,實際上Spring Security並不是單獨的一個模組,而是融入到每一個業務微服務模組中! 每個微服務都必須要有token認證才允許訪問API,它非常重要! 所以我將它給列到Spring Cloud模組中。

2.圖中有些模組目前還沒有實現。 目前實現了架構整體,包括以下的服務(下一個章節會具體說明每個模組的用途):

xygerp-ald

xygerp-albc

xygerp-server-eureka

xygerp-server-zuul

注意: 以後會按需新增別的模組。

3 系統開發流程

接下來是一步一步來開發一套這個基於Spring Cloud的微服務系統。

3.1 必須掌握的基礎開發技術知識點

開發系統都必須要打好基礎。所以,這裡列出了開發基於Spring Cloud的微服務系統需要掌握的開發技術。

下面我不會具體解說每一個開發技術如何學習,因為這並不是本文的重點。工欲善其事必先利其器,基礎還是必須要打好。

1)Java語言

必須要熟悉java,否則基本不用看文件了。先打好基礎吧!

2)Maven專案管理工具

系統開發的專案都是以maven做專案管理的,所以必須要先安裝並掌握maven工具。

3)Oracle資料庫+PLSQL+SQL語言

資料庫端的開發技術。這裡選用Oracle資料庫,因為EBS就是基於Oracle資料庫的ERP系統。

3.2 需要熟悉的java框架

Java技術發展到現在,已經出現了許多非常優秀的開源框架,我們可以藉助這些框架來快速開發系統。

3.2.1 Spring框架技術棧(全家桶)

Spring是目前開源的主流的技術包。 該系統主要用到的技術棧是:Spring boot,Spring Security以及Spring Cloud。

  1. Spring Boot

系統基於SpringBoot快速開發。選擇目前熱度很高的SpringBoot,最大限度地降低配置複雜度,把大量的精力投入到業務開發中來。

  1. Spring MVC

利用Spring MVC框架處理所有的url請求,簡單易用。

  1. Spring Security

Spring security是一個強大的和高度可定製的身份驗證和訪問控制框架。它是確保基於Spring的應用程式的標準。 這裡主要是用Spring Security框架處理Token機制。

  1. Spring Cloud

Spring Cloud是一系列框架的有序集合。 它利用Spring Boot的開發便利性巧妙地簡化了分散式系統基礎設施的開發,如服務發現註冊、配置中心、訊息匯流排、負載均衡、斷路器、資料監控等,都可以用Spring Boot的開發風格做到一鍵啟動和部署。 注意:本系統目前使用Spring Cloud的2個模組

  • 請求統一通過API閘道器(Zuul)來訪問內部服務.
  • 閘道器接收到請求後,從註冊中心(Eureka)獲取可用服務

3.2.2 MyBatis

ORM框架選用MyBatis。

主要是考慮到它能夠很好支援SQL語句:MyBatis是支援普通SQL查詢,儲存過程和高階對映的優秀持久層框架。 另外,還用到了MyBatis的一些提高開發效率的外掛,特別是通用Mapper和PageHelper分頁外掛!

3.2.3 Alibaba druid

DRUID是阿里巴巴開源平臺上一個資料庫連線池實現。它結合了C3P0、DBCP、PROXOOL等DB池的優點,同時加入了日誌監控,可以很好的監控DB池連線和SQL的執行情況。

3.2.4 Swagger

前端和後端的唯一聯絡,變成了API介面。

API文件變成了前後端開發人員聯絡的紐帶,變得越來越重要,swagger就是一款讓你更好的書寫API文件的框架。

3.3 需要準備的軟體工具

3.3.1Redis

目前用Redis的主要作用是存取token,以配合實現Spring Security完成api訪問的安全機制。

以後可以考慮做快取或者訊息佇列等高階功能。

3.3.2Docker

基於Docker的容器化部署。

由於使用了微服務架構後,我們的系統將會由很多子系統構成。 為了達到多個系統之間環境隔離的目的,我們可以將它們部署在多臺伺服器上,可這樣的成本會比較高,而且每臺伺服器的效能可能都沒有充分利用起來。

所以我們很自然地想到了虛擬機器,在同一臺伺服器上執行多個虛擬機器,從而實現環境的隔離,每個虛擬機器上執行獨立的服務。

然而虛擬機器的隔離成本依舊很高,因為它需要佔用伺服器較多的硬體資源和軟體資源。 所以,在微服務結構下,要實現服務環境的隔離,Docker是最佳選擇。它比虛擬機器更加輕量級,佔用資源較少,而且能夠實現快速部署。

備註:後面有專題說明這個工具如何安裝使用。由於篇幅原因,本文件暫時不講解容器化部署。

3.3.3 Jenkins

Jenkins自動化構建工具。

當我們採用了微服務架構後,我們會發現這樣一個問題。整個系統由許許多多的服務構成,這些服務都需要執行在單獨的容器中,那麼每次釋出的複雜度將非常高。

首先你要搞清楚這些服務之間的依賴關係、啟動的先後順序,然後再將多個子系統挨個編譯、打包、釋出。這些操作技術難度低,卻又容易出錯。

那麼有什麼工具能夠幫助我們解決這些問題呢?答案就是——Jenkins。

它是一款自動化構建的工具,簡單的來說,就是我們只需要在它的介面上按一個按鈕,就可以實現上述一系列複雜的過程。

備註:後面有專題說明這個工具如何安裝使用。由於篇幅原因,本文件暫時不講解自動化構建。

3.4 具體開發流程

現在開始手把手來搭建一套這樣子的系統。

3.4.1 建立Maven專案的組織結構

先建立一個微服務系統的父級專案:xygerp-api

再在這個專案下面分別建立下面幾個子專案:

專案名稱 說明
xygerp-ald ald模組,埠:8180。這個是整個微服務API的核心ald模組。這個模組的主要功能是驗證使用者的登入,為所有的api模組提供統一的token認證。相當於ebs的FND模組。
xygerp-albc albc子模組,埠:8181。這個專案是屬於微服務的API模組之一:條碼管理系統提供資料以及資料處理的API。主要是為條形碼傳輸系統用。
xygerp-comm comm模組這個專案是所有API專案的核心依賴專案。說白了就是將API微服務架構的所有專案的公用程式碼可以抽取在這裡。
xygerp-basic-support 核心基礎支撐模組這個專案是所有API專案的基礎資料支撐專案。這裡統一歸集了所有的Entity!因為對於Entity來說,應該是整個微服務都公用的。
xygerp-server-eureka Spring Cloud的服務與發現的服務中心。埠:8101。這個模組是Spring cloud的最核心的模組了,用來處理各個微服務之間的服務呼叫的。
xygerp-server-zuul Spring Cloud服務閘道器。埠:8102。在Spring Cloud架構體系內的所有微服務都通過Zuul來對外提供統一的訪問入口,所有需要和微服務架構內部服務進行通訊的請求都走統一閘道器。

它們的目錄結果是這樣子的:

這裡寫圖片描述

注意: 關於xygerp-basic-support:核心基礎支撐模組

可能您會有疑問:為什麼不將entity歸併在它所屬的模組?其實是這樣的,我主要是考慮到服務之間的互相呼叫的問題。

微服務雖然客觀上是一個單獨的服務,但是,實際上大部分的功能肯定是互相呼叫的。舉個例子,銷售訂單模組呼叫庫存模組的功能查詢個庫存是很正常的業務吧?

如果entity不共用的話,相當於銷售模組得到的庫存模組的結果無法歸集為bean來處理,這樣子對於後臺的處理會帶來極大的不便!

3.4.2 構建模組的依賴關係

接著需要通過pom檔案來指定它們之間的依賴關係,依賴關係如下圖所示。

1. 業務服務部分:

這裡寫圖片描述
注意,上面的4個專案是有依賴關係的。 所以,xygerp-ald和xygerp-albc部署方式 改為war部署 (主要是為了利用jenkins進行自動化部署)。

而xygerp-comm和xygerp-basic-support只是為各個微服務提供基礎程式碼支撐,所以是jar部署即可。

此外,為了簡化各個模組的配置,我們將所有模組的通用依賴放在Project的pom檔案中,然後讓所有模組作為Project的子模組。 這樣子模組就可以從父模組中繼承所有的依賴,而不需要自己再配置了。

在父pom中指定子模組

modules標籤指定了當前模組的子模組是誰,但是僅在父模組的pom檔案中指定子模組還不夠,還需要在子模組的pom檔案中指定父模組是誰。

    <modules>
        <module>xygerp-comm</module>          <!--核心Comm模組 -->
        <module>xygerp-basic-support</module>    <!--核心基礎支撐模組 -->
        <module>xygerp-server-eureka</module>   <!--Eureka服務治理模組 -->
        <module>xygerp-server-zuul</module>      <!--zuul動態路由模組 -->
        <module>xygerp-ald</module>             <!--核心ald模組 -->
        <module>xygerp-albc</module>           <!--子模組:albc模組,提供條碼對接API服務 -->
    </modules>

複製程式碼

需要在子模組中指定父模組

<parent>
        <artifactId>xygerp</artifactId>
        <groupId>com.xygerp</groupId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
</parent>

複製程式碼

備註:具體程式碼直接看原始碼吧。這裡只是提及了一些重點設定而已。

所以,到此為止,模組的依賴關係配置完畢!但要注意模組打包的順序。

由於所有模組都依賴於xygerp-comm模組和xygerp-basic-support模組,因此在構建模組時,首先需要編譯、打包、安裝xygerp-comm模組和xygerp-basic-support模組,將它打包進本地倉庫中,這樣上層模組才能引用到。當該模組安裝完畢後,再構建上層模組。 否則在構建上層模組的時候會出現找不到xygerp-comm模組中類庫的問題。

Tips: 其實,如果是在父級目錄直接用mvn package整體打包的話,那打包構建的順序在父pom中是直接指定了!

2. 微服務架構服務治理部分

xygerp-server-eureka:Spring Cloud服務註冊和發現。就是處理服務之間的治理。

xygerp-server-zuul:Spring Cloud的統一API閘道器服務。

Tips: 這2個專案是為了實現微服務架構而用到的核心服務。所以,它們是相對獨立的。不需要依賴父pom。

3.4.3用mvn編譯命令打包程式碼

上面的專案都建立好之後,再新增所有專案都需要用到的依賴(具體程式碼可以參考我的原始碼)。

都沒問題的話,就可以用mvn命令進行打包專案了:

mvn clean install -Dmaven.test.skip=true -P dev

這裡簡單解析一下指令:

mvn:Maven的統一指令。

clean install:表示要構建該專案。

-Dmaven.test.skip=true:表示構建的時候要跳過測試模組。

-P dev:表示構建的時候,啟用 dev 的Spring boot引數執行系統。

如果一切都OK,那正常的結果如下:

這裡寫圖片描述

3.5 本地電腦測試系統

程式碼搞定了,接下來需要考慮的事情應該是如何測試。 畢竟所有的系統都必須要經過測試,特別是這種配置多,涉及範圍廣的系統。

這個就不得不說一下Spring boot的優勢了。Spring boot的打包應用預設內建了tomcat服務。 換句話說,只需要java命令執行一下Spring boot打包的target結果,就可以啟動一個tomcat服務啦!真挺方便測試的!

3.5.1 啟動本地系統的服務

假設我的xygerp-api專案在這裡:D:\JSP_MyEclipse\xygerp-api

然後分別開啟4個cmd命令視窗,執行:

D:\JSP_MyEclipse\xygerp-api\xygerp-server-eureka\target>java -jar xygerp-server-eureka-1.0-SNAPSHOT.war
D:\JSP_MyEclipse\xygerp-api\xygerp-server-zuul\target>java -jar xygerp-server-zuul-1.0-SNAPSHOT.war
D:\JSP_MyEclipse\xygerp-api\xygerp-ald\target>java -jar xygerp-ald-1.0-SNAPSHOT.war
D:\JSP_MyEclipse\xygerp-api\xygerp-albc\target>java -jar xygerp-albc-1.0-SNAPSHOT.war

複製程式碼

如下圖:

這裡寫圖片描述

3.5.2 本地測試API服務系統

本地測試環境的服務啟動起來了,接著就是進行具體的資料測試。

首先測試Eureka的服務註冊以及發現,確認服務是否都已經註冊到系統中:

這裡寫圖片描述

然後,用swagger測試使用者登入的功能: http://127.0.0.1:8102/xygerp/ald/swagger-ui.html 目前是測試是否可以正常產生token。

這裡寫圖片描述
這裡寫圖片描述
說明已經登入成功,並且產生了本次訪問的token!

將token記錄下來,接著測試。 繼續測試一個查詢的功能: http://127.0.0.1:8102/xygerp/albc/swagger-ui.html

這裡寫圖片描述

注意,這裡用了Spring Security框架,所以的API請求頭都必須攜帶token資訊。否則請求會返回401。

如果測試OK,那說明基本上系統已經成功搭建好了。 下一步就是如何在測試環境或者正式環境部署它,以及如何一鍵構建專案的問題了。

簡單來說,系統的部署是用 docker工具 ,一鍵部署專案用的是 Jenkins工具。後面將會用專題來說明這2個工具的使用。

3.6 該API微服務系統實現的功能難點

3.6.1 解決資料庫Session的環境變數問題,特別是語言環境和使用者環境。

關於這個問題,目前我用的辦法可能不一定是最優的,如果有別的兄臺有更好的解決辦法,請留言給我,十分感謝!

問題來源:

熟悉EBS開發的兄臺都應該知道,登入ERP之後,我們每次開啟Form,系統就會申請一個新的資料庫Session,這時候,EBS系統會 自動幫我們初始化該Session的環境變數 :例如基本的語言環境,使用者環境,業務實體等等。

這時候,我們在包裡面可以直接用FND_GLOBAL.USER_ID之類的函式就可以非常方便獲取環境變數的資訊。

但是,在Java Web開發裡面就不一樣了!

在Java訪問資料庫的理念中,Session的申請是一個極耗資源的動作!所以,大部分連線資料庫的Java軟體都提出了一個 資料庫連線池 的概念(例如DRUID資料庫連線池)。簡單來說就是session共用!

Session公用就會帶來一個併發問題:A使用者使用系統,並初始化了該Session的環境變數為A使用者;當A使用者不用系統的時候,Session會閒置並放回連線池裡面等待別的使用者使用。

這時候如果B的使用者很可能會使用該Session,如果不重新初始化環境變數的話,那B使用者使用系統的Session的環境變數還是A使用者,就會導致資料的bug! 如何處理該問題是開發該系統碰到的一個難題。

問題解決:

我目前的處理辦法是:在Service層,用AOP統一自動監控Service層的這個引數AuthUser user

只要在Service層將引數AuthUser user放在最後,AOP會自動初始化Session的環境變數。(需要注意的是,我這個系統的資料庫Transaction在Service層啟用!)

另外,語言環境變數,登入ID環境變數等,會一併自動初始化。因為AuthUser會攜帶該定義!

核心處理程式碼如下:

private static final String SQL_GLOBAL_INIT
	    = " DECLARE "
		+ "    L_session_id NUMBER;L_user_id NUMBER;L_login_id NUMBER;L_LANG VARCHAR2(10); "
		+ " BEGIN "
		+ "    L_user_id:=:P_USER_ID; L_login_id:=:P_LOGIN_ID; L_LANG:=:P_LANG;"
		+ "    APPS.fnd_global.INITIALIZE("
		+ "       session_id=>L_session_id, user_id =>L_user_id, resp_id =>NULL, "
		+ "       resp_appl_id=>NULL, security_group_id=>NULL, site_id=>NULL, login_id =>L_login_id, "
		+ "       conc_login_id=>NULL, prog_appl_id=>NULL, conc_program_id=>NULL, conc_request_id=>NULL, "
		+ "       conc_priority_request=>NULL"
		+ "     ); "
		+ "    IF NVL(L_LANG,'US') <> USERENV('LANG') THEN "
		+ "        IF L_LANG='ZHS' THEN "
		+ "            APPS.fnd_global.set_nls_context(p_nls_language => 'SIMPLIFIED CHINESE'); "
		+ "        ELSE "
		+ "            APPS.fnd_global.set_nls_context(p_nls_language => 'AMERICAN'); "
		+ "         END IF;"
		+ "    END IF;"
		+ " END; ";
	
    /*** 
     * service層呼叫之前先自動初始化環境變數
     * 需要注意的是,使用者變數必須的引數放在最後!
     * 只要在Service層將引數AuthUser user放在最後,AOP會自動初始化Session的環境變數。
     * @throws Exception 
     */  
	@SuppressWarnings("static-access")
	@Before("execution(* com.jebms.*.service..*.*(..))  && args(..,user)")  
    public void oracleDBInit(JoinPoint joinPoint,AuthUser user) throws Exception{
		Long dbLoginId=devDao.getJdbcTemplate().queryForObject("SELECT FND_GLOBAL.LOGIN_ID FROM DUAL", Long.class);
if(user.getLoginId()!=null&&user.getLoginId()>0&&!user.getLoginId().equals(dbLoginId)){
			Map<String,Object> inParamMap=new HashMap<String,Object>();
	    	inParamMap.put("P_USER_ID", user.getUserId());
	    	inParamMap.put("P_LOGIN_ID", user.getLoginId());
	    	inParamMap.put("P_LANG", user.getLanguage());
			devDao.getDevJdbcTemplate().execute(this.SQL_GLOBAL_INIT, inParamMap);
        }
    }

複製程式碼

原始碼在:com.jebms.comm.utils. AopUtil

3.6.2 解決EBS的使用者的登入問題:統一用EBS系統的帳號密碼登入API系統。

問題描述:

由於我這個是第三方的API系統,所以,使用者名稱和密碼資訊實際上並不是該API系統需要管理的事情。

相當於說,API系統無法按照正常的流程來驗證使用者名稱和密碼:輸入使用者名稱和密碼,系統驗證後臺資料庫的使用者名稱和密碼,再返回驗證結果。

而是:輸入使用者名稱和密碼,系統 呼叫ERP的使用者名稱密碼驗證包 進行驗證,再返回結果。 簡單來說:需要新增自定義驗證的邏輯。

還好Spring Security框架支援靈活的驗證邏輯。

新增步驟:

首先,寫一個自定義驗證的類:MyAuthenticationProvider

接著,在Spring Security框架的定義中,新增這個自定義的驗證。

AbstractWebSecurityConfig

private MyAuthenticationProvider provider;//自定義驗證 auth.authenticationProvider(provider);

即可以完美實現這個效果

核心程式碼:

    /**
     * 自定義驗證方式
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = (String) authentication.getCredentials();
        AuthUser user = (AuthUser) userService.loadUserByUsername(username);
        System.out.println("username:"+username+",password:"+password);
        if(user == null){
            throw new BadCredentialsException("Username not found.");
        }

        //加密過程在這裡體現
        if (!sysService.xygErpValidateLogin(username, password)) {
            throw new BadCredentialsException("Wrong password.");
        }
        
        user.setPassword(passwordEncoder.encode(password));

        Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
        return new UsernamePasswordAuthenticationToken(user, password, authorities);
    }

複製程式碼

3.6.3 統一的開發風格。

1.Entity基類封裝。

封裝了5who欄位,以及類似Form的FND_SET_WHO的方法,可以很方便進行開發。

另外,為了防止丟失更新,這邊每次更新前實際上會先檢測資料的一致性,對應的動作也有做了封裝。

2.查詢邏輯的封裝。

查詢功能相對來說還是會很多,對於複雜的查詢條件如何傳值是一個難題。

這裡封裝了一個SearchInfo積累,可以統一將所有的查詢條件都放在這個類,然後在java的Controller層定義好查詢條件對應匹配欄位,系統就可以自動產生對應的and條件。

例如:

   @GetMapping(value = "/getPageLocator")
    @ApiOperation(value = "貨位分頁列表介面")
    public ResultEntity<PageInfo<EslipLocatorRE>> getPageLocator(
    		@ApiParam(value = "庫存組織ID",required = true) @RequestParam(required = true) int organizationId,
    		@ApiParam(value = "庫別程式碼",required = true) @RequestParam(required = true) String subinventoryCode,
    		@ApiParam(value = "貨位程式碼",required = false) @RequestParam(required = false) String locatorCode,
    		SearchInfo searchInfo) throws Exception {
    	searchInfo.getConditionMap().put("organizationId", organizationId);
    	searchInfo.getConditionMap().put("subinventoryCode", subinventoryCode);
    	searchInfo.getConditionMap().put("locatorCode", locatorCode);
    	searchInfo.setAuthUser(this.authUser);
    	searchInfo.initSqlCondition();
    	searchInfo.andSqlCondition("MIL.ORGANIZATION_ID","organizationId");
    	searchInfo.andSqlCondition("MIL.SUBINVENTORY_CODE","subinventoryCode");
    	searchInfo.andSqlCondition("MIL.SEGMENT1","locatorCode");
        return eslipService.selectForPageLocator(searchInfo);
    }

複製程式碼

3.統一的處理結果的封裝。

基本上任何一個處理,要不成功,要不失敗(警告其實也算失敗)。

這裡封裝了一個返回結果的基類ResultEntity<T>,可以進行有效的應用端或者java端的互動。

這裡寫圖片描述

@ApiModelProperty(value = "狀態碼,0表示成功 其他表示失敗", example = "0",position = 1)
private String code;
複製程式碼

特別需要指出的是,前端獲取或者處理資料,也是統一要用這個處理結果基類的返回。

簡單來說,就是資料處理成功/失敗,會有一個統一的返回結果標識。注意,這個標識和請求的響應結果標識(200)是有所不同的!

請求響應標識只是說明web伺服器的響應是正常,但,具體的處理結果可能是處理失敗。

這裡寫圖片描述

下面是一個具體的例子(到時候實際開發處理的介面處理結果也是這樣子):

{
	"code": "0",
	"message": "",
	"description": "",
	"obj": [{
		"createdBy": -1,
		"creationDate": "2017-10-10 09:37:03",
		"lastUpdatedBy": 10,
		"lastUpdateDate": "2017-11-16 14:47:48",
		"lastUpdateLogin": 96,
		"valueUUID": null,
		"id": 2,
		"applId": 1,
		"respCode": "BASIC_SET",
		"menuId": 2,
		"startDate": "2017-10-10 09:37:03",
		"endDate": null,
		"respName": "系統設定職責",
		"description": "系統設定職責",
		"menuCode": "SYSTEM_SET",
		"menuName": "系統設定選單",
		"enabled": true
	}],
	"param1": null,
	"param2": null,
	"param3": null,
	"param4": null,
	"param5": null,
	"ok": true
}

複製程式碼

文件參考連結: https://juejin.im/entry/5a7812906fb9a0635014f19a http://blog.51cto.com/ityouknow/1974080

相關文章