一、准备工作
开发规范-Restful:
开发规范-统一响应结果:
二、部门管理-查询
前端发送请求后,请求会到Controller中的对应方法,随后调用service来获取数据,在service中调用了Mapper接口中的方法来查询全部部门信息,mapper中则执行对应sql语句来调取数据并且将获取到的数据封装为对应类型返回给service,service又返回给controller,再由controller将数据返回给前端。
三、部门管理-删除
Controller中设置对应方法:
调用的Servcice接口以及实现类:
调用的Mapper接口:
四、部门管理-新增
前端在请求的时候传递的json格式的请求参数在服务端如何接受:通过一个实体类来接受json参数,需要在实体类前增加一个注解:@RequestBody
补充基础属性:添加部门时页面只传输的json数据只包含部门的名称,除此以外的创建时间等信息需要自己补全
DeptController
Dept接口
Dept实现类
DeptMapper
五、员工管理-分页查询
基础分页查询:
SQL语句
接口规范:
controller:
service接口+实现类:
mapper:
PageHelper分页插件:
Service接口+实现类
分页查询(查询条件):
SQL语句
XML映射文件+动态SQL:
六、员工管理-删除员工
SQL语句:
接口文档:
Controller:
EmpService接口+实现类:
XML映射文件+动态SQL:
七、员工管理-新增员工
接口文档:
八、文件上传
文件上传介绍:
服务器端接受到文件时,该文件是临时文件,不进行存储则会在请求结束后自动删除
本地存储:
阿里云OSS:
api文档接口:
九、修改员工-查询回显
接口文档:
十、修改员工-根据id更新
文档接口:
SQL语句:
十一、配置文件
后续可以直接在配置文件中修改对应数据,而修改properties文件不需要重新编译
配置格式:
配置文件注解:@ConfigurationProperties:
十二、基础登录功能
登录功能:
接口文档:
登录校验:
思想:
会话技术:
方案一:Cookie
请求服务器的时候,可以自行设置一个Cookie用来存储相关信息,比如当前登录的用户的用户名或者ID,服务器端在给客户端响应数据的时候,会自动的将这个Cookie响应给客户端,客户端在接受时,会自动将其存储在本地,而在后续客户端向服务器的每一次请求时,也会自动的将浏览器本地存储的Cookie携带到客户端,而在服务器端就可以获取到该Cookie,通过判断该Cookie的值是否存在,如果不存在,则表示这个客户端之前没有登录过,存在则代表这个客户端之前登录过
方案二:Session
Session就是加密过的Cookie,本质上还是通过Cookie实现的
方案三:令牌技术
本质是一个字符串,通过浏览器发送请求,如登录请求时,服务器端会生成一个令牌并在响应时一同发送给客户端,客户端在接受时,需要将令牌存储起来,可以在Cookie中,也可在其他的地方,在后续其他请求时,都需要携带这个令牌,通过检测令牌,判断客户端是否登录过,如果是在同一次会话的多次请求之间,需要共享数据,则可以将共享的数据存储在令牌中。
JWT令牌:
package com.itheima;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
//@SpringBootTest
class TliasWebManagementApplicationTests {
@Test
public void testUuid(){
for(int i=0;i<1000;i++){
String uuid= UUID.randomUUID().toString();
System.out.println(uuid);
}
}
/*测试JWT生成*/
@Test
public void testGenJwt(){
Map<String,Object> claims=new HashMap<>();
claims.put("id",1);
claims.put("name","tom");
String jwt=Jwts.builder()
.signWith(SignatureAlgorithm.HS256,"itheima")//设置签名算法
.setClaims(claims)//设置自定义数据 Map对象
.setExpiration(new Date(System.currentTimeMillis()+3600*1000))//设置有效期 Date对象 当前时间+1小时(毫秒值)
.compact();
System.out.println(jwt);
}
/*解析JWT*/
@Test
public void testParseJwt(){
Claims claims=Jwts.parser()
.setSigningKey("itheima")
.parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidG9tIiwiaWQiOjEsImV4cCI6MTc0MDYzMDExMX0._Iwxv6qbaIsr-DDm3rrmHF0i2PQo77G-8UBDtEeq5b4")
.getBody();
System.out.println(claims);
//{name=tom, id=1, exp=1740630111}
}
}
登录校验-JWT令牌 :
api接口文档:
校验令牌-过滤器(Filter):
快速入门:
执行流程:
拦截路径:
过滤器链:
登录校验Filter:
package com.itheima.filter;
import com.alibaba.fastjson.JSONObject;
import com.itheima.pojo.Result;
import com.itheima.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
@WebFilter(urlPatterns = "/*")
public class LoginCheckFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req=(HttpServletRequest) request;
HttpServletResponse resp=(HttpServletResponse) response;
//1.获取请求url
String url = req.getRequestURI().toString();
log.info("请求的url:{}",url);
//2.判断请求url中是否包含login 如果是则代表登录操作,放行
if(url.contains("login")){
log.info("这是登录操作..放行");
chain.doFilter(request,response);
return; //终止代码
}
//3.如果不是,则获取请求头中的令牌
String jwt = req.getHeader("token"); // token 代表请求头的名字
//4.判断令牌是否存在,如果不存在则返回错误结果(未登录)
if(!StringUtils.hasLength(jwt)){//判断是否有长度,没长度则代表空串
log.info("请求头token为空,返回未登录的信息");
Result error=Result.error("NOT_LOGIN"); // 拿到了返回的结果,需要将其转为json格式的返回值 (之前Controller中是自动转换)
String notLogin = JSONObject.toJSONString(error);//调用工具类中的方法:将对象转化为json格式的字符串
resp.getWriter().write(notLogin);
return; //终止程序
}
//5.存在则解析token,如果解析失败,则返回错误结果(未登录)
try {
JwtUtils.parseJWT(jwt);
} catch (Exception e) {//解析失败
e.printStackTrace();
log.info("解析令牌失败,返回错误信息");
Result error=Result.error("NOT_LOGIN"); // 拿到了返回的结果,需要将其转为json格式的返回值 (之前Controller中是自动转换)
String notLogin = JSONObject.toJSONString(error);//调用工具类中的方法:将对象转化为json格式的字符串
resp.getWriter().write(notLogin);
return; //终止程序
}
//6.解析成功,放行
log.info("令牌合法,放行");
chain.doFilter(request,response);
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
新增依赖: (用于将字符串数据转为json格式)
拦截器Interceptor:
快速入门:
返回值 true 则放行 返回值false 不放行
拦截路径:
执行流程:
package com.itheima.interceptor;
import com.alibaba.fastjson.JSONObject;
import com.itheima.pojo.Result;
import com.itheima.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
@Slf4j
public class LoginCheckInterceptor implements HandlerInterceptor {
@Override //在目标资源方法运行前运行,返回true-放行,返回false-不放行
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
System.out.println("preHandle...");
/*return true;*/
//1.获取请求url
String url = req.getRequestURI().toString();
log.info("请求的url:{}",url);
//2.判断请求url中是否包含login 如果是则代表登录操作,放行
if(url.contains("login")){
log.info("这是登录操作..放行");
return true; //终止代码
}
//3.如果不是,则获取请求头中的令牌
String jwt = req.getHeader("token"); // token 代表请求头的名字
//4.判断令牌是否存在,如果不存在则返回错误结果(未登录)
if(!StringUtils.hasLength(jwt)){//判断是否有长度,没长度则代表空串
log.info("请求头token为空,返回未登录的信息");
Result error=Result.error("NOT_LOGIN"); // 拿到了返回的结果,需要将其转为json格式的返回值 (之前Controller中是自动转换)
String notLogin = JSONObject.toJSONString(error);//调用工具类中的方法:将对象转化为json格式的字符串
resp.getWriter().write(notLogin);
return false; //终止程序
}
//5.存在则解析token,如果解析失败,则返回错误结果(未登录)
try {
JwtUtils.parseJWT(jwt);
} catch (Exception e) {//解析失败
e.printStackTrace();
log.info("解析令牌失败,返回错误信息");
Result error=Result.error("NOT_LOGIN"); // 拿到了返回的结果,需要将其转为json格式的返回值 (之前Controller中是自动转换)
String notLogin = JSONObject.toJSONString(error);//调用工具类中的方法:将对象转化为json格式的字符串
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...");
}
}
拦截器和过滤器 代码区别:放行和不放行的表示方式不同,一个是通过返回值,一个是调用doFilter方法
十三、异常处理
全局异常处理器:
十四、事物管理
spring事物管理:
模拟异常,则会出现部门删除,但是对应员工数据仍然存在的情况,此时需要使用事务来进行处理
yml配置文件中增加
事务进阶:
事务传播行为:
设置REQUIRES_NEW :无论delete方法中的事务是否出现问题,都会创建一个新的事物来实现日志记录
十五、AOP
快速入门:
package com.itheima.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Component
@Aspect //AOP类
@Slf4j
public class TimeAspect {
@Around("execution(* com.itheima.service.*.*(..))") //切入点表达式
// excution(返回值类型 包名.类名/接口名.方法名(..))
public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
//1.记录开始时间
long begin=System.currentTimeMillis(); //获取当前时间的毫秒值
//2.调用原始方法运行
Object result=joinPoint.proceed();
//3.记录结束时间,计算执行耗时
long end=System.currentTimeMillis();
long cost=begin-end;
log.info(joinPoint.getSignature()+"方法执行耗时:{}ms",cost);
return result;
}
}
AOP核心概念:
AOP执行流程:
进行AOP程序的开发,运行的就不再是原始的对象,而是基于目标对象所产生的代理对象,也就是增强后的代理对象(所谓增强就是原方法的基础上,增加了通知部分增加的方法)
AOP进阶:
通知类型:
package com.itheima.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Slf4j
@Aspect
@Component
public class MyAspect {
/*抽取切入点表达式*/
@Pointcut("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
private void pt(){} //修饰词为private 无法在其他切面类文件中调用
@Before("pt()")
public void before(){
log.info("before ...");
}
@Around("pt()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("around before ...");
//调用目标对象的原始方法执行
Object result=joinPoint.proceed();
log.info("around after ...");
return result;
}
@After("pt()")
public void after(){
log.info("after ....");
}
@AfterReturning("pt()")
public void afterReturning(){
log.info("afterReturning ...");
}
@AfterThrowing("pt()")
public void afterThrowing(){
log.info("afterThrowing ...");
}
}
通知顺序:
切入点表达式:
execution:
———————————————————————————————————————————
@annotation:
连接点:
package com.itheima.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Slf4j
@Aspect
@Component
public class MyAspect8 {
@Pointcut("execution(* com.itheima.service.DeptService.*(..))")
private void pt(){}
@Before("pt()")
public void before(JoinPoint joinPoint){
log.info("MyAspect8 .. before ..");
}
@Around("pt()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("MyAspect8 .. around before ..");
//1.获取 目标对象类名
String classname= joinPoint.getTarget().getClass().toString();
log.info("目标对象类名:{}",classname);
//2.获取 目标方法名
String methodname=joinPoint.getSignature().getName();
log.info("目标方法名:{}",methodname);
//3.获取 目标方法运行时传入的参数
Object[] args=joinPoint.getArgs();
log.info("目标方法运行传入的参数:{}",args);
//4.放行 目标方法执行
Object result=joinPoint.proceed();
//5.获取 目标方法运行的返回值
log.info("返回值: {}",result);
log.info("MyAspect8 .. around after ..");
return result;
}
}
AOP案例:
package com.itheima.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) //标识生效时间
@Target(ElementType.METHOD) //标识方法
public @interface MyLog {
}
package com.itheima.aop;
import com.alibaba.fastjson.JSONObject;
import com.itheima.mapper.OperateLogMapper;
import com.itheima.pojo.DeptLog;
import com.itheima.pojo.OperateLog;
import com.itheima.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.Arrays;
@Slf4j
@Aspect
@Component
public class MyAspect {
@Autowired
private OperateLogMapper operateLogMapper;
@Autowired
private HttpServletRequest request;
//在 Spring Boot 的应用程序中使用 @Autowired 来注入 HttpServletRequest 对象时,它实际上是从请求的上下文中获取对象,而不是从 Spring 容器中获取。
@Pointcut("@annotation(com.itheima.anno.MyLog)")
private void pt(){};
@Around("pt()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("around before ...");
//获取操作人的id 当前员工的ID
//获取请求头中的jwt令牌
//解析jwt令牌信息
String jwt=request.getHeader("token");
Claims claims = JwtUtils.parseJWT(jwt); //其实是一个Map集合
Integer id = (Integer) claims.get("id");
//操作时间
LocalDateTime operateTime = LocalDateTime.now();
//操作类名
String className = joinPoint.getTarget().getClass().toString();
//操作方法名
String methodName = joinPoint.getSignature().getName();
//操作方法参数
Object[] args = joinPoint.getArgs();
String methodParams = Arrays.toString(args);
Long begin=System.currentTimeMillis();
//调用目标对象的原始方法执行
Object result=joinPoint.proceed();
Long end= System.currentTimeMillis();
//方法返回值
String returnValue = JSONObject.toJSONString(result);
//操作耗时
Long costTime=begin-end;
//操作日志-记录
OperateLog operateLog=new OperateLog(null,id,operateTime,className,methodName,methodParams,returnValue,costTime);
operateLogMapper.insert(operateLog);
log.info("AOP记录操作日志:{}",operateLog);
return result;
}
}
package com.itheima.mapper;
import com.itheima.pojo.OperateLog;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface OperateLogMapper {
//插入日志数据
@Insert("insert into tlias.operate_log (operate_user, operate_time, class_name, method_name, method_params, return_value, cost_time) " +
"values (#{operateUser}, #{operateTime}, #{className}, #{methodName}, #{methodParams}, #{returnValue}, #{costTime});")
public void insert(OperateLog log);
}
package com.itheima.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OperateLog {
private Integer id; //ID
private Integer operateUser; //操作人ID
private LocalDateTime operateTime; //操作时间
private String className; //操作类名
private String methodName; //操作方法名
private String methodParams; //操作方法参数
private String returnValue; //操作方法返回值
private Long costTime; //操作耗时
}