package com.ruoyi.system.service.impl; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.system.domain.QyWechatArticle; import com.ruoyi.system.mapper.SysUserMapper; import com.ruoyi.system.service.IQyWechatAccessTokenService; import com.ruoyi.system.service.IQyWechatService; import com.ruoyi.system.service.ISysConfigService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 企业微信服务实现类 * * @author ruoyi * @date 2025-12-11 */ @Service public class QyWechatServiceImpl implements IQyWechatService { private static final Logger log = LoggerFactory.getLogger(QyWechatServiceImpl.class); @Autowired private IQyWechatAccessTokenService qyWechatAccessTokenService; @Autowired private ISysConfigService configService; @Autowired private SysUserMapper userMapper; /** * 发送企业微信消息 */ @Override public boolean sendNotifyMessage(Long userId, String title, String content,String notifyUrl) { try { // 检查服务是否启用 if (!isEnabled()) { log.info("企业微信服务未启用,跳过消息发送"); return false; } // 获取用户的企业微信ID String qyUserId = getQyUserIdByUserId(userId); if (StringUtils.isEmpty(qyUserId)) { log.warn("用户{}未绑定企业微信ID,无法发送消息", userId); return false; } // 发送文本消息 return sendTextMessage(qyUserId, title, content, notifyUrl); } catch (Exception e) { log.error("企业微信消息发送异常,userId={}", userId, e); return false; } } /** * 发送企业微信文本消息 */ @Override public boolean sendTextMessage(String qyUserId, String title, String content, String notifyUrl) { try { // 检查服务是否启用 if (!isEnabled()) { log.info("企业微信服务未启用,跳过消息发送"); return false; } // 获取企业微信配置 String corpId = configService.selectConfigByKey("qy_wechat.corp_id"); String corpSecret = configService.selectConfigByKey("qy_wechat.corp_secret"); if (StringUtils.isEmpty(corpId) || StringUtils.isEmpty(corpSecret)) { log.error("企业微信配置不完整,缺少corpId或corpSecret"); return false; } // 获取AccessToken String accessToken = qyWechatAccessTokenService.getAppAccessToken(corpId, corpSecret); if (StringUtils.isEmpty(accessToken)) { log.error("获取企业微信AccessToken失败"); return false; } // 构造请求URL String url = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=" + accessToken; // 构造文章对象 QyWechatArticle article = new QyWechatArticle(); article.setTitle(title); article.setDescription(content); article.setUrl(notifyUrl); // 设置默认图片URL,您可以根据需要修改 // 构造请求参数 Map params = new HashMap<>(); params.put("touser", qyUserId); params.put("msgtype", "news"); params.put("agentid", Integer.parseInt(configService.selectConfigByKey("qy_wechat.agent_id"))); // 构造文章列表 List articles = new ArrayList<>(); articles.add(article); params.put("news", Collections.singletonMap("articles", articles)); // 发送HTTP POST请求 String response = sendHttpPostRequest(url, params); if (StringUtils.isEmpty(response)) { log.error("发送企业微信消息失败,响应为空"); return false; } // 解析响应结果 QyWechatResponse result = parseResponse(response); if (result != null && result.getErrcode() == 0) { log.info("企业微信消息发送成功,用户ID: {}", qyUserId); return true; } else { log.error("企业微信消息发送失败,错误码: {}, 错误信息: {}", result != null ? result.getErrcode() : "unknown", result != null ? result.getErrmsg() : response); return false; } } catch (Exception e) { log.error("企业微信文本消息发送异常,qyUserId={}", qyUserId, e); return false; } } /** * 获取用户的企业微信ID */ @Override public String getQyUserIdByUserId(Long userId) { try { if (userId == null) { log.warn("用户ID不能为空"); return null; } // 查询用户信息 SysUser user = userMapper.selectUserById(userId); if (user == null) { log.warn("未找到用户,userId={}", userId); return null; } // 返回企业微信用户ID return user.getQyWechatUserId(); } catch (Exception e) { log.error("获取用户企业微信ID异常,userId={}", userId, e); return null; } } /** * 检查企业微信服务是否启用 */ @Override public boolean isEnabled() { try { String enabled = configService.selectConfigByKey("qy_wechat.enable"); return "true".equals(enabled); } catch (Exception e) { log.warn("获取企业微信服务启用状态失败,使用默认值(false)", e); return false; } } /** * 发送HTTP POST请求 * * @param url 请求URL * @param params 请求参数 * @return 响应内容 */ private String sendHttpPostRequest(String url, Map params) { try { // 将参数转换为JSON字符串 String jsonParams = toJsonString(params); java.net.HttpURLConnection conn = (java.net.HttpURLConnection) new java.net.URL(url).openConnection(); conn.setRequestMethod("POST"); conn.setConnectTimeout(5000); conn.setReadTimeout(5000); conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); conn.setDoOutput(true); // 发送请求数据 java.io.OutputStream os = conn.getOutputStream(); os.write(jsonParams.getBytes("UTF-8")); os.flush(); os.close(); int responseCode = conn.getResponseCode(); if (responseCode == 200) { java.io.BufferedReader reader = new java.io.BufferedReader( new java.io.InputStreamReader(conn.getInputStream(), "UTF-8")); StringBuilder response = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { response.append(line); } reader.close(); return response.toString(); } else { log.error("HTTP请求失败,响应码: {}", responseCode); return null; } } catch (Exception e) { log.error("发送HTTP POST请求失败", e); return null; } } /** * 简单的JSON序列化方法 * * @param map 要序列化的Map * @return JSON字符串 */ private String toJsonString(Map map) { StringBuilder json = new StringBuilder("{"); boolean first = true; for (Map.Entry entry : map.entrySet()) { if (!first) { json.append(","); } json.append("\"").append(entry.getKey()).append("\":"); Object value = entry.getValue(); if (value instanceof String) { json.append("\"").append(value).append("\""); } else if (value instanceof Map) { // 处理嵌套Map json.append(toJsonString((Map) value)); } else if (value instanceof List) { // 处理列表 json.append(toJsonList((List) value)); } else { json.append(value); } first = false; } json.append("}"); return json.toString(); } /** * 序列化列表为JSON * * @param list 列表 * @return JSON字符串 */ private String toJsonList(List list) { StringBuilder json = new StringBuilder("["); boolean first = true; for (Object item : list) { if (!first) { json.append(","); } if (item instanceof String) { json.append("\"").append(item).append("\""); } else if (item instanceof Map) { json.append(toJsonString((Map) item)); } else if (item instanceof QyWechatArticle) { json.append(toJsonArticle((QyWechatArticle) item)); } else { json.append(item); } first = false; } json.append("]"); return json.toString(); } /** * 序列化文章对象为JSON * * @param article 文章对象 * @return JSON字符串 */ private String toJsonArticle(QyWechatArticle article) { StringBuilder json = new StringBuilder("{"); boolean first = true; // 添加非空字段 if (article.getTitle() != null) { if (!first) json.append(","); json.append("\"title\":\"").append(escapeJsonString(article.getTitle())).append("\""); first = false; } if (article.getDescription() != null) { if (!first) json.append(","); json.append("\"description\":\"").append(escapeJsonString(article.getDescription())).append("\""); first = false; } if (article.getUrl() != null) { if (!first) json.append(","); json.append("\"url\":\"").append(escapeJsonString(article.getUrl())).append("\""); first = false; } if (article.getPicurl() != null) { if (!first) json.append(","); json.append("\"picurl\":\"").append(escapeJsonString(article.getPicurl())).append("\""); first = false; } if (article.getAppid() != null) { if (!first) json.append(","); json.append("\"appid\":\"").append(escapeJsonString(article.getAppid())).append("\""); first = false; } if (article.getPagepath() != null) { if (!first) json.append(","); json.append("\"pagepath\":\"").append(escapeJsonString(article.getPagepath())).append("\""); first = false; } json.append("}"); return json.toString(); } /** * 转义JSON字符串中的特殊字符 * * @param str 原始字符串 * @return 转义后的字符串 */ private String escapeJsonString(String str) { if (str == null) return ""; return str.replace("\\", "\\\\") .replace("\"", "\\\"") .replace("\n", "\\n") .replace("\r", "\\r") .replace("\t", "\\t"); } /** * 解析企业微信响应 * * @param response 响应JSON * @return 响应对象 */ private QyWechatResponse parseResponse(String response) { try { // 使用简单JSON解析 QyWechatResponse result = new QyWechatResponse(); // 移除首尾花括号 String content = response.trim(); if (content.startsWith("{")) { content = content.substring(1); } if (content.endsWith("}")) { content = content.substring(0, content.length() - 1); } // 按逗号分割键值对 String[] pairs = content.split(","); for (String pair : pairs) { String[] keyValue = pair.split(":", 2); // 只分割第一个冒号 if (keyValue.length == 2) { String key = keyValue[0].trim().replaceAll("\"", ""); String value = keyValue[1].trim().replaceAll("\"", ""); switch (key) { case "errcode": result.setErrcode(Integer.parseInt(value)); break; case "errmsg": result.setErrmsg(value); break; } } } return result; } catch (Exception e) { log.error("解析企业微信响应失败: {}", response, e); return null; } } /** * 企业微信响应内部类 */ private static class QyWechatResponse { private int errcode; private String errmsg; // Getters and Setters public int getErrcode() { return errcode; } public void setErrcode(int errcode) { this.errcode = errcode; } public String getErrmsg() { return errmsg; } public void setErrmsg(String errmsg) { this.errmsg = errmsg; } } }