使用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对象就会为空,这是我踩过坑滴

你可能感兴趣的:(spring,boot,后端,java,拦截器,青少年模式)