Skip to content

Commit 2a6fb89

Browse files
authored
feat: subject tag indices and event listener. (#738)
* build: v0.20.5 * docs: update BUILD.MD * build: add windows run config for idea. * feat: 将条目标签加入条目全局搜索引擎的索引里 * feat: 条目标签更新事件监听,重建对应条目索引
1 parent ba1f7d4 commit 2a6fb89

File tree

13 files changed

+201
-10
lines changed

13 files changed

+201
-10
lines changed

.run/RunAppOnWindows.run.xml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<component name="ProjectRunConfigurationManager">
2+
<configuration default="false" name="RunAppOnWindows" type="Application" factoryName="Application">
3+
<option name="MAIN_CLASS_NAME" value="run.ikaros.server.IkarosApplication" />
4+
<module name="ikaros.server.main" />
5+
<option name="VM_PARAMETERS" value="-Dspring.profiles.active=dev,win-dev,local" />
6+
<extension name="coverage">
7+
<pattern>
8+
<option name="PATTERN" value="run.ikaros.server.*" />
9+
<option name="ENABLED" value="true" />
10+
</pattern>
11+
</extension>
12+
<method v="2">
13+
<option name="Make" enabled="true" />
14+
</method>
15+
</configuration>
16+
</component>

BUILD.MD

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ commit之前,用checkstyle检查下代码,确认没有问题后再commit。
7979

8080
## 本地开发
8181

82-
`IkarosApplication`的运行配置里,将`Active profiles` 配置成:`dev,win-dev,local`
82+
`IkarosApplication`的运行配置里,将`Active profiles` 配置成:`dev,win-dev,local`,社区版则添加VM设置`-Dspring.profiles.active=dev,win-dev,local`
8383

8484
这里的`local`请先确保您已经进行了上面的`application-local.yaml.example`实例文件复制移动重命名
8585

CHANGELOG.MD

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22

33
更新日志文档,版本顺序从新到旧,最新版本在最前(上)面。
44

5+
# 0.20.5
6+
7+
# 新特性
8+
9+
- 将条目标签加入条目全局搜索引擎的索引里
10+
- 条目标签更新事件监听,重建对应条目索引
11+
512
# 0.20.4
613

714
## 优化

api/src/main/java/run/ikaros/api/search/subject/SubjectDoc.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package run.ikaros.api.search.subject;
22

3+
import java.util.List;
34
import lombok.Data;
45
import run.ikaros.api.store.enums.SubjectType;
56

@@ -14,4 +15,5 @@ public class SubjectDoc {
1415
private Boolean nsfw;
1516
private Long airTime;
1617
private String cover;
18+
private List<String> tags;
1719
}

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
version=0.20.4
1+
version=0.20.5

server/src/main/java/run/ikaros/server/core/tag/DefaultTagService.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import java.util.Objects;
66
import lombok.extern.slf4j.Slf4j;
7+
import org.springframework.context.ApplicationEventPublisher;
78
import org.springframework.data.domain.Sort;
89
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
910
import org.springframework.data.relational.core.query.Criteria;
@@ -18,6 +19,8 @@
1819
import run.ikaros.api.infra.exception.NotFoundException;
1920
import run.ikaros.api.infra.utils.StringUtils;
2021
import run.ikaros.api.store.enums.TagType;
22+
import run.ikaros.server.core.tag.event.TagCreateEvent;
23+
import run.ikaros.server.core.tag.event.TagRemoveEvent;
2124
import run.ikaros.server.store.entity.TagEntity;
2225
import run.ikaros.server.store.repository.TagRepository;
2326

@@ -26,14 +29,17 @@
2629
public class DefaultTagService implements TagService {
2730
private final TagRepository tagRepository;
2831
private final R2dbcEntityTemplate r2dbcEntityTemplate;
32+
private final ApplicationEventPublisher eventPublisher;
2933

3034
/**
3135
* Construct.
3236
*/
3337
public DefaultTagService(TagRepository tagRepository,
34-
R2dbcEntityTemplate r2dbcEntityTemplate) {
38+
R2dbcEntityTemplate r2dbcEntityTemplate,
39+
ApplicationEventPublisher eventPublisher) {
3540
this.tagRepository = tagRepository;
3641
this.r2dbcEntityTemplate = r2dbcEntityTemplate;
42+
this.eventPublisher = eventPublisher;
3743
}
3844

3945
@Override
@@ -102,6 +108,8 @@ public Mono<Tag> create(Tag tag) {
102108
.filter(exists -> !exists)
103109
.flatMap(exists -> copyProperties(tag, new TagEntity()))
104110
.flatMap(tagRepository::save)
111+
.doOnSuccess(savedTag ->
112+
eventPublisher.publishEvent(new TagCreateEvent(this, savedTag)))
105113
.flatMap(tagEntity -> copyProperties(tagEntity, tag));
106114
}
107115

@@ -120,7 +128,9 @@ public Mono<Void> removeById(Long tagId) {
120128
Assert.isTrue(tagId >= 0, "'tagId' must >=0.");
121129
return tagRepository.findById(tagId)
122130
.switchIfEmpty(Mono.error(new NotFoundException("Tag not found for id = " + tagId)))
123-
.map(TagEntity::getId)
124-
.flatMap(tagRepository::deleteById);
131+
.flatMap(tagEntity -> tagRepository.deleteById(tagEntity.getId())
132+
.doOnSuccess(unused ->
133+
eventPublisher.publishEvent(new TagRemoveEvent(this, tagEntity)))
134+
);
125135
}
126136
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package run.ikaros.server.core.tag.event;
2+
3+
import java.time.Clock;
4+
import lombok.Getter;
5+
import org.springframework.context.ApplicationEvent;
6+
import run.ikaros.server.store.entity.TagEntity;
7+
8+
@Getter
9+
public class TagChangeEvent extends ApplicationEvent {
10+
private final TagEntity entity;
11+
12+
public TagChangeEvent(Object source, TagEntity entity) {
13+
super(source);
14+
this.entity = entity;
15+
}
16+
17+
public TagChangeEvent(Object source, Clock clock, TagEntity entity) {
18+
super(source, clock);
19+
this.entity = entity;
20+
}
21+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package run.ikaros.server.core.tag.event;
2+
3+
import java.time.Clock;
4+
import lombok.Getter;
5+
import run.ikaros.server.store.entity.TagEntity;
6+
7+
@Getter
8+
public class TagCreateEvent extends TagChangeEvent {
9+
10+
public TagCreateEvent(Object source, TagEntity entity) {
11+
super(source, entity);
12+
}
13+
14+
public TagCreateEvent(Object source, Clock clock, TagEntity entity) {
15+
super(source, clock, entity);
16+
}
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package run.ikaros.server.core.tag.event;
2+
3+
import java.time.Clock;
4+
import lombok.Getter;
5+
import run.ikaros.server.store.entity.TagEntity;
6+
7+
@Getter
8+
public class TagRemoveEvent extends TagChangeEvent {
9+
10+
public TagRemoveEvent(Object source, TagEntity entity) {
11+
super(source, entity);
12+
}
13+
14+
public TagRemoveEvent(Object source, Clock clock, TagEntity entity) {
15+
super(source, clock, entity);
16+
}
17+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package run.ikaros.server.core.tag.listener;
2+
3+
import java.util.List;
4+
import lombok.extern.slf4j.Slf4j;
5+
import org.springframework.context.event.EventListener;
6+
import org.springframework.stereotype.Component;
7+
import reactor.core.publisher.Mono;
8+
import run.ikaros.api.search.subject.SubjectSearchService;
9+
import run.ikaros.api.store.enums.TagType;
10+
import run.ikaros.server.core.tag.event.TagChangeEvent;
11+
import run.ikaros.server.search.IndicesProperties;
12+
import run.ikaros.server.search.subject.ReactiveSubjectDocConverter;
13+
import run.ikaros.server.store.entity.TagEntity;
14+
import run.ikaros.server.store.repository.SubjectRepository;
15+
import run.ikaros.server.store.repository.TagRepository;
16+
17+
@Slf4j
18+
@Component
19+
public class TagChangeEventListener {
20+
21+
private final IndicesProperties indicesProperties;
22+
private final SubjectRepository subjectRepository;
23+
private final TagRepository tagRepository;
24+
private final SubjectSearchService subjectSearchService;
25+
26+
/**
27+
* Construct.
28+
*/
29+
public TagChangeEventListener(IndicesProperties indicesProperties,
30+
SubjectRepository subjectRepository, TagRepository tagRepository,
31+
SubjectSearchService subjectSearchService) {
32+
this.indicesProperties = indicesProperties;
33+
this.subjectRepository = subjectRepository;
34+
this.tagRepository = tagRepository;
35+
this.subjectSearchService = subjectSearchService;
36+
}
37+
38+
/**
39+
* 条目对应的标签新增或删除了,则更新对应的索引文档.
40+
*/
41+
@EventListener(TagChangeEvent.class)
42+
public Mono<Void> onTagCreateEvent(TagChangeEvent event) {
43+
if (!indicesProperties.getInitializer().isEnabled()) {
44+
return Mono.empty();
45+
}
46+
TagEntity tagEntity = event.getEntity();
47+
if (tagEntity.getType() != TagType.SUBJECT) {
48+
return Mono.empty();
49+
}
50+
Long subjectId = tagEntity.getMasterId();
51+
return subjectRepository.findById(subjectId)
52+
.flatMap(ReactiveSubjectDocConverter::fromEntity)
53+
.flatMap(subjectDoc ->
54+
tagRepository.findAllByTypeAndMasterId(TagType.SUBJECT, subjectDoc.getId())
55+
.map(TagEntity::getName).collectList()
56+
.map(tags -> {
57+
subjectDoc.setTags(tags);
58+
return subjectDoc;
59+
})
60+
).doOnSuccess(subjectDoc -> {
61+
try {
62+
subjectSearchService.updateDocument(List.of(subjectDoc));
63+
} catch (Exception e) {
64+
throw new RuntimeException(e);
65+
}
66+
}).then();
67+
}
68+
}

server/src/main/java/run/ikaros/server/search/IndicesServiceImpl.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,30 @@
33
import lombok.extern.slf4j.Slf4j;
44
import org.springframework.stereotype.Service;
55
import reactor.core.publisher.Mono;
6+
import run.ikaros.api.search.subject.SubjectDoc;
67
import run.ikaros.api.search.subject.SubjectSearchService;
8+
import run.ikaros.api.store.enums.TagType;
79
import run.ikaros.server.search.subject.ReactiveSubjectDocConverter;
10+
import run.ikaros.server.store.entity.TagEntity;
811
import run.ikaros.server.store.repository.SubjectRepository;
12+
import run.ikaros.server.store.repository.TagRepository;
913

1014
@Slf4j
1115
@Service
1216
public class IndicesServiceImpl implements IndicesService {
1317

1418
private final SubjectRepository subjectRepository;
19+
private final TagRepository tagRepository;
1520
private final SubjectSearchService subjectSearchService;
1621

1722
/**
1823
* Construct.
1924
*/
2025
public IndicesServiceImpl(
21-
SubjectRepository subjectRepository,
26+
SubjectRepository subjectRepository, TagRepository tagRepository,
2227
SubjectSearchService subjectSearchService) {
2328
this.subjectRepository = subjectRepository;
29+
this.tagRepository = tagRepository;
2430
this.subjectSearchService = subjectSearchService;
2531
}
2632

@@ -29,6 +35,7 @@ public IndicesServiceImpl(
2935
public Mono<Void> rebuildSubjectIndices() {
3036
return subjectRepository.findAll()
3137
.flatMap(ReactiveSubjectDocConverter::fromEntity)
38+
.flatMap(this::fetchSubTags)
3239
.collectList()
3340
.handle((subjectDocs, sink) -> {
3441
try {
@@ -39,4 +46,14 @@ public Mono<Void> rebuildSubjectIndices() {
3946
})
4047
.then();
4148
}
49+
50+
private Mono<SubjectDoc> fetchSubTags(SubjectDoc subjectDoc) {
51+
return tagRepository.findAllByTypeAndMasterId(TagType.SUBJECT, subjectDoc.getId())
52+
.map(TagEntity::getName)
53+
.collectList()
54+
.map(tags -> {
55+
subjectDoc.setTags(tags);
56+
return subjectDoc;
57+
});
58+
}
4259
}

server/src/main/java/run/ikaros/server/search/subject/LuceneSubjectSearchService.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public void updateDocument(List<SubjectDoc> subjectDocs) throws Exception {
124124
}
125125
});
126126
} catch (AlreadyClosedException alreadyClosedException) {
127-
log.warn("can not rebuild indies for dir has closed.");
127+
log.warn("can not update indies for dir has closed.");
128128
}
129129
}
130130

@@ -134,22 +134,25 @@ public void rebuild(List<SubjectDoc> subjectDocs) throws IOException {
134134
writeConfig.setOpenMode(CREATE_OR_APPEND);
135135
try (var writer = new IndexWriter(subjectIndexDir, writeConfig)) {
136136
writer.deleteAll();
137+
if (log.isDebugEnabled()) {
138+
log.debug("Delete all before rebuild documents.");
139+
}
137140
subjectDocs.forEach(subjectDoc -> {
138141
var doc = this.convert(subjectDoc);
139142
try {
140143
var seqNum = writer.updateDocument(new Term(SubjectHint.ID_FIELD,
141144
String.valueOf(subjectDoc.getId())),
142145
doc);
143146
if (log.isDebugEnabled()) {
144-
log.debug("Updated document({}) with sequence number {} returned",
147+
log.debug("Rebuild document({}) with sequence number {} returned",
145148
subjectDoc.getName(), seqNum);
146149
}
147150
} catch (Exception e) {
148151
throw Exceptions.propagate(e);
149152
}
150153
});
151154
} catch (AlreadyClosedException alreadyClosedException) {
152-
log.warn("can not rebuild indies for dir has closed.");
155+
log.warn("Can not rebuild indies for dir has closed.");
153156
}
154157
}
155158

@@ -193,6 +196,15 @@ private Document convert(SubjectDoc subjectDoc) {
193196
if (StringUtils.hasText(subjectDoc.getCover())) {
194197
doc.add(new TextField("cover", subjectDoc.getCover(), YES));
195198
}
199+
var tags = "";
200+
if (subjectDoc.getTags() != null && !subjectDoc.getTags().isEmpty()) {
201+
StringBuilder sb = new StringBuilder(tags);
202+
for (String tag : subjectDoc.getTags()) {
203+
sb.append(stripToEmpty(tag)).append(SPACE);
204+
doc.add(new StringField("tag", tag, YES));
205+
}
206+
tags = sb.toString();
207+
}
196208
doc.add(new StringField("nsfw", String.valueOf(subjectDoc.getNsfw()), YES));
197209
doc.add(new StringField("type", String.valueOf(subjectDoc.getType()), YES));
198210
doc.add(new StringField("airTime", formatTimestamp(subjectDoc.getAirTime()), YES));
@@ -205,7 +217,8 @@ private Document convert(SubjectDoc subjectDoc) {
205217
+ stripToEmpty(subjectDoc.getSummary()) + SPACE
206218
+ subjectDoc.getNsfw() + SPACE
207219
+ subjectDoc.getType() + SPACE
208-
+ formatTimestamp(subjectDoc.getAirTime()) + SPACE,
220+
+ formatTimestamp(subjectDoc.getAirTime()) + SPACE
221+
+ tags + SPACE,
209222
Safelist.none());
210223
doc.add(new StoredField("content", content));
211224
doc.add(new TextField("searchable", subjectDoc.getName() + content, NO));

server/src/main/java/run/ikaros/server/store/repository/TagRepository.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package run.ikaros.server.store.repository;
22

33
import org.springframework.data.r2dbc.repository.R2dbcRepository;
4+
import reactor.core.publisher.Flux;
45
import reactor.core.publisher.Mono;
56
import run.ikaros.api.store.enums.TagType;
67
import run.ikaros.server.store.entity.TagEntity;
78

89
public interface TagRepository
910
extends R2dbcRepository<TagEntity, Long> {
1011

12+
Flux<TagEntity> findAllByTypeAndMasterId(TagType type, Long masterId);
13+
1114
Mono<TagEntity> findByTypeAndMasterIdAndName(TagType type, Long masterId, String name);
1215

1316
Mono<Boolean> existsByTypeAndMasterIdAndName(TagType type, Long masterId, String name);

0 commit comments

Comments
 (0)