1
0
mirror of synced 2026-05-21 01:36:26 +08:00

🆕 #3952【小程序】新增服务卡片消息(订阅消息 2.0)服务端能力

This commit is contained in:
Copilot
2026-05-11 20:26:44 +08:00
committed by GitHub
parent 9cf7e232d6
commit b545874f1a
8 changed files with 451 additions and 0 deletions

View File

@@ -1,5 +1,9 @@
package cn.binarywang.wx.miniapp.api;
import cn.binarywang.wx.miniapp.bean.WxMaGetUserNotifyRequest;
import cn.binarywang.wx.miniapp.bean.WxMaGetUserNotifyResult;
import cn.binarywang.wx.miniapp.bean.WxMaServiceNotifyExtRequest;
import cn.binarywang.wx.miniapp.bean.WxMaServiceNotifyRequest;
import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage;
import me.chanjar.weixin.common.bean.subscribemsg.CategoryData;
import me.chanjar.weixin.common.bean.subscribemsg.PubTemplateKeyword;
@@ -113,4 +117,44 @@ public interface WxMaSubscribeService {
*/
void sendSubscribeMsg(WxMaSubscribeMessage subscribeMessage) throws WxErrorException;
/**
* <pre>
* 激活与更新服务卡片
*
* 详情请见: <a href="https://developers.weixin.qq.com/miniprogram/dev/server/API/mp-message-management/subscribe-message/api_setusernotify.html">激活与更新服务卡片</a>
* 接口url格式: POST https://api.weixin.qq.com/wxa/setusernotify?access_token=ACCESS_TOKEN
* </pre>
*
* @param request 请求参数
* @throws WxErrorException .
*/
void setUserNotify(WxMaServiceNotifyRequest request) throws WxErrorException;
/**
* <pre>
* 更新服务卡片扩展信息
*
* 详情请见: <a href="https://developers.weixin.qq.com/miniprogram/dev/server/API/mp-message-management/subscribe-message/api_setusernotifyext.html">更新服务卡片扩展信息</a>
* 接口url格式: POST https://api.weixin.qq.com/wxa/setusernotifyext?access_token=ACCESS_TOKEN
* </pre>
*
* @param request 请求参数
* @throws WxErrorException .
*/
void setUserNotifyExt(WxMaServiceNotifyExtRequest request) throws WxErrorException;
/**
* <pre>
* 查询服务卡片状态
*
* 详情请见: <a href="https://developers.weixin.qq.com/miniprogram/dev/server/API/mp-message-management/subscribe-message/api_getusernotify.html">查询服务卡片状态</a>
* 接口url格式: POST https://api.weixin.qq.com/wxa/getusernotify?access_token=ACCESS_TOKEN
* </pre>
*
* @param request 请求参数
* @return 服务卡片状态
* @throws WxErrorException .
*/
WxMaGetUserNotifyResult getUserNotify(WxMaGetUserNotifyRequest request) throws WxErrorException;
}

View File

@@ -2,6 +2,10 @@ package cn.binarywang.wx.miniapp.api.impl;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.WxMaSubscribeService;
import cn.binarywang.wx.miniapp.bean.WxMaGetUserNotifyRequest;
import cn.binarywang.wx.miniapp.bean.WxMaGetUserNotifyResult;
import cn.binarywang.wx.miniapp.bean.WxMaServiceNotifyExtRequest;
import cn.binarywang.wx.miniapp.bean.WxMaServiceNotifyRequest;
import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.bean.subscribemsg.CategoryData;
@@ -89,4 +93,32 @@ public class WxMaSubscribeServiceImpl implements WxMaSubscribeService {
throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
}
}
@Override
public void setUserNotify(WxMaServiceNotifyRequest request) throws WxErrorException {
String responseContent = this.service.post(SERVICE_NOTIFY_SET_URL, request.toJson());
JsonObject jsonObject = GsonParser.parse(responseContent);
if (jsonObject.get(WxConsts.ERR_CODE).getAsInt() != 0) {
throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
}
}
@Override
public void setUserNotifyExt(WxMaServiceNotifyExtRequest request) throws WxErrorException {
String responseContent = this.service.post(SERVICE_NOTIFY_SET_EXT_URL, request.toJson());
JsonObject jsonObject = GsonParser.parse(responseContent);
if (jsonObject.get(WxConsts.ERR_CODE).getAsInt() != 0) {
throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
}
}
@Override
public WxMaGetUserNotifyResult getUserNotify(WxMaGetUserNotifyRequest request) throws WxErrorException {
String responseContent = this.service.post(SERVICE_NOTIFY_GET_URL, request.toJson());
JsonObject jsonObject = GsonParser.parse(responseContent);
if (jsonObject.get(WxConsts.ERR_CODE).getAsInt() != 0) {
throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
}
return WxMaGsonBuilder.create().fromJson(responseContent, WxMaGetUserNotifyResult.class);
}
}

View File

@@ -0,0 +1,66 @@
package cn.binarywang.wx.miniapp.bean;
import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
import com.google.gson.annotations.SerializedName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 查询服务卡片状态请求.
*
* <p>接口文档:
* <a href="https://developers.weixin.qq.com/miniprogram/dev/server/API/mp-message-management/subscribe-message/api_getusernotify.html">
* 查询服务卡片状态</a>
*
* @author GitHub Copilot
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class WxMaGetUserNotifyRequest implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户身份标识符.
* <pre>
* 参数openid
* 是否必填:是
* </pre>
*/
@SerializedName("openid")
private String openid;
/**
* 动态更新令牌.
* <pre>
* 参数notify_code
* 是否必填:是
* </pre>
*/
@SerializedName("notify_code")
private String notifyCode;
/**
* 卡片ID.
* <pre>
* 参数notify_type
* 是否必填:是
* </pre>
*/
@SerializedName("notify_type")
private Integer notifyType;
/**
* 转为 JSON 字符串.
*
* @return JSON 字符串
*/
public String toJson() {
return WxMaGsonBuilder.create().toJson(this);
}
}

View File

@@ -0,0 +1,60 @@
package cn.binarywang.wx.miniapp.bean;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
/**
* 查询服务卡片状态响应.
*
* <p>接口文档:
* <a href="https://developers.weixin.qq.com/miniprogram/dev/server/API/mp-message-management/subscribe-message/api_getusernotify.html">
* 查询服务卡片状态</a>
*
* @author GitHub Copilot
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class WxMaGetUserNotifyResult extends WxMaBaseResponse {
private static final long serialVersionUID = 1L;
/**
* 卡片状态信息.
*/
@SerializedName("notify_info")
private NotifyInfo notifyInfo;
/**
* 卡片状态详情.
*/
@Data
public static class NotifyInfo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 卡片ID.
*/
@SerializedName("notify_type")
private Integer notifyType;
/**
* 上次有效推送的卡片状态与状态相关字段,没推送过为空字符串.
*/
@SerializedName("content_json")
private String contentJson;
/**
* code 状态0 正常1 有风险2 异常10 用户拒收本次code.
*/
@SerializedName("code_state")
private Integer codeState;
/**
* code 过期时间,秒级时间戳.
*/
@SerializedName("code_expire_time")
private Long codeExpireTime;
}
}

View File

@@ -0,0 +1,82 @@
package cn.binarywang.wx.miniapp.bean;
import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
import com.google.gson.annotations.SerializedName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 更新服务卡片扩展信息请求.
*
* <p>接口文档:
* <a href="https://developers.weixin.qq.com/miniprogram/dev/server/API/mp-message-management/subscribe-message/api_setusernotifyext.html">
* 更新服务卡片扩展信息</a>
*
* @author GitHub Copilot
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class WxMaServiceNotifyExtRequest implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户身份标识符.
* <pre>
* 参数openid
* 是否必填:是
* 描述:用户身份标识符。
* 当使用微信支付订单号作为 code 时,需要与实际支付用户一致;
* 当通过前端获取 code 时,需要与点击 button 的用户一致。
* </pre>
*/
@SerializedName("openid")
private String openid;
/**
* 卡片ID.
* <pre>
* 参数notify_type
* 是否必填:是
* 描述卡片ID。
* </pre>
*/
@SerializedName("notify_type")
private Integer notifyType;
/**
* 动态更新令牌.
* <pre>
* 参数notify_code
* 是否必填:是
* 描述:动态更新令牌。
* </pre>
*/
@SerializedName("notify_code")
private String notifyCode;
/**
* 扩展信息.
* <pre>
* 参数ext_json
* 是否必填:是
* 描述:扩展信息,不同卡片的定义不同。
* </pre>
*/
@SerializedName("ext_json")
private String extJson;
/**
* 转为 JSON 字符串.
*
* @return JSON 字符串
*/
public String toJson() {
return WxMaGsonBuilder.create().toJson(this);
}
}

View File

@@ -0,0 +1,93 @@
package cn.binarywang.wx.miniapp.bean;
import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
import com.google.gson.annotations.SerializedName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 激活与更新服务卡片请求.
*
* <p>接口文档:
* <a href="https://developers.weixin.qq.com/miniprogram/dev/server/API/mp-message-management/subscribe-message/api_setusernotify.html">
* 激活与更新服务卡片</a>
*
* @author GitHub Copilot
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class WxMaServiceNotifyRequest implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户身份标识符.
* <pre>
* 参数openid
* 是否必填:是
* 描述:用户身份标识符。
* 当使用微信支付订单号作为 code 时,需要与实际支付用户一致;
* 当通过前端获取 code 时,需要与点击 button 的用户一致。
* </pre>
*/
@SerializedName("openid")
private String openid;
/**
* 卡片ID.
* <pre>
* 参数notify_type
* 是否必填:是
* 描述卡片ID。
* </pre>
*/
@SerializedName("notify_type")
private Integer notifyType;
/**
* 动态更新令牌.
* <pre>
* 参数notify_code
* 是否必填:是
* 描述:动态更新令牌。
* </pre>
*/
@SerializedName("notify_code")
private String notifyCode;
/**
* 卡片状态与状态相关字段.
* <pre>
* 参数content_json
* 是否必填:是
* 描述:卡片状态与状态相关字段,不同卡片的定义不同。
* </pre>
*/
@SerializedName("content_json")
private String contentJson;
/**
* 微信支付订单号验证字段(可选).
* <pre>
* 参数check_json
* 是否必填:否
* 描述:微信支付订单号验证字段。当将微信支付订单号作为 notify_code 时,在激活时需要传入。
* </pre>
*/
@SerializedName("check_json")
private String checkJson;
/**
* 转为 JSON 字符串.
*
* @return JSON 字符串
*/
public String toJson() {
return WxMaGsonBuilder.create().toJson(this);
}
}

View File

@@ -358,6 +358,15 @@ public class WxMaApiUrlConstants {
/** 发送订阅消息 */
String SUBSCRIBE_MSG_SEND_URL = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send";
/** 激活与更新服务卡片 */
String SERVICE_NOTIFY_SET_URL = "https://api.weixin.qq.com/wxa/setusernotify";
/** 更新服务卡片扩展信息 */
String SERVICE_NOTIFY_SET_EXT_URL = "https://api.weixin.qq.com/wxa/setusernotifyext";
/** 查询服务卡片状态 */
String SERVICE_NOTIFY_GET_URL = "https://api.weixin.qq.com/wxa/getusernotify";
}
public interface User {

View File

@@ -1,6 +1,11 @@
package cn.binarywang.wx.miniapp.api.impl;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.WxMaSubscribeService;
import cn.binarywang.wx.miniapp.bean.WxMaGetUserNotifyRequest;
import cn.binarywang.wx.miniapp.bean.WxMaGetUserNotifyResult;
import cn.binarywang.wx.miniapp.bean.WxMaServiceNotifyExtRequest;
import cn.binarywang.wx.miniapp.bean.WxMaServiceNotifyRequest;
import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage;
import me.chanjar.weixin.common.bean.subscribemsg.CategoryData;
import me.chanjar.weixin.common.bean.subscribemsg.PubTemplateKeyword;
@@ -10,12 +15,16 @@ import cn.binarywang.wx.miniapp.test.ApiTestModule;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
import me.chanjar.weixin.common.error.WxErrorException;
import org.testng.Assert;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* 测试类.
@@ -71,4 +80,60 @@ public class WxMaSubscribeServiceImplTest {
// TODO 待完善补充
this.wxService.getSubscribeService().sendSubscribeMsg(WxMaSubscribeMessage.builder().build());
}
@Test
public void testSetUserNotify() throws WxErrorException {
WxMaService service = mock(WxMaService.class);
when(service.post(anyString(), anyString())).thenReturn("{\"errcode\":0,\"errmsg\":\"ok\"}");
WxMaSubscribeService subscribeService = new WxMaSubscribeServiceImpl(service);
WxMaServiceNotifyRequest request = WxMaServiceNotifyRequest.builder()
.openid("test_openid")
.notifyType(1)
.notifyCode("test_notify_code")
.contentJson("{}")
.build();
subscribeService.setUserNotify(request);
}
@Test
public void testSetUserNotifyExt() throws WxErrorException {
WxMaService service = mock(WxMaService.class);
when(service.post(anyString(), anyString())).thenReturn("{\"errcode\":0,\"errmsg\":\"ok\"}");
WxMaSubscribeService subscribeService = new WxMaSubscribeServiceImpl(service);
WxMaServiceNotifyExtRequest request = WxMaServiceNotifyExtRequest.builder()
.openid("test_openid")
.notifyType(1)
.notifyCode("test_notify_code")
.extJson("{}")
.build();
subscribeService.setUserNotifyExt(request);
}
@Test
public void testGetUserNotify() throws WxErrorException {
WxMaService service = mock(WxMaService.class);
when(service.post(anyString(), anyString())).thenReturn(
"{\"errcode\":0,\"errmsg\":\"ok\","
+ "\"notify_info\":{"
+ "\"notify_type\":1,"
+ "\"content_json\":\"{\\\"status\\\":1}\","
+ "\"code_state\":0,"
+ "\"code_expire_time\":1700000000"
+ "}}");
WxMaSubscribeService subscribeService = new WxMaSubscribeServiceImpl(service);
WxMaGetUserNotifyRequest request = WxMaGetUserNotifyRequest.builder()
.openid("test_openid")
.notifyCode("test_notify_code")
.notifyType(1)
.build();
WxMaGetUserNotifyResult result = subscribeService.getUserNotify(request);
Assert.assertNotNull(result);
Assert.assertNotNull(result.getNotifyInfo());
Assert.assertEquals(result.getNotifyInfo().getNotifyType().intValue(), 1);
Assert.assertEquals(result.getNotifyInfo().getCodeState().intValue(), 0);
Assert.assertEquals(result.getNotifyInfo().getCodeExpireTime().longValue(), 1700000000L);
}
}