快速上手springboot+vue的前后端分离项目
1.环境配置
- Java环境
- Mavan项目管理工具
2.springboot上手
- 新建项目
- 创建目录Controller和类Hello,启动项目
@RestController
public class Hello {
@GetMapping("/hello")
public String hello(){
return "hello";
}
}
- 访问 http://localhost:8080/hello,运行结果如下
- 系统配置(配置端口)
在application.properties文件中添加配置
server.port = 8080
3.springboot Controller
- @Controller 请求页面和数据
- @RestController 只请求数据,默认情况下会将返回的对象转换为JSON格式
4.springboot文件上传与拦截器
- 使用IDEA创建Spring Boot项目,会默认创建出classpath:/static/目录,静态资源一般放在这个目录下即可。
- 在application.properties中定义过滤规则和静态资源路径
spring.mvc.static-path-pattern=/static/**
spring.web.resources.static-locations=classpath:/static/
- 设置表单的enctype=“multipart/form-data”
- Spring Boot工程嵌入的tomcat限制了请求的文件大小,每个文件的配置最大为1Mb,单次请求的文件的总数不能大于10Mb。修改配置文件
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB
- 使用MultipartFile获取上传的文件数据,再通过transferTo方法写入到磁盘
@PostMapping("/up")
public String upload(MultipartFile f) throws IOException {
System.out.println(f.getSize());
System.out.println(f.getContentType());
System.out.println(f.getOriginalFilename());
saveFile(f);
return "上传成功";
}
public void saveFile(MultipartFile f) throws IOException {
File upDir = new File("E:\\2.Study\\SpringBoot2\\photos\\");
if (!upDir.exists()){
upDir.mkdir();
}
File file = new File("E:\\2.Study\\SpringBoot2\\photos\\" + f.getOriginalFilename());
f.transferTo(file);
}
- 拦截器在Web系统中非常常见,对于某些全局统一的操作,我们可以把它提取到拦截器中实现,主要用于:权限检查,性能监控,通用行为
- Spring Boot定义了HandlerInterceptor接口来实现自定义拦截器的功能HandlerInterceptor接口定义了preHandle、postHandle、afterCompletion三种方法,通过重写这三种方法实现请求前、请求后等操作
- 拦截器定义
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("登录拦截器");
return true;
}
}
- 拦截器注册
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 仅拦截user路径下的所有资源
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/user/**");
}
}
5.RESTful风格和Swagger
- 客户端使用GET、POST、PUT、DELETE四种表示操作方式的动词对服务端资源进行操作:GET用于获取资源,POST用于新建资源(也可以用于更新资源),PUT用于更新资源,DELETE用于删除资源。
- HTTP提供了POST、GET、PUT、DELETE等操作类型对某个Web资源进行Create、Read、Update和Delete操作。
- spring boot实现restful api
@GetMapping:处理GET请求,获取资源。
@PostMapping:处理POST请求,新增资源。
@PutMapping:处理PUT请求,更新资源。
@DeleteMapping:处理DELETE请求,删除资源。
@PatchMapping:处理PATCH请求,用于部分更新资源。
@GetMapping("/user/{id}")
public String getUserById(@PathVariable String id){
return "查询用户";
}
@PostMapping("/user")
public String save(User user){
return "保存用户";
}
@PutMapping("/user")
public String update(User user){
return "更新用户";
}
@DeleteMapping("/user/{id}")
public String DeleteUserById(@PathVariable String id){
return "删除用户";
}
- Swagger是一个规范和完整的框架,用于生成、描述、调用和可视化RESTful风格的Web服务,是非常流行的API表达工具。
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
- 配置Swagger
@Configuration//告诉Spring容器,这个类是一个配置类
@EnableSwagger2//启Swagger2功能
public class SwaggerConfig extends WebMvcConfigurationSupport {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com"))
.paths(PathSelectors.any()).build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("演示项目API")
.description("学习xxx的项目")
.build();
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("doc.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
配置解决springboot和swagger的冲突
# 解决springboot与Swagger的冲突
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
- 访问http://localhost:8080/swagger-ui.html得到如下页面
- Swagger提供了一系列注解来描述接口信息,包括接口说明、请求方法、请求参数、返回信息等
6.使用MybatisPlus
- 添加依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.31</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
- 全局配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=mzk
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# 开启驼峰命名
mybatis-plus.configuration.map-underscore-to-camel-case=true
- 添加@MapperScan注解
@SpringBootApplication
@MapperScan("com.example.demo.mapper")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
- 使用MybatisX插件来生成Service,Mapper文件,进行SQL查询的几种方法
1、在xxxMapper.xml中写SQL语句
2、在xxxMapper类中使用注解写SQL
3、使用继承的BaseMapper和IService类自带的方法进行简单的SQL
@Override
public String getAllUser() {
return userMapper.selectList(null).toString();
}
- 分页查询
1、使用xxxMapper自带的selectPage方法,传入Page和QueryWrapper
2、使用xxxService自带的page方法,传入Page和QueryWrapper
@GetMapping("/findAll")
public IPage findAll(){
Page page = new Page(1,2);
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
Page IPage = userService.page(page, queryWrapper);
return IPage;
}
7.vue组件化开发
- 使用element ui
npm install element-ui
import ElementUI from 'element-ui';
Vue.use(ElementUI)
- 使用第三方图标库
npm install font-awesome
import 'font-awesome/css/font-awesome.min.css'
使用webStorm新建项目后,把 .lock文件删除后项目运行才不报错,配置文件 vue.config.js 中加入 lintOnSave: false 关闭eslint语法检测
8.axios发送请求
npm install axios
created() {
axios.get('/user').then(response => {
this.movies = response.data;
console.log(this.movies)
})
},
在实际项目开发中,几乎每个组件中都会用到 axios 发起数据请求。此时会遇到如下两个问题:
- 每个组件中都需要导入 axios
- 每次发请求都需要填写完整的请求路径,可以通过全局配置的方式解决上述问题:
//配置请求根路径
axios.defaults.baseURL = 'http://api.com'
//将ax1os作为全局的自定义属性,每个组件可以在内部直接访问(Vue3)
app.config.globalProperties.$http = axios
//将axios作为全局的自定义属性,每个组件可以在内部直接访问(Vue2)
Vue.prototype.$http = axios
跨域问题,多种解决方法,需要时可百度
其中一种解决CORS 的方法, 在springboot项目中跨域的方法或类加上@CrossOrigin注解
9. 前端路由 VueRouter
- Vue路由vue-router是官方的路由插件,能够轻松的管理 SPA 项目中组件的切换。
- Vue的单页面应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来
- vue-router 目前有 3.x 的版本和 4.x 的版本,vue-router 3.x 只能结合 vue2进行使用,vue-router 4.x 只能结合 vue3 进行使用
- 安装:
npm install vue-router@3
<template>
<div id="app">
<!-- 声明路由链接-->
<router-link to="/discover">发现音乐</router-link>
<br>
<router-link to="/my">我的</router-link>
<br>
<router-link to="/friends">朋友</router-link>
<br>
<button @click="toDiscover">跳转发现音乐</button>
<!-- 声明路由占位标签-->
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App',
components: {},
methods: {
toDiscover: function () {
// 编程式导航 上面的为声明式,跳转同一页面会报错,所以catch
this.$router.push('/discover').catch(error => error)
}
}
}
</script>
route.js
Vue.use(VueRouter)
const router = new VueRouter({
// 指定hash属于与组件的对应关系
routes: [
{path: '/', redirect: '/my'},
{path: '/discover', component: Discover},
{
path: '/friends', component: Friends,
children: [
{path: ":id", component: f, props:true}// props:true 表面以参数形式传参
],
},
{
path: '/my',
component: My,
children: [
{path: 'son1', component: my_son1},
{path: 'son2', component: my_son2}
]
}
]
})
export default router
main.js
import router from "@/router";
new Vue({
render: h => h(App),
router : router
}).$mount('#app')
导航守卫
- vue-router提供的导航守卫主要用来拦截导航,让它完成跳转或取消。
- to:Route:即将要进入的目标路由。
- from:Route:当前导航正要离开的路由。
- next:在守卫方法中如果声明了next形参,则必须调用 next() 函数,否则不允许用户访问任何一个路由
- 直接放行:next(),强制其跳转到登录页面:next(‘/login’),强制停留在当前页面:next(false)
router.beforeEach((to, from, next) => {
if(to.path === '/main' && !isAuthenticated){
next('/login')
}
else{
next()
}
})
10.状态管理 Vuex
对于组件化开发来说,大型应用的状态往往跨越多个组件。在多层嵌套的父子组件之间传递状态已经十分麻烦,而Vue更是没有为兄弟组件提供直接共享数据的办法。
基于这个问题,许多框架提供了解决方案——使用全局的状态管理器,将所有分散的共享数据交由状态管理器保管,Vue也不例外。
- 安装
npm install vuex@ + 版本号
- Vuex中有5个重要的概念:State、Getter、Mutation、Action、Module。
- State用于维护所有应用层的状态,并确保应用只有唯一的数据源,在组件中,可以直接使用this.$store.state.count访问数据,也可以先用mapState辅助函数将其映射下来
- Getter维护由State派生的一些状态,这些状态随着State状态的变化而变化,在组件中,可以直接使用this.$store.getters.doneTodos,也可以先用mapGetters辅助函数将其映射下来
- Mutation提供修改State状态的方法,在组件中,可以直接使用store.commit来提交mutation,也可以先用mapMutation辅助函数将其映射下来
- Action类似Mutation,不同在于: Action不能直接修改状态,只能通过提交mutation来修改,Action可以包含异步操作, 在组件中,可以直接使用this.$store.dispatch(‘xxx’)分发 action,或者使用mapActions辅助函数先将其映射下来
- 由于使用单一状态树,当项目的状态非常多时,store对象就会变得十分臃肿。因此,Vuex允许我们将store分割成模块(Module), 每个模块拥有独立的State、Getter、Mutation和Action,模块之中还可以嵌套模块,每一级都有着相同的结构。
代码举例:
store.js
import Vuex from "vuex";
import Vue from "vue";
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0,
todos: [
{id: 1, text: '吃饭', done: true},
{id: 2, text: '睡觉', done: false}
]
},
mutations: {
increment(state, n) {
state.count += n;
}
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
})
export default store
main.js
import Vue from 'vue'
import App from './App.vue'
import store from "@/store";
Vue.config.productionTip = false
new Vue({
render: h => h(App),
store: store
}).$mount('#app')
hello.vue
<template>
<div class="hello">
<!-- <h1>{{this.$store.state.count}}</h1>-->
{{ count }}
<button @click="addOne">+1</button>
<span v-for="todo in doneTodos" :key="todo.id">{{ todo.text }}</span>
</div>
</template>
<script>
import {mapState, mapGetters} from 'vuex'
export default {
name: 'HelloWorld',
computed: {
...mapState([
'count', 'todos'
]),
...mapGetters([
'doneTodos'
])
},
// computed:{
// count(){
// return this.$store.state.count
// }
// },
methods: {
addOne() {
this.$store.commit('increment', 2);
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
11.跨域认证
Session认证
互联网服务离不开用户认证。一般流程是下面这样。
- 用户向服务器发送用户名和密码。
- 服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等。
- 服务器向用户返回一个 session_id,写入用户的 Cookie。
- 用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。
- 服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。
Token认证
Token 是在服务端产生的一串字符串,是客户端访问资源接口(API)时所需要的资源凭证,流程如下:
- 客户端使用用户名跟密码请求登录,服务端收到请求,去验证用户名与密码
- 验证成功后,服务端会签发一个 token 并把这个 token 发送给客户端
- 客户端收到 token 以后,会把它存储起来,比如放在 cookie 里或者localStorage 里
- 客户端每次向服务端请求资源的时候需要带着服务端签发的 token
- 服务端收到请求,然后去验证客户端请求里面带着的 token ,如果验证成功,就向客户端返回请求的数据
JWT
- JSON Web Token(简称 JWT)是一个token的具体实现方式,是目前最流行的跨域认证解决方案。
- JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,具体如下:
- 用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。
- 为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名。
JWT 的由三个部分组成,依次如下:
- Header(头部)
- Payload(负载)
- Signature(签名)
三部分最终组合为完整的字符串,中间使用 . 分隔,如下:
- Header.Payload.Signature
加入依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
生成token
//7天过期
private static Long expire 604800;
//32位秘钥
private static String secret "abcdfghiabcdfghiabcdfghiabcdfghi";
//生成token
public static String generateToken(String username){
Date now new Date();
Date expiration new Date(now.getTime() + 1000 * expire);
return Jwts.builder()
.setHeaderParam("type","JWT")
.setSubject(username)
.setIssuedAt(now)
.setExpiration(expiration)
.signwith(SignatureAlgorithm.HS512,secret)
.compact();
}
解析Token
//解析token
public static Claims getclaimsByToken(String token){
return Jwts.parser()
.setsigningKey(secret)
.parseclaimsJws(token)
.getBody();
}