F1V3.0-17 微服務常用功能開發

引言


本文中的功能是以7.1快速開發一個微服務爲基礎, 如果不瞭解,請先閱讀那一篇博客

本文介紹了F1平臺的一些常用功能:使用統一權限、使用緩存、使用統一配置、獲取常用配置參數、微服務自定義配置參數、使用模型服務對模型數據增刪改查、使用工作流服務、BD控件事件定製、Bp控件服務定製、異構數據庫支持、多數據源支持、即時推送、jms消息、kafka消息、自動裝配組件開發、interface組件開發、服務調用、服務事件擴展


使用統一權限


F1平臺的各模塊都是以微服務的形式存在的,都需要有權限認證才能訪問,我們用一個名叫authServer的微服務作爲授權服務器,各微服務都通過授權服務器進行統一權限認證,方便進行權限的管理。給當前微服務加權限的方法如下。

在當前的微服務中依賴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來實現的。使用方法如下。

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

 

如下新建一個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;
	}

}



把當前微服務的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消費者

@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

 

 

這樣一個標準的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, 只能有一個擴展,如果再有其它的註冊就會報錯。

發佈了69 篇原創文章 · 獲贊 25 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章