作者 钟来

运维告警服务

  1 +package com.ruoyi.quartz.task;
  2 +
  3 +import cn.hutool.core.bean.BeanUtil;
  4 +import cn.hutool.http.HttpUtil;
  5 +import com.alibaba.fastjson.JSONObject;
  6 +import com.google.gson.JsonObject;
  7 +import com.ruoyi.common.utils.DateUtils;
  8 +import com.ruoyi.system.domain.sys.SysMonitorServer;
  9 +import com.zhonglai.luhui.dao.service.PublicService;
  10 +import org.springframework.beans.factory.annotation.Autowired;
  11 +import org.springframework.stereotype.Component;
  12 +
  13 +import java.util.Date;
  14 +import java.util.List;
  15 +import java.util.Map;
  16 +
  17 +/**
  18 + * 服务器告警
  19 + */
  20 +@Component("serverAlarmTask")
  21 +public class ServerAlarmTask {
  22 + @Autowired
  23 + private PublicService publicService;
  24 +
  25 + //检测服务器运行状态
  26 + public void checkServerStatus(Integer cpu_usage, Integer memory_usage, Integer disk_usage, Integer connection_count) throws Exception {
  27 + List<Map<String,Object>> list = publicService.getObjectListBySQL("SELECT * FROM `sys_monitor_server` WHERE cpu_usage>="+cpu_usage+" OR memory_usage>="+memory_usage+" OR disk_usage>="+disk_usage+" OR connection_count>="+connection_count);
  28 + if(null != list && list.size()!=0)
  29 + {
  30 + StringBuffer stringBuffer = new StringBuffer("**服务器运维状态:**");
  31 + for (Map<String,Object> map:list)
  32 + {
  33 + SysMonitorServer sms = BeanUtil.mapToBean(map,SysMonitorServer.class,false,null);
  34 + stringBuffer.append("\n");
  35 + stringBuffer.append("\n");
  36 +
  37 + stringBuffer.append(">ip:");
  38 + stringBuffer.append(sms.getIp());
  39 +
  40 + stringBuffer.append("\n");
  41 + if(sms.getCpu_usage()>=cpu_usage)
  42 + {
  43 + stringBuffer.append("<font color=\"warning\">");
  44 + stringBuffer.append(">CPU使用率:");
  45 + stringBuffer.append(sms.getCpu_usage()/100+"%");
  46 + stringBuffer.append("</font>");
  47 + }else{
  48 + stringBuffer.append(">CPU使用率:");
  49 + stringBuffer.append(sms.getCpu_usage()/100+"%");
  50 + }
  51 +
  52 + stringBuffer.append("\n");
  53 + if(sms.getMemory_usage()>=memory_usage)
  54 + {
  55 + stringBuffer.append("<font color=\"warning\">");
  56 + stringBuffer.append(">内存使用率:");
  57 + stringBuffer.append(sms.getMemory_usage()/100+"%");
  58 + stringBuffer.append("</font>");
  59 + }else{
  60 + stringBuffer.append(">内存使用率:");
  61 + stringBuffer.append(sms.getMemory_usage()/100+"%");
  62 + }
  63 +
  64 + stringBuffer.append("\n");
  65 + if(sms.getDisk_usage()>=disk_usage)
  66 + {
  67 + stringBuffer.append("<font color=\"warning\">");
  68 + stringBuffer.append(">磁盘使用率:");
  69 + stringBuffer.append(sms.getDisk_usage()/100+"%");
  70 + stringBuffer.append("</font>");
  71 + }else{
  72 + stringBuffer.append(">磁盘使用率:");
  73 + stringBuffer.append(sms.getDisk_usage()/100+"%");
  74 + }
  75 +
  76 + stringBuffer.append("\n");
  77 +
  78 + if (sms.getConnection_count()>=connection_count)
  79 + {
  80 + stringBuffer.append("<font color=\"warning\">");
  81 + stringBuffer.append(">系统连接数:");
  82 + stringBuffer.append(sms.getConnection_count());
  83 + stringBuffer.append("</font>");
  84 + }else{
  85 + stringBuffer.append(">系统连接数:");
  86 + stringBuffer.append(sms.getConnection_count());
  87 + }
  88 +
  89 + stringBuffer.append("\n");
  90 + stringBuffer.append(DateUtils.parseDateToStr("yyyy年MM月dd日HH时mm分ss秒",new Date()));
  91 + }
  92 + JSONObject jsonObject = new JSONObject();
  93 + jsonObject.put("msgtype","markdown");
  94 + JSONObject text = new JSONObject();
  95 + text.put("content",stringBuffer.toString());
  96 + text.put("mentioned_mobile_list",new String[]{"@all"});
  97 + jsonObject.put("markdown",text);
  98 + HttpUtil.post("https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=0c811725-6ee8-4bb5-b7de-378b6fa5b9b0",jsonObject.toJSONString());
  99 + }
  100 + }
  101 +
  102 + public static void main(String[] args) {
  103 + StringBuffer stringBuffer = new StringBuffer("颜色测试:");
  104 + stringBuffer.append("<font color=\"warning\">警告</font>");
  105 + JSONObject jsonObject = new JSONObject();
  106 + jsonObject.put("msgtype","markdown");
  107 + JSONObject text = new JSONObject();
  108 + text.put("content",stringBuffer.toString());
  109 + text.put("mentioned_mobile_list",new String[]{"@all"});
  110 + jsonObject.put("markdown",text);
  111 + String str = HttpUtil.post("https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=0c811725-6ee8-4bb5-b7de-378b6fa5b9b0",jsonObject.toJSONString());
  112 + System.out.println(str);
  113 + }
  114 +
  115 +}
1 package com.zhonglai.luhui.admin.controller.monitor; 1 package com.zhonglai.luhui.admin.controller.monitor;
2 2
  3 +import cn.hutool.core.bean.BeanUtil;
  4 +import com.alibaba.fastjson.JSONObject;
3 import com.ruoyi.common.core.domain.AjaxResult; 5 import com.ruoyi.common.core.domain.AjaxResult;
4 import com.ruoyi.common.core.page.TableDataInfo; 6 import com.ruoyi.common.core.page.TableDataInfo;
  7 +import com.ruoyi.common.utils.DateUtils;
5 import com.ruoyi.common.utils.ServletUtils; 8 import com.ruoyi.common.utils.ServletUtils;
6 import com.ruoyi.common.utils.ip.IpUtils; 9 import com.ruoyi.common.utils.ip.IpUtils;
7 import com.ruoyi.framework.web.domain.Server; 10 import com.ruoyi.framework.web.domain.Server;
@@ -9,35 +12,37 @@ import com.ruoyi.system.domain.sys.SysMonitorServer; @@ -9,35 +12,37 @@ import com.ruoyi.system.domain.sys.SysMonitorServer;
9 import com.ruoyi.system.domain.sys.SysMonitorServerLog; 12 import com.ruoyi.system.domain.sys.SysMonitorServerLog;
10 import com.zhonglai.luhui.action.BaseController; 13 import com.zhonglai.luhui.action.BaseController;
11 import com.zhonglai.luhui.admin.dto.MonitorServerUploadDto; 14 import com.zhonglai.luhui.admin.dto.MonitorServerUploadDto;
  15 +import com.zhonglai.luhui.admin.qywx.AesException;
  16 +import com.zhonglai.luhui.admin.qywx.QyWxApplication;
  17 +import com.zhonglai.luhui.admin.qywx.WXBizMsgCrypt;
  18 +import com.zhonglai.luhui.admin.service.SendSysMonitorServerMessge;
12 import com.zhonglai.luhui.dao.service.PublicService; 19 import com.zhonglai.luhui.dao.service.PublicService;
13 -import com.zhonglai.luhui.device.domain.IotAlertLog;  
14 import io.swagger.annotations.Api; 20 import io.swagger.annotations.Api;
15 import io.swagger.annotations.ApiOperation; 21 import io.swagger.annotations.ApiOperation;
16 import org.springframework.beans.factory.annotation.Autowired; 22 import org.springframework.beans.factory.annotation.Autowired;
  23 +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
17 import org.springframework.security.access.prepost.PreAuthorize; 24 import org.springframework.security.access.prepost.PreAuthorize;
  25 +import org.springframework.util.StreamUtils;
18 import org.springframework.web.bind.annotation.*; 26 import org.springframework.web.bind.annotation.*;
  27 +import org.w3c.dom.Document;
  28 +import org.w3c.dom.Element;
  29 +import org.w3c.dom.NodeList;
  30 +import org.xml.sax.InputSource;
  31 +import org.xml.sax.SAXException;
19 32
20 33
21 import javax.servlet.http.HttpServletRequest; 34 import javax.servlet.http.HttpServletRequest;
22 -import java.io.UnsupportedEncodingException;  
23 -import java.net.URLDecoder; 35 +import javax.xml.parsers.DocumentBuilder;
  36 +import javax.xml.parsers.DocumentBuilderFactory;
  37 +import javax.xml.parsers.ParserConfigurationException;
  38 +import java.io.IOException;
  39 +import java.io.StringReader;
  40 +import java.util.Date;
24 import java.util.HashMap; 41 import java.util.HashMap;
25 import java.util.List; 42 import java.util.List;
26 import java.util.Map; 43 import java.util.Map;
27 -  
28 -import java.security.MessageDigest;  
29 -import java.security.NoSuchAlgorithmException;  
30 -import java.util.Arrays;  
31 -import java.util.stream.Collectors;  
32 -  
33 -import javax.crypto.Cipher;  
34 -import javax.crypto.spec.IvParameterSpec;  
35 -import javax.crypto.spec.SecretKeySpec;  
36 -import java.security.MessageDigest;  
37 -import java.security.NoSuchAlgorithmException;  
38 -import java.util.Base64;  
39 -import java.util.Arrays;  
40 -import java.util.stream.Collectors; 44 +import java.util.concurrent.ScheduledExecutorService;
  45 +import java.util.concurrent.TimeUnit;
41 46
42 47
43 /** 48 /**
@@ -52,6 +57,10 @@ public class ServerController extends BaseController @@ -52,6 +57,10 @@ public class ServerController extends BaseController
52 { 57 {
53 @Autowired 58 @Autowired
54 private PublicService publicService; 59 private PublicService publicService;
  60 +
  61 + @Autowired
  62 + private ScheduledExecutorService scheduledExecutorService;
  63 +
55 @ApiOperation("获取详情") 64 @ApiOperation("获取详情")
56 @PreAuthorize("@ss.hasPermi('monitor:server:list')") 65 @PreAuthorize("@ss.hasPermi('monitor:server:list')")
57 @GetMapping() 66 @GetMapping()
@@ -93,104 +102,80 @@ public class ServerController extends BaseController @@ -93,104 +102,80 @@ public class ServerController extends BaseController
93 return AjaxResult.success(); 102 return AjaxResult.success();
94 } 103 }
95 @ApiOperation("企业微信消息") 104 @ApiOperation("企业微信消息")
96 - @RequestMapping("/uploadWxMessage/{agentId}")  
97 - public AjaxResult uploadWxMessage(@PathVariable String agentId) 105 + @RequestMapping("/uploadWxMessage/{sCorpID}")
  106 + public String uploadWxMessage(@PathVariable String sCorpID, HttpServletRequest httpServletRequest)
98 { 107 {
99 -  
100 - return AjaxResult.success();  
101 - }  
102 -  
103 - public static void main(String[] args) throws UnsupportedEncodingException {  
104 - String TOKEN = "DDbVb3cSiTAbCSgI4UndPlvy"; // 替换为你的token  
105 - String ENCODING_AES_KEY = "FH1CDpqZhNkcXCWEYJOyAO4HLG2mxYrE1c4j83mXruB";  
106 - String msgSignature = "6cfbf56d42c00a182a6da650023ef5ba407cb203";  
107 - String timestamp = "1724921459";  
108 - String nonce = "1725665565";  
109 - String echoStr = "FQYmaDL0vzQPBz5rRVYYGVLx62EmD8krbPYO6RzVaAtsIuFz+n8nPZ8fPtnNTbMCiGaRrjZCYgbsaeDcqPqvNQ\u003d\u003d";  
110 -  
111 - // URL解码  
112 - String decodedEchoStr = decodeEchoStr(echoStr);  
113 -  
114 - // 校验msg_signature  
115 - boolean isValid = validateMsgSignature(TOKEN, timestamp, nonce, decodedEchoStr, msgSignature);  
116 - if (isValid) {  
117 - System.out.println("请求合法");  
118 - } else {  
119 - System.out.println("请求不合法");  
120 - }  
121 -  
122 - // 解密echostr  
123 - String messageContent = decryptEchoStr(decodedEchoStr,ENCODING_AES_KEY);  
124 - System.out.println("消息内容: " + messageContent);  
125 - }  
126 -  
127 - private static String decodeEchoStr(String echoStr) throws UnsupportedEncodingException {  
128 - // 假设echoStr已经被Base64编码过,这里直接返回  
129 - return URLDecoder.decode(echoStr, "UTF-8"); // 实际上需要根据情况解码  
130 - }  
131 -  
132 - private static boolean validateMsgSignature(String token, String timestamp, String nonce, String msgEncrypt, String msgSignature) { 108 + String sToken = "JC52PriN";
  109 + String sEncodingAESKey = "U8Ofa8HdXHlZdg6qVPOg0GurYC35Tufhs11K612Uy5b";
  110 + WXBizMsgCrypt wxcpt = null;
133 try { 111 try {
134 - String signature = sha1(Arrays.asList(token, timestamp, nonce, msgEncrypt).stream().sorted().collect(Collectors.joining()));  
135 - return msgSignature.equals(signature);  
136 - } catch (NoSuchAlgorithmException e) {  
137 - e.printStackTrace();  
138 - return false;  
139 - }  
140 - } 112 + wxcpt = new WXBizMsgCrypt(sToken, sEncodingAESKey, sCorpID);
141 113
142 - private static String sha1(String input) throws NoSuchAlgorithmException {  
143 - MessageDigest mDigest = MessageDigest.getInstance("SHA1");  
144 - byte[] result = mDigest.digest(input.getBytes());  
145 - StringBuilder sb = new StringBuilder();  
146 - for (byte aResult : result) {  
147 - sb.append(Integer.toString((aResult & 0xff) + 0x100, 16).substring(1));  
148 - }  
149 - return sb.toString();  
150 - }  
151 - private static final String CORPID = "wx5823bf96d3bd56c7";  
152 - private static String decryptEchoStr(String encryptedMsg,String ENCODING_AES_KEY) {  
153 - // 这里需要实现具体的解密逻辑  
154 - try {  
155 - // 1. BASE64解码  
156 - byte[] base64Decoded = Base64.getDecoder().decode(encryptedMsg);  
157 -  
158 - // 2. AES解密  
159 - byte[] keyBytes = Base64.getDecoder().decode(ENCODING_AES_KEY + "=");  
160 - SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");  
161 - IvParameterSpec ivSpec = new IvParameterSpec(keyBytes, 0, 16); // 使用前16个字节作为IV  
162 -  
163 - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");  
164 - cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);  
165 -  
166 - byte[] decrypted = cipher.doFinal(base64Decoded);  
167 -  
168 - // 3. 去除随机字节和msg_len  
169 - byte[] content = Arrays.copyOfRange(decrypted, 16, decrypted.length);  
170 - int msgLen = ((content[0] & 0xFF) << 24) |  
171 - ((content[1] & 0xFF) << 16) |  
172 - ((content[2] & 0xFF) << 8) |  
173 - (content[3] & 0xFF);  
174 -  
175 - byte[] msg = Arrays.copyOfRange(content, 4, 4 + msgLen);  
176 - byte[] receiveId = Arrays.copyOfRange(content, 4 + msgLen, content.length);  
177 -  
178 - // 4. 转换为字符串  
179 - String msgStr = new String(msg, "UTF-8");  
180 - String receiveIdStr = new String(receiveId, "UTF-8");  
181 -  
182 - // 5. 验证receiveId  
183 - if (receiveIdStr.equals(CORPID)) {  
184 - return msgStr;  
185 - } else {  
186 - throw new IllegalArgumentException("ReceiveId 不匹配");  
187 - }  
188 - } catch (Exception e) {  
189 - e.printStackTrace(); 114 + switch (httpServletRequest.getMethod().toUpperCase())
  115 + {
  116 + case "GET":
  117 + String sVerifyMsgSig = httpServletRequest.getParameter("msg_signature");
  118 + String sVerifyTimeStamp = httpServletRequest.getParameter("timestamp");
  119 + String sVerifyNonce = httpServletRequest.getParameter("nonce");
  120 + String sVerifyEchoStr = httpServletRequest.getParameter("echostr");
  121 + String sEchoStr = wxcpt.VerifyURL(sVerifyMsgSig, sVerifyTimeStamp,
  122 + sVerifyNonce, sVerifyEchoStr);
  123 + return sEchoStr;
  124 + case "POST":
  125 + String sReqMsgSig = httpServletRequest.getParameter("msg_signature");
  126 + String sReqTimeStamp = httpServletRequest.getParameter("timestamp");
  127 + String sReqNonce = httpServletRequest.getParameter("nonce");
  128 + String sReqData = new String(StreamUtils.copyToByteArray(httpServletRequest.getInputStream()));
  129 +
  130 + String sMsg = wxcpt.DecryptMsg(sReqMsgSig, sReqTimeStamp, sReqNonce, sReqData);
  131 + System.out.println("after decrypt msg: " + sMsg);
  132 +
  133 + // TODO: 解析出明文xml标签的内容进行处理
  134 + // For example:
  135 + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
  136 + DocumentBuilder db = dbf.newDocumentBuilder();
  137 + StringReader sr = new StringReader(sMsg);
  138 + InputSource is = new InputSource(sr);
  139 + Document document = db.parse(is);
  140 +
  141 + Element root = document.getDocumentElement();
  142 + NodeList nodelist1 = root.getElementsByTagName("Content");
  143 + String Content = nodelist1.item(0).getTextContent();
  144 + System.out.println("Content:" + Content);
  145 +
  146 + NodeList nodelist2 = root.getElementsByTagName("FromUserName");
  147 + String FromUserName = nodelist2.item(0).getTextContent();
  148 + System.out.println("FromUserName:" + FromUserName);
  149 +
  150 + NodeList nodelist3 = root.getElementsByTagName("AgentID");
  151 + String AgentID = nodelist3.item(0).getTextContent();
  152 + System.out.println("AgentID:" + AgentID);
  153 +
  154 + logger.info("接收到消息:{}",Content);
  155 +
  156 + scheduledExecutorService.schedule(SendSysMonitorServerMessge.createSendConmmd(publicService,FromUserName,Content,Integer.parseInt(AgentID)),0, TimeUnit.SECONDS);
  157 +
  158 + return AjaxResult.success().toString();
  159 + default:
  160 + return AjaxResult.success().toString();
  161 + }
  162 +
  163 + } catch (AesException e) {
  164 + logger.error("企业微信消息错误",e);
  165 + return null;
  166 + } catch (IOException e) {
  167 + logger.error("消息解码失败",e);
  168 + return null;
  169 + } catch (ParserConfigurationException e) {
  170 + logger.error("消息解析失败",e);
  171 + return null;
  172 + } catch (SAXException e) {
  173 + logger.error("消息解析失败",e);
190 return null; 174 return null;
191 } 175 }
192 } 176 }
193 177
  178 +
194 @ApiOperation("获取状态列表") 179 @ApiOperation("获取状态列表")
195 @GetMapping("/getSysMonitorServerList") 180 @GetMapping("/getSysMonitorServerList")
196 public TableDataInfo getSysMonitorServerList(SysMonitorServer sysMonitorServer) throws Exception 181 public TableDataInfo getSysMonitorServerList(SysMonitorServer sysMonitorServer) throws Exception
  1 +package com.zhonglai.luhui.admin.qywx;
  2 +
  3 +@SuppressWarnings("serial")
  4 +public class AesException extends Exception {
  5 +
  6 + public final static int OK = 0;
  7 + public final static int ValidateSignatureError = -40001;
  8 + public final static int ParseXmlError = -40002;
  9 + public final static int ComputeSignatureError = -40003;
  10 + public final static int IllegalAesKey = -40004;
  11 + public final static int ValidateCorpidError = -40005;
  12 + public final static int EncryptAESError = -40006;
  13 + public final static int DecryptAESError = -40007;
  14 + public final static int IllegalBuffer = -40008;
  15 + //public final static int EncodeBase64Error = -40009;
  16 + //public final static int DecodeBase64Error = -40010;
  17 + //public final static int GenReturnXmlError = -40011;
  18 +
  19 + private int code;
  20 +
  21 + private static String getMessage(int code) {
  22 + switch (code) {
  23 + case ValidateSignatureError:
  24 + return "签名验证错误";
  25 + case ParseXmlError:
  26 + return "xml解析失败";
  27 + case ComputeSignatureError:
  28 + return "sha加密生成签名失败";
  29 + case IllegalAesKey:
  30 + return "SymmetricKey非法";
  31 + case ValidateCorpidError:
  32 + return "corpid校验失败";
  33 + case EncryptAESError:
  34 + return "aes加密失败";
  35 + case DecryptAESError:
  36 + return "aes解密失败";
  37 + case IllegalBuffer:
  38 + return "解密后得到的buffer非法";
  39 +// case EncodeBase64Error:
  40 +// return "base64加密错误";
  41 +// case DecodeBase64Error:
  42 +// return "base64解密错误";
  43 +// case GenReturnXmlError:
  44 +// return "xml生成失败";
  45 + default:
  46 + return null; // cannot be
  47 + }
  48 + }
  49 +
  50 + public int getCode() {
  51 + return code;
  52 + }
  53 +
  54 + AesException(int code) {
  55 + super(getMessage(code));
  56 + this.code = code;
  57 + }
  58 +
  59 +}
  1 +package com.zhonglai.luhui.admin.qywx;
  2 +
  3 +import java.util.ArrayList;
  4 +
  5 +class ByteGroup {
  6 + ArrayList<Byte> byteContainer = new ArrayList<Byte>();
  7 +
  8 + public byte[] toBytes() {
  9 + byte[] bytes = new byte[byteContainer.size()];
  10 + for (int i = 0; i < byteContainer.size(); i++) {
  11 + bytes[i] = byteContainer.get(i);
  12 + }
  13 + return bytes;
  14 + }
  15 +
  16 + public ByteGroup addBytes(byte[] bytes) {
  17 + for (byte b : bytes) {
  18 + byteContainer.add(b);
  19 + }
  20 + return this;
  21 + }
  22 +
  23 + public int size() {
  24 + return byteContainer.size();
  25 + }
  26 +}
  1 +package com.zhonglai.luhui.admin.qywx;
  2 +
  3 +/**
  4 + * 获取token对象
  5 + */
  6 +public class GettokenDto {
  7 + private Integer errcode;
  8 + private String errmsg;
  9 + private String access_token;
  10 + private Integer expires_in;
  11 +
  12 + public Integer getErrcode() {
  13 + return errcode;
  14 + }
  15 +
  16 + public void setErrcode(Integer errcode) {
  17 + this.errcode = errcode;
  18 + }
  19 +
  20 + public String getErrmsg() {
  21 + return errmsg;
  22 + }
  23 +
  24 + public void setErrmsg(String errmsg) {
  25 + this.errmsg = errmsg;
  26 + }
  27 +
  28 + public String getAccess_token() {
  29 + return access_token;
  30 + }
  31 +
  32 + public void setAccess_token(String access_token) {
  33 + this.access_token = access_token;
  34 + }
  35 +
  36 + public Integer getExpires_in() {
  37 + return expires_in;
  38 + }
  39 +
  40 + public void setExpires_in(Integer expires_in) {
  41 + this.expires_in = expires_in;
  42 + }
  43 +}
  1 +/**
  2 + * 对企业微信发送给企业后台的消息加解密示例代码.
  3 + *
  4 + * @copyright Copyright (c) 1998-2014 Tencent Inc.
  5 + */
  6 +
  7 +// ------------------------------------------------------------------------
  8 +
  9 +package com.zhonglai.luhui.admin.qywx;
  10 +
  11 +import java.nio.charset.Charset;
  12 +import java.util.Arrays;
  13 +
  14 +/**
  15 + * 提供基于PKCS7算法的加解密接口.
  16 + */
  17 +class PKCS7Encoder {
  18 + static Charset CHARSET = Charset.forName("utf-8");
  19 + static int BLOCK_SIZE = 32;
  20 +
  21 + /**
  22 + * 获得对明文进行补位填充的字节.
  23 + *
  24 + * @param count 需要进行填充补位操作的明文字节个数
  25 + * @return 补齐用的字节数组
  26 + */
  27 + static byte[] encode(int count) {
  28 + // 计算需要填充的位数
  29 + int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);
  30 + if (amountToPad == 0) {
  31 + amountToPad = BLOCK_SIZE;
  32 + }
  33 + // 获得补位所用的字符
  34 + char padChr = chr(amountToPad);
  35 + String tmp = new String();
  36 + for (int index = 0; index < amountToPad; index++) {
  37 + tmp += padChr;
  38 + }
  39 + return tmp.getBytes(CHARSET);
  40 + }
  41 +
  42 + /**
  43 + * 删除解密后明文的补位字符
  44 + *
  45 + * @param decrypted 解密后的明文
  46 + * @return 删除补位字符后的明文
  47 + */
  48 + static byte[] decode(byte[] decrypted) {
  49 + int pad = (int) decrypted[decrypted.length - 1];
  50 + if (pad < 1 || pad > 32) {
  51 + pad = 0;
  52 + }
  53 + return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);
  54 + }
  55 +
  56 + /**
  57 + * 将数字转化成ASCII码对应的字符,用于对明文进行补码
  58 + *
  59 + * @param a 需要转化的数字
  60 + * @return 转化得到的字符
  61 + */
  62 + static char chr(int a) {
  63 + byte target = (byte) (a & 0xFF);
  64 + return (char) target;
  65 + }
  66 +
  67 +}
  1 +package com.zhonglai.luhui.admin.qywx;
  2 +
  3 +import cn.hutool.http.HttpUtil;
  4 +import com.google.gson.JsonObject;
  5 +import com.ruoyi.common.utils.DateUtils;
  6 +import com.ruoyi.common.utils.GsonConstructor;
  7 +import com.zhonglai.luhui.admin.qywx.message.BaseMessage;
  8 +import com.zhonglai.luhui.admin.qywx.message.MessageResp;
  9 +import com.zhonglai.luhui.admin.qywx.message.Text;
  10 +import com.zhonglai.luhui.admin.qywx.message.TextMessage;
  11 +import org.slf4j.Logger;
  12 +import org.slf4j.LoggerFactory;
  13 +
  14 +/**
  15 + * 企业微信应用
  16 + */
  17 +public class QyWxApplication {
  18 + protected final static Logger logger = LoggerFactory.getLogger(QyWxApplication.class);
  19 + private static GettokenDto gettokenDto = new GettokenDto();
  20 +
  21 + private final static String corpid = "ww140afc6429653bd8";
  22 + private final static String corpsecret = "Xl12XcP3eMJKt1I1YAPz5Dum9JO-ATq8OKpSLx8huiU";
  23 + public static String getToken() {
  24 + if(null == gettokenDto.getExpires_in() || DateUtils.getNowTimeMilly()-gettokenDto.getExpires_in()>=0)
  25 + {
  26 + String str = HttpUtil.get("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid="+corpid+"&corpsecret="+corpsecret);
  27 + GettokenDto gettokenDto = GsonConstructor.get().fromJson(str, GettokenDto.class);
  28 + if(0==gettokenDto.getErrcode())
  29 + {
  30 + QyWxApplication.gettokenDto.setAccess_token(gettokenDto.getAccess_token());
  31 + QyWxApplication.gettokenDto.setExpires_in(DateUtils.getNowTimeMilly()+gettokenDto.getExpires_in());
  32 + return gettokenDto.getAccess_token();
  33 + }else{
  34 + logger.error("获取token失败:"+str);
  35 + return null;
  36 + }
  37 + }
  38 + return gettokenDto.getAccess_token();
  39 + }
  40 +
  41 + /**
  42 + * 发送文本消息
  43 + * @param content 消息内容,最长不超过2048个字节,超过将截断(支持id转译)
  44 + * @param touser 指定接收消息的成员,成员ID列表(多个接收者用‘|’分隔,最多支持1000个)。
  45 + * 特殊情况:指定为"@all",则向该企业应用的全部成员发送
  46 + * @param toparty 指定接收消息的部门,部门ID列表,多个接收者用‘|’分隔,最多支持100个。
  47 + * 当touser为"@all"时忽略本参数
  48 + * @param totag 指定接收消息的标签,标签ID列表,多个接收者用‘|’分隔,最多支持100个。
  49 + * 当touser为"@all"时忽略本参数
  50 + */
  51 + public static void sendTextMessage(String content,String touser,String toparty,String totag,Integer agentid)
  52 + {
  53 + TextMessage textMessage = new TextMessage();
  54 + textMessage.setTouser(touser);
  55 + textMessage.setToparty(toparty);
  56 + textMessage.setTotag(totag);
  57 + textMessage.setMsgtype("text");
  58 + textMessage.setAgentid(agentid);
  59 + textMessage.setText(new Text(content));
  60 + textMessage.setSafe(0);
  61 + textMessage.setEnable_id_trans(0);
  62 + textMessage.setEnable_duplicate_check(0);
  63 + textMessage.setDuplicate_check_interval(1800);
  64 + send(textMessage);
  65 + }
  66 +
  67 + private static void send(BaseMessage baseMessage)
  68 + {
  69 + String str = HttpUtil.post(" https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token="+getToken(),GsonConstructor.get().toJson(baseMessage));
  70 + MessageResp messageResp = GsonConstructor.get().fromJson(str, MessageResp.class);
  71 + if(0!=messageResp.getErrcode())
  72 + {
  73 + logger.error("发送企业微信消息失败:"+str);
  74 + return;
  75 + }
  76 + logger.info("发送企业微信消息成功:"+str);
  77 + }
  78 +}
  1 +/**
  2 + * 对企业微信发送给企业后台的消息加解密示例代码.
  3 + *
  4 + * @copyright Copyright (c) 1998-2014 Tencent Inc.
  5 + */
  6 +
  7 +// ------------------------------------------------------------------------
  8 +
  9 +package com.zhonglai.luhui.admin.qywx;
  10 +
  11 +import java.security.MessageDigest;
  12 +import java.util.Arrays;
  13 +
  14 +/**
  15 + * SHA1 class
  16 + *
  17 + * 计算消息签名接口.
  18 + */
  19 +class SHA1 {
  20 +
  21 + /**
  22 + * 用SHA1算法生成安全签名
  23 + * @param token 票据
  24 + * @param timestamp 时间戳
  25 + * @param nonce 随机字符串
  26 + * @param encrypt 密文
  27 + * @return 安全签名
  28 + * @throws AesException
  29 + */
  30 + public static String getSHA1(String token, String timestamp, String nonce, String encrypt) throws AesException
  31 + {
  32 + try {
  33 + String[] array = new String[] { token, timestamp, nonce, encrypt };
  34 + StringBuffer sb = new StringBuffer();
  35 + // 字符串排序
  36 + Arrays.sort(array);
  37 + for (int i = 0; i < 4; i++) {
  38 + sb.append(array[i]);
  39 + }
  40 + String str = sb.toString();
  41 + // SHA1签名生成
  42 + MessageDigest md = MessageDigest.getInstance("SHA-1");
  43 + md.update(str.getBytes());
  44 + byte[] digest = md.digest();
  45 +
  46 + StringBuffer hexstr = new StringBuffer();
  47 + String shaHex = "";
  48 + for (int i = 0; i < digest.length; i++) {
  49 + shaHex = Integer.toHexString(digest[i] & 0xFF);
  50 + if (shaHex.length() < 2) {
  51 + hexstr.append(0);
  52 + }
  53 + hexstr.append(shaHex);
  54 + }
  55 + return hexstr.toString();
  56 + } catch (Exception e) {
  57 + e.printStackTrace();
  58 + throw new AesException(AesException.ComputeSignatureError);
  59 + }
  60 + }
  61 +}
  1 +package com.zhonglai.luhui.admin.qywx;
  2 +
  3 +import java.io.StringReader;
  4 +
  5 +import javax.xml.parsers.DocumentBuilder;
  6 +import javax.xml.parsers.DocumentBuilderFactory;
  7 +
  8 +import org.w3c.dom.Document;
  9 +import org.w3c.dom.Element;
  10 +import org.w3c.dom.NodeList;
  11 +import org.xml.sax.InputSource;
  12 +
  13 +public class Sample {
  14 +
  15 + public static void main(String[] args) throws Exception {
  16 + String sToken = "QDG6eK";
  17 + String sCorpID = "wx5823bf96d3bd56c7";
  18 + String sEncodingAESKey = "jWmYm7qr5nMoAUwZRjGtBxmz3KA1tkAj3ykkR6q2B2C";
  19 +
  20 + WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(sToken, sEncodingAESKey, sCorpID);
  21 + /*
  22 + ------------使用示例一:验证回调URL---------------
  23 + *企业开启回调模式时,企业微信会向验证url发送一个get请求
  24 + 假设点击验证时,企业收到类似请求:
  25 + * GET /cgi-bin/wxpush?msg_signature=5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3&timestamp=1409659589&nonce=263014780&echostr=P9nAzCzyDtyTWESHep1vC5X9xho%2FqYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp%2B4RPcs8TgAE7OaBO%2BFZXvnaqQ%3D%3D
  26 + * HTTP/1.1 Host: qy.weixin.qq.com
  27 +
  28 + 接收到该请求时,企业应 1.解析出Get请求的参数,包括消息体签名(msg_signature),时间戳(timestamp),随机数字串(nonce)以及企业微信推送过来的随机加密字符串(echostr),
  29 + 这一步注意作URL解码。
  30 + 2.验证消息体签名的正确性
  31 + 3. 解密出echostr原文,将原文当作Get请求的response,返回给企业微信
  32 + 第2,3步可以用企业微信提供的库函数VerifyURL来实现。
  33 +
  34 + */
  35 + // 解析出url上的参数值如下:
  36 + // String sVerifyMsgSig = HttpUtils.ParseUrl("msg_signature");
  37 + String sVerifyMsgSig = "5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3";
  38 + // String sVerifyTimeStamp = HttpUtils.ParseUrl("timestamp");
  39 + String sVerifyTimeStamp = "1409659589";
  40 + // String sVerifyNonce = HttpUtils.ParseUrl("nonce");
  41 + String sVerifyNonce = "263014780";
  42 + // String sVerifyEchoStr = HttpUtils.ParseUrl("echostr");
  43 + String sVerifyEchoStr = "P9nAzCzyDtyTWESHep1vC5X9xho/qYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp+4RPcs8TgAE7OaBO+FZXvnaqQ==";
  44 + String sEchoStr; //需要返回的明文
  45 + try {
  46 + sEchoStr = wxcpt.VerifyURL(sVerifyMsgSig, sVerifyTimeStamp,
  47 + sVerifyNonce, sVerifyEchoStr);
  48 + System.out.println("verifyurl echostr: " + sEchoStr);
  49 + // 验证URL成功,将sEchoStr返回
  50 + // HttpUtils.SetResponse(sEchoStr);
  51 + } catch (Exception e) {
  52 + //验证URL失败,错误原因请查看异常
  53 + e.printStackTrace();
  54 + }
  55 +
  56 + /*
  57 + ------------使用示例二:对用户回复的消息解密---------------
  58 + 用户回复消息或者点击事件响应时,企业会收到回调消息,此消息是经过企业微信加密之后的密文以post形式发送给企业,密文格式请参考官方文档
  59 + 假设企业收到企业微信的回调消息如下:
  60 + POST /cgi-bin/wxpush? msg_signature=477715d11cdb4164915debcba66cb864d751f3e6&timestamp=1409659813&nonce=1372623149 HTTP/1.1
  61 + Host: qy.weixin.qq.com
  62 + Content-Length: 613
  63 + <xml> <ToUserName><![CDATA[wx5823bf96d3bd56c7]]></ToUserName><Encrypt><![CDATA[RypEvHKD8QQKFhvQ6QleEB4J58tiPdvo+rtK1I9qca6aM/wvqnLSV5zEPeusUiX5L5X/0lWfrf0QADHHhGd3QczcdCUpj911L3vg3W/sYYvuJTs3TUUkSUXxaccAS0qhxchrRYt66wiSpGLYL42aM6A8dTT+6k4aSknmPj48kzJs8qLjvd4Xgpue06DOdnLxAUHzM6+kDZ+HMZfJYuR+LtwGc2hgf5gsijff0ekUNXZiqATP7PF5mZxZ3Izoun1s4zG4LUMnvw2r+KqCKIw+3IQH03v+BCA9nMELNqbSf6tiWSrXJB3LAVGUcallcrw8V2t9EL4EhzJWrQUax5wLVMNS0+rUPA3k22Ncx4XXZS9o0MBH27Bo6BpNelZpS+/uh9KsNlY6bHCmJU9p8g7m3fVKn28H3KDYA5Pl/T8Z1ptDAVe0lXdQ2YoyyH2uyPIGHBZZIs2pDBS8R07+qN+E7Q==]]></Encrypt>
  64 + <AgentID><![CDATA[218]]></AgentID>
  65 + </xml>
  66 +
  67 + 企业收到post请求之后应该 1.解析出url上的参数,包括消息体签名(msg_signature),时间戳(timestamp)以及随机数字串(nonce)
  68 + 2.验证消息体签名的正确性。
  69 + 3.将post请求的数据进行xml解析,并将<Encrypt>标签的内容进行解密,解密出来的明文即是用户回复消息的明文,明文格式请参考官方文档
  70 + 第2,3步可以用企业微信提供的库函数DecryptMsg来实现。
  71 + */
  72 + // String sReqMsgSig = HttpUtils.ParseUrl("msg_signature");
  73 + String sReqMsgSig = "477715d11cdb4164915debcba66cb864d751f3e6";
  74 + // String sReqTimeStamp = HttpUtils.ParseUrl("timestamp");
  75 + String sReqTimeStamp = "1409659813";
  76 + // String sReqNonce = HttpUtils.ParseUrl("nonce");
  77 + String sReqNonce = "1372623149";
  78 + // post请求的密文数据
  79 + // sReqData = HttpUtils.PostData();
  80 + String sReqData = "<xml><ToUserName><![CDATA[wx5823bf96d3bd56c7]]></ToUserName><Encrypt><![CDATA[RypEvHKD8QQKFhvQ6QleEB4J58tiPdvo+rtK1I9qca6aM/wvqnLSV5zEPeusUiX5L5X/0lWfrf0QADHHhGd3QczcdCUpj911L3vg3W/sYYvuJTs3TUUkSUXxaccAS0qhxchrRYt66wiSpGLYL42aM6A8dTT+6k4aSknmPj48kzJs8qLjvd4Xgpue06DOdnLxAUHzM6+kDZ+HMZfJYuR+LtwGc2hgf5gsijff0ekUNXZiqATP7PF5mZxZ3Izoun1s4zG4LUMnvw2r+KqCKIw+3IQH03v+BCA9nMELNqbSf6tiWSrXJB3LAVGUcallcrw8V2t9EL4EhzJWrQUax5wLVMNS0+rUPA3k22Ncx4XXZS9o0MBH27Bo6BpNelZpS+/uh9KsNlY6bHCmJU9p8g7m3fVKn28H3KDYA5Pl/T8Z1ptDAVe0lXdQ2YoyyH2uyPIGHBZZIs2pDBS8R07+qN+E7Q==]]></Encrypt><AgentID><![CDATA[218]]></AgentID></xml>";
  81 +
  82 + try {
  83 + String sMsg = wxcpt.DecryptMsg(sReqMsgSig, sReqTimeStamp, sReqNonce, sReqData);
  84 + System.out.println("after decrypt msg: " + sMsg);
  85 + // TODO: 解析出明文xml标签的内容进行处理
  86 + // For example:
  87 + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
  88 + DocumentBuilder db = dbf.newDocumentBuilder();
  89 + StringReader sr = new StringReader(sMsg);
  90 + InputSource is = new InputSource(sr);
  91 + Document document = db.parse(is);
  92 +
  93 + Element root = document.getDocumentElement();
  94 + NodeList nodelist1 = root.getElementsByTagName("Content");
  95 + String Content = nodelist1.item(0).getTextContent();
  96 + System.out.println("Content:" + Content);
  97 +
  98 + } catch (Exception e) {
  99 + // TODO
  100 + // 解密失败,失败原因请查看异常
  101 + e.printStackTrace();
  102 + }
  103 +
  104 + /*
  105 + ------------使用示例三:企业回复用户消息的加密---------------
  106 + 企业被动回复用户的消息也需要进行加密,并且拼接成密文格式的xml串。
  107 + 假设企业需要回复用户的明文如下:
  108 + <xml>
  109 + <ToUserName><![CDATA[mycreate]]></ToUserName>
  110 + <FromUserName><![CDATA[wx5823bf96d3bd56c7]]></FromUserName>
  111 + <CreateTime>1348831860</CreateTime>
  112 + <MsgType><![CDATA[text]]></MsgType>
  113 + <Content><![CDATA[this is a test]]></Content>
  114 + <MsgId>1234567890123456</MsgId>
  115 + <AgentID>128</AgentID>
  116 + </xml>
  117 +
  118 + 为了将此段明文回复给用户,企业应: 1.自己生成时间时间戳(timestamp),随机数字串(nonce)以便生成消息体签名,也可以直接用从企业微信的post url上解析出的对应值。
  119 + 2.将明文加密得到密文。 3.用密文,步骤1生成的timestamp,nonce和企业在企业微信设定的token生成消息体签名。 4.将密文,消息体签名,时间戳,随机数字串拼接成xml格式的字符串,发送给企业。
  120 + 以上2,3,4步可以用企业微信提供的库函数EncryptMsg来实现。
  121 + */
  122 + String sRespData = "<xml><ToUserName><![CDATA[mycreate]]></ToUserName><FromUserName><![CDATA[wx5823bf96d3bd56c7]]></FromUserName><CreateTime>1348831860</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[this is a test]]></Content><MsgId>1234567890123456</MsgId><AgentID>128</AgentID></xml>";
  123 + try{
  124 + String sEncryptMsg = wxcpt.EncryptMsg(sRespData, sReqTimeStamp, sReqNonce);
  125 + System.out.println("after encrypt sEncrytMsg: " + sEncryptMsg);
  126 + // 加密成功
  127 + // TODO:
  128 + // HttpUtils.SetResponse(sEncryptMsg);
  129 + }
  130 + catch(Exception e)
  131 + {
  132 + e.printStackTrace();
  133 + // 加密失败
  134 + }
  135 +
  136 + }
  137 +}
  1 +/**
  2 + * 对企业微信发送给企业后台的消息加解密示例代码.
  3 + *
  4 + * @copyright Copyright (c) 1998-2014 Tencent Inc.
  5 + */
  6 +
  7 +// ------------------------------------------------------------------------
  8 +
  9 +/**
  10 + * 针对org.apache.commons.codec.binary.Base64,
  11 + * 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本)
  12 + * 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi
  13 + */
  14 +package com.zhonglai.luhui.admin.qywx;
  15 +
  16 +import java.nio.charset.Charset;
  17 +import java.util.Arrays;
  18 +import java.util.Random;
  19 +
  20 +import javax.crypto.Cipher;
  21 +import javax.crypto.spec.IvParameterSpec;
  22 +import javax.crypto.spec.SecretKeySpec;
  23 +
  24 +import org.apache.commons.codec.binary.Base64;
  25 +
  26 +/**
  27 + * 提供接收和推送给企业微信消息的加解密接口(UTF8编码的字符串).
  28 + * <ol>
  29 + * <li>第三方回复加密消息给企业微信</li>
  30 + * <li>第三方收到企业微信发送的消息,验证消息的安全性,并对消息进行解密。</li>
  31 + * </ol>
  32 + * 说明:异常java.security.InvalidKeyException:illegal Key Size的解决方案
  33 + * <ol>
  34 + * <li>在官方网站下载JCE无限制权限策略文件(JDK7的下载地址:
  35 + * http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html</li>
  36 + * <li>下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt</li>
  37 + * <li>如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件</li>
  38 + * <li>如果安装了JDK,将两个jar文件放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件</li>
  39 + * </ol>
  40 + */
  41 +public class WXBizMsgCrypt {
  42 + static Charset CHARSET = Charset.forName("utf-8");
  43 + Base64 base64 = new Base64();
  44 + byte[] aesKey;
  45 + String token;
  46 + String receiveid;
  47 +
  48 + /**
  49 + * 构造函数
  50 + * @param token 企业微信后台,开发者设置的token
  51 + * @param encodingAesKey 企业微信后台,开发者设置的EncodingAESKey
  52 + * @param receiveid, 不同场景含义不同,详见文档
  53 + *
  54 + * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
  55 + */
  56 + public WXBizMsgCrypt(String token, String encodingAesKey, String receiveid) throws AesException {
  57 + if (encodingAesKey.length() != 43) {
  58 + throw new AesException(AesException.IllegalAesKey);
  59 + }
  60 +
  61 + this.token = token;
  62 + this.receiveid = receiveid;
  63 + aesKey = Base64.decodeBase64(encodingAesKey + "=");
  64 + }
  65 +
  66 + // 生成4个字节的网络字节序
  67 + byte[] getNetworkBytesOrder(int sourceNumber) {
  68 + byte[] orderBytes = new byte[4];
  69 + orderBytes[3] = (byte) (sourceNumber & 0xFF);
  70 + orderBytes[2] = (byte) (sourceNumber >> 8 & 0xFF);
  71 + orderBytes[1] = (byte) (sourceNumber >> 16 & 0xFF);
  72 + orderBytes[0] = (byte) (sourceNumber >> 24 & 0xFF);
  73 + return orderBytes;
  74 + }
  75 +
  76 + // 还原4个字节的网络字节序
  77 + int recoverNetworkBytesOrder(byte[] orderBytes) {
  78 + int sourceNumber = 0;
  79 + for (int i = 0; i < 4; i++) {
  80 + sourceNumber <<= 8;
  81 + sourceNumber |= orderBytes[i] & 0xff;
  82 + }
  83 + return sourceNumber;
  84 + }
  85 +
  86 + // 随机生成16位字符串
  87 + String getRandomStr() {
  88 + String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  89 + Random random = new Random();
  90 + StringBuffer sb = new StringBuffer();
  91 + for (int i = 0; i < 16; i++) {
  92 + int number = random.nextInt(base.length());
  93 + sb.append(base.charAt(number));
  94 + }
  95 + return sb.toString();
  96 + }
  97 +
  98 + /**
  99 + * 对明文进行加密.
  100 + *
  101 + * @param text 需要加密的明文
  102 + * @return 加密后base64编码的字符串
  103 + * @throws AesException aes加密失败
  104 + */
  105 + String encrypt(String randomStr, String text) throws AesException {
  106 + ByteGroup byteCollector = new ByteGroup();
  107 + byte[] randomStrBytes = randomStr.getBytes(CHARSET);
  108 + byte[] textBytes = text.getBytes(CHARSET);
  109 + byte[] networkBytesOrder = getNetworkBytesOrder(textBytes.length);
  110 + byte[] receiveidBytes = receiveid.getBytes(CHARSET);
  111 +
  112 + // randomStr + networkBytesOrder + text + receiveid
  113 + byteCollector.addBytes(randomStrBytes);
  114 + byteCollector.addBytes(networkBytesOrder);
  115 + byteCollector.addBytes(textBytes);
  116 + byteCollector.addBytes(receiveidBytes);
  117 +
  118 + // ... + pad: 使用自定义的填充方式对明文进行补位填充
  119 + byte[] padBytes = PKCS7Encoder.encode(byteCollector.size());
  120 + byteCollector.addBytes(padBytes);
  121 +
  122 + // 获得最终的字节流, 未加密
  123 + byte[] unencrypted = byteCollector.toBytes();
  124 +
  125 + try {
  126 + // 设置加密模式为AES的CBC模式
  127 + Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
  128 + SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
  129 + IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16);
  130 + cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
  131 +
  132 + // 加密
  133 + byte[] encrypted = cipher.doFinal(unencrypted);
  134 +
  135 + // 使用BASE64对加密后的字符串进行编码
  136 + String base64Encrypted = base64.encodeToString(encrypted);
  137 +
  138 + return base64Encrypted;
  139 + } catch (Exception e) {
  140 + e.printStackTrace();
  141 + throw new AesException(AesException.EncryptAESError);
  142 + }
  143 + }
  144 +
  145 + /**
  146 + * 对密文进行解密.
  147 + *
  148 + * @param text 需要解密的密文
  149 + * @return 解密得到的明文
  150 + * @throws AesException aes解密失败
  151 + */
  152 + String decrypt(String text) throws AesException {
  153 + byte[] original;
  154 + try {
  155 + // 设置解密模式为AES的CBC模式
  156 + Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
  157 + SecretKeySpec key_spec = new SecretKeySpec(aesKey, "AES");
  158 + IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
  159 + cipher.init(Cipher.DECRYPT_MODE, key_spec, iv);
  160 +
  161 + // 使用BASE64对密文进行解码
  162 + byte[] encrypted = Base64.decodeBase64(text);
  163 +
  164 + // 解密
  165 + original = cipher.doFinal(encrypted);
  166 + } catch (Exception e) {
  167 + e.printStackTrace();
  168 + throw new AesException(AesException.DecryptAESError);
  169 + }
  170 +
  171 + String xmlContent, from_receiveid;
  172 + try {
  173 + // 去除补位字符
  174 + byte[] bytes = PKCS7Encoder.decode(original);
  175 +
  176 + // 分离16位随机字符串,网络字节序和receiveid
  177 + byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20);
  178 +
  179 + int xmlLength = recoverNetworkBytesOrder(networkOrder);
  180 +
  181 + xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET);
  182 + from_receiveid = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length),
  183 + CHARSET);
  184 + } catch (Exception e) {
  185 + e.printStackTrace();
  186 + throw new AesException(AesException.IllegalBuffer);
  187 + }
  188 +
  189 + // receiveid不相同的情况
  190 + if (!from_receiveid.equals(receiveid)) {
  191 + throw new AesException(AesException.ValidateCorpidError);
  192 + }
  193 + return xmlContent;
  194 +
  195 + }
  196 +
  197 + /**
  198 + * 将企业微信回复用户的消息加密打包.
  199 + * <ol>
  200 + * <li>对要发送的消息进行AES-CBC加密</li>
  201 + * <li>生成安全签名</li>
  202 + * <li>将消息密文和安全签名打包成xml格式</li>
  203 + * </ol>
  204 + *
  205 + * @param replyMsg 企业微信待回复用户的消息,xml格式的字符串
  206 + * @param timeStamp 时间戳,可以自己生成,也可以用URL参数的timestamp
  207 + * @param nonce 随机串,可以自己生成,也可以用URL参数的nonce
  208 + *
  209 + * @return 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串
  210 + * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
  211 + */
  212 + public String EncryptMsg(String replyMsg, String timeStamp, String nonce) throws AesException {
  213 + // 加密
  214 + String encrypt = encrypt(getRandomStr(), replyMsg);
  215 +
  216 + // 生成安全签名
  217 + if (timeStamp == "") {
  218 + timeStamp = Long.toString(System.currentTimeMillis());
  219 + }
  220 +
  221 + String signature = SHA1.getSHA1(token, timeStamp, nonce, encrypt);
  222 +
  223 + // System.out.println("发送给平台的签名是: " + signature[1].toString());
  224 + // 生成发送的xml
  225 + String result = XMLParse.generate(encrypt, signature, timeStamp, nonce);
  226 + return result;
  227 + }
  228 +
  229 + /**
  230 + * 检验消息的真实性,并且获取解密后的明文.
  231 + * <ol>
  232 + * <li>利用收到的密文生成安全签名,进行签名验证</li>
  233 + * <li>若验证通过,则提取xml中的加密消息</li>
  234 + * <li>对消息进行解密</li>
  235 + * </ol>
  236 + *
  237 + * @param msgSignature 签名串,对应URL参数的msg_signature
  238 + * @param timeStamp 时间戳,对应URL参数的timestamp
  239 + * @param nonce 随机串,对应URL参数的nonce
  240 + * @param postData 密文,对应POST请求的数据
  241 + *
  242 + * @return 解密后的原文
  243 + * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
  244 + */
  245 + public String DecryptMsg(String msgSignature, String timeStamp, String nonce, String postData)
  246 + throws AesException {
  247 +
  248 + // 密钥,公众账号的app secret
  249 + // 提取密文
  250 + Object[] encrypt = XMLParse.extract(postData);
  251 +
  252 + // 验证安全签名
  253 + String signature = SHA1.getSHA1(token, timeStamp, nonce, encrypt[1].toString());
  254 +
  255 + // 和URL中的签名比较是否相等
  256 + // System.out.println("第三方收到URL中的签名:" + msg_sign);
  257 + // System.out.println("第三方校验签名:" + signature);
  258 + if (!signature.equals(msgSignature)) {
  259 + throw new AesException(AesException.ValidateSignatureError);
  260 + }
  261 +
  262 + // 解密
  263 + String result = decrypt(encrypt[1].toString());
  264 + return result;
  265 + }
  266 +
  267 + /**
  268 + * 验证URL
  269 + * @param msgSignature 签名串,对应URL参数的msg_signature
  270 + * @param timeStamp 时间戳,对应URL参数的timestamp
  271 + * @param nonce 随机串,对应URL参数的nonce
  272 + * @param echoStr 随机串,对应URL参数的echostr
  273 + *
  274 + * @return 解密之后的echostr
  275 + * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
  276 + */
  277 + public String VerifyURL(String msgSignature, String timeStamp, String nonce, String echoStr)
  278 + throws AesException {
  279 + String signature = SHA1.getSHA1(token, timeStamp, nonce, echoStr);
  280 +
  281 + if (!signature.equals(msgSignature)) {
  282 + throw new AesException(AesException.ValidateSignatureError);
  283 + }
  284 +
  285 + String result = decrypt(echoStr);
  286 + return result;
  287 + }
  288 +
  289 +}
  1 +/**
  2 + * 对企业微信发送给企业后台的消息加解密示例代码.
  3 + *
  4 + * @copyright Copyright (c) 1998-2014 Tencent Inc.
  5 + */
  6 +
  7 +// ------------------------------------------------------------------------
  8 +
  9 +package com.zhonglai.luhui.admin.qywx;
  10 +
  11 +import java.io.StringReader;
  12 +
  13 +import javax.xml.parsers.DocumentBuilder;
  14 +import javax.xml.parsers.DocumentBuilderFactory;
  15 +
  16 +import org.w3c.dom.Document;
  17 +import org.w3c.dom.Element;
  18 +import org.w3c.dom.NodeList;
  19 +import org.xml.sax.InputSource;
  20 +
  21 +/**
  22 + * XMLParse class
  23 + *
  24 + * 提供提取消息格式中的密文及生成回复消息格式的接口.
  25 + */
  26 +class XMLParse {
  27 +
  28 + /**
  29 + * 提取出xml数据包中的加密消息
  30 + * @param xmltext 待提取的xml字符串
  31 + * @return 提取出的加密消息字符串
  32 + * @throws AesException
  33 + */
  34 + public static Object[] extract(String xmltext) throws AesException {
  35 + Object[] result = new Object[3];
  36 + try {
  37 + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
  38 +
  39 + String FEATURE = null;
  40 + // This is the PRIMARY defense. If DTDs (doctypes) are disallowed, almost all XML entity attacks are prevented
  41 + // Xerces 2 only - http://xerces.apache.org/xerces2-j/features.html#disallow-doctype-decl
  42 + FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
  43 + dbf.setFeature(FEATURE, true);
  44 +
  45 + // If you can't completely disable DTDs, then at least do the following:
  46 + // Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-general-entities
  47 + // Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-general-entities
  48 + // JDK7+ - http://xml.org/sax/features/external-general-entities
  49 + FEATURE = "http://xml.org/sax/features/external-general-entities";
  50 + dbf.setFeature(FEATURE, false);
  51 +
  52 + // Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-parameter-entities
  53 + // Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-parameter-entities
  54 + // JDK7+ - http://xml.org/sax/features/external-parameter-entities
  55 + FEATURE = "http://xml.org/sax/features/external-parameter-entities";
  56 + dbf.setFeature(FEATURE, false);
  57 +
  58 + // Disable external DTDs as well
  59 + FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
  60 + dbf.setFeature(FEATURE, false);
  61 +
  62 + // and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
  63 + dbf.setXIncludeAware(false);
  64 + dbf.setExpandEntityReferences(false);
  65 +
  66 + // And, per Timothy Morgan: "If for some reason support for inline DOCTYPEs are a requirement, then
  67 + // ensure the entity settings are disabled (as shown above) and beware that SSRF attacks
  68 + // (http://cwe.mitre.org/data/definitions/918.html) and denial
  69 + // of service attacks (such as billion laughs or decompression bombs via "jar:") are a risk."
  70 +
  71 + // remaining parser logic
  72 + DocumentBuilder db = dbf.newDocumentBuilder();
  73 + StringReader sr = new StringReader(xmltext);
  74 + InputSource is = new InputSource(sr);
  75 + Document document = db.parse(is);
  76 +
  77 + Element root = document.getDocumentElement();
  78 + NodeList nodelist1 = root.getElementsByTagName("Encrypt");
  79 + result[0] = 0;
  80 + result[1] = nodelist1.item(0).getTextContent();
  81 + return result;
  82 + } catch (Exception e) {
  83 + e.printStackTrace();
  84 + throw new AesException(AesException.ParseXmlError);
  85 + }
  86 + }
  87 +
  88 + /**
  89 + * 生成xml消息
  90 + * @param encrypt 加密后的消息密文
  91 + * @param signature 安全签名
  92 + * @param timestamp 时间戳
  93 + * @param nonce 随机字符串
  94 + * @return 生成的xml字符串
  95 + */
  96 + public static String generate(String encrypt, String signature, String timestamp, String nonce) {
  97 +
  98 + String format = "<xml>\n" + "<Encrypt><![CDATA[%1$s]]></Encrypt>\n"
  99 + + "<MsgSignature><![CDATA[%2$s]]></MsgSignature>\n"
  100 + + "<TimeStamp>%3$s</TimeStamp>\n" + "<Nonce><![CDATA[%4$s]]></Nonce>\n" + "</xml>";
  101 + return String.format(format, encrypt, signature, timestamp, nonce);
  102 +
  103 + }
  104 +}
  1 +package com.zhonglai.luhui.admin.qywx.message;
  2 +
  3 +/**
  4 + * 消息公共参数
  5 + *
  6 + * touser 否 指定接收消息的成员,成员ID列表(多个接收者用‘|’分隔,最多支持1000个)。
  7 + * 特殊情况:指定为"@all",则向该企业应用的全部成员发送
  8 + * toparty 否 指定接收消息的部门,部门ID列表,多个接收者用‘|’分隔,最多支持100个。
  9 + * 当touser为"@all"时忽略本参数
  10 + * totag 否 指定接收消息的标签,标签ID列表,多个接收者用‘|’分隔,最多支持100个。
  11 + * 当touser为"@all"时忽略本参数
  12 + * msgtype 是 消息类型,此时固定为:text
  13 + * agentid 是 企业应用的id,整型。企业内部开发,可在应用的设置页面查看;第三方服务商,可通过接口 获取企业授权信息 获取该参数值
  14 + * enable_duplicate_check 否 表示是否开启重复消息检查,0表示否,1表示是,默认0
  15 + * duplicate_check_interval 否 表示是否重复消息检查的时间间隔,默认1800s,最大不超过4小时
  16 + */
  17 +public class BaseMessage {
  18 + private String touser;
  19 + private String toparty;
  20 + private String totag;
  21 + private String msgtype;
  22 + private Integer agentid;
  23 +
  24 + private Integer enable_duplicate_check;
  25 + private Integer duplicate_check_interval;
  26 +
  27 + public String getTouser() {
  28 + return touser;
  29 + }
  30 +
  31 + public void setTouser(String touser) {
  32 + this.touser = touser;
  33 + }
  34 +
  35 + public String getToparty() {
  36 + return toparty;
  37 + }
  38 +
  39 + public void setToparty(String toparty) {
  40 + this.toparty = toparty;
  41 + }
  42 +
  43 + public String getTotag() {
  44 + return totag;
  45 + }
  46 +
  47 + public void setTotag(String totag) {
  48 + this.totag = totag;
  49 + }
  50 +
  51 + public String getMsgtype() {
  52 + return msgtype;
  53 + }
  54 +
  55 + public void setMsgtype(String msgtype) {
  56 + this.msgtype = msgtype;
  57 + }
  58 +
  59 + public Integer getAgentid() {
  60 + return agentid;
  61 + }
  62 +
  63 + public void setAgentid(Integer agentid) {
  64 + this.agentid = agentid;
  65 + }
  66 +
  67 + public Integer getEnable_duplicate_check() {
  68 + return enable_duplicate_check;
  69 + }
  70 +
  71 + public void setEnable_duplicate_check(Integer enable_duplicate_check) {
  72 + this.enable_duplicate_check = enable_duplicate_check;
  73 + }
  74 +
  75 + public Integer getDuplicate_check_interval() {
  76 + return duplicate_check_interval;
  77 + }
  78 +
  79 + public void setDuplicate_check_interval(Integer duplicate_check_interval) {
  80 + this.duplicate_check_interval = duplicate_check_interval;
  81 + }
  82 +}
  1 +package com.zhonglai.luhui.admin.qywx.message;
  2 +
  3 +/**
  4 + * 返回参数
  5 + * errcode 返回码
  6 + * errmsg 对返回码的文本描述内容
  7 + * invaliduser 不合法的userid,不区分大小写,统一转为小写
  8 + * invalidparty 不合法的partyid
  9 + * invalidtag 不合法的标签id
  10 + * unlicenseduser 没有基础接口许可(包含已过期)的userid
  11 + * msgid 消息id,用于撤回应用消息
  12 + * response_code 仅消息类型为“按钮交互型”,“投票选择型”和“多项选择型”的模板卡片消息返回,应用可使用response_code调用更新模版卡片消息接口,72小时内有效,且只能使用一次
  13 + */
  14 +public class MessageResp {
  15 + private Integer errcode;
  16 + private String errmsg;
  17 + private String invaliduser;
  18 + private String invalidparty;
  19 + private String invalidtag;
  20 + private String unlicenseduser;
  21 + private String msgid;
  22 + private String response_code;
  23 +
  24 + public Integer getErrcode() {
  25 + return errcode;
  26 + }
  27 +
  28 + public void setErrcode(Integer errcode) {
  29 + this.errcode = errcode;
  30 + }
  31 +
  32 + public String getErrmsg() {
  33 + return errmsg;
  34 + }
  35 +
  36 + public void setErrmsg(String errmsg) {
  37 + this.errmsg = errmsg;
  38 + }
  39 +
  40 + public String getInvaliduser() {
  41 + return invaliduser;
  42 + }
  43 +
  44 + public void setInvaliduser(String invaliduser) {
  45 + this.invaliduser = invaliduser;
  46 + }
  47 +
  48 + public String getInvalidparty() {
  49 + return invalidparty;
  50 + }
  51 +
  52 + public void setInvalidparty(String invalidparty) {
  53 + this.invalidparty = invalidparty;
  54 + }
  55 +
  56 + public String getInvalidtag() {
  57 + return invalidtag;
  58 + }
  59 +
  60 + public void setInvalidtag(String invalidtag) {
  61 + this.invalidtag = invalidtag;
  62 + }
  63 +
  64 + public String getUnlicenseduser() {
  65 + return unlicenseduser;
  66 + }
  67 +
  68 + public void setUnlicenseduser(String unlicenseduser) {
  69 + this.unlicenseduser = unlicenseduser;
  70 + }
  71 +
  72 + public String getMsgid() {
  73 + return msgid;
  74 + }
  75 +
  76 + public void setMsgid(String msgid) {
  77 + this.msgid = msgid;
  78 + }
  79 +
  80 + public String getResponse_code() {
  81 + return response_code;
  82 + }
  83 +
  84 + public void setResponse_code(String response_code) {
  85 + this.response_code = response_code;
  86 + }
  87 +}
  1 +package com.zhonglai.luhui.admin.qywx.message;
  2 +
  3 +public class Text {
  4 + private String content;
  5 +
  6 + public Text(String content) {
  7 + this.content = content;
  8 + }
  9 +
  10 + public String getContent() {
  11 + return content;
  12 + }
  13 +
  14 + public void setContent(String content) {
  15 + this.content = content;
  16 + }
  17 +}
  1 +package com.zhonglai.luhui.admin.qywx.message;
  2 +
  3 +/**
  4 + * 发送文本消息
  5 + *
  6 + * content 是 消息内容,最长不超过2048个字节,超过将截断(支持id转译)
  7 + * safe 否 表示是否是保密消息,0表示可对外分享,1表示不能分享且内容显示水印,默认为0
  8 + * enable_id_trans 否 表示是否开启id转译,0表示否,1表示是,默认0。
  9 + */
  10 +public class TextMessage extends BaseMessage{
  11 + private Text text;
  12 + private Integer safe;
  13 + private Integer enable_id_trans;
  14 +
  15 + public Text getText() {
  16 + return text;
  17 + }
  18 +
  19 + public void setText(Text text) {
  20 + this.text = text;
  21 + }
  22 +
  23 + public Integer getSafe() {
  24 + return safe;
  25 + }
  26 +
  27 + public void setSafe(Integer safe) {
  28 + this.safe = safe;
  29 + }
  30 +
  31 + public Integer getEnable_id_trans() {
  32 + return enable_id_trans;
  33 + }
  34 +
  35 + public void setEnable_id_trans(Integer enable_id_trans) {
  36 + this.enable_id_trans = enable_id_trans;
  37 + }
  38 +}
  1 +package com.zhonglai.luhui.admin.service;
  2 +
  3 +import cn.hutool.core.bean.BeanUtil;
  4 +import com.ruoyi.common.utils.DateUtils;
  5 +import com.ruoyi.system.domain.sys.SysMonitorServer;
  6 +import com.zhonglai.luhui.admin.qywx.QyWxApplication;
  7 +import com.zhonglai.luhui.dao.service.PublicService;
  8 +import org.slf4j.Logger;
  9 +import org.slf4j.LoggerFactory;
  10 +import org.springframework.beans.factory.annotation.Autowired;
  11 +import org.springframework.stereotype.Service;
  12 +
  13 +import java.util.Date;
  14 +import java.util.List;
  15 +import java.util.Map;
  16 +
  17 +public class SendSysMonitorServerMessge implements Runnable{
  18 + private final Logger logger = LoggerFactory.getLogger(this.getClass());
  19 + private PublicService publicService;
  20 + private String Content;
  21 + private String toUser;
  22 + private Integer agentid;
  23 +
  24 + public static SendSysMonitorServerMessge createSendConmmd(PublicService publicService, String toUser, String content,Integer agentid)
  25 + {
  26 + return new SendSysMonitorServerMessge(publicService,toUser,content,agentid);
  27 + }
  28 +
  29 + private SendSysMonitorServerMessge(PublicService publicService, String toUser, String content,Integer agentid) {
  30 + this.publicService = publicService;
  31 + Content = content;
  32 + this.toUser = toUser;
  33 + this.agentid = agentid;
  34 + }
  35 +
  36 + @Override
  37 + public void run() {
  38 + logger.info("开始执行企业微信消息发送");
  39 + String string = null;
  40 + switch (Content.split(":")[0])
  41 + {
  42 + case "1":
  43 + string = conmmd_1();
  44 + break;
  45 +// case "2":
  46 +// string = conmmd_2();
  47 +// break;
  48 +// case "3":
  49 +// string = conmmd_3();
  50 +// break;
  51 + default:
  52 + string = conmmd_default();
  53 + break;
  54 + }
  55 + if(null != string && string.length()!=0)
  56 + {
  57 + QyWxApplication.sendTextMessage(string,"ZhongLai",null,null,agentid);
  58 + }
  59 + }
  60 +
  61 + private String conmmd_default()
  62 + {
  63 + StringBuffer stringBuffer = new StringBuffer("机器人指令:");
  64 + stringBuffer.append("\n");
  65 + stringBuffer.append("1 ---》查询所有服务器状态");
  66 + stringBuffer.append("2:{ip} ---》指定服务器内存使用前10");
  67 + stringBuffer.append("3:{ip} ---》指定服务器cpu使用前10");
  68 + return stringBuffer.toString();
  69 + }
  70 +
  71 + private String conmmd_1()
  72 + {
  73 + SysMonitorServer sysMonitorServer = new SysMonitorServer();
  74 + List<Map<String,Object>> list = publicService.getObjectList(sysMonitorServer,"*",null,null,0,0);
  75 +
  76 + StringBuffer stringBuffer = null;
  77 + if(null != list && list.size()!=0)
  78 + {
  79 + stringBuffer = new StringBuffer("服务器运维状态:");
  80 + for (Map<String,Object> map:list)
  81 + {
  82 + SysMonitorServer sms = BeanUtil.mapToBean(map,SysMonitorServer.class,false,null);
  83 + stringBuffer.append("\n");
  84 + stringBuffer.append("\n");
  85 + stringBuffer.append("ip:");
  86 + stringBuffer.append(sms.getIp());
  87 + stringBuffer.append("\n");
  88 + stringBuffer.append("CPU使用率:");
  89 + stringBuffer.append(sms.getCpu_usage()/100+"%");
  90 + stringBuffer.append("\n");
  91 + stringBuffer.append("内存使用率:");
  92 + stringBuffer.append(sms.getMemory_usage()/100+"%");
  93 + stringBuffer.append("\n");
  94 + stringBuffer.append("磁盘使用率:");
  95 + stringBuffer.append(sms.getDisk_usage()/100+"%");
  96 + stringBuffer.append("\n");
  97 + stringBuffer.append("系统连接数:");
  98 + stringBuffer.append(sms.getConnection_count());
  99 + stringBuffer.append("\n");
  100 + stringBuffer.append("当前时间:");
  101 + stringBuffer.append(DateUtils.parseDateToStr("yyyy年MM月dd日HH时mm分ss秒",new Date()));
  102 + }
  103 + return stringBuffer.toString();
  104 + }
  105 + return null;
  106 + }
  107 +
  108 +// private StringBuffer conmmd_2()
  109 +// {
  110 +//
  111 +// }
  112 +//
  113 +// private StringBuffer conmmd_3()
  114 +// {
  115 +//
  116 +// }
  117 +}