最新产品
This commit is contained in:
26
src/main/java/com/yj/earth/common/config/CorsConfig.java
Normal file
26
src/main/java/com/yj/earth/common/config/CorsConfig.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
42
src/main/java/com/yj/earth/common/config/JacksonConfig.java
Normal file
42
src/main/java/com/yj/earth/common/config/JacksonConfig.java
Normal 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;
|
||||
}
|
||||
}
|
||||
32
src/main/java/com/yj/earth/common/config/Knife4jConfig.java
Normal file
32
src/main/java/com/yj/earth/common/config/Knife4jConfig.java
Normal 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")));
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
41
src/main/java/com/yj/earth/common/config/SaTokenConfig.java
Normal file
41
src/main/java/com/yj/earth/common/config/SaTokenConfig.java
Normal 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);
|
||||
}
|
||||
}
|
||||
18
src/main/java/com/yj/earth/common/config/ServerConfig.java
Normal file
18
src/main/java/com/yj/earth/common/config/ServerConfig.java
Normal 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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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";
|
||||
}
|
||||
170
src/main/java/com/yj/earth/common/core/MapRedisTemplate.java
Normal file
170
src/main/java/com/yj/earth/common/core/MapRedisTemplate.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package com.yj.earth.common.exception;
|
||||
|
||||
public class UnAuthException extends RuntimeException{
|
||||
/**
|
||||
* 带异常信息的构造方法
|
||||
*/
|
||||
public UnAuthException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
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