Part07 中州养老AI+IOT项目实训

YangeIT大约 10 分钟中州养老AIIOT版本MysqlApifoxServletHTTPGETPOST

Part07 中州养老AI+IOT项目实训

1. 拦截器和ThreadLocal优化

1.1 拦截器介绍和入门 🎯

前言

拦截器:

  • 是一种动态拦截方法调用的机制,类似于过滤器。
  • 拦截器是Spring框架中提供的,用来动态拦截控制器方法的执行

拦截器的作用:

  • 拦截请求,在指定方法调用前后,根据业务需要执行预先设定的代码。
image
image

在拦截器当中,我们通常也是做一些通用性的操作,比如:我们可以通过拦截器来拦截前端发起的请求,将登录校验的逻辑全部编写在拦截器当中。在校验的过程当中,如发现用户登录了(携带JWT令牌且是合法令牌),就可以直接放行,去访问spring当中的资源。如果校验时发现并没有登录或是非法令牌,就可以直接给前端响应未登录的错误信息。

代码操作

集成步骤

  1. 自定义拦截器:实现HandlerInterceptor接口,并重写其所有方法
  2. 注册配置拦截器 :创建一个config包,创建一个类,实现WebMvcConfigurer接口,并重写addInterceptors方法
    1. 配置拦截器的拦截路径

1. 自定义拦截器:实现HandlerInterceptor接口,并重写其所有方法

在src/main/java/cn.yangeit(可以是自己的包)目录下创建一个config包,创建一个LoginCheckInterceptor类,实现HandlerInterceptor接口,并重写方法

//自定义拦截器
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
    //目标资源方法执行前执行。 返回true:放行    返回false:不放行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle .... ");
        
        return true; //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 .... ");
    }
}
 
 






 














注意:

​ preHandle方法:目标资源方法执行前执行。 返回true:放行 返回false:不放行

​ postHandle方法:目标资源方法执行后执行

​ afterCompletion方法:视图渲染完毕后执行,最后执行

2.注册配置拦截器 :实现WebMvcConfigurer接口,并重写addInterceptors方法

在src/main/java/cn.yangeit(可以是自己的包)目录下创建一个config包,创建一个WebConfig类,实现WebMvcConfigurer接口,并重写addInterceptors方法

@Configuration
public class WebConfig implements WebMvcConfigurer {

    //自定义的拦截器对象
    @Autowired
    private LoginCheckInterceptor loginCheckInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
       //注册自定义拦截器对象
        registry.addInterceptor(loginCheckInterceptor)
                .addPathPatterns("/**")//设置拦截器拦截的请求路径( /** 表示拦截所有请求)
                //设置不拦截的请求路径
                .excludePathPatterns("/customer/user/login","/customer/roomTypes","/error");
    }
}





 



 
 

 


重新启动SpringBoot服务,打开使用微信进行测试

image
image

接下来我们再来做一个测试:将拦截器中返回值改为false

再次发送请求,没有响应数据,说明请求被拦截了没有放行

image
image

有些路径不需要拦截,如登录,查看房型等接口,需要在拦截器中放行image

说明:

  1. /** 表示拦截所有请求
  2. preHandle方法:目标资源方法执行前执行。 返回true:放行 返回false:不放行
  3. 那么可以这样,如果登录了,就放行,没有登录,就拦截,并返回未登录的错误信息
  4. prehandle方法在运行在Controller之前,因此可以在这里解析token,解析成功后,将用户信息存到一个地方,给Controller使用

总结

课堂作业

  1. 什么是拦截器,拦截器有什么作用?🎤

1.2 拦截器开发 🎯

前言

可以利用拦截器的特性,完成一些公共操作,比如:权限校验、日志记录、事务控制、性能监控、参数校验等

image
image
  1. 在config包下导入如下类BaseContext后面讲解该类的作用
public class BaseContext {

    public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    //存入到threadlocal中
    public static void setCurrentId(Long id) {
        threadLocal.set(id);
    }

    //从threadlocal中获取
    public static Long getCurrentId() {
        return threadLocal.get();
    }

    //从threadlocal中删除
    public static void removeCurrentId() {
        threadLocal.remove();
    }

}
  1. 在LoginCheckInterceptor中书写代码,用来校验token,并解析出用户id,存入到BaseContext中
//自定义拦截器
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
    //目标资源方法执行前执行。 返回true:放行    返回false:不放行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle .... ");
//        获得当前请求的url
        String url = request.getRequestURI();
        System.out.println("获得当前请求的url: " + url);
        // //判断当前请求是否是handler()  controller中的一个方法
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        //获取token,并判断token是否为空
        // 开始隐藏,自己写,start









       // 开始隐藏,自己写,end
        //获取用户数据
        Claims claims = JwtUtils.parseJWT(token);
        Long userId = MapUtil.get(claims, "userId", Long.class);
        if (ObjectUtil.isEmpty(userId)) {
            AjaxResult error = AjaxResult.error("认证失败");
            String json = JSONUtil.toJsonStr(error);
            //设置响应头
            response.setContentType("application/json;charset=utf-8");
            //响应
            response.getWriter().write(json);
        }
        //存入到TheadLocal中
        BaseContext.setCurrentId(userId);
        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 .... ");
        BaseContext.removeCurrentId();
    }
}
















 
 
 
 
 
 
 
 
 
 
 












 













 



总结

课堂作业

  1. 本节主讲解的是利用拦截器实现token的校验,并解析出用户id,存入到TheadLocal中,那么TheadLocal有什么作用?🎤

1.3ThreadLocal应用场景 🎯

前言

介绍:

ThreadLocal 并不是一个Thread,而是Thread的局部变量。 ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果只有在线程内才能获取到对应的值,线程外则不能访问

常用方法:

  • public void set(T value) 设置当前线程的线程局部变量的值
  • public T get() 返回当前线程所对应的线程局部变量的值
  • public void remove() 移除当前线程的线程局部变量

对ThreadLocal有了一定认识后,接下来继续解决问题二

image-20221111212349365

接下来,来判断一下,拦截器和Controller是否一条线程中!!!

在拦截器中打印线程id,在Controller中打印线程id,如果一致,说明在同一个线程中,如果不一致,说明不在同一个线程中

image
image

通过日志,可以发现,同一个请求,拦截器和Controller在同一个线程中,所以,ThreadLocal解决数据从拦截器到Controller的传递

总结

课堂作业

  1. ThreadLocal有什么作用?🎤

1.4利用ThreadLocal优化代码 🎯

利用ThreadLocal优化代码

从上节课我们知道,拦截器中解析出来的用户id,存入到ThreadLocal中,那么在Controller中就可以通过ThreadLocal获取到用户id,从而实现用户id的传递

image
image

在FamilyMemberController,MemberNursingProjectController,MemberReservationController凡是用到userId的地方都可以调用BaseContext.getCurrentId()获取到用户id,从而实现用户id的传递,如下:

image
image

总结

课堂作业

  1. 参考上述讲义,完成优化代码,理解ThreadLocal的作用🎤

1.5异常处理 🎯

前言

那么在三层构架项目中,出现了异常,该如何处理?

image
image
image-20231206173822553
  • 方案一:在所有Controller的所有方法中进行try…catch处理

    • 缺点:代码臃肿(不推荐)
  • 方案二:全局异常处理器

    • 好处:简单、优雅(推荐)
image-20231206173923449

全局异常处理器代码操作

全局异常处理器

我们该怎么样定义全局异常处理器?

  • 定义全局异常处理器非常简单,就是定义一个类,在类上加上一个注解@RestControllerAdvice,加上这个注解就代表我们定义了一个全局异常处理器。
  • 在全局异常处理器当中,需要定义一个方法来捕获异常,在这个方法上需要加上注解@ExceptionHandler。通过@ExceptionHandler注解当中的value属性来指定我们要捕获的是哪一类型的异常。

cn.yangeit.config

@RestControllerAdvice
public class GlobalExceptionHandler {

    //处理异常
    @ExceptionHandler
    public AjaxResult ex(BaseException e){//方法形参中指定能够处理的异常类型
        e.printStackTrace();//打印堆栈中的异常信息
        //捕获到异常之后,响应一个标准的Result
        return AjaxResult.error(e.getMessage());
    }
}

然后可以导入一个通用的自定义异常到common包下:👇

/**
 * 业务异常
 */
public class BaseException extends RuntimeException {

    public BaseException() {
    }

    public BaseException(String msg) {
        super(msg);
    }

}

@RestControllerAdvice = @ControllerAdvice + @ResponseBody

处理异常的方法返回值会转换为json后再响应给前端

image
image

接下来,在需要异常处理的地方,全部抛出自定义异常,交给GGlobalExceptionHandler处理:👇如下,

image
image

总结

课堂作业

  1. 根据上述提示完成统一异常捕捉类的书写🎤

1.6分页查询护理服务列表 🎯

分页查询护理服务列表

接下来完成分页查询护理服务列表接口,接口信息如下:

  • 接口地址:/customer/orders/project/page
  • 请求方式:GET
  • 请求参数:
    • pageNum:当前页码,默认为1
    • pageSize:每页显示条数,默认为10
  • 返回结果:
{
    "total": 7,
    "rows": [
        {
            "id": 3,
            "name": "整理床铺",
            "orderNo": 1,
            "unit": "次",
            "price": 15,
            "image": "https://itheim.oss-cn-beijing.aliyuncs.com/e611fcc9-dc45-49ac-abeb-f2ea99c2cffc.png",
            "nursingRequirement": "无",
            "status": 1,
            "createBy": "1",
            "updateBy": null,
            "remark": null,
            "createTime": "2024-08-29T08:52:52.000+00:00",
            "updateTime": "2024-08-29T00:51:46.000+00:00"
        },
        {
            "id": 4,
            "name": "助餐",
            "orderNo": 1,
            "unit": "餐",
            "price": 15,
            "image": "https://itheim.oss-cn-beijing.aliyuncs.com/d91ba642-88e5-4c3d-8e50-a681ae3300e5.png",
            "nursingRequirement": "无",
            "status": 1,
            "createBy": "1",
            "updateBy": null,
            "remark": null,
            "createTime": "2024-08-29T08:53:29.000+00:00",
            "updateTime": "2024-08-29T00:52:24.000+00:00"
        }
    ],
    "code": 200,
    "msg": "请求成功"
}

核心代码:👇

image
image

最终效果图:

image
image

总结

课堂作业

  1. 参考上述代码,完成代码的书写🎤

1.7护理服务详情查询 🎯

护理服务详情查询

接下来完成护理服务详情查询接口,接口信息如下:

  • 接口地址:/customer/orders/project/{id}
  • 请求方式:GET
  • 请求参数:
    • id:护理服务编号
  • 返回结果:
{
    "msg": "操作成功",
    "code": 200,
    "data": {
        "id": 7,
        "name": "洗脸",
        "orderNo": 1,
        "unit": "次",
        "price": 15,
        "image": "https://itheim.oss-cn-beijing.aliyuncs.com/95b0ad37-5d61-4ec2-a961-d6fb691a18f0.png",
        "nursingRequirement": "无",
        "status": 1,
        "createBy": "1",
        "updateBy": null,
        "remark": null,
        "createTime": "2024-08-29T08:54:45.000+00:00",
        "updateTime": "2024-08-29T00:53:40.000+00:00"
    }
}

核心代码如下👇

@GetMapping("/project/{id}")
@ApiOperation("根据编号查询护理项目信息")
public AjaxResult getById(@PathVariable("id") Long id) {
    NursingProject nursingProject = nursingProjectMapper.selectById(id);
    return AjaxResult.success(nursingProject);
}

最终效果图:image