最新产品
This commit is contained in:
66
src/main/java/com/yj/earth/common/util/AesEncryptUtil.java
Normal file
66
src/main/java/com/yj/earth/common/util/AesEncryptUtil.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
56
src/main/java/com/yj/earth/common/util/ApiResponse.java
Normal file
56
src/main/java/com/yj/earth/common/util/ApiResponse.java
Normal 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;
|
||||
}
|
||||
}
|
||||
86
src/main/java/com/yj/earth/common/util/CodeUtil.java
Normal file
86
src/main/java/com/yj/earth/common/util/CodeUtil.java
Normal 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();
|
||||
}
|
||||
}
|
||||
180
src/main/java/com/yj/earth/common/util/HttpUtil.java
Normal file
180
src/main/java/com/yj/earth/common/util/HttpUtil.java
Normal 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;
|
||||
}
|
||||
}
|
||||
50
src/main/java/com/yj/earth/common/util/JsonMapConverter.java
Normal file
50
src/main/java/com/yj/earth/common/util/JsonMapConverter.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
116
src/main/java/com/yj/earth/common/util/MapUtil.java
Normal file
116
src/main/java/com/yj/earth/common/util/MapUtil.java
Normal 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);
|
||||
}
|
||||
}
|
||||
114
src/main/java/com/yj/earth/common/util/PortKillUtil.java
Normal file
114
src/main/java/com/yj/earth/common/util/PortKillUtil.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
33
src/main/java/com/yj/earth/common/util/PositionUtil.java
Normal file
33
src/main/java/com/yj/earth/common/util/PositionUtil.java
Normal 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};
|
||||
}
|
||||
}
|
||||
126
src/main/java/com/yj/earth/common/util/SdkUtil.java
Normal file
126
src/main/java/com/yj/earth/common/util/SdkUtil.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user