Spring Boot + Vue3 个人博客系统
前言
欲渡黄河冰塞川,将登太行雪满山。
------李白《行路难》
有时候会思考,AI 给人类带来了便利、效率、社会的进步,但是真的有让我们生活更美好吗?作为前端,想必大家也感受到了冲击。想着尝试下 Java,下面是搭建的一个博客系统,主要介绍后端基础知识。
一、项目简介
这是一个前后端分离的个人博客系统。后端基于 Java 21 + Spring Boot 3 + Spring Data JPA + MySQL 提供 RESTful API;前端使用 Vue 3 + Vite + Pinia 构建页面,通过 Axios 调用后端接口完成文章的增删改查。
整体采用经典的三层架构:
bash
浏览器(Vue3 前端)
│
│ HTTP 请求 /api/articles
▼
Controller 控制器层 ← 定义 API 路由,接收/返回 JSON
│
▼
Service 业务层 ← 处理业务逻辑、事务控制
│
▼
Repository 数据层 ← JPA 操作数据库
│
▼
MySQL 数据库(blog 库,article 表)
二、技术栈
| 类别 | 技术 | 说明 |
|---|---|---|
| 语言 | Java 21 | 项目编译与运行版本 |
| 框架 | Spring Boot 3.4.5 | 快速构建 Web 应用 |
| ORM | Spring Data JPA + Hibernate | 对象关系映射,免写大部分 SQL |
| 数据库 | MySQL 8 | 持久化存储 |
| 校验 | Jakarta Validation | 请求参数自动校验 |
| 工具 | Lombok | 减少 getter/setter 等样板代码 |
| 构建 | Maven | 依赖管理与打包 |
前端(简要):Vue 3、Vue Router、Pinia、Axios、Vite,开发端口 5173,通过代理将 /api 转发到后端 8080。
三、后端目录结构
bash
backend/
├── pom.xml # Maven 依赖配置
├── mvnw / mvnw.cmd # Maven Wrapper,无需全局安装 Maven
└── src/main/
├── java/com/blog/
│ ├── BlogBackendApplication.java # 程序入口
│ ├── config/
│ │ └── CorsConfig.java # 跨域配置
│ ├── controller/
│ │ └── ArticleController.java # 文章 API 接口
│ ├── service/
│ │ └── ArticleService.java # 文章业务逻辑
│ ├── repository/
│ │ └── ArticleRepository.java # 数据访问接口
│ ├── entity/
│ │ └── Article.java # 数据库实体(表映射)
│ ├── dto/
│ │ └── ArticleRequest.java # 请求参数对象
│ ├── common/
│ │ └── ApiResponse.java # 统一响应格式
│ └── exception/
│ └── GlobalExceptionHandler.java # 全局异常处理
└── resources/
└── application.yml # 应用配置(端口、数据库等)
四、各 Java 文件详解
1. BlogBackendApplication.java --- 启动入口
java
@SpringBootApplication
public class BlogBackendApplication {
public static void main(String[] args) {
SpringApplication.run(BlogBackendApplication.class, args);
}
}
@SpringBootApplication 是一个组合注解,等价于开启自动配置、组件扫描和配置类注册。执行 main 方法后,Spring 容器启动,自动发现并注册所有带 @RestController、@Service、@Configuration 等注解的 Bean。
启动命令:
bash
cd backend
.\mvnw.cmd spring-boot:run
服务默认监听 8080 端口。
2. application.yml --- 核心配置
yaml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/blog?...
username: root
password: 你的密码
jpa:
hibernate:
ddl-auto: update # 根据 Entity 自动建表/更新表结构
show-sql: true # 控制台打印 SQL,便于调试
open-in-view: false # 关闭 OSIV,避免懒加载问题延伸到 Web 层
关键配置说明:
ddl-auto: update:首次启动时根据Article实体自动创建article表,字段变更时自动同步,开发阶段非常方便。show-sql: true:在终端输出 Hibernate 生成的 SQL,便于排查问题。open-in-view: false:推荐的生产实践,事务在 Service 层结束,避免在 Controller 渲染时才触发数据库查询。
3. entity/Article.java --- 数据库实体
实体类与数据库表 article 一一对应,是 JPA 操作的核心对象。
java
@Data
@Entity
@Table(name = "article")
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; // 主键,自增
@Column(nullable = false, length = 200)
private String title; // 标题,最长 200 字符
@Column(nullable = false, columnDefinition = "TEXT")
private String content; // 正文,TEXT 类型
@Column(length = 500)
private String summary; // 摘要,可选
@Column(nullable = false, length = 50)
private String author; // 作者
@CreationTimestamp
@Column(nullable = false, updatable = false)
private LocalDateTime createdAt; // 创建时间,插入时自动填充
@UpdateTimestamp
@Column(nullable = false)
private LocalDateTime updatedAt; // 更新时间,每次修改自动刷新
}
注解含义:
| 注解 | 作用 |
|---|---|
@Entity |
声明为 JPA 实体 |
@Table(name = "article") |
映射到 article 表 |
@Id + @GeneratedValue |
主键 + 数据库自增策略 |
@Column |
列约束(非空、长度、类型) |
@CreationTimestamp |
Hibernate 在插入时自动写入当前时间 |
@UpdateTimestamp |
Hibernate 在更新时自动刷新时间 |
@Data(Lombok) |
自动生成 getter/setter/toString 等 |
注意:createdAt 和 updatedAt 由 Hibernate 管理,前端创建/更新文章时不需要传入这两个字段。
4. dto/ArticleRequest.java --- 请求参数对象
DTO(Data Transfer Object)用于接收前端传来的 JSON,与 Article 实体分离,避免把数据库字段(如 id、时间戳)暴露给外部随意修改。
java
@Data
public class ArticleRequest {
@NotBlank(message = "标题不能为空")
@Size(max = 200, message = "标题长度不能超过200")
private String title;
@NotBlank(message = "内容不能为空")
private String content;
@Size(max = 500, message = "摘要长度不能超过500")
private String summary;
@NotBlank(message = "作者不能为空")
@Size(max = 50, message = "作者长度不能超过50")
private String author;
}
校验规则:
@NotBlank:不能为 null、空字符串或纯空格@Size(max = N):字符串最大长度限制
Controller 方法参数加上 @Valid 后,校验失败会抛出 MethodArgumentNotValidException,由全局异常处理器统一返回 400 错误信息。
5. repository/ArticleRepository.java --- 数据访问层
java
public interface ArticleRepository extends JpaRepository<Article, Long> {
}
继承 JpaRepository<Article, Long> 后,无需编写实现类,Spring Data JPA 在运行时自动生成代理,直接拥有以下方法:
| 方法 | 作用 |
|---|---|
findAll() |
查询全部文章 |
findById(id) |
按 ID 查询,返回 Optional<Article> |
save(article) |
新增或更新(有 id 则更新,无 id 则插入) |
deleteById(id) |
按 ID 删除 |
existsById(id) |
判断记录是否存在 |
如需复杂查询(按作者筛选、分页等),可在此接口中按命名规则添加方法,例如 List<Article> findByAuthor(String author)。
6. service/ArticleService.java --- 业务逻辑层
Service 是业务核心,Controller 不直接操作数据库,所有逻辑在此完成。
java
@Service
@RequiredArgsConstructor
public class ArticleService {
private final ArticleRepository articleRepository;
// 查询列表
public List<Article> listAll() {
return articleRepository.findAll();
}
// 查询详情,不存在则抛异常
public Article getById(Long id) {
return articleRepository.findById(id)
.orElseThrow(() -> new RuntimeException("文章不存在"));
}
// 创建文章
@Transactional
public Article create(ArticleRequest request) {
Article article = new Article();
copyProperties(request, article);
return articleRepository.save(article);
}
// 更新文章
@Transactional
public Article update(Long id, ArticleRequest request) {
Article article = getById(id);
copyProperties(request, article);
return articleRepository.save(article);
}
// 删除文章
@Transactional
public void delete(Long id) {
if (!articleRepository.existsById(id)) {
throw new RuntimeException("文章不存在");
}
articleRepository.deleteById(id);
}
private void copyProperties(ArticleRequest request, Article article) {
article.setTitle(request.getTitle());
article.setContent(request.getContent());
article.setSummary(request.getSummary());
article.setAuthor(request.getAuthor());
}
}
设计要点:
@Service:注册为 Spring Bean,供 Controller 注入使用@RequiredArgsConstructor(Lombok):为final字段生成构造器,实现构造器注入(推荐方式)@Transactional:写操作(增删改)开启事务,出错时自动回滚copyProperties:将 DTO 字段复制到实体,创建时 new 新对象,更新时先查出再覆盖
7. controller/ArticleController.java --- API 接口层
Controller 负责接收 HTTP 请求、调用 Service、返回统一格式的 JSON。
java
@RestController
@RequestMapping("/api/articles")
@RequiredArgsConstructor
public class ArticleController {
private final ArticleService articleService;
@GetMapping
public ApiResponse<List<Article>> list() { ... }
@GetMapping("/{id}")
public ApiResponse<Article> detail(@PathVariable Long id) { ... }
@PostMapping
public ApiResponse<Article> create(@Valid @RequestBody ArticleRequest request) { ... }
@PutMapping("/{id}")
public ApiResponse<Article> update(@PathVariable Long id, @Valid @RequestBody ArticleRequest request) { ... }
@DeleteMapping("/{id}")
public ApiResponse<Void> delete(@PathVariable Long id) { ... }
}
完整 API 列表:
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/articles |
获取文章列表 |
| GET | /api/articles/{id} |
获取文章详情 |
| POST | /api/articles |
创建文章 |
| PUT | /api/articles/{id} |
更新文章 |
| DELETE | /api/articles/{id} |
删除文章 |
常用注解:
| 注解 | 作用 |
|---|---|
@RestController |
组合 @Controller + @ResponseBody,返回值直接序列化为 JSON |
@RequestMapping |
类级别路由前缀 |
@GetMapping / @PostMapping 等 |
映射 HTTP 方法 |
@PathVariable |
读取 URL 路径中的 {id} |
@RequestBody |
将请求体 JSON 反序列化为 Java 对象 |
@Valid |
触发 DTO 上的校验注解 |
8. common/ApiResponse.java --- 统一响应格式
所有接口返回相同结构,前端只需判断 code 字段即可:
json
{
"code": 200,
"message": "success",
"data": { ... }
}
java
@Data
public class ApiResponse<T> {
private int code;
private String message;
private T data;
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "success", data);
}
public static <T> ApiResponse<T> success(String message, T data) {
return new ApiResponse<>(200, message, data);
}
public static <T> ApiResponse<T> error(int code, String message) {
return new ApiResponse<>(code, message, null);
}
}
成功时 code = 200;失败时由异常处理器返回 code = 400 及具体错误信息。
9. exception/GlobalExceptionHandler.java --- 全局异常处理
java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResponse<Void> handleRuntimeException(RuntimeException e) {
return ApiResponse.error(400, e.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResponse<Void> handleValidationException(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining("; "));
return ApiResponse.error(400, message);
}
}
@RestControllerAdvice 拦截所有 Controller 抛出的异常,统一转换为 ApiResponse 格式,避免直接返回 Spring 默认的错误页面。
处理的异常类型:
RuntimeException:业务异常,如「文章不存在」MethodArgumentNotValidException:参数校验失败,如「标题不能为空」
10. config/CorsConfig.java --- 跨域配置
前后端分离开发时,前端 localhost:5173 访问后端 localhost:8080 属于跨域请求,需要配置 CORS:
java
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOriginPattern("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
允许所有来源、请求头、HTTP 方法,解决浏览器跨域拦截问题。生产环境建议将 * 改为具体的前端域名。
五、完整请求流程:以「创建文章」为例
用户在前端填写标题、作者、摘要、正文,点击保存。以下是请求在后端的完整流转过程:
sql
① 前端发送 POST /api/articles
Body: { "title": "...", "author": "...", "summary": "...", "content": "..." }
│
▼
② CorsConfig 放行跨域请求
│
▼
③ ArticleController.create()
- @RequestBody 将 JSON 转为 ArticleRequest 对象
- @Valid 触发字段校验(标题/内容/作者非空等)
- 校验失败 → GlobalExceptionHandler 返回 400
│
▼
④ ArticleService.create()
- new Article() 创建空实体
- copyProperties() 将 DTO 字段写入实体
- articleRepository.save() 持久化到数据库
- @Transactional 保证事务一致性
- Hibernate 自动填充 createdAt、updatedAt
│
▼
⑤ ArticleRepository.save()
- JPA 生成 INSERT SQL 写入 MySQL
│
▼
⑥ Controller 包装为 ApiResponse 返回
{ "code": 200, "message": "创建成功", "data": { "id": 1, "title": "...", ... } }
│
▼
⑦ 前端收到响应,跳转到文章详情页
查询列表 流程更简单:GET /api/articles → Controller → Service.listAll() → Repository.findAll() → 返回 List。
删除文章 流程:Controller 接收 DELETE /api/articles/{id} → Service 先 existsById 检查 → deleteById 删除 → 返回成功消息。
六、Maven 依赖说明(pom.xml)
xml
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
spring-boot-starter-web <!-- Web MVC、内嵌 Tomcat -->
spring-boot-starter-data-jpa <!-- JPA + Hibernate -->
spring-boot-starter-validation <!-- @Valid 参数校验 -->
mysql-connector-j <!-- MySQL 驱动 -->
lombok <!-- 简化代码 -->
spring-boot-starter-test <!-- 单元测试 -->
</dependencies>
七、前端简要说明
前端位于 frontend/ 目录,使用 Vue 3 + Vite 构建,主要页面:
| 页面 | 路由 | 功能 |
|---|---|---|
| 首页 | / |
展示最新文章 |
| 文章列表 | /articles |
列表展示、搜索 |
| 文章详情 | /articles/:id |
阅读文章 |
| 写文章/编辑 | /articles/new、/articles/:id/edit |
表单提交 |
src/api/article.js 封装了五个 API 方法,对应后端五个接口;src/api/request.js 配置 Axios 实例,统一处理 ApiResponse 的 code 判断和错误提示。
前端通过 Vite 代理将 /api 请求转发到 http://localhost:8080,开发时无需关心跨域问题。
八、总结
本项目的 Java 后端遵循 Controller → Service → Repository 分层设计,职责清晰:
- Entity 映射数据库表结构
- DTO 隔离外部输入,配合 Validation 做参数校验
- Repository 借助 JPA 简化数据访问
- Service 承载业务逻辑与事务
- Controller 暴露 REST API
- ApiResponse + GlobalExceptionHandler 统一响应与错误处理
- CorsConfig 解决前后端分离的跨域问题
这种结构是 Spring Boot 项目的标准实践,扩展新功能(如用户登录、评论、标签分类)时,只需按相同模式新增 Entity → Repository → Service → Controller 即可。