From 002aac2eb465e9b41a6209b71c241b214ac30258 Mon Sep 17 00:00:00 2001 From: qfrjava <13402782+qfrjava@user.noreply.gitee.com> Date: 星期日, 27 四月 2025 18:40:03 +0800 Subject: [PATCH] feat(lightrag): 增加通用转发接口和文档上传功能 --- JAVA/SMTAIServer/src/main/java/com/smtaiserver/smtaiserver/control/SMTAIWeixinControl.java | 420 ++++++++++++++++++++++++++++++++++++++++++++++------------- 1 files changed, 325 insertions(+), 95 deletions(-) diff --git a/JAVA/SMTAIServer/src/main/java/com/smtaiserver/smtaiserver/control/SMTAIWeixinControl.java b/JAVA/SMTAIServer/src/main/java/com/smtaiserver/smtaiserver/control/SMTAIWeixinControl.java index 7b212bd..d368c4c 100644 --- a/JAVA/SMTAIServer/src/main/java/com/smtaiserver/smtaiserver/control/SMTAIWeixinControl.java +++ b/JAVA/SMTAIServer/src/main/java/com/smtaiserver/smtaiserver/control/SMTAIWeixinControl.java @@ -1,116 +1,346 @@ package com.smtaiserver.smtaiserver.control; import com.smtaiserver.smtaiserver.core.SMTAIServerApp; -import com.smtservlet.core.SMTRequest; +import com.smtaiserver.smtaiserver.core.SMTAIServerRequest; +import com.smtaiserver.smtaiserver.database.SMTDatabase; +import com.smtaiserver.smtaiserver.database.SMTDatabase.DBRecords; +import com.smtaiserver.smtaiserver.javaai.SMTJavaAIChat; +import com.smtaiserver.smtaiserver.util.SMTWXSStatic; +import com.smtservlet.util.Json; +import com.smtservlet.util.SMTJsonWriter; +import com.smtservlet.util.SMTStatic; +import java.util.*; +import javax.servlet.http.HttpServletRequest; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.springframework.boot.configurationprocessor.json.JSONObject; -import org.springframework.stereotype.Component; +import org.jetbrains.annotations.NotNull; +import org.springframework.web.servlet.ModelAndView; -import javax.servlet.http.HttpServletRequest; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -import static java.util.Arrays.sort; public class SMTAIWeixinControl { - private static Logger _logger = LogManager.getLogger(SMTAIServerControl.class); + private static final HashMap<String, String> asynchronousList = new HashMap<>(); + private static final String FROM_USER_NAME = "FromUserName"; + private static final String TO_USER_NAME = "ToUserName"; + private static final String CONTENT = "Content"; + private static final Logger _logger = LogManager.getLogger(SMTAIServerControl.class); - /** - * 寰俊楠岃瘉 - * - * @param tranReq - * @param request - * @return - * @throws Exception - */ - public String weChatVerification(SMTRequest tranReq, HttpServletRequest request) throws Exception { - _logger.info(tranReq); - // 鑾峰彇寰俊璇锋眰鍙傛暟 - String signature = request.getParameter("signature"); - String timestamp = request.getParameter("timestamp"); - String nonce = request.getParameter("nonce"); - String echostr = request.getParameter("echostr"); - _logger.info( - "寮�濮嬫牎楠屾娆℃秷鎭槸鍚︽潵鑷井淇℃湇鍔″櫒锛宲aram->signature:{},\ntimestamp:{},\nnonce:{},\nechostr:{}", - signature, - timestamp, - nonce, - echostr); - // 闇�瑕侀獙璇佺殑鏃跺�欏氨鍚敤 - if (checkSignature(signature, timestamp, nonce)) { - return echostr; + private final Object _lockToken = new Object(); + + /** + * 寰俊楠岃瘉 + */ + public ModelAndView weChatNotify(SMTAIServerRequest tranReq) throws Exception { + String method = tranReq.getRequest().getMethod(); + if (method.equals("GET")) return SMTWXSStatic.getModelAndView(tranReq); + return reply(tranReq); } - return ""; - } - /** - * 楠岃瘉绛惧悕 - * - * @param signature - * @param timestamp - * @param nonce - * @return - */ - public static boolean checkSignature(String signature, String timestamp, String nonce) + /** 琚姩鍥炲 */ + private ModelAndView reply(SMTAIServerRequest tranReq) { + + long l = System.currentTimeMillis() / 1000; + String createTimeStr = String.valueOf(l); + HttpServletRequest request = tranReq.getRequest(); + Map<String, String> requestMap = SMTWXSStatic.getWechatReqMap(request); + String fromUserName = requestMap.get(FROM_USER_NAME); + String toUserName = requestMap.get(TO_USER_NAME); + if (requestMap.isEmpty()) { + return null; + } + String reqContent = requestMap.get(CONTENT); + if (asynchronousList.get(fromUserName) != null && !reqContent.equals("鍋滄杈撳嚭")) { + String dissuadeReturn = dissuadeReturn(fromUserName, toUserName, createTimeStr); + return tranReq.returnText(dissuadeReturn); + } + UUID randomUuid = UUID.randomUUID(); + asynchronousList.put(fromUserName, randomUuid.toString()); + String reply; + reply = getReply(reqContent); + String textContent = + "鎴戞鍦ㄦ�濊�冨摝~璇风◢绛夆�︹�n<a href=\"weixin://bizmsgmenu?msgmenucontent=鍋滄杈撳嚭&msgmenuid=101\">鍋滄杈撳嚭</a>"; + String baiduTextContent = "鎴戞槸鐧惧害\n<a href=\"www.baidu.com\">鐧惧害</a>"; + String result = + getString( + fromUserName, + toUserName, + createTimeStr, + reply, + null, + "https://pics0.baidu.com/feed/d31b0ef41bd5ad6e9388c7f8d8eda0d4b6fd3c60.png@f_auto?token=3da8e06f44a46832a7d0f50fa9e92c34", + "鎬讳功璁板紩鐢ㄧ殑杩欎簺鍙よ鑰愪汉瀵诲懗", + "鈥滄病鏈夎鐭╋紝涓嶆垚鏂瑰渾鈥濄�佲�滃繁涓嶆锛岀剦鑳芥浜衡�濄�佲�滃敖灏忚�呭ぇ锛屾厧寰�呰憲鈥濃�︹�︽�讳功璁板紩鐢ㄧ殑杩欎簺鍙よ鑰愪汉瀵诲懗銆�", + "https://www.baidu.com/s?&wd=%E6%80%BB%E4%B9%A6%E8%AE%B0%E5%BC%95%E7%94%A8%E7%9A%84%E8%BF%99%E4%BA%9B%E5%8F%A4%E8%AF%AD%E8%80%90%E4%BA%BA%E5%AF%BB%E5%91%B3", + null); + if (reqContent.equals("鍋滄杈撳嚭")) { + asynchronousList.remove(fromUserName); + _logger.info("鐢ㄦ埛鍋滄杈撳嚭"); + return null; + } + + // 寮傛璋冪敤 aiReplyToTheUserASecondTime +// CompletableFuture.runAsync( +// () -> { +// try { +// SMTAIServerRequest threadTranReq = new SMTAIServerRequest(); +// threadTranReq.initInstance(null, "", null); +// String answer = callAIForAnswerQuestion(reqContent, threadTranReq); // Ai璋冪敤 杩斿洖缁撴灉 +// aiReplyToTheUserASecondTime( +// answer, requestMap.get(FROM_USER_NAME), randomUuid.toString()); +// } catch (Exception e) { +// _logger.error("aiReplyToTheUserASecondTime error", e); +// asynchronousList.remove(request.getParameter("FromUserName")); +// } +// }); + _logger.info("寰俊娑堟伅杩斿弬锛�" + result); + // 杩斿洖 XML 瀛楃涓� + return tranReq.returnText(result); + } + +// public ModelAndView weChatTest(SMTAIServerRequest tranReq) throws Exception { +// String question = "鍐扮鏄粈涔�"; +// tranReq.setAIQuestion(question); +// tranReq.setTextResultMode(); +// +// Set<String> setAgentGroup = new HashSet<>(); +// String weixinGroupId = +// (String) SMTAIServerApp.getApp().getGlobalConfig("weixin.group_id", false); +// setAgentGroup.add(weixinGroupId); +// +// SMTJavaAIChat.questionChat("aliyun", setAgentGroup, "涓氬姟鍦烘櫙", question, null, false, tranReq); +// String text = tranReq.getTextResult(); +// +// return tranReq.returnText(text); +// } + + /** ai鍥炲 */ +// private String callAIForAnswerQuestion(String question, SMTAIServerRequest tranReq) +// throws Exception { +// tranReq.setAIQuestion(question); +// tranReq.setTextResultMode(); +// +// Set<String> setAgentGroup = new HashSet<>(); +// String weixinGroupId = +// (String) SMTAIServerApp.getApp().getGlobalConfig("weixin.group_id", false); +// setAgentGroup.add(weixinGroupId); +// +// SMTJavaAIChat.questionChat("aliyun", setAgentGroup, "涓氬姟鍦烘櫙", question, null, false, tranReq); +// return tranReq.getTextResult(); +// } + + /** 浜屾鍥炲 */ + public void aiReplyToTheUserASecondTime(String answer, String fromUserName, String abortID) throws Exception { - Object weixinParam = SMTAIServerApp.getApp().getGlobalConfig("weixin_core_param", "false"); - JSONObject weixinJson = (JSONObject) weixinParam; - Object appId = weixinJson.get("appId"); - Object secret = weixinJson.get("secret"); - String token = (String) weixinJson.get("token"); - - String[] arr = new String[] {token, timestamp, nonce}; - // 灏唗oken銆乼imestamp銆乶once涓変釜鍙傛暟杩涜瀛楀吀搴忔帓搴� - // Arrays.sort(arr); - sort(arr); - StringBuilder content = new StringBuilder(); - for (String s : arr) { - content.append(s); + String accessToken = getAccessToken(); + if (answer.isEmpty()) answer = "鎶辨瓑锛屾垜鏆傛椂鏃犳硶鐞嗚В鎮ㄧ殑闂銆�"; + SMTJsonWriter jsonWr = new SMTJsonWriter(false); + jsonWr.addKeyValue("touser", fromUserName); + jsonWr.addKeyValue("msgtype", "text"); + jsonWr.beginMap("text"); + { + jsonWr.addKeyValue("content", answer); } - MessageDigest md = null; - String tmpStr = null; - - try { - md = MessageDigest.getInstance("SHA-1"); - // 灏嗕笁涓弬鏁板瓧绗︿覆鎷兼帴鎴愪竴涓瓧绗︿覆杩涜sha1鍔犲瘑 - byte[] digest = md.digest(content.toString().getBytes()); - tmpStr = byteToStr(digest); - } catch (NoSuchAlgorithmException e) { - _logger.error("绛惧悕寮傚父", e); + jsonWr.endMap(); + String url = + String.format( + "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=%s", accessToken); + if (abortID.equals(asynchronousList.get(fromUserName))) { + String s = SMTWXSStatic.sendPost(url, jsonWr.getRootJson()); + asynchronousList.remove(fromUserName); + _logger.info("涓婁紶缁撴灉: : " + s); + } else { + asynchronousList.remove(fromUserName); + _logger.info("寮傛璋冪敤琚彇娑�"); } - content = null; - // 灏唖ha1鍔犲瘑鍚庣殑瀛楃涓插彲涓巗ignature瀵规瘮锛屾爣璇嗚璇锋眰鏉ユ簮浜庡井淇� - - return tmpStr != null && tmpStr.equals(signature.toUpperCase()); } - /** - * 灏嗗瓧鑺傛暟缁勮浆鎹负鍗佸叚杩涘埗瀛楃涓� - * - * @param byteArray - * @return - */ - private static String byteToStr(byte[] byteArray) { - StringBuilder strDigest = new StringBuilder(); - for (byte b : byteArray) { - strDigest.append(byteToHexStr(b)); + /** 鏁版嵁搴撹幏鍙� access_koen */ + public String getAccessToken() throws Exception { + synchronized (this._lockToken) { + SMTDatabase db = SMTAIServerApp.getApp().allocDatabase(); + // try (SMTDatabase db = SMTAIServerApp.getApp().allocDatabase()) { + HashMap<String, String> weixinParam = SMTWXSStatic.getWeixinParam(); + Date curTime = new Date(); + String appId = weixinParam.get("appId"); + + // 鏌ヨ鏈繃鏈熺殑 access_token + DBRecords dbRecord = + db.querySQL( + "SELECT app_id, access_token, expires_time " + + "FROM ai_weixin_token " + + "WHERE app_id = ? " + + " AND ? < expires_time", + new Object[] {appId, curTime}); + + // 鏁版嵁搴撴棤璁板綍锛屼粠寰俊鏈嶅姟鍣ㄨ幏鍙� access_token + List<SMTDatabase.DBRecord> records = dbRecord.getRecords(); + + if (dbRecord.getRowCount() > 0) { + return records.get(0).getString("access_token"); + } + // 寰俊鍙栵紝杩斿洖token骞朵笖淇濆瓨鎴栬鐩栨暟鎹� + else { + + Object[] accessToken = fetchAccessTokenFromWeixinServer(); // 浠庡井淇℃湇鍔″櫒鑾峰彇 access_token + + Date expiresTime = + SMTStatic.calculateTime( + curTime, SMTStatic.SMTCalcTime.ADD_SECOND, ((int) accessToken[1]) / 2); + + String sql = + "INSERT INTO ai_weixin_token (app_id, access_token, expires_time) " + + "VALUES (?, ?, ?) " + + "ON CONFLICT (app_id) " + + // 濡傛灉 app_id 鍐茬獊 + "DO UPDATE SET " + + " access_token = EXCLUDED.access_token, " + + " expires_time = EXCLUDED.expires_time;"; + db.executeSQL(sql, new Object[] {appId, accessToken[0], expiresTime}); + return (String) accessToken[0]; + } + // } catch (Exception e) { + // throw new Exception("Failed to get access token", e); + // + // } } - return strDigest.toString(); } - /** - * 灏嗗瓧鑺傝浆鎹负鍗佸叚杩涘埗瀛楃涓� - * - * @param mByte - * @return - */ - private static String byteToHexStr(byte mByte) { - char[] Digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; - char[] tempArr = new char[2]; - tempArr[0] = Digit[(mByte >>> 4) & 0X0F]; - tempArr[1] = Digit[mByte & 0X0F]; - String s = new String(tempArr); - return s; + /** 浠庡井淇℃湇鍔″櫒鑾峰彇 access_token */ + private Object[] fetchAccessTokenFromWeixinServer() throws Exception { + HashMap<String, String> weixinParam = SMTWXSStatic.getWeixinParam(); + OkHttpClient okHttpClient = new OkHttpClient(); + // 鍒涘缓璇锋眰 + Request request = + new Request.Builder() + .url( + "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + + weixinParam.get("appId") + + "&secret=" + + weixinParam.get("secret")) // 璇锋眰URL + .get() // 浣跨敤GET鏂规硶 + .build(); + Response response = okHttpClient.newCall(request).execute(); + if (!response.isSuccessful() || response.body() == null) { + throw new Exception("can't get weixin token"); + } + Json json = Json.read(response.body().string()); + String accessToken = json.safeGetStr("access_token", null); + String expiresIn = json.safeGetStr("expires_in", null); + + if (accessToken != null) { + return new Object[] {accessToken, SMTStatic.toInt(expiresIn)}; + } else { + _logger.info("can't get weixin token : " + json); + return null; + // throw new Exception("can't get weixin token : " + json); + } } + + @NotNull + private static String getString(String fromUserName, String toUserName, String createTimeStr, String msgType, String content, String mediaId, String title, String description, String musicUrl, String hqMusicUrl) { + StringBuilder xmlBuilder = new StringBuilder(); + xmlBuilder.append("<xml>\n") + .append(" <ToUserName><![CDATA[").append(fromUserName).append("]]></ToUserName>\n") + .append(" <FromUserName><![CDATA[").append(toUserName).append("]]></FromUserName>\n") + .append(" <CreateTime>").append(createTimeStr).append("</CreateTime>\n") + .append(" <MsgType><![CDATA[").append(msgType).append("]]></MsgType>\n"); + + switch (msgType) { + case "text": + xmlBuilder.append(" <Content><![CDATA[").append(content).append("]]></Content>\n"); + break; + case "image": + xmlBuilder.append(" <Image><MediaId><![CDATA[").append(mediaId).append("]]></MediaId></Image>\n"); + break; + case "voice": + xmlBuilder.append(" <Voice><MediaId><![CDATA[").append(mediaId).append("]]></MediaId></Voice>\n"); + break; + case "video": + xmlBuilder.append(" <Video>\n") + .append(" <MediaId><![CDATA[").append(mediaId).append("]]></MediaId>\n") + .append(" <Title><![CDATA[").append(title).append("]]></Title>\n") + .append(" <Description><![CDATA[").append(description).append("]]></Description>\n") + .append(" </Video>\n"); + break; + case "music": + xmlBuilder.append(" <Music>\n") + .append(" <Title><![CDATA[").append(title).append("]]></Title>\n") + .append(" <Description><![CDATA[").append(description).append("]]></Description>\n") + .append(" <MusicUrl><![CDATA[").append(musicUrl).append("]]></MusicUrl>\n") + .append(" <HQMusicUrl><![CDATA[").append(hqMusicUrl).append("]]></HQMusicUrl>\n") + .append(" <ThumbMediaId><![CDATA[").append(mediaId).append("]]></ThumbMediaId>\n") + .append(" </Music>\n"); + break; + case "news": // 鍥炬枃娑堟伅 + xmlBuilder.append(" <ArticleCount>2</ArticleCount>\n") + .append(" <Articles>\n") + .append(" <item>\n") + .append(" <Title><![CDATA[").append(title).append("]]></Title>\n") + .append(" <Description><![CDATA[").append(description).append("]]></Description>\n") + .append(" <PicUrl><![CDATA[").append(mediaId).append("]]></PicUrl>\n") + .append(" <Url><![CDATA[").append(musicUrl).append("]]></Url>\n") + .append(" </item>\n") + .append(" </Articles>\n"); + break; + default: + xmlBuilder.append(" <Content><![CDATA[鏈煡鐨勬秷鎭被鍨媇]></Content>\n"); + } + xmlBuilder.append("</xml>"); + return xmlBuilder.toString(); + } + + /** + * 鐢ㄤ簬娴嬭瘯锛屾甯稿彲浠ヤ笉鐢� + * + * @param reqContent + * @return + */ + @NotNull + private static String getReply(String reqContent) { + String reply; + switch (reqContent) { + case "鏂囧瓧": + reply = "text"; + break; + case "鍥剧墖": + reply = "image"; + break; + case "璇煶": + reply = "voice"; + break; + case "瑙嗛": + reply = "video"; + break; + case "闊充箰": + reply = "music"; + break; + case "鍥炬枃": + reply = "news"; + break; + default: + reply = "text"; + break; + } + return reply; + } + + private static String dissuadeReturn( + String fromUserName, String toUserName, String createTimeStr) { + String xmltemp = + "<xml>\n" + + " <ToUserName><![CDATA[{{{toUser}}}]]></ToUserName>\n" + + " <FromUserName><![CDATA[{{{fromUser}}}]]></FromUserName>\n" + + " <CreateTime>{{{CreateTime}}}</CreateTime>\n" + + " <MsgType><![CDATA[text]]></MsgType>\n" + + " <Content><![CDATA[涓婁竴鏉℃秷鎭繕鍦ㄥ姞杞戒腑鍝︼紝璇风◢绛夋垨鐐瑰嚮涓嬫鍋滄涓婁竴杞洖澶峔n<a href=\"weixin://bizmsgmenu?msgmenucontent=鍋滄杈撳嚭&msgmenuid=101\">鍋滄杈撳嚭</a>]]></Content>\n" + + " <Content><![CDATA[]]></Content>\n" + + "</xml>"; + // 鏇挎崲鍗犱綅绗� + return xmltemp + .replace("{{{toUser}}}", fromUserName) + .replace("{{{fromUser}}}", toUserName) + .replace("{{{CreateTime}}}", createTimeStr); + } } -- Gitblit v1.9.3