使用spring boot拦截器实现青少年模式
思路方法一:
便用Spring Boot拦截器采累计在线时间采实现青少年模式的步骤,可以参考以卜步骤:
1.创建一个拦截器类,实现Handlerlnterceptor 接口。
2.在拦截器类中,定义一个变量来记录用户在线时间。
3.在preHandle方法中,记录用户的登录时间。
4.在afterCompletion方法中,计算用户在线时间,并将其保存到数据库中。
这样就可以实现青少年模式了。当用户在线时间超过一定时长时,就可以提示用户休息一下,保护眼睛和身体健康。
思路方法二:
在用户类中添加一个属性loginTime,用于记录用户的登录时间。每次用户登录时,将该属性设置
为当前时间。每次用户退出或者Session销毁时,获取当前时间,并与loginTime相减,得到用户的
在线时长。
在数据库中添加一个表onlineTime,用于存储用户的在线时长。该表可以包含以下字段:userld
(用户ID)、totalTime(总在线时长)、dailyTime(每日在线时长)、date(日期)。
每次用户退出或者Session 销毁时,将用户的在线时长更新到onlineTime表中。如果该用户当天
已经有记录,则累加totalTime和dailyTime字段。如果该用户当天没有记录,则插入一条新记录,
并设置 totalTime和dailyTime字段为相同的值。
在前端展示用户的在线时长,可以使用Echarts或其他可视化工具,来绘制折线图、柱状图、饼图
等。你可以根据用户的需求,选择展示总在线时长、每日在线时长、每周在线时长、每月在线时长
等。
思路方法三:
我的搜索结果,spring boot拦截器是一种基于AOP的技术,可以用于对接口进行权限验证控制。青少
年模式是一种防止未成年人沉迷网络的系统,会在使用时段、时长、功能和浏览内容等方面对未成年人的
上网行为进行规范。您可以参考以下的步骤来实现这个功能:
1、创建一个拦截器类,实现Handlerlnterceptor接口,重写preHandle和postHandle方法
2、在preHandle方法中,获取请求的用户信息,判断是否是未成年人,如果是,则检查当前时间是否在
允许的使用时段内,如果不是,则返回false,拒绝请求,并提示用户进入青少年模式。
3、在postHandle方法中,获取请求的用户信息,判断是否是未成年人,如果是,则累计用户的在线时
间,并判断是否超过了允许的使用时长,如果超过了,则返回false,拒绝请求,并提示用户进入青少
年模式。
4、创建一个配置类,继承WebMvcConfigurerAdapter类,重写addlnterceptors方法,将拦截器注册到拦
截器链中,并指定拦截的路径
5、在application.properties文件中,配置青少年模式的相关参数,如使用时段、时长、功能和浏览内容
等。
6、在青少年模式下,限制用户的功能和浏览内容,如禁止直播、弹幕、评论、打赏等功能,过滤不良内
容等。
思路方法四:
1、利用AOP或拦截器来收集日志和统计方法执行时长的方法,你可以在切面类中定义一个ThreadLocal
绑定线程变量来存储每个请求的开始时间和结束时间,然后计算出执行时长,并记录到数据库或日志文件中。
2、给出了一种统计用户在线时长的方法,你可以在用户登录和退出时记录时间戳,然后根据用户的状态
(主动游戏或挂机)来累加时长,并定期更新到数据库中。
3、给出了一种利用监听事件来实现异步操作的方法,你可以在用户请求到达时发布一个事件,然后在事
件监听器中处理业务逻辑,并记录在线时长。
思路方法五:
1.创建一个自定义的拦截器类,实现Handlerlnterceptor接口
2.在拦截器类中,重写preHandle方法,在该方法中获取用户的登录信息和在线时间,并判断是否超过
青少年模式的限制,如果超过则返回false并跳转到提示页面,否则返回true并继续执行。
3.在拦截器类中,重写postHandle方法,在该方法中更新用户的在线时间,并保存到数据库或缓存中。
4.在拦截器类中,重写afterCompletion方法,在该方法中做一些清理工作,例如关闭数据库连接等。
5.在Spring Boot的配置类中,注册拦截器,并指定拦截的路径和排除的路径。
代码实现
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Optional;
/**
* 描述:TeenagerModeInterceptor
* 青少年模式拦截器
* 先判断是否开启,如果没有开启 -->>通过
* 如果开启了 --->> 从Redis判断有没有该key,
* 如果没有该key则创建,将用户id作为key后缀,将TeenagerMode_作为前缀,将当前时间作为value
* 如果有该key,(用当前时间-redis存放的时间)>=40分钟,如果大于等于,给出提示并删除该 redis key
*/
@Component
//只有加了这注解,热更新才生效
@RefreshScope
public class TeenagerModeInterceptor implements HandlerInterceptor {
/**
* spring 提供的命名ThreadLocal
* 它是线程绑定的变量,提供线程局部变量
* 我定义的泛型存储的是 System.currentTimeMillis()
* 优化点:可以把这两个整合成一个,泛型使用实体对象
*/
private static final NamedThreadLocal<Long> START_TIME_HOLDER = new NamedThreadLocal<>("StopWatch-StartTime");
private static final NamedThreadLocal<String> USER_HOLDER = new NamedThreadLocal<>("StopWatch-UserId");
/**
* 设置默认值为22
*/
@Value("${teenager.mode.after.time:22}")
private Integer teenagerModeAfterTime;
/**
* 设置默认值为6
*/
@Value("${teenager.mode.before.time:6}")
private Integer teenagerModeBeforeTime;
/**
* 接口统计总时间:单位毫秒
* 设置默认值为12万 即接口统计时长2分钟
*/
@Value("${teenager.mode.total.time:120000}")
private Integer teenagerModeTotalTime;
/**
* 在请求处理之前调用,返回 true 表示继续处理,返回 false 表示中断请求
*
* @param request current HTTP request
* @param response current HTTP response
* @param handler chosen handler to execute, for type and/or instance evaluation
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = ServletRequestUtil.getToken();
//未登录,放行
if (StringUtils.isBlank(token)) {
return true;
}
String userId = getUserId();
//从数据库中查找
ResultEntity<Boolean> resultEntity = xxxService.teenagerModeIsOpen(userId);
boolean open = resultEntity.getData();
if (!open) {
//如果没有开启,直接放行
return true;
} else {
//晚上10点至早上6点无法使用
//使用servletUtil 是为了能从参数中动态获取获取,方便测试
Integer after = ServletRequestUtil.getTeenagerModeAfterTime() == "" ? teenagerModeAfterTime : Integer.valueOf(ServletRequestUtil.getTeenagerModeAfterTime());
Integer before = ServletRequestUtil.getTeenagerModeBeforeTime() == "" ? teenagerModeBeforeTime : Integer.valueOf(ServletRequestUtil.getTeenagerModeBeforeTime());
if (LocalDateTime.now().isAfter(LocalDateTime.of(LocalDate.now(), LocalTime.of(after, 0, 0)))
|| LocalDateTime.now().isBefore(LocalDateTime.of(LocalDate.now(), LocalTime.of(before, 0, 0)))) {
//抛出你们项目中自定义的异常,这里直接抛出RuntimeException
throw new RuntimeException(xxxxx);
}
//如果开启了
//记录开始运行时间,在postHandle方法计算运行时间
START_TIME_HOLDER.set(System.currentTimeMillis());
//设置请求userId属性,方便在postHandle方法获取
//request.setAttribute("userId", userId);
USER_HOLDER.set(userId);
String total = redisClient.getTeenagerModeUserIdKey(userId);
//从Redis判断有没有该key,如果没有该key则创建
if (StringUtils.isBlank(total)) {
redisxxx.setTeenagerModeUserIdKey(userId,"0");
return true;
}
//判断总的时间是否已经大于设定值
if (Long.valueOf(total).longValue() >= teenagerModeTotalTime) {
//抛出你们项目中自定义的异常,这里直接抛出RuntimeException
throw new RuntimeException(xxxxx);
}
}
return true;
}
//一个是统计每个接口的运行时间、一个是【每天】累计总的时间
/**
* 在请求处理之后调用,但在视图渲染之前,即处理之后,返回响应之前
* 但在实际测试中,基本已经返回响应了,只是可以在响应数据里添加一些东西,但是会出现奇怪的东西
*
* @param request current HTTP request
* @param response current HTTP response
* @param handler the handler (or {@link HandlerMethod}) that started asynchronous
* execution, for type and/or instance examination
* @param modelAndView the {@code ModelAndView} that the handler returned
* (can also be {@code null})
* @throws Exception
* @date 2023年3月29日09:47:56
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 在这里可以对响应进行一些修改或添加额外的信息
Long startTime = START_TIME_HOLDER.get();
if (startTime == null) {
return;
}
String userId = Optional.of(USER_HOLDER.get()).orElse("");
if (userId == "") {
return;
}
//2400000 毫秒值
//接口运行时间
long interfaceRunTime = System.currentTimeMillis() - startTime.longValue();
//从redis 中获取总时间
String valueForUserTotalTime = redisxxx.getTeenagerModeUserIdKey(userId);
//如果没有该key对应的value
if (StringUtils.isBlank(valueForUserTotalTime)) {
redisxxx.setTeenagerModeUserIdKey(userId, String.valueOf(interfaceRunTime));
} else {
//原累计时长+接口运行时间
//teenagerModeTotalTime 单位毫秒
Long total = interfaceRunTime + Long.valueOf(valueForUserTotalTime).longValue();
//更新累计时间
redisxxx.setTeenagerModeUserIdKey(userId, String.valueOf(total));
}
}
/**
* 在整个请求结束之后调用,用于清理资源
*
* @param request current HTTP request
* @param response current HTTP response
* @param handler the handler (or {@link HandlerMethod}) that started asynchronous
* execution, for type and/or instance examination
* @param ex any exception thrown on handler execution, if any; this does not
* include exceptions that have been handled through an exception resolver
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 在这里可以释放 ThreadLocal 变量中的信息,避免内存泄漏
START_TIME_HOLDER.remove();
USER_HOLDER.remove();
}
}
为什么不在拦截器中使用request.setAttribute获取数据?
原因是一旦preHandle方法抛出异常,postHandle中的request对象就会为空,这是我踩过坑滴