net.ymate.platform.module.wechat.WeChat.java Source code

Java tutorial

Introduction

Here is the source code for net.ymate.platform.module.wechat.WeChat.java

Source

/*
 * 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>2014313?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?
     * voice256K?60s?AMR\MP3?
     * video1MB?MP4?
     * thumb64KB?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       IDID100000???
     * @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=";
    }

}