本文最后更新于:1 年前
1.开发环境热部署 1. 说明
在实际的项目开发调试过程中会频繁地修改后台类文件,导致需要重新编译、重新启动,整个过程非常麻烦,影响开发效率。
Spring Boot 提供了 spring-boot-devtools 组件,使得无须手动重启 SpringBoot 应用即可重新编译、启动项目,大大缩短编译启动的时间。devtools 会监听 classpath 下的文件变动,触发 Restart 类加载器重新加载该类,从而实现类文件和属性文件的热部署。
并不是所有的更改都需要重启应用 (如静态资源、视图模板),可以通过设置spring. devtools. restart. exclude 属性来指定一些文件或目录的修改不用重启应用
2. 配置
在 pom. xml 配置文件中添加 dev-tools 依赖 。 使用 optional=true 表示依赖不会传递 ,即该项目依赖 devtools; 其他项目如果引入此项目生成的 JAR 包,则不会包含 devtools
1.pom. xml 1 2 3 4 5 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true </optional> </dependency>
2. application. properties
在 application. properties 中配置 devtools.
1 2 3 4 5 6 #热部署生效 spring.devtools.restart.enabLed=true #设置重启目录 spring.devtooLs.restart.additional-paths=src/main/java #设置classpath目录下的 WEB-INF 文件夹内容修改不重启 spring.devtooLs.restart.exclude=static
如果使用了 Eclipse , 那么在修改完代码并保存之后,项目将自动编译并触发重启,而如果使用了 IntelliJ IDEA , 还需要配置项目自动编译。
打开 Settings 页面,在左边的菜单栏依次找到Build, Execution, Deployment-→Compile, 勾选 Build project automatically
按 Ctrl+ Shift+ Alt+ / 快捷键调出 Maintenance 页面,单击 Registry , 勾选compiler.automake.allow.when.app.running 复选框 (可能找不到,修改设置为下图即可解决)。
做完这两步配置之后,若开发者再次在 IntelliJ IDEA 中修改代码,则项目会自动重启。
2. 控制器
Spring Boot 提供了@Controller 和@RestController 两种注解来标识此类负责接收和处理 HTTP 请求 。
如果请求的是页面和数据 ,使用@Controller 注解即可; 如果只是请求数据 ,则可以使用@RestController 注解。
1.demo @ RestController 的用法 默认情况下,@RestController 注解会将返回的对象数据转换为 JSON 格式 。
1 2 3 4 5 6 7 8 9 10 @RestController public class HelloController { @RequestMapping ("/user" ) public User getUser () { User user = new User (); user.setUsername ("zhangsan" ); user.setPassword ("123" ) ; return user; } }
2.路由映射
@RequestMapping 注解主要负责 URL 的路由映射。它可以添加Controller类或者具体的方法上。 如果添加在 Controller 类上,则这个 Controller 中的所有路由映射都将会加上此映射规则,如果添加在方法上,则只对当前方法生效。 @RequestMapping 注解包含很多属性参数来定义 HTTP 的请求映射规则。常用的属性参数如下:- value: 请求 URL 的路径, 支持 URL 模板、正则表达式 - method: HTTP 请求方法
consumes:请求的媒体类型 (Content- Type),如 application/json
produces: 响应的媒体类型- params, headers: 请求的参数及请求头的值
1. 路由规则
@RequestMapping 的 value 属性用于匹配 URL 映射 ,value 支持简单表达式
@RequestMapping (“/user”)
@RequestMapping 支持使用通配符匹配 URL,用于统一映射某些 URL 规则类似的请求: @RequestMapping (“/getJson/*. json”), 当在浏览器中请求/getJson/a.json 或者/getJson/b.json 时都会匹配到后台的 Json 方法
@RequestMapping 的通配符匹配非常简单实用,支持**“*“ “?” “**“ ** 等通配符
符号 “*“ 匹配任意字符 ,符号 “**“ 匹配任意路径 ,符号 “?” 匹配单个字符 。有通配符的优先级低于没有通配符的,比如/user/addjson 比/user/* .json 优先匹配。有“**” 通配符的优先级低于有”*“ 通配符的。
2. Method 匹配
HTTP 请求 Method 有 GET、POST、 PUT、DELETE 等方式。HTTP 支持的全部Method
@RequestMapping 注解提供了 method 参数指定请求的 Method 类型,包括RequestMethod. GET、RequestMethod. POST、RequestMethod.DELETE、RequestMethod. PUT 等值,分别对应 HTTP 请求的 Method1 2 3 4 @RequestMapping (value = "/getData" , method = RequestMethod.GET)public String getData () { return "hello" ; }
Method 匹配也可以使用@GetMapping、@PostMapping 等注解代替。
3. 参数传递 1. get 请求
视图函数定义 query_params 路由参数,函数体内可以直接获取该参数。
demo:
方式 1 同参(参数可有可无都能成功响应)
1 2 3 4 5 6 7 8 9 @RestController public class HelloController { @RequestMapping(value = "/index1",method = RequestMethod.GET) public String hello1 (String name) { return "欢迎您:" +name; } }
方式 2 不同参(参数必须携带,否则 400 错误响应)
将参数 usrname 映射为 name,默认 require=true (必须携带参数否则报错),设置为 false 可以不携带参数访问 public String hello2(@RequestParam(value = "usrname",required = false) String name)
1 2 3 4 5 @RequestMapping(value = "/index2",method = RequestMethod.GET) public String hello2 (@RequestParam("usrname") String name) { return "欢迎您:" +name; }
通配符匹配路由:
1 2 3 4 5 @GetMapping("test/*") @GetMapping("test/**") public String test () { return "匹配同级任意路径" ; }
2.Post 请求 1. urlencode 格式数据 方式 1 直接传数据:
适合参数少的情景
1 2 3 4 5 6 @PostMapping("login/") public String login (String name,String pwd) { System.out.println("name:" +name); System.out.println("pwd:" +pwd); return name!=null && pwd!=null ? "登陆成功" : "登陆失败" ; }
方式 2 对象传数据:
适合参数多的情景,User 为实体类
1 2 3 4 5 6 7 @PostMapping("login2/") public String login2 (User user) { System.out.println("name:" + user.getUsername()); System.out.println("pwd:" + user.getPassword()); System.out.println(user); return user.getUsername() != null && user.getPassword() != null ? "登陆成功" : "登陆失败" ; }
[!NOTE] Tips上述两种方式,发送的 data 数据必须经过 urlencode 编码,否则接收不到。
2. json 格式数据
需要给视图函数形参中添加注解@RequestBody ,且 json data 中的参数键名需要与后端中的实体类的属性并一致。
1 2 3 4 5 6 7 @PostMapping("login3/") public String login3 (@RequestBody User user) { System.out.println("name:" + user.getUsername()); System.out.println("pwd:" + user.getPassword()); System.out.println(user); return user.getUsername() != null && user.getPassword() != null ? "登陆成功" : "登陆失败" ; }
3. 文件上传 1. 静态资源访问
使用 IDEA 创建 Spring Boot 项目,会默认创建出 classpath:/static/ 目录, 静态资源一般放在这个目录下即可。
如果默认的静态资源过滤策略不能满足开发需求,也可以自定义静态资源过滤策略。
在 application. properties 中直接定义过滤规则和静态资源位置:
spring.mvc.static-path-pattern=/static/\*\*
spring.web.resources.static-locations=classpath:/static/
过滤规则为/static/** ,静态资源位置为 classpath:/static/
1 2 3 spring.mvc.static-path-pattern =images/** spring.web.resources.static-locations =classpath:/static/images/
2. 文件上传 1. 文件上传原理
表单的 enctype 属性 规定在发送到服务器之前对表单数据的编码方式 。
当表单的 enctype=”application/x-www-form-urlencoded “ (默认)时, form 表单中的数据格式为: key=value&key=value
当表单的 enctype=”multipart/form-data “时,其传输数据形式如下:
2. 配置文件大小
Spring Boot 工程嵌入的 tomcat 限制了请求的文件大小,每个文件的配置最大为 1 Mb,单次请求的文件的总数不能大于 10 Mb.
要更改这个默认值需要在配置文件 (如 application.properties ) 中加入两个配置1 2 spring.servlet.multipart.max-file-size=10MB spring.servlet.multipart.max-request-size=10MB
当表单的 enctype= “multipart/form-data “时, 可以使用 MultipartFile 获取上传的文件数据,再通过 transferTo 方法将其写入到磁盘中
demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package com.alleyf.helloworld.controller; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; @RestController public class FileUploadController { @PostMapping("/upload") public String upload (String name, MultipartFile avatar, HttpServletRequest request) throws IOException { System.out.println(name); System.out.println("filename:" + avatar.getOriginalFilename()); System.out.println(avatar.getContentType()); String path = "E:\\IDEAProjects\\helloworld\\src\\main\\resources\\static\\images\\" ; System.out.println(path); saveFile(avatar, path); return "上传成功" ; } private void saveFile (MultipartFile avatar, String path) throws IOException { File dir = new File (path); if (!dir.exists()) { dir.mkdir(); } File file = new File (path + avatar.getOriginalFilename()); System.out.println(file.getPath()); avatar.transferTo(file); } }
3. 拦截器 简介:
拦截器在 Web 系统中非常常见,对于某些全局统一-的操作,我们可以把它提取到拦截器中实现。总结起来,拦截器大致有以下几种使用场景:
权限检查: 如登录检测,进入处理程序检测是否登录,如果没有,则直接返回登录页面。 3. 性能监控: 有时系统在某段时间莫名其妙很慢,可以通过拦截器在进入处理程序之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间
通用行为: 读取 cookie 得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有提取 Locale、Theme 信息等,只要是多个处理程序都需要的,即可使用拦截器实现。
[!NOTE] tips
Spring Boot 定义了 HandlerInterceptor 接口 来实现自定义拦截器的功能
HandlerInterceptor 接口定义了 preHandle、postHandle、 afterCompletion 三种方法,通过重写这三种方法实现请求前、请求后等操作
1. 拦截器定义
类似于 django 的 Midleware 中间件,控制请求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 pub1ic class LoginInterceptor extends HandlerInterceptor {@override pub1ic boolean preHandle (HttpServ1etRequest request, HttpServ1etResponse response, object handTer) throws Exception { if (条件) { System.out.print1n("通过" ); return true ; }else { System.out.print1n("不通过" ); return false ; } } }
返回为 true 则进入下一个拦截器,否则拒绝通过。
2. 拦截器注册
addPathPatterns 方法定义拦截的地址
excludePathPatterns 定义排除某些地址不被拦截
添加的一个拦截器没有 addPathPattern 任何一个 url 则默认拦截所有请求
如果没有 excludePathPatterns 任何一个请求,则默认不放过任何一个请求
1 2 3 4 5 6 7 8 9 10 11 @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor ()); } }
4. RESTful 1.RESTful 介绍
HTTP 提供了 POST、GET、 PUT、DELETE 等操作类型对某个 Web 资源进行 Create、Read、 Update 和 Delete 操作。
一个 HTTP 请求除了利用 URI 标志目标资源之外,还需要通过 HTTP Method 指定针对该资源的操作类型,一些常见的 HTTP 方法及其在 RESTful 风格下的使用:
HTTP 状态码
HTTP 状态码就是服务向用户返回的状态码和提示信息,客户端的每一次请求,服务都必须给出回应,回应包括 HTTP 状态码和数据 两部分。
HTTP 定义了 40 个标准状态码,可用于传达客户端请求的结果。状态码分为以下5 个类别:
1 xx: 信息,通信传输协议级信息 2 xx: 成功,表示客户端的请求已成功接受 3 xx: 重定向,表示客户端必须执行一些其他操作才能完成其请求 4 xx: 客户端错误,此类错误状态码指向客户端 5 xx: 服务器错误,服务器负责这写错误状态码
2. 构建 RESTful 应用接口 Spring Boot 提供的 spring-boot-starter-web 组件完全支持开发 RESTful API, 提供了与 REST 操作方式 (GET、POST、 PUT、DELETE) 对应的注解。
@GetMapping:
处理 GET 请求,获取资源。
@PostMapping:
处理 POST 请求,新增资源。
@PutMapping:
处理 PUT 请求,更新资源。
@DeleteMapping:
处理 DELETE 请求,删除资源。
@PatchMapping:
处理 PATCH 请求,用于部分更新资源。
类似于 django 的视图装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @RestController public cLass UserController {@GetMapping("/user/{id}") public String getUserById (@PathVariable int id) {return "根据ID获取用户" ; }@PostMapping("/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删除用户" ;1 } }
3. Swagger 生成 API 接口文档
Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务,是非常流行的 API 表达工具。
Swagger 能够自动生成完善的 RESTful API 文档,同时并根据后台代码的修改同步更新,同时提供完整的测试页面来调试 APl。
pom. xml 依赖配置:
1 2 3 4 5 6 7 8 9 10 11 <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 >
添加配置类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package com.alleyf.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; @Configuration @EnableSwagger2 public class SwaggerConfig { @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("学习Swagger2的演示项目" ) .version("1.0" ) .build(); } }
spring 3 的依赖:
1 2 3 4 5 6 7 8 9 10 11 12 <dependency > <groupId > org.springdoc</groupId > <artifactId > springdoc-openapi-starter-webmvc-ui</artifactId > <version > 2.0.2</version > </dependency > <dependency > <groupId > org.springdoc</groupId > <artifactId > springdoc-openapi-starter-webmvc-api</artifactId > <version > 2.0.2</version > </dependency >
配置文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.alleyf.helloworld.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Info; @Configuration public class OpenApiConfig { @Bean public OpenAPI springOpenAPI () { return new OpenAPI ().info(new Info () .title("SpringDoc API Test" ) .description("SpringDoc Simple Application Test" ) .version("0.0.1" )); } }
1.注解 swagger 常用注解如下图所示:
从 Springfox 迁移过来的,需要修改注解:
> 1. @Api → @Tag > 2. @ApiIgnore → @Parameter (hidden = true) or @Operation (hidden = true) or @Hidden > 3. @ApiImplicitParam → @Parameter > 4. @ApiImplicitParams → @Parameters > 5. @ApiModel → @Schema > 6. @ApiModelProperty (hidden = true) → @Schema (accessMode = READ_ONLY) > 7. @ApiModelProperty → @Schema > 8. @ApiOperation (value = “foo”, notes = “bar”) → @Operation (summary = “foo”, description = “bar”) > 9. @ApiParam → @Parameter > 10. @ApiResponse (code = 404, message = “foo”) → @ApiResponse (responseCode = “404”, description = “foo”)
2.访问 swagger
swagger 2: http://localhost:8080/swagger-ui.html
swagger 3:
5. MybatisPlus 1.ORM 介绍
2.MyBatis-Plus 介绍
MyBatis 是一款优秀的数据持久层 ORM 框架,被广泛地应用于应用系统。
MyBatis 能够非常灵活地实现动态 SQL,可以使用 XML 或注解来配置和映射原生信息,能够轻松地将 Java 的 POJO(PlainOrdinaryJavaObject,普通的Java 对象)与数据库中的表和字段进行映射关联。
MyBatis-Plus 是一个 MyBatis 的增强工具,在 MyBatis 的基础上做了增强,简化了开发。
添加依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <! MyBatisPlus依赖 <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4 .2 </version> </dependency> <!--mysql驱动依赖 <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1 .47 </version> </dependency> <!-- 数据连接池druid <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1 .20 </version> </dependency>
全局配置:
配置数据库相关信息(application.properties):
1 2 3 4 5 6 7 spring.datasource.type=com.alibaba.druid.pool.DruidDatasource spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.ur1=jdbc:mysql: spring.datasource.username=root spring.datasource.password=123456 mybatis-plus.configuration.1og-impl=org.apache.ibatis.logging.stdout.stdoutImp1
1 2 3 4 5 6 @springBootApplication @Mapperscan("com.xx.mapper") public class MybatisplusDemoApplication public static void main (string[] args) { SpringApplication.run(MybatisplusDemoApplication.class,args);
Mybitis CRUD 注解: 注解功能@Insert 实现插入 @Update 实现更新 @Delete 实现删除 @Select 实现查询 @Result 实现结果集封装 @Results 可以与@Result 一起使用,封装多个结果集 @One 实现一对一结果集封装 @Many 实现一对多结果集封装
3.MyBatis-Plus CRUD 操作 mybatis 的 Mapper 操作方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Mapper public interface UserMapper { @Insert("insert into user values(#{id),#(username},#(password),#(birthday)")//id,username等属性为user对象的属性 int add(User user); @update("update user set username=#(username},password=#(password),birthday=#(birthday} where id=#{id}") int update(User user); @Delete("delete from user where id=#(id") int delete(int id); @select("select * from user where id=#(id") User findByid(int id); @select("select * from user") List<User> getA11();
mybatis-plus 的操作方法
BaseMapper 泛型类里已经实现了基本的增删改查任务
1 2 3 4 @Mapper public interface UserMapper extends BaseMapper <User> { }
[!NOTE] 注意 Mybatis-plus 有一些对 entity 里的实体类的注解 ,标识对应的表名、主键和字段名等,如果不进行注解则实体类的类名必须与数据表名一致,属性必须与数据表的字段名一致。注解细节
4. 多表查询
实现复杂关系映射,可以使用@Results 注解,@Result 注解,@One 注解, @Many 注解组合完成复杂关系的配置。
示例:任务表
entity:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.alleyf.airesume.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import java.sql.Date; import java.time.LocalDateTime; @TableName(value = "u_task") public class Task { @TableId(value = "id", type = IdType.AUTO) private int id; private int uid; private String name; private String content; @TableField(value = "b_date") private LocalDateTime b_date; @TableField(value = "e_date") private LocalDateTime e_date; @TableField(exist = false) private User user; }
Mapper:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Mapper public interface TaskMapper extends BaseMapper <Task> { @Select("select * from task where uid = #{uid}") List<Task> selectByUid (int uid) ; @Select("select * from task") @Results( { @Result(column = "id", property = "id"), @Result(column = "name", property = "name"), @Result(column = "content", property = "content"), @Result(column = "b_date", property = "b_date", javaType = LocalDateTime.class, jdbcType = JdbcType.TIMESTAMP), @Result(column = "e_date", property = "e_date", javaType = LocalDateTime.class, jdbcType = JdbcType.TIMESTAMP), @Result(column = "uid", property = "user", javaType = User.class, one = @One(select = "com.alleyf.airesume.mapper.UserMapper.selectById")), } ) List<Task> queryAllTaskAndUser () ; }
用户表
entity:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.alleyf.airesume.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import java.util.List; @TableName(value = "user") public class User { @TableId(value = "id", type = IdType.AUTO) private int id; private String username; private String password; @TableField(exist = false) private List<Task> tasks; }
mapper:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public interface UserMapper extends BaseMapper <User> { @Select("select * from user") @Results( { @Result(column = "id", property = "id"), @Result(column = "username", property = "username"), @Result(column = "password", property = "password"), @Result(column = "id", property = "tasks", javaType = List.class, many = @Many(select = "com.alleyf.airesume.mapper.TaskMapper.selectByUid")), } ) List<User> queryAllUserAndTasks () ; }
[!NOTE] 注意 查询用户的同时查出与用户相关联的所有任务 Result 中的 column 的字段为查询到的数据库字段值,用来赋值给后面类对象的属性 property,对应的属性与字段相同,含有不存在的属性则使用外键间接查询。
5. 条件查询 Mybatis 实现:
在 mapper 的接口中写 sql 语句进行条件查询。
示例: mapper:
1 2 3 4 5 6 7 8 9 10 11 12 13 @Select("select * from user where username = #{username}") @Results({ @Result(column = "id", property = "id"), @Result(column = "username", property = "username"), @Result(column = "password", property = "password"), @Result(column = "id", property = "tasks", javaType = List.class, many = @Many(select = "com.alleyf.airesume.mapper.TaskMapper.selectByUid")), }) User selectByName (String username) ;
controller:
1 2 3 4 5 6 7 @ApiOperation("按照用户名查询用户") @GetMapping("/queryByMName") public User queryByMName (@RequestParam("username") String username) { return userMapper.selectByName(username); }
Mybatis-Plus 实现:
使用 QueryWrapper (条件查询)和 UpdateWrapper (条件更新) 两个条件查询类进行条件查询。
可选条件有:eq(等于),lt(大于),st(小于),le(大于等于),se(小于等于)等
示例:
1 2 3 4 5 6 @ApiOperation("按照用户名查询用户(MP)") @GetMapping("/queryByMPName") public User queryByMPName (@RequestParam("username") String username) { return userMapper.selectOne(new QueryWrapper <User>().eq("username" , username)); }
6. 分页查询 编写配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.alleyf.airesume.config; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class PaginationConfig { @Bean public MybatisPlusInterceptor paginationInterceptor () { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor (); PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor (DbType.MYSQL); interceptor.addInnerInterceptor(paginationInterceptor); return interceptor; } }
controller:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @GetMapping("findAll") public IPage findAl1 () t Page<User> page = new Page <>(0 ,2 ); return userMapper.selectPage(page,nul1);@ApiOperation("按照页码查询用户(MP)") @GetMapping("/queryByPage/{page}") public IPage queryByPage (@PathVariable("page") int page) { Page<User> page1 = new Page <>(page, 5 ); IPage iPage = userMapper.selectPage(page1, null ); return iPage; }
6. JWT 跨域认证 1. Session 认证 互联网服务离不开用户认证。一般流程是下面这样。
用户向服务器发送用户名和密码 。
服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等。
服务器向用户返回一个 session_id,写入用户的 Cookie。
用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。
服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。
session 认证流程: session 认证的方式应用非常普遍,但也存在一些问题,扩展性不好,如果是服务 器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能 够读取 session,针对此种问题一般有两种方案:
一种解决方案是 session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。
一种方案是服务器不再保存 session 数据,所有数据都保存在客户端,每次请求都发回服务器。Token 认证就是这种方案的一个代表。
2. Token 认证 Token 是在服务端产生的一串字符串, 是客户端访问资源接口(API) 时所需要的资 源凭证,流程如下:
客户端使用用户名跟密码请求登录,服务端收到请求,去验证用户名与密码
验证成功后,服务端会签发一个 token 并把这个 token 发送给客户端
客户端收到 token 以后,会把它存储起来,比如放在 cookie 里或者 localStorage 里
客户端每次向服务端请求资源的时候需要带着服务端签发的 token
服务端收到请求,然后去验证客户端请求里面带着的 token,如果验证成功,就向客户端返回请求的数据
基于 token 的用户认证是一种服务端无状态的认证方式,服务端不用存放 token 数据。
用解析 token 的计算时间换取 session 的存储空间,从而减轻服务器的压力 减少频繁的查询数据库
token 完全由应用管理,所以它可以避开同源策略
3. JWT 的使用
JSON Web Token(简称 JWT)是一个 token 的具体实现方式,是目前最流行 的跨域认证解决方案。 JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,具体如下:
1 2 3 "姓名" : "张三" , "角色" : "管理员" , "到期时间" : "2018 年 7 月 1 日 0 点 0 分"
用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。 为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名 。
JWT 的由三个部分组成,依次如下: Header (头部) Payload (负载) Signature (签名) 三部分最终组合为完整的字符串,中间使用·分隔,如下: Header.Payload.Signature eyJhbGci0iJIUzI1NiIsInR5cCI6IkpXVCJ9. eyJzdwIi0iIxMjMoNTY30DkwIiwibmFtzsI6IkpvaG4 gRG91IiwiaXNTb2NpYWwiOnRydwV9. 4pcPyMD09o1PSyXnrXCjTwXyr4BsezdI1AVTmud2fU4
Header 部分是一个 JSON 对象,描述 JWT 的元数据
1 2 3 4 { "alg" : "H256" , "typ" : "JWT" }
alg 属性表示签名的算法(algorithm ),默认是 HMAC SHA 256 (写成HS256 )
typ 属性表示这个令牌(token)的类型(type),JWT 令牌统一写为 JWT
最后,将上面的 JSON 对象使用 Base 64 URL 算法转成字符串。
Payload
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了 7 个官方字段,供选用。
iss (issuer):签发人
exp (expiration time): 过期时间
sub (subject): 主题
aud (audience): 受众
nbf (Not Before): 生效时间
iat (Issued At):签发时间
jti (WT ID): 编号
注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在个部分。 这个 JSON 对象也要使用 Base 64 URL 算法转成字符串。
Signature
Signature 部分是对前两部分的签名,防止数据篡改。 首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户; 然后,使用 Header 里面指定的签名算法 (默认是 HMAC SHA 256),按照下面的公式产生签名。
1 2 3 4 HMACSHA 256 ( base64UrlEncode (header) + "." + base64UrlEncode (payload), secret)
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用”点”(’.’)分隔,就可以返回给用户。
特点
客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。
客户端每次与服务器通信,都要带上这个 JWT,可以把它放在 Cookie 里面自动发送,但是这样不能跨域。
更好的做法是放在 HTTP 请求的头信息’Authorization’字段里面,单独发送。
请求认证
JWT验证拦截器
定义拦截器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package com.alleyf.config; import com.alibaba.fastjson2.JSON; import com.alleyf.sys.utils.Result; import com.alleyf.common.JwtUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Component @Slf4j public class JwtValidateInterceptor implements HandlerInterceptor { @Autowired private JwtUtils jwtUtils; @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("X-Token" ); log.debug(request.getRequestURI() + "待验证:" + token); if (token != null ) { try { jwtUtils.getClaimsByToken(token); log.debug(request.getRequestURI() + " 验证通过" ); return true ; } catch (Exception e) { e.printStackTrace(); } } log.debug(request.getRequestURI() + " 禁止访问" ); response.setContentType("application/json;charset=utf-8" ); response.getWriter().write(JSON.toJSONString(Result.error().message("jwt令牌无效,请重新登录" ))); return false ; } }
配置使用拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package com.alleyf.config; import com.alleyf.config.JwtValidateInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class MyInterceptorConfig implements WebMvcConfigurer { @Autowired private JwtValidateInterceptor jwtValidateInterceptor; @Override public void addInterceptors (InterceptorRegistry registry) { InterceptorRegistration registration = registry.addInterceptor(jwtValidateInterceptor); registration.addPathPatterns("/**" ).excludePathPatterns( "/user/login" , "/user/register" , "/user/logout" , "/user/info" , "/error" ); } }
Swagger授权配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 package com.alleyf.config; import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j; import io.swagger.annotations.Api; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.context.request.async.DeferredResult; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.oas.annotations.EnableOpenApi; import springfox.documentation.service.*; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spi.service.contexts.SecurityContext; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; import java.util.Collections; import java.util.List; @Configuration @EnableKnife4j @EnableOpenApi public class SwaggerConfig { @Bean public Docket Api () { return new Docket (DocumentationType.OAS_30) .enable(true ) .groupName("spring-boot-2.7.12" ) .genericModelSubstitutes(DeferredResult.class) .useDefaultResponseMessages(false ) .forCodeGeneration(true ) .ignoredParameterTypes(CookieValue.class) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.withClassAnnotation(Api.class)) .paths(PathSelectors.any()) .build() .securitySchemes(Collections.singletonList(securityScheme())) .securityContexts(Collections.singletonList(securityContext())); } private SecurityScheme securityScheme () { return new ApiKey ("X-Token" , "X-Token" , "header" ); } private SecurityContext securityContext () { return SecurityContext.builder() .securityReferences(defaultAuth()) .forPaths(PathSelectors.regex("^(?!auth).*$" )) .build(); } private List<SecurityReference> defaultAuth () { AuthorizationScope authorizationScope = new AuthorizationScope ("global" , "accessEverything" ); AuthorizationScope[] authorizationScopes = new AuthorizationScope [1 ]; authorizationScopes[0 ] = authorizationScope; return Collections.singletonList( new SecurityReference ("X-Token" , authorizationScopes)); } private ApiInfo apiInfo () { return new ApiInfoBuilder () .title("WHUT leave Go-Swagger3接口文档" ) .description("WHUT leave Go-前后端分离的接口文档" ) .version("1.0" ) .contact(new Contact ("alleyf" , "https://fcsy.com" , "alleyf@qq.com" )) .build(); } }
后端实现 加入依赖 1 2 3 4 5 <dependency > <groupId > io.jsonwebtoken</groupId > <artifactId > jjwt</artifactId > <version > 0.9.1</version > </dependency >
生成 Token 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private static Long expire = 604800 ;private static String secret = "abcdfghiabcdfghiabcdfghiabcdfghi" ;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 1 2 3 4 5 6 7 public static Claims getClaimsByToken (String token) { return Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); }
后端完整部分 UserController. java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 package com.alleyf.airesume.controller; import com.alleyf.airesume.entity.User; import com.alleyf.airesume.mapper.UserMapper; import com.alleyf.airesume.utils.JwtUtils; import com.alleyf.airesume.utils.Result; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; @Api(tags = "用户", value = "用户") @RestController @CrossOrigin @RequestMapping("/user") public class UserController { @Autowired UserMapper userMapper; @ApiOperation("用户登录") @PostMapping("/login") public Result login (@RequestBody User user) { String token = JwtUtils.generateToken(user.getUsername()); return Result.ok().data("token" , token); } @ApiOperation("获取用户信息") @GetMapping("/info") public Result info (String token) { String username = JwtUtils.getClaimsByToken(token).getSubject(); String url = "https://img2.baidu.com/it/u=1325995315,4158780794&fm=26&fmt=auto&gp=0.jpg" ; return Result.ok().data("name" , username).data("avatar" , url); } @ApiOperation("注销") @PostMapping("/logout") public Result logout () { return Result.ok(); } @ApiOperation("查询所有用户") @GetMapping("/queryAll") public List<User> queryAllUser () { return userMapper.queryAllUserAndTask(); } @ApiOperation("按照用户名查询用户") @GetMapping("/queryByMName") public User queryByMName (@RequestParam("username") String username) { return userMapper.selectByName(username); } @ApiOperation("按照用户名查询用户(MP)") @GetMapping("/queryByMPName") public User queryByMPName (@RequestParam("username") String username) { return userMapper.selectOne(new QueryWrapper <User>().eq("username" , username)); } @ApiOperation("按照用户名路径查询用户(MP)") @GetMapping("/queryByPMPName/{username}") public User queryByPMPName (@PathVariable("username") String username) { return userMapper.selectOne(new QueryWrapper <User>().eq("username" , username)); } @ApiOperation("按照页码查询用户(MP)") @GetMapping("/queryByPage/{page}") public IPage queryByPage (@PathVariable("page") int page) { Page<User> page1 = new Page <>(page, 5 ); IPage iPage = userMapper.selectPage(page1, null ); return iPage; } @ApiOperation("添加用户") @PostMapping("/add") public String addUser (User user) { return userMapper.insert(user) > 0 ? "添加成功" : "添加失败" ; } }
Result.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 package com.alleyf.airesume.utils; import java.util.HashMap; import java.util.Map; public class Result { private Boolean success; private Integer code; private String message; private Map<String, Object> data = new HashMap <>(); private Result () { } public static Result ok () { Result r = new Result (); r.setCode(ResultCode.Success); r.setSuccess(true ); r.setMessage("成功" ); return r; } public static Result error () { Result r = new Result (); r.setCode(ResultCode.Error); r.setSuccess(false ); r.setMessage("失败" ); return r; } public Result success (Boolean success) { this .setSuccess(success); return this ; } public Result message (String message) { this .setMessage(message); return this ; } public Result code (Integer code) { this .setCode(code); return this ; } public Result data (String key, Object value) { this .data.put(key, value); return this ; } public Result data (Map<String, Object> map) { this .setData(map); return this ; } public Boolean getSuccess () { return success; } public void setSuccess (Boolean success) { this .success = success; } public Integer getCode () { return code; } public void setCode (Integer code) { this .code = code; } public String getMessage () { return message; } public void setMessage (String message) { this .message = message; } public Map<String, Object> getData () { return data; } public void setData (Map<String, Object> data) { this .data = data; } }
JwtUtils.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package com.alleyf.airesume.utils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import java.util.Date; public class JwtUtils { private static final Long expire = 604800L ; private static final String secret = "abcdfghiabcdfghiabcdfghiabcdfghi" ; 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(); } public static Claims getClaimsByToken (String token) { return Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); } }
项目实战-角色管理 1.预览效果
2.前端 role.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 <template> <div> <!-- 添加角色--> <el-dialog :title="dialogTitle" :visible.sync="dialogFormVisible" center width="32%" @close="clearForm" > <el-form ref="roleForm" :model="roleForm" :rules="rules" show-message status-icon > <el-form-item label="角色名" label-width="80px" prop="roleName"> <el-input v-model="roleForm.roleName" clearable prefix-icon="el-icon-user" /> </el-form-item> <el-form-item label="角色描述" label-width="80px" prop="roleDesc"> <el-input v-model="roleForm.roleDesc" clearable prefix-icon="el-icon-key" /> </el-form-item> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="dialogFormVisible = false">取 消</el-button> <el-button type="primary" @click="saveRole('roleForm')">确 定 </el-button> </div> </el-dialog> <!-- 角色检索--> <el-card id="search" shadow="hover"> <el-row :gutter="5"> <el-col :span="18"> <div class="grid-content bg-purple"> <el-input v-model="searchModel.queryContent" clearable placeholder="请输入用户名" suffix-icon="el-icon-user" > <el-button slot="append" icon="el-icon-search" type="success" @click="getRoles" /> </el-input> </div> </el-col> <el-col :span="6" align="right"> <div class="grid-content bg-purple"> <el-button icon="el-icon-plus" size="medium" type="success" @click="addRole" /> </div> </el-col> </el-row> </el-card> <!-- 用户信息--> <el-card shadow="hover"> <el-table v-loading="loading" :cell-style="{ 'text-align': 'center' }" :data="tableData" :default-sort="{ prop: 'id', order: 'ascending' }" :header-cell-style="{ 'text-align': 'center' }" border max-height="520" style="width: 100%" > <el-table-column label="#" type="index" width="80"> <template slot-scope="scope"> <!-- (pageNum-1)*pageSize+index+1--> {{ (searchModel.pageNum - 1) * searchModel.pageSize + scope.$index + 1 }} </template> </el-table-column> <el-table-column label="角色id" prop="roleId" sortable width="200"/> <el-table-column class="name-wrapper" label="角色名称" prop="roleName" width="260" /> <el-table-column label="角色描述" prop="roleDesc" resizable width="280"/> <el-table-column fixed="right" label="操作" width="160"> <template slot-scope="scope"> <el-button circle icon="el-icon-delete" size="small" type="danger" @click.native.prevent="deleteRole(scope.$index, tableData)" > </el-button> <el-button circle icon="el-icon-edit" size="small" type="primary" @click="editRole(scope.row)" > </el-button> </template> </el-table-column> </el-table> </el-card> <!-- 分页组件--> <el-footer align="center"> <div class="block"> <el-pagination :current-page="searchModel.currentPage" :page-size="searchModel.pageNum" :page-sizes="pageSizes" :total="total" layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange" @current-change="handleCurrentChange" /> </div> </el-footer> <el-backtop target=".page-component__scroll .el-scrollbar__wrap"></el-backtop> </div> </template> <script> import roleApi from '@/api/roleManage' import current from 'element-ui/packages/table/src/store/current' // import '@/styles/roleManage.css' export default { name: 'Role', data() { return { searchModel: { queryContent: '', pageNum: 1, pageSize: 10 }, pageSizes: [10, 20, 50, 100], total: 0, tableData: [], loading: true, dialogFormVisible: false, dialogTitle: '添加角色', roleForm: { roleName: '', roleDesc: '' }, rules: { roleName: [ { required: true, message: '请输入角色名', trigger: 'blur' }, { min: 1, max: 20, message: '长度在 1 到 20 个字符', trigger: 'blur' } ], roleDesc: [ { required: true, message: '请输入角色描述', trigger: 'blur' }, { min: 1, max: 30, message: '长度在 3 到 30 个字符', trigger: 'blur' } ] } } }, computed: { current() { return current } }, created() { this.getRoles() }, methods: { getRoles() { roleApi .search(this.searchModel) .then((res) => { // console.log(res) this.tableData = res.data.data.records this.total = res.data.data.total this.searchModel.pageNum = res.data.data.current this.loading = false // console.log(this.searchModel.queryContent, this.searchModel.queryItem, this.searchModel.pageSize, this.searchModel.pageNum) }) .catch((error) => { console.log(error) }) }, saveRole(formName) { // alert(this.roleForm) // 提交则触发表单验证 this.$refs[formName].validate((valid) => { if (valid) { console.log(this.roleForm) // 提交请求到后台 roleApi .save(this.roleForm) .then((res) => { // console.log(res) this.dialogFormVisible = false // 提交结果消息提示 this.notify(res, this.message) // 刷新表格 this.getRoles() }) .catch((error) => { console.log(error) }) } else { console.log('error submit!!') return false } }) }, clearForm() { this.roleForm = {} this.$refs.roleForm.clearValidate() }, handleSizeChange(pageSize) { this.searchModel.pageSize = pageSize this.getRoles() }, handleCurrentChange(pageNum) { this.searchModel.pageNum = pageNum this.getRoles() }, addRole() { this.dialogTitle = '添加角色' this.dialogFormVisible = true this.message = '添加角色成功!' }, deleteRole(index, rows) { // console.log(rows[index]) this.$confirm(`此操作将永久删除角色<strong style="color: red">${rows[index].roleName}</strong>, 是否继续?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', center: true, dangerouslyUseHTMLString: true }).then(() => { roleApi.del(rows[index].roleId).then((res) => { // console.log(res) rows.splice(index, 1) this.$message({ type: 'success', message: res.message }) }) }).catch(() => { this.$message({ type: 'info', message: '已取消删除' }) }) }, editRole(row) { roleApi.getById(row.roleId).then(res => { this.roleForm = res.data.data // console.log(this.userForm) this.dialogTitle = '编辑角色' this.dialogFormVisible = true this.message = '更新角色成功!' }).catch(error => { console.log(error) }) }, notify(res, message = null) { if (res.code === 20000) { // 提交结果消息提示 this.$notify({ title: '成功', message: message != null ? message : res.message, type: 'success' }) } else { this.$notify.error({ title: '失败', message: res.message }) } } } } </script> <style scoped></style>
roleManage.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import request from '@/utils/request' export default { search (searchModel) { return request ({ url : '/role/search' , method : 'get' , params : { queryContent : searchModel.queryContent , pageSize : searchModel.pageSize , pageNum : searchModel.pageNum } }) }, save (roleForm) { return request ({ url : '/role/save' , method : 'post' , data : roleForm }) }, del (id) { return request ({ url : `/role/${id} ` , method : 'delete' }) }, getById (id) { return request ({ url : `/role/${id} ` , method : 'get' }) } }
3.后端 RoleController:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 package com.alleyf.sys.controller; import com.alleyf.sys.entity.Role; import com.alleyf.sys.service.IRoleService; import com.alleyf.sys.utils.Result; import com.baomidou.mybatisplus.core.metadata.IPage; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.util.Json; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.stereotype.Controller; @RestController @Slf4j @RequestMapping("/role") @Api(tags = {"角色"}) public class RoleController { @Autowired IRoleService roleService; @ApiOperation("按照角色名查询角色(MP)") @GetMapping("/queryByName") public Result queryByName (@RequestParam("roleName") String roleName) { Role role = roleService.queryByName(roleName); if (role != null ) { return Result.ok().data("data" , role); } else { return Result.error().message(roleName + "角色不存在" ); } } @ApiOperation("按照角色名路径查询角色(MP)") @GetMapping("/queryByPName/{roleName}") public Result queryByPName (@PathVariable("roleName") String roleName) { Role role = roleService.queryByName(roleName); if (role != null ) { return Result.ok().data("data" , role); } else { return Result.error().message(roleName + "角色不存在" ); } } @ApiOperation("按照id路径查询角色") @GetMapping("/{id}") public Result queryById (@PathVariable("id") Integer id) { Role role = roleService.getById(id); if (role != null ) { return Result.ok().data("data" , role); } else { return Result.error().message("id为:" + id + "的角色不存在" ); } } @ApiOperation("按照页码查询角色(MP)") @GetMapping("/queryByPage") public Result queryByPage (@RequestParam("pageNum") Long pageNum, @RequestParam("pageSize") Long pageSize) { IPage<Role> roleIPage = roleService.queryByPage(pageNum, pageSize, null ); return roleIPage != null ? Result.ok().data("page" , roleIPage) : Result.error().message("平台没有角色" ); } @ApiOperation("添加或更新角色") @PostMapping("/save") public Result addRole (@RequestBody Role role) { boolean saveStatus = roleService.saveOrUpdate(role); return saveStatus ? Result.ok().message("添加或更新角色成功" ) : Result.error().message("添加或更新角色失败" ); } @ApiOperation("删除角色") @DeleteMapping("/{id}") public Result delRole (@PathVariable("id") Integer id) { boolean saveStatus = roleService.removeById(id); return saveStatus ? Result.ok().message("删除角色成功" ) : Result.error().message("删除角色失败" ); } @ApiOperation("搜索角色") @GetMapping("/search") public Result search (@RequestParam(value = "queryContent", required = false) String queryContent, @RequestParam(value = "pageSize", defaultValue = "10") Long pageSize, @RequestParam(value = "pageNum", defaultValue = "1") Long pageNum) { IPage<Role> roles = roleService.search(queryContent, pageSize, pageNum); if (roles != null ) { log.debug("roles: " + roles); return Result.ok().data("data" , roles); } else { return Result.error().message("平台没有该角色" ); } } }
角色权限设置 1.效果预览
2.前端 role.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 <template> <div> <!-- 添加角色--> <el-dialog :title="dialogTitle" :visible.sync="dialogFormVisible" center width="32%" @close="clearForm" > <el-form ref="roleForm" :model="roleForm" :rules="rules" show-message status-icon > <el-form-item label="角色名" label-width="80px" prop="roleName"> <el-input v-model="roleForm.roleName" clearable prefix-icon="el-icon-user" /> </el-form-item> <el-form-item label="角色描述" label-width="80px" prop="roleDesc"> <el-input v-model="roleForm.roleDesc" clearable prefix-icon="el-icon-key" /> </el-form-item> <el-form-item label="权限设置" label-width="80px" prop="menuIdList"> <el-tree ref="menuRef" :data="menuList" :props="menuProps" default-expand-all node-key="menuId" show-checkbox > </el-tree> </el-form-item> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="dialogFormVisible = false">取 消</el-button> <el-button type="primary" @click="saveRole('roleForm')">确 定 </el-button> </div> </el-dialog> <!-- 角色检索--> <el-card id="search" shadow="hover"> <el-row :gutter="5"> <el-col :span="18"> <div class="grid-content bg-purple"> <el-input v-model="searchModel.queryContent" clearable placeholder="请输入用户名" suffix-icon="el-icon-user" > <el-button slot="append" icon="el-icon-search" type="success" @click="getRoles" /> </el-input> </div> </el-col> <el-col :span="6" align="right"> <div class="grid-content bg-purple"> <el-button icon="el-icon-plus" size="medium" type="success" @click="addRole" /> </div> </el-col> </el-row> </el-card> <!-- 用户信息--> <el-card shadow="hover"> <el-table v-loading="loading" :cell-style="{ 'text-align': 'center' }" :data="tableData" :default-sort="{ prop: 'id', order: 'ascending' }" :header-cell-style="{ 'text-align': 'center' }" border max-height="520" style="width: 100%" > <el-table-column label="#" type="index" width="80"> <template slot-scope="scope"> <!-- (pageNum-1)*pageSize+index+1--> {{ (searchModel.pageNum - 1) * searchModel.pageSize + scope.$index + 1 }} </template> </el-table-column> <el-table-column label="角色id" prop="roleId" sortable width="200"/> <el-table-column class="name-wrapper" label="角色名称" prop="roleName" width="260" /> <el-table-column label="角色描述" prop="roleDesc" resizable width="280"/> <el-table-column fixed="right" label="操作" width="160"> <template slot-scope="scope"> <el-button circle icon="el-icon-delete" size="small" type="danger" @click.native.prevent="deleteRole(scope.$index, tableData)" > </el-button> <el-button circle icon="el-icon-edit" size="small" type="primary" @click="editRole(scope.row)" > </el-button> </template> </el-table-column> </el-table> </el-card> <!-- 分页组件--> <el-footer align="center"> <div class="block"> <el-pagination :current-page="searchModel.currentPage" :page-size="searchModel.pageNum" :page-sizes="pageSizes" :total="total" layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange" @current-change="handleCurrentChange" /> </div> </el-footer> <el-backtop target=".page-component__scroll .el-scrollbar__wrap"></el-backtop> </div> </template> <script> import roleApi from '@/api/roleManage' import menuApi from '@/api/menuManage' import current from 'element-ui/packages/table/src/store/current' // import '@/styles/roleManage.css' export default { name: 'Role', data() { return { searchModel: { queryContent: '', pageNum: 1, pageSize: 10 }, pageSizes: [10, 20, 50, 100], total: 0, tableData: [], loading: true, dialogFormVisible: false, dialogTitle: '添加角色', roleForm: { roleName: '', roleDesc: '' }, rules: { roleName: [ { required: true, message: '请输入角色名', trigger: 'blur' }, { min: 1, max: 20, message: '长度在 1 到 20 个字符', trigger: 'blur' } ], roleDesc: [ { required: true, message: '请输入角色描述', trigger: 'blur' }, { min: 1, max: 30, message: '长度在 3 到 30 个字符', trigger: 'blur' } ] }, menuProps: { label: 'title', children: 'children' }, menuList: [] } }, computed: { current() { return current } }, created() { this.getRoles() this.getAllMenu() }, methods: { getAllMenu() { menuApi.getAllMenu().then(res => { this.menuList = res.data.data }) }, getRoles() { roleApi .search(this.searchModel) .then((res) => { // console.log(res) this.tableData = res.data.data.records this.total = res.data.data.total this.searchModel.pageNum = res.data.data.current this.loading = false // console.log(this.searchModel.queryContent, this.searchModel.queryItem, this.searchModel.pageSize, this.searchModel.pageNum) }) .catch((error) => { console.log(error) }) }, saveRole(formName) { // alert(this.roleForm) // 提交则触发表单验证 this.$refs[formName].validate((valid) => { if (valid) { const checkedKeys = this.$refs.menuRef.getCheckedKeys() const halfcheckedKeys = this.$refs.menuRef.getHalfCheckedKeys() this.roleForm.menuIdList = checkedKeys.concat(halfcheckedKeys) // console.log(this.roleForm.menuIdList) // console.log(this.roleForm) // 提交请求到后台 roleApi .save(this.roleForm) .then((res) => { // console.log(res) this.dialogFormVisible = false // 提交结果消息提示 this.notify(res, this.message) // 刷新表格 this.getRoles() }) .catch((error) => { console.log(error) }) } else { console.log('error submit!!') return false } }) }, clearForm() { this.roleForm = {} this.$refs.roleForm.clearValidate() this.$refs.menuRef.setCheckedKeys([]) }, handleSizeChange(pageSize) { this.searchModel.pageSize = pageSize this.getRoles() }, handleCurrentChange(pageNum) { this.searchModel.pageNum = pageNum this.getRoles() }, addRole() { this.dialogTitle = '添加角色' this.dialogFormVisible = true this.message = '添加角色成功!' this.getAllMenu() }, deleteRole(index, rows) { // console.log(rows[index]) this.$confirm(`此操作将永久删除角色<strong style="color: red">${rows[index].roleName}</strong>, 是否继续?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', center: true, dangerouslyUseHTMLString: true }).then(() => { roleApi.del(rows[index].roleId).then((res) => { // console.log(res) rows.splice(index, 1) this.$message({ type: 'success', message: res.message }) }) }).catch(() => { this.$message({ type: 'info', message: '已取消删除' }) }) }, editRole(row) { roleApi.getById(row.roleId).then(res => { this.roleForm = res.data.data console.log(res.data.data.menuIdList) this.dialogTitle = '编辑角色' this.dialogFormVisible = true this.$refs.menuRef.setCheckedKeys(res.data.data.menuIdList) this.message = '更新角色成功!' }).catch(error => { console.log(error) }) }, notify(res, message = null) { if (res.code === 20000) { // 提交结果消息提示 this.$notify({ title: '成功', message: message != null ? message : res.message, type: 'success' }) } else { this.$notify.error({ title: '失败', message: res.message }) } } } } </script> <style scoped></style>
3.后端 RoleController.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 package com.alleyf.sys.controller; import com.alleyf.sys.entity.Role; import com.alleyf.sys.service.IRoleService; import com.alleyf.sys.utils.Result; import com.baomidou.mybatisplus.core.metadata.IPage; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.util.Json; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.stereotype.Controller; import java.util.List; @RestController @Slf4j @RequestMapping("/role") @Api(tags = {"角色"}) public class RoleController { @Autowired IRoleService roleService; @ApiOperation("按照角色名查询角色(MP)") @GetMapping("/queryByName") public Result queryByName (@RequestParam("roleName") String roleName) { Role role = roleService.queryByName(roleName); if (role != null ) { return Result.ok().data("data" , role); } else { return Result.error().message(roleName + "角色不存在" ); } } @ApiOperation("按照角色名路径查询角色(MP)") @GetMapping("/queryByPName/{roleName}") public Result queryByPName (@PathVariable("roleName") String roleName) { Role role = roleService.queryByName(roleName); if (role != null ) { return Result.ok().data("data" , role); } else { return Result.error().message(roleName + "角色不存在" ); } } @ApiOperation("按照id路径查询角色") @GetMapping("/{id}") public Result queryById (@PathVariable("id") Integer id) { Role role = roleService.getRoleById(id); if (role != null ) { return Result.ok().data("data" , role); } else { return Result.error().message("id为:" + id + "的角色不存在" ); } } @ApiOperation("按照页码查询角色(MP)") @GetMapping("/queryByPage") public Result queryByPage (@RequestParam("pageNum") Long pageNum, @RequestParam("pageSize") Long pageSize) { IPage<Role> roleIPage = roleService.queryByPage(pageNum, pageSize, null ); return roleIPage != null ? Result.ok().data("page" , roleIPage) : Result.error().message("平台没有角色" ); } @ApiOperation("添加或更新角色") @PostMapping("/save") public Result addRole (@RequestBody Role role) { boolean saveStatus = roleService.addOrUpdate(role); return saveStatus ? Result.ok().message("添加或更新角色成功" ) : Result.error().message("添加或更新角色失败" ); } @ApiOperation("删除角色") @DeleteMapping("/{id}") public Result delRole (@PathVariable("id") Integer id) { boolean saveStatus = roleService.delete(id); return saveStatus ? Result.ok().message("删除角色成功" ) : Result.error().message("删除角色失败" ); } @ApiOperation("搜索角色") @GetMapping("/search") public Result search (@RequestParam(value = "queryContent", required = false) String queryContent, @RequestParam(value = "pageSize", defaultValue = "10") Long pageSize, @RequestParam(value = "pageNum", defaultValue = "1") Long pageNum) { IPage<Role> roles = roleService.search(queryContent, pageSize, pageNum); if (roles != null ) { log.debug("roles: " + roles); return Result.ok().data("data" , roles); } else { return Result.error().message("平台没有该角色" ); } } @ApiOperation("获取所有角色") @GetMapping("/all") public Result getAll () { List<Role> roles = roleService.list(); return Result.ok().data("data" , roles); } }
4.数据库 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 delete from x_menu;insert into ‘x_menu’('menu_id' ,‘component',‘path' ,‘redirect',‘name' ,‘title',‘icon' ,‘parent_id',‘is_leaf' , "hidden")values ('1",' Layout',"/sys' ,'/sys/user' ,'sysManage' ,系统管理',' userManage',' o',' N',' o'); insert into‘x_menu' (menu_id',‘component' ,‘path',‘redirect' ,‘name',‘title' ,‘icon',‘parent_id' ,‘is_leaf', "hidden")values(' 2 ',' sys/ user ',' user ',NULL,' userList',用户列表' ,'user' ,'1' ,'Y",' o'); insert into‘x_menu(' menu_id',‘component' ,‘path',‘redirect' ,‘name',‘title' ,‘icon',‘parent_id' ,‘is_leaf', ‘hidden' )values ('3' ,'sys/role' ,'role' ,NULL ,'roleList' ,'角色列表' ,'roleManage' , ,'Y''o' );insert into ‘x_menu’(menu_id',‘component' ,‘path',‘redirect' ,‘name',‘title' , ,icon',‘parent_id' ,‘is_leaf', "hidden' )values ('4' ,'Layout' ,'/test' ,'/test/test1' ,'test' ,'功能测试' ,'form' ,'o' 0 '); insert into‘x_menu’(menu_id' ,‘component',‘path' ,‘redirect',‘name' 'title' "icon',‘parent_id',‘is_leaf', "hidden")values("5 ',' test/ test1',' test1',"",' test1',' 测试点- ',' form', insert into‘x_menu’(' menu_id',‘component' ,‘path',‘redirect' ,‘name", 'title' 'icon',‘parent_id',is_leaf', ‘hidden')values('6','test/test2','test2', ,test2',测试点二",'form' 0 '); insert into‘x_menu(menu_id' ,‘component',path' ,‘redirect',‘name' , ,‘title',icon' ,parent_id,‘is_leaf, hidden')values(' 7 ',' test/ test3',' test3', ,' test3","测试点三',' form',' 4 '",' Y",o');
动态路由 1.前端 1.修改原路由配置 src/router/index.js,保留基础路由,其它的删掉或注释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 import Vue from 'vue' import Router from 'vue-router' Vue .use (Router ) import Layout from '@/layout' export const constantRoutes = [ { path : '/redirect' , component : Layout , hidden : true , children : [ { path : '/redirect/:path(.*)' , component : () => import ('@/views/redirect/index' ) } ] }, { path : '/login' , component : () => import ('@/views/login/index' ), hidden : true }, { path : '/404' , component : () => import ('@/views/404' ), hidden : true }, { path : '/' , component : Layout , redirect : '/dashboard' , children : [{ path : 'dashboard' , name : 'Dashboard' , component : () => import ('@/views/dashboard/index' ), meta : { title : '首 页' , icon : 'dashboard' , affix : true } }] }, { path : '*' , redirect : '/404' , hidden : true } ] const createRouter = ( ) => new Router ({ scrollBehavior : () => ({ y : 0 }), routes : constantRoutes }) const router = createRouter () export function resetRouter ( ) { const newRouter = createRouter () router.matcher = newRouter.matcher } export default router
2.获取菜单数据并保存到Vuex src/store/modules/user.js src/store/getters.js
3.路由转换 修改src目录下的permiss.js (1)导入Layout
1 import layout from '@/layout'
(2)添加动态路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 let myRoutes = myFilterAsyncRoutes (store.getters .menuList ) myRoutes.push ({ path : '*' , redirect : '/404' , hidden : true }) router.addRoutes (myRoutes) global .myRoutes = myRoutes next ({ ...to, replace : true })
完整部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 import router from './router' import store from './store' import { Message } from 'element-ui' import NProgress from 'nprogress' import 'nprogress/nprogress.css' import { getToken } from '@/utils/auth' import getPageTitle from '@/utils/get-page-title' import Layout from '@/layout' NProgress .configure ({ showSpinner : false }) const whiteList = ['/login' ] router.beforeEach (async (to, from , next) => { NProgress .start () document .title = getPageTitle (to.meta .title ) const hasToken = getToken () if (hasToken) { if (to.path === '/login' ) { next ({ path : '/' }) NProgress .done () } else { const hasGetUserInfo = store.getters .name if (hasGetUserInfo) { next () } else { try { await store.dispatch ('user/getInfo' ) const myRoutes = myFilterAsyncRoutes (store.getters .menuList ) myRoutes.push ({ path : '*' , redirect : '/404' , hidden : true }) router.addRoutes (myRoutes) global .myRoutes = myRoutes next ({ ...to, replace : true }) } catch (error) { await store.dispatch ('user/resetToken' ) Message .error ({ message : error || 'Has Error' }) next (`/login?redirect=${to.path} ` ) NProgress .done () } } } } else { if (whiteList.indexOf (to.path ) !== -1 ) { next () } else { next (`/login?redirect=${to.path} ` ) NProgress .done () } } }) router.afterEach (() => { NProgress .done () }) function myFilterAsyncRoutes (menuList ) { menuList.filter (menu => { if (menu.component === 'Layout' ) { menu.component = Layout } else { menu.component = require (`@/views/${menu.component} .vue` ).default } if (menu.children && menu.children .length ) { menu.children = myFilterAsyncRoutes (menu.children ) } return true }) return menuList }
4.路由合并 src/layout/components/Sidebar/index.vue
1 2 3 4 routes ( ) { return this .$router .options .routes .concat (global .myRoutes ) },
Vue 框架快速上手
[[Vue]]
1. 前端环境准备 Vscode 或者WebStorm
2.Vue 框架介绍 尤雨溪制作的渐进式 js 框架
3.Vue 快速入门
<script src="https://unpkg.com/vue@next"></script>
在页面中声明一个将要被 vue 所控制的 DOM 区域,既 MVVM 中的 View
`<div id="app">
{{ message }}
</div>`
const hello = { //指定数据源,既 MVVM 中的 Mode1 data: function () { return { message: 'Hello Vue!' } } const app = Vue.createApp (hello) app. mount(' #app ')//指定当前 vue 实例要控制页面的哪个区域
7. 项目部署 1. 云端环境准备 安装 Mysql
安装 Nginx 1 2 3 yum install epel-release yum update yum -y install nginx
nginx 命令
1 2 3 systemctl start nginx #开启nginx服务 systemctl stop nginx #停止nginx服务 systemctl restart nginx #重启nginx服务
配置 JDK 下载 JDK,登录官方 https://www.oracle.com/java/technologies/downloads/java8下载所需版本的JDK ,版本为 JDK1.8
解压
1 tar -zvxf jdk-8u131-linux-x64.tar.gz
编辑/etc/profile 文件
1 2 3 vi /etc/profile# 文件末尾增加 export JAVA_HOME=/usr/server/jdk1.8.0_131
执行 source 命令,使配置立即生效
检查是否安装成功
项目部署 部署 Vue 项目 打包 vue 项目 进入到 Vue 项目目录,执行
将生成的 dist 目录上传至服务器/usr/vue/dist
配置 nginx 进入到/etc/nginx/conf.d 目录,创建 vue.conf 文件,内容如下
1 2 3 4 5 6 7 8 9 server { listen 80; server_name locahost; 1ocation / { root /usr/app/dist; index index.html; } }
使配置生效
打包 Java 程序 因为 springboot 有内置 tomcat 容器,这点比较方便,省去了 tomcat 的部署。我们到时候直接可以直接把 jar 包扔到 linux 上
1 nohup java -jar demo-0.0.1-SNAPSHOT.jar > logName.1og 2>&1 &
参考文献
1天搞定SpringBoot+Vue全栈开发_哔哩哔哩_bilibili
Springdoc和Springfox