From 61a335646962d1ff864afa220acfb463ef8c20ce Mon Sep 17 00:00:00 2001 From: Binary Wang Date: Sun, 16 Sep 2018 18:05:54 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../github/binarywang/wxpay/bean/result/BaseWxPayResult.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/BaseWxPayResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/BaseWxPayResult.java index 9d5829a1fe..877d0a7e2b 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/BaseWxPayResult.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/BaseWxPayResult.java @@ -118,7 +118,7 @@ public abstract class BaseWxPayResult implements Serializable { * @param fen 将要被转换为元的分的数值 */ public static String fenToYuan(Integer fen) { - return new BigDecimal(Double.valueOf(fen) / 100).setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString(); + return BigDecimal.valueOf(Double.valueOf(fen) / 100).setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString(); } /** From 314871cea61a29353dfe9b8b5b739bb9d940bfe6 Mon Sep 17 00:00:00 2001 From: Binary Wang Date: Sun, 16 Sep 2018 18:18:00 +0800 Subject: [PATCH 2/5] =?UTF-8?q?#730=20=E5=85=AC=E4=BC=97=E5=8F=B7=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E5=A2=9E=E5=8A=A0=E5=AE=A2=E6=9C=8D=E8=BE=93=E5=85=A5?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../weixin/mp/api/WxMpKefuService.java | 67 ++++++++++++++++--- .../mp/api/impl/WxMpKefuServiceImpl.java | 39 +++++++---- .../mp/api/impl/WxMpKefuServiceImplTest.java | 29 +++++--- 3 files changed, 106 insertions(+), 29 deletions(-) diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpKefuService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpKefuService.java index e5c2735037..99dcaba99b 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpKefuService.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpKefuService.java @@ -1,16 +1,21 @@ package me.chanjar.weixin.mp.api; +import java.io.File; +import java.util.Date; + import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage; import me.chanjar.weixin.mp.bean.kefu.request.WxMpKfAccountRequest; -import me.chanjar.weixin.mp.bean.kefu.result.*; - -import java.io.File; -import java.util.Date; +import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfList; +import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfMsgList; +import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfOnlineList; +import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfSessionGetResult; +import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfSessionList; +import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfSessionWaitCaseList; /** *
- * 客服接口 ,
+ * 客服接口.
  * 注意:命名采用kefu拼音的原因是:其英文CustomerService如果再加上Service后缀显得有点啰嗦,如果不加又显得表意不完整。
  * 
* @@ -30,7 +35,8 @@ public interface WxMpKefuService { String KFSESSION_GET_SESSION = "https://api.weixin.qq.com/customservice/kfsession/getsession?openid=%s"; String KFSESSION_GET_SESSION_LIST = "https://api.weixin.qq.com/customservice/kfsession/getsessionlist?kf_account=%s"; String KFSESSION_GET_WAIT_CASE = "https://api.weixin.qq.com/customservice/kfsession/getwaitcase"; - String MSGRECORD_GET_MSG_LIST = "https://api.weixin.qq.com/customservice/msgrecord/getmsglist"; + String MSG_RECORD_LIST = "https://api.weixin.qq.com/customservice/msgrecord/getmsglist"; + String CUSTOM_TYPING = "https://api.weixin.qq.com/cgi-bin/message/custom/typing"; /** *
@@ -38,6 +44,8 @@ public interface WxMpKefuService {
    * 详情请见: 发送客服消息
    * 接口url格式:https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=ACCESS_TOKEN
    * 
+ * + * @throws WxErrorException 异常 */ boolean sendKefuMessage(WxMpKefuMessage message) throws WxErrorException; @@ -49,6 +57,8 @@ public interface WxMpKefuService { * 详情请见:客服管理 * 接口url格式:https://api.weixin.qq.com/cgi-bin/customservice/getkflist?access_token=ACCESS_TOKEN * + * + * @throws WxErrorException 异常 */ WxMpKfList kfList() throws WxErrorException; @@ -58,6 +68,8 @@ public interface WxMpKefuService { * 详情请见:客服管理 * 接口url格式:https://api.weixin.qq.com/cgi-bin/customservice/getonlinekflist?access_token=ACCESS_TOKEN * + * + * @throws WxErrorException 异常 */ WxMpKfOnlineList kfOnlineList() throws WxErrorException; @@ -67,6 +79,8 @@ public interface WxMpKefuService { * 详情请见:客服管理 * 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/add?access_token=ACCESS_TOKEN * + * + * @throws WxErrorException 异常 */ boolean kfAccountAdd(WxMpKfAccountRequest request) throws WxErrorException; @@ -85,6 +99,8 @@ public interface WxMpKefuService { * 详情请见:客服管理 * 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/inviteworker?access_token=ACCESS_TOKEN * + * + * @throws WxErrorException 异常 */ boolean kfAccountInviteWorker(WxMpKfAccountRequest request) throws WxErrorException; @@ -94,8 +110,10 @@ public interface WxMpKefuService { * 详情请见:客服管理 * 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/uploadheadimg?access_token=ACCESS_TOKEN&kf_account=KFACCOUNT * + * + * @throws WxErrorException 异常 */ - boolean kfAccountUploadHeadImg(String kfAccount, File imgFile) throws WxErrorException; + boolean kfAccountUploadHeadImg(String kfAccount, File imgFile) throws WxErrorException; /** *
@@ -103,6 +121,8 @@ public interface WxMpKefuService {
    * 详情请见:客服管理
    * 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/del?access_token=ACCESS_TOKEN&kf_account=KFACCOUNT
    * 
+ * + * @throws WxErrorException 异常 */ boolean kfAccountDel(String kfAccount) throws WxErrorException; @@ -115,6 +135,8 @@ public interface WxMpKefuService { * 详情请见:客服会话控制接口 * 接口url格式: https://api.weixin.qq.com/customservice/kfsession/create?access_token=ACCESS_TOKEN * + * + * @throws WxErrorException 异常 */ boolean kfSessionCreate(String openid, String kfAccount) throws WxErrorException; @@ -125,6 +147,8 @@ public interface WxMpKefuService { * 详情请见:客服会话控制接口 * 接口url格式: https://api.weixin.qq.com/customservice/kfsession/close?access_token=ACCESS_TOKEN * + * + * @throws WxErrorException 异常 */ boolean kfSessionClose(String openid, String kfAccount) throws WxErrorException; @@ -135,6 +159,8 @@ public interface WxMpKefuService { * 详情请见:客服会话控制接口 * 接口url格式: https://api.weixin.qq.com/customservice/kfsession/getsession?access_token=ACCESS_TOKEN&openid=OPENID * + * + * @throws WxErrorException 异常 */ WxMpKfSessionGetResult kfSessionGet(String openid) throws WxErrorException; @@ -145,6 +171,8 @@ public interface WxMpKefuService { * 详情请见:客服会话控制 * 接口url格式: https://api.weixin.qq.com/customservice/kfsession/getsessionlist?access_token=ACCESS_TOKEN&kf_account=KFACCOUNT * + * + * @throws WxErrorException 异常 */ WxMpKfSessionList kfSessionList(String kfAccount) throws WxErrorException; @@ -155,6 +183,8 @@ public interface WxMpKefuService { * 详情请见:客服会话控制 * 接口url格式: https://api.weixin.qq.com/customservice/kfsession/getwaitcase?access_token=ACCESS_TOKEN * + * + * @throws WxErrorException 异常 */ WxMpKfSessionWaitCaseList kfSessionGetWaitCase() throws WxErrorException; @@ -173,7 +203,7 @@ public interface WxMpKefuService { * @param msgId 消息id顺序从小到大,从1开始 * @param number 每次获取条数,最多10000条 * @return 聊天记录对象 - * @throws WxErrorException + * @throws WxErrorException 异常 */ WxMpKfMsgList kfMsgList(Date startTime, Date endTime, Long msgId, Integer number) throws WxErrorException; @@ -188,8 +218,27 @@ public interface WxMpKefuService { * @param startTime 起始时间 * @param endTime 结束时间 * @return 聊天记录对象 - * @throws WxErrorException + * @throws WxErrorException 异常 */ WxMpKfMsgList kfMsgList(Date startTime, Date endTime) throws WxErrorException; + /** + *
+   * 客服输入状态
+   * 开发者可通过调用“客服输入状态”接口,返回客服当前输入状态给用户。
+   * 此接口需要客服消息接口权限。
+   * 如果不满足发送客服消息的触发条件,则无法下发输入状态。
+   * 下发输入状态,需要客服之前30秒内跟用户有过消息交互。
+   * 在输入状态中(持续15s),不可重复下发输入态。
+   * 在输入状态中,如果向用户下发消息,会同时取消输入状态。
+   *
+   * 详情请见:客服输入状态
+   * 接口url格式:https://api.weixin.qq.com/cgi-bin/message/custom/typing?access_token=ACCESS_TOKEN
+   * 
+ * + * @param openid 用户id + * @param command "Typing":对用户下发“正在输入"状态 "CancelTyping":取消对用户的”正在输入"状态 + * @throws WxErrorException 异常 + */ + boolean sendKfTypingState(String openid, String command) throws WxErrorException; } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpKefuServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpKefuServiceImpl.java index 604ce8327e..7707c567dd 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpKefuServiceImpl.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpKefuServiceImpl.java @@ -1,8 +1,14 @@ package me.chanjar.weixin.mp.api.impl; +import java.io.File; +import java.util.Date; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.google.gson.JsonObject; -import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; +import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; import me.chanjar.weixin.mp.api.WxMpKefuService; @@ -10,12 +16,12 @@ import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage; import me.chanjar.weixin.mp.bean.kefu.request.WxMpKfAccountRequest; import me.chanjar.weixin.mp.bean.kefu.request.WxMpKfSessionRequest; -import me.chanjar.weixin.mp.bean.kefu.result.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.util.Date; +import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfList; +import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfMsgList; +import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfOnlineList; +import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfSessionGetResult; +import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfSessionList; +import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfSessionWaitCaseList; /** * @author Binary Wang @@ -120,12 +126,12 @@ public WxMpKfMsgList kfMsgList(Date startTime, Date endTime, Long msgId, Integer } JsonObject param = new JsonObject(); - param.addProperty("starttime", startTime.getTime() / 1000); //starttime 起始时间,unix时间戳 - param.addProperty("endtime", endTime.getTime() / 1000); //endtime 结束时间,unix时间戳,每次查询时段不能超过24小时 - param.addProperty("msgid", msgId); //msgid 消息id顺序从小到大,从1开始 - param.addProperty("number", number); //number 每次获取条数,最多10000条 + param.addProperty("starttime", startTime.getTime() / 1000); + param.addProperty("endtime", endTime.getTime() / 1000); + param.addProperty("msgid", msgId); + param.addProperty("number", number); - String responseContent = this.wxMpService.post(MSGRECORD_GET_MSG_LIST, param.toString()); + String responseContent = this.wxMpService.post(MSG_RECORD_LIST, param.toString()); return WxMpKfMsgList.fromJson(responseContent); } @@ -149,4 +155,13 @@ public WxMpKfMsgList kfMsgList(Date startTime, Date endTime) throws WxErrorExcep return result; } + @Override + public boolean sendKfTypingState(String openid, String command) throws WxErrorException { + JsonObject params = new JsonObject(); + params.addProperty("touser", openid); + params.addProperty("command", command); + String responseContent = this.wxMpService.post(CUSTOM_TYPING, params.toString()); + return responseContent != null; + } + } diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpKefuServiceImplTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpKefuServiceImplTest.java index 13a161a189..712e887423 100644 --- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpKefuServiceImplTest.java +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpKefuServiceImplTest.java @@ -1,5 +1,11 @@ package me.chanjar.weixin.mp.api.impl; +import java.io.File; +import java.util.Date; + +import org.joda.time.DateTime; +import org.testng.annotations.*; + import com.google.inject.Inject; import me.chanjar.weixin.common.api.WxConsts; import me.chanjar.weixin.common.error.WxErrorException; @@ -8,12 +14,13 @@ import me.chanjar.weixin.mp.api.test.TestConfigStorage; import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage; import me.chanjar.weixin.mp.bean.kefu.request.WxMpKfAccountRequest; -import me.chanjar.weixin.mp.bean.kefu.result.*; -import org.joda.time.DateTime; -import org.testng.annotations.*; - -import java.io.File; -import java.util.Date; +import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfInfo; +import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfList; +import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfMsgList; +import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfOnlineList; +import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfSessionGetResult; +import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfSessionList; +import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfSessionWaitCaseList; import static org.assertj.core.api.Assertions.assertThat; @@ -134,8 +141,7 @@ public void testKfSessionCreate(String kfAccount, String openid) throws WxErrorE } @Test(dataProvider = "getKfAccountAndOpenid") - public void testKfSessionClose(String kfAccount, String openid) - throws WxErrorException { + public void testKfSessionClose(String kfAccount, String openid) throws WxErrorException { boolean result = this.wxService.getKefuService().kfSessionClose(openid, kfAccount); assertThat(result).isTrue(); } @@ -178,4 +184,11 @@ public void testKfMsgListAll() throws WxErrorException { assertThat(result).isNotNull(); System.err.println(result); } + + @Test + public void testSendKfTypingState() throws WxErrorException { + TestConfigStorage configStorage = (TestConfigStorage) this.wxService.getWxMpConfigStorage(); + boolean result = this.wxService.getKefuService().sendKfTypingState(configStorage.getOpenid(), "Typing"); + assertThat(result).isTrue(); + } } From 4990c622a35130f9100775f496c0d4af739ecf95 Mon Sep 17 00:00:00 2001 From: Binary Wang Date: Sun, 16 Sep 2018 18:38:11 +0800 Subject: [PATCH 3/5] =?UTF-8?q?#727=20=E5=B0=8F=E7=A8=8B=E5=BA=8F=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E5=A2=9E=E5=8A=A0=E5=86=85=E5=AE=B9=E5=AE=89=E5=85=A8?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wx/miniapp/api/WxMaService.java | 18 ++++++- .../wx/miniapp/api/impl/WxMaServiceImpl.java | 54 +++++++++++++------ .../miniapp/api/impl/WxMaServiceImplTest.java | 16 ++++-- 3 files changed, 66 insertions(+), 22 deletions(-) diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java index bc625ac035..0904d606c1 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java @@ -1,8 +1,9 @@ package cn.binarywang.wx.miniapp.api; +import java.io.File; + import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; import cn.binarywang.wx.miniapp.config.WxMaConfig; -import me.chanjar.weixin.common.bean.WxJsapiSignature; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; import me.chanjar.weixin.common.util.http.RequestExecutor; @@ -19,12 +20,24 @@ public interface WxMaService { String JSCODE_TO_SESSION_URL = "https://api.weixin.qq.com/sns/jscode2session"; + String IMG_SEC_CHECK_URL = "https://api.weixin.qq.com/wxa/img_sec_check"; + /** - * 获取登录后的session信息 + *
+   * 校验一张图片是否含有违法违规内容.
+   * 应用场景举例:1)图片智能鉴黄:涉及拍照的工具类应用(如美拍,识图类应用)用户拍照上传检测;电商类商品上架图片检测;媒体类用户文章里的图片检测等;2)敏感人脸识别:用户头像;媒体类用户文章里的图片检测;社交类用户上传的图片检测等。频率限制:单个 appId 调用上限为 1000 次/分钟,100,000 次/天
+   * 详情请见: https://developers.weixin.qq.com/miniprogram/dev/api/imgSecCheck.html
+   * 
+ */ + boolean imgSecCheck(File file) throws WxErrorException; + + /** + * 获取登录后的session信息. * * @param jsCode 登录时获取的 code */ WxMaJscode2SessionResult jsCode2SessionInfo(String jsCode) throws WxErrorException; + /** *
    * 验证消息的确来自微信服务器.
@@ -133,6 +146,7 @@ public interface WxMaService {
 
   /**
    * 返回模板配置相关接口方法的实现类对象, 以方便调用其各个接口.
+   *
    * @return WxMaTemplateService
    */
   WxMaTemplateService getTemplateService();
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java
index df8afadd2d..9c8ac82d73 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java
@@ -1,31 +1,48 @@
 package cn.binarywang.wx.miniapp.api.impl;
 
-import cn.binarywang.wx.miniapp.api.*;
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.locks.Lock;
+
+import org.apache.http.HttpHost;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.BasicResponseHandler;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import cn.binarywang.wx.miniapp.api.WxMaAnalysisService;
+import cn.binarywang.wx.miniapp.api.WxMaCodeService;
+import cn.binarywang.wx.miniapp.api.WxMaJsapiService;
+import cn.binarywang.wx.miniapp.api.WxMaMediaService;
+import cn.binarywang.wx.miniapp.api.WxMaMsgService;
+import cn.binarywang.wx.miniapp.api.WxMaQrcodeService;
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.api.WxMaSettingService;
+import cn.binarywang.wx.miniapp.api.WxMaTemplateService;
+import cn.binarywang.wx.miniapp.api.WxMaUserService;
 import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
 import cn.binarywang.wx.miniapp.config.WxMaConfig;
 import com.google.common.base.Joiner;
 import com.google.gson.Gson;
 import me.chanjar.weixin.common.bean.WxAccessToken;
+import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
 import me.chanjar.weixin.common.error.WxError;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.util.DataUtils;
 import me.chanjar.weixin.common.util.crypto.SHA1;
-import me.chanjar.weixin.common.util.http.*;
+import me.chanjar.weixin.common.util.http.HttpType;
+import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor;
+import me.chanjar.weixin.common.util.http.RequestExecutor;
+import me.chanjar.weixin.common.util.http.RequestHttp;
+import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor;
+import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor;
 import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
 import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
-import org.apache.http.HttpHost;
-import org.apache.http.client.config.RequestConfig;
-import org.apache.http.client.methods.CloseableHttpResponse;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.impl.client.BasicResponseHandler;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.locks.Lock;
 
 import static cn.binarywang.wx.miniapp.constant.WxMaConstants.ErrorCode.*;
 
@@ -132,6 +149,13 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException {
     return this.getWxMaConfig().getAccessToken();
   }
 
+  @Override
+  public boolean imgSecCheck(File file) throws WxErrorException {
+    //这里只是借用MediaUploadRequestExecutor,并不使用其返回值WxMediaUploadResult
+    WxMediaUploadResult result = this.execute(MediaUploadRequestExecutor.create(this.getRequestHttp()), IMG_SEC_CHECK_URL, file);
+    return result != null;
+  }
+
   @Override
   public WxMaJscode2SessionResult jsCode2SessionInfo(String jsCode) throws WxErrorException {
     final WxMaConfig config = getWxMaConfig();
diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImplTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImplTest.java
index 1d7ec2f3ee..dcefc68c72 100644
--- a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImplTest.java
+++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImplTest.java
@@ -1,16 +1,17 @@
 package cn.binarywang.wx.miniapp.api.impl;
 
+import java.io.File;
+
+import org.apache.commons.lang3.StringUtils;
+import org.testng.annotations.*;
+
 import cn.binarywang.wx.miniapp.api.WxMaService;
 import cn.binarywang.wx.miniapp.config.WxMaConfig;
 import cn.binarywang.wx.miniapp.test.ApiTestModule;
 import com.google.inject.Inject;
 import me.chanjar.weixin.common.error.WxErrorException;
-import org.apache.commons.lang3.StringUtils;
-import org.testng.annotations.Guice;
-import org.testng.annotations.Test;
 
-import static org.testng.Assert.assertNotEquals;
-import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.*;
 
 /**
  * @author Binary Wang
@@ -32,4 +33,9 @@ public void testRefreshAccessToken() throws WxErrorException {
     assertTrue(StringUtils.isNotBlank(after));
   }
 
+  @Test
+  public void testImgSecCheck() throws WxErrorException {
+    boolean result = this.wxService.imgSecCheck(new File(ClassLoader.getSystemResource("tmp.png").getFile()));
+    assertTrue(result);
+  }
 }

From d5d106f42670b4b58855bbe3475814a5e36cd9ce Mon Sep 17 00:00:00 2001
From: Binary Wang 
Date: Sun, 16 Sep 2018 19:30:21 +0800
Subject: [PATCH 4/5] =?UTF-8?q?#705=20=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1?=
 =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=9B=B8=E5=85=B3=E6=8E=A5=E5=8F=A3=E5=A2=9E?=
 =?UTF-8?q?=E5=8A=A0=E6=88=90=E5=91=98=E5=AF=B9=E5=A4=96=E4=BF=A1=E6=81=AF?=
 =?UTF-8?q?external=5Fprofile?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../me/chanjar/weixin/cp/bean/WxCpUser.java   |  46 +++++++
 .../weixin/cp/util/json/WxCpGsonBuilder.java  |   3 +
 .../cp/util/json/WxCpUserGsonAdapter.java     |  91 ++++++++++++-
 .../cp/util/json/WxCpUserGsonAdapterTest.java | 124 ++++++++++++++++++
 4 files changed, 262 insertions(+), 2 deletions(-)
 create mode 100644 weixin-java-cp/src/test/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapterTest.java

diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java
index 9321295a95..85dfe2985d 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java
@@ -5,7 +5,9 @@
 import java.util.List;
 
 import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Data;
+import lombok.NoArgsConstructor;
 import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
 
 /**
@@ -34,6 +36,14 @@ public class WxCpUser implements Serializable {
   private String telephone;
   private String qrCode;
   private Boolean toInvite;
+  /**
+   * 成员对外信息.
+   */
+  private List externalAttrs = new ArrayList<>();
+
+  public void addExternalAttr(ExternalAttr externalAttr) {
+    this.externalAttrs.add(externalAttr);
+  }
 
   public void addExtAttr(String name, String value) {
     this.extAttrs.add(new Attr(name, value));
@@ -54,4 +64,40 @@ public static class Attr {
     private String value;
   }
 
+  @Data
+  @Builder
+  @NoArgsConstructor
+  @AllArgsConstructor
+  public static class ExternalAttr {
+    /**
+     * 属性类型: 0-本文 1-网页 2-小程序.
+     */
+    private int type;
+    /**
+     * 属性名称: 需要先确保在管理端有创建改属性,否则会忽略.
+     */
+    private String name;
+    /**
+     * 文本属性内容,长度限制12个UTF8字符.
+     */
+    private String value;
+    /**
+     * 网页的url,必须包含http或者https头.
+     */
+    private String url;
+    /**
+     * 小程序的展示标题,长度限制12个UTF8字符.
+     * 或者 网页的展示标题,长度限制12个UTF8字符
+     */
+    private String title;
+    /**
+     * 小程序appid,必须是有在本企业安装授权的小程序,否则会被忽略.
+     */
+    private String appid;
+    /**
+     * 小程序的页面路径.
+     */
+    private String pagePath;
+  }
+
 }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpGsonBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpGsonBuilder.java
index c9c5596466..f4dc691ea4 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpGsonBuilder.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpGsonBuilder.java
@@ -10,6 +10,9 @@
 import me.chanjar.weixin.cp.bean.WxCpTag;
 import me.chanjar.weixin.cp.bean.WxCpUser;
 
+/**
+ * @author Daniel Qian
+ */
 public class WxCpGsonBuilder {
 
   public static final GsonBuilder INSTANCE = new GsonBuilder();
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java
index 7e49d83965..f04ce29be1 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java
@@ -27,10 +27,11 @@
  * @author Daniel Qian
  */
 public class WxCpUserGsonAdapter implements JsonDeserializer, JsonSerializer {
+  private static final String EXTERNAL_PROFILE = "external_profile";
+  private static final String EXTERNAL_ATTR = "external_attr";
 
   @Override
-  public WxCpUser deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
-    throws JsonParseException {
+  public WxCpUser deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
     JsonObject o = json.getAsJsonObject();
     WxCpUser user = new WxCpUser();
 
@@ -71,6 +72,53 @@ public WxCpUser deserialize(JsonElement json, Type typeOfT, JsonDeserializationC
         user.getExtAttrs().add(attr);
       }
     }
+
+    if (GsonHelper.isNotNull(o.get(EXTERNAL_PROFILE))) {
+      JsonArray attrJsonElements = o.get(EXTERNAL_PROFILE).getAsJsonObject().get(EXTERNAL_ATTR).getAsJsonArray();
+      for (JsonElement element : attrJsonElements) {
+        final Integer type = GsonHelper.getInteger(element.getAsJsonObject(), "type");
+        final String name = GsonHelper.getString(element.getAsJsonObject(), "name");
+
+        switch (type) {
+          case 0: {
+            user.getExternalAttrs()
+              .add(WxCpUser.ExternalAttr.builder()
+                .type(type)
+                .name(name)
+                .value(GsonHelper.getString(element.getAsJsonObject().get("text").getAsJsonObject(), "value"))
+                .build()
+              );
+            break;
+          }
+          case 1: {
+            final JsonObject web = element.getAsJsonObject().get("web").getAsJsonObject();
+            user.getExternalAttrs()
+              .add(WxCpUser.ExternalAttr.builder()
+                .type(type)
+                .name(name)
+                .url(GsonHelper.getString(web, "url"))
+                .title(GsonHelper.getString(web, "title"))
+                .build()
+              );
+            break;
+          }
+          case 2: {
+            final JsonObject miniprogram = element.getAsJsonObject().get("miniprogram").getAsJsonObject();
+            user.getExternalAttrs()
+              .add(WxCpUser.ExternalAttr.builder()
+                .type(type)
+                .name(name)
+                .appid(GsonHelper.getString(miniprogram, "appid"))
+                .pagePath(GsonHelper.getString(miniprogram, "pagepath"))
+                .title(GsonHelper.getString(miniprogram, "title"))
+                .build()
+              );
+            break;
+          }
+          default://ignored
+        }
+      }
+    }
     return user;
   }
 
@@ -145,6 +193,45 @@ public JsonElement serialize(WxCpUser user, Type typeOfSrc, JsonSerializationCon
       attrsJson.add("attrs", attrsJsonArray);
       o.add("extattr", attrsJson);
     }
+
+    if (user.getExternalAttrs().size() > 0) {
+      JsonArray attrsJsonArray = new JsonArray();
+      for (WxCpUser.ExternalAttr attr : user.getExternalAttrs()) {
+        JsonObject attrJson = new JsonObject();
+        attrJson.addProperty("type",attr.getType());
+        attrJson.addProperty("name", attr.getName());
+        switch (attr.getType()) {
+          case 0: {
+            JsonObject text = new JsonObject();
+            text.addProperty("value", attr.getValue());
+            attrJson.add("text", text);
+            break;
+          }
+          case 1: {
+            JsonObject web = new JsonObject();
+            web.addProperty("url", attr.getUrl());
+            web.addProperty("title", attr.getTitle());
+            attrJson.add("web", web);
+            break;
+          }
+          case 2: {
+            JsonObject miniprogram = new JsonObject();
+            miniprogram.addProperty("appid", attr.getAppid());
+            miniprogram.addProperty("pagepath", attr.getPagePath());
+            miniprogram.addProperty("title", attr.getTitle());
+            attrJson.add("miniprogram", miniprogram);
+            break;
+          }
+          default://忽略
+        }
+        attrsJsonArray.add(attrJson);
+      }
+
+      JsonObject attrsJson = new JsonObject();
+      attrsJson.add(EXTERNAL_ATTR, attrsJsonArray);
+      o.add(EXTERNAL_PROFILE, attrsJson);
+    }
+
     return o;
   }
 
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapterTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapterTest.java
new file mode 100644
index 0000000000..4f2c8ea08b
--- /dev/null
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapterTest.java
@@ -0,0 +1,124 @@
+package me.chanjar.weixin.cp.util.json;
+
+import org.testng.annotations.*;
+
+import me.chanjar.weixin.cp.bean.WxCpUser;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * 
+ *
+ * Created by Binary Wang on 2018/9/16.
+ * 
+ * + * @author BinaryWang + */ +public class WxCpUserGsonAdapterTest { + + @Test + public void testDeserialize() { + final String userJson = "{\n" + + " \"errcode\": 0,\n" + + " \"errmsg\": \"ok\",\n" + + " \"userid\": \"zhangsan\",\n" + + " \"name\": \"李四\",\n" + + " \"department\": [1, 2],\n" + + " \"order\": [1, 2],\n" + + " \"position\": \"后台工程师\",\n" + + " \"mobile\": \"15913215421\",\n" + + " \"gender\": \"1\",\n" + + " \"email\": \"zhangsan@gzdev.com\",\n" + + " \"isleader\": 1,\n" + + " \"avatar\": \"http://wx.qlogo.cn/mmopen/ajNVdqHZLLA3WJ6DSZUfiakYe37PKnQhBIeOQBO4czqrnZDS79FH5Wm5m4X69TBicnHFlhiafvDwklOpZeXYQQ2icg/0\",\n" + + " \"telephone\": \"020-123456\",\n" + + " \"enable\": 1,\n" + + " \"alias\": \"jackzhang\",\n" + + " \"extattr\": {\n" + + " \"attrs\": [{\n" + + " \"name\": \"爱好\",\n" + + " \"value\": \"旅游\"\n" + + " }, {\n" + + " \"name\": \"卡号\",\n" + + " \"value\": \"1234567234\"\n" + + " }]\n" + + " },\n" + + " \"status\": 1,\n" + + " \"qr_code\": \"https://open.work.weixin.qq.com/wwopen/userQRCode?vcode=xxx\",\n" + + " \"external_profile\": {\n" + + " \"external_attr\": [{\n" + + " \"type\": 0,\n" + + " \"name\": \"文本名称\",\n" + + " \"text\": {\n" + + " \"value\": \"文本\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"type\": 1,\n" + + " \"name\": \"网页名称\",\n" + + " \"web\": {\n" + + " \"url\": \"http://www.test.com\",\n" + + " \"title\": \"标题\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"type\": 2,\n" + + " \"name\": \"测试app\",\n" + + " \"miniprogram\": {\n" + + " \"appid\": \"wx8bd80126147df384\",\n" + + " \"pagepath\": \"/index\",\n" + + " \"title\": \"my miniprogram\"\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + "}"; + + final WxCpUser user = WxCpUser.fromJson(userJson); + assertThat(user).isNotNull(); + assertThat(user.getExternalAttrs()).isNotEmpty(); + + final WxCpUser.ExternalAttr externalAttr1 = user.getExternalAttrs().get(0); + assertThat(externalAttr1.getType()).isEqualTo(0); + assertThat(externalAttr1.getName()).isEqualTo("文本名称"); + assertThat(externalAttr1.getValue()).isEqualTo("文本"); + + final WxCpUser.ExternalAttr externalAttr2 = user.getExternalAttrs().get(1); + assertThat(externalAttr2.getType()).isEqualTo(1); + assertThat(externalAttr2.getName()).isEqualTo("网页名称"); + assertThat(externalAttr2.getUrl()).isEqualTo("http://www.test.com"); + assertThat(externalAttr2.getTitle()).isEqualTo("标题"); + + final WxCpUser.ExternalAttr externalAttr3 = user.getExternalAttrs().get(2); + assertThat(externalAttr3.getType()).isEqualTo(2); + assertThat(externalAttr3.getName()).isEqualTo("测试app"); + assertThat(externalAttr3.getAppid()).isEqualTo("wx8bd80126147df384"); + assertThat(externalAttr3.getPagePath()).isEqualTo("/index"); + assertThat(externalAttr3.getTitle()).isEqualTo("my miniprogram"); + } + + @Test + public void testSerialize() { + WxCpUser user = new WxCpUser(); + user.addExternalAttr(WxCpUser.ExternalAttr.builder() + .type(0) + .name("文本名称") + .value("文本") + .build()); + user.addExternalAttr(WxCpUser.ExternalAttr.builder() + .type(1) + .name("网页名称") + .url("http://www.test.com") + .title("标题") + .build()); + user.addExternalAttr(WxCpUser.ExternalAttr.builder() + .type(2) + .name("测试app") + .appid("wx8bd80126147df384") + .pagePath("/index") + .title("my miniprogram") + .build()); + + assertThat(user.toJson()).isEqualTo("{\"external_profile\":{\"external_attr\":[{\"type\":0,\"name\":\"文本名称\",\"text\":{\"value\":\"文本\"}},{\"type\":1,\"name\":\"网页名称\",\"web\":{\"url\":\"http://www.test.com\",\"title\":\"标题\"}},{\"type\":2,\"name\":\"测试app\",\"miniprogram\":{\"appid\":\"wx8bd80126147df384\",\"pagepath\":\"/index\",\"title\":\"my miniprogram\"}}]}}"); + } +} From 505cdafe98c036428c4bb4aec70c3b717bde0faa Mon Sep 17 00:00:00 2001 From: Binary Wang Date: Sun, 16 Sep 2018 20:40:40 +0800 Subject: [PATCH 5/5] =?UTF-8?q?#705=20=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=8E=B7=E5=8F=96=E5=A4=96=E9=83=A8=E8=81=94?= =?UTF-8?q?=E7=B3=BB=E4=BA=BA=E8=AF=A6=E6=83=85=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../weixin/cp/api/WxCpUserService.java | 42 ++++-- .../cp/api/impl/WxCpUserServiceImpl.java | 20 ++- .../me/chanjar/weixin/cp/bean/WxCpUser.java | 7 +- .../cp/bean/WxCpUserExternalContactInfo.java | 126 +++++++++++++++++ .../cp/util/json/WxCpUserGsonAdapter.java | 8 +- .../cp/api/impl/WxCpUserServiceImplTest.java | 26 ++-- .../bean/WxCpUserExternalContactInfoTest.java | 129 ++++++++++++++++++ .../cp/util/json/WxCpUserGsonAdapterTest.java | 12 +- 8 files changed, 329 insertions(+), 41 deletions(-) create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalContactInfo.java create mode 100644 weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpUserExternalContactInfoTest.java diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpUserService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpUserService.java index 05211d71dd..9f9317256e 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpUserService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpUserService.java @@ -1,11 +1,12 @@ package me.chanjar.weixin.cp.api; +import java.util.List; +import java.util.Map; + import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.cp.bean.WxCpInviteResult; import me.chanjar.weixin.cp.bean.WxCpUser; - -import java.util.List; -import java.util.Map; +import me.chanjar.weixin.cp.bean.WxCpUserExternalContactInfo; /** *
@@ -18,7 +19,7 @@
 public interface WxCpUserService {
   /**
    * 
-   *   用在二次验证的时候
+   *   用在二次验证的时候.
    *   企业在员工验证成功后,调用本方法告诉企业号平台该员工关注成功。
    * 
* @@ -28,7 +29,7 @@ public interface WxCpUserService { /** *
-   * 获取部门成员(详情)
+   * 获取部门成员(详情).
    *
    * http://qydev.weixin.qq.com/wiki/index.php?title=管理成员#.E8.8E.B7.E5.8F.96.E9.83.A8.E9.97.A8.E6.88.90.E5.91.98.28.E8.AF.A6.E6.83.85.29
    * 
@@ -41,7 +42,7 @@ public interface WxCpUserService { /** *
-   * 获取部门成员
+   * 获取部门成员.
    *
    * http://qydev.weixin.qq.com/wiki/index.php?title=管理成员#.E8.8E.B7.E5.8F.96.E9.83.A8.E9.97.A8.E6.88.90.E5.91.98
    * 
@@ -53,14 +54,14 @@ public interface WxCpUserService { List listSimpleByDepartment(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException; /** - * 新建用户 + * 新建用户. * * @param user 用户对象 */ void create(WxCpUser user) throws WxErrorException; /** - * 更新用户 + * 更新用户. * * @param user 用户对象 */ @@ -68,7 +69,7 @@ public interface WxCpUserService { /** *
-   * 删除用户/批量删除成员
+   * 删除用户/批量删除成员.
    * http://qydev.weixin.qq.com/wiki/index.php?title=管理成员#.E6.89.B9.E9.87.8F.E5.88.A0.E9.99.A4.E6.88.90.E5.91.98
    * 
* @@ -77,7 +78,7 @@ public interface WxCpUserService { void delete(String... userIds) throws WxErrorException; /** - * 获取用户 + * 获取用户. * * @param userid 用户id */ @@ -85,7 +86,7 @@ public interface WxCpUserService { /** *
-   * 邀请成员
+   * 邀请成员.
    * 企业可通过接口批量邀请成员使用企业微信,邀请后将通过短信或邮件下发通知。
    * 请求方式:POST(HTTPS)
    * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/batch/invite?access_token=ACCESS_TOKEN
@@ -113,14 +114,14 @@ public interface WxCpUserService {
    * @param userId  企业内的成员id
    * @param agentId 非必填,整型,仅用于发红包。其它场景该参数不要填,如微信支付、企业转账、电子发票
    * @return map对象,可能包含以下值:
-   * - openid	企业微信成员userid对应的openid,若有传参agentid,则是针对该agentid的openid。否则是针对企业微信corpid的openid
-   * - appid	应用的appid,若请求包中不包含agentid则不返回appid。该appid在使用微信红包时会用到
+   * - openid 企业微信成员userid对应的openid,若有传参agentid,则是针对该agentid的openid。否则是针对企业微信corpid的openid
+   * - appid 应用的appid,若请求包中不包含agentid则不返回appid。该appid在使用微信红包时会用到
    */
   Map userId2Openid(String userId, Integer agentId) throws WxErrorException;
 
   /**
    * 
-   * openid转userid
+   * openid转userid.
    *
    * 该接口主要应用于使用微信支付、微信红包和企业转账之后的结果查询。
    * 开发者需要知道某个结果事件的openid对应企业微信内成员的信息时,可以通过调用该接口进行转换查询。
@@ -134,4 +135,17 @@ public interface WxCpUserService {
    * @return userid 该openid在企业微信对应的成员userid
    */
   String openid2UserId(String openid) throws WxErrorException;
+
+  /**
+   * 获取外部联系人详情.
+   * 
+   *   企业可通过此接口,根据外部联系人的userid,拉取外部联系人详情。权限说明:
+   * 企业需要使用外部联系人管理secret所获取的accesstoken来调用
+   * 第三方应用需拥有“企业客户”权限。
+   * 第三方应用调用时,返回的跟进人follow_user仅包含应用可见范围之内的成员。
+   * 
+ * + * @param userId 外部联系人的userid + */ + WxCpUserExternalContactInfo getExternalContact(String userId) throws WxErrorException; } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpUserServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpUserServiceImpl.java index e4baff52bc..68224b1eb9 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpUserServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpUserServiceImpl.java @@ -1,18 +1,23 @@ package me.chanjar.weixin.cp.api.impl; +import java.util.List; +import java.util.Map; + import com.google.common.collect.Maps; -import com.google.gson.*; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonPrimitive; import com.google.gson.reflect.TypeToken; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.cp.api.WxCpService; import me.chanjar.weixin.cp.api.WxCpUserService; import me.chanjar.weixin.cp.bean.WxCpInviteResult; import me.chanjar.weixin.cp.bean.WxCpUser; +import me.chanjar.weixin.cp.bean.WxCpUserExternalContactInfo; import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; -import java.util.List; -import java.util.Map; - /** *
  *  Created by BinaryWang on 2017/6/24.
@@ -178,4 +183,11 @@ public String openid2UserId(String openid) throws WxErrorException {
     JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
     return tmpJsonElement.getAsJsonObject().get("userid").getAsString();
   }
+
+  @Override
+  public WxCpUserExternalContactInfo getExternalContact(String userId) throws WxErrorException {
+    String url = "https://qyapi.weixin.qq.com/cgi-bin/crm/get_external_contact?external_userid=" + userId;
+    String responseContent = this.mainService.get(url, null);
+    return WxCpUserExternalContactInfo.fromJson(responseContent);
+  }
 }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java
index 85dfe2985d..f3d5aa2282 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java
@@ -39,9 +39,9 @@ public class WxCpUser implements Serializable {
   /**
    * 成员对外信息.
    */
-  private List externalAttrs = new ArrayList<>();
+  private List externalAttrs = new ArrayList<>();
 
-  public void addExternalAttr(ExternalAttr externalAttr) {
+  public void addExternalAttr(ExternalAttribute externalAttr) {
     this.externalAttrs.add(externalAttr);
   }
 
@@ -68,7 +68,7 @@ public static class Attr {
   @Builder
   @NoArgsConstructor
   @AllArgsConstructor
-  public static class ExternalAttr {
+  public static class ExternalAttribute {
     /**
      * 属性类型: 0-本文 1-网页 2-小程序.
      */
@@ -99,5 +99,4 @@ public static class ExternalAttr {
      */
     private String pagePath;
   }
-
 }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalContactInfo.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalContactInfo.java
new file mode 100644
index 0000000000..466eac4a6e
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalContactInfo.java
@@ -0,0 +1,126 @@
+package me.chanjar.weixin.cp.bean;
+
+import java.util.List;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+/**
+ * 
+ * 外部联系人详情
+ * Created by Binary Wang on 2018/9/16.
+ * 参考文档:https://work.weixin.qq.com/api/doc#13878
+ * 
+ * + * @author Binary Wang + */ +@Getter +@Setter +public class WxCpUserExternalContactInfo { + @SerializedName("external_contact") + private ExternalContact externalContact; + + @SerializedName("follow_user") + private List followedUsers; + + @Getter + @Setter + public static class ExternalContact { + @SerializedName("external_userid") + private String externalUserId; + + @SerializedName("position") + private String position; + + @SerializedName("name") + private String name; + + @SerializedName("avatar") + private String avatar; + + @SerializedName("corp_name") + private String corpName; + + @SerializedName("corp_full_name") + private String corpFullName; + + @SerializedName("type") + private Integer type; + + @SerializedName("gender") + private Integer gender; + + @SerializedName("unionid") + private String unionId; + + @SerializedName("external_profile") + private ExternalProfile externalProfile; + } + + @Setter + @Getter + public static class ExternalProfile { + @SerializedName("external_attr") + private List externalAttrs; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class ExternalAttribute { + @Setter + @Getter + public static class Text { + private String value; + } + + @Setter + @Getter + public static class Web { + private String title; + private String url; + } + + @Setter + @Getter + public static class MiniProgram { + @SerializedName("pagepath") + private String pagePath; + private String appid; + private String title; + } + + private int type; + + private String name; + + private Text text; + + private Web web; + + @SerializedName("miniprogram") + private MiniProgram miniProgram; + } + + @Setter + @Getter + public static class FollowedUser { + @SerializedName("userid") + private String userId; + private String remark; + private String description; + @SerializedName("createtime") + private Long createTime; + } + + public static WxCpUserExternalContactInfo fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpUserExternalContactInfo.class); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java index f04ce29be1..1dc3f687d5 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java @@ -82,7 +82,7 @@ public WxCpUser deserialize(JsonElement json, Type typeOfT, JsonDeserializationC switch (type) { case 0: { user.getExternalAttrs() - .add(WxCpUser.ExternalAttr.builder() + .add(WxCpUser.ExternalAttribute.builder() .type(type) .name(name) .value(GsonHelper.getString(element.getAsJsonObject().get("text").getAsJsonObject(), "value")) @@ -93,7 +93,7 @@ public WxCpUser deserialize(JsonElement json, Type typeOfT, JsonDeserializationC case 1: { final JsonObject web = element.getAsJsonObject().get("web").getAsJsonObject(); user.getExternalAttrs() - .add(WxCpUser.ExternalAttr.builder() + .add(WxCpUser.ExternalAttribute.builder() .type(type) .name(name) .url(GsonHelper.getString(web, "url")) @@ -105,7 +105,7 @@ public WxCpUser deserialize(JsonElement json, Type typeOfT, JsonDeserializationC case 2: { final JsonObject miniprogram = element.getAsJsonObject().get("miniprogram").getAsJsonObject(); user.getExternalAttrs() - .add(WxCpUser.ExternalAttr.builder() + .add(WxCpUser.ExternalAttribute.builder() .type(type) .name(name) .appid(GsonHelper.getString(miniprogram, "appid")) @@ -196,7 +196,7 @@ public JsonElement serialize(WxCpUser user, Type typeOfSrc, JsonSerializationCon if (user.getExternalAttrs().size() > 0) { JsonArray attrsJsonArray = new JsonArray(); - for (WxCpUser.ExternalAttr attr : user.getExternalAttrs()) { + for (WxCpUser.ExternalAttribute attr : user.getExternalAttrs()) { JsonObject attrJson = new JsonObject(); attrJson.addProperty("type",attr.getType()); attrJson.addProperty("name", attr.getName()); diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpUserServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpUserServiceImplTest.java index 9babeb3f0b..6b67112095 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpUserServiceImplTest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpUserServiceImplTest.java @@ -1,22 +1,23 @@ package me.chanjar.weixin.cp.api.impl; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import org.testng.annotations.*; + import com.google.common.collect.Lists; import com.google.inject.Inject; +import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.cp.api.ApiTestModule; import me.chanjar.weixin.cp.api.WxCpService; import me.chanjar.weixin.cp.bean.Gender; import me.chanjar.weixin.cp.bean.WxCpInviteResult; import me.chanjar.weixin.cp.bean.WxCpUser; -import org.apache.commons.lang3.builder.ToStringBuilder; -import org.apache.commons.lang3.builder.ToStringStyle; -import org.testng.annotations.Guice; -import org.testng.annotations.Test; +import me.chanjar.weixin.cp.bean.WxCpUserExternalContactInfo; -import java.util.List; -import java.util.Map; - -import static org.testng.Assert.assertNotEquals; -import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.*; /** *
@@ -109,4 +110,11 @@ public void testOpenid2UserId() throws Exception {
     System.out.println(result);
     assertNotNull(result);
   }
+
+  @Test
+  public void testGetExternalContact() throws WxErrorException {
+    WxCpUserExternalContactInfo result = this.wxCpService.getUserService().getExternalContact(userId);
+    System.out.println(result);
+    assertNotNull(result);
+  }
 }
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpUserExternalContactInfoTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpUserExternalContactInfoTest.java
new file mode 100644
index 0000000000..3c1b327cd3
--- /dev/null
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpUserExternalContactInfoTest.java
@@ -0,0 +1,129 @@
+package me.chanjar.weixin.cp.bean;
+
+import java.util.List;
+
+import org.testng.annotations.*;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * 
+ *
+ * Created by Binary Wang on 2018/9/16.
+ * 
+ * + * @author Binary Wang + */ +public class WxCpUserExternalContactInfoTest { + + @Test + public void testFromJson() { + final String json = "{\n" + + " \"errcode\": 0,\n" + + " \"errmsg\": \"ok\",\n" + + " \"external_contact\": {\n" + + " \"external_userid\": \"woAJ2GCAAAXtWyujaWJHDDGi0mACH71w\",\n" + + " \"name\": \"李四\",\n" + + " \"position\": \"Mangaer\",\n" + + " \"avatar\": \"http://p.qlogo.cn/bizmail/IcsdgagqefergqerhewSdage/0\",\n" + + " \"corp_name\": \"腾讯\",\n" + + " \"corp_full_name\": \"腾讯科技有限公司\",\n" + + " \"type\": 2,\n" + + " \"gender\": 1,\n" + + " \"unionid\": \"ozynqsulJFCZ2z1aYeS8h-nuasdfR1\",\n" + + " \"external_profile\": {\n" + + " \"external_attr\": [\n" + + " {\n" + + " \"type\": 0,\n" + + " \"name\": \"文本名称\",\n" + + " \"text\": {\n" + + " \"value\": \"文本\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"type\": 1,\n" + + " \"name\": \"网页名称\",\n" + + " \"web\": {\n" + + " \"url\": \"http://www.test.com\",\n" + + " \"title\": \"标题\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"type\": 2,\n" + + " \"name\": \"测试app\",\n" + + " \"miniprogram\": {\n" + + " \"appid\": \"wx8bd80126147df384\",\n" + + " \"pagepath\": \"/index\",\n" + + " \"title\": \"my miniprogram\"\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " },\n" + + " \"follow_user\": [\n" + + " {\n" + + " \"userid\": \"rocky\",\n" + + " \"remark\": \"李部长\",\n" + + " \"description\": \"对接采购事物\",\n" + + " \"createtime\": 1525779812\n" + + " },\n" + + " {\n" + + " \"userid\": \"tommy\",\n" + + " \"remark\": \"李总\",\n" + + " \"description\": \"采购问题咨询\",\n" + + " \"createtime\": 1525881637\n" + + " }\n" + + " ]\n" + + "}"; + + final WxCpUserExternalContactInfo contactInfo = WxCpUserExternalContactInfo.fromJson(json); + assertThat(contactInfo).isNotNull(); + assertThat(contactInfo.getExternalContact()).isNotNull(); + + assertThat(contactInfo.getExternalContact().getExternalUserId()).isEqualTo("woAJ2GCAAAXtWyujaWJHDDGi0mACH71w"); + assertThat(contactInfo.getExternalContact().getPosition()).isEqualTo("Mangaer"); + assertThat(contactInfo.getExternalContact().getAvatar()).isEqualTo("http://p.qlogo.cn/bizmail/IcsdgagqefergqerhewSdage/0"); + assertThat(contactInfo.getExternalContact().getCorpName()).isEqualTo("腾讯"); + assertThat(contactInfo.getExternalContact().getCorpFullName()).isEqualTo("腾讯科技有限公司"); + assertThat(contactInfo.getExternalContact().getType()).isEqualTo(2); + assertThat(contactInfo.getExternalContact().getGender()).isEqualTo(1); + assertThat(contactInfo.getExternalContact().getUnionId()).isEqualTo("ozynqsulJFCZ2z1aYeS8h-nuasdfR1"); + assertThat(contactInfo.getExternalContact().getName()).isEqualTo("李四"); + + assertThat(contactInfo.getExternalContact().getExternalProfile()).isNotNull(); + + final List externalAttrs = contactInfo.getExternalContact().getExternalProfile().getExternalAttrs(); + assertThat(externalAttrs).isNotEmpty(); + + final WxCpUserExternalContactInfo.ExternalAttribute externalAttr1 = externalAttrs.get(0); + assertThat(externalAttr1.getType()).isEqualTo(0); + assertThat(externalAttr1.getName()).isEqualTo("文本名称"); + assertThat(externalAttr1.getText().getValue()).isEqualTo("文本"); + + final WxCpUserExternalContactInfo.ExternalAttribute externalAttr2 = externalAttrs.get(1); + assertThat(externalAttr2.getType()).isEqualTo(1); + assertThat(externalAttr2.getName()).isEqualTo("网页名称"); + assertThat(externalAttr2.getWeb().getUrl()).isEqualTo("http://www.test.com"); + assertThat(externalAttr2.getWeb().getTitle()).isEqualTo("标题"); + + final WxCpUserExternalContactInfo.ExternalAttribute externalAttr3 = externalAttrs.get(2); + assertThat(externalAttr3.getType()).isEqualTo(2); + assertThat(externalAttr3.getName()).isEqualTo("测试app"); + assertThat(externalAttr3.getMiniProgram().getAppid()).isEqualTo("wx8bd80126147df384"); + assertThat(externalAttr3.getMiniProgram().getPagePath()).isEqualTo("/index"); + assertThat(externalAttr3.getMiniProgram().getTitle()).isEqualTo("my miniprogram"); + + + List followedUsers = contactInfo.getFollowedUsers(); + assertThat(followedUsers).isNotEmpty(); + assertThat(followedUsers.get(0).getUserId()).isEqualTo("rocky"); + assertThat(followedUsers.get(0).getRemark()).isEqualTo("李部长"); + assertThat(followedUsers.get(0).getDescription()).isEqualTo("对接采购事物"); + assertThat(followedUsers.get(0).getCreateTime()).isEqualTo(1525779812); + + assertThat(followedUsers.get(1).getUserId()).isEqualTo("tommy"); + assertThat(followedUsers.get(1).getRemark()).isEqualTo("李总"); + assertThat(followedUsers.get(1).getDescription()).isEqualTo("采购问题咨询"); + assertThat(followedUsers.get(1).getCreateTime()).isEqualTo(1525881637); + } +} diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapterTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapterTest.java index 4f2c8ea08b..ec553f7521 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapterTest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapterTest.java @@ -78,18 +78,18 @@ public void testDeserialize() { assertThat(user).isNotNull(); assertThat(user.getExternalAttrs()).isNotEmpty(); - final WxCpUser.ExternalAttr externalAttr1 = user.getExternalAttrs().get(0); + final WxCpUser.ExternalAttribute externalAttr1 = user.getExternalAttrs().get(0); assertThat(externalAttr1.getType()).isEqualTo(0); assertThat(externalAttr1.getName()).isEqualTo("文本名称"); assertThat(externalAttr1.getValue()).isEqualTo("文本"); - final WxCpUser.ExternalAttr externalAttr2 = user.getExternalAttrs().get(1); + final WxCpUser.ExternalAttribute externalAttr2 = user.getExternalAttrs().get(1); assertThat(externalAttr2.getType()).isEqualTo(1); assertThat(externalAttr2.getName()).isEqualTo("网页名称"); assertThat(externalAttr2.getUrl()).isEqualTo("http://www.test.com"); assertThat(externalAttr2.getTitle()).isEqualTo("标题"); - final WxCpUser.ExternalAttr externalAttr3 = user.getExternalAttrs().get(2); + final WxCpUser.ExternalAttribute externalAttr3 = user.getExternalAttrs().get(2); assertThat(externalAttr3.getType()).isEqualTo(2); assertThat(externalAttr3.getName()).isEqualTo("测试app"); assertThat(externalAttr3.getAppid()).isEqualTo("wx8bd80126147df384"); @@ -100,18 +100,18 @@ public void testDeserialize() { @Test public void testSerialize() { WxCpUser user = new WxCpUser(); - user.addExternalAttr(WxCpUser.ExternalAttr.builder() + user.addExternalAttr(WxCpUser.ExternalAttribute.builder() .type(0) .name("文本名称") .value("文本") .build()); - user.addExternalAttr(WxCpUser.ExternalAttr.builder() + user.addExternalAttr(WxCpUser.ExternalAttribute.builder() .type(1) .name("网页名称") .url("http://www.test.com") .title("标题") .build()); - user.addExternalAttr(WxCpUser.ExternalAttr.builder() + user.addExternalAttr(WxCpUser.ExternalAttribute.builder() .type(2) .name("测试app") .appid("wx8bd80126147df384")