news 2026/5/1 8:12:59

手把手教你用 Spring Boot + Vue 搭建个人博客系统(后台管理篇)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手把手教你用 Spring Boot + Vue 搭建个人博客系统(后台管理篇)

视频看了几百小时还迷糊?关注我,几分钟让你秒懂!


在前三篇中,我们已经完成了:

  • 后端 API(Spring Boot)
  • 前台展示(Vue 3)
  • 全栈整合与部署(Nginx + HTTPS)

但作为一个真正的“个人博客”,你肯定不希望每次发文章都要手动插数据库!
现在,是时候打造一个专属后台管理系统——让你能登录、写文章、管理分类,像 WordPress 一样丝滑操作!

本篇将手把手教你实现:用户登录 + JWT 鉴权 + 富文本编辑 + 文章管理,真正掌控你的博客内容。


一、为什么需要后台管理?

场景痛点解决方案
想发新文章要连数据库写 SQL后台表单提交
修改错别字无法在线编辑富文本编辑器
分类混乱手动改 category_id分类下拉选择
安全风险任何人都能删文章登录 + 权限控制

💡 目标:只有你自己能进后台,其他人只能看前台!


二、功能需求

  1. 管理员登录/登出
  2. 文章列表页(带分页、搜索、删除)
  3. 新增/编辑文章(支持 Markdown 或富文本)
  4. 分类管理(增删改查)
  5. JWT Token 鉴权(保护所有后台接口)

三、技术选型升级

模块技术
后端鉴权Spring Security + JWT
前端 UIElement Plus(Vue 3 官方合作组件库)
富文本@wangeditor/editor-for-vue(轻量、国产、好用)
表单验证VeeValidate 或手动校验

四、后端改造:添加用户与鉴权

1. 新增管理员表(简单版,仅1个账号)

CREATE TABLE blog_admin ( id BIGINT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, -- 存储 BCrypt 加密后的密码 create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 插入初始管理员(密码:123456,加密后) INSERT INTO blog_admin (username, password) VALUES ('admin', '$2a$10$DdFvR7sZ9KzXqQlLbUeBFeuT8rWJvOyYkGzVxHmIjKlMnOpQrStUv');

🔐 密码生成方式(Java 测试类):

System.out.println(new BCryptPasswordEncoder().encode("123456"));

2. 添加 JWT 工具类

// src/main/java/com/example/blog/util/JwtUtil.java @Component public class JwtUtil { private String secret = "myBlogSecretKey2026"; // 实际项目建议从配置读取 private long expire = 24 * 60 * 60 * 1000; // 24小时 public String generateToken(String username) { return Jwts.builder() .setSubject(username) .setExpiration(new Date(System.currentTimeMillis() + expire)) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } public String getUsernameFromToken(String token) { return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getSubject(); } public boolean validateToken(String token) { try { Jwts.parser().setSigningKey(secret).parseClaimsJws(token); return true; } catch (Exception e) { return false; } } }

⚠️ 依赖:需引入io.jsonwebtoken:jjwt


3. 登录接口

@RestController @RequestMapping("/api/admin") public class AdminController { @Autowired private AdminService adminService; @PostMapping("/login") public ResponseEntity<Map<String, Object>> login(@RequestBody Map<String, String> credentials) { String username = credentials.get("username"); String password = credentials.get("password"); if (adminService.authenticate(username, password)) { String token = adminService.generateToken(username); Map<String, Object> resp = new HashMap<>(); resp.put("token", token); resp.put("message", "登录成功"); return ResponseEntity.ok(resp); } return ResponseEntity.status(401).body(Map.of("error", "用户名或密码错误")); } }

4. 鉴权拦截器(保护后台接口)

@Component public class AuthInterceptor implements HandlerInterceptor { @Autowired private JwtUtil jwtUtil; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String authHeader = request.getHeader("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { String token = authHeader.substring(7); if (jwtUtil.validateToken(token)) { return true; } } response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return false; } } // 注册拦截器 @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new AuthInterceptor()) .addPathPatterns("/api/admin/**") // 拦截所有后台接口 .excludePathPatterns("/api/admin/login"); } }

✅ 所有/api/admin/**接口(除登录外)都需携带Authorization: Bearer <token>


五、前端改造:后台管理页面

1. 安装依赖

npm install element-plus @wangeditor/editor @wangeditor/editor-for-vue axios

2. 创建后台布局(AdminLayout.vue)

<!-- src/views/admin/AdminLayout.vue --> <template> <el-container style="height: 100vh"> <el-aside width="200px" style="background: #2c3e50; color: white;"> <div style="padding: 20px; font-size: 18px;">博客后台</div> <el-menu background-color="#2c3e50" text-color="#fff" active-text-color="#ffd04b" router > <el-menu-item index="/admin/posts">文章管理</el-menu-item> <el-menu-item index="/admin/categories">分类管理</el-menu-item> <el-menu-item index="/admin/logout" @click="logout">退出登录</el-menu-item> </el-menu> </el-aside> <el-main> <router-view /> </el-main> </el-container> </template> <script setup> import { useRouter } from 'vue-router'; const router = useRouter(); function logout() { localStorage.removeItem('admin_token'); router.push('/admin/login'); } </script>

3. 登录页(AdminLogin.vue)

<template> <div class="login-container"> <el-card style="width: 400px;"> <h2>管理员登录</h2> <el-form @submit.prevent="handleLogin"> <el-form-item> <el-input v-model="form.username" placeholder="用户名" /> </el-form-item> <el-form-item> <el-input v-model="form.password" type="password" placeholder="密码" /> </el-form-item> <el-button type="primary" native-type="submit" :loading="loading">登录</el-button> </el-form> </el-card> </div> </template> <script setup> import { ref } from 'vue'; import { useRouter } from 'vue-router'; import api from '@/api'; const form = ref({ username: 'admin', password: '' }); const loading = ref(false); const router = useRouter(); async function handleLogin() { loading.value = true; try { const res = await api.post('/admin/login', form.value); localStorage.setItem('admin_token', res.data.token); router.push('/admin/posts'); } catch (err) { ElMessage.error('登录失败'); } finally { loading.value = false; } } </script> <style scoped> .login-container { display: flex; justify-content: center; align-items: center; height: 100vh; background: #f5f5f5; } </style>

4. 请求拦截器:自动携带 Token

// src/api/index.js(补充) api.interceptors.request.use(config => { const token = localStorage.getItem('admin_token'); if (token && config.url?.startsWith('/admin')) { config.headers.Authorization = `Bearer ${token}`; } return config; });

5. 文章编辑页(使用 WangEditor)

<template> <el-card> <el-form :model="post" label-width="80px"> <el-form-item label="标题"> <el-input v-model="post.title" /> </el-form-item> <el-form-item label="分类"> <el-select v-model="post.categoryId" placeholder="请选择"> <el-option v-for="cat in categories" :key="cat.id" :label="cat.name" :value="cat.id" /> </el-select> </el-form-item> <el-form-item label="内容"> <div style="border: 1px solid #ccc;"> <Toolbar style="border-bottom: 1px solid #ccc" :editor="editorRef" :defaultConfig="toolbarConfig" :mode="mode" /> <Editor v-model="post.content" :defaultConfig="editorConfig" :mode="mode" @onCreated="handleCreated" /> </div> </el-form-item> <el-button type="primary" @click="savePost">保存</el-button> </el-form> </el-card> </template> <script setup> import { onMounted, ref, shallowRef } from 'vue'; import { Editor, Toolbar } from '@wangeditor/editor-for-vue'; import api from '@/api'; const props = defineProps({ id: [String, Number] }); const post = ref({ title: '', content: '', categoryId: null }); const categories = ref([]); const editorRef = shallowRef(); const mode = 'default'; const toolbarConfig = {}; const editorConfig = { placeholder: '请输入内容...' }; onMounted(async () => { // 加载分类 const catRes = await api.get('/categories'); // 假设你已提供分类接口 categories.value = catRes.data; // 如果是编辑,加载文章 if (props.id) { const res = await api.get(`/admin/posts/${props.id}`); post.value = res.data; } }); function handleCreated(editor) { editorRef.value = editor; } async function savePost() { if (props.id) { await api.put(`/admin/posts/${props.id}`, post.value); } else { await api.post('/admin/posts', post.value); } ElMessage.success('保存成功'); } </script>

📌 注意:你需要在后端新增/api/admin/posts的增删改接口,并加上@PreAuthorize("hasRole('ADMIN')")或拦截器保护。


六、反例 & 注意事项

❌ 反例:Token 存在 Cookie 且未设 HttpOnly

  • 容易被 XSS 窃取。
  • ✅ 正确做法:存 localStorage + HTTPS + 短期 Token

❌ 反例:富文本内容直接v-html渲染(前台)

  • 若内容来自后台(可信),可接受;
  • 若未来开放评论,则必须过滤(如DOMPurify.sanitize())。

⚠️ 注意事项

  1. 密码安全:永远不要明文存储,用 BCrypt;
  2. Token 刷新:本例 Token 24 小时过期,简单场景够用;
  3. 权限粒度:目前只有一个管理员,无需复杂 RBAC;
  4. CSRF:因使用 Token 而非 Cookie,天然免疫 CSRF。

七、最终效果

  • 访问https://your-domain.com/admin/login
  • 输入账号密码 → 进入后台
  • 点击“新建文章” → 使用富文本编辑器写作
  • 保存后,前台首页立即更新!

视频看了几百小时还迷糊?关注我,几分钟让你秒懂!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/30 17:42:13

【Dify access_token 配置全攻略】:从零掌握高效安全的Token管理策略

第一章&#xff1a;Dify access_token 配置概述在使用 Dify 平台进行应用开发与集成时&#xff0c;access_token 是实现身份验证和接口调用权限控制的核心凭证。该令牌用于标识调用方身份&#xff0c;确保 API 请求的安全性和合法性。正确配置 access_token 不仅能保障系统间通…

作者头像 李华
网站建设 2026/5/1 6:58:10

揭秘Dify附件ID异常问题:3步快速定位与修复方案

第一章&#xff1a;Dify附件ID异常问题概述在使用 Dify 平台进行应用开发与集成时&#xff0c;部分用户反馈在处理文件上传与附件调用过程中出现“附件ID异常”的错误。该问题通常表现为系统无法正确识别或解析由平台生成的附件唯一标识符&#xff08;Attachment ID&#xff09…

作者头像 李华
网站建设 2026/5/1 6:59:25

基于微信小程序的农产品销售系统(毕设源码+文档)

课题说明 随着乡村振兴战略推进与数字农业发展&#xff0c;农产品上行成为拓宽销售渠道、助力农户增收的关键路径&#xff0c;但当前农产品销售普遍存在渠道单一、品牌曝光不足、供需信息不对称、流通环节繁琐、品质溯源难等问题&#xff0c;难以适配消费者对优质农产品的便捷采…

作者头像 李华
网站建设 2026/5/1 1:41:28

工业级交流电流采集模块:高精度多路独立采样,稳定无忧

交流电流监测采集模块是一种用于测量、监控和采集交流电路中电流数据的电子设备。通常作为工业自动化、能源管理、智能电网、设备状态监测等系统中的关键组件。一、规格分类&#xff1a; 4路AC0-10A输入量程-485通讯方式 4路AC0-50A输入量程-485通讯方式 4路AC0-100A输入量程-4…

作者头像 李华
网站建设 2026/5/1 6:58:12

基于微信小程序的电子数据取证知识测试系统毕设源码+文档+讲解视频

前言 随着数字经济快速发展&#xff0c;电子数据取证作为司法实践与网络安全领域的核心环节&#xff0c;对专业人才的知识储备与实操能力提出了更高要求。当前电子数据取证知识学习存在测试场景单一、学习便捷性不足、成果反馈不及时等问题&#xff0c;难以满足学习者碎片化学习…

作者头像 李华
网站建设 2026/5/1 5:01:29

docker快速部署nginx

前言记录docker部署nginx部署nginx#创建挂载目录 sudo mkdir -p /zero/nginx/{html,conf.d,log} #分配权限 sudo chmod -R 755 /zero/nginx #启动一个临时nginx docker run -d -p 80:80 --name nginx nginx:1.24.0 # 从容器复制配置文件到新的挂载目录 docker cp nginx:/etc…

作者头像 李华