Compare commits
17 Commits
cd7bc31e6b
...
v1.0.2
| Author | SHA1 | Date | |
|---|---|---|---|
| 523edffc4e | |||
|
|
3cc371c53e | ||
| c7ddb1a8b0 | |||
|
|
b46e9079d5 | ||
|
|
dc20070bf8 | ||
| 45c9fc814a | |||
|
|
78163441dd | ||
| 407dc13ac4 | |||
|
|
9762be1244 | ||
| 0c06bf16c2 | |||
|
|
971cad7365 | ||
| 1db39e77d3 | |||
|
|
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>
|
<parent>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-parent</artifactId>
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
<version>3.4.0</version>
|
<version>3.5.11</version>
|
||||||
<relativePath/>
|
<relativePath/>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<groupId>com.imyeyu.timiserverapi</groupId>
|
<groupId>com.imyeyu.timiserverapi</groupId>
|
||||||
<artifactId>TimiServerAPI</artifactId>
|
<artifactId>TimiServerAPI</artifactId>
|
||||||
<version>1.0.0</version>
|
<version>1.0.1</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<name>TimiServerAPI</name>
|
<name>TimiServerAPI</name>
|
||||||
<description>imyeyu.com API</description>
|
<description>imyeyu.com API</description>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<springboot.version>3.4.0</springboot.version>
|
<springboot.version>3.5.11</springboot.version>
|
||||||
<maven.compiler.source>21</maven.compiler.source>
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
<maven.compiler.multilingualField>21</maven.compiler.multilingualField>
|
<maven.compiler.multilingualField>21</maven.compiler.multilingualField>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<repositories>
|
|
||||||
<repository>
|
|
||||||
<id>apache-maven</id>
|
|
||||||
<url>https://repo.maven.apache.org/maven2/</url>
|
|
||||||
</repository>
|
|
||||||
</repositories>
|
|
||||||
|
|
||||||
<profiles>
|
<profiles>
|
||||||
<profile>
|
<profile>
|
||||||
<id>dev-windows</id>
|
<id>dev-windows</id>
|
||||||
@@ -112,22 +105,38 @@
|
|||||||
<configuration>
|
<configuration>
|
||||||
<excludeDevtools>true</excludeDevtools>
|
<excludeDevtools>true</excludeDevtools>
|
||||||
<mainClass>com.imyeyu.api.TimiServerAPI</mainClass>
|
<mainClass>com.imyeyu.api.TimiServerAPI</mainClass>
|
||||||
<finalName>${project.artifactId}</finalName>
|
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</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>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.imyeyu.spring</groupId>
|
<groupId>com.imyeyu.spring</groupId>
|
||||||
<artifactId>timi-spring</artifactId>
|
<artifactId>timi-spring</artifactId>
|
||||||
<version>0.0.2</version>
|
<version>0.0.10</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.imyeyu.network</groupId>
|
<groupId>com.imyeyu.network</groupId>
|
||||||
<artifactId>timi-network</artifactId>
|
<artifactId>timi-network</artifactId>
|
||||||
<version>0.0.2</version>
|
<version>0.0.8</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.imyeyu.lang</groupId>
|
<groupId>com.imyeyu.lang</groupId>
|
||||||
@@ -184,12 +193,12 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jgit</groupId>
|
<groupId>org.eclipse.jgit</groupId>
|
||||||
<artifactId>org.eclipse.jgit</artifactId>
|
<artifactId>org.eclipse.jgit</artifactId>
|
||||||
<version>6.7.0.202309050840-r</version>
|
<version>7.2.1.202505142326-r</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jgit</groupId>
|
<groupId>org.eclipse.jgit</groupId>
|
||||||
<artifactId>org.eclipse.jgit.archive</artifactId>
|
<artifactId>org.eclipse.jgit.archive</artifactId>
|
||||||
<version>6.7.0.202309050840-r</version>
|
<version>7.2.1.202505142326-r</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
@@ -220,7 +229,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.tika</groupId>
|
<groupId>org.apache.tika</groupId>
|
||||||
<artifactId>tika-core</artifactId>
|
<artifactId>tika-core</artifactId>
|
||||||
<version>2.9.2</version>
|
<version>3.2.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jcodec</groupId>
|
<groupId>org.jcodec</groupId>
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
package com.imyeyu.api;
|
package com.imyeyu.api;
|
||||||
|
|
||||||
import com.imyeyu.io.IO;
|
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 com.imyeyu.utils.OS;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -13,7 +9,6 @@ import org.springframework.boot.SpringApplication;
|
|||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.ApplicationContextAware;
|
import org.springframework.context.ApplicationContextAware;
|
||||||
import org.springframework.core.env.Environment;
|
|
||||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -31,8 +26,6 @@ import java.io.File;
|
|||||||
@EnableTransactionManagement
|
@EnableTransactionManagement
|
||||||
public class TimiServerAPI implements OS.FileSystem, ApplicationContextAware {
|
public class TimiServerAPI implements OS.FileSystem, ApplicationContextAware {
|
||||||
|
|
||||||
private static final String DEV_LANG_CONFIG = "dev.lang";
|
|
||||||
|
|
||||||
public static ApplicationContext applicationContext;
|
public static ApplicationContext applicationContext;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -40,32 +33,18 @@ public class TimiServerAPI implements OS.FileSystem, ApplicationContextAware {
|
|||||||
TimiServerAPI.applicationContext = applicationContext;
|
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) {
|
public static void main(String[] args) {
|
||||||
try {
|
try {
|
||||||
{
|
{
|
||||||
// 导出配置
|
File application = new File("config" + SEP + "application.yml");
|
||||||
String[] files = {"application.yml", "logback.xml"};
|
if (!application.exists()) {
|
||||||
for (int i = 0; i < files.length; i++) {
|
IO.resourceToDisk(TimiServerAPI.class, "application_export.yml", application.getAbsolutePath());
|
||||||
File file = new File("config" + SEP + files[i]);
|
}
|
||||||
if (!file.exists() || !file.isFile()) {
|
File logback = new File("config" + SEP + "logback.xml");
|
||||||
log.info("exporting default config at {}", file.getAbsolutePath());
|
if (!logback.exists()) {
|
||||||
IO.resourceToDisk(TimiServerAPI.class, files[i], file.getAbsolutePath());
|
IO.resourceToDisk(TimiServerAPI.class, "logback.xml", logback.getAbsolutePath());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 启动 SpringBoot
|
// 启动 SpringBoot
|
||||||
SpringApplication.run(TimiServerAPI.class, args);
|
SpringApplication.run(TimiServerAPI.class, args);
|
||||||
} catch (Exception e) {
|
} 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;
|
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.springframework.context.annotation.Configuration;
|
||||||
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
@@ -10,7 +15,17 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
@Configuration
|
@Configuration
|
||||||
public class BeanConfig {
|
public class BeanConfig {
|
||||||
|
|
||||||
public Gson gson() {
|
@Bean
|
||||||
return new Gson();
|
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 String[] allowOrigin;
|
||||||
|
|
||||||
/** 是否允许请求带有验证信息 */
|
|
||||||
private boolean allowCredentials;
|
|
||||||
|
|
||||||
/** 允许请求的方法 */
|
|
||||||
private String allowMethods;
|
|
||||||
|
|
||||||
/** 允许服务端访问的客户端请求头 */
|
|
||||||
private String allowHeaders;
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public FilterRegistrationBean<Filter> corsFilter() {
|
public FilterRegistrationBean<Filter> corsFilter() {
|
||||||
CorsConfiguration config = new CorsConfiguration();
|
CorsConfiguration config = new CorsConfiguration();
|
||||||
config.addAllowedHeader(allowHeaders);
|
config.addAllowedHeader("*");
|
||||||
config.addAllowedMethod(allowMethods);
|
config.addAllowedMethod("*");
|
||||||
config.setAllowCredentials(allowCredentials);
|
config.setAllowCredentials(true);
|
||||||
config.setAllowedOriginPatterns(Arrays.asList(allowOrigin));
|
config.setAllowedOriginPatterns(Arrays.asList(allowOrigin));
|
||||||
|
|
||||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
package com.imyeyu.api.config;
|
package com.imyeyu.api.config;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.imyeyu.api.modules.blog.entity.ArticleRanking;
|
import com.imyeyu.api.modules.blog.entity.ArticleRanking;
|
||||||
import com.imyeyu.api.modules.common.entity.Multilingual;
|
import com.imyeyu.api.modules.common.entity.Multilingual;
|
||||||
import com.imyeyu.spring.bean.RedisConfigParams;
|
import com.imyeyu.spring.bean.RedisConfigParams;
|
||||||
import com.imyeyu.spring.config.AbstractRedisConfig;
|
import com.imyeyu.spring.config.AbstractRedisConfig;
|
||||||
import com.imyeyu.spring.util.Redis;
|
import com.imyeyu.spring.util.Redis;
|
||||||
import com.imyeyu.spring.util.RedisSerializers;
|
import com.imyeyu.spring.util.RedisSerializers;
|
||||||
|
import com.imyeyu.utils.Time;
|
||||||
|
import io.lettuce.core.api.StatefulConnection;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
|
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
|
||||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
@@ -32,9 +36,12 @@ import java.time.Duration;
|
|||||||
@Configuration
|
@Configuration
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@EnableAutoConfiguration
|
@EnableAutoConfiguration
|
||||||
|
@RequiredArgsConstructor
|
||||||
@ConfigurationProperties(prefix = "spring.redis")
|
@ConfigurationProperties(prefix = "spring.redis")
|
||||||
public class RedisConfig extends AbstractRedisConfig {
|
public class RedisConfig extends AbstractRedisConfig {
|
||||||
|
|
||||||
|
private final ObjectMapper jackson;
|
||||||
|
|
||||||
// ---------- 连接配置 ----------
|
// ---------- 连接配置 ----------
|
||||||
|
|
||||||
/** 地址 */
|
/** 地址 */
|
||||||
@@ -46,50 +53,9 @@ public class RedisConfig extends AbstractRedisConfig {
|
|||||||
/** 密码 */
|
/** 密码 */
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
/** 超时(毫秒) */
|
|
||||||
private int timeout;
|
|
||||||
|
|
||||||
/** 连接池 */
|
|
||||||
private Lettuce lettuce;
|
|
||||||
|
|
||||||
/** 数据库 */
|
/** 数据库 */
|
||||||
private Database database;
|
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);
|
setHost(host);
|
||||||
setPort(port);
|
setPort(port);
|
||||||
setPassword(password);
|
setPassword(password);
|
||||||
setTimeout(timeout);
|
setTimeout(Time.SI * 8);
|
||||||
setMaxActive(lettuce.pool.maxActive);
|
setMaxActive(8);
|
||||||
setMinIdle(lettuce.pool.minIdle);
|
setMinIdle(1);
|
||||||
setMaxIdle(lettuce.pool.maxIdle);
|
setMaxIdle(8);
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return 连接池配置 */
|
/** @return 连接池配置 */
|
||||||
@Bean
|
@Bean
|
||||||
@Override
|
@Override
|
||||||
public GenericObjectPoolConfig<?> getPoolConfig() {
|
public GenericObjectPoolConfig<StatefulConnection<?, ?>> getPoolConfig() {
|
||||||
GenericObjectPoolConfig<?> config = new GenericObjectPoolConfig<>();
|
RedisConfigParams configArgs = configParams();
|
||||||
config.setMaxTotal(lettuce.pool.maxActive);
|
GenericObjectPoolConfig<StatefulConnection<?, ?>> config = new GenericObjectPoolConfig<>();
|
||||||
config.setMinIdle(lettuce.pool.minIdle);
|
config.setMaxTotal(8);
|
||||||
config.setMaxIdle(lettuce.pool.maxIdle);
|
config.setMinIdle(configArgs.getMinIdle());
|
||||||
config.setMaxWait(Duration.ofMillis(lettuce.pool.maxWait));
|
config.setMaxIdle(configArgs.getMaxIdle());
|
||||||
|
config.setMaxWait(Duration.ofMillis(-1));
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,7 +191,7 @@ public class RedisConfig extends AbstractRedisConfig {
|
|||||||
/** @return 文章访问统计,文章 ID: {@link ArticleRanking}(JSON) */
|
/** @return 文章访问统计,文章 ID: {@link ArticleRanking}(JSON) */
|
||||||
@Bean("redisArticleRanking")
|
@Bean("redisArticleRanking")
|
||||||
public Redis<Long, ArticleRanking> getArticleRankingRedisTemplate() {
|
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] */
|
/** @return 文章访问记录,IP: [文章 ID] */
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package com.imyeyu.api.config;
|
|||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
@@ -19,37 +17,17 @@ import java.util.concurrent.ThreadPoolExecutor;
|
|||||||
@Data
|
@Data
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableAutoConfiguration
|
|
||||||
@ConfigurationProperties(prefix = "spring.async.thread-pool")
|
|
||||||
public class ThreadPoolConfig {
|
public class ThreadPoolConfig {
|
||||||
|
|
||||||
/** 核心数量 */
|
|
||||||
private int corePoolSize;
|
|
||||||
|
|
||||||
/** 最大数量 */
|
|
||||||
private int maxPoolSize;
|
|
||||||
|
|
||||||
/** 等待区容量 */
|
|
||||||
private int queueCapacity;
|
|
||||||
|
|
||||||
/** 最大保持活跃时间(秒) */
|
|
||||||
private int keepAliveSeconds;
|
|
||||||
|
|
||||||
/** 最大等待时间(秒) */
|
|
||||||
private int awaitTerminationSeconds;
|
|
||||||
|
|
||||||
/** 线程名称前缀 */
|
|
||||||
private String threadNamePrefix;
|
|
||||||
|
|
||||||
@Bean(name = "threadPoolTaskExecutor")
|
@Bean(name = "threadPoolTaskExecutor")
|
||||||
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
|
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
|
||||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||||
executor.setCorePoolSize(corePoolSize);
|
executor.setCorePoolSize(16);
|
||||||
executor.setMaxPoolSize(maxPoolSize);
|
executor.setMaxPoolSize(32);
|
||||||
executor.setQueueCapacity(queueCapacity);
|
executor.setQueueCapacity(16);
|
||||||
executor.setKeepAliveSeconds(keepAliveSeconds);
|
executor.setKeepAliveSeconds(60);
|
||||||
executor.setAwaitTerminationSeconds(awaitTerminationSeconds);
|
executor.setAwaitTerminationSeconds(60);
|
||||||
executor.setThreadNamePrefix(threadNamePrefix);
|
executor.setThreadNamePrefix("thread-pool-task-executor-");
|
||||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||||
executor.initialize();
|
executor.initialize();
|
||||||
return executor;
|
return executor;
|
||||||
|
|||||||
@@ -1,32 +1,28 @@
|
|||||||
package com.imyeyu.api.config;
|
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.EnableSettingInterceptor;
|
||||||
import com.imyeyu.api.annotation.RequestRateLimitInterceptor;
|
import com.imyeyu.api.annotation.RequestRateLimitInterceptor;
|
||||||
import com.imyeyu.api.annotation.RequiredTokenInterceptor;
|
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.journal.util.JournalAPIInterceptor;
|
||||||
import com.imyeyu.api.modules.minecraft.annotation.RequiredFMCServerTokenInterceptor;
|
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.modules.system.util.SystemAPIInterceptor;
|
||||||
import com.imyeyu.api.util.GsonSerializerAdapter;
|
import com.imyeyu.spring.annotation.RequestBodyValueArgumentResolver;
|
||||||
import com.imyeyu.spring.annotation.RequestSingleParamResolver;
|
import com.imyeyu.utils.Time;
|
||||||
import jakarta.validation.constraints.NotNull;
|
|
||||||
import lombok.NonNull;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.http.converter.HttpMessageConverter;
|
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.method.support.HandlerMethodArgumentResolver;
|
||||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
import java.io.Writer;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,12 +36,11 @@ import java.util.List;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class WebConfig implements WebMvcConfigurer {
|
public class WebConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
private final ObjectMapper jackson;
|
||||||
private final SystemAPIInterceptor systemAPIInterceptor;
|
private final SystemAPIInterceptor systemAPIInterceptor;
|
||||||
private final GsonSerializerAdapter gsonSerializerAdapter;
|
|
||||||
private final JournalAPIInterceptor journalAPIInterceptor;
|
private final JournalAPIInterceptor journalAPIInterceptor;
|
||||||
private final RequiredTokenInterceptor requiredTokenInterceptor;
|
private final RequiredTokenInterceptor requiredTokenInterceptor;
|
||||||
private final EnableSettingInterceptor enableSettingInterceptor;
|
private final EnableSettingInterceptor enableSettingInterceptor;
|
||||||
private final RequestSingleParamResolver requestSingleParamResolver;
|
|
||||||
private final RequestRateLimitInterceptor requestRateLimitInterceptor;
|
private final RequestRateLimitInterceptor requestRateLimitInterceptor;
|
||||||
private final RequiredFMCServerTokenInterceptor requiredFMCServerTokenInterceptor;
|
private final RequiredFMCServerTokenInterceptor requiredFMCServerTokenInterceptor;
|
||||||
|
|
||||||
@@ -66,7 +61,7 @@ public class WebConfig implements WebMvcConfigurer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
|
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
|
||||||
argumentResolvers.add(requestSingleParamResolver);
|
argumentResolvers.add(new RequestBodyValueArgumentResolver(jackson));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -76,22 +71,17 @@ public class WebConfig implements WebMvcConfigurer {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
|
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||||
GsonHttpMessageConverter converter = new GsonHttpMessageConverter() {
|
JsonMapper jsonMapper = JsonMapper.builder()
|
||||||
|
// 设置默认属性包含规则:忽略 null 值
|
||||||
@Override
|
.serializationInclusion(JsonInclude.Include.NON_NULL)
|
||||||
protected void writeInternal(@NotNull Object object, Type type, @NonNull Writer writer) {
|
// 日期格式化
|
||||||
// 忽略参数类型,因为接口返回对象会被全局返回处理器包装为 TimiResponse,否则会序列化转型错误
|
.defaultDateFormat(Time.dateTime)
|
||||||
getGson().toJson(object, writer);
|
// 忽略不存在字段
|
||||||
}
|
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
|
||||||
};
|
// 启用默认视图包含
|
||||||
|
.enable(MapperFeature.DEFAULT_VIEW_INCLUSION).build();
|
||||||
GsonBuilder builder = new GsonBuilder();
|
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(jsonMapper);
|
||||||
builder.registerTypeAdapter(Attachment.class, gsonSerializerAdapter);
|
converter.setDefaultCharset(StandardCharsets.UTF_8);
|
||||||
builder.registerTypeAdapter(UserView.class, gsonSerializerAdapter);
|
|
||||||
builder.registerTypeAdapter(MirrorView.class, gsonSerializerAdapter);
|
|
||||||
builder.registerTypeAdapter(UserProfileView.class, gsonSerializerAdapter);
|
|
||||||
builder.registerTypeAdapter(MinecraftPlayer.class, gsonSerializerAdapter);
|
|
||||||
converter.setGson(builder.create());
|
|
||||||
converters.add(converter);
|
converters.add(converter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,8 +70,8 @@ public class GiteaDBConfig {
|
|||||||
ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
|
ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
|
||||||
List<String> mapperLocations = new ArrayList<>();
|
List<String> mapperLocations = new ArrayList<>();
|
||||||
mapperLocations.add("classpath:mapper/gitea/**/*.xml");
|
mapperLocations.add("classpath:mapper/gitea/**/*.xml");
|
||||||
for (int i = 0; i < mapperLocations.size(); i++) {
|
for (String mapperLocation : mapperLocations) {
|
||||||
resources.addAll(List.of(resourceResolver.getResources(mapperLocations.get(i))));
|
resources.addAll(List.of(resourceResolver.getResources(mapperLocation)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
String[] typeAliases = {
|
String[] typeAliases = {
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ public class TimiServerDBConfig {
|
|||||||
mapperLocations.add("classpath:mapper/system/**/*.xml");
|
mapperLocations.add("classpath:mapper/system/**/*.xml");
|
||||||
mapperLocations.add("classpath:mapper/journal/**/*.xml");
|
mapperLocations.add("classpath:mapper/journal/**/*.xml");
|
||||||
mapperLocations.add("classpath:mapper/minecraft/**/*.xml");
|
mapperLocations.add("classpath:mapper/minecraft/**/*.xml");
|
||||||
for (int i = 0; i < mapperLocations.size(); i++) {
|
for (String mapperLocation : mapperLocations) {
|
||||||
resources.addAll(List.of(resourceResolver.getResources(mapperLocations.get(i))));
|
resources.addAll(List.of(resourceResolver.getResources(mapperLocation)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
String[] typeAliases = {
|
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;
|
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.api.modules.common.bean.CommentSupport;
|
||||||
import com.imyeyu.spring.entity.Destroyable;
|
import com.imyeyu.spring.entity.Destroyable;
|
||||||
import com.imyeyu.spring.entity.Entity;
|
import com.imyeyu.spring.entity.Entity;
|
||||||
@@ -48,7 +48,7 @@ public class Article extends Entity implements CommentSupport, Destroyable {
|
|||||||
protected String data;
|
protected String data;
|
||||||
|
|
||||||
/** 扩展数据 */
|
/** 扩展数据 */
|
||||||
protected JsonElement extendData;
|
protected JsonNode extendData;
|
||||||
|
|
||||||
/** 阅读数量 */
|
/** 阅读数量 */
|
||||||
protected int reads;
|
protected int reads;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package com.imyeyu.api.modules.blog.entity;
|
package com.imyeyu.api.modules.blog.entity;
|
||||||
|
|
||||||
|
import com.imyeyu.spring.entity.Entity;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
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
|
* @since 2021-03-01 17:10
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public class ArticleRanking extends Entity {
|
public class ArticleRanking extends Entity {
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.imyeyu.api.modules.common.controller;
|
package com.imyeyu.api.modules.common.controller;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.imyeyu.api.bean.CaptchaFrom;
|
import com.imyeyu.api.bean.CaptchaFrom;
|
||||||
import com.imyeyu.api.modules.common.bean.ImageType;
|
import com.imyeyu.api.modules.common.bean.ImageType;
|
||||||
import com.imyeyu.api.modules.common.bean.SettingKey;
|
import com.imyeyu.api.modules.common.bean.SettingKey;
|
||||||
@@ -95,12 +95,28 @@ public class CommonController {
|
|||||||
private final AttachmentService attachmentService;
|
private final AttachmentService attachmentService;
|
||||||
private final ClipboardService clipboardService;
|
private final ClipboardService clipboardService;
|
||||||
|
|
||||||
private final Gson gson;
|
private final ObjectMapper jackson;
|
||||||
private final Yaml yaml;
|
private final Yaml yaml;
|
||||||
private final GridFSBucket gridFSBucket;
|
private final GridFSBucket gridFSBucket;
|
||||||
private final CaptchaManager captchaManager;
|
private final CaptchaManager captchaManager;
|
||||||
private final ResourceHandler resourceHandler;
|
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
|
@AOPLog
|
||||||
@RequestMapping("")
|
@RequestMapping("")
|
||||||
public String root() {
|
public String root() {
|
||||||
@@ -253,12 +269,12 @@ public class CommonController {
|
|||||||
case JSON -> {
|
case JSON -> {
|
||||||
if (setting.getType() == Setting.Type.YAML) {
|
if (setting.getType() == Setting.Type.YAML) {
|
||||||
Map<String, Object> obj = yaml.load(setting.getValue());
|
Map<String, Object> obj = yaml.load(setting.getValue());
|
||||||
result = gson.toJson(obj);
|
result = writeJson(obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case YAML -> {
|
case YAML -> {
|
||||||
if (setting.getType() == Setting.Type.JSON) {
|
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);
|
result = yaml.dump(obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -284,12 +300,12 @@ public class CommonController {
|
|||||||
case JSON -> {
|
case JSON -> {
|
||||||
if (setting.getType() == Setting.Type.YAML) {
|
if (setting.getType() == Setting.Type.YAML) {
|
||||||
Map<String, Object> obj = new Yaml().load(setting.getValue());
|
Map<String, Object> obj = new Yaml().load(setting.getValue());
|
||||||
setting.setValue(gson.toJson(obj));
|
setting.setValue(writeJson(obj));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case YAML -> {
|
case YAML -> {
|
||||||
if (setting.getType() == Setting.Type.JSON) {
|
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));
|
setting.setValue(new Yaml().dump(obj));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -483,7 +499,7 @@ public class CommonController {
|
|||||||
GridFSFile file = attachmentService.readByMongoId(mongoId);
|
GridFSFile file = attachmentService.readByMongoId(mongoId);
|
||||||
@Cleanup
|
@Cleanup
|
||||||
GridFSDownloadStream downloadStream = gridFSBucket.openDownloadStream(file.getObjectId());
|
GridFSDownloadStream downloadStream = gridFSBucket.openDownloadStream(file.getObjectId());
|
||||||
RequestRange range = TimiSpring.requestRange(attach.getSize());
|
RequestRange range = TimiSpring.getRequestRange(attach.getSize());
|
||||||
if (range == null) {
|
if (range == null) {
|
||||||
// 完整文件
|
// 完整文件
|
||||||
resp.setContentLengthLong(attach.getSize());
|
resp.setContentLengthLong(attach.getSize());
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import com.imyeyu.java.TimiJava;
|
|||||||
import com.imyeyu.java.bean.timi.TimiException;
|
import com.imyeyu.java.bean.timi.TimiException;
|
||||||
import com.imyeyu.spring.annotation.AOPLog;
|
import com.imyeyu.spring.annotation.AOPLog;
|
||||||
import com.imyeyu.spring.annotation.RequestRateLimit;
|
import com.imyeyu.spring.annotation.RequestRateLimit;
|
||||||
import com.imyeyu.spring.annotation.RequestSingleParam;
|
|
||||||
import com.imyeyu.spring.annotation.RequiredToken;
|
import com.imyeyu.spring.annotation.RequiredToken;
|
||||||
import com.imyeyu.spring.bean.CaptchaData;
|
import com.imyeyu.spring.bean.CaptchaData;
|
||||||
import com.imyeyu.spring.bean.Page;
|
import com.imyeyu.spring.bean.Page;
|
||||||
@@ -184,7 +183,7 @@ public class UserController implements TimiJava {
|
|||||||
@RequiredToken
|
@RequiredToken
|
||||||
@RequestRateLimit
|
@RequestRateLimit
|
||||||
@PostMapping("/cancel")
|
@PostMapping("/cancel")
|
||||||
public void cancel(@RequestSingleParam String password) {
|
public void cancel(@RequestBody String password) {
|
||||||
service.cancel(password);
|
service.cancel(password);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,7 +274,7 @@ public class UserController implements TimiJava {
|
|||||||
@RequiredToken
|
@RequiredToken
|
||||||
@RequestRateLimit
|
@RequestRateLimit
|
||||||
@PostMapping("/comment/delete")
|
@PostMapping("/comment/delete")
|
||||||
public void deleteComment(@RequestSingleParam Long commentId) {
|
public void deleteComment(@RequestBody Long commentId) {
|
||||||
commentService.get(commentId);
|
commentService.get(commentId);
|
||||||
commentService.delete(commentId);
|
commentService.delete(commentId);
|
||||||
}
|
}
|
||||||
@@ -298,7 +297,7 @@ public class UserController implements TimiJava {
|
|||||||
@RequiredToken
|
@RequiredToken
|
||||||
@RequestRateLimit
|
@RequestRateLimit
|
||||||
@PostMapping("/comment/reply/delete")
|
@PostMapping("/comment/reply/delete")
|
||||||
public void deleteCommentReply(@RequestSingleParam Long replyId) {
|
public void deleteCommentReply(@RequestBody Long replyId) {
|
||||||
CommentReply reply = commentReplyService.get(replyId);
|
CommentReply reply = commentReplyService.get(replyId);
|
||||||
TimiException.requiredTrue(reply.getSenderId().equals(service.getLoginUser().getId()), "user.comment.reply.delete.not_owner");
|
TimiException.requiredTrue(reply.getSenderId().equals(service.getLoginUser().getId()), "user.comment.reply.delete.not_owner");
|
||||||
commentReplyService.delete(replyId);
|
commentReplyService.delete(replyId);
|
||||||
@@ -308,7 +307,7 @@ public class UserController implements TimiJava {
|
|||||||
@RequiredToken
|
@RequiredToken
|
||||||
@RequestRateLimit
|
@RequestRateLimit
|
||||||
@PostMapping("/comment/reply/ignore")
|
@PostMapping("/comment/reply/ignore")
|
||||||
public void ignoreCommentReply(@RequestSingleParam Long replyId) {
|
public void ignoreCommentReply(@RequestBody Long replyId) {
|
||||||
CommentReply reply = commentReplyService.get(replyId);
|
CommentReply reply = commentReplyService.get(replyId);
|
||||||
TimiException.requiredTrue(reply.getReceiverId().equals(service.getLoginUser().getId()), "user.comment.reply.ignore.not_owner");
|
TimiException.requiredTrue(reply.getReceiverId().equals(service.getLoginUser().getId()), "user.comment.reply.ignore.not_owner");
|
||||||
reply.setIgnoredAt(Time.now());
|
reply.setIgnoredAt(Time.now());
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.imyeyu.api.modules.common.entity;
|
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.TimiServerAPI;
|
||||||
import com.imyeyu.api.bean.MultilingualHandler;
|
import com.imyeyu.api.bean.MultilingualHandler;
|
||||||
import com.imyeyu.api.modules.common.service.AttachmentService;
|
import com.imyeyu.api.modules.common.service.AttachmentService;
|
||||||
@@ -81,7 +81,7 @@ public class Attachment extends Entity implements MultilingualHandler {
|
|||||||
|
|
||||||
protected String mimeType;
|
protected String mimeType;
|
||||||
|
|
||||||
protected JsonObject metadata;
|
protected JsonNode metadata;
|
||||||
|
|
||||||
protected Long size;
|
protected Long size;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package com.imyeyu.api.modules.common.service;
|
package com.imyeyu.api.modules.common.service;
|
||||||
|
|
||||||
import com.google.gson.JsonArray;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.google.gson.JsonElement;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.google.gson.JsonObject;
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
import com.imyeyu.api.modules.common.bean.SettingKey;
|
import com.imyeyu.api.modules.common.bean.SettingKey;
|
||||||
import com.imyeyu.api.modules.common.entity.Setting;
|
import com.imyeyu.api.modules.common.entity.Setting;
|
||||||
import com.imyeyu.java.bean.timi.TimiException;
|
import com.imyeyu.java.bean.timi.TimiException;
|
||||||
@@ -60,15 +60,15 @@ public interface SettingService extends UpdatableService<Setting> {
|
|||||||
*/
|
*/
|
||||||
boolean not(SettingKey key);
|
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, 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);
|
<T> T fromYaml(SettingKey key, Class<T> clazz);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.imyeyu.api.modules.common.service.implement;
|
package com.imyeyu.api.modules.common.service.implement;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.google.gson.JsonObject;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
|
import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
|
||||||
import com.imyeyu.api.modules.common.bean.MediaAttach;
|
import com.imyeyu.api.modules.common.bean.MediaAttach;
|
||||||
import com.imyeyu.api.modules.common.bean.Metadata;
|
import com.imyeyu.api.modules.common.bean.Metadata;
|
||||||
@@ -55,7 +55,7 @@ public class AttachmentServiceImplement extends AbstractEntityService<Attachment
|
|||||||
|
|
||||||
private final AttachmentMapper mapper;
|
private final AttachmentMapper mapper;
|
||||||
|
|
||||||
private final Gson gson;
|
private final ObjectMapper jackson;
|
||||||
private final GridFSBucket gridFSBucket;
|
private final GridFSBucket gridFSBucket;
|
||||||
private final GridFsTemplate gridFsTemplate;
|
private final GridFsTemplate gridFsTemplate;
|
||||||
|
|
||||||
@@ -90,10 +90,10 @@ public class AttachmentServiceImplement extends AbstractEntityService<Attachment
|
|||||||
attachment.setIsDestroyed(false);
|
attachment.setIsDestroyed(false);
|
||||||
if (attachment.getMimeType().startsWith("image")) {
|
if (attachment.getMimeType().startsWith("image")) {
|
||||||
BufferedImage image = ImageIO.read(gridFSBucket.openDownloadStream(gridFSFile.getObjectId()));
|
BufferedImage image = ImageIO.read(gridFSBucket.openDownloadStream(gridFSFile.getObjectId()));
|
||||||
attachment.setMetadata(TimiJava.defaultIfNull(attachment.getMetadata(), new JsonObject()));
|
attachment.setMetadata(TimiJava.defaultIfNull(attachment.getMetadata(), jackson.createObjectNode()));
|
||||||
JsonObject metadata = attachment.getMetadata();
|
ObjectNode metadata = (ObjectNode) attachment.getMetadata();
|
||||||
metadata.addProperty("width", image.getWidth());
|
metadata.put("width", image.getWidth());
|
||||||
metadata.addProperty("height", image.getHeight());
|
metadata.put("height", image.getHeight());
|
||||||
}
|
}
|
||||||
mapper.insert(attachment);
|
mapper.insert(attachment);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -166,7 +166,7 @@ public class AttachmentServiceImplement extends AbstractEntityService<Attachment
|
|||||||
thumbAttach.setBizType(attachment.getBizType());
|
thumbAttach.setBizType(attachment.getBizType());
|
||||||
thumbAttach.setBizId(attachment.getBizId());
|
thumbAttach.setBizId(attachment.getBizId());
|
||||||
thumbAttach.setAttachTypeValue(MediaAttach.Type.THUMB);
|
thumbAttach.setAttachTypeValue(MediaAttach.Type.THUMB);
|
||||||
thumbAttach.setMetadata(gson.toJsonTree(thumbMetadata).getAsJsonObject());
|
thumbAttach.setMetadata(jackson.valueToTree(thumbMetadata));
|
||||||
thumbAttach.setInputStream(new ByteArrayInputStream(thumbStream.toByteArray()));
|
thumbAttach.setInputStream(new ByteArrayInputStream(thumbStream.toByteArray()));
|
||||||
create(thumbAttach);
|
create(thumbAttach);
|
||||||
|
|
||||||
@@ -182,7 +182,7 @@ public class AttachmentServiceImplement extends AbstractEntityService<Attachment
|
|||||||
public void deleteMedia(Long thumbId) throws TimiException {
|
public void deleteMedia(Long thumbId) throws TimiException {
|
||||||
Attachment attachment = get(thumbId);
|
Attachment attachment = get(thumbId);
|
||||||
delete(attachment.getId());
|
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());
|
delete(thumbMetadata.getSourceId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package com.imyeyu.api.modules.common.service.implement;
|
package com.imyeyu.api.modules.common.service.implement;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.google.gson.JsonArray;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.google.gson.JsonElement;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.google.gson.JsonObject;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.google.gson.JsonParser;
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
import com.imyeyu.api.modules.common.bean.SettingKey;
|
import com.imyeyu.api.modules.common.bean.SettingKey;
|
||||||
import com.imyeyu.api.modules.common.entity.Setting;
|
import com.imyeyu.api.modules.common.entity.Setting;
|
||||||
import com.imyeyu.api.modules.common.mapper.SettingMapper;
|
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 SettingMapper mapper;
|
||||||
private final Redis<String, String> redisSetting;
|
private final Redis<String, String> redisSetting;
|
||||||
|
|
||||||
private final Gson gson;
|
private final ObjectMapper jackson;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected BaseMapper<Setting, String> mapper() {
|
protected BaseMapper<Setting, String> mapper() {
|
||||||
@@ -59,7 +59,11 @@ public class SettingServiceImplement extends AbstractEntityService<Setting, Stri
|
|||||||
|
|
||||||
String cacheValue = redisSetting.get(key.toString());
|
String cacheValue = redisSetting.get(key.toString());
|
||||||
if (TimiJava.isNotEmpty(cacheValue)) {
|
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);
|
Setting setting = mapper.selectByKey(key);
|
||||||
if (TimiJava.isEmpty(setting.getValue())) {
|
if (TimiJava.isEmpty(setting.getValue())) {
|
||||||
@@ -72,7 +76,11 @@ public class SettingServiceImplement extends AbstractEntityService<Setting, Stri
|
|||||||
settingTTL = Integer.parseInt(getByKey(SettingKey.TTL_SETTING).getValue());
|
settingTTL = Integer.parseInt(getByKey(SettingKey.TTL_SETTING).getValue());
|
||||||
}
|
}
|
||||||
if (0 < settingTTL) {
|
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;
|
return setting;
|
||||||
}
|
}
|
||||||
@@ -117,28 +125,36 @@ public class SettingServiceImplement extends AbstractEntityService<Setting, Stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JsonElement getAsJsonElement(SettingKey key) {
|
public JsonNode getAsJsonNode(SettingKey key) {
|
||||||
return JsonParser.parseString(getAsString(key));
|
try {
|
||||||
|
return jackson.readTree(getAsString(key));
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
throw new TimiException(TimiCode.ERROR, "read setting json error", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JsonObject getAsJsonObject(SettingKey key) {
|
public ObjectNode getAsJsonObject(SettingKey key) {
|
||||||
return getAsJsonElement(key).getAsJsonObject();
|
return (ObjectNode) getAsJsonNode(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JsonArray getAsJsonArray(SettingKey key) {
|
public ArrayNode getAsArrayNode(SettingKey key) {
|
||||||
return getAsJsonElement(key).getAsJsonArray();
|
return (ArrayNode) getAsJsonNode(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> T fromJson(SettingKey key, Class<T> clazz) {
|
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
|
@Override
|
||||||
public <T> T fromJson(SettingKey key, TypeToken<T> typeToken) {
|
public <T> T fromJson(SettingKey key, TypeReference<T> typeReference) {
|
||||||
return gson.fromJson(getAsJsonElement(key), typeToken);
|
return jackson.convertValue(getAsJsonNode(key), typeReference);
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> T fromYaml(SettingKey key, Class<T> clazz) {
|
public <T> T fromYaml(SettingKey key, Class<T> clazz) {
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
package com.imyeyu.api.modules.common.service.implement;
|
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.bean.SettingKey;
|
||||||
import com.imyeyu.api.modules.common.entity.Attachment;
|
import com.imyeyu.api.modules.common.entity.Attachment;
|
||||||
import com.imyeyu.api.modules.common.service.AttachmentService;
|
import com.imyeyu.api.modules.common.service.AttachmentService;
|
||||||
@@ -31,8 +29,6 @@ import java.util.List;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class TempFileServiceImplement implements TempFileService {
|
public class TempFileServiceImplement implements TempFileService {
|
||||||
|
|
||||||
private final Gson gson;
|
|
||||||
|
|
||||||
private final SettingService settingService;
|
private final SettingService settingService;
|
||||||
private final AttachmentService attachmentService;
|
private final AttachmentService attachmentService;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
package com.imyeyu.api.modules.common.task;
|
package com.imyeyu.api.modules.common.task;
|
||||||
|
|
||||||
import com.google.gson.JsonArray;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.google.gson.JsonObject;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.google.gson.JsonParser;
|
|
||||||
import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
|
import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
|
||||||
import com.imyeyu.api.modules.common.bean.SettingKey;
|
import com.imyeyu.api.modules.common.bean.SettingKey;
|
||||||
import com.imyeyu.api.modules.common.entity.Multilingual;
|
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 SettingService settingService;
|
||||||
private final MultilingualService service;
|
private final MultilingualService service;
|
||||||
|
|
||||||
@@ -147,18 +147,17 @@ public class MultilingualTranslateTask {
|
|||||||
.execute()
|
.execute()
|
||||||
.returnContent()
|
.returnContent()
|
||||||
.asString();
|
.asString();
|
||||||
JsonObject jo = JsonParser.parseString(response).getAsJsonObject();
|
JsonNode jo = jackson.readTree(response);
|
||||||
if (jo.has("error_code")) {
|
if (jo.has("error_code")) {
|
||||||
System.err.println(jo);
|
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<>();
|
Map<String, String> result = new HashMap<>();
|
||||||
for (int i = 0; i < ja.size(); i++) {
|
for (int i = 0; i < ja.size(); i++) {
|
||||||
resultJO = ja.get(i).getAsJsonObject();
|
JsonNode resultJO = ja.get(i);
|
||||||
result.put(resultJO.get("src").getAsString(), resultJO.get("dst").getAsString());
|
result.put(resultJO.get("src").asText(), resultJO.get("dst").asText());
|
||||||
}
|
}
|
||||||
wait(200);
|
wait(200);
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.imyeyu.api.modules.git.bean.gitea;
|
package com.imyeyu.api.modules.git.bean.gitea;
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -12,6 +12,6 @@ public class Branch {
|
|||||||
|
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
@SerializedName("protected")
|
@JsonProperty("protected")
|
||||||
private boolean isProtected;
|
private boolean isProtected;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.imyeyu.api.modules.git.bean.gitea;
|
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 com.imyeyu.api.modules.git.util.GiteaTimestampAdapter;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@@ -36,6 +37,7 @@ public class File {
|
|||||||
|
|
||||||
private String lastCommitSha;
|
private String lastCommitSha;
|
||||||
|
|
||||||
@JsonAdapter(GiteaTimestampAdapter.class)
|
@JsonSerialize(using = GiteaTimestampAdapter.Serializer.class)
|
||||||
|
@JsonDeserialize(using = GiteaTimestampAdapter.Deserializer.class)
|
||||||
private Long lastCommitterDate;
|
private Long lastCommitterDate;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.imyeyu.api.modules.git.bean.gitea;
|
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 com.imyeyu.api.modules.git.util.GiteaTimestampAdapter;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@@ -35,13 +36,16 @@ public class Repository {
|
|||||||
|
|
||||||
private Boolean archived;
|
private Boolean archived;
|
||||||
|
|
||||||
@JsonAdapter(GiteaTimestampAdapter.class)
|
@JsonSerialize(using = GiteaTimestampAdapter.Serializer.class)
|
||||||
|
@JsonDeserialize(using = GiteaTimestampAdapter.Deserializer.class)
|
||||||
private Long createdAt;
|
private Long createdAt;
|
||||||
|
|
||||||
@JsonAdapter(GiteaTimestampAdapter.class)
|
@JsonSerialize(using = GiteaTimestampAdapter.Serializer.class)
|
||||||
|
@JsonDeserialize(using = GiteaTimestampAdapter.Deserializer.class)
|
||||||
private Long updatedAt;
|
private Long updatedAt;
|
||||||
|
|
||||||
@JsonAdapter(GiteaTimestampAdapter.class)
|
@JsonSerialize(using = GiteaTimestampAdapter.Serializer.class)
|
||||||
|
@JsonDeserialize(using = GiteaTimestampAdapter.Deserializer.class)
|
||||||
private Long archivedAt;
|
private Long archivedAt;
|
||||||
|
|
||||||
private List<String> licenses;
|
private List<String> licenses;
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
package com.imyeyu.api.modules.git.service.implement;
|
package com.imyeyu.api.modules.git.service.implement;
|
||||||
|
|
||||||
import com.google.gson.FieldNamingPolicy;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.google.gson.Gson;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
import com.imyeyu.java.TimiJava;
|
import com.imyeyu.java.TimiJava;
|
||||||
import com.imyeyu.java.bean.timi.TimiCode;
|
import com.imyeyu.java.bean.timi.TimiCode;
|
||||||
import com.imyeyu.java.bean.timi.TimiException;
|
import com.imyeyu.java.bean.timi.TimiException;
|
||||||
@@ -56,17 +55,18 @@ import java.util.List;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class RepositoryServiceImplement implements RepositoryService {
|
public class RepositoryServiceImplement implements RepositoryService {
|
||||||
|
|
||||||
private final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
|
|
||||||
|
|
||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
private final GiteaService giteaService;
|
private final GiteaService giteaService;
|
||||||
private final SettingService settingService;
|
private final SettingService settingService;
|
||||||
|
private final ObjectMapper jackson;
|
||||||
|
|
||||||
private User owner;
|
private User owner;
|
||||||
|
private ObjectMapper giteaJackson;
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
private void postConstruct() {
|
private void postConstruct() {
|
||||||
owner = giteaService.getOwner();
|
owner = giteaService.getOwner();
|
||||||
|
giteaJackson = jackson.copy().setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -80,8 +80,7 @@ public class RepositoryServiceImplement implements RepositoryService {
|
|||||||
put("limit", page.getLimit());
|
put("limit", page.getLimit());
|
||||||
}})).execute().returnResponse();
|
}})).execute().returnResponse();
|
||||||
String respText = EntityUtils.toString(resp.getEntity());
|
String respText = EntityUtils.toString(resp.getEntity());
|
||||||
GiteaResponse<List<Repository>> respObj = gson.fromJson(respText, new TypeToken<GiteaResponse<List<Repository>>>() {
|
GiteaResponse<List<Repository>> respObj = giteaJackson.readValue(respText, new TypeReference<>() {});
|
||||||
}.getType());
|
|
||||||
|
|
||||||
PageResult<Repository> result = new PageResult<>();
|
PageResult<Repository> result = new PageResult<>();
|
||||||
result.setTotal(Long.parseLong(resp.getHeader("X-Total-Count").getValue()));
|
result.setTotal(Long.parseLong(resp.getHeader("X-Total-Count").getValue()));
|
||||||
@@ -100,7 +99,7 @@ public class RepositoryServiceImplement implements RepositoryService {
|
|||||||
put("owner", owner.getName());
|
put("owner", owner.getName());
|
||||||
put("repoName", repoName);
|
put("repoName", repoName);
|
||||||
}})).execute().returnContent().asString();
|
}})).execute().returnContent().asString();
|
||||||
return gson.fromJson(respText, Repository.class);
|
return giteaJackson.readValue(respText, Repository.class);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("get repository error", e);
|
log.error("get repository error", e);
|
||||||
throw new TimiException(TimiCode.ERROR, "get repository error", e);
|
throw new TimiException(TimiCode.ERROR, "get repository error", e);
|
||||||
@@ -110,11 +109,11 @@ public class RepositoryServiceImplement implements RepositoryService {
|
|||||||
@Override
|
@Override
|
||||||
public List<Branch> listBranches(String repoName) {
|
public List<Branch> listBranches(String repoName) {
|
||||||
try {
|
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("owner", owner.getName());
|
||||||
put("repoName", repoName);
|
put("repoName", repoName);
|
||||||
}})).execute().returnContent().asString();
|
}})).execute().returnContent().asString();
|
||||||
return gson.fromJson(respText, new TypeToken<List<Branch>>() {}.getType());
|
return giteaJackson.readValue(respText, new TypeReference<>() {});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("list repository branches error", e);
|
log.error("list repository branches error", e);
|
||||||
throw new TimiException(TimiCode.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
|
@Override
|
||||||
public List<File> listFile(String repoName, String branch, String path) {
|
public List<File> listFile(String repoName, String branch, String path) {
|
||||||
try {
|
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("owner", owner.getName());
|
||||||
put("repoName", repoName);
|
put("repoName", repoName);
|
||||||
put("path", path);
|
put("path", path);
|
||||||
}}, new HashMap<>() {{
|
}}, new HashMap<>() {{
|
||||||
put("ref", branch);
|
put("ref", branch);
|
||||||
}})).execute().returnContent().asString();
|
}})).execute().returnContent().asString();
|
||||||
List<File> list = gson.fromJson(respText, new TypeToken<List<File>>() {}.getType());
|
List<File> list = giteaJackson.readValue(respText, new TypeReference<>() {});
|
||||||
// 排序
|
// 排序
|
||||||
list.sort((f1, f2) -> {
|
list.sort((f1, f2) -> {
|
||||||
if (f1.getType() == File.Type.dir && f2.getType() == File.Type.file) {
|
if (f1.getType() == File.Type.dir && f2.getType() == File.Type.file) {
|
||||||
|
|||||||
@@ -1,28 +1,54 @@
|
|||||||
package com.imyeyu.api.modules.git.util;
|
package com.imyeyu.api.modules.git.util;
|
||||||
|
|
||||||
import com.google.gson.JsonDeserializationContext;
|
import com.fasterxml.jackson.core.JacksonException;
|
||||||
import com.google.gson.JsonDeserializer;
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
import com.google.gson.JsonElement;
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
import com.google.gson.JsonPrimitive;
|
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||||
import com.google.gson.JsonSerializationContext;
|
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||||
import com.google.gson.JsonSerializer;
|
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;
|
import java.time.Instant;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @since 2025-06-26 11:40
|
* @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;
|
package com.imyeyu.api.modules.gitea.bean;
|
||||||
|
|
||||||
import com.google.gson.annotations.JsonAdapter;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
import com.imyeyu.api.modules.gitea.util.GiteaUTCTimestampAdapter;
|
import com.imyeyu.api.modules.gitea.util.GiteaUTCTimestampAdapter;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@@ -50,14 +51,15 @@ public class ActionLogDTO {
|
|||||||
@Data
|
@Data
|
||||||
public static class Commit {
|
public static class Commit {
|
||||||
|
|
||||||
@SerializedName("Sha1")
|
@JsonProperty("Sha1")
|
||||||
private String sha;
|
private String sha;
|
||||||
|
|
||||||
@SerializedName("Message")
|
@JsonProperty("Message")
|
||||||
private String message;
|
private String message;
|
||||||
|
|
||||||
@JsonAdapter(GiteaUTCTimestampAdapter.class)
|
@JsonProperty("Timestamp")
|
||||||
@SerializedName("Timestamp")
|
@JsonSerialize(using = GiteaUTCTimestampAdapter.Serializer.class)
|
||||||
|
@JsonDeserialize(using = GiteaUTCTimestampAdapter.Deserializer.class)
|
||||||
private Long timestamp;
|
private Long timestamp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
package com.imyeyu.api.modules.gitea.util;
|
package com.imyeyu.api.modules.gitea.util;
|
||||||
|
|
||||||
import com.google.gson.JsonDeserializationContext;
|
import com.fasterxml.jackson.core.JacksonException;
|
||||||
import com.google.gson.JsonDeserializer;
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
import com.google.gson.JsonElement;
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
import com.google.gson.JsonPrimitive;
|
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||||
import com.google.gson.JsonSerializationContext;
|
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||||
import com.google.gson.JsonSerializer;
|
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.Instant;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
@@ -18,15 +19,21 @@ import java.time.ZoneOffset;
|
|||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @since 2025-06-27 16:18
|
* @since 2025-06-27 16:18
|
||||||
*/
|
*/
|
||||||
public class GiteaUTCTimestampAdapter implements JsonDeserializer<Long>, JsonSerializer<Long> {
|
public class GiteaUTCTimestampAdapter {
|
||||||
|
|
||||||
@Override
|
public static class Deserializer extends JsonDeserializer<Long> {
|
||||||
public Long deserialize(JsonElement json, Type type, JsonDeserializationContext context) {
|
|
||||||
return OffsetDateTime.parse(json.getAsString()).toInstant().toEpochMilli();
|
@Override
|
||||||
|
public Long deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
|
||||||
|
return OffsetDateTime.parse(p.getText()).toInstant().toEpochMilli();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public static class Serializer extends JsonSerializer<Long> {
|
||||||
public JsonElement serialize(Long timestamp, Type type, JsonSerializationContext context) {
|
|
||||||
return new JsonPrimitive(OffsetDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC).toString());
|
@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;
|
package com.imyeyu.api.modules.gitea.vo;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.google.gson.JsonObject;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.google.gson.JsonParser;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
import com.imyeyu.java.TimiJava;
|
import com.imyeyu.java.TimiJava;
|
||||||
import com.imyeyu.api.TimiServerAPI;
|
import com.imyeyu.api.TimiServerAPI;
|
||||||
import com.imyeyu.api.modules.common.service.UserService;
|
import com.imyeyu.api.modules.common.service.UserService;
|
||||||
@@ -34,7 +33,7 @@ public class ActionLogView {
|
|||||||
private UserView operator;
|
private UserView operator;
|
||||||
|
|
||||||
public static ActionLogView fromDTO(ActionLogDTO dto) {
|
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);
|
UserService userService = TimiServerAPI.applicationContext.getBean(UserService.class);
|
||||||
|
|
||||||
ActionLogView view = new ActionLogView();
|
ActionLogView view = new ActionLogView();
|
||||||
@@ -45,15 +44,19 @@ public class ActionLogView {
|
|||||||
view.setOperator(userService.view((long) dto.getOperatorId()).doFilter());
|
view.setOperator(userService.view((long) dto.getOperatorId()).doFilter());
|
||||||
{
|
{
|
||||||
if (TimiJava.isNotEmpty(dto.getContent())) {
|
if (TimiJava.isNotEmpty(dto.getContent())) {
|
||||||
JsonObject content = JsonParser.parseString(dto.getContent()).getAsJsonObject();
|
try {
|
||||||
List<ActionLogDTO.Commit> commitList = gson.fromJson(content.get("Commits"), new TypeToken<List<ActionLogDTO.Commit>>() {}.getType());
|
JsonNode content = jackson.readTree(dto.getContent());
|
||||||
view.setCommitList(new ArrayList<>());
|
List<ActionLogDTO.Commit> commitList = jackson.convertValue(content.get("Commits"), new TypeReference<>() {});
|
||||||
for (ActionLogDTO.Commit dtoCommit : commitList) {
|
view.setCommitList(new ArrayList<>());
|
||||||
Commit commit = new Commit();
|
for (ActionLogDTO.Commit dtoCommit : commitList) {
|
||||||
commit.setSha(dtoCommit.getSha());
|
Commit commit = new Commit();
|
||||||
commit.setMessage(dtoCommit.getMessage().trim());
|
commit.setSha(dtoCommit.getSha());
|
||||||
commit.setCommittedAt(dtoCommit.getTimestamp());
|
commit.setMessage(dtoCommit.getMessage().trim());
|
||||||
view.getCommitList().add(commit);
|
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;
|
private Long committedAt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.imyeyu.api.modules.journal.controller;
|
package com.imyeyu.api.modules.journal.controller;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.imyeyu.api.bean.PreviewPage;
|
import com.imyeyu.api.bean.PreviewPage;
|
||||||
import com.imyeyu.api.bean.wechat.InitCodeResponse;
|
import com.imyeyu.api.bean.wechat.InitCodeResponse;
|
||||||
import com.imyeyu.api.modules.common.bean.MediaAttach;
|
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.TimiCode;
|
||||||
import com.imyeyu.java.bean.timi.TimiException;
|
import com.imyeyu.java.bean.timi.TimiException;
|
||||||
import com.imyeyu.network.ArgMap;
|
import com.imyeyu.network.ArgMap;
|
||||||
import com.imyeyu.network.GsonRequest;
|
|
||||||
import com.imyeyu.spring.annotation.AOPLog;
|
import com.imyeyu.spring.annotation.AOPLog;
|
||||||
import com.imyeyu.spring.annotation.RequestRateLimit;
|
import com.imyeyu.spring.annotation.RequestRateLimit;
|
||||||
import com.imyeyu.spring.annotation.RequestSingleParam;
|
|
||||||
import com.imyeyu.spring.bean.Page;
|
import com.imyeyu.spring.bean.Page;
|
||||||
import com.imyeyu.spring.bean.PageResult;
|
import com.imyeyu.spring.bean.PageResult;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
@@ -53,6 +52,7 @@ public class JournalController {
|
|||||||
private final JournalService service;
|
private final JournalService service;
|
||||||
private final SettingService settingService;
|
private final SettingService settingService;
|
||||||
private final AttachmentService attachmentService;
|
private final AttachmentService attachmentService;
|
||||||
|
private final ObjectMapper jackson;
|
||||||
|
|
||||||
private final JournalAPIInterceptor apiInterceptor;
|
private final JournalAPIInterceptor apiInterceptor;
|
||||||
|
|
||||||
@@ -65,14 +65,18 @@ public class JournalController {
|
|||||||
@AOPLog
|
@AOPLog
|
||||||
@RequestRateLimit
|
@RequestRateLimit
|
||||||
@PostMapping("/openid")
|
@PostMapping("/openid")
|
||||||
public String initOpenId(@RequestSingleParam String code) {
|
public String initOpenId(@RequestBody String code) {
|
||||||
try {
|
try {
|
||||||
ArgMap<String, String> args = new ArgMap<>();
|
ArgMap<String, String> args = new ArgMap<>();
|
||||||
args.put("appid", settingService.getAsString(SettingKey.JOURNAL_APP_ID));
|
args.put("appid", settingService.getAsString(SettingKey.JOURNAL_APP_ID));
|
||||||
args.put("secret", settingService.getAsString(SettingKey.JOURNAL_APP_SECRET));
|
args.put("secret", settingService.getAsString(SettingKey.JOURNAL_APP_SECRET));
|
||||||
args.put("js_code", code);
|
args.put("js_code", code);
|
||||||
args.put("grant_type", "authorization_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();
|
return resp.getOpenid();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("init WeChat openId error", e);
|
log.error("init WeChat openId error", e);
|
||||||
@@ -139,7 +143,7 @@ public class JournalController {
|
|||||||
@AOPLog
|
@AOPLog
|
||||||
@RequiredUploadPermission
|
@RequiredUploadPermission
|
||||||
@PostMapping("/delete")
|
@PostMapping("/delete")
|
||||||
public void delete(@RequestSingleParam Long id) {
|
public void delete(@RequestBody Long id) {
|
||||||
service.delete(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.entity.Setting;
|
||||||
import com.imyeyu.api.modules.common.service.SettingService;
|
import com.imyeyu.api.modules.common.service.SettingService;
|
||||||
import com.imyeyu.api.modules.journal.bean.RequiredUploadPermission;
|
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.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
@@ -31,7 +31,7 @@ public class ToolController {
|
|||||||
|
|
||||||
@RequiredUploadPermission
|
@RequiredUploadPermission
|
||||||
@PostMapping("/memo/update")
|
@PostMapping("/memo/update")
|
||||||
public void updateMemo(@RequestSingleParam String data) {
|
public void updateMemo(@RequestBodyValue String data) {
|
||||||
Setting setting = settingService.getByKey(SettingKey.JOURNAL_MEMO);
|
Setting setting = settingService.getByKey(SettingKey.JOURNAL_MEMO);
|
||||||
setting.setValue(data);
|
setting.setValue(data);
|
||||||
settingService.update(setting);
|
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.api.modules.journal.service.TravelService;
|
||||||
import com.imyeyu.spring.annotation.AOPLog;
|
import com.imyeyu.spring.annotation.AOPLog;
|
||||||
import com.imyeyu.spring.annotation.RequestRateLimit;
|
import com.imyeyu.spring.annotation.RequestRateLimit;
|
||||||
import com.imyeyu.spring.annotation.RequestSingleParam;
|
|
||||||
import com.imyeyu.spring.bean.Page;
|
import com.imyeyu.spring.bean.Page;
|
||||||
import com.imyeyu.spring.bean.PageResult;
|
import com.imyeyu.spring.bean.PageResult;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
@@ -67,7 +66,7 @@ public class TravelController {
|
|||||||
@RequestRateLimit
|
@RequestRateLimit
|
||||||
@RequiredUploadPermission
|
@RequiredUploadPermission
|
||||||
@PostMapping("/delete")
|
@PostMapping("/delete")
|
||||||
public void delete(@RequestSingleParam Long id) {
|
public void delete(@RequestBody Long id) {
|
||||||
service.delete(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.api.modules.journal.service.TravelLocationService;
|
||||||
import com.imyeyu.spring.annotation.AOPLog;
|
import com.imyeyu.spring.annotation.AOPLog;
|
||||||
import com.imyeyu.spring.annotation.RequestRateLimit;
|
import com.imyeyu.spring.annotation.RequestRateLimit;
|
||||||
import com.imyeyu.spring.annotation.RequestSingleParam;
|
|
||||||
import com.imyeyu.spring.bean.Page;
|
import com.imyeyu.spring.bean.Page;
|
||||||
import com.imyeyu.spring.bean.PageResult;
|
import com.imyeyu.spring.bean.PageResult;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
@@ -74,7 +73,7 @@ public class TravelLocationController {
|
|||||||
@RequestRateLimit
|
@RequestRateLimit
|
||||||
@RequiredUploadPermission
|
@RequiredUploadPermission
|
||||||
@PostMapping("/delete")
|
@PostMapping("/delete")
|
||||||
public void delete(@RequestSingleParam Long id) {
|
public void delete(@RequestBody Long id) {
|
||||||
service.delete(id);
|
service.delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.imyeyu.api.modules.journal.service.implement;
|
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.config.dbsource.TimiServerDBConfig;
|
||||||
import com.imyeyu.api.modules.common.bean.MediaAttach;
|
import com.imyeyu.api.modules.common.bean.MediaAttach;
|
||||||
import com.imyeyu.api.modules.common.bean.Metadata;
|
import com.imyeyu.api.modules.common.bean.Metadata;
|
||||||
@@ -40,7 +40,7 @@ import java.util.stream.Collectors;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class JournalServiceImplement extends AbstractEntityService<Journal, Long> implements JournalService {
|
public class JournalServiceImplement extends AbstractEntityService<Journal, Long> implements JournalService {
|
||||||
|
|
||||||
private final Gson gson;
|
private final ObjectMapper jackson;
|
||||||
|
|
||||||
private final SettingService settingService;
|
private final SettingService settingService;
|
||||||
private final TempFileService tempFileService;
|
private final TempFileService tempFileService;
|
||||||
@@ -192,7 +192,7 @@ public class JournalServiceImplement extends AbstractEntityService<Journal, Long
|
|||||||
thumbAttach.setBizId(journal.getId());
|
thumbAttach.setBizId(journal.getId());
|
||||||
attachmentService.update(thumbAttach);
|
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());
|
Attachment sourceAttach = attachmentService.get(thumbMetadata.getSourceId());
|
||||||
sourceAttach.setBizType(Attachment.BizType.JOURNAL);
|
sourceAttach.setBizType(Attachment.BizType.JOURNAL);
|
||||||
sourceAttach.setBizId(journal.getId());
|
sourceAttach.setBizId(journal.getId());
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public class JournalAPIInterceptor implements HandlerInterceptor {
|
|||||||
|
|
||||||
public boolean canAccess() {
|
public boolean canAccess() {
|
||||||
String reqKey = TimiJava.defaultIfEmpty(TimiSpring.getHeader("Key"), TimiSpring.getRequestArg("key"));
|
String reqKey = TimiJava.defaultIfEmpty(TimiSpring.getHeader("Key"), TimiSpring.getRequestArg("key"));
|
||||||
return keys.contains(reqKey);
|
return TimiJava.isNotEmpty(reqKey) && keys.contains(reqKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean canUploadKey() {
|
public boolean canUploadKey() {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.imyeyu.api.modules.lyric.service.implement;
|
package com.imyeyu.api.modules.lyric.service.implement;
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.google.gson.JsonParser;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.imyeyu.java.bean.timi.TimiCode;
|
import com.imyeyu.java.bean.timi.TimiCode;
|
||||||
import com.imyeyu.java.bean.timi.TimiException;
|
import com.imyeyu.java.bean.timi.TimiException;
|
||||||
import com.imyeyu.api.modules.lyric.entity.Lyric;
|
import com.imyeyu.api.modules.lyric.entity.Lyric;
|
||||||
@@ -50,6 +50,7 @@ public class LyricServiceImplement implements LyricService {
|
|||||||
|
|
||||||
private final LyricMapper mapper;
|
private final LyricMapper mapper;
|
||||||
private final Map<Long, String> allLyric;
|
private final Map<Long, String> allLyric;
|
||||||
|
private final ObjectMapper jackson;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Lyric get(Long id) {
|
public Lyric get(Long id) {
|
||||||
@@ -113,12 +114,12 @@ public class LyricServiceImplement implements LyricService {
|
|||||||
Request request = Request.get(API_GET_SONG + Encoder.urlArgs(args));
|
Request request = Request.get(API_GET_SONG + Encoder.urlArgs(args));
|
||||||
HEADER.forEach(request::addHeader);
|
HEADER.forEach(request::addHeader);
|
||||||
response = request.execute().returnContent().asString();
|
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.clear();
|
||||||
args.put("cmd", "100");
|
args.put("cmd", "100");
|
||||||
args.put("keyword", data.get("songname").getAsString());
|
args.put("keyword", data.path("songname").asText());
|
||||||
args.put("hash", data.get("hash").getAsString());
|
args.put("hash", data.path("hash").asText());
|
||||||
args.put("timelength", tl);
|
args.put("timelength", tl);
|
||||||
args.put("d", String.valueOf(Math.random()));
|
args.put("d", String.valueOf(Math.random()));
|
||||||
request = Request.get(API_GET_LRC + Encoder.urlArgs(args));
|
request = Request.get(API_GET_LRC + Encoder.urlArgs(args));
|
||||||
@@ -128,8 +129,8 @@ public class LyricServiceImplement implements LyricService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Lyric lyric = new Lyric();
|
Lyric lyric = new Lyric();
|
||||||
lyric.setSong(data.get("songname").getAsString());
|
lyric.setSong(data.path("songname").asText());
|
||||||
lyric.setSinger(data.get("singername").getAsString());
|
lyric.setSinger(data.path("singername").asText());
|
||||||
lyric.setData(response);
|
lyric.setData(response);
|
||||||
return lyric;
|
return lyric;
|
||||||
} catch (Exception e) {
|
} 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.api.modules.minecraft.vo.TokenResponse;
|
||||||
import com.imyeyu.spring.annotation.AOPLog;
|
import com.imyeyu.spring.annotation.AOPLog;
|
||||||
import com.imyeyu.spring.annotation.RequestRateLimit;
|
import com.imyeyu.spring.annotation.RequestRateLimit;
|
||||||
import com.imyeyu.spring.annotation.RequestSingleParam;
|
|
||||||
import com.imyeyu.spring.annotation.RequiredToken;
|
import com.imyeyu.spring.annotation.RequiredToken;
|
||||||
import com.imyeyu.spring.util.Redis;
|
import com.imyeyu.spring.util.Redis;
|
||||||
import com.imyeyu.spring.util.RedisSerializers;
|
import com.imyeyu.spring.util.RedisSerializers;
|
||||||
@@ -73,7 +72,7 @@ public class PlayerController {
|
|||||||
@RequiredToken
|
@RequiredToken
|
||||||
@RequestRateLimit
|
@RequestRateLimit
|
||||||
@PostMapping("/bind")
|
@PostMapping("/bind")
|
||||||
public void bind(@RequestSingleParam String name) {
|
public void bind(@RequestBody String name) {
|
||||||
MinecraftPlayer player = new MinecraftPlayer();
|
MinecraftPlayer player = new MinecraftPlayer();
|
||||||
player.setName(name);
|
player.setName(name);
|
||||||
player.setUserId(userService.getLoginUser().getId());
|
player.setUserId(userService.getLoginUser().getId());
|
||||||
@@ -84,7 +83,7 @@ public class PlayerController {
|
|||||||
@RequiredToken
|
@RequiredToken
|
||||||
@RequestRateLimit
|
@RequestRateLimit
|
||||||
@PostMapping("/unbind")
|
@PostMapping("/unbind")
|
||||||
public void unbind(@RequestSingleParam Long id) {
|
public void unbind(@RequestBody Long id) {
|
||||||
service.listByUserId(userService.getLoginUser().getId())
|
service.listByUserId(userService.getLoginUser().getId())
|
||||||
.stream()
|
.stream()
|
||||||
.filter(player -> player.getId().equals(id))
|
.filter(player -> player.getId().equals(id))
|
||||||
@@ -113,7 +112,7 @@ public class PlayerController {
|
|||||||
@RequestRateLimit
|
@RequestRateLimit
|
||||||
@EnableSetting(value = SettingKey.FMC_PLAYER_LOGIN_ENABLE, message = "登录服务未启用")
|
@EnableSetting(value = SettingKey.FMC_PLAYER_LOGIN_ENABLE, message = "登录服务未启用")
|
||||||
@PostMapping("/login")
|
@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);
|
return service.login(playerId, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.imyeyu.api.modules.mirror;
|
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.entity.Attachment;
|
||||||
import com.imyeyu.api.modules.common.service.AttachmentService;
|
import com.imyeyu.api.modules.common.service.AttachmentService;
|
||||||
import com.imyeyu.api.modules.mirror.bean.AttachType;
|
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 static final Pattern versionRegex = Pattern.compile("^(\\d+\\.){1,2}(\\*|\\d+)$");
|
||||||
|
|
||||||
private final Gson gson;
|
private final ObjectMapper jackson;
|
||||||
|
|
||||||
private final MirrorService service;
|
private final MirrorService service;
|
||||||
private final AttachmentService attachmentService;
|
private final AttachmentService attachmentService;
|
||||||
@@ -100,7 +100,7 @@ public class FabricAPIMirror extends AttachmentMirror {
|
|||||||
result.add(item);
|
result.add(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mirror.setData(gson.toJsonTree(result));
|
mirror.setData(jackson.valueToTree(result));
|
||||||
service.update(mirror);
|
service.update(mirror);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
package com.imyeyu.api.modules.mirror;
|
package com.imyeyu.api.modules.mirror;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.google.gson.JsonArray;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
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.imyeyu.api.modules.mirror.data.OpenJDK;
|
import com.imyeyu.api.modules.mirror.data.OpenJDK;
|
||||||
import com.imyeyu.api.modules.mirror.entity.Mirror;
|
import com.imyeyu.api.modules.mirror.entity.Mirror;
|
||||||
import com.imyeyu.api.modules.mirror.service.MirrorService;
|
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 com.imyeyu.utils.OS;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -27,9 +24,9 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Github JDK 镜像,仅同步下载链接等信息,不储存文件
|
* Github JDK 闀滃儚锛屼粎鍚屾涓嬭浇閾炬帴绛変俊鎭紝涓嶅偍瀛樻枃浠?
|
||||||
*
|
*
|
||||||
* @author 夜雨
|
* @author 澶滈洦
|
||||||
* @version 2024-06-10 10:51
|
* @version 2024-06-10 10:51
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -37,10 +34,10 @@ import java.util.Map;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class OpenJDKGithubMirror extends AbstractMirror {
|
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";
|
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<>() {{
|
private static final Map<String, String> REPOS_MAP = Collections.unmodifiableMap(new HashMap<>() {{
|
||||||
put("8", "temurin8-binaries");
|
put("8", "temurin8-binaries");
|
||||||
put("11", "temurin11-binaries");
|
put("11", "temurin11-binaries");
|
||||||
@@ -48,31 +45,30 @@ public class OpenJDKGithubMirror extends AbstractMirror {
|
|||||||
put("21", "temurin21-binaries");
|
put("21", "temurin21-binaries");
|
||||||
}});
|
}});
|
||||||
|
|
||||||
private final Gson gson;
|
private final ObjectMapper jackson;
|
||||||
private final MirrorService service;
|
private final MirrorService service;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void sync(Mirror mirror) throws Exception {
|
protected void sync(Mirror mirror) throws Exception {
|
||||||
mirror.setData(gson.toJsonTree(fetch()));
|
mirror.setData(jackson.valueToTree(fetch()));
|
||||||
service.update(mirror);
|
service.update(mirror);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 {@link OpenJDK} 列表,{@link OpenJDKMirror} 也会使用此接口
|
* 鑾峰彇 {@link OpenJDK} 鍒楄〃锛寋@link OpenJDKMirror} 涔熶細浣跨敤姝ゆ帴鍙?
|
||||||
*
|
*
|
||||||
* @return jdk 列表
|
* @return jdk 鍒楄〃
|
||||||
* @throws Exception 获取异常
|
* @throws Exception 鑾峰彇寮傚父
|
||||||
*/
|
*/
|
||||||
final List<OpenJDK> fetch() throws Exception {
|
final List<OpenJDK> fetch() throws Exception {
|
||||||
List<OpenJDK> result = new ArrayList<>();
|
List<OpenJDK> result = new ArrayList<>();
|
||||||
for (Map.Entry<String, String> repo : REPOS_MAP.entrySet()) {
|
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();
|
String respText = Request.get(API_RELEASE.formatted(repo.getValue())).connectTimeout(Timeout.ofSeconds(60)).execute().returnContent().asString();
|
||||||
JsonArray root = JsonParser.parseString(respText).getAsJsonArray();
|
JsonNode root = jackson.readTree(respText);
|
||||||
JsonObject itemRel = null;
|
JsonNode itemRel = null;
|
||||||
for (JsonElement el : root) {
|
for (JsonNode el : root) {
|
||||||
itemRel = el.getAsJsonObject();
|
itemRel = el;
|
||||||
if (itemRel.get("prerelease").getAsBoolean() || itemRel.get("draft").getAsBoolean()) {
|
if (itemRel.path("prerelease").asBoolean() || itemRel.path("draft").asBoolean()) {
|
||||||
// 忽略草稿或预发布版本
|
|
||||||
itemRel = null;
|
itemRel = null;
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
@@ -81,12 +77,8 @@ public class OpenJDKGithubMirror extends AbstractMirror {
|
|||||||
if (itemRel == null) {
|
if (itemRel == null) {
|
||||||
throw new TimiException(TimiCode.ERROR, "not found release item for " + repo.getValue());
|
throw new TimiException(TimiCode.ERROR, "not found release item for " + repo.getValue());
|
||||||
}
|
}
|
||||||
JsonArray assets = itemRel.get("assets").getAsJsonArray();
|
for (JsonNode itemAsset : itemRel.path("assets")) {
|
||||||
for (JsonElement asset : assets) {
|
String name = itemAsset.path("name").asText();
|
||||||
JsonObject itemAsset = asset.getAsJsonObject();
|
|
||||||
// OpenJDK21U-jdk_x64_windows_hotspot_21.0.3_9.zip
|
|
||||||
String name = itemAsset.get("name").getAsString();
|
|
||||||
|
|
||||||
if (!name.contains("-") || !name.contains("_")) {
|
if (!name.contains("-") || !name.contains("_")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -102,14 +94,13 @@ public class OpenJDKGithubMirror extends AbstractMirror {
|
|||||||
}
|
}
|
||||||
OS.Platform platform = Ref.toType(OS.Platform.class, split[2].toUpperCase());
|
OS.Platform platform = Ref.toType(OS.Platform.class, split[2].toUpperCase());
|
||||||
OpenJDK.Type type = Ref.toType(OpenJDK.Type.class, split[0].toUpperCase());
|
OpenJDK.Type type = Ref.toType(OpenJDK.Type.class, split[0].toUpperCase());
|
||||||
|
|
||||||
if (platform != null && type != null) {
|
if (platform != null && type != null) {
|
||||||
OpenJDK jdk = new OpenJDK();
|
OpenJDK jdk = new OpenJDK();
|
||||||
jdk.setPlatform(platform);
|
jdk.setPlatform(platform);
|
||||||
jdk.setType(type);
|
jdk.setType(type);
|
||||||
jdk.setName(name);
|
jdk.setName(name);
|
||||||
jdk.setVersion(repo.getKey());
|
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);
|
result.add(jdk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.imyeyu.api.modules.mirror;
|
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.entity.Attachment;
|
||||||
import com.imyeyu.api.modules.common.service.AttachmentService;
|
import com.imyeyu.api.modules.common.service.AttachmentService;
|
||||||
import com.imyeyu.api.modules.mirror.bean.AttachType;
|
import com.imyeyu.api.modules.mirror.bean.AttachType;
|
||||||
@@ -30,7 +30,7 @@ import java.util.stream.Collectors;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class OpenJDKMirror extends AttachmentMirror {
|
public class OpenJDKMirror extends AttachmentMirror {
|
||||||
|
|
||||||
private final Gson gson;
|
private final ObjectMapper jackson;
|
||||||
private final MirrorService service;
|
private final MirrorService service;
|
||||||
private final AttachmentService attachmentService;
|
private final AttachmentService attachmentService;
|
||||||
private final OpenJDKGithubMirror githubMirror;
|
private final OpenJDKGithubMirror githubMirror;
|
||||||
@@ -64,7 +64,7 @@ public class OpenJDKMirror extends AttachmentMirror {
|
|||||||
result.add(jdk);
|
result.add(jdk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mirror.setData(gson.toJsonTree(result));
|
mirror.setData(jackson.valueToTree(result));
|
||||||
service.update(mirror);
|
service.update(mirror);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
package com.imyeyu.api.modules.mirror;
|
package com.imyeyu.api.modules.mirror;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import com.imyeyu.api.modules.mirror.data.OpenJDK;
|
import com.imyeyu.api.modules.mirror.data.OpenJDK;
|
||||||
import com.imyeyu.api.modules.mirror.entity.Mirror;
|
import com.imyeyu.api.modules.mirror.entity.Mirror;
|
||||||
import com.imyeyu.api.modules.mirror.service.MirrorService;
|
import com.imyeyu.api.modules.mirror.service.MirrorService;
|
||||||
import com.imyeyu.utils.OS;
|
import com.imyeyu.utils.OS;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.jsoup.Jsoup;
|
import org.jsoup.Jsoup;
|
||||||
import org.jsoup.nodes.Document;
|
import org.jsoup.nodes.Document;
|
||||||
import org.jsoup.nodes.Element;
|
import org.jsoup.nodes.Element;
|
||||||
@@ -41,18 +41,18 @@ public class OpenJDKTunaMirror extends AbstractMirror {
|
|||||||
/** 版本列表 */
|
/** 版本列表 */
|
||||||
private static final String[] VERSIONS = {"8", "11", "17", "21"};
|
private static final String[] VERSIONS = {"8", "11", "17", "21"};
|
||||||
|
|
||||||
private final Gson gson;
|
private final ObjectMapper jackson;
|
||||||
private final MirrorService service;
|
private final MirrorService service;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void sync(Mirror mirror) throws Exception {
|
protected void sync(Mirror mirror) throws Exception {
|
||||||
List<OpenJDK> result = new ArrayList<>();
|
List<OpenJDK> result = new ArrayList<>();
|
||||||
for (int i = 0; i < VERSIONS.length; i++) {
|
for (String version : VERSIONS) {
|
||||||
OpenJDK.Type[] types = OpenJDK.Type.values();
|
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();
|
OS.Platform[] platforms = OS.Platform.values();
|
||||||
for (int k = 0; k < platforms.length; k++) {
|
for (OS.Platform platform : platforms) {
|
||||||
String url = PAGE_URL_TEMPLATE.formatted(VERSIONS[i], types[j].toString().toLowerCase(), platforms[k].toString().toLowerCase());
|
String url = PAGE_URL_TEMPLATE.formatted(version, type.toString().toLowerCase(), platform.toString().toLowerCase());
|
||||||
Document document = Jsoup.connect(url).get();
|
Document document = Jsoup.connect(url).get();
|
||||||
Element fileList = document.getElementById("list");
|
Element fileList = document.getElementById("list");
|
||||||
Elements linkTDList = fileList.getElementsByClass("link");
|
Elements linkTDList = fileList.getElementsByClass("link");
|
||||||
@@ -64,10 +64,10 @@ public class OpenJDKTunaMirror extends AbstractMirror {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
OpenJDK jdk = new OpenJDK();
|
OpenJDK jdk = new OpenJDK();
|
||||||
jdk.setPlatform(platforms[k]);
|
jdk.setPlatform(platform);
|
||||||
jdk.setType(types[j]);
|
jdk.setType(type);
|
||||||
jdk.setName(href);
|
jdk.setName(href);
|
||||||
jdk.setVersion(VERSIONS[i]);
|
jdk.setVersion(version);
|
||||||
jdk.setData(url + href);
|
jdk.setData(url + href);
|
||||||
|
|
||||||
result.add(jdk);
|
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);
|
service.update(mirror);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.imyeyu.api.modules.mirror.controller;
|
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.entity.Mirror;
|
||||||
import com.imyeyu.api.modules.mirror.service.MirrorService;
|
import com.imyeyu.api.modules.mirror.service.MirrorService;
|
||||||
import com.imyeyu.api.modules.mirror.vo.MirrorView;
|
import com.imyeyu.api.modules.mirror.vo.MirrorView;
|
||||||
@@ -38,7 +38,7 @@ public class MirrorController {
|
|||||||
*/
|
*/
|
||||||
@RequestRateLimit
|
@RequestRateLimit
|
||||||
@GetMapping("/{mirrorName}")
|
@GetMapping("/{mirrorName}")
|
||||||
public JsonElement get(@PathVariable String mirrorName) {
|
public JsonNode get(@PathVariable String mirrorName) {
|
||||||
return service.getByName(mirrorName).getData();
|
return service.getByName(mirrorName).getData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package com.imyeyu.api.modules.mirror.entity;
|
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.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import com.imyeyu.spring.entity.Entity;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 镜像
|
* 镜像
|
||||||
@@ -22,7 +22,7 @@ public class Mirror extends Entity {
|
|||||||
protected String name;
|
protected String name;
|
||||||
|
|
||||||
/** 同步数据 */
|
/** 同步数据 */
|
||||||
protected JsonElement data;
|
protected JsonNode data;
|
||||||
|
|
||||||
/** 周期(分钟) */
|
/** 周期(分钟) */
|
||||||
protected int period;
|
protected int period;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.imyeyu.api.modules.music.core;
|
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.ByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
@@ -36,7 +37,7 @@ public class Middleware implements SchedulingConfigurer {
|
|||||||
private static final String CACHE_CLEAR_CORN = "0 0/20 * * * ?";
|
private static final String CACHE_CLEAR_CORN = "0 0/20 * * * ?";
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private Gson gson;
|
private ObjectMapper jackson;
|
||||||
|
|
||||||
/** 缓存频道,Map<频道 ID, 关联频道> */
|
/** 缓存频道,Map<频道 ID, 关联频道> */
|
||||||
private final Map<String, ChannelBinding> channels = new HashMap<>();
|
private final Map<String, ChannelBinding> channels = new HashMap<>();
|
||||||
@@ -67,7 +68,7 @@ public class Middleware implements SchedulingConfigurer {
|
|||||||
channelBinding.setPlayerChannel(ctx);
|
channelBinding.setPlayerChannel(ctx);
|
||||||
channelBinding.setLastActiviedAt(Time.now());
|
channelBinding.setLastActiviedAt(Time.now());
|
||||||
if (channelBinding.getControllerChannel() != null) {
|
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.setControllerChannel(ctx);
|
||||||
channelBinding.setLastActiviedAt(Time.now());
|
channelBinding.setLastActiviedAt(Time.now());
|
||||||
if (pkg.getAction() != null && channelBinding.getPlayerChannel() != null) {
|
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();
|
ByteBuf buffer = Unpooled.buffer();
|
||||||
buffer.writeInt(bytes.length);
|
buffer.writeInt(bytes.length);
|
||||||
buffer.writeBytes(bytes);
|
buffer.writeBytes(bytes);
|
||||||
channelBinding.getPlayerChannel().writeAndFlush(buffer);
|
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;
|
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.ChannelFutureListener;
|
||||||
import io.netty.channel.ChannelHandler;
|
import io.netty.channel.ChannelHandler;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
@@ -26,7 +26,7 @@ import org.springframework.stereotype.Component;
|
|||||||
public class ControllerMessageHandler extends SimpleChannelInboundHandler<WebSocketFrame> {
|
public class ControllerMessageHandler extends SimpleChannelInboundHandler<WebSocketFrame> {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private Gson gson;
|
private ObjectMapper jackson;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private Middleware middleware;
|
private Middleware middleware;
|
||||||
@@ -34,7 +34,11 @@ public class ControllerMessageHandler extends SimpleChannelInboundHandler<WebSoc
|
|||||||
@Override
|
@Override
|
||||||
protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame msg) {
|
protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame msg) {
|
||||||
if (msg instanceof TextWebSocketFrame textWebSocketFrame) {
|
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 {
|
} else {
|
||||||
ctx.channel().writeAndFlush(WebSocketCloseStatus.INVALID_MESSAGE_TYPE).addListener(ChannelFutureListener.CLOSE);
|
ctx.channel().writeAndFlush(WebSocketCloseStatus.INVALID_MESSAGE_TYPE).addListener(ChannelFutureListener.CLOSE);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.imyeyu.api.modules.music.handler;
|
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.ChannelHandler;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.channel.SimpleChannelInboundHandler;
|
import io.netty.channel.SimpleChannelInboundHandler;
|
||||||
@@ -22,14 +22,18 @@ import org.springframework.stereotype.Component;
|
|||||||
public class PlayerMessageHandler extends SimpleChannelInboundHandler<String> {
|
public class PlayerMessageHandler extends SimpleChannelInboundHandler<String> {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private Gson gson;
|
private ObjectMapper jackson;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private Middleware middleware;
|
private Middleware middleware;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected synchronized void channelRead0(ChannelHandlerContext ctx, String result) {
|
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
|
@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;
|
package com.imyeyu.api.modules.system.bean;
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
import com.imyeyu.java.TimiJava;
|
import com.imyeyu.java.TimiJava;
|
||||||
|
import lombok.Data;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
@@ -11,22 +11,24 @@ import java.util.LinkedList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 服务器状态数据,所有动态数据左出右进,此对象由 IOC 托管
|
* 服务端状态缓存
|
||||||
|
*
|
||||||
|
* <p>该对象只用于采集任务内部缓存,不直接作为接口协议返回。</p>
|
||||||
*
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2022-01-31 15:35
|
* @since 2022-01-31 15:35
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Component
|
@Component
|
||||||
public class ServerStatus implements TimiJava {
|
public class ServerStatus implements TimiJava {
|
||||||
|
|
||||||
/** 动态数据更新时轴 */
|
/** 采样时间轴 */
|
||||||
private LinkedList<Number> updateAxis = new LinkedList<>();
|
private LinkedList<Number> updateAxis = new LinkedList<>();
|
||||||
|
|
||||||
/** 系统 */
|
/** 操作系统 */
|
||||||
private OS os = new OS();
|
private OS os = new OS();
|
||||||
|
|
||||||
/** CPU 使用率 */
|
/** CPU */
|
||||||
private CPU cpu = new CPU();
|
private CPU cpu = new CPU();
|
||||||
|
|
||||||
/** 系统内存 */
|
/** 系统内存 */
|
||||||
@@ -35,17 +37,20 @@ public class ServerStatus implements TimiJava {
|
|||||||
/** 网络 */
|
/** 网络 */
|
||||||
private Network network = new Network();
|
private Network network = new Network();
|
||||||
|
|
||||||
/** 本程序状态 */
|
/** 硬件 */
|
||||||
|
private Hardware hardware = new Hardware();
|
||||||
|
|
||||||
|
/** JVM */
|
||||||
private JVM jvm = new JVM();
|
private JVM jvm = new JVM();
|
||||||
|
|
||||||
/** 磁盘 */
|
/** 存储分区 */
|
||||||
private List<Partition> partitions = new ArrayList<>();
|
private List<StoragePartition> storagePartitions = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 系统
|
* 操作系统
|
||||||
*
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2022-08-12 20:55
|
* @since 2022-08-12 20:55
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public static class OS {
|
public static class OS {
|
||||||
@@ -58,10 +63,10 @@ public class ServerStatus implements TimiJava {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 虚拟机状态
|
* JVM
|
||||||
*
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2022-01-31 21:10
|
* @since 2022-01-31 21:10
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public static class JVM {
|
public static class JVM {
|
||||||
@@ -69,80 +74,83 @@ public class ServerStatus implements TimiJava {
|
|||||||
/** 启动时间 */
|
/** 启动时间 */
|
||||||
private long bootAt;
|
private long bootAt;
|
||||||
|
|
||||||
/** JVM 名称 */
|
/** 名称 */
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
/** JVM 版本 */
|
/** 版本 */
|
||||||
private String version;
|
private String version;
|
||||||
|
|
||||||
/** 内存 */
|
/** GC 名称 */
|
||||||
|
private String gcName;
|
||||||
|
|
||||||
|
/** 堆内存 */
|
||||||
private Memory memory = new Memory();
|
private Memory memory = new Memory();
|
||||||
|
|
||||||
/** 内存回收 */
|
/** GC 状态 */
|
||||||
private ZGC zgc = new ZGC();
|
private GC gc = new GC();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 内存
|
* JVM 内存
|
||||||
*
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2022-08-12 20:32
|
* @since 2022-08-12 20:32
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public static class Memory {
|
public static class Memory {
|
||||||
|
|
||||||
/** 初始化 */
|
/** 初始大小 */
|
||||||
private long init;
|
private long init;
|
||||||
|
|
||||||
/** 最大 */
|
/** 最大大小 */
|
||||||
private long max;
|
private long max;
|
||||||
|
|
||||||
/** 已使用 */
|
/** 已用大小 */
|
||||||
private final Deque<Number> used = new ArrayDeque<>();
|
private final Deque<Number> used = new ArrayDeque<>();
|
||||||
|
|
||||||
/** 已提交 */
|
/** 已提交大小 */
|
||||||
private final Deque<Number> committed = new ArrayDeque<>();
|
private final Deque<Number> committed = new ArrayDeque<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 内存回收
|
* GC 状态
|
||||||
*
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2022-08-12 20:32
|
* @since 2022-08-12 20:32
|
||||||
*/
|
*/
|
||||||
@Data
|
@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> syncCyclesTime = new ArrayDeque<>();
|
||||||
|
|
||||||
/** 回收暂停时长 */
|
/** 暂停耗时序列 */
|
||||||
private final Deque<Number> pausesTime = new ArrayDeque<>();
|
private final Deque<Number> pausesTime = new ArrayDeque<>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 中央处理器
|
* CPU
|
||||||
*
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2022-01-31 15:40
|
* @since 2022-01-31 15:40
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public static class CPU {
|
public static class CPU {
|
||||||
@@ -150,19 +158,19 @@ public class ServerStatus implements TimiJava {
|
|||||||
/** 名称 */
|
/** 名称 */
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
/** 物理核心数量 */
|
/** 物理核心数 */
|
||||||
private int coreCount;
|
private int coreCount;
|
||||||
|
|
||||||
/** 线程数量 */
|
/** 逻辑核心数 */
|
||||||
private int logicalCount;
|
private int logicalCount;
|
||||||
|
|
||||||
/** 温度 */
|
/** 温度 */
|
||||||
private double temperature;
|
private double temperature;
|
||||||
|
|
||||||
/** 系统使用 */
|
/** 系统占用 */
|
||||||
private final Deque<Number> system = new ArrayDeque<>();
|
private final Deque<Number> system = new ArrayDeque<>();
|
||||||
|
|
||||||
/** 总共已使用 */
|
/** 总占用 */
|
||||||
private final Deque<Number> used = new ArrayDeque<>();
|
private final Deque<Number> used = new ArrayDeque<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,7 +178,7 @@ public class ServerStatus implements TimiJava {
|
|||||||
* 系统内存
|
* 系统内存
|
||||||
*
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2022-01-31 15:50
|
* @since 2022-01-31 15:50
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public static class Memory {
|
public static class Memory {
|
||||||
@@ -178,69 +186,174 @@ public class ServerStatus implements TimiJava {
|
|||||||
/** 物理内存大小 */
|
/** 物理内存大小 */
|
||||||
private long size;
|
private long size;
|
||||||
|
|
||||||
/** 交换区大小 */
|
/** 交换分区大小 */
|
||||||
private long swapSize;
|
private long swapSize;
|
||||||
|
|
||||||
/** 已使用 */
|
/** 已用内存 */
|
||||||
private final Deque<Number> used = new ArrayDeque<>();
|
private final Deque<Number> used = new ArrayDeque<>();
|
||||||
|
|
||||||
/** 交换区已使用 */
|
/** 已用交换分区 */
|
||||||
private final Deque<Number> swapUsed = new ArrayDeque<>();
|
private final Deque<Number> swapUsed = new ArrayDeque<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 网卡网速
|
* 网络
|
||||||
*
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2022-08-10 21:41
|
* @since 2022-08-10 21:41
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public static class Network {
|
public static class Network {
|
||||||
|
|
||||||
|
/** 网卡名称 */
|
||||||
|
private String name;
|
||||||
|
|
||||||
/** 累计接收 */
|
/** 累计接收 */
|
||||||
private long recvTotal;
|
private long recvTotal;
|
||||||
|
|
||||||
/** 累计发送 */
|
/** 累计发送 */
|
||||||
private long sentTotal;
|
private long sentTotal;
|
||||||
|
|
||||||
/** 实时接收速度 */
|
/** 实时接收速率 */
|
||||||
private long recvNow;
|
private long recvNow;
|
||||||
|
|
||||||
/** 实时发送速度 */
|
/** 实时发送速率 */
|
||||||
private long sentNow;
|
private long sentNow;
|
||||||
|
|
||||||
/** MAC 地址 */
|
/** MAC 地址 */
|
||||||
private String 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> sent = new ArrayDeque<>();
|
||||||
|
|
||||||
/** 接收 */
|
/** 接收序列 */
|
||||||
private final Deque<Number> recv = new ArrayDeque<>();
|
private final Deque<Number> recv = new ArrayDeque<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分区
|
* 硬件信息
|
||||||
*
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2022-01-31 20:19
|
* @since 2026-04-06
|
||||||
*/
|
*/
|
||||||
@Data
|
@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 uuid;
|
||||||
|
|
||||||
/** 路径 */
|
/** 挂载点 */
|
||||||
private String path;
|
private String mountPoint;
|
||||||
|
|
||||||
/** 文件系统类型 */
|
/** 分区总空间 */
|
||||||
private String type;
|
|
||||||
|
|
||||||
/** 已使用 */
|
|
||||||
private long used;
|
|
||||||
|
|
||||||
/** 总大小 */
|
|
||||||
private long total;
|
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("Content-Disposition", Network.getFileDownloadHeader(file.getName()));
|
||||||
resp.setHeader("Accept-Ranges", "bytes");
|
resp.setHeader("Accept-Ranges", "bytes");
|
||||||
|
|
||||||
RequestRange range = TimiSpring.requestRange(file.length());
|
RequestRange range = TimiSpring.getRequestRange(file.length());
|
||||||
if (range == null) {
|
if (range == null) {
|
||||||
// 完整文件
|
// 完整文件
|
||||||
resp.setContentLengthLong(file.length());
|
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.entity.Attachment;
|
||||||
import com.imyeyu.api.modules.common.service.AttachmentService;
|
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.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.api.modules.system.vo.TempAttachRequest;
|
||||||
import com.imyeyu.io.IO;
|
import com.imyeyu.io.IO;
|
||||||
import com.imyeyu.java.bean.timi.TimiCode;
|
import com.imyeyu.java.bean.timi.TimiCode;
|
||||||
@@ -12,6 +14,7 @@ import com.imyeyu.spring.annotation.AOPLog;
|
|||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
@@ -23,10 +26,10 @@ import java.io.IOException;
|
|||||||
import java.util.concurrent.Semaphore;
|
import java.util.concurrent.Semaphore;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 服务器控制接口
|
* 服务端控制器
|
||||||
*
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2022-01-31 22:47
|
* @since 2022-01-31 22:47
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@@ -34,7 +37,7 @@ import java.util.concurrent.Semaphore;
|
|||||||
@RequestMapping("/system/server")
|
@RequestMapping("/system/server")
|
||||||
public class SystemController {
|
public class SystemController {
|
||||||
|
|
||||||
private final ServerStatus serverStatus;
|
private final StatusService statusService;
|
||||||
private final SystemService service;
|
private final SystemService service;
|
||||||
private final AttachmentService attachmentService;
|
private final AttachmentService attachmentService;
|
||||||
|
|
||||||
@@ -42,16 +45,36 @@ public class SystemController {
|
|||||||
private final Semaphore rebootSemaphore = new Semaphore(1);
|
private final Semaphore rebootSemaphore = new Semaphore(1);
|
||||||
private final Semaphore restoreSemaphore = 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
|
@AOPLog
|
||||||
@PostMapping("/update")
|
@PostMapping("/update")
|
||||||
@@ -85,8 +108,7 @@ public class SystemController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 停止系统
|
* 关闭系统
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
@AOPLog
|
@AOPLog
|
||||||
@RequestMapping("/shutdown")
|
@RequestMapping("/shutdown")
|
||||||
@@ -111,7 +133,11 @@ public class SystemController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO 临时接口
|
/**
|
||||||
|
* 上传临时附件
|
||||||
|
*
|
||||||
|
* @param request 上传请求
|
||||||
|
*/
|
||||||
@AOPLog
|
@AOPLog
|
||||||
@PostMapping("/attach")
|
@PostMapping("/attach")
|
||||||
public void uploadAttachment(TempAttachRequest request) {
|
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.service.TerminalService;
|
||||||
import com.imyeyu.api.modules.system.vo.terminal.ExecCommand;
|
import com.imyeyu.api.modules.system.vo.terminal.ExecCommand;
|
||||||
import com.imyeyu.spring.annotation.AOPLog;
|
import com.imyeyu.spring.annotation.AOPLog;
|
||||||
import com.imyeyu.spring.annotation.RequestSingleParam;
|
|
||||||
import com.imyeyu.spring.annotation.RequiredToken;
|
import com.imyeyu.spring.annotation.RequiredToken;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
@@ -51,7 +50,7 @@ public class TerminalController {
|
|||||||
*/
|
*/
|
||||||
@RequiredToken
|
@RequiredToken
|
||||||
@PostMapping("/alive")
|
@PostMapping("/alive")
|
||||||
public boolean isAlive(@RequestSingleParam String sessionId) {
|
public boolean isAlive(@RequestBody String sessionId) {
|
||||||
return service.isAlive(sessionId);
|
return service.isAlive(sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +63,7 @@ public class TerminalController {
|
|||||||
@AOPLog
|
@AOPLog
|
||||||
@RequiredToken
|
@RequiredToken
|
||||||
@PostMapping("/fill")
|
@PostMapping("/fill")
|
||||||
public synchronized String pathFill(@RequestSingleParam String path) {
|
public synchronized String pathFill(@RequestBody String path) {
|
||||||
return service.pathFill(path);
|
return service.pathFill(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +93,7 @@ public class TerminalController {
|
|||||||
@AOPLog
|
@AOPLog
|
||||||
@RequiredToken
|
@RequiredToken
|
||||||
@PostMapping("/close")
|
@PostMapping("/close")
|
||||||
public void close(@RequestSingleParam String sessionId) {
|
public void close(@RequestBody String sessionId) {
|
||||||
service.close(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,319 @@
|
|||||||
|
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.java.TimiJava;
|
||||||
|
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.LinkedHashMap;
|
||||||
|
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();
|
||||||
|
Map<String, DockerStatusStore.Container> collectedContainers = new LinkedHashMap<>();
|
||||||
|
for (JsonNode summary : containers) {
|
||||||
|
try {
|
||||||
|
String containerId = getAsString(summary, "Id");
|
||||||
|
DockerStatusStore.Container container = new DockerStatusStore.Container();
|
||||||
|
if (TimiJava.isEmpty(container)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
updateContainerSummary(container, summary);
|
||||||
|
updateContainerInspect(containerId, container);
|
||||||
|
updateContainerStats(containerId, container, now);
|
||||||
|
collectedContainers.put(container.getId(), container);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("collect docker container item error", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
synchronized (dockerStatusStore) {
|
||||||
|
Set<String> activeIds = new HashSet<>(collectedContainers.keySet());
|
||||||
|
for (Map.Entry<String, DockerStatusStore.Container> item : collectedContainers.entrySet()) {
|
||||||
|
DockerStatusStore.Container container = dockerStatusStore.getContainers().computeIfAbsent(item.getKey(), key -> new DockerStatusStore.Container());
|
||||||
|
applyCollectedContainer(container, item.getValue());
|
||||||
|
}
|
||||||
|
dockerStatusStore.getContainers().entrySet().removeIf(item -> !activeIds.contains(item.getKey()));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("collect docker container status error", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyCollectedContainer(DockerStatusStore.Container target, DockerStatusStore.Container source) {
|
||||||
|
target.setId(source.getId());
|
||||||
|
target.setName(source.getName());
|
||||||
|
target.setImage(source.getImage());
|
||||||
|
target.setImageId(source.getImageId());
|
||||||
|
target.setCreatedAt(source.getCreatedAt());
|
||||||
|
target.setState(source.getState());
|
||||||
|
target.setStatus(source.getStatus());
|
||||||
|
target.setHealthStatus(source.getHealthStatus());
|
||||||
|
target.setStartedAt(source.getStartedAt());
|
||||||
|
target.setFinishedAt(source.getFinishedAt());
|
||||||
|
target.setExitCode(source.getExitCode());
|
||||||
|
target.setRestartCount(source.getRestartCount());
|
||||||
|
target.setOomKilled(source.isOomKilled());
|
||||||
|
target.setCpuPercent(source.getCpuPercent());
|
||||||
|
target.setMemoryUsageBytes(source.getMemoryUsageBytes());
|
||||||
|
target.setMemoryLimitBytes(source.getMemoryLimitBytes());
|
||||||
|
target.setMemoryPercent(source.getMemoryPercent());
|
||||||
|
target.setNetworkRxBytes(source.getNetworkRxBytes());
|
||||||
|
target.setNetworkTxBytes(source.getNetworkTxBytes());
|
||||||
|
target.setBlockReadBytes(source.getBlockReadBytes());
|
||||||
|
target.setBlockWriteBytes(source.getBlockWriteBytes());
|
||||||
|
target.setPids(source.getPids());
|
||||||
|
target.setUpdatedAt(source.getUpdatedAt());
|
||||||
|
DockerStatusStore.Point point = source.getHistory().peekLast();
|
||||||
|
if (point != null) {
|
||||||
|
target.getHistory().addLast(point);
|
||||||
|
while (historyLimit < target.getHistory().size()) {
|
||||||
|
target.getHistory().pollFirst();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
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.bean.SettingKey;
|
||||||
import com.imyeyu.api.modules.common.service.SettingService;
|
import com.imyeyu.api.modules.common.service.SettingService;
|
||||||
import com.imyeyu.api.modules.system.bean.ServerStatus;
|
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.annotation.SchedulingConfigurer;
|
||||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||||
import org.springframework.scheduling.support.CronTrigger;
|
import org.springframework.scheduling.support.CronTrigger;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import oshi.SystemInfo;
|
import oshi.SystemInfo;
|
||||||
import oshi.hardware.CentralProcessor;
|
|
||||||
import oshi.hardware.GlobalMemory;
|
|
||||||
import oshi.hardware.HardwareAbstractionLayer;
|
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.ManagementFactory;
|
||||||
import java.lang.management.MemoryMXBean;
|
|
||||||
import java.util.Deque;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 服务器状态收集任务
|
* 服务端状态采集任务
|
||||||
*
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2022-01-31 15:18
|
* @since 2022-01-31 15:18
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class ServerStatusTask implements SchedulingConfigurer, TimiJava {
|
public class ServerStatusTask implements SchedulingConfigurer {
|
||||||
|
|
||||||
private final ServerStatus status;
|
private final ServerStatus status;
|
||||||
private final SettingService settingService;
|
private final SettingService settingService;
|
||||||
|
private final List<StatusCollector> statusCollectors;
|
||||||
private GlobalMemory globalMemory; // 内存
|
|
||||||
private MemoryMXBean jvmMemory; // JVM 内存
|
|
||||||
private OperatingSystem os; // 操作系统
|
|
||||||
private CentralProcessor processor; // 中央处理器
|
|
||||||
private HardwareAbstractionLayer hardware; // 硬件
|
|
||||||
|
|
||||||
private long[] lastCPUTicks; // CPU 上一时刻状态
|
|
||||||
private long lastLinuxAPIMemoryUsed; // 上一周期 JVM 内存大小
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configureTasks(@NotNull ScheduledTaskRegistrar taskRegistrar) {
|
public void configureTasks(@NotNull ScheduledTaskRegistrar taskRegistrar) {
|
||||||
lastLinuxAPIMemoryUsed = -1;
|
SystemInfo systemInfo = new SystemInfo();
|
||||||
|
HardwareAbstractionLayer hardware = systemInfo.getHardware();
|
||||||
|
|
||||||
// 系统信息
|
StatusCollectContext context = new StatusCollectContext(
|
||||||
SystemInfo system = new SystemInfo();
|
status,
|
||||||
|
hardware.getMemory(),
|
||||||
// 硬件信息
|
ManagementFactory.getMemoryMXBean(),
|
||||||
hardware = system.getHardware();
|
settingService,
|
||||||
|
hardware.getComputerSystem(),
|
||||||
// 操作系统
|
systemInfo.getOperatingSystem(),
|
||||||
os = system.getOperatingSystem();
|
hardware.getProcessor(),
|
||||||
|
hardware,
|
||||||
// ---------- 静态数据 ----------
|
Time.now()
|
||||||
|
);
|
||||||
// 系统
|
synchronized (status) {
|
||||||
status.getOs().setName(OS.NAME);
|
for (StatusCollector collector : statusCollectors) {
|
||||||
status.getOs().setBootAt(os.getSystemBootTime() * 1000);
|
collector.initialize(context);
|
||||||
|
|
||||||
// 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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
taskRegistrar.addTriggerTask(() -> {
|
taskRegistrar.addTriggerTask(() -> {
|
||||||
long now = Time.now();
|
synchronized (status) {
|
||||||
|
context.setCollectAt(Time.now());
|
||||||
// ---------- JVM 内存 ----------
|
for (StatusCollector collector : statusCollectors) {
|
||||||
long nowLinuxAPIMemoryUsed = jvmMemory.getHeapMemoryUsage().getUsed();
|
collector.collect(context);
|
||||||
long linuxAPIMemoryDiff = Math.abs(nowLinuxAPIMemoryUsed - lastLinuxAPIMemoryUsed);
|
}
|
||||||
lastLinuxAPIMemoryUsed = nowLinuxAPIMemoryUsed;
|
status.getUpdateAxis().addLast(context.getCollectAt());
|
||||||
status.getJvm().getMemory().setInit(jvmMemory.getHeapMemoryUsage().getInit());
|
if (settingService.getAsInt(SettingKey.SYSTEM_STATUS_LIMIT) < status.getUpdateAxis().size()) {
|
||||||
status.getJvm().getMemory().setMax(jvmMemory.getHeapMemoryUsage().getMax());
|
status.getUpdateAxis().pollFirst();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
putDeque(status.getJvm().getZgc().getSyncCyclesTime(), gcSyncCyclesTime - status.getJvm().getZgc().getSyncCyclesTimeTotal());
|
}, triggerContext -> new CronTrigger("0/%s * * * * ?".formatted(settingService.getAsInt(SettingKey.SYSTEM_STATUS_RATE) / 1000)).nextExecution(triggerContext));
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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:false}")
|
||||||
|
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,234 @@
|
|||||||
|
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.Files;
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断 Docker Engine 当前是否可访问。
|
||||||
|
*
|
||||||
|
* @return 可访问返回 true,不可访问返回 false
|
||||||
|
*/
|
||||||
|
public boolean isAvailable() {
|
||||||
|
if (host.startsWith("unix://")) {
|
||||||
|
String socketPath = host.substring("unix://".length());
|
||||||
|
return Files.exists(Path.of(socketPath));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 Docker Engine 主机配置。
|
||||||
|
*
|
||||||
|
* @return 主机配置字符串
|
||||||
|
*/
|
||||||
|
public String getHost() {
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
private final SettingService settingService;
|
||||||
|
|
||||||
public boolean preHandle(@NonNull HttpServletRequest req, @NonNull HttpServletResponse resp, @NonNull Object handler) {
|
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)) {
|
if (TimiJava.isEmpty(key)) {
|
||||||
key = req.getParameter("key");
|
key = req.getParameter("token");
|
||||||
}
|
}
|
||||||
String dbKey = settingService.getAsString(SettingKey.SYSTEM_API_KEY);
|
String dbKey = settingService.getAsString(SettingKey.SYSTEM_API_KEY);
|
||||||
String dbSuperKey = settingService.getAsString(SettingKey.SYSTEM_API_SUPER_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;
|
package com.imyeyu.api.util;
|
||||||
|
|
||||||
import com.google.gson.JsonArray;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.google.gson.JsonElement;
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
import com.google.gson.JsonObject;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
import com.imyeyu.api.TimiServerAPI;
|
|
||||||
import com.imyeyu.api.modules.common.bean.SettingKey;
|
import com.imyeyu.api.modules.common.bean.SettingKey;
|
||||||
import com.imyeyu.api.modules.common.entity.Multilingual;
|
import com.imyeyu.api.modules.common.entity.Multilingual;
|
||||||
import com.imyeyu.api.modules.common.entity.Setting;
|
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.api.modules.system.bean.ServerFile;
|
||||||
import com.imyeyu.java.ref.Ref;
|
import com.imyeyu.java.ref.Ref;
|
||||||
import com.imyeyu.lang.mapper.AbstractLanguageMapper;
|
import com.imyeyu.lang.mapper.AbstractLanguageMapper;
|
||||||
|
import com.imyeyu.spring.TimiSpring;
|
||||||
import com.imyeyu.spring.util.GlobalReturnHandler;
|
import com.imyeyu.spring.util.GlobalReturnHandler;
|
||||||
import com.imyeyu.spring.util.Redis;
|
import com.imyeyu.spring.util.Redis;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@@ -80,30 +80,29 @@ public class InitApplication implements ApplicationRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initMultilingual() {
|
private void initMultilingual() {
|
||||||
// redisLanguage.flushAll();
|
|
||||||
globalReturnHandler.setMultilingualHeader(mapping -> {
|
globalReturnHandler.setMultilingualHeader(mapping -> {
|
||||||
AbstractLanguageMapper map = redisMultilingual.map(TimiServerAPI.getUserLanguage());
|
AbstractLanguageMapper map = redisMultilingual.map(TimiSpring.getLanguage());
|
||||||
return map.textArgs(mapping.getMsgKey(), mapping.getMsgArgs());
|
return map.textArgs(mapping.getMsgKey(), mapping.getMsgArgs());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initFileType() {
|
private void initFileType() {
|
||||||
JsonObject items = settingService.getAsJsonObject(SettingKey.SYSTEM_FILE_TYPE);
|
ObjectNode items = settingService.getAsJsonObject(SettingKey.SYSTEM_FILE_TYPE);
|
||||||
|
|
||||||
String[] extensions;
|
String[] extensions;
|
||||||
JsonArray extensionsArray;
|
ArrayNode extensionsArray;
|
||||||
JsonObject itemObject;
|
JsonNode itemObject;
|
||||||
List<String> extensionsList;
|
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());
|
ServerFile.FileType fileType = Ref.toType(ServerFile.FileType.class, item.getKey());
|
||||||
itemObject = item.getValue().getAsJsonObject();
|
itemObject = item.getValue();
|
||||||
extensionsList = new ArrayList<>();
|
extensionsList = new ArrayList<>();
|
||||||
extensionsArray = itemObject.get("extensions").getAsJsonArray();
|
extensionsArray = (ArrayNode) itemObject.get("extensions");
|
||||||
for (int i = 0; i < extensionsArray.size(); i++) {
|
for (JsonNode extensionNode : extensionsArray) {
|
||||||
if (extensionsArray.get(i).isJsonObject()) {
|
if (extensionNode.isObject()) {
|
||||||
extensionsList.add(extensionsArray.get(i).getAsJsonObject().get("value").getAsString());
|
extensionsList.add(extensionNode.path("value").asText());
|
||||||
} else {
|
} else {
|
||||||
extensionsList.add(extensionsArray.get(i).getAsString());
|
extensionsList.add(extensionNode.asText());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
extensions = new String[extensionsList.size()];
|
extensions = new String[extensionsList.size()];
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
server:
|
server:
|
||||||
port: 8091
|
|
||||||
shutdown: graceful
|
shutdown: graceful
|
||||||
|
# 压缩
|
||||||
|
compression:
|
||||||
|
enable: true
|
||||||
|
min-response-size: 10KB
|
||||||
|
|
||||||
# 开发环境语言,激活开发配置时,多语言系统始终使用此语言环境
|
# 开发环境语言,激活开发配置时,多语言系统始终使用此语言环境
|
||||||
dev:
|
dev:
|
||||||
@@ -12,26 +15,13 @@ logging:
|
|||||||
|
|
||||||
# Spring
|
# Spring
|
||||||
spring:
|
spring:
|
||||||
profiles:
|
|
||||||
active: prod
|
|
||||||
servlet:
|
servlet:
|
||||||
multipart:
|
multipart:
|
||||||
max-file-size: 4GB
|
max-file-size: 4GB
|
||||||
max-request-size: 4GB
|
max-request-size: 4GB
|
||||||
lifecycle:
|
lifecycle:
|
||||||
timeout-per-shutdown-phase: 32s
|
timeout-per-shutdown-phase: 32s
|
||||||
async:
|
mail:
|
||||||
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
|
|
||||||
properties:
|
properties:
|
||||||
mail:
|
mail:
|
||||||
smtp:
|
smtp:
|
||||||
@@ -40,63 +30,3 @@ spring:
|
|||||||
enable: true
|
enable: true
|
||||||
required: true
|
required: true
|
||||||
default-encoding: UTF-8
|
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;
|
package test;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.imyeyu.api.TimiServerAPI;
|
import com.imyeyu.api.TimiServerAPI;
|
||||||
import com.imyeyu.api.modules.blog.entity.Article;
|
import com.imyeyu.api.modules.blog.entity.Article;
|
||||||
import com.imyeyu.api.modules.blog.mapper.ArticleMapper;
|
import com.imyeyu.api.modules.blog.mapper.ArticleMapper;
|
||||||
@@ -118,6 +117,5 @@ public class SpringTest {
|
|||||||
for (int i = 0; i < icon.size(); i++) {
|
for (int i = 0; i < icon.size(); i++) {
|
||||||
map.put(icon.get(i).getName(), icon.get(i).getSvg());
|
map.put(icon.get(i).getName(), icon.get(i).getSvg());
|
||||||
}
|
}
|
||||||
System.out.println(new Gson().toJson(map));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user