Преглед на файлове

:new: #2942【企业微信】增加企业互联相关接口

1ibo преди 2 години
родител
ревизия
a9b8667053
променени са 26 файла, в които са добавени 1927 реда и са изтрити 12 реда
  1. 18 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpCorpGroupService.java
  2. 8 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java
  3. 8 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java
  4. 48 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpCorpGroupServiceImpl.java
  5. 43 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/corpgroup/WxCpCorpGroupCorp.java
  6. 25 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/corpgroup/WxCpCorpGroupCorpGetTokenReq.java
  7. 27 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/corpgroup/WxCpCorpGroupCorpListAppShareInfoResp.java
  8. 38 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/corpgroup/WxCpCorpGroupCorpToken.java
  9. 47 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/corpgroup/WxCpMaTransferSession.java
  10. 23 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/linkedcorp/WxCpLinkedCorpAgentPerm.java
  11. 33 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/linkedcorp/WxCpLinkedCorpDepartment.java
  12. 64 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/linkedcorp/WxCpLinkedCorpUser.java
  13. 148 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpCorpGroupConfigStorage.java
  14. 203 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpCorpGroupDefaultConfigImpl.java
  15. 222 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpCorpGroupRedissonConfigImpl.java
  16. 59 8
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java
  17. 174 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/WxCpCgService.java
  18. 30 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/WxCpLinkedCorpService.java
  19. 300 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/impl/BaseWxCpCgServiceImpl.java
  20. 57 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/impl/WxCpCgServiceApacheHttpClientImpl.java
  21. 96 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/impl/WxCpLinkedCorpServiceImpl.java
  22. 43 0
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpCorpGroupServiceImplTest.java
  23. 1 1
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpMeetingServiceImplTest.java
  24. 114 0
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/corpgroup/service/impl/WxCpCgServiceApacheHttpClientImplTest.java
  25. 98 0
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/corpgroup/service/impl/WxCpLinkedCorpServiceImplTest.java
  26. 0 3
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImplTest.java

+ 18 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpCorpGroupService.java

@@ -0,0 +1,18 @@
+package me.chanjar.weixin.cp.api;
+
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.cp.bean.corpgroup.*;
+
+import java.util.List;
+
+/**
+ * @Project: WxJava
+ * @Package: me.chanjar.weixin.cp.api
+ * @Description: 企业互联相关接口
+ * @Author: libo
+ * @Email: 422423229@qq.com
+ * @Date: 27/2/2023 9:57 PM
+ */
+public interface WxCpCorpGroupService {
+  List<WxCpCorpGroupCorp> listAppShareInfo(Integer agentId,Integer businessType,String corpId,Integer limit,String cursor) throws WxErrorException;
+}

+ 8 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java

@@ -12,6 +12,7 @@ import me.chanjar.weixin.cp.bean.WxCpAgentJsapiSignature;
 import me.chanjar.weixin.cp.bean.WxCpMaJsCode2SessionResult;
 import me.chanjar.weixin.cp.bean.WxCpProviderToken;
 import me.chanjar.weixin.cp.config.WxCpConfigStorage;
+import me.chanjar.weixin.cp.corpgroup.service.WxCpLinkedCorpService;
 
 /**
  * 微信API的Service.
@@ -569,4 +570,11 @@ public interface WxCpService extends WxService {
    * @return  the meeting service
    */
   WxCpMeetingService getMeetingService();
+
+  /**
+   * 企业互联的服务类对象
+   *
+   * @return
+   */
+  WxCpCorpGroupService getCorpGroupService();
 }

+ 8 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java

@@ -24,6 +24,8 @@ import me.chanjar.weixin.cp.bean.WxCpAgentJsapiSignature;
 import me.chanjar.weixin.cp.bean.WxCpMaJsCode2SessionResult;
 import me.chanjar.weixin.cp.bean.WxCpProviderToken;
 import me.chanjar.weixin.cp.config.WxCpConfigStorage;
+import me.chanjar.weixin.cp.corpgroup.service.WxCpLinkedCorpService;
+import me.chanjar.weixin.cp.corpgroup.service.impl.WxCpLinkedCorpServiceImpl;
 import org.apache.commons.lang3.StringUtils;
 
 import java.io.File;
@@ -71,6 +73,7 @@ public abstract class BaseWxCpServiceImpl<H, P> implements WxCpService, RequestH
   private WxCpExportService exportService = new WxCpExportServiceImpl(this);
 
   private final WxCpMeetingService meetingService = new WxCpMeetingServiceImpl(this);
+  private final WxCpCorpGroupService corpGroupService = new WxCpCorpGroupServiceImpl(this);
 
   /**
    * 全局的是否正在刷新access token的锁.
@@ -672,4 +675,9 @@ public abstract class BaseWxCpServiceImpl<H, P> implements WxCpService, RequestH
   public WxCpMeetingService getMeetingService() {
     return meetingService;
   }
+
+  @Override
+  public WxCpCorpGroupService getCorpGroupService() {
+    return corpGroupService;
+  }
 }

+ 48 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpCorpGroupServiceImpl.java

@@ -0,0 +1,48 @@
+package me.chanjar.weixin.cp.api.impl;
+
+import com.google.gson.JsonObject;
+import com.google.gson.reflect.TypeToken;
+import lombok.RequiredArgsConstructor;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.json.GsonParser;
+import me.chanjar.weixin.cp.api.WxCpCorpGroupService;
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.bean.corpgroup.*;
+import me.chanjar.weixin.cp.constant.WxCpApiPathConsts;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.util.List;
+
+import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.CorpGroup.*;
+import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.LinkedCorp.GET_PERM_LIST;
+
+/**
+ * @Project: WxJava
+ * @Package: me.chanjar.weixin.cp.api.impl
+ * @Description: 企业互联相关接口实现类
+ * @Author: libo
+ * @Email: 422423229@qq.com
+ * @Date: 27/2/2023 10:02 PM
+ */
+@RequiredArgsConstructor
+public class WxCpCorpGroupServiceImpl implements WxCpCorpGroupService {
+  private final WxCpService cpService;
+
+  @Override
+  public List<WxCpCorpGroupCorp> listAppShareInfo(Integer agentId, Integer businessType, String corpId, Integer limit, String cursor) throws WxErrorException {
+    final String url = this.cpService.getWxCpConfigStorage().getApiUrl(LIST_SHARE_APP_INFO);
+    JsonObject jsonObject = new JsonObject();
+    jsonObject.addProperty("agentid", agentId);
+    jsonObject.addProperty("corpid", corpId);
+    jsonObject.addProperty("business_type", businessType);
+    jsonObject.addProperty("limit", limit);
+    jsonObject.addProperty("cursor", cursor);
+    String responseContent = this.cpService.post(url, jsonObject);
+    JsonObject tmpJson = GsonParser.parse(responseContent);
+
+    return WxCpGsonBuilder.create().fromJson(tmpJson.get("corp_list"),
+      new TypeToken<List<WxCpCorpGroupCorpListAppShareInfoResp>>() {
+      }.getType()
+    );
+  }
+}

+ 43 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/corpgroup/WxCpCorpGroupCorp.java

@@ -0,0 +1,43 @@
+package me.chanjar.weixin.cp.bean.corpgroup;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * @Project: WxJava
+ * @Package: me.chanjar.weixin.cp.bean.corpgroup
+ * @Description: 应用类
+ * @Author: libo
+ * @Email: 422423229@qq.com
+ * @Date: 27/2/2023 9:50 PM
+ */
+@NoArgsConstructor
+@Data
+public class WxCpCorpGroupCorp implements Serializable {
+
+  private static final long serialVersionUID = 6842919838272832415L;
+  @SerializedName("corpid")
+  private String corpid;
+  @SerializedName("corp_name")
+  private String corpName;
+  @SerializedName("agentid")
+  private Integer agentid;
+
+  public static WxCpCorpGroupCorp fromJson(String json) {
+    return WxCpGsonBuilder.create().fromJson(json, WxCpCorpGroupCorp.class);
+  }
+
+  /**
+   * To json string.
+   *
+   * @return the string
+   */
+  public String toJson() {
+    return WxCpGsonBuilder.create().toJson(this);
+  }
+
+}

+ 25 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/corpgroup/WxCpCorpGroupCorpGetTokenReq.java

@@ -0,0 +1,25 @@
+package me.chanjar.weixin.cp.bean.corpgroup;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @Project: WxJava
+ * @Package: me.chanjar.weixin.cp.bean.corpgroup
+ * @Description: 获取下级/下游企业的access_token
+ * @Author: libo
+ * @Email: 422423229@qq.com
+ * @Date: 27/2/2023 9:07 PM
+ */
+@Data
+public class WxCpCorpGroupCorpGetTokenReq implements Serializable {
+  private static final long serialVersionUID = -1876754768932436524L;
+  @SerializedName("corpid")
+  private String corpId;
+  @SerializedName("business_type")
+  private int businessType;
+  @SerializedName("agentid")
+  private int agentId;
+}

+ 27 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/corpgroup/WxCpCorpGroupCorpListAppShareInfoResp.java

@@ -0,0 +1,27 @@
+package me.chanjar.weixin.cp.bean.corpgroup;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import me.chanjar.weixin.cp.bean.WxCpBaseResp;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @Project: WxJava
+ * @Package: me.chanjar.weixin.cp.bean.corpgroup
+ * @Description: 获取应用共享信息返回类
+ * @Author: libo
+ * @Email: 422423229@qq.com
+ * @Date: 27/2/2023 9:02 PM
+ */
+@Data
+public class WxCpCorpGroupCorpListAppShareInfoResp implements Serializable {
+  private static final long serialVersionUID = 7165788382879237583L;
+  @SerializedName("ending")
+  private int ending;
+  @SerializedName("corp_list")
+  private List<WxCpCorpGroupCorp> corpList;
+  @SerializedName("next_cursor")
+  private String nextCursor;
+}

+ 38 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/corpgroup/WxCpCorpGroupCorpToken.java

@@ -0,0 +1,38 @@
+package me.chanjar.weixin.cp.bean.corpgroup;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * @Project: WxJava
+ * @Package: me.chanjar.weixin.cp.bean.corpgroup
+ * @Description: 获取下级/下游企业的access_token返回类
+ * @Author: libo
+ * @Email: 422423229@qq.com
+ * @Date: 27/2/2023 9:07 PM
+ */
+@Data
+public class WxCpCorpGroupCorpToken implements Serializable {
+  private static final long serialVersionUID = -8139617060677460515L;
+  @SerializedName("access_token")
+  private String accessToken;
+  @SerializedName("expires_in")
+  private int expiresIn;
+
+  public static WxCpCorpGroupCorpToken fromJson(String json) {
+    return WxCpGsonBuilder.create().fromJson(json, WxCpCorpGroupCorpToken.class);
+  }
+
+  /**
+   * To json string.
+   *
+   * @return the string
+   */
+  public String toJson() {
+    return WxCpGsonBuilder.create().toJson(this);
+  }
+
+}

+ 47 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/corpgroup/WxCpMaTransferSession.java

@@ -0,0 +1,47 @@
+package me.chanjar.weixin.cp.bean.corpgroup;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import me.chanjar.weixin.cp.bean.WxCpAgent;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * @Project: WxJava
+ * @Package: me.chanjar.weixin.cp.bean.corpgroup
+ * @Description: 获取下级/下游企业小程序session返回类
+ * @Author: libo
+ * @Email: 422423229@qq.com
+ * @Date: 27/2/2023 9:10 PM
+ */
+@Data
+public class WxCpMaTransferSession implements Serializable {
+
+  private static final long serialVersionUID = 4189407986285166516L;
+  @SerializedName("userid")
+  private String userId;
+  @SerializedName("session_key")
+  private String sessionKey;
+
+
+  /**
+   * From json WxCpMaTransferSession.
+   *
+   * @param json the json
+   * @return the WxCpMaTransferSession
+   */
+  public static WxCpMaTransferSession fromJson(String json) {
+    return WxCpGsonBuilder.create().fromJson(json, WxCpMaTransferSession.class);
+  }
+
+  /**
+   * To json string.
+   *
+   * @return the string
+   */
+  public String toJson() {
+    return WxCpGsonBuilder.create().toJson(this);
+  }
+
+}

+ 23 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/linkedcorp/WxCpLinkedCorpAgentPerm.java

@@ -0,0 +1,23 @@
+package me.chanjar.weixin.cp.bean.linkedcorp;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @Project: WxJava
+ * @Package: me.chanjar.weixin.cp.bean.linkedcorp
+ * @Description: 获取应用可见范围请求类
+ * @Author: libo
+ * @Email: 422423229@qq.com
+ * @Date: 28/2/2023 6:16 PM
+ */
+@Data
+public class WxCpLinkedCorpAgentPerm implements Serializable {
+  private static final long serialVersionUID = 6794613362541093845L;
+  @SerializedName("userids")
+  private String[] userIdList;
+  @SerializedName("department_ids")
+  private String[] departmentIdList;
+}

+ 33 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/linkedcorp/WxCpLinkedCorpDepartment.java

@@ -0,0 +1,33 @@
+package me.chanjar.weixin.cp.bean.linkedcorp;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @Project: WxJava
+ * @Package: me.chanjar.weixin.cp.bean.linkedcorp
+ * @Description: 获取互联企业部门列表
+ * @Author: libo
+ * @Email: 422423229@qq.com
+ * @Date: 28/2/2023 6:16 PM
+ */
+@Data
+public class WxCpLinkedCorpDepartment implements Serializable {
+  private static final long serialVersionUID = -210249269343292440L;
+  @SerializedName("department_id")
+  private String departmentId;
+  @SerializedName("department_name")
+  private String departmentName;
+  @SerializedName("parentid")
+  private String parentId;
+  @SerializedName("order")
+  private Integer order;
+}

+ 64 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/linkedcorp/WxCpLinkedCorpUser.java

@@ -0,0 +1,64 @@
+package me.chanjar.weixin.cp.bean.linkedcorp;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+import me.chanjar.weixin.cp.bean.WxCpBaseResp;
+import me.chanjar.weixin.cp.bean.WxCpUser;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @Project: WxJava
+ * @Package: me.chanjar.weixin.cp.bean.linkedcorp
+ * @Description: 获取互联企业成员详细信息
+ * @Author: libo
+ * @Email: 422423229@qq.com
+ * @Date: 28/2/2023 6:16 PM
+ */
+@Data
+public class WxCpLinkedCorpUser implements Serializable {
+  private static final long serialVersionUID = -5197865724556226531L;
+  @SerializedName("userid")
+  private String userId;
+  @SerializedName("name")
+  private String name;
+  @SerializedName("department")
+  private String[] department;
+  @SerializedName("mobile")
+  private String mobile;
+  @SerializedName("email")
+  private String email;
+  @SerializedName("position")
+  private String position;
+  @SerializedName("corpid")
+  private String corpId;
+  private final List<Attr> extAttrs = new ArrayList<>();
+
+  /**
+   * The type Attr.
+   */
+  @Data
+  @Accessors(chain = true)
+  @Builder
+  @NoArgsConstructor
+  @AllArgsConstructor
+  public static class Attr implements Serializable {
+    private static final long serialVersionUID = -5696099236344075582L;
+
+    /**
+     * 属性类型: 0-文本 1-网页
+     */
+    private Integer type;
+    private String name;
+    private String textValue;
+    private String webUrl;
+    private String webTitle;
+  }
+
+}

+ 148 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpCorpGroupConfigStorage.java

@@ -0,0 +1,148 @@
+package me.chanjar.weixin.cp.config;
+
+import me.chanjar.weixin.common.bean.WxAccessToken;
+import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
+import me.chanjar.weixin.cp.bean.WxCpProviderToken;
+
+import java.io.File;
+import java.util.concurrent.locks.Lock;
+
+/**
+ * @Project: WxJava
+ * @Package: me.chanjar.weixin.cp.config
+ * @Description: 微信客户端(企业互联)配置
+ * @or: libo
+ * @Email: 422423229@qq.com
+ * @Date: 1/3/2023 9:56 AM
+ */
+public interface WxCpCorpGroupConfigStorage {
+  /**
+   * 设置企业微信服务器 baseUrl.
+   * 默认值是 https://qyapi.weixin.qq.com , 如果使用默认值,则不需要调用 setBaseApiUrl
+   *
+   * @param baseUrl 企业微信服务器 Url
+   */
+  void setBaseApiUrl(String baseUrl);
+
+  /**
+   * 读取企业微信 API Url.
+   * 支持私有化企业微信服务器.
+   *
+   * @param path the path
+   * @return the api url
+   */
+  String getApiUrl(String path);
+
+  /**
+   * Update corp access token.
+   *
+   * @param corpId
+   * @param agentId
+   * @param corpAccessToken  the corp access token
+   * @param expiresInSeconds the expires in seconds
+   */
+  void updateCorpAccessToken(String corpId, Integer agentId, String corpAccessToken, int expiresInSeconds);
+
+  /**
+   * 授权企业的access token相关
+   *
+   * @param corpId  the corp id
+   * @param agentId
+   * @return the access token
+   */
+  String getCorpAccessToken(String corpId, Integer agentId);
+
+  /**
+   * Gets access token entity.
+   *
+   * @param corpId  the  corp id
+   * @param agentId
+   * @return the access token entity
+   */
+  WxAccessToken getCorpAccessTokenEntity(String corpId, Integer agentId);
+
+  /**
+   * Is access token expired boolean.
+   *
+   * @param corpId  the  corp id
+   * @param agentId
+   * @return the boolean
+   */
+  boolean isCorpAccessTokenExpired(String corpId, Integer agentId);
+
+  /**
+   * Expire access token.
+   *
+   * @param corpId  the  corp id
+   * @param agentId
+   */
+  void expireCorpAccessToken(String corpId, Integer agentId);
+
+  /**
+   * 网络代理相关
+   *
+   * @return the http proxy host
+   */
+  String getHttpProxyHost();
+
+  /**
+   * Gets http proxy port.
+   *
+   * @return the http proxy port
+   */
+  int getHttpProxyPort();
+
+  /**
+   * Gets http proxy username.
+   *
+   * @return the http proxy username
+   */
+  String getHttpProxyUsername();
+
+  /**
+   * Gets http proxy password.
+   *
+   * @return the http proxy password
+   */
+  String getHttpProxyPassword();
+
+  /**
+   * Gets apache http client builder.
+   *
+   * @return the apache http client builder
+   */
+  ApacheHttpClientBuilder getApacheHttpClientBuilder();
+
+  /**
+   * Auto refresh token boolean.
+   *
+   * @return the boolean
+   */
+  boolean autoRefreshToken();
+
+  /**
+   * Gets access token lock.
+   *
+   * @param corpId the corp id
+   * @return the access token lock
+   */
+  Lock getCorpAccessTokenLock(String corpId, Integer agentId);
+
+  void setCorpId(String corpId);
+
+  void setAgentId(Integer agentId);
+
+  /**
+   * Gets corp id.
+   *
+   * @return the corp id
+   */
+  String getCorpId();
+
+  /**
+   * Gets agent id.
+   *
+   * @return the agent id
+   */
+  Integer getAgentId();
+}

+ 203 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpCorpGroupDefaultConfigImpl.java

@@ -0,0 +1,203 @@
+package me.chanjar.weixin.cp.config.impl;
+
+import me.chanjar.weixin.common.bean.WxAccessToken;
+import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
+import me.chanjar.weixin.cp.config.WxCpConfigStorage;
+import me.chanjar.weixin.cp.config.WxCpCorpGroupConfigStorage;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * @Project: WxJava
+ * @Package: me.chanjar.weixin.cp.config.impl
+ * @Description: 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化.
+ * @Author: libo
+ * @Email: 422423229@qq.com
+ * @Date: 1/3/2023 10:30 AM
+ */
+public class WxCpCorpGroupDefaultConfigImpl implements WxCpCorpGroupConfigStorage, Serializable {
+  private final transient Map<String, Lock> corpAccessTokenLocker = new ConcurrentHashMap<>();
+
+  private final Map<String, String> corpAccessTokenMap = new HashMap<>();
+  private final Map<String, Long> corpAccessTokenExpireTimeMap = new HashMap<>();
+
+  private volatile String httpProxyHost;
+  private volatile int httpProxyPort;
+  private volatile String httpProxyUsername;
+  private volatile String httpProxyPassword;
+  private volatile ApacheHttpClientBuilder apacheHttpClientBuilder;
+  private volatile String baseApiUrl;
+
+  /**
+   * 微信企业号 corpId
+   */
+  private volatile String corpId;
+  /**
+   * 微信企业号应用 ID
+   */
+  private volatile Integer agentId;
+
+  @Override
+  public void setBaseApiUrl(String baseUrl) {
+    this.baseApiUrl = baseUrl;
+  }
+
+  @Override
+  public String getApiUrl(String path) {
+    if (baseApiUrl == null) {
+      baseApiUrl = "https://qyapi.weixin.qq.com";
+    }
+    return baseApiUrl + path;
+  }
+
+  @Override
+  public String getCorpId() {
+    return corpId;
+  }
+
+  @Override
+  public void setCorpId(String corpId) {
+    this.corpId = corpId;
+  }
+
+  @Override
+  public Integer getAgentId() {
+    return agentId;
+  }
+
+  @Override
+  public void setAgentId(Integer agentId) {
+    this.agentId = agentId;
+  }
+
+  @Override
+  public void updateCorpAccessToken(String corpId, Integer agentId, String corpAccessToken, int expiresInSeconds) {
+    String key = generateAccessTokenKey(corpId, agentId);
+    corpAccessTokenMap.put(key, corpAccessToken);
+    //预留200秒的时间
+    corpAccessTokenExpireTimeMap.put(key, System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L);
+  }
+
+  @Override
+  public String getCorpAccessToken(String corpId, Integer agentId) {
+    return this.corpAccessTokenMap.get(generateAccessTokenKey(corpId, agentId));
+  }
+
+  @Override
+  public WxAccessToken getCorpAccessTokenEntity(String corpId, Integer agentId) {
+    String key = generateAccessTokenKey(corpId, agentId);
+    String accessToken = corpAccessTokenMap.getOrDefault(key, StringUtils.EMPTY);
+    Long expire = corpAccessTokenExpireTimeMap.getOrDefault(key, 0L);
+    WxAccessToken accessTokenEntity = new WxAccessToken();
+    accessTokenEntity.setAccessToken(accessToken);
+    accessTokenEntity.setExpiresIn((int) ((expire - System.currentTimeMillis()) / 1000 + 200));
+    return accessTokenEntity;
+  }
+
+  @Override
+  public boolean isCorpAccessTokenExpired(String corpId, Integer agentId) {
+    //不存在或者过期
+    String key = generateAccessTokenKey(corpId, agentId);
+    return corpAccessTokenExpireTimeMap.get(key) == null
+      || System.currentTimeMillis() > corpAccessTokenExpireTimeMap.get(key);
+  }
+
+  @Override
+  public void expireCorpAccessToken(String corpId, Integer agentId) {
+    String key = generateAccessTokenKey(corpId, agentId);
+    corpAccessTokenMap.remove(key);
+    corpAccessTokenExpireTimeMap.remove(key);
+  }
+
+  @Override
+  public String getHttpProxyHost() {
+    return this.httpProxyHost;
+  }
+
+  /**
+   * Sets http proxy host.
+   *
+   * @param httpProxyHost the http proxy host
+   */
+  public void setHttpProxyHost(String httpProxyHost) {
+    this.httpProxyHost = httpProxyHost;
+  }
+
+  @Override
+  public int getHttpProxyPort() {
+    return this.httpProxyPort;
+  }
+
+  /**
+   * Sets http proxy port.
+   *
+   * @param httpProxyPort the http proxy port
+   */
+  public void setHttpProxyPort(int httpProxyPort) {
+    this.httpProxyPort = httpProxyPort;
+  }
+
+  @Override
+  public String getHttpProxyUsername() {
+    return this.httpProxyUsername;
+  }
+
+  /**
+   * Sets http proxy username.
+   *
+   * @param httpProxyUsername the http proxy username
+   */
+  public void setHttpProxyUsername(String httpProxyUsername) {
+    this.httpProxyUsername = httpProxyUsername;
+  }
+
+  @Override
+  public String getHttpProxyPassword() {
+    return this.httpProxyPassword;
+  }
+
+  /**
+   * Sets http proxy password.
+   *
+   * @param httpProxyPassword the http proxy password
+   */
+  public void setHttpProxyPassword(String httpProxyPassword) {
+    this.httpProxyPassword = httpProxyPassword;
+  }
+
+  @Override
+  public ApacheHttpClientBuilder getApacheHttpClientBuilder() {
+    return this.apacheHttpClientBuilder;
+  }
+
+  /**
+   * Sets apache http client builder.
+   *
+   * @param apacheHttpClientBuilder the apache http client builder
+   */
+  public void setApacheHttpClientBuilder(ApacheHttpClientBuilder apacheHttpClientBuilder) {
+    this.apacheHttpClientBuilder = apacheHttpClientBuilder;
+  }
+
+  @Override
+  public boolean autoRefreshToken() {
+    return true;
+  }
+
+  @Override
+  public Lock getCorpAccessTokenLock(String corpId, Integer agentId) {
+    return this.corpAccessTokenLocker
+      .computeIfAbsent(generateAccessTokenKey(corpId, agentId), key -> new ReentrantLock());
+  }
+
+  private String generateAccessTokenKey(String corpId, Integer agentId) {
+    return String.join(":", this.corpId, String.valueOf(this.agentId), corpId, String.valueOf(agentId));
+  }
+}

+ 222 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpCorpGroupRedissonConfigImpl.java

@@ -0,0 +1,222 @@
+package me.chanjar.weixin.cp.config.impl;
+
+import lombok.Builder;
+import lombok.NonNull;
+import lombok.Setter;
+import me.chanjar.weixin.common.bean.WxAccessToken;
+import me.chanjar.weixin.common.redis.WxRedisOps;
+import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
+import me.chanjar.weixin.cp.bean.WxCpProviderToken;
+import me.chanjar.weixin.cp.config.WxCpCorpGroupConfigStorage;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.Serializable;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * @Project: WxJava
+ * @Package: me.chanjar.weixin.cp.config.impl
+ * @Description: 企业微信企业互联各种固定、授权配置的Redisson存储实现
+ * @Author: libo
+ * @Email: 422423229@qq.com
+ * @Date: 1/3/2023 10:48 AM
+ */
+@Builder
+public class WxCpCorpGroupRedissonConfigImpl implements WxCpCorpGroupConfigStorage, Serializable {
+  private final transient Map<String, Lock> corpAccessTokenLocker = new ConcurrentHashMap<>();
+
+  /**
+   * The constant LOCK_KEY.
+   */
+  protected static final String LOCK_KEY = "wechat_cg_lock:";
+  /**
+   * The constant LOCKER_CORP_ACCESS_TOKEN.
+   */
+  protected static final String LOCKER_CORP_ACCESS_TOKEN = "corpAccessTokenLock";
+  /**
+   * The constant CG_ACCESS_TOKEN_KEY.
+   */
+  protected static final String CG_ACCESS_TOKEN_KEY = "wechat_cg_access_token_key:";
+  @NonNull
+  private final WxRedisOps wxRedisOps;
+  /**
+   * redis里面key的统一前缀
+   */
+  @Setter
+  private String keyPrefix = "";
+
+  private volatile String httpProxyHost;
+  private volatile int httpProxyPort;
+  private volatile String httpProxyUsername;
+  private volatile String httpProxyPassword;
+  private volatile ApacheHttpClientBuilder apacheHttpClientBuilder;
+  private volatile String baseApiUrl;
+  /**
+   * 微信企业号 corpId
+   */
+  private volatile String corpId;
+  /**
+   * 微信企业号应用 ID
+   */
+  private volatile Integer agentId;
+
+  @Override
+  public void setBaseApiUrl(String baseUrl) {
+    this.baseApiUrl = baseUrl;
+  }
+
+  @Override
+  public String getApiUrl(String path) {
+    if (baseApiUrl == null) {
+      baseApiUrl = "https://qyapi.weixin.qq.com";
+    }
+    return baseApiUrl + path;
+  }
+
+  @Override
+  public String getCorpId() {
+    return corpId;
+  }
+
+  @Override
+  public void setCorpId(String corpId) {
+    this.corpId = corpId;
+  }
+
+  @Override
+  public Integer getAgentId() {
+    return agentId;
+  }
+
+  @Override
+  public void setAgentId(Integer agentId) {
+    this.agentId = agentId;
+  }
+
+  @Override
+  public void updateCorpAccessToken(String corpId, Integer agentId, String corpAccessToken, int expiresInSeconds) {
+    wxRedisOps.setValue(generateAccessTokenKey(corpId, agentId), corpAccessToken, expiresInSeconds, TimeUnit.SECONDS);
+  }
+
+  @Override
+  public String getCorpAccessToken(String corpId, Integer agentId) {
+    return wxRedisOps.getValue(generateAccessTokenKey(corpId, agentId));
+  }
+
+  @Override
+  public WxAccessToken getCorpAccessTokenEntity(String corpId, Integer agentId) {
+    String key = generateAccessTokenKey(corpId, agentId);
+    String accessToken = wxRedisOps.getValue(key);
+    Long expire = wxRedisOps.getExpire(key);
+    if (StringUtils.isBlank(accessToken) || expire == null || expire == 0 || expire == -2) {
+      return new WxAccessToken();
+    }
+    WxAccessToken accessTokenEntity = new WxAccessToken();
+    accessTokenEntity.setAccessToken(accessToken);
+    accessTokenEntity.setExpiresIn(Math.max(Math.toIntExact(expire), 0));
+    return accessTokenEntity;
+  }
+
+  @Override
+  public boolean isCorpAccessTokenExpired(String corpId, Integer agentId) {
+    String key = generateAccessTokenKey(corpId, agentId);
+    return wxRedisOps.getExpire(key) == 0L || wxRedisOps.getExpire(key) == -2;
+  }
+
+  @Override
+  public void expireCorpAccessToken(String corpId, Integer agentId) {
+    wxRedisOps.expire(generateAccessTokenKey(corpId, agentId), 0, TimeUnit.SECONDS);
+  }
+
+  @Override
+  public String getHttpProxyHost() {
+    return this.httpProxyHost;
+  }
+
+  /**
+   * Sets http proxy host.
+   *
+   * @param httpProxyHost the http proxy host
+   */
+  public void setHttpProxyHost(String httpProxyHost) {
+    this.httpProxyHost = httpProxyHost;
+  }
+
+  @Override
+  public int getHttpProxyPort() {
+    return this.httpProxyPort;
+  }
+
+  /**
+   * Sets http proxy port.
+   *
+   * @param httpProxyPort the http proxy port
+   */
+  public void setHttpProxyPort(int httpProxyPort) {
+    this.httpProxyPort = httpProxyPort;
+  }
+
+  @Override
+  public String getHttpProxyUsername() {
+    return this.httpProxyUsername;
+  }
+
+  /**
+   * Sets http proxy username.
+   *
+   * @param httpProxyUsername the http proxy username
+   */
+  public void setHttpProxyUsername(String httpProxyUsername) {
+    this.httpProxyUsername = httpProxyUsername;
+  }
+
+  @Override
+  public String getHttpProxyPassword() {
+    return this.httpProxyPassword;
+  }
+
+  /**
+   * Sets http proxy password.
+   *
+   * @param httpProxyPassword the http proxy password
+   */
+  public void setHttpProxyPassword(String httpProxyPassword) {
+    this.httpProxyPassword = httpProxyPassword;
+  }
+
+  @Override
+  public ApacheHttpClientBuilder getApacheHttpClientBuilder() {
+    return this.apacheHttpClientBuilder;
+  }
+
+  /**
+   * Sets apache http client builder.
+   *
+   * @param apacheHttpClientBuilder the apache http client builder
+   */
+  public void setApacheHttpClientBuilder(ApacheHttpClientBuilder apacheHttpClientBuilder) {
+    this.apacheHttpClientBuilder = apacheHttpClientBuilder;
+  }
+
+  @Override
+  public boolean autoRefreshToken() {
+    return true;
+  }
+
+  @Override
+  public Lock getCorpAccessTokenLock(String corpId, Integer agentId) {
+    return this.getLockByKey(String.join(":", corpId, String.valueOf(agentId), LOCKER_CORP_ACCESS_TOKEN));
+  }
+
+  private String generateAccessTokenKey(String corpId, Integer agentId) {
+    return String.join(":", keyPrefix, CG_ACCESS_TOKEN_KEY, corpId, String.valueOf(agentId));
+  }
+
+  private Lock getLockByKey(String key) {
+    return this.wxRedisOps.getLock(String.join(":", keyPrefix, LOCK_KEY, key));
+  }
+}

+ 59 - 8
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java

@@ -73,12 +73,6 @@ public interface WxCpApiPathConsts {
     String GET_STATISTICS = "/cgi-bin/message/get_statistics";
 
     /**
-     * 互联企业发送应用消息
-     * https://developer.work.weixin.qq.com/document/path/90250
-     */
-    String LINKEDCORP_MESSAGE_SEND = "/cgi-bin/linkedcorp/message/send";
-
-    /**
      * 发送「学校通知」
      * https://developer.work.weixin.qq.com/document/path/92321
      */
@@ -90,6 +84,12 @@ public interface WxCpApiPathConsts {
      */
     String MESSAGE_RECALL = "/cgi-bin/message/recall";
 
+    /**
+     * 互联企业发送应用消息
+     * https://developer.work.weixin.qq.com/document/path/90250
+     */
+    String LINKEDCORP_MESSAGE_SEND = "/cgi-bin/linkedcorp/message/send";
+
   }
 
   /**
@@ -826,12 +826,12 @@ public interface WxCpApiPathConsts {
      */
     String GET_ADMIN_LIST = "/cgi-bin/service/get_admin_list";
     /**
-     *  The constant GET_APP_QRCODE.
+     * The constant GET_APP_QRCODE.
      */
     String GET_APP_QRCODE = "/cgi-bin/service/get_app_qrcode";
 
     /**
-     *  The constant CORPID_TO_OPENCORPID.
+     * The constant CORPID_TO_OPENCORPID.
      */
     String CORPID_TO_OPENCORPID = "/cgi-bin/service/corpid_to_opencorpid";
 
@@ -1400,4 +1400,55 @@ public interface WxCpApiPathConsts {
      */
     String GET_RESULT = "/cgi-bin/export/get_result?jobid=%s";
   }
+
+  interface CorpGroup {
+    /**
+     * 获取应用共享信息
+     * https://developer.work.weixin.qq.com/document/path/93403
+     */
+    String LIST_SHARE_APP_INFO = "/cgi-bin/corpgroup/corp/list_app_share_info";
+    /**
+     * 获取下级/下游企业的access_token
+     * https://developer.work.weixin.qq.com/document/path/93359
+     */
+    String CORP_GET_TOKEN = "/cgi-bin/corpgroup/corp/gettoken";
+    /**
+     * 获取下级/下游企业小程序session
+     * https://developer.work.weixin.qq.com/document/path/93355
+     */
+    String MA_TRANSFER_SESSION = "/cgi-bin/miniprogram/transfer_session";
+  }
+
+  interface LinkedCorp {
+    /**
+     * 获取应用的可见范围
+     * https://developer.work.weixin.qq.com/document/path/93172
+     */
+    String GET_PERM_LIST = "/cgi-bin/linkedcorp/agent/get_perm_list";
+    /**
+     * 获取互联企业成员详细信息
+     * https://developer.work.weixin.qq.com/document/path/93171
+     */
+    String GET_USER = "/cgi-bin/linkedcorp/user/get";
+    /**
+     * 获取互联企业部门成员
+     * https://developer.work.weixin.qq.com/document/path/93168
+     */
+    String GET_USER_SIMPLELIST = "/cgi-bin/linkedcorp/user/simplelist";
+    /**
+     * 获取互联企业部门成员详情
+     * https://developer.work.weixin.qq.com/document/path/93169
+     */
+    String GET_USER_LIST = "/cgi-bin/linkedcorp/user/list";
+    /**
+     * 获取互联企业部门列表
+     * https://developer.work.weixin.qq.com/document/path/93170
+     */
+    String GET_DEPARTMENT_LIST = "/cgi-bin/linkedcorp/department/list";
+    /**
+     * 发送应用消息
+     * https://developer.work.weixin.qq.com/document/path/90250
+     */
+    String SENG_MESSAGE="/cgi-bin/linkedcorp/message/send";
+  }
 }

+ 174 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/WxCpCgService.java

@@ -0,0 +1,174 @@
+package me.chanjar.weixin.cp.corpgroup.service;
+
+import me.chanjar.weixin.common.bean.WxAccessToken;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor;
+import me.chanjar.weixin.common.util.http.RequestExecutor;
+import me.chanjar.weixin.common.util.http.RequestHttp;
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.bean.corpgroup.WxCpCorpGroupCorpGetTokenReq;
+import me.chanjar.weixin.cp.bean.corpgroup.WxCpMaTransferSession;
+import me.chanjar.weixin.cp.config.WxCpCorpGroupConfigStorage;
+
+/**
+ * @Project: WxJava
+ * @Package: me.chanjar.weixin.cp.corpgroup.service
+ * @Description: 企业微信企业互联API的Service.
+ * @Author: libo
+ * @Email: 422423229@qq.com
+ * @Date: 1/3/2023 5:37 PM
+ */
+public interface WxCpCgService {
+  /**
+   * Update corp access token.
+   *
+   * @param corpId
+   * @param agentId
+   * @param corpAccessToken  the corp access token
+   * @param expiresInSeconds the expires in seconds
+   */
+  void updateCorpAccessToken(String corpId, Integer agentId, String corpAccessToken, int expiresInSeconds);
+
+  String getCorpAccessToken(String corpId, Integer agentId, Integer businessType) throws WxErrorException;
+
+  String getCorpAccessToken(String corpId, Integer agentId, Integer businessType, boolean forceRefresh) throws WxErrorException;
+
+  /**
+   * 授权企业的access token相关
+   *
+   * @param corpId       the corp id
+   * @param agentId
+   * @param businessType
+   * @return the access token
+   */
+  WxAccessToken getCorpAccessTokenEntity(String corpId, Integer agentId, Integer businessType) throws WxErrorException;
+
+  /**
+   * Gets access token entity.
+   *
+   * @param corpId       the  corp id
+   * @param agentId
+   * @param businessType
+   * @return the access token entity
+   */
+  WxAccessToken getCorpAccessTokenEntity(String corpId, Integer agentId, Integer businessType, boolean forceRefresh) throws WxErrorException;
+
+  /**
+   * Is access token expired boolean.
+   *
+   * @param corpId  the  corp id
+   * @param agentId
+   * @return the boolean
+   */
+  boolean isCorpAccessTokenExpired(String corpId, Integer agentId);
+
+  /**
+   * Expire access token.
+   *
+   * @param corpId  the  corp id
+   * @param agentId
+   */
+  void expireCorpAccessToken(String corpId, Integer agentId);
+
+  /**
+   * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的GET请求.
+   *
+   * @param url        接口地址
+   * @param queryParam 请求参数
+   * @return the string
+   * @throws WxErrorException the wx error exception
+   */
+  String get(String url, String queryParam, WxCpCorpGroupCorpGetTokenReq req) throws WxErrorException;
+
+  /**
+   * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的GET请求.
+   *
+   * @param url                    接口地址
+   * @param queryParam             请求参数
+   * @param withoutCorpAccessToken 请求是否忽略CorpAccessToken 默认不忽略-false
+   * @return the string
+   * @throws WxErrorException the wx error exception
+   */
+  String get(String url, String queryParam, boolean withoutCorpAccessToken, WxCpCorpGroupCorpGetTokenReq req) throws WxErrorException;
+
+  /**
+   * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求.
+   *
+   * @param url      接口地址
+   * @param postData 请求body字符串
+   * @return the string
+   * @throws WxErrorException the wx error exception
+   */
+  String post(String url, String postData, WxCpCorpGroupCorpGetTokenReq req) throws WxErrorException;
+
+  /**
+   * <pre>
+   * Service没有实现某个API的时候,可以用这个,
+   * 比{@link #get}和{@link #post}方法更灵活,可以自己构造RequestExecutor用来处理不同的参数和不同的返回类型。
+   * 可以参考,{@link MediaUploadRequestExecutor}的实现方法
+   * </pre>
+   *
+   * @param <T>      请求值类型
+   * @param <E>      返回值类型
+   * @param executor 执行器
+   * @param uri      请求地址
+   * @param data     参数
+   * @return the t
+   * @throws WxErrorException the wx error exception
+   */
+  <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data, WxCpCorpGroupCorpGetTokenReq req) throws WxErrorException;
+
+  /**
+   * <pre>
+   * 设置当微信系统响应系统繁忙时,要等待多少 retrySleepMillis(ms) * 2^(重试次数 - 1) 再发起重试.
+   * 默认:1000ms
+   * </pre>
+   *
+   * @param retrySleepMillis 重试休息时间
+   */
+  void setRetrySleepMillis(int retrySleepMillis);
+
+  /**
+   * <pre>
+   * 设置当微信系统响应系统繁忙时,最大重试次数.
+   * 默认:5次
+   * </pre>
+   *
+   * @param maxRetryTimes 最大重试次数
+   */
+  void setMaxRetryTimes(int maxRetryTimes);
+
+  /**
+   * 初始化http请求对象
+   */
+  void initHttp();
+
+  void setWxCpCorpGroupConfigStorage(WxCpCorpGroupConfigStorage wxCpCorpGroupConfigStorage);
+
+  WxCpCorpGroupConfigStorage getWxCpCorpGroupConfigStorage();
+  /**
+   * http请求对象.
+   *
+   * @return the request http
+   */
+  RequestHttp<?, ?> getRequestHttp();
+
+  void setWxCpService(WxCpService wxCpService);
+
+  /**
+   * 互联企业的服务类对象
+   *
+   * @return
+   */
+  WxCpLinkedCorpService getLinkedCorpService();
+
+  /**
+   * 获取下级/下游企业小程序session
+   * https://developer.work.weixin.qq.com/document/path/93355
+   * @param userId
+   * @param sessionKey
+   * @return
+   * @throws WxErrorException
+   */
+  WxCpMaTransferSession getCorpTransferSession(String userId, String sessionKey,WxCpCorpGroupCorpGetTokenReq req) throws WxErrorException;
+}

+ 30 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/WxCpLinkedCorpService.java

@@ -0,0 +1,30 @@
+package me.chanjar.weixin.cp.corpgroup.service;
+
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.cp.bean.corpgroup.*;
+import me.chanjar.weixin.cp.bean.linkedcorp.WxCpLinkedCorpAgentPerm;
+import me.chanjar.weixin.cp.bean.linkedcorp.WxCpLinkedCorpDepartment;
+import me.chanjar.weixin.cp.bean.linkedcorp.WxCpLinkedCorpUser;
+
+import java.util.List;
+
+/**
+ * @Project: WxJava
+ * @Package: me.chanjar.weixin.cp.api
+ * @Description: 互联企业相关接口
+ * @Author: libo
+ * @Email: 422423229@qq.com
+ * @Date: 27/2/2023 9:57 PM
+ */
+public interface WxCpLinkedCorpService {
+  WxCpLinkedCorpAgentPerm getLinkedCorpAgentPerm(WxCpCorpGroupCorpGetTokenReq req) throws WxErrorException;
+
+  WxCpLinkedCorpUser getLinkedCorpUser(String userId,WxCpCorpGroupCorpGetTokenReq req) throws WxErrorException;
+
+  List<WxCpLinkedCorpUser> getLinkedCorpSimpleUserList(String departmentId,WxCpCorpGroupCorpGetTokenReq req) throws WxErrorException;
+
+  List<WxCpLinkedCorpUser> getLinkedCorpUserList(String departmentId,WxCpCorpGroupCorpGetTokenReq req) throws WxErrorException;
+
+  List<WxCpLinkedCorpDepartment> getLinkedCorpDepartmentList(String departmentId,WxCpCorpGroupCorpGetTokenReq req) throws WxErrorException;
+
+}

+ 300 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/impl/BaseWxCpCgServiceImpl.java

@@ -0,0 +1,300 @@
+package me.chanjar.weixin.cp.corpgroup.service.impl;
+
+import com.google.gson.JsonObject;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.bean.WxAccessToken;
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxCpErrorMsgEnum;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.error.WxRuntimeException;
+import me.chanjar.weixin.common.util.DataUtils;
+import me.chanjar.weixin.common.util.http.RequestExecutor;
+import me.chanjar.weixin.common.util.http.RequestHttp;
+import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor;
+import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor;
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.bean.corpgroup.WxCpCorpGroupCorpGetTokenReq;
+import me.chanjar.weixin.cp.bean.corpgroup.WxCpMaTransferSession;
+import me.chanjar.weixin.cp.config.WxCpCorpGroupConfigStorage;
+import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
+import me.chanjar.weixin.cp.corpgroup.service.WxCpCgService;
+import me.chanjar.weixin.cp.corpgroup.service.WxCpLinkedCorpService;
+
+import java.io.IOException;
+
+import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.CorpGroup.*;
+
+/**
+ * @Project: WxJava
+ * @Package: me.chanjar.weixin.cp.corpgroup.service.impl
+ * @Description:
+ * @Author: libo
+ * @Email: 422423229@qq.com
+ * @Date: 1/3/2023 5:45 PM
+ */
+@Slf4j
+public abstract class BaseWxCpCgServiceImpl<H, P> implements WxCpCgService, RequestHttp<H, P> {
+
+  WxCpService wxCpService;
+  /**
+   * The Config storage.
+   */
+  protected WxCpCorpGroupConfigStorage configStorage;
+
+  private int retrySleepMillis = 1000;
+  private int maxRetryTimes = 5;
+
+  private final WxCpLinkedCorpService linkedCorpService = new WxCpLinkedCorpServiceImpl(this);
+
+  @Override
+  public void updateCorpAccessToken(String corpId, Integer agentId, String corpAccessToken, int expiresInSeconds) {
+
+  }
+
+  @Override
+  public String getCorpAccessToken(String corpId, Integer agentId, Integer businessType) throws WxErrorException {
+    return getCorpAccessToken(corpId, agentId, businessType, false);
+  }
+
+  @Override
+  public String getCorpAccessToken(String corpId, Integer agentId, Integer businessType, boolean forceRefresh) throws WxErrorException {
+    if (!this.configStorage.isCorpAccessTokenExpired(corpId, agentId) && !forceRefresh) {
+      return this.configStorage.getCorpAccessToken(corpId, agentId);
+    }
+    synchronized (this) {
+      JsonObject jsonObject = new JsonObject();
+      jsonObject.addProperty("corpid", corpId);
+      jsonObject.addProperty("agentid", agentId);
+      jsonObject.addProperty("business_type", businessType);
+      final String url = this.wxCpService.getWxCpConfigStorage().getApiUrl(CORP_GET_TOKEN);
+      String responseContent = this.wxCpService.post(url, jsonObject);
+      WxAccessToken corpToken = WxAccessToken.fromJson(responseContent);
+      this.configStorage.updateCorpAccessToken(corpId, agentId, corpToken.getAccessToken(), corpToken.getExpiresIn());
+    }
+    return this.configStorage.getCorpAccessToken(corpId, agentId);
+  }
+
+  @Override
+  public WxAccessToken getCorpAccessTokenEntity(String corpId, Integer agentId, Integer businessType) throws WxErrorException {
+    return this.getCorpAccessTokenEntity(corpId, agentId, businessType, false);
+  }
+
+
+  @Override
+  public WxAccessToken getCorpAccessTokenEntity(String corpId, Integer agentId, Integer businessType, boolean forceRefresh) throws WxErrorException {
+    return this.configStorage.getCorpAccessTokenEntity(corpId, agentId);
+  }
+
+  @Override
+  public boolean isCorpAccessTokenExpired(String corpId, Integer agentId) {
+    return this.configStorage.isCorpAccessTokenExpired(corpId, agentId);
+  }
+
+  @Override
+  public void expireCorpAccessToken(String corpId, Integer agentId) {
+    this.configStorage.expireCorpAccessToken(corpId, agentId);
+  }
+
+  @Override
+  public String get(String url, String queryParam, WxCpCorpGroupCorpGetTokenReq req) throws WxErrorException {
+    return execute(SimpleGetRequestExecutor.create(this), url, queryParam, req);
+  }
+
+  @Override
+  public String get(String url, String queryParam, boolean withoutCorpAccessToken, WxCpCorpGroupCorpGetTokenReq req) throws WxErrorException {
+    return execute(SimpleGetRequestExecutor.create(this), url, queryParam, withoutCorpAccessToken, req);
+  }
+
+  @Override
+  public String post(String url, String postData, WxCpCorpGroupCorpGetTokenReq req) throws WxErrorException {
+    return execute(SimplePostRequestExecutor.create(this), url, postData, false, req);
+  }
+
+  /**
+   * Post string.
+   *
+   * @param url                    the url
+   * @param postData               the post data
+   * @param withoutCorpAccessToken the without Corp access token
+   * @return the string
+   * @throws WxErrorException the wx error exception
+   */
+  public String post(String url, String postData, boolean withoutCorpAccessToken, WxCpCorpGroupCorpGetTokenReq req) throws WxErrorException {
+    return execute(SimplePostRequestExecutor.create(this), url, postData, withoutCorpAccessToken, req);
+  }
+
+  /**
+   * 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求.
+   */
+  @Override
+  public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data, WxCpCorpGroupCorpGetTokenReq req) throws WxErrorException {
+    return execute(executor, uri, data, false, req);
+  }
+
+  /**
+   * Execute t.
+   *
+   * @param <T>                    the type parameter
+   * @param <E>                    the type parameter
+   * @param executor               the executor
+   * @param uri                    the uri
+   * @param data                   the data
+   * @param withoutCorpAccessToken the without Corp access token
+   * @return the t
+   * @throws WxErrorException the wx error exception
+   */
+  public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data, boolean withoutCorpAccessToken, WxCpCorpGroupCorpGetTokenReq req) throws WxErrorException {
+    int retryTimes = 0;
+    do {
+      try {
+        return this.executeInternal(executor, uri, data, withoutCorpAccessToken, req);
+      } catch (WxErrorException e) {
+        if (retryTimes + 1 > this.maxRetryTimes) {
+          log.warn("重试达到最大次数【{}】", this.maxRetryTimes);
+          //最后一次重试失败后,直接抛出异常,不再等待
+          throw new WxRuntimeException("微信服务端异常,超出重试次数");
+        }
+
+        WxError error = e.getError();
+        /*
+         * -1 系统繁忙, 1000ms后重试
+         */
+        if (error.getErrorCode() == -1) {
+          int sleepMillis = this.retrySleepMillis * (1 << retryTimes);
+          try {
+            log.debug("微信系统繁忙,{} ms 后重试(第{}次)", sleepMillis, retryTimes + 1);
+            Thread.sleep(sleepMillis);
+          } catch (InterruptedException e1) {
+            Thread.currentThread().interrupt();
+          }
+        } else {
+          throw e;
+        }
+      }
+    } while (retryTimes++ < this.maxRetryTimes);
+
+    log.warn("重试达到最大次数【{}】", this.maxRetryTimes);
+    throw new WxRuntimeException("微信服务端异常,超出重试次数");
+  }
+
+  /**
+   * Execute internal t.
+   *
+   * @param <T>      the type parameter
+   * @param <E>      the type parameter
+   * @param executor the executor
+   * @param uri      the uri
+   * @param data     the data
+   * @return the t
+   * @throws WxErrorException the wx error exception
+   */
+  protected <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data, WxCpCorpGroupCorpGetTokenReq req) throws WxErrorException {
+    return executeInternal(executor, uri, data, false, req);
+  }
+
+  /**
+   * Execute internal t.
+   *
+   * @param <T>                    the type parameter
+   * @param <E>                    the type parameter
+   * @param executor               the executor
+   * @param uri                    the uri
+   * @param data                   the data
+   * @param withoutCorpAccessToken the without Corp access token
+   * @return the t
+   * @throws WxErrorException the wx error exception
+   */
+  protected <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data,
+                                     boolean withoutCorpAccessToken, WxCpCorpGroupCorpGetTokenReq req) throws WxErrorException {
+    E dataForLog = DataUtils.handleDataWithSecret(data);
+
+    if (uri.contains("access_token=")) {
+      throw new IllegalArgumentException("uri参数中不允许有access_token: " + uri);
+    }
+    String uriWithAccessToken;
+    if (!withoutCorpAccessToken) {
+      String corpAccessToken = getCorpAccessToken(req.getCorpId(), req.getAgentId(), req.getBusinessType());
+      uriWithAccessToken = uri + (uri.contains("?") ? "&" : "?") + "access_token=" + corpAccessToken;
+    } else {
+      uriWithAccessToken = uri;
+    }
+
+
+    try {
+      T result = executor.execute(uriWithAccessToken, data, WxType.CP);
+      log.debug("\n【请求地址】: {}\n【请求参数】:{}\n【响应数据】:{}", uriWithAccessToken, dataForLog, result);
+      return result;
+    } catch (WxErrorException e) {
+      WxError error = e.getError();
+      /*
+       * 发生以下情况时尝试刷新Corp_access_token
+       * 42009 Corp_access_token已过期
+       */
+      if (error.getErrorCode() == WxCpErrorMsgEnum.CODE_42009.getCode()) {
+        // 强制设置wxCpCorpGroupConfigStorage它的corp access token过期了,这样在下一次请求里就会刷新corp access token
+        this.configStorage.expireCorpAccessToken(req.getCorpId(), req.getAgentId());
+        if (this.getWxCpCorpGroupConfigStorage().autoRefreshToken()) {
+          log.warn("即将重新获取新的access_token,错误代码:{},错误信息:{}", error.getErrorCode(), error.getErrorMsg());
+          return this.execute(executor, uri, data, req);
+        }
+      }
+
+      if (error.getErrorCode() != 0) {
+        log.error("\n【请求地址】: {}\n【请求参数】:{}\n【错误信息】:{}", uriWithAccessToken, dataForLog, error);
+        throw new WxErrorException(error, e);
+      }
+      return null;
+    } catch (IOException e) {
+      log.error("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uriWithAccessToken, dataForLog, e.getMessage());
+      throw new WxRuntimeException(e);
+    }
+  }
+
+  @Override
+  public void setWxCpCorpGroupConfigStorage(WxCpCorpGroupConfigStorage wxCpCorpGroupConfigStorage) {
+    this.configStorage = wxCpCorpGroupConfigStorage;
+    this.initHttp();
+  }
+
+  @Override
+  public WxCpCorpGroupConfigStorage getWxCpCorpGroupConfigStorage() {
+    return configStorage;
+  }
+
+  @Override
+  public void setRetrySleepMillis(int retrySleepMillis) {
+    this.retrySleepMillis = retrySleepMillis;
+  }
+
+
+  @Override
+  public void setMaxRetryTimes(int maxRetryTimes) {
+    this.maxRetryTimes = maxRetryTimes;
+  }
+
+  @Override
+  public RequestHttp<?, ?> getRequestHttp() {
+    return this;
+  }
+
+  @Override
+  public void setWxCpService(WxCpService wxCpService) {
+    this.wxCpService = wxCpService;
+  }
+
+  @Override
+  public WxCpLinkedCorpService getLinkedCorpService() {
+    return linkedCorpService;
+  }
+
+  @Override
+  public WxCpMaTransferSession getCorpTransferSession(String userId, String sessionKey, WxCpCorpGroupCorpGetTokenReq req) throws WxErrorException {
+    final String url = this.wxCpService.getWxCpConfigStorage().getApiUrl(MA_TRANSFER_SESSION);
+    JsonObject jsonObject = new JsonObject();
+    jsonObject.addProperty("userid", userId);
+    jsonObject.addProperty("session_key", sessionKey);
+    String result = this.post(url, jsonObject.toString(), req);
+    return WxCpMaTransferSession.fromJson(result);
+  }
+}

+ 57 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/impl/WxCpCgServiceApacheHttpClientImpl.java

@@ -0,0 +1,57 @@
+package me.chanjar.weixin.cp.corpgroup.service.impl;
+
+import com.fasterxml.jackson.databind.ser.Serializers;
+import me.chanjar.weixin.common.util.http.HttpType;
+import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
+import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
+import org.apache.http.HttpHost;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.impl.client.CloseableHttpClient;
+
+/**
+ * @Project: WxJava
+ * @Package: me.chanjar.weixin.cp.corpgroup.service.impl
+ * @Description:
+ * @Author: libo
+ * @Email: 422423229@qq.com
+ * @Date: 1/3/2023 6:16 PM
+ */
+public class WxCpCgServiceApacheHttpClientImpl extends BaseWxCpCgServiceImpl<CloseableHttpClient, HttpHost> {
+  private CloseableHttpClient httpClient;
+  private HttpHost httpProxy;
+
+  @Override
+  public CloseableHttpClient getRequestHttpClient() {
+    return httpClient;
+  }
+
+  @Override
+  public HttpHost getRequestHttpProxy() {
+    return httpProxy;
+  }
+
+  @Override
+  public HttpType getRequestType() {
+    return HttpType.APACHE_HTTP;
+  }
+
+  @Override
+  public void initHttp() {
+    ApacheHttpClientBuilder apacheHttpClientBuilder = this.configStorage.getApacheHttpClientBuilder();
+    if (null == apacheHttpClientBuilder) {
+      apacheHttpClientBuilder = DefaultApacheHttpClientBuilder.get();
+    }
+
+    apacheHttpClientBuilder.httpProxyHost(this.configStorage.getHttpProxyHost())
+      .httpProxyPort(this.configStorage.getHttpProxyPort())
+      .httpProxyUsername(this.configStorage.getHttpProxyUsername())
+      .httpProxyPassword(this.configStorage.getHttpProxyPassword());
+
+    if (this.configStorage.getHttpProxyHost() != null && this.configStorage.getHttpProxyPort() > 0) {
+      this.httpProxy = new HttpHost(this.configStorage.getHttpProxyHost(), this.configStorage.getHttpProxyPort());
+    }
+
+    this.httpClient = apacheHttpClientBuilder.build();
+  }
+
+}

+ 96 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/corpgroup/service/impl/WxCpLinkedCorpServiceImpl.java

@@ -0,0 +1,96 @@
+package me.chanjar.weixin.cp.corpgroup.service.impl;
+
+import com.google.gson.JsonObject;
+import com.google.gson.reflect.TypeToken;
+import lombok.RequiredArgsConstructor;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.json.GsonParser;
+import me.chanjar.weixin.cp.bean.corpgroup.WxCpCorpGroupCorpGetTokenReq;
+import me.chanjar.weixin.cp.corpgroup.service.WxCpCgService;
+import me.chanjar.weixin.cp.corpgroup.service.WxCpLinkedCorpService;
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.bean.linkedcorp.WxCpLinkedCorpAgentPerm;
+import me.chanjar.weixin.cp.bean.linkedcorp.WxCpLinkedCorpDepartment;
+import me.chanjar.weixin.cp.bean.linkedcorp.WxCpLinkedCorpUser;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.util.List;
+
+import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.LinkedCorp.*;
+
+/**
+ * @Project: WxJava
+ * @Package: me.chanjar.weixin.cp.api.impl
+ * @Description: 互联企业相关接口实现类
+ * @Author: libo
+ * @Email: 422423229@qq.com
+ * @Date: 27/2/2023 10:02 PM
+ */
+@RequiredArgsConstructor
+public class WxCpLinkedCorpServiceImpl implements WxCpLinkedCorpService {
+  private final WxCpCgService cpCgService;
+
+  @Override
+  public WxCpLinkedCorpAgentPerm getLinkedCorpAgentPerm(WxCpCorpGroupCorpGetTokenReq req) throws WxErrorException {
+    final String url = this.cpCgService.getWxCpCorpGroupConfigStorage().getApiUrl(GET_PERM_LIST);
+    JsonObject jsonObject = new JsonObject();
+    String responseContent = this.cpCgService.post(url, jsonObject.toString(), req);
+    return WxCpGsonBuilder.create().fromJson(responseContent, WxCpLinkedCorpAgentPerm.class);
+  }
+
+  @Override
+  public WxCpLinkedCorpUser getLinkedCorpUser(String userId, WxCpCorpGroupCorpGetTokenReq req) throws WxErrorException {
+    final String url = this.cpCgService.getWxCpCorpGroupConfigStorage().getApiUrl(GET_USER);
+    JsonObject jsonObject = new JsonObject();
+    jsonObject.addProperty("userid", userId);
+    String responseContent = this.cpCgService.post(url, jsonObject.toString(), req);
+    JsonObject tmpJson = GsonParser.parse(responseContent);
+
+    return WxCpGsonBuilder.create().fromJson(tmpJson.get("user_info"),
+      new TypeToken<WxCpLinkedCorpUser>() {
+      }.getType()
+    );
+  }
+
+  @Override
+  public List<WxCpLinkedCorpUser> getLinkedCorpSimpleUserList(String departmentId, WxCpCorpGroupCorpGetTokenReq req) throws WxErrorException {
+    final String url = this.cpCgService.getWxCpCorpGroupConfigStorage().getApiUrl(GET_USER_SIMPLELIST);
+    JsonObject jsonObject = new JsonObject();
+    jsonObject.addProperty("department_id", departmentId);
+    String responseContent = this.cpCgService.post(url, jsonObject.toString(),req);
+    JsonObject tmpJson = GsonParser.parse(responseContent);
+
+    return WxCpGsonBuilder.create().fromJson(tmpJson.get("userlist"),
+      new TypeToken<List<WxCpLinkedCorpUser>>() {
+      }.getType()
+    );
+  }
+
+  @Override
+  public List<WxCpLinkedCorpUser> getLinkedCorpUserList(String departmentId, WxCpCorpGroupCorpGetTokenReq req) throws WxErrorException {
+    final String url = this.cpCgService.getWxCpCorpGroupConfigStorage().getApiUrl(GET_USER_LIST);
+    JsonObject jsonObject = new JsonObject();
+    jsonObject.addProperty("department_id", departmentId);
+    String responseContent = this.cpCgService.post(url, jsonObject.toString(),req);
+    JsonObject tmpJson = GsonParser.parse(responseContent);
+
+    return WxCpGsonBuilder.create().fromJson(tmpJson.get("userlist"),
+      new TypeToken<List<WxCpLinkedCorpUser>>() {
+      }.getType()
+    );
+  }
+
+  @Override
+  public List<WxCpLinkedCorpDepartment> getLinkedCorpDepartmentList(String departmentId, WxCpCorpGroupCorpGetTokenReq req) throws WxErrorException {
+    final String url = this.cpCgService.getWxCpCorpGroupConfigStorage().getApiUrl(GET_DEPARTMENT_LIST);
+    JsonObject jsonObject = new JsonObject();
+    jsonObject.addProperty("department_id", departmentId);
+    String responseContent = this.cpCgService.post(url, jsonObject.toString(),req);
+    JsonObject tmpJson = GsonParser.parse(responseContent);
+
+    return WxCpGsonBuilder.create().fromJson(tmpJson.get("department_list"),
+      new TypeToken<List<WxCpLinkedCorpDepartment>>() {
+      }.getType()
+    );
+  }
+}

+ 43 - 0
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpCorpGroupServiceImplTest.java

@@ -0,0 +1,43 @@
+package me.chanjar.weixin.cp.api.impl;
+
+import com.google.gson.JsonObject;
+import com.google.inject.Inject;
+import me.chanjar.weixin.common.bean.WxAccessToken;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.cp.api.ApiTestModule;
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.bean.corpgroup.*;
+import me.chanjar.weixin.cp.corpgroup.service.WxCpCgService;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import java.util.List;
+
+import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.CorpGroup.CORP_GET_TOKEN;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+/**
+ * @Project: WxJava
+ * @Package: me.chanjar.weixin.cp.api.impl
+ * @Description:
+ * @Author: libo
+ * @Email: 422423229@qq.com
+ * @Date: 28/2/2023 7:06 PM
+ */
+@Guice(modules = ApiTestModule.class)
+public class WxCpCorpGroupServiceImplTest {
+  @Inject
+  private WxCpService wxService;
+
+  @Test
+  public void testListAppShareInfo() throws WxErrorException {
+    Integer agentId = wxService.getWxCpConfigStorage().getAgentId();
+    Integer businessType = 0;
+    String corpId = null;
+    Integer limit = null;
+    String cursor = null;
+    List<WxCpCorpGroupCorp> resp = wxService.getCorpGroupService().listAppShareInfo(agentId, businessType, corpId, limit, cursor);
+    assertNotNull(resp);
+  }
+}

+ 1 - 1
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpMeetingServiceImplTest.java

@@ -79,7 +79,7 @@ public class WxCpMeetingServiceImplTest {
     wxCpMeeting.setTitle("修改会议");
     wxCpMeeting.setDescription("修改会议描述");
     WxCpMeetingUpdateResult wxCpMeetingUpdateResult = wxCpMeetingService.update(wxCpMeeting);
-    assertEquals(wxCpMeetingUpdateResult.getErrcode(), 0L);
+    assertEquals(wxCpMeetingUpdateResult.getErrcode().longValue(), 0L);
     /*
       测试 获取会议详情
      */

+ 114 - 0
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/corpgroup/service/impl/WxCpCgServiceApacheHttpClientImplTest.java

@@ -0,0 +1,114 @@
+package me.chanjar.weixin.cp.corpgroup.service.impl;
+
+import com.google.gson.JsonObject;
+import com.google.inject.Inject;
+import me.chanjar.weixin.common.bean.WxAccessToken;
+import me.chanjar.weixin.cp.api.ApiTestModule;
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.api.impl.WxCpServiceApacheHttpClientImpl;
+import me.chanjar.weixin.cp.bean.WxCpAgent;
+import me.chanjar.weixin.cp.bean.WxCpUser;
+import me.chanjar.weixin.cp.bean.corpgroup.WxCpCorpGroupCorpGetTokenReq;
+import me.chanjar.weixin.cp.bean.corpgroup.WxCpMaTransferSession;
+import me.chanjar.weixin.cp.config.WxCpCorpGroupConfigStorage;
+import me.chanjar.weixin.cp.config.impl.WxCpCorpGroupDefaultConfigImpl;
+import me.chanjar.weixin.cp.config.impl.WxCpCorpGroupRedissonConfigImpl;
+import me.chanjar.weixin.cp.constant.WxCpApiPathConsts;
+import me.chanjar.weixin.cp.corpgroup.service.WxCpCgService;
+
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+import org.mockito.Mockito;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.CorpGroup.MA_TRANSFER_SESSION;
+import static org.testng.Assert.assertNotNull;
+
+/**
+ * @Project: WxJava
+ * @Package: me.chanjar.weixin.cp.corpgroup.service.impl
+ * @Description:
+ * @Author: libo
+ * @Email: 422423229@qq.com
+ * @Date: 2/3/2023 4:00 PM
+ */
+@Guice(modules = ApiTestModule.class)
+public class WxCpCgServiceApacheHttpClientImplTest {
+  private final WxCpCgService cgService = Mockito.spy(new WxCpCgServiceApacheHttpClientImpl());
+  WxCpCorpGroupConfigStorage wxCpCorpGroupConfigStorage;
+  @Inject
+  WxCpService wxCpService;
+
+  //下游企业的corpId
+  String corpId = "";
+  //下游企业的agentId
+  int agentId = 0;
+  int businessType = 0;
+  String userId = "";
+  WxCpCorpGroupCorpGetTokenReq wxCpCorpGroupCorpGetTokenReq;
+
+  @BeforeMethod
+  public void setUp() {
+    cgService.setWxCpCorpGroupConfigStorage(wxCpCorpGroupConfigStorage());
+    cgService.setWxCpService(wxCpService);
+
+    wxCpCorpGroupCorpGetTokenReq = new WxCpCorpGroupCorpGetTokenReq();
+    wxCpCorpGroupCorpGetTokenReq.setCorpId(corpId);
+    wxCpCorpGroupCorpGetTokenReq.setAgentId(agentId);
+    wxCpCorpGroupCorpGetTokenReq.setBusinessType(businessType);
+  }
+
+  public WxCpCorpGroupConfigStorage wxCpCorpGroupConfigStorage() {
+    wxCpCorpGroupConfigStorage = new WxCpCorpGroupDefaultConfigImpl();
+    wxCpCorpGroupConfigStorage.setCorpId(wxCpService.getWxCpConfigStorage().getCorpId());
+    wxCpCorpGroupConfigStorage.setAgentId(wxCpService.getWxCpConfigStorage().getAgentId());
+    return wxCpCorpGroupConfigStorage;
+  }
+
+  @Test
+  public void testGetCorpAccessToken() throws Exception {
+    String corpAccessToken = cgService.getCorpAccessToken(corpId, agentId, businessType);
+    assertNotNull(corpAccessToken);
+  }
+
+  /**
+   * 通讯录-读取成员
+   *
+   * @throws Exception
+   */
+  @Test
+  public void testGetCorpUser() throws Exception {
+    String result = cgService.get(wxCpService.getWxCpConfigStorage().getApiUrl(WxCpApiPathConsts.User.USER_GET + userId), null, wxCpCorpGroupCorpGetTokenReq);
+    assertNotNull(result);
+    WxCpUser wxCpUser = WxCpUser.fromJson(result);
+    assertNotNull(wxCpUser.getUserId());
+  }
+
+  /**
+   * 应用管理-获取指定的应用详情
+   *
+   * @throws Exception
+   */
+  @Test
+  public void testGetAgent() throws Exception {
+
+    String result = cgService.get(wxCpService.getWxCpConfigStorage().getApiUrl(String.format(WxCpApiPathConsts.Agent.AGENT_GET, agentId)), null, wxCpCorpGroupCorpGetTokenReq);
+    assertNotNull(result);
+    WxCpAgent wxCpAgent = WxCpAgent.fromJson(result);
+    assertNotNull(wxCpAgent.getAgentId());
+  }
+
+  /**
+   * 获取下级/下游企业小程序session
+   *
+   * @throws Exception
+   */
+  @Test
+  public void testGetTransferSession() throws Exception {
+    String sessionKey = "";
+
+    WxCpMaTransferSession wxCpMaTransferSession = cgService.getCorpTransferSession(userId, sessionKey, wxCpCorpGroupCorpGetTokenReq);
+    assertNotNull(wxCpMaTransferSession);
+  }
+}

+ 98 - 0
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/corpgroup/service/impl/WxCpLinkedCorpServiceImplTest.java

@@ -0,0 +1,98 @@
+package me.chanjar.weixin.cp.corpgroup.service.impl;
+
+import com.google.inject.Inject;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.cp.api.ApiTestModule;
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.bean.corpgroup.WxCpCorpGroupCorpGetTokenReq;
+import me.chanjar.weixin.cp.bean.linkedcorp.WxCpLinkedCorpAgentPerm;
+import me.chanjar.weixin.cp.bean.linkedcorp.WxCpLinkedCorpDepartment;
+import me.chanjar.weixin.cp.bean.linkedcorp.WxCpLinkedCorpUser;
+import me.chanjar.weixin.cp.config.WxCpCorpGroupConfigStorage;
+import me.chanjar.weixin.cp.config.impl.WxCpCorpGroupDefaultConfigImpl;
+import me.chanjar.weixin.cp.corpgroup.service.WxCpCgService;
+import org.mockito.Mockito;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import java.util.List;
+
+import static org.testng.Assert.assertNotNull;
+
+/**
+ * @Project: WxJava
+ * @Package: me.chanjar.weixin.cp.api.impl
+ * @Description:
+ * @Author: libo
+ * @Email: 422423229@qq.com
+ * @Date: 28/2/2023 7:06 PM
+ */
+@Guice(modules = ApiTestModule.class)
+public class WxCpLinkedCorpServiceImplTest {
+  WxCpCorpGroupConfigStorage wxCpCorpGroupConfigStorage;
+  @Inject
+  WxCpService wxCpService;
+
+  WxCpCgService cgService;
+
+  //下游企业的corpId
+  String corpId = "";
+  //下游企业的agentId
+  int agentId = 0;
+  int businessType = 0;
+  //CorpID/UserID
+  String corpUserId = "";
+  String departmentId = "";
+  WxCpCorpGroupCorpGetTokenReq wxCpCorpGroupCorpGetTokenReq;
+
+  @BeforeMethod
+  public void setUp() {
+    cgService = new WxCpCgServiceApacheHttpClientImpl();
+    cgService.setWxCpCorpGroupConfigStorage(wxCpCorpGroupConfigStorage());
+    cgService.setWxCpService(wxCpService);
+
+    wxCpCorpGroupCorpGetTokenReq = new WxCpCorpGroupCorpGetTokenReq();
+    wxCpCorpGroupCorpGetTokenReq.setCorpId(corpId);
+    wxCpCorpGroupCorpGetTokenReq.setAgentId(agentId);
+    wxCpCorpGroupCorpGetTokenReq.setBusinessType(businessType);
+  }
+
+  public WxCpCorpGroupConfigStorage wxCpCorpGroupConfigStorage() {
+    wxCpCorpGroupConfigStorage = new WxCpCorpGroupDefaultConfigImpl();
+    wxCpCorpGroupConfigStorage.setCorpId(wxCpService.getWxCpConfigStorage().getCorpId());
+    wxCpCorpGroupConfigStorage.setAgentId(wxCpService.getWxCpConfigStorage().getAgentId());
+    return wxCpCorpGroupConfigStorage;
+  }
+
+  @Test
+  public void testGetLinkedCorpAgentPerm() throws WxErrorException {
+    WxCpLinkedCorpAgentPerm resp = cgService.getLinkedCorpService().getLinkedCorpAgentPerm(wxCpCorpGroupCorpGetTokenReq);
+    assertNotNull(resp);
+  }
+
+  @Test
+  public void testGetLinkedCorpUser() throws WxErrorException {
+    WxCpLinkedCorpUser resp = cgService.getLinkedCorpService().getLinkedCorpUser(corpUserId, wxCpCorpGroupCorpGetTokenReq);
+    assertNotNull(resp);
+  }
+
+  @Test
+  public void testGetLinkedCorpSimpleUserList() throws WxErrorException {
+    List<WxCpLinkedCorpUser> resp = cgService.getLinkedCorpService().getLinkedCorpSimpleUserList(departmentId, wxCpCorpGroupCorpGetTokenReq);
+    assertNotNull(resp);
+  }
+
+  @Test
+  public void testGetLinkedCorpUserList() throws WxErrorException {
+    List<WxCpLinkedCorpUser> resp = cgService.getLinkedCorpService().getLinkedCorpUserList(departmentId, wxCpCorpGroupCorpGetTokenReq);
+    assertNotNull(resp);
+  }
+
+  @Test
+  public void testGetLinkedCorpDepartmentList() throws WxErrorException {
+    List<WxCpLinkedCorpDepartment> resp = cgService.getLinkedCorpService().getLinkedCorpDepartmentList(departmentId, wxCpCorpGroupCorpGetTokenReq);
+    assertNotNull(resp);
+  }
+
+}

+ 0 - 3
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImplTest.java

@@ -8,11 +8,9 @@ import me.chanjar.weixin.cp.bean.WxCpTpCorp;
 import me.chanjar.weixin.cp.bean.WxCpTpPermanentCodeInfo;
 import me.chanjar.weixin.cp.bean.WxTpCustomizedAuthUrl;
 import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
-import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
 import me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImpl;
 import me.chanjar.weixin.cp.config.impl.WxCpTpRedissonConfigImpl;
 import me.chanjar.weixin.cp.tp.service.WxCpTpService;
-import org.apache.http.util.Asserts;
 import org.mockito.Mockito;
 import org.redisson.Redisson;
 import org.redisson.api.RedissonClient;
@@ -22,7 +20,6 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
 import java.util.Objects;