2026/2/3 8:16:41
网站建设
项目流程
营销型网站一般有哪些内容,做电商网站注意什么问题,三合一网站管理系统怎么做的,营销型公司网站视频看了几百小时还迷糊#xff1f;关注我#xff0c;几分钟让你秒懂#xff01;在前三篇中#xff0c;我们已经完成了#xff1a;✅ 后端 API#xff08;Spring Boot#xff09;✅ 前台展示#xff08;Vue 3#xff09;✅ 全栈整合与部署#xff08;Nginx HTTPS关注我几分钟让你秒懂在前三篇中我们已经完成了✅后端 APISpring Boot✅前台展示Vue 3✅全栈整合与部署Nginx HTTPS但作为一个真正的“个人博客”你肯定不希望每次发文章都要手动插数据库现在是时候打造一个专属后台管理系统——让你能登录、写文章、管理分类像 WordPress 一样丝滑操作本篇将手把手教你实现用户登录 JWT 鉴权 富文本编辑 文章管理真正掌控你的博客内容。一、为什么需要后台管理场景痛点解决方案想发新文章要连数据库写 SQL后台表单提交修改错别字无法在线编辑富文本编辑器分类混乱手动改 category_id分类下拉选择安全风险任何人都能删文章登录 权限控制 目标只有你自己能进后台其他人只能看前台二、功能需求管理员登录/登出文章列表页带分页、搜索、删除新增/编辑文章支持 Markdown 或富文本分类管理增删改查JWT Token 鉴权保护所有后台接口三、技术选型升级模块技术后端鉴权Spring Security JWT前端 UIElement PlusVue 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:jjwt3. 登录接口RestController RequestMapping(/api/admin) public class AdminController { Autowired private AdminService adminService; PostMapping(/login) public ResponseEntityMapString, Object login(RequestBody MapString, String credentials) { String username credentials.get(username); String password credentials.get(password); if (adminService.authenticate(username, password)) { String token adminService.generateToken(username); MapString, 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 axios2. 创建后台布局AdminLayout.vue!-- src/views/admin/AdminLayout.vue -- template el-container styleheight: 100vh el-aside width200px stylebackground: #2c3e50; color: white; div stylepadding: 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 clicklogout退出登录/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); } /script3. 登录页AdminLogin.vuetemplate div classlogin-container el-card stylewidth: 400px; h2管理员登录/h2 el-form submit.preventhandleLogin el-form-item el-input v-modelform.username placeholder用户名 / /el-form-item el-form-item el-input v-modelform.password typepassword placeholder密码 / /el-form-item el-button typeprimary native-typesubmit :loadingloading登录/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; } /style4. 请求拦截器自动携带 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. 文章编辑页使用 WangEditortemplate el-card el-form :modelpost label-width80px el-form-item label标题 el-input v-modelpost.title / /el-form-item el-form-item label分类 el-select v-modelpost.categoryId placeholder请选择 el-option v-forcat in categories :keycat.id :labelcat.name :valuecat.id / /el-select /el-form-item el-form-item label内容 div styleborder: 1px solid #ccc; Toolbar styleborder-bottom: 1px solid #ccc :editoreditorRef :defaultConfigtoolbarConfig :modemode / Editor v-modelpost.content :defaultConfigeditorConfig :modemode onCreatedhandleCreated / /div /el-form-item el-button typeprimary clicksavePost保存/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()。⚠️ 注意事项密码安全永远不要明文存储用 BCryptToken 刷新本例 Token 24 小时过期简单场景够用权限粒度目前只有一个管理员无需复杂 RBACCSRF因使用 Token 而非 Cookie天然免疫 CSRF。七、最终效果访问https://your-domain.com/admin/login输入账号密码 → 进入后台点击“新建文章” → 使用富文本编辑器写作保存后前台首页立即更新视频看了几百小时还迷糊关注我几分钟让你秒懂