feat: 为企业微信第三方应用SDK添加消息发送服务(WxCpTpMessageService)
Co-authored-by: binarywang <1343140+binarywang@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
package me.chanjar.weixin.cp.tp.service;
|
||||
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
import me.chanjar.weixin.cp.bean.message.*;
|
||||
|
||||
/**
|
||||
* 企业微信第三方应用消息推送接口.
|
||||
*
|
||||
* <p>第三方应用使用授权企业的 access_token 代表授权企业发送应用消息。</p>
|
||||
*
|
||||
* @author <a href="https://github.com/github-copilot">GitHub Copilot</a>
|
||||
*/
|
||||
public interface WxCpTpMessageService {
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 发送应用消息(代授权企业发送).
|
||||
* 详情请见: https://work.weixin.qq.com/api/doc/90000/90135/90236
|
||||
* </pre>
|
||||
*
|
||||
* @param message 要发送的消息对象
|
||||
* @param corpId 授权企业的 corpId
|
||||
* @return 消息发送结果
|
||||
* @throws WxErrorException 微信错误异常
|
||||
*/
|
||||
WxCpMessageSendResult send(WxCpMessage message, String corpId) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 查询应用消息发送统计.
|
||||
* 请求方式:POST(HTTPS)
|
||||
* 请求地址:https://qyapi.weixin.qq.com/cgi-bin/message/get_statistics?access_token=ACCESS_TOKEN
|
||||
* 详情请见: https://work.weixin.qq.com/api/doc/90000/90135/92369
|
||||
* </pre>
|
||||
*
|
||||
* @param timeType 查询哪天的数据,0:当天;1:昨天。默认为0。
|
||||
* @param corpId 授权企业的 corpId
|
||||
* @return 统计结果
|
||||
* @throws WxErrorException 微信错误异常
|
||||
*/
|
||||
WxCpMessageSendStatistics getStatistics(int timeType, String corpId) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 互联企业发送应用消息.
|
||||
* 请求地址:https://qyapi.weixin.qq.com/cgi-bin/linkedcorp/message/send?access_token=ACCESS_TOKEN
|
||||
* 文章地址:https://work.weixin.qq.com/api/doc/90000/90135/90250
|
||||
* </pre>
|
||||
*
|
||||
* @param message 要发送的消息对象
|
||||
* @param corpId 授权企业的 corpId
|
||||
* @return 消息发送结果
|
||||
* @throws WxErrorException 微信错误异常
|
||||
*/
|
||||
WxCpLinkedCorpMessageSendResult sendLinkedCorpMessage(WxCpLinkedCorpMessage message, String corpId) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 发送「学校通知」.
|
||||
* https://developer.work.weixin.qq.com/document/path/92321
|
||||
* 学校可以通过此接口来给家长发送不同类型的学校通知。
|
||||
* 请求方式:POST(HTTPS)
|
||||
* 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/message/send?access_token=ACCESS_TOKEN
|
||||
* </pre>
|
||||
*
|
||||
* @param message 要发送的消息对象
|
||||
* @param corpId 授权企业的 corpId
|
||||
* @return 消息发送结果
|
||||
* @throws WxErrorException 微信错误异常
|
||||
*/
|
||||
WxCpSchoolContactMessageSendResult sendSchoolContactMessage(WxCpSchoolContactMessage message, String corpId) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 撤回应用消息.
|
||||
* 请求地址: https://qyapi.weixin.qq.com/cgi-bin/message/recall?access_token=ACCESS_TOKEN
|
||||
* 文档地址: https://developer.work.weixin.qq.com/document/path/94867
|
||||
* </pre>
|
||||
*
|
||||
* @param msgId 消息id
|
||||
* @param corpId 授权企业的 corpId
|
||||
* @throws WxErrorException 微信错误异常
|
||||
*/
|
||||
void recall(String msgId, String corpId) throws WxErrorException;
|
||||
|
||||
}
|
||||
@@ -530,6 +530,20 @@ public interface WxCpTpService {
|
||||
*/
|
||||
WxCpTpLicenseService getWxCpTpLicenseService();
|
||||
|
||||
/**
|
||||
* get message service
|
||||
*
|
||||
* @return WxCpTpMessageService wx cp tp message service
|
||||
*/
|
||||
WxCpTpMessageService getWxCpTpMessageService();
|
||||
|
||||
/**
|
||||
* set message service
|
||||
*
|
||||
* @param wxCpTpMessageService the message service
|
||||
*/
|
||||
void setWxCpTpMessageService(WxCpTpMessageService wxCpTpMessageService);
|
||||
|
||||
WxCpTpXmlMessage fromEncryptedXml(String encryptedXml,
|
||||
String timestamp, String nonce, String msgSignature);
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ public abstract class BaseWxCpTpServiceImpl<H, P> implements WxCpTpService, Requ
|
||||
private WxCpTpIdConvertService wxCpTpIdConvertService = new WxCpTpIdConvertServiceImpl(this);
|
||||
private WxCpTpOAuth2Service wxCpTpOAuth2Service = new WxCpTpOAuth2ServiceImpl(this);
|
||||
private WxCpTpCustomizedService wxCpTpCustomizedService = new WxCpTpCustomizedServiceImpl(this);
|
||||
private WxCpTpMessageService wxCpTpMessageService = new WxCpTpMessageServiceImpl(this);
|
||||
/**
|
||||
* 全局的是否正在刷新access token的锁.
|
||||
*/
|
||||
@@ -665,6 +666,16 @@ public abstract class BaseWxCpTpServiceImpl<H, P> implements WxCpTpService, Requ
|
||||
this.wxCpTpLicenseService = wxCpTpLicenseService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxCpTpMessageService getWxCpTpMessageService() {
|
||||
return wxCpTpMessageService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWxCpTpMessageService(WxCpTpMessageService wxCpTpMessageService) {
|
||||
this.wxCpTpMessageService = wxCpTpMessageService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWxCpTpUserService(WxCpTpUserService wxCpTpUserService) {
|
||||
this.wxCpTpUserService = wxCpTpUserService;
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package me.chanjar.weixin.cp.tp.service.impl;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.JsonObject;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
import me.chanjar.weixin.cp.bean.message.*;
|
||||
import me.chanjar.weixin.cp.tp.service.WxCpTpMessageService;
|
||||
import me.chanjar.weixin.cp.tp.service.WxCpTpService;
|
||||
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
|
||||
|
||||
import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Message.*;
|
||||
|
||||
/**
|
||||
* 企业微信第三方应用消息推送接口实现类.
|
||||
*
|
||||
* <p>代授权企业发送应用消息,所有方法均需传入授权企业的 corpId。</p>
|
||||
*
|
||||
* @author <a href="https://github.com/github-copilot">GitHub Copilot</a>
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class WxCpTpMessageServiceImpl implements WxCpTpMessageService {
|
||||
|
||||
private final WxCpTpService mainService;
|
||||
|
||||
@Override
|
||||
public WxCpMessageSendResult send(WxCpMessage message, String corpId) throws WxErrorException {
|
||||
String url = mainService.getWxCpTpConfigStorage().getApiUrl(MESSAGE_SEND)
|
||||
+ "?access_token=" + mainService.getWxCpTpConfigStorage().getAccessToken(corpId);
|
||||
return WxCpMessageSendResult.fromJson(this.mainService.post(url, message.toJson(), true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxCpMessageSendStatistics getStatistics(int timeType, String corpId) throws WxErrorException {
|
||||
String url = mainService.getWxCpTpConfigStorage().getApiUrl(GET_STATISTICS)
|
||||
+ "?access_token=" + mainService.getWxCpTpConfigStorage().getAccessToken(corpId);
|
||||
return WxCpMessageSendStatistics.fromJson(
|
||||
this.mainService.post(url, WxCpGsonBuilder.create().toJson(ImmutableMap.of("time_type", timeType)), true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxCpLinkedCorpMessageSendResult sendLinkedCorpMessage(WxCpLinkedCorpMessage message, String corpId)
|
||||
throws WxErrorException {
|
||||
String url = mainService.getWxCpTpConfigStorage().getApiUrl(LINKEDCORP_MESSAGE_SEND)
|
||||
+ "?access_token=" + mainService.getWxCpTpConfigStorage().getAccessToken(corpId);
|
||||
return WxCpLinkedCorpMessageSendResult.fromJson(this.mainService.post(url, message.toJson(), true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxCpSchoolContactMessageSendResult sendSchoolContactMessage(WxCpSchoolContactMessage message, String corpId)
|
||||
throws WxErrorException {
|
||||
String url = mainService.getWxCpTpConfigStorage().getApiUrl(EXTERNAL_CONTACT_MESSAGE_SEND)
|
||||
+ "?access_token=" + mainService.getWxCpTpConfigStorage().getAccessToken(corpId);
|
||||
return WxCpSchoolContactMessageSendResult.fromJson(this.mainService.post(url, message.toJson(), true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recall(String msgId, String corpId) throws WxErrorException {
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.addProperty("msgid", msgId);
|
||||
String url = mainService.getWxCpTpConfigStorage().getApiUrl(MESSAGE_RECALL)
|
||||
+ "?access_token=" + mainService.getWxCpTpConfigStorage().getAccessToken(corpId);
|
||||
this.mainService.post(url, jsonObject.toString(), true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package me.chanjar.weixin.cp.tp.service.impl;
|
||||
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
import me.chanjar.weixin.cp.bean.message.WxCpMessage;
|
||||
import me.chanjar.weixin.cp.bean.message.WxCpMessageSendResult;
|
||||
import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
|
||||
import me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImpl;
|
||||
import me.chanjar.weixin.cp.tp.service.WxCpTpMessageService;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.testng.annotations.AfterClass;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Message.MESSAGE_RECALL;
|
||||
import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Message.MESSAGE_SEND;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.contains;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.testng.Assert.assertNotNull;
|
||||
|
||||
/**
|
||||
* 企业微信第三方应用消息推送服务测试.
|
||||
*
|
||||
* @author GitHub Copilot
|
||||
*/
|
||||
public class WxCpTpMessageServiceImplTest {
|
||||
|
||||
@Mock
|
||||
private WxCpTpServiceApacheHttpClientImpl wxCpTpService;
|
||||
|
||||
@Mock
|
||||
private WxCpTpConfigStorage configStorage;
|
||||
|
||||
private WxCpTpMessageService wxCpTpMessageService;
|
||||
|
||||
private AutoCloseable mockitoAnnotations;
|
||||
|
||||
/**
|
||||
* Sets up.
|
||||
*/
|
||||
@BeforeClass
|
||||
public void setUp() {
|
||||
mockitoAnnotations = MockitoAnnotations.openMocks(this);
|
||||
when(wxCpTpService.getWxCpTpConfigStorage()).thenReturn(configStorage);
|
||||
WxCpTpDefaultConfigImpl defaultConfig = new WxCpTpDefaultConfigImpl();
|
||||
when(configStorage.getApiUrl(anyString()))
|
||||
.thenAnswer(invocation -> defaultConfig.getApiUrl(invocation.getArgument(0)));
|
||||
wxCpTpMessageService = new WxCpTpMessageServiceImpl(wxCpTpService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tear down.
|
||||
*
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
@AfterClass
|
||||
public void tearDown() throws Exception {
|
||||
mockitoAnnotations.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 send 方法:验证使用了 corpId 对应的 access_token,并以 withoutSuiteAccessToken=true 发起请求.
|
||||
*
|
||||
* @throws WxErrorException 微信错误异常
|
||||
*/
|
||||
@Test
|
||||
public void testSendMessage() throws WxErrorException {
|
||||
String corpId = "test_corp_id";
|
||||
String accessToken = "test_access_token";
|
||||
String mockResponse = "{\"errcode\":0,\"errmsg\":\"ok\",\"msgid\":\"msg_001\"}";
|
||||
|
||||
when(configStorage.getAccessToken(corpId)).thenReturn(accessToken);
|
||||
String expectedUrl = new WxCpTpDefaultConfigImpl().getApiUrl(MESSAGE_SEND)
|
||||
+ "?access_token=" + accessToken;
|
||||
when(wxCpTpService.post(eq(expectedUrl), anyString(), eq(true))).thenReturn(mockResponse);
|
||||
|
||||
WxCpMessage message = WxCpMessage.TEXT().toUser("zhangsan").content("hello").agentId(1).build();
|
||||
WxCpMessageSendResult result = wxCpTpMessageService.send(message, corpId);
|
||||
assertNotNull(result);
|
||||
|
||||
// 验证调用时传入了 withoutSuiteAccessToken=true,确保不会附加 suite_access_token
|
||||
verify(wxCpTpService).post(eq(expectedUrl), anyString(), eq(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 recall 方法:验证使用了 corpId 对应的 access_token,并以 withoutSuiteAccessToken=true 发起请求.
|
||||
*
|
||||
* @throws WxErrorException 微信错误异常
|
||||
*/
|
||||
@Test
|
||||
public void testRecallMessage() throws WxErrorException {
|
||||
String corpId = "test_corp_id";
|
||||
String accessToken = "test_access_token";
|
||||
String msgId = "test_msg_id";
|
||||
|
||||
when(configStorage.getAccessToken(corpId)).thenReturn(accessToken);
|
||||
String expectedUrl = new WxCpTpDefaultConfigImpl().getApiUrl(MESSAGE_RECALL)
|
||||
+ "?access_token=" + accessToken;
|
||||
when(wxCpTpService.post(eq(expectedUrl), contains(msgId), eq(true))).thenReturn("{\"errcode\":0,\"errmsg\":\"ok\"}");
|
||||
|
||||
wxCpTpMessageService.recall(msgId, corpId);
|
||||
|
||||
// 验证调用时传入了 withoutSuiteAccessToken=true,确保不会附加 suite_access_token
|
||||
verify(wxCpTpService).post(eq(expectedUrl), contains(msgId), eq(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 getWxCpTpMessageService 方法:验证 BaseWxCpTpServiceImpl 中正确初始化了消息服务.
|
||||
*/
|
||||
@Test
|
||||
public void testGetWxCpTpMessageServiceFromBase() {
|
||||
WxCpTpServiceApacheHttpClientImpl tpService = new WxCpTpServiceApacheHttpClientImpl();
|
||||
assertNotNull(tpService.getWxCpTpMessageService());
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
<class name="me.chanjar.weixin.cp.tp.service.impl.WxCpTpServiceApacheHttpClientImplTest"/>
|
||||
<class name="me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImplTest"/>
|
||||
<class name="me.chanjar.weixin.cp.tp.service.impl.WxCpTpTagServiceImplTest"/>
|
||||
<class name="me.chanjar.weixin.cp.tp.service.impl.WxCpTpMessageServiceImplTest"/>
|
||||
</classes>
|
||||
</test>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user