🎨 #3376 【小程序】完善事件消息推送对json数据格式的支持
This commit is contained in:
@@ -0,0 +1,67 @@
|
|||||||
|
package cn.binarywang.wx.miniapp.message;
|
||||||
|
|
||||||
|
import cn.binarywang.wx.miniapp.config.WxMaConfig;
|
||||||
|
import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
|
||||||
|
import cn.binarywang.wx.miniapp.util.crypt.WxMaCryptUtils;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信小程序输出给微信服务器的JSON格式消息.
|
||||||
|
*
|
||||||
|
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class WxMaJsonOutMessage implements WxMaOutMessage {
|
||||||
|
private static final long serialVersionUID = 4241135225946919154L;
|
||||||
|
|
||||||
|
protected String toUserName;
|
||||||
|
protected String fromUserName;
|
||||||
|
protected Long createTime;
|
||||||
|
protected String msgType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换成JSON格式.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toJson() {
|
||||||
|
return WxMaGsonBuilder.create().toJson(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换成XML格式(对于JSON消息类型,返回JSON格式).
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toXml() {
|
||||||
|
// JSON消息类型默认返回JSON格式
|
||||||
|
return toJson();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换成加密的JSON格式.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toEncryptedJson(WxMaConfig config) {
|
||||||
|
String plainJson = toJson();
|
||||||
|
WxMaCryptUtils pc = new WxMaCryptUtils(config);
|
||||||
|
return pc.encrypt(plainJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换成加密的XML格式(对于JSON消息类型,返回加密的JSON格式).
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toEncryptedXml(WxMaConfig config) {
|
||||||
|
// JSON消息类型默认返回加密的JSON格式
|
||||||
|
return toEncryptedJson(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,10 +20,10 @@ public interface WxMaMessageHandler {
|
|||||||
* @param context 上下文
|
* @param context 上下文
|
||||||
* @param service 服务类
|
* @param service 服务类
|
||||||
* @param sessionManager session管理器
|
* @param sessionManager session管理器
|
||||||
* @return 输出消息
|
* @return 输出消息,可以是XML格式或JSON格式
|
||||||
* @throws WxErrorException 异常
|
* @throws WxErrorException 异常
|
||||||
*/
|
*/
|
||||||
WxMaXmlOutMessage handle(WxMaMessage message, Map<String, Object> context,
|
WxMaOutMessage handle(WxMaMessage message, Map<String, Object> context,
|
||||||
WxMaService service, WxSessionManager sessionManager) throws WxErrorException;
|
WxMaService service, WxSessionManager sessionManager) throws WxErrorException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ public class WxMaMessageRouter {
|
|||||||
/**
|
/**
|
||||||
* 处理微信消息.
|
* 处理微信消息.
|
||||||
*/
|
*/
|
||||||
public WxMaXmlOutMessage route(final WxMaMessage wxMessage, final Map<String, Object> context) {
|
public WxMaOutMessage route(final WxMaMessage wxMessage, final Map<String, Object> context) {
|
||||||
if (isMsgDuplicated(wxMessage)) {
|
if (isMsgDuplicated(wxMessage)) {
|
||||||
// 如果是重复消息,那么就不做处理
|
// 如果是重复消息,那么就不做处理
|
||||||
return null;
|
return null;
|
||||||
@@ -129,7 +129,7 @@ public class WxMaMessageRouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final List<Future<?>> futures = new ArrayList<>();
|
final List<Future<?>> futures = new ArrayList<>();
|
||||||
WxMaXmlOutMessage result = null;
|
WxMaOutMessage result = null;
|
||||||
for (final WxMaMessageRouterRule rule : matchRules) {
|
for (final WxMaMessageRouterRule rule : matchRules) {
|
||||||
// 返回最后一个非异步的rule的执行结果
|
// 返回最后一个非异步的rule的执行结果
|
||||||
if (rule.isAsync()) {
|
if (rule.isAsync()) {
|
||||||
@@ -168,7 +168,7 @@ public class WxMaMessageRouter {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public WxMaXmlOutMessage route(final WxMaMessage wxMessage) {
|
public WxMaOutMessage route(final WxMaMessage wxMessage) {
|
||||||
return this.route(wxMessage, new HashMap<>(2));
|
return this.route(wxMessage, new HashMap<>(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ public class WxMaMessageRouterRule {
|
|||||||
/**
|
/**
|
||||||
* 处理微信推送过来的消息.
|
* 处理微信推送过来的消息.
|
||||||
*/
|
*/
|
||||||
protected WxMaXmlOutMessage service(WxMaMessage wxMessage,
|
protected WxMaOutMessage service(WxMaMessage wxMessage,
|
||||||
Map<String, Object> context,
|
Map<String, Object> context,
|
||||||
WxMaService wxMaService,
|
WxMaService wxMaService,
|
||||||
WxSessionManager sessionManager,
|
WxSessionManager sessionManager,
|
||||||
@@ -210,7 +210,7 @@ public class WxMaMessageRouterRule {
|
|||||||
context = new HashMap<>(16);
|
context = new HashMap<>(16);
|
||||||
}
|
}
|
||||||
|
|
||||||
WxMaXmlOutMessage outMessage = null;
|
WxMaOutMessage outMessage = null;
|
||||||
try {
|
try {
|
||||||
// 如果拦截器不通过
|
// 如果拦截器不通过
|
||||||
for (WxMaMessageInterceptor interceptor : this.interceptors) {
|
for (WxMaMessageInterceptor interceptor : this.interceptors) {
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package cn.binarywang.wx.miniapp.message;
|
||||||
|
|
||||||
|
import cn.binarywang.wx.miniapp.config.WxMaConfig;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信小程序输出消息的通用接口,支持XML和JSON两种格式.
|
||||||
|
*
|
||||||
|
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||||
|
*/
|
||||||
|
public interface WxMaOutMessage extends Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换成XML格式.
|
||||||
|
*
|
||||||
|
* @return XML格式的消息
|
||||||
|
*/
|
||||||
|
String toXml();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换成JSON格式.
|
||||||
|
*
|
||||||
|
* @return JSON格式的消息
|
||||||
|
*/
|
||||||
|
String toJson();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换成加密的XML格式.
|
||||||
|
*
|
||||||
|
* @param config 配置对象
|
||||||
|
* @return 加密后的XML格式消息
|
||||||
|
*/
|
||||||
|
String toEncryptedXml(WxMaConfig config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换成加密的JSON格式.
|
||||||
|
*
|
||||||
|
* @param config 配置对象
|
||||||
|
* @return 加密后的JSON格式消息
|
||||||
|
*/
|
||||||
|
String toEncryptedJson(WxMaConfig config);
|
||||||
|
}
|
||||||
@@ -26,7 +26,7 @@ import java.io.Serializable;
|
|||||||
@Builder
|
@Builder
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public class WxMaXmlOutMessage implements Serializable {
|
public class WxMaXmlOutMessage implements WxMaOutMessage {
|
||||||
private static final long serialVersionUID = 4241135225946919153L;
|
private static final long serialVersionUID = 4241135225946919153L;
|
||||||
|
|
||||||
@XStreamAlias("ToUserName")
|
@XStreamAlias("ToUserName")
|
||||||
@@ -45,16 +45,36 @@ public class WxMaXmlOutMessage implements Serializable {
|
|||||||
protected String msgType;
|
protected String msgType;
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
public String toXml() {
|
public String toXml() {
|
||||||
return XStreamTransformer.toXml((Class<WxMaXmlOutMessage>) this.getClass(), this);
|
return XStreamTransformer.toXml((Class<WxMaXmlOutMessage>) this.getClass(), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换成JSON格式(对于XML消息类型,返回XML格式).
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toJson() {
|
||||||
|
// XML消息类型默认返回XML格式
|
||||||
|
return toXml();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 转换成加密的xml格式.
|
* 转换成加密的xml格式.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public String toEncryptedXml(WxMaConfig config) {
|
public String toEncryptedXml(WxMaConfig config) {
|
||||||
String plainXml = toXml();
|
String plainXml = toXml();
|
||||||
WxMaCryptUtils pc = new WxMaCryptUtils(config);
|
WxMaCryptUtils pc = new WxMaCryptUtils(config);
|
||||||
return pc.encrypt(plainXml);
|
return pc.encrypt(plainXml);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换成加密的JSON格式(对于XML消息类型,返回加密的XML格式).
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toEncryptedJson(WxMaConfig config) {
|
||||||
|
// XML消息类型默认返回加密的XML格式
|
||||||
|
return toEncryptedXml(config);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import cn.binarywang.wx.miniapp.config.WxMaConfig;
|
|||||||
import cn.binarywang.wx.miniapp.constant.WxMaConstants;
|
import cn.binarywang.wx.miniapp.constant.WxMaConstants;
|
||||||
import cn.binarywang.wx.miniapp.message.WxMaMessageHandler;
|
import cn.binarywang.wx.miniapp.message.WxMaMessageHandler;
|
||||||
import cn.binarywang.wx.miniapp.message.WxMaMessageRouter;
|
import cn.binarywang.wx.miniapp.message.WxMaMessageRouter;
|
||||||
|
import cn.binarywang.wx.miniapp.message.WxMaOutMessage;
|
||||||
import cn.binarywang.wx.miniapp.message.WxMaXmlOutMessage;
|
import cn.binarywang.wx.miniapp.message.WxMaXmlOutMessage;
|
||||||
import cn.binarywang.wx.miniapp.test.TestConfig;
|
import cn.binarywang.wx.miniapp.test.TestConfig;
|
||||||
import me.chanjar.weixin.common.api.WxConsts;
|
import me.chanjar.weixin.common.api.WxConsts;
|
||||||
@@ -32,8 +33,8 @@ public class WxMaDemoServer {
|
|||||||
|
|
||||||
private static final WxMaMessageHandler logHandler = new WxMaMessageHandler() {
|
private static final WxMaMessageHandler logHandler = new WxMaMessageHandler() {
|
||||||
@Override
|
@Override
|
||||||
public WxMaXmlOutMessage handle(WxMaMessage wxMessage, Map<String, Object> context,
|
public WxMaOutMessage handle(WxMaMessage wxMessage, Map<String, Object> context,
|
||||||
WxMaService service, WxSessionManager sessionManager) throws WxErrorException {
|
WxMaService service, WxSessionManager sessionManager) throws WxErrorException {
|
||||||
System.out.println("收到消息:" + wxMessage.toString());
|
System.out.println("收到消息:" + wxMessage.toString());
|
||||||
service.getMsgService().sendKefuMsg(WxMaKefuMessage.newTextBuilder().content("收到信息为:" + wxMessage.toJson())
|
service.getMsgService().sendKefuMsg(WxMaKefuMessage.newTextBuilder().content("收到信息为:" + wxMessage.toJson())
|
||||||
.toUser(wxMessage.getFromUser()).build());
|
.toUser(wxMessage.getFromUser()).build());
|
||||||
@@ -43,8 +44,8 @@ public class WxMaDemoServer {
|
|||||||
|
|
||||||
private static final WxMaMessageHandler textHandler = new WxMaMessageHandler() {
|
private static final WxMaMessageHandler textHandler = new WxMaMessageHandler() {
|
||||||
@Override
|
@Override
|
||||||
public WxMaXmlOutMessage handle(WxMaMessage wxMessage, Map<String, Object> context,
|
public WxMaOutMessage handle(WxMaMessage wxMessage, Map<String, Object> context,
|
||||||
WxMaService service, WxSessionManager sessionManager)
|
WxMaService service, WxSessionManager sessionManager)
|
||||||
throws WxErrorException {
|
throws WxErrorException {
|
||||||
service.getMsgService().sendKefuMsg(WxMaKefuMessage.newTextBuilder().content("回复文本消息")
|
service.getMsgService().sendKefuMsg(WxMaKefuMessage.newTextBuilder().content("回复文本消息")
|
||||||
.toUser(wxMessage.getFromUser()).build());
|
.toUser(wxMessage.getFromUser()).build());
|
||||||
@@ -55,8 +56,8 @@ public class WxMaDemoServer {
|
|||||||
|
|
||||||
private static final WxMaMessageHandler picHandler = new WxMaMessageHandler() {
|
private static final WxMaMessageHandler picHandler = new WxMaMessageHandler() {
|
||||||
@Override
|
@Override
|
||||||
public WxMaXmlOutMessage handle(WxMaMessage wxMessage, Map<String, Object> context,
|
public WxMaOutMessage handle(WxMaMessage wxMessage, Map<String, Object> context,
|
||||||
WxMaService service, WxSessionManager sessionManager) throws WxErrorException {
|
WxMaService service, WxSessionManager sessionManager) throws WxErrorException {
|
||||||
try {
|
try {
|
||||||
WxMediaUploadResult uploadResult = service.getMediaService()
|
WxMediaUploadResult uploadResult = service.getMediaService()
|
||||||
.uploadMedia(WxMaConstants.MediaType.IMAGE, "png",
|
.uploadMedia(WxMaConstants.MediaType.IMAGE, "png",
|
||||||
@@ -76,8 +77,8 @@ public class WxMaDemoServer {
|
|||||||
|
|
||||||
private static final WxMaMessageHandler qrcodeHandler = new WxMaMessageHandler() {
|
private static final WxMaMessageHandler qrcodeHandler = new WxMaMessageHandler() {
|
||||||
@Override
|
@Override
|
||||||
public WxMaXmlOutMessage handle(WxMaMessage wxMessage, Map<String, Object> context,
|
public WxMaOutMessage handle(WxMaMessage wxMessage, Map<String, Object> context,
|
||||||
WxMaService service, WxSessionManager sessionManager) throws WxErrorException {
|
WxMaService service, WxSessionManager sessionManager) throws WxErrorException {
|
||||||
try {
|
try {
|
||||||
final File file = service.getQrcodeService().createQrcode("123", 430);
|
final File file = service.getQrcodeService().createQrcode("123", 430);
|
||||||
WxMediaUploadResult uploadResult = service.getMediaService().uploadMedia(WxMaConstants.MediaType.IMAGE, file);
|
WxMediaUploadResult uploadResult = service.getMediaService().uploadMedia(WxMaConstants.MediaType.IMAGE, file);
|
||||||
@@ -96,7 +97,7 @@ public class WxMaDemoServer {
|
|||||||
|
|
||||||
private static final WxMaMessageHandler customerServiceMessageHandler = new WxMaMessageHandler() {
|
private static final WxMaMessageHandler customerServiceMessageHandler = new WxMaMessageHandler() {
|
||||||
@Override
|
@Override
|
||||||
public WxMaXmlOutMessage handle(WxMaMessage message, Map<String, Object> context, WxMaService service, WxSessionManager sessionManager) {
|
public WxMaOutMessage handle(WxMaMessage message, Map<String, Object> context, WxMaService service, WxSessionManager sessionManager) {
|
||||||
return new WxMaXmlOutMessage()
|
return new WxMaXmlOutMessage()
|
||||||
.setMsgType(WxConsts.XmlMsgType.TRANSFER_CUSTOMER_SERVICE)
|
.setMsgType(WxConsts.XmlMsgType.TRANSFER_CUSTOMER_SERVICE)
|
||||||
.setFromUserName(message.getToUser())
|
.setFromUserName(message.getToUser())
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import cn.binarywang.wx.miniapp.bean.WxMaMessage;
|
|||||||
import cn.binarywang.wx.miniapp.config.WxMaConfig;
|
import cn.binarywang.wx.miniapp.config.WxMaConfig;
|
||||||
import cn.binarywang.wx.miniapp.constant.WxMaConstants;
|
import cn.binarywang.wx.miniapp.constant.WxMaConstants;
|
||||||
import cn.binarywang.wx.miniapp.message.WxMaMessageRouter;
|
import cn.binarywang.wx.miniapp.message.WxMaMessageRouter;
|
||||||
import cn.binarywang.wx.miniapp.message.WxMaXmlOutMessage;
|
import cn.binarywang.wx.miniapp.message.WxMaOutMessage;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
@@ -62,9 +62,13 @@ public class WxMaPortalServlet extends HttpServlet {
|
|||||||
inMessage = WxMaMessage.fromXml(request.getInputStream());
|
inMessage = WxMaMessage.fromXml(request.getInputStream());
|
||||||
}
|
}
|
||||||
|
|
||||||
final WxMaXmlOutMessage outMessage = this.messageRouter.route(inMessage);
|
final WxMaOutMessage outMessage = this.messageRouter.route(inMessage);
|
||||||
if (outMessage != null) {
|
if (outMessage != null) {
|
||||||
response.getWriter().write(outMessage.toXml());
|
if (isJson) {
|
||||||
|
response.getWriter().write(outMessage.toJson());
|
||||||
|
} else {
|
||||||
|
response.getWriter().write(outMessage.toXml());
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,9 +86,13 @@ public class WxMaPortalServlet extends HttpServlet {
|
|||||||
inMessage = WxMaMessage.fromEncryptedXml(request.getInputStream(), this.config, timestamp, nonce, msgSignature);
|
inMessage = WxMaMessage.fromEncryptedXml(request.getInputStream(), this.config, timestamp, nonce, msgSignature);
|
||||||
}
|
}
|
||||||
|
|
||||||
final WxMaXmlOutMessage outMessage = this.messageRouter.route(inMessage);
|
final WxMaOutMessage outMessage = this.messageRouter.route(inMessage);
|
||||||
if (outMessage != null) {
|
if (outMessage != null) {
|
||||||
response.getWriter().write(outMessage.toEncryptedXml(this.config));
|
if (isJson) {
|
||||||
|
response.getWriter().write(outMessage.toEncryptedJson(this.config));
|
||||||
|
} else {
|
||||||
|
response.getWriter().write(outMessage.toEncryptedXml(this.config));
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
response.getWriter().write("success");
|
response.getWriter().write("success");
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package cn.binarywang.wx.miniapp.message;
|
||||||
|
|
||||||
|
import me.chanjar.weixin.common.api.WxConsts;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
public class WxMaJsonOutMessageTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToJson() {
|
||||||
|
WxMaJsonOutMessage message = WxMaJsonOutMessage.builder()
|
||||||
|
.fromUserName("test_from_user")
|
||||||
|
.toUserName("test_to_user")
|
||||||
|
.msgType(WxConsts.XmlMsgType.TRANSFER_CUSTOMER_SERVICE)
|
||||||
|
.createTime(System.currentTimeMillis() / 1000)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
String jsonResult = message.toJson();
|
||||||
|
assertThat(jsonResult).isNotEmpty();
|
||||||
|
assertThat(jsonResult).contains("test_from_user");
|
||||||
|
assertThat(jsonResult).contains("test_to_user");
|
||||||
|
assertThat(jsonResult).contains(WxConsts.XmlMsgType.TRANSFER_CUSTOMER_SERVICE);
|
||||||
|
|
||||||
|
System.out.println("JSON Output:");
|
||||||
|
System.out.println(jsonResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEmptyMessage() {
|
||||||
|
WxMaJsonOutMessage message = new WxMaJsonOutMessage();
|
||||||
|
String jsonResult = message.toJson();
|
||||||
|
assertThat(jsonResult).isNotEmpty();
|
||||||
|
System.out.println("Empty message JSON:");
|
||||||
|
System.out.println(jsonResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testImplementsInterface() {
|
||||||
|
WxMaJsonOutMessage message = WxMaJsonOutMessage.builder()
|
||||||
|
.fromUserName("test_from_user")
|
||||||
|
.toUserName("test_to_user")
|
||||||
|
.msgType(WxConsts.XmlMsgType.TEXT)
|
||||||
|
.createTime(System.currentTimeMillis() / 1000)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Test that it implements WxMaOutMessage interface
|
||||||
|
WxMaOutMessage outMessage = message;
|
||||||
|
assertThat(outMessage).isNotNull();
|
||||||
|
|
||||||
|
// Test both toJson and toXml methods (for JSON messages, both return JSON format)
|
||||||
|
assertThat(outMessage.toJson()).isNotEmpty();
|
||||||
|
assertThat(outMessage.toXml()).isNotEmpty();
|
||||||
|
assertThat(outMessage.toJson()).isEqualTo(outMessage.toXml());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user