Part07 中州养老AI+IOT项目实训
Part07 中州养老AI+IOT项目实训
1. 拦截器和ThreadLocal优化
1.1 拦截器介绍和入门 🎯
前言
拦截器:
- 是一种动态拦截方法调用的机制,类似于过滤器。
- 拦截器是Spring框架中提供的,用来动态拦截控制器方法的执行。
拦截器的作用:
- 拦截请求,在指定方法调用前后,根据业务需要执行预先设定的代码。

在拦截器当中,我们通常也是做一些通用性的操作,比如:我们可以通过拦截器来拦截前端发起的请求,将登录校验的逻辑全部编写在拦截器当中。在校验的过程当中,如发现用户登录了(携带JWT令牌且是合法令牌),就可以直接放行,去访问spring当中的资源。如果校验时发现并没有登录或是非法令牌,就可以直接给前端响应未登录的错误信息。
代码操作
集成步骤
- 自定义拦截器:实现HandlerInterceptor接口,并重写其所有方法
- 注册配置拦截器 :创建一个config包,创建一个类,实现WebMvcConfigurer接口,并重写addInterceptors方法
- 配置拦截器的拦截路径
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服务,打开使用微信进行测试

接下来我们再来做一个测试:将拦截器中返回值改为false
再次发送请求,没有响应数据,说明请求被拦截了没有放行

有些路径不需要拦截,如登录,查看房型等接口,需要在拦截器中放行
说明:
/** 表示拦截所有请求
- preHandle方法:目标资源方法执行前执行。 返回true:放行 返回false:不放行
- 那么可以这样,如果登录了,就放行,没有登录,就拦截,并返回未登录的错误信息
- prehandle方法在运行在Controller之前,因此可以在这里解析token,解析成功后,将用户信息存到一个地方,给Controller使用
总结
课堂作业
- 什么是拦截器,拦截器有什么作用?🎤
1.2 拦截器开发 🎯
前言
可以利用拦截器的特性,完成一些公共操作,比如:权限校验、日志记录、事务控制、性能监控、参数校验等

- 在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();
}
}
- 在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();
}
}
总结
课堂作业
- 本节主讲解的是利用拦截器实现token的校验,并解析出用户id,存入到TheadLocal中,那么TheadLocal有什么作用?🎤
1.3ThreadLocal应用场景 🎯
前言
介绍:
ThreadLocal 并不是一个Thread,而是Thread的局部变量。 ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
常用方法:
- public void set(T value) 设置当前线程的线程局部变量的值
- public T get() 返回当前线程所对应的线程局部变量的值
- public void remove() 移除当前线程的线程局部变量
对ThreadLocal有了一定认识后,接下来继续解决问题二

接下来,来判断一下,拦截器和Controller是否一条线程中!!!
在拦截器中打印线程id,在Controller中打印线程id,如果一致,说明在同一个线程中,如果不一致,说明不在同一个线程中

通过日志,可以发现,同一个请求,拦截器和Controller在同一个线程中,所以,ThreadLocal解决数据从拦截器到Controller的传递
总结
课堂作业
- ThreadLocal有什么作用?🎤
1.4利用ThreadLocal优化代码 🎯
利用ThreadLocal优化代码
从上节课我们知道,拦截器中解析出来的用户id,存入到ThreadLocal中,那么在Controller中就可以通过ThreadLocal获取到用户id,从而实现用户id的传递

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

总结
课堂作业
- 参考上述讲义,完成优化代码,理解ThreadLocal的作用🎤
1.5异常处理 🎯
前言
那么在三层构架项目中,出现了异常,该如何处理?


方案一:在所有Controller的所有方法中进行try…catch处理
- 缺点:代码臃肿(不推荐)
方案二:全局异常处理器
- 好处:简单、优雅(推荐)

全局异常处理器代码操作
全局异常处理器
我们该怎么样定义全局异常处理器?
- 定义全局异常处理器非常简单,就是定义一个类,在类上加上一个注解
@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后再响应给前端

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

总结
课堂作业
- 根据上述提示完成统一异常捕捉类的书写🎤
1.6分页查询护理服务列表 🎯
分页查询护理服务列表
接下来完成分页查询护理服务列表接口,接口信息如下:
- 接口地址:
/customer/orders/project/page
- 请求方式:
GET
- 请求参数:
pageNum
:当前页码,默认为1pageSize
:每页显示条数,默认为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": "请求成功"
}
核心代码:👇

最终效果图:

总结
课堂作业
- 参考上述代码,完成代码的书写🎤
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);
}
最终效果图: