🆕 #3853 【企业微信】新增服务商代开发相关接口实现

This commit is contained in:
Copilot
2026-01-15 15:01:46 +08:00
committed by GitHub
parent 6a9852f7e3
commit 0dfedb160a
8 changed files with 621 additions and 0 deletions

View File

@@ -0,0 +1,221 @@
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.io.Serializable;
import java.util.List;
/**
* 代开发应用详情.
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
* created on 2026-01-14
*/
@Data
public class WxCpTpCustomizedAppDetail extends WxCpBaseResp {
/**
* 授权方企业id
*/
@SerializedName("auth_corpid")
private String authCorpId;
/**
* 授权方企业名称
*/
@SerializedName("auth_corp_name")
private String authCorpName;
/**
* 授权方企业方形头像
*/
@SerializedName("auth_corp_square_logo_url")
private String authCorpSquareLogoUrl;
/**
* 授权方企业圆形头像
*/
@SerializedName("auth_corp_round_logo_url")
private String authCorpRoundLogoUrl;
/**
* 授权方企业类型1. 企业; 2. 政府以及事业单位; 3. 其他组织, 4.团队小型企业(原企业微信认证版用户)
*/
@SerializedName("auth_corp_type")
private Integer authCorpType;
/**
* 授权方企业在微工作台(原企业号)的二维码,可用于关注微工作台
*/
@SerializedName("auth_corp_qrcode_url")
private String authCorpQrcodeUrl;
/**
* 授权方企业用户规模
*/
@SerializedName("auth_corp_user_limit")
private Integer authCorpUserLimit;
/**
* 授权方企业的主体名称(仅认证或验证过的企业有),即企业全称
*/
@SerializedName("auth_corp_full_name")
private String authCorpFullName;
/**
* 企业类型1. 已验证企业2. 已认证企业
*/
@SerializedName("auth_corp_verified_type")
private Integer authCorpVerifiedType;
/**
* 授权方企业所属行业
*/
@SerializedName("auth_corp_industry")
private String authCorpIndustry;
/**
* 授权方企业所属子行业
*/
@SerializedName("auth_corp_sub_industry")
private String authCorpSubIndustry;
/**
* 授权方企业所在地址
*/
@SerializedName("auth_corp_location")
private String authCorpLocation;
/**
* 代开发自建应用详情
*/
@SerializedName("customized_app_list")
private List<CustomizedApp> customizedAppList;
/**
* From json wx cp tp customized app detail.
*
* @param json the json
* @return the wx cp tp customized app detail
*/
public static WxCpTpCustomizedAppDetail fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, WxCpTpCustomizedAppDetail.class);
}
@Override
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
/**
* 代开发自建应用信息
*/
@Data
public static class CustomizedApp implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 代开发自建应用的agentid
*/
@SerializedName("agentid")
private Integer agentId;
/**
* 代开发自建应用对应的模板id
*/
@SerializedName("template_id")
private String templateId;
/**
* 代开发自建应用的name
*/
@SerializedName("name")
private String name;
/**
* 代开发自建应用的description
*/
@SerializedName("description")
private String description;
/**
* 代开发自建应用的logo url
*/
@SerializedName("logo_url")
private String logoUrl;
/**
* 代开发自建应用的可见范围
*/
@SerializedName("allow_userinfos")
private AllowUserInfos allowUserInfos;
/**
* 代开发自建应用是否被禁用
*/
@SerializedName("close")
private Integer close;
/**
* 代开发自建应用主页url
*/
@SerializedName("home_url")
private String homeUrl;
/**
* 代开发自建应用的模式0 = 代开发自建应用1 = 第三方应用代开发
*/
@SerializedName("app_type")
private Integer appType;
}
/**
* 应用可见范围
*/
@Data
public static class AllowUserInfos implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 应用可见范围(成员)
*/
@SerializedName("user")
private List<User> users;
/**
* 应用可见范围(部门)
*/
@SerializedName("department")
private List<Department> departments;
}
/**
* 成员信息
*/
@Data
public static class User implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 成员userid
*/
@SerializedName("userid")
private String userId;
}
/**
* 部门信息
*/
@Data
public static class Department implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 部门id
*/
@SerializedName("id")
private Integer id;
}
}

View File

@@ -0,0 +1,83 @@
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.io.Serializable;
import java.util.List;
/**
* 应用模板列表.
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
* created on 2026-01-14
*/
@Data
public class WxCpTpTemplateList extends WxCpBaseResp {
/**
* 应用模板列表
*/
@SerializedName("template_list")
private List<Template> templateList;
/**
* From json wx cp tp template list.
*
* @param json the json
* @return the wx cp tp template list
*/
public static WxCpTpTemplateList fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, WxCpTpTemplateList.class);
}
@Override
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
/**
* 应用模板信息
*/
@Data
public static class Template implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 模板id
*/
@SerializedName("template_id")
private String templateId;
/**
* 模板类型
*/
@SerializedName("template_type")
private Integer templateType;
/**
* 应用名称
*/
@SerializedName("app_name")
private String appName;
/**
* 应用logo url
*/
@SerializedName("logo_url")
private String logoUrl;
/**
* 应用简介
*/
@SerializedName("app_desc")
private String appDesc;
/**
* 应用状态
*/
@SerializedName("status")
private Integer status;
}
}

View File

@@ -920,6 +920,15 @@ public interface WxCpApiPathConsts {
*/ */
String GET_CUSTOMIZED_AUTH_URL = "/cgi-bin/service/get_customized_auth_url"; String GET_CUSTOMIZED_AUTH_URL = "/cgi-bin/service/get_customized_auth_url";
/**
* The constant GET_TEMPLATE_LIST.
*/
String GET_TEMPLATE_LIST = "/cgi-bin/service/get_template_list";
/**
* The constant GET_CUSTOMIZED_APP_DETAIL.
*/
String GET_CUSTOMIZED_APP_DETAIL = "/cgi-bin/service/get_customized_app_detail";
/** /**
* The constant CONTACT_SEARCH. * The constant CONTACT_SEARCH.

View File

@@ -0,0 +1,41 @@
package me.chanjar.weixin.cp.tp.service;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.bean.WxCpTpCustomizedAppDetail;
import me.chanjar.weixin.cp.bean.WxCpTpTemplateList;
/**
* 企业微信第三方应用 - 代开发相关接口.
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
* created on 2026-01-14
*/
public interface WxCpTpCustomizedService {
/**
* 获取应用模板列表
* <pre>
* 服务商可通过本接口获取服务商所拥有的应用模板列表
* 文档地址https://developer.work.weixin.qq.com/document/path/97111
* </pre>
*
* @return 应用模板列表
* @throws WxErrorException 微信错误异常
*/
WxCpTpTemplateList getTemplateList() throws WxErrorException;
/**
* 获取代开发应用详情
* <pre>
* 服务商可通过本接口获取某个授权企业中已经安装的代开发自建应用的详情
* 文档地址https://developer.work.weixin.qq.com/document/path/97111
* </pre>
*
* @param authCorpId 授权企业的corpid
* @param agentId 应用的agentid为空时则返回该企业所有的代开发自建应用详情
* @return 代开发应用详情
* @throws WxErrorException 微信错误异常
*/
WxCpTpCustomizedAppDetail getCustomizedAppDetail(String authCorpId, Integer agentId) throws WxErrorException;
}

View File

@@ -662,4 +662,18 @@ public interface WxCpTpService {
void setWxCpTpOAuth2Service(WxCpTpOAuth2Service wxCpTpOAuth2Service); void setWxCpTpOAuth2Service(WxCpTpOAuth2Service wxCpTpOAuth2Service);
/**
* 获取代开发服务
*
* @return 代开发服务
*/
WxCpTpCustomizedService getWxCpTpCustomizedService();
/**
* 设置代开发服务
*
* @param wxCpTpCustomizedService 代开发服务
*/
void setWxCpTpCustomizedService(WxCpTpCustomizedService wxCpTpCustomizedService);
} }

View File

@@ -60,6 +60,7 @@ public abstract class BaseWxCpTpServiceImpl<H, P> implements WxCpTpService, Requ
private WxCpTpLicenseService wxCpTpLicenseService = new WxCpTpLicenseServiceImpl(this); private WxCpTpLicenseService wxCpTpLicenseService = new WxCpTpLicenseServiceImpl(this);
private WxCpTpIdConvertService wxCpTpIdConvertService = new WxCpTpIdConvertServiceImpl(this); private WxCpTpIdConvertService wxCpTpIdConvertService = new WxCpTpIdConvertServiceImpl(this);
private WxCpTpOAuth2Service wxCpTpOAuth2Service = new WxCpTpOAuth2ServiceImpl(this); private WxCpTpOAuth2Service wxCpTpOAuth2Service = new WxCpTpOAuth2ServiceImpl(this);
private WxCpTpCustomizedService wxCpTpCustomizedService = new WxCpTpCustomizedServiceImpl(this);
/** /**
* 全局的是否正在刷新access token的锁. * 全局的是否正在刷新access token的锁.
*/ */
@@ -809,6 +810,16 @@ public abstract class BaseWxCpTpServiceImpl<H, P> implements WxCpTpService, Requ
this.wxCpTpOAuth2Service = wxCpTpOAuth2Service; this.wxCpTpOAuth2Service = wxCpTpOAuth2Service;
} }
@Override
public WxCpTpCustomizedService getWxCpTpCustomizedService() {
return wxCpTpCustomizedService;
}
@Override
public void setWxCpTpCustomizedService(WxCpTpCustomizedService wxCpTpCustomizedService) {
this.wxCpTpCustomizedService = wxCpTpCustomizedService;
}
@Override @Override
public WxCpTpConfigStorage getWxCpTpConfigStorage() { public WxCpTpConfigStorage getWxCpTpConfigStorage() {
return this.configStorage; return this.configStorage;

View File

@@ -0,0 +1,64 @@
package me.chanjar.weixin.cp.tp.service.impl;
import com.google.gson.JsonObject;
import lombok.RequiredArgsConstructor;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.bean.WxCpTpCustomizedAppDetail;
import me.chanjar.weixin.cp.bean.WxCpTpTemplateList;
import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
import me.chanjar.weixin.cp.tp.service.WxCpTpCustomizedService;
import me.chanjar.weixin.cp.tp.service.WxCpTpService;
import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Tp.GET_CUSTOMIZED_APP_DETAIL;
import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Tp.GET_TEMPLATE_LIST;
/**
* 企业微信第三方应用 - 代开发相关接口实现.
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
* created on 2026-01-14
*/
@RequiredArgsConstructor
public class WxCpTpCustomizedServiceImpl implements WxCpTpCustomizedService {
private final WxCpTpService mainService;
@Override
public WxCpTpTemplateList getTemplateList() throws WxErrorException {
String responseText = this.mainService.get(getWxCpTpConfigStorage().getApiUrl(GET_TEMPLATE_LIST)
+ getProviderAccessToken(), null, true);
return WxCpTpTemplateList.fromJson(responseText);
}
@Override
public WxCpTpCustomizedAppDetail getCustomizedAppDetail(String authCorpId, Integer agentId) throws WxErrorException {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("auth_corpid", authCorpId);
if (agentId != null) {
jsonObject.addProperty("agentid", agentId);
}
String responseText = this.mainService.post(getWxCpTpConfigStorage().getApiUrl(GET_CUSTOMIZED_APP_DETAIL)
+ getProviderAccessToken(), jsonObject.toString(), true);
return WxCpTpCustomizedAppDetail.fromJson(responseText);
}
/**
* 获取provider_access_token参数
*
* @return provider_access_token参数
* @throws WxErrorException 微信错误异常
*/
private String getProviderAccessToken() throws WxErrorException {
return "?provider_access_token=" + mainService.getWxCpProviderToken();
}
/**
* 获取tp参数配置
*
* @return config
*/
private WxCpTpConfigStorage getWxCpTpConfigStorage() {
return mainService.getWxCpTpConfigStorage();
}
}

View File

@@ -0,0 +1,178 @@
package me.chanjar.weixin.cp.tp.service.impl;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.bean.WxCpTpCustomizedAppDetail;
import me.chanjar.weixin.cp.bean.WxCpTpTemplateList;
import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
import me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImpl;
import me.chanjar.weixin.cp.tp.service.WxCpTpCustomizedService;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Tp.GET_CUSTOMIZED_APP_DETAIL;
import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Tp.GET_TEMPLATE_LIST;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
/**
* 代开发相关接口测试
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
* created on 2026-01-14
*/
public class WxCpTpCustomizedServiceImplTest {
@Mock
private WxCpTpServiceApacheHttpClientImpl wxCpTpService;
private WxCpTpConfigStorage configStorage;
private WxCpTpCustomizedService wxCpTpCustomizedService;
/**
* Sets up.
*/
@BeforeClass
public void setUp() {
MockitoAnnotations.openMocks(this);
configStorage = new WxCpTpDefaultConfigImpl();
when(wxCpTpService.getWxCpTpConfigStorage()).thenReturn(configStorage);
wxCpTpCustomizedService = new WxCpTpCustomizedServiceImpl(wxCpTpService);
}
/**
* 测试获取应用模板列表
*
* @throws WxErrorException the wx error exception
*/
@Test
public void testGetTemplateList() throws WxErrorException {
String result = "{\n" +
" \"errcode\": 0,\n" +
" \"errmsg\": \"ok\",\n" +
" \"template_list\": [\n" +
" {\n" +
" \"template_id\": \"tpl001\",\n" +
" \"template_type\": 1,\n" +
" \"app_name\": \"测试应用\",\n" +
" \"logo_url\": \"https://example.com/logo.png\",\n" +
" \"app_desc\": \"这是一个测试应用\",\n" +
" \"status\": 1\n" +
" }\n" +
" ]\n" +
"}";
String url = configStorage.getApiUrl(GET_TEMPLATE_LIST);
when(wxCpTpService.getWxCpProviderToken()).thenReturn("mock_provider_token");
when(wxCpTpService.get(eq(url + "?provider_access_token=mock_provider_token"), eq(null), eq(true)))
.thenReturn(result);
final WxCpTpTemplateList templateList = wxCpTpCustomizedService.getTemplateList();
assertNotNull(templateList);
assertEquals(templateList.getErrcode(), Long.valueOf(0));
assertNotNull(templateList.getTemplateList());
assertEquals(templateList.getTemplateList().size(), 1);
assertEquals(templateList.getTemplateList().get(0).getTemplateId(), "tpl001");
assertEquals(templateList.getTemplateList().get(0).getAppName(), "测试应用");
}
/**
* 测试获取代开发应用详情
*
* @throws WxErrorException the wx error exception
*/
@Test
public void testGetCustomizedAppDetail() throws WxErrorException {
String authCorpId = "ww1234567890abcdef";
Integer agentId = 1000001;
String result = "{\n" +
" \"errcode\": 0,\n" +
" \"errmsg\": \"ok\",\n" +
" \"auth_corpid\": \"ww1234567890abcdef\",\n" +
" \"auth_corp_name\": \"测试企业\",\n" +
" \"auth_corp_square_logo_url\": \"https://example.com/square_logo.png\",\n" +
" \"auth_corp_round_logo_url\": \"https://example.com/round_logo.png\",\n" +
" \"auth_corp_type\": 1,\n" +
" \"auth_corp_qrcode_url\": \"https://example.com/qrcode.png\",\n" +
" \"auth_corp_user_limit\": 200,\n" +
" \"auth_corp_full_name\": \"测试企业有限公司\",\n" +
" \"auth_corp_verified_type\": 2,\n" +
" \"auth_corp_industry\": \"互联网\",\n" +
" \"auth_corp_sub_industry\": \"软件服务\",\n" +
" \"auth_corp_location\": \"广东省深圳市\",\n" +
" \"customized_app_list\": [\n" +
" {\n" +
" \"agentid\": 1000001,\n" +
" \"template_id\": \"tpl001\",\n" +
" \"name\": \"测试应用\",\n" +
" \"description\": \"这是一个测试应用\",\n" +
" \"logo_url\": \"https://example.com/logo.png\",\n" +
" \"allow_userinfos\": {\n" +
" \"user\": [\n" +
" {\"userid\": \"zhangsan\"}\n" +
" ],\n" +
" \"department\": [\n" +
" {\"id\": 1}\n" +
" ]\n" +
" },\n" +
" \"close\": 0,\n" +
" \"home_url\": \"https://example.com/home\",\n" +
" \"app_type\": 0\n" +
" }\n" +
" ]\n" +
"}";
String url = configStorage.getApiUrl(GET_CUSTOMIZED_APP_DETAIL);
when(wxCpTpService.getWxCpProviderToken()).thenReturn("mock_provider_token");
when(wxCpTpService.post(eq(url + "?provider_access_token=mock_provider_token"), any(String.class), eq(true)))
.thenReturn(result);
final WxCpTpCustomizedAppDetail appDetail = wxCpTpCustomizedService.getCustomizedAppDetail(authCorpId, agentId);
assertNotNull(appDetail);
assertEquals(appDetail.getErrcode(), Long.valueOf(0));
assertEquals(appDetail.getAuthCorpId(), authCorpId);
assertEquals(appDetail.getAuthCorpName(), "测试企业");
assertNotNull(appDetail.getCustomizedAppList());
assertEquals(appDetail.getCustomizedAppList().size(), 1);
assertEquals(appDetail.getCustomizedAppList().get(0).getAgentId(), agentId);
assertEquals(appDetail.getCustomizedAppList().get(0).getTemplateId(), "tpl001");
assertEquals(appDetail.getCustomizedAppList().get(0).getName(), "测试应用");
}
/**
* 测试获取代开发应用详情不指定agentId
*
* @throws WxErrorException the wx error exception
*/
@Test
public void testGetCustomizedAppDetailWithoutAgentId() throws WxErrorException {
String authCorpId = "ww1234567890abcdef";
String result = "{\n" +
" \"errcode\": 0,\n" +
" \"errmsg\": \"ok\",\n" +
" \"auth_corpid\": \"ww1234567890abcdef\",\n" +
" \"auth_corp_name\": \"测试企业\",\n" +
" \"customized_app_list\": []\n" +
"}";
String url = configStorage.getApiUrl(GET_CUSTOMIZED_APP_DETAIL);
when(wxCpTpService.getWxCpProviderToken()).thenReturn("mock_provider_token");
when(wxCpTpService.post(eq(url + "?provider_access_token=mock_provider_token"), any(String.class), eq(true)))
.thenReturn(result);
final WxCpTpCustomizedAppDetail appDetail = wxCpTpCustomizedService.getCustomizedAppDetail(authCorpId, null);
assertNotNull(appDetail);
assertEquals(appDetail.getErrcode(), Long.valueOf(0));
assertEquals(appDetail.getAuthCorpId(), authCorpId);
}
}