refactor(admin): remove unused imports and clean up code

- Remove unused IpExtractor and HttpServletRequest from
  admin controllers
- Remove unused log import from service implementations
- Reorganize and alphabetize imports
- Update class-level doc comments for consistency
This commit is contained in:
2026-05-24 00:08:23 +08:00
parent 65a5718f9c
commit 21dc992971
11 changed files with 277 additions and 175 deletions
@@ -0,0 +1,263 @@
package com.msksbr.bookmgr.config
import com.msksbr.bookmgr.dto.UserLoginDto
import com.msksbr.bookmgr.entity.Book
import com.msksbr.bookmgr.entity.User
import com.msksbr.bookmgr.script.audit
import com.msksbr.bookmgr.template.ApiResult
import com.msksbr.bookmgr.vo.BorrowInfoVo
import com.msksbr.bookmgr.vo.LoginVo
import com.msksbr.bookmgr.vo.MyBorrowVo
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Pointcut
import org.aspectj.lang.reflect.MethodSignature
import org.slf4j.MDC
import org.springframework.stereotype.Component
import org.springframework.web.context.request.RequestContextHolder
import org.springframework.web.context.request.ServletRequestAttributes
/*
* 审计日志切面(等保三级 GB/T 22239-2019 8.1.4.3
*
* 覆盖范围:
* - controller 层:记录用户标识、来源 IP、User-Agent、HTTP 方法/路径、操作参数、结果状态码
* - service 层:记录业务操作、参数、结果摘要(含实体 id/name)
*
* 用户上下文传递:controller 切面将用户信息写入 MDCservice 切面从 MDC 读取
* 登录盲区处理:JWT 未填充时(如 /login),从请求参数/请求体中提取用户标识
*
* 敏感参数掩码:参数名匹配 password/token/secret/authorization 时输出 "***"
*
* 日志格式(结构化 key=value,单行不换行):
* AUDIT | event=CTRL | user=admin | userId=1 | role=admin | ip=127.0.0.1 |
* method=POST | path=/api/auth/login | ua=Mozilla/5.0... | op=AuthController.login |
* args=[UserLoginDto(username=admin, password="***")]
* AUDIT | event=CTRL | user=admin | userId=1 | role=admin | ip=127.0.0.1 |
* op=AuthController.login | result=SUCCESS | code=200 | ids=[] | elapsed=12ms
*
* 日志级别:成功 → INFO,异常 → WARN
* 建议为 "AUDIT" logger 配置独立 FileAppender 并设置文件权限 640
*/
@Aspect
@Component
class AuditAspect(private val ipExtractor: IpExtractor) {
companion object {
const val MDC_USER = "auditUser"
const val MDC_USER_ID = "auditUserId"
const val MDC_ROLE = "auditRole"
const val MDC_IP = "auditIp"
}
private val sensitiveParamNames = setOf("password", "token", "secret", "authorization")
// ── Pointcuts ────────────────────────────────────────────────
@Pointcut("within(com.msksbr.bookmgr.controller..*)")
fun controller() {
}
@Pointcut("within(com.msksbr.bookmgr.service.impl..*)")
fun serviceImpl() {
}
// ── Controller audit ─────────────────────────────────────────
@Around("controller()")
fun auditController(joinPoint: ProceedingJoinPoint): Any? {
val ctx = extractHttpContext(joinPoint)
populateMdc(ctx)
val op = "${joinPoint.target::class.simpleName}.${joinPoint.signature.name}"
val args = formatArgs(joinPoint)
val ids = extractKeyIds(joinPoint)
audit.info(
"event=CTRL | user={} | userId={} | role={} | ip={} | method={} | path={} | ua={} | op={} | args=[{}]",
ctx.username, ctx.userId, ctx.role, ctx.ip,
ctx.method, ctx.path, ctx.userAgent, op, args
)
val start = System.currentTimeMillis()
return try {
val result = joinPoint.proceed()
val elapsed = System.currentTimeMillis() - start
val code = (result as? ApiResult<*>)?.code
audit.info(
"event=CTRL | user={} | userId={} | role={} | ip={} | op={} | result=SUCCESS | code={} | ids=[{}] | elapsed={}ms",
ctx.username, ctx.userId, ctx.role, ctx.ip, op, code, ids, elapsed
)
result
} catch (e: Throwable) {
val elapsed = System.currentTimeMillis() - start
audit.warn(
"event=CTRL | user={} | userId={} | role={} | ip={} | op={} | result=FAILURE | error={} | ids=[{}] | elapsed={}ms",
ctx.username, ctx.userId, ctx.role, ctx.ip, op, e.message ?: e::class.simpleName, ids, elapsed
)
throw e
} finally {
clearMdc()
}
}
// ── Service audit ────────────────────────────────────────────
@Around("serviceImpl()")
fun auditService(joinPoint: ProceedingJoinPoint): Any? {
val user = MDC.get(MDC_USER) ?: "guest"
val userId = MDC.get(MDC_USER_ID) ?: "?"
val op = "${joinPoint.target::class.simpleName}.${joinPoint.signature.name}"
val args = formatArgs(joinPoint)
val ids = extractKeyIds(joinPoint)
audit.info("event=SVC | user={} | userId={} | op={} | args=[{}]", user, userId, op, args)
val start = System.currentTimeMillis()
return try {
val result = joinPoint.proceed()
val elapsed = System.currentTimeMillis() - start
val summary = summarizeResult(result)
audit.info(
"event=SVC | user={} | userId={} | op={} | result=SUCCESS | summary=[{}] | ids=[{}] | elapsed={}ms",
user, userId, op, summary, ids, elapsed
)
result
} catch (e: Throwable) {
val elapsed = System.currentTimeMillis() - start
audit.warn(
"event=SVC | user={} | userId={} | op={} | result=FAILURE | error={} | ids=[{}] | elapsed={}ms",
user, userId, op, e.message ?: e::class.simpleName, ids, elapsed
)
throw e
}
}
// ── HTTP context ─────────────────────────────────────────────
private data class HttpCtx(
val username: String,
val userId: String,
val role: String,
val ip: String,
val method: String,
val path: String,
val userAgent: String
)
private fun extractHttpContext(joinPoint: ProceedingJoinPoint): HttpCtx {
val attrs = try {
(RequestContextHolder.currentRequestAttributes() as ServletRequestAttributes)
} catch (_: IllegalStateException) {
return HttpCtx("guest", "?", "guest", "0.0.0.0", "?", "?", "?")
}
val request = attrs.request
val jwtUser = request.getAttribute("username") as? String
val jwtUserId = request.getAttribute("userId")?.toString()
val jwtRole = request.getAttribute("role") as? String
return if (jwtUser != null) {
HttpCtx(
jwtUser, jwtUserId ?: "?", jwtRole ?: "guest",
ipExtractor.getRealIp(request), request.method, request.requestURI,
request.getHeader("User-Agent") ?: "?"
)
} else {
// JWT 未填充时(如 /login),尝试从方法参数中提取身份标识
val dtoUser = extractLoginUsername(joinPoint)
HttpCtx(
dtoUser ?: "guest", "?", "guest",
ipExtractor.getRealIp(request), request.method, request.requestURI,
request.getHeader("User-Agent") ?: "?"
)
}
}
// 从方法参数中提取 UserLoginDto 的用户名(用于登录端点等 JWT 未填充的场景)
private fun extractLoginUsername(joinPoint: ProceedingJoinPoint): String? =
joinPoint.args.firstNotNullOfOrNull { arg ->
(arg as? UserLoginDto)?.username
}
// ── MDC ──────────────────────────────────────────────────────
private fun populateMdc(ctx: HttpCtx) {
MDC.put(MDC_USER, ctx.username)
MDC.put(MDC_USER_ID, ctx.userId)
MDC.put(MDC_ROLE, ctx.role)
MDC.put(MDC_IP, ctx.ip)
}
private fun clearMdc() {
MDC.remove(MDC_USER)
MDC.remove(MDC_USER_ID)
MDC.remove(MDC_ROLE)
MDC.remove(MDC_IP)
}
// ── Argument formatting ──────────────────────────────────────
private fun formatArgs(joinPoint: ProceedingJoinPoint): String {
val paramNames = runCatching {
(joinPoint.signature as MethodSignature).parameterNames
}.getOrNull() ?: emptyArray()
return joinPoint.args.mapIndexed { idx, arg ->
val paramName = paramNames.getOrNull(idx)?.lowercase() ?: ""
when {
arg == null -> "null"
arg is HttpServletRequest -> "req"
arg is HttpServletResponse -> "resp"
sensitiveParamNames.any { paramName.contains(it) } -> "\"***\""
else -> arg.toString()
}
}.joinToString(", ")
}
// ── Key ID extraction ───────────────────────────────────────
/*
* 从方法参数中提取所有 ID 类参数(以 "id" 结尾的 Long/Int 参数)
* 用于出口日志定位操作对象,回答"操作了哪条记录"
*/
private fun extractKeyIds(joinPoint: ProceedingJoinPoint): String {
val paramNames = runCatching {
(joinPoint.signature as MethodSignature).parameterNames
}.getOrNull() ?: emptyArray()
return joinPoint.args.zip(paramNames.asIterable())
.filter { (arg, name) ->
name.lowercase().let { it.endsWith("id") || it == "id" || it == "query" }
&& arg is Number
}
.joinToString(", ") { (arg, name) -> "$name=$arg" }
}
// ── Result summarization ─────────────────────────────────────
private fun summarizeResult(result: Any?): String = when (result) {
null -> "null"
is ApiResult<*> -> buildString {
append("code=${result.code}")
result.data?.let { append(", data=").append(dataSummary(it)) }
}
is Collection<*> -> "Collection(size=${result.size})"
is String -> result.take(200)
else -> "${result::class.simpleName}"
}
private fun dataSummary(data: Any): String = when (data) {
is Book -> "Book(id=${data.id}, name=${data.name}, author=${data.author})"
is User -> "User(id=${data.id}, username=${data.username}, role=${data.role})"
is LoginVo -> "LoginVo(username=${data.username}, role=${data.role})"
is MyBorrowVo -> "MyBorrowVo(id=${data.id}, status=${data.status}, book=${data.bookBorrowVo.name})"
is BorrowInfoVo -> "BorrowInfoVo(id=${data.id}, status=${data.status}, book=${data.bookBorrowVo.name}, user=${data.userBorrowVo.username})"
is List<*> -> "List(size=${data.size})"
is String -> data.take(100)
else -> "${data::class.simpleName}"
}
}
@@ -1,13 +1,10 @@
package com.msksbr.bookmgr.controller package com.msksbr.bookmgr.controller
import com.msksbr.bookmgr.annotation.RequireRole import com.msksbr.bookmgr.annotation.RequireRole
import com.msksbr.bookmgr.config.IpExtractor
import com.msksbr.bookmgr.dto.BookAddDto import com.msksbr.bookmgr.dto.BookAddDto
import com.msksbr.bookmgr.dto.BookUpdateDto import com.msksbr.bookmgr.dto.BookUpdateDto
import com.msksbr.bookmgr.script.log
import com.msksbr.bookmgr.service.AdminBookService import com.msksbr.bookmgr.service.AdminBookService
import com.msksbr.bookmgr.template.ApiResult import com.msksbr.bookmgr.template.ApiResult
import jakarta.servlet.http.HttpServletRequest
import jakarta.validation.Valid import jakarta.validation.Valid
import org.springframework.web.bind.annotation.* import org.springframework.web.bind.annotation.*
@@ -25,7 +22,7 @@ import org.springframework.web.bind.annotation.*
*/ */
@RestController @RestController
@RequestMapping("/api/admin/books") @RequestMapping("/api/admin/books")
class AdminBookController(private val adminBookService: AdminBookService, private val ipExtractor: IpExtractor) { class AdminBookController(private val adminBookService: AdminBookService) {
/* /*
* POST /api/admin/books/add * POST /api/admin/books/add
@@ -37,16 +34,7 @@ class AdminBookController(private val adminBookService: AdminBookService, privat
@Valid @Valid
@RequestBody @RequestBody
bookAddDto: BookAddDto, bookAddDto: BookAddDto,
request: HttpServletRequest,
@RequestAttribute(required = false) username: String?
): ApiResult<String> { ): ApiResult<String> {
log.info(
"[AdminBook] add: user={}, name={}, author={}",
username ?: "guest",
bookAddDto.name,
bookAddDto.author
)
log.info("[AdminBook] user agent: {}, ip={}", request.getHeader("User-Agent"), ipExtractor.getRealIp(request))
return adminBookService.addBook(bookAddDto) return adminBookService.addBook(bookAddDto)
} }
@@ -61,11 +49,7 @@ class AdminBookController(private val adminBookService: AdminBookService, privat
@Valid @Valid
@RequestBody @RequestBody
bookUpdateDto: BookUpdateDto, bookUpdateDto: BookUpdateDto,
request: HttpServletRequest,
@RequestAttribute(required = false) username: String?
): ApiResult<String> { ): ApiResult<String> {
log.info("[AdminBook] update: user={}, id={}", username ?: "guest", id)
log.info("[AdminBook] user agent: {}, ip={}", request.getHeader("User-Agent"), ipExtractor.getRealIp(request))
return adminBookService.updateBook(id, bookUpdateDto) return adminBookService.updateBook(id, bookUpdateDto)
} }
@@ -77,11 +61,7 @@ class AdminBookController(private val adminBookService: AdminBookService, privat
@PostMapping("/delete") @PostMapping("/delete")
fun deleteBook( fun deleteBook(
id: Long, id: Long,
request: HttpServletRequest,
@RequestAttribute(required = false) username: String?
): ApiResult<String> { ): ApiResult<String> {
log.info("[AdminBook] delete: user={}, id={}", username ?: "guest", id)
log.info("[AdminBook] user agent: {}, ip={}", request.getHeader("User-Agent"), ipExtractor.getRealIp(request))
return adminBookService.deleteBook(id) return adminBookService.deleteBook(id)
} }
@@ -94,11 +74,7 @@ class AdminBookController(private val adminBookService: AdminBookService, privat
fun updateStock( fun updateStock(
id: Long, id: Long,
stock: Int, stock: Int,
request: HttpServletRequest,
@RequestAttribute(required = false) username: String?
): ApiResult<String> { ): ApiResult<String> {
log.info("[AdminBook] updateStock: user={}, id={}, stock={}", username ?: "guest", id, stock)
log.info("[AdminBook] user agent: {}, ip={}", request.getHeader("User-Agent"), ipExtractor.getRealIp(request))
return adminBookService.updateStock(id, stock) return adminBookService.updateStock(id, stock)
} }
} }
@@ -1,12 +1,9 @@
package com.msksbr.bookmgr.controller package com.msksbr.bookmgr.controller
import com.msksbr.bookmgr.annotation.RequireRole 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.service.AdminBorrowService
import com.msksbr.bookmgr.template.ApiResult import com.msksbr.bookmgr.template.ApiResult
import com.msksbr.bookmgr.vo.BorrowInfoVo import com.msksbr.bookmgr.vo.BorrowInfoVo
import jakarta.servlet.http.HttpServletRequest
import org.springframework.web.bind.annotation.* import org.springframework.web.bind.annotation.*
/* /*
@@ -26,7 +23,6 @@ import org.springframework.web.bind.annotation.*
@RequestMapping("/api/admin/borrows") @RequestMapping("/api/admin/borrows")
class AdminBorrowController( class AdminBorrowController(
private val adminBorrowService: AdminBorrowService, private val adminBorrowService: AdminBorrowService,
private val ipExtractor: IpExtractor
) { ) {
/* /*
* GET /api/admin/borrows/search?query=xxx * GET /api/admin/borrows/search?query=xxx
@@ -35,12 +31,8 @@ class AdminBorrowController(
@RequireRole("admin") @RequireRole("admin")
@GetMapping("/search") @GetMapping("/search")
fun searchBorrows( fun searchBorrows(
@RequestAttribute(required = false) username: String?,
query: String, query: String,
request: HttpServletRequest
): ApiResult<List<BorrowInfoVo>> { ): ApiResult<List<BorrowInfoVo>> {
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) return adminBorrowService.searchBorrows(query)
} }
@@ -51,12 +43,8 @@ class AdminBorrowController(
@RequireRole("admin") @RequireRole("admin")
@GetMapping("/getone") @GetMapping("/getone")
fun getOneBorrow( fun getOneBorrow(
@RequestAttribute(required = false) username: String?,
id: Long, id: Long,
request: HttpServletRequest
): ApiResult<BorrowInfoVo> { ): ApiResult<BorrowInfoVo> {
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) return adminBorrowService.getOneBorrow(id)
} }
@@ -66,12 +54,7 @@ class AdminBorrowController(
*/ */
@RequireRole("admin") @RequireRole("admin")
@GetMapping("/getall") @GetMapping("/getall")
fun getAllBorrows( fun getAllBorrows(): ApiResult<List<BorrowInfoVo>> {
@RequestAttribute(required = false) username: String?,
request: HttpServletRequest
): ApiResult<List<BorrowInfoVo>> {
log.info("[AdminBorrow] getAll: user={}, ip={}", username ?: "guest", ipExtractor.getRealIp(request))
log.info("[AdminBorrow] user agent: {}", request.getHeader("User-Agent"))
return adminBorrowService.getAllBorrows() return adminBorrowService.getAllBorrows()
} }
@@ -82,13 +65,9 @@ class AdminBorrowController(
@RequireRole("admin") @RequireRole("admin")
@PostMapping("/borrowbook") @PostMapping("/borrowbook")
fun borrowBook( fun borrowBook(
@RequestAttribute(required = false) username: String?,
bookId: Long, bookId: Long,
userId: Long, userId: Long,
request: HttpServletRequest
): ApiResult<String> { ): ApiResult<String> {
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) return adminBorrowService.borrowBook(bookId, userId)
} }
@@ -99,12 +78,8 @@ class AdminBorrowController(
@RequireRole("admin") @RequireRole("admin")
@PostMapping("/returnbook") @PostMapping("/returnbook")
fun returnBook( fun returnBook(
@RequestAttribute(required = false) username: String?,
recordId: Long, recordId: Long,
request: HttpServletRequest
): ApiResult<String> { ): ApiResult<String> {
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) return adminBorrowService.returnBook(recordId)
} }
} }
@@ -1,13 +1,10 @@
package com.msksbr.bookmgr.controller package com.msksbr.bookmgr.controller
import com.msksbr.bookmgr.config.IpExtractor
import com.msksbr.bookmgr.config.JwtUtils import com.msksbr.bookmgr.config.JwtUtils
import com.msksbr.bookmgr.dto.UserLoginDto import com.msksbr.bookmgr.dto.UserLoginDto
import com.msksbr.bookmgr.script.log
import com.msksbr.bookmgr.service.AuthService import com.msksbr.bookmgr.service.AuthService
import com.msksbr.bookmgr.template.ApiResult import com.msksbr.bookmgr.template.ApiResult
import com.msksbr.bookmgr.vo.LoginVo import com.msksbr.bookmgr.vo.LoginVo
import jakarta.servlet.http.HttpServletRequest
import jakarta.validation.Valid import jakarta.validation.Valid
import org.springframework.web.bind.annotation.* import org.springframework.web.bind.annotation.*
@@ -21,7 +18,6 @@ import org.springframework.web.bind.annotation.*
@RequestMapping("/api/auth") @RequestMapping("/api/auth")
class AuthController( class AuthController(
val authService: AuthService, val authService: AuthService,
val ipExtractor: IpExtractor,
private val jwtUtils: JwtUtils private val jwtUtils: JwtUtils
) { ) {
/* /*
@@ -42,15 +38,11 @@ class AuthController(
@Valid @Valid
@RequestBody @RequestBody
loginDTO: UserLoginDto, loginDTO: UserLoginDto,
request: HttpServletRequest
): ApiResult<LoginVo> { ): ApiResult<LoginVo> {
log.info("[Auth] login attempt: user={}, ip={}", loginDTO.username, ipExtractor.getRealIp(request))
log.debug("[Auth] user agent: {}", request.getHeader("User-Agent"))
val user = authService.login(loginDTO) val user = authService.login(loginDTO)
return if (user != null) { return if (user != null) {
// 登录成功,签发 JWT // 登录成功,签发 JWT
val token = jwtUtils.generateToken(user.username, user.role, user.id!!) val token = jwtUtils.generateToken(user.username, user.role, user.id!!)
log.info("[Auth] login success: user={}", user.username)
val loginVo = LoginVo( val loginVo = LoginVo(
token = token, token = token,
username = user.username, username = user.username,
@@ -58,7 +50,6 @@ class AuthController(
) )
ApiResult.success(loginVo) ApiResult.success(loginVo)
} else { } else {
log.warn("[Auth] login failed: user={}", loginDTO.username)
ApiResult.unauthorized("Incorrect username or password") ApiResult.unauthorized("Incorrect username or password")
} }
} }
@@ -71,17 +62,7 @@ class AuthController(
* { "code": 200, "message": "success", "data": "logout successfully" } * { "code": 200, "message": "success", "data": "logout successfully" }
*/ */
@PostMapping("/logout") @PostMapping("/logout")
fun logout( fun logout(): ApiResult<String> {
@RequestAttribute(required = false) username: String?,
request: HttpServletRequest
): ApiResult<String> {
if (username != null) {
log.info("[Auth] logout username: {}", username)
}
log.info("[Auth] logout: ip={}", ipExtractor.getRealIp(request))
log.debug("[Auth] user agent: {}", request.getHeader("User-Agent"))
// JWT 无状态,登出只需客户端删除 token
log.info("[Auth] logout success")
return ApiResult.success("logout successfully") return ApiResult.success("logout successfully")
} }
} }
@@ -1,13 +1,9 @@
package com.msksbr.bookmgr.controller package com.msksbr.bookmgr.controller
import com.msksbr.bookmgr.config.IpExtractor
import com.msksbr.bookmgr.entity.Book import com.msksbr.bookmgr.entity.Book
import com.msksbr.bookmgr.script.log
import com.msksbr.bookmgr.service.BookService import com.msksbr.bookmgr.service.BookService
import com.msksbr.bookmgr.template.ApiResult import com.msksbr.bookmgr.template.ApiResult
import jakarta.servlet.http.HttpServletRequest
import org.springframework.web.bind.annotation.GetMapping 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.RequestMapping
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
@@ -21,19 +17,15 @@ import org.springframework.web.bind.annotation.RestController
*/ */
@RestController @RestController
@RequestMapping("/api/books") @RequestMapping("/api/books")
class BookController(private val bookService: BookService, private val ipExtractor: IpExtractor) { class BookController(private val bookService: BookService) {
/* /*
* GET /api/books/search?query=xxx * GET /api/books/search?query=xxx
* 按书名或作者模糊搜索图书 * 按书名或作者模糊搜索图书
*/ */
@GetMapping("/search") @GetMapping("/search")
fun searchBook( fun searchBook(
@RequestAttribute(required = false) username: String?,
query: String, query: String,
request: HttpServletRequest
): ApiResult<List<Book>> { ): ApiResult<List<Book>> {
log.info("[Book] search: user={}, query={}", username ?: "guest", query)
log.info("[Book] user agent: {}, ip={}", request.getHeader("User-Agent"), ipExtractor.getRealIp(request))
return bookService.searchBook(query) return bookService.searchBook(query)
} }
@@ -43,12 +35,8 @@ class BookController(private val bookService: BookService, private val ipExtract
*/ */
@GetMapping("/getone") @GetMapping("/getone")
fun getOneBook( fun getOneBook(
@RequestAttribute(required = false) username: String?,
request: HttpServletRequest,
id: Long id: Long
): ApiResult<Book> { ): ApiResult<Book> {
log.info("[Book] getOne: user={}, book id={}", username ?: "guest", id)
log.info("[Book] user agent: {}, ip={}", request.getHeader("User-Agent"), ipExtractor.getRealIp(request))
return bookService.getOneBook(id) return bookService.getOneBook(id)
} }
@@ -57,12 +45,7 @@ class BookController(private val bookService: BookService, private val ipExtract
* 查询所有图书 * 查询所有图书
*/ */
@GetMapping("/getall") @GetMapping("/getall")
fun getAllBooks( fun getAllBooks(): ApiResult<List<Book>> {
@RequestAttribute(required = false) username: String?,
request: HttpServletRequest
): ApiResult<List<Book>> {
log.info("[Book] getAll: user={}", username ?: "guest")
log.info("[Book] user agent: {}, ip={}", request.getHeader("User-Agent"), ipExtractor.getRealIp(request))
return bookService.getAllBooks() return bookService.getAllBooks()
} }
} }
@@ -1,12 +1,9 @@
package com.msksbr.bookmgr.controller package com.msksbr.bookmgr.controller
import com.msksbr.bookmgr.annotation.RequireRole import com.msksbr.bookmgr.annotation.RequireRole
import com.msksbr.bookmgr.config.IpExtractor
import com.msksbr.bookmgr.script.log
import com.msksbr.bookmgr.service.BorrowService import com.msksbr.bookmgr.service.BorrowService
import com.msksbr.bookmgr.template.ApiResult import com.msksbr.bookmgr.template.ApiResult
import com.msksbr.bookmgr.vo.MyBorrowVo import com.msksbr.bookmgr.vo.MyBorrowVo
import jakarta.servlet.http.HttpServletRequest
import org.springframework.web.bind.annotation.* import org.springframework.web.bind.annotation.*
/* /*
@@ -26,7 +23,6 @@ import org.springframework.web.bind.annotation.*
@RequestMapping("/api/borrows") @RequestMapping("/api/borrows")
class BorrowController( class BorrowController(
private val borrowService: BorrowService, private val borrowService: BorrowService,
private val ipExtractor: IpExtractor
) { ) {
/* /*
* GET /api/borrows/getall * GET /api/borrows/getall
@@ -35,12 +31,8 @@ class BorrowController(
@RequireRole("user") @RequireRole("user")
@GetMapping("/getall") @GetMapping("/getall")
fun getAllMyBorrows( fun getAllMyBorrows(
@RequestAttribute(required = false) username: String?,
@RequestAttribute(required = false) userId: Long?, @RequestAttribute(required = false) userId: Long?,
request: HttpServletRequest
): ApiResult<List<MyBorrowVo>> { ): ApiResult<List<MyBorrowVo>> {
log.info("[Borrow] getAll: user={}, userId={}", username ?: "guest", userId)
log.info("[Borrow] user agent: {}, ip={}", request.getHeader("User-Agent"), ipExtractor.getRealIp(request))
return borrowService.getAllMyBorrows(userId!!) return borrowService.getAllMyBorrows(userId!!)
} }
@@ -51,13 +43,9 @@ class BorrowController(
@RequireRole("user") @RequireRole("user")
@GetMapping("/search") @GetMapping("/search")
fun searchMyBorrows( fun searchMyBorrows(
@RequestAttribute(required = false) username: String?,
query: String, query: String,
@RequestAttribute(required = false) userId: Long?, @RequestAttribute(required = false) userId: Long?,
request: HttpServletRequest
): ApiResult<List<MyBorrowVo>> { ): ApiResult<List<MyBorrowVo>> {
log.info("[Borrow] search: user={}, userId={}, query={}", username ?: "guest", userId, query)
log.info("[Borrow] user agent: {}, ip={}", request.getHeader("User-Agent"), ipExtractor.getRealIp(request))
return borrowService.searchMyBorrows(query, userId!!) return borrowService.searchMyBorrows(query, userId!!)
} }
@@ -68,13 +56,9 @@ class BorrowController(
@RequireRole("user") @RequireRole("user")
@GetMapping("/getone") @GetMapping("/getone")
fun getOneMyBorrow( fun getOneMyBorrow(
@RequestAttribute(required = false) username: String?,
borrowId: Long, borrowId: Long,
@RequestAttribute(required = false) userId: Long?, @RequestAttribute(required = false) userId: Long?,
request: HttpServletRequest
): ApiResult<MyBorrowVo> { ): ApiResult<MyBorrowVo> {
log.info("[Borrow] getOne: user={}, userId={}, borrowId={}", username ?: "guest", userId, borrowId)
log.info("[Borrow] user agent: {}, ip={}", request.getHeader("User-Agent"), ipExtractor.getRealIp(request))
return borrowService.getOneMyBorrow(borrowId, userId!!) return borrowService.getOneMyBorrow(borrowId, userId!!)
} }
@@ -85,13 +69,9 @@ class BorrowController(
@RequireRole("user") @RequireRole("user")
@PostMapping("/borrowbook") @PostMapping("/borrowbook")
fun borrowBookForMe( fun borrowBookForMe(
@RequestAttribute(required = false) username: String?,
bookId: Long, bookId: Long,
@RequestAttribute(required = false) userId: Long?, @RequestAttribute(required = false) userId: Long?,
request: HttpServletRequest
): ApiResult<String> { ): ApiResult<String> {
log.info("[Borrow] borrow: user={}, userId={}, bookId={}", username ?: "guest", userId, bookId)
log.info("[Borrow] user agent: {}, ip={}", request.getHeader("User-Agent"), ipExtractor.getRealIp(request))
return borrowService.borrowBookForMe(bookId, userId!!) return borrowService.borrowBookForMe(bookId, userId!!)
} }
@@ -102,13 +82,9 @@ class BorrowController(
@RequireRole("user") @RequireRole("user")
@PostMapping("/returnbook") @PostMapping("/returnbook")
fun returnBookForMe( fun returnBookForMe(
@RequestAttribute(required = false) username: String?,
borrowId: Long, borrowId: Long,
@RequestAttribute(required = false) userId: Long?, @RequestAttribute(required = false) userId: Long?,
request: HttpServletRequest
): ApiResult<String> { ): ApiResult<String> {
log.info("[Borrow] return: user={}, userId={}, borrowId={}", username ?: "guest", userId, borrowId)
log.info("[Borrow] user agent: {}, ip={}", request.getHeader("User-Agent"), ipExtractor.getRealIp(request))
return borrowService.returnBookForMe(borrowId, userId!!) return borrowService.returnBookForMe(borrowId, userId!!)
} }
} }
@@ -16,3 +16,11 @@ import org.slf4j.LoggerFactory
*/ */
val <T : Any> T.log: Logger val <T : Any> T.log: Logger
get() = LoggerFactory.getLogger(this::class.java) get() = LoggerFactory.getLogger(this::class.java)
/*
* 审计日志 Logger(等保三级 GB/T 22239-2019
* 使用固定 logger name "AUDIT",业务代码中通过 T.audit.info(...) 调用
* 建议在 logback/log4j 中为 "AUDIT" 配置独立的 FileAppender,并设置文件权限 640
*/
val Any.audit: Logger
get() = LoggerFactory.getLogger("AUDIT")
@@ -5,7 +5,6 @@ import com.msksbr.bookmgr.dto.BookAddDto
import com.msksbr.bookmgr.dto.BookUpdateDto import com.msksbr.bookmgr.dto.BookUpdateDto
import com.msksbr.bookmgr.entity.Book 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.service.AdminBookService import com.msksbr.bookmgr.service.AdminBookService
import com.msksbr.bookmgr.template.ApiResult import com.msksbr.bookmgr.template.ApiResult
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@@ -29,11 +28,9 @@ class AdminBookServiceImpl(private val bookMapper: BookMapper) : AdminBookServic
stock = bookAddDto.stock, stock = bookAddDto.stock,
) )
if (isBookDuplicate(book)) { if (isBookDuplicate(book)) {
log.warn("[AdminBook] add: duplicate book, name={}, author={}", book.name, book.author)
return ApiResult.conflict("Book already exists, please update the stock instead") return ApiResult.conflict("Book already exists, please update the stock instead")
} }
bookMapper.insert(book) bookMapper.insert(book)
log.info("[AdminBook] add: success, id={}, name={}", book.id, book.name)
return ApiResult.success("Book added successfully") return ApiResult.success("Book added successfully")
} }
@@ -48,12 +45,10 @@ class AdminBookServiceImpl(private val bookMapper: BookMapper) : AdminBookServic
bookUpdateDto: BookUpdateDto bookUpdateDto: BookUpdateDto
): ApiResult<String> { ): ApiResult<String> {
if (bookUpdateDto.name.isBlank() && bookUpdateDto.author.isBlank()) { if (bookUpdateDto.name.isBlank() && bookUpdateDto.author.isBlank()) {
log.warn("[AdminBook] update: both name and author are blank, id={}", id)
return ApiResult.badRequest("At least one of name or author must be provided") return ApiResult.badRequest("At least one of name or author must be provided")
} }
val existing = bookMapper.selectById(id) val existing = bookMapper.selectById(id)
?: run { ?: run {
log.warn("[AdminBook] update: book not found, id={}", id)
return ApiResult.notFound("Book does not exist") return ApiResult.notFound("Book does not exist")
} }
val book = Book( val book = Book(
@@ -63,11 +58,9 @@ class AdminBookServiceImpl(private val bookMapper: BookMapper) : AdminBookServic
stock = existing.stock, stock = existing.stock,
) )
if (isBookDuplicate(book, book.id)) { if (isBookDuplicate(book, book.id)) {
log.warn("[AdminBook] update: duplicate book, name={}, author={}", book.name, book.author)
return ApiResult.conflict("Book already exists, please update the stock instead") return ApiResult.conflict("Book already exists, please update the stock instead")
} }
bookMapper.updateById(book) bookMapper.updateById(book)
log.info("[AdminBook] update: success, id={}", book.id)
return ApiResult.success("Book updated successfully") return ApiResult.success("Book updated successfully")
} }
@@ -77,13 +70,11 @@ class AdminBookServiceImpl(private val bookMapper: BookMapper) : AdminBookServic
* @return 操作结果,不存在的书返回 404 * @return 操作结果,不存在的书返回 404
*/ */
override fun deleteBook(id: Long): ApiResult<String> { override fun deleteBook(id: Long): ApiResult<String> {
val existing = bookMapper.selectById(id) bookMapper.selectById(id)
?: run { ?: run {
log.warn("[AdminBook] delete: book not found, id={}", id)
return ApiResult.notFound("Book does not exist") return ApiResult.notFound("Book does not exist")
} }
bookMapper.deleteById(id) bookMapper.deleteById(id)
log.info("[AdminBook] delete: success, id={}, name={}", id, existing.name)
return ApiResult.success("Book deleted successfully") return ApiResult.success("Book deleted successfully")
} }
@@ -95,12 +86,10 @@ class AdminBookServiceImpl(private val bookMapper: BookMapper) : AdminBookServic
*/ */
override fun updateStock(id: Long, stock: Int): ApiResult<String> { override fun updateStock(id: Long, stock: Int): ApiResult<String> {
if (stock <= 0) { if (stock <= 0) {
log.warn("[AdminBook] updateStock: invalid stock={}, id={}", stock, id)
return ApiResult.badRequest("Stock must be greater than zero") return ApiResult.badRequest("Stock must be greater than zero")
} }
val existing = bookMapper.selectById(id) val existing = bookMapper.selectById(id)
?: run { ?: run {
log.warn("[AdminBook] updateStock: book not found, id={}", id)
return ApiResult.notFound("Book does not exist") return ApiResult.notFound("Book does not exist")
} }
val book = Book( val book = Book(
@@ -110,7 +99,6 @@ class AdminBookServiceImpl(private val bookMapper: BookMapper) : AdminBookServic
stock = stock stock = stock
) )
bookMapper.updateById(book) bookMapper.updateById(book)
log.debug("[AdminBook] updateStock: success, id={}, stock={}", id, stock)
return ApiResult.success("Book updated successfully") return ApiResult.success("Book updated successfully")
} }
@@ -7,7 +7,6 @@ import com.msksbr.bookmgr.entity.User
import com.msksbr.bookmgr.mapper.BookMapper import com.msksbr.bookmgr.mapper.BookMapper
import com.msksbr.bookmgr.mapper.BorrowRecordMapper import com.msksbr.bookmgr.mapper.BorrowRecordMapper
import com.msksbr.bookmgr.mapper.UserMapper 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.ApiResult import com.msksbr.bookmgr.template.ApiResult
import com.msksbr.bookmgr.vo.BorrowInfoVo import com.msksbr.bookmgr.vo.BorrowInfoVo
@@ -36,7 +35,6 @@ class AdminBorrowServiceImpl(
*/ */
override fun searchBorrows(query: String): ApiResult<List<BorrowInfoVo>> { override fun searchBorrows(query: String): ApiResult<List<BorrowInfoVo>> {
if (query.isBlank()) { if (query.isBlank()) {
log.warn("[AdminBorrow] search: query is blank")
return ApiResult.badRequest("Search query cannot be empty") return ApiResult.badRequest("Search query cannot be empty")
} }
val (matchedUserIds, matchedBookIds) = runBlocking { val (matchedUserIds, matchedBookIds) = runBlocking {
@@ -59,7 +57,6 @@ class AdminBorrowServiceImpl(
userDeferred.await() to bookDeferred.await() userDeferred.await() to bookDeferred.await()
} }
if (matchedUserIds.isEmpty() && matchedBookIds.isEmpty()) { if (matchedUserIds.isEmpty() && matchedBookIds.isEmpty()) {
log.info("[AdminBorrow] search: no results for {}", query)
return ApiResult.notFound("No matching borrow records found") return ApiResult.notFound("No matching borrow records found")
} }
val borrows = borrowRecordMapper.selectList( val borrows = borrowRecordMapper.selectList(
@@ -74,7 +71,6 @@ class AdminBorrowServiceImpl(
val result = runBlocking { val result = runBlocking {
buildBorrowVos(borrows, userIds, bookIds) buildBorrowVos(borrows, userIds, bookIds)
} }
log.info("[AdminBorrow] search: found {} records for {}", result.size, query)
return ApiResult.success(result) return ApiResult.success(result)
} }
@@ -84,7 +80,6 @@ class AdminBorrowServiceImpl(
override fun getOneBorrow(id: Long): ApiResult<BorrowInfoVo> { override fun getOneBorrow(id: Long): ApiResult<BorrowInfoVo> {
val borrow = borrowRecordMapper.selectById(id) val borrow = borrowRecordMapper.selectById(id)
if (borrow == null) { if (borrow == null) {
log.info("[AdminBorrow] getOne: no record for id={}", id)
return ApiResult.notFound("Borrow record not found") return ApiResult.notFound("Borrow record not found")
} }
val user = userMapper.selectById(borrow.userId) val user = userMapper.selectById(borrow.userId)
@@ -105,7 +100,6 @@ class AdminBorrowServiceImpl(
role = user.role, role = user.role,
) )
) )
log.info("[AdminBorrow] getOne: found record id={}, user={}, book={}", id, user.username, book.name)
return ApiResult.success(result) return ApiResult.success(result)
} }
@@ -115,7 +109,6 @@ class AdminBorrowServiceImpl(
override fun getAllBorrows(): ApiResult<List<BorrowInfoVo>> { override fun getAllBorrows(): ApiResult<List<BorrowInfoVo>> {
val borrows = borrowRecordMapper.selectList(null) val borrows = borrowRecordMapper.selectList(null)
if (borrows.isEmpty()) { if (borrows.isEmpty()) {
log.info("[AdminBorrow] getAll: no records")
return ApiResult.notFound("No borrow records found") return ApiResult.notFound("No borrow records found")
} }
val userIds = borrows.map { it.userId }.distinct() val userIds = borrows.map { it.userId }.distinct()
@@ -124,7 +117,6 @@ class AdminBorrowServiceImpl(
val result = runBlocking { buildBorrowVos(borrows, userIds, bookIds) } val result = runBlocking { buildBorrowVos(borrows, userIds, bookIds) }
log.info("[AdminBorrow] getAll: found {} records", result.size)
return ApiResult.success(result) return ApiResult.success(result)
} }
@@ -142,15 +134,12 @@ class AdminBorrowServiceImpl(
userDeferred.await() to bookDeferred.await() userDeferred.await() to bookDeferred.await()
} }
if (matchedUser == null) { if (matchedUser == null) {
log.warn("[AdminBorrow] borrowBook: user not found, userId={}", userId)
return ApiResult.notFound("User does not exist") return ApiResult.notFound("User does not exist")
} }
if (matchedBook == null) { if (matchedBook == null) {
log.warn("[AdminBorrow] borrowBook: book not found, bookId={}", bookId)
return ApiResult.notFound("Book does not exist") return ApiResult.notFound("Book does not exist")
} }
if (matchedBook.stock < 1) { if (matchedBook.stock < 1) {
log.warn("[AdminBorrow] borrowBook: book out of stock, bookId={}, stock={}", bookId, matchedBook.stock)
return ApiResult.conflict("Book is out of stock") return ApiResult.conflict("Book is out of stock")
} }
val book = Book( val book = Book(
@@ -169,7 +158,6 @@ class AdminBorrowServiceImpl(
) )
bookMapper.updateById(book) bookMapper.updateById(book)
borrowRecordMapper.insert(borrow) borrowRecordMapper.insert(borrow)
log.info("[AdminBorrow] borrowBook: success, userId={}, bookId={}, book={}", userId, bookId, matchedBook.name)
return ApiResult.success("Book borrowed successfully") return ApiResult.success("Book borrowed successfully")
} }
@@ -179,11 +167,9 @@ class AdminBorrowServiceImpl(
override fun returnBook(recordId: Long): ApiResult<String> { override fun returnBook(recordId: Long): ApiResult<String> {
val matchedBorrow = borrowRecordMapper.selectById(recordId) val matchedBorrow = borrowRecordMapper.selectById(recordId)
if (matchedBorrow == null) { if (matchedBorrow == null) {
log.warn("[AdminBorrow] returnBook: record not found, recordId={}", recordId)
return ApiResult.notFound("Borrow record does not exist") return ApiResult.notFound("Borrow record does not exist")
} }
if (matchedBorrow.status == "RETURNED") { if (matchedBorrow.status == "RETURNED") {
log.warn("[AdminBorrow] returnBook: already returned, recordId={}", recordId)
return ApiResult.conflict("Book has already been returned") return ApiResult.conflict("Book has already been returned")
} }
val borrow = BorrowRecord( val borrow = BorrowRecord(
@@ -196,7 +182,6 @@ class AdminBorrowServiceImpl(
) )
val matchedBook = bookMapper.selectById(matchedBorrow.bookId) val matchedBook = bookMapper.selectById(matchedBorrow.bookId)
if (matchedBook == null) { if (matchedBook == null) {
log.warn("[AdminBorrow] returnBook: book not found, bookId={}", matchedBorrow.bookId)
return ApiResult.notFound("Book does not exist") return ApiResult.notFound("Book does not exist")
} }
val book = Book( val book = Book(
@@ -207,7 +192,6 @@ class AdminBorrowServiceImpl(
) )
bookMapper.updateById(book) bookMapper.updateById(book)
borrowRecordMapper.updateById(borrow) borrowRecordMapper.updateById(borrow)
log.info("[AdminBorrow] returnBook: success, recordId={}, userId={}, book={}", recordId, borrow.userId, matchedBook.name)
return ApiResult.success("Book returned successfully") return ApiResult.success("Book returned successfully")
} }
@@ -3,7 +3,6 @@ package com.msksbr.bookmgr.service.impl
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper
import com.msksbr.bookmgr.entity.Book 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.service.BookService import com.msksbr.bookmgr.service.BookService
import com.msksbr.bookmgr.template.ApiResult import com.msksbr.bookmgr.template.ApiResult
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@@ -21,7 +20,6 @@ class BookServiceImpl(private val bookMapper: BookMapper) : BookService {
*/ */
override fun searchBook(query: String): ApiResult<List<Book>> { override fun searchBook(query: String): ApiResult<List<Book>> {
if (query.isBlank()) { if (query.isBlank()) {
log.warn("[Book] search: query is blank")
return ApiResult.badRequest("Search query cannot be empty") return ApiResult.badRequest("Search query cannot be empty")
} }
val result = bookMapper.selectList( val result = bookMapper.selectList(
@@ -31,10 +29,8 @@ class BookServiceImpl(private val bookMapper: BookMapper) : BookService {
.like("author", query) .like("author", query)
) )
if (result.isEmpty()) { if (result.isEmpty()) {
log.info("[Book] search: no results for {}", query)
return ApiResult.notFound("No matching books found") return ApiResult.notFound("No matching books found")
} }
log.info("[Book] search: found {} results for {}", result.size, query)
return ApiResult.success(result) return ApiResult.success(result)
} }
@@ -45,7 +41,6 @@ class BookServiceImpl(private val bookMapper: BookMapper) : BookService {
*/ */
override fun getOneBook(id: Long): ApiResult<Book> { override fun getOneBook(id: Long): ApiResult<Book> {
if (id < 1) { if (id < 1) {
log.warn("[Book] getOne: invalid id={}", id)
return ApiResult.badRequest("Invalid book ID") return ApiResult.badRequest("Invalid book ID")
} }
val result = bookMapper.selectOne( val result = bookMapper.selectOne(
@@ -53,10 +48,8 @@ class BookServiceImpl(private val bookMapper: BookMapper) : BookService {
.eq("id", id) .eq("id", id)
) )
if (result == null) { if (result == null) {
log.info("[Book] getOneBook: no results for {}", id)
return ApiResult.notFound("Book not found") return ApiResult.notFound("Book not found")
} }
log.info("[Book] getOne: found {}, author={}", result.name, result.author)
return ApiResult.success(result) return ApiResult.success(result)
} }
@@ -66,7 +59,6 @@ class BookServiceImpl(private val bookMapper: BookMapper) : BookService {
*/ */
override fun getAllBooks(): ApiResult<List<Book>> { override fun getAllBooks(): ApiResult<List<Book>> {
val books = bookMapper.selectList(null) val books = bookMapper.selectList(null)
log.info("[Book] getAll: found {} books", books.size)
return ApiResult.success(books) return ApiResult.success(books)
} }
} }
@@ -5,7 +5,6 @@ import com.msksbr.bookmgr.entity.Book
import com.msksbr.bookmgr.entity.BorrowRecord import com.msksbr.bookmgr.entity.BorrowRecord
import com.msksbr.bookmgr.mapper.BookMapper import com.msksbr.bookmgr.mapper.BookMapper
import com.msksbr.bookmgr.mapper.BorrowRecordMapper import com.msksbr.bookmgr.mapper.BorrowRecordMapper
import com.msksbr.bookmgr.script.log
import com.msksbr.bookmgr.service.BorrowService import com.msksbr.bookmgr.service.BorrowService
import com.msksbr.bookmgr.template.ApiResult import com.msksbr.bookmgr.template.ApiResult
import com.msksbr.bookmgr.vo.MyBorrowVo import com.msksbr.bookmgr.vo.MyBorrowVo
@@ -33,12 +32,10 @@ class BorrowServiceImpl(
QueryWrapper<BorrowRecord>().eq("user_id", userId).orderByDesc("borrow_time") QueryWrapper<BorrowRecord>().eq("user_id", userId).orderByDesc("borrow_time")
) )
if (borrows.isEmpty()) { if (borrows.isEmpty()) {
log.info("[Borrow] getAll: no records for userId={}", userId)
return ApiResult.notFound("No borrow records found") return ApiResult.notFound("No borrow records found")
} }
val bookIds = borrows.map { it.bookId }.distinct() val bookIds = borrows.map { it.bookId }.distinct()
val result = runBlocking { buildMyBorrowVos(borrows, bookIds) } val result = runBlocking { buildMyBorrowVos(borrows, bookIds) }
log.info("[Borrow] getAll: found {} records for userId={}", result.size, userId)
return ApiResult.success(result) return ApiResult.success(result)
} }
@@ -50,7 +47,6 @@ class BorrowServiceImpl(
userId: Long userId: Long
): ApiResult<List<MyBorrowVo>> { ): ApiResult<List<MyBorrowVo>> {
if (query.isBlank()) { if (query.isBlank()) {
log.warn("[Borrow] search: query is blank")
return ApiResult.badRequest("Search query cannot be empty") return ApiResult.badRequest("Search query cannot be empty")
} }
val matchedBookIds = bookMapper.selectList( val matchedBookIds = bookMapper.selectList(
@@ -60,7 +56,6 @@ class BorrowServiceImpl(
.like("author", query) .like("author", query)
).map { it.id } ).map { it.id }
if (matchedBookIds.isEmpty()) { if (matchedBookIds.isEmpty()) {
log.info("[Borrow] search: no results for {}", query)
return ApiResult.notFound("No matching borrow records found") return ApiResult.notFound("No matching borrow records found")
} }
val borrows = borrowRecordMapper.selectList( val borrows = borrowRecordMapper.selectList(
@@ -70,12 +65,10 @@ class BorrowServiceImpl(
.orderByDesc("borrow_time") .orderByDesc("borrow_time")
) )
if (borrows.isEmpty()) { if (borrows.isEmpty()) {
log.info("[Borrow] search: no borrows match for userId={}, query={}", userId, query)
return ApiResult.notFound("No matching borrow records found") return ApiResult.notFound("No matching borrow records found")
} }
val bookIds = borrows.map { it.bookId }.distinct() val bookIds = borrows.map { it.bookId }.distinct()
val result = runBlocking { buildMyBorrowVos(borrows, bookIds) } val result = runBlocking { buildMyBorrowVos(borrows, bookIds) }
log.info("[Borrow] search: found {} records for userId={}, query={}", result.size, userId, query)
return ApiResult.success(result) return ApiResult.success(result)
} }
@@ -88,14 +81,9 @@ class BorrowServiceImpl(
): ApiResult<MyBorrowVo> { ): ApiResult<MyBorrowVo> {
val borrow = borrowRecordMapper.selectById(borrowId) val borrow = borrowRecordMapper.selectById(borrowId)
if (borrow == null) { if (borrow == null) {
log.info("[Borrow] getOne: record not found, borrowId={}", borrowId)
return ApiResult.notFound("Borrow record not found") return ApiResult.notFound("Borrow record not found")
} }
if (borrow.userId != userId) { if (borrow.userId != userId) {
log.warn(
"[Borrow] getOne: OWNERSHIP MISMATCH — userId={} attempted to access borrowId={} belonging to userId={}, bookId={}, status={}",
userId, borrowId, borrow.userId, borrow.bookId, borrow.status
)
return ApiResult.notFound("Borrow record not found") return ApiResult.notFound("Borrow record not found")
} }
val book = bookMapper.selectById(borrow.bookId) val book = bookMapper.selectById(borrow.bookId)
@@ -108,7 +96,6 @@ class BorrowServiceImpl(
BookBorrowVo(id = it.id!!, name = it.name, author = it.author) BookBorrowVo(id = it.id!!, name = it.name, author = it.author)
} ?: BookBorrowVo() } ?: BookBorrowVo()
) )
log.info("[Borrow] getOne: found record borrowId={}, userId={}, book={}", borrowId, userId, book?.name)
return ApiResult.success(result) return ApiResult.success(result)
} }
@@ -121,11 +108,9 @@ class BorrowServiceImpl(
): ApiResult<String> { ): ApiResult<String> {
val matchedBook = bookMapper.selectById(bookId) val matchedBook = bookMapper.selectById(bookId)
if (matchedBook == null) { if (matchedBook == null) {
log.warn("[Borrow] borrow: book not found, bookId={}", bookId)
return ApiResult.notFound("Book does not exist") return ApiResult.notFound("Book does not exist")
} }
if (matchedBook.stock < 1) { if (matchedBook.stock < 1) {
log.warn("[Borrow] borrow: book out of stock, bookId={}, stock={}", bookId, matchedBook.stock)
return ApiResult.conflict("Book is out of stock") return ApiResult.conflict("Book is out of stock")
} }
val book = Book( val book = Book(
@@ -144,7 +129,6 @@ class BorrowServiceImpl(
) )
bookMapper.updateById(book) bookMapper.updateById(book)
borrowRecordMapper.insert(borrow) borrowRecordMapper.insert(borrow)
log.info("[Borrow] borrow: success, userId={}, bookId={}, book={}", userId, bookId, matchedBook.name)
return ApiResult.success("Book borrowed successfully") return ApiResult.success("Book borrowed successfully")
} }
@@ -157,18 +141,12 @@ class BorrowServiceImpl(
): ApiResult<String> { ): ApiResult<String> {
val matchedBorrow = borrowRecordMapper.selectById(borrowId) val matchedBorrow = borrowRecordMapper.selectById(borrowId)
if (matchedBorrow == null) { if (matchedBorrow == null) {
log.warn("[Borrow] return: record not found, borrowId={}", borrowId)
return ApiResult.notFound("Borrow record does not exist") return ApiResult.notFound("Borrow record does not exist")
} }
if (matchedBorrow.userId != userId) { if (matchedBorrow.userId != userId) {
log.warn(
"[Borrow] return: OWNERSHIP MISMATCH — userId={} attempted to return borrowId={} belonging to userId={}, bookId={}, status={}",
userId, borrowId, matchedBorrow.userId, matchedBorrow.bookId, matchedBorrow.status
)
return ApiResult.notFound("Borrow record does not exist") return ApiResult.notFound("Borrow record does not exist")
} }
if (matchedBorrow.status == "RETURNED") { if (matchedBorrow.status == "RETURNED") {
log.warn("[Borrow] return: already returned, borrowId={}", borrowId)
return ApiResult.conflict("Book has already been returned") return ApiResult.conflict("Book has already been returned")
} }
val borrow = BorrowRecord( val borrow = BorrowRecord(
@@ -181,7 +159,6 @@ class BorrowServiceImpl(
) )
val matchedBook = bookMapper.selectById(matchedBorrow.bookId) val matchedBook = bookMapper.selectById(matchedBorrow.bookId)
if (matchedBook == null) { if (matchedBook == null) {
log.warn("[Borrow] return: book not found, bookId={}", matchedBorrow.bookId)
return ApiResult.notFound("Book does not exist") return ApiResult.notFound("Book does not exist")
} }
val book = Book( val book = Book(
@@ -192,7 +169,6 @@ class BorrowServiceImpl(
) )
bookMapper.updateById(book) bookMapper.updateById(book)
borrowRecordMapper.updateById(borrow) borrowRecordMapper.updateById(borrow)
log.info("[Borrow] return: success, borrowId={}, userId={}, book={}", borrowId, userId, matchedBook.name)
return ApiResult.success("Book returned successfully") return ApiResult.success("Book returned successfully")
} }