Spring Boot是由Pivotal团队提供的基于Spring的全新框架,旨在简化Spring应用的初始搭建和开发过程(Spring+SpringMVC+MyBatis简称SSM)。
Spring Boot是所有基于Spring开发项目的起点。
Spring Boot就是尽可能地简化应用开发的门槛,让应用开发、测试、部署变得更加简单。
遵循“约定优于配置”的原则,只需要很少的配置或使用默认的配置。
能够使用内嵌的Tomcat、Jetty服务器,不需要部署war文件。
提供定制化的启动器Starters,简化Maven配置,开箱即用。纯Java配置,没有代码生成,也不需要XML配置。
提供了生产级的服务监控方案,如安全监控、应用监控健康检测等。
示例中返回了hello页面和name的数据,在前端页面中可以通过${name}参数获取后台返回的数据并显示。
@Controller通常与Thymeleaf模板引擎结合使用。
默认情况下,@RestController注解会将返回的对象数据转换为JSON格式。
RequestMapping注解主要负责URL的路由映射。它可以添加在Controller类或者具体的方法上。
如果添加在Controller类上,则这个Controller中的所有路由映射都将会加上此映射规则,如果添加在方法上,则只对当前方法生效。
RequestMapping注解包含很多属性参数来定义HIP的有X块羽则。常用的属性参数如下:
value:请求URL的路径,支持URL模板、正则表达式
method: HTTP请求方法。若不设置此参数,任何方法均可请求
@GetMapping=@RequestMapping(value = “/getData” ,method = RequestMethod.GET)
consumes:请求的媒体类型(Content-Type), 如application/json
produces:响应的媒体类型
params,headers:请求的参数及请求头的值
@RequestMapping的value属性用于匹配URL映射,value支持简单表达式RequestMapping(“/user”)
@RequestMapping支持使用通配符匹配URL,用于统一映射某些URL规则类似的请求:@RequestMapping("/getJson/.json"),当在浏览器中请求/getJson/a.json或者/getJson/b.json时都会匹配到后台的Json方法
@RequestMapping的通配符匹配非常简单实用,支持"*"、“?”、“**”,等通配符
符号“*”匹配任意字符,符号“**”匹配任意路径,符号“?”匹配单个字符。
有通配符的优先级低于没有通配符的,比如/user/add.json比/user/.json优先匹配。
有“**”通配符的优先级低于有“”通配符的。
package com.example.helloworld; import com.example.helloworld.entity.User; import org.springframework.web.bind.annotation.*; @RestController public class HelloController { /* *https://www.baidu.com:80/.../.... https为协议 www.baidu.com为域名(com后面的80为端口,当为80端口时可以省略)域名后面的为路径(上述/hello就是路径) * 本地域名为localhost,端口为8080!! * */ // @GetMapping("/hello") // @GetMapping("/hello")=@RequestMapping(value = "/hello",method = RequestMethod.GET) 第二个参数为HTTP的访问方法 // 浏览器将会发送Get请求访问这个方法,括号里面为访问的链接地址 // http://localhost:8088/hello?nickname=zhangsan&phone=123456 ?后面为要传的参数 nickname为参数名,zhangsan为值 多个参数时参数之间用&连接 @RequestMapping(value = "hello",method = RequestMethod.GET) public String hello(String phone,String nickname){ //方法中的参数为浏览器端要传入的参数,参数名称与发送请求时的名称必须一致。(这些参数在发送请求可以不传) System.out.println(nickname); System.out.println(phone); return "你好"+nickname+"phone:"+phone; } @RequestMapping(value = "helloTest1",method = RequestMethod.GET) public String helloTest1(@RequestParam(value = "phone",required = false) String p, @RequestParam(value = "nickname") String n){ //方法中参数名称与发送请求时的名称不一致。http://localhost:8088/helloTest1?nickname=zhangsan //请求时参数nickname与方法形参n映射,请求时参数phone与方法形参p映射 //在这种写法中,请求时phone可以不传,nickname必须传,因为required默认为true System.out.println(n); System.out.println(p); return "你好"+n+"phone:"+p; } @RequestMapping(value = "helloTest2",method = RequestMethod.GET) public String helloTest2(User user){ //user类有两个私有属性username和password并实现了他们的get、set方法以及user类的toString方法 //发送请求时参数名与user类属性名保持一致就可以自动封装成user类(传参可缺省) //http://localhost:8088/helloTest2?username=zhangsan&password=****** System.out.println(user.getUsername()); System.out.println(user.getPassword()); return user.toString(); } @RequestMapping(value = "helloTest3",method = RequestMethod.POST) //由于method为POST,而浏览端只能发送GET请求,需要借助Apipost软件来发送POST请求 public String helloTest3(String phone,String nickname){ System.out.println(nickname); System.out.println(phone); return "你好"+nickname+"phone:"+phone; } @RequestMapping(value = "helloTest4",method = RequestMethod.POST) //由于method为POST,而浏览端只能发送GET请求,需要借助Apipost软件来发送POST请求 //若要用Body+json形式发送POST请求,需要在方法形参前加上@RequestBody注解 public String helloTest4(@RequestBody User user){ System.out.println(user); System.out.println(user); return user.toString(); } }
Apipost发送post请求的方法
①采用Body与x-www-form-urlencoded的形式
②采用Query的形式
③采用Body与json的形式
表单的enctype属性规定在发送到服务器之前应该如何对表单数据进行编码。
当表单的enctype=“application/x-www-form-urlencoded”(默认)时,form表单中的数据格式为: key=value&key=value
当表单的enctype="multipart/form-data"时,其传输数据形式如下
Spring Boot工程嵌入的tomcat限制了请求的文件大小。每个文件的配置最大为1Mb,单次请求的文件的总数不能大于10Mb。
要更改这个默认值需要在配置文件(如application.properties)中加入两个配置
#设置上传的每个文件最大为10MB 默认1MB spring.servlet.multipart.max-file-size=10MB #设置单次上传所有文件总共不能超过50MB 默认10MB spring.servlet.multipart.max-request-size=50MB
package com.example.helloworld; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest; import java.io.File; import java.io.IOException; //文件上传 @RestController public class FileUploadController { @PostMapping(value = "/upload")//发送请求时表单类型必须是form-data类型,注意参数名保持一致 public String up(String nickname, MultipartFile photo, HttpServletRequest request) throws IOException{ //HttpServletRequest参数在发送请求时不用管,系统会自动传 System.out.println(nickname); //获取图片原始名称 System.out.println(photo.getOriginalFilename()); //获取文件类型 System.out.println(photo.getContentType()); //getServletContext().getRealPath获取Tomcat运行时服务器路径 //在使用getServletContext.getRealPath()时,传入的参数是从当前servlet部署在tomcat中的文件夹算起的相对路径,可以自定义 // 要以"/" 开头,否则会找不到路径,导致NullPointerException String path=request.getServletContext().getRealPath("/upload/"); System.out.println(path); saveFile(photo,path); return "Upload Success!"; } public void saveFile(MultipartFile photo, String path) throws IOException{ File dir = new File(path); //判断存储的目录是否存在,如果不存在则创建 if(!dir.exists()){ //创建目录 dir.mkdir(); } //getOriginalFilename()获取文件名称。存储文件时需存储路径+文件名 File file=new File(path+photo.getOriginalFilename()); //transferTo(file)自动将该文件存储到file路径 photo.transferTo(file); } } /* 最终输出: zhangsan QQ图片20201117170834.jpg image/jpeg C:\Users\zhy\AppData\Local\Temp\tomcat-docbase.8088.8922082603726198350\upload\ 注意:本地服务器地址tomcat-docbase.8088.8922082603726198350每次运行都会变,部署到云端后服务地址就不改变了 */
用Apipost发送post请求
拦截器在Web系统中非常常见,对于某些全局统一的操作,我们可以把它提取到拦截器中实现。总结起来,拦截器大致有以下几种使用场景:
Spring Boot定义了HandlerInterceptor接口来实现自定义拦截器的功能
Handlerlnterceptor接口定义了preHandle、postHandle、afterCompletion三种方法,通过重写这三种方法实现请求前、请求后等操作
①首先自定义一个java类实现HandlerInterceptor
package com.example.helloworld.Interceptor; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; //拦截器 发送的请求先经过拦截器,当preHandle返回true时,才会将请求发给后端 //如有需要也可以重写postHandle和afterCompletion方法 public class LoginInterceptor implements HandlerInterceptor { @Override //在请求处理之前进行调用(controller方法调用之前) public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if(条件){ System.out.println("通过"); return true; }else{ System.out.println("不通过"); return false; } } }
②注册拦截器
package com.example.helloworld.config; import com.example.helloworld.Interceptor.LoginInterceptor; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration//加上这个注解SpringBoot才会自动读取这个类,类里面对拦截器的配置才会生效 public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { //在这里对拦截器进行配置,不配置,拦截器无法使用 //参数为对 服务器配置路径/ueser/下的所有路径进行拦截. 当参数为空时,对所有路径都进行拦截 registry.addInterceptor(new LoginInterceptor()).addPathPatterns(); } }
符合RESTful规范的Web API需要具备如下两个关键特性:
Spring Boot提供的spring-boot-starter-web组件完全支持开发RESTful API,提供了与REST操作方式(GET、POST、PUT、DELETE)对应的注解。
在RESTful架构中,每个网址代表一种资源,所以URI中建议不要包含动词,只包含名词即可,而且所用的名词往往与数据库的表格名对应。
用户管理模块API示例:
package com.example.helloworld.Controller; import com.example.helloworld.entity.User; import org.springframework.web.bind.annotation.*; //RESTful 风格:路径中没有动词。 // GetMapping只用于获取数据。PostMapping只用于添加数据。PutMapping只用于更新数据。DeleteMapping只用于删除数据。 @RestController //局部配置Cors。仅为此控制器配置Cors,由于在CorsConfig中已经全局配置了,故此不需要局部配置 //@CrossOrigin public class UserController { @GetMapping(value = "/user/{id}") //如果没有@PathVariable,方法只能获取到路径上的?后面的东西,没办法获取到{}中的。 在浏览器输入请求时不写{} public String getUserById(@PathVariable int id){ return "根据ID获取用户信息 "+id; } @GetMapping(value = "/user/message") public String getUserMessage(){ return "All User Message"; } @PostMapping(value = "/user")//要传的数据放到请求体中 public String save(User user){ return "添加用户"; } @PutMapping("/user")//要传的数据放到请求体中 public String update(User user){ return "更新用户"; } @DeleteMapping("/user/{id}") public String deleteById(@PathVariable int id){ return "删除用户:"+id; } }
Swagger是一个规范和完整的框架,用于生成、描述、调用和可视化RESTful风,格的Web服务,是非常流行的API表达工具。
Swagger能够自动生成完善的RESTful API文档,,同时并根据后台代码的修改同步更新,同时提供完整的测试页面来调试API.
在Spring Boot项目中集成Swagger同样非常简单
①在项目pom.xml中引入springfox-swagger2和springfox-swagger-ui依赖即可。
io.springfox springfox-swagger2 2.9.2 io.springfox springfox-swagger-ui 2.9.2
②Spring Boot 2.6.X后与Swagger有版本冲突问题,需要在application.properties中加入以下配置:
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
③创建Swagger配置类
package com.example.helloworld.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; //启动项目访问http://127.0.0.1:8080/swagger-ui.html 即可打开自动生成的可视化测试页面 @Configuration//告诉Spring容器这个类是一个配置类 @EnableSwagger2//启动Swagger2 public class Swagger2Config { //配置Swagger2相关的bean @Bean public Docket creatRestApi(){ return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com"))//com包下所有的API都交给Swagger2扫描、管理 .paths(PathSelectors.any()).build(); } //API文档页面显示信息 private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("演示项目API")//标题 .description("演示项目")//描述 .version("1.0")//版本 .build(); } }
④浏览器访问http://localhost:端口号/swagger-ui.html即可访问Swagger生成的API文档
Swagger提供了一系列注解来描述接口信息,包括接口说明、请求方法、请求参数、返回信息等
举例
@RequestMapping(value = "helloTest1",method = RequestMethod.GET) @ApiImplicitParams({@ApiImplicitParam(name = "phone", value = "电话", required = false, dataType = "String"),@ApiImplicitParam(name = "nickname", value = "姓名", required = true, dataType = "String")}) @ApiOperation("获取用户") public String helloTest1(@RequestParam(value = "phone",required = false) String p, @RequestParam(value = "nickname") String n){ System.out.println(n); System.out.println(p); return "你好"+n+"phone:"+p; }
ORM (Object Relational Mapping,对象关系映射)是为了解决面向对象与关系数据库存在的互不匹配现象的一种技术。
ORM通过使用描述对象和数据库之间映射的元数据将程序中的对象自动持久化到关系数据库中。
ORM框架的本质是简化编程中操作数据库的编码。
MyBatis是一款优秀的数据持久层ORM框架,被广泛地应用于应用系统。
MyBatis能够非常灵活地实现动态SQL,可以使用XML或注解来配置和映射原生信息,能够轻松地将Java的POJO (Plain Ordinary Java Object,普通的Java对象)与数据库中的表和字段进行映射关联。
MyBatis-Plus是一个MyBatis 的增强工具,在MyBatis的基础上做了增强,简化了开发。
Developer Tools选项卡中选择Spring Boot DevTools热部署工具和Lombok工具
web选项卡中选择Spring Web
在SQL选项卡中选择MySQL Driver
在pom.xml中加入依赖
4.0.0 org.springframework.boot spring-boot-starter-parent 2.6.4 com.example mpdemo 0.0.1-SNAPSHOT mpdemo Demo project for Spring Boot 17 org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-devtools runtime true com.baomidou mybatis-plus-boot-starter 3.5.1 mysql mysql-connector-java 8.0.28 runtime com.alibaba druid-spring-boot-starter 1.1.20 org.projectlombok lombok true org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-maven-plugin
在Resource文件夹下新建File(yml文件)配置数据库相关信息,并将自动生成的properties文件删除
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mydb?serverTimezone=GMT%2B8 username: root password: 123 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
一般新建一个entity包用来存放与数据库对应的实体类
package com.example.mpdemo.entity; import lombok.Data; /** * @author zhy * @create 2023-01-04 17:42 */ //与数据库中的表做映射。类中的属性要和表中的字段对应 //@Data//有了该注解后,该类的get、set、toString、equals等方法不用再写,由lombok自动生成 public class User { private int id; private String username; private String password; private String birthday; public int getId() { return id; } public void setId(int 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 String getBirthday() { return birthday; } public void setBirthday(String birthday) { this.birthday = birthday; } @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + ", birthday='" + birthday + '\'' + '}'; } }
一般新建一个mapper包,该包下存放操作数据库的接口
package com.example.mpdemo.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.example.mpdemo.entity.User; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; import org.mybatis.spring.annotation.MapperScan; import org.springframework.stereotype.Service; import java.util.List; /** * 操作数据库的接口命名一般为:要操作的表名+Mapper。 * 该接口不用自己实例化,实例化过程由SpringBoot+MyBatis完成。但是使用之前需要Spring来注入实例 * @author zhy * @create 2023-01-04 17:38 */ public interface UserMapper { //查询所有用户 @Select("select * from user") Listfind(); } //也可以直接继承BaseMapper(该类实现了对数据库增删改查的所有操作,可以直接调用) //public interface UserMapper extends BaseMapper { // //}
一般新建controller包用来存放控制器
package com.example.mpdemo.controller; import com.example.mpdemo.entity.User; import com.example.mpdemo.mapper.UserMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.List; /** * @author zhy * @create 2023-01-04 17:34 */ @RestController public class UserController { @Resource//Spring自动注入UserMapper实例 private UserMapper userMapper; @GetMapping("/users") public Object query(){ return userMapper.find(); } }
@SpringBootApplication @MapperScan("com.example.mpdemo.mapper") //指定扫描那个包,与数据库映射的类一般放在mapper包。注意不要自己写路径,右键mapper包→Copy→Copy Reference public class MpdemoApplication { public static void main(String[] args) { SpringApplication.run(MpdemoApplication.class, args); } }
实现复杂关系映射,可以使用@Results注解,@Result注解,@One注解,@Many注解组合完成复杂关系的配置。(以上配置均有mybatis提供)
实现通过用户查询所有用户及其订单信息,以及通过订单查询所有订单及用户信息的多表查询
//与数据库中的表做映射。类中的属性要和表中的字段对应 //@Data//有了该注解后,该类的get、set、toString、equals等方法不用再写,由lombok自动生成 //标名需要和类名保持一致(忽略大小写)。如果两者不一样需要在类前面加@TableName("表名") @ApiModel("用户对象") public class User { @ApiModelProperty("用户id") private int id; //当类中属性与表中对应的字段名不一致时加注解@TableField("对应字段名") @ApiModelProperty("用户姓名") private String username; @ApiModelProperty("用户密码") private String password; @ApiModelProperty("用户生日") private String birthday; @TableField(exist = false) //若类中属性对应的表中并没有对应的字段名加注解@TableField(exist = false) //描述用户所有订单 private Listorders; //实现其get、set、toString方法 }
public class Order { private int id; private String ordertime; private int total; private int uid; @TableField(exist = false) private User user; //实现其get、set、toString方法 }
public interface UserMapper { @Select("select * from user where id=#{id}") User selectById(int id); @Select("select * from user") //column的值为数据库中的字段名,property的值为类中的属性名,最终会将查询结果赋值给property //javaType为查询到结果的类型,在调用另一个mapper中的方法时必须写 //@Many表示会查询到多个结果 @Results(value = { @Result(column = "id",property = "id"), @Result(column = "username",property = "username"), @Result(column = "password",property = "password"), @Result(column = "id",property = "orders",javaType = List.class, many = @Many(select = "com.example.mpdemo.mapper.OrderMapper.selectByUid")) }) ListselectAllUserAndOrders(); }
public interface OrderMapper extends BaseMapper{ //数据库表名最好不要写成order,因为他是关键字 @Select("select * from orders where uid = #{uid}") List selectByUid(int uid); // @Select("select * from orders") @Results({ @Result(column = "id",property = "id"), @Result(column = "ordertime",property = "ordertime"), @Result(column = "total",property = "total"), @Result(column = "uid",property = "uid"), @Result(column = "uid",property = "user",javaType = User.class, one = @One(select = "com.example.mpdemo.mapper.UserMapper.selectById")) }) List selectAllOrdersAndUser(); }
@RestController @Api(tags = {"用户管理控制器"}) public class UserController { @Resource//Spring自动注入UserMapper实例 private UserMapper userMapper; @GetMapping("/user/findAll") @ApiOperation("查询所有用户及订单信息") ListfindAll(){ return userMapper.selectAllUserAndOrders(); } }
@RestController @Api(tags = {"订单管理控制器"}) public class OrderController { @Resource private OrderMapper mapper; @ApiOperation("查询所有订单及其客户") @GetMapping("/mapper/findAll") public Object findAll(){ return mapper.selectAllOrdersAndUser(); } }
5.1.2.1使用mybatisplus的分页插件
创建配置类(配置类一般放在config包下)
@Configuration public class MyBatisPlusConfig { @Bean public MybatisPlusInterceptor paginationInterceptor(){ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(); interceptor.addInnerInterceptor(paginationInterceptor); return interceptor; } }
HTTP状态码
HTTP状态码就是服务向用户返回的状态码和提示信息,客户端的每一次请求,服务都必须给出回应,回应包括HTTP状态码和数据两部分。
HTTP定义了40个标准状态码,可用于传达客户端请求的结果。状态码分为以下5个类别:
RESTful API中使用HTTP状态码来表示请求执行结果的状态,适用于REST API设计的代码以及对应的HTTP方法。
4开头的错误一般都是后端的问题
**404:**访问路径不对、后端代码修改后没有重启
**405:**发送请求方式不正确
**500:**后端程序出现运行时异常
编码工具:VSCode
依赖管理:NPM
项目构建: VueCli
Vue (读音/vju:/,类似于view)是一套用于构建用户界面的渐进式框架。
Vue.js提供了MVVM数据绑定和一个可组合的组件系统,具有简单、灵活的API。
保证视图和数据的一致性。
其目标是通过尽可能简单的API实现响应式的数据绑定和可组合的视图组件。
导入vue.js的script 脚本文件
在页面中声明一个将要被vue所控制的DOM区域,既MVVM中的View
创建vm实例对象(vue 实例对象)
NPM(Node Package Manager)是一个NodeJS包管理和分发工具。
NPM以其优秀的依赖管理机制和庞大的用户群体,目前已经发展成为整个JS领域的依赖管理工具
NPM最常见的用法就是用于安装和更新依赖。要使用NPM,首先要安装Noge工具。
先安装Node.js
Vue CLI是Vue官方提供的构建工具,通常称为脚手架。
用于快速搭建一个带有热重载(在代码修改后不必刷新页面即可呈现修改后的效果)及构建生产版本等功能的单页面应用。
Vue CLI基于webpack构建,也可以通过项目内的配置文件进行配置。
安装: npm install -g @vue/cli
利用Vue CLI新建Vue项目
package.json记录了项目的详细信息:项目名称、版本、是否为私有、依赖等等
src目录是我们以后写代码的目录
组件(Component)是Vue.js最强大的功能之一。组件可以扩展HTML元素,封装可重用的代码。
Vue的组件系统允许我们使用小型、独立和通常可复用的组件构建大型应用。
Vue 中规定组件的后缀名是.vue
每个.vue组件都由3部分构成,分别是
公
组件可以由内部的Data提供数据,也可以由父组件通过prop的方式传值。
{{ title }}
{{ rating }}
兄弟组件之间可以通过Vuex等统一数据源提供数据共享
Element是国内饿了么公司提供的一套开源前端框架,简洁优雅,提供了Vue.React、Angular等多个版本。
文档地址: https://element.eleme.cn/#/zh-CN/
安装:npm i element-ui
引入 Element:
在main.js中加上
import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; Vue.use(ElementUI);
//main.js import Vue from 'vue' import App from './App.vue' import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; Vue.config.productionTip = false Vue.use(ElementUI);//全局注册 new Vue({ render: h => h(App), }).$mount('#app')
由于Element UI提供的字体图符较少,一般会采用其他图表库,如著名的FontAwesome
Font Awesome提供了675个可缩放的矢量图标,可以使用CSS所提供的所有特性对它们进行更改,包括大小、颜色、阴影或者其他任何支持的效果。
文档地址: http://fontawesome.dashgame.com/
安装: npm install font-awesome
使用时在package.json中引入: import ‘font-awesome/css/font-awesome.min.css’
在实际项目开发中,前端页面所需要的数据往往需要从服务器端获取,这必然涉及与服务器的通信。
Axios是一个基于promise 网络请求库,作用于node.js和浏览器中。
Axios在浏览器端使用XMLHttpRequests发送网络请求,并能自动完成JSON数据的转换。
安装: npm install axios
地址: https://www.axios-http.cn/
在实际项目开发中,几乎每个组件中都会用到axios 发起数据请求。此时会遇到如下两个问题:
每个组件中都需要导入axios
每次发请求都需要填写完整的请求路径可以通过全局配置的方式解决上述问题:
为了保证浏览器的安全,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源,称为同源策略,同源策略是浏览器安全的基石
同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能
所谓同源(即指在同一个域)就是两个页面具有相同的协议( protocol),主机(host)和端口号(port)
当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域,此时无法读取非同源网页的Cookie,无法向非同源地址发送AJAX请求
CORS (Cross-Origin Resource Sharing)是由W3C制定的一种跨域资源共享技术标准,其目的就是为了解决前端的跨域请求。
CORS可以在不破坏即有规则的情况下,通过后端服务器实现CORS接口,从而实现跨域通信。
CORS将请求分为两类:简单请求和非简单请求,分别对跨域通信提供了支持。
Access-Control-Allow-Credentials:是否允许用户发送、处理cookie
Access-Control-Max-Age:预检请求的有效期,单位为秒,有效期内不会重复发送预检请求。
一般在config包下新建一个配置类来配置CORS
package com.example.mpdemo.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * @author zhy * @create 2023-01-13 18:50 */ @Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping ( "/**")//允许跨域访问的路径 .allowedOriginPatterns ("*")//允许跨域访问的源 .allowedMethods ( "POST","GET","PUT","OPTIONS","DELETE")//允许请求方法 .maxAge (168000)//预检间隔时间 .allowedHeaders ("*")//允许头部设置 .allowCredentials (true);//是否发送cookie } }
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@4或npm install vue-router@3 (默认安装最新的)
在项目中定义Discover.vue、Friends.vue、My.vue三个组件,将来要使用vue-router来控制它们的展示与切换:
发现音乐
关注
我的音乐
可以使用标签来声明路由链接,并使用标签来声明路由占位符。示例代码如下:
App.vue:
发现音乐 关注 我的音乐
在项目中创建index.js路由模块,加入以下代码:
import VueRouter from "vue-router"; import Vue from "vue"; import Discover from '../components/Discover.vue' import Friends from '../components/Friends.vue' import My from '../components/My.vue' Vue.use(VueRouter) const router=new VueRouter({ routes:[ {path:'/',redirect:"/discover"},//redirect为重定向,使得初始化页面也是有Discover组件的页面 {path:'/discover',component:Discover}, {path:'/friends',component:Friends}, {path:'/my',component:My} ] }) // 将router对象导出 export default router
在main.js中导入并挂载router
import Vue from 'vue' import App from './App.vue' import router from './router' Vue.config.productionTip = false new Vue({ // 前面是属性名,后面是值 render: h => h(App), router:router }).$mount('#app')
路由重定向指的是:用户在访问地址A的时候,强制用户跳转到地址C,从而展示特定的组件页面。通过路由规则的redirect属性,指定一个
新的路由地址,可以很方便地设置路由的重定向:
const router=new VueRouter({ //指定hash属性与组件的对应关系 routes:[ //当用户访问'/'时,跳转到'/discover' {path:'/',redirect:"/discover"},//redirect为重定向,使得初始化页面也是有Discover组件的页面 {path:'/discover',component:Discover}, {path:'/friends',component:Friends}, {path:'/my',component:My} ] })
以Discover为例子,创建toplist和playlist两个子路由。
首先在components目录下创建TopList.vue和Play.vue两个组件
在Discover.vue组件中,声明toplist和 playlist的子路由链接以及子路由占位符。示例代码如下:
发现音乐
推荐 歌单
在src/router/index.js路由模块中,导入需要的组件,并使用children属性声明子路由规则:
import VueRouter from "vue-router"; import Vue from "vue"; import Discover from '../components/Discover.vue' import Friends from '../components/Friends.vue' import My from '../components/My.vue' import TopList from '../components/TopList.vue' import PlayList from '../components/PlayList.vue' Vue.use(VueRouter) const router=new VueRouter({ //指定hash属性与组件的对应关系 routes:[ //当用户访问'/'时,跳转到'/discover' {path:'/',redirect:"/discover"},//redirect为重定向,使得初始化页面也是有Discover组件的页面 { path:'/discover', component:Discover, children:[ {path:"toplist",component:TopList}, {path:"playlist",component:PlayList} ] }, {path:'/friends',component:Friends}, {path:'/my',component:My} ] }) // 将router对象导出 export default router
思考:有如下3个路由链接:
商品1 商品2 商品3
const router =new vueRouter({ //指定hash属性与组件的对应关系 routes:[ {path: '/product/1', component: Product }, {path: '/product/2', component: Product }, {path: '/product/3', component: Product }, ] })
上述方式复用性非常差。
动态路由指的是:把Hash地址中可变的部分定义为参数项,从而提高路由规则的复用性。在vue-router中使用英文的冒号(:)来定义路由的参数项。示例代码如下:
{path: '/product/:id', component:Product }
通过动态路由匹配的方式渲染出来的组件中,可以使用$route.params对象访问到动态匹配的参数值,比如在商品详情组件的内部,根据id值,请求不同的商品数据。
商品{{ $route.params.id }}
为了简化路由参数的获取形式,vue-router允许在路由规则中开启props传参。示例代码如下:
商品{{ id }}
import VueRouter from "vue-router"; import Vue from "vue"; import Discover from '../components/Discover.vue' import Friends from '../components/Friends.vue' import My from '../components/My.vue' import TopList from '../components/TopList.vue' import PlayList from '../components/PlayList.vue' import Product from '../components/Product.vue' Vue.use(VueRouter) const router=new VueRouter({ //指定hash属性与组件的对应关系 routes:[ //当用户访问'/'时,跳转到'/discover' {path:'/',redirect:"/discover"},//redirect为重定向,使得初始化页面也是有Discover组件的页面 { path:'/discover', component:Discover, children:[ {path:"toplist",component:TopList}, {path:"playlist",component:PlayList} ] }, {path:'/friends',component:Friends}, { path:'/my', component:My, children:[ //:说明后面是动态获取的路由,props:true表示将动态获取的路由作为参数传给组件 {path:"product/:id",component:Product,props:true}, ] } ] }) // 将router对象导出 export default router
声明式导航 | 编程式导航 |
---|---|
router.push(…) |
除了使用创建a标签来定义导航链接,我们还可以借助router的实例方法,通过编写代码来实现
想要导航到不同的URL,则使用router.push方法。这个方法会向history栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的URL。
当你点击时,这个方法会在内部调用,所以说,点击等同于调用router.push(…),
我的音乐
商品1 商品2 商品3
导航守卫可以控制路由的访问权限。示意图如下:
全局导航守卫会拦截每个路由规则,从而对每个路由进行访问权限的控制。
你可以使用router.beforeEach 注册一个全局前置守卫:
to:即将要进入的目标
from:当前导航正要离开的路由
在守卫方法中如果声明了next形参,则必须调用next()函数,否则不允许用户访问任何一个路由!
对于组件化开发来说,大型应用的状态往往跨越多个组件。在多层嵌套的父子组件之间传递状态已经十分麻烦,而Vue更是没有为兄弟组件提供直接共享数据的办法。
基于这个问题,许多框架提供了解决方案——使用全局的状态管理器,将所有分散的共享数据交由状态管理器保管,Vue也不例外。
Vuex是一个专为Vue.js应用程序开发的状态管理库,采用集中式存储管理应用的所有组件的状态。
简单的说,Vuex用于管理分散在Vue各个组件中的数据
每一个Vuex应用的核心都是一个store,与普通的全局对象不同的是,基于Vue数据与视图绑定的特点,当store中的状态发生变化时,
与之绑定的视图也会被重新渲染。
store中的状态不允许被直接修改,改变store中的状态的唯一途径就是显式地提交(commit) mutation,这可以让我们方便地跟踪每一个状态的变化。在大型复杂应用中,如果无法有效地跟踪到状态的变化,将会对理解和维护代码带来极大的困扰。
Vuex中有5个重要的概念: State、Getter、Mutation.Action、Module。
State用于维护所有应用层的状态,并确保应用只有唯一的数据源(下图以Vuex4为例)
Mutation提供修改State状态的方法。
Action类似Mutation,不同在于:
Action不能直接修改状态,只能通过提交mutation来修改,Action可以包含异步操作
Vuex有3和4两个版本,Vuex3对应Vue2,Vuex4对应Vue3
安装: npm install vuex@3或npm install vuex@4
Mock.js是一款前端开发中拦截Ajax请求再生成随机数据响应的工具,可以用来模拟服务器响应.
优点是非常简单方便,无侵入性,基本覆盖常用的接口数据类型…
支持生成随机的文本、数字、布尔值、日期、邮箱、链接、图片、颜色等。
安装: npm install mockjs
在项目中创建mock目录,新建index.js文件
组件中调用mock.js中模拟的数据接口,这时返回的response就是mock.js中用Mock.mock( ‘url’ ,data)中设置的data
Mock.mock( rurl?, rtype?, templatelfunction(options))
mock的语法规范包含两层规范:
数据模板中的每个属性由3部分构成:属性名name、生成规则rule、属性值value:
'name|rule' : value
属性名和生成规则之间用竖线|分隔,属性名是自定义的,生成规则是可选的,属性值可以是固定值也可以是一个占位符(@)生成随机的值
生成规则有7种格式:
'name|min-max' : value 'name|count': value 'name|min-max.dmin-dmax' : value 'name|min-max.dcount': value 'name|count.dmin-dmax' : value 'name|count.dcount' : value 'name|+step' : value
//通过重复string生成一个字符串,重复次数大于等于min,小于等于max。 'name|min-max' : string //通过重复string生成一个字符串,重复次数等于count。 'name|count' : string
var data = Mock.mock({ 'name1|1-3' : 'a',//重复生成1到3个a(随机) 'name2|2' : 'b' //生成bb })
//属性值自动加1,初始值为number。 'name|+1': number //生成一个大于等于 min、小于等于max的整数,属性值 number只是用来确定类型。 'name|min-max' : number //生成一个浮点数,整数部分大于等于min、小于等于 max,小数部分保留 dmin到 dmax位。 'name|min-max.dmin-dmax' : number
Mock.mock({ 'number1|1-100.1-10' : 1, 'number2|123.1-10' : 1, 'number3|123.3': 1, 'number4|123.10' : 1.123 }) //结果: { "number1": 12.92, "number2": 123.51, "number3": 123.777, "number4": 123.1231091814 }
var data = Mock .mock({ 'name1|+1':4, //生成4,如果循环每次加1 'name2|1-7 ':2, //生成一个数字,1到7之间 'name3|1-4.5-8':1 ///生成一个小数,整数部分1到4,小数部分5到8位,数字1只是为了确定类型 })
//随机生成一个布尔值,值为 true 的概率是1/2,值为 fa1se的概率同样是1/2。 'name|1': boo1ean //随机生成一个布尔值,值为 value 的概率是 min / (min + max),值为 !value 的概率是 max / (min + max) 'name|min-mak' : value
var data = Mock . mock({ 'name|1 ' :true, //生成一个布尔值,各一半 'name1|1-3' :true //1/4是true,3/4是false })
//从属性值 object中随机选取count个属性。 'name|count' : object //从属性值 object中随机选取min到max个属性。 'name|min-max' : object
var obj = { a:1, b:2, c:3, d:4 } var data = Mock .mock({ 'name|1-3 ' :obj, //随机从obj中寻找1到3个属性,新对象 'name|2 ' :obj //随机从onj中找到两个属性,新对象 })
//从属性值array中随机选取1个元素,作为最终值。 'name|1' : array //从属性值array中顺序选取1个元素,作为最终值。 'name|+1': array //通过重复属性值 array 生成一个新数组,重复次数大于等于 min,小于等于max。 'name|min-max ' : array //通过重复属性值array生成一个新数组,重复次数为count。 'name|count' : array
Mock .mock ({ //通过重复属性值array生成一个新数组,重复次数为1-3次。 "favorite_games |1-3": [3,5 ,4,6,23,28,42,45], });
var arr = [1,2,3];var data = Mock.mock({ 'name1|1' :arr, //从数组里随机取出1个值 'name2|2 ' :arr, //数组重复count次,这里count为2 'name3|1-3 ' :arr, //数组重复1到3次 }
//执行函数 function,取其返回值作为最终的属性值,函数的上下文为属性 'name’所在的对象。 'name' : function
var fun = function(x){ return x+10; } var data = Mock .mock(i 'name' :fun(10) //返回函数的返回值20 })
占位符只是在属性值字符串中占个位置,并不出现在最终的属性值中。占位符的格式为:
@占位符 @占位符(参数[,参数])
关于占位符需要知道以下几点
//引入mockjs import Mock from 'mockjs '//使用mockjs模拟数据 Mock.mock ( '/api/msdk/proxy/query_common_credit', { "ret": 0, "data" : { "mtime" : "@datetime ",//随机生成日期时间 "score" : "@natura1(1,800)",//随机生成1-800的数字 "rank" :"anatura7(1,100)",//随机生成1-100的数字 "stars " : "@natura1(o,5)",//随机生成1-5的数字 "nickname " : "@cname " ,//随机生成中文名字 } });
vue-element-admin是一个后台前端解决方案,它基于vue和element-ui实现。
内置了i18国际化解决方案,动态路由,权限验证,提炼了典型的业务模型,提供了丰富的功能组件。
可以快速搭建企业级中后台产品原型。
地址: https://panjiachen.github.io/vue-element-admin-site/zh/guide/
见官网
互联网服务离不开用户认证。一般流程是下面这样。
session认证的方式应用非常普遍,但也存在一些问题,扩展性不好,如果是服务器集群,或者是跨域的服务导向架构,就要求session数据共享,每台服务器都能够读取session,针对此种问题一般有两种方案:
Token是在服务端产生的一串字符串,是客户端访问资源接口(API)时所需要的资源凭证,流程如下:
Token认证的特点
JSON Web Token(简称JWT)是一个token的具体实现方式,是目前最流行的跨域认证解决方案。
JWT的原理是,服务器认证以后,生成一个JSON对象,发回给用户,具体如下:
{ "姓名":"张三", "角色":"管理员", "到期时间": "2018年7月1日0点0分" }
用户与服务端通信的时候,都要发回这个JSON对象。服务器完全只靠这个对象认定用户身份。
为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名。
JWT的由三个部分组成,依次如下:
Header(头部)
Header部分是一个JSON对象,描述JWT的元数据
{ "a1g" : "HS256", "typ": "JwT" }
Payload(负载)
Signature(签名)
Signature部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户
然后,使用Header里面指定的签名算法(默认是HMAC SHA256),按照下面的公式产生签名。
HMACSHA256( base64Ur1Encode(header) + "." + base64Ur1Encode(pay1oad), secret)
三部分最终组合为完整的字符串,中间使用.分隔,如下:
Header.Payload.Signature
JWT特点
加入依赖
io.jsonwebtoken jjwt 0.9.1
生成token
//7天过期 private static long expire = 604800;//单位是毫秒 //32位秘钥 private static String secret = "*****************" ;//密钥自定义 //生成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")//设置Header(头部) 固定的 .setSubject(username)//Payload(负载) .setIssuedAt (now)//设置生效时间 .setExpiration(expiration)//设置过期时间 .signwith(signatureALgorithm.HS512, secret)//第一个参数是枚举类型:生成签名算法,第二个参数是密钥 .compact();//合成 }
解析token
//解析token public static cLaims getclaimsByToken(string token){ return Jwts.parser() .setSigningKey(secret)//传入密钥 .parseClaimsJws(token)//传入token .getBody();//解析 }