348 changed files with 118137 additions and 47139 deletions
@ -1,8 +1,8 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<project version="4"> |
|||
<component name="Encoding"> |
|||
<component name="Encoding" native2AsciiForPropertiesFiles="true" defaultCharsetForPropertiesFiles="UTF-8"> |
|||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" /> |
|||
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" /> |
|||
<file url="file://$PROJECT_DIR$/src/main/resources/templates/pages/scanQrCode/scanQrCode2.html" charset="UTF-8" /> |
|||
<file url="PROJECT" charset="UTF-8" /> |
|||
</component> |
|||
</project> |
|||
@ -0,0 +1,78 @@ |
|||
package com.dreamchaser.depository_manage.config; |
|||
|
|||
import org.apache.commons.codec.digest.DigestUtils; |
|||
import sun.misc.BASE64Decoder; |
|||
import sun.misc.BASE64Encoder; |
|||
|
|||
import javax.crypto.Cipher; |
|||
import javax.crypto.SecretKey; |
|||
import javax.crypto.spec.SecretKeySpec; |
|||
|
|||
|
|||
/** |
|||
* 用于3DES加密解密 |
|||
*/ |
|||
public class JM_3DES { |
|||
|
|||
public static String JM_Key = "scanQrCode"; |
|||
/** |
|||
* 获取key |
|||
* @param key |
|||
* @return |
|||
*/ |
|||
public static byte[] hex(String key){ |
|||
String f = DigestUtils.md5Hex(key); |
|||
byte[] bkeys = new String(f).getBytes(); |
|||
byte[] enk = new byte[24]; |
|||
for (int i=0;i<24;i++){ |
|||
enk[i] = bkeys[i]; |
|||
} |
|||
return enk; |
|||
} |
|||
|
|||
/** |
|||
* 3DES加密 |
|||
* @param key 密钥 |
|||
* @param srcStr 需要加密的字符串 |
|||
* @return |
|||
*/ |
|||
public static String encode3Des(String key, String srcStr){ |
|||
byte[] keybyte = hex(key); |
|||
byte[] src = srcStr.getBytes(); |
|||
try { |
|||
//生成密钥
|
|||
SecretKey deskey = new SecretKeySpec(keybyte, "DESede"); |
|||
//加密
|
|||
Cipher c1 = Cipher.getInstance("DESede"); |
|||
c1.init(Cipher.ENCRYPT_MODE, deskey); |
|||
String pwd = (new BASE64Encoder()).encodeBuffer(c1.doFinal(src)); |
|||
return pwd; |
|||
}catch(Exception e){ |
|||
e.printStackTrace(); |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* 3DES解密 |
|||
* @param key 加密密钥 |
|||
* @param desStr 需要解密的字符串 |
|||
* @return |
|||
*/ |
|||
public static String decode3Des(String key, String desStr){ |
|||
byte[] keybyte = hex(key); |
|||
try { |
|||
byte[] src = (new BASE64Decoder()).decodeBuffer(desStr); |
|||
//生成密钥
|
|||
SecretKey deskey = new SecretKeySpec(keybyte, "DESede"); |
|||
//解密
|
|||
Cipher c1 = Cipher.getInstance("DESede"); |
|||
c1.init(Cipher.DECRYPT_MODE, deskey); |
|||
String pwd = new String(c1.doFinal(src)); |
|||
return pwd; |
|||
}catch(Exception e){ |
|||
e.printStackTrace(); |
|||
} |
|||
return null; |
|||
} |
|||
} |
|||
@ -0,0 +1,77 @@ |
|||
package com.dreamchaser.depository_manage.config; |
|||
|
|||
|
|||
import com.alibaba.fastjson.JSON; |
|||
import com.alibaba.fastjson.JSONObject; |
|||
import com.dreamchaser.depository_manage.config.QyWx_template_card.*; |
|||
import com.dreamchaser.depository_manage.utils.HttpUtils; |
|||
import com.dreamchaser.depository_manage.utils.ObjectFormatUtil; |
|||
import lombok.Data; |
|||
|
|||
import java.io.UnsupportedEncodingException; |
|||
import java.net.URLEncoder; |
|||
import java.util.ArrayList; |
|||
import java.util.HashMap; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
|
|||
|
|||
// 用于实现企业微信相关功能
|
|||
|
|||
@Data |
|||
public class QyWxConfig { |
|||
public static String corpid = "ww02f310301953277a"; // 企业的CorpID
|
|||
public static String secret = "GYwyoAGwMwumAVFrFn-RZIc2q11P3pm8NWY9pWDjLqw"; // 应用的凭证密钥
|
|||
public static int AgentId = 1000037; //应用agentid
|
|||
public static String callBackUrl = "https://jy.hxgk.group/QyWxLogin"; |
|||
public static String token = ""; //access_token
|
|||
public static String code = ""; //userCode
|
|||
public static String sendMessage_url = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=ACCESS_TOKEN"; |
|||
|
|||
// 用于回调配置的token
|
|||
public static String sToken = "sM4MFE44fAKdtqvq81HYygqmrdUn"; |
|||
// 用于回调配置的EncodingAESKey
|
|||
public static String sEncodingAESKey = "10cruMoq3ixrQQngJcMN6CzOYrHWmHMpxp2Xn5iYrsk"; |
|||
|
|||
// 用于获取企业微信对应token
|
|||
public static String GetQYWXToken(){ |
|||
String url = String.format(" https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s",QyWxConfig.corpid,QyWxConfig.secret); |
|||
String get = HttpUtils.doGet(url); |
|||
JSONObject jsonObject = JSONObject.parseObject(get); |
|||
Integer errcord = ObjectFormatUtil.toInteger(jsonObject.get("errcode")); |
|||
String accessToken = (String) jsonObject.get("access_token"); |
|||
String errmsg = (String) jsonObject.get("errmsg"); |
|||
if(errcord == 0){ |
|||
// 如果成功获取access_token
|
|||
return accessToken; |
|||
}else{ |
|||
// 否则返回空值
|
|||
return "visitToFail:"+errmsg; |
|||
} |
|||
} |
|||
|
|||
|
|||
// 根据获取到的用户code以及token获取用户id
|
|||
public static JSONObject GetQYWXUserId(){ |
|||
String url = String.format("https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token=%s&code=%s",QyWxConfig.token,QyWxConfig.code); |
|||
String get = HttpUtils.doGet(url); |
|||
JSONObject jsonObject = JSONObject.parseObject(get); |
|||
return jsonObject; |
|||
|
|||
} |
|||
|
|||
// 用于拼接发送链接
|
|||
public static String getQYWXCodeUrl(){ |
|||
String encode = null; |
|||
try { |
|||
encode = URLEncoder.encode(QyWxConfig.callBackUrl, "utf-8"); |
|||
} catch (UnsupportedEncodingException e) { |
|||
e.printStackTrace(); |
|||
} |
|||
String url = String.format("https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_base&agentid=%s#wechat_redirect",QyWxConfig.corpid,encode,QyWxConfig.secret); |
|||
return url; |
|||
} |
|||
|
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
package com.dreamchaser.depository_manage.config.QyWxJMJM.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 ParseXmlError = -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 GenReturnXmlError = -40011;
|
|||
|
|||
private int code; |
|||
|
|||
private static String getMessage(int code) { |
|||
switch (code) { |
|||
case ValidateSignatureError: |
|||
return "签名验证错误"; |
|||
case ParseXmlError: |
|||
return "xml解析失败"; |
|||
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 GenReturnXmlError:
|
|||
// return "xml生成失败";
|
|||
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.dreamchaser.depository_manage.config.QyWxJMJM.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,67 @@ |
|||
/** |
|||
* 对企业微信发送给企业后台的消息加解密示例代码. |
|||
* |
|||
* @copyright Copyright (c) 1998-2014 Tencent Inc. |
|||
*/ |
|||
|
|||
// ------------------------------------------------------------------------
|
|||
|
|||
package com.dreamchaser.depository_manage.config.QyWxJMJM.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.dreamchaser.depository_manage.config.QyWxJMJM.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.dreamchaser.depository_manage.config.QyWxJMJM.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 WXBizMsgCrypt { |
|||
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 WXBizMsgCrypt(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 xmlContent, from_receiveid; |
|||
try { |
|||
// 去除补位字符
|
|||
byte[] bytes = PKCS7Encoder.decode(original); |
|||
|
|||
// 分离16位随机字符串,网络字节序和receiveid
|
|||
byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20); |
|||
|
|||
int xmlLength = recoverNetworkBytesOrder(networkOrder); |
|||
|
|||
xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET); |
|||
from_receiveid = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, 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 xmlContent; |
|||
|
|||
} |
|||
|
|||
/** |
|||
* 将企业微信回复用户的消息加密打包. |
|||
* <ol> |
|||
* <li>对要发送的消息进行AES-CBC加密</li> |
|||
* <li>生成安全签名</li> |
|||
* <li>将消息密文和安全签名打包成xml格式</li> |
|||
* </ol> |
|||
* |
|||
* @param replyMsg 企业微信待回复用户的消息,xml格式的字符串 |
|||
* @param timeStamp 时间戳,可以自己生成,也可以用URL参数的timestamp |
|||
* @param nonce 随机串,可以自己生成,也可以用URL参数的nonce |
|||
* |
|||
* @return 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串 |
|||
* @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());
|
|||
// 生成发送的xml
|
|||
String result = XMLParse.generate(encrypt, signature, timeStamp, nonce); |
|||
return result; |
|||
} |
|||
|
|||
/** |
|||
* 检验消息的真实性,并且获取解密后的明文. |
|||
* <ol> |
|||
* <li>利用收到的密文生成安全签名,进行签名验证</li> |
|||
* <li>若验证通过,则提取xml中的加密消息</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 = XMLParse.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,104 @@ |
|||
/** |
|||
* 对企业微信发送给企业后台的消息加解密示例代码. |
|||
* |
|||
* @copyright Copyright (c) 1998-2014 Tencent Inc. |
|||
*/ |
|||
|
|||
// ------------------------------------------------------------------------
|
|||
|
|||
package com.dreamchaser.depository_manage.config.QyWxJMJM.com.qq.weixin.mp.aes; |
|||
|
|||
import java.io.StringReader; |
|||
|
|||
import javax.xml.parsers.DocumentBuilder; |
|||
import javax.xml.parsers.DocumentBuilderFactory; |
|||
|
|||
import org.w3c.dom.Document; |
|||
import org.w3c.dom.Element; |
|||
import org.w3c.dom.NodeList; |
|||
import org.xml.sax.InputSource; |
|||
|
|||
/** |
|||
* XMLParse class |
|||
* |
|||
* 提供提取消息格式中的密文及生成回复消息格式的接口. |
|||
*/ |
|||
class XMLParse { |
|||
|
|||
/** |
|||
* 提取出xml数据包中的加密消息 |
|||
* @param xmltext 待提取的xml字符串 |
|||
* @return 提取出的加密消息字符串 |
|||
* @throws AesException |
|||
*/ |
|||
public static Object[] extract(String xmltext) throws AesException { |
|||
Object[] result = new Object[3]; |
|||
try { |
|||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); |
|||
|
|||
String FEATURE = null; |
|||
// This is the PRIMARY defense. If DTDs (doctypes) are disallowed, almost all XML entity attacks are prevented
|
|||
// Xerces 2 only - http://xerces.apache.org/xerces2-j/features.html#disallow-doctype-decl
|
|||
FEATURE = "http://apache.org/xml/features/disallow-doctype-decl"; |
|||
dbf.setFeature(FEATURE, true); |
|||
|
|||
// If you can't completely disable DTDs, then at least do the following:
|
|||
// Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-general-entities
|
|||
// Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-general-entities
|
|||
// JDK7+ - http://xml.org/sax/features/external-general-entities
|
|||
FEATURE = "http://xml.org/sax/features/external-general-entities"; |
|||
dbf.setFeature(FEATURE, false); |
|||
|
|||
// Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-parameter-entities
|
|||
// Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-parameter-entities
|
|||
// JDK7+ - http://xml.org/sax/features/external-parameter-entities
|
|||
FEATURE = "http://xml.org/sax/features/external-parameter-entities"; |
|||
dbf.setFeature(FEATURE, false); |
|||
|
|||
// Disable external DTDs as well
|
|||
FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd"; |
|||
dbf.setFeature(FEATURE, false); |
|||
|
|||
// and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
|
|||
dbf.setXIncludeAware(false); |
|||
dbf.setExpandEntityReferences(false); |
|||
|
|||
// And, per Timothy Morgan: "If for some reason support for inline DOCTYPEs are a requirement, then
|
|||
// ensure the entity settings are disabled (as shown above) and beware that SSRF attacks
|
|||
// (http://cwe.mitre.org/data/definitions/918.html) and denial
|
|||
// of service attacks (such as billion laughs or decompression bombs via "jar:") are a risk."
|
|||
|
|||
// remaining parser logic
|
|||
DocumentBuilder db = dbf.newDocumentBuilder(); |
|||
StringReader sr = new StringReader(xmltext); |
|||
InputSource is = new InputSource(sr); |
|||
Document document = db.parse(is); |
|||
|
|||
Element root = document.getDocumentElement(); |
|||
NodeList nodelist1 = root.getElementsByTagName("Encrypt"); |
|||
result[0] = 0; |
|||
result[1] = nodelist1.item(0).getTextContent(); |
|||
return result; |
|||
} catch (Exception e) { |
|||
e.printStackTrace(); |
|||
throw new AesException(AesException.ParseXmlError); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 生成xml消息 |
|||
* @param encrypt 加密后的消息密文 |
|||
* @param signature 安全签名 |
|||
* @param timestamp 时间戳 |
|||
* @param nonce 随机字符串 |
|||
* @return 生成的xml字符串 |
|||
*/ |
|||
public static String generate(String encrypt, String signature, String timestamp, String nonce) { |
|||
|
|||
String format = "<xml>\n" + "<Encrypt><![CDATA[%1$s]]></Encrypt>\n" |
|||
+ "<MsgSignature><![CDATA[%2$s]]></MsgSignature>\n" |
|||
+ "<TimeStamp>%3$s</TimeStamp>\n" + "<Nonce><![CDATA[%4$s]]></Nonce>\n" + "</xml>"; |
|||
return String.format(format, encrypt, signature, timestamp, nonce); |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
package com.dreamchaser.depository_manage.config.QyWx_template_card; |
|||
|
|||
import lombok.Data; |
|||
|
|||
/** |
|||
* 消息基类(企业号 -> 普通用户) |
|||
* |
|||
*/ |
|||
@Data |
|||
public class BaseMessage { |
|||
// 否 成员ID列表(消息接收者,多个接收者用'|'分隔,最多支持1000个)。特殊情况:指定为@all,则向该企业应用的全部成员发送
|
|||
private String touser; |
|||
// 否 部门ID列表,多个接收者用'|'分隔,最多支持100个。当touser为@all时忽略本参数
|
|||
private String toparty; |
|||
// 否 标签ID列表,多个接收者用'|'分隔,最多支持100个。当touser为@all时忽略本参数
|
|||
private String totag; |
|||
// 是 消息类型
|
|||
private String msgtype; |
|||
// 是 企业应用的id,整型。可在应用的设置页面查看
|
|||
private int agentid; |
|||
// 否 表示是否开启id转译,0表示否,1表示是,默认0
|
|||
private int enable_id_trans; |
|||
// 否 表示是否开启重复消息检查,0表示否,1表示是,默认0
|
|||
private int enable_duplicate_check; |
|||
// 否 表示是否重复消息检查的时间间隔,默认1800s,最大不超过4小时
|
|||
private int duplicate_check_interval; |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
package com.dreamchaser.depository_manage.config.QyWx_template_card; |
|||
|
|||
import lombok.Data; |
|||
|
|||
/** |
|||
* 按钮交互性 |
|||
* |
|||
*/ |
|||
@Data |
|||
public class ButtonInteraction extends BaseMessage { |
|||
// 模板卡片
|
|||
private TemplateCard_button_interaction template_card; |
|||
// 否 表示是否是保密消息,0表示否,1表示是,默认0
|
|||
private int safe; |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
package com.dreamchaser.depository_manage.config.QyWx_template_card; |
|||
|
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
public class MessageByMarkDown extends BaseMessage { |
|||
private Object markdown; |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
package com.dreamchaser.depository_manage.config.QyWx_template_card; |
|||
|
|||
import lombok.Data; |
|||
|
|||
/** |
|||
* 操作 |
|||
*/ |
|||
@Data |
|||
public class TemplateCard_action { |
|||
/** |
|||
* 操作的描述文案 |
|||
*/ |
|||
private String text; |
|||
/** |
|||
* 操作key值,用户点击后,会产生回调事件将本参数作为EventKey返回,回调事件会带上该key值,最长支持1024字节,不可重复 |
|||
*/ |
|||
private String key; |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
package com.dreamchaser.depository_manage.config.QyWx_template_card; |
|||
|
|||
import lombok.Data; |
|||
|
|||
import javax.xml.bind.annotation.XmlRootElement; |
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 卡片右上角更多操作按钮 |
|||
*/ |
|||
@XmlRootElement |
|||
@Data |
|||
public class TemplateCard_action_menu { |
|||
/** |
|||
* 更多操作界面的描述 |
|||
*/ |
|||
private String desc; |
|||
|
|||
/** |
|||
* 操作列表,列表长度取值范围为 [1, 3] |
|||
*/ |
|||
private List<TemplateCard_action> action_list; |
|||
|
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
package com.dreamchaser.depository_manage.config.QyWx_template_card; |
|||
|
|||
import lombok.Data; |
|||
|
|||
import javax.xml.bind.annotation.XmlRootElement; |
|||
|
|||
/** |
|||
* 按钮 |
|||
*/ |
|||
@XmlRootElement |
|||
@Data |
|||
public class TemplateCard_button { |
|||
/** |
|||
* 按钮点击事件类型,0 或不填代表回调点击事件,1 代表跳转url |
|||
*/ |
|||
private Integer type; |
|||
/** |
|||
* 按钮文案,建议不超过10个字 |
|||
*/ |
|||
private String text; |
|||
/** |
|||
* 按钮样式,目前可填1~4,不填或错填默认1 |
|||
*/ |
|||
private Integer style; |
|||
/** |
|||
* 按钮key值,用户点击后,会产生回调事件将本参数作为EventKey返回,回调事件会带上该key值,最长支持1024字节,不可重复,button_list.type是0时必填 |
|||
*/ |
|||
private String key; |
|||
/** |
|||
* 跳转事件的url,button_list.type是1时必填 |
|||
*/ |
|||
private String url; |
|||
} |
|||
@ -0,0 +1,74 @@ |
|||
package com.dreamchaser.depository_manage.config.QyWx_template_card; |
|||
|
|||
import lombok.Data; |
|||
|
|||
import javax.xml.bind.annotation.XmlRootElement; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 卡片模板--按钮交互型 |
|||
* |
|||
*/ |
|||
@XmlRootElement |
|||
@Data |
|||
public class TemplateCard_button_interaction { |
|||
/** |
|||
* 模板卡片类型,投票选择型卡片填写"vote_interaction" |
|||
*/ |
|||
|
|||
private String card_type; |
|||
/** |
|||
* 卡片来源样式信息,不需要来源样式可不填写 |
|||
*/ |
|||
private TemplateCard_source source; |
|||
|
|||
/** |
|||
* 卡片右上角更多操作按钮 |
|||
*/ |
|||
private TemplateCard_action_menu action_menu; |
|||
|
|||
/** |
|||
* 一级标题 |
|||
*/ |
|||
private TemplateCard_main_title main_title; |
|||
|
|||
/** |
|||
* 引用文献样式 |
|||
*/ |
|||
private Template_quote_area quote_area; |
|||
|
|||
/** |
|||
* 二级普通文本,建议不超过160个字,(支持id转译) |
|||
*/ |
|||
private String sub_title_text; |
|||
|
|||
|
|||
/** |
|||
* 二级标题+文本列表,该字段可为空数组,但有数据的话需确认对应字段是否必填,列表长度不超过6 |
|||
*/ |
|||
private List<TemplateCard_horizontal_content> horizontal_content_list; |
|||
|
|||
|
|||
/** |
|||
* 整体卡片的点击跳转事件 |
|||
*/ |
|||
private TemplateCard_card_action card_action; |
|||
|
|||
/** |
|||
* 任务id,同一个应用任务id不能重复,只能由数字、字母和“_-@”组成,最长128字节 |
|||
*/ |
|||
private String task_id; |
|||
|
|||
|
|||
/** |
|||
* 下拉式的选择器 |
|||
*/ |
|||
private TemplateCard_button_selection button_selection; |
|||
|
|||
/** |
|||
* 按钮列表,列表长度不超过6 |
|||
*/ |
|||
private List<TemplateCard_button> button_list; |
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
package com.dreamchaser.depository_manage.config.QyWx_template_card; |
|||
|
|||
import lombok.Data; |
|||
|
|||
import javax.xml.bind.annotation.XmlRootElement; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 下拉式的选择器 |
|||
*/ |
|||
@XmlRootElement |
|||
@Data |
|||
public class TemplateCard_button_selection { |
|||
/** |
|||
* 下拉式的选择器的key,用户提交选项后,会产生回调事件,回调事件会带上该key值表示该题,最长支持1024字节 |
|||
*/ |
|||
private String question_key; |
|||
|
|||
/** |
|||
* 下拉式的选择器左边的标题 |
|||
*/ |
|||
private String title; |
|||
|
|||
/** |
|||
* 选项列表,下拉选项不超过 10 个,最少1个 |
|||
*/ |
|||
private List<TemplateCard_button_selection_option> option_list; |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
package com.dreamchaser.depository_manage.config.QyWx_template_card; |
|||
|
|||
import lombok.Data; |
|||
|
|||
/** |
|||
* 下拉选项 |
|||
*/ |
|||
@Data |
|||
public class TemplateCard_button_selection_option { |
|||
/** |
|||
* 下拉式的选择器选项的id,用户提交后,会产生回调事件,回调事件会带上该id值表示该选项,最长支持128字节,不可重复 |
|||
*/ |
|||
private String id; |
|||
/** |
|||
* 下拉式的选择器选项的文案,建议不超过16个字 |
|||
*/ |
|||
private String text; |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
package com.dreamchaser.depository_manage.config.QyWx_template_card; |
|||
|
|||
import lombok.Data; |
|||
|
|||
import javax.xml.bind.annotation.XmlRootElement; |
|||
|
|||
|
|||
/** |
|||
* 整体卡片的点击跳转事件 |
|||
*/ |
|||
@XmlRootElement |
|||
@Data |
|||
public class TemplateCard_card_action { |
|||
/** |
|||
* 跳转事件类型,0或不填代表不是链接,1 代表跳转url,2 代表打开小程序 |
|||
*/ |
|||
private Integer type; |
|||
|
|||
/** |
|||
* 跳转事件的url,card_action.type是1时必填 |
|||
*/ |
|||
private String url; |
|||
|
|||
/** |
|||
* 跳转事件的小程序的appid,必须是与当前应用关联的小程序,card_action.type是2时必填 |
|||
*/ |
|||
private Integer appid; |
|||
|
|||
/** |
|||
* 跳转事件的小程序的pagepath,card_action.type是2时选填 |
|||
*/ |
|||
private String pagepath; |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
package com.dreamchaser.depository_manage.config.QyWx_template_card; |
|||
|
|||
import lombok.Data; |
|||
|
|||
/** |
|||
* 关键数据样式 |
|||
*/ |
|||
@Data |
|||
public class TemplateCard_emphasis_content { |
|||
/** |
|||
* 关键数据样式的数据内容,建议不超过14个字 |
|||
*/ |
|||
private String title; |
|||
/** |
|||
* 关键数据样式的数据描述内容,建议不超过22个字 |
|||
*/ |
|||
private String desc; |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
package com.dreamchaser.depository_manage.config.QyWx_template_card; |
|||
|
|||
import lombok.Data; |
|||
|
|||
import javax.xml.bind.annotation.XmlRootElement; |
|||
|
|||
/** |
|||
* 二级标题+文本列表 |
|||
*/ |
|||
@XmlRootElement |
|||
@Data |
|||
public class TemplateCard_horizontal_content { |
|||
/** |
|||
* 链接类型,0或不填代表不是链接,1 代表跳转url,2 代表下载附件,3 代表点击跳转成员详情 |
|||
*/ |
|||
private Integer type; |
|||
|
|||
/** |
|||
* 二级标题,建议不超过5个字 |
|||
*/ |
|||
private String keyname; |
|||
|
|||
/** |
|||
* 二级文本,如果horizontal_content_list.type是2,该字段代表文件名称(要包含文件类型),建议不超过30个字,(支持id转译) |
|||
*/ |
|||
private String value; |
|||
|
|||
/** |
|||
* 链接跳转的url,horizontal_content_list.type是1时必填 |
|||
*/ |
|||
private String url; |
|||
|
|||
/** |
|||
* 附件的media_id,horizontal_content_list.type是2时必填 |
|||
*/ |
|||
private Integer media_id; |
|||
|
|||
|
|||
/** |
|||
* 成员详情的userid,horizontal_content_list.type是3时必填 |
|||
*/ |
|||
private String userid; |
|||
|
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
package com.dreamchaser.depository_manage.config.QyWx_template_card; |
|||
|
|||
import lombok.Data; |
|||
|
|||
/** |
|||
* 跳转指引样式 |
|||
*/ |
|||
@Data |
|||
public class TemplateCard_jump { |
|||
/** |
|||
* 跳转链接样式的文案内容,建议不超过18个字 |
|||
*/ |
|||
private String title; |
|||
/** |
|||
* 跳转链接类型,0或不填代表不是链接,1 代表跳转url,2 代表跳转小程序 |
|||
*/ |
|||
private String type; |
|||
/** |
|||
* 跳转链接的url,jump_list.type是1时必填 |
|||
*/ |
|||
private String url; |
|||
/** |
|||
* 跳转链接的小程序的appid,必须是与当前应用关联的小程序,jump_list.type是2时必填 |
|||
*/ |
|||
private String appid; |
|||
/** |
|||
* 跳转链接的小程序的pagepath,jump_list.type是2时选填 |
|||
*/ |
|||
private String pagepath; |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
package com.dreamchaser.depository_manage.config.QyWx_template_card; |
|||
|
|||
import lombok.Data; |
|||
|
|||
import javax.xml.bind.annotation.XmlRootElement; |
|||
|
|||
|
|||
/** |
|||
* 一级标题 |
|||
*/ |
|||
@XmlRootElement |
|||
@Data |
|||
public class TemplateCard_main_title { |
|||
/** |
|||
* 一级标题,建议不超过36个字,(支持id转译) |
|||
*/ |
|||
private String title; |
|||
/** |
|||
* 标题辅助信息,建议不超过44个字,(支持id转译) |
|||
*/ |
|||
private String desc; |
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
package com.dreamchaser.depository_manage.config.QyWx_template_card; |
|||
|
|||
import lombok.Data; |
|||
|
|||
import javax.xml.bind.annotation.XmlRootElement; |
|||
|
|||
/* |
|||
卡片来源样式信息 |
|||
*/ |
|||
@XmlRootElement |
|||
@Data |
|||
public class TemplateCard_source { |
|||
/** |
|||
* 来源图片的url,来源图片的尺寸建议为72*72 |
|||
*/ |
|||
private String icon_url; |
|||
|
|||
/** |
|||
* 来源图片的描述,建议不超过20个字,(支持id转译) |
|||
*/ |
|||
private String desc; |
|||
|
|||
/** |
|||
* 来源文字的颜色,目前支持:0(默认) 灰色,1 黑色,2 红色,3 绿色 |
|||
*/ |
|||
private Integer desc_color; |
|||
|
|||
} |
|||
@ -0,0 +1,74 @@ |
|||
package com.dreamchaser.depository_manage.config.QyWx_template_card; |
|||
|
|||
|
|||
import lombok.Data; |
|||
|
|||
import javax.xml.bind.annotation.XmlRootElement; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 卡片模板--文本通知型 |
|||
*/ |
|||
@XmlRootElement |
|||
@Data |
|||
public class TemplateCard_text_notice { |
|||
/** |
|||
* 模板卡片类型,投票选择型卡片填写"vote_interaction" |
|||
*/ |
|||
|
|||
private String card_type; |
|||
/** |
|||
* 卡片来源样式信息,不需要来源样式可不填写 |
|||
*/ |
|||
private TemplateCard_source source; |
|||
|
|||
/** |
|||
* 卡片右上角更多操作按钮 |
|||
*/ |
|||
private TemplateCard_action_menu action_menu; |
|||
|
|||
/** |
|||
* 一级标题 |
|||
*/ |
|||
private TemplateCard_main_title main_title; |
|||
|
|||
/** |
|||
* 引用文献样式 |
|||
*/ |
|||
private Template_quote_area quote_area; |
|||
|
|||
/** |
|||
* 关键数据样式 |
|||
*/ |
|||
private TemplateCard_emphasis_content emphasis_content; |
|||
|
|||
/** |
|||
* 二级普通文本,建议不超过160个字,(支持id转译) |
|||
*/ |
|||
private String sub_title_text; |
|||
|
|||
|
|||
/** |
|||
* 二级标题+文本列表,该字段可为空数组,但有数据的话需确认对应字段是否必填,列表长度不超过6 |
|||
*/ |
|||
private List<TemplateCard_horizontal_content> horizontal_content_list; |
|||
|
|||
/** |
|||
* 跳转指引样式的列表,该字段可为空数组,但有数据的话需确认对应字段是否必填,列表长度不超过3 |
|||
*/ |
|||
private List<TemplateCard_jump> jump_list; |
|||
|
|||
|
|||
/** |
|||
* 整体卡片的点击跳转事件 |
|||
*/ |
|||
private TemplateCard_card_action card_action; |
|||
|
|||
/** |
|||
* 任务id,同一个应用任务id不能重复,只能由数字、字母和“_-@”组成,最长128字节 |
|||
*/ |
|||
private String task_id; |
|||
|
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
package com.dreamchaser.depository_manage.config.QyWx_template_card; |
|||
|
|||
import lombok.Data; |
|||
|
|||
import javax.xml.bind.annotation.XmlRootElement; |
|||
|
|||
/** |
|||
* 引用文献样式 |
|||
*/ |
|||
@XmlRootElement |
|||
@Data |
|||
public class Template_quote_area { |
|||
/** |
|||
* 引用文献样式区域点击事件,0或不填代表没有点击事件,1 代表跳转url,2 代表跳转小程序 |
|||
*/ |
|||
private Integer type; |
|||
/** |
|||
* 点击跳转的url,quote_area.type是1时必填 |
|||
*/ |
|||
private String url; |
|||
/** |
|||
* 点击跳转的小程序的appid,必须是与当前应用关联的小程序,quote_area.type是2时必填 |
|||
*/ |
|||
private Integer appid; |
|||
/** |
|||
* 点击跳转的小程序的pagepath,quote_area.type是2时选填 |
|||
*/ |
|||
private String pagepath; |
|||
/** |
|||
* 引用文献样式的标题 |
|||
*/ |
|||
private String title; |
|||
/** |
|||
* 引用文献样式的引用文案 |
|||
*/ |
|||
private String quote_text; |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
package com.dreamchaser.depository_manage.config.QyWx_template_card; |
|||
|
|||
|
|||
import lombok.Data; |
|||
|
|||
/** |
|||
* 文本通知型 |
|||
*/ |
|||
|
|||
@Data |
|||
public class TextNotice extends BaseMessage{ |
|||
// 模板卡片
|
|||
private TemplateCard_text_notice template_card; |
|||
// 否 表示是否是保密消息,0表示否,1表示是,默认0
|
|||
private int safe; |
|||
} |
|||
@ -0,0 +1,276 @@ |
|||
package com.dreamchaser.depository_manage.controller; |
|||
|
|||
import cn.hutool.http.HttpUtil; |
|||
import com.alibaba.fastjson.JSONObject; |
|||
import com.dreamchaser.depository_manage.config.PortConfig; |
|||
import com.dreamchaser.depository_manage.config.QyWxConfig; |
|||
import com.dreamchaser.depository_manage.config.QyWxJMJM.com.qq.weixin.mp.aes.AesException; |
|||
import com.dreamchaser.depository_manage.config.QyWxJMJM.com.qq.weixin.mp.aes.WXBizMsgCrypt; |
|||
import com.dreamchaser.depository_manage.entity.CallBackLog; |
|||
import com.dreamchaser.depository_manage.entity.UserByPort; |
|||
import com.dreamchaser.depository_manage.pojo.callBackXml.CallBackBaseXml; |
|||
import com.dreamchaser.depository_manage.pojo.callBackXml.callBackXml_button_templatecard.TemplateCard; |
|||
import com.dreamchaser.depository_manage.security.pool.AuthenticationTokenPool; |
|||
import com.dreamchaser.depository_manage.security.pool.UserKeyAndTokenPool; |
|||
import com.dreamchaser.depository_manage.service.CallBackLogService; |
|||
import com.dreamchaser.depository_manage.service.DepositoryRecordService; |
|||
import com.dreamchaser.depository_manage.service.DepositoryService; |
|||
import com.dreamchaser.depository_manage.service.impl.QyWxOperationService; |
|||
import com.dreamchaser.depository_manage.utils.ObjectFormatUtil; |
|||
import com.dreamchaser.depository_manage.utils.QyWxXMLUtils; |
|||
import io.micrometer.core.instrument.util.IOUtils; |
|||
import org.joda.time.format.FormatUtils; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.http.MediaType; |
|||
import org.springframework.stereotype.Controller; |
|||
import org.springframework.web.bind.annotation.*; |
|||
import org.springframework.web.servlet.ModelAndView; |
|||
|
|||
import javax.servlet.http.HttpServletRequest; |
|||
import javax.servlet.http.HttpServletResponse; |
|||
import javax.servlet.http.HttpSession; |
|||
import java.io.IOException; |
|||
import java.io.InputStream; |
|||
import java.io.PrintWriter; |
|||
import java.time.Instant; |
|||
import java.util.Enumeration; |
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
|
|||
|
|||
/** |
|||
* 用于企业微信相关操作的控制器 |
|||
*/ |
|||
@Controller |
|||
public class QyWxOperationController { |
|||
|
|||
|
|||
@Autowired |
|||
CallBackLogService callBackLogService; |
|||
|
|||
|
|||
@Autowired |
|||
QyWxOperationService qyWxOperationService; |
|||
|
|||
|
|||
@Autowired |
|||
DepositoryRecordService depositoryRecordService; |
|||
|
|||
|
|||
/** |
|||
* 用于接收企业微信的回调,get方式 |
|||
*/ |
|||
@GetMapping("/callback") |
|||
public void callBackForGet(@RequestParam Map<String,Object> map, HttpServletResponse response){ |
|||
try { |
|||
// 构造解密对象
|
|||
WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(QyWxConfig.sToken,QyWxConfig.sEncodingAESKey,QyWxConfig.corpid); |
|||
// 企业微信加密签名
|
|||
String sVerifyMsgSig = (String) map.get("msg_signature"); |
|||
// 时间戳
|
|||
Integer sVerifyTimeStamp = ObjectFormatUtil.toInteger(map.get("timestamp")); |
|||
// 随机数
|
|||
String sVerifyNonce = (String) map.get("nonce"); |
|||
// 加密的字符串
|
|||
String sVerifyEchoStr = (String) map.get("echostr"); |
|||
String sEchoStr; //需要返回的明文
|
|||
sEchoStr = wxcpt.VerifyURL(sVerifyMsgSig, sVerifyTimeStamp.toString(), |
|||
sVerifyNonce, sVerifyEchoStr); |
|||
|
|||
// 添加日志
|
|||
CallBackLog callBackLog = new CallBackLog(); |
|||
callBackLog.setTimestamp(sVerifyTimeStamp); |
|||
callBackLog.setNonce(sVerifyNonce); |
|||
callBackLog.setEchostr(sVerifyEchoStr); |
|||
callBackLogService.addCallBackLog(callBackLog); |
|||
|
|||
//返回明文
|
|||
PrintWriter writer = response.getWriter(); |
|||
writer.println(sEchoStr); |
|||
|
|||
System.out.println(sEchoStr); |
|||
} catch (AesException | IOException e) { |
|||
e.printStackTrace(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 用于接收企业微信的回调,post方式 |
|||
*/ |
|||
@PostMapping("/callback") |
|||
public void callBackForPost(@RequestParam Map<String,Object> param, |
|||
@RequestBody(required = false) Map<String,Object> map, |
|||
HttpServletRequest request,HttpServletResponse response){ |
|||
try { |
|||
|
|||
|
|||
// 构造解密对象
|
|||
WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(QyWxConfig.sToken,QyWxConfig.sEncodingAESKey,QyWxConfig.corpid); |
|||
// 企业微信加密签名
|
|||
String sVerifyMsgSig = (String) param.get("msg_signature"); |
|||
// 时间戳
|
|||
Integer sVerifyTimeStamp = ObjectFormatUtil.toInteger(param.get("timestamp")); |
|||
// 随机数
|
|||
String sVerifyNonce = (String) param.get("nonce"); |
|||
// 加密的字符串
|
|||
String sVerifyEchoStr = (String) param.get("echostr"); |
|||
|
|||
|
|||
if(sVerifyEchoStr != null) { |
|||
// 如果是验证url
|
|||
|
|||
String sEchoStr; //需要返回的明文
|
|||
sEchoStr = wxcpt.VerifyURL(sVerifyMsgSig, sVerifyTimeStamp.toString(), |
|||
sVerifyNonce, sVerifyEchoStr); |
|||
|
|||
// 添加日志
|
|||
CallBackLog callBackLog = new CallBackLog(); |
|||
callBackLog.setTimestamp(sVerifyTimeStamp); |
|||
callBackLog.setNonce(sVerifyNonce); |
|||
callBackLog.setEchostr(sVerifyEchoStr); |
|||
callBackLogService.addCallBackLog(callBackLog); |
|||
|
|||
//返回明文
|
|||
PrintWriter writer = response.getWriter(); |
|||
writer.println(sEchoStr); |
|||
writer.close(); |
|||
}else{ |
|||
// 如果是响应事件
|
|||
|
|||
String ToUserName = (String) map.get("ToUserName"); |
|||
String Encrypt = (String) map.get("Encrypt"); |
|||
String AgentID = (String) map.get("AgentID"); |
|||
|
|||
// 需要解密的xml
|
|||
String sReqData = String.format("<xml><ToUserName><![CDATA[%s]]></ToUserName>" + |
|||
"<Encrypt><![CDATA[%s]]></Encrypt>" + |
|||
"<AgentID><![CDATA[%s]]></AgentID></xml>",ToUserName,Encrypt,AgentID); |
|||
// 解析后的数据
|
|||
String sMsg = wxcpt.DecryptMsg(sVerifyMsgSig, sVerifyTimeStamp.toString(), sVerifyNonce, sReqData); |
|||
// 将数据转为java对象
|
|||
TemplateCard templateCard = (TemplateCard) QyWxXMLUtils.convertXmlStrToObject(TemplateCard.class, sMsg); |
|||
// 点击用户
|
|||
String fromUserName = templateCard.getFromUserName(); |
|||
// 根据userId获取处理人
|
|||
Map<String, Object> portInfo = PortConfig.findUserByQyWxUserId(fromUserName); |
|||
UserByPort userByPort = (UserByPort) portInfo.get("user"); |
|||
|
|||
// 获取点击的按钮
|
|||
String clickKey = templateCard.getEventKey().split("_")[1]; |
|||
String result = ""; |
|||
if("pass".equals(clickKey)){ |
|||
result = "通过"; |
|||
}else{ |
|||
result = "驳回"; |
|||
} |
|||
// 开启线程处理审批
|
|||
new Thread(new Runnable() { |
|||
@Override |
|||
public void run() { |
|||
depositoryRecordService.reviewByQyWx(templateCard); |
|||
} |
|||
}).start(); |
|||
|
|||
// 开启线程更改其他用户卡片模板样式
|
|||
String finalResult = result; |
|||
new Thread(new Runnable() { |
|||
@Override |
|||
public void run() { |
|||
qyWxOperationService.updateTemplateCard(templateCard.getResponseCode(),userByPort.getName(), finalResult); |
|||
} |
|||
}).start(); |
|||
|
|||
|
|||
// 待加密模板
|
|||
String sRespData = String.format("<xml><ToUserName><![CDATA[%s]]></ToUserName>" + |
|||
"<FromUserName><![CDATA[%s]]></FromUserName>" + |
|||
"<CreateTime>%s</CreateTime>"+ |
|||
"<MsgType><![CDATA[update_button]]></MsgType>"+ |
|||
"<Button>" + |
|||
"<ReplaceName><![CDATA[%s]]></ReplaceName></Button>" + |
|||
"</xml>",ToUserName,QyWxConfig.corpid,templateCard.getCreateTime(),"已"+result); |
|||
// 加密
|
|||
String sEncryptMsg = wxcpt.EncryptMsg(sRespData, sVerifyTimeStamp.toString(), sVerifyNonce); |
|||
//3.响应消息
|
|||
PrintWriter out = response.getWriter(); |
|||
out.print(sEncryptMsg); |
|||
out.close(); |
|||
} |
|||
} catch (AesException | IOException e) { |
|||
e.printStackTrace(); |
|||
} |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 用于企业微信登录 |
|||
* @param code |
|||
* @param action |
|||
* @param state |
|||
* @param request |
|||
* @return |
|||
*/ |
|||
@GetMapping("/QyWxLogin") |
|||
public ModelAndView QyWxLogin(@RequestParam(required = false)String code, |
|||
@RequestParam(required = false)String action, |
|||
@RequestParam(required = false)String state, |
|||
HttpServletRequest request) |
|||
{ |
|||
ModelAndView mv = new ModelAndView(); |
|||
mv.addObject("userWxId",""); |
|||
mv.setViewName("pages/user/login"); |
|||
if(code != null) { |
|||
QyWxConfig.code = code; |
|||
JSONObject jsonObject = QyWxConfig.GetQYWXUserId(); |
|||
Integer errCode = jsonObject.getInteger("errcode"); |
|||
String userId = jsonObject.getString("userid"); |
|||
if (errCode == 0) { |
|||
// 如果成功获取userid
|
|||
Map<String, Object> portInfo = PortConfig.findUserByQyWxUserId(userId); |
|||
UserByPort userByPort =(UserByPort) portInfo.get("user"); |
|||
String key = (String) portInfo.get("key"); |
|||
String token = (String) portInfo.get("token"); |
|||
if (userByPort != null) { |
|||
// 如果数据库中存在该用户
|
|||
String keyAndToken = key + "&" +token; |
|||
// 将key与token暂存至池中保存
|
|||
UserKeyAndTokenPool.addKeyAndToken(userByPort.getNumber(),keyAndToken); |
|||
// 设置放入时间
|
|||
userByPort.setInstant(Instant.now()); |
|||
AuthenticationTokenPool.addToken(token, userByPort); |
|||
HttpSession session = request.getSession(); |
|||
session.setAttribute("token"+userByPort.getId(), token); |
|||
session.setAttribute("userToken",userByPort); |
|||
session.setMaxInactiveInterval(1800); |
|||
mv.addObject("user",userByPort); |
|||
mv.setViewName("index"); |
|||
}else{ |
|||
JSONObject captcha = PageController.Captcha(request); |
|||
String picPath = (String) captcha.get("picPath"); |
|||
String captchaid = (String) captcha.get("captchaid"); |
|||
mv.addObject("picPath", picPath); |
|||
mv.addObject("captchaid", captchaid); |
|||
mv.addObject("userWxId",userId); |
|||
} |
|||
}else{ |
|||
JSONObject captcha = PageController.Captcha(request); |
|||
String picPath = (String) captcha.get("picPath"); |
|||
String captchaid = (String) captcha.get("captchaid"); |
|||
mv.addObject("picPath", picPath); |
|||
mv.addObject("captchaid", captchaid); |
|||
} |
|||
}else{ |
|||
JSONObject captcha = PageController.Captcha(request); |
|||
String picPath = (String) captcha.get("picPath"); |
|||
String captchaid = (String) captcha.get("captchaid"); |
|||
mv.addObject("picPath", picPath); |
|||
mv.addObject("captchaid", captchaid); |
|||
} |
|||
return mv; |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
package com.dreamchaser.depository_manage.entity; |
|||
|
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
public class CallBackLog { |
|||
public Integer id; |
|||
public String msg_signature; |
|||
public Integer timestamp; |
|||
public String nonce; |
|||
public String echostr; |
|||
public String xmlstr; |
|||
public String jsonstr; |
|||
public String reqdata; |
|||
public Integer addtime; |
|||
|
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
package com.dreamchaser.depository_manage.mapper; |
|||
|
|||
|
|||
import com.dreamchaser.depository_manage.entity.CallBackLog; |
|||
import org.apache.ibatis.annotations.Mapper; |
|||
import org.springframework.stereotype.Repository; |
|||
|
|||
import java.util.Map; |
|||
|
|||
@Mapper |
|||
@Repository |
|||
public interface CallBackLogMapper { |
|||
|
|||
/** |
|||
* 添加回调日志 |
|||
* @param map |
|||
* @return |
|||
*/ |
|||
Integer addCallBackLog(Map<String,Object> map); |
|||
|
|||
|
|||
/** |
|||
* 添加回调日志 |
|||
* @param callBackLog |
|||
* @return |
|||
*/ |
|||
Integer addCallBackLog(CallBackLog callBackLog); |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
|||
|
|||
<!-- depository --> |
|||
<mapper namespace="com.dreamchaser.depository_manage.mapper.CallBackLogMapper"> |
|||
<resultMap id="callBackMap" type="com.dreamchaser.depository_manage.entity.CallBackLog"> |
|||
<id column="id" property="id" jdbcType="INTEGER" /> |
|||
<result column="msg_signature" property="msg_signature" jdbcType="VARCHAR" /> |
|||
<result column="timestamp" property="timestamp" jdbcType="INTEGER" /> |
|||
<result column="nonce" property="nonce" jdbcType="VARCHAR" /> |
|||
<result column="echostr" property="echostr" jdbcType="VARCHAR" /> |
|||
<result column="xmlstr" property="xmlstr" jdbcType="VARCHAR" /> |
|||
<result column="jsonstr" property="jsonstr" jdbcType="VARCHAR" /> |
|||
<result column="reqdata" property="reqdata" jdbcType="VARCHAR"/> |
|||
<result column="addtime" property="addtime" jdbcType="INTEGER"/> |
|||
</resultMap> |
|||
|
|||
<insert id="addCallBackLog"> |
|||
|
|||
INSERT INTO callback_log ( |
|||
id, msg_signature, timestamp,nonce,echostr,xmlstr,jsonstr,reqdata,addtime |
|||
) VALUES ( |
|||
#{id}, |
|||
#{msg_signature}, |
|||
#{timestamp}, |
|||
#{nonce}, |
|||
#{echostr}, |
|||
#{xmlstr}, |
|||
#{jsonstr}, |
|||
#{reqdata}, |
|||
#{addtime} |
|||
) |
|||
|
|||
</insert> |
|||
</mapper> |
|||
@ -0,0 +1,102 @@ |
|||
package com.dreamchaser.depository_manage.pojo; |
|||
|
|||
import com.dreamchaser.depository_manage.entity.ApplicationOutRecordMin; |
|||
import lombok.Data; |
|||
|
|||
import java.math.BigInteger; |
|||
|
|||
@Data |
|||
public class ApplicationOutRecordMinP { |
|||
/** |
|||
* id |
|||
*/ |
|||
private Integer id; |
|||
|
|||
/** |
|||
* 物料id |
|||
*/ |
|||
private Integer mid; |
|||
|
|||
/** |
|||
* 物料名称 |
|||
*/ |
|||
private String mname; |
|||
|
|||
/** |
|||
* 物料编码 |
|||
*/ |
|||
private BigInteger mcode; |
|||
/** |
|||
* 仓库id |
|||
*/ |
|||
private Integer depositoryId; |
|||
|
|||
|
|||
/** |
|||
* 仓库名称 |
|||
*/ |
|||
private String depositoryName; |
|||
|
|||
/** |
|||
* 对应库位id |
|||
*/ |
|||
private Integer placeId; |
|||
|
|||
|
|||
/** |
|||
* 库位编码 |
|||
*/ |
|||
private String placeCode; |
|||
/** |
|||
* 数量 |
|||
*/ |
|||
private Integer quantity; |
|||
/** |
|||
* 出库单号 |
|||
*/ |
|||
private String code; |
|||
|
|||
/** |
|||
* 审核人编号 |
|||
*/ |
|||
private Integer checkId; |
|||
|
|||
|
|||
/** |
|||
* 审核人姓名 |
|||
*/ |
|||
private String checkerName; |
|||
|
|||
/** |
|||
* 申请人姓名 |
|||
*/ |
|||
private String applicantName; |
|||
/** |
|||
* 主订单编号 |
|||
*/ |
|||
private Integer parentId; |
|||
|
|||
|
|||
/** |
|||
* 当前申请金额 |
|||
*/ |
|||
private Double price; |
|||
|
|||
/** |
|||
* 子订单状态(1未完成,2完成) |
|||
*/ |
|||
private Integer state; |
|||
|
|||
|
|||
public ApplicationOutRecordMinP(ApplicationOutRecordMin recordMin) { |
|||
this.id = recordMin.getId(); |
|||
this.mid = recordMin.getMid(); |
|||
this.depositoryId = recordMin.getDepositoryId(); |
|||
this.checkId = recordMin.getCheckId(); |
|||
this.code = recordMin.getCode(); |
|||
this.quantity = recordMin.getQuantity(); |
|||
this.state = recordMin.getState(); |
|||
this.parentId = recordMin.getParentId(); |
|||
this.placeId = recordMin.getPlaceId(); |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
package com.dreamchaser.depository_manage.pojo.callBackXml; |
|||
|
|||
|
|||
import lombok.Data; |
|||
import org.apache.commons.math3.util.Precision; |
|||
|
|||
import javax.xml.bind.annotation.*; |
|||
|
|||
|
|||
/** |
|||
* 基础xml |
|||
*/ |
|||
@Data |
|||
//@XmlRootElement(name = "xml")
|
|||
@XmlAccessorType(XmlAccessType.FIELD) |
|||
public class CallBackBaseXml { |
|||
|
|||
|
|||
private String ToUserName; //企业微信CorpID
|
|||
private String FromUserName; //成员UserID
|
|||
private String CreateTime; //消息创建时间(整型)
|
|||
private String MsgType; //消息类型,此时固定为:event
|
|||
private String AgentID; //企业应用的id,整型。可在应用的设置页面查看
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
package com.dreamchaser.depository_manage.pojo.callBackXml; |
|||
|
|||
|
|||
import lombok.Data; |
|||
|
|||
import javax.xml.bind.annotation.XmlRootElement; |
|||
|
|||
@Data |
|||
@XmlRootElement(name = "xml") |
|||
public class CallBackXMl_DLWZ extends CallBackBaseXml { |
|||
|
|||
/** |
|||
* 用于上报地理位置时的回调xml |
|||
*/ |
|||
|
|||
private String Event; // 事件类型,subscribe(关注)、unsubscribe(取消关注)
|
|||
private String EventKey; // 事件KEY值,此事件该值为空
|
|||
private String Latitude; //地理位置纬度
|
|||
private String Longitude; //地理位置经度
|
|||
private String Precision; // 地理位置精度
|
|||
private String AppType; // app类型,在企业微信固定返回wxwork,在微信不返回该字段
|
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
package com.dreamchaser.depository_manage.pojo.callBackXml; |
|||
|
|||
import com.dreamchaser.depository_manage.pojo.callBackXml.callBackXml_button_templatecard.TemplateCard; |
|||
import lombok.Data; |
|||
|
|||
import javax.xml.bind.annotation.XmlAccessType; |
|||
import javax.xml.bind.annotation.XmlAccessorType; |
|||
import javax.xml.bind.annotation.XmlRootElement; |
|||
|
|||
|
|||
@XmlAccessorType(XmlAccessType.FIELD) |
|||
@Data |
|||
public class CallBackXml_button extends CallBackBaseXml { |
|||
private TemplateCard TemplateCard; |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
package com.dreamchaser.depository_manage.pojo.callBackXml.callBackXml_button_templatecard; |
|||
|
|||
import com.dreamchaser.depository_manage.config.QyWx_template_card.BaseMessage; |
|||
import lombok.Data; |
|||
|
|||
/** |
|||
* 文本消息 |
|||
* |
|||
*/ |
|||
@Data |
|||
public class ButtonInteraction extends BaseMessage { |
|||
// 模板卡片
|
|||
private TemplateCard template_card; |
|||
// 否 表示是否是保密消息,0表示否,1表示是,默认0
|
|||
private int safe; |
|||
} |
|||
@ -0,0 +1,73 @@ |
|||
package com.dreamchaser.depository_manage.pojo.callBackXml.callBackXml_button_templatecard; |
|||
|
|||
import com.dreamchaser.depository_manage.pojo.callBackXml.CallBackBaseXml; |
|||
import lombok.Data; |
|||
|
|||
import javax.xml.bind.annotation.XmlAccessType; |
|||
import javax.xml.bind.annotation.XmlAccessorType; |
|||
import javax.xml.bind.annotation.XmlRootElement; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 卡片模板 |
|||
* |
|||
*/ |
|||
@XmlAccessorType(XmlAccessType.FIELD) |
|||
@Data |
|||
@XmlRootElement(name="xml")//根节点
|
|||
public class TemplateCard { |
|||
|
|||
private String ToUserName; //企业微信CorpID
|
|||
private String FromUserName; //成员UserID
|
|||
private String CreateTime; //消息创建时间(整型)
|
|||
private String MsgType; //消息类型,此时固定为:event
|
|||
private String AgentID; //企业应用的id,整型。可在应用的设置页面查看
|
|||
/** |
|||
* 模板卡片类型,投票选择型卡片填写"vote_interaction" |
|||
*/ |
|||
|
|||
private String CardType; |
|||
|
|||
/** |
|||
* 二级普通文本,建议不超过160个字,(支持id转译) |
|||
*/ |
|||
private String SubTitleText; |
|||
|
|||
|
|||
/** |
|||
* 响应事件key |
|||
*/ |
|||
private String EventKey; |
|||
|
|||
/** |
|||
* 任务id,同一个应用任务id不能重复,只能由数字、字母和“_-@”组成,最长128字节 |
|||
*/ |
|||
private String TaskId; |
|||
|
|||
/** |
|||
* 响应的事件类型 |
|||
*/ |
|||
private String Event; |
|||
|
|||
|
|||
/** |
|||
* 下拉式的选择器 |
|||
*/ |
|||
|
|||
private TemplateCard_SelectedItems SelectedItems; |
|||
|
|||
|
|||
/** |
|||
* 按钮替换文案,填写本字段后会展现灰色不可点击按钮 |
|||
*/ |
|||
private String ReplaceText; |
|||
|
|||
/** |
|||
* ResponseCode |
|||
*/ |
|||
private String ResponseCode; |
|||
|
|||
|
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
package com.dreamchaser.depository_manage.pojo.callBackXml.callBackXml_button_templatecard; |
|||
|
|||
|
|||
import lombok.Data; |
|||
|
|||
import javax.xml.bind.annotation.XmlAccessType; |
|||
import javax.xml.bind.annotation.XmlAccessorType; |
|||
import javax.xml.bind.annotation.XmlRootElement; |
|||
import java.util.List; |
|||
|
|||
@XmlAccessorType(XmlAccessType.FIELD) |
|||
@Data |
|||
//@XmlRootElement(name="SelectedItems")//根节点
|
|||
public class TemplateCard_SelectedItems { |
|||
TemplateCard_button_selection SelectedItem; |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
package com.dreamchaser.depository_manage.pojo.callBackXml.callBackXml_button_templatecard; |
|||
|
|||
import lombok.Data; |
|||
|
|||
/** |
|||
* 操作 |
|||
*/ |
|||
@Data |
|||
public class TemplateCard_action { |
|||
/** |
|||
* 操作的描述文案 |
|||
*/ |
|||
private String Text; |
|||
/** |
|||
* 操作key值,用户点击后,会产生回调事件将本参数作为EventKey返回,回调事件会带上该key值,最长支持1024字节,不可重复 |
|||
*/ |
|||
private String Key; |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
package com.dreamchaser.depository_manage.pojo.callBackXml.callBackXml_button_templatecard; |
|||
|
|||
import lombok.Data; |
|||
|
|||
import javax.xml.bind.annotation.XmlAccessType; |
|||
import javax.xml.bind.annotation.XmlAccessorType; |
|||
import javax.xml.bind.annotation.XmlRootElement; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 卡片右上角更多操作按钮 |
|||
*/ |
|||
@XmlAccessorType(XmlAccessType.FIELD) |
|||
@Data |
|||
@XmlRootElement(name = "ActionMenu") |
|||
public class TemplateCard_action_menu { |
|||
/** |
|||
* 更多操作界面的描述 |
|||
*/ |
|||
private String Desc; |
|||
|
|||
/** |
|||
* 操作列表,列表长度取值范围为 [1, 3] |
|||
*/ |
|||
private List<TemplateCard_action> ActionList; |
|||
|
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
package com.dreamchaser.depository_manage.pojo.callBackXml.callBackXml_button_templatecard; |
|||
|
|||
import lombok.Data; |
|||
|
|||
import javax.xml.bind.annotation.XmlAccessType; |
|||
import javax.xml.bind.annotation.XmlAccessorType; |
|||
|
|||
/** |
|||
* 按钮 |
|||
*/ |
|||
@XmlAccessorType(XmlAccessType.FIELD) |
|||
@Data |
|||
public class TemplateCard_button { |
|||
/** |
|||
* 按钮点击事件类型,0 或不填代表回调点击事件,1 代表跳转url |
|||
*/ |
|||
private Integer Yype; |
|||
/** |
|||
* 按钮文案,建议不超过10个字 |
|||
*/ |
|||
private String Text; |
|||
/** |
|||
* 按钮样式,目前可填1~4,不填或错填默认1 |
|||
*/ |
|||
private Integer Style; |
|||
/** |
|||
* 按钮key值,用户点击后,会产生回调事件将本参数作为EventKey返回,回调事件会带上该key值,最长支持1024字节,不可重复,button_list.type是0时必填 |
|||
*/ |
|||
private String Key; |
|||
/** |
|||
* 跳转事件的url,button_list.type是1时必填 |
|||
*/ |
|||
private String Url; |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
package com.dreamchaser.depository_manage.pojo.callBackXml.callBackXml_button_templatecard; |
|||
|
|||
import lombok.Data; |
|||
|
|||
import javax.xml.bind.annotation.XmlAccessType; |
|||
import javax.xml.bind.annotation.XmlAccessorType; |
|||
import javax.xml.bind.annotation.XmlRootElement; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 下拉式的选择器 |
|||
*/ |
|||
@XmlAccessorType(XmlAccessType.FIELD) |
|||
@Data |
|||
public class TemplateCard_button_selection { |
|||
/** |
|||
* 下拉式的选择器的key,用户提交选项后,会产生回调事件,回调事件会带上该key值表示该题,最长支持1024字节 |
|||
*/ |
|||
private String QuestionKey; |
|||
|
|||
/** |
|||
* 下拉式的选择器左边的标题 |
|||
*/ |
|||
private String Title; |
|||
|
|||
/** |
|||
* 选项列表,下拉选项不超过 10 个,最少1个 |
|||
*/ |
|||
private TemplateCard_button_selection_option OptionIds; |
|||
|
|||
/** |
|||
* 下拉式的选择器默认选定的选项 |
|||
*/ |
|||
private String SelectedId; |
|||
|
|||
/** |
|||
* 是否可以选择状态 |
|||
*/ |
|||
private Boolean Disable; |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
package com.dreamchaser.depository_manage.pojo.callBackXml.callBackXml_button_templatecard; |
|||
|
|||
import lombok.Data; |
|||
|
|||
import javax.xml.bind.annotation.XmlAccessType; |
|||
import javax.xml.bind.annotation.XmlAccessorType; |
|||
import javax.xml.bind.annotation.XmlRootElement; |
|||
|
|||
/** |
|||
* 下拉选项 |
|||
*/ |
|||
@Data |
|||
@XmlAccessorType(XmlAccessType.FIELD) |
|||
public class TemplateCard_button_selection_option { |
|||
/** |
|||
* 下拉式的选择器选项的id,用户提交后,会产生回调事件,回调事件会带上该id值表示该选项,最长支持128字节,不可重复 |
|||
*/ |
|||
private String OptionId; |
|||
/** |
|||
* 下拉式的选择器选项的文案,建议不超过16个字 |
|||
*/ |
|||
private String Text; |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
package com.dreamchaser.depository_manage.pojo.callBackXml.callBackXml_button_templatecard; |
|||
|
|||
import lombok.Data; |
|||
|
|||
import javax.xml.bind.annotation.XmlAccessType; |
|||
import javax.xml.bind.annotation.XmlAccessorType; |
|||
|
|||
|
|||
/** |
|||
* 整体卡片的点击跳转事件 |
|||
*/ |
|||
@XmlAccessorType(XmlAccessType.FIELD) |
|||
@Data |
|||
public class TemplateCard_card_action { |
|||
/** |
|||
* 跳转事件类型,0或不填代表不是链接,1 代表跳转url,2 代表打开小程序 |
|||
*/ |
|||
private Integer Type; |
|||
|
|||
/** |
|||
* 跳转事件的url,card_action.type是1时必填 |
|||
*/ |
|||
private String Url; |
|||
|
|||
/** |
|||
* 跳转事件的小程序的appid,必须是与当前应用关联的小程序,card_action.type是2时必填 |
|||
*/ |
|||
private Integer AppId; |
|||
|
|||
/** |
|||
* 跳转事件的小程序的pagepath,card_action.type是2时选填 |
|||
*/ |
|||
private String PagePath; |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
package com.dreamchaser.depository_manage.pojo.callBackXml.callBackXml_button_templatecard; |
|||
|
|||
import lombok.Data; |
|||
|
|||
import javax.xml.bind.annotation.XmlAccessType; |
|||
import javax.xml.bind.annotation.XmlAccessorType; |
|||
|
|||
/** |
|||
* 二级标题+文本列表 |
|||
*/ |
|||
@XmlAccessorType(XmlAccessType.FIELD) |
|||
@Data |
|||
public class TemplateCard_horizontal_content { |
|||
/** |
|||
* 链接类型,0或不填代表不是链接,1 代表跳转url,2 代表下载附件,3 代表点击跳转成员详情 |
|||
*/ |
|||
private Integer Type; |
|||
|
|||
/** |
|||
* 二级标题,建议不超过5个字 |
|||
*/ |
|||
private String KeyName; |
|||
|
|||
/** |
|||
* 二级文本,如果horizontal_content_list.type是2,该字段代表文件名称(要包含文件类型),建议不超过30个字,(支持id转译) |
|||
*/ |
|||
private String Value; |
|||
|
|||
/** |
|||
* 链接跳转的url,horizontal_content_list.type是1时必填 |
|||
*/ |
|||
private String Url; |
|||
|
|||
/** |
|||
* 附件的media_id,horizontal_content_list.type是2时必填 |
|||
*/ |
|||
private Integer MediaId; |
|||
|
|||
|
|||
/** |
|||
* 成员详情的userid,horizontal_content_list.type是3时必填 |
|||
*/ |
|||
private String UserId; |
|||
|
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
package com.dreamchaser.depository_manage.pojo.callBackXml.callBackXml_button_templatecard; |
|||
|
|||
import lombok.Data; |
|||
|
|||
import javax.xml.bind.annotation.XmlAccessType; |
|||
import javax.xml.bind.annotation.XmlAccessorType; |
|||
import javax.xml.bind.annotation.XmlRootElement; |
|||
|
|||
|
|||
/** |
|||
* 一级标题 |
|||
*/ |
|||
@XmlAccessorType(XmlAccessType.FIELD) |
|||
@Data |
|||
@XmlRootElement(name = "MainTitle") |
|||
public class TemplateCard_main_title { |
|||
/** |
|||
* 一级标题,建议不超过36个字,(支持id转译) |
|||
*/ |
|||
private String Title; |
|||
/** |
|||
* 标题辅助信息,建议不超过44个字,(支持id转译) |
|||
*/ |
|||
private String Desc; |
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
package com.dreamchaser.depository_manage.pojo.callBackXml.callBackXml_button_templatecard; |
|||
|
|||
import lombok.Data; |
|||
|
|||
import javax.xml.bind.annotation.XmlAccessType; |
|||
import javax.xml.bind.annotation.XmlAccessorType; |
|||
import javax.xml.bind.annotation.XmlRootElement; |
|||
|
|||
/* |
|||
卡片来源样式信息 |
|||
*/ |
|||
@XmlAccessorType(XmlAccessType.FIELD) |
|||
@Data |
|||
@XmlRootElement(name = "Source") |
|||
public class TemplateCard_source { |
|||
/** |
|||
* 来源图片的url,来源图片的尺寸建议为72*72 |
|||
*/ |
|||
private String IconUrl; |
|||
|
|||
/** |
|||
* 来源图片的描述,建议不超过20个字,(支持id转译) |
|||
*/ |
|||
private String Desc; |
|||
|
|||
/** |
|||
* 来源文字的颜色,目前支持:0(默认) 灰色,1 黑色,2 红色,3 绿色 |
|||
*/ |
|||
private Integer DescColor; |
|||
|
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
package com.dreamchaser.depository_manage.pojo.callBackXml.callBackXml_button_templatecard; |
|||
|
|||
import lombok.Data; |
|||
|
|||
import javax.xml.bind.annotation.XmlAccessType; |
|||
import javax.xml.bind.annotation.XmlAccessorType; |
|||
|
|||
/** |
|||
* 引用文献样式 |
|||
*/ |
|||
@XmlAccessorType(XmlAccessType.FIELD) |
|||
@Data |
|||
public class Template_quote_area { |
|||
/** |
|||
* 引用文献样式区域点击事件,0或不填代表没有点击事件,1 代表跳转url,2 代表跳转小程序 |
|||
*/ |
|||
private Integer Type; |
|||
/** |
|||
* 点击跳转的url,quote_area.type是1时必填 |
|||
*/ |
|||
private String Url; |
|||
/** |
|||
* 点击跳转的小程序的appid,必须是与当前应用关联的小程序,quote_area.type是2时必填 |
|||
*/ |
|||
private Integer Appid; |
|||
/** |
|||
* 点击跳转的小程序的pagepath,quote_area.type是2时选填 |
|||
*/ |
|||
private String PagePath; |
|||
/** |
|||
* 引用文献样式的标题 |
|||
*/ |
|||
private String Title; |
|||
/** |
|||
* 引用文献样式的引用文案 |
|||
*/ |
|||
private String QuoteText; |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
package com.dreamchaser.depository_manage.security.pool; |
|||
|
|||
|
|||
import com.dreamchaser.depository_manage.entity.UserByPort; |
|||
|
|||
import java.util.Map; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
|
|||
/** |
|||
* 用户登录后的key与token池 |
|||
*/ |
|||
public class UserKeyAndTokenPool { |
|||
private static Map<String, String> pool = new ConcurrentHashMap<>(200); |
|||
|
|||
|
|||
/** |
|||
* 用于暂存当前用户的key和token |
|||
* @param key 用户工号 |
|||
* @param keyAndToken key+token => key & token |
|||
*/ |
|||
public static void addKeyAndToken(String key, String keyAndToken){ |
|||
pool.put(key, keyAndToken); |
|||
} |
|||
|
|||
public static String getKeyAndToken(String key){ |
|||
String keyAndToken = pool.get(key); |
|||
return keyAndToken; |
|||
} |
|||
|
|||
public static void removeKeyAndToken(String key){ |
|||
pool.remove(key); |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
package com.dreamchaser.depository_manage.service; |
|||
|
|||
import com.dreamchaser.depository_manage.entity.CallBackLog; |
|||
|
|||
import java.util.Map; |
|||
|
|||
public interface CallBackLogService { |
|||
|
|||
/** |
|||
* 添加回调日志 |
|||
* @param map |
|||
* @return |
|||
*/ |
|||
Integer addCallBackLog(Map<String,Object> map); |
|||
/** |
|||
* 添加回调日志 |
|||
* @param callBackLog |
|||
* @return |
|||
*/ |
|||
Integer addCallBackLog(CallBackLog callBackLog); |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
package com.dreamchaser.depository_manage.service.impl; |
|||
|
|||
import com.dreamchaser.depository_manage.entity.CallBackLog; |
|||
import com.dreamchaser.depository_manage.mapper.CallBackLogMapper; |
|||
import com.dreamchaser.depository_manage.service.CallBackLogService; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Service; |
|||
|
|||
import java.util.Map; |
|||
|
|||
@Service |
|||
public class CallBackLogServiceImpl implements CallBackLogService { |
|||
/** |
|||
* 添加回调日志 |
|||
* @param map |
|||
* @return |
|||
*/ |
|||
@Autowired |
|||
CallBackLogMapper callBackLogMapper; |
|||
|
|||
@Override |
|||
public Integer addCallBackLog(Map<String, Object> map) { |
|||
return callBackLogMapper.addCallBackLog(map); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 添加回调日志 |
|||
* @param callBackLog |
|||
* @return |
|||
*/ |
|||
public Integer addCallBackLog(CallBackLog callBackLog){ |
|||
return callBackLogMapper.addCallBackLog(callBackLog); |
|||
} |
|||
} |
|||
@ -0,0 +1,497 @@ |
|||
package com.dreamchaser.depository_manage.service.impl; |
|||
|
|||
import cn.hutool.core.lang.Snowflake; |
|||
import com.alibaba.fastjson.JSON; |
|||
import com.alibaba.fastjson.JSONObject; |
|||
import com.dreamchaser.depository_manage.config.QyWxConfig; |
|||
import com.dreamchaser.depository_manage.config.QyWx_template_card.*; |
|||
import com.dreamchaser.depository_manage.controller.PageController; |
|||
import com.dreamchaser.depository_manage.entity.*; |
|||
import com.dreamchaser.depository_manage.mapper.DepositoryMapper; |
|||
import com.dreamchaser.depository_manage.mapper.DepositoryRecordMapper; |
|||
import com.dreamchaser.depository_manage.mapper.MaterialMapper; |
|||
import com.dreamchaser.depository_manage.pojo.ApplicationOutRecordP; |
|||
import com.dreamchaser.depository_manage.pojo.callBackXml.callBackXml_button_templatecard.TemplateCard; |
|||
import com.dreamchaser.depository_manage.utils.DateUtil; |
|||
import com.dreamchaser.depository_manage.utils.HttpUtils; |
|||
import org.apache.poi.ss.formula.functions.T; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.data.redis.core.RedisTemplate; |
|||
import org.springframework.stereotype.Service; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.HashMap; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
|
|||
|
|||
@Service |
|||
public class QyWxOperationService { |
|||
|
|||
|
|||
@Autowired |
|||
DepositoryRecordMapper depositoryRecordMapper; |
|||
|
|||
@Autowired |
|||
MaterialMapper materialMapper; |
|||
|
|||
@Autowired |
|||
DepositoryMapper depositoryMapper; |
|||
|
|||
@Autowired |
|||
RedisTemplate<String,String> redisTemplate; |
|||
|
|||
/** |
|||
* 用于向企业微信发送消息 |
|||
* @param uid 接收人 |
|||
* @param outId 申请出库编号 |
|||
* @param flag 用于判断发送类型是部门负责人还是仓储负责人(true为部门,false为仓储) |
|||
* @return |
|||
*/ |
|||
public JSONObject sendQyWxMessage(String uid, Integer outId, Boolean flag) { |
|||
|
|||
// 获取将要发送申请的订单记录
|
|||
ApplicationOutRecordP applicationOutRecordPById = depositoryRecordMapper.findApplicationOutRecordPById(outId); |
|||
// 申请人id
|
|||
Integer applicantId = applicationOutRecordPById.getApplicantId(); |
|||
// 申请人
|
|||
UserByPort applicant = PageController.FindUserById(applicantId, null); |
|||
|
|||
// 获取所有子订单
|
|||
List<ApplicationOutRecordMin> applicationOutRecordMinByParent = depositoryRecordMapper.findApplicationOutRecordMinByParent(outId); |
|||
StringBuilder mname = new StringBuilder(); |
|||
StringBuilder depositoryName = new StringBuilder(); |
|||
StringBuilder sumQuantity = new StringBuilder(); |
|||
for (int i = 0; i < applicationOutRecordMinByParent.size(); i++) { |
|||
// 获取子订单信息
|
|||
ApplicationOutRecordMin applicationOutRecordMin = applicationOutRecordMinByParent.get(i); |
|||
// 获取当前申请物料
|
|||
Material materialById = materialMapper.findMaterialById(applicationOutRecordMin.getMid()); |
|||
// 获取当前物料所在仓库
|
|||
Depository depositoryRecordById = depositoryMapper.findDepositoryRecordById(materialById.getDepositoryId()); |
|||
sumQuantity.append(applicationOutRecordMin.getQuantity()).append(","); |
|||
mname.append(materialById.getMname()).append(","); |
|||
depositoryName.append(depositoryRecordById.getDname()).append(","); |
|||
} |
|||
|
|||
// 1.获取access_token:根据企业id和应用密钥获取access_token,并拼接请求url
|
|||
String accessToken = "".equals(QyWxConfig.token)?QyWxConfig.GetQYWXToken():QyWxConfig.token; |
|||
// 2.获取发送对象,并转成json
|
|||
ButtonInteraction buttonInteraction = new ButtonInteraction(); |
|||
// 1.1非必需
|
|||
//设置消息接收者
|
|||
String[] split = uid.split(","); |
|||
StringBuilder toUserName = new StringBuilder(); |
|||
for (int i = 0; i < split.length - 1; i++) { |
|||
toUserName.append(split[i]).append("|"); |
|||
} |
|||
toUserName.append(split[split.length - 1]); |
|||
buttonInteraction.setTouser(toUserName.toString()); // 不区分大小写
|
|||
|
|||
// 1.2必需
|
|||
// 消息类型
|
|||
buttonInteraction.setMsgtype("template_card"); |
|||
// 企业应用的id,整型
|
|||
buttonInteraction.setAgentid(QyWxConfig.AgentId); |
|||
// 卡片模板
|
|||
TemplateCard_button_interaction templateCard_button_interaction = new TemplateCard_button_interaction(); |
|||
// 模板卡片类型,按钮交互型卡片填写"button_interaction"
|
|||
templateCard_button_interaction.setCard_type("button_interaction"); |
|||
// 卡片右上角更多操作按钮
|
|||
TemplateCard_action_menu action_menu = new TemplateCard_action_menu(); |
|||
action_menu.setDesc("卡片副交互辅助文本说明"); |
|||
|
|||
// 卡片右上角操作按钮
|
|||
TemplateCard_action action1 = new TemplateCard_action(); |
|||
action1.setKey("AcceptThePush"); |
|||
action1.setText("接受推送"); |
|||
TemplateCard_action action2 = new TemplateCard_action(); |
|||
action2.setKey("NoPush"); |
|||
action2.setText("不再推送"); |
|||
List<TemplateCard_action> actionList = new ArrayList<>(); |
|||
actionList.add(action1); |
|||
actionList.add(action2); |
|||
action_menu.setAction_list(actionList); |
|||
// 设置操作按钮
|
|||
templateCard_button_interaction.setAction_menu(action_menu); |
|||
|
|||
// 一级标题
|
|||
TemplateCard_main_title main_title = new TemplateCard_main_title(); |
|||
// main_title.setTitle(applicant+"的出库申请");
|
|||
main_title.setTitle(applicant.getName()+"的出库申请"); |
|||
main_title.setDesc("申请时间:"+ DateUtil.TimeStampToDateTime(Long.valueOf(applicationOutRecordPById.getApplicantTime()))); |
|||
|
|||
// 设置一级标题
|
|||
templateCard_button_interaction.setMain_title(main_title); |
|||
|
|||
// 二级标题+文本列表,用于设置物料名称
|
|||
TemplateCard_horizontal_content horizontal_content_mname = new TemplateCard_horizontal_content(); |
|||
// 链接类型 0代表不是链接
|
|||
horizontal_content_mname.setType(0); |
|||
// 二级标题
|
|||
horizontal_content_mname.setKeyname("申请物料:"); |
|||
horizontal_content_mname.setValue(mname.toString()); |
|||
|
|||
// 二级标题+文本列表,用于设置物料数量
|
|||
TemplateCard_horizontal_content horizontal_content_quantity = new TemplateCard_horizontal_content(); |
|||
// 链接类型 0代表不是链接
|
|||
horizontal_content_quantity.setType(0); |
|||
// 二级标题
|
|||
horizontal_content_quantity.setKeyname("数量"); |
|||
horizontal_content_quantity.setValue(sumQuantity.toString()); |
|||
|
|||
// 二级标题+文本列表,用于设置物料对应仓库
|
|||
TemplateCard_horizontal_content horizontal_content_depositoryName = new TemplateCard_horizontal_content(); |
|||
// 链接类型 0代表不是链接
|
|||
horizontal_content_depositoryName.setType(0); |
|||
// 二级标题
|
|||
horizontal_content_depositoryName.setKeyname("仓库名称"); |
|||
horizontal_content_depositoryName.setValue(depositoryName.toString()); |
|||
|
|||
|
|||
// 二级标题+文本列表,用于设置申请查看明细
|
|||
TemplateCard_horizontal_content horizontal_content_detail = new TemplateCard_horizontal_content(); |
|||
// 链接类型 0代表不是链接
|
|||
horizontal_content_detail.setType(1); |
|||
// 二级标题
|
|||
horizontal_content_detail.setKeyname("申请明细"); |
|||
horizontal_content_detail.setValue("查看明细"); |
|||
horizontal_content_detail.setUrl("https://jy.hxgk.group/ApplicationOutView?id="+outId); |
|||
|
|||
|
|||
List<TemplateCard_horizontal_content> horizontal_contentList = new ArrayList<>(); |
|||
horizontal_contentList.add(horizontal_content_mname); |
|||
horizontal_contentList.add(horizontal_content_quantity); |
|||
horizontal_contentList.add(horizontal_content_depositoryName); |
|||
horizontal_contentList.add(horizontal_content_detail); |
|||
|
|||
// 设置二级标题
|
|||
templateCard_button_interaction.setHorizontal_content_list(horizontal_contentList); |
|||
|
|||
|
|||
// 任务id,同一个应用任务id不能重复,只能由数字、字母和“_-@”组成,最长128字节
|
|||
// 通过雪花算法获取taskId
|
|||
Snowflake snowflake = new Snowflake(10,10,true); |
|||
templateCard_button_interaction.setTask_id(snowflake.nextIdStr()); |
|||
|
|||
// 下拉式的选择器
|
|||
TemplateCard_button_selection button_selection = new TemplateCard_button_selection(); |
|||
// 下拉式的选择器的key
|
|||
button_selection.setQuestion_key("btn_status"); |
|||
button_selection.setTitle("您的身份"); |
|||
|
|||
List<TemplateCard_button_selection_option> optionList = new ArrayList<>(); |
|||
// 选项
|
|||
TemplateCard_button_selection_option button_selection_option = new TemplateCard_button_selection_option(); |
|||
button_selection_option.setText("部门负责人"); |
|||
if(flag) { |
|||
// 如果是发送给部门负责人
|
|||
button_selection_option.setId("btn_status_departManagerHead"); |
|||
}else{ |
|||
// 如果是发送给仓储负责人
|
|||
button_selection_option.setId("btn_status_depositoryManager"); |
|||
} |
|||
optionList.add(button_selection_option); |
|||
|
|||
button_selection.setOption_list(optionList); |
|||
|
|||
templateCard_button_interaction.setButton_selection(button_selection); |
|||
|
|||
// 按钮列表,列表长度不超过6
|
|||
List<TemplateCard_button> buttonList = new ArrayList<>(); |
|||
|
|||
TemplateCard_button button1 = new TemplateCard_button(); |
|||
button1.setKey("wms_pass_outId"+outId); |
|||
button1.setStyle(1); |
|||
button1.setText("通过"); |
|||
|
|||
TemplateCard_button button2 = new TemplateCard_button(); |
|||
button2.setKey("wms_reject_outId"+outId); |
|||
button2.setStyle(2); |
|||
button2.setText("驳回"); |
|||
|
|||
buttonList.add(button1); |
|||
buttonList.add(button2); |
|||
|
|||
templateCard_button_interaction.setButton_list(buttonList); |
|||
|
|||
buttonInteraction.setTemplate_card(templateCard_button_interaction); |
|||
|
|||
String s = JSONObject.toJSONString(buttonInteraction); |
|||
|
|||
// 3.获取请求的url
|
|||
String url = QyWxConfig.sendMessage_url.replace("ACCESS_TOKEN", accessToken); |
|||
|
|||
// 4.调用接口,发送消息
|
|||
String s1 = HttpUtils.doPost(url, s); |
|||
|
|||
// 将返回结果转为json对象
|
|||
JSONObject jsonObject = JSON.parseObject(s1); |
|||
|
|||
// 返回
|
|||
return jsonObject; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 将最终完成的订单抄送给仓储负责人 |
|||
* @param uid 仓储负责人编号 |
|||
* @param outId 订单编号 |
|||
* @return |
|||
*/ |
|||
public JSONObject sendCcMessageToUsers(String uid,Integer outId){ |
|||
// 获取已经完成的订单
|
|||
ApplicationOutRecordP recordP = depositoryRecordMapper.findApplicationOutRecordPById(outId); |
|||
// 申请人id
|
|||
Integer applicantId = recordP.getApplicantId(); |
|||
// 申请人
|
|||
UserByPort applicant = PageController.FindUserById(applicantId, null); |
|||
// 获取所有子订单
|
|||
List<ApplicationOutRecordMin> applicationOutRecordMinByParent = depositoryRecordMapper.findApplicationOutRecordMinByParent(outId); |
|||
|
|||
MessageByMarkDown markDown = new MessageByMarkDown(); |
|||
//设置消息接收者
|
|||
String[] split = uid.split(","); |
|||
StringBuilder toUserName = new StringBuilder(); |
|||
for (int i = 0; i < split.length - 1; i++) { |
|||
toUserName.append(split[i]).append("|"); |
|||
} |
|||
toUserName.append(split[split.length - 1]); |
|||
markDown.setTouser(toUserName.toString()); // 不区分大小写
|
|||
|
|||
|
|||
|
|||
// 设置agentId
|
|||
markDown.setAgentid(QyWxConfig.AgentId); |
|||
markDown.setMsgtype("markdown"); |
|||
|
|||
|
|||
// 设置content
|
|||
Map<String,String> markdown = new HashMap<>(); |
|||
StringBuilder content = new StringBuilder("## `抄送信息:`%n"); |
|||
content.append(">### **"+applicant.getName()+"的出库申请** %n<font color='warning'>申请时间:2022-10-23 14:30:18</font> %n"); |
|||
content.append("%n---%n"); |
|||
for (ApplicationOutRecordMin recordMin : applicationOutRecordMinByParent) { |
|||
// 获取子订单信息
|
|||
// 获取申请物料信息
|
|||
Material materialById = materialMapper.findMaterialById(recordMin.getMid()); |
|||
// 获取仓库信息
|
|||
Depository depositoryRecordById = depositoryMapper.findDepositoryRecordById(recordMin.getDepositoryId()); |
|||
// 获取处理人信息
|
|||
UserByPort userByPort = PageController.FindUserById(recordMin.getCheckId(), null); |
|||
content.append(">- 物料名称:").append(materialById.getMname()).append("%n"); |
|||
content.append(">- 申请数量:").append(recordMin.getQuantity()).append("%n"); |
|||
content.append(">- 所处仓库:").append(depositoryRecordById.getDname()).append("%n"); |
|||
content.append(">- 出库人员:").append(userByPort.getName()).append("%n"); |
|||
content.append("%n---%n"); |
|||
} |
|||
content.append(">## '''%n" + |
|||
">如需要查看详细信息,请点击:[查看信息](https://jy.hxgk.group/ApplicationOutView?id="+recordP.getId()+")"); |
|||
markdown.put("content",content.toString()); |
|||
markDown.setMarkdown(markdown); |
|||
String jsonString = JSONObject.toJSONString(markDown); |
|||
jsonString = String.format(jsonString); |
|||
System.out.println(jsonString); |
|||
// 3.获取请求的url
|
|||
// 获取access_token:根据企业id和应用密钥获取access_token,并拼接请求url
|
|||
String accessToken = "".equals(QyWxConfig.token)?QyWxConfig.GetQYWXToken():QyWxConfig.token; |
|||
String url = QyWxConfig.sendMessage_url.replace("ACCESS_TOKEN", accessToken); |
|||
|
|||
// 4.调用接口,发送消息
|
|||
String s1 = HttpUtils.doPost(url, jsonString); |
|||
|
|||
// 将返回结果转为json对象
|
|||
JSONObject jsonObject = JSON.parseObject(s1); |
|||
|
|||
// 返回
|
|||
return jsonObject; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 给仓库管理员发送出库通知 |
|||
* @param uid |
|||
* @param outMinId |
|||
* @return |
|||
*/ |
|||
public JSONObject sendNotificationToDepositoryManager(String uid,Integer outMinId){ |
|||
// 需要出库的子订单
|
|||
ApplicationOutRecordMin recordMin = depositoryRecordMapper.findApplicationOutMinById(outMinId); |
|||
// 获取其主订单
|
|||
ApplicationOutRecordP outRecordP = depositoryRecordMapper.findApplicationOutRecordPById(recordMin.getParentId()); |
|||
// 申请人id
|
|||
Integer applicantId = outRecordP.getApplicantId(); |
|||
// 申请人
|
|||
UserByPort applicant = PageController.FindUserById(applicantId, null); |
|||
// 定义文本通知型卡片
|
|||
TextNotice textNotice = new TextNotice(); |
|||
|
|||
//设置消息接收者
|
|||
String[] split = uid.split(","); |
|||
StringBuilder toUserName = new StringBuilder(); |
|||
for (int i = 0; i < split.length - 1; i++) { |
|||
toUserName.append(split[i]).append("|"); |
|||
} |
|||
toUserName.append(split[split.length - 1]); |
|||
textNotice.setTouser(toUserName.toString()); // 不区分大小写
|
|||
|
|||
|
|||
// 设置agentId
|
|||
textNotice.setAgentid(QyWxConfig.AgentId); |
|||
|
|||
// 定义卡片模板
|
|||
TemplateCard_text_notice text_notice = new TemplateCard_text_notice(); |
|||
text_notice.setCard_type("text_notice"); |
|||
|
|||
// 任务id,同一个应用任务id不能重复,只能由数字、字母和“_-@”组成,最长128字节
|
|||
// 通过雪花算法获取taskId
|
|||
Snowflake snowflake = new Snowflake(10,10,true); |
|||
text_notice.setTask_id(snowflake.nextIdStr()); |
|||
|
|||
textNotice.setMsgtype("template_card"); |
|||
|
|||
// 设置主标题
|
|||
TemplateCard_main_title main_title = new TemplateCard_main_title(); |
|||
main_title.setTitle(applicant.getName()+"的出库请求"); |
|||
main_title.setDesc("申请时间:"+ DateUtil.TimeStampToDateTime(Long.valueOf(outRecordP.getApplicantTime()))); |
|||
|
|||
text_notice.setMain_title(main_title); |
|||
// 卡片右上角更多操作按钮
|
|||
TemplateCard_action_menu action_menu = new TemplateCard_action_menu(); |
|||
action_menu.setDesc("卡片副交互辅助文本说明"); |
|||
|
|||
// 卡片右上角操作按钮
|
|||
TemplateCard_action action1 = new TemplateCard_action(); |
|||
action1.setKey("AcceptThePush"); |
|||
action1.setText("接受推送"); |
|||
TemplateCard_action action2 = new TemplateCard_action(); |
|||
action2.setKey("NoPush"); |
|||
action2.setText("不再推送"); |
|||
List<TemplateCard_action> actionList = new ArrayList<>(); |
|||
actionList.add(action1); |
|||
actionList.add(action2); |
|||
action_menu.setAction_list(actionList); |
|||
text_notice.setAction_menu(action_menu); |
|||
|
|||
|
|||
|
|||
List<TemplateCard_horizontal_content> horizontalContentList = new ArrayList<>(); |
|||
|
|||
// 获取申请物料信息
|
|||
Material materialById = materialMapper.findMaterialById(recordMin.getMid()); |
|||
// 获取仓库信息
|
|||
Depository depositoryRecordById = depositoryMapper.findDepositoryRecordById(recordMin.getDepositoryId()); |
|||
// 设至二级标题
|
|||
// 物料名称
|
|||
TemplateCard_horizontal_content horizontal_content_mname = new TemplateCard_horizontal_content(); |
|||
horizontal_content_mname.setType(0); |
|||
horizontal_content_mname.setKeyname("物料名称:"); |
|||
horizontal_content_mname.setValue(materialById.getMname()); |
|||
// 物料编码
|
|||
TemplateCard_horizontal_content horizontal_content_mcode = new TemplateCard_horizontal_content(); |
|||
horizontal_content_mcode.setType(0); |
|||
horizontal_content_mcode.setKeyname("物料编码:"); |
|||
horizontal_content_mcode.setValue(materialById.getCode().toString()); |
|||
// 申请数量
|
|||
TemplateCard_horizontal_content horizontal_content_quantity = new TemplateCard_horizontal_content(); |
|||
horizontal_content_quantity.setType(0); |
|||
horizontal_content_quantity.setKeyname("申请数量:"); |
|||
horizontal_content_quantity.setValue(recordMin.getQuantity().toString()); |
|||
// 所在仓库
|
|||
TemplateCard_horizontal_content horizontal_content_depository = new TemplateCard_horizontal_content(); |
|||
horizontal_content_depository.setType(0); |
|||
horizontal_content_depository.setKeyname("所在仓库:"); |
|||
horizontal_content_depository.setValue(depositoryRecordById.getDname()); |
|||
|
|||
// 申请备注
|
|||
TemplateCard_horizontal_content horizontal_content_applyRemark = new TemplateCard_horizontal_content(); |
|||
horizontal_content_applyRemark.setType(0); |
|||
horizontal_content_applyRemark.setKeyname("申请备注:"); |
|||
horizontal_content_applyRemark.setValue(outRecordP.getApplyRemark()); |
|||
|
|||
horizontalContentList.add(horizontal_content_mname); |
|||
horizontalContentList.add(horizontal_content_mcode); |
|||
horizontalContentList.add(horizontal_content_quantity); |
|||
horizontalContentList.add(horizontal_content_depository); |
|||
horizontalContentList.add(horizontal_content_applyRemark); |
|||
|
|||
text_notice.setHorizontal_content_list(horizontalContentList); |
|||
|
|||
// 卡片整体点击事件
|
|||
TemplateCard_card_action card_action = new TemplateCard_card_action(); |
|||
card_action.setType(1); |
|||
card_action.setUrl("https://jy.hxgk.group/ApplicationOutMinByDidForMobile?depositoryId="+depositoryRecordById.getId()+"&state=0"); |
|||
text_notice.setCard_action(card_action); |
|||
|
|||
//跳转指引样式的列表
|
|||
List<TemplateCard_jump> jumpList = new ArrayList<>(); |
|||
TemplateCard_jump jump = new TemplateCard_jump(); |
|||
jump.setType("1"); |
|||
jump.setUrl("https://jy.hxgk.group"); |
|||
jump.setTitle("进入系统"); |
|||
jumpList.add(jump); |
|||
|
|||
text_notice.setJump_list(jumpList); |
|||
|
|||
textNotice.setTemplate_card(text_notice); |
|||
String s = JSONObject.toJSONString(textNotice); |
|||
|
|||
// 3.获取请求的url
|
|||
// 获取access_token:根据企业id和应用密钥获取access_token,并拼接请求url
|
|||
String accessToken = "".equals(QyWxConfig.token)?QyWxConfig.GetQYWXToken():QyWxConfig.token; |
|||
String url = QyWxConfig.sendMessage_url.replace("ACCESS_TOKEN", accessToken); |
|||
|
|||
// 4.调用接口,发送消息
|
|||
String s1 = HttpUtils.doPost(url, s); |
|||
|
|||
// 将返回结果转为json对象
|
|||
JSONObject jsonObject = JSON.parseObject(s1); |
|||
|
|||
// 返回
|
|||
return jsonObject; |
|||
} |
|||
/** |
|||
* 用于撤回发送给企业微信的消息 |
|||
* @param msgid 待撤回消息的id |
|||
* @return |
|||
*/ |
|||
public String withdrawQyWxMessage(String msgid){ |
|||
String url = String.format("https://qyapi.weixin.qq.com/cgi-bin/message/recall?access_token=%s", QyWxConfig.token); |
|||
Map<String,Object> param = new HashMap<>(); |
|||
param.put("msgid",msgid); |
|||
String jsonString = JSONObject.toJSONString(param); |
|||
String post = HttpUtils.doPost(url, jsonString); |
|||
JSONObject jsonObject = JSON.parseObject(post); |
|||
String errmsg = jsonObject.getString("errmsg"); |
|||
Integer errcode = jsonObject.getInteger("errcode"); |
|||
if(errcode == 0){ |
|||
// 如果撤回成功
|
|||
return errmsg; |
|||
}else{ |
|||
return errmsg; |
|||
} |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 用于更新卡片中的按钮为不可点击状态 |
|||
* @param response_code |
|||
* @return |
|||
*/ |
|||
public JSONObject updateTemplateCard(String response_code,String userName,String state){ |
|||
String url = String.format("https://qyapi.weixin.qq.com/cgi-bin/message/update_template_card?access_token="+QyWxConfig.token+"&debug=1"); |
|||
Map<String,Object> map = new HashMap<>(); |
|||
map.put("atall",1); |
|||
map.put("agentid",QyWxConfig.AgentId); |
|||
map.put("response_code",response_code); |
|||
Map<String,Object> button = new HashMap<>(); |
|||
button.put("replace_name",userName+"已"+state); |
|||
map.put("button",button); |
|||
String jsonString = JSONObject.toJSONString(map); |
|||
String s1 = HttpUtils.doPost(url, jsonString); |
|||
JSONObject jsonObject = JSON.parseObject(s1); |
|||
return jsonObject; |
|||
} |
|||
|
|||
|
|||
} |
|||
@ -1,4 +1,121 @@ |
|||
package com.dreamchaser.depository_manage.utils; |
|||
|
|||
import com.dreamchaser.depository_manage.config.QyWxConfig; |
|||
import org.apache.commons.lang.StringEscapeUtils; |
|||
import org.dom4j.Document; |
|||
import org.dom4j.DocumentHelper; |
|||
import org.dom4j.Element; |
|||
import org.dom4j.io.OutputFormat; |
|||
|
|||
import javax.xml.bind.JAXBContext; |
|||
import javax.xml.bind.JAXBException; |
|||
import javax.xml.bind.Marshaller; |
|||
import javax.xml.bind.Unmarshaller; |
|||
import java.io.FileOutputStream; |
|||
import java.io.StringReader; |
|||
import java.io.StringWriter; |
|||
|
|||
/** |
|||
* 用于企业微信xml的工具类 |
|||
*/ |
|||
|
|||
|
|||
|
|||
public class QyWxXMLUtils { |
|||
|
|||
/** |
|||
* 用于拼接回调post请求时的xml |
|||
* @param Encrypt |
|||
* @return |
|||
*/ |
|||
public static String formatDataToXml(String Encrypt){ |
|||
// 创建document对象
|
|||
Document document = DocumentHelper.createDocument(); |
|||
// 创建根节点xml
|
|||
Element root = document.addElement("xml"); |
|||
// 4、生成子节点及子节点内容
|
|||
Element ToUserName = root.addElement("ToUserName"); |
|||
ToUserName.setText("<![CDATA["+QyWxConfig.corpid+"]]>"); |
|||
|
|||
Element agentID = root.addElement("AgentID"); |
|||
agentID.setText("<![CDATA["+QyWxConfig.AgentId+"]]>"); |
|||
|
|||
Element encrypt = root.addElement("Encrypt"); |
|||
encrypt.setText("<![CDATA["+Encrypt+"]]>"); |
|||
// 5、设置生成xml的格式
|
|||
OutputFormat format = OutputFormat.createPrettyPrint(); |
|||
// 设置编码格式
|
|||
format.setEncoding("UTF-8"); |
|||
String xml = document.asXML(); |
|||
xml = QyWxXMLUtils.dealxmlHeader(xml); |
|||
xml = StringEscapeUtils.unescapeXml(xml); |
|||
return xml; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 用于删除企业微信中的头标识 |
|||
* @param xmlText |
|||
* @return |
|||
*/ |
|||
public static String dealxmlHeader(String xmlText) |
|||
{ |
|||
StringBuilder sb=new StringBuilder(xmlText); |
|||
StringBuilder newsb=new StringBuilder(""); |
|||
String startstring=sb.substring(0,xmlText.indexOf("<?xml")); |
|||
String endstring=sb.substring(xmlText.indexOf("?>") + 2,xmlText.length()); |
|||
newsb.append(startstring+endstring); |
|||
xmlText=new String(newsb.toString()); |
|||
return xmlText; |
|||
|
|||
} |
|||
|
|||
|
|||
/** |
|||
* 将String类型的xml转换成对象 |
|||
* @param clazz |
|||
* @param xmlStr |
|||
* @return |
|||
*/ |
|||
public static Object convertXmlStrToObject(Class clazz, String xmlStr) { |
|||
Object xmlObject = null; |
|||
try { |
|||
JAXBContext context = JAXBContext.newInstance(clazz); |
|||
// 进行将Xml转成对象的核心接口
|
|||
Unmarshaller unmarshaller = context.createUnmarshaller(); |
|||
StringReader sr = new StringReader(xmlStr); |
|||
xmlObject = unmarshaller.unmarshal(sr); |
|||
} catch (JAXBException e) { |
|||
e.printStackTrace(); |
|||
} |
|||
return xmlObject; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 将对象直接转换成String类型的 XML输出 |
|||
* |
|||
* @param obj |
|||
* @return |
|||
*/ |
|||
public static String convertToXml(Object obj) { |
|||
// 创建输出流
|
|||
StringWriter sw = new StringWriter(); |
|||
try { |
|||
// 利用jdk中自带的转换类实现
|
|||
JAXBContext context = JAXBContext.newInstance(obj.getClass()); |
|||
|
|||
Marshaller marshaller = context.createMarshaller(); |
|||
// 格式化xml输出的格式
|
|||
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, |
|||
Boolean.TRUE); |
|||
// 将对象转换成输出流形式的xml
|
|||
marshaller.marshal(obj, sw); |
|||
} catch (JAXBException e) { |
|||
e.printStackTrace(); |
|||
} |
|||
return sw.toString(); |
|||
} |
|||
|
|||
|
|||
} |
|||
|
|||
@ -1,23 +1,87 @@ |
|||
{ |
|||
"code": 0, |
|||
"msg": "", |
|||
"count": 16, |
|||
"data": [ |
|||
{ "id":"001", "username":"张玉林", "sex":"女" }, |
|||
{ "id":"002", "username":"刘晓军", "sex":"男" }, |
|||
{ "id":"003", "username":"张恒", "sex":"男" }, |
|||
{ "id":"004", "username":"朱一", "sex":"男" }, |
|||
{ "id":"005", "username":"刘佳能", "sex":"女" }, |
|||
{ "id":"006", "username":"晓梅", "sex":"女" }, |
|||
{ "id":"007", "username":"马冬梅", "sex":"女" }, |
|||
{ "id":"008", "username":"刘晓庆", "sex":"女" }, |
|||
{ "id":"009", "username":"刘晓庆", "sex":"女" }, |
|||
{ "id":"010", "username":"刘晓庆", "sex":"女" }, |
|||
{ "id":"011", "username":"刘晓庆", "sex":"女" }, |
|||
{ "id":"012", "username":"刘晓庆", "sex":"女" }, |
|||
{ "id":"013", "username":"刘晓庆", "sex":"女" }, |
|||
{ "id":"014", "username":"刘晓庆", "sex":"女" }, |
|||
{ "id":"015", "username":"刘晓庆", "sex":"女" }, |
|||
{ "id":"016", "username":"刘晓庆", "sex":"女" } |
|||
] |
|||
"code": 0, |
|||
"msg": "", |
|||
"count": 16, |
|||
"data": [ |
|||
{ |
|||
"id": "001", |
|||
"username": "张玉林", |
|||
"sex": "女" |
|||
}, |
|||
{ |
|||
"id": "002", |
|||
"username": "刘晓军", |
|||
"sex": "男" |
|||
}, |
|||
{ |
|||
"id": "003", |
|||
"username": "张恒", |
|||
"sex": "男" |
|||
}, |
|||
{ |
|||
"id": "004", |
|||
"username": "朱一", |
|||
"sex": "男" |
|||
}, |
|||
{ |
|||
"id": "005", |
|||
"username": "刘佳能", |
|||
"sex": "女" |
|||
}, |
|||
{ |
|||
"id": "006", |
|||
"username": "晓梅", |
|||
"sex": "女" |
|||
}, |
|||
{ |
|||
"id": "007", |
|||
"username": "马冬梅", |
|||
"sex": "女" |
|||
}, |
|||
{ |
|||
"id": "008", |
|||
"username": "刘晓庆", |
|||
"sex": "女" |
|||
}, |
|||
{ |
|||
"id": "009", |
|||
"username": "刘晓庆", |
|||
"sex": "女" |
|||
}, |
|||
{ |
|||
"id": "010", |
|||
"username": "刘晓庆", |
|||
"sex": "女" |
|||
}, |
|||
{ |
|||
"id": "011", |
|||
"username": "刘晓庆", |
|||
"sex": "女" |
|||
}, |
|||
{ |
|||
"id": "012", |
|||
"username": "刘晓庆", |
|||
"sex": "女" |
|||
}, |
|||
{ |
|||
"id": "013", |
|||
"username": "刘晓庆", |
|||
"sex": "女" |
|||
}, |
|||
{ |
|||
"id": "014", |
|||
"username": "刘晓庆", |
|||
"sex": "女" |
|||
}, |
|||
{ |
|||
"id": "015", |
|||
"username": "刘晓庆", |
|||
"sex": "女" |
|||
}, |
|||
{ |
|||
"id": "016", |
|||
"username": "刘晓庆", |
|||
"sex": "女" |
|||
} |
|||
] |
|||
} |
|||
|
After Width: | Height: | Size: 119 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
@ -1,247 +1,249 @@ |
|||
layui.define(['table', 'laypage','jquery', 'element'], function(exports) { |
|||
"use strict"; |
|||
var filePath = layui.cache.modules.cardTable |
|||
.substr(0, layui.cache.modules.cardTable.lastIndexOf('/')); |
|||
// 引入tablePlug.css
|
|||
layui.link(filePath + '/cardTable.css'); |
|||
var MOD_NAME = 'cardTable', |
|||
$ = layui.jquery, |
|||
element = layui.element, |
|||
laypage = layui.laypage; |
|||
var _instances = {}; // 记录所有实例
|
|||
/* 默认参数 */ |
|||
var defaultOption = { |
|||
elem: "#currentTableId",// 构建的模型
|
|||
url: "",// 数据 url 连接
|
|||
loading: true,//是否加载
|
|||
limit: 0, //每页数量默认是每行数量的双倍
|
|||
linenum: 4, //每行数量 2,3,4,6
|
|||
currentPage: 1,//当前页
|
|||
data:[], //静态数据
|
|||
limits:[], //页码
|
|||
page: true, //是否分页
|
|||
layout: ['count', 'prev', 'page', 'next','limit', 'skip'],//分页控件
|
|||
request: { |
|||
pageName: 'page' //页码的参数名称,默认:page
|
|||
, limitName: 'limit' //每页数据量的参数名,默认:limit
|
|||
, idName: 'id' //主键名称,默认:id
|
|||
, titleName: 'title' //标题名称,默认:title
|
|||
, imageName: 'image' //图片地址,默认:image
|
|||
, remarkName: 'remark' //备注名称,默认:remark
|
|||
, timeName: 'time' //时间名称,默认:time
|
|||
}, |
|||
response: { |
|||
statusName: 'code' //规定数据状态的字段名称,默认:code
|
|||
, statusCode: 0 //规定成功的状态码,默认:0
|
|||
, msgName: 'msg' //规定状态信息的字段名称,默认:msg
|
|||
, countName: 'count' //规定数据总数的字段名称,默认:count
|
|||
, dataName: 'data' //规定数据列表的字段名称,默认:data
|
|||
}, |
|||
// 完 成 函 数
|
|||
done: function () { |
|||
layui.define(['table', 'laypage', 'jquery', 'element'], function (exports) { |
|||
"use strict"; |
|||
var filePath = layui.cache.modules.cardTable |
|||
.substr(0, layui.cache.modules.cardTable.lastIndexOf('/')); |
|||
// 引入tablePlug.css
|
|||
layui.link(filePath + '/cardTable.css'); |
|||
var MOD_NAME = 'cardTable', |
|||
$ = layui.jquery, |
|||
element = layui.element, |
|||
laypage = layui.laypage; |
|||
var _instances = {}; // 记录所有实例
|
|||
/* 默认参数 */ |
|||
var defaultOption = { |
|||
elem: "#currentTableId",// 构建的模型
|
|||
url: "",// 数据 url 连接
|
|||
loading: true,//是否加载
|
|||
limit: 0, //每页数量默认是每行数量的双倍
|
|||
linenum: 4, //每行数量 2,3,4,6
|
|||
currentPage: 1,//当前页
|
|||
data: [], //静态数据
|
|||
limits: [], //页码
|
|||
page: true, //是否分页
|
|||
layout: ['count', 'prev', 'page', 'next', 'limit', 'skip'],//分页控件
|
|||
request: { |
|||
pageName: 'page' //页码的参数名称,默认:page
|
|||
, limitName: 'limit' //每页数据量的参数名,默认:limit
|
|||
, idName: 'id' //主键名称,默认:id
|
|||
, titleName: 'title' //标题名称,默认:title
|
|||
, imageName: 'image' //图片地址,默认:image
|
|||
, remarkName: 'remark' //备注名称,默认:remark
|
|||
, timeName: 'time' //时间名称,默认:time
|
|||
}, |
|||
response: { |
|||
statusName: 'code' //规定数据状态的字段名称,默认:code
|
|||
, statusCode: 0 //规定成功的状态码,默认:0
|
|||
, msgName: 'msg' //规定状态信息的字段名称,默认:msg
|
|||
, countName: 'count' //规定数据总数的字段名称,默认:count
|
|||
, dataName: 'data' //规定数据列表的字段名称,默认:data
|
|||
}, |
|||
// 完 成 函 数
|
|||
done: function () { |
|||
|
|||
} |
|||
}; |
|||
var card = function(opt) { |
|||
_instances[opt.elem.substring(1)] = this; |
|||
this.reload(opt); |
|||
}; |
|||
/** 参数设置 */ |
|||
card.prototype.initOptions = function (opt) { |
|||
this.option = $.extend(true, {}, defaultOption, opt); |
|||
if (!this.option.limit || this.option.limit == 0) { |
|||
this.option.limit = this.option.linenum * 2; |
|||
} |
|||
if (!this.option.limits || this.option.limits.length == 0) { |
|||
this.option.limits = [this.option.limit]; |
|||
} |
|||
}; |
|||
card.prototype.init = function () { |
|||
var option = this.option; |
|||
var url = option.url; |
|||
var html = ""; |
|||
html += option.loading == true ? ' <div class="ew-table-loading">' : ' <div class="ew-table-loading layui-hide">'; |
|||
html += ' <i class="layui-icon layui-icon-loading layui-anim layui-anim-rotate layui-anim-loop"></i>'; |
|||
html += ' </div>'; |
|||
$(option.elem).html(html); |
|||
// 根 据 请 求 方 式 获 取 数 据
|
|||
html = ""; |
|||
if (!!url) { |
|||
if (url.indexOf("?") >= 0) { |
|||
url = url + '&v=1.0.0'; |
|||
} |
|||
else { |
|||
url = url + '?v=1.0.0'; |
|||
} |
|||
if (!!option.page) { |
|||
url = url + '&' + option.request.limitName + '=' + option.limit; |
|||
url = url + '&' + option.request.pageName + '=' + option.currentPage; |
|||
} |
|||
if (!!option.where) { |
|||
for (let key in option.where) { |
|||
url = url + '&' + key + '=' + option.where[key]; |
|||
} |
|||
}; |
|||
var card = function (opt) { |
|||
_instances[opt.elem.substring(1)] = this; |
|||
this.reload(opt); |
|||
}; |
|||
/** 参数设置 */ |
|||
card.prototype.initOptions = function (opt) { |
|||
this.option = $.extend(true, {}, defaultOption, opt); |
|||
if (!this.option.limit || this.option.limit == 0) { |
|||
this.option.limit = this.option.linenum * 2; |
|||
} |
|||
if (!this.option.limits || this.option.limits.length == 0) { |
|||
this.option.limits = [this.option.limit]; |
|||
} |
|||
}; |
|||
card.prototype.init = function () { |
|||
var option = this.option; |
|||
var url = option.url; |
|||
var html = ""; |
|||
html += option.loading == true ? ' <div class="ew-table-loading">' : ' <div class="ew-table-loading layui-hide">'; |
|||
html += ' <i class="layui-icon layui-icon-loading layui-anim layui-anim-rotate layui-anim-loop"></i>'; |
|||
html += ' </div>'; |
|||
$(option.elem).html(html); |
|||
// 根 据 请 求 方 式 获 取 数 据
|
|||
html = ""; |
|||
if (!!url) { |
|||
if (url.indexOf("?") >= 0) { |
|||
url = url + '&v=1.0.0'; |
|||
} else { |
|||
url = url + '?v=1.0.0'; |
|||
} |
|||
var data = getData(url); |
|||
data = initData(data, option); |
|||
if (data.code != option.response.statusCode) { |
|||
option.data = []; |
|||
option.count = 0; |
|||
if (!!option.page) { |
|||
url = url + '&' + option.request.limitName + '=' + option.limit; |
|||
url = url + '&' + option.request.pageName + '=' + option.currentPage; |
|||
} |
|||
if (!!option.where) { |
|||
for (let key in option.where) { |
|||
url = url + '&' + key + '=' + option.where[key]; |
|||
} |
|||
} |
|||
var data = getData(url); |
|||
data = initData(data, option); |
|||
if (data.code != option.response.statusCode) { |
|||
option.data = []; |
|||
option.count = 0; |
|||
} else { |
|||
option.data = data.data; |
|||
option.count = option.data.length; |
|||
} |
|||
option.data = data.data; |
|||
option.count = option.data.length; |
|||
} |
|||
|
|||
} |
|||
else { |
|||
if (!option.alldata) { |
|||
option.alldata = option.data; |
|||
} else { |
|||
if (!option.alldata) { |
|||
option.alldata = option.data; |
|||
} |
|||
if (option.page) { |
|||
var data = []; |
|||
option.count = option.alldata.length; |
|||
for (var i = (option.currentPage - 1) * option.limit; i < option.currentPage * option.limit && i<option.alldata.length; i++) { |
|||
data.push(option.alldata[i]); |
|||
} |
|||
option.data = data; |
|||
} |
|||
} |
|||
// 根据结果进行相应结构的创建
|
|||
if (!!option.data && option.data.length > 0) { |
|||
html = createComponent(option.elem.substring(1), option.linenum, option.data); |
|||
html += "<div id='cardpage'></div>"; |
|||
} |
|||
else { |
|||
html = "<p>没有数据</p>"; |
|||
} |
|||
$(option.elem).html(html); |
|||
if (option.page) { |
|||
// 初始化分页组件
|
|||
laypage.render({ |
|||
elem: 'cardpage' |
|||
, count: option.count, limit: option.limit, limits: option.limits, curr: option.currentPage |
|||
, layout: option.layout |
|||
, jump: function (obj, first) { |
|||
option.limit = obj.limit; |
|||
option.currentPage = obj.curr; |
|||
if (!first) { |
|||
_instances[option.elem.substring(1)].reload(option); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
card.prototype.reload = function (opt) { |
|||
this.initOptions(this.option ? $.extend(true, this.option, opt) : opt); |
|||
this.init(); // 初始化表格
|
|||
if (option.page) { |
|||
var data = []; |
|||
option.count = option.alldata.length; |
|||
for (var i = (option.currentPage - 1) * option.limit; i < option.currentPage * option.limit && i < option.alldata.length; i++) { |
|||
data.push(option.alldata[i]); |
|||
} |
|||
option.data = data; |
|||
} |
|||
} |
|||
// 根据结果进行相应结构的创建
|
|||
if (!!option.data && option.data.length > 0) { |
|||
html = createComponent(option.elem.substring(1), option.linenum, option.data); |
|||
html += "<div id='cardpage'></div>"; |
|||
} else { |
|||
html = "<p>没有数据</p>"; |
|||
} |
|||
$(option.elem).html(html); |
|||
if (option.page) { |
|||
// 初始化分页组件
|
|||
laypage.render({ |
|||
elem: 'cardpage' |
|||
, count: option.count, limit: option.limit, limits: option.limits, curr: option.currentPage |
|||
, layout: option.layout |
|||
, jump: function (obj, first) { |
|||
option.limit = obj.limit; |
|||
option.currentPage = obj.curr; |
|||
if (!first) { |
|||
_instances[option.elem.substring(1)].reload(option); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
card.prototype.reload = function (opt) { |
|||
this.initOptions(this.option ? $.extend(true, this.option, opt) : opt); |
|||
this.init(); // 初始化表格
|
|||
} |
|||
|
|||
function createComponent(elem,linenum,data) { |
|||
var html = "<div class='cloud-card-component'>" |
|||
var content = createCards(elem, linenum,data); |
|||
function createComponent(elem, linenum, data) { |
|||
var html = "<div class='cloud-card-component'>" |
|||
var content = createCards(elem, linenum, data); |
|||
var page = ""; |
|||
content = content + page; |
|||
html += content + "</div>" |
|||
return html; |
|||
} |
|||
/** 创建指定数量的卡片 */ |
|||
function createCards(elem, linenum,data) { |
|||
var content = "<div class='layui-row layui-col-space30'>"; |
|||
for (var i = 0; i < data.length; i++) { |
|||
content += createCard(elem, linenum,data[i],i); |
|||
} |
|||
|
|||
/** 创建指定数量的卡片 */ |
|||
function createCards(elem, linenum, data) { |
|||
var content = "<div class='layui-row layui-col-space30'>"; |
|||
for (var i = 0; i < data.length; i++) { |
|||
content += createCard(elem, linenum, data[i], i); |
|||
} |
|||
content += "</div>"; |
|||
return content; |
|||
} |
|||
|
|||
/** 创建一个卡片 */ |
|||
function createCard(elem, linenum, item, no) { |
|||
var line = 12 / linenum; |
|||
var card = |
|||
'<div id=' + item.id + ' onclick="cardTableCheckedCard(' + elem + ',this)" class="layui-col-md' + line + ' ew-datagrid-item" data-index="' + no + '" data-number="1"> <div class="project-list-item"> <img class="project-list-item-cover" src="' + item.image + '"> <div class="project-list-item-body"> <h2>' + item.title + '</h2> <div class="project-list-item-text layui-text">' + item.remark + '</div> <div class="project-list-item-desc"> <span class="time">' + item.time + '</span> <div class="ew-head-list"></div> </div> </div > </div > </div > ' |
|||
return card; |
|||
} |
|||
|
|||
/** 格式化返回参数 */ |
|||
function initData(tempData, option) { |
|||
var data = {}; |
|||
data.code = tempData[option.response.statusName]; |
|||
data.msg = tempData[option.response.msgName]; |
|||
data.count = tempData[option.response.countName]; |
|||
var dataList = tempData[option.response.dataName]; |
|||
data.data = []; |
|||
for (var i = 0; i < dataList.length; i++) { |
|||
var item = {}; |
|||
item.id = dataList[i][option.request.idName]; |
|||
item.image = dataList[i][option.request.imageName]; |
|||
item.title = dataList[i][option.request.titleName]; |
|||
item.remark = dataList[i][option.request.remarkName]; |
|||
item.time = dataList[i][option.request.timeName]; |
|||
data.data.push(item); |
|||
} |
|||
content += "</div>"; |
|||
return content; |
|||
} |
|||
/** 创建一个卡片 */ |
|||
function createCard(elem, linenum, item, no) { |
|||
var line = 12 / linenum; |
|||
var card = |
|||
'<div id=' + item.id + ' onclick="cardTableCheckedCard(' + elem + ',this)" class="layui-col-md' + line + ' ew-datagrid-item" data-index="' + no+'" data-number="1"> <div class="project-list-item"> <img class="project-list-item-cover" src="' +item.image + '"> <div class="project-list-item-body"> <h2>' + item.title + '</h2> <div class="project-list-item-text layui-text">' + item.remark + '</div> <div class="project-list-item-desc"> <span class="time">' +item.time + '</span> <div class="ew-head-list"></div> </div> </div > </div > </div > ' |
|||
return card; |
|||
} |
|||
/** 格式化返回参数 */ |
|||
function initData(tempData, option) { |
|||
var data = {}; |
|||
data.code = tempData[option.response.statusName]; |
|||
data.msg = tempData[option.response.msgName]; |
|||
data.count = tempData[option.response.countName]; |
|||
var dataList = tempData[option.response.dataName]; |
|||
data.data = []; |
|||
for (var i = 0; i < dataList.length; i++) { |
|||
var item = {}; |
|||
item.id = dataList[i][option.request.idName]; |
|||
item.image = dataList[i][option.request.imageName]; |
|||
item.title = dataList[i][option.request.titleName]; |
|||
item.remark = dataList[i][option.request.remarkName]; |
|||
item.time = dataList[i][option.request.timeName]; |
|||
data.data.push(item); |
|||
} |
|||
return data; |
|||
return data; |
|||
} |
|||
/** 同 步 请 求 获 取 数 据 */ |
|||
function getData(url) { |
|||
$.ajaxSettings.async = false; |
|||
var redata = null; |
|||
$.getJSON(url, function (data) { |
|||
redata = data; |
|||
}).fail(function () { |
|||
redata = null; |
|||
}); |
|||
return redata; |
|||
} |
|||
//卡片点击事件
|
|||
window.cardTableCheckedCard = function (elem,obj) { |
|||
$(obj).addClass('layui-table-click').siblings().removeClass('layui-table-click'); |
|||
var item = {}; |
|||
item.id = obj.id; |
|||
item.image = $(obj).find('.project-list-item-cover')[0].src; |
|||
item.title = $(obj).find('h2')[0].innerHTML; |
|||
item.remark = $(obj).find('.project-list-item-text')[0].innerHTML; |
|||
item.time = $(obj).find('.time')[0].innerHTML; |
|||
_instances[elem.id].option.checkedItem = item; |
|||
} |
|||
/** 对外提供的方法 */ |
|||
var tt = { |
|||
/* 渲染 */ |
|||
render: function (options) { |
|||
return new card(options); |
|||
}, |
|||
/* 重载 */ |
|||
reload: function (id, opt) { |
|||
_instances[id].option.checkedItem = null; |
|||
_instances[id].reload(opt); |
|||
}, |
|||
/* 获取选中数据 */ |
|||
getChecked: function (id) { |
|||
var option = _instances[id].option; |
|||
var data = option.checkedItem; |
|||
var item = {}; |
|||
|
|||
/** 同 步 请 求 获 取 数 据 */ |
|||
function getData(url) { |
|||
$.ajaxSettings.async = false; |
|||
var redata = null; |
|||
$.getJSON(url, function (data) { |
|||
redata = data; |
|||
}).fail(function () { |
|||
redata = null; |
|||
}); |
|||
return redata; |
|||
} |
|||
|
|||
//卡片点击事件
|
|||
window.cardTableCheckedCard = function (elem, obj) { |
|||
$(obj).addClass('layui-table-click').siblings().removeClass('layui-table-click'); |
|||
var item = {}; |
|||
item.id = obj.id; |
|||
item.image = $(obj).find('.project-list-item-cover')[0].src; |
|||
item.title = $(obj).find('h2')[0].innerHTML; |
|||
item.remark = $(obj).find('.project-list-item-text')[0].innerHTML; |
|||
item.time = $(obj).find('.time')[0].innerHTML; |
|||
_instances[elem.id].option.checkedItem = item; |
|||
} |
|||
/** 对外提供的方法 */ |
|||
var tt = { |
|||
/* 渲染 */ |
|||
render: function (options) { |
|||
return new card(options); |
|||
}, |
|||
/* 重载 */ |
|||
reload: function (id, opt) { |
|||
_instances[id].option.checkedItem = null; |
|||
_instances[id].reload(opt); |
|||
}, |
|||
/* 获取选中数据 */ |
|||
getChecked: function (id) { |
|||
var option = _instances[id].option; |
|||
var data = option.checkedItem; |
|||
var item = {}; |
|||
if (!data) { |
|||
return null; |
|||
return null; |
|||
} |
|||
item[option.request.idName] = data.id; |
|||
item[option.request.imageName] = data.image; |
|||
item[option.request.titleName] = data.title; |
|||
item[option.request.remarkName] = data.remark; |
|||
item[option.request.timeName] = data.time; |
|||
return item; |
|||
}, |
|||
/* 获取表格数据 */ |
|||
getAllData: function (id) { |
|||
var option = _instances[id].option; |
|||
var data = []; |
|||
for (var i = 0; i < option.data.length; i++) { |
|||
var item = {}; |
|||
item[option.request.idName] = option.data[i].id; |
|||
item[option.request.imageName] = option.data[i].image; |
|||
item[option.request.titleName] = option.data[i].title; |
|||
item[option.request.remarkName] = option.data[i].remark; |
|||
item[option.request.timeName] = option.data[i].time; |
|||
data.push(item); |
|||
item[option.request.idName] = data.id; |
|||
item[option.request.imageName] = data.image; |
|||
item[option.request.titleName] = data.title; |
|||
item[option.request.remarkName] = data.remark; |
|||
item[option.request.timeName] = data.time; |
|||
return item; |
|||
}, |
|||
/* 获取表格数据 */ |
|||
getAllData: function (id) { |
|||
var option = _instances[id].option; |
|||
var data = []; |
|||
for (var i = 0; i < option.data.length; i++) { |
|||
var item = {}; |
|||
item[option.request.idName] = option.data[i].id; |
|||
item[option.request.imageName] = option.data[i].image; |
|||
item[option.request.titleName] = option.data[i].title; |
|||
item[option.request.remarkName] = option.data[i].remark; |
|||
item[option.request.timeName] = option.data[i].time; |
|||
data.push(item); |
|||
} |
|||
return data; |
|||
}, |
|||
} |
|||
exports(MOD_NAME, tt); |
|||
return data; |
|||
}, |
|||
} |
|||
exports(MOD_NAME, tt); |
|||
}) |
|||
|
|||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue