打造獨立資料庫訪問的中間服務

来源:http://www.cnblogs.com/java-class/archive/2017/06/23/7064163.html
-Advertisement-
Play Games

隨著公司業務的不斷變化,幾年前的 A 項目和底層 DB_A 資料庫華麗轉身為核心業務服務和核心資料庫。 想從 DB_A 資料庫獲取數據的 web 服務越來越多,項目之間的關係逐漸演變為下麵這樣: 很容易看出來按上圖這樣的發展趨勢會存在很多問題(項目關係為個人抽象出來的簡化版,實際情況比這要複雜的多) ...


   隨著公司業務的不斷變化,幾年前的 A 項目和底層 DB_A 資料庫華麗轉身為核心業務服務和核心資料庫。

   想從  DB_A  資料庫獲取數據的 web 服務越來越多,項目之間的關係逐漸演變為下麵這樣:

   很容易看出來按上圖這樣的發展趨勢會存在很多問題(項目關係為個人抽象出來的簡化版,實際情況比這要複雜的多)。

   a. 當 webappA 運行過程中出現異常無法訪問,webappB/ webappC .... 還能正常獲取  DB_A 數據嗎?

   b. 各種各樣的提供給 webappB/webappC ... 獲取 DB_A 數據的服務都集中在 webappA 中,webappA 的體積會無限水平擴張,誰都不喜歡贅肉對吧?

   c. webappA  項目在運行過程中除了要正常提供自己的服務給用戶以外,還要兼顧其他項目獲取數據的請求,勢必會造成性能瓶頸。

   其中的有些問題已經在項目上線推進中出現過,隔三差五停機維護變成響亮的巴掌扇到項目組的臉上確實也不好受。

   題外話:按照目前互聯網的發展速度和各公司業務擴展,能準確預測項目兩年以內發展方向/並提前做好擴展的架構師,能力已經非常不錯。

   項目組有人提出繞開項目 webappA ,其餘的 webappB/webappC ...直接連接 DB_A 進行交互,但很快被否決了(每個項目資料庫訪問層你都要重新定義和編寫)。

   能否將其中的資料庫訪問層獨立出來,做為服務容許授權的項目進行訪問?如下:

   核心想法是為無限增多的N個 wabapp 提供特定資料庫的訪問。這樣既避免了項目之間的耦合,也提高的數據訪問層的重用率。

   想法已經有了,那就開乾吧,BB 解決不了問題。大概花了兩天時間進行搭建,填了無數坑,終於出落的和我預想中的一樣貼切。

   原項目因商用無法開源,demo 經我重新組織已開源到:http://git.oschina.net/LanboEx/dao-service-demo

   需要這方面實踐的同學,clone 到本地跑起來一切也就明朗了。

1. 服務介面層

   

   需要 DB_A 數據項目依賴 dap-service-api 訪問 dao-service-impl 服務即可。

   dao-service-api 為提供給外層的介面,最終的呈現方式為 jar, maven 項目直接依賴即可。

   

   如果存在老舊非 maven 項目,使用 maven-jar-plugin/maven-assembly-plugin 將所依賴的 jar 都裝配進去添加到項目 lib 裡面。

2. 服務實現層

   dao-service-impl 由 cxf + spring + druid + jpa(hibernate impl) 開源類庫搭建而成的純後端組件服務。

   

   做為服務介面的實現層,最終呈現方式為 war,可進行集群或分散式部署,給其他項目提供服務。

   目錄結構一目瞭然,上手開發速度很快,其中自己實現了簡易的代碼生成(GenCodeServlet),dao 層 + webService 層 介面和實現都可以自動生成。

   webSevice 實現層註入 dao 層介面,針對單表封裝增刪改查5個方法,大體上不用寫多餘的方法,避免編寫百分之 90 的 SQL 。

@WebService
@SOAPBinding(style = SOAPBinding.Style.RPC)
public interface UserWs {

    /**
     * 通過 ID 過去單個 User 實體對象
     * cxf 傳輸返回對象不可為null,Dao 層獲取為null時
     * 實例化返回空對象,判空時使用對象主鍵進行判斷即可
     *
     * @param id 主鍵ID
     */
    UserPO getUser(String id);

    /**
     * 通過類似的 PO 獲取多個 User 實體對象
     *
     * @param userPO 對照的實體對象
     */
    List<UserPO> listUser(UserPO userPO);

    /**
     * 通過類似的 PO 獲取多個 User 實體對象
     *
     * @param userPO  對照的實體對象
     * @param orderby 排序欄位
     * @param asc     是否升序
     */
    List<UserPO> listUserOrdrBy(UserPO userPO, String orderby, Boolean asc);

    /**
     * 新增 User 實體對象
     *
     * @param userPO 要新增的對象
     */
    UserPO addUser(UserPO userPO);

    /**
     * 更新 User 實體對象
     *
     * @param userPO 要更新的對象
     */
    UserPO updateUser(UserPO userPO);
}

   開發方式簡單粗暴,使用工具反向生成 hibernate 資料庫 po ,訪問 GenCodeServlet 生成 dao/ws 層介面和實現。

   添加配置文件選項,發佈 cxf webService 服務即可,估計5分鐘的時間都不要。

3. 服務調用方

   發佈的單表服務在調用方裡面理解為資料庫訪問層,在你項目規定的地方註入,進行耦合處理業務邏輯。

   這個模塊存在的意義,相當於一個怎樣集成 cxf 發佈的服務的 demo。

   a.調用方項目中已集成了 spring (依賴 dao-service-api)

    <jaxws:client id="UserWs" serviceClass="com.rambo.dsd.sys.ws.inter.UserWs"  address="${cxf.server.url}/UserWs">
        <jaxws:outInterceptors>
            <ref bean="wss4JOutInterceptor"/>
        </jaxws:outInterceptors>
    </jaxws:client>

   具體的使用方式(在 spring 註入的前提下)

        Map<String, Object> map = new HashMap<>();
        UserWs userWs = (UserWs) SpringContextUtil.getBean("UserWs");
        UserPO user = userWs.getUser("031e7a36972e11e6acede16e8241c0fe");
        map.put("1.獲取單個用戶:", user);

        user.setPhone("18975468245");
        UserPO userPO1 = userWs.updateUser(user);
        map.put("2.更新單個用戶:", userPO1);

        UserPO userPO2 = new UserPO();
        userPO2.setName("rambo");
        userPO2.setPasswd(SecurityUtil.encryptMD5("123456"));
        userPO2.setSex("男");
        userPO2.setYxbz("Y");
        UserPO userPO3 = userWs.addUser(userPO2);
        map.put("3.新增單個用戶:", userPO3);

        UserPO userPO4 = new UserPO();
        userPO4.setSex("男");
        List<UserPO> userPOList = userWs.listUser(userPO4);
        map.put("4.獲取所有的男用戶:", userPOList);

        UserPO userPO5 = new UserPO();
        userPO5.setSex("男");
        List<UserPO> userPOList1 = userWs.listUserOrdrBy(userPO5, "sorts", true);
        map.put("5.獲取所有的男用戶並按照 sorts 欄位排序:", userPOList1);
        return map;

   b.調用方項目中未集成 spring (依賴 dao-service-api)

   使用工具或者命令生成 cxf 服務客戶端,引入工廠模式在使用的地方獲取服務實例,進行耦合即可。

            UserWsImplService userWsImplService = new UserWsImplService(new URL(cxfServerUrl + "/UserWs?wsdl"));
            UserWs userWs = userWsImplService.getUserWsImplPort();
            addWSS4JOutInterceptor(userWs);

            UserPO user = userWs.getUser("031e7a36972e11e6acede16e8241c0fe");
            map.put("1.獲取單個用戶:", user);

            user.setPhone("18975468245");
            UserPO userPO1 = userWs.updateUser(user);
            map.put("2.更新單個用戶:", userPO1);

            UserPO userPO2 = new UserPO();
            userPO2.setUuid(StringUtil.getUUID());
            userPO2.setName("rambo");
            userPO2.setPasswd(SecurityUtil.encryptMD5("123456"));
            userPO2.setSex("男");
            userPO2.setYxbz("Y");
            UserPO userPO3 = userWs.addUser(userPO2);
            map.put("3.新增單個用戶:", userPO3);

            UserPO userPO4 = new UserPO();
            userPO4.setSex("男");
            UserPOArray userPOArray1 = userWs.listUser(userPO4);
            map.put("4.獲取所有的男用戶:", userPOArray1);

            UserPO userPO5 = new UserPO();
            userPO5.setSex("男");
            UserPOArray userPOArray2 = userWs.listUserOrdrBy(userPO5, "sorts", true);
            map.put("5.獲取所有的男用戶並按照 sorts 欄位排序:", userPOArray2.getItem());

4. cxf 安全認證機制

   cxf 採用 soap 通信協議,畢竟是對外發佈出去的服務,安全性還是很重要。

   安全認證引入 cxf ws-security wss4j  攔截器實現,soap 報文頭添加認證信息。

   a.服務端配置

   <!--服務端安全認證回調函數-->
    <bean id="serverAuthCallback" class="com.rambo.dsd.base.handler.CXFServerAuthHandler"/>

    <!--安全日誌認證攔截器-->
    <bean id="wss4JInInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
        <constructor-arg>
            <map>
                <entry key="action" value="UsernameToken"/>
                <entry key="passwordType" value="PasswordDigest"/>
                <entry key="passwordCallbackRef" value-ref="serverAuthCallback"/>
            </map>
        </constructor-arg>
    </bean>

  服務端實現 javax.security.auth.callback.CallbackHandler 的安全回調函數:

public class CXFServerAuthHandler implements CallbackHandler {
    protected final static Logger log = LoggerFactory.getLogger(CXFServerAuthHandler.class);
    private static final Map<String, String> userMap = new HashMap<String, String>();

    static {
        userMap.put("webappA", "webappA2017");
        userMap.put("webappB", "webappB2017");
    }

    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
        for (Callback callback : callbacks) {
            WSPasswordCallback pc = (WSPasswordCallback) callback;

            String clientUsername = pc.getIdentifier();
            String serverPassword = userMap.get(clientUsername);
            log.info(" client:{} is starting webservice...", clientUsername);
            int usage = pc.getUsage();
            if (usage == WSPasswordCallback.USERNAME_TOKEN) {
                pc.setPassword(serverPassword);
            } else if (usage == WSPasswordCallback.SIGNATURE) {
                pc.setPassword(serverPassword);
            }
        }
    }
}

    b.集成 Spring 的客戶端配置

    <!--客戶端安全認證回調函數-->
    <bean id="wsClientAuthHandler" class="com.rambo.dsc.handler.WsClientAuthHandler"/>

    <!--安全認證對外攔截器-->
    <bean id="wss4JOutInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
        <constructor-arg>
            <map>
                <entry key="action" value="UsernameToken"/>
                <entry key="user" value="webappA"/>
                <entry key="passwordType" value="PasswordDigest"/>
                <entry key="passwordCallbackRef" value-ref="wsClientAuthHandler"/>
            </map>
        </constructor-arg>
    </bean> 

   註入的 webService 服務配置攔截器:

        <jaxws:outInterceptors>
            <ref bean="wss4JOutInterceptor"/>
        </jaxws:outInterceptors>

   客戶端實現 javax.security.auth.callback.CallbackHandler 的安全回調函數:

public class WsClientAuthHandler implements CallbackHandler {

    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
        for (Callback callback : callbacks) {
            WSPasswordCallback pc = (WSPasswordCallback) callback;
            pc.setPassword("webappA2017");
        }
    }
}

   c.未集成 Spring 的客戶端進行編碼

  private void addWSS4JOutInterceptor(Object wsClass) {
        Endpoint cxfEndpoint = ClientProxy.getClient(wsClass).getEndpoint();
        Map outProps = new HashMap();
        outProps.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
        outProps.put(WSHandlerConstants.USER,"webappA");
        outProps.put(WSHandlerConstants.MUST_UNDERSTAND, "0");
        outProps.put(WSHandlerConstants.PASSWORD_TYPE, "PasswordDigest");
        outProps.put(WSHandlerConstants.PW_CALLBACK_CLASS, WsClientAuthHandler.class.getName());
        cxfEndpoint.getOutInterceptors().add(new WSS4JOutInterceptor(outProps));
    }

   項目中服務端安全認證使用的是 UsernameToken,cxf 支持認證方式/密碼類型還有很多,當然你也可以自定義安全認證方式。

 4.結束語

   互聯網公司服務架構是血液,是習慣,每家公司都有自己的套路和架構,細節有不同,但是核心理念是通的。

   這次實踐有點微服務的感覺,但還遠遠不夠,如服務的註冊/路由/容錯/緩存.....很多很多,項目已開源到上面,有興趣一起完善它吧。

   


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 把一個 dic綁定到了listview上,有時候下拉列表會報這個異常。因為直接使用了itemssource = dic,而dic在另一個線程上不定期更新,這樣如果直接綁定的話就會報這個錯誤,原因是直接綁定的話會把itemssource的記憶體地址直接指向dic的記憶體地址,當dic更新後,會導致記憶體地址 ...
  • 訂單系統設計 總體設計 1.每次下單時間少於3秒 2.庫存驗證不存在多買的情況 3.訂單能夠按照不同供應商進程拆分 4. 物流信息能夠回傳 訂單狀態機設計 1.待系統審核 2.待支付 3.待發貨 4.待簽收 5.已完成 6.訂單關閉 訂單狀態流轉如下圖示: 1)審核失敗 2)未支付(待支付24小時) ...
  • 1.根據單個分隔字元用split截取 例如 即可得到sArray[0]="GT123",sArray[1]="1"; 2.利用多個字元來分隔字元串 例如 得到sArray[0]="GTAZB",sArray[1]="Jiang",sArray[2]="Ben",sArray[3]="123"; 3根 ...
  • 在前端 UI 開發中,有時,我們會遇到這樣的需求:在一個 ScrollViewer 中有很多內容,而我們需要實現在執行某個操作後能夠定位到其中指定的控制項處;這很像在 HTML 頁面中點擊一個鏈接後定位到當前網頁上的某個 anchor。 要實現它,首先我們需要看 ScrollViewer 為我們提供的 ...
  • TypeInfo,PropertyInfo,MethodInfo,FieldInfo ...
  • 在這篇博客中,我們將介紹如下內容: ==運算符與基元類型 ==運算符與引用類型 ==運算符與String類型 ==運算符與值類型 ==運算符與泛型 ==運算符與基元類型 我們分別用兩種方式比較兩個整數,第一個使用的是Equals(int)方法,每二個使用的是==運算符: 運行上面的示例,兩個語句出的 ...
  • 微軟利用OAuth2為RESTful API提供了完整的鑒權機制,但是可能微軟保姆做的太完整了,在這個機制中指定了數據持久化的方法是用EF,而且對於用戶、許可權等已經進行了封裝,對於系統中已經有了自己的用戶表,和不是採用EF做持久化的系統來說限制太大,不自由,而且現實中很多情況下,授權伺服器和資源服務 ...
  • 背水一戰 Windows 10 之 控制項(集合類 - ItemsControl): SemanticZoom, ISemanticZoomInformation ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...