使用springboot2.6、vue2.6以及mysql从0开始搭建个人博客网页
从零开始搭建博客网站:使用 Spring Boot 2.6、Vue 2.6 和 MySQL
搭建一个简单的博客网站从零开始,需要覆盖数据库设计、后端 API 开发(Spring Boot 处理 CRUD 操作)和前端界面(Vue 处理用户交互)。这个教程基于一个典型的博客模型:文章(Post)包括标题、内容、发布状态和作者。我们将实现基本的 CRUD 功能(创建、读取、更新、删除文章),并添加搜索和列表显示。整个过程假设你有基本的 Java、JavaScript 和 SQL 知识。
前提环境准备:
- 安装 JDK 8/11/17(Spring Boot 2.6 兼容这些版本)。
- 安装 MySQL 5.7 或更高版本。
- 安装 Node.js 和 npm(用于 Vue)。
- IDE:IntelliJ IDEA 或 Eclipse(后端),VS Code(前端)。
- Maven(后端构建工具)。
预计时间:2-4 小时,视经验而定。
1. 数据库设计(MySQL)
首先,设计数据库 schema。我们使用一个简单的 posts
表来存储博客文章。
步骤:
-
启动 MySQL,创建一个数据库:
CREATE DATABASE blogdb; USE blogdb;
-
创建表
posts
:CREATE TABLE posts (id BIGINT AUTO_INCREMENT PRIMARY KEY,title VARCHAR(255) NOT NULL,content TEXT,published BOOLEAN DEFAULT FALSE,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
id
:文章唯一 ID,自增主键。title
:文章标题。content
:文章内容。published
:是否发布(布尔值)。created_at
:创建时间。
-
(可选)插入测试数据:
INSERT INTO posts (title, content, published) VALUES ('第一篇博客', '这是我的第一篇博客内容。', TRUE), ('Spring Boot 学习笔记', 'Spring Boot 是快速开发框架。', FALSE);
Spring Boot 将通过 JPA 自动管理 schema 更新,无需手动维护。
2. 后端开发(Spring Boot 2.6)
后端使用 Spring Boot 2.6 构建 RESTful API,支持文章的 CRUD 和按标题搜索。
步骤:
-
创建项目:
- 访问 Spring Initializr,选择:
- Project: Maven
- Language: Java
- Spring Boot: 2.6.x(最新 2.6 版本)
- Dependencies: Spring Web、Spring Data JPA、MySQL Driver
- 下载并导入 IDE,项目名为
blog-backend
。
- 访问 Spring Initializr,选择:
-
配置 pom.xml(添加依赖,如果 Initializr 未包含):
org.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starter-data-jpamysqlmysql-connector-javaruntime
-
配置 application.properties(src/main/resources/application.properties):
spring.datasource.url=jdbc:mysql://localhost:3306/blogdb?useSSL=false&serverTimezone=UTC spring.datasource.username=root spring.datasource.password=your_passwordspring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true
- 替换
your_password
为你的 MySQL 密码。 ddl-auto=update
:自动创建/更新表(生产环境改为validate
)。
- 替换
-
创建实体类(model 包下,新建 Post.java):
package com.example.blog.model;import javax.persistence.*;@Entity @Table(name = "posts") public class Post {@Id@GeneratedValue(strategy = GenerationType.AUTO)private Long id;@Column(name = "title")private String title;@Column(name = "content")private String content;@Column(name = "published")private boolean published;// 无参构造函数public Post() {}// Getters 和 Setters(使用 Lombok 或手动添加)public Long getId() { return id; }public void setId(Long id) { this.id = id; }public String getTitle() { return title; }public void setTitle(String title) { this.title = title; }public String getContent() { return content; }public void setContent(String content) { this.content = content; }public boolean isPublished() { return published; }public void setPublished(boolean published) { this.published = published; } }
-
创建仓库接口(repository 包下,新建 PostRepository.java):
package com.example.blog.repository;import com.example.blog.model.Post; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List;public interface PostRepository extends JpaRepository {List findByPublished(boolean published);List findByTitleContaining(String title); }
- 继承
JpaRepository
自动获得 CRUD 方法。 - 自定义方法:按发布状态或标题搜索。
- 继承
-
创建控制器(controller 包下,新建 PostController.java):
package com.example.blog.controller;import com.example.blog.model.Post; import com.example.blog.repository.PostRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*;import java.util.List; import java.util.Optional;@CrossOrigin(origins = "http://localhost:8081") // 允许 Vue 前端跨域 @RestController @RequestMapping("/api") public class PostController {@Autowiredprivate PostRepository postRepository;@GetMapping("/posts")public ResponseEntity> getAllPosts(@RequestParam(required = false) String title) {try {List posts;if (title == null)posts = postRepository.findAll();elseposts = postRepository.findByTitleContaining(title);if (posts.isEmpty())return new ResponseEntity<>(HttpStatus.NO_CONTENT);return new ResponseEntity<>(posts, HttpStatus.OK);} catch (Exception e) {return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);}}@GetMapping("/posts/{id}")public ResponseEntity getPostById(@PathVariable Long id) {Optional post = postRepository.findById(id);return post.map(value -> new ResponseEntity<>(value, HttpStatus.OK)).orElseGet(() -> new ResponseEntity<>(HttpStatus.NOT_FOUND));}@PostMapping("/posts")public ResponseEntity createPost(@RequestBody Post post) {try {Post _post = postRepository.save(new Post(post.getTitle(), post.getContent(), post.isPublished()));return new ResponseEntity<>(_post, HttpStatus.CREATED);} catch (Exception e) {return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);}}@PutMapping("/posts/{id}")public ResponseEntity updatePost(@PathVariable Long id, @RequestBody Post post) {Optional existingPost = postRepository.findById(id);if (existingPost.isPresent()) {Post _post = existingPost.get();_post.setTitle(post.getTitle());_post.setContent(post.getContent());_post.setPublished(post.isPublished());return new ResponseEntity<>(postRepository.save(_post), HttpStatus.OK);} else {return new ResponseEntity<>(HttpStatus.NOT_FOUND);}}@DeleteMapping("/posts/{id}")public ResponseEntity deletePost(@PathVariable Long id) {try {postRepository.deleteById(id);return new ResponseEntity<>(HttpStatus.NO_CONTENT);} catch (Exception e) {return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);}}@DeleteMapping("/posts")public ResponseEntity deleteAllPosts() {try {postRepository.deleteAll();return new ResponseEntity<>(HttpStatus.NO_CONTENT);} catch (Exception e) {return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);}}@GetMapping("/posts/published")public ResponseEntity> findByPublished() {try {List posts = postRepository.findByPublished(true);if (posts.isEmpty())return new ResponseEntity<>(HttpStatus.NO_CONTENT);return new ResponseEntity<>(posts, HttpStatus.OK);} catch (Exception e) {return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);}} }
- API 端点:
/api/posts
(GET 列表/搜索),/api/posts/{id}
(GET/ PUT/ DELETE),/api/posts
(POST 创建),/api/posts/published
(GET 已发布)。
- API 端点:
-
运行后端:
- 在 IDE 中运行
BlogBackendApplication
主类,或命令行mvn spring-boot:run
。 - 测试:浏览器访问
http://localhost:8080/api/posts
,应返回 JSON 列表(如果有数据)。
- 在 IDE 中运行
3. 前端开发(Vue 2.6)
前端使用 Vue 2.6 构建单页应用(SPA),包括文章列表、详情/编辑页和添加页。使用 Axios 调用后端 API。
步骤:
-
创建项目:
- 命令行运行
vue create blog-frontend
(选择默认配置:Vue 2.x)。 - 进入目录:
cd blog-frontend
,安装依赖:npm install vue-router@3 axios bootstrap
。
- 命令行运行
-
配置路由(src/router/index.js):
import Vue from 'vue'; import VueRouter from 'vue-router'; import PostsList from '../components/PostsList.vue'; import Post from '../components/Post.vue'; import AddPost from '../components/AddPost.vue';Vue.use(VueRouter);export default new VueRouter({mode: 'history',routes: [{ path: '/', name: 'posts', component: PostsList },{ path: '/posts/:id', name: 'post-details', component: Post },{ path: '/add', name: 'add', component: AddPost }] });
-
主应用配置(src/main.js):
import Vue from 'vue'; import App from './App.vue'; import router from './router'; import 'bootstrap/dist/css/bootstrap.min.css';Vue.config.productionTip = false; new Vue({router,render: h => h(App), }).$mount('#app');
-
配置 Axios(src/services/PostDataService.js):
import http from '../http-common.js';class PostDataService {getAll() {return http.get('/posts');}get(id) {return http.get(`/posts/${id}`);}create(data) {return http.post('/posts', data);}update(id, data) {return http.put(`/posts/${id}`, data);}delete(id) {return http.delete(`/posts/${id}`);}deleteAll() {return http.delete('/posts');}findByTitle(title) {return http.get(`/posts?title=${title}`);} }export default new PostDataService();
http-common.js(src/http-common.js):
import axios from 'axios';export default axios.create({baseURL: 'http://localhost:8080/api',headers: {'Content-Type': 'application/json'} });
-
导航栏(src/App.vue):
我的博客文章列表添加文章
-
文章列表组件(src/components/PostsList.vue):
搜索全部无文章{{ post.title }} - {{ post.content.substring(0, 50) }}...{{ post.published ? '已发布' : '草稿' }}编辑删除import PostDataService from '../services/PostDataService'; import { useRouter } from 'vue-router'; // Vue 2.6 使用 this.$routerexport default {data() {return { posts: [], title: '' };},methods: {retrievePosts() {PostDataService.getAll().then(response => { this.posts = response.data; });},searchTitle() {PostDataService.findByTitle(this.title).then(response => { this.posts = response.data; });},getPost(id) { this.$router.push(`/posts/${id}`); },removePost(id) {PostDataService.delete(id).then(() => { this.retrievePosts(); });}},mounted() { this.retrievePosts(); } };
-
添加文章组件(src/components/AddPost.vue)和详情/编辑组件(src/components/Post.vue):类似列表组件,使用表单(v-model 绑定 title/content)和服务方法(create/update)。完整代码可参考类似 CRUD 模板,添加表单验证。
-
运行前端:
npm run serve
,访问http://localhost:8081
。
4. 集成、测试与部署
- 集成:后端运行在 8080 端口,前端 8081 端口。确保 CORS 配置正确(@CrossOrigin)。
- 测试:
- 添加文章:导航到 /add,提交表单。
- 列表:首页显示文章,支持搜索和删除。
- 编辑:点击编辑跳转到详情页,更新后返回列表。
- 已发布:
/api/posts/published
只显示 published=true 的文章。
- 常见问题:
- 跨域错误:检查 @CrossOrigin。
- 数据库连接失败:验证 properties 配置。
- Vue 版本:确保使用 Vue 2.6(package.json 中 “vue”: “^2.6.0”)。
- 部署(简单方式):后端打包 JAR (
mvn package
) 部署到服务器;前端构建npm run build
,静态文件上传到 Nginx 或 Vercel。使用 Docker 容器化。
这个教程提供了一个最小 viable 博客。如果你需要添加用户认证(Spring Security)或富文本编辑器(Vue-Quill-Editor),可以扩展。参考完整 CRUD 示例进行调整。
10