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: subject tag indices and event listener. #738

Merged
merged 5 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .run/RunAppOnWindows.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="RunAppOnWindows" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="run.ikaros.server.IkarosApplication" />
<module name="ikaros.server.main" />
<option name="VM_PARAMETERS" value="-Dspring.profiles.active=dev,win-dev,local" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="run.ikaros.server.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
2 changes: 1 addition & 1 deletion BUILD.MD
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ commit之前,用checkstyle检查下代码,确认没有问题后再commit。

## 本地开发

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

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

Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.MD
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

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

# 0.20.5

# 新特性

- 将条目标签加入条目全局搜索引擎的索引里
- 条目标签更新事件监听,重建对应条目索引

# 0.20.4

## 优化
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package run.ikaros.api.search.subject;

import java.util.List;
import lombok.Data;
import run.ikaros.api.store.enums.SubjectType;

Expand All @@ -14,4 +15,5 @@ public class SubjectDoc {
private Boolean nsfw;
private Long airTime;
private String cover;
private List<String> tags;
}
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
version=0.20.4
version=0.20.5
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.domain.Sort;
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
import org.springframework.data.relational.core.query.Criteria;
Expand All @@ -18,6 +19,8 @@
import run.ikaros.api.infra.exception.NotFoundException;
import run.ikaros.api.infra.utils.StringUtils;
import run.ikaros.api.store.enums.TagType;
import run.ikaros.server.core.tag.event.TagCreateEvent;
import run.ikaros.server.core.tag.event.TagRemoveEvent;
import run.ikaros.server.store.entity.TagEntity;
import run.ikaros.server.store.repository.TagRepository;

Expand All @@ -26,14 +29,17 @@
public class DefaultTagService implements TagService {
private final TagRepository tagRepository;
private final R2dbcEntityTemplate r2dbcEntityTemplate;
private final ApplicationEventPublisher eventPublisher;

/**
* Construct.
*/
public DefaultTagService(TagRepository tagRepository,
R2dbcEntityTemplate r2dbcEntityTemplate) {
R2dbcEntityTemplate r2dbcEntityTemplate,
ApplicationEventPublisher eventPublisher) {
this.tagRepository = tagRepository;
this.r2dbcEntityTemplate = r2dbcEntityTemplate;
this.eventPublisher = eventPublisher;
}

@Override
Expand Down Expand Up @@ -102,6 +108,8 @@ public Mono<Tag> create(Tag tag) {
.filter(exists -> !exists)
.flatMap(exists -> copyProperties(tag, new TagEntity()))
.flatMap(tagRepository::save)
.doOnSuccess(savedTag ->
eventPublisher.publishEvent(new TagCreateEvent(this, savedTag)))
.flatMap(tagEntity -> copyProperties(tagEntity, tag));
}

Expand All @@ -120,7 +128,9 @@ public Mono<Void> removeById(Long tagId) {
Assert.isTrue(tagId >= 0, "'tagId' must >=0.");
return tagRepository.findById(tagId)
.switchIfEmpty(Mono.error(new NotFoundException("Tag not found for id = " + tagId)))
.map(TagEntity::getId)
.flatMap(tagRepository::deleteById);
.flatMap(tagEntity -> tagRepository.deleteById(tagEntity.getId())
.doOnSuccess(unused ->
eventPublisher.publishEvent(new TagRemoveEvent(this, tagEntity)))
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package run.ikaros.server.core.tag.event;

import java.time.Clock;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
import run.ikaros.server.store.entity.TagEntity;

@Getter
public class TagChangeEvent extends ApplicationEvent {
private final TagEntity entity;

public TagChangeEvent(Object source, TagEntity entity) {
super(source);
this.entity = entity;
}

public TagChangeEvent(Object source, Clock clock, TagEntity entity) {
super(source, clock);
this.entity = entity;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package run.ikaros.server.core.tag.event;

import java.time.Clock;
import lombok.Getter;
import run.ikaros.server.store.entity.TagEntity;

@Getter
public class TagCreateEvent extends TagChangeEvent {

public TagCreateEvent(Object source, TagEntity entity) {
super(source, entity);
}

public TagCreateEvent(Object source, Clock clock, TagEntity entity) {
super(source, clock, entity);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package run.ikaros.server.core.tag.event;

import java.time.Clock;
import lombok.Getter;
import run.ikaros.server.store.entity.TagEntity;

@Getter
public class TagRemoveEvent extends TagChangeEvent {

public TagRemoveEvent(Object source, TagEntity entity) {
super(source, entity);
}

public TagRemoveEvent(Object source, Clock clock, TagEntity entity) {
super(source, clock, entity);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package run.ikaros.server.core.tag.listener;

import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import run.ikaros.api.search.subject.SubjectSearchService;
import run.ikaros.api.store.enums.TagType;
import run.ikaros.server.core.tag.event.TagChangeEvent;
import run.ikaros.server.search.IndicesProperties;
import run.ikaros.server.search.subject.ReactiveSubjectDocConverter;
import run.ikaros.server.store.entity.TagEntity;
import run.ikaros.server.store.repository.SubjectRepository;
import run.ikaros.server.store.repository.TagRepository;

@Slf4j
@Component
public class TagChangeEventListener {

private final IndicesProperties indicesProperties;
private final SubjectRepository subjectRepository;
private final TagRepository tagRepository;
private final SubjectSearchService subjectSearchService;

/**
* Construct.
*/
public TagChangeEventListener(IndicesProperties indicesProperties,
SubjectRepository subjectRepository, TagRepository tagRepository,
SubjectSearchService subjectSearchService) {
this.indicesProperties = indicesProperties;
this.subjectRepository = subjectRepository;
this.tagRepository = tagRepository;
this.subjectSearchService = subjectSearchService;
}

/**
* 条目对应的标签新增或删除了,则更新对应的索引文档.
*/
@EventListener(TagChangeEvent.class)
public Mono<Void> onTagCreateEvent(TagChangeEvent event) {
if (!indicesProperties.getInitializer().isEnabled()) {
return Mono.empty();
}
TagEntity tagEntity = event.getEntity();
if (tagEntity.getType() != TagType.SUBJECT) {
return Mono.empty();
}
Long subjectId = tagEntity.getMasterId();
return subjectRepository.findById(subjectId)
.flatMap(ReactiveSubjectDocConverter::fromEntity)
.flatMap(subjectDoc ->
tagRepository.findAllByTypeAndMasterId(TagType.SUBJECT, subjectDoc.getId())
.map(TagEntity::getName).collectList()
.map(tags -> {
subjectDoc.setTags(tags);
return subjectDoc;
})
).doOnSuccess(subjectDoc -> {
try {
subjectSearchService.updateDocument(List.of(subjectDoc));
} catch (Exception e) {
throw new RuntimeException(e);
}
}).then();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,30 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import run.ikaros.api.search.subject.SubjectDoc;
import run.ikaros.api.search.subject.SubjectSearchService;
import run.ikaros.api.store.enums.TagType;
import run.ikaros.server.search.subject.ReactiveSubjectDocConverter;
import run.ikaros.server.store.entity.TagEntity;
import run.ikaros.server.store.repository.SubjectRepository;
import run.ikaros.server.store.repository.TagRepository;

@Slf4j
@Service
public class IndicesServiceImpl implements IndicesService {

private final SubjectRepository subjectRepository;
private final TagRepository tagRepository;
private final SubjectSearchService subjectSearchService;

/**
* Construct.
*/
public IndicesServiceImpl(
SubjectRepository subjectRepository,
SubjectRepository subjectRepository, TagRepository tagRepository,
SubjectSearchService subjectSearchService) {
this.subjectRepository = subjectRepository;
this.tagRepository = tagRepository;
this.subjectSearchService = subjectSearchService;
}

Expand All @@ -29,6 +35,7 @@ public IndicesServiceImpl(
public Mono<Void> rebuildSubjectIndices() {
return subjectRepository.findAll()
.flatMap(ReactiveSubjectDocConverter::fromEntity)
.flatMap(this::fetchSubTags)
.collectList()
.handle((subjectDocs, sink) -> {
try {
Expand All @@ -39,4 +46,14 @@ public Mono<Void> rebuildSubjectIndices() {
})
.then();
}

private Mono<SubjectDoc> fetchSubTags(SubjectDoc subjectDoc) {
return tagRepository.findAllByTypeAndMasterId(TagType.SUBJECT, subjectDoc.getId())
.map(TagEntity::getName)
.collectList()
.map(tags -> {
subjectDoc.setTags(tags);
return subjectDoc;
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public void updateDocument(List<SubjectDoc> subjectDocs) throws Exception {
}
});
} catch (AlreadyClosedException alreadyClosedException) {
log.warn("can not rebuild indies for dir has closed.");
log.warn("can not update indies for dir has closed.");
}
}

Expand All @@ -134,22 +134,25 @@ public void rebuild(List<SubjectDoc> subjectDocs) throws IOException {
writeConfig.setOpenMode(CREATE_OR_APPEND);
try (var writer = new IndexWriter(subjectIndexDir, writeConfig)) {
writer.deleteAll();
if (log.isDebugEnabled()) {
log.debug("Delete all before rebuild documents.");
}
subjectDocs.forEach(subjectDoc -> {
var doc = this.convert(subjectDoc);
try {
var seqNum = writer.updateDocument(new Term(SubjectHint.ID_FIELD,
String.valueOf(subjectDoc.getId())),
doc);
if (log.isDebugEnabled()) {
log.debug("Updated document({}) with sequence number {} returned",
log.debug("Rebuild document({}) with sequence number {} returned",
subjectDoc.getName(), seqNum);
}
} catch (Exception e) {
throw Exceptions.propagate(e);
}
});
} catch (AlreadyClosedException alreadyClosedException) {
log.warn("can not rebuild indies for dir has closed.");
log.warn("Can not rebuild indies for dir has closed.");
}
}

Expand Down Expand Up @@ -193,6 +196,15 @@ private Document convert(SubjectDoc subjectDoc) {
if (StringUtils.hasText(subjectDoc.getCover())) {
doc.add(new TextField("cover", subjectDoc.getCover(), YES));
}
var tags = "";
if (subjectDoc.getTags() != null && !subjectDoc.getTags().isEmpty()) {
StringBuilder sb = new StringBuilder(tags);
for (String tag : subjectDoc.getTags()) {
sb.append(stripToEmpty(tag)).append(SPACE);
doc.add(new StringField("tag", tag, YES));
}
tags = sb.toString();
}
doc.add(new StringField("nsfw", String.valueOf(subjectDoc.getNsfw()), YES));
doc.add(new StringField("type", String.valueOf(subjectDoc.getType()), YES));
doc.add(new StringField("airTime", formatTimestamp(subjectDoc.getAirTime()), YES));
Expand All @@ -205,7 +217,8 @@ private Document convert(SubjectDoc subjectDoc) {
+ stripToEmpty(subjectDoc.getSummary()) + SPACE
+ subjectDoc.getNsfw() + SPACE
+ subjectDoc.getType() + SPACE
+ formatTimestamp(subjectDoc.getAirTime()) + SPACE,
+ formatTimestamp(subjectDoc.getAirTime()) + SPACE
+ tags + SPACE,
Safelist.none());
doc.add(new StoredField("content", content));
doc.add(new TextField("searchable", subjectDoc.getName() + content, NO));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package run.ikaros.server.store.repository;

import org.springframework.data.r2dbc.repository.R2dbcRepository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import run.ikaros.api.store.enums.TagType;
import run.ikaros.server.store.entity.TagEntity;

public interface TagRepository
extends R2dbcRepository<TagEntity, Long> {

Flux<TagEntity> findAllByTypeAndMasterId(TagType type, Long masterId);

Mono<TagEntity> findByTypeAndMasterIdAndName(TagType type, Long masterId, String name);

Mono<Boolean> existsByTypeAndMasterIdAndName(TagType type, Long masterId, String name);
Expand Down
Loading