一个基于 MySQL + Docker + Vue + Spring Boot 的前后端分离博客系统
本项目是一个博客管理系统,支持管理员登录、文章管理,以及基于 Markdown 的内容编辑器。它通过前后端分离架构实现高效开发与易用性。
- 访客阅读和查找文章
- 基于 JWT 的管理员登录认证与权限管理的增删改
- Markdown 编辑器支持
- 标签和分类管理
- Docker 支持,便于部署
- Vue 3
- Element Plus
- Spring Boot
- MySQL
- Docker & Docker Compose
- Maven
.
├── blog-frontend/ # 前端代码
│ ├── public/ # 静态资源
│ ├── src/ # 前端源代码
│ │ ├── components/ # 公共组件
│ │ ├── views/ # 页面组件
│ │ ├── router/ # 路由配置
│ │ ├── services/ # API 接口
│ └── vue.config.js # 配置文件
├── src/ # 后端代码
│ ├── main/java/ # Java 源代码
│ ├── main/resources/ # 配置和资源文件
│ └── test/ # 测试代码
├── docker-compose.yml # Docker 编排文件
├── pom.xml # Maven 配置
├── demo.env # 示例环境变量(需重命名为 .env)
└── README.md # 项目说明
-
克隆前后端镜像:
docker pull muchengzju/blog-frontend:latest docker pull muchengzju/blog-backend:latest
-
克隆本仓库内的
docker-compose.yml
和demo.env
文件:curl -O https://raw.githubusercontent.com/MuChengZJU/MDVSBlog/main/docker-compose.yml curl -O https://raw.githubusercontent.com/MuChengZJU/MDVSBlog/main/demo.env
-
复制
demo.env
文件为.env
文件,并根据需要修改其中的环境变量,在文件中有对应说明。 -
运行 Docker 容器:
docker-compose up
项目会根据
docker-compose.yml
文件中的配置启动前端、后端和数据库容器。- 前端:
http://localhost:9091
- 后端 API:
http://localhost:9090/api
- 前端:
找一个合适的地方,悄悄地把项目克隆到本地:
git clone https://github.com/MuChengZJU/MDVSBlog.git
或者用你喜欢的方式把代码扒下来 :)
注意:
由于能力有限,项目目前跑 Docker 流程最顺,因为环境变量配置在 .env
文件中,而这造成 Maven 无法直接读取,所以我禁用了检查,当前代码跑得起来。如果你在在本地运行,可能需要手动修改 application.properties
文件中的数据库连接信息。
-
在
Blog/blog-fronted
前端目录安装依赖:cd blog-frontend npm install
-
运行开发服务器:
npm run serve
-
配置数据库(MySQL):
- 在
src/main/resources/application.properties
中修改数据库连接信息。 - 初始化数据库
- 设置root密码
- 创建数据库:
CREATE DATABASE db_blog;
- ( 可选 ) 创建专用的数据库用户
- 在
-
启动后端服务:
如果你在
IDEA
中运行后端,可以直接运行BlogApplication
类。或者在
Blog
根目录运行:mvn spring-boot:run
-
在
Blog/blog-frontend
前端目录打包:npm run build
会在
dist
目录下生成前端静态文件。 -
在
Blog
根目录打包后端:mvn clean package
会在
target
目录下生成后端 jar 包。 -
在你的服务器上部署后端 jar 包和前端静态文件,方法自选,例如
- 使用
nohup
后台运行 jar 包 - 使用
nginx
部署前端静态文件
- 使用
-
配置环境变量:
- 复制
demo.env
文件为.env
文件,并根据需要修改其中的环境变量,在文件中有对应说明。 - 这些环境变量会在 Docker 容器中使用。 有如下的环境变量:
SPRING_DATASOURCE_URL=jdbc:mysql://db:3306/blog_db # 数据库连接 SPRING_DATASOURCE_USERNAME=EDITABLE_USERNAME SPRING_DATASOURCE_PASSWORD=EDITABLE_PASSWORD SERVER_PORT=9090 # 后端端口 JWT_SECRET=EDITABLE_SECRET # 需要符合base64编码规则 ADMIN_USERNAME=EDITABLE_USERNAME ADMIN_PASSWORD=EDITABLE_PASSWORD # MySQL 环境变量 MYSQL_ROOT_PASSWORD=EDITABLE_PASSWORD # 初始化数据库密码 MYSQL_DATABASE=EDITABLE_DATABASE # 初始化数据库名称
- 复制
-
在
Blog
根目录构建并运行容器:docker-compose up --build
项目会根据
docker-compose.yml
文件中的配置启动前端、后端和数据库容器。- 前端:
Blog\blog-frontend\Dockerfile
- 后端:
Blog\Dockerfile
- 数据库:
mysql:8
,在服务器上会自己创建容器。- 注意可能遇到 Docker 拉取镜像慢的问题,可以使用一些魔法,以及先手动拉取镜像。
- 前端:
- 前端:`http://localhost:9091`
- 后端 API:`http://localhost:9090/api`
-
首页
- 首页有按照时间线排列的所有文章。
- 顶栏有搜索框,可以根据关键字搜索文章。
- 右上角有可自定义的头像和个人链接。
- 点击标签可以查看对应标签的文章。
-
文章内容
点击
查看详情
可以进入查看完整的文章内容。(它甚至可以加载图片,此图是从 98 的图床加载过来的,但是不经过博客服务器,也就是用户前端从 98 读取的,所以需要在可连接图床的环境下才能看到,也就是连接校网才能看到 98 图床里的图片) -
管理员登录
点击右上角的
管理员登录
按钮,输入管理员账号密码即可登录。管理员账号密码通过配置文件中设置,通过 Docker 部署则通过.env -> docker-compose.yml -> application.properties -> 后端程序
在后端初始化且无法改变(是的这很不优雅 QAQ)。登录后,请刷新网页,这样可以看到文章管理页面和发布文章的选项,可以对文章进行增删改查。
-
文章管理
- 点击
文章管理
可以查看所有文章,可以对文章进行增删改查。 - 点击修改会跳转到文章编辑页面,可以修改文章内容。
- 点击删除会删除文章,且有弹窗提示。
- 点击
-
文章编辑和发布
- 点击
发布文章
可以进入文章编辑页面,支持 Markdown 编辑。
- 点击
- 页面导航:
- 通过
index.js
定义的路由,支持页面间的跳转,如首页、文章详情、管理员登录、文章发布与管理。
- 通过
- 动态设置网页标题:
- 在
main.js
中,通过router.beforeEach
动态设置页面标题。
- 在
- 用户认证:
- 在
api.js
中,拦截器会检测本地存储的token
,并在未授权时重定向至登录页面。
- 在
- 文章管理:
- 提供文章的查看、发布和管理功能,由多个页面组件配合实现。
-
动态标题设置(
main.js
):router.beforeEach((to, from, next) => { document.title = "MuCheng's Secret Base " + to.meta.title || "MuCheng's Secret Base"; // 默认标题 next(); });
-
路由配置(
index.js
):const routes = [ { path: '/', component: ArticleList, meta: { title: '首页' } }, { path: '/article/:id', component: ArticleView, meta: { title: '文章' } }, { path: '/login', component: AdminLogin, meta: { title: '管理员登录' } }, { path: '/publish', component: ArticleForm, meta: { title: '发布文章' } }, { path: '/manage', component: ArticleManagement, meta: { title: '管理文章' } }, ];
-
API 请求拦截器(
api.js
):apiClient.interceptors.request.use((config) => { const token = localStorage.getItem('token'); if (token) { config.headers['Authorization'] = `Bearer ${token}`; } return config; }); apiClient.interceptors.response.use( (response) => response, (error) => { if (error.response && error.response.status === 401) { localStorage.removeItem('token'); window.location.href = '/login'; // 重定向到登录页面 } return Promise.reject(error); } );
- 命名规范:
- 文件名使用 PascalCase(如
ArticleForm.vue
)。 - 变量和函数名采用 camelCase(如
apiClient
)。
- 文件名使用 PascalCase(如
- 接口约定:
- 基础 URL 通过动态协议和主机名拼接:
${protocol}//${hostname}:${port}/api
。 - 请求头默认为
Content-Type: application/json
。
- 基础 URL 通过动态协议和主机名拼接:
- 组件开发:
- 每个页面组件负责单一功能,模块化设计,便于维护。
- 通过
props
和emits
实现组件间的数据流动。
- 错误处理:
- 未授权时清除本地存储的
token
,并重定向到登录页面。
- 未授权时清除本地存储的
项目的后端代码主要基于 Spring Boot 和 Spring Data JPA 框架开发,目录结构如下:
-
配置类:
CorsConfig
:配置跨域访问规则。SecurityConfig
:配置安全规则、JWT 认证和角色权限管理。- 数据库配置文件:
application.properties
,包括数据库连接和 JWT 密钥。
-
入口类:
BlogApplication
:Spring Boot 应用的主入口。
-
控制器:
ArticleController
:文章管理接口,包括增删改查功能。AuthController
:用户登录认证接口。TagController
:标签管理接口。
-
实体类:
Article
:文章实体,包含分类和标签关联。Category
:分类实体。Tag
:标签实体。User
:用户实体,用于认证与授权。
-
数据访问层:
ArticleRepository
:文章数据操作接口。TagRepository
:标签数据操作接口。UserRepository
:用户数据操作接口,支持通过用户名查询用户信息。
-
文章管理:
- 查询所有文章。
- 根据 ID 查询文章详情。
- 创建新文章、更新或删除现有文章。
-
用户认证:
- 用户登录,返回 JWT Token。
-
标签管理:
- 查询所有标签。
- 创建新标签。
-
安全与跨域:
- 使用 Spring Security 实现基于 JWT 的认证和权限管理。
- 支持角色权限分离,例如管理员可执行增删改操作,普通用户仅可查询。
- 配置跨域访问规则,允许特定来源访问。
-
RESTful API 的使用
- 功能:设计清晰的接口,支持文章、标签和用户的增删改查。
- 核心代码(以文章操作为例):
@GetMapping("/{id}") public Article getArticleById(@PathVariable Long id) { return articleService.findById(id); } @PostMapping public Article createArticle(@RequestBody Article article) { return articleService.save(article); } @DeleteMapping("/{id}") public void deleteArticle(@PathVariable Long id) { articleService.deleteById(id); }
GET /api/articles/{id}
获取文章详情。POST /api/articles
创建文章,接收 JSON 数据。DELETE /api/articles/{id}
删除指定文章。- 数据操作通过
ArticleService
调用ArticleRepository
实现。
-
JWT 认证与授权
- 功能:使用 JWT(JSON Web Token)实现用户认证与授权。
- 核心代码:
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeHttpRequests(authorize -> authorize .requestMatchers("/api/auth/login").permitAll() .requestMatchers("/api/articles").permitAll() .requestMatchers("/api/articles/**").hasRole("ADMIN") .anyRequest().authenticated() ) .addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); }
- 禁用 CSRF,采用无状态会话(Stateless),通过 JWT 实现用户认证。
- 配置权限:
GET /api/articles
对所有用户开放。POST /api/articles
仅限管理员角色访问。
JwtTokenFilter
验证请求中的 JWT,确保访问安全。
-
跨域配置
- 功能:支持前端跨域访问,确保前后端分离环境下的正常通信。
- 核心代码:
@Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("http://*:9091") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowCredentials(true); }
- 匹配所有 API 路径(
/**
)。 - 允许来自
http://*:9091
的请求,支持动态来源。 - 支持跨域的 Cookie 传递(如 JWT 认证场景)。
- 匹配所有 API 路径(
-
用户认证与存储:
- 用户通过
AuthController
提交用户名和密码登录。 - 在服务层中调用
UserRepository
,根据用户名查询用户数据,并校验密码。 - 登录成功后生成并返回 JWT Token。
- 用户通过
-
数据操作:
- 使用 Spring Data JPA 的 Repository 接口实现数据操作,简化了 CRUD 的实现。
- 例如,
ArticleRepository
和TagRepository
提供文章与标签的数据库访问。
-
动态权限控制:
SecurityConfig
配置基于路径的权限控制,例如/api/articles/**
仅管理员可访问增删改功能,而/api/articles
对所有用户开放。
-
RESTful API:
- 路由设计符合 RESTful 风格。
- 动词与 HTTP 方法匹配:GET(查询)、POST(创建)、PUT(更新)、DELETE(删除)。
-
实体与数据库表的映射:
- 使用
@Entity
和@Table
注解,明确实体与数据库表的对应关系。 - 字段设计规范,例如
User
的用户名字段必须唯一。
- 使用
-
接口返回格式:
- 认证接口返回 JSON 格式:
{"token": "xxx"}
。 - 数据查询接口返回实体列表或单个对象。
- 认证接口返回 JSON 格式:
-
代码风格:
- 遵循 Java 命名规范,方法命名具有描述性。
- 提供适当的注释,便于代码阅读与维护。
由于之前把敏感信息放在了代码里,所以之前的仓库不公开了,把环境变量放在了 .env
文件里之后新开了这个仓库并公开。
- 文章的网友评论
- 照片墙
- 个人简历页面
- 环境变量配置优化,支持更多配置项
- 优化前端的分辨率适配
- 单元测试和集成测试。
- 完善代码注释
- 简单自定义配置说明
- 配置 CI/CD 流程。
Legend:
- 创建了未来计划
欢迎贡献代码或提出建议!
- Fork 仓库
- 创建新分支:
git checkout -b feature/your-feature
- 提交代码并创建 PR。
本项目采用 MIT License。