# MyBlog **Repository Path**: CyberSoviet/MyBlog ## Basic Information - **Project Name**: MyBlog - **Description**: 一个简单的基于SpringBoot的个人博客 - **Primary Language**: Unknown - **License**: BSD-3-Clause - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-26 - **Last Updated**: 2026-10-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Blog 系统 README [![License](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) ## 项目概述 一个基于 Spring Boot 4.1.1 + JDK 25 的个人博客系统。支持 Markdown 文章在线编辑与预览、压缩包上传与下载、图片外置存储。前端使用 TypeScript + markdown-it 渲染,后端提供 REST API。 ## 技术栈 | 层次 | 技术 | 版本 | 说明 | |------|------|------|------| | 语言 | Java | 25 (LTS) | 2025年9月发布,Oracle 支持至 2033 年 | | 框架 | Spring Boot | 4.1.1 | 2026年8月发布,包含 98 项 Bug 修复 | | 框架 | Spring Framework | 7.0.9+ | Spring Boot 4.1.1 的最低要求 | | 数据库 | MySQL | 8.x | `MEDIUMTEXT` 存 Markdown 原文 | | ORM | Spring Data JPA | 随 Boot 管理 | Hibernate 7 基础 | | JSON | Jackson 3 | 随 Boot 管理 | 包名从 `com.fasterxml.jackson` 变更为 `tools.jackson` | | ZIP | JDK内置 | 随JDK | 支持加密压缩包; | | 构建 | Maven | 3.6.3+ | `./mvnw` 自动管理 | | 前端 | VueTS | 最新 | 原生 TS | | 渲染 | markdown-it | 最新 | 前端渲染 Markdown → HTML | ### 系统要求 - **JDK 25**(Spring Boot 4.1.1 兼容 Java 17 ~ 26,推荐 25 LTS) - **Maven 3.6.3+** 或使用项目自带的 `./mvnw` - **MySQL 8.x** - **Node.js 20+**(前端开发) ## 项目结构 ``` blog/ ├── backend/ # Spring Boot 后端 │ ├── src/main/java/com/example/blog/ │ │ ├── BlogApplication.java │ │ ├── config/ │ │ │ ├── WebConfig.java # 静态资源映射 │ │ │ └── JacksonConfig.java # Jackson 3 配置(如需) │ │ ├── entity/ │ │ │ └── Article.java │ │ ├── repository/ │ │ │ └── ArticleRepository.java │ │ ├── service/ │ │ │ ├── ArticleService.java │ │ │ └── ZipService.java # 压缩包处理 │ │ ├── controller/ │ │ │ └── ArticleController.java │ │ └── dto/ │ │ ├── ArticleListDto.java # 列表用,不含 content │ │ ├── ArticleDetailDto.java # 详情用,含 content │ │ └── ArticleSaveDto.java # 保存用 │ ├── src/main/resources/ │ │ └── application.yml │ └── pom.xml └── README.md # 前端暂时省略 ``` **磁盘存储目录(jar 外部,运行时读写):** ``` /var/www/blog-data/ # 由配置 blog.storage.root 指定 └── articles/ ├── gdb-cheatsheet/ │ └── images/ │ ├── a.png │ └── b.png └── systemd-intro/ └── images/ └── c.png ``` > ⚠️ **不要把图片存到 `src/main/resources` 下**。打包成 jar 后该目录只读,无法写入。必须使用 jar 外部的磁盘目录。 ## 架构设计 ### 数据流 ``` 【上传】 用户上传 .zip(含 .md + images/) → 后端解压到临时目录(校验 zip slip) → 提取 Markdown 内容 → 存入 MySQL → 移动 images/ → {storageRoot}/articles/{slug}/images/ → 清理临时目录 【在线编辑】 前端 textarea 输入 Markdown → 左侧编辑,右侧 markdown-it 实时预览 → 保存 → PUT /api/articles/{id} → 更新 MySQL 【渲染显示】 GET /api/articles/{slug} → 后端返回 { title, content: "markdown原文", ... } (JSON) → 前端 markdown-it 渲染 → 插入页面 → 图片路径替换为 /articles/{slug}/images/xxx 【下载】 GET /api/articles/{slug}/download → 后端从 MySQL 取 Markdown → 从磁盘取 images/ → 打包为 .zip 返回 【图片访问】 Markdown 中 ![](images/a.png) → 后端静态资源映射 /articles/** → file:{storageRoot}/articles/ → 前端渲染时替换为 /articles/{slug}/images/a.png ``` ### 存储分工原则 | 内容 | 存储位置 | 原因 | |------|----------|------| | Markdown 原文 | MySQL `MEDIUMTEXT` | 纯文本,查询方便,单行 16MB | | 图片文件 | 磁盘 `{storageRoot}/articles/{slug}/images/` | 避免数据库膨胀,直读文件 | | 文章元数据 | MySQL(title, slug, summary, create_time) | 列表查询、排序、搜索 | ### 数据库表 ```sql CREATE DATABASE blog DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE blog; CREATE TABLE article ( id BIGINT PRIMARY KEY AUTO_INCREMENT, slug VARCHAR(100) UNIQUE NOT NULL COMMENT 'URL 路径标识,也是图片目录名', title VARCHAR(200) NOT NULL, content MEDIUMTEXT NOT NULL COMMENT 'Markdown 原文', summary VARCHAR(500) COMMENT '列表页摘要', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ``` > `slug` 是文章的 URL 友好标识(如 `gdb-cheatsheet`),同时作为图片在磁盘上的目录名。不单独存储文件路径,路径由 `slug` 推导得出,避免数据不一致。 ## 后端配置 ### `application.yml` ```yaml server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/blog?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8 username: root password: ${DB_PASSWORD:root} driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: none show-sql: true properties: hibernate: format_sql: true blog: storage: root: ${BLOG_STORAGE_ROOT:./data} # 开发环境用 ./data,生产环境改为 /var/www/blog-data ``` ### Jackson 3 注意事项 Spring Boot 4 默认使用 Jackson 3,包名从 `com.fasterxml.jackson` 变更为 `tools.jackson`。如果你需要自定义序列化: ```java // Jackson 3 的包名 import tools.jackson.databind.json.JsonMapper; import tools.jackson.databind.module.SimpleModule; ``` `LocalDateTime` 的格式化在 Jackson 3 中需要显式配置模块(`JavaTimeModule` 已内置但默认不注册): ```java @Configuration public class JacksonConfig { @Bean public JsonMapper jsonMapper() { return JsonMapper.builder() .addModule(new SimpleModule() .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))) .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))) .build(); } } ``` ### 静态资源映射(WebMvcConfigurer) ```java @Configuration public class WebConfig implements WebMvcConfigurer { @Value("${blog.storage.root}") private String storageRoot; @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/articles/**") .addResourceLocations("file:" + storageRoot + "/articles/"); } } ``` > **Spring Boot 4 注意**:`WebMvcConfigurerAdapter` 已被移除。`setUseTrailingSlashMatch()` 也已在 Spring Framework 7.0 中删除,不能再用于配置尾部斜杠匹配。上述 `addResourceHandlers` 方式仍然有效。 ### ZIP 处理依赖 ```xml net.lingala.zip4j zip4j 2.11.5 ``` 也可以使用 JDK 内置的 `java.util.zip` 避免额外依赖。zip4j 的优势是支持加密压缩包和更简洁的 API。 ## API 接口 | 方法 | 路径 | 用途 | 返回 | |------|------|------|------| | GET | `/api/articles` | 文章列表 | `[{ id, slug, title, summary, createTime }]` | | GET | `/api/articles/{slug}` | 文章详情 | `{ id, slug, title, content, createTime }` | | POST | `/api/articles` | 上传 zip 新建 | `{ id, slug }` | | PUT | `/api/articles/{id}` | 在线编辑保存 | `{ id, slug }` | | POST | `/api/articles/{id}/upload` | 上传 zip 覆盖已有文章 | `{ id, slug }` | | GET | `/api/articles/{slug}/download` | 下载文章 zip | `.zip` 文件流 | | DELETE | `/api/articles/{id}` | 删除文章 | 204 | > 列表接口**不返回 `content`**。1000 篇文章的 Markdown 全量返回会导致前端卡死。列表只返回标题、摘要、时间。 ### DTO 设计 ```java // 列表 DTO — 不含 content public record ArticleListDto( Long id, String slug, String title, String summary, LocalDateTime createTime ) {} // 详情 DTO — 含 content public record ArticleDetailDto( Long id, String slug, String title, String content, LocalDateTime createTime, LocalDateTime updateTime ) {} // 保存 DTO public record ArticleSaveDto( String title, String slug, String content ) {} ``` ## 前端配置 ### `vite.config.ts` ```ts import { defineConfig } from 'vite' export default defineConfig({ server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, }, '/articles': { target: 'http://localhost:8080', changeOrigin: true, }, }, }, }) ``` `/articles` 的 proxy 用于开发环境访问图片资源。 ### markdown-it 渲染与图片路径处理 ```ts import MarkdownIt from 'markdown-it' const md = new MarkdownIt({ html: true, linkify: true, breaks: true, }) /** * 将 Markdown 中的相对图片路径转换为绝对路径 * images/a.png → /articles/{slug}/images/a.png */ export function renderMarkdown(content:string, slug:string):string { const fixed = content.replace( /!\[([^\]]*)\]\((?:\.\/images|images)\//g, `![$1](/articles/${slug}/images/` ) return md.render(fixed) } ``` ## 开发路线 **阶段 1:读和显示** 手写一篇 Markdown 存入数据库 → 后端 `GET /api/articles/{slug}` 返回 → 前端 markdown-it 渲染 → 页面上看到渲染后的文章。目标是跑通“数据库 → JSON → 前端渲染”这条线。 **阶段 2:在线编辑** 添加 `PUT /api/articles/{id}` 接口 → 前端 textarea + 实时预览 → 能修改文章并保存。 **阶段 3:图片** 后端静态资源映射 `/articles/**` → Markdown 图片路径替换 → 文章中的图片正常显示。 **阶段 4:zip 上传** 解压、校验、提取 Markdown、移动 images → 上传压缩包自动创建文章。解压时必须校验 zip slip:检查每个解压条目的规范路径是否在目标目录内。 **阶段 5:zip 下载** 从数据库取 Markdown + 从磁盘取 images → 打包为 zip 返回。 阶段 4 和 5 的坑最多(zip slip、编码、路径处理),前三个阶段跑通后再做。 ## 启动方式 ### 后端 ```bash cd backend ./mvnw spring-boot:run # 默认 http://localhost:8080 ``` ### 前端 ```bash cd frontend npm install npm run dev # 默认 http://localhost:5173 ``` ### 数据库 ```bash mysql -u root -p < schema.sql ``` ## 已知限制与后续扩展 | 限制 | 当前方案 | 后续可扩展 | |------|----------|------------| | 无认证 | 所有 API 开放 | Spring Security + JWT | | 无全文搜索 | 未实现 | MySQL LIKE / 全文索引 / Elasticsearch | | 无缓存 | 每次请求实时返回 | Caffeine / Redis 缓存渲染结果 | | 无分页 | 列表返回全部 | `Pageable` 分页 | | 无图片压缩 | 原图存储 | 上传时生成缩略图 |