mirror of
https://gitee.com/binary/weixin-java-tools.git
synced 2025-11-02 21:26:01 +08:00
🎨 优化代码
This commit is contained in:
@ -9,6 +9,7 @@ import me.chanjar.weixin.common.bean.WxJsapiSignature;
|
||||
import me.chanjar.weixin.common.enums.WxType;
|
||||
import me.chanjar.weixin.common.error.WxError;
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
import me.chanjar.weixin.common.error.WxRuntimeException;
|
||||
import me.chanjar.weixin.common.session.StandardSessionManager;
|
||||
import me.chanjar.weixin.common.session.WxSession;
|
||||
import me.chanjar.weixin.common.session.WxSessionManager;
|
||||
@ -227,7 +228,7 @@ public abstract class BaseWxCpServiceImpl<H, P> implements WxCpService, RequestH
|
||||
if (retryTimes + 1 > this.maxRetryTimes) {
|
||||
log.warn("重试达到最大次数【{}】", this.maxRetryTimes);
|
||||
//最后一次重试失败后,直接抛出异常,不再等待
|
||||
throw new RuntimeException("微信服务端异常,超出重试次数");
|
||||
throw new WxRuntimeException("微信服务端异常,超出重试次数");
|
||||
}
|
||||
|
||||
WxError error = e.getError();
|
||||
@ -249,7 +250,7 @@ public abstract class BaseWxCpServiceImpl<H, P> implements WxCpService, RequestH
|
||||
} while (retryTimes++ < this.maxRetryTimes);
|
||||
|
||||
log.warn("重试达到最大次数【{}】", this.maxRetryTimes);
|
||||
throw new RuntimeException("微信服务端异常,超出重试次数");
|
||||
throw new WxRuntimeException("微信服务端异常,超出重试次数");
|
||||
}
|
||||
|
||||
protected <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
|
||||
@ -285,7 +286,7 @@ public abstract class BaseWxCpServiceImpl<H, P> implements WxCpService, RequestH
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
log.error("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uriWithAccessToken, dataForLog, e.getMessage());
|
||||
throw new RuntimeException(e);
|
||||
throw new WxRuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.chanjar.weixin.common.error.WxCpErrorMsgEnum;
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
import me.chanjar.weixin.common.error.WxRuntimeException;
|
||||
import me.chanjar.weixin.cp.api.WxCpExternalContactService;
|
||||
import me.chanjar.weixin.cp.api.WxCpService;
|
||||
import me.chanjar.weixin.cp.bean.WxCpBaseResp;
|
||||
@ -30,7 +31,7 @@ public class WxCpExternalContactServiceImpl implements WxCpExternalContactServic
|
||||
public WxCpContactWayResult addContactWay(@NonNull WxCpContactWayInfo info) throws WxErrorException {
|
||||
|
||||
if (info.getContactWay().getUsers() != null && info.getContactWay().getUsers().size() > 100) {
|
||||
throw new RuntimeException("「联系我」使用人数默认限制不超过100人(包括部门展开后的人数)");
|
||||
throw new WxRuntimeException("「联系我」使用人数默认限制不超过100人(包括部门展开后的人数)");
|
||||
}
|
||||
|
||||
final String url = this.mainService.getWxCpConfigStorage().getApiUrl(ADD_CONTACT_WAY);
|
||||
@ -52,10 +53,10 @@ public class WxCpExternalContactServiceImpl implements WxCpExternalContactServic
|
||||
@Override
|
||||
public WxCpBaseResp updateContactWay(@NonNull WxCpContactWayInfo info) throws WxErrorException {
|
||||
if (StringUtils.isBlank(info.getContactWay().getConfigId())) {
|
||||
throw new RuntimeException("更新「联系我」方式需要指定configId");
|
||||
throw new WxRuntimeException("更新「联系我」方式需要指定configId");
|
||||
}
|
||||
if (info.getContactWay().getUsers() != null && info.getContactWay().getUsers().size() > 100) {
|
||||
throw new RuntimeException("「联系我」使用人数默认限制不超过100人(包括部门展开后的人数)");
|
||||
throw new WxRuntimeException("「联系我」使用人数默认限制不超过100人(包括部门展开后的人数)");
|
||||
}
|
||||
|
||||
final String url = this.mainService.getWxCpConfigStorage().getApiUrl(UPDATE_CONTACT_WAY);
|
||||
|
||||
@ -31,7 +31,7 @@ public class WxCpGroupRobotServiceImpl implements WxCpGroupRobotService {
|
||||
WxCpConfigStorage wxCpConfigStorage = this.cpService.getWxCpConfigStorage();
|
||||
final String webhookKey = wxCpConfigStorage.getWebhookKey();
|
||||
if (StringUtils.isEmpty(webhookKey)) {
|
||||
throw new WxErrorException(WxError.builder().errorCode(-1).errorMsg("请先设置WebhookKey").build());
|
||||
throw new WxErrorException("请先设置WebhookKey");
|
||||
}
|
||||
return wxCpConfigStorage.getApiUrl(WxCpApiPathConsts.WEBHOOK_SEND) + webhookKey;
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import com.google.gson.reflect.TypeToken;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
import me.chanjar.weixin.common.error.WxRuntimeException;
|
||||
import me.chanjar.weixin.common.util.json.GsonParser;
|
||||
import me.chanjar.weixin.cp.api.WxCpOaService;
|
||||
import me.chanjar.weixin.cp.api.WxCpService;
|
||||
@ -42,18 +43,18 @@ public class WxCpOaServiceImpl implements WxCpOaService {
|
||||
public List<WxCpCheckinData> getCheckinData(Integer openCheckinDataType, Date startTime, Date endTime,
|
||||
List<String> userIdList) throws WxErrorException {
|
||||
if (startTime == null || endTime == null) {
|
||||
throw new RuntimeException("starttime and endtime can't be null");
|
||||
throw new WxRuntimeException("starttime and endtime can't be null");
|
||||
}
|
||||
|
||||
if (userIdList == null || userIdList.size() > USER_IDS_LIMIT) {
|
||||
throw new RuntimeException("用户列表不能为空,不超过 " + USER_IDS_LIMIT + " 个,若用户超过 " + USER_IDS_LIMIT + " 个,请分批获取");
|
||||
throw new WxRuntimeException("用户列表不能为空,不超过 " + USER_IDS_LIMIT + " 个,若用户超过 " + USER_IDS_LIMIT + " 个,请分批获取");
|
||||
}
|
||||
|
||||
long endTimestamp = endTime.getTime() / 1000L;
|
||||
long startTimestamp = startTime.getTime() / 1000L;
|
||||
|
||||
if (endTimestamp - startTimestamp < 0 || endTimestamp - startTimestamp >= MONTH_SECONDS) {
|
||||
throw new RuntimeException("获取记录时间跨度不超过一个月");
|
||||
throw new WxRuntimeException("获取记录时间跨度不超过一个月");
|
||||
}
|
||||
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
@ -83,11 +84,11 @@ public class WxCpOaServiceImpl implements WxCpOaService {
|
||||
@Override
|
||||
public List<WxCpCheckinOption> getCheckinOption(Date datetime, List<String> userIdList) throws WxErrorException {
|
||||
if (datetime == null) {
|
||||
throw new RuntimeException("datetime can't be null");
|
||||
throw new WxRuntimeException("datetime can't be null");
|
||||
}
|
||||
|
||||
if (userIdList == null || userIdList.size() > USER_IDS_LIMIT) {
|
||||
throw new RuntimeException("用户列表不能为空,不超过 " + USER_IDS_LIMIT + " 个,若用户超过 " + USER_IDS_LIMIT + " 个,请分批获取");
|
||||
throw new WxRuntimeException("用户列表不能为空,不超过 " + USER_IDS_LIMIT + " 个,若用户超过 " + USER_IDS_LIMIT + " 个,请分批获取");
|
||||
}
|
||||
|
||||
JsonArray jsonArray = new JsonArray();
|
||||
@ -186,7 +187,7 @@ public class WxCpOaServiceImpl implements WxCpOaService {
|
||||
long starttimestamp = startTime.getTime() / 1000L;
|
||||
|
||||
if (endtimestamp - starttimestamp < 0 || endtimestamp - starttimestamp >= MONTH_SECONDS) {
|
||||
throw new RuntimeException("受限于网络传输,起止时间的最大跨度为30天,如超过30天,则以结束时间为基准向前取30天进行查询");
|
||||
throw new WxRuntimeException("受限于网络传输,起止时间的最大跨度为30天,如超过30天,则以结束时间为基准向前取30天进行查询");
|
||||
}
|
||||
|
||||
jsonObject.addProperty("start_time", starttimestamp);
|
||||
|
||||
@ -5,6 +5,7 @@ import me.chanjar.weixin.common.enums.WxType;
|
||||
import me.chanjar.weixin.common.bean.WxAccessToken;
|
||||
import me.chanjar.weixin.common.error.WxError;
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
import me.chanjar.weixin.common.error.WxRuntimeException;
|
||||
import me.chanjar.weixin.common.util.http.HttpType;
|
||||
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
|
||||
import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
|
||||
@ -72,7 +73,7 @@ public class WxCpServiceApacheHttpClientImpl extends BaseWxCpServiceImpl<Closeab
|
||||
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
|
||||
this.configStorage.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
throw new WxRuntimeException(e);
|
||||
}
|
||||
}
|
||||
return this.configStorage.getAccessToken();
|
||||
|
||||
@ -5,6 +5,7 @@ import me.chanjar.weixin.common.enums.WxType;
|
||||
import me.chanjar.weixin.common.bean.WxAccessToken;
|
||||
import me.chanjar.weixin.common.error.WxError;
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
import me.chanjar.weixin.common.error.WxRuntimeException;
|
||||
import me.chanjar.weixin.common.util.json.GsonParser;
|
||||
import me.chanjar.weixin.cp.constant.WxCpApiPathConsts;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
@ -68,7 +69,7 @@ public class WxCpServiceImpl extends WxCpServiceApacheHttpClientImpl {
|
||||
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
|
||||
getWxCpConfigStorage().updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
throw new WxRuntimeException(e);
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
|
||||
@ -6,6 +6,7 @@ import com.thoughtworks.xstream.annotations.XStreamImplicit;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.chanjar.weixin.common.api.WxConsts;
|
||||
import me.chanjar.weixin.common.error.WxRuntimeException;
|
||||
import me.chanjar.weixin.common.util.XmlUtils;
|
||||
import me.chanjar.weixin.common.util.xml.IntegerArrayConverter;
|
||||
import me.chanjar.weixin.common.util.xml.LongArrayConverter;
|
||||
@ -450,7 +451,7 @@ public class WxCpXmlMessage implements Serializable {
|
||||
try {
|
||||
return fromEncryptedXml(IOUtils.toString(is, StandardCharsets.UTF_8), wxCpConfigStorage, timestamp, nonce, msgSignature);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
throw new WxRuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ import me.chanjar.weixin.common.enums.WxType;
|
||||
import me.chanjar.weixin.common.error.WxCpErrorMsgEnum;
|
||||
import me.chanjar.weixin.common.error.WxError;
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
import me.chanjar.weixin.common.error.WxRuntimeException;
|
||||
import me.chanjar.weixin.common.session.StandardSessionManager;
|
||||
import me.chanjar.weixin.common.session.WxSessionManager;
|
||||
import me.chanjar.weixin.common.util.DataUtils;
|
||||
@ -183,7 +184,7 @@ public abstract class BaseWxCpTpServiceImpl<H, P> implements WxCpTpService, Requ
|
||||
if (retryTimes + 1 > this.maxRetryTimes) {
|
||||
log.warn("重试达到最大次数【{}】", this.maxRetryTimes);
|
||||
//最后一次重试失败后,直接抛出异常,不再等待
|
||||
throw new RuntimeException("微信服务端异常,超出重试次数");
|
||||
throw new WxRuntimeException("微信服务端异常,超出重试次数");
|
||||
}
|
||||
|
||||
WxError error = e.getError();
|
||||
@ -205,7 +206,7 @@ public abstract class BaseWxCpTpServiceImpl<H, P> implements WxCpTpService, Requ
|
||||
} while (retryTimes++ < this.maxRetryTimes);
|
||||
|
||||
log.warn("重试达到最大次数【{}】", this.maxRetryTimes);
|
||||
throw new RuntimeException("微信服务端异常,超出重试次数");
|
||||
throw new WxRuntimeException("微信服务端异常,超出重试次数");
|
||||
}
|
||||
|
||||
protected <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
|
||||
@ -244,7 +245,7 @@ public abstract class BaseWxCpTpServiceImpl<H, P> implements WxCpTpService, Requ
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
log.error("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uriWithAccessToken, dataForLog, e.getMessage());
|
||||
throw new RuntimeException(e);
|
||||
throw new WxRuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import com.google.gson.JsonObject;
|
||||
import me.chanjar.weixin.common.enums.WxType;
|
||||
import me.chanjar.weixin.common.error.WxError;
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
import me.chanjar.weixin.common.error.WxRuntimeException;
|
||||
import me.chanjar.weixin.common.util.http.HttpType;
|
||||
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
|
||||
import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
|
||||
@ -81,7 +82,7 @@ public class WxCpTpServiceApacheHttpClientImpl extends BaseWxCpTpServiceImpl<Clo
|
||||
Integer expiresIn = jsonObject.get("expires_in").getAsInt();
|
||||
this.configStorage.updateSuiteAccessToken(suiteAccussToken, expiresIn);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
throw new WxRuntimeException(e);
|
||||
}
|
||||
}
|
||||
return this.configStorage.getSuiteAccessToken();
|
||||
|
||||
@ -16,6 +16,8 @@ import me.chanjar.weixin.cp.bean.WxCpUser;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import static me.chanjar.weixin.cp.bean.WxCpUser.*;
|
||||
|
||||
/**
|
||||
* cp user gson adapter.
|
||||
*
|
||||
@ -104,7 +106,7 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri
|
||||
JsonArray attrJsonElements = o.get(EXTRA_ATTR).getAsJsonObject().get("attrs").getAsJsonArray();
|
||||
for (JsonElement attrJsonElement : attrJsonElements) {
|
||||
final Integer type = GsonHelper.getInteger(attrJsonElement.getAsJsonObject(), "type");
|
||||
final WxCpUser.Attr attr = new WxCpUser.Attr().setType(type)
|
||||
final Attr attr = new Attr().setType(type)
|
||||
.setName(GsonHelper.getString(attrJsonElement.getAsJsonObject(), "name"));
|
||||
user.getExtAttrs().add(attr);
|
||||
|
||||
@ -142,7 +144,7 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri
|
||||
switch (type) {
|
||||
case 0: {
|
||||
user.getExternalAttrs()
|
||||
.add(WxCpUser.ExternalAttribute.builder()
|
||||
.add(ExternalAttribute.builder()
|
||||
.type(type)
|
||||
.name(name)
|
||||
.value(GsonHelper.getString(element.getAsJsonObject().get("text").getAsJsonObject(), "value"))
|
||||
@ -153,7 +155,7 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri
|
||||
case 1: {
|
||||
final JsonObject web = element.getAsJsonObject().get("web").getAsJsonObject();
|
||||
user.getExternalAttrs()
|
||||
.add(WxCpUser.ExternalAttribute.builder()
|
||||
.add(ExternalAttribute.builder()
|
||||
.type(type)
|
||||
.name(name)
|
||||
.url(GsonHelper.getString(web, "url"))
|
||||
@ -165,7 +167,7 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri
|
||||
case 2: {
|
||||
final JsonObject miniprogram = element.getAsJsonObject().get("miniprogram").getAsJsonObject();
|
||||
user.getExternalAttrs()
|
||||
.add(WxCpUser.ExternalAttribute.builder()
|
||||
.add(ExternalAttribute.builder()
|
||||
.type(type)
|
||||
.name(name)
|
||||
.appid(GsonHelper.getString(miniprogram, "appid"))
|
||||
@ -278,7 +280,7 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri
|
||||
|
||||
if (!user.getExtAttrs().isEmpty()) {
|
||||
JsonArray attrsJsonArray = new JsonArray();
|
||||
for (WxCpUser.Attr attr : user.getExtAttrs()) {
|
||||
for (Attr attr : user.getExtAttrs()) {
|
||||
JsonObject attrJson = GsonHelper.buildJsonObject("type", attr.getType(),
|
||||
"name", attr.getName());
|
||||
attrsJsonArray.add(attrJson);
|
||||
@ -317,7 +319,7 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri
|
||||
|
||||
if (!user.getExternalAttrs().isEmpty()) {
|
||||
JsonArray attrsJsonArray = new JsonArray();
|
||||
for (WxCpUser.ExternalAttribute attr : user.getExternalAttrs()) {
|
||||
for (ExternalAttribute attr : user.getExternalAttrs()) {
|
||||
JsonObject attrJson = GsonHelper.buildJsonObject("type", attr.getType(),
|
||||
"name", attr.getName());
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ import com.thoughtworks.xstream.annotations.XStreamAlias;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.chanjar.weixin.common.error.WxRuntimeException;
|
||||
import me.chanjar.weixin.common.util.xml.XStreamInitializer;
|
||||
import me.chanjar.weixin.cp.api.impl.WxCpServiceImpl;
|
||||
import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
|
||||
@ -30,7 +31,7 @@ public class ApiTestModule implements Module {
|
||||
public void configure(Binder binder) {
|
||||
try (InputStream inputStream = ClassLoader.getSystemResourceAsStream(TEST_CONFIG_XML)) {
|
||||
if (inputStream == null) {
|
||||
throw new RuntimeException("测试配置文件【" + TEST_CONFIG_XML + "】未找到,请参照test-config-sample.xml文件生成");
|
||||
throw new WxRuntimeException("测试配置文件【" + TEST_CONFIG_XML + "】未找到,请参照test-config-sample.xml文件生成");
|
||||
}
|
||||
|
||||
config = fromXml(WxXmlCpInMemoryConfigStorage.class, inputStream);
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
package me.chanjar.weixin.cp.api;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.chanjar.weixin.common.error.WxError;
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
import me.chanjar.weixin.common.error.WxRuntimeException;
|
||||
import me.chanjar.weixin.common.util.http.RequestExecutor;
|
||||
import me.chanjar.weixin.cp.api.impl.WxCpServiceImpl;
|
||||
import org.testng.annotations.DataProvider;
|
||||
@ -25,7 +25,7 @@ public class WxCpBusyRetryTest {
|
||||
RequestExecutor<T, E> executor, String uri, E data)
|
||||
throws WxErrorException {
|
||||
log.info("Executed");
|
||||
throw new WxErrorException(WxError.builder().errorCode(-1).build());
|
||||
throw new WxErrorException("something");
|
||||
}
|
||||
};
|
||||
|
||||
@ -45,18 +45,15 @@ public class WxCpBusyRetryTest {
|
||||
public void testRetryInThreadPool(final WxCpService service) throws InterruptedException, ExecutionException {
|
||||
// 当线程池中的线程复用的时候,还是能保证相同的重试次数
|
||||
ExecutorService executorService = Executors.newFixedThreadPool(1);
|
||||
Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
System.out.println("=====================");
|
||||
System.out.println(Thread.currentThread().getName() + ": testRetry");
|
||||
service.execute(null, null, null);
|
||||
} catch (WxErrorException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (RuntimeException e) {
|
||||
// OK
|
||||
}
|
||||
Runnable runnable = () -> {
|
||||
try {
|
||||
System.out.println("=====================");
|
||||
System.out.println(Thread.currentThread().getName() + ": testRetry");
|
||||
service.execute(null, null, null);
|
||||
} catch (WxErrorException e) {
|
||||
throw new WxRuntimeException(e);
|
||||
} catch (RuntimeException e) {
|
||||
// OK
|
||||
}
|
||||
};
|
||||
Future<?> submit1 = executorService.submit(runnable);
|
||||
|
||||
@ -84,14 +84,11 @@ public class WxCpMessageRouterTest {
|
||||
}).end();
|
||||
|
||||
final WxCpXmlMessage m = new WxCpXmlMessage();
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
router.route(m);
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
Runnable r = () -> {
|
||||
router.route(m);
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
};
|
||||
for (int i = 0; i < 10; i++) {
|
||||
|
||||
Reference in New Issue
Block a user