打造獨立數據庫訪問的中間服務

   隨著公司業務的不斷變化,幾年前的 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 經我重新組織已開源到:。

   需要這方面實踐的同學,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.結束語

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

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

   來源:itnose

上一篇: 再談AbstractQueuedSynchronizer:獨占模式

下一篇: 七、spring boot 1.5.4 集成shiro+cas,實現單點登錄和權限控制

分享到: 更多
福彩快三稳赚技巧 海南彩票七星彩开奖结果 11选5怎么玩才能赚钱 大乐透走势图40期 通比牛牛官网 澳客彩票网 双色球开奖软件哪个好 棋牌游戏送现金20元 极速pk10app开奖 快三稳赚不赔技巧 LG游戏平台网址 北京pk10单吊有啥规律 怎样赌龙虎稳赢 黑龙江时时彩 时时彩组六稳赚不赔 牛看4张牌抢庄老是输