Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement new mechanisms for generating and managing attachment thumbnails #6454

Merged
merged 2 commits into from
Aug 26, 2024

Conversation

guqing
Copy link
Member

@guqing guqing commented Aug 9, 2024

What type of PR is this?

/kind feature
/area core
/milestone 2.19.x

What this PR does / why we need it:

实现了图片类型的附件缩略图生成和管理的新机制

实现依据 RFC:halo-dev/rfcs#24

使用缩略图前需要配置 externalUrl 才能生成

How to test it?

  1. 测试本地缩略图的文件是否正确,每个图片对应到相应 size 的目录如 thumbnails/w400 应该是一对一
  2. 每个图片生成缩略图的只会在 http://localhost:8090/apis/storage.halo.run/v1alpha1/thumbnails 中存在一份记录
  3. 测试删除附件会删除对应的缩略图文件和 thumbnails
    记录
  4. 修改 externalUrl 以上功能均不会受到影响

Which issue(s) this PR fixes:

Fixes #2387

Does this PR introduce a user-facing change?

附件图片支持生成多尺寸图片,文章支持响应式图片。

@f2c-ci-robot f2c-ci-robot bot added release-note Denotes a PR that will be considered when it comes time to generate release notes. do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. labels Aug 9, 2024
@f2c-ci-robot f2c-ci-robot bot added this to the 2.19.x milestone Aug 9, 2024
@f2c-ci-robot f2c-ci-robot bot added the kind/feature Categorizes issue or PR as related to a new feature. label Aug 9, 2024
@f2c-ci-robot f2c-ci-robot bot added the area/core Issues or PRs related to the Halo Core label Aug 9, 2024
Copy link

codecov bot commented Aug 9, 2024

Codecov Report

Attention: Patch coverage is 33.80952% with 417 lines in your changes missing coverage. Please review.

Project coverage is 57.79%. Comparing base (6cd8dc8) to head (fa528a0).
Report is 22 commits behind head on main.

Files Patch % Lines
...ore/attachment/impl/LocalThumbnailServiceImpl.java 30.46% 89 Missing ⚠️
...tachment/reconciler/LocalThumbnailsReconciler.java 7.22% 77 Missing ⚠️
...n/halo/app/core/attachment/ThumbnailGenerator.java 27.50% 57 Missing and 1 partial ⚠️
...run/halo/app/theme/endpoint/ThumbnailEndpoint.java 38.02% 44 Missing ⚠️
...re/attachment/reconciler/AttachmentReconciler.java 3.33% 29 Missing ⚠️
...app/core/attachment/impl/ThumbnailServiceImpl.java 69.49% 17 Missing and 1 partial ⚠️
...va/run/halo/app/core/attachment/ThumbnailSize.java 0.00% 17 Missing ⚠️
.../halo/app/content/HtmlThumbnailSrcsetInjector.java 65.90% 13 Missing and 2 partials ⚠️
.../app/core/extension/attachment/LocalThumbnail.java 27.77% 13 Missing ⚠️
...lo/app/core/attachment/LocalThumbnailProvider.java 8.33% 11 Missing ⚠️
... and 9 more
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #6454      +/-   ##
============================================
- Coverage     58.18%   57.79%   -0.39%     
- Complexity     3774     3860      +86     
============================================
  Files           651      668      +17     
  Lines         22125    22829     +704     
  Branches       1538     1584      +46     
============================================
+ Hits          12873    13195     +322     
- Misses         8641     9014     +373     
- Partials        611      620       +9     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@ruibaby
Copy link
Member

ruibaby commented Aug 12, 2024

如果不同的存储策略上传了同名的图片,生成缩略图会异常,所以建议 thumbnails 目录根据存储策略隔离。

image

日志:

2024-08-12T16:40:33.203+08:00  WARN 73062 --- [actor-tcp-nio-6] r.h.a.c.a.impl.ThumbnailServiceImpl      : Failed to generate thumbnail for image: http://127.0.0.1:8090/upload/local-b/logo.png

run.halo.app.infra.exception.DuplicateNameException: 400 BAD_REQUEST "The value [77e4220ae17945f24a0ada3d33821cfe53839f20f81da204c511363c1727ae1f] is already exists for unique index [spec.thumbSignature]."
        at run.halo.app.extension.index.IndexEntryImpl.addEntry(IndexEntryImpl.java:69) ~[main/:na]
        at run.halo.app.extension.index.IndexerTransactionImpl.applyChange(IndexerTransactionImpl.java:82) ~[main/:na]
        at run.halo.app.extension.index.IndexerTransactionImpl.commit(IndexerTransactionImpl.java:38) ~[main/:na]
        at run.halo.app.extension.index.DefaultIndexer.indexRecord(DefaultIndexer.java:70) ~[main/:na]
        at run.halo.app.extension.ReactiveExtensionClientImpl.lambda$doCreate$29(ReactiveExtensionClientImpl.java:323) ~[main/:na]
        at reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:185) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:122) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.MonoUsingWhen$MonoUsingWhenSubscriber.deferredComplete(MonoUsingWhen.java:268) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxUsingWhen$CommitInner.onComplete(FluxUsingWhen.java:532) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.Operators.complete(Operators.java:137) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.MonoEmpty.subscribe(MonoEmpty.java:46) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.Mono.subscribe(Mono.java:4568) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxUsingWhen$UsingWhenSubscriber.onComplete(FluxUsingWhen.java:389) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:159) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.Operators$BaseFluxToMonoOperator.completePossiblyEmpty(Operators.java:2097) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.MonoTakeLastOne$TakeLastOneSubscriber.onComplete(MonoTakeLastOne.java:162) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onComplete(Operators.java:2231) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxUsingWhen$UsingWhenSubscriber.deferredComplete(FluxUsingWhen.java:397) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxUsingWhen$CommitInner.onComplete(FluxUsingWhen.java:532) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onComplete(Operators.java:2231) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.MonoIgnoreElements$IgnoreElementsSubscriber.onComplete(MonoIgnoreElements.java:89) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:144) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxFilter$FilterSubscriber.onComplete(FluxFilter.java:166) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxFilter$FilterConditionalSubscriber.onComplete(FluxFilter.java:300) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxMap$MapConditionalSubscriber.onComplete(FluxMap.java:275) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2573) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxMap$MapConditionalSubscriber.request(FluxMap.java:295) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxFilter$FilterConditionalSubscriber.request(FluxFilter.java:321) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxFilter$FilterSubscriber.request(FluxFilter.java:186) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxMap$MapSubscriber.request(FluxMap.java:164) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.MonoIgnoreElements$IgnoreElementsSubscriber.onSubscribe(MonoIgnoreElements.java:72) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxMap$MapSubscriber.onSubscribe(FluxMap.java:92) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxFilter$FilterSubscriber.onSubscribe(FluxFilter.java:85) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxFilter$FilterConditionalSubscriber.onSubscribe(FluxFilter.java:219) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxMap$MapConditionalSubscriber.onSubscribe(FluxMap.java:194) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:55) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.MonoDeferContextual.subscribe(MonoDeferContextual.java:55) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:76) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:53) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.Mono.subscribe(Mono.java:4568) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxUsingWhen$UsingWhenSubscriber.onComplete(FluxUsingWhen.java:389) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxFlatMap$FlatMapMain.checkTerminated(FluxFlatMap.java:850) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxFlatMap$FlatMapMain.drainLoop(FluxFlatMap.java:612) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxFlatMap$FlatMapMain.innerComplete(FluxFlatMap.java:898) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxFlatMap$FlatMapInner.onComplete(FluxFlatMap.java:1001) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxHandle$HandleSubscriber.onComplete(FluxHandle.java:223) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.MonoFlatMapMany$FlatMapManyInner.onComplete(MonoFlatMapMany.java:261) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxHandleFuseable$HandleFuseableSubscriber.onComplete(FluxHandleFuseable.java:239) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onComplete(FluxFilterFuseable.java:391) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onComplete(FluxContextWrite.java:126) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxPeekFuseable$PeekConditionalSubscriber.onComplete(FluxPeekFuseable.java:940) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxPeekFuseable$PeekConditionalSubscriber.onComplete(FluxPeekFuseable.java:940) ~[reactor-core-3.6.7.jar:3.6.7]
        at io.r2dbc.postgresql.util.FluxDiscardOnCancel$FluxDiscardOnCancelSubscriber.onComplete(FluxDiscardOnCancel.java:104) ~[r2dbc-postgresql-1.0.5.RELEASE.jar:1.0.5.RELEASE]
        at reactor.core.publisher.FluxDoFinally$DoFinallySubscriber.onComplete(FluxDoFinally.java:128) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxHandle$HandleSubscriber.onComplete(FluxHandle.java:223) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxCreate$BaseSink.complete(FluxCreate.java:465) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxCreate$BufferAsyncSink.drain(FluxCreate.java:871) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxCreate$BufferAsyncSink.complete(FluxCreate.java:819) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxCreate$SerializedFluxSink.drainLoop(FluxCreate.java:249) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxCreate$SerializedFluxSink.drain(FluxCreate.java:215) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxCreate$SerializedFluxSink.complete(FluxCreate.java:206) ~[reactor-core-3.6.7.jar:3.6.7]
        at io.r2dbc.postgresql.client.ReactorNettyClient$Conversation.complete(ReactorNettyClient.java:668) ~[r2dbc-postgresql-1.0.5.RELEASE.jar:1.0.5.RELEASE]
        at io.r2dbc.postgresql.client.ReactorNettyClient$BackendMessageSubscriber.emit(ReactorNettyClient.java:934) ~[r2dbc-postgresql-1.0.5.RELEASE.jar:1.0.5.RELEASE]
        at io.r2dbc.postgresql.client.ReactorNettyClient$BackendMessageSubscriber.onNext(ReactorNettyClient.java:810) ~[r2dbc-postgresql-1.0.5.RELEASE.jar:1.0.5.RELEASE]
        at io.r2dbc.postgresql.client.ReactorNettyClient$BackendMessageSubscriber.onNext(ReactorNettyClient.java:716) ~[r2dbc-postgresql-1.0.5.RELEASE.jar:1.0.5.RELEASE]
        at reactor.core.publisher.FluxHandle$HandleSubscriber.onNext(FluxHandle.java:129) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxPeekFuseable$PeekConditionalSubscriber.onNext(FluxPeekFuseable.java:854) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxMap$MapConditionalSubscriber.onNext(FluxMap.java:224) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.core.publisher.FluxMap$MapConditionalSubscriber.onNext(FluxMap.java:224) ~[reactor-core-3.6.7.jar:3.6.7]
        at reactor.netty.channel.FluxReceive.drainReceiver(FluxReceive.java:294) ~[reactor-netty-core-1.1.20.jar:1.1.20]
        at reactor.netty.channel.FluxReceive.onInboundNext(FluxReceive.java:403) ~[reactor-netty-core-1.1.20.jar:1.1.20]
        at reactor.netty.channel.ChannelOperations.onInboundNext(ChannelOperations.java:426) ~[reactor-netty-core-1.1.20.jar:1.1.20]
        at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:114) ~[reactor-netty-core-1.1.20.jar:1.1.20]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346) ~[netty-codec-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:318) ~[netty-codec-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1407) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:918) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:994) ~[netty-common-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.111.Final.jar:4.1.111.Final]
        at java.base/java.lang.Thread.run(Thread.java:840) ~[na:na]

@guqing
Copy link
Member Author

guqing commented Aug 13, 2024

如果不同的存储策略上传了同名的图片,生成缩略图会异常,所以建议 thumbnails 目录根据存储策略隔离。

改了,这是个bug,之前做了检查的,但是检查的是文件名,应该检查 thumbnail 记录

@guqing guqing marked this pull request as ready for review August 14, 2024 03:08
@f2c-ci-robot f2c-ci-robot bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Aug 14, 2024
@f2c-ci-robot f2c-ci-robot bot requested a review from LIlGG August 14, 2024 03:08
@guqing guqing marked this pull request as draft August 14, 2024 09:13
@f2c-ci-robot f2c-ci-robot bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Aug 14, 2024
@guqing guqing force-pushed the feature/2387 branch 8 times, most recently from 549a4cf to 22590c8 Compare August 20, 2024 04:21
@guqing
Copy link
Member Author

guqing commented Aug 21, 2024

/ping @halo-dev/sig-halo

Copy link
Member

@ruibaby ruibaby left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/approve

@f2c-ci-robot f2c-ci-robot bot added approved Indicates a PR has been approved by an approver from all required OWNERS files. needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. and removed needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. labels Aug 23, 2024
@LIlGG
Copy link
Member

LIlGG commented Aug 26, 2024

/hold

@f2c-ci-robot f2c-ci-robot bot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Aug 26, 2024
@guqing guqing force-pushed the feature/2387 branch 5 times, most recently from 70b1376 to a60d0aa Compare August 26, 2024 08:40
@LIlGG
Copy link
Member

LIlGG commented Aug 26, 2024

/unhold

@f2c-ci-robot f2c-ci-robot bot removed the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Aug 26, 2024
Copy link

sonarcloud bot commented Aug 26, 2024

Copy link
Member

@ruibaby ruibaby left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/lgtm

@f2c-ci-robot f2c-ci-robot bot added the lgtm Indicates that a PR is ready to be merged. label Aug 26, 2024
Copy link

f2c-ci-robot bot commented Aug 26, 2024

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: ruibaby

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@f2c-ci-robot f2c-ci-robot bot merged commit ef37aa7 into halo-dev:main Aug 26, 2024
8 checks passed
@guqing guqing deleted the feature/2387 branch August 26, 2024 10:28
@JohnNiang JohnNiang modified the milestones: 2.19.x, 2.19.0 Aug 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. area/core Issues or PRs related to the Halo Core kind/feature Categorizes issue or PR as related to a new feature. lgtm Indicates that a PR is ready to be merged. release-note Denotes a PR that will be considered when it comes time to generate release notes. tide/merge-method-merge Denotes a PR that should use a standard merge by tide when it merges.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

希望 Halo 支持附件缩略图功能
4 participants