mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-09-30 16:26:36 +08:00
init
This commit is contained in:
@@ -0,0 +1,393 @@
|
||||
package com.admin.common.utils;
|
||||
|
||||
import com.admin.common.dto.GostDto;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import org.aspectj.apache.bcel.generic.RET;
|
||||
|
||||
public class GostUtil {
|
||||
|
||||
private static final String API_BASE_URL = "/api/config/";
|
||||
private static final String LIMITERS_ENDPOINT = "limiters";
|
||||
private static final String SERVICES_ENDPOINT = "services";
|
||||
private static final String CHAINS_ENDPOINT = "chains";
|
||||
|
||||
|
||||
public static GostDto SaveConfig(String addr, String secret) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("format", "json");
|
||||
String url = "http://" + addr + "/api/config?format=json";
|
||||
return HttpUtils.post(url, data, secret);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加限流器配置
|
||||
*
|
||||
* @param addr 服务器地址
|
||||
* @param name 限流器名称
|
||||
* @param speed 限速值(MB)
|
||||
* @param secret 认证密钥
|
||||
* @return 请求结果
|
||||
*/
|
||||
public static GostDto AddLimiters(String addr, Long name, String speed, String secret) {
|
||||
JSONObject data = createLimiterData(name, speed);
|
||||
String url = buildUrl(addr, LIMITERS_ENDPOINT);
|
||||
return HttpUtils.post(url, data, secret);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新限流器配置
|
||||
*
|
||||
* @param addr 服务器地址
|
||||
* @param name 限流器名称
|
||||
* @param speed 限速值(MB)
|
||||
* @param secret 认证密钥
|
||||
* @return 请求结果
|
||||
*/
|
||||
public static GostDto UpdateLimiters(String addr, Long name, String speed, String secret) {
|
||||
JSONObject data = createLimiterData(name, speed);
|
||||
String url = buildUrl(addr, LIMITERS_ENDPOINT + "/" + name);
|
||||
return HttpUtils.put(url, data, secret);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除限流器配置
|
||||
*
|
||||
* @param addr 服务器地址
|
||||
* @param name 限流器名称
|
||||
* @param secret 认证密钥
|
||||
* @return 请求结果
|
||||
*/
|
||||
public static GostDto DeleteLimiters(String addr, Long name, String secret) {
|
||||
String url = buildUrl(addr, LIMITERS_ENDPOINT + "/" + name);
|
||||
return HttpUtils.delete(url, secret);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建限流器数据
|
||||
*/
|
||||
private static JSONObject createLimiterData(Long name, String speed) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("name", name.toString());
|
||||
JSONArray limits = new JSONArray();
|
||||
limits.add("$ " + speed + "MB " + speed + "MB");
|
||||
data.put("limits", limits);
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加服务配置(支持端口转发和隧道转发)
|
||||
*
|
||||
* @param addr 服务器地址
|
||||
* @param name 服务名称
|
||||
* @param in_port 监听端口
|
||||
* @param limiter 限流器ID
|
||||
* @param remoteAddr 远程地址(端口转发时使用)
|
||||
* @param secret 认证密钥
|
||||
* @param fow_type 转发类型:1=端口转发,2=隧道转发
|
||||
* @return 请求结果
|
||||
*/
|
||||
public static GostDto AddService(String addr, String name, Integer in_port, Integer limiter, String remoteAddr, String secret, Integer fow_type) {
|
||||
JSONArray services = new JSONArray();
|
||||
String[] protocols = {"tcp", "udp"};
|
||||
for (String protocol : protocols) {
|
||||
JSONObject service = createServiceConfig(name, in_port, limiter, remoteAddr, protocol, fow_type);
|
||||
services.add(service);
|
||||
}
|
||||
String url = buildUrl(addr, SERVICES_ENDPOINT + "/batch");
|
||||
return HttpUtils.post(url, services, secret);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新服务配置(批量更新TCP和UDP服务)
|
||||
*
|
||||
* @param addr 服务器地址
|
||||
* @param name 服务名称
|
||||
* @param in_port 监听端口
|
||||
* @param limiter 限流器ID
|
||||
* @param remoteAddr 远程地址(端口转发时使用)
|
||||
* @param secret 认证密钥
|
||||
* @param fow_type 转发类型:1=端口转发,2=隧道转发
|
||||
* @return 请求结果
|
||||
*/
|
||||
public static GostDto UpdateService(String addr, String name, Integer in_port, Integer limiter, String remoteAddr, String secret, Integer fow_type) {
|
||||
JSONArray services = new JSONArray();
|
||||
String[] protocols = {"tcp", "udp"};
|
||||
for (String protocol : protocols) {
|
||||
JSONObject service = createServiceConfig(name, in_port, limiter, remoteAddr, protocol, fow_type);
|
||||
services.add(service);
|
||||
}
|
||||
String url = buildUrl(addr, SERVICES_ENDPOINT + "/batch");
|
||||
return HttpUtils.put(url, services, secret);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除服务配置(批量删除TCP和UDP服务)
|
||||
*
|
||||
* @param addr 服务器地址
|
||||
* @param name 服务名称
|
||||
* @param secret 认证密钥
|
||||
* @return 请求结果
|
||||
*/
|
||||
public static GostDto DeleteService(String addr, String name, String secret) {
|
||||
JSONObject data = new JSONObject();
|
||||
JSONArray services = new JSONArray();
|
||||
services.add(name + "_tcp");
|
||||
services.add(name + "_udp");
|
||||
data.put("services", services);
|
||||
|
||||
String url = buildUrl(addr, SERVICES_ENDPOINT + "/batch");
|
||||
return HttpUtils.delete(url, data, secret);
|
||||
}
|
||||
|
||||
|
||||
public static GostDto PauseService(String addr, String name, String secret) {
|
||||
JSONObject data = new JSONObject();
|
||||
JSONArray services = new JSONArray();
|
||||
services.add(name + "_tcp");
|
||||
services.add(name + "_udp");
|
||||
data.put("services", services);
|
||||
String url = buildUrl(addr, SERVICES_ENDPOINT + "/batch/pause");
|
||||
return HttpUtils.post(url, data, secret);
|
||||
}
|
||||
|
||||
public static GostDto ResumeService(String addr, String name, String secret) {
|
||||
JSONObject data = new JSONObject();
|
||||
JSONArray services = new JSONArray();
|
||||
services.add(name + "_tcp");
|
||||
services.add(name + "_udp");
|
||||
data.put("services", services);
|
||||
String url = buildUrl(addr, SERVICES_ENDPOINT + "/batch/resume");
|
||||
return HttpUtils.post(url, data, secret);
|
||||
}
|
||||
|
||||
public static GostDto PauseRemoteService(String addr, String name, String secret) {
|
||||
JSONObject data = new JSONObject();
|
||||
JSONArray services = new JSONArray();
|
||||
services.add(name + "_tls");
|
||||
data.put("services", services);
|
||||
String url = buildUrl(addr, SERVICES_ENDPOINT + "/batch/pause");
|
||||
return HttpUtils.post(url, data, secret);
|
||||
}
|
||||
|
||||
public static GostDto ResumeRemoteService(String addr, String name, String secret) {
|
||||
JSONObject data = new JSONObject();
|
||||
JSONArray services = new JSONArray();
|
||||
services.add(name + "_tls");
|
||||
data.put("services", services);
|
||||
String url = buildUrl(addr, SERVICES_ENDPOINT + "/batch/resume");
|
||||
return HttpUtils.post(url, data, secret);
|
||||
}
|
||||
|
||||
public static GostDto AddChains(String addr, String name, String remoteAddr, String secret) {
|
||||
JSONObject dialer = new JSONObject();
|
||||
dialer.put("type", "tls");
|
||||
|
||||
JSONObject connector = new JSONObject();
|
||||
connector.put("type", "relay");
|
||||
|
||||
JSONObject node = new JSONObject();
|
||||
node.put("name", "node-" + name);
|
||||
node.put("addr", remoteAddr);
|
||||
node.put("connector", connector);
|
||||
node.put("dialer", dialer);
|
||||
|
||||
JSONArray nodes = new JSONArray();
|
||||
nodes.add(node);
|
||||
|
||||
JSONObject hop = new JSONObject();
|
||||
hop.put("name", "hop-" + name);
|
||||
hop.put("nodes", nodes);
|
||||
|
||||
JSONArray hops = new JSONArray();
|
||||
hops.add(hop);
|
||||
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("name", name + "_chains");
|
||||
data.put("hops", hops);
|
||||
|
||||
String url = buildUrl(addr, CHAINS_ENDPOINT);
|
||||
return HttpUtils.post(url, data, secret);
|
||||
}
|
||||
|
||||
public static GostDto UpdateChains(String addr, String name, String remoteAddr, String secret) {
|
||||
JSONObject dialer = new JSONObject();
|
||||
dialer.put("type", "tls");
|
||||
|
||||
JSONObject connector = new JSONObject();
|
||||
connector.put("type", "relay");
|
||||
|
||||
JSONObject node = new JSONObject();
|
||||
node.put("name", "node-" + name);
|
||||
node.put("addr", remoteAddr);
|
||||
node.put("connector", connector);
|
||||
node.put("dialer", dialer);
|
||||
|
||||
JSONArray nodes = new JSONArray();
|
||||
nodes.add(node);
|
||||
|
||||
JSONObject hop = new JSONObject();
|
||||
hop.put("name", "hop-" + name);
|
||||
hop.put("nodes", nodes);
|
||||
|
||||
JSONArray hops = new JSONArray();
|
||||
hops.add(hop);
|
||||
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("name", name + "_chains");
|
||||
data.put("hops", hops);
|
||||
|
||||
String url = buildUrl(addr, CHAINS_ENDPOINT + "/" + name + "_chains");
|
||||
return HttpUtils.put(url, data, secret);
|
||||
}
|
||||
|
||||
|
||||
public static GostDto DeleteChains(String addr, String name, String secret) {
|
||||
String url = buildUrl(addr, CHAINS_ENDPOINT + "/" + name + "_chains");
|
||||
return HttpUtils.delete(url, secret);
|
||||
}
|
||||
|
||||
public static GostDto AddRemoteService(String addr, String name, Integer out_port, String remoteAddr, String secret) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("name", name + "_tls");
|
||||
data.put("addr", ":" + out_port);
|
||||
JSONObject handler = new JSONObject();
|
||||
handler.put("type", "relay");
|
||||
data.put("handler", handler);
|
||||
JSONObject listener = new JSONObject();
|
||||
listener.put("type", "tls");
|
||||
data.put("listener", listener);
|
||||
JSONObject forwarder = new JSONObject();
|
||||
JSONArray nodes = new JSONArray();
|
||||
JSONObject node = new JSONObject();
|
||||
node.put("name", name + "_node");
|
||||
node.put("addr", remoteAddr);
|
||||
nodes.add(node);
|
||||
forwarder.put("nodes", nodes);
|
||||
data.put("forwarder", forwarder);
|
||||
String url = buildUrl(addr, SERVICES_ENDPOINT);
|
||||
return HttpUtils.post(url, data, secret);
|
||||
}
|
||||
|
||||
|
||||
public static GostDto UpdateRemoteService(String addr, String name, Integer out_port, String remoteAddr, String secret) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("name", name + "_tls");
|
||||
data.put("addr", ":" + out_port);
|
||||
JSONObject handler = new JSONObject();
|
||||
handler.put("type", "relay");
|
||||
data.put("handler", handler);
|
||||
JSONObject listener = new JSONObject();
|
||||
listener.put("type", "tls");
|
||||
data.put("listener", listener);
|
||||
JSONObject forwarder = new JSONObject();
|
||||
JSONArray nodes = new JSONArray();
|
||||
JSONObject node = new JSONObject();
|
||||
node.put("name", name + "_node");
|
||||
node.put("addr", remoteAddr);
|
||||
nodes.add(node);
|
||||
forwarder.put("nodes", nodes);
|
||||
data.put("forwarder", forwarder);
|
||||
String url = buildUrl(addr, SERVICES_ENDPOINT + "/" + name + "_tls");
|
||||
return HttpUtils.put(url, data, secret);
|
||||
}
|
||||
|
||||
|
||||
public static GostDto DeleteRemoteService(String addr, String name, String secret) {
|
||||
String url = buildUrl(addr, SERVICES_ENDPOINT + "/" + name + "_tls");
|
||||
return HttpUtils.delete(url, secret);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建单个服务配置
|
||||
*/
|
||||
private static JSONObject createServiceConfig(String name, Integer in_port, Integer limiter, String remoteAddr, String protocol, Integer fow_type) {
|
||||
JSONObject service = new JSONObject();
|
||||
service.put("name", name + "_" + protocol);
|
||||
service.put("addr", ":" + in_port);
|
||||
|
||||
// 添加限流器配置
|
||||
if (limiter != null) {
|
||||
service.put("limiter", limiter.toString());
|
||||
}
|
||||
|
||||
// 配置处理器
|
||||
JSONObject handler = createHandler(protocol, name, fow_type);
|
||||
service.put("handler", handler);
|
||||
|
||||
// 配置监听器
|
||||
JSONObject listener = createListener(protocol);
|
||||
service.put("listener", listener);
|
||||
|
||||
// 端口转发需要配置转发器
|
||||
if (isPortForwarding(fow_type)) {
|
||||
JSONObject forwarder = createForwarder(protocol, remoteAddr);
|
||||
service.put("forwarder", forwarder);
|
||||
}
|
||||
|
||||
return service;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建处理器配置
|
||||
*/
|
||||
private static JSONObject createHandler(String protocol, String name, Integer fow_type) {
|
||||
JSONObject handler = new JSONObject();
|
||||
handler.put("type", protocol);
|
||||
|
||||
// 隧道转发需要添加链配置
|
||||
if (isTunnelForwarding(fow_type)) {
|
||||
handler.put("chain", name + "_chains");
|
||||
}
|
||||
|
||||
return handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建监听器配置
|
||||
*/
|
||||
private static JSONObject createListener(String protocol) {
|
||||
JSONObject listener = new JSONObject();
|
||||
listener.put("type", protocol);
|
||||
return listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建转发器配置
|
||||
*/
|
||||
private static JSONObject createForwarder(String protocol, String remoteAddr) {
|
||||
JSONObject forwarder = new JSONObject();
|
||||
JSONArray nodes = new JSONArray();
|
||||
JSONObject node = new JSONObject();
|
||||
node.put("name", protocol);
|
||||
node.put("addr", remoteAddr);
|
||||
nodes.add(node);
|
||||
forwarder.put("nodes", nodes);
|
||||
return forwarder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为端口转发
|
||||
*/
|
||||
private static boolean isPortForwarding(Integer fow_type) {
|
||||
return fow_type != null && fow_type == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为隧道转发
|
||||
*/
|
||||
private static boolean isTunnelForwarding(Integer fow_type) {
|
||||
return fow_type != null && fow_type != 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构建API URL
|
||||
*/
|
||||
private static String buildUrl(String addr, String endpoint) {
|
||||
return "http://" + addr + API_BASE_URL + endpoint;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.admin.common.utils;
|
||||
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
public class HttpContextUtils {
|
||||
|
||||
public static HttpServletRequest getHttpServletRequest(){
|
||||
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
package com.admin.common.utils;
|
||||
|
||||
import com.admin.common.dto.GostDto;
|
||||
import com.admin.common.task.SaveConfigAsync;
|
||||
import com.admin.config.RestTemplateConfig;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.SneakyThrows;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.utils.URIBuilder;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.ResponseErrorHandler;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* HTTP请求工具类
|
||||
* 支持GET和POST请求,支持表单和JSON格式的请求体
|
||||
*/
|
||||
@Component
|
||||
public class HttpUtils implements ApplicationContextAware {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(HttpUtils.class);
|
||||
|
||||
// 10秒超时配置
|
||||
private static final int TIMEOUT_SECONDS = 10;
|
||||
private static final int TIMEOUT_MILLISECONDS = TIMEOUT_SECONDS * 1000;
|
||||
|
||||
private static ApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext context) {
|
||||
HttpUtils.applicationContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取SaveConfigAsync Bean
|
||||
*/
|
||||
private static SaveConfigAsync getSaveConfigAsync() {
|
||||
try {
|
||||
return applicationContext.getBean(SaveConfigAsync.class);
|
||||
} catch (Exception e) {
|
||||
logger.warn("无法获取SaveConfigAsync Bean: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从URL中提取IP和端口
|
||||
*/
|
||||
private static String extractIpAndPortFromUrl(String url) {
|
||||
try {
|
||||
URI uri = URI.create(url);
|
||||
String host = uri.getHost();
|
||||
int port = uri.getPort();
|
||||
if (port == -1) {
|
||||
port = uri.getScheme().equals("https") ? 443 : 80;
|
||||
}
|
||||
return host + ":" + port;
|
||||
} catch (Exception e) {
|
||||
logger.warn("无法从URL提取IP和端口: {}", url);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步保存配置
|
||||
*/
|
||||
private static void asyncSaveConfig(String url, String secret) {
|
||||
try {
|
||||
SaveConfigAsync saveConfigAsync = getSaveConfigAsync();
|
||||
if (saveConfigAsync != null) {
|
||||
String ipAndPort = extractIpAndPortFromUrl(url);
|
||||
saveConfigAsync.run(ipAndPort, secret);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("异步保存配置失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义错误处理器,不抛出异常
|
||||
*/
|
||||
private static class NoOpResponseErrorHandler implements ResponseErrorHandler {
|
||||
@Override
|
||||
public boolean hasError(ClientHttpResponse response) throws IOException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleError(ClientHttpResponse response) throws IOException {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建带超时配置的RestTemplate
|
||||
*/
|
||||
private static RestTemplate createRestTemplateWithTimeout() {
|
||||
// 创建基础的RequestFactory
|
||||
org.springframework.http.client.SimpleClientHttpRequestFactory factory =
|
||||
new org.springframework.http.client.SimpleClientHttpRequestFactory();
|
||||
|
||||
// 设置连接超时和读取超时
|
||||
factory.setConnectTimeout(TIMEOUT_MILLISECONDS);
|
||||
factory.setReadTimeout(TIMEOUT_MILLISECONDS);
|
||||
|
||||
// 创建RestTemplate
|
||||
RestTemplate restTemplate = new RestTemplate(factory);
|
||||
restTemplate.setErrorHandler(new NoOpResponseErrorHandler());
|
||||
|
||||
return restTemplate;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static GostDto post(String url, Object requestBody, String secret) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
|
||||
String auth = secret + ":" + secret;
|
||||
String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes(StandardCharsets.UTF_8));
|
||||
headers.set("Authorization", "Basic " + encodedAuth);
|
||||
HttpEntity<Object> entity = new HttpEntity<>(requestBody, headers);
|
||||
RestTemplate restTemplate = createRestTemplateWithTimeout();
|
||||
try {
|
||||
ResponseEntity<GostDto> response = restTemplate.postForEntity(url, entity, GostDto.class);
|
||||
GostDto body = response.getBody();
|
||||
if (body.getMsg() != null && body.getMsg().contains("exists")) {
|
||||
body.setMsg("OK");
|
||||
}
|
||||
|
||||
if (!url.contains("/api/config?format=json")) {
|
||||
asyncSaveConfig(url, secret);
|
||||
}
|
||||
|
||||
return body;
|
||||
} catch (Exception e) {
|
||||
GostDto gostDto = new GostDto();
|
||||
gostDto.setCode(500);
|
||||
gostDto.setMsg("请求失败");
|
||||
return gostDto;
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static GostDto put(String url, Object requestBody, String secret) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
|
||||
String auth = secret + ":" + secret;
|
||||
String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes(StandardCharsets.UTF_8));
|
||||
headers.set("Authorization", "Basic " + encodedAuth);
|
||||
HttpEntity<Object> entity = new HttpEntity<>(requestBody, headers);
|
||||
RestTemplate restTemplate = createRestTemplateWithTimeout();
|
||||
try {
|
||||
ResponseEntity<GostDto> response = restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.PUT,
|
||||
entity,
|
||||
GostDto.class
|
||||
);
|
||||
GostDto body = response.getBody();
|
||||
asyncSaveConfig(url, secret);
|
||||
return body;
|
||||
} catch (Exception e) {
|
||||
GostDto gostDto = new GostDto();
|
||||
gostDto.setCode(500);
|
||||
gostDto.setMsg("请求失败");
|
||||
return gostDto;
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static GostDto delete(String url, String secret) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
|
||||
|
||||
// Basic Auth
|
||||
String auth = secret + ":" + secret;
|
||||
String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes(StandardCharsets.UTF_8));
|
||||
headers.set("Authorization", "Basic " + encodedAuth);
|
||||
|
||||
HttpEntity<Void> entity = new HttpEntity<>(headers);
|
||||
RestTemplate restTemplate = createRestTemplateWithTimeout();
|
||||
|
||||
try {
|
||||
ResponseEntity<GostDto> response = restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.DELETE,
|
||||
entity,
|
||||
GostDto.class
|
||||
);
|
||||
GostDto body = response.getBody();
|
||||
if (body != null && body.getMsg() != null && body.getMsg().contains("not found")) {
|
||||
body.setMsg("OK");
|
||||
}
|
||||
asyncSaveConfig(url, secret);
|
||||
return body;
|
||||
} catch (Exception e) {
|
||||
GostDto gostDto = new GostDto();
|
||||
gostDto.setCode(500);
|
||||
gostDto.setMsg("请求失败");
|
||||
return gostDto;
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static GostDto delete(String url, JSONObject data, String secret) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
|
||||
|
||||
// Basic Auth
|
||||
String auth = secret + ":" + secret;
|
||||
String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes(StandardCharsets.UTF_8));
|
||||
headers.set("Authorization", "Basic " + encodedAuth);
|
||||
|
||||
HttpEntity<JSONObject> entity = new HttpEntity<>(data, headers);
|
||||
RestTemplate restTemplate = createRestTemplateWithTimeout();
|
||||
|
||||
try {
|
||||
ResponseEntity<GostDto> response = restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.DELETE,
|
||||
entity,
|
||||
GostDto.class
|
||||
);
|
||||
GostDto body = response.getBody();
|
||||
if (body != null && body.getMsg() != null && body.getMsg().contains("not found")) {
|
||||
body.setMsg("OK");
|
||||
}
|
||||
asyncSaveConfig(url, secret);
|
||||
return body;
|
||||
} catch (Exception e) {
|
||||
GostDto gostDto = new GostDto();
|
||||
gostDto.setCode(500);
|
||||
gostDto.setMsg("请求失败");
|
||||
return gostDto;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.admin.common.utils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
public class IpUtils {
|
||||
|
||||
public static String getIpAddr(HttpServletRequest request) {
|
||||
String ipAddress = null;
|
||||
try {
|
||||
ipAddress = request.getHeader("x-forwarded-for");
|
||||
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
|
||||
ipAddress = request.getHeader("Proxy-Client-IP");
|
||||
}
|
||||
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
|
||||
ipAddress = request.getHeader("WL-Proxy-Client-IP");
|
||||
}
|
||||
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
|
||||
ipAddress = request.getRemoteAddr();
|
||||
if (ipAddress.equals("127.0.0.1")) {
|
||||
// 根据网卡取本机配置的IP
|
||||
InetAddress inet = null;
|
||||
try {
|
||||
inet = InetAddress.getLocalHost();
|
||||
} catch (UnknownHostException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
ipAddress = inet.getHostAddress();
|
||||
}
|
||||
}
|
||||
// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
|
||||
if (ipAddress != null && ipAddress.length() > 15) {
|
||||
// "***.***.***.***".length()
|
||||
// = 15
|
||||
if (ipAddress.indexOf(",") > 0) {
|
||||
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ipAddress="";
|
||||
}
|
||||
return ipAddress;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
package com.admin.common.utils;
|
||||
|
||||
import com.admin.entity.User;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import lombok.SneakyThrows;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* JWT工具类,不使用第三方库实现
|
||||
*/
|
||||
@Component
|
||||
public class JwtUtil {
|
||||
|
||||
@Value("${jwt-secret}")
|
||||
private String secretKey;
|
||||
|
||||
private static String SECRET_KEY;
|
||||
|
||||
// token有效期,7天
|
||||
private static final long EXPIRE_TIME = 7 * 24 * 60 * 60 * 1000;
|
||||
// 算法
|
||||
private static final String ALGORITHM = "HmacSHA256";
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
SECRET_KEY = this.secretKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成JWT Token
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @return 生成的JWT Token
|
||||
*/
|
||||
public static String generateToken(User user) {
|
||||
try {
|
||||
long nowMillis = System.currentTimeMillis();
|
||||
Date now = new Date(nowMillis);
|
||||
Date expireDate = new Date(nowMillis + EXPIRE_TIME);
|
||||
|
||||
// Header
|
||||
Map<String, Object> header = new HashMap<>();
|
||||
header.put("alg", ALGORITHM);
|
||||
header.put("typ", "JWT");
|
||||
String headerJson = JSON.toJSONString(header);
|
||||
String encodedHeader = Base64.getUrlEncoder().withoutPadding()
|
||||
.encodeToString(headerJson.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// Payload
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
payload.put("sub", user.getId().toString());
|
||||
payload.put("iat", now.getTime() / 1000); // 发布时间
|
||||
payload.put("exp", expireDate.getTime() / 1000); // 过期时间
|
||||
payload.put("user", user.getUser());
|
||||
payload.put("name", user.getName());
|
||||
payload.put("role_id", user.getRoleId());
|
||||
|
||||
String payloadJson = JSON.toJSONString(payload);
|
||||
String encodedPayload = Base64.getUrlEncoder().withoutPadding()
|
||||
.encodeToString(payloadJson.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// Signature
|
||||
String signature = calculateSignature(encodedHeader, encodedPayload);
|
||||
|
||||
// Token
|
||||
return encodedHeader + "." + encodedPayload + "." + signature;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("JWT token generation failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证JWT Token
|
||||
*
|
||||
* @param token JWT Token
|
||||
* @return 验证是否通过
|
||||
*/
|
||||
public static boolean validateToken(String token) {
|
||||
try {
|
||||
if (token == null || token.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String[] parts = token.split("\\.");
|
||||
if (parts.length != 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String encodedHeader = parts[0];
|
||||
String encodedPayload = parts[1];
|
||||
String signature = parts[2];
|
||||
|
||||
// 验证签名
|
||||
String expectedSignature = calculateSignature(encodedHeader, encodedPayload);
|
||||
if (!expectedSignature.equals(signature)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证过期时间
|
||||
String decodedPayload = new String(Base64.getUrlDecoder().decode(encodedPayload), StandardCharsets.UTF_8);
|
||||
Map<String, Object> payload = JSON.parseObject(decodedPayload, Map.class);
|
||||
long exp = Long.parseLong(payload.get("exp").toString());
|
||||
long now = System.currentTimeMillis() / 1000;
|
||||
|
||||
return exp > now;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从JWT Token中获取用户ID
|
||||
*
|
||||
* @param token JWT Token
|
||||
* @return 用户ID
|
||||
*/
|
||||
public static Long getUserIdFromToken(String token) {
|
||||
String[] parts = token.split("\\.");
|
||||
String encodedPayload = parts[1];
|
||||
String decodedPayload = new String(Base64.getUrlDecoder().decode(encodedPayload), StandardCharsets.UTF_8);
|
||||
Map<String, Object> payload = JSON.parseObject(decodedPayload, Map.class);
|
||||
return Long.parseLong(payload.get("sub").toString());
|
||||
}
|
||||
|
||||
|
||||
public static Integer getUserIdFromToken() {
|
||||
String token = HttpContextUtils.getHttpServletRequest().getHeader("Authorization");
|
||||
String[] parts = token.split("\\.");
|
||||
String encodedPayload = parts[1];
|
||||
String decodedPayload = new String(Base64.getUrlDecoder().decode(encodedPayload), StandardCharsets.UTF_8);
|
||||
Map<String, Object> payload = JSON.parseObject(decodedPayload, Map.class);
|
||||
return Integer.parseInt(payload.get("sub").toString());
|
||||
}
|
||||
|
||||
public static String getNameFromToken() {
|
||||
String token = HttpContextUtils.getHttpServletRequest().getHeader("Authorization");
|
||||
String[] parts = token.split("\\.");
|
||||
String encodedPayload = parts[1];
|
||||
String decodedPayload = new String(Base64.getUrlDecoder().decode(encodedPayload), StandardCharsets.UTF_8);
|
||||
Map<String, Object> payload = JSON.parseObject(decodedPayload, Map.class);
|
||||
return payload.get("name").toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从JWT Token中获取用户角色ID
|
||||
*
|
||||
* @param token JWT Token
|
||||
* @return 角色ID
|
||||
*/
|
||||
public static Integer getRoleIdFromToken(String token) {
|
||||
String[] parts = token.split("\\.");
|
||||
String encodedPayload = parts[1];
|
||||
String decodedPayload = new String(Base64.getUrlDecoder().decode(encodedPayload), StandardCharsets.UTF_8);
|
||||
Map<String, Object> payload = JSON.parseObject(decodedPayload, Map.class);
|
||||
return Integer.parseInt(payload.get("role_id").toString());
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static Integer getRoleIdFromToken() {
|
||||
String token = HttpContextUtils.getHttpServletRequest().getHeader("Authorization");
|
||||
if (token == null || token.isEmpty()) throw new Exception();
|
||||
String[] parts = token.split("\\.");
|
||||
String encodedPayload = parts[1];
|
||||
String decodedPayload = new String(Base64.getUrlDecoder().decode(encodedPayload), StandardCharsets.UTF_8);
|
||||
Map<String, Object> payload = JSON.parseObject(decodedPayload, Map.class);
|
||||
return Integer.parseInt(payload.get("role_id").toString());
|
||||
}
|
||||
/**
|
||||
* 计算签名
|
||||
*
|
||||
* @param encodedHeader 编码后的头部
|
||||
* @param encodedPayload 编码后的负载
|
||||
* @return 签名
|
||||
* @throws Exception 签名计算异常
|
||||
*/
|
||||
private static String calculateSignature(String encodedHeader, String encodedPayload) throws Exception {
|
||||
String content = encodedHeader + "." + encodedPayload;
|
||||
Mac hmac = Mac.getInstance(ALGORITHM);
|
||||
SecretKeySpec secretKeySpec = new SecretKeySpec(SECRET_KEY.getBytes(StandardCharsets.UTF_8), ALGORITHM);
|
||||
hmac.init(secretKeySpec);
|
||||
byte[] signatureBytes = hmac.doFinal(content.getBytes(StandardCharsets.UTF_8));
|
||||
return Base64.getUrlEncoder().withoutPadding().encodeToString(signatureBytes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package com.admin.common.utils;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* MD5工具类
|
||||
*/
|
||||
public class Md5Util {
|
||||
|
||||
private static final String MD5_ALGORITHM = "MD5";
|
||||
private static final String DEFAULT_SALT = "admin_salt_2024";
|
||||
|
||||
/**
|
||||
* 基础MD5加密
|
||||
*
|
||||
* @param input 待加密字符串
|
||||
* @return MD5加密后的字符串(32位小写)
|
||||
*/
|
||||
public static String md5(String input) {
|
||||
if (input == null || input.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance(MD5_ALGORITHM);
|
||||
byte[] digest = md.digest(input.getBytes());
|
||||
return bytesToHex(digest);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("MD5算法不可用", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* MD5加密(使用默认盐值)
|
||||
*
|
||||
* @param input 待加密字符串
|
||||
* @return MD5加密后的字符串
|
||||
*/
|
||||
public static String md5WithSalt(String input) {
|
||||
return md5WithSalt(input, DEFAULT_SALT);
|
||||
}
|
||||
|
||||
/**
|
||||
* MD5加密(使用自定义盐值)
|
||||
*
|
||||
* @param input 待加密字符串
|
||||
* @param salt 盐值
|
||||
* @return MD5加密后的字符串
|
||||
*/
|
||||
public static String md5WithSalt(String input, String salt) {
|
||||
if (input == null || input.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (salt == null) {
|
||||
salt = DEFAULT_SALT;
|
||||
}
|
||||
|
||||
return md5(input + salt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机盐值
|
||||
*
|
||||
* @param length 盐值长度
|
||||
* @return 随机盐值
|
||||
*/
|
||||
public static String generateSalt(int length) {
|
||||
SecureRandom random = new SecureRandom();
|
||||
byte[] salt = new byte[length];
|
||||
random.nextBytes(salt);
|
||||
return Base64.getEncoder().encodeToString(salt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成默认长度(16字节)的随机盐值
|
||||
*
|
||||
* @return 随机盐值
|
||||
*/
|
||||
public static String generateSalt() {
|
||||
return generateSalt(16);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证密码
|
||||
*
|
||||
* @param password 原始密码
|
||||
* @param hashedPassword 已加密的密码
|
||||
* @return 是否匹配
|
||||
*/
|
||||
public static boolean verify(String password, String hashedPassword) {
|
||||
if (password == null || hashedPassword == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String encrypted = md5WithSalt(password);
|
||||
return encrypted.equals(hashedPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证密码(使用自定义盐值)
|
||||
*
|
||||
* @param password 原始密码
|
||||
* @param salt 盐值
|
||||
* @param hashedPassword 已加密的密码
|
||||
* @return 是否匹配
|
||||
*/
|
||||
public static boolean verify(String password, String salt, String hashedPassword) {
|
||||
if (password == null || hashedPassword == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String encrypted = md5WithSalt(password, salt);
|
||||
return encrypted.equals(hashedPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* 多次MD5加密
|
||||
*
|
||||
* @param input 待加密字符串
|
||||
* @param times 加密次数
|
||||
* @return 加密后的字符串
|
||||
*/
|
||||
public static String md5Multiple(String input, int times) {
|
||||
if (input == null || input.isEmpty() || times <= 0) {
|
||||
return input;
|
||||
}
|
||||
|
||||
String result = input;
|
||||
for (int i = 0; i < times; i++) {
|
||||
result = md5(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字节数组转十六进制字符串
|
||||
*
|
||||
* @param bytes 字节数组
|
||||
* @return 十六进制字符串
|
||||
*/
|
||||
private static String bytesToHex(byte[] bytes) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
result.append(String.format("%02x", b));
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件的MD5值
|
||||
*
|
||||
* @param bytes 文件字节数组
|
||||
* @return MD5值
|
||||
*/
|
||||
public static String getFileMd5(byte[] bytes) {
|
||||
if (bytes == null || bytes.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance(MD5_ALGORITHM);
|
||||
byte[] digest = md.digest(bytes);
|
||||
return bytesToHex(digest);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("MD5算法不可用", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package com.admin.common.utils;
|
||||
|
||||
|
||||
import com.admin.entity.Node;
|
||||
import com.admin.service.NodeService;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.handler.TextWebSocketHandler;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
|
||||
@Slf4j
|
||||
public class WebSocketServer extends TextWebSocketHandler {
|
||||
|
||||
@Resource
|
||||
NodeService nodeService;
|
||||
|
||||
// 存储所有活跃的 WebSocket 连接
|
||||
private static final CopyOnWriteArraySet<WebSocketSession> activeSessions = new CopyOnWriteArraySet<>();
|
||||
|
||||
// 为每个session提供锁对象,防止并发发送消息
|
||||
private static final ConcurrentHashMap<String, Object> sessionLocks = new ConcurrentHashMap<>();
|
||||
|
||||
//接受客户端消息
|
||||
@Override
|
||||
public void handleTextMessage(WebSocketSession session, TextMessage message) {
|
||||
try {
|
||||
if (StringUtils.isNoneBlank(message.getPayload())) {
|
||||
//log.info("收到消息: {}", message.getPayload());
|
||||
|
||||
String id = session.getAttributes().get("id").toString();
|
||||
String type = session.getAttributes().get("type").toString();
|
||||
|
||||
// 先发送确认消息
|
||||
sendToUser(session, "ok");
|
||||
|
||||
// 如果是节点类型,转发消息给其他会话
|
||||
if (Objects.equals(type, "1")) {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("id", id);
|
||||
jsonObject.put("type", "info");
|
||||
jsonObject.put("data", message.getPayload());
|
||||
String broadcastMessage = jsonObject.toJSONString();
|
||||
|
||||
// 异步处理广播消息,避免阻塞当前线程
|
||||
for (WebSocketSession targetSession : activeSessions) {
|
||||
if (targetSession != null && targetSession.isOpen() && !targetSession.equals(session)) {
|
||||
sendToUser(targetSession, broadcastMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("处理WebSocket消息时发生异常: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// 建立连接
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession session) {
|
||||
try {
|
||||
String id = session.getAttributes().get("id").toString();
|
||||
String type = session.getAttributes().get("type").toString();
|
||||
if (!Objects.equals(type, "1")) {
|
||||
activeSessions.add(session);
|
||||
}else {
|
||||
Node byId = nodeService.getById(id);
|
||||
if (byId != null) {
|
||||
byId.setStatus(1);
|
||||
nodeService.updateById(byId);
|
||||
JSONObject res = new JSONObject();
|
||||
res.put("id", id);
|
||||
res.put("type", "status");
|
||||
res.put("data", 1);
|
||||
broadcastMessage(res.toJSONString());
|
||||
}
|
||||
}
|
||||
log.info("WebSocket 连接建立成功 - id: {}, type: {}, 当前连接数: {}", id, type, activeSessions.size());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("建立连接时发生异常: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// 连接关闭后
|
||||
@Override
|
||||
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
|
||||
try {
|
||||
String id = session.getAttributes().get("id").toString();
|
||||
String type = session.getAttributes().get("type").toString();
|
||||
String sessionId = session.getId();
|
||||
|
||||
if (!Objects.equals(type, "1")) {
|
||||
activeSessions.remove(session);
|
||||
}else {
|
||||
Node byId = nodeService.getById(id);
|
||||
if (byId != null) {
|
||||
byId.setStatus(0);
|
||||
nodeService.updateById(byId);
|
||||
JSONObject res = new JSONObject();
|
||||
res.put("id", id);
|
||||
res.put("type", "status");
|
||||
res.put("data", 0);
|
||||
broadcastMessage(res.toJSONString());
|
||||
}
|
||||
}
|
||||
|
||||
// 清理session锁对象
|
||||
sessionLocks.remove(sessionId);
|
||||
|
||||
log.info("WebSocket 连接关闭 - id: {}, sessionId: {}, 关闭状态: {}, 当前连接数: {}",
|
||||
id, sessionId, status, activeSessions.size());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("关闭连接时发生异常: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// 点对点发送消息
|
||||
@SneakyThrows
|
||||
public static void sendToUser(WebSocketSession socketSession, String message) {
|
||||
if (socketSession != null && socketSession.isOpen()) {
|
||||
String sessionId = socketSession.getId();
|
||||
Object lock = sessionLocks.computeIfAbsent(sessionId, k -> new Object());
|
||||
|
||||
synchronized (lock) {
|
||||
try {
|
||||
if (socketSession.isOpen()) {
|
||||
socketSession.sendMessage(new TextMessage(message));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("发送WebSocket消息失败 [sessionId={}]: {}", sessionId, e.getMessage());
|
||||
activeSessions.remove(socketSession);
|
||||
sessionLocks.remove(sessionId);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
activeSessions.remove(socketSession);
|
||||
if (socketSession != null) {
|
||||
sessionLocks.remove(socketSession.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 广播消息
|
||||
public static void broadcastMessage(String message) {
|
||||
for (WebSocketSession session : activeSessions) {
|
||||
sendToUser(session, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user