From 8056245adeab6e4219773431a68f81f2808365d0 Mon Sep 17 00:00:00 2001 From: ZZX9599 <536509593@qq.com> Date: Mon, 8 Sep 2025 17:01:50 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9C=80=E6=96=B0=E4=BA=A7=E5=93=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 50 ++ .idea/.gitignore | 0 .idea/encodings.xml | 8 + .idea/misc.xml | 15 + .idea/uiDesigner.xml | 124 +++ .idea/workspace.xml | 191 +++++ geo.html | 768 ++++++++++++++++++ index.html | 438 ++++++++++ pom.xml | 177 ++++ src/main/java/com/yj/earth/ServerApp.java | 54 ++ .../yj/earth/annotation/EncryptResponse.java | 22 + .../com/yj/earth/annotation/ExcludeField.java | 34 + .../com/yj/earth/annotation/RoleAccess.java | 16 + .../com/yj/earth/annotation/SourceType.java | 9 + .../earth/aspect/EncryptResponseAspect.java | 64 ++ .../com/yj/earth/aspect/RoleAccessAspect.java | 78 ++ .../java/com/yj/earth/auth/AuthGenerator.java | 74 ++ src/main/java/com/yj/earth/auth/AuthInfo.java | 32 + .../java/com/yj/earth/auth/AuthValidator.java | 115 +++ .../business/controller/AuthController.java | 86 ++ .../controller/FileInfoController.java | 241 ++++++ .../controller/GraphHopperController.java | 237 ++++++ .../business/controller/RoleController.java | 62 ++ .../controller/RoleSourceController.java | 44 + .../business/controller/SourceController.java | 168 ++++ .../business/controller/UserController.java | 140 ++++ .../yj/earth/business/domain/FileInfo.java | 50 ++ .../com/yj/earth/business/domain/Role.java | 41 + .../yj/earth/business/domain/RoleSource.java | 39 + .../com/yj/earth/business/domain/Source.java | 56 ++ .../com/yj/earth/business/domain/User.java | 51 ++ .../earth/business/mapper/FileInfoMapper.java | 18 + .../yj/earth/business/mapper/RoleMapper.java | 18 + .../business/mapper/RoleSourceMapper.java | 18 + .../earth/business/mapper/SourceMapper.java | 18 + .../yj/earth/business/mapper/UserMapper.java | 18 + .../business/service/FileInfoService.java | 9 + .../earth/business/service/RoleService.java | 16 + .../business/service/RoleSourceService.java | 9 + .../earth/business/service/SourceService.java | 25 + .../earth/business/service/UserService.java | 16 + .../service/impl/FileInfoServiceImpl.java | 40 + .../service/impl/RoleServiceImpl.java | 20 + .../service/impl/RoleSourceServiceImpl.java | 20 + .../service/impl/SourceServiceImpl.java | 166 ++++ .../service/impl/UserServiceImpl.java | 20 + .../yj/earth/common/config/CorsConfig.java | 26 + .../common/config/GlobalExceptionHandler.java | 25 + .../common/config/GraphHopperProperties.java | 15 + .../yj/earth/common/config/JacksonConfig.java | 42 + .../yj/earth/common/config/Knife4jConfig.java | 32 + .../common/config/MyMetaObjectConfig.java | 37 + .../yj/earth/common/config/SaTokenConfig.java | 41 + .../yj/earth/common/config/ServerConfig.java | 18 + .../earth/common/config/SourceTypeConfig.java | 29 + .../earth/common/constant/GlobalConstant.java | 14 + .../earth/common/core/MapRedisTemplate.java | 170 ++++ .../common/exception/UnAuthException.java | 10 + .../common/service/ServerInitService.java | 67 ++ .../common/service/SourceParamsValidator.java | 57 ++ .../yj/earth/common/util/AesEncryptUtil.java | 66 ++ .../com/yj/earth/common/util/ApiResponse.java | 56 ++ .../com/yj/earth/common/util/CodeUtil.java | 86 ++ .../com/yj/earth/common/util/HttpUtil.java | 180 ++++ .../earth/common/util/JsonMapConverter.java | 50 ++ .../com/yj/earth/common/util/MapUtil.java | 116 +++ .../yj/earth/common/util/PortKillUtil.java | 114 +++ .../yj/earth/common/util/PositionUtil.java | 33 + .../com/yj/earth/common/util/SdkUtil.java | 126 +++ .../earth/common/util/ServerUniqueIdUtil.java | 80 ++ .../yj/earth/datasource/DatabaseManager.java | 401 +++++++++ .../earth/datasource/JdbcTemplateConfig.java | 18 + .../datasource/MysqlDataSourceConfig.java | 35 + .../datasource/SqliteDataSourceConfig.java | 39 + .../java/com/yj/earth/design/FileInfo.java | 38 + src/main/java/com/yj/earth/design/Role.java | 28 + .../java/com/yj/earth/design/RoleSource.java | 25 + src/main/java/com/yj/earth/design/Source.java | 43 + src/main/java/com/yj/earth/design/User.java | 38 + .../relation/RoleBindOrUnBindSourceDto.java | 14 + .../relation/SourceBindOrUnBindRoleDto.java | 14 + .../dto/relation/UserBindOrUnBindRoleDto.java | 14 + .../com/yj/earth/dto/role/AddRoleDto.java | 16 + .../com/yj/earth/dto/role/UpdateRoleDto.java | 16 + .../yj/earth/dto/source/AddDirectoryDto.java | 17 + .../earth/dto/source/AddModelSourceDto.java | 18 + .../earth/dto/source/AddOtherSourceDto.java | 22 + .../yj/earth/dto/source/DragSourceDto.java | 16 + .../yj/earth/dto/source/UpdateSourceDto.java | 27 + .../com/yj/earth/dto/user/AddUserDto.java | 26 + .../yj/earth/dto/user/UpdatePasswordDto.java | 14 + .../com/yj/earth/dto/user/UpdateUserDto.java | 20 + .../com/yj/earth/dto/user/UserLoginDto.java | 12 + src/main/java/com/yj/earth/model/Point.java | 14 + .../java/com/yj/earth/model/RouteRequest.java | 22 + .../com/yj/earth/model/RouteResponse.java | 14 + .../com/yj/earth/model/StatusResponse.java | 12 + .../com/yj/earth/params/BillboardObject.java | 222 +++++ src/main/java/com/yj/earth/params/Circle.java | 70 ++ .../com/yj/earth/params/CurvelineObject.java | 192 +++++ .../java/com/yj/earth/params/GroundText.java | 26 + src/main/java/com/yj/earth/params/Layer.java | 11 + .../params/PolygonAttackArrowObject.java | 187 +++++ .../yj/earth/params/PolygonPanelObject.java | 178 ++++ .../params/PolygonPincerArrowObject.java | 187 +++++ .../earth/params/PolygonRectangleObject.java | 178 ++++ .../earth/params/PolygonRendezvousObject.java | 178 ++++ .../com/yj/earth/params/PolylineObject.java | 194 +++++ .../java/com/yj/earth/params/StandText.java | 25 + .../java/com/yj/earth/params/Tileset.java | 33 + .../com/yj/earth/params/WallStereoscopic.java | 7 + src/main/java/com/yj/earth/vo/FileInfoVo.java | 13 + src/main/resources/application.yml | 50 ++ src/main/resources/logback.xml | 42 + src/main/resources/mapper/FileInfoMapper.xml | 23 + src/main/resources/mapper/RoleMapper.xml | 20 + .../resources/mapper/RoleSourceMapper.xml | 19 + src/main/resources/mapper/SourceMapper.xml | 25 + src/main/resources/mapper/UserMapper.xml | 23 + 119 files changed, 8281 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/encodings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/uiDesigner.xml create mode 100644 .idea/workspace.xml create mode 100644 geo.html create mode 100644 index.html create mode 100644 pom.xml create mode 100644 src/main/java/com/yj/earth/ServerApp.java create mode 100644 src/main/java/com/yj/earth/annotation/EncryptResponse.java create mode 100644 src/main/java/com/yj/earth/annotation/ExcludeField.java create mode 100644 src/main/java/com/yj/earth/annotation/RoleAccess.java create mode 100644 src/main/java/com/yj/earth/annotation/SourceType.java create mode 100644 src/main/java/com/yj/earth/aspect/EncryptResponseAspect.java create mode 100644 src/main/java/com/yj/earth/aspect/RoleAccessAspect.java create mode 100644 src/main/java/com/yj/earth/auth/AuthGenerator.java create mode 100644 src/main/java/com/yj/earth/auth/AuthInfo.java create mode 100644 src/main/java/com/yj/earth/auth/AuthValidator.java create mode 100644 src/main/java/com/yj/earth/business/controller/AuthController.java create mode 100644 src/main/java/com/yj/earth/business/controller/FileInfoController.java create mode 100644 src/main/java/com/yj/earth/business/controller/GraphHopperController.java create mode 100644 src/main/java/com/yj/earth/business/controller/RoleController.java create mode 100644 src/main/java/com/yj/earth/business/controller/RoleSourceController.java create mode 100644 src/main/java/com/yj/earth/business/controller/SourceController.java create mode 100644 src/main/java/com/yj/earth/business/controller/UserController.java create mode 100644 src/main/java/com/yj/earth/business/domain/FileInfo.java create mode 100644 src/main/java/com/yj/earth/business/domain/Role.java create mode 100644 src/main/java/com/yj/earth/business/domain/RoleSource.java create mode 100644 src/main/java/com/yj/earth/business/domain/Source.java create mode 100644 src/main/java/com/yj/earth/business/domain/User.java create mode 100644 src/main/java/com/yj/earth/business/mapper/FileInfoMapper.java create mode 100644 src/main/java/com/yj/earth/business/mapper/RoleMapper.java create mode 100644 src/main/java/com/yj/earth/business/mapper/RoleSourceMapper.java create mode 100644 src/main/java/com/yj/earth/business/mapper/SourceMapper.java create mode 100644 src/main/java/com/yj/earth/business/mapper/UserMapper.java create mode 100644 src/main/java/com/yj/earth/business/service/FileInfoService.java create mode 100644 src/main/java/com/yj/earth/business/service/RoleService.java create mode 100644 src/main/java/com/yj/earth/business/service/RoleSourceService.java create mode 100644 src/main/java/com/yj/earth/business/service/SourceService.java create mode 100644 src/main/java/com/yj/earth/business/service/UserService.java create mode 100644 src/main/java/com/yj/earth/business/service/impl/FileInfoServiceImpl.java create mode 100644 src/main/java/com/yj/earth/business/service/impl/RoleServiceImpl.java create mode 100644 src/main/java/com/yj/earth/business/service/impl/RoleSourceServiceImpl.java create mode 100644 src/main/java/com/yj/earth/business/service/impl/SourceServiceImpl.java create mode 100644 src/main/java/com/yj/earth/business/service/impl/UserServiceImpl.java create mode 100644 src/main/java/com/yj/earth/common/config/CorsConfig.java create mode 100644 src/main/java/com/yj/earth/common/config/GlobalExceptionHandler.java create mode 100644 src/main/java/com/yj/earth/common/config/GraphHopperProperties.java create mode 100644 src/main/java/com/yj/earth/common/config/JacksonConfig.java create mode 100644 src/main/java/com/yj/earth/common/config/Knife4jConfig.java create mode 100644 src/main/java/com/yj/earth/common/config/MyMetaObjectConfig.java create mode 100644 src/main/java/com/yj/earth/common/config/SaTokenConfig.java create mode 100644 src/main/java/com/yj/earth/common/config/ServerConfig.java create mode 100644 src/main/java/com/yj/earth/common/config/SourceTypeConfig.java create mode 100644 src/main/java/com/yj/earth/common/constant/GlobalConstant.java create mode 100644 src/main/java/com/yj/earth/common/core/MapRedisTemplate.java create mode 100644 src/main/java/com/yj/earth/common/exception/UnAuthException.java create mode 100644 src/main/java/com/yj/earth/common/service/ServerInitService.java create mode 100644 src/main/java/com/yj/earth/common/service/SourceParamsValidator.java create mode 100644 src/main/java/com/yj/earth/common/util/AesEncryptUtil.java create mode 100644 src/main/java/com/yj/earth/common/util/ApiResponse.java create mode 100644 src/main/java/com/yj/earth/common/util/CodeUtil.java create mode 100644 src/main/java/com/yj/earth/common/util/HttpUtil.java create mode 100644 src/main/java/com/yj/earth/common/util/JsonMapConverter.java create mode 100644 src/main/java/com/yj/earth/common/util/MapUtil.java create mode 100644 src/main/java/com/yj/earth/common/util/PortKillUtil.java create mode 100644 src/main/java/com/yj/earth/common/util/PositionUtil.java create mode 100644 src/main/java/com/yj/earth/common/util/SdkUtil.java create mode 100644 src/main/java/com/yj/earth/common/util/ServerUniqueIdUtil.java create mode 100644 src/main/java/com/yj/earth/datasource/DatabaseManager.java create mode 100644 src/main/java/com/yj/earth/datasource/JdbcTemplateConfig.java create mode 100644 src/main/java/com/yj/earth/datasource/MysqlDataSourceConfig.java create mode 100644 src/main/java/com/yj/earth/datasource/SqliteDataSourceConfig.java create mode 100644 src/main/java/com/yj/earth/design/FileInfo.java create mode 100644 src/main/java/com/yj/earth/design/Role.java create mode 100644 src/main/java/com/yj/earth/design/RoleSource.java create mode 100644 src/main/java/com/yj/earth/design/Source.java create mode 100644 src/main/java/com/yj/earth/design/User.java create mode 100644 src/main/java/com/yj/earth/dto/relation/RoleBindOrUnBindSourceDto.java create mode 100644 src/main/java/com/yj/earth/dto/relation/SourceBindOrUnBindRoleDto.java create mode 100644 src/main/java/com/yj/earth/dto/relation/UserBindOrUnBindRoleDto.java create mode 100644 src/main/java/com/yj/earth/dto/role/AddRoleDto.java create mode 100644 src/main/java/com/yj/earth/dto/role/UpdateRoleDto.java create mode 100644 src/main/java/com/yj/earth/dto/source/AddDirectoryDto.java create mode 100644 src/main/java/com/yj/earth/dto/source/AddModelSourceDto.java create mode 100644 src/main/java/com/yj/earth/dto/source/AddOtherSourceDto.java create mode 100644 src/main/java/com/yj/earth/dto/source/DragSourceDto.java create mode 100644 src/main/java/com/yj/earth/dto/source/UpdateSourceDto.java create mode 100644 src/main/java/com/yj/earth/dto/user/AddUserDto.java create mode 100644 src/main/java/com/yj/earth/dto/user/UpdatePasswordDto.java create mode 100644 src/main/java/com/yj/earth/dto/user/UpdateUserDto.java create mode 100644 src/main/java/com/yj/earth/dto/user/UserLoginDto.java create mode 100644 src/main/java/com/yj/earth/model/Point.java create mode 100644 src/main/java/com/yj/earth/model/RouteRequest.java create mode 100644 src/main/java/com/yj/earth/model/RouteResponse.java create mode 100644 src/main/java/com/yj/earth/model/StatusResponse.java create mode 100644 src/main/java/com/yj/earth/params/BillboardObject.java create mode 100644 src/main/java/com/yj/earth/params/Circle.java create mode 100644 src/main/java/com/yj/earth/params/CurvelineObject.java create mode 100644 src/main/java/com/yj/earth/params/GroundText.java create mode 100644 src/main/java/com/yj/earth/params/Layer.java create mode 100644 src/main/java/com/yj/earth/params/PolygonAttackArrowObject.java create mode 100644 src/main/java/com/yj/earth/params/PolygonPanelObject.java create mode 100644 src/main/java/com/yj/earth/params/PolygonPincerArrowObject.java create mode 100644 src/main/java/com/yj/earth/params/PolygonRectangleObject.java create mode 100644 src/main/java/com/yj/earth/params/PolygonRendezvousObject.java create mode 100644 src/main/java/com/yj/earth/params/PolylineObject.java create mode 100644 src/main/java/com/yj/earth/params/StandText.java create mode 100644 src/main/java/com/yj/earth/params/Tileset.java create mode 100644 src/main/java/com/yj/earth/params/WallStereoscopic.java create mode 100644 src/main/java/com/yj/earth/vo/FileInfoVo.java create mode 100644 src/main/resources/application.yml create mode 100644 src/main/resources/logback.xml create mode 100644 src/main/resources/mapper/FileInfoMapper.xml create mode 100644 src/main/resources/mapper/RoleMapper.xml create mode 100644 src/main/resources/mapper/RoleSourceMapper.xml create mode 100644 src/main/resources/mapper/SourceMapper.xml create mode 100644 src/main/resources/mapper/UserMapper.xml 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 + + + + + + \ 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 + + +