qfrjava
2025-03-06 f24fcdf2e20eccf6b155e83cb11136503138d5f7
JAVA/SMTAIServer/src/main/java/com/smtaiserver/smtaiserver/control/SMTAIWeixinControl.java
@@ -7,11 +7,13 @@
import com.smtaiserver.smtaiserver.database.SMTDatabase.DBRecords;
import com.smtaiserver.smtaiserver.javaai.ast.ASTDBMap;
import com.smtaiserver.smtaiserver.javaai.llm.core.SMTLLMConnect;
import com.smtaiserver.smtaiserver.util.SMTWXSStatic.WeixinuUtil;
import com.smtservlet.core.SMTRequest;
import com.smtservlet.util.Json;
import com.smtservlet.util.SMTJsonWriter;
import com.smtservlet.util.SMTStatic;
import okhttp3.OkHttpClient;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dom4j.Document;
@@ -23,20 +25,18 @@
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.Timestamp;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import static com.smtaiserver.smtaiserver.util.SMTWXSStatic.WeixinuUtil.*;
import static java.util.Arrays.sort;
public class SMTAIWeixinControl {
@@ -46,27 +46,29 @@
  private static final String CONTENT = "Content";
  private static Logger _logger = LogManager.getLogger(SMTAIServerControl.class);
  private Object _lockToken = new Object();
  private final Object _lockToken = new Object();
  private String _tokenValue = null;
  private long _tokenTicket = 0;
  private OkHttpClient _web;
  /** 微信验证 */
  public ModelAndView weChatNotify(SMTAIServerRequest tranReq) throws Exception {
    String method = tranReq.getRequest().getMethod();
    if (method.equals("GET")) return getModelAndView(tranReq);
    if (method.equals("GET")) return WeixinuUtil.getModelAndView(tranReq);
    return reply(tranReq);
  }
  /** 被动回复 */
  private ModelAndView reply(SMTAIServerRequest tranReq) throws Exception {
  private ModelAndView reply(SMTAIServerRequest tranReq) {
    long l = System.currentTimeMillis() / 1000;
    String createTimeStr = String.valueOf(l);
    HttpServletRequest request = tranReq.getRequest();
    Map<String, String> requestMap = getWechatReqMap(request);
    String fromUserName = requestMap.get(FROM_USER_NAME);
    String toUserName = requestMap.get(TO_USER_NAME);
    if (requestMap.isEmpty()) {
      return null;
    }
    //    wechatMessage.setFromUserName(requestMap.get(TO_USER_NAME));
    //    wechatMessage.setToUserName(requestMap.get(FROM_USER_NAME));
    //    wechatMessage.setCreateTime(System.currentTimeMillis() / 1000);
    String xmltemp =
        "<xml>\n"
            + "  <ToUserName><![CDATA[{{{toUser}}}]]></ToUserName>\n"
@@ -75,42 +77,13 @@
            + "  <MsgType><![CDATA[text]]></MsgType>\n"
            + "  <Content><![CDATA[我正在思考哦~请稍等……]]></Content>\n"
            + "</xml>";
    long l = System.currentTimeMillis() / 1000;
    String createTimeStr = String.valueOf(l); // 将 long 转换为 String
    // 替换占位符
    String result =
        xmltemp
            .replace("{{{toUser}}}", requestMap.get(TO_USER_NAME))
            .replace("{{{fromUser}}}", requestMap.get(FROM_USER_NAME))
            .replace("{{{toUser}}}", fromUserName)
            .replace("{{{fromUser}}}", toUserName)
            .replace("{{{CreateTime}}}", createTimeStr);
    String reqContent = requestMap.get(CONTENT);
    // 设置消息类型
    //    switch (reqContent) {
    //      case "文字":
    //        wechatMessage.setMsgType("text");
    //        break;
    //      case "图片":
    //        wechatMessage.setMsgType("image");
    //        break;
    //      case "语音":
    //        wechatMessage.setMsgType("voice");
    //        break;
    //      case "视频":
    //        wechatMessage.setMsgType("video");
    //        break;
    //      case "音乐":
    //        wechatMessage.setMsgType("music");
    //        break;
    //      case "图文":
    //        wechatMessage.setMsgType("news");
    //        break;
    //      default:
    //        wechatMessage.setMsgType("text");
    //        break;
    //    }
    //    MediaVo media = new MediaVo();
    //    wechatMessage.setContent("我正在思考哦~请稍等……");
    // 异步调用 aiReplyToTheUserASecondTime
    CompletableFuture.runAsync(
        () -> {
@@ -121,57 +94,10 @@
            _logger.error("aiReplyToTheUserASecondTime error", e);
          }
        });
    // 设置消息内容
    //    switch (reqContent) {
    //      case "文字":
    //        wechatMessage.setContent("我正在思考哦~请稍等……");
    //        // 异步调用 aiReplyToTheUserASecondTime
    //        CompletableFuture.runAsync(
    //            () -> {
    //              try {
    //                aiReplyToTheUserASecondTime(tranReq, requestMap.get(FROM_USER_NAME));
    //              } catch (Exception e) {
    //                e.printStackTrace();
    //              }
    //            });
    //        break;
    //      case "图片":
    //        media.setMediaId("SI7HPwMI5PL1QV_I9M5AFw6K-1ZVMyTGE0-a5jQM4czTmffKTQpHa6zlYDmvIAPX");
    //        wechatMessage.setImage(media);
    //        break;
    //      case "语音":
    //        media.setMediaId("c6hRH_X2HGwrOa1MiTQAcg35D7M42Xa4VMhyzSFMk8MA0pWFhly19W4K3W5NaH4b");
    //        wechatMessage.setVoice(media);
    //        break;
    //      case "视频":
    //        media.setMediaId("c6hRH_X2HGwrOa1MiTQAcmo7zQjGAIV7uSP1U1S-tsnR0VJXUS0y10Z5FkaueU5Y");
    //        media.setTitle("你好");
    //        media.setDescription("你好啊");
    //        wechatMessage.setVideo(media);
    //        break;
    //      case "音乐":
    //        System.out.println("进入音乐分支");
    //        break;
    //      case "图文":
    //        System.out.println("进入图文分支");
    //        break;
    //      default:
    //        System.out.println("进入默认分支");
    //        wechatMessage.setContent("我不太认识你的输入哦");
    //        break;
    //    }
    try {
      // 将 WechatMessageVO 对象转换为 XML
      //      JAXBContext jaxbContext = JAXBContext.newInstance(WechatMessageVO.class);
      //      Marshaller marshaller = jaxbContext.createMarshaller();
      //      marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
      //      StringWriter sw = new StringWriter();
      //      marshaller.marshal(wechatMessage, sw);
      _logger.info("微信消息返参:" + xmltemp);
      _logger.info("微信消息返参:" + result);
      // 返回 XML 字符串
      return tranReq.returnText(xmltemp.toString());
      return tranReq.returnText(result);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
@@ -203,41 +129,45 @@
    SMTLLMConnect llm = SMTAIServerApp.getApp().allocLLMConnect(null);
    String answer = llm.callWithMessage(new String[] {prompt}, question, tranReq);
    tranReq.traceLLMDebug(answer);
    List<Json> jsonASTList = SMTStatic.convLLMAnswerToJson(answer, true).asJsonList();
    if (jsonASTList.size() > 0) {
      Json jsonAST = jsonASTList.get(0);
      if ("query_water_fee".equals(jsonAST.safeGetStr("call", null))) {
        jsonAST = jsonAST.getJson("args");
        try (ASTDBMap dbMap = new ASTDBMap()) {
          SMTDatabase db = dbMap.getDatabase("DS_74_CHENGTOU");
          DBRecords recs =
              db.querySQL(
                  " SELECT ROUND(SUM("
                      + jsonAST.getJson("value_name").asString()
                      + ")::NUMERIC(10, 2), 2) AS TOTAL"
                      + " FROM chengtou_data.bill_data WHERE billing_date BETWEEN ? AND ?",
                  new Object[] {
                    SMTStatic.toDate(jsonAST.getJson("start_time").asString()),
                    SMTStatic.toDate(jsonAST.getJson("end_time").asString())
                  });
    Json ojsonASTList = SMTStatic.convLLMAnswerToJson(answer, true);
    if (ojsonASTList != null && ojsonASTList.isArray()) {
      List<Json> jsonASTList = ojsonASTList.asJsonList();
      if (jsonASTList.size() > 0) {
        Json jsonAST = jsonASTList.get(0);
        if ("query_water_fee".equals(jsonAST.safeGetStr("call", null))) {
          jsonAST = jsonAST.getJson("args");
          try (ASTDBMap dbMap = new ASTDBMap()) {
            SMTDatabase db = dbMap.getDatabase("DS_74_CHENGTOU");
            DBRecords recs =
                db.querySQL(
                    " SELECT ROUND(SUM("
                        + jsonAST.getJson("value_name").asString()
                        + ")::NUMERIC(10, 2), 2) AS TOTAL"
                        + " FROM chengtou_data.bill_data WHERE billing_date BETWEEN ? AND ?",
                    new Object[] {
                      SMTStatic.toDate(jsonAST.getJson("start_time").asString()),
                      SMTStatic.toDate(jsonAST.getJson("end_time").asString())
                    });
          if (recs.getRowCount() == 0)
            if (recs.getRecord(0).getString(0) == null)
              return "从"
                  + jsonAST.getJson("start_time").asString()
                  + "到"
                  + jsonAST.getJson("end_time").asString()
                  + "的"
                  + jsonAST.getJson("value_title").asString()
                  + "未查到任何数据";
            return "从"
                + jsonAST.getJson("start_time").asString()
                + "到"
                + jsonAST.getJson("end_time").asString()
                + "的"
                + jsonAST.getJson("value_title").asString()
                + "未查到任何数据";
          return "从"
              + jsonAST.getJson("start_time").asString()
              + "到"
              + jsonAST.getJson("end_time").asString()
              + "的"
              + jsonAST.getJson("value_title").asString()
              + "总计"
              + recs.getRecord(0).getString(0);
                + "总计"
                + recs.getRecord(0).getString(0)
                + ("volume".equals(jsonAST.getJson("value_name").asString()) ? "吨" : "元");
          }
        }
      }
    }
@@ -250,7 +180,6 @@
  public ModelAndView aiReplyToTheUserASecondTime(String answer, String fromUserName)
      throws Exception {
    String accessToken = getAccessToken();
    SMTJsonWriter jsonWr = new SMTJsonWriter(false);
    jsonWr.addKeyValue("touser", fromUserName);
    jsonWr.addKeyValue("msgtype", "text");
@@ -259,174 +188,81 @@
      jsonWr.addKeyValue("content", answer);
    }
    jsonWr.endMap();
    //    JSONObject jsonObject = new JSONObject();
    //    jsonObject.put("touser", fromUserName);
    //    jsonObject.put("msgtype", "text");
    //    JSONObject jsonObject1 = new JSONObject();
    //    jsonObject1.put("content", answer);
    //    jsonObject.put("text", jsonObject1);
    //    _logger.info("jsonObject: {}", jsonObject);
    String url =
        String.format(
            "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=%s", accessToken);
    // Map<String, Object> stringObjectMap = jsonObjectToMap(jsonWr.getRootJson());
    String s = sendPost(url, jsonWr.getRootJson());
    _logger.info("上传结果: {}", s);
    return null;
  }
  /** 微信验证 */
  private static ModelAndView getModelAndView(SMTRequest tranReq) throws Exception {
    String signature = tranReq.convParamToString("signature", true);
    String timestamp = tranReq.convParamToString("timestamp", true);
    String nonce = tranReq.convParamToString("nonce", true);
    String echostr = tranReq.convParamToString("echostr", true);
    // 获取微信请求参数
    _logger.info(
        "开始校验此次消息是否来自微信服务器,param->signature:{},\ntimestamp:{},\nnonce:{},\nechostr:{}",
        signature,
        timestamp,
        nonce,
        echostr);
    // 需要验证的时候就启用
    if (checkSignature(signature, timestamp, nonce)) {
      return tranReq.returnText(echostr);
    }
    return tranReq.returnText("");
  }
  public String getAccessToken() throws Exception {
    synchronized (this._lockToken) {
      if (_tokenValue == null || (System.currentTimeMillis() - _tokenTicket) > 3600 * 1000) {
      SMTDatabase db = SMTAIServerApp.getApp().allocDatabase();
      try {
        HashMap<String, String> weixinParam = getWeixinParam();
        String url =
            "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="
                + weixinParam.get("appId")
                + "&secret="
                + weixinParam.get("secret");
        String response = HttpUtil.get(url);
        JSONObject jsonObject = new JSONObject(response);
        if (jsonObject.has("access_token")) {
          String accessToken = jsonObject.getString("access_token");
        long ONE_HOUR_IN_MILLIS = 3600 * 1000;
        long expiresTime = System.currentTimeMillis() + ONE_HOUR_IN_MILLIS;
        Timestamp expiresTimestamp = new Timestamp(expiresTime);
        String appId = weixinParam.get("appId");
          _tokenValue = accessToken;
        } else {
          throw new Exception("can't get weixin token");
        // 查询未过期的 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, expiresTimestamp});
        // 数据库无记录,从微信服务器获取 access_token
        List<SMTDatabase.DBRecord> records = dbRecord.getRecords();
        boolean res = false;
        if (dbRecord.getRowCount() != 0) {
          String expires_time = records.get(0).getString("expires_time");
          long dbExpiresTime = Timestamp.valueOf(expires_time).getTime();
          res = System.currentTimeMillis() <= dbExpiresTime;
        }
        _tokenTicket = System.currentTimeMillis();
        // 微信取,返回token并且保存或覆盖数据
        if (dbRecord.getRowCount() == 0 || !res) {
          String accessToken = fetchAccessTokenFromWeixinServer(); // 从微信服务器获取 access_token
          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, expiresTimestamp});
          return accessToken;
        } else { // 直接拿数据库accesstoken
          return records.get(0).getString("access_token");
        }
      } catch (Exception e) {
        throw new Exception("Failed to get access token: " + e);
      } finally {
        if (db != null) {
          db.close();
        }
      }
      return _tokenValue;
    }
  }
  /** 验证签名util */
  public static boolean checkSignature(String signature, String timestamp, String nonce)
      throws Exception {
  private String fetchAccessTokenFromWeixinServer() throws Exception {
    HashMap<String, String> weixinParam = getWeixinParam();
    String[] arr = new String[] {weixinParam.get("token"), timestamp, nonce};
    // 将token、timestamp、nonce三个参数进行字典序排序
    // Arrays.sort(arr);
    sort(arr);
    StringBuilder content = new StringBuilder();
    for (String s : arr) {
      content.append(s);
    }
    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);
    }
    content = null;
    // 将sha1加密后的字符串可与signature对比,标识该请求来源于微信
    return tmpStr != null && tmpStr.equals(signature.toUpperCase());
  }
  private static HashMap<String, String> getWeixinParam() throws Exception {
    Object weixinParam = SMTAIServerApp.getApp().getGlobalConfig("weixin_core_param", "false");
    _logger.info("微信参数:{}", weixinParam);
    JSONObject weixinJson = new JSONObject(weixinParam.toString());
    String appId = (String) weixinJson.get("appId");
    String secret = (String) weixinJson.get("secret");
    String token = (String) weixinJson.get("token");
    HashMap<String, String> paramMap = new HashMap<>();
    paramMap.put("appId", appId);
    paramMap.put("secret", secret);
    paramMap.put("token", token);
    return paramMap;
  }
  public static Map<String, String> getWechatReqMap(HttpServletRequest request) {
    Map<String, String> requestMap = new HashMap<>();
    SAXReader reader = new SAXReader();
    try (ServletInputStream inputStream = request.getInputStream()) {
      Document document = reader.read(inputStream);
      Element root = document.getRootElement();
      List<Element> elementList = root.elements();
      for (Element e : elementList) {
        requestMap.put(e.getName(), e.getText());
      }
    } catch (IOException | DocumentException e) {
      throw new RuntimeException(e);
    }
    return requestMap;
  }
  /**
   * 将字节数组转换为十六进制字符串
   *
   * @param byteArray
   * @return
   */
  private static String byteToStr(byte[] byteArray) {
    StringBuilder strDigest = new StringBuilder();
    for (byte b : byteArray) {
      strDigest.append(byteToHexStr(b));
    }
    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;
  }
  public static String sendPost(String urlString, Json jsonParam) throws Exception {
    // 将参数转换为 JSON 格式字符串
    // JSONObject jsonParams = new JSONObject(params);
    String payload = jsonParam.toString();
    // 创建连接
    URL url = new URL(urlString);
    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
    connection.setRequestMethod("POST");
    // 设置请求头
    connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
    connection.setDoOutput(true);
    // 写入请求体
    try (OutputStream os = connection.getOutputStream()) {
      byte[] input = payload.getBytes(StandardCharsets.UTF_8);
      os.write(input, 0, input.length);
    }
    // 读取响应
    try (java.io.InputStream in = connection.getInputStream()) {
      java.util.Scanner scanner = new java.util.Scanner(in).useDelimiter("\\A");
      return scanner.hasNext() ? scanner.next() : "";
    String url =
        "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="
            + weixinParam.get("appId")
            + "&secret="
            + weixinParam.get("secret");
    String response = HttpUtil.get(url);
    Json json = Json.read(response);
    String accessToken = json.safeGetStr("access_token", null);
    if (accessToken != null) {
      return accessToken;
    } else {
      throw new Exception("can't get weixin token : " + json);
    }
  }
}