diff --git a/build.gradle.kts b/build.gradle.kts index 80b76a5..c0bdd33 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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") diff --git a/src/main/kotlin/com/msksbr/bookmgr/controller/AdminBorrowController.kt b/src/main/kotlin/com/msksbr/bookmgr/controller/AdminBorrowController.kt index 07408a3..caf3f75 100644 --- a/src/main/kotlin/com/msksbr/bookmgr/controller/AdminBorrowController.kt +++ b/src/main/kotlin/com/msksbr/bookmgr/controller/AdminBorrowController.kt @@ -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 { + 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 { + 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( diff --git a/src/main/kotlin/com/msksbr/bookmgr/controller/DashBoardController.kt b/src/main/kotlin/com/msksbr/bookmgr/controller/DashBoardController.kt deleted file mode 100644 index 916578c..0000000 --- a/src/main/kotlin/com/msksbr/bookmgr/controller/DashBoardController.kt +++ /dev/null @@ -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 { - 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 { - log.info("[DashBoard] getAllBorrowRecords: user={}, ip={}", username ?: "unknown", ipExtractor.getRealIp(request)) - log.info("[DashBoard] user agent: {}", request.getHeader("User-Agent")) - return dashBoardService.getAllBorrowRecords() - } -} diff --git a/src/main/kotlin/com/msksbr/bookmgr/service/DashBoardService.kt b/src/main/kotlin/com/msksbr/bookmgr/service/DashBoardService.kt deleted file mode 100644 index 27250e7..0000000 --- a/src/main/kotlin/com/msksbr/bookmgr/service/DashBoardService.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.msksbr.bookmgr.service - -import com.msksbr.bookmgr.template.Result - -/* -* 仪表盘服务接口 -* 聚合图书全量查询和借阅记录全量查询 -*/ -interface DashBoardService { - /* - * 查询全部图书列表 - * @return 包含所有图书的 JSON 列表 - */ - fun getAllBooks(): Result - /* - * 查询全部借阅记录列表 - * @return 包含所有借阅记录的 JSON 列表 - */ - fun getAllBorrowRecords(): Result -} \ No newline at end of file diff --git a/src/main/kotlin/com/msksbr/bookmgr/service/impl/AdminBorrowServiceImpl.kt b/src/main/kotlin/com/msksbr/bookmgr/service/impl/AdminBorrowServiceImpl.kt index 168c451..a82b616 100644 --- a/src/main/kotlin/com/msksbr/bookmgr/service/impl/AdminBorrowServiceImpl.kt +++ b/src/main/kotlin/com/msksbr/bookmgr/service/impl/AdminBorrowServiceImpl.kt @@ -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 { - 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() + .like("username", query) + .or() + .like("role", query) + ).map { it.id } + } + val bookDeferred = async(Dispatchers.IO) { + bookMapper.selectList( + QueryWrapper() + .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() + .`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 { - 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 { 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, + userIds: List, + bookIds: List, + ): List = 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) } } diff --git a/src/main/kotlin/com/msksbr/bookmgr/service/impl/DashBoardServiceImpl.kt b/src/main/kotlin/com/msksbr/bookmgr/service/impl/DashBoardServiceImpl.kt deleted file mode 100644 index 31e76a1..0000000 --- a/src/main/kotlin/com/msksbr/bookmgr/service/impl/DashBoardServiceImpl.kt +++ /dev/null @@ -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 { - 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 { - 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) - } -}