Compare commits

25 Commits

Author SHA1 Message Date
45c9fc814a Merge pull request 'v1.0.0' (#4) from dev into master
Reviewed-on: #4
2026-04-09 05:16:04 +00:00
Timi
78163441dd v1.0.0
All checks were successful
CI / build-deploy (pull_request) Successful in 16s
CI / notify-on-failure (pull_request) Has been skipped
2026-04-09 13:14:20 +08:00
407dc13ac4 Merge pull request 'v1.0.0' (#3) from dev into master
Reviewed-on: #3
2026-04-09 04:09:16 +00:00
Timi
9762be1244 v1.0.0
Some checks failed
CI / build-deploy (pull_request) Failing after 51s
CI / notify-on-failure (pull_request) Successful in 0s
2026-04-09 12:08:24 +08:00
0c06bf16c2 Merge pull request 'v1.0.0' (#2) from dev into master
Reviewed-on: #2
2026-04-09 04:03:43 +00:00
Timi
971cad7365 v1.0.0
Some checks failed
CI / build-deploy (pull_request) Failing after 5s
CI / notify-on-failure (pull_request) Successful in 0s
2026-04-09 12:03:15 +08:00
1db39e77d3 Merge pull request 'v1.0.0' (#1) from dev into master
Reviewed-on: #1
2026-04-08 08:48:12 +00:00
Timi
b5e9da0e9b v1.0.0
Some checks failed
CI / build-deploy (pull_request) Failing after 3s
CI / notify-on-failure (pull_request) Successful in 0s
2026-04-08 16:30:10 +08:00
Timi
34e1ac6264 remove gson 2026-04-08 12:00:52 +08:00
Timi
ef192daa93 add export config 2026-04-08 11:56:07 +08:00
Timi
8947269351 remove gson 2026-04-08 11:25:45 +08:00
Timi
b6a58b7376 update system status api, add UPS/Docker status api 2026-04-07 20:12:52 +08:00
Timi
cd7bc31e6b add upload permission 2026-01-29 16:27:52 +08:00
Timi
e619f3a1e2 add ClipboardService 2026-01-15 13:47:38 +08:00
Timi
230864fa43 fix comment page 2026-01-15 13:03:57 +08:00
Timi
fa11b388db refactor TempFileService, use Attachment and mongodb 2026-01-05 14:35:38 +08:00
Timi
b6eb7980e4 remove AttachmentRequest and AttachmentView 2026-01-04 19:17:42 +08:00
Timi
3410c540ab remove Attachment.ext 2026-01-04 19:13:02 +08:00
Timi
040a46934d update BaseMapper list rename to select 2026-01-04 19:12:47 +08:00
Timi
7eb409f6d0 add MALL travel location type 2025-12-23 14:46:21 +08:00
Timi
7b3d4e2a65 nullable ttl arg for temp file 2025-12-22 11:01:21 +08:00
Timi
4e113e8fae update firstNotNull 2025-12-22 11:00:52 +08:00
Timi
7a34a481aa add ttl arg for tempFileUpload 2025-12-19 20:15:48 +08:00
Timi
a402ea86ef fix circular dependency 2025-12-19 19:47:45 +08:00
Timi
3eaa560aec sync travel updatedAt 2025-12-18 21:09:21 +08:00
146 changed files with 5224 additions and 2058 deletions

333
.gitea/workflows/ci.yml Normal file
View File

@@ -0,0 +1,333 @@
name: CI
on:
pull_request:
branches:
- master
types:
- closed
jobs:
build-deploy:
runs-on: act_runner_java
if: ${{ github.event.pull_request.merged == true }}
outputs:
deployment_status: ${{ steps.set_status.outputs.status }}
env:
JAVA_HOME: /usr/lib/jvm/java-21-openjdk
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up environment
run: |
echo "PR #${{ github.event.number }} merged into master"
echo "Source branch: ${{ github.event.pull_request.head.ref }}"
echo "Target branch: ${{ github.event.pull_request.base.ref }}"
- name: Run tests
run: |
echo "Running test suite..."
- name: Setup Maven settings
run: |
if [ -z "${{ vars.TIMI_NEXUS_USERNAME }}" ] || [ -z "${{ vars.TIMI_NEXUS_PASSWORD }}" ]; then
echo "Missing vars.TIMI_NEXUS_USERNAME or vars.TIMI_NEXUS_PASSWORD"
exit 1
fi
mkdir -p ~/.m2
cat > ~/.m2/settings.xml <<EOF
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<servers>
<server>
<id>timi_nexus</id>
<username>${{ vars.TIMI_NEXUS_USERNAME }}</username>
<password>${{ vars.TIMI_NEXUS_PASSWORD }}</password>
</server>
</servers>
</settings>
EOF
- name: Build project
run: |
mvn -B -DskipTests clean package -P prod-linux
- name: Deploy service
if: success()
env:
CONTAINER_NAME: ${{ vars.CONTAINER_NAME }}
CONTAINER_TARGET_PATH: ${{ vars.CONTAINER_TARGET_PATH }}
MAX_RETRIES: 3
RETRY_DELAY: 10
run: |
if [ -z "$CONTAINER_NAME" ] || [ -z "$CONTAINER_TARGET_PATH" ]; then
echo "Missing production environment variables"
echo "Required: CONTAINER_NAME, CONTAINER_TARGET_PATH"
exit 1
fi
retry_command() {
local cmd="$1"
local desc="$2"
local attempt=1
while [ $attempt -le $MAX_RETRIES ]; do
echo "[$desc] Attempt $attempt/$MAX_RETRIES..."
if eval "$cmd"; then
echo "OK: $desc succeeded"
return 0
fi
echo "FAIL: $desc failed (attempt $attempt/$MAX_RETRIES)"
if [ $attempt -lt $MAX_RETRIES ]; then
echo "Retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY
fi
attempt=$((attempt + 1))
done
echo "FAIL: $desc failed after $MAX_RETRIES attempts"
return 1
}
version=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version)
artifact_id=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.artifactId)
jar_file="target/${artifact_id}-${version}.jar"
if [ ! -f "$jar_file" ]; then
echo "Build artifact not found: $jar_file"
exit 1
fi
if ! command -v docker >/dev/null 2>&1; then
echo "docker command not found in runner environment"
exit 1
fi
if ! docker inspect "$CONTAINER_NAME" >/dev/null 2>&1; then
echo "Docker container not found: $CONTAINER_NAME"
exit 1
fi
target_jar="${artifact_id}.jar"
container_target="${CONTAINER_TARGET_PATH%/}/$target_jar"
echo "Deploying $jar_file to container $CONTAINER_NAME:$container_target"
if ! retry_command "docker cp \"$jar_file\" \"$CONTAINER_NAME:$container_target\"" "Docker copy"; then
exit 1
fi
echo "Restarting Docker container: $CONTAINER_NAME"
if ! retry_command "docker restart \"$CONTAINER_NAME\"" "Docker restart"; then
exit 1
fi
echo "Deployment completed successfully"
- name: Create release
if: ${{ success() && startsWith(github.event.pull_request.title, 'v') }}
env:
GITEA_TOKEN: ${{ secrets.RUNNER_TOKEN }}
GITEA_SERVER_URL: ${{ github.server_url }}
GITEA_INTERNAL_URL: ${{ vars.TIMI_GITEA_INTERNAL_URL }}
GITEA_REPOSITORY: ${{ github.repository }}
RELEASE_TAG: ${{ github.event.pull_request.title }}
RELEASE_TARGET: ${{ github.sha }}
MAX_RETRIES: 3
RETRY_DELAY: 10
run: |
if [ -z "$GITEA_TOKEN" ]; then
echo "Missing secrets.RUNNER_TOKEN"
exit 1
fi
if [ -n "$GITEA_INTERNAL_URL" ]; then
api_base_url="$GITEA_INTERNAL_URL"
echo "Using internal Gitea URL: $api_base_url"
else
api_base_url="$GITEA_SERVER_URL"
echo "Using public Gitea URL: $api_base_url"
fi
version=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version)
artifact_id=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.artifactId)
jar_file="target/${artifact_id}-${version}.jar"
if [ ! -f "$jar_file" ]; then
echo "Build artifact not found: $jar_file"
exit 1
fi
file_size=$(stat -c%s "$jar_file" 2>/dev/null || stat -f%z "$jar_file" 2>/dev/null || echo "unknown")
echo "Found fat jar: $jar_file (size: $file_size bytes)"
api_url="$api_base_url/api/v1/repos/$GITEA_REPOSITORY/releases"
payload=$(cat <<EOF
{
"tag_name": "$RELEASE_TAG",
"name": "$RELEASE_TAG",
"target_commitish": "$RELEASE_TARGET",
"draft": false,
"prerelease": false
}
EOF
)
echo "Creating release with tag: $RELEASE_TAG"
echo "API URL: $api_url"
echo "Target commit: $RELEASE_TARGET"
release_response_file=$(mktemp /tmp/release_response_XXXXXX.json)
trap "rm -f $release_response_file" EXIT
release_id=""
attempt=1
while [ $attempt -le $MAX_RETRIES ] && [ -z "$release_id" ]; do
echo "[Create release] Attempt $attempt/$MAX_RETRIES..."
> "$release_response_file"
http_code=$(curl -sS -w "%{http_code}" -o "$release_response_file" -X POST "$api_url" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
--connect-timeout 30 \
--max-time 60 \
-d "$payload" 2>/dev/null) || http_code="000"
response=$(cat "$release_response_file" 2>/dev/null || echo "{}")
echo "HTTP Status: $http_code"
if [ "$http_code" = "201" ]; then
if command -v jq >/dev/null 2>&1; then
release_id=$(echo "$response" | jq -r '.id' 2>/dev/null)
else
release_id=$(echo "$response" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 | tr -d '\n\r')
fi
echo "OK: Release created: id=$release_id"
elif [ "$http_code" = "409" ]; then
echo "Release already exists (HTTP 409), fetching existing release..."
existing=$(curl -sS "$api_url" -H "Authorization: token $GITEA_TOKEN" --connect-timeout 30 2>/dev/null || echo "[]")
if command -v jq >/dev/null 2>&1; then
release_id=$(echo "$existing" | jq -r ".[] | select(.tag_name==\"$RELEASE_TAG\") | .id" 2>/dev/null | head -1)
else
release_id=$(echo "$existing" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 | tr -d '\n\r')
fi
if [ -n "$release_id" ]; then
echo "OK: Found existing release: id=$release_id"
else
echo "FAIL: Could not find existing release id"
fi
else
echo "FAIL: Create release failed (HTTP $http_code)"
if [ $attempt -lt $MAX_RETRIES ]; then
echo "Retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY
fi
fi
attempt=$((attempt + 1))
done
if [ -z "$release_id" ]; then
echo "FAIL: Failed to create or find release after $MAX_RETRIES attempts"
exit 1
fi
asset_name=$(basename "$jar_file")
echo "Uploading asset: $asset_name (size: $file_size bytes)"
upload_url="$api_url/$release_id/assets?name=$asset_name"
echo "Upload URL: $upload_url"
asset_response_file=$(mktemp /tmp/asset_response_XXXXXX.json)
trap "rm -f $release_response_file $asset_response_file" EXIT
upload_success=false
attempt=1
while [ $attempt -le $MAX_RETRIES ] && [ "$upload_success" = "false" ]; do
echo "[Upload asset] Attempt $attempt/$MAX_RETRIES..."
> "$asset_response_file"
http_code=$(curl -sS -w "%{http_code}" -o "$asset_response_file" -X POST "$upload_url" \
-H "Authorization: token $GITEA_TOKEN" \
--connect-timeout 30 \
--max-time 300 \
-F "attachment=@$jar_file" 2>/dev/null) || http_code="000"
if [ "$http_code" = "201" ]; then
upload_success=true
echo "OK: Successfully uploaded: $asset_name"
else
echo "FAIL: Upload failed (HTTP $http_code)"
cat "$asset_response_file" 2>/dev/null || true
fi
if [ "$upload_success" = "false" ] && [ $attempt -lt $MAX_RETRIES ]; then
echo "Retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY
fi
attempt=$((attempt + 1))
done
if [ "$upload_success" = "false" ]; then
echo "FAIL: Failed to upload asset after $MAX_RETRIES attempts"
exit 1
fi
- name: Mark deployment success
id: set_status
if: always()
run: |
echo "status=success" >> $GITHUB_OUTPUT
notify-on-failure:
runs-on: act_runner_java
needs: build-deploy
if: ${{ always() && github.event.pull_request.merged == true && needs.build-deploy.result == 'failure' }}
steps:
- name: Notify CI failure
env:
PR_NUMBER: ${{ github.event.number }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_URL: ${{ github.event.pull_request.html_url }}
SOURCE_BRANCH: ${{ github.event.pull_request.head.ref }}
AUTHOR: ${{ github.event.pull_request.user.login }}
COMMIT_SHA: ${{ github.sha }}
REPO: ${{ github.repository }}
SERVER_URL: ${{ github.server_url }}
WEBHOOK_URL: ${{ vars.NOTIFY_WEBHOOK_URL }}
run: |
echo "========================================="
echo "CI Pipeline Failed - Manual Review Required"
echo "========================================="
echo ""
echo "PR: #$PR_NUMBER - $PR_TITLE"
echo "Branch: $SOURCE_BRANCH"
echo "Author: $AUTHOR"
echo "Commit: $COMMIT_SHA"
echo ""
echo "Actions:"
echo " 1. Re-run CI: $SERVER_URL/$REPO/actions"
echo " 2. Revert PR: $PR_URL (click 'Revert' button)"
echo ""
echo "========================================="
if [ -n "$WEBHOOK_URL" ]; then
message="CI 部署失败\n\nPR: #$PR_NUMBER - $PR_TITLE\n分支: $SOURCE_BRANCH\n提交者: $AUTHOR\n\n请检查并决定:\n- 重试 CI\n- 回滚合并"
payload=$(cat <<EOF
{
"msgtype": "text",
"text": {
"content": "$message"
}
}
EOF
)
curl -sS -X POST "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "$payload" || echo "Warning: Failed to send notification"
echo "OK: Notification sent"
else
echo "Note: Set vars.NOTIFY_WEBHOOK_URL to enable webhook notifications"
fi

4
.gitignore vendored
View File

@@ -4,7 +4,9 @@
/logs /logs
/target /target
/temp /temp
CLAUDE.md /.claude
/CLAUDE.md
/AGENTS.md
multilingualField/ multilingualField/
!.mvn/wrapper/maven-wrapper.jar !.mvn/wrapper/maven-wrapper.jar

39
pom.xml
View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.0</version> <version>3.5.11</version>
<relativePath/> <relativePath/>
</parent> </parent>
@@ -17,19 +17,12 @@
<description>imyeyu.com API</description> <description>imyeyu.com API</description>
<properties> <properties>
<springboot.version>3.4.0</springboot.version> <springboot.version>3.5.11</springboot.version>
<maven.compiler.source>21</maven.compiler.source> <maven.compiler.source>21</maven.compiler.source>
<maven.compiler.multilingualField>21</maven.compiler.multilingualField> <maven.compiler.multilingualField>21</maven.compiler.multilingualField>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
<repositories>
<repository>
<id>apache-maven</id>
<url>https://repo.maven.apache.org/maven2/</url>
</repository>
</repositories>
<profiles> <profiles>
<profile> <profile>
<id>dev-windows</id> <id>dev-windows</id>
@@ -112,22 +105,38 @@
<configuration> <configuration>
<excludeDevtools>true</excludeDevtools> <excludeDevtools>true</excludeDevtools>
<mainClass>com.imyeyu.api.TimiServerAPI</mainClass> <mainClass>com.imyeyu.api.TimiServerAPI</mainClass>
<finalName>${project.artifactId}</finalName>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
<repositories>
<repository>
<id>apache-maven</id>
<url>https://repo.maven.apache.org/maven2/</url>
</repository>
<repository>
<id>timi_nexus</id>
<url>https://nexus.imyeyu.com/repository/maven-public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>com.imyeyu.spring</groupId> <groupId>com.imyeyu.spring</groupId>
<artifactId>timi-spring</artifactId> <artifactId>timi-spring</artifactId>
<version>0.0.2</version> <version>0.0.10</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.imyeyu.network</groupId> <groupId>com.imyeyu.network</groupId>
<artifactId>timi-network</artifactId> <artifactId>timi-network</artifactId>
<version>0.0.2</version> <version>0.0.8</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.imyeyu.lang</groupId> <groupId>com.imyeyu.lang</groupId>
@@ -184,12 +193,12 @@
<dependency> <dependency>
<groupId>org.eclipse.jgit</groupId> <groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit</artifactId> <artifactId>org.eclipse.jgit</artifactId>
<version>6.7.0.202309050840-r</version> <version>7.2.1.202505142326-r</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.eclipse.jgit</groupId> <groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit.archive</artifactId> <artifactId>org.eclipse.jgit.archive</artifactId>
<version>6.7.0.202309050840-r</version> <version>7.2.1.202505142326-r</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
@@ -220,7 +229,7 @@
<dependency> <dependency>
<groupId>org.apache.tika</groupId> <groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId> <artifactId>tika-core</artifactId>
<version>2.9.2</version> <version>3.2.2</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jcodec</groupId> <groupId>org.jcodec</groupId>

View File

@@ -1,10 +1,6 @@
package com.imyeyu.api; package com.imyeyu.api;
import com.imyeyu.io.IO; import com.imyeyu.io.IO;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.Language;
import com.imyeyu.java.ref.Ref;
import com.imyeyu.spring.TimiSpring;
import com.imyeyu.utils.OS; import com.imyeyu.utils.OS;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -13,7 +9,6 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
import org.springframework.core.env.Environment;
import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.EnableTransactionManagement;
import java.io.File; import java.io.File;
@@ -31,8 +26,6 @@ import java.io.File;
@EnableTransactionManagement @EnableTransactionManagement
public class TimiServerAPI implements OS.FileSystem, ApplicationContextAware { public class TimiServerAPI implements OS.FileSystem, ApplicationContextAware {
private static final String DEV_LANG_CONFIG = "dev.lang";
public static ApplicationContext applicationContext; public static ApplicationContext applicationContext;
@Override @Override
@@ -40,32 +33,18 @@ public class TimiServerAPI implements OS.FileSystem, ApplicationContextAware {
TimiServerAPI.applicationContext = applicationContext; TimiServerAPI.applicationContext = applicationContext;
} }
public static Language.Enum getUserLanguage() {
Language.Enum userLanguage = TimiSpring.getLanguage();
Environment env = applicationContext.getBean(Environment.class);
if (env.containsProperty(DEV_LANG_CONFIG)) {
String property = env.getProperty(DEV_LANG_CONFIG);
if (TimiJava.isNotEmpty(property)) {
userLanguage = Ref.toType(Language.Enum.class, property);
}
}
return userLanguage;
}
public static void main(String[] args) { public static void main(String[] args) {
try { try {
{ {
// 导出配置 File application = new File("config" + SEP + "application.yml");
String[] files = {"application.yml", "logback.xml"}; if (!application.exists()) {
for (int i = 0; i < files.length; i++) { IO.resourceToDisk(TimiServerAPI.class, "application_export.yml", application.getAbsolutePath());
File file = new File("config" + SEP + files[i]); }
if (!file.exists() || !file.isFile()) { File logback = new File("config" + SEP + "logback.xml");
log.info("exporting default config at {}", file.getAbsolutePath()); if (!logback.exists()) {
IO.resourceToDisk(TimiServerAPI.class, files[i], file.getAbsolutePath()); IO.resourceToDisk(TimiServerAPI.class, "logback.xml", logback.getAbsolutePath());
}
} }
} }
// 启动 SpringBoot // 启动 SpringBoot
SpringApplication.run(TimiServerAPI.class, args); SpringApplication.run(TimiServerAPI.class, args);
} catch (Exception e) { } catch (Exception e) {

View File

@@ -1,18 +0,0 @@
package com.imyeyu.api.bean;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.yaml.snakeyaml.Yaml;
/**
* @author 夜雨
* @since 2025-01-13 11:42
*/
@Component
public class IOCBeans {
@Bean
public Yaml yaml() {
return new Yaml();
}
}

View File

@@ -1,7 +1,12 @@
package com.imyeyu.api.config; package com.imyeyu.api.config;
import com.google.gson.Gson; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.imyeyu.utils.Time;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.yaml.snakeyaml.Yaml;
/** /**
* @author 夜雨 * @author 夜雨
@@ -10,7 +15,17 @@ import org.springframework.context.annotation.Configuration;
@Configuration @Configuration
public class BeanConfig { public class BeanConfig {
public Gson gson() { @Bean
return new Gson(); public ObjectMapper jackson() {
ObjectMapper mapper = new ObjectMapper();
mapper.setDateFormat(Time.dateTime);
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return mapper;
}
@Bean
public Yaml yaml() {
return new Yaml();
} }
} }

View File

@@ -29,21 +29,12 @@ public class CORSConfig {
/** 允许跨域的地址 */ /** 允许跨域的地址 */
private String[] allowOrigin; private String[] allowOrigin;
/** 是否允许请求带有验证信息 */
private boolean allowCredentials;
/** 允许请求的方法 */
private String allowMethods;
/** 允许服务端访问的客户端请求头 */
private String allowHeaders;
@Bean @Bean
public FilterRegistrationBean<Filter> corsFilter() { public FilterRegistrationBean<Filter> corsFilter() {
CorsConfiguration config = new CorsConfiguration(); CorsConfiguration config = new CorsConfiguration();
config.addAllowedHeader(allowHeaders); config.addAllowedHeader("*");
config.addAllowedMethod(allowMethods); config.addAllowedMethod("*");
config.setAllowCredentials(allowCredentials); config.setAllowCredentials(true);
config.setAllowedOriginPatterns(Arrays.asList(allowOrigin)); config.setAllowedOriginPatterns(Arrays.asList(allowOrigin));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

View File

@@ -1,13 +1,17 @@
package com.imyeyu.api.config; package com.imyeyu.api.config;
import lombok.Data; import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.EqualsAndHashCode;
import com.imyeyu.api.modules.blog.entity.ArticleRanking; import com.imyeyu.api.modules.blog.entity.ArticleRanking;
import com.imyeyu.api.modules.common.entity.Multilingual; import com.imyeyu.api.modules.common.entity.Multilingual;
import com.imyeyu.spring.bean.RedisConfigParams; import com.imyeyu.spring.bean.RedisConfigParams;
import com.imyeyu.spring.config.AbstractRedisConfig; import com.imyeyu.spring.config.AbstractRedisConfig;
import com.imyeyu.spring.util.Redis; import com.imyeyu.spring.util.Redis;
import com.imyeyu.spring.util.RedisSerializers; import com.imyeyu.spring.util.RedisSerializers;
import com.imyeyu.utils.Time;
import io.lettuce.core.api.StatefulConnection;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -32,9 +36,12 @@ import java.time.Duration;
@Configuration @Configuration
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@EnableAutoConfiguration @EnableAutoConfiguration
@RequiredArgsConstructor
@ConfigurationProperties(prefix = "spring.redis") @ConfigurationProperties(prefix = "spring.redis")
public class RedisConfig extends AbstractRedisConfig { public class RedisConfig extends AbstractRedisConfig {
private final ObjectMapper jackson;
// ---------- 连接配置 ---------- // ---------- 连接配置 ----------
/** 地址 */ /** 地址 */
@@ -46,50 +53,9 @@ public class RedisConfig extends AbstractRedisConfig {
/** 密码 */ /** 密码 */
private String password; private String password;
/** 超时(毫秒) */
private int timeout;
/** 连接池 */
private Lettuce lettuce;
/** 数据库 */ /** 数据库 */
private Database database; private Database database;
/**
* 连接池
*
* @author 夜雨
* @since 2023-08-21 16:23
*/
@Data
public static class Lettuce {
/** 配置 */
private Pool pool;
/**
* 配置
*
* @author 夜雨
* @since 2023-08-21 16:23
*/
@Data
public static class Pool {
/** 最大活跃连接 */
private int maxActive;
/** 最小空闲连接 */
private int minIdle;
/** 最大空闲连接 */
private int maxIdle;
/** 最大等待时间(秒) */
private int maxWait;
}
}
/** /**
* 数据库 * 数据库
* *
@@ -134,6 +100,9 @@ public class RedisConfig extends AbstractRedisConfig {
/** Minecraft 登录 */ /** Minecraft 登录 */
private int fmcPlayerToken; private int fmcPlayerToken;
/** 共享剪切板 */
private int clipboard;
} }
@Override @Override
@@ -142,22 +111,23 @@ public class RedisConfig extends AbstractRedisConfig {
setHost(host); setHost(host);
setPort(port); setPort(port);
setPassword(password); setPassword(password);
setTimeout(timeout); setTimeout(Time.SI * 8);
setMaxActive(lettuce.pool.maxActive); setMaxActive(8);
setMinIdle(lettuce.pool.minIdle); setMinIdle(1);
setMaxIdle(lettuce.pool.maxIdle); setMaxIdle(8);
}}; }};
} }
/** @return 连接池配置 */ /** @return 连接池配置 */
@Bean @Bean
@Override @Override
public GenericObjectPoolConfig<?> getPoolConfig() { public GenericObjectPoolConfig<StatefulConnection<?, ?>> getPoolConfig() {
GenericObjectPoolConfig<?> config = new GenericObjectPoolConfig<>(); RedisConfigParams configArgs = configParams();
config.setMaxTotal(lettuce.pool.maxActive); GenericObjectPoolConfig<StatefulConnection<?, ?>> config = new GenericObjectPoolConfig<>();
config.setMinIdle(lettuce.pool.minIdle); config.setMaxTotal(8);
config.setMaxIdle(lettuce.pool.maxIdle); config.setMinIdle(configArgs.getMinIdle());
config.setMaxWait(Duration.ofMillis(lettuce.pool.maxWait)); config.setMaxIdle(configArgs.getMaxIdle());
config.setMaxWait(Duration.ofMillis(-1));
return config; return config;
} }
@@ -221,7 +191,7 @@ public class RedisConfig extends AbstractRedisConfig {
/** @return 文章访问统计,文章 ID: {@link ArticleRanking}(JSON) */ /** @return 文章访问统计,文章 ID: {@link ArticleRanking}(JSON) */
@Bean("redisArticleRanking") @Bean("redisArticleRanking")
public Redis<Long, ArticleRanking> getArticleRankingRedisTemplate() { public Redis<Long, ArticleRanking> getArticleRankingRedisTemplate() {
return getRedis(database.articleRanking, RedisSerializers.LONG, RedisSerializers.gsonSerializer(ArticleRanking.class)); return getRedis(database.articleRanking, RedisSerializers.LONG, RedisSerializers.jacksonSerializer(jackson, ArticleRanking.class));
} }
/** @return 文章访问记录IP: [文章 ID] */ /** @return 文章访问记录IP: [文章 ID] */
@@ -265,4 +235,10 @@ public class RedisConfig extends AbstractRedisConfig {
public Redis<String, Long> getMCPlayerLoginRedisTemplate() { public Redis<String, Long> getMCPlayerLoginRedisTemplate() {
return getRedis(database.fmcPlayerToken, RedisSerializers.STRING, RedisSerializers.LONG); return getRedis(database.fmcPlayerToken, RedisSerializers.STRING, RedisSerializers.LONG);
} }
/** @return 共享剪切板,会话 ID: 内容 */
@Bean("redisClipboard")
public Redis<String, String> getClipboardRedisTemplate() {
return getRedis(database.clipboard, RedisSerializers.STRING, RedisSerializers.STRING);
}
} }

View File

@@ -2,8 +2,6 @@ package com.imyeyu.api.config;
import lombok.Data; import lombok.Data;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@@ -19,37 +17,17 @@ import java.util.concurrent.ThreadPoolExecutor;
@Data @Data
@Slf4j @Slf4j
@Configuration @Configuration
@EnableAutoConfiguration
@ConfigurationProperties(prefix = "spring.async.thread-pool")
public class ThreadPoolConfig { public class ThreadPoolConfig {
/** 核心数量 */
private int corePoolSize;
/** 最大数量 */
private int maxPoolSize;
/** 等待区容量 */
private int queueCapacity;
/** 最大保持活跃时间(秒) */
private int keepAliveSeconds;
/** 最大等待时间(秒) */
private int awaitTerminationSeconds;
/** 线程名称前缀 */
private String threadNamePrefix;
@Bean(name = "threadPoolTaskExecutor") @Bean(name = "threadPoolTaskExecutor")
public ThreadPoolTaskExecutor threadPoolTaskExecutor() { public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize); executor.setCorePoolSize(16);
executor.setMaxPoolSize(maxPoolSize); executor.setMaxPoolSize(32);
executor.setQueueCapacity(queueCapacity); executor.setQueueCapacity(16);
executor.setKeepAliveSeconds(keepAliveSeconds); executor.setKeepAliveSeconds(60);
executor.setAwaitTerminationSeconds(awaitTerminationSeconds); executor.setAwaitTerminationSeconds(60);
executor.setThreadNamePrefix(threadNamePrefix); executor.setThreadNamePrefix("thread-pool-task-executor-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize(); executor.initialize();
return executor; return executor;

View File

@@ -1,32 +1,28 @@
package com.imyeyu.api.config; package com.imyeyu.api.config;
import com.google.gson.GsonBuilder; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.imyeyu.api.annotation.EnableSettingInterceptor; import com.imyeyu.api.annotation.EnableSettingInterceptor;
import com.imyeyu.api.annotation.RequestRateLimitInterceptor; import com.imyeyu.api.annotation.RequestRateLimitInterceptor;
import com.imyeyu.api.annotation.RequiredTokenInterceptor; import com.imyeyu.api.annotation.RequiredTokenInterceptor;
import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.vo.user.UserProfileView;
import com.imyeyu.api.modules.common.vo.user.UserView;
import com.imyeyu.api.modules.journal.util.JournalAPIInterceptor; import com.imyeyu.api.modules.journal.util.JournalAPIInterceptor;
import com.imyeyu.api.modules.minecraft.annotation.RequiredFMCServerTokenInterceptor; import com.imyeyu.api.modules.minecraft.annotation.RequiredFMCServerTokenInterceptor;
import com.imyeyu.api.modules.minecraft.entity.MinecraftPlayer;
import com.imyeyu.api.modules.mirror.vo.MirrorView;
import com.imyeyu.api.modules.system.util.SystemAPIInterceptor; import com.imyeyu.api.modules.system.util.SystemAPIInterceptor;
import com.imyeyu.api.util.GsonSerializerAdapter; import com.imyeyu.spring.annotation.RequestBodyValueArgumentResolver;
import com.imyeyu.spring.annotation.RequestSingleParamResolver; import com.imyeyu.utils.Time;
import jakarta.validation.constraints.NotNull;
import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.GsonHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.io.Writer; import java.nio.charset.StandardCharsets;
import java.lang.reflect.Type;
import java.util.List; import java.util.List;
/** /**
@@ -40,12 +36,11 @@ import java.util.List;
@RequiredArgsConstructor @RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer { public class WebConfig implements WebMvcConfigurer {
private final ObjectMapper jackson;
private final SystemAPIInterceptor systemAPIInterceptor; private final SystemAPIInterceptor systemAPIInterceptor;
private final GsonSerializerAdapter gsonSerializerAdapter;
private final JournalAPIInterceptor journalAPIInterceptor; private final JournalAPIInterceptor journalAPIInterceptor;
private final RequiredTokenInterceptor requiredTokenInterceptor; private final RequiredTokenInterceptor requiredTokenInterceptor;
private final EnableSettingInterceptor enableSettingInterceptor; private final EnableSettingInterceptor enableSettingInterceptor;
private final RequestSingleParamResolver requestSingleParamResolver;
private final RequestRateLimitInterceptor requestRateLimitInterceptor; private final RequestRateLimitInterceptor requestRateLimitInterceptor;
private final RequiredFMCServerTokenInterceptor requiredFMCServerTokenInterceptor; private final RequiredFMCServerTokenInterceptor requiredFMCServerTokenInterceptor;
@@ -66,7 +61,7 @@ public class WebConfig implements WebMvcConfigurer {
@Override @Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(requestSingleParamResolver); argumentResolvers.add(new RequestBodyValueArgumentResolver(jackson));
} }
/** /**
@@ -76,22 +71,17 @@ public class WebConfig implements WebMvcConfigurer {
*/ */
@Override @Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
GsonHttpMessageConverter converter = new GsonHttpMessageConverter() { JsonMapper jsonMapper = JsonMapper.builder()
// 设置默认属性包含规则:忽略 null 值
@Override .serializationInclusion(JsonInclude.Include.NON_NULL)
protected void writeInternal(@NotNull Object object, Type type, @NonNull Writer writer) { // 日期格式化
// 忽略参数类型,因为接口返回对象会被全局返回处理器包装为 TimiResponse否则会序列化转型错误 .defaultDateFormat(Time.dateTime)
getGson().toJson(object, writer); // 忽略不存在字段
} .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
}; // 启用默认视图包含
.enable(MapperFeature.DEFAULT_VIEW_INCLUSION).build();
GsonBuilder builder = new GsonBuilder(); MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(jsonMapper);
builder.registerTypeAdapter(Attachment.class, gsonSerializerAdapter); converter.setDefaultCharset(StandardCharsets.UTF_8);
builder.registerTypeAdapter(UserView.class, gsonSerializerAdapter);
builder.registerTypeAdapter(MirrorView.class, gsonSerializerAdapter);
builder.registerTypeAdapter(UserProfileView.class, gsonSerializerAdapter);
builder.registerTypeAdapter(MinecraftPlayer.class, gsonSerializerAdapter);
converter.setGson(builder.create());
converters.add(converter); converters.add(converter);
} }
} }

View File

@@ -70,8 +70,8 @@ public class GiteaDBConfig {
ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver(); ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
List<String> mapperLocations = new ArrayList<>(); List<String> mapperLocations = new ArrayList<>();
mapperLocations.add("classpath:mapper/gitea/**/*.xml"); mapperLocations.add("classpath:mapper/gitea/**/*.xml");
for (int i = 0; i < mapperLocations.size(); i++) { for (String mapperLocation : mapperLocations) {
resources.addAll(List.of(resourceResolver.getResources(mapperLocations.get(i)))); resources.addAll(List.of(resourceResolver.getResources(mapperLocation)));
} }
} }
String[] typeAliases = { String[] typeAliases = {

View File

@@ -83,8 +83,8 @@ public class TimiServerDBConfig {
mapperLocations.add("classpath:mapper/system/**/*.xml"); mapperLocations.add("classpath:mapper/system/**/*.xml");
mapperLocations.add("classpath:mapper/journal/**/*.xml"); mapperLocations.add("classpath:mapper/journal/**/*.xml");
mapperLocations.add("classpath:mapper/minecraft/**/*.xml"); mapperLocations.add("classpath:mapper/minecraft/**/*.xml");
for (int i = 0; i < mapperLocations.size(); i++) { for (String mapperLocation : mapperLocations) {
resources.addAll(List.of(resourceResolver.getResources(mapperLocations.get(i)))); resources.addAll(List.of(resourceResolver.getResources(mapperLocation)));
} }
} }
String[] typeAliases = { String[] typeAliases = {

View File

@@ -1,49 +0,0 @@
package com.imyeyu.api.modules.bill.controller;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.api.modules.bill.entity.Bill;
import com.imyeyu.api.modules.bill.service.BillService;
import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.service.SettingService;
import com.imyeyu.spring.TimiSpring;
import com.imyeyu.spring.annotation.AOPLog;
import com.imyeyu.spring.annotation.RequestRateLimit;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 收支帐单接口
*
* @author 夜雨
* @since 2023-02-04 01:02
*/
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/bill")
public class BillController {
private final BillService service;
private final SettingService settingService;
/**
* 创建收支帐单
*
* @param bill 账单
*/
@AOPLog
@RequestRateLimit
@PostMapping("/create")
public void createREBill(@Valid @RequestBody Bill bill) {
if (!settingService.getAsString(SettingKey.BILL_API_TOKEN).equals(TimiSpring.getToken())) {
throw new TimiException(TimiCode.REQUEST_BAD).msgKey("token.illegal");
}
service.create(bill);
}
}

View File

@@ -1,120 +0,0 @@
package com.imyeyu.api.modules.bill.entity;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import com.imyeyu.spring.entity.Entity;
/**
* 收支账单
*
* @author 夜雨
* @since 2022-03-29 11:28
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class Bill extends Entity {
/**
* 类型
*
* @author 夜雨
* @since 2022-03-29 11:28
*/
@Getter
public enum Type {
/** 收入 */
REVENUE,
/** 支出 */
EXPENDITURE
}
/**
* 收入类型
*
* @author 夜雨
* @since 2022-03-29 11:28
*/
@Getter
public enum RevenueType {
/** 工作 */
WORK,
/** 退款 */
REFUND,
/** 其他 */
OTHER
}
/**
* 支出类型
*
* @author 夜雨
* @since 2022-03-29 11:28
*/
@Getter
public enum ExpenditureType {
/** 饮食 */
FOOD,
/** 生活 */
LIFE,
/** 通信 */
COMMUNICATION,
/** 交通 */
TRAFFIC,
/** 娱乐 */
GAME,
/** 工作 */
WORK,
/** 服饰 */
CLOTHES,
/** 医疗 */
HEALTH,
/** 其他 */
OTHER
}
/** 收入类型 */
private RevenueType revenueType;
/** 支出类型 */
private ExpenditureType expenditureType;
/** 描述 */
@NotBlank(message = "bill.description.empty")
private String description;
/** 金额(未确保计算精度,放大了 100 倍) */
@NotNull(message = "bill.decimal.empty")
@DecimalMin(value = "0", message = "bill.decimal.limit")
private Long decimal;
/** 备注 */
private String remarks;
/** @return true 为收入账单 */
public boolean isRevenue() {
return revenueType != null;
}
/** @return true 为支出账单 */
public boolean isExpenditure() {
return expenditureType != null;
}
}

View File

@@ -1,13 +0,0 @@
package com.imyeyu.api.modules.bill.mapper;
import com.imyeyu.api.modules.bill.entity.Bill;
import com.imyeyu.spring.mapper.BaseMapper;
/**
* 收支帐单表
*
* @author 夜雨
* @since 2022-04-01 16:26
*/
public interface BillMapper extends BaseMapper<Bill, Long> {
}

View File

@@ -1,13 +0,0 @@
package com.imyeyu.api.modules.bill.service;
import com.imyeyu.api.modules.bill.entity.Bill;
import com.imyeyu.spring.service.CreatableService;
/**
* 收支帐单服务
*
* @author 夜雨
* @since 2022-04-01 16:24
*/
public interface BillService extends CreatableService<Bill> {
}

View File

@@ -1,27 +0,0 @@
package com.imyeyu.api.modules.bill.service.implement;
import com.imyeyu.api.modules.bill.entity.Bill;
import com.imyeyu.api.modules.bill.mapper.BillMapper;
import com.imyeyu.api.modules.bill.service.BillService;
import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.spring.service.AbstractEntityService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
* 收支账单服务
*
* @author 夜雨
* @since 2022-04-01 16:25
*/
@Service
@RequiredArgsConstructor
public class BillServiceImplement extends AbstractEntityService<Bill, Long> implements BillService {
private final BillMapper mapper;
@Override
protected BaseMapper<Bill, Long> mapper() {
return mapper;
}
}

View File

@@ -1,6 +1,6 @@
package com.imyeyu.api.modules.blog.entity; package com.imyeyu.api.modules.blog.entity;
import com.google.gson.JsonElement; import com.fasterxml.jackson.databind.JsonNode;
import com.imyeyu.api.modules.common.bean.CommentSupport; import com.imyeyu.api.modules.common.bean.CommentSupport;
import com.imyeyu.spring.entity.Destroyable; import com.imyeyu.spring.entity.Destroyable;
import com.imyeyu.spring.entity.Entity; import com.imyeyu.spring.entity.Entity;
@@ -48,7 +48,7 @@ public class Article extends Entity implements CommentSupport, Destroyable {
protected String data; protected String data;
/** 扩展数据 */ /** 扩展数据 */
protected JsonElement extendData; protected JsonNode extendData;
/** 阅读数量 */ /** 阅读数量 */
protected int reads; protected int reads;

View File

@@ -1,8 +1,9 @@
package com.imyeyu.api.modules.blog.entity; package com.imyeyu.api.modules.blog.entity;
import com.imyeyu.spring.entity.Entity;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import com.imyeyu.spring.entity.Entity; import lombok.NoArgsConstructor;
/** /**
* 访问排行(每周) * 访问排行(每周)
@@ -12,6 +13,7 @@ import com.imyeyu.spring.entity.Entity;
* @since 2021-03-01 17:10 * @since 2021-03-01 17:10
*/ */
@Data @Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class ArticleRanking extends Entity { public class ArticleRanking extends Entity {

View File

@@ -1,6 +1,6 @@
package com.imyeyu.api.modules.blog.entity; package com.imyeyu.api.modules.blog.entity;
import com.imyeyu.api.modules.common.vo.comment.CommentReplyView; import com.imyeyu.api.modules.common.entity.CommentReply;
import com.imyeyu.spring.annotation.table.AutoUUID; import com.imyeyu.spring.annotation.table.AutoUUID;
import com.imyeyu.spring.annotation.table.Id; import com.imyeyu.spring.annotation.table.Id;
import lombok.Data; import lombok.Data;
@@ -32,5 +32,5 @@ public class CommentRemindQueue {
private Long replyId; private Long replyId;
private CommentReplyView reply; private CommentReply reply;
} }

View File

@@ -19,7 +19,7 @@ public interface ArticleMapper extends BaseMapper<Article, Long> {
long countByPage(Page page); long countByPage(Page page);
@Override @Override
List<Article> listByPage(Page page); List<Article> selectByPage(Page page);
long countByKeyword(String keyword); long countByKeyword(String keyword);

View File

@@ -58,7 +58,7 @@ public class ArticleServiceImplement extends AbstractEntityService<Article, Long
@Override @Override
public PageResult<Article> page(Page page) { public PageResult<Article> page(Page page) {
PageResult<Article> result = new PageResult<>(); PageResult<Article> result = new PageResult<>();
result.setList(mapper.listByPage(page)); result.setList(mapper.selectByPage(page));
result.setTotal(mapper.countByPage(page)); result.setTotal(mapper.countByPage(page));
return result; return result;
} }

View File

@@ -37,12 +37,16 @@ public class CommentRemindQueueServiceImplement extends AbstractEntityService<Co
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public void destroyByUserId(Long userId) { public void destroyByUserId(Long userId) {
mapper.destroyByUserId(userId); CommentRemindQueue example = new CommentRemindQueue();
example.setUserId(userId);
mapper.deleteAllByExample(example);
} }
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public void destroyByReplyId(Long replyId) { public void destroyByReplyId(Long replyId) {
mapper.destroyByReplyId(replyId); CommentRemindQueue example = new CommentRemindQueue();
example.setReplyId(replyId);
mapper.destroyAllByExample(example);
} }
} }

View File

@@ -1,7 +1,5 @@
package com.imyeyu.api.modules.common.bean; package com.imyeyu.api.modules.common.bean;
import lombok.Data;
/** /**
* @author 夜雨 * @author 夜雨
* @since 2025-10-20 15:04 * @since 2025-10-20 15:04
@@ -18,22 +16,4 @@ public class MediaAttach {
THUMB THUMB
} }
/**
*
*
* @author 夜雨
* @since 2025-10-20 15:04
*/
@Data
public static class ExtData {
private Long sourceId;
private String sourceMongoId;
private boolean isImage;
private boolean isVideo;
}
} }

View File

@@ -1,6 +1,7 @@
package com.imyeyu.api.modules.common.bean; package com.imyeyu.api.modules.common.bean;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode;
/** /**
* @author 夜雨 * @author 夜雨
@@ -9,7 +10,7 @@ import lombok.Data;
public class Metadata { public class Metadata {
/** /**
* * 图片
* *
* @author 夜雨 * @author 夜雨
* @since 2025-12-11 18:15 * @since 2025-12-11 18:15
@@ -21,4 +22,21 @@ public class Metadata {
private int height; private int height;
} }
/**
* 缩略图
*
* @author 夜雨
* @since 2026-01-04 18:10
*/
@Data
@EqualsAndHashCode(callSuper = true)
public static class ThumbImage extends Image {
private long sourceId;
private String sourceMongoId;
private String sourceMimeType;
}
} }

View File

@@ -131,7 +131,7 @@ public enum SettingKey {
MUSIC_CONTROLLER_URI, MUSIC_CONTROLLER_URI,
// ---------- ---------- // ---------- 日记 ----------
JOURNAL_KEY, JOURNAL_KEY,
@@ -139,7 +139,26 @@ public enum SettingKey {
JOURNAL_APP_SECRET, JOURNAL_APP_SECRET,
JOURNAL_TRAVEL, 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,
// ---------- 系统 ---------- // ---------- 系统 ----------

View File

@@ -1,23 +0,0 @@
package com.imyeyu.api.modules.common.bean;
import lombok.Data;
import java.nio.file.Path;
/**
* @author 夜雨
* @since 2025-09-27 01:47
*/
@Data
public class TempFileMetaData {
private String id;
private String name;
private String originalName;
private Path path;
private Long lastAccessAt;
}

View File

@@ -8,13 +8,10 @@ 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.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.api.modules.common.vo.comment.CommentReplyPage;
import com.imyeyu.api.modules.common.vo.comment.CommentReplyView;
import com.imyeyu.api.modules.common.vo.comment.CommentView;
import com.imyeyu.api.modules.git.vo.issue.CommentPage;
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.bean.CaptchaData; import com.imyeyu.spring.bean.CaptchaData;
import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.bean.PageResult;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -51,8 +48,8 @@ public class CommentController {
@RequestRateLimit @RequestRateLimit
@PostMapping("/list") @PostMapping("/list")
public PageResult<CommentView> list(@Valid @RequestBody CommentPage commentPage) { public PageResult<Comment> list(@Valid @RequestBody Page<Comment> page) {
return service.pageByBizId(commentPage); return service.page(page);
} }
/** /**
@@ -77,9 +74,14 @@ public class CommentController {
*/ */
@RequestRateLimit @RequestRateLimit
@RequestMapping("/reply/list") @RequestMapping("/reply/list")
public PageResult<CommentReplyView> pageCommentReplies(@Valid @RequestBody CommentReplyPage page) { public PageResult<CommentReply> pageReplies(@Valid @RequestBody Page<CommentReply> page) {
// 通用接口,只允许查询评论的回复 // 通用接口,只允许查询评论的回复
page.setBizType(CommentReplyPage.BizType.COMMENT); CommentReply example = new CommentReply();
return replyService.pageByBizType(page); if (page.getEqualsExample() != null) {
example.setCommentId(page.getEqualsExample().getCommentId());
}
page.setEqualsExample(example);
page.setLikesExample(null);
return replyService.page(page);
} }
} }

View File

@@ -1,26 +1,26 @@
package com.imyeyu.api.modules.common.controller; package com.imyeyu.api.modules.common.controller;
import com.google.gson.Gson; import com.fasterxml.jackson.core.type.TypeReference;
import com.google.gson.reflect.TypeToken; import com.fasterxml.jackson.databind.ObjectMapper;
import com.imyeyu.api.bean.CaptchaFrom; import com.imyeyu.api.bean.CaptchaFrom;
import com.imyeyu.api.modules.common.bean.ImageType; import com.imyeyu.api.modules.common.bean.ImageType;
import com.imyeyu.api.modules.common.bean.SettingKey; import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.bean.TempFileMetaData;
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;
import com.imyeyu.api.modules.common.entity.Template; import com.imyeyu.api.modules.common.entity.Template;
import com.imyeyu.api.modules.common.entity.Version; import com.imyeyu.api.modules.common.entity.Version;
import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.api.modules.common.service.ClipboardService;
import com.imyeyu.api.modules.common.service.FeedbackService; import com.imyeyu.api.modules.common.service.FeedbackService;
import com.imyeyu.api.modules.common.service.SettingService; import com.imyeyu.api.modules.common.service.SettingService;
import com.imyeyu.api.modules.common.service.TaskService; import com.imyeyu.api.modules.common.service.TaskService;
import com.imyeyu.api.modules.common.service.TempFileService; import com.imyeyu.api.modules.common.service.TempFileService;
import com.imyeyu.api.modules.common.service.TemplateService; import com.imyeyu.api.modules.common.service.TemplateService;
import com.imyeyu.api.modules.common.service.VersionService; import com.imyeyu.api.modules.common.service.VersionService;
import com.imyeyu.api.modules.common.vo.ClipboardRequest;
import com.imyeyu.api.modules.common.vo.FeedbackRequest; import com.imyeyu.api.modules.common.vo.FeedbackRequest;
import com.imyeyu.api.modules.common.vo.TempFileResponse; import com.imyeyu.api.modules.common.vo.TempFileResponse;
import com.imyeyu.api.modules.common.vo.attachment.AttachmentView;
import com.imyeyu.api.modules.system.util.ResourceHandler; import com.imyeyu.api.modules.system.util.ResourceHandler;
import com.imyeyu.api.util.CaptchaManager; import com.imyeyu.api.util.CaptchaManager;
import com.imyeyu.io.IO; import com.imyeyu.io.IO;
@@ -47,6 +47,7 @@ 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.http.MediaType;
import org.springframework.data.mongodb.gridfs.GridFsResource; 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;
@@ -57,6 +58,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.Yaml;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
@@ -64,12 +66,8 @@ import java.awt.Graphics2D;
import java.awt.RenderingHints; import java.awt.RenderingHints;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -95,19 +93,75 @@ public class CommonController {
private final TemplateService templateService; private final TemplateService templateService;
private final TempFileService tempFileService; private final TempFileService tempFileService;
private final AttachmentService attachmentService; private final AttachmentService attachmentService;
private final ClipboardService clipboardService;
private final Gson gson; private final ObjectMapper jackson;
private final Yaml yaml; private final Yaml yaml;
private final GridFSBucket gridFSBucket; private final GridFSBucket gridFSBucket;
private final CaptchaManager captchaManager; private final CaptchaManager captchaManager;
private final ResourceHandler resourceHandler; private final ResourceHandler resourceHandler;
private String writeJson(Object value) {
try {
return jackson.writeValueAsString(value);
} catch (IOException e) {
throw new TimiException(TimiCode.ERROR, "write json error", e);
}
}
private Map<String, Object> readJsonMap(String value) {
try {
return jackson.readValue(value, new TypeReference<>() {});
} catch (IOException e) {
throw new TimiException(TimiCode.ERROR, "read json error", e);
}
}
@AOPLog @AOPLog
@RequestMapping("") @RequestMapping("")
public String root() { public String root() {
return "IT WORKING! " + TimiSpring.getRequestIP(); return "IT WORKING! " + TimiSpring.getRequestIP();
} }
/**
* 获取共享剪切板内容
*
* @param id 会话 ID
* @return 剪切板内容
*/
@AOPLog
@RequestRateLimit
@GetMapping("/clipboard/{id}")
public String clipboardGet(@Valid @NotBlank @PathVariable("id") String id) {
return clipboardService.getContent(id);
}
/**
* 设置共享剪切板内容
*
* @param id 会话 ID
* @param request 请求内容
*/
@AOPLog
@RequestRateLimit
@PostMapping("/clipboard/{id}")
public void clipboardSet(@Valid @NotBlank @PathVariable("id") String id, @Valid @RequestBody ClipboardRequest request) {
clipboardService.setContent(id, request.getContent());
}
/**
* 订阅共享剪切板实时更新
*
* @param id 会话 ID
* @return SSE 发射器
*/
@AOPLog
@IgnoreGlobalReturn
@GetMapping(value = "/clipboard/stream/{id}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter clipboardStream(@Valid @NotBlank @PathVariable("id") String id) {
return clipboardService.subscribe(id);
}
/** /**
* 获取验证码 * 获取验证码
* *
@@ -215,12 +269,12 @@ public class CommonController {
case JSON -> { case JSON -> {
if (setting.getType() == Setting.Type.YAML) { if (setting.getType() == Setting.Type.YAML) {
Map<String, Object> obj = yaml.load(setting.getValue()); Map<String, Object> obj = yaml.load(setting.getValue());
result = gson.toJson(obj); result = writeJson(obj);
} }
} }
case YAML -> { case YAML -> {
if (setting.getType() == Setting.Type.JSON) { if (setting.getType() == Setting.Type.JSON) {
Map<String, Object> obj = gson.fromJson(setting.getValue(), new TypeToken<Map<String, Object>>() {}.getType()); Map<String, Object> obj = readJsonMap(setting.getValue());
result = yaml.dump(obj); result = yaml.dump(obj);
} }
} }
@@ -246,12 +300,12 @@ public class CommonController {
case JSON -> { case JSON -> {
if (setting.getType() == Setting.Type.YAML) { if (setting.getType() == Setting.Type.YAML) {
Map<String, Object> obj = new Yaml().load(setting.getValue()); Map<String, Object> obj = new Yaml().load(setting.getValue());
setting.setValue(gson.toJson(obj)); setting.setValue(writeJson(obj));
} }
} }
case YAML -> { case YAML -> {
if (setting.getType() == Setting.Type.JSON) { if (setting.getType() == Setting.Type.JSON) {
Map<String, Object> obj = gson.fromJson(setting.getValue(), new TypeToken<Map<String, Object>>() {}.getType()); Map<String, Object> obj = readJsonMap(setting.getValue());
setting.setValue(new Yaml().dump(obj)); setting.setValue(new Yaml().dump(obj));
} }
} }
@@ -264,8 +318,8 @@ public class CommonController {
@AOPLog @AOPLog
@RequestRateLimit @RequestRateLimit
@GetMapping("/attachment/{mongoId}") @GetMapping("/attachment/{mongoId}")
public AttachmentView getAttachment(@PathVariable String mongoId) { public Attachment getAttachment(@PathVariable String mongoId) {
return attachmentService.viewByMongoId(mongoId); return attachmentService.getByMongoId(mongoId);
} }
@AOPLog @AOPLog
@@ -380,41 +434,41 @@ public class CommonController {
/** /**
* 上传临时文件 * 上传临时文件
* *
* @param files * @param files 文件列表
* @return * @param ttl 缓存时长(毫秒),最长 3 天259200000ms
* @return 临时文件响应列表
*/ */
@AOPLog @AOPLog
@PostMapping("/temp/file/upload") @PostMapping("/temp/file/upload")
public List<TempFileResponse> uploadFile(@RequestParam("file") List<MultipartFile> files) { public List<TempFileResponse> tempFileUpload(@RequestParam("file") List<MultipartFile> files, @RequestParam(value = "ttl", required = false) Long ttl) {
return tempFileService.store(files); return tempFileService.store(files, ttl);
} }
/** /**
* 读取临时文件 * 读取临时文件
* *
* @param fileId * @param mongoId mongoId
* @param req * @param req 请求
* @param resp * @param resp 返回
*/ */
@AOPLog @AOPLog
@RequestRateLimit @RequestRateLimit
@IgnoreGlobalReturn @IgnoreGlobalReturn
@GetMapping("/temp/file/read/{fileId}") @GetMapping("/temp/file/read/{mongoId}")
public void tempFileRead(@PathVariable String fileId, HttpServletRequest req, HttpServletResponse resp) { public void tempFileRead(@PathVariable String mongoId, HttpServletRequest req, HttpServletResponse resp) {
try { try {
File file = tempFileService.get(fileId); Attachment attach = attachmentService.getByMongoId(mongoId);
if (TimiJava.isEmpty(file) && file.exists()) { if (TimiJava.isEmpty(attach)) {
resp.setStatus(HttpServletResponse.SC_NOT_FOUND); resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
return; return;
} }
Path filePath = file.toPath(); GridFSFile file = attachmentService.readByMongoId(attach.getMongoId());
resp.setContentLengthLong(Files.size(filePath)); GridFSDownloadStream downloadStream = gridFSBucket.openDownloadStream(file.getObjectId());
String mimeType = new Tika().detect(filePath); GridFsResource gridFsResource = new GridFsResource(file, downloadStream);
if (TimiJava.isNotEmpty(mimeType)) { req.setAttribute(ResourceHandler.ATTR_TYPE, ResourceHandler.Type.MONGO);
resp.setContentType(mimeType); req.setAttribute(ResourceHandler.ATTR_VALUE, gridFsResource);
}
req.setAttribute(ResourceHandler.ATTR_TYPE, ResourceHandler.Type.FILE); resp.setContentType(attach.getMimeType());
req.setAttribute(ResourceHandler.ATTR_VALUE, filePath);
resourceHandler.handleRequest(req, resp); resourceHandler.handleRequest(req, resp);
} catch (Exception e) { } catch (Exception e) {
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
@@ -424,41 +478,40 @@ public class CommonController {
/** /**
* 下载临时文件 * 下载临时文件
* *
* @param fileId * @param mongoId
* @param resp * @param resp
*/ */
@AOPLog @AOPLog
@RequestRateLimit @RequestRateLimit
@IgnoreGlobalReturn @IgnoreGlobalReturn
@RequestMapping("/temp/file/download/{fileId}") @RequestMapping("/temp/file/download/{mongoId}")
public void tempFileDownload(@PathVariable String fileId, HttpServletResponse resp) { public void tempFileDownload(@PathVariable String mongoId, HttpServletResponse resp) {
try { try {
TempFileMetaData metadata = tempFileService.metadata(fileId); Attachment attach = attachmentService.getByMongoId(mongoId);
File file = tempFileService.get(fileId); if (TimiJava.isEmpty(attach)) {
if (TimiJava.isEmpty(file) && file.exists()) {
resp.setStatus(HttpServletResponse.SC_NOT_FOUND); resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
return; return;
} }
String mimeType = new Tika().detect(file); resp.setContentType(attach.getMimeType());
resp.setContentType(mimeType); resp.setHeader("Content-Disposition", Network.getFileDownloadHeader(attach.getName()));
resp.setHeader("Content-Disposition", Network.getFileDownloadHeader(metadata.getOriginalName()));
resp.setHeader("Accept-Ranges", "bytes"); resp.setHeader("Accept-Ranges", "bytes");
RequestRange range = TimiSpring.requestRange(file.length()); GridFSFile file = attachmentService.readByMongoId(mongoId);
@Cleanup
GridFSDownloadStream downloadStream = gridFSBucket.openDownloadStream(file.getObjectId());
RequestRange range = TimiSpring.getRequestRange(attach.getSize());
if (range == null) { if (range == null) {
// 完整文件 // 完整文件
resp.setContentLengthLong(file.length()); resp.setContentLengthLong(attach.getSize());
resp.setStatus(HttpServletResponse.SC_OK); resp.setStatus(HttpServletResponse.SC_OK);
IO.toOutputStream(resp.getOutputStream(), file); IO.toOutputStream(downloadStream, resp.getOutputStream());
} else { } else {
// 分片文件 // 分片文件
resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
resp.setHeader("Content-Range", "bytes %s-%s/%s".formatted(range.getStart(), range.getEnd(), file.length())); resp.setHeader("Content-Range", "bytes %s-%s/%s".formatted(range.getStart(), range.getEnd(), attach.getSize()));
resp.setContentLengthLong(range.getLength()); resp.setContentLengthLong(range.getLength());
IO.toOutputStream(downloadStream, resp.getOutputStream(), range.getStart(), range.getEnd());
@Cleanup RandomAccessFile raf = new RandomAccessFile(file, "r"); resp.flushBuffer();
raf.seek(range.getStart());
IO.toOutputStream(resp.getOutputStream(), raf, range.getStart(), range.getLength());
} }
} catch (Exception e) { } catch (Exception e) {
log.error("download error", e); log.error("download error", e);

View File

@@ -1,11 +1,10 @@
package com.imyeyu.api.modules.common.controller; package com.imyeyu.api.modules.common.controller;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiException;
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.bean.SettingKey;
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.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;
@@ -15,10 +14,6 @@ import com.imyeyu.api.modules.common.service.UserConfigService;
import com.imyeyu.api.modules.common.service.UserPrivacyService; import com.imyeyu.api.modules.common.service.UserPrivacyService;
import com.imyeyu.api.modules.common.service.UserProfileService; import com.imyeyu.api.modules.common.service.UserProfileService;
import com.imyeyu.api.modules.common.service.UserService; import com.imyeyu.api.modules.common.service.UserService;
import com.imyeyu.api.modules.common.vo.comment.CommentReplyPage;
import com.imyeyu.api.modules.common.vo.comment.CommentReplyView;
import com.imyeyu.api.modules.common.vo.comment.CommentView;
import com.imyeyu.api.modules.common.vo.comment.UserCommentPage;
import com.imyeyu.api.modules.common.vo.user.EmailVerifyCallbackRequest; import com.imyeyu.api.modules.common.vo.user.EmailVerifyCallbackRequest;
import com.imyeyu.api.modules.common.vo.user.LoginRequest; import com.imyeyu.api.modules.common.vo.user.LoginRequest;
import com.imyeyu.api.modules.common.vo.user.LoginResponse; import com.imyeyu.api.modules.common.vo.user.LoginResponse;
@@ -27,11 +22,13 @@ import com.imyeyu.api.modules.common.vo.user.UpdatePasswordByKeyRequest;
import com.imyeyu.api.modules.common.vo.user.UpdatePasswordRequest; import com.imyeyu.api.modules.common.vo.user.UpdatePasswordRequest;
import com.imyeyu.api.modules.common.vo.user.UserRequest; import com.imyeyu.api.modules.common.vo.user.UserRequest;
import com.imyeyu.api.modules.common.vo.user.UserView; import com.imyeyu.api.modules.common.vo.user.UserView;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.AOPLog;
import com.imyeyu.spring.annotation.RequestRateLimit; import com.imyeyu.spring.annotation.RequestRateLimit;
import com.imyeyu.spring.annotation.RequestSingleParam;
import com.imyeyu.spring.annotation.RequiredToken; import com.imyeyu.spring.annotation.RequiredToken;
import com.imyeyu.spring.bean.CaptchaData; import com.imyeyu.spring.bean.CaptchaData;
import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.utils.Time; import com.imyeyu.utils.Time;
import jakarta.validation.Valid; import jakarta.validation.Valid;
@@ -186,7 +183,7 @@ public class UserController implements TimiJava {
@RequiredToken @RequiredToken
@RequestRateLimit @RequestRateLimit
@PostMapping("/cancel") @PostMapping("/cancel")
public void cancel(@RequestSingleParam String password) { public void cancel(@RequestBody String password) {
service.cancel(password); service.cancel(password);
} }
@@ -266,16 +263,18 @@ public class UserController implements TimiJava {
@RequiredToken @RequiredToken
@RequestRateLimit @RequestRateLimit
@PostMapping("/comment/list") @PostMapping("/comment/list")
public PageResult<CommentView> listComment(@Valid @RequestBody UserCommentPage page) { public PageResult<Comment> listComment(@Valid @RequestBody Page<Comment> page) {
page.setUserId(service.getLoginUser().getId()); Comment example = new Comment();
return commentService.pageByUserId(page); example.setUserId(service.getLoginUser().getId());
page.setEqualsExample(example);
return commentService.page(page);
} }
@AOPLog @AOPLog
@RequiredToken @RequiredToken
@RequestRateLimit @RequestRateLimit
@PostMapping("/comment/delete") @PostMapping("/comment/delete")
public void deleteComment(@RequestSingleParam Long commentId) { public void deleteComment(@RequestBody Long commentId) {
commentService.get(commentId); commentService.get(commentId);
commentService.delete(commentId); commentService.delete(commentId);
} }
@@ -287,16 +286,18 @@ public class UserController implements TimiJava {
@RequiredToken @RequiredToken
@RequestRateLimit @RequestRateLimit
@PostMapping("/comment/reply/list") @PostMapping("/comment/reply/list")
public PageResult<CommentReplyView> listCommentReply(@Valid @RequestBody CommentReplyPage page) { public PageResult<CommentReply> listCommentReply(@Valid @RequestBody Page<CommentReply> page) {
page.setBizId(service.getLoginUser().getId()); CommentReply example = new CommentReply();
return commentReplyService.pageByBizType(page); example.setReceiverId(service.getLoginUser().getId());
page.setEqualsExample(example);
return commentReplyService.page(page);
} }
@AOPLog @AOPLog
@RequiredToken @RequiredToken
@RequestRateLimit @RequestRateLimit
@PostMapping("/comment/reply/delete") @PostMapping("/comment/reply/delete")
public void deleteCommentReply(@RequestSingleParam Long replyId) { public void deleteCommentReply(@RequestBody Long replyId) {
CommentReply reply = commentReplyService.get(replyId); CommentReply reply = commentReplyService.get(replyId);
TimiException.requiredTrue(reply.getSenderId().equals(service.getLoginUser().getId()), "user.comment.reply.delete.not_owner"); TimiException.requiredTrue(reply.getSenderId().equals(service.getLoginUser().getId()), "user.comment.reply.delete.not_owner");
commentReplyService.delete(replyId); commentReplyService.delete(replyId);
@@ -306,7 +307,7 @@ public class UserController implements TimiJava {
@RequiredToken @RequiredToken
@RequestRateLimit @RequestRateLimit
@PostMapping("/comment/reply/ignore") @PostMapping("/comment/reply/ignore")
public void ignoreCommentReply(@RequestSingleParam Long replyId) { public void ignoreCommentReply(@RequestBody Long replyId) {
CommentReply reply = commentReplyService.get(replyId); CommentReply reply = commentReplyService.get(replyId);
TimiException.requiredTrue(reply.getReceiverId().equals(service.getLoginUser().getId()), "user.comment.reply.ignore.not_owner"); TimiException.requiredTrue(reply.getReceiverId().equals(service.getLoginUser().getId()), "user.comment.reply.ignore.not_owner");
reply.setIgnoredAt(Time.now()); reply.setIgnoredAt(Time.now());

View File

@@ -1,7 +1,9 @@
package com.imyeyu.api.modules.common.entity; package com.imyeyu.api.modules.common.entity;
import com.google.gson.JsonObject; import com.fasterxml.jackson.databind.JsonNode;
import com.imyeyu.api.TimiServerAPI;
import com.imyeyu.api.bean.MultilingualHandler; import com.imyeyu.api.bean.MultilingualHandler;
import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.java.ref.Ref; import com.imyeyu.java.ref.Ref;
import com.imyeyu.spring.annotation.table.Transient; import com.imyeyu.spring.annotation.table.Transient;
import com.imyeyu.spring.entity.Entity; import com.imyeyu.spring.entity.Entity;
@@ -48,14 +50,20 @@ public class Attachment extends Entity implements MultilingualHandler {
/** 镜像 */ /** 镜像 */
MIRROR, MIRROR,
/** 日记 */
JOURNAL, JOURNAL,
/** 日记出行 */
JOURNAL_TRAVEL, JOURNAL_TRAVEL,
/** 日记瞬间 */
JOURNAL_MOMENT, JOURNAL_MOMENT,
/** 系统 */ /** 系统 */
SYSTEM SYSTEM,
/** 临时文件 */
TEMP_FILE
} }
protected BizType bizType; protected BizType bizType;
@@ -73,19 +81,26 @@ public class Attachment extends Entity implements MultilingualHandler {
protected String mimeType; protected String mimeType;
protected JsonObject metadata; protected JsonNode metadata;
protected Long size; protected Long size;
protected String md5; protected String md5;
protected String ext; protected String uploaderIp;
protected Boolean isDestroyed;
protected Long destroyAt; protected Long destroyAt;
@Transient @Transient
protected InputStream inputStream; protected InputStream inputStream;
public InputStream openInputStream() {
AttachmentService service = TimiServerAPI.applicationContext.getBean(AttachmentService.class);
return service.getInputStreamByMongoId(mongoId);
}
public void setAttachTypeValue(Enum<?> attachType) { public void setAttachTypeValue(Enum<?> attachType) {
this.attachType = attachType.toString(); this.attachType = attachType.toString();
} }

View File

@@ -1,5 +1,14 @@
package com.imyeyu.api.modules.common.entity; package com.imyeyu.api.modules.common.entity;
import com.imyeyu.api.modules.blog.entity.Article;
import com.imyeyu.api.modules.blog.service.implement.ArticleServiceImplement;
import com.imyeyu.api.modules.common.bean.CommentSupport;
import com.imyeyu.api.modules.common.vo.user.UserView;
import com.imyeyu.api.modules.git.bean.gitea.Repository;
import com.imyeyu.api.modules.git.service.implement.IssueServiceImplement;
import com.imyeyu.api.modules.git.service.implement.MergeServiceImplement;
import com.imyeyu.spring.annotation.table.Transient;
import com.imyeyu.spring.entity.Entity;
import com.imyeyu.spring.service.GettableService; import com.imyeyu.spring.service.GettableService;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
@@ -7,11 +16,8 @@ import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import com.imyeyu.api.modules.blog.service.implement.ArticleServiceImplement;
import com.imyeyu.api.modules.common.bean.CommentSupport; import java.util.List;
import com.imyeyu.api.modules.git.service.implement.IssueServiceImplement;
import com.imyeyu.api.modules.git.service.implement.MergeServiceImplement;
import com.imyeyu.spring.entity.Entity;
/** /**
* 评论 * 评论
@@ -64,4 +70,24 @@ public class Comment extends Entity {
/** 发送用户 IP */ /** 发送用户 IP */
private String ip; private String ip;
/** 回复数量 */
@Transient
private long repliesLength;
/** 发送用户 */
@Transient
private UserView user;
/** 关联文章 */
@Transient
private Article article;
/** 关联仓库 */
@Transient
private Repository repository;
/** 回复列表 */
@Transient
private List<CommentReply> replies;
} }

View File

@@ -1,9 +1,11 @@
package com.imyeyu.api.modules.common.entity; package com.imyeyu.api.modules.common.entity;
import com.imyeyu.api.modules.common.vo.user.UserView;
import com.imyeyu.spring.annotation.table.Transient;
import com.imyeyu.spring.entity.Entity;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import com.imyeyu.spring.entity.Entity;
/** /**
* 评论回复 * 评论回复
@@ -42,4 +44,16 @@ public class CommentReply extends Entity {
/** 被回复用户忽略该回复的时间 */ /** 被回复用户忽略该回复的时间 */
private Long ignoredAt; private Long ignoredAt;
/** 所属评论 */
@Transient
private Comment comment;
/** 发送用户 */
@Transient
private UserView sender;
/** 回复用户 */
@Transient
private UserView receiver;
} }

View File

@@ -3,6 +3,7 @@ package com.imyeyu.api.modules.common.mapper;
import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.spring.bean.Page; import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.spring.mapper.RawMapper;
import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Select;
import java.util.List; import java.util.List;
@@ -11,10 +12,10 @@ import java.util.List;
* @author 夜雨 * @author 夜雨
* @since 2023-08-15 10:22 * @since 2023-08-15 10:22
*/ */
public interface AttachmentMapper extends BaseMapper<Attachment, Long> { public interface AttachmentMapper extends BaseMapper<Attachment, Long>, RawMapper<Attachment, Long> {
/** 有效条件,非删除和销毁 */ /** 有效条件,非删除和销毁 */
String VALID = NOT_DELETE + " AND destroy_at IS NULL"; String VALID = NOT_DELETE + " AND (`destroy_at` IS NULL OR " + UNIX_TIME + " < `destroy_at`)";
@Select("SELECT * FROM attachment WHERE biz_type = #{bizType} AND biz_id = #{bizId} " + VALID + LIMIT_1) @Select("SELECT * FROM attachment WHERE biz_type = #{bizType} AND biz_id = #{bizId} " + VALID + LIMIT_1)
Attachment selectByBizId(Attachment.BizType bizType, long bizId); Attachment selectByBizId(Attachment.BizType bizType, long bizId);
@@ -25,9 +26,12 @@ public interface AttachmentMapper extends BaseMapper<Attachment, Long> {
@Select("SELECT * FROM attachment WHERE biz_type = #{bizType} AND biz_id = #{bizId} AND attach_type = #{attachType} " + VALID + LIMIT_1) @Select("SELECT * FROM attachment WHERE biz_type = #{bizType} AND biz_id = #{bizId} AND attach_type = #{attachType} " + VALID + LIMIT_1)
Attachment selectByAttachType(Attachment.BizType bizType, long bizId, Enum<?> attachType); Attachment selectByAttachType(Attachment.BizType bizType, long bizId, Enum<?> attachType);
List<Attachment> listByBizId(Attachment.BizType bizType, Long bizId, List<Enum<?>> attachTypes, Page page); List<Attachment> listByBizId(Attachment.BizType bizType, Long bizId, List<Enum<?>> attachTypes, Page<Attachment> page);
long countByBizId(Attachment.BizType bizType, Long bizId, List<Enum<?>> attachTypes); long countByBizId(Attachment.BizType bizType, Long bizId, List<Enum<?>> attachTypes);
List<Attachment> listByMd5s(Attachment.BizType bizType, Long bizId, List<Enum<?>> attachTypes, List<String> md5s); List<Attachment> listByMd5s(Attachment.BizType bizType, Long bizId, List<Enum<?>> attachTypes, List<String> md5s);
@Select("SELECT * FROM attachment WHERE `is_destroyed` = FALSE AND `destroy_at` < " + UNIX_TIME)
List<Attachment> selectNeedDestroy();
} }

View File

@@ -1,15 +1,9 @@
package com.imyeyu.api.modules.common.mapper; package com.imyeyu.api.modules.common.mapper;
import com.imyeyu.api.modules.common.entity.Comment; import com.imyeyu.api.modules.common.entity.Comment;
import com.imyeyu.api.modules.common.vo.comment.CommentView;
import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.mapper.BaseMapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update; import org.apache.ibatis.annotations.Update;
import java.util.LinkedHashMap;
import java.util.List;
/** /**
* 评论 * 评论
* *
@@ -18,30 +12,8 @@ import java.util.List;
*/ */
public interface CommentMapper extends BaseMapper<Comment, Long> { public interface CommentMapper extends BaseMapper<Comment, Long> {
@Override
long countByPage(Page page);
@Override
List<Comment> listByPage(Page page);
@Select("SELECT * FROM comment WHERE id = #{id}" + NOT_DELETE)
Comment select(Long id);
@Update("UPDATE comment SET deleted_at = FLOOR(UNIX_TIMESTAMP(NOW(3)) * 1000) WHERE id = #{id}")
@Override
void delete(Long id);
@Select("SELECT COUNT(1) FROM comment WHERE biz_type = #{bizType} AND biz_id = #{bizId}" + NOT_DELETE)
long count(Comment.BizType bizType, Long bizId);
long countAll(Comment.BizType bizType, Long bizId); long countAll(Comment.BizType bizType, Long bizId);
List<CommentView> list(Comment.BizType bizType, Long bizId, Long offset, long limit, LinkedHashMap<String, OrderType> orderMap);
long countByUserId(Long userId);
List<CommentView> listByUserId(Long userId, Long offset, long limit, LinkedHashMap<String, OrderType> orderMap);
@Update("UPDATE comment SET deleted_at = FLOOR(UNIX_TIMESTAMP(NOW(3)) * 1000) WHERE user_id = #{userId} ") @Update("UPDATE comment SET deleted_at = FLOOR(UNIX_TIMESTAMP(NOW(3)) * 1000) WHERE user_id = #{userId} ")
void deleteByUserId(Long userId); void deleteByUserId(Long userId);
} }

View File

@@ -2,7 +2,6 @@ package com.imyeyu.api.modules.common.mapper;
import com.imyeyu.api.modules.blog.entity.CommentRemindQueue; import com.imyeyu.api.modules.blog.entity.CommentRemindQueue;
import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.mapper.BaseMapper;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Select;
import java.util.List; import java.util.List;
@@ -17,10 +16,4 @@ public interface CommentRemindQueueMapper extends BaseMapper<CommentRemindQueue,
@Select("SELECT * FROM comment_remind_queue WHERE user_id = #{userId}") @Select("SELECT * FROM comment_remind_queue WHERE user_id = #{userId}")
List<CommentRemindQueue> listByUserId(Long userId); List<CommentRemindQueue> listByUserId(Long userId);
@Delete("DELETE FROM comment_remind_queue WHERE user_id = #{userId}")
void destroyByUserId(Long userId);
@Delete("DELETE FROM comment_remind_queue WHERE reply_id = #{replyId}")
void destroyByReplyId(Long replyId);
} }

View File

@@ -2,13 +2,7 @@ package com.imyeyu.api.modules.common.mapper;
import com.imyeyu.api.modules.common.entity.CommentReply; import com.imyeyu.api.modules.common.entity.CommentReply;
import com.imyeyu.api.modules.common.vo.comment.CommentReplyPage;
import com.imyeyu.api.modules.common.vo.comment.CommentReplyView;
import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.mapper.BaseMapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
/** /**
* 评论回复 * 评论回复
@@ -17,19 +11,4 @@ import java.util.List;
* @since 2021-08-24 10:36 * @since 2021-08-24 10:36
*/ */
public interface CommentReplyMapper extends BaseMapper<CommentReply, Long> { public interface CommentReplyMapper extends BaseMapper<CommentReply, Long> {
@Select("SELECT * FROM comment_reply WHERE sender_id = #{senderId}" + NOT_DELETE)
List<CommentReply> listAllBySenderId(Long senderId);
@Select("SELECT COUNT(1) FROM comment_reply WHERE ${bizType.column} = #{bizId}" + NOT_DELETE)
long countByBizType(CommentReplyPage.BizType bizType, Long bizId);
@Select("SELECT * FROM comment_reply WHERE ${bizType.column} = #{bizId} AND ignored_at IS NULL" + NOT_DELETE + PAGE)
List<CommentReplyView> listByBizType(CommentReplyPage.BizType bizType, Long bizId, Long offset, long limit);
@Update("UPDATE comment_reply SET deleted_at = " + UNIX_TIME + " WHERE sender_id = #{userId} OR receiver_id = #{userId}")
void deleteByUserId(Long userId);
@Update("UPDATE comment_reply SET deleted_at = " + UNIX_TIME + " WHERE comment_id = #{commentId}")
void deleteByCommentId(Long commentId);
} }

View File

@@ -1,8 +1,6 @@
package com.imyeyu.api.modules.common.service; package com.imyeyu.api.modules.common.service;
import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.vo.attachment.AttachmentRequest;
import com.imyeyu.api.modules.common.vo.attachment.AttachmentView;
import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.service.BaseService; import com.imyeyu.spring.service.BaseService;
import com.mongodb.client.gridfs.model.GridFSFile; import com.mongodb.client.gridfs.model.GridFSFile;
@@ -22,27 +20,6 @@ public interface AttachmentService extends BaseService<Attachment, Long> {
Attachment createMedia(Attachment attachment); Attachment createMedia(Attachment attachment);
/**
*
*
* @param request
*/
@Deprecated
default void create(AttachmentRequest request) {
create((Attachment) request);
}
/**
* 创建媒体附件,同步创建缩略图
*
* @param request 附件请求
* @return 缩略图附件
*/
@Deprecated
default Attachment createMedia(AttachmentRequest request) throws TimiException {
return createMedia((Attachment) request);
}
void deleteMedia(Long thumbId) throws TimiException; void deleteMedia(Long thumbId) throws TimiException;
Attachment getByBizId(Attachment.BizType bizType, long bizId); Attachment getByBizId(Attachment.BizType bizType, long bizId);
@@ -51,8 +28,6 @@ public interface AttachmentService extends BaseService<Attachment, Long> {
Attachment getByMongoId(String mongoId); Attachment getByMongoId(String mongoId);
AttachmentView viewByMongoId(String mongoId);
GridFSFile readByMongoId(String mongoId); GridFSFile readByMongoId(String mongoId);
InputStream getInputStreamByMongoId(String mongoId); InputStream getInputStreamByMongoId(String mongoId);
@@ -81,7 +56,9 @@ public interface AttachmentService extends BaseService<Attachment, Long> {
*/ */
long countByBizId(Attachment.BizType bizType, Long bizId, Enum<?> ...attachTypes); long countByBizId(Attachment.BizType bizType, Long bizId, Enum<?> ...attachTypes);
List<Attachment> listByMd5s(Attachment.BizType bizType, Long bizId, List<Enum<?>> attachTypeList, List<String> md5s) throws TimiException; List<Attachment> listByMd5s(Attachment.BizType bizType, Long bizId, List<Enum<?>> attachTypeList, List<String> md5s);
void deleteByBizId(Attachment.BizType bizType, long bizId, Enum<?> ...attachTypes); void deleteByBizId(Attachment.BizType bizType, long bizId, Enum<?> ...attachTypes);
List<Attachment> listNeedDestroy();
} }

View File

@@ -0,0 +1,36 @@
package com.imyeyu.api.modules.common.service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
/**
* 共享剪切板服务
*
* @author 夜雨
* @since 2026-01-15 16:40
*/
public interface ClipboardService {
/**
* 获取共享剪切板内容
*
* @param id 会话 ID
* @return 剪切板内容
*/
String getContent(String id);
/**
* 设置共享剪切板内容
*
* @param id 会话 ID
* @param content 剪切板内容
*/
void setContent(String id, String content);
/**
* 订阅共享剪切板实时更新
*
* @param id 会话 ID
* @return SSE 发射器
*/
SseEmitter subscribe(String id);
}

View File

@@ -1,13 +1,7 @@
package com.imyeyu.api.modules.common.service; package com.imyeyu.api.modules.common.service;
import com.imyeyu.api.modules.common.entity.CommentReply; import com.imyeyu.api.modules.common.entity.CommentReply;
import com.imyeyu.api.modules.common.vo.comment.CommentReplyPage; import com.imyeyu.spring.service.BaseService;
import com.imyeyu.api.modules.common.vo.comment.CommentReplyView;
import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.service.CreatableService;
import com.imyeyu.spring.service.DeletableService;
import com.imyeyu.spring.service.GettableService;
import com.imyeyu.spring.service.UpdatableService;
/** /**
* 评论回复服务 * 评论回复服务
@@ -15,7 +9,5 @@ import com.imyeyu.spring.service.UpdatableService;
* @author 夜雨 * @author 夜雨
* @since 2021-08-24 10:33 * @since 2021-08-24 10:33
*/ */
public interface CommentReplyService extends CreatableService<CommentReply>, GettableService<CommentReply, Long>, UpdatableService<CommentReply>, DeletableService<Long> { public interface CommentReplyService extends BaseService<CommentReply, Long> {
PageResult<CommentReplyView> pageByBizType(CommentReplyPage page);
} }

View File

@@ -1,14 +1,7 @@
package com.imyeyu.api.modules.common.service; package com.imyeyu.api.modules.common.service;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.api.modules.common.entity.Comment; import com.imyeyu.api.modules.common.entity.Comment;
import com.imyeyu.api.modules.common.vo.comment.CommentView; import com.imyeyu.spring.service.BaseService;
import com.imyeyu.api.modules.common.vo.comment.UserCommentPage;
import com.imyeyu.api.modules.git.vo.issue.CommentPage;
import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.service.CreatableService;
import com.imyeyu.spring.service.DeletableService;
import com.imyeyu.spring.service.GettableService;
/** /**
* 评论服务 * 评论服务
@@ -16,16 +9,5 @@ import com.imyeyu.spring.service.GettableService;
* @author 夜雨 * @author 夜雨
* @since 2021-02-23 21:32 * @since 2021-02-23 21:32
*/ */
public interface CommentService extends CreatableService<Comment>, GettableService<Comment, Long>, DeletableService<Long> { public interface CommentService extends BaseService<Comment, Long> {
PageResult<CommentView> pageByBizId(CommentPage page);
/**
* 获取用户评论页面
*
* @param userCommentPage 页面参数
* @return 页面列表
* @throws TimiException 服务异常
*/
PageResult<CommentView> pageByUserId(UserCommentPage userCommentPage);
} }

View File

@@ -1,12 +1,12 @@
package com.imyeyu.api.modules.common.service; package com.imyeyu.api.modules.common.service;
import com.google.gson.JsonArray; import com.fasterxml.jackson.core.type.TypeReference;
import com.google.gson.JsonElement; import com.fasterxml.jackson.databind.JsonNode;
import com.google.gson.JsonObject; import com.fasterxml.jackson.databind.node.ArrayNode;
import com.google.gson.reflect.TypeToken; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.api.modules.common.bean.SettingKey; import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.entity.Setting; import com.imyeyu.api.modules.common.entity.Setting;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.service.UpdatableService; import com.imyeyu.spring.service.UpdatableService;
import java.util.Arrays; import java.util.Arrays;
@@ -60,15 +60,15 @@ public interface SettingService extends UpdatableService<Setting> {
*/ */
boolean not(SettingKey key); boolean not(SettingKey key);
JsonElement getAsJsonElement(SettingKey key); JsonNode getAsJsonNode(SettingKey key);
JsonObject getAsJsonObject(SettingKey key); ObjectNode getAsJsonObject(SettingKey key);
JsonArray getAsJsonArray(SettingKey key); ArrayNode getAsArrayNode(SettingKey key);
<T> T fromJson(SettingKey key, Class<T> clazz); <T> T fromJson(SettingKey key, Class<T> clazz);
<T> T fromJson(SettingKey key, TypeToken<T> typeToken); <T> T fromJson(SettingKey key, TypeReference<T> typeReference);
<T> T fromYaml(SettingKey key, Class<T> clazz); <T> T fromYaml(SettingKey key, Class<T> clazz);

View File

@@ -1,12 +1,8 @@
package com.imyeyu.api.modules.common.service; package com.imyeyu.api.modules.common.service;
import com.imyeyu.api.modules.common.bean.TempFileMetaData;
import com.imyeyu.api.modules.common.vo.TempFileResponse; import com.imyeyu.api.modules.common.vo.TempFileResponse;
import com.imyeyu.java.bean.timi.TimiException;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.InputStream;
import java.util.List; import java.util.List;
/** /**
@@ -15,11 +11,5 @@ import java.util.List;
*/ */
public interface TempFileService { public interface TempFileService {
List<TempFileResponse> store(List<MultipartFile> files) throws TimiException; List<TempFileResponse> store(List<MultipartFile> files, Long ttl);
File get(String id) throws TimiException;
InputStream getInputStream(String id) throws TimiException;
TempFileMetaData metadata(String id) throws TimiException;
} }

View File

@@ -1,6 +1,7 @@
package com.imyeyu.api.modules.common.service.implement; package com.imyeyu.api.modules.common.service.implement;
import com.google.gson.Gson; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig; import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
import com.imyeyu.api.modules.common.bean.MediaAttach; import com.imyeyu.api.modules.common.bean.MediaAttach;
import com.imyeyu.api.modules.common.bean.Metadata; import com.imyeyu.api.modules.common.bean.Metadata;
@@ -8,13 +9,13 @@ import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.mapper.AttachmentMapper; import com.imyeyu.api.modules.common.mapper.AttachmentMapper;
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.vo.attachment.AttachmentView;
import com.imyeyu.api.util.JavaCV; import com.imyeyu.api.util.JavaCV;
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.network.Network; import com.imyeyu.network.Network;
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;
import com.imyeyu.utils.Time; import com.imyeyu.utils.Time;
@@ -24,7 +25,6 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails; import net.coobird.thumbnailator.Thumbnails;
import org.apache.tika.Tika; import org.apache.tika.Tika;
import org.springframework.beans.BeanUtils;
import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.gridfs.GridFsTemplate; import org.springframework.data.mongodb.gridfs.GridFsTemplate;
@@ -55,7 +55,7 @@ public class AttachmentServiceImplement extends AbstractEntityService<Attachment
private final AttachmentMapper mapper; private final AttachmentMapper mapper;
private final Gson gson; private final ObjectMapper jackson;
private final GridFSBucket gridFSBucket; private final GridFSBucket gridFSBucket;
private final GridFsTemplate gridFsTemplate; private final GridFsTemplate gridFsTemplate;
@@ -68,13 +68,11 @@ public class AttachmentServiceImplement extends AbstractEntityService<Attachment
@Override @Override
public void create(Attachment attachment) { public void create(Attachment attachment) {
TimiException.required(attachment.getBizType(), "not found attachment.bizType"); TimiException.required(attachment.getBizType(), "not found attachment.bizType");
TimiException.required(attachment.getBizId(), "not found attachment.bizId");
TimiException.required(attachment.getName(), "not found attachment.name"); TimiException.required(attachment.getName(), "not found attachment.name");
String mongoId = null; String mongoId = null;
try { try {
InputStream is = attachment.getInputStream(); InputStream is = attachment.getInputStream();
TimiException.required(is, "not found attachment.inputStream"); TimiException.required(is, "not found attachment.inputStream");
TimiException.requiredTrue(is.available() != 0, "empty attachment.inputStream");
StringBuilder mongoName = new StringBuilder(attachment.getBizType().toString()); StringBuilder mongoName = new StringBuilder(attachment.getBizType().toString());
if (TimiJava.isNotEmpty(attachment.getAttachType())) { if (TimiJava.isNotEmpty(attachment.getAttachType())) {
@@ -88,12 +86,14 @@ public class AttachmentServiceImplement extends AbstractEntityService<Attachment
attachment.setSize(gridFSFile.getLength()); attachment.setSize(gridFSFile.getLength());
attachment.setMd5(IO.md5(gridFSBucket.openDownloadStream(gridFSFile.getObjectId()))); attachment.setMd5(IO.md5(gridFSBucket.openDownloadStream(gridFSFile.getObjectId())));
attachment.setMimeType(new Tika().detect(gridFSBucket.openDownloadStream(gridFSFile.getObjectId()))); attachment.setMimeType(new Tika().detect(gridFSBucket.openDownloadStream(gridFSFile.getObjectId())));
attachment.setUploaderIp(TimiSpring.getRequestIP());
attachment.setIsDestroyed(false);
if (attachment.getMimeType().startsWith("image")) { if (attachment.getMimeType().startsWith("image")) {
BufferedImage image = ImageIO.read(gridFSBucket.openDownloadStream(gridFSFile.getObjectId())); BufferedImage image = ImageIO.read(gridFSBucket.openDownloadStream(gridFSFile.getObjectId()));
Metadata.Image metadata = new Metadata.Image(); attachment.setMetadata(TimiJava.defaultIfNull(attachment.getMetadata(), jackson.createObjectNode()));
metadata.setWidth(image.getWidth()); ObjectNode metadata = (ObjectNode) attachment.getMetadata();
metadata.setHeight(image.getHeight()); metadata.put("width", image.getWidth());
attachment.setMetadata(gson.toJsonTree(metadata).getAsJsonObject()); metadata.put("height", image.getHeight());
} }
mapper.insert(attachment); mapper.insert(attachment);
} catch (Exception e) { } catch (Exception e) {
@@ -109,12 +109,17 @@ public class AttachmentServiceImplement extends AbstractEntityService<Attachment
@Override @Override
public void destroy(Long id) { public void destroy(Long id) {
try { try {
Attachment attachment = get(id); Attachment attach = mapper.selectRaw(id);
gridFsTemplate.delete(Query.query(Criteria.where("_id").is(attachment.getMongoId()))); GridFSFile file = readByMongoId(attach.getMongoId());
if (file != null) {
attachment.setDeletedAt(Time.now()); gridFsTemplate.delete(Query.query(Criteria.where("_id").is(attach.getMongoId())));
attachment.setDestroyAt(Time.now()); }
mapper.update(attachment); if (!attach.isDeleted()) {
attach.setDeletedAt(Time.now());
}
attach.setIsDestroyed(true);
attach.setDestroyAt(Time.now());
mapper.update(attach);
} catch (Exception e) { } catch (Exception e) {
log.error("delete mongo file error", e); log.error("delete mongo file error", e);
throw new TimiException(TimiCode.ERROR).msgKey("TODO delete mongo file error"); throw new TimiException(TimiCode.ERROR).msgKey("TODO delete mongo file error");
@@ -130,17 +135,13 @@ public class AttachmentServiceImplement extends AbstractEntityService<Attachment
create(attachment); create(attachment);
// 生成缩略图 // 生成缩略图
InputStream mimeStream = getInputStreamByMongoId(attachment.getMongoId()); InputStream mimeStream = attachment.openInputStream();
String mimeType = new Tika().detect(mimeStream); String mimeType = new Tika().detect(mimeStream);
boolean isImage = false;
InputStream sourceStream = getInputStreamByMongoId(attachment.getMongoId()); InputStream sourceStream = attachment.openInputStream();
ByteArrayOutputStream thumbStream = new ByteArrayOutputStream(); ByteArrayOutputStream thumbStream = new ByteArrayOutputStream();
switch (mimeType) { switch (mimeType) {
case "image/png", "image/bmp", "image/jpeg" -> { case "image/png", "image/bmp", "image/jpeg" -> Thumbnails.of(sourceStream).width(256).keepAspectRatio(true).toOutputStream(thumbStream);
isImage = true;
Thumbnails.of(sourceStream).width(256).keepAspectRatio(true).toOutputStream(thumbStream);
}
case "video/mp4", "video/quicktime" -> { case "video/mp4", "video/quicktime" -> {
log.info("capturing thumbnail: {}", attachment.getName()); log.info("capturing thumbnail: {}", attachment.getName());
long start = Time.now(); long start = Time.now();
@@ -155,18 +156,17 @@ public class AttachmentServiceImplement extends AbstractEntityService<Attachment
} }
} }
} }
MediaAttach.ExtData extData = new MediaAttach.ExtData(); Metadata.ThumbImage thumbMetadata = new Metadata.ThumbImage();
extData.setImage(isImage); thumbMetadata.setSourceId(attachment.getId());
extData.setVideo(!isImage); thumbMetadata.setSourceMongoId(attachment.getMongoId());
extData.setSourceId(attachment.getId()); thumbMetadata.setSourceMimeType(mimeType);
extData.setSourceMongoId(attachment.getMongoId());
Attachment thumbAttach = new Attachment(); Attachment thumbAttach = new Attachment();
thumbAttach.setName(Network.simpleURIFileName(attachment.getName()) + ".png"); thumbAttach.setName(Network.simpleURIFileName(attachment.getName()) + ".png");
thumbAttach.setBizType(attachment.getBizType()); thumbAttach.setBizType(attachment.getBizType());
thumbAttach.setBizId(attachment.getBizId()); thumbAttach.setBizId(attachment.getBizId());
thumbAttach.setAttachTypeValue(MediaAttach.Type.THUMB); thumbAttach.setAttachTypeValue(MediaAttach.Type.THUMB);
thumbAttach.setExt(gson.toJson(extData)); thumbAttach.setMetadata(jackson.valueToTree(thumbMetadata));
thumbAttach.setInputStream(new ByteArrayInputStream(thumbStream.toByteArray())); thumbAttach.setInputStream(new ByteArrayInputStream(thumbStream.toByteArray()));
create(thumbAttach); create(thumbAttach);
@@ -182,8 +182,8 @@ public class AttachmentServiceImplement extends AbstractEntityService<Attachment
public void deleteMedia(Long thumbId) throws TimiException { public void deleteMedia(Long thumbId) throws TimiException {
Attachment attachment = get(thumbId); Attachment attachment = get(thumbId);
delete(attachment.getId()); delete(attachment.getId());
MediaAttach.ExtData data = gson.fromJson(attachment.getExt(), MediaAttach.ExtData.class); Metadata.ThumbImage thumbMetadata = jackson.convertValue(attachment.getMetadata(), Metadata.ThumbImage.class);
delete(data.getSourceId()); delete(thumbMetadata.getSourceId());
} }
@Override @Override
@@ -201,22 +201,11 @@ public class AttachmentServiceImplement extends AbstractEntityService<Attachment
return mapper.selectByMongoId(mongoId); return mapper.selectByMongoId(mongoId);
} }
@Override
public AttachmentView viewByMongoId(String mongoId) {
Attachment attachment = getByMongoId(mongoId);
if (attachment == null) {
throw new TimiException(TimiCode.RESULT_NULL).msgKey("TODO not found attachment");
}
AttachmentView view = new AttachmentView();
BeanUtils.copyProperties(attachment, view);
return view;
}
@Override @Override
public GridFSFile readByMongoId(String mongoId) { public GridFSFile readByMongoId(String mongoId) {
Attachment view = mapper.selectByMongoId(mongoId); Attachment attach = mapper.selectByMongoId(mongoId);
TimiException.required(view, "not found attachment: %s".formatted(mongoId)); TimiException.required(attach, "not found attachment: %s".formatted(mongoId));
return gridFsTemplate.findOne(new Query(Criteria.where("_id").is(view.getMongoId()))); return gridFsTemplate.findOne(new Query(Criteria.where("_id").is(attach.getMongoId())));
} }
@Override @Override
@@ -227,7 +216,7 @@ public class AttachmentServiceImplement extends AbstractEntityService<Attachment
@Override @Override
public byte[] readAllByMongoId(String mongoId) { public byte[] readAllByMongoId(String mongoId) {
try { try {
return IO.toBytes(getInputStreamByMongoId(mongoId)); return IO.toBytes(getByMongoId(mongoId).openInputStream());
} catch (IOException e) { } catch (IOException e) {
throw new TimiException(TimiCode.ERROR, "TODO 读取失败"); throw new TimiException(TimiCode.ERROR, "TODO 读取失败");
} }
@@ -262,4 +251,9 @@ public class AttachmentServiceImplement extends AbstractEntityService<Attachment
} }
} }
} }
@Override
public List<Attachment> listNeedDestroy() {
return mapper.selectNeedDestroy();
}
} }

View File

@@ -0,0 +1,140 @@
package com.imyeyu.api.modules.common.service.implement;
import com.imyeyu.api.modules.common.service.ClipboardService;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.util.Redis;
import com.imyeyu.utils.Time;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 共享剪切板服务实现
*
* @author 夜雨
* @since 2026-01-15 16:40
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class ClipboardServiceImplement implements ClipboardService {
/** Redis key 前缀 */
private static final String KEY_PREFIX = "clipboard:";
/** 剪切板内容最大长度 */
private static final int MAX_CONTENT_SIZE = 10 * 1024 * 1024;
/** 剪切板缓存 */
private final Redis<String, String> redisClipboard;
/** SSE 订阅者 */
private final Map<String, List<SseEmitter>> emitterMap = new ConcurrentHashMap<>();
@Override
public String getContent(String id) {
validateId(id);
return redisClipboard.get(getKey(id));
}
@Override
public void setContent(String id, String content) {
validateId(id);
if (content == null) {
throw new TimiException(TimiCode.ARG_MISS).msgKey("剪切板内容不能为空");
}
byte[] contentBytes = content.getBytes(StandardCharsets.UTF_8);
if (MAX_CONTENT_SIZE < contentBytes.length) {
throw new TimiException(TimiCode.ARG_BAD).msgKey("剪切板内容不能超过 10MB");
}
redisClipboard.set(getKey(id), content, Time.D * 7);
notifySubscribers(id, content);
}
@Override
public SseEmitter subscribe(String id) {
validateId(id);
SseEmitter emitter = new SseEmitter(0L);
emitter.onCompletion(() -> removeEmitter(id, emitter));
emitter.onTimeout(() -> removeEmitter(id, emitter));
emitter.onError(e -> removeEmitter(id, emitter));
emitterMap.computeIfAbsent(id, key -> new CopyOnWriteArrayList<>()).add(emitter);
String content = getContent(id);
if (content != null) {
sendEvent(emitter, content);
}
return emitter;
}
private void validateId(String id) {
if (TimiJava.isEmpty(id)) {
throw new TimiException(TimiCode.ARG_MISS).msgKey("id 不能为空");
}
}
private String getKey(String id) {
return "%s%s".formatted(KEY_PREFIX, id);
}
private void notifySubscribers(String id, String content) {
List<SseEmitter> emitterList = emitterMap.get(id);
if (emitterList == null || emitterList.isEmpty()) {
return;
}
for (SseEmitter emitter : emitterList) {
boolean success;
try {
success = sendEvent(emitter, content);
} catch (Exception e) {
log.debug("发送剪切板事件异常", e);
success = false;
}
if (!success) {
completeEmitter(id, emitter);
}
}
}
private boolean sendEvent(SseEmitter emitter, String content) {
try {
emitter.send(SseEmitter.event().name("clipboard").data(content));
return true;
} catch (IOException e) {
log.debug("剪切板连接已断开", e);
return false;
} catch (IllegalStateException e) {
log.debug("剪切板 SSE 状态异常", e);
return false;
}
}
private void completeEmitter(String id, SseEmitter emitter) {
removeEmitter(id, emitter);
try {
emitter.complete();
} catch (Exception e) {
log.debug("关闭剪切板 SSE 连接失败", e);
}
}
private void removeEmitter(String id, SseEmitter emitter) {
List<SseEmitter> emitterList = emitterMap.get(id);
if (emitterList == null) {
return;
}
emitterList.remove(emitter);
if (emitterList.isEmpty()) {
emitterMap.remove(id);
}
}
}

View File

@@ -1,13 +1,8 @@
package com.imyeyu.api.modules.common.service.implement; package com.imyeyu.api.modules.common.service.implement;
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.TimiServerAPI;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig; import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
import com.imyeyu.api.modules.blog.entity.Article;
import com.imyeyu.api.modules.blog.entity.CommentRemindQueue; import com.imyeyu.api.modules.blog.entity.CommentRemindQueue;
import com.imyeyu.api.modules.blog.service.ArticleService;
import com.imyeyu.api.modules.blog.service.CommentRemindQueueService; import com.imyeyu.api.modules.blog.service.CommentRemindQueueService;
import com.imyeyu.api.modules.common.bean.CommentSupport; import com.imyeyu.api.modules.common.bean.CommentSupport;
import com.imyeyu.api.modules.common.entity.Comment; import com.imyeyu.api.modules.common.entity.Comment;
@@ -21,23 +16,21 @@ import com.imyeyu.api.modules.common.service.CommentService;
import com.imyeyu.api.modules.common.service.EmailQueueService; import com.imyeyu.api.modules.common.service.EmailQueueService;
import com.imyeyu.api.modules.common.service.UserConfigService; import com.imyeyu.api.modules.common.service.UserConfigService;
import com.imyeyu.api.modules.common.service.UserService; import com.imyeyu.api.modules.common.service.UserService;
import com.imyeyu.api.modules.common.vo.comment.CommentReplyPage; import com.imyeyu.java.TimiJava;
import com.imyeyu.api.modules.common.vo.comment.CommentReplyView; import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.api.modules.common.vo.comment.CommentView; import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.api.modules.git.service.RepositoryService;
import com.imyeyu.spring.TimiSpring; import com.imyeyu.spring.TimiSpring;
import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.bean.PageResult;
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.service.GettableService; import com.imyeyu.spring.service.GettableService;
import com.imyeyu.utils.Time; import com.imyeyu.utils.Time;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.UUID; import java.util.UUID;
/** /**
@@ -52,8 +45,6 @@ public class CommentReplyServiceImplement extends AbstractEntityService<CommentR
private final UserService userService; private final UserService userService;
private final CommentService commentService; private final CommentService commentService;
private final ArticleService articleService;
private final RepositoryService repositoryService;
private final UserConfigService userConfigService; private final UserConfigService userConfigService;
private final EmailQueueService emailQueueService; private final EmailQueueService emailQueueService;
private final CommentRemindQueueService commentRemindQueueService; private final CommentRemindQueueService commentRemindQueueService;
@@ -65,11 +56,23 @@ public class CommentReplyServiceImplement extends AbstractEntityService<CommentR
return mapper; return mapper;
} }
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public void delete(Long crId) { public PageResult<CommentReply> page(Page<CommentReply> page) {
super.delete(crId); PageResult<CommentReply> result = super.page(page);
commentRemindQueueService.destroyByReplyId(crId); for (CommentReply reply : result.getList()) {
Comment comment = commentService.get(reply.getCommentId());
if (TimiJava.isNotEmpty(comment.getUserId())) {
comment.setUser(userService.view(comment.getUserId()));
}
reply.setComment(comment);
if (TimiJava.isNotEmpty(reply.getSenderId())) {
reply.setSender(userService.view(reply.getSenderId()));
}
if (TimiJava.isNotEmpty(reply.getReceiverId())) {
reply.setReceiver(userService.view(reply.getReceiverId()));
}
}
return result;
} }
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@@ -160,37 +163,10 @@ public class CommentReplyServiceImplement extends AbstractEntityService<CommentR
} }
} }
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public PageResult<CommentReplyView> pageByBizType(CommentReplyPage page) { public void delete(Long crId) {
PageResult<CommentReplyView> result = new PageResult<>(); super.delete(crId);
List<CommentReplyView> list = mapper.listByBizType(page.getBizType(), page.getBizId(), page.getOffset(), page.getLimit()); commentRemindQueueService.destroyByReplyId(crId);
for (int i = 0; i < list.size(); i++) {
CommentReplyView reply = list.get(i);
CommentView comment = new CommentView();
BeanUtils.copyProperties(commentService.get(reply.getCommentId()), comment);
if (TimiJava.isNotEmpty(comment.getUserId())) {
comment.setUser(userService.view(comment.getUserId()));
}
switch (comment.getBizType()) {
case ARTICLE -> {
Article article = articleService.get(comment.getBizId());
article.setData(null);
article.setExtendData(null);
comment.setArticle(article);
}
// case GIT_ISSUE, GIT_MERGE -> comment.setRepository(repositoryService.get(comment.getBizId()));
}
reply.setComment(comment);
if (TimiJava.isNotEmpty(reply.getSenderId())) {
reply.setSender(userService.view(reply.getSenderId()));
}
if (TimiJava.isNotEmpty(reply.getReceiverId())) {
reply.setReceiver(userService.view(reply.getReceiverId()));
}
}
result.setList(list);
result.setTotal(mapper.countByBizType(page.getBizType(), page.getBizId()));
return result;
} }
} }

View File

@@ -1,12 +1,8 @@
package com.imyeyu.api.modules.common.service.implement; package com.imyeyu.api.modules.common.service.implement;
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.TimiServerAPI;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig; import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
import com.imyeyu.api.modules.blog.entity.Article; import com.imyeyu.api.modules.blog.entity.CommentRemindQueue;
import com.imyeyu.api.modules.blog.service.ArticleService;
import com.imyeyu.api.modules.common.bean.CommentSupport; import com.imyeyu.api.modules.common.bean.CommentSupport;
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;
@@ -16,12 +12,11 @@ import com.imyeyu.api.modules.common.mapper.CommentRemindQueueMapper;
import com.imyeyu.api.modules.common.mapper.CommentReplyMapper; import com.imyeyu.api.modules.common.mapper.CommentReplyMapper;
import com.imyeyu.api.modules.common.service.CommentService; import com.imyeyu.api.modules.common.service.CommentService;
import com.imyeyu.api.modules.common.service.UserService; import com.imyeyu.api.modules.common.service.UserService;
import com.imyeyu.api.modules.common.vo.comment.CommentReplyView; import com.imyeyu.java.TimiJava;
import com.imyeyu.api.modules.common.vo.comment.CommentView; import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.api.modules.common.vo.comment.UserCommentPage; import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.api.modules.git.service.RepositoryService;
import com.imyeyu.api.modules.git.vo.issue.CommentPage;
import com.imyeyu.spring.TimiSpring; import com.imyeyu.spring.TimiSpring;
import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.spring.service.AbstractEntityService; import com.imyeyu.spring.service.AbstractEntityService;
@@ -31,7 +26,6 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
/** /**
@@ -45,8 +39,6 @@ import java.util.List;
public class CommentServiceImplement extends AbstractEntityService<Comment, Long> implements CommentService { public class CommentServiceImplement extends AbstractEntityService<Comment, Long> implements CommentService {
private final UserService userService; private final UserService userService;
private final ArticleService articleService;
private final RepositoryService repositoryService;
private final CommentMapper mapper; private final CommentMapper mapper;
private final CommentReplyMapper replyMapper; private final CommentReplyMapper replyMapper;
@@ -57,6 +49,36 @@ public class CommentServiceImplement extends AbstractEntityService<Comment, Long
return mapper; return mapper;
} }
@Override
public PageResult<Comment> page(Page<Comment> page) {
PageResult<Comment> result = super.page(page);
for (Comment comment : result.getList()) {
if (TimiJava.isNotEmpty(comment.getUserId())) {
comment.setUser(userService.view(comment.getUserId()));
}
Page<CommentReply> replyPage = new Page<>();
replyPage.setIndex(0);
replyPage.setSize(6);
{
CommentReply exampleReply = new CommentReply();
exampleReply.setCommentId(comment.getId());
replyPage.setEqualsExample(exampleReply);
}
PageResult<CommentReply> replyResult = replyMapper.selectPageResult(replyPage);
comment.setReplies(replyResult.getList());
comment.setRepliesLength(replyResult.getTotal());
for (CommentReply reply : replyResult.getList()) {
if (TimiJava.isNotEmpty(reply.getSenderId())) {
reply.setSender(userService.view(reply.getSenderId()));
}
if (TimiJava.isNotEmpty(reply.getReceiverId())) {
reply.setReceiver(userService.view(reply.getReceiverId()));
}
}
}
return result;
}
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public void create(Comment comment) { public void create(Comment comment) {
@@ -83,77 +105,31 @@ public class CommentServiceImplement extends AbstractEntityService<Comment, Long
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public void delete(Long cId) { public void delete(Long commentId) {
User user = userService.getLoginUser(); User user = userService.getLoginUser();
Comment comment = get(cId); Comment comment = get(commentId);
if (!comment.getUserId().equals(user.getId())) { if (!comment.getUserId().equals(user.getId())) {
throw new TimiException(TimiCode.PERMISSION_ERROR).msgKey("token.illegal"); throw new TimiException(TimiCode.PERMISSION_ERROR).msgKey("token.illegal");
} }
List<CommentReply> replies = replyMapper.listAllBySenderId(user.getId()); {
for (int i = 0; i < replies.size(); i++) { // 移除被回复者的回复提醒队列
// 移出被回复者的回复提醒队列 CommentReply example = new CommentReply();
remindQueueMapper.destroyByReplyId(replies.get(i).getId()); example.setSenderId(user.getId());
} List<CommentReply> replies = replyMapper.selectAllByExample(example);
replyMapper.deleteByCommentId(cId); for (CommentReply reply : replies) {
super.delete(cId); CommentRemindQueue destroyRemindExample = new CommentRemindQueue();
} destroyRemindExample.setReplyId(reply.getReplyId());
remindQueueMapper.destroyAllByExample(destroyRemindExample);
@Override
public PageResult<CommentView> pageByBizId(CommentPage page) {
if (page.getOrderMap() == null) {
page.setOrderMap(new LinkedHashMap<>());
}
if (page.getOrderMap().isEmpty()) {
page.getOrderMap().put("createdAt", BaseMapper.OrderType.DESC);
}
List<CommentView> list = mapper.list(page.getBizType(), page.getBizId(), page.getOffset(), page.getLimit(), page.getOrderMap());
for (int i = 0; i < list.size(); i++) {
CommentView comment = list.get(i);
if (TimiJava.isNotEmpty(comment.getUserId())) {
comment.setUser(userService.view(comment.getUserId()));
}
List<CommentReplyView> replies = comment.getReplies();
for (int j = 0; j < replies.size(); j++) {
CommentReplyView reply = replies.get(j);
if (TimiJava.isNotEmpty(reply.getSenderId())) {
reply.setSender(userService.view(reply.getSenderId()));
}
if (TimiJava.isNotEmpty(reply.getReceiverId())) {
reply.setReceiver(userService.view(reply.getReceiverId()));
}
} }
} }
PageResult<CommentView> result = new PageResult<>(); {
result.setList(list); // 删除回复
result.setTotal(mapper.count(page.getBizType(), page.getBizId())); CommentReply example = new CommentReply();
return result; example.setCommentId(commentId);
} replyMapper.deleteAllByExample(example);
@Override
public PageResult<CommentView> pageByUserId(UserCommentPage page) {
if (page.getOrderMap() == null) {
page.setOrderMap(new LinkedHashMap<>());
} }
if (page.getOrderMap().isEmpty()) { // 删除评论
page.getOrderMap().put("createdAt", BaseMapper.OrderType.DESC); super.delete(commentId);
}
PageResult<CommentView> result = new PageResult<>();
result.setList(mapper.listByUserId(page.getUserId(), page.getOffset(), page.getLimit(), page.getOrderMap()));
result.setTotal(mapper.countByUserId(page.getUserId()));
for (int i = 0; i < result.getList().size(); i++) {
CommentView view = result.getList().get(i);
switch (view.getBizType()) {
case ARTICLE -> {
Article article = articleService.get(view.getBizId());
article.setData(null);
article.setExtendData(null);
view.setArticle(article);
}
// case GIT_ISSUE, GIT_MERGE -> view.setRepository(repositoryService.get(view.getBizId()));
}
}
return result;
} }
} }

View File

@@ -1,11 +1,11 @@
package com.imyeyu.api.modules.common.service.implement; package com.imyeyu.api.modules.common.service.implement;
import com.google.gson.Gson; import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.gson.JsonArray; import com.fasterxml.jackson.core.type.TypeReference;
import com.google.gson.JsonElement; import com.fasterxml.jackson.databind.JsonNode;
import com.google.gson.JsonObject; import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.JsonParser; import com.fasterxml.jackson.databind.node.ArrayNode;
import com.google.gson.reflect.TypeToken; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.imyeyu.api.modules.common.bean.SettingKey; import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.entity.Setting; import com.imyeyu.api.modules.common.entity.Setting;
import com.imyeyu.api.modules.common.mapper.SettingMapper; import com.imyeyu.api.modules.common.mapper.SettingMapper;
@@ -39,13 +39,19 @@ public class SettingServiceImplement extends AbstractEntityService<Setting, Stri
private final SettingMapper mapper; private final SettingMapper mapper;
private final Redis<String, String> redisSetting; private final Redis<String, String> redisSetting;
private final Gson gson; private final ObjectMapper jackson;
@Override @Override
protected BaseMapper<Setting, String> mapper() { protected BaseMapper<Setting, String> mapper() {
return mapper; return mapper;
} }
@Override
public void update(Setting setting) {
super.update(setting);
clearCache(setting.getKey());
}
public Setting getByKey(SettingKey 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");
@@ -53,7 +59,11 @@ public class SettingServiceImplement extends AbstractEntityService<Setting, Stri
String cacheValue = redisSetting.get(key.toString()); String cacheValue = redisSetting.get(key.toString());
if (TimiJava.isNotEmpty(cacheValue)) { if (TimiJava.isNotEmpty(cacheValue)) {
return gson.fromJson(cacheValue, Setting.class); try {
return jackson.readValue(cacheValue, Setting.class);
} catch (JsonProcessingException e) {
throw new TimiException(TimiCode.ERROR, "read setting cache error", e);
}
} }
Setting setting = mapper.selectByKey(key); Setting setting = mapper.selectByKey(key);
if (TimiJava.isEmpty(setting.getValue())) { if (TimiJava.isEmpty(setting.getValue())) {
@@ -66,7 +76,11 @@ public class SettingServiceImplement extends AbstractEntityService<Setting, Stri
settingTTL = Integer.parseInt(getByKey(SettingKey.TTL_SETTING).getValue()); settingTTL = Integer.parseInt(getByKey(SettingKey.TTL_SETTING).getValue());
} }
if (0 < settingTTL) { if (0 < settingTTL) {
redisSetting.set(key.toString(), gson.toJson(setting), Time.M * settingTTL); try {
redisSetting.set(key.toString(), jackson.writeValueAsString(setting), Time.M * settingTTL);
} catch (JsonProcessingException e) {
throw new TimiException(TimiCode.ERROR, "write setting cache error", e);
}
} }
return setting; return setting;
} }
@@ -111,28 +125,36 @@ public class SettingServiceImplement extends AbstractEntityService<Setting, Stri
} }
@Override @Override
public JsonElement getAsJsonElement(SettingKey key) { public JsonNode getAsJsonNode(SettingKey key) {
return JsonParser.parseString(getAsString(key)); try {
return jackson.readTree(getAsString(key));
} catch (JsonProcessingException e) {
throw new TimiException(TimiCode.ERROR, "read setting json error", e);
}
} }
@Override @Override
public JsonObject getAsJsonObject(SettingKey key) { public ObjectNode getAsJsonObject(SettingKey key) {
return getAsJsonElement(key).getAsJsonObject(); return (ObjectNode) getAsJsonNode(key);
} }
@Override @Override
public JsonArray getAsJsonArray(SettingKey key) { public ArrayNode getAsArrayNode(SettingKey key) {
return getAsJsonElement(key).getAsJsonArray(); return (ArrayNode) getAsJsonNode(key);
} }
@Override @Override
public <T> T fromJson(SettingKey key, Class<T> clazz) { public <T> T fromJson(SettingKey key, Class<T> clazz) {
return gson.fromJson(getAsJsonElement(key), clazz); try {
return jackson.treeToValue(getAsJsonNode(key), clazz);
} catch (JsonProcessingException e) {
throw new TimiException(TimiCode.ERROR, "read setting json error", e);
}
} }
@Override @Override
public <T> T fromJson(SettingKey key, TypeToken<T> typeToken) { public <T> T fromJson(SettingKey key, TypeReference<T> typeReference) {
return gson.fromJson(getAsJsonElement(key), typeToken); return jackson.convertValue(getAsJsonNode(key), typeReference);
} }
public <T> T fromYaml(SettingKey key, Class<T> clazz) { public <T> T fromYaml(SettingKey key, Class<T> clazz) {

View File

@@ -1,38 +1,24 @@
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.bean.SettingKey;
import com.imyeyu.api.modules.common.bean.TempFileMetaData; 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.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;
import com.imyeyu.api.modules.common.vo.TempFileResponse; import com.imyeyu.api.modules.common.vo.TempFileResponse;
import com.imyeyu.io.IO;
import com.imyeyu.io.IOSize; import com.imyeyu.io.IOSize;
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.network.Network;
import com.imyeyu.spring.TimiSpring; import com.imyeyu.spring.TimiSpring;
import com.imyeyu.spring.bean.Page;
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.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/** /**
* @author 夜雨 * @author 夜雨
@@ -43,56 +29,56 @@ import java.util.concurrent.ConcurrentHashMap;
@RequiredArgsConstructor @RequiredArgsConstructor
public class TempFileServiceImplement implements TempFileService { public class TempFileServiceImplement implements TempFileService {
private static final Long TTL = Time.H * 6;
private static final Long LIMIT = IOSize.GB * 10;
private final SettingService settingService; private final SettingService settingService;
private final AttachmentService attachmentService; private final AttachmentService attachmentService;
private Path storagePath; public List<TempFileResponse> store(List<MultipartFile> files, Long ttl) throws TimiException {
String ttlMinStr = settingService.getAsString(SettingKey.TEMP_FILE_TTL_MIN);
String ttlMaxStr = settingService.getAsString(SettingKey.TEMP_FILE_TTL_MAX);
String limitStr = settingService.getAsString(SettingKey.TEMP_FILE_LIMIT);
private final Map<String, TempFileMetaData> metadataMap = new ConcurrentHashMap<>(); long minTTL = Time.parseToMS(ttlMinStr);
private final Map<String, Long> usageMap = new ConcurrentHashMap<>(); long maxTTL = Time.parseToMS(ttlMaxStr);
long defaultTTL = Time.parseToMS(settingService.getAsString(SettingKey.TEMP_FILE_TTL_DEFAULT));
long limit = IOSize.parse(limitStr);
long residueTime = Time.parseToMS(settingService.getAsString(SettingKey.TEMP_FILE_RESIDUE_TIME));
@PostConstruct ttl = TimiJava.defaultIfNull(ttl, defaultTTL);
public void init() throws IOException { TimiException.requiredTrue(minTTL < ttl && ttl <= maxTTL, String.format("ttl must be between %s and %s", ttlMinStr, ttlMaxStr));
storagePath = Paths.get(settingService.getAsString(SettingKey.TEMP_FILE_PATH));
IO.destroy(storagePath.toFile());
Files.createDirectories(storagePath);
}
public List<TempFileResponse> store(List<MultipartFile> files) throws TimiException {
long newFileSize = files.stream().mapToLong(MultipartFile::getSize).sum(); long newFileSize = files.stream().mapToLong(MultipartFile::getSize).sum();
long currentUsage = usageMap.getOrDefault(TimiSpring.getRequestIP(), 0L); {
TimiException.requiredTrue(currentUsage + newFileSize < LIMIT, "out of storage limit(10 GB)"); // 缓存池大小限制
Page<Attachment> page = new Page<>();
page.setIndex(0);
page.setSize(Long.MAX_VALUE);
{
Attachment example = new Attachment();
example.setUploaderIp(TimiSpring.getRequestIP());
page.setEqualsExample(example);
}
long currentUsage = attachmentService.page(page).getList().stream().mapToLong(Attachment::getSize).sum();
TimiException.requiredTrue(currentUsage + newFileSize < limit, "out of storage limit(%s)".formatted(limitStr));
}
try { try {
List<TempFileResponse> result = new ArrayList<>(); List<TempFileResponse> result = new ArrayList<>();
long deletedAt = Time.now() + ttl;
long destroyAt = deletedAt + residueTime;
for (int i = 0; i < files.size(); i++) { for (int i = 0; i < files.size(); i++) {
MultipartFile file = files.get(i); MultipartFile file = files.get(i);
String fileId = UUID.randomUUID().toString(); Attachment attach = new Attachment();
String fileName = fileId; attach.setBizType(Attachment.BizType.TEMP_FILE);
if (TimiJava.isNotEmpty(file.getOriginalFilename())) { attach.setName(file.getOriginalFilename());
fileName = fileId + "." + Network.uriFileExtension(file.getOriginalFilename()); attach.setDeletedAt(deletedAt);
} attach.setDestroyAt(destroyAt);
Path filePath = storagePath.resolve(fileName); attach.setInputStream(file.getInputStream());
Files.copy(file.getInputStream(), filePath); attachmentService.create(attach);
// 创建元数据
TempFileMetaData metadata = new TempFileMetaData();
metadata.setId(fileId);
metadata.setPath(filePath);
metadata.setName(fileName);
metadata.setOriginalName(file.getOriginalFilename());
metadata.setLastAccessAt(Time.now());
metadataMap.put(metadata.getId(), metadata);
TempFileResponse resp = new TempFileResponse(); TempFileResponse resp = new TempFileResponse();
resp.setId(metadata.getId()); resp.setId(attach.getMongoId());
resp.setExpireAt(metadata.getLastAccessAt() + TTL); resp.setExpireAt(deletedAt);
usageMap.put(TimiSpring.getRequestIP(), currentUsage + newFileSize);
result.add(resp); result.add(resp);
} }
return result; return result;
@@ -101,43 +87,4 @@ public class TempFileServiceImplement implements TempFileService {
throw new TimiException(TimiCode.ERROR, "store temp file error", e); throw new TimiException(TimiCode.ERROR, "store temp file error", e);
} }
} }
@Override
public File get(String id) throws TimiException {
TempFileMetaData metaData = metadataMap.get(id);
TimiException.required(metaData, "not found temp file");
metaData.setLastAccessAt(Time.now());
return metaData.getPath().toFile();
}
@Override
public InputStream getInputStream(String id) throws TimiException {
try {
return IO.getInputStream(get(id));
} catch (FileNotFoundException e) {
throw new TimiException(TimiCode.RESULT_NULL, "not found temp file");
}
}
@Override
public TempFileMetaData metadata(String id) throws TimiException {
return metadataMap.get(id);
}
@Scheduled(fixedRate = 3600000)
public void cleanup() {
List<String> expiredIds = metadataMap.values()
.stream()
.filter(metadata -> metadata.getLastAccessAt() + TTL < Time.now())
.map(TempFileMetaData::getId)
.toList();
for (int i = 0; i < expiredIds.size(); i++) {
TempFileMetaData removed = metadataMap.remove(expiredIds.get(i));
if (TimiJava.isNotEmpty(removed)) {
File file = removed.getPath().toFile();
IO.destroy(file);
usageMap.computeIfPresent(TimiSpring.getRequestIP(), (ip, usage) -> usage - file.length());
}
}
}
} }

View File

@@ -1,9 +1,5 @@
package com.imyeyu.api.modules.common.service.implement; package com.imyeyu.api.modules.common.service.implement;
import com.imyeyu.io.IOSize;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig; import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.entity.User; import com.imyeyu.api.modules.common.entity.User;
@@ -12,8 +8,11 @@ import com.imyeyu.api.modules.common.mapper.UserProfileMapper;
import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.api.modules.common.service.UserProfileService; import com.imyeyu.api.modules.common.service.UserProfileService;
import com.imyeyu.api.modules.common.service.UserService; import com.imyeyu.api.modules.common.service.UserService;
import com.imyeyu.api.modules.common.vo.attachment.AttachmentRequest;
import com.imyeyu.api.modules.common.vo.user.UserRequest; import com.imyeyu.api.modules.common.vo.user.UserRequest;
import com.imyeyu.io.IOSize;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode;
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 lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -73,7 +72,7 @@ public class UserProfileServiceImplement extends AbstractEntityService<UserProfi
if (IOSize.MB < bytes.length) { if (IOSize.MB < bytes.length) {
throw new TimiException(TimiCode.ARG_BAD).msgKey("限制上传文件大小 1 MB"); throw new TimiException(TimiCode.ARG_BAD).msgKey("限制上传文件大小 1 MB");
} }
AttachmentRequest wrapperAttach = new AttachmentRequest(); Attachment wrapperAttach = new Attachment();
wrapperAttach.setName(request.getId() + ".png"); wrapperAttach.setName(request.getId() + ".png");
wrapperAttach.setBizType(Attachment.BizType.USER); wrapperAttach.setBizType(Attachment.BizType.USER);
wrapperAttach.setBizId(request.getId()); wrapperAttach.setBizId(request.getId());
@@ -88,7 +87,7 @@ public class UserProfileServiceImplement extends AbstractEntityService<UserProfi
attachmentService.delete(dbAvatar.getId()); attachmentService.delete(dbAvatar.getId());
} }
MultipartFile avatar = request.getProfile().getAvatar(); MultipartFile avatar = request.getProfile().getAvatar();
AttachmentRequest avatarAttach = new AttachmentRequest(); Attachment avatarAttach = new Attachment();
avatarAttach.setName(request.getId() + ".png"); avatarAttach.setName(request.getId() + ".png");
avatarAttach.setBizType(Attachment.BizType.USER); avatarAttach.setBizType(Attachment.BizType.USER);
avatarAttach.setBizId(request.getId()); avatarAttach.setBizId(request.getId());

View File

@@ -1,11 +1,9 @@
package com.imyeyu.api.modules.common.service.implement; package com.imyeyu.api.modules.common.service.implement;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig; import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
import com.imyeyu.api.modules.blog.util.UserToken; import com.imyeyu.api.modules.blog.util.UserToken;
import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.entity.CommentReply;
import com.imyeyu.api.modules.common.entity.EmailQueue; import com.imyeyu.api.modules.common.entity.EmailQueue;
import com.imyeyu.api.modules.common.entity.User; import com.imyeyu.api.modules.common.entity.User;
import com.imyeyu.api.modules.common.entity.UserConfig; import com.imyeyu.api.modules.common.entity.UserConfig;
@@ -29,7 +27,11 @@ import com.imyeyu.api.modules.common.vo.user.UserView;
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.api.modules.minecraft.service.PlayerService; import com.imyeyu.api.modules.minecraft.service.PlayerService;
import com.imyeyu.java.TimiJava;
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.bean.Logic;
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;
@@ -360,8 +362,13 @@ public class UserServiceImplement extends AbstractEntityService<User, Long> impl
} }
// 删除评论 // 删除评论
commentMapper.deleteByUserId(user.getId()); commentMapper.deleteByUserId(user.getId());
// 删除回复 {
commentReplyMapper.deleteByUserId(user.getId()); // 删除回复
CommentReply example = new CommentReply();
example.setSenderId(user.getId());
example.setReceiverId(user.getId());
commentReplyMapper.deleteAllByExample(example, Logic.OR);
}
// 删除账号 // 删除账号
delete(user.getId()); delete(user.getId());
// 清除登录会话 // 清除登录会话

View File

@@ -0,0 +1,49 @@
package com.imyeyu.api.modules.common.task;
import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.service.AttachmentService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import java.util.List;
/**
* 附件清除定时任务。每天凌晨 1 点执行,清除已过期但未销毁的附件
*
* @author 夜雨
* @since 2026-01-05 12:37
*/
@Slf4j
@Configuration
@EnableScheduling
@RequiredArgsConstructor
public class AttachmentClearTask {
private final AttachmentService attachmentService;
@Scheduled(cron = "0 0 1 * * ?")
public void run() {
log.info("start clear expired attachments");
try {
List<Attachment> needDestroyList = attachmentService.listNeedDestroy();
if (needDestroyList.isEmpty()) {
log.info("nothing attachment need clear");
return;
}
for (Attachment attach : needDestroyList) {
try {
attachmentService.destroy(attach.getId());
log.info("clear attachment success: id[{}]", attach.getId());
} catch (Exception e) {
log.error("clear attachment error: id[{}]", attach.getId(), e);
}
}
log.info("end clear expired attachments");
} catch (Exception e) {
log.error("attachment clear task error", e);
}
}
}

View File

@@ -17,7 +17,6 @@ import com.imyeyu.api.modules.common.entity.User;
import com.imyeyu.api.modules.common.service.CommentReplyService; import com.imyeyu.api.modules.common.service.CommentReplyService;
import com.imyeyu.api.modules.common.service.EmailQueueService; import com.imyeyu.api.modules.common.service.EmailQueueService;
import com.imyeyu.api.modules.common.service.UserService; import com.imyeyu.api.modules.common.service.UserService;
import com.imyeyu.api.modules.common.vo.comment.CommentReplyView;
import com.imyeyu.spring.util.Redis; import com.imyeyu.spring.util.Redis;
import com.imyeyu.utils.Text; import com.imyeyu.utils.Text;
import com.imyeyu.utils.Time; import com.imyeyu.utils.Time;
@@ -175,9 +174,7 @@ public class EmailTask implements TimiJava {
for (CommentRemindQueue remind : reminds) { for (CommentRemindQueue remind : reminds) {
// 回复 // 回复
CommentReply reply = commentReplyService.get(remind.getReplyId()); CommentReply reply = commentReplyService.get(remind.getReplyId());
CommentReplyView replyView = new CommentReplyView(); remind.setReply(reply);
BeanUtils.copyProperties(reply, replyView);
remind.setReply(replyView);
if (TimiJava.isNotEmpty(remind.getReply().getSenderId())) { if (TimiJava.isNotEmpty(remind.getReply().getSenderId())) {
// 发送者 // 发送者
remind.getReply().setSender(userService.view(remind.getReply().getSenderId())); remind.getReply().setSender(userService.view(remind.getReply().getSenderId()));

View File

@@ -1,8 +1,7 @@
package com.imyeyu.api.modules.common.task; package com.imyeyu.api.modules.common.task;
import com.google.gson.JsonArray; import com.fasterxml.jackson.databind.JsonNode;
import com.google.gson.JsonObject; import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.JsonParser;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig; import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
import com.imyeyu.api.modules.common.bean.SettingKey; import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.entity.Multilingual; import com.imyeyu.api.modules.common.entity.Multilingual;
@@ -81,6 +80,7 @@ public class MultilingualTranslateTask {
} }
} }
private final ObjectMapper jackson;
private final SettingService settingService; private final SettingService settingService;
private final MultilingualService service; private final MultilingualService service;
@@ -147,18 +147,17 @@ public class MultilingualTranslateTask {
.execute() .execute()
.returnContent() .returnContent()
.asString(); .asString();
JsonObject jo = JsonParser.parseString(response).getAsJsonObject(); JsonNode jo = jackson.readTree(response);
if (jo.has("error_code")) { if (jo.has("error_code")) {
System.err.println(jo); System.err.println(jo);
throw new TimiException(TimiCode.ERROR, jo.get("error_msg").getAsString()); throw new TimiException(TimiCode.ERROR, jo.get("error_msg").asText());
} }
JsonArray ja = jo.get("trans_result").getAsJsonArray(); JsonNode ja = jo.get("trans_result");
JsonObject resultJO;
Map<String, String> result = new HashMap<>(); Map<String, String> result = new HashMap<>();
for (int i = 0; i < ja.size(); i++) { for (int i = 0; i < ja.size(); i++) {
resultJO = ja.get(i).getAsJsonObject(); JsonNode resultJO = ja.get(i);
result.put(resultJO.get("src").getAsString(), resultJO.get("dst").getAsString()); result.put(resultJO.get("src").asText(), resultJO.get("dst").asText());
} }
wait(200); wait(200);
return result; return result;

View File

@@ -0,0 +1,18 @@
package com.imyeyu.api.modules.common.vo;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 共享剪切板请求
*
* @author 夜雨
* @since 2026-01-15 16:40
*/
@Data
public class ClipboardRequest {
/** 剪切板内容 */
@NotNull
private String content;
}

View File

@@ -1,18 +0,0 @@
package com.imyeyu.api.modules.common.vo.attachment;
import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.spring.annotation.table.Transient;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author 夜雨
* @since 2024-02-21 10:58
*/
@Data
@Transient
@Deprecated
@EqualsAndHashCode(callSuper = true)
public class AttachmentRequest extends Attachment {
}

View File

@@ -1,15 +0,0 @@
package com.imyeyu.api.modules.common.vo.attachment;
import lombok.Data;
import lombok.EqualsAndHashCode;
import com.imyeyu.api.bean.MultilingualHandler;
import com.imyeyu.api.modules.common.entity.Attachment;
/**
* @author 夜雨
* @since 2024-02-21 10:55
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class AttachmentView extends Attachment implements MultilingualHandler {
}

View File

@@ -1,41 +0,0 @@
package com.imyeyu.api.modules.common.vo.comment;
import com.imyeyu.spring.bean.Page;
import jakarta.validation.constraints.Min;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
/**
* @author 夜雨
* @since 2023-07-15 09:03
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class CommentReplyPage extends Page {
/**
*
*
* @author 夜雨
* @since 2025-04-17 23:29
*/
@Getter
@AllArgsConstructor
public enum BizType {
COMMENT("comment_id"),
SENDER("sender_id"),
RECEIVER("receiver_id");
final String column;
}
private BizType bizType;
@Min(1)
private Long bizId;
}

View File

@@ -1,28 +0,0 @@
package com.imyeyu.api.modules.common.vo.comment;
import com.imyeyu.api.modules.common.entity.CommentReply;
import com.imyeyu.api.modules.common.vo.user.UserView;
import com.imyeyu.spring.annotation.table.Transient;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author 夜雨
* @since 2024-03-05 17:48
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class CommentReplyView extends CommentReply {
/** 所属评论 */
@Transient
private CommentView comment;
/** 发送用户 */
@Transient
private UserView sender;
/** 回复用户 */
@Transient
private UserView receiver;
}

View File

@@ -1,34 +0,0 @@
package com.imyeyu.api.modules.common.vo.comment;
import com.imyeyu.api.modules.blog.entity.Article;
import com.imyeyu.api.modules.common.entity.Comment;
import com.imyeyu.api.modules.common.vo.user.UserView;
import com.imyeyu.api.modules.git.bean.gitea.Repository;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
/**
* @author 夜雨
* @since 2024-02-29 16:37
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class CommentView extends Comment {
/** 回复数量 */
private int repliesLength;
/** 发送用户 */
private UserView user;
/** 关联文章 */
private Article article;
/** 关联仓库 */
private Repository repository;
/** 回复列表 */
private List<CommentReplyView> replies;
}

View File

@@ -1,18 +0,0 @@
package com.imyeyu.api.modules.common.vo.comment;
import jakarta.validation.constraints.Min;
import lombok.Data;
import lombok.EqualsAndHashCode;
import com.imyeyu.spring.bean.Page;
/**
* @author 夜雨
* @since 2023-07-15 14:13
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class UserCommentPage extends Page {
@Min(1)
private Long userId;
}

View File

@@ -56,7 +56,7 @@ public class ServerServiceImplement implements ServerService {
public void report(ReportRequest request) { public void report(ReportRequest request) {
ServerStatus status = serverMap.get(request.getId()); ServerStatus status = serverMap.get(request.getId());
if (status != null) { if (status != null) {
request.setBaseInfo(TimiJava.firstNotNull(request.getBaseInfo(), status.getBaseInfo())); request.setBaseInfo(TimiJava.defaultIfNull(request.getBaseInfo(), status.getBaseInfo()));
if (request.getBaseInfo() == null || TimiJava.isEmpty(request.getBaseInfo().getCore())) { if (request.getBaseInfo() == null || TimiJava.isEmpty(request.getBaseInfo().getCore())) {
// 需要报告基本信息,与调用方约定返回代码为 IGNORE // 需要报告基本信息,与调用方约定返回代码为 IGNORE
throw new TimiException(TimiCode.IGNORE); throw new TimiException(TimiCode.IGNORE);

View File

@@ -1,6 +1,6 @@
package com.imyeyu.api.modules.git.bean.gitea; package com.imyeyu.api.modules.git.bean.gitea;
import com.google.gson.annotations.SerializedName; import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data; import lombok.Data;
/** /**
@@ -12,6 +12,6 @@ public class Branch {
private String name; private String name;
@SerializedName("protected") @JsonProperty("protected")
private boolean isProtected; private boolean isProtected;
} }

View File

@@ -1,6 +1,7 @@
package com.imyeyu.api.modules.git.bean.gitea; package com.imyeyu.api.modules.git.bean.gitea;
import com.google.gson.annotations.JsonAdapter; import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.imyeyu.api.modules.git.util.GiteaTimestampAdapter; import com.imyeyu.api.modules.git.util.GiteaTimestampAdapter;
import lombok.Data; import lombok.Data;
@@ -36,6 +37,7 @@ public class File {
private String lastCommitSha; private String lastCommitSha;
@JsonAdapter(GiteaTimestampAdapter.class) @JsonSerialize(using = GiteaTimestampAdapter.Serializer.class)
@JsonDeserialize(using = GiteaTimestampAdapter.Deserializer.class)
private Long lastCommitterDate; private Long lastCommitterDate;
} }

View File

@@ -1,6 +1,7 @@
package com.imyeyu.api.modules.git.bean.gitea; package com.imyeyu.api.modules.git.bean.gitea;
import com.google.gson.annotations.JsonAdapter; import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.imyeyu.api.modules.git.util.GiteaTimestampAdapter; import com.imyeyu.api.modules.git.util.GiteaTimestampAdapter;
import lombok.Data; import lombok.Data;
@@ -35,13 +36,16 @@ public class Repository {
private Boolean archived; private Boolean archived;
@JsonAdapter(GiteaTimestampAdapter.class) @JsonSerialize(using = GiteaTimestampAdapter.Serializer.class)
@JsonDeserialize(using = GiteaTimestampAdapter.Deserializer.class)
private Long createdAt; private Long createdAt;
@JsonAdapter(GiteaTimestampAdapter.class) @JsonSerialize(using = GiteaTimestampAdapter.Serializer.class)
@JsonDeserialize(using = GiteaTimestampAdapter.Deserializer.class)
private Long updatedAt; private Long updatedAt;
@JsonAdapter(GiteaTimestampAdapter.class) @JsonSerialize(using = GiteaTimestampAdapter.Serializer.class)
@JsonDeserialize(using = GiteaTimestampAdapter.Deserializer.class)
private Long archivedAt; private Long archivedAt;
private List<String> licenses; private List<String> licenses;

View File

@@ -1,9 +1,8 @@
package com.imyeyu.api.modules.git.service.implement; package com.imyeyu.api.modules.git.service.implement;
import com.google.gson.FieldNamingPolicy; import com.fasterxml.jackson.core.type.TypeReference;
import com.google.gson.Gson; import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.GsonBuilder; import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.google.gson.reflect.TypeToken;
import com.imyeyu.java.TimiJava; import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode; import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.java.bean.timi.TimiException;
@@ -56,17 +55,18 @@ import java.util.List;
@RequiredArgsConstructor @RequiredArgsConstructor
public class RepositoryServiceImplement implements RepositoryService { public class RepositoryServiceImplement implements RepositoryService {
private final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
private final UserService userService; private final UserService userService;
private final GiteaService giteaService; private final GiteaService giteaService;
private final SettingService settingService; private final SettingService settingService;
private final ObjectMapper jackson;
private User owner; private User owner;
private ObjectMapper giteaJackson;
@PostConstruct @PostConstruct
private void postConstruct() { private void postConstruct() {
owner = giteaService.getOwner(); owner = giteaService.getOwner();
giteaJackson = jackson.copy().setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
} }
@Override @Override
@@ -80,8 +80,7 @@ public class RepositoryServiceImplement implements RepositoryService {
put("limit", page.getLimit()); put("limit", page.getLimit());
}})).execute().returnResponse(); }})).execute().returnResponse();
String respText = EntityUtils.toString(resp.getEntity()); String respText = EntityUtils.toString(resp.getEntity());
GiteaResponse<List<Repository>> respObj = gson.fromJson(respText, new TypeToken<GiteaResponse<List<Repository>>>() { GiteaResponse<List<Repository>> respObj = giteaJackson.readValue(respText, new TypeReference<>() {});
}.getType());
PageResult<Repository> result = new PageResult<>(); PageResult<Repository> result = new PageResult<>();
result.setTotal(Long.parseLong(resp.getHeader("X-Total-Count").getValue())); result.setTotal(Long.parseLong(resp.getHeader("X-Total-Count").getValue()));
@@ -100,7 +99,7 @@ public class RepositoryServiceImplement implements RepositoryService {
put("owner", owner.getName()); put("owner", owner.getName());
put("repoName", repoName); put("repoName", repoName);
}})).execute().returnContent().asString(); }})).execute().returnContent().asString();
return gson.fromJson(respText, Repository.class); return giteaJackson.readValue(respText, Repository.class);
} catch (Exception e) { } catch (Exception e) {
log.error("get repository error", e); log.error("get repository error", e);
throw new TimiException(TimiCode.ERROR, "get repository error", e); throw new TimiException(TimiCode.ERROR, "get repository error", e);
@@ -110,11 +109,11 @@ public class RepositoryServiceImplement implements RepositoryService {
@Override @Override
public List<Branch> listBranches(String repoName) { public List<Branch> listBranches(String repoName) {
try { try {
String respText = Request.get(API.REPO_BRANCHES_LIST.buildURL(new HashMap<>() {{ String respText = Request.get(API.REPO_BRANCHES_LIST.buildURL(new HashMap<>() {{
put("owner", owner.getName()); put("owner", owner.getName());
put("repoName", repoName); put("repoName", repoName);
}})).execute().returnContent().asString(); }})).execute().returnContent().asString();
return gson.fromJson(respText, new TypeToken<List<Branch>>() {}.getType()); return giteaJackson.readValue(respText, new TypeReference<>() {});
} catch (Exception e) { } catch (Exception e) {
log.error("list repository branches error", e); log.error("list repository branches error", e);
throw new TimiException(TimiCode.ERROR, "list repository branches error", e); throw new TimiException(TimiCode.ERROR, "list repository branches error", e);
@@ -124,14 +123,14 @@ public class RepositoryServiceImplement implements RepositoryService {
@Override @Override
public List<File> listFile(String repoName, String branch, String path) { public List<File> listFile(String repoName, String branch, String path) {
try { try {
String respText = Request.get(API.REPO_FILE_LIST.buildURL(new HashMap<>() {{ String respText = Request.get(API.REPO_FILE_LIST.buildURL(new HashMap<>() {{
put("owner", owner.getName()); put("owner", owner.getName());
put("repoName", repoName); put("repoName", repoName);
put("path", path); put("path", path);
}}, new HashMap<>() {{ }}, new HashMap<>() {{
put("ref", branch); put("ref", branch);
}})).execute().returnContent().asString(); }})).execute().returnContent().asString();
List<File> list = gson.fromJson(respText, new TypeToken<List<File>>() {}.getType()); List<File> list = giteaJackson.readValue(respText, new TypeReference<>() {});
// 排序 // 排序
list.sort((f1, f2) -> { list.sort((f1, f2) -> {
if (f1.getType() == File.Type.dir && f2.getType() == File.Type.file) { if (f1.getType() == File.Type.dir && f2.getType() == File.Type.file) {

View File

@@ -1,28 +1,54 @@
package com.imyeyu.api.modules.git.util; package com.imyeyu.api.modules.git.util;
import com.google.gson.JsonDeserializationContext; import com.fasterxml.jackson.core.JacksonException;
import com.google.gson.JsonDeserializer; import com.fasterxml.jackson.core.JsonGenerator;
import com.google.gson.JsonElement; import com.fasterxml.jackson.core.JsonParser;
import com.google.gson.JsonPrimitive; import com.fasterxml.jackson.databind.DeserializationContext;
import com.google.gson.JsonSerializationContext; import com.fasterxml.jackson.databind.JsonDeserializer;
import com.google.gson.JsonSerializer; import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.lang.reflect.Type; import java.io.IOException;
import java.time.Instant; import java.time.Instant;
/** /**
* @author 夜雨 * @author 夜雨
* @since 2025-06-26 11:40 * @since 2025-06-26 11:40
*/ */
public class GiteaTimestampAdapter implements JsonSerializer<Long>, JsonDeserializer<Long> { @NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class GiteaTimestampAdapter {
@Override /**
public Long deserialize(JsonElement json, Type type, JsonDeserializationContext context) { *
return Instant.parse(json.getAsString()).toEpochMilli(); *
* @author 夜雨
* @since 2026-04-07 22:16
*/
public static class Serializer extends JsonSerializer<Long> {
@Override
public void serialize(Long value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (value == null) {
gen.writeNull();
} else {
gen.writeNumber(value);
}
}
} }
@Override /**
public JsonElement serialize(Long src, Type type, JsonSerializationContext context) { *
return new JsonPrimitive(src); *
* @author 夜雨
* @since 2026-04-07 22:19
*/
public static class Deserializer extends JsonDeserializer<Long> {
@Override
public Long deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
return Instant.parse(p.getText()).toEpochMilli();
}
} }
} }

View File

@@ -1,21 +0,0 @@
package com.imyeyu.api.modules.git.vo.issue;
import com.imyeyu.api.modules.common.entity.Comment;
import com.imyeyu.spring.bean.Page;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author 夜雨
* @since 2024-02-28 14:27
*/
@Data
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class CommentPage extends Page<Comment> {
private Comment.BizType bizType;
private long bizId;
}

View File

@@ -1,7 +1,8 @@
package com.imyeyu.api.modules.gitea.bean; package com.imyeyu.api.modules.gitea.bean;
import com.google.gson.annotations.JsonAdapter; import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.gson.annotations.SerializedName; import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.imyeyu.api.modules.gitea.util.GiteaUTCTimestampAdapter; import com.imyeyu.api.modules.gitea.util.GiteaUTCTimestampAdapter;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
@@ -50,14 +51,15 @@ public class ActionLogDTO {
@Data @Data
public static class Commit { public static class Commit {
@SerializedName("Sha1") @JsonProperty("Sha1")
private String sha; private String sha;
@SerializedName("Message") @JsonProperty("Message")
private String message; private String message;
@JsonAdapter(GiteaUTCTimestampAdapter.class) @JsonProperty("Timestamp")
@SerializedName("Timestamp") @JsonSerialize(using = GiteaUTCTimestampAdapter.Serializer.class)
@JsonDeserialize(using = GiteaUTCTimestampAdapter.Deserializer.class)
private Long timestamp; private Long timestamp;
} }
} }

View File

@@ -1,13 +1,14 @@
package com.imyeyu.api.modules.gitea.util; package com.imyeyu.api.modules.gitea.util;
import com.google.gson.JsonDeserializationContext; import com.fasterxml.jackson.core.JacksonException;
import com.google.gson.JsonDeserializer; import com.fasterxml.jackson.core.JsonGenerator;
import com.google.gson.JsonElement; import com.fasterxml.jackson.core.JsonParser;
import com.google.gson.JsonPrimitive; import com.fasterxml.jackson.databind.DeserializationContext;
import com.google.gson.JsonSerializationContext; import com.fasterxml.jackson.databind.JsonDeserializer;
import com.google.gson.JsonSerializer; import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.lang.reflect.Type; import java.io.IOException;
import java.time.Instant; import java.time.Instant;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.time.ZoneOffset; import java.time.ZoneOffset;
@@ -18,15 +19,21 @@ import java.time.ZoneOffset;
* @author 夜雨 * @author 夜雨
* @since 2025-06-27 16:18 * @since 2025-06-27 16:18
*/ */
public class GiteaUTCTimestampAdapter implements JsonDeserializer<Long>, JsonSerializer<Long> { public class GiteaUTCTimestampAdapter {
@Override public static class Deserializer extends JsonDeserializer<Long> {
public Long deserialize(JsonElement json, Type type, JsonDeserializationContext context) {
return OffsetDateTime.parse(json.getAsString()).toInstant().toEpochMilli(); @Override
public Long deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
return OffsetDateTime.parse(p.getText()).toInstant().toEpochMilli();
}
} }
@Override public static class Serializer extends JsonSerializer<Long> {
public JsonElement serialize(Long timestamp, Type type, JsonSerializationContext context) {
return new JsonPrimitive(OffsetDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC).toString()); @Override
public void serialize(Long timestamp, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(OffsetDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC).toString());
}
} }
} }

View File

@@ -1,9 +1,8 @@
package com.imyeyu.api.modules.gitea.vo; package com.imyeyu.api.modules.gitea.vo;
import com.google.gson.Gson; import com.fasterxml.jackson.core.type.TypeReference;
import com.google.gson.JsonObject; import com.fasterxml.jackson.databind.JsonNode;
import com.google.gson.JsonParser; import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.reflect.TypeToken;
import com.imyeyu.java.TimiJava; import com.imyeyu.java.TimiJava;
import com.imyeyu.api.TimiServerAPI; import com.imyeyu.api.TimiServerAPI;
import com.imyeyu.api.modules.common.service.UserService; import com.imyeyu.api.modules.common.service.UserService;
@@ -34,7 +33,7 @@ public class ActionLogView {
private UserView operator; private UserView operator;
public static ActionLogView fromDTO(ActionLogDTO dto) { public static ActionLogView fromDTO(ActionLogDTO dto) {
Gson gson = TimiServerAPI.applicationContext.getBean(Gson.class); ObjectMapper jackson = TimiServerAPI.applicationContext.getBean(ObjectMapper.class);
UserService userService = TimiServerAPI.applicationContext.getBean(UserService.class); UserService userService = TimiServerAPI.applicationContext.getBean(UserService.class);
ActionLogView view = new ActionLogView(); ActionLogView view = new ActionLogView();
@@ -45,15 +44,19 @@ public class ActionLogView {
view.setOperator(userService.view((long) dto.getOperatorId()).doFilter()); view.setOperator(userService.view((long) dto.getOperatorId()).doFilter());
{ {
if (TimiJava.isNotEmpty(dto.getContent())) { if (TimiJava.isNotEmpty(dto.getContent())) {
JsonObject content = JsonParser.parseString(dto.getContent()).getAsJsonObject(); try {
List<ActionLogDTO.Commit> commitList = gson.fromJson(content.get("Commits"), new TypeToken<List<ActionLogDTO.Commit>>() {}.getType()); JsonNode content = jackson.readTree(dto.getContent());
view.setCommitList(new ArrayList<>()); List<ActionLogDTO.Commit> commitList = jackson.convertValue(content.get("Commits"), new TypeReference<>() {});
for (ActionLogDTO.Commit dtoCommit : commitList) { view.setCommitList(new ArrayList<>());
Commit commit = new Commit(); for (ActionLogDTO.Commit dtoCommit : commitList) {
commit.setSha(dtoCommit.getSha()); Commit commit = new Commit();
commit.setMessage(dtoCommit.getMessage().trim()); commit.setSha(dtoCommit.getSha());
commit.setCommittedAt(dtoCommit.getTimestamp()); commit.setMessage(dtoCommit.getMessage().trim());
view.getCommitList().add(commit); commit.setCommittedAt(dtoCommit.getTimestamp());
view.getCommitList().add(commit);
}
} catch (Exception e) {
throw new IllegalStateException("read gitea action log content error", e);
} }
} }
} }
@@ -75,4 +78,4 @@ public class ActionLogView {
private Long committedAt; private Long committedAt;
} }
} }

View File

@@ -0,0 +1,8 @@
package com.imyeyu.api.modules.journal.bean;
/**
* @author 夜雨
* @since 2026-01-29 10:59
*/
public @interface RequiredUploadPermission {
}

View File

@@ -1,59 +0,0 @@
package com.imyeyu.api.modules.journal.bean;
import lombok.Data;
import java.util.List;
/**
* @author 夜雨
* @since 2025-09-30 11:51
*/
@Data
public class Travel {
private Luggage luggage;
private List<Guide> guides;
/**
*
*
* @author 夜雨
* @since 2025-09-30 11:52
*/
@Data
public static class Luggage {
private List<Item> gao;
private List<Item> yu;
/**
*
*
* @author 夜雨
* @since 2025-09-30 11:52
*/
@Data
public static class Item {
private String name;
private boolean isTaken;
}
}
/**
*
*
* @author 夜雨
* @since 2025-09-30 11:54
*/
@Data
public static class Guide {
private String title;
private List<String> images;
}
}

View File

@@ -1,5 +1,6 @@
package com.imyeyu.api.modules.journal.controller; package com.imyeyu.api.modules.journal.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.imyeyu.api.bean.PreviewPage; import com.imyeyu.api.bean.PreviewPage;
import com.imyeyu.api.bean.wechat.InitCodeResponse; import com.imyeyu.api.bean.wechat.InitCodeResponse;
import com.imyeyu.api.modules.common.bean.MediaAttach; import com.imyeyu.api.modules.common.bean.MediaAttach;
@@ -7,9 +8,10 @@ import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.api.modules.common.service.SettingService; import com.imyeyu.api.modules.common.service.SettingService;
import com.imyeyu.api.modules.journal.bean.Travel; import com.imyeyu.api.modules.journal.bean.RequiredUploadPermission;
import com.imyeyu.api.modules.journal.entity.Journal; import com.imyeyu.api.modules.journal.entity.Journal;
import com.imyeyu.api.modules.journal.service.JournalService; import com.imyeyu.api.modules.journal.service.JournalService;
import com.imyeyu.api.modules.journal.util.JournalAPIInterceptor;
import com.imyeyu.api.modules.journal.vo.journal.ArchiveRequest; import com.imyeyu.api.modules.journal.vo.journal.ArchiveRequest;
import com.imyeyu.api.modules.journal.vo.journal.JournalRequest; import com.imyeyu.api.modules.journal.vo.journal.JournalRequest;
import com.imyeyu.api.modules.journal.vo.journal.JournalResponse; import com.imyeyu.api.modules.journal.vo.journal.JournalResponse;
@@ -17,10 +19,8 @@ import com.imyeyu.api.modules.journal.vo.journal.UpdateRequest;
import com.imyeyu.java.bean.timi.TimiCode; import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.network.ArgMap; import com.imyeyu.network.ArgMap;
import com.imyeyu.network.GsonRequest;
import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.AOPLog;
import com.imyeyu.spring.annotation.RequestRateLimit; import com.imyeyu.spring.annotation.RequestRateLimit;
import com.imyeyu.spring.annotation.RequestSingleParam;
import com.imyeyu.spring.bean.Page; import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.bean.PageResult;
import jakarta.validation.Valid; import jakarta.validation.Valid;
@@ -52,6 +52,9 @@ public class JournalController {
private final JournalService service; private final JournalService service;
private final SettingService settingService; private final SettingService settingService;
private final AttachmentService attachmentService; private final AttachmentService attachmentService;
private final ObjectMapper jackson;
private final JournalAPIInterceptor apiInterceptor;
/** /**
* 初始化用户 OpenId * 初始化用户 OpenId
@@ -62,14 +65,18 @@ public class JournalController {
@AOPLog @AOPLog
@RequestRateLimit @RequestRateLimit
@PostMapping("/openid") @PostMapping("/openid")
public String initOpenId(@RequestSingleParam String code) { public String initOpenId(@RequestBody String code) {
try { try {
ArgMap<String, String> args = new ArgMap<>(); ArgMap<String, String> args = new ArgMap<>();
args.put("appid", settingService.getAsString(SettingKey.JOURNAL_APP_ID)); args.put("appid", settingService.getAsString(SettingKey.JOURNAL_APP_ID));
args.put("secret", settingService.getAsString(SettingKey.JOURNAL_APP_SECRET)); args.put("secret", settingService.getAsString(SettingKey.JOURNAL_APP_SECRET));
args.put("js_code", code); args.put("js_code", code);
args.put("grant_type", "authorization_code"); args.put("grant_type", "authorization_code");
InitCodeResponse resp = GsonRequest.get(args.toURL("https://api.weixin.qq.com/sns/jscode2session")).resultAs(InitCodeResponse.class); String response = org.apache.hc.client5.http.fluent.Request.get(args.toURL("https://api.weixin.qq.com/sns/jscode2session"))
.execute()
.returnContent()
.asString();
InitCodeResponse resp = jackson.readValue(response, InitCodeResponse.class);
return resp.getOpenid(); return resp.getOpenid();
} catch (Exception e) { } catch (Exception e) {
log.error("init WeChat openId error", e); log.error("init WeChat openId error", e);
@@ -77,6 +84,11 @@ public class JournalController {
} }
} }
@PostMapping("/can-upload")
public boolean canUpload() {
return apiInterceptor.canUploadKey();
}
@AOPLog @AOPLog
@RequestRateLimit @RequestRateLimit
@RequestMapping("/{id}") @RequestMapping("/{id}")
@@ -104,6 +116,7 @@ public class JournalController {
*/ */
@AOPLog @AOPLog
@RequestRateLimit @RequestRateLimit
@RequiredUploadPermission
@PostMapping("/create") @PostMapping("/create")
public void create(@RequestBody JournalRequest request) { public void create(@RequestBody JournalRequest request) {
service.create(request); service.create(request);
@@ -116,6 +129,7 @@ public class JournalController {
*/ */
@AOPLog @AOPLog
@RequestRateLimit @RequestRateLimit
@RequiredUploadPermission
@PostMapping("/update") @PostMapping("/update")
public void update(@RequestBody @Valid UpdateRequest request) { public void update(@RequestBody @Valid UpdateRequest request) {
service.update(request); service.update(request);
@@ -127,8 +141,9 @@ public class JournalController {
* @param id 记录 ID * @param id 记录 ID
*/ */
@AOPLog @AOPLog
@RequiredUploadPermission
@PostMapping("/delete") @PostMapping("/delete")
public void delete(@RequestSingleParam Long id) { public void delete(@RequestBody Long id) {
service.delete(id); service.delete(id);
} }
@@ -206,24 +221,6 @@ public class JournalController {
return journal + journalTravel; return journal + journalTravel;
} }
/**
*
*
* @return
*/
@RequestRateLimit
@GetMapping("/travel")
public Travel getTravel() {
return service.getTravel();
}
@AOPLog
@RequestRateLimit
@PostMapping("/travel/luggage/update")
public void updateTravel(@RequestBody Travel.Luggage luggage) {
service.updateTravelLuggage(luggage);
}
/** /**
* 创建瞬间(上传的临时文件持久化储存,微信限制单次上传数量) * 创建瞬间(上传的临时文件持久化储存,微信限制单次上传数量)
* *
@@ -232,6 +229,7 @@ public class JournalController {
*/ */
@AOPLog @AOPLog
@RequestRateLimit @RequestRateLimit
@RequiredUploadPermission
@PostMapping("/moment/create") @PostMapping("/moment/create")
public List<Attachment> createMoment(@RequestBody String[] tempFileIds) { public List<Attachment> createMoment(@RequestBody String[] tempFileIds) {
return service.createMoment(tempFileIds); return service.createMoment(tempFileIds);
@@ -279,6 +277,7 @@ public class JournalController {
*/ */
@AOPLog @AOPLog
@RequestRateLimit @RequestRateLimit
@RequiredUploadPermission
@PostMapping("/moment/delete") @PostMapping("/moment/delete")
public void deleteMoment(@RequestBody Long[] thumbIds) { public void deleteMoment(@RequestBody Long[] thumbIds) {
service.deleteMoment(thumbIds); service.deleteMoment(thumbIds);
@@ -291,6 +290,7 @@ public class JournalController {
*/ */
@AOPLog @AOPLog
@RequestRateLimit @RequestRateLimit
@RequiredUploadPermission
@PostMapping("/moment/archive") @PostMapping("/moment/archive")
public void archiveMoment(@RequestBody ArchiveRequest request) { public void archiveMoment(@RequestBody ArchiveRequest request) {
service.archiveMoment(request); service.archiveMoment(request);

View File

@@ -0,0 +1,39 @@
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.service.SettingService;
import com.imyeyu.api.modules.journal.bean.RequiredUploadPermission;
import com.imyeyu.spring.annotation.RequestBodyValue;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 夜雨
* @since 2026-01-28 11:36
*/
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/journal/tool")
public class ToolController {
private final SettingService settingService;
@GetMapping("/memo")
public String getMemo() {
return settingService.getAsString(SettingKey.JOURNAL_MEMO);
}
@RequiredUploadPermission
@PostMapping("/memo/update")
public void updateMemo(@RequestBodyValue String data) {
Setting setting = settingService.getByKey(SettingKey.JOURNAL_MEMO);
setting.setValue(data);
settingService.update(setting);
}
}

View File

@@ -1,11 +1,10 @@
package com.imyeyu.api.modules.journal.controller; package com.imyeyu.api.modules.journal.controller;
import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.api.modules.journal.bean.RequiredUploadPermission;
import com.imyeyu.api.modules.journal.entity.Travel; import com.imyeyu.api.modules.journal.entity.Travel;
import com.imyeyu.api.modules.journal.service.TravelService; import com.imyeyu.api.modules.journal.service.TravelService;
import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.AOPLog;
import com.imyeyu.spring.annotation.RequestRateLimit; import com.imyeyu.spring.annotation.RequestRateLimit;
import com.imyeyu.spring.annotation.RequestSingleParam;
import com.imyeyu.spring.bean.Page; import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.bean.PageResult;
import jakarta.validation.Valid; import jakarta.validation.Valid;
@@ -31,7 +30,6 @@ import org.springframework.web.bind.annotation.RestController;
public class TravelController { public class TravelController {
private final TravelService service; private final TravelService service;
private final AttachmentService attachmentService;
/** /**
* 创建旅行计划 * 创建旅行计划
@@ -40,6 +38,7 @@ public class TravelController {
*/ */
@AOPLog @AOPLog
@RequestRateLimit @RequestRateLimit
@RequiredUploadPermission
@PostMapping("/create") @PostMapping("/create")
public void create(@RequestBody @Valid Travel travel) { public void create(@RequestBody @Valid Travel travel) {
service.create(travel); service.create(travel);
@@ -52,6 +51,7 @@ public class TravelController {
*/ */
@AOPLog @AOPLog
@RequestRateLimit @RequestRateLimit
@RequiredUploadPermission
@PostMapping("/update") @PostMapping("/update")
public void update(@RequestBody @Valid Travel travel) { public void update(@RequestBody @Valid Travel travel) {
service.update(travel); service.update(travel);
@@ -64,8 +64,9 @@ public class TravelController {
*/ */
@AOPLog @AOPLog
@RequestRateLimit @RequestRateLimit
@RequiredUploadPermission
@PostMapping("/delete") @PostMapping("/delete")
public void delete(@RequestSingleParam Long id) { public void delete(@RequestBody Long id) {
service.delete(id); service.delete(id);
} }

View File

@@ -1,13 +1,14 @@
package com.imyeyu.api.modules.journal.controller; package com.imyeyu.api.modules.journal.controller;
import com.imyeyu.api.bean.PreviewPage; import com.imyeyu.api.bean.PreviewPage;
import com.imyeyu.api.modules.common.bean.MediaAttach;
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.journal.bean.RequiredUploadPermission;
import com.imyeyu.api.modules.journal.entity.TravelLocation; import com.imyeyu.api.modules.journal.entity.TravelLocation;
import com.imyeyu.api.modules.journal.service.TravelLocationService; import com.imyeyu.api.modules.journal.service.TravelLocationService;
import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.AOPLog;
import com.imyeyu.spring.annotation.RequestRateLimit; import com.imyeyu.spring.annotation.RequestRateLimit;
import com.imyeyu.spring.annotation.RequestSingleParam;
import com.imyeyu.spring.bean.Page; import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.bean.PageResult;
import jakarta.validation.Valid; import jakarta.validation.Valid;
@@ -44,6 +45,7 @@ public class TravelLocationController {
*/ */
@AOPLog @AOPLog
@RequestRateLimit @RequestRateLimit
@RequiredUploadPermission
@PostMapping("/create") @PostMapping("/create")
public void create(@RequestBody @Valid TravelLocation location) { public void create(@RequestBody @Valid TravelLocation location) {
service.create(location); service.create(location);
@@ -56,6 +58,7 @@ public class TravelLocationController {
*/ */
@AOPLog @AOPLog
@RequestRateLimit @RequestRateLimit
@RequiredUploadPermission
@PostMapping("/update") @PostMapping("/update")
public void update(@RequestBody @Valid TravelLocation location) { public void update(@RequestBody @Valid TravelLocation location) {
service.update(location); service.update(location);
@@ -68,8 +71,9 @@ public class TravelLocationController {
*/ */
@AOPLog @AOPLog
@RequestRateLimit @RequestRateLimit
@RequiredUploadPermission
@PostMapping("/delete") @PostMapping("/delete")
public void delete(@RequestSingleParam Long id) { public void delete(@RequestBody Long id) {
service.delete(id); service.delete(id);
} }
@@ -105,6 +109,7 @@ public class TravelLocationController {
Attachment example = new Attachment(); Attachment example = new Attachment();
example.setBizType(Attachment.BizType.JOURNAL_TRAVEL); example.setBizType(Attachment.BizType.JOURNAL_TRAVEL);
example.setBizId(location.getId()); example.setBizId(location.getId());
example.setAttachTypeValue(MediaAttach.Type.THUMB);
attachPage.setEqualsExample(example); attachPage.setEqualsExample(example);
} }
attachPage.setIndex(0); attachPage.setIndex(0);

View File

@@ -39,6 +39,9 @@ public class TravelLocation extends Entity {
/** 景点 */ /** 景点 */
ATTRACTION, ATTRACTION,
/** 商场 */
MALL,
/** 购物 */ /** 购物 */
SHOPPING, SHOPPING,

View File

@@ -1,7 +1,6 @@
package com.imyeyu.api.modules.journal.service; package com.imyeyu.api.modules.journal.service;
import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.journal.bean.Travel;
import com.imyeyu.api.modules.journal.entity.Journal; import com.imyeyu.api.modules.journal.entity.Journal;
import com.imyeyu.api.modules.journal.vo.journal.ArchiveRequest; import com.imyeyu.api.modules.journal.vo.journal.ArchiveRequest;
import com.imyeyu.api.modules.journal.vo.journal.JournalRequest; import com.imyeyu.api.modules.journal.vo.journal.JournalRequest;
@@ -39,8 +38,4 @@ public interface JournalService extends PageableService<Journal>, GettableServic
void deleteMoment(Long[] thumbIds) throws TimiException; void deleteMoment(Long[] thumbIds) throws TimiException;
void archiveMoment(ArchiveRequest request) throws TimiException; void archiveMoment(ArchiveRequest request) throws TimiException;
Travel getTravel() throws TimiException;
void updateTravelLuggage(Travel.Luggage luggage) throws TimiException;
} }

View File

@@ -1,23 +1,19 @@
package com.imyeyu.api.modules.journal.service.implement; package com.imyeyu.api.modules.journal.service.implement;
import com.google.gson.Gson; import com.fasterxml.jackson.databind.ObjectMapper;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig; import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
import com.imyeyu.api.modules.common.bean.MediaAttach; import com.imyeyu.api.modules.common.bean.MediaAttach;
import com.imyeyu.api.modules.common.bean.SettingKey; import com.imyeyu.api.modules.common.bean.Metadata;
import com.imyeyu.api.modules.common.bean.TempFileMetaData;
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;
import com.imyeyu.api.modules.journal.bean.Travel;
import com.imyeyu.api.modules.journal.entity.Journal; import com.imyeyu.api.modules.journal.entity.Journal;
import com.imyeyu.api.modules.journal.mapper.JournalMapper; import com.imyeyu.api.modules.journal.mapper.JournalMapper;
import com.imyeyu.api.modules.journal.service.JournalService; import com.imyeyu.api.modules.journal.service.JournalService;
import com.imyeyu.api.modules.journal.vo.journal.ArchiveRequest; import com.imyeyu.api.modules.journal.vo.journal.ArchiveRequest;
import com.imyeyu.api.modules.journal.vo.journal.JournalRequest; import com.imyeyu.api.modules.journal.vo.journal.JournalRequest;
import com.imyeyu.api.modules.journal.vo.journal.UpdateRequest; import com.imyeyu.api.modules.journal.vo.journal.UpdateRequest;
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;
@@ -29,7 +25,6 @@ import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@@ -45,7 +40,7 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor @RequiredArgsConstructor
public class JournalServiceImplement extends AbstractEntityService<Journal, Long> implements JournalService { public class JournalServiceImplement extends AbstractEntityService<Journal, Long> implements JournalService {
private final Gson gson; private final ObjectMapper jackson;
private final SettingService settingService; private final SettingService settingService;
private final TempFileService tempFileService; private final TempFileService tempFileService;
@@ -71,14 +66,13 @@ public class JournalServiceImplement extends AbstractEntityService<Journal, Long
return; return;
} }
for (int i = 0; i < tempFileIds.length; i++) { for (int i = 0; i < tempFileIds.length; i++) {
TempFileMetaData metadata = tempFileService.metadata(tempFileIds[i]); Attachment tempFileAttach = attachmentService.getByMongoId(tempFileIds[i]);
File file = tempFileService.get(tempFileIds[i]);
Attachment sourceAttach = new Attachment(); Attachment sourceAttach = new Attachment();
sourceAttach.setName(metadata.getOriginalName()); sourceAttach.setName(tempFileAttach.getName());
sourceAttach.setBizType(Attachment.BizType.JOURNAL); sourceAttach.setBizType(Attachment.BizType.JOURNAL);
sourceAttach.setBizId(journal.getId()); sourceAttach.setBizId(journal.getId());
sourceAttach.setInputStream(IO.getInputStream(file)); sourceAttach.setInputStream(tempFileAttach.openInputStream());
attachmentService.createMedia(sourceAttach); attachmentService.createMedia(sourceAttach);
} }
} catch (Exception e) { } catch (Exception e) {
@@ -106,14 +100,14 @@ public class JournalServiceImplement extends AbstractEntityService<Journal, Long
attachmentService.deleteMedia(removeId); attachmentService.deleteMedia(removeId);
} }
// 新增 // 新增
for (String tempFileId : TimiJava.firstNotNull(request.getTempFileIds(), new String[0])) { for (String tempFileId : TimiJava.defaultIfNull(request.getTempFileIds(), new String[0])) {
TempFileMetaData metadata = tempFileService.metadata(tempFileId); Attachment tempFileAttach = attachmentService.getByMongoId(tempFileId);
Attachment sourceAttach = new Attachment(); Attachment sourceAttach = new Attachment();
sourceAttach.setName(metadata.getOriginalName()); sourceAttach.setName(tempFileAttach.getName());
sourceAttach.setBizType(Attachment.BizType.JOURNAL); sourceAttach.setBizType(Attachment.BizType.JOURNAL);
sourceAttach.setBizId(journal.getId()); sourceAttach.setBizId(journal.getId());
sourceAttach.setInputStream(tempFileService.getInputStream(tempFileId)); sourceAttach.setInputStream(tempFileAttach.openInputStream());
attachmentService.createMedia(sourceAttach); attachmentService.createMedia(sourceAttach);
} }
} }
@@ -150,16 +144,15 @@ public class JournalServiceImplement extends AbstractEntityService<Journal, Long
try { try {
List<Attachment> thumbResult = new ArrayList<>(); List<Attachment> thumbResult = new ArrayList<>();
for (int i = 0; i < tempFileIds.length; i++) { for (int i = 0; i < tempFileIds.length; i++) {
TempFileMetaData metadata = tempFileService.metadata(tempFileIds[i]); Attachment tempFileAttach = attachmentService.getByMongoId(tempFileIds[i]);
File file = tempFileService.get(tempFileIds[i]);
Attachment sourceAttach = new Attachment(); Attachment sourceAttach = new Attachment();
sourceAttach.setName(metadata.getOriginalName()); sourceAttach.setName(tempFileAttach.getName());
sourceAttach.setBizType(Attachment.BizType.JOURNAL_MOMENT); sourceAttach.setBizType(Attachment.BizType.JOURNAL_MOMENT);
sourceAttach.setBizId(0L); sourceAttach.setBizId(0L);
sourceAttach.setAttachTypeValue(MediaAttach.Type.SOURCE); sourceAttach.setAttachTypeValue(MediaAttach.Type.SOURCE);
sourceAttach.setSize(file.length()); sourceAttach.setSize(tempFileAttach.getSize());
sourceAttach.setInputStream(IO.getInputStream(file)); sourceAttach.setInputStream(tempFileAttach.openInputStream());
thumbResult.add(attachmentService.createMedia(sourceAttach)); thumbResult.add(attachmentService.createMedia(sourceAttach));
} }
return thumbResult; return thumbResult;
@@ -199,8 +192,8 @@ public class JournalServiceImplement extends AbstractEntityService<Journal, Long
thumbAttach.setBizId(journal.getId()); thumbAttach.setBizId(journal.getId());
attachmentService.update(thumbAttach); attachmentService.update(thumbAttach);
MediaAttach.ExtData extData = gson.fromJson(thumbAttach.getExt(), MediaAttach.ExtData.class); Metadata.ThumbImage thumbMetadata = jackson.convertValue(thumbAttach.getMetadata(), Metadata.ThumbImage.class);
Attachment sourceAttach = attachmentService.get(extData.getSourceId()); Attachment sourceAttach = attachmentService.get(thumbMetadata.getSourceId());
sourceAttach.setBizType(Attachment.BizType.JOURNAL); sourceAttach.setBizType(Attachment.BizType.JOURNAL);
sourceAttach.setBizId(journal.getId()); sourceAttach.setBizId(journal.getId());
attachmentService.update(sourceAttach); attachmentService.update(sourceAttach);
@@ -210,19 +203,4 @@ public class JournalServiceImplement extends AbstractEntityService<Journal, Long
throw new TimiException(TimiCode.ERROR).msgKey("TODO archive journal error"); throw new TimiException(TimiCode.ERROR).msgKey("TODO archive journal error");
} }
} }
@Override
public Travel getTravel() throws TimiException {
return gson.fromJson(settingService.getAsJsonObject(SettingKey.JOURNAL_TRAVEL), Travel.class);
}
@Override
public void updateTravelLuggage(Travel.Luggage luggage) throws TimiException {
Travel dbTravel = getTravel();
dbTravel.setLuggage(luggage);
Setting setting = settingService.getByKey(SettingKey.JOURNAL_TRAVEL);
setting.setValue(gson.toJson(dbTravel));
settingService.update(setting);
settingService.clearCache(SettingKey.JOURNAL_TRAVEL);
}
} }

View File

@@ -2,19 +2,19 @@ package com.imyeyu.api.modules.journal.service.implement;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig; import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
import com.imyeyu.api.modules.common.bean.MediaAttach; import com.imyeyu.api.modules.common.bean.MediaAttach;
import com.imyeyu.api.modules.common.bean.TempFileMetaData;
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.common.service.TempFileService;
import com.imyeyu.api.modules.journal.entity.TravelLocation; import com.imyeyu.api.modules.journal.entity.TravelLocation;
import com.imyeyu.api.modules.journal.mapper.TravelLocationMapper; import com.imyeyu.api.modules.journal.mapper.TravelLocationMapper;
import com.imyeyu.api.modules.journal.service.TravelLocationService; import com.imyeyu.api.modules.journal.service.TravelLocationService;
import com.imyeyu.api.modules.journal.service.TravelService;
import com.imyeyu.java.TimiJava; import com.imyeyu.java.TimiJava;
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 lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -30,10 +30,10 @@ import java.util.stream.Collectors;
*/ */
@Slf4j @Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor(onConstructor_ = {@Lazy})
public class TravelLocationServiceImplement extends AbstractEntityService<TravelLocation, Long> implements TravelLocationService { public class TravelLocationServiceImplement extends AbstractEntityService<TravelLocation, Long> implements TravelLocationService {
private final TempFileService tempFileService; private final TravelService travelService;
private final AttachmentService attachmentService; private final AttachmentService attachmentService;
private final TravelLocationMapper mapper; private final TravelLocationMapper mapper;
@@ -48,15 +48,17 @@ public class TravelLocationServiceImplement extends AbstractEntityService<Travel
public void create(TravelLocation travelLocation) { public void create(TravelLocation travelLocation) {
super.create(travelLocation); super.create(travelLocation);
for (String tempFileId : travelLocation.getTempFileIds()) { for (String tempFileId : travelLocation.getTempFileIds()) {
TempFileMetaData metadata = tempFileService.metadata(tempFileId); Attachment tempFileAttach = attachmentService.getByMongoId(tempFileId);
Attachment attach = new Attachment(); Attachment attach = new Attachment();
attach.setName(metadata.getOriginalName()); attach.setName(tempFileAttach.getName());
attach.setBizType(Attachment.BizType.JOURNAL_TRAVEL); attach.setBizType(Attachment.BizType.JOURNAL_TRAVEL);
attach.setBizId(travelLocation.getId()); attach.setBizId(travelLocation.getId());
attach.setInputStream(tempFileService.getInputStream(tempFileId)); attach.setInputStream(tempFileAttach.openInputStream());
attachmentService.createMedia(attach); attachmentService.createMedia(attach);
} }
// 更新操作时间以保证排序
travelService.update(travelService.get(travelLocation.getTravelId()));
} }
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@@ -77,16 +79,18 @@ public class TravelLocationServiceImplement extends AbstractEntityService<Travel
} }
} }
// 新增 // 新增
for (String tempFileId : TimiJava.firstNotNull(travelLocation.getTempFileIds(), new String[0])) { for (String tempFileId : TimiJava.defaultIfNull(travelLocation.getTempFileIds(), new String[0])) {
TempFileMetaData metadata = tempFileService.metadata(tempFileId); Attachment tempFileAttach = attachmentService.getByMongoId(tempFileId);
Attachment attach = new Attachment(); Attachment attach = new Attachment();
attach.setName(metadata.getOriginalName()); attach.setName(tempFileAttach.getName());
attach.setBizType(Attachment.BizType.JOURNAL_TRAVEL); attach.setBizType(Attachment.BizType.JOURNAL_TRAVEL);
attach.setBizId(travelLocation.getId()); attach.setBizId(travelLocation.getId());
attach.setInputStream(tempFileService.getInputStream(tempFileId)); attach.setInputStream(tempFileAttach.openInputStream());
attachmentService.createMedia(attach); attachmentService.createMedia(attach);
} }
// 更新操作时间以保证排序
travelService.update(travelService.get(travelLocation.getTravelId()));
} }
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)

View File

@@ -9,6 +9,7 @@ import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.spring.service.AbstractEntityService; import com.imyeyu.spring.service.AbstractEntityService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -20,7 +21,7 @@ import org.springframework.transaction.annotation.Transactional;
*/ */
@Slf4j @Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor(onConstructor_ = {@Lazy})
public class TravelServiceImplement extends AbstractEntityService<Travel, Long> implements TravelService { public class TravelServiceImplement extends AbstractEntityService<Travel, Long> implements TravelService {
private final TravelMapper mapper; private final TravelMapper mapper;

View File

@@ -2,6 +2,7 @@ package com.imyeyu.api.modules.journal.util;
import com.imyeyu.api.modules.common.bean.SettingKey; import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.service.SettingService; import com.imyeyu.api.modules.common.service.SettingService;
import com.imyeyu.api.modules.journal.bean.RequiredUploadPermission;
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;
@@ -13,8 +14,11 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
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.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor;
import java.util.Set;
/** /**
* @author 夜雨 * @author 夜雨
* @version 2023-11-23 17:09 * @version 2023-11-23 17:09
@@ -28,20 +32,34 @@ public class JournalAPIInterceptor implements HandlerInterceptor {
private final SettingService settingService; private final SettingService settingService;
private String[] keys; private Set<String> keys, openIds;
@PostConstruct @PostConstruct
private void postConstruct() { private void postConstruct() {
keys = settingService.getAsString(SettingKey.JOURNAL_KEY).split(","); keys = Set.of(settingService.getAsString(SettingKey.JOURNAL_KEY).split(","));
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) {
String key = TimiJava.firstNotEmpty(TimiSpring.getHeader("Key"), TimiSpring.getRequestArg("key")); boolean requiredUploadPermission = false;
for (int i = 0; i < keys.length; i++) { if (handler instanceof HandlerMethod handlerMethod) {
if (keys[i].equals(key)) { requiredUploadPermission = handlerMethod.getMethodAnnotation(RequiredUploadPermission.class) != null;
return true;
}
} }
throw new TimiException(TimiCode.PERMISSION_MISS).msgKey("invalid.key"); if (!canAccess()) {
throw new TimiException(TimiCode.PERMISSION_MISS).msgKey("invalid.key");
}
return !requiredUploadPermission || canUploadKey();
}
public boolean canAccess() {
String reqKey = TimiJava.defaultIfEmpty(TimiSpring.getHeader("Key"), TimiSpring.getRequestArg("key"));
return TimiJava.isNotEmpty(reqKey) && keys.contains(reqKey);
}
public boolean canUploadKey() {
String reqKey = TimiJava.defaultIfEmpty(TimiSpring.getHeader("Key"), TimiSpring.getRequestArg("key"));
// String reqOpenId = TimiJava.defaultIfEmpty(TimiSpring.getHeader("OpenId"), TimiSpring.getRequestArg("openid"));
// return canAccess() && reqKey.startsWith("i") && openIds.contains(reqOpenId);
return canAccess() && reqKey.startsWith("i");
} }
} }

View File

@@ -1,18 +1,17 @@
package com.imyeyu.api.modules.lyric.service.implement; package com.imyeyu.api.modules.lyric.service.implement;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig; import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.entity.User; import com.imyeyu.api.modules.common.entity.User;
import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.api.modules.common.service.UserService; import com.imyeyu.api.modules.common.service.UserService;
import com.imyeyu.api.modules.common.vo.attachment.AttachmentRequest;
import com.imyeyu.api.modules.lyric.entity.LyricCorrect; import com.imyeyu.api.modules.lyric.entity.LyricCorrect;
import com.imyeyu.api.modules.lyric.mapper.LyricCorrectMapper; import com.imyeyu.api.modules.lyric.mapper.LyricCorrectMapper;
import com.imyeyu.api.modules.lyric.service.LyricCorrectService; import com.imyeyu.api.modules.lyric.service.LyricCorrectService;
import com.imyeyu.api.modules.lyric.vo.LyricCorrectRequest; import com.imyeyu.api.modules.lyric.vo.LyricCorrectRequest;
import com.imyeyu.java.TimiJava;
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.bean.Page; import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.bean.PageResult;
@@ -76,7 +75,7 @@ public class LyricCorrectServiceImplement extends AbstractEntityService<LyricCor
create(correct); create(correct);
try { try {
MultipartFile file = request.getFile(); MultipartFile file = request.getFile();
AttachmentRequest attachment = new AttachmentRequest(); Attachment attachment = new Attachment();
attachment.setBizType(Attachment.BizType.LYRIC); attachment.setBizType(Attachment.BizType.LYRIC);
attachment.setBizId(correct.getId()); attachment.setBizId(correct.getId());
attachment.setName(correct.getId() + ".lrc"); attachment.setName(correct.getId() + ".lrc");

View File

@@ -1,7 +1,7 @@
package com.imyeyu.api.modules.lyric.service.implement; package com.imyeyu.api.modules.lyric.service.implement;
import com.google.gson.JsonObject; import com.fasterxml.jackson.databind.JsonNode;
import com.google.gson.JsonParser; import com.fasterxml.jackson.databind.ObjectMapper;
import com.imyeyu.java.bean.timi.TimiCode; import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.api.modules.lyric.entity.Lyric; import com.imyeyu.api.modules.lyric.entity.Lyric;
@@ -50,6 +50,7 @@ public class LyricServiceImplement implements LyricService {
private final LyricMapper mapper; private final LyricMapper mapper;
private final Map<Long, String> allLyric; private final Map<Long, String> allLyric;
private final ObjectMapper jackson;
@Override @Override
public Lyric get(Long id) { public Lyric get(Long id) {
@@ -113,12 +114,12 @@ public class LyricServiceImplement implements LyricService {
Request request = Request.get(API_GET_SONG + Encoder.urlArgs(args)); Request request = Request.get(API_GET_SONG + Encoder.urlArgs(args));
HEADER.forEach(request::addHeader); HEADER.forEach(request::addHeader);
response = request.execute().returnContent().asString(); response = request.execute().returnContent().asString();
JsonObject data = JsonParser.parseString(response).getAsJsonObject().get("data").getAsJsonObject().get("info").getAsJsonArray().get(0).getAsJsonObject(); JsonNode data = jackson.readTree(response).path("data").path("info").get(0);
args.clear(); args.clear();
args.put("cmd", "100"); args.put("cmd", "100");
args.put("keyword", data.get("songname").getAsString()); args.put("keyword", data.path("songname").asText());
args.put("hash", data.get("hash").getAsString()); args.put("hash", data.path("hash").asText());
args.put("timelength", tl); args.put("timelength", tl);
args.put("d", String.valueOf(Math.random())); args.put("d", String.valueOf(Math.random()));
request = Request.get(API_GET_LRC + Encoder.urlArgs(args)); request = Request.get(API_GET_LRC + Encoder.urlArgs(args));
@@ -128,8 +129,8 @@ public class LyricServiceImplement implements LyricService {
} }
Lyric lyric = new Lyric(); Lyric lyric = new Lyric();
lyric.setSong(data.get("songname").getAsString()); lyric.setSong(data.path("songname").asText());
lyric.setSinger(data.get("singername").getAsString()); lyric.setSinger(data.path("singername").asText());
lyric.setData(response); lyric.setData(response);
return lyric; return lyric;
} catch (Exception e) { } catch (Exception e) {

View File

@@ -17,7 +17,6 @@ import com.imyeyu.api.modules.minecraft.vo.TokenRequest;
import com.imyeyu.api.modules.minecraft.vo.TokenResponse; import com.imyeyu.api.modules.minecraft.vo.TokenResponse;
import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.AOPLog;
import com.imyeyu.spring.annotation.RequestRateLimit; import com.imyeyu.spring.annotation.RequestRateLimit;
import com.imyeyu.spring.annotation.RequestSingleParam;
import com.imyeyu.spring.annotation.RequiredToken; import com.imyeyu.spring.annotation.RequiredToken;
import com.imyeyu.spring.util.Redis; import com.imyeyu.spring.util.Redis;
import com.imyeyu.spring.util.RedisSerializers; import com.imyeyu.spring.util.RedisSerializers;
@@ -73,7 +72,7 @@ public class PlayerController {
@RequiredToken @RequiredToken
@RequestRateLimit @RequestRateLimit
@PostMapping("/bind") @PostMapping("/bind")
public void bind(@RequestSingleParam String name) { public void bind(@RequestBody String name) {
MinecraftPlayer player = new MinecraftPlayer(); MinecraftPlayer player = new MinecraftPlayer();
player.setName(name); player.setName(name);
player.setUserId(userService.getLoginUser().getId()); player.setUserId(userService.getLoginUser().getId());
@@ -84,7 +83,7 @@ public class PlayerController {
@RequiredToken @RequiredToken
@RequestRateLimit @RequestRateLimit
@PostMapping("/unbind") @PostMapping("/unbind")
public void unbind(@RequestSingleParam Long id) { public void unbind(@RequestBody Long id) {
service.listByUserId(userService.getLoginUser().getId()) service.listByUserId(userService.getLoginUser().getId())
.stream() .stream()
.filter(player -> player.getId().equals(id)) .filter(player -> player.getId().equals(id))
@@ -95,7 +94,7 @@ public class PlayerController {
@RequestRateLimit @RequestRateLimit
@PostMapping("/list") @PostMapping("/list")
public List<MinecraftPlayer> listPlayer(@RequestHeader("Token") String token) { public List<MinecraftPlayer> listPlayer(@RequestHeader("Token") String token) {
Long userId = TimiJava.firstNotNull(redis.get(token), userToken.getUserId(token)); Long userId = TimiJava.defaultIfNull(redis.get(token), userToken.getUserId(token));
if (userId == null) { if (userId == null) {
throw new TimiException(TimiCode.RESULT_BAD).msgKey("token.illegal"); throw new TimiException(TimiCode.RESULT_BAD).msgKey("token.illegal");
} }
@@ -113,7 +112,7 @@ public class PlayerController {
@RequestRateLimit @RequestRateLimit
@EnableSetting(value = SettingKey.FMC_PLAYER_LOGIN_ENABLE, message = "登录服务未启用") @EnableSetting(value = SettingKey.FMC_PLAYER_LOGIN_ENABLE, message = "登录服务未启用")
@PostMapping("/login") @PostMapping("/login")
public TokenResponse login(@RequestSingleParam Long playerId, @RequestHeader("Token") String token) { public TokenResponse login(@RequestBody Long playerId, @RequestHeader("Token") String token) {
return service.login(playerId, token); return service.login(playerId, token);
} }

View File

@@ -1,12 +1,11 @@
package com.imyeyu.api.modules.mirror; package com.imyeyu.api.modules.mirror;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import com.imyeyu.api.TimiServerAPI; import com.imyeyu.api.TimiServerAPI;
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.common.vo.attachment.AttachmentRequest;
import com.imyeyu.api.modules.mirror.entity.Mirror; import com.imyeyu.api.modules.mirror.entity.Mirror;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.util.List; import java.util.List;
@@ -42,7 +41,7 @@ public abstract class AttachmentMirror extends AbstractMirror {
* @return 新增附件 * @return 新增附件
* @throws Exception 处理异常 * @throws Exception 处理异常
*/ */
protected abstract List<AttachmentRequest> diffAdd(List<Attachment> dbFiles) throws Exception; protected abstract List<Attachment> diffAdd(List<Attachment> dbFiles) throws Exception;
/** /**
* 差分移除附件 * 差分移除附件
@@ -70,10 +69,10 @@ public abstract class AttachmentMirror extends AbstractMirror {
attachmentService.destroy(diffRemoveList.get(i).getId()); attachmentService.destroy(diffRemoveList.get(i).getId());
} }
List<AttachmentRequest> diffAddList = diffAdd(dbFiles); List<Attachment> diffAddList = diffAdd(dbFiles);
syncAdded = diffAddList.size(); syncAdded = diffAddList.size();
for (int i = 0; i < diffAddList.size(); i++) { for (int i = 0; i < diffAddList.size(); i++) {
AttachmentRequest request = diffAddList.get(i); Attachment request = diffAddList.get(i);
request.setBizType(Attachment.BizType.MIRROR); request.setBizType(Attachment.BizType.MIRROR);
request.setBizId(mirror.getId()); request.setBizId(mirror.getId());
attachmentService.create(request); attachmentService.create(request);

View File

@@ -1,9 +1,8 @@
package com.imyeyu.api.modules.mirror; package com.imyeyu.api.modules.mirror;
import com.google.gson.Gson; import com.fasterxml.jackson.databind.ObjectMapper;
import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.api.modules.common.vo.attachment.AttachmentRequest;
import com.imyeyu.api.modules.mirror.bean.AttachType; import com.imyeyu.api.modules.mirror.bean.AttachType;
import com.imyeyu.api.modules.mirror.data.FabricAPI; import com.imyeyu.api.modules.mirror.data.FabricAPI;
import com.imyeyu.api.modules.mirror.entity.Mirror; import com.imyeyu.api.modules.mirror.entity.Mirror;
@@ -48,7 +47,7 @@ public class FabricAPIMirror extends AttachmentMirror {
/** 版本匹配正则 */ /** 版本匹配正则 */
private static final Pattern versionRegex = Pattern.compile("^(\\d+\\.){1,2}(\\*|\\d+)$"); private static final Pattern versionRegex = Pattern.compile("^(\\d+\\.){1,2}(\\*|\\d+)$");
private final Gson gson; private final ObjectMapper jackson;
private final MirrorService service; private final MirrorService service;
private final AttachmentService attachmentService; private final AttachmentService attachmentService;
@@ -101,15 +100,15 @@ public class FabricAPIMirror extends AttachmentMirror {
result.add(item); result.add(item);
} }
} }
mirror.setData(gson.toJsonTree(result)); mirror.setData(jackson.valueToTree(result));
service.update(mirror); service.update(mirror);
} }
@Override @Override
protected List<AttachmentRequest> diffAdd(List<Attachment> dbFiles) throws Exception { protected List<Attachment> diffAdd(List<Attachment> dbFiles) throws Exception {
Set<String> dbNameSet = dbFiles.stream().map(Attachment::getName).collect(Collectors.toSet()); Set<String> dbNameSet = dbFiles.stream().map(Attachment::getName).collect(Collectors.toSet());
List<AttachmentRequest> result = new ArrayList<>(); List<Attachment> result = new ArrayList<>();
for (Map.Entry<String, String> item : versionMap.entrySet()) { for (Map.Entry<String, String> item : versionMap.entrySet()) {
String version = "%s+%s".formatted(item.getValue(), item.getKey()); String version = "%s+%s".formatted(item.getValue(), item.getKey());
String name = "fabric-api-%s.jar".formatted(version); String name = "fabric-api-%s.jar".formatted(version);
@@ -119,7 +118,7 @@ public class FabricAPIMirror extends AttachmentMirror {
byte[] bytes = Request.get(url).viaProxy(proxy).execute().returnContent().asBytes(); byte[] bytes = Request.get(url).viaProxy(proxy).execute().returnContent().asBytes();
AttachmentRequest attachment = new AttachmentRequest(); Attachment attachment = new Attachment();
attachment.setAttachTypeValue(AttachType.FABRIC_API); attachment.setAttachTypeValue(AttachType.FABRIC_API);
attachment.setName(name); attachment.setName(name);
attachment.setSize((long) bytes.length); attachment.setSize((long) bytes.length);

View File

@@ -1,16 +1,13 @@
package com.imyeyu.api.modules.mirror; package com.imyeyu.api.modules.mirror;
import com.google.gson.Gson; import com.fasterxml.jackson.databind.JsonNode;
import com.google.gson.JsonArray; import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.java.ref.Ref;
import com.imyeyu.api.modules.mirror.data.OpenJDK; import com.imyeyu.api.modules.mirror.data.OpenJDK;
import com.imyeyu.api.modules.mirror.entity.Mirror; import com.imyeyu.api.modules.mirror.entity.Mirror;
import com.imyeyu.api.modules.mirror.service.MirrorService; import com.imyeyu.api.modules.mirror.service.MirrorService;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.java.ref.Ref;
import com.imyeyu.utils.OS; import com.imyeyu.utils.OS;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -27,9 +24,9 @@ import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
* Github JDK 镜像,仅同步下载链接等信息,不储存文件 * Github JDK 闀滃儚锛屼粎鍚屾涓嬭浇閾炬帴绛変俊鎭紝涓嶅偍瀛樻枃浠?
* *
* @author 夜雨 * @author 澶滈洦
* @version 2024-06-10 10:51 * @version 2024-06-10 10:51
*/ */
@Slf4j @Slf4j
@@ -37,10 +34,10 @@ import java.util.Map;
@RequiredArgsConstructor @RequiredArgsConstructor
public class OpenJDKGithubMirror extends AbstractMirror { public class OpenJDKGithubMirror extends AbstractMirror {
/** 版本发布列表接口,插值 {@link #REPOS_MAP} 的值 */ /** 鐗堟湰鍙戝竷鍒楄〃鎺ュ彛锛屾彃鍊?{@link #REPOS_MAP} 鐨勫€?*/
private static final String API_RELEASE = "https://api.github.com/repos/adoptium/%s/releases?page=1"; private static final String API_RELEASE = "https://api.github.com/repos/adoptium/%s/releases?page=1";
/** 版本仓库映射key 为版本value 为对应仓库,只读 */ /** 鐗堟湰浠撳簱鏄犲皠锛宬ey 涓虹増鏈紝value 涓哄搴斾粨搴擄紝鍙 */
private static final Map<String, String> REPOS_MAP = Collections.unmodifiableMap(new HashMap<>() {{ private static final Map<String, String> REPOS_MAP = Collections.unmodifiableMap(new HashMap<>() {{
put("8", "temurin8-binaries"); put("8", "temurin8-binaries");
put("11", "temurin11-binaries"); put("11", "temurin11-binaries");
@@ -48,31 +45,30 @@ public class OpenJDKGithubMirror extends AbstractMirror {
put("21", "temurin21-binaries"); put("21", "temurin21-binaries");
}}); }});
private final Gson gson; private final ObjectMapper jackson;
private final MirrorService service; private final MirrorService service;
@Override @Override
protected void sync(Mirror mirror) throws Exception { protected void sync(Mirror mirror) throws Exception {
mirror.setData(gson.toJsonTree(fetch())); mirror.setData(jackson.valueToTree(fetch()));
service.update(mirror); service.update(mirror);
} }
/** /**
* 获取 {@link OpenJDK} 列表,{@link OpenJDKMirror} 也会使用此接口 * 鑾峰彇 {@link OpenJDK} 鍒楄〃锛寋@link OpenJDKMirror} 涔熶細浣跨敤姝ゆ帴鍙?
* *
* @return jdk 列表 * @return jdk 鍒楄〃
* @throws Exception 获取异常 * @throws Exception 鑾峰彇寮傚父
*/ */
final List<OpenJDK> fetch() throws Exception { final List<OpenJDK> fetch() throws Exception {
List<OpenJDK> result = new ArrayList<>(); List<OpenJDK> result = new ArrayList<>();
for (Map.Entry<String, String> repo : REPOS_MAP.entrySet()) { for (Map.Entry<String, String> repo : REPOS_MAP.entrySet()) {
String respText = Request.get(API_RELEASE.formatted(repo.getValue())).connectTimeout(Timeout.ofSeconds(60)).execute().returnContent().asString(); String respText = Request.get(API_RELEASE.formatted(repo.getValue())).connectTimeout(Timeout.ofSeconds(60)).execute().returnContent().asString();
JsonArray root = JsonParser.parseString(respText).getAsJsonArray(); JsonNode root = jackson.readTree(respText);
JsonObject itemRel = null; JsonNode itemRel = null;
for (JsonElement el : root) { for (JsonNode el : root) {
itemRel = el.getAsJsonObject(); itemRel = el;
if (itemRel.get("prerelease").getAsBoolean() || itemRel.get("draft").getAsBoolean()) { if (itemRel.path("prerelease").asBoolean() || itemRel.path("draft").asBoolean()) {
// 忽略草稿或预发布版本
itemRel = null; itemRel = null;
} else { } else {
break; break;
@@ -81,12 +77,8 @@ public class OpenJDKGithubMirror extends AbstractMirror {
if (itemRel == null) { if (itemRel == null) {
throw new TimiException(TimiCode.ERROR, "not found release item for " + repo.getValue()); throw new TimiException(TimiCode.ERROR, "not found release item for " + repo.getValue());
} }
JsonArray assets = itemRel.get("assets").getAsJsonArray(); for (JsonNode itemAsset : itemRel.path("assets")) {
for (JsonElement asset : assets) { String name = itemAsset.path("name").asText();
JsonObject itemAsset = asset.getAsJsonObject();
// OpenJDK21U-jdk_x64_windows_hotspot_21.0.3_9.zip
String name = itemAsset.get("name").getAsString();
if (!name.contains("-") || !name.contains("_")) { if (!name.contains("-") || !name.contains("_")) {
continue; continue;
} }
@@ -102,14 +94,13 @@ public class OpenJDKGithubMirror extends AbstractMirror {
} }
OS.Platform platform = Ref.toType(OS.Platform.class, split[2].toUpperCase()); OS.Platform platform = Ref.toType(OS.Platform.class, split[2].toUpperCase());
OpenJDK.Type type = Ref.toType(OpenJDK.Type.class, split[0].toUpperCase()); OpenJDK.Type type = Ref.toType(OpenJDK.Type.class, split[0].toUpperCase());
if (platform != null && type != null) { if (platform != null && type != null) {
OpenJDK jdk = new OpenJDK(); OpenJDK jdk = new OpenJDK();
jdk.setPlatform(platform); jdk.setPlatform(platform);
jdk.setType(type); jdk.setType(type);
jdk.setName(name); jdk.setName(name);
jdk.setVersion(repo.getKey()); jdk.setVersion(repo.getKey());
jdk.setData(URLDecoder.decode(itemAsset.get("browser_download_url").getAsString(), StandardCharsets.UTF_8)); jdk.setData(URLDecoder.decode(itemAsset.path("browser_download_url").asText(), StandardCharsets.UTF_8));
result.add(jdk); result.add(jdk);
} }
} }

View File

@@ -1,9 +1,8 @@
package com.imyeyu.api.modules.mirror; package com.imyeyu.api.modules.mirror;
import com.google.gson.Gson; import com.fasterxml.jackson.databind.ObjectMapper;
import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.api.modules.common.vo.attachment.AttachmentRequest;
import com.imyeyu.api.modules.mirror.bean.AttachType; import com.imyeyu.api.modules.mirror.bean.AttachType;
import com.imyeyu.api.modules.mirror.data.OpenJDK; import com.imyeyu.api.modules.mirror.data.OpenJDK;
import com.imyeyu.api.modules.mirror.entity.Mirror; import com.imyeyu.api.modules.mirror.entity.Mirror;
@@ -31,7 +30,7 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor @RequiredArgsConstructor
public class OpenJDKMirror extends AttachmentMirror { public class OpenJDKMirror extends AttachmentMirror {
private final Gson gson; private final ObjectMapper jackson;
private final MirrorService service; private final MirrorService service;
private final AttachmentService attachmentService; private final AttachmentService attachmentService;
private final OpenJDKGithubMirror githubMirror; private final OpenJDKGithubMirror githubMirror;
@@ -65,16 +64,16 @@ public class OpenJDKMirror extends AttachmentMirror {
result.add(jdk); result.add(jdk);
} }
} }
mirror.setData(gson.toJsonTree(result)); mirror.setData(jackson.valueToTree(result));
service.update(mirror); service.update(mirror);
} }
@Override @Override
protected List<AttachmentRequest> diffAdd(List<Attachment> dbFiles) throws Exception { protected List<Attachment> diffAdd(List<Attachment> dbFiles) throws Exception {
Map<String, OpenJDK> githubNameMap = githubMirrorResult.stream().collect(Collectors.toMap(OpenJDK::getName, item -> item)); Map<String, OpenJDK> githubNameMap = githubMirrorResult.stream().collect(Collectors.toMap(OpenJDK::getName, item -> item));
Set<String> dbNameSet = dbFiles.stream().map(Attachment::getName).collect(Collectors.toSet()); Set<String> dbNameSet = dbFiles.stream().map(Attachment::getName).collect(Collectors.toSet());
List<AttachmentRequest> result = new ArrayList<>(); List<Attachment> result = new ArrayList<>();
for (Map.Entry<String, OpenJDK> item : githubNameMap.entrySet()) { for (Map.Entry<String, OpenJDK> item : githubNameMap.entrySet()) {
if (!dbNameSet.contains(item.getKey())) { if (!dbNameSet.contains(item.getKey())) {
String url = item.getValue().getData(); String url = item.getValue().getData();
@@ -82,7 +81,7 @@ public class OpenJDKMirror extends AttachmentMirror {
byte[] bytes = Request.get(url).viaProxy(proxy).execute().returnContent().asBytes(); byte[] bytes = Request.get(url).viaProxy(proxy).execute().returnContent().asBytes();
AttachmentRequest attachment = new AttachmentRequest(); Attachment attachment = new Attachment();
attachment.setAttachTypeValue(AttachType.OPEN_JDK); attachment.setAttachTypeValue(AttachType.OPEN_JDK);
attachment.setName(item.getKey()); attachment.setName(item.getKey());
attachment.setSize((long) bytes.length); attachment.setSize((long) bytes.length);

View File

@@ -1,12 +1,12 @@
package com.imyeyu.api.modules.mirror; package com.imyeyu.api.modules.mirror;
import com.google.gson.Gson; import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import com.imyeyu.api.modules.mirror.data.OpenJDK; import com.imyeyu.api.modules.mirror.data.OpenJDK;
import com.imyeyu.api.modules.mirror.entity.Mirror; import com.imyeyu.api.modules.mirror.entity.Mirror;
import com.imyeyu.api.modules.mirror.service.MirrorService; import com.imyeyu.api.modules.mirror.service.MirrorService;
import com.imyeyu.utils.OS; import com.imyeyu.utils.OS;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element; import org.jsoup.nodes.Element;
@@ -41,18 +41,18 @@ public class OpenJDKTunaMirror extends AbstractMirror {
/** 版本列表 */ /** 版本列表 */
private static final String[] VERSIONS = {"8", "11", "17", "21"}; private static final String[] VERSIONS = {"8", "11", "17", "21"};
private final Gson gson; private final ObjectMapper jackson;
private final MirrorService service; private final MirrorService service;
@Override @Override
protected void sync(Mirror mirror) throws Exception { protected void sync(Mirror mirror) throws Exception {
List<OpenJDK> result = new ArrayList<>(); List<OpenJDK> result = new ArrayList<>();
for (int i = 0; i < VERSIONS.length; i++) { for (String version : VERSIONS) {
OpenJDK.Type[] types = OpenJDK.Type.values(); OpenJDK.Type[] types = OpenJDK.Type.values();
for (int j = 0; j < types.length; j++) { for (OpenJDK.Type type : types) {
OS.Platform[] platforms = OS.Platform.values(); OS.Platform[] platforms = OS.Platform.values();
for (int k = 0; k < platforms.length; k++) { for (OS.Platform platform : platforms) {
String url = PAGE_URL_TEMPLATE.formatted(VERSIONS[i], types[j].toString().toLowerCase(), platforms[k].toString().toLowerCase()); String url = PAGE_URL_TEMPLATE.formatted(version, type.toString().toLowerCase(), platform.toString().toLowerCase());
Document document = Jsoup.connect(url).get(); Document document = Jsoup.connect(url).get();
Element fileList = document.getElementById("list"); Element fileList = document.getElementById("list");
Elements linkTDList = fileList.getElementsByClass("link"); Elements linkTDList = fileList.getElementsByClass("link");
@@ -64,10 +64,10 @@ public class OpenJDKTunaMirror extends AbstractMirror {
continue; continue;
} }
OpenJDK jdk = new OpenJDK(); OpenJDK jdk = new OpenJDK();
jdk.setPlatform(platforms[k]); jdk.setPlatform(platform);
jdk.setType(types[j]); jdk.setType(type);
jdk.setName(href); jdk.setName(href);
jdk.setVersion(VERSIONS[i]); jdk.setVersion(version);
jdk.setData(url + href); jdk.setData(url + href);
result.add(jdk); result.add(jdk);
@@ -77,7 +77,7 @@ public class OpenJDKTunaMirror extends AbstractMirror {
} }
} }
} }
mirror.setData(gson.toJsonTree(result)); mirror.setData(jackson.valueToTree(result));
service.update(mirror); service.update(mirror);
} }
} }

View File

@@ -1,6 +1,6 @@
package com.imyeyu.api.modules.mirror.controller; package com.imyeyu.api.modules.mirror.controller;
import com.google.gson.JsonElement; import com.fasterxml.jackson.databind.JsonNode;
import com.imyeyu.api.modules.mirror.entity.Mirror; import com.imyeyu.api.modules.mirror.entity.Mirror;
import com.imyeyu.api.modules.mirror.service.MirrorService; import com.imyeyu.api.modules.mirror.service.MirrorService;
import com.imyeyu.api.modules.mirror.vo.MirrorView; import com.imyeyu.api.modules.mirror.vo.MirrorView;
@@ -38,7 +38,7 @@ public class MirrorController {
*/ */
@RequestRateLimit @RequestRateLimit
@GetMapping("/{mirrorName}") @GetMapping("/{mirrorName}")
public JsonElement get(@PathVariable String mirrorName) { public JsonNode get(@PathVariable String mirrorName) {
return service.getByName(mirrorName).getData(); return service.getByName(mirrorName).getData();
} }

View File

@@ -1,9 +1,9 @@
package com.imyeyu.api.modules.mirror.entity; package com.imyeyu.api.modules.mirror.entity;
import com.google.gson.JsonElement; import com.fasterxml.jackson.databind.JsonNode;
import com.imyeyu.spring.entity.Entity;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import com.imyeyu.spring.entity.Entity;
/** /**
* 镜像 * 镜像
@@ -22,7 +22,7 @@ public class Mirror extends Entity {
protected String name; protected String name;
/** 同步数据 */ /** 同步数据 */
protected JsonElement data; protected JsonNode data;
/** 周期(分钟) */ /** 周期(分钟) */
protected int period; protected int period;

View File

@@ -1,6 +1,7 @@
package com.imyeyu.api.modules.music.core; package com.imyeyu.api.modules.music.core;
import com.google.gson.Gson; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
@@ -36,7 +37,7 @@ public class Middleware implements SchedulingConfigurer {
private static final String CACHE_CLEAR_CORN = "0 0/20 * * * ?"; private static final String CACHE_CLEAR_CORN = "0 0/20 * * * ?";
@Autowired @Autowired
private Gson gson; private ObjectMapper jackson;
/** 缓存频道Map&lt;频道 ID, 关联频道&gt; */ /** 缓存频道Map&lt;频道 ID, 关联频道&gt; */
private final Map<String, ChannelBinding> channels = new HashMap<>(); private final Map<String, ChannelBinding> channels = new HashMap<>();
@@ -67,7 +68,7 @@ public class Middleware implements SchedulingConfigurer {
channelBinding.setPlayerChannel(ctx); channelBinding.setPlayerChannel(ctx);
channelBinding.setLastActiviedAt(Time.now()); channelBinding.setLastActiviedAt(Time.now());
if (channelBinding.getControllerChannel() != null) { if (channelBinding.getControllerChannel() != null) {
channelBinding.getControllerChannel().writeAndFlush(new TextWebSocketFrame(gson.toJson(pkg))); channelBinding.getControllerChannel().writeAndFlush(new TextWebSocketFrame(writeValueAsString(pkg)));
} }
} }
@@ -90,11 +91,19 @@ public class Middleware implements SchedulingConfigurer {
channelBinding.setControllerChannel(ctx); channelBinding.setControllerChannel(ctx);
channelBinding.setLastActiviedAt(Time.now()); channelBinding.setLastActiviedAt(Time.now());
if (pkg.getAction() != null && channelBinding.getPlayerChannel() != null) { if (pkg.getAction() != null && channelBinding.getPlayerChannel() != null) {
byte[] bytes = gson.toJson(pkg).getBytes(StandardCharsets.UTF_8); byte[] bytes = writeValueAsString(pkg).getBytes(StandardCharsets.UTF_8);
ByteBuf buffer = Unpooled.buffer(); ByteBuf buffer = Unpooled.buffer();
buffer.writeInt(bytes.length); buffer.writeInt(bytes.length);
buffer.writeBytes(bytes); buffer.writeBytes(bytes);
channelBinding.getPlayerChannel().writeAndFlush(buffer); channelBinding.getPlayerChannel().writeAndFlush(buffer);
} }
} }
private String writeValueAsString(Object value) {
try {
return jackson.writeValueAsString(value);
} catch (JsonProcessingException e) {
throw new IllegalStateException("write music websocket payload error", e);
}
}
} }

View File

@@ -1,6 +1,6 @@
package com.imyeyu.api.modules.music.handler; package com.imyeyu.api.modules.music.handler;
import com.google.gson.Gson; import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
@@ -26,7 +26,7 @@ import org.springframework.stereotype.Component;
public class ControllerMessageHandler extends SimpleChannelInboundHandler<WebSocketFrame> { public class ControllerMessageHandler extends SimpleChannelInboundHandler<WebSocketFrame> {
@Autowired @Autowired
private Gson gson; private ObjectMapper jackson;
@Autowired @Autowired
private Middleware middleware; private Middleware middleware;
@@ -34,7 +34,11 @@ public class ControllerMessageHandler extends SimpleChannelInboundHandler<WebSoc
@Override @Override
protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame msg) { protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame msg) {
if (msg instanceof TextWebSocketFrame textWebSocketFrame) { if (msg instanceof TextWebSocketFrame textWebSocketFrame) {
middleware.pushControllerData(ctx, gson.fromJson(textWebSocketFrame.text(), ControllerPackage.class)); try {
middleware.pushControllerData(ctx, jackson.readValue(textWebSocketFrame.text(), ControllerPackage.class));
} catch (Exception e) {
throw new IllegalStateException("read controller websocket payload error", e);
}
} else { } else {
ctx.channel().writeAndFlush(WebSocketCloseStatus.INVALID_MESSAGE_TYPE).addListener(ChannelFutureListener.CLOSE); ctx.channel().writeAndFlush(WebSocketCloseStatus.INVALID_MESSAGE_TYPE).addListener(ChannelFutureListener.CLOSE);
} }

View File

@@ -1,6 +1,6 @@
package com.imyeyu.api.modules.music.handler; package com.imyeyu.api.modules.music.handler;
import com.google.gson.Gson; import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.SimpleChannelInboundHandler;
@@ -22,14 +22,18 @@ import org.springframework.stereotype.Component;
public class PlayerMessageHandler extends SimpleChannelInboundHandler<String> { public class PlayerMessageHandler extends SimpleChannelInboundHandler<String> {
@Autowired @Autowired
private Gson gson; private ObjectMapper jackson;
@Autowired @Autowired
private Middleware middleware; private Middleware middleware;
@Override @Override
protected synchronized void channelRead0(ChannelHandlerContext ctx, String result) { protected synchronized void channelRead0(ChannelHandlerContext ctx, String result) {
middleware.pushPlayerData(ctx, gson.fromJson(result, PlayerPackage.class)); try {
middleware.pushPlayerData(ctx, jackson.readValue(result, PlayerPackage.class));
} catch (Exception e) {
throw new IllegalStateException("read player websocket payload error", e);
}
} }
@Override @Override

View File

@@ -0,0 +1,142 @@
package com.imyeyu.api.modules.system.bean;
import lombok.Data;
import org.springframework.stereotype.Component;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Docker 容器状态缓存
*
* @author Codex
* @since 2026-04-06
*/
@Data
@Component
public class DockerStatusStore {
/** 容器状态映射 */
private final Map<String, Container> containers = new LinkedHashMap<>();
/**
* 容器缓存
*
* @author Codex
* @since 2026-04-06
*/
@Data
public static class Container {
/** 容器 ID */
private String id;
/** 容器名称 */
private String name;
/** 镜像 */
private String image;
/** 镜像 ID */
private String imageId;
/** 创建时间 */
private long createdAt;
/** 运行状态 */
private String state;
/** 状态描述 */
private String status;
/** 健康状态 */
private String healthStatus;
/** 启动时间 */
private String startedAt;
/** 结束时间 */
private String finishedAt;
/** 退出码 */
private Integer exitCode;
/** 重启次数 */
private int restartCount;
/** OOM 标记 */
private boolean oomKilled;
/** CPU 百分比 */
private Double cpuPercent;
/** 内存使用量 */
private Long memoryUsageBytes;
/** 内存限制 */
private Long memoryLimitBytes;
/** 内存百分比 */
private Double memoryPercent;
/** 网络接收字节 */
private Long networkRxBytes;
/** 网络发送字节 */
private Long networkTxBytes;
/** 块设备读取字节 */
private Long blockReadBytes;
/** 块设备写入字节 */
private Long blockWriteBytes;
/** 进程数 */
private Integer pids;
/** 采样时间 */
private long updatedAt;
/** 历史点 */
private final Deque<Point> history = new ArrayDeque<>();
}
/**
* 容器指标历史点
*
* @author Codex
* @since 2026-04-06
*/
@Data
public static class Point {
/** 时间 */
private long at;
/** CPU 百分比 */
private Double cpuPercent;
/** 内存使用量 */
private Long memoryUsageBytes;
/** 内存百分比 */
private Double memoryPercent;
/** 网络接收字节 */
private Long networkRxBytes;
/** 网络发送字节 */
private Long networkTxBytes;
/** 块设备读取字节 */
private Long blockReadBytes;
/** 块设备写入字节 */
private Long blockWriteBytes;
/** 进程数 */
private Integer pids;
}
}

View File

@@ -1,7 +1,7 @@
package com.imyeyu.api.modules.system.bean; package com.imyeyu.api.modules.system.bean;
import lombok.Data;
import com.imyeyu.java.TimiJava; import com.imyeyu.java.TimiJava;
import lombok.Data;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.ArrayDeque; import java.util.ArrayDeque;
@@ -11,22 +11,24 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
/** /**
* 服务状态数据,所有动态数据左出右进,此对象由 IOC 托管 * 服务状态缓存
*
* <p>该对象只用于采集任务内部缓存,不直接作为接口协议返回。</p>
* *
* @author 夜雨 * @author 夜雨
* @version 2022-01-31 15:35 * @since 2022-01-31 15:35
*/ */
@Data @Data
@Component @Component
public class ServerStatus implements TimiJava { public class ServerStatus implements TimiJava {
/** 动态数据更新时轴 */ /** 采样时间轴 */
private LinkedList<Number> updateAxis = new LinkedList<>(); private LinkedList<Number> updateAxis = new LinkedList<>();
/** 系统 */ /** 操作系统 */
private OS os = new OS(); private OS os = new OS();
/** CPU 使用率 */ /** CPU */
private CPU cpu = new CPU(); private CPU cpu = new CPU();
/** 系统内存 */ /** 系统内存 */
@@ -35,17 +37,20 @@ public class ServerStatus implements TimiJava {
/** 网络 */ /** 网络 */
private Network network = new Network(); private Network network = new Network();
/** 本程序状态 */ /** 硬件 */
private Hardware hardware = new Hardware();
/** JVM */
private JVM jvm = new JVM(); private JVM jvm = new JVM();
/** 磁盘 */ /** 存储分区 */
private List<Partition> partitions = new ArrayList<>(); private List<StoragePartition> storagePartitions = new ArrayList<>();
/** /**
* 系统 * 操作系统
* *
* @author 夜雨 * @author 夜雨
* @version 2022-08-12 20:55 * @since 2022-08-12 20:55
*/ */
@Data @Data
public static class OS { public static class OS {
@@ -58,10 +63,10 @@ public class ServerStatus implements TimiJava {
} }
/** /**
* 虚拟机状态 * JVM
* *
* @author 夜雨 * @author 夜雨
* @version 2022-01-31 21:10 * @since 2022-01-31 21:10
*/ */
@Data @Data
public static class JVM { public static class JVM {
@@ -69,80 +74,83 @@ public class ServerStatus implements TimiJava {
/** 启动时间 */ /** 启动时间 */
private long bootAt; private long bootAt;
/** JVM 名称 */ /** 名称 */
private String name; private String name;
/** JVM 版本 */ /** 版本 */
private String version; private String version;
/** 内存 */ /** GC 名称 */
private String gcName;
/** 堆内存 */
private Memory memory = new Memory(); private Memory memory = new Memory();
/** 内存回收 */ /** GC 状态 */
private ZGC zgc = new ZGC(); private GC gc = new GC();
/** /**
* 内存 * JVM 内存
* *
* @author 夜雨 * @author 夜雨
* @version 2022-08-12 20:32 * @since 2022-08-12 20:32
*/ */
@Data @Data
public static class Memory { public static class Memory {
/** 初始 */ /** 初始大小 */
private long init; private long init;
/** 最大 */ /** 最大大小 */
private long max; private long max;
/** 已使用 */ /** 已用大小 */
private final Deque<Number> used = new ArrayDeque<>(); private final Deque<Number> used = new ArrayDeque<>();
/** 已提交 */ /** 已提交大小 */
private final Deque<Number> committed = new ArrayDeque<>(); private final Deque<Number> committed = new ArrayDeque<>();
} }
/** /**
* 内存回收 * GC 状态
* *
* @author 夜雨 * @author 夜雨
* @version 2022-08-12 20:32 * @since 2022-08-12 20:32
*/ */
@Data @Data
public static class ZGC { public static class GC {
/** 并发周期 */ /** 周期次数 */
private long syncCycles = 0; private long syncCycles;
/** 累计并发周期耗时(毫秒) */ /** 周期累计耗时 */
private long syncCyclesTimeTotal = 0; private long syncCyclesTimeTotal;
/** 累计次数 */ /** 暂停次数 */
private long pauses = 0; private long pauses;
/** 累计回收暂停时长(毫秒) */ /** 暂停累计耗时 */
private long pausesTimeTotal = 0; private long pausesTimeTotal;
/** 上一次回收时间 */ /** 上次暂停时间 */
private long lastPauseAt = 0; private long lastPauseAt;
/** 上次回收大小 */ /** 上次回收大小 */
private long lastRecoverySize = 0; private long lastRecoverySize;
/** 并发周期耗时 */ /** 周期耗时序列 */
private final Deque<Number> syncCyclesTime = new ArrayDeque<>(); private final Deque<Number> syncCyclesTime = new ArrayDeque<>();
/** 回收暂停时长 */ /** 暂停耗时序列 */
private final Deque<Number> pausesTime = new ArrayDeque<>(); private final Deque<Number> pausesTime = new ArrayDeque<>();
} }
} }
/** /**
* 中央处理器 * CPU
* *
* @author 夜雨 * @author 夜雨
* @version 2022-01-31 15:40 * @since 2022-01-31 15:40
*/ */
@Data @Data
public static class CPU { public static class CPU {
@@ -150,19 +158,19 @@ public class ServerStatus implements TimiJava {
/** 名称 */ /** 名称 */
private String name; private String name;
/** 物理核心数 */ /** 物理核心数 */
private int coreCount; private int coreCount;
/** 线程数量 */ /** 逻辑核心数 */
private int logicalCount; private int logicalCount;
/** 温度 */ /** 温度 */
private double temperature; private double temperature;
/** 系统使用 */ /** 系统用 */
private final Deque<Number> system = new ArrayDeque<>(); private final Deque<Number> system = new ArrayDeque<>();
/** 总共已使用 */ /** 总用 */
private final Deque<Number> used = new ArrayDeque<>(); private final Deque<Number> used = new ArrayDeque<>();
} }
@@ -170,7 +178,7 @@ public class ServerStatus implements TimiJava {
* 系统内存 * 系统内存
* *
* @author 夜雨 * @author 夜雨
* @version 2022-01-31 15:50 * @since 2022-01-31 15:50
*/ */
@Data @Data
public static class Memory { public static class Memory {
@@ -178,69 +186,174 @@ public class ServerStatus implements TimiJava {
/** 物理内存大小 */ /** 物理内存大小 */
private long size; private long size;
/** 交换区大小 */ /** 交换区大小 */
private long swapSize; private long swapSize;
/** 已使用 */ /** 已用内存 */
private final Deque<Number> used = new ArrayDeque<>(); private final Deque<Number> used = new ArrayDeque<>();
/** 交换区已使用 */ /** 已用交换分区 */
private final Deque<Number> swapUsed = new ArrayDeque<>(); private final Deque<Number> swapUsed = new ArrayDeque<>();
} }
/** /**
* 网卡网速 * 网
* *
* @author 夜雨 * @author 夜雨
* @version 2022-08-10 21:41 * @since 2022-08-10 21:41
*/ */
@Data @Data
public static class Network { public static class Network {
/** 网卡名称 */
private String name;
/** 累计接收 */ /** 累计接收 */
private long recvTotal; private long recvTotal;
/** 累计发送 */ /** 累计发送 */
private long sentTotal; private long sentTotal;
/** 实时接收速 */ /** 实时接收速 */
private long recvNow; private long recvNow;
/** 实时发送速 */ /** 实时发送速 */
private long sentNow; private long sentNow;
/** MAC 地址 */ /** MAC 地址 */
private String mac; private String mac;
/** 发送 */ /** 接收包总数 */
private long recvPacketsTotal;
/** 发送包总数 */
private long sentPacketsTotal;
/** 输入错误包总数 */
private long inErrors;
/** 输出错误包总数 */
private long outErrors;
/** 输入丢弃包总数 */
private long inDrops;
/** 碰撞总数 */
private long collisions;
/** 发送序列 */
private final Deque<Number> sent = new ArrayDeque<>(); private final Deque<Number> sent = new ArrayDeque<>();
/** 接收 */ /** 接收序列 */
private final Deque<Number> recv = new ArrayDeque<>(); private final Deque<Number> recv = new ArrayDeque<>();
} }
/** /**
* 分区 * 硬件信息
* *
* @author 夜雨 * @author 夜雨
* @version 2022-01-31 20:19 * @since 2026-04-06
*/ */
@Data @Data
public static class Partition { public static class Hardware {
/** 识别 UUID */ /** 风扇转速 */
private List<Integer> fanSpeeds = new ArrayList<>();
/** 主板信息 */
private Baseboard baseboard = new Baseboard();
/** BIOS 信息 */
private Firmware firmware = new Firmware();
}
/**
* 主板信息
*
* @author 夜雨
* @since 2026-04-06
*/
@Data
public static class Baseboard {
/** 厂商 */
private String manufacturer;
/** 型号 */
private String model;
/** 版本 */
private String version;
/** 序列号 */
private String serialNumber;
}
/**
* BIOS 信息
*
* @author 夜雨
* @since 2026-04-06
*/
@Data
public static class Firmware {
/** 厂商 */
private String manufacturer;
/** 名称 */
private String name;
/** 描述 */
private String description;
/** 版本 */
private String version;
/** 发布时间 */
private String releaseDate;
}
/**
* 存储分区
*
* @author 夜雨
* @since 2026-04-06
*/
@Data
public static class StoragePartition {
/** 物理磁盘名称 */
private String diskName;
/** 物理磁盘型号 */
private String diskModel;
/** 物理磁盘序列号 */
private String diskSerial;
/** 分区标识 */
private String partitionId;
/** 分区名称 */
private String partitionName;
/** 分区类型 */
private String partitionType;
/** 分区 UUID */
private String uuid; private String uuid;
/** 路径 */ /** 挂载点 */
private String path; private String mountPoint;
/** 文件系统类型 */ /** 分区总空间 */
private String type; private long totalBytes;
/** 已使用 */ /** 分区已用空间 */
private long used; private Long usedBytes;
/** 总大小 */ /** 磁盘传输耗时 */
private long total; private long transferTimeMs;
} }
} }

View File

@@ -0,0 +1,169 @@
package com.imyeyu.api.modules.system.bean;
import lombok.Data;
import org.springframework.stereotype.Component;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
/**
* UPS 状态缓存
*
* @author Codex
* @since 2026-04-07
*/
@Data
@Component
public class UpsStatusStore {
/** 当前状态 */
private Snapshot current;
/** 历史点 */
private final Deque<Point> history = new ArrayDeque<>();
/**
* UPS 当前快照
*
* @author Codex
* @since 2026-04-07
*/
@Data
public static class Snapshot {
/** 采样时间 */
private long updatedAt;
/** UPS 数据时间 */
private Long upsTime;
/** 上游主机地址 */
private String hostName;
/** 厂商名称 */
private String customer;
/** 上游版本 */
private String version;
/** 设备标识 */
private String deviceId;
/** UPS 类型 */
private String upsType;
/** UPS 形态 */
private String morphological;
/** 输入输出相位 */
private String ioPhase;
/** 工作模式 */
private String workMode;
/** 输入电压 */
private Double inputVoltage;
/** 输入频率 */
private Double inputFrequency;
/** 输出电压 */
private Double outputVoltage;
/** 输出频率 */
private Double outputFrequency;
/** 输出负载百分比 */
private Integer outputLoadPercent;
/** 电池电压 */
private Double batteryVoltage;
/** 电池容量百分比 */
private Integer batteryCapacity;
/** 电池剩余时间 */
private Integer batteryRemainTime;
/** 温度 */
private Double temperature;
/** 是否旁路运行 */
private boolean bypassActive;
/** 是否关机中 */
private boolean shutdownActive;
/** 是否输出开启 */
private boolean outputOn;
/** 是否充电中 */
private boolean charging;
/** 故障类型 */
private String faultType;
/** 故障明细 */
private String faultKind;
/** 告警列表 */
private List<String> warnings = new ArrayList<>();
}
/**
* UPS 历史点
*
* @author Codex
* @since 2026-04-07
*/
@Data
public static class Point {
/** 采样时间 */
private long at;
/** UPS 数据时间 */
private Long upsTime;
/** 工作模式 */
private String workMode;
/** 输入电压 */
private Double inputVoltage;
/** 输入频率 */
private Double inputFrequency;
/** 输出电压 */
private Double outputVoltage;
/** 输出频率 */
private Double outputFrequency;
/** 输出负载百分比 */
private Integer outputLoadPercent;
/** 电池电压 */
private Double batteryVoltage;
/** 电池容量百分比 */
private Integer batteryCapacity;
/** 电池剩余时间 */
private Integer batteryRemainTime;
/** 温度 */
private Double temperature;
/** 是否旁路运行 */
private boolean bypassActive;
/** 是否关机中 */
private boolean shutdownActive;
/** 是否输出开启 */
private boolean outputOn;
}
}

View File

@@ -0,0 +1,43 @@
package com.imyeyu.api.modules.system.controller;
import com.imyeyu.api.modules.system.service.DockerService;
import com.imyeyu.api.modules.system.vo.docker.DockerContainerHistoryView;
import com.imyeyu.api.modules.system.vo.docker.DockerContainerStatusView;
import com.imyeyu.api.modules.system.vo.docker.DockerContainerSummaryView;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* Docker 控制器
*
* @author Codex
* @since 2026-04-06
*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/system/docker")
public class DockerController {
private final DockerService dockerService;
@GetMapping("/containers")
public List<DockerContainerSummaryView> listContainers() {
return dockerService.listContainers();
}
@GetMapping("/containers/{containerId}/status")
public DockerContainerStatusView getContainerStatus(@PathVariable String containerId) {
return dockerService.getContainerStatus(containerId);
}
@GetMapping("/containers/{containerId}/history")
public DockerContainerHistoryView getContainerHistory(@PathVariable String containerId, @RequestParam(required = false) String window) {
return dockerService.getContainerHistory(containerId, window);
}
}

Some files were not shown because too many files have changed in this diff Show More