SpringBoot+Vue 实现博客前后端分离
发表于
浏览量960
评论数1
SpringBoot+Vue 实现博客前后端分离
目录:
- 前言;
- 整体架构;
- 用户使用;
- 踩坑及总结;
- 写在最后;
前言;
看了一下,7月10号放假,7月14号开始做前后端分离的工作,算下来已经有18天了,不过自己也是变学变做的,花了这么长时间也是在所难免,
> > (其实也在疯狂摸鱼),好在赶在了7月的尾巴终于做好了,欢迎大家体验。 就前端页面来说,还是和原来的博客差不多的,毕竟自己审美只有这么高了,不过页面交互感觉有很大的提升以及代码的量减少了很多,框架用起来是真的香啊。 其实一开始(21年初就把项目建好了)本来想的是直接做一个前后端分离的在线答题网站拿来当毕设的,Github也有开源可以借鉴,想着以此可以边学边做还是可以做出来的,但是打开git下来的源码,发现自己有好多知识盲区(哭,所以就一直搁置了,到现在项目都还没正式开始写。 然后现在说毕设要改革,是要组队做,真好,又可以摸鱼了(笑 之前的博客还是可以访问的,部署在了:8000
端口,访问地址:kasuie.top:8000
,欢迎对照体验一下
整体架构;
后端框架依旧是 SpringBoot,Mybatis持久层,Mysql数据库 主要是前端,使用了现在流行的前端框架Vue.js,构建的是单页应用(SPA),我的版本是vue3,UI框架是用的经典的饿了么ElmentUI,不过我用的还是2.x版本的,没用最新的3.x,前后端交互采用的是axios 其他一些组件有:日期处理类库
链接
,图片裁剪组件链接
,瀑布流组件链接
,Markdown编辑器组件链接
...等。
用户使用;
首先是用户登录注册,PC端游客在页面右上角就能看到一个登录注册图标,通过图标可以进行注册登录操作,QQ和微信快速登录暂不支持,因为申请需要我网站上线,所以等后面我申请好了再添加上吧,移动端就在左上角有一个小喵咪头像图标,点击可以在侧边栏找到登录注册入口。 更改头像,暂时只能有一次改头像的机会,PC端登录后在右上角会显示自己的头像,通过头像可以看到自己个人资料的卡片,如未更改过头像可以在这里更改,移动端点击左上角猫猫头在侧边栏如果为更改过头像可以有一次更改机会。 留言评论,支持私密评论,如果是自己的评论可以选择删除。
踩坑记录;
这个就有点多了,太南了。
1.跨域问题;
首先前后端分离嘛,由于使用端口不同,肯定要进行跨域处理才可以进行交互,vue3版本没有config文件夹,要进行跨域配置需要在自己项目的根目录下新建一个名为
vue.config.js
的文件,里面配置如下:
module.exports = {
devServer: {
/* 自动打开浏览器 */
open: false,
/* 设置为0.0.0.0则所有的地址均能访问 */
host: '0.0.0.0',
https: false,
hotOnly: false,
/* 使用代理 */
proxy: {
'/api': {
target: 'http://localhost:8000', //后端地址
secure: false, //如果是https接口,需要配置这个参数
ws: true, //是否代理websockets
changeOrigin: true, //如果接口跨域,需要进行这个参数配置
pathRewrite: {
'^/api': ''
}
}
}
},
}
这里
proxy:
字段就是跨域的配置,'/api'
是匹配以此开头的请求都由它来代理,pathRewrite
中的^/api
是一个正则表达式,意思是匹配到这样的url会进行改写,将/api
替换成空白,也就是去掉了,例如: http://localhost:8080/api/login 请求转换后为 http://localhost:8080/login,而且被代理后实际请求地址为 http://localhost:8000/login。当然这个/api
可以是任意的,正则表达式也是任意的,这里只是举例。 那这样是不是前端每一次请求都要带上api
这个前缀,的确是要,但可以在自己main.js
文件中统一设定,添加以下配置:
axios.defaults.baseURL = '/api'
axios.defaults.headers.post['Content-Type'] = 'application/json;charset=UTF-8'
axios.defaults.headers.common['accessToken'] = 'FA4C308D5E8F6409E01344ADDAEB4C71'
当然前提是你用的是 axios ,设置请求头也要加上哦,
(虽然我也不知道为啥一定要加)。2.Vuex使用;
起初在创建项目的时候没有选择安装这个,但在实际操作中,由于组件之间也会有很多关联,所以没有vuex确实操作起来很不方便,因此我后来又重新安装了vuex模块,以下是在项目中的简单的使用:
//登录时在组件中调用mutations中setLogin方法,将token等信息保存在store中以供各组件使用
this.$store.commit('setLogin',data)
//由于组件刷新之后state中的值就会丢失,所以将一些必要信息保存在localStorage中
localStorage.setItem('user', JSON.stringify(state.user))
//这里就设定了刷新之后可以从localStorage中重新拿值
user: localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user')) : {
xxx: '',
xxx: '',
xxx: '',
}
//这里就是组件调用store中getters中的方法,获取state中各数据状态
this.userInfo = this.$store.getters.getUser
//这里就是getters中的getUser方法,返回state中的user对象
getters: {
getUser(state){
return state.user
}
},
这里踩了一个坑就是,如果一个变量是
null
,存入 localStorage 它就是字符串了,之前在项目中从localStorag取变量判断变量是否等于null,明明在后端拿到的是null,到了判断的时候缺不等于null了,找了好久的问题,原来是已经变成字符串了(哭
3.局部组件刷新
在进行某一操作之后,页面需要更新,这个时候可以采用的操作就是刷新页面,而vue刷新当前页面有挺多种方法,比如:
window.location.reload()
或
this.$router.go(0)
但是这两种方法是会出现一瞬间的白屏,体验不好,一种比较好的处理方式是在
app.vue
的<router-view></router-view>
加上v-if属性,在data里面加上isRouterAlive,当然这个属性名可以自己定义,默认值为true,methods里面加入一个刷新的方法,这个方法就是改变isRouterAlive值的,通过改变这个值,会使该元素重新被渲染以达到刷新的效果,data和刷新方法如下:
data() {
return {
isRouterAlive: true
}
},
methods:{
reload(){
this.isRouterAlive = false
this.$nextTick(function(){
this.isRouterAlive = true
})
},
}
最后,需要把这个函数 provide 出去:
provide () {
return{
reload: this.reload //调用刚刚写的刷新方法
}
},
以上都是对
app.vue
里面设置的,接下来是需要刷新的时候在需要的组件中调用,代码如下:
/* 首先注入这个函数*/
inject: ['reload']
/*在需要用到这个函数的地方去调用*/
refresh () {
this.reload()
}
4.父组件与子组件
父组件和子组件“通信”,刚开始不是很理解,不过在项目中用的地方还是有很多,现在已经熟练使用了,这里也做一个总结吧,当然前提是父组件注册并使用子组件啊(废话。
父组件向子组件传值
在父组件中通过
v-bind:
(可以直接简写:
)动态绑定一个自定义属性,子组件有一个```props````属性,在它里面写上刚刚的自定义属性名,就可以接收了,具体代码示例如下:
/* 这里是父组件绑定自定义属性 'user'* ,讲父组件的user对象传给子组件,这里我为了区分,把user改成了data */
<MyCard :user="data" v-if="login"></MyCard>
/* 这里是子组件 'props' 中,通过写上同名的 'user' */
props: ['user']
这种传值是单向的,也就是说只能父组件传个子组件,父组件数据更新子组件也能更新,而反过来不行,同时子组件
props
中的 user 子组件是不能改变的,只能是父组件更新改变,所以一般子组件通过watch
监听该属性,然后将数据更新到子组件data
中的某个变量,然后进行数据处理。子组件调用父组件方法
在子组件中使用
this.$emit('函数名','数据')
,父组件在子组件上加上@'函数名'='需要调用的父组件的函数'
,具体示例代码如下:
/* 子组件需要调用的地方 */
openL(msg){
this.$emit('openLogin',msg)
}
/* 父组件在子组件上监听这个事件 */
<Login @openLogin='openLogin'></Login>
/* 父组件中的openLogin方法 */
openLogin(msg){
this.$notify({
title: '成功',
message: msg,
type: 'success'
});
this.sendbar()
},
子组件向父组件传值
刚刚的子组件调用父组件方法中子组件使用
this.$emit('函数名','数据')
,即可向父组件传值。父组件调用子组件方法
在父组件使用子组件上使用
ref='子组件名'
属性,然后在需要调用子组件方法的地方,使用this.$refs.'子组件名'.'子组件的函数名'
,示例代码如下:
/* search即为子组件名 */
<Search ref='search'></Search>
/* 在需要调用的地方使用,当然这里也可以传参的 */
searchIn(){
this.$refs.search.searchIn()
},
/* 子组件不需要做配置,这里是子组件的searchIn方法 */
searchIn(){
this.search?this.move():this.unmove()
this.search = !this.search
},
当然父组件与子组件的通信方式不止我说的,其他的我没用过就不再写了。
使用第三方组件的坑
可费了我好多时间了
vue-cropper
这个是图片剪裁的,用户更改头像有用到这个,基础配置前面有开源链接有详细说,这里我主要说我遇到的一些问题。 第一是上传的文件
reader.readAsDataURL(file)
,报错说:
[Vue warn]: Error in v-on handler: "TypeError: Failed to execute 'readAsDataURL' on 'FileReader': parameter 1 is not of type 'Blob'."
根据报错提示,我们应该传入一个Blob对象。也就是说,我们这个file不是Blob对象,这个时候可以控制台输出一下这个文件,发现有个raw这个属性,就可以用
reader.readAsDataURL(file.raw)
就没有报错了。 然后在上传文件的时候由于不止是上传文件,还需要携带用户信息,这个时候可以用formData.append('uid',this.uid)
,把信息保存在formData中。后端直接可以通过@RequestParam注解就可以获得数据。vue-waterfall-easy
这个最主要就是图片的属性名必须要和它的一致,图片地址为src,链接为href,如果不一样数据就用不了
路由跳转
路由跳转页面不在顶部
从一个长页面跳到另一个路由后,页面的位置还停留在上个页面的位置,没有回到顶部,解决方式就是在
router.js
中添加如下代码:
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes,
/* 重点是下面这个函数 */
scrollBehavior() {
return {x: 0, y: 0}
}
})
路由二级页面显示空白
这个有可能是自己静态资源路径有问题,我的就是这样,另外还有就是要在
nginx
配置的时候需要加上try_files $uri $uri/ /index.html;
, 我因为这个路径问题打包了近十次才试出来正确的写法,同样是在vue.config.js
中进行配置:
module.exports = {
publicPath: '../',
}
这只是我的文件路径配置,我的dist文件夹下没有static文件夹,我看网上很多都是放在这个文件夹下的,以下是我的文件夹放置
路由导航守卫
不是全局的导航守卫,是某个路由的导航守卫,这个就在其路由配置里设置就好了,以下是我的:
{
path: '/admin',
name: 'Admin',
component: () => import('../views/admin/Admin.vue'),
beforeEnter: (to,from,next) => {
let user = JSON.parse(localStorage.getItem('user'))
/* 如果user不为空就不拦截,放行,为空就跳转到'/',即主页 */
if(user){
next()
}else{
next('/')
}
}
}
e.preventDefault()
在需要设置弹出层的时候,页面需要固定,就用到了
e.preventDefault()
,因为一直都是在PC端进行开发的也没有问题,但最后到了移动端,发现这样有个问题就是因为使用了这个页面再也不能滑动了。 解决方法就是...尽量别使用,通过设置overflow
为hidden,也能达到想要的效果。
Nginx 配置
首先是服务器安装nginx,安装好后需要注意的就是如果要配置htpps的话,就必须要有ssl模块,一般情况下自己安装的nginx都是不存在ssl模块的,先检查一下有没有,进入安装好后的nginx下的sbin目录,输入./nginx -V
,如下图:出现with-http_ssl_module
就是安装好的,没有就要自己安装。 安装好ssl模块后就是要为vue项目配置了,进入安装目录下的conf目录下,有一个nginx.conf的文件,我们就是要在这里面进行配置,cd进这个目录下,输入vim nginx.conf
,输入i进入编辑模式,里面的配置如下(当然前提是有ssl证书,下载好nginx服务器类型的ssl证书,里面有一个.pem
和.key
文件,上传到服务器记住存放目录)
server {
listen 443 ssl;
server_name kasuie.top;
ssl on;
ssl_certificate /usr/local/nginx/ssl/ssl.pem; //这里是ssl证书的pem文件路径
ssl_certificate_key /usr/local/nginx/ssl/ssl.key; //这里是ssl证书的key文件路径
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
#nstall openssl openssl-devel
#charset koi8-r;
#access_log logs/host.access.log main;
这里是配置自己vue项目的存放路径,
location / {
root /usr/local/webapp/dist;
try_files $uri $uri/ @router;
index index.html index.htm;
}
//防止二级路由刷新404
location @router {
rewrite ^.*$ /index.html last;
}
// 这里是nginx的配置跨域,由nginx进行代理,和vue里面的配置差不多
location /api/ {
rewrite_log on;
rewrite ^/api/(.*)$ /$1 break;
proxy_pass http://localhost:8000;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-real-ip $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
另外一个就是自己图片上传后,之前是springboot负责映射图片资源的,但现在用nginx后之前的映射好像都不能用了,于是找了一下,解决方法如下,也是在
nginx.conf
里进行配置:
// 由nginx进行映射,给/home/admin/user/avatars/ 起一个别名为/user/avatar/,即可通过该路径访问其目录下的文件
location /user/avatar/ {
alias /home/admin/user/avatars/;
index index.html index.htm;
}
还有配置服务器的
:80
端口转到:443
端口,也是在nginx.conf
里进行配置,当然需要注意的是,国内的话需要把对应的端口添加到安全组内,不然也不能访问:
server {
listen 80;
server_name kasuie.top;
rewrite ^ https://$http_host$request_uri? permanent;
}
到这里就算是配置好了,保存退出,上传好vue的dist项目文件,重新启动nginx服务器,就可以通过自己的域名访问项目了。
写在最后;
踩的坑远不止这些,由于之前没有记录,这里想到什么了就写了,还有一些不记得的或者是太小的问题就没再写了
(毕竟有的问题实在太迷惑了,说出了我都不好意思)还有现在好热啊!!!现在虽然是傍晚了,但是还是满头大汗的 如果有其他问题也可以问我哦 碎碎念模式开启了
- 地点: 家里
- 时间: 2020-07-31 19:35:20
- 心情: 不开心
- OS: WINDOWS 10
- 阿巴阿巴:
最近脱发好严重啊(危