news 2026/4/23 15:26:02

手把手教你用 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/23 13:58:15

【课程设计/毕业设计】机器学习基于CNN卷积神经网络对辣椒类别识别

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/4/22 15:46:28

基于微信小程序的旅游服务小助手系统(毕设源码+文档)

课题说明随着文旅产业数字化升级&#xff0c;大众旅游需求愈发多元化、个性化&#xff0c;而传统旅游服务存在信息获取分散、行程规划繁琐、本地服务对接不畅、应急响应滞后等问题&#xff0c;难以适配游客碎片化、即时性的出行需求。本课题聚焦这一痛点&#xff0c;设计并构建…

作者头像 李华
网站建设 2026/4/17 21:08:13

基于微信小程序的某乡村文旅系统(毕设源码+文档)

课题说明随着乡村振兴战略推进&#xff0c;乡村文旅成为文旅产业新增长点&#xff0c;但当前乡村文旅服务普遍存在信息传播滞后、特色资源展示不足、游玩体验单一、农文旅资源联动不畅等问题&#xff0c;难以适配游客深度体验乡村风貌的需求&#xff0c;也制约了乡村文旅产业的…

作者头像 李华
网站建设 2026/4/23 13:54:36

基于微信小程序的琴房预约系统(毕设源码+文档)

课题说明随着素质教育普及&#xff0c;学琴人群持续扩大&#xff0c;高校、艺术培训机构等场所的琴房资源愈发紧张。传统琴房预约模式普遍存在流程繁琐&#xff08;如线下登记、电话预约&#xff09;、档期信息不透明、预约记录难查询、资源分配不均、管理统计低效等问题&#…

作者头像 李华
网站建设 2026/4/23 13:23:20

深度测评10个AI论文平台,专科生毕业论文必备!

深度测评10个AI论文平台&#xff0c;专科生毕业论文必备&#xff01; AI 工具如何改变论文写作的未来 在如今这个信息爆炸的时代&#xff0c;专科生们面对毕业论文的压力愈发明显。无论是选题、撰写还是降重&#xff0c;每一个环节都可能成为困扰学生的难题。而 AI 工具的出现&…

作者头像 李华