package org.nutz.weixin.impl;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import org.nutz.castor.Castors;
import org.nutz.http.Request;
import org.nutz.http.Request.METHOD;
import org.nutz.http.Response;
import org.nutz.http.Sender;
import org.nutz.http.sender.FilePostSender;
import org.nutz.json.Json;
import org.nutz.json.JsonFormat;
import org.nutz.lang.*;
import org.nutz.lang.util.NutMap;
import org.nutz.log.Log;
import org.nutz.log.Logs;
import org.nutz.resource.NutResource;
import org.nutz.weixin.bean.*;
import org.nutz.weixin.spi.WxResp;
import org.nutz.weixin.util.WxPaySign;
import org.nutz.weixin.util.WxPaySSL;
import org.nutz.weixin.util.Wxs;
import javax.net.ssl.*;
public class WxApi2Impl extends AbstractWxApi2 {
private static final Log log = Logs.get().setTag("weixin");
public WxApi2Impl() {
}
// ===============================
// 基本API
@Override
public WxResp send(WxOutMsg out) {
if (out.getFromUserName() == null)
out.setFromUserName(openid);
String str = Wxs.asJson(out);
if (Wxs.DEV_MODE)
log.debug("api out msg>\n" + str);
return call("/message/custom/send", METHOD.POST, str);
}
@Override
public List<String> getcallbackip() {
return get("/getcallbackip").getList("ip_list", String.class);
}
// -------------------------------
// 用户API
@Override
public WxResp user_info(String openid, String lang) {
return get("/user/info", "openid", openid, "lang", lang);
}
@Override
public WxResp user_info_updatemark(String openid, String remark) {
return postJson("/user/info/updateremark", "openid", openid, "remark", remark);
}
@Override
@SuppressWarnings("unchecked")
public void user_get(Each<String> each) {
String next_openid = null;
WxResp map = null;
int count = 0;
int total = 0;
int index = 0;
while (true) {
if (next_openid == null)
map = call("/user/get", METHOD.GET, null);
else
map = call("/user/get?next_openid=" + next_openid, METHOD.GET, null);
count = ((Number) map.get("count")).intValue();
if (count < 1)
return;
total = ((Number) map.get("total")).intValue();
next_openid = Strings.sNull(map.get("next_openid"));
if (next_openid.length() == 0)
next_openid = null;
List<String> openids = (List<String>) ((Map<String, Object>) map.get("data")).get("openid");
for (String openid : openids) {
try {
each.invoke(index, openid, total);
} catch (ExitLoop e) {
return;
} catch (ContinueLoop e) {
continue;
} catch (LoopException e) {
throw e;
}
index++;
}
}
}
@Override
public WxResp groups_create(WxGroup group) {
return postJson("/groups/create", "group", group);
}
@Override
public WxResp groups_get() {
return call("/groups/get", METHOD.GET, null);
}
@Override
public WxResp groups_getid(String openid) {
return postJson("/groups/getid", "openid", openid);
}
@Override
public WxResp groups_update(WxGroup group) {
return postJson("/groups/update", "group", group);
}
@Override
public WxResp groups_member_update(String openid, String to_groupid) {
return postJson("/groups/member/update", "openid", openid, "to_groupid", to_groupid);
}
// -------------------------------------------------------
// 二维码API
@Override
public WxResp qrcode_create(Object scene_id, int expire_seconds) {
NutMap params = new NutMap();
NutMap scene;
// 临时二维码
if (expire_seconds > 0) {
params.put("action_name", "QR_SCENE");
params.put("expire_seconds", expire_seconds);
scene = Lang.map("scene_id", Castors.me().castTo(scene_id, Integer.class));
}
// 永久二维码
else if (scene_id instanceof Number) {
params.put("action_name", "QR_LIMIT_SCENE");
scene = Lang.map("scene_id", Castors.me().castTo(scene_id, Integer.class));
}
// 永久字符串二维码
else {
params.put("action_name", "QR_LIMIT_STR_SCENE");
scene = Lang.map("scene_str", scene_id.toString());
}
params.put("action_info", Lang.map("scene", scene));
return postJson("/qrcode/create", params);
}
@Override
public String qrcode_show(String ticket) {
return "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + ticket;
}
@Override
public String shorturl(String long_url) {
return postJson("/shorturl", new NutMap().setv("long_url", long_url).setv("action", "long2short")).getString("short_url");
}
// --------------------------------------------------------
// 模板消息
@Override
public WxResp template_api_set_industry(String industry_id1, String industry_id2) {
return postJson("/template/api_set_industry", "industry_id1", industry_id1, "industry_id2", industry_id2);
}
@Override
public WxResp template_api_add_template(String template_id_short) {
return postJson("/template/api_add_template", "template_id_short", template_id_short);
}
@Override
public WxResp template_api_del_template(String template_id) {
return postJson("/template/del_private_template", "template_id", template_id);
}
@Override
public WxResp template_send(String touser, String template_id, String url, Map<String, WxTemplateData> data) {
return postJson("/message/template/send", "touser", touser, "template_id", template_id, "url", url, "data", data);
}
@Override
public WxResp template_send(String touser, String template_id, String url, Map<String, Object> miniprogram, Map<String, WxTemplateData> data) {
return postJson("/message/template/send", "touser", touser, "template_id", template_id, "url", url, miniprogram, miniprogram, "data", data);
}
// ------------------------------------------------------------
// 自定义菜单
@Override
public WxResp menu_create(NutMap map) {
return postJson("/menu/create", map);
}
@Override
public WxResp menu_create(List<WxMenu> button) {
return postJson("/menu/create", "button", button);
}
@Override
public WxResp menu_get() {
return call("/menu/get", METHOD.GET, null);
}
@Override
public WxResp menu_delete() {
return call("/menu/delete", METHOD.GET, null);
}
// 多媒体上传下载
@Override
public WxResp media_upload(String type, File f) {
if (type == null)
throw new NullPointerException("media type is NULL");
if (f == null)
throw new NullPointerException("meida file is NULL");
String url = String.format("http://file.api.weixin.qq.com/cgi-bin/media/upload?access_token=%s&type=%s", getAccessToken(), type);
Request req = Request.create(url, METHOD.POST);
req.getParams().put("media", f);
Response resp = new FilePostSender(req).send();
if (!resp.isOK())
throw new IllegalStateException("media upload file, resp code=" + resp.getStatus());
return Json.fromJson(WxResp.class, resp.getReader("UTF-8"));
}
@Override
public NutResource media_get(String mediaId) {
String url = String.format("http://file.api.weixin.qq.com/cgi-bin/media/get?access_token=%s&media_id=%s", getAccessToken(), mediaId);
final Response resp = Sender.create(Request.create(url, METHOD.GET)).send();
if (!resp.isOK())
throw new IllegalStateException("download media file, resp code=" + resp.getStatus());
String disposition = resp.getHeader().get("Content-disposition");
return new WxResource(disposition, resp.getStream());
}
// 高级群发
@Override
public WxResp mass_uploadnews(List<WxArticle> articles) {
return postJson("/message/mass/uploadnews", "articles", articles);
}
public WxResp _mass_send(NutMap filter, List<String> to_user, String touser, WxOutMsg msg) {
NutMap params = new NutMap();
if (filter != null)
params.setv("filter", filter);
else if (to_user != null) {
params.setv("touser", to_user);
} else {
params.put("touser", touser);
}
String tp = msg.getMsgType();
if ("text".equals(tp)) {
params.put("text", new NutMap().setv("content", msg.getContent()));
} else if ("image".equals(tp) || "voice".equals(tp) || "mpnews".equals(tp)) {
params.put(tp, new NutMap().setv("media_id", msg.getMedia_id()));
} else if ("video".equals(tp)) {
NutMap tm = new NutMap();
tm.put("media_id", msg.getMedia_id());
tm.put("thumb_media_id", msg.getVideo().getThumb_media_id());
tm.put("title", msg.getVideo().getTitle());
tm.put("description", msg.getVideo().getDescription());
params.put(tp, tm);
} else if ("music".equals(tp)) {
NutMap tm = new NutMap();
tm.put("musicurl", msg.getMusic().getMusicUrl());
tm.put("hqmusicurl", msg.getMusic().getHQMusicUrl());
tm.put("thumb_media_id", msg.getMusic().getThumbMediaId());
tm.put("title", msg.getMusic().getTitle());
tm.put("description", msg.getMusic().getDescription());
params.put(tp, tm);
} else if ("news".equals(tp)) {
params.put("news", msg.getArticles());
} else if ("wxcard".equals(tp)) {
params.put("wxcard", new NutMap().setv("card_id", msg.getCard().getId()).setv("card_ext", msg.getCard().getExt()));
} else {
params.put(msg.getMsgType(), new NutMap().setv("media_id", msg.getMedia_id()));
}
params.setv("msgtype", msg.getMsgType());
if (msg.getKfAccount() != null) {
params.setv("customservice", new NutMap().setv("kf_account", msg.getKfAccount().getAccount()));
}
if (filter != null)
return postJson("/message/mass/sendall", params);
else if (to_user != null)
return postJson("/message/mass/send", params);
return postJson("/message/mass/preview", params);
}
@Override
public WxResp mass_sendall(boolean is_to_all, String group_id, WxOutMsg msg) {
NutMap filter = new NutMap();
filter.put("is_to_all", is_to_all);
if (!is_to_all) {
filter.put("group_id", group_id);
}
return this._mass_send(filter, null, null, msg);
}
@Override
public WxResp mass_send(List<String> to_user, WxOutMsg msg) {
return this._mass_send(null, to_user, null, msg);
}
@Override
public WxResp mass_del(String msg_id) {
return this.postJson("/message/mass/del", "msg_id", msg_id);
}
@Override
public WxResp mass_get(String msg_id) {
return postJson("/message/mass/get", "msg_id", msg_id);
}
@Override
public WxResp mass_preview(String touser, WxOutMsg msg) {
return _mass_send(null, null, touser, msg);
}
// 摇一摇API
public static final String ShakeUrlBase = "https://api.weixin.qq.com/shakearound";
@Override
public WxResp applyId(int quantity, String apply_reason, String comment, int poi_id) {
return postJson(ShakeUrlBase + "/device/applyid", "quantity", quantity, "apply_reason", apply_reason, "comment", comment);
}
@Override
public WxResp applyStatus(String apply_id) {
return postJson(ShakeUrlBase + "/device/applystatus", "apply_id", apply_id);
}
@Override
public WxResp update(int device_id, String comment) {
NutMap params = new NutMap();
params.put("device_identifier", new NutMap().setv("device_id", device_id));
params.put("comment", comment);
return postJson(ShakeUrlBase + "/device/update", params);
}
@Override
public WxResp update(String uuid, int major, int minor, String comment) {
NutMap params = new NutMap();
params.put("device_identifier", new NutMap().setv("uuid", uuid).setv("major", major).setv("minor", minor));
params.put("comment", comment);
return postJson(ShakeUrlBase + "/device/update", params);
}
@Override
public WxResp bindLocation(int device_id, int poi_id) {
NutMap params = new NutMap();
params.put("device_identifier", new NutMap().setv("device_id", device_id));
params.put("poi_id", poi_id);
return postJson(ShakeUrlBase + "/device/bindlocation", params);
}
@Override
public WxResp bindLocation(String uuid, int major, int minor, int poi_id) {
NutMap params = new NutMap();
params.put("device_identifier", new NutMap().setv("uuid", uuid).setv("major", major).setv("minor", minor));
params.put("poi_id", poi_id);
return postJson(ShakeUrlBase + "/device/bindlocation", params);
}
@Override
public WxResp search(int device_id) {
NutMap params = new NutMap();
params.put("device_identifier", new NutMap().setv("device_id", device_id));
return postJson(ShakeUrlBase + "/device/search", params);
}
@Override
public WxResp search(String uuid, int major, int minor) {
NutMap params = new NutMap();
params.put("device_identifier", new NutMap().setv("uuid", uuid).setv("major", major).setv("minor", minor));
return postJson(ShakeUrlBase + "/device/search", params);
}
@Override
public WxResp search(int begin, int count) {
return postJson(ShakeUrlBase + "/device/search", "begin", begin, "count", count);
}
@Override
public WxResp search(int apply_id, int begin, int count) {
return postJson(ShakeUrlBase + "/device/search", "apply_id", apply_id, "begin", begin, "count", count);
}
@Override
public WxResp getShakeInfo(String ticket, int need_poi) {
return postJson(ShakeUrlBase + "/user/getshakeinfo", "ticket", ticket, "need_poi", need_poi);
}
@Override
public WxResp createQRTicket(long expire, Type type, int id) {
NutMap json = NutMap.NEW();
json.put("expire_seconds", expire);
json.put("action_name", type.getValue());
NutMap action = NutMap.NEW();
NutMap scene = NutMap.NEW();
scene.put("scene_id", id);
action.put("scene", scene);
json.put("action_info", action);
return postJson("/qrcode/create", json);
}
@Override
public WxResp createQRTicket(long expire, Type type, String str) {
NutMap json = NutMap.NEW();
json.put("expire_seconds", expire);
json.put("action_name", type.getValue());
NutMap action = NutMap.NEW();
NutMap scene = NutMap.NEW();
scene.put("scene_str", str);
action.put("scene", scene);
json.put("action_info", action);
return postJson("/qrcode/create", json);
}
@Override
public String qrURL(String ticket) {
return String.format("https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=%s", ticket);
}
public WxResp get_all_private_template() {
return postJson("/template/get_all_private_template", NutMap.NEW());
}
public WxResp get_industry() {
return postJson("/template/get_industry", NutMap.NEW());
}
public WxResp add_news(WxArticle... news) {
return postJson("/material/add_news", new NutMap().put("articles", Arrays.asList(news)));
}
@Override
public WxResp uploadimg(File f) {
if (f == null)
throw new NullPointerException("meida file is NULL");
String url = String.format("https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token=%s", getAccessToken());
Request req = Request.create(url, METHOD.POST);
req.getParams().put("media", f);
Response resp = new FilePostSender(req).send();
if (!resp.isOK())
throw new IllegalStateException("uploadimg, resp code=" + resp.getStatus());
return Json.fromJson(WxResp.class, resp.getReader("UTF-8"));
}
@Override
public WxResp uploadnews(List<WxMassArticle> articles) {
//用postJson方法总是抛空指针异常,只好用下面写法了,不知道原因
return call("/media/uploadnews", METHOD.POST, Json.toJson(new NutMap().setv("articles", articles)));
}
@Override
public WxResp add_material(String type, File f) {
if (f == null)
throw new NullPointerException("meida file is NULL");
String url = String.format("https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=%s&type=%s", getAccessToken(), type);
Request req = Request.create(url, METHOD.POST);
req.getParams().put("media", f);
Response resp = new FilePostSender(req).send();
if (!resp.isOK())
throw new IllegalStateException("add_material, resp code=" + resp.getStatus());
return Json.fromJson(WxResp.class, resp.getReader("UTF-8"));
}
@Override
public WxResp add_video(File f, String title, String introduction) {
if (f == null)
throw new NullPointerException("meida file is NULL");
String url = String.format("https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=%s", getAccessToken());
Request req = Request.create(url, METHOD.POST);
req.getParams().put("media", f);
req.getParams().put("description",
Json.toJson(new NutMap().setv("title", title).setv("introduction", introduction),
JsonFormat.compact().setQuoteName(true)));
Response resp = new FilePostSender(req).send();
if (!resp.isOK())
throw new IllegalStateException("add_material, resp code=" + resp.getStatus());
return Json.fromJson(WxResp.class, resp.getReader("UTF-8"));
}
public NutResource get_material(String media_id) {
String url = String.format("https://api.weixin.qq.com/cgi-bin/material/get_material?access_token=%s", getAccessToken());
Request req = Request.create(url, METHOD.POST);
req.getParams().put("media_id", media_id);
final Response resp = Sender.create(req).send();
if (!resp.isOK())
throw new IllegalStateException("download media file, resp code=" + resp.getStatus());
String disposition = resp.getHeader().get("Content-disposition");
return new WxResource(disposition, resp.getStream());
}
@SuppressWarnings("rawtypes")
public List<WxArticle> get_material_news(String media_id) {
try {
NutMap re = Json.fromJson(NutMap.class, get_material(media_id).getReader());
List<WxArticle> list = new ArrayList<WxArticle>();
for (Object obj : re.getAs("news_item", List.class)) {
list.add(Lang.map2Object((Map) obj, WxArticle.class));
}
return list;
} catch (Exception e) {
throw Lang.wrapThrow(e);
}
}
public WxResp get_material_video(String media_id) {
return postJson("/material/get_material", new NutMap().setv("media_id", media_id));
}
public WxResp del_material(String media_id) {
return postJson("/material/del_material", new NutMap().setv("media_id", media_id));
}
public WxResp update_material(String media_id, int index, WxArticle article) {
return postJson("/material/update_news", new NutMap().setv("media_id", media_id).setv("index", index).setv("articles", article));
}
@Override
public WxResp get_materialcount() {
return get("/material/get_materialcount");
}
@Override
public WxResp batchget_material(String type, int offset, int count) {
return postJson("/material/batchget_material", new NutMap().setv("type", type).setv("offset", offset).setv("count", count));
}
static class WxResource extends NutResource {
String disposition;
InputStream ins;
public WxResource(String disposition, InputStream ins) {
super();
this.disposition = disposition;
this.ins = ins;
}
public String getName() {
if (disposition == null)
return "file.data";
for (String str : disposition.split(";")) {
str = str.trim();
if (str.startsWith("filename=")) {
str = str.substring("filename=".length());
if (str.startsWith("\""))
str = str.substring(1);
if (str.endsWith("\""))
str = str.substring(0, str.length() - 1);
return str.trim().intern();
}
}
return "file.data";
}
public InputStream getInputStream() throws IOException {
return ins;
}
}
@Override
public List<WxKfAccount> getkflist() {
return get("/customservice/getkflist").check().getTo("kf_list", WxKfAccount.class);
}
@Override
public List<WxKfAccount> getonlinekflist() {
return get("/customservice/getonlinekflist").check().getTo("kf_online_list", WxKfAccount.class);
}
@Override
public WxResp kfaccount_add(String kf_account, String nickname, String password) {
return postJson("/customservice/kfaccount/add", "kf_account", kf_account, "nickname", nickname, "password", password);
}
@Override
public WxResp kfaccount_update(String kf_account, String nickname, String password) {
return postJson("/customservice/kfaccount/update", "kf_account", kf_account, "nickname", nickname, "password", password);
}
@Override
public WxResp kfaccount_uploadheadimg(String kf_account, File f) {
if (f == null)
throw new NullPointerException("meida file is NULL");
String url = String.format("https://api.weixin.qq.com/customservice/kfaccount/uploadheadimg?access_token=%s", getAccessToken());
Request req = Request.create(url, METHOD.POST);
req.getParams().put("media", f);
Response resp = new FilePostSender(req).send();
if (!resp.isOK())
throw new IllegalStateException("uploadimg, resp code=" + resp.getStatus());
return Json.fromJson(WxResp.class, resp.getReader("UTF-8"));
}
@Override
public WxResp kfaccount_del(String kf_account) {
return postJson("/customservice/kfaccount/del", "kf_account", kf_account);
}
/**
* 微信支付公共POST方法(不带证书)
*
* @param url 请求路径
* @param key 商户KEY
* @param params 参数
* @return
*/
@Override
public NutMap postPay(String url, String key, Map<String, Object> params) {
params.remove("sign");
String sign = WxPaySign.createSign(key, params);
params.put("sign", sign);
Request req = Request.create(url, METHOD.POST);
req.setData(Xmls.mapToXml(params));
Response resp = Sender.create(req).send();
if (!resp.isOK())
throw new IllegalStateException("postPay, resp code=" + resp.getStatus());
return Xmls.xmlToMap(resp.getContent("UTF-8"));
}
/**
* 微信支付公共POST方法(带证书)
*
* @param url 请求路径
* @param key 商户KEY
* @param params 参数
* @param file 证书文件
* @param password 证书密码
* @return
*/
@Override
public NutMap postPay(String url, String key, Map<String, Object> params, File file, String password) {
params.remove("sign");
String sign = WxPaySign.createSign(key, params);
params.put("sign", sign);
Request req = Request.create(url, METHOD.POST);
req.setData(Xmls.mapToXml(params));
Sender sender = Sender.create(req);
SSLSocketFactory sslSocketFactory;
try {
sslSocketFactory = WxPaySSL.buildSSL(file, password);
} catch (Exception e) {
throw Lang.wrapThrow(e);
}
sender.setSSLSocketFactory(sslSocketFactory);
Response resp = sender.send();
if (!resp.isOK())
throw new IllegalStateException("postPay with SSL, resp code=" + resp.getStatus());
return Xmls.xmlToMap(resp.getContent("UTF-8"));
}
/**
* 统一下单
*
* @param key 商户KEY
* @param wxPayUnifiedOrder 交易订单内容
* @return
*/
@Override
public NutMap pay_unifiedorder(String key, WxPayUnifiedOrder wxPayUnifiedOrder) {
String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
Map<String, Object> params = Lang.obj2map(wxPayUnifiedOrder);
return this.postPay(url, key, params);
}
/**
* 企业向个人付款
*
* @param key 商户KEY
* @param wxPayTransfers 付款内容
* @param file 证书文件
* @param password 证书密码
* @return
*/
@Override
public NutMap pay_transfers(String key, WxPayTransfers wxPayTransfers, File file, String password) {
String url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";
Map<String, Object> params = Lang.obj2map(wxPayTransfers);
return this.postPay(url, key, params, file, password);
}
/**
* 发送普通红包
*
* @param key 商户KEY
* @param wxRedPack 红包内容
* @param file 证书文件
* @param password 证书密码
* @return
*/
@Override
public NutMap send_redpack(String key, WxPayRedPack wxRedPack, File file, String password) {
String url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack";
Map<String, Object> params = Lang.obj2map(wxRedPack);
return this.postPay(url, key, params, file, password);
}
/**
* 发送裂变红包
*
* @param key 商户KEY
* @param wxRedPackGroup 红包内容
* @param file 证书文件
* @param password 证书密码
* @return
*/
@Override
public NutMap send_redpackgroup(String key, WxPayRedPackGroup wxRedPackGroup, File file, String password) {
String url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendgroupredpack";
Map<String, Object> params = Lang.obj2map(wxRedPackGroup);
return this.postPay(url, key, params, file, password);
}
/**
* 发送代金卷
*
* @param key 商户KEY
* @param wxPayCoupon 代金卷内容
* @param file 证书文件
* @param password 证书密码
* @return
*/
@Override
public NutMap send_coupon(String key, WxPayCoupon wxPayCoupon, File file, String password) {
String url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/send_coupon";
Map<String, Object> params = Lang.obj2map(wxPayCoupon);
return this.postPay(url, key, params, file, password);
}
}