irpas技术客

springboot+jwt+shiro+vue+elementUI+axios+redis+mysql完成一个前后端分离项目(笔记,帮填坑)_深林中的书海

irpas 7004

根据B站up主MarkerHub视频制作的一个笔记

我的博客

B站博主链接: https://·/post/6844903823966732302 部署视频 https://·blogs.com/erlongxizhu-03/p/12193646.html

idea报Could not autowired解决办法: https://blog.csdn.net/yxm234786/article/details/81460752

ElementUI官方文档 https://element.eleme.cn/#/zh-CN/component/quickstart

PostMan安装包下载 https://blog.csdn.net/weixin_43184774/article/details/100578557

VsCode保存自动格式化样式 https://blog.csdn.net/wang0112233/article/details/90608328

Redis的安装和启动 https://·/#/zh-CN/component/installation

ctrl+`(~键)打开终端输入安装命令

# 安装element-ui cnpm install element-ui --save

然后我们打开项目src目录下的main.js,引入element-ui依赖。

import Element from 'element-ui' import "element-ui/lib/theme-chalk/index.css" Vue.use(Element)

测试elementUi是否引入成功 import Vue from 'vue' import Router from 'vue-router' import HelloWorld from '@/components/HelloWorld' import Demo from '@/views/Demo' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'HelloWorld', component: HelloWorld }, { path: '/Demo', name: 'Demo', component: Demo } ] }) 新建Demo.vue

src/views/demo 引入button按钮(官方直接复制)

<template> <div class="Demo"> {{msg}} <el-button type="primary">主要按钮</el-button> </div> </template> <script> export default{ name: "Demo", data() { return { msg: "测试" }; } } </script>

App.vue添加: <div id="nav"> <router-link to="/demo">Demo页面</router-link> </div>

运行项目浏览器打开项目

显示下方则成功

补充配置:

autoOpenBrowser修改为true。

autoOpenBrowser:true

安装axios

接下来,我们来安装axios(·blogs.com/pretty-sunshine/p/10615287.html 完善配置Header组件

<template> <div class="m-content"> <h3>欢迎来到我的博客世界</h3> <div class="block"> <div class="block"> <el-avatar :size="50" :src="user.avatar"></el-avatar> </div> <div>{{user.username}}</div> <div class="maction"> <span> <el-link>主页</el-link> </span> <el-divider direction="vertical"></el-divider> <span> <el-link type="success">发表博客</el-link> </span> <el-divider direction="vertical"></el-divider> <span v-show="!hasLogin"> <el-link type="primary" @click="login">登录</el-link> </span> <span v-show="hasLogin"> <el-link type="danger" @click="logout">退出</el-link> </span> </div> </div> </div> </template> <script> export default { name: "Header" , data () { return { user: { username: '请先登录', avatar: '../assets/logo.png' }, hasLogin: false } }, methods: { //退出操作 logout () { const _this = this //首先调用后端logout接口(因该接口需要认证权限,所以需要传入token) //其次调用$store清除用户信息及token _this.$axios.get("/logout", { headers: { "Authorization": localStorage.getItem("token") } }).then(res => { _this.$store.commit("REMOVE_INFO") _this.$router.push("/login") }) }, login () { //跳转登录页面进行登录 this.$router.push("/login") } }, //页面创建时即会调用,进而获取用户信息 created () { console.log("=======123=") console.log(this.$store.getters.getUser) if (this.$store.getters.getUser.username) {//如果username不为空 this.user.username = this.$store.getters.getUser.username this.user.avatar = this.$store.getters.getUser.avatar //判断是登录状态还是非登录显示 退出按钮或者登录按钮 this.hasLogin = true; } } } </script> <style> .m-content { max-width: 960px; margin: 0 auto; text-align: center; } .maction { } </style>

运行项目可以自行测试登录退出功能

完善blogs.vue 先给实体类Blog加上JsonFormat @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime created;

完善Blogs.vue <template> <div class="mcontaner"> <Header></Header> <div class="block"> <el-timeline> <el-timeline-item :timestamp="blog.created" placement="top" v-for="(blog,key) in blogs" :key=key> <el-card> <h4> <router-link :to="{ name: 'BlogDetail', params: {blogid: blog.id}}">{{blog.title}}</router-link> </h4> <p>{{blog.description}}</p> </el-card> </el-timeline-item> </el-timeline> <el-pagination class="mage" background layout="prev, pager, next" :current-page="currentPage" :page-size="pageSize" :total="total" @current-change=page> </el-pagination> </div> </div> </template> <script> //引入header组件 import Header from "../components/Header"; export default { data () { return { blogs: {}, currentPage: 1, total: 0, pageSize: 5 } }, name: "Blogs", components: { Header }, methods: { page (currentPage) { const _this = this _this.$axios.get("/blogs?currentPage=" + currentPage).then(res => { var data = res.data.data _this.blogs = data.records _this.currentPage = data.current _this.pageSize = data.size _this.total = data.total }) } }, created () { this.page(1) } } </script> <style scoped> .block { margin: 20px; } .mage { margin: 0 auto; } </style>

博客编辑(发表) 安装mavon-editor 基于Vue的markdown编辑器mavon-editor cnpm install mavon-editor --save 然后在main.js中全局注册: // 全局注册 import mavonEditor from 'mavon-editor' import 'mavon-editor/dist/css/index.css' // use Vue.use(mavonEditor) // The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from 'vue' import App from './App' // 存储 import store from './store' // 路由 import router from './router' // 引入element-ui依赖 import Element from 'element-ui' import "element-ui/lib/theme-chalk/index.css" // 引入axios依赖 import axios from 'axios' // 引入自定义axios.js import "./axios.js" //mavonEditor import mavonEditor from 'mavon-editor' import 'mavon-editor/dist/css/index.css' //引用全局 Vue.prototype.$axios = axios // use Vue.use(mavonEditor) Vue.use(Element) Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', router, store , components: { App }, template: '<App/>' })

编写BlogEdit.vue

<template> <div> <Header></Header> <div class="m-content"> <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm"> <el-form-item label="标题" prop="title"> <el-input v-model="ruleForm.title"></el-input> </el-form-item> <el-form-item label="摘要" prop="description"> <el-input type="textarea" v-model="ruleForm.description"></el-input> </el-form-item> <el-form-item label="内容" prop="content"> <mavon-editor v-model="ruleForm.content"></mavon-editor> </el-form-item> <el-form-item> <el-button type="primary" @click="submitForm('ruleForm')">立即创建</el-button> <el-button @click="resetForm('ruleForm')">重置</el-button> </el-form-item> </el-form> </div> </div> </template> <script> import Header from "../components/Header"; export default { name: "BlogEdit", components: { Header }, data () { return { ruleForm: { id: '', title: '', description: '', content: '' }, rules: { title: [ { required: true, message: '请输入标题', trigger: 'blur' }, { min: 3, max: 5, message: '长度在 3 到 15 个字符', trigger: 'blur' } ], description: [ { required: true, message: '请输入摘要', trigger: 'blur' }, ], content: [ { required: true, message: '请输入内容', trigger: 'blur' }, ] } }; }, methods: { submitForm (formName) { this.$refs[formName].validate((valid) => { if (valid) { const _this = this this.$axios.post('/blog/edit', this.ruleForm, { headers: { "Authorization": localStorage.getItem("token") } }).then(res => { _this.$alert('操作成功', '提示', { confirmButtonText: '确定', callback: action => { _this.$router.push("/blogs") } }); }) } else { console.log('error submit!!'); return false; } }); }, resetForm (formName) { this.$refs[formName].resetFields(); } }, created () { //获取动态路由的 blogid const blogId = this.$route.params.blogid console.log(blogId) const _this = this if (blogId) { this.$axios.get("/blog/" + blogId).then(res => { console.log(res) const blog = res.data.data _this.ruleForm.id = blog.id _this.ruleForm.title = blog.title _this.ruleForm.description = blog.description _this.ruleForm.content = blog.content }) } } } </script> <style scoped> .m-content { text-align: center; } </style>

运行效果

编辑:

博客详情和编辑按钮的权限

博客详情中需要回显博客信息,然后有个问题就是,后端传过来的是博客内容是markdown格式的内容,我们需要进行渲染然后显示出来,这里我们使用一个插件markdown-it,用于解析md文档,然后导入github-markdown-c,所谓md的样式。

# 用于解析md文档 cnpm install markdown-it --save # md样式 cnpm install github-markdown-css

然后就可以在需要渲染的地方使用:

views\BlogDetail.vue <template> <div> <Header></Header> <div class="blog"> <h2>{{blog.title}}</h2> <el-link icon="el-icon-edit" v-if="ownblog"> <router-link :to="{name:'BlogEdit',params:{blogid:blog.id}}"> 编辑 </router-link> </el-link> <el-divider></el-divider> <div class="markdown-body" v-html="blog.content"></div> </div> </div> </template> <script> import 'github-markdown-css' import Header from "../components/Header"; export default { name: "BlogDetail", components: { Header }, data () { return { blog: { id: '', title: '', content: '', description: '' }, ownblog: false } }, created () { //获取动态路由的 blogid const blogId = this.$route.params.blogid const _this = this if (blogId) { this.$axios.get("/blog/" + blogId).then(res => { const blog = res.data.data _this.blog.id = blog.id _this.blog.title = blog.title _this.blog.description = blog.description //MardownIt 渲染 var MardownIt = require("markdown-it") var md = new MardownIt(); var result = md.render(blog.content) _this.blog.content = result //查看是否是登录人 是则可以编辑 _this.ownBlog = (blog.userId === _this.$store.getters.getUser.id) }) } } } </script> <style scoped> .blog { margin-top: 10px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); width: 100%; min-height: 700px; padding: 10px; } </style> 路由权限拦截

页面已经开发完毕之后,我们来控制一下哪些页面是需要登录之后才能跳转的,如果未登录访问就直接重定向到登录页面,因此我们在src目录下定义一个js文件:

//配置一个路由前置拦截 rounter是路由

src\permission.js import router from "./router"; // 路由判断登录 根据路由配置文件的参数 router.beforeEach((to, from, next) => { if (to.matched.some(record => record.meta.requireAuth)) { // 判断该路由是否需要登录权限 const token = localStorage.getItem("token") console.log("------------" + token) if (token) { // 判断当前的token是否存在 ; 登录存入的token if (to.path === '/login') { } else { next() } } else { next({ path: '/login' }) } } else { next() } })

通过之前我们再定义页面路由时候的的meta信息,指定requireAuth: true,需要登录才能访问,因此这里我们在每次路由之前(router.beforeEach)判断token的状态,觉得是否需要跳转到登录页面。

src/rouer/index.js 添加:

meta: { requireAuth: true }

{ path: '/blog/add', // 注意放在 path: '/blog/:blogId'之前 name: 'BlogAdd', meta: { requireAuth: true }, component: BlogEdit },{ path: '/blog/:blogid/edit', name: 'BlogEdit', component: BlogEdit, meta: { requireAuth: true } import Vue from 'vue' import Router from 'vue-router' import HelloWorld from '@/components/HelloWorld' import Demo from '@/views/Demo' import Login from '@/views/Login' import Blogs from '@/views/Blogs' import BlogEdit from '@/views/BlogEdit' import BlogDetail from '@/views/BlogDetail' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'Index', redirect:{name : "Blogs"} }, { path: '/blogs', name: 'Blogs', component: Blogs },{ path: '/Login', name: 'Login', component: Login },{ path: '/blog/add', name: 'BlogEdit', component: BlogEdit, meta: { requireAuth: true } }, { path: '/Demo', name: 'Demo', component: Demo },{ path: '/blog/:blogid', name: 'BlogDetail', component: BlogDetail } ,{ path: '/blog/:blogid/edit', name: 'BlogEdit', component: BlogEdit, meta: { requireAuth: true } } ]})

然后我们再main.js中import我们的permission.js

src/main.js

添加: import ‘./permission.js’ // 路由拦截

// The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from 'vue' import App from './App' // 存储 import store from './store' // 路由 import router from './router' // 引入element-ui依赖 import Element from 'element-ui' import "element-ui/lib/theme-chalk/index.css" // 引入axios依赖 import axios from 'axios' // 引入自定义axios.js import "./axios.js" import './permission.js' // 路由拦截 //mavonEditor import mavonEditor from 'mavon-editor' import 'mavon-editor/dist/css/index.css' //引用全局 Vue.prototype.$axios = axios // use Vue.use(mavonEditor) Vue.use(Element) Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', router, store , components: { App }, template: '<App/>' }) 第十章 其他 增加删除文章功能 前端

src/views/BlogDetail.vue

<template> <div> <Header></Header> <div class="blog"> <h2>{{blog.title}}</h2> <el-link icon="el-icon-edit" v-if="ownblog" class="linklist"> <router-link :to="{name:'BlogEdit',params:{blogid:blog.id}}"> 编辑 </router-link> </el-link> <el-link icon="el-icon-delete" v-if="ownblog" class="linklist"> <el-button type="danger" round @click="delblog">删除</el-button> </el-link> <el-divider></el-divider> <div class="markdown-body" v-html="blog.content"></div> </div> </div> </template> <script> import 'github-markdown-css' import Header from "../components/Header"; export default { name: "BlogDetail", components: { Header }, data () { return { blog: { id: '', title: '', content: '', description: '' }, ownblog: false } }, methods: { delblog () { const blogId = this.$route.params.blogid const _this = this if (blogId) { this.$confirm('此操作将永久删除该文章, 是否继续?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { _this.$axios.post(`/blogdel/${blogId}`, null, { headers: { "Authorization": localStorage.getItem("token") } }).then(res => { this.$message({ type: 'success', message: res.data.data }); _this.$router.push("/blogs") }) }).catch(() => { this.$message({ type: 'info', message: '已取消删除' }); }); } } }, created () { //获取动态路由的 blogid const blogId = this.$route.params.blogid const _this = this if (blogId) { this.$axios.get("/blog/" + blogId).then(res => { const blog = res.data.data _this.blog.id = blog.id _this.blog.title = blog.title _this.blog.description = blog.description //MardownIt 渲染 var MardownIt = require("markdown-it") var md = new MardownIt(); var result = md.render(blog.content) _this.blog.content = result //查看是否是登录人 是则可以编辑和删除 _this.ownblog = (blog.userId === _this.$store.getters.getUser.id) }) } } } </script> <style scoped> .blog { margin-top: 10px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); width: 100%; min-height: 700px; padding: 10px; } .linklist { margin: 5px; } </style> 后端 src/main/java/com/vuelog/controller/BlogController //@PathVariable动态路由 @RequiresAuthentication //需要认证之后才能操作 @PostMapping("/blogdel/{id}") public Result del(@PathVariable Long id){ boolean b = blogService.removeById(id); //判断是否为空 为空则断言异常 if(b==true){ return Result.succ("文章删除成功"); }else{ return Result.fail("文章删除失败"); } } package com.vueblog.controller; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.lang.Assert; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.vueblog.common.lang.Result; import com.vueblog.entity.Blog; import com.vueblog.service.BlogService; import com.vueblog.util.ShiroUtil; import org.apache.shiro.authz.annotation.RequiresAuthentication; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.time.LocalDateTime; /** * <p> * 前端控制器 * </p> * * @author 深林中的书海 * @since 2021-02-05 */ @RestController public class BlogController { @Autowired BlogService blogService; //木有值默认为1 @GetMapping("/blogs") public Result list(@RequestParam(defaultValue = "1") Integer currentPage){ Page page = new Page(currentPage, 5); IPage pageData = blogService.page(page, new QueryWrapper<Blog>().orderByDesc("created")); return Result.succ(pageData); } //@PathVariable动态路由 @GetMapping("/blog/{id}") public Result detail(@PathVariable Long id){ Blog blog = blogService.getById(id); //判断是否为空 为空则断言异常 Assert.notNull(blog,"该博客已被删除"); return Result.succ(blog); } //@Validated校验 //@RequestBody //添加删除 木有id则添加 有id则编辑 @RequiresAuthentication //需要认证之后才能操作 @PostMapping("/blog/edit") public Result edit(@Validated @RequestBody Blog blog){ //一个空对象用于赋值 Blog temp = null; //如果有id则是编辑 if(blog.getId() != null){ temp = blogService.getById(blog.getId());//将数据库的内容传递给temp //只能编辑自己的文章 Assert.isTrue(temp.getUserId().longValue()== ShiroUtil.getProfile().getId().longValue(),"没有编辑权限"); } else { temp = new Blog(); temp.setUserId(ShiroUtil.getProfile().getId()); temp.setCreated(LocalDateTime.now()); temp.setStatus(0); } //将blog的值赋给temp 忽略 id userid created status 引用于hutool BeanUtil.copyProperties(blog,temp,"id","userId","created","status"); blogService.saveOrUpdate(temp); return Result.succ(null); } //@PathVariable动态路由 @RequiresAuthentication //需要认证之后才能操作 @PostMapping("/blogdel/{id}") public Result del(@PathVariable Long id){ boolean b = blogService.removeById(id); //判断是否为空 为空则断言异常 if(b==true){ return Result.succ("文章删除成功"); }else{ return Result.fail("文章删除失败"); } } }


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #帮填坑 #创建项目第八步骤就行第一