mirror of
https://gitee.com/binary/weixin-java-tools.git
synced 2025-08-23 22:11:40 +08:00
#436 添加一次性订阅消息接口
This commit is contained in:
parent
be50ea009c
commit
f2b05480b5
2
demo.md
2
demo.md
@ -5,7 +5,7 @@
|
||||
1. 微信支付Demo:[码云](http://gitee.com/binary/weixin-java-pay-demo)、[GitHub](http://github.com/binarywang/weixin-java-pay-demo)
|
||||
1. 企业号/企业微信Demo:[码云](http://gitee.com/binary/weixin-java-cp-demo)、[GitHub](http://github.com/binarywang/weixin-java-cp-demo)
|
||||
1. 微信小程序Demo:[码云](http://gitee.com/binary/weixin-java-miniapp-demo)、[GitHub](http://github.com/binarywang/weixin-java-miniapp-demo)
|
||||
1. 开放平台Demo:[码云](http://gitee.com/binary/weixin-java-open-demo)、[GitHub](http://github.com/binarywang/weixin-java-open-demo)
|
||||
1. 开放平台Demo:[码云](http://gitee.com/binary/weixin-java-open-demo)、[GitHub](http://github.com/Wechat-Group/weixin-java-open-demo)
|
||||
1. 公众号Demo:
|
||||
- 使用Spring MVC实现的公众号Demo:[码云](http://gitee.com/binary/weixin-java-mp-demo)、[GitHub](http://github.com/binarywang/weixin-java-mp-demo)
|
||||
- 使用Spring Boot实现的公众号Demo:[码云](http://gitee.com/binary/weixin-java-mp-demo-springboot)、[GitHub](http://github.com/binarywang/weixin-java-mp-demo-springboot)
|
||||
|
@ -85,6 +85,8 @@ public interface WxMpConfigStorage {
|
||||
|
||||
String getAesKey();
|
||||
|
||||
String getTemplateId();
|
||||
|
||||
long getExpiresTime();
|
||||
|
||||
String getOauth2redirectUri();
|
||||
|
@ -18,6 +18,7 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage {
|
||||
protected volatile String appId;
|
||||
protected volatile String secret;
|
||||
protected volatile String token;
|
||||
protected volatile String templateId;
|
||||
protected volatile String accessToken;
|
||||
protected volatile String aesKey;
|
||||
protected volatile long expiresTime;
|
||||
@ -173,6 +174,15 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTemplateId() {
|
||||
return this.templateId;
|
||||
}
|
||||
|
||||
public void setTemplateId(String templateId) {
|
||||
this.templateId = templateId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getExpiresTime() {
|
||||
return this.expiresTime;
|
||||
|
@ -363,6 +363,13 @@ public interface WxMpService {
|
||||
*/
|
||||
WxMpTemplateMsgService getTemplateMsgService();
|
||||
|
||||
/**
|
||||
* 返回一次性订阅消息相关接口方法的实现类对象,以方便调用其各个接口
|
||||
*
|
||||
* @return WxMpSubscribeMsgService
|
||||
*/
|
||||
WxMpSubscribeMsgService getSubscribeMsgService();
|
||||
|
||||
/**
|
||||
* 返回硬件平台相关接口方法的实现类对象,以方便调用其各个接口
|
||||
*
|
||||
|
@ -0,0 +1,40 @@
|
||||
package me.chanjar.weixin.mp.api;
|
||||
|
||||
import me.chanjar.weixin.common.exception.WxErrorException;
|
||||
import me.chanjar.weixin.mp.bean.subscribe.WxMpSubscribeMessage;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 一次性订阅消息接口
|
||||
* https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1500374289_66bvB
|
||||
* </pre>
|
||||
*
|
||||
* @author Mklaus
|
||||
* @date 2018-01-22 上午11:07
|
||||
*/
|
||||
public interface WxMpSubscribeMsgService {
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 构造用户订阅一条模板消息授权的url连接
|
||||
* 详情请见: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1500374289_66bvB
|
||||
* </pre>
|
||||
*
|
||||
* @param redirectURI 用户授权完成后的重定向链接,无需urlencode, 方法内会进行encode
|
||||
* @param scene 重定向后会带上scene参数,开发者可以填0-10000的整形值,用来标识订阅场景值
|
||||
* @param reserved 用于保持请求和回调的状态,授权请后原样带回给第三方 (最多128字节,要求做urlencode)
|
||||
* @return url
|
||||
*/
|
||||
String subscribeMsgAuthorizationUrl(String redirectURI, int scene, String reserved);
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 发送一次性订阅消息
|
||||
* 详情请见: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1500374289_66bvB
|
||||
* </pre>
|
||||
*
|
||||
* @return 消息Id
|
||||
*/
|
||||
boolean sendSubscribeMessage(WxMpSubscribeMessage message) throws WxErrorException;
|
||||
|
||||
}
|
@ -40,6 +40,7 @@ public abstract class WxMpServiceAbstractImpl<H, P> implements WxMpService, Requ
|
||||
private WxMpDataCubeService dataCubeService = new WxMpDataCubeServiceImpl(this);
|
||||
private WxMpUserBlacklistService blackListService = new WxMpUserBlacklistServiceImpl(this);
|
||||
private WxMpTemplateMsgService templateMsgService = new WxMpTemplateMsgServiceImpl(this);
|
||||
private WxMpSubscribeMsgService subscribeMsgService = new WxMpSubscribeMsgServiceImpl(this);
|
||||
private WxMpDeviceService deviceService = new WxMpDeviceServiceImpl(this);
|
||||
private WxMpShakeService shakeService = new WxMpShakeServiceImpl(this);
|
||||
private WxMpMemberCardService memberCardService = new WxMpMemberCardServiceImpl(this);
|
||||
@ -375,6 +376,11 @@ public abstract class WxMpServiceAbstractImpl<H, P> implements WxMpService, Requ
|
||||
return this.templateMsgService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxMpSubscribeMsgService getSubscribeMsgService() {
|
||||
return this.subscribeMsgService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxMpDeviceService getDeviceService() {
|
||||
return this.deviceService;
|
||||
|
@ -0,0 +1,42 @@
|
||||
package me.chanjar.weixin.mp.api.impl;
|
||||
|
||||
import me.chanjar.weixin.common.exception.WxErrorException;
|
||||
import me.chanjar.weixin.common.util.http.URIUtil;
|
||||
import me.chanjar.weixin.mp.api.WxMpConfigStorage;
|
||||
import me.chanjar.weixin.mp.api.WxMpService;
|
||||
import me.chanjar.weixin.mp.api.WxMpSubscribeMsgService;
|
||||
import me.chanjar.weixin.mp.bean.subscribe.WxMpSubscribeMessage;
|
||||
|
||||
/**
|
||||
* @author Mklaus
|
||||
* @date 2018-01-22 上午11:19
|
||||
*/
|
||||
public class WxMpSubscribeMsgServiceImpl implements WxMpSubscribeMsgService {
|
||||
private static final String SUBSCRIBE_MESSAGE_AUTHORIZE_URL =
|
||||
"https://mp.weixin.qq.com/mp/subscribemsg?action=get_confirm&appid=%s&scene=%d&template_id=%s&redirect_url=%s&reserved=%s#wechat_redirect";
|
||||
private static final String SEND_MESSAGE_URL = "https://api.weixin.qq.com/cgi-bin/message/template/subscribe";
|
||||
|
||||
|
||||
private WxMpService wxMpService;
|
||||
|
||||
public WxMpSubscribeMsgServiceImpl(WxMpService wxMpService) {
|
||||
this.wxMpService = wxMpService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String subscribeMsgAuthorizationUrl(String redirectURI, int scene, String reserved) {
|
||||
WxMpConfigStorage storage = this.wxMpService.getWxMpConfigStorage();
|
||||
return String.format(SUBSCRIBE_MESSAGE_AUTHORIZE_URL,
|
||||
storage.getAppId(), scene, storage.getTemplateId(), URIUtil.encodeURIComponent(redirectURI), reserved);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendSubscribeMessage(WxMpSubscribeMessage message) throws WxErrorException {
|
||||
if (message.getTemplateId() == null) {
|
||||
message.setTemplateId(this.wxMpService.getWxMpConfigStorage().getTemplateId());
|
||||
}
|
||||
|
||||
String responseContent = this.wxMpService.post(SEND_MESSAGE_URL, message.toJson());
|
||||
return responseContent != null;
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
package me.chanjar.weixin.mp.bean.subscribe;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage;
|
||||
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;
|
||||
|
||||
/**
|
||||
* @author Mklaus
|
||||
* @date 2018-01-22 下午12:18
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
public class WxMpSubscribeMessage {
|
||||
|
||||
/**
|
||||
* 接收者openid.
|
||||
*/
|
||||
private String toUser;
|
||||
|
||||
/**
|
||||
* 模板ID.
|
||||
*/
|
||||
private String templateId;
|
||||
|
||||
/**
|
||||
* 模板跳转链接.
|
||||
* <pre>
|
||||
* url和miniprogram都是非必填字段,若都不传则模板无跳转;若都传,会优先跳转至小程序。
|
||||
* 开发者可根据实际需要选择其中一种跳转方式即可。当用户的微信客户端版本不支持跳小程序时,将会跳转至url。
|
||||
* </pre>
|
||||
*/
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* 跳小程序所需数据,不需跳小程序可不用传该数据.
|
||||
*
|
||||
* @see #url
|
||||
*/
|
||||
private WxMpTemplateMessage.MiniProgram miniProgram;
|
||||
|
||||
/**
|
||||
* 订阅场景值
|
||||
*/
|
||||
private String scene;
|
||||
|
||||
/**
|
||||
* 消息标题 (15字以内)
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 消息内容文本 (200字以内)
|
||||
*/
|
||||
private String contentValue;
|
||||
|
||||
/**
|
||||
* 消息内容文本颜色
|
||||
*/
|
||||
private String contentColor;
|
||||
|
||||
|
||||
public String toJson() {
|
||||
return WxMpGsonBuilder.INSTANCE.create().toJson(this);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -10,6 +10,7 @@ import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardUpdateResult;
|
||||
import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardUserInfoResult;
|
||||
import me.chanjar.weixin.mp.bean.material.*;
|
||||
import me.chanjar.weixin.mp.bean.result.*;
|
||||
import me.chanjar.weixin.mp.bean.subscribe.WxMpSubscribeMessage;
|
||||
import me.chanjar.weixin.mp.bean.template.WxMpTemplateIndustry;
|
||||
import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage;
|
||||
|
||||
@ -30,6 +31,7 @@ public class WxMpGsonBuilder {
|
||||
INSTANCE.registerTypeAdapter(WxMpMassUploadResult.class, new WxMpMassUploadResultAdapter());
|
||||
INSTANCE.registerTypeAdapter(WxMpQrCodeTicket.class, new WxQrCodeTicketAdapter());
|
||||
INSTANCE.registerTypeAdapter(WxMpTemplateMessage.class, new WxMpTemplateMessageGsonAdapter());
|
||||
INSTANCE.registerTypeAdapter(WxMpSubscribeMessage.class, new WxMpSubscribeMessageGsonAdapter());
|
||||
INSTANCE.registerTypeAdapter(WxMpSemanticQueryResult.class, new WxMpSemanticQueryResultAdapter());
|
||||
INSTANCE.registerTypeAdapter(WxMpOAuth2AccessToken.class, new WxMpOAuth2AccessTokenAdapter());
|
||||
INSTANCE.registerTypeAdapter(WxDataCubeUserSummary.class, new WxMpUserSummaryGsonAdapter());
|
||||
|
@ -0,0 +1,54 @@
|
||||
package me.chanjar.weixin.mp.util.json;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import me.chanjar.weixin.mp.bean.subscribe.WxMpSubscribeMessage;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
/**
|
||||
* @author Mklaus
|
||||
* @date 2018-01-22 下午12:31
|
||||
*/
|
||||
public class WxMpSubscribeMessageGsonAdapter implements JsonSerializer<WxMpSubscribeMessage> {
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(WxMpSubscribeMessage message, Type type, JsonSerializationContext jsonSerializationContext) {
|
||||
JsonObject messageJson = new JsonObject();
|
||||
messageJson.addProperty("touser", message.getToUser());
|
||||
messageJson.addProperty("template_id", message.getTemplateId());
|
||||
|
||||
if (message.getUrl() != null) {
|
||||
messageJson.addProperty("url", message.getUrl());
|
||||
}
|
||||
|
||||
if (message.getMiniProgram() != null) {
|
||||
JsonObject miniProgramJson = new JsonObject();
|
||||
miniProgramJson.addProperty("appid", message.getMiniProgram().getAppid());
|
||||
miniProgramJson.addProperty("pagepath", message.getMiniProgram().getPagePath());
|
||||
messageJson.add("miniprogram", miniProgramJson);
|
||||
}
|
||||
|
||||
messageJson.addProperty("scene", message.getScene());
|
||||
messageJson.addProperty("title", message.getTitle());
|
||||
|
||||
JsonObject data = new JsonObject();
|
||||
messageJson.add("data", data);
|
||||
|
||||
JsonObject content = new JsonObject();
|
||||
data.add("content", content);
|
||||
|
||||
if (message.getContentValue() != null) {
|
||||
content.addProperty("value", message.getContentValue());
|
||||
}
|
||||
|
||||
if (message.getContentColor() != null) {
|
||||
content.addProperty("color", message.getContentColor());
|
||||
}
|
||||
|
||||
return messageJson;
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package me.chanjar.weixin.mp.api.impl;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import me.chanjar.weixin.common.exception.WxErrorException;
|
||||
import me.chanjar.weixin.mp.api.WxMpService;
|
||||
import me.chanjar.weixin.mp.api.test.ApiTestModule;
|
||||
import me.chanjar.weixin.mp.api.test.TestConfigStorage;
|
||||
import me.chanjar.weixin.mp.bean.subscribe.WxMpSubscribeMessage;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Guice;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
/**
|
||||
* @author Mklaus
|
||||
* @date 2018-01-22 下午2:02
|
||||
*/
|
||||
@Guice(modules = ApiTestModule.class)
|
||||
public class WxMpSubscribeMsgServiceImplTest {
|
||||
|
||||
@Inject
|
||||
protected WxMpService wxService;
|
||||
|
||||
@Test
|
||||
public void testSendSubscribeMessage() throws WxErrorException {
|
||||
TestConfigStorage configStorage = (TestConfigStorage) this.wxService
|
||||
.getWxMpConfigStorage();
|
||||
|
||||
WxMpSubscribeMessage message = WxMpSubscribeMessage.builder()
|
||||
.title("weixin test")
|
||||
.toUser(configStorage.getOpenid())
|
||||
.scene("1000")
|
||||
.contentColor("#FF0000")
|
||||
.contentValue("Send subscribe message test")
|
||||
.build();
|
||||
|
||||
try {
|
||||
boolean send = this.wxService.getSubscribeMsgService().sendSubscribeMessage(message);
|
||||
Assert.assertTrue(send);
|
||||
} catch (WxErrorException e) {
|
||||
// 当用户没有授权,获取之前的授权已使用。微信会返回错误代码 {"errcode":43101,"errmsg":"user refuse to accept the msg hint: [xxxxxxxxxxx]"}
|
||||
if (e.getError().getErrorCode() != 43101) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package me.chanjar.weixin.mp.bean.subscribe;
|
||||
|
||||
import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage;
|
||||
import org.testng.annotations.Test;
|
||||
import static org.testng.AssertJUnit.assertEquals;
|
||||
|
||||
/**
|
||||
* @author Mklaus
|
||||
* @date 2018-01-22 下午1:41
|
||||
*/
|
||||
public class WxMpSubscribeMessageTest {
|
||||
@Test
|
||||
public void testToJson() {
|
||||
String actual = "{" +
|
||||
"\"touser\":\"OPENID\"," +
|
||||
"\"template_id\":\"TEMPLATE_ID\"," +
|
||||
"\"url\":\"URL\"," +
|
||||
"\"miniprogram\":{" +
|
||||
"\"appid\":\"xiaochengxuappid12345\"," +
|
||||
"\"pagepath\":\"index?foo=bar\"" +
|
||||
"}," +
|
||||
"\"scene\":\"SCENE\"," +
|
||||
"\"title\":\"TITLE\"," +
|
||||
"\"data\":{" +
|
||||
"\"content\":{" +
|
||||
"\"value\":\"VALUE\"," +
|
||||
"\"color\":\"COLOR\"" +
|
||||
"}" +
|
||||
"}" +
|
||||
"}";
|
||||
|
||||
WxMpSubscribeMessage message = WxMpSubscribeMessage.builder()
|
||||
.toUser("OPENID")
|
||||
.templateId("TEMPLATE_ID")
|
||||
.url("URL")
|
||||
.miniProgram(new WxMpTemplateMessage.MiniProgram("xiaochengxuappid12345", "index?foo=bar"))
|
||||
.scene("SCENE")
|
||||
.title("TITLE")
|
||||
.contentValue("VALUE")
|
||||
.contentColor("COLOR")
|
||||
.build();
|
||||
|
||||
assertEquals(message.toJson(), actual);
|
||||
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@ class WxMpDemoInMemoryConfigStorage extends WxMpInMemoryConfigStorage {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SimpleWxConfigProvider [appId=" + this.appId + ", secret=" + this.secret + ", accessToken=" + this.accessToken
|
||||
+ ", expiresTime=" + this.expiresTime + ", token=" + this.token + ", aesKey=" + this.aesKey + "]";
|
||||
+ ", expiresTime=" + this.expiresTime + ", token=" + this.token + ", aesKey=" + this.aesKey + ", templateId=" + this.templateId + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -342,6 +342,11 @@ public class WxOpenInMemoryConfigStorage implements WxOpenConfigStorage {
|
||||
return wxOpenConfigStorage.getComponentToken();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTemplateId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getExpiresTime() {
|
||||
return 0;
|
||||
|
Loading…
Reference in New Issue
Block a user