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)
核心原因
- 资源加载路径不匹配:默认
StaticHandler.create("static/")从类路径加载资源,而上传文件写入本地src/main/resources/static目录,类路径资源不会实时同步本地文件系统变更,需重启服务器重新加载。 - 缓存机制干扰:Vertx StaticHandler默认开启缓存,文件更新后仍返回缓存内容或判定资源不存在。
- 目录选择不合理:
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
相关产品推荐
相关产品推荐

