SpringBoot-常见场景(下)-三更补充版
接上文(准备开始前后端分离的模式) 准备工作: 基本测试(数据库查询–可跳过,与整合步骤基本一致)
因为是前后端分离的项目,所以最终方法的返回值都会放到请求体当中—>@
所有文件与之前测试的相同
实体类
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {private Integer id;private String username;private Integer age;private String address;
}
记得加注解,才会知道这个是个接口
接口相应格式统一(另一项目的)
前端发送请求代码编写
出现了跨域问题
4.5 跨域请求 4.5.1 什么是跨域
浏览器出于安全的考虑,使用 对象发起 HTTP请求时必须遵守同源策略,否则就是跨域的HTTP请求,默认情况下是被禁止的。 同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致。
4.5.2 CORS解决跨域
CORS是一个W3C标准,全称是”跨域资源共享”(Cross- ),允许浏览器向跨源服务器,发出请求,从而克服了AJAX只能同源使用的限制。
它通过服务器增加一个特殊的[--Allow-]来告诉客户端跨域的限制,如果浏览器支持CORS、并且判断通过的话,就会允许发起跨域请求。
4.5.3 使用CORS解决跨域 1.使用@
可以在支持跨域的方法上或者是上加上@注解
@RestController
@RequestMapping("/user")
@CrossOrigin
public class UserController {@Autowiredprivate UserServcie userServcie;@RequestMapping("/findAll")public ResponseResult findAll(){//调用service查询数据 ,进行返回List<User> users = userServcie.findAll();return new ResponseResult(200,users);}
}
2.使用 的 方法配置(更好的方式)
@Configuration //表所示为配置类
public class CorsConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {// 设置允许跨域的路径registry.addMapping("/**")// 设置允许跨域请求的域名.allowedOriginPatterns("*")// 是否允许cookie.allowCredentials(true)// 设置允许的请求方式.allowedMethods("GET", "POST", "DELETE", "PUT")// 设置允许的header属性.allowedHeaders("*")// 跨域允许时间.maxAge(3600);}
}
比如:
继续实现功能 前端
运用debug
最终结果
为什么要统一响应格式
data属性当中就是把我们后端响应的json字符串解析出来。
这样对前端来说判断方便很多
4.6 拦截器 4.6.0 登录案例
4.6.0.1 思路分析
在前后端分离的场景中,很多时候会采用token的方案进行登录校验。
登录成功时,后端会根据一些用户信息生成一个token字符串返回给前端。
前端会存储这个token。以后前端发起请求时如果有token就会把token放在请求头中发送给后端。
后端接口就可以获取请求头中的token信息进行解析,如果解析不成功说明token超时了或者不是正确的token,相当于是未登录状态。
如果解析成功,说明前端是已经登录过的。
4.6.0.2 Token生成方案-JWT
本案例采用目前企业中运用比较多的JWT来生成token。
使用时先引入相关依赖
<dependency><groupId>io.jsonwebtokengroupId><artifactId>jjwtartifactId><version>0.9.0version>dependency>
然后可以使用下面的工具类来生成和解析token
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;/*** JWT工具类*/
public class JwtUtil {//有效期为public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000 一个小时//设置秘钥明文public static final String JWT_KEY = "sangeng";/*** 创建token* @param id* @param subject* @param ttlMillis* @return*/public static String createJWT(String id, String subject, Long ttlMillis) {SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;long nowMillis = System.currentTimeMillis();Date now = new Date(nowMillis);if(ttlMillis==null){ttlMillis=JwtUtil.JWT_TTL;}long expMillis = nowMillis + ttlMillis;Date expDate = new Date(expMillis);SecretKey secretKey = generalKey();JwtBuilder builder = Jwts.builder().setId(id) //唯一的ID.setSubject(subject) // 主题 可以是JSON数据.setIssuer("sg") // 签发者.setIssuedAt(now) // 签发时间.signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥.setExpiration(expDate);// 设置过期时间return builder.compact();}/*** 生成加密后的秘钥 secretKey* @return*/public static SecretKey generalKey() {byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");return key;}/*** 解析** @param jwt* @return* @throws Exception*/public static Claims parseJWT(String jwt) throws Exception {SecretKey secretKey = generalKey();return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();}}
乱写就会抛出异常
4.6.0.3 登录接口实现
数据准备
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (`id` int(11) NOT NULL AUTO_INCREMENT,`username` varchar(50) DEFAULT NULL,`password` varchar(50) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;/*Data for the table `sys_user` */insert into `sys_user`(`id`,`username`,`password`) values (1,'root','root'),(2,'sangeng','caotang');
实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SystemUser {private Integer id;private String username;private String password;
}
import com.sangeng.domain.ResponseResult;
import com.sangeng.domain.SystemUser;
import com.sangeng.service.SystemUserService;
import com.sangeng.utils.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.HashMap;
import java.util.Map;
import java.util.UUID;@RestController
@RequestMapping("/sys_user")
public class SystemUserController {@Autowiredprivate SystemUserService userService;@PostMapping("/login")public ResponseResult login(@RequestBody SystemUser user) {//校验用户名密码是否正确SystemUser loginUser = userService.login(user);Map<String, Object> map;if (loginUser != null) {//如果正确 生成token返回map = new HashMap<>();String token = JwtUtil.createJWT(UUID.randomUUID().toString(), String.valueOf(loginUser.getId()), null);map.put("token", token);} else {//如果不正确 给出相应的提示return new ResponseResult(300, "用户名或密码错误,请重新登录");}return new ResponseResult(200, "登录成功", map);}
}
public interface SystemUserService {public SystemUser login(SystemUser user);
}
@Service
public class SystemUserServcieImpl implements SystemUserService {@Autowiredprivate SystemUserMapper systemUserMapper;@Overridepublic SystemUser login(SystemUser user) {SystemUser loginUser = systemUserMapper.login(user);return loginUser;}
}
dao
@Mapper
@Repository
public interface UserMapper {List<User> findAll();
}
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sangeng.mapper.SystemUserMapper"><select id="login" resultType="com.sangeng.domain.SystemUser">select * from sys_user where username = #{username} and password = #{password}select>
mapper>
4.6.0.4 登录页面
<head><!-- Meta--><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"><meta name="description" content=""><meta name="keywords" content=""><meta name="author" content=""><title>BeAdmin - Bootstrap Admin Theme</title><!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries --><!--[if lt IE 9]><script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script><script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script><![endif]--><!-- Bootstrap CSS--><link rel="stylesheet" href="../app/css/bootstrap.css"><!-- Vendor CSS--><link rel="stylesheet" href="../vendor/fontawesome/css/font-awesome.min.css"><link rel="stylesheet" href="../vendor/animo/animate+animo.css"><!-- App CSS--><link rel="stylesheet" href="../app/css/app.css"><link rel="stylesheet" href="../app/css/common.css"><!-- Modernizr JS Script--><script src="../vendor/modernizr/modernizr.js" type="application/javascript"></script><!-- FastClick for mobiles--><script src="../vendor/fastclick/fastclick.js" type="application/javascript"></script>
</head><body><!-- START wrapper--><div class="row row-table page-wrapper" id="app"><div class="col-lg-3 col-md-6 col-sm-8 col-xs-12 align-middle"><!-- START panel--><div data-toggle="play-animation" data-play="fadeIn" data-offset="0" class="panel panel-dark panel-flat"><div class="panel-heading text-center"><a href="login.html#"><img src="../app/img/logo.png" alt="Image" class="block-center img-rounded"></a><p class="text-center mt-lg"><strong>SIGN IN TO CONTINUE.</strong></p></div><div class="panel-body"><form role="form" class="mb-lg"><div class="text-right mb-sm"><a href="login.html#" class="text-muted">Need to Signup?</a></div><div class="form-group has-feedback"><input id="exampleInputEmail1" v-model="user.username" type="email" placeholder="请输入用户名" class="form-control"><span class="fa fa-envelope form-control-feedback text-muted"></span></div><div class="form-group has-feedback"><input id="exampleInputPassword1" v-model="user.password" type="password" placeholder="请输入密码" class="form-control"><span class="fa fa-lock form-control-feedback text-muted"></span></div><div class="clearfix"><div class="checkbox c-checkbox pull-left mt0"><label><input type="checkbox" value=""><span class="fa fa-check"></span>Remember Me</label></div><div class="pull-right"><a href="login.html#" class="text-muted">Forgot your password?</a></div></div><button type="submit" class="btn btn-block btn-primary" @click="handleLogin()">登录</button></form></div></div><!-- END panel--></div></div><!-- END wrapper--><!-- START Scripts--><!-- Main vendor Scripts--><script src="../vendor/jquery/jquery.min.js"></script><script src="../vendor/bootstrap/js/bootstrap.min.js"></script><!-- Animo--><script src="../vendor/animo/animo.min.js"></script><!-- Custom script for pages--><script src="../app/js/pages.js"></script><script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script><script src="https://unpkg.com/axios/dist/axios.min.js"></script><script>var v = new Vue({el:"#app",data:{user:{}},methods:{handleLogin(){//请求后台登录接口axios.post("http://localhost/sys_user/login",this.user).then((res)=>{// console.log(res);//判断是否成功if(res.data.code==200){//登录成功// alert("登录成功");//存储tokenlocalStorage.setItem("token",res.data.data.token);//跳转页面到index.htmllocation.href = "/index.html";}else{//登录失败alert(res.data.msg);}})}}});</script><!-- END Scripts-->
</body></html>
4.6.1 拦截器的概念(和MVC基本无异)
如果我们想在多个方法执行之前或者之后都进行一些处理,甚至某些情况下需要拦截掉,不让方法执行。那么可以使用为我们提供的拦截器。
详情见 课程中拦截器相关章节。
4.6.1 使用步骤 ①创建类实现接口
public class LoginInterceptor implements HandlerInterceptor {
}
②实现方法
@Component //把类放到spring容器里面,底下为相应的业务逻辑
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//获取请求头中的tokenString token = request.getHeader("token");//判断token是否为空,如果为空也代表未登录 提醒重新登录(401)if(!StringUtils.hasText(token)){response.sendError(HttpServletResponse.SC_UNAUTHORIZED);return false;}//解析token看看是否成功try {Claims claims = JwtUtil.parseJWT(token);String subject = claims.getSubject();System.out.println(subject);} catch (Exception e) {e.printStackTrace();//如果解析过程中没有出现异常说明是登录状态//如果出现了异常,说明未登录,提醒重新登录(401)response.sendError(HttpServletResponse.SC_UNAUTHORIZED);return false;}return true;}
}
③配置拦截器
@Configuration
public class LoginConfig implements WebMvcConfigurer {@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor)//添加拦截器.addPathPatterns("/**") //配置拦截路径.excludePathPatterns("/sys_user/login");//配置排除路径}
}
4.7 异常统一处理(整合MVC的) ①创建类加上@注解进行标识
@ControllerAdvice
public class MyControllerAdvice {}
②定义异常处理方法
定义异常处理方法,使用 @ 标识可以处理的异常。
@ControllerAdvice
public class MyControllerAdvice {@ExceptionHandler(RuntimeException.class)@ResponseBodypublic ResponseResult handlerException(Exception e){//获取异常信息,存放如ResponseResult的msg属性String message = e.getMessage();ResponseResult result = new ResponseResult(300,message);//把ResponseResult作为返回值返回,要求到时候转换成json存入响应体中return result;}
}
4.8 获取web原生对象
我们之前在web阶段我们经常要使用到对象,,对象等。我们也可以通过获取到这些对象。(不过在MVC中我们很少获取这些对象,因为有更简便的方式,避免了我们使用这些原生对象相对繁琐的API。)
我们只需要在方法上添加对应类型的参数即可,但是注意数据类型不要写错了,会把我们需要的对象传给我们的形参。
@RestController
public class TestController {@RequestMapping("/getRequestAndResponse")public ResponseResult getRequestAndResponse(HttpServletRequest request, HttpServletResponse response, HttpSession session){System.out.println(request);return new ResponseResult(200,"成功");}
}
4.9 自定义参数解析
所有方法都用,这样就很麻烦。
如果我们想实现像获取请求体中的数据那样,在方法的参数上增加一个@注解就可以获取到对应的数据的话。
可以使用来实现自定义的参数解析。
新建一个包
①定义用来标识的注解
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentUserId {}
②创建类实现接口并重写其中的方法
注意加上@注解注入容器
@Component
public class UserIdArgumentResolver implements HandlerMethodArgumentResolver {//判断方法参数使用能使用当前的参数解析器进行解析@Overridepublic boolean supportsParameter(MethodParameter parameter) {//如果方法参数有加上CurrentUserId注解,就能把被我们的解析器解析return parameter.hasParameterAnnotation(CurrentUserId.class);}//进行参数解析的方法,可以在方法中获取对应的数据,然后把数据作为返回值返回。方法的返回值就会赋值给对应的方法参数@Overridepublic Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {//获取请求头中的tokenString token = webRequest.getHeader("token");if(StringUtils.hasText(token)){//解析token,获取userIdClaims claims = JwtUtil.parseJWT(token);String userId = claims.getSubject();//返回结果return userId;}return null;}
}
③配置参数解析器,因为 这种参数解析器属于MVC中的主键,我们要进行主键相关的配置。就像之前的
@Configuration
public class ArgumentResolverConfig implements WebMvcConfigurer {@Autowiredprivate UserIdArgumentResolver userIdArgumentResolver;@Overridepublic void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {resolvers.add(userIdArgumentResolver);}
}
④测试
在需要获取的方法中增加对应的方法参数然后使用@进行标识即可获取到数据
@RestController
@RequestMapping("/user")
//@CrossOrigin
public class UserController {@Autowiredprivate UserServcie userServcie;@RequestMapping("/findAll")public ResponseResult findAll(@CurrentUserId String userId) throws Exception {System.out.println(userId);//调用service查询数据 ,进行返回sList<User> users = userServcie.findAll();return new ResponseResult(200,users);}
}