TangCheng
2025-03-05 35c3d6fb4284eccae884b9f17a0712e9c54343c6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
package com.smtaiserver.smtaiserver.control;
 
import cn.hutool.http.HttpUtil;
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.ast.ASTDBMap;
import com.smtaiserver.smtaiserver.javaai.llm.core.SMTLLMConnect;
import com.smtservlet.core.SMTRequest;
import com.smtservlet.util.Json;
import com.smtservlet.util.SMTJsonWriter;
import com.smtservlet.util.SMTStatic;
 
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.boot.configurationprocessor.json.JSONObject;
import org.springframework.web.servlet.ModelAndView;
 
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.util.*;
import java.util.concurrent.CompletableFuture;
 
import static java.util.Arrays.sort;
 
public class SMTAIWeixinControl {
 
  private static final String FROM_USER_NAME = "FromUserName";
  private static final String TO_USER_NAME = "ToUserName";
  private static final String CONTENT = "Content";
  private static Logger _logger = LogManager.getLogger(SMTAIServerControl.class);
 
  private Object _lockToken = new Object();
  private String _tokenValue = null;
  private long _tokenTicket = 0;
 
  /** 微信验证 */
  public ModelAndView weChatNotify(SMTAIServerRequest tranReq) throws Exception {
    String method = tranReq.getRequest().getMethod();
    if (method.equals("GET")) return getModelAndView(tranReq);
    return reply(tranReq);
  }
 
  /** 被动回复 */
  private ModelAndView reply(SMTAIServerRequest tranReq) throws Exception {
    HttpServletRequest request = tranReq.getRequest();
    Map<String, String> requestMap = getWechatReqMap(request);
    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"
            + "  <FromUserName><![CDATA[{{{fromUser}}}]]></FromUserName>\n"
            + "  <CreateTime>{{{CreateTime}}}</CreateTime>\n"
            + "  <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("{{{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(
        () -> {
          try {
            String answer = callAIForAnswerQuestion(reqContent, tranReq); // Ai调用 返回结果
            aiReplyToTheUserASecondTime(answer, requestMap.get(FROM_USER_NAME));
          } catch (Exception e) {
            _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);
      // 返回 XML 字符串
      return tranReq.returnText(xmltemp.toString());
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
 
  /**
   * ai回复
   *
   * @throws Exception
   */
  private String callAIForAnswerQuestion(String question, SMTAIServerRequest tranReq)
      throws Exception {
    String callFunc =
        "query_water_fee:\n"
            + "    功能:\n"
            + "          查询用户用水量和水费信息\n"
            + "    参数:\n"
            + "          question:用户问题\n"
            + "          user_name:用户名\n"
            + "          value_title:'用水量'或'水费'\n"
            + "          value_name:用水量:volume, 水费:amount\n"
            + "          start_time:查询起始日期,格式:年-月-日\n"
            + "          end_time:查询结束时间,格式:年-月-日\n";
 
    String prompt =
        ((String) SMTAIServerApp.getApp().getGlobalConfig("prompt.agent_tools"))
            .replace("{{{AGENT_TOOL_DEFINE_LIST}}}", callFunc);
 
    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())
                  });
 
          if (recs.getRowCount() == 0)
            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);
        }
      }
    }
    answer = llm.callWithMessage(null, question, tranReq);
 
    return answer;
  }
 
  /** 二次回复 */
  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");
    jsonWr.beginMap("text");
    {
      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) {
        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");
 
          _tokenValue = accessToken;
        } else {
          throw new Exception("can't get weixin token");
        }
        _tokenTicket = System.currentTimeMillis();
      }
      return _tokenValue;
    }
  }
 
  /** 验证签名util */
  public static boolean checkSignature(String signature, String timestamp, String nonce)
      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() : "";
    }
  }
}