目录
- 引出
- 入门案例:登陆和注册 & 用户信息分页 之 固定的步骤:
- (1)建普通项目+配置pom.xml文件
- (2)写主启动类 + application.yml文件
- 【bug】pom.xml文件导了mybatis的包,但是application.yml文件没有配置
- (3)static静态资源 + templates前端页面
- (4)先用form表单尝试一下【有坑】关于日期传输和接收
- (5)用vue发送axios请求【主流】@RequestBody接收
- 集成mybatis:
- 1. 配置文件application.xml文件,加入mybatis相关
- 2. 注册功能的实现
- (1)核心sql语句,获取新增数据的id:useGeneratedKeys="true" keyProperty="id"
- (2)controll层代码
- (3)工具类1:判断一个对象除了自增id外,其他不为null,如果字符串,且不为空
- (3)工具类2:判断多个输入是否有空,支持Integer,String,Date类型
- (4)前端页面:给后端发送json对象{"username":this.username,"password":this.password}
- 3.登陆功能的实现
- (1)controller层代码
- (2)前端页面
- 4.用户信息分页展示 路径变量传参"/list/{pageNum}/{pageSize}",@PathVariable("pageNum")
- (1)controller层代码
- (2)前端页面:[[${username}]]
- 拦截器
- 1.拦截谁addInterceptors,配置类(@Configuration + implements WebMvcConfigurer )中
- 2.拦下来做什么—response.sendRedirect("/user/loginPage");,拦截器(@Component + implements HandlerInterceptor)
- 【bug】302重定向,ERR_TOO_MANY_REDIRECTS,如果配置类中的,excludePathPatterns忘记加第一个反斜杠 /
- 总结
引出
1.如何把mybatis也集成进spring中,有哪些步骤;
2.前端发送json对象,后端怎么接收;
3.前端传路径变量的rest风格,后端怎么接收处理;
4.spring的拦截的配置和使用;
入门案例:登陆和注册 & 用户信息分页 之 固定的步骤:
(1)建普通项目+配置pom.xml文件
<!-- 继承一个父--><parent><groupId>org.springframework.boot</groupId><version>2.3.0.RELEASE</version><artifactId>spring-boot-starter-parent</artifactId></parent><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><!-- 做web项目的包--><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 前端模板引擎,功能类似于jsp--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><!-- mysql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!-- 分页工具,匹配springBoot的jar包-->
<!-- 后面加spring-boot-starter,表示按照spring重新改写的版本--><dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>1.2.12</version></dependency><!-- mybatis的包--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.1</version></dependency><!-- druid--><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.16</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!-- 工具包--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.11</version></dependency></dependencies>
(2)写主启动类 + application.yml文件
主启动类
package com.tianju;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** springMVC的主启动类* 1.是主启动类;@SpringBootApplication* 2.启动:SpringApplication.run(Main.class);*/
@SpringBootApplication
public class Main {public static void main(String[] args) {SpringApplication.run(Main.class);}
}
application.yml文件,注意如果有mybatis包,则必须配置一下,不然会报错
server:port: 80
spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=trueusername: rootpassword: 123
【bug】pom.xml文件导了mybatis的包,但是application.yml文件没有配置
项目启动失败,报错信息如下:
报错信息:
Failed to configure a DataSource: ‘url’ attribute is not specified and no embedded datasource could be configured.
Reason: Failed to determine a suitable driver class
(3)static静态资源 + templates前端页面
(4)先用form表单尝试一下【有坑】关于日期传输和接收
报错信息:
Failed to convert from type [java.lang.String] to type [java.util.Date] for value ‘2023-06-08’
解决方案:controller层接收参数上加注解
@DateTimeFormat(pattern = "yyyy-MM-dd") Date birthday
前端代码:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>注册</title>
</head>
<body>
注册
<form action="/user/register" method="post">用户名:<input type="text" name="username">密码:<input type="text" name="username">性别:<select name="sex"><option value="male">男</option><option value="female">女</option></select>生日:<input type="date" name="birthday"><input type="submit" value="提交">
</form>
</body>
</html>
实体类
package com.tianju.entity;import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.text.Format;
import java.util.Date;/*** user实体类*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {private String username;private String password;private String sex;@JsonFormat(pattern = "yyyy-MM-dd")private Date birthday;}
controller层代码
package com.tianju.controller;import com.tianju.entity.ResData;
import com.tianju.entity.User;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;import java.util.Date;/*** 用于处理用户相关请求的controller* 1.在容器中;@Controller* 2.访问路径;一级目录,@RequestMapping("/user")*/@Controller
@RequestMapping("/user")
public class UserController {// 首先到登陆页面,响应一个页面@RequestMapping("/registerPage")public String registerPage(){// 返回string类型return "/user/register";}// 在登陆页面,用户点击登陆按钮,处理请求@RequestMapping("register")@ResponseBodypublic ResData register(String username, String password, String sex,@DateTimeFormat(pattern = "yyyy-MM-dd") Date birthday){System.out.println(birthday);User user = new User(username,password,sex,birthday);System.out.println(user);return new ResData(200, "ok", null);}
}
(5)用vue发送axios请求【主流】@RequestBody接收
要点:
- 前端传参用Json对象传;
- 后端接收需要加上@RequestBody注解;
- 实体类中规定日期的格式, @JsonFormat(pattern = “yyyy-MM-dd”)
前端发送对象的方式:
(1) user对象逐个赋值,发送post请求
let user = {}
user.username = this.username;
user.password = this.password;
user.sex = this.sex;
user.birthday = this.birthday;
axios.post("/user/register",user)
(2) 直接创建好user对象,发送post请求
let user = {"username":this.username,"password":this.password,"sex":this.sex,"birthday":this.birthday,
}
axios.post("/user/register",user)
后端接收要加上@RequestBody,在类上加 @JsonFormat(pattern = “yyyy-MM-dd”)
// 在登陆页面,用户点击登陆按钮,处理请求@RequestMapping("register")@ResponseBody// 如果前端用json对象发,后端需要加上@RequestBodypublic ResData register(@RequestBody User user){System.out.println(user);return new ResData(200, "ok", null);}
集成mybatis:
之前的模式下:
在spring中集成mybatis
1. 配置文件application.xml文件,加入mybatis相关
server:port: 80# 1.连接数据库——对应之前 xml文件的数据库
spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/javaweb?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=trueusername: rootpassword: 123# mybatis其他配置
mybatis:# 2.给实体类起别名,首字母小写type-aliases-package: com.tianju.entityconfiguration:# 3.开启驼峰命名map-underscore-to-camel-case: true# 4.让日志生效log-impl: org.apache.ibatis.logging.stdout.StdOutImpl# 5.扫描sql的位置mapper-locations: classpath:/mapper/*Mapper.xml
2. 注册功能的实现
(1)核心sql语句,获取新增数据的id:useGeneratedKeys=“true” keyProperty=“id”
<!-- TODO:如何知道新增的人的id--><insert id="add" useGeneratedKeys="true" keyProperty="id">INSERT INTO com_user(username,password,sex,birthday)VALUES (#{username},#{password},#{sex},#{birthday})</insert>
前端导包:
<link rel="stylesheet" href="/bootstrap/css/bootstrap.css"><script src="/js/jquery-3.5.1.js"></script><script src="/bootstrap/js/bootstrap.js"></script><script src="/js/vue.min-v2.5.16.js"></script><script src="/js/axios.min.js"></script>
(2)controll层代码
要点:
- 前端用json对象发,后端需要加上@RequestBody User user;
- 响应页面,不带数据,返回值为string
@Autowiredprivate IUserService userService;// 首先到注册页面,响应一个页面@RequestMapping("/registerPage")public String registerPage(){// 返回string类型return "/user/register";}// 在注册页面,用户点击注册按钮,处理请求@RequestMapping("register")@ResponseBody// 如果前端用json对象发,后端需要加上@RequestBodypublic ResData register(@RequestBody User user){System.out.println(user);System.out.println(EntityUtils.isAllNotNull(user));// 1.判断前端输入不为null或空,除了自增的idboolean allNotNull = EntityUtils.isAllNotNull(user);if (!allNotNull){System.out.println("输入有空");return new ResData(1001,"输入为空",null);}// 1.判断两个输入的密码是否一致if (!user.getPassword().equals(user.getRePassword())){return new ResData(1001,"两次密码不一致",null);}// 2.判断是否有重名的User userDB = userService.queryByUsername(user.getUsername());if (userDB!=null){System.out.println(userDB);return new ResData(1002,"用户名重复",null);}// 3.插入数据库中// 密码加密存储user.setPassword(SecureUtil.md5(user.getRePassword()));Integer addFlag = userService.add(user);System.out.println(user);if (addFlag<1){return new ResData(3001,"系统繁忙,请稍后",null);}return new ResData(200, "ok", null);}
(3)工具类1:判断一个对象除了自增id外,其他不为null,如果字符串,且不为空
EntityUtils.java文件
package com.tianju.util;import java.lang.reflect.Field;/*** 传入一个对象,判断对象里面的每个属性是否为null,或空;* 如果有null或空,返回false;否则,返回true;*/
public class EntityUtils {public static boolean isAllNotNull(Object obj) {Class<?> aClass = obj.getClass();// 获取所有的filesField[] fields = aClass.getDeclaredFields();for(Field field:fields){// 如果第一个是id,id是数据库自增的,前端不输入,就跳过if (field.getName().contains("id")){continue;}field.setAccessible(true);Object value = null;try {value = field.get(obj);}catch (Exception e) {throw new RuntimeException(e);}if (value==null || "".equals(value)){return false;}}return true;}
}
(3)工具类2:判断多个输入是否有空,支持Integer,String,Date类型
StringUtils.java文件
package com.tianju.util;import java.util.Date;/*** 判读输入是否为空,支持可变长度参数*/
public class StringUtils {/*** 最初始版本,只能判断string类型是否为null,空字符串*/public static Boolean isBlank(String str){if(str==null || str.trim().equals("")){return true;}return false;}/*** 升级版本,* 可以判断String,Integer,Date类型* 是不是null,string判断是不是空* @param objs 可变长度参数* @return*/public static Boolean isBlank(Object... objs){for (Object obj:objs){
// System.out.println(obj);if (obj==null){return true;}// 如果是字符串,判断是null,和 空字符串if (String.class.equals(obj.getClass())){String str = (String) obj;if(str==null || str.trim().equals("")){return true;}// Integer 和 Date类型判断是不是null}else {if (obj==null){return true;}}}return false;}public static void main(String[] args) {Object s = null;String str = (String) s;
// System.out.println(str==null);Integer i = null;Boolean blank = isBlank(str,i,2);System.out.println(blank);System.out.println(isBlank("er",3,new Date()));System.out.println("UserId".contains("id"));}
}
(4)前端页面:给后端发送json对象{“username”:this.username,“password”:this.password}
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>登陆</title><link rel="stylesheet" href="/bootstrap/css/bootstrap.css"><script src="/js/jquery-3.5.1.js"></script><script src="/bootstrap/js/bootstrap.js"></script><script src="/js/vue.min-v2.5.16.js"></script><script src="/js/axios.min.js"></script>
</head>
<body>
<div id="app">Vue登陆页面<br>用户名:<input type="text" v-model="username"><br>输入密码:<input type="text" v-model="password"><br><button @click="loginBtn">提交</button>
</div>
<script>let app = new Vue({el:"#app",data:{username:"",password:"",},methods:{loginBtn(){axios.post("/user/login",{"username":this.username,"password":this.password}).then(response=>{let resp = response.data;console.log(resp)if (resp.code==200){alert(resp.msg)// 跳转到index页面,也要过controllerlocation.href = "/user/listPage"}else {alert(resp.msg)}})}},created(){}})
</script></body>
</html>
3.登陆功能的实现
(1)controller层代码
// 到登陆页面@RequestMapping("/loginPage")public String loginPage(){// 返回string类型return "/user/login";}// 处理用户输入的用户名和密码@RequestMapping("/login")@ResponseBodypublic ResData login(@RequestBody User user, HttpSession session){System.out.println(user);// 1.输入不为空if (StringUtils.isBlank(user.getUsername(),user.getPassword())){return new ResData(1001, "用户名|密码为空", null);}// 2.判断用户名,密码是否正确User userDB = userService.queryByUsername(user.getUsername());if (userDB==null || userDB.getPassword().equals(SecureUtil.md5(user.getPassword()))){return new ResData(1001, "用户名|密码错误", null);}// 3.登陆成功,保存到sessionsession.setAttribute("user", userDB);return new ResData(200, "OK", null);}
(2)前端页面
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>登陆</title><link rel="stylesheet" href="/bootstrap/css/bootstrap.css"><script src="/js/jquery-3.5.1.js"></script><script src="/bootstrap/js/bootstrap.js"></script><script src="/js/vue.min-v2.5.16.js"></script><script src="/js/axios.min.js"></script>
</head>
<body>
<div id="app">Vue登陆页面<br>用户名:<input type="text" v-model="username"><br>输入密码:<input type="text" v-model="password"><br><button @click="loginBtn">提交</button>
</div>
<script>let app = new Vue({el:"#app",data:{username:"",password:"",},methods:{loginBtn(){axios.post("/user/login",{"username":this.username,"password":this.password}).then(response=>{let resp = response.data;console.log(resp)if (resp.code==200){alert(resp.msg)// 跳转到index页面,也要过controllerlocation.href = "/user/listPage"}else {alert(resp.msg)}})}},created(){}})
</script></body>
</html>
4.用户信息分页展示 路径变量传参"/list/{pageNum}/{pageSize}",@PathVariable(“pageNum”)
(1)controller层代码
要点:
- 共享值用ModelAndView,前端获取 [[${username}]]
- 路径变量传参"/list/{pageNum}/{pageSize}",@PathVariable(“pageNum”);
// 到list页面// 1.直接到页面
// @RequestMapping("/listPage")public String listPage(){// 返回string类型return "/user/list";}// 2.共享值@RequestMapping("/listPage")public ModelAndView listPageMV(){ModelAndView mv = new ModelAndView("/user/list"); // 到哪个页面mv.addObject("username", "peter"); // 共享的值return mv;}// 处理list页面的请求,pageNum,pageSize@RequestMapping("/list/{pageNum}/{pageSize}")@ResponseBodypublic ResData userList(@PathVariable("pageNum") Integer pageNum,// TODO:可以多个注解吗?答案:用路径变量必须都传,所以下面无效
// @RequestParam(value = "pageSize",defaultValue = "3")@PathVariable("pageSize") Integer pageSize){PageInfo<User> pageInfo = userService.queryList(pageNum, pageSize);System.out.println(pageInfo);return new ResData(200, "OK", pageInfo);}
(2)前端页面:[[${username}]]
<!DOCTYPE html>
<html lang="en">
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>列表</title><link rel="stylesheet" href="/bootstrap/css/bootstrap.css"><script src="/js/jquery-3.5.1.js"></script><script src="/bootstrap/js/bootstrap.js"></script><script src="/js/vue.min-v2.5.16.js"></script><script src="/js/axios.min.js"></script>
</head>
<body>
<div id="app">列表页面[[${username}]]<br><div>
<!-- 进行搜索--><input type="text" v-model="pageSize"><button @click="searchBtn">提交</button></div>
<!-- pageNum,pageSize,name,date,--><table class="table-condensed table-hover table-striped table-responsive table-cell table-row-cell table-view table-bordered"><tr><th>id</th><th>username</th><th>gender</th><th>birthday</th></tr><tr v-for="user in pageInfo.list"><td>{{user.id}}</td><td>{{user.username}}</td><td>{{user.sex}}</td><td>{{user.birthday}}</td></tr></table><div>
<!-- 首页,尾页,上下页,跳转到--><button v-show="!pageInfo.isFirstPage" @click="toFirstPage">首页</button><button v-show="pageInfo.hasNextPage" @click="toNextPage">下页</button><button v-show="pageInfo.hasPreviousPage" @click="toPreviousPage">上页</button><button v-show="!pageInfo.isLastPage" @click="toLastPage">尾页</button></div></div><script>let app = new Vue({el:"#app",data:{pageInfo:{},pageSize:3,},methods:{// 设置pageNum 和 pageSizequeryList(pageNum,pageSize){axios.post("/user/list/"+pageNum+"/"+pageSize).then(response=>{let resp = response.data;console.log(resp)this.pageInfo = resp.data;})},searchBtn(){this.queryList(1,this.pageSize)},// 首页,尾页,下页,上页toFirstPage(){this.queryList(1,this.pageSize)},toNextPage(){this.queryList(this.pageInfo.pageNum+1,this.pageSize)},toPreviousPage(){this.queryList(this.pageInfo.pageNum-1,this.pageSize)},toLastPage(){this.queryList(this.pageInfo.pages,this.pageSize)},},created(){this.queryList(1,3);}})
</script></body>
</html>
拦截器
拦截器是基于增强方法做的
拦截谁,在配置类中配置;——对应SpringMvcConfig.java文件
拦下来做什么;——interceptor/LoginAuthInterceptor.java文件
1.拦截谁addInterceptors,配置类(@Configuration + implements WebMvcConfigurer )中
要点:
- 在soring的容器中,@Configuration
- 是spring的配置类 implements WebMvcConfigurer
package com.tianju.config;import com.tianju.inteceptor.LoginAuthInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** spring的配置类* 1.在容器,@Configuration* 2.是spring的配置类,*/
@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {@AutowiredLoginAuthInterceptor loginAuthInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginAuthInterceptor).addPathPatterns("/**") // 拦截器 /** 表示子孙目录.excludePathPatterns("/user/loginPage","/user/login","/user/registerPage","/user/register","/js/**","/css/**","/bootstrap/**"); // 在拦截的基础上,放行谁}
}
2.拦下来做什么—response.sendRedirect(“/user/loginPage”);,拦截器(@Component + implements HandlerInterceptor)
要点:
- 在容器中,@Component
- 是拦截器,implements HandlerInterceptor
- 拦下来做什么,登陆了,放行,返回true;没有登陆,重定向去登陆页面,返回false;
package com.tianju.inteceptor;import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;/*** 拦截器相关,拦下来做什么* 1.在容器中,@Component* 2.是拦截器,implements HandlerInterceptor*/
@Component
public class LoginAuthInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 如果登陆了,就放行HttpSession session = request.getSession();Object user = session.getAttribute("user");if (user!=null){return true;}else {// 没有登陆,去登陆页面response.sendRedirect("/user/loginPage");return false;}}
}
【bug】302重定向,ERR_TOO_MANY_REDIRECTS,如果配置类中的,excludePathPatterns忘记加第一个反斜杠 /
报错:ERR_TOO_MANY_REDIRECTS
原因:.excludePathPatterns里面的路径反斜杠没加
总结
1.集成mybatis,在application.yml配置文件中进行配置;
2.前端传参用Json对象传,后端接收需要加上@RequestBody注解,实体类中规定日期的格式, @JsonFormat(pattern = “yyyy-MM-dd”);
3.获取新增数据的id:useGeneratedKeys=“true” keyProperty=“id”;
4.路径变量传参"/list/{pageNum}/{pageSize}“,后端接收用@PathVariable(“pageNum”);
5.后端共享值用ModelAndView,前端获取用[[${username}]];
6.spring配置类:在soring的容器中,@Configuration;是spring的配置类 implements WebMvcConfigurer;
7.拦截器(@Component + implements HandlerInterceptor),拦下来做什么—response.sendRedirect(”/user/loginPage");
8.拦截器在Spring配置中使用addInterceptors方法;