最新产品

This commit is contained in:
ZZX9599
2025-09-08 17:01:50 +08:00
commit 8056245ade
119 changed files with 8281 additions and 0 deletions

View File

@ -0,0 +1,66 @@
package com.yj.earth.common.util;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
/**
* AES对称加密工具类
*/
public class AesEncryptUtil {
/**
* AES加密
* @param content 待加密内容
* @param key 密钥16位/24位/32位、对应AES-128/AES-192/AES-256
* @param algorithm 加密算法如AES/CBC/PKCS5Padding
* @return 加密后的Base64字符串
*/
public static String encrypt(String content, String key, String algorithm) {
try {
// 创建密钥
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
// 初始化加密器
Cipher cipher = Cipher.getInstance(algorithm);
// 如果是CBC模式、需要初始化向量IV与密钥同长度
if (algorithm.contains("CBC")) {
IvParameterSpec iv = new IvParameterSpec(key.substring(0, 16).getBytes(StandardCharsets.UTF_8));
cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
} else {
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
}
// 加密并转为Base64
byte[] encrypted = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception e) {
throw new RuntimeException("AES加密失败", e);
}
}
/**
* 解密方法(如果需要解密可以实现)
*/
public static String decrypt(String encryptedContent, String key, String algorithm) {
try {
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
Cipher cipher = Cipher.getInstance(algorithm);
if (algorithm.contains("CBC")) {
IvParameterSpec iv = new IvParameterSpec(key.substring(0, 16).getBytes(StandardCharsets.UTF_8));
cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
} else {
cipher.init(Cipher.DECRYPT_MODE, secretKey);
}
byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encryptedContent));
return new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException("AES解密失败", e);
}
}
}

View File

@ -0,0 +1,56 @@
package com.yj.earth.common.util;
import lombok.Data;
@Data
public class ApiResponse<T> {
private int code; // 状态码
private T data; // 响应数据
private String message; // 响应消息
// 私有化构造方法
private ApiResponse(int code, T data, String message) {
this.code = code;
this.data = data;
this.message = message;
}
// 成功响应(带数据)
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, data, "操作成功");
}
// 成功响应(无数据)
public static <T> ApiResponse<T> successWithMessage(String message) {
return new ApiResponse<>(200, null, message);
}
// 失败响应(带自定义消息)
public static <T> ApiResponse<T> failure(String message) {
return new ApiResponse<>(20000, null, message);
}
// 失败响应(未授权)
public static <T> ApiResponse<T> failureWithNoAuth(String message) {
return new ApiResponse<>(401, null, message);
}
// 设置 data 字段、并返回当前对象、支持链式调用
public ApiResponse<T> setData(T data) {
this.data = data;
return this;
}
// 设置 message 字段、并返回当前对象、支持链式调用
public ApiResponse<T> setMessage(String message) {
this.message = message;
return this;
}
// 设置 code 字段、并返回当前对象、支持链式调用
public ApiResponse<T> setCode(int code) {
this.code = code;
return this;
}
}

View File

@ -0,0 +1,86 @@
package com.yj.earth.common.util;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.fill.Column;
import com.yj.earth.datasource.DatabaseManager;
import java.io.File;
import java.util.Collections;
public class CodeUtil {
// SQLite数据库配置
private static String databasePath = null;
private static String author = "周志雄";
public static void main(String[] args) {
DatabaseManager.initDatabase(DatabaseManager.DatabaseType.SQLITE);
databasePath = DatabaseManager.getSqliteDbFilePath();
// 检查数据库路径是否有效
if (databasePath == null || databasePath.trim().isEmpty()) {
throw new RuntimeException("数据库路径未正确初始化");
}
// 确保数据库目录存在
File dbFile = new File(databasePath);
File parentDir = dbFile.getParentFile();
if (!parentDir.exists()) {
parentDir.mkdirs();
}
// 传入需要生成代码的表名
Generation("file_info");
}
public static void Generation(String... tableName) {
// 构建SQLite连接URL
String jdbcUrl = "jdbc:sqlite:" + databasePath;
// FastAutoGenerator 用来创建代码生成器实例
FastAutoGenerator.create(jdbcUrl, "", "")
.globalConfig(builder -> {
builder.author(author)
.enableSpringdoc()
.outputDir(System.getProperty("user.dir") + "/src/main/java");
}).packageConfig(builder -> {
builder.entity("domain")
.parent("com.yj.earth.business")
.controller("controller")
.mapper("mapper")
.service("service")
.serviceImpl("service.impl")
.pathInfo(Collections.singletonMap(OutputFile.xml,
System.getProperty("user.dir") + "/src/main/resources/mapper"));
}).strategyConfig(builder -> {
builder.addInclude(tableName)
.addTablePrefix("t_")
.entityBuilder()
.enableLombok()
.enableChainModel()
.addTableFills(new Column("created_at", FieldFill.INSERT),
new Column("updated_at", FieldFill.UPDATE))
.naming(NamingStrategy.underline_to_camel)
.columnNaming(NamingStrategy.underline_to_camel)
.idType(IdType.ASSIGN_UUID)
.formatFileName("%s")
.mapperBuilder()
.enableMapperAnnotation()
.enableBaseResultMap()
.enableBaseColumnList()
.formatMapperFileName("%sMapper")
.formatXmlFileName("%sMapper")
.serviceBuilder()
.formatServiceFileName("%sService")
.formatServiceImplFileName("%sServiceImpl")
.controllerBuilder()
.enableRestStyle()
.formatFileName("%sController")
.enableHyphenStyle();
}).execute();
}
}

View File

@ -0,0 +1,180 @@
package com.yj.earth.common.util;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
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.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.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* HTTP 请求工具类
*/
@Slf4j
public class HttpUtil {
// 编码格式
private static final String CHARSET = "UTF-8";
// 连接超时时间 5 秒
private static final int CONNECT_TIMEOUT = 5000;
// 读取超时时间 10 秒
private static final int READ_TIMEOUT = 10000;
// JSON 处理器
private static final ObjectMapper objectMapper = new ObjectMapper();
/**
* 发送 GET 请求
*/
public static String doGet(String url) {
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpGet httpGet = new HttpGet(url);
// 设置超时配置
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(CONNECT_TIMEOUT)
.setSocketTimeout(READ_TIMEOUT)
.build();
httpGet.setConfig(requestConfig);
try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
return handleResponse(response);
}
} catch (Exception e) {
log.error("GET 请求发生异常、请求 URL: {}", url, e);
return null;
}
}
/**
* 发送表单参数的 POST 请求
* 参数类型改为 Map<String, Object> 以支持更多类型的参数值
*/
public static String doPostForm(String url, Map<String, Object> params) {
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpPost httpPost = new HttpPost(url);
// 设置超时配置
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(CONNECT_TIMEOUT)
.setSocketTimeout(READ_TIMEOUT)
.build();
httpPost.setConfig(requestConfig);
// 组装表单参数
if (params != null && !params.isEmpty()) {
List<NameValuePair> nameValuePairs = new ArrayList<>();
for (Map.Entry<String, Object> entry : params.entrySet()) {
// 将 Object 类型的值转换为字符串
String value = entry.getValue() != null ? entry.getValue().toString() : null;
nameValuePairs.add(new BasicNameValuePair(entry.getKey(), value));
}
httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, CHARSET));
}
// 执行请求
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
return handleResponse(response);
}
} catch (Exception e) {
log.error("表单 POST 请求发生异常、请求 URL: {}、请求参数: {}", url, params, e);
return null;
}
}
/**
* 发送 JSON 参数的 POST 请求
* 参数类型统一为 Map<String, Object>
*/
public static String doPostJson(String url, Map<String, Object> params) {
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpPost httpPost = new HttpPost(url);
// 设置超时配置
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(CONNECT_TIMEOUT)
.setSocketTimeout(READ_TIMEOUT)
.build();
httpPost.setConfig(requestConfig);
// 设置 JSON 请求头
httpPost.setHeader("Content-Type", "application/json;charset=" + CHARSET);
// Map 转 JSON 字符串并设置为请求体
if (params != null && !params.isEmpty()) {
String jsonParams = objectMapper.writeValueAsString(params);
httpPost.setEntity(new StringEntity(jsonParams, CHARSET));
}
// 执行请求
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
return handleResponse(response);
}
} catch (JsonProcessingException e) {
log.error("JSON POST 请求参数序列化失败、请求 URL: {}、请求参数: {}", url, params, e);
} catch (Exception e) {
log.error("JSON POST 请求发生异常、请求 URL: {}、请求参数: {}", url, params, e);
}
return null;
}
/**
* 发送 GET 请求、返回字节数组的ResponseEntity、适用于下载文件
*/
public static ResponseEntity<byte[]> doGetForByteArrayResponse(String url) {
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpGet httpGet = new HttpGet(url);
// 设置超时配置
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(CONNECT_TIMEOUT)
.setSocketTimeout(READ_TIMEOUT)
.build();
httpGet.setConfig(requestConfig);
try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
// 获取状态码
int statusCode = response.getStatusLine().getStatusCode();
HttpStatus httpStatus = HttpStatus.valueOf(statusCode);
// 处理响应头
HttpHeaders headers = new HttpHeaders();
Header[] allHeaders = response.getAllHeaders();
for (Header header : allHeaders) {
// 特别保留Content-Disposition和Content-Type、用于前端下载
headers.add(header.getName(), header.getValue());
}
// 处理响应体(二进制数据)
HttpEntity entity = response.getEntity();
byte[] body = entity != null ? EntityUtils.toByteArray(entity) : null;
// 返回ResponseEntity对象
return new ResponseEntity<>(body, headers, httpStatus);
}
} catch (Exception e) {
log.error("GET 下载请求发生异常、请求 URL: {}", url, e);
// 发生异常时返回500错误
return new ResponseEntity<>(e.getMessage().getBytes(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
/**
* 通用响应处理方法
*/
private static String handleResponse(CloseableHttpResponse response) throws IOException {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
HttpEntity entity = response.getEntity();
return entity != null ? EntityUtils.toString(entity, CHARSET) : null;
}
log.warn("HTTP 请求失败、状态码: {}、响应内容: {}", statusCode, response.getEntity() != null ? EntityUtils.toString(response.getEntity(), CHARSET) : null);
return null;
}
}

View File

@ -0,0 +1,50 @@
package com.yj.earth.common.util;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.Map;
/**
* Map与JSON互相转换工具类
*/
@Slf4j
public class JsonMapConverter {
private static final ObjectMapper objectMapper = new ObjectMapper();
/**
* 将 Map 转换为 JSON 字符串
*/
public static String mapToJson(Map<String, Object> map) {
if (map == null || map.isEmpty()) {
return "{}";
}
try {
return objectMapper.writeValueAsString(map);
} catch (JsonProcessingException e) {
log.error("Map转JSON失败", e);
return null;
}
}
/**
* 将 JSON 字符串转换为 Map
*/
public static Map<String, Object> jsonToMap(String json) {
if (json == null || json.trim().isEmpty()) {
return new HashMap<>(0);
}
try {
return objectMapper.readValue(json, new TypeReference<Map<String, Object>>() {});
} catch (Exception e) {
log.error("JSON转Map失败、JSON内容: {}", json, e);
return new HashMap<>(0);
}
}
}

View File

@ -0,0 +1,116 @@
package com.yj.earth.common.util;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.HashMap;
import java.util.Map;
public class MapUtil {
private static final ObjectMapper objectMapper = new ObjectMapper();
/**
* 合并两个 Map、如果存在相同的 Key、则用新 Map 中的值替换原始 Map 中的值
* 如果原始 Map 中有某个 Key 而新 Map 中没有、则保持原始 Map 中的值不变
*/
public static <K, V> void mergeMaps(Map<K, V> originalMap, Map<K, V> newMap) {
// 检查参数是否为null、避免空指针异常
if (originalMap == null || newMap == null) {
throw new IllegalArgumentException("参数Map不能为null");
}
// 遍历新Map中的所有键值对
for (Map.Entry<K, V> entry : newMap.entrySet()) {
K key = entry.getKey();
// 如果原始Map中存在相同的key、则替换值
if (originalMap.containsKey(key)) {
originalMap.put(key, entry.getValue());
}
// 如果原始Map中不存在该key、则不做任何操作
}
}
/**
* 将JSON字符串转换为Map对象
*
* @param jsonString JSON格式的字符串
* @return 转换后的Map对象、如果JSON为空则返回空Map
* @throws IllegalArgumentException 当JSON字符串无效或解析失败时抛出
*/
public static <K, V> Map<K, V> jsonToMap(String jsonString) {
if (jsonString == null || jsonString.trim().isEmpty()) {
return new HashMap<>();
}
try {
// 将JSON字符串转换为Map
return objectMapper.readValue(jsonString, Map.class);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("JSON字符串解析失败: " + e.getMessage(), e);
}
}
/**
* 将 Map 对象转换为JSON字符串
*/
public static <K, V> String mapToString(Map<K, V> map) {
if (map == null) {
return "";
}
try {
// 将Map转换为JSON字符串
return objectMapper.writeValueAsString(map);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Map转换为JSON字符串失败: " + e.getMessage(), e);
}
}
/**
* 将任意类型对象转换为JSON字符串
*/
public static <T> String objectToJson(T object) {
if (object == null) {
return "";
}
try {
// 将对象转换为JSON字符串
return objectMapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("对象转换为JSON字符串失败: " + e.getMessage(), e);
}
}
/**
* 从Map中获取指定key的字符串值
*
* @param map 数据源Map
* @param key 要获取的字段名
* @return 字段的字符串值、如果map为null或key不存在则返回null
*/
public static String getString(Map<?, ?> map, String key) {
if (map == null || key == null) {
return null;
}
Object value = map.get(key);
return value != null ? value.toString() : null;
}
/**
* 直接从JSON字符串中获取指定key的字符串值
*
* @param jsonString JSON格式的字符串
* @param key 要获取的字段名
* @return 字段的字符串值、如果JSON为空或key不存在则返回null
*/
public static String getString(String jsonString, String key) {
if (jsonString == null || key == null) {
return null;
}
Map<?, ?> map = jsonToMap(jsonString);
return getString(map, key);
}
}

View File

@ -0,0 +1,114 @@
package com.yj.earth.common.util;
import lombok.extern.slf4j.Slf4j;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@Slf4j
public class PortKillUtil {
/**
* 根据端口号杀死对应的进程
*/
public static boolean killProcessByPort(int port) {
// 获取操作系统类型
String osName = System.getProperty("os.name").toLowerCase();
try {
if (osName.contains("windows")) {
// Windows系统处理逻辑
return killWindowsProcess(port);
} else if (osName.contains("linux") || osName.contains("unix")) {
// Linux/Unix系统处理逻辑
return killLinuxProcess(port);
} else {
log.error("不支持的操作系统: " + osName);
return false;
}
} catch (Exception e) {
log.error("杀死进程时发生错误: " + e.getMessage(), e);
return false;
}
}
/**
* 杀死 Windows 系统中占用指定端口的进程
*/
private static boolean killWindowsProcess(int port) throws IOException, InterruptedException {
// 查找占用端口的进程ID
Process process = Runtime.getRuntime().exec("netstat -ano | findstr :" + port);
process.waitFor();
// 读取命令输出
String pid = getWindowsPidFromOutput(process.getInputStream());
if (pid == null || pid.isEmpty()) {
log.error("端口 " + port + " 未被占用");
return true;
}
// 杀死找到的进程
Process killProcess = Runtime.getRuntime().exec("taskkill /F /PID " + pid);
int exitCode = killProcess.waitFor();
if (exitCode == 0) {
log.info("成功杀死端口 " + port + " 对应的进程、PID: " + pid);
return true;
} else {
log.error("杀死端口 " + port + " 对应的进程失败、PID: " + pid);
return false;
}
}
/**
* 从 Windows 命令输出中提取进程 ID
*/
private static String getWindowsPidFromOutput(InputStream inputStream) throws IOException {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
// 查找包含LISTENING状态的行
if (line.contains("LISTENING")) {
// 提取最后一个空格后的数字作为PID
String[] parts = line.split("\\s+");
return parts[parts.length - 1];
}
}
}
return null;
}
/**
* 杀死 Linux 系统中占用指定端口的进程
*/
private static boolean killLinuxProcess(int port) throws IOException, InterruptedException {
// 查找占用端口的进程ID
Process process = Runtime.getRuntime().exec("lsof -i:" + port + " -t");
process.waitFor();
// 读取命令输出获取PID
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String pid = reader.readLine();
if (pid == null || pid.isEmpty()) {
log.error("端口 " + port + " 未被占用");
return true;
}
// 杀死找到的进程
Process killProcess = Runtime.getRuntime().exec("kill -9 " + pid);
int exitCode = killProcess.waitFor();
if (exitCode == 0) {
log.info("成功杀死端口 " + port + " 对应的进程、PID: " + pid);
return true;
} else {
log.error("杀死端口 " + port + " 对应的进程失败、PID: " + pid);
return false;
}
}
}
}

View File

@ -0,0 +1,33 @@
package com.yj.earth.common.util;
public class PositionUtil {
private static final double a = 6378137.0; // 椭球长半轴
private static final double e = 0.0818191908426; // 椭球第一偏心率
private static final double epsilon = 1e-8; // 迭代精度
private static final double r2d = 180.0 / Math.PI; // 弧度转角度
public static double[] xyz2Blh(double x, double y, double z) {
double tmpX = x;
double tmpY = y;
double tmpZ = z;
double curB = 0.0;
double N = 0.0;
double calB = Math.atan2(tmpZ, Math.sqrt(tmpX * tmpX + tmpY * tmpY));
int counter = 0;
while (Math.abs(curB - calB) * r2d > epsilon && counter < 25) {
curB = calB;
N = a / Math.sqrt(1 - e * e * Math.sin(curB) * Math.sin(curB));
calB = Math.atan2(tmpZ + N * e * e * Math.sin(curB),
Math.sqrt(tmpX * tmpX + tmpY * tmpY));
counter++;
}
double longitude = Math.atan2(tmpY, tmpX) * r2d;
double latitude = curB * r2d;
double height = tmpZ / Math.sin(curB) - N * (1 - e * e);
return new double[]{longitude, latitude, height};
}
}

View File

@ -0,0 +1,126 @@
package com.yj.earth.common.util;
import com.yj.earth.common.constant.GlobalConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.yaml.snakeyaml.Yaml;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Slf4j
public class SdkUtil {
// 保存SDK进程引用
private static Process sdkProcess;
// 保存SDK端口号、用于关闭时强制终止
private static Integer sdkPort;
// 对外提供的启动入口
public static void startSdkIfConfigured() throws IOException {
// 读取配置
sdkPort = getSdkPortFromYamlConfig();
// 未配置则不启动
if (sdkPort == null) {
log.info("请先配置SDK端口");
return;
}
// 配置存在时、正常启动SDK
startSdkJar(sdkPort);
}
// 接收已确认的端口、启动SDK
private static void startSdkJar(int sdkPort) throws IOException {
// 获取项目根目录(当前工作目录)
String projectRoot = System.getProperty("user.dir");
// 获取SDK完整路径
String sdkJarPath = new File(projectRoot, GlobalConstant.SDKPATH).getAbsolutePath();
// 校验SDK
File sdkJarFile = new File(sdkJarPath);
if (!sdkJarFile.exists() || !sdkJarFile.isFile()) {
log.error("SDK不存在或不是有效文件:{}", sdkJarPath);
}
log.info("准备启动SDK: {}", sdkJarPath);
log.info("使用SDK端口: {}", sdkPort);
// 构建启动命令、添加 -Dserver.port 参数
List<String> command = new ArrayList<>();
command.add("java");
command.add("-Dserver.port=" + sdkPort);
command.add("-jar");
command.add(sdkJarPath);
// 构建进程启动器
ProcessBuilder processBuilder = new ProcessBuilder(command);
// 打印执行的命令
String commandStr = command.stream().collect(Collectors.joining(" "));
log.info("执行命令: {}", commandStr);
// 输出SDK的控制台日志到当前应用的日志中
processBuilder.redirectErrorStream(true);
// 日志文件路径建议优化: 避免与项目根目录混淆
File sdkLogFile = new File(projectRoot, GlobalConstant.SDKLOG);
// 确保目录存在(避免日志写入失败)
if (!sdkLogFile.getParentFile().exists()) {
sdkLogFile.getParentFile().mkdirs();
}
processBuilder.redirectOutput(sdkLogFile);
// 启动进程(非阻塞)
sdkProcess = processBuilder.start();
log.info("SDK已在后台启动、进程ID: {}", sdkProcess.pid());
// 注册JVM关闭钩子、在主程序退出时关闭SDK进程
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
if (sdkProcess != null && sdkProcess.isAlive()) {
log.info("主程序关闭、正在停止SDK进程PID: {}...", sdkProcess.pid());
// 销毁子进程
sdkProcess.destroy();
try {
// 等待进程终止最多5秒
boolean terminated = sdkProcess.waitFor(5, TimeUnit.SECONDS);
if (terminated) {
log.info("SDK进程已成功停止");
} else {
log.warn("SDK进程未能正常停止、尝试通过端口{}强制终止...", sdkPort);
// 通过端口强制终止
boolean killSuccess = PortKillUtil.killProcessByPort(sdkPort);
if (killSuccess) {
log.info("已通过端口{}强制终止SDK进程", sdkPort);
} else {
log.error("通过端口{}强制终止SDK进程失败", sdkPort);
}
}
} catch (InterruptedException e) {
log.error("停止SDK进程时发生中断", e);
Thread.currentThread().interrupt();
}
}
}, "SDK-Process-Shutdown-Hook"));
}
/**
* 从配置文件读取SDK端口配置
*/
private static Integer getSdkPortFromYamlConfig() {
Yaml yaml = new Yaml();
try (InputStream inputStream = new ClassPathResource("application.yml").getInputStream()) {
// 解析YAML文件为Map
Map<String, Object> yamlMap = yaml.load(inputStream);
// 逐级获取配置
if (yamlMap.containsKey("sdk")) {
Object sdkObj = yamlMap.get("sdk");
if (sdkObj instanceof Map) {
Map<?, ?> sdkMap = (Map<?, ?>) sdkObj;
if (sdkMap.containsKey("port")) {
return ((Number) sdkMap.get("port")).intValue();
}
}
}
log.error("未配置SDK端口");
} catch (IOException e) {
log.error("读取配置文件失败", e);
}
return null;
}
}

View File

@ -0,0 +1,80 @@
package com.yj.earth.common.util;
import lombok.extern.slf4j.Slf4j;
import oshi.SystemInfo;
import oshi.hardware.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.HexFormat;
/**
* 服务器唯一标识工具类
* 基于CPU、主板、磁盘、网卡硬件信息生成唯一标识、不更换核心硬件则标识不变
*/
@Slf4j
public class ServerUniqueIdUtil {
/**
* 获取服务器唯一标识
*/
public static String getServerUniqueId() {
// 初始化系统信息oshi核心入口
SystemInfo systemInfo = new SystemInfo();
HardwareAbstractionLayer hardware = systemInfo.getHardware();
try {
// 收集稳定的核心硬件信息
StringBuilder hardwareRawInfo = new StringBuilder();
// CPU唯一标识
CentralProcessor cpu = hardware.getProcessor();
String cpuId = cpu.getProcessorIdentifier().getProcessorID();
if (cpuId != null && !cpuId.trim().isEmpty()) {
hardwareRawInfo.append(cpuId).append("|");
}
// 主板UUID
ComputerSystem mainBoard = hardware.getComputerSystem();
String boardUuid = mainBoard.getHardwareUUID();
if (boardUuid != null && !boardUuid.trim().isEmpty()) {
hardwareRawInfo.append(boardUuid).append("|");
}
// 第一个物理磁盘序列号
List<HWDiskStore> disks = hardware.getDiskStores();
for (HWDiskStore disk : disks) {
// 过滤虚拟磁盘(
if (!disk.getModel().toLowerCase().contains("virtual") && disk.getSize() > 0) {
String diskSerial = disk.getSerial();
if (diskSerial != null && !diskSerial.trim().isEmpty()) {
hardwareRawInfo.append(diskSerial).append("|");
break;
}
}
}
// 第一个物理网卡MAC地址
List<NetworkIF> netCards = hardware.getNetworkIFs();
for (NetworkIF netCard : netCards) {
String mac = netCard.getMacaddr();
// 过滤条件非空、非全零MAC、非回环网卡
if (mac != null && !mac.trim().isEmpty() && !mac.startsWith("00:00:00:00:00:00")) {
hardwareRawInfo.append(mac).append("|");
break;
}
}
// MD5哈希
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] hashBytes = md.digest(hardwareRawInfo.toString().getBytes());
// 字节数组转十六进制字符串
return HexFormat.of().formatHex(hashBytes).toUpperCase();
} catch (Exception e) {
return "unknown";
}
}
}