此类是HttpRequest.java
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
/**
* get请求
* @param url 为接口地址参数
* @return
*/
public static JSONObject doGetStr(String url){
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet(url);
CloseableHttpResponse response = null;
JSONObject jsonObject = null;//接收结果
try {
response = httpClient.execute(httpGet);
HttpEntity entity = response.getEntity();//从消息体里获取结果
if(entity!=null){
String result = EntityUtils.toString(entity,"UTF-8");
jsonObject = JSONObject.parseObject(result);
}
EntityUtils.consume(entity);
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(response != null){
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return jsonObject;
}

/**
* post请求
* @param url 为接口地址参数
* @param outStr
* @return
*/
public static JSONObject doPostStr(String url,String outStr){
CloseableHttpClient httpClient = HttpClients.createDefault();
//DefaultHttpClient httpClient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(url);
JSONObject jsonObject = null;
try {
httpPost.setEntity(new StringEntity(outStr, "UTF-8"));
HttpResponse response = httpClient.execute(httpPost);
String result = EntityUtils.toString(response.getEntity(), "UTF-8");
jsonObject = JSONObject.parseObject(result);
} catch (Exception e) {
e.printStackTrace();
}
return jsonObject;
}
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2.Constants.java

/** 注册事件回调接口(请求方式:post) */
public static final String REGISTER_CALL_BACK = "https://oapi.dingtalk.com/call_back/register_call_back?access_token=ACCESS_TOKEN";

/** 查询事件回调接口(请求方式:get) */
public static final String GET_CALL_BACK = "https://oapi.dingtalk.com/call_back/get_call_back?access_token=ACCESS_TOKEN";

/** 更新事件回调接口(请求方式:post) */
public static final String UPDATE_CALL_BACK = "https://oapi.dingtalk.com/call_back/update_call_back?access_token=ACCESS_TOKEN";

/** 删除事件回调接口(请求方式:get) */
public static final String DELETE_CALL_BACK = "https://oapi.dingtalk.com/call_back/delete_call_back?access_token=ACCESS_TOKEN";

/** 获取回调失败的结果 (请求方式:get)*/
public static final String GET_CALL_BACK_FAILED_RESULT = "https://oapi.dingtalk.com/call_back/get_call_back_failed_result?access_token=ACCESS_TOKEN";
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
3.Utils.java

/**
*
* @return
*/
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();
}

/*
* int转byte数组,高位在前
*/
public static byte[] int2Bytes(int count) {
byte[] byteArr = new byte[4];
byteArr[3] = (byte) (count & 0xFF);
byteArr[2] = (byte) (count >> 8 & 0xFF);
byteArr[1] = (byte) (count >> 16 & 0xFF);
byteArr[0] = (byte) (count >> 24 & 0xFF);
return byteArr;
}

/**
* 高位在前bytes数组转int
* @param byteArr
* @return
*/
public static int bytes2int(byte[] byteArr) {
int count = 0;
for (int i = 0; i < 4; i++) {
count <<= 8;
count |= byteArr[i] & 0xff;
}
return count;
}
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
4.DingtalkController.java
@CrossOrigin
@ResponseBody
@RequestMapping(value = "/join/dingtalk/callbackreceive.htm", method = RequestMethod.POST,produces = "application/json;charset=UTF-8")
public String addCallbackreceive(HttpServletRequest request,HttpServletResponse response) {
String result="false";
try {
/** url中的签名 **/
String msgSignature = request.getParameter("signature");
/** url中的时间戳 **/
String timeStamp = request.getParameter("timestamp");
/** url中的随机字符串 **/
String nonce = request.getParameter("nonce");
/** 取得JSON对象中的encrypt字段 **/
String encrypt = "";

ServletInputStream sis = request.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(sis));
String line = null;
StringBuilder sb = new StringBuilder();
while ((line = br.readLine()) != null) {
sb.append(line);
}
JSONObject jsonEncrypt = JSONObject.parseObject(sb.toString());
String res = "success"; //res是需要返回给钉钉服务器的字符串,一般为success;
if(jsonEncrypt!=null&&jsonEncrypt.size()>0&&jsonEncrypt.containsKey("encrypt")){
encrypt = jsonEncrypt.getString("encrypt");
String decodeEncrypt = decodeEncrypt(msgSignature, timeStamp, nonce, encrypt); //密文解密
JSONObject decodeEncryptJson = JSONObject.parseObject(decodeEncrypt);
String eventType ="";
if(decodeEncryptJson!=null&&decodeEncryptJson.size()>0&&decodeEncryptJson.containsKey("EventType")){
eventType = decodeEncryptJson.getString("EventType"); //回调类型
}

JSONObject jsonObjectData = new JSONObject();
//根据不同的回调类型,进行相应的操作
switch (eventType) {
//审批任务开始,结束,转交
case AddressListRegister.SEALAPPLY_TASK_CHANGE :
String processInstanceId =decodeEncryptJson.getString("processInstanceId");//审批实例id
//根据你审批实例获取审批单内容
String corpId =decodeEncryptJson.getString("corpId");//审批实例id
String type = decodeEncryptJson.getString("type");//类型,判断是开始还是结束
String staffId = decodeEncryptJson.getString("staffId");//审批人的id
String createTime = decodeEncryptJson.getString("createTime");//审批开始时间
long time = Long.parseLong(createTime);
Date date = new Date(time);
createTime= StringUtil.DateToString(date, DateUtil.DATE_FORMAT_YMDHM);//开始时间
Date createTimeDate =StringUtil.StringToDate(createTime, DateUtil.DATE_FORMAT_LX_YMDHMS);
String title = decodeEncryptJson.getString("title");//实例的标题
if(decodeEncryptJson.getString("type").equals("strat")){

}else if(decodeEncryptJson.getString("type").equals("finish")){
String finishTime = decodeEncryptJson.getString("finishTime");//审批结束时间
long timeEnd = Long.parseLong(finishTime);
Date dateEnd = new Date(timeEnd);
createTime= StringUtil.DateToString(dateEnd, DateUtil.DATE_FORMAT_YMDHM);//审批结束时间
Date finishTimeDate =StringUtil.StringToDate(createTime, DateUtil.DATE_FORMAT_LX_YMDHMS);

String result_type = decodeEncryptJson.getString("result");//同意时result为agree,拒绝时result为refuse
if("agree".equals(result_type)||"refuse".equals(result_type)){//任务结束(同意或者拒绝)
String remark = decodeEncryptJson.getString("remark");//审批意见

}else if("redirect".equals(result_type)){//任务转交

}
}
break;
//审批实例开始,结束
case AddressListRegister.SEALAPPLY_INSTANCE_CHANGE :
String start_instance_Person = decodeEncryptJson.getString("staffId");//发起实例的员工

break;

default: // do something
break;
}
result = codeEncrypt(res, timeStamp, nonce).toString();
//response.getWriter().append(codeEncrypt(res, timeStamp, nonce).toString()); //返回加密后的数据
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 创建加密/解密 类
* @return
*/
public DingTalkEncryptor createDingTalkEncryptor(){
DingTalkEncryptor dingTalkEncryptor = null; //加密方法类
try {
dingTalkEncryptor = new DingTalkEncryptor(AddressListRegister.TOKEN, AddressListRegister.AES_KEY,AddressListRegister.CORPID); //创建加解密类
} catch (DingTalkEncryptException e) {
e.printStackTrace();
}
return dingTalkEncryptor;
}

/**
* encrypt解密
* @param msgSignature
* @param timeStamp
* @param nonce
* @param encrypt 密文
* @return decodeEncrypt 解密后的明文
*/
public String decodeEncrypt(String msgSignature,String timeStamp,String nonce,String encrypt){
String decodeEncrypt = null;
try {
decodeEncrypt = createDingTalkEncryptor().getDecryptMsg(msgSignature, timeStamp, nonce, encrypt); //encrypt解密
} catch (DingTalkEncryptException e) {
e.printStackTrace();
}
return decodeEncrypt;
}

/**
* 对返回信息进行加密
* @param res
* @param timeStamp
* @param nonce
* @return
*/
public JSONObject codeEncrypt(String res,String timeStamp,String nonce){
long timeStampLong = Long.parseLong(timeStamp);
Map<String, String> jsonMap = null;
try {
jsonMap = createDingTalkEncryptor().getEncryptedMap(res, timeStampLong, nonce); //jsonMap是需要返回给钉钉服务器的加密数据包
} catch (DingTalkEncryptException e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
JSONObject json = new JSONObject();
json.putAll(jsonMap);
return json;
}

//测试方法
public static void main(String[] args) throws ApiException {
String accesstoken = DingTalkUtils.getAccessToken_new("ding9doc7pnuxjwpvpw7","XwEoy0NQSCZWRmR8GfwsBoqg0_1nVnRsZy7GWgROsQhaFnnvixaZMj_5bzosLVQa");
String token = AddressListRegister.TOKEN;
String aesKey = AddressListRegister.AES_KEY;
String callBackUrl = "http://21664l066q.imwork.net:50125/join/dingtalk/callbackreceive.htm";

List<String> listStr = new ArrayList<String>();
listStr.add("bpms_task_change");
listStr.add("bpms_instance_change");
JSONObject registerCallBack = DingTalkUtils.registerCallBack(accesstoken, listStr, token, aesKey, callBackUrl);
System.out.println("注册事件返回:"+registerCallBack);

JSONObject callBack = DingTalkUtils.getCallBack(accesstoken);
System.out.println("查询注册事件:"+callBack);

}
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
5. 以下是公共类
5.1
package com.shjysoft.base.util.dingtalk;

/**
* <p>几个参数和通讯录和审批注册 需要监听的事件类型<p>
* @version 1.0
* @author li_hao
* @date 2017年12月15日
*/
public class AddressListRegister{

/**企业的corpid */
public static final String CORPID = "ding7ee8e144c63d6a48";
/**钉钉开放平台上,开发者设置的token */
public static final String TOKEN = "token";
/**数据加密密钥。用于回调数据的加密,长度固定为43个字符,从a-z, A-Z, 0-9共62个字符中选取,您可以随机生成,ISV(服务提供商)推荐使用注册套件时填写的EncodingAESKey */
public static final String AES_KEY = "abcxx7p5qnb6zs3xxxxxlkfmxqfkv23d40yd0xxxxxx";

/**通讯录用户增加 */
public static final String USER_ADD_ORG = "user_add_org";
/**通讯录用户更改*/
public static final String USER_MODIFY_ORG = "user_modify_org";
/** 通讯录用户离职 */
public static final String USER_LEAVE_ORG = "user_leave_org";
/** 通讯录用户被设为管理员 */
public static final String ORG_ADMIN_ADD = "org_admin_add";
/** 通讯录用户被取消设置管理员 */
public static final String ORG_ADMIN_REMOVE = "org_admin_remove";
/**通讯录企业部门创建*/
public static final String ORG_DEPT_CREATE = "org_dept_create";
/** 通讯录企业部门修改 */
public static final String ORG_DEPT_MODIFY = "org_dept_modify";
/**通讯录企业部门删除*/
public static final String ORG_DEPT_REMOVE = "org_dept_remove";
/**企业被解散*/
public static final String ORG_REMOVE = "org_remove";
/**企业信息发生变更*/
public static final String ORG_CHANGE = "org_change";
/**员工角色信息发生变更*/
public static final String LABEL_USER_CHANGE = "label_user_change";
/**增加角色或者角色组*/
public static final String LABEL_CONF_ADD = "label_conf_add";
/**删除角色或者角色组*/
public static final String LABEL_CONF_DEL = "label_conf_del";
/**修改角色或者角色组*/
public static final String LABEL_CONF_MODIFY = "label_conf_modify";
/**测试回调接口事件类型*/
public static final String CHECK_URL = "check_url";
public static final String SEALAPPLY_TASK_CHANGE = "bpms_task_change";//审批任务开始,结束,转交
public static final String SEALAPPLY_INSTANCE_CHANGE = "bpms_instance_change";//审批实例开始,结束
}
5.2
package com.shjysoft.base.util.dingtalk;
import java.util.HashMap;
import java.util.Map;

/**
* 加解密异常类
*/
public class DingTalkEncryptException extends Exception {
/**成功**/
public static final int SUCCESS = 0;
/**加密明文文本非法**/
public final static int ENCRYPTION_PLAINTEXT_ILLEGAL = 900001;
/**加密时间戳参数非法**/
public final static int ENCRYPTION_TIMESTAMP_ILLEGAL = 900002;
/**加密随机字符串参数非法**/
public final static int ENCRYPTION_NONCE_ILLEGAL = 900003;
/**不合法的aeskey**/
public final static int AES_KEY_ILLEGAL = 900004;
/**签名不匹配**/
public final static int SIGNATURE_NOT_MATCH = 900005;
/**计算签名错误**/
public final static int COMPUTE_SIGNATURE_ERROR = 900006;
/**计算加密文字错误**/
public final static int COMPUTE_ENCRYPT_TEXT_ERROR = 900007;
/**计算解密文字错误**/
public final static int COMPUTE_DECRYPT_TEXT_ERROR = 900008;
/**计算解密文字长度不匹配**/
public final static int COMPUTE_DECRYPT_TEXT_LENGTH_ERROR = 900009;
/**计算解密文字suiteKey(ISV)或者corpid(普通企业)不匹配**/
public final static int COMPUTE_DECRYPT_TEXT_CORPID_ERROR = 900010;

private static Map<Integer,String> msgMap = new HashMap<Integer,String>();
static{
msgMap.put(SUCCESS,"成功");
msgMap.put(ENCRYPTION_PLAINTEXT_ILLEGAL,"加密明文文本非法");
msgMap.put(ENCRYPTION_TIMESTAMP_ILLEGAL,"加密时间戳参数非法");
msgMap.put(ENCRYPTION_NONCE_ILLEGAL,"加密随机字符串参数非法");
msgMap.put(SIGNATURE_NOT_MATCH,"签名不匹配");
msgMap.put(COMPUTE_SIGNATURE_ERROR,"签名计算失败");
msgMap.put(AES_KEY_ILLEGAL,"不合法的aes key");
msgMap.put(COMPUTE_ENCRYPT_TEXT_ERROR,"计算加密文字错误");
msgMap.put(COMPUTE_DECRYPT_TEXT_ERROR,"计算解密文字错误");
msgMap.put(COMPUTE_DECRYPT_TEXT_LENGTH_ERROR,"计算解密文字长度不匹配");
msgMap.put(COMPUTE_DECRYPT_TEXT_CORPID_ERROR,"计算解密文字suiteKey(ISV)或者corpid(普通企业)不匹配");
}

public Integer code;
public DingTalkEncryptException(Integer exceptionCode){
super(msgMap.get(exceptionCode));
this.code = exceptionCode;
}
}
5.3
package com.shjysoft.base.util.dingtalk;
import java.io.ByteArrayOutputStream;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.util.*;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

import com.shjysoft.base.util.ip.Utils;

/**
* 加解密方法
* 在ORACLE官方网站下载JCE无限制权限策略文件
* JDK6的下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html
* JDK7的下载地址: http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html
*/
public class DingTalkEncryptor {

private static final Charset CHARSET = Charset.forName("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 ISV进行配置的时候应该传对应套件的SUITE_KEY(第一次创建时传的是默认的CREATE_SUITE_KEY),普通企业是Corpid
* @throws DingTalkEncryptException 执行失败,请查看该异常的错误码和具体的错误信息
*/
public DingTalkEncryptor(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 + "=");
}

/**
* 将和钉钉开放平台同步的消息体加密,返回加密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);
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 4; i++) {
sb.append(array[i]);
}
String str = sb.toString();
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);
}
}

}
5.4
package com.shjysoft.base.util.dingtalk;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.io.IOUtils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.OapiProcessinstanceCreateRequest;
import com.dingtalk.api.request.OapiProcessinstanceCreateRequest.FormComponentValueVo;
import com.dingtalk.api.request.OapiProcessinstanceGetRequest;
import com.dingtalk.api.response.OapiProcessinstanceCreateResponse;
import com.dingtalk.api.response.OapiProcessinstanceGetResponse;
import com.dingtalk.api.response.OapiProcessinstanceGetResponse.ProcessInstanceTopVo;
import com.shjysoft.base.util.common.HttpRequest;
import com.shjysoft.base.util.common.StringUtil;
import com.shjysoft.base.util.constants.Constants;
import com.taobao.api.ApiException;
/**
* 钉钉微应用接口工具类
* @author zhouqi
*
*/
public class DingTalkUtils {

/**
* 获取accestoken
* @return
*/
public static String getAccessToken(String corpid,String corpSecret){
String accesstoken = null;
try {
HttpClient httpClient = new HttpClient();
GetMethod getMethod = new GetMethod("https://oapi.dingtalk.com/gettoken?corpid="+corpid+"&corpsecret="+corpSecret);
int stats=httpClient.executeMethod(getMethod);
if(stats == HttpStatus.SC_OK){
String result = IOUtils.toString(getMethod.getResponseBodyAsStream(),"UTF-8");
JSONObject json = JSONObject.parseObject(result);
if(Integer.parseInt(json.get("errcode").toString()) == 0){
accesstoken = json.getString("access_token");
}
}
} catch (HttpException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return accesstoken;
}

/**
* 获取accestoken--新版本
* @return
*/
public static String getAccessToken_new(String appkey,String appsecret){
String accesstoken = null;
try {
HttpClient httpClient = new HttpClient();
GetMethod getMethod = new GetMethod("https://oapi.dingtalk.com/gettoken?appkey="+appkey+"&appsecret="+appsecret);
int stats=httpClient.executeMethod(getMethod);
if(stats == HttpStatus.SC_OK){
String result = IOUtils.toString(getMethod.getResponseBodyAsStream(),"UTF-8");
JSONObject json = JSONObject.parseObject(result);
if(Integer.parseInt(json.get("errcode").toString()) == 0){
accesstoken = json.getString("access_token");
}
}
} catch (HttpException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return accesstoken;
}

/**
* 获取jsapi_ticket
* @param accesstoken
* @return
*/
public static String getJsapi_ticket(String accesstoken){
String jsapi_ticket = null;
try {
HttpClient httpClient = new HttpClient();
GetMethod getMethod = new GetMethod("https://oapi.dingtalk.com/get_jsapi_ticket?access_token="+accesstoken);
int stats=httpClient.executeMethod(getMethod);
if(stats == HttpStatus.SC_OK){
String result = IOUtils.toString(getMethod.getResponseBodyAsStream(),"UTF-8");
JSONObject json = JSONObject.parseObject(result);
if(Integer.parseInt(json.get("errcode").toString()) == 0){
jsapi_ticket = json.getString("ticket");
}
}
} catch (HttpException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return jsapi_ticket;
}

/**
* 根据code获取userid
* @param accesstoken
* @param code
* @return
*/
public static String getUserId(String accesstoken,String code){
String userId = null;
try {
HttpClient httpClient = new HttpClient();
GetMethod getMethod = new GetMethod("https://oapi.dingtalk.com/user/getuserinfo?access_token="+accesstoken+"&code="+code);
int stats = httpClient.executeMethod(getMethod);
if(stats == HttpStatus.SC_OK){
String result = IOUtils.toString(getMethod.getResponseBodyAsStream(),"UTF-8");
JSONObject json = JSONObject.parseObject(result);
if(Integer.parseInt(json.get("errcode").toString()) == 0){
userId = json.getString("userid");
}
}
} catch (HttpException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return userId;
}

/**
* 根据userid获取用户信息
* @param accesstoken
* @param userId
* @return
*/
public static String getUserInfo(String accesstoken,String userId){
String userInfo = null;
try {
HttpClient httpClient = new HttpClient();
GetMethod getMethod = new GetMethod("https://oapi.dingtalk.com/user/get?access_token="+accesstoken+"&userid="+userId);
int stats = httpClient.executeMethod(getMethod);
if(stats == HttpStatus.SC_OK){
userInfo = IOUtils.toString(getMethod.getResponseBodyAsStream(),"UTF-8");
}
} catch (HttpException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return userInfo;
}

/**
* 获取部门列表
* @param accesstoken
* @return
*/
public static String getDepartmentList(String accesstoken,String parent_id){
try {
HttpClient httpClient = new HttpClient();
GetMethod getMethod = new GetMethod("https://oapi.dingtalk.com/department/list?access_token="+accesstoken+"&id="+parent_id);
int stats = httpClient.executeMethod(getMethod);
if(stats == HttpStatus.SC_OK){
String result = IOUtils.toString(getMethod.getResponseBodyAsStream(),"UTF-8");
JSONObject json = JSONObject.parseObject(result);
if(Integer.parseInt(json.get("errcode").toString()) == 0){
return json.getString("department");
}
}
} catch (HttpException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}

/**
* 获取部门列表
* @param accesstoken
* @return
*/
public static String getDepartmentList(String accesstoken){
try {
HttpClient httpClient = new HttpClient();
GetMethod getMethod = new GetMethod("https://oapi.dingtalk.com/department/list?access_token="+accesstoken+"");
int stats = httpClient.executeMethod(getMethod);
if(stats == HttpStatus.SC_OK){
String result = IOUtils.toString(getMethod.getResponseBodyAsStream(),"UTF-8");
JSONObject json = JSONObject.parseObject(result);
if(Integer.parseInt(json.get("errcode").toString()) == 0){
return json.getString("department");
}
}
} catch (HttpException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}

/**
* 获取部门下的员工
* @param accesstoken
* @return
*/
public static String getUserByDepartment(String accesstoken,String dept_id){
try {
HttpClient httpClient = new HttpClient();
GetMethod getMethod = new GetMethod("https://oapi.dingtalk.com/user/list?access_token="+accesstoken+"&department_id="+dept_id);
int stats = httpClient.executeMethod(getMethod);
if(stats == HttpStatus.SC_OK){
String result = IOUtils.toString(getMethod.getResponseBodyAsStream(),"UTF-8");
JSONObject json = JSONObject.parseObject(result);
if(Integer.parseInt(json.get("errcode").toString()) == 0){
return json.getString("userlist");
}
}
} catch (HttpException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}

/**
* 获取CorpSecret授权范围
* @param accesstoken
* @return
*/
public static String getCorpSecretScope(String accesstoken){
String result = null;
try {
HttpClient httpClient = new HttpClient();
GetMethod getMethod = new GetMethod("https://oapi.dingtalk.com/auth/scopes?access_token="+accesstoken);
int stats = httpClient.executeMethod(getMethod);
if(stats == HttpStatus.SC_OK){
result = IOUtils.toString(getMethod.getResponseBodyAsStream(),"UTF-8");
}
} catch (HttpException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
/**
* 发起审批
* @param map
* @param seal_names
* @param seal_counts
* @param agent_id
* @param processCode
* @param originatorUserId
* @param dept_id
* @param approvers
* @param ccList
* @param accesstoken
* @return
*/
public static String getProcess_instance_id(Map<String,String> map,String seal_names,String seal_counts,String agent_id,String processCode,
String originatorUserId,String dept_id,String approvers,String ccList,String accesstoken){
String process_instance_id = null;
try {
DefaultDingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/processinstance/create");
OapiProcessinstanceCreateRequest request = new OapiProcessinstanceCreateRequest();
if(StringUtil.isNotNullOrBlank(agent_id)){
request.setAgentId(Long.parseLong(agent_id));//企业微应用标识
}
if(StringUtil.isNotNullOrBlank(processCode)){
request.setProcessCode(processCode);//审批流的唯一码
}
if(StringUtil.isNotNullOrBlank(originatorUserId)){
request.setOriginatorUserId(originatorUserId);//审批实例发起人的userid
}
if(StringUtil.isNotNullOrBlank(dept_id)){
request.setDeptId(Long.parseLong(dept_id));//发起人所在的部门
}
if(StringUtil.isNotNullOrBlank(approvers)){
request.setApprovers(approvers);//送给谁审批
}
if(StringUtil.isNotNullOrBlank(ccList)){
request.setCcList(ccList);//抄送给谁的id
request.setCcPosition("START");//抄送时间
}
List<FormComponentValueVo> formComponentValues = new ArrayList<FormComponentValueVo>();

//循环单项的表单内容
for (Map.Entry<String, String> entry : map.entrySet()) {
//System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
FormComponentValueVo vo = new FormComponentValueVo();
vo.setName(entry.getKey());
if(StringUtil.isNotNullOrBlank(entry.getValue())){
vo.setValue(entry.getValue());
}else{
vo.setValue("");
}

formComponentValues.add(vo);
}
//明细:
String[] seal_names_ = seal_names.split(",");
String[] seal_counts_ = seal_counts.split(",");
if(seal_names_.length==1){
FormComponentValueVo detail1 = new FormComponentValueVo();
detail1.setName("印章名称");
detail1.setValue(seal_names_[0]);
FormComponentValueVo detail2 = new FormComponentValueVo();
detail2.setName("盖章次数");
detail2.setValue(seal_counts_[0]);
FormComponentValueVo obj = new FormComponentValueVo();
obj.setName("印章明细");
obj.setValue(JSON.toJSONString(Arrays.asList(Arrays.asList(detail1, detail2))));
formComponentValues.add(obj);
}else if(seal_names_.length==2){
FormComponentValueVo detail11 = new FormComponentValueVo();
detail11.setName("印章名称");
detail11.setValue(seal_names_[0]);
FormComponentValueVo detail12 = new FormComponentValueVo();
detail12.setName("盖章次数");
detail12.setValue(seal_counts_[0]);

FormComponentValueVo detail21 = new FormComponentValueVo();
detail21.setName("印章名称");
detail21.setValue(seal_names_[1]);
FormComponentValueVo detail22 = new FormComponentValueVo();
detail22.setName("盖章次数");
detail22.setValue(seal_counts_[1]);
FormComponentValueVo obj = new FormComponentValueVo();
obj.setName("印章明细");
obj.setValue(JSON.toJSONString(Arrays.asList(Arrays.asList(detail11, detail12),Arrays.asList(detail11, detail12))));
formComponentValues.add(obj);
}else if(seal_names_.length==3){
FormComponentValueVo detail11 = new FormComponentValueVo();
detail11.setName("印章名称");
detail11.setValue(seal_names_[0]);
FormComponentValueVo detail12 = new FormComponentValueVo();
detail12.setName("盖章次数");
detail12.setValue(seal_counts_[0]);

FormComponentValueVo detail21 = new FormComponentValueVo();
detail21.setName("印章名称");
detail21.setValue(seal_names_[1]);
FormComponentValueVo detail22 = new FormComponentValueVo();
detail22.setName("盖章次数");
detail22.setValue(seal_counts_[1]);

FormComponentValueVo detail31 = new FormComponentValueVo();
detail31.setName("印章名称");
detail31.setValue(seal_names_[2]);
FormComponentValueVo detail32 = new FormComponentValueVo();
detail32.setName("盖章次数");
detail32.setValue(seal_counts_[2]);
FormComponentValueVo obj = new FormComponentValueVo();
obj.setName("印章明细");
obj.setValue(JSON.toJSONString(Arrays.asList(Arrays.asList(detail11, detail12),Arrays.asList(detail21, detail22),Arrays.asList(detail31, detail32))));
formComponentValues.add(obj);
}
request.setFormComponentValues(formComponentValues);
OapiProcessinstanceCreateResponse response = client.execute(request,accesstoken);
String body = response.getBody();
process_instance_id = response.getProcessInstanceId();
} catch (ApiException e) {
e.printStackTrace();
}
return process_instance_id;
}
/**
* 根据审批实例id 获取审批实例(此处采用钉钉的sdk方式获取)
* @param accesstoken
* @param process_instance_id
* @return
*/
public static ProcessInstanceTopVo getProcess_instance(String accesstoken,String process_instance_id){
ProcessInstanceTopVo processInstanceTopVo = null;
DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/processinstance/get");
OapiProcessinstanceGetRequest request = new OapiProcessinstanceGetRequest();
request.setProcessInstanceId(process_instance_id);
try {
OapiProcessinstanceGetResponse response = client.execute(request,accesstoken);
if(response.getErrcode()==0){//请求成功
processInstanceTopVo = response.getProcessInstance();
}
} catch (ApiException e) {
e.printStackTrace();
}
return processInstanceTopVo;
}
//根据实例id 获取实例
public static void main(String[] args) {
String accesstoken = DingTalkUtils.getAccessToken_new("ding9doc7pnuxjwpvpw7","XwEoy0NQSCZWRmR8GfwsBoqg0_1nVnRsZy7GWgROsQhaFnnvixaZMj_5bzosLVQa");
getProcess_instance(accesstoken, "770d9292-4d75-442e-841c-6760d630557b");
}

//发起审批
// public static void main(String[] args) {
// Map<String,String> map = new HashMap<String,String>();
// map.put("文件名称", "使用财务章");
// map.put("文件编码", StringUtil.getBillNumberByTime());
// map.put("文件类型", "合同类");
// map.put("文件份数", "5");
// map.put("合同金额", "5000");
// map.put("往来单位", "上海建业信息科技股份有限公司");
// map.put("是否外带","否" );
// map.put("预计归还时间", "234");
// map.put("申请事由", "申请使用");
// map.put("是否远程盖章", "否");
// map.put("盖章确认人", "234");
// map.put("所属部门", "测试部");
// map.put("申请人","张三");
// map.put("申请时间", "2019-03-18");
// map.put("单据状态", "审批中");
// String seal_names ="财务章,法人章,合同章";
// String seal_counts ="10,20,30";
// String agent_id = "";
// String processCode = "PROC-5FYJXW8W-6IU3LLYP3XMZ18HW3OYA3-0DJULDTJ-L";
// String originatorUserId = "07320147141201617";//发起人
// String dept_id = "56176621";//发起人部门
// String approvers = "1715464811691399";//审批人
// String ccList = "48291940761009";
// String accesstoken = DingTalkUtils.getAccessToken_new("ding9doc7pnuxjwpvpw7","XwEoy0NQSCZWRmR8GfwsBoqg0_1nVnRsZy7GWgROsQhaFnnvixaZMj_5bzosLVQa");
// String processInstanceId = getProcess_instance_id(map, seal_names, seal_counts, agent_id, processCode, originatorUserId, dept_id, approvers, ccList, accesstoken);
// DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/processinstance/get");
// OapiProcessinstanceGetRequest request = new OapiProcessinstanceGetRequest();
// request.setProcessInstanceId(processInstanceId);
// try {
// OapiProcessinstanceGetResponse response = client.execute(request,accesstoken);
// ProcessInstanceTopVo ss =response.getProcessInstance();
// ss.getFormComponentValues();
// } catch (ApiException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
// }
/**
* 注册事件回调接口
* @param accesstoken 企业的accesstoken
* @param callBackTag 需要监听的事件类型,共有20种(Array[String])
* @param token 加解密需要用到的token,ISV(服务提供商)推荐使用注册套件时填写的token,普通企业可以随机填写
* @param aesKey 数据加密密钥。用于回调数据的加密,长度固定为43个字符,从a-z, A-Z, 0-9共62个字符中选取,您可以随机生成,ISV(服务提供商)推荐使用注册套件时填写的EncodingAESKey
* @param callBackUrl 接收事件回调的url
* @return
* @throws ApiException
*/
public static JSONObject registerCallBack(String accesstoken,List<String> callBackTag,String token,String aesKey,String callBackUrl) throws ApiException{
String url = Constants.REGISTER_CALL_BACK.replace("ACCESS_TOKEN", accesstoken);
JSONObject jsonReq = new JSONObject();
jsonReq.put("call_back_tag", callBackTag);
jsonReq.put("token", token);
jsonReq.put("aes_key", aesKey);
jsonReq.put("url", callBackUrl);
System.out.println(jsonReq.toString());
JSONObject jsonObject = HttpRequest.doPostStr(url, jsonReq.toString());
return jsonObject;
}

/**
* 查询事件回调接口
* @param accesstoken 企业的accesstoken
* @return
*/
public static JSONObject getCallBack(String accesstoken){
String url = Constants.GET_CALL_BACK.replace("ACCESS_TOKEN", accesstoken);
JSONObject jsonObject = HttpRequest.doGetStr(url);
return jsonObject;
}

/**
* 更新事件回调接口
* @param accesstoken 企业的accesstoken
* @param callBackTag 需要监听的事件类型,共有20种(Array[String])
* @param token 加解密需要用到的token,ISV(服务提供商)推荐使用注册套件时填写的token,普通企业可以随机填写
* @param aesKey 数据加密密钥。用于回调数据的加密,长度固定为43个字符,从a-z, A-Z, 0-9共62个字符中选取,您可以随机生成,ISV(服务提供商)推荐使用注册套件时填写的EncodingAESKey
* @param callBackUrl 接收事件回调的url
* @return
* @throws ApiException
*/
public static JSONObject updateCallBack(String accesstoken,List<String> callBackTag,String token,String aesKey,String callBackUrl) throws ApiException{
String url = Constants.UPDATE_CALL_BACK.replace("ACCESS_TOKEN", accesstoken);
JSONObject jsonReq = new JSONObject();
jsonReq.put("call_back_tag", callBackTag);
jsonReq.put("token", token);
jsonReq.put("aes_key", aesKey);
jsonReq.put("url", callBackUrl);

JSONObject jsonObject = HttpRequest.doPostStr(url, jsonReq.toString());
return jsonObject;
}

/**
* 删除事件回调接口
* @param accesstoken 企业的accesstoken
* @return
*/
public static JSONObject deleteCallBack(String accesstoken){
String url = Constants.DELETE_CALL_BACK.replace("ACCESS_TOKEN", accesstoken);
JSONObject jsonObject = HttpRequest.doGetStr(url);
return jsonObject;
}

/**
* 获取回调失败的结果
* @param accesstoken 企业的accesstoken
* @return
*/
public static JSONObject getCallBackFailedResult(String accesstoken){
String url = Constants.GET_CALL_BACK_FAILED_RESULT.replace("ACCESS_TOKEN", accesstoken);
JSONObject jsonObject = HttpRequest.doGetStr(url);
return jsonObject;
}

}
5.5
package com.shjysoft.base.util.dingtalk;
import java.nio.charset.Charset;
import java.util.Arrays;

/*
* PKCS7算法的加密填充
*/

public class PKCS7Padding {
private final static Charset CHARSET = Charset.forName("utf-8");
private final static int BLOCK_SIZE = 32;

/**
* 填充mode字节
* @param count
* @return
*/
public static byte[] getPaddingBytes(int count) {
int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);
if (amountToPad == 0) {
amountToPad = BLOCK_SIZE;
}
char padChr = chr(amountToPad);
String tmp = new String();
for (int index = 0; index < amountToPad; index++) {
tmp += padChr;
}
return tmp.getBytes(CHARSET);
}

/**
* 移除mode填充字节
* @param decrypted
* @return
*/
public static byte[] removePaddingBytes(byte[] decrypted) {
int pad = (int) decrypted[decrypted.length - 1];
if (pad < 1 || pad > BLOCK_SIZE) {
pad = 0;
}
return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);
}

private static char chr(int a) {
byte target = (byte) (a & 0xFF);
return (char) target;
}

}
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

转载于:https://www.cnblogs.com/ZhouQi-0921/p/10615145.html

钉钉中添加回调监听事件相关推荐

  1. android 常用的监听器,Android中的Keyboard监听事件

    前言 有关Keyboard监听事件,无非就是以下这几种情况: 1,操作显示或隐藏键盘: 2,判断当前键盘的状态: 3,键盘显示或隐藏后的拦截处理: 只要有使用到EditText,避免不了肯定会遇到以上 ...

  2. html中单选按钮设置监听事件,浅谈监听单选框radio改变事件(和layui中单选按钮改变事件),单选框radio...

    浅谈监听单选框radio改变事件(和layui中单选按钮改变事件),单选框radio 若是只引用jquery的话,监听单选按钮改变事件如下: 男 女 $(document).ready(functio ...

  3. Android 自定义Dialog类,并在Activity中实现按钮监听事件

    实际开发中,经常会用到Dialog,比如退出时候会弹出是否退出,或者还有一些编辑框也会用Dialog实现,效果图如下: 开发中遇到的问题无非在于如果在Activity中监听这个Dialog中实现的按钮 ...

  4. html中下拉列表监听事件,ExtJS 下拉框监听事件、日期选择器监听事件、实现动态给items添加删除数据...

    本文将为您描述ExtJS 下拉框监听事件.日期选择器监听事件.实现动态给items添加删除数据,具体实现方法: 1.下拉框 下拉框选择时,触发事件的方法: 在 Ext.form.ComboBox 组件 ...

  5. Bootstrap学习(七)modal模态框、data属性、过渡事件、滚动监听事件

    模态框 模态框(Modal)是覆盖在父窗体上的子窗体.通常,目的是显示来自一个单独的源的内容,可以在不离开父窗体的情况下有一些互动.子窗体可提供信息.交互等. <div class=" ...

  6. java gui 监听组合键,Java之GUI按钮监听事件ActionListener

    /** * ActionListener:按钮监听事件 * @author Administrator */ public class ActionListenerDemo{ public stati ...

  7. html5监听动画结束,css3动画是否结束监听事件

    css3动画是否结束监听事件: var domObj = $("#id"); domObj [0].addEventListener("webkitAnimationEn ...

  8. miniui单元格点击弹框_miniui 给表格行添加监听事件的几种方法以及点击某列列名数据不能排序的问题...

    最近在使用miniui框架做开发,在做表格行的点击监听事件中发现了几个属性,都可以起到监听效果但是执行的结果却大有不同.好了废话不多说,直接上代码. autoload="true" ...

  9. RecycleView添加适配器的监听事件

    看一下Adapter的完整代码. public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder>{// ...

最新文章

  1. 【2020新书推荐】Introduction to Deep Learning
  2. ADO.NET与XML的结合
  3. activiti候选人的多个场景应用
  4. python re模块 字符串匹配_re模块实现正则表达式之match()方法
  5. 【CC精品教程】任务三:CC刺像控点,提交空三,新建重建项目(三维格网、三维点云、DOM和DSM)
  6. The used SELECT statements have a different number of columns 错误解决
  7. SCOM2012R2 APM系列(三) 配置Java应用程序监控
  8. python os write_Python os.write() 方法
  9. Linux命令行操作基础练习
  10. STM32传感器外设集--心率模块(MAX30102)
  11. 三个水桶等分8升水的问题
  12. github rust 项目Travis ci配置
  13. PHP在线运行,在线编译
  14. Playing Atari with Deep Reinforcement Learning-笔记
  15. matlab2017b和2018a,Matlab 2018a 比2017b有哪些改进?
  16. 线下WINDOWS主机挂载华为云存储
  17. Hi3516DV300
  18. 嵌入式开发对学历门槛要求高吗?
  19. ESP32_micropython学习(一):点亮一盏灯
  20. Linux下使用hiredis库与libevent实现异步接口的I/O复用

热门文章

  1. 解决input输入的是空格问题
  2. python绘制哆啦a梦实训报告_python画哆啦A梦和大雄
  3. VS2017 常用函数与数据类型-笔记
  4. 一个html页面请求多个接口,前端页面,一个页面几个接口请求比较合理?
  5. 推荐一些实用的谷歌浏览器翻译插件
  6. 共享办公室,月赚2万-5万的阳光创业项目
  7. c#发送邮件(带抄送,密送,群发,附件)
  8. [SV]SystemVerilog 断言(SVA)检查器库(OVL)
  9. 企业项目实战k8s篇(十)Volumes配置管理
  10. 120G+训练好的word2vec模型(中文词向量)