diff --git a/.gitignore b/.gitignore
index 2827609..9d0c258 100644
--- a/.gitignore
+++ b/.gitignore
@@ -45,3 +45,4 @@ out/
**/application-dev.properties
**/.env
/log/
+.idea/
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..0f413b5
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 msksbr
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.MD b/README.MD
new file mode 100644
index 0000000..94614a4
--- /dev/null
+++ b/README.MD
@@ -0,0 +1,264 @@
+# 图书管理系统
+
+
+
+
+
+
+
+
+
+
+一个基于 **Spring Boot + Kotlin + MyBatis-Plus** 的后端图书管理系统,提供图书管理、用户认证、借阅归还等核心功能。项目注重安全性、可审计性,等保三级审计日志开箱即用。
+
+## 功能
+
+- **认证与授权** — JWT 无状态登录,Argon2 密码哈希,基于 AOP 的角色权限校验(admin / user),防用户枚举攻击
+- **图书管理** — 搜索(书名 / 作者)、查看详情、新增 / 修改 / 删除 / 调整库存(管理员),书名 + 作者判重
+- **借阅管理** — 借书 / 还书(自动扣减 / 恢复库存)、个人借阅记录、管理员可查看全部借阅及手动操作
+- **审计日志** — 等保三级(GB/T 22239-2019 8.1.4.3),结构化审计日志,含用户标识、来源 IP、操作参数、结果摘要,支持持久化到文件
+- **统一响应** — 所有接口返回 `ApiResult` 统一格式,业务码自动映射 HTTP 状态码
+- **OpenAPI 文档** — SpringDoc 自动生成,支持在线调试
+
+## 技术栈
+
+| 类别 | 技术 | 版本 | 说明 |
+|------|------|------|------|
+| 语言 | Kotlin | 2.2.21 | JVM 目标 |
+| 框架 | Spring Boot | 4.0.6 | |
+| ORM | MyBatis-Plus | 3.5.15 | 零 XML 配置,BaseMapper 通用 CRUD |
+| 数据库 | MySQL | 8.0 | InnoDB,utf8mb4 |
+| 认证 | jjwt (JSON Web Token) | 0.13.0 | HMAC-SHA256 签名 |
+| 密码 | Argon2 (Spring Security Crypto) | 5.3+ | 抗 GPU/ASIC 暴力破解 |
+| 协程 | kotlinx-coroutines | 1.10.2 | 并发查询用户 + 图书信息 |
+| 文档 | SpringDoc OpenAPI | 3.0.3 | Swagger UI |
+| AOP | AspectJ Weaver | — | 日志 / 审计 / 权限三切面 |
+| 日志 | Logback + kotlin-logging | 7.0.0 | Janino 条件表达式,按天滚动 |
+
+## 快速开始
+
+### 1. 准备数据库
+
+MySQL 8.0 中新建一个数据库(如 `bookmgr`),然后运行建表脚本:
+
+```bash
+mysql -u root -p bookmgr < bookmgr.schema.sql
+```
+
+脚本只建表,不插数据。默认用户由应用首次启动时自动创建。
+
+### 2. 配置环境变量
+
+生产环境通过环境变量注入配置:
+
+```bash
+export DB_DRIVER=com.mysql.cj.jdbc.Driver
+export DB_TYPE=mysql
+export DB_URL=localhost
+export DB_PORT=3306
+export DB_NAME=bookmgr
+export DB_USER=root
+export DB_PASSWORD=your-password
+export JWT_SECRET=your-secret-at-least-32-chars
+export JWT_EXPIRATION_TIME=86400000 # 可选,默认 24 小时,单位毫秒
+export LOG_PATH=/var/log/bookmgr # 可选,不设置则不写日志文件
+export LOG_EXPORT_AUDIT=true # 可选,审计日志持久化,默认开启
+export LOG_EXPORT_RUNTIME=false # 可选,运行日志持久化,默认关闭
+export LOG_LEVEL=INFO # 可选,包级别日志级别,默认 INFO
+```
+
+| 变量 | 必填 | 说明 |
+|------|------|------|
+| `DB_DRIVER` | 是 | 驱动类名 |
+| `DB_TYPE` | 是 | 数据库类型(mysql) |
+| `DB_URL` | 是 | 数据库地址 |
+| `DB_PORT` | 是 | 数据库端口 |
+| `DB_NAME` | 是 | 数据库名 |
+| `DB_USER` | 是 | 数据库用户 |
+| `DB_PASSWORD` | 是 | 数据库密码 |
+| `JWT_SECRET` | **强烈建议** | 不设置则每次启动随机生成,已签发 token 全部失效 |
+| `JWT_EXPIRATION_TIME` | 否 | token 有效期(毫秒),默认 86400000(24 小时) |
+
+### 3. 运行
+
+```bash
+./gradlew bootRun
+```
+
+### 4. 默认用户
+
+应用首次启动时自动创建以下用户(幂等,已存在则跳过):
+
+| 用户名 | 密码 | 角色 |
+|--------|------|------|
+| `admin` | `admin` | 管理员 |
+| `user01` | `user01` | 普通用户 |
+| `user02` | `user02` | 普通用户 |
+
+> **注意**:预置用户当前硬编码在 `InitUserRunner` 中。未来计划分三步演进:硬编码 → 配置文件 → 前端初始化向导。生产部署后请第一时间修改密码。
+
+## 开发指南
+
+### IDEA 运行配置
+
+1. 复制 `src/main/resources/application.yaml` 为 `application-dev.yaml`,修改数据库连接等本地配置
+2. IDEA → Run Configuration → Active profiles 填写 `dev`
+
+
+
+3. 启动即可
+
+示例 `application-dev.yaml`:
+
+```yaml
+spring:
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/bookmgr
+ username: root
+ password: your-password
+ jackson:
+ date-format: yyyy-MM-dd HH:mm:ss
+ time-zone: GMT+8
+mybatis-plus:
+ configuration:
+ log-impl: org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl
+logging:
+ level:
+ com.msksbr.bookmgr: DEBUG
+ export:
+ path: ./log
+ runtime-enabled: true
+jwt:
+ expiration: 86400000
+springdoc:
+ swagger-ui:
+ enabled: true
+ api-docs:
+ enabled: true
+```
+
+### Swagger 文档
+
+开发环境启动后访问:`http://localhost:8080/swagger-ui/index.html`
+
+> 生产环境默认关闭,防止 API 文档暴露。
+
+### API 概览
+
+```
+公开接口:
+ POST /api/auth/login — 登录
+ POST /api/auth/logout — 登出
+ GET /api/books/search — 搜索图书
+ GET /api/books/getone — 图书详情
+ GET /api/books/getall — 所有图书
+
+普通用户(需 JWT + user 角色):
+ GET /api/borrows/getall — 我的借阅
+ GET /api/borrows/search — 搜索我的借阅
+ GET /api/borrows/getone — 借阅详情
+ POST /api/borrows/borrowbook — 借书
+ POST /api/borrows/returnbook — 还书
+
+管理员(需 JWT + admin 角色):
+ POST /api/admin/books/add — 新增图书
+ POST /api/admin/books/update — 修改图书
+ POST /api/admin/books/delete — 删除图书
+ POST /api/admin/books/update-stock — 调整库存
+ GET /api/admin/borrows/search — 搜索借阅
+ GET /api/admin/borrows/getone — 借阅详情
+ GET /api/admin/borrows/getall — 全部借阅
+ POST /api/admin/borrows/borrowbook — 手动借书
+ POST /api/admin/borrows/returnbook — 手动还书
+```
+
+请求示例:
+
+```bash
+# 登录获取 token
+curl -X POST http://localhost:8080/api/auth/login \
+ -H "Content-Type: application/json" \
+ -d '{"username":"admin","password":"admin"}'
+
+# 搜索图书
+curl "http://localhost:8080/api/books/search?query=西游记"
+
+# 借书
+curl -X POST "http://localhost:8080/api/borrows/borrowbook?bookId=1" \
+ -H "Authorization: Bearer "
+```
+
+### 项目结构
+
+```
+src/main/kotlin/com/msksbr/bookmgr/
+├── controller/ # 控制器 — 接口入口
+├── service/ # 服务接口
+│ └── impl/ # 服务实现 — 业务逻辑
+├── mapper/ # MyBatis-Plus 映射器 — 数据访问
+├── entity/ # 实体 — 表映射
+├── dto/ # 请求体 DTO
+├── vo/ # 响应 VO
+│ └── borrow/ # 借阅相关 VO
+├── config/ # 配置 — JWT / 密码 / AOP 切面 / 异常处理 / OpenAPI
+├── annotation/ # 自定义注解 — @RequireRole
+├── runner/ # 启动任务 — InitUserRunner
+├── script/ # 扩展 — log / audit Logger 快捷获取
+└── template/ # ApiResult 统一响应模板
+```
+
+## 部署
+
+### Java 直接运行
+
+```bash
+./gradlew bootJar
+java -jar build/libs/bookMgr-0.1.jar
+```
+
+> `bootJar` 会自动排除 `application-dev.yaml`。
+
+### Docker
+
+Dockerfile 待补充。镜像后续发布至私有 Gitea 仓库:
+
+[git.msksbr.com/msksbr/bookMgr](https://git.msksbr.com/msksbr/bookMgr)
+
+### Docker Compose
+
+Compose 文件待补充。注意生产环境应执行 `bookmgr.schema.sql`(仅建表,不含种子数据),用户由应用初始化。
+
+## 审计日志
+
+满足等保三级要求的结构化审计日志,默认输出到控制台。配置 `LOG_PATH` 后自动持久化到文件:
+
+```
+$LOG_PATH/
+├── audit/
+│ ├── audit.log # 当天审计日志
+│ └── audit.2026-05-23.0.gz # 历史归档(滚动策略:按天 + 100MB,保留 365 天,上限 20GB)
+└── runtime/
+ ├── runtime.log # 当天运行日志(需 LOG_EXPORT_RUNTIME=true)
+ └── runtime.2026-05-23.0.gz
+```
+
+审计日志记录字段:事件类型(CTRL / SVC)、用户名、用户 ID、角色、来源 IP、User-Agent、HTTP 方法 + 路径、操作参数(敏感字段自动掩码)、返回状态码、操作对象 ID、耗时。
+
+## 安全特性
+
+- 密码使用 **Argon2** 哈希存储,即使数据库泄露也无法还原
+- 登录失败时对齐响应时间(dummy hash),防止基于延时的用户枚举
+- 敏感参数(password / token / secret / authorization)在审计日志中自动掩码为 `***`
+- JWT 密钥支持环境变量注入,不配置则每次启动随机生成
+- 全局异常捕获,不向客户端泄露堆栈信息
+- 生产环境默认关闭 Swagger 文档
+
+## 声明
+
+本项目基于 **MIT** 协议开源。本软件无任何销售渠道,不收取费用。请遵守开源协议合法使用。
+
+## 作者
+
+- 个人主页:[msksbr.com](https://msksbr.com/)
+- 项目仓库:[git.msksbr.com/msksbr/bookMgr](https://git.msksbr.com/msksbr/bookMgr)
diff --git a/bookmgr.schema.sql b/bookmgr.schema.sql
new file mode 100644
index 0000000..0420a89
--- /dev/null
+++ b/bookmgr.schema.sql
@@ -0,0 +1,59 @@
+/*
+ Navicat Premium Dump SQL
+
+ Source Server : root@localhost
+ Source Server Type : MySQL
+ Source Server Version : 80045 (8.0.45)
+ Source Host : 127.0.0.1:3306
+ Source Schema : bookmgr
+
+ Target Server Type : MySQL
+ Target Server Version : 80045 (8.0.45)
+ File Encoding : 65001
+
+ Date: 24/05/2026 01:05:44
+*/
+
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+-- ----------------------------
+-- Table structure for book
+-- ----------------------------
+DROP TABLE IF EXISTS `book`;
+CREATE TABLE `book` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `author` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+ `stock` int DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- ----------------------------
+-- Table structure for borrow_record
+-- ----------------------------
+DROP TABLE IF EXISTS `borrow_record`;
+CREATE TABLE `borrow_record` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `user_id` bigint DEFAULT NULL,
+ `book_id` bigint DEFAULT NULL,
+ `borrow_time` datetime DEFAULT NULL,
+ `return_time` datetime DEFAULT NULL,
+ `status` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- ----------------------------
+-- Table structure for user
+-- ----------------------------
+DROP TABLE IF EXISTS `user`;
+CREATE TABLE `user` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `username` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `role` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT 'USER',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `username` (`username`)
+) ENGINE=InnoDB AUTO_INCREMENT=25 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+SET FOREIGN_KEY_CHECKS = 1;
diff --git a/build.gradle.kts b/build.gradle.kts
index 61aaf18..0b0b136 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -3,10 +3,11 @@ plugins {
kotlin("plugin.spring") version "2.2.21"
id("org.springframework.boot") version "4.0.6"
id("io.spring.dependency-management") version "1.1.7"
+ id("com.bmuschko.docker-spring-boot-application") version "10.0.0"
}
group = "com.msksbr"
-version = "0.0.1-SNAPSHOT"
+version = "0.1"
description = "bookMgr"
java {
@@ -58,3 +59,23 @@ tasks.bootJar {
exclude("application-dev.yaml")
}
+val giteaRegistry = System.getenv("GITEA_REGISTRY") ?: "gitea.example.com"
+val giteaUser = System.getenv("GITEA_USERNAME") ?: ""
+val giteaToken = System.getenv("GITEA_TOKEN") ?: ""
+
+docker {
+ springBootApplication {
+ baseImage.set("bellsoft/liberica-runtime-container:jre-21-slim-musl")
+ ports.set(listOf(8080))
+ images.set(setOf(
+ "$giteaRegistry/$giteaUser/bookMgr:${project.version}",
+ "$giteaRegistry/$giteaUser/bookMgr:latest"
+ ))
+ jvmArgs.set(listOf("-Xmx512m", "-Dfile.encoding=UTF-8"))
+ }
+ registryCredentials {
+ url.set("https://$giteaRegistry")
+ username.set(giteaUser)
+ password.set(giteaToken)
+ }
+}
diff --git a/readme.Assets/IdeaRunConfig.png b/readme.Assets/IdeaRunConfig.png
new file mode 100644
index 0000000..d300c9a
Binary files /dev/null and b/readme.Assets/IdeaRunConfig.png differ