docs(core): add KDoc documentation to controllers, services, and entities

Add descriptive KDoc comments to all REST controllers, service interfaces,
entity classes, and mappers to improve code readability and maintainability.
Include annotations for controller-level API documentation.
This commit is contained in:
2026-05-21 18:47:23 +08:00
parent 5bb836eafc
commit 20660b91dc
24 changed files with 92 additions and 33 deletions
@@ -3,6 +3,9 @@ package com.msksbr.bookmgr
import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication import org.springframework.boot.runApplication
/*
* Spring Boot 启动类
*/
@SpringBootApplication @SpringBootApplication
class BookMgrApplication class BookMgrApplication
@@ -3,7 +3,9 @@ package com.msksbr.bookmgr.config
import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletRequest
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
//获取真实IP的bean /*
* 从请求头中提取真实客户端 IP(处理反向代理)
*/
@Component @Component
class IpExtractor { class IpExtractor {
fun getRealIp(request: HttpServletRequest): String { fun getRealIp(request: HttpServletRequest): String {
@@ -13,7 +13,8 @@ import java.util.*
import javax.crypto.SecretKey import javax.crypto.SecretKey
/* /*
* 注册JWT的失效时间和密钥 * JWT 令牌工具:签发、解析
* 密钥来源:环境变量 JWT_SECRET,未配置时自动生成(重启失效)
*/ */
@Component @Component
class JwtUtils( class JwtUtils(
@@ -22,10 +23,9 @@ class JwtUtils(
) { ) {
private val secretKey: SecretKey private val secretKey: SecretKey
// 没有配置key时自动生成 // 初始化密钥:有配置时用 SHA-256 哈希,无配置时随机生成
init { init {
val keyBytes = if (configuredSecret.isNotBlank()) { val keyBytes = if (configuredSecret.isNotBlank()) {
// 把用户提供的secret,用SHA-256哈希成256字节固定长度
val md = MessageDigest.getInstance("SHA-256") val md = MessageDigest.getInstance("SHA-256")
md.digest(configuredSecret.toByteArray()) md.digest(configuredSecret.toByteArray())
} else { } else {
@@ -39,7 +39,7 @@ class JwtUtils(
secretKey = Keys.hmacShaKeyFor(keyBytes) secretKey = Keys.hmacShaKeyFor(keyBytes)
} }
// 生成token // 签发 tokenpayload 包含 role
fun generateToken(username: String, role: String): String { fun generateToken(username: String, role: String): String {
val claims = Jwts.claims().add("role", role).build() val claims = Jwts.claims().add("role", role).build()
val token = Jwts.builder().claims(claims).subject(username) val token = Jwts.builder().claims(claims).subject(username)
@@ -52,15 +52,15 @@ class JwtUtils(
return token return token
} }
// 解析token // 解析 token,校验失败时记日志并返回 null
fun parseToken(token: String): Claims? { fun parseToken(token: String): Claims? {
try { try {
val claims = Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token) val claims = Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token)
return claims.payload return claims.payload
} catch (e: JwtException) { } catch (e: JwtException) {
log.error("Token parsing failed", e) log.error("[JWT] parse failed: {}", e.message)
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
log.error("Token parsing failed with illegalArguments: $token", e) log.error("[JWT] parse failed: illegal argument")
} }
return null return null
} }
@@ -7,6 +7,11 @@ import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Pointcut import org.aspectj.lang.annotation.Pointcut
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
/*
* AOP 日志切面:自动记录方法入口/出口/耗时
* - controller/service/runner/jwtDEBUG 级别,超 500ms 时 WARN
* - mapperTRACE 级别,日常不输出
*/
@Aspect @Aspect
@Component @Component
class LoggingAspect { class LoggingAspect {
@@ -5,6 +5,9 @@ import org.springframework.context.annotation.Configuration
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder import org.springframework.security.crypto.argon2.Argon2PasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.security.crypto.password.PasswordEncoder
/*
* Argon2 密码编码器配置
*/
// 将Argon2的加盐哈希方法注册成Bean // 将Argon2的加盐哈希方法注册成Bean
@Configuration @Configuration
class PasswordConfig { class PasswordConfig {
@@ -2,6 +2,9 @@ package com.msksbr.bookmgr.controller
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
/*
* 管理端图书接口
*/
@RestController @RestController
class AdminBookController { class AdminBookController {
} }
@@ -2,6 +2,9 @@ package com.msksbr.bookmgr.controller
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
/*
* 管理端借阅接口
*/
@RestController @RestController
class AdminBorrowController { class AdminBorrowController {
} }
@@ -14,8 +14,7 @@ import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
/* /*
* 登录接口 * 认证接口:登录 / 登出
* 接收DTO,返回json
*/ */
@RestController @RestController
@RequestMapping("/api/auth") @RequestMapping("/api/auth")
@@ -31,14 +30,13 @@ class AuthController(
loginDTO: UserLoginDTO, loginDTO: UserLoginDTO,
request: HttpServletRequest request: HttpServletRequest
): Result<out Any?> { ): Result<out Any?> {
log.info("Login from ${ipExtractor.getRealIp(request)} username: ${loginDTO.username}") log.info("[Auth] login attempt: user={}, ip={}", loginDTO.username, ipExtractor.getRealIp(request))
log.info("UA: ${request.getHeader("User-Agent")}") log.debug("[Auth] user agent: {}", request.getHeader("User-Agent"))
// 调用service验证
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) val token = jwtUtils.generateToken(user.username, user.role)
log.info("Login success") log.info("[Auth] login success: user={}", user.username)
Result.success( Result.success(
mapOf( mapOf(
"token" to token, "token" to token,
@@ -47,8 +45,8 @@ class AuthController(
) )
) )
} else { } else {
log.info("Login failed") log.info("[Auth] login failed: user={}", loginDTO.username)
Result.error("username or password not match") Result.error("Incorrect username or password")
} }
} }
@@ -56,10 +54,10 @@ class AuthController(
fun logout( fun logout(
request: HttpServletRequest request: HttpServletRequest
): Result<String> { ): Result<String> {
log.info("Logout from ${ipExtractor.getRealIp(request)}") log.info("[Auth] logout: ip={}", ipExtractor.getRealIp(request))
log.info("UA: ${request.getHeader("User-Agent")}") log.debug("[Auth] user agent: {}", request.getHeader("User-Agent"))
// JWT无状态,只需返回成功让客户端自行删除token即可 // JWT 无状态,登出只需客户端删除 token
log.info("Logout success") log.info("[Auth] logout success")
return Result.success("logout successfully") return Result.success("logout successfully")
} }
} }
@@ -2,6 +2,9 @@ package com.msksbr.bookmgr.controller
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
/*
* 图书接口
*/
@RestController @RestController
class BookController { class BookController {
} }
@@ -2,6 +2,9 @@ package com.msksbr.bookmgr.controller
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
/*
* 借阅接口
*/
@RestController @RestController
class BorrowController { class BorrowController {
} }
@@ -2,6 +2,9 @@ package com.msksbr.bookmgr.controller
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
/*
* 仪表盘接口
*/
@RestController @RestController
class DashBoardController { class DashBoardController {
} }
@@ -2,7 +2,9 @@ package com.msksbr.bookmgr.dto
import jakarta.validation.constraints.NotBlank import jakarta.validation.constraints.NotBlank
/*
* 登录请求体
*/
data class UserLoginDTO( data class UserLoginDTO(
@NotBlank(message = "username is required") @NotBlank(message = "username is required")
val username: String?, val username: String?,
@@ -4,6 +4,9 @@ import com.baomidou.mybatisplus.annotation.IdType
import com.baomidou.mybatisplus.annotation.TableId import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName import com.baomidou.mybatisplus.annotation.TableName
/*
* 图书实体,映射 book 表
*/
@TableName("book") @TableName("book")
data class Book( data class Book(
@TableId(type = IdType.AUTO) @TableId(type = IdType.AUTO)
@@ -5,6 +5,9 @@ import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName import com.baomidou.mybatisplus.annotation.TableName
import java.util.Date import java.util.Date
/*
* 借阅记录实体,映射 book_record 表
*/
@TableName("book_record") @TableName("book_record")
data class BorrowRecord( data class BorrowRecord(
@TableId(type = IdType.AUTO) @TableId(type = IdType.AUTO)
@@ -4,6 +4,9 @@ import com.baomidou.mybatisplus.annotation.IdType
import com.baomidou.mybatisplus.annotation.TableId import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName import com.baomidou.mybatisplus.annotation.TableName
/*
* 用户实体,映射 user 表
*/
@TableName("user") @TableName("user")
data class User( data class User(
@TableId(type = IdType.AUTO) // 设置自增主键 @TableId(type = IdType.AUTO) // 设置自增主键
@@ -1,12 +1,13 @@
package com.msksbr.bookmgr.interceptor package com.msksbr.bookmgr.interceptor
import com.fasterxml.jackson.databind.ObjectMapper
import com.msksbr.bookmgr.config.JwtUtils import com.msksbr.bookmgr.config.JwtUtils
import com.msksbr.bookmgr.template.Result import com.msksbr.bookmgr.template.Result
import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse import jakarta.servlet.http.HttpServletResponse
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
import org.springframework.web.servlet.HandlerInterceptor import org.springframework.web.servlet.HandlerInterceptor
import tools.jackson.databind.ObjectMapper
@Component @Component
class JwtAuthInterceptor( class JwtAuthInterceptor(
@@ -4,5 +4,8 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper
import com.msksbr.bookmgr.entity.Book import com.msksbr.bookmgr.entity.Book
import org.apache.ibatis.annotations.Mapper import org.apache.ibatis.annotations.Mapper
/*
* 图书 Mapper,继承 MyBatis-Plus BaseMapper 获得通用 CRUD
*/
@Mapper @Mapper
interface BookMapper: BaseMapper<Book> interface BookMapper: BaseMapper<Book>
@@ -4,5 +4,8 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper
import com.msksbr.bookmgr.entity.BorrowRecord import com.msksbr.bookmgr.entity.BorrowRecord
import org.apache.ibatis.annotations.Mapper import org.apache.ibatis.annotations.Mapper
/*
* 借阅记录 Mapper,继承 MyBatis-Plus BaseMapper 获得通用 CRUD
*/
@Mapper @Mapper
interface BorrowRecordMapper: BaseMapper<BorrowRecord> interface BorrowRecordMapper: BaseMapper<BorrowRecord>
@@ -4,5 +4,8 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper
import com.msksbr.bookmgr.entity.User import com.msksbr.bookmgr.entity.User
import org.apache.ibatis.annotations.Mapper import org.apache.ibatis.annotations.Mapper
/*
* 用户 Mapper,继承 MyBatis-Plus BaseMapper 获得通用 CRUD
*/
@Mapper @Mapper
interface UserMapper: BaseMapper<User> interface UserMapper: BaseMapper<User>
@@ -10,21 +10,24 @@ import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional import org.springframework.transaction.annotation.Transactional
/*
* 应用启动时初始化默认用户(admin / user01 / user02
* 已存在的用户会跳过,因此可安全重复执行
*/
@Component @Component
class InitUserRunner( class InitUserRunner(
val passwordEncoder: PasswordEncoder, val passwordEncoder: PasswordEncoder,
val userMapper: UserMapper, val userMapper: UserMapper,
) : ApplicationRunner { ) : ApplicationRunner {
// 添加注解,失败时可回滚
@Transactional @Transactional
override fun run(args: ApplicationArguments) { override fun run(args: ApplicationArguments) {
log.info("Starting default user initialization") log.info("[InitUser] starting")
val existsAdmin = userMapper.selectOne( val existsAdmin = userMapper.selectOne(
QueryWrapper<User>() QueryWrapper<User>()
.eq("username", "admin") .eq("username", "admin")
) )
if (existsAdmin == null) { if (existsAdmin == null) {
log.info("Admin user not found, creating...") log.info("[InitUser] admin not found, creating")
insertAdmin() insertAdmin()
} }
val existsUser01 = userMapper.selectOne( val existsUser01 = userMapper.selectOne(
@@ -32,7 +35,7 @@ class InitUserRunner(
.eq("username", "user01") .eq("username", "user01")
) )
if (existsUser01 == null) { if (existsUser01 == null) {
log.info("Common user01 not found, creating...") log.info("[InitUser] user01 not found, creating")
insertUser01() insertUser01()
} }
val existsUser02 = userMapper.selectOne( val existsUser02 = userMapper.selectOne(
@@ -40,10 +43,10 @@ class InitUserRunner(
.eq("username", "user02") .eq("username", "user02")
) )
if (existsUser02 == null) { if (existsUser02 == null) {
log.info("Common user02 not found, creating...") log.info("[InitUser] user02 not found, creating")
insertUser02() insertUser02()
} }
log.info("Default user initialization completed") log.info("[InitUser] completed")
} }
fun insertAdmin() { fun insertAdmin() {
@@ -4,7 +4,7 @@ import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
/* /*
* 手动日志属性 * SLF4J 日志扩展属性,通过 T.log 在任意类获取 Logger
*/ */
val <T: Any> T.log: Logger val <T: Any> T.log: Logger
@@ -3,6 +3,9 @@ package com.msksbr.bookmgr.service
import com.msksbr.bookmgr.dto.UserLoginDTO import com.msksbr.bookmgr.dto.UserLoginDTO
import com.msksbr.bookmgr.entity.User import com.msksbr.bookmgr.entity.User
/*
* 认证服务
*/
interface AuthService { interface AuthService {
fun login(loginDTO: UserLoginDTO): User? fun login(loginDTO: UserLoginDTO): User?
} }
@@ -8,6 +8,9 @@ import com.msksbr.bookmgr.service.AuthService
import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
/*
* 认证服务实现,含时序攻击防护
*/
@Service @Service
class AuthServiceImpl(private val userMapper: UserMapper, private val passwordEncoder: PasswordEncoder) : AuthService { class AuthServiceImpl(private val userMapper: UserMapper, private val passwordEncoder: PasswordEncoder) : AuthService {
override fun login(loginDTO: UserLoginDTO): User? { override fun login(loginDTO: UserLoginDTO): User? {
@@ -1,5 +1,8 @@
package com.msksbr.bookmgr.template package com.msksbr.bookmgr.template
/*
* 统一 API 响应格式
*/
data class Result<T>( data class Result<T>(
var code: Int, var code: Int,
var message: String, var message: String,