2025-01-08稻花香主数据企业微信同步
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
package com.qq.weixin.mp.aes;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class AesException extends Exception {
|
||||
|
||||
public final static int OK = 0;
|
||||
public final static int ValidateSignatureError = -40001;
|
||||
public final static int ParseJsonError = -40002;
|
||||
public final static int ComputeSignatureError = -40003;
|
||||
public final static int IllegalAesKey = -40004;
|
||||
public final static int ValidateCorpidError = -40005;
|
||||
public final static int EncryptAESError = -40006;
|
||||
public final static int DecryptAESError = -40007;
|
||||
public final static int IllegalBuffer = -40008;
|
||||
public final static int EncodeBase64Error = -40009;
|
||||
public final static int DecodeBase64Error = -40010;
|
||||
public final static int GenReturnJsonError = -40011;
|
||||
|
||||
private int code;
|
||||
|
||||
private static String getMessage(int code) {
|
||||
switch (code) {
|
||||
case ValidateSignatureError:
|
||||
return "签名验证错误";
|
||||
case ParseJsonError:
|
||||
return "json解析失败";
|
||||
case ComputeSignatureError:
|
||||
return "sha加密生成签名失败";
|
||||
case IllegalAesKey:
|
||||
return "SymmetricKey非法";
|
||||
case ValidateCorpidError:
|
||||
return "corpid校验失败";
|
||||
case EncryptAESError:
|
||||
return "aes加密失败";
|
||||
case DecryptAESError:
|
||||
return "aes解密失败";
|
||||
case IllegalBuffer:
|
||||
return "解密后得到的buffer非法";
|
||||
case EncodeBase64Error:
|
||||
return "base64加密错误";
|
||||
case DecodeBase64Error:
|
||||
return "base64解密错误";
|
||||
case GenReturnJsonError:
|
||||
return "josn生成失败";
|
||||
default:
|
||||
return null; // cannot be
|
||||
}
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
AesException(int code) {
|
||||
super(getMessage(code));
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.qq.weixin.mp.aes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
class ByteGroup {
|
||||
ArrayList<Byte> byteContainer = new ArrayList<Byte>();
|
||||
|
||||
public byte[] toBytes() {
|
||||
byte[] bytes = new byte[byteContainer.size()];
|
||||
for (int i = 0; i < byteContainer.size(); i++) {
|
||||
bytes[i] = byteContainer.get(i);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public ByteGroup addBytes(byte[] bytes) {
|
||||
for (byte b : bytes) {
|
||||
byteContainer.add(b);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return byteContainer.size();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 对企业微信发送给企业后台的消息加解密示例代码.
|
||||
*
|
||||
* @copyright Copyright (c) 1998-2020 Tencent Inc.
|
||||
*/
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
package com.qq.weixin.mp.aes;
|
||||
|
||||
/**
|
||||
* 针对 org.json.JSONObject,
|
||||
* 要编译打包架包json
|
||||
* 官方源码下载地址 : https://github.com/stleary/JSON-java, jar包下载地址 : https://mvnrepository.com/artifact/org.json/json
|
||||
*/
|
||||
import org.json.JSONObject;
|
||||
|
||||
|
||||
/**
|
||||
* JsonParse class
|
||||
*
|
||||
* 提供提取消息格式中的密文及生成回复消息格式的接口.
|
||||
*/
|
||||
class JsonParse {
|
||||
|
||||
/**
|
||||
* 提取出 JSON 包中的加密消息
|
||||
* @param jsontext 待提取的json字符串
|
||||
* @return 提取出的加密消息字符串
|
||||
* @throws AesException
|
||||
*/
|
||||
public static Object[] extract(String jsontext) throws AesException {
|
||||
Object[] result = new Object[3];
|
||||
try {
|
||||
|
||||
JSONObject json = new JSONObject(jsontext);
|
||||
String encrypt_msg = json.getString("encrypt");
|
||||
String tousername = json.getString("tousername");
|
||||
String agentid = json.getString("agentid");
|
||||
|
||||
result[0] = tousername;
|
||||
result[1] = encrypt_msg;
|
||||
result[2] = agentid;
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new AesException(AesException.ParseJsonError);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成json消息
|
||||
* @param encrypt 加密后的消息密文
|
||||
* @param signature 安全签名
|
||||
* @param timestamp 时间戳
|
||||
* @param nonce 随机字符串
|
||||
* @return 生成的json字符串
|
||||
*/
|
||||
public static String generate(String encrypt, String signature, String timestamp, String nonce) {
|
||||
|
||||
String format = "{\"encrypt\":\"%1$s\",\"msgsignature\":\"%2$s\",\"timestamp\":\"%3$s\",\"nonce\":\"%4$s\"}";
|
||||
return String.format(format, encrypt, signature, timestamp, nonce);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* 对企业微信发送给企业后台的消息加解密示例代码.
|
||||
*
|
||||
* @copyright Copyright (c) 1998-2014 Tencent Inc.
|
||||
*/
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
package com.qq.weixin.mp.aes;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 提供基于PKCS7算法的加解密接口.
|
||||
*/
|
||||
class PKCS7Encoder {
|
||||
static Charset CHARSET = Charset.forName("utf-8");
|
||||
static int BLOCK_SIZE = 32;
|
||||
|
||||
/**
|
||||
* 获得对明文进行补位填充的字节.
|
||||
*
|
||||
* @param count 需要进行填充补位操作的明文字节个数
|
||||
* @return 补齐用的字节数组
|
||||
*/
|
||||
static byte[] encode(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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除解密后明文的补位字符
|
||||
*
|
||||
* @param decrypted 解密后的明文
|
||||
* @return 删除补位字符后的明文
|
||||
*/
|
||||
static byte[] decode(byte[] decrypted) {
|
||||
int pad = (int) decrypted[decrypted.length - 1];
|
||||
if (pad < 1 || pad > 32) {
|
||||
pad = 0;
|
||||
}
|
||||
return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数字转化成ASCII码对应的字符,用于对明文进行补码
|
||||
*
|
||||
* @param a 需要转化的数字
|
||||
* @return 转化得到的字符
|
||||
*/
|
||||
static char chr(int a) {
|
||||
byte target = (byte) (a & 0xFF);
|
||||
return (char) target;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 对企业微信发送给企业后台的消息加解密示例代码.
|
||||
*
|
||||
* @copyright Copyright (c) 1998-2014 Tencent Inc.
|
||||
*/
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
package com.qq.weixin.mp.aes;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* SHA1 class
|
||||
*
|
||||
* 计算消息签名接口.
|
||||
*/
|
||||
class SHA1 {
|
||||
|
||||
/**
|
||||
* 用SHA1算法生成安全签名
|
||||
* @param token 票据
|
||||
* @param timestamp 时间戳
|
||||
* @param nonce 随机字符串
|
||||
* @param encrypt 密文
|
||||
* @return 安全签名
|
||||
* @throws AesException
|
||||
*/
|
||||
public static String getSHA1(String token, String timestamp, String nonce, String encrypt) throws AesException
|
||||
{
|
||||
try {
|
||||
String[] array = new String[] { token, timestamp, nonce, encrypt };
|
||||
StringBuffer sb = new StringBuffer();
|
||||
// 字符串排序
|
||||
Arrays.sort(array);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
sb.append(array[i]);
|
||||
}
|
||||
String str = sb.toString();
|
||||
// SHA1签名生成
|
||||
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) {
|
||||
e.printStackTrace();
|
||||
throw new AesException(AesException.ComputeSignatureError);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
/**
|
||||
* 对企业微信发送给企业后台的消息加解密示例代码.
|
||||
*
|
||||
* @copyright Copyright (c) 1998-2014 Tencent Inc.
|
||||
*/
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 针对org.apache.commons.codec.binary.Base64,
|
||||
* 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本)
|
||||
* 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi
|
||||
*/
|
||||
package com.qq.weixin.mp.aes;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
|
||||
/**
|
||||
* 提供接收和推送给企业微信消息的加解密接口(UTF8编码的字符串).
|
||||
* <ol>
|
||||
* <li>第三方回复加密消息给企业微信</li>
|
||||
* <li>第三方收到企业微信发送的消息,验证消息的安全性,并对消息进行解密。</li>
|
||||
* </ol>
|
||||
* 说明:异常java.security.InvalidKeyException:illegal Key Size的解决方案
|
||||
* <ol>
|
||||
* <li>在官方网站下载JCE无限制权限策略文件(JDK7的下载地址:
|
||||
* http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html</li>
|
||||
* <li>下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt</li>
|
||||
* <li>如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件</li>
|
||||
* <li>如果安装了JDK,将两个jar文件放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件</li>
|
||||
* </ol>
|
||||
*/
|
||||
public class WXBizJsonMsgCrypt {
|
||||
static Charset CHARSET = Charset.forName("utf-8");
|
||||
Base64 base64 = new Base64();
|
||||
byte[] aesKey;
|
||||
String token;
|
||||
String receiveid;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param token 企业微信后台,开发者设置的token
|
||||
* @param encodingAesKey 企业微信后台,开发者设置的EncodingAESKey
|
||||
* @param receiveid, 不同场景含义不同,详见文档
|
||||
*
|
||||
* @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
|
||||
*/
|
||||
public WXBizJsonMsgCrypt(String token, String encodingAesKey, String receiveid) throws AesException {
|
||||
if (encodingAesKey.length() != 43) {
|
||||
throw new AesException(AesException.IllegalAesKey);
|
||||
}
|
||||
|
||||
this.token = token;
|
||||
this.receiveid = receiveid;
|
||||
aesKey = Base64.decodeBase64(encodingAesKey + "=");
|
||||
}
|
||||
|
||||
// 生成4个字节的网络字节序
|
||||
byte[] getNetworkBytesOrder(int sourceNumber) {
|
||||
byte[] orderBytes = new byte[4];
|
||||
orderBytes[3] = (byte) (sourceNumber & 0xFF);
|
||||
orderBytes[2] = (byte) (sourceNumber >> 8 & 0xFF);
|
||||
orderBytes[1] = (byte) (sourceNumber >> 16 & 0xFF);
|
||||
orderBytes[0] = (byte) (sourceNumber >> 24 & 0xFF);
|
||||
return orderBytes;
|
||||
}
|
||||
|
||||
// 还原4个字节的网络字节序
|
||||
int recoverNetworkBytesOrder(byte[] orderBytes) {
|
||||
int sourceNumber = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
sourceNumber <<= 8;
|
||||
sourceNumber |= orderBytes[i] & 0xff;
|
||||
}
|
||||
return sourceNumber;
|
||||
}
|
||||
|
||||
// 随机生成16位字符串
|
||||
String getRandomStr() {
|
||||
String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
Random random = new Random();
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for (int i = 0; i < 16; i++) {
|
||||
int number = random.nextInt(base.length());
|
||||
sb.append(base.charAt(number));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 对明文进行加密.
|
||||
*
|
||||
* @param text 需要加密的明文
|
||||
* @return 加密后base64编码的字符串
|
||||
* @throws AesException aes加密失败
|
||||
*/
|
||||
String encrypt(String randomStr, String text) throws AesException {
|
||||
ByteGroup byteCollector = new ByteGroup();
|
||||
byte[] randomStrBytes = randomStr.getBytes(CHARSET);
|
||||
byte[] textBytes = text.getBytes(CHARSET);
|
||||
byte[] networkBytesOrder = getNetworkBytesOrder(textBytes.length);
|
||||
byte[] receiveidBytes = receiveid.getBytes(CHARSET);
|
||||
|
||||
// randomStr + networkBytesOrder + text + receiveid
|
||||
byteCollector.addBytes(randomStrBytes);
|
||||
byteCollector.addBytes(networkBytesOrder);
|
||||
byteCollector.addBytes(textBytes);
|
||||
byteCollector.addBytes(receiveidBytes);
|
||||
|
||||
// ... + pad: 使用自定义的填充方式对明文进行补位填充
|
||||
byte[] padBytes = PKCS7Encoder.encode(byteCollector.size());
|
||||
byteCollector.addBytes(padBytes);
|
||||
|
||||
// 获得最终的字节流, 未加密
|
||||
byte[] unencrypted = byteCollector.toBytes();
|
||||
|
||||
try {
|
||||
// 设置加密模式为AES的CBC模式
|
||||
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);
|
||||
|
||||
// 使用BASE64对加密后的字符串进行编码
|
||||
String base64Encrypted = base64.encodeToString(encrypted);
|
||||
|
||||
return base64Encrypted;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new AesException(AesException.EncryptAESError);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对密文进行解密.
|
||||
*
|
||||
* @param text 需要解密的密文
|
||||
* @return 解密得到的明文
|
||||
* @throws AesException aes解密失败
|
||||
*/
|
||||
String decrypt(String text) throws AesException {
|
||||
byte[] original;
|
||||
try {
|
||||
// 设置解密模式为AES的CBC模式
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
||||
SecretKeySpec key_spec = new SecretKeySpec(aesKey, "AES");
|
||||
IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
|
||||
cipher.init(Cipher.DECRYPT_MODE, key_spec, iv);
|
||||
|
||||
// 使用BASE64对密文进行解码
|
||||
byte[] encrypted = Base64.decodeBase64(text);
|
||||
|
||||
// 解密
|
||||
original = cipher.doFinal(encrypted);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new AesException(AesException.DecryptAESError);
|
||||
}
|
||||
|
||||
String jsonContent, from_receiveid;
|
||||
try {
|
||||
// 去除补位字符
|
||||
byte[] bytes = PKCS7Encoder.decode(original);
|
||||
|
||||
// 分离16位随机字符串,网络字节序和receiveid
|
||||
byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20);
|
||||
|
||||
int jsonLength = recoverNetworkBytesOrder(networkOrder);
|
||||
|
||||
jsonContent = new String(Arrays.copyOfRange(bytes, 20, 20 + jsonLength), CHARSET);
|
||||
from_receiveid = new String(Arrays.copyOfRange(bytes, 20 + jsonLength, bytes.length),
|
||||
CHARSET);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new AesException(AesException.IllegalBuffer);
|
||||
}
|
||||
|
||||
// receiveid不相同的情况
|
||||
if (!from_receiveid.equals(receiveid)) {
|
||||
throw new AesException(AesException.ValidateCorpidError);
|
||||
}
|
||||
return jsonContent;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 将企业微信回复用户的消息加密打包.
|
||||
* <ol>
|
||||
* <li>对要发送的消息进行AES-CBC加密</li>
|
||||
* <li>生成安全签名</li>
|
||||
* <li>将消息密文和安全签名打包成json格式</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param replyMsg 企业微信待回复用户的消息,json格式的字符串
|
||||
* @param timeStamp 时间戳,可以自己生成,也可以用URL参数的timestamp
|
||||
* @param nonce 随机串,可以自己生成,也可以用URL参数的nonce
|
||||
*
|
||||
* @return 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的json格式的字符串
|
||||
* @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
|
||||
*/
|
||||
public String EncryptMsg(String replyMsg, String timeStamp, String nonce) throws AesException {
|
||||
// 加密
|
||||
String encrypt = encrypt(getRandomStr(), replyMsg);
|
||||
|
||||
// 生成安全签名
|
||||
if (timeStamp == "") {
|
||||
timeStamp = Long.toString(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
String signature = SHA1.getSHA1(token, timeStamp, nonce, encrypt);
|
||||
|
||||
// System.out.println("发送给平台的签名是: " + signature[1].toString());
|
||||
// 生成发送的json
|
||||
String result = JsonParse.generate(encrypt, signature, timeStamp, nonce);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检验消息的真实性,并且获取解密后的明文.
|
||||
* <ol>
|
||||
* <li>利用收到的密文生成安全签名,进行签名验证</li>
|
||||
* <li>若验证通过,则提取json中的加密消息</li>
|
||||
* <li>对消息进行解密</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param msgSignature 签名串,对应URL参数的msg_signature
|
||||
* @param timeStamp 时间戳,对应URL参数的timestamp
|
||||
* @param nonce 随机串,对应URL参数的nonce
|
||||
* @param postData 密文,对应POST请求的数据
|
||||
*
|
||||
* @return 解密后的原文
|
||||
* @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
|
||||
*/
|
||||
public String DecryptMsg(String msgSignature, String timeStamp, String nonce, String postData)
|
||||
throws AesException {
|
||||
|
||||
// 密钥,公众账号的app secret
|
||||
// 提取密文
|
||||
Object[] encrypt = JsonParse.extract(postData);
|
||||
|
||||
// 验证安全签名
|
||||
String signature = SHA1.getSHA1(token, timeStamp, nonce, encrypt[1].toString());
|
||||
|
||||
// 和URL中的签名比较是否相等
|
||||
// System.out.println("第三方收到URL中的签名:" + msg_sign);
|
||||
// System.out.println("第三方校验签名:" + signature);
|
||||
if (!signature.equals(msgSignature)) {
|
||||
throw new AesException(AesException.ValidateSignatureError);
|
||||
}
|
||||
|
||||
// 解密
|
||||
String result = decrypt(encrypt[1].toString());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证URL
|
||||
* @param msgSignature 签名串,对应URL参数的msg_signature
|
||||
* @param timeStamp 时间戳,对应URL参数的timestamp
|
||||
* @param nonce 随机串,对应URL参数的nonce
|
||||
* @param echoStr 随机串,对应URL参数的echostr
|
||||
*
|
||||
* @return 解密之后的echostr
|
||||
* @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
|
||||
*/
|
||||
public String VerifyURL(String msgSignature, String timeStamp, String nonce, String echoStr)
|
||||
throws AesException {
|
||||
String signature = SHA1.getSHA1(token, timeStamp, nonce, echoStr);
|
||||
|
||||
if (!signature.equals(msgSignature)) {
|
||||
throw new AesException(AesException.ValidateSignatureError);
|
||||
}
|
||||
|
||||
String result = decrypt(echoStr);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package com.seeyon.apps.src_mainorganization;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import com.qq.weixin.mp.aes.WXBizJsonMsgCrypt;
|
||||
|
||||
public class Sample {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
String sToken = "rtfmfptSSM64EEedffcWcnvw6zukb9lG";
|
||||
String sCorpID = "ww69c49385cee3746c";
|
||||
String sEncodingAESKey = "cuDqCFGORfqjdPx3Yn9D11AAmijNh7Lpq2SdLQcvQkr";
|
||||
|
||||
WXBizJsonMsgCrypt wxcpt = new WXBizJsonMsgCrypt(sToken, sEncodingAESKey, sCorpID);
|
||||
/*
|
||||
------------使用示例一:验证回调URL---------------
|
||||
*企业开启回调模式时,企业微信会向验证url发送一个get请求
|
||||
假设点击验证时,企业收到类似请求:
|
||||
* GET /cgi-bin/wxpush?msg_signature=5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3×tamp=1409659589&nonce=263014780&echostr=P9nAzCzyDtyTWESHep1vC5X9xho%2FqYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp%2B4RPcs8TgAE7OaBO%2BFZXvnaqQ%3D%3D
|
||||
* HTTP/1.1 Host: qy.weixin.qq.com
|
||||
接收到该请求时,企业应 1.解析出Get请求的参数,包括消息体签名(msg_signature),时间戳(timestamp),随机数字串(nonce)以及企业微信推送过来的随机加密字符串(echostr),
|
||||
这一步注意作URL解码。
|
||||
2.验证消息体签名的正确性
|
||||
3. 解密出echostr原文,将原文当作Get请求的response,返回给企业微信
|
||||
第2,3步可以用企业微信提供的库函数VerifyURL来实现。
|
||||
|
||||
*/
|
||||
// 解析出url上的参数值如下:
|
||||
// String sVerifyMsgSig = HttpUtils.ParseUrl("msg_signature");
|
||||
String sVerifyMsgSig = "5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3";
|
||||
// String sVerifyTimeStamp = HttpUtils.ParseUrl("timestamp");
|
||||
String sVerifyTimeStamp = "1409659589";
|
||||
// String sVerifyNonce = HttpUtils.ParseUrl("nonce");
|
||||
String sVerifyNonce = "263014780";
|
||||
// String sVerifyEchoStr = HttpUtils.ParseUrl("echostr");
|
||||
String sVerifyEchoStr = "P9nAzCzyDtyTWESHep1vC5X9xho/qYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp+4RPcs8TgAE7OaBO+FZXvnaqQ==";
|
||||
String sEchoStr; //需要返回的明文
|
||||
try {
|
||||
sEchoStr = wxcpt.VerifyURL(sVerifyMsgSig, sVerifyTimeStamp,
|
||||
sVerifyNonce, sVerifyEchoStr);
|
||||
System.out.println("verifyurl echostr: " + sEchoStr);
|
||||
// 验证URL成功,将sEchoStr返回
|
||||
// HttpUtils.SetResponse(sEchoStr);
|
||||
} catch (Exception e) {
|
||||
//验证URL失败,错误原因请查看异常
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
/*
|
||||
------------使用示例二:对用户回复的消息解密---------------
|
||||
用户回复消息或者点击事件响应时,企业会收到回调消息,此消息是经过企业微信加密之后的密文以post形式发送给企业,密文格式请参考官方文档
|
||||
假设企业收到企业微信的回调消息如下:
|
||||
POST /cgi-bin/wxpush? msg_signature=477715d11cdb4164915debcba66cb864d751f3e6×tamp=1409659813&nonce=1372623149 HTTP/1.1
|
||||
Host: qy.weixin.qq.com
|
||||
Content-Length:
|
||||
Content-Type:text/json
|
||||
|
||||
{
|
||||
"tousername":"wx5823bf96d3bd56c7",
|
||||
"encrypt":"RypEvHKD8QQKFhvQ6QleEB4J58tiPdvo+rtK1I9qca6aM/wvqnLSV5zEPeusUiX5L5X/0lWfrf0QADHHhGd3QczcdCUpj911L3vg3W/sYYvuJTs3TUUkSUXxaccAS0qhxchrRYt66wiSpGLYL42aM6A8dTT+6k4aSknmPj48kzJs8qLjvd4Xgpue06DOdnLxAUHzM6+kDZ+HMZfJYuR+LtwGc2hgf5gsijff0ekUNXZiqATP7PF5mZxZ3Izoun1s4zG4LUMnvw2r+KqCKIw+3IQH03v+BCA9nMELNqbSf6tiWSrXJB3LAVGUcallcrw8V2t9EL4EhzJWrQUax5wLVMNS0+rUPA3k22Ncx4XXZS9o0MBH27Bo6BpNelZpS+/uh9KsNlY6bHCmJU9p8g7m3fVKn28H3KDYA5Pl/T8Z1ptDAVe0lXdQ2YoyyH2uyPIGHBZZIs2pDBS8R07+qN+E7Q==",
|
||||
"agentid":"218"
|
||||
}
|
||||
|
||||
企业收到post请求之后应该 1.解析出url上的参数,包括消息体签名(msg_signature),时间戳(timestamp)以及随机数字串(nonce)
|
||||
2.验证消息体签名的正确性。
|
||||
3.将post请求的数据进行json解析,并将"encrypt"标签的内容进行解密,解密出来的明文即是用户回复消息的明文,明文格式请参考官方文档
|
||||
第2,3步可以用企业微信提供的库函数DecryptMsg来实现。
|
||||
*/
|
||||
// String sReqMsgSig = HttpUtils.ParseUrl("msg_signature");
|
||||
String sReqMsgSig = "0623cbc5a8cbee5bcc137c70de99575366fc2af3";
|
||||
// String sReqTimeStamp = HttpUtils.ParseUrl("timestamp");
|
||||
String sReqTimeStamp = "1409659813";
|
||||
// String sReqNonce = HttpUtils.ParseUrl("nonce");
|
||||
String sReqNonce = "1372623149";
|
||||
// post请求的密文数据
|
||||
// sReqData = HttpUtils.PostData();
|
||||
String sReqData = "{\"tousername\":\"wx5823bf96d3bd56c7\",\"encrypt\":\"CZWs4CWRpI4VolQlvn4dlEC1alN2MUEY2VklGehgBVLBrlVF7SyT+SV+Toj43l4ayJ9UMGKphktKKmP7B2j/P1ey67XB8PBgS7Wr5/8+w/yWriZv3Vmoo/MH3/1HsIWZrPQ3N2mJrelStIfI2Y8kLKXA7EhfZgZX4o+ffdkZDM76SEl79Ib9mw7TGjZ9Aw/x/A2VjNbV1E8BtEbRxYYcQippYNw7hr8sFfa3nW1xLdxokt8QkRX83vK3DFP2F6TQFPL2Tu98UwhcUpPvdJBuu1/yiOQIScppV3eOuLWEsko=\",\"agentid\":\"218\"}";
|
||||
|
||||
try {
|
||||
String sMsg = wxcpt.DecryptMsg(sReqMsgSig, sReqTimeStamp, sReqNonce, sReqData);
|
||||
System.out.println("after decrypt msg: " + sMsg);
|
||||
// TODO: 解析出明文json标签的内容进行处理
|
||||
// For example:
|
||||
JSONObject json = new JSONObject(sMsg);
|
||||
String Content = json.getString("Content");
|
||||
|
||||
System.out.println("Content:" + Content);
|
||||
|
||||
} catch (Exception e) {
|
||||
// TODO
|
||||
// 解密失败,失败原因请查看异常
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
/*
|
||||
------------使用示例三:企业回复用户消息的加密---------------
|
||||
企业被动回复用户的消息也需要进行加密,并且拼接成密文格式的json串。
|
||||
假设企业需要回复用户的明文如下:
|
||||
{
|
||||
"ToUserName": "mycreate",
|
||||
"FromUserName":"wx5823bf96d3bd56c7",
|
||||
"CreateTime": 1348831860,
|
||||
"MsgType": "text",
|
||||
"Content": "this is a test",
|
||||
"MsgId": 1234567890123456,
|
||||
"AgentID": 128
|
||||
}
|
||||
|
||||
为了将此段明文回复给用户,企业应: 1.自己生成时间时间戳(timestamp),随机数字串(nonce)以便生成消息体签名,也可以直接用从企业微信的post url上解析出的对应值。
|
||||
2.将明文加密得到密文。 3.用密文,步骤1生成的timestamp,nonce和企业在企业微信设定的token生成消息体签名。 4.将密文,消息体签名,时间戳,随机数字串拼接成json格式的字符串,发送给企业。
|
||||
以上2,3,4步可以用企业微信提供的库函数EncryptMsg来实现。
|
||||
*/
|
||||
String sRespData = "{\"ToUserName\":\"wx5823bf96d3bd56c7\",\"FromUserName\":\"mycreate\",\"CreateTime\": 1409659813,\"MsgType\":\"text\",\"Content\":\"hello\",\"MsgId\":4561255354251345929,\"AgentID\": 218}";
|
||||
try{
|
||||
String sEncryptMsg = wxcpt.EncryptMsg(sRespData, sReqTimeStamp, sReqNonce);
|
||||
System.out.println("after encrypt sEncrytMsg: " + sEncryptMsg);
|
||||
// 加密成功
|
||||
// TODO:
|
||||
// HttpUtils.SetResponse(sEncryptMsg);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
// 加密失败
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -574,6 +574,7 @@ public class ThirdMemberBo {
|
||||
}
|
||||
|
||||
// member.put("isEnabled",fileMember.get("is_enable"));
|
||||
if(fileMember.get("is_enable")!=null){
|
||||
String memberState = fileMember.get("is_enable").toString();
|
||||
CtpEnumItem memberStateEnum = getEnumManagerNew().getEnumItem(Long.parseLong(memberState));
|
||||
if("在职,试用,待岗".contains(memberStateEnum.getShowvalue())){
|
||||
@@ -581,6 +582,10 @@ public class ThirdMemberBo {
|
||||
}else{
|
||||
member.put("isEnabled",false);
|
||||
}
|
||||
}else{
|
||||
member.put("isEnabled",false);
|
||||
}
|
||||
|
||||
member.put("telNumber",fileMember.get("telnumber"));
|
||||
member.put("loginName",fileMember.get("login_name"));
|
||||
member.put("birthday","");
|
||||
@@ -594,6 +599,7 @@ public class ThirdMemberBo {
|
||||
if(fileMember.get("gender")==null){
|
||||
member.put("gender","-1");
|
||||
}else{
|
||||
if(fileMember.get("gender")!=null){
|
||||
String genderId = fileMember.get("gender").toString();
|
||||
CtpEnumItem genderEnum = getEnumManagerNew().getEnumItem(Long.parseLong(genderId));
|
||||
if("0".equals(genderEnum.getEnumvalue())){
|
||||
@@ -603,6 +609,10 @@ public class ThirdMemberBo {
|
||||
}else{
|
||||
member.put("gender","-1");
|
||||
}
|
||||
}else{
|
||||
member.put("gender","-1");
|
||||
}
|
||||
|
||||
}
|
||||
// member.put("gender",fileMember.get("gender"));
|
||||
member.put("companyCode","");
|
||||
@@ -662,11 +672,12 @@ public class ThirdMemberBo {
|
||||
if(serviceAgentMember.get("yd_post_id")!=null){
|
||||
String ydPostId = serviceAgentMember.get("yd_post_id").toString();
|
||||
V3xOrgPost ydPost = orgManager.getPostById(Long.parseLong(ydPostId));
|
||||
member.put("orgPostId",ydPost.getId().toString());
|
||||
member.put("orgPostId",ydPost.getId());
|
||||
member.put("orgPostName",ydPost.getName());
|
||||
}
|
||||
member.put("orgLevelId","");
|
||||
member.put("orgLevelName","业代");
|
||||
if(serviceAgentMember.get("is_enabled")!=null){
|
||||
String memberState = serviceAgentMember.get("is_enabled").toString();
|
||||
CtpEnumItem memberStateEnum = getEnumManagerNew().getEnumItem(Long.parseLong(memberState));
|
||||
if("在职,试用,待岗".contains(memberStateEnum.getShowvalue())){
|
||||
@@ -674,6 +685,9 @@ public class ThirdMemberBo {
|
||||
}else{
|
||||
member.put("isEnabled",false);
|
||||
}
|
||||
}else{
|
||||
member.put("isEnabled",false);
|
||||
}
|
||||
member.put("telNumber",serviceAgentMember.get("telnember"));
|
||||
member.put("loginName",serviceAgentMember.get("telnember"));
|
||||
member.put("birthday","");
|
||||
@@ -687,6 +701,7 @@ public class ThirdMemberBo {
|
||||
if(serviceAgentMember.get("gender")==null){
|
||||
member.put("gender","-1");
|
||||
}else{
|
||||
if(serviceAgentMember.get("gender")!=null){
|
||||
String genderId = serviceAgentMember.get("gender").toString();
|
||||
CtpEnumItem genderEnum = getEnumManagerNew().getEnumItem(Long.parseLong(genderId));
|
||||
if("0".equals(genderEnum.getEnumvalue())){
|
||||
@@ -696,6 +711,10 @@ public class ThirdMemberBo {
|
||||
}else{
|
||||
member.put("gender","-1");
|
||||
}
|
||||
}else{
|
||||
member.put("gender","-1");
|
||||
}
|
||||
|
||||
}
|
||||
return member;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,13 @@ public enum SyncConstants {
|
||||
dingdingGetDept("https://oapi.dingtalk.com/topapi/v2/department/get", "查询钉钉部门URL"),
|
||||
dingdingLog("DDTBJL","钉钉同步记录"),
|
||||
dingdingLogloginName("demo","钉钉同步记录创建人"),
|
||||
|
||||
workweixinCorpid("ww69c49385cee3746c", "企业微信corpid"),
|
||||
workweixinCorpsecret("PGqFDqx5bvMtMwhoR3pfrVF_asaSQOPj92v9A0TnPxY", "企业微信Corpsecret"),
|
||||
workweixinTxlCorpsecret("mofPHLHcyOpEpfgXn9F0e1XVkuD0sILJCoa9UXqwGEw", "企业微信通讯录Corpsecret"),
|
||||
workweixinLog("QYWXTBJL","企业微信同步记录"),
|
||||
workweixinLogloginName("demo","企业微信同步记录创建人"),
|
||||
|
||||
qxxLog("QXXTBJL","企学星同步记录"),
|
||||
qxxUrl("http://10.0.1.92:7356","企学星URL"),
|
||||
qxxGetToken("/hklearn/openapi/v1/oauth/getToken?","企学星获取tokenURL"),
|
||||
|
||||
@@ -13,9 +13,6 @@ public interface ISyncDdDao {
|
||||
public List<Long> getMemberByMobile(String mobile);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 查询钉钉单位绑定信息
|
||||
public Map<String,Object> getAccountBinding(String orgAccountId);
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.seeyon.apps.src_mainorganization.dao;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface ISyncQywxDao {
|
||||
|
||||
// 新增钉钉单位绑定信息
|
||||
public int saveAccountBinding(String accountId,String accountName,String ddAccountId ,String ddAccountName);
|
||||
|
||||
public List<Long> getAccountsBinding();
|
||||
|
||||
public Map<String,Object> getAccountBindingByQywxAccount(String qywxAccountId);
|
||||
|
||||
public Map<String,Object> getDepartmentBindingByQywxDepartment(String qywxDepartmentId);
|
||||
|
||||
// 新增钉钉部门绑定信息
|
||||
public int saveDepartmentBinding(String departmentId,String departmentName ,String ddDepartmentId ,String ddDepartmentName);
|
||||
|
||||
public List<Long> getDepartmentsBinding();
|
||||
|
||||
public List<Long> getDepartmentsOABinding();
|
||||
|
||||
// 新增钉钉人员绑定信息
|
||||
public int saveMemberBinding(String memberId,String membertName ,String ddMemberId ,String ddMemberName,String telnumber);
|
||||
|
||||
public Map<String,Object> getMaxSynchronizationDate();
|
||||
|
||||
// 查询钉钉单位绑定信息
|
||||
public Map<String,Object> getAccountBinding(String orgAccountId);
|
||||
|
||||
// 根据单位ID修改钉钉单位绑定信息
|
||||
public int updateAccountBinding(String accountId,String accountName,String ddAccountId ,String ddAccountName);
|
||||
|
||||
// 根据部门ID删除钉钉部门绑定信息
|
||||
public int deleteDepartmentBinding(String departmentId);
|
||||
|
||||
// 查询钉钉部门绑定信息
|
||||
public Map<String,Object> getDepartmentBinding(String orgDepartmentId);
|
||||
|
||||
// 查询钉钉人员绑定信息
|
||||
public Map<String,Object> getMemberBinding(String orgMemberId);
|
||||
|
||||
// 根据人员ID删除钉钉人员绑定信息
|
||||
public int deleteMemberBinding(String memberId);
|
||||
}
|
||||
@@ -297,7 +297,6 @@ public class ArchiveDaoImpl implements IArchiveDao {
|
||||
List<Object> p = new ArrayList();
|
||||
agent.execute(sql.toString(), p);
|
||||
List<Map> list = agent.resultSetToList();
|
||||
|
||||
for(int i = 0; i < list.size(); ++i) {
|
||||
if (list.size() != 0) {
|
||||
Map<String,Object> map = (Map)list.get(i);
|
||||
|
||||
@@ -0,0 +1,387 @@
|
||||
package com.seeyon.apps.src_mainorganization.dao.impl;
|
||||
|
||||
import com.seeyon.apps.src_mainorganization.dao.ISyncQywxDao;
|
||||
import com.seeyon.ctp.common.exceptions.BusinessException;
|
||||
import com.seeyon.ctp.util.JDBCAgent;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class SyncQywxDaoImpl implements ISyncQywxDao {
|
||||
|
||||
private String saveAccountBinding = "INSERT INTO src_workweixin_unit (org_account_id, org_account_name, qywx_account_id, qywx_account_name) VALUES (?, ?, ?, ?)";
|
||||
@Override
|
||||
public int saveAccountBinding(String accountId, String accountName, String qywxAccountId, String qywxAccountName) {
|
||||
int i = 0 ;
|
||||
JDBCAgent agent = new JDBCAgent();
|
||||
try {
|
||||
StringBuilder sql = new StringBuilder(saveAccountBinding);
|
||||
List<Object> p = new ArrayList<Object>();
|
||||
p.add(accountId);
|
||||
p.add(accountName);
|
||||
p.add(qywxAccountId);
|
||||
p.add(qywxAccountName);
|
||||
i=agent.execute(sql.toString(), p);
|
||||
} catch (BusinessException e) {
|
||||
e.printStackTrace();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (agent != null) {
|
||||
agent.close();
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
private String getAccountsBinding = "select org_account_id,org_account_name,qywx_account_id,qywx_account_name from src_workweixin_unit ";
|
||||
@Override
|
||||
public List<Long> getAccountsBinding(){
|
||||
JDBCAgent agent = new JDBCAgent();
|
||||
List<Long> res = new ArrayList<>();
|
||||
try {
|
||||
StringBuilder sql = new StringBuilder(getAccountsBinding);
|
||||
List<Object> p = new ArrayList();
|
||||
agent.execute(sql.toString(), p);
|
||||
List<Map> list = agent.resultSetToList();
|
||||
for(int i = 0 ; i < list.size(); i++){
|
||||
res.add(Long.parseLong(list.get(i).get("qywx_account_id").toString()));
|
||||
}
|
||||
} catch (BusinessException var13) {
|
||||
var13.printStackTrace();
|
||||
} catch (SQLException var14) {
|
||||
var14.printStackTrace();
|
||||
} finally {
|
||||
if (agent != null) {
|
||||
agent.close();
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private String getAccountBindingByQywxAccount = "select org_account_id,org_account_name,qywx_account_id,qywx_account_name from src_workweixin_unit where qywx_account_id = ?";
|
||||
@Override
|
||||
public Map<String,Object> getAccountBindingByQywxAccount(String qywxAccountId){
|
||||
JDBCAgent agent = new JDBCAgent();
|
||||
Map<String,Object> res = new HashMap<>();
|
||||
try {
|
||||
StringBuilder sql = new StringBuilder(getAccountBindingByQywxAccount);
|
||||
List<Object> p = new ArrayList();
|
||||
p.add(qywxAccountId);
|
||||
agent.execute(sql.toString(), p);
|
||||
List<Map> list = agent.resultSetToList();
|
||||
if(list.size()>0){
|
||||
res = list.get(0);
|
||||
}
|
||||
} catch (BusinessException var13) {
|
||||
var13.printStackTrace();
|
||||
} catch (SQLException var14) {
|
||||
var14.printStackTrace();
|
||||
} finally {
|
||||
if (agent != null) {
|
||||
agent.close();
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private String getDepartmentBindingByQywxDepartment = "select org_department_id,org_department_name,qywx_department_id,qywx_department_name from src_workweixin_dept where qywx_department_id = ?";
|
||||
@Override
|
||||
public Map<String,Object> getDepartmentBindingByQywxDepartment(String qywxDepartmentId){
|
||||
JDBCAgent agent = new JDBCAgent();
|
||||
Map<String,Object> res = new HashMap<>();
|
||||
try {
|
||||
StringBuilder sql = new StringBuilder(getDepartmentBindingByQywxDepartment);
|
||||
List<Object> p = new ArrayList();
|
||||
p.add(qywxDepartmentId);
|
||||
agent.execute(sql.toString(), p);
|
||||
List<Map> list = agent.resultSetToList();
|
||||
if(list.size()>0){
|
||||
res = list.get(0);
|
||||
}
|
||||
} catch (BusinessException var13) {
|
||||
var13.printStackTrace();
|
||||
} catch (SQLException var14) {
|
||||
var14.printStackTrace();
|
||||
} finally {
|
||||
if (agent != null) {
|
||||
agent.close();
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private String saveDepartmentBinding = "INSERT INTO src_workweixin_dept (org_department_id, org_department_name, qywx_department_id, qywx_department_name) VALUES (?, ?, ?, ?)";
|
||||
@Override
|
||||
public int saveDepartmentBinding(String departmentId, String departmentName, String qywxDepartmentId, String qywxDepartmentName) {
|
||||
int i = 0 ;
|
||||
JDBCAgent agent = new JDBCAgent();
|
||||
try {
|
||||
StringBuilder sql = new StringBuilder(saveDepartmentBinding);
|
||||
List<Object> p = new ArrayList<Object>();
|
||||
p.add(departmentId);
|
||||
p.add(departmentName);
|
||||
p.add(qywxDepartmentId);
|
||||
p.add(qywxDepartmentName);
|
||||
i=agent.execute(sql.toString(), p);
|
||||
} catch (BusinessException e) {
|
||||
e.printStackTrace();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (agent != null) {
|
||||
agent.close();
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
private String getDepartmentsBinding = "select org_department_id,org_department_name,qywx_department_id,qywx_department_name from src_workweixin_dept ";
|
||||
@Override
|
||||
public List<Long> getDepartmentsBinding(){
|
||||
JDBCAgent agent = new JDBCAgent();
|
||||
List<Long> res = new ArrayList<>();
|
||||
try {
|
||||
StringBuilder sql = new StringBuilder(getDepartmentsBinding);
|
||||
List<Object> p = new ArrayList();
|
||||
agent.execute(sql.toString(), p);
|
||||
List<Map> list = agent.resultSetToList();
|
||||
for(int i = 0 ; i < list.size(); i++){
|
||||
res.add(Long.parseLong(list.get(i).get("qywx_department_id").toString()));
|
||||
}
|
||||
} catch (BusinessException var13) {
|
||||
var13.printStackTrace();
|
||||
} catch (SQLException var14) {
|
||||
var14.printStackTrace();
|
||||
} finally {
|
||||
if (agent != null) {
|
||||
agent.close();
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private String getDepartmentsOABinding = "select org_department_id,org_department_name,qywx_department_id,qywx_department_name from src_workweixin_dept ";
|
||||
@Override
|
||||
public List<Long> getDepartmentsOABinding(){
|
||||
JDBCAgent agent = new JDBCAgent();
|
||||
List<Long> res = new ArrayList<>();
|
||||
try {
|
||||
StringBuilder sql = new StringBuilder(getDepartmentsOABinding);
|
||||
List<Object> p = new ArrayList();
|
||||
agent.execute(sql.toString(), p);
|
||||
List<Map> list = agent.resultSetToList();
|
||||
for(int i = 0 ; i < list.size(); i++){
|
||||
res.add(Long.parseLong(list.get(i).get("org_department_id").toString()));
|
||||
}
|
||||
} catch (BusinessException var13) {
|
||||
var13.printStackTrace();
|
||||
} catch (SQLException var14) {
|
||||
var14.printStackTrace();
|
||||
} finally {
|
||||
if (agent != null) {
|
||||
agent.close();
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private String saveMemberBinding = "INSERT INTO src_workweixin_member (org_member_id, org_member_name, qywx_member_id, qywx_member_name,qywx_member_telnumber) VALUES (?, ?, ?, ?, ?)";
|
||||
@Override
|
||||
public int saveMemberBinding(String memberId, String membertName, String qywxMemberId, String qywxMemberName,String telnumber) {
|
||||
int i = 0 ;
|
||||
JDBCAgent agent = new JDBCAgent();
|
||||
try {
|
||||
StringBuilder sql = new StringBuilder(saveMemberBinding);
|
||||
List<Object> p = new ArrayList<Object>();
|
||||
p.add(memberId);
|
||||
p.add(membertName);
|
||||
p.add(qywxMemberId);
|
||||
p.add(qywxMemberName);
|
||||
p.add(telnumber);
|
||||
i=agent.execute(sql.toString(), p);
|
||||
} catch (BusinessException e) {
|
||||
e.printStackTrace();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (agent != null) {
|
||||
agent.close();
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
private String getMaxSynchronizationDate = "select max(create_date) as max_create_date from cst_quartz_log where PARENTID = (select id from CST_QUARTZ where bean_id = 'organizationWorkweixinQuartz') and message not like '%任务执行异常%' and message not like '%上次的任务还在运行中%'";
|
||||
public Map<String,Object> getMaxSynchronizationDate (){
|
||||
JDBCAgent agent = new JDBCAgent();
|
||||
Map<String,Object> res = new HashMap<>();
|
||||
try {
|
||||
StringBuilder sql = new StringBuilder(getMaxSynchronizationDate);
|
||||
List<Object> p = new ArrayList();
|
||||
agent.execute(sql.toString(), p);
|
||||
List<Map> list = agent.resultSetToList();
|
||||
if(list.size()>0){
|
||||
res = list.get(0);
|
||||
}
|
||||
} catch (BusinessException var13) {
|
||||
var13.printStackTrace();
|
||||
} catch (SQLException var14) {
|
||||
var14.printStackTrace();
|
||||
} finally {
|
||||
if (agent != null) {
|
||||
agent.close();
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private String getAccountBinding = "select org_account_id,org_account_name,qywx_account_id,qywx_account_name from src_workweixin_unit where org_account_id = ?";
|
||||
@Override
|
||||
public Map<String,Object> getAccountBinding(String orgAccountId) {
|
||||
JDBCAgent agent = new JDBCAgent();
|
||||
Map<String,Object> res = new HashMap<>();
|
||||
try {
|
||||
StringBuilder sql = new StringBuilder(getAccountBinding);
|
||||
List<Object> p = new ArrayList();
|
||||
p.add(orgAccountId);
|
||||
agent.execute(sql.toString(), p);
|
||||
List<Map> list = agent.resultSetToList();
|
||||
if(list.size()>0){
|
||||
res = list.get(0);
|
||||
}
|
||||
} catch (BusinessException var13) {
|
||||
var13.printStackTrace();
|
||||
} catch (SQLException var14) {
|
||||
var14.printStackTrace();
|
||||
} finally {
|
||||
if (agent != null) {
|
||||
agent.close();
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private String updateAccountBinding = "update src_workweixin_unit set qywx_account_id = ? , qywx_account_name = ? where org_account_id = ?";
|
||||
@Override
|
||||
public int updateAccountBinding(String accountId, String accountName, String qywxAccountId, String qywxAccountName) {
|
||||
int i = 0 ;
|
||||
JDBCAgent agent = new JDBCAgent();
|
||||
try {
|
||||
StringBuilder sql = new StringBuilder(updateAccountBinding);
|
||||
List<Object> p = new ArrayList<Object>();
|
||||
p.add(qywxAccountId);
|
||||
p.add(qywxAccountName);
|
||||
p.add(accountId);
|
||||
i=agent.execute(sql.toString(), p);
|
||||
} catch (BusinessException e) {
|
||||
e.printStackTrace();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (agent != null) {
|
||||
agent.close();
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
private String deleteDepartmentBinding = "delete from src_workweixin_dept where org_department_id = ?";
|
||||
@Override
|
||||
public int deleteDepartmentBinding(String departmentId){
|
||||
int i = 0 ;
|
||||
JDBCAgent agent = new JDBCAgent();
|
||||
try {
|
||||
StringBuilder sql = new StringBuilder(deleteDepartmentBinding);
|
||||
List<Object> p = new ArrayList<Object>();
|
||||
p.add(departmentId);
|
||||
i=agent.execute(sql.toString(), p);
|
||||
} catch (BusinessException e) {
|
||||
e.printStackTrace();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (agent != null) {
|
||||
agent.close();
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
private String getDepartmentBinding = "select org_department_id,org_department_name,qywx_department_id,qywx_department_name from src_workweixin_dept where org_department_id = ?";
|
||||
@Override
|
||||
public Map<String, Object> getDepartmentBinding(String orgDepartmentId) {
|
||||
JDBCAgent agent = new JDBCAgent();
|
||||
Map<String,Object> res = new HashMap<>();
|
||||
try {
|
||||
StringBuilder sql = new StringBuilder(getDepartmentBinding);
|
||||
List<Object> p = new ArrayList();
|
||||
p.add(orgDepartmentId);
|
||||
agent.execute(sql.toString(), p);
|
||||
List<Map> list = agent.resultSetToList();
|
||||
if(list.size()>0){
|
||||
res = list.get(0);
|
||||
}
|
||||
} catch (BusinessException var13) {
|
||||
var13.printStackTrace();
|
||||
} catch (SQLException var14) {
|
||||
var14.printStackTrace();
|
||||
} finally {
|
||||
if (agent != null) {
|
||||
agent.close();
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private String getMemberBinding = "select org_member_id,org_member_name,qywx_member_id,qywx_member_name,qywx_member_telnumber from src_workweixin_member where org_member_id = ? ";
|
||||
@Override
|
||||
public Map<String, Object> getMemberBinding(String orgMemberId) {
|
||||
JDBCAgent agent = new JDBCAgent();
|
||||
Map<String,Object> res = new HashMap<>();
|
||||
try {
|
||||
StringBuilder sql = new StringBuilder(getMemberBinding);
|
||||
List<Object> p = new ArrayList();
|
||||
p.add(orgMemberId);
|
||||
agent.execute(sql.toString(), p);
|
||||
List<Map> list = agent.resultSetToList();
|
||||
if(list.size()>0){
|
||||
res = list.get(0);
|
||||
}
|
||||
} catch (BusinessException var13) {
|
||||
var13.printStackTrace();
|
||||
} catch (SQLException var14) {
|
||||
var14.printStackTrace();
|
||||
} finally {
|
||||
if (agent != null) {
|
||||
agent.close();
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private String deleteMemberBinding = "delete from src_workweixin_member where org_member_id = ?";
|
||||
@Override
|
||||
public int deleteMemberBinding(String memberId){
|
||||
int i = 0 ;
|
||||
JDBCAgent agent = new JDBCAgent();
|
||||
try {
|
||||
StringBuilder sql = new StringBuilder(deleteMemberBinding);
|
||||
List<Object> p = new ArrayList<Object>();
|
||||
p.add(memberId);
|
||||
i=agent.execute(sql.toString(), p);
|
||||
} catch (BusinessException e) {
|
||||
e.printStackTrace();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (agent != null) {
|
||||
agent.close();
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package com.seeyon.apps.src_mainorganization.quartz;
|
||||
|
||||
import cn.hutool.log.Log;
|
||||
import com.seeyon.apps.common.config.ICstConfigApi;
|
||||
import com.seeyon.apps.common.plugin.vo.ConfigVo;
|
||||
import com.seeyon.apps.ext.quartz.AbstractQuartzTask;
|
||||
import com.seeyon.apps.src_mainorganization.constans.SyncConstants;
|
||||
import com.seeyon.apps.src_mainorganization.dao.ISyncQywxDao;
|
||||
import com.seeyon.apps.src_mainorganization.server.InitializeWorkweixinSyncServer;
|
||||
import com.seeyon.apps.src_mainorganization.server.OrganizationOrgWorkweixinSyncServer;
|
||||
import com.seeyon.ctp.common.AppContext;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
public class OrganizationWorkweixinQuartz extends AbstractQuartzTask {
|
||||
|
||||
private static Log log = Log.get(OrganizationQuartz.class);
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "企业微信通讯录同步定时任务";
|
||||
}
|
||||
|
||||
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
@Inject
|
||||
private ISyncQywxDao syncQywxDao;
|
||||
@Inject
|
||||
private OrganizationOrgWorkweixinSyncServer organizationOrgWorkweixinSyncServer;
|
||||
@Inject
|
||||
private InitializeWorkweixinSyncServer initializeWorkweixinSyncServer;
|
||||
|
||||
protected ICstConfigApi cstConfigApi = (ICstConfigApi) AppContext.getBean("cstConfigApi");
|
||||
public String getPluginId() {return SyncConstants.getPluginId();}
|
||||
public ConfigVo getOrganizationOrgConfig() {return cstConfigApi.getConfig(getPluginId());}
|
||||
|
||||
@Override
|
||||
public String taskRun(String s) throws Exception{
|
||||
if("initialize".equals(s)){
|
||||
log.info("进行企业微信初始化操作");
|
||||
Map<String,String> errorMapAccount = initializeWorkweixinSyncServer.initializeWorkweixinAccount();
|
||||
log.info("单位添加完成,部门添加开始");
|
||||
Map<String,String> errorMapDepartment = initializeWorkweixinSyncServer.initializeWorkweixinDepartment();
|
||||
log.info("部门添加完成,人员添加开始");
|
||||
Map<String,String> errorMapMember = initializeWorkweixinSyncServer.initializeWorkweixinMember();
|
||||
log.info("人员添加完成");
|
||||
return "初始化完成";
|
||||
}
|
||||
log.info("进入企业微信通讯录同步定时任务,参数为:"+s);
|
||||
ConfigVo configVo = this.cstConfigApi.getConfig(this.getPluginId());
|
||||
String res = "";
|
||||
// 查询上一次同步时间
|
||||
Map<String,Object> maxSynchronizationDate = syncQywxDao.getMaxSynchronizationDate();
|
||||
Date startDate = (Date) maxSynchronizationDate.get("max_create_date");
|
||||
// 如果当前调用定时任务时未进行同步,则直接查询全量数据进行同步
|
||||
String isServiceBroker = configVo.getParamVal(SyncConstants.isServiceBroker.name());
|
||||
if(startDate==null){
|
||||
// 进行企业微信单位同步操作
|
||||
// String errorAccountValue = organizationOrgWorkweixinSyncServer.workweixinSyncAllAccounts();
|
||||
// 进行企业微信部门同步操作
|
||||
String errorDepartmentValue = organizationOrgWorkweixinSyncServer.workweixinSyncAllDepartments();
|
||||
// 进行企业微信人员同步操作
|
||||
log.info("进行集团人员数据同步");
|
||||
String errorMemberValue = organizationOrgWorkweixinSyncServer.workweixinSyncAllMembers();
|
||||
// 进行企业微信业代人员同步
|
||||
String errorServiceBroker = "";
|
||||
if("是".equals(isServiceBroker)||"Y".equals(isServiceBroker)||"y".equals(isServiceBroker)){
|
||||
log.info("进行业代人员数据同步");
|
||||
errorServiceBroker = organizationOrgWorkweixinSyncServer.workweixinSyncAllServiceBrokers();
|
||||
}
|
||||
// 进行客户单位下人员同步
|
||||
log.info("进行客户人员数据同步");
|
||||
String errorClientMemberValue = organizationOrgWorkweixinSyncServer.workweixinSyncAllClientMembers();
|
||||
res = errorDepartmentValue+","+errorMemberValue+","+errorServiceBroker+","+errorClientMemberValue;
|
||||
}else{
|
||||
// 通过时间查询最新的修改数据并且同步到企业微信。
|
||||
// String accountRes = organizationOrgWorkweixinSyncServer.workweixinSyncBatchAccount(simpleDateFormat.format(startDate));
|
||||
String departmrntRes = organizationOrgWorkweixinSyncServer.workweixinSyncBatchDepartment(simpleDateFormat.format(startDate));
|
||||
log.info("进行增量集团人员数据同步");
|
||||
String memberRes = organizationOrgWorkweixinSyncServer.workweixinSyncBatchMember(simpleDateFormat.format(startDate));
|
||||
String serviceBrokerRes = "";
|
||||
if("是".equals(isServiceBroker)||"Y".equals(isServiceBroker)||"y".equals(isServiceBroker)){
|
||||
log.info("进行增量业代人员数据同步");
|
||||
serviceBrokerRes = organizationOrgWorkweixinSyncServer.workweixinSyncServiceBroker(simpleDateFormat.format(startDate));
|
||||
}
|
||||
log.info("进行增量客户人员数据同步");
|
||||
String clientMemberRes = organizationOrgWorkweixinSyncServer.workweixinSyncBatchClientMember(simpleDateFormat.format(startDate));
|
||||
res = departmrntRes+","+memberRes+","+serviceBrokerRes+","+clientMemberRes;
|
||||
}
|
||||
int i = 0 ;
|
||||
for(String str :res.split(",")){
|
||||
for(String str1s : str.split(";")){
|
||||
log.info(str1s);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return "同步完成,失败"+i;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -216,9 +216,4 @@ public class InitializeDingdingSyncServer {
|
||||
return errorMap;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
package com.seeyon.apps.src_mainorganization.server;
|
||||
|
||||
import cn.hutool.log.Log;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.seeyon.apps.common.config.ICstConfigApi;
|
||||
import com.seeyon.apps.common.plugin.vo.ConfigVo;
|
||||
import com.seeyon.apps.src_mainorganization.constans.SyncConstants;
|
||||
import com.seeyon.apps.src_mainorganization.dao.ISyncQywxDao;
|
||||
import com.seeyon.apps.src_mainorganization.util.WorkweixinProtUtil;
|
||||
import com.seeyon.ctp.common.exceptions.BusinessException;
|
||||
import com.seeyon.ctp.organization.bo.V3xOrgAccount;
|
||||
import com.seeyon.ctp.organization.bo.V3xOrgDepartment;
|
||||
import com.seeyon.ctp.organization.bo.V3xOrgMember;
|
||||
import com.seeyon.ctp.organization.manager.OrgManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class InitializeWorkweixinSyncServer {
|
||||
|
||||
private static Log log = Log.get(InitializeDingdingSyncServer.class);
|
||||
|
||||
@Inject
|
||||
private ICstConfigApi cstConfigApi;
|
||||
@Inject
|
||||
private OrgManager orgManager;
|
||||
@Inject
|
||||
private ISyncQywxDao syncQywxDao;
|
||||
|
||||
public String getPluginId() {
|
||||
return SyncConstants.getPluginId();
|
||||
}
|
||||
|
||||
// 进行钉钉单位信息关系绑定初始化
|
||||
public Map<String,String> initializeWorkweixinAccount() throws BusinessException {
|
||||
Map<String,String> errorMap = new HashMap<>();
|
||||
ConfigVo configVo = this.cstConfigApi.getConfig(this.getPluginId());
|
||||
String workweixinCorpid = configVo.getParamVal(SyncConstants.workweixinCorpid.name());
|
||||
String workweixinCorpsecret = configVo.getParamVal(SyncConstants.workweixinCorpsecret.name());
|
||||
// String accessToken = WorkweixinProtUtil.getAccessToken(workweixinCorpid,workweixinCorpsecret);
|
||||
// 调用企业微信接口获取企业微信中所有部门信息
|
||||
String departmentAllIds = WorkweixinProtUtil.departmentListsubid(workweixinCorpsecret,workweixinCorpid,1l);
|
||||
JSONObject departmentAllIdsJSON = JSONObject.parseObject(departmentAllIds);
|
||||
String errcode = departmentAllIdsJSON.getString("errcode");
|
||||
JSONArray workweixinDepartments = departmentAllIdsJSON.getJSONArray("department_id");
|
||||
if("0".equals(errcode)){
|
||||
// 设置跟部门ID
|
||||
List<Long> superDepartments = new ArrayList<>();
|
||||
superDepartments.add(1l);
|
||||
// 通过跟部门ID查询部门下级信息
|
||||
while(superDepartments.size()!=0){
|
||||
List<Long> departmentIds = new ArrayList<>();
|
||||
departmentIds.addAll(superDepartments);
|
||||
superDepartments.clear();
|
||||
for (Long superDepartmentId: departmentIds) {
|
||||
// 调用企业微信接口查询下级部门信息
|
||||
List<Long> workweixinDepartmentIds = new ArrayList<>();
|
||||
for(int i = 0 ; i < workweixinDepartments.size();i++){
|
||||
JSONObject workweixinDepartmentId = workweixinDepartments.getJSONObject(i);
|
||||
String superWorkweixinDepartmentId = workweixinDepartmentId.getString("parentid");
|
||||
if(superWorkweixinDepartmentId.equals(superDepartmentId.toString())){
|
||||
workweixinDepartmentIds.add(workweixinDepartmentId.getLongValue("id"));
|
||||
}
|
||||
}
|
||||
for(long workweixinDepartmentId : workweixinDepartmentIds){
|
||||
// 根据ID查询企业微信中的部门信息
|
||||
String departmentData = WorkweixinProtUtil.departmentGet(workweixinCorpsecret,workweixinCorpid,workweixinDepartmentId);
|
||||
JSONObject departmentDataJSON = JSONObject.parseObject(departmentData);
|
||||
String departmentDataJSONCode = departmentDataJSON.getString("errcode");
|
||||
if("0".equals(departmentDataJSONCode)){
|
||||
JSONObject departmentJSON = departmentDataJSON.getJSONObject("department");
|
||||
V3xOrgAccount v3xOrgAccount;
|
||||
if(superDepartmentId==1L && "稻花香集团".equals(departmentJSON.getString("name"))){
|
||||
//// 一级部门存在特殊情况,当前是一级部门时名称为稻花香集团绑定主数据平台一级单位
|
||||
v3xOrgAccount = orgManager.getAccountByName("主数据平台");
|
||||
}else{
|
||||
//// 判断当前获取的钉钉部门是否为单位
|
||||
v3xOrgAccount = orgManager.getAccountByName(departmentJSON.getString("name"));
|
||||
}
|
||||
if(v3xOrgAccount!=null){
|
||||
// 添加到临时储存,为下一个循环的请求参数
|
||||
superDepartments.add(workweixinDepartmentId);
|
||||
// 当前查询单位在钉钉中存在,添加绑定关系表
|
||||
int saveAccountBindingnum = syncQywxDao.saveAccountBinding(v3xOrgAccount.getId()+"",v3xOrgAccount.getName(),departmentJSON.getString("id"),departmentJSON.getString("name"));
|
||||
if(saveAccountBindingnum>0){
|
||||
// log.info("添加成功"+v3xOrgAccount.getName());
|
||||
}else{
|
||||
log.info("添加单位失败"+v3xOrgAccount.getId()+v3xOrgAccount.getName());
|
||||
}
|
||||
}
|
||||
}else{
|
||||
log.info("企业微信单位参数查询失败"+workweixinDepartmentId+departmentDataJSON);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return errorMap;
|
||||
}
|
||||
|
||||
// 进行钉钉部门信息关系绑定初始化
|
||||
public Map<String,String> initializeWorkweixinDepartment() throws BusinessException {
|
||||
Map<String,String> errorMap = new HashMap<>();
|
||||
ConfigVo configVo = this.cstConfigApi.getConfig(this.getPluginId());
|
||||
String workweixinCorpid = configVo.getParamVal(SyncConstants.workweixinCorpid.name());
|
||||
String workweixinCorpsecret = configVo.getParamVal(SyncConstants.workweixinCorpsecret.name());
|
||||
// 根据钉钉中的层级信息与OA中的单位信息添加初始化绑定
|
||||
// 查询单位列表
|
||||
List<Long> superDepartments = syncQywxDao.getAccountsBinding();
|
||||
while(superDepartments.size()!=0){
|
||||
// 创建临时储存列表
|
||||
List<Long> departmentIds = new ArrayList<>();
|
||||
// 临时储存复制
|
||||
departmentIds.addAll(superDepartments);
|
||||
// 清空上级储存内容
|
||||
superDepartments.clear();
|
||||
for (long superDepartmentId: departmentIds) {
|
||||
// 调用钉钉接口查询下级部门信息
|
||||
String subordinateDepartment = WorkweixinProtUtil.departmentListsubid(workweixinCorpid,workweixinCorpsecret,superDepartmentId);
|
||||
JSONObject subordinateDepartmentJson = JSONObject.parseObject(subordinateDepartment);
|
||||
if("0".equals(subordinateDepartmentJson.getString("errcode"))){
|
||||
JSONArray subordinateDepartmentIds = subordinateDepartmentJson.getJSONArray("department_id");
|
||||
List<Long> subordinateDepartmentIdList = new ArrayList<>();
|
||||
// 根据上级部门信息,筛选子集部门信息
|
||||
for(int i = 0 ; i <subordinateDepartmentIds.size();i++){
|
||||
JSONObject subordinateDepartmentId = subordinateDepartmentIds.getJSONObject(i);
|
||||
String subordinateDepartmentParentId = subordinateDepartmentId.getString("parentid");
|
||||
if(subordinateDepartmentParentId.equals(superDepartmentId+"")){
|
||||
subordinateDepartmentIdList.add(subordinateDepartmentId.getLongValue("id"));
|
||||
}
|
||||
}
|
||||
// 遍历当前子部门信息,保存绑定关系。
|
||||
for(int i = 0 ; i < subordinateDepartmentIdList.size() ; i++){
|
||||
long subordinateDepartmentId = subordinateDepartmentIdList.get(i);
|
||||
String departmentGet = WorkweixinProtUtil.departmentGet(workweixinCorpid,workweixinCorpsecret,subordinateDepartmentId);
|
||||
JSONObject departmentGetJson = JSONObject.parseObject(departmentGet);
|
||||
if("0".equals(departmentGetJson.getString("errcode"))){
|
||||
JSONObject departmentJSON = departmentGetJson.getJSONObject("department");
|
||||
Map<String,Object> superAccountMap = syncQywxDao.getAccountBindingByQywxAccount(superDepartmentId+"");
|
||||
Map<String,Object> superDepartmentMap = syncQywxDao.getDepartmentBindingByQywxDepartment(superDepartmentId+"");
|
||||
// 判断当前获取的企业微信部门是否为指定部门
|
||||
List<V3xOrgDepartment> v3xOrgDepartments = new ArrayList<>();
|
||||
if(superAccountMap.size()>0){
|
||||
v3xOrgDepartments = orgManager.getChildDeptsByAccountId(Long.parseLong(superAccountMap.get("org_account_id").toString()),true);
|
||||
}if(superDepartmentMap.size()>0){
|
||||
v3xOrgDepartments = orgManager.getChildDepartments(Long.parseLong(superDepartmentMap.get("org_department_id").toString()),true);
|
||||
}
|
||||
// 循环匹配子集部门信息
|
||||
for(int n = 0 ; n <v3xOrgDepartments.size() ; n++){
|
||||
V3xOrgDepartment department = v3xOrgDepartments.get(n);
|
||||
if(department.getName().equals(departmentJSON.getString("name"))){
|
||||
// 添加到临时储存,为下一个循环的请求参数
|
||||
superDepartments.add(subordinateDepartmentId);
|
||||
// 当前查询单位在钉钉中存在,添加绑定关系表
|
||||
int saveAccountBindingnum = syncQywxDao.saveDepartmentBinding(department.getId()+"",department.getName(),departmentJSON.getString("id"),departmentJSON.getString("name"));
|
||||
if(saveAccountBindingnum>0){
|
||||
// log.info("添加成功"+saveAccountBindingnum);
|
||||
}else{
|
||||
log.info("添加部门失败"+department.getId()+department.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
log.info("企业微信部门参数查询失败"+subordinateDepartmentId+departmentGetJson);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return errorMap;
|
||||
}
|
||||
|
||||
// 进行钉钉人员信息关系绑定初始化
|
||||
public Map<String,String> initializeWorkweixinMember() throws BusinessException {
|
||||
Map<String,String> errorMap = new HashMap<>();
|
||||
ConfigVo configVo = this.cstConfigApi.getConfig(this.getPluginId());
|
||||
String workweixinCorpid = configVo.getParamVal(SyncConstants.workweixinCorpid.name());
|
||||
String workweixinCorpsecret = configVo.getParamVal(SyncConstants.workweixinCorpsecret.name());
|
||||
// 根据部门绑定信息查询部门下人员信息,并添加绑定信息
|
||||
// 查询绑定关系中的所有的部门
|
||||
List<Long> superOADepartments = syncQywxDao.getDepartmentsOABinding();
|
||||
// 遍历当前部门,在钉钉中查询各部门下的人员
|
||||
for (Long superOADepartmentId: superOADepartments) {
|
||||
V3xOrgDepartment v3xOrgDepartment = orgManager.getDepartmentById(superOADepartmentId);
|
||||
List<V3xOrgMember> v3xOrgMembers = orgManager.getMembersByDepartment(v3xOrgDepartment.getId(),true);
|
||||
for(V3xOrgMember v3xOrgMember : v3xOrgMembers){
|
||||
String memberTelNumber = v3xOrgMember.getTelNumber();
|
||||
// 通过手机号查询企业微信人员ID
|
||||
String workweixinUserIdPost = WorkweixinProtUtil.getUserIdByMobile(workweixinCorpid,workweixinCorpsecret,memberTelNumber);
|
||||
JSONObject workweixinUserIdJson = JSONObject.parseObject(workweixinUserIdPost);
|
||||
String workweixinUserIdJsonErrcode = workweixinUserIdJson.getString("errcode");
|
||||
if(!"0".equals(workweixinUserIdJsonErrcode)){
|
||||
log.info(memberTelNumber+"手机号企业微信中没有查询到具体信息");
|
||||
continue;
|
||||
}
|
||||
String workweixinUserId = workweixinUserIdJson.getString("userid");
|
||||
int saveMemberBindingNum = syncQywxDao.saveMemberBinding(v3xOrgMember.getId().toString(),v3xOrgMember.getName(),workweixinUserId,v3xOrgMember.getName(),memberTelNumber);
|
||||
if(saveMemberBindingNum>0){
|
||||
// log.info(userMobile+userName+"添加成功"+saveMemberBindingNum);
|
||||
}else{
|
||||
log.info(memberTelNumber+v3xOrgMember.getName()+"添加人员绑定关系失败"+saveMemberBindingNum);
|
||||
}
|
||||
}
|
||||
}
|
||||
return errorMap;
|
||||
}
|
||||
}
|
||||
@@ -915,6 +915,15 @@ public class OrganizationOrgDingdingSyncServer {
|
||||
for(int i = 0 ; i < list.size() ; i++){
|
||||
// 遍历所有人员信息
|
||||
Map<String,Object> serviceBroker = list.get(i);
|
||||
String memberState = serviceBroker.get("state")!=null?serviceBroker.get("state").toString():"";
|
||||
boolean isenum = false;
|
||||
if(StringUtil.isNotEmpty(memberState)){
|
||||
CtpEnumItem memberStateItem = enumManagerNew.getCtpEnumItem(Long.parseLong(memberState));
|
||||
if(memberStateItem.getShowvalue().equals("在职")||memberStateItem.getShowvalue().equals("试用")||memberStateItem.getShowvalue().equals("待岗")){
|
||||
isenum = true;
|
||||
}
|
||||
}
|
||||
if(isenum){
|
||||
long departmentId;
|
||||
if(serviceBroker.get("dept_id")!=null){
|
||||
departmentId = Long.parseLong(serviceBroker.get("dept_id").toString());
|
||||
@@ -956,6 +965,9 @@ public class OrganizationOrgDingdingSyncServer {
|
||||
}else{
|
||||
log.info("当前人员档案中所属部门为空跳过处理"+serviceBroker.get("name"));
|
||||
}
|
||||
}else{
|
||||
errorMap.putAll(serviceBrokerSynchronization(serviceBroker,false,accessToken));
|
||||
}
|
||||
}
|
||||
|
||||
return errorMap;
|
||||
@@ -1206,7 +1218,7 @@ public class OrganizationOrgDingdingSyncServer {
|
||||
List<String> postBlackList = archiveDao.getPostBlackList();
|
||||
String postBlackString = "";
|
||||
for (String postBlack:postBlackList) {
|
||||
postBlackString = postBlackString+ postBlack+ ",";
|
||||
postBlackString = postBlackString+postBlack+ ",";
|
||||
}
|
||||
List<String> postBlackStringList = Arrays.asList(postBlackString.split(","));
|
||||
// 查询人员白名单
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,70 @@
|
||||
package com.seeyon.apps.src_mainorganization.servlet;
|
||||
|
||||
import com.qq.weixin.mp.aes.AesException;
|
||||
import com.qq.weixin.mp.aes.WXBizJsonMsgCrypt;
|
||||
import com.seeyon.apps.common.config.ICstConfigApi;
|
||||
import com.seeyon.apps.common.plugin.vo.ConfigVo;
|
||||
import com.seeyon.apps.src_mainorganization.constans.SyncConstants;
|
||||
import com.seeyon.ctp.common.AppContext;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
public class WorkWeixinbindingServlet extends HttpServlet {
|
||||
|
||||
private String token = "rtfmfptSSM64EEedffcWcnvw6zukb9lG";
|
||||
// private String corpId = "ww69c49385cee3746c";
|
||||
private String corpId = "ww26d46dab95703763";
|
||||
|
||||
private String encodingAESKey = "cuDqCFGORfqjdPx3Yn9D11AAmijNh7Lpq2SdLQcvQkr";
|
||||
|
||||
public String getPluginId() {
|
||||
return SyncConstants.getPluginId();
|
||||
}
|
||||
|
||||
public void doGet(HttpServletRequest request, HttpServletResponse response)throws IOException {
|
||||
ServletOutputStream servletout = response.getOutputStream();
|
||||
String msgSignature = request.getParameter("msg_signature");
|
||||
System.out.println(msgSignature);
|
||||
String timestamp = request.getParameter("timestamp");
|
||||
System.out.println(timestamp);
|
||||
String nonce = request.getParameter("nonce");
|
||||
System.out.println(nonce);
|
||||
String echostr = request.getParameter("echostr");
|
||||
System.out.println(echostr);
|
||||
try {
|
||||
WXBizJsonMsgCrypt wxcpt = new WXBizJsonMsgCrypt(token, encodingAESKey, corpId);
|
||||
|
||||
String str2 = wxcpt.VerifyURL(msgSignature,timestamp,nonce,echostr);
|
||||
System.out.println(str2);
|
||||
byte[] buf = str2.getBytes("UTF-8");
|
||||
servletout.write(buf);
|
||||
servletout.flush();
|
||||
servletout.close();
|
||||
// String str1 = wxcpt.DecryptMsg(msgSignature,timestamp,nonce,"");
|
||||
// System.out.println(str1);
|
||||
} catch (AesException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException{
|
||||
doGet(request,response);
|
||||
}
|
||||
|
||||
|
||||
public void init() throws ServletException { }
|
||||
|
||||
public void destroy() {
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.seeyon.apps.src_mainorganization.util;
|
||||
|
||||
import cn.hutool.log.Log;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.dingtalk.api.request.OapiV2UserCreateRequest;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpStatus;
|
||||
@@ -19,8 +20,7 @@ import java.io.*;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
public class ProtUtil {
|
||||
private static Log log = Log.get(ProtUtil.class);
|
||||
@@ -291,5 +291,4 @@ public class ProtUtil {
|
||||
return content;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
package com.seeyon.apps.src_mainorganization.util;
|
||||
|
||||
import cn.hutool.log.Log;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class WorkweixinProtUtil {
|
||||
private static Log log = Log.get(WorkweixinProtUtil.class);
|
||||
|
||||
/**
|
||||
* 调用get接口
|
||||
* @param strUrl 需要调用的url对应的配置文件的key
|
||||
* @return
|
||||
*/
|
||||
public static String doGet( String strUrl) {
|
||||
return ProtUtil.doGet(strUrl , new HashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用post接口
|
||||
* @param str 调用接口传递的参数json
|
||||
* @param urlStr 需要调用的url对应的参数文件中的编码
|
||||
* @return
|
||||
*/
|
||||
public static String doPost(String str, String urlStr) {
|
||||
return ProtUtil.doPost(str,urlStr ,new HashMap<>());
|
||||
}
|
||||
|
||||
// 获取企业微信token验证信息
|
||||
public static String getAccessToken(String corpid,String corpsecret) throws RuntimeException {
|
||||
StringBuilder address = new StringBuilder("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=")
|
||||
.append(corpid).append("&corpsecret=").append(corpsecret);
|
||||
DefaultHttpClient client = new DefaultHttpClient();
|
||||
String result = "";
|
||||
HttpGet get = new HttpGet(address.toString());
|
||||
String accessToken = "";
|
||||
try {
|
||||
HttpResponse res = client.execute(get);
|
||||
if (res.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
|
||||
result = EntityUtils.toString(res.getEntity());
|
||||
JSONObject resultJSON = JSONObject.parseObject(result);
|
||||
accessToken = resultJSON.getString("access_token");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
// 调用企业微信部门新增接口
|
||||
public static String departmentCreate(String corpid,String corpsecret,JSONObject req) {
|
||||
String accessToken = getAccessToken(corpid,corpsecret);
|
||||
String rsp = doPost(req.toString(),"https://qyapi.weixin.qq.com/cgi-bin/department/create?access_token="+accessToken);
|
||||
System.out.println("创建部门:"+rsp);
|
||||
return rsp;
|
||||
}
|
||||
|
||||
// 调用企业微信部门修改接口
|
||||
public static String departmentUpdate(String corpid,String corpsecret,JSONObject req) {
|
||||
String accessToken = getAccessToken(corpid,corpsecret);
|
||||
String rsp = doPost(req.toString(),"https://qyapi.weixin.qq.com/cgi-bin/department/update?access_token="+accessToken);
|
||||
System.out.println("修改部门:"+rsp);
|
||||
return rsp;
|
||||
}
|
||||
|
||||
// 调用企业微信部门删除接口
|
||||
public static String departmentDelete(String corpid,String corpsecret,String deptId) {
|
||||
String accessToken = getAccessToken(corpid,corpsecret);
|
||||
String rsp = doGet("https://qyapi.weixin.qq.com/cgi-bin/department/delete?access_token="+accessToken+"&id="+deptId);
|
||||
System.out.println("删除部门"+rsp);
|
||||
return rsp;
|
||||
}
|
||||
|
||||
// 调用企业微信人员新增接口
|
||||
public static String memberCreate(String corpid,String corpsecret,Map<String,Object> serviceBroker, Map<String,String> memberMap) {
|
||||
JSONObject req = new JSONObject();
|
||||
// 企业微信人员ID
|
||||
long l = Long.parseLong(serviceBroker.get("id").toString());
|
||||
if(l>0){
|
||||
req.put("userid",serviceBroker.get("id"));
|
||||
}else{
|
||||
req.put("userid",l*-1);
|
||||
}
|
||||
// 人员名称
|
||||
req.put("name",serviceBroker.get("name"));
|
||||
// 人员手机号,企业微信内唯一值
|
||||
req.put("mobile",memberMap.get("telNumber"));
|
||||
// 职位
|
||||
req.put("position",memberMap.get("postName"));
|
||||
// 所属部门ID
|
||||
List<Long> departments = new ArrayList<>();
|
||||
departments.add(Long.parseLong(memberMap.get("deptId")));
|
||||
req.put("department",departments);
|
||||
log.info("请求参数为:"+req.toString());
|
||||
String accessToken = getAccessToken(corpid,corpsecret);
|
||||
String rsp = doPost(req.toString(),"https://qyapi.weixin.qq.com/cgi-bin/user/create?access_token="+accessToken);
|
||||
System.out.println("新增人员:"+rsp);
|
||||
return rsp;
|
||||
}
|
||||
|
||||
// 调用企业微信人员修改接口
|
||||
public static String memberUpdate(String corpid,String corpsecret,Map<String,Object> serviceBroker, Map<String,String> memberMap) {
|
||||
JSONObject req = new JSONObject();
|
||||
// 企业微信用户ID
|
||||
req.put("userid",serviceBroker.get("qywx_member_id"));
|
||||
// 人员名称
|
||||
req.put("name",serviceBroker.get("name"));
|
||||
// 职位
|
||||
req.put("position",memberMap.get("postName"));
|
||||
List<Long> departments = new ArrayList<>();
|
||||
departments.add(Long.parseLong(memberMap.get("deptId")));
|
||||
req.put("department",departments);
|
||||
|
||||
String accessToken = getAccessToken(corpid,corpsecret);
|
||||
String rsp = doPost(req.toString(),"https://qyapi.weixin.qq.com/cgi-bin/user/update?access_token="+accessToken);
|
||||
System.out.println("修改人员:"+rsp);
|
||||
return rsp;
|
||||
}
|
||||
|
||||
// 调用企业微信人员删除接口,根据企业微信人员ID删除人员
|
||||
public static String memberDelete(String corpid,String corpsecret,String userId ){
|
||||
String accessToken = getAccessToken(corpid,corpsecret);
|
||||
String rsp = doGet("https://qyapi.weixin.qq.com/cgi-bin/user/delete?access_token="+accessToken+"&id="+userId);
|
||||
System.out.println("删除人员:"+rsp);
|
||||
return rsp;
|
||||
}
|
||||
|
||||
|
||||
// 调用企业微信接口获取部门下级部门ID
|
||||
public static String departmentListsubid(String corpid,String corpsecret,long parentId){
|
||||
String accessToken = getAccessToken(corpid,corpsecret);
|
||||
String rsp = doGet("https://qyapi.weixin.qq.com/cgi-bin/department/simplelist?access_token="+accessToken+"&id="+parentId);
|
||||
System.out.println("查询下级部门"+rsp);
|
||||
return rsp;
|
||||
}
|
||||
|
||||
|
||||
//调用企业微信接口,根据企业微信部门ID获取部门信息
|
||||
public static String departmentGet(String corpid,String corpsecret,long parentId) {
|
||||
String accessToken = getAccessToken(corpid,corpsecret);
|
||||
String rsp = doGet("https://qyapi.weixin.qq.com/cgi-bin/department/get?access_token="+accessToken+"&id="+parentId);
|
||||
System.out.println("查询部门对应信息"+rsp);
|
||||
return rsp;
|
||||
}
|
||||
|
||||
// 调用企业微信接口,根据企业微信部门ID获取部门下所有人员
|
||||
public static String userListId(String corpid,String corpsecret) {
|
||||
String accessToken = getAccessToken(corpid,corpsecret);
|
||||
JSONObject req = new JSONObject();
|
||||
req.put("limit",10000);
|
||||
String rsp = doPost(req.toString(),"https://qyapi.weixin.qq.com/cgi-bin/user/list_id?access_token="+accessToken);
|
||||
System.out.println(rsp);
|
||||
return rsp;
|
||||
}
|
||||
|
||||
// 调用企业微信接口,调用企业微信人员ID信息获取企业微信中人员详细信息
|
||||
public static String userGet(String corpid,String corpsecret,String userId) {
|
||||
String accessToken = getAccessToken(corpid,corpsecret);
|
||||
String rsp = doGet("https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token="+accessToken+"&id="+userId);
|
||||
System.out.println("查询人员信息:"+rsp);
|
||||
return rsp;
|
||||
}
|
||||
|
||||
public static String getUserIdByMobile(String corpid,String corpsecret,String mobile){
|
||||
String accessToken = getAccessToken(corpid,corpsecret);
|
||||
JSONObject req = new JSONObject();
|
||||
req.put("mobile",mobile);
|
||||
String rsp = doPost(req.toString(),"https://qyapi.weixin.qq.com/cgi-bin/user/getuserid?access_token="+accessToken);
|
||||
System.out.println(rsp);
|
||||
return rsp;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
<bean id="srcEnumDao" class="com.seeyon.apps.src_mainorganization.dao.impl.SrcEnumDaoImpl"></bean>
|
||||
<bean id="srcMemberDao" class="com.seeyon.apps.src_mainorganization.dao.impl.SrcMemberDaoImpl"></bean>
|
||||
<bean id="syncDdDao" class="com.seeyon.apps.src_mainorganization.dao.impl.SyncDdDaoImpl"></bean>
|
||||
<bean id="syncQywxDao" class="com.seeyon.apps.src_mainorganization.dao.impl.SyncQywxDaoImpl"></bean>
|
||||
<bean id="srcPostDao" class="com.seeyon.apps.src_mainorganization.dao.impl.SrcPostDaoImpl"></bean>
|
||||
<bean id="srcLevelDao" class="com.seeyon.apps.src_mainorganization.dao.impl.SrcLevelDaoImpl"></bean>
|
||||
<bean id="srcFormTableDao" class="com.seeyon.apps.src_mainorganization.dao.impl.SrcFormTableDaoImpl"></bean>
|
||||
|
||||
@@ -2,5 +2,6 @@
|
||||
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
|
||||
<beans default-autowire="byName">
|
||||
<bean id="organizationQuartz" class="com.seeyon.apps.src_mainorganization.quartz.OrganizationQuartz" />
|
||||
<bean id="organizationWorkweixinQuartz" class="com.seeyon.apps.src_mainorganization.quartz.OrganizationWorkweixinQuartz" />
|
||||
<bean id="qixuexingOrganizationQuartz" class="com.seeyon.apps.src_mainorganization.quartz.QixuexingOrganizationQuartz" />
|
||||
</beans>
|
||||
@@ -5,7 +5,9 @@
|
||||
<bean id="organizationOrgFlowSyncServer" class="com.seeyon.apps.src_mainorganization.server.OrganizationOrgFlowSyncServer"></bean>
|
||||
<bean id="organizationOrgGroupSyncServer" class="com.seeyon.apps.src_mainorganization.server.OrganizationOrgGroupSyncServer"></bean>
|
||||
<bean id="organizationOrgDingdingSyncServer" class="com.seeyon.apps.src_mainorganization.server.OrganizationOrgDingdingSyncServer"></bean>
|
||||
<bean id="organizationOrgWorkweixinSyncServer" class="com.seeyon.apps.src_mainorganization.server.OrganizationOrgWorkweixinSyncServer"></bean>
|
||||
<bean id="organizationOrgQixuexingSyncServer" class="com.seeyon.apps.src_mainorganization.server.OrganizationOrgQixuexingSyncServer"></bean>
|
||||
<bean id="initializeDingdingSyncServer" class="com.seeyon.apps.src_mainorganization.server.InitializeDingdingSyncServer"></bean>
|
||||
<bean id="initializeWorkweixinSyncServer" class="com.seeyon.apps.src_mainorganization.server.InitializeWorkweixinSyncServer"></bean>
|
||||
|
||||
</beans>
|
||||
Reference in New Issue
Block a user