技术栈

  • SpringBoot
  • mybatis plus
  • shiro
  • lombok
  • redis
  • hibernate validatior
  • jwt

第一阶段----雏形

1. vue前端

1.1 全局文件说明

在这里插入图片描述
所有页面注册路由的文件,router下index

import Vue from 'vue'
import Router from 'vue-router'
import Login from "../view/Login";
import Blogs from "../view/Blogs";
import Regist from "../view/Regist";
import BlogEdit from "../view/BlogEdit";
import BlogDetail from "../view/BlogDetail";
import Community from "../view/Community";

Vue.use(Router)


export default new Router({
  routes: [
    // 重定向
    {
      path: "/",
      redirect:'/login'

    },
    {
      path:"/blog_community",
      name:"BlogCommunity",
      component:Community
    },
    {
      path: "/login",
      name: "Login",
      component: Login
        },
    {
      path: "/regist",
      name: "Regist",
      component: Regist
    },
    {
      path:"/add",
      name:"Edit",
      meta:{
        requireAuth: true
      },
      component:BlogEdit
    },
    {
      path:"/blogs",
      name:"Home",
      meta:{
        requireAuth: true
      },
      component:Blogs
    },
    {
      path:"/blog_detail/:blogId",
      name:"BlogDetail",
      meta:{
        requireAuth: true
      },
      component:BlogDetail
    }
      ]
})

mainjs全局文件引入插件,自己写的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 router from './router'
import ElementUI, {MessageBox} from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import axios from 'axios';
import less from 'less'
// 添加全局样式
import './assets/css/globle.css';
// 引入iconfront
import './assets/font/iconfont.css'
// 引入vuex-store
import store from './store/index';
import mavonEditor from 'mavon-editor'
import 'mavon-editor/dist/css/index.css'
//引入路由守卫
import './permission'

Vue.use(mavonEditor)
Vue.prototype.$confirm=MessageBox.confirm
Vue.use(less)
Vue.prototype.$axios = axios;
Vue.use(ElementUI);

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store,
  components: { App },
  template: '<App/>'
})

App.vue路由占位符,vue是单页面,router-view也就是每次加载页面替换的页面部分

<template>
  <div id="app">
    <!--路由占位符-->
    <router-view></router-view>
  </div>
</template>

<style>
  #app{

  }
</style>

<script>
  export default {
  };
</script>

store下index.js

啥是store?
Vuex就是提供一个仓库,Store仓库里面放了很多对象。其中state就是数据源存放地,对应于与一般Vue对象里面的data(后面讲到的actions和mutations对应于methods)。
在使用Vuex的时候通常会创建Store实例new Vuex.store({state,getters,mutations,actions})有很多子模块的时候还会使用到modules。

  • vuex优势:相比sessionStorage,存储数据更安全,sessionStorage可以在控制台被看到。

  • vuex劣势:在F5刷新页面后,vuex会重新更新state,所以,存储的数据会丢失。(后面因为这个踩坑)

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)


export default new Vuex.Store({
  //相当于私有属性
  state: {
    token: '',
    userInfo: ''
  },

  //相当于set方法
  mutations: {
    SET_TOKEN: (state, token) => {
      state.token = token
      localStorage.setItem("token", token)
    },
    SET_USERINFO: (state, userInfo) => {
      state.userInfo = userInfo
      sessionStorage.setItem("userInfo", JSON.stringify(userInfo))
    },
    REMOVE_INFO: (state) => {
      localStorage.setItem("token", '')
      sessionStorage.setItem("userInfo", JSON.stringify(''))
      state.userInfo = {}
    }
  },

  // 相当于get
  getters: {
    getUser: state => {
      return state.userInfo
    }
  },
  actions: {},
  modules: {}
})

公共组件
因为后面页面都需要一个头部信息,所以抽取出来做成组件,只要引用就可以了。

<template>
  <div class="m-content">
    <h3>欢迎来到{{user.username}}的博客</h3>
    <div class="block">
      <el-avatar :size="50" :src="require('.././assets/css/img/user.jpg')" fit="cover"></el-avatar>
      <div>{{ user.username }}</div>
    </div>
    <div>
    <el-divider direction="vertical"></el-divider>
        <el-link class="el-icon-edit" @click="toAdd" type="primary">发表</el-link>
    <el-divider direction="vertical"></el-divider>
    <el-link class="el-icon-delete" type="danger" @click="toBlogs">主页</el-link>
    <el-divider direction="vertical"></el-divider>
    <el-link class="el-icon-switch-button" type="warning" v-show="hasLogin" @click="logout">退出</el-link>
    <el-link class="el-icon-switch-button" type="warning" v-show="!hasLogin">请登录</el-link>
    <el-divider direction="vertical"></el-divider>
    </div>
  </div>
</template>
<script>
    export default {
      name: "Header",
      data() {
        return {
          hasLogin: false,
          user: {
            username: '',
            avatar: "../assets/css/img/user.jpg"
          },
          blogs: {},
          currentPage: 1,
          total: 0
        }
      },
      methods: {
        logout() {
          const _this = this
            _this.$store.commit('REMOVE_INFO')
            _this.$router.push('/login')
        },
        toAdd() {
          this.$router.push({
            path:'/add',
          })
        },
        toBlogs(){
          this.$router.push({
            path:'/blogs',
          })
        }
      },
      // 从store中取出用户信息存到data中定义的变量user中
      created() {
        if(this.$store.getters.getUser.username) {
          this.user.username = this.$store.getters.getUser.username
          this.user.img = "../assets/css/img/user.jpg"
          this.hasLogin = true
        }
      }
    }
</script>

<style scoped>
  .m-content{
    text-align: center;
  }
</style>

引入方式
<template>中引入,注意别和小写header弄混
然后在<script>
import Header from "../components/Header";

1.2 登录功能

login页面


<template>
  <div>
      <el-container>
        <el-main>
<!--      绑定script中data数据model="submitForm"-->
          <el-form :model="submitForm" :rules="rules" ref="ruleForm" class="LoginForm">
            <el-form-item label="用户名" prop="username">
              <!--  v-model  绑定data中数据"-->
              <el-input v-model="submitForm.username" prefix-icon="iconfont icon-user"></el-input>
            </el-form-item>
            <el-form-item label="密码" prop="password">
              <el-input type="password" v-model="submitForm.password" prefix-icon="iconfont icon-unlock"></el-input>
            </el-form-item>
            <el-row>
              <el-button type="primary" @click="login" >登录/注册</el-button>
            </el-row>
          </el-form>
        </el-main>
      </el-container>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        userToken:"",
        // 登录表单的数据绑定对象
        submitForm: {
          username: '',
          password: ''
        },
        rules: {
          username: [
            { required: true, message: '请输入用户名', trigger: 'blur' },
            { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' },
            {
              required: true,
              pattern: /^[\u4e00-\u9fa5_a-zA-Z0-9.·-]+$/,
              message: '姓名不支持特殊字符',
              trigger: 'blur'
            }
          ],
          password: [
            { required: true, message: '请输入密码', trigger: 'change' },
            {
              pattern: /([a-zA-Z0-9][!@#$%^&()*])|([!@#$%^&()*][a-zA-Z0-9])+/,
              message: '密码必须由字母、数字、特殊符号组成,区分大小写',
              trigger: 'blur'
            }
          ]
        }
      };
    },
    methods: {
      login() {
        // 发起请求后this会丢失,先保存
        var that=this
        this.$refs.ruleForm.validate((valid) => {
          if (valid) {
            // 向后端发起请求,提交数据
            this.$axios.post('http://localhost:9090/login',this.submitForm
            ).then(function (response){
              //查询到用户跳转个人中心界面
              if (response.data.data!==""&&response.data.data!=null){
                alert("登陆成功!")
                const token=response.headers["authorization"]
                console.log(token)
                //先写死token,因为总是获取不到token
                that.$store.commit('SET_TOKEN',token)
                that.$store.commit('SET_USERINFO',response.data.data)
                console.log(that.$store.getters.getUser.userId)
                console.log(that.$store.getters.getUser.username)
                that.$router.push({
                  path:'/blogs'
                })
              }
              //查询不到弹窗提示注册
              else{
                const confirmRes=that.$confirm("用户名或密码错误!是否注册?",{
                  confirmButtonText:'注册',
                  type:'info'
                }).then((action)=>{
                  if(action === 'confirm'){
                    that.$router.push({
                      path:'/regist'
                    })
                  }
                })
              }
            })
          } else {
            console.log('error submit!!');
            return false;
          }
        });
      }
    }
  }
</script>

<style scoped lang="less">
  .el-form{
    background-color: gainsboro;
    /*表单边框*/
    border: 1px solid #DCDFE6;
    width: 350px;
    height: 300px;
    margin: 200px auto;
    /*设置各边上内边距的宽度*/
    padding: 35px 35px 15px 35px;
    /*设置圆角边框*/
    border-radius: 5px;
    /*处理圆角效果的*/
    -webkit-border-radius: 5px;
    -moz-border-radius: 5px;
    /*添加阴影*/
    box-shadow: 0 0 25px SlateGray;
    /*设置表单透明度*/
    filter:alpha(Opacity=90);
    -moz-opacity:0.9;
    opacity: 0.9;
 }
  .el-row{
    margin-top: 50px;
    /*文本对齐方式*/
    text-align: center;
  }
  .el-button{
    text-align: center;
    width:300px ;
    margin-top: 0px;

  }
</style>

使用了element ui的表单和弹窗,以及阿里巴巴矢量图,记得加入购物车再下载,把下载的文件复制到assert,具体百度。

踩坑: header的token怎么都拿不到,按网上的后台也打开了暴露设置,后来发现axios 会把 header 转为小写,比如 Content-Type -> content-type,Authorization 会被转为 authorization……

1.3 注册功能

regist页面

<template>
  <div>
    <el-container>
      <el-form :model="registForm" :rules="rules" ref="registForm" class="demo-registForm">
        <el-form-item label="用户名" prop="username" required>
          <el-input v-model="registForm.username" prefix-icon="iconfont icon-user"></el-input>
        </el-form-item>
        <el-form-item label="密码" prop="password" required>
          <el-input type="password" v-model="registForm.password" autocomplete="off" prefix-icon="iconfont icon-unlock"></el-input>
        </el-form-item>
        <el-form-item label="确认密码" prop="checkPass" required>
          <el-input type="password" v-model="registForm.checkPass" autocomplete="off" prefix-icon="iconfont icon-unlock"></el-input>
        </el-form-item>
        <el-form-item label="生日" required>
          <el-col :span="11">
            <el-form-item prop="date1">
              <el-date-picker type="date" placeholder="选择日期" v-model="registForm.date1" style="width: 100%;"></el-date-picker>
            </el-form-item>
          </el-col>
        </el-form-item>
        <el-form-item>
          <el-row>
          <el-button class="regist_button" type="primary" @click="submitForm('registForm')">立即注册</el-button>
          <el-button class="reset_button" @click="resetForm('registForm')">重置</el-button>
          </el-row>
        </el-form-item>
      </el-form>
    </el-container>
  </div>
</template>

<script>
  export default {
    data() {
      var test_Pass = (rule, value, callback) => {
        if (value !== this.registForm.password) {
          callback(new Error("两次输入的密码不一致!"));
        } else {
          callback();
        }
      };
      var test_username = (rule, value, callback) => {
        this.$axios.post('http://localhost:9090/query_name',{
          username:this.registForm.username
        }).then(function (response) {
          if (response.data.data==false){
            callback(new Error(response.data.msg))
          }
            callback();
        })
      };
      return {
        registForm: {
          username: '',
          password: '',
          birthday: ''
        },
        rules: {
          username: [
            // 有多个那么失去焦点时按顺序执行
            { required: true, message: '请输入用户名', trigger: 'blur' },
            { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' },
            {
              required: true,
              pattern: /^[\u4e00-\u9fa5_a-zA-Z0-9.·-]+$/,
              message: '姓名不支持特殊字符',
              trigger: 'blur'
            },{
            // 有message的话上面的error不会显示
              required: true,
              validator:test_username,
              trigger: 'blur'
            }
          ],
          password: [
            { required: true, message: '请输入密码', trigger: 'blur' },
            {
              pattern: /([a-zA-Z0-9][!@#$%^&()*])|([!@#$%^&()*][a-zA-Z0-9])+/,
              message: '密码必须由字母、数字、特殊符号组成,区分大小写',
              trigger: 'blur'
            }
          ],
          checkPass:[
            {
              required: true,
              message:'请确认密码!',
              trigger: 'blur'
            },
            {
              required: true,
              validator: test_Pass,
              trigger:'blur'
          }
          ],
          date1: [
            { type: 'date', required: true, message: '请选择日期', trigger: 'change' }
          ]
        }
      };
    },
    methods: {
      submitForm(registForm) {
        this.$refs[registForm].validate((valid) => {
          if (valid) {
            var _this=this
            this.$axios.post('http://localhost:9090/regist',this.registForm).then(function (response) {
                if (response.data.data==true){
                   _this.tips();
                  _this.$router.push({
                    path:'/login'
                  })
                }
            })

          } else {
            console.log('error submit!!');
            return false;
          }
        });
      },
      resetForm(registForm) {
        this.$refs[registForm].resetFields();
      },
      tips() {
        this.$notify({
          title: '成功',
          message: '注册成功请登陆!',
          type: 'success'
        });
      },
    }
  }
</script>

<style scoped lang="less">
  .el-form{
    background-color: gainsboro;
    /*表单边框*/
    border: 1px solid #DCDFE6;
    width: 350px;
    height: 450px;
    margin: 150px auto;
    /*设置各边上内边距的宽度*/
    padding: 35px 35px 15px 35px;
    /*设置圆角边框*/
    border-radius: 5px;
    /*处理圆角效果的*/
    -webkit-border-radius: 5px;
    -moz-border-radius: 5px;
    /*添加阴影*/
    box-shadow: 0 0 25px palegreen;
    /*设置表单透明度*/
    filter:alpha(Opacity=90);
    -moz-opacity:0.9;
    opacity: 0.9;
  }
  .el-row{
    margin-top: 30px;
  }
  .reset_button{
    position: absolute;
    margin-left: 180px;
  }
</style>

1.4 博客列表

blogs

<template>
  <div class="blogs">
    <Header></Header>
    <div class="block">
      <el-timeline>
        <el-timeline-item :timestamp="blog.created" placement="top" v-for="blog in blogs">
          <el-card>
            <router-link :to="{name: 'BlogDetail', params: {blogId: blog.id}}">
              <h4>{{blog.title}}</h4>
            </router-link>
            <p>{{blog.description}}</p>
          </el-card>
        </el-timeline-item>
      </el-timeline>
    </div>
<!--    分页-->
    <el-pagination class="mpage"
                   @current-change="page"
                   background
                   layout="prev, pager, next"
                   :current-page="currentPage"
                   :page-size="pageSize"
                   :total="total"

    >
      });
    </el-pagination>
  </div>
</template>

<script>
  import Header from "../components/Header";
  export default {
      name: "Blogs",
      components: {Header},
      data() {
        return {
          blogs: {},
          currentPage: 1,
          total: 0,
          pageSize: 5,
      }
      },
    methods: {
      // 分页
      page(currentPage){
        const _this=this
        var userInfo=JSON.parse(sessionStorage.getItem("userInfo"))
        console.log("id---"+userInfo.userId)
          this.$axios.get('http://localhost:9090/blogs?currentPage=' + currentPage ,{params:{userId:userInfo.userId}}).then((res) => {
            // console.log(res.data.data.records)
            _this.blogs = res.data.data.records
            _this.currentPage = res.data.data["current"]
            _this.total = res.data.data["total"]
            _this.pageSize = res.data.data["size"]
          })
      },
    },
    created() {
        this.page(1)
    }

  }
</script>

<style scoped>
  .mpage{
    margin: 0 auto;
    text-align: center;
  }
  .blogs{
    max-width:1000px;
    margin: 0 auto;
  }
</style>

踩坑:f5刷新后登录信息丢失,列表空白,是因为发起请求提交userId参数的时候用的是_this.$store.getters.getUser.userId,上面说过刷新的话store会重新清空,那么_this.$store.getters.getUser.userId就成了null,后台得不到userId,改成上面的userInfo=JSON.parse(sessionStorage.getItem("userInfo")),直接从sessionstorage取不通过store,但是注意store中是user整个json存的所以要先转换在取userid,params:{userId:userInfo.userId}

这里列表只显示登录用户的博客,标题是超链接,跳转url会携带博客id跳转到博客详情页面,http://localhost:8080/#/blog_detail/5

分页: 使用elementui分页组件
在这里插入图片描述

1.5 博客编辑

<template>
  <div>
    <el-container>
      <el-header>
        <Header></Header>
      </el-header>
      <el-main>
        <el-form ref="ruleForm" :model="editForm" label-width="80px" :rules="rules">
          <el-form-item label="标题">
            <el-input v-model="editForm.title"></el-input>
          </el-form-item>
          <el-form-item label="摘要">
            <el-input v-model="editForm.description"></el-input>
          </el-form-item>
          <el-form-item label="标签">
            <el-checkbox-group v-model="editForm.tag">
              <el-checkbox label="Java" name="type"></el-checkbox>
              <el-checkbox label="Vue" name="type"></el-checkbox>
              <el-checkbox label="SpringBoot" name="type"></el-checkbox>
              <el-checkbox label="SpringCloud" name="type"></el-checkbox>
            </el-checkbox-group>
            <el-button class="el-icon-plus">添加标签</el-button>
          </el-form-item>
          <el-form-item label="可见范围">
            <el-radio-group v-model="editForm.permission">
              <el-radio label="私密"></el-radio>
              <el-radio label="公开"></el-radio>
            </el-radio-group>
          </el-form-item>
          <el-form-item label="内容" class="content_input">
            <mavon-editor v-model="editForm.content"></mavon-editor>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" @click="SubmitFrom">立即创建</el-button>
            <el-button>取消</el-button>
          </el-form-item>
        </el-form>
      </el-main>
    </el-container>
  </div>
</template>
<script>
  import Header from "../components/Header";
  export default {
    components: {Header},
    data() {
      return {
        editForm: {
          user_id:this.$store.getters.getUser.user_id,
          title:'',
          description:'',
          tag:'',
          permission:'',
          content:''
        },
        rules: {
        title: [
          {required: true, message: '请输入标题', trigger: 'blur'},
          {min: 3, max: 50, message: '长度在 3 到 50 个字符', trigger: 'blur'}
        ],
          description: [
          {required: true, message: '请输入摘要', trigger: 'blur'}
        ]
      }
     }
    },
    created() {
      const blogId = this.$route.query.blogId
      console.log(blogId)
      const _this = this
      if(blogId) {
        this.$axios.get('http://localhost:9090/blog/' + blogId).then((res) => {
          const blog = res.data.data
          _this.editForm.id = blog.id
          _this.editForm.title = blog.title
          _this.editForm.description = blog.description
          _this.editForm.content = blog.content
        });
      }
    },
    methods: {
      SubmitFrom() {
        this.$refs.ruleForm.validate((valid) => {
          if (valid) {
            var _this=this
            console.log(this.$store.getters.getUser.user_id),
            // 向后端发起请求,提交数据
            this.$axios.post('http://localhost:9090/edit_blog',this.editForm,{
              header: {
                "Authorization": localStorage.getItem("token")
              }
            }
            ).then(function (response){
              if (response.data.data=true)
                _this.tips()
               _this.$router.push({
                 path:'/blogs'
               })
            })
          } else {
            console.log('error submit!!');
            return false;
          }
        });
        console.log('submit!');
      },
      tips() {
        this.$notify({
          title: '上传成功',
          message: '博客已成功上传!',
          type: 'success',
          duration:1000
        });
      }
    }
  }
</script>
<style>
  .el-main{
    margin-top: 110px;
  }
</style>

如果博客id不为空,说明从博客列表跳转的博客编辑,那么获取url上的博客id,后台发起请求,回显博客内容,否则是空白的新博客编辑,还调用了vue的markdown插件
在这里插入图片描述

cnpm install mavon-editor --save

1.6 博客详情

<template>
  <div class="m-container">
    <Header></Header>
      <div class="mblog">
        <h2>{{ blog.title }}</h2>
        <el-link type="success" class="edit" icon="el-icon-edit" v-if="ownBlog" @click="toEdit">编辑</el-link>
        <el-divider></el-divider>
        <div class="content markdown-body" v-html="blog.content"></div>
      </div>
  </div>
</template>

<script>
  import 'github-markdown-css/github-markdown.css' // 然后添加样式markdown-body
  import Header from "../components/Header";
  export default {
    name: "BlogDetail",
    components: {
      Header
    },
    data() {
      return {
        blog: {
          userId: null,
          title: "",
          description: "",
          content: ""
        },
        ownBlog: false
      }
    },
    methods: {
      getBlog() {
        // this.$route.params来获取路由中的参数
        const blogId = this.$route.params.blogId
        const _this = this
        this.$axios.get('http://localhost:9090/blog/' + blogId).then((res) => {
          console.log(res)
          console.log(res.data.data)
          _this.blog = res.data.data
          var MarkdownIt = require('markdown-it'),
            md = new MarkdownIt();
          var result = md.render(_this.blog.content);
          _this.blog.content = result
          // 判断是否是自己的文章,能否编辑,暂时没有用处
          _this.ownBlog =  (_this.blog.userId === _this.$store.getters.getUser.id)
        });
      },
      toEdit(){
        const blogId = this.$route.params.blogId
        console.log(blogId)
        this.$router.push({
          path:'/add',
          query:{blogId:blogId}
        })
      }
    },
    created() {
      this.getBlog()
    }
  }
</script>

<style scoped>
  .edit{
    margin-left: 1300px;
  }
</style>

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

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

这个页面的路由是带参数的,页面有编辑,点击携带id到编辑页面可以修改微博
在这里插入图片描述

1.7 路由守卫

permission

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

2. springboot后端

在这里插入图片描述

  • codegen是mybaitsplus的代码生成器
  • Result是统一结果封装
package com.tutougirl.duck.common;
import lombok.Data;

import java.io.Serializable;

@Data
public class Result implements Serializable {
      private String code;
      private String msg;
      private Object data;
      public static Result succ(Object data) {
        Result m = new Result();
        m.setCode("0");
        m.setData(data);
        m.setMsg("操作成功");
        return m;
      }
      public static Result succ(String mess, Object data) {
        Result m = new Result();
        m.setCode("0");
        m.setData(data);
        m.setMsg(mess);
        return m;
      }
      public static Result fail(String mess) {
        Result m = new Result();
        m.setCode("-1");
        m.setData(null);
        m.setMsg(mess);
        return m;
      }
      public static Result fail(String mess, Object data) {
        Result m = new Result();
        m.setCode("-1");
        m.setData(data);
        m.setMsg(mess);
        return m;
      }
}
  • corsconfig是解决跨域问题的配置类
package com.tutougirl.duck.Config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * Created with IntelliJ IDEA.
 *
 * @Auther: 何艳莹
 * @Date: 2021/04/12/21:41
 * @Description:
 */

//解决cros跨域问题
@Configuration
public class CrosConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOriginPatterns("*")
                .allowedMethods("GET","POST","HEAD","PUT","DELETE","OPTIONS")
                .allowCredentials(true)
                .maxAge(3600)
                .allowedHeaders("*");
    }
}

  • mybaitsplus里是开启mybaitsplus自带分页插件配置类
package com.tutougirl.duck.Config;

import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/**
 * Created with IntelliJ IDEA.
 *
 * @Auther: 何艳莹
 * @Date: 2021/05/02/17:54
 * @Description:
 */
@Configuration
@EnableTransactionManagement
@MapperScan("com.tutougirl.duck.Mapper")
public class MybatisPlusConfig {

    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }
}

  • tokenuntils是生产和验证token的工具类
package com.tutougirl.duck.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.tutougirl.duck.Entity.User;

import java.util.Date;

public class TokenUtil {

    private static final long EXPIRE_TIME= 60*1000;
    private static final String TOKEN_SECRET="txdy";  //密钥盐

    /**
     * 签名生成
     * @param user
     * @return
     */
    public static String produceToken(User user){
        String token = null;
        try {
            Date expiresAt = new Date(System.currentTimeMillis() + EXPIRE_TIME);
            token = JWT.create()
                    .withIssuer("auth0")
                    .withClaim("username", user.getUsername())
                    .withExpiresAt(expiresAt)
                    // 使用了HMAC256加密算法。
                    .sign(Algorithm.HMAC256(TOKEN_SECRET));
        } catch (Exception e){
            e.printStackTrace();
        }
        return token;
    }

    /**
     * 签名验证
     * @param token
     * @return
     */
    public static boolean verify(String token){
        try {
            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(TOKEN_SECRET)).withIssuer("auth0").build();
            DecodedJWT jwt = verifier.verify(token);
            System.out.println("认证通过:");
            System.out.println("username: " + jwt.getClaim("username").asString());
            System.out.println("过期时间:      " + jwt.getExpiresAt());
            return true;
        } catch (Exception e){
            return false;
        }
    }
}

踩坑:

  1. get请求,接收参数不用加@requestbody不然报错
  2. 根据登录的用户去数据库查出来的user的id一直是0,但是数据库不是0,原因是数据库中字段带下划线的在mybatisplus里对应的在java中默认是驼峰命名,er我的字段是user_id,它给我自动转换了,导致找不到这个数据库属性,可能默认返回了0.后来改了字段为userId, 在属性上加@TableField(value=“数据库字段名”) 来匹配数据库字段
 //mybaitsplus会自动加下划线,如果列名是userId报错,哪怕字段和列名对应也不行。
  @TableField("user_id")
  private int userId;

在这里插入图片描述
在这里插入图片描述

待完善内容

想增加一个公共博客页面,博客列表不再是自己的,而是所有用户的,大家都可以看见,还可以评论。
后台没有验证token,统一异常处理。考虑整合shiro,整合redis。

第二阶段 优化完善+新功能

这段时间我又成长了。并且学习方向从后端转向了前端。
2022.2.14 情人节
前几天对前端代码进行了优化,增加了API统一管理,axios二次封装,vuex存储公共数据,分割组件,增加事件委派减少组件的复用导致的多次发送请求,增加了函数防抖。并且将标签抽取成组件,实现了博客页面分类展示博客、动态增加标签。由于不再深入后端,学习了mock模拟后端数据。对header增加了个人中心。鼠标放置弹出组件,由于想要在所有页面都可以打开个人中心所以设置了z-index,然而由于使用了vue的动画,translation。导致出现bug,弹出先在最底下,延迟一会儿才会到最上层。还没有解决。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐