java代码发起钉钉审批流程实例

用钉钉应用,创建审批流程实例,但是官方给的代码,缺了很多包,后期根据参数重新编写

package com.fycx.businessv1.controller;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fycx.businessv1.service.SysUserService;
import com.fycx.common.core.exception.ServiceException;
import com.fycx.common.core.web.domain.AjaxResult;
import com.fycx.system.api.domain.SysUser;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.codec.binary.Base64;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
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.*;


/**
 * @ClassName DingTalkController
 * @author: maliang
 * @Description dingding控制层
 * @date 2023/5/30 11:38
 * @Version 1.0版本
 */
@RestController
@RequestMapping("/dingTalk")
public class DingTalkController {

    /**
     * 钉钉应用Key
     */
    private String corpId = "dingg3wi80ddyuzorsin";

    /**
     * 钉钉应用,回调的key
     */
    private String aesKey = "m4ceYAYx8AGP9zVKTVTAqm8gdvt2ujeNHTGElK3R8jI";

    /**
     * 应用回调的token
     */
    private String token = "xBuctYRbUlcGbI7h6kAmA5MFDKX8jYgpGBe5Q646Et7VWKdouj";

    /**
     * 钉钉应用id
     */
    private String agentId = "2613878378";

    /**
     * 钉钉应用key
     */
    private String appKey = "dingg3wi80ddyuzorsin";

    /**
     * 流程模板code
     */
    private String processCode = "PROC-74820311-D2F2-4111-AD85-82CAF722A927";

    /**
     * 钉钉应用
     */
    private String appSecret = "GC4yWJqqM1rdTMl65uMdERGWhj9fDmThs1TaseL5rRlouafLu5GVWCwvPLXSY9bV";

    @Resource
    private SysUserService sysUserService;

    /**
     * 测试使用接口
     *
     * @return
     */
    @PostMapping("getUser")
    public AjaxResult getById() {
        List<SysUser> page = sysUserService.getBaseMapper().selectList(Wrappers.lambdaQuery());
        return AjaxResult.success(page);
    }

    /**
     * 订阅流程事件
     *
     * @param msgSignature 签名
     * @param timeStamp    时间
     * @param nonce        生成参数
     * @param json         参数
     * @return
     * @throws DingCallbackCrypto.DingTalkEncryptException
     */
    @PostMapping("dealData")
    public Map<String, String> dealData(@RequestParam(value = "msg_signature", required = false) String msgSignature,
                                        @RequestParam(value = "timestamp", required = false) String timeStamp,
                                        @RequestParam(value = "nonce", required = false) String nonce,
                                        @RequestBody(required = false) JSONObject json) throws DingCallbackCrypto.DingTalkEncryptException {
        // 1. 从http请求中获取加解密参数
        String encrypt = json.getString("encrypt");

        // 2. 使用加解密类型
        DingCallbackCrypto callbackCrypto = new DingCallbackCrypto(token, aesKey, corpId);
        final String decryptMsg = callbackCrypto.getDecryptMsg(msgSignature, timeStamp, nonce, encrypt);

        // 3. 反序列化回调事件json数据
        JSONObject eventJson = JSON.parseObject(decryptMsg);
        String eventType = eventJson.getString("EventType");
        // 4. 根据EventType分类处理
        if ("check_url".equals(eventType)) {
            System.out.println("check_url");
            // 测试回调url的正确性
        } else if ("user_add_org".equals(eventType)) {
            System.out.println("user_add_org");
            // 处理通讯录用户增加时间
        } else if ("bpms_task_change".equals(eventType)) {
            System.out.println("bpms_task_change");
            // 处理审批实例变更(审批任务开始、结束、转交)
        } else if ("bpms_instance_change".equals(eventType)) {
            System.out.println("bpms_instance_change");
            String type = eventJson.getString("type");
            if ("finish".equals(type)) {
                // 处理审批实例始末(审批任务开始、结束)
                // 存入数据库
                String businessId = eventJson.getString("businessId");
                System.out.println("审批编号" + businessId);
            }
        }
        // 5. 返回success的加密数据
        return callbackCrypto.getEncryptedMap("success");
    }

    /**
     * 创建审批流程实例  权限:Workflow.Instance.Write,qyapi_get_member_by_mobile
     */
    @PostMapping("startProcessInstance")
    public void startProcessInstance(@RequestBody DingDingFormEntity dingDingFormEntity) {
        Integer num = dingDingFormEntity.getNum();
        if (Objects.isNull(num)) {
            throw new ServiceException("表单校验失败");
        }
        String url = "https://api.dingtalk.com/v1.0/workflow/processInstances";
        HashMap<Object, Object> formMap = MapUtil.newHashMap();
        formMap.put("microappAgentId", agentId);
        String token = getToken();
        formMap.put("originatorUserId", getUserId(token, "17640252619"));
        formMap.put("processCode", processCode);
        List<Map<String, Object>> paramList = ListUtil.toList();
        HashMap<String, Object> hashMap = MapUtil.newHashMap();
        hashMap.put("name", "数量");
        hashMap.put("value", num.toString());
        HashMap<String, Object> hashMap2 = MapUtil.newHashMap();
        hashMap2.put("name", "理由");
        hashMap2.put("value", dingDingFormEntity.getReason());
        paramList.add(hashMap);
        paramList.add(hashMap2);
        formMap.put("formComponentValues", paramList);
        List<HashMap<String, List<String>>> approversList = ListUtil.toList();
        System.out.println("token:" + token);
        List<String> phones = dingDingFormEntity.getPhones();
        for (String phone : phones) {
            HashMap<String, List<String>> userId = MapUtil.of("userIds", Collections.singletonList(getUserId(token, phone)));
            approversList.add(userId);
        }
        formMap.put("approvers", approversList);
        HttpResponse execute = HttpRequest.post(url).header("x-acs-dingtalk-access-token", token).body(JSONUtil.toJsonStr(formMap)).execute();
        String body = execute.body();
        System.out.println(body);
    }

    /**
     * 查询审批详情信息
     *
     * @param instanceId 实例id
     */
    @GetMapping("getByInstanceId")
    public void getByInstanceId(@RequestParam String instanceId) {
        String url = "https://api.dingtalk.com/v1.0/workflow/processInstances?processInstanceId={}";
        url = StrUtil.format(url, instanceId);
        HttpResponse execute = HttpRequest.get(url).header("x-acs-dingtalk-access-token", getToken()).execute();
        String body = execute.body();
        cn.hutool.json.JSONObject jsonObject = JSONUtil.parseObj(body);
        String result = jsonObject.getStr("result");
        cn.hutool.json.JSONObject resultJson = JSONUtil.parseObj(result);
        System.out.println(resultJson);
    }

    /**
     * @return 应用的token
     */
    public String getToken() {
        final String url = "https://api.dingtalk.com/v1.0/oauth2/accessToken";
        Map<String, Object> param = MapUtil.newHashMap();
        param.put("appKey", appKey);
        param.put("appSecret", appSecret);
        HttpResponse execute = HttpRequest.post(url).body(JSONUtil.toJsonStr(param)).execute();
        String body = execute.body();
        return JSONUtil.parseObj(body).getStr("accessToken");
    }

    public String getUserId(String token, String phone) {
        final String url = "https://oapi.dingtalk.com/topapi/v2/user/getbymobile?access_token=" + token;
        String body = HttpRequest.post(url).body(JSONUtil.toJsonStr(MapUtil.of("mobile", phone))).execute().body();
        cn.hutool.json.JSONObject jsonObject = JSONUtil.parseObj(body);
        String errmsg = jsonObject.getStr("errmsg");
        if (!"ok".equals(errmsg)) {
            throw new ServiceException(StrUtil.format("用户:{}未授权钉钉应用权限", phone));
        }
        String result = jsonObject.getStr("result");
        return JSONUtil.parseObj(result).getStr("userid");
    }

    public static class DingCallbackCrypto {
        private static final Charset CHARSET = StandardCharsets.UTF_8;
        private static final Base64 base64 = new Base64();
        private byte[] aesKey;
        private String token;
        private String corpId;
        /**
         * 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
         * @param corpId         企业自建应用-事件订阅, 使用appKey
         *                       企业自建应用-注册回调地址, 使用corpId
         *                       第三方企业应用, 使用suiteKey
         * @throws DingTalkEncryptException 执行失败,请查看该异常的错误码和具体的错误信息
         */
        public DingCallbackCrypto(String token, String encodingAesKey, String corpId) throws DingTalkEncryptException {
            if (null == encodingAesKey || encodingAesKey.length() != AES_ENCODE_KEY_LENGTH) {
                throw new DingTalkEncryptException(DingTalkEncryptException.AES_KEY_ILLEGAL);
            }
            this.token = token;
            this.corpId = corpId;
            aesKey = Base64.decodeBase64(encodingAesKey + "=");
        }

        public Map<String, String> getEncryptedMap(String plaintext) throws DingTalkEncryptException {
            return getEncryptedMap(plaintext, System.currentTimeMillis(), Utils.getRandomStr(16));
        }

        /**
         * 将和钉钉开放平台同步的消息体加密,返回加密Map
         *
         * @param plaintext 传递的消息体明文
         * @param timeStamp 时间戳
         * @param nonce     随机字符串
         * @return
         * @throws DingTalkEncryptException
         */
        public Map<String, String> getEncryptedMap(String plaintext, Long timeStamp, String nonce)
                throws DingTalkEncryptException {
            if (null == plaintext) {
                throw new DingTalkEncryptException(DingTalkEncryptException.ENCRYPTION_PLAINTEXT_ILLEGAL);
            }
            if (null == timeStamp) {
                throw new DingTalkEncryptException(DingTalkEncryptException.ENCRYPTION_TIMESTAMP_ILLEGAL);
            }
            if (null == nonce) {
                throw new DingTalkEncryptException(DingTalkEncryptException.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 DingTalkEncryptException
         */
        public String getDecryptMsg(String msgSignature, String timeStamp, String nonce, String encryptMsg)
                throws DingTalkEncryptException {
            //校验签名
            String signature = getSignature(token, timeStamp, nonce, encryptMsg);
            if (!signature.equals(msgSignature)) {
                throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_SIGNATURE_ERROR);
            }
            // 解密
            String result = decrypt(encryptMsg);
            return result;
        }

        /*
         * 对明文加密.
         * @param text 需要加密的明文
         * @return 加密后base64编码的字符串
         */
        private String encrypt(String random, String plaintext) throws DingTalkEncryptException {
            try {
                byte[] randomBytes = random.getBytes(CHARSET);
                byte[] plainTextBytes = plaintext.getBytes(CHARSET);
                byte[] lengthByte = Utils.int2Bytes(plainTextBytes.length);
                byte[] corpidBytes = corpId.getBytes(CHARSET);
                ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
                byteStream.write(randomBytes);
                byteStream.write(lengthByte);
                byteStream.write(plainTextBytes);
                byteStream.write(corpidBytes);
                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(aesKey, "AES");
                IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16);
                cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
                byte[] encrypted = cipher.doFinal(unencrypted);
                String result = base64.encodeToString(encrypted);
                return result;
            } catch (Exception e) {
                throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_ENCRYPT_TEXT_ERROR);
            }
        }

        /*
         * 对密文进行解密.
         * @param text 需要解密的密文
         * @return 解密得到的明文
         */
        private String decrypt(String text) throws DingTalkEncryptException {
            byte[] originalArr;
            try {
                // 设置解密模式为AES的CBC模式
                Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
                SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
                IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
                cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
                // 使用BASE64对密文进行解码
                byte[] encrypted = Base64.decodeBase64(text);
                // 解密
                originalArr = cipher.doFinal(encrypted);
            } catch (Exception e) {
                throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_ERROR);
            }

            String plainText;
            String fromCorpid;
            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);
                fromCorpid = new String(Arrays.copyOfRange(bytes, 20 + plainTextLegth, bytes.length), CHARSET);
            } catch (Exception e) {
                throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_LENGTH_ERROR);
            }

            // corpid不相同的情况
            if (!fromCorpid.equals(corpId)) {
                throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_CORPID_ERROR);
            }
            return plainText;
        }

        /**
         * 数字签名
         *
         * @param token     isv token
         * @param timestamp 时间戳
         * @param nonce     随机串
         * @param encrypt   加密文本
         * @return
         * @throws DingTalkEncryptException
         */
        public String getSignature(String token, String timestamp, String nonce, String encrypt)
                throws DingTalkEncryptException {
            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 DingTalkEncryptException(DingTalkEncryptException.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 = Charset.forName("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 DingTalkEncryptException 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 DingTalkEncryptException(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));
        }
    }

    @Getter
    @Setter
    public static class DingDingFormEntity {

        private List<String> phones;

        private Integer num;

        private String reason;
    }

}


参考资料
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
https://open-dev.dingtalk.com
https://open-dev.dingtalk.com/apiExplorer
能找到我这篇博客的网友应该已经费了很多心思,代码里的参数,应该不用我写的那么详细就能找到了,如果实在找不到可以给我留言,在下边追加

  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值