最新产品

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,26 @@
package com.yj.earth.common.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import java.util.Collections;
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.setAllowedOriginPatterns(Collections.singletonList("*"));
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}

View File

@ -0,0 +1,25 @@
package com.yj.earth.common.config;
import com.yj.earth.common.exception.UnAuthException;
import com.yj.earth.common.util.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ApiResponse handleException(Exception e) {
if (!e.getMessage().contains("No static resource")) {
log.error("全局异常处理:{}", e.getMessage());
}
return ApiResponse.failure(e.getMessage());
}
@ExceptionHandler(UnAuthException.class)
public ApiResponse handleUnAuthException(UnAuthException e) {
return ApiResponse.failureWithNoAuth(e.getMessage());
}
}

View File

@ -0,0 +1,15 @@
package com.yj.earth.common.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
@Data
@Component
@ConfigurationProperties(prefix = "graphhopper")
public class GraphHopperProperties {
private String graphLocation;
private List<String> profiles;
}

View File

@ -0,0 +1,42 @@
package com.yj.earth.common.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.yj.earth.annotation.ExcludeField;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* Jackson配置类
*/
@Configuration
public class JacksonConfig {
// 定义日期时间格式
private static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
@Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
// 注册 JavaTimeModule 以支持 LocalDateTime 等日期类型
JavaTimeModule javaTimeModule = new JavaTimeModule();
// 配置 LocalDateTime 的序列化格式
LocalDateTimeSerializer localDateTimeSerializer = new LocalDateTimeSerializer(
DateTimeFormatter.ofPattern(DATE_TIME_FORMAT)
);
javaTimeModule.addSerializer(LocalDateTime.class, localDateTimeSerializer);
objectMapper.registerModule(javaTimeModule);
// 配置自定义字段过滤器
SimpleFilterProvider filterProvider = new SimpleFilterProvider();
filterProvider.addFilter("excludeFieldFilter", new ExcludeField.Filter());
// 设置默认过滤器、防止未添加@JsonFilter的类报错
filterProvider.setDefaultFilter(com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter.serializeAll());
objectMapper.setFilterProvider(filterProvider);
return objectMapper;
}
}

View File

@ -0,0 +1,32 @@
package com.yj.earth.common.config;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
@Configuration
@EnableKnife4j
public class Knife4jConfig {
/**
* 自定义Swagger3文档信息
*/
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
// 文档基本信息
.info(new Info()
.title("最新产品API文档")
.description("远界大数据最新产品API文档【默认账号:admin、密码:admin123】")
.version("v1.0.0")
.contact(new Contact()
.name("周志雄"))
.license(new License()
.name("Apache 2.0")
.url("https://www.apache.org/licenses/LICENSE-2.0.html")));
}
}

View File

@ -0,0 +1,37 @@
package com.yj.earth.common.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Component
public class MyMetaObjectConfig implements MetaObjectHandler {
// 插入时自动填充
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createdAt", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updatedAt", LocalDateTime.class, LocalDateTime.now());
}
// 更新时自动填充
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updatedAt", LocalDateTime.class, LocalDateTime.now());
}
// 配置分页拦截器
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页拦截器
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}

View File

@ -0,0 +1,41 @@
package com.yj.earth.common.config;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.stp.StpUtil;
import com.yj.earth.common.exception.UnAuthException;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class SaTokenConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
List<String> excludePathPatterns = new ArrayList<>();
excludePathPatterns.add("/user/login");
excludePathPatterns.add("/user/add");
excludePathPatterns.add("/doc.html");
excludePathPatterns.add("/webjars/**");
excludePathPatterns.add("/v3/api-docs/**");
excludePathPatterns.add("/fileInfo/download/**");
excludePathPatterns.add("/fileInfo/preview/**");
excludePathPatterns.add("/data/clt/**");
excludePathPatterns.add("/data/mbtiles/**");
excludePathPatterns.add("/data/pak/**");
// 注册 Sa-Token 拦截器
registry.addInterceptor(new SaInterceptor(handle -> {
// 登录校验
try {
StpUtil.checkLogin();
} catch (Exception e) {
throw new UnAuthException("未携带登录凭证");
}
})).addPathPatterns("/**")
.excludePathPatterns(excludePathPatterns);
}
}

View File

@ -0,0 +1,18 @@
package com.yj.earth.common.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Data
@Component
public class ServerConfig {
@Value("${server.port}")
private int port;
@Value("${server.host}")
private String host;
@Value("${sdk.port}")
private int sdkPort;
}

View File

@ -0,0 +1,29 @@
package com.yj.earth.common.config;
import com.yj.earth.annotation.SourceType;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import java.util.HashSet;
import java.util.Set;
@Configuration
public class SourceTypeConfig {
private static final String PACKAGE = "com.yj.earth.params";
@Bean
public Set<Class<?>> sourceParamClasses() throws ClassNotFoundException {
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(SourceType.class));
Set<Class<?>> classes = new HashSet<>();
for (var beanDefinition : scanner.findCandidateComponents(PACKAGE)) {
classes.add(Class.forName(beanDefinition.getBeanClassName()));
}
return classes;
}
}

View File

@ -0,0 +1,14 @@
package com.yj.earth.common.constant;
public class GlobalConstant {
// 目录类型
public static final String DIRECTORY = "directory";
// 显示
public static final Integer SHOW = 1;
// 隐藏
public static final Integer HIDE = 0;
// SDK路径
public static final String SDKPATH = "sdk/geographysdk.jar";
// SDK日志路径
public static final String SDKLOG = "logs/sdk.log";
}

View File

@ -0,0 +1,170 @@
package com.yj.earth.common.core;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* 封装一个 StringRedisTemplate 的功能
*/
@Component
public class MapRedisTemplate {
// 底层存储结构、使用HashMap模拟Redis
private final Map<String, String> storage = new HashMap<>();
// 用于存储过期时间
private final Map<String, Long> expirationMap = new HashMap<>();
/**
* 获取操作字符串的接口
*/
public ValueOperations opsForValue() {
return new ValueOperations();
}
/**
* 删除指定的键
* @param key 要删除的键
* @return 是否删除成功
*/
public Boolean delete(String key) {
checkExpiration(key);
return storage.remove(key) != null;
}
/**
* 检查键是否存在
* @param key 要检查的键
* @return 键是否存在
*/
public Boolean hasKey(String key) {
checkExpiration(key);
return storage.containsKey(key);
}
/**
* 设置键的过期时间
* @param key 键
* @param timeout 过期时间
* @param unit 时间单位
* @return 是否设置成功
*/
public Boolean expire(String key, long timeout, TimeUnit unit) {
if (!storage.containsKey(key)) {
return false;
}
long expirationTime = System.currentTimeMillis() + unit.toMillis(timeout);
expirationMap.put(key, expirationTime);
return true;
}
/**
* 获取键的剩余过期时间
* @param key 键
* @param unit 时间单位
* @return 剩余过期时间
*/
public Long getExpire(String key, TimeUnit unit) {
checkExpiration(key);
if (!expirationMap.containsKey(key)) {
return -1L; // 永久有效
}
long remainingMillis = expirationMap.get(key) - System.currentTimeMillis();
if (remainingMillis <= 0) {
delete(key);
return 0L;
}
return unit.convert(remainingMillis, TimeUnit.MILLISECONDS);
}
/**
* 检查键是否过期、如果过期则删除
*/
private void checkExpiration(String key) {
if (expirationMap.containsKey(key)) {
long expirationTime = expirationMap.get(key);
if (System.currentTimeMillis() > expirationTime) {
storage.remove(key);
expirationMap.remove(key);
}
}
}
/**
* 操作字符串的内部类、模拟ValueOperations
*/
public class ValueOperations {
/**
* 设置键值对
* @param key 键
* @param value 值
*/
public void set(String key, String value) {
storage.put(key, value);
// 设置值时清除过期时间、模拟Redis行为
expirationMap.remove(key);
}
/**
* 设置键值对并指定过期时间
* @param key 键
* @param value 值
* @param timeout 过期时间
* @param unit 时间单位
*/
public void set(String key, String value, long timeout, TimeUnit unit) {
storage.put(key, value);
long expirationTime = System.currentTimeMillis() + unit.toMillis(timeout);
expirationMap.put(key, expirationTime);
}
/**
* 获取键对应的值
* @param key 键
* @return 对应的值
*/
public String get(String key) {
checkExpiration(key);
return storage.get(key);
}
/**
* 如果键不存在则设置值
* @param key 键
* @param value 值
* @return 是否设置成功
*/
public Boolean setIfAbsent(String key, String value) {
checkExpiration(key);
if (!storage.containsKey(key)) {
storage.put(key, value);
return true;
}
return false;
}
/**
* 自增操作
* @param key 键
* @return 自增后的值
*/
public Long increment(String key) {
return increment(key, 1);
}
/**
* 增加指定的值
* @param key 键
* @param delta 要增加的值
* @return 增加后的值
*/
public Long increment(String key, long delta) {
checkExpiration(key);
String value = storage.get(key);
long num = value == null ? 0 : Long.parseLong(value);
num += delta;
storage.put(key, String.valueOf(num));
return num;
}
}
}

View File

@ -0,0 +1,10 @@
package com.yj.earth.common.exception;
public class UnAuthException extends RuntimeException{
/**
* 带异常信息的构造方法
*/
public UnAuthException(String message) {
super(message);
}
}

View File

@ -0,0 +1,67 @@
package com.yj.earth.common.service;
import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yj.earth.business.domain.Role;
import com.yj.earth.business.domain.Source;
import com.yj.earth.business.domain.User;
import com.yj.earth.business.service.RoleService;
import com.yj.earth.business.service.SourceService;
import com.yj.earth.business.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
@Slf4j
@Service
public class ServerInitService {
@Resource
private SourceService sourceService;
@Resource
private UserService userService;
@Resource
private RoleService roleService;
public void init() {
// 查询数据库所有需要加载的资源
List<Source> list =sourceService.list(new LambdaQueryWrapper<Source>()
.eq(Source::getSourceType, "terrain")
.or().eq(Source::getSourceType, "layer")
.or().eq(Source::getSourceType, "tileset"));
// 依次初始化
for (Source source : list) {
// 同步资源
sourceService.getDetail(source.getSourcePath(), sourceService.addAndGetSourceId(source.getSourcePath()));
log.info("初始化资源<--{}-->完成", source.getSourceName());
}
}
public void checkDefaultData() {
// 查询角色表和用户表是否有数据
if(roleService.count() == 0 && userService.count() == 0) {
log.info("初始化默认数据");
// 新增一个管理员角色
Role adminRole = new Role();
adminRole.setRoleName("管理员");
adminRole.setDescription("系统管理员");
adminRole.setIsSuper(1);
roleService.save(adminRole);
// 新增一个默认角色
Role defaultRole = new Role();
defaultRole.setRoleName("默认角色");
defaultRole.setDescription("系统默认角色");
defaultRole.setIsSuper(0);
roleService.save(defaultRole);
// 新增一个用户
User user = new User();
user.setUsername("admin");
user.setPassword(BCrypt.hashpw("admin123", BCrypt.gensalt()));
user.setNickname("管理员");
user.setRoleId(adminRole.getId());
user.setPhone("13888888888");
userService.save(user);
}
}
}

View File

@ -0,0 +1,57 @@
package com.yj.earth.common.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yj.earth.annotation.SourceType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@Component
public class SourceParamsValidator {
private final ObjectMapper objectMapper;
private final Map<String, Class<?>> sourceTypeMap = new HashMap<>();
@Autowired
public SourceParamsValidator(ObjectMapper objectMapper, Set<Class<?>> sourceParamClasses) {
this.objectMapper = objectMapper;
// 初始化资源类型与参数类的映射关系
for (Class<?> clazz : sourceParamClasses) {
SourceType annotation = clazz.getAnnotation(SourceType.class);
if (annotation != null) {
sourceTypeMap.put(annotation.value(), clazz);
}
}
}
/**
* 验证并转换参数
*/
public Object validateAndConvert(String sourceType, Map<String, Object> params) {
// 检查是否有对应的参数类
Class<?> paramClass = sourceTypeMap.get(sourceType);
if (paramClass == null) {
String message = "不支持 " + sourceType + "的资源类型";
throw new IllegalArgumentException(message);
}
// 转换并验证参数
try {
return objectMapper.convertValue(params, paramClass);
} catch (IllegalArgumentException e) {
String message = "请核对类型和参数";
throw new IllegalArgumentException(message);
}
}
/**
* 获取所有支持的资源类型
*/
public Set<String> getSupportedSourceTypes() {
return sourceTypeMap.keySet();
}
}

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";
}
}
}