feat(admin-borrows): add search endpoint replacing getAll
Replace the simple \"get all borrows\" endpoint with a search-based approach supporting queries by book name, author, username, and role. - Change endpoint from GET /getall to GET /search?query=xxx - Add search service implementation using QueryWrapper with LIKE predicates across Book, User, and BorrowRecord tables - Use kotlinx-coroutines for parallel async data fetching per result - Add kotlinx-coroutines-core and kotlinx-coroutines-reactor deps
This commit is contained in:
@@ -22,6 +22,8 @@ repositories {
|
||||
dependencies {
|
||||
implementation("org.springframework.boot:spring-boot-starter")
|
||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:1.10.2")
|
||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||
implementation("org.aspectj:aspectjweaver")
|
||||
implementation("org.springframework.security:spring-security-crypto")
|
||||
|
||||
@@ -28,7 +28,42 @@ class AdminBorrowController(
|
||||
private val adminBorrowService: AdminBorrowService,
|
||||
private val ipExtractor: IpExtractor
|
||||
) {
|
||||
/*
|
||||
* GET /api/admin/borrows/search?query=xxx
|
||||
* 按书名、作者、用户名或角色搜索借阅记录
|
||||
*/
|
||||
@RequireRole("admin")
|
||||
@GetMapping("/search")
|
||||
fun searchBorrows(
|
||||
@RequestAttribute(required = false) username: String?,
|
||||
query: String,
|
||||
request: HttpServletRequest
|
||||
): Result<Any?> {
|
||||
log.info("[AdminBorrow] search: user={}, query={}", username ?: "guest", query)
|
||||
log.info("[AdminBorrow] user agent: {}, ip={}", request.getHeader("User-Agent"), ipExtractor.getRealIp(request))
|
||||
return adminBorrowService.searchBorrows(query)
|
||||
}
|
||||
|
||||
/*
|
||||
* GET /api/admin/borrows/getone?id=xxx
|
||||
* 查询单条借阅记录,含关联的图书和用户信息
|
||||
*/
|
||||
@RequireRole("admin")
|
||||
@GetMapping("/getone")
|
||||
fun getOneBorrow(
|
||||
@RequestAttribute(required = false) username: String?,
|
||||
id: Long,
|
||||
request: HttpServletRequest
|
||||
): Result<Any?> {
|
||||
log.info("[AdminBorrow] getOne: user={}, borrow record id={}", username ?: "guest", id)
|
||||
log.info("[AdminBorrow] user agent: {}, ip={}", request.getHeader("User-Agent"), ipExtractor.getRealIp(request))
|
||||
return adminBorrowService.getOneBorrow(id)
|
||||
}
|
||||
|
||||
/*
|
||||
* GET /api/admin/borrows/getall
|
||||
* 查询全部借阅记录(仅管理员)
|
||||
*/
|
||||
@RequireRole("admin")
|
||||
@GetMapping("/getall")
|
||||
fun getAllBorrows(
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
package com.msksbr.bookmgr.controller
|
||||
|
||||
import com.msksbr.bookmgr.annotation.RequireRole
|
||||
import com.msksbr.bookmgr.config.IpExtractor
|
||||
import com.msksbr.bookmgr.script.log
|
||||
import com.msksbr.bookmgr.service.DashBoardService
|
||||
import com.msksbr.bookmgr.template.Result
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.RequestAttribute
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
/*
|
||||
* 仪表盘接口
|
||||
* 路径前缀:/api/dashboard
|
||||
*
|
||||
* 权限模型:
|
||||
* getAllBooks — 无注解,游客 / 用户 / admin 均可
|
||||
* getAllBorrowRecords — @RequireRole("admin"),仅管理员
|
||||
*/
|
||||
|
||||
// @TODO:拆分到各自的服务中
|
||||
@RestController
|
||||
@RequestMapping("/api/dashboard")
|
||||
class DashBoardController(
|
||||
private val dashBoardService: DashBoardService,
|
||||
private val ipExtractor: IpExtractor,
|
||||
) {
|
||||
/*
|
||||
* GET /api/dashboard/get-all-books
|
||||
* 查询全部图书列表,游客无需登录即可访问
|
||||
*
|
||||
* 成功响应(code=200):
|
||||
* { "code":200, "message":"success", "data":[{"id":1,"name":"三体",...}, ...] }
|
||||
*/
|
||||
@GetMapping("/get-all-books")
|
||||
fun getAllBooks(
|
||||
@RequestAttribute(required = false) username: String?,
|
||||
request: HttpServletRequest
|
||||
): Result<Any?> {
|
||||
val user = username ?: "guest"
|
||||
log.info("[DashBoard] getAllBooks: user={}, ip={}", user, ipExtractor.getRealIp(request))
|
||||
log.info("[DashBoard] user agent: {}", request.getHeader("User-Agent"))
|
||||
return dashBoardService.getAllBooks()
|
||||
}
|
||||
|
||||
/*
|
||||
* GET /api/dashboard/admin/get-all-borrow-records
|
||||
* 查询全量借阅记录(仅管理员)
|
||||
*
|
||||
* 成功响应(code=200):
|
||||
* { "code":200, "message":"success", "data":[{"id":1,"userId":1,"bookId":2,...}, ...] }
|
||||
*/
|
||||
@RequireRole("admin")
|
||||
@GetMapping("/admin/get-all-borrow-records")
|
||||
fun getAllBorrowRecords(
|
||||
@RequestAttribute(required = false) username: String?,
|
||||
request: HttpServletRequest
|
||||
): Result<Any?> {
|
||||
log.info("[DashBoard] getAllBorrowRecords: user={}, ip={}", username ?: "unknown", ipExtractor.getRealIp(request))
|
||||
log.info("[DashBoard] user agent: {}", request.getHeader("User-Agent"))
|
||||
return dashBoardService.getAllBorrowRecords()
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package com.msksbr.bookmgr.service
|
||||
|
||||
import com.msksbr.bookmgr.template.Result
|
||||
|
||||
/*
|
||||
* 仪表盘服务接口
|
||||
* 聚合图书全量查询和借阅记录全量查询
|
||||
*/
|
||||
interface DashBoardService {
|
||||
/*
|
||||
* 查询全部图书列表
|
||||
* @return 包含所有图书的 JSON 列表
|
||||
*/
|
||||
fun getAllBooks(): Result<Any?>
|
||||
/*
|
||||
* 查询全部借阅记录列表
|
||||
* @return 包含所有借阅记录的 JSON 列表
|
||||
*/
|
||||
fun getAllBorrowRecords(): Result<Any?>
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
package com.msksbr.bookmgr.service.impl
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper
|
||||
import com.msksbr.bookmgr.entity.Book
|
||||
import com.msksbr.bookmgr.entity.BorrowRecord
|
||||
import com.msksbr.bookmgr.entity.User
|
||||
import com.msksbr.bookmgr.mapper.BookMapper
|
||||
import com.msksbr.bookmgr.mapper.BorrowRecordMapper
|
||||
import com.msksbr.bookmgr.mapper.UserMapper
|
||||
@@ -9,6 +13,10 @@ import com.msksbr.bookmgr.template.Result
|
||||
import com.msksbr.bookmgr.vo.BorrowInfoVo
|
||||
import com.msksbr.bookmgr.vo.borrow.BookBorrowVo
|
||||
import com.msksbr.bookmgr.vo.borrow.UserBorrowVo
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
/*
|
||||
@@ -21,27 +29,120 @@ class AdminBorrowServiceImpl(
|
||||
private val borrowRecordMapper: BorrowRecordMapper,
|
||||
private val bookMapper: BookMapper
|
||||
) : AdminBorrowService {
|
||||
/*
|
||||
* 搜索借阅记录:并发查询 user 表和 book 表,按用户名、角色、书名或作者匹配
|
||||
* 将匹配到的 userId / bookId 取并集后查询借阅记录,降序排列
|
||||
*/
|
||||
override fun searchBorrows(query: String): Result<Any?> {
|
||||
TODO("Not yet implemented")
|
||||
if (query.isBlank()) {
|
||||
log.warn("[AdminBorrow] search: query is blank")
|
||||
return Result.error("Search query cannot be empty")
|
||||
}
|
||||
val (matchedUserIds, matchedBookIds) = runBlocking {
|
||||
val userDeferred = async(Dispatchers.IO) {
|
||||
userMapper.selectList(
|
||||
QueryWrapper<User>()
|
||||
.like("username", query)
|
||||
.or()
|
||||
.like("role", query)
|
||||
).map { it.id }
|
||||
}
|
||||
val bookDeferred = async(Dispatchers.IO) {
|
||||
bookMapper.selectList(
|
||||
QueryWrapper<Book>()
|
||||
.like("name", query)
|
||||
.or()
|
||||
.like("author", query)
|
||||
).map { it.id }
|
||||
}
|
||||
userDeferred.await() to bookDeferred.await()
|
||||
}
|
||||
if (matchedUserIds.isEmpty() && matchedBookIds.isEmpty()) {
|
||||
log.info("[AdminBorrow] search: no results for {}", query)
|
||||
return Result.notFound("No matching borrow records found")
|
||||
}
|
||||
val borrows = borrowRecordMapper.selectList(
|
||||
QueryWrapper<BorrowRecord>()
|
||||
.`in`(matchedUserIds.isNotEmpty(), "user_id", matchedUserIds)
|
||||
.or()
|
||||
.`in`(matchedBookIds.isNotEmpty(), "book_id", matchedBookIds)
|
||||
.orderByDesc("borrow_time")
|
||||
)
|
||||
val userIds = borrows.map { it.userId }.distinct()
|
||||
val bookIds = borrows.map { it.bookId }.distinct()
|
||||
val result = runBlocking {
|
||||
buildBorrowVos(borrows, userIds, bookIds)
|
||||
}
|
||||
log.info("[AdminBorrow] search: found {} records for {}", result.size, query)
|
||||
return Result.success(result)
|
||||
}
|
||||
|
||||
/*
|
||||
* 查询单条借阅记录,含关联的图书和用户信息
|
||||
*/
|
||||
override fun getOneBorrow(id: Long): Result<Any?> {
|
||||
TODO("Not yet implemented")
|
||||
val borrow = borrowRecordMapper.selectById(id)
|
||||
if (borrow == null) {
|
||||
log.info("[AdminBorrow] getOne: no record for id={}", id)
|
||||
return Result.notFound("Borrow record not found")
|
||||
}
|
||||
val user = userMapper.selectById(borrow.userId)
|
||||
val book = bookMapper.selectById(borrow.bookId)
|
||||
val result = BorrowInfoVo(
|
||||
id = borrow.id!!,
|
||||
borrowTime = borrow.borrowTime,
|
||||
returnTime = borrow.returnTime,
|
||||
status = borrow.status,
|
||||
bookBorrowVo = BookBorrowVo(
|
||||
id = book.id!!,
|
||||
name = book.name,
|
||||
author = book.author,
|
||||
),
|
||||
userBorrowVo = UserBorrowVo(
|
||||
id = user.id!!,
|
||||
username = user.username,
|
||||
role = user.role,
|
||||
)
|
||||
)
|
||||
log.info("[AdminBorrow] getOne: found record id={}, user={}, book={}", id, user.username, book.name)
|
||||
return Result.success(result)
|
||||
}
|
||||
|
||||
/*
|
||||
* 查询全部借阅记录,并发加载关联的用户和图书信息
|
||||
*/
|
||||
override fun getAllBorrows(): Result<Any?> {
|
||||
val borrows = borrowRecordMapper.selectList(null)
|
||||
if (borrows.isEmpty()) {
|
||||
log.info("[AdminBorrow] getAll: no records")
|
||||
return Result.success(null)
|
||||
return Result.notFound("No borrow records found")
|
||||
}
|
||||
val userIds = borrows.map { it.userId }.distinct()
|
||||
val bookIds = borrows.map { it.bookId }.distinct()
|
||||
|
||||
val userMap = userMapper.selectByIds(userIds).associateBy { it.id }
|
||||
val bookMap = bookMapper.selectByIds(bookIds).associateBy { it.id }
|
||||
|
||||
val result = borrows.map { borrow ->
|
||||
val result = runBlocking { buildBorrowVos(borrows, userIds, bookIds) }
|
||||
|
||||
log.info("[AdminBorrow] getAll: found {} records", result.size)
|
||||
return Result.success(result)
|
||||
}
|
||||
|
||||
// 并发查询用户和图书信息,组装为 BorrowInfoVo 列表
|
||||
private suspend fun buildBorrowVos(
|
||||
borrows: List<BorrowRecord>,
|
||||
userIds: List<Long>,
|
||||
bookIds: List<Long>,
|
||||
): List<BorrowInfoVo> = coroutineScope {
|
||||
val userMapDeferred = async(Dispatchers.IO) {
|
||||
userMapper.selectByIds(userIds).associateBy { it.id }
|
||||
}
|
||||
val bookMapDeferred = async(Dispatchers.IO) {
|
||||
bookMapper.selectByIds(bookIds).associateBy { it.id }
|
||||
}
|
||||
val userMap = userMapDeferred.await()
|
||||
val bookMap = bookMapDeferred.await()
|
||||
|
||||
borrows.map { borrow ->
|
||||
BorrowInfoVo(
|
||||
id = borrow.id!!,
|
||||
borrowTime = borrow.borrowTime,
|
||||
@@ -58,13 +159,10 @@ class AdminBorrowServiceImpl(
|
||||
UserBorrowVo(
|
||||
id = user.id!!,
|
||||
username = user.username,
|
||||
role = user.role
|
||||
role = user.role,
|
||||
)
|
||||
} ?: UserBorrowVo()
|
||||
} ?: UserBorrowVo(),
|
||||
)
|
||||
}
|
||||
|
||||
log.info("[AdminBorrow] getAll: found {} records", result.size)
|
||||
return Result.success(result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
package com.msksbr.bookmgr.service.impl
|
||||
|
||||
import com.msksbr.bookmgr.mapper.BookMapper
|
||||
import com.msksbr.bookmgr.mapper.BorrowRecordMapper
|
||||
import com.msksbr.bookmgr.script.log
|
||||
import com.msksbr.bookmgr.service.DashBoardService
|
||||
import com.msksbr.bookmgr.template.Result
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
/*
|
||||
* 仪表盘服务实现
|
||||
* 聚合 BookMapper 和 BorrowRecordMapper 提供全量数据查询
|
||||
*/
|
||||
|
||||
// @TODO: 拆分到book和admin borrow服务中
|
||||
@Service
|
||||
class DashBoardServiceImpl(
|
||||
private val bookMapper: BookMapper,
|
||||
private val borrowRecordMapper: BorrowRecordMapper
|
||||
) : DashBoardService {
|
||||
override fun getAllBooks(): Result<Any?> {
|
||||
val bookList = bookMapper.selectList(null)
|
||||
val bookJsonList = bookList.map { book ->
|
||||
mapOf(
|
||||
"id" to book.id,
|
||||
"name" to book.name,
|
||||
"author" to book.author,
|
||||
"stock" to book.stock,
|
||||
)
|
||||
}
|
||||
log.debug("[DashBoard] getAllBooks completed, count={}", bookJsonList.size)
|
||||
return Result.success(bookJsonList)
|
||||
}
|
||||
|
||||
// @TODO: 改成返回BorrowInfoDto
|
||||
override fun getAllBorrowRecords(): Result<Any?> {
|
||||
val borrowRecordList = borrowRecordMapper.selectList(null)
|
||||
val borrowRecordJsonList = borrowRecordList.map { borrowRecord ->
|
||||
mapOf(
|
||||
"id" to borrowRecord.id,
|
||||
"userId" to borrowRecord.userId,
|
||||
"bookId" to borrowRecord.bookId,
|
||||
"borrowTime" to borrowRecord.borrowTime,
|
||||
"returnTime" to borrowRecord.returnTime,
|
||||
"status" to borrowRecord.status,
|
||||
)
|
||||
}
|
||||
log.debug("[DashBoard] getAllBorrowRecords completed, count={}", borrowRecordJsonList.size)
|
||||
return Result.success(borrowRecordJsonList)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user