正在显示
16 个修改的文件
包含
1413 行增加
和
108 行删除
| 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 | - } | ||
| 141 | - | ||
| 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 不匹配"); | 112 | + wxcpt = new WXBizMsgCrypt(sToken, sEncodingAESKey, sCorpID); |
| 113 | + | ||
| 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(); | ||
| 187 | } | 161 | } |
| 188 | - } catch (Exception e) { | ||
| 189 | - e.printStackTrace(); | 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×tamp=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×tamp=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 | +} |
lh-modules/lh-admin/src/main/java/com/zhonglai/luhui/admin/service/SendSysMonitorServerMessge.java
0 → 100644
| 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 | +} |
-
请 注册 或 登录 后发表评论