From 0af608f119d028a4dbe8f5964e0648d43b96bb5f Mon Sep 17 00:00:00 2001 From: uianz <33689429+uianz@users.noreply.github.com> Date: Tue, 29 Dec 2020 10:01:22 +0800 Subject: [PATCH] =?UTF-8?q?:new:=20#1947=20=E3=80=90=E4=BC=81=E4=B8=9A?= =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E3=80=91=E7=AC=AC=E4=B8=89=E6=96=B9=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E5=A2=9E=E5=8A=A0=E7=94=A8=E6=88=B7=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E3=80=81OA=E3=80=81=E5=A4=96=E9=83=A8=E8=81=94=E7=B3=BB?= =?UTF-8?q?=E4=BA=BA=E3=80=81=E9=83=A8=E9=97=A8=E3=80=81=E9=80=9A=E8=AE=AF?= =?UTF-8?q?=E5=BD=95=E6=90=9C=E7=B4=A2=E7=AD=89=E7=9B=B8=E5=85=B3=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E7=9A=84=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 修复getSuiteJsApiTicket和getAuthCorpJsApiTicket方法代码错误 * 增加企业微信第三方应用用户管理、oa、外部联系人、部门、通讯录搜索相关接口实现 --- .../weixin/cp/bean/WxCpTpContactSearch.java | 62 ++++++ .../cp/bean/WxCpTpContactSearchResp.java | 53 +++++ .../chanjar/weixin/cp/bean/WxCpTpDepart.java | 31 +++ .../cp/bean/WxCpUserExternalContactInfo.java | 142 ++++++++++++ .../chanjar/weixin/cp/bean/WxTpLoginInfo.java | 65 ++++++ .../weixin/cp/config/WxCpTpConfigStorage.java | 10 + .../config/impl/WxCpTpDefaultConfigImpl.java | 37 +++- .../config/impl/WxCpTpRedissonConfigImpl.java | 36 +++- .../weixin/cp/constant/WxCpApiPathConsts.java | 6 + .../cp/tp/service/WxCpTpContactService.java | 22 ++ .../tp/service/WxCpTpDepartmentService.java | 75 +++++++ .../cp/tp/service/WxCpTpMediaService.java | 60 ++++++ .../weixin/cp/tp/service/WxCpTpOAService.java | 60 ++++++ .../weixin/cp/tp/service/WxCpTpService.java | 75 +++++++ .../cp/tp/service/WxCpTpUserService.java | 172 +++++++++++++++ .../service/impl/BaseWxCpTpServiceImpl.java | 125 +++++++++-- .../impl/WxCpTpContactServiceImpl.java | 29 +++ .../impl/WxCpTpDepartmentServiceImpl.java | 70 ++++++ .../service/impl/WxCpTpMediaServiceImpl.java | 50 +++++ .../tp/service/impl/WxCpTpOAServiceImpl.java | 66 ++++++ .../service/impl/WxCpTpUserServiceImpl.java | 204 ++++++++++++++++++ 21 files changed, 1423 insertions(+), 27 deletions(-) create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpContactSearch.java create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpContactSearchResp.java create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpDepart.java create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalContactInfo.java create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxTpLoginInfo.java create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpContactService.java create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpDepartmentService.java create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpMediaService.java create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpOAService.java create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpUserService.java create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpContactServiceImpl.java create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpDepartmentServiceImpl.java create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpMediaServiceImpl.java create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpOAServiceImpl.java create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpUserServiceImpl.java diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpContactSearch.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpContactSearch.java new file mode 100644 index 000000000..00fce5140 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpContactSearch.java @@ -0,0 +1,62 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.experimental.Accessors; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +/** + * @author uianz + * @description + * @since 2020/12/23 下午 02:43 + */ +@Data +@Accessors(chain = true) +public class WxCpTpContactSearch { + + /** + * 查询的企业corpid + */ + @SerializedName("auth_corpid") + private String authCorpId; + + /** + * 搜索关键词。当查询用户时应为用户名称、名称拼音或者英文名;当查询部门时应为部门名称或者部门名称拼音 + */ + @SerializedName("query_word") + private String queryWord; + + /** + * 查询类型 1:查询用户,返回用户userid列表 2:查询部门,返回部门id列表。 不填该字段或者填0代表同时查询部门跟用户 + */ + @SerializedName("query_type") + private Integer type; + + /** + * 应用id,若非0则只返回应用可见范围内的用户或者部门信息 + */ + @SerializedName("agentid") + private Integer agentId; + + /** + * 查询的偏移量,每次调用的offset在上一次offset基础上加上limit + */ + @SerializedName("offset") + private Integer offset; + + /** + * 查询返回的最大数量,默认为50,最多为200,查询返回的数量可能小于limit指定的值 + */ + @SerializedName("limit") + private Integer limit; + + /** + * 如果需要精确匹配用户名称或者部门名称或者英文名,不填则默认为模糊匹配;1:匹配用户名称或者部门名称 2:匹配用户英文名 + */ + @SerializedName("full_match_field") + private Integer fullMatchField; + + public String toJson() { + return WxCpGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpContactSearchResp.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpContactSearchResp.java new file mode 100644 index 000000000..959a55f9c --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpContactSearchResp.java @@ -0,0 +1,53 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.List; + +/** + * @author uianz + * @description + * @since 2020/12/23 下午 02:55 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class WxCpTpContactSearchResp extends WxCpBaseResp { + + @SerializedName("is_last") + private Boolean isLast; + + @SerializedName("query_result") + private QueryResult queryResult; + + @Data + public static class QueryResult { + + @SerializedName("user") + private User user; + @SerializedName("party") + private Party party; + + @Data + public static class User { + @SerializedName("userid") + private List userid; + @SerializedName("open_userid") + private List openUserId; + } + + @Data + public static class Party { + @SerializedName("department_id") + private List departmentId; + } + + } + + public static WxCpTpContactSearchResp fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpTpContactSearchResp.class); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpDepart.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpDepart.java new file mode 100644 index 000000000..ab94a6b6b --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpDepart.java @@ -0,0 +1,31 @@ +package me.chanjar.weixin.cp.bean; + +import lombok.Data; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.io.Serializable; + +/** + * 企业微信的部门. + * + * @author Daniel Qian + */ +@Data +public class WxCpTpDepart implements Serializable { + private static final long serialVersionUID = -5028321625140879571L; + + private Integer id; + private String name; + private String enName; + private Integer parentid; + private Integer order; + + public static WxCpTpDepart fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpTpDepart.class); + } + + public String toJson() { + return WxCpGsonBuilder.create().toJson(this); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalContactInfo.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalContactInfo.java new file mode 100644 index 000000000..e9a8d5907 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalContactInfo.java @@ -0,0 +1,142 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.*; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.List; + +/** + *
+ * 外部联系人详情
+ * Created by Binary Wang on 2018/9/16.
+ * 参考文档:https://work.weixin.qq.com/api/doc#13878
+ * 
+ * + * @author Binary Wang + */ +@Getter +@Setter +public class WxCpUserExternalContactInfo { + @SerializedName("external_contact") + private ExternalContact externalContact; + + @SerializedName("follow_user") + private List followedUsers; + + @Getter + @Setter + public static class ExternalContact { + @SerializedName("external_userid") + private String externalUserId; + + @SerializedName("position") + private String position; + + @SerializedName("name") + private String name; + + @SerializedName("avatar") + private String avatar; + + @SerializedName("corp_name") + private String corpName; + + @SerializedName("corp_full_name") + private String corpFullName; + + @SerializedName("type") + private Integer type; + + @SerializedName("gender") + private Integer gender; + + @SerializedName("unionid") + private String unionId; + + @SerializedName("external_profile") + private ExternalProfile externalProfile; + } + + @Setter + @Getter + public static class ExternalProfile { + @SerializedName("external_attr") + private List externalAttrs; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class ExternalAttribute { + @Setter + @Getter + public static class Text { + private String value; + } + + @Setter + @Getter + public static class Web { + private String title; + private String url; + } + + @Setter + @Getter + public static class MiniProgram { + @SerializedName("pagepath") + private String pagePath; + private String appid; + private String title; + } + + private int type; + + private String name; + + private Text text; + + private Web web; + + @SerializedName("miniprogram") + private MiniProgram miniProgram; + } + + @Setter + @Getter + public static class FollowedUser { + @SerializedName("userid") + private String userId; + private String remark; + private String description; + @SerializedName("createtime") + private Long createTime; + private String state; + @SerializedName("remark_company") + private String remarkCompany; + @SerializedName("remark_mobiles") + private String[] remarkMobiles; + private Tag[] tags; + @SerializedName("add_way") + private Integer addWay; + @SerializedName("oper_userid") + private String operUserid; + + } + + public static WxCpUserExternalContactInfo fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpUserExternalContactInfo.class); + } + + @Setter + @Getter + public static class Tag { + @SerializedName("group_name") + private String groupName; + @SerializedName("tag_name") + private String tagName; + private int type; + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxTpLoginInfo.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxTpLoginInfo.java new file mode 100644 index 000000000..a47104a95 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxTpLoginInfo.java @@ -0,0 +1,65 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.List; + +/** + * @description: 登录信息 + * @author: Jamie.shi + * @create: 2020-08-03 17:18 + **/ +@Data +public class WxTpLoginInfo extends WxCpBaseResp { + @SerializedName("usertype") + private Integer userType; + @SerializedName("user_info") + private UserInfo userInfo; + @SerializedName("corp_info") + private CorpInfoBean corpInfo; + @SerializedName("auth_info") + private AuthInfo authInfo; + private List agent; + + @Data + public static class UserInfo { + @SerializedName("userid") + private String userId; + @SerializedName("open_userid") + private String openUserId; + private String name; + private String avatar; + } + + @Data + public static class CorpInfoBean { + @SerializedName("corpid") + private String corpId; + } + + @Data + public static class AuthInfo { + private List department; + + @Data + public static class Department { + + private int id; + private boolean writable; + } + } + + @Data + public static class Agent { + @SerializedName("agentid") + private int agentId; + @SerializedName("auth_type") + private int authType; + } + + public static WxTpLoginInfo fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxTpLoginInfo.class); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpTpConfigStorage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpTpConfigStorage.java index 0fda37663..d40c8e2d5 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpTpConfigStorage.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpTpConfigStorage.java @@ -62,6 +62,11 @@ public interface WxCpTpConfigStorage { String getCorpId(); String getCorpSecret(); + /** + * 服务商secret + */ + String getProviderSecret(); + /** * 授权企业的access token相关 */ @@ -83,6 +88,11 @@ public interface WxCpTpConfigStorage { boolean isAuthSuiteJsApiTicketExpired(String authCorpId); void updateAuthSuiteJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds);; + boolean isProviderTokenExpired(); + void updateProviderToken(String providerToken, int expiredInSeconds); + + String getProviderToken(); + /** * 网络代理相关 */ diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpDefaultConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpDefaultConfigImpl.java index a748e301d..48f9d3180 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpDefaultConfigImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpDefaultConfigImpl.java @@ -20,13 +20,19 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl private volatile String corpId; private volatile String corpSecret; + /** + * 服务商secret + */ + private volatile String providerSecret; + private volatile String providerToken; + private volatile long providerTokenExpiresTime; private volatile String suiteId; private volatile String suiteSecret; private volatile String token; private volatile String suiteAccessToken; - private volatile long suiteAccessTokenExpiresTime; + private volatile long suiteAccessTokenExpiresTime; private volatile String aesKey; private volatile String suiteTicket; @@ -88,7 +94,7 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl @Override public synchronized void updateSuiteAccessToken(WxAccessToken suiteAccessToken) { - updateSuiteAccessToken(suiteAccessToken.getAccessToken(), suiteAccessToken.getExpiresIn()); + updateSuiteAccessToken(suiteAccessToken.getAccessToken(), suiteAccessToken.getExpiresIn()); } @Override @@ -196,6 +202,11 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl return this.corpSecret; } + @Override + public String getProviderSecret() { + return providerSecret; + } + @Deprecated public void setCorpSecret(String corpSecret) { this.corpSecret = corpSecret; @@ -230,8 +241,7 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl Long t = this.authCorpJsApiTicketExpireTimeMap.get(authCorpId); if (t == null) { return System.currentTimeMillis() > t; - } - else { + } else { return true; } } @@ -254,8 +264,7 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl Long t = authSuiteJsApiTicketExpireTimeMap.get(authCorpId); if (t == null) { return System.currentTimeMillis() > t; - } - else { + } else { return true; } } @@ -268,6 +277,22 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl authSuiteJsApiTicketExpireTimeMap.put(authCorpId, System.currentTimeMillis() + (expiredInSeconds - 200) * 1000L); } + @Override + public boolean isProviderTokenExpired() { + return System.currentTimeMillis() > providerTokenExpiresTime; + } + + @Override + public void updateProviderToken(String providerToken, int expiredInSeconds) { + this.providerToken = providerToken; + this.providerTokenExpiresTime = System.currentTimeMillis() + expiredInSeconds * 1000L; + } + + @Override + public String getProviderToken() { + return providerToken; + } + public void setOauth2redirectUri(String oauth2redirectUri) { this.oauth2redirectUri = oauth2redirectUri; } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpRedissonConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpRedissonConfigImpl.java index 3b1414d9b..91e048b0f 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpRedissonConfigImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpRedissonConfigImpl.java @@ -35,6 +35,8 @@ public class WxCpTpRedissonConfigImpl implements WxCpTpConfigStorage, Serializab private final String authSuiteJsApiTicketKey = ":authSuiteJsApiTicketKey:"; + private final String providerTokenKey = ":providerTokenKey:"; + private volatile String baseApiUrl; private volatile String httpProxyHost; private volatile int httpProxyPort; @@ -59,6 +61,11 @@ public class WxCpTpRedissonConfigImpl implements WxCpTpConfigStorage, Serializab private volatile String corpId; private volatile String corpSecret; + /** + * 服务商secret + */ + private volatile String providerSecret; + @Override public void setBaseApiUrl(String baseUrl) { this.baseApiUrl = baseUrl; @@ -69,7 +76,8 @@ public class WxCpTpRedissonConfigImpl implements WxCpTpConfigStorage, Serializab if (baseApiUrl == null) { baseApiUrl = "https://qyapi.weixin.qq.com"; } - return baseApiUrl + path; } + return baseApiUrl + path; + } /** @@ -164,6 +172,10 @@ public class WxCpTpRedissonConfigImpl implements WxCpTpConfigStorage, Serializab return corpSecret; } + @Override + public String getProviderSecret() { + return providerSecret; + } /** * 授权企业的access token相关 @@ -203,7 +215,8 @@ public class WxCpTpRedissonConfigImpl implements WxCpTpConfigStorage, Serializab @Override public void updateAuthCorpJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds) { - wxRedisOps.setValue(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey, jsApiTicket, expiredInSeconds, TimeUnit.SECONDS); + wxRedisOps.setValue(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey, jsApiTicket, expiredInSeconds, + TimeUnit.SECONDS); } @@ -224,7 +237,24 @@ public class WxCpTpRedissonConfigImpl implements WxCpTpConfigStorage, Serializab @Override public void updateAuthSuiteJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds) { - wxRedisOps.setValue(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey, jsApiTicket, expiredInSeconds, TimeUnit.SECONDS); + wxRedisOps.setValue(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey, jsApiTicket, expiredInSeconds, + TimeUnit.SECONDS); + } + + @Override + public boolean isProviderTokenExpired() { + //remain time to live in seconds, or key not exist + return wxRedisOps.getExpire(keyWithPrefix(providerTokenKey)) == 0L || wxRedisOps.getExpire(keyWithPrefix(providerTokenKey)) == -2; + } + + @Override + public void updateProviderToken(String providerToken, int expiredInSeconds) { + wxRedisOps.setValue(keyWithPrefix(providerTokenKey), providerToken, expiredInSeconds, TimeUnit.SECONDS); + } + + @Override + public String getProviderToken() { + return wxRedisOps.getValue(keyWithPrefix(providerTokenKey)); } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java index 304bbe5a6..c1b204ee6 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java @@ -119,6 +119,8 @@ public final class WxCpApiPathConsts { public static final String SCHEDULE_GET = "/cgi-bin/oa/schedule/get"; public static final String SCHEDULE_DEL = "/cgi-bin/oa/schedule/del"; public static final String SCHEDULE_LIST = "/cgi-bin/oa/schedule/get_by_calendar"; + + public static final String COPY_TEMPLATE = "/cgi-bin/oa/approval/copytemplate"; } @UtilityClass @@ -150,6 +152,10 @@ public final class WxCpApiPathConsts { public static final String GET_SUITE_JSAPI_TICKET = "/cgi-bin/ticket/get"; public static final String GET_USERINFO3RD = "/cgi-bin/service/getuserinfo3rd"; public static final String GET_USERDETAIL3RD = "/cgi-bin/service/getuserdetail3rd"; + public static final String GET_LOGIN_INFO = "/cgi-bin/service/get_login_info"; + + + public static final String CONTACT_SEARCH = "/cgi-bin/service/contact/search"; } @UtilityClass diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpContactService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpContactService.java new file mode 100644 index 000000000..d25987c9d --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpContactService.java @@ -0,0 +1,22 @@ +package me.chanjar.weixin.cp.tp.service; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.WxCpTpContactSearch; +import me.chanjar.weixin.cp.bean.WxCpTpContactSearchResp; + +/** + * @author uianz + * @description + * @since 2020/12/23 下午 02:39 + */ +public interface WxCpTpContactService { + + /** + * https://work.weixin.qq.com/api/doc/90001/90143/91844 + * 通讯录单个搜索 + * @param wxCpTpContactSearch + * @return + * @throws WxErrorException + */ + WxCpTpContactSearchResp contactSearch(WxCpTpContactSearch wxCpTpContactSearch) throws WxErrorException; +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpDepartmentService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpDepartmentService.java new file mode 100644 index 000000000..b7ede9ae2 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpDepartmentService.java @@ -0,0 +1,75 @@ +package me.chanjar.weixin.cp.tp.service; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.WxCpTpDepart; + +import java.util.List; + +/** + *
+ *  部门管理接口
+ *  Created by jamie on 2020/7/22.
+ * 
+ */ +public interface WxCpTpDepartmentService { + + /** + *
+     * 部门管理接口 - 创建部门.
+     * 最多支持创建500个部门
+     * 详情请见: https://work.weixin.qq.com/api/doc#90000/90135/90205
+     * 
+ * + * @param depart 部门 + * @return 部门id + * @throws WxErrorException 异常 + */ + Long create(WxCpTpDepart depart) throws WxErrorException; + + /** + *
+     * 部门管理接口 - 获取部门列表.
+     * 详情请见: https://work.weixin.qq.com/api/doc#90000/90135/90208
+     * 
+ * + * @param id 部门id。获取指定部门及其下的子部门。非必需,可为null + * @return 获取的部门列表 + * @throws WxErrorException 异常 + */ + List list(Long id, String corpId) throws WxErrorException; + + /** + *
+     * 部门管理接口 - 更新部门.
+     * 详情请见: https://work.weixin.qq.com/api/doc#90000/90135/90206
+     * 如果id为0(未部门),1(黑名单),2(星标组),或者不存在的id,微信会返回系统繁忙的错误
+     * 
+ * + * @param group 要更新的group,group的id,name必须设置 + * @throws WxErrorException 异常 + */ + void update(WxCpTpDepart group) throws WxErrorException; + + /** + *
+     * 部门管理接口 - 删除部门.
+     * 详情请见: https://work.weixin.qq.com/api/doc#90000/90135/90207
+     * 应用须拥有指定部门的管理权限
+     * 
+ * + * @param departId 部门id + * @throws WxErrorException 异常 + */ + void delete(Long departId) throws WxErrorException; + + /** + *
+     * 部门管理接口 - 获取部门列表.
+     * 详情请见: https://work.weixin.qq.com/api/doc#90000/90135/90208
+     * 
+ * + * @return 获取所有的部门列表 + * @throws WxErrorException 异常 + */ + List list(String corpId) throws WxErrorException; +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpMediaService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpMediaService.java new file mode 100644 index 000000000..e8a8750d8 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpMediaService.java @@ -0,0 +1,60 @@ +package me.chanjar.weixin.cp.tp.service; + +import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; +import me.chanjar.weixin.common.error.WxErrorException; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +/** + *
+ *  媒体管理接口.
+ *  Created by BinaryWang on 2017/6/24.
+ * 
+ * + * @author Binary Wang + */ +public interface WxCpTpMediaService { + + /** + *
+     * 上传多媒体文件.
+     * 上传的多媒体文件有格式和大小限制,如下:
+     *   图片(image): 1M,支持JPG格式
+     *   语音(voice):2M,播放长度不超过60s,支持AMR\MP3格式
+     *   视频(video):10MB,支持MP4格式
+     *   缩略图(thumb):64KB,支持JPG格式
+     * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件
+     * 
+ * + * @param mediaType 媒体类型, 请看{@link me.chanjar.weixin.common.api.WxConsts} + * @param fileType 文件类型,请看{@link me.chanjar.weixin.common.api.WxConsts} + * @param inputStream 输入流,需要调用方控制关闭该输入流 + */ + WxMediaUploadResult upload(String mediaType, String fileType, InputStream inputStream, String corpId) + throws WxErrorException, IOException; + + /** + * 上传多媒体文件. + * + * @param mediaType 媒体类型 + * @param file 文件对象 + * @see #upload(String, String, InputStream, String) + */ + WxMediaUploadResult upload(String mediaType, File file, String corpId) throws WxErrorException; + + /** + *
+     * 上传图片.
+     * 上传图片得到图片URL,该URL永久有效
+     * 返回的图片URL,仅能用于图文消息(mpnews)正文中的图片展示;若用于非企业微信域名下的页面,图片将被屏蔽。
+     * 每个企业每天最多可上传100张图片
+     * 接口url格式:https://qyapi.weixin.qq.com/cgi-bin/media/uploadimg?access_token=ACCESS_TOKEN
+     * 
+ * + * @param file 上传的文件对象 + * @return 返回图片url + */ + String uploadImg(File file, String corpId) throws WxErrorException; +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpOAService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpOAService.java new file mode 100644 index 000000000..d6e65b697 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpOAService.java @@ -0,0 +1,60 @@ +package me.chanjar.weixin.cp.tp.service; + +import lombok.NonNull; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.oa.WxCpApprovalDetailResult; +import me.chanjar.weixin.cp.bean.oa.WxCpOaApplyEventRequest; +import me.chanjar.weixin.cp.bean.oa.WxCpTemplateResult; + +/** + * 企业微信OA相关接口. + * + * @author Element + * @date 2019-04-06 10:52 + */ +public interface WxCpTpOAService { + + /** + *
提交审批申请
+     * 调试工具
+     * 企业可通过审批应用或自建应用Secret调用本接口,代应用可见范围内员工在企业微信“审批应用”内提交指定类型的审批申请。
+     *
+     * 请求方式:POST(HTTPS)
+     * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/oa/applyevent?access_token=ACCESS_TOKEN
+     * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/91853
+     * 
+ * + * @param request 请求 + * @return 表单提交成功后,返回的表单编号 + * @throws WxErrorException . + */ + String apply(WxCpOaApplyEventRequest request, String corpId) throws WxErrorException; + + /** + * 获取审批模板详情 + * + * @param templateId 模板ID + * @return . + * @throws WxErrorException . + */ + WxCpTemplateResult getTemplateDetail(@NonNull String templateId, String corpId) throws WxErrorException; + + /** + * 复制/更新模板到企业 + * + * @param openTemplateId 模板ID + * @return . + * @throws WxErrorException . + */ + String copyTemplate(@NonNull String openTemplateId, String corpId) throws WxErrorException; + + /** + *
+     *   获取审批申请详情
+     *
+     * @param spNo 审批单编号。
+     * @return WxCpApprovaldetail
+     * @throws WxErrorException .
+     */
+    WxCpApprovalDetailResult getApprovalDetail(@NonNull String spNo, String corpId) throws WxErrorException;
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpService.java
index 67ab47b4e..1709a7bab 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpService.java
@@ -312,4 +312,79 @@ public interface WxCpTpService {
    * @return
    */
   WxCpTpUserDetail getUserDetail3rd(String userTicket) throws WxErrorException;
+
+  /**
+   * 企业用户登录信息
+   * @param authCode
+   * @return
+   * @throws WxErrorException
+   */
+  WxTpLoginInfo getLoginInfo(String authCode) throws WxErrorException;
+
+  /**
+   * 获取服务商providerToken
+   * @return
+   * @throws WxErrorException
+   */
+  String getWxCpProviderToken() throws WxErrorException;
+
+  /**
+   * get contact service
+   * @return WxCpTpContactService
+   */
+  WxCpTpContactService getWxCpTpContactService();
+
+  /**
+   * get department service
+   * @return WxCpTpDepartmentService
+   */
+  WxCpTpDepartmentService getWxCpTpDepartmentService();
+
+  /**
+   * get media service
+   * @return WxCpTpMediaService
+   */
+  WxCpTpMediaService getWxCpTpMediaService();
+
+  /**
+   * get oa service
+   * @return WxCpTpOAService
+   */
+  WxCpTpOAService getWxCpTpOAService();
+
+  /**
+   * get user service
+   * @return WxCpTpUserService
+   */
+  WxCpTpUserService getWxCpTpUserService();
+
+  /**
+   * set contact service
+   * @param wxCpTpContactService the contact service
+   */
+  void setWxCpTpContactService(WxCpTpContactService wxCpTpContactService);
+
+  /**
+   * set department service
+   * @param wxCpTpDepartmentService the department service
+   */
+  void setWxCpTpDepartmentService(WxCpTpDepartmentService wxCpTpDepartmentService);
+
+  /**
+   * set media service
+   * @param wxCpTpMediaService the media service
+   */
+  void setWxCpTpMediaService(WxCpTpMediaService wxCpTpMediaService);
+
+  /**
+   * set oa service
+   * @param wxCpTpOAService the oa service
+   */
+  void setWxCpTpOAService(WxCpTpOAService wxCpTpOAService);
+
+  /**
+   * set user service
+   * @param wxCpTpUserService the set user service
+   */
+  void setWxCpTpUserService(WxCpTpUserService wxCpTpUserService);
 }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpUserService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpUserService.java
new file mode 100644
index 000000000..55c04e3cf
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpUserService.java
@@ -0,0 +1,172 @@
+package me.chanjar.weixin.cp.tp.service;
+
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.cp.bean.WxCpInviteResult;
+import me.chanjar.weixin.cp.bean.WxCpUser;
+import me.chanjar.weixin.cp.bean.WxCpUserExternalContactInfo;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 
+ * 用户管理接口
+ *  Created by jamie on 2020/7/22.
+ * 
+ * + */ +public interface WxCpTpUserService { + + /** + *
+   *   用在二次验证的时候.
+   *   企业在员工验证成功后,调用本方法告诉企业号平台该员工关注成功。
+   * 
+ * + * @param userId 用户id + */ + void authenticate(String userId) throws WxErrorException; + + /** + *
+   * 获取部门成员(详情).
+   *
+   * http://qydev.weixin.qq.com/wiki/index.php?title=管理成员#.E8.8E.B7.E5.8F.96.E9.83.A8.E9.97.A8.E6.88.90.E5.91.98.28.E8.AF.A6.E6.83.85.29
+   * 
+ * + * @param departId 必填。部门id + * @param fetchChild 非必填。1/0:是否递归获取子部门下面的成员 + * @param status 非必填。0获取全部员工,1获取已关注成员列表,2获取禁用成员列表,4获取未关注成员列表。status可叠加 + */ + List listByDepartment(Long departId, Boolean fetchChild, Integer status,String corpId) throws WxErrorException; + + /** + *
+   * 获取部门成员.
+   *
+   * http://qydev.weixin.qq.com/wiki/index.php?title=管理成员#.E8.8E.B7.E5.8F.96.E9.83.A8.E9.97.A8.E6.88.90.E5.91.98
+   * 
+ * + * @param departId 必填。部门id + * @param fetchChild 非必填。1/0:是否递归获取子部门下面的成员 + * @param status 非必填。0获取全部员工,1获取已关注成员列表,2获取禁用成员列表,4获取未关注成员列表。status可叠加 + */ + List listSimpleByDepartment(Long departId, Boolean fetchChild, Integer status) throws WxErrorException; + + /** + * 新建用户. + * + * @param user 用户对象 + */ + void create(WxCpUser user) throws WxErrorException; + + /** + * 更新用户. + * + * @param user 用户对象 + */ + void update(WxCpUser user) throws WxErrorException; + + /** + *
+   * 删除用户/批量删除成员.
+   * http://qydev.weixin.qq.com/wiki/index.php?title=管理成员#.E6.89.B9.E9.87.8F.E5.88.A0.E9.99.A4.E6.88.90.E5.91.98
+   * 
+ * + * @param userIds 员工UserID列表。对应管理端的帐号 + */ + void delete(String... userIds) throws WxErrorException; + + /** + * 获取用户. + * + * @param userid 用户id + */ + WxCpUser getById(String userid,String corpId) throws WxErrorException; + + /** + *
+   * 邀请成员.
+   * 企业可通过接口批量邀请成员使用企业微信,邀请后将通过短信或邮件下发通知。
+   * 请求方式:POST(HTTPS)
+   * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/batch/invite?access_token=ACCESS_TOKEN
+   * 文档地址:https://work.weixin.qq.com/api/doc#12543
+   * 
+ * + * @param userIds 成员ID列表, 最多支持1000个。 + * @param partyIds 部门ID列表,最多支持100个。 + * @param tagIds 标签ID列表,最多支持100个。 + */ + WxCpInviteResult invite(List userIds, List partyIds, List tagIds) throws WxErrorException; + + /** + *
+   *  userid转openid.
+   *  该接口使用场景为微信支付、微信红包和企业转账。
+   *
+   * 在使用微信支付的功能时,需要自行将企业微信的userid转成openid。
+   * 在使用微信红包功能时,需要将应用id和userid转成appid和openid才能使用。
+   * 注:需要成员使用微信登录企业微信或者关注微信插件才能转成openid
+   *
+   * 文档地址:https://work.weixin.qq.com/api/doc#11279
+   * 
+ * + * @param userId 企业内的成员id + * @param agentId 非必填,整型,仅用于发红包。其它场景该参数不要填,如微信支付、企业转账、电子发票 + * @return map对象,可能包含以下值: + * - openid 企业微信成员userid对应的openid,若有传参agentid,则是针对该agentid的openid。否则是针对企业微信corpid的openid + * - appid 应用的appid,若请求包中不包含agentid则不返回appid。该appid在使用微信红包时会用到 + */ + Map userId2Openid(String userId, Integer agentId) throws WxErrorException; + + /** + *
+   * openid转userid.
+   *
+   * 该接口主要应用于使用微信支付、微信红包和企业转账之后的结果查询。
+   * 开发者需要知道某个结果事件的openid对应企业微信内成员的信息时,可以通过调用该接口进行转换查询。
+   * 权限说明:
+   * 管理组需对openid对应的企业微信成员有查看权限。
+   *
+   * 文档地址:https://work.weixin.qq.com/api/doc#11279
+   * 
+ * + * @param openid 在使用微信支付、微信红包和企业转账之后,返回结果的openid + * @return userid 该openid在企业微信对应的成员userid + */ + String openid2UserId(String openid) throws WxErrorException; + + /** + *
+   *
+   * 通过手机号获取其所对应的userid。
+   *
+   * 请求方式:POST(HTTPS)
+   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/user/getuserid?access_token=ACCESS_TOKEN
+   *
+   * 文档地址:https://work.weixin.qq.com/api/doc#90001/90143/91693
+   * 
+ * + * @param mobile 手机号码。长度为5~32个字节 + * @return userid mobile对应的成员userid + * @throws WxErrorException . + */ + String getUserId(String mobile) throws WxErrorException; + + /** + * 获取外部联系人详情. + *
+   *   企业可通过此接口,根据外部联系人的userid,拉取外部联系人详情。权限说明:
+   * 企业需要使用外部联系人管理secret所获取的accesstoken来调用
+   * 第三方应用需拥有“企业客户”权限。
+   * 第三方应用调用时,返回的跟进人follow_user仅包含应用可见范围之内的成员。
+   * 
+ * + * @param userId 外部联系人的userid + * @return 联系人详情 + * @throws WxErrorException . + */ + WxCpUserExternalContactInfo getExternalContact(String userId) throws WxErrorException; + + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImpl.java index 536fd7749..71f3b7fa3 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImpl.java @@ -22,7 +22,7 @@ import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; import me.chanjar.weixin.common.util.json.GsonParser; import me.chanjar.weixin.cp.bean.*; import me.chanjar.weixin.cp.config.WxCpTpConfigStorage; -import me.chanjar.weixin.cp.tp.service.WxCpTpService; +import me.chanjar.weixin.cp.tp.service.*; import org.apache.commons.lang3.StringUtils; import java.io.File; @@ -41,6 +41,12 @@ import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Tp.*; @Slf4j public abstract class BaseWxCpTpServiceImpl implements WxCpTpService, RequestHttp { + private WxCpTpContactService wxCpTpContactService = new WxCpTpContactServiceImpl(this); + private WxCpTpDepartmentService wxCpTpDepartmentService = new WxCpTpDepartmentServiceImpl(this); + private WxCpTpMediaService wxCpTpMediaService = new WxCpTpMediaServiceImpl(this); + private WxCpTpOAService wxCpTpOAService = new WxCpTpOAServiceImpl(this); + private WxCpTpUserService wxCpTpUserService = new WxCpTpUserServiceImpl(this); + /** * 全局的是否正在刷新access token的锁. */ @@ -57,6 +63,13 @@ public abstract class BaseWxCpTpServiceImpl implements WxCpTpService, Requ */ protected final Object globalJsApiTicketRefreshLock = new Object(); + /** + * 全局的是否正在刷新auth_corp_jsapi_ticket的锁. + */ + protected final Object globalAuthCorpJsApiTicketRefreshLock = new Object(); + + protected final Object globalProviderTokenRefreshLock = new Object(); + protected WxCpTpConfigStorage configStorage; private WxSessionManager sessionManager = new StandardSessionManager(); @@ -104,12 +117,12 @@ public abstract class BaseWxCpTpServiceImpl implements WxCpTpService, Requ } @Override - public void setSuiteTicket(String suiteTicket){ + public void setSuiteTicket(String suiteTicket) { setSuiteTicket(suiteTicket, 28 * 60); } @Override - public void setSuiteTicket(String suiteTicket, int expiresInSeconds){ + public void setSuiteTicket(String suiteTicket, int expiresInSeconds) { synchronized (globalSuiteTicketRefreshLock) { this.configStorage.updateSuiteTicket(suiteTicket, expiresInSeconds); } @@ -117,7 +130,7 @@ public abstract class BaseWxCpTpServiceImpl implements WxCpTpService, Requ @Override public String getSuiteJsApiTicket(String authCorpId) throws WxErrorException { - if (this.configStorage.isSuiteAccessTokenExpired()) { + if (this.configStorage.isAuthSuiteJsApiTicketExpired(authCorpId)) { String resp = get(configStorage.getApiUrl(GET_SUITE_JSAPI_TICKET), "type=agent_config&access_token=" + this.configStorage.getAccessToken(authCorpId)); @@ -129,18 +142,17 @@ public abstract class BaseWxCpTpServiceImpl implements WxCpTpService, Requ synchronized (globalJsApiTicketRefreshLock) { configStorage.updateAuthSuiteJsApiTicket(authCorpId, jsApiTicket, expiredInSeconds); } - } - else { + } else { throw new WxErrorException(WxError.fromJson(resp)); } } - return configStorage.getSuiteAccessToken(); + return configStorage.getAuthSuiteJsApiTicket(authCorpId); } @Override public String getAuthCorpJsApiTicket(String authCorpId) throws WxErrorException { - if (this.configStorage.isSuiteAccessTokenExpired()) { + if (this.configStorage.isAuthCorpJsApiTicketExpired(authCorpId)) { String resp = get(configStorage.getApiUrl(GET_AUTH_CORP_JSAPI_TICKET), "access_token=" + this.configStorage.getAccessToken(authCorpId)); @@ -150,16 +162,14 @@ public abstract class BaseWxCpTpServiceImpl implements WxCpTpService, Requ String jsApiTicket = jsonObject.get("ticket").getAsString(); int expiredInSeconds = jsonObject.get("expires_in").getAsInt(); - synchronized (globalJsApiTicketRefreshLock) { + synchronized (globalAuthCorpJsApiTicketRefreshLock) { configStorage.updateAuthCorpJsApiTicket(authCorpId, jsApiTicket, expiredInSeconds); } - } - else { + } else { throw new WxErrorException(WxError.fromJson(resp)); } } - - return configStorage.getSuiteAccessToken(); + return configStorage.getProviderToken(); } @Override @@ -223,9 +233,9 @@ public abstract class BaseWxCpTpServiceImpl implements WxCpTpService, Requ WxCpTpPreauthCode preAuthCode = WxCpTpPreauthCode.fromJson(result); String setSessionUrl = "https://qyapi.weixin.qq.com/cgi-bin/service/set_session_info"; - Map sessionInfo = new HashMap<>(1); + Map sessionInfo = new HashMap<>(1); sessionInfo.put("auth_type", authType); - Map param = new HashMap<>(2); + Map param = new HashMap<>(2); param.put("pre_auth_code", preAuthCode.getPreAuthCode()); param.put("session_info", sessionInfo); String postData = new Gson().toJson(param); @@ -373,18 +383,97 @@ public abstract class BaseWxCpTpServiceImpl implements WxCpTpService, Requ } @Override - public WxCpTpUserInfo getUserInfo3rd(String code) throws WxErrorException{ + public WxCpTpUserInfo getUserInfo3rd(String code) throws WxErrorException { String url = configStorage.getApiUrl(GET_USERINFO3RD); - String result = get(url+"?code="+code,null); + String result = get(url + "?code=" + code, null); return WxCpTpUserInfo.fromJson(result); } @Override - public WxCpTpUserDetail getUserDetail3rd(String userTicket) throws WxErrorException{ + public WxCpTpUserDetail getUserDetail3rd(String userTicket) throws WxErrorException { JsonObject jsonObject = new JsonObject(); jsonObject.addProperty("user_ticket", userTicket); String result = post(configStorage.getApiUrl(GET_USERDETAIL3RD), jsonObject.toString()); return WxCpTpUserDetail.fromJson(result); } + @Override + public WxTpLoginInfo getLoginInfo(String authCode) throws WxErrorException { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("auth_code", authCode); + jsonObject.addProperty("access_token", configStorage.getAccessToken(authCode)); + String responseText = post(configStorage.getApiUrl(GET_LOGIN_INFO), jsonObject.toString()); + return WxTpLoginInfo.fromJson(responseText); + } + + @Override + public String getWxCpProviderToken() throws WxErrorException { + if (this.configStorage.isProviderTokenExpired()) { + + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("corpid", configStorage.getCorpId()); + jsonObject.addProperty("provider_secret", configStorage.getProviderSecret()); + WxCpProviderToken wxCpProviderToken = + WxCpProviderToken.fromJson(this.post(this.configStorage.getApiUrl(GET_PROVIDER_TOKEN) + , jsonObject.toString())); + String providerAccessToken = wxCpProviderToken.getProviderAccessToken(); + Integer expiresIn = wxCpProviderToken.getExpiresIn(); + + synchronized (globalProviderTokenRefreshLock) { + configStorage.updateProviderToken(providerAccessToken, expiresIn - 200); + } + } + return configStorage.getProviderToken(); + } + + + @Override + public WxCpTpContactService getWxCpTpContactService() { + return wxCpTpContactService; + } + + @Override + public WxCpTpDepartmentService getWxCpTpDepartmentService(){ + return wxCpTpDepartmentService; + } + + @Override + public WxCpTpMediaService getWxCpTpMediaService(){ + return wxCpTpMediaService; + } + + @Override + public WxCpTpOAService getWxCpTpOAService(){ + return wxCpTpOAService; + } + + @Override + public WxCpTpUserService getWxCpTpUserService(){ + return wxCpTpUserService; + } + + @Override + public void setWxCpTpContactService(WxCpTpContactService wxCpTpContactService) { + this.wxCpTpContactService = wxCpTpContactService; + } + + @Override + public void setWxCpTpDepartmentService(WxCpTpDepartmentService wxCpTpDepartmentService) { + this.wxCpTpDepartmentService = wxCpTpDepartmentService; + } + + @Override + public void setWxCpTpMediaService(WxCpTpMediaService wxCpTpMediaService) { + this.wxCpTpMediaService = wxCpTpMediaService; + } + + @Override + public void setWxCpTpOAService(WxCpTpOAService wxCpTpOAService) { + this.wxCpTpOAService = wxCpTpOAService; + } + + @Override + public void setWxCpTpUserService(WxCpTpUserService wxCpTpUserService) { + this.wxCpTpUserService = wxCpTpUserService; + } } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpContactServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpContactServiceImpl.java new file mode 100644 index 000000000..392beaf2f --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpContactServiceImpl.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.cp.tp.service.impl; + +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.WxCpTpContactSearch; +import me.chanjar.weixin.cp.bean.WxCpTpContactSearchResp; +import me.chanjar.weixin.cp.tp.service.WxCpTpContactService; +import me.chanjar.weixin.cp.tp.service.WxCpTpService; + +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Tp.CONTACT_SEARCH; + +/** + * @author uianz + * @description + * @since 2020/12/23 下午 02:39 + */ +@RequiredArgsConstructor +public class WxCpTpContactServiceImpl implements WxCpTpContactService { + + private final WxCpTpService mainService; + + @Override + public WxCpTpContactSearchResp contactSearch(WxCpTpContactSearch wxCpTpContactSearch) throws WxErrorException { + String responseText = + mainService.post(mainService.getWxCpTpConfigStorage().getApiUrl(CONTACT_SEARCH) + "?provider_access_token=" + mainService.getWxCpTpConfigStorage().getCorpSecret(), wxCpTpContactSearch.toJson()); + return WxCpTpContactSearchResp.fromJson(responseText); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpDepartmentServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpDepartmentServiceImpl.java new file mode 100644 index 000000000..826ce27cd --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpDepartmentServiceImpl.java @@ -0,0 +1,70 @@ +package me.chanjar.weixin.cp.tp.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.GsonHelper; +import me.chanjar.weixin.common.util.json.GsonParser; +import me.chanjar.weixin.cp.api.impl.WxCpDepartmentServiceImpl; +import me.chanjar.weixin.cp.bean.WxCpDepart; +import me.chanjar.weixin.cp.bean.WxCpTpDepart; +import me.chanjar.weixin.cp.tp.service.WxCpTpDepartmentService; +import me.chanjar.weixin.cp.tp.service.WxCpTpService; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.List; + +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Department.*; + +/** + * @author uianz + * @description corp from {@link WxCpDepartmentServiceImpl )} + * 唯一不同在于获取部门列表时需要传对应企业的accessToken + * @since 2020/12/23 下午 02:39 + */ +@RequiredArgsConstructor +public class WxCpTpDepartmentServiceImpl implements WxCpTpDepartmentService { + private final WxCpTpService mainService; + + @Override + public Long create(WxCpTpDepart depart) throws WxErrorException { + String url = this.mainService.getWxCpTpConfigStorage().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(WxCpTpDepart group) throws WxErrorException { + String url = this.mainService.getWxCpTpConfigStorage().getApiUrl(DEPARTMENT_UPDATE); + this.mainService.post(url, group.toJson()); + } + + @Override + public void delete(Long departId) throws WxErrorException { + String url = String.format(this.mainService.getWxCpTpConfigStorage().getApiUrl(DEPARTMENT_DELETE), departId); + this.mainService.get(url, null); + } + + @Override + public List list(Long id, String corpId) throws WxErrorException { + String url = this.mainService.getWxCpTpConfigStorage().getApiUrl(DEPARTMENT_LIST); + url += "?access_token=" + this.mainService.getWxCpTpConfigStorage().getAccessToken(corpId); + 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>() { + }.getType() + ); + } + + @Override + public List list(String corpId) throws WxErrorException { + return list(null, corpId); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpMediaServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpMediaServiceImpl.java new file mode 100644 index 000000000..ef914b940 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpMediaServiceImpl.java @@ -0,0 +1,50 @@ +package me.chanjar.weixin.cp.tp.service.impl; + +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.fs.FileUtils; +import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; +import me.chanjar.weixin.cp.tp.service.WxCpTpMediaService; +import me.chanjar.weixin.cp.tp.service.WxCpTpService; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.UUID; + +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Media.IMG_UPLOAD; +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Media.MEDIA_UPLOAD; + +/** + *
+ * 媒体管理接口.
+ * Created by Binary Wang on 2017-6-25.
+ * 
+ * + * @author Binary Wang + */ +@RequiredArgsConstructor +public class WxCpTpMediaServiceImpl implements WxCpTpMediaService { + private final WxCpTpService mainService; + + @Override + public WxMediaUploadResult upload(String mediaType, String fileType, InputStream inputStream, String corpId) + throws WxErrorException, IOException { + return this.upload(mediaType, FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), fileType), corpId); + } + + @Override + public WxMediaUploadResult upload(String mediaType, File file, String corpId) throws WxErrorException { + return this.mainService.execute(MediaUploadRequestExecutor.create(this.mainService.getRequestHttp()), + mainService.getWxCpTpConfigStorage().getApiUrl(MEDIA_UPLOAD + mediaType) + "&access_token=" + mainService.getWxCpTpConfigStorage().getAccessToken(corpId), file); + } + + @Override + public String uploadImg(File file, String corpId) throws WxErrorException { + String url = mainService.getWxCpTpConfigStorage().getApiUrl(IMG_UPLOAD); + url += "&access_token=" + mainService.getWxCpTpConfigStorage().getAccessToken(corpId); + return this.mainService.execute(MediaUploadRequestExecutor.create(this.mainService.getRequestHttp()), url, file) + .getUrl(); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpOAServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpOAServiceImpl.java new file mode 100644 index 000000000..e9db407a7 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpOAServiceImpl.java @@ -0,0 +1,66 @@ +package me.chanjar.weixin.cp.tp.service.impl; + +import com.google.gson.JsonObject; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.json.GsonParser; +import me.chanjar.weixin.cp.bean.oa.WxCpApprovalDetailResult; +import me.chanjar.weixin.cp.bean.oa.WxCpOaApplyEventRequest; +import me.chanjar.weixin.cp.bean.oa.WxCpTemplateResult; +import me.chanjar.weixin.cp.tp.service.WxCpTpOAService; +import me.chanjar.weixin.cp.tp.service.WxCpTpService; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Oa.*; + +/** + * 企业微信 OA 接口实现 + * + * @author Element + * @date 2019-04-06 11:20 + */ +@RequiredArgsConstructor +public class WxCpTpOAServiceImpl implements WxCpTpOAService { + private final WxCpTpService mainService; + + + @Override + public String apply(WxCpOaApplyEventRequest request, String corpId) throws WxErrorException { + String url = mainService.getWxCpTpConfigStorage().getApiUrl(APPLY_EVENT) + + "?access_token=" + mainService.getWxCpTpConfigStorage().getAccessToken(corpId); + + String responseContent = this.mainService.post(url, request.toJson()); + return GsonParser.parse(responseContent).get("sp_no").getAsString(); + } + + @Override + public WxCpTemplateResult getTemplateDetail(@NonNull String templateId, String corpId) throws WxErrorException { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("template_id", templateId); + String url = mainService.getWxCpTpConfigStorage().getApiUrl(GET_TEMPLATE_DETAIL) + + "?access_token=" + mainService.getWxCpTpConfigStorage().getAccessToken(corpId); + String responseContent = this.mainService.post(url, jsonObject.toString()); + return WxCpGsonBuilder.create().fromJson(responseContent, WxCpTemplateResult.class); + } + + @Override + public String copyTemplate(@NonNull String openTemplateId, String corpId) throws WxErrorException { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("open_template_id", openTemplateId); + String url = mainService.getWxCpTpConfigStorage().getApiUrl(COPY_TEMPLATE) + + "?access_token=" + mainService.getWxCpTpConfigStorage().getAccessToken(corpId); + String responseContent = this.mainService.post(url, jsonObject.toString()); + return GsonParser.parse(responseContent).get("template_id").getAsString(); + } + + @Override + public WxCpApprovalDetailResult getApprovalDetail(@NonNull String spNo, String corpId) throws WxErrorException { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("sp_no", spNo); + final String url = mainService.getWxCpTpConfigStorage().getApiUrl(GET_APPROVAL_DETAIL) + + "?access_token=" + mainService.getWxCpTpConfigStorage().getAccessToken(corpId); + String responseContent = this.mainService.post(url, jsonObject.toString()); + return WxCpGsonBuilder.create().fromJson(responseContent, WxCpApprovalDetailResult.class); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpUserServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpUserServiceImpl.java new file mode 100644 index 000000000..6cbca3bd0 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpUserServiceImpl.java @@ -0,0 +1,204 @@ +package me.chanjar.weixin.cp.tp.service.impl; + +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 me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.json.GsonParser; +import me.chanjar.weixin.cp.bean.WxCpInviteResult; +import me.chanjar.weixin.cp.bean.WxCpUser; +import me.chanjar.weixin.cp.bean.WxCpUserExternalContactInfo; +import me.chanjar.weixin.cp.tp.service.WxCpTpService; +import me.chanjar.weixin.cp.tp.service.WxCpTpUserService; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.List; +import java.util.Map; + +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.User.*; + +/** + *
+ *  Created by jamie on 2020/7/22.
+ * 
+ */ +@RequiredArgsConstructor +public class WxCpTpUserServiceImpl implements WxCpTpUserService { + private final WxCpTpService mainService; + + @Override + public void authenticate(String userId) throws WxErrorException { + this.mainService.get(mainService.getWxCpTpConfigStorage().getApiUrl(USER_AUTHENTICATE + userId), null); + } + + @Override + public void create(WxCpUser user) throws WxErrorException { + String url = mainService.getWxCpTpConfigStorage().getApiUrl(USER_CREATE); + this.mainService.post(url, user.toJson()); + } + + @Override + public void update(WxCpUser user) throws WxErrorException { + String url = mainService.getWxCpTpConfigStorage().getApiUrl(USER_UPDATE); + this.mainService.post(url, user.toJson()); + } + + @Override + public void delete(String... userIds) throws WxErrorException { + if (userIds.length == 1) { + String url = mainService.getWxCpTpConfigStorage().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(mainService.getWxCpTpConfigStorage().getApiUrl(USER_BATCH_DELETE), + jsonObject.toString()); + } + + @Override + public WxCpUser getById(String userid, String corpId) throws WxErrorException { + String url = mainService.getWxCpTpConfigStorage().getApiUrl(USER_GET + userid); + url += "&access_token=" + mainService.getWxCpTpConfigStorage().getAccessToken(corpId); + String responseContent = this.mainService.get(url, null); + return WxCpUser.fromJson(responseContent); + } + + @Override + public List listByDepartment(Long departId, Boolean fetchChild, Integer status, String corpId) throws WxErrorException { + String params = ""; + if (fetchChild != null) { + params += "&fetch_child=" + (fetchChild ? "1" : "0"); + } + if (status != null) { + params += "&status=" + status; + } else { + params += "&status=0"; + } + params += "&access_token=" + mainService.getWxCpTpConfigStorage().getAccessToken(corpId); + + String url = mainService.getWxCpTpConfigStorage().getApiUrl(USER_LIST + departId); + String responseContent = this.mainService.get(url, params); + JsonObject tmpJsonElement = GsonParser.parse(responseContent); + return WxCpGsonBuilder.create() + .fromJson(tmpJsonElement.getAsJsonObject().get("userlist"), + new TypeToken>() { + }.getType() + ); + } + + @Override + public List listSimpleByDepartment(Long departId, Boolean fetchChild, Integer status) + throws WxErrorException { + String params = ""; + if (fetchChild != null) { + params += "&fetch_child=" + (fetchChild ? "1" : "0"); + } + if (status != null) { + params += "&status=" + status; + } else { + params += "&status=0"; + } + + String url = mainService.getWxCpTpConfigStorage().getApiUrl(USER_SIMPLE_LIST + departId); + String responseContent = this.mainService.get(url, params); + JsonObject tmpJsonElement = GsonParser.parse(responseContent); + return WxCpGsonBuilder.create() + .fromJson( + tmpJsonElement.getAsJsonObject().get("userlist"), + new TypeToken>() { + }.getType() + ); + } + + @Override + public WxCpInviteResult invite(List userIds, List partyIds, List tagIds) + throws WxErrorException { + 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 = mainService.getWxCpTpConfigStorage().getApiUrl(BATCH_INVITE); + return WxCpInviteResult.fromJson(this.mainService.post(url, jsonObject.toString())); + } + + @Override + public Map userId2Openid(String userId, Integer agentId) throws WxErrorException { + String url = mainService.getWxCpTpConfigStorage().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 tmpJsonElement = GsonParser.parse(responseContent); + Map result = Maps.newHashMap(); + if (tmpJsonElement.getAsJsonObject().get("openid") != null) { + result.put("openid", tmpJsonElement.getAsJsonObject().get("openid").getAsString()); + } + + if (tmpJsonElement.getAsJsonObject().get("appid") != null) { + result.put("appid", tmpJsonElement.getAsJsonObject().get("appid").getAsString()); + } + + return result; + } + + @Override + public String openid2UserId(String openid) throws WxErrorException { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("openid", openid); + String url = mainService.getWxCpTpConfigStorage().getApiUrl(USER_CONVERT_TO_USERID); + String responseContent = this.mainService.post(url, jsonObject.toString()); + JsonObject tmpJsonElement = GsonParser.parse(responseContent); + return tmpJsonElement.getAsJsonObject().get("userid").getAsString(); + } + + @Override + public String getUserId(String mobile) throws WxErrorException { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("mobile", mobile); + String url = mainService.getWxCpTpConfigStorage().getApiUrl(GET_USER_ID); + String responseContent = this.mainService.post(url, jsonObject.toString()); + JsonObject tmpJsonElement = GsonParser.parse(responseContent); + return tmpJsonElement.getAsJsonObject().get("userid").getAsString(); + } + + @Override + public WxCpUserExternalContactInfo getExternalContact(String userId) throws WxErrorException { + String url = mainService.getWxCpTpConfigStorage().getApiUrl(GET_EXTERNAL_CONTACT + userId); + String responseContent = this.mainService.get(url, null); + return WxCpUserExternalContactInfo.fromJson(responseContent); + } +}