You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Quarkus静态文件服务异常:上传文件后需重启服务器才可访问

Quarkus静态文件上传后无法立即访问的解决方案

问题场景

使用Quarkus官方静态文件服务模板实现MP3上传功能,上传文件后直接访问对应静态资源报错,必须重启Quarkus服务器才能正常访问。报错信息如下:

2024-04-03 19:33:51,462 INFO  [io.qua.dep.dev.RuntimeUpdatesProcessor] (vert.x-worker-thread-1) Files changed but restart not needed - notified extensions in: 0.075s uarkusDev
2024-04-03 19:33:51,618 ERROR [io.qua.ver.htt.run.QuarkusErrorHandler] (vert.x-eventloop-thread-1) HTTP Request to /static/audio_1712169223992.mp3 failed, error id: 9d613d1f-f728-46f4-8977-1161222efa2d-3: io.vertx.core.VertxException: Invalid resource: static/audio_1712169223992.mp3
at io.vertx.core.file.impl.FileResolverImpl.unpackFromFileURL(FileResolverImpl.java:278)
at io.vertx.core.file.impl.FileResolverImpl.unpackUrlResource(FileResolverImpl.java:246)
at io.vertx.core.file.impl.FileResolverImpl.resolveFile(FileResolverImpl.java:171)
at io.vertx.core.impl.VertxImpl.resolveFile(VertxImpl.java:929)
at io.vertx.core.file.impl.FileSystemImpl$20.perform(FileSystemImpl.java:1135)
at io.vertx.core.file.impl.FileSystemImpl$20.perform(FileSystemImpl.java:1133)
at io.vertx.core.file.impl.FileSystemImpl$BlockingAction.handle(FileSystemImpl.java:1174)
at io.vertx.core.file.impl.FileSystemImpl$BlockingAction.handle(FileSystemImpl.java:1156)
at io.vertx.core.impl.ContextImpl.lambda$executeBlocking$1(ContextImpl.java:190)
at io.vertx.core.impl.ContextInternal.dispatch(ContextInternal.java:276)
at io.vertx.core.impl.ContextImpl.lambda$internalExecuteBlocking$2(ContextImpl.java:209)
at io.vertx.core.impl.TaskQueue.run(TaskQueue.java:76)
at org.jboss.threads.ContextHandler$1.runWith(ContextHandler.java:18)
at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513)
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1538)
at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:1570)

核心原因

  1. 资源加载路径不匹配:默认StaticHandler.create("static/")从类路径加载资源,而上传文件写入本地src/main/resources/static目录,类路径资源不会实时同步本地文件系统变更,需重启服务器重新加载。
  2. 缓存机制干扰:Vertx StaticHandler默认开启缓存,文件更新后仍返回缓存内容或判定资源不存在。
  3. 目录选择不合理:src/main/resources是编译时资源目录,生产环境会被打包进Jar包,无法动态写入文件。

解决方案

1. 修改StaticHandler配置,直接读取本地文件系统

调整StaticHandler初始化逻辑,指向本地文件系统的实际目录,启用文件系统访问权限并禁用缓存:

package com.example.songcomponent.song.repository;

import io.quarkus.runtime.StartupEvent;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.StaticHandler;
import jakarta.enterprise.event.Observes;
import java.nio.file.Paths;

public class StaticResources {

    void installRoute(@Observes StartupEvent startupEvent, Router router) {
        // 指向项目根目录下的uploads目录(可替换为自定义绝对路径)
        String uploadDir = Paths.get("uploads").toAbsolutePath().toString();
        StaticHandler staticHandler = StaticHandler.create(uploadDir)
                .setAllowFileSystemAccess(true) // 允许访问本地文件系统
                .setCachingEnabled(false); // 禁用缓存,确保实时读取最新文件
        router.route("/static/*").handler(staticHandler);
    }
}

2. 调整文件上传目录与逻辑

将上传目录改为外部目录(如项目根目录下的uploads),并优化文件路径拼接与返回结果:

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriBuilder;

public Response saveSong(InputStream fileInputStream) {
    // 项目根目录下的uploads目录,适配跨平台路径分隔符
    String UPLOAD_DIRECTORY = Paths.get("uploads").toAbsolutePath().toString();
    try {
        Files.createDirectories(Paths.get(UPLOAD_DIRECTORY));
        String fileName = "audio_" + System.currentTimeMillis() + ".mp3";
        File uploadedFile = new File(UPLOAD_DIRECTORY, fileName);
        
        try (FileOutputStream outputStream = new FileOutputStream(uploadedFile)) {
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = fileInputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
            outputStream.flush();
        }
        // 返回可直接访问的HTTP路径,而非本地目录路径
        return Response.created(UriBuilder.fromPath("/static/" + fileName).build()).build();
    } catch (Exception e) {
        e.printStackTrace();
        return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
    }
}

3. 接口保持原有定义

原上传接口无需修改,确保参数与逻辑正确调用上述saveSong方法即可:

import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.jboss.resteasy.annotations.FormDataParam;

@POST
@Path("/savesong")
@PermitAll
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.APPLICATION_JSON)
public Response savesong(@FormDataParam("file") InputStream fileInputStream) {
    return songService.saveSong(fileInputStream);
}

生产环境注意事项

  • 生产环境可重新启用缓存,通过setCachingEnabled(true)并设置合理的缓存过期时间,提升访问性能。
  • 上传目录建议通过环境变量注入绝对路径,避免Jar包内无法写入的问题。
  • 添加文件大小限制、格式校验等安全措施,防止恶意文件上传。

内容的提问来源于stack exchange,提问作者Arturs Radionovs

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 10:21:01