qfrjava
8 天以前 b6d6d654ebbd46ff0c428f323c0007ba1e99e99f
JAVA/SMTAIServer/src/main/java/com/smtaiserver/smtaiserver/control/SMTAIWeixinControl.java
@@ -1,24 +1,19 @@
package com.smtaiserver.smtaiserver.control;
import com.fasterxml.uuid.UUIDTimer;
import com.smtaiserver.smtaiserver.core.SMTAIServerApp;
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.javaai.ast.ASTDBMap;
import com.smtaiserver.smtaiserver.javaai.llm.core.SMTLLMConnect;
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 java.util.concurrent.CompletableFuture;
import javax.servlet.http.HttpServletRequest;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
@@ -26,20 +21,22 @@
public class SMTAIWeixinControl {
  private static 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);
    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);
  private final Object _lockToken = new Object();
    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);
  }
    /**
     * 微信验证
     */
    public ModelAndView weChatNotify(SMTAIServerRequest tranReq) throws Exception {
        String method = tranReq.getRequest().getMethod();
        if (method.equals("GET")) return SMTWXSStatic.getModelAndView(tranReq);
        return reply(tranReq);
    }
  /** 被动回复 */
  private ModelAndView reply(SMTAIServerRequest tranReq) {
@@ -54,71 +51,90 @@
      return null;
    }
    String reqContent = requestMap.get(CONTENT);
    if (asynchronousList.get(fromUserName)!=null&&!reqContent.equals("停止输出")){
    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 result = getString(fromUserName, toUserName, createTimeStr);
    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);
          }
        });
    _logger.info("微信消息返参:" + result);
    // 返回 XML 字符串
    return tranReq.returnText(result);
  }
        // 异步调用 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>();
    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);
  }
//  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();
  }
//  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 {
    String accessToken = getAccessToken();
    if (answer.isEmpty()) answer = "抱歉,我暂时无法理解您的问题。";
    SMTJsonWriter jsonWr = new SMTJsonWriter(false);
    jsonWr.addKeyValue("touser", fromUserName);
    jsonWr.addKeyValue("msgtype", "text");
@@ -135,6 +151,7 @@
      asynchronousList.remove(fromUserName);
      _logger.info("上传结果: : " + s);
    } else {
      asynchronousList.remove(fromUserName);
      _logger.info("异步调用被取消");
    }
  }
@@ -142,49 +159,51 @@
  /** 数据库获取 access_koen */
  public String getAccessToken() throws Exception {
    synchronized (this._lockToken) {
      try (SMTDatabase db = SMTAIServerApp.getApp().allocDatabase()) {
        HashMap<String, String> weixinParam = SMTWXSStatic.getWeixinParam();
        Date curTime = new Date();
        String appId = weixinParam.get("appId");
      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
      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();
      // 数据库无记录,从微信服务器获取 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);
      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);
      //
      //      }
    }
  }
@@ -213,41 +232,115 @@
    if (accessToken != null) {
      return new Object[] {accessToken, SMTStatic.toInt(expiresIn)};
    } else {
      throw new Exception("can't get weixin token : " + json);
      _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 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);
  }
  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);
  }
    @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);
    }
}