halo-sigs / halo-sigs/plugin-alist

前端上传错误500

Open
#50 4 comments 0 reactions 0 assignees View on GitHub
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]
```

Image

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.