环境搭建

  • 准备数据库表(dept,emp)
  • 创建SpringBoot工程
    • 引入web开发起步依赖,mybatis依赖(mybatis依赖和mysql驱动),lombok依赖
  • 在application.properties中引入mybatis的配置信息
  • 准备对应的实体类
  • 准备对应的Mapper、Service(接口、实现类)、Controller基础结构

部门管理开发:根据接口文档阅读开发Restful风格

  • 明确三层架构的功能
  • Controller
    • 1.接收请求
    • 2.调用service层功能
    • 3.响应
  • Service
    • 调用mapper接口功能
  • Mapper
    • 执行对应的SQL语句与数据库进行互动

简化开发:二者效果相同

@RequestNapping(value = "/depts", method = RequestNethod.GET)
@GetMapping("/ depts")

注意在接口中注入Bean对象,例如Controller中注入Service对象,Service中注入Mapper对象,达到解耦的效果

==接口测试,postman / 前后端联调==

==日志:@Slf4j==

==注意某些路径的 @PathVariable==

==注意解析json格式的前端请求参数可以使用@RequestBody来将json封装到实体类中==

==@RequestMapping(“/depts”) 公共路径抽取:完整的请求路径,应该是类上的@RequestMapping 的value属性+方法上的@RequestMapping的value属性。==

员工管理开发:

  • 难点:分页查询

分页查询

需要两条SQL语句

  • 显示当前页面
select * from emp limit ?, ?;
  • 显示所有数据
select count(*) from emp;

前端传来的数据

  • 当前页码:page
  • 每页展示记录数:pageSize

后端响应的数据

  • 数据列表:List select * from emp limit ?,?;
  • 总记录数:total select count (*) from emp;

==目前需要返回两项数据,方法只能返回一个数据,使用实体类封装返回==

==@ResultParam(defaultvalue = ”默认值“) 参数 可以设置参数的默认值==

分页查询插件PageHelper

杂项04-02PageHelper

文件上传

杂项04-02文件上传

方法1:将文件保存到本地

/*@PostMapping("/upload")
public Result upload(String username, Integer age, MultipartFile image) throws Exception {
log.info("文件上传:{},{},{}", username, age, image);

//获取原始文件名
String originalFilename = image.getOriginalFilename();

//构造唯一的文件名(不能重复)-- uuid(通用唯一识别码)
int index = originalFilename.lastIndexOf('.');
String extname = originalFilename.substring(index);
String newFileName = UUID.randomUUID().toString() + extname;
log.info("新的文件名:{}", newFileName);

//将接收到的文件存储在服务器的磁盘目录中 D:\image
image.transferTo(new File("D:\\image\\" + newFileName));

return Result.success();
}*/

方法2:将文件保存到AliOSS

@PostMapping("/upload")
public Result upload(MultipartFile image) throws Exception {
log.info("文件上传,文件名:{}", image.getOriginalFilename());

//调用阿里云OSS工具类
String url = aliOSSUtils.upload(image);
log.info("文件上传完成,文件访问的url为:{}", url);

return Result.success(url);
}

==修改员工需要查询回显和update语句==

==配置注入使用@Value(“#{配置中的key}”)注解来防止硬编码==

登陆校验(难点)

==若按照普通的开发区编写映射页面,通过数据库判别是否用户名与密码正确式不可取的==

如此的话,直接访问类似于 /emps /depts 等页面可以直接进入控制台面板而跳过了判断是否为管理员的步骤

因此,我们需要在 /login /depts /emps 之间建立一种联系

实现思路:

  • 员工登陆成功存储一个标记
  • 在每一个方法接口前判断员工是否登陆,登陆则访问,未登录则返回给前端错误信息
  • 因此,需要两种技术==1.统一拦截== ==2.登陆标记缓存==
    • 统一拦截
      • Servlet提供的过滤器Filter
      • Spring提供的拦截器Interceptor
    • 用户登陆
      • 会话技术

会话技术

  • 会话:指的是用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。

  • 会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据

​ http协议是无状态协议,不会携带上一次会话的信息,因此无法数据共享

  • 会话跟踪方案
    • 客户端会话跟踪技术:Cookie(存储在浏览器)
    • 服务端会话跟踪技术:Session(存储在服务器端)
    • 令牌技术
  • Cookie:浏览器第一次请求时在服务器端设置Cookie,响应时携带Cookie,而后Cookie缓存在浏览器本地,之后的每次请求都会携带Cookie与客户端进行对比,服务器自动响应Cookie,浏览器自动保存Cookie,发起请求自动携带Cookie,三个自动表名Http支持Cookie技术
    • 请求头Cookie
    • 响应头Set-Cookie

优点:HTTP协议支持

缺点:1.移动端无法使用 2.不安全,用户可以自己禁用 3.Cookie不能跨域(跨域:协议、IP/域名、端口不同)

  • Session:浏览器第一次请求时在服务器端设置Session(ID),响应时通过Cookie返回Session的ID,之后的每次请求都会携带Cookie中的Session的ID到服务器端对比

优点:存储在服务器端,安全

缺点:1.服务器集群在负载均衡后有可能无法使用,因此无法在服务器集群环境下使用 2.Cookie的缺点

  • 令牌技术(==建议使用==):用户身份表示,登陆成功生成令牌,响应给浏览器,可以存储在Cookie或者其他存储空间中,之后的每次请求都会携带令牌,比对是否有效,而且想要共享数据只需要将数据保存在令牌中即可

优点:1.支持PC、移动端 2、解决集群环境下的认证问题 3、减轻了服务器端的存储压力

缺点:需要自己去实现

JWT(JSON Web Token)令牌

定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。

jwt令牌组成

  • 第一部分:Header(头),记录令牌类型、签名算法等。例如: {“alg”:”HS256” ,”type”:”JWT”}
  • 第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。例如: {“id”:” 1” ,”username”:”Tom”}
  • 第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、 payload,并加入指定秘钥,通过指定签名算法计算而来。

pom.xml引入

<!--JTW令牌-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>

jwt生成

public class JwtUtils {

private static String signKey = "wangnine";
private static Long expire = 43200000L;

/**
* 生成JWT令牌
* @param claims JWT第二部分负载 payload 中存储的内容
* @return
*/
public static String generateJwt(Map<String, Object> claims){
String jwt = Jwts.builder()
.addClaims(claims)
.signWith(SignatureAlgorithm.HS256, signKey)
.setExpiration(new Date(System.currentTimeMillis() + expire))
.compact();
return jwt;
}

/**
* 解析JWT令牌
* @param jwt JWT令牌
* @return JWT第二部分负载 payload 中存储的内容
*/
public static Claims parseJWT(String jwt){
Claims claims = Jwts.parser()
.setSigningKey(signKey)
.parseClaimsJws(jwt)
.getBody();
return claims;
}
}

统一拦截技术

Filter过滤器

JavaWeb三大组件(Servlet、Filter、Listener)之一。

  • 过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能。
  • 过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等。

相当于抽取了各个页面的登陆校验,优点aop的思想

==如何实现过滤器==

  • 1.定义Filter:定义一个类,实现 Filter接口,并重写其所有方法。
  • 配置Filter: Filter类上加@WebFilter注解,配置拦截资源的路径。引导类上加@ServletComponentScan开启Servlet组件支持。

结合了jwt后的代码如下:

@Slf4j
//@WebFilter(urlPatterns = "/*") //拦截所有
public class LoginCheckFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse resp = (HttpServletResponse) servletResponse;
//1.获取请求url
String url = req.getRequestURL().toString();
log.info("请求的url:{}", url);

//2.判断请求的url中是否包含login,如果包含,说明是登录操作,直接放行
if(url.contains("login")) {
log.info("登录操作,放行...");
filterChain.doFilter(servletRequest, servletResponse);
return;
}

//3.获取请求头中的token令牌
String jwt = req.getHeader("token");

//4.判断令牌是否存在,如果不存在,则返回错误结果(未登录)
if(!StringUtils.hasLength(jwt)) {
log.info("请求头token为空,返回未登录的信息");
Result error = Result.error("NOT_LOGIN");
//手动将对象转换为json格式
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return;
}

//5.解析token,如果解析失败,则返回错误结果(未登录)
try {
JwtUtils.parseJWT(jwt);
} catch (Exception e) {
log.info("解析令牌失败,返回未登录的误信息");
Result error = Result.error("NOT_LOGIN");
//手动将对象转换为json格式
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return;
}

//6.放行
log.info("令牌合法,放行");
filterChain.doFilter(servletRequest, servletResponse);
}
}

//对于init 和 destroy 有默认实现,二者只调用一次

Interceptor拦截器

@Slf4j
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {

@Override //目标资源方法运行前运行,返回true放行,返回false不放行
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {

//1.获取请求url
String url = req.getRequestURL().toString();
log.info("请求的url:{}", url);

//2.判断请求的url中是否包含login,如果包含,说明是登录操作,直接放行
if(url.contains("login")) {
log.info("登录操作,放行...");
return true;
}

//3.获取请求头中的token令牌
String jwt = req.getHeader("token");

//4.判断令牌是否存在,如果不存在,则返回错误结果(未登录)
if(!StringUtils.hasLength(jwt)) {
log.info("请求头token为空,返回未登录的信息");
Result error = Result.error("NOT_LOGIN");
//手动将对象转换为json格式
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return false;
}

//5.解析token,如果解析失败,则返回错误结果(未登录)
try {
JwtUtils.parseJWT(jwt);
} catch (Exception e) {
log.info("解析令牌失败,返回未登录的误信息");
Result error = Result.error("NOT_LOGIN");
//手动将对象转换为json格式
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return false;
}

//6.放行
log.info("令牌合法,放行");
return true;
}

@Override //目标资源方法运行后运行
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle...");
}

@Override //视图渲染完毕后运行,最后运行
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion...");
}
}

异常处理

在Controller层定义一个全局异常处理器

import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
* 全局异常处理器
*/

@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(Exception.class) //捕获所有的异常
public Result ex(Exception ex) {
ex.printStackTrace();
return Result.error("对不起,操作失败,请联系管理员");
}
}

@RestControllerAdvice

@ExceptionHandler(Exception.class) //捕获所有的异常