Young 2 лет назад
Родитель
Сommit
08ef0a516f
19 измененных файлов с 1841 добавлено и 62 удалено
  1. 3 3
      wx-java-tools/wx-java-common/src/main/java/cn/nosum/wx/common/service/WxService.java
  2. 107 40
      wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/WxCpExternalContactService.java
  3. 17 17
      wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/BaseWxCpServiceImpl.java
  4. 61 0
      wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/WxCpAgentServiceImpl.java
  5. 39 0
      wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/WxCpAgentWorkBenchServiceImpl.java
  6. 84 0
      wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/WxCpChatServiceImpl.java
  7. 62 0
      wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/WxCpDepartmentServiceImpl.java
  8. 421 0
      wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/WxCpExternalContactServiceImpl.java
  9. 100 0
      wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/WxCpGroupRobotServiceImpl.java
  10. 0 2
      wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/WxCpMediaServiceImpl.java
  11. 53 0
      wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/WxCpMenuServiceImpl.java
  12. 87 0
      wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/WxCpOAuth2ServiceImpl.java
  13. 48 0
      wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/WxCpOaCalendarServiceImpl.java
  14. 80 0
      wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/WxCpOaOaScheduleServiceImpl.java
  15. 297 0
      wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/WxCpOaServiceImpl.java
  16. 138 0
      wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/WxCpTagServiceImpl.java
  17. 37 0
      wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/WxCpTaskCardServiceImpl.java
  18. 206 0
      wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/WxCpUserServiceImpl.java
  19. 1 0
      wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/constant/WxCpApiPathConsts.java

+ 3 - 3
wx-java-tools/wx-java-common/src/main/java/cn/nosum/wx/common/service/WxService.java

@@ -36,7 +36,7 @@ public interface WxService {
      * @return 接口响应字符串
      * @throws WxErrorException 异常
      */
-    String post(String url, Object obj) throws WxErrorException;
+    String post(String url, Object obj) ;
 
     /**
      * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求.
@@ -46,7 +46,7 @@ public interface WxService {
      * @return 接口响应字符串
      * @throws WxErrorException 异常
      */
-    String post(String url, JsonObject jsonObject) throws WxErrorException;
+    String post(String url, JsonObject jsonObject) ;
 
     /**
      * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求.
@@ -56,5 +56,5 @@ public interface WxService {
      * @return 接口响应字符串
      * @throws WxErrorException 异常
      */
-    String post(String url, ToJson obj) throws WxErrorException;
+    String post(String url, ToJson obj) ;
 }

+ 107 - 40
wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/WxCpExternalContactService.java

@@ -34,20 +34,22 @@ public interface WxCpExternalContactService {
      *
      * @param info 客户联系「联系我」方式
      * @return wx cp contact way result
+     * @throws WxErrorException the wx error exception
      */
-    WxCpContactWayResult addContactWay(@NonNull WxCpContactWayInfo info);
+    WxCpContactWayResult addContactWay(@NonNull WxCpContactWayInfo info) throws WxErrorException;
 
     /**
      * 获取企业已配置的「联系我」方式
      *
      * <pre>
-     *  批量获取企业配置的「联系我」二维码和「联系我」小程序按钮
+     * <b>批量</b>获取企业配置的「联系我」二维码和「联系我」小程序按钮
      * </pre>
      *
      * @param configId 联系方式的配置id,必填
      * @return contact way
+     * @throws WxErrorException the wx error exception
      */
-    WxCpContactWayInfo getContactWay(@NonNull String configId);
+    WxCpContactWayInfo getContactWay(@NonNull String configId) throws WxErrorException;
 
     /**
      * 更新企业已配置的「联系我」方式
@@ -58,8 +60,9 @@ public interface WxCpExternalContactService {
      *
      * @param info 客户联系「联系我」方式
      * @return wx cp base resp
+     * @throws WxErrorException the wx error exception
      */
-    WxCpBaseResp updateContactWay(@NonNull WxCpContactWayInfo info);
+    WxCpBaseResp updateContactWay(@NonNull WxCpContactWayInfo info) throws WxErrorException;
 
     /**
      * 删除企业已配置的「联系我」方式
@@ -70,8 +73,9 @@ public interface WxCpExternalContactService {
      *
      * @param configId 企业联系方式的配置id,必填
      * @return wx cp base resp
+     * @throws WxErrorException the wx error exception
      */
-    WxCpBaseResp deleteContactWay(@NonNull String configId);
+    WxCpBaseResp deleteContactWay(@NonNull String configId) throws WxErrorException;
 
     /**
      * 结束临时会话
@@ -85,8 +89,9 @@ public interface WxCpExternalContactService {
      * @param userId         the user id
      * @param externalUserId the external user id
      * @return wx cp base resp
+     * @throws WxErrorException the wx error exception
      */
-    WxCpBaseResp closeTempChat(@NonNull String userId, @NonNull String externalUserId);
+    WxCpBaseResp closeTempChat(@NonNull String userId, @NonNull String externalUserId) throws WxErrorException;
 
 
     /**
@@ -99,11 +104,12 @@ public interface WxCpExternalContactService {
      * </pre>
      *
      * @param userId 外部联系人的userid
-     * @return external contact
+     * @return . external contact
+     * @throws WxErrorException the wx error exception
      * @deprecated 建议使用 {@link #getContactDetail(String)}
      */
     @Deprecated
-    WxCpExternalContactInfo getExternalContact(String userId);
+    WxCpExternalContactInfo getExternalContact(String userId) throws WxErrorException;
 
     /**
      * 获取客户详情.
@@ -121,17 +127,42 @@ public interface WxCpExternalContactService {
      * </pre>
      *
      * @param userId 外部联系人的userid,注意不是企业成员的帐号
-     * @return contact detail
+     * @return . contact detail
+     * @throws WxErrorException .
      */
-    WxCpExternalContactInfo getContactDetail(String userId);
+    WxCpExternalContactInfo getContactDetail(String userId) throws WxErrorException;
 
     /**
      * 企业和服务商可通过此接口,将微信外部联系人的userid转为微信openid,用于调用支付相关接口。暂不支持企业微信外部联系人(ExternalUserid为wo开头)的userid转openid。
      *
      * @param externalUserid 微信外部联系人的userid
      * @return 该企业的外部联系人openid
+     * @throws WxErrorException .
      */
-    String convertToOpenid(String externalUserid);
+    String convertToOpenid(String externalUserid) throws WxErrorException;
+
+    /**
+     * 服务商为企业代开发微信小程序的场景,服务商可通过此接口,将微信客户的unionid转为external_userid。
+     * <pre>
+     *
+     * 文档地址:https://work.weixin.qq.com/api/doc/90001/90143/93274
+     *
+     * 服务商代开发小程序指企业使用的小程序为企业主体的,非服务商主体的小程序。
+     * 场景:企业客户在微信端从企业主体的小程序(非服务商应用)登录,同时企业在企业微信安装了服务商的第三方应用,服务商可以调用该接口将登录用户的unionid转换为服务商全局唯一的外部联系人id
+     *
+     * 权限说明:
+     *
+     * 仅认证企业可调用
+     * unionid必须是企业主体下的unionid。即unionid的主体(为绑定了该小程序的微信开放平台账号主体)需与当前企业的主体一致。
+     * unionid的主体(即微信开放平台账号主体)需认证
+     * 该客户的跟进人必须在应用的可见范围之内
+     * </pre>
+     *
+     * @param unionid 微信客户的unionid
+     * @return 该企业的外部联系人ID
+     * @throws WxErrorException .
+     */
+    String unionidToExternalUserid(String unionid) throws WxErrorException;
 
     /**
      * 批量获取客户详情.
@@ -148,12 +179,15 @@ public interface WxCpExternalContactService {
      * 第三方/自建应用调用时,返回的跟进人follow_user仅包含应用可见范围之内的成员。
      * </pre>
      *
-     * @param userId 企业成员的userid,注意不是外部联系人的帐号
-     * @param cursor the cursor
-     * @param limit  the  limit
+     * @param userIdList 企业成员的userid列表,注意不是外部联系人的帐号
+     * @param cursor     the cursor
+     * @param limit      the  limit
      * @return wx cp user external contact batch info
+     * @throws WxErrorException .
      */
-    WxCpExternalContactBatchInfo getContactDetailBatch(String userId, String cursor, Integer limit);
+    WxCpExternalContactBatchInfo getContactDetailBatch(String[] userIdList, String cursor,
+                                                       Integer limit)
+            throws WxErrorException;
 
     /**
      * 修改客户备注信息.
@@ -165,8 +199,9 @@ public interface WxCpExternalContactService {
      * </pre>
      *
      * @param request 备注信息请求
+     * @throws WxErrorException .
      */
-    void updateRemark(WxCpUpdateRemarkRequest request);
+    void updateRemark(WxCpUpdateRemarkRequest request) throws WxErrorException;
 
     /**
      * 获取客户列表.
@@ -185,8 +220,9 @@ public interface WxCpExternalContactService {
      *
      * @param userId 企业成员的userid
      * @return List of External wx id
+     * @throws WxErrorException .
      */
-    List<String> listExternalContacts(String userId);
+    List<String> listExternalContacts(String userId) throws WxErrorException;
 
     /**
      * 企业和第三方服务商可通过此接口获取配置了客户联系功能的成员(Customer Contact)列表。
@@ -199,7 +235,7 @@ public interface WxCpExternalContactService {
      * @return List of CpUser id
      * @throws WxErrorException .
      */
-    List<String> listFollowers();
+    List<String> listFollowers() throws WxErrorException;
 
     /**
      * 企业和第三方可通过此接口,获取所有离职成员的客户列表,并可进一步调用离职成员的外部联系人再分配接口将这些客户重新分配给其他企业成员。
@@ -209,7 +245,20 @@ public interface WxCpExternalContactService {
      * @return wx cp user external unassign list
      * @throws WxErrorException the wx error exception
      */
-    WxCpUserExternalUnassignList listUnassignedList(Integer page, Integer pageSize);
+    WxCpUserExternalUnassignList listUnassignedList(Integer page, Integer pageSize) throws WxErrorException;
+
+    /**
+     * 企业可通过此接口,将已离职成员的外部联系人分配给另一个成员接替联系。
+     *
+     * @param externalUserid the external userid
+     * @param handOverUserid the hand over userid
+     * @param takeOverUserid the take over userid
+     * @return wx cp base resp
+     * @throws WxErrorException the wx error exception
+     * @deprecated 此后续将不再更新维护, 建议使用 {@link #transferCustomer(WxCpUserTransferCustomerReq)}
+     */
+    @Deprecated
+    WxCpBaseResp transferExternalContact(String externalUserid, String handOverUserid, String takeOverUserid) throws WxErrorException;
 
     /**
      * 企业可通过此接口,转接在职成员的客户给其他成员。
@@ -227,8 +276,9 @@ public interface WxCpExternalContactService {
      *
      * @param req 转接在职成员的客户给其他成员请求实体
      * @return wx cp base resp
+     * @throws WxErrorException the wx error exception
      */
-    WxCpUserTransferCustomerResp transferCustomer(WxCpUserTransferCustomerReq req);
+    WxCpUserTransferCustomerResp transferCustomer(WxCpUserTransferCustomerReq req) throws WxErrorException;
 
     /**
      * 企业和第三方可通过此接口查询在职成员的客户转接情况。
@@ -244,8 +294,9 @@ public interface WxCpExternalContactService {
      * @param takeOverUserid 接替成员的userid
      * @param cursor         分页查询的cursor,每个分页返回的数据不会超过1000条;不填或为空表示获取第一个分页;
      * @return 客户转接接口实体
+     * @throws WxErrorException the wx error exception
      */
-    WxCpUserTransferResultResp transferResult(@NotNull String handOverUserid, @NotNull String takeOverUserid, String cursor);
+    WxCpUserTransferResultResp transferResult(@NotNull String handOverUserid, @NotNull String takeOverUserid, String cursor) throws WxErrorException;
 
     /**
      * 企业可通过此接口,分配离职成员的客户给其他成员。
@@ -265,8 +316,9 @@ public interface WxCpExternalContactService {
      *
      * @param req 转接在职成员的客户给其他成员请求实体
      * @return wx cp base resp
+     * @throws WxErrorException the wx error exception
      */
-    WxCpUserTransferCustomerResp resignedTransferCustomer(WxCpUserTransferCustomerReq req);
+    WxCpUserTransferCustomerResp resignedTransferCustomer(WxCpUserTransferCustomerReq req) throws WxErrorException;
 
     /**
      * 企业和第三方可通过此接口查询离职成员的客户分配情况。
@@ -282,8 +334,9 @@ public interface WxCpExternalContactService {
      * @param takeOverUserid 接替成员的userid
      * @param cursor         分页查询的cursor,每个分页返回的数据不会超过1000条;不填或为空表示获取第一个分页;
      * @return 客户转接接口实体
+     * @throws WxErrorException the wx error exception
      */
-    WxCpUserTransferResultResp resignedTransferResult(@NotNull String handOverUserid, @NotNull String takeOverUserid, String cursor);
+    WxCpUserTransferResultResp resignedTransferResult(@NotNull String handOverUserid, @NotNull String takeOverUserid, String cursor) throws WxErrorException;
 
     /**
      * <pre>
@@ -299,10 +352,11 @@ public interface WxCpExternalContactService {
      * @param userIds   the user ids
      * @param partyIds  the party ids
      * @return the wx cp user external group chat list
+     * @throws WxErrorException the wx error exception
      * @deprecated 请使用 {@link WxCpExternalContactService#listGroupChat(Integer, String, int, String[])}
      */
     @Deprecated
-    WxCpUserExternalGroupChatList listGroupChat(Integer pageIndex, Integer pageSize, int status, String[] userIds, String[] partyIds);
+    WxCpUserExternalGroupChatList listGroupChat(Integer pageIndex, Integer pageSize, int status, String[] userIds, String[] partyIds) throws WxErrorException;
 
     /**
      * <pre>
@@ -317,8 +371,9 @@ public interface WxCpExternalContactService {
      * @param status  客户群跟进状态过滤。0 - 所有列表(即不过滤)  1 - 离职待继承  2 - 离职继承中  3 - 离职继承完成 默认为0
      * @param userIds 群主过滤。如果不填,表示获取应用可见范围内全部群主的数据(但是不建议这么用,如果可见范围人数超过1000人,为了防止数据包过大,会报错 81017);用户ID列表。最多100个
      * @return the wx cp user external group chat list
+     * @throws WxErrorException the wx error exception
      */
-    WxCpUserExternalGroupChatList listGroupChat(Integer limit, String cursor, int status, String[] userIds);
+    WxCpUserExternalGroupChatList listGroupChat(Integer limit, String cursor, int status, String[] userIds) throws WxErrorException;
 
     /**
      * <pre>
@@ -328,11 +383,11 @@ public interface WxCpExternalContactService {
      * 微信文档:https://work.weixin.qq.com/api/doc/90000/90135/92122
      * </pre>
      *
-     * @param chatId   the chat id
-     * @param needName the need name
+     * @param chatId the chat id
      * @return group chat
+     * @throws WxErrorException the wx error exception
      */
-    WxCpUserExternalGroupChatInfo getGroupChat(String chatId, Integer needName);
+    WxCpUserExternalGroupChatInfo getGroupChat(String chatId, Integer needName) throws WxErrorException;
 
     /**
      * 企业可通过此接口,将已离职成员为群主的群,分配给另一个客服成员。
@@ -356,8 +411,9 @@ public interface WxCpExternalContactService {
      * @param chatIds  需要转群主的客户群ID列表。取值范围: 1 ~ 100
      * @param newOwner 新群主ID
      * @return 分配结果,主要是分配失败的群列表
+     * @throws WxErrorException the wx error exception
      */
-    WxCpUserExternalGroupChatTransferResp transferGroupChat(String[] chatIds, String newOwner);
+    WxCpUserExternalGroupChatTransferResp transferGroupChat(String[] chatIds, String newOwner) throws WxErrorException;
 
     /**
      * <pre>
@@ -372,8 +428,9 @@ public interface WxCpExternalContactService {
      * @param userIds   the user ids
      * @param partyIds  the party ids
      * @return user behavior statistic
+     * @throws WxErrorException the wx error exception
      */
-    WxCpUserExternalUserBehaviorStatistic getUserBehaviorStatistic(Date startTime, Date endTime, String[] userIds, String[] partyIds);
+    WxCpUserExternalUserBehaviorStatistic getUserBehaviorStatistic(Date startTime, Date endTime, String[] userIds, String[] partyIds) throws WxErrorException;
 
     /**
      * <pre>
@@ -390,8 +447,9 @@ public interface WxCpExternalContactService {
      * @param userIds   the user ids
      * @param partyIds  the party ids
      * @return group chat statistic
+     * @throws WxErrorException the wx error exception
      */
-    WxCpUserExternalGroupChatStatistic getGroupChatStatistic(Date startTime, Integer orderBy, Integer orderAsc, Integer pageIndex, Integer pageSize, String[] userIds, String[] partyIds);
+    WxCpUserExternalGroupChatStatistic getGroupChatStatistic(Date startTime, Integer orderBy, Integer orderAsc, Integer pageIndex, Integer pageSize, String[] userIds, String[] partyIds) throws WxErrorException;
 
     /**
      * 添加企业群发消息任务
@@ -407,8 +465,9 @@ public interface WxCpExternalContactService {
      *
      * @param wxCpMsgTemplate the wx cp msg template
      * @return the wx cp msg template add result
+     * @throws WxErrorException the wx error exception
      */
-    WxCpMsgTemplateAddResult addMsgTemplate(WxCpMsgTemplate wxCpMsgTemplate);
+    WxCpMsgTemplateAddResult addMsgTemplate(WxCpMsgTemplate wxCpMsgTemplate) throws WxErrorException;
 
     /**
      * 发送新客户欢迎语
@@ -424,9 +483,10 @@ public interface WxCpExternalContactService {
      * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/92137
      * </pre>
      *
-     * @param msg 欢迎语消息内容
+     * @param msg .
+     * @throws WxErrorException .
      */
-    void sendWelcomeMsg(WxCpWelcomeMsg msg);
+    void sendWelcomeMsg(WxCpWelcomeMsg msg) throws WxErrorException;
 
     /**
      * <pre>
@@ -435,8 +495,9 @@ public interface WxCpExternalContactService {
      *
      * @param tagId the tag id
      * @return corp tag list
+     * @throws WxErrorException the wx error exception
      */
-    WxCpUserExternalTagGroupList getCorpTagList(String[] tagId);
+    WxCpUserExternalTagGroupList getCorpTagList(String[] tagId) throws WxErrorException;
 
     /**
      * <pre>
@@ -448,8 +509,9 @@ public interface WxCpExternalContactService {
      * @param tagId   the tag id
      * @param groupId the tagGroup id
      * @return corp tag list
+     * @throws WxErrorException the wx error exception
      */
-    WxCpUserExternalTagGroupList getCorpTagList(String[] tagId, String[] groupId);
+    WxCpUserExternalTagGroupList getCorpTagList(String[] tagId, String[] groupId) throws WxErrorException;
 
     /**
      * <pre>
@@ -459,8 +521,9 @@ public interface WxCpExternalContactService {
      *
      * @param tagGroup the tag group
      * @return wx cp user external tag group info
+     * @throws WxErrorException the wx error exception
      */
-    WxCpUserExternalTagGroupInfo addCorpTag(WxCpUserExternalTagGroupInfo tagGroup);
+    WxCpUserExternalTagGroupInfo addCorpTag(WxCpUserExternalTagGroupInfo tagGroup) throws WxErrorException;
 
     /**
      * <pre>
@@ -472,8 +535,9 @@ public interface WxCpExternalContactService {
      * @param name  the name
      * @param order the order
      * @return wx cp base resp
+     * @throws WxErrorException the wx error exception
      */
-    WxCpBaseResp editCorpTag(String id, String name, Integer order);
+    WxCpBaseResp editCorpTag(String id, String name, Integer order) throws WxErrorException;
 
     /**
      * <pre>
@@ -484,8 +548,9 @@ public interface WxCpExternalContactService {
      * @param tagId   the tag id
      * @param groupId the group id
      * @return wx cp base resp
+     * @throws WxErrorException the wx error exception
      */
-    WxCpBaseResp delCorpTag(String[] tagId, String[] groupId);
+    WxCpBaseResp delCorpTag(String[] tagId, String[] groupId) throws WxErrorException;
 
     /**
      * <pre>
@@ -498,8 +563,10 @@ public interface WxCpExternalContactService {
      * @param addTag         the add tag
      * @param removeTag      the remove tag
      * @return wx cp base resp
+     * @throws WxErrorException the wx error exception
      */
-    WxCpBaseResp markTag(String userid, String externalUserid, String[] addTag, String[] removeTag);
+    WxCpBaseResp markTag(String userid, String externalUserid, String[] addTag, String[] removeTag) throws WxErrorException;
+
 
 
 }

+ 17 - 17
wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/BaseWxCpServiceImpl.java

@@ -44,22 +44,22 @@ import static cn.nosum.wx.cp.constant.WxCpApiPathConsts.*;
 @Data
 public abstract class BaseWxCpServiceImpl<H, P> implements WxCpService, RequestHttp<H, P> {
 
-    private WxCpUserService userService = null;
-    private WxCpChatService chatService = null;
-    private WxCpDepartmentService departmentService = null;
+    private WxCpUserService userService = new WxCpUserServiceImpl(this);
+    private WxCpChatService chatService = new WxCpChatServiceImpl(this);
+    private WxCpDepartmentService departmentService = new WxCpDepartmentServiceImpl(this);
     private WxCpMediaService mediaService = new WxCpMediaServiceImpl(this);
-    private WxCpMenuService menuService = null;
-    private WxCpOAuth2Service oauth2Service = null;
-    private WxCpTagService tagService = null;
-    private WxCpAgentService agentService = null;
-    private WxCpOaService oaService = null;
-    private WxCpTaskCardService taskCardService = null;
-    private WxCpExternalContactService externalContactService = null;
-    private WxCpGroupRobotService groupRobotService = null;
+    private WxCpMenuService menuService = new WxCpMenuServiceImpl(this);
+    private WxCpOAuth2Service oauth2Service = new WxCpOAuth2ServiceImpl(this);
+    private WxCpTagService tagService = new WxCpTagServiceImpl(this);
+    private WxCpAgentService agentService = new WxCpAgentServiceImpl(this);
+    private WxCpOaService oaService = new WxCpOaServiceImpl(this);
+    private WxCpTaskCardService taskCardService = new WxCpTaskCardServiceImpl(this);
+    private WxCpExternalContactService externalContactService = new WxCpExternalContactServiceImpl(this);
+    private WxCpGroupRobotService groupRobotService = new WxCpGroupRobotServiceImpl(this);
     private WxCpMessageService messageService = new WxCpMessageServiceImpl(this);
-    private WxCpOaCalendarService oaCalendarService = null;
-    private WxCpOaScheduleService oaScheduleService = null;
-    private WxCpAgentWorkBenchService workBenchService = null;
+    private WxCpOaCalendarService oaCalendarService = new WxCpOaCalendarServiceImpl(this);
+    private WxCpOaScheduleService oaScheduleService = new WxCpOaOaScheduleServiceImpl(this);
+    private WxCpAgentWorkBenchService workBenchService = new WxCpAgentWorkBenchServiceImpl(this);
 
     protected WxCpConfigStorage configStorage;
 
@@ -233,17 +233,17 @@ public abstract class BaseWxCpServiceImpl<H, P> implements WxCpService, RequestH
     }
 
     @Override
-    public String post(String url, JsonObject jsonObject) throws WxErrorException {
+    public String post(String url, JsonObject jsonObject) {
         return this.post(url, jsonObject.toString());
     }
 
     @Override
-    public String post(String url, ToJson obj) throws WxErrorException {
+    public String post(String url, ToJson obj) {
         return this.post(url, obj.toJson());
     }
 
     @Override
-    public String post(String url, Object obj) throws WxErrorException {
+    public String post(String url, Object obj) {
         return this.post(url, obj.toString());
     }
 

+ 61 - 0
wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/WxCpAgentServiceImpl.java

@@ -0,0 +1,61 @@
+package cn.nosum.wx.cp.api.impl;
+
+import cn.nosum.wx.common.error.WxRuntimeException;
+import cn.nosum.wx.common.utils.json.GsonParser;
+import cn.nosum.wx.cp.api.WxCpAgentService;
+import cn.nosum.wx.cp.api.WxCpService;
+import cn.nosum.wx.cp.entity.WxCpAgent;
+import cn.nosum.wx.cp.utils.json.WxCpGsonBuilder;
+import com.google.gson.JsonObject;
+import com.google.gson.reflect.TypeToken;
+import lombok.RequiredArgsConstructor;
+
+import java.util.List;
+
+import static cn.nosum.wx.cp.constant.WxCpApiPathConsts.Agent.*;
+
+/**
+ * 管理企业号应用.
+ *
+ * @author Young
+ */
+@RequiredArgsConstructor
+public class WxCpAgentServiceImpl implements WxCpAgentService {
+
+
+    private final WxCpService mainService;
+
+    @Override
+    public WxCpAgent get(Integer agentId) {
+        if (agentId == null) {
+            throw new IllegalArgumentException("缺少agentid参数");
+        }
+
+        final String url = String.format(this.mainService.getWxCpConfigStorage().getApiUrl(AGENT_GET), agentId);
+        return WxCpAgent.fromJson(this.mainService.get(url, null));
+    }
+
+    @Override
+    public void set(WxCpAgent agentInfo) {
+        String url = this.mainService.getWxCpConfigStorage().getApiUrl(AGENT_SET);
+        String responseContent = this.mainService.post(url, agentInfo.toJson());
+        JsonObject jsonObject = GsonParser.parse(responseContent);
+        if (jsonObject.get("errcode").getAsInt() != 0) {
+            throw new WxRuntimeException(responseContent);
+        }
+    }
+
+    @Override
+    public List<WxCpAgent> list() {
+        String url = this.mainService.getWxCpConfigStorage().getApiUrl(AGENT_LIST);
+        String responseContent = this.mainService.get(url, null);
+        JsonObject jsonObject = GsonParser.parse(responseContent);
+        if (jsonObject.get("errcode").getAsInt() != 0) {
+            throw new WxRuntimeException(responseContent);
+        }
+
+        return WxCpGsonBuilder.create().fromJson(jsonObject.get("agentlist").toString(), new TypeToken<List<WxCpAgent>>() {
+        }.getType());
+    }
+
+}

+ 39 - 0
wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/WxCpAgentWorkBenchServiceImpl.java

@@ -0,0 +1,39 @@
+package cn.nosum.wx.cp.api.impl;
+
+import cn.nosum.wx.cp.api.WxCpAgentWorkBenchService;
+import cn.nosum.wx.cp.api.WxCpService;
+import cn.nosum.wx.cp.entity.WxCpAgentWorkBench;
+import com.google.gson.JsonObject;
+import lombok.RequiredArgsConstructor;
+
+import static cn.nosum.wx.cp.constant.WxCpApiPathConsts.WorkBench.WORKBENCH_DATA_SET;
+import static cn.nosum.wx.cp.constant.WxCpApiPathConsts.WorkBench.WORKBENCH_TEMPLATE_GET;
+import static cn.nosum.wx.cp.constant.WxCpApiPathConsts.WorkBench.WORKBENCH_TEMPLATE_SET;
+
+/**
+ * 工作台自定义展示实现.
+ *
+ * @author Young
+ */
+@RequiredArgsConstructor
+public class WxCpAgentWorkBenchServiceImpl implements WxCpAgentWorkBenchService {
+
+    private final WxCpService mainService;
+
+    @Override
+    public void setWorkBenchTemplate(WxCpAgentWorkBench wxCpAgentWorkBench) {
+        this.mainService.post(this.mainService.getWxCpConfigStorage().getApiUrl(WORKBENCH_TEMPLATE_SET), wxCpAgentWorkBench.toTemplateString());
+    }
+
+    @Override
+    public String getWorkBenchTemplate(Long agentId) {
+        JsonObject jsonObject = new JsonObject();
+        jsonObject.addProperty("agentid", agentId);
+        return this.mainService.post(this.mainService.getWxCpConfigStorage().getApiUrl(WORKBENCH_TEMPLATE_GET), jsonObject.toString());
+    }
+
+    @Override
+    public void setWorkBenchData(WxCpAgentWorkBench wxCpAgentWorkBench) {
+        this.mainService.post(this.mainService.getWxCpConfigStorage().getApiUrl(WORKBENCH_DATA_SET), wxCpAgentWorkBench.toUserDataString());
+    }
+}

+ 84 - 0
wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/WxCpChatServiceImpl.java

@@ -0,0 +1,84 @@
+package cn.nosum.wx.cp.api.impl;
+
+import cn.nosum.wx.common.utils.json.GsonParser;
+import cn.nosum.wx.common.utils.json.WxGsonBuilder;
+import cn.nosum.wx.cp.api.WxCpChatService;
+import cn.nosum.wx.cp.api.WxCpService;
+import cn.nosum.wx.cp.entity.WxCpChat;
+import cn.nosum.wx.cp.entity.message.WxCpAppChatMessage;
+import cn.nosum.wx.cp.utils.json.WxCpGsonBuilder;
+import lombok.RequiredArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static cn.nosum.wx.cp.constant.WxCpApiPathConsts.Chat.*;
+
+/**
+ * 群聊服务实现.
+ *
+ * @author Young
+ */
+@RequiredArgsConstructor
+public class WxCpChatServiceImpl implements WxCpChatService {
+    private final WxCpService cpService;
+
+    @Override
+    public String create(String name, String owner, List<String> users, String chatId) {
+        Map<String, Object> data = new HashMap<>(4);
+        if (StringUtils.isNotBlank(name)) {
+            data.put("name", name);
+        }
+        if (StringUtils.isNotBlank(owner)) {
+            data.put("owner", owner);
+        }
+        if (users != null) {
+            data.put("userlist", users);
+        }
+        if (StringUtils.isNotBlank(chatId)) {
+            data.put("chatid", chatId);
+        }
+        final String url = this.cpService.getWxCpConfigStorage().getApiUrl(APP_CHAT_CREATE);
+        String result = this.cpService.post(url, WxGsonBuilder.create().toJson(data));
+        return GsonParser.parse(result).get("chatid").getAsString();
+    }
+
+    @Override
+    public void update(String chatId, String name, String owner, List<String> usersToAdd, List<String> usersToDelete) {
+        Map<String, Object> data = new HashMap<>(5);
+        if (StringUtils.isNotBlank(chatId)) {
+            data.put("chatid", chatId);
+        }
+        if (StringUtils.isNotBlank(name)) {
+            data.put("name", name);
+        }
+        if (StringUtils.isNotBlank(owner)) {
+            data.put("owner", owner);
+        }
+        if (usersToAdd != null && !usersToAdd.isEmpty()) {
+            data.put("add_user_list", usersToAdd);
+        }
+        if (usersToDelete != null && !usersToDelete.isEmpty()) {
+            data.put("del_user_list", usersToDelete);
+        }
+
+        final String url = this.cpService.getWxCpConfigStorage().getApiUrl(APP_CHAT_UPDATE);
+        this.cpService.post(url, WxGsonBuilder.create().toJson(data));
+    }
+
+    @Override
+    public WxCpChat get(String chatId) {
+        final String url = this.cpService.getWxCpConfigStorage().getApiUrl(APP_CHAT_GET_CHAT_ID + chatId);
+        String result = this.cpService.get(url, null);
+        final String chatInfo = GsonParser.parse(result).getAsJsonObject("chat_info").toString();
+        return WxCpGsonBuilder.create().fromJson(chatInfo, WxCpChat.class);
+    }
+
+    @Override
+    public void sendMsg(WxCpAppChatMessage message) {
+        this.cpService.post(this.cpService.getWxCpConfigStorage().getApiUrl(APP_CHAT_SEND), message.toJson());
+    }
+
+}

+ 62 - 0
wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/WxCpDepartmentServiceImpl.java

@@ -0,0 +1,62 @@
+package cn.nosum.wx.cp.api.impl;
+
+import cn.nosum.wx.common.utils.json.GsonHelper;
+import cn.nosum.wx.common.utils.json.GsonParser;
+import cn.nosum.wx.cp.api.WxCpDepartmentService;
+import cn.nosum.wx.cp.api.WxCpService;
+import cn.nosum.wx.cp.entity.WxCpDepart;
+import cn.nosum.wx.cp.utils.json.WxCpGsonBuilder;
+import com.google.gson.JsonObject;
+import com.google.gson.reflect.TypeToken;
+import lombok.RequiredArgsConstructor;
+
+import java.util.List;
+
+import static cn.nosum.wx.cp.constant.WxCpApiPathConsts.Department.*;
+
+/**
+ * 部门管理服务实现.
+ *
+ * @author Young
+ */
+@RequiredArgsConstructor
+public class WxCpDepartmentServiceImpl implements WxCpDepartmentService {
+
+    private final WxCpService mainService;
+
+    @Override
+    public Long create(WxCpDepart depart) {
+        String url = this.mainService.getWxCpConfigStorage().getApiUrl(DEPARTMENT_CREATE);
+        String responseContent = this.mainService.post(url, depart.toJson());
+        JsonObject tmpJsonObject = GsonParser.parse(responseContent);
+        return GsonHelper.getAsLong(tmpJsonObject.get("id"));
+    }
+
+    @Override
+    public void update(WxCpDepart group) {
+        String url = this.mainService.getWxCpConfigStorage().getApiUrl(DEPARTMENT_UPDATE);
+        this.mainService.post(url, group.toJson());
+    }
+
+    @Override
+    public void delete(Long departId) {
+        String url = String.format(this.mainService.getWxCpConfigStorage().getApiUrl(DEPARTMENT_DELETE), departId);
+        this.mainService.get(url, null);
+    }
+
+    @Override
+    public List<WxCpDepart> list(Long id) {
+        String url = this.mainService.getWxCpConfigStorage().getApiUrl(DEPARTMENT_LIST);
+        if (id != null) {
+            url += "?id=" + id;
+        }
+
+        String responseContent = this.mainService.get(url, null);
+        JsonObject tmpJsonObject = GsonParser.parse(responseContent);
+        return WxCpGsonBuilder.create()
+                .fromJson(tmpJsonObject.get("department"),
+                        new TypeToken<List<WxCpDepart>>() {
+                        }.getType()
+                );
+    }
+}

+ 421 - 0
wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/WxCpExternalContactServiceImpl.java

@@ -0,0 +1,421 @@
+package cn.nosum.wx.cp.api.impl;
+
+import cn.nosum.wx.common.error.WxRuntimeException;
+import cn.nosum.wx.common.utils.json.GsonParser;
+import cn.nosum.wx.cp.api.WxCpExternalContactService;
+import cn.nosum.wx.cp.api.WxCpService;
+import cn.nosum.wx.cp.entity.WxCpBaseResp;
+import cn.nosum.wx.cp.entity.external.*;
+import cn.nosum.wx.cp.entity.external.contact.WxCpExternalContactBatchInfo;
+import cn.nosum.wx.cp.entity.external.contact.WxCpExternalContactInfo;
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Date;
+import java.util.List;
+
+import static cn.nosum.wx.cp.constant.WxCpApiPathConsts.ExternalContact.*;
+
+/**
+ * 外部联系人实现.
+ *
+ * @author Young
+ */
+@RequiredArgsConstructor
+public class WxCpExternalContactServiceImpl implements WxCpExternalContactService {
+
+    private final WxCpService mainService;
+
+    @Override
+    public WxCpContactWayResult addContactWay(@NonNull WxCpContactWayInfo info) {
+
+        if (info.getContactWay().getUsers() != null && info.getContactWay().getUsers().size() > 100) {
+            throw new WxRuntimeException("「联系我」使用人数默认限制不超过100人(包括部门展开后的人数)");
+        }
+
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(ADD_CONTACT_WAY);
+        String responseContent = this.mainService.post(url, info.getContactWay().toJson());
+
+        return WxCpContactWayResult.fromJson(responseContent);
+    }
+
+    @Override
+    public WxCpContactWayInfo getContactWay(@NonNull String configId) {
+        JsonObject json = new JsonObject();
+        json.addProperty("config_id", configId);
+
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_CONTACT_WAY);
+        String responseContent = this.mainService.post(url, json.toString());
+        return WxCpContactWayInfo.fromJson(responseContent);
+    }
+
+    @Override
+    public WxCpBaseResp updateContactWay(@NonNull WxCpContactWayInfo info) {
+        if (StringUtils.isBlank(info.getContactWay().getConfigId())) {
+            throw new WxRuntimeException("更新「联系我」方式需要指定configId");
+        }
+        if (info.getContactWay().getUsers() != null && info.getContactWay().getUsers().size() > 100) {
+            throw new WxRuntimeException("「联系我」使用人数默认限制不超过100人(包括部门展开后的人数)");
+        }
+
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(UPDATE_CONTACT_WAY);
+        String responseContent = this.mainService.post(url, info.getContactWay().toJson());
+
+        return WxCpBaseResp.fromJson(responseContent);
+    }
+
+    @Override
+    public WxCpBaseResp deleteContactWay(@NonNull String configId) {
+        JsonObject json = new JsonObject();
+        json.addProperty("config_id", configId);
+
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(DEL_CONTACT_WAY);
+        String responseContent = this.mainService.post(url, json.toString());
+
+        return WxCpBaseResp.fromJson(responseContent);
+    }
+
+    @Override
+    public WxCpBaseResp closeTempChat(@NonNull String userId, @NonNull String externalUserId) {
+
+        JsonObject json = new JsonObject();
+        json.addProperty("userid", userId);
+        json.addProperty("external_userid", externalUserId);
+
+
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(CLOSE_TEMP_CHAT);
+        String responseContent = this.mainService.post(url, json.toString());
+
+        return WxCpBaseResp.fromJson(responseContent);
+    }
+
+    @Override
+    public WxCpExternalContactInfo getExternalContact(String userId) {
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_EXTERNAL_CONTACT + userId);
+        String responseContent = this.mainService.get(url, null);
+        return WxCpExternalContactInfo.fromJson(responseContent);
+    }
+
+    @Override
+    public WxCpExternalContactInfo getContactDetail(String userId) {
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_CONTACT_DETAIL + userId);
+        String responseContent = this.mainService.get(url, null);
+        return WxCpExternalContactInfo.fromJson(responseContent);
+    }
+
+    @Override
+    public String convertToOpenid(String externalUserId) {
+        JsonObject json = new JsonObject();
+        json.addProperty("external_userid", externalUserId);
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(CONVERT_TO_OPENID);
+        String responseContent = this.mainService.post(url, json.toString());
+        JsonObject tmpJson = GsonParser.parse(responseContent);
+        return tmpJson.get("openid").getAsString();
+    }
+
+    @Override
+    public String unionidToExternalUserid(String unionid) {
+        JsonObject json = new JsonObject();
+        json.addProperty("unionid", unionid);
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(UNIONID_TO_EXTERNAL_USERID);
+        String responseContent = this.mainService.post(url, json.toString());
+        JsonObject tmpJson = GsonParser.parse(responseContent);
+        return tmpJson.get("external_userid").getAsString();
+    }
+
+    @Override
+    public WxCpExternalContactBatchInfo getContactDetailBatch(String[] userIdList,
+                                                              String cursor,
+                                                              Integer limit) {
+        final String url =
+                this.mainService
+                        .getWxCpConfigStorage()
+                        .getApiUrl(GET_CONTACT_DETAIL_BATCH);
+        JsonObject json = new JsonObject();
+        json.add("userid_list", new Gson().toJsonTree(userIdList).getAsJsonArray());
+        if (StringUtils.isNotBlank(cursor)) {
+            json.addProperty("cursor", cursor);
+        }
+        if (limit != null) {
+            json.addProperty("limit", limit);
+        }
+        String responseContent = this.mainService.post(url, json.toString());
+        return WxCpExternalContactBatchInfo.fromJson(responseContent);
+    }
+
+    @Override
+    public void updateRemark(WxCpUpdateRemarkRequest request) {
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(UPDATE_REMARK);
+        this.mainService.post(url, request.toJson());
+    }
+
+    @Override
+    public List<String> listExternalContacts(String userId) {
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(LIST_EXTERNAL_CONTACT + userId);
+        String responseContent = this.mainService.get(url, null);
+        return WxCpUserExternalContactList.fromJson(responseContent).getExternalUserId();
+    }
+
+    @Override
+    public List<String> listFollowers() {
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_FOLLOW_USER_LIST);
+        String responseContent = this.mainService.get(url, null);
+        return WxCpUserWithExternalPermission.fromJson(responseContent).getFollowers();
+    }
+
+    @Override
+    public WxCpUserExternalUnassignList listUnassignedList(Integer pageIndex, Integer pageSize) {
+        JsonObject json = new JsonObject();
+        json.addProperty("page_id", pageIndex == null ? 0 : pageIndex);
+        json.addProperty("page_size", pageSize == null ? 100 : pageSize);
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(LIST_UNASSIGNED_CONTACT);
+        final String result = this.mainService.post(url, json.toString());
+        return WxCpUserExternalUnassignList.fromJson(result);
+    }
+
+    @Override
+    public WxCpBaseResp transferExternalContact(String externalUserid, String handOverUserid, String takeOverUserid) {
+        JsonObject json = new JsonObject();
+        json.addProperty("external_userid", externalUserid);
+        json.addProperty("handover_userid", handOverUserid);
+        json.addProperty("takeover_userid", takeOverUserid);
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(TRANSFER_UNASSIGNED_CONTACT);
+        final String result = this.mainService.post(url, json.toString());
+        return WxCpBaseResp.fromJson(result);
+    }
+
+    @Override
+    public WxCpUserTransferCustomerResp transferCustomer(WxCpUserTransferCustomerReq req) {
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(TRANSFER_CUSTOMER);
+        final String result = this.mainService.post(url, req.toJson());
+        return WxCpUserTransferCustomerResp.fromJson(result);
+    }
+
+    @Override
+    public WxCpUserTransferResultResp transferResult(String handOverUserid, String takeOverUserid, String cursor) {
+        JsonObject json = new JsonObject();
+        json.addProperty("cursor", cursor);
+        json.addProperty("handover_userid", handOverUserid);
+        json.addProperty("takeover_userid", takeOverUserid);
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(TRANSFER_RESULT);
+        final String result = this.mainService.post(url, json.toString());
+        return WxCpUserTransferResultResp.fromJson(result);
+    }
+
+    @Override
+    public WxCpUserTransferCustomerResp resignedTransferCustomer(WxCpUserTransferCustomerReq req) {
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(RESIGNED_TRANSFER_CUSTOMER);
+        final String result = this.mainService.post(url, req.toJson());
+        return WxCpUserTransferCustomerResp.fromJson(result);
+    }
+
+    @Override
+    public WxCpUserTransferResultResp resignedTransferResult(String handOverUserid, String takeOverUserid, String cursor) {
+        JsonObject json = new JsonObject();
+        json.addProperty("cursor", cursor);
+        json.addProperty("handover_userid", handOverUserid);
+        json.addProperty("takeover_userid", takeOverUserid);
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(RESIGNED_TRANSFER_RESULT);
+        final String result = this.mainService.post(url, json.toString());
+        return WxCpUserTransferResultResp.fromJson(result);
+    }
+
+    @Override
+    public WxCpUserExternalGroupChatList listGroupChat(Integer pageIndex, Integer pageSize, int status, String[] userIds, String[] partyIds) {
+        JsonObject json = new JsonObject();
+        json.addProperty("offset", pageIndex == null ? 0 : pageIndex);
+        json.addProperty("limit", pageSize == null ? 100 : pageSize);
+        json.addProperty("status_filter", status);
+        if (ArrayUtils.isNotEmpty(userIds) || ArrayUtils.isNotEmpty(partyIds)) {
+            JsonObject ownerFilter = new JsonObject();
+            if (ArrayUtils.isNotEmpty(userIds)) {
+                ownerFilter.add("userid_list", new Gson().toJsonTree(userIds).getAsJsonArray());
+            }
+            if (ArrayUtils.isNotEmpty(partyIds)) {
+                ownerFilter.add("partyid_list", new Gson().toJsonTree(partyIds).getAsJsonArray());
+            }
+            json.add("owner_filter", ownerFilter);
+        }
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GROUP_CHAT_LIST);
+        final String result = this.mainService.post(url, json.toString());
+        return WxCpUserExternalGroupChatList.fromJson(result);
+    }
+
+    @Override
+    public WxCpUserExternalGroupChatList listGroupChat(Integer limit, String cursor, int status, String[] userIds) {
+        JsonObject json = new JsonObject();
+        json.addProperty("cursor", cursor == null ? "" : cursor);
+        json.addProperty("limit", limit == null ? 100 : limit);
+        json.addProperty("status_filter", status);
+        if (ArrayUtils.isNotEmpty(userIds)) {
+            JsonObject ownerFilter = new JsonObject();
+            if (ArrayUtils.isNotEmpty(userIds)) {
+                ownerFilter.add("userid_list", new Gson().toJsonTree(userIds).getAsJsonArray());
+            }
+            json.add("owner_filter", ownerFilter);
+        }
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GROUP_CHAT_LIST);
+        final String result = this.mainService.post(url, json.toString());
+        return WxCpUserExternalGroupChatList.fromJson(result);
+    }
+
+    @Override
+    public WxCpUserExternalGroupChatInfo getGroupChat(String chatId, Integer needName) {
+        JsonObject json = new JsonObject();
+        json.addProperty("chat_id", chatId);
+        json.addProperty("need_name", needName);
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GROUP_CHAT_INFO);
+        final String result = this.mainService.post(url, json.toString());
+        return WxCpUserExternalGroupChatInfo.fromJson(result);
+    }
+
+    @Override
+    public WxCpUserExternalGroupChatTransferResp transferGroupChat(String[] chatIds, String newOwner) {
+        JsonObject json = new JsonObject();
+        if (ArrayUtils.isNotEmpty(chatIds)) {
+            json.add("chat_id_list", new Gson().toJsonTree(chatIds).getAsJsonArray());
+        }
+        json.addProperty("new_owner", newOwner);
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GROUP_CHAT_TRANSFER);
+        final String result = this.mainService.post(url, json.toString());
+        return WxCpUserExternalGroupChatTransferResp.fromJson(result);
+    }
+
+    @Override
+    public WxCpUserExternalUserBehaviorStatistic getUserBehaviorStatistic(Date startTime, Date endTime, String[] userIds, String[] partyIds) {
+        JsonObject json = new JsonObject();
+        json.addProperty("start_time", startTime.getTime() / 1000);
+        json.addProperty("end_time", endTime.getTime() / 1000);
+        if (ArrayUtils.isNotEmpty(userIds) || ArrayUtils.isNotEmpty(partyIds)) {
+            if (ArrayUtils.isNotEmpty(userIds)) {
+                json.add("userid", new Gson().toJsonTree(userIds).getAsJsonArray());
+            }
+            if (ArrayUtils.isNotEmpty(partyIds)) {
+                json.add("partyid", new Gson().toJsonTree(partyIds).getAsJsonArray());
+            }
+        }
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(LIST_USER_BEHAVIOR_DATA);
+        final String result = this.mainService.post(url, json.toString());
+        return WxCpUserExternalUserBehaviorStatistic.fromJson(result);
+    }
+
+    @Override
+    public WxCpUserExternalGroupChatStatistic getGroupChatStatistic(Date startTime, Integer orderBy, Integer orderAsc, Integer pageIndex, Integer pageSize, String[] userIds, String[] partyIds) {
+        JsonObject json = new JsonObject();
+        json.addProperty("day_begin_time", startTime.getTime() / 1000);
+        json.addProperty("order_by", orderBy == null ? 1 : orderBy);
+        json.addProperty("order_asc", orderAsc == null ? 0 : orderAsc);
+        json.addProperty("offset", pageIndex == null ? 0 : pageIndex);
+        json.addProperty("limit", pageSize == null ? 500 : pageSize);
+        if (ArrayUtils.isNotEmpty(userIds) || ArrayUtils.isNotEmpty(partyIds)) {
+            JsonObject ownerFilter = new JsonObject();
+            if (ArrayUtils.isNotEmpty(userIds)) {
+                ownerFilter.add("userid_list", new Gson().toJsonTree(userIds).getAsJsonArray());
+            }
+            if (ArrayUtils.isNotEmpty(partyIds)) {
+                ownerFilter.add("partyid_list", new Gson().toJsonTree(partyIds).getAsJsonArray());
+            }
+            json.add("owner_filter", ownerFilter);
+        }
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(LIST_GROUP_CHAT_DATA);
+        final String result = this.mainService.post(url, json.toString());
+        return WxCpUserExternalGroupChatStatistic.fromJson(result);
+    }
+
+    @Override
+    public WxCpMsgTemplateAddResult addMsgTemplate(WxCpMsgTemplate wxCpMsgTemplate) {
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(ADD_MSG_TEMPLATE);
+        final String result = this.mainService.post(url, wxCpMsgTemplate.toJson());
+        return WxCpMsgTemplateAddResult.fromJson(result);
+    }
+
+    @Override
+    public void sendWelcomeMsg(WxCpWelcomeMsg msg) {
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(SEND_WELCOME_MSG);
+        this.mainService.post(url, msg.toJson());
+    }
+
+    @Override
+    public WxCpUserExternalTagGroupList getCorpTagList(String[] tagId) {
+        JsonObject json = new JsonObject();
+        if (ArrayUtils.isNotEmpty(tagId)) {
+            json.add("tag_id", new Gson().toJsonTree(tagId).getAsJsonArray());
+        }
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_CORP_TAG_LIST);
+        final String result = this.mainService.post(url, json.toString());
+        return WxCpUserExternalTagGroupList.fromJson(result);
+    }
+
+    @Override
+    public WxCpUserExternalTagGroupList getCorpTagList(String[] tagId, String[] groupId) {
+        JsonObject json = new JsonObject();
+        if (ArrayUtils.isNotEmpty(tagId)) {
+            json.add("tag_id", new Gson().toJsonTree(tagId).getAsJsonArray());
+        }
+        if (ArrayUtils.isNotEmpty(groupId)) {
+            json.add("group_id", new Gson().toJsonTree(groupId).getAsJsonArray());
+        }
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_CORP_TAG_LIST);
+        final String result = this.mainService.post(url, json.toString());
+        return WxCpUserExternalTagGroupList.fromJson(result);
+    }
+
+    @Override
+    public WxCpUserExternalTagGroupInfo addCorpTag(WxCpUserExternalTagGroupInfo tagGroup) {
+
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(ADD_CORP_TAG);
+        final String result = this.mainService.post(url, tagGroup.getTagGroup().toJson());
+        return WxCpUserExternalTagGroupInfo.fromJson(result);
+    }
+
+    @Override
+    public WxCpBaseResp editCorpTag(String id, String name, Integer order) {
+
+        JsonObject json = new JsonObject();
+        json.addProperty("id", id);
+        json.addProperty("name", name);
+        json.addProperty("order", order);
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(EDIT_CORP_TAG);
+        final String result = this.mainService.post(url, json.toString());
+        return WxCpBaseResp.fromJson(result);
+    }
+
+    @Override
+    public WxCpBaseResp delCorpTag(String[] tagId, String[] groupId) {
+        JsonObject json = new JsonObject();
+        if (ArrayUtils.isNotEmpty(tagId)) {
+            json.add("tag_id", new Gson().toJsonTree(tagId).getAsJsonArray());
+        }
+        if (ArrayUtils.isNotEmpty(groupId)) {
+            json.add("group_id", new Gson().toJsonTree(groupId).getAsJsonArray());
+        }
+
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(DEL_CORP_TAG);
+        final String result = this.mainService.post(url, json.toString());
+        return WxCpBaseResp.fromJson(result);
+    }
+
+    @Override
+    public WxCpBaseResp markTag(String userid, String externalUserid, String[] addTag, String[] removeTag) {
+
+
+        JsonObject json = new JsonObject();
+        json.addProperty("userid", userid);
+        json.addProperty("external_userid", externalUserid);
+
+        if (ArrayUtils.isNotEmpty(addTag)) {
+            json.add("add_tag", new Gson().toJsonTree(addTag).getAsJsonArray());
+        }
+        if (ArrayUtils.isNotEmpty(removeTag)) {
+            json.add("remove_tag", new Gson().toJsonTree(removeTag).getAsJsonArray());
+        }
+
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(MARK_TAG);
+        final String result = this.mainService.post(url, json.toString());
+        return WxCpBaseResp.fromJson(result);
+    }
+}

+ 100 - 0
wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/WxCpGroupRobotServiceImpl.java

@@ -0,0 +1,100 @@
+package cn.nosum.wx.cp.api.impl;
+
+import cn.nosum.wx.common.error.WxErrorException;
+import cn.nosum.wx.common.error.WxRuntimeException;
+import cn.nosum.wx.cp.api.WxCpGroupRobotService;
+import cn.nosum.wx.cp.api.WxCpService;
+import cn.nosum.wx.cp.config.WxCpConfigStorage;
+import cn.nosum.wx.cp.constant.WxCpApiPathConsts;
+import cn.nosum.wx.cp.entity.article.NewArticle;
+import cn.nosum.wx.cp.entity.message.WxCpGroupRobotMessage;
+import lombok.RequiredArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.List;
+
+import static cn.nosum.wx.cp.constant.WxCpConsts.GroupRobotMsgType;
+import static cn.nosum.wx.cp.constant.WxCpConsts.GroupRobotMsgType.MARKDOWN;
+import static cn.nosum.wx.cp.constant.WxCpConsts.GroupRobotMsgType.TEXT;
+
+/**
+ * 企业微信群机器人消息发送api 实现.
+ *
+ * @author Young
+ */
+@RequiredArgsConstructor
+public class WxCpGroupRobotServiceImpl implements WxCpGroupRobotService {
+
+    private final WxCpService cpService;
+
+    private String getWebhookUrl() {
+        WxCpConfigStorage wxCpConfigStorage = this.cpService.getWxCpConfigStorage();
+        final String webhookKey = wxCpConfigStorage.getWebhookKey();
+        if (StringUtils.isEmpty(webhookKey)) {
+            throw new WxRuntimeException("请先设置WebhookKey");
+        }
+        return wxCpConfigStorage.getApiUrl(WxCpApiPathConsts.WEBHOOK_SEND) + webhookKey;
+    }
+
+    @Override
+    public void sendText(String content, List<String> mentionedList, List<String> mobileList) {
+        this.sendText(this.getWebhookUrl(), content, mentionedList, mobileList);
+    }
+
+    @Override
+    public void sendMarkdown(String content) {
+        this.sendMarkdown(this.getWebhookUrl(), content);
+    }
+
+    @Override
+    public void sendImage(String base64, String md5) {
+        this.sendImage(this.getWebhookUrl(), base64, md5);
+    }
+
+    @Override
+    public void sendNews(List<NewArticle> articleList) {
+        this.sendNews(this.getWebhookUrl(), articleList);
+    }
+
+    @Override
+    public void sendText(String webhookUrl, String content, List<String> mentionedList, List<String> mobileList) {
+        try {
+            this.cpService.postWithoutToken(webhookUrl, new WxCpGroupRobotMessage()
+                    .setMsgType(TEXT)
+                    .setContent(content)
+                    .setMentionedList(mentionedList)
+                    .setMentionedMobileList(mobileList)
+                    .toJson());
+        } catch (WxErrorException e) {
+            throw new WxRuntimeException(e.getMessage());
+        }
+    }
+
+    @Override
+    public void sendMarkdown(String webhookUrl, String content) {
+        try {
+            this.cpService.postWithoutToken(webhookUrl, new WxCpGroupRobotMessage().setMsgType(MARKDOWN).setContent(content).toJson());
+        } catch (WxErrorException e) {
+            throw new WxRuntimeException(e.getMessage());
+        }
+    }
+
+    @Override
+    public void sendImage(String webhookUrl, String base64, String md5) {
+        try {
+            this.cpService.postWithoutToken(webhookUrl, new WxCpGroupRobotMessage().setMsgType(GroupRobotMsgType.IMAGE).setBase64(base64).setMd5(md5).toJson());
+        } catch (WxErrorException e) {
+            throw new WxRuntimeException(e.getMessage());
+        }
+    }
+
+    @Override
+    public void sendNews(String webhookUrl, List<NewArticle> articleList) {
+        try {
+            this.cpService.postWithoutToken(webhookUrl, new WxCpGroupRobotMessage().setMsgType(GroupRobotMsgType.NEWS).setArticles(articleList).toJson());
+        } catch (WxErrorException e) {
+            throw new WxRuntimeException(e.getMessage());
+        }
+    }
+
+}

+ 0 - 2
wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/WxCpMediaServiceImpl.java

@@ -11,8 +11,6 @@ import cn.nosum.wx.cp.api.WxCpMediaService;
 import cn.nosum.wx.cp.api.WxCpService;
 import lombok.RequiredArgsConstructor;
 
-import java.io.OutputStream;
-
 import static cn.nosum.wx.cp.constant.WxCpApiPathConsts.Media.*;
 
 

+ 53 - 0
wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/WxCpMenuServiceImpl.java

@@ -0,0 +1,53 @@
+package cn.nosum.wx.cp.api.impl;
+
+import cn.nosum.wx.common.entity.menu.WxMenu;
+import cn.nosum.wx.cp.api.WxCpMenuService;
+import cn.nosum.wx.cp.api.WxCpService;
+import cn.nosum.wx.cp.utils.json.WxCpGsonBuilder;
+import lombok.RequiredArgsConstructor;
+
+import static cn.nosum.wx.cp.constant.WxCpApiPathConsts.Menu.*;
+
+/**
+ * 菜单管理相关接口.
+ *
+ * @author Young
+ */
+@RequiredArgsConstructor
+public class WxCpMenuServiceImpl implements WxCpMenuService {
+
+    private final WxCpService mainService;
+
+    @Override
+    public void create(WxMenu menu) {
+        this.create(this.mainService.getWxCpConfigStorage().getAgentId(), menu);
+    }
+
+    @Override
+    public void create(Integer agentId, WxMenu menu) {
+        String url = String.format(this.mainService.getWxCpConfigStorage().getApiUrl(MENU_CREATE), agentId);
+        this.mainService.post(url, menu.toJson());
+    }
+
+    @Override
+    public void delete() {
+        this.delete(this.mainService.getWxCpConfigStorage().getAgentId());
+    }
+
+    @Override
+    public void delete(Integer agentId) {
+        String url = String.format(this.mainService.getWxCpConfigStorage().getApiUrl(MENU_DELETE), agentId);
+        this.mainService.get(url, null);
+    }
+
+    @Override
+    public WxMenu get() {
+        return this.get(this.mainService.getWxCpConfigStorage().getAgentId());
+    }
+
+    @Override
+    public WxMenu get(Integer agentId) {
+        String url = String.format(this.mainService.getWxCpConfigStorage().getApiUrl(MENU_GET), agentId);
+        return WxCpGsonBuilder.create().fromJson(this.mainService.get(url, null), WxMenu.class);
+    }
+}

+ 87 - 0
wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/WxCpOAuth2ServiceImpl.java

@@ -0,0 +1,87 @@
+package cn.nosum.wx.cp.api.impl;
+
+import cn.nosum.http.utils.UriUtil;
+import cn.nosum.wx.common.utils.json.GsonHelper;
+import cn.nosum.wx.common.utils.json.GsonParser;
+import cn.nosum.wx.cp.api.WxCpOAuth2Service;
+import cn.nosum.wx.cp.api.WxCpService;
+import cn.nosum.wx.cp.entity.WxCpOauth2UserInfo;
+import cn.nosum.wx.cp.entity.WxCpUserDetail;
+import cn.nosum.wx.cp.utils.json.WxCpGsonBuilder;
+import com.google.gson.JsonObject;
+import lombok.RequiredArgsConstructor;
+
+import static cn.nosum.wx.cp.constant.WxCpApiPathConsts.OAuth2.*;
+import static cn.nosum.wx.common.api.WxConsts.OAuth2Scope.*;
+
+/**
+ * oauth2 接口实现类.
+ *
+ * @author Young
+ */
+@RequiredArgsConstructor
+public class WxCpOAuth2ServiceImpl implements WxCpOAuth2Service {
+
+    private final WxCpService mainService;
+
+    @Override
+    public String buildAuthorizationUrl(String state) {
+        return this.buildAuthorizationUrl(
+                this.mainService.getWxCpConfigStorage().getOauth2redirectUri(),
+                state
+        );
+    }
+
+    @Override
+    public String buildAuthorizationUrl(String redirectUri, String state) {
+        return this.buildAuthorizationUrl(redirectUri, state, SNSAPI_BASE);
+    }
+
+    @Override
+    public String buildAuthorizationUrl(String redirectUri, String state, String scope) {
+        StringBuilder url = new StringBuilder(URL_OAUTH2_AUTHORIZE);
+        url.append("?appid=").append(this.mainService.getWxCpConfigStorage().getCorpId());
+        url.append("&redirect_uri=").append(UriUtil.encodeUriComponent(redirectUri));
+        url.append("&response_type=code");
+        url.append("&scope=").append(scope);
+
+        if (SNSAPI_PRIVATEINFO.equals(scope) || SNSAPI_USERINFO.equals(scope)) {
+            url.append("&agentid=").append(this.mainService.getWxCpConfigStorage().getAgentId());
+        }
+
+        if (state != null) {
+            url.append("&state=").append(state);
+        }
+
+        url.append("#wechat_redirect");
+        return url.toString();
+    }
+
+    @Override
+    public WxCpOauth2UserInfo getUserInfo(String code) {
+        return this.getUserInfo(this.mainService.getWxCpConfigStorage().getAgentId(), code);
+    }
+
+    @Override
+    public WxCpOauth2UserInfo getUserInfo(Integer agentId, String code) {
+        String responseText = this.mainService.get(String.format(this.mainService.getWxCpConfigStorage().getApiUrl(GET_USER_INFO), code, agentId), null);
+        JsonObject jo = GsonParser.parse(responseText);
+
+        return WxCpOauth2UserInfo.builder()
+                .userId(GsonHelper.getString(jo, "UserId"))
+                .deviceId(GsonHelper.getString(jo, "DeviceId"))
+                .openId(GsonHelper.getString(jo, "OpenId"))
+                .userTicket(GsonHelper.getString(jo, "user_ticket"))
+                .expiresIn(GsonHelper.getString(jo, "expires_in"))
+                .externalUserId(GsonHelper.getString(jo, "external_userid"))
+                .build();
+    }
+
+    @Override
+    public WxCpUserDetail getUserDetail(String userTicket) {
+        JsonObject param = new JsonObject();
+        param.addProperty("user_ticket", userTicket);
+        String responseText = this.mainService.post(this.mainService.getWxCpConfigStorage().getApiUrl(GET_USER_DETAIL), param.toString());
+        return WxCpGsonBuilder.create().fromJson(responseText, WxCpUserDetail.class);
+    }
+}

+ 48 - 0
wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/WxCpOaCalendarServiceImpl.java

@@ -0,0 +1,48 @@
+package cn.nosum.wx.cp.api.impl;
+
+import cn.nosum.wx.common.utils.json.GsonHelper;
+import cn.nosum.wx.common.utils.json.GsonParser;
+import cn.nosum.wx.cp.api.WxCpOaCalendarService;
+import cn.nosum.wx.cp.api.WxCpService;
+import cn.nosum.wx.cp.entity.oa.calendar.WxCpOaCalendar;
+import cn.nosum.wx.cp.utils.json.WxCpGsonBuilder;
+import com.google.gson.reflect.TypeToken;
+import lombok.RequiredArgsConstructor;
+
+import java.util.List;
+
+import static cn.nosum.wx.cp.constant.WxCpApiPathConsts.Oa.*;
+
+/**
+ * 企业微信日历实现.
+ *
+ * @author Young
+ */
+@RequiredArgsConstructor
+public class WxCpOaCalendarServiceImpl implements WxCpOaCalendarService {
+    private final WxCpService wxCpService;
+
+    @Override
+    public String add(WxCpOaCalendar calendar) {
+        return this.wxCpService.post(this.wxCpService.getWxCpConfigStorage().getApiUrl(CALENDAR_ADD), calendar);
+    }
+
+    @Override
+    public void update(WxCpOaCalendar calendar) {
+        this.wxCpService.post(this.wxCpService.getWxCpConfigStorage().getApiUrl(CALENDAR_UPDATE), calendar);
+    }
+
+    @Override
+    public List<WxCpOaCalendar> get(List<String> calIds) {
+        String response = this.wxCpService.post(this.wxCpService.getWxCpConfigStorage().getApiUrl(CALENDAR_GET),
+                GsonHelper.buildJsonObject("cal_id_list", calIds));
+        return WxCpGsonBuilder.create().fromJson(GsonParser.parse(response).get("calendar_list").getAsJsonArray().toString(),
+                new TypeToken<List<WxCpOaCalendar>>() {
+                }.getType());
+    }
+
+    @Override
+    public void delete(String calId) {
+        this.wxCpService.post(this.wxCpService.getWxCpConfigStorage().getApiUrl(CALENDAR_DEL), GsonHelper.buildJsonObject("cal_id", calId));
+    }
+}

+ 80 - 0
wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/WxCpOaOaScheduleServiceImpl.java

@@ -0,0 +1,80 @@
+package cn.nosum.wx.cp.api.impl;
+
+import cn.nosum.wx.common.utils.json.GsonParser;
+import cn.nosum.wx.cp.api.WxCpOaScheduleService;
+import cn.nosum.wx.cp.api.WxCpService;
+import cn.nosum.wx.cp.entity.oa.WxCpOaSchedule;
+import cn.nosum.wx.cp.utils.json.WxCpGsonBuilder;
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.reflect.TypeToken;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static cn.nosum.wx.cp.constant.WxCpApiPathConsts.Oa.*;
+
+/**
+ * 企业微信日程接口实现类.
+ *
+ * @author Young
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class WxCpOaOaScheduleServiceImpl implements WxCpOaScheduleService {
+    private final WxCpService cpService;
+
+    @Override
+    public String add(WxCpOaSchedule schedule, Integer agentId) {
+        Map<String, Serializable> param;
+        if (agentId == null) {
+            param = ImmutableMap.of("schedule", schedule);
+        } else {
+            param = ImmutableMap.of("schedule", schedule, "agentid", agentId);
+        }
+
+        return this.cpService.post(this.cpService.getWxCpConfigStorage().getApiUrl(SCHEDULE_ADD),
+                WxCpGsonBuilder.create().toJson(param));
+    }
+
+    @Override
+    public void update(WxCpOaSchedule schedule) {
+        this.cpService.post(this.cpService.getWxCpConfigStorage().getApiUrl(SCHEDULE_UPDATE),
+                WxCpGsonBuilder.create().toJson(ImmutableMap.of("schedule", schedule)));
+    }
+
+    @Override
+    public List<WxCpOaSchedule> getDetails(List<String> scheduleIds) {
+        final String response = this.cpService.post(this.cpService.getWxCpConfigStorage().getApiUrl(SCHEDULE_GET),
+                WxCpGsonBuilder.create().toJson(ImmutableMap.of("schedule_id_list", scheduleIds)));
+        return WxCpGsonBuilder.create().fromJson(GsonParser.parse(response).get("schedule_list"),
+                new TypeToken<List<WxCpOaSchedule>>() {
+                }.getType());
+    }
+
+    @Override
+    public void delete(String scheduleId) {
+        this.cpService.post(this.cpService.getWxCpConfigStorage().getApiUrl(SCHEDULE_DEL),
+                WxCpGsonBuilder.create().toJson(ImmutableMap.of("schedule_id", scheduleId)));
+    }
+
+    @Override
+    public List<WxCpOaSchedule> listByCalendar(String calId, Integer offset, Integer limit) {
+        final Map<String, Object> param = new HashMap<>(3);
+        param.put("cal_id", calId);
+        if (offset != null) {
+            param.put("offset", offset);
+        }
+        if (limit != null) {
+            param.put("limit", limit);
+        }
+        final String response = this.cpService.post(this.cpService.getWxCpConfigStorage().getApiUrl(SCHEDULE_LIST),
+                WxCpGsonBuilder.create().toJson(param));
+        return WxCpGsonBuilder.create().fromJson(GsonParser.parse(response).get("schedule_list"),
+                new TypeToken<List<WxCpOaSchedule>>() {
+                }.getType());
+    }
+}

+ 297 - 0
wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/WxCpOaServiceImpl.java

@@ -0,0 +1,297 @@
+package cn.nosum.wx.cp.api.impl;
+
+import cn.nosum.wx.common.error.WxRuntimeException;
+import cn.nosum.wx.common.utils.json.GsonParser;
+import cn.nosum.wx.cp.api.WxCpOaService;
+import cn.nosum.wx.cp.api.WxCpService;
+import cn.nosum.wx.cp.entity.oa.*;
+import cn.nosum.wx.cp.utils.json.WxCpGsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.reflect.TypeToken;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Date;
+import java.util.List;
+
+import static cn.nosum.wx.cp.constant.WxCpApiPathConsts.Oa.*;
+
+/**
+ * 企业微信 OA 接口实现.
+ *
+ * @author Young
+ */
+@RequiredArgsConstructor
+public class WxCpOaServiceImpl implements WxCpOaService {
+    private final WxCpService mainService;
+
+    private static final int MONTH_SECONDS = 30 * 24 * 60 * 60;
+    private static final int USER_IDS_LIMIT = 100;
+
+    @Override
+    public String apply(WxCpOaApplyEventRequest request) {
+        String responseContent = this.mainService.post(this.mainService.getWxCpConfigStorage().getApiUrl(APPLY_EVENT), request.toJson());
+        return GsonParser.parse(responseContent).get("sp_no").getAsString();
+    }
+
+    @Override
+    public List<WxCpCheckinData> getCheckinData(Integer openCheckinDataType, @NonNull Date startTime, @NonNull Date endTime, List<String> userIdList) {
+        if (userIdList == null || userIdList.size() > USER_IDS_LIMIT) {
+            throw new WxRuntimeException("用户列表不能为空,不超过 " + USER_IDS_LIMIT + " 个,若用户超过 " + USER_IDS_LIMIT + " 个,请分批获取");
+        }
+
+        long endTimestamp = endTime.getTime() / 1000L;
+        long startTimestamp = startTime.getTime() / 1000L;
+
+        if (endTimestamp - startTimestamp < 0 || endTimestamp - startTimestamp >= MONTH_SECONDS) {
+            throw new WxRuntimeException("获取记录时间跨度不超过一个月");
+        }
+
+        JsonObject jsonObject = new JsonObject();
+        JsonArray jsonArray = new JsonArray();
+
+        jsonObject.addProperty("opencheckindatatype", openCheckinDataType);
+        jsonObject.addProperty("starttime", startTimestamp);
+        jsonObject.addProperty("endtime", endTimestamp);
+
+        for (String userid : userIdList) {
+            jsonArray.add(userid);
+        }
+
+        jsonObject.add("useridlist", jsonArray);
+
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_CHECK_IN_DATA);
+        String responseContent = this.mainService.post(url, jsonObject.toString());
+        JsonObject tmpJson = GsonParser.parse(responseContent);
+        return WxCpGsonBuilder.create().fromJson(tmpJson.get("checkindata"), new TypeToken<List<WxCpCheckinData>>() {}.getType()
+        );
+    }
+
+    @Override
+    public List<WxCpCheckinOption> getCheckinOption(@NonNull Date datetime, List<String> userIdList) {
+        if (userIdList == null || userIdList.size() > USER_IDS_LIMIT) {
+            throw new WxRuntimeException("用户列表不能为空,不超过 " + USER_IDS_LIMIT + " 个,若用户超过 " + USER_IDS_LIMIT + " 个,请分批获取");
+        }
+
+        JsonArray jsonArray = new JsonArray();
+        for (String userid : userIdList) {
+            jsonArray.add(userid);
+        }
+
+        JsonObject jsonObject = new JsonObject();
+        jsonObject.addProperty("datetime", datetime.getTime() / 1000L);
+        jsonObject.add("useridlist", jsonArray);
+
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_CHECK_IN_OPTION);
+        String responseContent = this.mainService.post(url, jsonObject.toString());
+        JsonObject tmpJson = GsonParser.parse(responseContent);
+
+        return WxCpGsonBuilder.create().fromJson(tmpJson.get("info"),
+                new TypeToken<List<WxCpCheckinOption>>() {}.getType()
+        );
+    }
+
+    @Override
+    public List<WxCpCropCheckinOption> getCropCheckinOption() {
+        JsonObject jsonObject = new JsonObject();
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_CORP_CHECK_IN_OPTION);
+        String responseContent = this.mainService.post(url, jsonObject.toString());
+        JsonObject tmpJson = GsonParser.parse(responseContent);
+
+        return WxCpGsonBuilder.create().fromJson(tmpJson.get("group"),
+                new TypeToken<List<WxCpCropCheckinOption>>() {}.getType()
+        );
+    }
+
+    @Override
+    public WxCpApprovalInfo getApprovalInfo(@NonNull Date startTime, @NonNull Date endTime,
+                                            Integer cursor, Integer size, List<WxCpApprovalInfoQueryFilter> filters) {
+        if (cursor == null) {
+            cursor = 0;
+        }
+
+        if (size == null) {
+            size = 100;
+        }
+
+        if (size < 0 || size > 100) {
+            throw new IllegalArgumentException("size参数错误,请使用[1-100]填充,默认100");
+        }
+
+        JsonObject jsonObject = new JsonObject();
+        jsonObject.addProperty("starttime", startTime.getTime() / 1000L);
+        jsonObject.addProperty("endtime", endTime.getTime() / 1000L);
+        jsonObject.addProperty("size", size);
+        jsonObject.addProperty("cursor", cursor);
+
+        if (filters != null && !filters.isEmpty()) {
+            JsonArray filterJsonArray = new JsonArray();
+            for (WxCpApprovalInfoQueryFilter filter : filters) {
+                filterJsonArray.add(new JsonParser().parse(filter.toJson()));
+            }
+            jsonObject.add("filters", filterJsonArray);
+        }
+
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_APPROVAL_INFO);
+        String responseContent = this.mainService.post(url, jsonObject.toString());
+
+        return WxCpGsonBuilder.create().fromJson(responseContent, WxCpApprovalInfo.class);
+    }
+
+    @Override
+    public WxCpApprovalInfo getApprovalInfo(@NonNull Date startTime, @NonNull Date endTime) {
+        return this.getApprovalInfo(startTime, endTime, null, null, null);
+    }
+
+    @Override
+    public WxCpApprovalDetailResult getApprovalDetail(@NonNull String spNo) {
+        JsonObject jsonObject = new JsonObject();
+        jsonObject.addProperty("sp_no", spNo);
+
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_APPROVAL_DETAIL);
+        String responseContent = this.mainService.post(url, jsonObject.toString());
+
+        return WxCpGsonBuilder.create().fromJson(responseContent, WxCpApprovalDetailResult.class);
+    }
+
+    @Override
+    public List<WxCpDialRecord> getDialRecord(Date startTime, Date endTime, Integer offset, Integer limit) {
+        JsonObject jsonObject = new JsonObject();
+
+        if (offset == null) {
+            offset = 0;
+        }
+
+        if (limit == null || limit <= 0) {
+            limit = 100;
+        }
+
+        jsonObject.addProperty("offset", offset);
+        jsonObject.addProperty("limit", limit);
+
+        if (startTime != null && endTime != null) {
+            long endtimestamp = endTime.getTime() / 1000L;
+            long starttimestamp = startTime.getTime() / 1000L;
+
+            if (endtimestamp - starttimestamp < 0 || endtimestamp - starttimestamp >= MONTH_SECONDS) {
+                throw new WxRuntimeException("受限于网络传输,起止时间的最大跨度为30天,如超过30天,则以结束时间为基准向前取30天进行查询");
+            }
+
+            jsonObject.addProperty("start_time", starttimestamp);
+            jsonObject.addProperty("end_time", endtimestamp);
+        }
+
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_DIAL_RECORD);
+        String responseContent = this.mainService.post(url, jsonObject.toString());
+        JsonObject tmpJson = GsonParser.parse(responseContent);
+
+        return WxCpGsonBuilder.create().fromJson(tmpJson.get("record"),
+                new TypeToken<List<WxCpDialRecord>>() {
+                }.getType()
+        );
+    }
+
+    @Override
+    public WxCpTemplateResult getTemplateDetail(@NonNull String templateId) {
+        JsonObject jsonObject = new JsonObject();
+        jsonObject.addProperty("template_id", templateId);
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_TEMPLATE_DETAIL);
+        String responseContent = this.mainService.post(url, jsonObject.toString());
+        return WxCpGsonBuilder.create().fromJson(responseContent, WxCpTemplateResult.class);
+    }
+
+    @Override
+    public List<WxCpCheckinDayData> getCheckinDayData(@NonNull Date startTime, @NonNull Date endTime, List<String> userIdList) {
+        if (userIdList == null || userIdList.size() > USER_IDS_LIMIT) {
+            throw new WxRuntimeException("用户列表不能为空,不超过 " + USER_IDS_LIMIT + " 个,若用户超过 " + USER_IDS_LIMIT + " 个,请分批获取");
+        }
+
+        long endTimestamp = endTime.getTime() / 1000L;
+        long startTimestamp = startTime.getTime() / 1000L;
+
+        JsonObject jsonObject = new JsonObject();
+        JsonArray jsonArray = new JsonArray();
+
+        jsonObject.addProperty("starttime", startTimestamp);
+        jsonObject.addProperty("endtime", endTimestamp);
+
+        for (String userid : userIdList) {
+            jsonArray.add(userid);
+        }
+        jsonObject.add("useridlist", jsonArray);
+
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_CHECK_IN_DAY_DATA);
+        String responseContent = this.mainService.post(url, jsonObject.toString());
+        JsonObject tmpJson = GsonParser.parse(responseContent);
+        return WxCpGsonBuilder.create().fromJson(tmpJson.get("datas"),
+                new TypeToken<List<WxCpCheckinDayData>>() {
+                }.getType()
+        );
+    }
+
+    @Override
+    public List<WxCpCheckinMonthData> getCheckinMonthData(@NonNull Date startTime, @NonNull Date endTime, List<String> userIdList) {
+        if (userIdList == null || userIdList.size() > USER_IDS_LIMIT) {
+            throw new WxRuntimeException("用户列表不能为空,不超过 " + USER_IDS_LIMIT + " 个,若用户超过 " + USER_IDS_LIMIT + " 个,请分批获取");
+        }
+
+        long endTimestamp = endTime.getTime() / 1000L;
+        long startTimestamp = startTime.getTime() / 1000L;
+
+        JsonObject jsonObject = new JsonObject();
+        JsonArray jsonArray = new JsonArray();
+
+        jsonObject.addProperty("starttime", startTimestamp);
+        jsonObject.addProperty("endtime", endTimestamp);
+
+        for (String userid : userIdList) {
+            jsonArray.add(userid);
+        }
+        jsonObject.add("useridlist", jsonArray);
+
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_CHECK_IN_MONTH_DATA);
+        String responseContent = this.mainService.post(url, jsonObject.toString());
+        JsonObject tmpJson = GsonParser.parse(responseContent);
+        return WxCpGsonBuilder.create().fromJson(tmpJson.get("datas"),
+                new TypeToken<List<WxCpCheckinMonthData>>() {
+                }.getType()
+        );
+    }
+
+    @Override
+    public List<WxCpCheckinSchedule> getCheckinScheduleList(@NonNull Date startTime, @NonNull Date endTime, List<String> userIdList) {
+        if (userIdList == null || userIdList.size() > USER_IDS_LIMIT) {
+            throw new WxRuntimeException("用户列表不能为空,不超过 " + USER_IDS_LIMIT + " 个,若用户超过 " + USER_IDS_LIMIT + " 个,请分批获取");
+        }
+
+        long endTimestamp = endTime.getTime() / 1000L;
+        long startTimestamp = startTime.getTime() / 1000L;
+
+
+        JsonObject jsonObject = new JsonObject();
+        JsonArray jsonArray = new JsonArray();
+
+        jsonObject.addProperty("starttime", startTimestamp);
+        jsonObject.addProperty("endtime", endTimestamp);
+
+        for (String userid : userIdList) {
+            jsonArray.add(userid);
+        }
+        jsonObject.add("useridlist", jsonArray);
+
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_CHECK_IN_SCHEDULE_DATA);
+        String responseContent = this.mainService.post(url, jsonObject.toString());
+        JsonObject tmpJson = GsonParser.parse(responseContent);
+        return WxCpGsonBuilder.create().fromJson(tmpJson.get("schedule_list"),
+                new TypeToken<List<WxCpCheckinSchedule>>() {}.getType()
+        );
+    }
+
+    @Override
+    public void setCheckinScheduleList(WxCpSetCheckinSchedule wxCpSetCheckinSchedule) {
+        final String url = this.mainService.getWxCpConfigStorage().getApiUrl(SET_CHECK_IN_SCHEDULE_DATA);
+        this.mainService.post(url, WxCpGsonBuilder.create().toJson(wxCpSetCheckinSchedule));
+    }
+}

+ 138 - 0
wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/WxCpTagServiceImpl.java

@@ -0,0 +1,138 @@
+package cn.nosum.wx.cp.api.impl;
+
+import cn.nosum.wx.common.utils.json.GsonParser;
+import cn.nosum.wx.cp.api.WxCpService;
+import cn.nosum.wx.cp.api.WxCpTagService;
+import cn.nosum.wx.cp.entity.WxCpTag;
+import cn.nosum.wx.cp.entity.WxCpTagAddOrRemoveUsersResult;
+import cn.nosum.wx.cp.entity.WxCpTagGetResult;
+import cn.nosum.wx.cp.entity.WxCpUser;
+import cn.nosum.wx.cp.utils.json.WxCpGsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.reflect.TypeToken;
+import lombok.RequiredArgsConstructor;
+
+import java.util.List;
+
+import static cn.nosum.wx.cp.constant.WxCpApiPathConsts.Tag.*;
+
+/**
+ * 标签管理接口.
+ *
+ * @author Young
+ */
+@RequiredArgsConstructor
+public class WxCpTagServiceImpl implements WxCpTagService {
+
+    private final WxCpService mainService;
+
+    @Override
+    public String create(String name, Integer id) {
+        JsonObject o = new JsonObject();
+        o.addProperty("tagname", name);
+
+        if (id != null) {
+            o.addProperty("tagid", id);
+        }
+        return this.create(o);
+    }
+
+    private String create(JsonObject param) {
+        String url = this.mainService.getWxCpConfigStorage().getApiUrl(TAG_CREATE);
+        String responseContent = this.mainService.post(url, param.toString());
+        JsonObject jsonObject = GsonParser.parse(responseContent);
+        return jsonObject.get("tagid").getAsString();
+    }
+
+    @Override
+    public void update(String tagId, String tagName) {
+        String url = this.mainService.getWxCpConfigStorage().getApiUrl(TAG_UPDATE);
+        JsonObject o = new JsonObject();
+        o.addProperty("tagid", tagId);
+        o.addProperty("tagname", tagName);
+        this.mainService.post(url, o.toString());
+    }
+
+    @Override
+    public void delete(String tagId) {
+        String url = String.format(this.mainService.getWxCpConfigStorage().getApiUrl(TAG_DELETE), tagId);
+        this.mainService.get(url, null);
+    }
+
+    @Override
+    public List<WxCpTag> listAll() {
+        String url = this.mainService.getWxCpConfigStorage().getApiUrl(TAG_LIST);
+        String responseContent = this.mainService.get(url, null);
+        JsonObject tmpJson = GsonParser.parse(responseContent);
+        return WxCpGsonBuilder.create()
+                .fromJson(
+                        tmpJson.get("taglist"),
+                        new TypeToken<List<WxCpTag>>() {
+                        }.getType()
+                );
+    }
+
+    @Override
+    public List<WxCpUser> listUsersByTagId(String tagId) {
+        String url = String.format(this.mainService.getWxCpConfigStorage().getApiUrl(TAG_GET), tagId);
+        String responseContent = this.mainService.get(url, null);
+        JsonObject tmpJson = GsonParser.parse(responseContent);
+        return WxCpGsonBuilder.create()
+                .fromJson(
+                        tmpJson.get("userlist"),
+                        new TypeToken<List<WxCpUser>>() {
+                        }.getType()
+                );
+    }
+
+    @Override
+    public WxCpTagAddOrRemoveUsersResult addUsers2Tag(String tagId, List<String> userIds, List<String> partyIds) {
+        String url = this.mainService.getWxCpConfigStorage().getApiUrl(TAG_ADD_TAG_USERS);
+        JsonObject jsonObject = new JsonObject();
+        jsonObject.addProperty("tagid", tagId);
+        this.addUserIdsAndPartyIdsToJson(userIds, partyIds, jsonObject);
+
+        return WxCpTagAddOrRemoveUsersResult.fromJson(this.mainService.post(url, jsonObject.toString()));
+    }
+
+    @Override
+    public WxCpTagAddOrRemoveUsersResult removeUsersFromTag(String tagId, List<String> userIds, List<String> partyIds) {
+        String url = this.mainService.getWxCpConfigStorage().getApiUrl(TAG_DEL_TAG_USERS);
+        JsonObject jsonObject = new JsonObject();
+        jsonObject.addProperty("tagid", tagId);
+        this.addUserIdsAndPartyIdsToJson(userIds, partyIds, jsonObject);
+
+        return WxCpTagAddOrRemoveUsersResult.fromJson(this.mainService.post(url, jsonObject.toString()));
+    }
+
+    private void addUserIdsAndPartyIdsToJson(List<String> userIds, List<String> partyIds, JsonObject jsonObject) {
+        if (userIds != null) {
+            JsonArray jsonArray = new JsonArray();
+            for (String userId : userIds) {
+                jsonArray.add(new JsonPrimitive(userId));
+            }
+            jsonObject.add("userlist", jsonArray);
+        }
+
+        if (partyIds != null) {
+            JsonArray jsonArray = new JsonArray();
+            for (String userId : partyIds) {
+                jsonArray.add(new JsonPrimitive(userId));
+            }
+            jsonObject.add("partylist", jsonArray);
+        }
+    }
+
+    @Override
+    public WxCpTagGetResult get(String tagId) {
+        if (tagId == null) {
+            throw new IllegalArgumentException("缺少tagId参数");
+        }
+
+        String url = String.format(this.mainService.getWxCpConfigStorage().getApiUrl(TAG_GET), tagId);
+        String responseContent = this.mainService.get(url, null);
+        return WxCpTagGetResult.fromJson(responseContent);
+    }
+}

+ 37 - 0
wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/WxCpTaskCardServiceImpl.java

@@ -0,0 +1,37 @@
+package cn.nosum.wx.cp.api.impl;
+
+import cn.nosum.wx.common.utils.json.WxGsonBuilder;
+import cn.nosum.wx.cp.api.WxCpService;
+import cn.nosum.wx.cp.api.WxCpTaskCardService;
+import lombok.RequiredArgsConstructor;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import cn.nosum.wx.cp.constant.WxCpApiPathConsts.TaskCard;
+
+/**
+ * 任务卡片管理接口.
+ *
+ * @author Young
+ */
+@RequiredArgsConstructor
+public class WxCpTaskCardServiceImpl implements WxCpTaskCardService {
+
+    private final WxCpService mainService;
+
+    @Override
+    public void update(List<String> userIds, String taskId, String clickedKey) {
+        Integer agentId = this.mainService.getWxCpConfigStorage().getAgentId();
+
+        Map<String, Object> data = new HashMap<>(4);
+        data.put("userids", userIds);
+        data.put("agentid", agentId);
+        data.put("task_id", taskId);
+        data.put("clicked_key", clickedKey);
+
+        String url = this.mainService.getWxCpConfigStorage().getApiUrl(TaskCard.UPDATE_TASK_CARD);
+        this.mainService.post(url, WxGsonBuilder.create().toJson(data));
+    }
+}

+ 206 - 0
wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/api/impl/WxCpUserServiceImpl.java

@@ -0,0 +1,206 @@
+package cn.nosum.wx.cp.api.impl;
+
+import cn.nosum.wx.common.utils.json.GsonParser;
+import cn.nosum.wx.cp.api.WxCpService;
+import cn.nosum.wx.cp.api.WxCpUserService;
+import cn.nosum.wx.cp.entity.WxCpInviteResult;
+import cn.nosum.wx.cp.entity.WxCpUser;
+import cn.nosum.wx.cp.entity.external.contact.WxCpExternalContactInfo;
+import cn.nosum.wx.cp.utils.json.WxCpGsonBuilder;
+import com.google.common.collect.Maps;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.reflect.TypeToken;
+import lombok.RequiredArgsConstructor;
+
+import java.util.List;
+import java.util.Map;
+
+import static cn.nosum.wx.cp.constant.WxCpApiPathConsts.User.*;
+
+/**
+ * 用户服务实现.
+ *
+ * @author Young
+ */
+@RequiredArgsConstructor
+public class WxCpUserServiceImpl implements WxCpUserService {
+
+    private final WxCpService mainService;
+
+    @Override
+    public void authenticate(String userId) {
+        this.mainService.get(this.mainService.getWxCpConfigStorage().getApiUrl(USER_AUTHENTICATE + userId), null);
+    }
+
+    @Override
+    public void create(WxCpUser user) {
+        String url = this.mainService.getWxCpConfigStorage().getApiUrl(USER_CREATE);
+        this.mainService.post(url, user.toJson());
+    }
+
+    @Override
+    public void update(WxCpUser user) {
+        String url = this.mainService.getWxCpConfigStorage().getApiUrl(USER_UPDATE);
+        this.mainService.post(url, user.toJson());
+    }
+
+    @Override
+    public void delete(String... userIds) {
+        if (userIds.length == 1) {
+            String url = this.mainService.getWxCpConfigStorage().getApiUrl(USER_DELETE + userIds[0]);
+            this.mainService.get(url, null);
+            return;
+        }
+
+        JsonObject jsonObject = new JsonObject();
+        JsonArray jsonArray = new JsonArray();
+        for (String userId : userIds) {
+            jsonArray.add(new JsonPrimitive(userId));
+        }
+
+        jsonObject.add("useridlist", jsonArray);
+        this.mainService.post(this.mainService.getWxCpConfigStorage().getApiUrl(USER_BATCH_DELETE), jsonObject.toString());
+    }
+
+    @Override
+    public WxCpUser getById(String userid) {
+        String url = this.mainService.getWxCpConfigStorage().getApiUrl(USER_GET + userid);
+        String responseContent = this.mainService.get(url, null);
+        return WxCpUser.fromJson(responseContent);
+    }
+
+    @Override
+    public List<WxCpUser> listByDepartment(Long departId, Boolean fetchChild, Integer status) {
+        String params = "";
+        if (fetchChild != null) {
+            params += "&fetch_child=" + (fetchChild ? "1" : "0");
+        }
+        if (status != null) {
+            params += "&status=" + status;
+        } else {
+            params += "&status=0";
+        }
+
+        String url = this.mainService.getWxCpConfigStorage().getApiUrl(USER_LIST + departId);
+        String responseContent = this.mainService.get(url, params);
+        JsonObject jsonObject = GsonParser.parse(responseContent);
+        return WxCpGsonBuilder.create().fromJson(jsonObject.get("userlist"),
+                new TypeToken<List<WxCpUser>>() {
+                }.getType()
+        );
+    }
+
+    @Override
+    public List<WxCpUser> listSimpleByDepartment(Long departId, Boolean fetchChild, Integer status) {
+        String params = "";
+        if (fetchChild != null) {
+            params += "&fetch_child=" + (fetchChild ? "1" : "0");
+        }
+        if (status != null) {
+            params += "&status=" + status;
+        } else {
+            params += "&status=0";
+        }
+
+        String url = this.mainService.getWxCpConfigStorage().getApiUrl(USER_SIMPLE_LIST + departId);
+        String responseContent = this.mainService.get(url, params);
+        JsonObject tmpJson = GsonParser.parse(responseContent);
+        return WxCpGsonBuilder.create()
+                .fromJson(
+                        tmpJson.get("userlist"),
+                        new TypeToken<List<WxCpUser>>() {
+                        }.getType()
+                );
+    }
+
+    @Override
+    public WxCpInviteResult invite(List<String> userIds, List<String> partyIds, List<String> tagIds) {
+        JsonObject jsonObject = new JsonObject();
+        if (userIds != null) {
+            JsonArray jsonArray = new JsonArray();
+            for (String userId : userIds) {
+                jsonArray.add(new JsonPrimitive(userId));
+            }
+            jsonObject.add("user", jsonArray);
+        }
+
+        if (partyIds != null) {
+            JsonArray jsonArray = new JsonArray();
+            for (String userId : partyIds) {
+                jsonArray.add(new JsonPrimitive(userId));
+            }
+            jsonObject.add("party", jsonArray);
+        }
+
+        if (tagIds != null) {
+            JsonArray jsonArray = new JsonArray();
+            for (String tagId : tagIds) {
+                jsonArray.add(new JsonPrimitive(tagId));
+            }
+            jsonObject.add("tag", jsonArray);
+        }
+
+        String url = this.mainService.getWxCpConfigStorage().getApiUrl(BATCH_INVITE);
+        return WxCpInviteResult.fromJson(this.mainService.post(url, jsonObject.toString()));
+    }
+
+    @Override
+    public Map<String, String> userId2Openid(String userId, Integer agentId) {
+        String url = this.mainService.getWxCpConfigStorage().getApiUrl(USER_CONVERT_TO_OPENID);
+        JsonObject jsonObject = new JsonObject();
+        jsonObject.addProperty("userid", userId);
+        if (agentId != null) {
+            jsonObject.addProperty("agentid", agentId);
+        }
+
+        String responseContent = this.mainService.post(url, jsonObject.toString());
+        JsonObject tmpJson = GsonParser.parse(responseContent);
+        Map<String, String> result = Maps.newHashMap();
+        if (tmpJson.get("openid") != null) {
+            result.put("openid", tmpJson.get("openid").getAsString());
+        }
+
+        if (tmpJson.get("appid") != null) {
+            result.put("appid", tmpJson.get("appid").getAsString());
+        }
+
+        return result;
+    }
+
+    @Override
+    public String openid2UserId(String openid) {
+        JsonObject jsonObject = new JsonObject();
+        jsonObject.addProperty("openid", openid);
+        String url = this.mainService.getWxCpConfigStorage().getApiUrl(USER_CONVERT_TO_USERID);
+        String responseContent = this.mainService.post(url, jsonObject.toString());
+        JsonObject tmpJson = GsonParser.parse(responseContent);
+        return tmpJson.get("userid").getAsString();
+    }
+
+    @Override
+    public String getUserId(String mobile) {
+        JsonObject jsonObject = new JsonObject();
+        jsonObject.addProperty("mobile", mobile);
+        String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_USER_ID);
+        String responseContent = this.mainService.post(url, jsonObject.toString());
+        JsonObject tmpJson = GsonParser.parse(responseContent);
+        return tmpJson.get("userid").getAsString();
+    }
+
+    @Override
+    public WxCpExternalContactInfo getExternalContact(String userId) {
+        String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_EXTERNAL_CONTACT + userId);
+        String responseContent = this.mainService.get(url, null);
+        return WxCpExternalContactInfo.fromJson(responseContent);
+    }
+
+    @Override
+    public String getJoinQrCode(int sizeType) {
+        String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_JOIN_QR_CODE + sizeType);
+        String responseContent = this.mainService.get(url, null);
+        JsonObject tmpJson = GsonParser.parse(responseContent);
+        return tmpJson.get("join_qrcode").getAsString();
+    }
+}

+ 1 - 0
wx-java-tools/wx-java-cp/src/main/java/cn/nosum/wx/cp/constant/WxCpApiPathConsts.java

@@ -252,6 +252,7 @@ public interface WxCpApiPathConsts {
         String GET_FOLLOW_USER_LIST = "/cgi-bin/externalcontact/get_follow_user_list";
         String GET_CONTACT_DETAIL = "/cgi-bin/externalcontact/get?external_userid=";
         String CONVERT_TO_OPENID = "/cgi-bin/externalcontact/convert_to_openid";
+        String UNIONID_TO_EXTERNAL_USERID = "/cgi-bin/externalcontact/unionid_to_external_userid";
         String GET_CONTACT_DETAIL_BATCH = "/cgi-bin/externalcontact/batch/get_by_user?";
         String UPDATE_REMARK = "/cgi-bin/externalcontact/remark";
         String LIST_EXTERNAL_CONTACT = "/cgi-bin/externalcontact/list?userid=";