初次提交代码
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
package com.haobang.config;
|
||||
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
import java.io.File;
|
||||
import com.typesafe.config.ConfigValueFactory;
|
||||
|
||||
import java.util.Map;
|
||||
public class AppConfig {
|
||||
|
||||
private static final Config config;
|
||||
|
||||
static {
|
||||
// 加载配置文件
|
||||
File configFile = new File("application.properties");
|
||||
Config loadedConfig;
|
||||
|
||||
if (configFile.exists()) {
|
||||
loadedConfig = ConfigFactory.parseFile(configFile);
|
||||
} else {
|
||||
loadedConfig = ConfigFactory.load("application.properties");
|
||||
}
|
||||
|
||||
// 解析环境变量占位符
|
||||
config = resolveEnvironmentVariables(loadedConfig);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 解析环境变量占位符
|
||||
*/
|
||||
private static Config resolveEnvironmentVariables(Config originalConfig) {
|
||||
Config resolvedConfig = originalConfig;
|
||||
|
||||
// 遍历所有配置项,查找需要解析的占位符
|
||||
for (Map.Entry<String, com.typesafe.config.ConfigValue> entry : originalConfig.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String value = originalConfig.getString(key);
|
||||
|
||||
if (value.contains("${")) {
|
||||
String resolvedValue = resolvePlaceholder(value);
|
||||
resolvedConfig = resolvedConfig.withValue(
|
||||
key,
|
||||
ConfigValueFactory.fromAnyRef(resolvedValue)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return resolvedConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析单个占位符值
|
||||
* 格式: ${ENV_VAR:default_value}
|
||||
*/
|
||||
private static String resolvePlaceholder(String value) {
|
||||
if (!value.startsWith("${") || !value.endsWith("}")) {
|
||||
return value;
|
||||
}
|
||||
|
||||
String placeholder = value.substring(2, value.length() - 1);
|
||||
String[] parts = placeholder.split(":");
|
||||
|
||||
if (parts.length == 0) {
|
||||
return value; // 无效格式,返回原值
|
||||
}
|
||||
|
||||
String envVarName = parts[0].trim();
|
||||
String defaultValue = parts.length > 1 ? parts[1].trim() : "";
|
||||
|
||||
// 1. 从系统环境变量获取
|
||||
String envValue = System.getenv(envVarName);
|
||||
if (envValue != null && !envValue.trim().isEmpty()) {
|
||||
return envValue.trim();
|
||||
}
|
||||
|
||||
// 2. 从系统属性获取 (java -D参数)
|
||||
String sysValue = System.getProperty(envVarName);
|
||||
if (sysValue != null && !sysValue.trim().isEmpty()) {
|
||||
return sysValue.trim();
|
||||
}
|
||||
|
||||
// 3. 返回默认值
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// Syslog 配置
|
||||
public static int getSyslogTcpPort() {
|
||||
return config.getInt("syslog.tcp.port");
|
||||
}
|
||||
|
||||
public static int getSyslogUdpPort() {
|
||||
return config.getInt("syslog.udp.port");
|
||||
}
|
||||
|
||||
public static int getSyslogMaxFrameLength() {
|
||||
return config.getInt("syslog.max.frame.length");
|
||||
}
|
||||
|
||||
public static int getSyslogBufferSize() {
|
||||
return config.getInt("syslog.buffer.size");
|
||||
}
|
||||
|
||||
// app service 配置
|
||||
public static String getAppServieDeviceId() {
|
||||
return config.getString("app.service.device_id");
|
||||
}
|
||||
public static String getAppServieDeviceName() { return config.getString("app.service.device_name");
|
||||
}
|
||||
public static String getAppServieVendor() {
|
||||
return config.getString("app.service.vendor");
|
||||
}
|
||||
public static String getAppServieProductName() {
|
||||
return config.getString("app.service.product_name");
|
||||
}
|
||||
public static String getAppServieDataType() {
|
||||
return config.getString("app.service.data_type");
|
||||
}
|
||||
// kafka 配置
|
||||
public static String getKafkaProducerBootstrap() {
|
||||
return config.getString("spring.kafka.producer.bootstrap-servers");
|
||||
}
|
||||
public static String getKafkaProducerTopic() {
|
||||
return config.getString("spring.kafka.producer.topic");
|
||||
}
|
||||
|
||||
public static int getDeviceCollectId() {
|
||||
return config.getInt("app.service.device_collect_id");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.haobang.config;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
|
||||
|
||||
@ConfigurationProperties(prefix = "syslog")
|
||||
public class SyslogConfig {
|
||||
|
||||
// 读取字符串属性
|
||||
@Value("${syslog.tcp.port}")
|
||||
private int tcpPort;
|
||||
// 读取字符串属性
|
||||
@Value("${syslog.udp.port}")
|
||||
private int udpPort;
|
||||
// 读取字符串属性
|
||||
@Value("${syslog.max.frame.length}")
|
||||
private int maxFrameLength;
|
||||
// 读取字符串属性
|
||||
@Value("${syslog.buffer.size}")
|
||||
private int bufferSize;
|
||||
|
||||
// Getter 和 Setter 方法
|
||||
public int getTcpPort() {
|
||||
return tcpPort;
|
||||
}
|
||||
|
||||
public void setTcpPort(int tcpPort) {
|
||||
this.tcpPort = tcpPort;
|
||||
}
|
||||
|
||||
public int getUdpPort() {
|
||||
return udpPort;
|
||||
}
|
||||
|
||||
public void setUdpPort(int udpPort) {
|
||||
this.udpPort = udpPort;
|
||||
}
|
||||
|
||||
public int getMaxFrameLength() {
|
||||
return maxFrameLength;
|
||||
}
|
||||
|
||||
public void setMaxFrameLength(int maxFrameLength) {
|
||||
this.maxFrameLength = maxFrameLength;
|
||||
}
|
||||
|
||||
public int getBufferSize() {
|
||||
return bufferSize;
|
||||
}
|
||||
|
||||
public void setBufferSize(int bufferSize) {
|
||||
this.bufferSize = bufferSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("SyslogConfig[tcpPort=%d, udpPort=%d, maxFrameLength=%d, bufferSize=%d]",
|
||||
tcpPort, udpPort, maxFrameLength, bufferSize);
|
||||
}
|
||||
}
|
||||
+40
File diff suppressed because one or more lines are too long
+58
@@ -0,0 +1,58 @@
|
||||
package com.haobang.syslog;
|
||||
|
||||
import org.graylog2.syslog4j.SyslogConstants;
|
||||
import org.graylog2.syslog4j.server.*;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
|
||||
public class MySyslogServer {
|
||||
private static final String HOST = "127.0.0.1";
|
||||
private static final int PORT = 514;
|
||||
//514
|
||||
|
||||
private void receiveSyslogMessage() throws InterruptedException {
|
||||
SyslogServerIF server = SyslogServer.getInstance(SyslogConstants.UDP);
|
||||
SyslogServerConfigIF config = server.getConfig();
|
||||
config.setHost(HOST);
|
||||
config.setPort(PORT);
|
||||
config.addEventHandler(new SyslogServerSessionEventHandlerIF() {
|
||||
@Override
|
||||
public Object sessionOpened(SyslogServerIF syslogServerIF, SocketAddress socketAddress) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void event(Object o, SyslogServerIF syslogServerIF, SocketAddress socketAddress,
|
||||
SyslogServerEventIF syslogServerEventIF) {
|
||||
System.out.println("receive from:" + socketAddress + "\tmessage" + syslogServerEventIF.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exception(Object o, SyslogServerIF syslogServerIF, SocketAddress socketAddress, Exception e) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sessionClosed(Object o, SyslogServerIF syslogServerIF, SocketAddress socketAddress, boolean b) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(SyslogServerIF syslogServerIF) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy(SyslogServerIF syslogServerIF) {
|
||||
|
||||
}
|
||||
});
|
||||
SyslogServer.getThreadedInstance(SyslogConstants.UDP);
|
||||
Thread.sleep(100000);
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
new MySyslogServer().receiveSyslogMessage();
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package com.haobang.syslog;
|
||||
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
|
||||
public class TestSyslogServer {
|
||||
public static void main(String[] args) throws Throwable {
|
||||
// TODO Auto-generated method stub
|
||||
DatagramSocket datagramSocket = new DatagramSocket(514);
|
||||
while (true) {
|
||||
DatagramPacket packet = new DatagramPacket(new byte[1000], 1000);
|
||||
try {
|
||||
//不会造成死循环,因为receive是阻塞式方法,若发送方不发送数据,则阻塞在该处
|
||||
datagramSocket.receive(packet);
|
||||
String msg = new String(packet.getData(), 0, packet.getLength());
|
||||
//handelMessage(msg);
|
||||
|
||||
System.out.println(packet.getAddress() + "/" + packet.getPort() + ":" + msg);
|
||||
packet.setData("I am server!!!".getBytes());
|
||||
datagramSocket.send(packet);
|
||||
Thread.sleep(1000);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void handelMessage(String messageFragment){
|
||||
StringBuilder currentMessage = new StringBuilder();
|
||||
boolean isSplitMessage = messageFragment.endsWith("..."); // 根据拆分标志判断消息是否被拆分
|
||||
|
||||
if (isSplitMessage) {
|
||||
// 拆分的消息片段,将其合并到当前消息
|
||||
currentMessage.append(messageFragment.substring(0, messageFragment.length() - 3));
|
||||
} else {
|
||||
// 完整的消息,处理并重置当前消息
|
||||
currentMessage.append(messageFragment);
|
||||
handleCompleteMessage(currentMessage.toString());
|
||||
currentMessage.setLength(0);
|
||||
}
|
||||
}
|
||||
public static void handleCompleteMessage(String completeMessage){
|
||||
System.out.println("接收到完整消息:" + completeMessage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package com.haobang.util;
|
||||
|
||||
|
||||
import com.common.entity.DeviceDevice;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.haobang.config.AppConfig;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
public class DeviceInfoUtil {
|
||||
|
||||
/**
|
||||
* 补充设备配置信息 key value值
|
||||
* @param strSyslog
|
||||
* @return
|
||||
*/
|
||||
public static String getFullLogString(String strSyslog)
|
||||
{
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
// 创建设备配置信息MAP
|
||||
// 获取当前时间(精度到毫秒)
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
// 定义格式化器,包含毫秒
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
|
||||
// 转换为字符串
|
||||
String timeString = now.format(formatter);
|
||||
|
||||
Map<String, String> map = new LinkedHashMap<>();
|
||||
map.put("receive_time",timeString);
|
||||
map.put("device_id", AppConfig.getAppServieDeviceId());
|
||||
map.put("device_name", AppConfig.getAppServieDeviceName());
|
||||
map.put("vendor", AppConfig.getAppServieVendor());
|
||||
map.put("data_type", AppConfig.getAppServieDataType());
|
||||
map.put("device_collect_id", Long.toString(AppConfig.getDeviceCollectId()));
|
||||
|
||||
String formattedString = formatDeviceInfo(map);
|
||||
//return formattedString + "" + strSyslog.substring(34);
|
||||
|
||||
//测试环境截取34位之后字符串
|
||||
return formattedString + strSyslog;
|
||||
}
|
||||
/**
|
||||
* 补充设备配置信息 key value值
|
||||
* @param strSyslog
|
||||
* @return
|
||||
*/
|
||||
public static String getFullLogString(DeviceDevice deviceDevice,String strSyslog)
|
||||
{
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
// 创建设备配置信息MAP
|
||||
// 获取当前时间(精度到毫秒)
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
// 定义格式化器,包含毫秒
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
|
||||
// 转换为字符串
|
||||
String timeString = now.format(formatter);
|
||||
Map<String, String> map = new LinkedHashMap<>();
|
||||
map.put("receive_time",timeString);
|
||||
map.put("device_id",deviceDevice.getId().toString());
|
||||
map.put("device_name", deviceDevice.getName());
|
||||
map.put("vendor", deviceDevice.getVendor());
|
||||
map.put("data_type", AppConfig.getAppServieDataType());
|
||||
map.put("device_collect_id", Long.toString(AppConfig.getDeviceCollectId()));
|
||||
String formattedString = formatDeviceInfo(map);
|
||||
//return formattedString + "" + strSyslog.substring(34);
|
||||
//测试环境截取34位之后字符串
|
||||
return formattedString + strSyslog;
|
||||
}
|
||||
|
||||
public static String getFullLogString(DeviceDevice deviceDevice,String strSyslog,String receive_time)
|
||||
{
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
// 创建设备配置信息MAP
|
||||
// 获取当前时间(精度到毫秒)
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
// 定义格式化器,包含毫秒
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
|
||||
// 转换为字符串
|
||||
String timeString = now.format(formatter);
|
||||
Map<String, String> map = new LinkedHashMap<>();
|
||||
map.put("receive_time",receive_time);
|
||||
map.put("device_id",deviceDevice.getId().toString());
|
||||
map.put("device_name", deviceDevice.getName());
|
||||
map.put("vendor", deviceDevice.getVendor());
|
||||
map.put("data_type", AppConfig.getAppServieDataType());
|
||||
map.put("device_collect_id", Long.toString(AppConfig.getDeviceCollectId()));
|
||||
String formattedString = formatDeviceInfo(map);
|
||||
//return formattedString + "" + strSyslog.substring(34);
|
||||
//测试环境截取34位之后字符串
|
||||
return formattedString + strSyslog;
|
||||
}
|
||||
|
||||
|
||||
public static String formatDeviceInfo(Map<String, String> deviceData) {
|
||||
if (deviceData == null || deviceData.isEmpty()) {
|
||||
return "[]"; // 返回空括号表示无数据
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("["); // 开始字符
|
||||
|
||||
// 使用 LinkedHashMap 保持插入顺序
|
||||
boolean firstEntry = true;
|
||||
for (Map.Entry<String, String> entry : deviceData.entrySet()) {
|
||||
if (!firstEntry) {
|
||||
sb.append(" "); // 键值对之间用空格分隔
|
||||
}
|
||||
sb.append(entry.getKey())
|
||||
.append("=")
|
||||
.append(entry.getValue());
|
||||
firstEntry = false;
|
||||
}
|
||||
sb.append("]"); // 结束字符
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.haobang.util;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List ;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
public class SafeCacheUtil {
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
/**
|
||||
* 安全的缓存获取方法
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getSafe(String key, Class<T> clazz, Supplier<T> loader) {
|
||||
try {
|
||||
Object cached = redisTemplate.opsForValue().get(key);
|
||||
|
||||
if (cached == null) {
|
||||
// 缓存不存在,从数据源加载
|
||||
T value = loader.get();
|
||||
if (value != null) {
|
||||
redisTemplate.opsForValue().set(key, value, 30, TimeUnit.MINUTES);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// 类型匹配,直接返回
|
||||
if (clazz.isInstance(cached)) {
|
||||
return (T) cached;
|
||||
}
|
||||
|
||||
// 类型不匹配,尝试转换
|
||||
if (cached instanceof LinkedHashMap) {
|
||||
return objectMapper.convertValue(cached, clazz);
|
||||
}
|
||||
|
||||
// 无法转换,重新加载
|
||||
T value = loader.get();
|
||||
if (value != null) {
|
||||
redisTemplate.opsForValue().set(key, value, 30, TimeUnit.MINUTES);
|
||||
}
|
||||
return value;
|
||||
|
||||
} catch (Exception e) {
|
||||
// 缓存出错,降级到数据源
|
||||
return loader.get();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全的列表缓存获取
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> List<T> getSafeList(String key, Class<T> elementClass, Supplier<List<T>> loader) {
|
||||
try {
|
||||
Object cached = redisTemplate.opsForValue().get(key);
|
||||
|
||||
if (cached == null) {
|
||||
List<T> value = loader.get();
|
||||
if (value != null && !value.isEmpty()) {
|
||||
redisTemplate.opsForValue().set(key, value, 30, TimeUnit.MINUTES);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// 已经是正确的类型
|
||||
if (cached instanceof List &&
|
||||
!((List<?>) cached).isEmpty() &&
|
||||
elementClass.isInstance(((List<?>) cached).get(0))) {
|
||||
return (List<T>) cached;
|
||||
}
|
||||
|
||||
// 需要转换
|
||||
if (cached instanceof List) {
|
||||
List<LinkedHashMap> rawList = (List<LinkedHashMap>) cached;
|
||||
List<T> convertedList = rawList.stream()
|
||||
.map(item -> objectMapper.convertValue(item, elementClass))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 更新缓存为正确格式
|
||||
redisTemplate.opsForValue().set(key, convertedList, 30, TimeUnit.MINUTES);
|
||||
return convertedList;
|
||||
}
|
||||
|
||||
// 无法处理,重新加载
|
||||
List<T> value = loader.get();
|
||||
if (value != null && !value.isEmpty()) {
|
||||
redisTemplate.opsForValue().set(key, value, 30, TimeUnit.MINUTES);
|
||||
}
|
||||
return value;
|
||||
|
||||
} catch (Exception e) {
|
||||
return loader.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.haobang.util;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class SpringContextUtil implements ApplicationContextAware {
|
||||
|
||||
private static ApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
SpringContextUtil.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
public static ApplicationContext getApplicationContext() {
|
||||
return applicationContext;
|
||||
}
|
||||
|
||||
public static <T> T getBean(Class<T> clazz) {
|
||||
return applicationContext.getBean(clazz);
|
||||
}
|
||||
|
||||
public static <T> T getBean(String name, Class<T> clazz) {
|
||||
return applicationContext.getBean(name, clazz);
|
||||
}
|
||||
|
||||
// 专门获取 Mapper 的方法
|
||||
public static <T> T getMapper(Class<T> mapperClass) {
|
||||
return applicationContext.getBean(mapperClass);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.haobang.util;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
public class TimeUtils {
|
||||
|
||||
private static final DateTimeFormatter DEFAULT_FORMATTER =
|
||||
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
|
||||
|
||||
/**
|
||||
* 获取当前时间的字符串表示(包含毫秒)
|
||||
*/
|
||||
public static String getCurrentTimeString() {
|
||||
return LocalDateTime.now().format(DEFAULT_FORMATTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定格式的当前时间字符串
|
||||
*/
|
||||
public static String getCurrentTimeString(String pattern) {
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
|
||||
return LocalDateTime.now().format(formatter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取时间戳格式的字符串(无分隔符)
|
||||
*/
|
||||
public static String getTimestampString() {
|
||||
return getCurrentTimeString("yyyyMMddHHmmssSSS");
|
||||
}
|
||||
/**
|
||||
* 获取当前时间戳(毫秒)
|
||||
*/
|
||||
public static long getCurrentTimestamp() {
|
||||
return System.currentTimeMillis();
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
public static void main(String[] args) {
|
||||
System.out.println("默认格式: " + getCurrentTimeString());
|
||||
System.out.println("时间戳格式: " + getTimestampString());
|
||||
System.out.println("自定义格式: " + getCurrentTimeString("yyyy/MM/dd HH:mm:ss.SSS"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user