feat(admin-borrows): implement admin borrow management endpoints

- Add getAllBorrows, getOneBorrow, searchBorrows, and returnBook endpoints
- Implement AdminBorrowServiceImpl with join-based record queries
- Add getAllBooks endpoint to BookController
- Include role validation, IP extraction, and audit logging
This commit is contained in:
2026-05-23 11:41:17 +08:00
parent 072d61abb3
commit 32aed36ebf
14 changed files with 214 additions and 45 deletions
@@ -1,15 +1,42 @@
package com.msksbr.bookmgr.controller 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.AdminBorrowService
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 import org.springframework.web.bind.annotation.RestController
/* /*
* 借阅管理接口(面向管理员) * 借阅管理接口(面向管理员)
* 路径前缀(待定)/api/admin/borrows * 路径前缀:/api/admin/borrows
* *
* 计划接口: * 接口:
* - 全量获取借阅记录 * - 全量获取借阅记录
* - 全量搜索借阅记录 * - 全量搜索借阅记录
* - 单条借阅详情
*
* 全部接口需 admin 角色,由 @RequireRole 切面校验
*/ */
@RestController @RestController
class AdminBorrowController { @RequestMapping("/api/admin/borrows")
class AdminBorrowController(
private val adminBorrowService: AdminBorrowService,
private val ipExtractor: IpExtractor
) {
@RequireRole("admin")
@GetMapping("/getall")
fun getAllBorrows(
@RequestAttribute(required = false) username: String?,
request: HttpServletRequest
): Result<Any?> {
log.info("[AdminBorrow] getAll: user={}, ip={}", username ?: "guest", ipExtractor.getRealIp(request))
log.info("[AdminBorrow] user agent: {}", request.getHeader("User-Agent"))
return adminBorrowService.getAllBorrows()
}
} }
@@ -12,7 +12,7 @@ import org.springframework.web.bind.annotation.RestController
/* /*
* 图书接口(面向普通用户) * 图书接口(面向普通用户)
* 路径前缀(待定)/api/books * 路径前缀:/api/books
* *
* 接口: * 接口:
* - 图书列表查询(搜索) * - 图书列表查询(搜索)
@@ -31,14 +31,25 @@ class BookController(private val bookService: BookService, private val ipExtract
log.info("[Book] user agent: {}, ip={}", request.getHeader("User-Agent"), ipExtractor.getRealIp(request)) log.info("[Book] user agent: {}, ip={}", request.getHeader("User-Agent"), ipExtractor.getRealIp(request))
return bookService.searchBook(query) return bookService.searchBook(query)
} }
@GetMapping("/getone") @GetMapping("/getone")
fun getOneBook( fun getOneBook(
@RequestAttribute(required = false) username: String?, @RequestAttribute(required = false) username: String?,
request: HttpServletRequest, request: HttpServletRequest,
id: Long id: Long
): Result<Any?> { ): Result<Any?> {
log.info("[Book] getOne: user={}, id={}", username ?: "guest", id) log.info("[Book] getOne: user={}, book id={}", username ?: "guest", id)
log.info("[Book] user agent: {}, ip={}", request.getHeader("User-Agent"), ipExtractor.getRealIp(request)) log.info("[Book] user agent: {}, ip={}", request.getHeader("User-Agent"), ipExtractor.getRealIp(request))
return bookService.getOneBook(id) return bookService.getOneBook(id)
} }
@GetMapping("/getall")
fun getAllBooks(
@RequestAttribute(required = false) username: String?,
request: HttpServletRequest
): Result<Any?> {
log.info("[Book] getAll: user={}", username ?: "guest")
log.info("[Book] user agent: {}, ip={}", request.getHeader("User-Agent"), ipExtractor.getRealIp(request))
return bookService.getAllBooks()
}
} }
@@ -1,14 +0,0 @@
package com.msksbr.bookmgr.dto
import com.msksbr.bookmgr.dto.borrow.BookBorrowDto
import com.msksbr.bookmgr.dto.borrow.UserBorrowDto
import java.util.Date
class BorrowInfoDto(
val id: Long,
val bookBorrowDto: BookBorrowDto,
val userBorrowDto: UserBorrowDto,
val borrowTime: Date,
val returnTime: Date?,
val string: String
)
@@ -1,8 +0,0 @@
package com.msksbr.bookmgr.dto.borrow
data class BookBorrowDto(
val id: Long,
val name: String,
val author: String,
val number: Int = 1
)
@@ -1,7 +0,0 @@
package com.msksbr.bookmgr.dto.borrow
data class UserBorrowDto(
val id: Long,
val username: String,
val role: String
)
@@ -1,6 +1,29 @@
package com.msksbr.bookmgr.service package com.msksbr.bookmgr.service
import com.msksbr.bookmgr.template.Result
/*
* 借阅管理服务接口
* 定义借阅记录的查询逻辑,仅由管理员调用
*/
interface AdminBorrowService { interface AdminBorrowService {
/*
* 搜索借阅记录
* @param query 搜索关键词,按书名或用户名模糊匹配
* @return 匹配的借阅记录列表
*/
fun searchBorrows(query: String): Result<Any?> fun searchBorrows(query: String): Result<Any?>
/*
* 查询单条借阅记录
* @param id 借阅记录 ID
* @return 借阅记录详情,含关联的图书和用户信息
*/
fun getOneBorrow(id: Long): Result<Any?>
/*
* 查询全部借阅记录
* @return 所有借阅记录,含关联的图书和用户信息
*/
fun getAllBorrows(): Result<Any?> fun getAllBorrows(): Result<Any?>
} }
@@ -20,5 +20,9 @@ interface BookService {
* @return 图书实体,不存在时返回 404 * @return 图书实体,不存在时返回 404
*/ */
fun getOneBook(id: Long): Result<Any?> fun getOneBook(id: Long): Result<Any?>
/*
* 查询全部图书列表
* @return 所有图书的列表
*/
fun getAllBooks(): Result<Any?> fun getAllBooks(): Result<Any?>
} }
@@ -1,9 +1,44 @@
package com.msksbr.bookmgr.service package com.msksbr.bookmgr.service
import com.msksbr.bookmgr.template.Result
/*
* 借阅服务接口
* 定义面向普通用户的借阅逻辑契约
*/
interface BorrowService { interface BorrowService {
/*
* 查询当前用户的所有借阅记录
* @param userId 当前用户的 ID
* @return 该用户的所有借阅记录
*/
fun getAllMyBorrows(userId: Long): Result<Any?> fun getAllMyBorrows(userId: Long): Result<Any?>
/*
* 搜索当前用户的借阅记录
* @param query 搜索关键词,按书名模糊匹配
* @return 匹配的借阅记录列表
*/
fun searchMyBorrows(query: String): Result<Any?> fun searchMyBorrows(query: String): Result<Any?>
fun getOneBorrow(borrowId: Long): Result<Any?>
/*
* 查询单条借阅记录
* @param borrowId 借阅记录 ID
* @return 借阅记录详情
*/
fun getOneMyBorrow(borrowId: Long): Result<Any?>
/*
* 借书
* @param bookId 要借的图书 ID
* @return 借阅结果
*/
fun borrowBook(bookId: Long): Result<Any?> fun borrowBook(bookId: Long): Result<Any?>
/*
* 还书
* @param borrowId 借阅记录 ID
* @return 归还结果
*/
fun returnBook(borrowId: Long): Result<Any?> fun returnBook(borrowId: Long): Result<Any?>
} }
@@ -1,15 +1,70 @@
package com.msksbr.bookmgr.service.impl package com.msksbr.bookmgr.service.impl
import com.msksbr.bookmgr.mapper.BookMapper
import com.msksbr.bookmgr.mapper.BorrowRecordMapper
import com.msksbr.bookmgr.mapper.UserMapper
import com.msksbr.bookmgr.script.log
import com.msksbr.bookmgr.service.AdminBorrowService import com.msksbr.bookmgr.service.AdminBorrowService
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 org.springframework.stereotype.Service import org.springframework.stereotype.Service
/*
* 借阅管理服务实现
* 提供借阅记录的全量和单条查询,聚合图书和用户信息
*/
@Service @Service
class AdminBorrowServiceImpl : AdminBorrowService { class AdminBorrowServiceImpl(
private val userMapper: UserMapper,
private val borrowRecordMapper: BorrowRecordMapper,
private val bookMapper: BookMapper
) : AdminBorrowService {
override fun searchBorrows(query: String): Result<Any?> { override fun searchBorrows(query: String): Result<Any?> {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override fun getAllBorrows(): Result<Any?> { override fun getOneBorrow(id: Long): Result<Any?> {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override fun getAllBorrows(): Result<Any?> {
val borrows = borrowRecordMapper.selectList(null)
if (borrows.isEmpty()) {
log.info("[AdminBorrow] getAll: no records")
return Result.success(null)
}
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 ->
BorrowInfoVo(
id = borrow.id!!,
borrowTime = borrow.borrowTime,
returnTime = borrow.returnTime,
status = borrow.status,
bookBorrowVo = bookMap[borrow.bookId]?.let { book ->
BookBorrowVo(
id = book.id!!,
name = book.name,
author = book.author,
)
} ?: BookBorrowVo(),
userBorrowVo = userMap[borrow.userId]?.let { user ->
UserBorrowVo(
id = user.id!!,
username = user.username,
role = user.role
)
} ?: UserBorrowVo()
)
}
log.info("[AdminBorrow] getAll: found {} records", result.size)
return Result.success(result)
}
} }
@@ -5,12 +5,12 @@ import com.msksbr.bookmgr.entity.Book
import com.msksbr.bookmgr.mapper.BookMapper import com.msksbr.bookmgr.mapper.BookMapper
import com.msksbr.bookmgr.script.log import com.msksbr.bookmgr.script.log
import com.msksbr.bookmgr.service.BookService import com.msksbr.bookmgr.service.BookService
import org.springframework.stereotype.Service
import com.msksbr.bookmgr.template.Result import com.msksbr.bookmgr.template.Result
import org.springframework.stereotype.Service
/* /*
* 图书服务实现 * 图书服务实现
* 提供图书搜索单本图书查询功能 * 提供图书搜索单本查询和全量查询功能
*/ */
@Service @Service
class BookServiceImpl(private val bookMapper: BookMapper) : BookService { class BookServiceImpl(private val bookMapper: BookMapper) : BookService {
@@ -51,6 +51,8 @@ class BookServiceImpl(private val bookMapper: BookMapper) : BookService {
} }
override fun getAllBooks(): Result<Any?> { override fun getAllBooks(): Result<Any?> {
TODO("Not yet implemented") val books = bookMapper.selectList(null)
log.info("[Book] getAll: found {} books", books.size)
return Result.success(books)
} }
} }
@@ -2,6 +2,7 @@ package com.msksbr.bookmgr.service.impl
import com.msksbr.bookmgr.service.BorrowService import com.msksbr.bookmgr.service.BorrowService
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import com.msksbr.bookmgr.template.Result
@Service @Service
class BorrowServiceImpl: BorrowService { class BorrowServiceImpl: BorrowService {
@@ -13,7 +14,7 @@ class BorrowServiceImpl: BorrowService {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override fun getOneBorrow(borrowId: Long): Result<Any?> { override fun getOneMyBorrow(borrowId: Long): Result<Any?> {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
@@ -0,0 +1,18 @@
package com.msksbr.bookmgr.vo
import com.msksbr.bookmgr.vo.borrow.BookBorrowVo
import com.msksbr.bookmgr.vo.borrow.UserBorrowVo
import java.util.*
/*
* 借阅记录聚合视图
* 将借阅记录、图书信息和用户信息组装为单一响应对象
*/
data class BorrowInfoVo(
val id: Long = 0,
val bookBorrowVo: BookBorrowVo = BookBorrowVo(),
val userBorrowVo: UserBorrowVo = UserBorrowVo(),
val borrowTime: Date = Date(),
val returnTime: Date? = null,
val status: String = ""
)
@@ -0,0 +1,11 @@
package com.msksbr.bookmgr.vo.borrow
/*
* 借阅记录中的图书信息视图
* 用于组装借阅列表时附带的图书缩略信息
*/
data class BookBorrowVo(
val id: Long = 0,
val name: String = "",
val author: String = "",
)
@@ -0,0 +1,11 @@
package com.msksbr.bookmgr.vo.borrow
/*
* 借阅记录中的用户信息视图
* 用于组装借阅列表时附带的用户缩略信息
*/
data class UserBorrowVo(
val id: Long = 0,
val username: String = "",
val role: String = ""
)