mirror of
https://gitee.com/wujiawei1207537021/wu-lazy-cloud-network.git
synced 2026-02-04 15:05:54 +08:00
[fix] 修改bean 创建,添加网络榴莲监控
This commit is contained in:
@@ -2,6 +2,7 @@ package wu.framework.lazy.cloud.heartbeat.server.application.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import wu.framework.lazy.cloud.heartbeat.common.InternalNetworkPenetrationRealClient;
|
||||
import wu.framework.lazy.cloud.heartbeat.common.adapter.ChannelFlowAdapter;
|
||||
import wu.framework.lazy.cloud.heartbeat.server.application.InternalNetworkPenetrationMappingApplication;
|
||||
import wu.framework.lazy.cloud.heartbeat.server.application.assembler.InternalNetworkPenetrationMappingDTOAssembler;
|
||||
import wu.framework.lazy.cloud.heartbeat.server.application.command.internal.network.penetration.mapping.*;
|
||||
@@ -34,6 +35,9 @@ public class InternalNetworkPenetrationMappingApplicationImpl implements Interna
|
||||
@Resource
|
||||
InternalNetworkPenetrationMappingRepository internalNetworkPenetrationMappingRepository;
|
||||
|
||||
@Resource
|
||||
ChannelFlowAdapter channelFlowAdapter;
|
||||
|
||||
|
||||
/**
|
||||
* describe 新增内网穿透映射
|
||||
@@ -160,20 +164,28 @@ public class InternalNetworkPenetrationMappingApplicationImpl implements Interna
|
||||
String clientTargetIp = networkPenetrationMapping.getClientTargetIp();
|
||||
Integer clientTargetPort = networkPenetrationMapping.getClientTargetPort();
|
||||
|
||||
InternalNetworkPenetrationRealClient internalNetworkPenetrationRealClient = new InternalNetworkPenetrationRealClient();
|
||||
internalNetworkPenetrationRealClient.setClientTargetIp(clientTargetIp);
|
||||
internalNetworkPenetrationRealClient.setClientTargetPort(clientTargetPort);
|
||||
internalNetworkPenetrationRealClient.setClientId(clientId);
|
||||
internalNetworkPenetrationRealClient.setVisitorPort(visitorPort);
|
||||
// InternalNetworkPenetrationRealClient internalNetworkPenetrationRealClient = new InternalNetworkPenetrationRealClient();
|
||||
// internalNetworkPenetrationRealClient.setClientTargetIp(clientTargetIp);
|
||||
// internalNetworkPenetrationRealClient.setClientTargetPort(clientTargetPort);
|
||||
// internalNetworkPenetrationRealClient.setClientId(clientId);
|
||||
// internalNetworkPenetrationRealClient.setVisitorPort(visitorPort);
|
||||
|
||||
// 创建服务端代理连接
|
||||
VisitorFilter visitorFilter = new VisitorFilter(internalNetworkPenetrationRealClient);
|
||||
NettyVisitorSocket nettyVisitorSocket = new NettyVisitorSocket(visitorFilter);
|
||||
// VisitorFilter visitorFilter = new VisitorFilter(internalNetworkPenetrationRealClient);
|
||||
// NettyVisitorSocket nettyVisitorSocket = new NettyVisitorSocket(visitorFilter);
|
||||
NettyVisitorSocket nettyVisitorSocket = NettyVisitorSocket.NettyVisitorSocketBuilder
|
||||
.builder()
|
||||
.builderClientId(clientId)
|
||||
.builderClientTargetIp(clientTargetIp)
|
||||
.builderClientTargetPort(clientTargetPort)
|
||||
.builderVisitorPort(visitorPort)
|
||||
.builderChannelFlowAdapter(channelFlowAdapter)
|
||||
.build();
|
||||
|
||||
try {
|
||||
nettyVisitorSocket.startServer(visitorPort);
|
||||
} catch (Exception e) {
|
||||
log.error("客户端:{},网络端口:{},开放失败",clientId,visitorPort);
|
||||
log.error("客户端:{},网络端口:{},开放失败", clientId, visitorPort);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package wu.framework.lazy.cloud.heartbeat.server.netty.config;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Role;
|
||||
import wu.framework.lazy.cloud.heartbeat.common.adapter.ChannelFlowAdapter;
|
||||
import wu.framework.lazy.cloud.heartbeat.common.advanced.flow.HandleChannelFlowAdvanced;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @see ChannelFlowAdapter
|
||||
* @see HandleChannelFlowAdvanced
|
||||
*/
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
public class ServerFlowConfiguration {
|
||||
|
||||
|
||||
/**
|
||||
* 服务端流量适配器
|
||||
* @param handleChannelFlowAdvancedList 服务端流量适配者
|
||||
* @return 服务端流量适配器
|
||||
*/
|
||||
@ConditionalOnMissingBean(ChannelFlowAdapter.class)
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
public ChannelFlowAdapter channelFlowAdapter(List<HandleChannelFlowAdvanced> handleChannelFlowAdvancedList){
|
||||
return new ChannelFlowAdapter(handleChannelFlowAdvancedList);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,13 +3,16 @@ package wu.framework.lazy.cloud.heartbeat.server.netty.filter;
|
||||
import wu.framework.lazy.cloud.heartbeat.common.InternalNetworkPenetrationRealClient;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import wu.framework.lazy.cloud.heartbeat.common.adapter.ChannelFlowAdapter;
|
||||
import wu.framework.lazy.cloud.heartbeat.server.netty.handler.VisitorHandler;
|
||||
|
||||
public class VisitorFilter extends ChannelInitializer<SocketChannel> {
|
||||
private final InternalNetworkPenetrationRealClient internalNetworkPenetrationRealClient;
|
||||
private final ChannelFlowAdapter channelFlowAdapter;
|
||||
|
||||
public VisitorFilter(InternalNetworkPenetrationRealClient internalNetworkPenetrationRealClient) {
|
||||
public VisitorFilter(InternalNetworkPenetrationRealClient internalNetworkPenetrationRealClient, ChannelFlowAdapter channelFlowAdapter) {
|
||||
this.internalNetworkPenetrationRealClient = internalNetworkPenetrationRealClient;
|
||||
this.channelFlowAdapter = channelFlowAdapter;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -25,7 +28,7 @@ public class VisitorFilter extends ChannelInitializer<SocketChannel> {
|
||||
protected void initChannel(SocketChannel ch) throws Exception {
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
pipeline.addLast(new ChannelDuplexHandler());
|
||||
pipeline.addLast(new VisitorHandler(internalNetworkPenetrationRealClient));
|
||||
pipeline.addLast(new VisitorHandler(internalNetworkPenetrationRealClient,channelFlowAdapter));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package wu.framework.lazy.cloud.heartbeat.server.netty.flow;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import wu.framework.lazy.cloud.heartbeat.common.advanced.flow.ChannelFlow;
|
||||
import wu.framework.lazy.cloud.heartbeat.common.enums.ChannelFlowEnum;
|
||||
|
||||
@Builder
|
||||
@Data
|
||||
public class ServerChannelFlow implements ChannelFlow {
|
||||
private String clientId;
|
||||
private Integer port;
|
||||
private ChannelFlowEnum channelFlowEnum;
|
||||
private Integer flow;
|
||||
|
||||
/**
|
||||
* 通道客户端ID
|
||||
*
|
||||
* @return 通道客户端ID
|
||||
*/
|
||||
@Override
|
||||
public String clientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通道使用的端口(服务端访客端口、客户端真实端口)
|
||||
*
|
||||
* @return 端口
|
||||
*/
|
||||
@Override
|
||||
public Integer port() {
|
||||
return port;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通道流量类型
|
||||
*
|
||||
* @return ChannelFlowEnum
|
||||
* @see ChannelFlowEnum
|
||||
*/
|
||||
@Override
|
||||
public ChannelFlowEnum channelFlowEnum() {
|
||||
return channelFlowEnum;
|
||||
}
|
||||
|
||||
/**
|
||||
* 流量
|
||||
*
|
||||
* @return 流量
|
||||
*/
|
||||
@Override
|
||||
public Integer flow() {
|
||||
return flow;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ package wu.framework.lazy.cloud.heartbeat.server.netty.handler;
|
||||
|
||||
|
||||
import wu.framework.lazy.cloud.heartbeat.common.*;
|
||||
import wu.framework.lazy.cloud.heartbeat.common.adapter.ChannelFlowAdapter;
|
||||
import wu.framework.lazy.cloud.heartbeat.common.enums.ChannelFlowEnum;
|
||||
import wu.framework.lazy.cloud.heartbeat.common.utils.ChannelAttributeKeyUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
@@ -10,15 +12,18 @@ import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.util.internal.StringUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import wu.framework.lazy.cloud.heartbeat.server.netty.flow.ServerChannelFlow;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Slf4j
|
||||
public class VisitorHandler extends SimpleChannelInboundHandler<ByteBuf> {
|
||||
private final InternalNetworkPenetrationRealClient internalNetworkPenetrationRealClient;
|
||||
private final ChannelFlowAdapter channelFlowAdapter;// 流量适配器
|
||||
|
||||
public VisitorHandler(InternalNetworkPenetrationRealClient internalNetworkPenetrationRealClient) {
|
||||
public VisitorHandler(InternalNetworkPenetrationRealClient internalNetworkPenetrationRealClient, ChannelFlowAdapter channelFlowAdapter) {
|
||||
this.internalNetworkPenetrationRealClient = internalNetworkPenetrationRealClient;
|
||||
this.channelFlowAdapter = channelFlowAdapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -52,10 +57,10 @@ public class VisitorHandler extends SimpleChannelInboundHandler<ByteBuf> {
|
||||
// 客户端心跳通道
|
||||
ChannelContext.ClientChannel clientChannel = ChannelContext.get(clientId);
|
||||
if (clientChannel != null) {
|
||||
log.info("通过客户端:{},获取通道而后创建连接",clientId);
|
||||
log.info("通过客户端:{},获取通道而后创建连接", clientId);
|
||||
Channel channel = clientChannel.getChannel();
|
||||
channel.writeAndFlush(nettyProxyMsg);
|
||||
}else {
|
||||
} else {
|
||||
log.error("无法通过客户端ID获取客户端通道");
|
||||
}
|
||||
|
||||
@@ -70,11 +75,12 @@ public class VisitorHandler extends SimpleChannelInboundHandler<ByteBuf> {
|
||||
@Override
|
||||
public void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) {
|
||||
|
||||
Channel realChannel = ctx.channel();
|
||||
String clientId = internalNetworkPenetrationRealClient.getClientId();
|
||||
String clientTargetIp = internalNetworkPenetrationRealClient.getClientTargetIp();
|
||||
Integer clientTargetPort = internalNetworkPenetrationRealClient.getClientTargetPort();
|
||||
Integer visitorPort = internalNetworkPenetrationRealClient.getVisitorPort();
|
||||
String visitorId = ChannelAttributeKeyUtils.getVisitorId(ctx.channel());
|
||||
String visitorId = ChannelAttributeKeyUtils.getVisitorId(realChannel);
|
||||
if (StringUtil.isNullOrEmpty(clientId)) {
|
||||
return;
|
||||
}
|
||||
@@ -82,9 +88,11 @@ public class VisitorHandler extends SimpleChannelInboundHandler<ByteBuf> {
|
||||
buf.readBytes(bytes);
|
||||
// 获取客户端通道,而后进行数据下发
|
||||
log.debug("服务端访客端口成功接收数据:{}", new String(bytes));
|
||||
|
||||
// 使用访客的通信通道
|
||||
Channel visitorCommunicationChannel = NettyCommunicationIdContext.getVisitor(visitorId);
|
||||
|
||||
// 绑定数据流量
|
||||
ChannelAttributeKeyUtils.buildInFlow(visitorCommunicationChannel, bytes.length);
|
||||
NettyProxyMsg nettyProxyMsg = new NettyProxyMsg();
|
||||
nettyProxyMsg.setType(MessageType.DISTRIBUTE_CLIENT_TRANSFER);
|
||||
nettyProxyMsg.setClientId(clientId);
|
||||
@@ -94,6 +102,15 @@ public class VisitorHandler extends SimpleChannelInboundHandler<ByteBuf> {
|
||||
nettyProxyMsg.setVisitorId(visitorId);
|
||||
nettyProxyMsg.setData(bytes);
|
||||
visitorCommunicationChannel.writeAndFlush(nettyProxyMsg);
|
||||
// 处理访客流量
|
||||
ServerChannelFlow serverChannelFlow = ServerChannelFlow
|
||||
.builder()
|
||||
.channelFlowEnum(ChannelFlowEnum.IN_FLOW)
|
||||
.port(visitorPort)
|
||||
.clientId(clientId)
|
||||
.flow(bytes.length)
|
||||
.build();
|
||||
channelFlowAdapter.handler(realChannel, serverChannelFlow);
|
||||
log.debug("服务端访客端口成功发送数据了");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package wu.framework.lazy.cloud.heartbeat.server.netty.socket;
|
||||
|
||||
import wu.framework.lazy.cloud.heartbeat.common.InternalNetworkPenetrationRealClient;
|
||||
import wu.framework.lazy.cloud.heartbeat.common.NettyVisitorPortContext;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
@@ -9,6 +10,7 @@ import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import wu.framework.lazy.cloud.heartbeat.common.adapter.ChannelFlowAdapter;
|
||||
import wu.framework.lazy.cloud.heartbeat.server.netty.filter.VisitorFilter;
|
||||
|
||||
/**
|
||||
@@ -17,10 +19,11 @@ import wu.framework.lazy.cloud.heartbeat.server.netty.filter.VisitorFilter;
|
||||
@Slf4j
|
||||
public class NettyVisitorSocket {
|
||||
private final VisitorFilter visitorFilter;
|
||||
|
||||
private static final EventLoopGroup bossGroup = new NioEventLoopGroup();
|
||||
private static final EventLoopGroup workerGroup = new NioEventLoopGroup();
|
||||
|
||||
public NettyVisitorSocket(VisitorFilter visitorFilter) {
|
||||
public NettyVisitorSocket(VisitorFilter visitorFilter ) {
|
||||
this.visitorFilter = visitorFilter;
|
||||
}
|
||||
|
||||
@@ -39,7 +42,7 @@ public class NettyVisitorSocket {
|
||||
.childHandler(visitorFilter);
|
||||
ChannelFuture sync = b.bind(visitorPort).sync();
|
||||
sync.addListener((ChannelFutureListener) future -> {
|
||||
if(future.isSuccess()){
|
||||
if (future.isSuccess()) {
|
||||
Channel channel = future.channel();
|
||||
log.info("访客端口:{} 开启", visitorPort);
|
||||
NettyVisitorPortContext.pushVisitor(visitorPort, channel);
|
||||
@@ -52,4 +55,133 @@ public class NettyVisitorSocket {
|
||||
|
||||
}
|
||||
|
||||
public static final class NettyVisitorSocketBuilder {
|
||||
|
||||
/**
|
||||
* 客户端ID
|
||||
*/
|
||||
private String clientId;
|
||||
|
||||
/**
|
||||
* 客户端目标地址
|
||||
*/
|
||||
private String clientTargetIp;
|
||||
|
||||
/**
|
||||
* 客户端目标端口
|
||||
*/
|
||||
private Integer clientTargetPort;
|
||||
|
||||
|
||||
/**
|
||||
* 访问端口
|
||||
*/
|
||||
private Integer visitorPort;
|
||||
/**
|
||||
* 访客ID
|
||||
*/
|
||||
private String visitorId;
|
||||
|
||||
/**
|
||||
* 流量适配器
|
||||
*/
|
||||
private ChannelFlowAdapter channelFlowAdapter;
|
||||
|
||||
/**
|
||||
* 填充客户端
|
||||
*
|
||||
* @param clientId 客户端
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public NettyVisitorSocketBuilder builderClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定客户端目标IP
|
||||
*
|
||||
* @param clientTargetIp 客户端目标IP
|
||||
* @return 当前对象
|
||||
*/
|
||||
public NettyVisitorSocketBuilder builderClientTargetIp(String clientTargetIp) {
|
||||
this.clientTargetIp = clientTargetIp;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定客户端目标端口
|
||||
*
|
||||
* @param clientTargetPort 客户端目标端口
|
||||
* @return 当前对象
|
||||
*/
|
||||
public NettyVisitorSocketBuilder builderClientTargetPort(Integer clientTargetPort) {
|
||||
this.clientTargetPort = clientTargetPort;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定访客端口
|
||||
*
|
||||
* @param visitorPort 访客端口
|
||||
* @return 当前对象
|
||||
*/
|
||||
public NettyVisitorSocketBuilder builderVisitorPort(Integer visitorPort) {
|
||||
this.visitorPort = visitorPort;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定流量适配器
|
||||
* @param channelFlowAdapter 流量适配器
|
||||
* @return 当前对象
|
||||
*/
|
||||
public NettyVisitorSocketBuilder builderChannelFlowAdapter(ChannelFlowAdapter channelFlowAdapter) {
|
||||
this.channelFlowAdapter = channelFlowAdapter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定访客ID
|
||||
*
|
||||
* @param visitorId 访客ID
|
||||
* @return 当前对象
|
||||
*/
|
||||
public NettyVisitorSocketBuilder builderVisitorId(String visitorId) {
|
||||
this.visitorId = visitorId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public static NettyVisitorSocketBuilder builder() {
|
||||
return new NettyVisitorSocketBuilder();
|
||||
}
|
||||
|
||||
public NettyVisitorSocket build() {
|
||||
if (clientId == null) {
|
||||
throw new IllegalArgumentException("clientId must not null");
|
||||
}
|
||||
if (clientTargetIp == null) {
|
||||
throw new IllegalArgumentException("clientTargetIp must not null");
|
||||
}
|
||||
if (clientTargetPort == null) {
|
||||
throw new IllegalArgumentException("clientTargetPort must not null");
|
||||
}
|
||||
if (visitorPort == null) {
|
||||
throw new IllegalArgumentException("visitorPort must not null");
|
||||
}
|
||||
InternalNetworkPenetrationRealClient internalNetworkPenetrationRealClient = InternalNetworkPenetrationRealClient
|
||||
.builder()
|
||||
.clientId(clientId)
|
||||
.clientTargetIp(clientTargetIp)
|
||||
.clientTargetPort(clientTargetPort)
|
||||
.visitorPort(visitorPort)
|
||||
.visitorId(visitorId).build();
|
||||
|
||||
VisitorFilter visitorFilter = new VisitorFilter(internalNetworkPenetrationRealClient,channelFlowAdapter);
|
||||
return new NettyVisitorSocket(visitorFilter);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,4 +2,5 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
wu.framework.lazy.cloud.heartbeat.server.EnableHeartbeatServerAutoConfiguration,\
|
||||
wu.framework.lazy.cloud.heartbeat.server.netty.config.HeartbeatServerConfiguration,\
|
||||
wu.framework.lazy.cloud.heartbeat.server.netty.config.ServerAutoConfiguration
|
||||
wu.framework.lazy.cloud.heartbeat.server.netty.config.ServerAutoConfiguration,\
|
||||
wu.framework.lazy.cloud.heartbeat.server.netty.config.ServerFlowConfiguration
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
wu.framework.lazy.cloud.heartbeat.server.EnableHeartbeatServerAutoConfiguration
|
||||
wu.framework.lazy.cloud.heartbeat.server.netty.config.HeartbeatServerConfiguration
|
||||
wu.framework.lazy.cloud.heartbeat.server.netty.config.ServerAutoConfiguration
|
||||
wu.framework.lazy.cloud.heartbeat.server.netty.config.ServerFlowConfiguration
|
||||
|
||||
@@ -1 +1 @@
|
||||
<!doctype html><html lang="zh-cn"><head><meta charset="utf-8"/><meta http-equiv="X-UA-Compatible" content="IE=edge"/><link rel="icon" href="favicon.ico"/><title>lazy-ui</title><script defer="defer" src="js/chunk-elementPlusIcon.7f775a37.js"></script><script defer="defer" src="js/chunk-elementPlus.51ee9f03.js"></script><script defer="defer" src="js/chunk-mockjs.208b5e15.js"></script><script defer="defer" src="js/chunk-vendors.4fee82e9.js"></script><script defer="defer" src="js/app.ae95a4ac.js"></script><link href="css/chunk-elementPlus.e89c9935.css" rel="stylesheet"><link href="css/app.4ce91422.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but lazy-ui doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
|
||||
<!doctype html><html lang="zh-cn"><head><meta charset="utf-8"/><meta http-equiv="X-UA-Compatible" content="IE=edge"/><link rel="icon" href="favicon.ico"/><title>lazy-ui</title><script defer="defer" src="js/chunk-elementPlusIcon.7f775a37.js"></script><script defer="defer" src="js/chunk-elementPlus.51ee9f03.js"></script><script defer="defer" src="js/chunk-mockjs.208b5e15.js"></script><script defer="defer" src="js/chunk-vendors.4fee82e9.js"></script><script defer="defer" src="js/app.2f951f67.js"></script><link href="css/chunk-elementPlus.e89c9935.css" rel="stylesheet"><link href="css/app.4ce91422.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but lazy-ui doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
|
||||
Reference in New Issue
Block a user