diff --git a/src/main/java/com/hotlcc/wechat4j/Wechat.java b/src/main/java/com/hotlcc/wechat4j/Wechat.java index 9c30af3..c89d310 100644 --- a/src/main/java/com/hotlcc/wechat4j/Wechat.java +++ b/src/main/java/com/hotlcc/wechat4j/Wechat.java @@ -3,18 +3,14 @@ package com.hotlcc.wechat4j; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.hotlcc.wechat4j.api.WebWeixinApi; -import com.hotlcc.wechat4j.enums.ExitTypeEnum; -import com.hotlcc.wechat4j.enums.LoginTipEnum; -import com.hotlcc.wechat4j.enums.RetcodeEnum; -import com.hotlcc.wechat4j.enums.SelectorEnum; +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.util.CommonUtil; -import com.hotlcc.wechat4j.util.PropertiesUtil; -import com.hotlcc.wechat4j.util.QRCodeUtil; -import com.hotlcc.wechat4j.util.StringUtil; +import com.hotlcc.wechat4j.model.WxMessage; +import com.hotlcc.wechat4j.util.*; import org.apache.http.HttpException; import org.apache.http.HttpRequest; import org.apache.http.HttpRequestInterceptor; @@ -374,7 +370,7 @@ public class Wechat { * @return */ private boolean wxInit() { - JSONObject result = webWeixinApi.webWeixinInit(httpClient, passTicket, wxsid, skey, wxuin); + JSONObject result = webWeixinApi.webWeixinInit(httpClient, passTicket, new BaseRequest(wxsid, skey, wxuin)); if (result == null) { return false; } @@ -438,7 +434,7 @@ public class Wechat { */ private boolean statusNotify(int time) { for (int i = 0; i < time; i++) { - JSONObject result = webWeixinApi.statusNotify(httpClient, passTicket, wxsid, skey, wxuin, getLoginUserName(false)); + JSONObject result = webWeixinApi.statusNotify(httpClient, passTicket, new BaseRequest(wxsid, skey, wxuin), getLoginUserName(false)); if (result == null) { continue; } @@ -553,7 +549,7 @@ public class Wechat { try { isOnlineLock.lock(); - webWeixinApi.logout(httpClient, wxsid, skey, wxuin); + webWeixinApi.logout(httpClient, new BaseRequest(wxsid, skey, wxuin)); isOnline = false; } finally { isOnlineLock.unlock(); @@ -588,7 +584,7 @@ public class Wechat { try { //API调用异常导致退出 - JSONObject result = webWeixinApi.syncCheck(httpClient, wxsid, skey, wxuin, getSyncKeyList(false)); + JSONObject result = webWeixinApi.syncCheck(httpClient, new BaseRequest(wxsid, skey, wxuin), getSyncKeyList(false)); logger.debug("微信同步监听心跳返回数据:{}", result); if (result == null) { throw new RuntimeException("微信API调用异常"); @@ -719,7 +715,7 @@ public class Wechat { */ private void webWxSync() { try { - JSONObject result = webWeixinApi.webWxSync(httpClient, passTicket, wxsid, skey, wxuin, SyncKey); + JSONObject result = webWeixinApi.webWxSync(httpClient, passTicket, new BaseRequest(wxsid, skey, wxuin), SyncKey); if (result == null) { logger.error("从服务端同步新数据异常"); return; @@ -815,7 +811,7 @@ public class Wechat { */ public UserInfo getLoginUser(boolean update) { if (loginUser == null || update) { - JSONObject result = webWeixinApi.webWeixinInit(httpClient, passTicket, wxsid, skey, wxuin); + JSONObject result = webWeixinApi.webWeixinInit(httpClient, passTicket, new BaseRequest(wxsid, skey, wxuin)); if (result == null) { return loginUser; } @@ -878,7 +874,7 @@ public class Wechat { */ private JSONObject getSyncKey(boolean update) { if (SyncKey == null || update) { - JSONObject result = webWeixinApi.webWeixinInit(httpClient, passTicket, wxsid, skey, wxuin); + JSONObject result = webWeixinApi.webWeixinInit(httpClient, passTicket, new BaseRequest(wxsid, skey, wxuin)); if (result == null) { return SyncKey; } @@ -1047,4 +1043,81 @@ public class Wechat { return null; } + + /** + * 发送文本消息 + * + * @return + */ + public JSONObject sendText(String Content, String ToUserName) { + BaseRequest BaseRequest = new BaseRequest(wxsid, skey, wxuin); + + String msgId = WechatUtil.createMsgId(); + String loginUserName = getLoginUserName(false); + WxMessage message = new WxMessage(); + message.setClientMsgId(msgId); + message.setContent(Content); + message.setFromUserName(loginUserName); + message.setLocalID(msgId); + if (StringUtil.isEmpty(ToUserName)) { + message.setToUserName(loginUserName); + } else { + message.setToUserName(ToUserName); + } + message.setType(MsgTypeEnum.TEXT_MSG.getCode()); + + JSONObject result = webWeixinApi.sendMsg(httpClient, passTicket, BaseRequest, message); + + return result; + } + + /** + * 发送文本消息(根据昵称) + * + * @param Content + * @param NickName + * @return + */ + public JSONObject sendTextToNickName(String Content, String NickName) { + if (StringUtil.isEmpty(NickName)) { + return sendText(Content, null); + } + + UserInfo userInfo = getContactByNickName(false, NickName); + if (userInfo == null) { + return null; + } + + String UserName = userInfo.getUserName(); + if (StringUtil.isEmpty(UserName)) { + return null; + } + + return sendText(Content, UserName); + } + + /** + * 发送文本消息(根据备注名) + * + * @param Content + * @param RemarkName + * @return + */ + public JSONObject sendTextToRemarkName(String Content, String RemarkName) { + if (StringUtil.isEmpty(RemarkName)) { + return sendText(Content, null); + } + + UserInfo userInfo = getContactByRemarkName(false, RemarkName); + if (userInfo == null) { + return null; + } + + String UserName = userInfo.getUserName(); + if (StringUtil.isEmpty(UserName)) { + return null; + } + + return sendText(Content, UserName); + } } diff --git a/src/main/java/com/hotlcc/wechat4j/api/WebWeixinApi.java b/src/main/java/com/hotlcc/wechat4j/api/WebWeixinApi.java index eda2cd7..4740d83 100644 --- a/src/main/java/com/hotlcc/wechat4j/api/WebWeixinApi.java +++ b/src/main/java/com/hotlcc/wechat4j/api/WebWeixinApi.java @@ -3,9 +3,12 @@ package com.hotlcc.wechat4j.api; 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.WxMessage; import com.hotlcc.wechat4j.util.PropertiesUtil; import com.hotlcc.wechat4j.util.StringUtil; import com.hotlcc.wechat4j.util.WechatUtil; +import org.apache.commons.codec.digest.DigestUtils; import org.apache.http.*; import org.apache.http.client.HttpClient; import org.apache.http.client.config.RequestConfig; @@ -14,6 +17,8 @@ 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.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MultipartEntityBuilder; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import org.json.XML; @@ -225,19 +230,17 @@ public class WebWeixinApi { * 退出登录 */ public void logout(HttpClient httpClient, - String wxsid, - String skey, - String wxuin) { + BaseRequest BaseRequest) { try { List pairList = new ArrayList<>(); - pairList.add(new BasicNameValuePair("sid", wxsid)); - pairList.add(new BasicNameValuePair("uin", wxuin)); + 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("type", i) - .add("skey", StringUtil.encodeURL(skey, Consts.UTF_8.name())) + .add("skey", StringUtil.encodeURL(BaseRequest.getSkey(), Consts.UTF_8.name())) .render(); HttpPost httpPost = new HttpPost(url); @@ -288,9 +291,7 @@ public class WebWeixinApi { */ public JSONObject webWeixinInit(HttpClient httpClient, String passticket, - String wxsid, - String skey, - String wxuin) { + BaseRequest BaseRequest) { try { String url = new ST(PropertiesUtil.getProperty("webwx-url.webwxinit_url")) .add("pass_ticket", passticket) @@ -301,7 +302,7 @@ public class WebWeixinApi { httpPost.setHeader("Content-type", ContentType.APPLICATION_JSON.toString()); JSONObject paramJson = new JSONObject(); - paramJson.put("BaseRequest", WechatUtil.createBaseRequest(wxsid, skey, wxuin)); + paramJson.put("BaseRequest", BaseRequest); HttpEntity paramEntity = new StringEntity(paramJson.toJSONString(), Consts.UTF_8); httpPost.setEntity(paramEntity); @@ -328,9 +329,7 @@ public class WebWeixinApi { */ public JSONObject statusNotify(HttpClient httpClient, String passticket, - String wxsid, - String skey, - String wxuin, + BaseRequest BaseRequest, String loginUserName) { try { String url = new ST(PropertiesUtil.getProperty("webwx-url.statusnotify_url")) @@ -341,7 +340,7 @@ public class WebWeixinApi { httpPost.setHeader("Content-type", ContentType.APPLICATION_JSON.toString()); JSONObject paramJson = new JSONObject(); - paramJson.put("BaseRequest", WechatUtil.createBaseRequest(wxsid, skey, wxuin)); + paramJson.put("BaseRequest", BaseRequest); paramJson.put("ClientMsgId", System.currentTimeMillis()); paramJson.put("Code", 3); paramJson.put("FromUserName", loginUserName); @@ -369,17 +368,15 @@ public class WebWeixinApi { * 服务端状态同步心跳 */ public JSONObject syncCheck(HttpClient httpClient, - String wxsid, - String skey, - String wxuin, + BaseRequest BaseRequest, JSONArray SyncKeyList) { try { long millis = System.currentTimeMillis(); String url = new ST(PropertiesUtil.getProperty("webwx-url.synccheck_url")) .add("r", millis) - .add("skey", StringUtil.encodeURL(skey, Consts.UTF_8.name())) - .add("sid", wxsid) - .add("uin", wxuin) + .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("_", millis) @@ -453,9 +450,7 @@ public class WebWeixinApi { */ public JSONObject batchGetContact(HttpClient httpClient, String passticket, - String wxsid, - String skey, - String wxuin, + BaseRequest BaseRequest, JSONArray batchContactList) { try { String url = new ST(PropertiesUtil.getProperty("webwx-url.batchgetcontact_url")) @@ -467,7 +462,7 @@ public class WebWeixinApi { httpPost.setHeader("Content-type", ContentType.APPLICATION_JSON.toString()); JSONObject paramJson = new JSONObject(); - paramJson.put("BaseRequest", WechatUtil.createBaseRequest(wxsid, skey, wxuin)); + paramJson.put("BaseRequest", BaseRequest); paramJson.put("Count", batchContactList.size()); paramJson.put("List", batchContactList); HttpEntity paramEntity = new StringEntity(paramJson.toJSONString(), Consts.UTF_8); @@ -494,14 +489,12 @@ public class WebWeixinApi { */ public JSONObject webWxSync(HttpClient httpClient, String passticket, - String wxsid, - String skey, - String wxuin, + BaseRequest BaseRequest, JSONObject SyncKey) { try { String url = new ST(PropertiesUtil.getProperty("webwx-url.webwxsync_url")) - .add("skey", skey) - .add("sid", wxsid) + .add("skey", BaseRequest.getSkey()) + .add("sid", BaseRequest.getSid()) .add("pass_ticket", passticket) .render(); @@ -509,7 +502,7 @@ public class WebWeixinApi { httpPost.setHeader("Content-type", ContentType.APPLICATION_JSON.toString()); JSONObject paramJson = new JSONObject(); - paramJson.put("BaseRequest", WechatUtil.createBaseRequest(wxsid, skey, wxuin)); + paramJson.put("BaseRequest", BaseRequest); paramJson.put("SyncKey", SyncKey); HttpEntity paramEntity = new StringEntity(paramJson.toJSONString(), Consts.UTF_8); httpPost.setEntity(paramEntity); @@ -535,13 +528,8 @@ public class WebWeixinApi { */ public JSONObject sendMsg(HttpClient httpClient, String passticket, - String wxsid, - String skey, - String wxuin, - String Content, - int Type, - String FromUserName, - String ToUserName) { + BaseRequest BaseRequest, + WxMessage message) { try { String url = new ST(PropertiesUtil.getProperty("webwx-url.webwxsendmsg_url")) .add("pass_ticket", passticket) @@ -551,8 +539,8 @@ public class WebWeixinApi { 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("BaseRequest", BaseRequest); + paramJson.put("Msg", message); paramJson.put("Scene", 0); HttpEntity paramEntity = new StringEntity(paramJson.toJSONString(), Consts.UTF_8); httpPost.setEntity(paramEntity); @@ -574,4 +562,72 @@ public class WebWeixinApi { return null; } } + + /** + * 上传媒体文件 + * + * @return + */ + public JSONObject uploadMedia(HttpClient httpClient, + 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")) + .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; + } + } } diff --git a/src/main/java/com/hotlcc/wechat4j/model/BaseRequest.java b/src/main/java/com/hotlcc/wechat4j/model/BaseRequest.java new file mode 100644 index 0000000..8c17d7c --- /dev/null +++ b/src/main/java/com/hotlcc/wechat4j/model/BaseRequest.java @@ -0,0 +1,59 @@ +package com.hotlcc.wechat4j.model; + +import com.hotlcc.wechat4j.util.WechatUtil; + +/** + * 基本请求模型 + */ +public class BaseRequest { + public BaseRequest() { + } + + public BaseRequest(String DeviceID, String Sid, String Skey, String Uin) { + this.DeviceID = DeviceID; + this.Sid = Sid; + this.Skey = Skey; + this.Uin = Uin; + } + + public BaseRequest(String Sid, String Skey, String Uin) { + this(WechatUtil.createDeviceID(), Sid, Skey, Uin); + } + + private String DeviceID; + private String Sid; + private String Skey; + private String Uin; + + public String getDeviceID() { + return DeviceID; + } + + public void setDeviceID(String deviceID) { + DeviceID = deviceID; + } + + public String getSid() { + return Sid; + } + + public void setSid(String sid) { + Sid = sid; + } + + public String getSkey() { + return Skey; + } + + public void setSkey(String skey) { + Skey = skey; + } + + public String getUin() { + return Uin; + } + + public void setUin(String uin) { + Uin = uin; + } +} diff --git a/src/main/java/com/hotlcc/wechat4j/model/WxMessage.java b/src/main/java/com/hotlcc/wechat4j/model/WxMessage.java new file mode 100644 index 0000000..c958097 --- /dev/null +++ b/src/main/java/com/hotlcc/wechat4j/model/WxMessage.java @@ -0,0 +1,61 @@ +package com.hotlcc.wechat4j.model; + +/** + * 要发送的消息 + */ +public class WxMessage { + private String ClientMsgId; + private String Content; + private String FromUserName; + private String LocalID; + private String ToUserName; + private Integer type; + + public String getClientMsgId() { + return ClientMsgId; + } + + public void setClientMsgId(String clientMsgId) { + ClientMsgId = clientMsgId; + } + + public String getContent() { + return Content; + } + + public void setContent(String content) { + Content = content; + } + + public String getFromUserName() { + return FromUserName; + } + + public void setFromUserName(String fromUserName) { + FromUserName = fromUserName; + } + + public String getLocalID() { + return LocalID; + } + + public void setLocalID(String localID) { + LocalID = localID; + } + + public String getToUserName() { + return ToUserName; + } + + public void setToUserName(String toUserName) { + ToUserName = toUserName; + } + + public Integer getType() { + return type; + } + + public void setType(Integer type) { + this.type = type; + } +} diff --git a/src/main/java/com/hotlcc/wechat4j/util/StringUtil.java b/src/main/java/com/hotlcc/wechat4j/util/StringUtil.java index 20b0e06..fd51739 100644 --- a/src/main/java/com/hotlcc/wechat4j/util/StringUtil.java +++ b/src/main/java/com/hotlcc/wechat4j/util/StringUtil.java @@ -3,6 +3,7 @@ package com.hotlcc.wechat4j.util; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; +import java.util.UUID; public final class StringUtil { private StringUtil() { @@ -31,4 +32,8 @@ public final class StringUtil { throw new RuntimeException(e); } } + + public static String getUuid() { + return UUID.randomUUID().toString().replace("-", "").toLowerCase(); + } } diff --git a/src/main/java/com/hotlcc/wechat4j/util/WechatUtil.java b/src/main/java/com/hotlcc/wechat4j/util/WechatUtil.java index 25c3ef2..6f0138e 100644 --- a/src/main/java/com/hotlcc/wechat4j/util/WechatUtil.java +++ b/src/main/java/com/hotlcc/wechat4j/util/WechatUtil.java @@ -29,29 +29,6 @@ public final class WechatUtil { 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转为字符串格式 * @@ -76,19 +53,19 @@ public final class WechatUtil { } /** - * 创建要发送的Msg + * 根据ContentType得到微信上传所需的mediatype * + * @param contentType * @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; + public static String getMediatype(String contentType) { + if (contentType == null) { + return "doc"; + } + if (contentType.indexOf("image") >= 0) { + return "pic"; + } else { + return "doc"; + } } } diff --git a/src/main/resources/config/webwx-url.properties b/src/main/resources/config/webwx-url.properties index 416fcaa..534484d 100644 --- a/src/main/resources/config/webwx-url.properties +++ b/src/main/resources/config/webwx-url.properties @@ -28,3 +28,5 @@ webwx-url.batchgetcontact_url=https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxbatchg 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= +## 4.3、上传媒体文件 +webwx-url.uploadmedia_url=https://file.wx2.qq.com/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json \ No newline at end of file