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() : "";
|
}
|
}
|
}
|