最新产品
This commit is contained in:
		
							
								
								
									
										50
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -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/ | ||||||
							
								
								
									
										0
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
								
								
									
										8
									
								
								.idea/encodings.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.idea/encodings.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <project version="4"> | ||||||
|  |   <component name="Encoding"> | ||||||
|  |     <file url="file://$PROJECT_DIR$/logs/sdk.log" charset="GBK" /> | ||||||
|  |     <file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" /> | ||||||
|  |     <file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" /> | ||||||
|  |   </component> | ||||||
|  | </project> | ||||||
							
								
								
									
										15
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <project version="4"> | ||||||
|  |   <component name="ExternalStorageConfigurationManager" enabled="true" /> | ||||||
|  |   <component name="MavenProjectsManager"> | ||||||
|  |     <option name="originalFiles"> | ||||||
|  |       <list> | ||||||
|  |         <option value="$PROJECT_DIR$/pom.xml" /> | ||||||
|  |       </list> | ||||||
|  |     </option> | ||||||
|  |     <option name="workspaceImportForciblyTurnedOn" value="true" /> | ||||||
|  |   </component> | ||||||
|  |   <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK"> | ||||||
|  |     <output url="file://$PROJECT_DIR$/out" /> | ||||||
|  |   </component> | ||||||
|  | </project> | ||||||
							
								
								
									
										124
									
								
								.idea/uiDesigner.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								.idea/uiDesigner.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,124 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <project version="4"> | ||||||
|  |   <component name="Palette2"> | ||||||
|  |     <group name="Swing"> | ||||||
|  |       <item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false"> | ||||||
|  |         <default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" /> | ||||||
|  |       </item> | ||||||
|  |       <item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false"> | ||||||
|  |         <default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" /> | ||||||
|  |       </item> | ||||||
|  |       <item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false"> | ||||||
|  |         <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" /> | ||||||
|  |       </item> | ||||||
|  |       <item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true"> | ||||||
|  |         <default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" /> | ||||||
|  |       </item> | ||||||
|  |       <item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false"> | ||||||
|  |         <default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" /> | ||||||
|  |         <initial-values> | ||||||
|  |           <property name="text" value="Button" /> | ||||||
|  |         </initial-values> | ||||||
|  |       </item> | ||||||
|  |       <item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false"> | ||||||
|  |         <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" /> | ||||||
|  |         <initial-values> | ||||||
|  |           <property name="text" value="RadioButton" /> | ||||||
|  |         </initial-values> | ||||||
|  |       </item> | ||||||
|  |       <item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false"> | ||||||
|  |         <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" /> | ||||||
|  |         <initial-values> | ||||||
|  |           <property name="text" value="CheckBox" /> | ||||||
|  |         </initial-values> | ||||||
|  |       </item> | ||||||
|  |       <item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false"> | ||||||
|  |         <default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" /> | ||||||
|  |         <initial-values> | ||||||
|  |           <property name="text" value="Label" /> | ||||||
|  |         </initial-values> | ||||||
|  |       </item> | ||||||
|  |       <item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true"> | ||||||
|  |         <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> | ||||||
|  |           <preferred-size width="150" height="-1" /> | ||||||
|  |         </default-constraints> | ||||||
|  |       </item> | ||||||
|  |       <item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true"> | ||||||
|  |         <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> | ||||||
|  |           <preferred-size width="150" height="-1" /> | ||||||
|  |         </default-constraints> | ||||||
|  |       </item> | ||||||
|  |       <item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true"> | ||||||
|  |         <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> | ||||||
|  |           <preferred-size width="150" height="-1" /> | ||||||
|  |         </default-constraints> | ||||||
|  |       </item> | ||||||
|  |       <item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true"> | ||||||
|  |         <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> | ||||||
|  |           <preferred-size width="150" height="50" /> | ||||||
|  |         </default-constraints> | ||||||
|  |       </item> | ||||||
|  |       <item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true"> | ||||||
|  |         <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> | ||||||
|  |           <preferred-size width="150" height="50" /> | ||||||
|  |         </default-constraints> | ||||||
|  |       </item> | ||||||
|  |       <item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true"> | ||||||
|  |         <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> | ||||||
|  |           <preferred-size width="150" height="50" /> | ||||||
|  |         </default-constraints> | ||||||
|  |       </item> | ||||||
|  |       <item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true"> | ||||||
|  |         <default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" /> | ||||||
|  |       </item> | ||||||
|  |       <item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false"> | ||||||
|  |         <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> | ||||||
|  |           <preferred-size width="150" height="50" /> | ||||||
|  |         </default-constraints> | ||||||
|  |       </item> | ||||||
|  |       <item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false"> | ||||||
|  |         <default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3"> | ||||||
|  |           <preferred-size width="150" height="50" /> | ||||||
|  |         </default-constraints> | ||||||
|  |       </item> | ||||||
|  |       <item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false"> | ||||||
|  |         <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> | ||||||
|  |           <preferred-size width="150" height="50" /> | ||||||
|  |         </default-constraints> | ||||||
|  |       </item> | ||||||
|  |       <item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false"> | ||||||
|  |         <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3"> | ||||||
|  |           <preferred-size width="200" height="200" /> | ||||||
|  |         </default-constraints> | ||||||
|  |       </item> | ||||||
|  |       <item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false"> | ||||||
|  |         <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3"> | ||||||
|  |           <preferred-size width="200" height="200" /> | ||||||
|  |         </default-constraints> | ||||||
|  |       </item> | ||||||
|  |       <item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true"> | ||||||
|  |         <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" /> | ||||||
|  |       </item> | ||||||
|  |       <item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false"> | ||||||
|  |         <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" /> | ||||||
|  |       </item> | ||||||
|  |       <item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false"> | ||||||
|  |         <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" /> | ||||||
|  |       </item> | ||||||
|  |       <item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false"> | ||||||
|  |         <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" /> | ||||||
|  |       </item> | ||||||
|  |       <item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false"> | ||||||
|  |         <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1"> | ||||||
|  |           <preferred-size width="-1" height="20" /> | ||||||
|  |         </default-constraints> | ||||||
|  |       </item> | ||||||
|  |       <item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false"> | ||||||
|  |         <default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" /> | ||||||
|  |       </item> | ||||||
|  |       <item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false"> | ||||||
|  |         <default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" /> | ||||||
|  |       </item> | ||||||
|  |     </group> | ||||||
|  |   </component> | ||||||
|  | </project> | ||||||
							
								
								
									
										191
									
								
								.idea/workspace.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								.idea/workspace.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,191 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <project version="4"> | ||||||
|  |   <component name="AutoImportSettings"> | ||||||
|  |     <option name="autoReloadType" value="SELECTIVE" /> | ||||||
|  |   </component> | ||||||
|  |   <component name="ChangeListManager"> | ||||||
|  |     <list default="true" id="1ba8c79d-5980-42c3-ba31-837b4b96bb22" name="Changes" comment="" /> | ||||||
|  |     <option name="SHOW_DIALOG" value="false" /> | ||||||
|  |     <option name="HIGHLIGHT_CONFLICTS" value="true" /> | ||||||
|  |     <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" /> | ||||||
|  |     <option name="LAST_RESOLUTION" value="IGNORE" /> | ||||||
|  |   </component> | ||||||
|  |   <component name="FileTemplateManagerImpl"> | ||||||
|  |     <option name="RECENT_TEMPLATES"> | ||||||
|  |       <list> | ||||||
|  |         <option value="HTML File" /> | ||||||
|  |         <option value="Class" /> | ||||||
|  |       </list> | ||||||
|  |     </option> | ||||||
|  |   </component> | ||||||
|  |   <component name="HighlightingSettingsPerFile"> | ||||||
|  |     <setting file="file://$PROJECT_DIR$/geo.html" root0="SKIP_INSPECTION" /> | ||||||
|  |     <setting file="file://$PROJECT_DIR$/index.html" root0="SKIP_INSPECTION" /> | ||||||
|  |     <setting file="file://$PROJECT_DIR$/pom.xml" root0="SKIP_INSPECTION" /> | ||||||
|  |     <setting file="file://$PROJECT_DIR$/src/main/java/com/yj/earth/ServerApp.java" root0="SKIP_INSPECTION" /> | ||||||
|  |     <setting file="file://$PROJECT_DIR$/src/main/java/com/yj/earth/aspect/RoleAccessAspect.java" root0="SKIP_INSPECTION" /> | ||||||
|  |     <setting file="file://$PROJECT_DIR$/src/main/java/com/yj/earth/auth/AuthValidator.java" root0="SKIP_INSPECTION" /> | ||||||
|  |     <setting file="file://$PROJECT_DIR$/src/main/java/com/yj/earth/business/controller/AuthController.java" root0="SKIP_INSPECTION" /> | ||||||
|  |     <setting file="file://$PROJECT_DIR$/src/main/java/com/yj/earth/business/controller/FileInfoController.java" root0="SKIP_INSPECTION" /> | ||||||
|  |     <setting file="file://$PROJECT_DIR$/src/main/java/com/yj/earth/business/controller/GraphHopperController.java" root0="SKIP_INSPECTION" /> | ||||||
|  |     <setting file="file://$PROJECT_DIR$/src/main/java/com/yj/earth/business/controller/RoleSourceController.java" root0="SKIP_INSPECTION" /> | ||||||
|  |     <setting file="file://$PROJECT_DIR$/src/main/java/com/yj/earth/business/controller/SourceController.java" root0="SKIP_INSPECTION" /> | ||||||
|  |     <setting file="file://$PROJECT_DIR$/src/main/java/com/yj/earth/business/controller/UserController.java" root0="SKIP_INSPECTION" /> | ||||||
|  |     <setting file="file://$PROJECT_DIR$/src/main/java/com/yj/earth/business/service/impl/SourceServiceImpl.java" root0="SKIP_INSPECTION" /> | ||||||
|  |     <setting file="file://$PROJECT_DIR$/src/main/java/com/yj/earth/common/config/SourceTypeConfig.java" root0="SKIP_INSPECTION" /> | ||||||
|  |     <setting file="file://$PROJECT_DIR$/src/main/java/com/yj/earth/common/constant/GlobalConstant.java" root0="SKIP_INSPECTION" /> | ||||||
|  |     <setting file="file://$PROJECT_DIR$/src/main/java/com/yj/earth/common/service/ServerInitService.java" root0="SKIP_INSPECTION" /> | ||||||
|  |     <setting file="file://$PROJECT_DIR$/src/main/java/com/yj/earth/common/util/SdkUtil.java" root0="SKIP_INSPECTION" /> | ||||||
|  |     <setting file="file://$PROJECT_DIR$/src/main/java/com/yj/earth/datasource/DatabaseManager.java" root0="SKIP_INSPECTION" /> | ||||||
|  |     <setting file="file://$PROJECT_DIR$/src/main/java/com/yj/earth/datasource/MysqlDataSourceConfig.java" root0="SKIP_INSPECTION" /> | ||||||
|  |     <setting file="file://$PROJECT_DIR$/src/main/java/com/yj/earth/params/BillboardObject.java" root0="SKIP_INSPECTION" /> | ||||||
|  |     <setting file="file://$PROJECT_DIR$/src/main/java/com/yj/earth/params/PolylineObject.java" root0="SKIP_INSPECTION" /> | ||||||
|  |     <setting file="file://$PROJECT_DIR$/src/main/java/com/yj/earth/sdk/CltController.java" root0="SKIP_INSPECTION" /> | ||||||
|  |     <setting file="file://$PROJECT_DIR$/src/main/java/com/yj/earth/sdk/PakController.java" root0="SKIP_INSPECTION" /> | ||||||
|  |     <setting file="file://$PROJECT_DIR$/src/main/resources/application.yml" root0="SKIP_INSPECTION" /> | ||||||
|  |   </component> | ||||||
|  |   <component name="MarkdownSettingsMigration"> | ||||||
|  |     <option name="stateVersion" value="1" /> | ||||||
|  |   </component> | ||||||
|  |   <component name="MavenImportPreferences"> | ||||||
|  |     <option name="generalSettings"> | ||||||
|  |       <MavenGeneralSettings> | ||||||
|  |         <option name="localRepository" value="D:\study\Repository" /> | ||||||
|  |         <option name="mavenHome" value="D:\study\Maven\apache-maven-3.9.6" /> | ||||||
|  |         <option name="userSettingsFile" value="D:\study\Maven\apache-maven-3.9.6\conf\settings.xml" /> | ||||||
|  |       </MavenGeneralSettings> | ||||||
|  |     </option> | ||||||
|  |   </component> | ||||||
|  |   <component name="ProjectColorInfo">{ | ||||||
|  |   "associatedIndex": 7 | ||||||
|  | }</component> | ||||||
|  |   <component name="ProjectId" id="31lAoxTxxk0EgrlqLOJXEphma7d" /> | ||||||
|  |   <component name="ProjectViewState"> | ||||||
|  |     <option name="hideEmptyMiddlePackages" value="true" /> | ||||||
|  |     <option name="showLibraryContents" value="true" /> | ||||||
|  |   </component> | ||||||
|  |   <component name="PropertiesComponent"><![CDATA[{ | ||||||
|  |   "keyToString": { | ||||||
|  |     "Application.AuthValidator.executor": "Run", | ||||||
|  |     "Application.CodeGenerator.executor": "Run", | ||||||
|  |     "Application.CodeUtil.executor": "Run", | ||||||
|  |     "Application.ImageResizer.executor": "Run", | ||||||
|  |     "Application.PositionUtil.executor": "Run", | ||||||
|  |     "Application.ResourceMonitor.executor": "Run", | ||||||
|  |     "Application.ServerUniqueIdUtil.executor": "Run", | ||||||
|  |     "Application.SqliteTableCreator.executor": "Run", | ||||||
|  |     "Application.SystemUniqueIdGenerator.executor": "Run", | ||||||
|  |     "Application.YjearthCacheManager.executor": "Run", | ||||||
|  |     "Application.com.yj.earth.common.util.CodeGenerator.executor": "Run", | ||||||
|  |     "Application.com.zzx.djl.utils.CodeUtil.executor": "Run", | ||||||
|  |     "DefaultHtmlFileTemplate": "HTML File", | ||||||
|  |     "Maven.yjearth [clean].executor": "Run", | ||||||
|  |     "Maven.yjearth [package].executor": "Run", | ||||||
|  |     "RequestMappingsPanelOrder0": "0", | ||||||
|  |     "RequestMappingsPanelOrder1": "1", | ||||||
|  |     "RequestMappingsPanelWidth0": "75", | ||||||
|  |     "RequestMappingsPanelWidth1": "75", | ||||||
|  |     "RunOnceActivity.OpenProjectViewOnStart": "true", | ||||||
|  |     "RunOnceActivity.ShowReadmeOnStart": "true", | ||||||
|  |     "Spring Boot.ServerApp.executor": "Run", | ||||||
|  |     "kotlin-language-version-configured": "true", | ||||||
|  |     "last_opened_file_path": "E:/yjearth/sdk", | ||||||
|  |     "node.js.detected.package.eslint": "true", | ||||||
|  |     "node.js.detected.package.tslint": "true", | ||||||
|  |     "node.js.selected.package.eslint": "(autodetect)", | ||||||
|  |     "node.js.selected.package.tslint": "(autodetect)", | ||||||
|  |     "nodejs_package_manager_path": "npm", | ||||||
|  |     "project.structure.last.edited": "Modules", | ||||||
|  |     "project.structure.proportion": "0.0", | ||||||
|  |     "project.structure.side.proportion": "0.0", | ||||||
|  |     "settings.editor.selected.configurable": "MavenSettings", | ||||||
|  |     "vue.rearranger.settings.migration": "true" | ||||||
|  |   } | ||||||
|  | }]]></component> | ||||||
|  |   <component name="ReactorSettings"> | ||||||
|  |     <option name="notificationShown" value="true" /> | ||||||
|  |   </component> | ||||||
|  |   <component name="RecentsManager"> | ||||||
|  |     <key name="CopyFile.RECENT_KEYS"> | ||||||
|  |       <recent name="E:\yjearth\sdk" /> | ||||||
|  |       <recent name="E:\yjearth\src\main\java\com\yj\earth\auth" /> | ||||||
|  |       <recent name="E:\yjearth\src\main\java\com\yj\earth\params" /> | ||||||
|  |       <recent name="E:\yjearth" /> | ||||||
|  |       <recent name="E:\yjearth\src\main\java\com\yj\earth\model" /> | ||||||
|  |     </key> | ||||||
|  |     <key name="MoveFile.RECENT_KEYS"> | ||||||
|  |       <recent name="E:\yjearth\src\main\java\com\yj\earth\vo" /> | ||||||
|  |       <recent name="E:\yjearth\sdk" /> | ||||||
|  |     </key> | ||||||
|  |     <key name="CopyClassDialog.RECENTS_KEY"> | ||||||
|  |       <recent name="com.yj.earth.dto.user" /> | ||||||
|  |       <recent name="com.yj.earth.sdk" /> | ||||||
|  |       <recent name="com.yj.earth.business.controller" /> | ||||||
|  |       <recent name="com.yj.earth.design" /> | ||||||
|  |       <recent name="com.yj.earth.common.config" /> | ||||||
|  |     </key> | ||||||
|  |   </component> | ||||||
|  |   <component name="RunManager" selected="Spring Boot.ServerApp"> | ||||||
|  |     <configuration name="AuthValidator" type="Application" factoryName="Application" temporary="true" nameIsGenerated="true"> | ||||||
|  |       <option name="MAIN_CLASS_NAME" value="com.yj.earth.auth.AuthValidator" /> | ||||||
|  |       <module name="yjearth" /> | ||||||
|  |       <extension name="coverage"> | ||||||
|  |         <pattern> | ||||||
|  |           <option name="PATTERN" value="com.yj.earth.auth.*" /> | ||||||
|  |           <option name="ENABLED" value="true" /> | ||||||
|  |         </pattern> | ||||||
|  |       </extension> | ||||||
|  |       <method v="2"> | ||||||
|  |         <option name="Make" enabled="true" /> | ||||||
|  |       </method> | ||||||
|  |     </configuration> | ||||||
|  |     <configuration name="ServerApp" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" temporary="true" nameIsGenerated="true"> | ||||||
|  |       <module name="yjearth" /> | ||||||
|  |       <option name="SPRING_BOOT_MAIN_CLASS" value="com.yj.earth.ServerApp" /> | ||||||
|  |       <extension name="coverage"> | ||||||
|  |         <pattern> | ||||||
|  |           <option name="PATTERN" value="com.yj.earth.*" /> | ||||||
|  |           <option name="ENABLED" value="true" /> | ||||||
|  |         </pattern> | ||||||
|  |       </extension> | ||||||
|  |       <method v="2"> | ||||||
|  |         <option name="Make" enabled="true" /> | ||||||
|  |       </method> | ||||||
|  |     </configuration> | ||||||
|  |     <list> | ||||||
|  |       <item itemvalue="Application.AuthValidator" /> | ||||||
|  |       <item itemvalue="Spring Boot.ServerApp" /> | ||||||
|  |     </list> | ||||||
|  |     <recent_temporary> | ||||||
|  |       <list> | ||||||
|  |         <item itemvalue="Spring Boot.ServerApp" /> | ||||||
|  |         <item itemvalue="Application.AuthValidator" /> | ||||||
|  |       </list> | ||||||
|  |     </recent_temporary> | ||||||
|  |   </component> | ||||||
|  |   <component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" /> | ||||||
|  |   <component name="TaskManager"> | ||||||
|  |     <task active="true" id="Default" summary="Default task"> | ||||||
|  |       <changelist id="1ba8c79d-5980-42c3-ba31-837b4b96bb22" name="Changes" comment="" /> | ||||||
|  |       <created>1756088463625</created> | ||||||
|  |       <option name="number" value="Default" /> | ||||||
|  |       <option name="presentableId" value="Default" /> | ||||||
|  |       <updated>1756088463625</updated> | ||||||
|  |       <workItem from="1756088464610" duration="36122000" /> | ||||||
|  |       <workItem from="1756186510770" duration="3364000" /> | ||||||
|  |       <workItem from="1756190749415" duration="20215000" /> | ||||||
|  |       <workItem from="1756264811990" duration="16689000" /> | ||||||
|  |       <workItem from="1756290680677" duration="26000" /> | ||||||
|  |       <workItem from="1756343883318" duration="12487000" /> | ||||||
|  |       <workItem from="1756433614798" duration="33975000" /> | ||||||
|  |       <workItem from="1756710190316" duration="22200000" /> | ||||||
|  |       <workItem from="1756887638663" duration="1340000" /> | ||||||
|  |       <workItem from="1756951924235" duration="11288000" /> | ||||||
|  |       <workItem from="1757300079963" duration="14440000" /> | ||||||
|  |     </task> | ||||||
|  |     <servers /> | ||||||
|  |   </component> | ||||||
|  |   <component name="TypeScriptGeneratedFilesManager"> | ||||||
|  |     <option name="version" value="3" /> | ||||||
|  |   </component> | ||||||
|  | </project> | ||||||
							
								
								
									
										768
									
								
								geo.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										768
									
								
								geo.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,768 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <html lang="zh-CN"> | ||||||
|  | <head> | ||||||
|  |     <meta charset="UTF-8"> | ||||||
|  |     <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no"> | ||||||
|  |     <title>路径规划系统</title> | ||||||
|  |     <!-- 仅保留必要CDN --> | ||||||
|  |     <script src="https://cdn.tailwindcss.com"></script> | ||||||
|  |     <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"> | ||||||
|  |     <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/> | ||||||
|  |     <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script> | ||||||
|  |     <script src="https://cdn.jsdelivr.net/npm/axios@1.6.8/dist/axios.min.js"></script> | ||||||
|  |  | ||||||
|  |     <!-- Tailwind配置 --> | ||||||
|  |     <script> | ||||||
|  |         tailwind.config = { | ||||||
|  |             theme: { | ||||||
|  |                 extend: { | ||||||
|  |                     colors: { | ||||||
|  |                         primary: '#165DFF', | ||||||
|  |                         success: '#00B42A', | ||||||
|  |                         danger: '#F53F3F', | ||||||
|  |                         warning: '#FF7D00', | ||||||
|  |                         neutral: '#F5F7FA', | ||||||
|  |                         'neutral-dark': '#4E5969', | ||||||
|  |                     }, | ||||||
|  |                     fontFamily: { | ||||||
|  |                         inter: ['Inter', 'system-ui', 'sans-serif'], | ||||||
|  |                     }, | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     </script> | ||||||
|  |  | ||||||
|  |     <!-- 自定义工具类 --> | ||||||
|  |     <style type="text/tailwindcss"> | ||||||
|  |         @layer utilities { | ||||||
|  |             .content-auto { | ||||||
|  |                 content-visibility: auto; | ||||||
|  |             } | ||||||
|  |             .map-height { | ||||||
|  |                 height: 100vh; /* 调整为占满视口高度 */ | ||||||
|  |             } | ||||||
|  |             .sidebar-height { | ||||||
|  |                 height: 100vh; /* 调整为占满视口高度 */ | ||||||
|  |             } | ||||||
|  |             .scrollbar-hide { | ||||||
|  |                 -ms-overflow-style: none; | ||||||
|  |                 scrollbar-width: none; | ||||||
|  |             } | ||||||
|  |             .scrollbar-hide::-webkit-scrollbar { | ||||||
|  |                 display: none; | ||||||
|  |             } | ||||||
|  |             .custom-marker .fa { | ||||||
|  |                 font-size: 14px; | ||||||
|  |             } | ||||||
|  |             .input-error { | ||||||
|  |                 @apply border-danger focus:ring-danger/50 focus:border-danger; | ||||||
|  |             } | ||||||
|  |             .btn-disabled { | ||||||
|  |                 @apply bg-gray-300 text-gray-500 cursor-not-allowed hover:bg-gray-300; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     </style> | ||||||
|  | </head> | ||||||
|  | <body class="font-inter bg-gray-50 text-gray-800 antialiased m-0"> | ||||||
|  | <!-- 主内容区(直接顶到顶部、删除原header) --> | ||||||
|  | <main class="flex flex-col md:flex-row"> | ||||||
|  |     <!-- 左侧控制面板 --> | ||||||
|  |     <aside class="w-full md:w-96 bg-white shadow-sm z-10 md:sidebar-height overflow-y-auto scrollbar-hide transition-all"> | ||||||
|  |         <div class="p-4 space-y-6"> | ||||||
|  |             <!-- 地图加载区域 --> | ||||||
|  |             <div class="p-4 border border-gray-100 rounded-lg bg-neutral shadow-sm"> | ||||||
|  |                 <h2 class="text-lg font-semibold mb-3 flex items-center text-neutral-dark"> | ||||||
|  |                     <i class="fa fa-map text-primary mr-2"></i>地图管理 | ||||||
|  |                 </h2> | ||||||
|  |                 <div class="flex items-center space-x-2 mb-2"> | ||||||
|  |                     <input type="file" id="mapFile" accept=".pbf" class="hidden" multiple="false"/> | ||||||
|  |                     <button id="selectFileBtn" | ||||||
|  |                             class="flex-1 px-4 py-2 bg-gray-200 hover:bg-gray-300 rounded text-sm font-medium transition-colors duration-200"> | ||||||
|  |                         选择PBF文件 | ||||||
|  |                     </button> | ||||||
|  |                     <button id="loadMapBtn" | ||||||
|  |                             class="px-4 py-2 bg-primary hover:bg-primary/90 text-white rounded text-sm font-medium transition-colors duration-200"> | ||||||
|  |                         加载地图 | ||||||
|  |                     </button> | ||||||
|  |                 </div> | ||||||
|  |                 <div id="fileInfo" class="mt-1 text-sm hidden"></div> | ||||||
|  |                 <div id="loadProgress" class="mt-1 text-sm text-primary hidden flex items-center"> | ||||||
|  |                     <i class="fa fa-spinner fa-spin mr-1.5"></i> | ||||||
|  |                     <span id="progressText">正在处理...</span> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |  | ||||||
|  |             <!-- 路径规划参数 --> | ||||||
|  |             <div class="space-y-4"> | ||||||
|  |                 <h2 class="text-lg font-semibold flex items-center text-neutral-dark"> | ||||||
|  |                     <i class="fa fa-road text-primary mr-2"></i>路径参数 | ||||||
|  |                 </h2> | ||||||
|  |  | ||||||
|  |                 <!-- 起点:移除默认value、默认无数据 --> | ||||||
|  |                 <div class="space-y-1"> | ||||||
|  |                     <label class="block text-sm font-medium text-gray-700">起点 <span class="text-danger">*</span></label> | ||||||
|  |                     <div class="flex space-x-2"> | ||||||
|  |                         <div class="flex-1 space-y-0.5"> | ||||||
|  |                             <input type="text" id="startLat" placeholder="纬度" | ||||||
|  |                                    class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary text-sm transition-all" | ||||||
|  |                                    maxlength="10"> | ||||||
|  |                             <span id="startLatError" class="text-danger text-xs hidden">请输入有效纬度</span> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="flex-1 space-y-0.5"> | ||||||
|  |                             <input type="text" id="startLng" placeholder="经度" | ||||||
|  |                                    class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary text-sm transition-all" | ||||||
|  |                                    maxlength="10"> | ||||||
|  |                             <span id="startLngError" class="text-danger text-xs hidden">请输入有效经度</span> | ||||||
|  |                         </div> | ||||||
|  |                         <button id="setStartBtn" class="p-2 bg-gray-100 hover:bg-gray-200 rounded transition-colors duration-200" | ||||||
|  |                                 title="在地图上选择起点"> | ||||||
|  |                             <i class="fa fa-map-marker text-danger"></i> | ||||||
|  |                         </button> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |  | ||||||
|  |                 <!-- 终点 --> | ||||||
|  |                 <div class="space-y-1"> | ||||||
|  |                     <label class="block text-sm font-medium text-gray-700">终点 <span class="text-danger">*</span></label> | ||||||
|  |                     <div class="flex space-x-2"> | ||||||
|  |                         <div class="flex-1 space-y-0.5"> | ||||||
|  |                             <input type="text" id="endLat" placeholder="纬度" | ||||||
|  |                                    class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary text-sm transition-all" | ||||||
|  |                                    maxlength="10"> | ||||||
|  |                             <span id="endLatError" class="text-danger text-xs hidden">请输入有效纬度</span> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="flex-1 space-y-0.5"> | ||||||
|  |                             <input type="text" id="endLng" placeholder="经度" | ||||||
|  |                                    class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary text-sm transition-all" | ||||||
|  |                                    maxlength="10"> | ||||||
|  |                             <span id="endLngError" class="text-danger text-xs hidden">请输入有效经度</span> | ||||||
|  |                         </div> | ||||||
|  |                         <button id="setEndBtn" class="p-2 bg-gray-100 hover:bg-gray-200 rounded transition-colors duration-200" | ||||||
|  |                                 title="在地图上选择终点"> | ||||||
|  |                             <i class="fa fa-flag text-success"></i> | ||||||
|  |                         </button> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |  | ||||||
|  |                 <!-- 途经点 --> | ||||||
|  |                 <div class="space-y-1"> | ||||||
|  |                     <div class="flex justify-between items-center"> | ||||||
|  |                         <label class="block text-sm font-medium text-gray-700">途经点</label> | ||||||
|  |                         <button id="addWaypointBtn" | ||||||
|  |                                 class="text-xs px-2 py-1 bg-gray-100 hover:bg-gray-200 rounded transition-colors duration-200 flex items-center"> | ||||||
|  |                             <i class="fa fa-plus mr-1"></i>添加 | ||||||
|  |                         </button> | ||||||
|  |                     </div> | ||||||
|  |                     <div id="waypointsContainer" class="space-y-2"> | ||||||
|  |                         <!-- 途经点动态添加 --> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |  | ||||||
|  |                 <!-- 交通方式 --> | ||||||
|  |                 <div class="space-y-1"> | ||||||
|  |                     <label class="block text-sm font-medium text-gray-700">交通方式 <span class="text-danger">*</span></label> | ||||||
|  |                     <select id="profile" | ||||||
|  |                             class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary text-sm transition-all bg-white"> | ||||||
|  |                         <option value="car">驾车</option> | ||||||
|  |                         <option value="bike">骑行</option> | ||||||
|  |                         <option value="foot">步行</option> | ||||||
|  |                     </select> | ||||||
|  |                 </div> | ||||||
|  |  | ||||||
|  |                 <!-- 计算路径按钮 --> | ||||||
|  |                 <button id="calculateRouteBtn" | ||||||
|  |                         class="w-full py-2.5 bg-primary hover:bg-primary/90 text-white rounded-md font-medium transition-colors duration-200 flex items-center justify-center btn-disabled" | ||||||
|  |                         disabled> | ||||||
|  |                     <i class="fa fa-calculator mr-2"></i>请先加载地图 | ||||||
|  |                 </button> | ||||||
|  |             </div> | ||||||
|  |  | ||||||
|  |             <!-- 结果展示区域 --> | ||||||
|  |             <div id="resultContainer" class="p-4 border border-gray-100 rounded-lg bg-neutral shadow-sm hidden"> | ||||||
|  |                 <h2 class="text-lg font-semibold mb-3 flex items-center text-neutral-dark"> | ||||||
|  |                     <i class="fa fa-check-circle text-success mr-2"></i>路径结果 | ||||||
|  |                 </h2> | ||||||
|  |  | ||||||
|  |                 <div class="grid grid-cols-2 gap-4 mb-4"> | ||||||
|  |                     <div class="bg-white p-3 rounded shadow-sm border border-gray-100 hover:shadow-md transition-shadow"> | ||||||
|  |                         <div class="text-xs text-gray-500 mb-1">总距离</div> | ||||||
|  |                         <div id="distanceResult" class="text-lg font-semibold text-gray-800">-</div> | ||||||
|  |                     </div> | ||||||
|  |                     <div class="bg-white p-3 rounded shadow-sm border border-gray-100 hover:shadow-md transition-shadow"> | ||||||
|  |                         <div class="text-xs text-gray-500 mb-1">预计时间</div> | ||||||
|  |                         <div id="timeResult" class="text-lg font-semibold text-gray-800">-</div> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |  | ||||||
|  |                 <div class="grid grid-cols-2 gap-2"> | ||||||
|  |                     <button id="clearRouteBtn" | ||||||
|  |                             class="py-2 border border-gray-300 bg-white hover:bg-gray-50 text-gray-700 rounded-md text-sm font-medium transition-colors duration-200"> | ||||||
|  |                         <i class="fa fa-trash mr-1"></i>清除路径 | ||||||
|  |                     </button> | ||||||
|  |                     <button id="clearAllBtn" | ||||||
|  |                             class="py-2 border border-gray-300 bg-white hover:bg-gray-50 text-gray-700 rounded-md text-sm font-medium transition-colors duration-200"> | ||||||
|  |                         <i class="fa fa-refresh mr-1"></i>清空所有 | ||||||
|  |                     </button> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     </aside> | ||||||
|  |  | ||||||
|  |     <!-- 右侧地图区域 --> | ||||||
|  |     <section class="flex-1 relative"> | ||||||
|  |         <div id="map" class="w-full map-height z-0"></div> | ||||||
|  |  | ||||||
|  |         <!-- 地图控件 --> | ||||||
|  |         <div class="absolute top-4 right-4 z-10 flex flex-col space-y-2"> | ||||||
|  |             <button id="zoomInBtn" | ||||||
|  |                     class="w-10 h-10 bg-white rounded-full shadow-md flex items-center justify-center hover:bg-gray-100 transition-colors duration-200" | ||||||
|  |                     title="放大"> | ||||||
|  |                 <i class="fa fa-plus"></i> | ||||||
|  |             </button> | ||||||
|  |             <button id="zoomOutBtn" | ||||||
|  |                     class="w-10 h-10 bg-white rounded-full shadow-md flex items-center justify-center hover:bg-gray-100 transition-colors duration-200" | ||||||
|  |                     title="缩小"> | ||||||
|  |                 <i class="fa fa-minus"></i> | ||||||
|  |             </button> | ||||||
|  |             <button id="centerMapBtn" | ||||||
|  |                     class="w-10 h-10 bg-white rounded-full shadow-md flex items-center justify-center hover:bg-gray-100 transition-colors duration-200" | ||||||
|  |                     title="重置中心(成都)"> | ||||||
|  |                 <i class="fa fa-crosshairs"></i> | ||||||
|  |             </button> | ||||||
|  |         </div> | ||||||
|  |  | ||||||
|  |         <!-- 地图操作提示(移动端) --> | ||||||
|  |         <div class="absolute bottom-4 left-4 z-10 md:hidden bg-white/90 px-3 py-2 rounded-full text-xs text-neutral-dark shadow-md"> | ||||||
|  |             <i class="fa fa-hand-pointer-o text-primary mr-1"></i>点击地图设起点/终点 | ||||||
|  |         </div> | ||||||
|  |     </section> | ||||||
|  | </main> | ||||||
|  |  | ||||||
|  | <script> | ||||||
|  |     // 大整数转字符串处理(保留原逻辑) | ||||||
|  |     axios.defaults.transformResponse = [ | ||||||
|  |         function(data) { | ||||||
|  |             if (typeof data !== 'string') return data; | ||||||
|  |             const bigIntRegex = /(\s*"[^"]*"\s*:\s*)(\d{16,})(\s*)/g; | ||||||
|  |             return data.replace(bigIntRegex, (match, keyPart, bigInt, endPart) => { | ||||||
|  |                 return `${keyPart}"${bigInt}"${endPart}`; | ||||||
|  |             }); | ||||||
|  |         }, | ||||||
|  |         function(parsedData) { | ||||||
|  |             try { | ||||||
|  |                 return JSON.parse(parsedData); | ||||||
|  |             } catch (e) { | ||||||
|  |                 return parsedData; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     ]; | ||||||
|  |  | ||||||
|  |     // 全局变量 | ||||||
|  |     let map; | ||||||
|  |     let startMarker = null; | ||||||
|  |     let endMarker = null; | ||||||
|  |     let waypointMarkers = []; | ||||||
|  |     let routeLine = null; | ||||||
|  |     let waypointCount = 0; | ||||||
|  |     const API_BASE_URL = "http://127.0.0.1:8848"; | ||||||
|  |     const DEFAULT_CENTER = { lat: 30.6570, lng: 104.0650 }; // 成都默认坐标 | ||||||
|  |  | ||||||
|  |     // 地图初始化 | ||||||
|  |     function initMap() { | ||||||
|  |         map = L.map('map', { | ||||||
|  |             zoomControl: false, | ||||||
|  |             attributionControl: true, | ||||||
|  |             minZoom: 5, | ||||||
|  |             maxZoom: 18 | ||||||
|  |         }).setView([DEFAULT_CENTER.lat, DEFAULT_CENTER.lng], 12); | ||||||
|  |  | ||||||
|  |         L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { | ||||||
|  |             maxZoom: 18, | ||||||
|  |             tileSize: 256, | ||||||
|  |             zoomOffset: 0 | ||||||
|  |         }).addTo(map); | ||||||
|  |  | ||||||
|  |         map.on('click', handleMapClick); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 表单验证 | ||||||
|  |     function validateCoord(value, type) { | ||||||
|  |         const num = parseFloat(value); | ||||||
|  |         if (isNaN(num)) return false; | ||||||
|  |         return type === 'lat' ? (num >= -90 && num <= 90) : (num >= -180 && num <= 180); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     function toggleInputError(inputId, errorId, show, inputEl = null, errorEl = null) { | ||||||
|  |         inputEl = inputEl || document.getElementById(inputId); | ||||||
|  |         errorEl = errorEl || document.getElementById(errorId); | ||||||
|  |  | ||||||
|  |         if (show) { | ||||||
|  |             inputEl.classList.add('input-error'); | ||||||
|  |             errorEl.classList.remove('hidden'); | ||||||
|  |         } else { | ||||||
|  |             inputEl.classList.remove('input-error'); | ||||||
|  |             errorEl.classList.add('hidden'); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 绑定坐标验证 | ||||||
|  |     function bindCoordValidation() { | ||||||
|  |         // 起点验证 | ||||||
|  |         document.getElementById('startLat').addEventListener('input', (e) => { | ||||||
|  |             const isValid = validateCoord(e.target.value, 'lat'); | ||||||
|  |             toggleInputError('startLat', 'startLatError', !isValid); | ||||||
|  |         }); | ||||||
|  |         document.getElementById('startLng').addEventListener('input', (e) => { | ||||||
|  |             const isValid = validateCoord(e.target.value, 'lng'); | ||||||
|  |             toggleInputError('startLng', 'startLngError', !isValid); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // 终点验证 | ||||||
|  |         document.getElementById('endLat').addEventListener('input', (e) => { | ||||||
|  |             const isValid = validateCoord(e.target.value, 'lat'); | ||||||
|  |             toggleInputError('endLat', 'endLatError', !isValid); | ||||||
|  |         }); | ||||||
|  |         document.getElementById('endLng').addEventListener('input', (e) => { | ||||||
|  |             const isValid = validateCoord(e.target.value, 'lng'); | ||||||
|  |             toggleInputError('endLng', 'endLngError', !isValid); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 标记管理 | ||||||
|  |     function setStartPoint(lat, lng) { | ||||||
|  |         const latEl = document.getElementById('startLat'); | ||||||
|  |         const lngEl = document.getElementById('startLng'); | ||||||
|  |  | ||||||
|  |         latEl.value = lat.toFixed(6); | ||||||
|  |         lngEl.value = lng.toFixed(6); | ||||||
|  |         latEl.dispatchEvent(new Event('input')); | ||||||
|  |         lngEl.dispatchEvent(new Event('input')); | ||||||
|  |  | ||||||
|  |         if (startMarker) { | ||||||
|  |             startMarker.setLatLng([lat, lng]); | ||||||
|  |         } else { | ||||||
|  |             startMarker = L.marker([lat, lng], { | ||||||
|  |                 icon: L.divIcon({ | ||||||
|  |                     className: 'custom-marker', | ||||||
|  |                     html: '<div class="w-6 h-6 bg-danger rounded-full flex items-center justify-center text-white shadow-md"><i class="fa fa-map-marker"></i></div>', | ||||||
|  |                     iconSize: [30, 30], | ||||||
|  |                     iconAnchor: [15, 30] | ||||||
|  |                 }), | ||||||
|  |                 draggable: true, | ||||||
|  |                 riseOnHover: true | ||||||
|  |             }).addTo(map); | ||||||
|  |  | ||||||
|  |             startMarker.on('dragend', (e) => { | ||||||
|  |                 const { lat, lng } = e.target.getLatLng(); | ||||||
|  |                 setStartPoint(lat, lng); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     function setEndPoint(lat, lng) { | ||||||
|  |         const latEl = document.getElementById('endLat'); | ||||||
|  |         const lngEl = document.getElementById('endLng'); | ||||||
|  |  | ||||||
|  |         latEl.value = lat.toFixed(6); | ||||||
|  |         lngEl.value = lng.toFixed(6); | ||||||
|  |         latEl.dispatchEvent(new Event('input')); | ||||||
|  |         lngEl.dispatchEvent(new Event('input')); | ||||||
|  |  | ||||||
|  |         if (endMarker) { | ||||||
|  |             endMarker.setLatLng([lat, lng]); | ||||||
|  |         } else { | ||||||
|  |             endMarker = L.marker([lat, lng], { | ||||||
|  |                 icon: L.divIcon({ | ||||||
|  |                     className: 'custom-marker', | ||||||
|  |                     html: '<div class="w-6 h-6 bg-success rounded-full flex items-center justify-center text-white shadow-md"><i class="fa fa-flag"></i></div>', | ||||||
|  |                     iconSize: [30, 30], | ||||||
|  |                     iconAnchor: [15, 30] | ||||||
|  |                 }), | ||||||
|  |                 draggable: true, | ||||||
|  |                 riseOnHover: true | ||||||
|  |             }).addTo(map); | ||||||
|  |  | ||||||
|  |             endMarker.on('dragend', (e) => { | ||||||
|  |                 const { lat, lng } = e.target.getLatLng(); | ||||||
|  |                 setEndPoint(lat, lng); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     function addWaypoint(lat = '', lng = '') { | ||||||
|  |         waypointCount++; | ||||||
|  |         const container = document.getElementById('waypointsContainer'); | ||||||
|  |         const waypointDiv = document.createElement('div'); | ||||||
|  |         waypointDiv.className = 'flex space-x-2 waypoint-item'; | ||||||
|  |         waypointDiv.dataset.id = waypointCount; | ||||||
|  |  | ||||||
|  |         waypointDiv.innerHTML = ` | ||||||
|  |             <div class="flex-1 space-y-0.5"> | ||||||
|  |                 <input type="text" class="waypoint-lat w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary text-sm transition-all" | ||||||
|  |                        placeholder="纬度" value="${lat}" maxlength="10"> | ||||||
|  |                 <span class="waypoint-lat-error text-danger text-xs hidden">请输入有效纬度</span> | ||||||
|  |             </div> | ||||||
|  |             <div class="flex-1 space-y-0.5"> | ||||||
|  |                 <input type="text" class="waypoint-lng w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary text-sm transition-all" | ||||||
|  |                        placeholder="经度" value="${lng}" maxlength="10"> | ||||||
|  |                 <span class="waypoint-lng-error text-danger text-xs hidden">请输入有效经度</span> | ||||||
|  |             </div> | ||||||
|  |             <button class="set-waypoint-btn p-2 bg-gray-100 hover:bg-gray-200 rounded transition-colors duration-200" title="在地图上选择"> | ||||||
|  |                 <i class="fa fa-map-pin text-primary"></i> | ||||||
|  |             </button> | ||||||
|  |             <button class="remove-waypoint-btn p-2 bg-gray-100 hover:bg-gray-200 rounded transition-colors duration-200" title="删除途经点"> | ||||||
|  |                 <i class="fa fa-times text-gray-500"></i> | ||||||
|  |             </button> | ||||||
|  |         `; | ||||||
|  |  | ||||||
|  |         container.appendChild(waypointDiv); | ||||||
|  |  | ||||||
|  |         const latInput = waypointDiv.querySelector('.waypoint-lat'); | ||||||
|  |         const lngInput = waypointDiv.querySelector('.waypoint-lng'); | ||||||
|  |         const latError = waypointDiv.querySelector('.waypoint-lat-error'); | ||||||
|  |         const lngError = waypointDiv.querySelector('.waypoint-lng-error'); | ||||||
|  |  | ||||||
|  |         latInput.addEventListener('input', (e) => { | ||||||
|  |             const isValid = validateCoord(e.target.value, 'lat'); | ||||||
|  |             toggleInputError(null, null, !isValid, latInput, latError); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         lngInput.addEventListener('input', (e) => { | ||||||
|  |             const isValid = validateCoord(e.target.value, 'lng'); | ||||||
|  |             toggleInputError(null, null, !isValid, lngInput, lngError); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // 途经点地图选点 | ||||||
|  |         waypointDiv.querySelector('.set-waypoint-btn').addEventListener('click', () => { | ||||||
|  |             const id = parseInt(waypointDiv.dataset.id); | ||||||
|  |             map.once('click', (e) => { | ||||||
|  |                 const { lat, lng } = e.latlng; | ||||||
|  |                 latInput.value = lat.toFixed(6); | ||||||
|  |                 lngInput.value = lng.toFixed(6); | ||||||
|  |                 latInput.dispatchEvent(new Event('input')); | ||||||
|  |                 lngInput.dispatchEvent(new Event('input')); | ||||||
|  |  | ||||||
|  |                 if (waypointMarkers[id]) { | ||||||
|  |                     waypointMarkers[id].setLatLng([lat, lng]); | ||||||
|  |                 } else { | ||||||
|  |                     waypointMarkers[id] = L.marker([lat, lng], { | ||||||
|  |                         icon: L.divIcon({ | ||||||
|  |                             className: 'custom-marker', | ||||||
|  |                             html: `<div class="w-5 h-5 bg-primary rounded-full flex items-center justify-center text-white text-xs shadow-md">${id}</div>`, | ||||||
|  |                             iconSize: [25, 25], | ||||||
|  |                             iconAnchor: [12, 25] | ||||||
|  |                         }), | ||||||
|  |                         draggable: true, | ||||||
|  |                         riseOnHover: true | ||||||
|  |                     }).addTo(map); | ||||||
|  |  | ||||||
|  |                     waypointMarkers[id].on('dragend', (e) => { | ||||||
|  |                         const { lat, lng } = e.target.getLatLng(); | ||||||
|  |                         latInput.value = lat.toFixed(6); | ||||||
|  |                         lngInput.value = lng.toFixed(6); | ||||||
|  |                         latInput.dispatchEvent(new Event('input')); | ||||||
|  |                         lngInput.dispatchEvent(new Event('input')); | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // 删除途经点 | ||||||
|  |         waypointDiv.querySelector('.remove-waypoint-btn').addEventListener('click', () => { | ||||||
|  |             const id = parseInt(waypointDiv.dataset.id); | ||||||
|  |             if (waypointMarkers[id]) { | ||||||
|  |                 map.removeLayer(waypointMarkers[id]); | ||||||
|  |                 waypointMarkers[id] = null; | ||||||
|  |             } | ||||||
|  |             waypointDiv.remove(); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 地图点击处理 | ||||||
|  |     function handleMapClick(e) { | ||||||
|  |         const { lat, lng } = e.latlng; | ||||||
|  |         const startLat = document.getElementById('startLat').value; | ||||||
|  |         const endLat = document.getElementById('endLat').value; | ||||||
|  |  | ||||||
|  |         // 先设起点(空则设起点)→ 再设终点 | ||||||
|  |         if (!startLat.trim()) { | ||||||
|  |             setStartPoint(lat, lng); | ||||||
|  |         } else if (!endLat.trim()) { | ||||||
|  |             setEndPoint(lat, lng); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 路径操作 | ||||||
|  |     function clearRoute() { | ||||||
|  |         if (routeLine) { | ||||||
|  |             map.removeLayer(routeLine); | ||||||
|  |             routeLine = null; | ||||||
|  |         } | ||||||
|  |         document.getElementById('resultContainer').classList.add('hidden'); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 清空所有:起点重置为空(而非默认坐标) | ||||||
|  |     function clearAll() { | ||||||
|  |         clearRoute(); | ||||||
|  |  | ||||||
|  |         // 清除起点(重置为空) | ||||||
|  |         if (startMarker) { | ||||||
|  |             map.removeLayer(startMarker); | ||||||
|  |             startMarker = null; | ||||||
|  |         } | ||||||
|  |         document.getElementById('startLat').value = ''; | ||||||
|  |         document.getElementById('startLng').value = ''; | ||||||
|  |         toggleInputError('startLat', 'startLatError', false); | ||||||
|  |         toggleInputError('startLng', 'startLngError', false); | ||||||
|  |  | ||||||
|  |         // 清除终点 | ||||||
|  |         if (endMarker) { | ||||||
|  |             map.removeLayer(endMarker); | ||||||
|  |             endMarker = null; | ||||||
|  |         } | ||||||
|  |         document.getElementById('endLat').value = ''; | ||||||
|  |         document.getElementById('endLng').value = ''; | ||||||
|  |         toggleInputError('endLat', 'endLatError', false); | ||||||
|  |         toggleInputError('endLng', 'endLngError', false); | ||||||
|  |  | ||||||
|  |         // 清除途经点 | ||||||
|  |         waypointMarkers.forEach(marker => { | ||||||
|  |             if (marker) map.removeLayer(marker); | ||||||
|  |         }); | ||||||
|  |         waypointMarkers = []; | ||||||
|  |         document.getElementById('waypointsContainer').innerHTML = ''; | ||||||
|  |         waypointCount = 0; | ||||||
|  |         addWaypoint(); // 重置默认空途经点 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 地图加载进度 | ||||||
|  |     function updateLoadProgress(show, text = '正在处理...') { | ||||||
|  |         const progressEl = document.getElementById('loadProgress'); | ||||||
|  |         const textEl = document.getElementById('progressText'); | ||||||
|  |  | ||||||
|  |         if (show) { | ||||||
|  |             progressEl.classList.remove('hidden'); | ||||||
|  |             textEl.textContent = text; | ||||||
|  |         } else { | ||||||
|  |             progressEl.classList.add('hidden'); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 上传PBF文件 | ||||||
|  |     async function uploadPbfFile(file) { | ||||||
|  |         try { | ||||||
|  |             const formData = new FormData(); | ||||||
|  |             formData.append('files', file); | ||||||
|  |             updateLoadProgress(true, '正在上传地图文件...'); | ||||||
|  |  | ||||||
|  |             const response = await axios.post(`${API_BASE_URL}/fileInfo/upload`, formData, { | ||||||
|  |                 onUploadProgress: (progressEvent) => { | ||||||
|  |                     const percent = Math.round((progressEvent.loaded / progressEvent.total) * 100); | ||||||
|  |                     if (percent < 100) { | ||||||
|  |                         updateLoadProgress(true, `上传中... ${percent}%`); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             if (response.data.code !== 200 || !response.data.data || !response.data.data[0]) { | ||||||
|  |                 throw new Error(`上传失败:${response.data.message || '未知错误'}`); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             const fileId = response.data.data[0].id; | ||||||
|  |             updateLoadProgress(true, `上传成功(ID: ${fileId})、正在加载地图...`); | ||||||
|  |             return fileId; | ||||||
|  |         } catch (error) { | ||||||
|  |             const errorMsg = error.response?.data?.message || error.message || '上传异常'; | ||||||
|  |             updateLoadProgress(false); | ||||||
|  |             alert(`⚠️ 上传失败:${errorMsg}`); | ||||||
|  |             throw error; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 加载地图 | ||||||
|  |     async function loadMapByFileId(fileId) { | ||||||
|  |         try { | ||||||
|  |             const formData = new FormData(); | ||||||
|  |             formData.append('fileId', fileId); | ||||||
|  |  | ||||||
|  |             const response = await axios.post(`${API_BASE_URL}/graphhopper/loadMap`, formData); | ||||||
|  |             if (response.data.code !== 200) { | ||||||
|  |                 throw new Error(`加载失败:${response.data.message || '接口返回异常'}`); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             updateLoadProgress(false); | ||||||
|  |             const calcBtn = document.getElementById('calculateRouteBtn'); | ||||||
|  |             calcBtn.disabled = false; | ||||||
|  |             calcBtn.classList.remove('btn-disabled'); | ||||||
|  |             calcBtn.innerHTML = '<i class="fa fa-calculator mr-2"></i>计算路径'; | ||||||
|  |             return true; | ||||||
|  |         } catch (error) { | ||||||
|  |             const errorMsg = error.response?.data?.message || error.message || '加载异常'; | ||||||
|  |             updateLoadProgress(false); | ||||||
|  |             alert(`⚠️ 地图加载失败:${errorMsg}`); | ||||||
|  |             throw error; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 计算路径 | ||||||
|  |     async function calculateRoute() { | ||||||
|  |         const startLat = parseFloat(document.getElementById('startLat').value); | ||||||
|  |         const startLng = parseFloat(document.getElementById('startLng').value); | ||||||
|  |         const endLat = parseFloat(document.getElementById('endLat').value); | ||||||
|  |         const endLng = parseFloat(document.getElementById('endLng').value); | ||||||
|  |         const profile = document.getElementById('profile').value; | ||||||
|  |  | ||||||
|  |         // 基础验证 | ||||||
|  |         if ([startLat, startLng, endLat, endLng].some(isNaN)) { | ||||||
|  |             alert('⚠️ 请确保起点/终点坐标为有效数字'); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         if (!validateCoord(startLat, 'lat') || !validateCoord(startLng, 'lng')) { | ||||||
|  |             alert('⚠️ 起点坐标超出有效范围(纬度-90~90、经度-180~180)'); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         if (!validateCoord(endLat, 'lat') || !validateCoord(endLng, 'lng')) { | ||||||
|  |             alert('⚠️ 终点坐标超出有效范围(纬度-90~90、经度-180~180)'); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 处理途经点 | ||||||
|  |         const waypoints = []; | ||||||
|  |         document.querySelectorAll('.waypoint-item').forEach(item => { | ||||||
|  |             const lat = parseFloat(item.querySelector('.waypoint-lat').value); | ||||||
|  |             const lng = parseFloat(item.querySelector('.waypoint-lng').value); | ||||||
|  |             if (!isNaN(lat) && !isNaN(lng) && validateCoord(lat, 'lat') && validateCoord(lng, 'lng')) { | ||||||
|  |                 waypoints.push({ lat, lng }); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // 发起请求 | ||||||
|  |         const calcBtn = document.getElementById('calculateRouteBtn'); | ||||||
|  |         calcBtn.disabled = true; | ||||||
|  |         calcBtn.innerHTML = '<i class="fa fa-spinner fa-spin mr-2"></i>计算中...'; | ||||||
|  |  | ||||||
|  |         try { | ||||||
|  |             const response = await axios.post( | ||||||
|  |                 `${API_BASE_URL}/graphhopper/route`, | ||||||
|  |                 { startLat, startLng, endLat, endLng, profile, waypoints }, | ||||||
|  |                 { headers: { 'Content-Type': 'application/json' } } | ||||||
|  |             ); | ||||||
|  |  | ||||||
|  |             if (response.data.code !== 200 || !response.data.data) { | ||||||
|  |                 throw new Error(`计算失败:${response.data.message || '接口返回异常'}`); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             handleRouteResponse(response.data.data); | ||||||
|  |         } catch (error) { | ||||||
|  |             const errorMsg = error.response?.data?.message || error.message || '计算异常'; | ||||||
|  |             alert(`⚠️ 路径计算失败:${errorMsg}`); | ||||||
|  |         } finally { | ||||||
|  |             calcBtn.disabled = false; | ||||||
|  |             calcBtn.innerHTML = '<i class="fa fa-calculator mr-2"></i>计算路径'; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 处理路径结果 | ||||||
|  |     function handleRouteResponse(routeData) { | ||||||
|  |         document.getElementById('distanceResult').textContent = `${routeData.distanceKm.toFixed(2)} 公里`; | ||||||
|  |         document.getElementById('timeResult').textContent = `${routeData.timeMinutes} 分钟`; | ||||||
|  |         document.getElementById('resultContainer').classList.remove('hidden'); | ||||||
|  |  | ||||||
|  |         if (routeLine) map.removeLayer(routeLine); | ||||||
|  |         const latLngs = routeData.pathPoints.map(point => [point.lat, point.lng]); | ||||||
|  |         const lineStyles = { | ||||||
|  |             car: { color: '#165DFF', weight: 5, opacity: 0.8, dashArray: '' }, | ||||||
|  |             bike: { color: '#00B42A', weight: 4, opacity: 0.8, dashArray: '5,5' }, | ||||||
|  |             foot: { color: '#4b0c35', weight: 3, opacity: 0.8, dashArray: '2,2' } | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         routeLine = L.polyline(latLngs, lineStyles[document.getElementById('profile').value]) | ||||||
|  |             .addTo(map) | ||||||
|  |             .bindPopup(`<div class="text-sm"><p>距离:${routeData.distanceKm.toFixed(2)} 公里</p><p>时间:${routeData.timeMinutes} 分钟</p></div>`); | ||||||
|  |  | ||||||
|  |         map.fitBounds(routeLine.getBounds(), { padding: [50, 50], maxZoom: 14 }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 事件绑定:移除起点按钮弹窗 | ||||||
|  |     function bindEvents() { | ||||||
|  |         // 文件选择相关 | ||||||
|  |         document.getElementById('selectFileBtn').addEventListener('click', () => { | ||||||
|  |             document.getElementById('mapFile').click(); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         document.getElementById('mapFile').addEventListener('change', (e) => { | ||||||
|  |             const fileInfoEl = document.getElementById('fileInfo'); | ||||||
|  |             if (e.target.files.length === 0) { | ||||||
|  |                 fileInfoEl.classList.add('hidden'); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             const file = e.target.files[0]; | ||||||
|  |             if (file.name.toLowerCase().endsWith('.pbf')) { | ||||||
|  |                 const fileSizeMB = (file.size / (1024 * 1024)).toFixed(2); | ||||||
|  |                 fileInfoEl.textContent = `已选择:${file.name}(${fileSizeMB} MB)`; | ||||||
|  |                 fileInfoEl.className = 'mt-1 text-sm text-gray-600'; | ||||||
|  |             } else { | ||||||
|  |                 fileInfoEl.textContent = '❌ 请选择PBF格式的地图文件'; | ||||||
|  |                 fileInfoEl.className = 'mt-1 text-sm text-danger'; | ||||||
|  |                 e.target.value = ''; | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         document.getElementById('loadMapBtn').addEventListener('click', async () => { | ||||||
|  |             const fileInput = document.getElementById('mapFile'); | ||||||
|  |             if (fileInput.files.length === 0) { | ||||||
|  |                 alert('⚠️ 请先选择PBF格式地图文件'); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             try { | ||||||
|  |                 const file = fileInput.files[0]; | ||||||
|  |                 const fileId = await uploadPbfFile(file); | ||||||
|  |                 await loadMapByFileId(fileId); | ||||||
|  |                 fileInput.value = ''; | ||||||
|  |                 document.getElementById('fileInfo').classList.add('hidden'); | ||||||
|  |             } catch (error) { | ||||||
|  |                 console.error('地图加载失败:', error); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // 起点按钮:移除弹窗、仅保留地图选点逻辑 | ||||||
|  |         document.getElementById('setStartBtn').addEventListener('click', () => { | ||||||
|  |             map.once('click', (e) => setStartPoint(e.latlng.lat, e.latlng.lng)); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // 终点按钮(保留弹窗、如需统一移除可删除alert) | ||||||
|  |         document.getElementById('setEndBtn').addEventListener('click', () => { | ||||||
|  |             map.once('click', (e) => setEndPoint(e.latlng.lat, e.latlng.lng)); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // 途经点/路径操作 | ||||||
|  |         document.getElementById('addWaypointBtn').addEventListener('click', () => addWaypoint()); | ||||||
|  |         document.getElementById('calculateRouteBtn').addEventListener('click', calculateRoute); | ||||||
|  |         document.getElementById('clearRouteBtn').addEventListener('click', clearRoute); | ||||||
|  |         document.getElementById('clearAllBtn').addEventListener('click', clearAll); | ||||||
|  |  | ||||||
|  |         // 地图控件 | ||||||
|  |         document.getElementById('zoomInBtn').addEventListener('click', () => map.zoomIn()); | ||||||
|  |         document.getElementById('zoomOutBtn').addEventListener('click', () => map.zoomOut()); | ||||||
|  |         document.getElementById('centerMapBtn').addEventListener('click', () => { | ||||||
|  |             map.setView([DEFAULT_CENTER.lat, DEFAULT_CENTER.lng], 12); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // 表单验证绑定 | ||||||
|  |         bindCoordValidation(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 应用初始化 | ||||||
|  |     function initApp() { | ||||||
|  |         initMap(); | ||||||
|  |         bindEvents(); | ||||||
|  |         addWaypoint(); // 默认添加1个空途经点 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 页面加载完成后初始化 | ||||||
|  |     window.addEventListener('load', initApp); | ||||||
|  | </script> | ||||||
|  | </body> | ||||||
|  | </html> | ||||||
							
								
								
									
										438
									
								
								index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										438
									
								
								index.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,438 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <html lang="zh-CN"> | ||||||
|  | <head> | ||||||
|  |     <meta charset="UTF-8"> | ||||||
|  |     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||||
|  |     <title>Cesium 多资源整合加载平台</title> | ||||||
|  |     <!-- 引入Cesium库(使用1.116稳定版本) --> | ||||||
|  |     <script src="https://cesium.com/downloads/cesiumjs/releases/1.116/Build/Cesium/Cesium.js"></script> | ||||||
|  |     <link href="https://cesium.com/downloads/cesiumjs/releases/1.116/Build/Cesium/Widgets/widgets.css" rel="stylesheet"> | ||||||
|  |     <style> | ||||||
|  |         /* 基础样式重置 */ | ||||||
|  |         html, body, #cesiumContainer { | ||||||
|  |             width: 100%; | ||||||
|  |             height: 100%; | ||||||
|  |             margin: 0; | ||||||
|  |             padding: 0; | ||||||
|  |             overflow: hidden; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /* 控制面板样式 */ | ||||||
|  |         .control-panel { | ||||||
|  |             position: absolute; | ||||||
|  |             top: 20px; | ||||||
|  |             left: 20px; | ||||||
|  |             background: rgba(255, 255, 255, 0.95); | ||||||
|  |             padding: 20px; | ||||||
|  |             border-radius: 8px; | ||||||
|  |             box-shadow: 0 3px 15px rgba(0, 0, 0, 0.15); | ||||||
|  |             z-index: 100; | ||||||
|  |             width: 420px; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /* 表单标题 */ | ||||||
|  |         .panel-title { | ||||||
|  |             margin: 0 0 15px; | ||||||
|  |             font-size: 18px; | ||||||
|  |             color: #2c3e50; | ||||||
|  |             border-bottom: 1px solid #eee; | ||||||
|  |             padding-bottom: 8px; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /* 表单分组样式 */ | ||||||
|  |         .form-group { | ||||||
|  |             margin-bottom: 15px; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         .form-group label { | ||||||
|  |             display: block; | ||||||
|  |             margin-bottom: 6px; | ||||||
|  |             font-weight: 500; | ||||||
|  |             color: #34495e; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /* 输入框、选择框样式 */ | ||||||
|  |         .form-control { | ||||||
|  |             width: 100%; | ||||||
|  |             padding: 10px; | ||||||
|  |             border: 1px solid #ddd; | ||||||
|  |             border-radius: 4px; | ||||||
|  |             font-size: 14px; | ||||||
|  |             box-sizing: border-box; | ||||||
|  |             transition: border-color 0.3s; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         .form-control:focus { | ||||||
|  |             outline: none; | ||||||
|  |             border-color: #0070f3; | ||||||
|  |             box-shadow: 0 0 0 2px rgba(0, 112, 243, 0.1); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /* 经纬度输入行布局 */ | ||||||
|  |         .coord-group { | ||||||
|  |             display: flex; | ||||||
|  |             gap: 10px; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         .coord-group .form-group { | ||||||
|  |             flex: 1; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /* 按钮样式 */ | ||||||
|  |         .btn-group { | ||||||
|  |             display: flex; | ||||||
|  |             gap: 10px; | ||||||
|  |             margin-top: 10px; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         .btn { | ||||||
|  |             padding: 10px 16px; | ||||||
|  |             border: none; | ||||||
|  |             border-radius: 4px; | ||||||
|  |             font-size: 14px; | ||||||
|  |             cursor: pointer; | ||||||
|  |             transition: background-color 0.3s; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         .btn-primary { | ||||||
|  |             background-color: #0070f3; | ||||||
|  |             color: white; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         .btn-primary:hover { | ||||||
|  |             background-color: #0051aa; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         .btn-locate { | ||||||
|  |             background-color: #22c55e; | ||||||
|  |             color: white; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         .btn-locate:hover { | ||||||
|  |             background-color: #16a34a; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         .btn-clear { | ||||||
|  |             background-color: #f37000; | ||||||
|  |             color: white; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         .btn-clear:hover { | ||||||
|  |             background-color: #d96000; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /* 状态提示样式 */ | ||||||
|  |         .status { | ||||||
|  |             margin-top: 12px; | ||||||
|  |             padding: 10px; | ||||||
|  |             border-radius: 4px; | ||||||
|  |             font-size: 14px; | ||||||
|  |             display: none; /* 默认隐藏 */ | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         .status-success { | ||||||
|  |             background-color: #e8f5e9; | ||||||
|  |             color: #2e7d32; | ||||||
|  |             display: block; /* 成功时显示 */ | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         .status-error { | ||||||
|  |             background-color: #fdecea; | ||||||
|  |             color: #d32f2f; | ||||||
|  |             display: block; /* 错误时显示 */ | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /* 鼠标经纬度提示框样式 */ | ||||||
|  |         .latlng-tooltip { | ||||||
|  |             position: absolute; | ||||||
|  |             background: rgba(0, 0, 0, 0.8); | ||||||
|  |             color: white; | ||||||
|  |             padding: 6px 10px; | ||||||
|  |             border-radius: 4px; | ||||||
|  |             font-size: 12px; | ||||||
|  |             pointer-events: none; /* 避免遮挡鼠标事件 */ | ||||||
|  |             z-index: 1000; /* 确保在最上层显示 */ | ||||||
|  |             opacity: 0; /* 默认透明 */ | ||||||
|  |             transition: opacity 0.2s; /* 淡入淡出效果 */ | ||||||
|  |             white-space: nowrap; /* 防止文本换行 */ | ||||||
|  |         } | ||||||
|  |     </style> | ||||||
|  | </head> | ||||||
|  | <body> | ||||||
|  | <!-- Cesium 容器 --> | ||||||
|  | <div id="cesiumContainer"></div> | ||||||
|  |  | ||||||
|  | <!-- 鼠标经纬度提示框 --> | ||||||
|  | <div id="latlngTooltip" class="latlng-tooltip"></div> | ||||||
|  |  | ||||||
|  | <!-- 控制表单面板 --> | ||||||
|  | <div class="control-panel"> | ||||||
|  |     <h3 class="panel-title">Cesium 资源加载与定位</h3> | ||||||
|  |  | ||||||
|  |     <!-- 基础地址输入项 --> | ||||||
|  |     <div class="form-group"> | ||||||
|  |         <label for="baseUrl">基础地址(服务器根地址)</label> | ||||||
|  |         <input type="text" id="baseUrl" class="form-control" | ||||||
|  |                placeholder="例: http://192.168.110.25:8848" | ||||||
|  |                value="http://192.168.110.25:8848"> | ||||||
|  |     </div> | ||||||
|  |  | ||||||
|  |     <!-- 资源地址输入 --> | ||||||
|  |     <div class="form-group"> | ||||||
|  |         <label for="resourceUrl">资源相对路径</label> | ||||||
|  |         <input type="text" id="resourceUrl" class="form-control" | ||||||
|  |                placeholder="例: 1.倾斜模型: data/clt/.../tileset.json 2.高程: terrain_pak 3.瓦片: tiles/{z}/{x}/{y}.png" | ||||||
|  |                value="/data/pak/8870a7a573dc621d7347457a5497df3b/{z}/{x}/{y}.png"> | ||||||
|  |     </div> | ||||||
|  |  | ||||||
|  |     <!-- 经纬度定位输入 --> | ||||||
|  |     <div class="coord-group"> | ||||||
|  |         <div class="form-group"> | ||||||
|  |             <label for="longitude">经度</label> | ||||||
|  |             <input type="text" id="longitude" class="form-control" | ||||||
|  |                    placeholder="范围: -180 ~ 180" value="106.253200504443"> | ||||||
|  |         </div> | ||||||
|  |         <div class="form-group"> | ||||||
|  |             <label for="latitude">纬度</label> | ||||||
|  |             <input type="text" id="latitude" class="form-control" | ||||||
|  |                    placeholder="范围: -90 ~ 90" value="29.8500521523625"> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  |  | ||||||
|  |     <!-- 操作按钮组 --> | ||||||
|  |     <div class="btn-group"> | ||||||
|  |         <button id="loadBtn" class="btn btn-primary">加载资源</button> | ||||||
|  |         <button id="locateBtn" class="btn btn-locate">定位到经纬度</button> | ||||||
|  |         <button id="clearBtn" class="btn btn-clear">清除所有资源</button> | ||||||
|  |     </div> | ||||||
|  |  | ||||||
|  |     <!-- 状态提示 --> | ||||||
|  |     <div id="status" class="status"></div> | ||||||
|  | </div> | ||||||
|  |  | ||||||
|  | <script> | ||||||
|  |     // 1. 初始化Cesium核心配置 | ||||||
|  |     Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI5MGU0NGMwYS00ZDBkLTQzMDItYjc5Zi0zYTM1NDcwZGVjMmEiLCJpZCI6MTU1MTk5LCJpYXQiOjE2ODk4MTgzNTF9.V_eZ5KlAI9qmxsDivT6pMC3Pq6qLk0mXpBoe5C0Mm4g'; | ||||||
|  |  | ||||||
|  |     // 2. 创建Cesium Viewer(基础配置) | ||||||
|  |     const viewer = new Cesium.Viewer('cesiumContainer', { | ||||||
|  |         animation: false, | ||||||
|  |         timeline: false, | ||||||
|  |         vrButton: false, | ||||||
|  |         infoBox: false, | ||||||
|  |         selectionIndicator: false, | ||||||
|  |         homeButton: true, | ||||||
|  |         sceneModePicker: true, | ||||||
|  |         navigationHelpButton: true, | ||||||
|  |         fullscreenButton: true, | ||||||
|  |         imageryProvider: false | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     // 隐藏Cesium版权信息 | ||||||
|  |     viewer._cesiumWidget._creditContainer.style.display = 'none'; | ||||||
|  |  | ||||||
|  |     // 3. 全局变量 | ||||||
|  |     let loadedResource = { type: null, instance: null }; | ||||||
|  |     const statusEl = document.getElementById('status'); | ||||||
|  |     const latlngTooltip = document.getElementById('latlngTooltip'); | ||||||
|  |  | ||||||
|  |     // 4. 工具函数: 拼接资源URL | ||||||
|  |     function getFullResourceUrl() { | ||||||
|  |         const baseUrl = document.getElementById('baseUrl').value.trim(); | ||||||
|  |         const resourceUrl = document.getElementById('resourceUrl').value.trim(); | ||||||
|  |         if (!resourceUrl) return ''; | ||||||
|  |         if (!baseUrl) return resourceUrl; | ||||||
|  |         const baseUrlWithSlash = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`; | ||||||
|  |         const cleanResourceUrl = resourceUrl.startsWith('/') ? resourceUrl.slice(1) : resourceUrl; | ||||||
|  |         return `${baseUrlWithSlash}${cleanResourceUrl}`; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 5. 工具函数: 识别资源类型 | ||||||
|  |     function getResourceTypeByUrl(fullUrl) { | ||||||
|  |         const lowerUrl = fullUrl.toLowerCase(); | ||||||
|  |         if (lowerUrl.includes('.png') || lowerUrl.includes('.jpg')) return 'imagery'; | ||||||
|  |         if (lowerUrl.includes('tileset.json')) return 'tileset'; | ||||||
|  |         if (lowerUrl.includes('pak')) return 'terrain'; | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 6. 状态提示函数 | ||||||
|  |     function showStatus(message, isSuccess = true) { | ||||||
|  |         statusEl.textContent = message; | ||||||
|  |         statusEl.className = 'status'; | ||||||
|  |         statusEl.classList.add(isSuccess ? 'status-success' : 'status-error'); | ||||||
|  |         if (isSuccess) { | ||||||
|  |             setTimeout(() => { | ||||||
|  |                 statusEl.classList.remove('status-success'); | ||||||
|  |                 statusEl.style.display = 'none'; | ||||||
|  |             }, 5000); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 7. 清除资源函数 | ||||||
|  |     function clearLoadedResource() { | ||||||
|  |         if (!loadedResource.instance) return; | ||||||
|  |         switch (loadedResource.type) { | ||||||
|  |             case 'tileset': | ||||||
|  |                 viewer.scene.primitives.remove(loadedResource.instance); | ||||||
|  |                 showStatus('倾斜模型已清除'); | ||||||
|  |                 break; | ||||||
|  |             case 'imagery': | ||||||
|  |                 viewer.imageryLayers.remove(loadedResource.instance); | ||||||
|  |                 showStatus('二维瓦片已清除'); | ||||||
|  |                 break; | ||||||
|  |             case 'terrain': | ||||||
|  |                 viewer.terrainProvider = new Cesium.EllipsoidTerrainProvider(); | ||||||
|  |                 viewer.scene.globe.depthTestAgainstTerrain = false; | ||||||
|  |                 showStatus('高程模型已清除'); | ||||||
|  |                 break; | ||||||
|  |         } | ||||||
|  |         loadedResource = { type: null, instance: null }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 8. 资源加载函数 | ||||||
|  |     async function loadResource() { | ||||||
|  |         const fullUrl = getFullResourceUrl(); | ||||||
|  |         if (!fullUrl) { | ||||||
|  |             showStatus('请输入有效的资源相对路径', false); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const resourceType = getResourceTypeByUrl(fullUrl); | ||||||
|  |         if (!resourceType) { | ||||||
|  |             showStatus('无法识别资源类型、请检查路径是否包含tileset.json/pak/.png/.jpg', false); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         clearLoadedResource(); | ||||||
|  |  | ||||||
|  |         try { | ||||||
|  |             let instance; | ||||||
|  |             switch (resourceType) { | ||||||
|  |                 case 'tileset': | ||||||
|  |                     showStatus('正在加载倾斜模型...'); | ||||||
|  |                     instance = await Cesium.Cesium3DTileset.fromUrl(fullUrl); | ||||||
|  |                     viewer.scene.primitives.add(instance); | ||||||
|  |                     viewer.zoomTo(instance); | ||||||
|  |                     showStatus('倾斜模型加载成功'); | ||||||
|  |                     break; | ||||||
|  |                 case 'imagery': | ||||||
|  |                     showStatus('正在加载二维瓦片...'); | ||||||
|  |                     const fileExtension = fullUrl.toLowerCase().includes('.png') ? 'png' : 'jpg'; | ||||||
|  |                     const provider = new Cesium.UrlTemplateImageryProvider({ | ||||||
|  |                         url: fullUrl, | ||||||
|  |                         fileExtension: fileExtension, | ||||||
|  |                         minimumLevel: 0, | ||||||
|  |                         maximumLevel: 18, | ||||||
|  |                         credit: '自定义二维瓦片' | ||||||
|  |                     }); | ||||||
|  |                     instance = viewer.imageryLayers.addImageryProvider(provider); | ||||||
|  |                     showStatus('二维瓦片加载成功'); | ||||||
|  |                     break; | ||||||
|  |                 case 'terrain': | ||||||
|  |                     showStatus('正在加载高程模型...'); | ||||||
|  |                     instance = await Cesium.CesiumTerrainProvider.fromUrl(fullUrl); | ||||||
|  |                     viewer.terrainProvider = instance; | ||||||
|  |                     viewer.scene.globe.depthTestAgainstTerrain = true; | ||||||
|  |                     showStatus('高程模型加载成功'); | ||||||
|  |                     break; | ||||||
|  |             } | ||||||
|  |             loadedResource = { type: resourceType, instance: instance }; | ||||||
|  |         } catch (error) { | ||||||
|  |             console.error('资源加载失败: ', error); | ||||||
|  |             const errorMsg = error.message.includes('404') ? '资源地址不存在(404)' : | ||||||
|  |                 error.message.includes('CORS') ? '跨域访问被拒绝(CORS)' : | ||||||
|  |                     '服务器连接失败或资源格式错误'; | ||||||
|  |             showStatus(`资源加载失败: ${errorMsg}`, false); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 9. 经纬度定位函数 | ||||||
|  |     function flyToCoordinate() { | ||||||
|  |         const lng = parseFloat(document.getElementById('longitude').value.trim()); | ||||||
|  |         const lat = parseFloat(document.getElementById('latitude').value.trim()); | ||||||
|  |         if (isNaN(lng) || isNaN(lat) || lng < -180 || lng > 180 || lat < -90 || lat > 90) { | ||||||
|  |             showStatus('请输入有效的经纬度(经度: -180~180、纬度: -90~90)', false); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         viewer.camera.flyTo({ | ||||||
|  |             destination: Cesium.Cartesian3.fromDegrees(lng, lat, 10000), | ||||||
|  |             duration: 2, | ||||||
|  |             orientation: { | ||||||
|  |                 heading: Cesium.Math.toRadians(0), | ||||||
|  |                 pitch: Cesium.Math.toRadians(-30), | ||||||
|  |                 roll: 0 | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |         showStatus(`已定位到经纬度: (${lng.toFixed(4)}, ${lat.toFixed(4)})`); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 10. 修复: 鼠标悬浮显示经纬度(支持地球+地形表面、避免NaN) | ||||||
|  |     function initLatlngTooltip() { | ||||||
|  |         const canvas = viewer.scene.canvas; | ||||||
|  |  | ||||||
|  |         // 鼠标移动事件 | ||||||
|  |         canvas.addEventListener('mousemove', (e) => { | ||||||
|  |             // 1. 先尝试获取地形表面坐标(优先、因为加载高程后更准确) | ||||||
|  |             const windowPosition = new Cesium.Cartesian2(e.clientX, e.clientY); | ||||||
|  |             let cartographic = null; | ||||||
|  |  | ||||||
|  |             // 方法1: 从地形表面获取坐标(适用于加载了高程模型的场景) | ||||||
|  |             const ray = viewer.camera.getPickRay(windowPosition); | ||||||
|  |             if (ray) { | ||||||
|  |                 const hitResult = viewer.scene.pickFromRay(ray); | ||||||
|  |                 if (hitResult && hitResult.position) { | ||||||
|  |                     cartographic = Cesium.Cartographic.fromCartesian(hitResult.position); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // 方法2: 如果地形获取失败、从椭球面获取(适用于无高程的场景) | ||||||
|  |             if (!cartographic) { | ||||||
|  |                 cartographic = viewer.scene.camera.pickEllipsoid(windowPosition); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // 3. 计算并显示经纬度(增加异常处理) | ||||||
|  |             if (cartographic && !isNaN(cartographic.longitude) && !isNaN(cartographic.latitude)) { | ||||||
|  |                 const longitude = Cesium.Math.toDegrees(cartographic.longitude).toFixed(6); | ||||||
|  |                 const latitude = Cesium.Math.toDegrees(cartographic.latitude).toFixed(6); | ||||||
|  |                 // 显示经纬度(避免NaN) | ||||||
|  |                 latlngTooltip.textContent = `经度: ${longitude} | 纬度: ${latitude}`; | ||||||
|  |                 // 调整提示框位置(防止超出屏幕) | ||||||
|  |                 const tooltipWidth = latlngTooltip.offsetWidth || 150; // 预估宽度 | ||||||
|  |                 const left = e.clientX + 10 > window.innerWidth - tooltipWidth | ||||||
|  |                     ? e.clientX - tooltipWidth - 10 | ||||||
|  |                     : e.clientX + 10; | ||||||
|  |                 const top = e.clientY + 10 > window.innerHeight - 30 | ||||||
|  |                     ? e.clientY - 30 | ||||||
|  |                     : e.clientY + 10; | ||||||
|  |                 latlngTooltip.style.left = `${left}px`; | ||||||
|  |                 latlngTooltip.style.top = `${top}px`; | ||||||
|  |                 latlngTooltip.style.opacity = '1'; | ||||||
|  |             } else { | ||||||
|  |                 // 非地球表面时隐藏提示框 | ||||||
|  |                 latlngTooltip.style.opacity = '0'; | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // 鼠标离开画布时隐藏 | ||||||
|  |         canvas.addEventListener('mouseleave', () => { | ||||||
|  |             latlngTooltip.style.opacity = '0'; | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 11. 绑定按钮事件 | ||||||
|  |     document.getElementById('loadBtn').addEventListener('click', loadResource); | ||||||
|  |     document.getElementById('locateBtn').addEventListener('click', flyToCoordinate); | ||||||
|  |     document.getElementById('clearBtn').addEventListener('click', () => { | ||||||
|  |         clearLoadedResource(); | ||||||
|  |         if (viewer.imageryLayers.length > 0) { | ||||||
|  |             viewer.imageryLayers.removeAll(); | ||||||
|  |             showStatus('所有影像图层已清除'); | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     // 12. 初始化鼠标经纬度提示 | ||||||
|  |     initLatlngTooltip(); | ||||||
|  | </script> | ||||||
|  | </body> | ||||||
|  | </html> | ||||||
							
								
								
									
										177
									
								
								pom.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								pom.xml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,177 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <project xmlns="http://maven.apache.org/POM/4.0.0" | ||||||
|  |          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||||
|  |          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||||||
|  |     <modelVersion>4.0.0</modelVersion> | ||||||
|  |  | ||||||
|  |     <!-- 项目GAV --> | ||||||
|  |     <groupId>com.yj.earth</groupId> | ||||||
|  |     <artifactId>yjearth</artifactId> | ||||||
|  |     <version>1.0.0</version> | ||||||
|  |  | ||||||
|  |     <!-- SpringBoot3 --> | ||||||
|  |     <parent> | ||||||
|  |         <groupId>org.springframework.boot</groupId> | ||||||
|  |         <artifactId>spring-boot-starter-parent</artifactId> | ||||||
|  |         <version>3.2.0</version> | ||||||
|  |     </parent> | ||||||
|  |  | ||||||
|  |     <properties> | ||||||
|  |         <maven.compiler.source>17</maven.compiler.source> | ||||||
|  |         <maven.compiler.target>17</maven.compiler.target> | ||||||
|  |         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||||||
|  |     </properties> | ||||||
|  |  | ||||||
|  |     <dependencies> | ||||||
|  |         <!-- Web --> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>org.springframework.boot</groupId> | ||||||
|  |             <artifactId>spring-boot-starter-web</artifactId> | ||||||
|  |         </dependency> | ||||||
|  |  | ||||||
|  |         <!-- MybatisPlus --> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>com.baomidou</groupId> | ||||||
|  |             <artifactId>mybatis-plus-spring-boot3-starter</artifactId> | ||||||
|  |             <version>3.5.9</version> | ||||||
|  |         </dependency> | ||||||
|  |  | ||||||
|  |         <!-- MybatisPlus 代码生成器 --> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>com.baomidou</groupId> | ||||||
|  |             <artifactId>mybatis-plus-generator</artifactId> | ||||||
|  |             <version>3.5.9</version> | ||||||
|  |         </dependency> | ||||||
|  |  | ||||||
|  |         <!-- MybatisPlus 附加插件 --> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>com.baomidou</groupId> | ||||||
|  |             <artifactId>mybatis-plus-jsqlparser</artifactId> | ||||||
|  |             <version>3.5.9</version> | ||||||
|  |         </dependency> | ||||||
|  |  | ||||||
|  |         <!-- 模板引擎依赖 --> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>org.apache.velocity</groupId> | ||||||
|  |             <artifactId>velocity-engine-core</artifactId> | ||||||
|  |             <version>2.3</version> | ||||||
|  |         </dependency> | ||||||
|  |  | ||||||
|  |         <!-- Mysql --> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>mysql</groupId> | ||||||
|  |             <artifactId>mysql-connector-java</artifactId> | ||||||
|  |             <version>8.0.33</version> | ||||||
|  |         </dependency> | ||||||
|  |  | ||||||
|  |         <!-- SQlite --> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>org.xerial</groupId> | ||||||
|  |             <artifactId>sqlite-jdbc</artifactId> | ||||||
|  |         </dependency> | ||||||
|  |  | ||||||
|  |         <!-- 德鲁伊数据库连接池 --> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>com.alibaba</groupId> | ||||||
|  |             <artifactId>druid-spring-boot-starter</artifactId> | ||||||
|  |             <version>1.2.8</version> | ||||||
|  |         </dependency> | ||||||
|  |  | ||||||
|  |         <!-- Lombok --> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>org.projectlombok</groupId> | ||||||
|  |             <artifactId>lombok</artifactId> | ||||||
|  |         </dependency> | ||||||
|  |  | ||||||
|  |         <!-- Hutool工具类 --> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>cn.hutool</groupId> | ||||||
|  |             <artifactId>hutool-all</artifactId> | ||||||
|  |             <version>5.8.20</version> | ||||||
|  |         </dependency> | ||||||
|  |  | ||||||
|  |         <!-- Swagger3 --> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>com.github.xiaoymin</groupId> | ||||||
|  |             <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId> | ||||||
|  |             <version>4.5.0</version> | ||||||
|  |         </dependency> | ||||||
|  |  | ||||||
|  |         <!-- WebSocket --> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>org.springframework.boot</groupId> | ||||||
|  |             <artifactId>spring-boot-starter-websocket</artifactId> | ||||||
|  |         </dependency> | ||||||
|  |  | ||||||
|  |         <!-- 获取操作系统信息 --> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>com.github.oshi</groupId> | ||||||
|  |             <artifactId>oshi-core</artifactId> | ||||||
|  |             <version>6.4.0</version> | ||||||
|  |         </dependency> | ||||||
|  |  | ||||||
|  |         <!-- OkHttp --> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>com.squareup.okhttp3</groupId> | ||||||
|  |             <artifactId>okhttp</artifactId> | ||||||
|  |         </dependency> | ||||||
|  |  | ||||||
|  |         <!-- SaToken --> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>cn.dev33</groupId> | ||||||
|  |             <artifactId>sa-token-spring-boot3-starter</artifactId> | ||||||
|  |             <version>1.44.0</version> | ||||||
|  |         </dependency> | ||||||
|  |  | ||||||
|  |         <!-- GraphHopper --> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>com.graphhopper</groupId> | ||||||
|  |             <artifactId>graphhopper-core</artifactId> | ||||||
|  |             <version>7.0</version> | ||||||
|  |         </dependency> | ||||||
|  |  | ||||||
|  |         <!-- 用于地理多边形空间判断 --> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>org.locationtech.jts</groupId> | ||||||
|  |             <artifactId>jts-core</artifactId> | ||||||
|  |             <version>1.19.0</version> | ||||||
|  |         </dependency> | ||||||
|  |  | ||||||
|  |         <!-- Spring AOP --> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>org.springframework</groupId> | ||||||
|  |             <artifactId>spring-aop</artifactId> | ||||||
|  |         </dependency> | ||||||
|  |  | ||||||
|  |         <!-- AspectJ 织入器 --> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>org.aspectj</groupId> | ||||||
|  |             <artifactId>aspectjweaver</artifactId> | ||||||
|  |         </dependency> | ||||||
|  |  | ||||||
|  |         <!-- HttpClient5 --> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>org.apache.httpcomponents</groupId> | ||||||
|  |             <artifactId>httpclient</artifactId> | ||||||
|  |             <version>4.5.13</version> | ||||||
|  |             <exclusions> | ||||||
|  |                 <exclusion> | ||||||
|  |                     <groupId>commons-logging</groupId> | ||||||
|  |                     <artifactId>commons-logging</artifactId> | ||||||
|  |                 </exclusion> | ||||||
|  |             </exclusions> | ||||||
|  |         </dependency> | ||||||
|  |     </dependencies> | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     <build> | ||||||
|  |         <finalName>yjearth</finalName> | ||||||
|  |         <plugins> | ||||||
|  |             <plugin> | ||||||
|  |                 <groupId>org.springframework.boot</groupId> | ||||||
|  |                 <artifactId>spring-boot-maven-plugin</artifactId> | ||||||
|  |             </plugin> | ||||||
|  |         </plugins> | ||||||
|  |  | ||||||
|  |     </build> | ||||||
|  | </project> | ||||||
							
								
								
									
										54
									
								
								src/main/java/com/yj/earth/ServerApp.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/main/java/com/yj/earth/ServerApp.java
									
									
									
									
									
										Normal file
									
								
							| @ -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"); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										22
									
								
								src/main/java/com/yj/earth/annotation/EncryptResponse.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/main/java/com/yj/earth/annotation/EncryptResponse.java
									
									
									
									
									
										Normal file
									
								
							| @ -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"; | ||||||
|  | } | ||||||
							
								
								
									
										34
									
								
								src/main/java/com/yj/earth/annotation/ExcludeField.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/main/java/com/yj/earth/annotation/ExcludeField.java
									
									
									
									
									
										Normal file
									
								
							| @ -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); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								src/main/java/com/yj/earth/annotation/RoleAccess.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/main/java/com/yj/earth/annotation/RoleAccess.java
									
									
									
									
									
										Normal file
									
								
							| @ -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(); | ||||||
|  | } | ||||||
							
								
								
									
										9
									
								
								src/main/java/com/yj/earth/annotation/SourceType.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/main/java/com/yj/earth/annotation/SourceType.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | |||||||
|  | package com.yj.earth.annotation; | ||||||
|  |  | ||||||
|  | import java.lang.annotation.*; | ||||||
|  |  | ||||||
|  | @Target(ElementType.TYPE) | ||||||
|  | @Retention(RetentionPolicy.RUNTIME) | ||||||
|  | public @interface SourceType { | ||||||
|  |     String value(); | ||||||
|  | } | ||||||
							
								
								
									
										64
									
								
								src/main/java/com/yj/earth/aspect/EncryptResponseAspect.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/main/java/com/yj/earth/aspect/EncryptResponseAspect.java
									
									
									
									
									
										Normal file
									
								
							| @ -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); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										78
									
								
								src/main/java/com/yj/earth/aspect/RoleAccessAspect.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/main/java/com/yj/earth/aspect/RoleAccessAspect.java
									
									
									
									
									
										Normal file
									
								
							| @ -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(); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										74
									
								
								src/main/java/com/yj/earth/auth/AuthGenerator.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/main/java/com/yj/earth/auth/AuthGenerator.java
									
									
									
									
									
										Normal file
									
								
							| @ -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); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										32
									
								
								src/main/java/com/yj/earth/auth/AuthInfo.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/main/java/com/yj/earth/auth/AuthInfo.java
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||||
|  | } | ||||||
							
								
								
									
										115
									
								
								src/main/java/com/yj/earth/auth/AuthValidator.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								src/main/java/com/yj/earth/auth/AuthValidator.java
									
									
									
									
									
										Normal file
									
								
							| @ -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); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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<FileInfoVo> 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<FileInfo> 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<FileInfo> page = new Page<>(pageNum, pageSize); | ||||||
|  |         // 构建查询条件 | ||||||
|  |         LambdaQueryWrapper<FileInfo> queryWrapper = new LambdaQueryWrapper<>(); | ||||||
|  |         if (fileName != null && !fileName.isEmpty()) { | ||||||
|  |             queryWrapper.like(FileInfo::getFileName, fileName); | ||||||
|  |         } | ||||||
|  |         // 按创建时间倒序排列、最新上传的文件在前 | ||||||
|  |         queryWrapper.orderByDesc(FileInfo::getCreatedAt); | ||||||
|  |  | ||||||
|  |         // 执行分页查询 | ||||||
|  |         IPage<FileInfo> fileInfoPage = fileInfoService.page(page, queryWrapper); | ||||||
|  |  | ||||||
|  |         // 转换为VO对象并设置URL | ||||||
|  |         List<FileInfoVo> 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<FileInfoVo> 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); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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<FileInfo> 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<GHPoint> 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<Point> 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<Profile> 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(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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<Role> rolePage = roleService.page(new Page<>(pageNum, pageSize)); | ||||||
|  |         return ApiResponse.success(rolePage); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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<RoleSource>().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); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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<String, Object> 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<String> supportedTypes = sourceParamsValidator.getSupportedSourceTypes(); | ||||||
|  |         return ApiResponse.success(supportedTypes); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     @Operation(summary = "获取资源列表") | ||||||
|  |     @GetMapping("/list") | ||||||
|  |     public ApiResponse list() { | ||||||
|  |         return ApiResponse.success(sourceService.getSourceListByUserId(StpUtil.getLoginIdAsString())); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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<User>().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<Role>().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<User> 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<User>().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())); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										50
									
								
								src/main/java/com/yj/earth/business/domain/FileInfo.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/main/java/com/yj/earth/business/domain/FileInfo.java
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||||
|  | } | ||||||
							
								
								
									
										41
									
								
								src/main/java/com/yj/earth/business/domain/Role.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/main/java/com/yj/earth/business/domain/Role.java
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||||
|  | } | ||||||
							
								
								
									
										39
									
								
								src/main/java/com/yj/earth/business/domain/RoleSource.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/main/java/com/yj/earth/business/domain/RoleSource.java
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||||
|  | } | ||||||
							
								
								
									
										56
									
								
								src/main/java/com/yj/earth/business/domain/Source.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/main/java/com/yj/earth/business/domain/Source.java
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||||
|  | } | ||||||
							
								
								
									
										51
									
								
								src/main/java/com/yj/earth/business/domain/User.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/main/java/com/yj/earth/business/domain/User.java
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||||
|  | } | ||||||
| @ -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; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * <p> | ||||||
|  |  *  Mapper 接口 | ||||||
|  |  * </p> | ||||||
|  |  * | ||||||
|  |  * @author 周志雄 | ||||||
|  |  * @since 2025-08-29 | ||||||
|  |  */ | ||||||
|  | @Mapper | ||||||
|  | public interface FileInfoMapper extends BaseMapper<FileInfo> { | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								src/main/java/com/yj/earth/business/mapper/RoleMapper.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/main/java/com/yj/earth/business/mapper/RoleMapper.java
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * <p> | ||||||
|  |  *  Mapper 接口 | ||||||
|  |  * </p> | ||||||
|  |  * | ||||||
|  |  * @author 周志雄 | ||||||
|  |  * @since 2025-08-28 | ||||||
|  |  */ | ||||||
|  | @Mapper | ||||||
|  | public interface RoleMapper extends BaseMapper<Role> { | ||||||
|  |  | ||||||
|  | } | ||||||
| @ -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; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * <p> | ||||||
|  |  *  Mapper 接口 | ||||||
|  |  * </p> | ||||||
|  |  * | ||||||
|  |  * @author 周志雄 | ||||||
|  |  * @since 2025-08-27 | ||||||
|  |  */ | ||||||
|  | @Mapper | ||||||
|  | public interface RoleSourceMapper extends BaseMapper<RoleSource> { | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								src/main/java/com/yj/earth/business/mapper/SourceMapper.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/main/java/com/yj/earth/business/mapper/SourceMapper.java
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * <p> | ||||||
|  |  *  Mapper 接口 | ||||||
|  |  * </p> | ||||||
|  |  * | ||||||
|  |  * @author 周志雄 | ||||||
|  |  * @since 2025-08-26 | ||||||
|  |  */ | ||||||
|  | @Mapper | ||||||
|  | public interface SourceMapper extends BaseMapper<Source> { | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								src/main/java/com/yj/earth/business/mapper/UserMapper.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/main/java/com/yj/earth/business/mapper/UserMapper.java
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * <p> | ||||||
|  |  *  Mapper 接口 | ||||||
|  |  * </p> | ||||||
|  |  * | ||||||
|  |  * @author 周志雄 | ||||||
|  |  * @since 2025-08-28 | ||||||
|  |  */ | ||||||
|  | @Mapper | ||||||
|  | public interface UserMapper extends BaseMapper<User> { | ||||||
|  |  | ||||||
|  | } | ||||||
| @ -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<FileInfo> { | ||||||
|  |     // 根据文件ID获取文件绝对路径 | ||||||
|  |     String getFileAbsolutePath(String id); | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								src/main/java/com/yj/earth/business/service/RoleService.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/main/java/com/yj/earth/business/service/RoleService.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | |||||||
|  | package com.yj.earth.business.service; | ||||||
|  |  | ||||||
|  | import com.yj.earth.business.domain.Role; | ||||||
|  | import com.baomidou.mybatisplus.extension.service.IService; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * <p> | ||||||
|  |  *  服务类 | ||||||
|  |  * </p> | ||||||
|  |  * | ||||||
|  |  * @author 周志雄 | ||||||
|  |  * @since 2025-08-28 | ||||||
|  |  */ | ||||||
|  | public interface RoleService extends IService<Role> { | ||||||
|  |  | ||||||
|  | } | ||||||
| @ -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<RoleSource> { | ||||||
|  |     void addRoleSource(String roleId, String sourceId); | ||||||
|  | } | ||||||
| @ -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<Source> { | ||||||
|  |     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<Source> getSourceListByUserId(String userId); | ||||||
|  |  | ||||||
|  |     String checkIsPass(String parentId, String sourceName); | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								src/main/java/com/yj/earth/business/service/UserService.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/main/java/com/yj/earth/business/service/UserService.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | |||||||
|  | package com.yj.earth.business.service; | ||||||
|  |  | ||||||
|  | import com.yj.earth.business.domain.User; | ||||||
|  | import com.baomidou.mybatisplus.extension.service.IService; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * <p> | ||||||
|  |  *  服务类 | ||||||
|  |  * </p> | ||||||
|  |  * | ||||||
|  |  * @author 周志雄 | ||||||
|  |  * @since 2025-08-28 | ||||||
|  |  */ | ||||||
|  | public interface UserService extends IService<User> { | ||||||
|  |  | ||||||
|  | } | ||||||
| @ -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<FileInfoMapper, FileInfo> 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(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * <p> | ||||||
|  |  *  服务实现类 | ||||||
|  |  * </p> | ||||||
|  |  * | ||||||
|  |  * @author 周志雄 | ||||||
|  |  * @since 2025-08-28 | ||||||
|  |  */ | ||||||
|  | @Service | ||||||
|  | public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService { | ||||||
|  |  | ||||||
|  | } | ||||||
| @ -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<RoleSourceMapper, RoleSource> implements RoleSourceService { | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void addRoleSource(String roleId, String sourceId) { | ||||||
|  |         RoleSource roleSource = new RoleSource(); | ||||||
|  |         roleSource.setRoleId(roleId); | ||||||
|  |         roleSource.setSourceId(sourceId); | ||||||
|  |         save(roleSource); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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<SourceMapper, Source> 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<String, Function<String, String>> 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<String, Object> 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<Source> queryWrapper = new LambdaQueryWrapper<>(); | ||||||
|  |             queryWrapper.eq(Source::getId, parentId); | ||||||
|  |             List<Source> list = sourceService.list(queryWrapper); | ||||||
|  |             if (sourceService.count(queryWrapper) == 0) { | ||||||
|  |                 return "父级不存在"; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | //        // 验证该目录下是否已经存在此资源名一样的 | ||||||
|  | //        LambdaQueryWrapper<Source> 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<String, String> 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<Source> getSourceListByUserId(String userId) { | ||||||
|  |         // 查询该用户信息 | ||||||
|  |         LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>(); | ||||||
|  |         queryWrapper.eq(User::getId, userId); | ||||||
|  |         User user = userService.getOne(queryWrapper); | ||||||
|  |         // 查询角色信息 | ||||||
|  |         String roleId = user.getRoleId(); | ||||||
|  |         LambdaQueryWrapper<Role> roleQueryWrapper = new LambdaQueryWrapper<>(); | ||||||
|  |         roleQueryWrapper.eq(Role::getId, roleId); | ||||||
|  |         // 如果这个角色是管理员则直接返回所有资源 | ||||||
|  |         if (roleService.getOne(roleQueryWrapper).getIsSuper() == 1) { | ||||||
|  |             return sourceService.list(); | ||||||
|  |         } | ||||||
|  |         // 查询属于该角色的资源列表 | ||||||
|  |         LambdaQueryWrapper<RoleSource> roleSourceQueryWrapper = new LambdaQueryWrapper<>(); | ||||||
|  |         roleSourceQueryWrapper.eq(RoleSource::getRoleId, roleId); | ||||||
|  |         List<RoleSource> roleSourceList = roleSourceService.list(roleSourceQueryWrapper); | ||||||
|  |         // 从结果提取出资源ID列表 | ||||||
|  |         List<String> sourceIdList = roleSourceList.stream().map(RoleSource::getSourceId).toList(); | ||||||
|  |         return sourceService.listByIds(sourceIdList); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * <p> | ||||||
|  |  *  服务实现类 | ||||||
|  |  * </p> | ||||||
|  |  * | ||||||
|  |  * @author 周志雄 | ||||||
|  |  * @since 2025-08-28 | ||||||
|  |  */ | ||||||
|  | @Service | ||||||
|  | public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										26
									
								
								src/main/java/com/yj/earth/common/config/CorsConfig.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/main/java/com/yj/earth/common/config/CorsConfig.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | |||||||
|  | package com.yj.earth.common.config; | ||||||
|  |  | ||||||
|  | import org.springframework.context.annotation.Bean; | ||||||
|  | import org.springframework.context.annotation.Configuration; | ||||||
|  | import org.springframework.web.cors.CorsConfiguration; | ||||||
|  | import org.springframework.web.cors.UrlBasedCorsConfigurationSource; | ||||||
|  | import org.springframework.web.filter.CorsFilter; | ||||||
|  |  | ||||||
|  | import java.util.Collections; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @Configuration | ||||||
|  | public class CorsConfig { | ||||||
|  |  | ||||||
|  |     @Bean | ||||||
|  |     public CorsFilter corsFilter() { | ||||||
|  |         CorsConfiguration config = new CorsConfiguration(); | ||||||
|  |         config.addAllowedHeader("*"); | ||||||
|  |         config.addAllowedMethod("*"); | ||||||
|  |         config.setAllowedOriginPatterns(Collections.singletonList("*")); | ||||||
|  |         config.setAllowCredentials(true); | ||||||
|  |         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); | ||||||
|  |         source.registerCorsConfiguration("/**", config); | ||||||
|  |         return new CorsFilter(source); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,25 @@ | |||||||
|  | package com.yj.earth.common.config; | ||||||
|  |  | ||||||
|  | import com.yj.earth.common.exception.UnAuthException; | ||||||
|  | import com.yj.earth.common.util.ApiResponse; | ||||||
|  | import lombok.extern.slf4j.Slf4j; | ||||||
|  | import org.springframework.web.bind.annotation.ExceptionHandler; | ||||||
|  | import org.springframework.web.bind.annotation.RestControllerAdvice; | ||||||
|  |  | ||||||
|  | @Slf4j | ||||||
|  | @RestControllerAdvice | ||||||
|  | public class GlobalExceptionHandler { | ||||||
|  |     @ExceptionHandler(Exception.class) | ||||||
|  |     public ApiResponse handleException(Exception e) { | ||||||
|  |         if (!e.getMessage().contains("No static resource")) { | ||||||
|  |             log.error("全局异常处理:{}", e.getMessage()); | ||||||
|  |         } | ||||||
|  |         return ApiResponse.failure(e.getMessage()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @ExceptionHandler(UnAuthException.class) | ||||||
|  |     public ApiResponse handleUnAuthException(UnAuthException e) { | ||||||
|  |         return ApiResponse.failureWithNoAuth(e.getMessage()); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| @ -0,0 +1,15 @@ | |||||||
|  | package com.yj.earth.common.config; | ||||||
|  |  | ||||||
|  | import lombok.Data; | ||||||
|  | import org.springframework.boot.context.properties.ConfigurationProperties; | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  |  | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | @Data | ||||||
|  | @Component | ||||||
|  | @ConfigurationProperties(prefix = "graphhopper") | ||||||
|  | public class GraphHopperProperties { | ||||||
|  |     private String graphLocation; | ||||||
|  |     private List<String> profiles; | ||||||
|  | } | ||||||
							
								
								
									
										42
									
								
								src/main/java/com/yj/earth/common/config/JacksonConfig.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/main/java/com/yj/earth/common/config/JacksonConfig.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | |||||||
|  | package com.yj.earth.common.config; | ||||||
|  |  | ||||||
|  | import com.fasterxml.jackson.databind.ObjectMapper; | ||||||
|  | import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; | ||||||
|  | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; | ||||||
|  | import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; | ||||||
|  | import com.yj.earth.annotation.ExcludeField; | ||||||
|  | import org.springframework.context.annotation.Bean; | ||||||
|  | import org.springframework.context.annotation.Configuration; | ||||||
|  |  | ||||||
|  | import java.time.LocalDateTime; | ||||||
|  | import java.time.format.DateTimeFormatter; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Jackson配置类 | ||||||
|  |  */ | ||||||
|  | @Configuration | ||||||
|  | public class JacksonConfig { | ||||||
|  |  | ||||||
|  |     // 定义日期时间格式 | ||||||
|  |     private static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; | ||||||
|  |  | ||||||
|  |     @Bean | ||||||
|  |     public ObjectMapper objectMapper() { | ||||||
|  |         ObjectMapper objectMapper = new ObjectMapper(); | ||||||
|  |         // 注册 JavaTimeModule 以支持 LocalDateTime 等日期类型 | ||||||
|  |         JavaTimeModule javaTimeModule = new JavaTimeModule(); | ||||||
|  |         // 配置 LocalDateTime 的序列化格式 | ||||||
|  |         LocalDateTimeSerializer localDateTimeSerializer = new LocalDateTimeSerializer( | ||||||
|  |                 DateTimeFormatter.ofPattern(DATE_TIME_FORMAT) | ||||||
|  |         ); | ||||||
|  |         javaTimeModule.addSerializer(LocalDateTime.class, localDateTimeSerializer); | ||||||
|  |         objectMapper.registerModule(javaTimeModule); | ||||||
|  |         // 配置自定义字段过滤器 | ||||||
|  |         SimpleFilterProvider filterProvider = new SimpleFilterProvider(); | ||||||
|  |         filterProvider.addFilter("excludeFieldFilter", new ExcludeField.Filter()); | ||||||
|  |         // 设置默认过滤器、防止未添加@JsonFilter的类报错 | ||||||
|  |         filterProvider.setDefaultFilter(com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter.serializeAll()); | ||||||
|  |         objectMapper.setFilterProvider(filterProvider); | ||||||
|  |         return objectMapper; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										32
									
								
								src/main/java/com/yj/earth/common/config/Knife4jConfig.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/main/java/com/yj/earth/common/config/Knife4jConfig.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | |||||||
|  | package com.yj.earth.common.config; | ||||||
|  |  | ||||||
|  | import io.swagger.v3.oas.models.OpenAPI; | ||||||
|  | import io.swagger.v3.oas.models.info.Contact; | ||||||
|  | import io.swagger.v3.oas.models.info.Info; | ||||||
|  | import io.swagger.v3.oas.models.info.License; | ||||||
|  | import org.springframework.context.annotation.Bean; | ||||||
|  | import org.springframework.context.annotation.Configuration; | ||||||
|  | import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j; | ||||||
|  |  | ||||||
|  | @Configuration | ||||||
|  | @EnableKnife4j | ||||||
|  | public class Knife4jConfig { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 自定义Swagger3文档信息 | ||||||
|  |      */ | ||||||
|  |     @Bean | ||||||
|  |     public OpenAPI customOpenAPI() { | ||||||
|  |         return new OpenAPI() | ||||||
|  |                 // 文档基本信息 | ||||||
|  |                 .info(new Info() | ||||||
|  |                         .title("最新产品API文档") | ||||||
|  |                         .description("远界大数据最新产品API文档【默认账号:admin、密码:admin123】") | ||||||
|  |                         .version("v1.0.0") | ||||||
|  |                         .contact(new Contact() | ||||||
|  |                                 .name("周志雄")) | ||||||
|  |                         .license(new License() | ||||||
|  |                                 .name("Apache 2.0") | ||||||
|  |                                 .url("https://www.apache.org/licenses/LICENSE-2.0.html"))); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,37 @@ | |||||||
|  | package com.yj.earth.common.config; | ||||||
|  |  | ||||||
|  | import com.baomidou.mybatisplus.annotation.DbType; | ||||||
|  | import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; | ||||||
|  | import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; | ||||||
|  | import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; | ||||||
|  | import org.apache.ibatis.reflection.MetaObject; | ||||||
|  | import org.springframework.context.annotation.Bean; | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  |  | ||||||
|  | import java.time.LocalDateTime; | ||||||
|  |  | ||||||
|  | @Component | ||||||
|  | public class MyMetaObjectConfig implements MetaObjectHandler { | ||||||
|  |  | ||||||
|  |     // 插入时自动填充 | ||||||
|  |     @Override | ||||||
|  |     public void insertFill(MetaObject metaObject) { | ||||||
|  |         this.strictInsertFill(metaObject, "createdAt", LocalDateTime.class, LocalDateTime.now()); | ||||||
|  |         this.strictInsertFill(metaObject, "updatedAt", LocalDateTime.class, LocalDateTime.now()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 更新时自动填充 | ||||||
|  |     @Override | ||||||
|  |     public void updateFill(MetaObject metaObject) { | ||||||
|  |         this.strictUpdateFill(metaObject, "updatedAt", LocalDateTime.class, LocalDateTime.now()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 配置分页拦截器 | ||||||
|  |     @Bean | ||||||
|  |     public MybatisPlusInterceptor mybatisPlusInterceptor() { | ||||||
|  |         MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); | ||||||
|  |         // 添加分页拦截器 | ||||||
|  |         interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); | ||||||
|  |         return interceptor; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										41
									
								
								src/main/java/com/yj/earth/common/config/SaTokenConfig.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/main/java/com/yj/earth/common/config/SaTokenConfig.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,41 @@ | |||||||
|  | package com.yj.earth.common.config; | ||||||
|  |  | ||||||
|  | import cn.dev33.satoken.interceptor.SaInterceptor; | ||||||
|  | import cn.dev33.satoken.stp.StpUtil; | ||||||
|  | import com.yj.earth.common.exception.UnAuthException; | ||||||
|  | import org.springframework.context.annotation.Configuration; | ||||||
|  | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; | ||||||
|  | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; | ||||||
|  |  | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | @Configuration | ||||||
|  | public class SaTokenConfig implements WebMvcConfigurer { | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void addInterceptors(InterceptorRegistry registry) { | ||||||
|  |         List<String> excludePathPatterns = new ArrayList<>(); | ||||||
|  |         excludePathPatterns.add("/user/login"); | ||||||
|  |         excludePathPatterns.add("/user/add"); | ||||||
|  |         excludePathPatterns.add("/doc.html"); | ||||||
|  |         excludePathPatterns.add("/webjars/**"); | ||||||
|  |         excludePathPatterns.add("/v3/api-docs/**"); | ||||||
|  |         excludePathPatterns.add("/fileInfo/download/**"); | ||||||
|  |         excludePathPatterns.add("/fileInfo/preview/**"); | ||||||
|  |         excludePathPatterns.add("/data/clt/**"); | ||||||
|  |         excludePathPatterns.add("/data/mbtiles/**"); | ||||||
|  |         excludePathPatterns.add("/data/pak/**"); | ||||||
|  |  | ||||||
|  |         // 注册 Sa-Token 拦截器 | ||||||
|  |         registry.addInterceptor(new SaInterceptor(handle -> { | ||||||
|  |                     // 登录校验 | ||||||
|  |                     try { | ||||||
|  |                         StpUtil.checkLogin(); | ||||||
|  |                     } catch (Exception e) { | ||||||
|  |                         throw new UnAuthException("未携带登录凭证"); | ||||||
|  |                     } | ||||||
|  |                 })).addPathPatterns("/**") | ||||||
|  |                 .excludePathPatterns(excludePathPatterns); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								src/main/java/com/yj/earth/common/config/ServerConfig.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/main/java/com/yj/earth/common/config/ServerConfig.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | |||||||
|  | package com.yj.earth.common.config; | ||||||
|  |  | ||||||
|  | import lombok.Data; | ||||||
|  | import org.springframework.beans.factory.annotation.Value; | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  |  | ||||||
|  | @Data | ||||||
|  | @Component | ||||||
|  | public class ServerConfig { | ||||||
|  |     @Value("${server.port}") | ||||||
|  |     private int port; | ||||||
|  |  | ||||||
|  |     @Value("${server.host}") | ||||||
|  |     private String host; | ||||||
|  |  | ||||||
|  |     @Value("${sdk.port}") | ||||||
|  |     private int sdkPort; | ||||||
|  | } | ||||||
| @ -0,0 +1,29 @@ | |||||||
|  | package com.yj.earth.common.config; | ||||||
|  |  | ||||||
|  | import com.yj.earth.annotation.SourceType; | ||||||
|  | import org.springframework.context.annotation.Bean; | ||||||
|  | import org.springframework.context.annotation.Configuration; | ||||||
|  | import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; | ||||||
|  | import org.springframework.core.type.filter.AnnotationTypeFilter; | ||||||
|  |  | ||||||
|  | import java.util.HashSet; | ||||||
|  | import java.util.Set; | ||||||
|  |  | ||||||
|  | @Configuration | ||||||
|  | public class SourceTypeConfig { | ||||||
|  |  | ||||||
|  |     private static final String PACKAGE = "com.yj.earth.params"; | ||||||
|  |  | ||||||
|  |     @Bean | ||||||
|  |     public Set<Class<?>> sourceParamClasses() throws ClassNotFoundException { | ||||||
|  |         ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false); | ||||||
|  |         scanner.addIncludeFilter(new AnnotationTypeFilter(SourceType.class)); | ||||||
|  |  | ||||||
|  |         Set<Class<?>> classes = new HashSet<>(); | ||||||
|  |         for (var beanDefinition : scanner.findCandidateComponents(PACKAGE)) { | ||||||
|  |             classes.add(Class.forName(beanDefinition.getBeanClassName())); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return classes; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,14 @@ | |||||||
|  | package com.yj.earth.common.constant; | ||||||
|  |  | ||||||
|  | public class GlobalConstant { | ||||||
|  |     // 目录类型 | ||||||
|  |     public static final String DIRECTORY = "directory"; | ||||||
|  |     // 显示 | ||||||
|  |     public static final Integer SHOW = 1; | ||||||
|  |     // 隐藏 | ||||||
|  |     public static final Integer HIDE = 0; | ||||||
|  |     // SDK路径 | ||||||
|  |     public static final String SDKPATH = "sdk/geographysdk.jar"; | ||||||
|  |     // SDK日志路径 | ||||||
|  |     public static final String SDKLOG = "logs/sdk.log"; | ||||||
|  | } | ||||||
							
								
								
									
										170
									
								
								src/main/java/com/yj/earth/common/core/MapRedisTemplate.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								src/main/java/com/yj/earth/common/core/MapRedisTemplate.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,170 @@ | |||||||
|  | package com.yj.earth.common.core; | ||||||
|  |  | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  |  | ||||||
|  | import java.util.*; | ||||||
|  | import java.util.concurrent.TimeUnit; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 封装一个 StringRedisTemplate 的功能 | ||||||
|  |  */ | ||||||
|  | @Component | ||||||
|  | public class MapRedisTemplate { | ||||||
|  |     // 底层存储结构、使用HashMap模拟Redis | ||||||
|  |     private final Map<String, String> storage = new HashMap<>(); | ||||||
|  |     // 用于存储过期时间 | ||||||
|  |     private final Map<String, Long> expirationMap = new HashMap<>(); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 获取操作字符串的接口 | ||||||
|  |      */ | ||||||
|  |     public ValueOperations opsForValue() { | ||||||
|  |         return new ValueOperations(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 删除指定的键 | ||||||
|  |      * @param key 要删除的键 | ||||||
|  |      * @return 是否删除成功 | ||||||
|  |      */ | ||||||
|  |     public Boolean delete(String key) { | ||||||
|  |         checkExpiration(key); | ||||||
|  |         return storage.remove(key) != null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 检查键是否存在 | ||||||
|  |      * @param key 要检查的键 | ||||||
|  |      * @return 键是否存在 | ||||||
|  |      */ | ||||||
|  |     public Boolean hasKey(String key) { | ||||||
|  |         checkExpiration(key); | ||||||
|  |         return storage.containsKey(key); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 设置键的过期时间 | ||||||
|  |      * @param key 键 | ||||||
|  |      * @param timeout 过期时间 | ||||||
|  |      * @param unit 时间单位 | ||||||
|  |      * @return 是否设置成功 | ||||||
|  |      */ | ||||||
|  |     public Boolean expire(String key, long timeout, TimeUnit unit) { | ||||||
|  |         if (!storage.containsKey(key)) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         long expirationTime = System.currentTimeMillis() + unit.toMillis(timeout); | ||||||
|  |         expirationMap.put(key, expirationTime); | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 获取键的剩余过期时间 | ||||||
|  |      * @param key 键 | ||||||
|  |      * @param unit 时间单位 | ||||||
|  |      * @return 剩余过期时间 | ||||||
|  |      */ | ||||||
|  |     public Long getExpire(String key, TimeUnit unit) { | ||||||
|  |         checkExpiration(key); | ||||||
|  |         if (!expirationMap.containsKey(key)) { | ||||||
|  |             return -1L; // 永久有效 | ||||||
|  |         } | ||||||
|  |         long remainingMillis = expirationMap.get(key) - System.currentTimeMillis(); | ||||||
|  |         if (remainingMillis <= 0) { | ||||||
|  |             delete(key); | ||||||
|  |             return 0L; | ||||||
|  |         } | ||||||
|  |         return unit.convert(remainingMillis, TimeUnit.MILLISECONDS); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 检查键是否过期、如果过期则删除 | ||||||
|  |      */ | ||||||
|  |     private void checkExpiration(String key) { | ||||||
|  |         if (expirationMap.containsKey(key)) { | ||||||
|  |             long expirationTime = expirationMap.get(key); | ||||||
|  |             if (System.currentTimeMillis() > expirationTime) { | ||||||
|  |                 storage.remove(key); | ||||||
|  |                 expirationMap.remove(key); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 操作字符串的内部类、模拟ValueOperations | ||||||
|  |      */ | ||||||
|  |     public class ValueOperations { | ||||||
|  |         /** | ||||||
|  |          * 设置键值对 | ||||||
|  |          * @param key 键 | ||||||
|  |          * @param value 值 | ||||||
|  |          */ | ||||||
|  |         public void set(String key, String value) { | ||||||
|  |             storage.put(key, value); | ||||||
|  |             // 设置值时清除过期时间、模拟Redis行为 | ||||||
|  |             expirationMap.remove(key); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * 设置键值对并指定过期时间 | ||||||
|  |          * @param key 键 | ||||||
|  |          * @param value 值 | ||||||
|  |          * @param timeout 过期时间 | ||||||
|  |          * @param unit 时间单位 | ||||||
|  |          */ | ||||||
|  |         public void set(String key, String value, long timeout, TimeUnit unit) { | ||||||
|  |             storage.put(key, value); | ||||||
|  |             long expirationTime = System.currentTimeMillis() + unit.toMillis(timeout); | ||||||
|  |             expirationMap.put(key, expirationTime); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * 获取键对应的值 | ||||||
|  |          * @param key 键 | ||||||
|  |          * @return 对应的值 | ||||||
|  |          */ | ||||||
|  |         public String get(String key) { | ||||||
|  |             checkExpiration(key); | ||||||
|  |             return storage.get(key); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * 如果键不存在则设置值 | ||||||
|  |          * @param key 键 | ||||||
|  |          * @param value 值 | ||||||
|  |          * @return 是否设置成功 | ||||||
|  |          */ | ||||||
|  |         public Boolean setIfAbsent(String key, String value) { | ||||||
|  |             checkExpiration(key); | ||||||
|  |             if (!storage.containsKey(key)) { | ||||||
|  |                 storage.put(key, value); | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * 自增操作 | ||||||
|  |          * @param key 键 | ||||||
|  |          * @return 自增后的值 | ||||||
|  |          */ | ||||||
|  |         public Long increment(String key) { | ||||||
|  |             return increment(key, 1); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * 增加指定的值 | ||||||
|  |          * @param key 键 | ||||||
|  |          * @param delta 要增加的值 | ||||||
|  |          * @return 增加后的值 | ||||||
|  |          */ | ||||||
|  |         public Long increment(String key, long delta) { | ||||||
|  |             checkExpiration(key); | ||||||
|  |             String value = storage.get(key); | ||||||
|  |             long num = value == null ? 0 : Long.parseLong(value); | ||||||
|  |             num += delta; | ||||||
|  |             storage.put(key, String.valueOf(num)); | ||||||
|  |             return num; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,10 @@ | |||||||
|  | package com.yj.earth.common.exception; | ||||||
|  |  | ||||||
|  | public class UnAuthException extends RuntimeException{ | ||||||
|  |     /** | ||||||
|  |      * 带异常信息的构造方法 | ||||||
|  |      */ | ||||||
|  |     public UnAuthException(String message) { | ||||||
|  |         super(message); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,67 @@ | |||||||
|  | package com.yj.earth.common.service; | ||||||
|  |  | ||||||
|  | import cn.hutool.crypto.digest.BCrypt; | ||||||
|  | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | ||||||
|  | import com.yj.earth.business.domain.Role; | ||||||
|  | import com.yj.earth.business.domain.Source; | ||||||
|  | import com.yj.earth.business.domain.User; | ||||||
|  | import com.yj.earth.business.service.RoleService; | ||||||
|  | import com.yj.earth.business.service.SourceService; | ||||||
|  | import com.yj.earth.business.service.UserService; | ||||||
|  | import lombok.extern.slf4j.Slf4j; | ||||||
|  | import org.springframework.stereotype.Service; | ||||||
|  |  | ||||||
|  | import javax.annotation.Resource; | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | @Slf4j | ||||||
|  | @Service | ||||||
|  | public class ServerInitService { | ||||||
|  |     @Resource | ||||||
|  |     private SourceService sourceService; | ||||||
|  |     @Resource | ||||||
|  |     private UserService userService; | ||||||
|  |     @Resource | ||||||
|  |     private RoleService roleService; | ||||||
|  |  | ||||||
|  |     public void init() { | ||||||
|  |         // 查询数据库所有需要加载的资源 | ||||||
|  |         List<Source> list =sourceService.list(new LambdaQueryWrapper<Source>() | ||||||
|  |                 .eq(Source::getSourceType, "terrain") | ||||||
|  |                 .or().eq(Source::getSourceType, "layer") | ||||||
|  |                 .or().eq(Source::getSourceType, "tileset")); | ||||||
|  |         // 依次初始化 | ||||||
|  |         for (Source source : list) { | ||||||
|  |             // 同步资源 | ||||||
|  |             sourceService.getDetail(source.getSourcePath(), sourceService.addAndGetSourceId(source.getSourcePath())); | ||||||
|  |             log.info("初始化资源<--{}-->完成", source.getSourceName()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void checkDefaultData() { | ||||||
|  |         // 查询角色表和用户表是否有数据 | ||||||
|  |         if(roleService.count() == 0 && userService.count() == 0) { | ||||||
|  |             log.info("初始化默认数据"); | ||||||
|  |             // 新增一个管理员角色 | ||||||
|  |             Role adminRole = new Role(); | ||||||
|  |             adminRole.setRoleName("管理员"); | ||||||
|  |             adminRole.setDescription("系统管理员"); | ||||||
|  |             adminRole.setIsSuper(1); | ||||||
|  |             roleService.save(adminRole); | ||||||
|  |             // 新增一个默认角色 | ||||||
|  |             Role defaultRole = new Role(); | ||||||
|  |             defaultRole.setRoleName("默认角色"); | ||||||
|  |             defaultRole.setDescription("系统默认角色"); | ||||||
|  |             defaultRole.setIsSuper(0); | ||||||
|  |             roleService.save(defaultRole); | ||||||
|  |             // 新增一个用户 | ||||||
|  |             User user = new User(); | ||||||
|  |             user.setUsername("admin"); | ||||||
|  |             user.setPassword(BCrypt.hashpw("admin123", BCrypt.gensalt())); | ||||||
|  |             user.setNickname("管理员"); | ||||||
|  |             user.setRoleId(adminRole.getId()); | ||||||
|  |             user.setPhone("13888888888"); | ||||||
|  |             userService.save(user); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,57 @@ | |||||||
|  | package com.yj.earth.common.service; | ||||||
|  |  | ||||||
|  | import com.fasterxml.jackson.databind.ObjectMapper; | ||||||
|  | import com.yj.earth.annotation.SourceType; | ||||||
|  | import org.springframework.beans.factory.annotation.Autowired; | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  |  | ||||||
|  | import java.util.HashMap; | ||||||
|  | import java.util.Map; | ||||||
|  | import java.util.Set; | ||||||
|  |  | ||||||
|  | @Component | ||||||
|  | public class SourceParamsValidator { | ||||||
|  |  | ||||||
|  |     private final ObjectMapper objectMapper; | ||||||
|  |     private final Map<String, Class<?>> sourceTypeMap = new HashMap<>(); | ||||||
|  |  | ||||||
|  |     @Autowired | ||||||
|  |     public SourceParamsValidator(ObjectMapper objectMapper, Set<Class<?>> sourceParamClasses) { | ||||||
|  |         this.objectMapper = objectMapper; | ||||||
|  |  | ||||||
|  |         // 初始化资源类型与参数类的映射关系 | ||||||
|  |         for (Class<?> clazz : sourceParamClasses) { | ||||||
|  |             SourceType annotation = clazz.getAnnotation(SourceType.class); | ||||||
|  |             if (annotation != null) { | ||||||
|  |                 sourceTypeMap.put(annotation.value(), clazz); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 验证并转换参数 | ||||||
|  |      */ | ||||||
|  |     public Object validateAndConvert(String sourceType, Map<String, Object> params) { | ||||||
|  |         // 检查是否有对应的参数类 | ||||||
|  |         Class<?> paramClass = sourceTypeMap.get(sourceType); | ||||||
|  |         if (paramClass == null) { | ||||||
|  |             String message = "不支持 " + sourceType + "的资源类型"; | ||||||
|  |             throw new IllegalArgumentException(message); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 转换并验证参数 | ||||||
|  |         try { | ||||||
|  |             return objectMapper.convertValue(params, paramClass); | ||||||
|  |         } catch (IllegalArgumentException e) { | ||||||
|  |             String message = "请核对类型和参数"; | ||||||
|  |             throw new IllegalArgumentException(message); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 获取所有支持的资源类型 | ||||||
|  |      */ | ||||||
|  |     public Set<String> getSupportedSourceTypes() { | ||||||
|  |         return sourceTypeMap.keySet(); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										66
									
								
								src/main/java/com/yj/earth/common/util/AesEncryptUtil.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/main/java/com/yj/earth/common/util/AesEncryptUtil.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,66 @@ | |||||||
|  | package com.yj.earth.common.util; | ||||||
|  |  | ||||||
|  | import javax.crypto.Cipher; | ||||||
|  | import javax.crypto.spec.IvParameterSpec; | ||||||
|  | import javax.crypto.spec.SecretKeySpec; | ||||||
|  | import java.nio.charset.StandardCharsets; | ||||||
|  | import java.util.Base64; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * AES对称加密工具类 | ||||||
|  |  */ | ||||||
|  | public class AesEncryptUtil { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * AES加密 | ||||||
|  |      * @param content 待加密内容 | ||||||
|  |      * @param key 密钥(16位/24位/32位、对应AES-128/AES-192/AES-256) | ||||||
|  |      * @param algorithm 加密算法(如AES/CBC/PKCS5Padding) | ||||||
|  |      * @return 加密后的Base64字符串 | ||||||
|  |      */ | ||||||
|  |     public static String encrypt(String content, String key, String algorithm) { | ||||||
|  |         try { | ||||||
|  |             // 创建密钥 | ||||||
|  |             SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES"); | ||||||
|  |  | ||||||
|  |             // 初始化加密器 | ||||||
|  |             Cipher cipher = Cipher.getInstance(algorithm); | ||||||
|  |  | ||||||
|  |             // 如果是CBC模式、需要初始化向量IV(与密钥同长度) | ||||||
|  |             if (algorithm.contains("CBC")) { | ||||||
|  |                 IvParameterSpec iv = new IvParameterSpec(key.substring(0, 16).getBytes(StandardCharsets.UTF_8)); | ||||||
|  |                 cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv); | ||||||
|  |             } else { | ||||||
|  |                 cipher.init(Cipher.ENCRYPT_MODE, secretKey); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // 加密并转为Base64 | ||||||
|  |             byte[] encrypted = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8)); | ||||||
|  |             return Base64.getEncoder().encodeToString(encrypted); | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             throw new RuntimeException("AES加密失败", e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 解密方法(如果需要解密可以实现) | ||||||
|  |      */ | ||||||
|  |     public static String decrypt(String encryptedContent, String key, String algorithm) { | ||||||
|  |         try { | ||||||
|  |             SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES"); | ||||||
|  |             Cipher cipher = Cipher.getInstance(algorithm); | ||||||
|  |  | ||||||
|  |             if (algorithm.contains("CBC")) { | ||||||
|  |                 IvParameterSpec iv = new IvParameterSpec(key.substring(0, 16).getBytes(StandardCharsets.UTF_8)); | ||||||
|  |                 cipher.init(Cipher.DECRYPT_MODE, secretKey, iv); | ||||||
|  |             } else { | ||||||
|  |                 cipher.init(Cipher.DECRYPT_MODE, secretKey); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encryptedContent)); | ||||||
|  |             return new String(decrypted, StandardCharsets.UTF_8); | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             throw new RuntimeException("AES解密失败", e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										56
									
								
								src/main/java/com/yj/earth/common/util/ApiResponse.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/main/java/com/yj/earth/common/util/ApiResponse.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,56 @@ | |||||||
|  | package com.yj.earth.common.util; | ||||||
|  |  | ||||||
|  | import lombok.Data; | ||||||
|  |  | ||||||
|  | @Data | ||||||
|  | public class ApiResponse<T> { | ||||||
|  |  | ||||||
|  |     private int code;      // 状态码 | ||||||
|  |     private T data;        // 响应数据 | ||||||
|  |     private String message; // 响应消息 | ||||||
|  |  | ||||||
|  |     // 私有化构造方法 | ||||||
|  |     private ApiResponse(int code, T data, String message) { | ||||||
|  |         this.code = code; | ||||||
|  |         this.data = data; | ||||||
|  |         this.message = message; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 成功响应(带数据) | ||||||
|  |     public static <T> ApiResponse<T> success(T data) { | ||||||
|  |         return new ApiResponse<>(200, data, "操作成功"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 成功响应(无数据) | ||||||
|  |     public static <T> ApiResponse<T> successWithMessage(String message) { | ||||||
|  |         return new ApiResponse<>(200, null, message); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 失败响应(带自定义消息) | ||||||
|  |     public static <T> ApiResponse<T> failure(String message) { | ||||||
|  |         return new ApiResponse<>(20000, null, message); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 失败响应(未授权) | ||||||
|  |     public static <T> ApiResponse<T> failureWithNoAuth(String message) { | ||||||
|  |         return new ApiResponse<>(401, null, message); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 设置 data 字段、并返回当前对象、支持链式调用 | ||||||
|  |     public ApiResponse<T> setData(T data) { | ||||||
|  |         this.data = data; | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 设置 message 字段、并返回当前对象、支持链式调用 | ||||||
|  |     public ApiResponse<T> setMessage(String message) { | ||||||
|  |         this.message = message; | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 设置 code 字段、并返回当前对象、支持链式调用 | ||||||
|  |     public ApiResponse<T> setCode(int code) { | ||||||
|  |         this.code = code; | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										86
									
								
								src/main/java/com/yj/earth/common/util/CodeUtil.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								src/main/java/com/yj/earth/common/util/CodeUtil.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,86 @@ | |||||||
|  | package com.yj.earth.common.util; | ||||||
|  |  | ||||||
|  | import com.baomidou.mybatisplus.annotation.FieldFill; | ||||||
|  | import com.baomidou.mybatisplus.annotation.IdType; | ||||||
|  | import com.baomidou.mybatisplus.generator.FastAutoGenerator; | ||||||
|  | import com.baomidou.mybatisplus.generator.config.OutputFile; | ||||||
|  | import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; | ||||||
|  | import com.baomidou.mybatisplus.generator.fill.Column; | ||||||
|  | import com.yj.earth.datasource.DatabaseManager; | ||||||
|  |  | ||||||
|  | import java.io.File; | ||||||
|  | import java.util.Collections; | ||||||
|  |  | ||||||
|  | public class CodeUtil { | ||||||
|  |  | ||||||
|  |     // SQLite数据库配置 | ||||||
|  |     private static String databasePath = null; | ||||||
|  |     private static String author = "周志雄"; | ||||||
|  |  | ||||||
|  |     public static void main(String[] args) { | ||||||
|  |         DatabaseManager.initDatabase(DatabaseManager.DatabaseType.SQLITE); | ||||||
|  |         databasePath = DatabaseManager.getSqliteDbFilePath(); | ||||||
|  |  | ||||||
|  |         // 检查数据库路径是否有效 | ||||||
|  |         if (databasePath == null || databasePath.trim().isEmpty()) { | ||||||
|  |             throw new RuntimeException("数据库路径未正确初始化"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 确保数据库目录存在 | ||||||
|  |         File dbFile = new File(databasePath); | ||||||
|  |         File parentDir = dbFile.getParentFile(); | ||||||
|  |         if (!parentDir.exists()) { | ||||||
|  |             parentDir.mkdirs(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 传入需要生成代码的表名 | ||||||
|  |         Generation("file_info"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static void Generation(String... tableName) { | ||||||
|  |         // 构建SQLite连接URL | ||||||
|  |         String jdbcUrl = "jdbc:sqlite:" + databasePath; | ||||||
|  |  | ||||||
|  |         // FastAutoGenerator 用来创建代码生成器实例 | ||||||
|  |         FastAutoGenerator.create(jdbcUrl, "", "") | ||||||
|  |                 .globalConfig(builder -> { | ||||||
|  |                     builder.author(author) | ||||||
|  |                             .enableSpringdoc() | ||||||
|  |                             .outputDir(System.getProperty("user.dir") + "/src/main/java"); | ||||||
|  |                 }).packageConfig(builder -> { | ||||||
|  |                     builder.entity("domain") | ||||||
|  |                             .parent("com.yj.earth.business") | ||||||
|  |                             .controller("controller") | ||||||
|  |                             .mapper("mapper") | ||||||
|  |                             .service("service") | ||||||
|  |                             .serviceImpl("service.impl") | ||||||
|  |                             .pathInfo(Collections.singletonMap(OutputFile.xml, | ||||||
|  |                                     System.getProperty("user.dir") + "/src/main/resources/mapper")); | ||||||
|  |                 }).strategyConfig(builder -> { | ||||||
|  |                     builder.addInclude(tableName) | ||||||
|  |                             .addTablePrefix("t_") | ||||||
|  |                             .entityBuilder() | ||||||
|  |                             .enableLombok() | ||||||
|  |                             .enableChainModel() | ||||||
|  |                             .addTableFills(new Column("created_at", FieldFill.INSERT), | ||||||
|  |                                     new Column("updated_at", FieldFill.UPDATE)) | ||||||
|  |                             .naming(NamingStrategy.underline_to_camel) | ||||||
|  |                             .columnNaming(NamingStrategy.underline_to_camel) | ||||||
|  |                             .idType(IdType.ASSIGN_UUID) | ||||||
|  |                             .formatFileName("%s") | ||||||
|  |                             .mapperBuilder() | ||||||
|  |                             .enableMapperAnnotation() | ||||||
|  |                             .enableBaseResultMap() | ||||||
|  |                             .enableBaseColumnList() | ||||||
|  |                             .formatMapperFileName("%sMapper") | ||||||
|  |                             .formatXmlFileName("%sMapper") | ||||||
|  |                             .serviceBuilder() | ||||||
|  |                             .formatServiceFileName("%sService") | ||||||
|  |                             .formatServiceImplFileName("%sServiceImpl") | ||||||
|  |                             .controllerBuilder() | ||||||
|  |                             .enableRestStyle() | ||||||
|  |                             .formatFileName("%sController") | ||||||
|  |                             .enableHyphenStyle(); | ||||||
|  |                 }).execute(); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										180
									
								
								src/main/java/com/yj/earth/common/util/HttpUtil.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								src/main/java/com/yj/earth/common/util/HttpUtil.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,180 @@ | |||||||
|  | package com.yj.earth.common.util; | ||||||
|  |  | ||||||
|  | import com.fasterxml.jackson.core.JsonProcessingException; | ||||||
|  | import com.fasterxml.jackson.databind.ObjectMapper; | ||||||
|  | import lombok.extern.slf4j.Slf4j; | ||||||
|  | import org.apache.http.Header; | ||||||
|  | import org.apache.http.HttpEntity; | ||||||
|  | import org.apache.http.NameValuePair; | ||||||
|  | import org.apache.http.client.config.RequestConfig; | ||||||
|  | import org.apache.http.client.entity.UrlEncodedFormEntity; | ||||||
|  | import org.apache.http.client.methods.CloseableHttpResponse; | ||||||
|  | import org.apache.http.client.methods.HttpGet; | ||||||
|  | import org.apache.http.client.methods.HttpPost; | ||||||
|  | import org.apache.http.entity.StringEntity; | ||||||
|  | import org.apache.http.impl.client.CloseableHttpClient; | ||||||
|  | import org.apache.http.impl.client.HttpClients; | ||||||
|  | import org.apache.http.message.BasicNameValuePair; | ||||||
|  | import org.apache.http.util.EntityUtils; | ||||||
|  | import org.springframework.http.HttpHeaders; | ||||||
|  | import org.springframework.http.HttpStatus; | ||||||
|  | import org.springframework.http.ResponseEntity; | ||||||
|  |  | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Map; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * HTTP 请求工具类 | ||||||
|  |  */ | ||||||
|  | @Slf4j | ||||||
|  | public class HttpUtil { | ||||||
|  |     // 编码格式 | ||||||
|  |     private static final String CHARSET = "UTF-8"; | ||||||
|  |     // 连接超时时间 5 秒 | ||||||
|  |     private static final int CONNECT_TIMEOUT = 5000; | ||||||
|  |     // 读取超时时间 10 秒 | ||||||
|  |     private static final int READ_TIMEOUT = 10000; | ||||||
|  |     // JSON 处理器 | ||||||
|  |     private static final ObjectMapper objectMapper = new ObjectMapper(); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 发送 GET 请求 | ||||||
|  |      */ | ||||||
|  |     public static String doGet(String url) { | ||||||
|  |         try (CloseableHttpClient httpClient = HttpClients.createDefault()) { | ||||||
|  |             HttpGet httpGet = new HttpGet(url); | ||||||
|  |             // 设置超时配置 | ||||||
|  |             RequestConfig requestConfig = RequestConfig.custom() | ||||||
|  |                     .setConnectTimeout(CONNECT_TIMEOUT) | ||||||
|  |                     .setSocketTimeout(READ_TIMEOUT) | ||||||
|  |                     .build(); | ||||||
|  |             httpGet.setConfig(requestConfig); | ||||||
|  |             try (CloseableHttpResponse response = httpClient.execute(httpGet)) { | ||||||
|  |                 return handleResponse(response); | ||||||
|  |             } | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             log.error("GET 请求发生异常、请求 URL: {}", url, e); | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 发送表单参数的 POST 请求 | ||||||
|  |      * 参数类型改为 Map<String, Object> 以支持更多类型的参数值 | ||||||
|  |      */ | ||||||
|  |     public static String doPostForm(String url, Map<String, Object> params) { | ||||||
|  |         try (CloseableHttpClient httpClient = HttpClients.createDefault()) { | ||||||
|  |             HttpPost httpPost = new HttpPost(url); | ||||||
|  |             // 设置超时配置 | ||||||
|  |             RequestConfig requestConfig = RequestConfig.custom() | ||||||
|  |                     .setConnectTimeout(CONNECT_TIMEOUT) | ||||||
|  |                     .setSocketTimeout(READ_TIMEOUT) | ||||||
|  |                     .build(); | ||||||
|  |             httpPost.setConfig(requestConfig); | ||||||
|  |             // 组装表单参数 | ||||||
|  |             if (params != null && !params.isEmpty()) { | ||||||
|  |                 List<NameValuePair> nameValuePairs = new ArrayList<>(); | ||||||
|  |                 for (Map.Entry<String, Object> entry : params.entrySet()) { | ||||||
|  |                     // 将 Object 类型的值转换为字符串 | ||||||
|  |                     String value = entry.getValue() != null ? entry.getValue().toString() : null; | ||||||
|  |                     nameValuePairs.add(new BasicNameValuePair(entry.getKey(), value)); | ||||||
|  |                 } | ||||||
|  |                 httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, CHARSET)); | ||||||
|  |             } | ||||||
|  |             // 执行请求 | ||||||
|  |             try (CloseableHttpResponse response = httpClient.execute(httpPost)) { | ||||||
|  |                 return handleResponse(response); | ||||||
|  |             } | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             log.error("表单 POST 请求发生异常、请求 URL: {}、请求参数: {}", url, params, e); | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 发送 JSON 参数的 POST 请求 | ||||||
|  |      * 参数类型统一为 Map<String, Object> | ||||||
|  |      */ | ||||||
|  |     public static String doPostJson(String url, Map<String, Object> params) { | ||||||
|  |         try (CloseableHttpClient httpClient = HttpClients.createDefault()) { | ||||||
|  |             HttpPost httpPost = new HttpPost(url); | ||||||
|  |             // 设置超时配置 | ||||||
|  |             RequestConfig requestConfig = RequestConfig.custom() | ||||||
|  |                     .setConnectTimeout(CONNECT_TIMEOUT) | ||||||
|  |                     .setSocketTimeout(READ_TIMEOUT) | ||||||
|  |                     .build(); | ||||||
|  |             httpPost.setConfig(requestConfig); | ||||||
|  |             // 设置 JSON 请求头 | ||||||
|  |             httpPost.setHeader("Content-Type", "application/json;charset=" + CHARSET); | ||||||
|  |             // Map 转 JSON 字符串并设置为请求体 | ||||||
|  |             if (params != null && !params.isEmpty()) { | ||||||
|  |                 String jsonParams = objectMapper.writeValueAsString(params); | ||||||
|  |                 httpPost.setEntity(new StringEntity(jsonParams, CHARSET)); | ||||||
|  |             } | ||||||
|  |             // 执行请求 | ||||||
|  |             try (CloseableHttpResponse response = httpClient.execute(httpPost)) { | ||||||
|  |                 return handleResponse(response); | ||||||
|  |             } | ||||||
|  |         } catch (JsonProcessingException e) { | ||||||
|  |             log.error("JSON POST 请求参数序列化失败、请求 URL: {}、请求参数: {}", url, params, e); | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             log.error("JSON POST 请求发生异常、请求 URL: {}、请求参数: {}", url, params, e); | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 发送 GET 请求、返回字节数组的ResponseEntity、适用于下载文件 | ||||||
|  |      */ | ||||||
|  |     public static ResponseEntity<byte[]> doGetForByteArrayResponse(String url) { | ||||||
|  |         try (CloseableHttpClient httpClient = HttpClients.createDefault()) { | ||||||
|  |             HttpGet httpGet = new HttpGet(url); | ||||||
|  |             // 设置超时配置 | ||||||
|  |             RequestConfig requestConfig = RequestConfig.custom() | ||||||
|  |                     .setConnectTimeout(CONNECT_TIMEOUT) | ||||||
|  |                     .setSocketTimeout(READ_TIMEOUT) | ||||||
|  |                     .build(); | ||||||
|  |             httpGet.setConfig(requestConfig); | ||||||
|  |  | ||||||
|  |             try (CloseableHttpResponse response = httpClient.execute(httpGet)) { | ||||||
|  |                 // 获取状态码 | ||||||
|  |                 int statusCode = response.getStatusLine().getStatusCode(); | ||||||
|  |                 HttpStatus httpStatus = HttpStatus.valueOf(statusCode); | ||||||
|  |  | ||||||
|  |                 // 处理响应头 | ||||||
|  |                 HttpHeaders headers = new HttpHeaders(); | ||||||
|  |                 Header[] allHeaders = response.getAllHeaders(); | ||||||
|  |                 for (Header header : allHeaders) { | ||||||
|  |                     // 特别保留Content-Disposition和Content-Type、用于前端下载 | ||||||
|  |                     headers.add(header.getName(), header.getValue()); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 // 处理响应体(二进制数据) | ||||||
|  |                 HttpEntity entity = response.getEntity(); | ||||||
|  |                 byte[] body = entity != null ? EntityUtils.toByteArray(entity) : null; | ||||||
|  |  | ||||||
|  |                 // 返回ResponseEntity对象 | ||||||
|  |                 return new ResponseEntity<>(body, headers, httpStatus); | ||||||
|  |             } | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             log.error("GET 下载请求发生异常、请求 URL: {}", url, e); | ||||||
|  |             // 发生异常时返回500错误 | ||||||
|  |             return new ResponseEntity<>(e.getMessage().getBytes(), HttpStatus.INTERNAL_SERVER_ERROR); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 通用响应处理方法 | ||||||
|  |      */ | ||||||
|  |     private static String handleResponse(CloseableHttpResponse response) throws IOException { | ||||||
|  |         int statusCode = response.getStatusLine().getStatusCode(); | ||||||
|  |         if (statusCode == 200) { | ||||||
|  |             HttpEntity entity = response.getEntity(); | ||||||
|  |             return entity != null ? EntityUtils.toString(entity, CHARSET) : null; | ||||||
|  |         } | ||||||
|  |         log.warn("HTTP 请求失败、状态码: {}、响应内容: {}", statusCode, response.getEntity() != null ? EntityUtils.toString(response.getEntity(), CHARSET) : null); | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										50
									
								
								src/main/java/com/yj/earth/common/util/JsonMapConverter.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/main/java/com/yj/earth/common/util/JsonMapConverter.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,50 @@ | |||||||
|  | package com.yj.earth.common.util; | ||||||
|  |  | ||||||
|  | import com.fasterxml.jackson.core.JsonProcessingException; | ||||||
|  | import com.fasterxml.jackson.core.type.TypeReference; | ||||||
|  | import com.fasterxml.jackson.databind.ObjectMapper; | ||||||
|  | import lombok.extern.slf4j.Slf4j; | ||||||
|  |  | ||||||
|  | import java.util.HashMap; | ||||||
|  | import java.util.Map; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Map与JSON互相转换工具类 | ||||||
|  |  */ | ||||||
|  | @Slf4j | ||||||
|  | public class JsonMapConverter { | ||||||
|  |  | ||||||
|  |     private static final ObjectMapper objectMapper = new ObjectMapper(); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 将 Map 转换为 JSON 字符串 | ||||||
|  |      */ | ||||||
|  |     public static String mapToJson(Map<String, Object> map) { | ||||||
|  |         if (map == null || map.isEmpty()) { | ||||||
|  |             return "{}"; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         try { | ||||||
|  |             return objectMapper.writeValueAsString(map); | ||||||
|  |         } catch (JsonProcessingException e) { | ||||||
|  |             log.error("Map转JSON失败", e); | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 将 JSON 字符串转换为 Map | ||||||
|  |      */ | ||||||
|  |     public static Map<String, Object> jsonToMap(String json) { | ||||||
|  |         if (json == null || json.trim().isEmpty()) { | ||||||
|  |             return new HashMap<>(0); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         try { | ||||||
|  |             return objectMapper.readValue(json, new TypeReference<Map<String, Object>>() {}); | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             log.error("JSON转Map失败、JSON内容: {}", json, e); | ||||||
|  |             return new HashMap<>(0); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										116
									
								
								src/main/java/com/yj/earth/common/util/MapUtil.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								src/main/java/com/yj/earth/common/util/MapUtil.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,116 @@ | |||||||
|  | package com.yj.earth.common.util; | ||||||
|  |  | ||||||
|  | import com.fasterxml.jackson.core.JsonProcessingException; | ||||||
|  | import com.fasterxml.jackson.databind.ObjectMapper; | ||||||
|  | import java.util.HashMap; | ||||||
|  | import java.util.Map; | ||||||
|  |  | ||||||
|  | public class MapUtil { | ||||||
|  |  | ||||||
|  |     private static final ObjectMapper objectMapper = new ObjectMapper(); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 合并两个 Map、如果存在相同的 Key、则用新 Map 中的值替换原始 Map 中的值 | ||||||
|  |      * 如果原始 Map 中有某个 Key 而新 Map 中没有、则保持原始 Map 中的值不变 | ||||||
|  |      */ | ||||||
|  |     public static <K, V> void mergeMaps(Map<K, V> originalMap, Map<K, V> newMap) { | ||||||
|  |         // 检查参数是否为null、避免空指针异常 | ||||||
|  |         if (originalMap == null || newMap == null) { | ||||||
|  |             throw new IllegalArgumentException("参数Map不能为null"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 遍历新Map中的所有键值对 | ||||||
|  |         for (Map.Entry<K, V> entry : newMap.entrySet()) { | ||||||
|  |             K key = entry.getKey(); | ||||||
|  |             // 如果原始Map中存在相同的key、则替换值 | ||||||
|  |             if (originalMap.containsKey(key)) { | ||||||
|  |                 originalMap.put(key, entry.getValue()); | ||||||
|  |             } | ||||||
|  |             // 如果原始Map中不存在该key、则不做任何操作 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 将JSON字符串转换为Map对象 | ||||||
|  |      * | ||||||
|  |      * @param jsonString JSON格式的字符串 | ||||||
|  |      * @return 转换后的Map对象、如果JSON为空则返回空Map | ||||||
|  |      * @throws IllegalArgumentException 当JSON字符串无效或解析失败时抛出 | ||||||
|  |      */ | ||||||
|  |     public static <K, V> Map<K, V> jsonToMap(String jsonString) { | ||||||
|  |         if (jsonString == null || jsonString.trim().isEmpty()) { | ||||||
|  |             return new HashMap<>(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         try { | ||||||
|  |             // 将JSON字符串转换为Map | ||||||
|  |             return objectMapper.readValue(jsonString, Map.class); | ||||||
|  |         } catch (JsonProcessingException e) { | ||||||
|  |             throw new IllegalArgumentException("JSON字符串解析失败: " + e.getMessage(), e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 将 Map 对象转换为JSON字符串 | ||||||
|  |      */ | ||||||
|  |     public static <K, V> String mapToString(Map<K, V> map) { | ||||||
|  |         if (map == null) { | ||||||
|  |             return ""; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         try { | ||||||
|  |             // 将Map转换为JSON字符串 | ||||||
|  |             return objectMapper.writeValueAsString(map); | ||||||
|  |         } catch (JsonProcessingException e) { | ||||||
|  |             throw new IllegalArgumentException("Map转换为JSON字符串失败: " + e.getMessage(), e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 将任意类型对象转换为JSON字符串 | ||||||
|  |      */ | ||||||
|  |     public static <T> String objectToJson(T object) { | ||||||
|  |         if (object == null) { | ||||||
|  |             return ""; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         try { | ||||||
|  |             // 将对象转换为JSON字符串 | ||||||
|  |             return objectMapper.writeValueAsString(object); | ||||||
|  |         } catch (JsonProcessingException e) { | ||||||
|  |             throw new IllegalArgumentException("对象转换为JSON字符串失败: " + e.getMessage(), e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 从Map中获取指定key的字符串值 | ||||||
|  |      * | ||||||
|  |      * @param map 数据源Map | ||||||
|  |      * @param key 要获取的字段名 | ||||||
|  |      * @return 字段的字符串值、如果map为null或key不存在则返回null | ||||||
|  |      */ | ||||||
|  |     public static String getString(Map<?, ?> map, String key) { | ||||||
|  |         if (map == null || key == null) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         Object value = map.get(key); | ||||||
|  |         return value != null ? value.toString() : null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 直接从JSON字符串中获取指定key的字符串值 | ||||||
|  |      * | ||||||
|  |      * @param jsonString JSON格式的字符串 | ||||||
|  |      * @param key 要获取的字段名 | ||||||
|  |      * @return 字段的字符串值、如果JSON为空或key不存在则返回null | ||||||
|  |      */ | ||||||
|  |     public static String getString(String jsonString, String key) { | ||||||
|  |         if (jsonString == null || key == null) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         Map<?, ?> map = jsonToMap(jsonString); | ||||||
|  |         return getString(map, key); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										114
									
								
								src/main/java/com/yj/earth/common/util/PortKillUtil.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								src/main/java/com/yj/earth/common/util/PortKillUtil.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,114 @@ | |||||||
|  | package com.yj.earth.common.util; | ||||||
|  |  | ||||||
|  | import lombok.extern.slf4j.Slf4j; | ||||||
|  |  | ||||||
|  | import java.io.BufferedReader; | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.io.InputStream; | ||||||
|  | import java.io.InputStreamReader; | ||||||
|  |  | ||||||
|  | @Slf4j | ||||||
|  | public class PortKillUtil { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 根据端口号杀死对应的进程 | ||||||
|  |      */ | ||||||
|  |     public static boolean killProcessByPort(int port) { | ||||||
|  |         // 获取操作系统类型 | ||||||
|  |         String osName = System.getProperty("os.name").toLowerCase(); | ||||||
|  |  | ||||||
|  |         try { | ||||||
|  |             if (osName.contains("windows")) { | ||||||
|  |                 // Windows系统处理逻辑 | ||||||
|  |                 return killWindowsProcess(port); | ||||||
|  |             } else if (osName.contains("linux") || osName.contains("unix")) { | ||||||
|  |                 // Linux/Unix系统处理逻辑 | ||||||
|  |                 return killLinuxProcess(port); | ||||||
|  |             } else { | ||||||
|  |                 log.error("不支持的操作系统: " + osName); | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             log.error("杀死进程时发生错误: " + e.getMessage(), e); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 杀死 Windows 系统中占用指定端口的进程 | ||||||
|  |      */ | ||||||
|  |     private static boolean killWindowsProcess(int port) throws IOException, InterruptedException { | ||||||
|  |         // 查找占用端口的进程ID | ||||||
|  |         Process process = Runtime.getRuntime().exec("netstat -ano | findstr :" + port); | ||||||
|  |         process.waitFor(); | ||||||
|  |  | ||||||
|  |         // 读取命令输出 | ||||||
|  |         String pid = getWindowsPidFromOutput(process.getInputStream()); | ||||||
|  |         if (pid == null || pid.isEmpty()) { | ||||||
|  |             log.error("端口 " + port + " 未被占用"); | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 杀死找到的进程 | ||||||
|  |         Process killProcess = Runtime.getRuntime().exec("taskkill /F /PID " + pid); | ||||||
|  |         int exitCode = killProcess.waitFor(); | ||||||
|  |  | ||||||
|  |         if (exitCode == 0) { | ||||||
|  |             log.info("成功杀死端口 " + port + " 对应的进程、PID: " + pid); | ||||||
|  |             return true; | ||||||
|  |         } else { | ||||||
|  |             log.error("杀死端口 " + port + " 对应的进程失败、PID: " + pid); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 从 Windows 命令输出中提取进程 ID | ||||||
|  |      */ | ||||||
|  |     private static String getWindowsPidFromOutput(InputStream inputStream) throws IOException { | ||||||
|  |         try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { | ||||||
|  |             String line; | ||||||
|  |             while ((line = reader.readLine()) != null) { | ||||||
|  |                 line = line.trim(); | ||||||
|  |                 // 查找包含LISTENING状态的行 | ||||||
|  |                 if (line.contains("LISTENING")) { | ||||||
|  |                     // 提取最后一个空格后的数字作为PID | ||||||
|  |                     String[] parts = line.split("\\s+"); | ||||||
|  |                     return parts[parts.length - 1]; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 杀死 Linux 系统中占用指定端口的进程 | ||||||
|  |      */ | ||||||
|  |     private static boolean killLinuxProcess(int port) throws IOException, InterruptedException { | ||||||
|  |         // 查找占用端口的进程ID | ||||||
|  |         Process process = Runtime.getRuntime().exec("lsof -i:" + port + " -t"); | ||||||
|  |         process.waitFor(); | ||||||
|  |  | ||||||
|  |         // 读取命令输出获取PID | ||||||
|  |         try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { | ||||||
|  |             String pid = reader.readLine(); | ||||||
|  |             if (pid == null || pid.isEmpty()) { | ||||||
|  |                 log.error("端口 " + port + " 未被占用"); | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // 杀死找到的进程 | ||||||
|  |             Process killProcess = Runtime.getRuntime().exec("kill -9 " + pid); | ||||||
|  |             int exitCode = killProcess.waitFor(); | ||||||
|  |  | ||||||
|  |             if (exitCode == 0) { | ||||||
|  |                 log.info("成功杀死端口 " + port + " 对应的进程、PID: " + pid); | ||||||
|  |                 return true; | ||||||
|  |             } else { | ||||||
|  |                 log.error("杀死端口 " + port + " 对应的进程失败、PID: " + pid); | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										33
									
								
								src/main/java/com/yj/earth/common/util/PositionUtil.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/main/java/com/yj/earth/common/util/PositionUtil.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | |||||||
|  | package com.yj.earth.common.util; | ||||||
|  |  | ||||||
|  | public class PositionUtil { | ||||||
|  |     private static final double a = 6378137.0; // 椭球长半轴 | ||||||
|  |     private static final double e = 0.0818191908426; // 椭球第一偏心率 | ||||||
|  |     private static final double epsilon = 1e-8; // 迭代精度 | ||||||
|  |     private static final double r2d = 180.0 / Math.PI; // 弧度转角度 | ||||||
|  |  | ||||||
|  |     public static double[] xyz2Blh(double x, double y, double z) { | ||||||
|  |         double tmpX = x; | ||||||
|  |         double tmpY = y; | ||||||
|  |         double tmpZ = z; | ||||||
|  |  | ||||||
|  |         double curB = 0.0; | ||||||
|  |         double N = 0.0; | ||||||
|  |         double calB = Math.atan2(tmpZ, Math.sqrt(tmpX * tmpX + tmpY * tmpY)); | ||||||
|  |  | ||||||
|  |         int counter = 0; | ||||||
|  |         while (Math.abs(curB - calB) * r2d > epsilon && counter < 25) { | ||||||
|  |             curB = calB; | ||||||
|  |             N = a / Math.sqrt(1 - e * e * Math.sin(curB) * Math.sin(curB)); | ||||||
|  |             calB = Math.atan2(tmpZ + N * e * e * Math.sin(curB), | ||||||
|  |                     Math.sqrt(tmpX * tmpX + tmpY * tmpY)); | ||||||
|  |             counter++; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         double longitude = Math.atan2(tmpY, tmpX) * r2d; | ||||||
|  |         double latitude = curB * r2d; | ||||||
|  |         double height = tmpZ / Math.sin(curB) - N * (1 - e * e); | ||||||
|  |  | ||||||
|  |         return new double[]{longitude, latitude, height}; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										126
									
								
								src/main/java/com/yj/earth/common/util/SdkUtil.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								src/main/java/com/yj/earth/common/util/SdkUtil.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,126 @@ | |||||||
|  | package com.yj.earth.common.util; | ||||||
|  |  | ||||||
|  | import com.yj.earth.common.constant.GlobalConstant; | ||||||
|  | import lombok.extern.slf4j.Slf4j; | ||||||
|  | import org.springframework.core.io.ClassPathResource; | ||||||
|  | import org.yaml.snakeyaml.Yaml; | ||||||
|  |  | ||||||
|  | import java.io.File; | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.io.InputStream; | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Map; | ||||||
|  | import java.util.concurrent.TimeUnit; | ||||||
|  | import java.util.stream.Collectors; | ||||||
|  |  | ||||||
|  | @Slf4j | ||||||
|  | public class SdkUtil { | ||||||
|  |     // 保存SDK进程引用 | ||||||
|  |     private static Process sdkProcess; | ||||||
|  |     // 保存SDK端口号、用于关闭时强制终止 | ||||||
|  |     private static Integer sdkPort; | ||||||
|  |  | ||||||
|  |     // 对外提供的启动入口 | ||||||
|  |     public static void startSdkIfConfigured() throws IOException { | ||||||
|  |         // 读取配置 | ||||||
|  |         sdkPort = getSdkPortFromYamlConfig(); | ||||||
|  |         // 未配置则不启动 | ||||||
|  |         if (sdkPort == null) { | ||||||
|  |             log.info("请先配置SDK端口"); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         // 配置存在时、正常启动SDK | ||||||
|  |         startSdkJar(sdkPort); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 接收已确认的端口、启动SDK | ||||||
|  |     private static void startSdkJar(int sdkPort) throws IOException { | ||||||
|  |         // 获取项目根目录(当前工作目录) | ||||||
|  |         String projectRoot = System.getProperty("user.dir"); | ||||||
|  |         // 获取SDK完整路径 | ||||||
|  |         String sdkJarPath = new File(projectRoot, GlobalConstant.SDKPATH).getAbsolutePath(); | ||||||
|  |         // 校验SDK | ||||||
|  |         File sdkJarFile = new File(sdkJarPath); | ||||||
|  |         if (!sdkJarFile.exists() || !sdkJarFile.isFile()) { | ||||||
|  |             log.error("SDK不存在或不是有效文件:{}", sdkJarPath); | ||||||
|  |         } | ||||||
|  |         log.info("准备启动SDK: {}", sdkJarPath); | ||||||
|  |         log.info("使用SDK端口: {}", sdkPort); | ||||||
|  |         // 构建启动命令、添加 -Dserver.port 参数 | ||||||
|  |         List<String> command = new ArrayList<>(); | ||||||
|  |         command.add("java"); | ||||||
|  |         command.add("-Dserver.port=" + sdkPort); | ||||||
|  |         command.add("-jar"); | ||||||
|  |         command.add(sdkJarPath); | ||||||
|  |         // 构建进程启动器 | ||||||
|  |         ProcessBuilder processBuilder = new ProcessBuilder(command); | ||||||
|  |         // 打印执行的命令 | ||||||
|  |         String commandStr = command.stream().collect(Collectors.joining(" ")); | ||||||
|  |         log.info("执行命令: {}", commandStr); | ||||||
|  |         // 输出SDK的控制台日志到当前应用的日志中 | ||||||
|  |         processBuilder.redirectErrorStream(true); | ||||||
|  |         // 日志文件路径建议优化: 避免与项目根目录混淆 | ||||||
|  |         File sdkLogFile = new File(projectRoot, GlobalConstant.SDKLOG); | ||||||
|  |         // 确保目录存在(避免日志写入失败) | ||||||
|  |         if (!sdkLogFile.getParentFile().exists()) { | ||||||
|  |             sdkLogFile.getParentFile().mkdirs(); | ||||||
|  |         } | ||||||
|  |         processBuilder.redirectOutput(sdkLogFile); | ||||||
|  |         // 启动进程(非阻塞) | ||||||
|  |         sdkProcess = processBuilder.start(); | ||||||
|  |         log.info("SDK已在后台启动、进程ID: {}", sdkProcess.pid()); | ||||||
|  |         // 注册JVM关闭钩子、在主程序退出时关闭SDK进程 | ||||||
|  |         Runtime.getRuntime().addShutdownHook(new Thread(() -> { | ||||||
|  |             if (sdkProcess != null && sdkProcess.isAlive()) { | ||||||
|  |                 log.info("主程序关闭、正在停止SDK进程(PID: {})...", sdkProcess.pid()); | ||||||
|  |                 // 销毁子进程 | ||||||
|  |                 sdkProcess.destroy(); | ||||||
|  |                 try { | ||||||
|  |                     // 等待进程终止(最多5秒) | ||||||
|  |                     boolean terminated = sdkProcess.waitFor(5, TimeUnit.SECONDS); | ||||||
|  |                     if (terminated) { | ||||||
|  |                         log.info("SDK进程已成功停止"); | ||||||
|  |                     } else { | ||||||
|  |                         log.warn("SDK进程未能正常停止、尝试通过端口{}强制终止...", sdkPort); | ||||||
|  |                         // 通过端口强制终止 | ||||||
|  |                         boolean killSuccess = PortKillUtil.killProcessByPort(sdkPort); | ||||||
|  |                         if (killSuccess) { | ||||||
|  |                             log.info("已通过端口{}强制终止SDK进程", sdkPort); | ||||||
|  |                         } else { | ||||||
|  |                             log.error("通过端口{}强制终止SDK进程失败", sdkPort); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } catch (InterruptedException e) { | ||||||
|  |                     log.error("停止SDK进程时发生中断", e); | ||||||
|  |                     Thread.currentThread().interrupt(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, "SDK-Process-Shutdown-Hook")); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 从配置文件读取SDK端口配置 | ||||||
|  |      */ | ||||||
|  |     private static Integer getSdkPortFromYamlConfig() { | ||||||
|  |         Yaml yaml = new Yaml(); | ||||||
|  |         try (InputStream inputStream = new ClassPathResource("application.yml").getInputStream()) { | ||||||
|  |             // 解析YAML文件为Map | ||||||
|  |             Map<String, Object> yamlMap = yaml.load(inputStream); | ||||||
|  |             // 逐级获取配置 | ||||||
|  |             if (yamlMap.containsKey("sdk")) { | ||||||
|  |                 Object sdkObj = yamlMap.get("sdk"); | ||||||
|  |                 if (sdkObj instanceof Map) { | ||||||
|  |                     Map<?, ?> sdkMap = (Map<?, ?>) sdkObj; | ||||||
|  |                     if (sdkMap.containsKey("port")) { | ||||||
|  |                         return ((Number) sdkMap.get("port")).intValue(); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             log.error("未配置SDK端口"); | ||||||
|  |         } catch (IOException e) { | ||||||
|  |             log.error("读取配置文件失败", e); | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,80 @@ | |||||||
|  | package com.yj.earth.common.util; | ||||||
|  |  | ||||||
|  | import lombok.extern.slf4j.Slf4j; | ||||||
|  | import oshi.SystemInfo; | ||||||
|  | import oshi.hardware.*; | ||||||
|  |  | ||||||
|  | import java.security.MessageDigest; | ||||||
|  | import java.security.NoSuchAlgorithmException; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.HexFormat; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 服务器唯一标识工具类 | ||||||
|  |  * 基于CPU、主板、磁盘、网卡硬件信息生成唯一标识、不更换核心硬件则标识不变 | ||||||
|  |  */ | ||||||
|  | @Slf4j | ||||||
|  | public class ServerUniqueIdUtil { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 获取服务器唯一标识 | ||||||
|  |      */ | ||||||
|  |     public static String getServerUniqueId() { | ||||||
|  |         // 初始化系统信息(oshi核心入口) | ||||||
|  |         SystemInfo systemInfo = new SystemInfo(); | ||||||
|  |         HardwareAbstractionLayer hardware = systemInfo.getHardware(); | ||||||
|  |  | ||||||
|  |         try { | ||||||
|  |             // 收集稳定的核心硬件信息 | ||||||
|  |             StringBuilder hardwareRawInfo = new StringBuilder(); | ||||||
|  |  | ||||||
|  |             // CPU唯一标识 | ||||||
|  |             CentralProcessor cpu = hardware.getProcessor(); | ||||||
|  |             String cpuId = cpu.getProcessorIdentifier().getProcessorID(); | ||||||
|  |             if (cpuId != null && !cpuId.trim().isEmpty()) { | ||||||
|  |                 hardwareRawInfo.append(cpuId).append("|"); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // 主板UUID | ||||||
|  |             ComputerSystem mainBoard = hardware.getComputerSystem(); | ||||||
|  |             String boardUuid = mainBoard.getHardwareUUID(); | ||||||
|  |             if (boardUuid != null && !boardUuid.trim().isEmpty()) { | ||||||
|  |                 hardwareRawInfo.append(boardUuid).append("|"); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // 第一个物理磁盘序列号 | ||||||
|  |             List<HWDiskStore> disks = hardware.getDiskStores(); | ||||||
|  |             for (HWDiskStore disk : disks) { | ||||||
|  |                 // 过滤虚拟磁盘( | ||||||
|  |                 if (!disk.getModel().toLowerCase().contains("virtual") && disk.getSize() > 0) { | ||||||
|  |                     String diskSerial = disk.getSerial(); | ||||||
|  |                     if (diskSerial != null && !diskSerial.trim().isEmpty()) { | ||||||
|  |                         hardwareRawInfo.append(diskSerial).append("|"); | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // 第一个物理网卡MAC地址 | ||||||
|  |             List<NetworkIF> netCards = hardware.getNetworkIFs(); | ||||||
|  |             for (NetworkIF netCard : netCards) { | ||||||
|  |                 String mac = netCard.getMacaddr(); | ||||||
|  |                 // 过滤条件非空、非全零MAC、非回环网卡 | ||||||
|  |                 if (mac != null && !mac.trim().isEmpty() && !mac.startsWith("00:00:00:00:00:00")) { | ||||||
|  |                     hardwareRawInfo.append(mac).append("|"); | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // MD5哈希 | ||||||
|  |             MessageDigest md = MessageDigest.getInstance("MD5"); | ||||||
|  |             byte[] hashBytes = md.digest(hardwareRawInfo.toString().getBytes()); | ||||||
|  |  | ||||||
|  |             // 字节数组转十六进制字符串 | ||||||
|  |             return HexFormat.of().formatHex(hashBytes).toUpperCase(); | ||||||
|  |  | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             return "unknown"; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										401
									
								
								src/main/java/com/yj/earth/datasource/DatabaseManager.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										401
									
								
								src/main/java/com/yj/earth/datasource/DatabaseManager.java
									
									
									
									
									
										Normal file
									
								
							| @ -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<Class<?>> 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<Class<?>> 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<String, Object> yamlData = yaml.load(yamlInput); | ||||||
|  |             Map<String, Object> springMap = (Map<String, Object>) yamlData.get("spring"); | ||||||
|  |             Map<String, Object> datasourceMap = (Map<String, Object>) springMap.get("datasource"); | ||||||
|  |             Map<String, Object> mysqlMap = (Map<String, Object>) 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<String> 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<String, Object> 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<String, Object> yamlData = yaml.load(yamlInput); | ||||||
|  |             if (yamlData.containsKey("spring")) { | ||||||
|  |                 Object springObj = yamlData.get("spring"); | ||||||
|  |                 if (springObj instanceof Map) { | ||||||
|  |                     Map<String, Object> springMap = (Map<String, Object>) springObj; | ||||||
|  |                     if (springMap.containsKey("datasource")) { | ||||||
|  |                         Object datasourceObj = springMap.get("datasource"); | ||||||
|  |                         if (datasourceObj instanceof Map) { | ||||||
|  |                             Map<String, Object> datasourceMap = (Map<String, Object>) datasourceObj; | ||||||
|  |                             if (datasourceMap.containsKey("active")) { | ||||||
|  |                                 Object activeObj = datasourceMap.get("active"); | ||||||
|  |                                 if (activeObj != null) { | ||||||
|  |                                     return activeObj.toString().trim(); | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             log.error("读取配置文件出错"); | ||||||
|  |         } | ||||||
|  |         return "sqlite"; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										38
									
								
								src/main/java/com/yj/earth/design/FileInfo.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/main/java/com/yj/earth/design/FileInfo.java
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||||
|  | } | ||||||
							
								
								
									
										28
									
								
								src/main/java/com/yj/earth/design/Role.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/main/java/com/yj/earth/design/Role.java
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||||
|  | } | ||||||
							
								
								
									
										25
									
								
								src/main/java/com/yj/earth/design/RoleSource.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/main/java/com/yj/earth/design/RoleSource.java
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||||
|  | } | ||||||
							
								
								
									
										43
									
								
								src/main/java/com/yj/earth/design/Source.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/main/java/com/yj/earth/design/Source.java
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||||
|  | } | ||||||
							
								
								
									
										38
									
								
								src/main/java/com/yj/earth/design/User.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/main/java/com/yj/earth/design/User.java
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||||
|  | } | ||||||
| @ -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<String> sourceIdList; | ||||||
|  | } | ||||||
| @ -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<String> roleIdList; | ||||||
|  |     @Schema(description = "资源ID") | ||||||
|  |     private String sourceId; | ||||||
|  | } | ||||||
| @ -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; | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								src/main/java/com/yj/earth/dto/role/AddRoleDto.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/main/java/com/yj/earth/dto/role/AddRoleDto.java
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								src/main/java/com/yj/earth/dto/role/UpdateRoleDto.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/main/java/com/yj/earth/dto/role/UpdateRoleDto.java
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||||
|  | } | ||||||
							
								
								
									
										17
									
								
								src/main/java/com/yj/earth/dto/source/AddDirectoryDto.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/main/java/com/yj/earth/dto/source/AddDirectoryDto.java
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								src/main/java/com/yj/earth/dto/source/AddModelSourceDto.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/main/java/com/yj/earth/dto/source/AddModelSourceDto.java
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||||
|  | } | ||||||
							
								
								
									
										22
									
								
								src/main/java/com/yj/earth/dto/source/AddOtherSourceDto.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/main/java/com/yj/earth/dto/source/AddOtherSourceDto.java
									
									
									
									
									
										Normal file
									
								
							| @ -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<String, Object> params; | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								src/main/java/com/yj/earth/dto/source/DragSourceDto.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/main/java/com/yj/earth/dto/source/DragSourceDto.java
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||||
|  | } | ||||||
							
								
								
									
										27
									
								
								src/main/java/com/yj/earth/dto/source/UpdateSourceDto.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/main/java/com/yj/earth/dto/source/UpdateSourceDto.java
									
									
									
									
									
										Normal file
									
								
							| @ -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<String, Object> params; | ||||||
|  | } | ||||||
							
								
								
									
										26
									
								
								src/main/java/com/yj/earth/dto/user/AddUserDto.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/main/java/com/yj/earth/dto/user/AddUserDto.java
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||||
|  | } | ||||||
							
								
								
									
										14
									
								
								src/main/java/com/yj/earth/dto/user/UpdatePasswordDto.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/main/java/com/yj/earth/dto/user/UpdatePasswordDto.java
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								src/main/java/com/yj/earth/dto/user/UpdateUserDto.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/main/java/com/yj/earth/dto/user/UpdateUserDto.java
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||||
|  | } | ||||||
							
								
								
									
										12
									
								
								src/main/java/com/yj/earth/dto/user/UserLoginDto.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/main/java/com/yj/earth/dto/user/UserLoginDto.java
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||||
|  | } | ||||||
							
								
								
									
										14
									
								
								src/main/java/com/yj/earth/model/Point.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/main/java/com/yj/earth/model/Point.java
									
									
									
									
									
										Normal file
									
								
							| @ -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;  // 经度 | ||||||
|  | } | ||||||
							
								
								
									
										22
									
								
								src/main/java/com/yj/earth/model/RouteRequest.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/main/java/com/yj/earth/model/RouteRequest.java
									
									
									
									
									
										Normal file
									
								
							| @ -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<Point> waypoints; | ||||||
|  | } | ||||||
							
								
								
									
										14
									
								
								src/main/java/com/yj/earth/model/RouteResponse.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/main/java/com/yj/earth/model/RouteResponse.java
									
									
									
									
									
										Normal file
									
								
							| @ -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<Point> pathPoints; // 路径点列表 | ||||||
|  | } | ||||||
							
								
								
									
										12
									
								
								src/main/java/com/yj/earth/model/StatusResponse.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/main/java/com/yj/earth/model/StatusResponse.java
									
									
									
									
									
										Normal file
									
								
							| @ -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;     // 状态消息 | ||||||
|  | } | ||||||
							
								
								
									
										222
									
								
								src/main/java/com/yj/earth/params/BillboardObject.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								src/main/java/com/yj/earth/params/BillboardObject.java
									
									
									
									
									
										Normal file
									
								
							| @ -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<LinkContent> 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<VrContent> 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<Object> content = new ArrayList<>(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @Data | ||||||
|  |         @Schema(description = "ISC属性") | ||||||
|  |         public static class Isc { | ||||||
|  |             @Schema(description = "ISC内容列表") | ||||||
|  |             private List<Object> content = new ArrayList<>(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @Data | ||||||
|  |         @Schema(description = "物资属性") | ||||||
|  |         public static class Goods { | ||||||
|  |             @Schema(description = "物资内容列表") | ||||||
|  |             private List<GoodsContent> 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; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										70
									
								
								src/main/java/com/yj/earth/params/Circle.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/main/java/com/yj/earth/params/Circle.java
									
									
									
									
									
										Normal file
									
								
							| @ -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<String, Object> 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<String> 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<Object> content; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										192
									
								
								src/main/java/com/yj/earth/params/CurvelineObject.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										192
									
								
								src/main/java/com/yj/earth/params/CurvelineObject.java
									
									
									
									
									
										Normal file
									
								
							| @ -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<Position> 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<LinkContent> 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; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	 ZZX9599
					ZZX9599