Java后端tlias员工部门管理-全局异常和登录校验和AOP事务管理-6

目录

登录模块

登录校验JWT

拦截器

 全局异常处理

​编辑

事务管理

​编辑

AOP


完成了前面所有的模块之后,具体可以看我前面的博客,我们今天接下来完成登录校验、全局异常处理与事务管理和AOP

登录模块

简单的登录可以去查询数据库是否有这个员工,可以按照三层架构去完成

Controller层 

新建一个文件LoginController,表示专门处理登录

import com.itheima.pojo.Emp;
import com.itheima.pojo.Result;
import com.itheima.service.EmpService;
import lombok.extern.slf4j.Slf4j;
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.RestController;

@Slf4j
@RestController
public class LoginController {
    @Autowired
    private EmpService empService;

    @PostMapping("/login")
    public Result login(@RequestBody Emp emp) {
        log.info("员工登录,em={}", emp);
        Emp e = empService.login(emp);
        return e != null ? Result.success() : Result.error("用户名或密码错误");
    }
}

Service层

EmpService

/*
* 登录
* */
Emp login(Emp emp);

EmpServiceImpl

@Override
public Emp login(Emp emp) {
    return empMapper.getByUsernameAndPassword(emp);
}

为啥不是login方法名,而是getByUsernameAndPassword 。因为login是业务层的方法,mapper层是持久层直接操作数据库的,所以就该名成getByUsernameAndPassword

Mapper层

EmpMapper

/*
* 登录
* */
@Select("select * from emp where username = #{username} and password = #{password}")
Emp getByUsernameAndPassword(Emp emp);

这只是一个简单sql语句,所以直接写在注解上

那么运行程序,先点击退出

然后输入用户名和密码,

注意:用户名和密码要在数据库里面有才行!

点击登陆之后, 成功回到这个页面说明登陆功能完成

登录校验JWT

以上登录是不是少了什么?所以我们要进行 登录校验

为啥要用JWT令牌,因为对比了cookie和session,JWT优势更大,弊端更小

1.那么首先在pom.xml文件引入依赖

<!--引入servelet依赖-->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
</dependency>
<!--JWT令牌-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
<!--fastJSON-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.76</version>
</dependency>

2.然后我们在utils这个包当中,引入工具类

JwtUtils

package com.itheima.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.Map;

public class JwtUtils {

    private static String signKey = "itheima"; // 签名算法指定的Key值
    private static Long expire = 86400000L; // 有效时间长度

    /**
     * 生成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;
    }
}

3.不用更改工具类代码,之后在原来的LoginController文件,代码改成如下

@Slf4j
@RestController
public class LoginController {
    @Autowired
    private EmpService empService;

    @PostMapping("/login")
    public Result login(@RequestBody Emp emp){
        log.info("员工登录,em={}",emp);
        Emp e = empService.login(emp);
        //登陆成功,生成令牌,下发令牌
        if (e !=null) {
            Map<String, Object> claims = new HashMap<>();
            claims.put("id",e.getId());
            claims.put("name",e.getName());
            claims.put("useranme",e.getUsername());
            String jwt = JwtUtils.generateJwt(claims);
            return Result.success(jwt);
        }
        //登录失败返回登陆失败信息
        return Result.error("用户名或者密码错误");
    }
}

那么启动程序,然后编写一个测试,这是一个POST请求,输入用户名和密码json传入参数

然后点击发送,data里面就是返回来的token值

然后查询部门接口这个测试一下,但此时如果我们不携带上token这个请求头参数,是不会成功的,所以我们需要在Headers添加token这个参数,把刚才返回来的token值复制过去,然后点击发送,

如果成功返回结果,说明没问题

然后在前端进行联调,如果登录成功那就是没问题的。

 部门管理数据正常显示,确实没问题,嘿嘿~~

拦截器

拦截器就是在发起登陆之后执行controller之前,把请求拦截一下,校验登录是否合法,所以我们要在preHandle编写

那么我们新建一个interceptor包,并在该包下新建一个LoginCheckInterceptor登录拦截器

如下

代码如下

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.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.stereotype.Component;
import jakarta.servlet.http.HttpServletRequest; // 修改为jakarta
import jakarta.servlet.http.HttpServletResponse; // 修改为jakarta
import org.springframework.web.servlet.ModelAndView;

@Slf4j
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
    @Override // 目标资源方法运行之前执行,返回true,放行;返回false不放行
    public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
        System.out.println("preHandle... 登录校验");

        //1.获取请求的url
        String url = req.getRequestURL().toString();
        log.info("请求的url={}", url);
        //2.判断请求的url是否包含login,是的话则是登录操作,放行
//        由于不拦截/login的请求,所以这段代码可以直接删掉
        if (url.contains("login")) {
            log.info("是登陆状态,放行");
            return true; //拦截器的放行就是return true
        }
        //3.获取请求头中的令牌(token)
        String jwt = req.getHeader("token");

        //4.判断token是否存在,不存在返回错误的信息
        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) {
            e.printStackTrace();
            log.info("jwt令牌解析失败,返回错误登录信息");
            Result error = Result.error("NOT_LOGIN");
            //手动转换 将对象-->转换成JSON格式的字符串
            String notLogin = JSONObject.toJSONString(error);
            resp.getWriter().write(notLogin);
            return false;
        }
        //6.放行
        log.info("令牌解析成功,放行");
        return true;
    }
}

 然后新建一个config包,用来配置拦截器应该拦截哪些路径

如下,创建一个WebConfig类

代码如下,表示拦截所有路径除了/login

package com.itheima.config;

import com.itheima.interceptor.LoginCheckInterceptor;
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;

@Configuration //配置类
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private LoginCheckInterceptor loginCheckInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**").excludePathPatterns("/login");
    }
}

然后你退出登录,再次登录进去,如果能正常登录并且数据正常显示,那么拦截器配置成功

至此登录校验功能全部完成


 全局异常处理

如果在那些controller层出现了错误,那么是不是要加上try。。。catch。。。捕获异常并处理啊,但是如果每个地方都要加上try。。。catch。。。是不是会太多了?并且代码看起来繁琐,所以我们可以用全局异常处理器去处理异常,来捕获所有异常做统一的处理。

那么我们创建一个exception包,在里面新建一个GlobalExceptionHandler表示全局处理器

然后编写如下函数

import com.itheima.pojo.Result;
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("对不起操作失败,请联系系统管理员~");
    }
}

@ExceptionHandler:用于指定异常处理方法。当与@RestControllerAdvice配合使用时,用于全局处理控制器里的异常。Exception.class表示捕捉到所有异常,@RestControllerAdvice=@ControllerService+@RequestBody

捕捉到异常之后,报错,然后返回给前端错误

那么我们在部门管理故意制造一个异常,如下图,制造一个除以0的异常

那么你就可以看到这个弹窗Toast,说明你的全局异常处理器是生效的。 


事务管理

首先事务是什么我们得了解一下

那么我们回想一下,我们删除部门的同时是不是也得把对应的部门id删除掉,所以可以把这两个操作全部放在部门删除这个功能里,并添加上事务即可。部门以及对应部门id的员工删除要么同时成功,要么同时失败 

所以我们在业务层(为啥是业务层,因为具体的操作逻辑是在业务层编写的)的DeptServiceImpl中的删除员工部分的代码加上注解,并标记rollback回滚所有异常,

 记得在前面注入EmpMapper对象

然后在EmpMapper编写根据部门id删除员工代码 

/*
    * 根据部门id删除员工
    * */
@Delete("delete from emp where dept_id = #{deptId}")
void deleteByDeptId(Integer deptId);

然后将我们在controller层,人为制造的异常代码删掉

之后运行程序,如果删除没问题,那么删除部门id同时也会把对应部门的员工删除没问题了

 咨询部删除了,并且删除成功

同时,属于咨询部的员工的数据也没了,说明删除成功。


AOP

AOP是什么,就是它可以统一进行事务管理,记录操作日志等等一系列的繁琐操作。

所以接下来我们来用AOP去实现记录日志操作的案例

首先我们导入AOP的依赖,然后刷新maven图标

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

然后我们新建一个anno包,然后定义一个MyLog的Annotation

去自定义MyLog注解

package com.itheima.anno;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//创建自定义的注解
//annotation就是注解的意思
@Retention(RetentionPolicy.RUNTIME) //@Retention 什么时候生效,RetentionPolicy.RUNTIME运行时生效
@Target(ElementType.METHOD)         //@Target 可以作用在哪些地方,ElementType.METHOD 作用在方法上
public @interface MyLog {
}

之后在pojo实体类导入对象OperateLog,这是代表日志操作的对象

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; //操作耗时
}

之后记得在数据库新建一个表名为operate_log,sql语句如下

create table tlias.operate_log
(
    id            int unsigned auto_increment comment 'ID'
        primary key,
    operate_user  int unsigned  null comment '操作人ID',
    operate_time  datetime      null comment '操作时间',
    class_name    varchar(100)  null comment '操作的类名',
    method_name   varchar(100)  null comment '操作的方法名',
    method_params varchar(1000) null comment '方法参数',
    return_value  varchar(2000) null comment '返回值',
    cost_time     bigint        null comment '方法执行耗时, 单位:ms'
)
    comment '操作日志表';

之后在mapper层,新建一个OperateMapper,表示他会把得到的数据插入到数据库表中

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 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);

}

然后新建一个aop包,然后定义一个AOP操作 ,用来获取操作人ID,操作时间等等

LoaAspect的代码,注解@Aspect就代表AOP类

package com.itheima.aop;

import com.alibaba.fastjson.JSONObject;
import com.itheima.mapper.OperateLogMapper;
import com.itheima.pojo.OperateLog;
import com.itheima.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.HttpServletRequest;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.Arrays;

@Slf4j
@Component
@Aspect //AOP类
public class LogAspect {
    @Autowired
    private HttpServletRequest request;

    @Autowired
    private OperateLogMapper operateLogMapper;

    @Around("@annotation(com.itheima.anno.Log)")
    public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {
        //操作人ID -->可以通过jwt来获取
        String jwt = request.getHeader("token");
        Claims claims = JwtUtils.parseJWT(jwt);
        Integer OperatorUserId = (Integer) claims.get("id");

        //操作时间
        LocalDateTime operateTime = LocalDateTime.now();

        //操作类名
        String className = joinPoint.getTarget().getClass().getName();

        //操作方法名
        String methodName = joinPoint.getSignature().getName();

        //操作方法参数
        Object[] argsName = joinPoint.getArgs();
        String methodParams = Arrays.toString(argsName);

        //操作方法返回值
        //执行原始方法,获取返回值(环绕通知)
        long begin = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        String returnValue = JSONObject.toJSONString(result);
        long end = System.currentTimeMillis();

        //操作耗时
        Long costTime = end- begin;

        //记录i操作日志
        OperateLog operateLog = new OperateLog(null,OperatorUserId,operateTime,className,methodName,methodParams,returnValue,costTime);
        operateLogMapper.insert(operateLog);
        log.info("插入操作的日志{}",operateLog);
        return result;
    }
}

 最终我们在service层的实现类加上我们自定义的注解,比如要DeptServieImpl中,在插入、查询全部部门数据的方法上加上@MyLog注解

那么当我们程序启动,并执行插入部门操作的时候,如果出现如下图

能在控制台看到这条语句 

 并且在数据库中查看到最新的操作

则表示AOP管理我们已经编写完成了。

那么至此我们已经把所有后端的功能全部圆满完成了。JavaWeb的学习就告一段落了,之后希望我的6篇javaweb的学习博客能给大家带来帮助,有不懂的可以在评论区提问噢,我会热心解答!也欢迎大家提出不足的地方!之后我会不定期的更新高质量博客,期待大家的关注!

  • 17
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值