commit 8056245adeab6e4219773431a68f81f2808365d0
Author: ZZX9599 <536509593@qq.com>
Date: Mon Sep 8 17:01:50 2025 +0800
最新产品
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..27e45d9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,50 @@
+# Maven 相关忽略
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+# IntelliJ IDEA 相关忽略
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+
+# Eclipse 相关忽略
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+# NetBeans 相关忽略
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+# VS Code 相关忽略
+### VS Code ###
+.vscode/
+
+# Mac OS 系统文件忽略
+### Mac OS ###
+.DS_Store
+
+# 新增:忽略自定义的 sdk 文件夹和 upload 文件夹
+# (/ 表示忽略整个文件夹及其内部所有内容)
+sdk/
+upload/
+logs/
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..e69de29
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..4f2543a
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..5d6b03c
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100644
index 0000000..2b63946
--- /dev/null
+++ b/.idea/uiDesigner.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000..fffc8a4
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,191 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ "associatedIndex": 7
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1756088463625
+
+
+ 1756088463625
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/geo.html b/geo.html
new file mode 100644
index 0000000..dbc69f8
--- /dev/null
+++ b/geo.html
@@ -0,0 +1,768 @@
+
+
+
+
+
+ 路径规划系统
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 点击地图设起点/终点
+
+
+
+
+
+
+
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..2b1747c
--- /dev/null
+++ b/index.html
@@ -0,0 +1,438 @@
+
+
+
+
+
+ Cesium 多资源整合加载平台
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Cesium 资源加载与定位
+
+
+
+ 基础地址(服务器根地址)
+
+
+
+
+
+ 资源相对路径
+
+
+
+
+
+
+
+
+ 加载资源
+ 定位到经纬度
+ 清除所有资源
+
+
+
+
+
+
+
+
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..4d09feb
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,177 @@
+
+
+ 4.0.0
+
+
+ com.yj.earth
+ yjearth
+ 1.0.0
+
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.2.0
+
+
+
+ 17
+ 17
+ UTF-8
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+
+ com.baomidou
+ mybatis-plus-spring-boot3-starter
+ 3.5.9
+
+
+
+
+ com.baomidou
+ mybatis-plus-generator
+ 3.5.9
+
+
+
+
+ com.baomidou
+ mybatis-plus-jsqlparser
+ 3.5.9
+
+
+
+
+ org.apache.velocity
+ velocity-engine-core
+ 2.3
+
+
+
+
+ mysql
+ mysql-connector-java
+ 8.0.33
+
+
+
+
+ org.xerial
+ sqlite-jdbc
+
+
+
+
+ com.alibaba
+ druid-spring-boot-starter
+ 1.2.8
+
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+ cn.hutool
+ hutool-all
+ 5.8.20
+
+
+
+
+ com.github.xiaoymin
+ knife4j-openapi3-jakarta-spring-boot-starter
+ 4.5.0
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-websocket
+
+
+
+
+ com.github.oshi
+ oshi-core
+ 6.4.0
+
+
+
+
+ com.squareup.okhttp3
+ okhttp
+
+
+
+
+ cn.dev33
+ sa-token-spring-boot3-starter
+ 1.44.0
+
+
+
+
+ com.graphhopper
+ graphhopper-core
+ 7.0
+
+
+
+
+ org.locationtech.jts
+ jts-core
+ 1.19.0
+
+
+
+
+ org.springframework
+ spring-aop
+
+
+
+
+ org.aspectj
+ aspectjweaver
+
+
+
+
+ org.apache.httpcomponents
+ httpclient
+ 4.5.13
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+
+
+
+ yjearth
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/src/main/java/com/yj/earth/ServerApp.java b/src/main/java/com/yj/earth/ServerApp.java
new file mode 100644
index 0000000..241333d
--- /dev/null
+++ b/src/main/java/com/yj/earth/ServerApp.java
@@ -0,0 +1,54 @@
+package com.yj.earth;
+
+import com.yj.earth.business.service.SourceService;
+import com.yj.earth.common.config.ServerConfig;
+import com.yj.earth.common.service.ServerInitService;
+import com.yj.earth.common.util.SdkUtil;
+import com.yj.earth.datasource.DatabaseManager;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+
+import javax.annotation.Resource;
+import java.io.IOException;
+
+@Slf4j
+@EnableAspectJAutoProxy
+@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
+public class ServerApp implements CommandLineRunner {
+ @Resource
+ private SourceService sourceService;
+ @Resource
+ private ServerConfig serverConfig;
+ @Resource
+ private ServerInitService serverInitService;
+
+ public static void main(String[] args) throws IOException {
+ // 启动项目SDK服务
+ SdkUtil.startSdkIfConfigured();
+ // 初始化数据库相关操作
+ String activeDataSource = DatabaseManager.getActiveDataSource();
+ // 获取数据库类型
+ DatabaseManager.DatabaseType dbType = DatabaseManager.DatabaseType.valueOf(activeDataSource.toUpperCase());
+ // 初始化数据库
+ DatabaseManager.initDatabase(dbType);
+ // 启动应用服务
+ SpringApplication application = new SpringApplication(ServerApp.class);
+ // 允许循环引用
+ application.setAllowCircularReferences(true);
+ // 启动服务
+ application.run(args);
+ }
+
+ @Override
+ public void run(String... args) throws Exception {
+ // 初始化资源
+ serverInitService.init();
+ // 检查默认数据
+ serverInitService.checkDefaultData();
+ log.info("项目文档地址: {}", "http://" + serverConfig.getHost() + ":" + serverConfig.getPort() + "/doc.html");
+ }
+}
diff --git a/src/main/java/com/yj/earth/annotation/EncryptResponse.java b/src/main/java/com/yj/earth/annotation/EncryptResponse.java
new file mode 100644
index 0000000..aab99d1
--- /dev/null
+++ b/src/main/java/com/yj/earth/annotation/EncryptResponse.java
@@ -0,0 +1,22 @@
+package com.yj.earth.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 返回结果加密注解
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface EncryptResponse {
+
+ /**
+ * 加密密钥的配置键
+ */
+ String keyProperty() default "encrypt.aes.key";
+
+ /**
+ * 加密算法模式
+ */
+ String algorithm() default "AES/CBC/PKCS5Padding";
+}
diff --git a/src/main/java/com/yj/earth/annotation/ExcludeField.java b/src/main/java/com/yj/earth/annotation/ExcludeField.java
new file mode 100644
index 0000000..6298030
--- /dev/null
+++ b/src/main/java/com/yj/earth/annotation/ExcludeField.java
@@ -0,0 +1,34 @@
+package com.yj.earth.annotation;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.PropertyWriter;
+import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 用于标记需要在JSON序列化时排除的字段
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ExcludeField {
+
+ /**
+ * Jackson 序列化过滤器、用于过滤被 @ExcludeField 注解标记的字段
+ */
+ class Filter extends SimpleBeanPropertyFilter {
+ @Override
+ public void serializeAsField(Object pojo, JsonGenerator jsonGenerator, SerializerProvider provider, PropertyWriter writer) throws Exception {
+ // 检查字段是否有 @ExcludeField 注解、如果有则不序列化该字段
+ if (writer.getAnnotation(ExcludeField.class) != null) {
+ return;
+ }
+ // 没有注解的字段正常序列化
+ super.serializeAsField(pojo, jsonGenerator, provider, writer);
+ }
+ }
+}
diff --git a/src/main/java/com/yj/earth/annotation/RoleAccess.java b/src/main/java/com/yj/earth/annotation/RoleAccess.java
new file mode 100644
index 0000000..a2678e8
--- /dev/null
+++ b/src/main/java/com/yj/earth/annotation/RoleAccess.java
@@ -0,0 +1,16 @@
+package com.yj.earth.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 角色访问控制注解
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RoleAccess {
+ /**
+ * 允许访问的角色名称数组
+ */
+ String[] roleNames();
+}
diff --git a/src/main/java/com/yj/earth/annotation/SourceType.java b/src/main/java/com/yj/earth/annotation/SourceType.java
new file mode 100644
index 0000000..529d7b7
--- /dev/null
+++ b/src/main/java/com/yj/earth/annotation/SourceType.java
@@ -0,0 +1,9 @@
+package com.yj.earth.annotation;
+
+import java.lang.annotation.*;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface SourceType {
+ String value();
+}
diff --git a/src/main/java/com/yj/earth/aspect/EncryptResponseAspect.java b/src/main/java/com/yj/earth/aspect/EncryptResponseAspect.java
new file mode 100644
index 0000000..4e59601
--- /dev/null
+++ b/src/main/java/com/yj/earth/aspect/EncryptResponseAspect.java
@@ -0,0 +1,64 @@
+package com.yj.earth.aspect;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.yj.earth.annotation.EncryptResponse;
+import com.yj.earth.common.util.AesEncryptUtil;
+import com.yj.earth.common.util.ApiResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.springframework.core.env.Environment;
+import org.springframework.stereotype.Component;
+
+/**
+ * 响应加密切面: 拦截 @EncryptResponse 注解的方法、对返回结果进行加密
+ */
+@Slf4j
+@Aspect
+@Component
+@RequiredArgsConstructor
+public class EncryptResponseAspect {
+
+ private final ObjectMapper objectMapper;
+ private final Environment environment;
+
+ /**
+ * 定义切点: 拦截所有被 @EncryptResponse 标记的方法
+ */
+ @Pointcut("@annotation(encryptResponse)")
+ public void pointCut(EncryptResponse encryptResponse) {}
+
+ /**
+ * 环绕通知: 对方法返回结果进行加密处理
+ */
+ @Around("pointCut(encryptResponse)")
+ public Object around(ProceedingJoinPoint joinPoint, EncryptResponse encryptResponse) throws Throwable {
+ // 执行原方法、获取返回结果
+ Object result = joinPoint.proceed();
+
+ // 从配置文件获取密钥
+ String key = environment.getProperty(encryptResponse.keyProperty());
+ if (key == null || key.isEmpty()) {
+ log.error("加密密钥未配置、keyProperty: {}", encryptResponse.keyProperty());
+ throw new RuntimeException("加密密钥未配置");
+ }
+
+ // 将返回结果转为JSON字符串
+ String jsonResult;
+ try {
+ jsonResult = objectMapper.writeValueAsString(result);
+ } catch (JsonProcessingException e) {
+ log.error("返回结果转JSON失败", e);
+ throw new RuntimeException("返回结果序列化失败");
+ }
+
+ // 执行加密
+ String encryptedResult = AesEncryptUtil.encrypt(jsonResult, key, encryptResponse.algorithm());
+ log.debug("接口返回结果已加密、原始长度: {}、加密后长度: {}", jsonResult.length(), encryptedResult.length());
+ return ApiResponse.success(encryptedResult);
+ }
+}
diff --git a/src/main/java/com/yj/earth/aspect/RoleAccessAspect.java b/src/main/java/com/yj/earth/aspect/RoleAccessAspect.java
new file mode 100644
index 0000000..ec40073
--- /dev/null
+++ b/src/main/java/com/yj/earth/aspect/RoleAccessAspect.java
@@ -0,0 +1,78 @@
+package com.yj.earth.aspect;
+
+import cn.dev33.satoken.stp.StpUtil;
+import com.yj.earth.annotation.RoleAccess;
+import com.yj.earth.business.domain.User;
+import com.yj.earth.business.domain.Role;
+import com.yj.earth.business.service.UserService;
+import com.yj.earth.business.service.RoleService;
+import com.yj.earth.common.util.ApiResponse;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.lang.reflect.Method;
+
+/**
+ * 角色访问控制切面
+ */
+@Aspect
+@Component
+public class RoleAccessAspect {
+
+ @Resource
+ private UserService userService;
+
+ @Resource
+ private RoleService roleService;
+
+ /**
+ * 环绕通知、验证角色权限
+ */
+ @Around("@annotation(com.yj.earth.annotation.RoleAccess)")
+ public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
+ // 获取当前登录用户ID
+ if (!StpUtil.isLogin()) {
+ return ApiResponse.failure("请先登录");
+ }
+ String userId = StpUtil.getLoginIdAsString();
+
+ // 获取用户信息
+ User user = userService.getById(userId);
+ if (user == null) {
+ return ApiResponse.failure("用户不存在");
+ }
+
+ // 获取用户角色信息
+ Role role = roleService.getById(user.getRoleId());
+ if (role == null) {
+ return ApiResponse.failure("用户角色不存在");
+ }
+
+ // 获取注解信息
+ MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+ Method method = signature.getMethod();
+ RoleAccess roleAccess = method.getAnnotation(RoleAccess.class);
+ String[] allowedRoles = roleAccess.roleNames();
+
+ // 验证角色是否有权限
+ boolean hasPermission = false;
+ for (String roleName : allowedRoles) {
+ if (roleName.equals(role.getRoleName())) {
+ hasPermission = true;
+ break;
+ }
+ }
+
+ if (!hasPermission) {
+ return ApiResponse.failure("没有访问权限、需要角色: " + String.join(",", allowedRoles));
+ }
+
+ // 有权限、执行原方法
+ return joinPoint.proceed();
+ }
+}
diff --git a/src/main/java/com/yj/earth/auth/AuthGenerator.java b/src/main/java/com/yj/earth/auth/AuthGenerator.java
new file mode 100644
index 0000000..b77d911
--- /dev/null
+++ b/src/main/java/com/yj/earth/auth/AuthGenerator.java
@@ -0,0 +1,74 @@
+package com.yj.earth.auth;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.yj.earth.common.util.ServerUniqueIdUtil;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * 授权生成工具类
+ */
+public class AuthGenerator {
+ // AES加密密钥(16位)
+ private static final String AES_KEY = "7AJD6H5AGHY6SJU7";
+
+ // Jackson JSON处理器
+ private static final ObjectMapper objectMapper = new ObjectMapper();
+
+ /**
+ * 生成授权信息
+ *
+ * @param versionType 版本类型
+ * @param maxResourceCount 最大资源数量
+ * @param authDays 授权天数
+ * @param serverHardwareMd5 服务器硬件信息MD5
+ * @return 加密后的授权字符串
+ */
+ public static String generateAuth(String versionType, int maxResourceCount, int authDays, String serverHardwareMd5) {
+ // 创建授权信息对象
+ AuthInfo authInfo = new AuthInfo();
+ authInfo.setVersionType(versionType);
+ authInfo.setMaxResourceCount(maxResourceCount);
+ authInfo.setAuthDays(authDays);
+ authInfo.setServerHardwareMd5(serverHardwareMd5);
+
+ // 设置生成时间和过期时间
+ Date now = new Date();
+ authInfo.setGenerateTime(now);
+
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(now);
+ calendar.add(Calendar.DAY_OF_YEAR, authDays);
+ authInfo.setExpireTime(calendar.getTime());
+
+ // 序列化为JSON并加密
+ String jsonStr = null;
+ try {
+ jsonStr = objectMapper.writeValueAsString(authInfo);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ return encrypt(jsonStr);
+ }
+
+ /**
+ * AES加密
+ */
+ private static String encrypt(String content) {
+ try {
+ SecretKeySpec keySpec = new SecretKeySpec(AES_KEY.getBytes(StandardCharsets.UTF_8), "AES");
+ Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
+ cipher.init(Cipher.ENCRYPT_MODE, keySpec);
+ byte[] encrypted = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
+ return Base64.getEncoder().encodeToString(encrypted);
+ } catch (Exception e) {
+ throw new RuntimeException("授权信息加密失败", e);
+ }
+ }
+}
diff --git a/src/main/java/com/yj/earth/auth/AuthInfo.java b/src/main/java/com/yj/earth/auth/AuthInfo.java
new file mode 100644
index 0000000..c9b278a
--- /dev/null
+++ b/src/main/java/com/yj/earth/auth/AuthInfo.java
@@ -0,0 +1,32 @@
+package com.yj.earth.auth;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import java.util.Date;
+
+/**
+ * 授权信息模型类
+ * 存储授权相关的所有信息
+ */
+@Data
+public class AuthInfo {
+ // 版本类型
+ private String versionType;
+
+ // 最大可加载资源数量
+ private int maxResourceCount;
+
+ // 授权天数
+ private int authDays;
+
+ // 服务器硬件信息MD5
+ private String serverHardwareMd5;
+
+ // 授权生成时间
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ private Date generateTime;
+
+ // 授权过期时间
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ private Date expireTime;
+}
diff --git a/src/main/java/com/yj/earth/auth/AuthValidator.java b/src/main/java/com/yj/earth/auth/AuthValidator.java
new file mode 100644
index 0000000..ebbcfcf
--- /dev/null
+++ b/src/main/java/com/yj/earth/auth/AuthValidator.java
@@ -0,0 +1,115 @@
+package com.yj.earth.auth;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.yj.earth.common.util.ServerUniqueIdUtil;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.Date;
+
+/**
+ * 授权校验工具类
+ */
+public class AuthValidator {
+ // 与生成工具类使用相同的AES密钥
+ private static final String AES_KEY = "7AJD6H5AGHY6SJU7";
+
+ // Jackson JSON处理器
+ private static final ObjectMapper objectMapper = new ObjectMapper();
+
+ /**
+ * 校验授权是否有效
+ *
+ * @param authString 加密的授权字符串
+ * @param currentServerHardwareMd5 当前服务器硬件信息MD5
+ * @return 授权是否有效的布尔值
+ */
+ public static boolean validateAuth(String authString, String currentServerHardwareMd5) {
+ try {
+ // 解密授权信息
+ AuthInfo authInfo = getAuthInfo(authString);
+
+ // 校验服务器硬件信息是否匹配
+ if (!authInfo.getServerHardwareMd5().equals(currentServerHardwareMd5)) {
+ return false;
+ }
+
+ // 校验授权是否过期
+ Date now = new Date();
+ return now.before(authInfo.getExpireTime());
+ } catch (Exception e) {
+ // 解密失败或格式错误均视为无效授权
+ return false;
+ }
+ }
+
+ /**
+ * 获取授权详情
+ *
+ * @param authString 加密的授权字符串
+ * @return 授权信息对象
+ * @throws Exception 解密失败或格式错误时抛出异常
+ */
+ public static AuthInfo getAuthInfo(String authString) {
+ try {
+ // 解密
+ String decrypted = decrypt(authString);
+ // 转换为 AuthInfo 对象
+ return objectMapper.readValue(decrypted, AuthInfo.class);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * AES解密
+ */
+ private static String decrypt(String encryptedContent) throws Exception {
+ SecretKeySpec keySpec = new SecretKeySpec(AES_KEY.getBytes(StandardCharsets.UTF_8), "AES");
+ Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
+ cipher.init(Cipher.DECRYPT_MODE, keySpec);
+ byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encryptedContent));
+ return new String(decrypted, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * 获取授权剩余天数
+ */
+ public static int getRemainingDays(String authString){
+ try {
+ AuthInfo authInfo = getAuthInfo(authString);
+ Date now = new Date();
+
+ // 如果已过期、返回0
+ if (now.after(authInfo.getExpireTime())) {
+ return 0;
+ }
+
+ // 计算剩余天数
+ long remainingMillis = authInfo.getExpireTime().getTime() - now.getTime();
+ return (int) (remainingMillis / (24 * 60 * 60 * 1000)) + 1;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ public static void main(String[] args) {
+ String serverUniqueId = ServerUniqueIdUtil.getServerUniqueId();
+ // 生成授权
+ String authString = AuthGenerator.generateAuth("标准版", 1000, 30, serverUniqueId);
+ System.out.println("授权字符串:" + authString);
+ // 验证授权
+ boolean isValid = AuthValidator.validateAuth(authString, serverUniqueId);
+ System.out.println("授权是否有效:" + isValid);
+
+ int remainingDays = AuthValidator.getRemainingDays(authString);
+ System.out.println("剩余天数:" + remainingDays);
+
+ AuthInfo authInfo = AuthValidator.getAuthInfo(authString);
+ System.out.println("授权信息:" + authInfo);
+ }
+}
diff --git a/src/main/java/com/yj/earth/business/controller/AuthController.java b/src/main/java/com/yj/earth/business/controller/AuthController.java
new file mode 100644
index 0000000..f3e96b7
--- /dev/null
+++ b/src/main/java/com/yj/earth/business/controller/AuthController.java
@@ -0,0 +1,86 @@
+package com.yj.earth.business.controller;
+
+import com.yj.earth.auth.AuthInfo;
+import com.yj.earth.auth.AuthValidator;
+import com.yj.earth.common.util.ApiResponse;
+import com.yj.earth.common.util.ServerUniqueIdUtil;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+@Tag(name = "系统设置管理")
+@RestController
+@RequestMapping("/auth")
+public class AuthController {
+
+ // 授权文件存储路径、项目根目录下的license目录
+ private static final String AUTH_FILE_PATH = "license/yjearth.lic";
+
+ @GetMapping("/info")
+ @Operation(summary = "获取系统授权码")
+ public ApiResponse info() {
+ return ApiResponse.success(ServerUniqueIdUtil.getServerUniqueId());
+ }
+
+ @PostMapping("/import")
+ @Operation(summary = "导入授权信息")
+ public ApiResponse importAuth(@Parameter(description = "授权文件", required = true) @RequestParam("file") MultipartFile file) {
+ // 验证文件是否为空
+ if (file.isEmpty()) {
+ return ApiResponse.failure("请选择授权文件");
+ }
+
+ // 验证文件名是否为 yjearth.lic
+ String fileName = file.getOriginalFilename();
+ if (fileName == null || !fileName.equals("yjearth.lic")) {
+ return ApiResponse.failure("请上传 yjearth.lic");
+ }
+
+ try {
+ // 读取文件内容
+ String authContent = new String(file.getBytes(), StandardCharsets.UTF_8).trim();
+ // 验证授权内容有效性
+ String serverHardwareMd5 = ServerUniqueIdUtil.getServerUniqueId();
+ boolean isValid = AuthValidator.validateAuth(authContent, serverHardwareMd5);
+ if (!isValid) {
+ return ApiResponse.failure("授权文件无效或已过期");
+ }
+ // 创建目录(如果不存在)
+ Path path = Paths.get(AUTH_FILE_PATH);
+ if (!Files.exists(path.getParent())) {
+ Files.createDirectories(path.getParent());
+ }
+ // 保存授权文件
+ Files.write(path, authContent.getBytes(StandardCharsets.UTF_8));
+ return ApiResponse.success(null);
+ } catch (Exception e) {
+ return ApiResponse.failure("导入授权文件失败:" + e.getMessage());
+ }
+ }
+
+ @GetMapping("/show")
+ @Operation(summary = "查看授权信息")
+ public ApiResponse showAuth() {
+ try {
+ // 检查授权文件是否存在
+ Path path = Paths.get(AUTH_FILE_PATH);
+ if (!Files.exists(path)) {
+ return ApiResponse.failure("请先导入授权");
+ }
+ // 读取授权文件内容
+ String authContent = new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
+ // 获取授权详情
+ AuthInfo authInfo = AuthValidator.getAuthInfo(authContent);
+ return ApiResponse.success(authInfo);
+ } catch (Exception e) {
+ return ApiResponse.failure("获取授权信息失败:" + e.getMessage());
+ }
+ }
+}
diff --git a/src/main/java/com/yj/earth/business/controller/FileInfoController.java b/src/main/java/com/yj/earth/business/controller/FileInfoController.java
new file mode 100644
index 0000000..fcf9a40
--- /dev/null
+++ b/src/main/java/com/yj/earth/business/controller/FileInfoController.java
@@ -0,0 +1,241 @@
+package com.yj.earth.business.controller;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.crypto.digest.DigestUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.yj.earth.business.domain.FileInfo;
+import com.yj.earth.business.service.FileInfoService;
+import com.yj.earth.common.util.ApiResponse;
+import com.yj.earth.vo.FileInfoVo;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpHeaders;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Tag(name = "文件数据管理")
+@RestController
+@RequestMapping("/fileInfo")
+public class FileInfoController {
+
+ @Resource
+ private FileInfoService fileInfoService;
+
+ @Value("${file.upload.path}")
+ private String uploadPath;
+
+ // 获取项目根目录
+ private String getProjectRootPath() {
+ return System.getProperty("user.dir");
+ }
+
+ // 获取完整的上传目录路径
+ private String getFullUploadPath() {
+ // 拼接项目根目录和配置的上传路径
+ return getProjectRootPath() + File.separator + uploadPath;
+ }
+
+ @Operation(summary = "文件上传")
+ @PostMapping("/upload")
+ public ApiResponse uploadFiles(@Parameter(description = "上传的文件数组", required = true) @RequestParam("files") MultipartFile[] files) throws IOException {
+
+ // 校验文件数组是否为空
+ if (files == null || files.length == 0) {
+ return ApiResponse.failure("上传文件不能为空");
+ }
+
+ // 获取完整的上传目录路径
+ String fullUploadPath = getFullUploadPath();
+
+ List fileInfoVoList = new ArrayList<>();
+
+ // 遍历处理每个文件
+ for (MultipartFile file : files) {
+ // 跳过空文件
+ if (file.isEmpty()) {
+ continue;
+ }
+
+ // 获取原始文件名和后缀
+ String originalFilename = file.getOriginalFilename();
+ String fileSuffix = FileUtil.extName(originalFilename);
+ String contentType = file.getContentType();
+
+ // 生成唯一文件名、避免重复
+ String uniqueFileName = IdUtil.simpleUUID() + "." + fileSuffix;
+
+ // 创建文件存储目录(如果不存在则自动创建)
+ File uploadDir = new File(fullUploadPath);
+ FileUtil.mkdir(uploadDir);
+
+ // 构建完整文件路径
+ String filePath = fullUploadPath + File.separator + uniqueFileName;
+ File destFile = new File(filePath);
+
+ // 使用Hutool工具类保存文件
+ FileUtil.writeBytes(file.getBytes(), destFile);
+
+ // 计算文件MD5(用于校验文件完整性)
+ String fileMd5 = DigestUtil.md5Hex(destFile);
+
+ // 查询有没有文件名一样并且 MD5 也一样的数据
+ LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
+ queryWrapper.eq(FileInfo::getFileName, originalFilename).eq(FileInfo::getFileMd5, fileMd5);
+ if (fileInfoService.count(queryWrapper) > 0) { // 修复了此处的bug、添加了queryWrapper参数
+ return ApiResponse.failure("已存在文件名相同且内容完全一致的文件");
+ }
+
+ // 保存文件信息到数据库
+ FileInfo fileInfo = new FileInfo();
+ fileInfo.setFileName(originalFilename);
+ fileInfo.setFileSuffix(fileSuffix);
+ fileInfo.setContentType(contentType);
+ fileInfo.setFileSize(file.getSize());
+ fileInfo.setFilePath(uniqueFileName); // 只保存相对文件名、不保存完整路径
+ fileInfo.setFileMd5(fileMd5);
+
+ // 保存文件信息并获取ID
+ fileInfoService.save(fileInfo);
+
+ // 构建并设置预览URL和下载URL
+ String previewUrl = "/fileInfo/preview/" + fileInfo.getId();
+ String downloadUrl = "/fileInfo/download/" + fileInfo.getId();
+
+ FileInfoVo fileInfoVo = new FileInfoVo();
+ BeanUtils.copyProperties(fileInfo, fileInfoVo);
+ fileInfoVo.setPreviewUrl(previewUrl);
+ fileInfoVo.setDownloadUrl(downloadUrl);
+
+ fileInfoVoList.add(fileInfoVo);
+ }
+
+ if (fileInfoVoList.isEmpty()) {
+ return ApiResponse.failure("未成功上传任何文件");
+ }
+
+ return ApiResponse.success(fileInfoVoList);
+ }
+
+ @Operation(summary = "文件下载")
+ @GetMapping("/download/{id}")
+ public void downloadFile(@Parameter(description = "文件ID", required = true) @PathVariable String id, HttpServletResponse response) throws IOException {
+ // 根据ID查询文件信息
+ FileInfo fileInfo = fileInfoService.getById(id);
+ if (fileInfo == null) {
+ response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }
+
+ // 构建完整文件路径
+ String fullPath = getFullUploadPath() + File.separator + fileInfo.getFilePath();
+ File file = new File(fullPath);
+
+ // 校验文件是否存在
+ if (!file.exists()) {
+ response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }
+
+ // 设置响应头
+ response.setContentType(fileInfo.getContentType());
+ response.setContentLengthLong(fileInfo.getFileSize());
+ String encodedFileName = URLEncoder.encode(fileInfo.getFileName(), StandardCharsets.UTF_8.name());
+ response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
+ "attachment; filename=\"" + encodedFileName + "\"; filename*=UTF-8''" + encodedFileName);
+
+ // 使用 Hutool 工具类复制文件流到响应输出流
+ try (OutputStream os = response.getOutputStream()) {
+ FileUtil.writeToStream(file, os);
+ }
+ }
+
+ @Operation(summary = "文件预览")
+ @GetMapping("/preview/{id}")
+ public void previewFile(
+ @Parameter(description = "文件ID", required = true)
+ @PathVariable String id,
+ HttpServletResponse response) throws IOException {
+
+ // 根据ID查询文件信息
+ FileInfo fileInfo = fileInfoService.getById(id);
+ if (fileInfo == null) {
+ response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }
+
+ // 构建完整文件路径
+ String fullPath = getFullUploadPath() + File.separator + fileInfo.getFilePath();
+ File file = new File(fullPath);
+
+ // 校验文件是否存在
+ if (!file.exists()) {
+ response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }
+
+ // 设置响应头(不设置 attachment、让浏览器直接显示)
+ response.setContentType(fileInfo.getContentType());
+ response.setContentLengthLong(fileInfo.getFileSize());
+
+ // 使用 Hutool 工具类复制文件流到响应输出流
+ try (OutputStream os = response.getOutputStream()) {
+ FileUtil.writeToStream(file, os);
+ }
+ }
+
+ @Operation(summary = "文件列表")
+ @GetMapping("/list")
+ public ApiResponse getFileList(
+ @Parameter(description = "页码", required = true) Integer pageNum,
+ @Parameter(description = "每页条数", required = true) Integer pageSize,
+ @Parameter(description = "文件名称") String fileName) {
+
+ // 创建分页对象
+ Page page = new Page<>(pageNum, pageSize);
+ // 构建查询条件
+ LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
+ if (fileName != null && !fileName.isEmpty()) {
+ queryWrapper.like(FileInfo::getFileName, fileName);
+ }
+ // 按创建时间倒序排列、最新上传的文件在前
+ queryWrapper.orderByDesc(FileInfo::getCreatedAt);
+
+ // 执行分页查询
+ IPage fileInfoPage = fileInfoService.page(page, queryWrapper);
+
+ // 转换为VO对象并设置URL
+ List records = fileInfoPage.getRecords().stream().map(fileInfo -> {
+ FileInfoVo vo = new FileInfoVo();
+ BeanUtils.copyProperties(fileInfo, vo);
+ vo.setPreviewUrl("/fileInfo/preview/" + fileInfo.getId());
+ vo.setDownloadUrl("/fileInfo/download/" + fileInfo.getId());
+ return vo;
+ }).collect(Collectors.toList());
+
+ // 构建分页结果
+ Page resultPage = new Page<>();
+ resultPage.setRecords(records);
+ resultPage.setTotal(fileInfoPage.getTotal());
+ resultPage.setSize(fileInfoPage.getSize());
+ resultPage.setCurrent(fileInfoPage.getCurrent());
+ resultPage.setPages(fileInfoPage.getPages());
+ return ApiResponse.success(resultPage);
+ }
+}
diff --git a/src/main/java/com/yj/earth/business/controller/GraphHopperController.java b/src/main/java/com/yj/earth/business/controller/GraphHopperController.java
new file mode 100644
index 0000000..f9d96ed
--- /dev/null
+++ b/src/main/java/com/yj/earth/business/controller/GraphHopperController.java
@@ -0,0 +1,237 @@
+package com.yj.earth.business.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.graphhopper.GHRequest;
+import com.graphhopper.GHResponse;
+import com.graphhopper.GraphHopper;
+import com.graphhopper.ResponsePath;
+import com.graphhopper.config.Profile;
+import com.graphhopper.util.shapes.GHPoint;
+import com.yj.earth.business.domain.FileInfo;
+import com.yj.earth.business.service.FileInfoService;
+import com.yj.earth.common.config.GraphHopperProperties;
+import com.yj.earth.common.util.ApiResponse;
+import com.yj.earth.model.Point;
+import com.yj.earth.model.RouteRequest;
+import com.yj.earth.model.RouteResponse;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+
+@Data
+@Slf4j
+@Tag(name = "路径规划管理")
+@RestController
+@RequestMapping("/graphhopper")
+public class GraphHopperController {
+ @Resource
+ private FileInfoService fileInfoService;
+ @Resource
+ private GraphHopperProperties graphHopperProperties;
+
+ // 存储当前可用的 GraphHopper 实例
+ private volatile GraphHopper currentHopper;
+
+ // 状态控制: 线程安全
+ private final AtomicBoolean isLoading = new AtomicBoolean(false);
+ private final AtomicBoolean isLoaded = new AtomicBoolean(false);
+
+ @Operation(summary = "获取地图列表")
+ @GetMapping("/list")
+ public ApiResponse list() {
+ LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
+ queryWrapper.eq(FileInfo::getFileSuffix, "pbf");
+ return ApiResponse.success(fileInfoService.list(queryWrapper));
+ }
+
+ @Operation(summary = "加载地图数据")
+ @PostMapping("/loadMap")
+ public ApiResponse loadMap(@Parameter(description = "文件ID") @RequestParam String fileId) {
+ // 参数校验
+ if (fileId == null) {
+ return ApiResponse.failure("文件ID不能为空");
+ }
+
+ // 获取并校验OSM文件
+ String osmFilePath = fileInfoService.getFileAbsolutePath(fileId);
+ File osmFile = new File(osmFilePath);
+ if (!osmFile.exists()) {
+ return ApiResponse.failure("地图文件不存在: " + osmFilePath);
+ }
+ if (!osmFile.isFile() || !osmFile.getName().endsWith(".pbf")) {
+ return ApiResponse.failure("仅支持有效的.pbf格式OSM文件");
+ }
+ // 防止并发加载
+ if (isLoading.get()) {
+ return ApiResponse.failure("地图正在加载中、请稍后查询状态");
+ }
+
+ // 标记加载状态
+ isLoading.set(true);
+ isLoaded.set(false);
+
+ // 异步执行: 删除旧数据 → 创建新实例 → 加载新地图
+ new Thread(() -> {
+ GraphHopper newHopper = null;
+ try {
+ // 关键步骤1: 彻底删除旧地图数据目录
+ deleteOldGraphDir();
+ // 关键步骤2: 创建全新的GraphHopper实例
+ newHopper = createNewGraphHopperInstance(osmFilePath);
+ // 关键步骤3: 加载新地图
+ newHopper.importOrLoad();
+ // 关键步骤4: 加载成功 → 替换当前实例 + 更新状态
+ currentHopper = newHopper;
+ isLoaded.set(true);
+ log.info("地图加载成功");
+ } catch (Exception e) {
+ // 加载失败 → 清理新实例资源
+ if (newHopper != null) {
+ newHopper.close();
+ }
+ isLoaded.set(false);
+ e.printStackTrace();
+ log.error("地图加载失败: " + e.getMessage());
+
+ } finally {
+ // 无论成功/失败、释放加载锁
+ isLoading.set(false);
+ }
+ }).start();
+
+ return ApiResponse.success(null);
+ }
+
+ @Operation(summary = "路径规划")
+ @PostMapping("/route")
+ public ApiResponse calculateRoute(@RequestBody RouteRequest request) {
+ // 校验地图是否加载完成 + 实例是否可用
+ if (!isLoaded.get() || currentHopper == null) {
+ return ApiResponse.failure("地图未加载完成");
+ }
+ try {
+ // 构建路径点列表
+ List ghPoints = new ArrayList<>();
+ ghPoints.add(new GHPoint(request.getStartLat(), request.getStartLng())); // 起点
+ // 添加途经点
+ if (request.getWaypoints() != null && !request.getWaypoints().isEmpty()) {
+ for (Point waypoint : request.getWaypoints()) {
+ ghPoints.add(new GHPoint(waypoint.getLat(), waypoint.getLng()));
+ }
+ }
+ ghPoints.add(new GHPoint(request.getEndLat(), request.getEndLng())); // 终点
+
+ // 构建请求(仅指定Profile、无setWeighting)
+ String targetProfile = request.getProfile() != null ? request.getProfile() : "car";
+ GHRequest ghRequest = new GHRequest(ghPoints)
+ .setProfile(targetProfile);
+
+ // 用新实例计算路径
+ GHResponse response = currentHopper.route(ghRequest);
+
+ // 处理错误
+ if (response.hasErrors()) {
+ return ApiResponse.failure("路径计算失败: " + response.getErrors().toString());
+ }
+
+ // 解析结果
+ ResponsePath bestPath = response.getBest();
+ List pathPoints = new ArrayList<>();
+ bestPath.getPoints().forEach(ghPoint ->
+ pathPoints.add(new Point(ghPoint.getLat(), ghPoint.getLon()))
+ );
+
+ // 封装返回
+ RouteResponse routeResponse = new RouteResponse(bestPath.getDistance() / 1000, (double) (bestPath.getTime() / 60000), pathPoints);
+ return ApiResponse.success(routeResponse);
+
+ } catch (Exception e) {
+ return ApiResponse.failure("路径计算异常: " + e.getMessage());
+ }
+ }
+
+ @Operation(summary = "获取交通方式")
+ @PostMapping("/profiles")
+ public ApiResponse profiles() {
+ return ApiResponse.success(graphHopperProperties.getProfiles());
+ }
+
+ /**
+ * 创建全新的 GraphHopper 实例
+ */
+ private GraphHopper createNewGraphHopperInstance(String osmFilePath) {
+ GraphHopper hopper = new GraphHopper();
+ // 配置基础参数
+ hopper.setOSMFile(osmFilePath);
+ hopper.setGraphHopperLocation(graphHopperProperties.getGraphLocation());
+
+ // 配置交通方式 + 权重策略
+ List profileList = new ArrayList<>();
+ for (String profileName : graphHopperProperties.getProfiles()) {
+ Profile profile = new Profile(profileName);
+ profile.setVehicle(profileName);
+ profile.setWeighting("fastest");
+ profileList.add(profile);
+ }
+ hopper.setProfiles(profileList);
+
+ return hopper;
+ }
+
+ /**
+ * 递归删除旧地图数据目录
+ */
+ private void deleteOldGraphDir() {
+ File graphDir = new File(graphHopperProperties.getGraphLocation());
+ if (!graphDir.exists()) {
+ log.info("旧地图目录不存在、无需删除: " + graphHopperProperties.getGraphLocation());
+ return;
+ }
+
+ // 递归删除所有文件和子目录
+ File[] files = graphDir.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (file.isDirectory()) {
+ deleteOldGraphDir(file);
+ } else {
+ boolean deleted = file.delete();
+ log.info("删除旧地图文件: " + file.getAbsolutePath() + " → " + (deleted ? "成功" : "失败"));
+ }
+ }
+ }
+
+ // 删除空目录
+ boolean dirDeleted = graphDir.delete();
+ System.out.println("删除旧地图目录: " + graphDir.getAbsolutePath() + " → " + (dirDeleted ? "成功" : "失败"));
+ }
+
+ // 重载: 递归删除子目录
+ private void deleteOldGraphDir(File subDir) {
+ File[] files = subDir.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (file.isDirectory()) {
+ deleteOldGraphDir(file);
+ } else {
+ file.delete();
+ }
+ }
+ }
+ subDir.delete();
+ }
+}
diff --git a/src/main/java/com/yj/earth/business/controller/RoleController.java b/src/main/java/com/yj/earth/business/controller/RoleController.java
new file mode 100644
index 0000000..fa4a336
--- /dev/null
+++ b/src/main/java/com/yj/earth/business/controller/RoleController.java
@@ -0,0 +1,62 @@
+package com.yj.earth.business.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.yj.earth.business.domain.Role;
+import com.yj.earth.business.service.RoleService;
+import com.yj.earth.common.util.ApiResponse;
+import com.yj.earth.dto.role.AddRoleDto;
+import com.yj.earth.dto.role.UpdateRoleDto;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.BeanUtils;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+
+@Tag(name = "角色数据管理")
+@RestController
+@RequestMapping("/role")
+public class RoleController {
+ @Resource
+ private RoleService roleService;
+
+ @Operation(summary = "新增角色")
+ @PostMapping("/add")
+ public ApiResponse save(@RequestBody AddRoleDto addRoleDto) {
+ Role role = new Role();
+ BeanUtils.copyProperties(addRoleDto, role);
+ roleService.save(role);
+ return ApiResponse.success(null);
+ }
+
+ @Operation(summary = "删除角色")
+ @PostMapping("/delete")
+ public ApiResponse delete(@Parameter(description = "角色ID") String id) {
+ roleService.removeById(id);
+ return ApiResponse.success(null);
+ }
+
+ @Operation(summary = "更新角色")
+ @PostMapping("/update")
+ public ApiResponse update(@RequestBody UpdateRoleDto updateRoleDto) {
+ Role role = new Role();
+ BeanUtils.copyProperties(updateRoleDto, role);
+ roleService.updateById(role);
+ return ApiResponse.success(null);
+ }
+
+ @Operation(summary = "角色详情")
+ @GetMapping("/getById")
+ public ApiResponse get(@Parameter(description = "角色ID") String id) {
+ Role role = roleService.getById(id);
+ return ApiResponse.success(role);
+ }
+
+ @Operation(summary = "角色列表")
+ @GetMapping("/list")
+ public ApiResponse list(@Parameter(description = "分页数量") Integer pageNum, @Parameter(description = "分页大小") Integer pageSize) {
+ Page rolePage = roleService.page(new Page<>(pageNum, pageSize));
+ return ApiResponse.success(rolePage);
+ }
+}
diff --git a/src/main/java/com/yj/earth/business/controller/RoleSourceController.java b/src/main/java/com/yj/earth/business/controller/RoleSourceController.java
new file mode 100644
index 0000000..324a778
--- /dev/null
+++ b/src/main/java/com/yj/earth/business/controller/RoleSourceController.java
@@ -0,0 +1,44 @@
+package com.yj.earth.business.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.yj.earth.business.domain.RoleSource;
+import com.yj.earth.business.service.RoleService;
+import com.yj.earth.business.service.RoleSourceService;
+import com.yj.earth.business.service.SourceService;
+import com.yj.earth.common.util.ApiResponse;
+import com.yj.earth.dto.relation.RoleBindOrUnBindSourceDto;
+import com.yj.earth.dto.relation.SourceBindOrUnBindRoleDto;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Tag(name = "角色资源管理")
+@RestController
+@RequestMapping("/roleSource")
+public class RoleSourceController {
+ @Resource
+ private RoleSourceService roleSourceService;
+
+ @Operation(summary = "角色绑定资源")
+ @PostMapping("/roleBindSource")
+ public ApiResponse roleBindSource(@RequestBody RoleBindOrUnBindSourceDto roleBindOrUnBindSourceDto) {
+ // 先删除该角色下的所有资源
+ roleSourceService.remove(new LambdaQueryWrapper().eq(RoleSource::getRoleId, roleBindOrUnBindSourceDto.getRoleId()));
+ // 再设置新的资源
+ for (String sourceId : roleBindOrUnBindSourceDto.getSourceIdList()) {
+ RoleSource roleSource = new RoleSource();
+ roleSource.setRoleId(roleBindOrUnBindSourceDto.getRoleId());
+ roleSource.setSourceId(sourceId);
+ roleSourceService.save(roleSource);
+ }
+ return ApiResponse.success(null);
+ }
+}
diff --git a/src/main/java/com/yj/earth/business/controller/SourceController.java b/src/main/java/com/yj/earth/business/controller/SourceController.java
new file mode 100644
index 0000000..689d3f4
--- /dev/null
+++ b/src/main/java/com/yj/earth/business/controller/SourceController.java
@@ -0,0 +1,168 @@
+package com.yj.earth.business.controller;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.io.FileUtil;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.yj.earth.business.domain.Source;
+import com.yj.earth.business.service.RoleSourceService;
+import com.yj.earth.business.service.SourceService;
+import com.yj.earth.business.service.UserService;
+import com.yj.earth.common.service.SourceParamsValidator;
+import com.yj.earth.common.util.ApiResponse;
+import com.yj.earth.common.util.MapUtil;
+import com.yj.earth.dto.source.AddDirectoryDto;
+import com.yj.earth.dto.source.AddModelSourceDto;
+import com.yj.earth.dto.source.AddOtherSourceDto;
+import com.yj.earth.dto.source.UpdateSourceDto;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.Map;
+import java.util.Set;
+
+import static com.yj.earth.common.constant.GlobalConstant.DIRECTORY;
+import static com.yj.earth.common.constant.GlobalConstant.SHOW;
+
+@Slf4j
+@Tag(name = "树形结构管理")
+@RestController
+@RequestMapping("/source")
+public class SourceController {
+ @Resource
+ private SourceService sourceService;
+ @Resource
+ private UserService userService;
+ @Resource
+ private RoleSourceService roleSourceService;
+ @Resource
+ private SourceParamsValidator sourceParamsValidator;
+
+ @PostMapping("/addDirectory")
+ @Operation(summary = "新增目录资源")
+ public ApiResponse addDirectory(@RequestBody AddDirectoryDto addDirectoryDto) {
+ // 校验是否通过
+ String message = sourceService.checkIsPass(addDirectoryDto.getParentId(), addDirectoryDto.getSourceName());
+ if (message != null) {
+ return ApiResponse.failure(message);
+ }
+ // 通过之后保存资源
+ Source source = new Source();
+ BeanUtils.copyProperties(addDirectoryDto, source);
+ source.setSourceType(DIRECTORY);
+ source.setIsShow(SHOW);
+ sourceService.save(source);
+ // 添加资源到该用户的角色下
+ roleSourceService.addRoleSource(userService.getById(StpUtil.getLoginIdAsString()).getRoleId(), source.getId());
+ return ApiResponse.success(source);
+ }
+
+ @Operation(summary = "新增模型资源")
+ @PostMapping("/addModelSource")
+ public ApiResponse addModelSource(@RequestBody AddModelSourceDto addModelSourceDto) {
+ // 获取资源绝对路径
+ String sourcePath = addModelSourceDto.getSourcePath();
+ // 获取资源名称
+ String sourceName = FileUtil.mainName(sourcePath);
+ // 校验是否通过
+ String message = sourceService.checkIsPass(addModelSourceDto.getParentId(), sourceName);
+ if (message != null) {
+ return ApiResponse.failure(message);
+ }
+ // 调用SDK加载资源
+ String sourceId = sourceService.addAndGetSourceId(sourcePath);
+ // 获取文件路径并处理详情
+ String detail = sourceService.getDetail(sourcePath, sourceId);
+ // 构建并保存资源对象
+ Source source = new Source();
+ source.setSourcePath(sourcePath);
+ source.setSourceName(sourceName);
+ source.setParentId(addModelSourceDto.getParentId());
+ source.setTreeIndex(addModelSourceDto.getTreeIndex());
+ source.setDetail(detail);
+ source.setSourceType(MapUtil.getString(MapUtil.jsonToMap(detail), "fileType"));
+ source.setIsShow(SHOW);
+ source.setParams(addModelSourceDto.getParams());
+ sourceService.save(source);
+ // 添加资源到该用户的角色下
+ roleSourceService.addRoleSource(userService.getById(StpUtil.getLoginIdAsString()).getRoleId(), source.getId());
+ return ApiResponse.success(source);
+ }
+
+
+ @Operation(summary = "新增其他资源")
+ @PostMapping("/addOtherSource")
+ public ApiResponse addOtherSource(@RequestBody AddOtherSourceDto addOtherSourceDto) throws JsonProcessingException {
+ // 校验是否通过
+ String message = sourceService.checkIsPass(addOtherSourceDto.getParentId(), addOtherSourceDto.getSourceName());
+ if (message != null) {
+ return ApiResponse.failure(message);
+ }
+ // 验证并转换参数
+ Object validatedParams = sourceParamsValidator.validateAndConvert(
+ addOtherSourceDto.getSourceType(),
+ addOtherSourceDto.getParams()
+ );
+ System.out.println(validatedParams);
+ Source source = new Source();
+ BeanUtils.copyProperties(addOtherSourceDto, source);
+ source.setIsShow(SHOW);
+ source.setParams(MapUtil.objectToJson(validatedParams));
+ sourceService.save(source);
+ // 添加资源到该用户的角色下
+ roleSourceService.addRoleSource(userService.getById(StpUtil.getLoginIdAsString()).getRoleId(), source.getId());
+ return ApiResponse.success(source);
+ }
+
+
+ @Operation(summary = "更新资源信息及参数")
+ @PostMapping("/update")
+ public ApiResponse updateSource(@RequestBody UpdateSourceDto updateSourceDto) {
+ // 查询资源
+ Source source = sourceService.getById(updateSourceDto.getId());
+ if (source == null) {
+ return ApiResponse.failure("资源不存在");
+ }
+
+ // 更新基本信息
+ BeanUtils.copyProperties(updateSourceDto, source);
+
+ // 处理参数更新
+ if (updateSourceDto.getParams() != null && !updateSourceDto.getParams().isEmpty()) {
+ // 获取类型
+ String sourceType = source.getSourceType();
+ // 验证参数
+ Object validatedParams = sourceParamsValidator.validateAndConvert(
+ sourceType,
+ updateSourceDto.getParams()
+ );
+
+ // 获取原始数据的 Map 并合并新参数
+ Map dataMap = MapUtil.jsonToMap(source.getParams());
+ MapUtil.mergeMaps(dataMap, updateSourceDto.getParams());
+ source.setParams(MapUtil.mapToString(dataMap));
+ }
+
+ // 保存更新
+ sourceService.updateById(source);
+ return ApiResponse.success(source);
+ }
+
+ @GetMapping("/type")
+ @Operation(summary = "获取支持的资源类型")
+ public ApiResponse getSupportedSourceTypes() {
+ Set supportedTypes = sourceParamsValidator.getSupportedSourceTypes();
+ return ApiResponse.success(supportedTypes);
+ }
+
+
+ @Operation(summary = "获取资源列表")
+ @GetMapping("/list")
+ public ApiResponse list() {
+ return ApiResponse.success(sourceService.getSourceListByUserId(StpUtil.getLoginIdAsString()));
+ }
+}
diff --git a/src/main/java/com/yj/earth/business/controller/UserController.java b/src/main/java/com/yj/earth/business/controller/UserController.java
new file mode 100644
index 0000000..4590f73
--- /dev/null
+++ b/src/main/java/com/yj/earth/business/controller/UserController.java
@@ -0,0 +1,140 @@
+package com.yj.earth.business.controller;
+
+import cn.dev33.satoken.stp.SaTokenInfo;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.crypto.digest.BCrypt;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.yj.earth.annotation.EncryptResponse;
+import com.yj.earth.annotation.ExcludeField;
+import com.yj.earth.annotation.RoleAccess;
+import com.yj.earth.business.domain.Role;
+import com.yj.earth.business.domain.User;
+import com.yj.earth.business.service.RoleService;
+import com.yj.earth.dto.relation.UserBindOrUnBindRoleDto;
+import com.yj.earth.dto.user.AddUserDto;
+import com.yj.earth.dto.user.UpdatePasswordDto;
+import com.yj.earth.dto.user.UpdateUserDto;
+import com.yj.earth.dto.user.UserLoginDto;
+import com.yj.earth.business.service.UserService;
+import com.yj.earth.common.util.ApiResponse;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.BeanUtils;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Map;
+
+@Tag(name = "用户数据管理")
+@RestController
+@RequestMapping("/user")
+public class UserController {
+ @Resource
+ private UserService userService;
+ @Resource
+ private RoleService roleService;
+
+ @Operation(summary = "新增用户")
+ @PostMapping("/add")
+ @RoleAccess(roleNames = "管理员")
+ public ApiResponse save(@RequestBody AddUserDto addUserDto) {
+ User user = new User();
+ BeanUtils.copyProperties(addUserDto, user);
+ if (userService.getOne(new LambdaQueryWrapper().eq(User::getUsername, user.getUsername())) != null) {
+ return ApiResponse.failure("用户已存在");
+ }
+ String password = user.getPassword();
+ user.setPassword(BCrypt.hashpw(password, BCrypt.gensalt()));
+ if (addUserDto.getRoleId() == null) {
+ // 查询系统名字为默认角色的角色ID
+ user.setRoleId(roleService.getOne(new LambdaQueryWrapper().eq(Role::getRoleName, "默认角色")).getId());
+ }
+ userService.save(user);
+ return ApiResponse.success(null);
+ }
+
+ @Operation(summary = "删除用户")
+ @PostMapping("/delete")
+ @RoleAccess(roleNames = "管理员")
+ public ApiResponse delete(@Parameter(description = "用户ID") String id) {
+ userService.removeById(id);
+ return ApiResponse.success(null);
+ }
+
+ @Operation(summary = "更新信息")
+ @PostMapping("/update")
+ public ApiResponse update(@RequestBody UpdateUserDto updateUserDto) {
+ User user = new User();
+ BeanUtils.copyProperties(updateUserDto, user);
+ userService.updateById(user);
+ return ApiResponse.success(null);
+ }
+
+ @Operation(summary = "更新密码")
+ @PostMapping("/updatePassword")
+ public ApiResponse updatePassword(@RequestBody UpdatePasswordDto updatePasswordDto) {
+ User user = userService.getById(updatePasswordDto.getId());
+ if (user == null) {
+ return ApiResponse.failure("用户不存在");
+ }
+ if (!BCrypt.checkpw(updatePasswordDto.getOldPassword(), user.getPassword())) {
+ return ApiResponse.failure("旧密码错误");
+ }
+ user.setPassword(BCrypt.hashpw(updatePasswordDto.getNewPassword(), BCrypt.gensalt()));
+ userService.updateById(user);
+ return ApiResponse.success(null);
+ }
+
+ @Operation(summary = "用户详情")
+ @GetMapping("/getById")
+ public ApiResponse get(@Parameter(description = "用户ID") String id) {
+ return ApiResponse.success(userService.getById(id));
+ }
+
+ @Operation(summary = "用户列表")
+ @GetMapping("/list")
+ @RoleAccess(roleNames = "管理员")
+ public ApiResponse list(@Parameter(description = "分页数量") Integer pageNum, @Parameter(description = "分页大小") Integer pageSize) {
+ Page userPage = userService.page(new Page<>(pageNum, pageSize));
+ return ApiResponse.success(userPage);
+ }
+
+ @Operation(summary = "用户登录")
+ @PostMapping("/login")
+ public ApiResponse login(@RequestBody UserLoginDto userLoginDto) {
+ User user = userService.getOne(new LambdaQueryWrapper().eq(User::getUsername, userLoginDto.getUsername()));
+ if (user == null) {
+ return ApiResponse.failure("用户名不存在");
+ }
+ if (!BCrypt.checkpw(userLoginDto.getPassword(), user.getPassword())) {
+ return ApiResponse.failure("密码错误");
+ }
+ StpUtil.login(user.getId());
+ SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
+ return ApiResponse.success(Map.of("header", tokenInfo.getTokenName(), "token", tokenInfo.getTokenValue()));
+ }
+
+ @Operation(summary = "用户登出")
+ @PostMapping("/logout")
+ public ApiResponse logout() {
+ StpUtil.logout();
+ return ApiResponse.success(null);
+ }
+
+ @Operation(summary = "设置角色")
+ @PostMapping("/userBindOrUnBindRole")
+ @RoleAccess(roleNames = "管理员")
+ public ApiResponse userBindOrUnBindRole(@RequestBody UserBindOrUnBindRoleDto userBindOrUnBindRoleDto) {
+ userService.lambdaUpdate().set(User::getRoleId, userBindOrUnBindRoleDto.getRoleId()).eq(User::getId, userBindOrUnBindRoleDto.getUserId()).update();
+ return ApiResponse.success(null);
+ }
+
+ @Operation(summary = "获取当前用户信息")
+ @GetMapping("/getCurrentUserInfo")
+ public ApiResponse getCurrentUserInfo() {
+ return ApiResponse.success(userService.getById(StpUtil.getLoginIdAsString()));
+ }
+}
diff --git a/src/main/java/com/yj/earth/business/domain/FileInfo.java b/src/main/java/com/yj/earth/business/domain/FileInfo.java
new file mode 100644
index 0000000..5841b75
--- /dev/null
+++ b/src/main/java/com/yj/earth/business/domain/FileInfo.java
@@ -0,0 +1,50 @@
+package com.yj.earth.business.domain;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+@Data
+public class FileInfo implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Schema(description = "主键")
+ @TableId(value = "id", type = IdType.ASSIGN_UUID)
+ private String id;
+
+ @Schema(description = "文件名")
+ private String fileName;
+
+ @Schema(description = "文件后缀")
+ private String fileSuffix;
+
+ @Schema(description = "内容类型")
+ private String contentType;
+
+ @Schema(description = "文件大小")
+ private Long fileSize;
+
+ @Schema(description = "文件路径")
+ private String filePath;
+
+ @Schema(description = "文件MD5")
+ private String fileMd5;
+
+ @Schema(description = "创建时间")
+ @TableField(fill = FieldFill.INSERT)
+ private LocalDateTime createdAt;
+
+ @TableField(fill = FieldFill.UPDATE)
+ private LocalDateTime updatedAt;
+}
diff --git a/src/main/java/com/yj/earth/business/domain/Role.java b/src/main/java/com/yj/earth/business/domain/Role.java
new file mode 100644
index 0000000..ce48a9f
--- /dev/null
+++ b/src/main/java/com/yj/earth/business/domain/Role.java
@@ -0,0 +1,41 @@
+package com.yj.earth.business.domain;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+@Data
+public class Role implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Schema(description = "主键")
+ @TableId(value = "id", type = IdType.ASSIGN_UUID)
+ private String id;
+
+ @Schema(description = "角色名称")
+ private String roleName;
+
+ @Schema(description = "角色描述")
+ private String description;
+
+ @Schema(description = "是否超级管理员")
+ private Integer isSuper;
+
+ @Schema(description = "创建时间")
+ @TableField(fill = FieldFill.INSERT)
+ private LocalDateTime createdAt;
+
+ @Schema(description = "更新时间")
+ @TableField(fill = FieldFill.UPDATE)
+ private LocalDateTime updatedAt;
+}
diff --git a/src/main/java/com/yj/earth/business/domain/RoleSource.java b/src/main/java/com/yj/earth/business/domain/RoleSource.java
new file mode 100644
index 0000000..c27054c
--- /dev/null
+++ b/src/main/java/com/yj/earth/business/domain/RoleSource.java
@@ -0,0 +1,39 @@
+package com.yj.earth.business.domain;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+@Data
+public class RoleSource implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Schema(description = "主键")
+ @TableId(value = "id", type = IdType.ASSIGN_UUID)
+ private String id;
+
+ @Schema(description = "角色ID")
+ private String roleId;
+
+ @Schema(description = "资源ID")
+ private String sourceId;
+
+ @Schema(description = "创建时间")
+ @TableField(fill = FieldFill.INSERT)
+ private LocalDateTime createdAt;
+
+ @Schema(description = "更新时间")
+ @TableField(fill = FieldFill.UPDATE)
+ private LocalDateTime updatedAt;
+}
diff --git a/src/main/java/com/yj/earth/business/domain/Source.java b/src/main/java/com/yj/earth/business/domain/Source.java
new file mode 100644
index 0000000..9f0a73b
--- /dev/null
+++ b/src/main/java/com/yj/earth/business/domain/Source.java
@@ -0,0 +1,56 @@
+package com.yj.earth.business.domain;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+@Data
+public class Source implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Schema(description = "主键")
+ @TableId(value = "id", type = IdType.INPUT)
+ private String id;
+
+ @Schema(description = "资源名称")
+ private String sourceName;
+
+ @Schema(description = "资源类型")
+ private String sourceType;
+
+ @Schema(description = "资源路径")
+ private String sourcePath;
+
+ @Schema(description = "父级ID")
+ private String parentId;
+
+ @Schema(description = "树形索引")
+ private Integer treeIndex;
+
+ @Schema(description = "是否显示")
+ private Integer isShow;
+
+ @Schema (description = "其他内容")
+ private String detail;
+
+ @Schema (description = "前端参数")
+ private String params;
+
+ @Schema(description = "创建时间")
+ @TableField(fill = FieldFill.INSERT)
+ private LocalDateTime createdAt;
+
+ @Schema(description = "更新时间")
+ @TableField(fill = FieldFill.UPDATE)
+ private LocalDateTime updatedAt;
+}
diff --git a/src/main/java/com/yj/earth/business/domain/User.java b/src/main/java/com/yj/earth/business/domain/User.java
new file mode 100644
index 0000000..783d97f
--- /dev/null
+++ b/src/main/java/com/yj/earth/business/domain/User.java
@@ -0,0 +1,51 @@
+package com.yj.earth.business.domain;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+import com.yj.earth.annotation.ExcludeField;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+@Data
+public class User implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @TableId(value = "id", type = IdType.ASSIGN_UUID)
+ private String id;
+
+ @Schema(description = "用户名")
+ private String username;
+
+ @Schema(description = "密码")
+ @ExcludeField
+ private String password;
+
+ @Schema(description = "头像")
+ private String avatar;
+
+ @Schema(description = "昵称")
+ private String nickname;
+
+ @Schema(description = "手机号")
+ private String phone;
+
+ @Schema(description = "所属角色")
+ private String roleId;
+
+ @Schema(description = "创建时间")
+ @TableField(fill = FieldFill.INSERT)
+ private LocalDateTime createdAt;
+
+ @Schema(description = "更新时间")
+ @TableField(fill = FieldFill.UPDATE)
+ private LocalDateTime updatedAt;
+}
diff --git a/src/main/java/com/yj/earth/business/mapper/FileInfoMapper.java b/src/main/java/com/yj/earth/business/mapper/FileInfoMapper.java
new file mode 100644
index 0000000..7b0037c
--- /dev/null
+++ b/src/main/java/com/yj/earth/business/mapper/FileInfoMapper.java
@@ -0,0 +1,18 @@
+package com.yj.earth.business.mapper;
+
+import com.yj.earth.business.domain.FileInfo;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ *
+ * Mapper 接口
+ *
+ *
+ * @author 周志雄
+ * @since 2025-08-29
+ */
+@Mapper
+public interface FileInfoMapper extends BaseMapper {
+
+}
diff --git a/src/main/java/com/yj/earth/business/mapper/RoleMapper.java b/src/main/java/com/yj/earth/business/mapper/RoleMapper.java
new file mode 100644
index 0000000..2052803
--- /dev/null
+++ b/src/main/java/com/yj/earth/business/mapper/RoleMapper.java
@@ -0,0 +1,18 @@
+package com.yj.earth.business.mapper;
+
+import com.yj.earth.business.domain.Role;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ *
+ * Mapper 接口
+ *
+ *
+ * @author 周志雄
+ * @since 2025-08-28
+ */
+@Mapper
+public interface RoleMapper extends BaseMapper {
+
+}
diff --git a/src/main/java/com/yj/earth/business/mapper/RoleSourceMapper.java b/src/main/java/com/yj/earth/business/mapper/RoleSourceMapper.java
new file mode 100644
index 0000000..1492312
--- /dev/null
+++ b/src/main/java/com/yj/earth/business/mapper/RoleSourceMapper.java
@@ -0,0 +1,18 @@
+package com.yj.earth.business.mapper;
+
+import com.yj.earth.business.domain.RoleSource;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ *
+ * Mapper 接口
+ *
+ *
+ * @author 周志雄
+ * @since 2025-08-27
+ */
+@Mapper
+public interface RoleSourceMapper extends BaseMapper {
+
+}
diff --git a/src/main/java/com/yj/earth/business/mapper/SourceMapper.java b/src/main/java/com/yj/earth/business/mapper/SourceMapper.java
new file mode 100644
index 0000000..3ef862b
--- /dev/null
+++ b/src/main/java/com/yj/earth/business/mapper/SourceMapper.java
@@ -0,0 +1,18 @@
+package com.yj.earth.business.mapper;
+
+import com.yj.earth.business.domain.Source;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ *
+ * Mapper 接口
+ *
+ *
+ * @author 周志雄
+ * @since 2025-08-26
+ */
+@Mapper
+public interface SourceMapper extends BaseMapper {
+
+}
diff --git a/src/main/java/com/yj/earth/business/mapper/UserMapper.java b/src/main/java/com/yj/earth/business/mapper/UserMapper.java
new file mode 100644
index 0000000..74d4b54
--- /dev/null
+++ b/src/main/java/com/yj/earth/business/mapper/UserMapper.java
@@ -0,0 +1,18 @@
+package com.yj.earth.business.mapper;
+
+import com.yj.earth.business.domain.User;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ *
+ * Mapper 接口
+ *
+ *
+ * @author 周志雄
+ * @since 2025-08-28
+ */
+@Mapper
+public interface UserMapper extends BaseMapper {
+
+}
diff --git a/src/main/java/com/yj/earth/business/service/FileInfoService.java b/src/main/java/com/yj/earth/business/service/FileInfoService.java
new file mode 100644
index 0000000..d2d5de0
--- /dev/null
+++ b/src/main/java/com/yj/earth/business/service/FileInfoService.java
@@ -0,0 +1,9 @@
+package com.yj.earth.business.service;
+
+import com.yj.earth.business.domain.FileInfo;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+public interface FileInfoService extends IService {
+ // 根据文件ID获取文件绝对路径
+ String getFileAbsolutePath(String id);
+}
diff --git a/src/main/java/com/yj/earth/business/service/RoleService.java b/src/main/java/com/yj/earth/business/service/RoleService.java
new file mode 100644
index 0000000..250f764
--- /dev/null
+++ b/src/main/java/com/yj/earth/business/service/RoleService.java
@@ -0,0 +1,16 @@
+package com.yj.earth.business.service;
+
+import com.yj.earth.business.domain.Role;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ *
+ * 服务类
+ *
+ *
+ * @author 周志雄
+ * @since 2025-08-28
+ */
+public interface RoleService extends IService {
+
+}
diff --git a/src/main/java/com/yj/earth/business/service/RoleSourceService.java b/src/main/java/com/yj/earth/business/service/RoleSourceService.java
new file mode 100644
index 0000000..82be8b3
--- /dev/null
+++ b/src/main/java/com/yj/earth/business/service/RoleSourceService.java
@@ -0,0 +1,9 @@
+package com.yj.earth.business.service;
+
+import com.yj.earth.business.domain.RoleSource;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+
+public interface RoleSourceService extends IService {
+ void addRoleSource(String roleId, String sourceId);
+}
diff --git a/src/main/java/com/yj/earth/business/service/SourceService.java b/src/main/java/com/yj/earth/business/service/SourceService.java
new file mode 100644
index 0000000..12e7b59
--- /dev/null
+++ b/src/main/java/com/yj/earth/business/service/SourceService.java
@@ -0,0 +1,25 @@
+package com.yj.earth.business.service;
+
+import com.yj.earth.business.domain.Source;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.yj.earth.common.util.ApiResponse;
+
+import java.util.List;
+
+public interface SourceService extends IService {
+ String addAndGetSourceId(String sourcePath);
+
+ String getDetail(String sourcePath, String sourceId);
+
+ String buildSdkUrl(String path);
+
+ String fetchCltDetail(String sourceId);
+
+ String fetchMbtilesDetail(String sourceId);
+
+ String fetchPakDetail(String sourceId);
+
+ List getSourceListByUserId(String userId);
+
+ String checkIsPass(String parentId, String sourceName);
+}
diff --git a/src/main/java/com/yj/earth/business/service/UserService.java b/src/main/java/com/yj/earth/business/service/UserService.java
new file mode 100644
index 0000000..fc2bc03
--- /dev/null
+++ b/src/main/java/com/yj/earth/business/service/UserService.java
@@ -0,0 +1,16 @@
+package com.yj.earth.business.service;
+
+import com.yj.earth.business.domain.User;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ *
+ * 服务类
+ *
+ *
+ * @author 周志雄
+ * @since 2025-08-28
+ */
+public interface UserService extends IService {
+
+}
diff --git a/src/main/java/com/yj/earth/business/service/impl/FileInfoServiceImpl.java b/src/main/java/com/yj/earth/business/service/impl/FileInfoServiceImpl.java
new file mode 100644
index 0000000..36d5d5b
--- /dev/null
+++ b/src/main/java/com/yj/earth/business/service/impl/FileInfoServiceImpl.java
@@ -0,0 +1,40 @@
+package com.yj.earth.business.service.impl;
+
+import com.yj.earth.business.domain.FileInfo;
+import com.yj.earth.business.mapper.FileInfoMapper;
+import com.yj.earth.business.service.FileInfoService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import io.swagger.v3.oas.annotations.Parameter;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.web.bind.annotation.PathVariable;
+
+import java.io.File;
+
+@Service
+public class FileInfoServiceImpl extends ServiceImpl implements FileInfoService {
+
+ @Value("${file.upload.path}")
+ private String uploadPath;
+
+ public String getFileAbsolutePath(String id) {
+
+ // 根据ID查询文件信息
+ FileInfo fileInfo = this.getById(id);
+ if (fileInfo == null) {
+ return null;
+ }
+
+ // 构建完整文件路径
+ String fullPath = uploadPath + File.separator + fileInfo.getFilePath();
+ File file = new File(fullPath);
+
+ // 校验文件是否存在
+ if (!file.exists()) {
+ return null;
+ }
+
+ // 获取并返回绝对路径
+ return file.getAbsolutePath();
+ }
+}
diff --git a/src/main/java/com/yj/earth/business/service/impl/RoleServiceImpl.java b/src/main/java/com/yj/earth/business/service/impl/RoleServiceImpl.java
new file mode 100644
index 0000000..f7c9203
--- /dev/null
+++ b/src/main/java/com/yj/earth/business/service/impl/RoleServiceImpl.java
@@ -0,0 +1,20 @@
+package com.yj.earth.business.service.impl;
+
+import com.yj.earth.business.domain.Role;
+import com.yj.earth.business.mapper.RoleMapper;
+import com.yj.earth.business.service.RoleService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ *
+ * 服务实现类
+ *
+ *
+ * @author 周志雄
+ * @since 2025-08-28
+ */
+@Service
+public class RoleServiceImpl extends ServiceImpl implements RoleService {
+
+}
diff --git a/src/main/java/com/yj/earth/business/service/impl/RoleSourceServiceImpl.java b/src/main/java/com/yj/earth/business/service/impl/RoleSourceServiceImpl.java
new file mode 100644
index 0000000..c263d77
--- /dev/null
+++ b/src/main/java/com/yj/earth/business/service/impl/RoleSourceServiceImpl.java
@@ -0,0 +1,20 @@
+package com.yj.earth.business.service.impl;
+
+import com.yj.earth.business.domain.RoleSource;
+import com.yj.earth.business.mapper.RoleSourceMapper;
+import com.yj.earth.business.service.RoleSourceService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+
+@Service
+public class RoleSourceServiceImpl extends ServiceImpl implements RoleSourceService {
+
+ @Override
+ public void addRoleSource(String roleId, String sourceId) {
+ RoleSource roleSource = new RoleSource();
+ roleSource.setRoleId(roleId);
+ roleSource.setSourceId(sourceId);
+ save(roleSource);
+ }
+}
diff --git a/src/main/java/com/yj/earth/business/service/impl/SourceServiceImpl.java b/src/main/java/com/yj/earth/business/service/impl/SourceServiceImpl.java
new file mode 100644
index 0000000..ce8df84
--- /dev/null
+++ b/src/main/java/com/yj/earth/business/service/impl/SourceServiceImpl.java
@@ -0,0 +1,166 @@
+package com.yj.earth.business.service.impl;
+
+import cn.hutool.core.io.FileUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.yj.earth.business.domain.Role;
+import com.yj.earth.business.domain.RoleSource;
+import com.yj.earth.business.domain.Source;
+import com.yj.earth.business.domain.User;
+import com.yj.earth.business.mapper.SourceMapper;
+import com.yj.earth.business.service.RoleService;
+import com.yj.earth.business.service.RoleSourceService;
+import com.yj.earth.business.service.SourceService;
+import com.yj.earth.business.service.UserService;
+import com.yj.earth.common.config.ServerConfig;
+import com.yj.earth.common.util.ApiResponse;
+import com.yj.earth.common.util.HttpUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+
+@Slf4j
+@Service
+public class SourceServiceImpl extends ServiceImpl implements SourceService {
+
+ @Resource
+ private ServerConfig serverConfig;
+ @Resource
+ private RoleSourceService roleSourceService;
+ @Resource
+ private UserService userService;
+ @Resource
+ private SourceService sourceService;
+ @Resource
+ private RoleService roleService;
+
+ // 存储文件后缀与对应处理函数的映射关系
+ public final Map> detailFetchers;
+
+ // 初始化映射关系
+ public SourceServiceImpl() {
+ detailFetchers = new HashMap<>();
+ detailFetchers.put("clt", this::fetchCltDetail);
+ detailFetchers.put("mbtiles", this::fetchMbtilesDetail);
+ detailFetchers.put("pak", this::fetchPakDetail);
+ }
+
+ /**
+ * 调用SDK获取资源ID
+ */
+ @Override
+ public String addAndGetSourceId(String sourcePath) {
+ Map addParams = new HashMap<>();
+ addParams.put("filePath", sourcePath);
+ String url = buildSdkUrl("/sourceMap/add");
+ return HttpUtil.doPostForm(url, addParams);
+ }
+
+ /**
+ * 检测资源是否通过审核
+ */
+ @Override
+ public String checkIsPass(String parentId, String sourceName) {
+ // 先查询父节点是否存在
+ if (parentId != null) {
+ LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
+ queryWrapper.eq(Source::getId, parentId);
+ List list = sourceService.list(queryWrapper);
+ if (sourceService.count(queryWrapper) == 0) {
+ return "父级不存在";
+ }
+ }
+// // 验证该目录下是否已经存在此资源名一样的
+// LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
+// queryWrapper.eq(Source::getSourceName, sourceName);
+// if (sourceService.count(queryWrapper) > 0) {
+// return "此目录下已存在此资源";
+// }
+ return null;
+ }
+
+ /**
+ * 根据文件后缀获取详情信息
+ */
+ @Override
+ public String getDetail(String sourcePath, String sourceId) {
+ String ext = FileUtil.extName(sourcePath);
+ // 通过映射关系获取并执行对应的处理函数
+ Function fetcher = detailFetchers.get(ext);
+ if (fetcher != null) {
+ String detailResult = fetcher.apply(sourceId);
+ return detailResult;
+ } else {
+ log.info("未找到{}类型的处理方式", ext);
+ }
+ return null;
+ }
+
+ /**
+ * 构建SDK请求URL
+ */
+ @Override
+ public String buildSdkUrl(String path) {
+ return "http://" + serverConfig.getHost() + ":" + serverConfig.getSdkPort() + path;
+ }
+
+ /**
+ * 获取 CLT 类型资源详情
+ */
+ @Override
+ public String fetchCltDetail(String sourceId) {
+ String url = buildSdkUrl("/data/clt/detail/" + sourceId);
+ return HttpUtil.doGet(url);
+ }
+
+ /**
+ * 获取 MBTiles 类型资源详情
+ */
+ @Override
+ public String fetchMbtilesDetail(String sourceId) {
+ String url = buildSdkUrl("/data/mbtiles/detail/" + sourceId);
+ return HttpUtil.doGet(url);
+ }
+
+ /**
+ * 获取 PAK 类型资源详情
+ */
+ @Override
+ public String fetchPakDetail(String sourceId) {
+ String url = buildSdkUrl("/data/pak/detail/" + sourceId);
+ return HttpUtil.doGet(url);
+ }
+
+
+ /**
+ * 获取用户资源列表
+ */
+ @Override
+ public List getSourceListByUserId(String userId) {
+ // 查询该用户信息
+ LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
+ queryWrapper.eq(User::getId, userId);
+ User user = userService.getOne(queryWrapper);
+ // 查询角色信息
+ String roleId = user.getRoleId();
+ LambdaQueryWrapper roleQueryWrapper = new LambdaQueryWrapper<>();
+ roleQueryWrapper.eq(Role::getId, roleId);
+ // 如果这个角色是管理员则直接返回所有资源
+ if (roleService.getOne(roleQueryWrapper).getIsSuper() == 1) {
+ return sourceService.list();
+ }
+ // 查询属于该角色的资源列表
+ LambdaQueryWrapper roleSourceQueryWrapper = new LambdaQueryWrapper<>();
+ roleSourceQueryWrapper.eq(RoleSource::getRoleId, roleId);
+ List roleSourceList = roleSourceService.list(roleSourceQueryWrapper);
+ // 从结果提取出资源ID列表
+ List sourceIdList = roleSourceList.stream().map(RoleSource::getSourceId).toList();
+ return sourceService.listByIds(sourceIdList);
+ }
+}
diff --git a/src/main/java/com/yj/earth/business/service/impl/UserServiceImpl.java b/src/main/java/com/yj/earth/business/service/impl/UserServiceImpl.java
new file mode 100644
index 0000000..9ca2f77
--- /dev/null
+++ b/src/main/java/com/yj/earth/business/service/impl/UserServiceImpl.java
@@ -0,0 +1,20 @@
+package com.yj.earth.business.service.impl;
+
+import com.yj.earth.business.domain.User;
+import com.yj.earth.business.mapper.UserMapper;
+import com.yj.earth.business.service.UserService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ *
+ * 服务实现类
+ *
+ *
+ * @author 周志雄
+ * @since 2025-08-28
+ */
+@Service
+public class UserServiceImpl extends ServiceImpl implements UserService {
+
+}
diff --git a/src/main/java/com/yj/earth/common/config/CorsConfig.java b/src/main/java/com/yj/earth/common/config/CorsConfig.java
new file mode 100644
index 0000000..57a3909
--- /dev/null
+++ b/src/main/java/com/yj/earth/common/config/CorsConfig.java
@@ -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);
+ }
+}
diff --git a/src/main/java/com/yj/earth/common/config/GlobalExceptionHandler.java b/src/main/java/com/yj/earth/common/config/GlobalExceptionHandler.java
new file mode 100644
index 0000000..07cb141
--- /dev/null
+++ b/src/main/java/com/yj/earth/common/config/GlobalExceptionHandler.java
@@ -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());
+ }
+}
+
diff --git a/src/main/java/com/yj/earth/common/config/GraphHopperProperties.java b/src/main/java/com/yj/earth/common/config/GraphHopperProperties.java
new file mode 100644
index 0000000..c5c9d6b
--- /dev/null
+++ b/src/main/java/com/yj/earth/common/config/GraphHopperProperties.java
@@ -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 profiles;
+}
diff --git a/src/main/java/com/yj/earth/common/config/JacksonConfig.java b/src/main/java/com/yj/earth/common/config/JacksonConfig.java
new file mode 100644
index 0000000..9d8542c
--- /dev/null
+++ b/src/main/java/com/yj/earth/common/config/JacksonConfig.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/yj/earth/common/config/Knife4jConfig.java b/src/main/java/com/yj/earth/common/config/Knife4jConfig.java
new file mode 100644
index 0000000..1c3b24e
--- /dev/null
+++ b/src/main/java/com/yj/earth/common/config/Knife4jConfig.java
@@ -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")));
+ }
+}
diff --git a/src/main/java/com/yj/earth/common/config/MyMetaObjectConfig.java b/src/main/java/com/yj/earth/common/config/MyMetaObjectConfig.java
new file mode 100644
index 0000000..c1a9885
--- /dev/null
+++ b/src/main/java/com/yj/earth/common/config/MyMetaObjectConfig.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/yj/earth/common/config/SaTokenConfig.java b/src/main/java/com/yj/earth/common/config/SaTokenConfig.java
new file mode 100644
index 0000000..39c5296
--- /dev/null
+++ b/src/main/java/com/yj/earth/common/config/SaTokenConfig.java
@@ -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 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);
+ }
+}
diff --git a/src/main/java/com/yj/earth/common/config/ServerConfig.java b/src/main/java/com/yj/earth/common/config/ServerConfig.java
new file mode 100644
index 0000000..f1c231b
--- /dev/null
+++ b/src/main/java/com/yj/earth/common/config/ServerConfig.java
@@ -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;
+}
diff --git a/src/main/java/com/yj/earth/common/config/SourceTypeConfig.java b/src/main/java/com/yj/earth/common/config/SourceTypeConfig.java
new file mode 100644
index 0000000..8274cdd
--- /dev/null
+++ b/src/main/java/com/yj/earth/common/config/SourceTypeConfig.java
@@ -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> sourceParamClasses() throws ClassNotFoundException {
+ ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
+ scanner.addIncludeFilter(new AnnotationTypeFilter(SourceType.class));
+
+ Set> classes = new HashSet<>();
+ for (var beanDefinition : scanner.findCandidateComponents(PACKAGE)) {
+ classes.add(Class.forName(beanDefinition.getBeanClassName()));
+ }
+
+ return classes;
+ }
+}
diff --git a/src/main/java/com/yj/earth/common/constant/GlobalConstant.java b/src/main/java/com/yj/earth/common/constant/GlobalConstant.java
new file mode 100644
index 0000000..2a8a775
--- /dev/null
+++ b/src/main/java/com/yj/earth/common/constant/GlobalConstant.java
@@ -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";
+}
diff --git a/src/main/java/com/yj/earth/common/core/MapRedisTemplate.java b/src/main/java/com/yj/earth/common/core/MapRedisTemplate.java
new file mode 100644
index 0000000..0bc4cf0
--- /dev/null
+++ b/src/main/java/com/yj/earth/common/core/MapRedisTemplate.java
@@ -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 storage = new HashMap<>();
+ // 用于存储过期时间
+ private final Map 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;
+ }
+ }
+}
diff --git a/src/main/java/com/yj/earth/common/exception/UnAuthException.java b/src/main/java/com/yj/earth/common/exception/UnAuthException.java
new file mode 100644
index 0000000..367d88b
--- /dev/null
+++ b/src/main/java/com/yj/earth/common/exception/UnAuthException.java
@@ -0,0 +1,10 @@
+package com.yj.earth.common.exception;
+
+public class UnAuthException extends RuntimeException{
+ /**
+ * 带异常信息的构造方法
+ */
+ public UnAuthException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/com/yj/earth/common/service/ServerInitService.java b/src/main/java/com/yj/earth/common/service/ServerInitService.java
new file mode 100644
index 0000000..074ecbb
--- /dev/null
+++ b/src/main/java/com/yj/earth/common/service/ServerInitService.java
@@ -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 list =sourceService.list(new LambdaQueryWrapper()
+ .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);
+ }
+ }
+}
diff --git a/src/main/java/com/yj/earth/common/service/SourceParamsValidator.java b/src/main/java/com/yj/earth/common/service/SourceParamsValidator.java
new file mode 100644
index 0000000..5d226c4
--- /dev/null
+++ b/src/main/java/com/yj/earth/common/service/SourceParamsValidator.java
@@ -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> sourceTypeMap = new HashMap<>();
+
+ @Autowired
+ public SourceParamsValidator(ObjectMapper objectMapper, Set> 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 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 getSupportedSourceTypes() {
+ return sourceTypeMap.keySet();
+ }
+}
diff --git a/src/main/java/com/yj/earth/common/util/AesEncryptUtil.java b/src/main/java/com/yj/earth/common/util/AesEncryptUtil.java
new file mode 100644
index 0000000..9578dbe
--- /dev/null
+++ b/src/main/java/com/yj/earth/common/util/AesEncryptUtil.java
@@ -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);
+ }
+ }
+}
diff --git a/src/main/java/com/yj/earth/common/util/ApiResponse.java b/src/main/java/com/yj/earth/common/util/ApiResponse.java
new file mode 100644
index 0000000..4b9bc15
--- /dev/null
+++ b/src/main/java/com/yj/earth/common/util/ApiResponse.java
@@ -0,0 +1,56 @@
+package com.yj.earth.common.util;
+
+import lombok.Data;
+
+@Data
+public class ApiResponse {
+
+ 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 ApiResponse success(T data) {
+ return new ApiResponse<>(200, data, "操作成功");
+ }
+
+ // 成功响应(无数据)
+ public static ApiResponse successWithMessage(String message) {
+ return new ApiResponse<>(200, null, message);
+ }
+
+ // 失败响应(带自定义消息)
+ public static ApiResponse failure(String message) {
+ return new ApiResponse<>(20000, null, message);
+ }
+
+ // 失败响应(未授权)
+ public static ApiResponse failureWithNoAuth(String message) {
+ return new ApiResponse<>(401, null, message);
+ }
+
+ // 设置 data 字段、并返回当前对象、支持链式调用
+ public ApiResponse setData(T data) {
+ this.data = data;
+ return this;
+ }
+
+ // 设置 message 字段、并返回当前对象、支持链式调用
+ public ApiResponse setMessage(String message) {
+ this.message = message;
+ return this;
+ }
+
+ // 设置 code 字段、并返回当前对象、支持链式调用
+ public ApiResponse setCode(int code) {
+ this.code = code;
+ return this;
+ }
+}
diff --git a/src/main/java/com/yj/earth/common/util/CodeUtil.java b/src/main/java/com/yj/earth/common/util/CodeUtil.java
new file mode 100644
index 0000000..a6cacef
--- /dev/null
+++ b/src/main/java/com/yj/earth/common/util/CodeUtil.java
@@ -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();
+ }
+}
diff --git a/src/main/java/com/yj/earth/common/util/HttpUtil.java b/src/main/java/com/yj/earth/common/util/HttpUtil.java
new file mode 100644
index 0000000..12d100d
--- /dev/null
+++ b/src/main/java/com/yj/earth/common/util/HttpUtil.java
@@ -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 以支持更多类型的参数值
+ */
+ public static String doPostForm(String url, Map 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 nameValuePairs = new ArrayList<>();
+ for (Map.Entry 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
+ */
+ public static String doPostJson(String url, Map 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 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;
+ }
+}
diff --git a/src/main/java/com/yj/earth/common/util/JsonMapConverter.java b/src/main/java/com/yj/earth/common/util/JsonMapConverter.java
new file mode 100644
index 0000000..ce96649
--- /dev/null
+++ b/src/main/java/com/yj/earth/common/util/JsonMapConverter.java
@@ -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 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 jsonToMap(String json) {
+ if (json == null || json.trim().isEmpty()) {
+ return new HashMap<>(0);
+ }
+
+ try {
+ return objectMapper.readValue(json, new TypeReference>() {});
+ } catch (Exception e) {
+ log.error("JSON转Map失败、JSON内容: {}", json, e);
+ return new HashMap<>(0);
+ }
+ }
+}
diff --git a/src/main/java/com/yj/earth/common/util/MapUtil.java b/src/main/java/com/yj/earth/common/util/MapUtil.java
new file mode 100644
index 0000000..ac4aa7a
--- /dev/null
+++ b/src/main/java/com/yj/earth/common/util/MapUtil.java
@@ -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 void mergeMaps(Map originalMap, Map newMap) {
+ // 检查参数是否为null、避免空指针异常
+ if (originalMap == null || newMap == null) {
+ throw new IllegalArgumentException("参数Map不能为null");
+ }
+
+ // 遍历新Map中的所有键值对
+ for (Map.Entry 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 Map 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 String mapToString(Map 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 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);
+ }
+}
diff --git a/src/main/java/com/yj/earth/common/util/PortKillUtil.java b/src/main/java/com/yj/earth/common/util/PortKillUtil.java
new file mode 100644
index 0000000..04ad4ca
--- /dev/null
+++ b/src/main/java/com/yj/earth/common/util/PortKillUtil.java
@@ -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;
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/com/yj/earth/common/util/PositionUtil.java b/src/main/java/com/yj/earth/common/util/PositionUtil.java
new file mode 100644
index 0000000..59a3fc3
--- /dev/null
+++ b/src/main/java/com/yj/earth/common/util/PositionUtil.java
@@ -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};
+ }
+}
diff --git a/src/main/java/com/yj/earth/common/util/SdkUtil.java b/src/main/java/com/yj/earth/common/util/SdkUtil.java
new file mode 100644
index 0000000..88f0061
--- /dev/null
+++ b/src/main/java/com/yj/earth/common/util/SdkUtil.java
@@ -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 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 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;
+ }
+}
diff --git a/src/main/java/com/yj/earth/common/util/ServerUniqueIdUtil.java b/src/main/java/com/yj/earth/common/util/ServerUniqueIdUtil.java
new file mode 100644
index 0000000..98b8231
--- /dev/null
+++ b/src/main/java/com/yj/earth/common/util/ServerUniqueIdUtil.java
@@ -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 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 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";
+ }
+ }
+}
diff --git a/src/main/java/com/yj/earth/datasource/DatabaseManager.java b/src/main/java/com/yj/earth/datasource/DatabaseManager.java
new file mode 100644
index 0000000..38673b0
--- /dev/null
+++ b/src/main/java/com/yj/earth/datasource/DatabaseManager.java
@@ -0,0 +1,401 @@
+package com.yj.earth.datasource;
+
+import com.yj.earth.design.*;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.core.io.ClassPathResource;
+import org.yaml.snakeyaml.Yaml;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.sql.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+@Data
+@Slf4j
+public class DatabaseManager {
+ public enum DatabaseType {
+ SQLITE, MYSQL
+ }
+
+ private static final String EXCLUDE_SERIAL_FIELD = "serialVersionUID";
+ private static final List> ENTITY_CLASSES;
+ private static final String FOLDER_NAME = "yjearth";
+ private static final String DB_FILE_NAME = "app.db";
+ private static String sqliteDbFilePath;
+ private static boolean isSqliteInitialized = false;
+ private static String mysqlUrl;
+ private static String mysqlUsername;
+ private static String mysqlPassword;
+ private static boolean isMysqlInitialized = false;
+ private static final String SQLITE_DRIVER = "org.sqlite.JDBC";
+ private static final String MYSQL_DRIVER = "com.mysql.cj.jdbc.Driver";
+ private static final String MYSQL_TABLE_ENGINE = "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";
+
+ static {
+ List> classes = new ArrayList<>();
+ classes.add(User.class);
+ classes.add(Role.class);
+ classes.add(Source.class);
+ classes.add(RoleSource.class);
+ classes.add(FileInfo.class);
+ ENTITY_CLASSES = Collections.unmodifiableList(classes);
+ }
+
+ public static void initDatabase(DatabaseType dbType) {
+ if (dbType == null) {
+ log.error("数据库类型不能为空");
+ return;
+ }
+ if (isInitialized(dbType)) {
+ log.info("{}数据库已初始化、无需重复执行", dbType.name());
+ return;
+ }
+
+ try {
+ loadConfig(dbType);
+ if (!validateConfig(dbType)) {
+ log.error("{}数据库配置无效、初始化失败", dbType.name());
+ return;
+ }
+ loadDriver(dbType);
+ preProcess(dbType);
+ createTablesForEntities(dbType);
+ markInitialized(dbType, true);
+ log.info("{}数据库初始化成功({})", dbType.name(), getInitSuccessMsg(dbType));
+ } catch (ClassNotFoundException e) {
+ log.error("{}驱动类未找到、请检查依赖: {}", dbType.name(), e.getMessage(), e);
+ } catch (SQLException e) {
+ log.error("{}数据库操作失败: {}", dbType.name(), e.getMessage(), e);
+ } catch (Exception e) {
+ log.error("{}数据库初始化未知异常: {}", dbType.name(), e.getMessage(), e);
+ }
+ }
+
+ public static String getUnderlineName(String camelCaseName) {
+ if (camelCaseName == null || camelCaseName.isEmpty()) {
+ throw new IllegalArgumentException("命名转换的入参名称不能为空");
+ }
+ StringBuilder underlineName = new StringBuilder();
+ underlineName.append(Character.toLowerCase(camelCaseName.charAt(0)));
+
+ for (int i = 1; i < camelCaseName.length(); i++) {
+ char currentChar = camelCaseName.charAt(i);
+ if (Character.isUpperCase(currentChar)) {
+ underlineName.append("_").append(Character.toLowerCase(currentChar));
+ } else {
+ underlineName.append(currentChar);
+ }
+ }
+ return underlineName.toString();
+ }
+
+ public static String getSqliteDbFilePath() {
+ return sqliteDbFilePath;
+ }
+
+ private static void loadConfig(DatabaseType dbType) throws IOException {
+ if (dbType != DatabaseType.MYSQL) return;
+
+ try (InputStream yamlInput = new ClassPathResource("application.yml").getInputStream()) {
+ Yaml yaml = new Yaml();
+ Map yamlData = yaml.load(yamlInput);
+ Map springMap = (Map) yamlData.get("spring");
+ Map datasourceMap = (Map) springMap.get("datasource");
+ Map mysqlMap = (Map) datasourceMap.get("mysql");
+
+ mysqlUrl = getConfigValue(mysqlMap, "url");
+ mysqlUsername = getConfigValue(mysqlMap, "username");
+ mysqlPassword = getConfigValue(mysqlMap, "password");
+ }
+ }
+
+ private static boolean validateConfig(DatabaseType dbType) {
+ if (dbType == DatabaseType.SQLITE) return true;
+
+ if (mysqlUrl == null || mysqlUrl.isEmpty()) {
+ log.error("MySQL配置缺失: spring.datasource.mysql.url");
+ return false;
+ }
+ if (mysqlUsername == null || mysqlUsername.isEmpty()) {
+ log.error("MySQL配置缺失: spring.datasource.mysql.username");
+ return false;
+ }
+ if (!mysqlUrl.startsWith("jdbc:mysql://")) {
+ log.error("MySQL URL格式错误、需以[jdbc:mysql://]开头、当前: {}", mysqlUrl);
+ return false;
+ }
+ return true;
+ }
+
+ private static void loadDriver(DatabaseType dbType) throws ClassNotFoundException {
+ if (dbType == DatabaseType.SQLITE) {
+ Class.forName(SQLITE_DRIVER);
+ log.info("SQLite驱动加载成功");
+ } else {
+ Class.forName(MYSQL_DRIVER);
+ log.info("MySQL驱动加载成功");
+ }
+ }
+
+ private static void preProcess(DatabaseType dbType) throws IOException {
+ if (dbType != DatabaseType.SQLITE) return;
+
+ Path systemCacheDir = getRecommendedCacheDirectory();
+ if (systemCacheDir == null) {
+ throw new IOException("无法获取有效的系统缓存目录、无法创建SQLite文件");
+ }
+
+ Path appDir = systemCacheDir.resolve(FOLDER_NAME);
+ Path dbFile = appDir.resolve(DB_FILE_NAME);
+ sqliteDbFilePath = dbFile.toAbsolutePath().toString();
+
+ if (!Files.exists(appDir)) {
+ Files.createDirectories(appDir);
+ log.info("创建SQLite应用目录: {}", appDir);
+ }
+ if (!Files.isWritable(appDir)) {
+ throw new IOException("无权限写入SQLite目录: " + appDir);
+ }
+
+ if (!Files.exists(dbFile)) {
+ Files.createFile(dbFile);
+ log.info("创建SQLite新文件: {}", sqliteDbFilePath);
+ } else {
+ log.info("SQLite文件已存在: {}", sqliteDbFilePath);
+ }
+ }
+
+ private static void createTablesForEntities(DatabaseType dbType) throws SQLException {
+ if (ENTITY_CLASSES.isEmpty()) {
+ log.warn("未配置需要创建表的实体类、跳过批量建表");
+ return;
+ }
+
+ try (Connection connection = getConnection(dbType)) {
+ for (Class> entityClass : ENTITY_CLASSES) {
+ createTableIfNotExists(connection, dbType, entityClass);
+ }
+ }
+ }
+
+ private static void createTableIfNotExists(Connection connection, DatabaseType dbType, Class> entityClass) throws SQLException {
+ String tableName = getUnderlineName(entityClass.getSimpleName());
+
+ if (isTableExists(connection, dbType, tableName)) {
+ log.info("{}表[{}]已存在、跳过创建", dbType.name(), tableName);
+ return;
+ }
+
+ String createSql = generateCreateTableSql(dbType, entityClass, tableName);
+ try (Statement statement = connection.createStatement()) {
+ statement.execute(createSql);
+ log.info("{}表[{}]创建成功、执行SQL: {}", dbType.name(), tableName, createSql);
+ }
+ }
+
+ private static Connection getConnection(DatabaseType dbType) throws SQLException {
+ if (dbType == DatabaseType.SQLITE) {
+ return DriverManager.getConnection("jdbc:sqlite:" + sqliteDbFilePath);
+ } else {
+ return DriverManager.getConnection(mysqlUrl, mysqlUsername, mysqlPassword);
+ }
+ }
+
+ private static boolean isTableExists(Connection connection, DatabaseType dbType, String tableName) throws SQLException {
+ if (dbType == DatabaseType.SQLITE) {
+ try (ResultSet rs = connection.getMetaData().getTables(null, null, tableName, new String[]{"TABLE"})) {
+ return rs.next();
+ }
+ } else {
+ String dbName = extractDbNameFromMysqlUrl(mysqlUrl);
+ String checkSql = "SELECT 1 FROM information_schema.tables WHERE table_schema = ? AND table_name = ? LIMIT 1";
+
+ try (PreparedStatement pstmt = connection.prepareStatement(checkSql)) {
+ pstmt.setString(1, dbName);
+ pstmt.setString(2, tableName);
+ try (ResultSet rs = pstmt.executeQuery()) {
+ return rs.next();
+ }
+ }
+ }
+ }
+
+ private static String generateCreateTableSql(DatabaseType dbType, Class> entityClass, String tableName) {
+ StringBuilder sqlBuilder = new StringBuilder("CREATE TABLE IF NOT EXISTS ").append(tableName).append(" (");
+ Field[] fields = entityClass.getDeclaredFields();
+ List columnDefinitions = new ArrayList<>();
+
+ for (Field field : fields) {
+ if (EXCLUDE_SERIAL_FIELD.equals(field.getName()) || Modifier.isStatic(field.getModifiers())) {
+ continue;
+ }
+ field.setAccessible(true);
+
+ String columnName = getUnderlineName(field.getName());
+ String dbTypeStr = mapJavaTypeToDbType(dbType, field.getType());
+ StringBuilder columnDef = new StringBuilder(columnName).append(" ").append(dbTypeStr);
+
+ if ("id".equals(field.getName())) {
+ columnDef.append(dbType == DatabaseType.SQLITE ? " PRIMARY KEY" : " PRIMARY KEY AUTO_INCREMENT");
+ }
+
+ columnDefinitions.add(columnDef.toString());
+ }
+
+ for (int i = 0; i < columnDefinitions.size(); i++) {
+ sqlBuilder.append(columnDefinitions.get(i));
+ if (i != columnDefinitions.size() - 1) {
+ sqlBuilder.append(", ");
+ }
+ }
+
+ if (dbType == DatabaseType.MYSQL) {
+ sqlBuilder.append(") ").append(MYSQL_TABLE_ENGINE);
+ } else {
+ sqlBuilder.append(")");
+ }
+
+ return sqlBuilder.toString();
+ }
+
+ private static String mapJavaTypeToDbType(DatabaseType dbType, Class> javaType) {
+ return dbType == DatabaseType.SQLITE ? mapJavaTypeToSqlite(javaType) : mapJavaTypeToMysql(javaType);
+ }
+
+ private static String mapJavaTypeToSqlite(Class> javaType) {
+ if (javaType == int.class || javaType == Integer.class || javaType == long.class || javaType == Long.class || javaType == short.class || javaType == Short.class) {
+ return "INTEGER";
+ } else if (javaType == float.class || javaType == Float.class || javaType == double.class || javaType == Double.class) {
+ return "REAL";
+ } else if (javaType == boolean.class || javaType == Boolean.class) {
+ return "INTEGER";
+ } else if (javaType == String.class) {
+ return "TEXT";
+ } else if (javaType == byte[].class) {
+ return "BLOB";
+ } else {
+ return "TEXT";
+ }
+ }
+
+ private static Path getRecommendedCacheDirectory() {
+ String os = System.getProperty("os.name").toLowerCase();
+ if (os.contains("win")) {
+ String appData = System.getenv("APPDATA");
+ if (appData != null && !appData.isEmpty()) {
+ Path path = Paths.get(appData);
+ if (Files.exists(path) && Files.isWritable(path)) {
+ return path;
+ }
+ }
+ } else if (os.contains("nix") || os.contains("nux")) {
+ String userHome = System.getProperty("user.home");
+ if (userHome != null && !userHome.isEmpty()) {
+ Path path = Paths.get(userHome).resolve(".cache");
+ try {
+ if (!Files.exists(path)) {
+ Files.createDirectories(path);
+ }
+ if (Files.isWritable(path)) {
+ return path;
+ }
+ } catch (IOException e) {
+ log.error("无法访问Linux .cache目录: {}", e.getMessage(), e);
+ }
+ }
+ }
+ return null;
+ }
+
+ private static String mapJavaTypeToMysql(Class> javaType) {
+ if (javaType == int.class || javaType == Integer.class) {
+ return "INT";
+ } else if (javaType == long.class || javaType == Long.class) {
+ return "BIGINT";
+ } else if (javaType == float.class || javaType == Float.class) {
+ return "FLOAT";
+ } else if (javaType == double.class || javaType == Double.class) {
+ return "DOUBLE";
+ } else if (javaType == boolean.class || javaType == Boolean.class) {
+ return "TINYINT(1)";
+ } else if (javaType == String.class) {
+ return "VARCHAR(500)";
+ } else if (javaType == java.util.Date.class || javaType == java.sql.Date.class) {
+ return "DATE";
+ } else if (javaType == java.sql.Timestamp.class || javaType == java.time.LocalDateTime.class) {
+ return "DATETIME";
+ } else if (javaType == byte[].class) {
+ return "BLOB";
+ } else {
+ return "VARCHAR(1000)";
+ }
+ }
+
+ private static String extractDbNameFromMysqlUrl(String url) {
+ if (url == null) return null;
+ String urlWithoutPrefix = url.replace("jdbc:mysql://", "");
+ String urlWithoutParams = urlWithoutPrefix.split("\\?")[0];
+ return urlWithoutParams.substring(urlWithoutParams.lastIndexOf("/") + 1);
+ }
+
+ private static String getConfigValue(Map configMap, String key) {
+ Object value = configMap.get(key);
+ return value == null ? null : value.toString().trim();
+ }
+
+ private static boolean isInitialized(DatabaseType dbType) {
+ return dbType == DatabaseType.SQLITE ? isSqliteInitialized : isMysqlInitialized;
+ }
+
+ private static void markInitialized(DatabaseType dbType, boolean status) {
+ if (dbType == DatabaseType.SQLITE) {
+ isSqliteInitialized = status;
+ } else {
+ isMysqlInitialized = status;
+ }
+ }
+
+ private static String getInitSuccessMsg(DatabaseType dbType) {
+ return dbType == DatabaseType.SQLITE ?
+ "文件路径: " + sqliteDbFilePath :
+ "数据库: " + extractDbNameFromMysqlUrl(mysqlUrl) + "、用户: " + mysqlUsername;
+ }
+
+ public static String getActiveDataSource() throws IOException {
+ // 读取配置文件
+ try (InputStream yamlInput = new ClassPathResource("application.yml").getInputStream()) {
+ Yaml yaml = new Yaml();
+ Map yamlData = yaml.load(yamlInput);
+ if (yamlData.containsKey("spring")) {
+ Object springObj = yamlData.get("spring");
+ if (springObj instanceof Map) {
+ Map springMap = (Map) springObj;
+ if (springMap.containsKey("datasource")) {
+ Object datasourceObj = springMap.get("datasource");
+ if (datasourceObj instanceof Map) {
+ Map datasourceMap = (Map) datasourceObj;
+ if (datasourceMap.containsKey("active")) {
+ Object activeObj = datasourceMap.get("active");
+ if (activeObj != null) {
+ return activeObj.toString().trim();
+ }
+ }
+ }
+ }
+ }
+ }
+ } catch (Exception e) {
+ log.error("读取配置文件出错");
+ }
+ return "sqlite";
+ }
+}
diff --git a/src/main/java/com/yj/earth/datasource/JdbcTemplateConfig.java b/src/main/java/com/yj/earth/datasource/JdbcTemplateConfig.java
new file mode 100644
index 0000000..8847891
--- /dev/null
+++ b/src/main/java/com/yj/earth/datasource/JdbcTemplateConfig.java
@@ -0,0 +1,18 @@
+package com.yj.earth.datasource;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import javax.sql.DataSource;
+
+@Configuration
+public class JdbcTemplateConfig {
+ /**
+ * 配置JdbcTemplate
+ */
+ @Bean
+ public JdbcTemplate jdbcTemplate(DataSource dataSource) {
+ return new JdbcTemplate(dataSource);
+ }
+}
diff --git a/src/main/java/com/yj/earth/datasource/MysqlDataSourceConfig.java b/src/main/java/com/yj/earth/datasource/MysqlDataSourceConfig.java
new file mode 100644
index 0000000..25c24f3
--- /dev/null
+++ b/src/main/java/com/yj/earth/datasource/MysqlDataSourceConfig.java
@@ -0,0 +1,35 @@
+package com.yj.earth.datasource;
+
+import com.alibaba.druid.pool.DruidDataSource;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.sql.DataSource;
+
+/**
+ * MySQL 数据源配置类
+ */
+@Configuration
+@ConditionalOnProperty(name = "spring.datasource.active", havingValue = "mysql")
+public class MysqlDataSourceConfig {
+
+ /**
+ * 配置 MySQL 数据源
+ */
+ @Bean
+ @ConfigurationProperties(prefix = "spring.datasource.mysql")
+ public DataSource dataSource() {
+ DruidDataSource dataSource = new DruidDataSource();
+ dataSource.setInitialSize(5);
+ dataSource.setMaxActive(20);
+ dataSource.setMinIdle(5);
+ dataSource.setMaxWait(60000);
+ dataSource.setTestWhileIdle(true);
+ dataSource.setValidationQuery("SELECT 1");
+ dataSource.setTestOnBorrow(false);
+ dataSource.setTestOnReturn(false);
+ return dataSource;
+ }
+}
diff --git a/src/main/java/com/yj/earth/datasource/SqliteDataSourceConfig.java b/src/main/java/com/yj/earth/datasource/SqliteDataSourceConfig.java
new file mode 100644
index 0000000..08e4f75
--- /dev/null
+++ b/src/main/java/com/yj/earth/datasource/SqliteDataSourceConfig.java
@@ -0,0 +1,39 @@
+package com.yj.earth.datasource;
+
+import com.alibaba.druid.pool.DruidDataSource;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.sql.DataSource;
+
+/**
+ * SQLite 数据源配置类
+ */
+@Configuration
+@ConditionalOnProperty(name = "spring.datasource.active", havingValue = "sqlite", matchIfMissing = true)
+public class SqliteDataSourceConfig {
+
+ /**
+ * 配置 SQLite 数据源
+ */
+ @Bean
+ public DataSource dataSource() {
+ String dbPath = DatabaseManager.getSqliteDbFilePath();
+ if (dbPath == null) {
+ throw new RuntimeException("获取SQLite数据库文件路径失败");
+ }
+ DruidDataSource dataSource = new DruidDataSource();
+ dataSource.setDriverClassName("org.sqlite.JDBC");
+ dataSource.setUrl("jdbc:sqlite:" + dbPath);
+ dataSource.setInitialSize(5);
+ dataSource.setMaxActive(20);
+ dataSource.setMinIdle(5);
+ dataSource.setMaxWait(60000);
+ dataSource.setTestWhileIdle(true);
+ dataSource.setValidationQuery("SELECT 1");
+ dataSource.setTestOnBorrow(false);
+ dataSource.setTestOnReturn(false);
+ return dataSource;
+ }
+}
diff --git a/src/main/java/com/yj/earth/design/FileInfo.java b/src/main/java/com/yj/earth/design/FileInfo.java
new file mode 100644
index 0000000..ba49bed
--- /dev/null
+++ b/src/main/java/com/yj/earth/design/FileInfo.java
@@ -0,0 +1,38 @@
+package com.yj.earth.design;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+
+@Data
+public class FileInfo {
+
+ @Schema(description = "主键")
+ private String id;
+
+ @Schema(description = "文件名")
+ private String fileName;
+
+ @Schema(description = "文件后缀")
+ private String fileSuffix;
+
+ @Schema(description = "内容类型")
+ private String contentType;
+
+ @Schema(description = "文件大小")
+ private Long fileSize;
+
+ @Schema(description = "文件路径")
+ private String filePath;
+
+ @Schema(description = "文件MD5")
+ private String fileMd5;
+
+ @Schema(description = "创建时间")
+ private LocalDateTime createdAt;
+
+ @Schema(description = "更新时间")
+ private LocalDateTime updatedAt;
+}
diff --git a/src/main/java/com/yj/earth/design/Role.java b/src/main/java/com/yj/earth/design/Role.java
new file mode 100644
index 0000000..e9a53de
--- /dev/null
+++ b/src/main/java/com/yj/earth/design/Role.java
@@ -0,0 +1,28 @@
+package com.yj.earth.design;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+public class Role {
+
+ @Schema(description = "主键")
+ private String id;
+
+ @Schema(description = "角色名称")
+ private String roleName;
+
+ @Schema(description = "角色描述")
+ private String description;
+
+ @Schema(description = "是否超级管理员")
+ private Integer isSuper;
+
+ @Schema(description = "创建时间")
+ private LocalDateTime createdAt;
+
+ @Schema(description = "更新时间")
+ private LocalDateTime updatedAt;
+}
diff --git a/src/main/java/com/yj/earth/design/RoleSource.java b/src/main/java/com/yj/earth/design/RoleSource.java
new file mode 100644
index 0000000..1798cc2
--- /dev/null
+++ b/src/main/java/com/yj/earth/design/RoleSource.java
@@ -0,0 +1,25 @@
+package com.yj.earth.design;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+public class RoleSource {
+
+ @Schema(description = "主键")
+ private String id;
+
+ @Schema(description = "角色ID")
+ private String roleId;
+
+ @Schema(description = "资源ID")
+ private String sourceId;
+
+ @Schema(description = "创建时间")
+ private LocalDateTime createdAt;
+
+ @Schema(description = "更新时间")
+ private LocalDateTime updatedAt;
+}
diff --git a/src/main/java/com/yj/earth/design/Source.java b/src/main/java/com/yj/earth/design/Source.java
new file mode 100644
index 0000000..331e615
--- /dev/null
+++ b/src/main/java/com/yj/earth/design/Source.java
@@ -0,0 +1,43 @@
+package com.yj.earth.design;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+public class Source {
+
+ @Schema (description = "主键")
+ private String id;
+
+ @Schema (description = "资源名称")
+ private String sourceName;
+
+ @Schema (description = "资源类型")
+ private String sourceType;
+
+ @Schema (description = "资源路径")
+ private String sourcePath;
+
+ @Schema (description = "父级ID")
+ private String parentId;
+
+ @Schema (description = "树状索引")
+ private Integer treeIndex;
+
+ @Schema (description = "是否显示")
+ private Integer isShow;
+
+ @Schema (description = "其他内容")
+ private String detail;
+
+ @Schema (description = "前端参数")
+ private String params;
+
+ @Schema (description = "创建时间")
+ private LocalDateTime createdAt;
+
+ @Schema (description = "更新时间")
+ private LocalDateTime updatedAt;
+}
diff --git a/src/main/java/com/yj/earth/design/User.java b/src/main/java/com/yj/earth/design/User.java
new file mode 100644
index 0000000..89335e4
--- /dev/null
+++ b/src/main/java/com/yj/earth/design/User.java
@@ -0,0 +1,38 @@
+package com.yj.earth.design;
+
+import com.yj.earth.annotation.ExcludeField;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+public class User{
+
+ @Schema(description = "主键")
+ private String id;
+
+ @Schema(description = "用户名")
+ private String username;
+
+ @Schema(description = "密码")
+ private String password;
+
+ @Schema(description = "头像")
+ private String avatar;
+
+ @Schema(description = "昵称")
+ private String nickname;
+
+ @Schema(description = "手机号")
+ private String phone;
+
+ @Schema(description = "所属角色")
+ private String roleId;
+
+ @Schema(description = "创建时间")
+ private LocalDateTime createdAt;
+
+ @Schema(description = "更新时间")
+ private LocalDateTime updatedAt;
+}
diff --git a/src/main/java/com/yj/earth/dto/relation/RoleBindOrUnBindSourceDto.java b/src/main/java/com/yj/earth/dto/relation/RoleBindOrUnBindSourceDto.java
new file mode 100644
index 0000000..e408784
--- /dev/null
+++ b/src/main/java/com/yj/earth/dto/relation/RoleBindOrUnBindSourceDto.java
@@ -0,0 +1,14 @@
+package com.yj.earth.dto.relation;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class RoleBindOrUnBindSourceDto {
+ @Schema(description = "角色ID")
+ private String roleId;
+ @Schema(description = "资源ID列表")
+ private List sourceIdList;
+}
diff --git a/src/main/java/com/yj/earth/dto/relation/SourceBindOrUnBindRoleDto.java b/src/main/java/com/yj/earth/dto/relation/SourceBindOrUnBindRoleDto.java
new file mode 100644
index 0000000..05560ea
--- /dev/null
+++ b/src/main/java/com/yj/earth/dto/relation/SourceBindOrUnBindRoleDto.java
@@ -0,0 +1,14 @@
+package com.yj.earth.dto.relation;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class SourceBindOrUnBindRoleDto {
+ @Schema(description = "角色ID列表")
+ private List roleIdList;
+ @Schema(description = "资源ID")
+ private String sourceId;
+}
diff --git a/src/main/java/com/yj/earth/dto/relation/UserBindOrUnBindRoleDto.java b/src/main/java/com/yj/earth/dto/relation/UserBindOrUnBindRoleDto.java
new file mode 100644
index 0000000..91c4f28
--- /dev/null
+++ b/src/main/java/com/yj/earth/dto/relation/UserBindOrUnBindRoleDto.java
@@ -0,0 +1,14 @@
+package com.yj.earth.dto.relation;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class UserBindOrUnBindRoleDto {
+ @Schema(description = "角色ID")
+ private String roleId;
+ @Schema(description = "用户ID")
+ private String userId;
+}
diff --git a/src/main/java/com/yj/earth/dto/role/AddRoleDto.java b/src/main/java/com/yj/earth/dto/role/AddRoleDto.java
new file mode 100644
index 0000000..d727b81
--- /dev/null
+++ b/src/main/java/com/yj/earth/dto/role/AddRoleDto.java
@@ -0,0 +1,16 @@
+package com.yj.earth.dto.role;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+public class AddRoleDto {
+ @Schema(description = "角色名称")
+ private String roleName;
+
+ @Schema(description = "角色描述")
+ private String description;
+
+ @Schema(description = "是否超级管理员")
+ private Integer isSuper;
+}
diff --git a/src/main/java/com/yj/earth/dto/role/UpdateRoleDto.java b/src/main/java/com/yj/earth/dto/role/UpdateRoleDto.java
new file mode 100644
index 0000000..c8559e0
--- /dev/null
+++ b/src/main/java/com/yj/earth/dto/role/UpdateRoleDto.java
@@ -0,0 +1,16 @@
+package com.yj.earth.dto.role;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+public class UpdateRoleDto {
+ @Schema(description = "主键")
+ private String id;
+
+ @Schema(description = "角色描述")
+ private String description;
+
+ @Schema(description = "是否超级管理员")
+ private Integer isSuper;
+}
diff --git a/src/main/java/com/yj/earth/dto/source/AddDirectoryDto.java b/src/main/java/com/yj/earth/dto/source/AddDirectoryDto.java
new file mode 100644
index 0000000..1983844
--- /dev/null
+++ b/src/main/java/com/yj/earth/dto/source/AddDirectoryDto.java
@@ -0,0 +1,17 @@
+
+package com.yj.earth.dto.source;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+public class AddDirectoryDto {
+ @Schema (description = "资源ID")
+ private String id;
+ @Schema(description = "资源名称")
+ private String sourceName;
+ @Schema (description = "父级ID")
+ private String parentId;
+ @Schema (description = "树状索引")
+ private Integer treeIndex;
+}
diff --git a/src/main/java/com/yj/earth/dto/source/AddModelSourceDto.java b/src/main/java/com/yj/earth/dto/source/AddModelSourceDto.java
new file mode 100644
index 0000000..a525865
--- /dev/null
+++ b/src/main/java/com/yj/earth/dto/source/AddModelSourceDto.java
@@ -0,0 +1,18 @@
+package com.yj.earth.dto.source;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+public class AddModelSourceDto {
+ @Schema (description = "资源ID")
+ private String id;
+ @Schema(description = "资源路径")
+ private String sourcePath;
+ @Schema(description = "父节点ID")
+ private String parentId;
+ @Schema(description = "树状索引")
+ private Integer treeIndex;
+ @Schema(description = "前端参数")
+ private String params;
+}
diff --git a/src/main/java/com/yj/earth/dto/source/AddOtherSourceDto.java b/src/main/java/com/yj/earth/dto/source/AddOtherSourceDto.java
new file mode 100644
index 0000000..a4d8b19
--- /dev/null
+++ b/src/main/java/com/yj/earth/dto/source/AddOtherSourceDto.java
@@ -0,0 +1,22 @@
+package com.yj.earth.dto.source;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.Map;
+
+@Data
+public class AddOtherSourceDto {
+ @Schema (description = "资源ID")
+ private String id;
+ @Schema(description = "资源名称")
+ private String sourceName;
+ @Schema(description = "资源类型")
+ private String sourceType;
+ @Schema(description = "父级ID")
+ private String parentId;
+ @Schema(description = "树形索引")
+ private Integer treeIndex;
+ @Schema(description = "前端参数")
+ private Map params;
+}
diff --git a/src/main/java/com/yj/earth/dto/source/DragSourceDto.java b/src/main/java/com/yj/earth/dto/source/DragSourceDto.java
new file mode 100644
index 0000000..4f41a6d
--- /dev/null
+++ b/src/main/java/com/yj/earth/dto/source/DragSourceDto.java
@@ -0,0 +1,16 @@
+package com.yj.earth.dto.source;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+public class DragSourceDto {
+ @Schema(description = "主键")
+ private String id;
+
+ @Schema(description = "父级ID")
+ private String parentId;
+
+ @Schema(description = "树形索引")
+ private Integer treeIndex;
+}
diff --git a/src/main/java/com/yj/earth/dto/source/UpdateSourceDto.java b/src/main/java/com/yj/earth/dto/source/UpdateSourceDto.java
new file mode 100644
index 0000000..d438240
--- /dev/null
+++ b/src/main/java/com/yj/earth/dto/source/UpdateSourceDto.java
@@ -0,0 +1,27 @@
+package com.yj.earth.dto.source;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.Map;
+
+@Data
+public class UpdateSourceDto {
+ @Schema(description = "主键ID")
+ private String id;
+
+ @Schema(description = "资源名称")
+ private String sourceName;
+
+ @Schema(description = "上级ID")
+ private String parentId;
+
+ @Schema(description = "树形索引")
+ private Integer treeIndex;
+
+ @Schema(description = "是否显示")
+ private Integer isShow;
+
+ @Schema(description = "资源参数")
+ private Map params;
+}
diff --git a/src/main/java/com/yj/earth/dto/user/AddUserDto.java b/src/main/java/com/yj/earth/dto/user/AddUserDto.java
new file mode 100644
index 0000000..bc86dbe
--- /dev/null
+++ b/src/main/java/com/yj/earth/dto/user/AddUserDto.java
@@ -0,0 +1,26 @@
+package com.yj.earth.dto.user;
+
+import com.yj.earth.annotation.ExcludeField;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+public class AddUserDto {
+ @Schema(description = "用户名")
+ private String username;
+
+ @Schema(description = "密码")
+ private String password;
+
+ @Schema(description = "头像")
+ private String avatar;
+
+ @Schema(description = "昵称")
+ private String nickname;
+
+ @Schema(description = "手机号")
+ private String phone;
+
+ @Schema(description = "所属角色")
+ private String roleId;
+}
diff --git a/src/main/java/com/yj/earth/dto/user/UpdatePasswordDto.java b/src/main/java/com/yj/earth/dto/user/UpdatePasswordDto.java
new file mode 100644
index 0000000..fecada1
--- /dev/null
+++ b/src/main/java/com/yj/earth/dto/user/UpdatePasswordDto.java
@@ -0,0 +1,14 @@
+package com.yj.earth.dto.user;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+public class UpdatePasswordDto {
+ @Schema(description = "用户ID")
+ private String id;
+ @Schema(description = "旧密码")
+ private String oldPassword;
+ @Schema(description = "新密码")
+ private String newPassword;
+}
diff --git a/src/main/java/com/yj/earth/dto/user/UpdateUserDto.java b/src/main/java/com/yj/earth/dto/user/UpdateUserDto.java
new file mode 100644
index 0000000..4acd740
--- /dev/null
+++ b/src/main/java/com/yj/earth/dto/user/UpdateUserDto.java
@@ -0,0 +1,20 @@
+package com.yj.earth.dto.user;
+
+import com.yj.earth.annotation.ExcludeField;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+public class UpdateUserDto {
+ @Schema(description = "主键")
+ private String id;
+
+ @Schema(description = "头像")
+ private String avatar;
+
+ @Schema(description = "昵称")
+ private String nickname;
+
+ @Schema(description = "手机号")
+ private String phone;
+}
diff --git a/src/main/java/com/yj/earth/dto/user/UserLoginDto.java b/src/main/java/com/yj/earth/dto/user/UserLoginDto.java
new file mode 100644
index 0000000..8c1f5eb
--- /dev/null
+++ b/src/main/java/com/yj/earth/dto/user/UserLoginDto.java
@@ -0,0 +1,12 @@
+package com.yj.earth.dto.user;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+public class UserLoginDto {
+ @Schema(description = "用户名")
+ private String username;
+ @Schema(description = "密码")
+ private String password;
+}
diff --git a/src/main/java/com/yj/earth/model/Point.java b/src/main/java/com/yj/earth/model/Point.java
new file mode 100644
index 0000000..0877d54
--- /dev/null
+++ b/src/main/java/com/yj/earth/model/Point.java
@@ -0,0 +1,14 @@
+package com.yj.earth.model;
+
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class Point {
+ private double lat; // 纬度
+ private double lng; // 经度
+}
diff --git a/src/main/java/com/yj/earth/model/RouteRequest.java b/src/main/java/com/yj/earth/model/RouteRequest.java
new file mode 100644
index 0000000..6ee40a0
--- /dev/null
+++ b/src/main/java/com/yj/earth/model/RouteRequest.java
@@ -0,0 +1,22 @@
+package com.yj.earth.model;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class RouteRequest {
+ @Schema(description = "起点纬度")
+ private Double startLat;
+ @Schema(description = "起点经度")
+ private Double startLng;
+ @Schema(description = "终点纬度")
+ private Double endLat;
+ @Schema(description = "终点经度")
+ private Double endLng;
+ @Schema(description = "交通方式")
+ private String profile;
+ @Schema(description = "途经点")
+ private List waypoints;
+}
diff --git a/src/main/java/com/yj/earth/model/RouteResponse.java b/src/main/java/com/yj/earth/model/RouteResponse.java
new file mode 100644
index 0000000..26bdb54
--- /dev/null
+++ b/src/main/java/com/yj/earth/model/RouteResponse.java
@@ -0,0 +1,14 @@
+package com.yj.earth.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+public class RouteResponse {
+ private Double distanceKm; // 距离(公里)
+ private Double timeMinutes; // 时间(分钟)
+ private List pathPoints; // 路径点列表
+}
diff --git a/src/main/java/com/yj/earth/model/StatusResponse.java b/src/main/java/com/yj/earth/model/StatusResponse.java
new file mode 100644
index 0000000..4ef5b66
--- /dev/null
+++ b/src/main/java/com/yj/earth/model/StatusResponse.java
@@ -0,0 +1,12 @@
+package com.yj.earth.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+@Data
+@AllArgsConstructor
+public class StatusResponse {
+ private boolean loading; // 是否正在加载
+ private boolean loaded; // 是否已加载完成
+ private String message; // 状态消息
+}
diff --git a/src/main/java/com/yj/earth/params/BillboardObject.java b/src/main/java/com/yj/earth/params/BillboardObject.java
new file mode 100644
index 0000000..57493f2
--- /dev/null
+++ b/src/main/java/com/yj/earth/params/BillboardObject.java
@@ -0,0 +1,222 @@
+package com.yj.earth.params;
+
+import com.yj.earth.annotation.SourceType;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+@Schema(description = "点标注对象")
+@SourceType("point")
+public class BillboardObject {
+ @Schema(description = "唯一标识")
+ private String id;
+
+ @Schema(description = "标注整体的显隐", defaultValue = "true")
+ private boolean show = true;
+
+ @Schema(description = "名称")
+ private String name;
+
+ @Schema(description = "位置(必填)")
+ private Position position = new Position();
+
+ @Schema(description = "高度模式(0:海拔高度;1:相对地表;2:依附地表; 3:依附模型)", defaultValue = "3")
+ private int heightMode = 3;
+
+ @Schema(description = "是否开启跟随视野缩放", defaultValue = "true")
+ private boolean scaleByDistance = true;
+
+ @Schema(description = "视野缩放最近距离", defaultValue = "2000")
+ private int near = 2000;
+
+ @Schema(description = "视野缩放最远距离", defaultValue = "100000")
+ private int far = 100000;
+
+ @Schema(description = "图标参数")
+ private Billboard billboard = new Billboard();
+
+ @Schema(description = "文字参数")
+ private Label label = new Label();
+
+ @Schema(description = "属性内容")
+ private Attribute attribute = new Attribute();
+
+ @Schema(description = "富文本内容")
+ private String richTextContent;
+
+ @Schema(description = "默认视角")
+ private CustomView customView = new CustomView();
+
+ @Data
+ @Schema(description = "位置属性")
+ public static class Position {
+ @Schema(description = "经度")
+ private double lng;
+
+ @Schema(description = "纬度")
+ private double lat;
+
+ @Schema(description = "高度")
+ private double alt;
+ }
+
+ @Data
+ @Schema(description = "图标参数")
+ public static class Billboard {
+ @Schema(description = "图标显隐", defaultValue = "true")
+ private boolean show = true;
+
+ @Schema(description = "图标路径")
+ private String image;
+
+ @Schema(description = "默认图标的唯一标识")
+ private String defaultImage;
+
+ @Schema(description = "图标放大倍数", defaultValue = "3")
+ private int scale = 3;
+ }
+
+ @Data
+ @Schema(description = "文字参数")
+ public static class Label {
+ @Schema(description = "文字内容")
+ private String text;
+
+ @Schema(description = "文字显隐", defaultValue = "true")
+ private boolean show = true;
+
+ @Schema(description = "文字字体项(0:黑体;1:思源黑体;2:庞门正道标题体;3:数黑体)", defaultValue = "0")
+ private int fontFamily = 0;
+
+ @Schema(description = "文字大小、单位px", defaultValue = "39")
+ private int fontSize = 39;
+
+ @Schema(description = "文字颜色", defaultValue = "#00ffff")
+ private String color = "#00ffff";
+ }
+
+ @Data
+ @Schema(description = "属性内容")
+ public static class Attribute {
+ @Schema(description = "链接")
+ private Link link = new Link();
+
+ @Schema(description = "全景图")
+ private Vr vr = new Vr();
+
+ @Schema(description = "摄像头")
+ private Camera camera = new Camera();
+
+ @Schema(description = "ISC")
+ private Isc isc = new Isc();
+
+ @Schema(description = "物资")
+ private Goods goods = new Goods();
+
+ @Data
+ @Schema(description = "链接属性")
+ public static class Link {
+ @Schema(description = "链接内容列表")
+ private List content = new ArrayList<>();
+
+ @Data
+ @Schema(description = "链接内容")
+ public static class LinkContent {
+ @Schema(description = "链接名称")
+ private String name;
+
+ @Schema(description = "链接地址")
+ private String url;
+ }
+ }
+
+ @Data
+ @Schema(description = "全景图属性")
+ public static class Vr {
+ @Schema(description = "全景图内容列表")
+ private List content = new ArrayList<>();
+
+ @Data
+ @Schema(description = "全景图内容")
+ public static class VrContent {
+ @Schema(description = "名称")
+ private String name;
+
+ @Schema(description = "地址")
+ private String url;
+ }
+ }
+
+ @Data
+ @Schema(description = "摄像头属性")
+ public static class Camera {
+ @Schema(description = "摄像头内容列表")
+ private List content = new ArrayList<>();
+ }
+
+ @Data
+ @Schema(description = "ISC属性")
+ public static class Isc {
+ @Schema(description = "ISC内容列表")
+ private List content = new ArrayList<>();
+ }
+
+ @Data
+ @Schema(description = "物资属性")
+ public static class Goods {
+ @Schema(description = "物资内容列表")
+ private List content = new ArrayList<>();
+
+ @Data
+ @Schema(description = "物资内容")
+ public static class GoodsContent {
+ @Schema(description = "id")
+ private String id;
+
+ @Schema(description = "名称")
+ private String name;
+
+ @Schema(description = "数量")
+ private String cnt;
+ }
+ }
+ }
+
+ @Data
+ @Schema(description = "默认视角属性")
+ public static class CustomView {
+ @Schema(description = "默认视角方位")
+ private Orientation orientation = new Orientation();
+
+ @Schema(description = "视角相对位置")
+ private RelativePosition relativePosition = new RelativePosition();
+
+ @Data
+ @Schema(description = "视角方位属性")
+ public static class Orientation {
+ @Schema(description = "航向角")
+ private double heading;
+
+ @Schema(description = "俯仰角")
+ private double pitch;
+
+ @Schema(description = "翻滚角")
+ private double roll;
+ }
+
+ @Data
+ @Schema(description = "视角相对位置属性")
+ public static class RelativePosition {
+ @Schema(description = "经度")
+ private double lng;
+
+ @Schema(description = "纬度")
+ private double lat;
+
+ @Schema(description = "高度")
+ private double alt;
+ }
+ }
+}
diff --git a/src/main/java/com/yj/earth/params/Circle.java b/src/main/java/com/yj/earth/params/Circle.java
new file mode 100644
index 0000000..58485c0
--- /dev/null
+++ b/src/main/java/com/yj/earth/params/Circle.java
@@ -0,0 +1,70 @@
+package com.yj.earth.params;
+
+import com.yj.earth.annotation.SourceType;
+import lombok.Data;
+import java.util.List;
+import java.util.Map;
+
+@Data
+@SourceType("circle")
+public class Circle {
+ private String id;
+ private String name;
+ private Center center;
+ private int radius;
+ private Map customView;
+ private boolean show;
+ private String color;
+ private int heightMode;
+ private Line line;
+ private Label label;
+ private Attribute attribute;
+ private String richTextContent;
+
+ @Data
+ public static class Center {
+ private double lng;
+ private double lat;
+ private double alt;
+ }
+
+ @Data
+ public static class Line {
+ private int width;
+ private String color;
+ }
+
+ @Data
+ public static class Label {
+ private String text;
+ private boolean show;
+ private Position position;
+ private int fontSize;
+ private int fontFamily;
+ private String color;
+ private int lineWidth;
+ private int pixelOffset;
+ private List backgroundColor;
+ private String lineColor;
+ private boolean scaleByDistance;
+ private int near;
+ private int far;
+
+ @Data
+ public static class Position {
+ private double lng;
+ private double lat;
+ private double alt;
+ }
+ }
+
+ @Data
+ public static class Attribute {
+ private Link link;
+
+ @Data
+ public static class Link {
+ private List content;
+ }
+ }
+}
diff --git a/src/main/java/com/yj/earth/params/CurvelineObject.java b/src/main/java/com/yj/earth/params/CurvelineObject.java
new file mode 100644
index 0000000..d43e1cf
--- /dev/null
+++ b/src/main/java/com/yj/earth/params/CurvelineObject.java
@@ -0,0 +1,192 @@
+package com.yj.earth.params;
+
+import com.yj.earth.annotation.SourceType;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+@Schema(description = "曲线对象")
+@SourceType("curve")
+public class CurvelineObject {
+ @Schema(description = "唯一标识")
+ private String id;
+
+ @Schema(description = "名称")
+ private String name;
+
+ @Schema(description = "首尾相反", defaultValue = "false")
+ private boolean rotate = false;
+
+ @Schema(description = "间距", defaultValue = "1")
+
+ private int space = 1;
+ @Schema(description = "速度", defaultValue = "10")
+
+ private String speed = "10";
+ @Schema(description = "空间单位名称", defaultValue = "0")
+ private String wordsName;
+
+ @Schema(description = "长度单位", defaultValue = "0")
+ private String lengthUnit;
+
+ @Schema(description = "线宽", defaultValue = "3")
+ private double width = 3;
+
+ @Schema(description = "颜色", defaultValue = "#ff0000")
+ private String color = "#ff0000";
+
+ @Schema(description = "材质类型 0-实线 1-虚线 2-泛光...", defaultValue = "0")
+ private int type = 0;
+
+ @Schema(description = "高度模式(0:海拔高度;1:相对高度;2:依附模式)", defaultValue = "2")
+ private int heightMode = 2;
+
+ @Schema(description = "首尾相连", defaultValue = "false")
+ private boolean noseToTail = false;
+
+ @Schema(description = "线缓冲", defaultValue = "false")
+ private boolean extend = false;
+
+ @Schema(description = "线缓冲宽度", defaultValue = "10")
+ private double extendWidth = 10;
+
+ @Schema(description = "线缓冲颜色", defaultValue = "rgba(255,255,80,0.3)")
+ private String extendColor = "rgba(255,255,80,0.3)";
+
+ @Schema(description = "显隐", defaultValue = "true")
+ private boolean show = true;
+
+ @Schema(description = "经纬度和高度的列表(必填)")
+ private List positions = new ArrayList<>();
+
+ @Schema(description = "标签对象")
+ private Label label = new Label();
+
+ @Schema(description = "属性内容")
+ private Attribute attribute = new Attribute();
+
+ @Schema(description = "富文本内容")
+ private String richTextContent;
+
+ @Schema(description = "默认视角")
+ private CustomView customView = new CustomView();
+
+ @Data
+ @Schema(description = "位置属性")
+ public static class Position {
+ @Schema(description = "经度")
+ private double lng;
+
+ @Schema(description = "纬度")
+ private double lat;
+
+ @Schema(description = "高度")
+ private double alt;
+ }
+
+ @Data
+ @Schema(description = "标签参数")
+ public static class Label {
+ @Schema(description = "标签文本")
+ private String text;
+
+ @Schema(description = "标签显隐")
+ private Boolean show;
+
+ @Schema(description = "标签位置")
+ private Position position = new Position();
+
+ @Schema(description = "字体大小", defaultValue = "20")
+ private int fontSize = 20;
+
+ @Schema(description = "字体项 0:黑体;1:思源黑体;2:庞门正道标题体;3:数黑体", defaultValue = "0")
+ private int fontFamily = 0;
+
+ @Schema(description = "字体颜色", defaultValue = "#ffffff")
+ private String color = "#ffffff";
+
+ @Schema(description = "引线宽", defaultValue = "4")
+ private double lineWidth = 4;
+
+ @Schema(description = "引线颜色", defaultValue = "#00ffff80")
+ private String lineColor = "#00ffff80";
+
+ @Schema(description = "字体偏移(引线长度)", defaultValue = "20")
+ private double pixelOffset = 20;
+
+ @Schema(description = "背景颜色", defaultValue = "['#00ffff80', '#00ffff80']")
+ private String[] backgroundColor = {"#00ffff80", "#00ffff80"};
+
+ @Schema(description = "距离缩放")
+ private Boolean scaleByDistance;
+
+ @Schema(description = "视野缩放最近距离", defaultValue = "2000")
+ private int near = 2000;
+
+ @Schema(description = "视野缩放最远距离", defaultValue = "100000")
+ private int far = 100000;
+ }
+
+ @Data
+ @Schema(description = "属性内容")
+ public static class Attribute {
+ @Schema(description = "链接")
+ private Link link = new Link();
+
+ @Data
+ @Schema(description = "链接属性")
+ public static class Link {
+ @Schema(description = "链接内容列表", defaultValue = "[]")
+ private List content = new ArrayList<>();
+
+ @Data
+ @Schema(description = "链接内容")
+ public static class LinkContent {
+ @Schema(description = "链接名称")
+ private String name;
+
+ @Schema(description = "链接地址")
+ private String url;
+ }
+ }
+ }
+
+ @Data
+ @Schema(description = "默认视角属性")
+ public static class CustomView {
+ @Schema(description = "默认视角方位")
+ private Orientation orientation = new Orientation();
+
+ @Schema(description = "视角相对位置")
+ private RelativePosition relativePosition = new RelativePosition();
+
+ @Data
+ @Schema(description = "视角方位属性")
+ public static class Orientation {
+ @Schema(description = "航向角")
+ private double heading;
+
+ @Schema(description = "俯仰角")
+ private double pitch;
+
+ @Schema(description = "翻滚角")
+ private double roll;
+ }
+
+ @Data
+ @Schema(description = "视角相对位置属性")
+ public static class RelativePosition {
+ @Schema(description = "经度")
+ private double lng;
+
+ @Schema(description = "纬度")
+ private double lat;
+
+ @Schema(description = "高度")
+ private double alt;
+ }
+ }
+}
diff --git a/src/main/java/com/yj/earth/params/GroundText.java b/src/main/java/com/yj/earth/params/GroundText.java
new file mode 100644
index 0000000..89e1d67
--- /dev/null
+++ b/src/main/java/com/yj/earth/params/GroundText.java
@@ -0,0 +1,26 @@
+package com.yj.earth.params;
+
+import com.yj.earth.annotation.SourceType;
+import lombok.Data;
+
+import java.util.Map;
+
+@Data
+@SourceType("groundText")
+public class GroundText {
+ private String id;
+ private Map customView;
+ private boolean show;
+ private String text;
+ private double angle;
+ private int scale;
+ private int speed;
+ private String color;
+ private Position position;
+
+ @Data
+ public static class Position {
+ private double lng;
+ private double lat;
+ }
+}
diff --git a/src/main/java/com/yj/earth/params/Layer.java b/src/main/java/com/yj/earth/params/Layer.java
new file mode 100644
index 0000000..4fa9775
--- /dev/null
+++ b/src/main/java/com/yj/earth/params/Layer.java
@@ -0,0 +1,11 @@
+package com.yj.earth.params;
+
+import com.yj.earth.annotation.SourceType;
+import lombok.Data;
+
+@Data
+@SourceType("layer")
+public class Layer {
+ private Integer alpha;
+ private Integer brightness;
+}
diff --git a/src/main/java/com/yj/earth/params/PolygonAttackArrowObject.java b/src/main/java/com/yj/earth/params/PolygonAttackArrowObject.java
new file mode 100644
index 0000000..3cf4a83
--- /dev/null
+++ b/src/main/java/com/yj/earth/params/PolygonAttackArrowObject.java
@@ -0,0 +1,187 @@
+package com.yj.earth.params;
+
+import com.yj.earth.annotation.SourceType;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+@Schema(description = "箭头对象")
+@SourceType("attackArrow")
+public class PolygonAttackArrowObject {
+ @Schema(description = "唯一标识")
+ private String id;
+
+ @Schema(description = "显示/隐藏", defaultValue = "true")
+ private boolean show = true;
+
+ @Schema(description = "名称")
+ private String name;
+
+ @Schema(description = "颜色", defaultValue = "rgba(255, 0, 0, 0.5)")
+ private String color = "rgba(255, 0, 0, 0.5)";
+
+ @Schema(description = "高度")
+ private double height;
+
+ @Schema(description = "高度模式(0:海拔高度;1:相对地表;2:依附模式)", defaultValue = "2")
+ private int heightMode = 2;
+
+ @Schema(description = "面积单位", defaultValue = "平方米")
+ private String areaUnit = "平方米";
+
+ @Schema(description = "边框")
+ private Line line = new Line();
+
+ @Schema(description = "经纬度和高度的列表(必填)")
+ private List positions = new ArrayList<>();
+
+ @Schema(description = "动画", defaultValue = "false")
+ private boolean spreadState = false;
+
+ @Schema(description = "动画重复", defaultValue = "false")
+ private boolean loop = false;
+
+ @Schema(description = "动画持续时长(毫秒)", defaultValue = "3000")
+ private int spreadTime = 3000;
+
+ @Schema(description = "标签对象")
+ private Label label = new Label();
+
+ @Schema(description = "属性内容")
+ private Attribute attribute = new Attribute();
+
+ @Schema(description = "富文本内容")
+ private String richTextContent;
+
+ @Schema(description = "默认视角")
+ private CustomView customView = new CustomView();
+
+ @Data
+ @Schema(description = "边框属性")
+ public static class Line {
+ @Schema(description = "边框宽", defaultValue = "2")
+ private double width = 2;
+
+ @Schema(description = "边框颜色", defaultValue = "rgba(155, 155, 124, 0.89)")
+ private String color = "rgba(155, 155, 124, 0.89)";
+ }
+
+ @Data
+ @Schema(description = "位置属性")
+ public static class Position {
+ @Schema(description = "经度")
+ private double lng;
+
+ @Schema(description = "纬度")
+ private double lat;
+
+ @Schema(description = "高度")
+ private double alt;
+ }
+
+ @Data
+ @Schema(description = "标签参数")
+ public static class Label {
+ @Schema(description = "标签文本")
+ private String text;
+
+ @Schema(description = "标签显隐")
+ private Boolean show;
+
+ @Schema(description = "标签位置")
+ private Position position = new Position();
+
+ @Schema(description = "字体大小", defaultValue = "20")
+ private int fontSize = 20;
+
+ @Schema(description = "字体项 0:黑体;1:思源黑体;2:庞门正道标题体;3:数黑体", defaultValue = "0")
+ private int fontFamily = 0;
+
+ @Schema(description = "字体颜色", defaultValue = "#ffffff")
+ private String color = "#ffffff";
+
+ @Schema(description = "引线宽", defaultValue = "4")
+ private double lineWidth = 4;
+
+ @Schema(description = "引线颜色", defaultValue = "#00ffff80")
+ private String lineColor = "#00ffff80";
+
+ @Schema(description = "字体偏移(引线长度)", defaultValue = "20")
+ private double pixelOffset = 20;
+
+ @Schema(description = "背景颜色", defaultValue = "['#00ffff80', '#00ffff80']")
+ private String[] backgroundColor = {"#00ffff80", "#00ffff80"};
+
+ @Schema(description = "距离缩放", defaultValue = "false")
+ private boolean scaleByDistance = false;
+
+ @Schema(description = "视野缩放最近距离", defaultValue = "2000")
+ private int near = 2000;
+
+ @Schema(description = "视野缩放最远距离", defaultValue = "100000")
+ private int far = 100000;
+ }
+
+ @Data
+ @Schema(description = "属性内容")
+ public static class Attribute {
+ @Schema(description = "链接", defaultValue = "{}")
+ private Link link = new Link();
+
+ @Data
+ @Schema(description = "链接属性")
+ public static class Link {
+ @Schema(description = "链接内容列表", defaultValue = "[]")
+ private List content = new ArrayList<>();
+
+ @Data
+ @Schema(description = "链接内容")
+ public static class LinkContent {
+ @Schema(description = "链接名称")
+ private String name;
+
+ @Schema(description = "链接地址")
+ private String url;
+ }
+ }
+ }
+
+ @Data
+ @Schema(description = "默认视角属性")
+ public static class CustomView {
+ @Schema(description = "默认视角方位")
+ private Orientation orientation = new Orientation();
+
+ @Schema(description = "视角相对位置")
+ private RelativePosition relativePosition = new RelativePosition();
+
+ @Data
+ @Schema(description = "视角方位属性")
+ public static class Orientation {
+ @Schema(description = "航向角")
+ private double heading;
+
+ @Schema(description = "俯仰角")
+ private double pitch;
+
+ @Schema(description = "翻滚角")
+ private double roll;
+ }
+
+ @Data
+ @Schema(description = "视角相对位置属性")
+ public static class RelativePosition {
+ @Schema(description = "经度")
+ private double lng;
+
+ @Schema(description = "纬度")
+ private double lat;
+
+ @Schema(description = "高度")
+ private double alt;
+ }
+ }
+}
+
\ No newline at end of file
diff --git a/src/main/java/com/yj/earth/params/PolygonPanelObject.java b/src/main/java/com/yj/earth/params/PolygonPanelObject.java
new file mode 100644
index 0000000..0963234
--- /dev/null
+++ b/src/main/java/com/yj/earth/params/PolygonPanelObject.java
@@ -0,0 +1,178 @@
+package com.yj.earth.params;
+
+import com.yj.earth.annotation.SourceType;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+@Schema(description = "多边形对象")
+@SourceType("panel")
+public class PolygonPanelObject {
+ @Schema(description = "唯一标识")
+ private String id;
+
+ @Schema(description = "显示/隐藏", defaultValue = "true")
+ private boolean show = true;
+
+ @Schema(description = "名称")
+ private String name;
+
+ @Schema(description = "颜色", defaultValue = "rgba(255, 0, 0, 0.5)")
+ private String color = "rgba(255, 0, 0, 0.5)";
+
+ @Schema(description = "高度")
+ private double height;
+
+ @Schema(description = "高度模式(0:海拔高度;1:相对地表;2:依附模式)", defaultValue = "2")
+ private int heightMode = 2;
+
+ @Schema(description = "面积单位", defaultValue = "平方米")
+ private String areaUnit = "平方米";
+
+ @Schema(description = "边框")
+ private Line line = new Line();
+
+ @Schema(description = "经纬度和高度的列表(必填)")
+ private List positions = new ArrayList<>();
+
+ @Schema(description = "标签对象")
+ private Label label = new Label();
+
+ @Schema(description = "属性内容")
+ private Attribute attribute = new Attribute();
+
+ @Schema(description = "富文本内容")
+ private String richTextContent;
+
+ @Schema(description = "默认视角")
+ private CustomView customView = new CustomView();
+
+ @Data
+ @Schema(description = "边框属性")
+ public static class Line {
+ @Schema(description = "边框宽", defaultValue = "2")
+ private double width = 2;
+
+ @Schema(description = "边框颜色", defaultValue = "rgba(155, 155, 124, 0.89)")
+ private String color = "rgba(155, 155, 124, 0.89)";
+ }
+
+ @Data
+ @Schema(description = "位置属性")
+ public static class Position {
+ @Schema(description = "经度")
+ private double lng;
+
+ @Schema(description = "纬度")
+ private double lat;
+
+ @Schema(description = "高度")
+ private double alt;
+ }
+
+ @Data
+ @Schema(description = "标签参数")
+ public static class Label {
+ @Schema(description = "标签文本")
+ private String text;
+
+ @Schema(description = "标签显隐")
+ private Boolean show;
+
+ @Schema(description = "标签位置")
+ private Position position = new Position();
+
+ @Schema(description = "字体大小", defaultValue = "20")
+ private int fontSize = 20;
+
+ @Schema(description = "字体项 0:黑体;1:思源黑体;2:庞门正道标题体;3:数黑体", defaultValue = "0")
+ private int fontFamily = 0;
+
+ @Schema(description = "字体颜色", defaultValue = "#ffffff")
+ private String color = "#ffffff";
+
+ @Schema(description = "引线宽", defaultValue = "4")
+ private double lineWidth = 4;
+
+ @Schema(description = "引线颜色", defaultValue = "#00ffff80")
+ private String lineColor = "#00ffff80";
+
+ @Schema(description = "字体偏移(引线长度)", defaultValue = "20")
+ private double pixelOffset = 20;
+
+ @Schema(description = "背景颜色", defaultValue = "['#00ffff80', '#00ffff80']")
+ private String[] backgroundColor = {"#00ffff80", "#00ffff80"};
+
+ @Schema(description = "距离缩放", defaultValue = "false")
+ private boolean scaleByDistance = false;
+
+ @Schema(description = "视野缩放最近距离", defaultValue = "2000")
+ private int near = 2000;
+
+ @Schema(description = "视野缩放最远距离", defaultValue = "100000")
+ private int far = 100000;
+ }
+
+ @Data
+ @Schema(description = "属性内容")
+ public static class Attribute {
+ @Schema(description = "链接", defaultValue = "{}")
+ private Link link = new Link();
+
+ @Data
+ @Schema(description = "链接属性")
+ public static class Link {
+ @Schema(description = "链接内容列表", defaultValue = "[]")
+ private List content = new ArrayList<>();
+
+ @Data
+ @Schema(description = "链接内容")
+ public static class LinkContent {
+ @Schema(description = "链接名称")
+ private String name;
+
+ @Schema(description = "链接地址")
+ private String url;
+ }
+ }
+ }
+
+ @Data
+ @Schema(description = "默认视角属性")
+ public static class CustomView {
+ @Schema(description = "默认视角方位")
+ private Orientation orientation = new Orientation();
+
+ @Schema(description = "视角相对位置")
+ private RelativePosition relativePosition = new RelativePosition();
+
+ @Data
+ @Schema(description = "视角方位属性")
+ public static class Orientation {
+ @Schema(description = "航向角")
+ private double heading;
+
+ @Schema(description = "俯仰角")
+ private double pitch;
+
+ @Schema(description = "翻滚角")
+ private double roll;
+ }
+
+ @Data
+ @Schema(description = "视角相对位置属性")
+ public static class RelativePosition {
+ @Schema(description = "经度")
+ private double lng;
+
+ @Schema(description = "纬度")
+ private double lat;
+
+ @Schema(description = "高度")
+ private double alt;
+ }
+ }
+}
+
\ No newline at end of file
diff --git a/src/main/java/com/yj/earth/params/PolygonPincerArrowObject.java b/src/main/java/com/yj/earth/params/PolygonPincerArrowObject.java
new file mode 100644
index 0000000..2a43800
--- /dev/null
+++ b/src/main/java/com/yj/earth/params/PolygonPincerArrowObject.java
@@ -0,0 +1,187 @@
+package com.yj.earth.params;
+
+import com.yj.earth.annotation.SourceType;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+@Schema(description = "钳形箭头对象")
+@SourceType("pincerArrow")
+public class PolygonPincerArrowObject {
+ @Schema(description = "唯一标识")
+ private String id;
+
+ @Schema(description = "显示/隐藏", defaultValue = "true")
+ private boolean show = true;
+
+ @Schema(description = "名称")
+ private String name;
+
+ @Schema(description = "颜色", defaultValue = "rgba(255, 0, 0, 0.5)")
+ private String color = "rgba(255, 0, 0, 0.5)";
+
+ @Schema(description = "高度")
+ private double height;
+
+ @Schema(description = "高度模式(0:海拔高度;1:相对地表;2:依附模式)", defaultValue = "2")
+ private int heightMode = 2;
+
+ @Schema(description = "面积单位", defaultValue = "平方米")
+ private String areaUnit = "平方米";
+
+ @Schema(description = "边框")
+ private Line line = new Line();
+
+ @Schema(description = "经纬度和高度的列表(必填)")
+ private List positions = new ArrayList<>();
+
+ @Schema(description = "动画", defaultValue = "false")
+ private boolean spreadState = false;
+
+ @Schema(description = "动画重复", defaultValue = "false")
+ private boolean loop = false;
+
+ @Schema(description = "动画持续时长(毫秒)", defaultValue = "3000")
+ private int spreadTime = 3000;
+
+ @Schema(description = "标签对象")
+ private Label label = new Label();
+
+ @Schema(description = "属性内容")
+ private Attribute attribute = new Attribute();
+
+ @Schema(description = "富文本内容")
+ private String richTextContent;
+
+ @Schema(description = "默认视角")
+ private CustomView customView = new CustomView();
+
+ @Data
+ @Schema(description = "边框属性")
+ public static class Line {
+ @Schema(description = "边框宽", defaultValue = "2")
+ private double width = 2;
+
+ @Schema(description = "边框颜色", defaultValue = "rgba(155, 155, 124, 0.89)")
+ private String color = "rgba(155, 155, 124, 0.89)";
+ }
+
+ @Data
+ @Schema(description = "位置属性")
+ public static class Position {
+ @Schema(description = "经度")
+ private double lng;
+
+ @Schema(description = "纬度")
+ private double lat;
+
+ @Schema(description = "高度")
+ private double alt;
+ }
+
+ @Data
+ @Schema(description = "标签参数")
+ public static class Label {
+ @Schema(description = "标签文本")
+ private String text;
+
+ @Schema(description = "标签显隐")
+ private Boolean show;
+
+ @Schema(description = "标签位置")
+ private Position position = new Position();
+
+ @Schema(description = "字体大小", defaultValue = "20")
+ private int fontSize = 20;
+
+ @Schema(description = "字体项 0:黑体;1:思源黑体;2:庞门正道标题体;3:数黑体", defaultValue = "0")
+ private int fontFamily = 0;
+
+ @Schema(description = "字体颜色", defaultValue = "#ffffff")
+ private String color = "#ffffff";
+
+ @Schema(description = "引线宽", defaultValue = "4")
+ private double lineWidth = 4;
+
+ @Schema(description = "引线颜色", defaultValue = "#00ffff80")
+ private String lineColor = "#00ffff80";
+
+ @Schema(description = "字体偏移(引线长度)", defaultValue = "20")
+ private double pixelOffset = 20;
+
+ @Schema(description = "背景颜色", defaultValue = "['#00ffff80', '#00ffff80']")
+ private String[] backgroundColor = {"#00ffff80", "#00ffff80"};
+
+ @Schema(description = "距离缩放", defaultValue = "false")
+ private boolean scaleByDistance = false;
+
+ @Schema(description = "视野缩放最近距离", defaultValue = "2000")
+ private int near = 2000;
+
+ @Schema(description = "视野缩放最远距离", defaultValue = "100000")
+ private int far = 100000;
+ }
+
+ @Data
+ @Schema(description = "属性内容")
+ public static class Attribute {
+ @Schema(description = "链接", defaultValue = "{}")
+ private Link link = new Link();
+
+ @Data
+ @Schema(description = "链接属性")
+ public static class Link {
+ @Schema(description = "链接内容列表", defaultValue = "[]")
+ private List content = new ArrayList<>();
+
+ @Data
+ @Schema(description = "链接内容")
+ public static class LinkContent {
+ @Schema(description = "链接名称")
+ private String name;
+
+ @Schema(description = "链接地址")
+ private String url;
+ }
+ }
+ }
+
+ @Data
+ @Schema(description = "默认视角属性")
+ public static class CustomView {
+ @Schema(description = "默认视角方位")
+ private Orientation orientation = new Orientation();
+
+ @Schema(description = "视角相对位置")
+ private RelativePosition relativePosition = new RelativePosition();
+
+ @Data
+ @Schema(description = "视角方位属性")
+ public static class Orientation {
+ @Schema(description = "航向角")
+ private double heading;
+
+ @Schema(description = "俯仰角")
+ private double pitch;
+
+ @Schema(description = "翻滚角")
+ private double roll;
+ }
+
+ @Data
+ @Schema(description = "视角相对位置属性")
+ public static class RelativePosition {
+ @Schema(description = "经度")
+ private double lng;
+
+ @Schema(description = "纬度")
+ private double lat;
+
+ @Schema(description = "高度")
+ private double alt;
+ }
+ }
+}
+
\ No newline at end of file
diff --git a/src/main/java/com/yj/earth/params/PolygonRectangleObject.java b/src/main/java/com/yj/earth/params/PolygonRectangleObject.java
new file mode 100644
index 0000000..607ff51
--- /dev/null
+++ b/src/main/java/com/yj/earth/params/PolygonRectangleObject.java
@@ -0,0 +1,178 @@
+package com.yj.earth.params;
+
+import com.yj.earth.annotation.SourceType;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+@Schema(description = "矩形对象")
+@SourceType("rectangle")
+public class PolygonRectangleObject {
+ @Schema(description = "唯一标识")
+ private String id;
+
+ @Schema(description = "显示/隐藏", defaultValue = "true")
+ private boolean show = true;
+
+ @Schema(description = "名称")
+ private String name;
+
+ @Schema(description = "颜色", defaultValue = "rgba(255, 0, 0, 0.5)")
+ private String color = "rgba(255, 0, 0, 0.5)";
+
+ @Schema(description = "高度")
+ private double height;
+
+ @Schema(description = "高度模式(0:海拔高度;1:相对地表;2:依附模式)", defaultValue = "2")
+ private int heightMode = 2;
+
+ @Schema(description = "面积单位", defaultValue = "平方米")
+ private String areaUnit = "平方米";
+
+ @Schema(description = "边框")
+ private Line line = new Line();
+
+ @Schema(description = "经纬度和高度的列表(必填)")
+ private List positions = new ArrayList<>();
+
+ @Schema(description = "标签对象")
+ private Label label = new Label();
+
+ @Schema(description = "属性内容")
+ private Attribute attribute = new Attribute();
+
+ @Schema(description = "富文本内容")
+ private String richTextContent;
+
+ @Schema(description = "默认视角")
+ private CustomView customView = new CustomView();
+
+ @Data
+ @Schema(description = "边框属性")
+ public static class Line {
+ @Schema(description = "边框宽", defaultValue = "2")
+ private double width = 2;
+
+ @Schema(description = "边框颜色", defaultValue = "rgba(155, 155, 124, 0.89)")
+ private String color = "rgba(155, 155, 124, 0.89)";
+ }
+
+ @Data
+ @Schema(description = "位置属性")
+ public static class Position {
+ @Schema(description = "经度")
+ private double lng;
+
+ @Schema(description = "纬度")
+ private double lat;
+
+ @Schema(description = "高度")
+ private double alt;
+ }
+
+ @Data
+ @Schema(description = "标签参数")
+ public static class Label {
+ @Schema(description = "标签文本")
+ private String text;
+
+ @Schema(description = "标签显隐")
+ private Boolean show;
+
+ @Schema(description = "标签位置")
+ private Position position = new Position();
+
+ @Schema(description = "字体大小", defaultValue = "20")
+ private int fontSize = 20;
+
+ @Schema(description = "字体项 0:黑体;1:思源黑体;2:庞门正道标题体;3:数黑体", defaultValue = "0")
+ private int fontFamily = 0;
+
+ @Schema(description = "字体颜色", defaultValue = "#ffffff")
+ private String color = "#ffffff";
+
+ @Schema(description = "引线宽", defaultValue = "4")
+ private double lineWidth = 4;
+
+ @Schema(description = "引线颜色", defaultValue = "#00ffff80")
+ private String lineColor = "#00ffff80";
+
+ @Schema(description = "字体偏移(引线长度)", defaultValue = "20")
+ private double pixelOffset = 20;
+
+ @Schema(description = "背景颜色", defaultValue = "['#00ffff80', '#00ffff80']")
+ private String[] backgroundColor = {"#00ffff80", "#00ffff80"};
+
+ @Schema(description = "距离缩放", defaultValue = "false")
+ private boolean scaleByDistance = false;
+
+ @Schema(description = "视野缩放最近距离", defaultValue = "2000")
+ private int near = 2000;
+
+ @Schema(description = "视野缩放最远距离", defaultValue = "100000")
+ private int far = 100000;
+ }
+
+ @Data
+ @Schema(description = "属性内容")
+ public static class Attribute {
+ @Schema(description = "链接", defaultValue = "{}")
+ private Link link = new Link();
+
+ @Data
+ @Schema(description = "链接属性")
+ public static class Link {
+ @Schema(description = "链接内容列表", defaultValue = "[]")
+ private List content = new ArrayList<>();
+
+ @Data
+ @Schema(description = "链接内容")
+ public static class LinkContent {
+ @Schema(description = "链接名称")
+ private String name;
+
+ @Schema(description = "链接地址")
+ private String url;
+ }
+ }
+ }
+
+ @Data
+ @Schema(description = "默认视角属性")
+ public static class CustomView {
+ @Schema(description = "默认视角方位")
+ private Orientation orientation = new Orientation();
+
+ @Schema(description = "视角相对位置")
+ private RelativePosition relativePosition = new RelativePosition();
+
+ @Data
+ @Schema(description = "视角方位属性")
+ public static class Orientation {
+ @Schema(description = "航向角")
+ private double heading;
+
+ @Schema(description = "俯仰角")
+ private double pitch;
+
+ @Schema(description = "翻滚角")
+ private double roll;
+ }
+
+ @Data
+ @Schema(description = "视角相对位置属性")
+ public static class RelativePosition {
+ @Schema(description = "经度")
+ private double lng;
+
+ @Schema(description = "纬度")
+ private double lat;
+
+ @Schema(description = "高度")
+ private double alt;
+ }
+ }
+}
+
\ No newline at end of file
diff --git a/src/main/java/com/yj/earth/params/PolygonRendezvousObject.java b/src/main/java/com/yj/earth/params/PolygonRendezvousObject.java
new file mode 100644
index 0000000..f4aa1d4
--- /dev/null
+++ b/src/main/java/com/yj/earth/params/PolygonRendezvousObject.java
@@ -0,0 +1,178 @@
+package com.yj.earth.params;
+
+import com.yj.earth.annotation.SourceType;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+@Schema(description = "集结地对象")
+@SourceType("rendezvous")
+public class PolygonRendezvousObject {
+ @Schema(description = "唯一标识")
+ private String id;
+
+ @Schema(description = "显示/隐藏", defaultValue = "true")
+ private boolean show = true;
+
+ @Schema(description = "名称")
+ private String name;
+
+ @Schema(description = "颜色", defaultValue = "rgba(255, 0, 0, 0.5)")
+ private String color = "rgba(255, 0, 0, 0.5)";
+
+ @Schema(description = "高度")
+ private double height;
+
+ @Schema(description = "高度模式(0:海拔高度;1:相对地表;2:依附模式)", defaultValue = "2")
+ private int heightMode = 2;
+
+ @Schema(description = "面积单位", defaultValue = "平方米")
+ private String areaUnit = "平方米";
+
+ @Schema(description = "边框")
+ private Line line = new Line();
+
+ @Schema(description = "经纬度和高度的列表(必填)")
+ private List positions = new ArrayList<>();
+
+ @Schema(description = "标签对象")
+ private Label label = new Label();
+
+ @Schema(description = "属性内容")
+ private Attribute attribute = new Attribute();
+
+ @Schema(description = "富文本内容")
+ private String richTextContent;
+
+ @Schema(description = "默认视角")
+ private CustomView customView = new CustomView();
+
+ @Data
+ @Schema(description = "边框属性")
+ public static class Line {
+ @Schema(description = "边框宽", defaultValue = "2")
+ private double width = 2;
+
+ @Schema(description = "边框颜色", defaultValue = "rgba(155, 155, 124, 0.89)")
+ private String color = "rgba(155, 155, 124, 0.89)";
+ }
+
+ @Data
+ @Schema(description = "位置属性")
+ public static class Position {
+ @Schema(description = "经度")
+ private double lng;
+
+ @Schema(description = "纬度")
+ private double lat;
+
+ @Schema(description = "高度")
+ private double alt;
+ }
+
+ @Data
+ @Schema(description = "标签参数")
+ public static class Label {
+ @Schema(description = "标签文本")
+ private String text;
+
+ @Schema(description = "标签显隐")
+ private Boolean show;
+
+ @Schema(description = "标签位置")
+ private Position position = new Position();
+
+ @Schema(description = "字体大小", defaultValue = "20")
+ private int fontSize = 20;
+
+ @Schema(description = "字体项 0:黑体;1:思源黑体;2:庞门正道标题体;3:数黑体", defaultValue = "0")
+ private int fontFamily = 0;
+
+ @Schema(description = "字体颜色", defaultValue = "#ffffff")
+ private String color = "#ffffff";
+
+ @Schema(description = "引线宽", defaultValue = "4")
+ private double lineWidth = 4;
+
+ @Schema(description = "引线颜色", defaultValue = "#00ffff80")
+ private String lineColor = "#00ffff80";
+
+ @Schema(description = "字体偏移(引线长度)", defaultValue = "20")
+ private double pixelOffset = 20;
+
+ @Schema(description = "背景颜色", defaultValue = "['#00ffff80', '#00ffff80']")
+ private String[] backgroundColor = {"#00ffff80", "#00ffff80"};
+
+ @Schema(description = "距离缩放", defaultValue = "false")
+ private boolean scaleByDistance = false;
+
+ @Schema(description = "视野缩放最近距离", defaultValue = "2000")
+ private int near = 2000;
+
+ @Schema(description = "视野缩放最远距离", defaultValue = "100000")
+ private int far = 100000;
+ }
+
+ @Data
+ @Schema(description = "属性内容")
+ public static class Attribute {
+ @Schema(description = "链接", defaultValue = "{}")
+ private Link link = new Link();
+
+ @Data
+ @Schema(description = "链接属性")
+ public static class Link {
+ @Schema(description = "链接内容列表", defaultValue = "[]")
+ private List content = new ArrayList<>();
+
+ @Data
+ @Schema(description = "链接内容")
+ public static class LinkContent {
+ @Schema(description = "链接名称")
+ private String name;
+
+ @Schema(description = "链接地址")
+ private String url;
+ }
+ }
+ }
+
+ @Data
+ @Schema(description = "默认视角属性")
+ public static class CustomView {
+ @Schema(description = "默认视角方位")
+ private Orientation orientation = new Orientation();
+
+ @Schema(description = "视角相对位置")
+ private RelativePosition relativePosition = new RelativePosition();
+
+ @Data
+ @Schema(description = "视角方位属性")
+ public static class Orientation {
+ @Schema(description = "航向角")
+ private double heading;
+
+ @Schema(description = "俯仰角")
+ private double pitch;
+
+ @Schema(description = "翻滚角")
+ private double roll;
+ }
+
+ @Data
+ @Schema(description = "视角相对位置属性")
+ public static class RelativePosition {
+ @Schema(description = "经度")
+ private double lng;
+
+ @Schema(description = "纬度")
+ private double lat;
+
+ @Schema(description = "高度")
+ private double alt;
+ }
+ }
+}
+
\ No newline at end of file
diff --git a/src/main/java/com/yj/earth/params/PolylineObject.java b/src/main/java/com/yj/earth/params/PolylineObject.java
new file mode 100644
index 0000000..3dd329c
--- /dev/null
+++ b/src/main/java/com/yj/earth/params/PolylineObject.java
@@ -0,0 +1,194 @@
+package com.yj.earth.params;
+
+import com.yj.earth.annotation.SourceType;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+@Schema(description = "线对象")
+@SourceType("line")
+public class PolylineObject {
+ @Schema(description = "唯一标识")
+ private String id;
+
+ @Schema(description = "名称")
+ private String name;
+
+ @Schema(description = "首尾相反", defaultValue = "false")
+ private boolean rotate = false;
+
+ @Schema(description = "间距", defaultValue = "1")
+ private int space = 1;
+ @Schema(description = "速度", defaultValue = "10")
+
+ private String speed = "10";
+ @Schema(description = "空间单位名称", defaultValue = "0")
+ private String wordsName;
+
+ @Schema(description = "长度单位", defaultValue = "0")
+ private String lengthUnit;
+
+ @Schema(description = "线宽", defaultValue = "3")
+ private double width = 3;
+
+ @Schema(description = "颜色", defaultValue = "#ff0000")
+ private String color = "#ff0000";
+
+ @Schema(description = "材质类型 0-实线 1-虚线 2-泛光...", defaultValue = "0")
+ private int type = 0;
+
+ @Schema(description = "高度模式(0:海拔高度;1:相对高度;2:依附模式)", defaultValue = "2")
+ private int heightMode = 2;
+
+ @Schema(description = "首尾相连", defaultValue = "false")
+ private boolean noseToTail = false;
+
+ @Schema(description = "线段圆滑", defaultValue = "false")
+ private boolean smooth = false;
+
+ @Schema(description = "线缓冲", defaultValue = "false")
+ private boolean extend = false;
+
+ @Schema(description = "线缓冲宽度", defaultValue = "10")
+ private double extendWidth = 10;
+
+ @Schema(description = "线缓冲颜色", defaultValue = "rgba(255,255,80,0.3)")
+ private String extendColor = "rgba(255,255,80,0.3)";
+
+ @Schema(description = "显隐", defaultValue = "true")
+ private boolean show = true;
+
+ @Schema(description = "经纬度和高度的列表(必填)")
+ private List positions = new ArrayList<>();
+
+ @Schema(description = "标签对象")
+ private Label label = new Label();
+
+ @Schema(description = "属性内容")
+ private Attribute attribute = new Attribute();
+
+ @Schema(description = "富文本内容")
+ private String richTextContent;
+
+ @Schema(description = "默认视角")
+ private CustomView customView = new CustomView();
+
+ @Data
+ @Schema(description = "位置属性")
+ public static class Position {
+ @Schema(description = "经度")
+ private double lng;
+
+ @Schema(description = "纬度")
+ private double lat;
+
+ @Schema(description = "高度")
+ private double alt;
+ }
+
+ @Data
+ @Schema(description = "标签参数")
+ public static class Label {
+ @Schema(description = "标签文本")
+ private String text;
+
+ @Schema(description = "标签显隐")
+ private Boolean show;
+
+ @Schema(description = "标签位置")
+ private Position position = new Position();
+
+ @Schema(description = "字体大小", defaultValue = "20")
+ private int fontSize = 20;
+
+ @Schema(description = "字体项 0:黑体;1:思源黑体;2:庞门正道标题体;3:数黑体", defaultValue = "0")
+ private int fontFamily = 0;
+
+ @Schema(description = "字体颜色", defaultValue = "#ffffff")
+ private String color = "#ffffff";
+
+ @Schema(description = "引线宽", defaultValue = "4")
+ private double lineWidth = 4;
+
+ @Schema(description = "引线颜色", defaultValue = "#00ffff80")
+ private String lineColor = "#00ffff80";
+
+ @Schema(description = "字体偏移(引线长度)", defaultValue = "20")
+ private double pixelOffset = 20;
+
+ @Schema(description = "背景颜色", defaultValue = "['#00ffff80', '#00ffff80']")
+ private String[] backgroundColor = {"#00ffff80", "#00ffff80"};
+
+ @Schema(description = "距离缩放")
+ private Boolean scaleByDistance;
+
+ @Schema(description = "视野缩放最近距离", defaultValue = "2000")
+ private int near = 2000;
+
+ @Schema(description = "视野缩放最远距离", defaultValue = "100000")
+ private int far = 100000;
+ }
+
+ @Data
+ @Schema(description = "属性内容")
+ public static class Attribute {
+ @Schema(description = "链接")
+ private Link link = new Link();
+
+ @Data
+ @Schema(description = "链接属性")
+ public static class Link {
+ @Schema(description = "链接内容列表", defaultValue = "[]")
+ private List content = new ArrayList<>();
+
+ @Data
+ @Schema(description = "链接内容")
+ public static class LinkContent {
+ @Schema(description = "链接名称")
+ private String name;
+
+ @Schema(description = "链接地址")
+ private String url;
+ }
+ }
+ }
+
+ @Data
+ @Schema(description = "默认视角属性")
+ public static class CustomView {
+ @Schema(description = "默认视角方位")
+ private Orientation orientation = new Orientation();
+
+ @Schema(description = "视角相对位置")
+ private RelativePosition relativePosition = new RelativePosition();
+
+ @Data
+ @Schema(description = "视角方位属性")
+ public static class Orientation {
+ @Schema(description = "航向角")
+ private double heading;
+
+ @Schema(description = "俯仰角")
+ private double pitch;
+
+ @Schema(description = "翻滚角")
+ private double roll;
+ }
+
+ @Data
+ @Schema(description = "视角相对位置属性")
+ public static class RelativePosition {
+ @Schema(description = "经度")
+ private double lng;
+
+ @Schema(description = "纬度")
+ private double lat;
+
+ @Schema(description = "高度")
+ private double alt;
+ }
+ }
+}
diff --git a/src/main/java/com/yj/earth/params/StandText.java b/src/main/java/com/yj/earth/params/StandText.java
new file mode 100644
index 0000000..ffaf298
--- /dev/null
+++ b/src/main/java/com/yj/earth/params/StandText.java
@@ -0,0 +1,25 @@
+package com.yj.earth.params;
+
+import com.yj.earth.annotation.SourceType;
+import lombok.Data;
+import java.util.List;
+import java.util.Map;
+
+@Data
+@SourceType("standText")
+public class StandText {
+ private String id;
+ private List positions;
+ private Map customView;
+ private boolean show;
+ private String text;
+ private String color;
+ private int speed;
+
+ @Data
+ public static class Position {
+ private double lng;
+ private double lat;
+ private double alt;
+ }
+}
diff --git a/src/main/java/com/yj/earth/params/Tileset.java b/src/main/java/com/yj/earth/params/Tileset.java
new file mode 100644
index 0000000..4b9e310
--- /dev/null
+++ b/src/main/java/com/yj/earth/params/Tileset.java
@@ -0,0 +1,33 @@
+package com.yj.earth.params;
+
+import com.yj.earth.annotation.SourceType;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.Map;
+
+@Data
+@SourceType("tileset")
+public class Tileset {
+ private Orientation orientation;
+ private Position position;
+ private String id;
+ private Map customView;
+ private boolean show;
+ private String name;
+ private int accuracy;
+
+ @Data
+ public static class Orientation {
+ private int heading;
+ private int roll;
+ private int pitch;
+ }
+
+ @Data
+ public static class Position {
+ private double lng;
+ private double lat;
+ private double alt;
+ }
+}
diff --git a/src/main/java/com/yj/earth/params/WallStereoscopic.java b/src/main/java/com/yj/earth/params/WallStereoscopic.java
new file mode 100644
index 0000000..44f1640
--- /dev/null
+++ b/src/main/java/com/yj/earth/params/WallStereoscopic.java
@@ -0,0 +1,7 @@
+package com.yj.earth.params;
+
+import com.yj.earth.annotation.SourceType;
+
+@SourceType("wallStereoscopic")
+public class WallStereoscopic {
+}
diff --git a/src/main/java/com/yj/earth/vo/FileInfoVo.java b/src/main/java/com/yj/earth/vo/FileInfoVo.java
new file mode 100644
index 0000000..db471b4
--- /dev/null
+++ b/src/main/java/com/yj/earth/vo/FileInfoVo.java
@@ -0,0 +1,13 @@
+package com.yj.earth.vo;
+
+import com.yj.earth.business.domain.FileInfo;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+public class FileInfoVo extends FileInfo {
+ @Schema(description = "预览地址")
+ private String previewUrl;
+ @Schema(description = "下载地址")
+ private String downloadUrl;
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
new file mode 100644
index 0000000..4e0626b
--- /dev/null
+++ b/src/main/resources/application.yml
@@ -0,0 +1,50 @@
+server:
+ host: 192.168.110.25
+ port: 8848
+
+sdk:
+ port: 8888
+
+spring:
+ main:
+ banner-mode: off
+ allow-bean-definition-overriding: true
+ datasource:
+ active: sqlite
+ mysql:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://192.168.110.65:6975/yjearth?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+ username: yjearth
+ password: kNGxrsSSYMexZ2t4
+ servlet:
+ multipart:
+ max-file-size: 1024MB
+ max-request-size: 10240MB
+
+sa-token:
+ token-name: Authorization
+ timeout: 21600
+ active-timeout: 3600
+ is-concurrent: true
+ is-share: true
+ token-style: random-64
+ is-print: false
+
+mybatis-plus:
+ global-config:
+ banner: false
+
+encrypt:
+ aes:
+ key: "ah62ks8dj7dh3yd6"
+
+file:
+ upload:
+ path: upload
+
+graphhopper:
+ graphLocation: ./target/graphhopper
+ profiles:
+ - car
+ - bike
+ - foot
diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml
new file mode 100644
index 0000000..fda1c5d
--- /dev/null
+++ b/src/main/resources/logback.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${CONSOLE_LOG_PATTERN}
+
+
+
+
+
+
+ ${FILE_NAME_PATTERN}
+ ${LOG_MAX_FILE_SIZE}
+ ${LOG_MAX_HISTORY}
+
+ ${LOG_MAX_FILE_SIZE}
+
+
+
+ ${LOG_PATTERN}
+
+
+
+
+
+
+
diff --git a/src/main/resources/mapper/FileInfoMapper.xml b/src/main/resources/mapper/FileInfoMapper.xml
new file mode 100644
index 0000000..8a3d69f
--- /dev/null
+++ b/src/main/resources/mapper/FileInfoMapper.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ id, file_name, file_suffix, content_type, file_size, file_path, file_md5, created_at, updated_at
+
+
+
diff --git a/src/main/resources/mapper/RoleMapper.xml b/src/main/resources/mapper/RoleMapper.xml
new file mode 100644
index 0000000..12f613b
--- /dev/null
+++ b/src/main/resources/mapper/RoleMapper.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ id, role_name, description, is_super, created_at, updated_at
+
+
+
diff --git a/src/main/resources/mapper/RoleSourceMapper.xml b/src/main/resources/mapper/RoleSourceMapper.xml
new file mode 100644
index 0000000..75ed7c9
--- /dev/null
+++ b/src/main/resources/mapper/RoleSourceMapper.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ id, role_id, source_id, created_at, updated_at
+
+
+
diff --git a/src/main/resources/mapper/SourceMapper.xml b/src/main/resources/mapper/SourceMapper.xml
new file mode 100644
index 0000000..b556873
--- /dev/null
+++ b/src/main/resources/mapper/SourceMapper.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ id, source_name, source_type, source_path, parent_id, tree_index, is_show, detail, params, created_at, updated_at
+
+
+
diff --git a/src/main/resources/mapper/UserMapper.xml b/src/main/resources/mapper/UserMapper.xml
new file mode 100644
index 0000000..43bf661
--- /dev/null
+++ b/src/main/resources/mapper/UserMapper.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ id, username, password, avatar, nickname, phone, role_id, created_at, updated_at
+
+
+