feat: 新增了笔记相关的接口

- getNoteDetailed
- uploadNote
- updateNote
- deleteNote
- starNote
- likeNote
master
ArgonarioD 2023-07-07 01:42:12 +08:00
parent d50715f58c
commit e9b6521c89
17 changed files with 229 additions and 1 deletions

View File

@ -39,6 +39,7 @@ repositories {
dependencies { dependencies {
implementation("org.springframework.boot:spring-boot-starter-validation") implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("org.springframework.boot:spring-boot-starter-web") 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("com.fasterxml.jackson.module:jackson-module-kotlin")
// 日志 // 日志
implementation("org.springframework.boot:spring-boot-starter-log4j2") implementation("org.springframework.boot:spring-boot-starter-log4j2")

View File

@ -10,5 +10,5 @@ echo 'saving...'
docker save -o ..\docker\aics_main.tar auto/aics_main:latest docker save -o ..\docker\aics_main.tar auto/aics_main:latest
echo 'compressing...' 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 pause

View File

@ -1,5 +1,11 @@
package cn.edu.hfut.auto.knowledge.config 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.context.annotation.Configuration
import org.springframework.core.convert.converter.Converter import org.springframework.core.convert.converter.Converter
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
@ -16,4 +22,18 @@ class SerializeConfig {
return UUID.fromString(source) return UUID.fromString(source)
} }
} }
@Bean
fun nullableUUIDModule(): Module {
return SimpleModule().apply {
addDeserializer(UUID::class.java, NullableUUIDJsonDeserializer())
}
}
class NullableUUIDJsonDeserializer : JsonDeserializer<UUID?>() {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): UUID? {
return p.valueAsString?.takeIf { it != "null" }?.let { UUID.fromString(it) }
}
}
} }

View File

@ -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<QueryService>()
}
}

View File

@ -157,6 +157,7 @@ class KnowledgeController(
?.knowledgeFileAttribute ?.knowledgeFileAttribute
?.let { attr -> ?.let { attr ->
knowledgeFileAttributeRepository.update(new(KnowledgeFileAttribute::class).by { knowledgeFileAttributeRepository.update(new(KnowledgeFileAttribute::class).by {
id = knowledgeId
starers = attr.starers.filterNot { it.id == loginUserId } starers = attr.starers.filterNot { it.id == loginUserId }
if (active) { if (active) {
starers().addBy { id = loginUserId } starers().addBy { id = loginUserId }

View File

@ -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)
}
}

View File

@ -26,20 +26,24 @@ interface Knowledge {
val knowledgeFileAttribute: KnowledgeFileAttribute? val knowledgeFileAttribute: KnowledgeFileAttribute?
companion object { companion object {
@JvmField
val BRIEF_FETCHER = newFetcher(Knowledge::class).by { val BRIEF_FETCHER = newFetcher(Knowledge::class).by {
allScalarFields() allScalarFields()
knowledgeFileAttribute(KnowledgeFileAttribute.BRIEF_FETCHER) knowledgeFileAttribute(KnowledgeFileAttribute.BRIEF_FETCHER)
} }
@JvmField
val DETAILED_FILE_FETCHER = newFetcher(Knowledge::class).by { val DETAILED_FILE_FETCHER = newFetcher(Knowledge::class).by {
allScalarFields() allScalarFields()
knowledgeFileAttribute(KnowledgeFileAttribute.DETAILED_FETCHER) knowledgeFileAttribute(KnowledgeFileAttribute.DETAILED_FETCHER)
} }
@JvmField
val AS_PARENT_FETCHER = newFetcher(Knowledge::class).by { val AS_PARENT_FETCHER = newFetcher(Knowledge::class).by {
allScalarFields() allScalarFields()
parent() parent()
children(BRIEF_FETCHER) children(BRIEF_FETCHER)
knowledgeFileAttribute(KnowledgeFileAttribute.BRIEF_FETCHER) knowledgeFileAttribute(KnowledgeFileAttribute.BRIEF_FETCHER)
} }
@JvmField
val AS_CHILD_FETCHER = newFetcher(Knowledge::class).by { val AS_CHILD_FETCHER = newFetcher(Knowledge::class).by {
allScalarFields() allScalarFields()
parent(BRIEF_FETCHER) parent(BRIEF_FETCHER)

View File

@ -31,11 +31,14 @@ interface KnowledgeFileAttribute {
val notes: List<Note> val notes: List<Note>
companion object { companion object {
@JvmField
val EXTERNAL_PREVIEWABLE_SUFFIXES = listOf("pdf", "ppt", "pptx", "doc", "docx", "xls", "xlsx") val EXTERNAL_PREVIEWABLE_SUFFIXES = listOf("pdf", "ppt", "pptx", "doc", "docx", "xls", "xlsx")
@JvmField
val BRIEF_FETCHER = newFetcher(KnowledgeFileAttribute::class).by { val BRIEF_FETCHER = newFetcher(KnowledgeFileAttribute::class).by {
allScalarFields() allScalarFields()
tags(Tag.ALL_FETCHER) tags(Tag.ALL_FETCHER)
} }
@JvmField
val DETAILED_FETCHER = newFetcher(KnowledgeFileAttribute::class).by { val DETAILED_FETCHER = newFetcher(KnowledgeFileAttribute::class).by {
allScalarFields() allScalarFields()
starers() starers()

View File

@ -11,9 +11,11 @@ interface Note {
@Id @Id
@GeneratedValue(generatorType = UUIDIdGenerator::class) @GeneratedValue(generatorType = UUIDIdGenerator::class)
val id: UUID val id: UUID
@Key
val title: String val title: String
@ManyToOne @ManyToOne
@Key
val author: User val author: User
val createTime: LocalDateTime val createTime: LocalDateTime
val updateTime: LocalDateTime val updateTime: LocalDateTime
@ -39,6 +41,7 @@ interface Note {
val likers: List<User> val likers: List<User>
companion object { companion object {
@JvmField
val BRIEF_FETCHER = newFetcher(Note::class).by { val BRIEF_FETCHER = newFetcher(Note::class).by {
allScalarFields() allScalarFields()
author(User.BRIEF_FETCHER) author(User.BRIEF_FETCHER)
@ -46,5 +49,19 @@ interface Note {
starers() starers()
likers() 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()
}
} }
} }

View File

@ -27,6 +27,7 @@ interface Notice {
val targetUser: User val targetUser: User
companion object { companion object {
@JvmField
val BRIEF_FETCHER = newFetcher(Notice::class).by { val BRIEF_FETCHER = newFetcher(Notice::class).by {
allScalarFields() allScalarFields()
note { allScalarFields() } note { allScalarFields() }

View File

@ -4,6 +4,7 @@ import org.babyfish.jimmer.sql.Entity
import org.babyfish.jimmer.sql.GeneratedValue import org.babyfish.jimmer.sql.GeneratedValue
import org.babyfish.jimmer.sql.GenerationType import org.babyfish.jimmer.sql.GenerationType
import org.babyfish.jimmer.sql.Id import org.babyfish.jimmer.sql.Id
import org.babyfish.jimmer.sql.Key
import org.babyfish.jimmer.sql.kt.fetcher.newFetcher import org.babyfish.jimmer.sql.kt.fetcher.newFetcher
@Entity @Entity
@ -11,9 +12,11 @@ interface Tag {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long val id: Long
@Key
val name: String val name: String
companion object { companion object {
@JvmField
val ALL_FETCHER = newFetcher(Tag::class).by { val ALL_FETCHER = newFetcher(Tag::class).by {
allScalarFields() allScalarFields()
} }

View File

@ -31,6 +31,7 @@ interface User {
val starredNotes: List<Note> val starredNotes: List<Note>
companion object { companion object {
@JvmField
val BRIEF_FETCHER = newFetcher(User::class).by { val BRIEF_FETCHER = newFetcher(User::class).by {
allScalarFields() allScalarFields()
password(false) password(false)

View File

@ -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<Long>
)
data class NoteVO(
val title: String,
val content: String,
val tags: List<Long>,
val linkingKnowledgeFiles: List<UUID>
)

View File

@ -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<Note, UUID>

View File

@ -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)
}

View File

@ -31,6 +31,8 @@ logging:
aics: aics:
max-permission-level: 3 max-permission-level: 3
services-url:
query: http://localhost:8082
tencent: tencent:
secret-id: AKIDSlvvhEYfYBetvYzCBvhJrDLGwNbcR2B7 secret-id: AKIDSlvvhEYfYBetvYzCBvhJrDLGwNbcR2B7

View File

@ -30,6 +30,8 @@ logging:
aics: aics:
max-permission-level: 3 max-permission-level: 3
services-url:
query: http://aics_query:8082
tencent: tencent:
secret-id: AKIDSlvvhEYfYBetvYzCBvhJrDLGwNbcR2B7 secret-id: AKIDSlvvhEYfYBetvYzCBvhJrDLGwNbcR2B7