正在显示
29 个修改的文件
包含
995 行增加
和
0 行删除
| @@ -76,6 +76,10 @@ | @@ -76,6 +76,10 @@ | ||
| 76 | <groupId>com.zhonglai</groupId> | 76 | <groupId>com.zhonglai</groupId> |
| 77 | <artifactId>weixin-api</artifactId> | 77 | <artifactId>weixin-api</artifactId> |
| 78 | </dependency> | 78 | </dependency> |
| 79 | + <dependency> | ||
| 80 | + <groupId>org.apache.xmlgraphics</groupId> | ||
| 81 | + <artifactId>batik-transcoder</artifactId> | ||
| 82 | + </dependency> | ||
| 79 | </dependencies> | 83 | </dependencies> |
| 80 | 84 | ||
| 81 | <build> | 85 | <build> |
| @@ -9,11 +9,21 @@ import com.ruoyi.common.utils.GsonConstructor; | @@ -9,11 +9,21 @@ import com.ruoyi.common.utils.GsonConstructor; | ||
| 9 | import com.zhonglai.luhui.api.controller.test.dto.ClueData; | 9 | import com.zhonglai.luhui.api.controller.test.dto.ClueData; |
| 10 | import io.swagger.annotations.Api; | 10 | import io.swagger.annotations.Api; |
| 11 | import io.swagger.annotations.ApiOperation; | 11 | import io.swagger.annotations.ApiOperation; |
| 12 | +import org.apache.batik.transcoder.Transcoder; | ||
| 13 | +import org.apache.batik.transcoder.TranscoderException; | ||
| 14 | +import org.apache.batik.transcoder.TranscoderInput; | ||
| 15 | +import org.apache.batik.transcoder.TranscoderOutput; | ||
| 16 | +import org.apache.batik.transcoder.image.JPEGTranscoder; | ||
| 17 | +import org.apache.batik.transcoder.image.PNGTranscoder; | ||
| 12 | import org.springframework.util.StreamUtils; | 18 | import org.springframework.util.StreamUtils; |
| 13 | import org.springframework.web.bind.annotation.*; | 19 | import org.springframework.web.bind.annotation.*; |
| 14 | 20 | ||
| 21 | +import javax.servlet.ServletOutputStream; | ||
| 15 | import javax.servlet.http.HttpServletRequest; | 22 | import javax.servlet.http.HttpServletRequest; |
| 23 | +import javax.servlet.http.HttpServletResponse; | ||
| 16 | import java.io.IOException; | 24 | import java.io.IOException; |
| 25 | +import java.io.OutputStreamWriter; | ||
| 26 | +import java.io.StringReader; | ||
| 17 | import java.util.Enumeration; | 27 | import java.util.Enumeration; |
| 18 | import java.util.HashMap; | 28 | import java.util.HashMap; |
| 19 | import java.util.Map; | 29 | import java.util.Map; |
| @@ -90,6 +100,65 @@ public class TestController { | @@ -90,6 +100,65 @@ public class TestController { | ||
| 90 | "}"; | 100 | "}"; |
| 91 | } | 101 | } |
| 92 | 102 | ||
| 103 | + @ApiOperation("重写Highcharts导出") | ||
| 104 | + @RequestMapping(value = "getFeishuTable") | ||
| 105 | + public void getFeishuTable(HttpServletRequest request, HttpServletResponse response) throws IOException { | ||
| 106 | + String type = request.getParameter("type"); | ||
| 107 | + String svg = request.getParameter("svg"); | ||
| 108 | + String filename = request.getParameter("filename"); | ||
| 109 | + filename = filename == null ? "chart" : filename; | ||
| 110 | + | ||
| 111 | + response.setCharacterEncoding("utf-8"); | ||
| 112 | + response.addHeader("Content-Disposition", "attachment; filename=" + filename + "." + getFileExtension(type)); | ||
| 113 | + response.addHeader("Content-Type", type); | ||
| 114 | + | ||
| 115 | + ServletOutputStream out = response.getOutputStream(); | ||
| 116 | + | ||
| 117 | + try { | ||
| 118 | + if (type != null && svg != null) { | ||
| 119 | + svg = svg.replaceAll(":rect", "rect"); | ||
| 120 | + | ||
| 121 | + Transcoder t = null; | ||
| 122 | + if ("image/png".equals(type)) { | ||
| 123 | + t = new PNGTranscoder(); | ||
| 124 | + } else if ("image/jpeg".equals(type)) { | ||
| 125 | + t = new JPEGTranscoder(); | ||
| 126 | + } | ||
| 127 | + | ||
| 128 | + if (t != null) { | ||
| 129 | + TranscoderInput input = new TranscoderInput(new StringReader(svg)); | ||
| 130 | + TranscoderOutput output = new TranscoderOutput(out); | ||
| 131 | + t.transcode(input, output); | ||
| 132 | + } else if ("image/svg+xml".equals(type)) { | ||
| 133 | + OutputStreamWriter writer = new OutputStreamWriter(out, "UTF-8"); | ||
| 134 | + writer.write(svg); | ||
| 135 | + writer.flush(); | ||
| 136 | + writer.close(); | ||
| 137 | + } else { | ||
| 138 | + out.write("Invalid type: ".getBytes()); | ||
| 139 | + } | ||
| 140 | + } else { | ||
| 141 | + response.setContentType("text/html"); | ||
| 142 | + out.write("Usage:\n\tParameter [svg]: The DOM Element to be converted.\n\tParameter [type]: The destination MIME type for the element to be transcoded.".getBytes()); | ||
| 143 | + } | ||
| 144 | + } catch (TranscoderException e) { | ||
| 145 | + response.reset(); | ||
| 146 | + response.setContentType("text/plain"); | ||
| 147 | + out.write("Problem transcoding stream. See server logs for details.".getBytes()); | ||
| 148 | + e.printStackTrace(); | ||
| 149 | + } finally { | ||
| 150 | + out.flush(); | ||
| 151 | + out.close(); | ||
| 152 | + } | ||
| 153 | + } | ||
| 154 | + | ||
| 155 | + private String getFileExtension(String mimeType) { | ||
| 156 | + if ("image/png".equals(mimeType)) return "png"; | ||
| 157 | + if ("image/jpeg".equals(mimeType)) return "jpg"; | ||
| 158 | + if ("image/svg+xml".equals(mimeType)) return "svg"; | ||
| 159 | + return "bin"; | ||
| 160 | + } | ||
| 161 | + | ||
| 93 | public static void main(String[] args) { | 162 | public static void main(String[] args) { |
| 94 | JSONArray sort = new JSONArray(); | 163 | JSONArray sort = new JSONArray(); |
| 95 | JSONObject field_name = new JSONObject(); | 164 | JSONObject field_name = new JSONObject(); |
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | +<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
| 3 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| 4 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
| 5 | + <modelVersion>4.0.0</modelVersion> | ||
| 6 | + <parent> | ||
| 7 | + <groupId>com.zhonglai.luhui</groupId> | ||
| 8 | + <artifactId>lh-neutrino-proxy</artifactId> | ||
| 9 | + <version>1.0-SNAPSHOT</version> | ||
| 10 | + </parent> | ||
| 11 | + | ||
| 12 | + <artifactId>lh-neutrino-proxy-client</artifactId> | ||
| 13 | + | ||
| 14 | + <properties> | ||
| 15 | + <maven.compiler.source>8</maven.compiler.source> | ||
| 16 | + <maven.compiler.target>8</maven.compiler.target> | ||
| 17 | + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
| 18 | + </properties> | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + <dependencies> | ||
| 22 | + <dependency> | ||
| 23 | + <groupId>com.zhonglai.luhui</groupId> | ||
| 24 | + <artifactId>lh-neutrino-proxy-common</artifactId> | ||
| 25 | + </dependency> | ||
| 26 | + </dependencies> | ||
| 27 | + | ||
| 28 | +</project> |
| 1 | +package com.zhonglai.luhui.neutrino.proxy.client; | ||
| 2 | + | ||
| 3 | +import com.fasterxml.jackson.databind.ObjectMapper; | ||
| 4 | +import com.zhonglai.luhui.neutrino.proxy.common.RegisterMessage; | ||
| 5 | + | ||
| 6 | +import java.io.BufferedReader; | ||
| 7 | +import java.io.InputStreamReader; | ||
| 8 | +import java.io.PrintWriter; | ||
| 9 | +import java.net.Socket; | ||
| 10 | +import java.util.HashMap; | ||
| 11 | +import java.util.Map; | ||
| 12 | + | ||
| 13 | +public class ClientMain { | ||
| 14 | + public static void main(String[] args) throws Exception { | ||
| 15 | + Socket socket = new Socket("127.0.0.1", 9000); | ||
| 16 | + PrintWriter writer = new PrintWriter(socket.getOutputStream(), true); | ||
| 17 | + BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); | ||
| 18 | + | ||
| 19 | + ObjectMapper mapper = new ObjectMapper(); | ||
| 20 | + RegisterMessage register = new RegisterMessage("clientA", "abc123"); | ||
| 21 | + | ||
| 22 | + writer.println(mapper.writeValueAsString(register)); | ||
| 23 | + | ||
| 24 | + String response = reader.readLine(); | ||
| 25 | + System.out.println("服务端响应:" + response); | ||
| 26 | + new Thread(new HeartbeatTask(socket)).start(); | ||
| 27 | + } | ||
| 28 | +} |
| 1 | +package com.zhonglai.luhui.neutrino.proxy.client; | ||
| 2 | + | ||
| 3 | +import com.fasterxml.jackson.databind.ObjectMapper; | ||
| 4 | + | ||
| 5 | +import java.io.PrintWriter; | ||
| 6 | +import java.net.Socket; | ||
| 7 | +import java.util.HashMap; | ||
| 8 | +import java.util.Map; | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * 心跳线程(每30秒发送) | ||
| 12 | + */ | ||
| 13 | +public class HeartbeatTask implements Runnable { | ||
| 14 | + private final Socket socket; | ||
| 15 | + | ||
| 16 | + public HeartbeatTask(Socket socket) { | ||
| 17 | + this.socket = socket; | ||
| 18 | + } | ||
| 19 | + | ||
| 20 | + @Override | ||
| 21 | + public void run() { | ||
| 22 | + try { | ||
| 23 | + PrintWriter writer = new PrintWriter(socket.getOutputStream(), true); | ||
| 24 | + ObjectMapper mapper = new ObjectMapper(); | ||
| 25 | + while (!socket.isClosed()) { | ||
| 26 | + Map<String, String> heartbeat = new HashMap<>(); | ||
| 27 | + heartbeat.put("type", "heartbeat"); | ||
| 28 | + heartbeat.put("clientId", "clientA"); | ||
| 29 | + writer.println(mapper.writeValueAsString(heartbeat)); | ||
| 30 | + Thread.sleep(30000); // 30秒心跳 | ||
| 31 | + } | ||
| 32 | + } catch (Exception e) { | ||
| 33 | + System.out.println("心跳发送失败:" + e.getMessage()); | ||
| 34 | + } | ||
| 35 | + } | ||
| 36 | +} |
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | +<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
| 3 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| 4 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
| 5 | + <modelVersion>4.0.0</modelVersion> | ||
| 6 | + <parent> | ||
| 7 | + <groupId>com.zhonglai.luhui</groupId> | ||
| 8 | + <artifactId>lh-neutrino-proxy</artifactId> | ||
| 9 | + <version>1.0-SNAPSHOT</version> | ||
| 10 | + </parent> | ||
| 11 | + | ||
| 12 | + <artifactId>lh-neutrino-proxy-common</artifactId> | ||
| 13 | + | ||
| 14 | + <properties> | ||
| 15 | + <maven.compiler.source>8</maven.compiler.source> | ||
| 16 | + <maven.compiler.target>8</maven.compiler.target> | ||
| 17 | + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
| 18 | + </properties> | ||
| 19 | + | ||
| 20 | + <dependencies> | ||
| 21 | + <dependency> | ||
| 22 | + <groupId>com.fasterxml.jackson.core</groupId> | ||
| 23 | + <artifactId>jackson-databind</artifactId> | ||
| 24 | + </dependency> | ||
| 25 | + </dependencies> | ||
| 26 | + | ||
| 27 | +</project> |
| 1 | +package com.zhonglai.luhui.neutrino.proxy.common; | ||
| 2 | + | ||
| 3 | +/** | ||
| 4 | + * 注册协议类 | ||
| 5 | + */ | ||
| 6 | +public class RegisterMessage { | ||
| 7 | + public String type = "register"; | ||
| 8 | + public String clientId; | ||
| 9 | + public String token; | ||
| 10 | + | ||
| 11 | + public RegisterMessage() {} | ||
| 12 | + | ||
| 13 | + public RegisterMessage(String clientId, String token) { | ||
| 14 | + this.clientId = clientId; | ||
| 15 | + this.token = token; | ||
| 16 | + } | ||
| 17 | +} |
| 1 | +package com.zhonglai.luhui.neutrino.proxy.common; | ||
| 2 | + | ||
| 3 | +/** | ||
| 4 | + * 隧道请求协议 | ||
| 5 | + */ | ||
| 6 | +public class TunnelMessage { | ||
| 7 | + public String type = "tunnel"; | ||
| 8 | + public String tunnelId; // UUID | ||
| 9 | + public String clientId; | ||
| 10 | + public int remotePort; // 公网端口 | ||
| 11 | + public String targetHost; | ||
| 12 | + public int targetPort; | ||
| 13 | + | ||
| 14 | +} |
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | +<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
| 3 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| 4 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
| 5 | + <modelVersion>4.0.0</modelVersion> | ||
| 6 | + <parent> | ||
| 7 | + <groupId>com.zhonglai.luhui</groupId> | ||
| 8 | + <artifactId>lh-neutrino-proxy</artifactId> | ||
| 9 | + <version>1.0-SNAPSHOT</version> | ||
| 10 | + </parent> | ||
| 11 | + | ||
| 12 | + <artifactId>lh-neutrino-proxy-server</artifactId> | ||
| 13 | + | ||
| 14 | + <properties> | ||
| 15 | + <maven.compiler.source>8</maven.compiler.source> | ||
| 16 | + <maven.compiler.target>8</maven.compiler.target> | ||
| 17 | + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
| 18 | + </properties> | ||
| 19 | + | ||
| 20 | + <dependencies> | ||
| 21 | + <dependency> | ||
| 22 | + <groupId>io.netty</groupId> | ||
| 23 | + <artifactId>netty-all</artifactId> | ||
| 24 | + </dependency> | ||
| 25 | + <dependency> | ||
| 26 | + <groupId>com.zhonglai.luhui</groupId> | ||
| 27 | + <artifactId>lh-neutrino-proxy-common</artifactId> | ||
| 28 | + </dependency> | ||
| 29 | + <dependency> | ||
| 30 | + <groupId>commons-io</groupId> | ||
| 31 | + <artifactId>commons-io</artifactId> | ||
| 32 | + </dependency> | ||
| 33 | + <dependency> | ||
| 34 | + <groupId>com.alibaba</groupId> | ||
| 35 | + <artifactId>fastjson</artifactId> | ||
| 36 | + </dependency> | ||
| 37 | + </dependencies> | ||
| 38 | + | ||
| 39 | +</project> |
| 1 | +package com.zhonglai.luhui.neutrino.proxy.server; | ||
| 2 | + | ||
| 3 | +import io.netty.channel.Channel; | ||
| 4 | + | ||
| 5 | +import java.util.Map; | ||
| 6 | +import java.util.concurrent.ConcurrentHashMap; | ||
| 7 | + | ||
| 8 | +/** | ||
| 9 | + * 服务端维护客户端连接 | ||
| 10 | + */ | ||
| 11 | +public class ClientSessionManager { | ||
| 12 | + private static final Map<String, Channel> clientMap = new ConcurrentHashMap<>(); | ||
| 13 | + | ||
| 14 | + public static void register(String clientId, Channel channel) { | ||
| 15 | + clientMap.put(clientId, channel); | ||
| 16 | + System.out.println("注册客户端:" + clientId); | ||
| 17 | + } | ||
| 18 | + | ||
| 19 | + public static void remove(Channel channel) { | ||
| 20 | + clientMap.entrySet().removeIf(entry -> entry.getValue().equals(channel)); | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + public static void heartbeat(String clientId) { | ||
| 24 | + // 更新最后心跳时间(后续可扩展) | ||
| 25 | + System.out.println("收到心跳:" + clientId); | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + public static boolean isClientConnected(String clientId) { | ||
| 29 | + return clientMap.containsKey(clientId); | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + public static Channel getChannel(String clientId) { | ||
| 33 | + return clientMap.get(clientId); | ||
| 34 | + } | ||
| 35 | +} |
| 1 | +package com.zhonglai.luhui.neutrino.proxy.server; | ||
| 2 | + | ||
| 3 | +import com.zhonglai.luhui.neutrino.proxy.server.httpservice.SimpleHttpServer; | ||
| 4 | +import com.zhonglai.luhui.neutrino.proxy.server.proxy.ProxyServer; | ||
| 5 | + | ||
| 6 | +public class ControlServer { | ||
| 7 | + public static void main(String[] args) throws Exception { | ||
| 8 | + //启动代理服务 | ||
| 9 | + ProxyServer.start(9000); | ||
| 10 | + | ||
| 11 | + //启动接口服务 | ||
| 12 | + SimpleHttpServer.startHttpServer(8080); | ||
| 13 | + } | ||
| 14 | +} |
| 1 | +package com.zhonglai.luhui.neutrino.proxy.server.function; | ||
| 2 | + | ||
| 3 | +import com.fasterxml.jackson.databind.ObjectMapper; | ||
| 4 | +import com.fasterxml.jackson.databind.node.ObjectNode; | ||
| 5 | +import com.zhonglai.luhui.neutrino.proxy.common.TunnelMessage; | ||
| 6 | +import com.zhonglai.luhui.neutrino.proxy.server.ClientSessionManager; | ||
| 7 | +import io.netty.channel.Channel; | ||
| 8 | + | ||
| 9 | +import java.io.*; | ||
| 10 | +import java.net.*; | ||
| 11 | +import java.util.Map; | ||
| 12 | +import java.util.UUID; | ||
| 13 | +import java.util.concurrent.*; | ||
| 14 | + | ||
| 15 | +public class PortForwardManager { | ||
| 16 | + public static final Map<Integer, ServerSocket> activeServers = new ConcurrentHashMap<>(); | ||
| 17 | + public static final Map<String, Integer> clientBandwidthMap = new ConcurrentHashMap<>(); | ||
| 18 | + | ||
| 19 | + public void startPortForward(int remotePort, String clientId, int targetPort) throws IOException { | ||
| 20 | + if (activeServers.containsKey(remotePort)) return; | ||
| 21 | + | ||
| 22 | + ServerSocket serverSocket = new ServerSocket(remotePort); | ||
| 23 | + activeServers.put(remotePort, serverSocket); | ||
| 24 | + | ||
| 25 | + new Thread(() -> { | ||
| 26 | + System.out.println("端口转发监听启动: " + remotePort + " => " + clientId + ":" + targetPort); | ||
| 27 | + while (true) { | ||
| 28 | + try { | ||
| 29 | + Socket externalSocket = serverSocket.accept(); // 有外部连接过来了 | ||
| 30 | + String tunnelId = UUID.randomUUID().toString(); | ||
| 31 | + | ||
| 32 | + // 1. 注册外部连接 socket(暂时只 createBridge) | ||
| 33 | + TunnelBridgePool.createBridge(tunnelId, externalSocket); | ||
| 34 | + | ||
| 35 | + // 2. 通过 control 通道通知客户端建立内网连接 | ||
| 36 | + Channel channel = ClientSessionManager.getChannel(clientId); | ||
| 37 | + if (channel != null) { | ||
| 38 | + ObjectMapper mapper = new ObjectMapper(); | ||
| 39 | + ObjectNode msg = mapper.createObjectNode(); | ||
| 40 | + msg.put("type", "create_tunnel"); | ||
| 41 | + msg.put("tunnelId", tunnelId); | ||
| 42 | + msg.put("targetPort", targetPort); | ||
| 43 | + channel.writeAndFlush(msg.toString() + "\n"); | ||
| 44 | + } else { | ||
| 45 | + System.err.println("客户端未连接,无法建立隧道: " + clientId); | ||
| 46 | + externalSocket.close(); | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + // ✅ 注意:此时 **不要 bindAndStart**,等客户端连接进来后才执行。 | ||
| 50 | + | ||
| 51 | + } catch (IOException e) { | ||
| 52 | + e.printStackTrace(); | ||
| 53 | + } | ||
| 54 | + } | ||
| 55 | + }).start(); | ||
| 56 | + } | ||
| 57 | +} |
| 1 | +package com.zhonglai.luhui.neutrino.proxy.server.function; | ||
| 2 | + | ||
| 3 | +import com.zhonglai.luhui.neutrino.proxy.server.function.ratelimit.RateLimitedInputStream; | ||
| 4 | +import com.zhonglai.luhui.neutrino.proxy.server.function.ratelimit.RateLimitedOutputStream; | ||
| 5 | +import com.zhonglai.luhui.neutrino.proxy.server.function.ratelimit.TokenBucket; | ||
| 6 | + | ||
| 7 | +import java.io.IOException; | ||
| 8 | +import java.io.InputStream; | ||
| 9 | +import java.io.OutputStream; | ||
| 10 | +import java.net.Socket; | ||
| 11 | +import java.util.Map; | ||
| 12 | +import java.util.concurrent.ConcurrentHashMap; | ||
| 13 | + | ||
| 14 | +/** | ||
| 15 | + * 服务端建立转发通道并绑定 tunnelId | ||
| 16 | + */ | ||
| 17 | +public class TunnelBridgePool { | ||
| 18 | + private static final Map<String, Socket> bridgeMap = new ConcurrentHashMap<>(); | ||
| 19 | + private static final Map<String, Integer> bandwidthMap = new ConcurrentHashMap<>(); | ||
| 20 | + | ||
| 21 | + public static void createBridge(String tunnelId, Socket externalSocket) { | ||
| 22 | + bridgeMap.put(tunnelId, externalSocket); | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + public static void bindAndStart(String tunnelId, Socket tunnelSocket, int rate) { | ||
| 26 | + bandwidthMap.put(tunnelId, rate); | ||
| 27 | + bindAndStart(tunnelId, tunnelSocket); | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + public static void bindAndStart(String tunnelId, Socket tunnelSocket) { | ||
| 31 | + Socket externalSocket = bridgeMap.remove(tunnelId); | ||
| 32 | + int rate = bandwidthMap.remove(tunnelId); | ||
| 33 | + if (externalSocket != null) { | ||
| 34 | + try { | ||
| 35 | + TokenBucket upload = new TokenBucket(rate, rate); | ||
| 36 | + TokenBucket download = new TokenBucket(rate, rate); | ||
| 37 | + | ||
| 38 | + InputStream fromClient = new RateLimitedInputStream(tunnelSocket.getInputStream(), upload); | ||
| 39 | + OutputStream toExternal = new RateLimitedOutputStream(externalSocket.getOutputStream(), upload); | ||
| 40 | + | ||
| 41 | + InputStream fromExternal = new RateLimitedInputStream(externalSocket.getInputStream(), download); | ||
| 42 | + OutputStream toClient = new RateLimitedOutputStream(tunnelSocket.getOutputStream(), download); | ||
| 43 | + | ||
| 44 | + new Thread(() -> copy(fromClient, toExternal)).start(); | ||
| 45 | + new Thread(() -> copy(fromExternal, toClient)).start(); | ||
| 46 | + } catch (IOException e) { | ||
| 47 | + e.printStackTrace(); | ||
| 48 | + } | ||
| 49 | + } | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + private static void copy(InputStream in, OutputStream out) { | ||
| 53 | + try { | ||
| 54 | + byte[] buffer = new byte[8192]; | ||
| 55 | + int len; | ||
| 56 | + while ((len = in.read(buffer)) != -1) { | ||
| 57 | + out.write(buffer, 0, len); | ||
| 58 | + out.flush(); | ||
| 59 | + } | ||
| 60 | + } catch (IOException ignored) { | ||
| 61 | + } | ||
| 62 | + } | ||
| 63 | +} |
| 1 | +package com.zhonglai.luhui.neutrino.proxy.server.function; | ||
| 2 | + | ||
| 3 | +import java.io.BufferedReader; | ||
| 4 | +import java.io.InputStreamReader; | ||
| 5 | +import java.net.ServerSocket; | ||
| 6 | +import java.net.Socket; | ||
| 7 | + | ||
| 8 | +/** | ||
| 9 | + * 服务端独立启动数据转发端口(如 9100) | ||
| 10 | + */ | ||
| 11 | +public class TunnelSocketListener { | ||
| 12 | + public void start() throws Exception { | ||
| 13 | + ServerSocket serverSocket = new ServerSocket(9100); | ||
| 14 | + System.out.println("数据转发端口监听中: 9100"); | ||
| 15 | + while (true) { | ||
| 16 | + Socket socket = serverSocket.accept(); | ||
| 17 | + BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); | ||
| 18 | + String tunnelId = reader.readLine(); | ||
| 19 | + TunnelBridgePool.bindAndStart(tunnelId, socket); | ||
| 20 | + } | ||
| 21 | + } | ||
| 22 | +} |
| 1 | +package com.zhonglai.luhui.neutrino.proxy.server.function.ratelimit; | ||
| 2 | + | ||
| 3 | +import java.io.FilterInputStream; | ||
| 4 | +import java.io.IOException; | ||
| 5 | +import java.io.InputStream; | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * 限速 InputStream 和 OutputStream 封装类 | ||
| 9 | + */ | ||
| 10 | +public class RateLimitedInputStream extends FilterInputStream { | ||
| 11 | + private final TokenBucket bucket; | ||
| 12 | + | ||
| 13 | + public RateLimitedInputStream(InputStream in, TokenBucket bucket) { | ||
| 14 | + super(in); | ||
| 15 | + this.bucket = bucket; | ||
| 16 | + } | ||
| 17 | + | ||
| 18 | + @Override | ||
| 19 | + public int read(byte[] b, int off, int len) throws IOException { | ||
| 20 | + try { | ||
| 21 | + bucket.consume(len); | ||
| 22 | + } catch (InterruptedException e) { | ||
| 23 | + Thread.currentThread().interrupt(); | ||
| 24 | + return -1; | ||
| 25 | + } | ||
| 26 | + return super.read(b, off, len); | ||
| 27 | + } | ||
| 28 | +} |
| 1 | +package com.zhonglai.luhui.neutrino.proxy.server.function.ratelimit; | ||
| 2 | + | ||
| 3 | +import java.io.FilterOutputStream; | ||
| 4 | +import java.io.IOException; | ||
| 5 | +import java.io.OutputStream; | ||
| 6 | + | ||
| 7 | +public class RateLimitedOutputStream extends FilterOutputStream { | ||
| 8 | + private final TokenBucket bucket; | ||
| 9 | + | ||
| 10 | + public RateLimitedOutputStream(OutputStream out, TokenBucket bucket) { | ||
| 11 | + super(out); | ||
| 12 | + this.bucket = bucket; | ||
| 13 | + } | ||
| 14 | + | ||
| 15 | + @Override | ||
| 16 | + public void write(byte[] b, int off, int len) throws IOException { | ||
| 17 | + try { | ||
| 18 | + bucket.consume(len); | ||
| 19 | + } catch (InterruptedException e) { | ||
| 20 | + Thread.currentThread().interrupt(); | ||
| 21 | + return; | ||
| 22 | + } | ||
| 23 | + super.write(b, off, len); | ||
| 24 | + } | ||
| 25 | +} |
| 1 | +package com.zhonglai.luhui.neutrino.proxy.server.function.ratelimit; | ||
| 2 | + | ||
| 3 | +/** | ||
| 4 | + * 核心限速器 | ||
| 5 | + */ | ||
| 6 | +public class TokenBucket { | ||
| 7 | + private final long capacity; | ||
| 8 | + private final long refillRate; | ||
| 9 | + private long tokens; | ||
| 10 | + private long lastRefillTime; | ||
| 11 | + | ||
| 12 | + public TokenBucket(long capacity, long refillRate) { | ||
| 13 | + this.capacity = capacity; | ||
| 14 | + this.refillRate = refillRate; | ||
| 15 | + this.tokens = capacity; | ||
| 16 | + this.lastRefillTime = System.nanoTime(); | ||
| 17 | + } | ||
| 18 | + | ||
| 19 | + public synchronized void consume(long bytes) throws InterruptedException { | ||
| 20 | + refill(); | ||
| 21 | + while (tokens < bytes) { | ||
| 22 | + Thread.sleep(5); | ||
| 23 | + refill(); | ||
| 24 | + } | ||
| 25 | + tokens -= bytes; | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + private void refill() { | ||
| 29 | + long now = System.nanoTime(); | ||
| 30 | + long elapsed = now - lastRefillTime; | ||
| 31 | + long refillTokens = (elapsed * refillRate) / 1_000_000_000L; | ||
| 32 | + if (refillTokens > 0) { | ||
| 33 | + tokens = Math.min(capacity, tokens + refillTokens); | ||
| 34 | + lastRefillTime = now; | ||
| 35 | + } | ||
| 36 | + } | ||
| 37 | +} |
| 1 | +package com.zhonglai.luhui.neutrino.proxy.server.httpservice; | ||
| 2 | + | ||
| 3 | +import com.alibaba.fastjson.JSON; | ||
| 4 | +import com.alibaba.fastjson.JSONObject; | ||
| 5 | +import com.sun.net.httpserver.HttpExchange; | ||
| 6 | +import com.sun.net.httpserver.HttpHandler; | ||
| 7 | +import com.zhonglai.luhui.neutrino.proxy.server.function.PortForwardManager; | ||
| 8 | +import com.zhonglai.luhui.neutrino.proxy.server.httpservice.dto.ResponseMessage; | ||
| 9 | + | ||
| 10 | +import java.io.*; | ||
| 11 | +import java.net.URLDecoder; | ||
| 12 | +import java.nio.charset.StandardCharsets; | ||
| 13 | +import java.util.HashMap; | ||
| 14 | +import java.util.Map; | ||
| 15 | + | ||
| 16 | +/** | ||
| 17 | + * 端口映射操作 | ||
| 18 | + */ | ||
| 19 | +public class PortForwardHandler implements HttpHandler { | ||
| 20 | + @Override | ||
| 21 | + public void handle(HttpExchange httpExchange) throws IOException { | ||
| 22 | + try { | ||
| 23 | + Map<String,String> params = getParamsMap(httpExchange); | ||
| 24 | + String operate = params.get("operate"); | ||
| 25 | + switch (operate) | ||
| 26 | + { | ||
| 27 | + case "map": //端口映射 | ||
| 28 | + map(httpExchange,params); | ||
| 29 | + return; | ||
| 30 | + case "updateBandwidth": //限制带宽 | ||
| 31 | + updateBandwidth(httpExchange,params); | ||
| 32 | + return; | ||
| 33 | + default: | ||
| 34 | + response(httpExchange,new ResponseMessage("不支持的操作", 0)); | ||
| 35 | + } | ||
| 36 | + }catch (Exception e) | ||
| 37 | + { | ||
| 38 | + response(httpExchange,new ResponseMessage("接口请求失败", 0)); | ||
| 39 | + } | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + private void map(HttpExchange httpExchange, Map<String, String> params)throws IOException | ||
| 43 | + { | ||
| 44 | + | ||
| 45 | + int serverPort = Integer.parseInt(params.get("serverPort")); | ||
| 46 | + String clientId =params.get("clientId"); | ||
| 47 | + int targetPort = Integer.parseInt(params.get("targetPort")); | ||
| 48 | + int bandwidth = null != params.get("bandwidth") ? Integer.parseInt(params.get("bandwidth")) : 100 * 1024; | ||
| 49 | + PortForwardManager.clientBandwidthMap.put(clientId, bandwidth); | ||
| 50 | + | ||
| 51 | + new PortForwardManager().startPortForward(serverPort, clientId, targetPort); | ||
| 52 | + response(httpExchange,new ResponseMessage("映射成功", 1)); | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + private void updateBandwidth(HttpExchange httpExchange, Map<String, String> params)throws IOException | ||
| 56 | + { | ||
| 57 | + String clientId = params.get("clientId"); | ||
| 58 | + int bandwidth = Integer.parseInt(params.get("bandwidth")); | ||
| 59 | + PortForwardManager.clientBandwidthMap.put(clientId, bandwidth); | ||
| 60 | + response(httpExchange,new ResponseMessage("带宽更新成功", 1)); | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + private void response(HttpExchange httpExchange, ResponseMessage responseMessage) throws IOException | ||
| 64 | + { | ||
| 65 | + String response = JSON.toJSONString(responseMessage); | ||
| 66 | + OutputStream os = httpExchange.getResponseBody(); | ||
| 67 | + httpExchange.getResponseHeaders().set("Content-Type", "application/json; charset=UTF-8"); | ||
| 68 | + byte[] bytes = response.getBytes(StandardCharsets.UTF_8); | ||
| 69 | + httpExchange.sendResponseHeaders(200, bytes.length); | ||
| 70 | + os.write(bytes); | ||
| 71 | + os.close(); | ||
| 72 | + } | ||
| 73 | + | ||
| 74 | + private Map<String, String> getQueryParams(HttpExchange exchange) throws UnsupportedEncodingException { | ||
| 75 | + String query = exchange.getRequestURI().getQuery(); // 获取 name=张三&age=18 | ||
| 76 | + return parseQuery(query); | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + private Map<String, String> parseQuery(String query) throws UnsupportedEncodingException { | ||
| 80 | + Map<String, String> result = new HashMap<>(); | ||
| 81 | + if (query == null || query.isEmpty()) return result; | ||
| 82 | + | ||
| 83 | + for (String param : query.split("&")) { | ||
| 84 | + String[] pair = param.split("=", 2); | ||
| 85 | + if (pair.length == 2) { | ||
| 86 | + result.put(URLDecoder.decode(pair[0], StandardCharsets.UTF_8.toString()),URLDecoder.decode(pair[1], StandardCharsets.UTF_8.toString())); | ||
| 87 | + } else if (pair.length == 1) { | ||
| 88 | + result.put(URLDecoder.decode(pair[0], StandardCharsets.UTF_8.toString()), ""); | ||
| 89 | + } | ||
| 90 | + } | ||
| 91 | + return result; | ||
| 92 | + } | ||
| 93 | + | ||
| 94 | + private Map<String, String> getParamsMap(HttpExchange httpExchange) throws IOException { | ||
| 95 | + String method = httpExchange.getRequestMethod(); | ||
| 96 | + Map<String, String> params = null; | ||
| 97 | + if ("POST".equalsIgnoreCase(method)) | ||
| 98 | + { | ||
| 99 | + params = getPostParams(httpExchange); | ||
| 100 | + } else if ("GET".equalsIgnoreCase(method)) | ||
| 101 | + { | ||
| 102 | + params = getQueryParams(httpExchange); | ||
| 103 | + } | ||
| 104 | + return params; | ||
| 105 | + } | ||
| 106 | + | ||
| 107 | + private Map<String, String> getPostParams(HttpExchange exchange) throws IOException { | ||
| 108 | + String body = readRequestBody(exchange); | ||
| 109 | + return parseQuery(body); // 和 GET 参数一样解析 | ||
| 110 | + } | ||
| 111 | + | ||
| 112 | + private String readRequestBody(HttpExchange exchange) throws IOException { | ||
| 113 | + BufferedReader reader = new BufferedReader( | ||
| 114 | + new InputStreamReader(exchange.getRequestBody(), StandardCharsets.UTF_8)); | ||
| 115 | + | ||
| 116 | + StringBuilder sb = new StringBuilder(); | ||
| 117 | + String line; | ||
| 118 | + while ((line = reader.readLine()) != null) { | ||
| 119 | + sb.append(line); | ||
| 120 | + } | ||
| 121 | + return sb.toString(); | ||
| 122 | + } | ||
| 123 | + | ||
| 124 | +} |
| 1 | +package com.zhonglai.luhui.neutrino.proxy.server.httpservice; | ||
| 2 | + | ||
| 3 | +import com.sun.net.httpserver.HttpServer; | ||
| 4 | + | ||
| 5 | +import java.io.IOException; | ||
| 6 | +import java.net.InetSocketAddress; | ||
| 7 | + | ||
| 8 | +public class SimpleHttpServer { | ||
| 9 | + public static void startHttpServer(int port) throws IOException | ||
| 10 | + { | ||
| 11 | + HttpServer server = HttpServer.create(new InetSocketAddress(port), 0); | ||
| 12 | + | ||
| 13 | + // 映射 /static/ 到 static/ 目录 | ||
| 14 | + server.createContext("/static/", new StaticFileHandler("static")); | ||
| 15 | + | ||
| 16 | + server.createContext("/portForward", new PortForwardHandler()); | ||
| 17 | + | ||
| 18 | + server.setExecutor(null); // 默认线程池 | ||
| 19 | + server.start(); | ||
| 20 | + System.out.println("Server started at http://localhost:" + port); | ||
| 21 | + } | ||
| 22 | +} |
| 1 | +package com.zhonglai.luhui.neutrino.proxy.server.httpservice; | ||
| 2 | + | ||
| 3 | +import com.sun.net.httpserver.HttpExchange; | ||
| 4 | +import com.sun.net.httpserver.HttpHandler; | ||
| 5 | + | ||
| 6 | +import java.io.IOException; | ||
| 7 | +import java.io.OutputStream; | ||
| 8 | +import java.nio.file.Files; | ||
| 9 | +import java.nio.file.Path; | ||
| 10 | +import java.nio.file.Paths; | ||
| 11 | + | ||
| 12 | +/** | ||
| 13 | + * 静态文件处理器 | ||
| 14 | + */ | ||
| 15 | +public class StaticFileHandler implements HttpHandler { | ||
| 16 | + private final Path basePath; | ||
| 17 | + | ||
| 18 | + public StaticFileHandler(String rootDir) { | ||
| 19 | + this.basePath = Paths.get(rootDir).toAbsolutePath(); | ||
| 20 | + } | ||
| 21 | + | ||
| 22 | + @Override | ||
| 23 | + public void handle(HttpExchange exchange) throws IOException { | ||
| 24 | + String uriPath = exchange.getRequestURI().getPath(); | ||
| 25 | + String other = uriPath.replaceFirst("^/[^/]+/", ""); | ||
| 26 | + Path filePath = basePath.resolve(other).normalize(); | ||
| 27 | + //生成 | ||
| 28 | + if (!filePath.startsWith(basePath) || !Files.exists(filePath)) { | ||
| 29 | + exchange.sendResponseHeaders(404, -1); | ||
| 30 | + return; | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + String contentType = guessContentType(filePath); | ||
| 34 | + byte[] fileBytes = Files.readAllBytes(filePath); | ||
| 35 | + exchange.getResponseHeaders().add("Content-Type", contentType); | ||
| 36 | + exchange.sendResponseHeaders(200, fileBytes.length); | ||
| 37 | + try (OutputStream os = exchange.getResponseBody()) { | ||
| 38 | + os.write(fileBytes); | ||
| 39 | + } | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + private String guessContentType(Path path) { | ||
| 43 | + String name = path.getFileName().toString().toLowerCase(); | ||
| 44 | + if (name.endsWith(".html")) return "text/html"; | ||
| 45 | + if (name.endsWith(".js")) return "application/javascript"; | ||
| 46 | + if (name.endsWith(".css")) return "text/css"; | ||
| 47 | + if (name.endsWith(".m3u8")) return "application/vnd.apple.mpegurl"; | ||
| 48 | + if (name.endsWith(".ts")) return "video/MP2T"; | ||
| 49 | + return "application/octet-stream"; | ||
| 50 | + } | ||
| 51 | +} |
| 1 | +package com.zhonglai.luhui.neutrino.proxy.server.httpservice.dto; | ||
| 2 | + | ||
| 3 | +public class MappingPort { | ||
| 4 | + private Integer clientPort; //客服端端口 | ||
| 5 | + private Integer servicePort; //服务器端口 | ||
| 6 | + | ||
| 7 | + public Integer getClientPort() { | ||
| 8 | + return clientPort; | ||
| 9 | + } | ||
| 10 | + | ||
| 11 | + public void setClientPort(Integer clientPort) { | ||
| 12 | + this.clientPort = clientPort; | ||
| 13 | + } | ||
| 14 | + | ||
| 15 | + public Integer getServicePort() { | ||
| 16 | + return servicePort; | ||
| 17 | + } | ||
| 18 | + | ||
| 19 | + public void setServicePort(Integer servicePort) { | ||
| 20 | + this.servicePort = servicePort; | ||
| 21 | + } | ||
| 22 | +} |
| 1 | +package com.zhonglai.luhui.neutrino.proxy.server.httpservice.dto; | ||
| 2 | + | ||
| 3 | +import java.util.List; | ||
| 4 | +import java.util.Map; | ||
| 5 | + | ||
| 6 | +/** | ||
| 7 | + * 端口映射内容 | ||
| 8 | + */ | ||
| 9 | +public class PortForwardMapBody { | ||
| 10 | + private String clientId; | ||
| 11 | + private List<MappingPort> mappingPortList; | ||
| 12 | + | ||
| 13 | + public String getClientId() { | ||
| 14 | + return clientId; | ||
| 15 | + } | ||
| 16 | + | ||
| 17 | + public void setClientId(String clientId) { | ||
| 18 | + this.clientId = clientId; | ||
| 19 | + } | ||
| 20 | + | ||
| 21 | + public List<MappingPort> getMappingPortList() { | ||
| 22 | + return mappingPortList; | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + public void setMappingPortList(List<MappingPort> mappingPortList) { | ||
| 26 | + this.mappingPortList = mappingPortList; | ||
| 27 | + } | ||
| 28 | +} |
| 1 | +package com.zhonglai.luhui.neutrino.proxy.server.httpservice.dto; | ||
| 2 | + | ||
| 3 | +public class ResponseMessage { | ||
| 4 | + private String msg; | ||
| 5 | + private Integer code; | ||
| 6 | + private Object data; | ||
| 7 | + | ||
| 8 | + public ResponseMessage(String msg, Integer code) { | ||
| 9 | + this.msg = msg; | ||
| 10 | + this.code = code; | ||
| 11 | + } | ||
| 12 | + | ||
| 13 | + public ResponseMessage(String msg, Integer code, Object data) { | ||
| 14 | + this.msg = msg; | ||
| 15 | + this.code = code; | ||
| 16 | + this.data = data; | ||
| 17 | + } | ||
| 18 | + | ||
| 19 | + public String getMsg() { | ||
| 20 | + return msg; | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + public void setMsg(String msg) { | ||
| 24 | + this.msg = msg; | ||
| 25 | + } | ||
| 26 | + | ||
| 27 | + public Integer getCode() { | ||
| 28 | + return code; | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + public void setCode(Integer code) { | ||
| 32 | + this.code = code; | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + public Object getData() { | ||
| 36 | + return data; | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + public void setData(Object data) { | ||
| 40 | + this.data = data; | ||
| 41 | + } | ||
| 42 | +} |
| 1 | +package com.zhonglai.luhui.neutrino.proxy.server.proxy; | ||
| 2 | + | ||
| 3 | +import com.zhonglai.luhui.neutrino.proxy.server.proxy.handler.ControlHandler; | ||
| 4 | +import com.zhonglai.luhui.neutrino.proxy.server.proxy.handler.HeartbeatTimeoutHandler; | ||
| 5 | +import io.netty.bootstrap.ServerBootstrap; | ||
| 6 | +import io.netty.channel.ChannelFuture; | ||
| 7 | +import io.netty.channel.ChannelFutureListener; | ||
| 8 | +import io.netty.channel.ChannelInitializer; | ||
| 9 | +import io.netty.channel.nio.NioEventLoopGroup; | ||
| 10 | +import io.netty.channel.socket.SocketChannel; | ||
| 11 | +import io.netty.channel.socket.nio.NioServerSocketChannel; | ||
| 12 | +import io.netty.handler.codec.LineBasedFrameDecoder; | ||
| 13 | +import io.netty.handler.codec.string.StringDecoder; | ||
| 14 | +import io.netty.handler.codec.string.StringEncoder; | ||
| 15 | +import io.netty.handler.timeout.IdleStateHandler; | ||
| 16 | + | ||
| 17 | +/** | ||
| 18 | + * 代理服务 | ||
| 19 | + */ | ||
| 20 | +public class ProxyServer { | ||
| 21 | + public static void start(int proxyPort) throws Exception | ||
| 22 | + { | ||
| 23 | + NioEventLoopGroup bossGroup = new NioEventLoopGroup(1); | ||
| 24 | + NioEventLoopGroup workerGroup = new NioEventLoopGroup(); | ||
| 25 | + | ||
| 26 | + ServerBootstrap bootstrap = new ServerBootstrap() | ||
| 27 | + .group(bossGroup, workerGroup) | ||
| 28 | + .channel(NioServerSocketChannel.class) | ||
| 29 | + .childHandler(new ChannelInitializer<SocketChannel>() { | ||
| 30 | + @Override | ||
| 31 | + protected void initChannel(SocketChannel ch) { | ||
| 32 | + ch.pipeline().addLast(new LineBasedFrameDecoder(1024)); | ||
| 33 | + ch.pipeline().addLast(new IdleStateHandler(60, 0, 0)); | ||
| 34 | + ch.pipeline().addLast(new HeartbeatTimeoutHandler()); | ||
| 35 | + ch.pipeline().addLast(new StringDecoder()); | ||
| 36 | + ch.pipeline().addLast(new StringEncoder()); | ||
| 37 | + ch.pipeline().addLast(new ControlHandler()); | ||
| 38 | + } | ||
| 39 | + }); | ||
| 40 | + | ||
| 41 | + ChannelFuture future = bootstrap.bind(proxyPort).sync(); // bind 完成阻塞等待 | ||
| 42 | + | ||
| 43 | + future.channel().closeFuture().addListener((ChannelFutureListener) closeFuture -> { | ||
| 44 | + System.out.println("Server channel closed."); | ||
| 45 | + }); | ||
| 46 | + System.out.println("Netty server started on port: " + proxyPort); | ||
| 47 | + | ||
| 48 | + // ✅ 注册优雅关闭钩子,不阻塞主线程 | ||
| 49 | + Runtime.getRuntime().addShutdownHook(new Thread(() -> { | ||
| 50 | + System.out.println("Shutdown signal received. Closing Netty gracefully..."); | ||
| 51 | + bossGroup.shutdownGracefully(); | ||
| 52 | + workerGroup.shutdownGracefully(); | ||
| 53 | + })); | ||
| 54 | + | ||
| 55 | + | ||
| 56 | + } | ||
| 57 | +} |
| 1 | +package com.zhonglai.luhui.neutrino.proxy.server.proxy.handler; | ||
| 2 | + | ||
| 3 | +import com.fasterxml.jackson.databind.JsonNode; | ||
| 4 | +import com.fasterxml.jackson.databind.ObjectMapper; | ||
| 5 | +import com.zhonglai.luhui.neutrino.proxy.common.RegisterMessage; | ||
| 6 | +import com.zhonglai.luhui.neutrino.proxy.server.ClientSessionManager; | ||
| 7 | +import com.zhonglai.luhui.neutrino.proxy.server.function.PortForwardManager; | ||
| 8 | +import io.netty.channel.ChannelHandlerContext; | ||
| 9 | +import io.netty.channel.SimpleChannelInboundHandler; | ||
| 10 | +import org.apache.commons.io.IOUtils; | ||
| 11 | + | ||
| 12 | +import java.io.IOException; | ||
| 13 | +import java.io.PrintWriter; | ||
| 14 | +import java.net.Socket; | ||
| 15 | + | ||
| 16 | +public class ControlHandler extends SimpleChannelInboundHandler<String> { | ||
| 17 | + private static final ObjectMapper mapper = new ObjectMapper(); | ||
| 18 | + private String clientId; | ||
| 19 | + | ||
| 20 | + @Override | ||
| 21 | + protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { | ||
| 22 | + JsonNode json = mapper.readTree(msg); | ||
| 23 | + String type = json.get("type").asText(); | ||
| 24 | + | ||
| 25 | + switch (type) { | ||
| 26 | + case "register": | ||
| 27 | + clientId = json.get("clientId").asText(); | ||
| 28 | + ClientSessionManager.register(clientId, ctx.channel()); | ||
| 29 | + ctx.writeAndFlush("{\"type\":\"register_ack\",\"status\":\"ok\"}\n"); | ||
| 30 | + break; | ||
| 31 | + | ||
| 32 | + case "heartbeat": | ||
| 33 | + ClientSessionManager.heartbeat(json.get("clientId").asText()); | ||
| 34 | + break; | ||
| 35 | + default: | ||
| 36 | + System.out.println("未知消息类型: " + type); | ||
| 37 | + } | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + @Override | ||
| 41 | + public void channelInactive(ChannelHandlerContext ctx) throws Exception { | ||
| 42 | + ClientSessionManager.remove(ctx.channel()); | ||
| 43 | + System.out.println("客户端断开连接:" + clientId); | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | +} |
| 1 | +package com.zhonglai.luhui.neutrino.proxy.server.proxy.handler; | ||
| 2 | + | ||
| 3 | +import io.netty.channel.ChannelHandlerContext; | ||
| 4 | +import io.netty.channel.ChannelInboundHandlerAdapter; | ||
| 5 | +import io.netty.handler.timeout.IdleState; | ||
| 6 | +import io.netty.handler.timeout.IdleStateEvent; | ||
| 7 | + | ||
| 8 | +public class HeartbeatTimeoutHandler extends ChannelInboundHandlerAdapter { | ||
| 9 | + @Override | ||
| 10 | + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { | ||
| 11 | + if (evt instanceof IdleStateEvent && ((IdleStateEvent)evt).state() == IdleState.READER_IDLE) { | ||
| 12 | + System.out.println("心跳超时,断开客户端:" + ctx.channel().remoteAddress()); | ||
| 13 | + ctx.close(); | ||
| 14 | + } else { | ||
| 15 | + super.userEventTriggered(ctx, evt); | ||
| 16 | + } | ||
| 17 | + } | ||
| 18 | +} |
lh-modules/lh-neutrino-proxy/pom.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | +<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
| 3 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| 4 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
| 5 | + <modelVersion>4.0.0</modelVersion> | ||
| 6 | + <parent> | ||
| 7 | + <groupId>com.zhonglai.luhui</groupId> | ||
| 8 | + <artifactId>lh-modules</artifactId> | ||
| 9 | + <version>1.0-SNAPSHOT</version> | ||
| 10 | + </parent> | ||
| 11 | + | ||
| 12 | + <artifactId>lh-neutrino-proxy</artifactId> | ||
| 13 | + <modules> | ||
| 14 | + <module>lh-neutrino-proxy-server</module> | ||
| 15 | + <module>lh-neutrino-proxy-client</module> | ||
| 16 | + <module>lh-neutrino-proxy-common</module> | ||
| 17 | + </modules> | ||
| 18 | + | ||
| 19 | + <properties> | ||
| 20 | + <maven.compiler.source>8</maven.compiler.source> | ||
| 21 | + <maven.compiler.target>8</maven.compiler.target> | ||
| 22 | + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
| 23 | + </properties> | ||
| 24 | + | ||
| 25 | + <packaging>pom</packaging> | ||
| 26 | + <description> | ||
| 27 | + 透传代理 | ||
| 28 | + </description> | ||
| 29 | +</project> |
| @@ -38,6 +38,7 @@ | @@ -38,6 +38,7 @@ | ||
| 38 | <module>lh-ssh-service-lesten</module> | 38 | <module>lh-ssh-service-lesten</module> |
| 39 | <module>lh-deviceInfo-sync</module> | 39 | <module>lh-deviceInfo-sync</module> |
| 40 | <module>lh-camera</module> | 40 | <module>lh-camera</module> |
| 41 | + <module>lh-neutrino-proxy</module> | ||
| 41 | </modules> | 42 | </modules> |
| 42 | 43 | ||
| 43 | <properties> | 44 | <properties> |
| @@ -369,6 +369,11 @@ | @@ -369,6 +369,11 @@ | ||
| 369 | <version>${ruoyi.version}</version> | 369 | <version>${ruoyi.version}</version> |
| 370 | </dependency> | 370 | </dependency> |
| 371 | <dependency> | 371 | <dependency> |
| 372 | + <groupId>com.zhonglai.luhui</groupId> | ||
| 373 | + <artifactId>lh-neutrino-proxy-common</artifactId> | ||
| 374 | + <version>${ruoyi.version}</version> | ||
| 375 | + </dependency> | ||
| 376 | + <dependency> | ||
| 372 | <groupId>com.zhonglai</groupId> | 377 | <groupId>com.zhonglai</groupId> |
| 373 | <artifactId>ServiceDao</artifactId> | 378 | <artifactId>ServiceDao</artifactId> |
| 374 | <version>1.4.3</version> | 379 | <version>1.4.3</version> |
| @@ -603,6 +608,13 @@ | @@ -603,6 +608,13 @@ | ||
| 603 | <artifactId>httpclient5</artifactId> | 608 | <artifactId>httpclient5</artifactId> |
| 604 | <version>5.1.3</version> | 609 | <version>5.1.3</version> |
| 605 | </dependency> | 610 | </dependency> |
| 611 | + | ||
| 612 | + <dependency> | ||
| 613 | + <groupId>org.apache.xmlgraphics</groupId> | ||
| 614 | + <artifactId>batik-transcoder</artifactId> | ||
| 615 | + <version>1.18</version> | ||
| 616 | + </dependency> | ||
| 617 | + | ||
| 606 | </dependencies> | 618 | </dependencies> |
| 607 | 619 | ||
| 608 | 620 |
-
请 注册 或 登录 后发表评论