本文為您介紹Dataphin BPMS支援對接第三方審批流,包括開放了哪些介面能力,如何接入審批能力以及審批案例等內容。
前提條件
已在Dataphin系統配置審批設定,如何配置,詳情請參見審批設定。營運租戶參見審批設定中選擇審批系統的其他配置項。
審批介面
{url} :為第三方審批流對接地址,http(s)://{url}為提交審批申請URL。
串連測試。
請求方式:POST。
請求地址:http(s)://{url}/dataphin/bpms/check/connect。
請求時序圖

Query參數
參數名稱
參數類型
描述
accessToken
String
調用服務端API的憑證。
Dataphin頁面自動產生,或使用者產生填入Dataphin。如:6d1b2n1c。
timestamp
String
單位毫秒,資訊發送時間戳記。如:1654156836531。
Body參數
checkEvent(String):檢查事件為串連測試。如:connectivity。
返回參數
checkResult(String):檢查結果:成功,僅當傳回值為success時,認定為成功。其他值均被認為測試連接失敗。如:success。
提交審批。
請求方式:POST。
請求地址:http(s)://{url}/dataphin/bpms/processinstance/create。
提交審批的時序圖

Query參數
參數名稱
參數類型
描述
accessToken
String
調用服務端API的憑證。
Dataphin頁面自動產生,或使用者產生填入Dataphin。如:6d1b2n1c。
timestamp
String
單位毫秒,資訊發送時間戳記。如:1654156836531。
Body參數
參數名稱
參數類型
描述
applyId
String
Dataphin審批單Id。如:1223。
title
String
Dataphin審批單標題。如:dataphin bpms。
content
String
Dataphin審批單內容。如:bpms content。請參見審批訊息內容中繼資料描述。
type
String
審批單類型:APPROVAL_ DOC_TYPE。
代碼審核:CODE_REVIEW。
發布管控:PUBLISH。
業務規劃:BIZ_PLANNING。
許可權審批:AUTH。
預設:DEFAULT。
templateCode
String
審批模板code,可為空白,與頁面配置有關。
審批訊息內容中繼資料描述
參數名稱
參數類型
描述
resourceType
String
審批任務類型,為枚舉值,包含以下的類型:
PhysicalTable:物理表。
LogicTable:邏輯表。
MetaTable:即時元表。
MirrorTable:鏡像表。
Fun:函數。
DataSource:資料來源。
FeatureConfig:功能許可權配置。
OSAPP:資料服務APP。
OSAPI:資料服務API 。
OSLogicUnit:資料服務單元。
OSDS:資料服務資料來源。
SECRET_KEY:密鑰。
GLOBAL_PARAM:全域變數。
grantToUsers
List<GrantToUser>
授權使用者列表。請參見GrantToUser。
bpmsEnvironment
BpmsEnvironment
審批系統內容資訊。請參見BpmsEnvironment。
operates
List<String>
申請的權限類別型,包含以下類型:
SYNC_READ:資料來源同步讀。
SYNC_WRITE:資料來源同步寫。
SQL_QUERY: 物理表及邏輯表的SQL查詢。
SQL_WRITE: 物理表的SQL寫入。
SQL_ALTER: 物理表的SQL修改。
SQL_DROP: 物理表及邏輯表的SQL刪除。
SELECT:資料服務API的查詢。
WRITE:寫入許可權。
DEV:開發許可權。
USE:使用許可權。
UPDATE:改表資料。
PIPELINE_ENCRY:整合加密。
PIPELINE_DECRY:整合解密。
levels
List<String>
許可權等級,包含三個等級:HIGH、MIDDLE、LOW。
operations
List<String>
權限類別型,包含以下類型:
SELECT-查詢。
DESCRIBE- 查表結構。
UPDATE- 改表資料。
ALTER- 改表結構。
DELETE-刪除表。
COPY_TASK- 複製。
resources
List<BpmsResource>
資源內容,請參見BpmsResource。
applyObject
ApplyObject
申請對象資訊,請參見ApplyObject。
reason
String
申請原因。
GrantToUser
參數名稱
參數類型
描述
account
Account
授權賬戶列表,請參見Account。
period
Period
有效期間,請參見Period。
Account
參數名稱
參數類型
描述
accountType
String
授與類型,包括個人帳號、生產帳號和應用帳號三種。
PERSONAL:個人帳號。
PRODUCE:生產帳號。
APPLICATION:應用帳號。
userName
String
結束時間格式為yyyy-mm-dd。如:2022-09-11。
Period
參數名稱
參數類型
描述
periodType
String
有效期間類型支援長期(LONG_TERM)。
periodEnd
String
許可權到期時間格式為yyyy-mm-dd。如:2022-09-11。
BpmsEnvironment
參數名稱
參數類型
描述
projectName
String
專案名稱。
bizUnitName
String
業務板塊名稱。
resourceEnv
String
環境分為生產環境和開發環境兩種環境。
PROD:生產。
DEV:開發。
BpmsResource
參數名稱
參數類型
描述
resourceType
String
請參見審批訊息內容中繼資料描述。
resourceName
String
資源名稱。
children
List<Children>
欄位列表,請參見Children。
operations
List<String>
請參見審批訊息內容中繼資料描述的operates。
authTypes
String
申請權限類別型。
Children
參數名稱
參數類型
描述
resourceName
String
欄位名稱。
resourceProperties
String
欄位屬性。
ResourceProperties
參數名稱
參數類型
描述
columnType
String
欄位類型。
columnIsPartition
String
是否為分區欄位。
columnIsPk
String
是否為主鍵。
ApplyObject
參數名稱
參數類型
描述
objectName
String
對象名稱。
codeContent
String
代碼內容。
name
String
商務活動英文名稱。
bizObjectType
String
物件類型。
bizObjectChangeType
String
變更類型。
bizObjectPkField
String
組件欄位。
bizObjectParent
String
所屬父物件。
bizObjectChildren
String
下遊子物件。
bizProcessCn
String
活動名稱。
bizProcessType
String
活動類型。
bizProcessChangeType
String
變更類型。
bizProcessNodes
String
流程節點。
atomicIndexCn
String
指標名稱。
dataType
String
資料類型。
unit
String
計量單位。
bizProcess
String
活動名稱。
des
String
活動口徑。
derivedLogic
String
衍生公司。
protoLogics
String
計算邏輯。
返回參數
processInstanceId(String):第三方審批執行個體ID。如:6d1b2n1c。
撤銷審批。
說明提交審批後,若申請內容不符合預期,可撤銷審批。
請求方式:POST。
請求地址:http(s)://{url}/dataphin/bpms/processinstance/revoke。
Query參數
參數名稱
參數類型
描述
accessToken
String
調用服務端API的憑證。
Dataphin頁面自動產生,或使用者產生填入Dataphin。如:6d1b2n1c。
timestamp
String
單位毫秒,資訊發送時間戳記。如:1654156836531。
Body參數
參數名稱
參數類型
描述
applyId
String
Dataphin審批單Id。如:1223。
processInstanceId
String
第三方審批執行個體Id。如:6d1b2a3c。
operatingUserId
String
操作人工號。如:6d1bx1d2。
返回參數
result(String):撤回結果資訊。僅當傳回值為success時被認定為成功。
查詢審批執行個體Url。
請求方式:GET。
請求地址:http(s)://{url}/dataphin/bpms/processinstance/apply。
Query參數
參數名稱
參數類型
描述
accessToken
String
調用服務端API的憑證。
Dataphin頁面自動產生,或使用者產生填入Dataphin。如:6d1b2n1c。
timestamp
String
單位毫秒,資訊發送時間戳記。如:1654156836531。
回調介面
https://{callbackUrl}即為審批設定中其他配置的Callback URL。
accessToken:調用服務端API的憑證。Dataphin頁面自動產生,或使用者產生填入Dataphin。
callBackAesKey:回調認證憑證。Dataphin頁面自動產生,或使用者產生填入Dataphin。
回調方法。
請求方式:POST。
請求地址:http://{callbackUrl}。
Query參數
參數名稱
參數類型
描述
signature
String
訊息認證資訊,請參見加解密方法。
timestamp
String
單位毫秒,資訊發送時間戳記。如:1654156836531。
nonce
String
隨機數,請參見加解密方法。
Body參數
encrypt(String):加密資訊。如:ajls384k。
返回參數
encrypt(String):回調狀態加密資訊。如:ajls384k。
回調加密內容。
回調連通性校正。
傳入加密參數:
applyStatus(String):檢查連通性參數。如:CHECK。
返回加密參數:
success:成功回調傳回值。
回調返回bpms執行個體審批結果。
傳入加密參數:
參數名稱
參數類型
描述
applyId
String
Dataphin審批單Id。如:1223。
processInstanceId
String
第三方審批執行個體Id。如:6d1b2a3c。
comment
String
審批執行個體評論資訊。
applyStatus
String
accept:接受。
reject:拒絕。
revoke:撤銷。
返回加密參數:
success:成功回調傳回值。
加解密方法。
加密樣本:
Map<String, String> callBackJson = Maps.newHashMap(); callBackJson.put("applyId", "1"); callBackJson.put("applyStatus", "accept"); callBackJson.put("processInstanceId", "2"); callBackJson.put("comment", "test"); String callBackResult = JSON.toJSONString(callBackJson); ThirdPartyCrypto callbackCrypto = new ThirdPartyCrypto(token, aesKey); String timestamp = String.valueOf(System.currentTimeMillis()); String nonce = ThirdPartyCrypto.Utils.getRandomStr(16); String encrypt = callbackCrypto.encrypt(nonce, callBackResult); String signature = callbackCrypto.getSignature(token, timestamp, nonce, encrypt);解密樣本:
String encryptMsg = JSONObject.parseObject(encrypt); String decryptMsg = callbackCrypto.getDecryptMsg(signature, timestamp, nonce, encryptMsg); if ("success".equalsIgnoreCase(decryptMsg)) { log.error("call back success"); }加解密工具
import com.alibaba.fastjson.JSON; import org.apache.commons.codec.binary.Base64; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.io.ByteArrayOutputStream; import java.lang.reflect.Field; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.Permission; import java.security.PermissionCollection; import java.security.Security; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Random; /** * Dataphin開放平台加解密方法 */ public class ThirdPartyCrypto { private static final Charset CHARSET = StandardCharsets.UTF_8; private static final Base64 BASE_64 = new Base64(); private final byte[] AES_KEY; private final String TOKEN; /** * ask getPaddingBytes key固定長度 **/ private static final Integer AES_ENCODE_KEY_LENGTH = 43; /** * 加密隨機字串位元組長度 **/ private static final Integer RANDOM_LENGTH = 16; /** * 建構函式 * * @param token 開發人員設定的token * @param encodingAesKey 開發人員設定的EncodingAESKey * * @throws ThirdPartyEncryptException 執行失敗,請查看該異常的錯誤碼和具體的錯誤資訊 */ public ThirdPartyCrypto(String token, String encodingAesKey) throws ThirdPartyEncryptException { if (null == encodingAesKey || encodingAesKey.length() != AES_ENCODE_KEY_LENGTH) { throw new ThirdPartyEncryptException(ThirdPartyEncryptException.AES_KEY_ILLEGAL); } this.TOKEN = token; AES_KEY = Base64.decodeBase64(encodingAesKey + "="); } public Map<String, String> getEncryptedMap(String plaintext) throws ThirdPartyEncryptException { return getEncryptedMap(plaintext, System.currentTimeMillis(), Utils.getRandomStr(16)); } /** * 將和Dataphin同步的訊息體加密,返回加密Map * * @param plaintext 傳遞的訊息體明文 * @param timeStamp 時間戳記 * @param nonce 隨機字串 * @return * @throws ThirdPartyEncryptException */ public Map<String, String> getEncryptedMap(String plaintext, Long timeStamp, String nonce) throws ThirdPartyEncryptException { if (null == plaintext) { throw new ThirdPartyEncryptException(ThirdPartyEncryptException.ENCRYPTION_PLAINTEXT_ILLEGAL); } if (null == timeStamp) { throw new ThirdPartyEncryptException(ThirdPartyEncryptException.ENCRYPTION_TIMESTAMP_ILLEGAL); } if (null == nonce) { throw new ThirdPartyEncryptException(ThirdPartyEncryptException.ENCRYPTION_NONCE_ILLEGAL); } // 加密 String encrypt = encrypt(Utils.getRandomStr(RANDOM_LENGTH), plaintext); String signature = getSignature(TOKEN, String.valueOf(timeStamp), nonce, encrypt); Map<String, String> resultMap = new HashMap<String, String>(); resultMap.put("msg_signature", signature); resultMap.put("encrypt", encrypt); resultMap.put("timeStamp", String.valueOf(timeStamp)); resultMap.put("nonce", nonce); return resultMap; } /** * 密文解密 * * @param msgSignature 簽名串 * @param timeStamp 時間戳記 * @param nonce 隨機串 * @param encryptMsg 密文 * @return 解密後的原文 * @throws ThirdPartyEncryptException */ public String getDecryptMsg(String msgSignature, String timeStamp, String nonce, String encryptMsg) throws ThirdPartyEncryptException { //校正簽名 String signature = getSignature(TOKEN, timeStamp, nonce, encryptMsg); if (!signature.equals(msgSignature)) { throw new ThirdPartyEncryptException(ThirdPartyEncryptException.COMPUTE_SIGNATURE_ERROR); } // 解密 return decrypt(encryptMsg); } /** * 對明文加密. * @param plaintext 需要加密的明文 * @return 加密後base64編碼的字串 */ public String encrypt(String random, String plaintext) throws ThirdPartyEncryptException { try { byte[] randomBytes = random.getBytes(CHARSET); byte[] plainTextBytes = plaintext.getBytes(CHARSET); byte[] lengthByte = Utils.int2Bytes(plainTextBytes.length); ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); byteStream.write(randomBytes); byteStream.write(lengthByte); byteStream.write(plainTextBytes); byte[] padBytes = PKCS7Padding.getPaddingBytes(byteStream.size()); byteStream.write(padBytes); byte[] unencrypted = byteStream.toByteArray(); byteStream.close(); Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); SecretKeySpec keySpec = new SecretKeySpec(AES_KEY, "AES"); IvParameterSpec iv = new IvParameterSpec(AES_KEY, 0, 16); cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv); byte[] encrypted = cipher.doFinal(unencrypted); String result = BASE_64.encodeToString(encrypted); return result; } catch (Exception e) { throw new ThirdPartyEncryptException(ThirdPartyEncryptException.COMPUTE_ENCRYPT_TEXT_ERROR); } } /** * 對密文進行解密. * @param text 需要解密的密文 * @return 解密得到的明文 */ private String decrypt(String text) throws ThirdPartyEncryptException { byte[] originalArr; try { // 設定解密模式為AES的CBC模式 Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); SecretKeySpec keySpec = new SecretKeySpec(AES_KEY, "AES"); IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(AES_KEY, 0, 16)); cipher.init(Cipher.DECRYPT_MODE, keySpec, iv); // 使用BASE64對密文進行解碼 byte[] encrypted = Base64.decodeBase64(text); // 解密 originalArr = cipher.doFinal(encrypted); } catch (Exception e) { throw new ThirdPartyEncryptException(ThirdPartyEncryptException.COMPUTE_DECRYPT_TEXT_ERROR); } String plainText; try { // 去除補位字元 byte[] bytes = PKCS7Padding.removePaddingBytes(originalArr); // 分離16位隨機字串,網路位元組序和corpId byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20); int plainTextLegth = Utils.bytes2int(networkOrder); plainText = new String(Arrays.copyOfRange(bytes, 20, 20 + plainTextLegth), CHARSET); } catch (Exception e) { throw new ThirdPartyEncryptException(ThirdPartyEncryptException.COMPUTE_DECRYPT_TEXT_LENGTH_ERROR); } return plainText; } /** * 數位簽章 * * @param token isv token * @param timestamp 時間戳記 * @param nonce 隨機串 * @param encrypt 加密文本 * @return * @throws ThirdPartyEncryptException */ public String getSignature(String token, String timestamp, String nonce, String encrypt) throws ThirdPartyEncryptException { try { String[] array = new String[] {token, timestamp, nonce, encrypt}; Arrays.sort(array); System.out.println(JSON.toJSONString(array)); StringBuffer sb = new StringBuffer(); for (int i = 0; i < 4; i++) { sb.append(array[i]); } String str = sb.toString(); System.out.println(str); MessageDigest md = MessageDigest.getInstance("SHA-1"); md.update(str.getBytes()); byte[] digest = md.digest(); StringBuffer hexstr = new StringBuffer(); String shaHex = ""; for (int i = 0; i < digest.length; i++) { shaHex = Integer.toHexString(digest[i] & 0xFF); if (shaHex.length() < 2) { hexstr.append(0); } hexstr.append(shaHex); } return hexstr.toString(); } catch (Exception e) { throw new ThirdPartyEncryptException(ThirdPartyEncryptException.COMPUTE_SIGNATURE_ERROR); } } public static class Utils { public Utils() { } public static String getRandomStr(int count) { String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; Random random = new Random(); StringBuffer sb = new StringBuffer(); for (int i = 0; i < count; ++i) { int number = random.nextInt(base.length()); sb.append(base.charAt(number)); } return sb.toString(); } public static byte[] int2Bytes(int count) { byte[] byteArr = new byte[] {(byte)(count >> 24 & 255), (byte)(count >> 16 & 255), (byte)(count >> 8 & 255), (byte)(count & 255)}; return byteArr; } public static int bytes2int(byte[] byteArr) { int count = 0; for (int i = 0; i < 4; ++i) { count <<= 8; count |= byteArr[i] & 255; } return count; } } public static class PKCS7Padding { private static final Charset CHARSET = StandardCharsets.UTF_8; private static final int BLOCK_SIZE = 32; public PKCS7Padding() { } public static byte[] getPaddingBytes(int count) { int amountToPad = 32 - count % 32; if (amountToPad == 0) { amountToPad = 32; } char padChr = chr(amountToPad); String tmp = new String(); for (int index = 0; index < amountToPad; ++index) { tmp = tmp + padChr; } return tmp.getBytes(CHARSET); } public static byte[] removePaddingBytes(byte[] decrypted) { int pad = decrypted[decrypted.length - 1]; if (pad < 1 || pad > 32) { pad = 0; } return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad); } private static char chr(int a) { byte target = (byte)(a & 255); return (char)target; } } public static class ThirdPartyEncryptException extends Exception { public static final int SUCCESS = 0; public static final int ENCRYPTION_PLAINTEXT_ILLEGAL = 900001; public static final int ENCRYPTION_TIMESTAMP_ILLEGAL = 900002; public static final int ENCRYPTION_NONCE_ILLEGAL = 900003; public static final int AES_KEY_ILLEGAL = 900004; public static final int SIGNATURE_NOT_MATCH = 900005; public static final int COMPUTE_SIGNATURE_ERROR = 900006; public static final int COMPUTE_ENCRYPT_TEXT_ERROR = 900007; public static final int COMPUTE_DECRYPT_TEXT_ERROR = 900008; public static final int COMPUTE_DECRYPT_TEXT_LENGTH_ERROR = 900009; public static final int COMPUTE_DECRYPT_TEXT_CORPID_ERROR = 900010; private static Map<Integer, String> msgMap = new HashMap(); private Integer code; static { msgMap.put(0, "成功"); msgMap.put(900001, "加密明文文本非法"); msgMap.put(900002, "加密時間戳記參數非法"); msgMap.put(900003, "加密隨機字串參數非法"); msgMap.put(900005, "簽名不匹配"); msgMap.put(900006, "簽名計算失敗"); msgMap.put(900004, "不合法的aes key"); msgMap.put(900007, "計算加密文字錯誤"); msgMap.put(900008, "計算解密文字錯誤"); msgMap.put(900009, "計算解密文字長度不匹配"); msgMap.put(900010, "計算解密文字corpid不匹配"); } public Integer getCode() { return this.code; } public ThirdPartyEncryptException(Integer exceptionCode) { super((String)msgMap.get(exceptionCode)); this.code = exceptionCode; } } static { try { Security.setProperty("crypto.policy", "limited"); RemoveCryptographyRestrictions(); } catch (Exception var1) { } } private static void RemoveCryptographyRestrictions() throws Exception { Class<?> jceSecurity = getClazz("javax.crypto.JceSecurity"); Class<?> cryptoPermissions = getClazz("javax.crypto.CryptoPermissions"); Class<?> cryptoAllPermission = getClazz("javax.crypto.CryptoAllPermission"); if (jceSecurity != null) { setFinalStaticValue(jceSecurity, "isRestricted", false); PermissionCollection defaultPolicy = (PermissionCollection)getFieldValue(jceSecurity, "defaultPolicy", (Object)null, PermissionCollection.class); if (cryptoPermissions != null) { Map<?, ?> map = (Map)getFieldValue(cryptoPermissions, "perms", defaultPolicy, Map.class); map.clear(); } if (cryptoAllPermission != null) { Permission permission = (Permission)getFieldValue(cryptoAllPermission, "INSTANCE", (Object)null, Permission.class); defaultPolicy.add(permission); } } } private static Class<?> getClazz(String className) { Class clazz = null; try { clazz = Class.forName(className); } catch (Exception var3) { } return clazz; } private static void setFinalStaticValue(Class<?> srcClazz, String fieldName, Object newValue) throws Exception { Field field = srcClazz.getDeclaredField(fieldName); field.setAccessible(true); Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(field, field.getModifiers() & -17); field.set((Object)null, newValue); } private static <T> T getFieldValue(Class<?> srcClazz, String fieldName, Object owner, Class<T> dstClazz) throws Exception { Field field = srcClazz.getDeclaredField(fieldName); field.setAccessible(true); return dstClazz.cast(field.get(owner)); } }