本文为您介绍Dataphin BPMS支持对接第三方审批流,包括开放了哪些接口能力,如何接入审批能力以及审批案例等内容。
前提条件
已在Dataphin系统配置审批设置,如何配置,详情请参见审批设置。运维租户参见审批设置中选择审批系统的其他配置项。
审批接口
{url} :为第三方审批流对接地址,http://{url}为提交审批申请URL,协议仅支持HTTP,暂不支持HTTPS。
连接测试。
请求方式:POST。
请求地址:http://{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://{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://{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://{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)); } }