From 21dc9929716ec7411d53622affd311a877fe9ad7 Mon Sep 17 00:00:00 2001 From: msksbr515 Date: Sun, 24 May 2026 00:08:23 +0800 Subject: [PATCH] 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 --- .../com/msksbr/bookmgr/config/AuditAspect.kt | 263 ++++++++++++++++++ .../bookmgr/controller/AdminBookController.kt | 26 +- .../controller/AdminBorrowController.kt | 27 +- .../bookmgr/controller/AuthController.kt | 21 +- .../bookmgr/controller/BookController.kt | 21 +- .../bookmgr/controller/BorrowController.kt | 24 -- .../kotlin/com/msksbr/bookmgr/script/Log.kt | 8 + .../service/impl/AdminBookServiceImpl.kt | 14 +- .../service/impl/AdminBorrowServiceImpl.kt | 16 -- .../bookmgr/service/impl/BookServiceImpl.kt | 8 - .../bookmgr/service/impl/BorrowServiceImpl.kt | 24 -- 11 files changed, 277 insertions(+), 175 deletions(-) create mode 100644 src/main/kotlin/com/msksbr/bookmgr/config/AuditAspect.kt diff --git a/src/main/kotlin/com/msksbr/bookmgr/config/AuditAspect.kt b/src/main/kotlin/com/msksbr/bookmgr/config/AuditAspect.kt new file mode 100644 index 0000000..8204a66 --- /dev/null +++ b/src/main/kotlin/com/msksbr/bookmgr/config/AuditAspect.kt @@ -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}" + } +} diff --git a/src/main/kotlin/com/msksbr/bookmgr/controller/AdminBookController.kt b/src/main/kotlin/com/msksbr/bookmgr/controller/AdminBookController.kt index 66c5f8e..ef2fbd9 100644 --- a/src/main/kotlin/com/msksbr/bookmgr/controller/AdminBookController.kt +++ b/src/main/kotlin/com/msksbr/bookmgr/controller/AdminBookController.kt @@ -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 { - 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 { - 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 { - 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 { - 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) } } diff --git a/src/main/kotlin/com/msksbr/bookmgr/controller/AdminBorrowController.kt b/src/main/kotlin/com/msksbr/bookmgr/controller/AdminBorrowController.kt index 6e2d09e..b00465c 100644 --- a/src/main/kotlin/com/msksbr/bookmgr/controller/AdminBorrowController.kt +++ b/src/main/kotlin/com/msksbr/bookmgr/controller/AdminBorrowController.kt @@ -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> { - 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 { - 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> { - log.info("[AdminBorrow] getAll: user={}, ip={}", username ?: "guest", ipExtractor.getRealIp(request)) - log.info("[AdminBorrow] user agent: {}", request.getHeader("User-Agent")) + fun getAllBorrows(): ApiResult> { 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 { - 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 { - 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) } } diff --git a/src/main/kotlin/com/msksbr/bookmgr/controller/AuthController.kt b/src/main/kotlin/com/msksbr/bookmgr/controller/AuthController.kt index 2118ca4..573b550 100644 --- a/src/main/kotlin/com/msksbr/bookmgr/controller/AuthController.kt +++ b/src/main/kotlin/com/msksbr/bookmgr/controller/AuthController.kt @@ -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 { - 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 { - 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 { return ApiResult.success("logout successfully") } } diff --git a/src/main/kotlin/com/msksbr/bookmgr/controller/BookController.kt b/src/main/kotlin/com/msksbr/bookmgr/controller/BookController.kt index 1bb3c3a..5f90498 100644 --- a/src/main/kotlin/com/msksbr/bookmgr/controller/BookController.kt +++ b/src/main/kotlin/com/msksbr/bookmgr/controller/BookController.kt @@ -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> { - 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 { - 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> { - log.info("[Book] getAll: user={}", username ?: "guest") - log.info("[Book] user agent: {}, ip={}", request.getHeader("User-Agent"), ipExtractor.getRealIp(request)) + fun getAllBooks(): ApiResult> { return bookService.getAllBooks() } } diff --git a/src/main/kotlin/com/msksbr/bookmgr/controller/BorrowController.kt b/src/main/kotlin/com/msksbr/bookmgr/controller/BorrowController.kt index fd79432..6267526 100644 --- a/src/main/kotlin/com/msksbr/bookmgr/controller/BorrowController.kt +++ b/src/main/kotlin/com/msksbr/bookmgr/controller/BorrowController.kt @@ -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> { - 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> { - 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 { - 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 { - 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 { - 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!!) } } diff --git a/src/main/kotlin/com/msksbr/bookmgr/script/Log.kt b/src/main/kotlin/com/msksbr/bookmgr/script/Log.kt index b890d0f..dc4123b 100644 --- a/src/main/kotlin/com/msksbr/bookmgr/script/Log.kt +++ b/src/main/kotlin/com/msksbr/bookmgr/script/Log.kt @@ -16,3 +16,11 @@ import org.slf4j.LoggerFactory */ val 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") diff --git a/src/main/kotlin/com/msksbr/bookmgr/service/impl/AdminBookServiceImpl.kt b/src/main/kotlin/com/msksbr/bookmgr/service/impl/AdminBookServiceImpl.kt index 225d414..574831a 100644 --- a/src/main/kotlin/com/msksbr/bookmgr/service/impl/AdminBookServiceImpl.kt +++ b/src/main/kotlin/com/msksbr/bookmgr/service/impl/AdminBookServiceImpl.kt @@ -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 { 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 { - 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 { 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") } diff --git a/src/main/kotlin/com/msksbr/bookmgr/service/impl/AdminBorrowServiceImpl.kt b/src/main/kotlin/com/msksbr/bookmgr/service/impl/AdminBorrowServiceImpl.kt index 1fc8247..98f8d8e 100644 --- a/src/main/kotlin/com/msksbr/bookmgr/service/impl/AdminBorrowServiceImpl.kt +++ b/src/main/kotlin/com/msksbr/bookmgr/service/impl/AdminBorrowServiceImpl.kt @@ -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> { 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 { 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> { 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 { 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") } diff --git a/src/main/kotlin/com/msksbr/bookmgr/service/impl/BookServiceImpl.kt b/src/main/kotlin/com/msksbr/bookmgr/service/impl/BookServiceImpl.kt index 68f82fd..69113a8 100644 --- a/src/main/kotlin/com/msksbr/bookmgr/service/impl/BookServiceImpl.kt +++ b/src/main/kotlin/com/msksbr/bookmgr/service/impl/BookServiceImpl.kt @@ -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> { 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 { 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> { val books = bookMapper.selectList(null) - log.info("[Book] getAll: found {} books", books.size) return ApiResult.success(books) } } diff --git a/src/main/kotlin/com/msksbr/bookmgr/service/impl/BorrowServiceImpl.kt b/src/main/kotlin/com/msksbr/bookmgr/service/impl/BorrowServiceImpl.kt index c0e7340..93474c1 100644 --- a/src/main/kotlin/com/msksbr/bookmgr/service/impl/BorrowServiceImpl.kt +++ b/src/main/kotlin/com/msksbr/bookmgr/service/impl/BorrowServiceImpl.kt @@ -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().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> { 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 { 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 { 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 { 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") }