欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

java 微信开发的工具类WeChatUtils

程序员文章站 2023-11-10 22:42:04
import com.alibaba.fastjson.JSONObject;import com.bhudy.entity.BhudyPlugin;import com.bhudy.service.BhudyPluginService;import org.jdom2.Document;impor ......

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;
}
}