Compare commits

...

2 Commits

49 changed files with 2910 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
/mvnw text eol=lf
*.cmd text eol=crlf

33
.gitignore vendored Normal file
View File

@@ -0,0 +1,33 @@
HELP.md
target/
.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

10
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,10 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 已忽略包含查询文件的默认文件夹
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

18
.idea/compiler.xml generated Normal file
View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="pve-back-api" />
</profile>
</annotationProcessing>
</component>
<component name="JavacSettings">
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
<module name="pve-back-api" options="-parameters" />
</option>
</component>
</project>

6
.idea/encodings.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
</component>
</project>

View File

@@ -0,0 +1,23 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="VulnerableLibrariesLocal" enabled="true" level="WARNING" enabled_by_default="true">
<option name="isIgnoringEnabled" value="true" />
<option name="ignoredModules">
<list>
<option value="pve-back-api" />
</list>
</option>
<option name="ignoredPackages">
<list>
<option value="org.assertj:assertj-core:3.27.6" />
</list>
</option>
<option name="ignoredReasons">
<list>
<option value="进行中" />
</list>
</option>
</inspection_tool>
</profile>
</component>

20
.idea/jarRepositories.xml generated Normal file
View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Central Repository" />
<option name="url" value="https://repo.maven.apache.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
</component>
</project>

14
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,14 @@
<?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>
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="25" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

220
.idea/workspace.xml generated Normal file
View File

@@ -0,0 +1,220 @@
<?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="b81ac005-9b3d-43a0-a86e-8b9849200d36" name="更改" comment="">
<change afterPath="$PROJECT_DIR$/.gitattributes" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/.gitignore" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/codeStyles/codeStyleConfig.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/compiler.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/encodings.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/inspectionProfiles/Project_Default.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/jarRepositories.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/vcs.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.mvn/wrapper/maven-wrapper.properties" afterDir="false" />
<change afterPath="$PROJECT_DIR$/HELP.md" afterDir="false" />
<change afterPath="$PROJECT_DIR$/conf/config.yaml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/examples.md" afterDir="false" />
<change afterPath="$PROJECT_DIR$/fuck-u-code" afterDir="false" />
<change afterPath="$PROJECT_DIR$/fuck-u-code.exe" afterDir="false" />
<change afterPath="$PROJECT_DIR$/mvnw" afterDir="false" />
<change afterPath="$PROJECT_DIR$/mvnw.cmd" afterDir="false" />
<change afterPath="$PROJECT_DIR$/pom.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/top/gtb520/java/pve_back_api/Main.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/top/gtb520/java/pve_back_api/config/AppConfig.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/top/gtb520/java/pve_back_api/config/ConfigManager.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/top/gtb520/java/pve_back_api/config/GlobalConfig.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/top/gtb520/java/pve_back_api/config/YamlConfigLoader.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/top/gtb520/java/pve_back_api/route/pve/status.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/top/gtb520/java/pve_back_api/route/test.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/resources/application.properties" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/resources/config.yaml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/test/java/top/gtb520/java/pve_back_api/MainTests.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/test/java/top/gtb520/java/pve_back_api/ResourcesResultTest.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/target/classes/application.properties" afterDir="false" />
<change afterPath="$PROJECT_DIR$/target/classes/config.yaml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/target/classes/top/gtb520/java/pve_back_api/Main.class" afterDir="false" />
<change afterPath="$PROJECT_DIR$/target/classes/top/gtb520/java/pve_back_api/config/AppConfig.class" afterDir="false" />
<change afterPath="$PROJECT_DIR$/target/classes/top/gtb520/java/pve_back_api/config/ConfigManager.class" afterDir="false" />
<change afterPath="$PROJECT_DIR$/target/classes/top/gtb520/java/pve_back_api/config/GlobalConfig.class" afterDir="false" />
<change afterPath="$PROJECT_DIR$/target/classes/top/gtb520/java/pve_back_api/config/YamlConfigLoader.class" afterDir="false" />
<change afterPath="$PROJECT_DIR$/target/classes/top/gtb520/java/pve_back_api/route/pve/status.class" afterDir="false" />
<change afterPath="$PROJECT_DIR$/target/classes/top/gtb520/java/pve_back_api/route/test.class" afterDir="false" />
<change afterPath="$PROJECT_DIR$/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst" afterDir="false" />
<change afterPath="$PROJECT_DIR$/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst" afterDir="false" />
<change afterPath="$PROJECT_DIR$/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst" afterDir="false" />
<change afterPath="$PROJECT_DIR$/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst" afterDir="false" />
<change afterPath="$PROJECT_DIR$/target/surefire-reports/2026-02-04T18-14-48_524.dumpstream" afterDir="false" />
<change afterPath="$PROJECT_DIR$/target/surefire-reports/TEST-top.gtb520.java.pve_back_api.ResourcesResultTest.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/target/surefire-reports/top.gtb520.java.pve_back_api.ResourcesResultTest.txt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/target/test-classes/top/gtb520/java/pve_back_api/MainTests.class" afterDir="false" />
<change afterPath="$PROJECT_DIR$/target/test-classes/top/gtb520/java/pve_back_api/ResourcesResultTest.class" afterDir="false" />
</list>
<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="ComposerSettings">
<execution />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="Class" />
</list>
</option>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="KubernetesApiPersistence">{}</component>
<component name="KubernetesApiProvider">{
&quot;isMigrated&quot;: true
}</component>
<component name="MacroExpansionManager">
<option name="directoryName" value="IwU62d1q" />
</component>
<component name="MavenImportPreferences">
<option name="generalSettings">
<MavenGeneralSettings>
<option name="mavenHomeTypeForPersistence" value="WRAPPER" />
</MavenGeneralSettings>
</option>
</component>
<component name="MavenRunner">
<option name="skipTests" value="true" />
</component>
<component name="PhpWorkspaceProjectConfiguration" interpreter_name="php-7.4" />
<component name="ProjectCodeStyleSettingsMigration">
<option name="version" value="2" />
</component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 3
}</component>
<component name="ProjectId" id="397Avb8RVGvCmzdVVMJdi8C4ou0" />
<component name="ProjectViewState">
<option name="flattenModules" value="true" />
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
<option name="showMembers" value="true" />
<option name="showVisibilityIcons" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"JAR 应用程序.pve-back-api-0.0.1-SNAPSHOT.jar.executor": "Run",
"JUnit.ResourcesResultTest.executor": "Run",
"JUnit.ResourcesResultTest.test.executor": "Run",
"Maven.pve-back-api [clean].executor": "Run",
"Maven.pve-back-api [org.apache.maven.plugins:maven-install-plugin:3.1.4:install-file].executor": "Run",
"Maven.pve-back-api [org.apache.maven.plugins:maven-install-plugin:3.1.4:install].executor": "Run",
"Maven.pve-back-api [org.apache.maven.plugins:maven-jar-plugin:3.4.2:jar].executor": "Run",
"Maven.pve-back-api [package].executor": "Run",
"ModuleVcsDetector.initialDetectionPerformed": "true",
"RequestMappingsPanelOrder0": "0",
"RequestMappingsPanelOrder1": "1",
"RequestMappingsPanelWidth0": "75",
"RequestMappingsPanelWidth1": "75",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
"Spring Boot.Main.executor": "Run",
"git-widget-placeholder": "main",
"go.import.settings.migrated": "true",
"ignore.virus.scanning.warn.message": "true",
"kotlin-language-version-configured": "true",
"last_opened_file_path": "G:/Project/Java/pve-back-api",
"nodejs_package_manager_path": "npm",
"run.code.analysis.last.selected.profile": "pProject Default",
"settings.editor.selected.configurable": "preferences.pluginManager",
"vue.rearranger.settings.migration": "true"
}
}]]></component>
<component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="G:\Project\Java\pve-back-api" />
</key>
<key name="MoveClassesOrPackagesDialog.RECENTS_KEY">
<recent name="top.gtb520.java.pve_back_api.route" />
</key>
</component>
<component name="RunManager" selected="JUnit.ResourcesResultTest">
<configuration name="ResourcesResultTest" type="JUnit" factoryName="JUnit" temporary="true" nameIsGenerated="true">
<module name="pve-back-api" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="top.gtb520.java.pve_back_api.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="top.gtb520.java.pve_back_api" />
<option name="MAIN_CLASS_NAME" value="top.gtb520.java.pve_back_api.ResourcesResultTest" />
<option name="TEST_OBJECT" value="class" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
<configuration name="ResourcesResultTest.test" type="JUnit" factoryName="JUnit" temporary="true" nameIsGenerated="true">
<module name="pve-back-api" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="top.gtb520.java.pve_back_api.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="top.gtb520.java.pve_back_api" />
<option name="MAIN_CLASS_NAME" value="top.gtb520.java.pve_back_api.ResourcesResultTest" />
<option name="METHOD_NAME" value="test" />
<option name="TEST_OBJECT" value="method" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
<configuration name="pve-back-api-0.0.1-SNAPSHOT.jar" type="JarApplication" temporary="true">
<option name="JAR_PATH" value="$PROJECT_DIR$/target/pve-back-api-0.0.1-SNAPSHOT.jar" />
<method v="2" />
</configuration>
<configuration name="Main (1)" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
<module name="pve-back-api" />
<option name="SPRING_BOOT_MAIN_CLASS" value="top.gtb520.java.pve_back_api.Main" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
<configuration name="Main" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
<module name="pve-back-api" />
<option name="SPRING_BOOT_MAIN_CLASS" value="top.gtb520.java.pve_back_api.main.Main" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
<recent_temporary>
<list>
<item itemvalue="JUnit.ResourcesResultTest" />
<item itemvalue="JUnit.ResourcesResultTest.test" />
<item itemvalue="JAR 应用程序.pve-back-api-0.0.1-SNAPSHOT.jar" />
</list>
</recent_temporary>
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="默认任务">
<changelist id="b81ac005-9b3d-43a0-a86e-8b9849200d36" name="更改" comment="" />
<created>1770036968595</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1770036968595</updated>
<workItem from="1770036971569" duration="418000" />
<workItem from="1770037429449" duration="210000" />
<workItem from="1770037658639" duration="8615000" />
<workItem from="1770086564958" duration="25373000" />
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
</project>

3
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View File

@@ -0,0 +1,3 @@
wrapperVersion=3.3.4
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip

32
HELP.md Normal file
View File

@@ -0,0 +1,32 @@
# Getting Started
### Reference Documentation
For further reference, please consider the following sections:
* [Official Apache Maven documentation](https://maven.apache.org/guides/index.html)
* [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/4.0.2/maven-plugin)
* [Create an OCI image](https://docs.spring.io/spring-boot/4.0.2/maven-plugin/build-image.html)
* [Spring Data Redis (Access+Driver)](https://docs.spring.io/spring-boot/4.0.2/reference/data/nosql.html#data.nosql.redis)
* [JDBC API](https://docs.spring.io/spring-boot/4.0.2/reference/data/sql.html)
* [Spring Shell](https://docs.spring.io/spring-shell/reference/index.html)
* [WebSocket](https://docs.spring.io/spring-boot/4.0.2/reference/messaging/websockets.html)
### Guides
The following guides illustrate how to use some features concretely:
* [Messaging with Redis](https://spring.io/guides/gs/messaging-redis/)
* [Accessing Relational Data using JDBC with Spring](https://spring.io/guides/gs/relational-data-access/)
* [Managing Transactions](https://spring.io/guides/gs/managing-transactions/)
* [Accessing data with MySQL](https://spring.io/guides/gs/accessing-data-mysql/)
* [Using WebSocket to build an interactive web application](https://spring.io/guides/gs/messaging-stomp-websocket/)
### Maven Parent overrides
Due to Maven's design, elements are inherited from the parent POM to the project POM.
While most of the inheritance is fine, it also inherits unwanted elements like `<license>` and `<developers>` from the
parent.
To prevent this, the project POM contains empty overrides for these elements.
If you manually switch to a different parent and actually want the inheritance, you need to remove those overrides.

18
conf/config.yaml Normal file
View File

@@ -0,0 +1,18 @@
# 全局配置
global:
http_ip: "0.0.0.0"
http_port: "11554"
# 数据库配置
database:
mysql_jdbc: "jdbc:mysql://10.168.2.2:3306/pve_monitor?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&allowMultiQueries=true&useSSL=false&allowPublicKeyRetrieval=true&tinyInt1isBit=false&allowLoadLocalInfile=true&allowLocalInfile=true&allowUrlInLocalInfile=true&allowPublicKeyRetrieval=true&allowMultiQueries=true&allowPublicKeyRetrieval=true&allowLoadLocalInfile=true&allowUrlInLocalInfile=true&allowPublicKeyRetrieval=true&allowMultiQueries=true&allowPublicKeyRetrieval=true&allowLoadLocalInfile=true&allowUrlInLocal"
mysql_username: "root"
mysql_password: "Password"
# PVE配置
pve:
# PVE后端地址示例https://192.168.1.1:8006/ 协议HTTPS带结尾的“/”,不保留结尾其他参数)
url: "https://10.168.2.18:8006/"
api_tocken_name: "dev"
api_token_id: "root@pam!dev"
api_token_secret: "68e9dac5-3110-4f1b-b33a-feeaa4014f63"

431
examples.md Normal file
View File

@@ -0,0 +1,431 @@
# Basic Examples
This guide provides common usage patterns and practical examples for getting started with the Proxmox VE API.
## Getting Started
### **Basic Connection**
```java
import it.corsinvest.proxmoxve.api.*;
// Create client and authenticate
var client = new PveClient("pve.example.com", 8006);
client.setApiToken("user@pve!token=uuid");
// Test connection
var version = client.getVersion().version();
if (version.isSuccessStatusCode()) {
System.out.println("Connected to Proxmox VE " +
version.getResponse().get("data").get("version").asText());
}
```
### **Client Setup with Error Handling**
```java
public static PveClient createClient() {
var client = new PveClient("pve.local", 8006);
client.setValidateCertificate(false); // For development
client.setTimeout(120000); // 2 minutes
try {
// Use API token or login
String token = System.getenv("PVE_TOKEN");
if (token != null && !token.isEmpty()) {
client.setApiToken(token);
} else {
boolean success = client.login("root@pam", "password");
if (!success) {
throw new Exception("Authentication failed");
}
}
return client;
} catch (Exception ex) {
System.out.println("Failed to create client: " + ex.getMessage());
throw new RuntimeException(ex);
}
}
```
---
## Virtual Machine Operations
### **List Virtual Machines**
```java
// Get all VMs in cluster
var resources = client.getCluster().getResources().resources().getData();
for (JsonNode resource : resources) {
if ("qemu".equals(resource.get("type").asText())) {
System.out.printf("VM %d: %s on %s - %s%n",
resource.get("vmid").asInt(),
resource.get("name").asText(),
resource.get("node").asText(),
resource.get("status").asText());
}
}
// Filter running VMs
var runningVms = new ArrayList<JsonNode>();
for (JsonNode resource : resources) {
if ("qemu".equals(resource.get("type").asText()) &&
"running".equals(resource.get("status").asText())) {
runningVms.add(resource);
}
}
System.out.println("Running VMs: " + runningVms.size());
```
### **Get VM Configuration**
```java
// Get VM configuration
var config = client.getNodes().get("pve1").getQemu().get(100)
.getConfig().vmConfig().getData();
System.out.println("VM Name: " + config.get("name").asText());
System.out.println("Memory: " + config.get("memory").asInt() + " MB");
System.out.println("CPU Cores: " + config.get("cores").asInt());
System.out.println("Boot Order: " + config.get("boot").asText());
```
### **VM Power Management**
```java
var vm = client.getNodes().get("pve1").getQemu().get(100);
// Start VM
vm.getStatus().start();
System.out.println("VM started successfully");
// Stop VM
vm.getStatus().stop();
System.out.println("VM stopped successfully");
// Restart VM
vm.getStatus().reboot();
System.out.println("VM restarted successfully");
// Get current status
var data = vm.getStatus().current().getData();
System.out.println("VM Status: " + data.get("status").asText());
System.out.printf("CPU Usage: %.2f%%%n", data.get("cpu").asDouble() * 100);
System.out.printf("Memory: %.2f%%%n",
(data.get("mem").asDouble() / data.get("maxmem").asDouble()) * 100);
```
### **Snapshot Management**
```java
var vm = client.getNodes().get("pve1").getQemu().get(100);
// Create snapshot
vm.getSnapshot().snapshot("backup-2024", "Pre-update backup");
System.out.println("Snapshot created successfully");
// List snapshots
var snapshots = vm.getSnapshot().snapshotList().getData();
System.out.println("Available snapshots:");
for (JsonNode snapshot : snapshots) {
System.out.printf(" - %s: %s (%s)%n",
snapshot.get("name").asText(),
snapshot.get("description").asText(),
snapshot.get("snaptime").asText());
}
// Restore snapshot
vm.getSnapshot().get("backup-2024").rollback();
System.out.println("Snapshot restored successfully");
// Delete snapshot
vm.getSnapshot().get("backup-2024").delsnapshot();
System.out.println("Snapshot deleted successfully");
```
---
## Container Operations
### **List Containers**
```java
// Get all containers
var resources = client.getCluster().getResources().resources().getData();
for (JsonNode resource : resources) {
if ("lxc".equals(resource.get("type").asText())) {
System.out.printf("CT %d: %s on %s - %s%n",
resource.get("vmid").asInt(),
resource.get("name").asText(),
resource.get("node").asText(),
resource.get("status").asText());
}
}
```
### **Container Management**
```java
var container = client.getNodes().get("pve1").getLxc().get(101);
// Get container configuration
var ctConfig = container.getConfig().vmConfig().getData();
System.out.println("Container: " + ctConfig.get("hostname").asText());
System.out.println("OS Template: " + ctConfig.get("ostemplate").asText());
System.out.println("Memory: " + ctConfig.get("memory").asInt() + " MB");
// Start container
container.getStatus().start();
System.out.println("Container started");
// Get container status
var data = container.getStatus().current().getData();
System.out.println("Status: " + data.get("status").asText());
System.out.println("Uptime: " + data.get("uptime").asInt() + " seconds");
```
---
## Cluster Operations
### **Cluster Status**
```java
// Get cluster status
var status = client.getCluster().getStatus().getStatus().getData();
System.out.println("Cluster Status:");
for (JsonNode item : status) {
System.out.printf(" %s: %s - %s%n",
item.get("type").asText(),
item.get("name").asText(),
item.get("status").asText());
}
```
### **Node Information**
```java
// Get all nodes
var nodes = client.getNodes().index().getData();
System.out.println("Available Nodes:");
for (JsonNode node : nodes) {
System.out.println(" " + node.get("node").asText() + ": " +
node.get("status").asText());
System.out.printf(" CPU: %.2f%%%n", node.get("cpu").asDouble() * 100);
System.out.printf(" Memory: %.2f%%%n",
(node.get("mem").asDouble() / node.get("maxmem").asDouble()) * 100);
System.out.println(" Uptime: " +
java.time.Duration.ofSeconds(node.get("uptime").asInt()));
}
```
### **Storage Information**
```java
// Get storage for a specific node
var storages = client.getNodes().get("pve1").getStorage().index().getData();
System.out.println("Available Storage:");
for (JsonNode storage : storages) {
double usedPercent = (storage.get("used").asDouble() /
storage.get("total").asDouble()) * 100;
System.out.printf(" %s (%s): %.1f%% used%n",
storage.get("storage").asText(),
storage.get("type").asText(),
usedPercent);
System.out.printf(" Total: %.2f GB%n",
storage.get("total").asLong() / (1024.0 * 1024 * 1024));
System.out.printf(" Available: %.2f GB%n",
storage.get("avail").asLong() / (1024.0 * 1024 * 1024));
}
```
---
## Common Patterns
### **Resource Monitoring**
```java
public static void monitorResources(PveClient client) throws InterruptedException {
while (true) {
var resources = client.getCluster().getResources().resources().getData();
System.out.print("\033[H\033[2J"); // Clear console
System.out.flush();
System.out.println("Proxmox VE Resource Monitor - " +
java.time.LocalTime.now());
System.out.println("=".repeat(50));
// Count by type
int nodeCount = 0, vmCount = 0, ctCount = 0;
int runningVms = 0, runningCts = 0;
for (JsonNode resource : resources) {
String type = resource.get("type").asText();
String status = resource.get("status").asText();
switch (type) {
case "node":
nodeCount++;
System.out.printf(" %s: CPU %.1f%%, Memory %.1f%%%n",
resource.get("node").asText(),
resource.get("cpu").asDouble() * 100,
(resource.get("mem").asDouble() /
resource.get("maxmem").asDouble()) * 100);
break;
case "qemu":
vmCount++;
if ("running".equals(status)) runningVms++;
break;
case "lxc":
ctCount++;
if ("running".equals(status)) runningCts++;
break;
}
}
System.out.printf("\nNodes: %d%n", nodeCount);
System.out.printf("VMs: %d (%d running)%n", vmCount, runningVms);
System.out.printf("Containers: %d (%d running)%n", ctCount, runningCts);
Thread.sleep(5000); // Update every 5 seconds
}
}
```
### **Batch Operations**
```java
public static void batchVmOperation(PveClient client, int[] vmIds, String operation) {
var resources = client.getCluster().getResources().resources().getData();
for (int vmId : vmIds) {
// Find VM location
JsonNode vmResource = null;
for (JsonNode resource : resources) {
if ("qemu".equals(resource.get("type").asText()) &&
resource.get("vmid").asInt() == vmId) {
vmResource = resource;
break;
}
}
if (vmResource != null) {
String node = vmResource.get("node").asText();
var vm = client.getNodes().get(node).getQemu().get(vmId);
Result result = switch (operation.toLowerCase()) {
case "start" -> vm.getStatus().start();
case "stop" -> vm.getStatus().stop();
case "restart" -> vm.getStatus().reboot();
default -> throw new IllegalArgumentException("Unknown operation: " + operation);
};
boolean success = result.isSuccessStatusCode();
System.out.printf("VM %d %s: %s%n",
vmId, operation, success ? "Success" : "Failed");
}
}
}
```
### **Performance Monitoring**
```java
public static void getVmPerformance(PveClient client, String node, int vmId) {
var data = client.getNodes().get(node).getQemu().get(vmId)
.getStatus().current().getData();
System.out.println("VM " + vmId + " Performance:");
System.out.println(" Status: " + data.get("status").asText());
System.out.printf(" CPU Usage: %.2f%%%n", data.get("cpu").asDouble() * 100);
System.out.printf(" Memory: %.2f GB / %.2f GB (%.1f%%)%n",
data.get("mem").asLong() / (1024.0 * 1024 * 1024),
data.get("maxmem").asLong() / (1024.0 * 1024 * 1024),
(data.get("mem").asDouble() / data.get("maxmem").asDouble()) * 100);
System.out.printf(" Disk Read: %.2f MB%n",
data.get("diskread").asLong() / (1024.0 * 1024));
System.out.printf(" Disk Write: %.2f MB%n",
data.get("diskwrite").asLong() / (1024.0 * 1024));
System.out.printf(" Network In: %.2f MB%n",
data.get("netin").asLong() / (1024.0 * 1024));
System.out.printf(" Network Out: %.2f MB%n",
data.get("netout").asLong() / (1024.0 * 1024));
System.out.println(" Uptime: " +
java.time.Duration.ofSeconds(data.get("uptime").asInt()));
}
```
---
## Best Practices
### **Error Handling**
```java
public static boolean safeVmOperation(PveClient client, String node, int vmId, String operation) {
try {
var vm = client.getNodes().get(node).getQemu().get(vmId);
Result result = switch (operation.toLowerCase()) {
case "start" -> vm.getStatus().start();
case "stop" -> vm.getStatus().stop();
default -> throw new IllegalArgumentException("Unknown operation: " + operation);
};
if (result.isSuccessStatusCode()) {
System.out.println("VM " + vmId + " " + operation + " successful");
return true;
} else {
System.out.println("VM " + vmId + " " + operation + " failed: " +
result.getError());
return false;
}
} catch (Exception ex) {
System.out.println("Exception during " + operation + " on VM " + vmId + ": " +
ex.getMessage());
return false;
}
}
```
### **Resource Discovery**
```java
public static class VmLocation {
public String node;
public int vmId;
public VmLocation(String node, int vmId) {
this.node = node;
this.vmId = vmId;
}
}
public static VmLocation findVm(PveClient client, String vmName) {
var resources = client.getCluster().getResources().resources().getData();
for (JsonNode resource : resources) {
if ("qemu".equals(resource.get("type").asText()) &&
vmName.equalsIgnoreCase(resource.get("name").asText())) {
return new VmLocation(
resource.get("node").asText(),
resource.get("vmid").asInt()
);
}
}
return null;
}
// Usage
var vmLocation = findVm(client, "web-server");
if (vmLocation != null) {
var vm = client.getNodes().get(vmLocation.node).getQemu().get(vmLocation.vmId);
// ... work with VM
}
```

1
fuck-u-code Normal file
View File

@@ -0,0 +1 @@
unknown shorthand flag: 'G' in -G:\Project\Java\pve-back-api

BIN
fuck-u-code.exe Normal file

Binary file not shown.

295
mvnw vendored Normal file
View File

@@ -0,0 +1,295 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Apache Maven Wrapper startup batch script, version 3.3.4
#
# Optional ENV vars
# -----------------
# JAVA_HOME - location of a JDK home dir, required when download maven via java source
# MVNW_REPOURL - repo url base for downloading maven distribution
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
# ----------------------------------------------------------------------------
set -euf
[ "${MVNW_VERBOSE-}" != debug ] || set -x
# OS specific support.
native_path() { printf %s\\n "$1"; }
case "$(uname)" in
CYGWIN* | MINGW*)
[ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
native_path() { cygpath --path --windows "$1"; }
;;
esac
# set JAVACMD and JAVACCMD
set_java_home() {
# For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
if [ -n "${JAVA_HOME-}" ]; then
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACCMD="$JAVA_HOME/jre/sh/javac"
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACCMD="$JAVA_HOME/bin/javac"
if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
return 1
fi
fi
else
JAVACMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v java
)" || :
JAVACCMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v javac
)" || :
if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
return 1
fi
fi
}
# hash string like Java String::hashCode
hash_string() {
str="${1:-}" h=0
while [ -n "$str" ]; do
char="${str%"${str#?}"}"
h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
str="${str#?}"
done
printf %x\\n $h
}
verbose() { :; }
[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
die() {
printf %s\\n "$1" >&2
exit 1
}
trim() {
# MWRAPPER-139:
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
# Needed for removing poorly interpreted newline sequences when running in more
# exotic environments such as mingw bash on Windows.
printf "%s" "${1}" | tr -d '[:space:]'
}
scriptDir="$(dirname "$0")"
scriptName="$(basename "$0")"
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
while IFS="=" read -r key value; do
case "${key-}" in
distributionUrl) distributionUrl=$(trim "${value-}") ;;
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
esac
done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
case "${distributionUrl##*/}" in
maven-mvnd-*bin.*)
MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
*AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
:Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
:Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
:Linux*x86_64*) distributionPlatform=linux-amd64 ;;
*)
echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
distributionPlatform=linux-amd64
;;
esac
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
;;
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
esac
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
distributionUrlName="${distributionUrl##*/}"
distributionUrlNameMain="${distributionUrlName%.*}"
distributionUrlNameMain="${distributionUrlNameMain%-bin}"
MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
exec_maven() {
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
}
if [ -d "$MAVEN_HOME" ]; then
verbose "found existing MAVEN_HOME at $MAVEN_HOME"
exec_maven "$@"
fi
case "${distributionUrl-}" in
*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
esac
# prepare tmp dir
if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
trap clean HUP INT TERM EXIT
else
die "cannot create temp dir"
fi
mkdir -p -- "${MAVEN_HOME%/*}"
# Download and Install Apache Maven
verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
verbose "Downloading from: $distributionUrl"
verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
# select .zip or .tar.gz
if ! command -v unzip >/dev/null; then
distributionUrl="${distributionUrl%.zip}.tar.gz"
distributionUrlName="${distributionUrl##*/}"
fi
# verbose opt
__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
# normalize http auth
case "${MVNW_PASSWORD:+has-password}" in
'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
esac
if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
verbose "Found wget ... using wget"
wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
verbose "Found curl ... using curl"
curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
elif set_java_home; then
verbose "Falling back to use Java to download"
javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
cat >"$javaSource" <<-END
public class Downloader extends java.net.Authenticator
{
protected java.net.PasswordAuthentication getPasswordAuthentication()
{
return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
}
public static void main( String[] args ) throws Exception
{
setDefault( new Downloader() );
java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
}
}
END
# For Cygwin/MinGW, switch paths to Windows format before running javac and java
verbose " - Compiling Downloader.java ..."
"$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
verbose " - Running Downloader.java ..."
"$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
fi
# If specified, validate the SHA-256 sum of the Maven distribution zip file
if [ -n "${distributionSha256Sum-}" ]; then
distributionSha256Result=false
if [ "$MVN_CMD" = mvnd.sh ]; then
echo "Checksum validation is not supported for maven-mvnd." >&2
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
elif command -v sha256sum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then
distributionSha256Result=true
fi
elif command -v shasum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
distributionSha256Result=true
fi
else
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
fi
if [ $distributionSha256Result = false ]; then
echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
exit 1
fi
fi
# unzip and move
if command -v unzip >/dev/null; then
unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
else
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
fi
# Find the actual extracted directory name (handles snapshots where filename != directory name)
actualDistributionDir=""
# First try the expected directory name (for regular distributions)
if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
actualDistributionDir="$distributionUrlNameMain"
fi
fi
# If not found, search for any directory with the Maven executable (for snapshots)
if [ -z "$actualDistributionDir" ]; then
# enable globbing to iterate over items
set +f
for dir in "$TMP_DOWNLOAD_DIR"/*; do
if [ -d "$dir" ]; then
if [ -f "$dir/bin/$MVN_CMD" ]; then
actualDistributionDir="$(basename "$dir")"
break
fi
fi
done
set -f
fi
if [ -z "$actualDistributionDir" ]; then
verbose "Contents of $TMP_DOWNLOAD_DIR:"
verbose "$(ls -la "$TMP_DOWNLOAD_DIR")"
die "Could not find Maven distribution directory in extracted archive"
fi
verbose "Found extracted Maven distribution directory: $actualDistributionDir"
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
clean || :
exec_maven "$@"

189
mvnw.cmd vendored Normal file
View File

@@ -0,0 +1,189 @@
<# : batch portion
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Apache Maven Wrapper startup batch script, version 3.3.4
@REM
@REM Optional ENV vars
@REM MVNW_REPOURL - repo url base for downloading maven distribution
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
@REM ----------------------------------------------------------------------------
@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
@SET __MVNW_CMD__=
@SET __MVNW_ERROR__=
@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
@SET PSModulePath=
@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
)
@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
@SET __MVNW_PSMODULEP_SAVE=
@SET __MVNW_ARG0_NAME__=
@SET MVNW_USERNAME=
@SET MVNW_PASSWORD=
@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*)
@echo Cannot start maven from wrapper >&2 && exit /b 1
@GOTO :EOF
: end batch / begin powershell #>
$ErrorActionPreference = "Stop"
if ($env:MVNW_VERBOSE -eq "true") {
$VerbosePreference = "Continue"
}
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
if (!$distributionUrl) {
Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
}
switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
"maven-mvnd-*" {
$USE_MVND = $true
$distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
$MVN_CMD = "mvnd.cmd"
break
}
default {
$USE_MVND = $false
$MVN_CMD = $script -replace '^mvnw','mvn'
break
}
}
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
if ($env:MVNW_REPOURL) {
$MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" }
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')"
}
$distributionUrlName = $distributionUrl -replace '^.*/',''
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
$MAVEN_M2_PATH = "$HOME/.m2"
if ($env:MAVEN_USER_HOME) {
$MAVEN_M2_PATH = "$env:MAVEN_USER_HOME"
}
if (-not (Test-Path -Path $MAVEN_M2_PATH)) {
New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null
}
$MAVEN_WRAPPER_DISTS = $null
if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {
$MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists"
} else {
$MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists"
}
$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain"
$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
exit $?
}
if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
}
# prepare tmp dir
$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
trap {
if ($TMP_DOWNLOAD_DIR.Exists) {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
}
New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
# Download and Install Apache Maven
Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
Write-Verbose "Downloading from: $distributionUrl"
Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
$webclient = New-Object System.Net.WebClient
if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
$webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
# If specified, validate the SHA-256 sum of the Maven distribution zip file
$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
if ($distributionSha256Sum) {
if ($USE_MVND) {
Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
}
Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
}
}
# unzip and move
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
# Find the actual extracted directory name (handles snapshots where filename != directory name)
$actualDistributionDir = ""
# First try the expected directory name (for regular distributions)
$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {
$actualDistributionDir = $distributionUrlNameMain
}
# If not found, search for any directory with the Maven executable (for snapshots)
if (!$actualDistributionDir) {
Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
$testPath = Join-Path $_.FullName "bin/$MVN_CMD"
if (Test-Path -Path $testPath -PathType Leaf) {
$actualDistributionDir = $_.Name
}
}
}
if (!$actualDistributionDir) {
Write-Error "Could not find Maven distribution directory in extracted archive"
}
Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir"
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null
try {
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
} catch {
if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
Write-Error "fail to move MAVEN_HOME"
}
} finally {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"

91
pom.xml Normal file
View File

@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>top.gtb520.java.pve_back_api.main</groupId>
<artifactId>pve-back-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>pve-back-api</name>
<description>pve-back-api</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>25</java.version>
<spring-shell.version>4.0.1</spring-shell.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- YAML解析依赖 -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>2.0</version>
</dependency>
<!-- 日志依赖可选Spring Boot已包含 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<!-- 连接后端PVE的依赖-->
<dependency>
<groupId>it.corsinvest.proxmoxve</groupId>
<artifactId>cv4pve-api-java</artifactId>
<version>9.1.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,99 @@
package top.gtb520.java.pve_back_api;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import top.gtb520.java.pve_back_api.config.AppConfig;
import top.gtb520.java.pve_back_api.config.ConfigManager;
import java.util.Collection;
import java.util.Collections;
@SpringBootApplication
public class Main {
public static void main(String[] args) {
System.out.println("=== PVE后端API服务启动 ===");
// 初始化配置系统
initializeConfiguration();
// 执行初始化
Main main = new Main();
main.postInitialization();
// 启动Spring Boot应用
SpringApplication app = new SpringApplication(Main.class);
app.setDefaultProperties(Collections.singletonMap("server.port", AppConfig.HTTP_PORT));
app.run(args);
System.out.println("Spring框架加载完成!");
}
/**
* 初始化配置系统
*/
private static void initializeConfiguration() {
try {
String configPath = ConfigManager.initializeConfig();
if (configPath != null) {
System.out.println("配置系统初始化成功: " + configPath);
// 验证配置完整性
if (!AppConfig.validateRequiredConfig()) {
System.err.println("警告: 必要配置项不完整,请检查配置文件");
}
} else {
System.err.println("配置系统初始化失败!");
// 使用默认配置继续运行
AppConfig.markAsLoaded();
}
} catch (Exception e) {
System.err.println("配置初始化异常: " + e.getMessage());
e.printStackTrace();
// 即使配置失败也继续启动应用
AppConfig.markAsLoaded();
}
}
/**
* 应用启动后的初始化工作
*/
public void postInitialization() {
System.out.println("=== 应用初始化 ===");
if (AppConfig.isConfigLoaded()) {
System.out.println("配置状态: 已加载");
// 显示关键配置信息
System.out.println("服务地址: " + AppConfig.HTTP_IP + ":" + AppConfig.HTTP_PORT);
System.out.println("PVE地址: " + AppConfig.PVE_URL);
// 可以在这里添加其他初始化逻辑
performAdditionalInitialization();
} else {
System.out.println("配置状态: 未加载,使用默认配置");
}
System.out.println("应用初始化完成!");
}
/**
* 执行额外的初始化任务
*/
private void performAdditionalInitialization() {
// 这里可以添加数据库连接测试、PVE连接验证等
try {
// 示例:测试数据库连接配置
if (AppConfig.MYSQL_JDBC != null && !AppConfig.MYSQL_JDBC.isEmpty()) {
System.out.println("数据库配置已设置");
}
// 示例测试PVE配置
if (AppConfig.PVE_URL != null && !AppConfig.PVE_URL.isEmpty()) {
System.out.println("PVE配置已设置");
}
} catch (Exception e) {
System.err.println("初始化检查时发生错误: " + e.getMessage());
}
}
}

View File

@@ -0,0 +1,113 @@
package top.gtb520.java.pve_back_api.config;
/**
* 全局配置管理类
* 将配置文件中的配置项存储到全局静态变量中
*/
public class AppConfig {
// 全局配置
public static volatile String HTTP_IP = "0.0.0.0";
public static volatile String HTTP_PORT = "8080";
// 数据库配置
public static volatile String MYSQL_JDBC = "";
public static volatile String MYSQL_USERNAME = "";
public static volatile String MYSQL_PASSWORD = "";
// PVE配置 - 保留原有URL并新增拆解配置
public static volatile String PVE_URL = "";
public static volatile String PVE_HOSTNAME_IP = "";
public static volatile String PVE_PORT = "8006";
public static volatile String PVE_PROTOCOL = "https";
public static volatile String PVE_API_TOKEN_NAME = "";
public static volatile String PVE_API_TOKEN_ID = "";
public static volatile String PVE_API_TOKEN_SECRET = "";
// 配置状态
private static volatile boolean isLoaded = false;
private AppConfig() {
// 私有构造函数,防止实例化
}
/**
* 标记配置已加载
*/
public static void markAsLoaded() {
isLoaded = true;
}
/**
* 检查配置是否已加载
*/
public static boolean isConfigLoaded() {
return isLoaded;
}
/**
* 获取完整的PVE URL优先使用拆解后的配置构建
*/
public static String getPveFullUrl() {
// 如果有拆解后的配置优先使用它们构建URL
if (PVE_HOSTNAME_IP != null && !PVE_HOSTNAME_IP.isEmpty()) {
return PVE_PROTOCOL + "://" + PVE_HOSTNAME_IP + ":" + PVE_PORT + "/";
}
// 否则返回原始URL
return PVE_URL != null ? PVE_URL : "";
}
/**
* 获取完整的数据库连接URL包含用户名和密码
*/
public static String getFullJdbcUrl() {
if (MYSQL_JDBC == null || MYSQL_JDBC.isEmpty()) {
return "";
}
return MYSQL_JDBC + "?user=" + MYSQL_USERNAME + "&password=" + MYSQL_PASSWORD;
}
/**
* 验证必要配置是否完整
*/
public static boolean validateRequiredConfig() {
return HTTP_IP != null && !HTTP_IP.isEmpty() &&
HTTP_PORT != null && !HTTP_PORT.isEmpty() &&
(PVE_URL != null && !PVE_URL.isEmpty()) ||
(PVE_HOSTNAME_IP != null && !PVE_HOSTNAME_IP.isEmpty());
}
/**
* 打印当前配置信息
*/
public static void printCurrentConfig() {
System.out.println("=== 应用配置信息 ===");
System.out.println("HTTP 监听地址: " + HTTP_IP + ":" + HTTP_PORT);
System.out.println("MySQL 连接: " + (MYSQL_JDBC != null ? "已配置" : "未配置"));
System.out.println("PVE 原始URL: " + (PVE_URL != null ? PVE_URL : "未配置"));
System.out.println("PVE 主机: " + (PVE_HOSTNAME_IP != null ? PVE_HOSTNAME_IP : "未配置"));
System.out.println("PVE 端口: " + PVE_PORT);
System.out.println("PVE 协议: " + PVE_PROTOCOL);
System.out.println("配置加载状态: " + (isLoaded ? "已完成" : "未完成"));
System.out.println("==================");
}
/**
* 重置配置到默认值
*/
public static void resetToDefaults() {
HTTP_IP = "0.0.0.0";
HTTP_PORT = "8080";
MYSQL_JDBC = "";
MYSQL_USERNAME = "";
MYSQL_PASSWORD = "";
PVE_URL = "";
PVE_HOSTNAME_IP = "";
PVE_PORT = "8006";
PVE_PROTOCOL = "https";
PVE_API_TOKEN_NAME = "";
PVE_API_TOKEN_ID = "";
PVE_API_TOKEN_SECRET = "";
isLoaded = false;
}
}

View File

@@ -0,0 +1,405 @@
package top.gtb520.java.pve_back_api.config;
import org.yaml.snakeyaml.Yaml;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 配置文件管理器
* 负责配置文件的检测、创建、加载和解析
*/
public class ConfigManager {
private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private static final String CONFIG_TEMPLATE_PATH = "config.yaml";
private static final String CONFIG_DIR_NAME = "conf";
private static final String CONFIG_FILE_NAME = "config.yaml";
/**
* 初始化配置系统
* @return 配置文件路径
*/
public static String initializeConfig() {
lock.writeLock().lock();
try {
String jarDir = getApplicationDirectory();
Path configDir = Paths.get(jarDir, CONFIG_DIR_NAME);
Path configFile = configDir.resolve(CONFIG_FILE_NAME);
System.out.println("应用运行目录: " + jarDir);
// 确保配置目录存在
createConfigDirectory(configDir);
// 检查并创建配置文件
if (!Files.exists(configFile)) {
createDefaultConfigFile(configFile);
} else {
System.out.println("配置文件已存在: " + configFile.toString());
}
// 加载配置到全局变量
loadConfiguration(configFile.toString());
return configFile.toString();
} catch (Exception e) {
System.err.println("配置初始化失败: " + e.getMessage());
e.printStackTrace();
return null;
} finally {
lock.writeLock().unlock();
}
}
/**
* 获取应用运行目录
*/
private static String getApplicationDirectory() {
try {
String classPath = ConfigManager.class.getProtectionDomain()
.getCodeSource().getLocation().getPath();
classPath = java.net.URLDecoder.decode(classPath, "UTF-8");
File classFile = new File(classPath);
if (classFile.isFile() && classPath.endsWith(".jar")) {
return classFile.getParent();
} else if (classFile.isDirectory()) {
String projectDir = classFile.getAbsolutePath();
if (projectDir.contains("target" + File.separator + "classes")) {
return projectDir.substring(0, projectDir.indexOf("target"));
}
return projectDir;
}
} catch (Exception e) {
System.err.println("获取应用目录失败: " + e.getMessage());
}
return System.getProperty("user.dir");
}
/**
* 创建配置目录
*/
private static void createConfigDirectory(Path configDir) throws IOException {
if (!Files.exists(configDir)) {
Files.createDirectories(configDir);
System.out.println("创建配置目录: " + configDir.toString());
}
}
/**
* 创建默认配置文件
*/
private static void createDefaultConfigFile(Path configFile) throws IOException {
System.out.println("创建默认配置文件: " + configFile.toString());
try (InputStream templateStream = getResourceAsStream(CONFIG_TEMPLATE_PATH);
OutputStream outputStream = Files.newOutputStream(configFile)) {
if (templateStream == null) {
createMinimalConfigFile(configFile);
} else {
byte[] buffer = new byte[1024];
int length;
while ((length = templateStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, length);
}
System.out.println("从模板创建配置文件成功");
}
}
}
/**
* 创建最小化配置文件(当模板不可用时)
*/
private static void createMinimalConfigFile(Path configFile) throws IOException {
String minimalConfig = "# PVE后端API配置文件\n" +
"global:\n" +
" http_ip: \"0.0.0.0\"\n" +
" http_port: \"8080\"\n\n" +
"database:\n" +
" mysql_jdbc: \"\"\n" +
" mysql_username: \"\"\n" +
" mysql_password: \"\"\n\n" +
"pve:\n" +
" # 推荐使用拆分配置(优先级更高)\n" +
" hostname_ip: \"10.168.2.18\"\n" +
" port: \"8006\"\n" +
" protocol: \"https\"\n" +
" # 或者使用完整URL向后兼容\n" +
" url: \"https://10.168.2.18:8006/\"\n" +
" # API Token配置\n" +
" api_tocken_name: \"dev\"\n" +
" api_token_id: \"root@pam!dev\"\n" +
" api_token_secret: \"68e9dac5-3110-4f1b-b33a-feeaa4014f63\"\n";
Files.write(configFile, minimalConfig.getBytes("UTF-8"));
System.out.println("创建最小化配置文件成功");
}
/**
* 从资源获取输入流
*/
private static InputStream getResourceAsStream(String resourcePath) {
// 首先尝试从类路径获取
InputStream stream = ConfigManager.class.getClassLoader().getResourceAsStream(resourcePath);
if (stream != null) {
return stream;
}
// 如果类路径中没有,尝试从文件系统获取
try {
Path resourceFile = Paths.get(resourcePath);
if (Files.exists(resourceFile)) {
return Files.newInputStream(resourceFile);
}
} catch (IOException e) {
// 忽略文件系统访问异常
}
return null;
}
/**
* 加载配置文件到全局变量
* @param configFilePath 配置文件路径
*/
private static void loadConfiguration(String configFilePath) {
try {
Map<String, Object> configData = loadYamlConfig(configFilePath);
if (configData == null) {
handleEmptyConfig();
return;
}
// 解析并设置配置值
parseAndSetConfig(configData);
AppConfig.markAsLoaded();
System.out.println("✅ 配置加载成功!");
AppConfig.printCurrentConfig();
} catch (Exception e) {
handleConfigLoadError(e);
}
}
/**
* 加载YAML配置文件
*/
private static Map<String, Object> loadYamlConfig(String configFilePath) throws IOException {
Yaml yaml = new Yaml();
try (InputStream inputStream = Files.newInputStream(Paths.get(configFilePath))) {
return yaml.load(inputStream);
}
}
/**
* 处理空配置情况
*/
private static void handleEmptyConfig() {
System.err.println("❌ 配置文件为空或格式错误");
}
/**
* 处理配置加载错误
*/
private static void handleConfigLoadError(Exception e) {
System.err.println("❌ 加载配置文件失败: " + e.getMessage());
e.printStackTrace();
}
/**
* 解析配置数据并设置到全局变量
* @param data 配置数据映射
*/
private static void parseAndSetConfig(Map<String, Object> data) {
try {
parseGlobalSection(data);
parseDatabaseSection(data);
parsePveSection(data);
} catch (Exception e) {
System.err.println("❌ 解析配置数据时发生错误: " + e.getMessage());
throw e;
}
}
/**
* 解析全局配置部分
*/
private static void parseGlobalSection(Map<String, Object> data) {
if (!data.containsKey("global")) {
return;
}
@SuppressWarnings("unchecked")
Map<String, Object> global = (Map<String, Object>) data.get("global");
AppConfig.HTTP_IP = getStringValue(global, "http_ip", AppConfig.HTTP_IP);
AppConfig.HTTP_PORT = getStringValue(global, "http_port", AppConfig.HTTP_PORT);
}
/**
* 解析数据库配置部分
*/
private static void parseDatabaseSection(Map<String, Object> data) {
if (!data.containsKey("database")) {
return;
}
@SuppressWarnings("unchecked")
Map<String, Object> database = (Map<String, Object>) data.get("database");
AppConfig.MYSQL_JDBC = getStringValue(database, "mysql_jdbc", AppConfig.MYSQL_JDBC);
AppConfig.MYSQL_USERNAME = getStringValue(database, "mysql_username", AppConfig.MYSQL_USERNAME);
AppConfig.MYSQL_PASSWORD = getStringValue(database, "mysql_password", AppConfig.MYSQL_PASSWORD);
}
/**
* 解析PVE配置部分
*/
private static void parsePveSection(Map<String, Object> data) {
if (!data.containsKey("pve")) {
return;
}
@SuppressWarnings("unchecked")
Map<String, Object> pve = (Map<String, Object>) data.get("pve");
processPveUrlConfig(pve);
processPveApiTokens(pve);
}
/**
* 处理PVE URL相关配置
*/
private static void processPveUrlConfig(Map<String, Object> pve) {
// 保留原始URL配置
AppConfig.PVE_URL = getStringValue(pve, "url", AppConfig.PVE_URL);
// 解析拆分后的PVE配置优先级更高
AppConfig.PVE_HOSTNAME_IP = getStringValue(pve, "hostname_ip", AppConfig.PVE_HOSTNAME_IP);
AppConfig.PVE_PORT = getStringValue(pve, "port", AppConfig.PVE_PORT);
AppConfig.PVE_PROTOCOL = getStringValue(pve, "protocol", AppConfig.PVE_PROTOCOL);
// 如果提供了拆分配置同时更新URL
if (hasValidHostname()) {
AppConfig.PVE_URL = buildPveUrl();
}
// 如果只有URL而没有拆分配置则解析URL
else if (hasValidUrl()) {
parsePveUrl(AppConfig.PVE_URL);
}
}
/**
* 处理PVE API令牌配置
*/
private static void processPveApiTokens(Map<String, Object> pve) {
AppConfig.PVE_API_TOKEN_NAME = getStringValue(pve, "api_tocken_name", AppConfig.PVE_API_TOKEN_NAME);
AppConfig.PVE_API_TOKEN_ID = getStringValue(pve, "api_token_id", AppConfig.PVE_API_TOKEN_ID);
AppConfig.PVE_API_TOKEN_SECRET = getStringValue(pve, "api_token_secret", AppConfig.PVE_API_TOKEN_SECRET);
}
/**
* 检查是否有有效的主机名配置
*/
private static boolean hasValidHostname() {
return AppConfig.PVE_HOSTNAME_IP != null && !AppConfig.PVE_HOSTNAME_IP.isEmpty();
}
/**
* 检查是否有有效的URL配置
*/
private static boolean hasValidUrl() {
return AppConfig.PVE_URL != null && !AppConfig.PVE_URL.isEmpty();
}
/**
* 构建PVE完整URL
*/
private static String buildPveUrl() {
return AppConfig.PVE_PROTOCOL + "://" + AppConfig.PVE_HOSTNAME_IP + ":" + AppConfig.PVE_PORT + "/";
}
/**
* 从完整URL解析PVE主机和端口信息
*/
private static void parsePveUrl(String url) {
try {
if (url == null || url.isEmpty()) {
return;
}
// 移除末尾的斜杠
if (url.endsWith("/")) {
url = url.substring(0, url.length() - 1);
}
// 解析协议
if (url.startsWith("https://")) {
AppConfig.PVE_PROTOCOL = "https";
url = url.substring(8);
} else if (url.startsWith("http://")) {
AppConfig.PVE_PROTOCOL = "http";
url = url.substring(7);
}
// 解析主机和端口
int portIndex = url.lastIndexOf(":");
if (portIndex > 0) {
AppConfig.PVE_HOSTNAME_IP = url.substring(0, portIndex);
String portStr = url.substring(portIndex + 1);
if (!portStr.isEmpty()) {
AppConfig.PVE_PORT = portStr;
}
} else {
AppConfig.PVE_HOSTNAME_IP = url;
}
} catch (Exception e) {
System.err.println("解析PVE URL时发生错误: " + e.getMessage());
}
}
/**
* 安全获取字符串值
*/
private static String getStringValue(Map<String, Object> map, String key, String defaultValue) {
if (map.containsKey(key)) {
Object value = map.get(key);
return value != null ? value.toString() : defaultValue;
}
return defaultValue;
}
/**
* 重新加载配置
*/
public static boolean reloadConfiguration() {
lock.writeLock().lock();
try {
String jarDir = getApplicationDirectory();
Path configFile = Paths.get(jarDir, CONFIG_DIR_NAME, CONFIG_FILE_NAME);
if (Files.exists(configFile)) {
AppConfig.resetToDefaults();
loadConfiguration(configFile.toString());
return true;
} else {
System.err.println("配置文件不存在,无法重新加载");
return false;
}
} catch (Exception e) {
System.err.println("重新加载配置失败: " + e.getMessage());
return false;
} finally {
lock.writeLock().unlock();
}
}
}

View File

@@ -0,0 +1,46 @@
package top.gtb520.java.pve_back_api.config;
import org.yaml.snakeyaml.Yaml;
import java.io.InputStream;
import java.util.Map;
/**
* 全局配置类
* 存储所有配置项的全局变量
*/
public class GlobalConfig {
// 全局配置
public static String HTTP_IP = "0.0.0.0";
public static String HTTP_PORT = "8080";
// 数据库配置
public static String MYSQL_JDBC = "";
public static String MYSQL_USERNAME = "";
public static String MYSQL_PASSWORD = "";
// PVE配置
public static String PVE_URL = "";
public static String PVE_API_TOKEN_NAME = "";
public static String PVE_API_TOKEN_ID = "";
public static String PVE_API_TOKEN_SECRET = "";
// 私有构造函数,防止实例化
private GlobalConfig() {}
/**
* 打印所有配置信息(用于调试)
*/
public static void printConfig() {
System.out.println("=== 当前配置信息 ===");
System.out.println("HTTP IP: " + HTTP_IP);
System.out.println("HTTP PORT: " + HTTP_PORT);
System.out.println("MYSQL JDBC: " + MYSQL_JDBC);
System.out.println("MYSQL USERNAME: " + MYSQL_USERNAME);
System.out.println("PVE URL: " + PVE_URL);
System.out.println("PVE API TOKEN NAME: " + PVE_API_TOKEN_NAME);
System.out.println("PVE API TOKEN ID: " + PVE_API_TOKEN_ID);
System.out.println("===================");
}
}

View File

@@ -0,0 +1,148 @@
package top.gtb520.java.pve_back_api.config;
import org.yaml.snakeyaml.Yaml;
import java.io.InputStream;
import java.util.Map;
/**
* YAML配置文件解析器
* 负责从不同来源加载和解析YAML配置文件
*/
public class YamlConfigLoader {
private static final String GLOBAL_SECTION = "global";
private static final String DATABASE_SECTION = "database";
private static final String PVE_SECTION = "pve";
/**
* 从资源文件加载配置
* @param resourcePath 资源文件路径
*/
public static void loadConfigFromResource(String resourcePath) {
try {
Yaml yaml = new Yaml();
InputStream inputStream = YamlConfigLoader.class.getClassLoader().getResourceAsStream(resourcePath);
if (inputStream == null) {
handleResourceNotFound(resourcePath);
return;
}
Map<String, Object> data = yaml.load(inputStream);
parseConfig(data);
} catch (Exception e) {
handleLoadException("资源", resourcePath, e);
}
}
/**
* 从文件路径加载配置
* @param filePath 文件路径
*/
public static void loadConfigFromFile(String filePath) {
try {
Yaml yaml = new Yaml();
InputStream inputStream = java.nio.file.Files.newInputStream(java.nio.file.Paths.get(filePath));
Map<String, Object> data = yaml.load(inputStream);
parseConfig(data);
} catch (Exception e) {
handleLoadException("文件", filePath, e);
}
}
/**
* 解析配置数据并设置到全局变量
* @param data 配置数据映射
*/
@SuppressWarnings("unchecked")
private static void parseConfig(Map<String, Object> data) {
try {
parseGlobalConfig(data);
parseDatabaseConfig(data);
parsePveConfig(data);
System.out.println("配置加载成功!");
GlobalConfig.printConfig();
} catch (Exception e) {
System.err.println("解析配置数据时发生错误: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 解析全局配置部分
*/
private static void parseGlobalConfig(Map<String, Object> data) {
if (!data.containsKey(GLOBAL_SECTION)) {
return;
}
@SuppressWarnings("unchecked")
Map<String, Object> global = (Map<String, Object>) data.get(GLOBAL_SECTION);
GlobalConfig.HTTP_IP = getStringValue(global, "http_ip", GlobalConfig.HTTP_IP);
GlobalConfig.HTTP_PORT = getStringValue(global, "http_port", GlobalConfig.HTTP_PORT);
}
/**
* 解析数据库配置部分
*/
private static void parseDatabaseConfig(Map<String, Object> data) {
if (!data.containsKey(DATABASE_SECTION)) {
return;
}
@SuppressWarnings("unchecked")
Map<String, Object> database = (Map<String, Object>) data.get(DATABASE_SECTION);
GlobalConfig.MYSQL_JDBC = getStringValue(database, "mysql_jdbc", GlobalConfig.MYSQL_JDBC);
GlobalConfig.MYSQL_USERNAME = getStringValue(database, "mysql_username", GlobalConfig.MYSQL_USERNAME);
GlobalConfig.MYSQL_PASSWORD = getStringValue(database, "mysql_password", GlobalConfig.MYSQL_PASSWORD);
}
/**
* 解析PVE配置部分
*/
private static void parsePveConfig(Map<String, Object> data) {
if (!data.containsKey(PVE_SECTION)) {
return;
}
@SuppressWarnings("unchecked")
Map<String, Object> pve = (Map<String, Object>) data.get(PVE_SECTION);
GlobalConfig.PVE_URL = getStringValue(pve, "url", GlobalConfig.PVE_URL);
GlobalConfig.PVE_API_TOKEN_NAME = getStringValue(pve, "api_tocken_name", GlobalConfig.PVE_API_TOKEN_NAME);
GlobalConfig.PVE_API_TOKEN_ID = getStringValue(pve, "api_token_id", GlobalConfig.PVE_API_TOKEN_ID);
GlobalConfig.PVE_API_TOKEN_SECRET = getStringValue(pve, "api_token_secret", GlobalConfig.PVE_API_TOKEN_SECRET);
}
/**
* 安全获取字符串值
*/
private static String getStringValue(Map<String, Object> map, String key, String defaultValue) {
if (map.containsKey(key)) {
Object value = map.get(key);
return value != null ? value.toString() : defaultValue;
}
return defaultValue;
}
/**
* 处理资源未找到的情况
*/
private static void handleResourceNotFound(String resourcePath) {
System.err.println("❌ 无法找到资源配置文件: " + resourcePath);
}
/**
* 处理加载异常
*/
private static void handleLoadException(String type, String path, Exception e) {
System.err.println("❌ 解析" + type + "配置文件失败: " + path);
System.err.println("错误详情: " + e.getMessage());
e.printStackTrace();
}
}

View File

@@ -0,0 +1,168 @@
package top.gtb520.java.pve_back_api.route.pve;
import it.corsinvest.proxmoxve.api.*;
import org.springframework.web.bind.annotation.GetMapping;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.web.bind.annotation.RestController;
// 导入配置变量
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static top.gtb520.java.pve_back_api.config.AppConfig.*;
/**
* PVE状态相关路由处理器
* 提供与Proxmox VE服务器通信的客户端创建功能和状态查询
*/
@RestController
public class status {
private static final int DEFAULT_TIMEOUT = 120000; // 2分钟超时时间
/**
* API/api/pve/status
* 类型GET
* 获取PVE集群状态信息包括节点、虚拟机、容器等资源状态
*
* @return 包含集群状态信息的列表
* @throws Exception 当获取状态失败时抛出
*/
@GetMapping("/api/pve/status")
public List<Map<String, Object>> GetPveStatus() throws Exception {
System.out.println("开始获取PVE状态信息...");
List<Map<String, Object>> pveStatus = new ArrayList<>();
try {
// 创建PVE客户端
PveClient client = createClient();
// 获取集群资源信息
var resourcesResult = client.getCluster().getResources().resources();
if (resourcesResult.isSuccessStatusCode()) {
JsonNode resources = resourcesResult.getResponse().get("data");
// 统计各类资源
int nodeCount = 0, vmCount = 0, ctCount = 0;
int runningNodes = 0, runningVms = 0, runningCts = 0;
// 处理每个资源
for (JsonNode resource : resources) {
String type = resource.get("type").asText();
String status = resource.get("status").asText();
Map<String, Object> resourceInfo = new HashMap<>();
resourceInfo.put("type", type);
resourceInfo.put("status", status);
switch (type) {
case "node":
nodeCount++;
if ("online".equals(status)) runningNodes++;
resourceInfo.put("name", resource.get("node").asText());
resourceInfo.put("cpu_usage", String.format("%.2f%%",
resource.get("cpu").asDouble() * 100));
resourceInfo.put("memory_usage", String.format("%.2f%%",
(resource.get("mem").asDouble() / resource.get("maxmem").asDouble()) * 100));
resourceInfo.put("uptime", formatUptime(resource.get("uptime").asInt()));
break;
case "qemu":
vmCount++;
if ("running".equals(status)) runningVms++;
resourceInfo.put("vmid", resource.get("vmid").asInt());
resourceInfo.put("name", resource.get("name").asText());
resourceInfo.put("node", resource.get("node").asText());
break;
case "lxc":
ctCount++;
if ("running".equals(status)) runningCts++;
resourceInfo.put("vmid", resource.get("vmid").asInt());
resourceInfo.put("name", resource.get("name").asText());
resourceInfo.put("node", resource.get("node").asText());
break;
}
pveStatus.add(resourceInfo);
}
// 添加统计信息
Map<String, Object> summary = new HashMap<>();
summary.put("summary", true);
summary.put("total_nodes", nodeCount);
summary.put("running_nodes", runningNodes);
summary.put("total_vms", vmCount);
summary.put("running_vms", runningVms);
summary.put("total_containers", ctCount);
summary.put("running_containers", runningCts);
summary.put("timestamp", System.currentTimeMillis());
pveStatus.add(summary);
System.out.println("✅ 成功获取PVE状态信息");
System.out.printf("📊 统计: 节点%d(%d运行), VM%d(%d运行), 容器%d(%d运行)%n",
nodeCount, runningNodes, vmCount, runningVms, ctCount, runningCts);
} else {
// 更安全的错误处理
String errorMsg = "未知错误";
try {
if (resourcesResult != null) {
errorMsg = resourcesResult.getError();
} else {
errorMsg = "API调用返回null结果";
}
} catch (Exception e) {
errorMsg = "错误处理异常: " + e.getClass().getSimpleName() + " - " + e.getMessage();
}
throw new Exception("获取集群资源失败: " + errorMsg);
}
} catch (Exception e) {
System.err.println("❌ 获取PVE状态失败: " + e.getMessage());
throw new Exception("获取PVE状态失败: " + e.getMessage(), e);
}
return pveStatus;
}
/**
* 格式化运行时间
*/
private static String formatUptime(int seconds) {
int days = seconds / 86400;
int hours = (seconds % 86400) / 3600;
int minutes = (seconds % 3600) / 60;
StringBuilder sb = new StringBuilder();
if (days > 0) sb.append(days).append("");
if (hours > 0) sb.append(hours).append("小时 ");
if (minutes > 0) sb.append(minutes).append("分钟");
return sb.toString().trim();
}
/**
* 创建PVE客户端实例
* 支持API令牌认证和传统登录认证两种方式
*
* @return 配置好的PveClient实例
* @throws RuntimeException 当客户端创建失败时抛出
*/
public static PveClient createClient() throws RuntimeException {
// 构建API_TOKEN字符串
String[] api_token_list = PVE_API_TOKEN_ID.split("!");
String api_token = api_token_list[0] + ":" + api_token_list[1] + "@" + "token=" + PVE_API_TOKEN_SECRET;
System.out.println("API_TOKEN: " + api_token);
// 创建PVE客户端实例
var client = new PveClient(PVE_HOSTNAME_IP, Integer.parseInt(PVE_PORT));
client.setValidateCertificate(false); // SSL证书验证
client.setTimeout(DEFAULT_TIMEOUT); // 设置请求超时时间
client.setApiToken(api_token);
return client;
}
}

View File

@@ -0,0 +1,12 @@
package top.gtb520.java.pve_back_api.route;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class test {
@GetMapping("/test")
public String test() {
return "test";
}
}

View File

@@ -0,0 +1 @@
spring.application.name=pve-back-api

View File

@@ -0,0 +1,18 @@
# 全局配置
global:
http_ip: "0.0.0.0"
http_port: "8080"
# 数据库配置
database:
mysql_jdbc: "jdbc:mysql://10.168.2.2:3306/pve_monitor?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&allowMultiQueries=true&useSSL=false&allowPublicKeyRetrieval=true&tinyInt1isBit=false&allowLoadLocalInfile=true&allowLocalInfile=true&allowUrlInLocalInfile=true&allowPublicKeyRetrieval=true&allowMultiQueries=true&allowPublicKeyRetrieval=true&allowLoadLocalInfile=true&allowUrlInLocalInfile=true&allowPublicKeyRetrieval=true&allowMultiQueries=true&allowPublicKeyRetrieval=true&allowLoadLocalInfile=true&allowUrlInLocal"
mysql_username: "root"
mysql_password: "Password"
# PVE配置
pve:
# PVE后端地址示例https://192.168.1.1:8006/ 协议HTTPS带结尾的“/”,不保留结尾其他参数)
url: "https://10.168.2.18:8006/"
api_tocken_name: "dev"
api_token_id: "root@pam!dev"
api_token_secret: "68e9dac5-3110-4f1b-b33a-feeaa4014f63"

View File

@@ -0,0 +1,56 @@
package top.gtb520.java.pve_back_api;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import top.gtb520.java.pve_back_api.config.AppConfig;
import top.gtb520.java.pve_back_api.config.ConfigManager;
import top.gtb520.java.pve_back_api.config.YamlConfigLoader;
import top.gtb520.java.pve_back_api.route.pve.status;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class MainTests {
@Test
void contextLoads() {
}
@Test
void testAppConfigInitialization() {
// 测试配置类的基本功能
assertNotNull(AppConfig.HTTP_IP);
assertNotNull(AppConfig.HTTP_PORT);
assertFalse(AppConfig.isConfigLoaded());
}
@Test
void testConfigManagerMethodsExist() {
// 测试ConfigManager类的方法是否存在
assertTrue(ConfigManager.class.getDeclaredMethods().length > 0);
}
@Test
void testYamlConfigLoaderMethodsExist() {
// 测试YamlConfigLoader类的方法是否存在
assertTrue(YamlConfigLoader.class.getDeclaredMethods().length > 0);
}
@Test
void testStatusClassExists() {
// 测试status类是否存在createClient方法
assertNotNull(status.class.getDeclaredMethods());
}
@Test
void testPveUrlBuilding() {
// 测试PVE URL构建逻辑
AppConfig.PVE_PROTOCOL = "https";
AppConfig.PVE_HOSTNAME_IP = "192.168.1.100";
AppConfig.PVE_PORT = "8006";
String expectedUrl = "https://192.168.1.100:8006/";
assertEquals(expectedUrl, AppConfig.getPveFullUrl());
}
}

View File

@@ -0,0 +1,79 @@
package top.gtb520.java.pve_back_api;
import org.junit.jupiter.api.Test;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import reactor.netty.http.client.HttpClient;
import reactor.netty.tcp.TcpClient;
public class ResourcesResultTest {
/**
* 创建忽略SSL证书验证的WebClient实例
* 用于测试环境或自签名证书场景
*/
private static WebClient createInsecureWebClient() {
try {
// 使用Netty内置的不安全信任管理器工厂
TcpClient tcpClient = TcpClient.create()
.secure(sslContextSpec -> {
try {
sslContextSpec.sslContext(
io.netty.handler.ssl.SslContextBuilder.forClient()
.trustManager(io.netty.handler.ssl.util.InsecureTrustManagerFactory.INSTANCE)
.build()
);
} catch (Exception e) {
throw new RuntimeException("SSL配置失败", e);
}
});
// 创建HttpClient和连接器
HttpClient httpClient = HttpClient.from(tcpClient);
ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
// 构建并返回WebClient
return WebClient.builder()
.clientConnector(connector)
.build();
} catch (Exception e) {
throw new RuntimeException("创建WebClient失败", e);
}
}
@Test
void testHttpsRequest() {
System.out.println("开始发送HTTPS请求...");
try {
// 创建忽略证书验证的WebClient
WebClient client = createInsecureWebClient();
// 发送HTTPS GET请求
Mono<String> result = client.get()
.uri("https://10.168.2.2:8013/")
.retrieve()
.bodyToMono(String.class);
// 阻塞获取响应结果
String response = result.block();
System.out.println("✅ 请求成功!");
System.out.println("响应内容长度: " + (response != null ? response.length() : 0) + " 字符");
// 如果需要查看完整响应内容,取消下面的注释
// System.out.println("响应内容: " + response);
} catch (Exception e) {
System.err.println("❌ HTTPS请求失败: " + e.getMessage());
System.err.println("可能的原因:");
System.err.println("1. 目标服务器不可达");
System.err.println("2. 端口未开放");
System.err.println("3. 网络连接问题");
System.err.println("4. SSL配置问题");
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1 @@
spring.application.name=pve-back-api

View File

@@ -0,0 +1,18 @@
# 全局配置
global:
http_ip: "0.0.0.0"
http_port: "8080"
# 数据库配置
database:
mysql_jdbc: "jdbc:mysql://10.168.2.2:3306/pve_monitor?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&allowMultiQueries=true&useSSL=false&allowPublicKeyRetrieval=true&tinyInt1isBit=false&allowLoadLocalInfile=true&allowLocalInfile=true&allowUrlInLocalInfile=true&allowPublicKeyRetrieval=true&allowMultiQueries=true&allowPublicKeyRetrieval=true&allowLoadLocalInfile=true&allowUrlInLocalInfile=true&allowPublicKeyRetrieval=true&allowMultiQueries=true&allowPublicKeyRetrieval=true&allowLoadLocalInfile=true&allowUrlInLocal"
mysql_username: "root"
mysql_password: "Password"
# PVE配置
pve:
# PVE后端地址示例https://192.168.1.1:8006/ 协议HTTPS带结尾的“/”,不保留结尾其他参数)
url: "https://10.168.2.18:8006/"
api_tocken_name: "dev"
api_token_id: "root@pam!dev"
api_token_secret: "68e9dac5-3110-4f1b-b33a-feeaa4014f63"

Binary file not shown.

View File

@@ -0,0 +1,7 @@
top\gtb520\java\pve_back_api\route\pve\status.class
top\gtb520\java\pve_back_api\config\ConfigManager.class
top\gtb520\java\pve_back_api\config\GlobalConfig.class
top\gtb520\java\pve_back_api\Main.class
top\gtb520\java\pve_back_api\config\AppConfig.class
top\gtb520\java\pve_back_api\config\YamlConfigLoader.class
top\gtb520\java\pve_back_api\route\test.class

View File

@@ -0,0 +1,7 @@
G:\Project\Java\pve-back-api\src\main\java\top\gtb520\java\pve_back_api\config\AppConfig.java
G:\Project\Java\pve-back-api\src\main\java\top\gtb520\java\pve_back_api\config\ConfigManager.java
G:\Project\Java\pve-back-api\src\main\java\top\gtb520\java\pve_back_api\config\GlobalConfig.java
G:\Project\Java\pve-back-api\src\main\java\top\gtb520\java\pve_back_api\config\YamlConfigLoader.java
G:\Project\Java\pve-back-api\src\main\java\top\gtb520\java\pve_back_api\Main.java
G:\Project\Java\pve-back-api\src\main\java\top\gtb520\java\pve_back_api\route\pve\status.java
G:\Project\Java\pve-back-api\src\main\java\top\gtb520\java\pve_back_api\route\test.java

View File

@@ -0,0 +1,2 @@
top\gtb520\java\pve_back_api\MainTests.class
top\gtb520\java\pve_back_api\ResourcesResultTest.class

View File

@@ -0,0 +1,2 @@
G:\Project\Java\pve-back-api\src\test\java\top\gtb520\java\pve_back_api\MainTests.java
G:\Project\Java\pve-back-api\src\test\java\top\gtb520\java\pve_back_api\ResourcesResultTest.java

View File

@@ -0,0 +1,5 @@
# Created at 2026-02-04T18:14:51.799
Boot Manifest-JAR contains absolute paths in classpath 'G:\Project\Java\pve-back-api\target\test-classes'
Hint: <argLine>-Djdk.net.URLClassPath.disableClassPathURLCheck=true</argLine>
'other' has different root

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,4 @@
-------------------------------------------------------------------------------
Test set: top.gtb520.java.pve_back_api.ResourcesResultTest
-------------------------------------------------------------------------------
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.140 s -- in top.gtb520.java.pve_back_api.ResourcesResultTest