最新产品
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
	 ZZX9599
					ZZX9599