Compare commits

3 Commits

Author SHA1 Message Date
407dc13ac4 Merge pull request 'v1.0.0' (#3) from dev into master
Reviewed-on: #3
2026-04-09 04:09:16 +00:00
0c06bf16c2 Merge pull request 'v1.0.0' (#2) from dev into master
Reviewed-on: #2
2026-04-09 04:03:43 +00:00
1db39e77d3 Merge pull request 'v1.0.0' (#1) from dev into master
Reviewed-on: #1
2026-04-08 08:48:12 +00:00
53 changed files with 836 additions and 939 deletions

View File

@@ -57,17 +57,20 @@ jobs:
- name: Deploy service - name: Deploy service
if: success() if: success()
env: env:
CONTAINER_NAME: ${{ vars.CONTAINER_NAME }} HOST: host.docker.internal
CONTAINER_TARGET_PATH: ${{ vars.CONTAINER_TARGET_PATH }} APP_PATH: ${{ vars.APP_PATH }}
DOCKER_CONTAINER_NAME: ${{ vars.DOCKER_CONTAINER_NAME }}
SSHPASS: ${{ secrets.TIMI_SERVER_SSH_PWD }}
MAX_RETRIES: 3 MAX_RETRIES: 3
RETRY_DELAY: 10 RETRY_DELAY: 10
run: | run: |
if [ -z "$CONTAINER_NAME" ] || [ -z "$CONTAINER_TARGET_PATH" ]; then if [ -z "$HOST" ] || [ -z "$APP_PATH" ] || [ -z "DOCKER_CONTAINER_NAME" ] || [ -z "$SSHPASS" ]; then
echo "Missing production environment variables" echo "Missing production environment variables"
echo "Required: CONTAINER_NAME, CONTAINER_TARGET_PATH" echo "Required: APP_PATH, DOCKER_CONTAINER_NAME, TIMI_SERVER_SSH_PWD"
exit 1 exit 1
fi fi
# 重试函数
retry_command() { retry_command() {
local cmd="$1" local cmd="$1"
local desc="$2" local desc="$2"
@@ -76,10 +79,10 @@ jobs:
while [ $attempt -le $MAX_RETRIES ]; do while [ $attempt -le $MAX_RETRIES ]; do
echo "[$desc] Attempt $attempt/$MAX_RETRIES..." echo "[$desc] Attempt $attempt/$MAX_RETRIES..."
if eval "$cmd"; then if eval "$cmd"; then
echo "OK: $desc succeeded" echo " $desc succeeded"
return 0 return 0
fi fi
echo "FAIL: $desc failed (attempt $attempt/$MAX_RETRIES)" echo " $desc failed (attempt $attempt/$MAX_RETRIES)"
if [ $attempt -lt $MAX_RETRIES ]; then if [ $attempt -lt $MAX_RETRIES ]; then
echo "Retrying in ${RETRY_DELAY}s..." echo "Retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY sleep $RETRY_DELAY
@@ -87,10 +90,16 @@ jobs:
attempt=$((attempt + 1)) attempt=$((attempt + 1))
done done
echo "FAIL: $desc failed after $MAX_RETRIES attempts" echo " $desc failed after $MAX_RETRIES attempts"
return 1 return 1
} }
# SSH 配置(使用密码认证)
SSH_PORT="22"
SSH_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=30 -o ServerAliveInterval=10 -o ServerAliveCountMax=3 -p $SSH_PORT"
SCP_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=30 -o ServerAliveInterval=10 -o ServerAliveCountMax=3 -P $SSH_PORT"
# 获取构建产物信息
version=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version) version=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version)
artifact_id=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.artifactId) artifact_id=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.artifactId)
jar_file="target/${artifact_id}-${version}.jar" jar_file="target/${artifact_id}-${version}.jar"
@@ -100,26 +109,18 @@ jobs:
exit 1 exit 1
fi 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" target_jar="${artifact_id}.jar"
container_target="${CONTAINER_TARGET_PATH%/}/$target_jar" echo "Deploying $jar_file to $HOST:$APP_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 # 上传文件(带重试)
if ! retry_command "sshpass -e scp $SCP_OPTS \"$jar_file\" \"root@$HOST:$APP_PATH/$target_jar\"" "SCP upload"; then
exit 1 exit 1
fi fi
echo "Restarting Docker container: $CONTAINER_NAME" # 重启 Docker 服务(带重试)
if ! retry_command "docker restart \"$CONTAINER_NAME\"" "Docker restart"; then echo "Restarting Docker service: $DOCKER_SERVICE_NAME"
if ! retry_command "sshpass -e ssh $SSH_OPTS \"root@$HOST\" \"docker restart $DOCKER_SERVICE_NAME\"" "Docker restart"; then
exit 1 exit 1
fi fi
echo "Deployment completed successfully" echo "Deployment completed successfully"
@@ -141,6 +142,7 @@ jobs:
exit 1 exit 1
fi fi
# Use internal URL if available, fallback to public URL
if [ -n "$GITEA_INTERNAL_URL" ]; then if [ -n "$GITEA_INTERNAL_URL" ]; then
api_base_url="$GITEA_INTERNAL_URL" api_base_url="$GITEA_INTERNAL_URL"
echo "Using internal Gitea URL: $api_base_url" echo "Using internal Gitea URL: $api_base_url"
@@ -149,6 +151,7 @@ jobs:
echo "Using public Gitea URL: $api_base_url" echo "Using public Gitea URL: $api_base_url"
fi fi
# 获取构建产物信息
version=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version) version=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version)
artifact_id=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.artifactId) artifact_id=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.artifactId)
jar_file="target/${artifact_id}-${version}.jar" jar_file="target/${artifact_id}-${version}.jar"
@@ -176,14 +179,17 @@ jobs:
echo "API URL: $api_url" echo "API URL: $api_url"
echo "Target commit: $RELEASE_TARGET" echo "Target commit: $RELEASE_TARGET"
# 使用唯一临时文件避免跨 job 污染
release_response_file=$(mktemp /tmp/release_response_XXXXXX.json) release_response_file=$(mktemp /tmp/release_response_XXXXXX.json)
trap "rm -f $release_response_file" EXIT trap "rm -f $release_response_file" EXIT
# 创建 release带重试处理幂等性
release_id="" release_id=""
attempt=1 attempt=1
while [ $attempt -le $MAX_RETRIES ] && [ -z "$release_id" ]; do while [ $attempt -le $MAX_RETRIES ] && [ -z "$release_id" ]; do
echo "[Create release] Attempt $attempt/$MAX_RETRIES..." echo "[Create release] Attempt $attempt/$MAX_RETRIES..."
# 清空临时文件
> "$release_response_file" > "$release_response_file"
http_code=$(curl -sS -w "%{http_code}" -o "$release_response_file" -X POST "$api_url" \ http_code=$(curl -sS -w "%{http_code}" -o "$release_response_file" -X POST "$api_url" \
@@ -197,27 +203,30 @@ jobs:
echo "HTTP Status: $http_code" echo "HTTP Status: $http_code"
if [ "$http_code" = "201" ]; then if [ "$http_code" = "201" ]; then
# 提取第一个 id 字段的值,确保去除换行符
if command -v jq >/dev/null 2>&1; then if command -v jq >/dev/null 2>&1; then
release_id=$(echo "$response" | jq -r '.id' 2>/dev/null) release_id=$(echo "$response" | jq -r '.id' 2>/dev/null)
else else
release_id=$(echo "$response" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 | tr -d '\n\r') release_id=$(echo "$response" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 | tr -d '\n\r')
fi fi
echo "OK: Release created: id=$release_id" echo " Release created: id=$release_id"
elif [ "$http_code" = "409" ]; then elif [ "$http_code" = "409" ]; then
# HTTP 409 Conflict: Release 已存在,获取现有的 release_id
echo "Release already exists (HTTP 409), fetching existing release..." 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 "[]") existing=$(curl -sS "$api_url" -H "Authorization: token $GITEA_TOKEN" --connect-timeout 30 2>/dev/null || echo "[]")
# 使用 jq 解析 JSON如果没有 jq 则用 grep
if command -v jq >/dev/null 2>&1; then 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) release_id=$(echo "$existing" | jq -r ".[] | select(.tag_name==\"$RELEASE_TAG\") | .id" 2>/dev/null | head -1)
else else
release_id=$(echo "$existing" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 | tr -d '\n\r') release_id=$(echo "$existing" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 | tr -d '\n\r')
fi fi
if [ -n "$release_id" ]; then if [ -n "$release_id" ]; then
echo "OK: Found existing release: id=$release_id" echo " Found existing release: id=$release_id"
else else
echo "FAIL: Could not find existing release id" echo " Could not find existing release id"
fi fi
else else
echo "FAIL: Create release failed (HTTP $http_code)" echo "✗ Failed (HTTP $http_code)"
if [ $attempt -lt $MAX_RETRIES ]; then if [ $attempt -lt $MAX_RETRIES ]; then
echo "Retrying in ${RETRY_DELAY}s..." echo "Retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY sleep $RETRY_DELAY
@@ -227,15 +236,17 @@ jobs:
done done
if [ -z "$release_id" ]; then if [ -z "$release_id" ]; then
echo "FAIL: Failed to create or find release after $MAX_RETRIES attempts" echo " Failed to create/find release after $MAX_RETRIES attempts"
exit 1 exit 1
fi fi
# 上传 fat jar带重试
asset_name=$(basename "$jar_file") asset_name=$(basename "$jar_file")
echo "Uploading asset: $asset_name (size: $file_size bytes)" echo "Uploading asset: $asset_name (size: $file_size bytes)"
upload_url="$api_url/$release_id/assets?name=$asset_name" upload_url="$api_url/$release_id/assets?name=$asset_name"
echo "Upload URL: $upload_url" echo "Upload URL: $upload_url"
# 使用唯一临时文件避免跨 job 污染
asset_response_file=$(mktemp /tmp/asset_response_XXXXXX.json) asset_response_file=$(mktemp /tmp/asset_response_XXXXXX.json)
trap "rm -f $release_response_file $asset_response_file" EXIT trap "rm -f $release_response_file $asset_response_file" EXIT
@@ -244,8 +255,10 @@ jobs:
while [ $attempt -le $MAX_RETRIES ] && [ "$upload_success" = "false" ]; do while [ $attempt -le $MAX_RETRIES ] && [ "$upload_success" = "false" ]; do
echo "[Upload asset] Attempt $attempt/$MAX_RETRIES..." echo "[Upload asset] Attempt $attempt/$MAX_RETRIES..."
# 清空临时文件
> "$asset_response_file" > "$asset_response_file"
# Gitea API 要求使用 multipart/form-data 格式上传文件
http_code=$(curl -sS -w "%{http_code}" -o "$asset_response_file" -X POST "$upload_url" \ http_code=$(curl -sS -w "%{http_code}" -o "$asset_response_file" -X POST "$upload_url" \
-H "Authorization: token $GITEA_TOKEN" \ -H "Authorization: token $GITEA_TOKEN" \
--connect-timeout 30 \ --connect-timeout 30 \
@@ -254,9 +267,9 @@ jobs:
if [ "$http_code" = "201" ]; then if [ "$http_code" = "201" ]; then
upload_success=true upload_success=true
echo "OK: Successfully uploaded: $asset_name" echo " Successfully uploaded: $asset_name"
else else
echo "FAIL: Upload failed (HTTP $http_code)" echo " Upload failed (HTTP $http_code)"
cat "$asset_response_file" 2>/dev/null || true cat "$asset_response_file" 2>/dev/null || true
fi fi
@@ -268,7 +281,7 @@ jobs:
done done
if [ "$upload_success" = "false" ]; then if [ "$upload_success" = "false" ]; then
echo "FAIL: Failed to upload asset after $MAX_RETRIES attempts" echo " Failed to upload asset after $MAX_RETRIES attempts"
exit 1 exit 1
fi fi
@@ -293,6 +306,7 @@ jobs:
COMMIT_SHA: ${{ github.sha }} COMMIT_SHA: ${{ github.sha }}
REPO: ${{ github.repository }} REPO: ${{ github.repository }}
SERVER_URL: ${{ github.server_url }} SERVER_URL: ${{ github.server_url }}
# 通知配置(按需启用)
WEBHOOK_URL: ${{ vars.NOTIFY_WEBHOOK_URL }} WEBHOOK_URL: ${{ vars.NOTIFY_WEBHOOK_URL }}
run: | run: |
echo "=========================================" echo "========================================="
@@ -310,9 +324,11 @@ jobs:
echo "" echo ""
echo "=========================================" echo "========================================="
# 发送 Webhook 通知(钉钉/企业微信/Slack 等)
if [ -n "$WEBHOOK_URL" ]; then if [ -n "$WEBHOOK_URL" ]; then
message="CI 部署失败\n\nPR: #$PR_NUMBER - $PR_TITLE\n分支: $SOURCE_BRANCH\n提交者: $AUTHOR\n\n请检查并决定:\n- 重试 CI\n- 回滚合并" message="🚨 CI 部署失败\n\nPR: #$PR_NUMBER - $PR_TITLE\n分支: $SOURCE_BRANCH\n提交者: $AUTHOR\n\n请检查并决定:\n 重试 CI\n 回滚合并"
# 通用 JSON 格式(适配大多数 Webhook
payload=$(cat <<EOF payload=$(cat <<EOF
{ {
"msgtype": "text", "msgtype": "text",
@@ -327,7 +343,7 @@ jobs:
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d "$payload" || echo "Warning: Failed to send notification" -d "$payload" || echo "Warning: Failed to send notification"
echo "OK: Notification sent" echo " Notification sent"
else else
echo "Note: Set vars.NOTIFY_WEBHOOK_URL to enable webhook notifications" echo "Note: Set vars.NOTIFY_WEBHOOK_URL to enable webhook notifications"
fi fi

View File

@@ -11,7 +11,7 @@
<groupId>com.imyeyu.timiserverapi</groupId> <groupId>com.imyeyu.timiserverapi</groupId>
<artifactId>TimiServerAPI</artifactId> <artifactId>TimiServerAPI</artifactId>
<version>1.0.1</version> <version>1.0.0</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>TimiServerAPI</name> <name>TimiServerAPI</name>
<description>imyeyu.com API</description> <description>imyeyu.com API</description>
@@ -128,15 +128,10 @@
</repositories> </repositories>
<dependencies> <dependencies>
<dependency>
<groupId>com.imyeyu.java</groupId>
<artifactId>timi-java</artifactId>
<version>0.0.3</version>
</dependency>
<dependency> <dependency>
<groupId>com.imyeyu.spring</groupId> <groupId>com.imyeyu.spring</groupId>
<artifactId>timi-spring</artifactId> <artifactId>timi-spring</artifactId>
<version>0.0.12</version> <version>0.0.10</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.imyeyu.network</groupId> <groupId>com.imyeyu.network</groupId>

View File

@@ -1,6 +1,6 @@
package com.imyeyu.api.annotation; package com.imyeyu.api.annotation;
import com.imyeyu.api.modules.common.entity.Setting; import com.imyeyu.api.modules.common.bean.SettingKey;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
@@ -17,34 +17,8 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface EnableSetting { public @interface EnableSetting {
/** /** @return 配置键 */
* SettingKey value();
*
* @author 夜雨
* @since 2026-04-27 14:45
*/
enum Logic {
AND,
OR
}
Setting.Module.User[] user() default {};
Setting.Module.Comment[] comment() default {};
Setting.Module.ForeverMC[] foreverMC() default {};
Setting.Module.Music[] music() default {};
Setting.Module.Journal[] journal() default {};
Setting.Module.TempFile[] tempFile() default {};
Setting.Module.System[] system() default {};
Logic logic() default Logic.AND;
/** @return 未启用配置时响应消息语言映射键 */ /** @return 未启用配置时响应消息语言映射键 */
String message() default "service.offline"; String message() default "service.offline";

View File

@@ -1,20 +1,17 @@
package com.imyeyu.api.annotation; package com.imyeyu.api.annotation;
import com.imyeyu.api.modules.common.service.SettingService;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.api.modules.common.service.SettingService;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/** /**
* 启用配置注解处理器 * 启用配置注解处理器
* *
@@ -27,28 +24,16 @@ public class EnableSettingInterceptor implements HandlerInterceptor {
private final SettingService service; private final SettingService service;
public boolean preHandle(@NonNull HttpServletRequest req, @NonNull HttpServletResponse resp, @NonNull Object handler) throws InvocationTargetException, IllegalAccessException { public boolean preHandle(@NonNull HttpServletRequest req, @NonNull HttpServletResponse resp, @NonNull Object handler) {
if (handler instanceof HandlerMethod handlerMethod) { if (handler instanceof HandlerMethod handlerMethod) {
EnableSetting annotation = handlerMethod.getMethodAnnotation(EnableSetting.class); EnableSetting annotation = handlerMethod.getMethodAnnotation(EnableSetting.class);
if (annotation == null) { if (annotation == null) {
return true; return true;
} }
EnableSetting.Logic logic = annotation.logic(); if (service.is(annotation.value())) {
List<Boolean> valueList = new ArrayList<>(); return true;
for (Method method : annotation.getClass().getMethods()) {
if (method.getName().equals("message")) {
continue;
}
Enum<?>[] enums = (Enum<?>[]) method.invoke(annotation);
for (Enum<?> anEnum : enums) {
valueList.add(service.is(anEnum));
}
}
if (logic == EnableSetting.Logic.AND) {
return valueList.stream().allMatch(Boolean::booleanValue);
} else {
return valueList.stream().anyMatch(Boolean::booleanValue);
} }
throw new TimiException(TimiCode.ERROR_SERVICE_OFF, annotation.message());
} }
return true; return true;
} }

View File

@@ -1,12 +1,12 @@
package com.imyeyu.api.modules.blog.util; package com.imyeyu.api.modules.blog.util;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.api.config.RedisConfig; import com.imyeyu.api.config.RedisConfig;
import com.imyeyu.api.modules.common.entity.Setting; import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.entity.User; import com.imyeyu.api.modules.common.entity.User;
import com.imyeyu.api.modules.common.service.SettingService; import com.imyeyu.api.modules.common.service.SettingService;
import com.imyeyu.api.modules.common.service.UserService; import com.imyeyu.api.modules.common.service.UserService;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.TimiSpring; import com.imyeyu.spring.TimiSpring;
import com.imyeyu.spring.util.Redis; import com.imyeyu.spring.util.Redis;
import com.imyeyu.spring.util.RedisSerializers; import com.imyeyu.spring.util.RedisSerializers;
@@ -24,7 +24,7 @@ import java.util.Objects;
/** /**
* Redis 令牌缓存 * Redis 令牌缓存
* *
* <p>一级缓存 Session二级缓存 Redis有效期为 {@link com.imyeyu.api.modules.common.entity.Setting.Module.User#TOKEN_TTL} 天,每次触发 * <p>一级缓存 Session二级缓存 Redis有效期为 {@link SettingKey#TTL_USER_TOKEN} 天,每次触发
* 二级缓存获取时会刷新这个时间,即指定天数内不再访问则视为登出 * 二级缓存获取时会刷新这个时间,即指定天数内不再访问则视为登出
* *
* @author 夜雨 * @author 夜雨
@@ -47,12 +47,12 @@ public class UserToken {
} }
public Long set(String token, Long userId) { public Long set(String token, Long userId) {
long ttl = settingService.getAsTime(Setting.Module.User.TOKEN_TTL); long ttl = Time.D * settingService.getAsInt(SettingKey.TTL_USER_TOKEN);
// 会话 // 会话
TimiSpring.setSessionAttr(token, userId); TimiSpring.setSessionAttr(token, userId);
// 跨站 Cookie // 跨站 Cookie
Cookie cookie = Objects.requireNonNullElse(TimiSpring.getCookie("Token"), new Cookie("Token", token)); Cookie cookie = Objects.requireNonNullElse(TimiSpring.getCookie("Token"), new Cookie("Token", token));
cookie.setDomain(settingService.getAsString(Setting.Module.Domain.ROOT)); cookie.setDomain(settingService.getAsString(SettingKey.DOMAIN_ROOT));
cookie.setPath("/"); cookie.setPath("/");
cookie.setSecure(true); cookie.setSecure(true);
cookie.setMaxAge((int) (ttl / 1000)); cookie.setMaxAge((int) (ttl / 1000));
@@ -136,7 +136,7 @@ public class UserToken {
TimiSpring.removeSessionAttr(token); TimiSpring.removeSessionAttr(token);
// 清除跨站 Cookie // 清除跨站 Cookie
Cookie cookie = new Cookie("Token", "DIED"); Cookie cookie = new Cookie("Token", "DIED");
cookie.setDomain(settingService.getAsString(Setting.Module.Domain.ROOT)); cookie.setDomain(settingService.getAsString(SettingKey.DOMAIN_ROOT));
cookie.setPath("/"); cookie.setPath("/");
cookie.setSecure(true); cookie.setSecure(true);
cookie.setMaxAge(0); cookie.setMaxAge(0);

View File

@@ -0,0 +1,191 @@
package com.imyeyu.api.modules.common.bean;
/**
* 系统设置
*
* @author 夜雨
* @since 2021-07-20 21:46
*/
public enum SettingKey {
// ---------- 通用 ----------
RUN_ENV,
PUBLIC_RESOURCES,
/** 启用注册 */
ENABLE_REGISTER,
/** 启用登录 */
ENABLE_LOGIN,
/** 启用评论 */
ENABLE_COMMENT,
/** 启用测试 */
ENABLE_DEBUG,
/** 启用账号数据更新User 和 UserProfile */
ENABLE_USER_UPDATE,
/** 启用灰色滤镜 */
ENABLE_GRAY_FILTER,
TEMP_FILE_PATH,
// ---------- ICP 备案号 ----------
ICP_IMYEYU_COM,
// ---------- 域名 ----------
DOMAIN_ROOT,
DOMAIN_API,
DOMAIN_GIT,
DOMAIN_BLOG,
DOMAIN_SPACE,
DOMAIN_RESOURCE,
DOMAIN_DOWNLOAD,
DOMAIN_FOREVER_MC,
// ---------- ForeverMC ----------
/** 启用登录服务 */
FMC_PLAYER_LOGIN_ENABLE,
/** 最多绑定玩家数量 */
FMC_MAX_BIND,
/** 闪烁标语 */
FMC_SPLASHES,
/** 启动器背景 */
FMC_BG,
FMC_BGM,
FMC_BG_SWIPER,
/** JRE 列表 */
FMC_JRE,
/** 辅助登录模组 */
FMC_LOGIN_FABRIC,
/** 启用图片地图上传 */
FMC_ENABLE_IMAGE_MAP_UPLOAD,
/** 玩家登录令牌有效期(天) */
FMC_PLAYER_LOGIN_TOKEN_TTL,
/** 服务器与数据中心的通信令牌 */
FMC_SERVER_TOKEN,
// ---------- 生存时间 ----------
TTL_USER_TOKEN,
TTL_SETTING,
TTL_MULTILINGUAL,
// ---------- 多语言翻译 ----------
MULTILINGUAL_TRANSLATE_API,
MULTILINGUAL_TRANSLATE_APP_ID,
MULTILINGUAL_TRANSLATE_KEY,
// ---------- 账单 ----------
BILL_API_TOKEN,
// ---------- Git ----------
GIT_API,
GIT_ABOUT_ARTICLE,
GIT_REPO_PATH,
// ---------- 远程音乐 ----------
MUSIC_MAX_FRAME_LENGTH,
MUSIC_PLAYER_PORT,
MUSIC_PLAYER_IP,
MUSIC_CONTROLLER_PORT,
MUSIC_CONTROLLER_IP,
MUSIC_CONTROLLER_URI,
// ---------- 日记 ----------
JOURNAL_KEY,
JOURNAL_APP_ID,
JOURNAL_APP_SECRET,
JOURNAL_MEMO,
JOURNAL_OPEN_ID_WHITE_LIST,
// ---------- 临时文件 ----------
/** 临时文件最小缓存时间 */
TEMP_FILE_TTL_MIN,
/** 临时文件最长缓存时间 */
TEMP_FILE_TTL_MAX,
/** 临时文件默认缓存时间 */
TEMP_FILE_TTL_DEFAULT,
/** 已过期的临时文件保留时间 */
TEMP_FILE_RESIDUE_TIME,
/** 每个 IP 限制有效临时文件容量 */
TEMP_FILE_LIMIT,
// ---------- 系统 ----------
SYSTEM_FILE_BASE,
SYSTEM_FILE_TYPE,
SYSTEM_FILE_SYNC,
/** 文件过滤(通过密钥类型) */
SYSTEM_FILE_FILTER,
SYSTEM_STATUS_RATE,
SYSTEM_STATUS_LIMIT,
SYSTEM_STATUS_NETWORK_MAC,
SYSTEM_TERMINAL_TTL,
SYSTEM_TERMINAL_FILTERS,
/** 一般密钥 */
SYSTEM_API_KEY,
/** 超级密钥 */
SYSTEM_API_SUPER_KEY,
SYSTEM_REBOOT_COMMAND,
}

View File

@@ -1,35 +0,0 @@
package com.imyeyu.api.modules.common.bean;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
*
*
* @author 夜雨
* @since 2026-04-24 00:59
*/
@Getter
@AllArgsConstructor
public enum SettingModule {
COMMON("通用"),
DOMAIN("域名"),
FOREVER_MC("Forever MC"),
MULTILINGUAL("多语言环境"),
GIT("Git"),
MUSIC("远程音乐"),
JOURNAL("糕雨日记"),
TEMP_FILE("临时文件"),
SYSTEM("系统");
final String title;
}

View File

@@ -3,9 +3,9 @@ package com.imyeyu.api.modules.common.controller;
import com.imyeyu.api.annotation.CaptchaValid; import com.imyeyu.api.annotation.CaptchaValid;
import com.imyeyu.api.annotation.EnableSetting; import com.imyeyu.api.annotation.EnableSetting;
import com.imyeyu.api.bean.CaptchaFrom; import com.imyeyu.api.bean.CaptchaFrom;
import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.entity.Comment; import com.imyeyu.api.modules.common.entity.Comment;
import com.imyeyu.api.modules.common.entity.CommentReply; import com.imyeyu.api.modules.common.entity.CommentReply;
import com.imyeyu.api.modules.common.entity.Setting;
import com.imyeyu.api.modules.common.service.CommentReplyService; import com.imyeyu.api.modules.common.service.CommentReplyService;
import com.imyeyu.api.modules.common.service.CommentService; import com.imyeyu.api.modules.common.service.CommentService;
import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.AOPLog;
@@ -39,7 +39,7 @@ public class CommentController {
@AOPLog @AOPLog
@CaptchaValid(CaptchaFrom.COMMENT) @CaptchaValid(CaptchaFrom.COMMENT)
@EnableSetting(comment = Setting.Module.Comment.ENABLE, message = "comment.off_service") @EnableSetting(value = SettingKey.ENABLE_COMMENT, message = "comment.off_service")
@RequestRateLimit @RequestRateLimit
@PostMapping("/create") @PostMapping("/create")
public void create(@Valid @RequestBody CaptchaData<Comment> captchaData) { public void create(@Valid @RequestBody CaptchaData<Comment> captchaData) {
@@ -59,7 +59,7 @@ public class CommentController {
*/ */
@AOPLog @AOPLog
@CaptchaValid(CaptchaFrom.COMMENT_REPLY) @CaptchaValid(CaptchaFrom.COMMENT_REPLY)
@EnableSetting(comment = Setting.Module.Comment.ENABLE, message = "comment.off_service") @EnableSetting(value = SettingKey.ENABLE_COMMENT, message = "comment.off_service")
@RequestRateLimit @RequestRateLimit
@PostMapping("/reply/create") @PostMapping("/reply/create")
public void createReply(@Valid @RequestBody CaptchaData<CommentReply> request) { public void createReply(@Valid @RequestBody CaptchaData<CommentReply> request) {

View File

@@ -1,8 +1,10 @@
package com.imyeyu.api.modules.common.controller; package com.imyeyu.api.modules.common.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper; 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.entity.Attachment; import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.entity.Setting; import com.imyeyu.api.modules.common.entity.Setting;
import com.imyeyu.api.modules.common.entity.Task; import com.imyeyu.api.modules.common.entity.Task;
@@ -45,8 +47,8 @@ import lombok.Cleanup;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.tika.Tika; import org.apache.tika.Tika;
import org.springframework.data.mongodb.gridfs.GridFsResource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.data.mongodb.gridfs.GridFsResource;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
@@ -67,9 +69,9 @@ import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors;
/** /**
* 系统接口 * 系统接口
@@ -99,6 +101,22 @@ public class CommonController {
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() {
@@ -237,40 +255,64 @@ public class CommonController {
} }
@RequestRateLimit @RequestRateLimit
@GetMapping("/setting") @GetMapping("/setting/{key}")
public String settingByKey(@RequestParam String module, @RequestParam String key) throws ClassNotFoundException { public String settingByKey(@PathVariable("key") String key, @RequestParam(value = "as", required = false) Setting.Type asType) {
Setting setting = settingService.getByKey(SettingService.fromModuleKey(module, key)); Setting setting = settingService.getByKey(SettingKey.valueOf(key.toUpperCase()));
if (setting.isPrivate()) { if (setting.isPrivate()) {
throw new TimiException(TimiCode.PERMISSION_ERROR); throw new TimiException(TimiCode.PERMISSION_ERROR);
} }
return setting.getValue(); String result = setting.getValue();
if (asType == null) {
return result;
}
switch (asType) {
case JSON -> {
if (setting.getType() == Setting.Type.YAML) {
Map<String, Object> obj = yaml.load(setting.getValue());
result = writeJson(obj);
}
}
case YAML -> {
if (setting.getType() == Setting.Type.JSON) {
Map<String, Object> obj = readJsonMap(setting.getValue());
result = yaml.dump(obj);
}
}
}
return result;
} }
@RequestRateLimit @RequestRateLimit
@PostMapping("/setting/map") @PostMapping("/setting/map")
public Map<String, Map<String, String>> mapSettingByKeys(@RequestBody Map<String, List<String>> moduleKeyMap) { public Map<SettingKey, String> mapSettingByKeys(@RequestBody Map<SettingKey, Map<String, Object>> settingMap) {
List<Enum<?>> keyList = new ArrayList<>(); List<Setting> result = settingService.listByKeys(new ArrayList<>(settingMap.keySet()));
moduleKeyMap.forEach((k, v) -> { for (int i = 0; i < result.size(); i++) {
try { Setting setting = result.get(i);
for (String key : v) {
keyList.add(SettingService.fromModuleKey(k, key));
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
});
Map<String, Map<String, String>> result = new HashMap<>();
List<Setting> settingList = settingService.listByKeys(keyList);
for (Setting setting : settingList) {
if (setting.isPrivate()) { if (setting.isPrivate()) {
throw new TimiException(TimiCode.PERMISSION_ERROR); throw new TimiException(TimiCode.PERMISSION_ERROR);
} }
if (!result.containsKey(setting.getModule())) { Map<String, Object> args = settingMap.get(setting.getKey());
result.put(setting.getKey(), new HashMap<>()); if (args == null) {
continue;
} }
result.get(setting.getModule()).put(setting.getKey(), setting.getValue()); if (args.containsKey("as")) {
switch (Ref.toType(Setting.Type.class, args.get("as").toString())) {
case JSON -> {
if (setting.getType() == Setting.Type.YAML) {
Map<String, Object> obj = new Yaml().load(setting.getValue());
setting.setValue(writeJson(obj));
} }
return result; }
case YAML -> {
if (setting.getType() == Setting.Type.JSON) {
Map<String, Object> obj = readJsonMap(setting.getValue());
setting.setValue(new Yaml().dump(obj));
}
}
}
}
}
return result.stream().collect(Collectors.toMap(Setting::getKey, Setting::getValue));
} }
@AOPLog @AOPLog

View File

@@ -3,9 +3,9 @@ package com.imyeyu.api.modules.common.controller;
import com.imyeyu.api.annotation.CaptchaValid; import com.imyeyu.api.annotation.CaptchaValid;
import com.imyeyu.api.annotation.EnableSetting; import com.imyeyu.api.annotation.EnableSetting;
import com.imyeyu.api.bean.CaptchaFrom; import com.imyeyu.api.bean.CaptchaFrom;
import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.entity.Comment; import com.imyeyu.api.modules.common.entity.Comment;
import com.imyeyu.api.modules.common.entity.CommentReply; import com.imyeyu.api.modules.common.entity.CommentReply;
import com.imyeyu.api.modules.common.entity.Setting;
import com.imyeyu.api.modules.common.entity.UserConfig; import com.imyeyu.api.modules.common.entity.UserConfig;
import com.imyeyu.api.modules.common.entity.UserPrivacy; import com.imyeyu.api.modules.common.entity.UserPrivacy;
import com.imyeyu.api.modules.common.service.CommentReplyService; import com.imyeyu.api.modules.common.service.CommentReplyService;
@@ -71,7 +71,7 @@ public class UserController implements TimiJava {
*/ */
@AOPLog @AOPLog
@CaptchaValid(CaptchaFrom.REGISTER) @CaptchaValid(CaptchaFrom.REGISTER)
@EnableSetting(user = Setting.Module.User.ENABLE_REGISTER, message = "user.register.off_service") @EnableSetting(value = SettingKey.ENABLE_REGISTER, message = "user.register.off_service")
@RequestRateLimit(value = 1, lifeCycle = 60) @RequestRateLimit(value = 1, lifeCycle = 60)
@PostMapping("/register") @PostMapping("/register")
public LoginResponse register(@Valid @RequestBody CaptchaData<RegisterRequest> request) { public LoginResponse register(@Valid @RequestBody CaptchaData<RegisterRequest> request) {
@@ -86,7 +86,7 @@ public class UserController implements TimiJava {
*/ */
@AOPLog @AOPLog
@CaptchaValid(CaptchaFrom.LOGIN) @CaptchaValid(CaptchaFrom.LOGIN)
@EnableSetting(user = Setting.Module.User.ENABLE_LOGIN, message = "user.login.off_service") @EnableSetting(value = SettingKey.ENABLE_LOGIN, message = "user.login.off_service")
@RequestRateLimit @RequestRateLimit
@PostMapping("/login") @PostMapping("/login")
public LoginResponse login(@Valid @RequestBody CaptchaData<LoginRequest> request) { public LoginResponse login(@Valid @RequestBody CaptchaData<LoginRequest> request) {
@@ -99,7 +99,7 @@ public class UserController implements TimiJava {
* @return 登录数据 * @return 登录数据
*/ */
@AOPLog @AOPLog
@EnableSetting(user = Setting.Module.User.ENABLE_LOGIN, message = "user.login.off_service") @EnableSetting(value = SettingKey.ENABLE_LOGIN, message = "user.login.off_service")
@RequestRateLimit @RequestRateLimit
@PostMapping("/login/token") @PostMapping("/login/token")
public LoginResponse login4Token() { public LoginResponse login4Token() {
@@ -207,7 +207,7 @@ public class UserController implements TimiJava {
*/ */
@AOPLog @AOPLog
@RequiredToken @RequiredToken
@EnableSetting(user = Setting.Module.User.ENABLE_PROFILE_UPDATE, message = "user.data.off_service") @EnableSetting(value = SettingKey.ENABLE_USER_UPDATE, message = "user.data.off_service")
@RequestRateLimit @RequestRateLimit
@PostMapping("/profile/update") @PostMapping("/profile/update")
public void updateProfile(@Valid UserRequest data) { public void updateProfile(@Valid UserRequest data) {

View File

@@ -1,13 +1,10 @@
package com.imyeyu.api.modules.common.entity; package com.imyeyu.api.modules.common.entity;
import com.fasterxml.jackson.databind.JsonNode; import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.spring.annotation.table.Transient; import com.imyeyu.spring.annotation.table.Id;
import com.imyeyu.spring.entity.Creatable; import com.imyeyu.spring.entity.Creatable;
import com.imyeyu.spring.entity.Entity;
import com.imyeyu.spring.entity.Updatable; import com.imyeyu.spring.entity.Updatable;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/** /**
* 系统配置 * 系统配置
@@ -16,9 +13,7 @@ import lombok.NoArgsConstructor;
* @since 2021-07-20 21:46 * @since 2021-07-20 21:46
*/ */
@Data @Data
@NoArgsConstructor public class Setting implements Creatable, Updatable {
@EqualsAndHashCode(callSuper = true)
public class Setting extends Entity implements Creatable, Updatable {
/** /**
* *
@@ -26,377 +21,31 @@ public class Setting extends Entity implements Creatable, Updatable {
* @author 夜雨 * @author 夜雨
* @since 2025-01-10 17:08 * @since 2025-01-10 17:08
*/ */
public enum ValueType { public enum Type {
/** 字符串 */
STRING,
/** 单选 */
SELECT_RADIO,
/** 多选 */
SELECT_CHECKBOX,
/** 整数 */
INTEGER, INTEGER,
/** 颜色 */ STRING,
COLOR,
/** 浮点数 */ JSON,
FLOAT,
/** 日期 */ YAML,
DATE,
/** 日期时间 */
DATETIME,
/** 时间 */
TIME,
/** 时长 */
DURATION,
/** 布尔值 */
BOOLEAN,
/** JSON 列表 */
JSON_LIST,
/** JSON 对象 */
JSON_OBJECT
} }
private String module; @Id
private SettingKey key;
private String key;
private String value; private String value;
private ValueType valueType; private Type type;
private JsonNode valueArgs;
private boolean isPrivate; private boolean isPrivate;
@Transient private Long createdAt;
private Enum<?> keyRaw;
public Setting(Enum<?> key) { private Long updatedAt;
setKeyRaw(key);
}
public Setting(Enum<?> key, Object value, ValueType valueType) {
setKeyRaw(key);
this.value = value.toString();
this.valueType = valueType;
}
public void setKeyRaw(Enum<?> key) {
this.keyRaw = key;
this.module = key.getClass().getSimpleName().toUpperCase();
this.key = key.toString();
}
public boolean isPublic() { public boolean isPublic() {
return !isPrivate; return !isPrivate;
} }
public boolean is(Enum<?> anEnum) {
return anEnum.getClass().getSimpleName().toUpperCase().equals(module) && anEnum.toString().equals(key);
}
/**
*
*
* @author 夜雨
* @since 2026-04-24 14:23
*/
public static final class Module {
/**
*
*
* @author 夜雨
* @since 2026-04-24 15:39
*/
public enum Common {
/** 微信收款码 */
WECHAT_RECEIVE_QR_CODE
}
/**
* 域名
*
* @author 夜雨
* @since 2026-04-26 17:18
*/
public enum Domain {
ROOT,
API,
GIT,
BLOG,
SPACE,
FOREVER_MC
}
/**
*
*
* @author 夜雨
* @since 2026-04-24 15:14
*/
public enum Blog {
}
/**
*
*
* @author 夜雨
* @since 2026-04-24 15:03
*/
public enum User {
/** 启用登录 */
ENABLE_LOGIN,
/** 启用注册 */
ENABLE_REGISTER,
/** 允许数据更新 */
ENABLE_PROFILE_UPDATE,
/** 默认头像 */
DEFAULT_AVATAR,
/** 默认封面 */
DEFAULT_WRAPPER,
/** 令牌有效期 */
TOKEN_TTL
}
/**
*
*
* @author 夜雨
* @since 2026-04-24 15:21
*/
public enum Comment {
/** 启用服务 */
ENABLE
}
/**
*
*
* @author 夜雨
* @since 2026-04-24 15:03
*/
public enum ICP {
IMYEYU_COM
}
/**
*
*
* @author 夜雨
* @since 2026-04-24 15:01
*/
public enum ForeverMC {
PLAYER_BIND_MAX,
LOGIN_TOKEN_TTL
}
/**
*
*
* @author 夜雨
* @since 2026-04-24 14:57
*/
public enum Multilingual {
/** 翻译 API */
TRANSLATE_API,
/** 翻译 AppId */
TRANSLATE_APP_ID,
/** 翻译密钥 */
TRANSLATE_KEY,
/** 翻译计划任务表达式 */
TRANSLATE_CORN,
/** 缓存时长 */
CACHE_TTL
}
/**
*
*
* @author 夜雨
* @since 2026-04-24 14:55
*/
public enum Gitea {
/** 接口地址 */
API,
/** 关于页面所属文章 ID */
ABOUT_ARTICLE_ID,
/** 仓库文件地址 */
REPOSITORY_PATH
}
/**
*
*
* @author 夜雨
* @since 2026-04-24 14:52
*/
public enum Music {
/** 启用服务 */
ENABLE,
/** 通信消息最大帧长度 */
MAX_FRAME_LENGTH,
/** 受控端 IP */
PLAYER_IP,
/** 受控端端口 */
PLAYER_PORT,
/** 控制端 IP */
CONTROLLER_IP,
/** 控制端端口 */
CONTROLLER_PORT,
/** 控制端入口地址 */
CONTROLLER_URI,
}
/**
*
*
* @author 夜雨
* @since 2026-04-24 14:49
*/
public enum Journal {
/** 启用服务 */
ENABLE,
/** 访问密钥 */
KEY,
/** 微信小程序 AppId */
APP_ID,
/** 微信小程序 AppSecret */
APP_SECRET,
/** 备忘录内容 */
MEMO,
/** 高权限访问 OpenID 白名单(应付审核) */
OPEN_ID_WHITE_LIST
}
/**
*
*
* @author 夜雨
* @since 2026-04-24 14:47
*/
public enum TempFile {
/** 启用服务 */
ENABLE,
/** 最小缓存时长 */
TTL_MIN,
/** 默认缓存时长 */
TTL_DEFAULT,
/** 最大缓存时长 */
TTL_MAX,
/** 已过期文件残留时长 */
RESIDUE_TTL,
/** 每个 IP 限制有效临时文件容量 */
LIMIT_SIZE_OF_IP
}
/**
*
*
* @author 夜雨
* @since 2026-04-27 14:35
*/
public enum FileSync {
ENABLE,
CRON,
}
/**
*
*
* @author 夜雨
* @since 2026-04-24 14:23
*/
public enum System {
/** 一般密钥 */
API_KEY,
/** 超级密钥 */
API_KEY_SUPER,
/** 状态采集数据量 */
STATUS_LIMIT,
/** 状态采集网卡 */
STATUS_NETWORK_MAC,
/** 状态采集频率 */
STATUS_RATE,
/** 终端指令过滤 */
TERMINAL_FILTERS,
/** 终端会话有效时长 */
TERMINAL_TTL,
/** 文件系统基准路径 */
FILE_BASE_PATH,
/** 设置缓存时长 */
SETTING_TTL,
/** 哀悼滤镜 */
ENABLE_MOURN_FILTER,
}
}
} }

View File

@@ -1,5 +1,6 @@
package com.imyeyu.api.modules.common.mapper; package com.imyeyu.api.modules.common.mapper;
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.spring.mapper.BaseMapper; import com.imyeyu.spring.mapper.BaseMapper;
import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Select;
@@ -12,7 +13,10 @@ import java.util.List;
* @author 夜雨 * @author 夜雨
* @since 2021-07-20 22:26 * @since 2021-07-20 22:26
*/ */
public interface SettingMapper extends BaseMapper<Setting, Long> { public interface SettingMapper extends BaseMapper<Setting, String> {
@Select("SELECT * FROM `setting` WHERE `key` = #{key}")
Setting selectByKey(SettingKey key);
@Select("SELECT * FROM `setting`") @Select("SELECT * FROM `setting`")
List<Setting> listAll(); List<Setting> listAll();

View File

@@ -4,14 +4,13 @@ import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.entity.Setting; import com.imyeyu.api.modules.common.entity.Setting;
import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.java.ref.Ref; import com.imyeyu.spring.service.UpdatableService;
import com.imyeyu.spring.service.BaseService;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Set;
/** /**
* 系统配置服务 * 系统配置服务
@@ -19,21 +18,15 @@ import java.util.Set;
* @author 夜雨 * @author 夜雨
* @since 2021-07-20 22:06 * @since 2021-07-20 22:06
*/ */
public interface SettingService extends BaseService<Setting, Long> { public interface SettingService extends UpdatableService<Setting> {
@SuppressWarnings("unchecked") default List<Setting> listByKeys(SettingKey... keys) {
static <T extends Enum<?>> T fromModuleKey(String module, String key) throws ClassNotFoundException {
Class<?> moduleClass = Class.forName(String.format("com.imyeyu.api.modules.common.entity.Setting$Module$%s", module));
return Ref.toType((Class<T>) moduleClass, key);
}
default List<Setting> listByKeys(Enum<?>... keys) {
return listByKeys(Arrays.asList(keys)); return listByKeys(Arrays.asList(keys));
} }
List<Setting> listByKeys(List<Enum<?>> keyList); List<Setting> listByKeys(List<SettingKey> keyList);
Setting getByKey(Enum<?> key); Setting getByKey(SettingKey key);
/** /**
* 获取指定类型配置值字符串 * 获取指定类型配置值字符串
@@ -41,15 +34,13 @@ public interface SettingService extends BaseService<Setting, Long> {
* @param key 键 * @param key 键
* @return 配置值 * @return 配置值
*/ */
String getAsString(Enum<?> key); String getAsString(SettingKey key);
int getAsInt(Enum<?> key); int getAsInt(SettingKey key);
long getAsLong(Enum<?> key); long getAsLong(SettingKey key);
double getAsDouble(Enum<?> key); double getAsDouble(SettingKey key);
long getAsTime(Enum<?> key);
/** /**
* 获取为布尔值 * 获取为布尔值
@@ -58,7 +49,7 @@ public interface SettingService extends BaseService<Setting, Long> {
* @return 配置值 * @return 配置值
* @throws TimiException 服务异常 * @throws TimiException 服务异常
*/ */
boolean is(Enum<?> key); boolean is(SettingKey key);
/** /**
* 获取为布尔值,并取反 * 获取为布尔值,并取反
@@ -67,23 +58,23 @@ public interface SettingService extends BaseService<Setting, Long> {
* @return 配置值 * @return 配置值
* @throws TimiException 服务异常 * @throws TimiException 服务异常
*/ */
boolean not(Enum<?> key); boolean not(SettingKey key);
JsonNode getAsJsonNode(Enum<?> key); JsonNode getAsJsonNode(SettingKey key);
ObjectNode getAsJsonObject(Enum<?> key); ObjectNode getAsJsonObject(SettingKey key);
ArrayNode getAsArrayNode(Enum<?> key); ArrayNode getAsArrayNode(SettingKey key);
Set<String> getAsSet(Enum<?> key); <T> T fromJson(SettingKey key, Class<T> clazz);
List<String> getAsList(Enum<?> key); <T> T fromJson(SettingKey key, TypeReference<T> typeReference);
<T> T fromJson(Enum<?> key, Class<T> clazz); <T> T fromYaml(SettingKey key, Class<T> clazz);
<T> T fromJson(Enum<?> key, TypeReference<T> typeReference);
<T> T fromYaml(Enum<?> key, Class<T> clazz);
List<Setting> listAll(); List<Setting> listAll();
void clearCache(SettingKey key);
void flushCache();
} }

View File

@@ -1,10 +1,10 @@
package com.imyeyu.api.modules.common.service.implement; package com.imyeyu.api.modules.common.service.implement;
import com.imyeyu.api.modules.common.entity.Setting;
import com.imyeyu.java.TimiJava; import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.Language; import com.imyeyu.java.bean.Language;
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.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.mapper.MultilingualMapper; import com.imyeyu.api.modules.common.mapper.MultilingualMapper;
import com.imyeyu.api.modules.common.service.MultilingualService; import com.imyeyu.api.modules.common.service.MultilingualService;
@@ -66,7 +66,7 @@ public class MultilingualServiceImplement extends AbstractEntityService<Multilin
if (result == null) { if (result == null) {
throw new TimiException(TimiCode.RESULT_NULL).msgKey("TODO not found language"); throw new TimiException(TimiCode.RESULT_NULL).msgKey("TODO not found language");
} }
redisLanguage.set(id, result, settingService.getAsTime(Setting.Module.Multilingual.CACHE_TTL)); redisLanguage.set(id, result, Time.D * settingService.getAsInt(SettingKey.TTL_MULTILINGUAL));
} }
return result.getValue(language); return result.getValue(language);
} }
@@ -80,7 +80,7 @@ public class MultilingualServiceImplement extends AbstractEntityService<Multilin
log.warn("not found language for key: {}", key); log.warn("not found language for key: {}", key);
return key; return key;
} }
long ttl = settingService.getAsTime(Setting.Module.Multilingual.CACHE_TTL); long ttl = Time.D * settingService.getAsInt(SettingKey.TTL_MULTILINGUAL);
redisLanguage.set(result.getId(), result, ttl); redisLanguage.set(result.getId(), result, ttl);
redisLanguageMap.set(result.getKey(), result.getId(), ttl); redisLanguageMap.set(result.getKey(), result.getId(), ttl);
return result.getValue(language); return result.getValue(language);

View File

@@ -6,27 +6,24 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.entity.Setting; import com.imyeyu.api.modules.common.entity.Setting;
import com.imyeyu.api.modules.common.mapper.SettingMapper; import com.imyeyu.api.modules.common.mapper.SettingMapper;
import com.imyeyu.api.modules.common.service.SettingService; import com.imyeyu.api.modules.common.service.SettingService;
import com.imyeyu.java.TimiJava; import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.CallbackArg;
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.spring.mapper.BaseMapper; import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.spring.service.AbstractEntityService; import com.imyeyu.spring.service.AbstractEntityService;
import com.imyeyu.spring.util.Redis; import com.imyeyu.spring.util.Redis;
import com.imyeyu.utils.Time; import com.imyeyu.utils.Time;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.Yaml;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
/** /**
* 系统配置服务实现 * 系统配置服务实现
@@ -37,57 +34,30 @@ import java.util.Set;
@Slf4j @Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class SettingServiceImplement extends AbstractEntityService<Setting, Long> implements SettingService { public class SettingServiceImplement extends AbstractEntityService<Setting, String> implements SettingService {
/** 缓存键模板参数module, key */
private static final String CACHE_KEY_TMPL = "%s:%s";
private final SettingMapper mapper; private final SettingMapper mapper;
private final Redis<String, String> redisSetting; private final Redis<String, String> redisSetting;
private final ObjectMapper jackson; private final ObjectMapper jackson;
@PostConstruct
private void initialize() {
CallbackArg<Setting> createIfNotExist = setting -> {
Setting dbSetting = mapper.selectByExample(new Setting(setting.getKeyRaw()));
if (dbSetting == null) {
create(setting);
}
};
createIfNotExist.handler(new Setting(Setting.Module.System.SETTING_TTL, "10m", Setting.ValueType.DURATION));
createIfNotExist.handler(new Setting(Setting.Module.System.API_KEY, "[]", Setting.ValueType.JSON_LIST));
createIfNotExist.handler(new Setting(Setting.Module.System.API_KEY_SUPER, "[]", Setting.ValueType.JSON_LIST));
createIfNotExist.handler(new Setting(Setting.Module.System.STATUS_RATE, 3000, Setting.ValueType.INTEGER));
createIfNotExist.handler(new Setting(Setting.Module.System.STATUS_LIMIT, 2048, Setting.ValueType.INTEGER));
createIfNotExist.handler(new Setting(Setting.Module.System.STATUS_NETWORK_MAC, "", Setting.ValueType.STRING));
createIfNotExist.handler(new Setting(Setting.Module.Music.ENABLE, false, Setting.ValueType.BOOLEAN));
createIfNotExist.handler(new Setting(Setting.Module.Journal.KEY, "[]", Setting.ValueType.JSON_LIST));
createIfNotExist.handler(new Setting(Setting.Module.Journal.OPEN_ID_WHITE_LIST, "[]", Setting.ValueType.JSON_LIST));
}
@PostConstruct
private void postConstruct() {
redisSetting.flushAll();
}
@Override @Override
protected BaseMapper<Setting, Long> mapper() { protected BaseMapper<Setting, String> mapper() {
return mapper; return mapper;
} }
@Override @Override
public void update(Setting setting) { public void update(Setting setting) {
super.update(setting); super.update(setting);
redisSetting.destroy(CACHE_KEY_TMPL.formatted(setting.getModule(), setting.getKey())); clearCache(setting.getKey());
} }
public Setting getByKey(Enum<?> key) { public Setting getByKey(SettingKey key) {
if (key == null) { if (key == null) {
throw new TimiException(TimiCode.ARG_MISS).msgKey("key can not be null"); throw new TimiException(TimiCode.ARG_MISS).msgKey("key can not be null");
} }
String cacheKey = CACHE_KEY_TMPL.formatted(key.getClass().getSimpleName().toUpperCase(), key.toString());
String cacheValue = redisSetting.get(cacheKey); String cacheValue = redisSetting.get(key.toString());
if (TimiJava.isNotEmpty(cacheValue)) { if (TimiJava.isNotEmpty(cacheValue)) {
try { try {
return jackson.readValue(cacheValue, Setting.class); return jackson.readValue(cacheValue, Setting.class);
@@ -95,21 +65,19 @@ public class SettingServiceImplement extends AbstractEntityService<Setting, Long
throw new TimiException(TimiCode.ERROR, "read setting cache error", e); throw new TimiException(TimiCode.ERROR, "read setting cache error", e);
} }
} }
Setting setting = mapper.selectByExample(new Setting(key)); Setting setting = mapper.selectByKey(key);
TimiException.required(setting, "not found setting: %s.%s".formatted(key.getClass().getName(), key));
if (TimiJava.isEmpty(setting.getValue())) { if (TimiJava.isEmpty(setting.getValue())) {
// 无需缓存
return setting; return setting;
} }
long settingTTL; int settingTTL;
if (setting.is(Setting.Module.System.SETTING_TTL)) { if (key == SettingKey.TTL_SETTING) {
settingTTL = Time.parseToMS(setting.getValue()); settingTTL = Integer.parseInt(setting.getValue());
} else { } else {
settingTTL = Time.parseToMS(getByKey(Setting.Module.System.SETTING_TTL).getValue()); settingTTL = Integer.parseInt(getByKey(SettingKey.TTL_SETTING).getValue());
} }
if (0 < settingTTL) { if (0 < settingTTL) {
try { try {
redisSetting.set(key.toString(), jackson.writeValueAsString(setting), settingTTL); redisSetting.set(key.toString(), jackson.writeValueAsString(setting), Time.M * settingTTL);
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
throw new TimiException(TimiCode.ERROR, "write setting cache error", e); throw new TimiException(TimiCode.ERROR, "write setting cache error", e);
} }
@@ -118,51 +86,46 @@ public class SettingServiceImplement extends AbstractEntityService<Setting, Long
} }
@Override @Override
public List<Setting> listByKeys(List<Enum<?>> keyList) { public List<Setting> listByKeys(List<SettingKey> keyList) {
List<Setting> result = new ArrayList<>(); List<Setting> result = new ArrayList<>();
for (Enum<?> anEnum : keyList) { for (int i = 0; i < keyList.size(); i++) {
result.add(getByKey(anEnum)); result.add(getByKey(keyList.get(i)));
} }
return result; return result;
} }
@Override @Override
public String getAsString(Enum<?> key) { public String getAsString(SettingKey key) {
return getByKey(key).getValue(); return getByKey(key).getValue();
} }
@Override @Override
public int getAsInt(Enum<?> key) { public int getAsInt(SettingKey key) {
return Integer.parseInt(getAsString(key)); return Integer.parseInt(getAsString(key));
} }
@Override @Override
public long getAsLong(Enum<?> key) { public long getAsLong(SettingKey key) {
return Long.parseLong(getAsString(key)); return Long.parseLong(getAsString(key));
} }
@Override @Override
public double getAsDouble(Enum<?> key) { public double getAsDouble(SettingKey key) {
return Double.parseDouble(getAsString(key)); return Double.parseDouble(getAsString(key));
} }
@Override @Override
public long getAsTime(Enum<?> key) { public boolean is(SettingKey key) {
return Time.parseToMS(getAsString(key));
}
@Override
public boolean is(Enum<?> key) {
return Boolean.parseBoolean(getAsString(key)); return Boolean.parseBoolean(getAsString(key));
} }
@Override @Override
public boolean not(Enum<?> key) { public boolean not(SettingKey key) {
return !is(key); return !is(key);
} }
@Override @Override
public JsonNode getAsJsonNode(Enum<?> key) { public JsonNode getAsJsonNode(SettingKey key) {
try { try {
return jackson.readTree(getAsString(key)); return jackson.readTree(getAsString(key));
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
@@ -171,27 +134,17 @@ public class SettingServiceImplement extends AbstractEntityService<Setting, Long
} }
@Override @Override
public ObjectNode getAsJsonObject(Enum<?> key) { public ObjectNode getAsJsonObject(SettingKey key) {
return (ObjectNode) getAsJsonNode(key); return (ObjectNode) getAsJsonNode(key);
} }
@Override @Override
public ArrayNode getAsArrayNode(Enum<?> key) { public ArrayNode getAsArrayNode(SettingKey key) {
return (ArrayNode) getAsJsonNode(key); return (ArrayNode) getAsJsonNode(key);
} }
@Override @Override
public Set<String> getAsSet(Enum<?> key) { public <T> T fromJson(SettingKey key, Class<T> clazz) {
return new HashSet<>(getAsList(key));
}
@Override
public List<String> getAsList(Enum<?> key) {
return getAsArrayNode(key).valueStream().map(JsonNode::asText).toList();
}
@Override
public <T> T fromJson(Enum<?> key, Class<T> clazz) {
try { try {
return jackson.treeToValue(getAsJsonNode(key), clazz); return jackson.treeToValue(getAsJsonNode(key), clazz);
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
@@ -200,11 +153,11 @@ public class SettingServiceImplement extends AbstractEntityService<Setting, Long
} }
@Override @Override
public <T> T fromJson(Enum<?> key, TypeReference<T> typeReference) { public <T> T fromJson(SettingKey key, TypeReference<T> typeReference) {
return jackson.convertValue(getAsJsonNode(key), typeReference); return jackson.convertValue(getAsJsonNode(key), typeReference);
} }
public <T> T fromYaml(Enum<?> key, Class<T> clazz) { public <T> T fromYaml(SettingKey key, Class<T> clazz) {
return new Yaml().loadAs(getAsString(key), clazz); return new Yaml().loadAs(getAsString(key), clazz);
} }
@@ -212,4 +165,14 @@ public class SettingServiceImplement extends AbstractEntityService<Setting, Long
public List<Setting> listAll() { public List<Setting> listAll() {
return mapper.listAll(); return mapper.listAll();
} }
@Override
public void clearCache(SettingKey key) {
redisSetting.destroy(key.toString());
}
@Override
public void flushCache() {
redisSetting.flushAll();
}
} }

View File

@@ -1,6 +1,6 @@
package com.imyeyu.api.modules.common.service.implement; package com.imyeyu.api.modules.common.service.implement;
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.entity.Setting;
import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.api.modules.common.service.SettingService; import com.imyeyu.api.modules.common.service.SettingService;
import com.imyeyu.api.modules.common.service.TempFileService; import com.imyeyu.api.modules.common.service.TempFileService;
@@ -33,15 +33,15 @@ public class TempFileServiceImplement implements TempFileService {
private final AttachmentService attachmentService; private final AttachmentService attachmentService;
public List<TempFileResponse> store(List<MultipartFile> files, Long ttl) throws TimiException { public List<TempFileResponse> store(List<MultipartFile> files, Long ttl) throws TimiException {
String ttlMinStr = settingService.getAsString(Setting.Module.TempFile.TTL_MIN); String ttlMinStr = settingService.getAsString(SettingKey.TEMP_FILE_TTL_MIN);
String ttlMaxStr = settingService.getAsString(Setting.Module.TempFile.TTL_MAX); String ttlMaxStr = settingService.getAsString(SettingKey.TEMP_FILE_TTL_MAX);
String limitStr = settingService.getAsString(Setting.Module.TempFile.LIMIT_SIZE_OF_IP); String limitStr = settingService.getAsString(SettingKey.TEMP_FILE_LIMIT);
long minTTL = Time.parseToMS(ttlMinStr); long minTTL = Time.parseToMS(ttlMinStr);
long maxTTL = Time.parseToMS(ttlMaxStr); long maxTTL = Time.parseToMS(ttlMaxStr);
long defaultTTL = Time.parseToMS(settingService.getAsString(Setting.Module.TempFile.TTL_DEFAULT)); long defaultTTL = Time.parseToMS(settingService.getAsString(SettingKey.TEMP_FILE_TTL_DEFAULT));
long limit = IOSize.parse(limitStr); long limit = IOSize.parse(limitStr);
long residueTime = Time.parseToMS(settingService.getAsString(Setting.Module.TempFile.RESIDUE_TTL)); long residueTime = Time.parseToMS(settingService.getAsString(SettingKey.TEMP_FILE_RESIDUE_TIME));
ttl = TimiJava.defaultIfNull(ttl, defaultTTL); ttl = TimiJava.defaultIfNull(ttl, defaultTTL);
TimiException.requiredTrue(minTTL < ttl && ttl <= maxTTL, String.format("ttl must be between %s and %s", ttlMinStr, ttlMaxStr)); TimiException.requiredTrue(minTTL < ttl && ttl <= maxTTL, String.format("ttl must be between %s and %s", ttlMinStr, ttlMaxStr));
@@ -65,7 +65,9 @@ public class TempFileServiceImplement implements TempFileService {
long deletedAt = Time.now() + ttl; long deletedAt = Time.now() + ttl;
long destroyAt = deletedAt + residueTime; long destroyAt = deletedAt + residueTime;
for (MultipartFile file : files) { for (int i = 0; i < files.size(); i++) {
MultipartFile file = files.get(i);
Attachment attach = new Attachment(); Attachment attach = new Attachment();
attach.setBizType(Attachment.BizType.TEMP_FILE); attach.setBizType(Attachment.BizType.TEMP_FILE);
attach.setName(file.getOriginalFilename()); attach.setName(file.getOriginalFilename());

View File

@@ -3,8 +3,8 @@ package com.imyeyu.api.modules.common.task;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; 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.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.service.MultilingualService; import com.imyeyu.api.modules.common.service.MultilingualService;
import com.imyeyu.api.modules.common.service.SettingService; import com.imyeyu.api.modules.common.service.SettingService;
import com.imyeyu.java.TimiJava; import com.imyeyu.java.TimiJava;
@@ -131,8 +131,8 @@ public class MultilingualTranslateTask {
private synchronized Map<String, String> doTranslate(String text, BaiduLanguage to) throws Exception { private synchronized Map<String, String> doTranslate(String text, BaiduLanguage to) throws Exception {
String random = String.valueOf(Time.now()); String random = String.valueOf(Time.now());
String appId = settingService.getAsString(Setting.Module.Multilingual.TRANSLATE_APP_ID); String appId = settingService.getAsString(SettingKey.MULTILINGUAL_TRANSLATE_APP_ID);
String key = settingService.getAsString(Setting.Module.Multilingual.TRANSLATE_KEY); String key = settingService.getAsString(SettingKey.MULTILINGUAL_TRANSLATE_KEY);
ArgMap<String, Object> args = new ArgMap<>(); ArgMap<String, Object> args = new ArgMap<>();
args.put("q", text); args.put("q", text);
@@ -142,7 +142,7 @@ public class MultilingualTranslateTask {
args.put("salt", random); args.put("salt", random);
args.put("sign", Digest.md5(appId + text + random + key)); args.put("sign", Digest.md5(appId + text + random + key));
String response = Request.post(settingService.getAsString(Setting.Module.Multilingual.TRANSLATE_API)) String response = Request.post(settingService.getAsString(SettingKey.MULTILINGUAL_TRANSLATE_API))
.bodyForm(args.toNameValuePair()) .bodyForm(args.toNameValuePair())
.execute() .execute()
.returnContent() .returnContent()

View File

@@ -1,9 +1,9 @@
package com.imyeyu.api.modules.git.bean.gitea; package com.imyeyu.api.modules.git.bean.gitea;
import com.imyeyu.api.TimiServerAPI;
import com.imyeyu.api.modules.common.entity.Setting;
import com.imyeyu.api.modules.common.service.SettingService;
import com.imyeyu.java.TimiJava; import com.imyeyu.java.TimiJava;
import com.imyeyu.api.TimiServerAPI;
import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.service.SettingService;
import com.imyeyu.utils.Encoder; import com.imyeyu.utils.Encoder;
import com.imyeyu.utils.StringInterpolator; import com.imyeyu.utils.StringInterpolator;
@@ -13,7 +13,7 @@ import java.util.Map;
* @author 夜雨 * @author 夜雨
* @since 2025-06-26 16:04 * @since 2025-06-26 16:04
*/ */
public enum GiteaAPI { public enum API {
REPO_LIST("/api/v1/repos/search"), REPO_LIST("/api/v1/repos/search"),
@@ -36,7 +36,7 @@ public enum GiteaAPI {
final String uri; final String uri;
GiteaAPI(String uri) { API(String uri) {
this.uri = uri; this.uri = uri;
} }
@@ -48,7 +48,7 @@ public enum GiteaAPI {
public String buildURL(Map<String, Object> argsURI, Map<String, Object> argsURL) { public String buildURL(Map<String, Object> argsURI, Map<String, Object> argsURL) {
SettingService settingService = TimiServerAPI.applicationContext.getBean(SettingService.class); SettingService settingService = TimiServerAPI.applicationContext.getBean(SettingService.class);
StringBuilder url = new StringBuilder(); StringBuilder url = new StringBuilder();
url.append(settingService.getAsString(Setting.Module.Gitea.API)); url.append(settingService.getAsString(SettingKey.GIT_API));
url.append(INTERPOLATOR.inject(uri, argsURI)); url.append(INTERPOLATOR.inject(uri, argsURI));
if (TimiJava.isNotEmpty(argsURL)) { if (TimiJava.isNotEmpty(argsURL)) {
url.append("?"); url.append("?");

View File

@@ -1,13 +1,13 @@
package com.imyeyu.api.modules.git.controller; package com.imyeyu.api.modules.git.controller;
import lombok.RequiredArgsConstructor;
import com.imyeyu.api.annotation.EnableSetting; import com.imyeyu.api.annotation.EnableSetting;
import com.imyeyu.api.modules.common.entity.Setting; import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.git.entity.Developer; import com.imyeyu.api.modules.git.entity.Developer;
import com.imyeyu.api.modules.git.service.DeveloperService; import com.imyeyu.api.modules.git.service.DeveloperService;
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.RequiredToken; import com.imyeyu.spring.annotation.RequiredToken;
import lombok.RequiredArgsConstructor;
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;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@@ -34,7 +34,7 @@ public class DeveloperController {
@AOPLog @AOPLog
@RequiredToken @RequiredToken
@EnableSetting(user = Setting.Module.User.ENABLE_PROFILE_UPDATE, message = "user.data.off_service") @EnableSetting(value = SettingKey.ENABLE_USER_UPDATE, message = "user.data.off_service")
@RequestRateLimit @RequestRateLimit
@PostMapping("/update") @PostMapping("/update")
public void update(@RequestBody Developer developer) { public void update(@RequestBody Developer developer) {

View File

@@ -3,21 +3,21 @@ package com.imyeyu.api.modules.git.service.implement;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.imyeyu.api.modules.common.entity.Setting; import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.network.Network;
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.common.service.UserService; import com.imyeyu.api.modules.common.service.UserService;
import com.imyeyu.api.modules.git.bean.gitea.API;
import com.imyeyu.api.modules.git.bean.gitea.Branch; import com.imyeyu.api.modules.git.bean.gitea.Branch;
import com.imyeyu.api.modules.git.bean.gitea.File; import com.imyeyu.api.modules.git.bean.gitea.File;
import com.imyeyu.api.modules.git.bean.gitea.GiteaAPI;
import com.imyeyu.api.modules.git.bean.gitea.GiteaResponse; import com.imyeyu.api.modules.git.bean.gitea.GiteaResponse;
import com.imyeyu.api.modules.git.bean.gitea.Repository; import com.imyeyu.api.modules.git.bean.gitea.Repository;
import com.imyeyu.api.modules.git.service.RepositoryService; import com.imyeyu.api.modules.git.service.RepositoryService;
import com.imyeyu.api.modules.gitea.entity.User; import com.imyeyu.api.modules.gitea.entity.User;
import com.imyeyu.api.modules.gitea.service.GiteaService; import com.imyeyu.api.modules.gitea.service.GiteaService;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.network.Network;
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.activation.MimetypesFileTypeMap; import jakarta.activation.MimetypesFileTypeMap;
@@ -72,7 +72,7 @@ public class RepositoryServiceImplement implements RepositoryService {
@Override @Override
public PageResult<Repository> page(Page page) { public PageResult<Repository> page(Page page) {
try { try {
ClassicHttpResponse resp = (ClassicHttpResponse) Request.get(GiteaAPI.REPO_LIST.buildURL(null, new HashMap<>() {{ ClassicHttpResponse resp = (ClassicHttpResponse) Request.get(API.REPO_LIST.buildURL(null, new HashMap<>() {{
put("uid", owner.getId()); put("uid", owner.getId());
put("sort", "updated"); put("sort", "updated");
put("order", "desc"); put("order", "desc");
@@ -95,7 +95,7 @@ public class RepositoryServiceImplement implements RepositoryService {
@Override @Override
public Repository get(String repoName) { public Repository get(String repoName) {
try { try {
String respText = Request.get(GiteaAPI.REPO_GET.buildURL(new HashMap<>() {{ String respText = Request.get(API.REPO_GET.buildURL(new HashMap<>() {{
put("owner", owner.getName()); put("owner", owner.getName());
put("repoName", repoName); put("repoName", repoName);
}})).execute().returnContent().asString(); }})).execute().returnContent().asString();
@@ -109,7 +109,7 @@ 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(GiteaAPI.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();
@@ -123,7 +123,7 @@ 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(GiteaAPI.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);
@@ -152,7 +152,7 @@ public class RepositoryServiceImplement implements RepositoryService {
@Override @Override
public InputStream getFileRaw(String repoName, String branch, String path) { public InputStream getFileRaw(String repoName, String branch, String path) {
try { try {
ClassicHttpResponse resp = (ClassicHttpResponse) Request.get(GiteaAPI.REPO_FILE_RAW.buildURL(new HashMap<>() {{ ClassicHttpResponse resp = (ClassicHttpResponse) Request.get(API.REPO_FILE_RAW.buildURL(new HashMap<>() {{
put("owner", owner.getName()); put("owner", owner.getName());
put("repoName", repoName); put("repoName", repoName);
put("path", path); put("path", path);
@@ -169,7 +169,7 @@ public class RepositoryServiceImplement implements RepositoryService {
@Override @Override
public String getFileMimeType(String repoName, String branch, String path) { public String getFileMimeType(String repoName, String branch, String path) {
try { try {
String repoPath = settingService.getAsString(Setting.Module.Gitea.REPOSITORY_PATH); String repoPath = settingService.getAsString(SettingKey.GIT_REPO_PATH);
if (TimiJava.isEmpty(repoPath)) { if (TimiJava.isEmpty(repoPath)) {
// 根据文件名猜测 // 根据文件名猜测
return new MimetypesFileTypeMap().getContentType(Network.uriFileName(path)); return new MimetypesFileTypeMap().getContentType(Network.uriFileName(path));
@@ -210,7 +210,7 @@ public class RepositoryServiceImplement implements RepositoryService {
@Override @Override
public InputStream getArchive(String repoName, String branch) { public InputStream getArchive(String repoName, String branch) {
try { try {
ClassicHttpResponse resp = (ClassicHttpResponse) Request.get(GiteaAPI.REPO_ARCHIVE.buildURL(new HashMap<>() {{ ClassicHttpResponse resp = (ClassicHttpResponse) Request.get(API.REPO_ARCHIVE.buildURL(new HashMap<>() {{
put("owner", owner.getName()); put("owner", owner.getName());
put("repoName", repoName); put("repoName", repoName);
put("archiveFormat", "%s.tar.gz".formatted(branch)); put("archiveFormat", "%s.tar.gz".formatted(branch));

View File

@@ -4,8 +4,8 @@ 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;
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.entity.Setting;
import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.api.modules.common.service.AttachmentService;
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;
@@ -68,8 +68,8 @@ public class JournalController {
public String initOpenId(@RequestBody 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(Setting.Module.Journal.APP_ID)); args.put("appid", settingService.getAsString(SettingKey.JOURNAL_APP_ID));
args.put("secret", settingService.getAsString(Setting.Module.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");
String response = org.apache.hc.client5.http.fluent.Request.get(args.toURL("https://api.weixin.qq.com/sns/jscode2session")) String response = org.apache.hc.client5.http.fluent.Request.get(args.toURL("https://api.weixin.qq.com/sns/jscode2session"))

View File

@@ -1,5 +1,6 @@
package com.imyeyu.api.modules.journal.controller; package com.imyeyu.api.modules.journal.controller;
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;
@@ -25,13 +26,13 @@ public class ToolController {
@GetMapping("/memo") @GetMapping("/memo")
public String getMemo() { public String getMemo() {
return settingService.getAsString(Setting.Module.Journal.MEMO); return settingService.getAsString(SettingKey.JOURNAL_MEMO);
} }
@RequiredUploadPermission @RequiredUploadPermission
@PostMapping("/memo/update") @PostMapping("/memo/update")
public void updateMemo(@RequestBodyValue String data) { public void updateMemo(@RequestBodyValue String data) {
Setting setting = settingService.getByKey(Setting.Module.Journal.MEMO); Setting setting = settingService.getByKey(SettingKey.JOURNAL_MEMO);
setting.setValue(data); setting.setValue(data);
settingService.update(setting); settingService.update(setting);
} }

View File

@@ -1,6 +1,6 @@
package com.imyeyu.api.modules.journal.util; package com.imyeyu.api.modules.journal.util;
import com.imyeyu.api.modules.common.entity.Setting; 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.journal.bean.RequiredUploadPermission; import com.imyeyu.api.modules.journal.bean.RequiredUploadPermission;
import com.imyeyu.java.TimiJava; import com.imyeyu.java.TimiJava;
@@ -36,8 +36,8 @@ public class JournalAPIInterceptor implements HandlerInterceptor {
@PostConstruct @PostConstruct
private void postConstruct() { private void postConstruct() {
keys = settingService.getAsSet(Setting.Module.Journal.KEY); keys = Set.of(settingService.getAsString(SettingKey.JOURNAL_KEY).split(","));
openIds = settingService.getAsSet(Setting.Module.Journal.OPEN_ID_WHITE_LIST); openIds = Set.of(settingService.getAsString(SettingKey.JOURNAL_OPEN_ID_WHITE_LIST).split(","));
} }
public boolean preHandle(@NonNull HttpServletRequest req, @NonNull HttpServletResponse resp, @NonNull Object handler) { public boolean preHandle(@NonNull HttpServletRequest req, @NonNull HttpServletResponse resp, @NonNull Object handler) {

View File

@@ -1,9 +1,13 @@
package com.imyeyu.api.modules.minecraft.annotation; package com.imyeyu.api.modules.minecraft.annotation;
import com.imyeyu.api.modules.common.service.SettingService;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.service.SettingService;
import com.imyeyu.spring.TimiSpring;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.HandlerMethod;
@@ -37,9 +41,9 @@ public class RequiredFMCServerTokenInterceptor implements HandlerInterceptor {
if (requiredFMCServerToken == null) { if (requiredFMCServerToken == null) {
return true; return true;
} }
// if (!settingService.getAsString(SettingKey.FMC_SERVER_TOKEN).equals(TimiSpring.getToken())) { if (!settingService.getAsString(SettingKey.FMC_SERVER_TOKEN).equals(TimiSpring.getToken())) {
// throw new TimiException(TimiCode.ARG_MISS).msgKey("token.illegal"); throw new TimiException(TimiCode.ARG_MISS).msgKey("token.illegal");
// } }
return true; return true;
} }
return true; return true;

View File

@@ -1,16 +1,18 @@
package com.imyeyu.api.modules.minecraft.controller; package com.imyeyu.api.modules.minecraft.controller;
import com.imyeyu.api.modules.blog.util.UserToken;
import com.imyeyu.api.modules.minecraft.service.FMCImageMapService;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.annotation.AOPLog;
import com.imyeyu.spring.annotation.RequestRateLimit;
import com.imyeyu.spring.annotation.RequiredToken;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.api.annotation.EnableSetting;
import com.imyeyu.api.modules.blog.util.UserToken;
import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.minecraft.service.FMCImageMapService;
import com.imyeyu.spring.annotation.AOPLog;
import com.imyeyu.spring.annotation.RequestRateLimit;
import com.imyeyu.spring.annotation.RequiredToken;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -48,6 +50,7 @@ public class MinecraftController {
*/ */
@AOPLog @AOPLog
@RequiredToken @RequiredToken
@EnableSetting(value = SettingKey.FMC_ENABLE_IMAGE_MAP_UPLOAD, message = "minecraft.fmc_image_map.off_service")
@RequestRateLimit @RequestRateLimit
@PostMapping("/imagemap/upload") @PostMapping("/imagemap/upload")
public String uploadFMCImageMap(@NotNull @RequestParam("request") MultipartFile file) { public String uploadFMCImageMap(@NotNull @RequestParam("request") MultipartFile file) {

View File

@@ -1,23 +1,25 @@
package com.imyeyu.api.modules.minecraft.controller; package com.imyeyu.api.modules.minecraft.controller;
import jakarta.annotation.PostConstruct;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.api.annotation.EnableSetting;
import com.imyeyu.api.config.RedisConfig; import com.imyeyu.api.config.RedisConfig;
import com.imyeyu.api.modules.blog.util.UserToken; import com.imyeyu.api.modules.blog.util.UserToken;
import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.service.UserService; import com.imyeyu.api.modules.common.service.UserService;
import com.imyeyu.api.modules.minecraft.entity.MinecraftPlayer; import com.imyeyu.api.modules.minecraft.entity.MinecraftPlayer;
import com.imyeyu.api.modules.minecraft.service.PlayerService; import com.imyeyu.api.modules.minecraft.service.PlayerService;
import com.imyeyu.api.modules.minecraft.vo.TokenRequest; 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.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode;
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.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;
import jakarta.annotation.PostConstruct;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -60,6 +62,7 @@ public class PlayerController {
* @return true 为已绑定 * @return true 为已绑定
*/ */
@RequestRateLimit @RequestRateLimit
@EnableSetting(value = SettingKey.FMC_PLAYER_LOGIN_ENABLE, message = "登录服务未启用")
@GetMapping("/bound/{name}") @GetMapping("/bound/{name}")
public boolean isBound(@PathVariable("name") String name) { public boolean isBound(@PathVariable("name") String name) {
return service.getByName(name) != null; return service.getByName(name) != null;
@@ -107,6 +110,7 @@ public class PlayerController {
@AOPLog @AOPLog
@RequestRateLimit @RequestRateLimit
@EnableSetting(value = SettingKey.FMC_PLAYER_LOGIN_ENABLE, message = "登录服务未启用")
@PostMapping("/login") @PostMapping("/login")
public TokenResponse login(@RequestBody 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);
@@ -114,6 +118,7 @@ public class PlayerController {
@AOPLog @AOPLog
@RequestRateLimit @RequestRateLimit
@EnableSetting(value = SettingKey.FMC_PLAYER_LOGIN_ENABLE, message = "登录服务未启用")
@PostMapping("/login/token") @PostMapping("/login/token")
public TokenResponse genLoginToken(@Valid @RequestBody TokenRequest request) { public TokenResponse genLoginToken(@Valid @RequestBody TokenRequest request) {
return service.genLoginToken(request); return service.genLoginToken(request);

View File

@@ -1,7 +1,9 @@
package com.imyeyu.api.modules.minecraft.service.implement; package com.imyeyu.api.modules.minecraft.service.implement;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.api.config.RedisConfig; import com.imyeyu.api.config.RedisConfig;
import com.imyeyu.api.modules.common.entity.Setting; import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.entity.User; import com.imyeyu.api.modules.common.entity.User;
import com.imyeyu.api.modules.common.service.SettingService; import com.imyeyu.api.modules.common.service.SettingService;
import com.imyeyu.api.modules.common.service.UserService; import com.imyeyu.api.modules.common.service.UserService;
@@ -10,8 +12,6 @@ import com.imyeyu.api.modules.minecraft.mapper.PlayerMapper;
import com.imyeyu.api.modules.minecraft.service.PlayerService; import com.imyeyu.api.modules.minecraft.service.PlayerService;
import com.imyeyu.api.modules.minecraft.vo.TokenRequest; 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.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.TimiSpring; import com.imyeyu.spring.TimiSpring;
import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.spring.service.AbstractEntityService; import com.imyeyu.spring.service.AbstractEntityService;
@@ -62,12 +62,11 @@ public class PlayerServiceImplement extends AbstractEntityService<MinecraftPlaye
} }
// 自查重 // 自查重
List<MinecraftPlayer> playerList = listByUserId(player.getUserId()); List<MinecraftPlayer> playerList = listByUserId(player.getUserId());
int maxValue = settingService.getAsInt(Setting.Module.ForeverMC.PLAYER_BIND_MAX); if (settingService.getAsInt(SettingKey.FMC_MAX_BIND) <= playerList.size()) {
if (maxValue <= playerList.size()) { throw new TimiException(TimiCode.RESULT_BAD).msgKey("已达到最大绑定数量:%s 个".formatted(settingService.getAsInt(SettingKey.FMC_MAX_BIND)));
throw new TimiException(TimiCode.RESULT_BAD).msgKey("已达到最大绑定数量:%s 个".formatted(maxValue));
} }
for (MinecraftPlayer minecraftPlayer : playerList) { for (int i = 0; i < playerList.size(); i++) {
if (minecraftPlayer.getName().equals(player.getName())) { if (playerList.get(i).getName().equals(player.getName())) {
throw new TimiException(TimiCode.RESULT_BAD).msgKey("已绑定此玩家名称"); throw new TimiException(TimiCode.RESULT_BAD).msgKey("已绑定此玩家名称");
} }
} }
@@ -114,7 +113,7 @@ public class PlayerServiceImplement extends AbstractEntityService<MinecraftPlaye
mapper.update(player); mapper.update(player);
String playerToken = UUID.randomUUID().toString(); String playerToken = UUID.randomUUID().toString();
long ttl = settingService.getAsTime(Setting.Module.ForeverMC.LOGIN_TOKEN_TTL); long ttl = settingService.getAsInt(SettingKey.FMC_PLAYER_LOGIN_TOKEN_TTL) * Time.D;
redis.set(playerToken, playerId, ttl); redis.set(playerToken, playerId, ttl);
return new TokenResponse(userId, playerToken, Time.now() + ttl); return new TokenResponse(userId, playerToken, Time.now() + ttl);
} }
@@ -136,8 +135,7 @@ public class PlayerServiceImplement extends AbstractEntityService<MinecraftPlaye
user = userService.get(playerByName.getUserId()); user = userService.get(playerByName.getUserId());
} }
if (user == null) { if (user == null) {
String spaceDomain = settingService.getAsString(Setting.Module.Domain.SPACE); throw new TimiException(TimiCode.RESULT_NULL).msgKey("未注册或绑定此玩家昵称,请到 https://%s 进行注册或绑定".formatted(settingService.getAsString(SettingKey.DOMAIN_SPACE)));
throw new TimiException(TimiCode.RESULT_NULL).msgKey("未注册或绑定此玩家昵称,请到 https://%s 进行注册或绑定".formatted(spaceDomain));
} }
if (user.isBanning()) { if (user.isBanning()) {
throw new TimiException(TimiCode.RESULT_BAN).msgKey("账号封禁中"); throw new TimiException(TimiCode.RESULT_BAN).msgKey("账号封禁中");
@@ -146,7 +144,7 @@ public class PlayerServiceImplement extends AbstractEntityService<MinecraftPlaye
throw new TimiException(TimiCode.ARG_BAD).msgKey("密码错误"); throw new TimiException(TimiCode.ARG_BAD).msgKey("密码错误");
} }
String token = UUID.randomUUID().toString(); String token = UUID.randomUUID().toString();
long ttl = settingService.getAsTime(Setting.Module.ForeverMC.LOGIN_TOKEN_TTL); long ttl = settingService.getAsInt(SettingKey.FMC_PLAYER_LOGIN_TOKEN_TTL) * Time.D;
redis.set(token, user.getId(), ttl); redis.set(token, user.getId(), ttl);
return new TokenResponse(user.getId(), token, Time.now() + ttl); return new TokenResponse(user.getId(), token, Time.now() + ttl);
} }

View File

@@ -1,9 +1,5 @@
package com.imyeyu.api.modules.music.runner; package com.imyeyu.api.modules.music.runner;
import com.imyeyu.api.config.CORSConfig;
import com.imyeyu.api.modules.common.entity.Setting;
import com.imyeyu.api.modules.common.service.SettingService;
import com.imyeyu.api.modules.music.handler.ControllerMessageHandler;
import io.netty.bootstrap.ServerBootstrap; import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelFutureListener;
@@ -28,6 +24,10 @@ import io.netty.handler.stream.ChunkedWriteHandler;
import lombok.NonNull; import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import com.imyeyu.api.config.CORSConfig;
import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.service.SettingService;
import com.imyeyu.api.modules.music.handler.ControllerMessageHandler;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner; import org.springframework.boot.ApplicationRunner;
@@ -63,10 +63,6 @@ public class ControllerBootstrapRunner implements ApplicationRunner, Application
} }
public void run(ApplicationArguments args) { public void run(ApplicationArguments args) {
if (settingService.not(Setting.Module.Music.ENABLE)) {
return;
}
final DefaultFullHttpResponse NFP = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND); final DefaultFullHttpResponse NFP = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND);
final DefaultFullHttpResponse BRP = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST); final DefaultFullHttpResponse BRP = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST);
@@ -76,7 +72,7 @@ public class ControllerBootstrapRunner implements ApplicationRunner, Application
ServerBootstrap bootstrap = new ServerBootstrap(); ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boss, worker); bootstrap.group(boss, worker);
bootstrap.channel(NioServerSocketChannel.class); bootstrap.channel(NioServerSocketChannel.class);
bootstrap.localAddress(new InetSocketAddress(settingService.getAsInt(Setting.Module.Music.CONTROLLER_PORT))); bootstrap.localAddress(new InetSocketAddress(settingService.getAsInt(SettingKey.MUSIC_CONTROLLER_PORT)));
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() { bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override @Override
@@ -90,7 +86,7 @@ public class ControllerBootstrapRunner implements ApplicationRunner, Application
@Override @Override
public void channelRead(ChannelHandlerContext ctx, Object obj) throws Exception { public void channelRead(ChannelHandlerContext ctx, Object obj) throws Exception {
if (obj instanceof FullHttpRequest req) { if (obj instanceof FullHttpRequest req) {
if (!req.uri().equals(settingService.getAsString(Setting.Module.Music.CONTROLLER_URI))) { if (!req.uri().equals(settingService.getAsString(SettingKey.MUSIC_CONTROLLER_URI))) {
// 访问的路径不是 Web Socket 的端点地址,响应 404 // 访问的路径不是 Web Socket 的端点地址,响应 404
ctx.channel().writeAndFlush(NFP).addListener(ChannelFutureListener.CLOSE); ctx.channel().writeAndFlush(NFP).addListener(ChannelFutureListener.CLOSE);
return; return;
@@ -112,10 +108,10 @@ public class ControllerBootstrapRunner implements ApplicationRunner, Application
}); });
pipeline.addLast(new WebSocketServerCompressionHandler()); pipeline.addLast(new WebSocketServerCompressionHandler());
pipeline.addLast(new WebSocketServerProtocolHandler( pipeline.addLast(new WebSocketServerProtocolHandler(
settingService.getAsString(Setting.Module.Music.CONTROLLER_URI), settingService.getAsString(SettingKey.MUSIC_CONTROLLER_URI),
null, null,
true, true,
settingService.getAsInt(Setting.Module.Music.MAX_FRAME_LENGTH) settingService.getAsInt(SettingKey.MUSIC_MAX_FRAME_LENGTH)
) )
); );
@@ -124,7 +120,7 @@ public class ControllerBootstrapRunner implements ApplicationRunner, Application
} }
}); });
channel = bootstrap.bind().sync().channel(); channel = bootstrap.bind().sync().channel();
log.info("TimiMusicRC controller service startup with {}", settingService.getAsString(Setting.Module.Music.CONTROLLER_IP)); log.info("TimiMusicRC controller service startup with " + settingService.getAsString(SettingKey.MUSIC_CONTROLLER_IP));
} catch (Exception e) { } catch (Exception e) {
log.error("TimiMusicRC controller service startup error", e); log.error("TimiMusicRC controller service startup error", e);
} }

View File

@@ -1,8 +1,5 @@
package com.imyeyu.api.modules.music.runner; package com.imyeyu.api.modules.music.runner;
import com.imyeyu.api.modules.common.entity.Setting;
import com.imyeyu.api.modules.common.service.SettingService;
import com.imyeyu.api.modules.music.handler.PlayerMessageHandler;
import io.netty.bootstrap.ServerBootstrap; import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelInitializer;
@@ -15,6 +12,9 @@ import io.netty.handler.codec.string.StringDecoder;
import lombok.NonNull; import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.service.SettingService;
import com.imyeyu.api.modules.music.handler.PlayerMessageHandler;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner; import org.springframework.boot.ApplicationRunner;
@@ -50,22 +50,19 @@ public class PlayerBootstrapRunner implements ApplicationRunner, ApplicationList
} }
public void run(ApplicationArguments args) { public void run(ApplicationArguments args) {
if (settingService.not(Setting.Module.Music.ENABLE)) {
return;
}
ServerBootstrap bootstrap = new ServerBootstrap(); ServerBootstrap bootstrap = new ServerBootstrap();
boss = new NioEventLoopGroup(); boss = new NioEventLoopGroup();
worker = new NioEventLoopGroup(); worker = new NioEventLoopGroup();
try { try {
bootstrap.group(boss, worker); bootstrap.group(boss, worker);
bootstrap.channel(NioServerSocketChannel.class); bootstrap.channel(NioServerSocketChannel.class);
bootstrap.localAddress(new InetSocketAddress(settingService.getAsInt(Setting.Module.Music.PLAYER_PORT))); bootstrap.localAddress(new InetSocketAddress(settingService.getAsInt(SettingKey.MUSIC_PLAYER_PORT)));
bootstrap.childHandler(new ChannelInitializer<>() { bootstrap.childHandler(new ChannelInitializer<>() {
@Override @Override
protected void initChannel(Channel ch) { protected void initChannel(Channel ch) {
ch.pipeline().addLast(new LengthFieldBasedFrameDecoder( ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(
settingService.getAsInt(Setting.Module.Music.MAX_FRAME_LENGTH), settingService.getAsInt(SettingKey.MUSIC_MAX_FRAME_LENGTH),
0, 0,
4, 4,
0, 0,
@@ -80,7 +77,7 @@ public class PlayerBootstrapRunner implements ApplicationRunner, ApplicationList
channel = bootstrap.bind().sync().channel(); channel = bootstrap.bind().sync().channel();
log.info("TimiMusicRC player service startup with {}", settingService.getAsString(Setting.Module.Music.PLAYER_IP)); log.info("TimiMusicRC player service startup with " + settingService.getAsString(SettingKey.MUSIC_PLAYER_IP));
} catch (Exception e) { } catch (Exception e) {
log.error("TimiMusicRC player service startup error", e); log.error("TimiMusicRC player service startup error", e);
} }

View File

@@ -1,11 +1,11 @@
package com.imyeyu.api.modules.system.bean; package com.imyeyu.api.modules.system.bean;
import com.imyeyu.api.TimiServerAPI;
import com.imyeyu.api.modules.common.entity.Setting;
import com.imyeyu.api.modules.common.service.SettingService;
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;
import com.imyeyu.api.TimiServerAPI;
import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.service.SettingService;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.Getter; import lombok.Getter;
@@ -174,7 +174,7 @@ public class ServerFile implements Serializable {
name = file.getName(); name = file.getName();
absolutePath = file.getAbsolutePath().replaceAll("\\\\", "/"); absolutePath = file.getAbsolutePath().replaceAll("\\\\", "/");
SettingService settingService = TimiServerAPI.applicationContext.getBean(SettingService.class); SettingService settingService = TimiServerAPI.applicationContext.getBean(SettingService.class);
absolutePath = absolutePath.substring(settingService.getAsString(Setting.Module.System.FILE_BASE_PATH).length()); absolutePath = absolutePath.substring(settingService.getAsString(SettingKey.SYSTEM_FILE_BASE).length());
modifiedAt = file.lastModified(); modifiedAt = file.lastModified();
isFile = file.isFile(); isFile = file.isFile();

View File

@@ -348,10 +348,10 @@ public class ServerStatus implements TimiJava {
private String mountPoint; private String mountPoint;
/** 分区总空间 */ /** 分区总空间 */
private long total; private long totalBytes;
/** 分区已用空间 */ /** 分区已用空间 */
private Long used; private Long usedBytes;
/** 磁盘传输耗时 */ /** 磁盘传输耗时 */
private long transferTimeMs; private long transferTimeMs;

View File

@@ -26,17 +26,17 @@ public class DockerController {
private final DockerService dockerService; private final DockerService dockerService;
@GetMapping("/container") @GetMapping("/containers")
public List<DockerContainerSummaryView> listContainers() { public List<DockerContainerSummaryView> listContainers() {
return dockerService.listContainers(); return dockerService.listContainers();
} }
@GetMapping("/container/{containerId}/status") @GetMapping("/containers/{containerId}/status")
public DockerContainerStatusView getContainerStatus(@PathVariable String containerId) { public DockerContainerStatusView getContainerStatus(@PathVariable String containerId) {
return dockerService.getContainerStatus(containerId); return dockerService.getContainerStatus(containerId);
} }
@GetMapping("/container/{containerId}/history") @GetMapping("/containers/{containerId}/history")
public DockerContainerHistoryView getContainerHistory(@PathVariable String containerId, @RequestParam(required = false) String window) { public DockerContainerHistoryView getContainerHistory(@PathVariable String containerId, @RequestParam(required = false) String window) {
return dockerService.getContainerHistory(containerId, window); return dockerService.getContainerHistory(containerId, window);
} }

View File

@@ -1,6 +1,11 @@
package com.imyeyu.api.modules.system.controller; package com.imyeyu.api.modules.system.controller;
import com.imyeyu.api.modules.common.entity.Setting; import com.imyeyu.io.IO;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.network.Network;
import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.entity.Tag; import com.imyeyu.api.modules.common.entity.Tag;
import com.imyeyu.api.modules.common.service.SettingService; import com.imyeyu.api.modules.common.service.SettingService;
import com.imyeyu.api.modules.common.service.TagService; import com.imyeyu.api.modules.common.service.TagService;
@@ -18,11 +23,6 @@ import com.imyeyu.api.modules.system.task.async.FileUnZipAsyncTask;
import com.imyeyu.api.modules.system.task.async.FileZipAsyncTask; import com.imyeyu.api.modules.system.task.async.FileZipAsyncTask;
import com.imyeyu.api.modules.system.util.ResourceHandler; import com.imyeyu.api.modules.system.util.ResourceHandler;
import com.imyeyu.api.modules.system.vo.ListFileToRequest; import com.imyeyu.api.modules.system.vo.ListFileToRequest;
import com.imyeyu.io.IO;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.network.Network;
import com.imyeyu.spring.TimiSpring; import com.imyeyu.spring.TimiSpring;
import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.AOPLog;
import com.imyeyu.spring.annotation.IgnoreGlobalReturn; import com.imyeyu.spring.annotation.IgnoreGlobalReturn;
@@ -51,7 +51,6 @@ import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
import java.net.URI;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@@ -188,7 +187,7 @@ public class FileController implements TimiJava, OS.FileSystem {
public void read(HttpServletRequest req, HttpServletResponse resp) { public void read(HttpServletRequest req, HttpServletResponse resp) {
try { try {
String path = req.getServletPath().substring("/system/file/read".length()); String path = req.getServletPath().substring("/system/file/read".length());
path = settingService.getAsString(Setting.Module.System.FILE_BASE_PATH) + path; path = settingService.getAsString(SettingKey.SYSTEM_FILE_BASE) + path;
if (TimiJava.isEmpty(path)) { if (TimiJava.isEmpty(path)) {
resp.setStatus(HttpServletResponse.SC_NOT_FOUND); resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
return; return;
@@ -240,7 +239,7 @@ public class FileController implements TimiJava, OS.FileSystem {
resp.setStatus(HttpServletResponse.SC_NOT_FOUND); resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
return; return;
} }
path = settingService.getAsString(Setting.Module.System.FILE_BASE_PATH) + path; path = settingService.getAsString(SettingKey.SYSTEM_FILE_BASE) + path;
File file = new File(path); File file = new File(path);
if (!file.exists()) { if (!file.exists()) {
log.warn("preview a not exist file: {}", path); log.warn("preview a not exist file: {}", path);
@@ -467,8 +466,8 @@ public class FileController implements TimiJava, OS.FileSystem {
public void download(HttpServletResponse resp) { public void download(HttpServletResponse resp) {
try { try {
String path = TimiSpring.cutURIStartAt("/system/file/download"); String path = TimiSpring.cutURIStartAt("/system/file/download");
path = settingService.getAsString(Setting.Module.System.FILE_BASE_PATH) + path; path = settingService.getAsString(SettingKey.SYSTEM_FILE_BASE) + path;
File file = Paths.get(URI.create(path)).toFile(); File file = new File(path);
service.checkAccessPermission(file.getAbsolutePath()); service.checkAccessPermission(file.getAbsolutePath());
String mimeType = new Tika().detect(file); String mimeType = new Tika().detect(file);

View File

@@ -1,19 +0,0 @@
package com.imyeyu.api.modules.system.controller;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
/**
*
*
* @author 夜雨
* @since 2026-04-23 16:31
*/
@Slf4j
@RequiredArgsConstructor
@RequestMapping("/system/setting")
public class SettingController {
}

View File

@@ -3,11 +3,15 @@ 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.service.StatusService; import com.imyeyu.api.modules.system.service.StatusService;
import com.imyeyu.api.modules.system.service.SystemService;
import com.imyeyu.api.modules.system.vo.SystemStatusHistoryView; import com.imyeyu.api.modules.system.vo.SystemStatusHistoryView;
import com.imyeyu.api.modules.system.vo.SystemStatusSnapshotView; 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.TimiException;
import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.AOPLog;
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.GetMapping;
@@ -34,6 +38,7 @@ import java.util.concurrent.Semaphore;
public class SystemController { public class SystemController {
private final StatusService statusService; private final StatusService statusService;
private final SystemService service;
private final AttachmentService attachmentService; private final AttachmentService attachmentService;
private final Semaphore updateSemaphore = new Semaphore(1); private final Semaphore updateSemaphore = new Semaphore(1);
@@ -66,6 +71,68 @@ public class SystemController {
return statusService.getStatusHistory(window, metrics); return statusService.getStatusHistory(window, metrics);
} }
/**
* 更新系统
*
* @param file 更新文件
*/
@AOPLog
@PostMapping("/update")
public void update(@NotNull @RequestParam("file") MultipartFile file) {
if (updateSemaphore.tryAcquire()) {
try {
service.update(file);
} finally {
updateSemaphore.release();
}
} else {
throw new TimiException(TimiCode.ERROR_SERVICE_OFF).msgKey("TODO updating");
}
}
/**
* 恢复系统
*/
@AOPLog
@RequestMapping("/restore")
public void restore() {
if (restoreSemaphore.tryAcquire()) {
try {
service.restore();
} finally {
restoreSemaphore.release();
}
} else {
throw new TimiException(TimiCode.ERROR_SERVICE_OFF).msgKey("TODO updating");
}
}
/**
* 关闭系统
*/
@AOPLog
@RequestMapping("/shutdown")
public void shutdown() {
service.shutdown();
}
/**
* 重启系统
*/
@AOPLog
@RequestMapping("/reboot")
public void reboot() {
if (rebootSemaphore.tryAcquire()) {
try {
service.reboot();
} finally {
rebootSemaphore.release();
}
} else {
throw new TimiException(TimiCode.ERROR_SERVICE_OFF).msgKey("TODO rebooting");
}
}
/** /**
* 上传临时附件 * 上传临时附件
* *

View File

@@ -0,0 +1,18 @@
package com.imyeyu.api.modules.system.service;
import org.springframework.web.multipart.MultipartFile;
/**
* @author 夜雨
* @version 2024-03-13 00:44
*/
public interface SystemService {
void update(MultipartFile file);
void restore();
void shutdown();
void reboot();
}

View File

@@ -1,22 +1,26 @@
package com.imyeyu.api.modules.system.service.implement; package com.imyeyu.api.modules.system.service.implement;
import com.imyeyu.api.modules.common.entity.Setting;
import com.imyeyu.api.modules.common.service.SettingService;
import com.imyeyu.api.modules.system.bean.ServerFile;
import com.imyeyu.api.modules.system.bean.TransferFile;
import com.imyeyu.api.modules.system.service.AsyncTaskService;
import com.imyeyu.api.modules.system.service.FileService;
import com.imyeyu.api.modules.system.util.SystemAPIInterceptor;
import com.imyeyu.io.IO; import com.imyeyu.io.IO;
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;
import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.service.SettingService;
import com.imyeyu.api.modules.system.bean.FileSyncConfig;
import com.imyeyu.api.modules.system.bean.ServerFile;
import com.imyeyu.api.modules.system.bean.TransferFile;
import com.imyeyu.api.modules.system.entity.AsyncTask;
import com.imyeyu.api.modules.system.service.AsyncTaskService;
import com.imyeyu.api.modules.system.service.FileService;
import com.imyeyu.api.modules.system.task.async.FileSyncTask;
import com.imyeyu.api.modules.system.util.SystemAPIInterceptor;
import com.imyeyu.utils.Decoder; import com.imyeyu.utils.Decoder;
import com.imyeyu.utils.OS; import com.imyeyu.utils.OS;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import lombok.Data; import lombok.Data;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.config.CronTask;
import org.springframework.scheduling.config.ScheduledTaskRegistrar; import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -29,6 +33,7 @@ import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Deque; import java.util.Deque;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* 文件服务,不可直接使用入参路径 * 文件服务,不可直接使用入参路径
@@ -65,30 +70,30 @@ public class FileServiceImplement implements TimiJava, FileService {
@PostConstruct @PostConstruct
private void fileSyncPostConstruct() { private void fileSyncPostConstruct() {
// FileSyncConfig config = settingService.fromYaml(SettingKey.SYSTEM_FILE_SYNC, FileSyncConfig.class); FileSyncConfig config = settingService.fromYaml(SettingKey.SYSTEM_FILE_SYNC, FileSyncConfig.class);
// if (config.isEnable()) { if (config.isEnable()) {
// scheduledTaskRegistrar.scheduleCronTask(new CronTask(() -> { scheduledTaskRegistrar.scheduleCronTask(new CronTask(() -> {
// Map<String, FileSyncConfig.Task> tasks = config.getTasks(); Map<String, FileSyncConfig.Task> tasks = config.getTasks();
// configTask: for (Map.Entry<String, FileSyncConfig.Task> item : tasks.entrySet()) { configTask: for (Map.Entry<String, FileSyncConfig.Task> item : tasks.entrySet()) {
// if (item.getValue().isEnable()) { if (item.getValue().isEnable()) {
// List<AbstractAsyncTask> runningTaskList = asyncTaskService.listAll(); List<AbstractAsyncTask> runningTaskList = asyncTaskService.listAll();
// for (int i = 0; i < runningTaskList.size(); i++) { for (int i = 0; i < runningTaskList.size(); i++) {
// AbstractAsyncTask runningTask = runningTaskList.get(i); AbstractAsyncTask runningTask = runningTaskList.get(i);
// if (runningTask.getType() == AsyncTask.Type.FILE_SYNC && runningTask.getName().equals(item.getKey())) { if (runningTask.getType() == AsyncTask.Type.FILE_SYNC && runningTask.getName().equals(item.getKey())) {
// continue configTask; continue configTask;
// } }
// } }
// FileSyncTask task = new FileSyncTask(item.getKey(), item.getValue()); FileSyncTask task = new FileSyncTask(item.getKey(), item.getValue());
// asyncTaskService.addAsyncTask(task); asyncTaskService.addAsyncTask(task);
// } }
// } }
// }, config.getCron())); }, config.getCron()));
// } }
} }
@PostConstruct @PostConstruct
private void fileFilterPostConstruct() { private void fileFilterPostConstruct() {
// option = settingService.fromYaml(SettingKey.SYSTEM_FILE_FILTER, FilterOption.class); option = settingService.fromYaml(SettingKey.SYSTEM_FILE_FILTER, FilterOption.class);
} }
@Override @Override
@@ -257,7 +262,7 @@ public class FileServiceImplement implements TimiJava, FileService {
} }
private String getPath(String path) { private String getPath(String path) {
String basePath = settingService.getAsString(Setting.Module.System.FILE_BASE_PATH); String basePath = settingService.getAsString(SettingKey.SYSTEM_FILE_BASE);
if (TimiJava.isEmpty(path)) { if (TimiJava.isEmpty(path)) {
return basePath; return basePath;
} }

View File

@@ -1,6 +1,6 @@
package com.imyeyu.api.modules.system.service.implement; package com.imyeyu.api.modules.system.service.implement;
import com.imyeyu.api.modules.common.entity.Setting; 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.service.StatusService; import com.imyeyu.api.modules.system.service.StatusService;
@@ -33,7 +33,7 @@ public class StatusServiceImplement implements StatusService {
@Override @Override
public SystemStatusSnapshotView getStatus(String metrics) { public SystemStatusSnapshotView getStatus(String metrics) {
long serverTime = Time.now(); long serverTime = Time.now();
int sampleRateMs = settingService.getAsInt(Setting.Module.System.STATUS_RATE); int sampleRateMs = settingService.getAsInt(SettingKey.SYSTEM_STATUS_RATE);
EnumSet<Metric> selectedMetrics = parseMetrics(metrics); EnumSet<Metric> selectedMetrics = parseMetrics(metrics);
synchronized (serverStatus) { synchronized (serverStatus) {
@@ -48,7 +48,7 @@ public class StatusServiceImplement implements StatusService {
@Override @Override
public SystemStatusHistoryView getStatusHistory(String window, String metrics) { public SystemStatusHistoryView getStatusHistory(String window, String metrics) {
long serverTime = Time.now(); long serverTime = Time.now();
int sampleRateMs = settingService.getAsInt(Setting.Module.System.STATUS_RATE); int sampleRateMs = settingService.getAsInt(SettingKey.SYSTEM_STATUS_RATE);
EnumSet<Metric> selectedMetrics = parseMetrics(metrics); EnumSet<Metric> selectedMetrics = parseMetrics(metrics);
synchronized (serverStatus) { synchronized (serverStatus) {
@@ -72,6 +72,7 @@ public class StatusServiceImplement implements StatusService {
SystemStatusDataView.OS os = new SystemStatusDataView.OS(); SystemStatusDataView.OS os = new SystemStatusDataView.OS();
os.setName(serverStatus.getOs().getName()); os.setName(serverStatus.getOs().getName());
os.setBootAt(serverStatus.getOs().getBootAt()); os.setBootAt(serverStatus.getOs().getBootAt());
os.setUptimeMs(Math.max(0, serverTime - serverStatus.getOs().getBootAt()));
snapshot.setOs(os); snapshot.setOs(os);
} }
if (selectedMetrics.contains(Metric.CPU)) { if (selectedMetrics.contains(Metric.CPU)) {
@@ -79,8 +80,8 @@ public class StatusServiceImplement implements StatusService {
cpu.setModel(serverStatus.getCpu().getName()); cpu.setModel(serverStatus.getCpu().getName());
cpu.setPhysicalCores(serverStatus.getCpu().getCoreCount()); cpu.setPhysicalCores(serverStatus.getCpu().getCoreCount());
cpu.setLogicalCores(serverStatus.getCpu().getLogicalCount()); cpu.setLogicalCores(serverStatus.getCpu().getLogicalCount());
cpu.setUsageTotal(lastDouble(serverStatus.getCpu().getUsed())); cpu.setUsagePercent(lastDouble(serverStatus.getCpu().getUsed()));
cpu.setUsageSystem(lastDouble(serverStatus.getCpu().getSystem())); cpu.setSystemPercent(lastDouble(serverStatus.getCpu().getSystem()));
cpu.setTemperatureCelsius(serverStatus.getCpu().getTemperature()); cpu.setTemperatureCelsius(serverStatus.getCpu().getTemperature());
snapshot.setCpu(cpu); snapshot.setCpu(cpu);
} }
@@ -90,6 +91,7 @@ public class StatusServiceImplement implements StatusService {
Long swapUsedBytes = lastLong(serverStatus.getMemory().getSwapUsed()); Long swapUsedBytes = lastLong(serverStatus.getMemory().getSwapUsed());
memory.setTotalBytes(serverStatus.getMemory().getSize()); memory.setTotalBytes(serverStatus.getMemory().getSize());
memory.setUsedBytes(usedBytes); memory.setUsedBytes(usedBytes);
memory.setUsagePercent(toPercent(usedBytes, serverStatus.getMemory().getSize()));
memory.setSwapTotalBytes(serverStatus.getMemory().getSwapSize()); memory.setSwapTotalBytes(serverStatus.getMemory().getSwapSize());
memory.setSwapUsedBytes(swapUsedBytes); memory.setSwapUsedBytes(swapUsedBytes);
snapshot.setMemory(memory); snapshot.setMemory(memory);
@@ -158,9 +160,9 @@ public class StatusServiceImplement implements StatusService {
item.setPartitionType(partition.getPartitionType()); item.setPartitionType(partition.getPartitionType());
item.setUuid(partition.getUuid()); item.setUuid(partition.getUuid());
item.setMountPoint(partition.getMountPoint()); item.setMountPoint(partition.getMountPoint());
item.setTotal(partition.getTotal()); item.setTotalBytes(partition.getTotalBytes());
item.setUsed(partition.getUsed()); item.setUsedBytes(partition.getUsedBytes());
item.setUsagePercent(toPercent(partition.getUsed(), partition.getTotal())); item.setUsagePercent(toPercent(partition.getUsedBytes(), partition.getTotalBytes()));
item.setTransferTimeMs(partition.getTransferTimeMs()); item.setTransferTimeMs(partition.getTransferTimeMs());
storagePartitions.add(item); storagePartitions.add(item);
} }
@@ -261,7 +263,7 @@ public class StatusServiceImplement implements StatusService {
*/ */
private long parseWindowMs(String window, int sampleRateMs) { private long parseWindowMs(String window, int sampleRateMs) {
if (window == null || window.isBlank()) { if (window == null || window.isBlank()) {
return (long) settingService.getAsInt(Setting.Module.System.STATUS_LIMIT) * sampleRateMs; return (long) settingService.getAsInt(SettingKey.SYSTEM_STATUS_LIMIT) * sampleRateMs;
} }
String normalized = window.trim().toLowerCase(Locale.ROOT); String normalized = window.trim().toLowerCase(Locale.ROOT);
@@ -277,7 +279,7 @@ public class StatusServiceImplement implements StatusService {
try { try {
return Math.max(sampleRateMs, Long.parseLong(valueText) * unit); return Math.max(sampleRateMs, Long.parseLong(valueText) * unit);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
return (long) settingService.getAsInt(Setting.Module.System.STATUS_LIMIT) * sampleRateMs; return (long) settingService.getAsInt(SettingKey.SYSTEM_STATUS_LIMIT) * sampleRateMs;
} }
} }

View File

@@ -0,0 +1,73 @@
package com.imyeyu.api.modules.system.service.implement;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import com.imyeyu.io.IO;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.api.TimiServerAPI;
import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.service.SettingService;
import com.imyeyu.api.modules.system.service.SystemService;
import com.imyeyu.utils.OS;
import org.springframework.boot.SpringApplication;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.naming.NoPermissionException;
import java.io.IOException;
/**
* @author 夜雨
* @version 2024-03-13 00:45
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class SystemServiceImplement implements SystemService {
private final SettingService settingService;
@Override
public void update(MultipartFile file) {
try {
IO.toFile(IO.file("timi-server-api.jar"), file.getInputStream());
} catch (NoPermissionException | IOException e) {
log.error("update core error", e);
throw new TimiException(TimiCode.ERROR).msgKey("TODO update core error");
}
}
@Override
public void restore() {
try {
IO.copy(IO.file("default.jar"), IO.file("timi-server-api.jar").getAbsolutePath());
} catch (NoPermissionException | IOException e) {
log.error("restore core error", e);
throw new TimiException(TimiCode.ERROR).msgKey("TODO restore core error");
}
}
@Override
public void shutdown() {
if (TimiServerAPI.applicationContext instanceof AbstractApplicationContext ctx) {
ctx.close();
}
}
@Async
@Override
public void reboot() {
if (TimiServerAPI.applicationContext instanceof AbstractApplicationContext ctx) {
String command = settingService.getAsString(SettingKey.SYSTEM_REBOOT_COMMAND);
if (TimiJava.isEmpty(command)) {
throw new TimiException(TimiCode.ERROR_NPE_VARIABLE).msgKey("TODO not support reboot");
}
OS.runAfterShutdown(command);
SpringApplication.exit(ctx);
}
}
}

View File

@@ -1,31 +1,29 @@
package com.imyeyu.api.modules.system.service.implement; package com.imyeyu.api.modules.system.service.implement;
import com.fasterxml.jackson.databind.JsonNode; import jakarta.annotation.PostConstruct;
import com.fasterxml.jackson.databind.node.ArrayNode; import lombok.RequiredArgsConstructor;
import com.imyeyu.api.modules.common.entity.Setting; import lombok.extern.slf4j.Slf4j;
import com.imyeyu.api.modules.common.service.SettingService;
import com.imyeyu.api.modules.system.bean.TerminalPipe;
import com.imyeyu.api.modules.system.service.TerminalService;
import com.imyeyu.api.modules.system.vo.terminal.ExecCommand;
import com.imyeyu.java.TimiJava; import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.CallbackArg; import com.imyeyu.java.bean.CallbackArg;
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.common.bean.SettingKey;
import com.imyeyu.api.modules.common.service.SettingService;
import com.imyeyu.api.modules.system.bean.TerminalPipe;
import com.imyeyu.api.modules.system.service.TerminalService;
import com.imyeyu.api.modules.system.vo.terminal.ExecCommand;
import com.imyeyu.spring.TimiSpring; import com.imyeyu.spring.TimiSpring;
import com.imyeyu.utils.OS; import com.imyeyu.utils.OS;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors;
/** /**
* 指令服务 * 指令服务
@@ -52,8 +50,7 @@ public class TerminalServiceImplement implements TerminalService, TimiJava, OS.F
@PostConstruct @PostConstruct
private void postConstruct() { private void postConstruct() {
// ArrayNode array = settingService.getAsArrayNode(Setting.Module.System.TERMINAL_FILTERS); execLogFilters.addAll(List.of(settingService.getAsString(SettingKey.SYSTEM_TERMINAL_FILTERS).split(",")));
// execLogFilters.addAll(array.valueStream().map(JsonNode::asText).collect(Collectors.toSet()));
} }
@Override @Override

View File

@@ -23,7 +23,7 @@ public class UpsServiceImplement implements UpsService {
private final UpsStatusTask upsStatusTask; private final UpsStatusTask upsStatusTask;
private final UpsStatusStore upsStatusStore; private final UpsStatusStore upsStatusStore;
@Value("${ups.collect-rate-ms:3000}") @Value("${ups.collect-rate-ms:60000}")
private long collectRateMs; private long collectRateMs;
@Override @Override

View File

@@ -4,7 +4,6 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ArrayNode;
import com.imyeyu.api.modules.system.bean.DockerStatusStore; import com.imyeyu.api.modules.system.bean.DockerStatusStore;
import com.imyeyu.api.modules.system.util.DockerEngineClient; import com.imyeyu.api.modules.system.util.DockerEngineClient;
import com.imyeyu.java.TimiJava;
import com.imyeyu.utils.Time; import com.imyeyu.utils.Time;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -16,13 +15,12 @@ import org.springframework.scheduling.support.PeriodicTrigger;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
* Docker 容器状态采集任务 * Docker 鐘舵€侀噰闆嗕换鍔?
* *
* @author Codex * @author Codex
* @since 2026-04-06 * @since 2026-04-06
@@ -58,28 +56,20 @@ public class DockerStatusTask implements SchedulingConfigurer {
try { try {
ArrayNode containers = (ArrayNode) dockerEngineClient.getJson("/containers/json", DockerEngineClient.query("all", "true")); ArrayNode containers = (ArrayNode) dockerEngineClient.getJson("/containers/json", DockerEngineClient.query("all", "true"));
long now = Time.now(); long now = Time.now();
Map<String, DockerStatusStore.Container> collectedContainers = new LinkedHashMap<>(); synchronized (dockerStatusStore) {
Set<String> activeIds = new HashSet<>();
for (JsonNode summary : containers) { for (JsonNode summary : containers) {
try { try {
String containerId = getAsString(summary, "Id"); String containerId = getAsString(summary, "Id");
DockerStatusStore.Container container = new DockerStatusStore.Container(); activeIds.add(containerId);
if (TimiJava.isEmpty(container)) { DockerStatusStore.Container container = dockerStatusStore.getContainers().computeIfAbsent(containerId, key -> new DockerStatusStore.Container());
continue;
}
updateContainerSummary(container, summary); updateContainerSummary(container, summary);
updateContainerInspect(containerId, container); updateContainerInspect(containerId, container);
updateContainerStats(containerId, container, now); updateContainerStats(containerId, container, now);
collectedContainers.put(container.getId(), container);
} catch (Exception e) { } catch (Exception e) {
log.error("collect docker container item error", 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())); dockerStatusStore.getContainers().entrySet().removeIf(item -> !activeIds.contains(item.getKey()));
} }
} catch (Exception e) { } catch (Exception e) {
@@ -87,39 +77,6 @@ public class DockerStatusTask implements SchedulingConfigurer {
} }
} }
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) { private void updateContainerSummary(DockerStatusStore.Container container, JsonNode summary) {
container.setId(getAsString(summary, "Id")); container.setId(getAsString(summary, "Id"));
container.setName(trimContainerName(readFirstArrayText(summary, "Names"))); container.setName(trimContainerName(readFirstArrayText(summary, "Names")));
@@ -151,8 +108,8 @@ public class DockerStatusTask implements SchedulingConfigurer {
if (memoryUsageBytes != null && memoryLimitBytes != null && 0 < memoryLimitBytes) { if (memoryUsageBytes != null && memoryLimitBytes != null && 0 < memoryLimitBytes) {
memoryPercent = memoryUsageBytes * 100D / memoryLimitBytes; memoryPercent = memoryUsageBytes * 100D / memoryLimitBytes;
} }
long networkRxBytes = 0L; Long networkRxBytes = 0L;
long networkTxBytes = 0L; Long networkTxBytes = 0L;
JsonNode networks = getAsObject(stats, "networks"); JsonNode networks = getAsObject(stats, "networks");
if (networks != null) { if (networks != null) {
for (Map.Entry<String, JsonNode> item : (Iterable<Map.Entry<String, JsonNode>>) networks::fields) { for (Map.Entry<String, JsonNode> item : (Iterable<Map.Entry<String, JsonNode>>) networks::fields) {
@@ -161,8 +118,8 @@ public class DockerStatusTask implements SchedulingConfigurer {
networkTxBytes += getAsLong(network, "tx_bytes", 0L); networkTxBytes += getAsLong(network, "tx_bytes", 0L);
} }
} }
long blockReadBytes = 0L; Long blockReadBytes = 0L;
long blockWriteBytes = 0L; Long blockWriteBytes = 0L;
JsonNode blkioStats = getAsObject(stats, "blkio_stats"); JsonNode blkioStats = getAsObject(stats, "blkio_stats");
ArrayNode ioServiceBytes = blkioStats == null ? null : getAsArray(blkioStats, "io_service_bytes_recursive"); ArrayNode ioServiceBytes = blkioStats == null ? null : getAsArray(blkioStats, "io_service_bytes_recursive");
if (ioServiceBytes != null) { if (ioServiceBytes != null) {
@@ -177,6 +134,7 @@ public class DockerStatusTask implements SchedulingConfigurer {
} }
} }
Integer pids = getNestedInteger(stats, "pids_stats", "current"); Integer pids = getNestedInteger(stats, "pids_stats", "current");
container.setCpuPercent(cpuPercent); container.setCpuPercent(cpuPercent);
container.setMemoryUsageBytes(memoryUsageBytes); container.setMemoryUsageBytes(memoryUsageBytes);
container.setMemoryLimitBytes(memoryLimitBytes); container.setMemoryLimitBytes(memoryLimitBytes);

View File

@@ -1,6 +1,6 @@
package com.imyeyu.api.modules.system.task; package com.imyeyu.api.modules.system.task;
import com.imyeyu.api.modules.common.entity.Setting; 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.StatusCollectContext;
@@ -63,10 +63,10 @@ public class ServerStatusTask implements SchedulingConfigurer {
collector.collect(context); collector.collect(context);
} }
status.getUpdateAxis().addLast(context.getCollectAt()); status.getUpdateAxis().addLast(context.getCollectAt());
if (settingService.getAsInt(Setting.Module.System.STATUS_LIMIT) < status.getUpdateAxis().size()) { if (settingService.getAsInt(SettingKey.SYSTEM_STATUS_LIMIT) < status.getUpdateAxis().size()) {
status.getUpdateAxis().pollFirst(); status.getUpdateAxis().pollFirst();
} }
} }
}, triggerContext -> new CronTrigger("0/%s * * * * ?".formatted(settingService.getAsInt(Setting.Module.System.STATUS_RATE) / 1000)).nextExecution(triggerContext)); }, triggerContext -> new CronTrigger("0/%s * * * * ?".formatted(settingService.getAsInt(SettingKey.SYSTEM_STATUS_RATE) / 1000)).nextExecution(triggerContext));
} }
} }

View File

@@ -1,12 +1,12 @@
package com.imyeyu.api.modules.system.task; package com.imyeyu.api.modules.system.task;
import com.imyeyu.api.modules.common.entity.Setting; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.TerminalPipe; import com.imyeyu.api.modules.system.bean.TerminalPipe;
import com.imyeyu.api.modules.system.service.TerminalService; import com.imyeyu.api.modules.system.service.TerminalService;
import com.imyeyu.utils.Time; import com.imyeyu.utils.Time;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
@@ -30,10 +30,10 @@ public class TerminalTask {
private void run() { private void run() {
service.listTerminalPipe().entrySet().removeIf(entry -> { service.listTerminalPipe().entrySet().removeIf(entry -> {
if (entry.getValue().getStatus() == TerminalPipe.Status.DIED) { if (entry.getValue().getStatus() == TerminalPipe.Status.DIED) {
log.info("kill the died terminal session: {}", entry.getKey()); log.info("kill the died terminal session: " + entry.getKey());
return true; return true;
} }
long diedAt = entry.getValue().getLastExecAt() + settingService.getAsTime(Setting.Module.System.TERMINAL_TTL); long diedAt = entry.getValue().getLastExecAt() + Time.M * settingService.getAsInt(SettingKey.SYSTEM_TERMINAL_TTL);
if (diedAt < Time.now()) { if (diedAt < Time.now()) {
entry.getValue().destroy(); entry.getValue().destroy();
log.info("kill the timeout terminal session [DIED at: {}]: {}", Time.toDateTime(diedAt), entry.getKey()); log.info("kill the timeout terminal session [DIED at: {}]: {}", Time.toDateTime(diedAt), entry.getKey());

View File

@@ -1,6 +1,6 @@
package com.imyeyu.api.modules.system.task.status; package com.imyeyu.api.modules.system.task.status;
import com.imyeyu.api.modules.common.entity.Setting; import com.imyeyu.api.modules.common.bean.SettingKey;
import java.util.Deque; import java.util.Deque;
@@ -22,7 +22,7 @@ public abstract class AbstractDequeStatusCollector implements StatusCollector {
*/ */
protected <T> void putDeque(StatusCollectContext context, Deque<T> deque, T value) { protected <T> void putDeque(StatusCollectContext context, Deque<T> deque, T value) {
deque.addLast(value); deque.addLast(value);
if (context.getSettingService().getAsInt(Setting.Module.System.STATUS_LIMIT) < deque.size()) { if (context.getSettingService().getAsInt(SettingKey.SYSTEM_STATUS_LIMIT) < deque.size()) {
deque.pollFirst(); deque.pollFirst();
} }
} }

View File

@@ -38,10 +38,10 @@ public class CpuStatusCollector extends AbstractDequeStatusCollector {
long irq = ticks[CentralProcessor.TickType.IRQ.getIndex()] - lastCpuTicks[CentralProcessor.TickType.IRQ.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 softIrq = ticks[CentralProcessor.TickType.SOFTIRQ.getIndex()] - lastCpuTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()];
long steal = ticks[CentralProcessor.TickType.STEAL.getIndex()] - lastCpuTicks[CentralProcessor.TickType.STEAL.getIndex()]; long steal = ticks[CentralProcessor.TickType.STEAL.getIndex()] - lastCpuTicks[CentralProcessor.TickType.STEAL.getIndex()];
double total = user + nice + sys + idle + ioWait + irq + softIrq + steal; long total = user + nice + sys + idle + ioWait + irq + softIrq + steal;
if (0 < total) { if (0 < total) {
putDeque(context, context.getStatus().getCpu().getSystem(), sys / total); putDeque(context, context.getStatus().getCpu().getSystem(), 100D * sys / total);
putDeque(context, context.getStatus().getCpu().getUsed(), 1 - idle / total); putDeque(context, context.getStatus().getCpu().getUsed(), 100 - 100D * idle / total);
} }
} }
lastCpuTicks = ticks; lastCpuTicks = ticks;

View File

@@ -1,9 +1,10 @@
package com.imyeyu.api.modules.system.task.status.collector; package com.imyeyu.api.modules.system.task.status.collector;
import com.imyeyu.api.modules.common.entity.Setting; 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.AbstractDequeStatusCollector;
import com.imyeyu.api.modules.system.task.status.StatusCollectContext; import com.imyeyu.api.modules.system.task.status.StatusCollectContext;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.bytedeco.librealsense.context;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import oshi.hardware.NetworkIF; import oshi.hardware.NetworkIF;
@@ -24,7 +25,7 @@ public class NetworkStatusCollector extends AbstractDequeStatusCollector {
@Override @Override
public void initialize(StatusCollectContext context) { public void initialize(StatusCollectContext context) {
List<NetworkIF> networkIFs = context.getHardware().getNetworkIFs(); List<NetworkIF> networkIFs = context.getHardware().getNetworkIFs();
String targetMac = context.getSettingService().getAsString(Setting.Module.System.STATUS_NETWORK_MAC); String targetMac = context.getSettingService().getAsString(SettingKey.SYSTEM_STATUS_NETWORK_MAC);
for (NetworkIF networkIF : networkIFs) { for (NetworkIF networkIF : networkIFs) {
if (networkIF.getMacaddr().equals(targetMac)) { if (networkIF.getMacaddr().equals(targetMac)) {
networkIF.updateAttributes(); networkIF.updateAttributes();
@@ -41,8 +42,8 @@ public class NetworkStatusCollector extends AbstractDequeStatusCollector {
@Override @Override
public void collect(StatusCollectContext context) { public void collect(StatusCollectContext context) {
String targetMac = context.getSettingService().getAsString(Setting.Module.System.STATUS_NETWORK_MAC); String targetMac = context.getSettingService().getAsString(SettingKey.SYSTEM_STATUS_NETWORK_MAC);
int sampleRateMs = context.getSettingService().getAsInt(Setting.Module.System.STATUS_RATE); int sampleRateMs = context.getSettingService().getAsInt(SettingKey.SYSTEM_STATUS_RATE);
for (NetworkIF networkIF : context.getHardware().getNetworkIFs()) { for (NetworkIF networkIF : context.getHardware().getNetworkIFs()) {
if (networkIF.getMacaddr().equals(targetMac)) { if (networkIF.getMacaddr().equals(targetMac)) {
networkIF.updateAttributes(); networkIF.updateAttributes();

View File

@@ -38,13 +38,13 @@ public class StorageStatusCollector implements StatusCollector {
item.setPartitionType(partition.getType()); item.setPartitionType(partition.getType());
item.setUuid(partition.getUuid()); item.setUuid(partition.getUuid());
item.setMountPoint(partition.getMountPoint()); item.setMountPoint(partition.getMountPoint());
item.setTotal(partition.getSize()); item.setTotalBytes(partition.getSize());
item.setTransferTimeMs(diskStore.getTransferTime()); item.setTransferTimeMs(diskStore.getTransferTime());
OSFileStore fileStore = matchFileStore(partition, fileStoreMap); OSFileStore fileStore = matchFileStore(partition, fileStoreMap);
if (fileStore != null) { if (fileStore != null) {
fileStore.updateAttributes(); fileStore.updateAttributes();
item.setUsed(fileStore.getTotalSpace() - fileStore.getUsableSpace()); item.setUsedBytes(fileStore.getTotalSpace() - fileStore.getUsableSpace());
} }
context.getStatus().getStoragePartitions().add(item); context.getStatus().getStoragePartitions().add(item);
} }

View File

@@ -18,7 +18,6 @@ import java.net.http.HttpResponse;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel; import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Duration; import java.time.Duration;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@@ -74,28 +73,6 @@ public class DockerEngineClient {
} }
} }
/**
* 判断 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) { private String buildRequestPath(String path, Map<String, String> queryParams) {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
builder.append("/"); builder.append("/");

View File

@@ -1,9 +1,9 @@
package com.imyeyu.api.modules.system.util; package com.imyeyu.api.modules.system.util;
import com.imyeyu.api.modules.common.entity.Setting;
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;
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.spring.TimiSpring; import com.imyeyu.spring.TimiSpring;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
@@ -33,8 +33,8 @@ public class SystemAPIInterceptor implements HandlerInterceptor {
if (TimiJava.isEmpty(key)) { if (TimiJava.isEmpty(key)) {
key = req.getParameter("token"); key = req.getParameter("token");
} }
String dbKey = settingService.getAsString(Setting.Module.System.API_KEY); String dbKey = settingService.getAsString(SettingKey.SYSTEM_API_KEY);
String dbSuperKey = settingService.getAsString(Setting.Module.System.API_KEY_SUPER); String dbSuperKey = settingService.getAsString(SettingKey.SYSTEM_API_SUPER_KEY);
if (dbSuperKey.equals(key)) { if (dbSuperKey.equals(key)) {
TimiSpring.setRequestAttr(IS_SUPER_KEY, true); TimiSpring.setRequestAttr(IS_SUPER_KEY, true);
return true; return true;

View File

@@ -101,6 +101,9 @@ public class SystemStatusDataView {
/** 启动时间 */ /** 启动时间 */
private long bootAt; private long bootAt;
/** 运行时长 */
private long uptimeMs;
} }
/** /**
@@ -122,10 +125,10 @@ public class SystemStatusDataView {
private int logicalCores; private int logicalCores;
/** 总占用 */ /** 总占用 */
private Double usageTotal; private Double usagePercent;
/** 系统占用 */ /** 系统占用 */
private Double usageSystem; private Double systemPercent;
/** 温度 */ /** 温度 */
private double temperatureCelsius; private double temperatureCelsius;
@@ -146,6 +149,9 @@ public class SystemStatusDataView {
/** 已用内存 */ /** 已用内存 */
private Long usedBytes; private Long usedBytes;
/** 使用率 */
private Double usagePercent;
/** 交换分区总量 */ /** 交换分区总量 */
private long swapTotalBytes; private long swapTotalBytes;
@@ -358,10 +364,10 @@ public class SystemStatusDataView {
private String mountPoint; private String mountPoint;
/** 分区总空间 */ /** 分区总空间 */
private long total; private long totalBytes;
/** 已用空间 */ /** 已用空间 */
private Long used; private Long usedBytes;
/** 使用率 */ /** 使用率 */
private Double usagePercent; private Double usagePercent;

View File

@@ -1,11 +1,18 @@
package com.imyeyu.api.util; package com.imyeyu.api.util;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.entity.Multilingual;
import com.imyeyu.api.modules.common.entity.Setting; 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.java.TimiJava; import com.imyeyu.api.modules.system.bean.ServerFile;
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.TimiSpring;
import com.imyeyu.spring.util.GlobalReturnHandler; import com.imyeyu.spring.util.GlobalReturnHandler;
import com.imyeyu.spring.util.Redis;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.jgit.api.ArchiveCommand; import org.eclipse.jgit.api.ArchiveCommand;
@@ -16,7 +23,10 @@ import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects;
/** /**
* SpringBoot 启动事件,主要输出基本参数,避免混淆运行环境 * SpringBoot 启动事件,主要输出基本参数,避免混淆运行环境
@@ -47,6 +57,7 @@ public class InitApplication implements ApplicationRunner {
private final SettingService settingService; private final SettingService settingService;
private final RedisMultilingual redisMultilingual; private final RedisMultilingual redisMultilingual;
private final GlobalReturnHandler globalReturnHandler; private final GlobalReturnHandler globalReturnHandler;
private final Redis<Long, Multilingual> redisLanguage;
private void logBaseInfo() { private void logBaseInfo() {
log.info("JDBC URL: {}", jdbcURL); log.info("JDBC URL: {}", jdbcURL);
@@ -54,7 +65,7 @@ public class InitApplication implements ApplicationRunner {
log.info("System Setting:"); log.info("System Setting:");
List<Setting> settings = settingService.listAll(); List<Setting> settings = settingService.listAll();
for (Setting setting : settings) { for (Setting setting : settings) {
String value = TimiJava.defaultIfEmpty(setting.getValue(), ""); String value = Objects.requireNonNullElse(setting.getValue(), "");
if (64 < value.length()) { if (64 < value.length()) {
value = value.substring(0, 64) + ".."; value = value.substring(0, 64) + "..";
} }
@@ -75,13 +86,38 @@ public class InitApplication implements ApplicationRunner {
}); });
} }
private void initFileType() {
ObjectNode items = settingService.getAsJsonObject(SettingKey.SYSTEM_FILE_TYPE);
String[] extensions;
ArrayNode extensionsArray;
JsonNode itemObject;
List<String> extensionsList;
for (Map.Entry<String, JsonNode> item : (Iterable<Map.Entry<String, JsonNode>>) items::fields) {
ServerFile.FileType fileType = Ref.toType(ServerFile.FileType.class, item.getKey());
itemObject = item.getValue();
extensionsList = new ArrayList<>();
extensionsArray = (ArrayNode) itemObject.get("extensions");
for (JsonNode extensionNode : extensionsArray) {
if (extensionNode.isObject()) {
extensionsList.add(extensionNode.path("value").asText());
} else {
extensionsList.add(extensionNode.asText());
}
}
extensions = new String[extensionsList.size()];
// 设置扩展名所属文件类型
fileType.setExtensions(extensionsList.toArray(extensions));
}
}
@Override @Override
public void run(ApplicationArguments args) throws Exception { public void run(ApplicationArguments args) throws Exception {
Method[] methods = getClass().getDeclaredMethods(); Method[] methods = getClass().getDeclaredMethods();
for (Method method : methods) { for (int i = 0; i < methods.length; i++) {
if (!method.getName().equals("run") && !method.getName().contains("$")) { if (!methods[i].getName().equals("run") && !methods[i].getName().contains("$")) {
method.setAccessible(true); methods[i].setAccessible(true);
method.invoke(this); methods[i].invoke(this);
} }
} }
} }

View File

@@ -1,9 +1,5 @@
server: server:
shutdown: graceful shutdown: graceful
# 压缩
compression:
enable: true
min-response-size: 10KB
# 开发环境语言,激活开发配置时,多语言系统始终使用此语言环境 # 开发环境语言,激活开发配置时,多语言系统始终使用此语言环境
dev: dev: