基本配置
1.安装WebStorm。
2.安装NodeJS。
Node.js 安装配置
3.设置阿里镜像服务。
4.安装vue-cli组件。
命令行输入:npm install -g vue-cli
5.安装GIT客户端环境。
安装之后需要配置->高级系统设置->环境变量->系统变量->Path->新建->C:\Program Files\Git\bin
------------------------------------------------------------------------------------------------------------------------------
HelloWorld项目
项目写好之后,需要发布,发布的命令:
npm run build
注意dist目录下的index.html文件中的绝对路径和相对路径,应修改为:
myweb
即去掉static前面的"/"。将绝对路径改为相对路径。
或者修改config目录下的index.js文件中的build模块为:
build: {
// Template for index.html
index: path.resolve(__dirname, '../dist/index.html'),
// Paths
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
assetsPublicPath: '',
即将assetsPublicPath设置为空。
最后重新发布。
------------------------------------------------------------------------------------------------------------------------------
iview界面库
iView
使用 iview-loader 统一代码风格
View UI Loader
注意:要修改index.js文件中的
useEslint: false,
在main.js中引入ViewUI:
import ViewUI from 'view-design'
import 'view-design/dist/styles/iview.css'
Vue.use(ViewUI)
在iview界面库中寻找代码替换App.vue中的代码:
<style scoped>
.layout{
border: 1px solid #d7dde4;
background: #f5f7f9;
position: relative;
border-radius: 4px;
overflow: hidden;
}
.layout-logo{
width: 100px;
height: 30px;
background: #5b6270;
border-radius: 3px;
float: left;
position: relative;
top: 15px;
left: 20px;
}
.layout-nav{
width: 420px;
margin: 0 auto;
margin-right: 20px;
}
</style>
<template>
<div class="layout">
<Layout>
<Header>
<Menu mode="horizontal" theme="dark" active-name="1">
<div class="layout-logo"></div>
<div class="layout-nav">
<MenuItem name="1">
<Icon type="ios-navigate"></Icon>
Item 1
</MenuItem>
<MenuItem name="2">
<Icon type="ios-keypad"></Icon>
Item 2
</MenuItem>
<MenuItem name="3">
<Icon type="ios-analytics"></Icon>
Item 3
</MenuItem>
<MenuItem name="4">
<Icon type="ios-paper"></Icon>
Item 4
</MenuItem>
</div>
</Menu>
</Header>
<Layout>
<Sider hide-trigger :style="{background: '#fff'}">
<Menu active-name="1-2" theme="light" width="auto" :open-names="['1']">
<Submenu name="1">
<template slot="title">
<Icon type="ios-navigate"></Icon>
Item 1
</template>
<MenuItem name="1-1">Option 1</MenuItem>
<MenuItem name="1-2">Option 2</MenuItem>
<MenuItem name="1-3">Option 3</MenuItem>
</Submenu>
<Submenu name="2">
<template slot="title">
<Icon type="ios-keypad"></Icon>
Item 2
</template>
<MenuItem name="2-1">Option 1</MenuItem>
<MenuItem name="2-2">Option 2</MenuItem>
</Submenu>
<Submenu name="3">
<template slot="title">
<Icon type="ios-analytics"></Icon>
Item 3
</template>
<MenuItem name="3-1">Option 1</MenuItem>
<MenuItem name="3-2">Option 2</MenuItem>
</Submenu>
</Menu>
</Sider>
<Layout :style="{padding: '0 24px 24px'}">
<Breadcrumb :style="{margin: '24px 0'}">
<BreadcrumbItem>Home</BreadcrumbItem>
<BreadcrumbItem>Components</BreadcrumbItem>
<BreadcrumbItem>Layout</BreadcrumbItem>
</Breadcrumb>
<Content :style="{padding: '24px', minHeight: '280px', background: '#fff'}">
<div>
Props:
<Input prefix="ios-contact" placeholder="请输入姓名" style="width: auto" />
<Input suffix="ios-search" placeholder="请输入内容" style="width: auto" />
<Switch v-model="switch1" @on-change="change" />
<Rate v-model="fenshu" />
</div>
<div style="margin-top: 6px">
Slots:
<Input placeholder="请输入姓名" style="width: auto">
<Icon type="ios-contact" slot="prefix" />
</Input>
<Input placeholder="请输入内容" style="width: auto">
<Icon type="ios-search" slot="suffix" />
</Input>
</div>
</Content>
</Layout>
</Layout>
</Layout>
</div>
</template>
<script>
export default {
data () {
return {
switch1: false,
fenshu: 3
}
},
methods: {
change (status) {
this.$Message.info('开关状态:' + status);
}
}
}
</script>
------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
编写服务端REST数据接口
在pom.xml文件中引入依赖:
com.itshidu.web
web-tools
1.0
在Hello.java中添加代码:
package com.itshidu.demo.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.itshidu.web.tools.Result;
@RestController
public class Hello {
@RequestMapping("/api/score")
public Object score() {
return Result.of(1).put("xixi", "西西"); // {code:1,"name":"西西"}
}
}
------------------------------------------------------------------------------------------------------------------------------
使用 axios 进行AJAX功能开发
安装axios以及示例
------------------------------------------------------------------------------------------------------------------------------
用VUE跨域调用服务端的数据
在Hello.java中添加SpringBoot跨域注解:
package com.itshidu.demo.controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.itshidu.web.tools.Result;
@CrossOrigin(origins = "*") // SpringBoot跨域注解
@RestController
public class Hello {
@RequestMapping("/api/score")
public Object score() {
return Result.of(1).put("xixi", "西西"); // {code:1,"name":"西西"}
}
}
在main.js文件中添加代码:
import axios from 'axios'
Vue.prototype.axios = axios
在App.vue文件中添加代码:
export default {
data () {
return {
switch1: false,
fenshu: 3
}
},
methods: {
change (status) {
this.$Message.info('开关状态:' + status)
}
},
created () {
var url = 'http://localhost:8390/api/score'
this.axios.get(url).then(response => {
this.fenshu = response.data.code
})
}
}
------------------------------------------------------------------------------------------------------------------------------
自定义组件设置宽度为1200像素
在components目录下创建一个Vue组件,取名为:MyPanel,添加代码:
<template>
<div class = "my-panel"><slot></slot></div>
</template>
<script>
/* 此组件用于设置宽度为1000px */
export default {
name: 'MyPanel'
}
</script>
<style scoped>
.my-panel{
width: 1000px;
margin-right: auto;
margin-left: auto;
}
</style>
------------------------------------------------------------------------------------------------------------------------------
路由组件
Vue Router
#作为路由的开始。
先来看一下router文件夹下的index.js文件(整个路由都在这里定义):
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
component: HelloWorld
}
]
})
在App.vue中添加代码:
<router-view></router-view>
------------------------------------------------------------------------------------------------------------------------------
点击导航菜单然后路由到对应页面
在components目录下创建一个Index.vue组件,添加代码:
<style scoped>
.layout{
border: 1px solid #d7dde4;
background: #f5f7f9;
position: relative;
border-radius: 4px;
overflow: hidden;
}
.layout-logo{
width: 100px;
height: 30px;
background: #5b6270;
border-radius: 3px;
float: left;
position: relative;
top: 15px;
left: 20px;
}
.layout-nav{
width: 420px;
margin: 0 auto;
margin-right: 20px;
}
</style>
<template>
<div class="layout">
<Layout>
<Header>
<Menu mode="horizontal" theme="dark" active-name="1">
<div class="layout-logo"></div>
<div class="layout-nav">
<MenuItem name="1">
<Icon type="ios-navigate"></Icon>
Item 1
</MenuItem>
<MenuItem name="2">
<Icon type="ios-keypad"></Icon>
Item 2
</MenuItem>
<MenuItem name="3">
<Icon type="ios-analytics"></Icon>
Item 3
</MenuItem>
<MenuItem name="4">
<Icon type="ios-paper"></Icon>
Item 4
</MenuItem>
</div>
</Menu>
</Header>
<Layout>
<Sider hide-trigger :style="{background: '#fff'}">
<Menu active-name="1-2" theme="light" width="auto" :open-names="['1']">
<Submenu name="1">
<template slot="title">
<Icon type="ios-navigate"></Icon>
Item 1
</template>
<MenuItem name="1-1">Option 1</MenuItem>
<MenuItem name="1-2">Option 2</MenuItem>
<MenuItem name="1-3">Option 3</MenuItem>
</Submenu>
<Submenu name="2">
<template slot="title">
<Icon type="ios-keypad"></Icon>
Item 2
</template>
<MenuItem name="2-1">Option 1</MenuItem>
<MenuItem name="2-2">Option 2</MenuItem>
</Submenu>
<Submenu name="3">
<template slot="title">
<Icon type="ios-analytics"></Icon>
Item 3
</template>
<MenuItem name="3-1">Option 1</MenuItem>
<MenuItem name="3-2">Option 2</MenuItem>
</Submenu>
</Menu>
</Sider>
<Layout :style="{padding: '0 24px 24px'}">
<Breadcrumb :style="{margin: '24px 0'}">
<BreadcrumbItem>Home</BreadcrumbItem>
<BreadcrumbItem>Components</BreadcrumbItem>
<BreadcrumbItem>Layout</BreadcrumbItem>
</Breadcrumb>
<Content :style="{padding: '24px', minHeight: '280px', background: '#fff'}">
<div>
Props:
<Input prefix="ios-contact" placeholder="请输入姓名" style="width: auto" />
<Input suffix="ios-search" placeholder="请输入内容" style="width: auto" />
<Switch v-model="switch1" @on-change="change" />
<Rate v-model="fenshu" />
</div>
<div style="margin-top: 6px">
Slots:
<Input placeholder="请输入姓名" style="width: auto">
<Icon type="ios-contact" slot="prefix" />
</Input>
<Input placeholder="请输入内容" style="width: auto">
<Icon type="ios-search" slot="suffix" />
</Input>
</div>
</Content>
</Layout>
</Layout>
</Layout>
</div>
</template>
<script>
export default {
data () {
return {
switch1: false,
fenshu: 3
}
},
methods: {
change (status) {
this.$Message.info('开关状态:' + status)
}
},
created () {
var url = 'http://localhost:8390/api/score'
this.axios.get(url).then(response => {
this.fenshu = response.data.code
})
}
}
</script>
修改App.vue中的代码为:
<style scoped>
</style>
<template>
<router-view></router-view>
</template>
<script>
export default {
}
</script>
跳转方法一:修改Index.vue文件中的代码:
<MenuItem name="1" @click.native="urlto">
<Icon type="ios-navigate"></Icon>
欢迎界面
</MenuItem>
methods: {
change (status) {
this.$Message.info('开关状态:' + status)
},
urlto () {
this.$router.push('/hello')
}
},
运行结果:
跳转方法二:使用iView中的Menu导航菜单自身的事件(注意@on-select要写在中)。
修改Index.vue文件中的代码:
<Menu active-name="1-2" theme="light" width="auto" :open-names="['1']" @on-select="selectMenu">
<Submenu name="1">
<template slot="title">
<Icon type="ios-navigate"></Icon>
点我
</template>
<MenuItem name="/hello" >我也是欢迎菜单</MenuItem>
<MenuItem name="1-2">Option 2</MenuItem>
<MenuItem name="1-3">Option 3</MenuItem>
methods: {
change (status) {
this.$Message.info('开关状态:' + status)
},
urlto () {
this.$router.push('/hello')
},
selectMenu (name) {
this.$router.push(name)
}
},
运行结果:
------------------------------------------------------------------------------------------------------------------------------
编写service脚本统一处理数据请求
在src目录下创建一个utils目录,创建一个service.js文件,添加代码:
/*
*数据服务接口
**/
import Vue from 'vue'
import axios from 'axios'
const star = function (calback) {
axios.get('http://localhost:8390/api/score').then(response => {
calback(response.data.code)
})
}
export {
star
}
修改Index.vue中的代码:
import {star} from '@/utils/service'
created () {
star(code => {
this.fenshu = code
})
}
------------------------------------------------------------------------------------------------------------------------------
把获取名字的接口也放到service中
1.创建获取名字的接口
const getDemoName = function (value) {
axios.get('http://localhost:8390/api/value').then(response => {
value(response.data.name)
})
}
2.映射接口
export {
getDemoName
}
3.引用接口
import {getDemoName} from '@/utils/service'
4.使用
export default {
data () {
return {
value: ''
}
},
created () {
getDemoName(value => {
this.value = value
})
}
}
<Input prefix="ios-contact" placeholder="请输入姓名" v-model="value" style="width: auto" />
后台代码:
@RequestMapping("/api/value")
public Object value() {
return Result.of().put("name", "西西");
}
------------------------------------------------------------------------------------------------------------------------------
提取Header组件
我们以后会开发很多页面,并且Header对于很多页面都是通用的,所以我们可以单独把Header提取出来。
在components目录下创建一个MyHeader.vue组件,添加代码:
<template>
<Header>
<MyPanel>
<Menu mode="horizontal" theme="dark" active-name="1" @on-select="selectMenu">
<div class="layout-logo"></div>
<div class="layout-nav">
<MenuItem name="1" @click.native="urlto">
<Icon type="ios-navigate"></Icon>
欢迎界面
</MenuItem>
<MenuItem name="/hello" >
<Icon type="ios-keypad"></Icon>
Item2
</MenuItem>
<MenuItem name="/about">
<Icon type="ios-analytics"></Icon>
关于
</MenuItem>
<MenuItem name="4">
<Icon type="ios-paper"></Icon>
Item 4
</MenuItem>
</div>
</Menu>
</MyPanel>
</Header>
</template>
<script>
import MyPanel from '@/components/MyPanel'
export default {
name: 'MyHeader',
components: {MyPanel},
methods: {
selectMenu (name) {
this.$router.push(name)
},
urlto () {
this.$router.push('/hello')
}}
}
</script>
<style scoped>
.layout-logo{
width: 100px;
height: 30px;
background: #5b6270;
border-radius: 3px;
float: left;
position: relative;
top: 15px;
left: 20px;
}
.layout-nav{
width: 420px;
margin: 0 auto;
margin-right: 20px;
}
</style>
在其他组件中直接使用
即可。
------------------------------------------------------------------------------------------------------------------------------
创建About视图
在components目录下创建一个About.vue组件,添加代码:
<template>
<div>
<MyHeader></MyHeader>
<Circle :percent="80">
<span class="demo-Circle-inner" style="font-size:24px">80%</span>
</Circle>
<Circle :percent="100" stroke-color="#5cb85c">
<Icon type="ios-checkmark" size="60" style="color:#5cb85c"></Icon>
</Circle>
<Circle :percent="35" stroke-color="#ff5500">
<span class="demo-Circle-inner">
<Icon type="ios-close" size="50" style="color:#f39eff"></Icon>
</span>
</Circle>
</div>
</template>
<script>
import MyHeader from '@/components/MyHeader'
export default {
name: 'About',
components: {MyHeader}
}
</script>
<style scoped>
</style>
在index.js文件中添加路由:
import About from '@/components/About'
export default new Router({
routes: [
{
path: '/about',
name: 'About',
component: About
}
]
})
------------------------------------------------------------------------------------------------------------------------------
编写Layout组件简化页面结构
在components目录下创建一个MyLayout.vue组件,添加代码:
<style scoped>
.layout{
border: 1px solid #d7dde4;
background: #f5f7f9;
position: relative;
border-radius: 4px;
overflow: hidden;
}
.layout-footer-center{
text-align: center;
}
</style>
<template>
<div class="layout">
<Layout>
<MyHeader></MyHeader>
<Content :style="{padding: '0 50px'}" class="my-panel">
<MyPanel>
<slot></slot>
</MyPanel>
</Content>
<Footer class="layout-footer-center">2011-2016 © TalkingData</Footer>
</Layout>
</div>
</template>
<script>
import MyHeader from '@/components/MyHeader'
import MyPanel from '@/components/MyPanel'
export default {
components: {MyPanel, MyHeader}
}
</script>
修改About.vue组件的代码:
<template>
<div>
<MyLayout>
<Circle :percent="80">
<span class="demo-Circle-inner" style="font-size:24px">80%</span>
</Circle>
<Circle :percent="100" stroke-color="#5cb85c">
<Icon type="ios-checkmark" size="60" style="color:#5cb85c"></Icon>
</Circle>
<Circle :percent="35" stroke-color="#ff5500">
<span class="demo-Circle-inner">
<Icon type="ios-close" size="50" style="color:#f39eff"></Icon>
</span>
</Circle>
</MyLayout>
</div>
</template>
<script>
import MyHeader from '@/components/MyHeader'
import MyLayout from '@/components/MyLayout'
export default {
name: 'About',
components: {MyLayout, MyHeader}
}
</script>
<style scoped>
</style>
------------------------------------------------------------------------------------------------------------------------------
添加注册登录按钮以及相关的页面路由
修改MyHeader.vue组件中的代码:
<template>
<Header>
<MyPanel>
<Menu mode="horizontal" theme="dark" active-name="1" @on-select="routerTo">
<div class="layout-logo"></div>
<div class="layout-nav">
<MenuItem name=" " >
<Button type="info" @click="routerTo('/login')">登录</Button>
<Button type="success" @click="routerTo('/register')">注册</Button>
</MenuItem>
<MenuItem name="/hello" >
<Icon type="ios-keypad"></Icon>
Item2
</MenuItem>
<MenuItem name="/about">
<Icon type="ios-analytics"></Icon>
关于
</MenuItem>
<MenuItem name="4">
<Icon type="ios-paper"></Icon>
Item 4
</MenuItem>
</div>
</Menu>
</MyPanel>
</Header>
</template>
<script>
import MyPanel from '@/components/MyPanel'
export default {
name: 'MyHeader',
components: {MyPanel},
methods: {
routerTo (name) {
this.$router.push(name)
}
}
}
</script>
<style scoped>
.layout-logo{
width: 100px;
height: 30px;
background: #5b6270;
border-radius: 3px;
float: left;
position: relative;
top: 15px;
left: 20px;
}
.layout-nav{
width: 420px;
margin: 0 auto;
margin-right: 20px;
}
</style>
在src下创建一个views视图目录,在其下创建一个Login.vue视图和Register.vue视图。
Login.vue代码:
<template>
<MyLayout>登录页面</MyLayout>
</template>
<script>
import MyLayout from '@/components/MyLayout'
export default {
name: 'Login',
components: {MyLayout}
}
</script>
<style scoped>
</style>
Register.vue代码:
<template>
<MyLayout>注册页面</MyLayout>
</template>
<script>
import MyLayout from '@/components/MyLayout'
export default {
name: 'Register',
components: {MyLayout}
}
</script>
<style scoped>
</style>
在index.js中添加代码:
import Login from '@/views/Login'
import Register from '@/views/Register'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/login',
name: Login,
component: Login
},
{
path: '/register',
name: Register,
component: Register
}
]
})
------------------------------------------------------------------------------------------------------------------------------
路由的懒加载
延迟加载
将
import HelloWorld from '@/components/HelloWorld'
import Login from '@/views/Login'
import Register from '@/views/Register'
换成
const HelloWorld = () => { import('@/components/HelloWorld') }
const Login = () => { import(/* webpackChunkName: "group-user" */ '@/views/Login') }
const Register = () => { import(/* webpackChunkName: "group-user" */ '@/views/Register') }
------------------------------------------------------------------------------------------------------------------------------
Vuex的安装及初始化创建
官方文档
1.安装:
npm install vuex --save
2.在main.js中引入Vuex:
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})
new Vue({
store //全局对象
})
------------------------------------------------------------------------------------------------------------------------------
Vuex的核心作用及代码演示
可以将main.js中的
components: { App },
template: ' ',
替换为
render: h => h(App)
在Index.vue组件下添加代码:
<Button type = "info" @click="add">点我加1</Button>
<h1>当前数值:{{this.$store.state.count}}</h1>
methods: {
add () {
this.$store.commit('increment')
}
},
------------------------------------------------------------------------------------------------------------------------------
Vuex中的action动作
action和mutation类似,不同的是,action提交的是mutation,而不是直接变更状态。而且action支持异步,mutation必须同步执行。
mutation是最基本的操作,类似增删改查,是对数据的直接操作,而action则代表了具体的业务流程。
在main.js中添加代码:
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
waitIncrement (context, n) {
setTimeout(() => {
for (var i = 0; i < n; i++) {
context.commit('increment')
}
}, 2000)
}
}
})
修改Index.vue中代码:
add () {
//this.$store.commit('increment')
this.$store.dispatch('waitIncrement', 6)
}
------------------------------------------------------------------------------------------------------------------------------
Vuex的项目结构及模块
项目结构
在src下创建一个store目录,创建一个index.js文件,添加代码:
import Vue from 'vue'
// Vuex状态管理
import Vuex from 'vuex'
Vue.use(Vuex)
export const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
waitIncrement (context, n) {
setTimeout(() => {
for (var i = 0; i < n; i++) {
context.commit('increment')
}
}, 2000)
}
}
})
在main.js文件中引入store:
// 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 ViewUI from 'view-design'
import 'view-design/dist/styles/iview.css'
import axios from 'axios'
import {store} from '@/store/index'
Vue.prototype.axios = axios
Vue.use(ViewUI)
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
render: h => h(App)
})
------------------------------------------------------------------------------------------------------------------------------
Vuex的模块化代码编写
在store目录下创建一个modules目录,创建一个hello.js文件,添加代码:
export const hello = {
state: { count: 0 },
mutations: {
inc (state) {
state.count++
}
},
getters: {
doubleCount (state) {
return state.count * 2
}
},
actions: {
helloIncrement ({ state, commit, rootState }) {
alert(rootState.count)
commit('inc')// 此处的mutation名称不能与主模块的mutation名称相同,否则会同时调用主模块的mutation
}
}
}
在主模块store目录下的index.js文件中添加代码:
export const store = new Vuex.Store({
state: {
count: 6
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
waitIncrement (context, n) {
setTimeout(() => {
for (var i = 0; i < n; i++) {
context.commit('increment')
}
}, 2000)
}
},
modules: {
hello: hello
}
})
在Index.vue文件中添加代码:
<Button type = "info" @click="add">点我加1</Button>
<h1>主模块数值:{{this.$store.state.count}}</h1>
<h1>子模块数值:{{this.$store.state.hello.count}}</h1>
methods: {
add () {
this.$store.dispatch('helloIncrement')
}
},
------------------------------------------------------------------------------------------------------------------------------
SpringBoot中的HttpSession有跨域问题
服务器记不住你是谁
跨域,指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器施加的安全限制。
所谓同源是指:域名,协议,端口均相同,不明白没关系,举个栗子:
http://www.123.com/index.html 调用 http://www.123.com/server.php (非跨域)
http://www.123.com/index.html 调用 http://www.456.com/server.php (主域名不同:123/456,跨域)
http://abc.123.com/index.html 调用 http://def.123.com/server.php (子域名不同:abc/def,跨域)
http://www.123.com:8080/index.html 调用 http://www.123.com:8081/server.php (端口不同:8080/8081,跨域)
http://www.123.com/index.html 调用 https://www.123.com/server.php (协议不同:http/https,跨域)
请注意:localhost和127.0.0.1虽然都指向本机,但也属于跨域。
------------------------------------------------------------------------------------------------------------------------------
后端使用拦截器支持跨域
创建一个interceptor包,创建一个AccessInterceptor类,添加代码:
package com.itshidu.demo.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
public class AccessInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object object) throws Exception {
//response.setCharacterEncoding("UTF-8");
//response.setContentType("application/json; charset=utf-8");
response.setHeader("Access-Control-Allow-Credentials","true");
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH");
return true;
}
}
写完interceptor之后它是不生效的,还需要配置一下。
创建一个config包,创一个MvcConfig类,添加代码:
package com.itshidu.demo.config;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.itshidu.demo.interceptor.AccessInterceptor;
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AccessInterceptor()).addPathPatterns("/**");// /**代表可以访问所有资源
}
}
------------------------------------------------------------------------------------------------------------------------------
后端使用CrossOrigin注解支持跨域
在需要跨域的类或方法前添加注解:
@CrossOrigin(origins = "*")
------------------------------------------------------------------------------------------------------------------------------
前端发送ajax请求时携带cookie
axios.defaults.withCredentials=true; //每次ajax请求时携带cookie
------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
登录功能的JS前端代码
在service.js文件中添加代码:
const login = (username, password, callback) => {
let data = new FormData()
data.append('username', username)
data.append('password', password)
axios.post('http://localhost:8390/api/login', data).then(res => {
callback(res.data)
})
}
在Login.vue中添加代码:
<template>
<MyLayout>
<Card>
<h1>用户登录</h1>
<input v-model="username" placeholder="请输入用户名:" style="width: 300px">
<input v-model="password" placeholder="请输入密码:" style="width: 300px">
<Button type="info" @click="login">登录</Button>
</Card>
</MyLayout>
</template>
<script>
import MyLayout from '@/components/MyLayout'
import {login} from '@/utils/service'
export default {
name: 'Login',
components: {MyLayout},
data () {
return {
username: ' ',
password: ' '
}
},
methods: {
login () {
login(this.username, this.password, data => {
console.log(data)
})
}
}
}
</script>
<style scoped>
</style>
------------------------------------------------------------------------------------------------------------------------------
登录功能的Java后端代码
在Hello.java文件中添加代码:
@RequestMapping("/api/login")
public Object login(String username, String password) {
System.out.println(username);
System.out.println(password);
if (username.equals("xixi") && password.equals(" 123")){
return Result.of(100,"登陆成功");
}
return Result.of(101,"登陆失败");
}
------------------------------------------------------------------------------------------------------------------------------
三种POST传值与接收的方法
axios.post发送的参数:
1.JSON类型:将会以Payload形式发送,服务端需要使用@RequestBody接收为Map,Content-Type:application/json
2.FormDate类型:则Content-Type:multipart/form-data,可以上传表单也可以上传文件,不会编码处理。
3.QueryString:则Content-Type:application/x-www-form-urlencoded,标准的表单提交,会编码处理。
------------------------------------------------------------------------------------------------------------------------------
使用QS组件进行QueryString的序列化与反序列化
这个插件可以进行JSON数据和查询字符串之间的转换,能够实现序列化和反序列化。
安装命令:
npm i qs --save
导入qs:
import qs from 'qs'
qs插件中的两个主要方法:
qs.parse()
qs.stringify()
qs.parse()是将URL解析成对象的形式
qs.stringify()是将对象序列化成URL的形式,以&进行拼接
修改login.vue中的代码为:
const login = (username, password, callback) => {
let data = qs.stringify({
username: username,
password: password
})
axios.post('http://localhost:8390/api/login', data).then(res => {
callback(res.data)
})
}
------------------------------------------------------------------------------------------------------------------------------
Springboot中使用DataJPA框架
让Springboot连接mysql。
在pom.xml文件中引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
添加配置信息:
在application.yml文件中添加代码:
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/me_db?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&serverTimezone=UTC
username: root
password: 123
jpa:
show-sql: true
hibernate:
ddl-auto: update
dialect: org.hibernate.dialect.MySQL5Dialect
创建一个entity包,创建一个User类,添加代码:
package com.itshidu.demo.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
/**
* @Entity:对实体注释,任何Hibernate映射对象都要有这个注解。
* @Table:声明此对象映射到数据库的数据表,通过它可以为实体指定表(talbe),目录(Catalog)和数据库(schema)的名字。该注释不是必须的,如果没有则系统使用默认值(实体的短类名)。
**/
@Entity
@Table(name="vue_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;//这两个注解表示Id主键自增
private String username;
private String password;
private String nickname;
private int age;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
}
------------------------------------------------------------------------------------------------------------------------------
使用Lombok让代码更优雅
Lombok能以简单的注解形式来简化java代码,提高开发人员的开发效率。
例如开发中经常需要写的javabean,都需要花时间去添加相应的getter/setter,也许还要去写构造器、equals等方法,而且需要维护,当属性多时会出现大量的getter/setter方法,这些显得很冗长也没有太多技术含量,一旦修改属性,就容易出现忘记修改对应方法的失误。
Lombok能通过注解的方式,在编译时自动为属性生成构造器、getter/setter、equals、hashcode、toString方法。神奇的是在源码中没有getter和setter方法,但是在编译生成的字节码文件中有getter和setter方法。这样就省去了手动重建这些代码的麻烦,使代码看起来更简洁些。
@Data注解在类上,会为类的所有属性自动生成setter/getter、equals、canEqual、hashCode、toString方法,如为final属性,则不会为该属性生成setter方法。
如果觉得@Data太过残暴(因为@Data集合了@ToString、@EqualsAndHashCode、@Getter/@Setter、@RequiredArgsConstructor的所有特性)不够精细,可以使用@Getter/@Setter注解,此注解在属性上,可以为相应的属性自动生成Getter/Setter方法。
在pom.xml文件中添加依赖:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
<scope>provided</scope>
</dependency>
找到lombok.jar文件,双击安装。
注意可能遇到eclipse无法打开的问题,或者注解无效的问题,在eclipse.ini文件中添加代码即可解决:
-Xbootclasspath/a:lombok.jar
-vmargs -javaagent:lombok.jar
-javaagent:lombok.jar
修改User实体类中的代码为:
package com.itshidu.demo.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.Data;
/**
* @Entity:对实体注释,任何Hibernate映射对象都要有这个注解。
* @Table:声明此对象映射到数据库的数据表,通过它可以为实体指定表(talbe),目录(Catalog)和数据库(schema)的名字。该注释不是必须的,如果没有则系统使用默认值(实体的短类名)。
* @Data:注解在类上,会为类的所有属性自动生成setter/getter、equals、canEqual、hashCode、toString方法,如为final属性,则不会为该属性生成setter方法。
**/
@Data
@Entity
@Table(name="vue_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;//这两个注解表示Id主键自增
private String username;
private String password;
private String nickname;
private int age;
}
更多关于lombok的使用参考这里
------------------------------------------------------------------------------------------------------------------------------
使用DataJPA实现真正的登录查询
创建一个dao包,创建一个UserDao接口(所有的JPAdao都会被自动加载),添加代码:
package com.itshidu.demo.dao;
import org.springframework.data.jpa.repository.JpaRepository;
import com.itshidu.demo.entity.User;
public interface UserDao extends JpaRepository<User, Long>{
User findByUsername(String username);
}
在Hello.java中添加代码:
@Autowired UserDao userDao;
修改登录方法为:
@RequestMapping("/api/login")
public Object login(String username, String password) {
User user = userDao.findByUsername(username);
if (user == null) {
return Result.of(102, "用户不存在");
}
if (!user.getPassword().equals(password)) {
return Result.of(103, "密码不正确");
}
return Result.of(100, "登录成功");
}
修改前端中Login.vue中的代码为:
methods: {
login () {
login(this.username, this.password, data => {
console.log(data)
if (data.code == 100) {
this.$Message.success('登录成功')
} else {
this.$Message.warning(data.msg)
}
})
}
}
运行结果:
------------------------------------------------------------------------------------------------------------------------------
服务端和客户端存储登录信息
在User实体类中的password属性上添加注解(密码不会传输到浏览器上):
@JsonIgnore
private String password;
修改Hello.java中的登录方法:
@RequestMapping("/api/login")
public Object login(String username, String password,HttpSession session) {
User user = userDao.findByUsername(username);
if (user == null) {
return Result.of(102, "用户不存在");
}
if (!user.getPassword().equals(password)) {
return Result.of(103, "密码不正确");
}
session.setAttribute("loginUser", user);
return Result.of(100, "登录成功").put("user", user);
}
修改login.vue中的代码:
methods: {
login () {
login(this.username, this.password, data => {
console.log(data)
if (data.code === 100) {
this.$Message.success('登录成功')
this.$Message.success(data.user.nickname)
window.sessionStorage.setItem('user', JSON.stringify(data.user))
} else {
this.$Message.warning(data.msg)
}
})
}
}
------------------------------------------------------------------------------------------------------------------------------
使用VUEX处理全局用户信息
在store目录下的index.js文件中添加代码:
state: {
count: 6,
user: JSON.parse(window.sessionStorage.getItem('user'))
},
mutations: {
setUser (state, user) {
state.user = user
},
在Login.vue中添加代码:
this.$store.commit('setUser', data.user)
在MyHeader.vue中添加代码:
<MenuItem name=" " v-if="this.$store.state.user==null">
<Button type="info" @click="routerTo('/login')">登录</Button>
<Button type="success" @click="routerTo('/register')">注册</Button>
</MenuItem>
<Submenu name="" v-if="this.$store.state.user!=null">
<template slot="title">
<Icon type="ios-person" />
{{this.$store.state.user.nickname}}
</template>
<MenuGroup title="使用">
<MenuItem name="3-1">个人中心</MenuItem>
<MenuItem name="3-2">注销</MenuItem>
</MenuGroup>
</Submenu>
------------------------------------------------------------------------------------------------------------------------------
给路由跳转加上进度条
在main.js文件中添加代码:
import ViewUI from 'view-design';
Vue.use(ViewUI);
router.beforeEach((to, from, next) => {
ViewUI.LoadingBar.start();
next();
});
router.afterEach(route => {
ViewUI.LoadingBar.finish();
});
------------------------------------------------------------------------------------------------------------------------------
实现用户注销功能
修改MyHeader.vue中的代码为:
<MenuItem name="" @click.native="logout">注销</MenuItem>
import {logout} from '@/utils/service'
methods: {
logout () {
logout(data => {
console.log(data)
if (data.code === 100) {
window.sessionStorage.clear()// 客户端数据清理
this.$store.commit('setUser', null)// vuex数据清理
}
})
}
}
在service.js文件中添加代码:
const logout = (callback) => {
axios.get('http://localhost:8390/api/logout').then(res => {
callback(res.data)
})
}
export {
logout
}
在Hello.java文件中添加方法:
@RequestMapping("/api/logout")
public Object logout(HttpSession session) {
session.invalidate(); //废弃当前session
return Result.of(100, "注销成功");//服务端数据清理
}
------------------------------------------------------------------------------------------------------------------------------
换一个漂亮一些的登录页面
安装:less-loader
npm install less-loader --save -dev
安装:less
npm install less --save -dev
在assets目录下创建一个images目录,放入login-bg.jpg图片。
在view目录下创建一个login文件夹。
创建一个login.vue文件,添加代码:
<style lang="less">
@import './login.less';
</style>
<template>
<div class="login">
<div class="login-con">
<Card icon="log-in" title="欢迎登录" :bordered="false">
<div class="form-con">
<login-form @on-success-valid="handleSubmit"></login-form>
<p class="login-tip">输入任意用户名和密码即可</p>
</div>
</Card>
</div>
</div>
</template>
<script>
import LoginForm from '@/components/login-form'
import { mapActions } from 'vuex'
export default {
components: {
LoginForm
},
methods: {
...mapActions([
'handleLogin',
'getUserInfo'
]),
handleSubmit ({ userName, password }) {
this.handleLogin({ userName, password }).then(res => {
this.getUserInfo().then(res => {
this.$router.push({
name: this.$config.homeName
})
})
})
}
}
}
</script>
<style>
</style>
创建一个login.less文件,添加代码:
.login{
width: 100%;
height: 100%;
background-image: url('../../assets/images/login-bg.jpg');
background-size: cover;
background-position: center;
position: relative;
&-con{
position: absolute;
right: 160px;
top: 50%;
transform: translateY(-60%);
width: 300px;
&-header{
font-size: 16px;
font-weight: 300;
text-align: center;
padding: 30px 0;
}
.form-con{
padding: 10px 0 0;
}
.login-tip{
font-size: 10px;
text-align: center;
color: #c3c3c3;
}
}
}
html,body{
height: 100%;
}
在components目录下创建一个login-form.vue文件,添加代码:
<template>
<Form ref="loginForm" :model="form" :rules="rules" @keydown.enter.native="handleSubmit">
<FormItem prop="userName">
<Input v-model="form.userName" placeholder="请输入用户名">
<span slot="prepend">
<Icon :size="16" type="ios-person"></Icon>
</span>
</Input>
</FormItem>
<FormItem prop="password">
<Input type="password" v-model="form.password" placeholder="请输入密码">
<span slot="prepend">
<Icon :size="14" type="md-lock"></Icon>
</span>
</Input>
</FormItem>
<FormItem>
<Button @click="handleSubmit" type="primary" long>登录</Button>
</FormItem>
</Form>
</template>
<script>
export default {
name: 'LoginForm',
props: {
userNameRules: {
type: Array,
default: () => {
return [
{ required: true, message: '账号不能为空', trigger: 'blur' }
]
}
},
passwordRules: {
type: Array,
default: () => {
return [
{ required: true, message: '密码不能为空', trigger: 'blur' }
]
}
}
},
data () {
return {
form: {
userName: 'super_admin',
password: ''
}
}
},
computed: {
rules () {
return {
userName: this.userNameRules,
password: this.passwordRules
}
}
},
methods: {
handleSubmit () {
this.$refs.loginForm.validate((valid) => {
if (valid) {
this.$emit('on-success-valid', {
userName: this.form.userName,
password: this.form.password
})
}
})
}
}
}
</script>
修改router目录中的index.js文件(修改路由):
import Login from '@/views/login/login'
------------------------------------------------------------------------------------------------------------------------------
记住我7天功能
下载vue-cookies:
npm install vue-cookies --save
在login-form.vue文件中添加代码:
<form-item>
记住我7天
<Switch v-model='switchValue' @on-change="changes"></Switch>
</form-item>
data () {
return {
form: {
userName: 'super_admin',
password: '',
switchValue: false
}
}
},
methods: {
handleSubmit () {
this.$refs.loginForm.validate((valid) => {
if (valid) {
this.$emit('on-success-valid', {
userName: this.form.userName,
password: this.form.password,
rememberMe: this.switchValue
})
}
})
},
changes (status) {
this.$Message.info('开关状态' + status)
}
}
在main.js中添加代码(beforeEach之前):
import VueCookies from 'vue-cookies'
import {login} from '@/utils/service'
Vue.use(VueCookies)
Vue.use(ViewUI)
if (!store.state.user) {
let rememberMe = VueCookies.get('rememberMe')
if (rememberMe) {
// 利用这个登录信息去登录
login(rememberMe.username, rememberMe.password, data => {
if (data.code === 100) {
ViewUI.Message.success('自动登录成功')
window.sessionStorage.setItem('user', JSON.stringify(data.user))
store.commit('setUer', data.user)
} else {
ViewUI.Message.warning('自动登录结果' + data.msg)
}
})
}
}
在login.vue中添加代码:
<style lang="less">
@import './login.less';
</style>
<template>
<div class="login">
<div class="login-con">
<Card icon="log-in" title="欢迎登录" :bordered="false">
<div class="form-con">
<login-form @on-success-valid="handleSubmit"></login-form>
<p class="login-tip">输入任意用户名和密码即可</p>
</div>
</Card>
</div>
</div>
</template>
<script>
import LoginForm from '@/components/login-form'
import {login} from '@/utils/service'
export default {
components: {
LoginForm
},
methods: {
handleSubmit ({ userName, password, rememberMe }) {
alert('记住我' + rememberMe)
login(userName, password, data => {
if (data.code === 100) {
this.$Message.success('登录成功')
window.sessionStorage.setItem('user', JSON.stringify(data.user))
this.$store.commit('setUser', data.user)
// 保存7天自动登录的凭据
if (rememberMe) {
this.$cookies.set('rememberMe', JSON.stringify({
username: userName,
password: password
}), 60 * 60 * 24 * 7)// 设置过期时间为七天
}
this.$router.push('/')
} else {
this.$Message.warning(data.msg)
}
})
}
}
}
</script>
<style>
</style>
------------------------------------------------------------------------------------------------------------------------------
对用户的密码进行MD5加密保护用户隐私
首先将数据库中的密码改为32位MD5加密后的密码。
修改后端login方法:
@RequestMapping("/api/login")
public Object login(String username, String password,HttpSession session) {
User user = userDao.findByUsername(username);
if (user == null) {
return Result.of(102, "用户不存在");
}
String password1 = user.getPassword();// 数据库中的密码
String password2 = MD5Util.md5(password);// 前端表单填写的密码
if (!password1.equals(password2)) {
return Result.of(103, "密码不正确");
}
session.setAttribute("loginUser", user);
return Result.of(100, "登录成功").put("user", user);
}
------------------------------------------------------------------------------------------------------------------------------
设置注销后的Cookie清除
methods: {
logout () {
logout(data => {
this.$store.commit('setUser', null)
this.$cookies.remove('rememberMe')// 清除cookie
this.$router.push('/login')
})
}
}
------------------------------------------------------------------------------------------------------------------------------
为何要放弃Session使用Token
为了扩展性更强,适配App,所以我们放弃原有的Session系统,自己模拟一个,这个标记就叫他Token。
------------------------------------------------------------------------------------------------------------------------------
通过Header传递Token到后端
在main.js中(最前面)添加代码:
// Header携带Token
axios.defaults.headers.Token = 'HelloToken'
后端修改score方法:
@RequestMapping("/api/score")
public Object score(HttpSession session, HttpServletRequest request) {
//System.out.println("当前访问者是:" + session.getId());
System.out.println("Header-Token:" + request.getHeader("Token"));
return Result.of(1).put("xixi", "西西"); // {code:1,"name":"西西"}
}
------------------------------------------------------------------------------------------------------------------------------
搭建Reids服务器并使用SpringBoot成功连接
在后端pom.xml文件中添加redis依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
在application.yml中添加配置:
spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
password:
timeout: 2000
在Hello.java中添加代码:
@Autowired StringRedisTemplate srt;
@RequestMapping("/api/score")
public Object score(HttpSession session, HttpServletRequest request) {
//System.out.println("当前访问者是:" + session.getId());
System.out.println("Header-Token:" + request.getHeader("Token"));
srt.opsForValue().set("西西", "xixixi");
return Result.of(1).put("xixi", "西西"); // {code:1,"name":"西西"}
}
------------------------------------------------------------------------------------------------------------------------------
Redis保存Token信息,用户每次都携带Token值
后端login方法中添加代码:
//session.setAttribute("loginUser", user);
String json = new ObjectMapper().writeValueAsString(user);
String uuid = UUID.randomUUID().toString();
srt.opsForValue().set("User-Token:" + uuid, json, Duration.ofHours(1));
return Result.of(100, "登录成功").put("user", user).put("token", uuid);
前端main.js中修改代码:
// Header携带Token
axios.defaults.headers.Token = window.sessionStorage.getItem('token')
login.vue中添加代码:
axios.defaults.headers.Token = data.token
window.sessionStorage.setItem('token', data.token)
------------------------------------------------------------------------------------------------------------------------------
通过Token获取Redis中的登录信息
后端添加方法:
//获取当前用户自己的登录信息
@RequestMapping("/api/user/info")
public Object getUserInfo(HttpServletRequest request) {
String token = request.getHeader("Token");
String json = srt.opsForValue().get("User-Token:" + token);
return json;
}
前端MyHeader.vue中添加代码:
<MenuItem name="" @click.native="getUserInfo">个人中心</MenuItem>
methods: {
getUserInfo () {
this.axios.get('http://localhost:8390/api/user/info').then(response => {
console.log(response.data)
})
}
------------------------------------------------------------------------------------------------------------------------------
使用SpringSession方案,客户端保存服务端下发的Token值然后每次携带
引入依赖:
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
后端主程序中添加代码:
@EnableRedisHttpSession(maxInactiveIntervalInSeconds=1800)
@Bean
public HeaderHttpSessionIdResolver HeaderTokenResolver() {
return new HeaderHttpSessionIdResolver("Token");
}
前端main.js中
if (response.data.token) {
window.sessionStorage.setItem('token', response.headers.token)
this.axios.defaults.headers.Token = response.headers.token
}