引言
本文中的功能是以7.1快速开发一个微服务为基础, 如果不了解,请先阅读那一篇博客
本文介绍了F1平台的一些常用功能:使用统一权限、使用缓存、使用统一配置、获取常用配置参数、微服务自定义配置参数、使用模型服务对模型数据增删改查、使用工作流服务、BD控件事件定制、Bp控件服务定制、异构数据库支持、多数据源支持、即时推送、jms消息、kafka消息、自动装配组件开发、interface组件开发、服务调用、服务事件扩展
使用统一权限
在当前的微服务中依赖f1-starter-auth(如果已经引入了f1-starter,就会间接引入f1-starter-auth),如果没有授权的请求来访问,就会被拒绝。
application.properties中加入权限服务器参数:
###########################oauth服务器相关配置##################### # 认证服务器凭证 security.sessions:never security.oauth2.client.client-id: client-id security.oauth2.client.client-secret: client-secret security.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=oauth2 security.userOauth.tokenName=access_token security.userOauth.scope.code=write security.userOauth.scope.desc=write app.key=f1swagger app.name=F1平台微服务请求API app.desc=更多的下载资源和信息请查看:http://192.168.1.173/f1-platform/f1-microService/ app.version=3.0.0 app.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.0 app.licenseUrl=http://http://blog.csdn.net/zhbr_f1
加入redis的配置,因为权限认证信息要缓存在redis中
####################### REDIS (RedisProperties) # Redis数据库索引(默认为0 spring.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配置
依赖f1-starter会级联依赖f1-starter-cache
然后在application.properties中加入redis的配置
# REDIS (RedisProperties) # Redis数据库索引(默认为0 spring.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把对应的记录在更新时从缓存中清除
使用统一配置
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 # 配置中心服务id spring.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]:2181 spring.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; }
获取常用配置参数
例如:
String dbtype = Platform.INSTANCE.getString("dbtype"); 就可以得到数据库类型
微服务自定义配置参数
##自定义的参数 self.test.arg1=111111
在java代码中读取 有两种方式,如下所示
@Value("${self.test.arg1}") //第一种方式,用@Value private 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进行增删改查
@Autowired private SingleBDClient singleBDClient; @Override public 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控件事件定制
所用到的依赖: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 @Autowired private GenericDao genericDao; @Override public 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; } @Override public 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 @Autowired private GenericDao genericDao; @Override public 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; } @Override public 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; } }
把当前微服务的id和bp的service的beanid交给前台控件,就可以调用这些自定义的service
异构数据库支持
首先要有依赖:f1-starter-configure 如果已经引入了f1-starter就不用再引入了
在application.properties中加参数
##异构数据库配置(true时每次请求都会加载) platform.config.debugMode=true
在resources下加resource.xml
以查询消息发送时间的sql为例,把几种数据库的sql写到这里边
下边用ResourceManager.getInstance().getDBSS("queryMsgSendTime")从resource.xml中读出对应当前数据库的sql
多数据源支持
首先要在pom中引入f1-starter-data。已经引入了f1-starter就不用再单引了
启动类上用@Import引入DynamicDataSourceRegister 和 DynamicDataSourceAspect
在application中加入自定义的数据源配置ds1和ds2, 这时系统中连默认数据源,共有三个数据源
如下图中是在方法上使用自定义的数据源,就是加上@TargetDataSource("数据源的名字"),也可以加在类上
用jdbc的方式:
用genericDao的方式:
即时推送
加入依赖:
<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消费者
@Component public classMyJmsListener { @JmsListener(destination = "topic01",containerFactory = "myFactory") public void listen1(String foo) { System.out.println(foo); } }
以上jms的配置只适用于生产者和消费者在同一个项目中(因为默认是使用的是内存中的activeMQ,所以二者在不同的项目中无法消息监听),这种情况在不启动activeMQ Server时用于测试还是可以的。
# 使用jms或者是kafka作为消息中间件(jms/kafka) platform.config.messageSwitch=jms ##################### 给jms配置的activeMQ配置 spring.activemq.broker-url=tcp://localhost:61616 spring.activemq.user=admin spring.activemq.password=admin
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 { @Autowired private MsgSender msgSender; /** * @Title: kafkaSend * @Description: kafka消息发送 * @param * @return void * @throws */ public void kafkaSend() { msgSender.send("topic01", "发送kafka消息"); }
kafka消费者:
@Component public 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注解我们的类
@Configuration public class DaoAutoConfigure { @Autowired private 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") // @Autowired public 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组件开发
我们当前微服务的名字: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方法添加服务需要的变量。
@RestController public 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
@RestController public 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 { @Override public 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, 只能有一个扩展,如果再有其它的注册就会报错。