搭建基于OAuth2和SSO的开放平台

搭建基于OAuth2和SSO的开放平台

原创文章,转载或摘录请说明文章来源,谢谢!

开放平台介绍

什么是开放平台

开放平台在百科中的定义:
开放平台(Open Platform) 在软件行业和网络中,开放平台是指软件系统通过公开其应用程序编程接口(API)或函数(function)来使外部的程序可以增加该软件系统的功能或使用该软件系统的资源,而不需要更改该软件系统的源代码。

通俗或者说应景点的说法,开放平台,就是互联网企业,将其内部的资源(一般是数据),比如用户数据,平台业务数据,以技术的手段(一般是RESTFul接口API),开放给受控的第三方合作伙伴,活公司内部的其它一些产品,形成一个安全受控的资源暴露平台。

为什么要搭建开放平台

搭建开放平台的意义,一般在于:
1.搭建基于API的生态体系
2.利用开放平台,搭建基于计费的API数据平台
3.为APP端提供统一接口管控平台,类似于网关的概念
4.为第三方合作伙伴的业务对接提供授信可控的技术对接平台

开放平台体系结构图

open

开放平台核心模块

一个典型的开放平台,至少包含以下几个核心模块:
1.平台门户
平台门户负责向第三方展示用于进行业务及技术集成的管理界面,至少包含以下几个功能:
1.服务商入住(第三方合作伙伴入住)
2.应用配置(第三方应用管理)
3.权限申请(一般包括接口权限和字段权限)
4.运维中心(开放平台当前服务器、接口状态,服务商接口告警等)
5.帮助中心(入住流程说明,快速接入说明,API文档等)

2.鉴权服务
鉴权服务负责整个平台的安全性
1.接口调用鉴权(第三方合作伙伴是否有权限调用某接口)
2.用户授权管理(用户对某个第三方应用获取改用户信息的权限管理)
3.用户鉴权(平台用户的鉴权)
4.应用鉴权(第三方合作伙伴的应用是否有权调用该平台)

3.开放接口
开放接口用于将平台数据暴露给合作伙伴
1.平台用户接口(用于获取公司APP生态链中的用户信息)
2.平台数据接口(平台中的一些开放数据)
3.其它业务接口(平台开放的一些业务数据)

4.运营系统
运营系统是整个平台的后台业务管理系统,负责对第三方合作伙伴提出的各种申请进行审核操作,对当前应用的操作进行审计工作,对当前业务健康度进行监控等
1.服务商管理(对第三方合作伙伴的资质进行审核、操作)
2.应用管理(对第三方应用进行审核、上下线管理)
3.权限管理(对合作伙伴申请的资源进行审核、操作)
4.统计分析(监控平台当前运行状态,统计平台业务数据)

OAuth2介绍

什么是OAuth2

百科:OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是OAUTH的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此OAUTH是安全的。oAuth是Open Authorization的简写。

简单来说:OAuth2协议,定义了一套用户、第三方服务和存储着用户数据的平台之间的交互规则,可以使得用户无需将自己的用户名和密码暴露给第三方,即可使第三方应用获取用户在该平台上的数据,最常见的场景便是现在互联网上的各种使用XXX账号登录。

OAuth2协议中角色介绍

OAuth2协议中,共有四个参与方(角色):
1.resource owner:资源拥有者
即用户
2.resource server:资源服务器
即存储用户数据的服务器,一般对外都以RESTFul API的形式暴露用户数据,client使用access token访问resource server申请被保护起来的用户数据
3.client:客户端
即第三方应用
4.authorization server:授权服务器
用来鉴权第三方应用合法性,并对用户登录、是否授权第三方应用获取数据进行响应,并根据用户操作,想第三应用颁发用户token或者告知授权失败

OAuth2常用协议介绍

OAUTH2标准业务协议,如下图所示
oauth
A.第三方应用向用户请求授权,希望获取用户数据
B.用户同意授权
C.第三方应用拿着用户授权,向平台索要用户access token
D.平台校验第三应用合法性及用户授权真实性后,向平台发放用户access token
E.第三方应用拿着用户access token向平台索要用户数据
F.平台在校验用户access token真实性后,返回用户数据

OAuth2使用场景介绍

目前,OAuth2协议使用最多的场景还是用以给第三方应用获取用户信息,业务流程如下图所示
case
1.在浏览器中,用户点击第三方应用按钮,由第三方应用发起请求,向平台发起授权请求。
2.平台在接收到第三方应用请求后,浏览器跳转用户登录界面,请求用户进行登录。
3.用户在平台登录界面输入用户名、密码进行登录
4.平台判断用户合法性,校验失败,在浏览器中提示错误原因
5.平台判断用户是否需要对该第三方应用进行授权。(不需要授权的情况有两种:a.平台信任该第三方应用,如公司内部应用,无需用户进行授权,默认给予用户数据。b.该用户之前已经给该应用授予过权限,并且仍在有效期内)
6.如需授权,平台跳转浏览器界面至授权界面,告知用户将授予哪个第三方哪些数据权限
7.用户授权后,将用户授权码回调给第三方url
8.第三方在获取用户授权码后,带着用户授权码访问平台鉴权接口,请求用户token
9.平台在收到第三方请求后,校验授权码真实性,并返回用户token
10.第三方使用用户token向平台请求用户接口
11.平台接口判断用户token真实性,并向第三方返回用户数据

OAuth2核心功能说明

1.应用注册
应用注册后,OAuth2会下发应用app_id和app_secret,用以标记该应用的唯一性,并且这两个参数将贯穿整个OAuth协议,用以对应用合法性进行校验。同时,应用需要提供redirect_uri,用以和平台进行异步交互,获取用户令牌及错误信息。
2.授权/鉴权中心
a.对用户的应户名、密码进行鉴权
b.对第三方应用的app_id,app_secret进行鉴权
c.展示授权界面,并对用户对第三方应用的授权操作进行响应
d.对用户授权码及用户token的真实性进行鉴权
3.token管理
a.创建token、刷新token
b.查询token详细数据
c.校验token时效性

OAuth2体系结构

case

开放平台集成OAuth2体系

1.平台门户:
门户应用入住界面,需要集成OAuth2应用创建接口,录入第三方回调地址,并回显app_id和app_secret参数
2.鉴权服务:
鉴权服务需集成OAuth2的authorize及token接口,用以提供用户授权及code/token鉴权功能
3.开放接口:
开放接口需集成OAuth2的resource server角色,对用户数据进行安全管理,对第三方应用发起的请求做出响应,并对token进行真实性校验
4.运营系统:
运营系统需提供对当前OAuth2应用的管理功能,用户授权列表管理,用户token管理等OAuth2协议相关管理功能。

SSO介绍

什么是SSO

百科:SSO英文全称Single Sign On,单点登录。SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。它包括可以将这次主要的登录映射到其他应用中用于同一个用户的登录的机制。它是目前比较流行的企业业务整合的解决方案之一。

简单来说,SSO出现的目的在于解决同一产品体系中,多应用共享用户session的需求。SSO通过将用户登录信息映射到浏览器cookie中,解决其它应用免登获取用户session的问题。

为什么需要SSO

开放平台业务本身不需要SSO,但是如果平台的普通用户也可以在申请后成为一个应用开发者,那么就需要将平台加入到公司的整体账号体系中去,另外,对于企业级场景来说,一般都会有SSO系统,充当统一的账号校验入口。

CAS协议中概念介绍

SSO单点登录只是一个方案,而目前市面上最流行的单端登录系统是由耶鲁大学开发的CAS系统,而由其实现的CAS协议,也成为目前SSO协议中的既定协议,下文中的单点登录协议及结构,均为CAS中的体现结构
CAS协议中有以下几个概念:
1.CAS Client:需要集成单点登录的应用,称为单点登录客户端
2.CAS Server:单点登录服务器,用户登录鉴权、凭证下发及校验等操作
3.TGT:ticker granting ticket,用户凭证票据,用以标记用户凭证,用户在单点登录系统中登录一次后,再其有效期内,TGT即代表用户凭证,用户在其它client中无需再进行二次登录操作,即可共享单点登录系统中的已登录用户信息
4.ST:service ticket,服务票据,服务可以理解为客户端应用的一个业务模块,体现为客户端回调url,CAS用以进行服务权限校验,即CAS可以对接入的客户端进行管控
5.TGC:ticket granting cookie,存储用户票据的cookie,即用户登录凭证最终映射的cookies

CAS核心协议介绍

case
1.用户在浏览器中访问应用
2.应用发现需要索要用户信息,跳转至SSO服务器
3.SSO服务器向用户展示登录界面,用户进行登录操作,SSO服务器进行用户校验后,映射出TGC
4.SSO服务器向回调应用服务url,返回ST
5.应用去SSO服务器校验ST权限及合法性
6.SSO服务器校验成功后,返回用户信息

CAS基本流程介绍

以下为基本的CAS协议流程,图一为初次登录时的流程,图二为已进行过一次登录后的流程
case
case

代码及示例

spring提供了整套的开源包,用以搭建OAUTH2+SSO的体系:
1.spring-oauth2:用以实现OAuth2协议,提供了上述所有四个角色提供的功能
2.spring-cas:用以实现和cas的集成,将OAuth2的登录、登出功能委托给CAS处理,并提供了统一的回调机制及凭证校验机制
3.CAS,耶鲁大学官方提供的SSO开源实现,本文的单点登录协议即按照CAS进行的说明

本文还提供了基于GO语言实现的简单OAuth2+SSO功能,详见github:
https://github.com/janwenjohn/go-oauth2-sso

搭建基于consul,registrator,nsq的GO体系Docker开发环境

使用GO作为生产服务开发语言,避免不了与以下几大基础组件打交道,他们分别是:

consul,registrator,nsq

consul

Consul 是一个支持多数据中心分布式高可用的服务发现和配置共享的服务软件,通常用于docker 实例的注册与配置共享

registrator

Registrator 去除了需要手动管理Consul服务条目的复杂性,它监视容器的启动和停止,根据容器暴露的端口和环境变量自动注册服务。

nsq

NSQ是一个基于Go语言的分布式实时消息平台

基于Docker容器的组件搭建

以上组件都提供了官方docker镜像,我们只需要拉取镜像启动即可,以下提供组件使用命令,可供单机开发使用,复杂如consul组集群等,不在此讨论

consul启动命令
docker stop dev-consul
docker rm dev-consul
docker run -d --name=dev-consul -p 8400:8400 -p 8500:8500/tcp -p 8600:53/udp -e 'CONSUL_LOCAL_CONFIG={"bootstrap_expect":1,"datacenter":"dc1","data_dir":"/usr/local/bin/consul.d/data","server":true}' consul agent -server -bind=127.0.0.1 -client=0.0.0.0

bind为服务发布地址,默认为网卡发布,否则可能只发布到容器内部ip

registrator启动命令
docker stop registrator
docker rm registrator
docker run -d --restart=always --name=registrator \
-v /var/run/docker.sock:/tmp/docker.sock \
gliderlabs/registrator:latest -ip yourip  consul://yourip:8500

其中ip地址为该服务器ip地址,主动申明的目的在于防止registrator自动将127.0.0.1本地回环作为对外暴露的ip地址注册到consul上。

nsq启动命令

nsqlookupd: docker run -d --restart=always --name lookupd \ -p 4160:4160 \ -p 4161:4161 \ nsqio/nsq /nsqlookupd nsqd: docker run -d --restart=always --name nsqd \ -p 4150:4150 \ -p 4151:4151 \ nsqio/nsq /nsqd --broadcast-address=yourip --lookupd-tcp-address=yourip:4160 nsqadmin: docker run -d --restart=always --name nsqadmin \ -p 4171:4171 \ nsqio/nsq /nsqadmin --lookupd-http-address=yourip:4161

Docker配置使用harbor私服

Docker默认使用docker-hub拉取镜像,使用harbor私服,必须对docker进行配置,否则docker在拉取镜像时会报错

1. 添加harbor私服配置

创建或修改以下文件:

vim /etc/docker/daemon.json

添加或增加harbor配置:

{ "insecure-registries":["myhostname"] }
2. 登录harbor
docker login -u admin -p Harbor12345 yourhostname

登录完成后会在docker配置中增加一条授权记录

cat /root/.docker/config.json

Docker私服Harbor安装

Docker私服一般选用vmware的harbor

官方地址:

https://github.com/vmware/harbor/releases

1. 安装dcoker-compose

harbor依赖docker-compose组件,首先安装docker-compose

docker-compose官方地址:

https://github.com/docker/compose/releases

安装命令:

curl -L https://github.com/docker/compose/releases/download/1.13.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
2. 安装harbor安装包

harbor安装包分为离线安装和在线安装,考虑到国内网络环境,一般选择离线安装

下载最新版本,目前为1.1.1
wget https://github.com/vmware/harbor/releases/download/v1.1.1/harbor-offline-installer-v1.1.1.tgz
解压缩
tar xzvf harbor-offline-installer-v1.1.1.tgz
修改配置文件

进入harbor安装目录,修改配置文件

vim harbor.cfg

以下必须修改:

hostname ,harbor启动后的访问ip或者域名

以下酌情修改:

harbor_admin_password ,harbor的默认用户名密码,默认为:admin/Harbor12345

安装harbor
./install.sh
3. 验证harbor

浏览器打开以下url:
http://yourhostname/harbor

4. 启停harbor

harbor使用docker作为容器,启停harbor前,必须首先启动docker服务

停止harbor
docker-compose stop
启动start
docker-compose stop

Centos安装Docker特定版本

某些情况下,不希望安装官方最新版本,这个时候就需要指定版本进行安装

1. 前置条件
必须是CENTOS7,64位
2. 安装源
sudo tee /etc/yum.repos.d/docker.repo
<<-'EOF'
[dockerrepo]
name=Docker Repository
baseurl=https://yum.dockerproject.org/repo/main/centos/7/
enabled=1
gpgcheck=1
gpgkey=https://yum.dockerproject.org/gpg
EOF
3. 选择版本号

进入

https://yum.dockerproject.org/repo/main/centos/7/Packages/

选择需要安装的版本号,如:

docker-engine-1.12.4-1.el7.centos.src.rpm
docker-engine-1.12.4-1.el7.centos.x86_64.rpm
docker-engine-1.12.5-1.el7.centos.src.rpm
docker-engine-1.12.5-1.el7.centos.x86_64.rpm

4. 安装依赖包

docker-engine依赖docker-engine-selinux包,先安装相同版本的docker-engine-selinux包:

yum install docker-engine-selinux-1.12.5-1.el7.centos.noarch.rpm

5. 安装Docker

yum install docker-engine-1.12.5-1.el7.centos.x86_64.rpm

6. 启动Docker
sudo systemctl start docker
7. 测试Docker
sudo docker run hello-world

Centos安装Docker最新版

官方文档:

https://store.docker.com/editions/community/docker-ce-server-centos?tab=description

1. 前置条件
必须是CENTOS7,64位
2. 安装源

安装必要工具集

sudo yum install -y yum-utils

安装docker官方源

sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo

更新yum缓存

sudo yum makecache fast
3. 安装Dcoker
sudo yum -y install docker-ce
4. 启动Docker
sudo systemctl start docker
5. 测试Docker
sudo docker run hello-world

hibernate自动添加永真1=1,导致Druid sql防火墙报错的问题

最近工程突然报错:
java.sql.SQLException: sql injection violation, part alway true condition not allow : select count(*) where this_.id<>? and 1=1
at com.alibaba.druid.wall.WallFilter.check(WallFilter.java:671)
at com.alibaba.druid.wall.WallFilter.connection_prepareStatement(WallFilter.java:214)

错误内容是druid的sql防火墙报警,发现是hibernate自动拼接了1=1的永真条件,而druid只会放行排在第一的永真条件,查看hibernate源代码后发现,当引用Junction生成sql时,如果条件为空,则会自动拼接1=1的永真条件。

解决办法也很简单,修改自己的代码,将引用junction的条件拼接放在第一位即可。

druid升级到最新的1.0.16-SNAPSHOT引发的配置问题

盲升druid至最新的1.0.16-SNAPSHOT版本后,启动工程报如下错误:
[com.alibaba.druid.pool.vendor.MySqlValidConnectionChecker]-[WARN] Unexpected error in ping
Caused by: java.lang.IllegalArgumentException: timeout can’t be negative

查源码后发现,原来validationQueryTimeout变量默认值为-1,而不是0,导致socket接口报错。

解决办法:
在datasource中增加以下配置:

<property name="validationQueryTimeout" value="10000" />

vert.x笔记:6.vert.x集群化部署

vert.x支持集群化部署,默认封装使用的是一个叫Hazelcast的框架,从官方github上看到的开发进度表示,3.1可能会引入比较大众点的zookeeper作为集群的协作框架。

demo工程还是使用第5章中的dubbo服务demo代码

修改启动类:

package com.heartlifes.vertx.demo.dubbo;

import io.vertx.core.AsyncResult;
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
import io.vertx.core.spi.cluster.ClusterManager;
import io.vertx.spi.cluster.hazelcast.HazelcastClusterManager;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.hazelcast.config.Config;
import com.hazelcast.config.GroupConfig;

public class SpringMain {

    private static Vertx vertx = Vertx.vertx();// 集群初始化失败的情况下,可以使用默认vertx实例
    private static ApplicationContext ctx = null;

    public static void main(String[] args) {
        // 配置文件方式
        ctx = new ClassPathXmlApplicationContext("dubbo-consumer.xml");
        // Hazelcast配置类
        Config cfg = new Config();
        // 加入组的配置,防止广播环境下,负载串到别的开发机中
        GroupConfig group = new GroupConfig();
        group.setName("p-dev");
        group.setPassword("p-dev");
        cfg.setGroupConfig(group);
        // 申明集群管理器
        ClusterManager mgr = new HazelcastClusterManager(cfg);
        VertxOptions options = new VertxOptions().setClusterManager(mgr);
        // 集群化vertx
        Vertx.clusteredVertx(options, SpringMain::resultHandler);

    }

    private static void resultHandler(AsyncResult<Vertx> res) {
        // 如果成功,使用集群化的vertx实例
        if (res.succeeded()) {
            vertx = res.result();
            // 这里要注意,一定要在异步回调中,获取了vertx实例后,再去部署模块
            // 由于vert.x所有内部逻辑都是异步调用的,所以,如果你在异步回调前就去部署模块,最终会导致集群失败
            deploy(vertx);
        } else {
            System.out.println("cluster failed, using default vertx");
            deploy(vertx);
        }
    }

    private static void deploy(Vertx vertx) {
        vertx.deployVerticle(new SpringVerticle(ctx));
        vertx.deployVerticle(new ServerVerticle());
    }

}

启动多个主程序,会发现后台输出类似如下的日志信息

八月 04, 2015 2:05:09 下午 com.hazelcast.instance.DefaultAddressPicker
信息: [LOCAL] [p-dev] [3.5] Prefer IPv4 stack is true.
八月 04, 2015 2:05:09 下午 com.hazelcast.instance.DefaultAddressPicker
信息: [LOCAL] [p-dev] [3.5] Picked Address[192.168.1.119]:5701, using socket ServerSocket[addr=/0:0:0:0:0:0:0:0,localport=5701], bind any local is true
八月 04, 2015 2:05:09 下午 com.hazelcast.spi.OperationService
信息: [192.168.1.119]:5701 [p-dev] [3.5] Backpressure is disabled
八月 04, 2015 2:05:09 下午 com.hazelcast.spi.impl.operationexecutor.classic.ClassicOperationExecutor
信息: [192.168.1.119]:5701 [p-dev] [3.5] Starting with 2 generic operation threads and 4 partition operation threads.
八月 04, 2015 2:05:10 下午 com.hazelcast.system
信息: [192.168.1.119]:5701 [p-dev] [3.5] Hazelcast 3.5 (20150617 - 4270dc6) starting at Address[192.168.1.119]:5701
八月 04, 2015 2:05:10 下午 com.hazelcast.system
信息: [192.168.1.119]:5701 [p-dev] [3.5] Copyright (c) 2008-2015, Hazelcast, Inc. All Rights Reserved.
八月 04, 2015 2:05:10 下午 com.hazelcast.instance.Node
信息: [192.168.1.119]:5701 [p-dev] [3.5] Creating MulticastJoiner
八月 04, 2015 2:05:10 下午 com.hazelcast.core.LifecycleService
信息: [192.168.1.119]:5701 [p-dev] [3.5] Address[192.168.1.119]:5701 is STARTING
八月 04, 2015 2:05:14 下午 com.hazelcast.cluster.impl.MulticastJoiner
信息: [192.168.1.119]:5701 [p-dev] [3.5] 


Members [1] {
    Member [192.168.1.119]:5701 this
}

八月 04, 2015 2:05:14 下午 com.hazelcast.core.LifecycleService
信息: [192.168.1.119]:5701 [p-dev] [3.5] Address[192.168.1.119]:5701 is STARTED
八月 04, 2015 2:05:15 下午 com.hazelcast.partition.InternalPartitionService
信息: [192.168.1.119]:5701 [p-dev] [3.5] Initializing cluster partition table first arrangement...
八月 04, 2015 2:05:22 下午 com.hazelcast.nio.tcp.SocketAcceptor
信息: [192.168.1.119]:5701 [p-dev] [3.5] Accepting socket connection from /192.168.1.119:59906
八月 04, 2015 2:05:22 下午 com.hazelcast.nio.tcp.TcpIpConnectionManager
信息: [192.168.1.119]:5701 [p-dev] [3.5] Established socket connection between /192.168.1.119:5701
八月 04, 2015 2:05:28 下午 com.hazelcast.cluster.ClusterService
信息: [192.168.1.119]:5701 [p-dev] [3.5] 

Members [2] {
    Member [192.168.1.119]:5701 this
    Member [192.168.1.119]:5702
}

八月 04, 2015 2:05:29 下午 com.hazelcast.partition.InternalPartitionService
信息: [192.168.1.119]:5701 [p-dev] [3.5] Re-partitioning cluster data... Migration queue size: 135
八月 04, 2015 2:05:30 下午 com.hazelcast.partition.InternalPartitionService
信息: [192.168.1.119]:5701 [p-dev] [3.5] All migration tasks have been completed, queues are empty.

vert.x笔记:5.vert.x集成dubbo服务

vert.x

基础介绍:

dubbo是阿里巴巴内部的rpc远程调用框架,和spring无缝对接,自带loadbalance,是用来搭建soa服务架构的利器,可惜听说在阿里内部斗争中,已经被hsf干掉了。但是,对于我们这种小企业来说,dubbo还是搭建高可用服务的不二选择。dubbo官方地址:http://dubbo.io

vert.x+dubbo可以搭建一个逼格很高的微服务架构,即vert.x用于发布服务,通过事件总线,调用后端的dubbo业务处理服务。从而完成rest服务与业务代码的完美解耦。

本章基用到前序章节的所有知识,并且这里将不会介绍dubbo服务的开发,默认你会玩dubbo服务。

pom中加入以下依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>dubbo</artifactId>
    <version>${dubbo.version}</version>
    <exclusions>
            <exclusion>
            <groupId>org.springframework</groupId>
            <artifactId>spring</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.3.6</version>
    <exclusions>
        <exclusion>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>com.github.sgroschupf</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.1</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.1.41</version>
</dependency>
<dependency>
        <groupId>org.jboss.netty</groupId>
    <artifactId>netty</artifactId>
    <version>3.2.5.Final</version>
</dependency>

引用一个dubbo服务:

所谓集成dubbo,从本质来讲就是配置dubbo服务,并且以xml形式集成spring。
我们假设后台有这么一个dubbo服务,现在要在vert.x中调用,那么我们的做法和普通的dubbo consumer一样,申明一个spring配置文件,并引用该服务。配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee"
    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
        http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd
        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
        http://code.alibabatech.com/schema/dubbo 
        http://code.alibabatech.com/schema/dubbo/dubbo.xsd
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop-3.2.xsd ">

    <!-- 使用Annotation自动注册Bean,解决事物失效问题:在主容器中不扫描@Controller注解,在SpringMvc中只扫描@Controller注解。 -->
    <context:component-scan base-package="com"/>
    <dubbo:application name="demo-consumer" />
    <dubbo:registry address="multicast://224.5.6.7:1234" />
    <dubbo:protocol name="dubbo" host="127.0.0.1" port="20802"
        serialization="hessian2" threadpool="cached" threads="1000" />
    <!-- dubbo引用的服务 -->
    <dubbo:reference id="dubboService"
        interface="com.heartlifes.dubbo.DubboService" />
</beans> 

创建SpringVerticle:

package com.heartlifes.vertx.demo.dubbo;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Handler;
import io.vertx.core.eventbus.Message;

import org.springframework.context.ApplicationContext;

public class SpringVerticle extends AbstractVerticle {

    private DubboService service;

    public static final String PRINT_MSG_SERVICE_ADDRESS = "print_msg_service_address";

    public static final String GET_MSG_SERVICE_ADDRESS = "get_msg_service_address";

    public SpringVerticle(ApplicationContext ctx) {
        // 从spring上下文获取service
        this.service = (DubboService) ctx.getBean("dubboService");
    }

    @Override
    public void start() throws Exception {
        // 唤起事件总线,注册一个事件处理者,或者直译叫事件消费者
        vertx.eventBus().<String> consumer(PRINT_MSG_SERVICE_ADDRESS)
                .handler(msg -> {
                    // 获取事件内容后,调用service服务
                    // 这里是非阻塞式调用
                        service.printMsg("Asynchronous call dubbo service!!!");
                        msg.reply("success");
                    });

        vertx.eventBus().<String> consumer(GET_MSG_SERVICE_ADDRESS, printMsg());
    }

    // 模拟dubbo服务要从后台数据库获取数据,所以这里就是vert.x中的阻塞式调用
    // vert.x中规定,所有调用不可以阻塞其eventloop,所以当有数据库调用、thread.sleep等可能会阻塞线程的服务调动时
    // 需要使用vertx接口中的阻塞式调用接口
    private Handler<Message<String>> printMsg() {
        return msg -> {
            System.out.println("bus msg body is:" + msg.body());
            // 阻塞式接口调用
            vertx.<String> executeBlocking(future -> {
                // 通过future等待调用返回结果
                    String dubboMsg = "";
                    try {
                        dubboMsg = this.service.getMsg();
                    } catch (Exception e) {
                        e.printStackTrace();
                        future.fail(e);
                    }
                    // 把结果放到result中
                    future.complete(dubboMsg);
                }, result -> {
                    // 判断接口调用结果,成功的话讲结果放到事件总线的msg中传递给server端展示
                    if (result.succeeded()) {
                        System.out.println("msg from dubbo service is: "
                                + result.result());
                        msg.reply(result.result());
                    }
                    if (result.failed()) {
                        msg.fail(400, result.cause().getMessage());
                    }
                });
        };
    }

}

创建SpringVerticle:

package com.heartlifes.vertx.demo.dubbo;

import io.vertx.core.AbstractVerticle;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;

/**
 * 基本代码注释,请参见vert.x笔记:3.使用vert.x发布restful接口
 * 
 * @author john
 *
 */
public class ServerVerticle extends AbstractVerticle {

    @Override
    public void start() throws Exception {
        Router router = Router.router(vertx);
        router.route().handler(BodyHandler.create());
        router.route("/dubbo/get").handler(
        // 唤起vert.x的事件总线,并发送一个简单消息
                ctx -> vertx.eventBus().<String> send(
                        SpringVerticle.GET_MSG_SERVICE_ADDRESS,// 消息地址
                        "event bus calls dubbo service",// 消息内容
                        result -> {// 异步结果处理
                            if (result.succeeded()) {
                                // 成功的话,返回处理结果给前台,这里的处理结果就是service返回的一段字符串
                                ctx.response()
                                        .putHeader("content-type",
                                                "application/json")
                                        .end(result.result().body());
                            } else {
                                ctx.response().setStatusCode(400)
                                        .end(result.cause().toString());
                            }
                        }));
        router.route("/dubbo/print").handler(
        // 唤起vert.x的事件总线,并发送一个简单消息
                ctx -> vertx.eventBus().<String> send(
                        SpringVerticle.PRINT_MSG_SERVICE_ADDRESS,// 消息地址
                        "event bus calls dubbo service",// 消息内容
                        result -> {// 异步结果处理
                            if (result.succeeded()) {
                                // 成功的话,返回处理结果给前台
                                ctx.response()
                                        .putHeader("content-type",
                                                "application/json")
                                        .end("success");
                            } else {
                                ctx.response().setStatusCode(400)
                                        .end(result.cause().toString());
                            }
                        }));
        vertx.createHttpServer().requestHandler(router::accept).listen(8080);
    }
}

创建启动器:

package com.heartlifes.vertx.demo.dubbo;

import io.vertx.core.Vertx;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringMain {

    public static void main(String[] args) {
        // 配置文件方式
        ApplicationContext ctx = new ClassPathXmlApplicationContext(
                "dubbo-consumer.xml");
        Vertx vertx = Vertx.vertx();
        // 部署spring模块
        vertx.deployVerticle(new SpringVerticle(ctx));
        // 部署服务器模块
        vertx.deployVerticle(new ServerVerticle());
    }

}