java 微信开发的工具类WeChatUtils
import com.alibaba.fastjson.jsonobject;
import com.bhudy.entity.bhudyplugin;
import com.bhudy.service.bhudypluginservice;
import org.jdom2.document;
import org.jdom2.element;
import org.jdom2.jdomexception;
import org.jdom2.input.saxbuilder;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.stereotype.component;
import javax.xml.transform.transformer;
import javax.xml.transform.transformerconfigurationexception;
import javax.xml.transform.transformerexception;
import javax.xml.transform.transformerfactory;
import javax.xml.transform.dom.domsource;
import javax.xml.transform.stream.streamresult;
import java.io.ioexception;
import java.io.stringreader;
import java.io.stringwriter;
import java.security.messagedigest;
import java.util.*;
/**
* created by administrator on 2019/8/23/023.
*/
@component
public final class wechatutils {
@autowired
private wechatutils(bhudypluginservice bhudypluginservice) {
wechatutils.bhudypluginservice = bhudypluginservice;
}
private static bhudypluginservice bhudypluginservice; // 获取微信appid的接口
private static map<string, string> tokenmap = null;
public static string wxgzhworkorder = "xxxxx"; // 工单状态通知key
public static string wegzhreport = "xxxxxx"; // 报告生成通知key
private static string authorizationcode = "authorization_code"; // 微信调用接口使用参数authorization_code
private static string clientcredential = "client_credential"; // 微信调用接口使用参数client_credential
/**
* 发送报告生成通知
* 报告生成通知id: p9u-lyy4qtckkqsodb7sfqk4glfqvdu8g5jxwotkuqk
* <p>
* {{first.data}}
* 报告类型:{{keyword1.data}}
* 生成时间:{{keyword2.data}}
* {{remark.data}}
*
* @return 是否发送成功
*/
public static boolean sendwegzhreport(string openid, string first, string remark, map<string, object> keywordmap) {
return sendwxgzh(openid, wechatutils.wegzhreport, first, remark, keywordmap);
}
/**
* 发送微信公众号工单消息提醒
* 工单模板信息id: i5jtheqblym9vybyyr2eqrglbdziifzvya7rndboaum
* <p>
* {{first.data}}
* 工单编号:{{keyword1.data}}
* 工单标题:{{keyword2.data}}
* 时间:{{keyword3.data}}
* {{remark.data}}
*
* @return 是否发送成功
*/
public static boolean sendwxgzhworkorder(string openid, string first, string remark, map<string, object> keywordmap) {
return sendwxgzh(openid, wechatutils.wxgzhworkorder, first, remark, keywordmap);
}
/**
* 发送微信公众号信息
* <p>
* 参数 是否必填 说明
* touser 是 接收者openid
* template_id 是 模板id
* url 否 模板跳转链接(海外帐号没有跳转能力)
* miniprogram 否 跳小程序所需数据,不需跳小程序可不用传该数据
* appid 是 所需跳转到的小程序appid(该小程序appid必须与发模板消息的公众号是绑定关联关系,暂不支持小游戏)
* pagepath 否 所需跳转到小程序的具体页面路径,支持带参数,(示例index?foo=bar),要求该小程序已发布,暂不支持小游戏
* data 是 模板数据
* color 否 模板内容字体颜色,不填默认为黑色
* <p>
* json数据模板
* {"touser":"openid","template_id":"ngqipbwh8bufcssecmogfxcv14j0tqlepbo27izeyty","url":"http://weixin.qq.com/download",
* "miniprogram":{"appid":"xiaochengxuappid12345","pagepath":"index?foo=bar"},"data":{"first":{"value":"恭喜你购买成功!","color":"#173177"},
* "keyword1":{"value":"巧克力","color":"#173177"},"keyword2":{"value":"39.8元","color":"#173177"},"keyword3":{"value":"2014年9月22日","color":"#173177"},
* "remark":{"value":"欢迎再次购买!","color":"#173177"}}}
*
* @param openid 接收消息的用户openid
* @param templateid 消息模板id
* @param first 标题
* @param remark 结尾
* @param keywordmap 内容map
* @return 是否发送成功
*/
public static boolean sendwxgzh(string openid, string templateid, string first, string remark, map<string, object> keywordmap) {
if (openid == null || openid.equals("")) return false;
string url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + wechatutils.gettoken(bhudyplugin.tencent_tas);
map<string, object> map = new hashmap<>();
map.put("touser", openid);
map.put("template_id", templateid);
map.put("url", null);
map<string, object> miniprogrammap = new hashmap<>();
miniprogrammap.put("appid", bhudypluginservice.getbhudyplugindatabytype(bhudyplugin.tencent_tas).get(bhudyplugin.appid));
//miniprogrammap.put("pagepath", "index");
map.put("miniprogram", miniprogrammap);
map<string, object> datamap = new hashmap<>();
map firstmap = new hashmap<>();
firstmap.put("value", first);
datamap.put("first", firstmap);
for (map.entry entryset : keywordmap.entryset()) {
map keyword1map = new hashmap<>();
keyword1map.put("value", entryset.getvalue());
datamap.put(entryset.getkey().tostring(), keyword1map);
}
map remarkmap = new hashmap<>();
remarkmap.put("value", remark);
datamap.put("remark", remarkmap);
map.put("data", datamap);
map<string, object> resultmap = wechatutils.wxreqdatapost(url, jsonobject.tojsonstring(map));
return resultmap != null;
}
/**
* 获取用户基本信息(unionid机制)
* 在关注者与公众号产生消息交互后,公众号可获得关注者的openid(加密后的微信号,每个用户对每个公众号的openid是唯一的。对于不同公众号,同一用户的openid不同)。公众号可通过本接口来根据openid获取用户基本信息,包括昵称、头像、性别、所在城市、语言和关注时间。
* 请注意,如果开发者有在多个公众号,或在公众号、移动应用之间统一用户帐号的需求,需要前往微信开放平台(open.weixin.qq.com)绑定公众号后,才可利用unionid机制来满足上述需求。
* <p>
* 参数 是否必须 说明
* access_token 是 调用接口凭证
* openid 是 普通用户的标识,对当前公众号唯一
* lang 否 返回国家地区语言版本,zh_cn 简体,zh_tw 繁体,en 英语
* ***********************************
*
* @return <br>
* 参数 说明
* subscribe 用户是否订阅该公众号标识,值为0时,代表此用户没有关注该公众号,拉取不到其余信息。
* openid 用户的标识,对当前公众号唯一
* nickname 用户的昵称
* sex 用户的性别,值为1时是男性,值为2时是女性,值为0时是未知
* city 用户所在城市
* country 用户所在国家
* province 用户所在省份
* language 用户的语言,简体中文为zh_cn
* headimgurl 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空。若用户更换头像,原有头像url将失效。
* subscribe_time 用户关注时间,为时间戳。如果用户曾多次关注,则取最后关注时间
* unionid 只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。
* remark 公众号运营者对粉丝的备注,公众号运营者可在微信公众平台用户管理界面对粉丝添加备注
* groupid 用户所在的分组id(兼容旧的用户分组接口)
* tagid_list 用户被打上的标签id列表
* subscribe_scene 返回用户关注的渠道来源,add_scene_search 公众号搜索,add_scene_account_migration 公众号迁移,add_scene_profile_card 名片分享,add_scene_qr_code 扫描二维码,add_sceneprofile link 图文页内名称点击,add_scene_profile_item 图文页右上角菜单,add_scene_paid 支付后关注,add_scene_others 其他
* qr_scene 二维码扫码场景(开发者自定义)
* qr_scene_str 二维码扫码场景描述(开发者自定义)
* unionid
*/
public static map<string, object> getuserinfo(string openid, integer type) {
string url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=" + wechatutils.gettoken(type) + "&openid=" + openid + "&lang=zh_cn";
map resultmap = wechatutils.wxreqdataget(url);
return resultmap;
}
/**
* 获取公众号用户列表
* 公众号可通过本接口来获取帐号的关注者列表,关注者列表由一串openid(加密后的微信号,每个用户对每个公众号的openid是唯一的)组成。一次拉取调用最多拉取10000个关注者的openid,可以通过多次拉取的方式来满足需求。
* <p>
* 参数 是否必须 说明
* access_token 是 调用接口凭证
* next_openid 是 第一个拉取的openid,不填默认从头开始拉取
*
* @param nextopenid 第一个拉取的openid,不填默认从头开始拉取
* @return <br>
* 参数 说明
* total 关注该公众账号的总用户数
* count 拉取的openid个数,最大值为10000
* data 列表数据,openid的列表
* next_openid 拉取列表的最后一个用户的openid
*/
public static map<string, object> getopenidlistmap(string nextopenid) {
string url = "https://api.weixin.qq.com/cgi-bin/user/get?access_token=" + gettoken(bhudyplugin.tencent_tas);
if (nextopenid != null && nextopenid.equals("")) {
url += "&next_openid=" + nextopenid;
}
map resultmap = wechatutils.wxreqdataget(url);
return resultmap;
}
/**
* @param type 1是公众号请求
* @return * * *
* 属性 类型 说明
* access_token string 获取到的凭证
* expires_in number 凭证有效时间,单位:秒。目前是7200秒之内的值。
* errcode number 错误码
* errmsg string 错误信息
* <p>
* =====================================
* <p>
* errcode 的合法值
* 值 说明 最低版本
* -1 系统繁忙,此时请开发者稍候再试
* 0 请求成功
* 40001 appsecret 错误或者 appsecret 不属于这个小程序,请开发者确认 appsecret 的正确性
* 40002 请确保 grant_type 字段值为 client_credential
* 40013 不合法的 appid,请开发者检查 appid 的正确性,避免异常字符,注意大小写
*/
public static string gettoken(integer type) {
map<string, string> tokenmap = wechatutils.tokenmap;
// 微信的access_token有效期是7200秒,所以我们的过期时间要比微信的快token
if (tokenmap != null && utils.formatlong(tokenmap.get("date")) >= (new date().gettime() + (7200 * 1000))) {
return tokenmap.get("access_token");
}
map<string, string> wechatmap = bhudypluginservice.getbhudyplugindatabytype(type);
string url = "https://api.weixin.qq.com/cgi-bin/token?appid=" + wechatmap.get(bhudyplugin.appid) + "&secret=" + wechatmap.get(bhudyplugin.appsecret) + "&grant_type=" + clientcredential;
map<string, object> resultmap = wechatutils.wxreqdataget(url);
if (resultmap == null) throw new bhudyexception(bhudyexceptioncode.code_29);
string token = (string) resultmap.get("access_token");
wechatutils.tokenmap = new hashmap<>();
wechatutils.tokenmap.put("date", string.valueof(new date().gettime()));
wechatutils.tokenmap.put("access_token", token);
return token;
}
/**
* @param code string 是 登录时获取的 code
* @return * * *
* 属性 类型 说明
* openid string 用户唯一标识
* session_key string 会话密钥
* unionid string 用户在开放平台的唯一标识符,在满足 unionid 下发条件的情况下会返回,详见 unionid 机制说明。
* errcode number 错误码
* errmsg string 错误信息
* <p>
* ===================================
* <p>
* errcode 的合法值
* 值 说明 最低版本
* -1 系统繁忙,此时请开发者稍候再试
* 0 请求成功
* 40029 code 无效
* 45011 频率限制,每个用户每分钟100次
*/
public static map<string, object> getopenidandsessionkey(string code, integer type) {
map<string, string> wechatmap = bhudypluginservice.getbhudyplugindatabytype(type);
string url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + wechatmap.get(bhudyplugin.appid) + "&secret=" + wechatmap.get(bhudyplugin.appsecret) + "&grant_type=" + authorizationcode + "&js_code=" + code;
return wechatutils.wxreqdataget(url);
}
/**
* 微信post请求
* 如果微信端返回错误码或者没有返回数据,这个方法直接返回null
* 该方法没有使用线程,可能会卡死
*
* @param url 请求的url
* @param params
* @return
*/
public static map<string, object> wxreqdatapost(string url, string params) {
try {
map<string, object> reulstmap = utils.reqpost(url, params);
if (reulstmap == null || (reulstmap.containskey("errcode") && (integer) reulstmap.get("errcode") != 0)) {
return null;
}
return reulstmap;
} catch (exception e) {
return null;
}
}
/**
* 微信get请求
* 如果微信端返回错误码或者没有返回数据,这个方法直接返回null
* 该方法没有使用线程,可能会卡死
*
* @param url 请求的url
* @return
*/
public static map<string, object> wxreqdataget(string url) {
try {
map<string, object> reulstmap = utils.reqgetmap(url);
if (reulstmap == null || (reulstmap.containskey("errcode") && (integer) reulstmap.get("errcode") != 0)) {
return null;
}
return reulstmap;
} catch (exception e) {
return null;
}
}
/**
* 接收事件推送
* 用法@requestmapping(value = "/wx/api/v1/receiveeventpush.do", method = requestmethod.post, consumes = {"application/xml", "text/xml"}, produces = {"application/xml;charset=utf-8", "text/xml;charset=utf-8"})
* public object receiveeventpush(@requestbody domsource domsource) {
* return wxutils.receiveeventpush(domsource);
* }
*
* @param domsource
* @return * * *
* <p>
* 目录 msgtype event
* 1 关注/取消关注事件
* 2 扫描带参数二维码事件
* 3 上报地理位置事件
* 4 自定义菜单事件
* 5 点击菜单拉取消息时的事件推送
* 6 点击菜单跳转链接时的事件推送
* <p>
* <p>
* 》》》1.关注/取消关注事件《《《
* 参数 描述
* tousername 开发者微信号
* fromusername 发送方帐号(一个openid)
* createtime 消息创建时间 (整型)
* msgtype 消息类型,event
* event 事件类型,subscribe(订阅)、unsubscribe(取消订阅)
* <p>
* <p>
* 》》》2.扫描带参数二维码事件《《《
* 参数 描述
* tousername 开发者微信号
* fromusername 发送方帐号(一个openid)
* createtime 消息创建时间 (整型)
* msgtype 消息类型,event
* event 事件类型,subscribe
* eventkey 事件key值,qrscene_为前缀,后面为二维码的参数值
* ticket 二维码的ticket,可用来换取二维码图片
* <p>
* <p>
* 》》》3.上报地理位置事件《《《
* 参数 描述
* tousername 开发者微信号
* fromusername 发送方帐号(一个openid)
* createtime 消息创建时间 (整型)
* msgtype 消息类型,event
* event 事件类型,location
* latitude 地理位置纬度
* longitude 地理位置经度
* precision 地理位置精度
* <p>
* <p>
* 》》》5.点击菜单拉取消息时的事件推送《《《
* 参数 描述
* tousername 开发者微信号
* fromusername 发送方帐号(一个openid)
* createtime 消息创建时间 (整型)
* msgtype 消息类型,event
* event 事件类型,click
* eventkey 事件key值,与自定义菜单接口中key值对应
* <p>
* <p>
* 》》》6.点击菜单跳转链接时的事件推送《《《
* 参数 描述
* tousername 开发者微信号
* fromusername 发送方帐号(一个openid)
* createtime 消息创建时间 (整型)
* msgtype 消息类型,event
* event 事件类型,view
* eventkey 事件key值,设置的跳转url
* <p>
* <p>
* msgtype
* 消息类型,接收事件推送为event
* 消息类型,文本为text
* 消息类型,图片为image
* 消息类型,语音为voice
* 消息类型,视频为video
* 消息类型,音乐为music
* 消息类型,图文为news
*/
public static string receiveeventpush(domsource domsource) {
map<string, object> xmlmap = domsourcetomap(domsource);
if (xmlmap == null) return "";
string msgtype = (string) xmlmap.get("msgtype");
string fromusername = (string) xmlmap.get("fromusername");
string tousername = (string) xmlmap.get("tousername");
string event = (string) xmlmap.get("event");
string text;
if (msgtype.equals("event")) {
if (event.equals("subscribe")) {
// 关注事件
text = "关注事件";
} else if (event.equals("unsubscribe")) {
// 取消关注事件
text = "取消关注事件";
} else {
text = "对不起,无法识别消息类型";
}
} else if (msgtype.equals("text")) {
// 消息类型,为text
text = "消息类型,文本为text";
} else if (msgtype.equals("image")) {
// 消息类型,图片为image
text = "消息类型,图片为image";
} else if (msgtype.equals("voice")) {
// 消息类型,语音为voice
text = "消息类型,语音为voice";
} else if (msgtype.equals("video")) {
// 消息类型,视频为video
text = "消息类型,视频为video";
} else if (msgtype.equals("music")) {
// 消息类型,音乐为music
text = "消息类型,音乐为music";
} else if (msgtype.equals("news")) {
// 消息类型,图文为news
text = "消息类型,图文为news";
} else {
text = "对不起,无法识别消息类型";
}
string returndata = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?>" +
" <xml>" +
" <content>" + text + "</content>" +
" <tousername>" + fromusername + "</tousername>" +
" <fromusername>" + tousername + "</fromusername>" +
" <createtime>" + new date().gettime() / 1000 + "</createtime>" +
" <msgtype>text</msgtype>" +
" </xml>";
//输出格式化后的json
return returndata;
}
private static map<string, object> domsourcetomap(domsource domsource) {
try {
stringwriter writer = new stringwriter();
streamresult result = new streamresult(writer);
transformerfactory tf = transformerfactory.newinstance();
transformer transformer = tf.newtransformer();
transformer.transform(domsource, result);
saxbuilder sb = new saxbuilder();
document doc = sb.build(new stringreader(writer.tostring()));
element root = doc.getrootelement();
jsonobject json = new jsonobject();
json.put(root.getname(), iterateelement(root));
map<string, object> datamap = jsonobject.parseobject(json.tojsonstring(), map.class);
map<string, object> xmlmap = (map<string, object>) datamap.get("xml");
return xmlmap;
} catch (transformerconfigurationexception e) {
e.printstacktrace();
} catch (transformerexception e) {
e.printstacktrace();
} catch (ioexception e) {
e.printstacktrace();
} catch (jdomexception e) {
e.printstacktrace();
}
return null;
}
private static jsonobject iterateelement(element element) {
list<element> node = element.getchildren();
jsonobject obj = new jsonobject();
list list = null;
for (element child : node) {
list = new linkedlist();
string text = child.gettexttrim();
if (text == null || text.equals("")) {
if (child.getchildren().size() == 0) {
continue;
}
if (obj.containskey(child.getname())) {
list = (list) obj.get(child.getname());
}
list.add(iterateelement(child)); //遍历child的子节点
obj.put(child.getname(), list);
} else {
if (obj.containskey(child.getname())) {
object value = obj.get(child.getname());
try {
list = (list) value;
} catch (classcastexception e) {
list.add(value);
}
}
if (child.getchildren().size() == 0) { //child无子节点时直接设置text
obj.put(child.getname(), text);
} else {
list.add(text);
obj.put(child.getname(), list);
}
}
}
return obj;
}
/**
* 验证微信绑定服务器的方法
*
* @param signature
* @param timestamp
* @param nonce
* @return
*/
public static boolean checksignature(string signature, string timestamp, string nonce) {
//1.定义数组存放tooken,timestamp,nonce
string[] arr = {"7i6l5seu4npuyigvaxmy0znfxd6", timestamp, nonce};
//2.对数组进行排序
arrays.sort(arr);
//3.生成字符串
stringbuffer sb = new stringbuffer();
for (string s : arr) {
sb.append(s);
}
//4.sha1加密,网上均有现成代码
string temp = getsha(sb.tostring());
//5.将加密后的字符串,与微信传来的加密签名比较,返回结果
return temp.equals(signature);
}
public static string getsha(string str) {
if (str == null || str.length() == 0) {
return null;
}
char hexdigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
try {
messagedigest mdtemp = messagedigest.getinstance("sha1");
mdtemp.update(str.getbytes("utf-8"));
byte[] md = mdtemp.digest();
int j = md.length;
char buf[] = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = md[i];
buf[k++] = hexdigits[byte0 >>> 4 & 0xf];
buf[k++] = hexdigits[byte0 & 0xf];
}
return new string(buf);
} catch (exception e) {
// todo: handle exception
return null;
}
}
}
微信工具类需要的其他方法
/**
* 发送httppost请求
*
* @param strurl 服务地址
* @param params json字符串,例如: "{ \"id\":\"12345\" }" ;其中属性名必须带双引号<br/>
* @return 成功:返回json字符串<br/>
*/
public static map<string, object> reqpost(string strurl, string params) {
bufferedreader reader;
try {
url url = new url(strurl);// 创建连接
httpurlconnection connection = (httpurlconnection) url.openconnection();
connection.setdooutput(true);
connection.setdoinput(true);
connection.setusecaches(false);
connection.setinstancefollowredirects(true);
connection.setrequestmethod("post"); // 设置请求方式
connection.setrequestproperty("accept", "application/json"); // 设置接收数据的格式
connection.setrequestproperty("content-type", "application/json"); // 设置发送数据的格式
connection.connect();
//一定要用bufferedreader 来接收响应, 使用字节来接收响应的方法是接收不到内容的
outputstreamwriter out = new outputstreamwriter(connection.getoutputstream(), "utf-8"); // utf-8编码
out.append(params);
out.flush();
out.close();
// 读取响应
reader = new bufferedreader(new inputstreamreader(connection.getinputstream(), "utf-8"));
string line;
string res = "";
while ((line = reader.readline()) != null) {
res += line;
}
reader.close();
return json.parseobject(res, map.class);
} catch (ioexception e) {
e.printstacktrace();
return null;
}
}
/**
* 请求url
*
* @param url
* @return
*/
public static map<string, object> reqgetmap(string url) {
try {
url requrl = new url(url);
httpsurlconnection openconnection = (httpsurlconnection) requrl.openconnection();
openconnection.setconnecttimeout(10000);
//这里我感觉获取openid的时间比较长,不过也可能是我网络的问题,
//所以设置的响应时间比较长
openconnection.connect();
inputstream in = openconnection.getinputstream();
stringbuilder builder = new stringbuilder();
bufferedreader bufreader = new bufferedreader(new inputstreamreader(in));
for (string temp = bufreader.readline(); temp != null; temp = bufreader
.readline()) {
builder.append(temp);
}
string result = builder.tostring();
in.close();
openconnection.disconnect();
map<string, object> resultmap = json.parseobject(result, map.class);
return resultmap;
} catch (exception e) {
e.printstacktrace();
return null;
}
}