springboot+vue+elementUI搭建个人博客
springboot+vue+elementUI搭建个人博客1. vue画登录注册页面(前端比较凑活,毕竟是学后端的)1.1 elementUI找布局容器粘过来并修改1.2 App.vue三级目录1. vue画登录注册页面(前端比较凑活,毕竟是学后端的)1.1 elementUI找布局容器粘过来并修改1.2 App.vue因为想要全局背景图。把背景图片放在login.vue的时候总是无法完整填充屏
duck blog 博客项目
技术栈
- 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;
}
}
}
踩坑:
- get请求,接收参数不用加@requestbody不然报错
- 根据登录的用户去数据库查出来的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,弹出先在最底下,延迟一会儿才会到最上层。还没有解决。
更多推荐
所有评论(0)