1. 引言
1.1. 应用背景
随时企业的IT系统建设越来越多,往往一个企业中形成各种独立的系统,各系统相对独立,缺乏流程支掌,为达到通过构建流程服务中心向各处应用系统提供流程服务,同时将各个独立的系统以流程中心为扭带链接起来。
流程中心仅是作为后台管理,对于用户来说并不需要登录此平台上做相关的业务操作管理。流程中心作为应用系统的服务端,应用系统作为客户端。流程中心相对前端的客户来说是一个黑盒模式。
F2BPM流程服务中心(简称:F2流程中心),是指将F2-BPM做为流程平台独立部署成流程中心的方式,其它应用系统调用F2流程服务中心进行流程流转的使用场景。
由于每个企业自身的IT系统应用环境千差万别,本文档给出F2流程中心应用到企业中作为流程中心的常见应用解决方案。
目前越来越多的企业架构解决方案更加趋向于基于http协议“微服务”架构,即通过RESTfull方式进行交互,更加轻量整合调用更上方便。F2流程中心应用方案也是建议采用轻量级的RESTfull方案。
1应用方案模式
由于企业的IT建设的环境不心相同,构建流程中心的方案也会有所不同,由于独立部署面临的一个最大问题就:用户组织架构问题、登录授权身份问题。所以会有不同的企业IT环境会有不同的应用方案模式。
- 方案模式一:流程中心和应用系统共同相同数据库,程序独立部署方式
- 方案模式二:流程中心和应用系统的数据库和程序都是独立部署方式
- 方案模式三:有统一人事系统用户认证服务器,流程中心和应用系统的数据库和程序都是独立部署
2. 共用数据库模式
共用数据库模式是指流程中心与应用系统使用相同的数据,将流程中心的所有表建包括流程中心平台用户组织表都创建在应用系统所在的数据库中。
此时流程中心的用户组织架构管理仅是作为流程中心平台管理员使用的用户组织,用于登录维护管理流程中心相关的事务。流程中心的用户组织与应用系统的用户组织无关。而当调用流程中心执行流程流转时用户组织是通过引擎的用户组织架构接入的配置读取应用系统的用户组织,依然使用的是应用系统自身的用户组织架构。流程引擎使用的是应用系统自身的用户组织架构,用户组织架构的维护管理依然是由应用系统自身来管理。
RESTfull的数据交互机制详细见Oauth2.0接口交互授权。
3. 独立程序和数据库部署模式
数据库和程序都是独立部署模式是指流程中心与应用系统使用各自的数据库。
此时流程中心自身数据库中的用户组织架构管理仅是作为流程中心平台管理员使用的用户组织,用于登录维护管理流程中心相关的事务。流程中心的用户组织与应用系统的用户组织无关。
而当调用流程中心执行流程流转时用户组织是通过重写F2用户组织架构接口的实现读取应用系统的用户组织架构,依然使用的是应用系统自身的用户组织架构。流程引擎使用的是重写实现组织接口的应用系统自身的用户组织架构,用户组织架构的维护管理依然是由应用系统自身来管理。
4. 使用统一人事系统用户服务器模式
使用统一人事用户系统模式是最复杂但也是整体企业信息化环境比较好的方式,一般应用于比较大的集团企业,他们的特点是数据库和程序都是独立部署,流程中心与应用系统使用各自的数据库,同时各应用系统都是统一使用HR系统的用户组织架构。
此时流程中心自身数据库中的用户组织架构管理仅是作为流程中心平台管理员使用的用户组织,用于登录维护管理流程中心相关的事务。流程中心的用户组织与应用系统的用户组织无关。
而当调用流程中心执行流程流转时用户组织是通过重写F2用户组织架构接口的实现读取应用系统的用户组织架构,依然使用的是应用系统自身的用户组织架构。流程引擎使用的是重写实现组织接口的应用系统自身的用户组织架构,用户组织架构的维护管理依然是由应用系统自身来管理。
因企业信息化环境差异大,此方案需要各方一同共同实施才能达到比较好的效果。我们可以合作实施。
5. RESTfull接口的OAuth2.0身份授权
5.1. 什么是OAuth2.0
OAuth2.0是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是2.0版。本文是对OAuth 2.0的设计思路和运行原理应用到F2BPM平台中,目标是为了防止接口被不安全使用,接口的调用必须有安全的身份认证机制。
5.2. OAuth2.0与F2-BPM平台
因为要将F2-BPM平台做为流程服务中心,那么F2-BPM平台就会与多个应用系统打交道,多个应该系统通过调用RESTfull接口来驱动流程或者获取流程相关数据时需要安全验证后才可以进行接口影响返回正确的数据结果。
F2-BPM流程中心构建了OAuth2.0的身份认证机制,此身份的认证是应该系统级别的,目的验证是否来自合法注册的应该系统调用了流程中心的RESTfull接口。(数据关联的用户是应用系统开发调用流程中心接口时决定)。
本流程中心的OAuthor并非用于用户的第三方登录认证,当然也能扩展出来,但由于F2-BPM并非人事系统的统一用户管理中心。
5.3. F2-BPM流程中心OAUTH2.0身份验证时序图
每个接口必须传递的数据:{token:,corpId:,timeStamp:,nonce:,signature:,loginAccount:}
参数名 | 值示例 | 说明 |
token | oa_token | 系统令牌,在流程中心配置的固定值 |
corpId | app255e7feb2645dd23 | 应用系统的ID,在流程中心增加应用接入时生成的ID |
timeStamp | 1706261844 | 时间戳,传递时由客户端生成,格式:yymmddHHmm,客户端时间与F2-PBM流程中心系统时间误差充许在10分钟以内 |
nonce | 6723 | 随时数,由客户端生成一个至少4位的随时数 |
signature | ZDlUMYZeJKXrAZ3ofbZQnXgSPqjHw9xw2lZhj0hPwF5VUG0yMhknJ-8Ql8zK8tXK | 签名加密字符串,由客户端将timeStamp和nonce进行加密得到此签名加密字符串 |
loginAccount | zs | 当前登录者的账号,用于作为调用RESTfull接口流程引擎将以此账号作为流程引擎运行中的当前登录人身份 |
客户端与流程中心接口交互参数说明:
authorJson:主要是接口身份的认证相关参数,校验访问者的来源合法性
parmJson:请求业务数据的参数,比如分页参数,查询参数等 所有RESTfull都统一只有这两个Json参数 一般情况
get只适合参数相对简单的请求,如果参数过长或参数字符复杂,则使用Post 来传参请求。
F2BPM接口强大的特点:服务端可以利用F2BPM非常丰富的流程引擎WAPI进行发布成RESTfull服务接口供应用端系统调用。而且接口可以是无状态的请求
客户端Get请求
/** * get请求 get只适合参数相对简单的请求,如果参数过长或参数字符复杂,则使用Post 来传参请求 */ public void getTodoList() { String urlString = webApiUrl + "/workflowBusiness/getTodoList/?"; StringBuilder queryString = new StringBuilder(); queryString.append(StringUtil.format("authorJson={loginAccount:\"{0}\"}", "admin")); queryString.append(StringUtil.format("&parmJson={pageIndex:{0},pageSize:{1},sort:\"{2}\",order:\"{3}\"}", 1, 2, "CreatedTime", "desc")); String param = HttpClientUtil.urlEncode(queryString.toString()); // 特殊字符进行转义 urlString = urlString + param; String jsonRes = HttpClientUtil.get(urlString); System.out.println(jsonRes); }
对应的流程中心服务端
// 获取待办列表 @RequestMapping(value = "getTodoList", method = RequestMethod.GET) public void getTodoList(String authorJson, String parmJson, HttpServletRequest request, HttpServletResponse response) throws Exception { JSONObject jsonJSONObject = JSONObject.fromObject(parmJson); PageParams pageParams = JsonHelper.jsonToObject(parmJson, PageParams.class); AuthorEntity authorEntity = JsonHelper.jsonToObject(authorJson, AuthorEntity.class); String loginAccount = authorEntity.getLoginAccount(); MyInteger recordCount = new MyInteger(0); MyInteger pageCount = new MyInteger(0); String whereStr = JsonHelper.getString(jsonJSONObject, "whereStr"); IUser user = userService.getUserByAccount(loginAccount); Listlist = WorkflowAPI.getWorkTaskManager().getTodoList(user.getUserId(), whereStr.toString(), pageParams.getOrderBy(), pageParams.getPageIndex(), pageParams.getPageSize(), pageCount, recordCount, true); String jsonString = JsonHelper.listToJSON(list); String jsonResult = JsonHelper.convertToEasyUIJsonResult(jsonString, pageParams.getPageSize(), pageParams.getPageIndex(), recordCount.getValue(), pageCount.getValue()); JsonHelper.write(response, jsonResult); }
Post请求来获取已办列表
/** * post请求,参数复杂的建议使用Post来请求 */ public void getDoneList() { String urlString = webApiUrl + "/workflowBusiness/getDoneList/"; Mapparams = new HashMap (); StringBuilder queryString = new StringBuilder(); params.put("authorJson", StringUtil.format("{loginAccount:\"{0}\"}", "admin")); // isHistory:0 进行中的已办,isHistory:1流程已结束并归档的已办 params.put("parmJson", StringUtil.format("{isHistory:0,pageIndex:{0},pageSize:{1},sort:\"{2}\",order:\"{3}\",whereStr:\"{4}\",}", 1, 2, "CreatedTime", "desc", "")); String jsonRes = HttpClientUtil.post(urlString, params); System.out.println(jsonRes); }
// 流程中心服务-已办列表 @RequestMapping(value = "getDoneList", method = RequestMethod.POST) public void getDoneList(String authorJson, String parmJson, HttpServletRequest request, HttpServletResponse response) throws Exception { JSONObject jsonJSONObject = JSONObject.fromObject(parmJson); PageParams pageParams = JsonHelper.jsonToObject(parmJson, PageParams.class); AuthorEntity authorEntity = JsonHelper.jsonToObject(authorJson, AuthorEntity.class); String loginAccount = authorEntity.getLoginAccount(); MyInteger recordCount = new MyInteger(0); MyInteger pageCount = new MyInteger(0); String whereStr = JsonHelper.getString(jsonJSONObject, "whereStr"); int isHistory = JsonHelper.getInt(jsonJSONObject, "isHistory"); IUser user = userService.getUserByAccount(loginAccount); Listlist =null; if(isHistory==1){ //归档中的列表 list = WorkflowAPI.getHistoryDataManager().getHistoryDoneList(user.getUserId(), whereStr.toString(), pageParams.getOrderBy(), pageParams.getPageIndex(), pageParams.getPageSize(), pageCount, recordCount, true); }else { //进行中的已办 list = WorkflowAPI.getWorkTaskManager().getDoneList(user.getUserId(), whereStr.toString(), pageParams.getOrderBy(), pageParams.getPageIndex(), pageParams.getPageSize(), pageCount, recordCount, true); } String jsonString = JsonHelper.listToJSON(list); String jsonResult = JsonHelper.convertToEasyUIJsonResult(jsonString, pageParams.getPageSize(), pageParams.getPageIndex(), recordCount.getValue(), pageCount.getValue()); JsonHelper.write(response, jsonResult); }
输出响应结果到客户端
{"success":true,"msg":"","total":4,"pageCount":2,"pageSize":2,"pageIndex":1, "rows":[{"isDelegateDone":false,"workflowTitle":"[系统管理员费用报销申请","directBackAct":"","expectFinishedTime": {"time":0,"minutes":0,"seconds":0,"hours":8,"month":0,"year":70,"timezoneOffset":-480,"day":4,"date":1},"businessKey":"","delegatorUserId":"","respondType":"","delegatorRealName":"", "wiState":0,"taskExpireTime":null,"mainActivityInstanceId":"","urgency":1,"activityId":"","extStr":"","formId":"bdd11478-97ab-4612-beb9-575a3b3d9e83","description":"","userId":"3c1df0b3-a4d9-4731-b143-02e81bce17ce", "delegatorName":"","userName":"","opinion":"","taskDealHours":0,"appType":"表单规则2.0","isCirculated":false,"isContainDelegator":false,"currentActors":"","fromCreatorID":"","taskCreateType":"","currentActivityName":"", "openBizDate":"","isReferred":false,"delegatorMobile":"","importance":1,"workflowInstanceState":2,"activityShowName":"","userMobile":"","creatorRealName":"系统管理员","userDpId":"","taskState":0,"isValid":false,"userDpName":"", "appId":"AI","formType":"","workflowInstanceId":"5e2cfc5b-3fcb-4ae5-ae1d-fbdb27b4980e","creatorDepartId":"ZhiBoRuanJian","fromTaskId":"","activityInstanceId":"","taskSeq":"","creator":"admin","realTime":null, "isCompleter":false,"completedType":"","delegatorDpName":"","finishedTime":null,"isMobileApproval":true,"stepId":0,"delegatorDpId":"","isMobileStart":true,"taskId":"","requirement":"", "createdTime":{"time":1499268876000,"minutes":34,"seconds":36,"hours":23,"month":6,"year":117,"timezoneOffset":-480,"day":3,"date":5},"logs":"","creatorId":"3c1df0b3-a4d9-4731-b143-02e81bce17ce", "taskRemark":"","secrecy":0,"commentCount":0,"mainWorkflowInstanceId":"","appName":"费用报销申请","sheetId":"AI20170705233435818","completedTime":null,"isDelegatorCompleted":false,"workflowId":"3944ea6b-0c56-4c74-8b0e-af82d128f772", "urgeTimes":0,"creatorDpName":"致博软件","fromCreator":"","realName":"","activityName":"","startedTime":{"time":1499268876000,"minutes":34,"seconds":36,"hours":23,"month":6,"year":117, "timezoneOffset":-480,"day":3,"date":5}},{"isDelegateDone":false,"workflowTitle":"系统管理员请假申请","directBackAct":"","expectFinishedTime":{"time":0,"minutes":0,"seconds":0,"hours":8,"month":0,"year":70,"timezoneOffset":-480,"day":4,"date":1},"businessKey":"","delegatorUserId":"","respondType":"","delegatorRealName":"", "wiState":0,"taskExpireTime":null,"mainActivityInstanceId":"","urgency":1,"activityId":"","extStr":"","formId":"26eaad7d-ccfb-4b6a-96c0-4efc796f5d47","description":"","userId":"3c1df0b3-a4d9-4731-b143-02e81bce17ce","delegatorName":"","userName":"","opinion":"","taskDealHours":0,"appType":"表单规则2.0","isCirculated":false,"isContainDelegator":false,"currentActors":"","fromCreatorID":"","taskCreateType":"","currentActivityName":"","openBizDate":"","isReferred":false,"delegatorMobile":"","importance":1,"workflowInstanceState":2,"activityShowName":"","userMobile":"", "creatorRealName":"系统管理员","userDpId":"","taskState":0,"isValid":false,"userDpName":"", "appId":"AB","formType":"","workflowInstanceId":"a4d02561-7dc0-4a01-9368-687363081395","creatorDepartId":"ZhiBoRuanJian","fromTaskId":"", "activityInstanceId":"","taskSeq":"","creator":"admin","realTime":null,"isCompleter":false,"completedType":"","delegatorDpName":"","finishedTime":null,"isMobileApproval":true,"stepId":0,"delegatorDpId":"","isMobileStart":true,"taskId":"","requirement":"","createdTime":{"time":1499268669000,"minutes":31,"seconds":9,"hours":23,"month":6,"year":117,"timezoneOffset":-480,"day":3,"date":5},"logs":"","creatorId":"3c1df0b3-a4d9-4731-b143-02e81bce17ce","taskRemark":"","secrecy":0,"commentCount":0,"mainWorkflowInstanceId":"", "appName":"请假申请","sheetId":"AB20170705233109293","completedTime":null,"isDelegatorCompleted":false, "workflowId":"4ae848a4-70f7-4e76-bd35-8f33f5bbac1e","urgeTimes":0,"creatorDpName":"致博软件","fromCreator":"","realName":"","activityName":"", "startedTime":{"time":1499268669000,"minutes":31,"seconds":9,"hours":23,"month":6,"year":117,"timezoneOffset":-480,"day":3,"date":5}}]}
发起流程示例:
客户端
/** * post请求 */ public void startWorkflow() { String urlString = webApiUrl + "/workflowBusiness/startWorkflow/"; Mapparams = new HashMap (); StringBuilder queryString = new StringBuilder(); String onlineFormData = StringUtil.format("[{\"mainTable\":\"csb\",\"data\":[{\"name\":\"csb.nl\",\"value\":\"22\"},{\"name\":\"csb.MyId\",\"value\":\"\"},{\"name\":\"csb.zz\",\"value\":\"RestFull测试\"},{\"name\":\"csb.xm\",\"value\":\"RestFull姓名\"}],\"subTables\":[]}]"); params.put("authorJson", StringUtil.format("{loginAccount:\"{0}\"}", "admin")); params.put("parmJson", StringUtil.format("{appId:\"{0}\",wiid:\"{1}\",businessKey:\"{2}\",title:\"{3}\",opinion:\"{4}\",jsonFormData:{5}}", "ZX", Guid.getGuid(), Guid.getGuid(), "应用端RestFull请求测试", "同意", onlineFormData)); // String param = HttpClientUtil.urlEncode(queryString.toString()); // //特殊字符进行转义 String jsonRes = HttpClientUtil.post(urlString, params); System.out.println(jsonRes); }
流程中心处理发起请求
//响应发起流程 @RequestMapping(value = "startWorkflow", method = RequestMethod.POST) public void startWorkflow(String authorJson, String parmJson, HttpServletRequest request, HttpServletResponse response) throws IOException { JSONObject jsonJSONObject = JSONObject.fromObject(parmJson); AuthorEntity authorEntity = JsonHelper.jsonToObject(authorJson, AuthorEntity.class); String loginAccount = authorEntity.getLoginAccount(); // IUser user = userService.getUserByAccount(loginAccount); String appId = JsonHelper.getString(jsonJSONObject, "appId"); String wiid = JsonHelper.getString(jsonJSONObject, "wiid"); String businessKey = JsonHelper.getString(jsonJSONObject, "businessKey"); String title = JsonHelper.getString(jsonJSONObject, "title"); String opinion = JsonHelper.getString(jsonJSONObject, "opinion"); String jsonFormData = JsonHelper.getString(jsonJSONObject, "jsonFormData"); StringBuilder message = new StringBuilder(); boolean success = WorkflowAPI.getWorkflowEnactmentManager().startWorkflow(appId, wiid, businessKey, title, opinion, loginAccount, null, message, jsonFormData, null, 0, 0); String jsonResult = JsonHelper.outResult(success, message.toString()); JsonHelper.write(response, jsonResult); }