package com.smtaiserver.smtaiserver.control;
|
|
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.util.SMTWXSStatic;
|
import com.smtservlet.util.Json;
|
import com.smtservlet.util.SMTJsonWriter;
|
import com.smtservlet.util.SMTStatic;
|
import java.util.*;
|
import javax.servlet.http.HttpServletRequest;
|
import okhttp3.OkHttpClient;
|
import okhttp3.Request;
|
import okhttp3.Response;
|
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.Logger;
|
import org.jetbrains.annotations.NotNull;
|
import org.springframework.web.servlet.ModelAndView;
|
|
public class SMTAIWeixinControl {
|
|
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();
|
|
/**
|
* 微信验证
|
*/
|
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) {
|
|
long l = System.currentTimeMillis() / 1000;
|
String createTimeStr = String.valueOf(l);
|
HttpServletRequest request = tranReq.getRequest();
|
Map<String, String> requestMap = SMTWXSStatic.getWechatReqMap(request);
|
String fromUserName = requestMap.get(FROM_USER_NAME);
|
String toUserName = requestMap.get(TO_USER_NAME);
|
if (requestMap.isEmpty()) {
|
return null;
|
}
|
String reqContent = requestMap.get(CONTENT);
|
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 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,
|
"SI7HPwMI5PL1QV_I9M5AFw6K-1ZVMyTGE0-a5jQM4czTmffKTQpHa6zlYDmvIAPX",
|
"百度百度",
|
"baidubaidu des",
|
"www.baidu.com",
|
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);
|
// 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 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();
|
}
|
|
/** 二次回复 */
|
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");
|
jsonWr.beginMap("text");
|
{
|
jsonWr.addKeyValue("content", answer);
|
}
|
jsonWr.endMap();
|
String url =
|
String.format(
|
"https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=%s", accessToken);
|
if (abortID.equals(asynchronousList.get(fromUserName))) {
|
String s = SMTWXSStatic.sendPost(url, jsonWr.getRootJson());
|
asynchronousList.remove(fromUserName);
|
_logger.info("上传结果: : " + s);
|
} else {
|
asynchronousList.remove(fromUserName);
|
_logger.info("异步调用被取消");
|
}
|
}
|
|
/** 数据库获取 access_koen */
|
public String getAccessToken() throws Exception {
|
synchronized (this._lockToken) {
|
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
|
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);
|
//
|
// }
|
}
|
}
|
|
/** 从微信服务器获取 access_token */
|
private Object[] fetchAccessTokenFromWeixinServer() throws Exception {
|
HashMap<String, String> weixinParam = SMTWXSStatic.getWeixinParam();
|
OkHttpClient okHttpClient = new OkHttpClient();
|
// 创建请求
|
Request request =
|
new Request.Builder()
|
.url(
|
"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="
|
+ weixinParam.get("appId")
|
+ "&secret="
|
+ weixinParam.get("secret")) // 请求URL
|
.get() // 使用GET方法
|
.build();
|
Response response = okHttpClient.newCall(request).execute();
|
if (!response.isSuccessful() || response.body() == null) {
|
throw new Exception("can't get weixin token");
|
}
|
Json json = Json.read(response.body().string());
|
String accessToken = json.safeGetStr("access_token", null);
|
String expiresIn = json.safeGetStr("expires_in", null);
|
|
if (accessToken != null) {
|
return new Object[] {accessToken, SMTStatic.toInt(expiresIn)};
|
} else {
|
_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 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>1</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);
|
}
|
}
|