Skip to content

Commit

Permalink
feat: 全局统计数据
Browse files Browse the repository at this point in the history
  • Loading branch information
nullaqua committed Sep 15, 2024
1 parent 664b025 commit a287426
Show file tree
Hide file tree
Showing 11 changed files with 173 additions and 21 deletions.
3 changes: 3 additions & 0 deletions src/main/kotlin/subit/database/Likes.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package subit.database

import subit.dataClasses.*
import kotlin.time.Duration

/**
* @author nullaqua
Expand All @@ -17,4 +18,6 @@ interface Likes
begin: Long = 1,
limit: Int = Int.MAX_VALUE,
): Slice<Like>

suspend fun totalLikesCount(duration: Duration?): Long
}
4 changes: 4 additions & 0 deletions src/main/kotlin/subit/database/Posts.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package subit.database
import kotlinx.serialization.Serializable
import subit.dataClasses.*
import subit.router.home.AdvancedSearchData
import kotlin.time.Duration

interface Posts
{
Expand Down Expand Up @@ -117,4 +118,7 @@ interface Posts
* 获得***最近一个月***点赞数最多的帖子
*/
suspend fun monthly(loginUser: DatabaseUser?, begin: Long, count: Int): Slice<PostFullBasicInfo>

suspend fun totalPostCount(comment: Boolean, duration: Duration?): Map<State, Long>
suspend fun totalReadCount(): Long
}
3 changes: 3 additions & 0 deletions src/main/kotlin/subit/database/Stars.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import subit.dataClasses.PostId
import subit.dataClasses.Slice
import subit.dataClasses.Star
import subit.dataClasses.UserId
import kotlin.time.Duration

interface Stars
{
Expand All @@ -17,4 +18,6 @@ interface Stars
begin: Long = 1,
limit: Int = Int.MAX_VALUE,
): Slice<Star>

suspend fun totalStarsCount(duration: Duration?): Long
}
39 changes: 20 additions & 19 deletions src/main/kotlin/subit/database/memoryImpl/LikesImpl.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package subit.database.memoryImpl

import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import subit.dataClasses.Like
import subit.dataClasses.PostId
Expand All @@ -9,35 +8,37 @@ import subit.dataClasses.Slice.Companion.asSlice
import subit.dataClasses.UserId
import subit.database.Likes
import java.util.*
import kotlin.time.Duration

class LikesImpl: Likes
{
private val map = Collections.synchronizedMap(hashMapOf<Pair<UserId,PostId>,Instant>())

private val set = Collections.synchronizedSet(hashSetOf<Like>())
override suspend fun addLike(uid: UserId, pid: PostId)
{
map[uid to pid] = Clock.System.now()
set.add(Like(uid, pid, System.currentTimeMillis()))
}

override suspend fun removeLike(uid: UserId, pid: PostId)
{
map.remove(uid to pid)
}
override suspend fun getLike(uid: UserId, pid: PostId): Boolean = map[uid to pid] != null
override suspend fun getLikesCount(pid: PostId): Long
{
val likes = map.entries.filter { it.key.second == pid }
return likes.size.toLong()
set.removeIf { it.user == uid && it.post == pid }
}

override suspend fun getLikes(user: UserId?, post: PostId?, begin: Long, limit: Int): Slice<Like>
{
val likes = map.entries.filter { (user == null || it.key.first == user) && (post == null || it.key.second == post) }
return likes.asSequence().map { Like(it.key.first, it.key.second, it.value.toEpochMilliseconds()) }.asSlice(begin, limit)
}
override suspend fun getLike(uid: UserId, pid: PostId): Boolean =
set.count { it.user == uid && it.post == pid } > 0

override suspend fun getLikesCount(pid: PostId): Long =
set.count { it.post == pid }.toLong()

fun getLikesAfter(post: PostId, time: Instant): Long
override suspend fun getLikes(user: UserId?, post: PostId?, begin: Long, limit: Int): Slice<Like> =
set.filter { (user == null || it.user == user) && (post == null || it.post == post) }
.sortedByDescending(Like::time).asSequence().asSlice(begin, limit)

override suspend fun totalLikesCount(duration: Duration?): Long
{
val likes = map.entries.filter { it.key.second == post && it.value > time }
return likes.size.toLong()
val time = duration?.let { System.currentTimeMillis() - it.inWholeMilliseconds } ?: 0
return set.count { it.time > time }.toLong()
}

fun getLikesAfter(pid: PostId, time: Instant): Long =
set.count { it.post == pid && it.time > time.toEpochMilliseconds() }.toLong()
}
16 changes: 16 additions & 0 deletions src/main/kotlin/subit/database/memoryImpl/PostsImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import subit.dataClasses.PostId.Companion.toPostId
import subit.dataClasses.Slice.Companion.asSlice
import subit.database.*
import subit.router.home.AdvancedSearchData
import subit.utils.toInstant
import java.util.*
import kotlin.math.pow
import kotlin.time.Duration
import kotlin.time.Duration.Companion.days

class PostsImpl: Posts, KoinComponent
Expand Down Expand Up @@ -246,4 +248,18 @@ class PostsImpl: Posts, KoinComponent
.asSlice(begin, count)
.map { it.toPostFullBasicInfo() }
}

override suspend fun totalPostCount(comment: Boolean, duration: Duration?): Map<State, Long>
{
val time = duration?.let { Clock.System.now() - it } ?: 0L.toInstant()
val res = map.values
.filter { (it.first.parent != null) == comment }
.map { getPostFull(it.first.id)!! }
.filter { it.create != null && it.create >= time.toEpochMilliseconds() }
.groupBy { it.state }
.mapValues { it.value.size.toLong() }
return State.entries.associateWith { res[it] ?: 0 }
}

override suspend fun totalReadCount(): Long = map.values.sumOf { it.first.view }
}
9 changes: 9 additions & 0 deletions src/main/kotlin/subit/database/memoryImpl/StarsImpl.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package subit.database.memoryImpl

import kotlinx.datetime.Clock
import subit.dataClasses.PostId
import subit.dataClasses.Slice
import subit.dataClasses.Slice.Companion.asSlice
import subit.dataClasses.Star
import subit.dataClasses.UserId
import subit.database.Stars
import subit.utils.toInstant
import java.util.*
import kotlin.time.Duration

class StarsImpl: Stars
{
Expand All @@ -30,4 +33,10 @@ class StarsImpl: Stars
override suspend fun getStars(user: UserId?, post: PostId?, begin: Long, limit: Int): Slice<Star> =
set.filter { (user == null || it.user == user) && (post == null || it.post == post) }
.sortedByDescending(Star::time).asSequence().asSlice(begin, limit)

override suspend fun totalStarsCount(duration: Duration?): Long
{
val time = duration?.let { Clock.System.now() - it } ?: 0L.toInstant()
return set.count { it.time > time.toEpochMilliseconds() }.toLong()
}
}
9 changes: 9 additions & 0 deletions src/main/kotlin/subit/database/sqlImpl/LikesImpl.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package subit.database.sqlImpl

import kotlinx.datetime.Clock
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.kotlin.datetime.CurrentTimestamp
Expand All @@ -11,6 +12,8 @@ import subit.dataClasses.Slice
import subit.dataClasses.UserId
import subit.database.Likes
import subit.database.sqlImpl.utils.asSlice
import subit.utils.toInstant
import kotlin.time.Duration

class LikesImpl: DaoSqlImpl<LikesImpl.LikesTable>(LikesTable), Likes, KoinComponent
{
Expand Down Expand Up @@ -60,4 +63,10 @@ class LikesImpl: DaoSqlImpl<LikesImpl.LikesTable>(LikesTable), Likes, KoinCompon
post?.let { query.andWhere { LikesTable.post eq it } }
query.asSlice(begin, limit).map { deserialize(it) }
}

override suspend fun totalLikesCount(duration: Duration?): Long = query()
{
val time = duration?.let { Clock.System.now() - it } ?: 0L.toInstant()
table.selectAll().where { table.time greaterEq time }.count()
}
}
20 changes: 20 additions & 0 deletions src/main/kotlin/subit/database/sqlImpl/PostsImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@ import subit.database.Posts.PostListSort.*
import subit.database.sqlImpl.PostVersionsImpl.PostVersionsTable
import subit.database.sqlImpl.PostsImpl.PostsTable.view
import subit.database.sqlImpl.utils.asSlice
import subit.database.sqlImpl.utils.single
import subit.database.sqlImpl.utils.singleOrNull
import subit.router.home.AdvancedSearchData
import subit.utils.toInstant
import subit.utils.toTimestamp
import java.sql.ResultSet
import kotlin.reflect.typeOf
import kotlin.time.Duration
import kotlin.time.Duration.Companion.days

/**
Expand Down Expand Up @@ -620,4 +622,22 @@ class PostsImpl: DaoSqlImpl<PostsImpl.PostsTable>(PostsTable), Posts, KoinCompon
.asSlice(begin, count)
.map { deserializePost<PostFullBasicInfo>(it) }
}

override suspend fun totalPostCount(comment: Boolean, duration: Duration?): Map<State, Long> = query()
{
val time = duration?.let { Clock.System.now() - it } ?: 0L.toInstant()
val res = table
.joinPostFull(true)
.select(state, id.count())
.andWhere { if (comment) parent.isNotNull() else parent.isNull() }
.andWhere { lastModified.aliasOnlyExpression() greaterEq timestampParam(time) }
.groupBy(state)
.associate { it[state] to it[id.count()] }
State.entries.associateWith { (res[it] ?: 0) }
}

override suspend fun totalReadCount(): Long = query()
{
table.select(view.sum()).single()[view.sum()] ?: 0
}
}
9 changes: 9 additions & 0 deletions src/main/kotlin/subit/database/sqlImpl/StarsImpl.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package subit.database.sqlImpl

import kotlinx.datetime.Clock
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.kotlin.datetime.CurrentTimestamp
Expand All @@ -10,6 +11,8 @@ import subit.dataClasses.Star
import subit.dataClasses.UserId
import subit.database.Stars
import subit.database.sqlImpl.utils.asSlice
import subit.utils.toInstant
import kotlin.time.Duration

/**
* 收藏数据库交互类
Expand Down Expand Up @@ -68,4 +71,10 @@ class StarsImpl: DaoSqlImpl<StarsImpl.StarsTable>(StarsTable), Stars

q.asSlice(begin, limit).map(::deserialize)
}

override suspend fun totalStarsCount(duration: Duration?): Long = query()
{
val time = duration?.let { Clock.System.now() - it } ?: 0L.toInstant()
table.selectAll().where { table.time greaterEq time }.count()
}
}
81 changes: 80 additions & 1 deletion src/main/kotlin/subit/router/Admin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ import subit.utils.HttpStatus
import subit.utils.SSO
import subit.utils.respond
import subit.utils.statuses
import kotlin.time.Duration.Companion.days

fun Route.admin() = route("/admin", {
tags = listOf("用户管理")
tags = listOf("管理")
response {
statuses(HttpStatus.Unauthorized, HttpStatus.Forbidden)
}
Expand Down Expand Up @@ -65,6 +66,13 @@ fun Route.admin() = route("/admin", {
statuses(HttpStatus.OK)
}
}) { changePermission() }

get("/globalInfo", {
description = "获取全局信息, 需要当前用户的user权限大于ADMIN"
response {
statuses<GlobalInfo>(HttpStatus.OK, example = GlobalInfo.example)
}
}) { globalInfo() }
}

@Serializable
Expand Down Expand Up @@ -119,3 +127,74 @@ private suspend fun Context.changePermission()
)
call.respond(HttpStatus.OK)
}

@Serializable
private data class Data(
val day: Long,
val month: Long,
val year: Long,
val total: Long
)
{
companion object
{
val example = Data(1, 1, 1, 1)
}
}

@Serializable
private data class GlobalInfo(
val post: Map<State, Data>,
val comment: Data,
val like: Data,
val star: Data,
val read: Long,
)
{
companion object
{
val example = GlobalInfo(
State.entries.associateWith { Data.example },
Data.example,
Data.example,
Data.example,
1
)
}
}

private suspend fun Context.globalInfo()
{
withPermission { checkHasGlobalAdmin() }
val posts = get<Posts>()
val dayPost = posts.totalPostCount(false, 1.days)
val monthPost = posts.totalPostCount(false, 30.days)
val yearPost = posts.totalPostCount(false, 365.days)
val totalPost = posts.totalPostCount(false, null)
val post = State.entries.associateWith { Data(dayPost[it]!!, monthPost[it]!!, yearPost[it]!!, totalPost[it]!!) }

val dayComment = posts.totalPostCount(true, 1.days).values.sum()
val monthComment = posts.totalPostCount(true, 30.days).values.sum()
val yearComment = posts.totalPostCount(true, 365.days).values.sum()
val totalComment = posts.totalPostCount(true, null).values.sum()
val comment = Data(dayComment, monthComment, yearComment, totalComment)

val read = posts.totalReadCount()

val likes = get<Likes>()
val dayLike = likes.totalLikesCount(1.days)
val monthLike = likes.totalLikesCount(30.days)
val yearLike = likes.totalLikesCount(365.days)
val totalLike = likes.totalLikesCount(null)
val like = Data(dayLike, monthLike, yearLike, totalLike)

val stars = get<Stars>()
val dayStar = stars.totalStarsCount(1.days)
val monthStar = stars.totalStarsCount(30.days)
val yearStar = stars.totalStarsCount(365.days)
val totalStar = stars.totalStarsCount(null)
val star = Data(dayStar, monthStar, yearStar, totalStar)

val globalInfo = GlobalInfo(post, comment, like, star, read)
call.respond(HttpStatus.OK, globalInfo)
}
1 change: 0 additions & 1 deletion src/main/kotlin/subit/utils/FileUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import subit.dataClasses.PermissionLevel
import subit.dataClasses.UserFull
import subit.dataClasses.UserId
import subit.dataDir
import subit.workDir
import java.awt.image.BufferedImage
import java.io.File
import java.io.FileInputStream
Expand Down

0 comments on commit a287426

Please sign in to comment.