Skip to content

Commit

Permalink
feat(Post相关sql): 添加高级搜索功能
Browse files Browse the repository at this point in the history
  • Loading branch information
HXR-I committed Jul 22, 2024
1 parent cb913d5 commit 3bdb6fd
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 6 deletions.
3 changes: 2 additions & 1 deletion src/main/kotlin/subit/database/Posts.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package subit.database

import kotlinx.serialization.Serializable
import subit.dataClasses.*
import subit.router.home.AdvancedSearchData

interface Posts
{
Expand Down Expand Up @@ -49,7 +50,7 @@ interface Posts
): Slice<PostId>

suspend fun getBlockTopPosts(block: BlockId, begin: Long, count: Int): Slice<PostId>
suspend fun searchPosts(loginUser: UserId?, key: String, begin: Long, count: Int): Slice<PostId>
suspend fun searchPosts(loginUser: UserId?, key: String, advancedSearchData: AdvancedSearchData, begin: Long, count: Int): Slice<PostId>
suspend fun addView(pid: PostId)

/**
Expand Down
12 changes: 11 additions & 1 deletion src/main/kotlin/subit/database/memoryImpl/PostsImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import subit.dataClasses.*
import subit.dataClasses.PostId.Companion.toPostId
import subit.dataClasses.Slice.Companion.asSlice
import subit.database.*
import subit.router.home.AdvancedSearchData
import java.util.*
import kotlin.math.pow

Expand Down Expand Up @@ -102,14 +103,23 @@ class PostsImpl: Posts, KoinComponent
.asSlice(begin, count)
.map { it.id }

override suspend fun searchPosts(loginUser: UserId?, key: String, begin: Long, count: Int): Slice<PostId> = map.values
override suspend fun searchPosts(loginUser: UserId?, key: String, advancedSearchData: AdvancedSearchData, begin: Long, count: Int): Slice<PostId> = map.values
.filter { it.first.title.contains(key) || it.first.content.contains(key) }
.filter {
val blockFull = blocks.getBlock(it.first.block) ?: return@filter false
val permission = loginUser?.let { permissions.getPermission(blockFull.id, loginUser) }
?: PermissionLevel.NORMAL
permission >= blockFull.reading
}
.filter{
val post = it.first
val blockConstraint = if(advancedSearchData.blockIdList != null) (post.block in advancedSearchData.blockIdList) else true
val userConstraint = if(advancedSearchData.authorIdList != null) (post.author in advancedSearchData.authorIdList) else true
val contentConstraint = if(advancedSearchData.isOnlyTitle == true)( post.title.contains(key) ) else ( (post.title.contains(key)) || (post.content.contains(key)) )
val lastModifiedConstraint = if(advancedSearchData.lastModifiedAfter != null) (post.lastModified >= advancedSearchData.lastModifiedAfter) else true
val createTimeConstraint = if(advancedSearchData.createTime != null)(post.create >= advancedSearchData.createTime.first && post.create <= advancedSearchData.createTime.second ) else true
blockConstraint && userConstraint && contentConstraint && lastModifiedConstraint && createTimeConstraint
}
.asSequence()
.asSlice(begin, count)
.map { it.first.id }
Expand Down
31 changes: 29 additions & 2 deletions src/main/kotlin/subit/database/sqlImpl/PostsImpl.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package subit.database.sqlImpl

import kotlinx.datetime.Instant
import org.jetbrains.exposed.dao.id.IdTable
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.Function
Expand All @@ -21,6 +22,7 @@ import subit.database.*
import subit.database.Posts.PostListSort.*
import subit.database.sqlImpl.PostsImpl.PostsTable.create
import subit.database.sqlImpl.PostsImpl.PostsTable.view
import subit.router.home.AdvancedSearchData

/**
* 帖子数据库交互类
Expand Down Expand Up @@ -209,6 +211,7 @@ class PostsImpl: DaoSqlImpl<PostsImpl.PostsTable>(PostsTable), Posts, KoinCompon
override suspend fun searchPosts(
loginUser: UserId?,
key: String,
advancedSearchData: AdvancedSearchData,
begin: Long,
count: Int
): Slice<PostId> = query()
Expand All @@ -221,14 +224,38 @@ class PostsImpl: DaoSqlImpl<PostsImpl.PostsTable>(PostsTable), Posts, KoinCompon
val additionalConstraint: (SqlExpressionBuilder.()->Op<Boolean>)? =
if (loginUser != null) ({ permissionTable.user eq loginUser })
else null

val blockConstraint: (SqlExpressionBuilder.()->Op<Boolean>) =
if(advancedSearchData.blockIdList != null)({ block inList advancedSearchData.blockIdList })
else ({ Op.TRUE })
val userConstraint: (SqlExpressionBuilder.()->Op<Boolean>) =
if(advancedSearchData.authorIdList != null)({ author inList advancedSearchData.authorIdList })
else ({ Op.TRUE })
val contentConstraint: (SqlExpressionBuilder.()->Op<Boolean>) =
if(advancedSearchData.isOnlyTitle == true) ({ title like "%$key%" })
else ({ (title like "%$key%") or (content like "%$key%") })
val lastModifiedConstraint: (SqlExpressionBuilder.()->Op<Boolean>) =
if(advancedSearchData.lastModifiedAfter != null)({ lastModified greaterEq Instant.fromEpochMilliseconds(advancedSearchData.lastModifiedAfter) })
else ({ Op.TRUE })
val createTimeConstraint: (SqlExpressionBuilder.()->Op<Boolean>) =
if(advancedSearchData.createTime != null)({
val (l, r) = advancedSearchData.createTime
(create greaterEq Instant.fromEpochMilliseconds(l)) and (create lessEq Instant.fromEpochMilliseconds(r))
})
else ({ Op.TRUE })

PostsTable.join(blockTable, JoinType.INNER, block, blockTable.id)
.join(permissionTable, JoinType.LEFT, block, permissionTable.block, additionalConstraint)
.join(likesTable, JoinType.LEFT, id, likesTable.post)
.join(starsTable, JoinType.LEFT, id, starsTable.post)
.join(commentsTable, JoinType.LEFT, id, commentsTable.post)
.select(id)
.where { (title like "%$key%") or (content like "%$key%") }
.andWhere { state eq State.NORMAL }
.where { state eq State.NORMAL }
.andWhere(blockConstraint)
.andWhere(userConstraint)
.andWhere(contentConstraint)
.andWhere(lastModifiedConstraint)
.andWhere(createTimeConstraint)
.groupBy(id, create, blockTable.id, blockTable.reading)
.having { (permissionTable.permission.max() greaterEq blockTable.reading) or (blockTable.reading lessEq PermissionLevel.NORMAL) }
.orderBy(hotScoreOrder, SortOrder.DESC)
Expand Down
45 changes: 43 additions & 2 deletions src/main/kotlin/subit/router/Home.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import io.ktor.server.application.*
import io.ktor.server.plugins.ratelimit.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.serialization.Serializable
import subit.JWTAuth.getLoginUser
import subit.dataClasses.*
import subit.database.Blocks
import subit.database.Posts
import subit.database.Users
import subit.database.receiveAndCheckBody
import subit.plugin.RateLimit
import subit.router.*
import subit.utils.HttpStatus
Expand Down Expand Up @@ -73,7 +75,35 @@ fun Route.home() = route("/home", {
}) { searchBlock() }

get("/post", {
description = "搜索帖子, 会返回所有标题或内容包含关键词的帖子"
description = "搜索帖子, 会返回所有符合条件的帖子"
request {
queryParameter<Boolean>("openAdvancedSearch")
{
required = false
description = "是否开启高级搜索, 默认为否"
example = false
}
body<AdvancedSearchData>{
required = false
description = """
高级搜索条件 不需要的选项可不传,不传时选项为默认值
需要openAdvancedSearch为true时传入
blockIdList: 指定在哪些板块及其子版块中搜索, 默认为不限制
isOnlyTitle: 是否只在标题中搜索关键字, 默认为否
authorIdList: 指定在哪些用户为作者的帖子中搜索, 默认为不限制
lastModifiedAfter: 指定帖子最后修改时间需在哪个时间点之后, 传入时间戳, 默认为不限制
createTime: 指定帖子创建时间需在哪个时间段内, 传入时间戳组, 默认为不限制
""".trimIndent()
example("example", AdvancedSearchData(
blockIdList = listOf(BlockId(0)),
authorIdList = listOf(UserId(0)),
isOnlyTitle = false,
lastModifiedAfter = System.currentTimeMillis(),
createTime = Pair(System.currentTimeMillis(),System.currentTimeMillis())
)
)
}
}
response {
statuses<Slice<PostId>>(HttpStatus.OK, example = sliceOf(PostId(0)))
}
Expand Down Expand Up @@ -105,10 +135,21 @@ private suspend fun Context.searchBlock()
call.respond(HttpStatus.OK, blocks)
}

@Serializable
data class AdvancedSearchData(
val blockIdList: List<BlockId>? = null,
val authorIdList: List<UserId>? = null,
val isOnlyTitle: Boolean? = null,
val lastModifiedAfter: Long? = null,
val createTime: Pair<Long, Long>? = null,
)

private suspend fun Context.searchPost()
{
val key = call.parameters["key"] ?: return call.respond(HttpStatus.BadRequest)
val (begin, count) = call.getPage()
val posts = get<Posts>().searchPosts(getLoginUser()?.id, key, begin, count)
val openAdvancedSearch = call.parameters["openAdvancedSearch"]?.toBoolean() ?: false
val advancedSearchData: AdvancedSearchData = if(openAdvancedSearch) receiveAndCheckBody<AdvancedSearchData>() else AdvancedSearchData()
val posts = get<Posts>().searchPosts(getLoginUser()?.id, key, advancedSearchData, begin, count)
call.respond(HttpStatus.OK, posts)
}

0 comments on commit 3bdb6fd

Please sign in to comment.