提交 911a11ef 编写于 作者: 楼国栋

Merge branch 'cherry-pick-a3c00ce3' into 'develop_java8'

微信公众号接收文本消息后脚本处理 java8

See merge request o2oa/o2oa!343
......@@ -28,6 +28,8 @@ public class MPweixin extends ConfigObject {
private String encodingAesKey="";
@FieldDescribe("微信公众号测试菜单的门户地址")
private String portalId = "";
@FieldDescribe("接收到文本消息默认执行的服务脚本id")
private String scriptId = "";
@FieldDescribe("是否启用公众号模版消息")
private Boolean messageEnable;
......@@ -56,6 +58,7 @@ public class MPweixin extends ConfigObject {
this.token = "";
this.encodingAesKey = "";
this.portalId = "";
this.scriptId = "";
this.messageEnable = false;
this.tempMessageId = "";
this.fieldList = new ArrayList<>();
......@@ -177,6 +180,14 @@ public class MPweixin extends ConfigObject {
this.fieldList = fieldList;
}
public String getScriptId() {
return scriptId;
}
public void setScriptId(String scriptId) {
this.scriptId = scriptId;
}
/**
* 微信使用code获取accessToken openid 等数据
*/
......
......@@ -4,7 +4,10 @@ import com.x.base.core.project.jaxrs.CipherManagerJaxrsFilter;
import javax.servlet.annotation.WebFilter;
@WebFilter(urlPatterns = "/jaxrs/mpweixin/menu/*", asyncSupported = true)
@WebFilter(urlPatterns = {
"/jaxrs/mpweixin/menu/*",
"/jaxrs/mpweixin/media/*"
}, asyncSupported = true)
public class MPweixinManagerJaxrsFilter extends CipherManagerJaxrsFilter {
......
......@@ -6,6 +6,13 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.x.base.core.project.Application;
import com.x.base.core.project.config.CenterServer;
import com.x.base.core.project.config.Config;
import com.x.base.core.project.connection.ActionResponse;
import com.x.base.core.project.connection.CipherConnectionAction;
import com.x.base.core.project.gson.GsonPropertyObject;
import com.x.base.core.project.x_jpush_assemble_control;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.Element;
......@@ -38,7 +45,7 @@ public class ActionReceiveMsg extends BaseAction {
Map<String, String> map = xmlToMap(inputStream);
String msgType = map.get("MsgType");
if (WX_MSG_RECEIVE_TYPE_EVENT.equals(msgType)) { //
if (WX_MSG_RECEIVE_TYPE_EVENT.equals(msgType)) { // 事件出发
String event = map.get("Event");
String eventKey = map.get("EventKey");
logger.info("接收到事件消息: event: {} , eventKey: {}", event, eventKey);
......@@ -49,9 +56,46 @@ public class ActionReceiveMsg extends BaseAction {
String content = menu.getContent();
String toUser = map.get("FromUserName");
String fromUser = map.get("ToUserName");
// 特殊消息处理 默认是回复文字消息 如果是媒体消息 需要配置特殊的头 media:image: 后面跟微信素材的mediaId
if (content.startsWith(WX_MSG_BACK_MEDIA_KEY_IMAGE)) {
String mediaId = content.substring(WX_MSG_BACK_MEDIA_KEY_IMAGE.length());
String xml = imageMessageBack(toUser, fromUser, mediaId);
logger.info("回复点击菜单消息: {}", xml);
wo.setText(xml);
} else if (content.startsWith(WX_MSG_BACK_MEDIA_KEY_VOICE)) {
String mediaId = content.substring(WX_MSG_BACK_MEDIA_KEY_VOICE.length());
String xml = voiceMessageBack(toUser, fromUser, mediaId);
logger.info("回复点击菜单消息: {}", xml);
wo.setText(xml);
} else if (content.startsWith(WX_MSG_BACK_MEDIA_KEY_VIDEO)) {
// 视频消息需要多两个字段 title和description 用|分割 如: mediaId|title|description
String videoContent = content.substring(WX_MSG_BACK_MEDIA_KEY_VIDEO.length());
String xml = null;
try {
String[] arr = videoContent.split("\\|");
xml = videoMessageBack(toUser, fromUser, arr[0], arr[1], arr[2]);
} catch (Exception e) {
logger.error(e);
xml = txtMessageBack(toUser, fromUser, content);
}
logger.info("回复点击菜单消息: {}", xml);
wo.setText(xml);
} else if (content.startsWith(WX_MSG_BACK_MEDIA_KEY_SCRIPT)) {
String scriptId = content.substring(WX_MSG_BACK_MEDIA_KEY_SCRIPT.length());
if (StringUtils.isNotBlank(scriptId)) {
ExecuteServiceScriptThread runner1 = new ExecuteServiceScriptThread(toUser, "", scriptId);
Thread thread1 = new Thread(runner1);
thread1.start();
}
//执行脚本了 不回复消息
wo.setText("success");
actionResult.setData(wo);
return actionResult;
} else {
String xml = txtMessageBack(toUser, fromUser, content);
logger.info("回复点击菜单消息: {}", xml);
wo.setText(xml);
}
actionResult.setData(wo);
return actionResult;
} else {
......@@ -72,6 +116,17 @@ public class ActionReceiveMsg extends BaseAction {
logger.info("没有查询到对应的 eventKey:{}", eventKey);
}
}
} else if (WX_MSG_RECEIVE_TYPE_TEXT.equalsIgnoreCase(msgType)) { // 接收到文本消息
String text = map.get("Content");
String toUser = map.get("FromUserName");
logger.info("接收到文本消息: text: {} ", text);
//TODO 目前支持异步执行服务脚本,这里必须马上返回 否则会超时,脚本里面可以调用微信客服消息进行回复. 脚本id暂时先保存到配置文件
String id = Config.mPweixin().getScriptId();
if (StringUtils.isNotBlank(id)) {
ExecuteServiceScriptThread runner1 = new ExecuteServiceScriptThread(toUser, text, id);
Thread thread1 = new Thread(runner1);
thread1.start();
}
} else {
logger.info("未处理消息类型, MsgType: {}", msgType);
}
......@@ -84,6 +139,7 @@ public class ActionReceiveMsg extends BaseAction {
}
// 回复文本消息
private String txtMessageBack(String toUser, String from, String content) {
long time = new Date().getTime();
String xml = "<xml>" +
......@@ -96,8 +152,52 @@ public class ActionReceiveMsg extends BaseAction {
return xml;
}
// 回复图片消息
private String imageMessageBack(String toUser, String from, String mediaId) {
long time = new Date().getTime();
String xml = "<xml>" +
"<ToUserName><![CDATA[" + toUser + "]]></ToUserName>" +
"<FromUserName><![CDATA[" + from + "]]></FromUserName>" +
"<CreateTime>" + time + "</CreateTime>" +
"<MsgType><![CDATA[image]]></MsgType>" +
"<Image><MediaId><![CDATA["+mediaId+"]]></MediaId></Image>" +
"</xml>";
return xml;
}
// 回复音频消息
private String voiceMessageBack(String toUser, String from, String mediaId) {
long time = new Date().getTime();
String xml = "<xml>" +
"<ToUserName><![CDATA[" + toUser + "]]></ToUserName>" +
"<FromUserName><![CDATA[" + from + "]]></FromUserName>" +
"<CreateTime>" + time + "</CreateTime>" +
"<MsgType><![CDATA[voice]]></MsgType>" +
"<Voice><MediaId><![CDATA["+mediaId+"]]></MediaId></Voice>" +
"</xml>";
return xml;
}
//回复视频消息
private String videoMessageBack(String toUser, String from, String mediaId, String title, String description) {
long time = new Date().getTime();
String xml = "<xml>" +
"<ToUserName><![CDATA[" + toUser + "]]></ToUserName>" +
"<FromUserName><![CDATA[" + from + "]]></FromUserName>" +
"<CreateTime>" + time + "</CreateTime>" +
"<MsgType><![CDATA[video]]></MsgType>" +
"<Video>"+
"<MediaId><![CDATA["+mediaId+"]]></MediaId>" +
"<Title><![CDATA["+title+"]]></Title>" +
"<Description><![CDATA["+description+"]]></Description>" +
"</Video>" +
"</xml>";
return xml;
}
private void asyncPostServiceScript(String text) {
}
/**
......@@ -128,4 +228,62 @@ public class ActionReceiveMsg extends BaseAction {
public static class Wo extends WoText {
}
public static class ExecuteServiceScriptThread implements Runnable {
String toUser;
String text;
String scriptId;
ExecuteServiceScriptThread(String toUser, String text, String scriptId) {
this.toUser = toUser;
this.text = text;
this.scriptId = scriptId;
}
@Override
public void run() {
evalRemote();
}
private void evalRemote() {
try {
if (StringUtils.isNotBlank(scriptId)) {
ScriptExecuteBody body = new ScriptExecuteBody();
body.setKeyword(text);
body.setOpenId(toUser);
ActionResponse result = CipherConnectionAction.post(false,
Config.url_x_program_center_jaxrs("invoke",scriptId, "execute"), body);
logger.info("执行脚本结果: " + result.toJson());
} else {
logger.warn("没有配置服务脚本id");
}
} catch (Exception e) {
logger.error(e);
}
}
}
/**
* 脚本执行传入对象
*/
public static class ScriptExecuteBody extends GsonPropertyObject {
// 微信公众号 用户的 openId
private String openId;
private String keyword;
public String getOpenId() {
return openId;
}
public void setOpenId(String openId) {
this.openId = openId;
}
public String getKeyword() {
return keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
}
}
package com.x.program.center.jaxrs.mpweixin;
import com.x.base.core.project.annotation.FieldDescribe;
import com.x.base.core.project.config.Config;
import com.x.base.core.project.config.MPweixin;
import com.x.base.core.project.connection.ConnectionAction;
import com.x.base.core.project.gson.GsonPropertyObject;
import com.x.base.core.project.gson.XGsonBuilder;
import com.x.base.core.project.http.ActionResult;
import com.x.base.core.project.logger.Logger;
import com.x.base.core.project.logger.LoggerFactory;
import com.x.base.core.project.tools.DefaultCharset;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* 上传素材到微信 永久的素材
* Created by fancyLou on 2022/3/1.
* Copyright © 2022 O2. All rights reserved.
*/
public class ActionUploadMediaForever extends BaseAction {
private static Logger logger = LoggerFactory.getLogger(ActionUploadMediaForever.class);
ActionResult<Wo> execute(String type, String fileName, byte[] bytes, FormDataContentDisposition disposition,
String videoTitle, String videoIntroduction) throws Exception {
ActionResult<Wo> result = new ActionResult<Wo>();
if (StringUtils.isEmpty(type)) {
throw new ExceptionNotEmpty("type");
}
type = type.toLowerCase();
if (!"image".equals(type) && !"voice".equals(type) && !"video".equals(type) && !"thumb".equals(type) ) {
throw new ExceptionMediaTypeNotSupport();
}
/** 文件名编码转换 */
if (StringUtils.isEmpty(fileName)) {
try {
fileName = new String(disposition.getFileName().getBytes(DefaultCharset.charset_iso_8859_1),
DefaultCharset.charset);
} catch (Exception e) {
logger.error(e);
}
}
fileName = FilenameUtils.getName(fileName);
if (StringUtils.isEmpty(fileName)) {
throw new ExceptionNotEmpty("fileName");
}
// 视频需要上传 title 和 introduction
if ("video".equalsIgnoreCase(type)) {
if (StringUtils.isEmpty(videoTitle)) {
throw new ExceptionNotEmpty("videoTitle");
}
if (StringUtils.isEmpty(videoIntroduction)) {
throw new ExceptionNotEmpty("videoIntroduction");
}
}
logger.info("发起打包请求,form : " + type + " ," + fileName + " ," + videoTitle + " ," + videoIntroduction);
String boundary = "abcdefghijk";
String end = "\r\n";
String twoHyphens = "--";
String accessToken = Config.mPweixin().accessToken();
String addMediaUrl = MPweixin.default_apiAddress + "/cgi-bin/material/add_material?access_token="+accessToken+"&type="+type;
logger.info("上传永久素材url: "+addMediaUrl);
URL url = new URL(addMediaUrl);
HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection();
httpUrlConnection.setUseCaches(false);
httpUrlConnection.setRequestMethod(ConnectionAction.METHOD_POST);
httpUrlConnection.setDoOutput(true);
httpUrlConnection.setDoInput(true);
// heads
Map<String, String> map = new HashMap<>();
map.put(ConnectionAction.ACCESS_CONTROL_ALLOW_CREDENTIALS,
ConnectionAction.ACCESS_CONTROL_ALLOW_CREDENTIALS_VALUE);
map.put(ConnectionAction.ACCESS_CONTROL_ALLOW_HEADERS,
"x-requested-with, x-request, Content-Type, x-cipher, x-client, x-token, token");
map.put(ConnectionAction.ACCESS_CONTROL_ALLOW_METHODS, ConnectionAction.ACCESS_CONTROL_ALLOW_METHODS_VALUE);
map.put(ConnectionAction.CACHE_CONTROL, ConnectionAction.CACHE_CONTROL_VALUE);
map.put(ConnectionAction.CONTENT_TYPE, "multipart/form-data;boundary=" + boundary);
// 设置字符编码连接参数
map.put("Connection", "Keep-Alive");
map.put("Charset", "UTF-8");
for (Map.Entry<String, String> en : map.entrySet()) {
if (StringUtils.isNotEmpty(en.getValue())) {
httpUrlConnection.setRequestProperty(en.getKey(), en.getValue());
}
}
// form data
try (DataOutputStream ds = new DataOutputStream(httpUrlConnection.getOutputStream())) {
// properties
if ("video".equalsIgnoreCase(type)) {
String videoDes = "{\"title\":\""+videoTitle+"\", \"introduction\":\""+videoIntroduction+"\"}";
writeFormProperties("description", videoDes, boundary, end, twoHyphens, ds);
}
// file
ds.writeBytes(twoHyphens + boundary + end);
ds.writeBytes("Content-Disposition: form-data; " + "name=\"media\";filename=\""
+ URLEncoder.encode(fileName, DefaultCharset.name) + "\"" + end);
ds.writeBytes(end);
ds.write(bytes, 0, bytes.length);
ds.writeBytes(end);
ds.writeBytes(twoHyphens + boundary + twoHyphens + end);
/* close streams */
ds.flush();
}
String wxResult = "";
try (InputStream input = httpUrlConnection.getInputStream()) {
wxResult = IOUtils.toString(input, StandardCharsets.UTF_8);
}
int code = httpUrlConnection.getResponseCode();
if (code != 200) {
throw new Exception("connection{url:" + httpUrlConnection.getURL() + "}, response error{responseCode:"
+ code + "}, response:" + result + ".");
}
httpUrlConnection.disconnect();
logger.info("微信上传素材请求返回,result : " + wxResult);
Wo wo = XGsonBuilder.instance().fromJson(wxResult, Wo.class);
result.setData(wo);
return result;
}
private void writeFormProperties(String name, String value, String boundary, String end, String twoHyphens,
DataOutputStream ds) throws IOException {
ds.writeBytes(twoHyphens + boundary + end);
ds.writeBytes("Content-Disposition: form-data; name=\"" + name + "\"");
ds.writeBytes(end);
ds.writeBytes("Content-Length:" + value.length());
ds.writeBytes(end);
ds.writeBytes(end);
ds.write(value.getBytes(StandardCharsets.UTF_8));
ds.writeBytes(end);
}
public static class Wo extends GsonPropertyObject {
@FieldDescribe( "微信返回的素材id" )
private String media_id;
@FieldDescribe( "微信返回的素材url" )
private String url;
@FieldDescribe( "微信返回code" )
private Integer errcode;
@FieldDescribe( "微信返回错误信息" )
private String errmsg;
public String getMedia_id() {
return media_id;
}
public void setMedia_id(String media_id) {
this.media_id = media_id;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Integer getErrcode() {
return errcode;
}
public void setErrcode(Integer errcode) {
this.errcode = errcode;
}
public String getErrmsg() {
return errmsg;
}
public void setErrmsg(String errmsg) {
this.errmsg = errmsg;
}
}
}
......@@ -13,7 +13,8 @@ import java.util.List;
abstract class BaseAction extends StandardJaxrsAction {
public static final String WX_MSG_RECEIVE_TYPE_EVENT = "event"; //消息类型 事件
public static final String WX_MSG_RECEIVE_TYPE_EVENT = "event"; //公众号接收到的消息类型 事件 主要是菜单点击和订阅取消订阅
public static final String WX_MSG_RECEIVE_TYPE_TEXT = "text"; //公众号接收到的消息类型 文本
......@@ -24,6 +25,12 @@ abstract class BaseAction extends StandardJaxrsAction {
public static final String WX_MSG_RECEIVE_EVENT_VIEW = "VIEW"; //点击菜单跳转链接时的事件推送
public static final String WX_MSG_BACK_MEDIA_KEY_IMAGE = "media:image:";
public static final String WX_MSG_BACK_MEDIA_KEY_VIDEO = "media:video:";
public static final String WX_MSG_BACK_MEDIA_KEY_VOICE = "media:voice:";
public static final String WX_MSG_BACK_MEDIA_KEY_SCRIPT = "o2oa:script:";
......
package com.x.program.center.jaxrs.mpweixin;
import com.x.base.core.project.exception.LanguagePromptException;
/**
* Created by fancyLou on 3/12/21.
* Copyright © 2021 O2. All rights reserved.
*/
public class ExceptionMediaTypeNotSupport extends LanguagePromptException {
private static final long serialVersionUID = 4862362281353270832L;
ExceptionMediaTypeNotSupport() {
super("素材类型不支持,目前只支持 image、voice、video thumb 类型.");
}
}
......@@ -11,6 +11,9 @@ import com.x.base.core.project.jaxrs.ResponseFactory;
import com.x.base.core.project.jaxrs.StandardJaxrsAction;
import com.x.base.core.project.logger.Logger;
import com.x.base.core.project.logger.LoggerFactory;
import com.x.program.center.jaxrs.apppack.ActionAndroidPack;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.*;
......@@ -67,6 +70,34 @@ public class MPWeixinAction extends StandardJaxrsAction {
asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
}
// /////// media 素材 /////////////////////
@JaxrsMethodDescribe(value = "上传永久素材到微信服务器.", action = ActionUploadMediaForever.class)
@POST
@Path("media/add/forever")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
public void addMedia(@Suspended final AsyncResponse asyncResponse, @Context HttpServletRequest request,
@JaxrsParameterDescribe("类型: image、voice、video 、thumb") @FormDataParam("type") String type,
@JaxrsParameterDescribe("视频标题 video的类型必须传") @FormDataParam("videoTitle") String videoTitle,
@JaxrsParameterDescribe("视频介绍 video的类型必须传") @FormDataParam("videoIntroduction") String videoIntroduction,
@JaxrsParameterDescribe("附件名称") @FormDataParam(FILENAME_FIELD) String fileName,
@JaxrsParameterDescribe("附件标识") @FormDataParam(FILE_FIELD) final byte[] bytes,
@JaxrsParameterDescribe("上传文件") @FormDataParam(FILE_FIELD) final FormDataContentDisposition disposition){
ActionResult<ActionUploadMediaForever.Wo> result = new ActionResult<>();
EffectivePerson effectivePerson = this.effectivePerson(request);
try {
result = new ActionUploadMediaForever().execute(type, fileName, bytes, disposition, videoTitle, videoIntroduction);
} catch (Exception e) {
logger.error(e, effectivePerson, request, null);
result.error(e);
}
asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
}
// /menu/* 需要管理员权限
@JaxrsMethodDescribe(value = "微信菜单列表查看.", action = ActionListAllMenu.class)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册