From e9b6521c89f3e0a11e0becff0cfeefbcc6539817 Mon Sep 17 00:00:00 2001 From: ArgonarioD Date: Fri, 7 Jul 2023 01:42:12 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E4=BA=86=E7=AC=94?= =?UTF-8?q?=E8=AE=B0=E7=9B=B8=E5=85=B3=E7=9A=84=E6=8E=A5=E5=8F=A3=20-=20ge?= =?UTF-8?q?tNoteDetailed=20-=20uploadNote=20-=20updateNote=20-=20deleteNot?= =?UTF-8?q?e=20-=20starNote=20-=20likeNote?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 1 + buildDockerImage.ps1 | 2 +- .../auto/knowledge/config/SerializeConfig.kt | 20 ++++ .../auto/knowledge/config/WebClientConfig.kt | 29 +++++ .../controller/KnowledgeController.kt | 1 + .../knowledge/controller/NoteController.kt | 106 ++++++++++++++++++ .../hfut/auto/knowledge/entity/Knowledge.kt | 4 + .../entity/KnowledgeFileAttribute.kt | 3 + .../cn/edu/hfut/auto/knowledge/entity/Note.kt | 17 +++ .../edu/hfut/auto/knowledge/entity/Notice.kt | 1 + .../cn/edu/hfut/auto/knowledge/entity/Tag.kt | 3 + .../cn/edu/hfut/auto/knowledge/entity/User.kt | 1 + .../hfut/auto/knowledge/entity/vo/NoteVO.kt | 16 +++ .../knowledge/repository/NoteRepository.kt | 7 ++ .../auto/knowledge/service/QueryService.kt | 15 +++ src/main/resources/application-dev.yml | 2 + src/main/resources/application-prod.yml | 2 + 17 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/cn/edu/hfut/auto/knowledge/config/WebClientConfig.kt create mode 100644 src/main/kotlin/cn/edu/hfut/auto/knowledge/controller/NoteController.kt create mode 100644 src/main/kotlin/cn/edu/hfut/auto/knowledge/entity/vo/NoteVO.kt create mode 100644 src/main/kotlin/cn/edu/hfut/auto/knowledge/repository/NoteRepository.kt create mode 100644 src/main/kotlin/cn/edu/hfut/auto/knowledge/service/QueryService.kt diff --git a/build.gradle.kts b/build.gradle.kts index 08bca36..c3487a0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -39,6 +39,7 @@ repositories { dependencies { implementation("org.springframework.boot:spring-boot-starter-validation") implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.boot:spring-boot-starter-webflux") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") // 日志 implementation("org.springframework.boot:spring-boot-starter-log4j2") diff --git a/buildDockerImage.ps1 b/buildDockerImage.ps1 index acbeab1..eb9a6f9 100644 --- a/buildDockerImage.ps1 +++ b/buildDockerImage.ps1 @@ -10,5 +10,5 @@ echo 'saving...' docker save -o ..\docker\aics_main.tar auto/aics_main:latest echo 'compressing...' -Compress-Archive -Path ..\docker\aics_main.tar -DestinationPath ..\docker\aics_main.zip +Compress-Archive -Path ..\docker\aics_main.tar -DestinationPath ..\docker\aics_main.zip -Update pause \ No newline at end of file diff --git a/src/main/kotlin/cn/edu/hfut/auto/knowledge/config/SerializeConfig.kt b/src/main/kotlin/cn/edu/hfut/auto/knowledge/config/SerializeConfig.kt index e4629ae..578c944 100644 --- a/src/main/kotlin/cn/edu/hfut/auto/knowledge/config/SerializeConfig.kt +++ b/src/main/kotlin/cn/edu/hfut/auto/knowledge/config/SerializeConfig.kt @@ -1,5 +1,11 @@ package cn.edu.hfut.auto.knowledge.config +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.JsonDeserializer +import com.fasterxml.jackson.databind.Module +import com.fasterxml.jackson.databind.module.SimpleModule +import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.core.convert.converter.Converter import org.springframework.stereotype.Component @@ -16,4 +22,18 @@ class SerializeConfig { return UUID.fromString(source) } } + + @Bean + fun nullableUUIDModule(): Module { + return SimpleModule().apply { + addDeserializer(UUID::class.java, NullableUUIDJsonDeserializer()) + } + } + + class NullableUUIDJsonDeserializer : JsonDeserializer() { + override fun deserialize(p: JsonParser, ctxt: DeserializationContext): UUID? { + return p.valueAsString?.takeIf { it != "null" }?.let { UUID.fromString(it) } + } + + } } \ No newline at end of file diff --git a/src/main/kotlin/cn/edu/hfut/auto/knowledge/config/WebClientConfig.kt b/src/main/kotlin/cn/edu/hfut/auto/knowledge/config/WebClientConfig.kt new file mode 100644 index 0000000..320df03 --- /dev/null +++ b/src/main/kotlin/cn/edu/hfut/auto/knowledge/config/WebClientConfig.kt @@ -0,0 +1,29 @@ +package cn.edu.hfut.auto.knowledge.config + +import cn.edu.hfut.auto.knowledge.service.QueryService +import com.fasterxml.jackson.databind.ObjectMapper +import org.springframework.beans.factory.config.ConfigurableBeanFactory +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.web.reactive.function.client.WebClient +import org.springframework.web.reactive.function.client.support.WebClientAdapter +import org.springframework.web.service.invoker.HttpServiceProxyFactory +import org.springframework.web.service.invoker.createClient + + +@Configuration +class WebClientConfig { + @Bean + fun webClient(objectMapper: ObjectMapper): WebClient { + return WebClient.builder() + .build() + } + + @Bean + fun queryService(webClient: WebClient, configurableBeanFactory: ConfigurableBeanFactory): QueryService { + val httpServiceProxyFactory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(webClient)) + .embeddedValueResolver(configurableBeanFactory::resolveEmbeddedValue) + .build() + return httpServiceProxyFactory.createClient() + } +} \ No newline at end of file diff --git a/src/main/kotlin/cn/edu/hfut/auto/knowledge/controller/KnowledgeController.kt b/src/main/kotlin/cn/edu/hfut/auto/knowledge/controller/KnowledgeController.kt index abc7e1d..27ce0f1 100644 --- a/src/main/kotlin/cn/edu/hfut/auto/knowledge/controller/KnowledgeController.kt +++ b/src/main/kotlin/cn/edu/hfut/auto/knowledge/controller/KnowledgeController.kt @@ -157,6 +157,7 @@ class KnowledgeController( ?.knowledgeFileAttribute ?.let { attr -> knowledgeFileAttributeRepository.update(new(KnowledgeFileAttribute::class).by { + id = knowledgeId starers = attr.starers.filterNot { it.id == loginUserId } if (active) { starers().addBy { id = loginUserId } diff --git a/src/main/kotlin/cn/edu/hfut/auto/knowledge/controller/NoteController.kt b/src/main/kotlin/cn/edu/hfut/auto/knowledge/controller/NoteController.kt new file mode 100644 index 0000000..9adfb97 --- /dev/null +++ b/src/main/kotlin/cn/edu/hfut/auto/knowledge/controller/NoteController.kt @@ -0,0 +1,106 @@ +package cn.edu.hfut.auto.knowledge.controller + +import cn.edu.hfut.auto.knowledge.entity.* +import cn.edu.hfut.auto.knowledge.entity.vo.NotePutQueryVO +import cn.edu.hfut.auto.knowledge.entity.vo.NoteVO +import cn.edu.hfut.auto.knowledge.exception.BusinessError +import cn.edu.hfut.auto.knowledge.exception.ErrorCode +import cn.edu.hfut.auto.knowledge.repository.NoteRepository +import cn.edu.hfut.auto.knowledge.service.QueryService +import cn.edu.hfut.auto.knowledge.util.getLoginUser +import com.fasterxml.jackson.databind.ObjectMapper +import org.babyfish.jimmer.kt.new +import org.babyfish.jimmer.sql.kt.fetcher.newFetcher +import org.springframework.transaction.annotation.Transactional +import org.springframework.web.bind.annotation.* +import java.time.LocalDateTime +import java.util.* + +@RestController +@RequestMapping("/note") +class NoteController( + private val noteRepository: NoteRepository, + private val queryService: QueryService, + private val objectMapper: ObjectMapper +) { + @GetMapping("/{noteId}") + suspend fun getNoteDetailed(@PathVariable noteId: UUID): Note = + noteRepository.findNullable(noteId, Note.DETAILED_FETCHER) ?: throw BusinessError(ErrorCode.RESOURCE_NOT_FOUND) + + @PostMapping + @Transactional(rollbackFor = [Exception::class]) + suspend fun uploadNote(@RequestBody vo: NoteVO): Note { + val result = noteRepository.insert(new(Note::class).by { + author().id = getLoginUser(objectMapper).id + title = vo.title + createTime = LocalDateTime.now() + updateTime = createTime + pageView = 0 + vo.tags.forEach { + tags().addBy { id = it } + } + vo.linkingKnowledgeFiles.forEach { + knowledgeFiles().addBy { id = it } + } + }) + queryService.putNote(result.id, NotePutQueryVO(result.title, vo.content, vo.tags)) + return result + } + + @PutMapping("/{noteId}") + @Transactional(rollbackFor = [Exception::class]) + suspend fun updateNote(@PathVariable noteId: UUID, @RequestBody vo: NoteVO) { + val result = noteRepository.update(new(Note::class).by { + id = noteId + title = vo.title + createTime = LocalDateTime.now() + pageView = 0 + tags() + vo.tags.forEach { + tags().addBy { id = it } + } + knowledgeFiles() + vo.linkingKnowledgeFiles.forEach { + knowledgeFiles().addBy { id = it } + } + }) + queryService.putNote(result.id, NotePutQueryVO(result.title, vo.content, vo.tags)) + } + + @DeleteMapping("/{noteId}") + suspend fun deleteNote(@PathVariable noteId: UUID) { + noteRepository.deleteById(noteId) + } + + @PutMapping("/{noteId}/star") + suspend fun starNote(@PathVariable noteId: UUID, active: Boolean) { + val loginUserId = getLoginUser(objectMapper).id + noteRepository.findNullable(noteId, newFetcher(Note::class).by { + starers() + })?.let { attr -> + noteRepository.update(new(Note::class).by { + id = noteId + starers = attr.starers.filterNot { it.id == loginUserId } + if (active) { + starers().addBy { id = loginUserId } + } + }) + } ?: throw BusinessError(ErrorCode.RESOURCE_NOT_FOUND) + } + + @PutMapping("/{noteId}/like") + suspend fun likeNote(@PathVariable noteId: UUID, active: Boolean) { + val loginUserId = getLoginUser(objectMapper).id + noteRepository.findNullable(noteId, newFetcher(Note::class).by { + likers() + })?.let { attr -> + noteRepository.update(new(Note::class).by { + id = noteId + likers = attr.likers.filterNot { it.id == loginUserId } + if (active) { + likers().addBy { id = loginUserId } + } + }) + } ?: throw BusinessError(ErrorCode.RESOURCE_NOT_FOUND) + } +} \ No newline at end of file diff --git a/src/main/kotlin/cn/edu/hfut/auto/knowledge/entity/Knowledge.kt b/src/main/kotlin/cn/edu/hfut/auto/knowledge/entity/Knowledge.kt index b8b695d..6d4ea52 100644 --- a/src/main/kotlin/cn/edu/hfut/auto/knowledge/entity/Knowledge.kt +++ b/src/main/kotlin/cn/edu/hfut/auto/knowledge/entity/Knowledge.kt @@ -26,20 +26,24 @@ interface Knowledge { val knowledgeFileAttribute: KnowledgeFileAttribute? companion object { + @JvmField val BRIEF_FETCHER = newFetcher(Knowledge::class).by { allScalarFields() knowledgeFileAttribute(KnowledgeFileAttribute.BRIEF_FETCHER) } + @JvmField val DETAILED_FILE_FETCHER = newFetcher(Knowledge::class).by { allScalarFields() knowledgeFileAttribute(KnowledgeFileAttribute.DETAILED_FETCHER) } + @JvmField val AS_PARENT_FETCHER = newFetcher(Knowledge::class).by { allScalarFields() parent() children(BRIEF_FETCHER) knowledgeFileAttribute(KnowledgeFileAttribute.BRIEF_FETCHER) } + @JvmField val AS_CHILD_FETCHER = newFetcher(Knowledge::class).by { allScalarFields() parent(BRIEF_FETCHER) diff --git a/src/main/kotlin/cn/edu/hfut/auto/knowledge/entity/KnowledgeFileAttribute.kt b/src/main/kotlin/cn/edu/hfut/auto/knowledge/entity/KnowledgeFileAttribute.kt index d87ab04..2526345 100644 --- a/src/main/kotlin/cn/edu/hfut/auto/knowledge/entity/KnowledgeFileAttribute.kt +++ b/src/main/kotlin/cn/edu/hfut/auto/knowledge/entity/KnowledgeFileAttribute.kt @@ -31,11 +31,14 @@ interface KnowledgeFileAttribute { val notes: List companion object { + @JvmField val EXTERNAL_PREVIEWABLE_SUFFIXES = listOf("pdf", "ppt", "pptx", "doc", "docx", "xls", "xlsx") + @JvmField val BRIEF_FETCHER = newFetcher(KnowledgeFileAttribute::class).by { allScalarFields() tags(Tag.ALL_FETCHER) } + @JvmField val DETAILED_FETCHER = newFetcher(KnowledgeFileAttribute::class).by { allScalarFields() starers() diff --git a/src/main/kotlin/cn/edu/hfut/auto/knowledge/entity/Note.kt b/src/main/kotlin/cn/edu/hfut/auto/knowledge/entity/Note.kt index 1c9c080..f01c355 100644 --- a/src/main/kotlin/cn/edu/hfut/auto/knowledge/entity/Note.kt +++ b/src/main/kotlin/cn/edu/hfut/auto/knowledge/entity/Note.kt @@ -11,9 +11,11 @@ interface Note { @Id @GeneratedValue(generatorType = UUIDIdGenerator::class) val id: UUID + @Key val title: String @ManyToOne + @Key val author: User val createTime: LocalDateTime val updateTime: LocalDateTime @@ -39,6 +41,7 @@ interface Note { val likers: List companion object { + @JvmField val BRIEF_FETCHER = newFetcher(Note::class).by { allScalarFields() author(User.BRIEF_FETCHER) @@ -46,5 +49,19 @@ interface Note { starers() likers() } + @JvmField + val DETAILED_FETCHER = newFetcher(Note::class).by { + allScalarFields() + author(User.BRIEF_FETCHER) + tags(Tag.ALL_FETCHER) + knowledgeFiles { + allScalarFields() + starers() + tags(Tag.ALL_FETCHER) + knowledge { allScalarFields() } + } + starers() + likers() + } } } \ No newline at end of file diff --git a/src/main/kotlin/cn/edu/hfut/auto/knowledge/entity/Notice.kt b/src/main/kotlin/cn/edu/hfut/auto/knowledge/entity/Notice.kt index e1534e2..79a80a5 100644 --- a/src/main/kotlin/cn/edu/hfut/auto/knowledge/entity/Notice.kt +++ b/src/main/kotlin/cn/edu/hfut/auto/knowledge/entity/Notice.kt @@ -27,6 +27,7 @@ interface Notice { val targetUser: User companion object { + @JvmField val BRIEF_FETCHER = newFetcher(Notice::class).by { allScalarFields() note { allScalarFields() } diff --git a/src/main/kotlin/cn/edu/hfut/auto/knowledge/entity/Tag.kt b/src/main/kotlin/cn/edu/hfut/auto/knowledge/entity/Tag.kt index 60f0140..5300dff 100644 --- a/src/main/kotlin/cn/edu/hfut/auto/knowledge/entity/Tag.kt +++ b/src/main/kotlin/cn/edu/hfut/auto/knowledge/entity/Tag.kt @@ -4,6 +4,7 @@ import org.babyfish.jimmer.sql.Entity import org.babyfish.jimmer.sql.GeneratedValue import org.babyfish.jimmer.sql.GenerationType import org.babyfish.jimmer.sql.Id +import org.babyfish.jimmer.sql.Key import org.babyfish.jimmer.sql.kt.fetcher.newFetcher @Entity @@ -11,9 +12,11 @@ interface Tag { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long + @Key val name: String companion object { + @JvmField val ALL_FETCHER = newFetcher(Tag::class).by { allScalarFields() } diff --git a/src/main/kotlin/cn/edu/hfut/auto/knowledge/entity/User.kt b/src/main/kotlin/cn/edu/hfut/auto/knowledge/entity/User.kt index de0ca90..b16c9fa 100644 --- a/src/main/kotlin/cn/edu/hfut/auto/knowledge/entity/User.kt +++ b/src/main/kotlin/cn/edu/hfut/auto/knowledge/entity/User.kt @@ -31,6 +31,7 @@ interface User { val starredNotes: List companion object { + @JvmField val BRIEF_FETCHER = newFetcher(User::class).by { allScalarFields() password(false) diff --git a/src/main/kotlin/cn/edu/hfut/auto/knowledge/entity/vo/NoteVO.kt b/src/main/kotlin/cn/edu/hfut/auto/knowledge/entity/vo/NoteVO.kt new file mode 100644 index 0000000..3a48ceb --- /dev/null +++ b/src/main/kotlin/cn/edu/hfut/auto/knowledge/entity/vo/NoteVO.kt @@ -0,0 +1,16 @@ +package cn.edu.hfut.auto.knowledge.entity.vo + +import java.util.UUID + +data class NotePutQueryVO( + val title: String, + val content: String, + val tags: List +) + +data class NoteVO( + val title: String, + val content: String, + val tags: List, + val linkingKnowledgeFiles: List +) \ No newline at end of file diff --git a/src/main/kotlin/cn/edu/hfut/auto/knowledge/repository/NoteRepository.kt b/src/main/kotlin/cn/edu/hfut/auto/knowledge/repository/NoteRepository.kt new file mode 100644 index 0000000..9e23980 --- /dev/null +++ b/src/main/kotlin/cn/edu/hfut/auto/knowledge/repository/NoteRepository.kt @@ -0,0 +1,7 @@ +package cn.edu.hfut.auto.knowledge.repository + +import cn.edu.hfut.auto.knowledge.entity.Note +import org.babyfish.jimmer.spring.repository.KRepository +import java.util.UUID + +interface NoteRepository : KRepository \ No newline at end of file diff --git a/src/main/kotlin/cn/edu/hfut/auto/knowledge/service/QueryService.kt b/src/main/kotlin/cn/edu/hfut/auto/knowledge/service/QueryService.kt new file mode 100644 index 0000000..52809eb --- /dev/null +++ b/src/main/kotlin/cn/edu/hfut/auto/knowledge/service/QueryService.kt @@ -0,0 +1,15 @@ +package cn.edu.hfut.auto.knowledge.service + +import cn.edu.hfut.auto.knowledge.entity.vo.NotePutQueryVO +import org.springframework.http.MediaType +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.service.annotation.HttpExchange +import org.springframework.web.service.annotation.PutExchange +import java.util.UUID + +@HttpExchange(url = "\${aics.services-url.query}/search", contentType = MediaType.APPLICATION_JSON_VALUE) +interface QueryService { + @PutExchange("/note/{noteId}") + suspend fun putNote(@PathVariable noteId: UUID, @RequestBody vo: NotePutQueryVO) +} \ No newline at end of file diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 5d717bf..1fe24d1 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -31,6 +31,8 @@ logging: aics: max-permission-level: 3 + services-url: + query: http://localhost:8082 tencent: secret-id: AKIDSlvvhEYfYBetvYzCBvhJrDLGwNbcR2B7 diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 2c98006..85cf15b 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -30,6 +30,8 @@ logging: aics: max-permission-level: 3 + services-url: + query: http://aics_query:8082 tencent: secret-id: AKIDSlvvhEYfYBetvYzCBvhJrDLGwNbcR2B7