Compare commits
10 Commits
cd7bc31e6b
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b46e9079d5 | ||
|
|
dc20070bf8 | ||
|
|
78163441dd | ||
|
|
9762be1244 | ||
|
|
971cad7365 | ||
|
|
b5e9da0e9b | ||
|
|
34e1ac6264 | ||
|
|
ef192daa93 | ||
|
|
8947269351 | ||
|
|
b6a58b7376 |
333
.gitea/workflows/ci.yml
Normal file
333
.gitea/workflows/ci.yml
Normal file
@@ -0,0 +1,333 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
types:
|
||||
- closed
|
||||
|
||||
jobs:
|
||||
build-deploy:
|
||||
runs-on: act_runner_java
|
||||
if: ${{ github.event.pull_request.merged == true }}
|
||||
outputs:
|
||||
deployment_status: ${{ steps.set_status.outputs.status }}
|
||||
env:
|
||||
JAVA_HOME: /usr/lib/jvm/java-21-openjdk
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up environment
|
||||
run: |
|
||||
echo "PR #${{ github.event.number }} merged into master"
|
||||
echo "Source branch: ${{ github.event.pull_request.head.ref }}"
|
||||
echo "Target branch: ${{ github.event.pull_request.base.ref }}"
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
echo "Running test suite..."
|
||||
|
||||
- name: Setup Maven settings
|
||||
run: |
|
||||
if [ -z "${{ vars.TIMI_NEXUS_USERNAME }}" ] || [ -z "${{ vars.TIMI_NEXUS_PASSWORD }}" ]; then
|
||||
echo "Missing vars.TIMI_NEXUS_USERNAME or vars.TIMI_NEXUS_PASSWORD"
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p ~/.m2
|
||||
cat > ~/.m2/settings.xml <<EOF
|
||||
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
|
||||
<servers>
|
||||
<server>
|
||||
<id>timi_nexus</id>
|
||||
<username>${{ vars.TIMI_NEXUS_USERNAME }}</username>
|
||||
<password>${{ vars.TIMI_NEXUS_PASSWORD }}</password>
|
||||
</server>
|
||||
</servers>
|
||||
</settings>
|
||||
EOF
|
||||
|
||||
- name: Build project
|
||||
run: |
|
||||
mvn -B -DskipTests clean package -P prod-linux
|
||||
|
||||
- name: Deploy service
|
||||
if: success()
|
||||
env:
|
||||
CONTAINER_NAME: ${{ vars.CONTAINER_NAME }}
|
||||
CONTAINER_TARGET_PATH: ${{ vars.CONTAINER_TARGET_PATH }}
|
||||
MAX_RETRIES: 3
|
||||
RETRY_DELAY: 10
|
||||
run: |
|
||||
if [ -z "$CONTAINER_NAME" ] || [ -z "$CONTAINER_TARGET_PATH" ]; then
|
||||
echo "Missing production environment variables"
|
||||
echo "Required: CONTAINER_NAME, CONTAINER_TARGET_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
retry_command() {
|
||||
local cmd="$1"
|
||||
local desc="$2"
|
||||
local attempt=1
|
||||
|
||||
while [ $attempt -le $MAX_RETRIES ]; do
|
||||
echo "[$desc] Attempt $attempt/$MAX_RETRIES..."
|
||||
if eval "$cmd"; then
|
||||
echo "OK: $desc succeeded"
|
||||
return 0
|
||||
fi
|
||||
echo "FAIL: $desc failed (attempt $attempt/$MAX_RETRIES)"
|
||||
if [ $attempt -lt $MAX_RETRIES ]; then
|
||||
echo "Retrying in ${RETRY_DELAY}s..."
|
||||
sleep $RETRY_DELAY
|
||||
fi
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
|
||||
echo "FAIL: $desc failed after $MAX_RETRIES attempts"
|
||||
return 1
|
||||
}
|
||||
|
||||
version=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version)
|
||||
artifact_id=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.artifactId)
|
||||
jar_file="target/${artifact_id}-${version}.jar"
|
||||
|
||||
if [ ! -f "$jar_file" ]; then
|
||||
echo "Build artifact not found: $jar_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
echo "docker command not found in runner environment"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! docker inspect "$CONTAINER_NAME" >/dev/null 2>&1; then
|
||||
echo "Docker container not found: $CONTAINER_NAME"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
target_jar="${artifact_id}.jar"
|
||||
container_target="${CONTAINER_TARGET_PATH%/}/$target_jar"
|
||||
echo "Deploying $jar_file to container $CONTAINER_NAME:$container_target"
|
||||
|
||||
if ! retry_command "docker cp \"$jar_file\" \"$CONTAINER_NAME:$container_target\"" "Docker copy"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Restarting Docker container: $CONTAINER_NAME"
|
||||
if ! retry_command "docker restart \"$CONTAINER_NAME\"" "Docker restart"; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Deployment completed successfully"
|
||||
|
||||
- name: Create release
|
||||
if: ${{ success() && startsWith(github.event.pull_request.title, 'v') }}
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.RUNNER_TOKEN }}
|
||||
GITEA_SERVER_URL: ${{ github.server_url }}
|
||||
GITEA_INTERNAL_URL: ${{ vars.TIMI_GITEA_INTERNAL_URL }}
|
||||
GITEA_REPOSITORY: ${{ github.repository }}
|
||||
RELEASE_TAG: ${{ github.event.pull_request.title }}
|
||||
RELEASE_TARGET: ${{ github.sha }}
|
||||
MAX_RETRIES: 3
|
||||
RETRY_DELAY: 10
|
||||
run: |
|
||||
if [ -z "$GITEA_TOKEN" ]; then
|
||||
echo "Missing secrets.RUNNER_TOKEN"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -n "$GITEA_INTERNAL_URL" ]; then
|
||||
api_base_url="$GITEA_INTERNAL_URL"
|
||||
echo "Using internal Gitea URL: $api_base_url"
|
||||
else
|
||||
api_base_url="$GITEA_SERVER_URL"
|
||||
echo "Using public Gitea URL: $api_base_url"
|
||||
fi
|
||||
|
||||
version=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version)
|
||||
artifact_id=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.artifactId)
|
||||
jar_file="target/${artifact_id}-${version}.jar"
|
||||
|
||||
if [ ! -f "$jar_file" ]; then
|
||||
echo "Build artifact not found: $jar_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
file_size=$(stat -c%s "$jar_file" 2>/dev/null || stat -f%z "$jar_file" 2>/dev/null || echo "unknown")
|
||||
echo "Found fat jar: $jar_file (size: $file_size bytes)"
|
||||
|
||||
api_url="$api_base_url/api/v1/repos/$GITEA_REPOSITORY/releases"
|
||||
payload=$(cat <<EOF
|
||||
{
|
||||
"tag_name": "$RELEASE_TAG",
|
||||
"name": "$RELEASE_TAG",
|
||||
"target_commitish": "$RELEASE_TARGET",
|
||||
"draft": false,
|
||||
"prerelease": false
|
||||
}
|
||||
EOF
|
||||
)
|
||||
echo "Creating release with tag: $RELEASE_TAG"
|
||||
echo "API URL: $api_url"
|
||||
echo "Target commit: $RELEASE_TARGET"
|
||||
|
||||
release_response_file=$(mktemp /tmp/release_response_XXXXXX.json)
|
||||
trap "rm -f $release_response_file" EXIT
|
||||
|
||||
release_id=""
|
||||
attempt=1
|
||||
while [ $attempt -le $MAX_RETRIES ] && [ -z "$release_id" ]; do
|
||||
echo "[Create release] Attempt $attempt/$MAX_RETRIES..."
|
||||
|
||||
> "$release_response_file"
|
||||
|
||||
http_code=$(curl -sS -w "%{http_code}" -o "$release_response_file" -X POST "$api_url" \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
--connect-timeout 30 \
|
||||
--max-time 60 \
|
||||
-d "$payload" 2>/dev/null) || http_code="000"
|
||||
|
||||
response=$(cat "$release_response_file" 2>/dev/null || echo "{}")
|
||||
echo "HTTP Status: $http_code"
|
||||
|
||||
if [ "$http_code" = "201" ]; then
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
release_id=$(echo "$response" | jq -r '.id' 2>/dev/null)
|
||||
else
|
||||
release_id=$(echo "$response" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 | tr -d '\n\r')
|
||||
fi
|
||||
echo "OK: Release created: id=$release_id"
|
||||
elif [ "$http_code" = "409" ]; then
|
||||
echo "Release already exists (HTTP 409), fetching existing release..."
|
||||
existing=$(curl -sS "$api_url" -H "Authorization: token $GITEA_TOKEN" --connect-timeout 30 2>/dev/null || echo "[]")
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
release_id=$(echo "$existing" | jq -r ".[] | select(.tag_name==\"$RELEASE_TAG\") | .id" 2>/dev/null | head -1)
|
||||
else
|
||||
release_id=$(echo "$existing" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 | tr -d '\n\r')
|
||||
fi
|
||||
if [ -n "$release_id" ]; then
|
||||
echo "OK: Found existing release: id=$release_id"
|
||||
else
|
||||
echo "FAIL: Could not find existing release id"
|
||||
fi
|
||||
else
|
||||
echo "FAIL: Create release failed (HTTP $http_code)"
|
||||
if [ $attempt -lt $MAX_RETRIES ]; then
|
||||
echo "Retrying in ${RETRY_DELAY}s..."
|
||||
sleep $RETRY_DELAY
|
||||
fi
|
||||
fi
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
|
||||
if [ -z "$release_id" ]; then
|
||||
echo "FAIL: Failed to create or find release after $MAX_RETRIES attempts"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
asset_name=$(basename "$jar_file")
|
||||
echo "Uploading asset: $asset_name (size: $file_size bytes)"
|
||||
upload_url="$api_url/$release_id/assets?name=$asset_name"
|
||||
echo "Upload URL: $upload_url"
|
||||
|
||||
asset_response_file=$(mktemp /tmp/asset_response_XXXXXX.json)
|
||||
trap "rm -f $release_response_file $asset_response_file" EXIT
|
||||
|
||||
upload_success=false
|
||||
attempt=1
|
||||
while [ $attempt -le $MAX_RETRIES ] && [ "$upload_success" = "false" ]; do
|
||||
echo "[Upload asset] Attempt $attempt/$MAX_RETRIES..."
|
||||
|
||||
> "$asset_response_file"
|
||||
|
||||
http_code=$(curl -sS -w "%{http_code}" -o "$asset_response_file" -X POST "$upload_url" \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
--connect-timeout 30 \
|
||||
--max-time 300 \
|
||||
-F "attachment=@$jar_file" 2>/dev/null) || http_code="000"
|
||||
|
||||
if [ "$http_code" = "201" ]; then
|
||||
upload_success=true
|
||||
echo "OK: Successfully uploaded: $asset_name"
|
||||
else
|
||||
echo "FAIL: Upload failed (HTTP $http_code)"
|
||||
cat "$asset_response_file" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [ "$upload_success" = "false" ] && [ $attempt -lt $MAX_RETRIES ]; then
|
||||
echo "Retrying in ${RETRY_DELAY}s..."
|
||||
sleep $RETRY_DELAY
|
||||
fi
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
|
||||
if [ "$upload_success" = "false" ]; then
|
||||
echo "FAIL: Failed to upload asset after $MAX_RETRIES attempts"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Mark deployment success
|
||||
id: set_status
|
||||
if: always()
|
||||
run: |
|
||||
echo "status=success" >> $GITHUB_OUTPUT
|
||||
|
||||
notify-on-failure:
|
||||
runs-on: act_runner_java
|
||||
needs: build-deploy
|
||||
if: ${{ always() && github.event.pull_request.merged == true && needs.build-deploy.result == 'failure' }}
|
||||
steps:
|
||||
- name: Notify CI failure
|
||||
env:
|
||||
PR_NUMBER: ${{ github.event.number }}
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
SOURCE_BRANCH: ${{ github.event.pull_request.head.ref }}
|
||||
AUTHOR: ${{ github.event.pull_request.user.login }}
|
||||
COMMIT_SHA: ${{ github.sha }}
|
||||
REPO: ${{ github.repository }}
|
||||
SERVER_URL: ${{ github.server_url }}
|
||||
WEBHOOK_URL: ${{ vars.NOTIFY_WEBHOOK_URL }}
|
||||
run: |
|
||||
echo "========================================="
|
||||
echo "CI Pipeline Failed - Manual Review Required"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
echo "PR: #$PR_NUMBER - $PR_TITLE"
|
||||
echo "Branch: $SOURCE_BRANCH"
|
||||
echo "Author: $AUTHOR"
|
||||
echo "Commit: $COMMIT_SHA"
|
||||
echo ""
|
||||
echo "Actions:"
|
||||
echo " 1. Re-run CI: $SERVER_URL/$REPO/actions"
|
||||
echo " 2. Revert PR: $PR_URL (click 'Revert' button)"
|
||||
echo ""
|
||||
echo "========================================="
|
||||
|
||||
if [ -n "$WEBHOOK_URL" ]; then
|
||||
message="CI 部署失败\n\nPR: #$PR_NUMBER - $PR_TITLE\n分支: $SOURCE_BRANCH\n提交者: $AUTHOR\n\n请检查并决定:\n- 重试 CI\n- 回滚合并"
|
||||
|
||||
payload=$(cat <<EOF
|
||||
{
|
||||
"msgtype": "text",
|
||||
"text": {
|
||||
"content": "$message"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
curl -sS -X POST "$WEBHOOK_URL" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$payload" || echo "Warning: Failed to send notification"
|
||||
|
||||
echo "OK: Notification sent"
|
||||
else
|
||||
echo "Note: Set vars.NOTIFY_WEBHOOK_URL to enable webhook notifications"
|
||||
fi
|
||||
41
pom.xml
41
pom.xml
@@ -5,31 +5,24 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.4.0</version>
|
||||
<version>3.5.11</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<groupId>com.imyeyu.timiserverapi</groupId>
|
||||
<artifactId>TimiServerAPI</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<version>1.0.1</version>
|
||||
<packaging>jar</packaging>
|
||||
<name>TimiServerAPI</name>
|
||||
<description>imyeyu.com API</description>
|
||||
|
||||
<properties>
|
||||
<springboot.version>3.4.0</springboot.version>
|
||||
<springboot.version>3.5.11</springboot.version>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.multilingualField>21</maven.compiler.multilingualField>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>apache-maven</id>
|
||||
<url>https://repo.maven.apache.org/maven2/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>dev-windows</id>
|
||||
@@ -112,22 +105,38 @@
|
||||
<configuration>
|
||||
<excludeDevtools>true</excludeDevtools>
|
||||
<mainClass>com.imyeyu.api.TimiServerAPI</mainClass>
|
||||
<finalName>${project.artifactId}</finalName>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>apache-maven</id>
|
||||
<url>https://repo.maven.apache.org/maven2/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>timi_nexus</id>
|
||||
<url>https://nexus.imyeyu.com/repository/maven-public/</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.imyeyu.spring</groupId>
|
||||
<artifactId>timi-spring</artifactId>
|
||||
<version>0.0.2</version>
|
||||
<version>0.0.10</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.imyeyu.network</groupId>
|
||||
<artifactId>timi-network</artifactId>
|
||||
<version>0.0.2</version>
|
||||
<version>0.0.8</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.imyeyu.lang</groupId>
|
||||
@@ -184,12 +193,12 @@
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jgit</groupId>
|
||||
<artifactId>org.eclipse.jgit</artifactId>
|
||||
<version>6.7.0.202309050840-r</version>
|
||||
<version>7.2.1.202505142326-r</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jgit</groupId>
|
||||
<artifactId>org.eclipse.jgit.archive</artifactId>
|
||||
<version>6.7.0.202309050840-r</version>
|
||||
<version>7.2.1.202505142326-r</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
@@ -220,7 +229,7 @@
|
||||
<dependency>
|
||||
<groupId>org.apache.tika</groupId>
|
||||
<artifactId>tika-core</artifactId>
|
||||
<version>2.9.2</version>
|
||||
<version>3.2.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jcodec</groupId>
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
package com.imyeyu.api;
|
||||
|
||||
import com.imyeyu.io.IO;
|
||||
import com.imyeyu.java.TimiJava;
|
||||
import com.imyeyu.java.bean.Language;
|
||||
import com.imyeyu.java.ref.Ref;
|
||||
import com.imyeyu.spring.TimiSpring;
|
||||
import com.imyeyu.utils.OS;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -13,7 +9,6 @@ import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
import java.io.File;
|
||||
@@ -31,8 +26,6 @@ import java.io.File;
|
||||
@EnableTransactionManagement
|
||||
public class TimiServerAPI implements OS.FileSystem, ApplicationContextAware {
|
||||
|
||||
private static final String DEV_LANG_CONFIG = "dev.lang";
|
||||
|
||||
public static ApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
@@ -40,32 +33,18 @@ public class TimiServerAPI implements OS.FileSystem, ApplicationContextAware {
|
||||
TimiServerAPI.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
public static Language.Enum getUserLanguage() {
|
||||
Language.Enum userLanguage = TimiSpring.getLanguage();
|
||||
Environment env = applicationContext.getBean(Environment.class);
|
||||
if (env.containsProperty(DEV_LANG_CONFIG)) {
|
||||
String property = env.getProperty(DEV_LANG_CONFIG);
|
||||
if (TimiJava.isNotEmpty(property)) {
|
||||
userLanguage = Ref.toType(Language.Enum.class, property);
|
||||
}
|
||||
}
|
||||
return userLanguage;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
{
|
||||
// 导出配置
|
||||
String[] files = {"application.yml", "logback.xml"};
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
File file = new File("config" + SEP + files[i]);
|
||||
if (!file.exists() || !file.isFile()) {
|
||||
log.info("exporting default config at {}", file.getAbsolutePath());
|
||||
IO.resourceToDisk(TimiServerAPI.class, files[i], file.getAbsolutePath());
|
||||
}
|
||||
File application = new File("config" + SEP + "application.yml");
|
||||
if (!application.exists()) {
|
||||
IO.resourceToDisk(TimiServerAPI.class, "application_export.yml", application.getAbsolutePath());
|
||||
}
|
||||
File logback = new File("config" + SEP + "logback.xml");
|
||||
if (!logback.exists()) {
|
||||
IO.resourceToDisk(TimiServerAPI.class, "logback.xml", logback.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
// 启动 SpringBoot
|
||||
SpringApplication.run(TimiServerAPI.class, args);
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
package com.imyeyu.api.bean;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
/**
|
||||
* @author 夜雨
|
||||
* @since 2025-01-13 11:42
|
||||
*/
|
||||
@Component
|
||||
public class IOCBeans {
|
||||
|
||||
@Bean
|
||||
public Yaml yaml() {
|
||||
return new Yaml();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,12 @@
|
||||
package com.imyeyu.api.config;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.imyeyu.utils.Time;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
/**
|
||||
* @author 夜雨
|
||||
@@ -10,7 +15,17 @@ import org.springframework.context.annotation.Configuration;
|
||||
@Configuration
|
||||
public class BeanConfig {
|
||||
|
||||
public Gson gson() {
|
||||
return new Gson();
|
||||
@Bean
|
||||
public ObjectMapper jackson() {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.setDateFormat(Time.dateTime);
|
||||
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
||||
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
return mapper;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Yaml yaml() {
|
||||
return new Yaml();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,21 +29,12 @@ public class CORSConfig {
|
||||
/** 允许跨域的地址 */
|
||||
private String[] allowOrigin;
|
||||
|
||||
/** 是否允许请求带有验证信息 */
|
||||
private boolean allowCredentials;
|
||||
|
||||
/** 允许请求的方法 */
|
||||
private String allowMethods;
|
||||
|
||||
/** 允许服务端访问的客户端请求头 */
|
||||
private String allowHeaders;
|
||||
|
||||
@Bean
|
||||
public FilterRegistrationBean<Filter> corsFilter() {
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
config.addAllowedHeader(allowHeaders);
|
||||
config.addAllowedMethod(allowMethods);
|
||||
config.setAllowCredentials(allowCredentials);
|
||||
config.addAllowedHeader("*");
|
||||
config.addAllowedMethod("*");
|
||||
config.setAllowCredentials(true);
|
||||
config.setAllowedOriginPatterns(Arrays.asList(allowOrigin));
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package com.imyeyu.api.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.imyeyu.api.modules.blog.entity.ArticleRanking;
|
||||
import com.imyeyu.api.modules.common.entity.Multilingual;
|
||||
import com.imyeyu.spring.bean.RedisConfigParams;
|
||||
import com.imyeyu.spring.config.AbstractRedisConfig;
|
||||
import com.imyeyu.spring.util.Redis;
|
||||
import com.imyeyu.spring.util.RedisSerializers;
|
||||
import com.imyeyu.utils.Time;
|
||||
import io.lettuce.core.api.StatefulConnection;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
@@ -32,9 +36,12 @@ import java.time.Duration;
|
||||
@Configuration
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@EnableAutoConfiguration
|
||||
@RequiredArgsConstructor
|
||||
@ConfigurationProperties(prefix = "spring.redis")
|
||||
public class RedisConfig extends AbstractRedisConfig {
|
||||
|
||||
private final ObjectMapper jackson;
|
||||
|
||||
// ---------- 连接配置 ----------
|
||||
|
||||
/** 地址 */
|
||||
@@ -46,50 +53,9 @@ public class RedisConfig extends AbstractRedisConfig {
|
||||
/** 密码 */
|
||||
private String password;
|
||||
|
||||
/** 超时(毫秒) */
|
||||
private int timeout;
|
||||
|
||||
/** 连接池 */
|
||||
private Lettuce lettuce;
|
||||
|
||||
/** 数据库 */
|
||||
private Database database;
|
||||
|
||||
/**
|
||||
* 连接池
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2023-08-21 16:23
|
||||
*/
|
||||
@Data
|
||||
public static class Lettuce {
|
||||
|
||||
/** 配置 */
|
||||
private Pool pool;
|
||||
|
||||
/**
|
||||
* 配置
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2023-08-21 16:23
|
||||
*/
|
||||
@Data
|
||||
public static class Pool {
|
||||
|
||||
/** 最大活跃连接 */
|
||||
private int maxActive;
|
||||
|
||||
/** 最小空闲连接 */
|
||||
private int minIdle;
|
||||
|
||||
/** 最大空闲连接 */
|
||||
private int maxIdle;
|
||||
|
||||
/** 最大等待时间(秒) */
|
||||
private int maxWait;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库
|
||||
*
|
||||
@@ -145,22 +111,23 @@ public class RedisConfig extends AbstractRedisConfig {
|
||||
setHost(host);
|
||||
setPort(port);
|
||||
setPassword(password);
|
||||
setTimeout(timeout);
|
||||
setMaxActive(lettuce.pool.maxActive);
|
||||
setMinIdle(lettuce.pool.minIdle);
|
||||
setMaxIdle(lettuce.pool.maxIdle);
|
||||
setTimeout(Time.SI * 8);
|
||||
setMaxActive(8);
|
||||
setMinIdle(1);
|
||||
setMaxIdle(8);
|
||||
}};
|
||||
}
|
||||
|
||||
/** @return 连接池配置 */
|
||||
@Bean
|
||||
@Override
|
||||
public GenericObjectPoolConfig<?> getPoolConfig() {
|
||||
GenericObjectPoolConfig<?> config = new GenericObjectPoolConfig<>();
|
||||
config.setMaxTotal(lettuce.pool.maxActive);
|
||||
config.setMinIdle(lettuce.pool.minIdle);
|
||||
config.setMaxIdle(lettuce.pool.maxIdle);
|
||||
config.setMaxWait(Duration.ofMillis(lettuce.pool.maxWait));
|
||||
public GenericObjectPoolConfig<StatefulConnection<?, ?>> getPoolConfig() {
|
||||
RedisConfigParams configArgs = configParams();
|
||||
GenericObjectPoolConfig<StatefulConnection<?, ?>> config = new GenericObjectPoolConfig<>();
|
||||
config.setMaxTotal(8);
|
||||
config.setMinIdle(configArgs.getMinIdle());
|
||||
config.setMaxIdle(configArgs.getMaxIdle());
|
||||
config.setMaxWait(Duration.ofMillis(-1));
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -224,7 +191,7 @@ public class RedisConfig extends AbstractRedisConfig {
|
||||
/** @return 文章访问统计,文章 ID: {@link ArticleRanking}(JSON) */
|
||||
@Bean("redisArticleRanking")
|
||||
public Redis<Long, ArticleRanking> getArticleRankingRedisTemplate() {
|
||||
return getRedis(database.articleRanking, RedisSerializers.LONG, RedisSerializers.gsonSerializer(ArticleRanking.class));
|
||||
return getRedis(database.articleRanking, RedisSerializers.LONG, RedisSerializers.jacksonSerializer(jackson, ArticleRanking.class));
|
||||
}
|
||||
|
||||
/** @return 文章访问记录,IP: [文章 ID] */
|
||||
|
||||
@@ -2,8 +2,6 @@ package com.imyeyu.api.config;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
@@ -19,37 +17,17 @@ import java.util.concurrent.ThreadPoolExecutor;
|
||||
@Data
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
@ConfigurationProperties(prefix = "spring.async.thread-pool")
|
||||
public class ThreadPoolConfig {
|
||||
|
||||
/** 核心数量 */
|
||||
private int corePoolSize;
|
||||
|
||||
/** 最大数量 */
|
||||
private int maxPoolSize;
|
||||
|
||||
/** 等待区容量 */
|
||||
private int queueCapacity;
|
||||
|
||||
/** 最大保持活跃时间(秒) */
|
||||
private int keepAliveSeconds;
|
||||
|
||||
/** 最大等待时间(秒) */
|
||||
private int awaitTerminationSeconds;
|
||||
|
||||
/** 线程名称前缀 */
|
||||
private String threadNamePrefix;
|
||||
|
||||
@Bean(name = "threadPoolTaskExecutor")
|
||||
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(corePoolSize);
|
||||
executor.setMaxPoolSize(maxPoolSize);
|
||||
executor.setQueueCapacity(queueCapacity);
|
||||
executor.setKeepAliveSeconds(keepAliveSeconds);
|
||||
executor.setAwaitTerminationSeconds(awaitTerminationSeconds);
|
||||
executor.setThreadNamePrefix(threadNamePrefix);
|
||||
executor.setCorePoolSize(16);
|
||||
executor.setMaxPoolSize(32);
|
||||
executor.setQueueCapacity(16);
|
||||
executor.setKeepAliveSeconds(60);
|
||||
executor.setAwaitTerminationSeconds(60);
|
||||
executor.setThreadNamePrefix("thread-pool-task-executor-");
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
executor.initialize();
|
||||
return executor;
|
||||
|
||||
@@ -1,32 +1,28 @@
|
||||
package com.imyeyu.api.config;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.MapperFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||
import com.imyeyu.api.annotation.EnableSettingInterceptor;
|
||||
import com.imyeyu.api.annotation.RequestRateLimitInterceptor;
|
||||
import com.imyeyu.api.annotation.RequiredTokenInterceptor;
|
||||
import com.imyeyu.api.modules.common.entity.Attachment;
|
||||
import com.imyeyu.api.modules.common.vo.user.UserProfileView;
|
||||
import com.imyeyu.api.modules.common.vo.user.UserView;
|
||||
import com.imyeyu.api.modules.journal.util.JournalAPIInterceptor;
|
||||
import com.imyeyu.api.modules.minecraft.annotation.RequiredFMCServerTokenInterceptor;
|
||||
import com.imyeyu.api.modules.minecraft.entity.MinecraftPlayer;
|
||||
import com.imyeyu.api.modules.mirror.vo.MirrorView;
|
||||
import com.imyeyu.api.modules.system.util.SystemAPIInterceptor;
|
||||
import com.imyeyu.api.util.GsonSerializerAdapter;
|
||||
import com.imyeyu.spring.annotation.RequestSingleParamResolver;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.NonNull;
|
||||
import com.imyeyu.spring.annotation.RequestBodyValueArgumentResolver;
|
||||
import com.imyeyu.utils.Time;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.GsonHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -40,12 +36,11 @@ import java.util.List;
|
||||
@RequiredArgsConstructor
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
private final ObjectMapper jackson;
|
||||
private final SystemAPIInterceptor systemAPIInterceptor;
|
||||
private final GsonSerializerAdapter gsonSerializerAdapter;
|
||||
private final JournalAPIInterceptor journalAPIInterceptor;
|
||||
private final RequiredTokenInterceptor requiredTokenInterceptor;
|
||||
private final EnableSettingInterceptor enableSettingInterceptor;
|
||||
private final RequestSingleParamResolver requestSingleParamResolver;
|
||||
private final RequestRateLimitInterceptor requestRateLimitInterceptor;
|
||||
private final RequiredFMCServerTokenInterceptor requiredFMCServerTokenInterceptor;
|
||||
|
||||
@@ -66,7 +61,7 @@ public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
|
||||
argumentResolvers.add(requestSingleParamResolver);
|
||||
argumentResolvers.add(new RequestBodyValueArgumentResolver(jackson));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,22 +71,17 @@ public class WebConfig implements WebMvcConfigurer {
|
||||
*/
|
||||
@Override
|
||||
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||
GsonHttpMessageConverter converter = new GsonHttpMessageConverter() {
|
||||
|
||||
@Override
|
||||
protected void writeInternal(@NotNull Object object, Type type, @NonNull Writer writer) {
|
||||
// 忽略参数类型,因为接口返回对象会被全局返回处理器包装为 TimiResponse,否则会序列化转型错误
|
||||
getGson().toJson(object, writer);
|
||||
}
|
||||
};
|
||||
|
||||
GsonBuilder builder = new GsonBuilder();
|
||||
builder.registerTypeAdapter(Attachment.class, gsonSerializerAdapter);
|
||||
builder.registerTypeAdapter(UserView.class, gsonSerializerAdapter);
|
||||
builder.registerTypeAdapter(MirrorView.class, gsonSerializerAdapter);
|
||||
builder.registerTypeAdapter(UserProfileView.class, gsonSerializerAdapter);
|
||||
builder.registerTypeAdapter(MinecraftPlayer.class, gsonSerializerAdapter);
|
||||
converter.setGson(builder.create());
|
||||
JsonMapper jsonMapper = JsonMapper.builder()
|
||||
// 设置默认属性包含规则:忽略 null 值
|
||||
.serializationInclusion(JsonInclude.Include.NON_NULL)
|
||||
// 日期格式化
|
||||
.defaultDateFormat(Time.dateTime)
|
||||
// 忽略不存在字段
|
||||
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
|
||||
// 启用默认视图包含
|
||||
.enable(MapperFeature.DEFAULT_VIEW_INCLUSION).build();
|
||||
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(jsonMapper);
|
||||
converter.setDefaultCharset(StandardCharsets.UTF_8);
|
||||
converters.add(converter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,8 +70,8 @@ public class GiteaDBConfig {
|
||||
ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
|
||||
List<String> mapperLocations = new ArrayList<>();
|
||||
mapperLocations.add("classpath:mapper/gitea/**/*.xml");
|
||||
for (int i = 0; i < mapperLocations.size(); i++) {
|
||||
resources.addAll(List.of(resourceResolver.getResources(mapperLocations.get(i))));
|
||||
for (String mapperLocation : mapperLocations) {
|
||||
resources.addAll(List.of(resourceResolver.getResources(mapperLocation)));
|
||||
}
|
||||
}
|
||||
String[] typeAliases = {
|
||||
|
||||
@@ -83,8 +83,8 @@ public class TimiServerDBConfig {
|
||||
mapperLocations.add("classpath:mapper/system/**/*.xml");
|
||||
mapperLocations.add("classpath:mapper/journal/**/*.xml");
|
||||
mapperLocations.add("classpath:mapper/minecraft/**/*.xml");
|
||||
for (int i = 0; i < mapperLocations.size(); i++) {
|
||||
resources.addAll(List.of(resourceResolver.getResources(mapperLocations.get(i))));
|
||||
for (String mapperLocation : mapperLocations) {
|
||||
resources.addAll(List.of(resourceResolver.getResources(mapperLocation)));
|
||||
}
|
||||
}
|
||||
String[] typeAliases = {
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
package com.imyeyu.api.modules.bill.controller;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.imyeyu.java.bean.timi.TimiCode;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import com.imyeyu.api.modules.bill.entity.Bill;
|
||||
import com.imyeyu.api.modules.bill.service.BillService;
|
||||
import com.imyeyu.api.modules.common.bean.SettingKey;
|
||||
import com.imyeyu.api.modules.common.service.SettingService;
|
||||
import com.imyeyu.spring.TimiSpring;
|
||||
import com.imyeyu.spring.annotation.AOPLog;
|
||||
import com.imyeyu.spring.annotation.RequestRateLimit;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 收支帐单接口
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2023-02-04 01:02
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/bill")
|
||||
public class BillController {
|
||||
|
||||
private final BillService service;
|
||||
private final SettingService settingService;
|
||||
|
||||
/**
|
||||
* 创建收支帐单
|
||||
*
|
||||
* @param bill 账单
|
||||
*/
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@PostMapping("/create")
|
||||
public void createREBill(@Valid @RequestBody Bill bill) {
|
||||
if (!settingService.getAsString(SettingKey.BILL_API_TOKEN).equals(TimiSpring.getToken())) {
|
||||
throw new TimiException(TimiCode.REQUEST_BAD).msgKey("token.illegal");
|
||||
}
|
||||
service.create(bill);
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
package com.imyeyu.api.modules.bill.entity;
|
||||
|
||||
import jakarta.validation.constraints.DecimalMin;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import com.imyeyu.spring.entity.Entity;
|
||||
|
||||
/**
|
||||
* 收支账单
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2022-03-29 11:28
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class Bill extends Entity {
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2022-03-29 11:28
|
||||
*/
|
||||
@Getter
|
||||
public enum Type {
|
||||
|
||||
/** 收入 */
|
||||
REVENUE,
|
||||
|
||||
/** 支出 */
|
||||
EXPENDITURE
|
||||
}
|
||||
|
||||
/**
|
||||
* 收入类型
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2022-03-29 11:28
|
||||
*/
|
||||
@Getter
|
||||
public enum RevenueType {
|
||||
|
||||
/** 工作 */
|
||||
WORK,
|
||||
|
||||
/** 退款 */
|
||||
REFUND,
|
||||
|
||||
/** 其他 */
|
||||
OTHER
|
||||
}
|
||||
|
||||
/**
|
||||
* 支出类型
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2022-03-29 11:28
|
||||
*/
|
||||
@Getter
|
||||
public enum ExpenditureType {
|
||||
|
||||
/** 饮食 */
|
||||
FOOD,
|
||||
|
||||
/** 生活 */
|
||||
LIFE,
|
||||
|
||||
/** 通信 */
|
||||
COMMUNICATION,
|
||||
|
||||
/** 交通 */
|
||||
TRAFFIC,
|
||||
|
||||
/** 娱乐 */
|
||||
GAME,
|
||||
|
||||
/** 工作 */
|
||||
WORK,
|
||||
|
||||
/** 服饰 */
|
||||
CLOTHES,
|
||||
|
||||
/** 医疗 */
|
||||
HEALTH,
|
||||
|
||||
/** 其他 */
|
||||
OTHER
|
||||
}
|
||||
|
||||
/** 收入类型 */
|
||||
private RevenueType revenueType;
|
||||
|
||||
/** 支出类型 */
|
||||
private ExpenditureType expenditureType;
|
||||
|
||||
/** 描述 */
|
||||
@NotBlank(message = "bill.description.empty")
|
||||
private String description;
|
||||
|
||||
/** 金额(未确保计算精度,放大了 100 倍) */
|
||||
@NotNull(message = "bill.decimal.empty")
|
||||
@DecimalMin(value = "0", message = "bill.decimal.limit")
|
||||
private Long decimal;
|
||||
|
||||
/** 备注 */
|
||||
private String remarks;
|
||||
|
||||
/** @return true 为收入账单 */
|
||||
public boolean isRevenue() {
|
||||
return revenueType != null;
|
||||
}
|
||||
|
||||
/** @return true 为支出账单 */
|
||||
public boolean isExpenditure() {
|
||||
return expenditureType != null;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.imyeyu.api.modules.bill.mapper;
|
||||
|
||||
import com.imyeyu.api.modules.bill.entity.Bill;
|
||||
import com.imyeyu.spring.mapper.BaseMapper;
|
||||
|
||||
/**
|
||||
* 收支帐单表
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2022-04-01 16:26
|
||||
*/
|
||||
public interface BillMapper extends BaseMapper<Bill, Long> {
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.imyeyu.api.modules.bill.service;
|
||||
|
||||
import com.imyeyu.api.modules.bill.entity.Bill;
|
||||
import com.imyeyu.spring.service.CreatableService;
|
||||
|
||||
/**
|
||||
* 收支帐单服务
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2022-04-01 16:24
|
||||
*/
|
||||
public interface BillService extends CreatableService<Bill> {
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.imyeyu.api.modules.bill.service.implement;
|
||||
|
||||
import com.imyeyu.api.modules.bill.entity.Bill;
|
||||
import com.imyeyu.api.modules.bill.mapper.BillMapper;
|
||||
import com.imyeyu.api.modules.bill.service.BillService;
|
||||
import com.imyeyu.spring.mapper.BaseMapper;
|
||||
import com.imyeyu.spring.service.AbstractEntityService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 收支账单服务
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2022-04-01 16:25
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class BillServiceImplement extends AbstractEntityService<Bill, Long> implements BillService {
|
||||
|
||||
private final BillMapper mapper;
|
||||
|
||||
@Override
|
||||
protected BaseMapper<Bill, Long> mapper() {
|
||||
return mapper;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.imyeyu.api.modules.blog.entity;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.imyeyu.api.modules.common.bean.CommentSupport;
|
||||
import com.imyeyu.spring.entity.Destroyable;
|
||||
import com.imyeyu.spring.entity.Entity;
|
||||
@@ -48,7 +48,7 @@ public class Article extends Entity implements CommentSupport, Destroyable {
|
||||
protected String data;
|
||||
|
||||
/** 扩展数据 */
|
||||
protected JsonElement extendData;
|
||||
protected JsonNode extendData;
|
||||
|
||||
/** 阅读数量 */
|
||||
protected int reads;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package com.imyeyu.api.modules.blog.entity;
|
||||
|
||||
import com.imyeyu.spring.entity.Entity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import com.imyeyu.spring.entity.Entity;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 访问排行(每周)
|
||||
@@ -12,6 +13,7 @@ import com.imyeyu.spring.entity.Entity;
|
||||
* @since 2021-03-01 17:10
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ArticleRanking extends Entity {
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.imyeyu.api.modules.common.controller;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.imyeyu.api.bean.CaptchaFrom;
|
||||
import com.imyeyu.api.modules.common.bean.ImageType;
|
||||
import com.imyeyu.api.modules.common.bean.SettingKey;
|
||||
@@ -95,12 +95,28 @@ public class CommonController {
|
||||
private final AttachmentService attachmentService;
|
||||
private final ClipboardService clipboardService;
|
||||
|
||||
private final Gson gson;
|
||||
private final ObjectMapper jackson;
|
||||
private final Yaml yaml;
|
||||
private final GridFSBucket gridFSBucket;
|
||||
private final CaptchaManager captchaManager;
|
||||
private final ResourceHandler resourceHandler;
|
||||
|
||||
private String writeJson(Object value) {
|
||||
try {
|
||||
return jackson.writeValueAsString(value);
|
||||
} catch (IOException e) {
|
||||
throw new TimiException(TimiCode.ERROR, "write json error", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> readJsonMap(String value) {
|
||||
try {
|
||||
return jackson.readValue(value, new TypeReference<>() {});
|
||||
} catch (IOException e) {
|
||||
throw new TimiException(TimiCode.ERROR, "read json error", e);
|
||||
}
|
||||
}
|
||||
|
||||
@AOPLog
|
||||
@RequestMapping("")
|
||||
public String root() {
|
||||
@@ -253,12 +269,12 @@ public class CommonController {
|
||||
case JSON -> {
|
||||
if (setting.getType() == Setting.Type.YAML) {
|
||||
Map<String, Object> obj = yaml.load(setting.getValue());
|
||||
result = gson.toJson(obj);
|
||||
result = writeJson(obj);
|
||||
}
|
||||
}
|
||||
case YAML -> {
|
||||
if (setting.getType() == Setting.Type.JSON) {
|
||||
Map<String, Object> obj = gson.fromJson(setting.getValue(), new TypeToken<Map<String, Object>>() {}.getType());
|
||||
Map<String, Object> obj = readJsonMap(setting.getValue());
|
||||
result = yaml.dump(obj);
|
||||
}
|
||||
}
|
||||
@@ -284,12 +300,12 @@ public class CommonController {
|
||||
case JSON -> {
|
||||
if (setting.getType() == Setting.Type.YAML) {
|
||||
Map<String, Object> obj = new Yaml().load(setting.getValue());
|
||||
setting.setValue(gson.toJson(obj));
|
||||
setting.setValue(writeJson(obj));
|
||||
}
|
||||
}
|
||||
case YAML -> {
|
||||
if (setting.getType() == Setting.Type.JSON) {
|
||||
Map<String, Object> obj = gson.fromJson(setting.getValue(), new TypeToken<Map<String, Object>>() {}.getType());
|
||||
Map<String, Object> obj = readJsonMap(setting.getValue());
|
||||
setting.setValue(new Yaml().dump(obj));
|
||||
}
|
||||
}
|
||||
@@ -483,7 +499,7 @@ public class CommonController {
|
||||
GridFSFile file = attachmentService.readByMongoId(mongoId);
|
||||
@Cleanup
|
||||
GridFSDownloadStream downloadStream = gridFSBucket.openDownloadStream(file.getObjectId());
|
||||
RequestRange range = TimiSpring.requestRange(attach.getSize());
|
||||
RequestRange range = TimiSpring.getRequestRange(attach.getSize());
|
||||
if (range == null) {
|
||||
// 完整文件
|
||||
resp.setContentLengthLong(attach.getSize());
|
||||
|
||||
@@ -26,7 +26,6 @@ import com.imyeyu.java.TimiJava;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import com.imyeyu.spring.annotation.AOPLog;
|
||||
import com.imyeyu.spring.annotation.RequestRateLimit;
|
||||
import com.imyeyu.spring.annotation.RequestSingleParam;
|
||||
import com.imyeyu.spring.annotation.RequiredToken;
|
||||
import com.imyeyu.spring.bean.CaptchaData;
|
||||
import com.imyeyu.spring.bean.Page;
|
||||
@@ -184,7 +183,7 @@ public class UserController implements TimiJava {
|
||||
@RequiredToken
|
||||
@RequestRateLimit
|
||||
@PostMapping("/cancel")
|
||||
public void cancel(@RequestSingleParam String password) {
|
||||
public void cancel(@RequestBody String password) {
|
||||
service.cancel(password);
|
||||
}
|
||||
|
||||
@@ -275,7 +274,7 @@ public class UserController implements TimiJava {
|
||||
@RequiredToken
|
||||
@RequestRateLimit
|
||||
@PostMapping("/comment/delete")
|
||||
public void deleteComment(@RequestSingleParam Long commentId) {
|
||||
public void deleteComment(@RequestBody Long commentId) {
|
||||
commentService.get(commentId);
|
||||
commentService.delete(commentId);
|
||||
}
|
||||
@@ -298,7 +297,7 @@ public class UserController implements TimiJava {
|
||||
@RequiredToken
|
||||
@RequestRateLimit
|
||||
@PostMapping("/comment/reply/delete")
|
||||
public void deleteCommentReply(@RequestSingleParam Long replyId) {
|
||||
public void deleteCommentReply(@RequestBody Long replyId) {
|
||||
CommentReply reply = commentReplyService.get(replyId);
|
||||
TimiException.requiredTrue(reply.getSenderId().equals(service.getLoginUser().getId()), "user.comment.reply.delete.not_owner");
|
||||
commentReplyService.delete(replyId);
|
||||
@@ -308,7 +307,7 @@ public class UserController implements TimiJava {
|
||||
@RequiredToken
|
||||
@RequestRateLimit
|
||||
@PostMapping("/comment/reply/ignore")
|
||||
public void ignoreCommentReply(@RequestSingleParam Long replyId) {
|
||||
public void ignoreCommentReply(@RequestBody Long replyId) {
|
||||
CommentReply reply = commentReplyService.get(replyId);
|
||||
TimiException.requiredTrue(reply.getReceiverId().equals(service.getLoginUser().getId()), "user.comment.reply.ignore.not_owner");
|
||||
reply.setIgnoredAt(Time.now());
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.imyeyu.api.modules.common.entity;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.imyeyu.api.TimiServerAPI;
|
||||
import com.imyeyu.api.bean.MultilingualHandler;
|
||||
import com.imyeyu.api.modules.common.service.AttachmentService;
|
||||
@@ -81,7 +81,7 @@ public class Attachment extends Entity implements MultilingualHandler {
|
||||
|
||||
protected String mimeType;
|
||||
|
||||
protected JsonObject metadata;
|
||||
protected JsonNode metadata;
|
||||
|
||||
protected Long size;
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.imyeyu.api.modules.common.service;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.imyeyu.api.modules.common.bean.SettingKey;
|
||||
import com.imyeyu.api.modules.common.entity.Setting;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
@@ -60,15 +60,15 @@ public interface SettingService extends UpdatableService<Setting> {
|
||||
*/
|
||||
boolean not(SettingKey key);
|
||||
|
||||
JsonElement getAsJsonElement(SettingKey key);
|
||||
JsonNode getAsJsonNode(SettingKey key);
|
||||
|
||||
JsonObject getAsJsonObject(SettingKey key);
|
||||
ObjectNode getAsJsonObject(SettingKey key);
|
||||
|
||||
JsonArray getAsJsonArray(SettingKey key);
|
||||
ArrayNode getAsArrayNode(SettingKey key);
|
||||
|
||||
<T> T fromJson(SettingKey key, Class<T> clazz);
|
||||
|
||||
<T> T fromJson(SettingKey key, TypeToken<T> typeToken);
|
||||
<T> T fromJson(SettingKey key, TypeReference<T> typeReference);
|
||||
|
||||
<T> T fromYaml(SettingKey key, Class<T> clazz);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.imyeyu.api.modules.common.service.implement;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
|
||||
import com.imyeyu.api.modules.common.bean.MediaAttach;
|
||||
import com.imyeyu.api.modules.common.bean.Metadata;
|
||||
@@ -55,7 +55,7 @@ public class AttachmentServiceImplement extends AbstractEntityService<Attachment
|
||||
|
||||
private final AttachmentMapper mapper;
|
||||
|
||||
private final Gson gson;
|
||||
private final ObjectMapper jackson;
|
||||
private final GridFSBucket gridFSBucket;
|
||||
private final GridFsTemplate gridFsTemplate;
|
||||
|
||||
@@ -90,10 +90,10 @@ public class AttachmentServiceImplement extends AbstractEntityService<Attachment
|
||||
attachment.setIsDestroyed(false);
|
||||
if (attachment.getMimeType().startsWith("image")) {
|
||||
BufferedImage image = ImageIO.read(gridFSBucket.openDownloadStream(gridFSFile.getObjectId()));
|
||||
attachment.setMetadata(TimiJava.defaultIfNull(attachment.getMetadata(), new JsonObject()));
|
||||
JsonObject metadata = attachment.getMetadata();
|
||||
metadata.addProperty("width", image.getWidth());
|
||||
metadata.addProperty("height", image.getHeight());
|
||||
attachment.setMetadata(TimiJava.defaultIfNull(attachment.getMetadata(), jackson.createObjectNode()));
|
||||
ObjectNode metadata = (ObjectNode) attachment.getMetadata();
|
||||
metadata.put("width", image.getWidth());
|
||||
metadata.put("height", image.getHeight());
|
||||
}
|
||||
mapper.insert(attachment);
|
||||
} catch (Exception e) {
|
||||
@@ -166,7 +166,7 @@ public class AttachmentServiceImplement extends AbstractEntityService<Attachment
|
||||
thumbAttach.setBizType(attachment.getBizType());
|
||||
thumbAttach.setBizId(attachment.getBizId());
|
||||
thumbAttach.setAttachTypeValue(MediaAttach.Type.THUMB);
|
||||
thumbAttach.setMetadata(gson.toJsonTree(thumbMetadata).getAsJsonObject());
|
||||
thumbAttach.setMetadata(jackson.valueToTree(thumbMetadata));
|
||||
thumbAttach.setInputStream(new ByteArrayInputStream(thumbStream.toByteArray()));
|
||||
create(thumbAttach);
|
||||
|
||||
@@ -182,7 +182,7 @@ public class AttachmentServiceImplement extends AbstractEntityService<Attachment
|
||||
public void deleteMedia(Long thumbId) throws TimiException {
|
||||
Attachment attachment = get(thumbId);
|
||||
delete(attachment.getId());
|
||||
Metadata.ThumbImage thumbMetadata = gson.fromJson(attachment.getMetadata(), Metadata.ThumbImage.class);
|
||||
Metadata.ThumbImage thumbMetadata = jackson.convertValue(attachment.getMetadata(), Metadata.ThumbImage.class);
|
||||
delete(thumbMetadata.getSourceId());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package com.imyeyu.api.modules.common.service.implement;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.imyeyu.api.modules.common.bean.SettingKey;
|
||||
import com.imyeyu.api.modules.common.entity.Setting;
|
||||
import com.imyeyu.api.modules.common.mapper.SettingMapper;
|
||||
@@ -39,7 +39,7 @@ public class SettingServiceImplement extends AbstractEntityService<Setting, Stri
|
||||
private final SettingMapper mapper;
|
||||
private final Redis<String, String> redisSetting;
|
||||
|
||||
private final Gson gson;
|
||||
private final ObjectMapper jackson;
|
||||
|
||||
@Override
|
||||
protected BaseMapper<Setting, String> mapper() {
|
||||
@@ -59,7 +59,11 @@ public class SettingServiceImplement extends AbstractEntityService<Setting, Stri
|
||||
|
||||
String cacheValue = redisSetting.get(key.toString());
|
||||
if (TimiJava.isNotEmpty(cacheValue)) {
|
||||
return gson.fromJson(cacheValue, Setting.class);
|
||||
try {
|
||||
return jackson.readValue(cacheValue, Setting.class);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new TimiException(TimiCode.ERROR, "read setting cache error", e);
|
||||
}
|
||||
}
|
||||
Setting setting = mapper.selectByKey(key);
|
||||
if (TimiJava.isEmpty(setting.getValue())) {
|
||||
@@ -72,7 +76,11 @@ public class SettingServiceImplement extends AbstractEntityService<Setting, Stri
|
||||
settingTTL = Integer.parseInt(getByKey(SettingKey.TTL_SETTING).getValue());
|
||||
}
|
||||
if (0 < settingTTL) {
|
||||
redisSetting.set(key.toString(), gson.toJson(setting), Time.M * settingTTL);
|
||||
try {
|
||||
redisSetting.set(key.toString(), jackson.writeValueAsString(setting), Time.M * settingTTL);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new TimiException(TimiCode.ERROR, "write setting cache error", e);
|
||||
}
|
||||
}
|
||||
return setting;
|
||||
}
|
||||
@@ -117,28 +125,36 @@ public class SettingServiceImplement extends AbstractEntityService<Setting, Stri
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement getAsJsonElement(SettingKey key) {
|
||||
return JsonParser.parseString(getAsString(key));
|
||||
public JsonNode getAsJsonNode(SettingKey key) {
|
||||
try {
|
||||
return jackson.readTree(getAsString(key));
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new TimiException(TimiCode.ERROR, "read setting json error", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject getAsJsonObject(SettingKey key) {
|
||||
return getAsJsonElement(key).getAsJsonObject();
|
||||
public ObjectNode getAsJsonObject(SettingKey key) {
|
||||
return (ObjectNode) getAsJsonNode(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonArray getAsJsonArray(SettingKey key) {
|
||||
return getAsJsonElement(key).getAsJsonArray();
|
||||
public ArrayNode getAsArrayNode(SettingKey key) {
|
||||
return (ArrayNode) getAsJsonNode(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T fromJson(SettingKey key, Class<T> clazz) {
|
||||
return gson.fromJson(getAsJsonElement(key), clazz);
|
||||
try {
|
||||
return jackson.treeToValue(getAsJsonNode(key), clazz);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new TimiException(TimiCode.ERROR, "read setting json error", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T fromJson(SettingKey key, TypeToken<T> typeToken) {
|
||||
return gson.fromJson(getAsJsonElement(key), typeToken);
|
||||
public <T> T fromJson(SettingKey key, TypeReference<T> typeReference) {
|
||||
return jackson.convertValue(getAsJsonNode(key), typeReference);
|
||||
}
|
||||
|
||||
public <T> T fromYaml(SettingKey key, Class<T> clazz) {
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
package com.imyeyu.api.modules.common.service.implement;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.imyeyu.api.modules.common.bean.SettingKey;
|
||||
import com.imyeyu.api.modules.common.entity.Attachment;
|
||||
import com.imyeyu.api.modules.common.service.AttachmentService;
|
||||
@@ -31,8 +29,6 @@ import java.util.List;
|
||||
@RequiredArgsConstructor
|
||||
public class TempFileServiceImplement implements TempFileService {
|
||||
|
||||
private final Gson gson;
|
||||
|
||||
private final SettingService settingService;
|
||||
private final AttachmentService attachmentService;
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package com.imyeyu.api.modules.common.task;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
|
||||
import com.imyeyu.api.modules.common.bean.SettingKey;
|
||||
import com.imyeyu.api.modules.common.entity.Multilingual;
|
||||
@@ -81,6 +80,7 @@ public class MultilingualTranslateTask {
|
||||
}
|
||||
}
|
||||
|
||||
private final ObjectMapper jackson;
|
||||
private final SettingService settingService;
|
||||
private final MultilingualService service;
|
||||
|
||||
@@ -147,18 +147,17 @@ public class MultilingualTranslateTask {
|
||||
.execute()
|
||||
.returnContent()
|
||||
.asString();
|
||||
JsonObject jo = JsonParser.parseString(response).getAsJsonObject();
|
||||
JsonNode jo = jackson.readTree(response);
|
||||
if (jo.has("error_code")) {
|
||||
System.err.println(jo);
|
||||
throw new TimiException(TimiCode.ERROR, jo.get("error_msg").getAsString());
|
||||
throw new TimiException(TimiCode.ERROR, jo.get("error_msg").asText());
|
||||
}
|
||||
JsonArray ja = jo.get("trans_result").getAsJsonArray();
|
||||
JsonNode ja = jo.get("trans_result");
|
||||
|
||||
JsonObject resultJO;
|
||||
Map<String, String> result = new HashMap<>();
|
||||
for (int i = 0; i < ja.size(); i++) {
|
||||
resultJO = ja.get(i).getAsJsonObject();
|
||||
result.put(resultJO.get("src").getAsString(), resultJO.get("dst").getAsString());
|
||||
JsonNode resultJO = ja.get(i);
|
||||
result.put(resultJO.get("src").asText(), resultJO.get("dst").asText());
|
||||
}
|
||||
wait(200);
|
||||
return result;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.imyeyu.api.modules.git.bean.gitea;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
@@ -12,6 +12,6 @@ public class Branch {
|
||||
|
||||
private String name;
|
||||
|
||||
@SerializedName("protected")
|
||||
@JsonProperty("protected")
|
||||
private boolean isProtected;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.imyeyu.api.modules.git.bean.gitea;
|
||||
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.imyeyu.api.modules.git.util.GiteaTimestampAdapter;
|
||||
import lombok.Data;
|
||||
|
||||
@@ -36,6 +37,7 @@ public class File {
|
||||
|
||||
private String lastCommitSha;
|
||||
|
||||
@JsonAdapter(GiteaTimestampAdapter.class)
|
||||
@JsonSerialize(using = GiteaTimestampAdapter.Serializer.class)
|
||||
@JsonDeserialize(using = GiteaTimestampAdapter.Deserializer.class)
|
||||
private Long lastCommitterDate;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.imyeyu.api.modules.git.bean.gitea;
|
||||
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.imyeyu.api.modules.git.util.GiteaTimestampAdapter;
|
||||
import lombok.Data;
|
||||
|
||||
@@ -35,13 +36,16 @@ public class Repository {
|
||||
|
||||
private Boolean archived;
|
||||
|
||||
@JsonAdapter(GiteaTimestampAdapter.class)
|
||||
@JsonSerialize(using = GiteaTimestampAdapter.Serializer.class)
|
||||
@JsonDeserialize(using = GiteaTimestampAdapter.Deserializer.class)
|
||||
private Long createdAt;
|
||||
|
||||
@JsonAdapter(GiteaTimestampAdapter.class)
|
||||
@JsonSerialize(using = GiteaTimestampAdapter.Serializer.class)
|
||||
@JsonDeserialize(using = GiteaTimestampAdapter.Deserializer.class)
|
||||
private Long updatedAt;
|
||||
|
||||
@JsonAdapter(GiteaTimestampAdapter.class)
|
||||
@JsonSerialize(using = GiteaTimestampAdapter.Serializer.class)
|
||||
@JsonDeserialize(using = GiteaTimestampAdapter.Deserializer.class)
|
||||
private Long archivedAt;
|
||||
|
||||
private List<String> licenses;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package com.imyeyu.api.modules.git.service.implement;
|
||||
|
||||
import com.google.gson.FieldNamingPolicy;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
||||
import com.imyeyu.java.TimiJava;
|
||||
import com.imyeyu.java.bean.timi.TimiCode;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
@@ -56,17 +55,18 @@ import java.util.List;
|
||||
@RequiredArgsConstructor
|
||||
public class RepositoryServiceImplement implements RepositoryService {
|
||||
|
||||
private final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
|
||||
|
||||
private final UserService userService;
|
||||
private final GiteaService giteaService;
|
||||
private final SettingService settingService;
|
||||
private final ObjectMapper jackson;
|
||||
|
||||
private User owner;
|
||||
private ObjectMapper giteaJackson;
|
||||
|
||||
@PostConstruct
|
||||
private void postConstruct() {
|
||||
owner = giteaService.getOwner();
|
||||
giteaJackson = jackson.copy().setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -80,8 +80,7 @@ public class RepositoryServiceImplement implements RepositoryService {
|
||||
put("limit", page.getLimit());
|
||||
}})).execute().returnResponse();
|
||||
String respText = EntityUtils.toString(resp.getEntity());
|
||||
GiteaResponse<List<Repository>> respObj = gson.fromJson(respText, new TypeToken<GiteaResponse<List<Repository>>>() {
|
||||
}.getType());
|
||||
GiteaResponse<List<Repository>> respObj = giteaJackson.readValue(respText, new TypeReference<>() {});
|
||||
|
||||
PageResult<Repository> result = new PageResult<>();
|
||||
result.setTotal(Long.parseLong(resp.getHeader("X-Total-Count").getValue()));
|
||||
@@ -100,7 +99,7 @@ public class RepositoryServiceImplement implements RepositoryService {
|
||||
put("owner", owner.getName());
|
||||
put("repoName", repoName);
|
||||
}})).execute().returnContent().asString();
|
||||
return gson.fromJson(respText, Repository.class);
|
||||
return giteaJackson.readValue(respText, Repository.class);
|
||||
} catch (Exception e) {
|
||||
log.error("get repository error", e);
|
||||
throw new TimiException(TimiCode.ERROR, "get repository error", e);
|
||||
@@ -110,11 +109,11 @@ public class RepositoryServiceImplement implements RepositoryService {
|
||||
@Override
|
||||
public List<Branch> listBranches(String repoName) {
|
||||
try {
|
||||
String respText = Request.get(API.REPO_BRANCHES_LIST.buildURL(new HashMap<>() {{
|
||||
String respText = Request.get(API.REPO_BRANCHES_LIST.buildURL(new HashMap<>() {{
|
||||
put("owner", owner.getName());
|
||||
put("repoName", repoName);
|
||||
}})).execute().returnContent().asString();
|
||||
return gson.fromJson(respText, new TypeToken<List<Branch>>() {}.getType());
|
||||
}})).execute().returnContent().asString();
|
||||
return giteaJackson.readValue(respText, new TypeReference<>() {});
|
||||
} catch (Exception e) {
|
||||
log.error("list repository branches error", e);
|
||||
throw new TimiException(TimiCode.ERROR, "list repository branches error", e);
|
||||
@@ -124,14 +123,14 @@ public class RepositoryServiceImplement implements RepositoryService {
|
||||
@Override
|
||||
public List<File> listFile(String repoName, String branch, String path) {
|
||||
try {
|
||||
String respText = Request.get(API.REPO_FILE_LIST.buildURL(new HashMap<>() {{
|
||||
String respText = Request.get(API.REPO_FILE_LIST.buildURL(new HashMap<>() {{
|
||||
put("owner", owner.getName());
|
||||
put("repoName", repoName);
|
||||
put("path", path);
|
||||
}}, new HashMap<>() {{
|
||||
}}, new HashMap<>() {{
|
||||
put("ref", branch);
|
||||
}})).execute().returnContent().asString();
|
||||
List<File> list = gson.fromJson(respText, new TypeToken<List<File>>() {}.getType());
|
||||
}})).execute().returnContent().asString();
|
||||
List<File> list = giteaJackson.readValue(respText, new TypeReference<>() {});
|
||||
// 排序
|
||||
list.sort((f1, f2) -> {
|
||||
if (f1.getType() == File.Type.dir && f2.getType() == File.Type.file) {
|
||||
|
||||
@@ -1,28 +1,54 @@
|
||||
package com.imyeyu.api.modules.git.util;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import com.fasterxml.jackson.core.JacksonException;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* @author 夜雨
|
||||
* @since 2025-06-26 11:40
|
||||
*/
|
||||
public class GiteaTimestampAdapter implements JsonSerializer<Long>, JsonDeserializer<Long> {
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public final class GiteaTimestampAdapter {
|
||||
|
||||
@Override
|
||||
public Long deserialize(JsonElement json, Type type, JsonDeserializationContext context) {
|
||||
return Instant.parse(json.getAsString()).toEpochMilli();
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2026-04-07 22:16
|
||||
*/
|
||||
public static class Serializer extends JsonSerializer<Long> {
|
||||
|
||||
@Override
|
||||
public void serialize(Long value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||
if (value == null) {
|
||||
gen.writeNull();
|
||||
} else {
|
||||
gen.writeNumber(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(Long src, Type type, JsonSerializationContext context) {
|
||||
return new JsonPrimitive(src);
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2026-04-07 22:19
|
||||
*/
|
||||
public static class Deserializer extends JsonDeserializer<Long> {
|
||||
|
||||
@Override
|
||||
public Long deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
|
||||
return Instant.parse(p.getText()).toEpochMilli();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package com.imyeyu.api.modules.gitea.bean;
|
||||
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.imyeyu.api.modules.gitea.util.GiteaUTCTimestampAdapter;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
@@ -50,14 +51,15 @@ public class ActionLogDTO {
|
||||
@Data
|
||||
public static class Commit {
|
||||
|
||||
@SerializedName("Sha1")
|
||||
@JsonProperty("Sha1")
|
||||
private String sha;
|
||||
|
||||
@SerializedName("Message")
|
||||
@JsonProperty("Message")
|
||||
private String message;
|
||||
|
||||
@JsonAdapter(GiteaUTCTimestampAdapter.class)
|
||||
@SerializedName("Timestamp")
|
||||
@JsonProperty("Timestamp")
|
||||
@JsonSerialize(using = GiteaUTCTimestampAdapter.Serializer.class)
|
||||
@JsonDeserialize(using = GiteaUTCTimestampAdapter.Deserializer.class)
|
||||
private Long timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package com.imyeyu.api.modules.gitea.util;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import com.fasterxml.jackson.core.JacksonException;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
@@ -18,15 +19,21 @@ import java.time.ZoneOffset;
|
||||
* @author 夜雨
|
||||
* @since 2025-06-27 16:18
|
||||
*/
|
||||
public class GiteaUTCTimestampAdapter implements JsonDeserializer<Long>, JsonSerializer<Long> {
|
||||
public class GiteaUTCTimestampAdapter {
|
||||
|
||||
@Override
|
||||
public Long deserialize(JsonElement json, Type type, JsonDeserializationContext context) {
|
||||
return OffsetDateTime.parse(json.getAsString()).toInstant().toEpochMilli();
|
||||
public static class Deserializer extends JsonDeserializer<Long> {
|
||||
|
||||
@Override
|
||||
public Long deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
|
||||
return OffsetDateTime.parse(p.getText()).toInstant().toEpochMilli();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(Long timestamp, Type type, JsonSerializationContext context) {
|
||||
return new JsonPrimitive(OffsetDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC).toString());
|
||||
public static class Serializer extends JsonSerializer<Long> {
|
||||
|
||||
@Override
|
||||
public void serialize(Long timestamp, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||
gen.writeString(OffsetDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC).toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package com.imyeyu.api.modules.gitea.vo;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.imyeyu.java.TimiJava;
|
||||
import com.imyeyu.api.TimiServerAPI;
|
||||
import com.imyeyu.api.modules.common.service.UserService;
|
||||
@@ -34,7 +33,7 @@ public class ActionLogView {
|
||||
private UserView operator;
|
||||
|
||||
public static ActionLogView fromDTO(ActionLogDTO dto) {
|
||||
Gson gson = TimiServerAPI.applicationContext.getBean(Gson.class);
|
||||
ObjectMapper jackson = TimiServerAPI.applicationContext.getBean(ObjectMapper.class);
|
||||
UserService userService = TimiServerAPI.applicationContext.getBean(UserService.class);
|
||||
|
||||
ActionLogView view = new ActionLogView();
|
||||
@@ -45,15 +44,19 @@ public class ActionLogView {
|
||||
view.setOperator(userService.view((long) dto.getOperatorId()).doFilter());
|
||||
{
|
||||
if (TimiJava.isNotEmpty(dto.getContent())) {
|
||||
JsonObject content = JsonParser.parseString(dto.getContent()).getAsJsonObject();
|
||||
List<ActionLogDTO.Commit> commitList = gson.fromJson(content.get("Commits"), new TypeToken<List<ActionLogDTO.Commit>>() {}.getType());
|
||||
view.setCommitList(new ArrayList<>());
|
||||
for (ActionLogDTO.Commit dtoCommit : commitList) {
|
||||
Commit commit = new Commit();
|
||||
commit.setSha(dtoCommit.getSha());
|
||||
commit.setMessage(dtoCommit.getMessage().trim());
|
||||
commit.setCommittedAt(dtoCommit.getTimestamp());
|
||||
view.getCommitList().add(commit);
|
||||
try {
|
||||
JsonNode content = jackson.readTree(dto.getContent());
|
||||
List<ActionLogDTO.Commit> commitList = jackson.convertValue(content.get("Commits"), new TypeReference<>() {});
|
||||
view.setCommitList(new ArrayList<>());
|
||||
for (ActionLogDTO.Commit dtoCommit : commitList) {
|
||||
Commit commit = new Commit();
|
||||
commit.setSha(dtoCommit.getSha());
|
||||
commit.setMessage(dtoCommit.getMessage().trim());
|
||||
commit.setCommittedAt(dtoCommit.getTimestamp());
|
||||
view.getCommitList().add(commit);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("read gitea action log content error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,4 +78,4 @@ public class ActionLogView {
|
||||
|
||||
private Long committedAt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.imyeyu.api.modules.journal.controller;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.imyeyu.api.bean.PreviewPage;
|
||||
import com.imyeyu.api.bean.wechat.InitCodeResponse;
|
||||
import com.imyeyu.api.modules.common.bean.MediaAttach;
|
||||
@@ -18,10 +19,8 @@ import com.imyeyu.api.modules.journal.vo.journal.UpdateRequest;
|
||||
import com.imyeyu.java.bean.timi.TimiCode;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import com.imyeyu.network.ArgMap;
|
||||
import com.imyeyu.network.GsonRequest;
|
||||
import com.imyeyu.spring.annotation.AOPLog;
|
||||
import com.imyeyu.spring.annotation.RequestRateLimit;
|
||||
import com.imyeyu.spring.annotation.RequestSingleParam;
|
||||
import com.imyeyu.spring.bean.Page;
|
||||
import com.imyeyu.spring.bean.PageResult;
|
||||
import jakarta.validation.Valid;
|
||||
@@ -53,6 +52,7 @@ public class JournalController {
|
||||
private final JournalService service;
|
||||
private final SettingService settingService;
|
||||
private final AttachmentService attachmentService;
|
||||
private final ObjectMapper jackson;
|
||||
|
||||
private final JournalAPIInterceptor apiInterceptor;
|
||||
|
||||
@@ -65,14 +65,18 @@ public class JournalController {
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@PostMapping("/openid")
|
||||
public String initOpenId(@RequestSingleParam String code) {
|
||||
public String initOpenId(@RequestBody String code) {
|
||||
try {
|
||||
ArgMap<String, String> args = new ArgMap<>();
|
||||
args.put("appid", settingService.getAsString(SettingKey.JOURNAL_APP_ID));
|
||||
args.put("secret", settingService.getAsString(SettingKey.JOURNAL_APP_SECRET));
|
||||
args.put("js_code", code);
|
||||
args.put("grant_type", "authorization_code");
|
||||
InitCodeResponse resp = GsonRequest.get(args.toURL("https://api.weixin.qq.com/sns/jscode2session")).resultAs(InitCodeResponse.class);
|
||||
String response = org.apache.hc.client5.http.fluent.Request.get(args.toURL("https://api.weixin.qq.com/sns/jscode2session"))
|
||||
.execute()
|
||||
.returnContent()
|
||||
.asString();
|
||||
InitCodeResponse resp = jackson.readValue(response, InitCodeResponse.class);
|
||||
return resp.getOpenid();
|
||||
} catch (Exception e) {
|
||||
log.error("init WeChat openId error", e);
|
||||
@@ -139,7 +143,7 @@ public class JournalController {
|
||||
@AOPLog
|
||||
@RequiredUploadPermission
|
||||
@PostMapping("/delete")
|
||||
public void delete(@RequestSingleParam Long id) {
|
||||
public void delete(@RequestBody Long id) {
|
||||
service.delete(id);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import com.imyeyu.api.modules.common.bean.SettingKey;
|
||||
import com.imyeyu.api.modules.common.entity.Setting;
|
||||
import com.imyeyu.api.modules.common.service.SettingService;
|
||||
import com.imyeyu.api.modules.journal.bean.RequiredUploadPermission;
|
||||
import com.imyeyu.spring.annotation.RequestSingleParam;
|
||||
import com.imyeyu.spring.annotation.RequestBodyValue;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -31,7 +31,7 @@ public class ToolController {
|
||||
|
||||
@RequiredUploadPermission
|
||||
@PostMapping("/memo/update")
|
||||
public void updateMemo(@RequestSingleParam String data) {
|
||||
public void updateMemo(@RequestBodyValue String data) {
|
||||
Setting setting = settingService.getByKey(SettingKey.JOURNAL_MEMO);
|
||||
setting.setValue(data);
|
||||
settingService.update(setting);
|
||||
|
||||
@@ -5,7 +5,6 @@ import com.imyeyu.api.modules.journal.entity.Travel;
|
||||
import com.imyeyu.api.modules.journal.service.TravelService;
|
||||
import com.imyeyu.spring.annotation.AOPLog;
|
||||
import com.imyeyu.spring.annotation.RequestRateLimit;
|
||||
import com.imyeyu.spring.annotation.RequestSingleParam;
|
||||
import com.imyeyu.spring.bean.Page;
|
||||
import com.imyeyu.spring.bean.PageResult;
|
||||
import jakarta.validation.Valid;
|
||||
@@ -67,7 +66,7 @@ public class TravelController {
|
||||
@RequestRateLimit
|
||||
@RequiredUploadPermission
|
||||
@PostMapping("/delete")
|
||||
public void delete(@RequestSingleParam Long id) {
|
||||
public void delete(@RequestBody Long id) {
|
||||
service.delete(id);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import com.imyeyu.api.modules.journal.entity.TravelLocation;
|
||||
import com.imyeyu.api.modules.journal.service.TravelLocationService;
|
||||
import com.imyeyu.spring.annotation.AOPLog;
|
||||
import com.imyeyu.spring.annotation.RequestRateLimit;
|
||||
import com.imyeyu.spring.annotation.RequestSingleParam;
|
||||
import com.imyeyu.spring.bean.Page;
|
||||
import com.imyeyu.spring.bean.PageResult;
|
||||
import jakarta.validation.Valid;
|
||||
@@ -74,7 +73,7 @@ public class TravelLocationController {
|
||||
@RequestRateLimit
|
||||
@RequiredUploadPermission
|
||||
@PostMapping("/delete")
|
||||
public void delete(@RequestSingleParam Long id) {
|
||||
public void delete(@RequestBody Long id) {
|
||||
service.delete(id);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.imyeyu.api.modules.journal.service.implement;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
|
||||
import com.imyeyu.api.modules.common.bean.MediaAttach;
|
||||
import com.imyeyu.api.modules.common.bean.Metadata;
|
||||
@@ -40,7 +40,7 @@ import java.util.stream.Collectors;
|
||||
@RequiredArgsConstructor
|
||||
public class JournalServiceImplement extends AbstractEntityService<Journal, Long> implements JournalService {
|
||||
|
||||
private final Gson gson;
|
||||
private final ObjectMapper jackson;
|
||||
|
||||
private final SettingService settingService;
|
||||
private final TempFileService tempFileService;
|
||||
@@ -192,7 +192,7 @@ public class JournalServiceImplement extends AbstractEntityService<Journal, Long
|
||||
thumbAttach.setBizId(journal.getId());
|
||||
attachmentService.update(thumbAttach);
|
||||
|
||||
Metadata.ThumbImage thumbMetadata = gson.fromJson(thumbAttach.getMetadata(), Metadata.ThumbImage.class);
|
||||
Metadata.ThumbImage thumbMetadata = jackson.convertValue(thumbAttach.getMetadata(), Metadata.ThumbImage.class);
|
||||
Attachment sourceAttach = attachmentService.get(thumbMetadata.getSourceId());
|
||||
sourceAttach.setBizType(Attachment.BizType.JOURNAL);
|
||||
sourceAttach.setBizId(journal.getId());
|
||||
|
||||
@@ -53,7 +53,7 @@ public class JournalAPIInterceptor implements HandlerInterceptor {
|
||||
|
||||
public boolean canAccess() {
|
||||
String reqKey = TimiJava.defaultIfEmpty(TimiSpring.getHeader("Key"), TimiSpring.getRequestArg("key"));
|
||||
return keys.contains(reqKey);
|
||||
return TimiJava.isNotEmpty(reqKey) && keys.contains(reqKey);
|
||||
}
|
||||
|
||||
public boolean canUploadKey() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.imyeyu.api.modules.lyric.service.implement;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.imyeyu.java.bean.timi.TimiCode;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import com.imyeyu.api.modules.lyric.entity.Lyric;
|
||||
@@ -50,6 +50,7 @@ public class LyricServiceImplement implements LyricService {
|
||||
|
||||
private final LyricMapper mapper;
|
||||
private final Map<Long, String> allLyric;
|
||||
private final ObjectMapper jackson;
|
||||
|
||||
@Override
|
||||
public Lyric get(Long id) {
|
||||
@@ -113,12 +114,12 @@ public class LyricServiceImplement implements LyricService {
|
||||
Request request = Request.get(API_GET_SONG + Encoder.urlArgs(args));
|
||||
HEADER.forEach(request::addHeader);
|
||||
response = request.execute().returnContent().asString();
|
||||
JsonObject data = JsonParser.parseString(response).getAsJsonObject().get("data").getAsJsonObject().get("info").getAsJsonArray().get(0).getAsJsonObject();
|
||||
JsonNode data = jackson.readTree(response).path("data").path("info").get(0);
|
||||
|
||||
args.clear();
|
||||
args.put("cmd", "100");
|
||||
args.put("keyword", data.get("songname").getAsString());
|
||||
args.put("hash", data.get("hash").getAsString());
|
||||
args.put("keyword", data.path("songname").asText());
|
||||
args.put("hash", data.path("hash").asText());
|
||||
args.put("timelength", tl);
|
||||
args.put("d", String.valueOf(Math.random()));
|
||||
request = Request.get(API_GET_LRC + Encoder.urlArgs(args));
|
||||
@@ -128,8 +129,8 @@ public class LyricServiceImplement implements LyricService {
|
||||
}
|
||||
|
||||
Lyric lyric = new Lyric();
|
||||
lyric.setSong(data.get("songname").getAsString());
|
||||
lyric.setSinger(data.get("singername").getAsString());
|
||||
lyric.setSong(data.path("songname").asText());
|
||||
lyric.setSinger(data.path("singername").asText());
|
||||
lyric.setData(response);
|
||||
return lyric;
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -17,7 +17,6 @@ import com.imyeyu.api.modules.minecraft.vo.TokenRequest;
|
||||
import com.imyeyu.api.modules.minecraft.vo.TokenResponse;
|
||||
import com.imyeyu.spring.annotation.AOPLog;
|
||||
import com.imyeyu.spring.annotation.RequestRateLimit;
|
||||
import com.imyeyu.spring.annotation.RequestSingleParam;
|
||||
import com.imyeyu.spring.annotation.RequiredToken;
|
||||
import com.imyeyu.spring.util.Redis;
|
||||
import com.imyeyu.spring.util.RedisSerializers;
|
||||
@@ -73,7 +72,7 @@ public class PlayerController {
|
||||
@RequiredToken
|
||||
@RequestRateLimit
|
||||
@PostMapping("/bind")
|
||||
public void bind(@RequestSingleParam String name) {
|
||||
public void bind(@RequestBody String name) {
|
||||
MinecraftPlayer player = new MinecraftPlayer();
|
||||
player.setName(name);
|
||||
player.setUserId(userService.getLoginUser().getId());
|
||||
@@ -84,7 +83,7 @@ public class PlayerController {
|
||||
@RequiredToken
|
||||
@RequestRateLimit
|
||||
@PostMapping("/unbind")
|
||||
public void unbind(@RequestSingleParam Long id) {
|
||||
public void unbind(@RequestBody Long id) {
|
||||
service.listByUserId(userService.getLoginUser().getId())
|
||||
.stream()
|
||||
.filter(player -> player.getId().equals(id))
|
||||
@@ -113,7 +112,7 @@ public class PlayerController {
|
||||
@RequestRateLimit
|
||||
@EnableSetting(value = SettingKey.FMC_PLAYER_LOGIN_ENABLE, message = "登录服务未启用")
|
||||
@PostMapping("/login")
|
||||
public TokenResponse login(@RequestSingleParam Long playerId, @RequestHeader("Token") String token) {
|
||||
public TokenResponse login(@RequestBody Long playerId, @RequestHeader("Token") String token) {
|
||||
return service.login(playerId, token);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.imyeyu.api.modules.mirror;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.imyeyu.api.modules.common.entity.Attachment;
|
||||
import com.imyeyu.api.modules.common.service.AttachmentService;
|
||||
import com.imyeyu.api.modules.mirror.bean.AttachType;
|
||||
@@ -47,7 +47,7 @@ public class FabricAPIMirror extends AttachmentMirror {
|
||||
/** 版本匹配正则 */
|
||||
private static final Pattern versionRegex = Pattern.compile("^(\\d+\\.){1,2}(\\*|\\d+)$");
|
||||
|
||||
private final Gson gson;
|
||||
private final ObjectMapper jackson;
|
||||
|
||||
private final MirrorService service;
|
||||
private final AttachmentService attachmentService;
|
||||
@@ -100,7 +100,7 @@ public class FabricAPIMirror extends AttachmentMirror {
|
||||
result.add(item);
|
||||
}
|
||||
}
|
||||
mirror.setData(gson.toJsonTree(result));
|
||||
mirror.setData(jackson.valueToTree(result));
|
||||
service.update(mirror);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
package com.imyeyu.api.modules.mirror;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.imyeyu.java.bean.timi.TimiCode;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import com.imyeyu.java.ref.Ref;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.imyeyu.api.modules.mirror.data.OpenJDK;
|
||||
import com.imyeyu.api.modules.mirror.entity.Mirror;
|
||||
import com.imyeyu.api.modules.mirror.service.MirrorService;
|
||||
import com.imyeyu.java.bean.timi.TimiCode;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import com.imyeyu.java.ref.Ref;
|
||||
import com.imyeyu.utils.OS;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -27,9 +24,9 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Github JDK 镜像,仅同步下载链接等信息,不储存文件
|
||||
* Github JDK 闀滃儚锛屼粎鍚屾涓嬭浇閾炬帴绛変俊鎭紝涓嶅偍瀛樻枃浠?
|
||||
*
|
||||
* @author 夜雨
|
||||
* @author 澶滈洦
|
||||
* @version 2024-06-10 10:51
|
||||
*/
|
||||
@Slf4j
|
||||
@@ -37,10 +34,10 @@ import java.util.Map;
|
||||
@RequiredArgsConstructor
|
||||
public class OpenJDKGithubMirror extends AbstractMirror {
|
||||
|
||||
/** 版本发布列表接口,插值 {@link #REPOS_MAP} 的值 */
|
||||
/** 鐗堟湰鍙戝竷鍒楄〃鎺ュ彛锛屾彃鍊?{@link #REPOS_MAP} 鐨勫€?*/
|
||||
private static final String API_RELEASE = "https://api.github.com/repos/adoptium/%s/releases?page=1";
|
||||
|
||||
/** 版本仓库映射,key 为版本,value 为对应仓库,只读 */
|
||||
/** 鐗堟湰浠撳簱鏄犲皠锛宬ey 涓虹増鏈紝value 涓哄搴斾粨搴擄紝鍙 */
|
||||
private static final Map<String, String> REPOS_MAP = Collections.unmodifiableMap(new HashMap<>() {{
|
||||
put("8", "temurin8-binaries");
|
||||
put("11", "temurin11-binaries");
|
||||
@@ -48,31 +45,30 @@ public class OpenJDKGithubMirror extends AbstractMirror {
|
||||
put("21", "temurin21-binaries");
|
||||
}});
|
||||
|
||||
private final Gson gson;
|
||||
private final ObjectMapper jackson;
|
||||
private final MirrorService service;
|
||||
|
||||
@Override
|
||||
protected void sync(Mirror mirror) throws Exception {
|
||||
mirror.setData(gson.toJsonTree(fetch()));
|
||||
mirror.setData(jackson.valueToTree(fetch()));
|
||||
service.update(mirror);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 {@link OpenJDK} 列表,{@link OpenJDKMirror} 也会使用此接口
|
||||
* 鑾峰彇 {@link OpenJDK} 鍒楄〃锛寋@link OpenJDKMirror} 涔熶細浣跨敤姝ゆ帴鍙?
|
||||
*
|
||||
* @return jdk 列表
|
||||
* @throws Exception 获取异常
|
||||
* @return jdk 鍒楄〃
|
||||
* @throws Exception 鑾峰彇寮傚父
|
||||
*/
|
||||
final List<OpenJDK> fetch() throws Exception {
|
||||
List<OpenJDK> result = new ArrayList<>();
|
||||
for (Map.Entry<String, String> repo : REPOS_MAP.entrySet()) {
|
||||
String respText = Request.get(API_RELEASE.formatted(repo.getValue())).connectTimeout(Timeout.ofSeconds(60)).execute().returnContent().asString();
|
||||
JsonArray root = JsonParser.parseString(respText).getAsJsonArray();
|
||||
JsonObject itemRel = null;
|
||||
for (JsonElement el : root) {
|
||||
itemRel = el.getAsJsonObject();
|
||||
if (itemRel.get("prerelease").getAsBoolean() || itemRel.get("draft").getAsBoolean()) {
|
||||
// 忽略草稿或预发布版本
|
||||
JsonNode root = jackson.readTree(respText);
|
||||
JsonNode itemRel = null;
|
||||
for (JsonNode el : root) {
|
||||
itemRel = el;
|
||||
if (itemRel.path("prerelease").asBoolean() || itemRel.path("draft").asBoolean()) {
|
||||
itemRel = null;
|
||||
} else {
|
||||
break;
|
||||
@@ -81,12 +77,8 @@ public class OpenJDKGithubMirror extends AbstractMirror {
|
||||
if (itemRel == null) {
|
||||
throw new TimiException(TimiCode.ERROR, "not found release item for " + repo.getValue());
|
||||
}
|
||||
JsonArray assets = itemRel.get("assets").getAsJsonArray();
|
||||
for (JsonElement asset : assets) {
|
||||
JsonObject itemAsset = asset.getAsJsonObject();
|
||||
// OpenJDK21U-jdk_x64_windows_hotspot_21.0.3_9.zip
|
||||
String name = itemAsset.get("name").getAsString();
|
||||
|
||||
for (JsonNode itemAsset : itemRel.path("assets")) {
|
||||
String name = itemAsset.path("name").asText();
|
||||
if (!name.contains("-") || !name.contains("_")) {
|
||||
continue;
|
||||
}
|
||||
@@ -102,14 +94,13 @@ public class OpenJDKGithubMirror extends AbstractMirror {
|
||||
}
|
||||
OS.Platform platform = Ref.toType(OS.Platform.class, split[2].toUpperCase());
|
||||
OpenJDK.Type type = Ref.toType(OpenJDK.Type.class, split[0].toUpperCase());
|
||||
|
||||
if (platform != null && type != null) {
|
||||
OpenJDK jdk = new OpenJDK();
|
||||
jdk.setPlatform(platform);
|
||||
jdk.setType(type);
|
||||
jdk.setName(name);
|
||||
jdk.setVersion(repo.getKey());
|
||||
jdk.setData(URLDecoder.decode(itemAsset.get("browser_download_url").getAsString(), StandardCharsets.UTF_8));
|
||||
jdk.setData(URLDecoder.decode(itemAsset.path("browser_download_url").asText(), StandardCharsets.UTF_8));
|
||||
result.add(jdk);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.imyeyu.api.modules.mirror;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.imyeyu.api.modules.common.entity.Attachment;
|
||||
import com.imyeyu.api.modules.common.service.AttachmentService;
|
||||
import com.imyeyu.api.modules.mirror.bean.AttachType;
|
||||
@@ -30,7 +30,7 @@ import java.util.stream.Collectors;
|
||||
@RequiredArgsConstructor
|
||||
public class OpenJDKMirror extends AttachmentMirror {
|
||||
|
||||
private final Gson gson;
|
||||
private final ObjectMapper jackson;
|
||||
private final MirrorService service;
|
||||
private final AttachmentService attachmentService;
|
||||
private final OpenJDKGithubMirror githubMirror;
|
||||
@@ -64,7 +64,7 @@ public class OpenJDKMirror extends AttachmentMirror {
|
||||
result.add(jdk);
|
||||
}
|
||||
}
|
||||
mirror.setData(gson.toJsonTree(result));
|
||||
mirror.setData(jackson.valueToTree(result));
|
||||
service.update(mirror);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package com.imyeyu.api.modules.mirror;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.imyeyu.api.modules.mirror.data.OpenJDK;
|
||||
import com.imyeyu.api.modules.mirror.entity.Mirror;
|
||||
import com.imyeyu.api.modules.mirror.service.MirrorService;
|
||||
import com.imyeyu.utils.OS;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
@@ -41,18 +41,18 @@ public class OpenJDKTunaMirror extends AbstractMirror {
|
||||
/** 版本列表 */
|
||||
private static final String[] VERSIONS = {"8", "11", "17", "21"};
|
||||
|
||||
private final Gson gson;
|
||||
private final ObjectMapper jackson;
|
||||
private final MirrorService service;
|
||||
|
||||
@Override
|
||||
protected void sync(Mirror mirror) throws Exception {
|
||||
List<OpenJDK> result = new ArrayList<>();
|
||||
for (int i = 0; i < VERSIONS.length; i++) {
|
||||
for (String version : VERSIONS) {
|
||||
OpenJDK.Type[] types = OpenJDK.Type.values();
|
||||
for (int j = 0; j < types.length; j++) {
|
||||
for (OpenJDK.Type type : types) {
|
||||
OS.Platform[] platforms = OS.Platform.values();
|
||||
for (int k = 0; k < platforms.length; k++) {
|
||||
String url = PAGE_URL_TEMPLATE.formatted(VERSIONS[i], types[j].toString().toLowerCase(), platforms[k].toString().toLowerCase());
|
||||
for (OS.Platform platform : platforms) {
|
||||
String url = PAGE_URL_TEMPLATE.formatted(version, type.toString().toLowerCase(), platform.toString().toLowerCase());
|
||||
Document document = Jsoup.connect(url).get();
|
||||
Element fileList = document.getElementById("list");
|
||||
Elements linkTDList = fileList.getElementsByClass("link");
|
||||
@@ -64,10 +64,10 @@ public class OpenJDKTunaMirror extends AbstractMirror {
|
||||
continue;
|
||||
}
|
||||
OpenJDK jdk = new OpenJDK();
|
||||
jdk.setPlatform(platforms[k]);
|
||||
jdk.setType(types[j]);
|
||||
jdk.setPlatform(platform);
|
||||
jdk.setType(type);
|
||||
jdk.setName(href);
|
||||
jdk.setVersion(VERSIONS[i]);
|
||||
jdk.setVersion(version);
|
||||
jdk.setData(url + href);
|
||||
|
||||
result.add(jdk);
|
||||
@@ -77,7 +77,7 @@ public class OpenJDKTunaMirror extends AbstractMirror {
|
||||
}
|
||||
}
|
||||
}
|
||||
mirror.setData(gson.toJsonTree(result));
|
||||
mirror.setData(jackson.valueToTree(result));
|
||||
service.update(mirror);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.imyeyu.api.modules.mirror.controller;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.imyeyu.api.modules.mirror.entity.Mirror;
|
||||
import com.imyeyu.api.modules.mirror.service.MirrorService;
|
||||
import com.imyeyu.api.modules.mirror.vo.MirrorView;
|
||||
@@ -38,7 +38,7 @@ public class MirrorController {
|
||||
*/
|
||||
@RequestRateLimit
|
||||
@GetMapping("/{mirrorName}")
|
||||
public JsonElement get(@PathVariable String mirrorName) {
|
||||
public JsonNode get(@PathVariable String mirrorName) {
|
||||
return service.getByName(mirrorName).getData();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.imyeyu.api.modules.mirror.entity;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.imyeyu.spring.entity.Entity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import com.imyeyu.spring.entity.Entity;
|
||||
|
||||
/**
|
||||
* 镜像
|
||||
@@ -22,7 +22,7 @@ public class Mirror extends Entity {
|
||||
protected String name;
|
||||
|
||||
/** 同步数据 */
|
||||
protected JsonElement data;
|
||||
protected JsonNode data;
|
||||
|
||||
/** 周期(分钟) */
|
||||
protected int period;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.imyeyu.api.modules.music.core;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
@@ -36,7 +37,7 @@ public class Middleware implements SchedulingConfigurer {
|
||||
private static final String CACHE_CLEAR_CORN = "0 0/20 * * * ?";
|
||||
|
||||
@Autowired
|
||||
private Gson gson;
|
||||
private ObjectMapper jackson;
|
||||
|
||||
/** 缓存频道,Map<频道 ID, 关联频道> */
|
||||
private final Map<String, ChannelBinding> channels = new HashMap<>();
|
||||
@@ -67,7 +68,7 @@ public class Middleware implements SchedulingConfigurer {
|
||||
channelBinding.setPlayerChannel(ctx);
|
||||
channelBinding.setLastActiviedAt(Time.now());
|
||||
if (channelBinding.getControllerChannel() != null) {
|
||||
channelBinding.getControllerChannel().writeAndFlush(new TextWebSocketFrame(gson.toJson(pkg)));
|
||||
channelBinding.getControllerChannel().writeAndFlush(new TextWebSocketFrame(writeValueAsString(pkg)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,11 +91,19 @@ public class Middleware implements SchedulingConfigurer {
|
||||
channelBinding.setControllerChannel(ctx);
|
||||
channelBinding.setLastActiviedAt(Time.now());
|
||||
if (pkg.getAction() != null && channelBinding.getPlayerChannel() != null) {
|
||||
byte[] bytes = gson.toJson(pkg).getBytes(StandardCharsets.UTF_8);
|
||||
byte[] bytes = writeValueAsString(pkg).getBytes(StandardCharsets.UTF_8);
|
||||
ByteBuf buffer = Unpooled.buffer();
|
||||
buffer.writeInt(bytes.length);
|
||||
buffer.writeBytes(bytes);
|
||||
channelBinding.getPlayerChannel().writeAndFlush(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
private String writeValueAsString(Object value) {
|
||||
try {
|
||||
return jackson.writeValueAsString(value);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IllegalStateException("write music websocket payload error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.imyeyu.api.modules.music.handler;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
@@ -26,7 +26,7 @@ import org.springframework.stereotype.Component;
|
||||
public class ControllerMessageHandler extends SimpleChannelInboundHandler<WebSocketFrame> {
|
||||
|
||||
@Autowired
|
||||
private Gson gson;
|
||||
private ObjectMapper jackson;
|
||||
|
||||
@Autowired
|
||||
private Middleware middleware;
|
||||
@@ -34,7 +34,11 @@ public class ControllerMessageHandler extends SimpleChannelInboundHandler<WebSoc
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame msg) {
|
||||
if (msg instanceof TextWebSocketFrame textWebSocketFrame) {
|
||||
middleware.pushControllerData(ctx, gson.fromJson(textWebSocketFrame.text(), ControllerPackage.class));
|
||||
try {
|
||||
middleware.pushControllerData(ctx, jackson.readValue(textWebSocketFrame.text(), ControllerPackage.class));
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("read controller websocket payload error", e);
|
||||
}
|
||||
} else {
|
||||
ctx.channel().writeAndFlush(WebSocketCloseStatus.INVALID_MESSAGE_TYPE).addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.imyeyu.api.modules.music.handler;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
@@ -22,14 +22,18 @@ import org.springframework.stereotype.Component;
|
||||
public class PlayerMessageHandler extends SimpleChannelInboundHandler<String> {
|
||||
|
||||
@Autowired
|
||||
private Gson gson;
|
||||
private ObjectMapper jackson;
|
||||
|
||||
@Autowired
|
||||
private Middleware middleware;
|
||||
|
||||
@Override
|
||||
protected synchronized void channelRead0(ChannelHandlerContext ctx, String result) {
|
||||
middleware.pushPlayerData(ctx, gson.fromJson(result, PlayerPackage.class));
|
||||
try {
|
||||
middleware.pushPlayerData(ctx, jackson.readValue(result, PlayerPackage.class));
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("read player websocket payload error", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
package com.imyeyu.api.modules.system.bean;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Docker 容器状态缓存
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
public class DockerStatusStore {
|
||||
|
||||
/** 容器状态映射 */
|
||||
private final Map<String, Container> containers = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* 容器缓存
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@Data
|
||||
public static class Container {
|
||||
|
||||
/** 容器 ID */
|
||||
private String id;
|
||||
|
||||
/** 容器名称 */
|
||||
private String name;
|
||||
|
||||
/** 镜像 */
|
||||
private String image;
|
||||
|
||||
/** 镜像 ID */
|
||||
private String imageId;
|
||||
|
||||
/** 创建时间 */
|
||||
private long createdAt;
|
||||
|
||||
/** 运行状态 */
|
||||
private String state;
|
||||
|
||||
/** 状态描述 */
|
||||
private String status;
|
||||
|
||||
/** 健康状态 */
|
||||
private String healthStatus;
|
||||
|
||||
/** 启动时间 */
|
||||
private String startedAt;
|
||||
|
||||
/** 结束时间 */
|
||||
private String finishedAt;
|
||||
|
||||
/** 退出码 */
|
||||
private Integer exitCode;
|
||||
|
||||
/** 重启次数 */
|
||||
private int restartCount;
|
||||
|
||||
/** OOM 标记 */
|
||||
private boolean oomKilled;
|
||||
|
||||
/** CPU 百分比 */
|
||||
private Double cpuPercent;
|
||||
|
||||
/** 内存使用量 */
|
||||
private Long memoryUsageBytes;
|
||||
|
||||
/** 内存限制 */
|
||||
private Long memoryLimitBytes;
|
||||
|
||||
/** 内存百分比 */
|
||||
private Double memoryPercent;
|
||||
|
||||
/** 网络接收字节 */
|
||||
private Long networkRxBytes;
|
||||
|
||||
/** 网络发送字节 */
|
||||
private Long networkTxBytes;
|
||||
|
||||
/** 块设备读取字节 */
|
||||
private Long blockReadBytes;
|
||||
|
||||
/** 块设备写入字节 */
|
||||
private Long blockWriteBytes;
|
||||
|
||||
/** 进程数 */
|
||||
private Integer pids;
|
||||
|
||||
/** 采样时间 */
|
||||
private long updatedAt;
|
||||
|
||||
/** 历史点 */
|
||||
private final Deque<Point> history = new ArrayDeque<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 容器指标历史点
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@Data
|
||||
public static class Point {
|
||||
|
||||
/** 时间 */
|
||||
private long at;
|
||||
|
||||
/** CPU 百分比 */
|
||||
private Double cpuPercent;
|
||||
|
||||
/** 内存使用量 */
|
||||
private Long memoryUsageBytes;
|
||||
|
||||
/** 内存百分比 */
|
||||
private Double memoryPercent;
|
||||
|
||||
/** 网络接收字节 */
|
||||
private Long networkRxBytes;
|
||||
|
||||
/** 网络发送字节 */
|
||||
private Long networkTxBytes;
|
||||
|
||||
/** 块设备读取字节 */
|
||||
private Long blockReadBytes;
|
||||
|
||||
/** 块设备写入字节 */
|
||||
private Long blockWriteBytes;
|
||||
|
||||
/** 进程数 */
|
||||
private Integer pids;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.imyeyu.api.modules.system.bean;
|
||||
|
||||
import lombok.Data;
|
||||
import com.imyeyu.java.TimiJava;
|
||||
import lombok.Data;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
@@ -11,22 +11,24 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 服务器状态数据,所有动态数据左出右进,此对象由 IOC 托管
|
||||
* 服务端状态缓存
|
||||
*
|
||||
* <p>该对象只用于采集任务内部缓存,不直接作为接口协议返回。</p>
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2022-01-31 15:35
|
||||
* @since 2022-01-31 15:35
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
public class ServerStatus implements TimiJava {
|
||||
|
||||
/** 动态数据更新时轴 */
|
||||
/** 采样时间轴 */
|
||||
private LinkedList<Number> updateAxis = new LinkedList<>();
|
||||
|
||||
/** 系统 */
|
||||
/** 操作系统 */
|
||||
private OS os = new OS();
|
||||
|
||||
/** CPU 使用率 */
|
||||
/** CPU */
|
||||
private CPU cpu = new CPU();
|
||||
|
||||
/** 系统内存 */
|
||||
@@ -35,17 +37,20 @@ public class ServerStatus implements TimiJava {
|
||||
/** 网络 */
|
||||
private Network network = new Network();
|
||||
|
||||
/** 本程序状态 */
|
||||
/** 硬件 */
|
||||
private Hardware hardware = new Hardware();
|
||||
|
||||
/** JVM */
|
||||
private JVM jvm = new JVM();
|
||||
|
||||
/** 磁盘 */
|
||||
private List<Partition> partitions = new ArrayList<>();
|
||||
/** 存储分区 */
|
||||
private List<StoragePartition> storagePartitions = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 系统
|
||||
* 操作系统
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2022-08-12 20:55
|
||||
* @since 2022-08-12 20:55
|
||||
*/
|
||||
@Data
|
||||
public static class OS {
|
||||
@@ -58,10 +63,10 @@ public class ServerStatus implements TimiJava {
|
||||
}
|
||||
|
||||
/**
|
||||
* 虚拟机状态
|
||||
* JVM
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2022-01-31 21:10
|
||||
* @since 2022-01-31 21:10
|
||||
*/
|
||||
@Data
|
||||
public static class JVM {
|
||||
@@ -69,80 +74,83 @@ public class ServerStatus implements TimiJava {
|
||||
/** 启动时间 */
|
||||
private long bootAt;
|
||||
|
||||
/** JVM 名称 */
|
||||
/** 名称 */
|
||||
private String name;
|
||||
|
||||
/** JVM 版本 */
|
||||
/** 版本 */
|
||||
private String version;
|
||||
|
||||
/** 内存 */
|
||||
/** GC 名称 */
|
||||
private String gcName;
|
||||
|
||||
/** 堆内存 */
|
||||
private Memory memory = new Memory();
|
||||
|
||||
/** 内存回收 */
|
||||
private ZGC zgc = new ZGC();
|
||||
/** GC 状态 */
|
||||
private GC gc = new GC();
|
||||
|
||||
/**
|
||||
* 内存
|
||||
* JVM 内存
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2022-08-12 20:32
|
||||
* @since 2022-08-12 20:32
|
||||
*/
|
||||
@Data
|
||||
public static class Memory {
|
||||
|
||||
/** 初始化 */
|
||||
/** 初始大小 */
|
||||
private long init;
|
||||
|
||||
/** 最大 */
|
||||
/** 最大大小 */
|
||||
private long max;
|
||||
|
||||
/** 已使用 */
|
||||
/** 已用大小 */
|
||||
private final Deque<Number> used = new ArrayDeque<>();
|
||||
|
||||
/** 已提交 */
|
||||
/** 已提交大小 */
|
||||
private final Deque<Number> committed = new ArrayDeque<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 内存回收
|
||||
* GC 状态
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2022-08-12 20:32
|
||||
* @since 2022-08-12 20:32
|
||||
*/
|
||||
@Data
|
||||
public static class ZGC {
|
||||
public static class GC {
|
||||
|
||||
/** 并发周期 */
|
||||
private long syncCycles = 0;
|
||||
/** 周期次数 */
|
||||
private long syncCycles;
|
||||
|
||||
/** 累计并发周期耗时(毫秒) */
|
||||
private long syncCyclesTimeTotal = 0;
|
||||
/** 周期累计耗时 */
|
||||
private long syncCyclesTimeTotal;
|
||||
|
||||
/** 累计次数 */
|
||||
private long pauses = 0;
|
||||
/** 暂停次数 */
|
||||
private long pauses;
|
||||
|
||||
/** 累计回收暂停时长(毫秒) */
|
||||
private long pausesTimeTotal = 0;
|
||||
/** 暂停累计耗时 */
|
||||
private long pausesTimeTotal;
|
||||
|
||||
/** 上一次回收时间 */
|
||||
private long lastPauseAt = 0;
|
||||
/** 上次暂停时间 */
|
||||
private long lastPauseAt;
|
||||
|
||||
/** 上一次回收大小 */
|
||||
private long lastRecoverySize = 0;
|
||||
/** 上次回收大小 */
|
||||
private long lastRecoverySize;
|
||||
|
||||
/** 并发周期耗时 */
|
||||
/** 周期耗时序列 */
|
||||
private final Deque<Number> syncCyclesTime = new ArrayDeque<>();
|
||||
|
||||
/** 回收暂停时长 */
|
||||
/** 暂停耗时序列 */
|
||||
private final Deque<Number> pausesTime = new ArrayDeque<>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 中央处理器
|
||||
* CPU
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2022-01-31 15:40
|
||||
* @since 2022-01-31 15:40
|
||||
*/
|
||||
@Data
|
||||
public static class CPU {
|
||||
@@ -150,19 +158,19 @@ public class ServerStatus implements TimiJava {
|
||||
/** 名称 */
|
||||
private String name;
|
||||
|
||||
/** 物理核心数量 */
|
||||
/** 物理核心数 */
|
||||
private int coreCount;
|
||||
|
||||
/** 线程数量 */
|
||||
/** 逻辑核心数 */
|
||||
private int logicalCount;
|
||||
|
||||
/** 温度 */
|
||||
private double temperature;
|
||||
|
||||
/** 系统使用 */
|
||||
/** 系统占用 */
|
||||
private final Deque<Number> system = new ArrayDeque<>();
|
||||
|
||||
/** 总共已使用 */
|
||||
/** 总占用 */
|
||||
private final Deque<Number> used = new ArrayDeque<>();
|
||||
}
|
||||
|
||||
@@ -170,7 +178,7 @@ public class ServerStatus implements TimiJava {
|
||||
* 系统内存
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2022-01-31 15:50
|
||||
* @since 2022-01-31 15:50
|
||||
*/
|
||||
@Data
|
||||
public static class Memory {
|
||||
@@ -178,69 +186,174 @@ public class ServerStatus implements TimiJava {
|
||||
/** 物理内存大小 */
|
||||
private long size;
|
||||
|
||||
/** 交换区大小 */
|
||||
/** 交换分区大小 */
|
||||
private long swapSize;
|
||||
|
||||
/** 已使用 */
|
||||
/** 已用内存 */
|
||||
private final Deque<Number> used = new ArrayDeque<>();
|
||||
|
||||
/** 交换区已使用 */
|
||||
/** 已用交换分区 */
|
||||
private final Deque<Number> swapUsed = new ArrayDeque<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 网卡网速
|
||||
* 网络
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2022-08-10 21:41
|
||||
* @since 2022-08-10 21:41
|
||||
*/
|
||||
@Data
|
||||
public static class Network {
|
||||
|
||||
/** 网卡名称 */
|
||||
private String name;
|
||||
|
||||
/** 累计接收 */
|
||||
private long recvTotal;
|
||||
|
||||
/** 累计发送 */
|
||||
private long sentTotal;
|
||||
|
||||
/** 实时接收速度 */
|
||||
/** 实时接收速率 */
|
||||
private long recvNow;
|
||||
|
||||
/** 实时发送速度 */
|
||||
/** 实时发送速率 */
|
||||
private long sentNow;
|
||||
|
||||
/** MAC 地址 */
|
||||
private String mac;
|
||||
|
||||
/** 发送 */
|
||||
/** 接收包总数 */
|
||||
private long recvPacketsTotal;
|
||||
|
||||
/** 发送包总数 */
|
||||
private long sentPacketsTotal;
|
||||
|
||||
/** 输入错误包总数 */
|
||||
private long inErrors;
|
||||
|
||||
/** 输出错误包总数 */
|
||||
private long outErrors;
|
||||
|
||||
/** 输入丢弃包总数 */
|
||||
private long inDrops;
|
||||
|
||||
/** 碰撞总数 */
|
||||
private long collisions;
|
||||
|
||||
/** 发送序列 */
|
||||
private final Deque<Number> sent = new ArrayDeque<>();
|
||||
|
||||
/** 接收 */
|
||||
/** 接收序列 */
|
||||
private final Deque<Number> recv = new ArrayDeque<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 分区
|
||||
* 硬件信息
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2022-01-31 20:19
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@Data
|
||||
public static class Partition {
|
||||
public static class Hardware {
|
||||
|
||||
/** 识别 UUID */
|
||||
/** 风扇转速 */
|
||||
private List<Integer> fanSpeeds = new ArrayList<>();
|
||||
|
||||
/** 主板信息 */
|
||||
private Baseboard baseboard = new Baseboard();
|
||||
|
||||
/** BIOS 信息 */
|
||||
private Firmware firmware = new Firmware();
|
||||
}
|
||||
|
||||
/**
|
||||
* 主板信息
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@Data
|
||||
public static class Baseboard {
|
||||
|
||||
/** 厂商 */
|
||||
private String manufacturer;
|
||||
|
||||
/** 型号 */
|
||||
private String model;
|
||||
|
||||
/** 版本 */
|
||||
private String version;
|
||||
|
||||
/** 序列号 */
|
||||
private String serialNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* BIOS 信息
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@Data
|
||||
public static class Firmware {
|
||||
|
||||
/** 厂商 */
|
||||
private String manufacturer;
|
||||
|
||||
/** 名称 */
|
||||
private String name;
|
||||
|
||||
/** 描述 */
|
||||
private String description;
|
||||
|
||||
/** 版本 */
|
||||
private String version;
|
||||
|
||||
/** 发布时间 */
|
||||
private String releaseDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储分区
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@Data
|
||||
public static class StoragePartition {
|
||||
|
||||
/** 物理磁盘名称 */
|
||||
private String diskName;
|
||||
|
||||
/** 物理磁盘型号 */
|
||||
private String diskModel;
|
||||
|
||||
/** 物理磁盘序列号 */
|
||||
private String diskSerial;
|
||||
|
||||
/** 分区标识 */
|
||||
private String partitionId;
|
||||
|
||||
/** 分区名称 */
|
||||
private String partitionName;
|
||||
|
||||
/** 分区类型 */
|
||||
private String partitionType;
|
||||
|
||||
/** 分区 UUID */
|
||||
private String uuid;
|
||||
|
||||
/** 路径 */
|
||||
private String path;
|
||||
/** 挂载点 */
|
||||
private String mountPoint;
|
||||
|
||||
/** 文件系统类型 */
|
||||
private String type;
|
||||
|
||||
/** 已使用 */
|
||||
private long used;
|
||||
|
||||
/** 总大小 */
|
||||
/** 分区总空间 */
|
||||
private long total;
|
||||
|
||||
/** 分区已用空间 */
|
||||
private Long used;
|
||||
|
||||
/** 磁盘传输耗时 */
|
||||
private long transferTimeMs;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
package com.imyeyu.api.modules.system.bean;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* UPS 状态缓存
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-07
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
public class UpsStatusStore {
|
||||
|
||||
/** 当前状态 */
|
||||
private Snapshot current;
|
||||
|
||||
/** 历史点 */
|
||||
private final Deque<Point> history = new ArrayDeque<>();
|
||||
|
||||
/**
|
||||
* UPS 当前快照
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-07
|
||||
*/
|
||||
@Data
|
||||
public static class Snapshot {
|
||||
|
||||
/** 采样时间 */
|
||||
private long updatedAt;
|
||||
|
||||
/** UPS 数据时间 */
|
||||
private Long upsTime;
|
||||
|
||||
/** 上游主机地址 */
|
||||
private String hostName;
|
||||
|
||||
/** 厂商名称 */
|
||||
private String customer;
|
||||
|
||||
/** 上游版本 */
|
||||
private String version;
|
||||
|
||||
/** 设备标识 */
|
||||
private String deviceId;
|
||||
|
||||
/** UPS 类型 */
|
||||
private String upsType;
|
||||
|
||||
/** UPS 形态 */
|
||||
private String morphological;
|
||||
|
||||
/** 输入输出相位 */
|
||||
private String ioPhase;
|
||||
|
||||
/** 工作模式 */
|
||||
private String workMode;
|
||||
|
||||
/** 输入电压 */
|
||||
private Double inputVoltage;
|
||||
|
||||
/** 输入频率 */
|
||||
private Double inputFrequency;
|
||||
|
||||
/** 输出电压 */
|
||||
private Double outputVoltage;
|
||||
|
||||
/** 输出频率 */
|
||||
private Double outputFrequency;
|
||||
|
||||
/** 输出负载百分比 */
|
||||
private Integer outputLoadPercent;
|
||||
|
||||
/** 电池电压 */
|
||||
private Double batteryVoltage;
|
||||
|
||||
/** 电池容量百分比 */
|
||||
private Integer batteryCapacity;
|
||||
|
||||
/** 电池剩余时间 */
|
||||
private Integer batteryRemainTime;
|
||||
|
||||
/** 温度 */
|
||||
private Double temperature;
|
||||
|
||||
/** 是否旁路运行 */
|
||||
private boolean bypassActive;
|
||||
|
||||
/** 是否关机中 */
|
||||
private boolean shutdownActive;
|
||||
|
||||
/** 是否输出开启 */
|
||||
private boolean outputOn;
|
||||
|
||||
/** 是否充电中 */
|
||||
private boolean charging;
|
||||
|
||||
/** 故障类型 */
|
||||
private String faultType;
|
||||
|
||||
/** 故障明细 */
|
||||
private String faultKind;
|
||||
|
||||
/** 告警列表 */
|
||||
private List<String> warnings = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* UPS 历史点
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-07
|
||||
*/
|
||||
@Data
|
||||
public static class Point {
|
||||
|
||||
/** 采样时间 */
|
||||
private long at;
|
||||
|
||||
/** UPS 数据时间 */
|
||||
private Long upsTime;
|
||||
|
||||
/** 工作模式 */
|
||||
private String workMode;
|
||||
|
||||
/** 输入电压 */
|
||||
private Double inputVoltage;
|
||||
|
||||
/** 输入频率 */
|
||||
private Double inputFrequency;
|
||||
|
||||
/** 输出电压 */
|
||||
private Double outputVoltage;
|
||||
|
||||
/** 输出频率 */
|
||||
private Double outputFrequency;
|
||||
|
||||
/** 输出负载百分比 */
|
||||
private Integer outputLoadPercent;
|
||||
|
||||
/** 电池电压 */
|
||||
private Double batteryVoltage;
|
||||
|
||||
/** 电池容量百分比 */
|
||||
private Integer batteryCapacity;
|
||||
|
||||
/** 电池剩余时间 */
|
||||
private Integer batteryRemainTime;
|
||||
|
||||
/** 温度 */
|
||||
private Double temperature;
|
||||
|
||||
/** 是否旁路运行 */
|
||||
private boolean bypassActive;
|
||||
|
||||
/** 是否关机中 */
|
||||
private boolean shutdownActive;
|
||||
|
||||
/** 是否输出开启 */
|
||||
private boolean outputOn;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.imyeyu.api.modules.system.controller;
|
||||
|
||||
import com.imyeyu.api.modules.system.service.DockerService;
|
||||
import com.imyeyu.api.modules.system.vo.docker.DockerContainerHistoryView;
|
||||
import com.imyeyu.api.modules.system.vo.docker.DockerContainerStatusView;
|
||||
import com.imyeyu.api.modules.system.vo.docker.DockerContainerSummaryView;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Docker 控制器
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/system/docker")
|
||||
public class DockerController {
|
||||
|
||||
private final DockerService dockerService;
|
||||
|
||||
@GetMapping("/container")
|
||||
public List<DockerContainerSummaryView> listContainers() {
|
||||
return dockerService.listContainers();
|
||||
}
|
||||
|
||||
@GetMapping("/container/{containerId}/status")
|
||||
public DockerContainerStatusView getContainerStatus(@PathVariable String containerId) {
|
||||
return dockerService.getContainerStatus(containerId);
|
||||
}
|
||||
|
||||
@GetMapping("/container/{containerId}/history")
|
||||
public DockerContainerHistoryView getContainerHistory(@PathVariable String containerId, @RequestParam(required = false) String window) {
|
||||
return dockerService.getContainerHistory(containerId, window);
|
||||
}
|
||||
}
|
||||
@@ -475,7 +475,7 @@ public class FileController implements TimiJava, OS.FileSystem {
|
||||
resp.setHeader("Content-Disposition", Network.getFileDownloadHeader(file.getName()));
|
||||
resp.setHeader("Accept-Ranges", "bytes");
|
||||
|
||||
RequestRange range = TimiSpring.requestRange(file.length());
|
||||
RequestRange range = TimiSpring.getRequestRange(file.length());
|
||||
if (range == null) {
|
||||
// 完整文件
|
||||
resp.setContentLengthLong(file.length());
|
||||
|
||||
@@ -2,8 +2,10 @@ package com.imyeyu.api.modules.system.controller;
|
||||
|
||||
import com.imyeyu.api.modules.common.entity.Attachment;
|
||||
import com.imyeyu.api.modules.common.service.AttachmentService;
|
||||
import com.imyeyu.api.modules.system.bean.ServerStatus;
|
||||
import com.imyeyu.api.modules.system.service.StatusService;
|
||||
import com.imyeyu.api.modules.system.service.SystemService;
|
||||
import com.imyeyu.api.modules.system.vo.SystemStatusHistoryView;
|
||||
import com.imyeyu.api.modules.system.vo.SystemStatusSnapshotView;
|
||||
import com.imyeyu.api.modules.system.vo.TempAttachRequest;
|
||||
import com.imyeyu.io.IO;
|
||||
import com.imyeyu.java.bean.timi.TimiCode;
|
||||
@@ -12,6 +14,7 @@ import com.imyeyu.spring.annotation.AOPLog;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
@@ -23,10 +26,10 @@ import java.io.IOException;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
/**
|
||||
* 服务器控制接口
|
||||
* 服务端控制器
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2022-01-31 22:47
|
||||
* @since 2022-01-31 22:47
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@@ -34,7 +37,7 @@ import java.util.concurrent.Semaphore;
|
||||
@RequestMapping("/system/server")
|
||||
public class SystemController {
|
||||
|
||||
private final ServerStatus serverStatus;
|
||||
private final StatusService statusService;
|
||||
private final SystemService service;
|
||||
private final AttachmentService attachmentService;
|
||||
|
||||
@@ -42,16 +45,36 @@ public class SystemController {
|
||||
private final Semaphore rebootSemaphore = new Semaphore(1);
|
||||
private final Semaphore restoreSemaphore = new Semaphore(1);
|
||||
|
||||
/** @return 实时服务器状态 */
|
||||
@RequestMapping("/status")
|
||||
public ServerStatus getStatus() {
|
||||
return serverStatus;
|
||||
/**
|
||||
* 获取系统状态快照
|
||||
*
|
||||
* @param metrics 返回指标,使用逗号分隔
|
||||
* @return 状态快照
|
||||
*/
|
||||
@GetMapping("/status")
|
||||
public SystemStatusSnapshotView getStatus(@RequestParam(required = false) String metrics) {
|
||||
return statusService.getStatus(metrics);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统状态历史
|
||||
*
|
||||
* @param window 历史窗口,支持 s/m/h/d 后缀
|
||||
* @param metrics 返回指标,使用逗号分隔
|
||||
* @return 状态历史
|
||||
*/
|
||||
@GetMapping("/status/history")
|
||||
public SystemStatusHistoryView getStatusHistory(
|
||||
@RequestParam(required = false) String window,
|
||||
@RequestParam(required = false) String metrics
|
||||
) {
|
||||
return statusService.getStatusHistory(window, metrics);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新系统
|
||||
*
|
||||
* @param file
|
||||
* @param file 更新文件
|
||||
*/
|
||||
@AOPLog
|
||||
@PostMapping("/update")
|
||||
@@ -85,8 +108,7 @@ public class SystemController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止系统
|
||||
*
|
||||
* 关闭系统
|
||||
*/
|
||||
@AOPLog
|
||||
@RequestMapping("/shutdown")
|
||||
@@ -111,7 +133,11 @@ public class SystemController {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO 临时接口
|
||||
/**
|
||||
* 上传临时附件
|
||||
*
|
||||
* @param request 上传请求
|
||||
*/
|
||||
@AOPLog
|
||||
@PostMapping("/attach")
|
||||
public void uploadAttachment(TempAttachRequest request) {
|
||||
|
||||
@@ -7,7 +7,6 @@ import com.imyeyu.api.modules.common.service.SettingService;
|
||||
import com.imyeyu.api.modules.system.service.TerminalService;
|
||||
import com.imyeyu.api.modules.system.vo.terminal.ExecCommand;
|
||||
import com.imyeyu.spring.annotation.AOPLog;
|
||||
import com.imyeyu.spring.annotation.RequestSingleParam;
|
||||
import com.imyeyu.spring.annotation.RequiredToken;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
@@ -51,7 +50,7 @@ public class TerminalController {
|
||||
*/
|
||||
@RequiredToken
|
||||
@PostMapping("/alive")
|
||||
public boolean isAlive(@RequestSingleParam String sessionId) {
|
||||
public boolean isAlive(@RequestBody String sessionId) {
|
||||
return service.isAlive(sessionId);
|
||||
}
|
||||
|
||||
@@ -64,7 +63,7 @@ public class TerminalController {
|
||||
@AOPLog
|
||||
@RequiredToken
|
||||
@PostMapping("/fill")
|
||||
public synchronized String pathFill(@RequestSingleParam String path) {
|
||||
public synchronized String pathFill(@RequestBody String path) {
|
||||
return service.pathFill(path);
|
||||
}
|
||||
|
||||
@@ -94,7 +93,7 @@ public class TerminalController {
|
||||
@AOPLog
|
||||
@RequiredToken
|
||||
@PostMapping("/close")
|
||||
public void close(@RequestSingleParam String sessionId) {
|
||||
public void close(@RequestBody String sessionId) {
|
||||
service.close(sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.imyeyu.api.modules.system.controller;
|
||||
|
||||
import com.imyeyu.api.modules.system.service.UpsService;
|
||||
import com.imyeyu.api.modules.system.vo.ups.UpsHistoryView;
|
||||
import com.imyeyu.api.modules.system.vo.ups.UpsStatusView;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* UPS 控制器
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-07
|
||||
*/
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/system/ups")
|
||||
public class UpsController {
|
||||
|
||||
private final UpsService upsService;
|
||||
|
||||
@GetMapping("/status")
|
||||
public UpsStatusView getStatus() {
|
||||
return upsService.getStatus();
|
||||
}
|
||||
|
||||
@GetMapping("/history")
|
||||
public UpsHistoryView getHistory(@RequestParam(required = false) String window) {
|
||||
return upsService.getHistory(window);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.imyeyu.api.modules.system.service;
|
||||
|
||||
import com.imyeyu.api.modules.system.vo.docker.DockerContainerHistoryView;
|
||||
import com.imyeyu.api.modules.system.vo.docker.DockerContainerStatusView;
|
||||
import com.imyeyu.api.modules.system.vo.docker.DockerContainerSummaryView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Docker 查询服务
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
public interface DockerService {
|
||||
|
||||
/**
|
||||
* 获取容器列表
|
||||
*
|
||||
* @return 容器列表
|
||||
*/
|
||||
List<DockerContainerSummaryView> listContainers();
|
||||
|
||||
/**
|
||||
* 获取容器状态
|
||||
*
|
||||
* @param containerId 容器 ID
|
||||
* @return 容器状态
|
||||
*/
|
||||
DockerContainerStatusView getContainerStatus(String containerId);
|
||||
|
||||
/**
|
||||
* 获取容器历史
|
||||
*
|
||||
* @param containerId 容器 ID
|
||||
* @param window 历史窗口
|
||||
* @return 容器历史
|
||||
*/
|
||||
DockerContainerHistoryView getContainerHistory(String containerId, String window);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.imyeyu.api.modules.system.service;
|
||||
|
||||
import com.imyeyu.api.modules.system.vo.SystemStatusHistoryView;
|
||||
import com.imyeyu.api.modules.system.vo.SystemStatusSnapshotView;
|
||||
|
||||
/**
|
||||
* 系统状态查询服务
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
public interface StatusService {
|
||||
|
||||
/**
|
||||
* 获取当前状态快照
|
||||
*
|
||||
* @param metrics 返回指标
|
||||
* @return 状态快照
|
||||
*/
|
||||
SystemStatusSnapshotView getStatus(String metrics);
|
||||
|
||||
/**
|
||||
* 获取状态历史
|
||||
*
|
||||
* @param window 历史窗口
|
||||
* @param metrics 返回指标
|
||||
* @return 状态历史
|
||||
*/
|
||||
SystemStatusHistoryView getStatusHistory(String window, String metrics);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.imyeyu.api.modules.system.service;
|
||||
|
||||
import com.imyeyu.api.modules.system.vo.ups.UpsHistoryView;
|
||||
import com.imyeyu.api.modules.system.vo.ups.UpsStatusView;
|
||||
|
||||
/**
|
||||
* UPS 查询服务
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-07
|
||||
*/
|
||||
public interface UpsService {
|
||||
|
||||
/**
|
||||
* 获取 UPS 当前状态
|
||||
*
|
||||
* @return UPS 状态
|
||||
*/
|
||||
UpsStatusView getStatus();
|
||||
|
||||
/**
|
||||
* 获取 UPS 历史
|
||||
*
|
||||
* @param window 历史窗口
|
||||
* @return UPS 历史
|
||||
*/
|
||||
UpsHistoryView getHistory(String window);
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
package com.imyeyu.api.modules.system.service.implement;
|
||||
|
||||
import com.imyeyu.api.modules.system.bean.DockerStatusStore;
|
||||
import com.imyeyu.api.modules.system.service.DockerService;
|
||||
import com.imyeyu.api.modules.system.vo.docker.DockerContainerHistoryPointView;
|
||||
import com.imyeyu.api.modules.system.vo.docker.DockerContainerHistoryView;
|
||||
import com.imyeyu.api.modules.system.vo.docker.DockerContainerStatusView;
|
||||
import com.imyeyu.api.modules.system.vo.docker.DockerContainerSummaryView;
|
||||
import com.imyeyu.utils.Time;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Docker 查询服务实现
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DockerServiceImplement implements DockerService {
|
||||
|
||||
private final DockerStatusStore dockerStatusStore;
|
||||
|
||||
@Value("${docker.engine.collect-rate-ms:10000}")
|
||||
private long collectRateMs;
|
||||
|
||||
@Override
|
||||
public List<DockerContainerSummaryView> listContainers() {
|
||||
synchronized (dockerStatusStore) {
|
||||
List<DockerContainerSummaryView> result = new ArrayList<>(dockerStatusStore.getContainers().size());
|
||||
for (DockerStatusStore.Container container : dockerStatusStore.getContainers().values()) {
|
||||
DockerContainerSummaryView item = new DockerContainerSummaryView();
|
||||
item.setId(container.getId());
|
||||
item.setName(container.getName());
|
||||
item.setImage(container.getImage());
|
||||
item.setState(container.getState());
|
||||
item.setStatus(container.getStatus());
|
||||
item.setHealthStatus(container.getHealthStatus());
|
||||
item.setCpuPercent(container.getCpuPercent());
|
||||
item.setMemoryUsageBytes(container.getMemoryUsageBytes());
|
||||
item.setMemoryLimitBytes(container.getMemoryLimitBytes());
|
||||
item.setMemoryPercent(container.getMemoryPercent());
|
||||
item.setNetworkRxBytes(container.getNetworkRxBytes());
|
||||
item.setNetworkTxBytes(container.getNetworkTxBytes());
|
||||
item.setUpdatedAt(container.getUpdatedAt());
|
||||
result.add(item);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DockerContainerStatusView getContainerStatus(String containerId) {
|
||||
synchronized (dockerStatusStore) {
|
||||
DockerStatusStore.Container container = findContainer(containerId);
|
||||
if (container == null) {
|
||||
return null;
|
||||
}
|
||||
DockerContainerStatusView view = new DockerContainerStatusView();
|
||||
view.setId(container.getId());
|
||||
view.setName(container.getName());
|
||||
view.setImage(container.getImage());
|
||||
view.setImageId(container.getImageId());
|
||||
view.setCreatedAt(container.getCreatedAt());
|
||||
view.setState(container.getState());
|
||||
view.setStatus(container.getStatus());
|
||||
view.setHealthStatus(container.getHealthStatus());
|
||||
view.setStartedAt(container.getStartedAt());
|
||||
view.setFinishedAt(container.getFinishedAt());
|
||||
view.setExitCode(container.getExitCode());
|
||||
view.setRestartCount(container.getRestartCount());
|
||||
view.setOomKilled(container.isOomKilled());
|
||||
view.setCpuPercent(container.getCpuPercent());
|
||||
view.setMemoryUsageBytes(container.getMemoryUsageBytes());
|
||||
view.setMemoryLimitBytes(container.getMemoryLimitBytes());
|
||||
view.setMemoryPercent(container.getMemoryPercent());
|
||||
view.setNetworkRxBytes(container.getNetworkRxBytes());
|
||||
view.setNetworkTxBytes(container.getNetworkTxBytes());
|
||||
view.setBlockReadBytes(container.getBlockReadBytes());
|
||||
view.setBlockWriteBytes(container.getBlockWriteBytes());
|
||||
view.setPids(container.getPids());
|
||||
view.setUpdatedAt(container.getUpdatedAt());
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DockerContainerHistoryView getContainerHistory(String containerId, String window) {
|
||||
synchronized (dockerStatusStore) {
|
||||
DockerStatusStore.Container container = findContainer(containerId);
|
||||
if (container == null) {
|
||||
return null;
|
||||
}
|
||||
DockerContainerHistoryView view = new DockerContainerHistoryView();
|
||||
view.setId(container.getId());
|
||||
view.setName(container.getName());
|
||||
view.setServerTime(Time.now());
|
||||
view.setSampleRateMs(collectRateMs);
|
||||
|
||||
long windowMs = parseWindowMs(window);
|
||||
long threshold = Time.now() - windowMs;
|
||||
for (DockerStatusStore.Point point : container.getHistory()) {
|
||||
if (point.getAt() < threshold) {
|
||||
continue;
|
||||
}
|
||||
if (0 == view.getFrom()) {
|
||||
view.setFrom(point.getAt());
|
||||
}
|
||||
view.setTo(point.getAt());
|
||||
DockerContainerHistoryPointView item = new DockerContainerHistoryPointView();
|
||||
item.setAt(point.getAt());
|
||||
item.setCpuPercent(point.getCpuPercent());
|
||||
item.setMemoryUsageBytes(point.getMemoryUsageBytes());
|
||||
item.setMemoryPercent(point.getMemoryPercent());
|
||||
item.setNetworkRxBytes(point.getNetworkRxBytes());
|
||||
item.setNetworkTxBytes(point.getNetworkTxBytes());
|
||||
item.setBlockReadBytes(point.getBlockReadBytes());
|
||||
item.setBlockWriteBytes(point.getBlockWriteBytes());
|
||||
item.setPids(point.getPids());
|
||||
view.getPoints().add(item);
|
||||
}
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
private long parseWindowMs(String window) {
|
||||
if (window == null || window.isBlank()) {
|
||||
return 60L * collectRateMs;
|
||||
}
|
||||
|
||||
String normalized = window.trim().toLowerCase();
|
||||
char suffix = normalized.charAt(normalized.length() - 1);
|
||||
long unit = switch (suffix) {
|
||||
case 's' -> 1000L;
|
||||
case 'm' -> 60_000L;
|
||||
case 'h' -> 3_600_000L;
|
||||
case 'd' -> 86_400_000L;
|
||||
default -> collectRateMs;
|
||||
};
|
||||
String valueText = Character.isDigit(suffix) ? normalized : normalized.substring(0, normalized.length() - 1);
|
||||
try {
|
||||
return Math.max(collectRateMs, Long.parseLong(valueText) * unit);
|
||||
} catch (NumberFormatException e) {
|
||||
return 60L * collectRateMs;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按容器 ID 或前缀查找容器
|
||||
*
|
||||
* @param containerId 容器 ID
|
||||
* @return 容器缓存
|
||||
*/
|
||||
private DockerStatusStore.Container findContainer(String containerId) {
|
||||
DockerStatusStore.Container exact = dockerStatusStore.getContainers().get(containerId);
|
||||
if (exact != null) {
|
||||
return exact;
|
||||
}
|
||||
for (DockerStatusStore.Container container : dockerStatusStore.getContainers().values()) {
|
||||
if (container.getId() != null && container.getId().startsWith(containerId)) {
|
||||
return container;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
package com.imyeyu.api.modules.system.service.implement;
|
||||
|
||||
import com.imyeyu.api.modules.common.bean.SettingKey;
|
||||
import com.imyeyu.api.modules.common.service.SettingService;
|
||||
import com.imyeyu.api.modules.system.bean.ServerStatus;
|
||||
import com.imyeyu.api.modules.system.service.StatusService;
|
||||
import com.imyeyu.api.modules.system.vo.SystemStatusDataView;
|
||||
import com.imyeyu.api.modules.system.vo.SystemStatusHistoryView;
|
||||
import com.imyeyu.api.modules.system.vo.SystemStatusSnapshotView;
|
||||
import com.imyeyu.utils.Time;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* 系统状态查询服务实现
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class StatusServiceImplement implements StatusService {
|
||||
|
||||
private final ServerStatus serverStatus;
|
||||
private final SettingService settingService;
|
||||
|
||||
@Override
|
||||
public SystemStatusSnapshotView getStatus(String metrics) {
|
||||
long serverTime = Time.now();
|
||||
int sampleRateMs = settingService.getAsInt(SettingKey.SYSTEM_STATUS_RATE);
|
||||
EnumSet<Metric> selectedMetrics = parseMetrics(metrics);
|
||||
|
||||
synchronized (serverStatus) {
|
||||
SystemStatusSnapshotView view = new SystemStatusSnapshotView();
|
||||
view.setServerTime(serverTime);
|
||||
view.setSampleRateMs(sampleRateMs);
|
||||
view.setSnapshot(buildSnapshot(serverTime, selectedMetrics));
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SystemStatusHistoryView getStatusHistory(String window, String metrics) {
|
||||
long serverTime = Time.now();
|
||||
int sampleRateMs = settingService.getAsInt(SettingKey.SYSTEM_STATUS_RATE);
|
||||
EnumSet<Metric> selectedMetrics = parseMetrics(metrics);
|
||||
|
||||
synchronized (serverStatus) {
|
||||
SystemStatusHistoryView history = buildHistory(window, sampleRateMs, selectedMetrics);
|
||||
history.setServerTime(serverTime);
|
||||
history.setSampleRateMs(sampleRateMs);
|
||||
return history;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建当前快照
|
||||
*
|
||||
* @param serverTime 服务端时间
|
||||
* @param selectedMetrics 指标集合
|
||||
* @return 当前快照
|
||||
*/
|
||||
private SystemStatusDataView.Snapshot buildSnapshot(long serverTime, EnumSet<Metric> selectedMetrics) {
|
||||
SystemStatusDataView.Snapshot snapshot = new SystemStatusDataView.Snapshot();
|
||||
if (selectedMetrics.contains(Metric.OS)) {
|
||||
SystemStatusDataView.OS os = new SystemStatusDataView.OS();
|
||||
os.setName(serverStatus.getOs().getName());
|
||||
os.setBootAt(serverStatus.getOs().getBootAt());
|
||||
snapshot.setOs(os);
|
||||
}
|
||||
if (selectedMetrics.contains(Metric.CPU)) {
|
||||
SystemStatusDataView.CPU cpu = new SystemStatusDataView.CPU();
|
||||
cpu.setModel(serverStatus.getCpu().getName());
|
||||
cpu.setPhysicalCores(serverStatus.getCpu().getCoreCount());
|
||||
cpu.setLogicalCores(serverStatus.getCpu().getLogicalCount());
|
||||
cpu.setUsageTotal(lastDouble(serverStatus.getCpu().getUsed()));
|
||||
cpu.setUsageSystem(lastDouble(serverStatus.getCpu().getSystem()));
|
||||
cpu.setTemperatureCelsius(serverStatus.getCpu().getTemperature());
|
||||
snapshot.setCpu(cpu);
|
||||
}
|
||||
if (selectedMetrics.contains(Metric.MEMORY)) {
|
||||
SystemStatusDataView.Memory memory = new SystemStatusDataView.Memory();
|
||||
Long usedBytes = lastLong(serverStatus.getMemory().getUsed());
|
||||
Long swapUsedBytes = lastLong(serverStatus.getMemory().getSwapUsed());
|
||||
memory.setTotalBytes(serverStatus.getMemory().getSize());
|
||||
memory.setUsedBytes(usedBytes);
|
||||
memory.setSwapTotalBytes(serverStatus.getMemory().getSwapSize());
|
||||
memory.setSwapUsedBytes(swapUsedBytes);
|
||||
snapshot.setMemory(memory);
|
||||
}
|
||||
if (selectedMetrics.contains(Metric.JVM)) {
|
||||
SystemStatusDataView.JVM jvm = new SystemStatusDataView.JVM();
|
||||
SystemStatusDataView.GC gc = new SystemStatusDataView.GC();
|
||||
jvm.setName(serverStatus.getJvm().getName());
|
||||
jvm.setVersion(serverStatus.getJvm().getVersion());
|
||||
jvm.setBootAt(serverStatus.getJvm().getBootAt());
|
||||
jvm.setHeapInitBytes(serverStatus.getJvm().getMemory().getInit());
|
||||
jvm.setHeapMaxBytes(serverStatus.getJvm().getMemory().getMax());
|
||||
jvm.setHeapUsedBytes(lastLong(serverStatus.getJvm().getMemory().getUsed()));
|
||||
jvm.setHeapCommittedBytes(lastLong(serverStatus.getJvm().getMemory().getCommitted()));
|
||||
gc.setCollector(serverStatus.getJvm().getGcName());
|
||||
gc.setCycleCount(serverStatus.getJvm().getGc().getSyncCycles());
|
||||
gc.setPauseCount(serverStatus.getJvm().getGc().getPauses());
|
||||
gc.setLastPauseAt(serverStatus.getJvm().getGc().getLastPauseAt());
|
||||
gc.setLastRecoveredBytes(serverStatus.getJvm().getGc().getLastRecoverySize());
|
||||
jvm.setGc(gc);
|
||||
snapshot.setJvm(jvm);
|
||||
}
|
||||
if (selectedMetrics.contains(Metric.NETWORK)) {
|
||||
SystemStatusDataView.Network network = new SystemStatusDataView.Network();
|
||||
network.setInterfaceName(serverStatus.getNetwork().getName());
|
||||
network.setMac(serverStatus.getNetwork().getMac());
|
||||
network.setRxBytesPerSecond(serverStatus.getNetwork().getRecvNow());
|
||||
network.setTxBytesPerSecond(serverStatus.getNetwork().getSentNow());
|
||||
network.setRxTotalBytes(serverStatus.getNetwork().getRecvTotal());
|
||||
network.setTxTotalBytes(serverStatus.getNetwork().getSentTotal());
|
||||
network.setRxPacketsTotal(serverStatus.getNetwork().getRecvPacketsTotal());
|
||||
network.setTxPacketsTotal(serverStatus.getNetwork().getSentPacketsTotal());
|
||||
network.setInErrors(serverStatus.getNetwork().getInErrors());
|
||||
network.setOutErrors(serverStatus.getNetwork().getOutErrors());
|
||||
network.setInDrops(serverStatus.getNetwork().getInDrops());
|
||||
network.setCollisions(serverStatus.getNetwork().getCollisions());
|
||||
snapshot.setNetwork(network);
|
||||
}
|
||||
if (selectedMetrics.contains(Metric.HARDWARE)) {
|
||||
SystemStatusDataView.Hardware hardware = new SystemStatusDataView.Hardware();
|
||||
SystemStatusDataView.Baseboard baseboard = new SystemStatusDataView.Baseboard();
|
||||
SystemStatusDataView.Firmware firmware = new SystemStatusDataView.Firmware();
|
||||
hardware.setFanSpeeds(new ArrayList<>(serverStatus.getHardware().getFanSpeeds()));
|
||||
baseboard.setManufacturer(serverStatus.getHardware().getBaseboard().getManufacturer());
|
||||
baseboard.setModel(serverStatus.getHardware().getBaseboard().getModel());
|
||||
baseboard.setVersion(serverStatus.getHardware().getBaseboard().getVersion());
|
||||
baseboard.setSerialNumber(serverStatus.getHardware().getBaseboard().getSerialNumber());
|
||||
firmware.setManufacturer(serverStatus.getHardware().getFirmware().getManufacturer());
|
||||
firmware.setName(serverStatus.getHardware().getFirmware().getName());
|
||||
firmware.setDescription(serverStatus.getHardware().getFirmware().getDescription());
|
||||
firmware.setVersion(serverStatus.getHardware().getFirmware().getVersion());
|
||||
firmware.setReleaseDate(serverStatus.getHardware().getFirmware().getReleaseDate());
|
||||
hardware.setBaseboard(baseboard);
|
||||
hardware.setFirmware(firmware);
|
||||
snapshot.setHardware(hardware);
|
||||
}
|
||||
if (selectedMetrics.contains(Metric.STORAGE)) {
|
||||
List<SystemStatusDataView.StoragePartition> storagePartitions = new ArrayList<>();
|
||||
for (ServerStatus.StoragePartition partition : serverStatus.getStoragePartitions()) {
|
||||
SystemStatusDataView.StoragePartition item = new SystemStatusDataView.StoragePartition();
|
||||
item.setDiskName(partition.getDiskName());
|
||||
item.setDiskModel(partition.getDiskModel());
|
||||
item.setDiskSerial(partition.getDiskSerial());
|
||||
item.setPartitionId(partition.getPartitionId());
|
||||
item.setPartitionName(partition.getPartitionName());
|
||||
item.setPartitionType(partition.getPartitionType());
|
||||
item.setUuid(partition.getUuid());
|
||||
item.setMountPoint(partition.getMountPoint());
|
||||
item.setTotal(partition.getTotal());
|
||||
item.setUsed(partition.getUsed());
|
||||
item.setUsagePercent(toPercent(partition.getUsed(), partition.getTotal()));
|
||||
item.setTransferTimeMs(partition.getTransferTimeMs());
|
||||
storagePartitions.add(item);
|
||||
}
|
||||
snapshot.setStoragePartitions(storagePartitions);
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建历史数据
|
||||
*
|
||||
* @param window 历史窗口
|
||||
* @param sampleRateMs 采样周期
|
||||
* @param selectedMetrics 指标集合
|
||||
* @return 历史数据
|
||||
*/
|
||||
private SystemStatusHistoryView buildHistory(String window, int sampleRateMs, EnumSet<Metric> selectedMetrics) {
|
||||
SystemStatusHistoryView view = new SystemStatusHistoryView();
|
||||
List<Long> axis = copyLongs(serverStatus.getUpdateAxis());
|
||||
if (axis.isEmpty()) {
|
||||
return view;
|
||||
}
|
||||
|
||||
int startIndex = resolveStartIndex(axis, parseWindowMs(window, sampleRateMs));
|
||||
view.setFrom(axis.get(startIndex));
|
||||
view.setTo(axis.get(axis.size() - 1));
|
||||
|
||||
List<Double> cpuUsed = selectedMetrics.contains(Metric.CPU) ? copyDoubles(serverStatus.getCpu().getUsed()) : List.of();
|
||||
List<Double> cpuSystem = selectedMetrics.contains(Metric.CPU) ? copyDoubles(serverStatus.getCpu().getSystem()) : List.of();
|
||||
List<Long> memoryUsed = selectedMetrics.contains(Metric.MEMORY) ? copyLongs(serverStatus.getMemory().getUsed()) : List.of();
|
||||
List<Long> swapUsed = selectedMetrics.contains(Metric.MEMORY) ? copyLongs(serverStatus.getMemory().getSwapUsed()) : List.of();
|
||||
List<Long> heapUsed = selectedMetrics.contains(Metric.JVM) ? copyLongs(serverStatus.getJvm().getMemory().getUsed()) : List.of();
|
||||
List<Long> heapCommitted = selectedMetrics.contains(Metric.JVM) ? copyLongs(serverStatus.getJvm().getMemory().getCommitted()) : List.of();
|
||||
List<Long> gcCycleTime = selectedMetrics.contains(Metric.JVM) ? copyLongs(serverStatus.getJvm().getGc().getSyncCyclesTime()) : List.of();
|
||||
List<Long> gcPauseTime = selectedMetrics.contains(Metric.JVM) ? copyLongs(serverStatus.getJvm().getGc().getPausesTime()) : List.of();
|
||||
List<Long> rx = selectedMetrics.contains(Metric.NETWORK) ? toRate(copyLongs(serverStatus.getNetwork().getRecv()), sampleRateMs) : List.of();
|
||||
List<Long> tx = selectedMetrics.contains(Metric.NETWORK) ? toRate(copyLongs(serverStatus.getNetwork().getSent()), sampleRateMs) : List.of();
|
||||
|
||||
for (int index = startIndex; index < axis.size(); index++) {
|
||||
SystemStatusDataView.Point point = new SystemStatusDataView.Point();
|
||||
point.setAt(axis.get(index));
|
||||
if (selectedMetrics.contains(Metric.CPU)) {
|
||||
point.setCpuUsagePercent(getAlignedValue(cpuUsed, axis.size(), index));
|
||||
point.setCpuSystemPercent(getAlignedValue(cpuSystem, axis.size(), index));
|
||||
}
|
||||
if (selectedMetrics.contains(Metric.MEMORY)) {
|
||||
point.setMemoryUsedBytes(getAlignedValue(memoryUsed, axis.size(), index));
|
||||
point.setSwapUsedBytes(getAlignedValue(swapUsed, axis.size(), index));
|
||||
}
|
||||
if (selectedMetrics.contains(Metric.JVM)) {
|
||||
point.setHeapUsedBytes(getAlignedValue(heapUsed, axis.size(), index));
|
||||
point.setHeapCommittedBytes(getAlignedValue(heapCommitted, axis.size(), index));
|
||||
point.setGcCycleTimeMs(getAlignedValue(gcCycleTime, axis.size(), index));
|
||||
point.setGcPauseTimeMs(getAlignedValue(gcPauseTime, axis.size(), index));
|
||||
}
|
||||
if (selectedMetrics.contains(Metric.NETWORK)) {
|
||||
point.setRxBytesPerSecond(getAlignedValue(rx, axis.size(), index));
|
||||
point.setTxBytesPerSecond(getAlignedValue(tx, axis.size(), index));
|
||||
}
|
||||
view.getPoints().add(point);
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析指标
|
||||
*
|
||||
* @param metrics 指标字符串
|
||||
* @return 指标集合
|
||||
*/
|
||||
private EnumSet<Metric> parseMetrics(String metrics) {
|
||||
if (metrics == null || metrics.isBlank()) {
|
||||
return EnumSet.allOf(Metric.class);
|
||||
}
|
||||
EnumSet<Metric> selected = EnumSet.noneOf(Metric.class);
|
||||
for (String metric : metrics.split(",")) {
|
||||
switch (metric.trim().toLowerCase(Locale.ROOT)) {
|
||||
case "os" -> selected.add(Metric.OS);
|
||||
case "cpu" -> selected.add(Metric.CPU);
|
||||
case "memory" -> selected.add(Metric.MEMORY);
|
||||
case "jvm", "gc" -> selected.add(Metric.JVM);
|
||||
case "network" -> selected.add(Metric.NETWORK);
|
||||
case "storage", "disk", "disks" -> selected.add(Metric.STORAGE);
|
||||
case "hardware", "board", "bios", "fan", "fans" -> selected.add(Metric.HARDWARE);
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
return selected.isEmpty() ? EnumSet.allOf(Metric.class) : selected;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析窗口毫秒数
|
||||
*
|
||||
* @param window 窗口字符串
|
||||
* @param sampleRateMs 采样周期
|
||||
* @return 窗口毫秒数
|
||||
*/
|
||||
private long parseWindowMs(String window, int sampleRateMs) {
|
||||
if (window == null || window.isBlank()) {
|
||||
return (long) settingService.getAsInt(SettingKey.SYSTEM_STATUS_LIMIT) * sampleRateMs;
|
||||
}
|
||||
|
||||
String normalized = window.trim().toLowerCase(Locale.ROOT);
|
||||
char suffix = normalized.charAt(normalized.length() - 1);
|
||||
long unit = switch (suffix) {
|
||||
case 's' -> 1000L;
|
||||
case 'm' -> 60_000L;
|
||||
case 'h' -> 3_600_000L;
|
||||
case 'd' -> 86_400_000L;
|
||||
default -> sampleRateMs;
|
||||
};
|
||||
String valueText = Character.isDigit(suffix) ? normalized : normalized.substring(0, normalized.length() - 1);
|
||||
try {
|
||||
return Math.max(sampleRateMs, Long.parseLong(valueText) * unit);
|
||||
} catch (NumberFormatException e) {
|
||||
return (long) settingService.getAsInt(SettingKey.SYSTEM_STATUS_LIMIT) * sampleRateMs;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析历史起点
|
||||
*
|
||||
* @param axis 时间轴
|
||||
* @param windowMs 窗口毫秒数
|
||||
* @return 起点下标
|
||||
*/
|
||||
private int resolveStartIndex(List<Long> axis, long windowMs) {
|
||||
long threshold = axis.get(axis.size() - 1) - windowMs;
|
||||
for (int index = 0; index < axis.size(); index++) {
|
||||
if (threshold <= axis.get(index)) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
return axis.size() - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将周期累计值转换为每秒速率
|
||||
*
|
||||
* @param source 原始列表
|
||||
* @param sampleRateMs 采样周期
|
||||
* @return 速率列表
|
||||
*/
|
||||
private List<Long> toRate(List<Long> source, int sampleRateMs) {
|
||||
List<Long> result = new ArrayList<>(source.size());
|
||||
for (Long value : source) {
|
||||
result.add(value == null ? null : value * 1000 / sampleRateMs);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取与时间轴尾部对齐的值
|
||||
*
|
||||
* @param values 数据列表
|
||||
* @param axisSize 时间轴长度
|
||||
* @param axisIndex 时间轴下标
|
||||
* @param <T> 数据类型
|
||||
* @return 对齐后的值
|
||||
*/
|
||||
private <T> T getAlignedValue(List<T> values, int axisSize, int axisIndex) {
|
||||
int valueIndex = axisIndex - (axisSize - values.size());
|
||||
if (valueIndex < 0 || values.size() <= valueIndex) {
|
||||
return null;
|
||||
}
|
||||
return values.get(valueIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制长整型列表
|
||||
*
|
||||
* @param source 原始队列
|
||||
* @return 列表
|
||||
*/
|
||||
private List<Long> copyLongs(Deque<Number> source) {
|
||||
List<Long> result = new ArrayList<>(source.size());
|
||||
for (Number number : source) {
|
||||
result.add(number == null ? null : number.longValue());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制浮点列表
|
||||
*
|
||||
* @param source 原始队列
|
||||
* @return 列表
|
||||
*/
|
||||
private List<Double> copyDoubles(Deque<Number> source) {
|
||||
List<Double> result = new ArrayList<>(source.size());
|
||||
for (Number number : source) {
|
||||
result.add(number == null ? null : number.doubleValue());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最后一个长整型值
|
||||
*
|
||||
* @param source 队列
|
||||
* @return 值
|
||||
*/
|
||||
private Long lastLong(Deque<Number> source) {
|
||||
Number number = source.peekLast();
|
||||
return number == null ? null : number.longValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最后一个浮点值
|
||||
*
|
||||
* @param source 队列
|
||||
* @return 值
|
||||
*/
|
||||
private Double lastDouble(Deque<Number> source) {
|
||||
Number number = source.peekLast();
|
||||
return number == null ? null : number.doubleValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算百分比
|
||||
*
|
||||
* @param used 已用值
|
||||
* @param total 总值
|
||||
* @return 百分比
|
||||
*/
|
||||
private Double toPercent(Long used, long total) {
|
||||
if (used == null || total <= 0) {
|
||||
return null;
|
||||
}
|
||||
return used * 100D / total;
|
||||
}
|
||||
|
||||
/**
|
||||
* 状态指标
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
private enum Metric {
|
||||
OS,
|
||||
CPU,
|
||||
MEMORY,
|
||||
JVM,
|
||||
NETWORK,
|
||||
HARDWARE,
|
||||
STORAGE
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package com.imyeyu.api.modules.system.service.implement;
|
||||
|
||||
import com.imyeyu.api.modules.system.bean.UpsStatusStore;
|
||||
import com.imyeyu.api.modules.system.service.UpsService;
|
||||
import com.imyeyu.api.modules.system.task.UpsStatusTask;
|
||||
import com.imyeyu.api.modules.system.vo.ups.UpsHistoryPointView;
|
||||
import com.imyeyu.api.modules.system.vo.ups.UpsHistoryView;
|
||||
import com.imyeyu.api.modules.system.vo.ups.UpsStatusView;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* UPS 查询服务实现
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-07
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class UpsServiceImplement implements UpsService {
|
||||
|
||||
private final UpsStatusTask upsStatusTask;
|
||||
private final UpsStatusStore upsStatusStore;
|
||||
|
||||
@Value("${ups.collect-rate-ms:3000}")
|
||||
private long collectRateMs;
|
||||
|
||||
@Override
|
||||
public UpsStatusView getStatus() {
|
||||
ensureCurrentSnapshot();
|
||||
synchronized (upsStatusStore) {
|
||||
UpsStatusStore.Snapshot snapshot = upsStatusStore.getCurrent();
|
||||
if (snapshot == null) {
|
||||
return null;
|
||||
}
|
||||
UpsStatusView view = new UpsStatusView();
|
||||
view.setServerTime(System.currentTimeMillis());
|
||||
view.setUpsTime(snapshot.getUpsTime());
|
||||
view.setHostName(snapshot.getHostName());
|
||||
view.setCustomer(snapshot.getCustomer());
|
||||
view.setVersion(snapshot.getVersion());
|
||||
view.setDeviceId(snapshot.getDeviceId());
|
||||
view.setUpsType(snapshot.getUpsType());
|
||||
view.setMorphological(snapshot.getMorphological());
|
||||
view.setIoPhase(snapshot.getIoPhase());
|
||||
view.setWorkMode(snapshot.getWorkMode());
|
||||
view.setInputVoltage(snapshot.getInputVoltage());
|
||||
view.setInputFrequency(snapshot.getInputFrequency());
|
||||
view.setOutputVoltage(snapshot.getOutputVoltage());
|
||||
view.setOutputFrequency(snapshot.getOutputFrequency());
|
||||
view.setOutputLoadPercent(snapshot.getOutputLoadPercent());
|
||||
view.setBatteryVoltage(snapshot.getBatteryVoltage());
|
||||
view.setBatteryCapacity(snapshot.getBatteryCapacity());
|
||||
view.setBatteryRemainTime(snapshot.getBatteryRemainTime());
|
||||
view.setTemperature(snapshot.getTemperature());
|
||||
view.setBypassActive(snapshot.isBypassActive());
|
||||
view.setShutdownActive(snapshot.isShutdownActive());
|
||||
view.setOutputOn(snapshot.isOutputOn());
|
||||
view.setCharging(snapshot.isCharging());
|
||||
view.setFaultType(snapshot.getFaultType());
|
||||
view.setFaultKind(snapshot.getFaultKind());
|
||||
view.setWarnings(snapshot.getWarnings());
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpsHistoryView getHistory(String window) {
|
||||
ensureCurrentSnapshot();
|
||||
UpsHistoryView view = new UpsHistoryView();
|
||||
view.setServerTime(System.currentTimeMillis());
|
||||
view.setSampleRateMs(collectRateMs);
|
||||
|
||||
long windowMs = parseWindowMs(window);
|
||||
long threshold = view.getServerTime() - windowMs;
|
||||
|
||||
synchronized (upsStatusStore) {
|
||||
for (UpsStatusStore.Point point : upsStatusStore.getHistory()) {
|
||||
if (point.getAt() < threshold) {
|
||||
continue;
|
||||
}
|
||||
if (0 == view.getFrom()) {
|
||||
view.setFrom(point.getAt());
|
||||
}
|
||||
view.setTo(point.getAt());
|
||||
UpsHistoryPointView item = new UpsHistoryPointView();
|
||||
item.setAt(point.getAt());
|
||||
item.setUpsTime(point.getUpsTime());
|
||||
item.setWorkMode(point.getWorkMode());
|
||||
item.setInputVoltage(point.getInputVoltage());
|
||||
item.setInputFrequency(point.getInputFrequency());
|
||||
item.setOutputVoltage(point.getOutputVoltage());
|
||||
item.setOutputFrequency(point.getOutputFrequency());
|
||||
item.setOutputLoadPercent(point.getOutputLoadPercent());
|
||||
item.setBatteryVoltage(point.getBatteryVoltage());
|
||||
item.setBatteryCapacity(point.getBatteryCapacity());
|
||||
item.setBatteryRemainTime(point.getBatteryRemainTime());
|
||||
item.setTemperature(point.getTemperature());
|
||||
item.setBypassActive(point.isBypassActive());
|
||||
item.setShutdownActive(point.isShutdownActive());
|
||||
item.setOutputOn(point.isOutputOn());
|
||||
view.getPoints().add(item);
|
||||
}
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保证当前快照存在
|
||||
*/
|
||||
private void ensureCurrentSnapshot() {
|
||||
synchronized (upsStatusStore) {
|
||||
if (upsStatusStore.getCurrent() != null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
upsStatusTask.collectOnce();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析历史窗口
|
||||
*
|
||||
* @param window 历史窗口
|
||||
* @return 窗口毫秒数
|
||||
*/
|
||||
private long parseWindowMs(String window) {
|
||||
long defaultWindowMs = 24L * 60 * 60 * 1000;
|
||||
if (window == null || window.isBlank()) {
|
||||
return defaultWindowMs;
|
||||
}
|
||||
|
||||
String normalized = window.trim().toLowerCase();
|
||||
char suffix = normalized.charAt(normalized.length() - 1);
|
||||
long unit = switch (suffix) {
|
||||
case 's' -> 1000L;
|
||||
case 'm' -> 60_000L;
|
||||
case 'h' -> 3_600_000L;
|
||||
case 'd' -> 86_400_000L;
|
||||
default -> collectRateMs;
|
||||
};
|
||||
String valueText = Character.isDigit(suffix) ? normalized : normalized.substring(0, normalized.length() - 1);
|
||||
try {
|
||||
long parsed = Long.parseLong(valueText) * unit;
|
||||
long maxWindowMs = UpsStatusTask.MAX_HISTORY_MS;
|
||||
return Math.max(collectRateMs, Math.min(parsed, maxWindowMs));
|
||||
} catch (NumberFormatException e) {
|
||||
return defaultWindowMs;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
package com.imyeyu.api.modules.system.task;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.imyeyu.api.modules.system.bean.DockerStatusStore;
|
||||
import com.imyeyu.api.modules.system.util.DockerEngineClient;
|
||||
import com.imyeyu.utils.Time;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||
import org.springframework.scheduling.support.PeriodicTrigger;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Docker 鐘舵€侀噰闆嗕换鍔?
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DockerStatusTask implements SchedulingConfigurer {
|
||||
|
||||
private final DockerEngineClient dockerEngineClient;
|
||||
private final DockerStatusStore dockerStatusStore;
|
||||
|
||||
@Value("${docker.engine.collect-enabled:false}")
|
||||
private boolean collectEnabled;
|
||||
|
||||
@Value("${docker.engine.collect-rate-ms:10000}")
|
||||
private long collectRateMs;
|
||||
|
||||
@Value("${docker.engine.history-limit:120}")
|
||||
private int historyLimit;
|
||||
|
||||
@Override
|
||||
public void configureTasks(@NotNull ScheduledTaskRegistrar taskRegistrar) {
|
||||
if (!collectEnabled) {
|
||||
return;
|
||||
}
|
||||
PeriodicTrigger trigger = new PeriodicTrigger(collectRateMs, TimeUnit.MILLISECONDS);
|
||||
trigger.setInitialDelay(0);
|
||||
taskRegistrar.addTriggerTask(this::collect, trigger);
|
||||
}
|
||||
|
||||
private void collect() {
|
||||
try {
|
||||
ArrayNode containers = (ArrayNode) dockerEngineClient.getJson("/containers/json", DockerEngineClient.query("all", "true"));
|
||||
long now = Time.now();
|
||||
synchronized (dockerStatusStore) {
|
||||
Set<String> activeIds = new HashSet<>();
|
||||
for (JsonNode summary : containers) {
|
||||
try {
|
||||
String containerId = getAsString(summary, "Id");
|
||||
activeIds.add(containerId);
|
||||
DockerStatusStore.Container container = dockerStatusStore.getContainers().computeIfAbsent(containerId, key -> new DockerStatusStore.Container());
|
||||
updateContainerSummary(container, summary);
|
||||
updateContainerInspect(containerId, container);
|
||||
updateContainerStats(containerId, container, now);
|
||||
} catch (Exception e) {
|
||||
log.error("collect docker container item error", e);
|
||||
}
|
||||
}
|
||||
dockerStatusStore.getContainers().entrySet().removeIf(item -> !activeIds.contains(item.getKey()));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("collect docker container status error", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateContainerSummary(DockerStatusStore.Container container, JsonNode summary) {
|
||||
container.setId(getAsString(summary, "Id"));
|
||||
container.setName(trimContainerName(readFirstArrayText(summary, "Names")));
|
||||
container.setImage(getAsString(summary, "Image"));
|
||||
container.setImageId(getAsString(summary, "ImageID"));
|
||||
container.setCreatedAt(getAsLong(summary, "Created") * 1000);
|
||||
container.setState(getAsString(summary, "State"));
|
||||
container.setStatus(getAsString(summary, "Status"));
|
||||
}
|
||||
|
||||
private void updateContainerInspect(String containerId, DockerStatusStore.Container container) {
|
||||
JsonNode inspect = dockerEngineClient.getJson("/containers/%s/json".formatted(containerId), Map.of());
|
||||
JsonNode state = getAsObject(inspect, "State");
|
||||
container.setStartedAt(getAsString(state, "StartedAt"));
|
||||
container.setFinishedAt(getAsString(state, "FinishedAt"));
|
||||
container.setExitCode(getAsInteger(state, "ExitCode"));
|
||||
container.setRestartCount(getAsInteger(inspect, "RestartCount", 0));
|
||||
container.setOomKilled(getAsBoolean(state, "OOMKilled"));
|
||||
JsonNode health = getAsObject(state, "Health");
|
||||
container.setHealthStatus(health == null ? null : getAsString(health, "Status"));
|
||||
}
|
||||
|
||||
private void updateContainerStats(String containerId, DockerStatusStore.Container container, long now) {
|
||||
JsonNode stats = dockerEngineClient.getJson("/containers/%s/stats".formatted(containerId), DockerEngineClient.query("stream", "false"));
|
||||
Double cpuPercent = calculateCpuPercent(stats);
|
||||
Long memoryUsageBytes = getNestedLong(stats, "memory_stats", "usage");
|
||||
Long memoryLimitBytes = getNestedLong(stats, "memory_stats", "limit");
|
||||
Double memoryPercent = null;
|
||||
if (memoryUsageBytes != null && memoryLimitBytes != null && 0 < memoryLimitBytes) {
|
||||
memoryPercent = memoryUsageBytes * 100D / memoryLimitBytes;
|
||||
}
|
||||
Long networkRxBytes = 0L;
|
||||
Long networkTxBytes = 0L;
|
||||
JsonNode networks = getAsObject(stats, "networks");
|
||||
if (networks != null) {
|
||||
for (Map.Entry<String, JsonNode> item : (Iterable<Map.Entry<String, JsonNode>>) networks::fields) {
|
||||
JsonNode network = item.getValue();
|
||||
networkRxBytes += getAsLong(network, "rx_bytes", 0L);
|
||||
networkTxBytes += getAsLong(network, "tx_bytes", 0L);
|
||||
}
|
||||
}
|
||||
Long blockReadBytes = 0L;
|
||||
Long blockWriteBytes = 0L;
|
||||
JsonNode blkioStats = getAsObject(stats, "blkio_stats");
|
||||
ArrayNode ioServiceBytes = blkioStats == null ? null : getAsArray(blkioStats, "io_service_bytes_recursive");
|
||||
if (ioServiceBytes != null) {
|
||||
for (JsonNode io : ioServiceBytes) {
|
||||
String op = getAsString(io, "op");
|
||||
long value = getAsLong(io, "value", 0L);
|
||||
if ("Read".equalsIgnoreCase(op)) {
|
||||
blockReadBytes += value;
|
||||
} else if ("Write".equalsIgnoreCase(op)) {
|
||||
blockWriteBytes += value;
|
||||
}
|
||||
}
|
||||
}
|
||||
Integer pids = getNestedInteger(stats, "pids_stats", "current");
|
||||
|
||||
container.setCpuPercent(cpuPercent);
|
||||
container.setMemoryUsageBytes(memoryUsageBytes);
|
||||
container.setMemoryLimitBytes(memoryLimitBytes);
|
||||
container.setMemoryPercent(memoryPercent);
|
||||
container.setNetworkRxBytes(networkRxBytes);
|
||||
container.setNetworkTxBytes(networkTxBytes);
|
||||
container.setBlockReadBytes(blockReadBytes);
|
||||
container.setBlockWriteBytes(blockWriteBytes);
|
||||
container.setPids(pids);
|
||||
container.setUpdatedAt(now);
|
||||
|
||||
DockerStatusStore.Point point = new DockerStatusStore.Point();
|
||||
point.setAt(now);
|
||||
point.setCpuPercent(cpuPercent);
|
||||
point.setMemoryUsageBytes(memoryUsageBytes);
|
||||
point.setMemoryPercent(memoryPercent);
|
||||
point.setNetworkRxBytes(networkRxBytes);
|
||||
point.setNetworkTxBytes(networkTxBytes);
|
||||
point.setBlockReadBytes(blockReadBytes);
|
||||
point.setBlockWriteBytes(blockWriteBytes);
|
||||
point.setPids(pids);
|
||||
container.getHistory().addLast(point);
|
||||
while (historyLimit < container.getHistory().size()) {
|
||||
container.getHistory().pollFirst();
|
||||
}
|
||||
}
|
||||
|
||||
private Double calculateCpuPercent(JsonNode stats) {
|
||||
Long cpuTotal = getNestedLong(stats, "cpu_stats", "cpu_usage", "total_usage");
|
||||
Long preCpuTotal = getNestedLong(stats, "precpu_stats", "cpu_usage", "total_usage");
|
||||
Long systemTotal = getNestedLong(stats, "cpu_stats", "system_cpu_usage");
|
||||
Long preSystemTotal = getNestedLong(stats, "precpu_stats", "system_cpu_usage");
|
||||
Integer onlineCpus = getNestedInteger(stats, "cpu_stats", "online_cpus");
|
||||
if (onlineCpus == null || onlineCpus <= 0) {
|
||||
ArrayNode perCpuUsage = getNestedArray(stats, "cpu_stats", "cpu_usage", "percpu_usage");
|
||||
onlineCpus = perCpuUsage == null ? 1 : perCpuUsage.size();
|
||||
}
|
||||
if (cpuTotal == null || preCpuTotal == null || systemTotal == null || preSystemTotal == null) {
|
||||
return null;
|
||||
}
|
||||
long cpuDelta = cpuTotal - preCpuTotal;
|
||||
long systemDelta = systemTotal - preSystemTotal;
|
||||
if (cpuDelta <= 0 || systemDelta <= 0) {
|
||||
return 0D;
|
||||
}
|
||||
return cpuDelta * 100D * onlineCpus / systemDelta;
|
||||
}
|
||||
|
||||
private JsonNode getAsObject(JsonNode source, String key) {
|
||||
if (source == null || !source.has(key) || source.get(key).isNull() || !source.get(key).isObject()) {
|
||||
return null;
|
||||
}
|
||||
return source.get(key);
|
||||
}
|
||||
|
||||
private ArrayNode getAsArray(JsonNode source, String key) {
|
||||
if (source == null || !source.has(key) || !source.get(key).isArray()) {
|
||||
return null;
|
||||
}
|
||||
return (ArrayNode) source.get(key);
|
||||
}
|
||||
|
||||
private ArrayNode getNestedArray(JsonNode source, String... keys) {
|
||||
JsonNode current = source;
|
||||
for (String key : keys) {
|
||||
if (current == null || !current.isObject() || !current.has(key)) {
|
||||
return null;
|
||||
}
|
||||
current = current.get(key);
|
||||
}
|
||||
return current != null && current.isArray() ? (ArrayNode) current : null;
|
||||
}
|
||||
|
||||
private Long getNestedLong(JsonNode source, String... keys) {
|
||||
JsonNode current = source;
|
||||
for (String key : keys) {
|
||||
if (current == null || !current.isObject() || !current.has(key)) {
|
||||
return null;
|
||||
}
|
||||
current = current.get(key);
|
||||
}
|
||||
return current == null || current.isNull() ? null : current.asLong();
|
||||
}
|
||||
|
||||
private Integer getNestedInteger(JsonNode source, String... keys) {
|
||||
Long value = getNestedLong(source, keys);
|
||||
return value == null ? null : value.intValue();
|
||||
}
|
||||
|
||||
private String readFirstArrayText(JsonNode source, String key) {
|
||||
ArrayNode array = getAsArray(source, key);
|
||||
if (array == null || array.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return array.get(0).asText();
|
||||
}
|
||||
|
||||
private String getAsString(JsonNode source, String key) {
|
||||
if (source == null || !source.has(key) || source.get(key).isNull()) {
|
||||
return null;
|
||||
}
|
||||
return source.get(key).asText();
|
||||
}
|
||||
|
||||
private long getAsLong(JsonNode source, String key) {
|
||||
return getAsLong(source, key, 0L);
|
||||
}
|
||||
|
||||
private long getAsLong(JsonNode source, String key, long defaultValue) {
|
||||
if (source == null || !source.has(key) || source.get(key).isNull()) {
|
||||
return defaultValue;
|
||||
}
|
||||
return source.get(key).asLong();
|
||||
}
|
||||
|
||||
private Integer getAsInteger(JsonNode source, String key) {
|
||||
if (source == null || !source.has(key) || source.get(key).isNull()) {
|
||||
return null;
|
||||
}
|
||||
return source.get(key).asInt();
|
||||
}
|
||||
|
||||
private int getAsInteger(JsonNode source, String key, int defaultValue) {
|
||||
if (source == null || !source.has(key) || source.get(key).isNull()) {
|
||||
return defaultValue;
|
||||
}
|
||||
return source.get(key).asInt();
|
||||
}
|
||||
|
||||
private boolean getAsBoolean(JsonNode source, String key) {
|
||||
return source != null && source.has(key) && !source.get(key).isNull() && source.get(key).asBoolean();
|
||||
}
|
||||
|
||||
private String trimContainerName(String name) {
|
||||
if (name == null) {
|
||||
return null;
|
||||
}
|
||||
return name.startsWith("/") ? name.substring(1) : name;
|
||||
}
|
||||
}
|
||||
@@ -1,235 +1,72 @@
|
||||
package com.imyeyu.api.modules.system.task;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.imyeyu.utils.OS;
|
||||
import com.imyeyu.utils.Time;
|
||||
import com.imyeyu.java.TimiJava;
|
||||
import com.imyeyu.api.modules.common.bean.SettingKey;
|
||||
import com.imyeyu.api.modules.common.service.SettingService;
|
||||
import com.imyeyu.api.modules.system.bean.ServerStatus;
|
||||
import com.imyeyu.api.modules.system.task.status.StatusCollectContext;
|
||||
import com.imyeyu.api.modules.system.task.status.StatusCollector;
|
||||
import com.imyeyu.utils.Time;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||
import org.springframework.scheduling.support.CronTrigger;
|
||||
import org.springframework.stereotype.Service;
|
||||
import oshi.SystemInfo;
|
||||
import oshi.hardware.CentralProcessor;
|
||||
import oshi.hardware.GlobalMemory;
|
||||
import oshi.hardware.HardwareAbstractionLayer;
|
||||
import oshi.hardware.NetworkIF;
|
||||
import oshi.software.os.OSFileStore;
|
||||
import oshi.software.os.OperatingSystem;
|
||||
|
||||
import java.lang.management.GarbageCollectorMXBean;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.MemoryMXBean;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 服务器状态收集任务
|
||||
* 服务端状态采集任务
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2022-01-31 15:18
|
||||
* @since 2022-01-31 15:18
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ServerStatusTask implements SchedulingConfigurer, TimiJava {
|
||||
public class ServerStatusTask implements SchedulingConfigurer {
|
||||
|
||||
private final ServerStatus status;
|
||||
private final SettingService settingService;
|
||||
|
||||
private GlobalMemory globalMemory; // 内存
|
||||
private MemoryMXBean jvmMemory; // JVM 内存
|
||||
private OperatingSystem os; // 操作系统
|
||||
private CentralProcessor processor; // 中央处理器
|
||||
private HardwareAbstractionLayer hardware; // 硬件
|
||||
|
||||
private long[] lastCPUTicks; // CPU 上一时刻状态
|
||||
private long lastLinuxAPIMemoryUsed; // 上一周期 JVM 内存大小
|
||||
private final List<StatusCollector> statusCollectors;
|
||||
|
||||
@Override
|
||||
public void configureTasks(@NotNull ScheduledTaskRegistrar taskRegistrar) {
|
||||
lastLinuxAPIMemoryUsed = -1;
|
||||
SystemInfo systemInfo = new SystemInfo();
|
||||
HardwareAbstractionLayer hardware = systemInfo.getHardware();
|
||||
|
||||
// 系统信息
|
||||
SystemInfo system = new SystemInfo();
|
||||
|
||||
// 硬件信息
|
||||
hardware = system.getHardware();
|
||||
|
||||
// 操作系统
|
||||
os = system.getOperatingSystem();
|
||||
|
||||
// ---------- 静态数据 ----------
|
||||
|
||||
// 系统
|
||||
status.getOs().setName(OS.NAME);
|
||||
status.getOs().setBootAt(os.getSystemBootTime() * 1000);
|
||||
|
||||
// JVM
|
||||
jvmMemory = ManagementFactory.getMemoryMXBean();
|
||||
status.getJvm().setBootAt(Time.now());
|
||||
status.getJvm().setName(System.getProperty("java.vm.name"));
|
||||
status.getJvm().setVersion(System.getProperty("java.version"));
|
||||
|
||||
// CPU
|
||||
processor = hardware.getProcessor();
|
||||
status.getCpu().setName(processor.getProcessorIdentifier().getName().trim());
|
||||
status.getCpu().setCoreCount(processor.getPhysicalProcessorCount());
|
||||
status.getCpu().setLogicalCount(processor.getLogicalProcessorCount());
|
||||
|
||||
// 内存
|
||||
globalMemory = hardware.getMemory();
|
||||
status.getMemory().setSize(globalMemory.getTotal());
|
||||
status.getMemory().setSwapSize(globalMemory.getVirtualMemory().getSwapTotal());
|
||||
|
||||
// 网卡
|
||||
List<NetworkIF> networkIFs = hardware.getNetworkIFs();
|
||||
{
|
||||
NetworkIF networkIF;
|
||||
boolean isFound = false;
|
||||
for (int i = 0; i < networkIFs.size(); i++) {
|
||||
networkIF = networkIFs.get(i);
|
||||
if (networkIF.getMacaddr().equals(settingService.getAsString(SettingKey.SYSTEM_STATUS_NETWORK_MAC))) {
|
||||
status.getNetwork().setMac(networkIF.getMacaddr());
|
||||
|
||||
status.getNetwork().setRecvTotal(networkIF.getBytesRecv());
|
||||
status.getNetwork().setSentTotal(networkIF.getBytesSent());
|
||||
|
||||
isFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isFound) {
|
||||
log.error("not found setting networkIF MAC: %s" + settingService.getAsString(SettingKey.SYSTEM_STATUS_NETWORK_MAC));
|
||||
for (int i = 0; i < networkIFs.size(); i++) {
|
||||
log.info("Network Interface: {} -> {}", networkIFs.get(i).getMacaddr(), networkIFs.get(i).getDisplayName());
|
||||
}
|
||||
StatusCollectContext context = new StatusCollectContext(
|
||||
status,
|
||||
hardware.getMemory(),
|
||||
ManagementFactory.getMemoryMXBean(),
|
||||
settingService,
|
||||
hardware.getComputerSystem(),
|
||||
systemInfo.getOperatingSystem(),
|
||||
hardware.getProcessor(),
|
||||
hardware,
|
||||
Time.now()
|
||||
);
|
||||
synchronized (status) {
|
||||
for (StatusCollector collector : statusCollectors) {
|
||||
collector.initialize(context);
|
||||
}
|
||||
}
|
||||
|
||||
taskRegistrar.addTriggerTask(() -> {
|
||||
long now = Time.now();
|
||||
|
||||
// ---------- JVM 内存 ----------
|
||||
long nowLinuxAPIMemoryUsed = jvmMemory.getHeapMemoryUsage().getUsed();
|
||||
long linuxAPIMemoryDiff = Math.abs(nowLinuxAPIMemoryUsed - lastLinuxAPIMemoryUsed);
|
||||
lastLinuxAPIMemoryUsed = nowLinuxAPIMemoryUsed;
|
||||
status.getJvm().getMemory().setInit(jvmMemory.getHeapMemoryUsage().getInit());
|
||||
status.getJvm().getMemory().setMax(jvmMemory.getHeapMemoryUsage().getMax());
|
||||
putDeque(status.getJvm().getMemory().getUsed(), nowLinuxAPIMemoryUsed);
|
||||
putDeque(status.getJvm().getMemory().getCommitted(), jvmMemory.getHeapMemoryUsage().getCommitted());
|
||||
|
||||
// ---------- JVM GC ----------
|
||||
long gcSyncCycles = 0;
|
||||
long gcSyncCyclesTime = 0;
|
||||
long gcPauses = 0;
|
||||
long gcPausesTime = 0;
|
||||
for (GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans()) {
|
||||
gcSyncCycles += gc.getCollectionCount();
|
||||
gcSyncCyclesTime += gc.getCollectionTime();
|
||||
switch (gc.getName()) {
|
||||
case "ZGC Cycles" -> {
|
||||
gcSyncCycles += gc.getCollectionCount();
|
||||
gcSyncCyclesTime += gc.getCollectionTime();
|
||||
}
|
||||
case "ZGC Pauses" -> {
|
||||
gcPauses += gc.getCollectionCount();
|
||||
gcPausesTime += gc.getCollectionTime();
|
||||
if (status.getJvm().getZgc().getPauses() < gcPauses) {
|
||||
// 发生 GC 回收
|
||||
status.getJvm().getZgc().setLastPauseAt(now);
|
||||
status.getJvm().getZgc().setLastRecoverySize(linuxAPIMemoryDiff);
|
||||
}
|
||||
}
|
||||
synchronized (status) {
|
||||
context.setCollectAt(Time.now());
|
||||
for (StatusCollector collector : statusCollectors) {
|
||||
collector.collect(context);
|
||||
}
|
||||
status.getUpdateAxis().addLast(context.getCollectAt());
|
||||
if (settingService.getAsInt(SettingKey.SYSTEM_STATUS_LIMIT) < status.getUpdateAxis().size()) {
|
||||
status.getUpdateAxis().pollFirst();
|
||||
}
|
||||
}
|
||||
putDeque(status.getJvm().getZgc().getSyncCyclesTime(), gcSyncCyclesTime - status.getJvm().getZgc().getSyncCyclesTimeTotal());
|
||||
putDeque(status.getJvm().getZgc().getPausesTime(), gcPausesTime - status.getJvm().getZgc().getPausesTimeTotal());
|
||||
status.getJvm().getZgc().setSyncCycles(gcSyncCycles);
|
||||
status.getJvm().getZgc().setSyncCyclesTimeTotal(gcSyncCyclesTime);
|
||||
status.getJvm().getZgc().setPauses(gcPauses);
|
||||
status.getJvm().getZgc().setPausesTimeTotal(gcPausesTime);
|
||||
|
||||
// ---------- CPU ----------
|
||||
if (lastCPUTicks != null) {
|
||||
long[] ticks = processor.getSystemCpuLoadTicks();
|
||||
|
||||
long user = ticks[CentralProcessor.TickType.USER.getIndex()] - lastCPUTicks[CentralProcessor.TickType.USER.getIndex()];
|
||||
long nice = ticks[CentralProcessor.TickType.NICE.getIndex()] - lastCPUTicks[CentralProcessor.TickType.NICE.getIndex()];
|
||||
long sys = ticks[CentralProcessor.TickType.SYSTEM.getIndex()] - lastCPUTicks[CentralProcessor.TickType.SYSTEM.getIndex()];
|
||||
long idle = ticks[CentralProcessor.TickType.IDLE.getIndex()] - lastCPUTicks[CentralProcessor.TickType.IDLE.getIndex()];
|
||||
long ioWait = ticks[CentralProcessor.TickType.IOWAIT.getIndex()] - lastCPUTicks[CentralProcessor.TickType.IOWAIT.getIndex()];
|
||||
long irq = ticks[CentralProcessor.TickType.IRQ.getIndex()] - lastCPUTicks[CentralProcessor.TickType.IRQ.getIndex()];
|
||||
long softIRQ = ticks[CentralProcessor.TickType.SOFTIRQ.getIndex()] - lastCPUTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()];
|
||||
long steal = ticks[CentralProcessor.TickType.STEAL.getIndex()] - lastCPUTicks[CentralProcessor.TickType.STEAL.getIndex()];
|
||||
long total = user + nice + sys + idle + ioWait + irq + softIRQ + steal;
|
||||
|
||||
putDeque(status.getCpu().getSystem(), 100D * sys / total);
|
||||
putDeque(status.getCpu().getUsed(), 100 - 100D * idle / total);
|
||||
}
|
||||
lastCPUTicks = processor.getSystemCpuLoadTicks();
|
||||
status.getCpu().setTemperature(hardware.getSensors().getCpuTemperature());
|
||||
|
||||
// ---------- 内存 ----------
|
||||
putDeque(status.getMemory().getUsed(), globalMemory.getTotal() - globalMemory.getAvailable());
|
||||
putDeque(status.getMemory().getSwapUsed(), globalMemory.getVirtualMemory().getSwapUsed());
|
||||
|
||||
// ---------- 网络 ----------
|
||||
networkIFs.clear();
|
||||
networkIFs.addAll(hardware.getNetworkIFs());
|
||||
NetworkIF networkIF;
|
||||
for (int i = 0; i < networkIFs.size(); i++) {
|
||||
networkIF = networkIFs.get(i);
|
||||
if (networkIF.getMacaddr().equals(settingService.getAsString(SettingKey.SYSTEM_STATUS_NETWORK_MAC))) {
|
||||
long recv = networkIF.getBytesRecv() - status.getNetwork().getRecvTotal();
|
||||
long sent = networkIF.getBytesSent() - status.getNetwork().getSentTotal();
|
||||
status.getNetwork().setRecvNow(recv / (settingService.getAsInt(SettingKey.SYSTEM_STATUS_RATE) / 1000));
|
||||
status.getNetwork().setSentNow(sent / (settingService.getAsInt(SettingKey.SYSTEM_STATUS_RATE) / 1000));
|
||||
status.getNetwork().setRecvTotal(networkIF.getBytesRecv());
|
||||
status.getNetwork().setSentTotal(networkIF.getBytesSent());
|
||||
|
||||
putDeque(status.getNetwork().getRecv(), recv);
|
||||
putDeque(status.getNetwork().getSent(), sent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 磁盘分区 ----------
|
||||
ServerStatus.Partition partition;
|
||||
status.getPartitions().clear();
|
||||
// 分区从文件系统获取,而非物理分区
|
||||
List<OSFileStore> fileStores = os.getFileSystem().getFileStores();
|
||||
for (OSFileStore fileStore : fileStores) {
|
||||
partition = new ServerStatus.Partition();
|
||||
partition.setUuid(fileStore.getUUID());
|
||||
partition.setPath(fileStore.getMount());
|
||||
partition.setType(fileStore.getType());
|
||||
partition.setUsed(fileStore.getTotalSpace() - fileStore.getUsableSpace());
|
||||
partition.setTotal(fileStore.getTotalSpace());
|
||||
|
||||
status.getPartitions().add(partition);
|
||||
}
|
||||
|
||||
// ---------- 更新时轴 ----------
|
||||
putDeque(status.getUpdateAxis(), Time.now());
|
||||
}, tc -> new CronTrigger("0/%s * * * * ?".formatted(settingService.getAsInt(SettingKey.SYSTEM_STATUS_RATE) / 1000)).nextExecution(tc));
|
||||
}
|
||||
|
||||
/**
|
||||
* 有所限制地添加队列数据,达到配置 {@link SettingKey#SYSTEM_STATUS_LIMIT} 个时移除最旧的
|
||||
*
|
||||
* @param deque 队列
|
||||
* @param t 数据
|
||||
* @param <T> 数据类型
|
||||
*/
|
||||
private <T> void putDeque(Deque<T> deque, T t) {
|
||||
deque.addLast(t);
|
||||
if (settingService.getAsInt(SettingKey.SYSTEM_STATUS_LIMIT) < deque.size()) {
|
||||
deque.pollFirst();
|
||||
}
|
||||
}, triggerContext -> new CronTrigger("0/%s * * * * ?".formatted(settingService.getAsInt(SettingKey.SYSTEM_STATUS_RATE) / 1000)).nextExecution(triggerContext));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.imyeyu.api.modules.system.task;
|
||||
|
||||
import com.imyeyu.api.modules.system.bean.UpsStatusStore;
|
||||
import com.imyeyu.api.modules.system.util.UpsStatusClient;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||
import org.springframework.scheduling.support.PeriodicTrigger;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* UPS 状态采集任务
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-07
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class UpsStatusTask implements SchedulingConfigurer {
|
||||
|
||||
public static final long MAX_HISTORY_MS = 3L * 24 * 60 * 60 * 1000;
|
||||
|
||||
private final UpsStatusClient upsStatusClient;
|
||||
private final UpsStatusStore upsStatusStore;
|
||||
|
||||
@Value("${ups.collect-enabled:true}")
|
||||
private boolean collectEnabled;
|
||||
|
||||
@Value("${ups.collect-rate-ms:60000}")
|
||||
private long collectRateMs;
|
||||
|
||||
@Override
|
||||
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
|
||||
if (!collectEnabled) {
|
||||
return;
|
||||
}
|
||||
PeriodicTrigger trigger = new PeriodicTrigger(collectRateMs, TimeUnit.MILLISECONDS);
|
||||
trigger.setInitialDelay(0);
|
||||
taskRegistrar.addTriggerTask(this::collect, trigger);
|
||||
}
|
||||
|
||||
/**
|
||||
* 立即采集一次
|
||||
*/
|
||||
public void collectOnce() {
|
||||
collect();
|
||||
}
|
||||
|
||||
private void collect() {
|
||||
try {
|
||||
UpsStatusStore.Snapshot snapshot = upsStatusClient.fetchSnapshot();
|
||||
long now = System.currentTimeMillis();
|
||||
snapshot.setUpdatedAt(now);
|
||||
|
||||
UpsStatusStore.Point point = new UpsStatusStore.Point();
|
||||
point.setAt(now);
|
||||
point.setUpsTime(snapshot.getUpsTime());
|
||||
point.setWorkMode(snapshot.getWorkMode());
|
||||
point.setInputVoltage(snapshot.getInputVoltage());
|
||||
point.setInputFrequency(snapshot.getInputFrequency());
|
||||
point.setOutputVoltage(snapshot.getOutputVoltage());
|
||||
point.setOutputFrequency(snapshot.getOutputFrequency());
|
||||
point.setOutputLoadPercent(snapshot.getOutputLoadPercent());
|
||||
point.setBatteryVoltage(snapshot.getBatteryVoltage());
|
||||
point.setBatteryCapacity(snapshot.getBatteryCapacity());
|
||||
point.setBatteryRemainTime(snapshot.getBatteryRemainTime());
|
||||
point.setTemperature(snapshot.getTemperature());
|
||||
point.setBypassActive(snapshot.isBypassActive());
|
||||
point.setShutdownActive(snapshot.isShutdownActive());
|
||||
point.setOutputOn(snapshot.isOutputOn());
|
||||
|
||||
synchronized (upsStatusStore) {
|
||||
upsStatusStore.setCurrent(snapshot);
|
||||
upsStatusStore.getHistory().addLast(point);
|
||||
long threshold = now - MAX_HISTORY_MS;
|
||||
while (!upsStatusStore.getHistory().isEmpty() && upsStatusStore.getHistory().peekFirst().getAt() < threshold) {
|
||||
upsStatusStore.getHistory().pollFirst();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("collect ups status error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.imyeyu.api.modules.system.task.status;
|
||||
|
||||
import com.imyeyu.api.modules.common.bean.SettingKey;
|
||||
|
||||
import java.util.Deque;
|
||||
|
||||
/**
|
||||
* 带有限长队列工具的采集器基类
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
public abstract class AbstractDequeStatusCollector implements StatusCollector {
|
||||
|
||||
/**
|
||||
* 有限长度追加队列数据
|
||||
*
|
||||
* @param context 采集上下文
|
||||
* @param deque 队列
|
||||
* @param value 值
|
||||
* @param <T> 类型
|
||||
*/
|
||||
protected <T> void putDeque(StatusCollectContext context, Deque<T> deque, T value) {
|
||||
deque.addLast(value);
|
||||
if (context.getSettingService().getAsInt(SettingKey.SYSTEM_STATUS_LIMIT) < deque.size()) {
|
||||
deque.pollFirst();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.imyeyu.api.modules.system.task.status;
|
||||
|
||||
import com.imyeyu.api.modules.common.service.SettingService;
|
||||
import com.imyeyu.api.modules.system.bean.ServerStatus;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import oshi.hardware.CentralProcessor;
|
||||
import oshi.hardware.ComputerSystem;
|
||||
import oshi.hardware.GlobalMemory;
|
||||
import oshi.hardware.HardwareAbstractionLayer;
|
||||
import oshi.software.os.OperatingSystem;
|
||||
|
||||
import java.lang.management.MemoryMXBean;
|
||||
|
||||
/**
|
||||
* 状态采集上下文
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class StatusCollectContext {
|
||||
|
||||
private final ServerStatus status;
|
||||
private final GlobalMemory globalMemory;
|
||||
private final MemoryMXBean jvmMemory;
|
||||
private final SettingService settingService;
|
||||
private final ComputerSystem computerSystem;
|
||||
private final OperatingSystem operatingSystem;
|
||||
private final CentralProcessor processor;
|
||||
private final HardwareAbstractionLayer hardware;
|
||||
|
||||
@Setter
|
||||
private long collectAt;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.imyeyu.api.modules.system.task.status;
|
||||
|
||||
/**
|
||||
* 状态采集器
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
public interface StatusCollector {
|
||||
|
||||
/**
|
||||
* 初始化采集器
|
||||
*
|
||||
* @param context 采集上下文
|
||||
*/
|
||||
default void initialize(StatusCollectContext context) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 采集状态
|
||||
*
|
||||
* @param context 采集上下文
|
||||
*/
|
||||
void collect(StatusCollectContext context);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.imyeyu.api.modules.system.task.status.collector;
|
||||
|
||||
import com.imyeyu.api.modules.system.task.status.AbstractDequeStatusCollector;
|
||||
import com.imyeyu.api.modules.system.task.status.StatusCollectContext;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import oshi.hardware.CentralProcessor;
|
||||
|
||||
/**
|
||||
* CPU 状态采集器
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2026-04-07 11:18
|
||||
*/
|
||||
@Component
|
||||
@Order(20)
|
||||
public class CpuStatusCollector extends AbstractDequeStatusCollector {
|
||||
|
||||
private long[] lastCpuTicks;
|
||||
|
||||
@Override
|
||||
public void initialize(StatusCollectContext context) {
|
||||
context.getStatus().getCpu().setName(context.getProcessor().getProcessorIdentifier().getName().trim());
|
||||
context.getStatus().getCpu().setCoreCount(context.getProcessor().getPhysicalProcessorCount());
|
||||
context.getStatus().getCpu().setLogicalCount(context.getProcessor().getLogicalProcessorCount());
|
||||
lastCpuTicks = context.getProcessor().getSystemCpuLoadTicks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void collect(StatusCollectContext context) {
|
||||
long[] ticks = context.getProcessor().getSystemCpuLoadTicks();
|
||||
if (lastCpuTicks != null) {
|
||||
long user = ticks[CentralProcessor.TickType.USER.getIndex()] - lastCpuTicks[CentralProcessor.TickType.USER.getIndex()];
|
||||
long nice = ticks[CentralProcessor.TickType.NICE.getIndex()] - lastCpuTicks[CentralProcessor.TickType.NICE.getIndex()];
|
||||
long sys = ticks[CentralProcessor.TickType.SYSTEM.getIndex()] - lastCpuTicks[CentralProcessor.TickType.SYSTEM.getIndex()];
|
||||
long idle = ticks[CentralProcessor.TickType.IDLE.getIndex()] - lastCpuTicks[CentralProcessor.TickType.IDLE.getIndex()];
|
||||
long ioWait = ticks[CentralProcessor.TickType.IOWAIT.getIndex()] - lastCpuTicks[CentralProcessor.TickType.IOWAIT.getIndex()];
|
||||
long irq = ticks[CentralProcessor.TickType.IRQ.getIndex()] - lastCpuTicks[CentralProcessor.TickType.IRQ.getIndex()];
|
||||
long softIrq = ticks[CentralProcessor.TickType.SOFTIRQ.getIndex()] - lastCpuTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()];
|
||||
long steal = ticks[CentralProcessor.TickType.STEAL.getIndex()] - lastCpuTicks[CentralProcessor.TickType.STEAL.getIndex()];
|
||||
double total = user + nice + sys + idle + ioWait + irq + softIrq + steal;
|
||||
if (0 < total) {
|
||||
putDeque(context, context.getStatus().getCpu().getSystem(), sys / total);
|
||||
putDeque(context, context.getStatus().getCpu().getUsed(), 1 - idle / total);
|
||||
}
|
||||
}
|
||||
lastCpuTicks = ticks;
|
||||
context.getStatus().getCpu().setTemperature(context.getHardware().getSensors().getCpuTemperature());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.imyeyu.api.modules.system.task.status.collector;
|
||||
|
||||
import com.imyeyu.api.modules.system.bean.ServerStatus;
|
||||
import com.imyeyu.api.modules.system.task.status.StatusCollectContext;
|
||||
import com.imyeyu.api.modules.system.task.status.StatusCollector;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* 硬件状态采集器
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2026-04-07 11:18
|
||||
*/
|
||||
@Component
|
||||
@Order(35)
|
||||
public class HardwareStatusCollector implements StatusCollector {
|
||||
|
||||
@Override
|
||||
public void initialize(StatusCollectContext context) {
|
||||
ServerStatus.Baseboard baseboard = context.getStatus().getHardware().getBaseboard();
|
||||
baseboard.setManufacturer(context.getComputerSystem().getBaseboard().getManufacturer());
|
||||
baseboard.setModel(context.getComputerSystem().getBaseboard().getModel());
|
||||
baseboard.setVersion(context.getComputerSystem().getBaseboard().getVersion());
|
||||
baseboard.setSerialNumber(context.getComputerSystem().getBaseboard().getSerialNumber());
|
||||
|
||||
ServerStatus.Firmware firmware = context.getStatus().getHardware().getFirmware();
|
||||
firmware.setManufacturer(context.getComputerSystem().getFirmware().getManufacturer());
|
||||
firmware.setName(context.getComputerSystem().getFirmware().getName());
|
||||
firmware.setDescription(context.getComputerSystem().getFirmware().getDescription());
|
||||
firmware.setVersion(context.getComputerSystem().getFirmware().getVersion());
|
||||
firmware.setReleaseDate(context.getComputerSystem().getFirmware().getReleaseDate());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void collect(StatusCollectContext context) {
|
||||
int[] fanSpeeds = context.getHardware().getSensors().getFanSpeeds();
|
||||
ArrayList<Integer> values = new ArrayList<>(fanSpeeds.length);
|
||||
for (int fanSpeed : fanSpeeds) {
|
||||
values.add(fanSpeed);
|
||||
}
|
||||
context.getStatus().getHardware().setFanSpeeds(values);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.imyeyu.api.modules.system.task.status.collector;
|
||||
|
||||
import com.imyeyu.api.modules.system.task.status.AbstractDequeStatusCollector;
|
||||
import com.imyeyu.api.modules.system.task.status.StatusCollectContext;
|
||||
import com.imyeyu.utils.Time;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.management.GarbageCollectorMXBean;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* JVM 状态采集器
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2026-04-07 11:17
|
||||
*/
|
||||
@Component
|
||||
@Order(40)
|
||||
public class JvmStatusCollector extends AbstractDequeStatusCollector {
|
||||
|
||||
private long lastHeapUsed = -1;
|
||||
|
||||
@Override
|
||||
public void initialize(StatusCollectContext context) {
|
||||
context.getStatus().getJvm().setBootAt(Time.now());
|
||||
context.getStatus().getJvm().setName(System.getProperty("java.vm.name"));
|
||||
context.getStatus().getJvm().setVersion(System.getProperty("java.version"));
|
||||
context.getStatus().getJvm().setGcName(resolveGcName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void collect(StatusCollectContext context) {
|
||||
long heapUsed = context.getJvmMemory().getHeapMemoryUsage().getUsed();
|
||||
context.getStatus().getJvm().getMemory().setInit(context.getJvmMemory().getHeapMemoryUsage().getInit());
|
||||
context.getStatus().getJvm().getMemory().setMax(context.getJvmMemory().getHeapMemoryUsage().getMax());
|
||||
putDeque(context, context.getStatus().getJvm().getMemory().getUsed(), heapUsed);
|
||||
putDeque(context, context.getStatus().getJvm().getMemory().getCommitted(), context.getJvmMemory().getHeapMemoryUsage().getCommitted());
|
||||
|
||||
long recoverySize = 0;
|
||||
if (0 <= lastHeapUsed) {
|
||||
recoverySize = Math.abs(heapUsed - lastHeapUsed);
|
||||
}
|
||||
lastHeapUsed = heapUsed;
|
||||
|
||||
long gcSyncCycles = 0;
|
||||
long gcSyncCyclesTime = 0;
|
||||
long gcPauses = 0;
|
||||
long gcPausesTime = 0;
|
||||
for (GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans()) {
|
||||
switch (gc.getName()) {
|
||||
case "ZGC Cycles" -> {
|
||||
gcSyncCycles += gc.getCollectionCount();
|
||||
gcSyncCyclesTime += gc.getCollectionTime();
|
||||
}
|
||||
case "ZGC Pauses" -> {
|
||||
gcPauses += gc.getCollectionCount();
|
||||
gcPausesTime += gc.getCollectionTime();
|
||||
}
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (context.getStatus().getJvm().getGc().getPauses() < gcPauses) {
|
||||
context.getStatus().getJvm().getGc().setLastPauseAt(context.getCollectAt());
|
||||
context.getStatus().getJvm().getGc().setLastRecoverySize(recoverySize);
|
||||
}
|
||||
putDeque(context, context.getStatus().getJvm().getGc().getSyncCyclesTime(), gcSyncCyclesTime - context.getStatus().getJvm().getGc().getSyncCyclesTimeTotal());
|
||||
putDeque(context, context.getStatus().getJvm().getGc().getPausesTime(), gcPausesTime - context.getStatus().getJvm().getGc().getPausesTimeTotal());
|
||||
context.getStatus().getJvm().getGc().setSyncCycles(gcSyncCycles);
|
||||
context.getStatus().getJvm().getGc().setSyncCyclesTimeTotal(gcSyncCyclesTime);
|
||||
context.getStatus().getJvm().getGc().setPauses(gcPauses);
|
||||
context.getStatus().getJvm().getGc().setPausesTimeTotal(gcPausesTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析当前 JVM 的主要 GC 名称
|
||||
*
|
||||
* @return GC 名称
|
||||
*/
|
||||
private String resolveGcName() {
|
||||
List<GarbageCollectorMXBean> collectors = ManagementFactory.getGarbageCollectorMXBeans();
|
||||
if (collectors.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (1 == collectors.size()) {
|
||||
return collectors.get(0).getName();
|
||||
}
|
||||
StringBuilder gcName = new StringBuilder();
|
||||
for (GarbageCollectorMXBean collector : collectors) {
|
||||
if (!gcName.isEmpty()) {
|
||||
gcName.append(", ");
|
||||
}
|
||||
gcName.append(collector.getName());
|
||||
}
|
||||
return gcName.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.imyeyu.api.modules.system.task.status.collector;
|
||||
|
||||
import com.imyeyu.api.modules.system.task.status.AbstractDequeStatusCollector;
|
||||
import com.imyeyu.api.modules.system.task.status.StatusCollectContext;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 系统内存状态采集器
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2026-04-07 11:17
|
||||
*/
|
||||
@Component
|
||||
@Order(30)
|
||||
public class MemoryStatusCollector extends AbstractDequeStatusCollector {
|
||||
|
||||
@Override
|
||||
public void initialize(StatusCollectContext context) {
|
||||
context.getStatus().getMemory().setSize(context.getGlobalMemory().getTotal());
|
||||
context.getStatus().getMemory().setSwapSize(context.getGlobalMemory().getVirtualMemory().getSwapTotal());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void collect(StatusCollectContext context) {
|
||||
putDeque(context, context.getStatus().getMemory().getUsed(), context.getGlobalMemory().getTotal() - context.getGlobalMemory().getAvailable());
|
||||
putDeque(context, context.getStatus().getMemory().getSwapUsed(), context.getGlobalMemory().getVirtualMemory().getSwapUsed());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.imyeyu.api.modules.system.task.status.collector;
|
||||
|
||||
import com.imyeyu.api.modules.common.bean.SettingKey;
|
||||
import com.imyeyu.api.modules.system.task.status.AbstractDequeStatusCollector;
|
||||
import com.imyeyu.api.modules.system.task.status.StatusCollectContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bytedeco.librealsense.context;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import oshi.hardware.NetworkIF;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 网络状态采集器
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2026-04-07 11:15
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@Order(50)
|
||||
public class NetworkStatusCollector extends AbstractDequeStatusCollector {
|
||||
|
||||
@Override
|
||||
public void initialize(StatusCollectContext context) {
|
||||
List<NetworkIF> networkIFs = context.getHardware().getNetworkIFs();
|
||||
String targetMac = context.getSettingService().getAsString(SettingKey.SYSTEM_STATUS_NETWORK_MAC);
|
||||
for (NetworkIF networkIF : networkIFs) {
|
||||
if (networkIF.getMacaddr().equals(targetMac)) {
|
||||
networkIF.updateAttributes();
|
||||
updateNetworkStatus(context, networkIF);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
log.error("not found setting networkIF MAC: {}", targetMac);
|
||||
for (NetworkIF networkIF : networkIFs) {
|
||||
log.info("Network Interface: {} -> {}", networkIF.getMacaddr(), networkIF.getDisplayName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void collect(StatusCollectContext context) {
|
||||
String targetMac = context.getSettingService().getAsString(SettingKey.SYSTEM_STATUS_NETWORK_MAC);
|
||||
int sampleRateMs = context.getSettingService().getAsInt(SettingKey.SYSTEM_STATUS_RATE);
|
||||
for (NetworkIF networkIF : context.getHardware().getNetworkIFs()) {
|
||||
if (networkIF.getMacaddr().equals(targetMac)) {
|
||||
networkIF.updateAttributes();
|
||||
long recv = networkIF.getBytesRecv() - context.getStatus().getNetwork().getRecvTotal();
|
||||
long sent = networkIF.getBytesSent() - context.getStatus().getNetwork().getSentTotal();
|
||||
context.getStatus().getNetwork().setRecvNow(recv * 1000 / sampleRateMs);
|
||||
context.getStatus().getNetwork().setSentNow(sent * 1000 / sampleRateMs);
|
||||
updateNetworkStatus(context, networkIF);
|
||||
putDeque(context, context.getStatus().getNetwork().getRecv(), recv);
|
||||
putDeque(context, context.getStatus().getNetwork().getSent(), sent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新网络状态缓存
|
||||
*
|
||||
* @param context 采集上下文
|
||||
* @param networkIF 网卡
|
||||
*/
|
||||
private void updateNetworkStatus(StatusCollectContext context, NetworkIF networkIF) {
|
||||
context.getStatus().getNetwork().setName(networkIF.getDisplayName());
|
||||
context.getStatus().getNetwork().setMac(networkIF.getMacaddr());
|
||||
context.getStatus().getNetwork().setRecvTotal(networkIF.getBytesRecv());
|
||||
context.getStatus().getNetwork().setSentTotal(networkIF.getBytesSent());
|
||||
context.getStatus().getNetwork().setRecvPacketsTotal(networkIF.getPacketsRecv());
|
||||
context.getStatus().getNetwork().setSentPacketsTotal(networkIF.getPacketsSent());
|
||||
context.getStatus().getNetwork().setInErrors(networkIF.getInErrors());
|
||||
context.getStatus().getNetwork().setOutErrors(networkIF.getOutErrors());
|
||||
context.getStatus().getNetwork().setInDrops(networkIF.getInDrops());
|
||||
context.getStatus().getNetwork().setCollisions(networkIF.getCollisions());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.imyeyu.api.modules.system.task.status.collector;
|
||||
|
||||
import com.imyeyu.api.modules.system.task.status.StatusCollectContext;
|
||||
import com.imyeyu.api.modules.system.task.status.StatusCollector;
|
||||
import com.imyeyu.utils.OS;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 操作系统状态采集器
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2026-04-07 11:15
|
||||
*/
|
||||
@Component
|
||||
@Order(10)
|
||||
public class OSStatusCollector implements StatusCollector {
|
||||
|
||||
@Override
|
||||
public void initialize(StatusCollectContext context) {
|
||||
context.getStatus().getOs().setName(OS.NAME);
|
||||
context.getStatus().getOs().setBootAt(context.getOperatingSystem().getSystemBootTime() * 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void collect(StatusCollectContext context) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.imyeyu.api.modules.system.task.status.collector;
|
||||
|
||||
import com.imyeyu.api.modules.system.bean.ServerStatus;
|
||||
import com.imyeyu.api.modules.system.task.status.StatusCollectContext;
|
||||
import com.imyeyu.api.modules.system.task.status.StatusCollector;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import oshi.hardware.HWDiskStore;
|
||||
import oshi.hardware.HWPartition;
|
||||
import oshi.software.os.OSFileStore;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 存储状态采集器
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2026-04-07 11:14
|
||||
*/
|
||||
@Component
|
||||
@Order(60)
|
||||
public class StorageStatusCollector implements StatusCollector {
|
||||
|
||||
@Override
|
||||
public void collect(StatusCollectContext context) {
|
||||
Map<String, OSFileStore> fileStoreMap = createFileStoreMap(context);
|
||||
context.getStatus().getStoragePartitions().clear();
|
||||
for (HWDiskStore diskStore : context.getHardware().getDiskStores()) {
|
||||
diskStore.updateAttributes();
|
||||
for (HWPartition partition : diskStore.getPartitions()) {
|
||||
ServerStatus.StoragePartition item = new ServerStatus.StoragePartition();
|
||||
item.setDiskName(diskStore.getName());
|
||||
item.setDiskModel(diskStore.getModel());
|
||||
item.setDiskSerial(diskStore.getSerial());
|
||||
item.setPartitionId(partition.getIdentification());
|
||||
item.setPartitionName(partition.getName());
|
||||
item.setPartitionType(partition.getType());
|
||||
item.setUuid(partition.getUuid());
|
||||
item.setMountPoint(partition.getMountPoint());
|
||||
item.setTotal(partition.getSize());
|
||||
item.setTransferTimeMs(diskStore.getTransferTime());
|
||||
|
||||
OSFileStore fileStore = matchFileStore(partition, fileStoreMap);
|
||||
if (fileStore != null) {
|
||||
fileStore.updateAttributes();
|
||||
item.setUsed(fileStore.getTotalSpace() - fileStore.getUsableSpace());
|
||||
}
|
||||
context.getStatus().getStoragePartitions().add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建文件系统映射
|
||||
*
|
||||
* @param context 采集上下文
|
||||
* @return 映射表
|
||||
*/
|
||||
private Map<String, OSFileStore> createFileStoreMap(StatusCollectContext context) {
|
||||
Map<String, OSFileStore> result = new HashMap<>();
|
||||
for (OSFileStore fileStore : context.getOperatingSystem().getFileSystem().getFileStores()) {
|
||||
result.put("mount:" + fileStore.getMount(), fileStore);
|
||||
result.put("volume:" + fileStore.getVolume(), fileStore);
|
||||
result.put("name:" + fileStore.getName(), fileStore);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 匹配文件系统
|
||||
*
|
||||
* @param partition 物理分区
|
||||
* @param fileStoreMap 文件系统映射
|
||||
* @return 文件系统
|
||||
*/
|
||||
private OSFileStore matchFileStore(HWPartition partition, Map<String, OSFileStore> fileStoreMap) {
|
||||
if (partition.getMountPoint() != null && !partition.getMountPoint().isBlank()) {
|
||||
OSFileStore byMount = fileStoreMap.get("mount:" + partition.getMountPoint());
|
||||
if (byMount != null) {
|
||||
return byMount;
|
||||
}
|
||||
}
|
||||
OSFileStore byName = fileStoreMap.get("name:" + partition.getName());
|
||||
if (byName != null) {
|
||||
return byName;
|
||||
}
|
||||
return fileStoreMap.get("volume:" + partition.getIdentification());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
package com.imyeyu.api.modules.system.util;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.imyeyu.java.bean.timi.TimiCode;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.UnixDomainSocketAddress;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Docker Engine API 客户端
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DockerEngineClient {
|
||||
|
||||
private final String host;
|
||||
private final String apiVersion;
|
||||
private final Duration timeout;
|
||||
private final ObjectMapper jackson;
|
||||
private final HttpClient httpClient = HttpClient.newBuilder().build();
|
||||
|
||||
public DockerEngineClient(
|
||||
ObjectMapper jackson,
|
||||
@Value("${docker.engine.host:unix:///var/run/docker.sock}") String host,
|
||||
@Value("${docker.engine.api-version:v1.41}") String apiVersion,
|
||||
@Value("${docker.engine.timeout-ms:5000}") long timeoutMs
|
||||
) {
|
||||
this.jackson = jackson;
|
||||
this.host = host;
|
||||
this.apiVersion = apiVersion;
|
||||
this.timeout = Duration.ofMillis(timeoutMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 JSON 响应
|
||||
*
|
||||
* @param path 接口路径
|
||||
* @param queryParams 查询参数
|
||||
* @return JSON 数据
|
||||
*/
|
||||
public JsonNode getJson(String path, Map<String, String> queryParams) {
|
||||
String requestPath = buildRequestPath(path, queryParams);
|
||||
try {
|
||||
String body = host.startsWith("unix://") ? executeUnixGet(requestPath) : executeHttpGet(requestPath);
|
||||
return jackson.readTree(body);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.error("docker engine request interrupted: {}", requestPath, e);
|
||||
throw new TimiException(TimiCode.ERROR).msgKey("TODO docker engine request interrupted");
|
||||
} catch (IOException e) {
|
||||
log.error("docker engine request error: {}", requestPath, e);
|
||||
throw new TimiException(TimiCode.ERROR).msgKey("TODO docker engine request error");
|
||||
}
|
||||
}
|
||||
|
||||
private String buildRequestPath(String path, Map<String, String> queryParams) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("/");
|
||||
builder.append(apiVersion);
|
||||
if (!path.startsWith("/")) {
|
||||
builder.append("/");
|
||||
}
|
||||
builder.append(path);
|
||||
if (queryParams != null && !queryParams.isEmpty()) {
|
||||
builder.append("?");
|
||||
boolean first = true;
|
||||
for (Map.Entry<String, String> item : queryParams.entrySet()) {
|
||||
if (!first) {
|
||||
builder.append("&");
|
||||
}
|
||||
first = false;
|
||||
builder.append(item.getKey()).append("=").append(item.getValue());
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private String executeHttpGet(String requestPath) throws IOException, InterruptedException {
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(host + requestPath))
|
||||
.timeout(timeout)
|
||||
.GET()
|
||||
.build();
|
||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
||||
if (400 <= response.statusCode()) {
|
||||
throw new IOException("docker engine http error: " + response.statusCode());
|
||||
}
|
||||
return response.body();
|
||||
}
|
||||
|
||||
private String executeUnixGet(String requestPath) throws IOException {
|
||||
String socketPath = host.substring("unix://".length());
|
||||
UnixDomainSocketAddress address = UnixDomainSocketAddress.of(Path.of(socketPath));
|
||||
try (SocketChannel channel = SocketChannel.open(address)) {
|
||||
channel.configureBlocking(true);
|
||||
String requestText = """
|
||||
GET %s HTTP/1.1\r
|
||||
Host: docker\r
|
||||
Accept: application/json\r
|
||||
Connection: close\r
|
||||
\r
|
||||
""".formatted(requestPath);
|
||||
channel.write(StandardCharsets.UTF_8.encode(requestText));
|
||||
byte[] responseBytes = readAll(channel);
|
||||
return parseHttpBody(responseBytes);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] readAll(SocketChannel channel) throws IOException {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(8192);
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
while (true) {
|
||||
int readLength = channel.read(buffer);
|
||||
if (readLength < 0) {
|
||||
break;
|
||||
}
|
||||
if (0 == readLength) {
|
||||
continue;
|
||||
}
|
||||
buffer.flip();
|
||||
output.write(buffer.array(), 0, buffer.remaining());
|
||||
buffer.clear();
|
||||
}
|
||||
return output.toByteArray();
|
||||
}
|
||||
|
||||
private String parseHttpBody(byte[] responseBytes) throws IOException {
|
||||
int splitIndex = indexOf(responseBytes, new byte[] {'\r', '\n', '\r', '\n'});
|
||||
if (splitIndex < 0) {
|
||||
throw new IOException("invalid docker engine response");
|
||||
}
|
||||
String headerText = new String(responseBytes, 0, splitIndex, StandardCharsets.UTF_8);
|
||||
byte[] bodyBytes = new byte[responseBytes.length - splitIndex - 4];
|
||||
System.arraycopy(responseBytes, splitIndex + 4, bodyBytes, 0, bodyBytes.length);
|
||||
if (headerText.contains("Transfer-Encoding: chunked")) {
|
||||
bodyBytes = decodeChunkedBody(bodyBytes);
|
||||
}
|
||||
if (!headerText.startsWith("HTTP/1.1 200") && !headerText.startsWith("HTTP/1.1 204")) {
|
||||
throw new IOException("docker engine http error: " + headerText.lines().findFirst().orElse(headerText));
|
||||
}
|
||||
return new String(bodyBytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private byte[] decodeChunkedBody(byte[] bodyBytes) throws IOException {
|
||||
int offset = 0;
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
while (offset < bodyBytes.length) {
|
||||
int lineEnd = indexOf(bodyBytes, offset, new byte[] {'\r', '\n'});
|
||||
if (lineEnd < 0) {
|
||||
break;
|
||||
}
|
||||
String sizeText = new String(bodyBytes, offset, lineEnd - offset, StandardCharsets.UTF_8).trim();
|
||||
int chunkSize = Integer.parseInt(sizeText, 16);
|
||||
offset = lineEnd + 2;
|
||||
if (0 == chunkSize) {
|
||||
break;
|
||||
}
|
||||
output.write(bodyBytes, offset, chunkSize);
|
||||
offset += chunkSize + 2;
|
||||
}
|
||||
return output.toByteArray();
|
||||
}
|
||||
|
||||
private int indexOf(byte[] source, byte[] pattern) {
|
||||
return indexOf(source, 0, pattern);
|
||||
}
|
||||
|
||||
private int indexOf(byte[] source, int start, byte[] pattern) {
|
||||
for (int i = start; i <= source.length - pattern.length; i++) {
|
||||
boolean matched = true;
|
||||
for (int j = 0; j < pattern.length; j++) {
|
||||
if (source[i + j] != pattern[j]) {
|
||||
matched = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (matched) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static Map<String, String> query(String... entries) {
|
||||
LinkedHashMap<String, String> result = new LinkedHashMap<>();
|
||||
for (int i = 0; i + 1 < entries.length; i += 2) {
|
||||
result.put(entries[i], entries[i + 1]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -29,9 +29,9 @@ public class SystemAPIInterceptor implements HandlerInterceptor {
|
||||
private final SettingService settingService;
|
||||
|
||||
public boolean preHandle(@NonNull HttpServletRequest req, @NonNull HttpServletResponse resp, @NonNull Object handler) {
|
||||
String key = TimiSpring.getHeader("Key");
|
||||
String key = TimiSpring.getHeader("Token");
|
||||
if (TimiJava.isEmpty(key)) {
|
||||
key = req.getParameter("key");
|
||||
key = req.getParameter("token");
|
||||
}
|
||||
String dbKey = settingService.getAsString(SettingKey.SYSTEM_API_KEY);
|
||||
String dbSuperKey = settingService.getAsString(SettingKey.SYSTEM_API_SUPER_KEY);
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
package com.imyeyu.api.modules.system.util;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.imyeyu.api.modules.system.bean.UpsStatusStore;
|
||||
import com.imyeyu.java.bean.timi.TimiCode;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import com.imyeyu.network.ArgMap;
|
||||
import com.imyeyu.network.CommonRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.http.HttpClient;
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* UPS 状态客户端
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-07
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class UpsStatusClient {
|
||||
|
||||
private final String statusUrl;
|
||||
private final Duration timeout;
|
||||
private final ObjectMapper jackson;
|
||||
private final HttpClient httpClient = HttpClient.newBuilder().build();
|
||||
|
||||
public UpsStatusClient(
|
||||
ObjectMapper jackson,
|
||||
@Value("${ups.status-url:http://192.168.3.24:15178/ViewPower/workstatus/reqMonitorData?0.9053892723012932}") String statusUrl,
|
||||
@Value("${ups.timeout-ms:5000}") long timeoutMs
|
||||
) {
|
||||
this.jackson = jackson;
|
||||
this.statusUrl = statusUrl;
|
||||
this.timeout = Duration.ofMillis(timeoutMs);
|
||||
}
|
||||
|
||||
public JsonNode getStatusJson() {
|
||||
if (statusUrl == null || statusUrl.isBlank()) {
|
||||
throw new TimiException(TimiCode.ARG_MISS).msgKey("缺少配置:ups.status-url");
|
||||
}
|
||||
try {
|
||||
String response = CommonRequest.post(statusUrl).bodyEntity(ArgMap.of("portName", "USBusbdev3").toEntity()).asString();
|
||||
return jackson.readTree(response);
|
||||
} catch (IOException e) {
|
||||
log.error("request ups status error: {}", statusUrl, e);
|
||||
throw new TimiException(TimiCode.ERROR).msgKey("UPS 状态请求失败");
|
||||
}
|
||||
}
|
||||
|
||||
public UpsStatusStore.Snapshot fetchSnapshot() {
|
||||
JsonNode root = getStatusJson();
|
||||
JsonNode workInfo = getAsObject(root, "workInfo");
|
||||
|
||||
UpsStatusStore.Snapshot snapshot = new UpsStatusStore.Snapshot();
|
||||
snapshot.setUpsTime(readUpsTime(workInfo));
|
||||
snapshot.setHostName(readMeaningfulText(root, "hostName"));
|
||||
snapshot.setCustomer(readMeaningfulText(root, "customer"));
|
||||
snapshot.setVersion(readMeaningfulText(root, "version"));
|
||||
snapshot.setDeviceId(readMeaningfulText(workInfo, "deviceId"));
|
||||
snapshot.setUpsType(readMeaningfulText(workInfo, "upsType"));
|
||||
snapshot.setMorphological(readMeaningfulText(workInfo, "morphological"));
|
||||
snapshot.setIoPhase(readMeaningfulText(workInfo, "ioPhase"));
|
||||
snapshot.setWorkMode(readMeaningfulText(workInfo, "workMode"));
|
||||
snapshot.setInputVoltage(readMeaningfulDouble(workInfo, "inputVoltage"));
|
||||
snapshot.setInputFrequency(readMeaningfulDouble(workInfo, "inputFrequency"));
|
||||
snapshot.setOutputVoltage(readMeaningfulDouble(workInfo, "outputVoltage"));
|
||||
snapshot.setOutputFrequency(readMeaningfulDouble(workInfo, "outputFrequency"));
|
||||
snapshot.setOutputLoadPercent(readMeaningfulInteger(workInfo, "outputLoadPercent"));
|
||||
snapshot.setBatteryVoltage(readMeaningfulDouble(workInfo, "batteryVoltage"));
|
||||
snapshot.setBatteryCapacity(readMeaningfulInteger(workInfo, "batteryCapacity"));
|
||||
snapshot.setBatteryRemainTime(readMeaningfulInteger(workInfo, "batteryRemainTime"));
|
||||
snapshot.setTemperature(readMeaningfulDouble(workInfo, "temperatureView"));
|
||||
snapshot.setBypassActive(readAsBoolean(workInfo, "bypassActive"));
|
||||
snapshot.setShutdownActive(readAsBoolean(workInfo, "shutdownActive"));
|
||||
snapshot.setOutputOn(readAsBoolean(workInfo, "outputON"));
|
||||
snapshot.setCharging(readAsBoolean(workInfo, "chargeON"));
|
||||
snapshot.setFaultType(readMeaningfulText(workInfo, "faultType"));
|
||||
snapshot.setFaultKind(readMeaningfulText(workInfo, "faultKind"));
|
||||
snapshot.setWarnings(readWarnings(workInfo));
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
private Long readUpsTime(JsonNode workInfo) {
|
||||
JsonNode currentTime = getAsObject(workInfo, "currentTime");
|
||||
if (currentTime == null || !currentTime.has("time") || currentTime.get("time").isNull()) {
|
||||
return null;
|
||||
}
|
||||
return currentTime.get("time").asLong();
|
||||
}
|
||||
|
||||
private java.util.List<String> readWarnings(JsonNode workInfo) {
|
||||
java.util.List<String> warnings = new java.util.ArrayList<>();
|
||||
if (workInfo == null || !workInfo.has("warnings") || !workInfo.get("warnings").isArray()) {
|
||||
return warnings;
|
||||
}
|
||||
ArrayNode array = (ArrayNode) workInfo.get("warnings");
|
||||
for (JsonNode item : array) {
|
||||
if (!item.isValueNode()) {
|
||||
continue;
|
||||
}
|
||||
String warning = normalizeText(item.asText());
|
||||
if (warning != null) {
|
||||
warnings.add(warning);
|
||||
}
|
||||
}
|
||||
return warnings;
|
||||
}
|
||||
|
||||
private JsonNode getAsObject(JsonNode source, String key) {
|
||||
if (source == null || !source.has(key) || source.get(key).isNull() || !source.get(key).isObject()) {
|
||||
return null;
|
||||
}
|
||||
return source.get(key);
|
||||
}
|
||||
|
||||
private String readMeaningfulText(JsonNode source, String key) {
|
||||
if (source == null || !source.has(key) || source.get(key).isNull()) {
|
||||
return null;
|
||||
}
|
||||
return normalizeText(source.get(key).asText());
|
||||
}
|
||||
|
||||
private Integer readMeaningfulInteger(JsonNode source, String key) {
|
||||
if (source == null || !source.has(key) || source.get(key).isNull()) {
|
||||
return null;
|
||||
}
|
||||
JsonNode element = source.get(key);
|
||||
if (element.isNumber()) {
|
||||
return element.asInt();
|
||||
}
|
||||
String text = normalizeText(element.asText());
|
||||
if (text == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Integer.parseInt(text);
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Double readMeaningfulDouble(JsonNode source, String key) {
|
||||
if (source == null || !source.has(key) || source.get(key).isNull()) {
|
||||
return null;
|
||||
}
|
||||
JsonNode element = source.get(key);
|
||||
if (element.isNumber()) {
|
||||
return element.asDouble();
|
||||
}
|
||||
String text = normalizeText(element.asText());
|
||||
if (text == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Double.parseDouble(text);
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean readAsBoolean(JsonNode source, String key) {
|
||||
return source != null && source.has(key) && !source.get(key).isNull() && source.get(key).asBoolean();
|
||||
}
|
||||
|
||||
private String normalizeText(String text) {
|
||||
if (text == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String normalized = text.trim();
|
||||
if (normalized.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if ("----".equals(normalized)) {
|
||||
return null;
|
||||
}
|
||||
if (normalized.startsWith("----:")) {
|
||||
return null;
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
package com.imyeyu.api.modules.system.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 系统状态数据视图
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
public class SystemStatusDataView {
|
||||
|
||||
/**
|
||||
* 当前快照
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@Data
|
||||
public static class Snapshot {
|
||||
|
||||
/** 操作系统 */
|
||||
private OS os;
|
||||
|
||||
/** CPU */
|
||||
private CPU cpu;
|
||||
|
||||
/** 系统内存 */
|
||||
private Memory memory;
|
||||
|
||||
/** JVM */
|
||||
private JVM jvm;
|
||||
|
||||
/** 网络 */
|
||||
private Network network;
|
||||
|
||||
/** 硬件 */
|
||||
private Hardware hardware;
|
||||
|
||||
/** 存储分区 */
|
||||
private List<StoragePartition> storagePartitions = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 历史点
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@Data
|
||||
public static class Point {
|
||||
|
||||
/** 采样时间 */
|
||||
private long at;
|
||||
|
||||
/** CPU 总占用 */
|
||||
private Double cpuUsagePercent;
|
||||
|
||||
/** CPU 系统占用 */
|
||||
private Double cpuSystemPercent;
|
||||
|
||||
/** 系统已用内存 */
|
||||
private Long memoryUsedBytes;
|
||||
|
||||
/** 已用交换分区 */
|
||||
private Long swapUsedBytes;
|
||||
|
||||
/** JVM 已用堆内存 */
|
||||
private Long heapUsedBytes;
|
||||
|
||||
/** JVM 已提交堆内存 */
|
||||
private Long heapCommittedBytes;
|
||||
|
||||
/** GC 周期耗时 */
|
||||
private Long gcCycleTimeMs;
|
||||
|
||||
/** GC 暂停耗时 */
|
||||
private Long gcPauseTimeMs;
|
||||
|
||||
/** 接收速率 */
|
||||
private Long rxBytesPerSecond;
|
||||
|
||||
/** 发送速率 */
|
||||
private Long txBytesPerSecond;
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作系统快照
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@Data
|
||||
public static class OS {
|
||||
|
||||
/** 系统名称 */
|
||||
private String name;
|
||||
|
||||
/** 启动时间 */
|
||||
private long bootAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* CPU 快照
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@Data
|
||||
public static class CPU {
|
||||
|
||||
/** 型号 */
|
||||
private String model;
|
||||
|
||||
/** 物理核心数 */
|
||||
private int physicalCores;
|
||||
|
||||
/** 逻辑核心数 */
|
||||
private int logicalCores;
|
||||
|
||||
/** 总占用 */
|
||||
private Double usageTotal;
|
||||
|
||||
/** 系统占用 */
|
||||
private Double usageSystem;
|
||||
|
||||
/** 温度 */
|
||||
private double temperatureCelsius;
|
||||
}
|
||||
|
||||
/**
|
||||
* 内存快照
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@Data
|
||||
public static class Memory {
|
||||
|
||||
/** 总内存 */
|
||||
private long totalBytes;
|
||||
|
||||
/** 已用内存 */
|
||||
private Long usedBytes;
|
||||
|
||||
/** 交换分区总量 */
|
||||
private long swapTotalBytes;
|
||||
|
||||
/** 已用交换分区 */
|
||||
private Long swapUsedBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* JVM 快照
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@Data
|
||||
public static class JVM {
|
||||
|
||||
/** 名称 */
|
||||
private String name;
|
||||
|
||||
/** 版本 */
|
||||
private String version;
|
||||
|
||||
/** 启动时间 */
|
||||
private long bootAt;
|
||||
|
||||
/** 初始堆大小 */
|
||||
private long heapInitBytes;
|
||||
|
||||
/** 最大堆大小 */
|
||||
private long heapMaxBytes;
|
||||
|
||||
/** 已用堆大小 */
|
||||
private Long heapUsedBytes;
|
||||
|
||||
/** 已提交堆大小 */
|
||||
private Long heapCommittedBytes;
|
||||
|
||||
/** GC */
|
||||
private GC gc;
|
||||
}
|
||||
|
||||
/**
|
||||
* GC 快照
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@Data
|
||||
public static class GC {
|
||||
|
||||
/** 收集器名称 */
|
||||
private String collector;
|
||||
|
||||
/** 周期次数 */
|
||||
private long cycleCount;
|
||||
|
||||
/** 暂停次数 */
|
||||
private long pauseCount;
|
||||
|
||||
/** 上次暂停时间 */
|
||||
private long lastPauseAt;
|
||||
|
||||
/** 上次回收大小 */
|
||||
private long lastRecoveredBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络快照
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@Data
|
||||
public static class Network {
|
||||
|
||||
/** 网卡名称 */
|
||||
private String interfaceName;
|
||||
|
||||
/** MAC 地址 */
|
||||
private String mac;
|
||||
|
||||
/** 接收速率 */
|
||||
private long rxBytesPerSecond;
|
||||
|
||||
/** 发送速率 */
|
||||
private long txBytesPerSecond;
|
||||
|
||||
/** 累计接收 */
|
||||
private long rxTotalBytes;
|
||||
|
||||
/** 累计发送 */
|
||||
private long txTotalBytes;
|
||||
|
||||
/** 接收包总数 */
|
||||
private long rxPacketsTotal;
|
||||
|
||||
/** 发送包总数 */
|
||||
private long txPacketsTotal;
|
||||
|
||||
/** 输入错误包总数 */
|
||||
private long inErrors;
|
||||
|
||||
/** 输出错误包总数 */
|
||||
private long outErrors;
|
||||
|
||||
/** 输入丢弃包总数 */
|
||||
private long inDrops;
|
||||
|
||||
/** 碰撞总数 */
|
||||
private long collisions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 硬件快照
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@Data
|
||||
public static class Hardware {
|
||||
|
||||
/** 风扇转速 */
|
||||
private List<Integer> fanSpeeds = new ArrayList<>();
|
||||
|
||||
/** 主板信息 */
|
||||
private Baseboard baseboard;
|
||||
|
||||
/** BIOS 信息 */
|
||||
private Firmware firmware;
|
||||
}
|
||||
|
||||
/**
|
||||
* 主板快照
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@Data
|
||||
public static class Baseboard {
|
||||
|
||||
/** 厂商 */
|
||||
private String manufacturer;
|
||||
|
||||
/** 型号 */
|
||||
private String model;
|
||||
|
||||
/** 版本 */
|
||||
private String version;
|
||||
|
||||
/** 序列号 */
|
||||
private String serialNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* BIOS 快照
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@Data
|
||||
public static class Firmware {
|
||||
|
||||
/** 厂商 */
|
||||
private String manufacturer;
|
||||
|
||||
/** 名称 */
|
||||
private String name;
|
||||
|
||||
/** 描述 */
|
||||
private String description;
|
||||
|
||||
/** 版本 */
|
||||
private String version;
|
||||
|
||||
/** 发布时间 */
|
||||
private String releaseDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储分区快照
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@Data
|
||||
public static class StoragePartition {
|
||||
|
||||
/** 物理磁盘名称 */
|
||||
private String diskName;
|
||||
|
||||
/** 物理磁盘型号 */
|
||||
private String diskModel;
|
||||
|
||||
/** 物理磁盘序列号 */
|
||||
private String diskSerial;
|
||||
|
||||
/** 分区标识 */
|
||||
private String partitionId;
|
||||
|
||||
/** 分区名称 */
|
||||
private String partitionName;
|
||||
|
||||
/** 分区类型 */
|
||||
private String partitionType;
|
||||
|
||||
/** 分区 UUID */
|
||||
private String uuid;
|
||||
|
||||
/** 挂载点 */
|
||||
private String mountPoint;
|
||||
|
||||
/** 分区总空间 */
|
||||
private long total;
|
||||
|
||||
/** 已用空间 */
|
||||
private Long used;
|
||||
|
||||
/** 使用率 */
|
||||
private Double usagePercent;
|
||||
|
||||
/** 传输耗时 */
|
||||
private long transferTimeMs;
|
||||
|
||||
/** 健康状态 */
|
||||
private String healthStatus;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.imyeyu.api.modules.system.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 系统状态历史视图
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@Data
|
||||
public class SystemStatusHistoryView {
|
||||
|
||||
/** 服务端当前时间 */
|
||||
private long serverTime;
|
||||
|
||||
/** 采样周期 */
|
||||
private int sampleRateMs;
|
||||
|
||||
/** 起始时间 */
|
||||
private long from;
|
||||
|
||||
/** 结束时间 */
|
||||
private long to;
|
||||
|
||||
/** 历史点 */
|
||||
private List<SystemStatusDataView.Point> points = new ArrayList<>();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.imyeyu.api.modules.system.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 系统状态快照视图
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@Data
|
||||
public class SystemStatusSnapshotView {
|
||||
|
||||
/** 服务端当前时间 */
|
||||
private long serverTime;
|
||||
|
||||
/** 采样周期 */
|
||||
private int sampleRateMs;
|
||||
|
||||
/** 当前快照 */
|
||||
private SystemStatusDataView.Snapshot snapshot = new SystemStatusDataView.Snapshot();
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.imyeyu.api.modules.system.vo.docker;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Docker 容器历史点视图
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@Data
|
||||
public class DockerContainerHistoryPointView {
|
||||
|
||||
/** 时间 */
|
||||
private long at;
|
||||
|
||||
/** CPU 百分比 */
|
||||
private Double cpuPercent;
|
||||
|
||||
/** 内存使用量 */
|
||||
private Long memoryUsageBytes;
|
||||
|
||||
/** 内存百分比 */
|
||||
private Double memoryPercent;
|
||||
|
||||
/** 网络接收字节 */
|
||||
private Long networkRxBytes;
|
||||
|
||||
/** 网络发送字节 */
|
||||
private Long networkTxBytes;
|
||||
|
||||
/** 块设备读取字节 */
|
||||
private Long blockReadBytes;
|
||||
|
||||
/** 块设备写入字节 */
|
||||
private Long blockWriteBytes;
|
||||
|
||||
/** 进程数 */
|
||||
private Integer pids;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.imyeyu.api.modules.system.vo.docker;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Docker 容器历史视图
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@Data
|
||||
public class DockerContainerHistoryView {
|
||||
|
||||
/** 容器 ID */
|
||||
private String id;
|
||||
|
||||
/** 容器名称 */
|
||||
private String name;
|
||||
|
||||
/** 服务端当前时间 */
|
||||
private long serverTime;
|
||||
|
||||
/** 采样周期 */
|
||||
private long sampleRateMs;
|
||||
|
||||
/** 起始时间 */
|
||||
private long from;
|
||||
|
||||
/** 结束时间 */
|
||||
private long to;
|
||||
|
||||
/** 历史点 */
|
||||
private List<DockerContainerHistoryPointView> points = new ArrayList<>();
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.imyeyu.api.modules.system.vo.docker;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Docker 容器状态视图
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@Data
|
||||
public class DockerContainerStatusView {
|
||||
|
||||
/** 容器 ID */
|
||||
private String id;
|
||||
|
||||
/** 容器名称 */
|
||||
private String name;
|
||||
|
||||
/** 镜像 */
|
||||
private String image;
|
||||
|
||||
/** 镜像 ID */
|
||||
private String imageId;
|
||||
|
||||
/** 创建时间 */
|
||||
private long createdAt;
|
||||
|
||||
/** 运行状态 */
|
||||
private String state;
|
||||
|
||||
/** 状态描述 */
|
||||
private String status;
|
||||
|
||||
/** 健康状态 */
|
||||
private String healthStatus;
|
||||
|
||||
/** 启动时间 */
|
||||
private String startedAt;
|
||||
|
||||
/** 结束时间 */
|
||||
private String finishedAt;
|
||||
|
||||
/** 退出码 */
|
||||
private Integer exitCode;
|
||||
|
||||
/** 重启次数 */
|
||||
private int restartCount;
|
||||
|
||||
/** OOM 标记 */
|
||||
private boolean oomKilled;
|
||||
|
||||
/** CPU 百分比 */
|
||||
private Double cpuPercent;
|
||||
|
||||
/** 内存使用量 */
|
||||
private Long memoryUsageBytes;
|
||||
|
||||
/** 内存限制 */
|
||||
private Long memoryLimitBytes;
|
||||
|
||||
/** 内存百分比 */
|
||||
private Double memoryPercent;
|
||||
|
||||
/** 网络接收字节 */
|
||||
private Long networkRxBytes;
|
||||
|
||||
/** 网络发送字节 */
|
||||
private Long networkTxBytes;
|
||||
|
||||
/** 块设备读取字节 */
|
||||
private Long blockReadBytes;
|
||||
|
||||
/** 块设备写入字节 */
|
||||
private Long blockWriteBytes;
|
||||
|
||||
/** 进程数 */
|
||||
private Integer pids;
|
||||
|
||||
/** 更新时间 */
|
||||
private long updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.imyeyu.api.modules.system.vo.docker;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Docker 容器摘要视图
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-06
|
||||
*/
|
||||
@Data
|
||||
public class DockerContainerSummaryView {
|
||||
|
||||
/** 容器 ID */
|
||||
private String id;
|
||||
|
||||
/** 容器名称 */
|
||||
private String name;
|
||||
|
||||
/** 镜像 */
|
||||
private String image;
|
||||
|
||||
/** 运行状态 */
|
||||
private String state;
|
||||
|
||||
/** 状态描述 */
|
||||
private String status;
|
||||
|
||||
/** 健康状态 */
|
||||
private String healthStatus;
|
||||
|
||||
/** CPU 百分比 */
|
||||
private Double cpuPercent;
|
||||
|
||||
/** 内存使用量 */
|
||||
private Long memoryUsageBytes;
|
||||
|
||||
/** 内存限制 */
|
||||
private Long memoryLimitBytes;
|
||||
|
||||
/** 内存百分比 */
|
||||
private Double memoryPercent;
|
||||
|
||||
/** 网络接收字节 */
|
||||
private Long networkRxBytes;
|
||||
|
||||
/** 网络发送字节 */
|
||||
private Long networkTxBytes;
|
||||
|
||||
/** 更新时间 */
|
||||
private long updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.imyeyu.api.modules.system.vo.ups;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* UPS 历史点视图
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-07
|
||||
*/
|
||||
@Data
|
||||
public class UpsHistoryPointView {
|
||||
|
||||
/** 采样时间 */
|
||||
private long at;
|
||||
|
||||
/** UPS 数据时间 */
|
||||
private Long upsTime;
|
||||
|
||||
/** 工作模式 */
|
||||
private String workMode;
|
||||
|
||||
/** 输入电压 */
|
||||
private Double inputVoltage;
|
||||
|
||||
/** 输入频率 */
|
||||
private Double inputFrequency;
|
||||
|
||||
/** 输出电压 */
|
||||
private Double outputVoltage;
|
||||
|
||||
/** 输出频率 */
|
||||
private Double outputFrequency;
|
||||
|
||||
/** 输出负载百分比 */
|
||||
private Integer outputLoadPercent;
|
||||
|
||||
/** 电池电压 */
|
||||
private Double batteryVoltage;
|
||||
|
||||
/** 电池容量百分比 */
|
||||
private Integer batteryCapacity;
|
||||
|
||||
/** 电池剩余时间 */
|
||||
private Integer batteryRemainTime;
|
||||
|
||||
/** 温度 */
|
||||
private Double temperature;
|
||||
|
||||
/** 是否旁路运行 */
|
||||
private boolean bypassActive;
|
||||
|
||||
/** 是否关机中 */
|
||||
private boolean shutdownActive;
|
||||
|
||||
/** 是否输出开启 */
|
||||
private boolean outputOn;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.imyeyu.api.modules.system.vo.ups;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* UPS 历史视图
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-07
|
||||
*/
|
||||
@Data
|
||||
public class UpsHistoryView {
|
||||
|
||||
/** 服务端当前时间 */
|
||||
private long serverTime;
|
||||
|
||||
/** 采样周期 */
|
||||
private long sampleRateMs;
|
||||
|
||||
/** 查询开始时间 */
|
||||
private long from;
|
||||
|
||||
/** 查询结束时间 */
|
||||
private long to;
|
||||
|
||||
/** 历史点 */
|
||||
private List<UpsHistoryPointView> points = new ArrayList<>();
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.imyeyu.api.modules.system.vo.ups;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* UPS 状态视图
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-04-07
|
||||
*/
|
||||
@Data
|
||||
public class UpsStatusView {
|
||||
|
||||
/** 服务端当前时间 */
|
||||
private long serverTime;
|
||||
|
||||
/** UPS 数据时间 */
|
||||
private Long upsTime;
|
||||
|
||||
/** 上游主机地址 */
|
||||
private String hostName;
|
||||
|
||||
/** 厂商名称 */
|
||||
private String customer;
|
||||
|
||||
/** 上游版本 */
|
||||
private String version;
|
||||
|
||||
/** 设备标识 */
|
||||
private String deviceId;
|
||||
|
||||
/** UPS 类型 */
|
||||
private String upsType;
|
||||
|
||||
/** UPS 形态 */
|
||||
private String morphological;
|
||||
|
||||
/** 输入输出相位 */
|
||||
private String ioPhase;
|
||||
|
||||
/** 工作模式 */
|
||||
private String workMode;
|
||||
|
||||
/** 输入电压 */
|
||||
private Double inputVoltage;
|
||||
|
||||
/** 输入频率 */
|
||||
private Double inputFrequency;
|
||||
|
||||
/** 输出电压 */
|
||||
private Double outputVoltage;
|
||||
|
||||
/** 输出频率 */
|
||||
private Double outputFrequency;
|
||||
|
||||
/** 输出负载百分比 */
|
||||
private Integer outputLoadPercent;
|
||||
|
||||
/** 电池电压 */
|
||||
private Double batteryVoltage;
|
||||
|
||||
/** 电池容量百分比 */
|
||||
private Integer batteryCapacity;
|
||||
|
||||
/** 电池剩余时间 */
|
||||
private Integer batteryRemainTime;
|
||||
|
||||
/** 温度 */
|
||||
private Double temperature;
|
||||
|
||||
/** 是否旁路运行 */
|
||||
private boolean bypassActive;
|
||||
|
||||
/** 是否关机中 */
|
||||
private boolean shutdownActive;
|
||||
|
||||
/** 是否输出开启 */
|
||||
private boolean outputOn;
|
||||
|
||||
/** 是否充电中 */
|
||||
private boolean charging;
|
||||
|
||||
/** 故障类型 */
|
||||
private String faultType;
|
||||
|
||||
/** 故障明细 */
|
||||
private String faultKind;
|
||||
|
||||
/** 告警列表 */
|
||||
private List<String> warnings = new ArrayList<>();
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
package com.imyeyu.api.util;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import com.imyeyu.java.TimiJava;
|
||||
import com.imyeyu.java.ref.Ref;
|
||||
import com.imyeyu.api.TimiServerAPI;
|
||||
import com.imyeyu.api.bean.MultilingualHandler;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author 夜雨
|
||||
* @version 2023-10-26 10:16
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class GsonSerializerAdapter implements JsonSerializer<Object> {
|
||||
|
||||
private final Gson gson;
|
||||
private final RedisMultilingual redisMultilingual;
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(Object value, Type typeOfSrc, JsonSerializationContext context) {
|
||||
if (value instanceof MultilingualHandler _value) {
|
||||
fillMultilingual(_value);
|
||||
}
|
||||
return gson.toJsonTree(value);
|
||||
}
|
||||
|
||||
private <K extends MultilingualHandler> void fillMultilingual(K value) {
|
||||
try {
|
||||
List<Field> fields = Ref.listFields(value.getClass());
|
||||
for (int i = 0; i < fields.size(); i++) {
|
||||
Field field = fields.get(i);
|
||||
MultilingualHandler.MultilingualField multiField = field.getAnnotation(MultilingualHandler.MultilingualField.class);
|
||||
if (multiField != null) {
|
||||
String multiLangId = Ref.getFieldValue(value, field, String.class);
|
||||
if (TimiJava.isNotEmpty(multiLangId)) {
|
||||
Long langId = Long.parseLong(multiLangId);
|
||||
if (redisMultilingual.map(TimiServerAPI.getUserLanguage()) instanceof RedisLanguage rl) {
|
||||
// TODO 支持插值参数
|
||||
Ref.setFieldValue(value, field, rl.text(langId));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
package com.imyeyu.api.util;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.imyeyu.api.TimiServerAPI;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.imyeyu.api.modules.common.bean.SettingKey;
|
||||
import com.imyeyu.api.modules.common.entity.Multilingual;
|
||||
import com.imyeyu.api.modules.common.entity.Setting;
|
||||
@@ -11,6 +10,7 @@ import com.imyeyu.api.modules.common.service.SettingService;
|
||||
import com.imyeyu.api.modules.system.bean.ServerFile;
|
||||
import com.imyeyu.java.ref.Ref;
|
||||
import com.imyeyu.lang.mapper.AbstractLanguageMapper;
|
||||
import com.imyeyu.spring.TimiSpring;
|
||||
import com.imyeyu.spring.util.GlobalReturnHandler;
|
||||
import com.imyeyu.spring.util.Redis;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -80,30 +80,29 @@ public class InitApplication implements ApplicationRunner {
|
||||
}
|
||||
|
||||
private void initMultilingual() {
|
||||
// redisLanguage.flushAll();
|
||||
globalReturnHandler.setMultilingualHeader(mapping -> {
|
||||
AbstractLanguageMapper map = redisMultilingual.map(TimiServerAPI.getUserLanguage());
|
||||
AbstractLanguageMapper map = redisMultilingual.map(TimiSpring.getLanguage());
|
||||
return map.textArgs(mapping.getMsgKey(), mapping.getMsgArgs());
|
||||
});
|
||||
}
|
||||
|
||||
private void initFileType() {
|
||||
JsonObject items = settingService.getAsJsonObject(SettingKey.SYSTEM_FILE_TYPE);
|
||||
ObjectNode items = settingService.getAsJsonObject(SettingKey.SYSTEM_FILE_TYPE);
|
||||
|
||||
String[] extensions;
|
||||
JsonArray extensionsArray;
|
||||
JsonObject itemObject;
|
||||
ArrayNode extensionsArray;
|
||||
JsonNode itemObject;
|
||||
List<String> extensionsList;
|
||||
for (Map.Entry<String, JsonElement> item : items.entrySet()) {
|
||||
for (Map.Entry<String, JsonNode> item : (Iterable<Map.Entry<String, JsonNode>>) items::fields) {
|
||||
ServerFile.FileType fileType = Ref.toType(ServerFile.FileType.class, item.getKey());
|
||||
itemObject = item.getValue().getAsJsonObject();
|
||||
itemObject = item.getValue();
|
||||
extensionsList = new ArrayList<>();
|
||||
extensionsArray = itemObject.get("extensions").getAsJsonArray();
|
||||
for (int i = 0; i < extensionsArray.size(); i++) {
|
||||
if (extensionsArray.get(i).isJsonObject()) {
|
||||
extensionsList.add(extensionsArray.get(i).getAsJsonObject().get("value").getAsString());
|
||||
extensionsArray = (ArrayNode) itemObject.get("extensions");
|
||||
for (JsonNode extensionNode : extensionsArray) {
|
||||
if (extensionNode.isObject()) {
|
||||
extensionsList.add(extensionNode.path("value").asText());
|
||||
} else {
|
||||
extensionsList.add(extensionsArray.get(i).getAsString());
|
||||
extensionsList.add(extensionNode.asText());
|
||||
}
|
||||
}
|
||||
extensions = new String[extensionsList.size()];
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
server:
|
||||
port: 8091
|
||||
shutdown: graceful
|
||||
|
||||
# 开发环境语言,激活开发配置时,多语言系统始终使用此语言环境
|
||||
@@ -12,26 +11,13 @@ logging:
|
||||
|
||||
# Spring
|
||||
spring:
|
||||
profiles:
|
||||
active: prod
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 4GB
|
||||
max-request-size: 4GB
|
||||
lifecycle:
|
||||
timeout-per-shutdown-phase: 32s
|
||||
async:
|
||||
thread-pool:
|
||||
core-pool-size: 16
|
||||
max-pool-size: 32
|
||||
queueCapacity: 16
|
||||
keep-alive-seconds: 60
|
||||
thread-name-prefix: thread-pool-task-executor-
|
||||
await-termination-seconds: 60
|
||||
mail: # 邮件配置
|
||||
host: smtp.qq.com
|
||||
username: imyeyu@qq.com
|
||||
password: saodifhaposjfoas
|
||||
mail:
|
||||
properties:
|
||||
mail:
|
||||
smtp:
|
||||
@@ -40,63 +26,3 @@ spring:
|
||||
enable: true
|
||||
required: true
|
||||
default-encoding: UTF-8
|
||||
mvc: # JSON 序列化
|
||||
converters:
|
||||
preferred-json-mapper: gson # 返回 JSON 序列化使用 GSON
|
||||
redis: # Redis 数据库
|
||||
host: dev.vm.imyeyu.test
|
||||
port: 6379
|
||||
password:
|
||||
timeout: 8000
|
||||
database:
|
||||
locker: 0 # ID: Integer 全局锁,ID 规范:应用:模块:业务:方法
|
||||
multilingual: 1 # ID: Multilingual 多语言缓存
|
||||
multilingual-map: 2 # Key: ID 多语言键缓存
|
||||
article-ranking: 3 # AID: ArticleRanking(JSON) 热门文章排位
|
||||
article-read: 4 # IP: [AID..] IP 阅读文章记录
|
||||
user-token: 5 # TOKEN: LONG 用户登录令牌
|
||||
user-exp-flag: 6 # UID: NULL 用户登录经验标记,暂时没有值,数据死亡时间为次日零时
|
||||
user-email-verify: 7 # AES_KEY: UID 邮箱验证密钥缓存
|
||||
user-reset-pw-verify: 8 # AES_KEY: UID 重置密码密钥缓存
|
||||
qps-limit: 9 # APP.IP.method: COUNT_IN_LIFE_CYCLE 接口访问频率控制(多个系统公用,需要 App 标记)
|
||||
setting: 10 # Setting: SettingValue 系统配置
|
||||
fmc-player-token: 11 # TOKEN: USER_ID|PLAYER_ID MC 登录缓存
|
||||
clipboard: 12 # ID: CONTENT 共享剪切板
|
||||
lettuce: # 连接池配置
|
||||
pool:
|
||||
max-wait: -1
|
||||
max-idle: 8
|
||||
min-idle: 0
|
||||
max-active: 8
|
||||
datasource: # 数据库配置
|
||||
timiserver:
|
||||
jdbc-url: jdbc:mysql://dev.vm.imyeyu.test:3306/timi_server?serverTimezone=UTC&characterEncoding=UTF-8
|
||||
username: root
|
||||
password: 123123
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
forevermc:
|
||||
jdbc-url: jdbc:mysql://dev.vm.imyeyu.test:3306/authme?serverTimezone=UTC&characterEncoding=UTF-8
|
||||
username: root
|
||||
password: 123123
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
gitea:
|
||||
jdbc-url: jdbc:mysql://dev.vm.imyeyu.test:3306/authme?serverTimezone=UTC&characterEncoding=UTF-8
|
||||
username: root
|
||||
password: 123123
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
data:
|
||||
mongodb:
|
||||
host: dev.vm.imyeyu.test
|
||||
port: 27017
|
||||
database: db
|
||||
username: root
|
||||
password: qweqwe123
|
||||
|
||||
# CORS 跨域
|
||||
cors:
|
||||
# 允许访问的客户端域名,如:http://web.xxx.com,* 表示不做任何限制(不做任何限制时 allow-credentials 无效)
|
||||
allow-origin:
|
||||
- "http://localhost:8080"
|
||||
allow-methods: "*" # 允许请求的方法名,多个方法名逗号分割,如:GET, POST, PUT, DELETE, OPTIONS
|
||||
allow-credentials: true # 是否允许请求带有验证信息,若要获取客户端域下的 Cookie 或 Session 时,设置为 true
|
||||
allow-headers: "*" # 允许服务端访问的客户端请求头,多个请求头逗号分割,如:Content-BizType
|
||||
|
||||
58
src/main/resources/application_export.yml
Normal file
58
src/main/resources/application_export.yml
Normal file
@@ -0,0 +1,58 @@
|
||||
server:
|
||||
port: 8091
|
||||
|
||||
# Spring
|
||||
spring:
|
||||
profiles:
|
||||
active: prod
|
||||
mail: # 邮件配置
|
||||
host: smtp.qq.com
|
||||
username: imyeyu@qq.com
|
||||
password: saodifhaposjfoas
|
||||
redis: # Redis 数据库
|
||||
host: dev.vm.imyeyu.test
|
||||
port: 6379
|
||||
password:
|
||||
database:
|
||||
locker: 0 # ID: Integer 全局锁,ID 规范:应用:模块:业务:方法
|
||||
multilingual: 1 # ID: Multilingual 多语言缓存
|
||||
multilingual-map: 2 # Key: ID 多语言键缓存
|
||||
article-ranking: 3 # AID: ArticleRanking(JSON) 热门文章排位
|
||||
article-read: 4 # IP: [AID..] IP 阅读文章记录
|
||||
user-token: 5 # TOKEN: LONG 用户登录令牌
|
||||
user-exp-flag: 6 # UID: NULL 用户登录经验标记,暂时没有值,数据死亡时间为次日零时
|
||||
user-email-verify: 7 # AES_KEY: UID 邮箱验证密钥缓存
|
||||
user-reset-pw-verify: 8 # AES_KEY: UID 重置密码密钥缓存
|
||||
qps-limit: 9 # APP.IP.method: COUNT_IN_LIFE_CYCLE 接口访问频率控制(多个系统公用,需要 App 标记)
|
||||
setting: 10 # Setting: SettingValue 系统配置
|
||||
fmc-player-token: 11 # TOKEN: USER_ID|PLAYER_ID MC 登录缓存
|
||||
clipboard: 12 # ID: CONTENT 共享剪切板
|
||||
datasource: # 数据库配置
|
||||
timiserver:
|
||||
jdbc-url: jdbc:mysql://dev.vm.imyeyu.test:3306/timi_server?serverTimezone=UTC&characterEncoding=UTF-8
|
||||
username: root
|
||||
password: 123123
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
forevermc:
|
||||
jdbc-url: jdbc:mysql://dev.vm.imyeyu.test:3306/authme?serverTimezone=UTC&characterEncoding=UTF-8
|
||||
username: root
|
||||
password: 123123
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
gitea:
|
||||
jdbc-url: jdbc:mysql://dev.vm.imyeyu.test:3306/authme?serverTimezone=UTC&characterEncoding=UTF-8
|
||||
username: root
|
||||
password: 123123
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
data:
|
||||
mongodb:
|
||||
host: dev.vm.imyeyu.test
|
||||
port: 27017
|
||||
database: db
|
||||
username: root
|
||||
password: qweqwe123
|
||||
|
||||
# CORS 跨域
|
||||
cors:
|
||||
# 允许访问的客户端域名,如:http://web.xxx.com,* 表示不做任何限制(不做任何限制时 allow-credentials 无效)
|
||||
allow-origin:
|
||||
- "http://localhost:8080"
|
||||
@@ -1,6 +1,5 @@
|
||||
package test;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.imyeyu.api.TimiServerAPI;
|
||||
import com.imyeyu.api.modules.blog.entity.Article;
|
||||
import com.imyeyu.api.modules.blog.mapper.ArticleMapper;
|
||||
@@ -118,6 +117,5 @@ public class SpringTest {
|
||||
for (int i = 0; i < icon.size(); i++) {
|
||||
map.put(icon.get(i).getName(), icon.get(i).getSvg());
|
||||
}
|
||||
System.out.println(new Gson().toJson(map));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user