zoukankan      html  css  js  c++  java
  • 打造独立数据库访问的中间服务

       随着公司业务的不断变化,几年前的 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.结束语

       互联网公司服务架构是血液,是习惯,每家公司都有自己的套路和架构,细节有不同,但是核心理念是通的。

       这次实践有点微服务的感觉,但还远远不够,如服务的注册/路由/容错/缓存.....很多很多,项目已开源到上面,有兴趣一起完善它吧。

       

  • 相关阅读:
    [HDU2866] Special Prime (数论,公式)
    [骗分大法好] 信息学竞赛 骗分导论(论文搬运)
    flayway数据库管理
    RabbitMQ的基本概念与原理
    springboot+ideal实现远程调试
    盘点总结
    mysql查看进程命令
    Java字符串正则文本替换
    springboot代码级全局敏感信息加解密和脱敏方案
    使用PMD进行代码审查
  • 原文地址:https://www.cnblogs.com/java-class/p/7064163.html
Copyright © 2011-2022 走看看