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 java.io.IOException; import java.io.OutputStream; 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 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 = "\n" + " \n" + " \n" + " {{{CreateTime}}}\n" + " \n" + " \n" + ""; long l = System.currentTimeMillis() / 1000; String createTimeStr = String.valueOf(l); // 将 long 转换为 String // 替换占位符 String result = xmltemp .replace("{{{toUser}}}", requestMap.get(FROM_USER_NAME)) .replace("{{{fromUser}}}", requestMap.get(TO_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("微信消息返参:" + result); // 返回 XML 字符串 return tranReq.returnText(result); } 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); Json ojsonASTList = SMTStatic.convLLMAnswerToJson(answer, true); if(ojsonASTList != null && ojsonASTList.isArray()) { List 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.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() + "总计" + recs.getRecord(0).getString(0) + ("volume".equals(jsonAST.getJson("value_name").asString()) ? "吨" : "元") ; } } } } 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 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 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 : " + jsonObject.toString()); } _tokenTicket = System.currentTimeMillis(); } return _tokenValue; } } /** 验证签名util */ public static boolean checkSignature(String signature, String timestamp, String nonce) throws Exception { HashMap 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 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 paramMap = new HashMap<>(); paramMap.put("appId", appId); paramMap.put("secret", secret); paramMap.put("token", token); return paramMap; } public static Map getWechatReqMap(HttpServletRequest request) { Map requestMap = new HashMap<>(); SAXReader reader = new SAXReader(); try (ServletInputStream inputStream = request.getInputStream()) { Document document = reader.read(inputStream); Element root = document.getRootElement(); List 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() : ""; } } }