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