From bd6c61b3c3ae5dbe6c2f6f8f1b91b4f51c99c9b6 Mon Sep 17 00:00:00 2001 From: hotlcc Date: Sun, 22 Jul 2018 19:00:30 +0800 Subject: [PATCH] init code --- .gitignore | 4 + pom.xml | 112 ++++ src/main/java/com/hotlcc/wechat4j/Wechat.java | 44 ++ .../com/hotlcc/wechat4j/api/WebWeixinApi.java | 582 ++++++++++++++++++ .../hotlcc/wechat4j/util/PropertiesUtil.java | 56 ++ .../com/hotlcc/wechat4j/util/QRCodeUtil.java | 230 +++++++ .../com/hotlcc/wechat4j/util/StringUtil.java | 34 + .../com/hotlcc/wechat4j/util/WechatUtil.java | 94 +++ src/main/resources/config/app.properties | 4 + .../resources/config/webwx-url.properties | 28 + src/test/java/TestClass.java | 133 ++++ src/test/resources/logback.xml | 159 +++++ 12 files changed, 1480 insertions(+) create mode 100644 pom.xml create mode 100644 src/main/java/com/hotlcc/wechat4j/Wechat.java create mode 100644 src/main/java/com/hotlcc/wechat4j/api/WebWeixinApi.java create mode 100644 src/main/java/com/hotlcc/wechat4j/util/PropertiesUtil.java create mode 100644 src/main/java/com/hotlcc/wechat4j/util/QRCodeUtil.java create mode 100644 src/main/java/com/hotlcc/wechat4j/util/StringUtil.java create mode 100644 src/main/java/com/hotlcc/wechat4j/util/WechatUtil.java create mode 100644 src/main/resources/config/app.properties create mode 100644 src/main/resources/config/webwx-url.properties create mode 100644 src/test/java/TestClass.java create mode 100644 src/test/resources/logback.xml diff --git a/.gitignore b/.gitignore index 6143e53..efffed2 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,7 @@ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* + +.idea +*/target/* +*.iml diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..df95792 --- /dev/null +++ b/pom.xml @@ -0,0 +1,112 @@ + + + 4.0.0 + + com.hotlcc + wechat4j + 1.0-SNAPSHOT + + wechat4j + Wechat client for Java. + + + UTF-8 + UTF-8 + 1.7 + 1.7 + 1.7 + + + + + + org.apache.httpcomponents + httpclient + 4.5.3 + + + org.apache.httpcomponents + httpmime + 4.5 + + + + com.alibaba + fastjson + 1.2.31 + + + + org.apache.commons + commons-lang3 + 3.0 + + + + org.slf4j + slf4j-api + 1.7.25 + + + ch.qos.logback + logback-classic + 1.2.3 + + + + com.vdurmont + emoji-java + 3.2.0 + + + + org.antlr + ST4 + 4.0.8 + compile + + + + junit + junit + 4.12 + + + + com.google.zxing + javase + 3.3.3 + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-resources-plugin + + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + \ No newline at end of file diff --git a/src/main/java/com/hotlcc/wechat4j/Wechat.java b/src/main/java/com/hotlcc/wechat4j/Wechat.java new file mode 100644 index 0000000..c206707 --- /dev/null +++ b/src/main/java/com/hotlcc/wechat4j/Wechat.java @@ -0,0 +1,44 @@ +package com.hotlcc.wechat4j; + +import com.hotlcc.wechat4j.api.WebWeixinApi; +import org.apache.http.client.CookieStore; +import org.apache.http.client.HttpClient; +import org.apache.http.impl.client.BasicCookieStore; +import org.apache.http.impl.client.HttpClients; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 微信客户端 + * + * @author Allen + */ +public class Wechat { + private static Logger logger = LoggerFactory.getLogger(Wechat.class); + + private CookieStore cookieStore; + private HttpClient httpClient; + + private WebWeixinApi webWeixinApi; + + public void setWebWeixinApi(WebWeixinApi webWeixinApi) { + this.webWeixinApi = webWeixinApi; + } + + public Wechat(CookieStore cookieStore) { + this.cookieStore = cookieStore; + this.httpClient = HttpClients.custom().setDefaultCookieStore(cookieStore).build(); + } + + public Wechat() { + this.cookieStore = new BasicCookieStore(); + this.httpClient = HttpClients.custom().setDefaultCookieStore(cookieStore).build(); + } + + /** + * 自动登录 + */ + public void autoLogin() { + + } +} diff --git a/src/main/java/com/hotlcc/wechat4j/api/WebWeixinApi.java b/src/main/java/com/hotlcc/wechat4j/api/WebWeixinApi.java new file mode 100644 index 0000000..bafe99f --- /dev/null +++ b/src/main/java/com/hotlcc/wechat4j/api/WebWeixinApi.java @@ -0,0 +1,582 @@ +package com.hotlcc.wechat4j.api; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.hotlcc.wechat4j.util.PropertiesUtil; +import com.hotlcc.wechat4j.util.StringUtil; +import com.hotlcc.wechat4j.util.WechatUtil; +import org.apache.http.*; +import org.apache.http.client.HttpClient; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.util.EntityUtils; +import org.json.XML; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.stringtemplate.v4.ST; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * web微信接口封装 + * + * @author Allen + */ +@SuppressWarnings("Duplicates") +public class WebWeixinApi { + private static Logger logger = LoggerFactory.getLogger(WebWeixinApi.class); + + private static Pattern PATTERN_UUID_1 = Pattern.compile("window.QRLogin.code = (\\d+);"); + private static Pattern PATTERN_UUID_2 = Pattern.compile("window.QRLogin.code = (\\d+); window.QRLogin.uuid = \"(\\S+?)\";"); + private static Pattern PATTERN_REDIRECT_URI_1 = Pattern.compile("window.code=(\\d+);"); + private static Pattern PATTERN_REDIRECT_URI_2 = Pattern.compile("window.code=(\\d+);\\s*window.redirect_uri=\"(\\S+?)\";"); + + /** + * 获取微信uuid + */ + public JSONObject getWxUuid(HttpClient httpClient) { + String url = new ST(PropertiesUtil.getProperty("webwx-url.uuid_url")) + .add("appid", PropertiesUtil.getProperty("webwx.appid")) + .add("_", System.currentTimeMillis()) + .render(); + + HttpGet httpGet = new HttpGet(url); + httpGet.setHeader("User-Agent", PropertiesUtil.getProperty("wechat4j.userAgent")); + httpGet.setConfig(RequestConfig.custom().setRedirectsEnabled(false).build()); + + JSONObject result = new JSONObject(); + + try { + HttpResponse response = httpClient.execute(httpGet); + if (HttpStatus.SC_OK != response.getStatusLine().getStatusCode()) { + throw new RuntimeException("请求错误"); + } + + HttpEntity entity = response.getEntity(); + String res = EntityUtils.toString(entity); + + Matcher matcher = PATTERN_UUID_1.matcher(res); + if (!matcher.find()) { + throw new RuntimeException("获取登录uuid失败"); + } + + String code = matcher.group(1); + result.put("code", code); + if (!"200".equals(code)) { + result.put("code", code); + result.put("msg", "获取登录uuid失败,请确认appid是否有效"); + return result; + } + + matcher = PATTERN_UUID_2.matcher(res); + if (!matcher.find()) { + throw new RuntimeException("获取登录uuid失败"); + } + + String uuid = matcher.group(2); + result.put("uuid", uuid); + if (StringUtil.isEmpty(uuid)) { + throw new RuntimeException("获取登录uuid失败"); + } + + return result; + } catch (IOException e) { + logger.error("获取登录uuid异常", e); + result.put("code", "-1"); + result.put("msg", "获取登录uuid异常"); + return result; + } + } + + /** + * 获取二维码 + * + * @param uuid + */ + public JSONObject getQR(HttpClient httpClient, + String uuid) { + String url = new ST(PropertiesUtil.getProperty("webwx-url.qrcode_url")) + .add("uuid", uuid) + .render(); + + HttpGet httpGet = new HttpGet(url); + httpGet.setHeader("User-Agent", PropertiesUtil.getProperty("wechat4j.userAgent")); + httpGet.setConfig(RequestConfig.custom().setRedirectsEnabled(false).build()); + + JSONObject result = new JSONObject(); + + try { + HttpResponse response = httpClient.execute(httpGet); + if (HttpStatus.SC_OK != response.getStatusLine().getStatusCode()) { + throw new RuntimeException("请求错误"); + } + + HttpEntity entity = response.getEntity(); + byte[] data = EntityUtils.toByteArray(entity); + if (data == null || data.length <= 0) { + throw new RuntimeException("获取二维码失败"); + } + + result.put("code", "200"); + result.put("data", data); + return result; + } catch (IOException e) { + logger.error("获取二维码异常", e); + result.put("code", "-1"); + result.put("msg", "获取二维码异常"); + return result; + } + } + + /** + * 获取跳转uri(等待扫码认证) + * + * @return + */ + public JSONObject getRedirectUri(HttpClient httpClient, + String uuid) { + long millis = System.currentTimeMillis(); + String url = new ST(PropertiesUtil.getProperty("webwx-url.redirect_uri")) + .add("uuid", uuid) + .add("r", millis / 1252L) + .add("_", millis) + .render(); + + HttpGet httpGet = new HttpGet(url); + httpGet.setHeader("User-Agent", PropertiesUtil.getProperty("wechat4j.userAgent")); + httpGet.setConfig(RequestConfig.custom().setRedirectsEnabled(false).build()); + + JSONObject result = new JSONObject(); + + try { + HttpResponse response = httpClient.execute(httpGet); + if (HttpStatus.SC_OK != response.getStatusLine().getStatusCode()) { + throw new RuntimeException("请求错误"); + } + + HttpEntity entity = response.getEntity(); + String res = EntityUtils.toString(entity); + + Matcher matcher = PATTERN_REDIRECT_URI_1.matcher(res); + if (!matcher.find()) { + throw new RuntimeException("获取跳转uri失败"); + } + + String code = matcher.group(1); + result.put("code", code); + if ("408".equals(code)) { + result.put("msg", "请扫描二维码"); + } else if ("400".equals(code)) { + result.put("msg", "二维码失效"); + } else if ("201".equals(code)) { + result.put("msg", "请在手机上点击确认"); + } else if ("200".equals(code)) { + matcher = PATTERN_REDIRECT_URI_2.matcher(res); + if (!matcher.find()) { + throw new RuntimeException("获取跳转uri失败"); + } + result.put("msg", "手机确认成功"); + result.put("redirectUri", matcher.group(2)); + } else { + result.put("msg", "扫码失败"); + } + + return result; + } catch (IOException e) { + logger.error("获取跳转uri异常", e); + result.put("code", "-3"); + result.put("msg", "获取跳转uri异常"); + return result; + } + } + + /** + * 获取登录认证码 + * 此方法执行后,其它web端微信、pc端都会下线 + */ + public JSONObject getLoginCode(HttpClient httpClient, + String redirectUri) { + String url = new ST(PropertiesUtil.getProperty("webwx-url.newlogin_url")) + .add("redirectUri", redirectUri) + .render(); + + HttpGet httpGet = new HttpGet(url); + httpGet.setHeader("User-Agent", PropertiesUtil.getProperty("wechat4j.userAgent")); + httpGet.setConfig(RequestConfig.custom().setRedirectsEnabled(false).build()); + + JSONObject result = new JSONObject(); + + try { + HttpResponse response = httpClient.execute(httpGet); + if (HttpStatus.SC_OK != response.getStatusLine().getStatusCode()) { + throw new RuntimeException("请求错误"); + } + + HttpEntity entity = response.getEntity(); + String res = EntityUtils.toString(entity); + + JSONObject json = JSONObject.parseObject(XML.toJSONObject(res).toString()).getJSONObject("error"); + result.putAll(json); + result.put("msg", result.getString("message")); + if (result.getIntValue("ret") == 0) { + result.put("code", "200"); + } + + return result; + } catch (IOException e) { + logger.error("获取登录认证码异常", e); + result.put("code", "-1"); + result.put("msg", "获取登录认证码异常"); + return result; + } + } + + /** + * 退出登录 + */ + public void logout(HttpClient httpClient, + String wxsid, + String skey, + String wxuin) { + //type=0 + String url = new ST(PropertiesUtil.getProperty("webwx-url.logout_url")) + .add("type", 0) + .add("skey", StringUtil.encodeURL(skey, "UTF-8")) + .render(); + + HttpPost httpPost = new HttpPost(url); + httpPost.setHeader("User-Agent", PropertiesUtil.getProperty("wechat4j.userAgent")); + httpPost.setHeader("Content-type", ContentType.APPLICATION_FORM_URLENCODED.toString()); + + List pairList = new ArrayList<>(); + pairList.add(new BasicNameValuePair("sid", wxsid)); + pairList.add(new BasicNameValuePair("uin", wxuin)); + + try { + HttpEntity paramEntity = new UrlEncodedFormEntity(pairList); + httpPost.setEntity(paramEntity); + + httpClient.execute(httpPost); + } catch (IOException e) { + logger.error("退出登录异常", e); + } + + //type=1 + String url1 = new ST(PropertiesUtil.getProperty("webwx-url.logout_url")) + .add("type", 1) + .add("skey", StringUtil.encodeURL(skey, "UTF-8")) + .render(); + + HttpPost httpPost1 = new HttpPost(url1); + httpPost.setHeader("User-Agent", PropertiesUtil.getProperty("wechat4j.userAgent")); + httpPost.setHeader("Content-type", ContentType.APPLICATION_FORM_URLENCODED.toString()); + + try { + HttpEntity paramEntity = new UrlEncodedFormEntity(pairList); + httpPost.setEntity(paramEntity); + + httpClient.execute(httpPost1); + } catch (IOException e) { + logger.error("退出登录异常", e); + } + } + + /** + * 数据初始化 + */ + public JSONObject webWeixinInit(HttpClient httpClient, + String passticket, + String wxsid, + String skey, + String wxuin) { + String url = new ST(PropertiesUtil.getProperty("webwx-url.webwxinit_url")) + .add("pass_ticket", passticket) + .add("r", System.currentTimeMillis() / 1252L) + .render(); + + HttpPost httpPost = new HttpPost(url); + httpPost.setHeader("User-Agent", PropertiesUtil.getProperty("wechat4j.userAgent")); + httpPost.setHeader("Content-type", ContentType.APPLICATION_JSON.toString()); + + JSONObject paramJson = new JSONObject(); + paramJson.put("BaseRequest", WechatUtil.createBaseRequest(wxsid, skey, wxuin)); + HttpEntity paramEntity = new StringEntity(paramJson.toJSONString(), Consts.UTF_8); + httpPost.setEntity(paramEntity); + + try { + HttpResponse response = httpClient.execute(httpPost); + if (HttpStatus.SC_OK != response.getStatusLine().getStatusCode()) { + throw new RuntimeException("请求错误"); + } + + HttpEntity entity = response.getEntity(); + String res = EntityUtils.toString(entity, Consts.UTF_8); + + return JSONObject.parseObject(res); + } catch (IOException e) { + logger.error("数据初始化异常", e); + return null; + } + } + + /** + * 开启消息状态通知 + * + * @return + */ + public JSONObject statusNotify(HttpClient httpClient, + String passticket, + String wxsid, + String skey, + String wxuin, + String loginUserName) { + String url = new ST(PropertiesUtil.getProperty("webwx-url.statusnotify_url")) + .add("pass_ticket", passticket) + .render(); + + HttpPost httpPost = new HttpPost(url); + httpPost.setHeader("User-Agent", PropertiesUtil.getProperty("wechat4j.userAgent")); + httpPost.setHeader("Content-type", ContentType.APPLICATION_JSON.toString()); + + JSONObject paramJson = new JSONObject(); + paramJson.put("BaseRequest", WechatUtil.createBaseRequest(wxsid, skey, wxuin)); + paramJson.put("ClientMsgId", System.currentTimeMillis()); + paramJson.put("Code", 3); + paramJson.put("FromUserName", loginUserName); + paramJson.put("ToUserName", loginUserName); + HttpEntity paramEntity = new StringEntity(paramJson.toJSONString(), Consts.UTF_8); + httpPost.setEntity(paramEntity); + + try { + HttpResponse response = httpClient.execute(httpPost); + if (HttpStatus.SC_OK != response.getStatusLine().getStatusCode()) { + throw new RuntimeException("请求错误"); + } + + HttpEntity entity = response.getEntity(); + String res = EntityUtils.toString(entity, Consts.UTF_8); + + return JSONObject.parseObject(res); + } catch (IOException e) { + logger.error("开启消息状态通知异常", e); + return null; + } + } + + /** + * 服务端状态同步心跳 + */ + public JSONObject syncCheck(HttpClient httpClient, + String wxsid, + String skey, + String wxuin, + JSONArray SyncKeyList) { + long millis = System.currentTimeMillis(); + String url = new ST(PropertiesUtil.getProperty("webwx-url.synccheck_url")) + .add("r", millis) + .add("skey", StringUtil.encodeURL(skey, "UTF-8")) + .add("sid", wxsid) + .add("uin", wxuin) + .add("deviceid", WechatUtil.createDeviceID()) + .add("synckey", StringUtil.encodeURL(WechatUtil.syncKeyListToString(SyncKeyList), "UTF-8")) + .add("_", millis) + .render(); + + HttpGet httpGet = new HttpGet(url); + httpGet.setHeader("User-Agent", PropertiesUtil.getProperty("wechat4j.userAgent")); + httpGet.setConfig(RequestConfig.custom().setRedirectsEnabled(false).build()); + + try { + HttpResponse response = httpClient.execute(httpGet); + if (HttpStatus.SC_OK != response.getStatusLine().getStatusCode()) { + throw new RuntimeException("请求错误"); + } + + HttpEntity entity = response.getEntity(); + String res = EntityUtils.toString(entity); + + String regExp = "window.synccheck=\\{retcode:\"(\\d+)\",selector:\"(\\d+)\"}"; + Matcher matcher = Pattern.compile(regExp).matcher(res); + if (!matcher.find()) { + throw new RuntimeException("服务端状态同步失败"); + } + + JSONObject result = new JSONObject(); + result.put("retcode", matcher.group(1)); + result.put("selector", matcher.group(2)); + + return result; + } catch (IOException e) { + logger.error("服务端状态同步异常", e); + return null; + } + } + + /** + * 获取全部联系人列表 + */ + public JSONObject getContact(HttpClient httpClient, + String passticket, + String skey) { + String url = new ST(PropertiesUtil.getProperty("webwx-url.getcontact_url")) + .add("pass_ticket", StringUtil.encodeURL(passticket, "UTF-8")) + .add("r", System.currentTimeMillis()) + .add("skey", StringUtil.encodeURL(skey, "UTF-8")) + .render(); + + HttpGet httpGet = new HttpGet(url); + httpGet.setHeader("User-Agent", PropertiesUtil.getProperty("wechat4j.userAgent")); + httpGet.setConfig(RequestConfig.custom().setRedirectsEnabled(false).build()); + + try { + HttpResponse response = httpClient.execute(httpGet); + if (HttpStatus.SC_OK != response.getStatusLine().getStatusCode()) { + throw new RuntimeException("请求错误"); + } + + HttpEntity entity = response.getEntity(); + String res = EntityUtils.toString(entity); + + return JSONObject.parseObject(res); + } catch (IOException e) { + logger.error("获取全部联系人列表异常", e); + return null; + } + } + + /** + * 批量获取指定用户信息 + */ + public JSONObject batchGetContact(HttpClient httpClient, + String passticket, + String wxsid, + String skey, + String wxuin, + JSONArray batchContactList) { + String url = new ST(PropertiesUtil.getProperty("webwx-url.batchgetcontact_url")) + .add("pass_ticket", StringUtil.encodeURL(passticket, "UTF-8")) + .add("r", System.currentTimeMillis()) + .render(); + + HttpPost httpPost = new HttpPost(url); + httpPost.setHeader("User-Agent", PropertiesUtil.getProperty("wechat4j.userAgent")); + httpPost.setHeader("Content-type", ContentType.APPLICATION_JSON.toString()); + + JSONObject paramJson = new JSONObject(); + paramJson.put("BaseRequest", WechatUtil.createBaseRequest(wxsid, skey, wxuin)); + paramJson.put("Count", batchContactList.size()); + paramJson.put("List", batchContactList); + HttpEntity paramEntity = new StringEntity(paramJson.toJSONString(), Consts.UTF_8); + httpPost.setEntity(paramEntity); + + try { + HttpResponse response = httpClient.execute(httpPost); + if (HttpStatus.SC_OK != response.getStatusLine().getStatusCode()) { + throw new RuntimeException("请求错误"); + } + + HttpEntity entity = response.getEntity(); + String res = EntityUtils.toString(entity); + + return JSONObject.parseObject(res); + } catch (IOException e) { + logger.error("批量获取指定联系人信息异常", e); + return null; + } + } + + /** + * 从服务端拉取新消息 + */ + public JSONObject pullNewMsg(HttpClient httpClient, + String passticket, + String wxsid, + String skey, + String wxuin, + JSONObject SyncKey) { + String url = new ST(PropertiesUtil.getProperty("webwx-url.webwxsync_url")) + .add("skey", skey) + .add("sid", wxsid) + .add("pass_ticket", passticket) + .render(); + + HttpPost httpPost = new HttpPost(url); + httpPost.setHeader("User-Agent", PropertiesUtil.getProperty("wechat4j.userAgent")); + httpPost.setHeader("Content-type", ContentType.APPLICATION_JSON.toString()); + + JSONObject paramJson = new JSONObject(); + paramJson.put("BaseRequest", WechatUtil.createBaseRequest(wxsid, skey, wxuin)); + paramJson.put("SyncKey", SyncKey); + HttpEntity paramEntity = new StringEntity(paramJson.toJSONString(), Consts.UTF_8); + httpPost.setEntity(paramEntity); + + try { + HttpResponse response = httpClient.execute(httpPost); + if (HttpStatus.SC_OK != response.getStatusLine().getStatusCode()) { + throw new RuntimeException("请求错误"); + } + + HttpEntity entity = response.getEntity(); + String res = EntityUtils.toString(entity, Consts.UTF_8); + + return JSONObject.parseObject(res); + } catch (IOException e) { + logger.error("开启消息状态通知异常", e); + return null; + } + } + + /** + * 发送消息 + */ + public JSONObject sendMsg(HttpClient httpClient, + String passticket, + String wxsid, + String skey, + String wxuin, + String Content, + int Type, + String FromUserName, + String ToUserName) { + String url = new ST(PropertiesUtil.getProperty("webwx-url.webwxsendmsg_url")) + .add("pass_ticket", passticket) + .render(); + + HttpPost httpPost = new HttpPost(url); + httpPost.setHeader("User-Agent", PropertiesUtil.getProperty("wechat4j.userAgent")); + httpPost.setHeader("Content-type", ContentType.APPLICATION_JSON.toString()); + + JSONObject paramJson = new JSONObject(); + paramJson.put("BaseRequest", WechatUtil.createBaseRequest(wxsid, skey, wxuin)); + paramJson.put("Msg", WechatUtil.createSendMsg(Content, Type, FromUserName, ToUserName)); + paramJson.put("Scene", 0); + HttpEntity paramEntity = new StringEntity(paramJson.toJSONString(), Consts.UTF_8); + httpPost.setEntity(paramEntity); + + try { + HttpResponse response = httpClient.execute(httpPost); + if (HttpStatus.SC_OK != response.getStatusLine().getStatusCode()) { + throw new RuntimeException("请求错误"); + } + + HttpEntity entity = response.getEntity(); + String res = EntityUtils.toString(entity, Consts.UTF_8); + + JSONObject result = JSONObject.parseObject(res); + + return result; + } catch (IOException e) { + logger.error("开启消息状态通知异常", e); + return null; + } + } +} diff --git a/src/main/java/com/hotlcc/wechat4j/util/PropertiesUtil.java b/src/main/java/com/hotlcc/wechat4j/util/PropertiesUtil.java new file mode 100644 index 0000000..75a50a7 --- /dev/null +++ b/src/main/java/com/hotlcc/wechat4j/util/PropertiesUtil.java @@ -0,0 +1,56 @@ +package com.hotlcc.wechat4j.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +public final class PropertiesUtil { + private static Logger logger = LoggerFactory.getLogger(PropertiesUtil.class); + + private PropertiesUtil() { + } + + private static final Properties prop = new Properties(); + + static { + loadProperties(new String[]{ + "config/app.properties", + "config/webwx-url.properties" + }); + } + + private static void loadProperties(String[] paths) { + if (paths == null) { + return; + } + + for (String path : paths) { + InputStream is = null; + try { + is = PropertiesUtil.class.getClassLoader().getResourceAsStream(path); + prop.load(is); + } catch (Exception e) { + logger.error("Loading properties file \"" + path + "\" error.", e); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + } + + public static String getProperty(String key) { + return prop.getProperty(key); + } + + public static String getProperty(String key, String defaultValue) { + return prop.getProperty(key, defaultValue); + } +} diff --git a/src/main/java/com/hotlcc/wechat4j/util/QRCodeUtil.java b/src/main/java/com/hotlcc/wechat4j/util/QRCodeUtil.java new file mode 100644 index 0000000..c9ac534 --- /dev/null +++ b/src/main/java/com/hotlcc/wechat4j/util/QRCodeUtil.java @@ -0,0 +1,230 @@ +package com.hotlcc.wechat4j.util; + +import com.google.zxing.Binarizer; +import com.google.zxing.LuminanceSource; +import com.google.zxing.NotFoundException; +import com.google.zxing.client.j2se.BufferedImageLuminanceSource; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.common.HybridBinarizer; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * 二维码工具类 + * + * @author Allen + */ +public final class QRCodeUtil { + private QRCodeUtil() { + } + + /** + * 从BitMatrix中得到boolean矩阵(不去除周围空白部分) + * + * @return + */ + private static boolean[][] toBoolMatrix(BitMatrix matrix) { + return toBoolMatrix(matrix, false); + } + + /** + * 从BitMatrix中得到boolean矩阵 + * + * @param matrix + * @param noMargin 是否去除周围空白 + * @return + */ + private static boolean[][] toBoolMatrix(BitMatrix matrix, boolean noMargin) { + int width = matrix.getWidth(); + int height = matrix.getHeight(); + int top = 0, left = 0, bottom = 0, right = 0; + if (noMargin) { + int[] tl = matrix.getTopLeftOnBit(); + top = tl[0]; + left = tl[1]; + int[] br = matrix.getBottomRightOnBit(); + bottom = height - br[0] - 1; + right = width - br[1] - 1; + } + boolean[][] m = new boolean[height - top - bottom][width - left - right]; + for (int h = 0 + top, i = 0; h < height - bottom; h++, i++) { + for (int w = 0 + left, j = 0; w < width - right; w++, j++) { + m[i][j] = matrix.get(w, h); + } + } + return m; + } + + /** + * 将矩阵逆时针转90度 + * + * @param matrix + * @return + */ + private static boolean[][] reverseMatrix(boolean[][] matrix) { + if (matrix == null) { + return null; + } + + int height = matrix.length; + int width = matrix[0].length; + + boolean[][] matrix2 = new boolean[width][height]; + for (int h = 0; h < height; h++) { + for (int w = 0; w < width; w++) { + matrix2[width - 1 - w][h] = matrix[h][w]; + } + } + + return matrix2; + } + + /** + * 从boolMatrix左上角判断二维码定位标记的大小 + * + * @param boolMatrix + * @return + */ + private static int getBitCharSize(boolean[][] boolMatrix) { + int a = 0, b = 0; + out: + for (int i = 0, len = boolMatrix.length; i < len; i++) { + boolean find = false; + boolean[] boolArr = boolMatrix[i]; + for (int i2 = 0, len2 = boolArr.length; i2 < len2; i2++) { + if (!find && boolArr[i2]) { + find = true; + a = i2; + } + if (find && !boolArr[i2]) { + b = i2; + break out; + } + } + } + + return (b - a); + } + + /** + * 从boolMatrix判断bit-char占位比 + * + * @param boolMatrix + * @return + */ + private static int getBitCharRatio(boolean[][] boolMatrix) { + int[] size = new int[4]; + size[0] = getBitCharSize(boolMatrix); + for (int i = 1; i < 4; i++) { + boolMatrix = reverseMatrix(boolMatrix); + size[i] = getBitCharSize(boolMatrix); + } + + Map map = new HashMap<>(); + for (int s : size) { + Integer count = map.get(s); + if (count == null) { + map.put(s, 1); + } else { + map.put(s, count + 1); + } + } + Set> entrySet = map.entrySet(); + Integer k = null, v = null; + int flag = 0; + for (Map.Entry entry : entrySet) { + if (flag++ == 0) { + k = entry.getKey(); + v = entry.getValue(); + continue; + } + if (entry.getValue() > v) { + k = entry.getKey(); + v = entry.getValue(); + } + } + + return k.intValue() / 7; + } + + /** + * 将二维码图片转为字符矩阵 + * + * @param image + * @return + */ + public static String toCharMatrix(BufferedImage image, String onStr, String offStr) { + LuminanceSource source = new BufferedImageLuminanceSource(image); + Binarizer binarizer = new HybridBinarizer(source); + BitMatrix matrix = null; + try { + matrix = binarizer.getBlackMatrix(); + boolean[][] boolMatrix = toBoolMatrix(matrix, true); + int ratio = getBitCharRatio(boolMatrix); + StringBuffer sb = new StringBuffer(); + for (int i = 0, len = boolMatrix.length; i < len; i += ratio) { + boolean[] boolArr = boolMatrix[i]; + for (int i2 = 0, len2 = boolArr.length; i2 < len2; i2 += ratio) { + sb.append(boolArr[i2] ? onStr : offStr); + } + sb.append("\n"); + } + + return sb.toString(); + } catch (NotFoundException e) { + throw new RuntimeException(e); + } + } + + /** + * 将二维码图片转为字符矩阵 + * + * @param image + * @return + */ + public static String toCharMatrix(BufferedImage image) { + return toCharMatrix(image, " ", "██"); + } + + /** + * 将二维码图片转为字符矩阵 + * + * @param data + * @return + */ + public static String toCharMatrix(byte[] data, String onStr, String offStr) { + ByteArrayInputStream bais = null; + try { + bais = new ByteArrayInputStream(data); + BufferedImage image = ImageIO.read(bais); + return QRCodeUtil.toCharMatrix(image, onStr, offStr); + } catch (IOException e) { + e.printStackTrace(); + return null; + } finally { + if (bais != null) { + try { + bais.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + /** + * 将二维码图片转为字符矩阵 + * + * @param data + * @return + */ + public static String toCharMatrix(byte[] data) { + return toCharMatrix(data, " ", "██"); + } +} \ No newline at end of file diff --git a/src/main/java/com/hotlcc/wechat4j/util/StringUtil.java b/src/main/java/com/hotlcc/wechat4j/util/StringUtil.java new file mode 100644 index 0000000..20b0e06 --- /dev/null +++ b/src/main/java/com/hotlcc/wechat4j/util/StringUtil.java @@ -0,0 +1,34 @@ +package com.hotlcc.wechat4j.util; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; + +public final class StringUtil { + private StringUtil() { + } + + public static boolean isEmpty(String str) { + return str == null || "".equals(str); + } + + public static boolean isNotEmpty(String str) { + return !isEmpty(str); + } + + public static String encodeURL(String str, String enc) { + try { + return URLEncoder.encode(str, enc); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + public static String decodeURL(String str, String enc) { + try { + return URLDecoder.decode(str, enc); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/com/hotlcc/wechat4j/util/WechatUtil.java b/src/main/java/com/hotlcc/wechat4j/util/WechatUtil.java new file mode 100644 index 0000000..25c3ef2 --- /dev/null +++ b/src/main/java/com/hotlcc/wechat4j/util/WechatUtil.java @@ -0,0 +1,94 @@ +package com.hotlcc.wechat4j.util; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import org.apache.commons.lang3.RandomStringUtils; + +public final class WechatUtil { + private WechatUtil() { + } + + private static String STRING_CHARS_1 = "123456789"; + private static String STRING_CHARS_2 = "1234567890"; + + /** + * 创建一个设备ID + * + * @return + */ + public static String createDeviceID() { + return "e" + RandomStringUtils.random(15, STRING_CHARS_1); + } + + /** + * 创建一个消息ID + * + * @return + */ + public static String createMsgId() { + return System.currentTimeMillis() + RandomStringUtils.random(4, STRING_CHARS_2); + } + + /** + * 创建BaseRequest + * + * @return + */ + public static JSONObject createBaseRequest(String DeviceID, String wxsid, String skey, String wxuin) { + JSONObject BaseRequest = new JSONObject(); + BaseRequest.put("DeviceID", DeviceID); + BaseRequest.put("Sid", wxsid); + BaseRequest.put("Skey", skey); + BaseRequest.put("Uin", wxuin); + return BaseRequest; + } + + /** + * 创建BaseRequest + * + * @return + */ + public static JSONObject createBaseRequest(String wxsid, String skey, String wxuin) { + return createBaseRequest(createDeviceID(), wxsid, skey, wxuin); + } + + /** + * 把SyncKeyList转为字符串格式 + * + * @param SyncKeyList + * @return + */ + public static String syncKeyListToString(JSONArray SyncKeyList) { + if (SyncKeyList == null) { + return null; + } + StringBuffer synckey = new StringBuffer(); + for (int i = 0, len = SyncKeyList.size(); i < len; i++) { + JSONObject json = SyncKeyList.getJSONObject(i); + if (i > 0) { + synckey.append("|"); + } + synckey.append(json.getString("Key")) + .append("_") + .append(json.getString("Val")); + } + return synckey.toString(); + } + + /** + * 创建要发送的Msg + * + * @return + */ + public static JSONObject createSendMsg(String Content, int Type, String FromUserName, String ToUserName) { + JSONObject Msg = new JSONObject(); + String msgId = WechatUtil.createMsgId(); + Msg.put("ClientMsgId", msgId); + Msg.put("Content", Content); + Msg.put("FromUserName", FromUserName); + Msg.put("LocalID", msgId); + Msg.put("ToUserName", ToUserName); + Msg.put("Type", Type); + return Msg; + } +} diff --git a/src/main/resources/config/app.properties b/src/main/resources/config/app.properties new file mode 100644 index 0000000..d8eca08 --- /dev/null +++ b/src/main/resources/config/app.properties @@ -0,0 +1,4 @@ +# web微信对应的appid +webwx.appid=wx782c26e4c19acffb +# httpclient的UserAgent +wechat4j.userAgent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36 \ No newline at end of file diff --git a/src/main/resources/config/webwx-url.properties b/src/main/resources/config/webwx-url.properties new file mode 100644 index 0000000..f81c1de --- /dev/null +++ b/src/main/resources/config/webwx-url.properties @@ -0,0 +1,28 @@ +# 1、登录 +## 1.1、获取微信uuid +webwx-url.uuid_url=https://login.wx2.qq.com/jslogin?appid=&redirect_uri=https%3A%2F%2Fwx2.qq.com%2Fcgi-bin%2Fmmwebwx-bin%2Fwebwxnewloginpage&fun=new&lang=zh_CN&_=<_> +## 1.2、获取二维码 +webwx-url.qrcode_url=https://login.weixin.qq.com/qrcode/ +## 1.3、等待扫码登录并获取跳转url +webwx-url.redirect_uri=https://login.wx2.qq.com/cgi-bin/mmwebwx-bin/login?loginicon=true&uuid=&tip=0&r=&_=<_> +## 1.4、获取登录认证码 +webwx-url.newlogin_url=&fun=new&version=v2 +## 1.5、退出登录 +webwx-url.logout_url=https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxlogout?redirect=1&type=&skey= +# 2、数据同步 +## 2.1、页面初始化 +webwx-url.webwxinit_url=https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=&lang=zh_CN&pass_ticket= +## 2.2、开启消息状态通知 +webwx-url.statusnotify_url=https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxstatusnotify?lang=zh_CN&pass_ticket= +## 2.3、服务端状态同步 +webwx-url.synccheck_url=https://webpush.wx2.qq.com/cgi-bin/mmwebwx-bin/synccheck?r=&skey=&sid=&uin=&deviceid=&synckey=&_=<_> +# 3、联系人管理 +## 3.1、获取全部联系人列表 +webwx-url.getcontact_url=https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact?lang=zh_CN&pass_ticket=&r=&seq=0&skey= +## 3.2、批量获取指定联系人列表 +webwx-url.batchgetcontact_url=https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxbatchgetcontact?type=ex&r=&lang=zh_CN&pass_ticket= +# 4、收发消息 +## 4.1、从服务端拉取新消息 +webwx-url.webwxsync_url=https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid=&skey=&pass_ticket= +## 4.2、发送消息 +webwx-url.webwxsendmsg_url=https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?pass_ticket= diff --git a/src/test/java/TestClass.java b/src/test/java/TestClass.java new file mode 100644 index 0000000..0bff038 --- /dev/null +++ b/src/test/java/TestClass.java @@ -0,0 +1,133 @@ +import com.alibaba.fastjson.JSONObject; +import com.hotlcc.wechat4j.Wechat; +import com.hotlcc.wechat4j.api.WebWeixinApi; +import com.hotlcc.wechat4j.util.QRCodeUtil; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.FileInputStream; +import java.io.IOException; + +public class TestClass { + private static Logger logger = LoggerFactory.getLogger(TestClass.class); + + //@Test +// public void test01() { +// JSONObject result = null; +// +// String redirectUri = null; +// login: +// while (true) { +// //1、获取uuid +// logger.info("开始获取uuid..."); +// String uuid = null; +// while (uuid == null || "".equals(uuid)) { +// result = webWeixinApi.getWxUuid(); +// if (result != null) { +// uuid = result.getString("uuid"); +// } +// if (uuid == null || "".equals(uuid)) { +// logger.info("获取uuid失败,将自动重试"); +// try { +// Thread.sleep(2000); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } +// } else { +// break; +// } +// } +// logger.info("获取uuid成功,值为:{}", uuid); +// +// //2、获取二维码 +// logger.info("开始获取二维码..."); +// result = webWeixinApi.getQR(uuid); +// logger.info("获取二维码成功,请扫描二维码:\n{}", QRCodeUtil.toCharMatrix(result.getBytes("data"))); +// +// //3、轮询 +// String code = null; +// while (!"200".equals(code)) { +// result = webWeixinApi.getRedirectUri(uuid); +// code = result.getString("code"); +// if ("408".equals(code)) { +// continue; +// } else if ("400".equals(code)) { +// logger.info("二维码失效,将自动获取新的二维码"); +// continue login; +// } else if ("201".equals(code)) { +// logger.info("请在手机上确认"); +// continue; +// } else if ("200".equals(code)) { +// redirectUri = result.getString("redirectUri"); +// logger.info("手机端认证成功"); +// break login; +// } else { +// break login; +// } +// } +// } +// //4、获取登录认证码 +// logger.info("开始获取登录认证码"); +// result = webWeixinApi.getLoginCode(redirectUri); +// String wxsid = result.getString("wxsid"); +// String passTicket = result.getString("pass_ticket"); +// String skey = result.getString("skey"); +// String wxuin = result.getString("wxuin"); +// logger.info("获取登录认证码成功"); +// //5、初始化数据 +// logger.info("开始初始化数据"); +// result = webWeixinApi.webWeixinInit(passTicket, wxsid, skey, wxuin); +// JSONObject loginUser = result.getJSONObject("User"); +// logger.info("欢迎回来,{}", loginUser.getString("NickName")); +// JSONObject SyncKey = result.getJSONObject("SyncKey"); +// logger.info("初始化数据完成"); +// //6、开启消息状态通知 +// logger.info("开始开启消息状态通知"); +// result = webWeixinApi.statusNotify(passTicket, wxsid, skey, wxuin, loginUser.getString("UserName")); +// logger.info("开启消息状态通知完成"); +// //7、服务端状态同步 +// logger.info("开始轮询服务端状态"); +// while (true) { +// result = webWeixinApi.syncCheck(wxsid, skey, wxuin, SyncKey.getJSONArray("List")); +// int retcode = result.getIntValue("retcode"); +// if (retcode != 0) { +// logger.info("微信已退出登录"); +// break; +// } else { +// int selector = result.getIntValue("selector"); +// if (selector == 2) { +// logger.info("收到新消息"); +// //8、获取新消息内容 +// result = webWeixinApi.pullNewMsg(passTicket, wxsid, skey, wxuin, SyncKey); +// SyncKey = result.getJSONObject("SyncKey"); +// JSONObject Msg = result.getJSONArray("AddMsgList").getJSONObject(0); +// String Content = Msg.getString("Content"); +// int MsgType = Msg.getIntValue("MsgType"); +// String FromUserName = Msg.getString("FromUserName"); +// String ToUserName = Msg.getString("ToUserName"); +// logger.info("消息类型:{},消息内容:{},发送方:{},接收方:{}", MsgType, Content, FromUserName, ToUserName); +// result = webWeixinApi.sendMsg(passTicket, wxsid, skey, wxuin, Content, MsgType, ToUserName, FromUserName); +// logger.info("自动回复消息完成,返回:{}", result); +// } +// } +// } +// } + + public void test03() throws IOException { + BufferedImage image = ImageIO.read(new FileInputStream("D:/2.jpg")); + System.out.println(QRCodeUtil.toCharMatrix(image)); + } + + @Test + public void test04() { + WebWeixinApi api = new WebWeixinApi(); + Wechat wechat = new Wechat(); + wechat.setWebWeixinApi(api); + + + } +} diff --git a/src/test/resources/logback.xml b/src/test/resources/logback.xml new file mode 100644 index 0000000..3cdec0c --- /dev/null +++ b/src/test/resources/logback.xml @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + [%-5level] %date{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger{36} - %m%n + + + + + + + INFO + ACCEPT + DENY + + ${LOG_DIRECTORY}/info.log + true + + ${LOG_DIRECTORY}%d/info%i.log + ${MAX_HISTORY} + ${FILE_SIZE} + ${TOTAL_SIZE} + + + [%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger{36} - %m%n + + + + + + + [%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger{36} - %m%n + + + ERROR + ACCEPT + DENY + + ${LOG_DIRECTORY}/error.log + true + + ${LOG_DIRECTORY}%d/error%i.log + ${MAX_HISTORY} + ${FILE_SIZE} + ${TOTAL_SIZE} + + + + + + + [%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger{36} - %m%n + + + WARN + ACCEPT + DENY + + ${LOG_DIRECTORY}/warn.log + true + + ${LOG_DIRECTORY}%d/warn%i.log + ${MAX_HISTORY} + ${FILE_SIZE} + ${TOTAL_SIZE} + + + + + + + [%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger{36} - %m%n + + + DEBUG + ACCEPT + DENY + + ${LOG_DIRECTORY}/debug.log + true + + ${LOG_DIRECTORY}%d/debug%i.log + ${MAX_HISTORY} + ${FILE_SIZE} + + + + + + + [%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger{36} - %m%n + + + TRACE + ACCEPT + DENY + + ${LOG_DIRECTORY}/trace.log + true + + ${LOG_DIRECTORY}%d/trace%i.log + ${MAX_HISTORY} + ${FILE_SIZE} + + + + + + [%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger{36} - %m%n + + + + ${LOG_DIRECTORY}/${APP_NAME}.log + true + + ${LOG_DIRECTORY}%d/${APP_NAME}%i.log + ${MAX_HISTORY} + ${FILE_SIZE} + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file