F1V3.0-17 微服务常用功能开发

来源:互联网 发布:天下霸唱抄袭 知乎 编辑:程序博客网 时间:2024/06/12 22:06

引言


本文中的功能是以7.1快速开发一个微服务为基础, 如果不了解,请先阅读那一篇博客

本文介绍了F1平台的一些常用功能:使用统一权限、使用缓存、使用统一配置、获取常用配置参数、微服务自定义配置参数、使用模型服务对模型数据增删改查、使用工作流服务、BD控件事件定制、Bp控件服务定制、异构数据库支持、多数据源支持、即时推送、jms消息、kafka消息、自动装配组件开发、interface组件开发、服务调用、服务事件扩展


使用统一权限


F1平台的各模块都是以微服务的形式存在的,都需要有权限认证才能访问,我们用一个名叫authServer的微服务作为授权服务器,各微服务都通过授权服务器进行统一权限认证,方便进行权限的管理。给当前微服务加权限的方法如下。

在当前的微服务中依赖f1-starter-auth(如果已经引入了f1-starter,就会间接引入f1-starter-auth),如果没有授权的请求来访问,就会被拒绝。

 

application.properties中加入权限服务器参数:


###########################oauth服务器相关配置###################### 认证服务器凭证security.sessions:neversecurity.oauth2.client.client-id: client-idsecurity.oauth2.client.client-secret: client-secretsecurity.oauth2.client.access-token-uri: http://IP地址/uaa/oauth/token security.oauth2.client.user-authorization-uri: http://IP地址/uaa/oauth/authorize security.oauth2.resource.user-info-uri: http://IP地址/uaa/user
# 断路器配置共享security上下文hystrix.shareSecurityContext: true###########################swagger兼容授权配置#####################security.userOauth.type=oauth2security.userOauth.tokenName=access_tokensecurity.userOauth.scope.code=writesecurity.userOauth.scope.desc=writeapp.key=f1swaggerapp.name=F1平台微服务请求APIapp.desc=更多的下载资源和信息请查看:http://192.168.1.173/f1-platform/f1-microService/ app.version=3.0.0app.termsOfServiceUrl=http://192.168.1.173/f1-platform/f1-microService/ app.contact.name=平台组app.contact.url=http://http://blog.csdn.net/zhbr_f1 app.contact.email=**app.license=The F1 Platform, Version 3.0app.licenseUrl=http://http://blog.csdn.net/zhbr_f1 


加入redis的配置,因为权限认证信息要缓存在redis中

####################### REDIS (RedisProperties)# Redis数据库索引(默认为0spring.redis.database=0# Redis连接密码spring.redis.password=****# Redis数据库服务地址spring.redis.host=192.168.***.***# Redis服务器连接端口spring.redis.port=6379# 连接池最大连接数(使用负值表示没有限制)spring.redis.pool.max-active=8# 连接池最大阻塞等待时间(使用负值表示没有限制)spring.redis.pool.max-wait=-1# 连接池中的最大空闲连接spring.redis.pool.max-idle=8# 连接池中的最小空闲连接spring.redis.pool.min-idle=0# 连接超时时间(毫秒)spring.redis.timeout=0

在启动类上加上标注:

@EnableOAuth2Sso

这样就只有授权的请求可以访问当前微服务了

在刚才那个url后边加上权限相关的参数(用户名密码认证通过后返回的,真正系统中是自动加上的,这里加到url后边只是为了演示)就可以访问了



使用缓存


在平时的业务操作中,有一些请求会频繁访问数据库取一些重复的值,或是请求的是一些比较耗时的资源,这时我们都可以把请求的结果加到缓存中,来提交访问的效率。平台的缓存主要是用redis来实现的。使用方法如下。

redis配置

依赖f1-starter会级联依赖f1-starter-cache

然后在application.properties中加入redis的配置

 

# REDIS (RedisProperties)# Redis数据库索引(默认为0spring.redis.database=0# Redis连接密码spring.redis.password=sys# Redis数据库服务地址spring.redis.host=localhost# Redis服务器连接端口spring.redis.port=6379# 连接池最大连接数(使用负值表示没有限制)spring.redis.pool.max-active=8# 连接池最大阻塞等待时间(使用负值表示没有限制)spring.redis.pool.max-wait=-1# 连接池中的最大空闲连接spring.redis.pool.max-idle=8# 连接池中的最小空闲连接spring.redis.pool.min-idle=0# 连接超时时间(毫秒)spring.redis.timeout=0


缓存使用

下边在service方法上加一个用name作为key的缓存


@Cacheable(value="queryDb", key="#name")public String queryDb(String name) {//查询数据库示例List<?> ls = genericDao.getDataWithSQL("select count(1) from us_sys.tb_sys_person");int ranNum = new Random().nextInt(100);return ls.get(0).toString()+"人 "+ranNum+name;}


请求时的name不变,返回的值就还是原来的值,注意中间的随机数不会变,因为是从缓存中取的,方法没有被执行


更新

通常都是查询的时候从缓存中查,当这个值更新到数据库中时就更新这条缓存(从缓存中把这条数据清除)


@Override@CacheEvict(value="queryDb", key="#name")public void update(String name) {System.out.println("更新数据时,更新缓存");}


这样就可以用@CacheEvict把对应的记录在更新时从缓存中清除


使用统一配置


平台的各个微服务所需要的配置参数有些是相同的,为了避免在每个微服务中都配置相同的参数,我们把这些共享的参数配置在configServer配置服务器中,使用方法如下。

1.添加依赖

<!-- 需要配置服务器,依赖此项可以读到配置服务器中相应配置文件 --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-config</artifactId></dependency><!-- 监控:配置文件实时可见 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><!-- kafka消息总线 --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bus-kafka</artifactId></dependency>


2.配置参数

# 能否发现配置spring.cloud.config.discovery.enabled=true# 配置中心服务idspring.cloud.config.discovery.serviceId=f1-configserver# 场景spring.cloud.config.profile=dev# 分支spring.cloud.config.label=master# 配置中心地址spring.cloud.config.uri=http://localhost:7001/# 是否使用本地配置文件(当此配置项为native时,将使用本地配置文件)# spring.profiles.active=native # kafka,服务器上搭建的kafka(依赖kafka服务器)spring.cloud.stream.kafka.binder.zk-nodes=[kafkaIp]:2181spring.cloud.stream.kafka.binder.brokers=[kafkaIp]:9092


至此,我们的微服务已经使用了配置服务器提供的配置文件,当我们将git上的配置文件修改了该如何通知所有服务刷新呢?通过发送post请求:http://ip:配置服务器端口/bus/refresh进行配置信息的刷新。

 

3. 读取配置参数值

 

在配置服务器指定的配置文件中加一条参数

configServer.testValue=aaaaaaaallllll

下边可以用@Value把值注入到变量

/** 获取配置服务器中的参数 */@Value("${configServer.testValue}")private String testConfigValue;@Override@Cacheable(value="queryDb", key="#name")public String queryDb(String name) {//查询数据库示例List<?> ls = genericDao.getDataWithSQL("select count(1) from us_sys.tb_sys_person");int ranNum = new Random().nextInt(100);return ls.get(0).toString()+"人 "+ranNum+name+testConfigValue;}


获取常用配置参数


有时候我们在开发时要获取一些平台的配置参数(配置在application.properties中以platform.config开头的),比如获取当前连接的数据库的类型,可以用下边的方式去获取。

例如:

String dbtype = Platform.INSTANCE.getString("dbtype"); 就可以得到数据库类型


微服务自定义配置参数


有时候我们需要配置一些自己的参数,方法如下。
在application.properties配置文件中加一条名叫“self.test.arg1”的参数

##自定义的参数self.test.arg1=111111

在java代码中读取 有两种方式,如下所示

@Value("${self.test.arg1}") //第一种方式,用@Valueprivate String selfArgs;private Environment environment;public String getSelfArgs() {return selfArgs;}public String getPlatformSelfArgs() {return Platform.getPlatform().getString("self.test.", "arg1"); //第二种方式,用Platform取值}

使用模型服务

有时我们需要在后台对模型数据进行增删改查的操作,这时我们就要调用平台提供的模型服务接口,这里以singleDBClient为例对一个模型数据进行操作,具体如下。
增加依赖:

        <dependency>            <groupId>com.joinbright.f1</groupId>            <artifactId>f1-interface-model</artifactId>        </dependency>

在启动类中加标注

@EnableFeignClients("com.jb.*.client")@ComponentScan

调用SingleDBClient进行增删改查

@Autowiredprivate SingleBDClient singleBDClient;@Overridepublic void operModelData() {String clsID = "3AC9D405-B3A7-488B-A662-01ED06D73B60";String appID = "3883C374-E197-4216-83C6-ACE77EB7A0E2";//新增String newGUID = GUID.newGUID();String createData = "{\"GUID\":\""+newGUID+"\",\"BDZMC\":\"变电站修改测试1\",\"TYRQ\":\"2012-09-21 21:23:33\"}";String createReturn = singleBDClient.cmdCreateBD(createData, appID, clsID);System.out.println(createReturn);//查询String getReturn = singleBDClient.cmdGetBD(newGUID, appID, clsID);System.out.println(getReturn);//修改createData = "{\"GUID\":\""+newGUID+"\",\"BDZMC\":\"变电站修改测试1修改\",\"TYRQ\":\""+DateUtil.formatLongtime(new Date())+"\"}";String saveReturn = singleBDClient.cmdSaveBD(createData, appID, clsID);System.out.println(saveReturn);//删除String delReturn = singleBDClient.cmdDeleteBD(newGUID, appID, clsID);System.out.println(delReturn);}

使用工作流服务


有时候我们需要在后台对流程的数据进行操作,发起流程,下一步,结束流程,删除流程,下边对这些操作进行演示。

在pom中加依赖

        <dependency>            <groupId>com.joinbright.f1</groupId>            <artifactId>f1-interface-websocket</artifactId>        </dependency>

在启动类中加标注:

@EnableFeignClients("com.jb.*.client")@ComponentScan

 在下边的代码中调用WorkFlowControlClient进行流程操作


   @Autowired    private WorkFlowControlClient workFlowControlClient;        @Override    publicvoid operWorkflow() {                //发送流程        TransferInfotransferInfo = newTransferInfo();        transferInfo.setContent("你好,这有一个流程");        transferInfo.setForkStep("34870934");//如果流程经过fork节点进行迁移,则该节点设置为fork节点的stepid。        transferInfo.setJoinStep("349875994");//如果流程经过join节点进行迁移,则该节点设置为join节点的stepid。        /*发送信息参数,该对象为list数组,里边存储了下发迁移的具体信息,如果不经过fork         节点则该集合仅有一个元素,进过fork节点可设置多个元素对应发送的多个环节,其子元素SendParam对象的属性有"         orderNo(业务数据主键),nextActorId(下一步执行者),orderType(流程对象类型),"         taskId(任务id,流程启动时设置为0),content(流程审批意见),stepId(迁移到的环节id),"         sendModel(发送模式,多条流程进行发送还是单条流程进行发送,1101702为多条,1101702为单条)"         isSendInteracStep(是否需要发送到分布式流程的交互节点,通常不需要进行设置,如果要迁移到交互节点设置为'true')"         interacStep(分布式流程需要迁移到的交互环节id)" + "carbonCopyPersons(list数组,设置发送到该环节接收站内消息的人员id)"         sendType(发送模式,如果为发送设置为'send',如果是回退设置为'back')*/        transferInfo.setSendParam(newArrayList<SendParam>(){            {                add(new SendParam(){                    {                        setOrderNo("orderNo");                        setNextActorId("nextActorId");                        setOrderType("orderType");                        setTaskId("taskId");                        setContent("content");                        setStepId("stepId");                        setSendModel("sendModel");                        setIsSendInteracStep("isSendInteracStep");                        setInteracStep("interacStep");                        setCarbonCopyPersons(Arrays.asList(new String[]{"293749","2303948"}));                        setSendType("sendType");                    }                });            }        });        //设置一些附加的参数        transferInfo.setVariables(newHashMap<String, Object>(){            {                put("args1", "20973403297");                put("args2", "34083049549");            }        });        InvokeResult irt = workFlowControlClient.sendFlow(transferInfo);                         //从当前待处理环节迁移到任意环节        /*BackParam任意环节发送对象,对应的属性介绍如下:orderNo:业务数据主键。        backTaskActorId:处理当前任务的处理人。nextActorId:迁移到的环节的任务处理者。content:审批意见。        orderType:流程对象标识taskId:当前处理任务的id。        sendModel:发送模式,多条流程进行发送还是单条流程进行发送,1101702为多条,1101702为单条backNodeName:迁移到的环节名称        isSendInteracStep:是否需要发送到分布式流程的交互节点,通常不需要进行设置,如果要迁移到交互节点设置为'true'        interacStep:分布式流程需要迁移到的交互环节id        carbonCopyPersons:list数组,设置发送到该环节接收站内消息的人员id*/        BackParam freeSendParam = new BackParam();        freeSendParam.setOrderNo("orderNo");        freeSendParam.setBackTaskActorId("backTaskActorId");        freeSendParam.setNextActorId("nextActorId");        freeSendParam.setContent("content");        freeSendParam.setOrderType("orderType");        freeSendParam.setTaskId("taskId");        freeSendParam.setSendModel("sendModel");        freeSendParam.setBackNodeName("backNodeName");        freeSendParam.setIsSendInteracStep("isSendInteracStep");        freeSendParam.setInteracStep("interacStep");        freeSendParam.setCarbonCopyPersons(Arrays.asList(new String[]{"293749","2303948"}));        irt = workFlowControlClient.sendFreeNode(freeSendParam);                         //暂停流程        longpid = 1L;        irt = workFlowControlClient.cmdSuspendProcess(pid);                //恢复流程        pid = 1L;        irt = workFlowControlClient.cmdResumeTask(pid);                 //发送流程到结束环节        longtaskId = 1L;        irt = workFlowControlClient.endFlow(taskId);                                  //通过任务id删除流程        taskId = 1L;        irt = workFlowControlClient.deleteFlowByTaskId(taskId);                 //通过流程实例id删除流程        pid = 1L;        irt = workFlowControlClient.deleteFlowByPdId(pid);                         //通过业务主键获取流程实例id        String orderNo = "2934792374928734987";        longpdid = workFlowControlClient.getPdIdByOrderNo(orderNo);    }

 

 

BD控件事件定制


就是模型类的脚本,现在使用的是在模型工具中选中类型右键挂接脚本,写一个类继承BaseClsScript,就可以对这个模型类的事件进行拦截处理。具体方法如下。

所用到的依赖:f1-interface-script

 

如下新建一个service, 继承 BaseClsScript

 

有创建前后、保存前后、删除前后、查询后,共七个事件,需要哪个事件就重写一下哪个事件。

 

然后在模型工具中对类型进行右键挂接脚本,输入当前微服务的id以及新做的service的id

 

 

 


Bp控件服务定制


对于一些非模型数据的控件,我们需要在后台给它们提供一些类来进行数据的操作

bp控件有bpgrid, bptree, combobox,具体实现如下。

引入依赖f1-starter-ui:

如果引入了f1-starter,就不用单独引入f1-starter-ui了


在启动类上加componentScan标注,添加对com.jb.ui包的扫描。

在启动类上加EntityScan标注,添加对新添加实体类的扫描。

@ComponentScan(basePackages={"com.jb.mst","com.jb.ui"})@EntityScan("com.jb.mst.model")

实体类就是一般的Hibernate实体类的基础上,在字段上加@FieldEditor和@JsonProperty

FieldEditor是字段的一些显示属性,JsonProperty是指明实体对象转成JSON时的字段对应的属性名

@Entity@Table(name="tb_app_bdz", catalog="us_app")public class TbAppBdz extends PersistClass {/** TODO */private static final long serialVersionUID = -8103749525304011660L;@GenericGenerator(name = "generator", strategy = "com.jb.dao.id.UIDGenerator")@Id@GeneratedValue(generator = "generator")@Column(name = "GUID", nullable = false, length = 42)@FieldEditor(caption = "唯一标识", isReadOnly=true, dispIndex = 0, hidden = true)@JsonProperty("GUID")private String guid;@Column(name = "OBJ_CAPTION", nullable = true, length = 200)@FieldEditor(caption = "对象标识", isReadOnly=true, dispIndex = 1, hidden = true)@JsonProperty("OBJ_CAPTION")private String obj_caption;


bpgrid的service 继承EntityOperationServiceAdapter

@Service("testBpGridService")@Transactional(value="transactionManager", propagation=Propagation.REQUIRED)publicclass TestBpGridService extends EntityOperationServiceAdapter<TbAppBdz> { } 


bptree的service 继承TreeService


@Service("testBpTreeService")@Transactional(value="transactionManager", propagation=Propagation.REQUIRED)public class TestBpTreeService extends TreeService {//测试url: http://192.168.1.20:8081/zuul/f1-microserviceoftenuse/tree/query.do?service=testBpTreeService&filterStr={}&nodeAttr={"depth":0}&token_seat=token_seat&access_token=5f865d14-5efc-4522-a81c-882c6935ba66@Autowiredprivate GenericDao genericDao; @Overridepublic List<TreeNodeModel> query(TreeNodeModel queryModel, List<FilterModel> filterModels, UserModel userModel) {List<TreeNodeModel> model = new ArrayList<TreeNodeModel>();String sql = ""; // 如果是第0层if (queryModel.getDepth() == 0) {sql = "SELECT DEPT_ID,DEPT_NAME,IFNULL(SUPER_DEPT,'空') SUPER_DEPT " + "  FROM US_SYS.TB_SYS_DEPARTMENT " + " WHERE SUPER_DEPT is null";} else if (queryModel.getDepth() == 1) {sql = "SELECT DEPT_ID,DEPT_NAME,IFNULL(SUPER_DEPT,'空') SUPER_DEPT " + "  FROM US_SYS.TB_SYS_DEPARTMENT " + " WHERE SUPER_DEPT = '"+ queryModel.getId()+ "' AND (SFZF IS NULL or sfzf = 'F') AND DEPT_TYPE = 0100104 ORDER BY DEPT_NAME ";} else {System.out.println(queryModel.getId());sql = "SELECT PERS_ID, PERS_NAME ,'' DN FROM US_SYS.TB_SYS_PERSON WHERE DEPT_ID =  '" + queryModel.getId()+ "'";}@SuppressWarnings("unchecked")List<Object[]> list = (List<Object[]>) genericDao.getDataWithSQL(sql);for (Object[] objs : list) {TreeNodeModel tree = new TreeNodeModel(objs[0].toString(), objs[1].toString());tree.setChecked(queryModel.isChecked());if (queryModel.getDepth() == 2)tree.setLeaf(true);Map<String, String> attr = new HashMap<String, String>();attr.put("bmbm", objs[2].toString());tree.setAttr(attr);model.add(tree);}return model;}@Overridepublic Map<Object, String> putDisplay(Set selectedValueSet) {Map<Object, String> display = new HashMap<Object, String>();DataTable table = null;StringBuffer buffer = new StringBuffer();Iterator it = selectedValueSet.iterator();while (it.hasNext()) {String dStr = (String) it.next();if (buffer.length() > 0) {buffer.append(",");}buffer.append("'");buffer.append(dStr);buffer.append("'");}String values = buffer.toString();if (!StringUtils.isNullOrEmpty(values)) {String sql = String.format("SELECT PERS_ID,PERS_NAME FROM US_SYS.TB_SYS_PERSON   " + " WHERE PERS_ID in (%s) ", values);table = genericDao.exeSql(sql);}for (int i = 0; i < table.getRows().size(); i++) {DataRow dataRow = table.getRows().get(i);display.put(dataRow.getValue(0), dataRow.getValue(1).toString());}return display;}}

combobox的service 继承ComboBoxService


@Service("testComboboxService")@Transactional(value="transactionManager", propagation=Propagation.REQUIRED)public class TestComboboxService extends ComboBoxService {//测试url: http://192.168.1.21:8088/zuul/f1-microserviceoftenuse/comboBox/query.do?service=testComboboxService&token_seat=token_seat&access_token=5f865d14-5efc-4522-a81c-882c6935ba66@Autowiredprivate GenericDao genericDao;@Overridepublic ComboBoxModel query(Map<String, ?> filterMap, boolean blankItem, UserModel userModel) {    String sql = "select code_name,code from us_sys.tb_sys_code where label_code='02001'";    @SuppressWarnings("unchecked")List<Object[]> list = (List<Object[]>)genericDao.getDataWithSQL(sql);    ComboBoxModel cm = new ComboBoxModel();    if(list==null) {return cm;}    for(int i=0; i<list.size();i++){cm.addData(    new String[]{    list.get(i)[0].toString(),    list.get(i)[1].toString()    });    }    return cm;}@Overridepublic Map<Object, String> putDisplay(Set selectedValueSet) {Map<Object, String> display = new HashMap<Object, String>();DataTable table = null;StringBuffer buffer = new StringBuffer();Iterator it = selectedValueSet.iterator();while (it.hasNext()) {String dStr = (String) it.next();if (buffer.length() > 0) {buffer.append(",");}buffer.append("'");buffer.append(dStr);buffer.append("'");}String values = buffer.toString();if (!StringUtils.isNullOrEmpty(values)) {String sql = String.format("select code,code_name from us_sys.tb_sys_code where code in (%s) ", values);table = genericDao.exeSql(sql);}for (int i = 0; i < table.getRows().size(); i++) {DataRow dataRow = table.getRows().get(i);display.put(dataRow.getValue(0), dataRow.getValue(1).toString());}return display;}}



把当前微服务的idbpservicebeanid交给前台控件,就可以调用这些自定义的service


异构数据库支持


有时候对于同一个操作用到的sql在异构的数据库中sql语句是不同的,怎么才能把这些操作对于异构的数据库进行兼容呢,F1平台的解决方法如下。

首先要有依赖:f1-starter-configure 如果已经引入了f1-starter就不用再引入了

 

在application.properties中加参数

##异构数据库配置(true时每次请求都会加载)platform.config.debugMode=true

在resources下加resource.xml

 

以查询消息发送时间的sql为例,把几种数据库的sql写到这里边

 

下边用ResourceManager.getInstance().getDBSS("queryMsgSendTime")从resource.xml中读出对应当前数据库的sql


多数据源支持


有时候有的功能要对不同的数据源进行操作,比如A方法要对A数据库进行操作,同时B方法要对B数据库进行操作,对此F1平台的解决方法如下。

首先要在pom中引入f1-starter-data。已经引入了f1-starter就不用再单引了

 

启动类上用@Import引入DynamicDataSourceRegister 和 DynamicDataSourceAspect

 

在application中加入自定义的数据源配置ds1和ds2, 这时系统中连默认数据源,共有三个数据源

 

 

 

 

如下图中是在方法上使用自定义的数据源,就是加上@TargetDataSource("数据源的名字"),也可以加在类上

用jdbc的方式:

 

用genericDao的方式:

 

 


即时推送


有时候需要把消息即时推送到前台,比如给正在登录的用户一些消息提醒,就需要把数据推送到页面上,F1平台的解决方法如下。

加入依赖:

        <dependency>            <groupId>com.joinbright.f1</groupId>            <artifactId>f1-interface-websocket</artifactId>        </dependency>

在启动类上加标注:@EnableFeignClients("com.jb.**.client")

 

消息推送示例如下:

    @ApiOperation(value = "即时推送示例", httpMethod = "GET", response = String.class, notes = "即时推送示例")    @RequestMapping(value = "immediatePush", method=RequestMethod.GET)    publicvoid immediatePush() {        Message message = new Message("topicid1", "identityId", "消息内容", "发送者", "消息类型");        webSocketClient.sendMessage(message );    }

其中消息内容是字符串,发送者是pers_id,消息类型是(whole:发送消息体Json字符串、content:仅发送消息内容)

jms消息

jms可以解决一个地方发生了某个事件后,可以触发连锁的多个地方的操作,是观察者模式的具体实现,是消息队列的具体应用,F1平台用的activeMQ消息队列,下面是具体的实现方法。

加入依赖:

       <dependency>           <groupId>com.joinbright.f1</groupId>           <artifactId>f1-message</artifactId>       </dependency>

在application.properties中配置消息中间件为jms

 

# 使用jms或者是kafka作为消息中间件(jms/kafka)platform.config.messageSwitch=jms

jms生产者

@Service("jmsSendService")public class JmsSendServiceImpl {         publicvoid jmsSend() {                   msgSender.send("topic01","发送jms消息");         }}

jms消费者

@Componentpublic classMyJmsListener {       @JmsListener(destination = "topic01",containerFactory = "myFactory")    public void listen1(String foo) {       System.out.println(foo);    }}

以上jms的配置只适用于生产者和消费者在同一个项目中(因为默认是使用的是内存中的activeMQ,所以二者在不同的项目中无法消息监听),这种情况在不启动activeMQ Server时用于测试还是可以的。

下边介绍一下用activeMQ Server来实现jms

在生产者和消费者两边的application.properties中都加参数:
指定activeMQ Server的地址
# 使用jms或者是kafka作为消息中间件(jms/kafka)platform.config.messageSwitch=jms##################### 给jms配置的activeMQ配置spring.activemq.broker-url=tcp://localhost:61616spring.activemq.user=adminspring.activemq.password=admin

kafka消息


kafka所实现的功能和jms是类似的,二者是可以相互替换,kafka和jms的activeMQ的不同点见链接:消息中间件ActiveMQ与Kafka对比之Kafka 。下边是F1平台对kafka的实现方法。

加入依赖:

        <dependency>            <groupId>com.joinbright.f1</groupId>            <artifactId>f1-message</artifactId>        </dependency>

在application.properties中加kafka连接参数:

#Kafka配置#地址platform.config.kafka_server=127.0.0.1:64846#消费者组号platform.config.kafka_consumer_group_id=testT#消费者是否自动提交platform.config.kafka_consumer_auto_commit=false#消费者提交间隔platform.config.kafka_consumer_commit_interval=100#消费者session超时时间platform.config.kafka_consumer_session_timeout=15000#在ZK中没有offset值时,consumer应该从哪个offset开始消费platform.config.kafka_consumer_auto_offset_reset=earliest#Key反序列化类platform.config.kafka_consumer_key_deserializer=org.apache.kafka.common.serialization.IntegerDeserializer#value反序列化类platform.config.kafka_consumer_value_deserializer=org.apache.kafka.common.serialization.StringDeserializer    #生产者重试次数platform.config.kafka_producer_retries=0#生产者数据块大小platform.config.kafka_producer_batch_size=16384#生产者逗留时间platform.config.kafka_producer_linger_ms=1#生产者缓存的大小platform.config.kafka_producer_buffer_memory=33554432#Key序列化类platform.config.kafka_producer_key_serializer=org.apache.kafka.common.serialization.IntegerSerializer#value序列化类platform.config.kafka_producer_value_serializer=org.apache.kafka.common.serialization.StringSerializer    #监听容器的数量(并发的数量)platform.config.kafka_concurrency=3#监听容器poll的超时时长platform.config.kafka_poll_timeout=3000

在application.properties中配置消息中间件为kafka

# 使用jms或者是kafka作为消息中间件(jms/kafka)platform.config.messageSwitch=kafka 

kafka生产者:

 

@Service("kafkaSendService")public class KafkaSendServiceImpl implements KafkaSendService {@Autowiredprivate MsgSender msgSender;/** * @Title: kafkaSend * @Description: kafka消息发送 * @param  * @return void * @throws */public void kafkaSend() {msgSender.send("topic01", "发送kafka消息");}


kafka消费者:

 

@Componentpublic class MyKafkaListener {@KafkaListener(topics = "topic01")public void listen1(String foo) {System.out.println(foo);}}

自动装配组件开发


类似于springboot 的starter,提供一些开箱即用的功能。我们以f1-starter-data为例,来初始化sessionFactory,transactionManager来说明自定义starter

编写pom文件

<parent>        <groupId>com.joinbright.f1</groupId>        <artifactId>f1-parent</artifactId>        <version>3.0.0-SNAPSHOT</version>    </parent><dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter</artifactId>    </dependency>    <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-test</artifactId>            <optional>true</optional>    </dependency>

 

编写一个DaoAutoConfigure类内容如下,我们需要使用@Configuration注解我们的类

@Configurationpublic class DaoAutoConfigure {@Autowiredprivate EntityManagerFactory entityManagerFactory;@Bean("sessionFactory")public SessionFactory getSessionFactory() {if (entityManagerFactory.unwrap(SessionFactory.class) == null) {throw new NullPointerException("factory is not a hibernate factory");}return entityManagerFactory.unwrap(SessionFactory.class);}@Bean("genericDao")//@Autowiredpublic GenericDao getDao(SessionFactory sessionFactory){GenericDao dao=new GenericDaoImpl();dao.setSessionFactory(sessionFactory);return dao;}    //txManager事务开启    @Bean("transactionManager")    public HibernateTransactionManager txManager(SessionFactory sessionFactory) throws SQLException {        HibernateTransactionManager hibernateTransactionManager = new HibernateTransactionManager();        hibernateTransactionManager.setSessionFactory(sessionFactory);        return hibernateTransactionManager;    }}


最后resources/META-INF/spring.factories中加入这个DaoAutoConfigure:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\

com.jb.data.autoconfigure.DaoAutoConfigure

 

 

这样一个标准的starter就编写完成,然后我们只需要在具体微服务中引入这个starter就能做到开箱即用。


interface组件开发


当其它微服务想要调用当前微服务中的方法时,我们需要提供一个feign客户端对外开放这些服务。具体的实现方法如下。

我们当前微服务的名字:f1-microServiceOftenUse,其中有一个控制器:HelloWorldController, 我们想要把这个控制器中的功能暴露给其它的服务。

我们新建一个maven项目命名为f1-interface-microServiceOftenUse

pom 中引入如下依赖

<parent>        <artifactId>f1-parent</artifactId>        <groupId>com.joinbright.f1</groupId>        <version>3.0.0-SNAPSHOT</version></parent><dependency>        <groupId>org.springframework.cloud</groupId>        <artifactId>spring-cloud-starter-feign</artifactId></dependency>

然后在新建一个类com.jb.mst.client.HelloWorldControllerClient

@FeignClient(name="f1-microServiceOftenUse")public interface HelloWorldControllerClient {    @RequestMapping(method = {RequestMethod.GET}, value = "/first/operModel.do")    String gethello(String guid);}

对于其他微服务而言只需要引入f1-interface-microServiceOftenUse,就能访问HelloWorldController中的方法了

具体调用方法请参考:使用模型服务、使用工作流服务、BD控件事件定制、即时推送(websocket)这几个部分都是调用的对应微服务的interface来实现的。

 

服务调用


指的就是微服务间调用服务,有时候一个微服务中的功能要依赖其它微服务的功能来实现,这时就需要服务间的调用,有两种方式:1.Ribbon方式(serviceId可以动态改变)、2.Fegin方式(serviceId是静态的),下边是具体的实现方法。

 

Ribbon 方式访问

这里通过ribbon方式访问model-service中的attr/cmdGetAttribute.do方法

通过postParameters.add方法添加服务需要的变量。

 

@RestControllerpublic class RibbonController extends BaseAgent{    private static final Log log = LogFactory.getLog(RibbonController.class);    @Autowired    private RestTemplate restTemplate;    private String serviceid="model-service";    @RequestMapping("/ribbon")    @ResponseBody    public String service1() throws Exception {    HttpHeaders headers =  setHeader();// 获取请求参数MultiValueMap<String, String> postParameters = new LinkedMultiValueMap<String, String>();postParameters.add("guid", "00370C16-4CA9-43BE-88B4-4DA5E4FF4FB8-00096");String url = "http://" + serviceid + "/attr/cmdGetAttribute.do";    HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<MultiValueMap<String, String>>(postParameters, headers);return restTemplate.postForObject(url, requestEntity, String.class);    }}


    

Feign方式

我们需要编写feignClient客户端, 方式和上一小节中的interface组件的实现是一样的。

@FeignClient(name="model-service")public interface FeginClient {    @RequestMapping(method = {RequestMethod.POST}, value = "/attr/cmdGetAttribute.do")    String gethello(String guid);}


在调用feign接口的地方,将上一步的FeignClient 注入进来。下边示例是在一个控制器中调用了feignClient

@RestControllerpublic class FeignControl {    @Autowired    private FeignClient feginClient;    @RequestMapping("/feign")    public String fegin(){        return feignClient.gethello("00370C16-4CA9-43BE-88B4-4DA5E4FF4FB8-00096");    }}


服务事件扩展


有时候平台中一些公共的service的处理结果不满足实际的需要,这时候就需要用事件扩展,事件扩展就是对平台中一些暴露出扩展点service进行扩展,用自定义的方法来实现自己的逻辑。具体实现方法如下。

这里以扩展usermenu的service为例

1.引入依赖(如果已经引入了f1-starter就不用再单独引入f1-starter-listener了):

<dependency><groupId>com.joinbright.f1</groupId><artifactId>f1-starter-listener</artifactId></dependency>

2.加入配置项:

# 启动后执行类(自动注册本服务中扩展类)

context.listener.classes=com.jb.listener.client.starter.RegisterListener

# 通用接口实现类

platform.config.listeners=服务名:扩展类型

服务名是自定义的beanid, 扩展类型是被扩展的类型,

# 启动后执行类(自动注册本服务中扩展类)context.listener.classes=com.jb.listener.client.starter.RegisterListener# 通用接口实现类platform.config.listeners=usermenu:usermenuExtend:0

 

3.扩展类

@Service("usermenuExtend")public class UserMenuExtend extends Listener {@Overridepublic Object extend(Event event) {DataTable t = (DataTable) event.getOrignalData();System.out.println("事件中数据量为:"+t.getRows().size());System.out.println("修改后,事件中数据量为:"+t.getRows().size());return t;}}


然后先启动usermenu原service的微服务,再启动当前扩展的微服务就可以覆盖之前的usermenu服务了。

注:对于一个原service, 只能有一个扩展,如果再有其它的注册就会报错。