refactor(admin-borrows): simplify returnBook to use recordId instead of bookId and userId

- Change returnBook signature to accept recordId only, reducing coupling
- Improve Javadoc comments across controller, service, and implementation
- Clean up imports and reformat class structure in impl

Closes: #125
This commit is contained in:
2026-05-23 15:08:41 +08:00
parent 5b3e92209d
commit 1037b93a68
3 changed files with 109 additions and 10 deletions
@@ -6,10 +6,7 @@ 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.*
/*
* 借阅管理接口(面向管理员)
@@ -76,4 +73,37 @@ class AdminBorrowController(
log.info("[AdminBorrow] user agent: {}", request.getHeader("User-Agent"))
return adminBorrowService.getAllBorrows()
}
/*
* POST /api/admin/borrows/borrowbook?bookId=xxx&userId=xxx
* 管理员手动借书
*/
@RequireRole("admin")
@PostMapping("/borrowbook")
fun borrowBook(
@RequestAttribute(required = false) username: String?,
bookId: Long,
userId: Long,
request: HttpServletRequest
): Result<Any?> {
log.info("[AdminBorrow] borrowBook: user={}, bookId={}, userId={}", username ?: "guest", bookId, userId)
log.info("[AdminBorrow] user agent: {}, ip={}", request.getHeader("User-Agent"), ipExtractor.getRealIp(request))
return adminBorrowService.borrowBook(bookId, userId)
}
/*
* POST /api/admin/borrows/returnbook?recordId=xxx
* 管理员手动还书
*/
@RequireRole("admin")
@PostMapping("/returnbook")
fun returnBook(
@RequestAttribute(required = false) username: String?,
recordId: Long,
request: HttpServletRequest
): Result<Any?> {
log.info("[AdminBorrow] returnBook: user={}, recordId={}", username ?: "guest", recordId)
log.info("[AdminBorrow] user agent: {}, ip={}", request.getHeader("User-Agent"), ipExtractor.getRealIp(request))
return adminBorrowService.returnBook(recordId)
}
}
@@ -37,9 +37,8 @@ interface AdminBorrowService {
/*
* 管理员手动还书
* @param bookId 要还的图书 ID
* @param userId 还书的用户 ID
* @param recordId 借阅记录 ID
* @return 归还结果
*/
fun returnBook(bookId: Long, userId: Long): Result<Any?>
fun returnBook(recordId: Long): Result<Any?>
}
@@ -18,6 +18,7 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.runBlocking
import org.springframework.stereotype.Service
import java.util.*
/*
* 借阅管理服务实现
@@ -131,14 +132,83 @@ class AdminBorrowServiceImpl(
* 管理员手动借书
*/
override fun borrowBook(bookId: Long, userId: Long): Result<Any?> {
TODO("Not yet implemented")
val (matchedUser, matchedBook) = runBlocking {
val userDeferred = async(Dispatchers.IO) {
userMapper.selectById(userId)
}
val bookDeferred = async(Dispatchers.IO) {
bookMapper.selectById(bookId)
}
userDeferred.await() to bookDeferred.await()
}
if (matchedUser == null) {
log.warn("[AdminBorrow] borrowBook: user not found, userId={}", userId)
return Result.error("User does not exist")
}
if (matchedBook == null) {
log.warn("[AdminBorrow] borrowBook: book not found, bookId={}", bookId)
return Result.error("Book does not exist")
}
if (matchedBook.stock < 1) {
log.warn("[AdminBorrow] borrowBook: book out of stock, bookId={}, stock={}", bookId, matchedBook.stock)
return Result.conflict("Book is out of stock")
}
val book = Book(
id = matchedBook.id,
name = matchedBook.name,
author = matchedBook.author,
stock = matchedBook.stock - 1,
)
val borrow = BorrowRecord(
id = null,
userId = userId,
bookId = bookId,
borrowTime = Date(),
returnTime = null,
status = "BORROWED"
)
bookMapper.updateById(book)
borrowRecordMapper.insert(borrow)
log.info("[AdminBorrow] borrowBook: success, userId={}, bookId={}, book={}", userId, bookId, matchedBook.name)
return Result.success("Book borrowed successfully")
}
/*
* 管理员手动还书
*/
override fun returnBook(bookId: Long, userId: Long): Result<Any?> {
TODO("Not yet implemented")
override fun returnBook(recordId: Long): Result<Any?> {
val matchedBorrow = borrowRecordMapper.selectById(recordId)
if (matchedBorrow == null) {
log.warn("[AdminBorrow] returnBook: record not found, recordId={}", recordId)
return Result.error("Borrow record does not exist")
}
if (matchedBorrow.status == "RETURNED") {
log.warn("[AdminBorrow] returnBook: already returned, recordId={}", recordId)
return Result.conflict("Book has already been returned")
}
val borrow = BorrowRecord(
id = matchedBorrow.id,
bookId = matchedBorrow.bookId,
userId = matchedBorrow.userId,
borrowTime = matchedBorrow.borrowTime,
returnTime = Date(),
status = "RETURNED"
)
val matchedBook = bookMapper.selectById(matchedBorrow.bookId)
if (matchedBook == null) {
log.warn("[AdminBorrow] returnBook: book not found, bookId={}", matchedBorrow.bookId)
return Result.error("Book does not exist")
}
val book = Book(
id = matchedBook.id,
name = matchedBook.name,
author = matchedBook.author,
stock = matchedBook.stock + 1,
)
bookMapper.updateById(book)
borrowRecordMapper.updateById(borrow)
log.info("[AdminBorrow] returnBook: success, recordId={}, userId={}, book={}", recordId, borrow.userId, matchedBook.name)
return Result.success("Book returned successfully")
}
// 并发查询用户和图书信息,组装为 BorrowInfoVo 列表