/*
* Copyright 2007-2107 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.ymate.platform.module.wechat;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import net.ymate.platform.commons.util.DateTimeUtils;
import net.ymate.platform.commons.util.UUIDUtils;
import net.ymate.platform.module.wechat.base.*;
import net.ymate.platform.module.wechat.message.OutMessage;
import net.ymate.platform.module.wechat.message.TemplateOutMessage;
import net.ymate.platform.module.wechat.support.DefaultMessageProcessor;
import net.ymate.platform.module.wechat.support.HttpClientHelper;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.NullArgumentException;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* <p>
* WeChat
* </p>
* <p>
* 微信公众平台服务接入框架管理器;
* </p>
*
* @author 刘镇(suninformation@163.com)
* @version 0.0.0
* <table style="border:1px solid gray;">
* <tr>
* <th width="100px">版本号</th><th width="100px">动作</th><th
* width="100px">修改人</th><th width="100px">修改时间</th>
* </tr>
* <!-- 以 Table 方式书写修改历史 -->
* <tr>
* <td>0.0.0</td>
* <td>创建类</td>
* <td>刘镇</td>
* <td>2014年3月13日上午1:14:17</td>
* </tr>
* </table>
*/
public class WeChat {
// private static final Log _LOG = LogFactory.getLog(WeChat.class);
/**
* 当前微信公众平台服务接入框架初始化配置对象
*/
private static IWeChatConfig __CFG_CONFIG;
private static boolean __IS_INITED;
private static IAccountDataProvider __dataProvider;
private static IMessageProcessor __messageProcessor;
/**
* 初始化微信公众平台服务接入框架管理器
*
* @param config
* @throws Exception
*/
public static void initialize(IWeChatConfig config) throws Exception {
if (!__IS_INITED) {
if (config == null) {
throw new NullArgumentException("config");
}
if ((__dataProvider = config.getAccountDataProviderImpl()) == null) {
throw new NullArgumentException("AccountDataProviderImpl");
}
__dataProvider.initialize();
//
if ((__messageProcessor = config.getMessageProcessorImpl()) == null) {
// _LOG.debug("Default Message Processor Used");
if (config.getMessageHandlerImpl() == null) {
throw new NullArgumentException("MessageHandlerImpl");
}
__messageProcessor = new DefaultMessageProcessor(config.getMessageHandlerImpl());
}
__CFG_CONFIG = config;
__IS_INITED = true;
}
}
/**
* @return 获取微信开放平台服务接入框架初始化配置对象
*/
public static IWeChatConfig getConfig() throws Exception {
__doCheckModuleInited();
return __CFG_CONFIG;
}
/**
* 销毁模块
*/
public static void destroy() throws Exception {
if (__IS_INITED) {
__IS_INITED = false;
__dataProvider.destroy();
}
}
/**
* @return 返回微信多帐号数据提供者
* @throws Exception
*/
public static IAccountDataProvider getAccountDataProvider() throws Exception {
__doCheckModuleInited();
return __dataProvider;
}
/**
* @return 返回消息处理器
* @throws Exception
*/
public static IMessageProcessor getMessageProcessor() throws Exception {
__doCheckModuleInited();
return __messageProcessor;
}
private static void __doCheckModuleInited() throws Exception {
if (!__IS_INITED) {
throw new Exception("YMP Module WeChat was not Inited");
}
}
public static JSONObject __doCheckJsonResult(String jsonStr) throws Exception {
if (StringUtils.isBlank(jsonStr)) {
return null;
}
JSONObject _result = JSON.parseObject(jsonStr);
if (_result.containsKey("errcode") && _result.getIntValue("errcode") != 0) {
throw new Exception("[" + _result.getIntValue("errcode") + "]" + _result.getString("errmsg"));
}
return _result;
}
private static String __doParamSignatureSort(Map<String, Object> params, boolean encode) {
StringBuilder _paramSB = new StringBuilder();
String[] _keys = params.keySet().toArray(new String[0]);
Arrays.sort(_keys);
boolean _flag = true;
for (String _key : _keys) {
String _value = (String) params.get(_key);
if (StringUtils.isNotEmpty(_value)) {
if (_flag) {
_flag = false;
} else {
_paramSB.append("&");
}
_paramSB.append(_key).append("=");
if (encode) {
try {
_paramSB.append(URLEncoder.encode(_value, HttpClientHelper.DEFAULT_CHARSET));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} else {
_paramSB.append(_value);
}
}
}
return _paramSB.toString();
}
/**
* @param token
* @param signature
* @param timestamp
* @param nonce
* @return 返回签名检查结果
*/
public static boolean checkSignature(String token, String signature, String timestamp, String nonce) {
List<String> _params = new ArrayList<String>();
_params.add(token);
_params.add(timestamp);
_params.add(nonce);
Collections.sort(_params, new Comparator<String>() {
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
});
return DigestUtils.shaHex(_params.get(0) + _params.get(1) + _params.get(2)).equals(signature);
}
/**
* 生成JSAPI配置对象
*
* @param accountId 微信公众帐号ID
* @param url 使用JSAPI的页面URL地址
* @return 返回wx.config对象
* @throws Exception
*/
public static JSONObject wxCreateJsApiConfig(String accountId, String url) throws Exception {
String _jsapiTicket = __dataProvider.getJsApiTicket(accountId);
String _timestamp = DateTimeUtils.currentTimeMillisUTC() + "";
String _noncestr = UUIDUtils.uuid();
//
StringBuilder _signSB = new StringBuilder()
.append("jsapi_ticket=").append(_jsapiTicket).append("&")
.append("noncestr=").append(_noncestr).append("&")
.append("timestamp=").append(_timestamp).append("&")
.append("url=").append(StringUtils.substringBefore(url, "#"));
//
JSONObject _json = new JSONObject();
_json.put("jsapi_ticket", _jsapiTicket);
_json.put("timestamp", _timestamp);
_json.put("nonceStr", _noncestr);
_json.put("url", url);
_json.put("signature", DigestUtils.shaHex(_signSB.toString()));
_json.put("appId", __dataProvider.getAppId(accountId));
return _json;
}
/**
* @param accountId 微信公众帐号ID
* @return 获取AccessToken,在有效期内将被缓存,过期后会重新获取新的Token
* @throws Exception
*/
public static String wxGetAccessToken(String accountId) throws Exception {
__doCheckModuleInited();
if (StringUtils.isBlank(accountId)) {
throw new NullArgumentException("accountId");
}
return __dataProvider.getAccessToken(accountId);
}
/**
* @param accountId 微信公众帐号ID
* @return 基于安全等考虑,需要获知微信服务器的IP地址列表,以便进行相关限制,可以通过该接口获得微信服务器IP地址列表
* @throws Exception
*/
public static String[] wxGetCallbackIP(String accountId) throws Exception {
__doCheckModuleInited();
if (StringUtils.isBlank(accountId)) {
throw new NullArgumentException("accountId");
}
JSONObject _json = __doCheckJsonResult(HttpClientHelper.create().doGet(WX_API.WX_GET_CALLBACK_IP.concat(wxGetAccessToken(accountId))));
return _json.getJSONArray("ip_list").toArray(new String[0]);
}
/**
* @param accountId 微信公众帐号ID
* @param mediaId
* @return 获取媒体资源
* @throws Exception
*/
public static IMediaFileWrapper wxMediaGetFile(String accountId, String mediaId) throws Exception {
__doCheckModuleInited();
IMediaFileWrapper _wrapper = HttpClientHelper.create().doDownload(WX_API.MEDIA_GET + wxGetAccessToken(accountId) + "&media_id=" + mediaId);
if (_wrapper.getErrorMsg() != null && StringUtils.isNotEmpty(_wrapper.getErrorMsg())) {
__doCheckJsonResult(_wrapper.getErrorMsg());
}
return _wrapper;
}
/**
* 上传的多媒体文件有格式和大小限制,如下:
* 图片(image): 128K,支持JPG格式
* 语音(voice):256K,播放长度不超过60s,支持AMR\MP3格式
* 视频(video):1MB,支持MP4格式
* 缩略图(thumb):64KB,支持JPG格式
* 媒体文件在后台保存时间为3天,即3天后media_id失效
*
* @param accountId 微信公众帐号ID
* @param type
* @param file
* @return 上传媒体文件
* @throws Exception
*/
public static WxMediaUploadResult wxMediaUploadFile(String accountId, WxMediaType type, File file) throws Exception {
__doCheckModuleInited();
if (WxMediaType.NEWS.equals(type)) {
throw new UnsupportedOperationException("News media type need use wxMediaUploadNews method.");
}
// {"type":"TYPE","media_id":"MEDIA_ID","created_at":123456789}
// {"type":"TYPE","thubm_media_id":"MEDIA_ID","created_at":123456789}
JSONObject _json = __doCheckJsonResult(HttpClientHelper.create().doUpload(WX_API.MEDIA_UPLOAD + wxGetAccessToken(accountId) + "&type=" + type.toString().toLowerCase(), file));
return new WxMediaUploadResult(type, _json.getString("media_id"), _json.getString("thumb_media_id"), _json.getLong("created_at"));
}
/**
* @param accountId 微信公众帐号ID
* @param articles 图文消息对象集合
* @return 上传图文消息素材
* @throws Exception
*/
public static WxMediaUploadResult wxMediaUploadNews(String accountId, List<WxMassArticle> articles) throws Exception {
__doCheckModuleInited();
if (articles == null || articles.isEmpty()) {
throw new NullArgumentException("articles");
}
StringBuilder _paramSB = new StringBuilder("{ \"articles\": [");
boolean _flag = false;
for (WxMassArticle article : articles) {
if (_flag) {
_paramSB.append(",");
}
_paramSB.append(article.toJSON());
_flag = true;
}
_paramSB.append("]}");
JSONObject _json = __doCheckJsonResult(HttpClientHelper.create().doPost(WX_API.MEDIA_UPLOAD_NEWS + wxGetAccessToken(accountId), _paramSB.toString()));
return new WxMediaUploadResult(WxMediaType.NEWS, _json.getString("media_id"), _json.getString("thumb_media_id"), _json.getLong("created_at"));
}
/**
* @param accountId
* @param video
* @return 上传群发用的视频
* @throws Exception
*/
public static WxMediaUploadResult wxMediaUploadVideo(String accountId, WxMassVideo video) throws Exception {
__doCheckModuleInited();
JSONObject _json = __doCheckJsonResult(HttpClientHelper.create().doPost(WX_API.MEDIA_UPLOAD_VIDEO + wxGetAccessToken(accountId), video.toJSON()));
return new WxMediaUploadResult(WxMediaType.VIDEO, _json.getString("media_id"), _json.getString("thumb_media_id"), _json.getLong("created_at"));
}
/**
* @param accountId
* @param groupId
* @param msgType
* @param mediaIdOrContent
* @return 根据分组进行群发并返回消息ID
* @throws Exception
*/
public static Long wxMassSendByGroupId(String accountId, String groupId, String msgType, String mediaIdOrContent) throws Exception {
__doCheckModuleInited();
StringBuilder _paramSB = new StringBuilder("{");
_paramSB.append("\"filter\": {").append("\"group_id\":").append("\"").append(groupId).append("\"},");
//
String _msgType = WX_MESSAGE.TYPE_MP_NEWS;
String _bodyAttr = "media_id";
if (WX_MESSAGE.TYPE_MP_NEWS.equals(msgType)) {
// default..
} else if (WX_MESSAGE.TYPE_TEXT.equals(msgType)) {
_msgType = WX_MESSAGE.TYPE_TEXT;
_bodyAttr = "content";
} else if (WX_MESSAGE.TYPE_VOICE.equals(msgType)) {
_msgType = WX_MESSAGE.TYPE_VOICE;
} else if (WX_MESSAGE.TYPE_IMAGE.equals(msgType)) {
_msgType = WX_MESSAGE.TYPE_IMAGE;
} else if (WX_MESSAGE.TYPE_MP_VIDEO.equals(msgType)) {
_msgType = WX_MESSAGE.TYPE_MP_VIDEO;
} else {
throw new UnsupportedOperationException("Unsupport Message Type \"" + msgType + "\".");
}
_paramSB.append("\"").append(_msgType).append("\": {").append("\"").append(_bodyAttr).append("\":\"").append(mediaIdOrContent).append("\"},");
_paramSB.append("\"msgtype\": \"").append(_msgType).append("\"}");
JSONObject _json = __doCheckJsonResult(HttpClientHelper.create().doPost(WX_API.MASS_SEND_BY_GROUP + wxGetAccessToken(accountId), _paramSB.toString()));
return _json.getLong("msg_id");
}
/**
* @param accountId
* @param openIds
* @param mediaIdOrContent
* @return 根据OpenID列表群发并返回消息ID
* @throws Exception
*/
public static Long wxMassSendByOpenId(String accountId, List<String> openIds, String msgType, String mediaIdOrContent) throws Exception {
__doCheckModuleInited();
if (openIds == null || openIds.isEmpty()) {
throw new NullArgumentException("openIds");
}
StringBuilder _paramSB = new StringBuilder("{");
_paramSB.append("\"touser\": ").append(JSON.toJSONString(openIds)).append(",");
//
String _msgType = WX_MESSAGE.TYPE_MP_NEWS;
String _bodyAttr = "media_id";
if (WX_MESSAGE.TYPE_MP_NEWS.equals(msgType)) {
// default..
} else if (WX_MESSAGE.TYPE_TEXT.equals(msgType)) {
_msgType = WX_MESSAGE.TYPE_TEXT;
_bodyAttr = "content";
} else if (WX_MESSAGE.TYPE_VOICE.equals(msgType)) {
_msgType = WX_MESSAGE.TYPE_VOICE;
} else if (WX_MESSAGE.TYPE_IMAGE.equals(msgType)) {
_msgType = WX_MESSAGE.TYPE_IMAGE;
} else if (WX_MESSAGE.TYPE_MP_VIDEO.equals(msgType)) {
_msgType = WX_MESSAGE.TYPE_MP_VIDEO;
} else {
throw new UnsupportedOperationException("Unsupport Message Type \"" + msgType + "\".");
}
_paramSB.append("\"").append(_msgType).append("\": {").append("\"").append(_bodyAttr).append("\":\"").append(mediaIdOrContent).append("\"},");
_paramSB.append("\"msgtype\": \"").append(_msgType).append("\"}");
JSONObject _json = __doCheckJsonResult(HttpClientHelper.create().doPost(WX_API.MASS_SEND_BY_OPENID + wxGetAccessToken(accountId), _paramSB.toString()));
return _json.getLong("msg_id");
}
/**
* @param accountId
* @param msgId
* @return 删除群发(只是将消息的图文详情页失效)
* @throws Exception
*/
public static boolean wxMassDelete(String accountId, Long msgId) throws Exception {
__doCheckModuleInited();
JSONObject _result = __doCheckJsonResult(HttpClientHelper.create().doPost(WX_API.MASS_DELETE.concat(wxGetAccessToken(accountId)), "{\"msgid\":" + msgId + "}"));
return 0 == _result.getIntValue("errcode");
}
/**
* @param accountId 微信公众帐号ID
* @param message
* @return 发送客服消息
* @throws Exception
*/
public static String wxMessageSendCustom(String accountId, OutMessage message) throws Exception {
__doCheckModuleInited();
return HttpClientHelper.create().doPost(WX_API.MESSAGE_SEND.concat(wxGetAccessToken(accountId)), message.toJSON());
}
/**
* @param accountId 微信公众帐号ID
* @param message 模板消息对象
* @return 发送模板消息,若成功则返回msgid
* @throws Exception
*/
public static String wxMessageSendTemplate(String accountId, TemplateOutMessage message) throws Exception {
__doCheckModuleInited();
JSONObject _result = __doCheckJsonResult(HttpClientHelper.create().doPost(WX_API.MESSAGE_TEMPLATE_SEND.concat(wxGetAccessToken(accountId)), message.toJSON()));
return _result.getString("msgid");
}
/**
* @param accountId 微信公众帐号ID
* @param openid
* @param lang 语言(可选)
* @return 获取用户基本信息
* @throws Exception
*/
public static WxUser wxUserGetInfo(String accountId, String openid, WxLangType lang) throws Exception {
__doCheckModuleInited();
Map<String, String> _params = new HashMap<String, String>();
_params.put("access_token", wxGetAccessToken(accountId));
_params.put("openid", openid);
if (lang != null) {
_params.put("lang", lang.toString());
}
JSONObject _json = __doCheckJsonResult(HttpClientHelper.create().doGet(WX_API.USER_INFO, _params));
return new WxUser(_json.getString("openid"),
_json.getString("nickname"), _json.getInteger("sex"),
_json.getString("city"), _json.getString("province"),
_json.getString("country"), _json.getString("headimgurl"),
_json.getInteger("subscribe"), _json.getLong("subscribe_time"));
}
/**
* @param accountId 微信公众帐号ID
* @param next_openid
* @return 获取关注者列表
* @throws Exception
*/
public static WxFollwersResult wxUserGetList(String accountId, String next_openid) throws Exception {
__doCheckModuleInited();
Map<String, String> params = new HashMap<String, String>();
params.put("access_token", wxGetAccessToken(accountId));
params.put("next_openid", next_openid);
JSONObject _json = __doCheckJsonResult(HttpClientHelper.create().doGet(WX_API.USER_GET, params));
List<String> _datas = null;
if (_json.containsKey("data") && _json.getJSONObject("data").containsKey("openid")) {
_datas = JSON.parseArray(_json.getJSONObject("data").getJSONArray("openid").toJSONString(), String.class);
}
if (_datas == null) {
_datas = Collections.emptyList();
}
return new WxFollwersResult(_json.getLongValue("total"), _json.getIntValue("count"), _datas, _json.getString("next_openid"));
}
/**
* @param accountId
* @param openid 用户标识
* @param remark 新的备注名,长度必须小于30字符
* @return 对指定用户设置备注名
* @throws Exception
*/
public static boolean wxUserUpdateRemark(String accountId, String openid, String remark) throws Exception {
__doCheckModuleInited();
Map<String, String> _params = new HashMap<String, String>();
_params.put("access_token", wxGetAccessToken(accountId));
_params.put("openid", openid);
_params.put("remark", remark);
JSONObject _json = __doCheckJsonResult(HttpClientHelper.create().doGet(WX_API.USER_UPDATE_REMARK, _params));
return 0 == _json.getIntValue("errcode");
}
/**
* @param accountId 微信公众帐号ID
* @param name
* @return 创建分组
* @throws Exception
*/
public static WxGroup wxGroupCreate(String accountId, String name) throws Exception {
__doCheckModuleInited();
JSONObject _groupJSON = new JSONObject();
JSONObject _nameJSON = new JSONObject();
_nameJSON.put("name", name);
_groupJSON.put("group", _nameJSON);
return JSON.toJavaObject(__doCheckJsonResult(HttpClientHelper.create().doPost(WX_API.GROUP_CREATE.concat(wxGetAccessToken(accountId)), _groupJSON.toString())), WxGroup.class);
}
/**
* @param accountId 微信公众帐号ID
* @return 查询所有分组
* @throws Exception
*/
public static List<WxGroup> wxGroupGetList(String accountId) throws Exception {
__doCheckModuleInited();
JSONObject _json = __doCheckJsonResult(HttpClientHelper.create().doGet(WX_API.GROUP_GET.concat(wxGetAccessToken(accountId))));
if (_json.containsKey("groups")) {
return JSON.parseArray(_json.getJSONArray("groups").toJSONString(), WxGroup.class);
}
return Collections.emptyList();
}
/**
* @param accountId 微信公众帐号ID
* @param openid
* @return 查询用户所在分组ID
* @throws Exception
*/
public static int wxGroupGetId(String accountId, String openid) throws Exception {
__doCheckModuleInited();
JSONObject _openidJSON = new JSONObject();
_openidJSON.put("openid", openid);
JSONObject _json = __doCheckJsonResult(HttpClientHelper.create().doPost(WX_API.GROUP_GET_ID.concat(wxGetAccessToken(accountId)), _openidJSON.toString()));
return _json.getIntValue("groupid");
}
/**
* @param accountId 微信公众帐号ID
* @param id
* @param name
* @return 修改分组名
* @throws Exception
*/
public static boolean wxGroupUpdate(String accountId, String id, String name) throws Exception {
__doCheckModuleInited();
JSONObject _groupJSON = new JSONObject();
_groupJSON.put("id", id);
_groupJSON.put("name", name);
JSONObject _paramJSON = new JSONObject();
_paramJSON.put("group", _groupJSON);
JSONObject _json = __doCheckJsonResult(HttpClientHelper.create().doPost(WX_API.GROUP_UPDATE.concat(wxGetAccessToken(accountId)), _paramJSON.toString()));
return 0 == _json.getIntValue("errcode");
}
/**
* @param accountId 微信公众帐号ID
* @param openid
* @param to_groupid
* @return 移动用户分组
* @throws Exception
*/
public static boolean wxGroupMembersMove(String accountId, String openid, String to_groupid) throws Exception {
__doCheckModuleInited();
JSONObject _paramJSON = new JSONObject();
_paramJSON.put("openid", openid);
_paramJSON.put("to_groupid", to_groupid);
JSONObject _json = __doCheckJsonResult(HttpClientHelper.create().doPost(WX_API.GROUP_MEMBERS_UPDATE.concat(wxGetAccessToken(accountId)), _paramJSON.toString()));
return 0 == _json.getIntValue("errcode");
}
/**
* @param accountId 微信公众帐号ID
* @param menu
* @return 创建菜单
* @throws Exception
*/
public static boolean wxMenuCreate(String accountId, WxMenu menu) throws Exception {
__doCheckModuleInited();
JSONObject _result = __doCheckJsonResult(HttpClientHelper.create().doPost(WX_API.MENU_CREATE.concat(wxGetAccessToken(accountId)), JSON.toJSONString(menu)));
return 0 == _result.getIntValue("errcode");
}
/**
* @param accountId 微信公众帐号ID
* @return 查询菜单
* @throws Exception
*/
public static WxMenu wxMenuGet(String accountId) throws Exception {
__doCheckModuleInited();
JSONObject _json = __doCheckJsonResult(HttpClientHelper.create().doGet(WX_API.MENU_GET.concat(wxGetAccessToken(accountId))));
return JSON.toJavaObject(_json.getJSONObject("menu"), WxMenu.class);
}
/**
* @param accountId 微信公众帐号ID
* @return 删除自定义菜单
* @throws Exception
*/
public static boolean wxMenuDelete(String accountId) throws Exception {
__doCheckModuleInited();
JSONObject _result = __doCheckJsonResult(HttpClientHelper.create().doGet(WX_API.MENU_DELETE.concat(wxGetAccessToken(accountId))));
return 0 == _result.getIntValue("errcode");
}
/**
* @param accountId 微信公众帐号ID
* @param scene_id 场景ID,场景ID若大于100000时自动转换成为临时二维码
* @param expire_seconds 二维码有效时间,0表示永久保存,最大1800,单位:秒
* @return 创建二维码Ticket
* @throws Exception
*/
public static WxQRCode wxQRCodeCreate(String accountId, int scene_id, int expire_seconds) throws Exception {
__doCheckModuleInited();
JSONObject _paramJSON = new JSONObject();
if (expire_seconds > 0 || scene_id > 100000) {
_paramJSON.put("action_name", "QR_SCENE");
_paramJSON.put("expire_seconds", expire_seconds <= 0 ? 1800 : expire_seconds);
}
JSONObject _sceneJSON = new JSONObject();
_sceneJSON.put("scene_id", scene_id);
JSONObject _infoJSON = new JSONObject();
_infoJSON.put("scene", _sceneJSON);
_paramJSON.put("action_info", _infoJSON);
// {"ticket":"gQG28DoAAAAAAAAAASxodHRwOi8vd2VpeGluLnFxLmNvbS9xL0FuWC1DNmZuVEhvMVp4NDNMRnNRAAIEesLvUQMECAcAAA==","expire_seconds":1800}
JSONObject _json = __doCheckJsonResult(HttpClientHelper.create().doPost(WX_API.QRCODE_CREATE.concat(wxGetAccessToken(accountId)), _paramJSON.toString()));
return new WxQRCode(scene_id, _json.getString("ticket"), _json.getIntValue("expire_seconds"));
}
/**
* @param ticket
* @return 返回二维码访问URL地址
*/
public static String wxQRCodeShowURL(String ticket) {
try {
return WX_API.QRCODE_SHOW.concat(URLEncoder.encode(ticket, "UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
/**
* @param accountId 微信公众帐号ID
* @param needUserInfo 是否弹出授权页面
* @param state 自定义参数(a-zA-Z0-9)
* @return 返回微信用户授权URL地址
* @throws Exception
*/
public static String wxOAuthGetCodeURL(String accountId, boolean needUserInfo, String state) throws Exception {
String _appId = __dataProvider.getAppId(accountId);
if (StringUtils.isBlank(_appId)) {
throw new NullArgumentException("appId");
}
return wxOAuthGetCodeURL(accountId, needUserInfo, state, __dataProvider.getRedirectURI(accountId));
}
/**
* @param accountId 微信公众帐号ID
* @param needUserInfo 是否弹出授权页面
* @param state 自定义参数(a-zA-Z0-9)
* @param redirectURI 自定义重定向地址
* @return 返回微信用户授权URL地址
* @throws Exception
*/
public static String wxOAuthGetCodeURL(String accountId, boolean needUserInfo, String state, String redirectURI) throws Exception {
__doCheckModuleInited();
String _appId = __dataProvider.getAppId(accountId);
if (StringUtils.isBlank(_appId)) {
throw new NullArgumentException("appId");
}
if (StringUtils.isBlank(redirectURI)) {
throw new NullArgumentException("redirectURI");
}
Map<String, Object> _params = new HashMap<String, Object>();
_params.put("appid", _appId);
_params.put("response_type", "code");
_params.put("redirect_uri", URLEncoder.encode(redirectURI, HttpClientHelper.DEFAULT_CHARSET));
_params.put("scope", needUserInfo ? "snsapi_userinfo" : "snsapi_base");
_params.put("state", StringUtils.defaultIfEmpty(state, "") + "#wechat_redirect");
return WX_API.OAUTH_GET_CODE.concat(__doParamSignatureSort(_params, false));
}
/**
* { "access_token":"ACCESS_TOKEN", "expires_in":7200, "refresh_token":"REFRESH_TOKEN", "openid":"OPENID", "scope":"SCOPE" }
*
* @param accountId 微信公众帐号ID
* @param code
* @return 通过Code换取网页授权的AccessToken
* @throws Exception
*/
public static WxOAuthToken wxOAuthGetToken(String accountId, String code) throws Exception {
__doCheckModuleInited();
String _appId = __dataProvider.getAppId(accountId);
if (StringUtils.isBlank(_appId)) {
throw new NullArgumentException("appId");
}
String _appSecret = __dataProvider.getAppSecret(accountId);
if (StringUtils.isBlank(_appSecret)) {
throw new NullArgumentException("appSecret");
}
if (StringUtils.isBlank(code)) {
throw new NullArgumentException("code");
}
Map<String, String> _params = new HashMap<String, String>();
_params.put("appid", _appId);
_params.put("secret", _appSecret);
_params.put("code", code);
_params.put("grant_type", "authorization_code");
JSONObject _json = __doCheckJsonResult(HttpClientHelper.create().doGet(WX_API.OAUTH_ACCESS_TOKEN, _params));
return new WxOAuthToken(_json.getString("access_token"), _json.getIntValue("expires_in"), _json.getString("refresh_token"), _json.getString("openid"), _json.getString("scope"));
}
/**
* @param accountId 微信公众帐号ID
* @param refreshToken 哪个想刷就刷哪个~
* @return 刷新AccessToken
* @throws Exception
*/
public static WxOAuthToken wxOAuthRefreshToken(String accountId, String refreshToken) throws Exception {
__doCheckModuleInited();
String _appId = __dataProvider.getAppId(accountId);
if (StringUtils.isBlank(_appId)) {
throw new NullArgumentException("appId");
}
if (StringUtils.isBlank(refreshToken)) {
throw new NullArgumentException("refreshToken");
}
Map<String, String> _params = new HashMap<String, String>();
_params.put("appid", _appId);
_params.put("grant_type", "refresh_token");
_params.put("refresh_token", refreshToken);
JSONObject _json = __doCheckJsonResult(HttpClientHelper.create().doGet(WX_API.OAUTH_REFRESH_TOKEN, _params));
return new WxOAuthToken(_json.getString("access_token"), _json.getIntValue("expires_in"), _json.getString("refresh_token"), _json.getString("openid"), _json.getString("scope"));
}
/**
* @param openid
* @param lang 语言(可选)
* @return 拉取用户信息
* @throws Exception
*/
public static WxOAuthUser wxOAuthUserGetInfo(String oauthAccessToken, String openid, WxLangType lang) throws Exception {
__doCheckModuleInited();
if (StringUtils.isBlank(oauthAccessToken)) {
throw new NullArgumentException("oauthAccessToken");
}
if (StringUtils.isBlank(openid)) {
throw new NullArgumentException("openid");
}
Map<String, String> _params = new HashMap<String, String>();
_params.put("openid", openid);
if (lang != null) {
_params.put("lang", lang.toString());
}
JSONObject _json = __doCheckJsonResult(HttpClientHelper.create().doGet(WX_API.OAUTH_USER_INFO.concat(oauthAccessToken), _params));
return new WxOAuthUser(_json.getString("openid"),
_json.getString("nickname"), _json.getInteger("sex"),
_json.getString("province"), _json.getString("city"),
_json.getString("country"), _json.getString("headimgurl"),
JSON.parseArray(_json.getJSONArray("privilege").toJSONString(), String.class));
}
/**
* 检验授权凭证(access_token)是否有效
*
* @param oauthAccessToken 网页授权接口调用凭证
* @param openid 用户的唯一标识
* @return true / false
* @throws Exception
*/
public static boolean wxOAuthAuthAccessToken(String oauthAccessToken, String openid) throws Exception {
__doCheckModuleInited();
if (StringUtils.isBlank(oauthAccessToken)) {
throw new NullArgumentException("oauthAccessToken");
}
if (StringUtils.isBlank(openid)) {
throw new NullArgumentException("openid");
}
Map<String, String> _params = new HashMap<String, String>();
_params.put("openid", openid);
try {
__doCheckJsonResult(HttpClientHelper.create().doGet(WX_API.OAUTH_AUTH_ACCESS_TOKEN.concat(oauthAccessToken), _params));
} catch (Exception e) {
return false;
}
return true;
}
/**
* @param accountId
* @param longUrl 需要转换的长链接
* @return 长链接转成短链接
* @throws Exception
*/
public static String wxShortUrl(String accountId, String longUrl) throws Exception {
__doCheckModuleInited();
JSONObject _json = __doCheckJsonResult(HttpClientHelper.create().doGet(WX_API.SHORT_URL.concat(wxGetAccessToken(accountId))));
return _json.getString("short_url");
}
/**
* 上传永久图文素材
*
* @param accountId
* @param articles
* @return
* @throws Exception
*/
public static String wxMaterialAddNews(String accountId, List<WxMassArticle> articles) throws Exception {
__doCheckModuleInited();
if (articles == null || articles.isEmpty()) {
throw new NullArgumentException("articles");
}
StringBuilder _paramSB = new StringBuilder("{ \"articles\": [");
boolean _flag = false;
for (WxMassArticle article : articles) {
if (_flag) {
_paramSB.append(",");
}
_paramSB.append(article.toJSON());
_flag = true;
}
_paramSB.append("]}");
JSONObject _json = __doCheckJsonResult(HttpClientHelper.create().doPost(WX_API.MATERIAL_ADD_NEWS + wxGetAccessToken(accountId), _paramSB.toString()));
return _json.getString("media_id");
}
/**
* 上传永久媒体素材(不包括视频)
*
* @param accountId
* @param type
* @param file
* @return
* @throws Exception
*/
public static String wxMaterialAddNews(String accountId, WxMediaType type, File file) throws Exception {
__doCheckModuleInited();
if (WxMediaType.NEWS.equals(type)) {
throw new UnsupportedOperationException("News media type need use wxMediaUploadNews method.");
} else if (WxMediaType.VIDEO.equals(type) || WxMediaType.SHORT_VIDEO.equals(type)) {
throw new UnsupportedOperationException("Unsupport Video and Short Video");
}
JSONObject _json = __doCheckJsonResult(HttpClientHelper.create().doUpload(WX_API.MATERIAL_ADD_MATERIAL + wxGetAccessToken(accountId) + "&type=" + type.toString().toLowerCase(), file));
return _json.getString("media_id");
}
/**
* 上传永久视频素材
*
* @param accountId
* @param title
* @param introduction
* @param file
* @return
* @throws Exception
*/
public static String wxMaterialAddNews(String accountId, String title, String introduction, File file) throws Exception {
__doCheckModuleInited();
String _description = URLEncoder.encode("{\"title\":\"" + title + "\", \"introduction\":\"" + introduction + "\"}", "UTF-8");
JSONObject _json = __doCheckJsonResult(HttpClientHelper.create().doUpload(WX_API.MATERIAL_ADD_MATERIAL + wxGetAccessToken(accountId) + "&description=" + _description + "&type=" + WxMediaType.VIDEO.toString().toLowerCase(), file));
return _json.getString("media_id");
}
/**
* 删除永久素材
*
* @param accountId
* @param mediaId
* @return
* @throws Exception
*/
public static boolean wxMaterialDelete(String accountId, String mediaId) throws Exception {
__doCheckModuleInited();
JSONObject _result = __doCheckJsonResult(HttpClientHelper.create().doPost(WX_API.MATERIAL_DEL_MATERIAL.concat(wxGetAccessToken(accountId)), "{\"media_id\":\"" + mediaId + "\"}"));
return 0 == _result.getIntValue("errcode");
}
/**
* 获取素材列表
*
* @param accountId
* @param type
* @param offset
* @param count
* @return
* @throws Exception
*/
public static WxMaterialResult wxMaterialGetNews(String accountId, WxMediaType type, int offset, int count) throws Exception {
__doCheckModuleInited();
String _params = "{\"type\":\"" + type.toString().toLowerCase() + "\", \"offset\":" + offset + ", \"count\":" + count + "}";
JSONObject _resultJSON = __doCheckJsonResult(HttpClientHelper.create().doPost(WX_API.MATERIAL_BATCH_GET.concat(wxGetAccessToken(accountId)), _params));
if (_resultJSON != null) {
WxMaterialResult _result = JSON.toJavaObject(_resultJSON, WxMaterialResult.class);
return _result;
}
return null;
}
/**
* 获取永久素材(图文和视频)
*
* @param accountId
* @param mediaId
* @return
* @throws Exception
*/
public static WxNewsItem wxMaterialNewOrVideoGet(String accountId, String mediaId) throws Exception {
__doCheckModuleInited();
JSONObject _resultJSON = __doCheckJsonResult(HttpClientHelper.create().doPost(WX_API.MATERIAL_GET_MATERIAL.concat(wxGetAccessToken(accountId)), "{\"media_id\":\"" + mediaId + "\"}"));
if (_resultJSON != null) {
WxNewsItem _result = JSON.toJavaObject(_resultJSON, WxNewsItem.class);
return _result;
}
return null;
}
/**
* 获取永久素材(非图文和非视频)
*
* @param accountId
* @param mediaId
* @return
* @throws Exception
*/
public static IMediaFileWrapper wxMaterialFileGet(String accountId, String mediaId) throws Exception {
__doCheckModuleInited();
return HttpClientHelper.create().doDownload(WX_API.MATERIAL_GET_MATERIAL.concat(wxGetAccessToken(accountId)), "{\"media_id\":\"" + mediaId + "\"}");
}
/**
* 修改永久图文素材
*
* @param accountId
* @param mediaId
* @param index
* @param article
* @return
* @throws Exception
*/
public static boolean wxMaterialUpdate(String accountId, String mediaId, int index, WxMassArticle article) throws Exception {
__doCheckModuleInited();
JSONObject _params = new JSONObject();
_params.put("media_id", mediaId);
_params.put("index", index);
_params.put("articles", JSON.toJSON(article));
JSONObject _result = __doCheckJsonResult(HttpClientHelper.create().doPost(WX_API.MATERIAL_UPDATE_NEWS.concat(wxGetAccessToken(accountId)), _params.toJSONString()));
return 0 == _result.getIntValue("errcode");
}
/**
* 获取素材总数
*
* @param accountId
* @return
* @throws Exception
*/
public static WxMaterialCount wxMaterialCount(String accountId) throws Exception {
__doCheckModuleInited();
JSONObject _resultJSON = __doCheckJsonResult(HttpClientHelper.create().doPost(WX_API.MATERIAL_GET_COUNT.concat(wxGetAccessToken(accountId)), ""));
if (_resultJSON != null) {
WxMaterialCount _result = JSON.toJavaObject(_resultJSON, WxMaterialCount.class);
return _result;
}
return null;
}
/**
* @param request Http请求对象
* @param needVer5 是否需要判断版本大于5.0
* @return 判断当前是否在微信环境
*/
public static boolean isInWeChat(HttpServletRequest request, boolean needVer5) {
String userAgent = request.getHeader("User-Agent");
if (StringUtils.isNotBlank(userAgent)) {
Pattern p = Pattern.compile("MicroMessenger/(\\d+).+");
Matcher m = p.matcher(userAgent);
String version = null;
if (m.find()) {
version = m.group(1);
}
if (null != version) {
if (needVer5) {
return NumberUtils.toInt(version) >= 5;
}
return true;
}
}
return false;
}
/**
* <p>
* WX_MESSAGE
* </p>
* <p>
* 微信消息类型
* </p>
*/
public static class WX_MESSAGE {
public final static String TYPE_TEXT = "text";
public final static String TYPE_LOCATION = "location";
public final static String TYPE_IMAGE = "image";
public final static String TYPE_LINK = "link";
public final static String TYPE_VOICE = "voice";
public final static String TYPE_EVENT = "event";
public final static String TYPE_VIDEO = "video";
public final static String TYPE_SHORT_VIDEO = "shortvideo";
public final static String TYPE_NEWS = "news";
public final static String TYPE_MP_NEWS = "mpnews"; // 此类型仅用于群发图文
public final static String TYPE_MP_VIDEO = "mpvideo"; // 此类型仅用于群发视频
public final static String TYPE_MUSIC = "music";
public final static String EVENT_LOCATION = "LOCATION";
public final static String EVENT_SCAN = "SCAN";
public final static String EVENT_SUBSCRIBE = "subscribe";
public final static String EVENT_UNSUBSCRIBE = "unsubscribe";
public final static String EVENT_CLICK = "click";
public final static String EVENT_VIEW = "view";
public final static String EVENT_MASS_SEND_JOB_FINISH = "MASSSENDJOBFINISH";
public final static String EVENT_TEMPLATE_SEND_JOB_FINISH = "TEMPLATESENDJOBFINISH";
}
/**
* <p>
* WxMediaType
* </p>
* <p>
* 微信媒体资源类型
* </p>
*/
public static enum WxMediaType {
IMAGE, VOICE, VIDEO, SHORT_VIDEO, THUMB, NEWS
}
/**
* <p>
* WxLangType
* </p>
* <p>
* 微信支持的语言
* </p>
*/
public static enum WxLangType {
zh_CN, zh_TW, en
}
/**
* <p>
* WX_API
* </p>
* <p>
* 微信API地址
* </p>
*/
public static class WX_API {
public static final String WX_ACCESS_TOKEN = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential";
public static final String WX_GET_CALLBACK_IP = "https://api.weixin.qq.com/cgi-bin/getcallbackip?access_token=";
public static final String MEDIA_GET = "http://file.api.weixin.qq.com/cgi-bin/media/get?access_token=";
public static final String MEDIA_UPLOAD = "http://file.api.weixin.qq.com/cgi-bin/media/upload?access_token=";
public static final String MEDIA_UPLOAD_NEWS = "https://api.weixin.qq.com/cgi-bin/media/uploadnews?access_token=";
public static final String MEDIA_UPLOAD_VIDEO = "http://file.api.weixin.qq.com/cgi-bin/media/uploadvideo?access_token=";
public static final String GROUP_CREATE = "https://api.weixin.qq.com/cgi-bin/groups/create?access_token=";
public static final String GROUP_GET = "https://api.weixin.qq.com/cgi-bin/groups/get?access_token=";
public static final String GROUP_GET_ID = "https://api.weixin.qq.com/cgi-bin/groups/getid?access_token=";
public static final String GROUP_UPDATE = "https://api.weixin.qq.com/cgi-bin/groups/update?access_token=";
public static final String GROUP_MEMBERS_UPDATE = "https://api.weixin.qq.com/cgi-bin/groups/members/update?access_token=";
public static final String USER_INFO = "https://api.weixin.qq.com/cgi-bin/user/info";
public static final String USER_GET = "https://api.weixin.qq.com/cgi-bin/user/get";
public static final String USER_UPDATE_REMARK = "https://api.weixin.qq.com/cgi-bin/user/info/updateremark?access_token=";
public static final String MENU_CREATE = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=";
public static final String MENU_GET = "https://api.weixin.qq.com/cgi-bin/menu/get?access_token=";
public static final String MENU_DELETE = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=";
public static final String QRCODE_CREATE = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=";
public static final String QRCODE_SHOW = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=";
public static final String MESSAGE_SEND = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=";
public static final String MESSAGE_TEMPLATE_SEND = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=";
public static final String MASS_SEND_BY_GROUP = "https://api.weixin.qq.com/cgi-bin/message/mass/sendall?access_token=";
public static final String MASS_SEND_BY_OPENID = "https://api.weixin.qq.com/cgi-bin/message/mass/send?access_token=";
public static final String MASS_DELETE = "https://api.weixin.qq.com//cgi-bin/message/mass/delete?access_token=";
public static final String OAUTH_GET_CODE = "https://open.weixin.qq.com/connect/oauth2/authorize?";
public static final String OAUTH_ACCESS_TOKEN = "https://api.weixin.qq.com/sns/oauth2/access_token";
public static final String OAUTH_REFRESH_TOKEN = "https://api.weixin.qq.com/sns/oauth2/refresh_token";
public static final String OAUTH_USER_INFO = "https://api.weixin.qq.com/sns/userinfo?access_token=";
public static final String OAUTH_AUTH_ACCESS_TOKEN = "https://api.weixin.qq.com/sns/auth?access_token=";
public static final String OAUTH_JSAPI_TICKET = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token=";
public static final String SHORT_URL = "https://api.weixin.qq.com/cgi-bin/shorturl?action=long2short&access_token=";
// 新增永久图文素材
public static final String MATERIAL_ADD_NEWS = "https://api.weixin.qq.com/cgi-bin/material/add_news?access_token=";
// 新增其他类型永久素材
public static final String MATERIAL_ADD_MATERIAL = "https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=";
// 获取永久素材
public static final String MATERIAL_GET_MATERIAL = "https://api.weixin.qq.com/cgi-bin/material/get_material?access_token=";
// 删除永久素材
public static final String MATERIAL_DEL_MATERIAL = "https://api.weixin.qq.com/cgi-bin/material/del_material?access_token=";
// 修改永久图文素材
public static final String MATERIAL_UPDATE_NEWS = "https://api.weixin.qq.com/cgi-bin/material/update_news?access_token=";
// 获取素材总数
public static final String MATERIAL_GET_COUNT = "https://api.weixin.qq.com/cgi-bin/material/get_materialcount?access_token=";
// 获取素材列表
public static final String MATERIAL_BATCH_GET = "https://api.weixin.qq.com/cgi-bin/material/batchget_material?access_token=";
}
}