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

时间:2022-01-17 20:08:51

引言


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

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


使用统一权限


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

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

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

 

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后边只是为了演示)就可以访问了

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


使用缓存


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

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把对应的记录在更新时从缓存中清除


使用统一配置


平台的各个微服务所需要的配置参数有些是相同的,为了避免在每个微服务中都配置相同的参数,我们把这些共享的参数配置在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
# 配置中心服务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;
	}


获取常用配置参数


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

例如:

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


微服务自定义配置参数


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

##自定义的参数
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控件事件定制


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

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

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

 

如下新建一个service, 继承 BaseClsScript

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

 

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

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

 

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

 

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

 

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

 

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


Bp控件服务定制


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

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

引入依赖f1-starter-ui:

F1V3.0-17 微服务常用功能开发如果引入了f1-starter,就不用单独引入f1-starter-ui了


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

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

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

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

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

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

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

@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;
	}

}



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


异构数据库支持


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

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

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

 

在application.properties中加参数

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

在resources下加resource.xml

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

 

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

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

 

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

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


多数据源支持


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

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

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

 

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

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

 

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

 

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

 

 

 

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

用jdbc的方式:

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

 

用genericDao的方式:

 

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

 


即时推送


有时候需要把消息即时推送到前台,比如给正在登录的用户一些消息提醒,就需要把数据推送到页面上,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消费者

@Component
public 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:61616
spring.activemq.user=admin
spring.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 {
	
	@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

 

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

 

这样一个标准的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方法添加服务需要的变量。

 

@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, 只能有一个扩展,如果再有其它的注册就会报错。