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:
@@ -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 切面将用户信息写入 MDC,service 切面从 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
|
||||
|
||||
import com.msksbr.bookmgr.annotation.RequireRole
|
||||
import com.msksbr.bookmgr.config.IpExtractor
|
||||
import com.msksbr.bookmgr.dto.BookAddDto
|
||||
import com.msksbr.bookmgr.dto.BookUpdateDto
|
||||
import com.msksbr.bookmgr.script.log
|
||||
import com.msksbr.bookmgr.service.AdminBookService
|
||||
import com.msksbr.bookmgr.template.ApiResult
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.validation.Valid
|
||||
import org.springframework.web.bind.annotation.*
|
||||
|
||||
@@ -25,7 +22,7 @@ import org.springframework.web.bind.annotation.*
|
||||
*/
|
||||
@RestController
|
||||
@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
|
||||
@@ -37,16 +34,7 @@ class AdminBookController(private val adminBookService: AdminBookService, privat
|
||||
@Valid
|
||||
@RequestBody
|
||||
bookAddDto: BookAddDto,
|
||||
request: HttpServletRequest,
|
||||
@RequestAttribute(required = false) username: 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)
|
||||
}
|
||||
|
||||
@@ -61,11 +49,7 @@ class AdminBookController(private val adminBookService: AdminBookService, privat
|
||||
@Valid
|
||||
@RequestBody
|
||||
bookUpdateDto: BookUpdateDto,
|
||||
request: HttpServletRequest,
|
||||
@RequestAttribute(required = false) username: 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)
|
||||
}
|
||||
|
||||
@@ -77,11 +61,7 @@ class AdminBookController(private val adminBookService: AdminBookService, privat
|
||||
@PostMapping("/delete")
|
||||
fun deleteBook(
|
||||
id: Long,
|
||||
request: HttpServletRequest,
|
||||
@RequestAttribute(required = false) username: 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)
|
||||
}
|
||||
|
||||
@@ -94,11 +74,7 @@ class AdminBookController(private val adminBookService: AdminBookService, privat
|
||||
fun updateStock(
|
||||
id: Long,
|
||||
stock: Int,
|
||||
request: HttpServletRequest,
|
||||
@RequestAttribute(required = false) username: 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
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.ApiResult
|
||||
import com.msksbr.bookmgr.vo.BorrowInfoVo
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import org.springframework.web.bind.annotation.*
|
||||
|
||||
/*
|
||||
@@ -26,7 +23,6 @@ import org.springframework.web.bind.annotation.*
|
||||
@RequestMapping("/api/admin/borrows")
|
||||
class AdminBorrowController(
|
||||
private val adminBorrowService: AdminBorrowService,
|
||||
private val ipExtractor: IpExtractor
|
||||
) {
|
||||
/*
|
||||
* GET /api/admin/borrows/search?query=xxx
|
||||
@@ -35,12 +31,8 @@ class AdminBorrowController(
|
||||
@RequireRole("admin")
|
||||
@GetMapping("/search")
|
||||
fun searchBorrows(
|
||||
@RequestAttribute(required = false) username: String?,
|
||||
query: String,
|
||||
request: HttpServletRequest
|
||||
): 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)
|
||||
}
|
||||
|
||||
@@ -51,12 +43,8 @@ class AdminBorrowController(
|
||||
@RequireRole("admin")
|
||||
@GetMapping("/getone")
|
||||
fun getOneBorrow(
|
||||
@RequestAttribute(required = false) username: String?,
|
||||
id: Long,
|
||||
request: HttpServletRequest
|
||||
): 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)
|
||||
}
|
||||
|
||||
@@ -66,12 +54,7 @@ class AdminBorrowController(
|
||||
*/
|
||||
@RequireRole("admin")
|
||||
@GetMapping("/getall")
|
||||
fun getAllBorrows(
|
||||
@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"))
|
||||
fun getAllBorrows(): ApiResult<List<BorrowInfoVo>> {
|
||||
return adminBorrowService.getAllBorrows()
|
||||
}
|
||||
|
||||
@@ -82,13 +65,9 @@ class AdminBorrowController(
|
||||
@RequireRole("admin")
|
||||
@PostMapping("/borrowbook")
|
||||
fun borrowBook(
|
||||
@RequestAttribute(required = false) username: String?,
|
||||
bookId: Long,
|
||||
userId: Long,
|
||||
request: HttpServletRequest
|
||||
): 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)
|
||||
}
|
||||
|
||||
@@ -99,12 +78,8 @@ class AdminBorrowController(
|
||||
@RequireRole("admin")
|
||||
@PostMapping("/returnbook")
|
||||
fun returnBook(
|
||||
@RequestAttribute(required = false) username: String?,
|
||||
recordId: Long,
|
||||
request: HttpServletRequest
|
||||
): 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
package com.msksbr.bookmgr.controller
|
||||
|
||||
import com.msksbr.bookmgr.config.IpExtractor
|
||||
import com.msksbr.bookmgr.config.JwtUtils
|
||||
import com.msksbr.bookmgr.dto.UserLoginDto
|
||||
import com.msksbr.bookmgr.script.log
|
||||
import com.msksbr.bookmgr.service.AuthService
|
||||
import com.msksbr.bookmgr.template.ApiResult
|
||||
import com.msksbr.bookmgr.vo.LoginVo
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.validation.Valid
|
||||
import org.springframework.web.bind.annotation.*
|
||||
|
||||
@@ -21,7 +18,6 @@ import org.springframework.web.bind.annotation.*
|
||||
@RequestMapping("/api/auth")
|
||||
class AuthController(
|
||||
val authService: AuthService,
|
||||
val ipExtractor: IpExtractor,
|
||||
private val jwtUtils: JwtUtils
|
||||
) {
|
||||
/*
|
||||
@@ -42,15 +38,11 @@ class AuthController(
|
||||
@Valid
|
||||
@RequestBody
|
||||
loginDTO: UserLoginDto,
|
||||
request: HttpServletRequest
|
||||
): 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)
|
||||
return if (user != null) {
|
||||
// 登录成功,签发 JWT
|
||||
val token = jwtUtils.generateToken(user.username, user.role, user.id!!)
|
||||
log.info("[Auth] login success: user={}", user.username)
|
||||
val loginVo = LoginVo(
|
||||
token = token,
|
||||
username = user.username,
|
||||
@@ -58,7 +50,6 @@ class AuthController(
|
||||
)
|
||||
ApiResult.success(loginVo)
|
||||
} else {
|
||||
log.warn("[Auth] login failed: user={}", loginDTO.username)
|
||||
ApiResult.unauthorized("Incorrect username or password")
|
||||
}
|
||||
}
|
||||
@@ -71,17 +62,7 @@ class AuthController(
|
||||
* { "code": 200, "message": "success", "data": "logout successfully" }
|
||||
*/
|
||||
@PostMapping("/logout")
|
||||
fun logout(
|
||||
@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")
|
||||
fun logout(): ApiResult<String> {
|
||||
return ApiResult.success("logout successfully")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
package com.msksbr.bookmgr.controller
|
||||
|
||||
import com.msksbr.bookmgr.config.IpExtractor
|
||||
import com.msksbr.bookmgr.entity.Book
|
||||
import com.msksbr.bookmgr.script.log
|
||||
import com.msksbr.bookmgr.service.BookService
|
||||
import com.msksbr.bookmgr.template.ApiResult
|
||||
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
|
||||
|
||||
@@ -21,19 +17,15 @@ import org.springframework.web.bind.annotation.RestController
|
||||
*/
|
||||
@RestController
|
||||
@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
|
||||
* 按书名或作者模糊搜索图书
|
||||
*/
|
||||
@GetMapping("/search")
|
||||
fun searchBook(
|
||||
@RequestAttribute(required = false) username: String?,
|
||||
query: String,
|
||||
request: HttpServletRequest
|
||||
): 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)
|
||||
}
|
||||
|
||||
@@ -43,12 +35,8 @@ class BookController(private val bookService: BookService, private val ipExtract
|
||||
*/
|
||||
@GetMapping("/getone")
|
||||
fun getOneBook(
|
||||
@RequestAttribute(required = false) username: String?,
|
||||
request: HttpServletRequest,
|
||||
id: Long
|
||||
): 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)
|
||||
}
|
||||
|
||||
@@ -57,12 +45,7 @@ class BookController(private val bookService: BookService, private val ipExtract
|
||||
* 查询所有图书
|
||||
*/
|
||||
@GetMapping("/getall")
|
||||
fun getAllBooks(
|
||||
@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))
|
||||
fun getAllBooks(): ApiResult<List<Book>> {
|
||||
return bookService.getAllBooks()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
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.BorrowService
|
||||
import com.msksbr.bookmgr.template.ApiResult
|
||||
import com.msksbr.bookmgr.vo.MyBorrowVo
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import org.springframework.web.bind.annotation.*
|
||||
|
||||
/*
|
||||
@@ -26,7 +23,6 @@ import org.springframework.web.bind.annotation.*
|
||||
@RequestMapping("/api/borrows")
|
||||
class BorrowController(
|
||||
private val borrowService: BorrowService,
|
||||
private val ipExtractor: IpExtractor
|
||||
) {
|
||||
/*
|
||||
* GET /api/borrows/getall
|
||||
@@ -35,12 +31,8 @@ class BorrowController(
|
||||
@RequireRole("user")
|
||||
@GetMapping("/getall")
|
||||
fun getAllMyBorrows(
|
||||
@RequestAttribute(required = false) username: String?,
|
||||
@RequestAttribute(required = false) userId: Long?,
|
||||
request: HttpServletRequest
|
||||
): 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!!)
|
||||
}
|
||||
|
||||
@@ -51,13 +43,9 @@ class BorrowController(
|
||||
@RequireRole("user")
|
||||
@GetMapping("/search")
|
||||
fun searchMyBorrows(
|
||||
@RequestAttribute(required = false) username: String?,
|
||||
query: String,
|
||||
@RequestAttribute(required = false) userId: Long?,
|
||||
request: HttpServletRequest
|
||||
): 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!!)
|
||||
}
|
||||
|
||||
@@ -68,13 +56,9 @@ class BorrowController(
|
||||
@RequireRole("user")
|
||||
@GetMapping("/getone")
|
||||
fun getOneMyBorrow(
|
||||
@RequestAttribute(required = false) username: String?,
|
||||
borrowId: Long,
|
||||
@RequestAttribute(required = false) userId: Long?,
|
||||
request: HttpServletRequest
|
||||
): 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!!)
|
||||
}
|
||||
|
||||
@@ -85,13 +69,9 @@ class BorrowController(
|
||||
@RequireRole("user")
|
||||
@PostMapping("/borrowbook")
|
||||
fun borrowBookForMe(
|
||||
@RequestAttribute(required = false) username: String?,
|
||||
bookId: Long,
|
||||
@RequestAttribute(required = false) userId: Long?,
|
||||
request: HttpServletRequest
|
||||
): 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!!)
|
||||
}
|
||||
|
||||
@@ -102,13 +82,9 @@ class BorrowController(
|
||||
@RequireRole("user")
|
||||
@PostMapping("/returnbook")
|
||||
fun returnBookForMe(
|
||||
@RequestAttribute(required = false) username: String?,
|
||||
borrowId: Long,
|
||||
@RequestAttribute(required = false) userId: Long?,
|
||||
request: HttpServletRequest
|
||||
): 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!!)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,3 +16,11 @@ import org.slf4j.LoggerFactory
|
||||
*/
|
||||
val <T : Any> T.log: Logger
|
||||
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.entity.Book
|
||||
import com.msksbr.bookmgr.mapper.BookMapper
|
||||
import com.msksbr.bookmgr.script.log
|
||||
import com.msksbr.bookmgr.service.AdminBookService
|
||||
import com.msksbr.bookmgr.template.ApiResult
|
||||
import org.springframework.stereotype.Service
|
||||
@@ -29,11 +28,9 @@ class AdminBookServiceImpl(private val bookMapper: BookMapper) : AdminBookServic
|
||||
stock = bookAddDto.stock,
|
||||
)
|
||||
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")
|
||||
}
|
||||
bookMapper.insert(book)
|
||||
log.info("[AdminBook] add: success, id={}, name={}", book.id, book.name)
|
||||
return ApiResult.success("Book added successfully")
|
||||
}
|
||||
|
||||
@@ -48,12 +45,10 @@ class AdminBookServiceImpl(private val bookMapper: BookMapper) : AdminBookServic
|
||||
bookUpdateDto: BookUpdateDto
|
||||
): ApiResult<String> {
|
||||
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")
|
||||
}
|
||||
val existing = bookMapper.selectById(id)
|
||||
?: run {
|
||||
log.warn("[AdminBook] update: book not found, id={}", id)
|
||||
return ApiResult.notFound("Book does not exist")
|
||||
}
|
||||
val book = Book(
|
||||
@@ -63,11 +58,9 @@ class AdminBookServiceImpl(private val bookMapper: BookMapper) : AdminBookServic
|
||||
stock = existing.stock,
|
||||
)
|
||||
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")
|
||||
}
|
||||
bookMapper.updateById(book)
|
||||
log.info("[AdminBook] update: success, id={}", book.id)
|
||||
return ApiResult.success("Book updated successfully")
|
||||
}
|
||||
|
||||
@@ -77,13 +70,11 @@ class AdminBookServiceImpl(private val bookMapper: BookMapper) : AdminBookServic
|
||||
* @return 操作结果,不存在的书返回 404
|
||||
*/
|
||||
override fun deleteBook(id: Long): ApiResult<String> {
|
||||
val existing = bookMapper.selectById(id)
|
||||
bookMapper.selectById(id)
|
||||
?: run {
|
||||
log.warn("[AdminBook] delete: book not found, id={}", id)
|
||||
return ApiResult.notFound("Book does not exist")
|
||||
}
|
||||
bookMapper.deleteById(id)
|
||||
log.info("[AdminBook] delete: success, id={}, name={}", id, existing.name)
|
||||
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> {
|
||||
if (stock <= 0) {
|
||||
log.warn("[AdminBook] updateStock: invalid stock={}, id={}", stock, id)
|
||||
return ApiResult.badRequest("Stock must be greater than zero")
|
||||
}
|
||||
val existing = bookMapper.selectById(id)
|
||||
?: run {
|
||||
log.warn("[AdminBook] updateStock: book not found, id={}", id)
|
||||
return ApiResult.notFound("Book does not exist")
|
||||
}
|
||||
val book = Book(
|
||||
@@ -110,7 +99,6 @@ class AdminBookServiceImpl(private val bookMapper: BookMapper) : AdminBookServic
|
||||
stock = stock
|
||||
)
|
||||
bookMapper.updateById(book)
|
||||
log.debug("[AdminBook] updateStock: success, id={}, stock={}", id, stock)
|
||||
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.BorrowRecordMapper
|
||||
import com.msksbr.bookmgr.mapper.UserMapper
|
||||
import com.msksbr.bookmgr.script.log
|
||||
import com.msksbr.bookmgr.service.AdminBorrowService
|
||||
import com.msksbr.bookmgr.template.ApiResult
|
||||
import com.msksbr.bookmgr.vo.BorrowInfoVo
|
||||
@@ -36,7 +35,6 @@ class AdminBorrowServiceImpl(
|
||||
*/
|
||||
override fun searchBorrows(query: String): ApiResult<List<BorrowInfoVo>> {
|
||||
if (query.isBlank()) {
|
||||
log.warn("[AdminBorrow] search: query is blank")
|
||||
return ApiResult.badRequest("Search query cannot be empty")
|
||||
}
|
||||
val (matchedUserIds, matchedBookIds) = runBlocking {
|
||||
@@ -59,7 +57,6 @@ class AdminBorrowServiceImpl(
|
||||
userDeferred.await() to bookDeferred.await()
|
||||
}
|
||||
if (matchedUserIds.isEmpty() && matchedBookIds.isEmpty()) {
|
||||
log.info("[AdminBorrow] search: no results for {}", query)
|
||||
return ApiResult.notFound("No matching borrow records found")
|
||||
}
|
||||
val borrows = borrowRecordMapper.selectList(
|
||||
@@ -74,7 +71,6 @@ class AdminBorrowServiceImpl(
|
||||
val result = runBlocking {
|
||||
buildBorrowVos(borrows, userIds, bookIds)
|
||||
}
|
||||
log.info("[AdminBorrow] search: found {} records for {}", result.size, query)
|
||||
return ApiResult.success(result)
|
||||
}
|
||||
|
||||
@@ -84,7 +80,6 @@ class AdminBorrowServiceImpl(
|
||||
override fun getOneBorrow(id: Long): ApiResult<BorrowInfoVo> {
|
||||
val borrow = borrowRecordMapper.selectById(id)
|
||||
if (borrow == null) {
|
||||
log.info("[AdminBorrow] getOne: no record for id={}", id)
|
||||
return ApiResult.notFound("Borrow record not found")
|
||||
}
|
||||
val user = userMapper.selectById(borrow.userId)
|
||||
@@ -105,7 +100,6 @@ class AdminBorrowServiceImpl(
|
||||
role = user.role,
|
||||
)
|
||||
)
|
||||
log.info("[AdminBorrow] getOne: found record id={}, user={}, book={}", id, user.username, book.name)
|
||||
return ApiResult.success(result)
|
||||
}
|
||||
|
||||
@@ -115,7 +109,6 @@ class AdminBorrowServiceImpl(
|
||||
override fun getAllBorrows(): ApiResult<List<BorrowInfoVo>> {
|
||||
val borrows = borrowRecordMapper.selectList(null)
|
||||
if (borrows.isEmpty()) {
|
||||
log.info("[AdminBorrow] getAll: no records")
|
||||
return ApiResult.notFound("No borrow records found")
|
||||
}
|
||||
val userIds = borrows.map { it.userId }.distinct()
|
||||
@@ -124,7 +117,6 @@ class AdminBorrowServiceImpl(
|
||||
|
||||
val result = runBlocking { buildBorrowVos(borrows, userIds, bookIds) }
|
||||
|
||||
log.info("[AdminBorrow] getAll: found {} records", result.size)
|
||||
return ApiResult.success(result)
|
||||
}
|
||||
|
||||
@@ -142,15 +134,12 @@ class AdminBorrowServiceImpl(
|
||||
userDeferred.await() to bookDeferred.await()
|
||||
}
|
||||
if (matchedUser == null) {
|
||||
log.warn("[AdminBorrow] borrowBook: user not found, userId={}", userId)
|
||||
return ApiResult.notFound("User does not exist")
|
||||
}
|
||||
if (matchedBook == null) {
|
||||
log.warn("[AdminBorrow] borrowBook: book not found, bookId={}", bookId)
|
||||
return ApiResult.notFound("Book does not exist")
|
||||
}
|
||||
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")
|
||||
}
|
||||
val book = Book(
|
||||
@@ -169,7 +158,6 @@ class AdminBorrowServiceImpl(
|
||||
)
|
||||
bookMapper.updateById(book)
|
||||
borrowRecordMapper.insert(borrow)
|
||||
log.info("[AdminBorrow] borrowBook: success, userId={}, bookId={}, book={}", userId, bookId, matchedBook.name)
|
||||
return ApiResult.success("Book borrowed successfully")
|
||||
}
|
||||
|
||||
@@ -179,11 +167,9 @@ class AdminBorrowServiceImpl(
|
||||
override fun returnBook(recordId: Long): ApiResult<String> {
|
||||
val matchedBorrow = borrowRecordMapper.selectById(recordId)
|
||||
if (matchedBorrow == null) {
|
||||
log.warn("[AdminBorrow] returnBook: record not found, recordId={}", recordId)
|
||||
return ApiResult.notFound("Borrow record does not exist")
|
||||
}
|
||||
if (matchedBorrow.status == "RETURNED") {
|
||||
log.warn("[AdminBorrow] returnBook: already returned, recordId={}", recordId)
|
||||
return ApiResult.conflict("Book has already been returned")
|
||||
}
|
||||
val borrow = BorrowRecord(
|
||||
@@ -196,7 +182,6 @@ class AdminBorrowServiceImpl(
|
||||
)
|
||||
val matchedBook = bookMapper.selectById(matchedBorrow.bookId)
|
||||
if (matchedBook == null) {
|
||||
log.warn("[AdminBorrow] returnBook: book not found, bookId={}", matchedBorrow.bookId)
|
||||
return ApiResult.notFound("Book does not exist")
|
||||
}
|
||||
val book = Book(
|
||||
@@ -207,7 +192,6 @@ class AdminBorrowServiceImpl(
|
||||
)
|
||||
bookMapper.updateById(book)
|
||||
borrowRecordMapper.updateById(borrow)
|
||||
log.info("[AdminBorrow] returnBook: success, recordId={}, userId={}, book={}", recordId, borrow.userId, matchedBook.name)
|
||||
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.msksbr.bookmgr.entity.Book
|
||||
import com.msksbr.bookmgr.mapper.BookMapper
|
||||
import com.msksbr.bookmgr.script.log
|
||||
import com.msksbr.bookmgr.service.BookService
|
||||
import com.msksbr.bookmgr.template.ApiResult
|
||||
import org.springframework.stereotype.Service
|
||||
@@ -21,7 +20,6 @@ class BookServiceImpl(private val bookMapper: BookMapper) : BookService {
|
||||
*/
|
||||
override fun searchBook(query: String): ApiResult<List<Book>> {
|
||||
if (query.isBlank()) {
|
||||
log.warn("[Book] search: query is blank")
|
||||
return ApiResult.badRequest("Search query cannot be empty")
|
||||
}
|
||||
val result = bookMapper.selectList(
|
||||
@@ -31,10 +29,8 @@ class BookServiceImpl(private val bookMapper: BookMapper) : BookService {
|
||||
.like("author", query)
|
||||
)
|
||||
if (result.isEmpty()) {
|
||||
log.info("[Book] search: no results for {}", query)
|
||||
return ApiResult.notFound("No matching books found")
|
||||
}
|
||||
log.info("[Book] search: found {} results for {}", result.size, query)
|
||||
return ApiResult.success(result)
|
||||
}
|
||||
|
||||
@@ -45,7 +41,6 @@ class BookServiceImpl(private val bookMapper: BookMapper) : BookService {
|
||||
*/
|
||||
override fun getOneBook(id: Long): ApiResult<Book> {
|
||||
if (id < 1) {
|
||||
log.warn("[Book] getOne: invalid id={}", id)
|
||||
return ApiResult.badRequest("Invalid book ID")
|
||||
}
|
||||
val result = bookMapper.selectOne(
|
||||
@@ -53,10 +48,8 @@ class BookServiceImpl(private val bookMapper: BookMapper) : BookService {
|
||||
.eq("id", id)
|
||||
)
|
||||
if (result == null) {
|
||||
log.info("[Book] getOneBook: no results for {}", id)
|
||||
return ApiResult.notFound("Book not found")
|
||||
}
|
||||
log.info("[Book] getOne: found {}, author={}", result.name, result.author)
|
||||
return ApiResult.success(result)
|
||||
}
|
||||
|
||||
@@ -66,7 +59,6 @@ class BookServiceImpl(private val bookMapper: BookMapper) : BookService {
|
||||
*/
|
||||
override fun getAllBooks(): ApiResult<List<Book>> {
|
||||
val books = bookMapper.selectList(null)
|
||||
log.info("[Book] getAll: found {} books", books.size)
|
||||
return ApiResult.success(books)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import com.msksbr.bookmgr.entity.Book
|
||||
import com.msksbr.bookmgr.entity.BorrowRecord
|
||||
import com.msksbr.bookmgr.mapper.BookMapper
|
||||
import com.msksbr.bookmgr.mapper.BorrowRecordMapper
|
||||
import com.msksbr.bookmgr.script.log
|
||||
import com.msksbr.bookmgr.service.BorrowService
|
||||
import com.msksbr.bookmgr.template.ApiResult
|
||||
import com.msksbr.bookmgr.vo.MyBorrowVo
|
||||
@@ -33,12 +32,10 @@ class BorrowServiceImpl(
|
||||
QueryWrapper<BorrowRecord>().eq("user_id", userId).orderByDesc("borrow_time")
|
||||
)
|
||||
if (borrows.isEmpty()) {
|
||||
log.info("[Borrow] getAll: no records for userId={}", userId)
|
||||
return ApiResult.notFound("No borrow records found")
|
||||
}
|
||||
val bookIds = borrows.map { it.bookId }.distinct()
|
||||
val result = runBlocking { buildMyBorrowVos(borrows, bookIds) }
|
||||
log.info("[Borrow] getAll: found {} records for userId={}", result.size, userId)
|
||||
return ApiResult.success(result)
|
||||
}
|
||||
|
||||
@@ -50,7 +47,6 @@ class BorrowServiceImpl(
|
||||
userId: Long
|
||||
): ApiResult<List<MyBorrowVo>> {
|
||||
if (query.isBlank()) {
|
||||
log.warn("[Borrow] search: query is blank")
|
||||
return ApiResult.badRequest("Search query cannot be empty")
|
||||
}
|
||||
val matchedBookIds = bookMapper.selectList(
|
||||
@@ -60,7 +56,6 @@ class BorrowServiceImpl(
|
||||
.like("author", query)
|
||||
).map { it.id }
|
||||
if (matchedBookIds.isEmpty()) {
|
||||
log.info("[Borrow] search: no results for {}", query)
|
||||
return ApiResult.notFound("No matching borrow records found")
|
||||
}
|
||||
val borrows = borrowRecordMapper.selectList(
|
||||
@@ -70,12 +65,10 @@ class BorrowServiceImpl(
|
||||
.orderByDesc("borrow_time")
|
||||
)
|
||||
if (borrows.isEmpty()) {
|
||||
log.info("[Borrow] search: no borrows match for userId={}, query={}", userId, query)
|
||||
return ApiResult.notFound("No matching borrow records found")
|
||||
}
|
||||
val bookIds = borrows.map { it.bookId }.distinct()
|
||||
val result = runBlocking { buildMyBorrowVos(borrows, bookIds) }
|
||||
log.info("[Borrow] search: found {} records for userId={}, query={}", result.size, userId, query)
|
||||
return ApiResult.success(result)
|
||||
}
|
||||
|
||||
@@ -88,14 +81,9 @@ class BorrowServiceImpl(
|
||||
): ApiResult<MyBorrowVo> {
|
||||
val borrow = borrowRecordMapper.selectById(borrowId)
|
||||
if (borrow == null) {
|
||||
log.info("[Borrow] getOne: record not found, borrowId={}", borrowId)
|
||||
return ApiResult.notFound("Borrow record not found")
|
||||
}
|
||||
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")
|
||||
}
|
||||
val book = bookMapper.selectById(borrow.bookId)
|
||||
@@ -108,7 +96,6 @@ class BorrowServiceImpl(
|
||||
BookBorrowVo(id = it.id!!, name = it.name, author = it.author)
|
||||
} ?: BookBorrowVo()
|
||||
)
|
||||
log.info("[Borrow] getOne: found record borrowId={}, userId={}, book={}", borrowId, userId, book?.name)
|
||||
return ApiResult.success(result)
|
||||
}
|
||||
|
||||
@@ -121,11 +108,9 @@ class BorrowServiceImpl(
|
||||
): ApiResult<String> {
|
||||
val matchedBook = bookMapper.selectById(bookId)
|
||||
if (matchedBook == null) {
|
||||
log.warn("[Borrow] borrow: book not found, bookId={}", bookId)
|
||||
return ApiResult.notFound("Book does not exist")
|
||||
}
|
||||
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")
|
||||
}
|
||||
val book = Book(
|
||||
@@ -144,7 +129,6 @@ class BorrowServiceImpl(
|
||||
)
|
||||
bookMapper.updateById(book)
|
||||
borrowRecordMapper.insert(borrow)
|
||||
log.info("[Borrow] borrow: success, userId={}, bookId={}, book={}", userId, bookId, matchedBook.name)
|
||||
return ApiResult.success("Book borrowed successfully")
|
||||
}
|
||||
|
||||
@@ -157,18 +141,12 @@ class BorrowServiceImpl(
|
||||
): ApiResult<String> {
|
||||
val matchedBorrow = borrowRecordMapper.selectById(borrowId)
|
||||
if (matchedBorrow == null) {
|
||||
log.warn("[Borrow] return: record not found, borrowId={}", borrowId)
|
||||
return ApiResult.notFound("Borrow record does not exist")
|
||||
}
|
||||
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")
|
||||
}
|
||||
if (matchedBorrow.status == "RETURNED") {
|
||||
log.warn("[Borrow] return: already returned, borrowId={}", borrowId)
|
||||
return ApiResult.conflict("Book has already been returned")
|
||||
}
|
||||
val borrow = BorrowRecord(
|
||||
@@ -181,7 +159,6 @@ class BorrowServiceImpl(
|
||||
)
|
||||
val matchedBook = bookMapper.selectById(matchedBorrow.bookId)
|
||||
if (matchedBook == null) {
|
||||
log.warn("[Borrow] return: book not found, bookId={}", matchedBorrow.bookId)
|
||||
return ApiResult.notFound("Book does not exist")
|
||||
}
|
||||
val book = Book(
|
||||
@@ -192,7 +169,6 @@ class BorrowServiceImpl(
|
||||
)
|
||||
bookMapper.updateById(book)
|
||||
borrowRecordMapper.updateById(borrow)
|
||||
log.info("[Borrow] return: success, borrowId={}, userId={}, book={}", borrowId, userId, matchedBook.name)
|
||||
return ApiResult.success("Book returned successfully")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user