halo-sigs / halo-sigs/plugin-alist
前端上传错误500
- Dominant language
- Java
- Stars
- 12
- Forks
- 7
- PR merge metrics
- No merged PRs in 30d
Description
复现方式
后台设置-附件设置-个人中心设置-alist存储
在个人中心手动上传无问题
写了一个瞬间前端上传
经测试 本地/alist/s3/lsky
只有alist会报500上传错误,但在后台查看附件,是上传成功的
```
{
"type": "about:blank",
"title": "服务器内部错误",
"status": 500,
"detail": "服务器内部发生错误,请稍候再试。",
"instance": "http://localhost:8090/apis/uc.api.storage.halo.run/v1alpha1/attachments/-/upload",
"requestId": "d4281b2b-1004",
"timestamp": "2026-01-27T00:14:06.574805554Z"
}
```
```
*__checkpoint ⇢ Handler run.halo.app.core.endpoint.uc.AttachmentUcEndpoint$$Lambda/0x000000009343d2a0@da24e554 [DispatcherHandler]
*__checkpoint ⇢ run.halo.app.security.InitializeRedirectionWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ SecurityWebFilterChainProxy [DefaultWebFilterChain]
*__checkpoint ⇢ SwitchUserWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ AuthorizationWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ ExceptionTranslationWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ LogoutWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ ServerRequestCacheWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ SecurityContextServerWebExchangeWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ SecurityWebFilterChainProxy [DefaultWebFilterChain]
*__checkpoint ⇢ AnonymousAuthenticationWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ SecurityWebFilterChainProxy [DefaultWebFilterChain]
*__checkpoint ⇢ run.halo.social.login.security.SocialAuthenticatorFilter [DefaultWebFilterChain]
*__checkpoint ⇢ run.halo.oauth.HaloOAuth2AuthenticationWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ SecurityWebFilterChainProxy [DefaultWebFilterChain]
*__checkpoint ⇢ AuthenticationWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ AuthenticationWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ AuthenticationWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ MapOAuth2AuthenticationFilter [DefaultWebFilterChain]
*__checkpoint ⇢ AuthenticationWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ SecurityWebFilterChainProxy [DefaultWebFilterChain]
*__checkpoint ⇢ [DefaultWebFilterChain]
*__checkpoint ⇢ run.halo.social.login.security.SocialAuthorizationRequestRedirectWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ run.halo.oauth.HaloOAuth2RedirectWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ SecurityWebFilterChainProxy [DefaultWebFilterChain]
*__checkpoint ⇢ CaptchaWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ DeviceSessionFilter [DefaultWebFilterChain]
*__checkpoint ⇢ ReactorContextWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ CsrfWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ CorsWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ HttpHeaderWriterWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ SecurityWebFilterChainProxy [DefaultWebFilterChain]
*__checkpoint ⇢ ServerWebExchangeReactorContextWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ org.springframework.security.web.server.WebFilterChainProxy [DefaultWebFilterChain]
*__checkpoint ⇢ run.halo.moments.rss.OldRssRouteRedirectionFilter [DefaultWebFilterChain]
*__checkpoint ⇢ run.halo.seo.tools.AdvancedRedirectWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ run.halo.comment.widget.captcha.CommentCaptchaFilter [DefaultWebFilterChain]
*__checkpoint ⇢ run.halo.seo.tools.CrawlRecordFilter [DefaultWebFilterChain]
*__checkpoint ⇢ run.halo.app.infra.webfilter.AdditionalWebFilterChainProxy [DefaultWebFilterChain]
*__checkpoint ⇢ org.springframework.web.filter.reactive.ServerWebExchangeContextFilter [DefaultWebFilterChain]
*__checkpoint ⇢ run.halo.app.infra.webfilter.LocaleChangeWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ org.springframework.web.filter.reactive.UrlHandlerFilter [DefaultWebFilterChain]
*__checkpoint ⇢ run.halo.app.theme.UserLocaleRequestAttributeWriteFilter [DefaultWebFilterChain]
*__checkpoint ⇢ HTTP POST "/apis/uc.api.storage.halo.run/v1alpha1/attachments/-/upload" [ExceptionHandlingWebHandler]
Original Stack Trace:
at run.halo.app.core.endpoint.AttachmentHandler.lambda$handleUpload$2(AttachmentHandler.java:108) ~[classes/:2.22.12]
at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.onNext(FluxPeekFuseable.java:196) ~[reactor-core-3.7.14.jar:3.7.14]
at reactor.core.publisher.MonoFlatMap$FlatMapMain.secondComplete(MonoFlatMap.java:245) ~[reactor-core-3.7.14.jar:3.7.14]
at reactor.core.publisher.MonoFlatMap$FlatMapInner.onNext(MonoFlatMap.java:305) ~[reactor-core-3.7.14.jar:3.7.14]
at reactor.core.publisher.MonoFlatMap$FlatMapMain.secondComplete(MonoFlatMap.java:245) ~[reactor-core-3.7.14.jar:3.7.14]
at reactor.core.publisher.MonoFlatMap$FlatMapInner.onNext(MonoFlatMap.java:305) ~[reactor-core-3.7.14.jar:3.7.14]
at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:82) ~[reactor-core-3.7.14.jar:3.7.14]
at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.innerNext(FluxConcatMapNoPrefetch.java:259) ~[reactor-core-3.7.14.jar:3.7.14]
at reactor.core.publisher.FluxConcatMap$ConcatMapInner.onNext(FluxConcatMap.java:865) ~[reactor-core-3.7.14.jar:3.7.14]
at reactor.core.publisher.MonoFlatMap$FlatMapMain.secondComplete(MonoFlatMap.java:245) ~[reactor-core-3.7.14.jar:3.7.14]
at reactor.core.publisher.MonoFlatMap$FlatMapInner.onNext(MonoFlatMap.java:305) ~[reactor-core-3.7.14.jar:3.7.14]
at reactor.core.publisher.MonoFlatMap$FlatMapMain.secondComplete(MonoFlatMap.java:245) ~[reactor-core-3.7.14.jar:3.7.14]
at reactor.core.publisher.MonoFlatMap$FlatMapInner.onNext(MonoFlatMap.java:305) ~[reactor-core-3.7.14.jar:3.7.14]
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:129) ~[reactor-core-3.7.14.jar:3.7.14]
at reactor.core.publisher.MonoFlatMap$FlatMapMain.secondComplete(MonoFlatMap.java:245) ~[reactor-core-3.7.14.jar:3.7.14]
at reactor.core.publisher.MonoFlatMap$FlatMapInner.onNext(MonoFlatMap.java:305) ~[reactor-core-3.7.14.jar:3.7.14]
at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79) ~[reactor-core-3.7.14.jar:3.7.14]
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:158) ~[reactor-core-3.7.14.jar:3.7.14]
at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107) ~[reactor-core-3.7.14.jar:3.7.14]
at reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:299) ~[reactor-core-3.7.14.jar:3.7.14]
at reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onNext(FluxFilterFuseable.java:337) ~[reactor-core-3.7.14.jar:3.7.14]
at reactor.core.publisher.Operators$BaseFluxToMonoOperator.completePossiblyEmpty(Operators.java:2096) ~[reactor-core-3.7.14.jar:3.7.14]
at reactor.core.publisher.MonoCollect$CollectSubscriber.onComplete(MonoCollect.java:145) ~[reactor-core-3.7.14.jar:3.7.14]
at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:144) ~[reactor-core-3.7.14.jar:3.7.14]
at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:260) ~[reactor-core-3.7.14.jar:3.7.14]
at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:144) ~[reactor-core-3.7.14.jar:3.7.14]
at reactor.netty.channel.FluxReceive.onInboundComplete(FluxReceive.java:413) ~[reactor-netty-core-1.2.13.jar:1.2.13]
at reactor.netty.channel.ChannelOperations.onInboundComplete(ChannelOperations.java:464) ~[reactor-netty-core-1.2.13.jar:1.2.13]
at reactor.netty.channel.ChannelOperations.terminate(ChannelOperations.java:518) ~[reactor-netty-core-1.2.13.jar:1.2.13]
at reactor.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:928) ~[reactor-netty-http-1.2.13.jar:1.2.13]
at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:115) ~[reactor-netty-core-1.2.13.jar:1.2.13]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.130.Final.jar:4.1.130.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.130.Final.jar:4.1.130.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.130.Final.jar:4.1.130.Final]
at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436) ~[netty-transport-4.1.130.Final.jar:4.1.130.Final]
at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:361) ~[netty-codec-4.1.130.Final.jar:4.1.130.Final]
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:325) ~[netty-codec-4.1.130.Final.jar:4.1.130.Final]
at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251) ~[netty-transport-4.1.130.Final.jar:4.1.130.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442) ~[netty-transport-4.1.130.Final.jar:4.1.130.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.130.Final.jar:4.1.130.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.130.Final.jar:4.1.130.Final]
at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:289) ~[netty-handler-4.1.130.Final.jar:4.1.130.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442) ~[netty-transport-4.1.130.Final.jar:4.1.130.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.130.Final.jar:4.1.130.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.130.Final.jar:4.1.130.Final]
at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1519) ~[netty-handler-4.1.130.Final.jar:4.1.130.Final]
at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1377) ~[netty-handler-4.1.130.Final.jar:4.1.130.Final]
at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1428) ~[netty-handler-4.1.130.Final.jar:4.1.130.Final]
at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:545) ~[netty-codec-4.1.130.Final.jar:4.1.130.Final]
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:484) ~[netty-codec-4.1.130.Final.jar:4.1.130.Final]
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:296) ~[netty-codec-4.1.130.Final.jar:4.1.130.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.130.Final.jar:4.1.130.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.130.Final.jar:4.1.130.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.130.Final.jar:4.1.130.Final]
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1357) ~[netty-transport-4.1.130.Final.jar:4.1.130.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) ~[netty-transport-4.1.130.Final.jar:4.1.130.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.130.Final.jar:4.1.130.Final]
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:868) ~[netty-transport-4.1.130.Final.jar:4.1.130.Final]
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) ~[netty-transport-4.1.130.Final.jar:4.1.130.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:796) ~[netty-transport-4.1.130.Final.jar:4.1.130.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:732) ~[netty-transport-4.1.130.Final.jar:4.1.130.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:658) ~[netty-transport-4.1.130.Final.jar:4.1.130.Final]
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562) ~[netty-transport-4.1.130.Final.jar:4.1.130.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:998) ~[netty-common-4.1.130.Final.jar:4.1.130.Final]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.130.Final.jar:4.1.130.Final]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.130.Final.jar:4.1.130.Final]
at java.base/java.lang.Thread.run(Unknown Source) ~[na:na]
```
ai给出的解析 仅供参考
🐛 Alist 插件的技术问题
核心问题:status 字段未初始化
Alist 插件的实现缺陷:
// ❌ Alist 插件的实现(简化示意)public Mono upload(FilePart filePart) { Attachment attachment = new Attachment(); attachment.setMetadata(...); attachment.setSpec(...); // ⚠️ 没有初始化 status! // attachment.setStatus(new AttachmentStatus()); ← 缺少这行 // 异步上传到 Alist 远程服务器 return alistClient.uploadAsync(filePart) .map(result -> { // 上传完成后,通过 Reconciler 更新 status return attachment; });}
对比 Lsky 插件的正确实现:
// ✅ Lsky 插件的实现public Mono upload(FilePart filePart) { Attachment attachment = new Attachment(); attachment.setMetadata(...); attachment.setSpec(...); // ✅ 立即初始化 status AttachmentStatus status = new AttachmentStatus(); status.setPermalink(null); // 先设为 null,上传完成后更新 attachment.setStatus(status); return lskyClient.upload(filePart) .doOnSuccess(url -> { status.setPermalink(url); });}
为什么会崩溃?
Halo 核心代码的问题(AttachmentHandler.java:108):
// Halo 核心代码在上传完成后尝试设置 permalinkAttachment attachment = storagePlugin.upload(file); // 调用插件上传// ❌ 直接调用,没有检查 nullattachment.getStatus().setPermalink(permalink);// ↑// Alist 插件返回 null → NullPointerException
应该这样写(防御性编程):
// ✅ 正确的写法if (attachment.getStatus() == null) { attachment.setStatus(new AttachmentStatus());}attachment.getStatus().setPermalink(permalink);
Contributor guide
No contributing guide indexed for this repository
Research direction
Start at AttachmentHandler.java:108, which appears in the stack trace for POST /apis/uc.api.storage.halo.run/v1alpha1/attachments/-/upload, and compare the frontend upload path with the working personal-center upload path for Alist storage. Reproduce the issue using the listed Alist setup and verify that the upload no longer returns 500 while the attachment remains successfully stored.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100