2
0
mirror of https://gitee.com/hotlcc/wechat4j.git synced 2026-01-15 15:21:56 +08:00

28 Commits

Author SHA1 Message Date
Allen
53e7fedc6c !11 升级版本号[0.2.2->0.2.3]
Merge pull request !11 from Allen/feature/20180914
2019-04-09 11:42:30 +08:00
Allen
31f84b62bf 升级版本号[0.2.2->0.2.3] 2019-04-09 11:41:26 +08:00
Allen
c9b7c7c4bb !10 升级版本号[0.2.2->0.2.3]
Merge pull request !10 from Allen/feature/20180914
2019-04-09 11:38:40 +08:00
Allen
a98b21b23c 支持Anti996协议 2019-04-09 11:35:30 +08:00
Allen
9f36722bc4 更新 README.md 2019-03-31 00:56:54 +08:00
Allen
533f936ac0 !9 v0.2.2
Merge pull request !9 from Allen/feature/20180914
2018-09-19 17:02:15 +08:00
Allen
f0b1690023 提交代码:升级版本号[0.2.1->0.2.2] 2018-09-19 16:57:58 +08:00
Allen
743d40c847 提交代码:修复参数位置错误的验证bug 2018-09-19 16:57:07 +08:00
Allen
5c9aee686e !8 v0.2.1
Merge pull request !8 from Allen/feature/20180914
2018-09-18 14:43:15 +08:00
Allen
0d8b3c78e7 提交代码:升级版本号[0.2.0->0.2.1] 2018-09-18 14:40:46 +08:00
Allen
cf228920ee 提交代码:代码规范性调整 2018-09-18 14:39:55 +08:00
Allen
4eebb0ab10 提交代码:修改潜在bug 2018-09-14 21:48:43 +08:00
Allen
ce21807e3b 提交代码:bug修复 2018-09-14 20:55:12 +08:00
Allen
cba6b34a16 提交代码:bug修复 2018-09-14 20:48:48 +08:00
Allen
1c57501c76 !7 v0.2.0
Merge pull request !7 from Allen/feature/20180914
2018-09-14 16:30:30 +08:00
Allen
ed6a842b8f 提交代码:更新readme文档 2018-09-14 16:29:03 +08:00
Allen
8120000809 !6 v0.2.0
Merge pull request !6 from Allen/feature/20180914
2018-09-14 16:23:05 +08:00
Allen
97e67e28e1 提交代码:web微信api文档修改 2018-09-14 16:21:39 +08:00
Allen
80538def1a !5 v0.2.0
Merge pull request !5 from Allen/feature/20180914
2018-09-14 16:19:36 +08:00
Allen
c6d1ca83f3 提交代码:升级版本号[0.1.2->0.2.0] 2018-09-14 16:16:31 +08:00
Allen
ab094dad76 提交代码:更新web微信api文档 2018-09-14 16:15:35 +08:00
Allen
59a95df99a 提交代码:新增发送图片方法;调整发送文本方法的参数; 2018-09-14 16:15:00 +08:00
Allen
5995be80ba !4 v0.1.2
Merge pull request !4 from Allen/feature/20180914
2018-09-14 10:54:08 +08:00
Allen
0c6356a71a 提交代码:升级版本号[0.1.1->0.1.2] 2018-09-14 10:50:13 +08:00
Allen
76a2fa0dc1 提交代码:添加发送文本消息(根据多种名称)的方法 2018-09-14 10:49:24 +08:00
Allen
560d47bb85 !3 v0.1.1
Merge pull request !3 from Allen/feature/20180914
2018-09-14 10:24:01 +08:00
Allen
b1572e2bc7 提交代码:升级版本号[0.1.0->0.1.1] 2018-09-14 10:20:28 +08:00
Allen
329eb96602 提交代码:调整了test目录的logback.xml 2018-09-14 10:16:35 +08:00
29 changed files with 1018 additions and 547 deletions

View File

@@ -6,11 +6,9 @@
码云主页:[https://gitee.com/hotlcc](https://gitee.com/hotlcc)
[![gitee](https://img.shields.io/badge/gitee-%40hotlcc-green.svg)](https://gitee.com/hotlcc)
[![gitee](https://img.shields.io/badge/github-%40hotlcc-blank.svg)](https://github.com/hotlcc)
[![toutiao](https://img.shields.io/badge/toutiao-%40Allen-red.svg)](https://www.toutiao.com/c/user/3341863552/#mid=51655113888)
[![@Allen on weibo](https://img.shields.io/badge/weibo-%40Allen-orange.svg)](https://weibo.com/hotloveu?is_hot=1)
[![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](http://www.apache.org/licenses/LICENSE-2.0.html)
[![License](https://img.shields.io/badge/license-Anti%20996-4EB1BA.svg)](https://raw.githubusercontent.com/996icu/996.ICU/master/LICENSE_CN)
<a href="https://996.icu"><img src="https://img.shields.io/badge/link-996.icu-red.svg"></a>
## Web微信API文档
@@ -33,14 +31,33 @@ wechat.autoLogin();
```java
// 通过userName发送文本消息
public JSONObject sendText(String content, String userName);
// 通过昵称发送消息
public JSONObject sendTextToNickName(String content, String nickName);
// 通过备注名发送消息
public JSONObject sendTextToRemarkName(String content, String remarkName);
JSONObject sendText(String content, String userName);
// 通过昵称发送文本消息
JSONObject sendTextToNickName(String content, String nickName);
// 通过备注名发送文本消息
JSONObject sendTextToRemarkName(String content, String remarkName);
// 发送文本消息(根据多种名称)
JSONObject sendText(String userName, String nickName, String remarkName, String content);
```
> 目前仅支持发送文本消息,更多消息类型支持尽请期待。
### 图片消息
```java
// 通过userName发送图片消息
JSONObject sendImage(String userName, byte[] mediaData, String mediaName, ContentType contentType);
JSONObject sendImage(String userName, File image);
// 通过昵称发送图片消息
JSONObject sendImageToNickName(String nickName, byte[] mediaData, String mediaName, ContentType contentType);
JSONObject sendImageToNickName(String nickName, File image);
// 通过备注名发送图片消息
JSONObject sendImageToRemarkName(String remarkName, byte[] mediaData, String mediaName, ContentType contentType);
JSONObject sendImageToRemarkName(String remarkName, File image);
// 发送图片消息(根据多种名称)
JSONObject sendImage(String userName, String nickName, String remarkName, byte[] mediaData, String mediaName, ContentType contentType);
JSONObject sendImage(String userName, String nickName, String remarkName, File image);
```
> 更多消息类型支持尽请期待。
## 消息处理器

View File

@@ -158,7 +158,7 @@ URL参数分析
FORM表单参数分析
|数名|示例值|描述|
|数名|示例值|描述|
|---|---|---|
|sid||登录时获取的;|
|uin||登录时获取的;|
@@ -996,3 +996,128 @@ POST参数分析
```
https://file.wx.qq.com/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json
```
若文件较小则只会产生一个该请求若文件较大则会产生多个该请求。多个请求表示以分片的方式发送文件数据分片大小约为524288。
FORM表单参数分析
|参数名|示例值|描述|
|---|---|---|
|~~id~~|WU_FILE_0|页面文件组件ID非必须|
|~~name~~|bd00c8eed5c2cd522e69f38317b46903.jpg|文件名;非必须;|
|~~type~~|image/jpeg|文件的MimeType非必须|
|~~lastModifieDate~~||文件最后修改时间;非必须;|
|~~size~~|99785|文件大小;非必须;|
|chunks|3|文件分片数量;文件较大需要分片时必须;|
|chunk|0|文件分片序号从0开始文件较大需要分片时必须|
|mediatype|pic|文件类型图片为pic其它文件为doc非必须|
|uploadmediarequest||JSON字符串必须|
|webwx_data_ticket||cookie中的数据必须|
|pass_ticket||登录时获取的;必须;|
|filename||文件二进制数据;必须;|
uploadmediarequest结构示例
```json
{
"UploadType": 2,
"BaseRequest": {
"Uin": 1245,
"Sid": "zxaODbK4ed",
"Skey": "@crypt_4f399da_3c7a87e93b7872bce",
"DeviceID": "e69688819413"
},
"ClientMediaId": 153686311,
"TotalLen": 87508,
"StartPos": 0,
"DataLen": 87508,
"MediaType": 4,
"FromUserName": "@6feb88ee01067121479f686",
"ToUserName": "@6feb88ee0106f67121479f686",
"FileMd5": "717d1dbb9833c7cdbdd09ac"
}
```
服务端返回数据:
```json
{
"BaseResponse": {
"Ret": 0,
"ErrMsg": ""
},
"MediaId": "@crypt_17ab5.................39bd9cfa9ab",
"StartPos": 99785,
"CDNThumbImgHeight": 56,
"CDNThumbImgWidth": 100,
"EncryFileName": "bd00c8e.....38317b46903.jpg"
}
```
上传成功后返回数据中会有MediaId如果是分片上传的则最后一个请求会返回MediaId。
### 4.4、发送图片消息
发送图片后紧接着4.3的是如下POST请求
```
https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsgimg?fun=async&f=json&lang=zh_CN&pass_ticket=xFAjPcq0eXh.......TYHHdHgVL52eHo%253D
```
URL参数分析
|数名|示例值|描述|
|---|---|---|
|pass_ticket|xFAjPcq0eXh.......TYHHdHgVL52eHo%253D|同前面获取的;|
POST的数据JSON
```json
{
"BaseRequest": {
"Uin": 16...245,
"Sid": "KvCP..../TNqh8",
"Skey": "@crypt_4fb399da......9f1b3d997c4b5eef82",
"DeviceID": "e2825...3652448"
},
"Msg": {
"Type": 3,
"MediaId": "@crypt_17ab5.................39bd9cfa9ab",
"Content": "",
"FromUserName": "@3641f45.......69bed57642",
"ToUserName": "@3641f454....9bed57642",
"LocalID": "153691....0527",
"ClientMsgId": "1536....390527"
},
"Scene": 0
}
```
POST参数分析
|Key|示例值|描述|
|---|---|---|
|BaseRequest||同上;|
|Msg.ClientMsgId||同LocalID|
|Msg.Content||消息内容消息为媒体时该值为媒体ID。|
|Msg.FromUserName||发送用户;|
|Msg.LocalID||13位时间戳+4位随机数|
|Msg.ToUserName||接收用户;|
|Msg.Type|3|消息类型同3.1中接收的消息类型;|
|Msg.MediaId||4.3接口中返回的MediaId|
|Scene|0|固定值;|
服务端返回数据:
```json
{
"BaseResponse": {
"Ret": 0,
"ErrMsg": ""
},
"MsgID": "28832......8291836",
"LocalID": "1536.....54390527"
}
```
Web微信发送图片需要先调4.3的接口将媒体文件上传到服务器然后再调4.4的接口发送图片的相关信息。

View File

@@ -6,7 +6,7 @@
<groupId>com.hotlcc</groupId>
<artifactId>wechat4j</artifactId>
<version>0.1.0</version>
<version>0.2.3</version>
<name>wechat4j</name>
<description>Wechat client for Java.</description>

View File

@@ -6,10 +6,7 @@ import com.hotlcc.wechat4j.api.WebWeixinApi;
import com.hotlcc.wechat4j.enums.*;
import com.hotlcc.wechat4j.handler.ExitEventHandler;
import com.hotlcc.wechat4j.handler.ReceivedMsgHandler;
import com.hotlcc.wechat4j.model.BaseRequest;
import com.hotlcc.wechat4j.model.ReceivedMsg;
import com.hotlcc.wechat4j.model.UserInfo;
import com.hotlcc.wechat4j.model.WxMessage;
import com.hotlcc.wechat4j.model.*;
import com.hotlcc.wechat4j.util.*;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
@@ -17,6 +14,7 @@ import org.apache.http.HttpRequestInterceptor;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.protocol.HttpContext;
@@ -37,7 +35,7 @@ import java.util.concurrent.locks.ReentrantLock;
*
* @author Allen
*/
@SuppressWarnings("Duplicates")
@SuppressWarnings({"Duplicates", "unused"})
public class Wechat {
private static Logger logger = LoggerFactory.getLogger(Wechat.class);
@@ -84,6 +82,12 @@ public class Wechat {
this.webWeixinApi = webWeixinApi;
}
/**
* 获取Cookie
*
* @param name key
* @return Cookie
*/
private Cookie getCookie(String name) {
List<Cookie> cookies = cookieStore.getCookies();
if (cookies == null) {
@@ -97,6 +101,12 @@ public class Wechat {
return null;
}
/**
* 获取Cookie值
*
* @param name key
* @return Cookie值
*/
public String getCookieValue(String name) {
Cookie cookie = getCookie(name);
if (cookie == null) {
@@ -105,6 +115,11 @@ public class Wechat {
return cookie.getValue();
}
/**
* 添加退出事件处理器
*
* @param handler 退出事件处理器
*/
public void addExitEventHandler(ExitEventHandler handler) {
if (handler == null) {
return;
@@ -116,6 +131,11 @@ public class Wechat {
exitEventHandlers.add(handler);
}
/**
* 添加退出事件处理器
*
* @param handlers 退出事件处理器
*/
public void addExitEventHandler(Collection<ExitEventHandler> handlers) {
if (handlers == null || handlers.isEmpty()) {
return;
@@ -128,6 +148,11 @@ public class Wechat {
}
}
/**
* 添加接收消息处理器
*
* @param handler 接收消息处理器
*/
public void addReceivedMsgHandler(ReceivedMsgHandler handler) {
if (handler == null) {
return;
@@ -138,6 +163,11 @@ public class Wechat {
receivedMsgHandlers.add(handler);
}
/**
* 添加接收消息处理器
*
* @param handlers 接收消息处理器
*/
public void addReceivedMsgHandler(Collection<ReceivedMsgHandler> handlers) {
if (handlers == null || handlers.isEmpty()) {
return;
@@ -150,6 +180,12 @@ public class Wechat {
}
}
/**
* 构建http客户端
*
* @param cookieStore Cookie保存
* @return http客户端
*/
private HttpClient buildHttpClient(CookieStore cookieStore) {
HttpRequestInterceptor interceptor = new HttpRequestInterceptor() {
@Override
@@ -476,7 +512,7 @@ public class Wechat {
return true;
}
JSONObject result = null;
JSONObject result;
int time = PropertiesUtil.getIntValue("wechat4j.retry.time", 3);
// 2、登录
@@ -540,7 +576,7 @@ public class Wechat {
/**
* 自动登录
*
* @return
* @return 成功状态
*/
public boolean autoLogin() {
return autoLogin(null, false);
@@ -548,6 +584,8 @@ public class Wechat {
/**
* 退出登录
*
* @param clearAllLoginInfo 是否清除全部登录信息
*/
public void logout(boolean clearAllLoginInfo) {
if (isOnline) {
@@ -601,7 +639,7 @@ public class Wechat {
/**
* 判断在线状态
*
* @return
* @return 是否在线
*/
public boolean isOnline() {
return isOnline;
@@ -675,6 +713,9 @@ public class Wechat {
/**
* 处理退出事件
*
* @param type 退出类型
* @param t 异常
*/
private void processExitEvent(ExitTypeEnum type, Throwable t) {
try {
@@ -721,7 +762,7 @@ public class Wechat {
/**
* 处理selector值
*
* @param selector
* @param selector selector值
*/
private void processSelector(int selector) {
try {
@@ -753,8 +794,6 @@ public class Wechat {
/**
* 同步数据
*
* @return
*/
private void webWxSync() {
try {
@@ -764,21 +803,21 @@ public class Wechat {
return;
}
JSONObject BaseResponse = result.getJSONObject("BaseResponse");
if (BaseResponse == null) {
JSONObject baseResponse = result.getJSONObject("BaseResponse");
if (baseResponse == null) {
logger.warn("同步接口返回数据格式错误");
return;
}
int Ret = BaseResponse.getIntValue("Ret");
if (Ret != RetcodeEnum.RECODE_0.getCode()) {
logger.warn("同步接口返回错误代码:{}", Ret);
int ret = baseResponse.getIntValue("Ret");
if (ret != RetcodeEnum.RECODE_0.getCode()) {
logger.warn("同步接口返回错误代码:{}", ret);
return;
}
//新消息处理
JSONArray AddMsgList = result.getJSONArray("AddMsgList");
processNewMsg(AddMsgList);
JSONArray addMsgList = result.getJSONArray("AddMsgList");
processNewMsg(addMsgList);
//更新SyncKey
try {
@@ -795,15 +834,15 @@ public class Wechat {
/**
* 处理新消息
*
* @param AddMsgList
* @param addMsgList 消息列表
*/
private void processNewMsg(JSONArray AddMsgList) {
private void processNewMsg(JSONArray addMsgList) {
try {
if (AddMsgList == null || AddMsgList.isEmpty()) {
if (addMsgList == null || addMsgList.isEmpty()) {
return;
}
int len = AddMsgList.size();
int len = addMsgList.size();
logger.debug("收到{}条新消息", len);
if (receivedMsgHandlers == null || receivedMsgHandlers.isEmpty()) {
@@ -811,7 +850,7 @@ public class Wechat {
return;
}
List<ReceivedMsg> receivedMsgList = ReceivedMsg.valueOf(AddMsgList);
List<ReceivedMsg> receivedMsgList = ReceivedMsg.valueOf(addMsgList);
for (ReceivedMsg receivedMsg : receivedMsgList) {
for (ReceivedMsgHandler handler : receivedMsgHandlers) {
if (handler != null) {
@@ -836,7 +875,7 @@ public class Wechat {
/**
* 获取登录用户对象
*
* @return
* @return 登录用户对象
*/
public UserInfo getLoginUser(boolean update) {
if (loginUser == null || update) {
@@ -845,13 +884,13 @@ public class Wechat {
return loginUser;
}
JSONObject BaseResponse = result.getJSONObject("BaseResponse");
if (result == null) {
JSONObject baseResponse = result.getJSONObject("BaseResponse");
if (baseResponse == null) {
return loginUser;
}
int Ret = BaseResponse.getIntValue("Ret");
if (Ret != 0) {
int ret = baseResponse.getIntValue("Ret");
if (ret != 0) {
return loginUser;
}
@@ -876,7 +915,7 @@ public class Wechat {
/**
* 获取登录用户名
*
* @return
* @return 登录用户的用户名(加密的)
*/
public String getLoginUserName(boolean update) {
UserInfo loginUser = getLoginUser(update);
@@ -893,7 +932,7 @@ public class Wechat {
/**
* 获取登录用户的昵称
*
* @return
* @return 登录用户的昵称
*/
public String getLoginUserNickName(boolean update) {
UserInfo loginUser = getLoginUser(update);
@@ -910,8 +949,8 @@ public class Wechat {
/**
* 获取SyncKey
*
* @param update
* @return
* @param update 是否强制更新
* @return 返回数据
*/
private JSONObject getSyncKey(boolean update) {
if (syncKey == null || update) {
@@ -920,13 +959,13 @@ public class Wechat {
return syncKey;
}
JSONObject BaseResponse = result.getJSONObject("BaseResponse");
if (result == null) {
JSONObject baseResponse = result.getJSONObject("BaseResponse");
if (baseResponse == null) {
return syncKey;
}
int Ret = BaseResponse.getIntValue("Ret");
if (Ret != 0) {
int ret = baseResponse.getIntValue("Ret");
if (ret != 0) {
return syncKey;
}
@@ -947,22 +986,22 @@ public class Wechat {
/**
* 获取SyncKey的List
*
* @param update
* @return
* @param update 是否强制更新
* @return 返回数据
*/
private JSONArray getSyncKeyList(boolean update) {
JSONObject SyncKey = getSyncKey(update);
if (SyncKey == null) {
JSONObject syncKey = getSyncKey(update);
if (syncKey == null) {
return null;
}
return SyncKey.getJSONArray("List");
return syncKey.getJSONArray("List");
}
/**
* 获取联系人列表
*
* @param update
* @return
* @param update 是否强制更新
* @return 返回数据
*/
public List<UserInfo> getContactList(boolean update) {
if (contactList == null || update) {
@@ -971,13 +1010,13 @@ public class Wechat {
return contactList;
}
JSONObject BaseResponse = result.getJSONObject("BaseResponse");
if (BaseResponse == null) {
JSONObject baseResponse = result.getJSONObject("BaseResponse");
if (baseResponse == null) {
return contactList;
}
String Ret = BaseResponse.getString("Ret");
if (!"0".equals(Ret)) {
String ret = baseResponse.getString("Ret");
if (!"0".equals(ret)) {
return contactList;
}
@@ -1002,9 +1041,9 @@ public class Wechat {
/**
* 根据UserName获取联系人信息
*
* @param update
* @param userName
* @return
* @param update 是否强制更新
* @param userName 用户名(加密的)
* @return 返回数据
*/
public UserInfo getContactByUserName(boolean update, String userName) {
if (StringUtil.isEmpty(userName)) {
@@ -1036,9 +1075,9 @@ public class Wechat {
/**
* 根据NickName获取联系人信息
*
* @param update
* @param nickName
* @return
* @param update 是否强制更新
* @param nickName 昵称
* @return 返回数据
*/
public UserInfo getContactByNickName(boolean update, String nickName) {
if (StringUtil.isEmpty(nickName)) {
@@ -1070,9 +1109,9 @@ public class Wechat {
/**
* 根据RemarkName获取联系人信息
*
* @param update
* @param remarkName
* @return
* @param update 是否强制更新
* @param remarkName 备注名
* @return 返回数据
*/
public UserInfo getContactByRemarkName(boolean update, String remarkName) {
if (StringUtil.isEmpty(remarkName)) {
@@ -1104,9 +1143,11 @@ public class Wechat {
/**
* 发送文本消息
*
* @return
* @param userName 用户名(加密的)
* @param content 文本消息内容
* @return 返回数据
*/
public JSONObject sendText(String content, String userName) {
public JSONObject sendText(String userName, String content) {
BaseRequest baseRequest = new BaseRequest(wxsid, skey, wxuin);
String msgId = WechatUtil.createMsgId();
@@ -1123,21 +1164,19 @@ public class Wechat {
}
message.setType(MsgTypeEnum.TEXT_MSG.getCode());
JSONObject result = webWeixinApi.sendMsg(httpClient, urlVersion, passTicket, baseRequest, message);
return result;
return webWeixinApi.sendMsg(httpClient, urlVersion, passTicket, baseRequest, message);
}
/**
* 发送文本消息(根据昵称)
*
* @param content
* @param nickName
* @return
* @param nickName 昵称
* @param content 文本消息内容
* @return 返回数据
*/
public JSONObject sendTextToNickName(String content, String nickName) {
public JSONObject sendTextToNickName(String nickName, String content) {
if (StringUtil.isEmpty(nickName)) {
return sendText(content, null);
return sendText(null, content);
}
UserInfo userInfo = getContactByNickName(false, nickName);
@@ -1150,19 +1189,19 @@ public class Wechat {
return null;
}
return sendText(content, userName);
return sendText(userName, content);
}
/**
* 发送文本消息(根据备注名)
*
* @param content
* @param remarkName
* @return
* @param remarkName 备注
* @param content 文本消息内容
* @return 返回数据
*/
public JSONObject sendTextToRemarkName(String content, String remarkName) {
public JSONObject sendTextToRemarkName(String remarkName, String content) {
if (StringUtil.isEmpty(remarkName)) {
return sendText(content, null);
return sendText(null, content);
}
UserInfo userInfo = getContactByRemarkName(false, remarkName);
@@ -1175,18 +1214,232 @@ public class Wechat {
return null;
}
return sendText(content, userName);
return sendText(userName, content);
}
//TODO 待完成
@Deprecated
public JSONObject sendImage(File image, String userName) {
/**
* 发送文本消息(根据多种名称)
*
* @param userName 用户名(加密的)
* @param nickName 昵称
* @param remarkName 备注
* @param content 文本消息内容
* @return 返回数据
*/
public JSONObject sendText(String userName, String nickName, String remarkName, String content) {
UserInfo userInfo;
if (StringUtil.isNotEmpty(userName)) {
return sendText(userName, content);
} else if (StringUtil.isNotEmpty(nickName)) {
userInfo = getContactByNickName(false, nickName);
} else if (StringUtil.isNotEmpty(remarkName)) {
userInfo = getContactByRemarkName(false, remarkName);
} else {
String loginUserName = getLoginUserName(false);
return sendText(loginUserName, content);
}
if (userInfo == null) {
return null;
}
userName = userInfo.getUserName();
if (StringUtil.isEmpty(userName)) {
return null;
}
return sendText(userName, content);
}
/**
* 发送图片消息
*
* @param userName 用户名(加密的)
* @param mediaData 媒体文件数据
* @param mediaName 媒体文件名
* @param contentType 媒体文件类型
* @return 返回数据
*/
public JSONObject sendImage(String userName, byte[] mediaData, String mediaName, ContentType contentType) {
String loginUserName = getLoginUserName(false);
String toUserName = StringUtil.isEmpty(userName) ? loginUserName : userName;
BaseRequest baseRequest = new BaseRequest(wxsid, skey, wxuin);
String dataTicket = getCookieValue("webwx_data_ticket");
JSONObject result = webWeixinApi.uploadMedia(httpClient, urlVersion, passTicket, baseRequest, loginUserName, toUserName, dataTicket, image);
// 上传媒体文件
String dataTicket = getCookieValue("webwx_data_ticket");
JSONObject result = webWeixinApi.uploadMedia(httpClient, urlVersion, passTicket, baseRequest, loginUserName, toUserName, dataTicket, mediaData, mediaName, contentType);
if (result == null) {
return null;
}
JSONObject br = result.getJSONObject("BaseResponse");
if (br == null) {
return result;
}
int ret = br.getIntValue("Ret");
if (ret != 0) {
return result;
}
String mediaId = result.getString("MediaId");
if (StringUtil.isEmpty(mediaId)) {
return result;
}
// 发送图片消息
String msgId = WechatUtil.createMsgId();
MediaMessage message = new MediaMessage();
message.setClientMsgId(msgId);
message.setContent("");
message.setFromUserName(loginUserName);
message.setLocalID(msgId);
message.setMediaId(mediaId);
message.setToUserName(toUserName);
message.setType(MsgTypeEnum.IMAGE_MSG.getCode());
result = webWeixinApi.sendImageMsg(httpClient, urlVersion, passTicket, baseRequest, message);
return result;
}
/**
* 发送图片消息
*
* @param userName 用户名(加密的)
* @param image 图片文件
* @return 返回数据
*/
public JSONObject sendImage(String userName, File image) {
ContentType contentType = FileUtil.getContentBody(image);
byte[] mediaData = FileUtil.getBytes(image);
return sendImage(userName, mediaData, image.getName(), contentType);
}
/**
* 发送图片消息(根据昵称)
*
* @param nickName 昵称
* @param mediaData 媒体文件数据
* @param mediaName 媒体文件名
* @param contentType 媒体文件类型
* @return 返回数据
*/
public JSONObject sendImageToNickName(String nickName, byte[] mediaData, String mediaName, ContentType contentType) {
if (StringUtil.isEmpty(nickName)) {
return sendImage(null, mediaData, mediaName, contentType);
}
UserInfo userInfo = getContactByNickName(false, nickName);
if (userInfo == null) {
return null;
}
String userName = userInfo.getUserName();
if (StringUtil.isEmpty(userName)) {
return null;
}
return sendImage(userName, mediaData, mediaName, contentType);
}
/**
* 发送图片消息(根据昵称)
*
* @param nickName 昵称
* @param image 图片文件
* @return 返回数据
*/
public JSONObject sendImageToNickName(String nickName, File image) {
ContentType contentType = FileUtil.getContentBody(image);
byte[] mediaData = FileUtil.getBytes(image);
return sendImageToNickName(nickName, mediaData, image.getName(), contentType);
}
/**
* 发送图片消息(根据备注名)
*
* @param remarkName 备注名
* @param mediaData 媒体文件数据
* @param mediaName 媒体文件名
* @param contentType 媒体文件类型
* @return 返回数据
*/
public JSONObject sendImageToRemarkName(String remarkName, byte[] mediaData, String mediaName, ContentType contentType) {
if (StringUtil.isEmpty(remarkName)) {
return sendImage(null, mediaData, mediaName, contentType);
}
UserInfo userInfo = getContactByRemarkName(false, remarkName);
if (userInfo == null) {
return null;
}
String userName = userInfo.getUserName();
if (StringUtil.isEmpty(userName)) {
return null;
}
return sendImage(userName, mediaData, mediaName, contentType);
}
/**
* 发送图片消息(根据备注名)
*
* @param remarkName 备注名
* @param image 图片文件
* @return 返回数据
*/
public JSONObject sendImageToRemarkName(String remarkName, File image) {
ContentType contentType = FileUtil.getContentBody(image);
byte[] mediaData = FileUtil.getBytes(image);
return sendImageToRemarkName(remarkName, mediaData, image.getName(), contentType);
}
/**
* 发送图片消息(根据多种名称)
*
* @param userName 用户名(加密的)
* @param nickName 昵称
* @param remarkName 备注名
* @param mediaData 媒体文件数据
* @param mediaName 媒体文件名
* @param contentType 媒体文件类型
* @return 返回数据
*/
public JSONObject sendImage(String userName, String nickName, String remarkName
, byte[] mediaData, String mediaName, ContentType contentType) {
UserInfo userInfo;
if (StringUtil.isNotEmpty(userName)) {
return sendImage(userName, mediaData, mediaName, contentType);
} else if (StringUtil.isNotEmpty(nickName)) {
userInfo = getContactByNickName(false, nickName);
} else if (StringUtil.isNotEmpty(remarkName)) {
userInfo = getContactByRemarkName(false, remarkName);
} else {
String loginUserName = getLoginUserName(false);
return sendImage(loginUserName, mediaData, mediaName, contentType);
}
if (userInfo == null) {
return null;
}
userName = userInfo.getUserName();
if (StringUtil.isEmpty(userName)) {
return null;
}
return sendImage(userName, mediaData, mediaName, contentType);
}
/**
* 发送图片消息(根据多种名称)
*
* @param userName 用户名(加密的)
* @param nickName 昵称
* @param remarkName 备注名
* @param image 图片文件
* @return 返回数据
*/
public JSONObject sendImage(String userName, String nickName, String remarkName, File image) {
ContentType contentType = FileUtil.getContentBody(image);
byte[] mediaData = FileUtil.getBytes(image);
return sendImage(userName, nickName, remarkName, mediaData, image.getName(), contentType);
}
}

View File

@@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.hotlcc.wechat4j.enums.LoginTipEnum;
import com.hotlcc.wechat4j.model.BaseRequest;
import com.hotlcc.wechat4j.model.MediaMessage;
import com.hotlcc.wechat4j.model.WxMessage;
import com.hotlcc.wechat4j.util.PropertiesUtil;
import com.hotlcc.wechat4j.util.StringUtil;
@@ -26,10 +27,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.stringtemplate.v4.ST;
import javax.activation.MimetypesFileTypeMap;
import java.io.File;
import java.io.FileInputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -39,19 +39,29 @@ import java.util.regex.Pattern;
*
* @author Allen
*/
@SuppressWarnings("Duplicates")
@SuppressWarnings({"Duplicates", "unused"})
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+?)\";");
private static Pattern PATTERN_REDIRECT_URI_3 = Pattern.compile("http(s*)://wx(\\d*)\\.qq\\.com\\/");
private static Pattern PATTERN_REDIRECT_URI_3 = Pattern.compile("http(s*)://wx(\\d*)\\.qq\\.com/");
/**
* 上传媒体文件分片大小
*/
private static final int UPLOAD_MEDIA_FILE_CHUNK_SIZE = 524288;
/**
* 获取微信uuid
*
* @param httpClient http客户端
* @return 返回数据
*/
public JSONObject getWxUuid(HttpClient httpClient) {
try {
@@ -106,7 +116,9 @@ public class WebWeixinApi {
/**
* 获取二维码
*
* @param uuid
* @param httpClient http客户端
* @param uuid uuid
* @return 二维码图片字节数据
*/
public byte[] getQR(HttpClient httpClient,
String uuid) {
@@ -140,7 +152,10 @@ public class WebWeixinApi {
/**
* 获取跳转uri等待扫码认证
*
* @return
* @param httpClient http客户端
* @param tip 登录tip
* @param uuid uuid
* @return 返回数据
*/
public JSONObject getRedirectUri(HttpClient httpClient,
LoginTipEnum tip,
@@ -209,6 +224,10 @@ public class WebWeixinApi {
/**
* 获取登录认证码
* 此方法执行后其它web端微信、pc端都会下线
*
* @param httpClient http客户端
* @param redirectUri 调整uri
* @return 返回数据
*/
public JSONObject getLoginCode(HttpClient httpClient,
String redirectUri) {
@@ -229,9 +248,7 @@ public class WebWeixinApi {
HttpEntity entity = response.getEntity();
String res = EntityUtils.toString(entity, Consts.UTF_8);
JSONObject result = JSONObject.parseObject(XML.toJSONObject(res).toString()).getJSONObject("error");
return result;
return JSONObject.parseObject(XML.toJSONObject(res).toString()).getJSONObject("error");
} catch (Exception e) {
logger.error("获取登录认证码异常", e);
return null;
@@ -240,21 +257,25 @@ public class WebWeixinApi {
/**
* 退出登录
*
* @param httpClient http客户端
* @param urlVersion url版本号
* @param baseRequest BaseRequest
*/
public void logout(HttpClient httpClient,
String urlVersion,
BaseRequest BaseRequest) {
BaseRequest baseRequest) {
try {
List<NameValuePair> pairList = new ArrayList<>();
pairList.add(new BasicNameValuePair("sid", BaseRequest.getSid()));
pairList.add(new BasicNameValuePair("uin", BaseRequest.getUin()));
pairList.add(new BasicNameValuePair("sid", baseRequest.getSid()));
pairList.add(new BasicNameValuePair("uin", baseRequest.getUin()));
//分两步进行
for (int i = 0; i <= 1; i++) {
String url = new ST(PropertiesUtil.getProperty("webwx-url.logout_url"))
.add("urlVersion", urlVersion)
.add("type", i)
.add("skey", StringUtil.encodeURL(BaseRequest.getSkey(), Consts.UTF_8.name()))
.add("skey", StringUtil.encodeURL(baseRequest.getSkey(), Consts.UTF_8.name()))
.render();
HttpPost httpPost = new HttpPost(url);
@@ -272,12 +293,16 @@ public class WebWeixinApi {
/**
* push登录
*
* @param httpClient http客户端
* @param urlVersion url版本号
* @param wxuin uin
* @return 返回数据
*/
public JSONObject pushLogin(HttpClient httpClient,
String urlVersion,
String wxuin) {
try {
long millis = System.currentTimeMillis();
String url = new ST(PropertiesUtil.getProperty("webwx-url.pushlogin_url"))
.add("urlVersion", urlVersion)
.add("uin", wxuin)
@@ -304,15 +329,21 @@ public class WebWeixinApi {
/**
* 获取初始化数据
*
* @param httpClient http客户端
* @param urlVersion url版本号
* @param passticket passticket
* @param baseRequest BaseRequest
* @return 返回数据
*/
public JSONObject webWeixinInit(HttpClient httpClient,
String urlVersion,
String passticket,
BaseRequest BaseRequest) {
BaseRequest baseRequest) {
try {
String url = new ST(PropertiesUtil.getProperty("webwx-url.webwxinit_url"))
.add("urlVersion", urlVersion)
.add("pass_ticket", passticket)
.add("pass_ticket", StringUtil.encodeURL(passticket, Consts.UTF_8.name()))
.add("r", System.currentTimeMillis() / 1252L)
.render();
@@ -320,7 +351,7 @@ public class WebWeixinApi {
httpPost.setHeader("Content-type", ContentType.APPLICATION_JSON.toString());
JSONObject paramJson = new JSONObject();
paramJson.put("BaseRequest", BaseRequest);
paramJson.put("BaseRequest", baseRequest);
HttpEntity paramEntity = new StringEntity(paramJson.toJSONString(), Consts.UTF_8);
httpPost.setEntity(paramEntity);
@@ -343,24 +374,29 @@ public class WebWeixinApi {
/**
* 开启消息状态通知
*
* @return
* @param httpClient http客户端
* @param urlVersion url版本号
* @param passticket passticket
* @param baseRequest BaseRequest
* @param loginUserName 当前登录账号用户名
* @return 返回数据
*/
public JSONObject statusNotify(HttpClient httpClient,
String urlVersion,
String passticket,
BaseRequest BaseRequest,
BaseRequest baseRequest,
String loginUserName) {
try {
String url = new ST(PropertiesUtil.getProperty("webwx-url.statusnotify_url"))
.add("urlVersion", urlVersion)
.add("pass_ticket", passticket)
.add("pass_ticket", StringUtil.encodeURL(passticket, Consts.UTF_8.name()))
.render();
HttpPost httpPost = new HttpPost(url);
httpPost.setHeader("Content-type", ContentType.APPLICATION_JSON.toString());
JSONObject paramJson = new JSONObject();
paramJson.put("BaseRequest", BaseRequest);
paramJson.put("BaseRequest", baseRequest);
paramJson.put("ClientMsgId", System.currentTimeMillis());
paramJson.put("Code", 3);
paramJson.put("FromUserName", loginUserName);
@@ -386,21 +422,27 @@ public class WebWeixinApi {
/**
* 服务端状态同步心跳
*
* @param httpClient http客户端
* @param urlVersion url版本号
* @param baseRequest BaseRequest
* @param syncKeyList SyncKeyList
* @return 返回数据
*/
public JSONObject syncCheck(HttpClient httpClient,
String urlVersion,
BaseRequest BaseRequest,
JSONArray SyncKeyList) {
BaseRequest baseRequest,
JSONArray syncKeyList) {
try {
long millis = System.currentTimeMillis();
String url = new ST(PropertiesUtil.getProperty("webwx-url.synccheck_url"))
.add("urlVersion", urlVersion)
.add("r", millis)
.add("skey", StringUtil.encodeURL(BaseRequest.getSkey(), Consts.UTF_8.name()))
.add("sid", BaseRequest.getSid())
.add("uin", BaseRequest.getUin())
.add("skey", StringUtil.encodeURL(baseRequest.getSkey(), Consts.UTF_8.name()))
.add("sid", baseRequest.getSid())
.add("uin", baseRequest.getUin())
.add("deviceid", WechatUtil.createDeviceID())
.add("synckey", StringUtil.encodeURL(WechatUtil.syncKeyListToString(SyncKeyList), Consts.UTF_8.name()))
.add("synckey", StringUtil.encodeURL(WechatUtil.syncKeyListToString(syncKeyList), Consts.UTF_8.name()))
.add("_", millis)
.render();
@@ -439,6 +481,12 @@ public class WebWeixinApi {
/**
* 获取全部联系人列表
*
* @param httpClient http客户端
* @param urlVersion url版本号
* @param passticket passticket
* @param skey skey
* @return 返回数据
*/
public JSONObject getContact(HttpClient httpClient,
String urlVersion,
@@ -472,11 +520,18 @@ public class WebWeixinApi {
/**
* 批量获取指定用户信息
*
* @param httpClient http客户端
* @param urlVersion url版本号
* @param passticket passticket
* @param baseRequest BaseRequest
* @param batchContactList 联系人列表
* @return 返回数据
*/
public JSONObject batchGetContact(HttpClient httpClient,
String urlVersion,
String passticket,
BaseRequest BaseRequest,
BaseRequest baseRequest,
JSONArray batchContactList) {
try {
String url = new ST(PropertiesUtil.getProperty("webwx-url.batchgetcontact_url"))
@@ -489,7 +544,7 @@ public class WebWeixinApi {
httpPost.setHeader("Content-type", ContentType.APPLICATION_JSON.toString());
JSONObject paramJson = new JSONObject();
paramJson.put("BaseRequest", BaseRequest);
paramJson.put("BaseRequest", baseRequest);
paramJson.put("Count", batchContactList.size());
paramJson.put("List", batchContactList);
HttpEntity paramEntity = new StringEntity(paramJson.toJSONString(), Consts.UTF_8);
@@ -513,26 +568,33 @@ public class WebWeixinApi {
/**
* 从服务端同步新数据
*
* @param httpClient http客户端
* @param urlVersion url版本号
* @param passticket passticket
* @param baseRequest BaseRequest
* @param syncKey syncKey
* @return 返回数据
*/
public JSONObject webWxSync(HttpClient httpClient,
String urlVersion,
String passticket,
BaseRequest BaseRequest,
JSONObject SyncKey) {
BaseRequest baseRequest,
JSONObject syncKey) {
try {
String url = new ST(PropertiesUtil.getProperty("webwx-url.webwxsync_url"))
.add("urlVersion", urlVersion)
.add("skey", BaseRequest.getSkey())
.add("sid", BaseRequest.getSid())
.add("pass_ticket", passticket)
.add("skey", baseRequest.getSkey())
.add("sid", baseRequest.getSid())
.add("pass_ticket", StringUtil.encodeURL(passticket, Consts.UTF_8.name()))
.render();
HttpPost httpPost = new HttpPost(url);
httpPost.setHeader("Content-type", ContentType.APPLICATION_JSON.toString());
JSONObject paramJson = new JSONObject();
paramJson.put("BaseRequest", BaseRequest);
paramJson.put("SyncKey", SyncKey);
paramJson.put("BaseRequest", baseRequest);
paramJson.put("SyncKey", syncKey);
HttpEntity paramEntity = new StringEntity(paramJson.toJSONString(), Consts.UTF_8);
httpPost.setEntity(paramEntity);
@@ -554,6 +616,13 @@ public class WebWeixinApi {
/**
* 发送消息
*
* @param httpClient http客户端
* @param urlVersion url版本号
* @param passticket passticket
* @param baseRequest BaseRequest
* @param message 消息
* @return 返回数据
*/
public JSONObject sendMsg(HttpClient httpClient,
String urlVersion,
@@ -563,7 +632,7 @@ public class WebWeixinApi {
try {
String url = new ST(PropertiesUtil.getProperty("webwx-url.webwxsendmsg_url"))
.add("urlVersion", urlVersion)
.add("pass_ticket", passticket)
.add("pass_ticket", StringUtil.encodeURL(passticket, Consts.UTF_8.name()))
.render();
HttpPost httpPost = new HttpPost(url);
@@ -585,9 +654,7 @@ public class WebWeixinApi {
HttpEntity entity = response.getEntity();
String res = EntityUtils.toString(entity, Consts.UTF_8);
JSONObject result = JSONObject.parseObject(res);
return result;
return JSONObject.parseObject(res);
} catch (Exception e) {
logger.error("发送消息异常", e);
return null;
@@ -595,79 +662,19 @@ public class WebWeixinApi {
}
/**
* 上传媒体文件
* 上传媒体文件(支持大文件自动分片上传)
*
* @return
*/
public JSONObject uploadMedia(HttpClient httpClient,
String urlVersion,
String passticket,
BaseRequest BaseRequest,
String FromUserName,
String ToUserName,
String dataTicket,
byte[] data,
String fileName,
ContentType contentType) {
try {
String url = new ST(PropertiesUtil.getProperty("webwx-url.uploadmedia_url"))
.add("urlVersion", urlVersion)
.render();
HttpPost httpPost = new HttpPost(url);
httpPost.setHeader("Content-type", ContentType.MULTIPART_FORM_DATA.toString());
long millis = System.currentTimeMillis();
JSONObject uploadmediarequest = new JSONObject();
uploadmediarequest.put("UploadType", 2);
uploadmediarequest.put("BaseRequest", BaseRequest);
uploadmediarequest.put("ClientMediaId", millis);
uploadmediarequest.put("TotalLen", data.length);
uploadmediarequest.put("StartPos", 0);
uploadmediarequest.put("DataLen", data.length);
uploadmediarequest.put("MediaType", 4);
uploadmediarequest.put("FromUserName", FromUserName);
uploadmediarequest.put("ToUserName", ToUserName);
uploadmediarequest.put("FileMd5", DigestUtils.md5(data));
HttpEntity paramEntity = MultipartEntityBuilder.create()
.setMode(HttpMultipartMode.BROWSER_COMPATIBLE)
.addTextBody("id", StringUtil.getUuid(), ContentType.TEXT_PLAIN)
.addTextBody("name", fileName, ContentType.TEXT_PLAIN)
.addTextBody("type", contentType.getMimeType(), ContentType.TEXT_PLAIN)
.addTextBody("lastModifieDate", millis + "", ContentType.TEXT_PLAIN)
.addTextBody("size", data.length + "", ContentType.TEXT_PLAIN)
.addTextBody("mediatype", WechatUtil.getMediatype(contentType.getMimeType()), ContentType.TEXT_PLAIN)
.addTextBody("uploadmediarequest", uploadmediarequest.toJSONString(), ContentType.TEXT_PLAIN)
.addTextBody("webwx_data_ticket", dataTicket, ContentType.TEXT_PLAIN)
.addTextBody("pass_ticket", passticket, ContentType.TEXT_PLAIN)
.addBinaryBody("filename", data, contentType, fileName)
.build();
httpPost.setEntity(paramEntity);
HttpResponse response = httpClient.execute(httpPost);
int statusCode = response.getStatusLine().getStatusCode();
if (HttpStatus.SC_OK != statusCode) {
throw new RuntimeException("响应失败(" + statusCode + ")");
}
HttpEntity entity = response.getEntity();
String res = EntityUtils.toString(entity, Consts.UTF_8);
JSONObject result = JSONObject.parseObject(res);
return result;
} catch (Exception e) {
logger.error("上传媒体文件异常", e);
return null;
}
}
/**
* 上传媒体文件
*
* @return
* @param httpClient http客户端
* @param urlVersion url版本号
* @param passticket passticket
* @param baseRequest BaseRequest
* @param fromUserName 发送者用户名
* @param toUserName 接受者用户名
* @param dataTicket dataTicket
* @param mediaData 媒体文件二进制数据
* @param mediaName 媒体文件名称
* @param contentType 媒体文件类型
* @return 返回数据
*/
public JSONObject uploadMedia(HttpClient httpClient,
String urlVersion,
@@ -676,44 +683,120 @@ public class WebWeixinApi {
String fromUserName,
String toUserName,
String dataTicket,
File file) {
byte[] mediaData,
String mediaName,
ContentType contentType) {
try {
String url = new ST(PropertiesUtil.getProperty("webwx-url.uploadmedia_url"))
.add("urlVersion", urlVersion)
.render();
long millis = System.currentTimeMillis();
int mediaLength = mediaData.length;
// String mimeType = contentType.getMimeType();
HttpPost httpPost = new HttpPost(url);
httpPost.setHeader("Content-type", ContentType.MULTIPART_FORM_DATA.toString());
long millis = System.currentTimeMillis();
String contentTypeStr = new MimetypesFileTypeMap().getContentType(file);
ContentType contentType = ContentType.parse(contentTypeStr);
JSONObject uploadmediarequest = new JSONObject();
uploadmediarequest.put("UploadType", 2);
uploadmediarequest.put("BaseRequest", baseRequest);
uploadmediarequest.put("ClientMediaId", millis);
uploadmediarequest.put("TotalLen", file.length());
uploadmediarequest.put("TotalLen", mediaLength);
uploadmediarequest.put("StartPos", 0);
uploadmediarequest.put("DataLen", file.length());
uploadmediarequest.put("DataLen", mediaLength);
uploadmediarequest.put("MediaType", 4);
uploadmediarequest.put("FromUserName", fromUserName);
uploadmediarequest.put("ToUserName", toUserName);
uploadmediarequest.put("FileMd5", DigestUtils.md5(new FileInputStream(file)));
uploadmediarequest.put("FileMd5", DigestUtils.md5Hex(mediaData));
HttpEntity paramEntity = MultipartEntityBuilder.create()
.setMode(HttpMultipartMode.BROWSER_COMPATIBLE)
.addTextBody("id", StringUtil.getUuid(), ContentType.TEXT_PLAIN)
.addTextBody("name", file.getName(), ContentType.TEXT_PLAIN)
.addTextBody("type", contentTypeStr, ContentType.TEXT_PLAIN)
.addTextBody("lastModifieDate", millis + "", ContentType.TEXT_PLAIN)
.addTextBody("size", file.length() + "", ContentType.TEXT_PLAIN)
.addTextBody("mediatype", WechatUtil.getMediatype(contentType.getMimeType()), ContentType.TEXT_PLAIN)
.addTextBody("uploadmediarequest", uploadmediarequest.toJSONString(), ContentType.TEXT_PLAIN)
.addTextBody("webwx_data_ticket", dataTicket, ContentType.TEXT_PLAIN)
.addTextBody("pass_ticket", passticket, ContentType.TEXT_PLAIN)
.addBinaryBody("filename", file, contentType, file.getName())
.build();
// 分片数量
int chunks = new BigDecimal(mediaLength).divide(new BigDecimal(UPLOAD_MEDIA_FILE_CHUNK_SIZE), 0, BigDecimal.ROUND_UP).intValue();
JSONObject result = null;
for (int chunk = 0; chunk < chunks; chunk++) {
int from = chunk * UPLOAD_MEDIA_FILE_CHUNK_SIZE;
int to = (chunk + 1) * UPLOAD_MEDIA_FILE_CHUNK_SIZE;
to = Math.min(to, mediaLength);
byte[] temp = Arrays.copyOfRange(mediaData, from, to);
HttpEntity paramEntity = MultipartEntityBuilder.create()
.setMode(HttpMultipartMode.BROWSER_COMPATIBLE)
// .addTextBody("id", StringUtil.getUuid(), ContentType.TEXT_PLAIN)
// .addTextBody("name", mediaName, ContentType.TEXT_PLAIN)
// .addTextBody("type", mimeType, ContentType.TEXT_PLAIN)
// .addTextBody("lastModifieDate", String.valueOf(millis), ContentType.TEXT_PLAIN)
// .addTextBody("size", String.valueOf(mediaLength), ContentType.TEXT_PLAIN)
.addTextBody("chunks", String.valueOf(chunks))
.addTextBody("chunk", String.valueOf(chunk))
// .addTextBody("mediatype", WechatUtil.getMediatype(mimeType), ContentType.TEXT_PLAIN)
.addTextBody("uploadmediarequest", uploadmediarequest.toJSONString(), ContentType.TEXT_PLAIN)
.addTextBody("webwx_data_ticket", dataTicket, ContentType.TEXT_PLAIN)
.addTextBody("pass_ticket", passticket, ContentType.TEXT_PLAIN)
.addBinaryBody("filename", temp, contentType, mediaName)
.build();
httpPost.setEntity(paramEntity);
HttpResponse response = httpClient.execute(httpPost);
int statusCode = response.getStatusLine().getStatusCode();
if (HttpStatus.SC_OK != statusCode) {
throw new RuntimeException("响应失败(" + statusCode + ")");
}
HttpEntity entity = response.getEntity();
String res = EntityUtils.toString(entity, Consts.UTF_8);
result = JSONObject.parseObject(res);
if (result == null) {
break;
}
JSONObject baseResponse = result.getJSONObject("BaseResponse");
if (baseResponse == null) {
break;
}
int ret = baseResponse.getIntValue("Ret");
if (ret != 0) {
break;
}
}
return result;
} catch (Exception e) {
logger.error("上传媒体文件异常", e);
return null;
}
}
/**
* 发送图片消息
*
* @param httpClient http客户端
* @param urlVersion url版本号
* @param passticket passticket
* @param baseRequest BaseRequest
* @param message 消息
* @return 返回数据
*/
public JSONObject sendImageMsg(HttpClient httpClient,
String urlVersion,
String passticket,
BaseRequest baseRequest,
MediaMessage message) {
try {
String url = new ST(PropertiesUtil.getProperty("webwx-url.webwxsendmsgimg_url"))
.add("urlVersion", urlVersion)
.add("pass_ticket", StringUtil.encodeURL(passticket, Consts.UTF_8.name()))
.render();
HttpPost httpPost = new HttpPost(url);
httpPost.setHeader("Content-type", ContentType.APPLICATION_JSON.toString());
JSONObject paramJson = new JSONObject();
paramJson.put("BaseRequest", baseRequest);
paramJson.put("Msg", message);
paramJson.put("Scene", 0);
HttpEntity paramEntity = new StringEntity(paramJson.toJSONString(), Consts.UTF_8);
httpPost.setEntity(paramEntity);
HttpResponse response = httpClient.execute(httpPost);
@@ -725,11 +808,9 @@ public class WebWeixinApi {
HttpEntity entity = response.getEntity();
String res = EntityUtils.toString(entity, Consts.UTF_8);
JSONObject result = JSONObject.parseObject(res);
return result;
return JSONObject.parseObject(res);
} catch (Exception e) {
logger.error("上传媒体文件异常", e);
logger.error("发送图片消息异常", e);
return null;
}
}

View File

@@ -2,11 +2,13 @@ package com.hotlcc.wechat4j.enums;
/**
* 微信退出类型
*
* @author Allen
*/
public enum ExitTypeEnum {
ERROR_EXIT("错误导致退出"),
LOCAL_EXIT("次手动退出"),
REMOTE_EXIT("远程操作退出");
ERROR_EXIT("错误退出"),
LOCAL_EXIT("退出"),
REMOTE_EXIT("远程退出");
private String desc;

View File

@@ -3,8 +3,9 @@ package com.hotlcc.wechat4j.enums;
/**
* 等待确认登录的tip
*
* @author https://gitee.com/hotlcc
* @author Allen
*/
@SuppressWarnings("unused")
public enum LoginTipEnum {
TIP_0(0, "扫码登录"),
TIP_1(1, "确认登录");

View File

@@ -3,8 +3,9 @@ package com.hotlcc.wechat4j.enums;
/**
* 消息类型enum
*
* @author https://gitee.com/hotlcc
* @author Allen
*/
@SuppressWarnings({"unused"})
public enum MsgTypeEnum {
TEXT_MSG(1, "文本消息"),
IMAGE_MSG(3, "图片消息"),

View File

@@ -3,7 +3,7 @@ package com.hotlcc.wechat4j.enums;
/**
* 操作系统enum
*
* @author https://gitee.com/hotlcc
* @author Allen
*/
public enum OperatingSystemEnum {
DARWIN("darwin"),

View File

@@ -1,5 +1,10 @@
package com.hotlcc.wechat4j.enums;
/**
* Ret代码
*
* @author Allen
*/
public enum RetcodeEnum {
RECODE_0(0, "正常"),
RECODE_1100(1100, "失败/登出微信"),

View File

@@ -1,5 +1,10 @@
package com.hotlcc.wechat4j.enums;
/**
* Selector代码
*
* @author Allen
*/
public enum SelectorEnum {
SELECTOR_0(0, "正常"),
SELECTOR_2(2, "有新消息"),
@@ -15,10 +20,6 @@ public enum SelectorEnum {
this.desc = desc;
}
public int getCode() {
return code;
}
public static SelectorEnum valueOf(int code) {
SelectorEnum[] es = values();
for (SelectorEnum e : es) {

View File

@@ -5,35 +5,37 @@ import com.hotlcc.wechat4j.enums.ExitTypeEnum;
/**
* 退出事件处理器
*
* @author Allen
*/
public interface ExitEventHandler {
/**
* 针对所有类型的退出事件
*
* @param wechat
* @param type
* @param t
* @param wechat 微信客户端
* @param type 退出类型
* @param t 异常
*/
void handleAllType(Wechat wechat, ExitTypeEnum type, Throwable t);
/**
* 针对错误导致的退出事件
*
* @param wechat
* @param wechat 微信客户端
*/
void handleErrorExitEvent(Wechat wechat);
/**
* 针对远程人为导致的退出事件
*
* @param wechat
* @param wechat 微信客户端
*/
void handleRemoteExitEvent(Wechat wechat);
/**
* 针对本地任务导致的退出事件
*
* @param wechat
* @param wechat 微信客户端
*/
void handleLocalExitEvent(Wechat wechat);
}

View File

@@ -4,14 +4,16 @@ import com.hotlcc.wechat4j.Wechat;
import com.hotlcc.wechat4j.model.ReceivedMsg;
/**
* 接收消息处理器
* 接收消息的消息处理器
*
* @author Allen
*/
public interface ReceivedMsgHandler {
/**
* 处理所有类型的消息
*
* @param wechat
* @param msg
* @param wechat 微信客户端
* @param msg 接收的消息
*/
void handleAllType(Wechat wechat, ReceivedMsg msg);
}

View File

@@ -1,15 +1,26 @@
package com.hotlcc.wechat4j.model;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* AppInfo
*
* @author Allen
*/
@Getter
public final class AppInfo {
@Setter
public final class AppInfo implements Serializable {
private static final long serialVersionUID = 1L;
private AppInfo() {
}
@@ -22,10 +33,7 @@ public final class AppInfo {
if (info == null) {
return null;
}
AppInfo appInfo = new AppInfo();
appInfo.type = info.getInteger("Type");
appInfo.appID = info.getString("AppID");
return appInfo;
return JSON.toJavaObject(info, AppInfo.class);
}
public static List<AppInfo> valueOf(JSONArray infos) {

View File

@@ -5,12 +5,18 @@ import com.hotlcc.wechat4j.util.WechatUtil;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
/**
* 基本请求模型
*
* @author Allen
*/
@Getter
@Setter
public class BaseRequest {
public class BaseRequest implements Serializable {
private static final long serialVersionUID = 1L;
public BaseRequest() {
}

View File

@@ -0,0 +1,17 @@
package com.hotlcc.wechat4j.model;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Getter;
import lombok.Setter;
/**
* 媒体消息
*
* @author Allen
*/
@Getter
@Setter
public class MediaMessage extends WxMessage {
@JSONField(name = "MediaId")
private String mediaId;
}

View File

@@ -1,15 +1,21 @@
package com.hotlcc.wechat4j.model;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@Getter
public final class ReceivedMsg {
@Setter
public final class ReceivedMsg implements Serializable {
private static final long serialVersionUID = 1L;
private ReceivedMsg() {
}
@@ -75,37 +81,7 @@ public final class ReceivedMsg {
return null;
}
ReceivedMsg receivedMsg = new ReceivedMsg();
receivedMsg.subMsgType = msg.getInteger("SubMsgType");
receivedMsg.voiceLength = msg.getLong("VoiceLength");
receivedMsg.fileName = msg.getString("FileName");
receivedMsg.imgHeight = msg.getLong("ImgHeight");
receivedMsg.toUserName = msg.getString("ToUserName");
receivedMsg.hasProductId = msg.getLong("HasProductId");
receivedMsg.imgStatus = msg.getInteger("ImgStatus");
receivedMsg.url = msg.getString("Url");
receivedMsg.imgWidth = msg.getInteger("ImgWidth");
receivedMsg.forwardFlag = msg.getInteger("ForwardFlag");
receivedMsg.status = msg.getInteger("Status");
receivedMsg.ticket = msg.getString("Ticket");
receivedMsg.recommendInfo = com.hotlcc.wechat4j.model.RecommendInfo.valueOf(msg.getJSONObject("RecommendInfo"));
receivedMsg.createTime = msg.getLong("CreateTime");
receivedMsg.newMsgId = msg.getLong("NewMsgId");
receivedMsg.msgType = msg.getInteger("MsgType");
receivedMsg.encryFileName = msg.getString("EncryFileName");
receivedMsg.msgId = msg.getString("MsgId");
receivedMsg.statusNotifyCode = msg.getInteger("StatusNotifyCode");
receivedMsg.appInfo = com.hotlcc.wechat4j.model.AppInfo.valueOf(msg.getJSONObject("AppInfo"));
receivedMsg.playLength = msg.getLong("PlayLength");
receivedMsg.mediaId = msg.getString("MediaId");
receivedMsg.content = msg.getString("Content");
receivedMsg.statusNotifyUserName = msg.getString("StatusNotifyUserName");
receivedMsg.fromUserName = msg.getString("FromUserName");
receivedMsg.oriContent = msg.getString("OriContent");
receivedMsg.fileSize = msg.getString("FileSize");
return receivedMsg;
return JSON.toJavaObject(msg, ReceivedMsg.class);
}
public static List<ReceivedMsg> valueOf(JSONArray msgs) {

View File

@@ -1,15 +1,26 @@
package com.hotlcc.wechat4j.model;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* RecommendInfo
*
* @author Allen
*/
@Getter
public final class RecommendInfo {
@Setter
public final class RecommendInfo implements Serializable {
private static final long serialVersionUID = 1L;
private RecommendInfo() {
}
@@ -47,24 +58,7 @@ public final class RecommendInfo {
return null;
}
RecommendInfo recommendInfo = new RecommendInfo();
recommendInfo.ticket = info.getString("Ticket");
recommendInfo.userName = info.getString("UserName");
recommendInfo.sex = info.getInteger("Sex");
recommendInfo.attrStatus = info.getInteger("AttrStatus");
recommendInfo.city = info.getString("City");
recommendInfo.nickName = info.getString("NickName");
recommendInfo.scene = info.getInteger("Scene");
recommendInfo.province = info.getString("Province");
recommendInfo.content = info.getString("Content");
recommendInfo.alias = info.getString("Alias");
recommendInfo.signature = info.getString("Signature");
recommendInfo.opCode = info.getInteger("OpCode");
recommendInfo.qqNum = info.getLong("QQNum");
recommendInfo.verifyFlag = info.getInteger("VerifyFlag");
return recommendInfo;
return JSON.toJavaObject(info, RecommendInfo.class);
}
public static List<RecommendInfo> valueOf(JSONArray infos) {

View File

@@ -1,18 +1,26 @@
package com.hotlcc.wechat4j.model;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* 微信用户信息
*
* @author Allen
*/
@Getter
public final class UserInfo {
@Setter
public final class UserInfo implements Serializable {
private static final long serialVersionUID = 1L;
private UserInfo() {
}
@@ -84,41 +92,7 @@ public final class UserInfo {
return null;
}
UserInfo userInfo = new UserInfo();
userInfo.uin = info.getLong("Uin");
userInfo.nickName = info.getString("NickName");
userInfo.headImgUrl = info.getString("HeadImgUrl");
userInfo.contactFlag = info.getInteger("ContactFlag");
userInfo.memberCount = info.getInteger("MemberCount");
userInfo.memberList = valueOf(info.getJSONArray("MemberList"));
userInfo.remarkName = info.getString("RemarkName");
userInfo.hideInputBarFlag = info.getInteger("HideInputBarFlag");
userInfo.sex = info.getInteger("Sex");
userInfo.signature = info.getString("Signature");
userInfo.verifyFlag = info.getInteger("VerifyFlag");
userInfo.ownerUin = info.getLong("OwnerUin");
userInfo.pyInitial = info.getString("PYInitial");
userInfo.pyQuanPin = info.getString("PYQuanPin");
userInfo.remarkPYInitial = info.getString("RemarkPYInitial");
userInfo.remarkPYQuanPin = info.getString("RemarkPYQuanPin");
userInfo.starFriend = info.getInteger("StarFriend");
userInfo.appAccountFlag = info.getInteger("AppAccountFlag");
userInfo.statues = info.getInteger("Statues");
userInfo.attrStatus = info.getInteger("AttrStatus");
userInfo.province = info.getString("Province");
userInfo.city = info.getString("City");
userInfo.alias = info.getString("Alias");
userInfo.snsFlag = info.getInteger("SnsFlag");
userInfo.uniFriend = info.getInteger("UniFriend");
userInfo.displayName = info.getString("DisplayName");
userInfo.chatRoomId = info.getLong("ChatRoomId");
userInfo.keyWord = info.getString("KeyWord");
userInfo.encryChatRoomId = info.getString("EncryChatRoomId");
userInfo.isOwner = info.getInteger("IsOwner");
userInfo.userName = info.getString("UserName");
return userInfo;
return JSON.toJavaObject(info, UserInfo.class);
}
public static List<UserInfo> valueOf(JSONArray infos) {

View File

@@ -4,12 +4,18 @@ import com.alibaba.fastjson.annotation.JSONField;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
/**
* 要发送的消息
*
* @author Allen
*/
@Getter
@Setter
public class WxMessage {
public class WxMessage implements Serializable {
private static final long serialVersionUID = 1L;
@JSONField(name = "ClientMsgId")
private String clientMsgId;
@JSONField(name = "Content")

View File

@@ -3,12 +3,23 @@ package com.hotlcc.wechat4j.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 通用工具类
*
* @author Allen
*/
public final class CommonUtil {
private static Logger logger = LoggerFactory.getLogger(CommonUtil.class);
private CommonUtil() {
}
/**
* 睡眠线程
*
* @param millis 时间
* @param nanos nanos
*/
public static void threadSleep(long millis, int nanos) {
try {
Thread.sleep(millis, nanos);
@@ -17,6 +28,11 @@ public final class CommonUtil {
}
}
/**
* 睡眠线程
*
* @param millis 时间
*/
public static void threadSleep(long millis) {
try {
Thread.sleep(millis);

View File

@@ -0,0 +1,84 @@
package com.hotlcc.wechat4j.util;
import org.apache.http.entity.ContentType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.activation.MimetypesFileTypeMap;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
/**
* 文件工具类
*
* @author Allen
*/
public final class FileUtil {
private static Logger logger = LoggerFactory.getLogger(FileUtil.class);
private FileUtil() {
}
/**
* 从文件中获取二进制数据
*
* @param file 文件
* @return 二进制数据
*/
public static byte[] getBytes(File file) {
if (file == null) {
throw new IllegalArgumentException("参数file不能为null");
}
FileInputStream fis = null;
ByteArrayOutputStream baos = null;
try {
fis = new FileInputStream(file);
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
baos.flush();
baos.close();
fis.close();
return baos.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (baos != null) {
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 获取文件的ContentType
*
* @param file 文件
* @return ContentType
*/
public static ContentType getContentBody(File file) {
String mimeType = new MimetypesFileTypeMap().getContentType(file);
ContentType contentType = ContentType.parse(mimeType);
return contentType;
}
}

View File

@@ -7,6 +7,11 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* Properties工具类
*
* @author Allen
*/
public final class PropertiesUtil {
private static Logger logger = LoggerFactory.getLogger(PropertiesUtil.class);

View File

@@ -16,15 +16,13 @@ import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* 二维码工具类
*
* @author Allen
*/
@SuppressWarnings({"unused", "WeakerAccess"})
public final class QRCodeUtil {
private static Logger logger = LoggerFactory.getLogger(QRCodeUtil.class);
@@ -34,7 +32,7 @@ public final class QRCodeUtil {
/**
* 从BitMatrix中得到boolean矩阵不去除周围空白部分
*
* @return
* @return 得到的boolean矩阵
*/
private static boolean[][] toBoolMatrix(BitMatrix matrix) {
return toBoolMatrix(matrix, false);
@@ -43,9 +41,9 @@ public final class QRCodeUtil {
/**
* 从BitMatrix中得到boolean矩阵
*
* @param matrix
* @param matrix BitMatrix
* @param noMargin 是否去除周围空白
* @return
* @return 得到的boolean矩阵
*/
private static boolean[][] toBoolMatrix(BitMatrix matrix, boolean noMargin) {
int width = matrix.getWidth();
@@ -60,8 +58,8 @@ public final class QRCodeUtil {
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++) {
for (int h = top, i = 0; h < height - bottom; h++, i++) {
for (int w = left, j = 0; w < width - right; w++, j++) {
m[i][j] = matrix.get(w, h);
}
}
@@ -71,8 +69,8 @@ public final class QRCodeUtil {
/**
* 将矩阵逆时针转90度
*
* @param matrix
* @return
* @param matrix 旋转前的矩阵
* @return 旋转后的矩阵
*/
private static boolean[][] reverseMatrix(boolean[][] matrix) {
if (matrix == null) {
@@ -95,15 +93,14 @@ public final class QRCodeUtil {
/**
* 从boolMatrix左上角判断二维码定位标记的大小
*
* @param boolMatrix
* @return
* @param boolMatrix bool矩阵
* @return 定位标记大小
*/
private static int getBitCharSize(boolean[][] boolMatrix) {
int a = 0, b = 0;
out:
for (int i = 0, len = boolMatrix.length; i < len; i++) {
for (boolean[] boolArr : boolMatrix) {
boolean find = false;
boolean[] boolArr = boolMatrix[i];
for (int i2 = 0, len2 = boolArr.length; i2 < len2; i2++) {
if (!find && boolArr[i2]) {
find = true;
@@ -122,59 +119,62 @@ public final class QRCodeUtil {
/**
* 从boolMatrix判断bit-char占位比
*
* @param boolMatrix
* @return
* @param boolMatrix bool矩阵
* @return 占位比
*/
private static int getBitCharRatio(boolean[][] boolMatrix) {
int[] size = new int[4];
int len = 4;
// 找出四个角的占位数
int[] size = new int[len];
size[0] = getBitCharSize(boolMatrix);
for (int i = 1; i < 4; i++) {
for (int i = 1; i < len; i++) {
boolMatrix = reverseMatrix(boolMatrix);
size[i] = getBitCharSize(boolMatrix);
}
Map<Integer, Integer> 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);
// 统计每个占位数出现的次数
int[] num = new int[len];
for (int i = 0; i < len; i++) {
int n = 0;
for (int s : size) {
if (s == size[i]) {
n++;
}
}
num[i] = n;
}
Set<Map.Entry<Integer, Integer>> entrySet = map.entrySet();
Integer k = null, v = null;
int flag = 0;
for (Map.Entry<Integer, Integer> entry : entrySet) {
if (flag++ == 0) {
k = entry.getKey();
v = entry.getValue();
continue;
}
if (entry.getValue() > v) {
k = entry.getKey();
v = entry.getValue();
// 找出最多的次数
int maxNum = num[0];
for (int i = 1; i < len; i++) {
maxNum = Math.max(maxNum, num[i]);
}
// 找出出现次数最多的占位数
int s = 0;
for (int i = 0; i < len; i++) {
if (num[i] == maxNum) {
s = size[i];
}
}
return k.intValue() / 7;
return s / 7;
}
/**
* 将二维码图片转为字符矩阵
*
* @param image
* @return
* @param image 二维码图片
* @param onStr 实体字符串
* @param offStr 空白字符串
* @return 字符矩阵
*/
public static String toCharMatrix(BufferedImage image, String onStr, String offStr) {
LuminanceSource source = new BufferedImageLuminanceSource(image);
Binarizer binarizer = new HybridBinarizer(source);
BitMatrix matrix = null;
BitMatrix matrix;
try {
matrix = binarizer.getBlackMatrix();
boolean[][] boolMatrix = toBoolMatrix(matrix, true);
int ratio = getBitCharRatio(boolMatrix);
StringBuffer sb = new StringBuffer();
StringBuilder sb = new StringBuilder();
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) {
@@ -192,8 +192,8 @@ public final class QRCodeUtil {
/**
* 将二维码图片转为字符矩阵
*
* @param image
* @return
* @param image 二维码图片
* @return 字符矩阵
*/
public static String toCharMatrix(BufferedImage image) {
return toCharMatrix(image, " ", "██");
@@ -202,8 +202,10 @@ public final class QRCodeUtil {
/**
* 将二维码图片转为字符矩阵
*
* @param data
* @return
* @param data 二维码图片的字节数据
* @param onStr 实体字符串
* @param offStr 空白字符串
* @return 字符矩阵
*/
public static String toCharMatrix(byte[] data, String onStr, String offStr) {
ByteArrayInputStream bais = null;
@@ -228,8 +230,8 @@ public final class QRCodeUtil {
/**
* 将二维码图片转为字符矩阵
*
* @param data
* @return
* @param data 二维码图片的字节数据
* @return 字符矩阵
*/
public static String toCharMatrix(byte[] data) {
return toCharMatrix(data, " ", "██");
@@ -238,8 +240,8 @@ public final class QRCodeUtil {
/**
* 将二维码图片数据写入到临时文件
*
* @param data
* @return
* @param data 二维码图片的字节数据
* @return 字符矩阵
*/
public static File writeToTempFile(byte[] data) {
FileOutputStream fos = null;
@@ -266,12 +268,12 @@ public final class QRCodeUtil {
/**
* 打开二维码图片
*
* @param data
* @param data 二维码图片的字节数据
*/
public static void openQRCodeImage(byte[] data) {
OperatingSystemEnum os = OperatingSystemEnum.currentOperatingSystem();
Runtime runtime = null;
File tmp = null;
Runtime runtime;
File tmp;
switch (os) {
case WINDOWS:
runtime = Runtime.getRuntime();

View File

@@ -5,6 +5,11 @@ import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.UUID;
/**
* 字符串工具类
*
* @author Allen
*/
public final class StringUtil {
private StringUtil() {
}

View File

@@ -4,6 +4,11 @@ import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.RandomStringUtils;
/**
* 微信工具类
*
* @author Allen
*/
public final class WechatUtil {
private WechatUtil() {
}
@@ -14,7 +19,7 @@ public final class WechatUtil {
/**
* 创建一个设备ID
*
* @return
* @return 设备ID
*/
public static String createDeviceID() {
return "e" + RandomStringUtils.random(15, STRING_CHARS_1);
@@ -23,7 +28,7 @@ public final class WechatUtil {
/**
* 创建一个消息ID
*
* @return
* @return 消息ID
*/
public static String createMsgId() {
return System.currentTimeMillis() + RandomStringUtils.random(4, STRING_CHARS_2);
@@ -32,8 +37,8 @@ public final class WechatUtil {
/**
* 把SyncKeyList转为字符串格式
*
* @param SyncKeyList
* @return
* @param SyncKeyList SyncKeyList
* @return 字符串
*/
public static String syncKeyListToString(JSONArray SyncKeyList) {
if (SyncKeyList == null) {
@@ -55,8 +60,8 @@ public final class WechatUtil {
/**
* 根据ContentType得到微信上传所需的mediatype
*
* @param contentType
* @return
* @param contentType contentType
* @return 微信上传所需的mediatype
*/
public static String getMediatype(String contentType) {
if (contentType == null) {

View File

@@ -29,4 +29,6 @@ webwx-url.webwxsync_url=https://wx<urlVersion>.qq.com/cgi-bin/mmwebwx-bin/webwxs
## 4.2、发送消息
webwx-url.webwxsendmsg_url=https://wx<urlVersion>.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?pass_ticket=<pass_ticket>
## 4.3、上传媒体文件
webwx-url.uploadmedia_url=https://file.wx<urlVersion>.qq.com/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json
webwx-url.uploadmedia_url=https://file.wx<urlVersion>.qq.com/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json
## 4.4、发送图片消息
webwx-url.webwxsendmsgimg_url=https://wx<urlVersion>.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsgimg?fun=async&f=json&lang=zh_CN&pass_ticket=<pass_ticket>

View File

@@ -1,14 +1,22 @@
import com.alibaba.fastjson.JSONObject;
import com.hotlcc.wechat4j.Wechat;
import com.hotlcc.wechat4j.api.WebWeixinApi;
import com.hotlcc.wechat4j.handler.ReceivedMsgHandler;
import com.hotlcc.wechat4j.model.ReceivedMsg;
import com.hotlcc.wechat4j.model.UserInfo;
import com.hotlcc.wechat4j.util.StringUtil;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
public class TestClass {
public static void main(String[] args) {
private Wechat wechat;
@Before
public void initAndLogin() {
wechat = new Wechat();
WebWeixinApi api = new WebWeixinApi();
Wechat wechat = new Wechat();
wechat.setWebWeixinApi(api);
wechat.addReceivedMsgHandler(new ReceivedMsgHandler() {
@Override
@@ -18,6 +26,20 @@ public class TestClass {
System.out.println(name + ": " + msg.getContent());
}
});
wechat.autoLogin();
}
public void testSendText() {
JSONObject result = wechat.sendText(null, "这是消息内容");
System.out.println(result);
}
@Test
public void testSendImage() {
File file = new File("D:\\Downloads\\images\\6600e90b8b0ce2037a5291a7147ffd2b.jpeg");
JSONObject result = wechat.sendImage(null, file);
System.out.println(result);
}
}

View File

@@ -1,11 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false" scan="true" scanPeriod="30 second">
<property name="APP_NAME" value="emall-admin"/>
<property name="LOG_DIRECTORY" value="${user.home}/logs/${APP_NAME}/"/>
<property name="FILE_SIZE" value="20MB"/>
<property name="MAX_HISTORY" value="100"/>
<property name="TOTAL_SIZE" value="1GB"/>
<property name="APP_NAME" value="wechat4j"/>
<property name="LOG_LEVEL" value="INFO"/>
<!-- 控制台打印 -->
@@ -15,145 +11,8 @@
</encoder>
</appender>
<!-- INFO 输入到文件,按文件大小 -->
<appender name="INFO-OUT" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<file>${LOG_DIRECTORY}/info.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_DIRECTORY}%d/info%i.log</fileNamePattern>
<maxHistory>${MAX_HISTORY}</maxHistory>
<maxFileSize>${FILE_SIZE}</maxFileSize>
<totalSizeCap>${TOTAL_SIZE}</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger{36} - %m%n</pattern>
</encoder>
</appender>
<!-- ERROR 输入到文件,按文件大小 -->
<appender name="ERROR-OUT" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger{36} - %m%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<file>${LOG_DIRECTORY}/error.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_DIRECTORY}%d/error%i.log</fileNamePattern>
<maxHistory>${MAX_HISTORY}</maxHistory>
<maxFileSize>${FILE_SIZE}</maxFileSize>
<totalSizeCap>${TOTAL_SIZE}</totalSizeCap>
</rollingPolicy>
</appender>
<!-- WARN 输入到文件,按文件大小 -->
<appender name="WARN-OUT" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger{36} - %m%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<file>${LOG_DIRECTORY}/warn.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_DIRECTORY}%d/warn%i.log</fileNamePattern>
<maxHistory>${MAX_HISTORY}</maxHistory>
<maxFileSize>${FILE_SIZE}</maxFileSize>
<totalSizeCap>${TOTAL_SIZE}</totalSizeCap>
</rollingPolicy>
</appender>
<!-- DEBUG 输入到文件,按文件大小 -->
<appender name="DEBUG-OUT" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger{36} - %m%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<file>${LOG_DIRECTORY}/debug.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_DIRECTORY}%d/debug%i.log</fileNamePattern>
<maxHistory>${MAX_HISTORY}</maxHistory>
<maxFileSize>${FILE_SIZE}</maxFileSize>
</rollingPolicy>
</appender>
<!-- TRACE 输入到文件,按日期和文件大小 -->
<appender name="TRACE-OUT" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger{36} - %m%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>TRACE</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<file>${LOG_DIRECTORY}/trace.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_DIRECTORY}%d/trace%i.log</fileNamePattern>
<maxHistory>${MAX_HISTORY}</maxHistory>
<maxFileSize>${FILE_SIZE}</maxFileSize>
</rollingPolicy>
</appender>
<appender name="${APP_NAME}" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger{36} - %m%n</pattern>
</encoder>
<!-- 过滤日志级别 -->
<!--<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>-->
<file>${LOG_DIRECTORY}/${APP_NAME}.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_DIRECTORY}%d/${APP_NAME}%i.log</fileNamePattern>
<maxHistory>${MAX_HISTORY}</maxHistory>
<maxFileSize>${FILE_SIZE}</maxFileSize>
</rollingPolicy>
</appender>
<!-- 包级别的日志 additivity="false" -->
<logger name="com.aaron.springcloud.consumer.controller" additivity="false">
<level value="{LOG_LEVEL}"/>
<appender-ref ref="${APP_NAME}"/>
<!-- 还是要输出到控制台中-->
<appender-ref ref="STDOUT"/>
</logger>
<!-- org包下面设置日志级别为Error-->
<!--<logger name="org" level="ERROR"/>-->
<!-- org包下面设置日志级别为Error-->
<!--<logger name="com.netflix" level="ERROR"/>-->
<!-- Logger 根目录 -->
<root level="${LOG_LEVEL}">
<appender-ref ref="STDOUT"/>
<!--<appender-ref ref="ERROR-OUT"/>
<appender-ref ref="WARN-OUT"/>
<appender-ref ref="INFO-OUT"/>
<appender-ref ref="DEBUG-OUT"/>
<appender-ref ref="TRACE-OUT"/>-->
</root>
</configuration>