From 76c6da9429c635cbe976524428704958c6ab558d Mon Sep 17 00:00:00 2001 From: ChiveHao Date: Mon, 11 Nov 2024 05:47:58 +0800 Subject: [PATCH] feat: add EpisodeGetRecordsBySubjectId #730 (#732) * feat: add EpisodeGetRecordsBySubjectId #730 * build: gen new api-client v20.1.0 * fix: console SubjectDetails.vue episode and resource get by records. --- CHANGELOG.MD | 4 + .../api/core/subject/EpisodeRecord.java | 12 ++ console/packages/api-client/README.md | 2 +- console/packages/api-client/package.json | 2 +- .../api-client/src/.openapi-generator/FILES | 1 + .../src/api/v1alpha1-episode-api.ts | 124 ++++++++++++++++++ .../api-client/src/models/episode-record.ts | 40 ++++++ .../packages/api-client/src/models/index.ts | 1 + .../api-client/src/models/subject-hint.ts | 8 +- .../content/subject/SubjectDetails.vue | 96 +++++++------- .../run/ikaros/server/cache/CacheAspect.java | 6 +- .../cache/annotation/FluxCacheEvict.java | 1 + .../cache/annotation/MonoCacheEvict.java | 3 +- .../cache/annotation/MonoCacheable.java | 4 +- .../core/episode/DefaultEpisodeService.java | 15 ++- .../server/core/episode/EpisodeEndpoint.java | 23 +++- .../server/core/episode/EpisodeService.java | 3 + .../service/impl/SubjectServiceImpl.java | 3 + .../service/impl/SubjectSyncServiceImpl.java | 14 +- 19 files changed, 294 insertions(+), 68 deletions(-) create mode 100644 api/src/main/java/run/ikaros/api/core/subject/EpisodeRecord.java create mode 100644 console/packages/api-client/src/models/episode-record.ts diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 2862f7cf1..126208bb9 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -4,6 +4,10 @@ # 0.20.2 +## 新特性 + +- 添加接口 EpisodeGetRecordsBySubjectId #730 + ## 问题修复 - ObjectMapper的默认日期转化问题 #729 diff --git a/api/src/main/java/run/ikaros/api/core/subject/EpisodeRecord.java b/api/src/main/java/run/ikaros/api/core/subject/EpisodeRecord.java new file mode 100644 index 000000000..fdb88062a --- /dev/null +++ b/api/src/main/java/run/ikaros/api/core/subject/EpisodeRecord.java @@ -0,0 +1,12 @@ +package run.ikaros.api.core.subject; + +import java.util.List; + +/** + * 剧集相关数据的组合,使用复杂的数据,会增加单个接口的耗时,但对整个条目来说,能有效降低并发请求次数. + * + * @param episode 剧集 + * @param resources 剧集附件资源集合 + */ +public record EpisodeRecord(Episode episode, List resources) { +} diff --git a/console/packages/api-client/README.md b/console/packages/api-client/README.md index acfaebf46..208dfb68f 100644 --- a/console/packages/api-client/README.md +++ b/console/packages/api-client/README.md @@ -58,7 +58,7 @@ npm publish 选择当前目录下的更改进行`git add .` ```bash -git commit -am "build: gen new api-client v18.1.0" +git commit -am "build: gen new api-client v20.1.0" ``` 合成版(powershell),升级 package.json 版本,并启动服务端后,在 api-client 路径下: diff --git a/console/packages/api-client/package.json b/console/packages/api-client/package.json index 01614ae2d..a7b0eee36 100644 --- a/console/packages/api-client/package.json +++ b/console/packages/api-client/package.json @@ -1,6 +1,6 @@ { "name": "@runikaros/api-client", - "version": "18.1.0", + "version": "20.1.0", "description": "Project ikaros console api-client package", "type": "module", "scripts": { diff --git a/console/packages/api-client/src/.openapi-generator/FILES b/console/packages/api-client/src/.openapi-generator/FILES index 87f147f07..168e2e071 100644 --- a/console/packages/api-client/src/.openapi-generator/FILES +++ b/console/packages/api-client/src/.openapi-generator/FILES @@ -43,6 +43,7 @@ models/batch-matching-subject-episodes-attachment.ts models/config-map.ts models/create-user-req-params.ts models/episode-collection.ts +models/episode-record.ts models/episode-resource.ts models/episode.ts models/index.ts diff --git a/console/packages/api-client/src/api/v1alpha1-episode-api.ts b/console/packages/api-client/src/api/v1alpha1-episode-api.ts index a1c3eb845..c3946194f 100644 --- a/console/packages/api-client/src/api/v1alpha1-episode-api.ts +++ b/console/packages/api-client/src/api/v1alpha1-episode-api.ts @@ -40,6 +40,8 @@ import { // @ts-ignore import { Episode } from "../models"; // @ts-ignore +import { EpisodeRecord } from "../models"; +// @ts-ignore import { EpisodeResource } from "../models"; /** * V1alpha1EpisodeApi - axios parameter creator @@ -463,6 +465,60 @@ export const V1alpha1EpisodeApiAxiosParamCreator = function ( options: localVarRequestOptions, }; }, + /** + * Get episode records by subject id. + * @param {number} id Subject id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getRecordsBySubjectId: async ( + id: number, + options: AxiosRequestConfig = {} + ): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists("getRecordsBySubjectId", "id", id); + const localVarPath = + `/api/v1alpha1/episode/records/subjectId/{id}`.replace( + `{${"id"}}`, + encodeURIComponent(String(id)) + ); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { + method: "GET", + ...baseOptions, + ...options, + }; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication BasicAuth required + // http basic authentication required + setBasicAuthToObject(localVarRequestOptions, configuration); + + // authentication BearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration); + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = + baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = { + ...localVarHeaderParameter, + ...headersFromBaseOptions, + ...options.headers, + }; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * Post episode. * @param {Episode} [episode] Episode @@ -769,6 +825,30 @@ export const V1alpha1EpisodeApiFp = function (configuration?: Configuration) { configuration ); }, + /** + * Get episode records by subject id. + * @param {number} id Subject id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getRecordsBySubjectId( + id: number, + options?: AxiosRequestConfig + ): Promise< + ( + axios?: AxiosInstance, + basePath?: string + ) => AxiosPromise> + > { + const localVarAxiosArgs = + await localVarAxiosParamCreator.getRecordsBySubjectId(id, options); + return createRequestFunction( + localVarAxiosArgs, + globalAxios, + BASE_PATH, + configuration + ); + }, /** * Post episode. * @param {Episode} [episode] Episode @@ -933,6 +1013,20 @@ export const V1alpha1EpisodeApiFactory = function ( .getCountTotalBySubjectId(requestParameters.id, options) .then((request) => request(axios, basePath)); }, + /** + * Get episode records by subject id. + * @param {V1alpha1EpisodeApiGetRecordsBySubjectIdRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getRecordsBySubjectId( + requestParameters: V1alpha1EpisodeApiGetRecordsBySubjectIdRequest, + options?: AxiosRequestConfig + ): AxiosPromise> { + return localVarFp + .getRecordsBySubjectId(requestParameters.id, options) + .then((request) => request(axios, basePath)); + }, /** * Post episode. * @param {V1alpha1EpisodeApiPostEpisodeRequest} requestParameters Request parameters. @@ -1100,6 +1194,20 @@ export interface V1alpha1EpisodeApiGetCountTotalBySubjectIdRequest { readonly id: number; } +/** + * Request parameters for getRecordsBySubjectId operation in V1alpha1EpisodeApi. + * @export + * @interface V1alpha1EpisodeApiGetRecordsBySubjectIdRequest + */ +export interface V1alpha1EpisodeApiGetRecordsBySubjectIdRequest { + /** + * Subject id + * @type {number} + * @memberof V1alpha1EpisodeApiGetRecordsBySubjectId + */ + readonly id: number; +} + /** * Request parameters for postEpisode operation in V1alpha1EpisodeApi. * @export @@ -1253,6 +1361,22 @@ export class V1alpha1EpisodeApi extends BaseAPI { .then((request) => request(this.axios, this.basePath)); } + /** + * Get episode records by subject id. + * @param {V1alpha1EpisodeApiGetRecordsBySubjectIdRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof V1alpha1EpisodeApi + */ + public getRecordsBySubjectId( + requestParameters: V1alpha1EpisodeApiGetRecordsBySubjectIdRequest, + options?: AxiosRequestConfig + ) { + return V1alpha1EpisodeApiFp(this.configuration) + .getRecordsBySubjectId(requestParameters.id, options) + .then((request) => request(this.axios, this.basePath)); + } + /** * Post episode. * @param {V1alpha1EpisodeApiPostEpisodeRequest} requestParameters Request parameters. diff --git a/console/packages/api-client/src/models/episode-record.ts b/console/packages/api-client/src/models/episode-record.ts new file mode 100644 index 000000000..2af29d28e --- /dev/null +++ b/console/packages/api-client/src/models/episode-record.ts @@ -0,0 +1,40 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Ikaros Open API Documentation + * Documentation for Ikaros Open API + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +// May contain unused imports in some cases +// @ts-ignore +import { Episode } from "./episode"; +// May contain unused imports in some cases +// @ts-ignore +import { EpisodeResource } from "./episode-resource"; + +/** + * + * @export + * @interface EpisodeRecord + */ +export interface EpisodeRecord { + /** + * + * @type {Episode} + * @memberof EpisodeRecord + */ + episode?: Episode; + /** + * + * @type {Array} + * @memberof EpisodeRecord + */ + resources?: Array; +} diff --git a/console/packages/api-client/src/models/index.ts b/console/packages/api-client/src/models/index.ts index daaea344f..7f32fa357 100644 --- a/console/packages/api-client/src/models/index.ts +++ b/console/packages/api-client/src/models/index.ts @@ -10,6 +10,7 @@ export * from "./config-map"; export * from "./create-user-req-params"; export * from "./episode"; export * from "./episode-collection"; +export * from "./episode-record"; export * from "./episode-resource"; export * from "./jwt-apply-param"; export * from "./link"; diff --git a/console/packages/api-client/src/models/subject-hint.ts b/console/packages/api-client/src/models/subject-hint.ts index 4a69f7488..c4e907242 100644 --- a/console/packages/api-client/src/models/subject-hint.ts +++ b/console/packages/api-client/src/models/subject-hint.ts @@ -60,18 +60,12 @@ export interface SubjectHint { * @memberof SubjectHint */ type?: SubjectHintTypeEnum; - /** - * - * @type {number} - * @memberof SubjectHint - */ - airTime?: number; /** * * @type {string} * @memberof SubjectHint */ - cover?: string; + airTime?: string; } export const SubjectHintTypeEnum = { diff --git a/console/src/modules/content/subject/SubjectDetails.vue b/console/src/modules/content/subject/SubjectDetails.vue index 0c3074a41..e4a5a24bb 100644 --- a/console/src/modules/content/subject/SubjectDetails.vue +++ b/console/src/modules/content/subject/SubjectDetails.vue @@ -5,6 +5,7 @@ import { Episode, EpisodeCollection, EpisodeGroupEnum, + EpisodeRecord, EpisodeResource, Subject, SubjectCollection, @@ -78,8 +79,9 @@ const subject = ref({ name_cn: '', }); -const episodes = ref([]); -const episodeResources = ref([]); +const episodeRecords = ref([]) +// const episodes = ref([]); +// const episodeResources = ref([]); // eslint-disable-next-line no-unused-vars const fetchSubjectById = async () => { @@ -87,49 +89,49 @@ const fetchSubjectById = async () => { subject.value = await subjectStore.fetchSubjectById( subject.value.id as number ); - await fetchEpisodes(); - await fetchEpisodeResources(); + await fetchEpisodeRecords(); await fetchSubjectSyncs(); } }; -const fetchEpisodes = async () => { - const { data } = await apiClient.episode.getAllBySubjectId({ +const fetchEpisodeRecords = async () => { + const { data } = await apiClient.episode.getRecordsBySubjectId({ id: subject.value.id as number, }); - episodes.value = data; - if (!episodes.value || episodes.value.length === 0) { + episodeRecords.value = data; + if (!episodeRecords.value || episodeRecords.value.length === 0) { batchMatchingSubjectButtonDisable.value = true; deleteMatchingSubjectButtonDisable.value = true; } else { batchMatchingSubjectButtonDisable.value = false; deleteMatchingSubjectButtonDisable.value = false; } - if (episodes.value) { - episodes.value = episodes.value.sort( - (a, b) => (a.sequence ?? 0) - (b.sequence ?? 0) + + if (episodeRecords.value) { + episodeRecords.value = episodeRecords.value.sort( + (a, b) => (a.episode?.sequence ?? 0) - (b.episode?.sequence ?? 0) ); loadEpisodeGroupLabels(); } - // console.debug('episodes', episodes.value); -}; +} -const fetchEpisodeResources = async () => { - // console.debug('episodes', episodes.value); - episodeResources.value = []; - episodes.value.forEach(async (episode) => { - var ep = episode as Episode; - // console.debug('ep', ep); - if (ep.id) { - const { data } = await apiClient.episode.getAttachmentRefsById({ - id: ep.id as number, - }); - data.forEach((res) => { - if (res) episodeResources.value.push(res); - }); - } - }); -}; + +// const fetchEpisodeResources = async () => { +// // console.debug('episodes', episodes.value); +// episodeResources.value = []; +// episodes.value.forEach(async (episode) => { +// var ep = episode as Episode; +// // console.debug('ep', ep); +// if (ep.id) { +// const { data } = await apiClient.episode.getAttachmentRefsById({ +// id: ep.id as number, +// }); +// data.forEach((res) => { +// if (res) episodeResources.value.push(res); +// }); +// } +// }); +// }; // eslint-disable-next-line no-unused-vars const infoMap = ref>(); @@ -190,16 +192,14 @@ watch(subject, () => { // eslint-disable-next-line no-unused-vars const airTimeDateFormatter = (row) => { // console.log('row', row); - return formatDate(new Date(row.air_time), 'yyyy-MM-dd'); + return formatDate(new Date(row.episode.air_time), 'yyyy-MM-dd'); }; const currentEpisode = ref(); -const showEpisodeDetails = (ep: Episode) => { - currentEpisode.value = ep; +const showEpisodeDetails = (record: EpisodeRecord) => { + currentEpisode.value = record.episode; episodeDetailsDialogVisible.value = true; - const resources: EpisodeResource[] = episodeResources.value.filter( - (e) => e.episodeId === ep.id - ); + const resources: EpisodeResource[] = record.resources ?? []; episodeHasMultiResource.value = (resources && resources.length > 1) as boolean; }; @@ -557,11 +557,11 @@ const batchCancenMatchingSubjectButtonLoading = ref(false); const deleteBatchingAttachments = async () => { // console.debug('deleteBatchingAttachments subject episodes', subject.value.episodes) batchCancenMatchingSubjectButtonLoading.value = true; - await episodes.value?.forEach(async (ep) => { + await episodeRecords.value?.forEach(async (record) => { await apiClient.attachmentRef.removeAllByTypeAndReferenceId({ attachmentReference: { type: 'EPISODE', - referenceId: ep.id, + referenceId: record.episode?.id, }, }); }); @@ -572,10 +572,8 @@ const deleteBatchingAttachments = async () => { fetchDatas(); }; -const isEpisodeBindResource = (episode: Episode): boolean | undefined => { - const resources: EpisodeResource[] = episodeResources.value.filter( - (e) => e.episodeId === episode.id - ); +const isEpisodeBindResource = (record: EpisodeRecord): boolean | undefined => { + const resources: EpisodeResource[] = record.resources ?? []; // console.debug('episodeResources.value', episodeResources.value); // console.debug('episode', episode); // console.debug('resources', resources); @@ -607,8 +605,8 @@ const doPushEpGroupItems = ( const loadEpisodeGroupLabels = () => { const groupCountMap = new Map(); console.debug('subject', subject.value); - episodes.value?.forEach((ep) => { - const epGroup = ep.group as EpisodeGroupEnum; + episodeRecords.value?.forEach((record) => { + const epGroup = record.episode?.group as EpisodeGroupEnum; if (groupCountMap.get(epGroup)) { let count = groupCountMap.get(epGroup) as number; count++; @@ -910,26 +908,26 @@ onMounted(fetchDatas); :label="item.label" > @@ -1076,7 +1074,7 @@ onMounted(fetchDatas); v-model:visible="episodeDetailsDialogVisible" v-model:ep="currentEpisode" v-model:multiResource="episodeHasMultiResource" - @close="fetchEpisodeResources" + @close="fetchEpisodeRecords" @removeEpisodeFilesBind="fetchSubjectById" /> diff --git a/server/src/main/java/run/ikaros/server/cache/CacheAspect.java b/server/src/main/java/run/ikaros/server/cache/CacheAspect.java index 0c9475b6d..76a298fb5 100644 --- a/server/src/main/java/run/ikaros/server/cache/CacheAspect.java +++ b/server/src/main/java/run/ikaros/server/cache/CacheAspect.java @@ -39,11 +39,13 @@ public void monoCacheableMethods() { public void fluxCacheableMethods() { } - @Pointcut("@annotation(run.ikaros.server.cache.annotation.MonoCacheEvict)") + @Pointcut("@annotation(run.ikaros.server.cache.annotation.MonoCacheEvict) " + + "&& execution(public reactor.core.publisher.Mono *(..))") public void monoCacheEvictMethods() { } - @Pointcut("@annotation(run.ikaros.server.cache.annotation.FluxCacheEvict)") + @Pointcut("@annotation(run.ikaros.server.cache.annotation.FluxCacheEvict) " + + "&& execution(public reactor.core.publisher.Flux *(..))") public void fluxCacheEvictMethods() { } diff --git a/server/src/main/java/run/ikaros/server/cache/annotation/FluxCacheEvict.java b/server/src/main/java/run/ikaros/server/cache/annotation/FluxCacheEvict.java index 1581f19c2..ca4e5fe21 100644 --- a/server/src/main/java/run/ikaros/server/cache/annotation/FluxCacheEvict.java +++ b/server/src/main/java/run/ikaros/server/cache/annotation/FluxCacheEvict.java @@ -12,6 +12,7 @@ /** * 当 value或者cacheNames 和 key 啥都不填,代表清空缓存 + * 目前不能和事务注解共用 * . * * @see CacheAspect#fluxCacheEvictMethods() diff --git a/server/src/main/java/run/ikaros/server/cache/annotation/MonoCacheEvict.java b/server/src/main/java/run/ikaros/server/cache/annotation/MonoCacheEvict.java index 10daddbf7..81958e965 100644 --- a/server/src/main/java/run/ikaros/server/cache/annotation/MonoCacheEvict.java +++ b/server/src/main/java/run/ikaros/server/cache/annotation/MonoCacheEvict.java @@ -11,7 +11,8 @@ import run.ikaros.server.cache.CacheAspect; /** - * 当 value或者cacheNames 和 key 啥都不填,代表清空缓存 + * 当 value或者cacheNames 和 key 啥都不填,代表清空缓存, + * 目前不能和事务注解共用 * . * * @see CacheAspect#monoCacheEvictMethods() diff --git a/server/src/main/java/run/ikaros/server/cache/annotation/MonoCacheable.java b/server/src/main/java/run/ikaros/server/cache/annotation/MonoCacheable.java index c72e4d099..8295d8b66 100644 --- a/server/src/main/java/run/ikaros/server/cache/annotation/MonoCacheable.java +++ b/server/src/main/java/run/ikaros/server/cache/annotation/MonoCacheable.java @@ -10,7 +10,9 @@ import org.springframework.core.annotation.AliasFor; /** - * 数字类型返回值,统一用 Mono Long 接收. + * 数字类型返回值,统一用 Mono Long 接收 + * 目前不能和事务注解共用 + * . */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) diff --git a/server/src/main/java/run/ikaros/server/core/episode/DefaultEpisodeService.java b/server/src/main/java/run/ikaros/server/core/episode/DefaultEpisodeService.java index 62ed64366..d5e850a12 100644 --- a/server/src/main/java/run/ikaros/server/core/episode/DefaultEpisodeService.java +++ b/server/src/main/java/run/ikaros/server/core/episode/DefaultEpisodeService.java @@ -11,6 +11,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import run.ikaros.api.core.subject.Episode; +import run.ikaros.api.core.subject.EpisodeRecord; import run.ikaros.api.core.subject.EpisodeResource; import run.ikaros.api.infra.utils.ReflectUtils; import run.ikaros.api.store.enums.EpisodeGroup; @@ -49,7 +50,6 @@ public DefaultEpisodeService(EpisodeRepository episodeRepository, @Override - @MonoCacheEvict public Mono save(Episode episode) { Assert.notNull(episode, "episode must not be null"); Long episodeId = episode.getId(); @@ -81,6 +81,17 @@ public Flux findAllBySubjectId(Long subjectId) { .flatMap(episodeEntity -> copyProperties(episodeEntity, new Episode())); } + @Override + @FluxCacheable(value = "episodeRecords:subjectId:", key = "#subjectId") + public Flux findRecordsBySubjectId(Long subjectId) { + Assert.isTrue(subjectId >= 0, "'subjectId' must >= 0."); + return findAllBySubjectId(subjectId) + .flatMap(episode -> findResourcesById(episode.getId()) + .collectList() + .flatMap(resources -> Mono.just(new EpisodeRecord(episode, resources))) + ); + } + @Override @MonoCacheable(value = "episode:subjectId_group_sequence_name", key = "#subjectId + #group + #sequence + #name") @@ -142,7 +153,7 @@ public Mono countMatchingBySubjectId(Long subjectId) { @Override - @FluxCacheable(value = "episode_resources:episodeId:", key = "#episodeId") + @FluxCacheable(value = "episode_resources:episodeId", key = "#episodeId") public Flux findResourcesById(Long episodeId) { Assert.isTrue(episodeId >= 0, "'episodeId' must >= 0."); return databaseClient.sql("select att_ref.ATTACHMENT_ID as attachment_id, " diff --git a/server/src/main/java/run/ikaros/server/core/episode/EpisodeEndpoint.java b/server/src/main/java/run/ikaros/server/core/episode/EpisodeEndpoint.java index c7bca5a36..4b641e87f 100644 --- a/server/src/main/java/run/ikaros/server/core/episode/EpisodeEndpoint.java +++ b/server/src/main/java/run/ikaros/server/core/episode/EpisodeEndpoint.java @@ -16,6 +16,7 @@ import reactor.core.publisher.Mono; import run.ikaros.api.constant.OpenApiConst; import run.ikaros.api.core.subject.Episode; +import run.ikaros.api.core.subject.EpisodeRecord; import run.ikaros.api.core.subject.EpisodeResource; import run.ikaros.api.infra.utils.StringUtils; import run.ikaros.api.store.enums.EpisodeGroup; @@ -107,7 +108,7 @@ public RouterFunction endpoint() { .response(Builder.responseBuilder().implementation(Episode.class))) .GET("/episodes/subjectId/{id}", this::getAllBySubjectId, - builder -> builder.operationId("getAllBySubjectId") + builder -> builder.operationId("GetAllBySubjectId") .tag(tag).description("Get all by subject id.") .parameter(parameterBuilder() .name("id") @@ -118,6 +119,18 @@ public RouterFunction endpoint() { .response(Builder.responseBuilder().implementationArray(Episode.class)) ) + .GET("/episode/records/subjectId/{id}", this::getRecordsBySubjectId, + builder -> builder.operationId("GetRecordsBySubjectId") + .tag(tag).description("Get episode records by subject id.") + .parameter(parameterBuilder() + .name("id") + .description("Subject id") + .in(ParameterIn.PATH) + .required(true) + .implementation(Long.class)) + .response(Builder.responseBuilder().implementationArray(EpisodeRecord.class)) + ) + .GET("/episode/attachment/refs/{id}", this::getAttachmentRefsById, builder -> builder.operationId("GetAttachmentRefsById") .tag(tag).description("Get attachment refs by episode id.") @@ -225,6 +238,14 @@ private Mono getAllBySubjectId(ServerRequest request) { .flatMap(episodes -> ServerResponse.ok().bodyValue(episodes)); } + private Mono getRecordsBySubjectId(ServerRequest request) { + String id = request.pathVariable("id"); + Long subjectId = Long.valueOf(id); + return episodeService.findRecordsBySubjectId(subjectId) + .collectList() + .flatMap(episodes -> ServerResponse.ok().bodyValue(episodes)); + } + private Mono getAttachmentRefsById(ServerRequest request) { String id = request.pathVariable("id"); Long episodeId = Long.valueOf(id); diff --git a/server/src/main/java/run/ikaros/server/core/episode/EpisodeService.java b/server/src/main/java/run/ikaros/server/core/episode/EpisodeService.java index 02415ae86..0ec90544f 100644 --- a/server/src/main/java/run/ikaros/server/core/episode/EpisodeService.java +++ b/server/src/main/java/run/ikaros/server/core/episode/EpisodeService.java @@ -5,6 +5,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import run.ikaros.api.core.subject.Episode; +import run.ikaros.api.core.subject.EpisodeRecord; import run.ikaros.api.core.subject.EpisodeResource; import run.ikaros.api.store.enums.EpisodeGroup; @@ -16,6 +17,8 @@ public interface EpisodeService { Flux findAllBySubjectId(Long subjectId); + Flux findRecordsBySubjectId(Long subjectId); + Mono findBySubjectIdAndGroupAndSequenceAndName( Long subjectId, EpisodeGroup group, Float sequence, String name); diff --git a/server/src/main/java/run/ikaros/server/core/subject/service/impl/SubjectServiceImpl.java b/server/src/main/java/run/ikaros/server/core/subject/service/impl/SubjectServiceImpl.java index d796815e2..06e7e0c8d 100644 --- a/server/src/main/java/run/ikaros/server/core/subject/service/impl/SubjectServiceImpl.java +++ b/server/src/main/java/run/ikaros/server/core/subject/service/impl/SubjectServiceImpl.java @@ -33,6 +33,7 @@ import run.ikaros.api.store.enums.SubjectSyncPlatform; import run.ikaros.api.store.enums.SubjectType; import run.ikaros.api.wrap.PagingWrap; +import run.ikaros.server.cache.annotation.MonoCacheEvict; import run.ikaros.server.core.subject.event.SubjectAddEvent; import run.ikaros.server.core.subject.event.SubjectRemoveEvent; import run.ikaros.server.core.subject.event.SubjectUpdateEvent; @@ -220,6 +221,7 @@ private Mono updateSubjectSyncEntity(SubjectSyncEntity entity } @Override + @MonoCacheEvict public Mono deleteById(Long id) { Assert.isTrue(id > 0, "'id' must gt 0."); return subjectRepository.existsById(id) @@ -321,6 +323,7 @@ public Mono> listEntitiesByCondition(FindSubjectCondition co } @Override + @MonoCacheEvict public Mono deleteAll() { return subjectRepository.findAll() .map(BaseEntity::getId) diff --git a/server/src/main/java/run/ikaros/server/core/subject/service/impl/SubjectSyncServiceImpl.java b/server/src/main/java/run/ikaros/server/core/subject/service/impl/SubjectSyncServiceImpl.java index 058fadc0d..d31a47aeb 100644 --- a/server/src/main/java/run/ikaros/server/core/subject/service/impl/SubjectSyncServiceImpl.java +++ b/server/src/main/java/run/ikaros/server/core/subject/service/impl/SubjectSyncServiceImpl.java @@ -32,6 +32,7 @@ import run.ikaros.api.infra.exception.subject.NoAvailableSubjectPlatformSynchronizerException; import run.ikaros.api.infra.utils.FileUtils; import run.ikaros.api.store.enums.AttachmentReferenceType; +import run.ikaros.api.store.enums.AttachmentType; import run.ikaros.api.store.enums.SubjectSyncPlatform; import run.ikaros.api.store.enums.TagType; import run.ikaros.server.core.attachment.service.AttachmentService; @@ -49,6 +50,7 @@ import run.ikaros.server.store.entity.SubjectSyncEntity; import run.ikaros.server.store.entity.TagEntity; import run.ikaros.server.store.repository.AttachmentReferenceRepository; +import run.ikaros.server.store.repository.AttachmentRepository; import run.ikaros.server.store.repository.CharacterRepository; import run.ikaros.server.store.repository.EpisodeRepository; import run.ikaros.server.store.repository.PersonRepository; @@ -71,6 +73,7 @@ public class SubjectSyncServiceImpl implements SubjectSyncService, private final SubjectCharacterRepository subjectCharacterRepository; private final PersonRepository personRepository; private final SubjectPersonRepository subjectPersonRepository; + private final AttachmentRepository attachmentRepository; private ApplicationContext applicationContext; private final SubjectSyncRepository subjectSyncRepository; private final AttachmentReferenceRepository attachmentReferenceRepository; @@ -92,7 +95,8 @@ public SubjectSyncServiceImpl(ExtensionComponentsFinder extensionComponentsFinde SubjectPersonRepository subjectPersonRepository, AttachmentReferenceRepository attachmentReferenceRepository, ApplicationEventPublisher applicationEventPublisher, - AttachmentService attachmentService) { + AttachmentService attachmentService, + AttachmentRepository attachmentRepository) { this.extensionComponentsFinder = extensionComponentsFinder; this.subjectService = subjectService; this.subjectSyncRepository = subjectSyncRepository; @@ -106,6 +110,7 @@ public SubjectSyncServiceImpl(ExtensionComponentsFinder extensionComponentsFinde this.attachmentReferenceRepository = attachmentReferenceRepository; this.applicationEventPublisher = applicationEventPublisher; this.attachmentService = attachmentService; + this.attachmentRepository = attachmentRepository; } class SyncTargetExistsException extends RuntimeException { @@ -324,11 +329,14 @@ private Mono downloadCoverAndSaveRef(SubjectEntity entity) { coverFileName = System.currentTimeMillis() + "-" + coverFileName + "." + FileUtils.parseFilePostfix(FileUtils.parseFileName(url)); - return attachmentService.upload(AttachmentUploadCondition.builder() + return attachmentRepository.findByTypeAndParentIdAndName( + AttachmentType.File, COVER_DIRECTORY_ID, coverFileName + ).flatMap(attEnt -> copyProperties(attEnt, new Attachment())) + .switchIfEmpty(attachmentService.upload(AttachmentUploadCondition.builder() .parentId(COVER_DIRECTORY_ID) .name(coverFileName) .dataBufferFlux(Mono.just(dataBufferFactory.wrap(bytes)).flux()) - .build()) + .build())) .flatMap(attachment -> saveCoverAndAttRef(attachment, entity)); }